Skip to content

Commit e24e31e

Browse files
author
Jiri Kosina
committed
Merge branch 'for-6.14/steelseries' into for-linus
- SteelSeries Arctis 9 support (Christian Mayer)
2 parents 068815e + ad8ef3d commit e24e31e

File tree

1 file changed

+110
-10
lines changed

1 file changed

+110
-10
lines changed

drivers/hid/hid-steelseries.c

Lines changed: 110 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#define STEELSERIES_SRWS1 BIT(0)
2121
#define STEELSERIES_ARCTIS_1 BIT(1)
22+
#define STEELSERIES_ARCTIS_9 BIT(2)
2223

2324
struct 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
378380
static 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+
432444
static 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

479502
static 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+
526565
static 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+
602659
static 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+
650745
request_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
};
670769
MODULE_DEVICE_TABLE(hid, steelseries_devices);
@@ -683,3 +782,4 @@ MODULE_DESCRIPTION("HID driver for Steelseries devices");
683782
MODULE_LICENSE("GPL");
684783
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
685784
MODULE_AUTHOR("Simon Wood <simon@mungewell.org>");
785+
MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");

0 commit comments

Comments
 (0)