Skip to content

Commit b1b4806

Browse files
cristiccJiri Kosina
authored andcommitted
HID: playstation: Support DualSense audio jack hotplug detection
The default audio output path on DualSense controller hardware is set to headphones, regardless of whether they are actually inserted or not. Detect when the plugged state of the 3.5mm audio jack changes and toggle audio output between headphones and internal speaker, as required. The latter is achieved by essentially routing the right channel of the audio source to the mono speaker. Additionally, adjust the speaker volume since its default level is too low and, therefore, cannot generate any audible sound. It's worth noting the audio functionality is currently not supported for Bluetooth, hence it's limited to USB connectivity. Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com> Tested-by: Benjamin Tissoires <bentiss@kernel.org> Signed-off-by: Jiri Kosina <jkosina@suse.com>
1 parent d7b744f commit b1b4806

1 file changed

Lines changed: 88 additions & 3 deletions

File tree

drivers/hid/hid-playstation.c

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,13 @@ struct ps_led_info {
112112
#define DS_BUTTONS2_TOUCHPAD BIT(1)
113113
#define DS_BUTTONS2_MIC_MUTE BIT(2)
114114

115-
/* Battery status field of DualSense input report. */
115+
/* Status fields of DualSense input report. */
116116
#define DS_STATUS0_BATTERY_CAPACITY GENMASK(3, 0)
117117
#define DS_STATUS0_CHARGING GENMASK(7, 4)
118+
#define DS_STATUS1_HP_DETECT BIT(0)
119+
#define DS_STATUS1_MIC_DETECT BIT(1)
120+
#define DS_STATUS1_JACK_DETECT (DS_STATUS1_HP_DETECT | DS_STATUS1_MIC_DETECT)
121+
#define DS_STATUS1_MIC_MUTE BIT(2)
118122

119123
/* Feature version from DualSense Firmware Info report. */
120124
#define DS_FEATURE_VERSION_MINOR GENMASK(7, 0)
@@ -143,13 +147,19 @@ struct ps_led_info {
143147
/* Flags for DualSense output report. */
144148
#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0)
145149
#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1)
150+
#define DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE BIT(5)
151+
#define DS_OUTPUT_VALID_FLAG0_MIC_VOLUME_ENABLE BIT(6)
152+
#define DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE BIT(7)
146153
#define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0)
147154
#define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1)
148155
#define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2)
149156
#define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3)
150157
#define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4)
158+
#define DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE BIT(7)
151159
#define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1)
152160
#define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2)
161+
#define DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL GENMASK(5, 4)
162+
#define DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN GENMASK(2, 0)
153163
#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4)
154164
#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1)
155165

@@ -192,6 +202,11 @@ struct dualsense {
192202
u8 lightbar_green;
193203
u8 lightbar_blue;
194204

205+
/* Audio Jack plugged state */
206+
u8 plugged_state;
207+
u8 prev_plugged_state;
208+
bool prev_plugged_state_valid;
209+
195210
/* Microphone */
196211
bool update_mic_mute;
197212
bool mic_muted;
@@ -251,11 +266,15 @@ struct dualsense_output_report_common {
251266
u8 motor_left;
252267

253268
/* Audio controls */
254-
u8 reserved[4];
269+
u8 headphone_volume; /* 0x0 - 0x7f */
270+
u8 speaker_volume; /* 0x0 - 0xff */
271+
u8 mic_volume; /* 0x0 - 0x40 */
272+
u8 audio_control;
255273
u8 mute_button_led;
256274

257275
u8 power_save_control;
258-
u8 reserved2[28];
276+
u8 reserved2[27];
277+
u8 audio_control2;
259278

260279
/* LEDs and lightbar */
261280
u8 valid_flag2;
@@ -1303,6 +1322,46 @@ static void dualsense_output_worker(struct work_struct *work)
13031322
ds->update_player_leds = false;
13041323
}
13051324

1325+
if (ds->plugged_state != ds->prev_plugged_state) {
1326+
u8 val = ds->plugged_state & DS_STATUS1_HP_DETECT;
1327+
1328+
if (val != (ds->prev_plugged_state & DS_STATUS1_HP_DETECT)) {
1329+
common->valid_flag0 = DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE;
1330+
/*
1331+
* _--------> Output path setup in audio_flag0
1332+
* / _------> Headphone (HP) Left channel sink
1333+
* | / _----> Headphone (HP) Right channel sink
1334+
* | | / _--> Internal Speaker (SP) sink
1335+
* | | | /
1336+
* | | | | L/R - Left/Right channel source
1337+
* 0 L-R X X - Unrouted (muted) channel source
1338+
* 1 L-L X
1339+
* 2 L-L R
1340+
* 3 X-X R
1341+
*/
1342+
if (val) {
1343+
/* Mute SP and route L+R channels to HP */
1344+
common->audio_control = 0;
1345+
} else {
1346+
/* Mute HP and route R channel to SP */
1347+
common->audio_control =
1348+
FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL, 0x3);
1349+
/*
1350+
* Set SP hardware volume to 100%.
1351+
* Note the accepted range seems to be [0x3d..0x64]
1352+
*/
1353+
common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE;
1354+
common->speaker_volume = 0x64;
1355+
/* Set SP preamp gain to ~30% */
1356+
common->valid_flag1 = DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE;
1357+
common->audio_control2 =
1358+
FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN, 0x2);
1359+
}
1360+
}
1361+
1362+
ds->prev_plugged_state = ds->plugged_state;
1363+
}
1364+
13061365
if (ds->update_mic_mute) {
13071366
common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE;
13081367
common->mute_button_led = ds->mic_muted;
@@ -1406,6 +1465,32 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
14061465
}
14071466
ds->last_btn_mic_state = btn_mic_state;
14081467

1468+
/*
1469+
* Parse HP/MIC plugged state data for USB use case, since Bluetooth
1470+
* audio is currently not supported.
1471+
*/
1472+
if (hdev->bus == BUS_USB) {
1473+
value = ds_report->status[1] & DS_STATUS1_JACK_DETECT;
1474+
1475+
if (!ds->prev_plugged_state_valid) {
1476+
/* Initial handling of the plugged state report */
1477+
scoped_guard(spinlock_irqsave, &ps_dev->lock) {
1478+
ds->plugged_state = (~value) & DS_STATUS1_JACK_DETECT;
1479+
ds->prev_plugged_state_valid = true;
1480+
}
1481+
}
1482+
1483+
if (value != ds->plugged_state) {
1484+
scoped_guard(spinlock_irqsave, &ps_dev->lock) {
1485+
ds->prev_plugged_state = ds->plugged_state;
1486+
ds->plugged_state = value;
1487+
}
1488+
1489+
/* Schedule audio routing towards active endpoint. */
1490+
dualsense_schedule_work(ds);
1491+
}
1492+
}
1493+
14091494
/* Parse and calibrate gyroscope data. */
14101495
for (i = 0; i < ARRAY_SIZE(ds_report->gyro); i++) {
14111496
int raw_data = (short)le16_to_cpu(ds_report->gyro[i]);

0 commit comments

Comments
 (0)