11// SPDX-License-Identifier: GPL-2.0-or-later
22/*
3- * HID driver for Steelseries SRW-S1
3+ * HID driver for Steelseries devices
44 *
55 * Copyright (c) 2013 Simon Wood
6+ * Copyright (c) 2023 Bastien Nocera
67 */
78
89/*
1112#include <linux/device.h>
1213#include <linux/hid.h>
1314#include <linux/module.h>
15+ #include <linux/usb.h>
1416#include <linux/leds.h>
1517
1618#include "hid-ids.h"
1719
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+
1837#if IS_BUILTIN (CONFIG_LEDS_CLASS ) || \
1938 (IS_MODULE (CONFIG_LEDS_CLASS ) && IS_MODULE (CONFIG_HID_STEELSERIES ))
2039#define SRWS1_NUMBER_LEDS 15
@@ -353,9 +372,211 @@ static void steelseries_srws1_remove(struct hid_device *hdev)
353372}
354373#endif
355374
375+ #define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
376+
377+ #define ARCTIS_1_BATTERY_RESPONSE_LEN 8
378+ static 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+
356573static __u8 * steelseries_srws1_report_fixup (struct hid_device * hdev , __u8 * rdesc ,
357574 unsigned int * rsize )
358575{
576+ if (hdev -> vendor != USB_VENDOR_ID_STEELSERIES ||
577+ hdev -> product != USB_DEVICE_ID_STEELSERIES_SRWS1 )
578+ return rdesc ;
579+
359580 if (* rsize >= 115 && rdesc [11 ] == 0x02 && rdesc [13 ] == 0xc8
360581 && rdesc [29 ] == 0xbb && rdesc [40 ] == 0xc5 ) {
361582 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
365586 return rdesc ;
366587}
367588
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+
370651 { }
371652};
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 ,
383662};
384663
385- module_hid_driver (steelseries_srws1_driver );
664+ module_hid_driver (steelseries_driver );
386665MODULE_LICENSE ("GPL" );
666+ MODULE_AUTHOR ("Bastien Nocera <hadess@hadess.net>" );
667+ MODULE_AUTHOR ("Simon Wood <simon@mungewell.org>" );
0 commit comments