Skip to content

Commit c64ed0c

Browse files
Roderick ColenbranderJiri Kosina
authored andcommitted
HID: playstation: add DualShock4 dongle support.
This patch adds support for the DualShock4 dongle in a very similar way we contributed to hid-sony before. The dongle is a USB to Bluetooth bridge and uses the same HID reports as a USB device. It reports data through the DS4's main USB input report independent on whether a Bluetooth controller is connected. For this reason there is custom dongle report parsing code to detect controller hotplug and kick of calibration work until we are ready to process actual input reports. The logic also incorporates a workaround needed for Steam in which hid-playstation and Steam using hidraw can fight. Signed-off-by: Roderick Colenbrander <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent 58feecb commit c64ed0c

File tree

1 file changed

+140
-6
lines changed

1 file changed

+140
-6
lines changed

drivers/hid/hid-playstation.c

Lines changed: 140 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ struct dualsense_output_report {
315315
#define DS4_STATUS0_CABLE_STATE BIT(4)
316316
/* Battery status within batery_status field. */
317317
#define DS4_BATTERY_STATUS_FULL 11
318+
/* Status1 bit2 contains dongle connection state:
319+
* 0 = connectd
320+
* 1 = disconnected
321+
*/
322+
#define DS4_STATUS1_DONGLE_STATE BIT(2)
318323

319324
/* The lower 6 bits of hw_control of the Bluetooth main output report
320325
* control the interval at which Dualshock 4 reports data:
@@ -344,6 +349,13 @@ struct dualsense_output_report {
344349
#define DS4_TOUCHPAD_WIDTH 1920
345350
#define DS4_TOUCHPAD_HEIGHT 942
346351

352+
enum dualshock4_dongle_state {
353+
DONGLE_DISCONNECTED,
354+
DONGLE_CALIBRATING,
355+
DONGLE_CONNECTED,
356+
DONGLE_DISABLED
357+
};
358+
347359
struct dualshock4 {
348360
struct ps_device base;
349361
struct input_dev *gamepad;
@@ -354,6 +366,11 @@ struct dualshock4 {
354366
struct ps_calibration_data accel_calib_data[3];
355367
struct ps_calibration_data gyro_calib_data[3];
356368

369+
/* Only used on dongle to track state transitions. */
370+
enum dualshock4_dongle_state dongle_state;
371+
/* Used during calibration. */
372+
struct work_struct dongle_hotplug_worker;
373+
357374
/* Timestamp for sensor data */
358375
bool sensor_timestamp_initialized;
359376
uint32_t prev_sensor_timestamp;
@@ -513,9 +530,11 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = {
513530
{0, 0},
514531
};
515532

533+
static int dualshock4_get_calibration_data(struct dualshock4 *ds4);
516534
static inline void dualsense_schedule_work(struct dualsense *ds);
517535
static inline void dualshock4_schedule_work(struct dualshock4 *ds4);
518536
static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue);
537+
static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4);
519538

520539
/*
521540
* Add a new ps_device to ps_devices if it doesn't exist.
@@ -1678,6 +1697,33 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
16781697
return ERR_PTR(ret);
16791698
}
16801699

1700+
static void dualshock4_dongle_calibration_work(struct work_struct *work)
1701+
{
1702+
struct dualshock4 *ds4 = container_of(work, struct dualshock4, dongle_hotplug_worker);
1703+
unsigned long flags;
1704+
enum dualshock4_dongle_state dongle_state;
1705+
int ret;
1706+
1707+
ret = dualshock4_get_calibration_data(ds4);
1708+
if (ret < 0) {
1709+
/* This call is very unlikely to fail for the dongle. When it
1710+
* fails we are probably in a very bad state, so mark the
1711+
* dongle as disabled. We will re-enable the dongle if a new
1712+
* DS4 hotplug is detect from sony_raw_event as any issues
1713+
* are likely resolved then (the dongle is quite stupid).
1714+
*/
1715+
hid_err(ds4->base.hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n");
1716+
dongle_state = DONGLE_DISABLED;
1717+
} else {
1718+
hid_info(ds4->base.hdev, "DualShock 4 USB dongle: calibration completed\n");
1719+
dongle_state = DONGLE_CONNECTED;
1720+
}
1721+
1722+
spin_lock_irqsave(&ds4->base.lock, flags);
1723+
ds4->dongle_state = dongle_state;
1724+
spin_unlock_irqrestore(&ds4->base.lock, flags);
1725+
}
1726+
16811727
static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
16821728
{
16831729
struct hid_device *hdev = ds4->base.hdev;
@@ -1694,15 +1740,34 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
16941740
uint8_t *buf;
16951741

16961742
if (ds4->base.hdev->bus == BUS_USB) {
1743+
int retries;
1744+
16971745
buf = kzalloc(DS4_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL);
16981746
if (!buf)
16991747
return -ENOMEM;
17001748

1701-
ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION, buf,
1702-
DS4_FEATURE_REPORT_CALIBRATION_SIZE, true);
1703-
if (ret) {
1704-
hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
1705-
goto err_free;
1749+
/* We should normally receive the feature report data we asked
1750+
* for, but hidraw applications such as Steam can issue feature
1751+
* reports as well. In particular for Dongle reconnects, Steam
1752+
* and this function are competing resulting in often receiving
1753+
* data for a different HID report, so retry a few times.
1754+
*/
1755+
for (retries = 0; retries < 3; retries++) {
1756+
ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION, buf,
1757+
DS4_FEATURE_REPORT_CALIBRATION_SIZE, true);
1758+
if (ret) {
1759+
if (retries < 2) {
1760+
hid_warn(hdev, "Retrying DualShock 4 get calibration report (0x02) request\n");
1761+
continue;
1762+
} else {
1763+
ret = -EILSEQ;
1764+
goto err_free;
1765+
}
1766+
hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
1767+
goto err_free;
1768+
} else {
1769+
break;
1770+
}
17061771
}
17071772
} else { /* Bluetooth */
17081773
buf = kzalloc(DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, GFP_KERNEL);
@@ -2220,6 +2285,62 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
22202285
return 0;
22212286
}
22222287

