diff options
| -rw-r--r-- | drivers/hid/hid-playstation.c | 91 |
1 files changed, 88 insertions, 3 deletions
diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index f523ca989a2f..4285260c7e22 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -112,9 +112,13 @@ struct ps_led_info { #define DS_BUTTONS2_TOUCHPAD BIT(1) #define DS_BUTTONS2_MIC_MUTE BIT(2) -/* Battery status field of DualSense input report. */ +/* Status fields of DualSense input report. */ #define DS_STATUS0_BATTERY_CAPACITY GENMASK(3, 0) #define DS_STATUS0_CHARGING GENMASK(7, 4) +#define DS_STATUS1_HP_DETECT BIT(0) +#define DS_STATUS1_MIC_DETECT BIT(1) +#define DS_STATUS1_JACK_DETECT (DS_STATUS1_HP_DETECT | DS_STATUS1_MIC_DETECT) +#define DS_STATUS1_MIC_MUTE BIT(2) /* Feature version from DualSense Firmware Info report. */ #define DS_FEATURE_VERSION_MINOR GENMASK(7, 0) @@ -143,13 +147,19 @@ struct ps_led_info { /* Flags for DualSense output report. */ #define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0) #define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1) +#define DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE BIT(5) +#define DS_OUTPUT_VALID_FLAG0_MIC_VOLUME_ENABLE BIT(6) +#define DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE BIT(7) #define DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE BIT(0) #define DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE BIT(1) #define DS_OUTPUT_VALID_FLAG1_LIGHTBAR_CONTROL_ENABLE BIT(2) #define DS_OUTPUT_VALID_FLAG1_RELEASE_LEDS BIT(3) #define DS_OUTPUT_VALID_FLAG1_PLAYER_INDICATOR_CONTROL_ENABLE BIT(4) +#define DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE BIT(7) #define DS_OUTPUT_VALID_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1) #define DS_OUTPUT_VALID_FLAG2_COMPATIBLE_VIBRATION2 BIT(2) +#define DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL GENMASK(5, 4) +#define DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN GENMASK(2, 0) #define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4) #define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1) @@ -192,6 +202,11 @@ struct dualsense { u8 lightbar_green; u8 lightbar_blue; + /* Audio Jack plugged state */ + u8 plugged_state; + u8 prev_plugged_state; + bool prev_plugged_state_valid; + /* Microphone */ bool update_mic_mute; bool mic_muted; @@ -251,11 +266,15 @@ struct dualsense_output_report_common { u8 motor_left; /* Audio controls */ - u8 reserved[4]; + u8 headphone_volume; /* 0x0 - 0x7f */ + u8 speaker_volume; /* 0x0 - 0xff */ + u8 mic_volume; /* 0x0 - 0x40 */ + u8 audio_control; u8 mute_button_led; u8 power_save_control; - u8 reserved2[28]; + u8 reserved2[27]; + u8 audio_control2; /* LEDs and lightbar */ u8 valid_flag2; @@ -1303,6 +1322,46 @@ static void dualsense_output_worker(struct work_struct *work) ds->update_player_leds = false; } + if (ds->plugged_state != ds->prev_plugged_state) { + u8 val = ds->plugged_state & DS_STATUS1_HP_DETECT; + + if (val != (ds->prev_plugged_state & DS_STATUS1_HP_DETECT)) { + common->valid_flag0 = DS_OUTPUT_VALID_FLAG0_AUDIO_CONTROL_ENABLE; + /* + * _--------> Output path setup in audio_flag0 + * / _------> Headphone (HP) Left channel sink + * | / _----> Headphone (HP) Right channel sink + * | | / _--> Internal Speaker (SP) sink + * | | | / + * | | | | L/R - Left/Right channel source + * 0 L-R X X - Unrouted (muted) channel source + * 1 L-L X + * 2 L-L R + * 3 X-X R + */ + if (val) { + /* Mute SP and route L+R channels to HP */ + common->audio_control = 0; + } else { + /* Mute HP and route R channel to SP */ + common->audio_control = + FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS_OUTPUT_PATH_SEL, 0x3); + /* + * Set SP hardware volume to 100%. + * Note the accepted range seems to be [0x3d..0x64] + */ + common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_SPEAKER_VOLUME_ENABLE; + common->speaker_volume = 0x64; + /* Set SP preamp gain to ~30% */ + common->valid_flag1 = DS_OUTPUT_VALID_FLAG1_AUDIO_CONTROL2_ENABLE; + common->audio_control2 = + FIELD_PREP(DS_OUTPUT_AUDIO_FLAGS2_SP_PREAMP_GAIN, 0x2); + } + } + + ds->prev_plugged_state = ds->plugged_state; + } + if (ds->update_mic_mute) { common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE; 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 } ds->last_btn_mic_state = btn_mic_state; + /* + * Parse HP/MIC plugged state data for USB use case, since Bluetooth + * audio is currently not supported. + */ + if (hdev->bus == BUS_USB) { + value = ds_report->status[1] & DS_STATUS1_JACK_DETECT; + + if (!ds->prev_plugged_state_valid) { + /* Initial handling of the plugged state report */ + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ds->plugged_state = (~value) & DS_STATUS1_JACK_DETECT; + ds->prev_plugged_state_valid = true; + } + } + + if (value != ds->plugged_state) { + scoped_guard(spinlock_irqsave, &ps_dev->lock) { + ds->prev_plugged_state = ds->plugged_state; + ds->plugged_state = value; + } + + /* Schedule audio routing towards active endpoint. */ + dualsense_schedule_work(ds); + } + } + /* Parse and calibrate gyroscope data. */ for (i = 0; i < ARRAY_SIZE(ds_report->gyro); i++) { int raw_data = (short)le16_to_cpu(ds_report->gyro[i]); |
