1
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
2
/*
3
- * HID driver for Steelseries SRW-S1
3
+ * HID driver for Steelseries devices
4
4
*
5
5
* Copyright (c) 2013 Simon Wood
6
+ * Copyright (c) 2023 Bastien Nocera
6
7
*/
7
8
8
9
/*
11
12
#include <linux/device.h>
12
13
#include <linux/hid.h>
13
14
#include <linux/module.h>
15
+ #include <linux/usb.h>
14
16
#include <linux/leds.h>
15
17
16
18
#include "hid-ids.h"
17
19
20
+ #define STEELSERIES_SRWS1 BIT(0)
21
+ #define STEELSERIES_ARCTIS_1 BIT(1)
22
+
23
+ struct steelseries_device {
24
+ struct hid_device * hdev ;
25
+ unsigned long quirks ;
26
+
27
+ struct delayed_work battery_work ;
28
+ spinlock_t lock ;
29
+ bool removed ;
30
+
31
+ struct power_supply_desc battery_desc ;
32
+ struct power_supply * battery ;
33
+ uint8_t battery_capacity ;
34
+ bool headset_connected ;
35
+ };
36
+
18
37
#if IS_BUILTIN (CONFIG_LEDS_CLASS ) || \
19
38
(IS_MODULE (CONFIG_LEDS_CLASS ) && IS_MODULE (CONFIG_HID_STEELSERIES ))
20
39
#define SRWS1_NUMBER_LEDS 15
@@ -353,9 +372,211 @@ static void steelseries_srws1_remove(struct hid_device *hdev)
353
372
}
354
373
#endif
355
374
375
+ #define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
376
+
377
+ #define ARCTIS_1_BATTERY_RESPONSE_LEN 8
378
+ const char arctis_1_battery_request [] = { 0x06 , 0x12 };
379
+
380
+ static int steelseries_headset_arctis_1_fetch_battery (struct hid_device * hdev )
381
+ {
382
+ u8 * write_buf ;
383
+ int ret ;
384
+
385
+ /* Request battery information */
386
+ write_buf = kmemdup (arctis_1_battery_request , sizeof (arctis_1_battery_request ), GFP_KERNEL );
387
+ if (!write_buf )
388
+ return - ENOMEM ;
389
+
390
+ ret = hid_hw_raw_request (hdev , arctis_1_battery_request [0 ],
391
+ write_buf , sizeof (arctis_1_battery_request ),
392
+ HID_OUTPUT_REPORT , HID_REQ_SET_REPORT );
393
+ if (ret < sizeof (arctis_1_battery_request )) {
394
+ hid_err (hdev , "hid_hw_raw_request() failed with %d\n" , ret );
395
+ ret = - ENODATA ;
396
+ }
397
+ kfree (write_buf );
398
+ return ret ;
399
+ }
400
+
401
+ static void steelseries_headset_fetch_battery (struct hid_device * hdev )
402
+ {
403
+ struct steelseries_device * sd = hid_get_drvdata (hdev );
404
+ int ret = 0 ;
405
+
406
+ if (sd -> quirks & STEELSERIES_ARCTIS_1 )
407
+ ret = steelseries_headset_arctis_1_fetch_battery (hdev );
408
+
409
+ if (ret < 0 )
410
+ hid_dbg (hdev ,
411
+ "Battery query failed (err: %d)\n" , ret );
412
+ }
413
+
414
+ static void steelseries_headset_battery_timer_tick (struct work_struct * work )
415
+ {
416
+ struct steelseries_device * sd = container_of (work ,
417
+ struct steelseries_device , battery_work .work );
418
+ struct hid_device * hdev = sd -> hdev ;
419
+
420
+ steelseries_headset_fetch_battery (hdev );
421
+ }
422
+
423
+ static int steelseries_headset_battery_get_property (struct power_supply * psy ,
424
+ enum power_supply_property psp ,
425
+ union power_supply_propval * val )
426
+ {
427
+ struct steelseries_device * sd = power_supply_get_drvdata (psy );
428
+ int ret = 0 ;
429
+
430
+ switch (psp ) {
431
+ case POWER_SUPPLY_PROP_PRESENT :
432
+ val -> intval = 1 ;
433
+ break ;
434
+ case POWER_SUPPLY_PROP_STATUS :
435
+ val -> intval = sd -> headset_connected ?
436
+ POWER_SUPPLY_STATUS_DISCHARGING :
437
+ POWER_SUPPLY_STATUS_UNKNOWN ;
438
+ break ;
439
+ case POWER_SUPPLY_PROP_SCOPE :
440
+ val -> intval = POWER_SUPPLY_SCOPE_DEVICE ;
441
+ break ;
442
+ case POWER_SUPPLY_PROP_CAPACITY :
443
+ val -> intval = sd -> battery_capacity ;
444
+ break ;
445
+ default :
446
+ ret = - EINVAL ;
447
+ break ;
448
+ }
449
+ return ret ;
450
+ }
451
+
452
+ static void
453
+ steelseries_headset_set_wireless_status (struct hid_device * hdev ,
454
+ bool connected )
455
+ {
456
+ struct usb_interface * intf ;
457
+
458
+ if (!hid_is_usb (hdev ))
459
+ return ;
460
+
461
+ intf = to_usb_interface (hdev -> dev .parent );
462
+ usb_set_wireless_status (intf , connected ?
463
+ USB_WIRELESS_STATUS_CONNECTED :
464
+ USB_WIRELESS_STATUS_DISCONNECTED );
465
+ }
466
+
467
+ static enum power_supply_property steelseries_headset_battery_props [] = {
468
+ POWER_SUPPLY_PROP_PRESENT ,
469
+ POWER_SUPPLY_PROP_STATUS ,
470
+ POWER_SUPPLY_PROP_SCOPE ,
471
+ POWER_SUPPLY_PROP_CAPACITY ,
472
+ };
473
+
474
+ static int steelseries_headset_battery_register (struct steelseries_device * sd )
475
+ {
476
+ static atomic_t battery_no = ATOMIC_INIT (0 );
477
+ struct power_supply_config battery_cfg = { .drv_data = sd , };
478
+ unsigned long n ;
479
+ int ret ;
480
+
481
+ sd -> battery_desc .type = POWER_SUPPLY_TYPE_BATTERY ;
482
+ sd -> battery_desc .properties = steelseries_headset_battery_props ;
483
+ sd -> battery_desc .num_properties = ARRAY_SIZE (steelseries_headset_battery_props );
484
+ sd -> battery_desc .get_property = steelseries_headset_battery_get_property ;
485
+ sd -> battery_desc .use_for_apm = 0 ;
486
+ n = atomic_inc_return (& battery_no ) - 1 ;
487
+ sd -> battery_desc .name = devm_kasprintf (& sd -> hdev -> dev , GFP_KERNEL ,
488
+ "steelseries_headset_battery_%ld" , n );
489
+ if (!sd -> battery_desc .name )
490
+ return - ENOMEM ;
491
+
492
+ /* avoid the warning of 0% battery while waiting for the first info */
493
+ steelseries_headset_set_wireless_status (sd -> hdev , false);
494
+ sd -> battery_capacity = 100 ;
495
+
496
+ sd -> battery = devm_power_supply_register (& sd -> hdev -> dev ,
497
+ & sd -> battery_desc , & battery_cfg );
498
+ if (IS_ERR (sd -> battery )) {
499
+ ret = PTR_ERR (sd -> battery );
500
+ hid_err (sd -> hdev ,
501
+ "%s:power_supply_register failed with error %d\n" ,
502
+ __func__ , ret );
503
+ return ret ;
504
+ }
505
+ power_supply_powers (sd -> battery , & sd -> hdev -> dev );
506
+
507
+ INIT_DELAYED_WORK (& sd -> battery_work , steelseries_headset_battery_timer_tick );
508
+ steelseries_headset_fetch_battery (sd -> hdev );
509
+
510
+ return 0 ;
511
+ }
512
+
513
+ static int steelseries_probe (struct hid_device * hdev , const struct hid_device_id * id )
514
+ {
515
+ struct steelseries_device * sd ;
516
+ int ret ;
517
+
518
+ sd = devm_kzalloc (& hdev -> dev , sizeof (* sd ), GFP_KERNEL );
519
+ if (!sd )
520
+ return - ENOMEM ;
521
+ hid_set_drvdata (hdev , sd );
522
+ sd -> hdev = hdev ;
523
+ sd -> quirks = id -> driver_data ;
524
+
525
+ if (sd -> quirks & STEELSERIES_SRWS1 ) {
526
+ #if IS_BUILTIN (CONFIG_LEDS_CLASS ) || \
527
+ (IS_MODULE (CONFIG_LEDS_CLASS ) && IS_MODULE (CONFIG_HID_STEELSERIES ))
528
+ return steelseries_srws1_probe (hdev , id );
529
+ #else
530
+ return - ENODEV ;
531
+ #endif
532
+ }
533
+
534
+ ret = hid_parse (hdev );
535
+ if (ret )
536
+ return ret ;
537
+
538
+ spin_lock_init (& sd -> lock );
539
+
540
+ ret = hid_hw_start (hdev , HID_CONNECT_DEFAULT );
541
+ if (ret )
542
+ return ret ;
543
+
544
+ if (steelseries_headset_battery_register (sd ) < 0 )
545
+ hid_err (sd -> hdev ,
546
+ "Failed to register battery for headset\n" );
547
+
548
+ return ret ;
549
+ }
550
+
551
+ static void steelseries_remove (struct hid_device * hdev )
552
+ {
553
+ struct steelseries_device * sd = hid_get_drvdata (hdev );
554
+ unsigned long flags ;
555
+
556
+ if (sd -> quirks & STEELSERIES_SRWS1 ) {
557
+ #if IS_BUILTIN (CONFIG_LEDS_CLASS ) || \
558
+ (IS_MODULE (CONFIG_LEDS_CLASS ) && IS_MODULE (CONFIG_HID_STEELSERIES ))
559
+ steelseries_srws1_remove (hdev );
560
+ #endif
561
+ return ;
562
+ }
563
+
564
+ spin_lock_irqsave (& sd -> lock , flags );
565
+ sd -> removed = true;
566
+ spin_unlock_irqrestore (& sd -> lock , flags );
567
+
568
+ cancel_delayed_work_sync (& sd -> battery_work );
569
+
570
+ hid_hw_stop (hdev );
571
+ }
572
+
356
573
static __u8 * steelseries_srws1_report_fixup (struct hid_device * hdev , __u8 * rdesc ,
357
574
unsigned int * rsize )
358
575
{
576
+ if (hdev -> vendor != USB_VENDOR_ID_STEELSERIES ||
577
+ hdev -> product != USB_DEVICE_ID_STEELSERIES_SRWS1 )
578
+ return rdesc ;
579
+
359
580
if (* rsize >= 115 && rdesc [11 ] == 0x02 && rdesc [13 ] == 0xc8
360
581
&& rdesc [29 ] == 0xbb && rdesc [40 ] == 0xc5 ) {
361
582
hid_info (hdev , "Fixing up Steelseries SRW-S1 report descriptor\n" );
@@ -365,22 +586,82 @@ static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc
365
586
return rdesc ;
366
587
}
367
588
368
- static const struct hid_device_id steelseries_srws1_devices [] = {
369
- { HID_USB_DEVICE (USB_VENDOR_ID_STEELSERIES , USB_DEVICE_ID_STEELSERIES_SRWS1 ) },
589
+ static int steelseries_headset_raw_event (struct hid_device * hdev ,
590
+ struct hid_report * report , u8 * read_buf ,
591
+ int size )
592
+ {
593
+ struct steelseries_device * sd = hid_get_drvdata (hdev );
594
+ int capacity = sd -> battery_capacity ;
595
+ bool connected = sd -> headset_connected ;
596
+ unsigned long flags ;
597
+
598
+ /* Not a headset */
599
+ if (sd -> quirks & STEELSERIES_SRWS1 )
600
+ return 0 ;
601
+
602
+ if (sd -> quirks & STEELSERIES_ARCTIS_1 ) {
603
+ hid_dbg (sd -> hdev ,
604
+ "Parsing raw event for Arctis 1 headset (%*ph)\n" , size , read_buf );
605
+ if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
606
+ memcmp (read_buf , arctis_1_battery_request , sizeof (arctis_1_battery_request )))
607
+ return 0 ;
608
+ if (read_buf [2 ] == 0x01 ) {
609
+ connected = false;
610
+ capacity = 100 ;
611
+ } else {
612
+ connected = true;
613
+ capacity = read_buf [3 ];
614
+ }
615
+ }
616
+
617
+ if (connected != sd -> headset_connected ) {
618
+ hid_dbg (sd -> hdev ,
619
+ "Connected status changed from %sconnected to %sconnected\n" ,
620
+ sd -> headset_connected ? "" : "not " ,
621
+ connected ? "" : "not " );
622
+ sd -> headset_connected = connected ;
623
+ steelseries_headset_set_wireless_status (hdev , connected );
624
+ }
625
+
626
+ if (capacity != sd -> battery_capacity ) {
627
+ hid_dbg (sd -> hdev ,
628
+ "Battery capacity changed from %d%% to %d%%\n" ,
629
+ sd -> battery_capacity , capacity );
630
+ sd -> battery_capacity = capacity ;
631
+ power_supply_changed (sd -> battery );
632
+ }
633
+
634
+ spin_lock_irqsave (& sd -> lock , flags );
635
+ if (!sd -> removed )
636
+ schedule_delayed_work (& sd -> battery_work ,
637
+ msecs_to_jiffies (STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS ));
638
+ spin_unlock_irqrestore (& sd -> lock , flags );
639
+
640
+ return 0 ;
641
+ }
642
+
643
+ static const struct hid_device_id steelseries_devices [] = {
644
+ { HID_USB_DEVICE (USB_VENDOR_ID_STEELSERIES , USB_DEVICE_ID_STEELSERIES_SRWS1 ),
645
+ .driver_data = STEELSERIES_SRWS1 },
646
+
647
+ { /* SteelSeries Arctis 1 Wireless for XBox */
648
+ HID_USB_DEVICE (USB_VENDOR_ID_STEELSERIES , 0x12b6 ),
649
+ .driver_data = STEELSERIES_ARCTIS_1 },
650
+
370
651
{ }
371
652
};
372
- MODULE_DEVICE_TABLE (hid , steelseries_srws1_devices );
373
-
374
- static struct hid_driver steelseries_srws1_driver = {
375
- .name = "steelseries_srws1" ,
376
- .id_table = steelseries_srws1_devices ,
377
- #if IS_BUILTIN (CONFIG_LEDS_CLASS ) || \
378
- (IS_MODULE (CONFIG_LEDS_CLASS ) && IS_MODULE (CONFIG_HID_STEELSERIES ))
379
- .probe = steelseries_srws1_probe ,
380
- .remove = steelseries_srws1_remove ,
381
- #endif
382
- .report_fixup = steelseries_srws1_report_fixup
653
+ MODULE_DEVICE_TABLE (hid , steelseries_devices );
654
+
655
+ static struct hid_driver steelseries_driver = {
656
+ .name = "steelseries" ,
657
+ .id_table = steelseries_devices ,
658
+ .probe = steelseries_probe ,
659
+ .remove = steelseries_remove ,
660
+ .report_fixup = steelseries_srws1_report_fixup ,
661
+ .raw_event = steelseries_headset_raw_event ,
383
662
};
384
663
385
- module_hid_driver (steelseries_srws1_driver );
664
+ module_hid_driver (steelseries_driver );
386
665
MODULE_LICENSE ("GPL" );
666
+ MODULE_AUTHOR (
"Bastien Nocera <[email protected] >" );
667
+ MODULE_AUTHOR (
"Simon Wood <[email protected] >" );
0 commit comments