diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index b96abb1a3f50..1bba50925201 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -807,6 +807,17 @@ config HID_PLAYSTATION its special functionalities e.g. touchpad, lights and motion sensors. +config PLAYSTATION_FF + bool "PlayStation force feedback support" + depends on HID_PLAYSTATION + select INPUT_FF_MEMLESS + help + Provides the force feedback support for Playstation game + controllers. + + Say Y here if you would like to enable force feedback support for + PlayStation game controllers. + config HID_PRIMAX tristate "Primax non-fully HID-compliant devices" depends on HID diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index a805a0d60fff..89ae898eb3fa 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -37,12 +37,17 @@ struct ps_device { /* Seed values for DualShock4 / DualSense CRC32 for different report types. */ #define PS_INPUT_CRC32_SEED 0xA1 +#define PS_OUTPUT_CRC32_SEED 0xA2 #define PS_FEATURE_CRC32_SEED 0xA3 #define DS_INPUT_REPORT_USB 0x01 #define DS_INPUT_REPORT_USB_SIZE 64 #define DS_INPUT_REPORT_BT 0x31 #define DS_INPUT_REPORT_BT_SIZE 78 +#define DS_OUTPUT_REPORT_USB 0x02 +#define DS_OUTPUT_REPORT_USB_SIZE 63 +#define DS_OUTPUT_REPORT_BT 0x31 +#define DS_OUTPUT_REPORT_BT_SIZE 78 #define DS_FEATURE_REPORT_PAIRING_INFO 0x09 #define DS_FEATURE_REPORT_PAIRING_INFO_SIZE 20 @@ -76,6 +81,13 @@ struct ps_device { + */ #define DS_TOUCH_POINT_INACTIVE BIT(7) +/* Magic value required in tag field of Bluetooth output report. */ +#define DS_OUTPUT_TAG 0x10 +/* Flags for DualSense output report. */ +#define DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION BIT(0) +#define DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT BIT(1) + + /* DualSense hardware limits */ #define DS_TOUCHPAD_WIDTH 1920 #define DS_TOUCHPAD_HEIGHT 1080 @@ -84,6 +96,14 @@ struct dualsense { struct ps_device base; struct input_dev *gamepad; struct input_dev *touchpad; + + /* Compatible rumble state */ + bool update_rumble; + uint8_t motor_left; + uint8_t motor_right; + struct work_struct output_worker; + void *output_report_dmabuf; + uint8_t output_seq; /* Sequence number for output report. */ }; struct dualsense_touch_point { @@ -93,6 +113,68 @@ struct dualsense_touch_point { uint8_t y_hi; } __packed; +/* Common data between DualSense BT/USB main output report. */ +struct dualsense_output_report_common { + uint8_t valid_flag0; + uint8_t valid_flag1; + + /* For DualShock 4 compatibility mode. */ + uint8_t motor_right; + uint8_t motor_left; + + /* Audio controls */ + uint8_t reserved[4]; + uint8_t mute_button_led; + + uint8_t power_save_control; + uint8_t reserved2[28]; + + /* LEDs and lightbar */ + uint8_t valid_flag2; + uint8_t reserved3[2]; + uint8_t lightbar_setup; + uint8_t led_brightness; + uint8_t player_leds; + uint8_t lightbar_red; + uint8_t lightbar_green; + uint8_t lightbar_blue; +} __packed; + +struct dualsense_output_report_bt { + uint8_t report_id; /* 0x31 */ + uint8_t seq_tag; + uint8_t tag; + struct dualsense_output_report_common common; + uint8_t reserved[24]; + __le32 crc32; +} __packed; + +struct dualsense_output_report_usb { + uint8_t report_id; /* 0x02 */ + struct dualsense_output_report_common common; + uint8_t reserved[15]; +} __packed; + +/* + * The DualSense has a main output report used to control most features. + * It is largely the same between Bluetooth and USB except for different + * headers and CRC. This structure hide the differences between the two to + * simplify sending output reports. + */ +struct dualsense_output_report { + uint8_t *data; /* Start of data */ + uint8_t len; /* Size of output report */ + + /* Points to Bluetooth data payload + * in case for a Bluetooth report else NULL. + */ + struct dualsense_output_report_bt *bt; + /* Points to USB data payload in case for a USB report else NULL. */ + struct dualsense_output_report_usb *usb; + /* Points to common section of report, so past any headers. */ + struct dualsense_output_report_common *common; +}; + /* Main DualSense input report excluding any BT/USB specific headers. */ struct dualsense_input_report { uint8_t x, y; @@ -288,7 +370,8 @@ static int ps_device_register_battery(struct ps_device *dev) return 0; } -static struct input_dev *ps_gamepad_create(struct hid_device *hdev) +static struct input_dev *ps_gamepad_create(struct hid_device *hdev, + int (*play_effect)(struct input_dev *, void *, struct ff_effect *)) { struct input_dev *gamepad; unsigned int i; @@ -311,6 +394,13 @@ static struct input_dev *ps_gamepad_create(struct hid_device *hdev) for (i = 0; i < ARRAY_SIZE(ps_gamepad_buttons); i++) input_set_capability(gamepad, EV_KEY, ps_gamepad_buttons[i]); +#if IS_ENABLED(CONFIG_PLAYSTATION_FF) + if (play_effect) { + input_set_capability(gamepad, EV_FF, FF_RUMBLE); + input_ff_create_memless(gamepad, NULL, play_effect); + } +#endif + ret = input_register_device(gamepad); if (ret) return ERR_PTR(ret); @@ -318,24 +408,25 @@ static struct input_dev *ps_gamepad_create(struct hid_device *hdev) return gamepad; } -static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *buf, size_t size) +static int ps_get_report(struct hid_device *hdev, uint8_t report_id, + uint8_t *buf, size_t size) { int ret; ret = hid_hw_raw_request(hdev, report_id, buf, size, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) { - hid_err(hdev, "Failed to retrieve feature with reportID %d: %d\n", report_id, ret); + hid_err(hdev, "Failed to retrieve: reportID %d: %d\n", report_id, ret); return ret; } if (ret != size) { - hid_err(hdev, "Invalid byte count transferred, expected %zu got %d\n", size, ret); + hid_err(hdev, "Invalid byte count, expected %zu got %d\n", size, ret); return -EINVAL; } if (buf[0] != report_id) { - hid_err(hdev, "Invalid reportID received, expected %d got %d\n", report_id, buf[0]); + hid_err(hdev, "Invalid reportID: expected %d got %d\n", report_id, buf[0]); return -EINVAL; } @@ -393,7 +484,7 @@ static int dualsense_get_mac_address(struct dualsense *ds) ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_PAIRING_INFO, buf, DS_FEATURE_REPORT_PAIRING_INFO_SIZE); if (ret) { - hid_err(ds->base.hdev, "Failed to retrieve DualSense pairing info: %d\n", ret); + hid_err(ds->base.hdev, "Failed to retrieve DualSense pair: %d\n", ret); goto err_free; } @@ -404,8 +495,101 @@ static int dualsense_get_mac_address(struct dualsense *ds) return ret; } -static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *report, - u8 *data, int size) +static void dualsense_init_output_report(struct dualsense *ds, + struct dualsense_output_report *rp, void *buf) +{ + struct hid_device *hdev = ds->base.hdev; + + if (hdev->bus == BUS_BLUETOOTH) { + struct dualsense_output_report_bt *bt = buf; + + memset(bt, 0, sizeof(*bt)); + bt->report_id = DS_OUTPUT_REPORT_BT; + bt->tag = DS_OUTPUT_TAG; /* Tag to be set. It is unclear. */ + + /* + * Highest 4-bit is a sequence number, which needs to be + * increased every report. Lowest 4-bit is tag and can be + * zero for now. + */ + bt->seq_tag = (ds->output_seq << 4) | 0x0; + if (++ds->output_seq == 16) + ds->output_seq = 0; + + rp->data = buf; + rp->len = sizeof(*bt); + rp->bt = bt; + rp->usb = NULL; + rp->common = &bt->common; + } else { /* USB */ + struct dualsense_output_report_usb *usb = buf; + + memset(usb, 0, sizeof(*usb)); + usb->report_id = DS_OUTPUT_REPORT_USB; + + rp->data = buf; + rp->len = sizeof(*usb); + rp->bt = NULL; + rp->usb = usb; + rp->common = &usb->common; + } +} + +/* + * Helper function to send DualSense output reports. Applies a CRC + * at the end of a report for Bluetooth reports. + */ +static void dualsense_send_output_report(struct dualsense *ds, + struct dualsense_output_report *report) +{ + struct hid_device *hdev = ds->base.hdev; + + /* Bluetooth packets need to be signed + * with a CRC in the last 4 bytes. + */ + if (report->bt) { + uint32_t crc; + uint8_t seed = PS_OUTPUT_CRC32_SEED; + + crc = crc32_le(0xFFFFFFFF, &seed, 1); + crc = ~crc32_le(crc, report->data, report->len - 4); + + report->bt->crc32 = cpu_to_le32(crc); + } + + hid_hw_output_report(hdev, report->data, report->len); +} + +static void dualsense_output_worker(struct work_struct *work) +{ + struct dualsense *ds = container_of(work, struct dualsense, + output_worker); + struct dualsense_output_report report; + struct dualsense_output_report_common *common; + unsigned long flags; + + dualsense_init_output_report(ds, &report, ds->output_report_dmabuf); + common = report.common; + + spin_lock_irqsave(&ds->base.lock, flags); + + if (ds->update_rumble) { + /* Select classic rumble style haptics and enable it. */ + common->valid_flag0 |= DS_OUTPUT_VALID_FLAG0_HAPTICS_SELECT; + common->valid_flag0 |= + DS_OUTPUT_VALID_FLAG0_COMPATIBLE_VIBRATION; + common->motor_left = ds->motor_left; + common->motor_right = ds->motor_right; + ds->update_rumble = false; + } + + spin_unlock_irqrestore(&ds->base.lock, flags); + + dualsense_send_output_report(ds, &report); +} + +static int dualsense_parse_report(struct ps_device *ps_dev, + struct hid_report *report, u8 *data, int size) { struct hid_device *hdev = ps_dev->hdev; struct dualsense *ds = container_of(ps_dev, struct dualsense, base); @@ -423,12 +607,14 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r if (hdev->bus == BUS_USB && report->id == DS_INPUT_REPORT_USB && size == DS_INPUT_REPORT_USB_SIZE) { ds_report = (struct dualsense_input_report *)&data[1]; - } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS_INPUT_REPORT_BT && + } else if (hdev->bus == BUS_BLUETOOTH && + report->id == DS_INPUT_REPORT_BT && size == DS_INPUT_REPORT_BT_SIZE) { /* Last 4 bytes of input report contain crc32 */ uint32_t report_crc = get_unaligned_le32(&data[size - 4]); - if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) { + if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, + report_crc)) { hid_err(hdev, "DualSense input CRC's check failed\n"); return -EILSEQ; } @@ -525,10 +711,31 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r return 0; } +static int dualsense_play_effect(struct input_dev *dev, + void *data, struct ff_effect *effect) +{ + struct hid_device *hdev = input_get_drvdata(dev); + struct dualsense *ds = hid_get_drvdata(hdev); + unsigned long flags; + + if (effect->type != FF_RUMBLE) + return 0; + + spin_lock_irqsave(&ds->base.lock, flags); + ds->update_rumble = true; + ds->motor_left = effect->u.rumble.strong_magnitude / 256; + ds->motor_right = effect->u.rumble.weak_magnitude / 256; + spin_unlock_irqrestore(&ds->base.lock, flags); + + schedule_work(&ds->output_worker); + return 0; +} + static struct ps_device *dualsense_create(struct hid_device *hdev) { struct dualsense *ds; struct ps_device *ps_dev; + uint8_t max_output_report_size; int ret; ds = devm_kzalloc(&hdev->dev, sizeof(*ds), GFP_KERNEL); @@ -547,8 +754,15 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) ps_dev->battery_capacity = 100; /* initial value until parse_report. */ ps_dev->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; ps_dev->parse_report = dualsense_parse_report; + INIT_WORK(&ds->output_worker, dualsense_output_worker); hid_set_drvdata(hdev, ds); + max_output_report_size = sizeof(struct dualsense_output_report_bt); + ds->output_report_dmabuf = devm_kzalloc(&hdev->dev, + max_output_report_size, GFP_KERNEL); + if (!ds->output_report_dmabuf) + return ERR_PTR(-ENOMEM); + ret = dualsense_get_mac_address(ds); if (ret) { hid_err(hdev, "Failed to get MAC address from DualSense\n"); @@ -560,13 +774,14 @@ static struct ps_device *dualsense_create(struct hid_device *hdev) if (ret) return ERR_PTR(ret); - ds->gamepad = ps_gamepad_create(hdev); + ds->gamepad = ps_gamepad_create(hdev, dualsense_play_effect); if (IS_ERR(ds->gamepad)) { ret = PTR_ERR(ds->gamepad); goto err; } - ds->touchpad = ps_touchpad_create(hdev, DS_TOUCHPAD_WIDTH, DS_TOUCHPAD_HEIGHT, 2); + ds->touchpad = ps_touchpad_create(hdev, DS_TOUCHPAD_WIDTH, + DS_TOUCHPAD_HEIGHT, 2); if (IS_ERR(ds->touchpad)) { ret = PTR_ERR(ds->touchpad); goto err;