1919
2020#define STEELSERIES_SRWS1 BIT(0)
2121#define STEELSERIES_ARCTIS_1 BIT(1)
22+ #define STEELSERIES_ARCTIS_9 BIT(2)
2223
2324struct steelseries_device {
2425 struct hid_device * hdev ;
@@ -32,6 +33,7 @@ struct steelseries_device {
3233 struct power_supply * battery ;
3334 uint8_t battery_capacity ;
3435 bool headset_connected ;
36+ bool battery_charging ;
3537};
3638
3739#if IS_BUILTIN (CONFIG_LEDS_CLASS ) || \
@@ -368,32 +370,35 @@ static void steelseries_srws1_remove(struct hid_device *hdev)
368370
369371 hid_hw_stop (hdev );
370372 kfree (drv_data );
371- return ;
372373}
373374#endif
374375
375376#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
376377
377378#define ARCTIS_1_BATTERY_RESPONSE_LEN 8
379+ #define ARCTIS_9_BATTERY_RESPONSE_LEN 64
378380static const char arctis_1_battery_request [] = { 0x06 , 0x12 };
381+ static const char arctis_9_battery_request [] = { 0x00 , 0x20 };
379382
380- static int steelseries_headset_arctis_1_fetch_battery (struct hid_device * hdev )
383+ static int steelseries_headset_request_battery (struct hid_device * hdev ,
384+ const char * request , size_t len )
381385{
382386 u8 * write_buf ;
383387 int ret ;
384388
385389 /* Request battery information */
386- write_buf = kmemdup (arctis_1_battery_request , sizeof ( arctis_1_battery_request ) , GFP_KERNEL );
390+ write_buf = kmemdup (request , len , GFP_KERNEL );
387391 if (!write_buf )
388392 return - ENOMEM ;
389393
390- ret = hid_hw_raw_request (hdev , arctis_1_battery_request [ 0 ],
391- write_buf , sizeof ( arctis_1_battery_request ) ,
394+ hid_dbg (hdev , "Sending battery request report" );
395+ ret = hid_hw_raw_request ( hdev , request [ 0 ], write_buf , len ,
392396 HID_OUTPUT_REPORT , HID_REQ_SET_REPORT );
393- if (ret < (int )sizeof ( arctis_1_battery_request ) ) {
397+ if (ret < (int )len ) {
394398 hid_err (hdev , "hid_hw_raw_request() failed with %d\n" , ret );
395399 ret = - ENODATA ;
396400 }
401+
397402 kfree (write_buf );
398403 return ret ;
399404}
@@ -404,7 +409,11 @@ static void steelseries_headset_fetch_battery(struct hid_device *hdev)
404409 int ret = 0 ;
405410
406411 if (sd -> quirks & STEELSERIES_ARCTIS_1 )
407- ret = steelseries_headset_arctis_1_fetch_battery (hdev );
412+ ret = steelseries_headset_request_battery (hdev ,
413+ arctis_1_battery_request , sizeof (arctis_1_battery_request ));
414+ else if (sd -> quirks & STEELSERIES_ARCTIS_9 )
415+ ret = steelseries_headset_request_battery (hdev ,
416+ arctis_9_battery_request , sizeof (arctis_9_battery_request ));
408417
409418 if (ret < 0 )
410419 hid_dbg (hdev ,
@@ -429,6 +438,9 @@ static void steelseries_headset_battery_timer_tick(struct work_struct *work)
429438 steelseries_headset_fetch_battery (hdev );
430439}
431440
441+ #define STEELSERIES_PREFIX "SteelSeries "
442+ #define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX)
443+
432444static int steelseries_headset_battery_get_property (struct power_supply * psy ,
433445 enum power_supply_property psp ,
434446 union power_supply_propval * val )
@@ -437,13 +449,24 @@ static int steelseries_headset_battery_get_property(struct power_supply *psy,
437449 int ret = 0 ;
438450
439451 switch (psp ) {
452+ case POWER_SUPPLY_PROP_MODEL_NAME :
453+ val -> strval = sd -> hdev -> name ;
454+ while (!strncmp (val -> strval , STEELSERIES_PREFIX , STEELSERIES_PREFIX_LEN ))
455+ val -> strval += STEELSERIES_PREFIX_LEN ;
456+ break ;
457+ case POWER_SUPPLY_PROP_MANUFACTURER :
458+ val -> strval = "SteelSeries" ;
459+ break ;
440460 case POWER_SUPPLY_PROP_PRESENT :
441461 val -> intval = 1 ;
442462 break ;
443463 case POWER_SUPPLY_PROP_STATUS :
444- val -> intval = sd -> headset_connected ?
445- POWER_SUPPLY_STATUS_DISCHARGING :
446- POWER_SUPPLY_STATUS_UNKNOWN ;
464+ if (sd -> headset_connected ) {
465+ val -> intval = sd -> battery_charging ?
466+ POWER_SUPPLY_STATUS_CHARGING :
467+ POWER_SUPPLY_STATUS_DISCHARGING ;
468+ } else
469+ val -> intval = POWER_SUPPLY_STATUS_UNKNOWN ;
447470 break ;
448471 case POWER_SUPPLY_PROP_SCOPE :
449472 val -> intval = POWER_SUPPLY_SCOPE_DEVICE ;
@@ -477,6 +500,8 @@ steelseries_headset_set_wireless_status(struct hid_device *hdev,
477500}
478501
479502static enum power_supply_property steelseries_headset_battery_props [] = {
503+ POWER_SUPPLY_PROP_MODEL_NAME ,
504+ POWER_SUPPLY_PROP_MANUFACTURER ,
480505 POWER_SUPPLY_PROP_PRESENT ,
481506 POWER_SUPPLY_PROP_STATUS ,
482507 POWER_SUPPLY_PROP_SCOPE ,
@@ -505,6 +530,7 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
505530 /* avoid the warning of 0% battery while waiting for the first info */
506531 steelseries_headset_set_wireless_status (sd -> hdev , false);
507532 sd -> battery_capacity = 100 ;
533+ sd -> battery_charging = false;
508534
509535 sd -> battery = devm_power_supply_register (& sd -> hdev -> dev ,
510536 & sd -> battery_desc , & battery_cfg );
@@ -520,9 +546,22 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
520546 INIT_DELAYED_WORK (& sd -> battery_work , steelseries_headset_battery_timer_tick );
521547 steelseries_headset_fetch_battery (sd -> hdev );
522548
549+ if (sd -> quirks & STEELSERIES_ARCTIS_9 ) {
550+ /* The first fetch_battery request can remain unanswered in some cases */
551+ schedule_delayed_work (& sd -> battery_work ,
552+ msecs_to_jiffies (STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS ));
553+ }
554+
523555 return 0 ;
524556}
525557
558+ static bool steelseries_is_vendor_usage_page (struct hid_device * hdev , uint8_t usage_page )
559+ {
560+ return hdev -> rdesc [0 ] == 0x06 &&
561+ hdev -> rdesc [1 ] == usage_page &&
562+ hdev -> rdesc [2 ] == 0xff ;
563+ }
564+
526565static int steelseries_probe (struct hid_device * hdev , const struct hid_device_id * id )
527566{
528567 struct steelseries_device * sd ;
@@ -548,12 +587,20 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
548587 if (ret )
549588 return ret ;
550589
590+ if (sd -> quirks & STEELSERIES_ARCTIS_9 &&
591+ !steelseries_is_vendor_usage_page (hdev , 0xc0 ))
592+ return - ENODEV ;
593+
551594 spin_lock_init (& sd -> lock );
552595
553596 ret = hid_hw_start (hdev , HID_CONNECT_DEFAULT );
554597 if (ret )
555598 return ret ;
556599
600+ ret = hid_hw_open (hdev );
601+ if (ret )
602+ return ret ;
603+
557604 if (steelseries_headset_battery_register (sd ) < 0 )
558605 hid_err (sd -> hdev ,
559606 "Failed to register battery for headset\n" );
@@ -580,6 +627,7 @@ static void steelseries_remove(struct hid_device *hdev)
580627
581628 cancel_delayed_work_sync (& sd -> battery_work );
582629
630+ hid_hw_close (hdev );
583631 hid_hw_stop (hdev );
584632}
585633
@@ -599,13 +647,23 @@ static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
599647 return rdesc ;
600648}
601649
650+ static uint8_t steelseries_headset_map_capacity (uint8_t capacity , uint8_t min_in , uint8_t max_in )
651+ {
652+ if (capacity >= max_in )
653+ return 100 ;
654+ if (capacity <= min_in )
655+ return 0 ;
656+ return (capacity - min_in ) * 100 / (max_in - min_in );
657+ }
658+
602659static int steelseries_headset_raw_event (struct hid_device * hdev ,
603660 struct hid_report * report , u8 * read_buf ,
604661 int size )
605662{
606663 struct steelseries_device * sd = hid_get_drvdata (hdev );
607664 int capacity = sd -> battery_capacity ;
608665 bool connected = sd -> headset_connected ;
666+ bool charging = sd -> battery_charging ;
609667 unsigned long flags ;
610668
611669 /* Not a headset */
@@ -630,6 +688,34 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
630688 }
631689 }
632690
691+ if (sd -> quirks & STEELSERIES_ARCTIS_9 ) {
692+ hid_dbg (sd -> hdev ,
693+ "Parsing raw event for Arctis 9 headset (%*ph)\n" , size , read_buf );
694+ if (size < ARCTIS_9_BATTERY_RESPONSE_LEN ) {
695+ if (!delayed_work_pending (& sd -> battery_work ))
696+ goto request_battery ;
697+ return 0 ;
698+ }
699+
700+ if (read_buf [0 ] == 0xaa && read_buf [1 ] == 0x01 ) {
701+ connected = true;
702+ charging = read_buf [4 ] == 0x01 ;
703+
704+ /*
705+ * Found no official documentation about min and max.
706+ * Values defined by testing.
707+ */
708+ capacity = steelseries_headset_map_capacity (read_buf [3 ], 0x68 , 0x9d );
709+ } else {
710+ /*
711+ * Device is off and sends the last known status read_buf[1] == 0x03 or
712+ * there is no known status of the device read_buf[0] == 0x55
713+ */
714+ connected = false;
715+ charging = false;
716+ }
717+ }
718+
633719 if (connected != sd -> headset_connected ) {
634720 hid_dbg (sd -> hdev ,
635721 "Connected status changed from %sconnected to %sconnected\n" ,
@@ -647,6 +733,15 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
647733 power_supply_changed (sd -> battery );
648734 }
649735
736+ if (charging != sd -> battery_charging ) {
737+ hid_dbg (sd -> hdev ,
738+ "Battery charging status changed from %scharging to %scharging\n" ,
739+ sd -> battery_charging ? "" : "not " ,
740+ charging ? "" : "not " );
741+ sd -> battery_charging = charging ;
742+ power_supply_changed (sd -> battery );
743+ }
744+
650745request_battery :
651746 spin_lock_irqsave (& sd -> lock , flags );
652747 if (!sd -> removed )
@@ -665,6 +760,10 @@ static const struct hid_device_id steelseries_devices[] = {
665760 HID_USB_DEVICE (USB_VENDOR_ID_STEELSERIES , 0x12b6 ),
666761 .driver_data = STEELSERIES_ARCTIS_1 },
667762
763+ { /* SteelSeries Arctis 9 Wireless for XBox */
764+ HID_USB_DEVICE (USB_VENDOR_ID_STEELSERIES , 0x12c2 ),
765+ .driver_data = STEELSERIES_ARCTIS_9 },
766+
668767 { }
669768};
670769MODULE_DEVICE_TABLE (hid , steelseries_devices );
@@ -683,3 +782,4 @@ MODULE_DESCRIPTION("HID driver for Steelseries devices");
683782MODULE_LICENSE ("GPL" );
684783MODULE_AUTHOR ("Bastien Nocera <hadess@hadess.net>" );
685784MODULE_AUTHOR ("Simon Wood <simon@mungewell.org>" );
785+ MODULE_AUTHOR ("Christian Mayer <git@mayer-bgk.de>" );
0 commit comments