@@ -857,6 +857,8 @@ config HID_PLAYSTATION
tristate "PlayStation HID Driver"
depends on HID
select CRC32
+ select NEW_LEDS
+ select LEDS_CLASS
select LEDS_CLASS_MULTICOLOR
select POWER_SUPPLY
help
@@ -10,6 +10,7 @@
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/input/mt.h>
+#include <linux/leds.h>
#include <linux/led-class-multicolor.h>
#include <linux/module.h>
@@ -47,6 +48,12 @@ struct ps_calibration_data {
int sens_denom;
};
+struct ps_led_info {
+ const char *name;
+ enum led_brightness (*brightness_get)(struct led_classdev *cdev);
+ void (*brightness_set)(struct led_classdev *cdev, enum led_brightness);
+};
+
/* Seed values for DualShock4 / DualSense CRC32 for different report types. */
#define PS_INPUT_CRC32_SEED 0xA1
#define PS_OUTPUT_CRC32_SEED 0xA2
@@ -82,6 +89,7 @@ struct ps_calibration_data {
#define DS_BUTTONS1_R3 BIT(7)
#define DS_BUTTONS2_PS_HOME BIT(0)
#define DS_BUTTONS2_TOUCHPAD BIT(1)
+#define DS_BUTTONS2_MIC_MUTE BIT(2)
/* Status field of DualSense input report. */
#define DS_STATUS_BATTERY_CAPACITY GENMASK(3, 0)
@@ -100,9 +108,12 @@ struct ps_calibration_data {
/* 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_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_FLAG2_LIGHTBAR_SETUP_CONTROL_ENABLE BIT(1)
+#define DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE BIT(4)
#define DS_OUTPUT_LIGHTBAR_SETUP_LIGHT_OUT BIT(1)
/* DualSense hardware limits */
@@ -140,6 +151,12 @@ struct dualsense {
uint8_t lightbar_green;
uint8_t lightbar_blue;
+ /* Microphone */
+ bool update_mic_mute;
+ bool mic_muted;
+ bool last_btn_mic_state;
+ struct led_classdev mute_led;
+
struct work_struct output_worker;
void *output_report_dmabuf;
uint8_t output_seq; /* Sequence number for output report. */
@@ -476,6 +493,32 @@ static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *bu
return 0;
}
+static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led,
+ const struct ps_led_info *led_info)
+{
+ int ret;
+
+ led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
+ "playstation::%pMR::%s", ps_dev->mac_address, led_info->name);
+
+ if (!led->name)
+ return -ENOMEM;
+
+ led->brightness = 0;
+ led->max_brightness = 1;
+ led->flags = LED_CORE_SUSPENDRESUME;
+ led->brightness_get = led_info->brightness_get;
+ led->brightness_set = led_info->brightness_set;
+
+ ret = devm_led_classdev_register(&ps_dev->hdev->dev, led);
+ if (ret) {
+ hid_err(ps_dev->hdev, "Failed to register LED %s: %d\n", led_info->name, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
/* Register a DualSense/DualShock4 RGB lightbar represented by a multicolor LED. */
static int ps_lightbar_register(struct ps_device *ps_dev, struct led_classdev_mc *lightbar_mc_dev,
int (*brightness_set)(struct led_classdev *, enum led_brightness))
@@ -710,6 +753,19 @@ static int dualsense_lightbar_set_brightness(struct led_classdev *cdev,
return 0;
}
+static enum led_brightness dualsense_mute_led_get_brightness(struct led_classdev *led)
+{
+ struct dualsense *ds = container_of(led, struct dualsense, mute_led);
+
+ return ds->mic_muted;
+}
+
+/* The mute LED is treated as read-only. This set call prevents ENOTSUP errors e.g. on unload. */
+static void dualsense_mute_led_set_brightness(struct led_classdev *led, enum led_brightness value)
+{
+
+}
+
static void dualsense_init_output_report(struct dualsense *ds, struct dualsense_output_report *rp,
void *buf)
{
@@ -802,6 +858,23 @@ static void dualsense_output_worker(struct work_struct *work)
ds->update_lightbar = false;
}
+ if (ds->update_mic_mute) {
+ common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_MIC_MUTE_LED_CONTROL_ENABLE;
+ common->mute_button_led = ds->mic_muted;
+
+ if (ds->mic_muted) {
+ /* Disable microphone */
+ common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
+ common->power_save_control |= DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
+ } else {
+ /* Enable microphone */
+ common->valid_flag1 |= DS_OUTPUT_VALID_FLAG1_POWER_SAVE_CONTROL_ENABLE;
+ common->power_save_control &= ~DS_OUTPUT_POWER_SAVE_CONTROL_MIC_MUTE;
+ }
+
+ ds->update_mic_mute = false;
+ }
+
spin_unlock_irqrestore(&ds->base.lock, flags);
dualsense_send_output_report(ds, &report);
@@ -816,6 +889,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
uint8_t battery_data, battery_capacity, charging_status, value;
int battery_status;
uint32_t sensor_timestamp;
+ bool btn_mic_state;
unsigned long flags;
int i;
@@ -871,6 +945,23 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
input_report_key(ds->gamepad, BTN_MODE, ds_report->buttons[2] & DS_BUTTONS2_PS_HOME);
input_sync(ds->gamepad);
+ /*
+ * The DualSense has an internal microphone, which can be muted through a mute button
+ * on the device. The driver is expected to read the button state and program the device
+ * to mute/unmute audio at the hardware level.
+ */
+ btn_mic_state = !!(ds_report->buttons[2] & DS_BUTTONS2_MIC_MUTE);
+ if (btn_mic_state && !ds->last_btn_mic_state) {
+ spin_lock_irqsave(&ps_dev->lock, flags);
+ ds->update_mic_mute = true;
+ ds->mic_muted = !ds->mic_muted; /* toggle */
+ spin_unlock_irqrestore(&ps_dev->lock, flags);
+
+ /* Schedule updating of microphone state at hardware level. */
+ schedule_work(&ds->output_worker);
+ }
+ ds->last_btn_mic_state = btn_mic_state;
+
/* 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]);
@@ -1018,6 +1109,10 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
uint8_t max_output_report_size;
int ret;
+ static const struct ps_led_info mute_led_info = {
+ "micmute", dualsense_mute_led_get_brightness, dualsense_mute_led_set_brightness
+ };
+
ds = devm_kzalloc(&hdev->dev, sizeof(*ds), GFP_KERNEL);
if (!ds)
return ERR_PTR(-ENOMEM);
@@ -1095,6 +1190,10 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
if (ret)
goto err;
+ ret = ps_led_register(ps_dev, &ds->mute_led, &mute_led_info);
+ if (ret)
+ goto err;
+
return &ds->base;
err: