@@ -315,6 +315,11 @@ struct dualsense_output_report {
315
315
#define DS4_STATUS0_CABLE_STATE BIT(4)
316
316
/* Battery status within batery_status field. */
317
317
#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)
318
323
319
324
/* The lower 6 bits of hw_control of the Bluetooth main output report
320
325
* control the interval at which Dualshock 4 reports data:
@@ -344,6 +349,13 @@ struct dualsense_output_report {
344
349
#define DS4_TOUCHPAD_WIDTH 1920
345
350
#define DS4_TOUCHPAD_HEIGHT 942
346
351
352
+ enum dualshock4_dongle_state {
353
+ DONGLE_DISCONNECTED ,
354
+ DONGLE_CALIBRATING ,
355
+ DONGLE_CONNECTED ,
356
+ DONGLE_DISABLED
357
+ };
358
+
347
359
struct dualshock4 {
348
360
struct ps_device base ;
349
361
struct input_dev * gamepad ;
@@ -354,6 +366,11 @@ struct dualshock4 {
354
366
struct ps_calibration_data accel_calib_data [3 ];
355
367
struct ps_calibration_data gyro_calib_data [3 ];
356
368
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
+
357
374
/* Timestamp for sensor data */
358
375
bool sensor_timestamp_initialized ;
359
376
uint32_t prev_sensor_timestamp ;
@@ -513,9 +530,11 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = {
513
530
{0 , 0 },
514
531
};
515
532
533
+ static int dualshock4_get_calibration_data (struct dualshock4 * ds4 );
516
534
static inline void dualsense_schedule_work (struct dualsense * ds );
517
535
static inline void dualshock4_schedule_work (struct dualshock4 * ds4 );
518
536
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 );
519
538
520
539
/*
521
540
* 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)
1678
1697
return ERR_PTR (ret );
1679
1698
}
1680
1699
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
+
1681
1727
static int dualshock4_get_calibration_data (struct dualshock4 * ds4 )
1682
1728
{
1683
1729
struct hid_device * hdev = ds4 -> base .hdev ;
@@ -1694,15 +1740,34 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
1694
1740
uint8_t * buf ;
1695
1741
1696
1742
if (ds4 -> base .hdev -> bus == BUS_USB ) {
1743
+ int retries ;
1744
+
1697
1745
buf = kzalloc (DS4_FEATURE_REPORT_CALIBRATION_SIZE , GFP_KERNEL );
1698
1746
if (!buf )
1699
1747
return - ENOMEM ;
1700
1748
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
+ }
1706
1771
}
1707
1772
} else { /* Bluetooth */
1708
1773
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 *
2220
2285
return 0 ;
2221
2286
}
2222
2287
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
+
2223
2344
static int dualshock4_play_effect (struct input_dev * dev , void * data , struct ff_effect * effect )
2224
2345
{
2225
2346
struct hid_device * hdev = input_get_drvdata (dev );
@@ -2249,6 +2370,9 @@ static void dualshock4_remove(struct ps_device *ps_dev)
2249
2370
spin_unlock_irqrestore (& ds4 -> base .lock , flags );
2250
2371
2251
2372
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 );
2252
2376
}
2253
2377
2254
2378
static inline void dualshock4_schedule_work (struct dualshock4 * ds4 )
@@ -2342,6 +2466,14 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
2342
2466
if (!ds4 -> output_report_dmabuf )
2343
2467
return ERR_PTR (- ENOMEM );
2344
2468
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
+
2345
2477
ret = dualshock4_get_mac_address (ds4 );
2346
2478
if (ret ) {
2347
2479
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)
2457
2589
}
2458
2590
2459
2591
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 ) {
2461
2594
dev = dualshock4_create (hdev );
2462
2595
if (IS_ERR (dev )) {
2463
2596
hid_err (hdev , "Failed to create dualshock4.\n" );
@@ -2503,6 +2636,7 @@ static const struct hid_device_id ps_devices[] = {
2503
2636
{ HID_USB_DEVICE (USB_VENDOR_ID_SONY , USB_DEVICE_ID_SONY_PS4_CONTROLLER ) },
2504
2637
{ HID_BLUETOOTH_DEVICE (USB_VENDOR_ID_SONY , USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 ) },
2505
2638
{ 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 ) },
2506
2640
/* Sony DualSense controllers for PS5 */
2507
2641
{ HID_BLUETOOTH_DEVICE (USB_VENDOR_ID_SONY , USB_DEVICE_ID_SONY_PS5_CONTROLLER ) },
2508
2642
{ HID_USB_DEVICE (USB_VENDOR_ID_SONY , USB_DEVICE_ID_SONY_PS5_CONTROLLER ) },
0 commit comments