1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * HID driver for Steelseries arctis headsets 4 * 5 * Copyright (c) 2023 Bastien Nocera 6 * Copyright (c) 2026 Sriman Achanta 7 */ 8 9 #include <linux/device.h> 10 #include <linux/hid.h> 11 #include <linux/module.h> 12 #include <linux/power_supply.h> 13 #include <linux/spinlock.h> 14 #include <linux/usb.h> 15 #include <linux/workqueue.h> 16 17 #include "hid-ids.h" 18 19 #define SS_CAP_BATTERY BIT(0) 20 21 struct steelseries_device; 22 23 struct steelseries_device_info { 24 unsigned long capabilities; 25 26 u8 sync_interface; 27 28 int (*request_status)(struct hid_device *hdev); 29 void (*parse_status)(struct steelseries_device *sd, u8 *data, int size); 30 }; 31 32 struct steelseries_device { 33 struct hid_device *hdev; 34 const struct steelseries_device_info *info; 35 36 struct delayed_work status_work; 37 38 struct power_supply_desc battery_desc; 39 struct power_supply *battery; 40 bool headset_connected; 41 u8 battery_capacity; 42 bool battery_charging; 43 44 spinlock_t lock; 45 bool removed; 46 }; 47 48 /* 49 * Headset report helpers 50 */ 51 52 static int steelseries_send_report(struct hid_device *hdev, const u8 *data, 53 int len, enum hid_report_type type) 54 { 55 u8 *buf; 56 int ret; 57 58 buf = kmemdup(data, len, GFP_KERNEL); 59 if (!buf) 60 return -ENOMEM; 61 62 ret = hid_hw_raw_request(hdev, data[0], buf, len, type, 63 HID_REQ_SET_REPORT); 64 kfree(buf); 65 66 if (ret < 0) 67 return ret; 68 if (ret < len) 69 return -EIO; 70 71 return 0; 72 } 73 74 static inline int steelseries_send_output_report(struct hid_device *hdev, 75 const u8 *data, int len) 76 { 77 return steelseries_send_report(hdev, data, len, HID_OUTPUT_REPORT); 78 } 79 80 /* 81 * Headset status request functions 82 */ 83 84 static int steelseries_arctis_1_request_status(struct hid_device *hdev) 85 { 86 const u8 data[] = { 0x06, 0x12 }; 87 88 return steelseries_send_output_report(hdev, data, sizeof(data)); 89 } 90 91 static int steelseries_arctis_9_request_status(struct hid_device *hdev) 92 { 93 const u8 data[] = { 0x00, 0x20 }; 94 95 return steelseries_send_output_report(hdev, data, sizeof(data)); 96 } 97 98 /* 99 * Headset battery helpers 100 */ 101 102 static int battery_capacity_to_level(int capacity) 103 { 104 if (capacity >= 50) 105 return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; 106 if (capacity >= 20) 107 return POWER_SUPPLY_CAPACITY_LEVEL_LOW; 108 return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; 109 } 110 111 static u8 steelseries_map_capacity(u8 capacity, u8 min_in, u8 max_in) 112 { 113 if (capacity >= max_in) 114 return 100; 115 if (capacity <= min_in) 116 return 0; 117 return (capacity - min_in) * 100 / (max_in - min_in); 118 } 119 120 /* 121 * Headset status parse functions 122 */ 123 124 static void steelseries_arctis_1_parse_status(struct steelseries_device *sd, 125 u8 *data, int size) 126 { 127 /* Only the battery status report echoes the request header. */ 128 if (size < 8 || data[0] != 0x06 || data[1] != 0x12) 129 return; 130 131 sd->headset_connected = (data[2] != 0x01); 132 sd->battery_capacity = data[3]; 133 } 134 135 static void steelseries_arctis_9_parse_status(struct steelseries_device *sd, 136 u8 *data, int size) 137 { 138 if (size < 5) 139 return; 140 141 if (data[0] == 0xaa && data[1] == 0x01) { 142 sd->headset_connected = true; 143 sd->battery_charging = (data[4] == 0x01); 144 sd->battery_capacity = steelseries_map_capacity(data[3], 0x68, 0x9d); 145 } else { 146 /* Device off: 0x55 (no status) or 0x03 (stale status). */ 147 sd->headset_connected = false; 148 sd->battery_charging = false; 149 } 150 } 151 152 /* 153 * Device info definitions 154 */ 155 156 static const struct steelseries_device_info arctis_1_info = { 157 .sync_interface = 3, 158 .capabilities = SS_CAP_BATTERY, 159 .request_status = steelseries_arctis_1_request_status, 160 .parse_status = steelseries_arctis_1_parse_status, 161 }; 162 163 static const struct steelseries_device_info arctis_9_info = { 164 .sync_interface = 0, 165 .capabilities = SS_CAP_BATTERY, 166 .request_status = steelseries_arctis_9_request_status, 167 .parse_status = steelseries_arctis_9_parse_status, 168 }; 169 170 /* 171 * Headset wireless status and battery infrastructure 172 */ 173 174 #define STEELSERIES_HEADSET_STATUS_TIMEOUT_MS 3000 175 176 static void 177 steelseries_headset_set_wireless_status(struct hid_device *hdev, 178 bool connected) 179 { 180 struct usb_interface *intf; 181 182 if (!hid_is_usb(hdev)) 183 return; 184 185 intf = to_usb_interface(hdev->dev.parent); 186 usb_set_wireless_status(intf, connected ? 187 USB_WIRELESS_STATUS_CONNECTED : 188 USB_WIRELESS_STATUS_DISCONNECTED); 189 } 190 191 #define STEELSERIES_PREFIX "SteelSeries " 192 193 static int steelseries_battery_get_property(struct power_supply *psy, 194 enum power_supply_property psp, 195 union power_supply_propval *val) 196 { 197 struct steelseries_device *sd = power_supply_get_drvdata(psy); 198 size_t prefix_len; 199 int ret = 0; 200 201 switch (psp) { 202 case POWER_SUPPLY_PROP_MODEL_NAME: 203 val->strval = sd->hdev->name; 204 while ((prefix_len = str_has_prefix(val->strval, STEELSERIES_PREFIX))) 205 val->strval += prefix_len; 206 break; 207 case POWER_SUPPLY_PROP_MANUFACTURER: 208 val->strval = "SteelSeries"; 209 break; 210 case POWER_SUPPLY_PROP_PRESENT: 211 val->intval = 1; 212 break; 213 case POWER_SUPPLY_PROP_STATUS: 214 if (!sd->headset_connected) 215 val->intval = POWER_SUPPLY_STATUS_UNKNOWN; 216 else if (sd->battery_charging) 217 val->intval = POWER_SUPPLY_STATUS_CHARGING; 218 else 219 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 220 break; 221 case POWER_SUPPLY_PROP_SCOPE: 222 val->intval = POWER_SUPPLY_SCOPE_DEVICE; 223 break; 224 case POWER_SUPPLY_PROP_CAPACITY: 225 val->intval = sd->battery_capacity; 226 break; 227 case POWER_SUPPLY_PROP_CAPACITY_LEVEL: 228 val->intval = battery_capacity_to_level(sd->battery_capacity); 229 break; 230 default: 231 ret = -EINVAL; 232 break; 233 } 234 return ret; 235 } 236 237 static enum power_supply_property steelseries_battery_props[] = { 238 POWER_SUPPLY_PROP_MODEL_NAME, 239 POWER_SUPPLY_PROP_MANUFACTURER, 240 POWER_SUPPLY_PROP_PRESENT, 241 POWER_SUPPLY_PROP_STATUS, 242 POWER_SUPPLY_PROP_SCOPE, 243 POWER_SUPPLY_PROP_CAPACITY, 244 POWER_SUPPLY_PROP_CAPACITY_LEVEL, 245 }; 246 247 /* 248 * Delayed work handlers for status polling 249 */ 250 251 static void steelseries_status_timer_work_handler(struct work_struct *work) 252 { 253 struct steelseries_device *sd = container_of( 254 work, struct steelseries_device, status_work.work); 255 unsigned long flags; 256 257 sd->info->request_status(sd->hdev); 258 259 spin_lock_irqsave(&sd->lock, flags); 260 if (!sd->removed) 261 schedule_delayed_work(&sd->status_work, 262 msecs_to_jiffies(STEELSERIES_HEADSET_STATUS_TIMEOUT_MS)); 263 spin_unlock_irqrestore(&sd->lock, flags); 264 } 265 266 static int steelseries_battery_register(struct steelseries_device *sd) 267 { 268 static atomic_t battery_no = ATOMIC_INIT(0); 269 struct power_supply_config battery_cfg = { .drv_data = sd, }; 270 unsigned long n; 271 int ret; 272 273 sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; 274 sd->battery_desc.properties = steelseries_battery_props; 275 sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_battery_props); 276 sd->battery_desc.get_property = steelseries_battery_get_property; 277 sd->battery_desc.use_for_apm = 0; 278 n = atomic_inc_return(&battery_no) - 1; 279 sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL, 280 "steelseries_headset_battery_%ld", n); 281 if (!sd->battery_desc.name) 282 return -ENOMEM; 283 284 /* avoid the warning of 0% battery while waiting for the first info */ 285 sd->battery_capacity = 100; 286 sd->battery_charging = false; 287 sd->headset_connected = false; 288 steelseries_headset_set_wireless_status(sd->hdev, false); 289 290 sd->battery = devm_power_supply_register(&sd->hdev->dev, 291 &sd->battery_desc, &battery_cfg); 292 if (IS_ERR(sd->battery)) { 293 ret = PTR_ERR(sd->battery); 294 sd->battery = NULL; 295 hid_err(sd->hdev, 296 "%s:power_supply_register failed with error %d\n", 297 __func__, ret); 298 return ret; 299 } 300 power_supply_powers(sd->battery, &sd->hdev->dev); 301 302 return 0; 303 } 304 305 static int steelseries_arctis_probe(struct hid_device *hdev, 306 const struct hid_device_id *id) 307 { 308 const struct steelseries_device_info *info = 309 (const struct steelseries_device_info *)id->driver_data; 310 struct steelseries_device *sd; 311 struct usb_interface *intf; 312 u8 interface_num; 313 int ret; 314 315 if (hid_is_usb(hdev)) { 316 intf = to_usb_interface(hdev->dev.parent); 317 interface_num = intf->cur_altsetting->desc.bInterfaceNumber; 318 } else { 319 return -ENODEV; 320 } 321 322 ret = hid_parse(hdev); 323 if (ret) 324 return ret; 325 326 /* Let hid-generic handle non-sync interfaces */ 327 if (interface_num != info->sync_interface) 328 return hid_hw_start(hdev, HID_CONNECT_DEFAULT); 329 330 sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); 331 if (!sd) 332 return -ENOMEM; 333 334 sd->hdev = hdev; 335 sd->info = info; 336 spin_lock_init(&sd->lock); 337 338 hid_set_drvdata(hdev, sd); 339 340 ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); 341 if (ret) 342 return ret; 343 344 ret = hid_hw_open(hdev); 345 if (ret) 346 goto err_stop; 347 348 if (info->capabilities & SS_CAP_BATTERY) { 349 ret = steelseries_battery_register(sd); 350 if (ret < 0) 351 hid_warn(hdev, "Failed to register battery: %d\n", ret); 352 } 353 354 INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler); 355 schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100)); 356 357 return 0; 358 359 err_stop: 360 hid_hw_stop(hdev); 361 return ret; 362 } 363 364 static void steelseries_arctis_remove(struct hid_device *hdev) 365 { 366 struct steelseries_device *sd; 367 unsigned long flags; 368 struct usb_interface *intf; 369 u8 interface_num; 370 371 if (hid_is_usb(hdev)) { 372 intf = to_usb_interface(hdev->dev.parent); 373 interface_num = intf->cur_altsetting->desc.bInterfaceNumber; 374 } else { 375 return; 376 } 377 378 sd = hid_get_drvdata(hdev); 379 380 if (!sd) { 381 hid_hw_stop(hdev); 382 return; 383 } 384 385 if (interface_num == sd->info->sync_interface) { 386 spin_lock_irqsave(&sd->lock, flags); 387 sd->removed = true; 388 spin_unlock_irqrestore(&sd->lock, flags); 389 390 cancel_delayed_work_sync(&sd->status_work); 391 } 392 393 hid_hw_close(hdev); 394 hid_hw_stop(hdev); 395 } 396 397 static int steelseries_arctis_raw_event(struct hid_device *hdev, 398 struct hid_report *report, u8 *data, int size) 399 { 400 struct steelseries_device *sd = hid_get_drvdata(hdev); 401 u8 old_capacity; 402 bool old_connected; 403 bool old_charging; 404 405 if (!sd) 406 return 0; 407 408 old_capacity = sd->battery_capacity; 409 old_connected = sd->headset_connected; 410 old_charging = sd->battery_charging; 411 412 sd->info->parse_status(sd, data, size); 413 414 if (sd->headset_connected != old_connected) { 415 hid_dbg(hdev, 416 "Connected status changed from %sconnected to %sconnected\n", 417 old_connected ? "" : "not ", 418 sd->headset_connected ? "" : "not "); 419 420 if (sd->battery) { 421 steelseries_headset_set_wireless_status(sd->hdev, 422 sd->headset_connected); 423 power_supply_changed(sd->battery); 424 } 425 } 426 427 if (sd->battery_capacity != old_capacity) { 428 hid_dbg(hdev, "Battery capacity changed from %d%% to %d%%\n", 429 old_capacity, sd->battery_capacity); 430 if (sd->battery) 431 power_supply_changed(sd->battery); 432 } 433 434 if (sd->battery_charging != old_charging) { 435 hid_dbg(hdev, 436 "Battery charging status changed from %scharging to %scharging\n", 437 old_charging ? "" : "not ", 438 sd->battery_charging ? "" : "not "); 439 if (sd->battery) 440 power_supply_changed(sd->battery); 441 } 442 443 return 0; 444 } 445 446 static const struct hid_device_id steelseries_arctis_devices[] = { 447 { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 448 USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X), 449 .driver_data = (unsigned long)&arctis_1_info }, 450 { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 451 USB_DEVICE_ID_STEELSERIES_ARCTIS_9), 452 .driver_data = (unsigned long)&arctis_9_info }, 453 {} 454 }; 455 MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices); 456 457 static struct hid_driver steelseries_arctis_driver = { 458 .name = "hid-steelseries-arctis", 459 .id_table = steelseries_arctis_devices, 460 .probe = steelseries_arctis_probe, 461 .remove = steelseries_arctis_remove, 462 .raw_event = steelseries_arctis_raw_event, 463 }; 464 465 module_hid_driver(steelseries_arctis_driver); 466 MODULE_DESCRIPTION("HID driver for Steelseries arctis headsets"); 467 MODULE_LICENSE("GPL"); 468 MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>"); 469 MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>"); 470 MODULE_AUTHOR("Sriman Achanta <srimanachanta@gmail.com>"); 471