Message ID | 20210102223109.996781-2-roderick@gaikai.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | HID: new driver for PS5 'DualSense' controller | expand |
On Sat, 2 Jan 2021, Roderick Colenbrander wrote: > From: Roderick Colenbrander <roderick.colenbrander@sony.com> > > Implement support for PlayStation DualSense gamepad in USB mode. > Support features include buttons and sticks, which adhere to the > Linux gamepad spec. > > Signed-off-by: Roderick Colenbrander <roderick.colenbrander@sony.com> > --- > MAINTAINERS | 6 + > drivers/hid/Kconfig | 9 + > drivers/hid/Makefile | 1 + > drivers/hid/hid-ids.h | 1 + > drivers/hid/hid-playstation.c | 321 ++++++++++++++++++++++++++++++++++ > drivers/hid/hid-quirks.c | 3 + > 6 files changed, 341 insertions(+) > create mode 100644 drivers/hid/hid-playstation.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index f81d598a8556..0ecae30af074 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -7918,6 +7918,12 @@ F: drivers/hid/ > F: include/linux/hid* > F: include/uapi/linux/hid* > > +HID PLAYSTATION DRIVER > +M: Roderick Colenbrander <roderick.colenbrander@sony.com> > +L: linux-input@vger.kernel.org > +S: Supported > +F: drivers/hid/hid-playstation.c > + > HID SENSOR HUB DRIVERS > M: Jiri Kosina <jikos@kernel.org> > M: Jonathan Cameron <jic23@kernel.org> > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig > index 7bdda1b5b221..d3258e806998 100644 > --- a/drivers/hid/Kconfig > +++ b/drivers/hid/Kconfig > @@ -853,6 +853,15 @@ config HID_PLANTRONICS > > Say M here if you may ever plug in a Plantronics USB audio device. > > +config HID_PLAYSTATION > + tristate "PlayStation HID Driver" > + default !EXPERT Minor nit: these '!EXPERT' defaults are there only for drivers that were created during the big "let's separate all the quirks from hid-core into individual driver" bang that happened ages ago. For new drivers, we follow what's common in other driver subsystems, and don't force the default. No need to resend if it'd be just for this change, I can adjust it when applying. Thanks,
Hi Roderick, Thanks for the v2. I'll also let the reviewers of v1 do a second pass on this version, but I have one comment here: On Sat, Jan 2, 2021 at 11:31 PM Roderick Colenbrander <roderick@gaikai.com> wrote: > > From: Roderick Colenbrander <roderick.colenbrander@sony.com> > > Implement support for PlayStation DualSense gamepad in USB mode. > Support features include buttons and sticks, which adhere to the > Linux gamepad spec. > > Signed-off-by: Roderick Colenbrander <roderick.colenbrander@sony.com> > --- > MAINTAINERS | 6 + > drivers/hid/Kconfig | 9 + > drivers/hid/Makefile | 1 + > drivers/hid/hid-ids.h | 1 + > drivers/hid/hid-playstation.c | 321 ++++++++++++++++++++++++++++++++++ > drivers/hid/hid-quirks.c | 3 + > 6 files changed, 341 insertions(+) > create mode 100644 drivers/hid/hid-playstation.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index f81d598a8556..0ecae30af074 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -7918,6 +7918,12 @@ F: drivers/hid/ > F: include/linux/hid* > F: include/uapi/linux/hid* > > +HID PLAYSTATION DRIVER > +M: Roderick Colenbrander <roderick.colenbrander@sony.com> > +L: linux-input@vger.kernel.org > +S: Supported > +F: drivers/hid/hid-playstation.c > + > HID SENSOR HUB DRIVERS > M: Jiri Kosina <jikos@kernel.org> > M: Jonathan Cameron <jic23@kernel.org> > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig > index 7bdda1b5b221..d3258e806998 100644 > --- a/drivers/hid/Kconfig > +++ b/drivers/hid/Kconfig > @@ -853,6 +853,15 @@ config HID_PLANTRONICS > > Say M here if you may ever plug in a Plantronics USB audio device. > > +config HID_PLAYSTATION > + tristate "PlayStation HID Driver" > + default !EXPERT > + depends on HID > + help > + Provides support for Sony PS5 controllers including support for > + its special functionalities e.g. touchpad, lights and motion > + sensors. > + > config HID_PRIMAX > tristate "Primax non-fully HID-compliant devices" > depends on HID > diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile > index 014d21fe7dac..3cdbfb60ca57 100644 > --- a/drivers/hid/Makefile > +++ b/drivers/hid/Makefile > @@ -94,6 +94,7 @@ hid-picolcd-$(CONFIG_HID_PICOLCD_CIR) += hid-picolcd_cir.o > hid-picolcd-$(CONFIG_DEBUG_FS) += hid-picolcd_debugfs.o > > obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o > +obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o > obj-$(CONFIG_HID_PRIMAX) += hid-primax.o > obj-$(CONFIG_HID_REDRAGON) += hid-redragon.o > obj-$(CONFIG_HID_RETRODE) += hid-retrode.o > diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h > index 4c5f23640f9c..70c51ec6395c 100644 > --- a/drivers/hid/hid-ids.h > +++ b/drivers/hid/hid-ids.h > @@ -1072,6 +1072,7 @@ > #define USB_DEVICE_ID_SONY_PS4_CONTROLLER 0x05c4 > #define USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 0x09cc > #define USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE 0x0ba0 > +#define USB_DEVICE_ID_SONY_PS5_CONTROLLER 0x0ce6 > #define USB_DEVICE_ID_SONY_MOTION_CONTROLLER 0x03d5 > #define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER 0x042f > #define USB_DEVICE_ID_SONY_BUZZ_CONTROLLER 0x0002 > diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c > new file mode 100644 > index 000000000000..3d5fe9069c26 > --- /dev/null > +++ b/drivers/hid/hid-playstation.c > @@ -0,0 +1,321 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * HID driver for Sony DualSense(TM) controller. > + * > + * Copyright (c) 2020 Sony Interactive Entertainment > + */ > + > +#include <linux/bits.h> > +#include <linux/crc32.h> > +#include <linux/device.h> > +#include <linux/hid.h> > +#include <linux/input/mt.h> > +#include <linux/module.h> > + > +#include <asm/unaligned.h> > + > +#include "hid-ids.h" > + > +#define HID_PLAYSTATION_VERSION_PATCH 0x8000 > + > +/* Base class for playstation devices. */ > +struct ps_device { > + struct hid_device *hdev; > + > + int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size); > +}; > + > +#define DS_INPUT_REPORT_USB 0x01 > + > +/* Button masks for DualSense input report. */ > +#define DS_BUTTONS0_HAT_SWITCH GENMASK(3, 0) > +#define DS_BUTTONS0_SQUARE BIT(4) > +#define DS_BUTTONS0_CROSS BIT(5) > +#define DS_BUTTONS0_CIRCLE BIT(6) > +#define DS_BUTTONS0_TRIANGLE BIT(7) > +#define DS_BUTTONS1_L1 BIT(0) > +#define DS_BUTTONS1_R1 BIT(1) > +#define DS_BUTTONS1_L2 BIT(2) > +#define DS_BUTTONS1_R2 BIT(3) > +#define DS_BUTTONS1_CREATE BIT(4) > +#define DS_BUTTONS1_OPTIONS BIT(5) > +#define DS_BUTTONS1_L3 BIT(6) > +#define DS_BUTTONS1_R3 BIT(7) > +#define DS_BUTTONS2_PS_HOME BIT(0) > +#define DS_BUTTONS2_TOUCHPAD BIT(1) > + > +struct dualsense { > + struct ps_device base; > + struct input_dev *gamepad; > +}; > + > +struct dualsense_touch_point { > + uint8_t contact; > + uint8_t x_lo; > + uint8_t x_hi:4, y_lo:4; > + uint8_t y_hi; > +} __packed; > + > +/* Main DualSense input report excluding any BT/USB specific headers. */ > +struct dualsense_input_report { > + uint8_t x, y; > + uint8_t rx, ry; > + uint8_t z, rz; > + uint8_t seq_number; > + uint8_t buttons[4]; > + uint8_t reserved[4]; > + > + /* Motion sensors */ > + __le16 gyro[3]; /* x, y, z */ > + __le16 accel[3]; /* x, y, z */ > + __le32 sensor_timestamp; > + uint8_t reserved2; > + > + /* Touchpad */ > + struct dualsense_touch_point points[2]; > + > + uint8_t reserved3[12]; > + uint8_t status; > + uint8_t reserved4[11]; > +} __packed; > + > +/* Common gamepad buttons across DualShock 3 / 4 and DualSense. > + * Note: for device with a touchpad, touchpad button is not included > + * as it will be part of the touchpad device. > + */ > +static const int ps_gamepad_buttons[] = { > + BTN_WEST, /* Square */ > + BTN_NORTH, /* Triangle */ > + BTN_EAST, /* Circle */ > + BTN_SOUTH, /* Cross */ > + BTN_TL, /* L1 */ > + BTN_TR, /* R1 */ > + BTN_TL2, /* L2 */ > + BTN_TR2, /* R2 */ > + BTN_SELECT, /* Create (PS5) / Share (PS4) */ > + BTN_START, /* Option */ > + BTN_THUMBL, /* L3 */ > + BTN_THUMBR, /* R3 */ > + BTN_MODE, /* PS Home */ > +}; > + > +static const struct {int x; int y; } ps_gamepad_hat_mapping[] = { > + {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, > + {0, 0} > +}; > + > +static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const char *name_suffix) > +{ > + struct input_dev *input_dev; > + > + input_dev = devm_input_allocate_device(&hdev->dev); > + if (!input_dev) > + return ERR_PTR(-ENOMEM); > + > + input_dev->id.bustype = hdev->bus; > + input_dev->id.vendor = hdev->vendor; > + input_dev->id.product = hdev->product; > + input_dev->id.version = hdev->version; > + input_dev->uniq = hdev->uniq; > + > + if (name_suffix) { > + input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, > + name_suffix); > + if (!input_dev->name) > + return ERR_PTR(-ENOMEM); > + } else { > + input_dev->name = hdev->name; > + } > + > + input_set_drvdata(input_dev, hdev); > + > + return input_dev; > +} > + > +static struct input_dev *ps_gamepad_create(struct hid_device *hdev) > +{ > + struct input_dev *gamepad; > + unsigned int i; > + int ret; > + > + gamepad = ps_allocate_input_dev(hdev, NULL); > + if (IS_ERR(gamepad)) > + return ERR_PTR(-ENOMEM); > + > + input_set_abs_params(gamepad, ABS_X, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_Y, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_Z, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_RX, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_RY, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_RZ, 0, 255, 0, 0); > + > + input_set_abs_params(gamepad, ABS_HAT0X, -1, 1, 0, 0); > + input_set_abs_params(gamepad, ABS_HAT0Y, -1, 1, 0, 0); > + > + for (i = 0; i < ARRAY_SIZE(ps_gamepad_buttons); i++) > + input_set_capability(gamepad, EV_KEY, ps_gamepad_buttons[i]); > + > + ret = input_register_device(gamepad); > + if (ret) > + return ERR_PTR(ret); > + > + return gamepad; > +} > + > +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); > + struct dualsense_input_report *ds_report; > + uint8_t value; > + > + /* DualSense in USB uses the full HID report for reportID 1, but > + * Bluetooth uses a minimal HID report for reportID 1 and reports > + * the full report using reportID 49. > + */ > + if (report->id == DS_INPUT_REPORT_USB && hdev->bus == BUS_USB) { > + ds_report = (struct dualsense_input_report *)&data[1]; > + } else { > + hid_err(hdev, "Unhandled reportID=%d\n", report->id); > + return -1; > + } > + > + input_report_abs(ds->gamepad, ABS_X, ds_report->x); > + input_report_abs(ds->gamepad, ABS_Y, ds_report->y); > + input_report_abs(ds->gamepad, ABS_RX, ds_report->rx); > + input_report_abs(ds->gamepad, ABS_RY, ds_report->ry); > + input_report_abs(ds->gamepad, ABS_Z, ds_report->z); > + input_report_abs(ds->gamepad, ABS_RZ, ds_report->rz); > + > + value = ds_report->buttons[0] & DS_BUTTONS0_HAT_SWITCH; > + if (value > 7) > + value = 8; /* center */ > + input_report_abs(ds->gamepad, ABS_HAT0X, ps_gamepad_hat_mapping[value].x); > + input_report_abs(ds->gamepad, ABS_HAT0Y, ps_gamepad_hat_mapping[value].y); > + > + input_report_key(ds->gamepad, BTN_WEST, ds_report->buttons[0] & DS_BUTTONS0_SQUARE); > + input_report_key(ds->gamepad, BTN_SOUTH, ds_report->buttons[0] & DS_BUTTONS0_CROSS); > + input_report_key(ds->gamepad, BTN_EAST, ds_report->buttons[0] & DS_BUTTONS0_CIRCLE); > + input_report_key(ds->gamepad, BTN_NORTH, ds_report->buttons[0] & DS_BUTTONS0_TRIANGLE); > + input_report_key(ds->gamepad, BTN_TL, ds_report->buttons[1] & DS_BUTTONS1_L1); > + input_report_key(ds->gamepad, BTN_TR, ds_report->buttons[1] & DS_BUTTONS1_R1); > + input_report_key(ds->gamepad, BTN_TL2, ds_report->buttons[1] & DS_BUTTONS1_L2); > + input_report_key(ds->gamepad, BTN_TR2, ds_report->buttons[1] & DS_BUTTONS1_R2); > + input_report_key(ds->gamepad, BTN_SELECT, ds_report->buttons[1] & DS_BUTTONS1_CREATE); > + input_report_key(ds->gamepad, BTN_START, ds_report->buttons[1] & DS_BUTTONS1_OPTIONS); > + input_report_key(ds->gamepad, BTN_THUMBL, ds_report->buttons[1] & DS_BUTTONS1_L3); > + input_report_key(ds->gamepad, BTN_THUMBR, ds_report->buttons[1] & DS_BUTTONS1_R3); > + input_report_key(ds->gamepad, BTN_MODE, ds_report->buttons[2] & DS_BUTTONS2_PS_HOME); > + input_sync(ds->gamepad); > + > + return 0; > +} > + > +static struct ps_device *dualsense_create(struct hid_device *hdev) > +{ > + struct dualsense *ds; > + int ret; > + > + ds = devm_kzalloc(&hdev->dev, sizeof(*ds), GFP_KERNEL); > + if (!ds) > + return ERR_PTR(-ENOMEM); > + > + /* Patch version to allow userspace to distinguish between > + * hid-generic vs hid-playstation axis and button mapping. > + */ > + hdev->version |= HID_PLAYSTATION_VERSION_PATCH; > + > + ds->base.hdev = hdev; > + ds->base.parse_report = dualsense_parse_report; > + hid_set_drvdata(hdev, ds); > + > + ds->gamepad = ps_gamepad_create(hdev); > + if (IS_ERR(ds->gamepad)) { > + ret = PTR_ERR(ds->gamepad); > + goto err; > + } > + > + return &ds->base; > + > +err: > + return ERR_PTR(ret); > +} > + > +static int ps_raw_event(struct hid_device *hdev, struct hid_report *report, > + u8 *data, int size) > +{ > + struct ps_device *dev = hid_get_drvdata(hdev); > + > + if (dev && dev->parse_report) > + return dev->parse_report(dev, report, data, size); > + > + return 0; > +} > + > +static int ps_probe(struct hid_device *hdev, const struct hid_device_id *id) > +{ > + struct ps_device *dev; > + int ret; > + > + ret = hid_parse(hdev); > + if (ret) { > + hid_err(hdev, "parse failed\n"); > + return ret; > + } > + > + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); > + if (ret) { > + hid_err(hdev, "hw start failed\n"); > + return ret; > + } > + > + ret = hid_hw_open(hdev); > + if (ret) { > + hid_err(hdev, "hw open failed\n"); > + goto err_stop; > + } > + > + if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER) { > + dev = dualsense_create(hdev); > + if (IS_ERR(dev)) { > + hid_err(hdev, "Failed to create dualsense.\n"); > + ret = PTR_ERR(dev); > + goto err_close; > + } > + } > + > + return ret; > + > +err_close: > + hid_hw_close(hdev); > +err_stop: > + hid_hw_stop(hdev); > + return ret; > +} > + > +static void ps_remove(struct hid_device *hdev) > +{ > + hid_hw_close(hdev); > + hid_hw_stop(hdev); > +} > + > +static const struct hid_device_id ps_devices[] = { > + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) }, > + { } > +}; > +MODULE_DEVICE_TABLE(hid, ps_devices); > + > +static struct hid_driver ps_driver = { > + .name = "playstation", > + .id_table = ps_devices, > + .probe = ps_probe, > + .remove = ps_remove, > + .raw_event = ps_raw_event, > +}; > + > +module_hid_driver(ps_driver); > + > +MODULE_AUTHOR("Sony Interactive Entertainment"); > +MODULE_DESCRIPTION("HID Driver for PlayStation peripherals."); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c > index d9ca874dffac..1ca46cb445be 100644 > --- a/drivers/hid/hid-quirks.c > +++ b/drivers/hid/hid-quirks.c > @@ -565,6 +565,9 @@ static const struct hid_device_id hid_have_special_driver[] = { > #if IS_ENABLED(CONFIG_HID_PLANTRONICS) > { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) }, > #endif > +#if IS_ENABLED(CONFIG_HID_PLAYSTATION) > + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) }, > +#endif Can you please drop this hunk? The DS controller "works" fine with hid-generic, and I'd rather not have this list grows just for the sake of it. Cheers, Benjamin > #if IS_ENABLED(CONFIG_HID_PRIMAX) > { HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) }, > #endif > -- > 2.26.2 >
Hi I have just a couple minor comments. 2021. január 2., szombat 23:30 keltezéssel, Roderick Colenbrander írta: > From: Roderick Colenbrander roderick.colenbrander@sony.com > > Implement support for PlayStation DualSense gamepad in USB mode. > Support features include buttons and sticks, which adhere to the > Linux gamepad spec. > > Signed-off-by: Roderick Colenbrander roderick.colenbrander@sony.com > [...] > +/* Common gamepad buttons across DualShock 3 / 4 and DualSense. > + * Note: for device with a touchpad, touchpad button is not included > + * as it will be part of the touchpad device. > + */ > +static const int ps_gamepad_buttons[] = { > + BTN_WEST, /* Square */ > + BTN_NORTH, /* Triangle */ > + BTN_EAST, /* Circle */ > + BTN_SOUTH, /* Cross */ > + BTN_TL, /* L1 */ > + BTN_TR, /* R1 */ > + BTN_TL2, /* L2 */ > + BTN_TR2, /* R2 */ > + BTN_SELECT, /* Create (PS5) / Share (PS4) */ > + BTN_START, /* Option */ > + BTN_THUMBL, /* L3 */ > + BTN_THUMBR, /* R3 */ > + BTN_MODE, /* PS Home */ > +}; > + > +static const struct {int x; int y; } ps_gamepad_hat_mapping[] = { > + {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, > + {0, 0} > +}; I believe the preferred way is to have a comma after each array/enum/etc. element unless it is a terminating entry. > + > +static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const char *name_suffix) > +{ > + struct input_dev *input_dev; > + > + input_dev = devm_input_allocate_device(&hdev->dev); > + if (!input_dev) > + return ERR_PTR(-ENOMEM); > + > + input_dev->id.bustype = hdev->bus; > + input_dev->id.vendor = hdev->vendor; > + input_dev->id.product = hdev->product; > + input_dev->id.version = hdev->version; > + input_dev->uniq = hdev->uniq; > + > + if (name_suffix) { > + input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, > + name_suffix); > + if (!input_dev->name) > + return ERR_PTR(-ENOMEM); > + } else { > + input_dev->name = hdev->name; > + } > + > + input_set_drvdata(input_dev, hdev); > + > + return input_dev; > +} > + > +static struct input_dev *ps_gamepad_create(struct hid_device *hdev) > +{ > + struct input_dev *gamepad; > + unsigned int i; > + int ret; > + > + gamepad = ps_allocate_input_dev(hdev, NULL); > + if (IS_ERR(gamepad)) > + return ERR_PTR(-ENOMEM); I know that at the moment ENOMEM is the only possible error, but I believe `return ERR_CAST(gamepad);` would be better. (Or even just `return gamepad;`.) > + > + input_set_abs_params(gamepad, ABS_X, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_Y, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_Z, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_RX, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_RY, 0, 255, 0, 0); > + input_set_abs_params(gamepad, ABS_RZ, 0, 255, 0, 0); > + > + input_set_abs_params(gamepad, ABS_HAT0X, -1, 1, 0, 0); > + input_set_abs_params(gamepad, ABS_HAT0Y, -1, 1, 0, 0); > + > + for (i = 0; i < ARRAY_SIZE(ps_gamepad_buttons); i++) > + input_set_capability(gamepad, EV_KEY, ps_gamepad_buttons[i]); > + > + ret = input_register_device(gamepad); > + if (ret) > + return ERR_PTR(ret); > + > + return gamepad; > +} > + > +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); > + struct dualsense_input_report *ds_report; > + uint8_t value; > + I think `size` should be checked somewhere around here. > + /* DualSense in USB uses the full HID report for reportID 1, but > + * Bluetooth uses a minimal HID report for reportID 1 and reports > + * the full report using reportID 49. > + */ > + if (report->id == DS_INPUT_REPORT_USB && hdev->bus == BUS_USB) { > + ds_report = (struct dualsense_input_report *)&data[1]; > + } else { > + hid_err(hdev, "Unhandled reportID=%d\n", report->id); > + return -1; > + } > + > + input_report_abs(ds->gamepad, ABS_X, ds_report->x); > + input_report_abs(ds->gamepad, ABS_Y, ds_report->y); > + input_report_abs(ds->gamepad, ABS_RX, ds_report->rx); > + input_report_abs(ds->gamepad, ABS_RY, ds_report->ry); > + input_report_abs(ds->gamepad, ABS_Z, ds_report->z); > + input_report_abs(ds->gamepad, ABS_RZ, ds_report->rz); > + > + value = ds_report->buttons[0] & DS_BUTTONS0_HAT_SWITCH; > + if (value > 7) > + value = 8; /* center */ This seems a bit flimsy to me, it relies on a different part of the code being in a certain way that is not enforced by anything. I'd probably do something like this: enum { HAT_DIR_W = 0, HAT_DIR_NW, ... HAT_DIR_SW, HAT_DIR_NONE, }; static const struct {int x; int y; } ps_gamepad_hat_mapping[] = { [HAT_DIR_W] = {0, -1}, ... [HAT_DIR_NONE] = {0, 0}, }; and then if (value >= ARRAY_SIZE(ps_gamepad_hat_mapping)) value = HAT_DIR_NONE; Please consider it. By the way, are values 9..15 actually sent by the controller? > + input_report_abs(ds->gamepad, ABS_HAT0X, ps_gamepad_hat_mapping[value].x); > + input_report_abs(ds->gamepad, ABS_HAT0Y, ps_gamepad_hat_mapping[value].y); > + > + input_report_key(ds->gamepad, BTN_WEST, ds_report->buttons[0] & DS_BUTTONS0_SQUARE); > + input_report_key(ds->gamepad, BTN_SOUTH, ds_report->buttons[0] & DS_BUTTONS0_CROSS); > + input_report_key(ds->gamepad, BTN_EAST, ds_report->buttons[0] & DS_BUTTONS0_CIRCLE); > + input_report_key(ds->gamepad, BTN_NORTH, ds_report->buttons[0] & DS_BUTTONS0_TRIANGLE); > + input_report_key(ds->gamepad, BTN_TL, ds_report->buttons[1] & DS_BUTTONS1_L1); > + input_report_key(ds->gamepad, BTN_TR, ds_report->buttons[1] & DS_BUTTONS1_R1); > + input_report_key(ds->gamepad, BTN_TL2, ds_report->buttons[1] & DS_BUTTONS1_L2); > + input_report_key(ds->gamepad, BTN_TR2, ds_report->buttons[1] & DS_BUTTONS1_R2); > + input_report_key(ds->gamepad, BTN_SELECT, ds_report->buttons[1] & DS_BUTTONS1_CREATE); > + input_report_key(ds->gamepad, BTN_START, ds_report->buttons[1] & DS_BUTTONS1_OPTIONS); > + input_report_key(ds->gamepad, BTN_THUMBL, ds_report->buttons[1] & DS_BUTTONS1_L3); > + input_report_key(ds->gamepad, BTN_THUMBR, ds_report->buttons[1] & DS_BUTTONS1_R3); > + input_report_key(ds->gamepad, BTN_MODE, ds_report->buttons[2] & DS_BUTTONS2_PS_HOME); > + input_sync(ds->gamepad); > + > + return 0; > +} > + > +static struct ps_device *dualsense_create(struct hid_device *hdev) > +{ > + struct dualsense *ds; > + int ret; > + > + ds = devm_kzalloc(&hdev->dev, sizeof(*ds), GFP_KERNEL); > + if (!ds) > + return ERR_PTR(-ENOMEM); > + > + /* Patch version to allow userspace to distinguish between > + * hid-generic vs hid-playstation axis and button mapping. > + */ > + hdev->version |= HID_PLAYSTATION_VERSION_PATCH; > + > + ds->base.hdev = hdev; > + ds->base.parse_report = dualsense_parse_report; > + hid_set_drvdata(hdev, ds); > + > + ds->gamepad = ps_gamepad_create(hdev); > + if (IS_ERR(ds->gamepad)) { > + ret = PTR_ERR(ds->gamepad); > + goto err; > + } > + > + return &ds->base; > + > +err: > + return ERR_PTR(ret); > +} > [...] > +static int ps_probe(struct hid_device *hdev, const struct hid_device_id *id) > +{ > + struct ps_device *dev; > + int ret; > + > + ret = hid_parse(hdev); > + if (ret) { > + hid_err(hdev, "parse failed\n"); > + return ret; > + } > + > + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); > + if (ret) { > + hid_err(hdev, "hw start failed\n"); > + return ret; > + } > + > + ret = hid_hw_open(hdev); > + if (ret) { > + hid_err(hdev, "hw open failed\n"); > + goto err_stop; > + } > + > + if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER) { I'm still not fully seeing the purpose of this `if`. The probe should not be called for devices not in the id_table, so this seems to me to be a long way of writing `if (true)`. Or am I missing something? > + dev = dualsense_create(hdev); > + if (IS_ERR(dev)) { > + hid_err(hdev, "Failed to create dualsense.\n"); I think it'd be preferable if all log messages would either be lowercase or uppercase, not a mix of both. Same for punctuation. This applies to all patches. > + ret = PTR_ERR(dev); > + goto err_close; > + } > + } > + > + return ret; > + > +err_close: > + hid_hw_close(hdev); > +err_stop: > + hid_hw_stop(hdev); > + return ret; > +} > [...] Regards, Barnabás Pőcze
Hi Barnabas, Thanks for your suggestions. On Thu, Jan 7, 2021 at 9:14 AM Barnabás Pőcze <pobrn@protonmail.com> wrote: > > Hi > > > I have just a couple minor comments. > > > 2021. január 2., szombat 23:30 keltezéssel, Roderick Colenbrander írta: > > > From: Roderick Colenbrander roderick.colenbrander@sony.com > > > > Implement support for PlayStation DualSense gamepad in USB mode. > > Support features include buttons and sticks, which adhere to the > > Linux gamepad spec. > > > > Signed-off-by: Roderick Colenbrander roderick.colenbrander@sony.com > > [...] > > +/* Common gamepad buttons across DualShock 3 / 4 and DualSense. > > + * Note: for device with a touchpad, touchpad button is not included > > + * as it will be part of the touchpad device. > > + */ > > +static const int ps_gamepad_buttons[] = { > > + BTN_WEST, /* Square */ > > + BTN_NORTH, /* Triangle */ > > + BTN_EAST, /* Circle */ > > + BTN_SOUTH, /* Cross */ > > + BTN_TL, /* L1 */ > > + BTN_TR, /* R1 */ > > + BTN_TL2, /* L2 */ > > + BTN_TR2, /* R2 */ > > + BTN_SELECT, /* Create (PS5) / Share (PS4) */ > > + BTN_START, /* Option */ > > + BTN_THUMBL, /* L3 */ > > + BTN_THUMBR, /* R3 */ > > + BTN_MODE, /* PS Home */ > > +}; > > + > > +static const struct {int x; int y; } ps_gamepad_hat_mapping[] = { > > + {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, > > + {0, 0} > > +}; > > I believe the preferred way is to have a comma after each array/enum/etc. element > unless it is a terminating entry. > > > > + > > +static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const char *name_suffix) > > +{ > > + struct input_dev *input_dev; > > + > > + input_dev = devm_input_allocate_device(&hdev->dev); > > + if (!input_dev) > > + return ERR_PTR(-ENOMEM); > > + > > + input_dev->id.bustype = hdev->bus; > > + input_dev->id.vendor = hdev->vendor; > > + input_dev->id.product = hdev->product; > > + input_dev->id.version = hdev->version; > > + input_dev->uniq = hdev->uniq; > > + > > + if (name_suffix) { > > + input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, > > + name_suffix); > > + if (!input_dev->name) > > + return ERR_PTR(-ENOMEM); > > + } else { > > + input_dev->name = hdev->name; > > + } > > + > > + input_set_drvdata(input_dev, hdev); > > + > > + return input_dev; > > +} > > + > > +static struct input_dev *ps_gamepad_create(struct hid_device *hdev) > > +{ > > + struct input_dev *gamepad; > > + unsigned int i; > > + int ret; > > + > > + gamepad = ps_allocate_input_dev(hdev, NULL); > > + if (IS_ERR(gamepad)) > > + return ERR_PTR(-ENOMEM); > > I know that at the moment ENOMEM is the only possible error, but I believe > `return ERR_CAST(gamepad);` would be better. (Or even just `return gamepad;`.) > > > > + > > + input_set_abs_params(gamepad, ABS_X, 0, 255, 0, 0); > > + input_set_abs_params(gamepad, ABS_Y, 0, 255, 0, 0); > > + input_set_abs_params(gamepad, ABS_Z, 0, 255, 0, 0); > > + input_set_abs_params(gamepad, ABS_RX, 0, 255, 0, 0); > > + input_set_abs_params(gamepad, ABS_RY, 0, 255, 0, 0); > > + input_set_abs_params(gamepad, ABS_RZ, 0, 255, 0, 0); > > + > > + input_set_abs_params(gamepad, ABS_HAT0X, -1, 1, 0, 0); > > + input_set_abs_params(gamepad, ABS_HAT0Y, -1, 1, 0, 0); > > + > > + for (i = 0; i < ARRAY_SIZE(ps_gamepad_buttons); i++) > > + input_set_capability(gamepad, EV_KEY, ps_gamepad_buttons[i]); > > + > > + ret = input_register_device(gamepad); > > + if (ret) > > + return ERR_PTR(ret); > > + > > + return gamepad; > > +} > > + > > +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); > > + struct dualsense_input_report *ds_report; > > + uint8_t value; > > + > > I think `size` should be checked somewhere around here. > > > > + /* DualSense in USB uses the full HID report for reportID 1, but > > + * Bluetooth uses a minimal HID report for reportID 1 and reports > > + * the full report using reportID 49. > > + */ > > + if (report->id == DS_INPUT_REPORT_USB && hdev->bus == BUS_USB) { > > + ds_report = (struct dualsense_input_report *)&data[1]; > > + } else { > > + hid_err(hdev, "Unhandled reportID=%d\n", report->id); > > + return -1; > > + } > > + > > + input_report_abs(ds->gamepad, ABS_X, ds_report->x); > > + input_report_abs(ds->gamepad, ABS_Y, ds_report->y); > > + input_report_abs(ds->gamepad, ABS_RX, ds_report->rx); > > + input_report_abs(ds->gamepad, ABS_RY, ds_report->ry); > > + input_report_abs(ds->gamepad, ABS_Z, ds_report->z); > > + input_report_abs(ds->gamepad, ABS_RZ, ds_report->rz); > > + > > + value = ds_report->buttons[0] & DS_BUTTONS0_HAT_SWITCH; > > + if (value > 7) > > + value = 8; /* center */ > > This seems a bit flimsy to me, it relies on a different part of the code > being in a certain way that is not enforced by anything What do you mean with not enforced? I'm not saying I'm a big fan of the code, but HATs seem to work like this. The DualShock4/DualSense describe it in their HID descriptors with a logical minimum value of 0 and a max value of 7. The code is very similar to hid-input.c: static const struct { __s32 x; __s32 y; } hid_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; int hat_dir = usage->hat_dir; if (!hat_dir) hat_dir = (value - usage->hat_min) * 8 / (usage->hat_max - usage->hat_min + 1) + 1; if (hat_dir < 0 || hat_dir > 8) hat_dir = 0; input_event(input, usage->type, usage->code , hid_hat_to_axis[hat_dir].x); input_event(input, usage->type, usage->code + 1, hid_hat_to_axis[hat_dir].y); Main difference seems to be that this code places {0, 0} at the start and adds a "+1" to avoid having to set the value to "8" when out of range. I'd probably do something > like this: > > enum { > HAT_DIR_W = 0, > HAT_DIR_NW, > ... > HAT_DIR_SW, > HAT_DIR_NONE, > }; > > static const struct {int x; int y; } ps_gamepad_hat_mapping[] = { > [HAT_DIR_W] = {0, -1}, > ... > [HAT_DIR_NONE] = {0, 0}, > }; > > and then > > if (value >= ARRAY_SIZE(ps_gamepad_hat_mapping)) > value = HAT_DIR_NONE; > > Please consider it. By the way, are values 9..15 actually sent by the controller? See above. They are not sent. The Hat Switch in the report descriptor is reported with a logical minimum of 0 and a max of 8. > > > + input_report_abs(ds->gamepad, ABS_HAT0X, ps_gamepad_hat_mapping[value].x); > > + input_report_abs(ds->gamepad, ABS_HAT0Y, ps_gamepad_hat_mapping[value].y); > > + > > + input_report_key(ds->gamepad, BTN_WEST, ds_report->buttons[0] & DS_BUTTONS0_SQUARE); > > + input_report_key(ds->gamepad, BTN_SOUTH, ds_report->buttons[0] & DS_BUTTONS0_CROSS); > > + input_report_key(ds->gamepad, BTN_EAST, ds_report->buttons[0] & DS_BUTTONS0_CIRCLE); > > + input_report_key(ds->gamepad, BTN_NORTH, ds_report->buttons[0] & DS_BUTTONS0_TRIANGLE); > > + input_report_key(ds->gamepad, BTN_TL, ds_report->buttons[1] & DS_BUTTONS1_L1); > > + input_report_key(ds->gamepad, BTN_TR, ds_report->buttons[1] & DS_BUTTONS1_R1); > > + input_report_key(ds->gamepad, BTN_TL2, ds_report->buttons[1] & DS_BUTTONS1_L2); > > + input_report_key(ds->gamepad, BTN_TR2, ds_report->buttons[1] & DS_BUTTONS1_R2); > > + input_report_key(ds->gamepad, BTN_SELECT, ds_report->buttons[1] & DS_BUTTONS1_CREATE); > > + input_report_key(ds->gamepad, BTN_START, ds_report->buttons[1] & DS_BUTTONS1_OPTIONS); > > + input_report_key(ds->gamepad, BTN_THUMBL, ds_report->buttons[1] & DS_BUTTONS1_L3); > > + input_report_key(ds->gamepad, BTN_THUMBR, ds_report->buttons[1] & DS_BUTTONS1_R3); > > + input_report_key(ds->gamepad, BTN_MODE, ds_report->buttons[2] & DS_BUTTONS2_PS_HOME); > > + input_sync(ds->gamepad); > > + > > + return 0; > > +} > > + > > +static struct ps_device *dualsense_create(struct hid_device *hdev) > > +{ > > + struct dualsense *ds; > > + int ret; > > + > > + ds = devm_kzalloc(&hdev->dev, sizeof(*ds), GFP_KERNEL); > > + if (!ds) > > + return ERR_PTR(-ENOMEM); > > + > > + /* Patch version to allow userspace to distinguish between > > + * hid-generic vs hid-playstation axis and button mapping. > > + */ > > + hdev->version |= HID_PLAYSTATION_VERSION_PATCH; > > + > > + ds->base.hdev = hdev; > > + ds->base.parse_report = dualsense_parse_report; > > + hid_set_drvdata(hdev, ds); > > + > > + ds->gamepad = ps_gamepad_create(hdev); > > + if (IS_ERR(ds->gamepad)) { > > + ret = PTR_ERR(ds->gamepad); > > + goto err; > > + } > > + > > + return &ds->base; > > + > > +err: > > + return ERR_PTR(ret); > > +} > > [...] > > +static int ps_probe(struct hid_device *hdev, const struct hid_device_id *id) > > +{ > > + struct ps_device *dev; > > + int ret; > > + > > + ret = hid_parse(hdev); > > + if (ret) { > > + hid_err(hdev, "parse failed\n"); > > + return ret; > > + } > > + > > + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); > > + if (ret) { > > + hid_err(hdev, "hw start failed\n"); > > + return ret; > > + } > > + > > + ret = hid_hw_open(hdev); > > + if (ret) { > > + hid_err(hdev, "hw open failed\n"); > > + goto err_stop; > > + } > > + > > + if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER) { > > I'm still not fully seeing the purpose of this `if`. The probe should not be > called for devices not in the id_table, so this seems to me to be a long way > of writing `if (true)`. Or am I missing something? It is not used. It more there for the future, when we will add DualShock 4 and perhaps some other devices here. > > > + dev = dualsense_create(hdev); > > + if (IS_ERR(dev)) { > > + hid_err(hdev, "Failed to create dualsense.\n"); > > I think it'd be preferable if all log messages would either be lowercase or > uppercase, not a mix of both. Same for punctuation. This applies to all patches. > > > > + ret = PTR_ERR(dev); > > + goto err_close; > > + } > > + } > > + > > + return ret; > > + > > +err_close: > > + hid_hw_close(hdev); > > +err_stop: > > + hid_hw_stop(hdev); > > + return ret; > > +} > > [...] > > > Regards, > Barnabás Pőcze Regards, Roderick Colenbrander
Hi 2021. január 8., péntek 8:12 keltezéssel, Roderick Colenbrander írta: > [...] > > > +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); > > > + struct dualsense_input_report *ds_report; > > > + uint8_t value; > > > + > > > > I think `size` should be checked somewhere around here. > > > > > > > + /* DualSense in USB uses the full HID report for reportID 1, but > > > + * Bluetooth uses a minimal HID report for reportID 1 and reports > > > + * the full report using reportID 49. > > > + */ > > > + if (report->id == DS_INPUT_REPORT_USB && hdev->bus == BUS_USB) { > > > + ds_report = (struct dualsense_input_report *)&data[1]; > > > + } else { > > > + hid_err(hdev, "Unhandled reportID=%d\n", report->id); > > > + return -1; > > > + } > > > + > > > + input_report_abs(ds->gamepad, ABS_X, ds_report->x); > > > + input_report_abs(ds->gamepad, ABS_Y, ds_report->y); > > > + input_report_abs(ds->gamepad, ABS_RX, ds_report->rx); > > > + input_report_abs(ds->gamepad, ABS_RY, ds_report->ry); > > > + input_report_abs(ds->gamepad, ABS_Z, ds_report->z); > > > + input_report_abs(ds->gamepad, ABS_RZ, ds_report->rz); > > > + > > > + value = ds_report->buttons[0] & DS_BUTTONS0_HAT_SWITCH; > > > + if (value > 7) > > > + value = 8; /* center */ > > > > This seems a bit flimsy to me, it relies on a different part of the code > > being in a certain way that is not enforced by anything > > What do you mean with not enforced? I'm not saying I'm a big fan of > the code, but HATs seem to work like this. The DualShock4/DualSense > describe it in their HID descriptors with a logical minimum value of 0 > and a max value of 7. What I really meant is that I think it would be better to somehow decrease the chances of unintentionally breaking the code by providing stronger guarantees that `ps_gamepad_hat_mapping[value]` will not be an out of bounds access. E.g. checking with static_assert that it has 9 elements, using ARRAY_SIZE() in some way in the condition, etc. I'm not trying to say it's very probable, but I think it's easily preventable and thus it should be done. By "not enforced", I meant that there are no compile time or even runtime guarantees that the `ps_gamepad_hat_mapping` contains as many elements as expected by this part of the code. > > The code is very similar to hid-input.c: > > static const struct { > __s32 x; > __s32 y; > } hid_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { > 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; > > int hat_dir = usage->hat_dir; > if (!hat_dir) > hat_dir = (value - usage->hat_min) * 8 / (usage->hat_max - > usage->hat_min + 1) + 1; > if (hat_dir < 0 || hat_dir > 8) hat_dir = 0; > input_event(input, usage->type, usage->code , hid_hat_to_axis[hat_dir].x); > input_event(input, usage->type, usage->code + 1, hid_hat_to_axis[hat_dir].y); > > Main difference seems to be that this code places {0, 0} at the start > and adds a "+1" to avoid having to set the value to "8" when out of > range. > > I'd probably do something > > like this: > > > > enum { > > HAT_DIR_W = 0, > > HAT_DIR_NW, > > ... > > HAT_DIR_SW, > > HAT_DIR_NONE, > > }; > > > > static const struct {int x; int y; } ps_gamepad_hat_mapping[] = { > > [HAT_DIR_W] = {0, -1}, > > ... > > [HAT_DIR_NONE] = {0, 0}, > > }; > > > > and then > > > > if (value >= ARRAY_SIZE(ps_gamepad_hat_mapping)) > > value = HAT_DIR_NONE; > > > > Please consider it. By the way, are values 9..15 actually sent by the controller? > > See above. They are not sent. The Hat Switch in the report descriptor > is reported with a logical minimum of 0 and a max of 8. > > > > > > + input_report_abs(ds->gamepad, ABS_HAT0X, ps_gamepad_hat_mapping[value].x); > > > + input_report_abs(ds->gamepad, ABS_HAT0Y, ps_gamepad_hat_mapping[value].y); > > > + > > > + input_report_key(ds->gamepad, BTN_WEST, ds_report->buttons[0] & DS_BUTTONS0_SQUARE); > > > + input_report_key(ds->gamepad, BTN_SOUTH, ds_report->buttons[0] & DS_BUTTONS0_CROSS); > > > + input_report_key(ds->gamepad, BTN_EAST, ds_report->buttons[0] & DS_BUTTONS0_CIRCLE); > > > + input_report_key(ds->gamepad, BTN_NORTH, ds_report->buttons[0] & DS_BUTTONS0_TRIANGLE); > > > + input_report_key(ds->gamepad, BTN_TL, ds_report->buttons[1] & DS_BUTTONS1_L1); > > > + input_report_key(ds->gamepad, BTN_TR, ds_report->buttons[1] & DS_BUTTONS1_R1); > > > + input_report_key(ds->gamepad, BTN_TL2, ds_report->buttons[1] & DS_BUTTONS1_L2); > > > + input_report_key(ds->gamepad, BTN_TR2, ds_report->buttons[1] & DS_BUTTONS1_R2); > > > + input_report_key(ds->gamepad, BTN_SELECT, ds_report->buttons[1] & DS_BUTTONS1_CREATE); > > > + input_report_key(ds->gamepad, BTN_START, ds_report->buttons[1] & DS_BUTTONS1_OPTIONS); > > > + input_report_key(ds->gamepad, BTN_THUMBL, ds_report->buttons[1] & DS_BUTTONS1_L3); > > > + input_report_key(ds->gamepad, BTN_THUMBR, ds_report->buttons[1] & DS_BUTTONS1_R3); > > > + input_report_key(ds->gamepad, BTN_MODE, ds_report->buttons[2] & DS_BUTTONS2_PS_HOME); > > > + input_sync(ds->gamepad); > > > + > > > + return 0; > > > +} > [...] Regards, Barnabás Pőcze
diff --git a/MAINTAINERS b/MAINTAINERS index f81d598a8556..0ecae30af074 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7918,6 +7918,12 @@ F: drivers/hid/ F: include/linux/hid* F: include/uapi/linux/hid* +HID PLAYSTATION DRIVER +M: Roderick Colenbrander <roderick.colenbrander@sony.com> +L: linux-input@vger.kernel.org +S: Supported +F: drivers/hid/hid-playstation.c + HID SENSOR HUB DRIVERS M: Jiri Kosina <jikos@kernel.org> M: Jonathan Cameron <jic23@kernel.org> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 7bdda1b5b221..d3258e806998 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -853,6 +853,15 @@ config HID_PLANTRONICS Say M here if you may ever plug in a Plantronics USB audio device. +config HID_PLAYSTATION + tristate "PlayStation HID Driver" + default !EXPERT + depends on HID + help + Provides support for Sony PS5 controllers including support for + its special functionalities e.g. touchpad, lights and motion + sensors. + config HID_PRIMAX tristate "Primax non-fully HID-compliant devices" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 014d21fe7dac..3cdbfb60ca57 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -94,6 +94,7 @@ hid-picolcd-$(CONFIG_HID_PICOLCD_CIR) += hid-picolcd_cir.o hid-picolcd-$(CONFIG_DEBUG_FS) += hid-picolcd_debugfs.o obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o +obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o obj-$(CONFIG_HID_PRIMAX) += hid-primax.o obj-$(CONFIG_HID_REDRAGON) += hid-redragon.o obj-$(CONFIG_HID_RETRODE) += hid-retrode.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 4c5f23640f9c..70c51ec6395c 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1072,6 +1072,7 @@ #define USB_DEVICE_ID_SONY_PS4_CONTROLLER 0x05c4 #define USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 0x09cc #define USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE 0x0ba0 +#define USB_DEVICE_ID_SONY_PS5_CONTROLLER 0x0ce6 #define USB_DEVICE_ID_SONY_MOTION_CONTROLLER 0x03d5 #define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER 0x042f #define USB_DEVICE_ID_SONY_BUZZ_CONTROLLER 0x0002 diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c new file mode 100644 index 000000000000..3d5fe9069c26 --- /dev/null +++ b/drivers/hid/hid-playstation.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Sony DualSense(TM) controller. + * + * Copyright (c) 2020 Sony Interactive Entertainment + */ + +#include <linux/bits.h> +#include <linux/crc32.h> +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/input/mt.h> +#include <linux/module.h> + +#include <asm/unaligned.h> + +#include "hid-ids.h" + +#define HID_PLAYSTATION_VERSION_PATCH 0x8000 + +/* Base class for playstation devices. */ +struct ps_device { + struct hid_device *hdev; + + int (*parse_report)(struct ps_device *dev, struct hid_report *report, u8 *data, int size); +}; + +#define DS_INPUT_REPORT_USB 0x01 + +/* Button masks for DualSense input report. */ +#define DS_BUTTONS0_HAT_SWITCH GENMASK(3, 0) +#define DS_BUTTONS0_SQUARE BIT(4) +#define DS_BUTTONS0_CROSS BIT(5) +#define DS_BUTTONS0_CIRCLE BIT(6) +#define DS_BUTTONS0_TRIANGLE BIT(7) +#define DS_BUTTONS1_L1 BIT(0) +#define DS_BUTTONS1_R1 BIT(1) +#define DS_BUTTONS1_L2 BIT(2) +#define DS_BUTTONS1_R2 BIT(3) +#define DS_BUTTONS1_CREATE BIT(4) +#define DS_BUTTONS1_OPTIONS BIT(5) +#define DS_BUTTONS1_L3 BIT(6) +#define DS_BUTTONS1_R3 BIT(7) +#define DS_BUTTONS2_PS_HOME BIT(0) +#define DS_BUTTONS2_TOUCHPAD BIT(1) + +struct dualsense { + struct ps_device base; + struct input_dev *gamepad; +}; + +struct dualsense_touch_point { + uint8_t contact; + uint8_t x_lo; + uint8_t x_hi:4, y_lo:4; + uint8_t y_hi; +} __packed; + +/* Main DualSense input report excluding any BT/USB specific headers. */ +struct dualsense_input_report { + uint8_t x, y; + uint8_t rx, ry; + uint8_t z, rz; + uint8_t seq_number; + uint8_t buttons[4]; + uint8_t reserved[4]; + + /* Motion sensors */ + __le16 gyro[3]; /* x, y, z */ + __le16 accel[3]; /* x, y, z */ + __le32 sensor_timestamp; + uint8_t reserved2; + + /* Touchpad */ + struct dualsense_touch_point points[2]; + + uint8_t reserved3[12]; + uint8_t status; + uint8_t reserved4[11]; +} __packed; + +/* Common gamepad buttons across DualShock 3 / 4 and DualSense. + * Note: for device with a touchpad, touchpad button is not included + * as it will be part of the touchpad device. + */ +static const int ps_gamepad_buttons[] = { + BTN_WEST, /* Square */ + BTN_NORTH, /* Triangle */ + BTN_EAST, /* Circle */ + BTN_SOUTH, /* Cross */ + BTN_TL, /* L1 */ + BTN_TR, /* R1 */ + BTN_TL2, /* L2 */ + BTN_TR2, /* R2 */ + BTN_SELECT, /* Create (PS5) / Share (PS4) */ + BTN_START, /* Option */ + BTN_THUMBL, /* L3 */ + BTN_THUMBR, /* R3 */ + BTN_MODE, /* PS Home */ +}; + +static const struct {int x; int y; } ps_gamepad_hat_mapping[] = { + {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, + {0, 0} +}; + +static struct input_dev *ps_allocate_input_dev(struct hid_device *hdev, const char *name_suffix) +{ + struct input_dev *input_dev; + + input_dev = devm_input_allocate_device(&hdev->dev); + if (!input_dev) + return ERR_PTR(-ENOMEM); + + input_dev->id.bustype = hdev->bus; + input_dev->id.vendor = hdev->vendor; + input_dev->id.product = hdev->product; + input_dev->id.version = hdev->version; + input_dev->uniq = hdev->uniq; + + if (name_suffix) { + input_dev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, + name_suffix); + if (!input_dev->name) + return ERR_PTR(-ENOMEM); + } else { + input_dev->name = hdev->name; + } + + input_set_drvdata(input_dev, hdev); + + return input_dev; +} + +static struct input_dev *ps_gamepad_create(struct hid_device *hdev) +{ + struct input_dev *gamepad; + unsigned int i; + int ret; + + gamepad = ps_allocate_input_dev(hdev, NULL); + if (IS_ERR(gamepad)) + return ERR_PTR(-ENOMEM); + + input_set_abs_params(gamepad, ABS_X, 0, 255, 0, 0); + input_set_abs_params(gamepad, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(gamepad, ABS_Z, 0, 255, 0, 0); + input_set_abs_params(gamepad, ABS_RX, 0, 255, 0, 0); + input_set_abs_params(gamepad, ABS_RY, 0, 255, 0, 0); + input_set_abs_params(gamepad, ABS_RZ, 0, 255, 0, 0); + + input_set_abs_params(gamepad, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(gamepad, ABS_HAT0Y, -1, 1, 0, 0); + + for (i = 0; i < ARRAY_SIZE(ps_gamepad_buttons); i++) + input_set_capability(gamepad, EV_KEY, ps_gamepad_buttons[i]); + + ret = input_register_device(gamepad); + if (ret) + return ERR_PTR(ret); + + return gamepad; +} + +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); + struct dualsense_input_report *ds_report; + uint8_t value; + + /* DualSense in USB uses the full HID report for reportID 1, but + * Bluetooth uses a minimal HID report for reportID 1 and reports + * the full report using reportID 49. + */ + if (report->id == DS_INPUT_REPORT_USB && hdev->bus == BUS_USB) { + ds_report = (struct dualsense_input_report *)&data[1]; + } else { + hid_err(hdev, "Unhandled reportID=%d\n", report->id); + return -1; + } + + input_report_abs(ds->gamepad, ABS_X, ds_report->x); + input_report_abs(ds->gamepad, ABS_Y, ds_report->y); + input_report_abs(ds->gamepad, ABS_RX, ds_report->rx); + input_report_abs(ds->gamepad, ABS_RY, ds_report->ry); + input_report_abs(ds->gamepad, ABS_Z, ds_report->z); + input_report_abs(ds->gamepad, ABS_RZ, ds_report->rz); + + value = ds_report->buttons[0] & DS_BUTTONS0_HAT_SWITCH; + if (value > 7) + value = 8; /* center */ + input_report_abs(ds->gamepad, ABS_HAT0X, ps_gamepad_hat_mapping[value].x); + input_report_abs(ds->gamepad, ABS_HAT0Y, ps_gamepad_hat_mapping[value].y); + + input_report_key(ds->gamepad, BTN_WEST, ds_report->buttons[0] & DS_BUTTONS0_SQUARE); + input_report_key(ds->gamepad, BTN_SOUTH, ds_report->buttons[0] & DS_BUTTONS0_CROSS); + input_report_key(ds->gamepad, BTN_EAST, ds_report->buttons[0] & DS_BUTTONS0_CIRCLE); + input_report_key(ds->gamepad, BTN_NORTH, ds_report->buttons[0] & DS_BUTTONS0_TRIANGLE); + input_report_key(ds->gamepad, BTN_TL, ds_report->buttons[1] & DS_BUTTONS1_L1); + input_report_key(ds->gamepad, BTN_TR, ds_report->buttons[1] & DS_BUTTONS1_R1); + input_report_key(ds->gamepad, BTN_TL2, ds_report->buttons[1] & DS_BUTTONS1_L2); + input_report_key(ds->gamepad, BTN_TR2, ds_report->buttons[1] & DS_BUTTONS1_R2); + input_report_key(ds->gamepad, BTN_SELECT, ds_report->buttons[1] & DS_BUTTONS1_CREATE); + input_report_key(ds->gamepad, BTN_START, ds_report->buttons[1] & DS_BUTTONS1_OPTIONS); + input_report_key(ds->gamepad, BTN_THUMBL, ds_report->buttons[1] & DS_BUTTONS1_L3); + input_report_key(ds->gamepad, BTN_THUMBR, ds_report->buttons[1] & DS_BUTTONS1_R3); + input_report_key(ds->gamepad, BTN_MODE, ds_report->buttons[2] & DS_BUTTONS2_PS_HOME); + input_sync(ds->gamepad); + + return 0; +} + +static struct ps_device *dualsense_create(struct hid_device *hdev) +{ + struct dualsense *ds; + int ret; + + ds = devm_kzalloc(&hdev->dev, sizeof(*ds), GFP_KERNEL); + if (!ds) + return ERR_PTR(-ENOMEM); + + /* Patch version to allow userspace to distinguish between + * hid-generic vs hid-playstation axis and button mapping. + */ + hdev->version |= HID_PLAYSTATION_VERSION_PATCH; + + ds->base.hdev = hdev; + ds->base.parse_report = dualsense_parse_report; + hid_set_drvdata(hdev, ds); + + ds->gamepad = ps_gamepad_create(hdev); + if (IS_ERR(ds->gamepad)) { + ret = PTR_ERR(ds->gamepad); + goto err; + } + + return &ds->base; + +err: + return ERR_PTR(ret); +} + +static int ps_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct ps_device *dev = hid_get_drvdata(hdev); + + if (dev && dev->parse_report) + return dev->parse_report(dev, report, data, size); + + return 0; +} + +static int ps_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct ps_device *dev; + int ret; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "hw open failed\n"); + goto err_stop; + } + + if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER) { + dev = dualsense_create(hdev); + if (IS_ERR(dev)) { + hid_err(hdev, "Failed to create dualsense.\n"); + ret = PTR_ERR(dev); + goto err_close; + } + } + + return ret; + +err_close: + hid_hw_close(hdev); +err_stop: + hid_hw_stop(hdev); + return ret; +} + +static void ps_remove(struct hid_device *hdev) +{ + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id ps_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) }, + { } +}; +MODULE_DEVICE_TABLE(hid, ps_devices); + +static struct hid_driver ps_driver = { + .name = "playstation", + .id_table = ps_devices, + .probe = ps_probe, + .remove = ps_remove, + .raw_event = ps_raw_event, +}; + +module_hid_driver(ps_driver); + +MODULE_AUTHOR("Sony Interactive Entertainment"); +MODULE_DESCRIPTION("HID Driver for PlayStation peripherals."); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index d9ca874dffac..1ca46cb445be 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -565,6 +565,9 @@ static const struct hid_device_id hid_have_special_driver[] = { #if IS_ENABLED(CONFIG_HID_PLANTRONICS) { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) }, #endif +#if IS_ENABLED(CONFIG_HID_PLAYSTATION) + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) }, +#endif #if IS_ENABLED(CONFIG_HID_PRIMAX) { HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) }, #endif