Skip to content

Commit

Permalink
BACKPORT: HID: playstation: initial DualSense USB support.
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Barnabás Pőcze <[email protected]>
Signed-off-by: Benjamin Tissoires <[email protected]>

Bug: 167947264
Change-Id: I7cf496f9b6f721cdd3e79387caa86b2ccc6378fb
(cherry picked from commit bc2e15a9a0228b10fece576d4f6a974c002ff07b)
[roderick: Removed static_assert checks to fix compile failure.]
Signed-off-by: Roderick Colenbrander <[email protected]>
Signed-off-by: Farid Chahla <[email protected]>
Signed-off-by: Chenyang Zhong <[email protected]>
  • Loading branch information
Roderick Colenbrander authored and YumeMichi committed Aug 9, 2021
1 parent f75b02f commit 26fd31a
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 0 deletions.
6 changes: 6 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -5690,6 +5690,12 @@ F: drivers/hid/
F: include/linux/hid*
F: include/uapi/linux/hid*

HID PLAYSTATION DRIVER
M: Roderick Colenbrander <[email protected]>
L: [email protected]
S: Supported
F: drivers/hid/hid-playstation.c

HID SENSOR HUB DRIVERS
M: Jiri Kosina <[email protected]>
M: Jonathan Cameron <[email protected]>
Expand Down
8 changes: 8 additions & 0 deletions drivers/hid/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,14 @@ config HID_PLANTRONICS

Say M here if you may ever plug in a Plantronics USB audio device.

config HID_PLAYSTATION
tristate "PlayStation HID Driver"
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
Expand Down
1 change: 1 addition & 0 deletions drivers/hid/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,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_QVR) += hid-qvr.o hid-trace.o
obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \
Expand Down
1 change: 1 addition & 0 deletions drivers/hid/hid-ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,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
Expand Down
325 changes: 325 additions & 0 deletions drivers/hid/hid-playstation.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
// 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/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
#define DS_INPUT_REPORT_USB_SIZE 64

/* 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[10];
} __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_CAST(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;

/*
* 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 (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 {
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 > ARRAY_SIZE(ps_gamepad_hat_mapping))
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, "Failed to start HID device\n");
return ret;
}

ret = hid_hw_open(hdev);
if (ret) {
hid_err(hdev, "Failed to open HID device\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");

0 comments on commit 26fd31a

Please sign in to comment.