xref: /linux/drivers/hid/hid-steelseries-arctis.c (revision 49ef6ed1bbe25cf591af60c2b71cc2a65e07ea68)
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