2288+
static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_report *report,
2289+
u8 *data, int size)
2290+
{
2291+
struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
2292+
bool connected = false;
2293+
2294+
/* The dongle reports data using the main USB report (0x1) no matter whether a controller
2295+
* is connected with mostly zeros. The report does contain dongle status, which we use to
2296+
* determine if a controller is connected and if so we forward to the regular DualShock4
2297+
* parsing code.
2298+
*/
2299+
if (data[0] == DS4_INPUT_REPORT_USB && size == DS4_INPUT_REPORT_USB_SIZE) {
2300+
struct dualshock4_input_report_common *ds4_report = (struct dualshock4_input_report_common *)&data[1];
2301+
unsigned long flags;
2302+
2303+
connected = ds4_report->status[1] & DS4_STATUS1_DONGLE_STATE ? false : true;
2304+
2305+
if (ds4->dongle_state == DONGLE_DISCONNECTED && connected) {
2306+
hid_info(ps_dev->hdev, "DualShock 4 USB dongle: controller connected\n");
2307+
2308+
dualshock4_set_default_lightbar_colors(ds4);
2309+
2310+
spin_lock_irqsave(&ps_dev->lock, flags);
2311+
ds4->dongle_state = DONGLE_CALIBRATING;
2312+
spin_unlock_irqrestore(&ps_dev->lock, flags);
2313+
2314+
schedule_work(&ds4->dongle_hotplug_worker);
2315+
2316+
/* Don't process the report since we don't have
2317+
* calibration data, but let hidraw have it anyway.
2318+
*/
2319+
return 0;
2320+
} else if ((ds4->dongle_state == DONGLE_CONNECTED ||
2321+
ds4->dongle_state == DONGLE_DISABLED) && !connected) {
2322+
hid_info(ps_dev->hdev, "DualShock 4 USB dongle: controller disconnected\n");
2323+
2324+
spin_lock_irqsave(&ps_dev->lock, flags);
2325+
ds4->dongle_state = DONGLE_DISCONNECTED;
2326+
spin_unlock_irqrestore(&ps_dev->lock, flags);
2327+
2328+
/* Return 0, so hidraw can get the report. */
2329+
return 0;
2330+
} else if (ds4->dongle_state == DONGLE_CALIBRATING ||
2331+
ds4->dongle_state == DONGLE_DISABLED ||
2332+
ds4->dongle_state == DONGLE_DISCONNECTED) {
2333+
/* Return 0, so hidraw can get the report. */
2334+
return 0;
2335+
}
2336+
}
2337+
2338+
if (connected)
2339+
return dualshock4_parse_report(ps_dev, report, data, size);
2340+
2341+
return 0;
2342+
}
2343+
22232344
static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
22242345
{
22252346
struct hid_device *hdev = input_get_drvdata(dev);
@@ -2249,6 +2370,9 @@ static void dualshock4_remove(struct ps_device *ps_dev)
22492370
spin_unlock_irqrestore(&ds4->base.lock, flags);
22502371

22512372
cancel_work_sync(&ds4->output_worker);
2373+
2374+
if (ps_dev->hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE)
2375+
cancel_work_sync(&ds4->dongle_hotplug_worker);
22522376
}
22532377

22542378
static inline void dualshock4_schedule_work(struct dualshock4 *ds4)
@@ -2342,6 +2466,14 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
23422466
if (!ds4->output_report_dmabuf)
23432467
return ERR_PTR(-ENOMEM);
23442468

2469+
if (hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) {
2470+
ds4->dongle_state = DONGLE_DISCONNECTED;
2471+
INIT_WORK(&ds4->dongle_hotplug_worker, dualshock4_dongle_calibration_work);
2472+
2473+
/* Override parse report for dongle specific hotplug handling. */
2474+
ps_dev->parse_report = dualshock4_dongle_parse_report;
2475+
}
2476+
23452477
ret = dualshock4_get_mac_address(ds4);
23462478
if (ret) {
23472479
hid_err(hdev, "Failed to get MAC address from DualShock4\n");
@@ -2457,7 +2589,8 @@ static int ps_probe(struct hid_device *hdev, const struct hid_device_id *id)
24572589
}
24582590

24592591
if (hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER ||
2460-
hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) {
2592+
hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 ||
2593+
hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) {
24612594
dev = dualshock4_create(hdev);
24622595
if (IS_ERR(dev)) {
24632596
hid_err(hdev, "Failed to create dualshock4.\n");
@@ -2503,6 +2636,7 @@ static const struct hid_device_id ps_devices[] = {
25032636
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
25042637
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
25052638
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
2639+
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) },
25062640
/* Sony DualSense controllers for PS5 */
25072641
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
25082642
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },

0 commit comments

Comments
 (0)