xref: /linux/drivers/hid/hid-steelseries-arctis.c (revision 1fc5a74b108fc90951890ec513ac81869f5eaff1)
13d569aafSSriman Achanta // SPDX-License-Identifier: GPL-2.0-or-later
23d569aafSSriman Achanta /*
33d569aafSSriman Achanta  *  HID driver for Steelseries arctis headsets
43d569aafSSriman Achanta  *
53d569aafSSriman Achanta  *  Copyright (c) 2023 Bastien Nocera
649ef6ed1SSriman Achanta  *  Copyright (c) 2026 Sriman Achanta
73d569aafSSriman Achanta  */
83d569aafSSriman Achanta 
93d569aafSSriman Achanta #include <linux/device.h>
103d569aafSSriman Achanta #include <linux/hid.h>
119bc17130SSriman Achanta #include <linux/kref.h>
129bc17130SSriman Achanta #include <linux/slab.h>
133d569aafSSriman Achanta #include <linux/module.h>
143d569aafSSriman Achanta #include <linux/power_supply.h>
153d569aafSSriman Achanta #include <linux/spinlock.h>
163d569aafSSriman Achanta #include <linux/usb.h>
173d569aafSSriman Achanta #include <linux/workqueue.h>
183d569aafSSriman Achanta 
193d569aafSSriman Achanta #include "hid-ids.h"
203d569aafSSriman Achanta 
2149ef6ed1SSriman Achanta #define SS_CAP_BATTERY			BIT(0)
2249ef6ed1SSriman Achanta 
2349ef6ed1SSriman Achanta struct steelseries_device;
2449ef6ed1SSriman Achanta 
2549ef6ed1SSriman Achanta struct steelseries_device_info {
2649ef6ed1SSriman Achanta 	unsigned long capabilities;
2749ef6ed1SSriman Achanta 
2849ef6ed1SSriman Achanta 	u8 sync_interface;
29bebeca20SSriman Achanta 	u8 async_interface;
3049ef6ed1SSriman Achanta 
3149ef6ed1SSriman Achanta 	int (*request_status)(struct hid_device *hdev);
3249ef6ed1SSriman Achanta 	void (*parse_status)(struct steelseries_device *sd, u8 *data, int size);
3349ef6ed1SSriman Achanta };
343d569aafSSriman Achanta 
353d569aafSSriman Achanta struct steelseries_device {
369bc17130SSriman Achanta 	struct kref refcnt;
379bc17130SSriman Achanta 
383d569aafSSriman Achanta 	struct hid_device *hdev;
3949ef6ed1SSriman Achanta 	const struct steelseries_device_info *info;
403d569aafSSriman Achanta 
4149ef6ed1SSriman Achanta 	struct delayed_work status_work;
423d569aafSSriman Achanta 
433d569aafSSriman Achanta 	struct power_supply_desc battery_desc;
443d569aafSSriman Achanta 	struct power_supply *battery;
453d569aafSSriman Achanta 	bool headset_connected;
4649ef6ed1SSriman Achanta 	u8 battery_capacity;
473d569aafSSriman Achanta 	bool battery_charging;
4849ef6ed1SSriman Achanta 
4949ef6ed1SSriman Achanta 	spinlock_t lock;
5049ef6ed1SSriman Achanta 	bool removed;
513d569aafSSriman Achanta };
523d569aafSSriman Achanta 
steelseries_device_release(struct kref * ref)539bc17130SSriman Achanta static void steelseries_device_release(struct kref *ref)
549bc17130SSriman Achanta {
559bc17130SSriman Achanta 	struct steelseries_device *sd =
569bc17130SSriman Achanta 		container_of(ref, struct steelseries_device, refcnt);
579bc17130SSriman Achanta 
589bc17130SSriman Achanta 	kfree(sd);
599bc17130SSriman Achanta }
609bc17130SSriman Achanta 
6149ef6ed1SSriman Achanta /*
6249ef6ed1SSriman Achanta  * Headset report helpers
6349ef6ed1SSriman Achanta  */
643d569aafSSriman Achanta 
steelseries_send_report(struct hid_device * hdev,const u8 * data,int len,enum hid_report_type type)6549ef6ed1SSriman Achanta static int steelseries_send_report(struct hid_device *hdev, const u8 *data,
6649ef6ed1SSriman Achanta 				    int len, enum hid_report_type type)
673d569aafSSriman Achanta {
6849ef6ed1SSriman Achanta 	u8 *buf;
693d569aafSSriman Achanta 	int ret;
703d569aafSSriman Achanta 
7149ef6ed1SSriman Achanta 	buf = kmemdup(data, len, GFP_KERNEL);
7249ef6ed1SSriman Achanta 	if (!buf)
733d569aafSSriman Achanta 		return -ENOMEM;
743d569aafSSriman Achanta 
7549ef6ed1SSriman Achanta 	ret = hid_hw_raw_request(hdev, data[0], buf, len, type,
7649ef6ed1SSriman Achanta 				 HID_REQ_SET_REPORT);
7749ef6ed1SSriman Achanta 	kfree(buf);
783d569aafSSriman Achanta 
793d569aafSSriman Achanta 	if (ret < 0)
8049ef6ed1SSriman Achanta 		return ret;
8149ef6ed1SSriman Achanta 	if (ret < len)
8249ef6ed1SSriman Achanta 		return -EIO;
8349ef6ed1SSriman Achanta 
8449ef6ed1SSriman Achanta 	return 0;
853d569aafSSriman Achanta }
863d569aafSSriman Achanta 
steelseries_send_output_report(struct hid_device * hdev,const u8 * data,int len)8749ef6ed1SSriman Achanta static inline int steelseries_send_output_report(struct hid_device *hdev,
8849ef6ed1SSriman Achanta 						  const u8 *data, int len)
8949ef6ed1SSriman Achanta {
9049ef6ed1SSriman Achanta 	return steelseries_send_report(hdev, data, len, HID_OUTPUT_REPORT);
9149ef6ed1SSriman Achanta }
9249ef6ed1SSriman Achanta 
9349ef6ed1SSriman Achanta /*
9449ef6ed1SSriman Achanta  * Headset status request functions
9549ef6ed1SSriman Achanta  */
9649ef6ed1SSriman Achanta 
steelseries_arctis_1_request_status(struct hid_device * hdev)9749ef6ed1SSriman Achanta static int steelseries_arctis_1_request_status(struct hid_device *hdev)
9849ef6ed1SSriman Achanta {
9949ef6ed1SSriman Achanta 	const u8 data[] = { 0x06, 0x12 };
10049ef6ed1SSriman Achanta 
10149ef6ed1SSriman Achanta 	return steelseries_send_output_report(hdev, data, sizeof(data));
10249ef6ed1SSriman Achanta }
10349ef6ed1SSriman Achanta 
steelseries_arctis_9_request_status(struct hid_device * hdev)10449ef6ed1SSriman Achanta static int steelseries_arctis_9_request_status(struct hid_device *hdev)
10549ef6ed1SSriman Achanta {
10649ef6ed1SSriman Achanta 	const u8 data[] = { 0x00, 0x20 };
10749ef6ed1SSriman Achanta 
10849ef6ed1SSriman Achanta 	return steelseries_send_output_report(hdev, data, sizeof(data));
10949ef6ed1SSriman Achanta }
11049ef6ed1SSriman Achanta 
steelseries_arctis_nova_request_status(struct hid_device * hdev)11183e70cedSSriman Achanta static int steelseries_arctis_nova_request_status(struct hid_device *hdev)
11283e70cedSSriman Achanta {
11383e70cedSSriman Achanta 	const u8 data[] = { 0x00, 0xb0 };
11483e70cedSSriman Achanta 
11583e70cedSSriman Achanta 	return steelseries_send_output_report(hdev, data, sizeof(data));
11683e70cedSSriman Achanta }
11783e70cedSSriman Achanta 
11849ef6ed1SSriman Achanta /*
11949ef6ed1SSriman Achanta  * Headset battery helpers
12049ef6ed1SSriman Achanta  */
12149ef6ed1SSriman Achanta 
battery_capacity_to_level(int capacity)1223d569aafSSriman Achanta static int battery_capacity_to_level(int capacity)
1233d569aafSSriman Achanta {
1243d569aafSSriman Achanta 	if (capacity >= 50)
1253d569aafSSriman Achanta 		return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
1263d569aafSSriman Achanta 	if (capacity >= 20)
1273d569aafSSriman Achanta 		return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
1283d569aafSSriman Achanta 	return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
1293d569aafSSriman Achanta }
1303d569aafSSriman Achanta 
steelseries_map_capacity(u8 capacity,u8 min_in,u8 max_in)13149ef6ed1SSriman Achanta static u8 steelseries_map_capacity(u8 capacity, u8 min_in, u8 max_in)
1323d569aafSSriman Achanta {
13349ef6ed1SSriman Achanta 	if (capacity >= max_in)
13449ef6ed1SSriman Achanta 		return 100;
13549ef6ed1SSriman Achanta 	if (capacity <= min_in)
13649ef6ed1SSriman Achanta 		return 0;
13749ef6ed1SSriman Achanta 	return (capacity - min_in) * 100 / (max_in - min_in);
13849ef6ed1SSriman Achanta }
1393d569aafSSriman Achanta 
14049ef6ed1SSriman Achanta /*
14149ef6ed1SSriman Achanta  * Headset status parse functions
14249ef6ed1SSriman Achanta  */
14349ef6ed1SSriman Achanta 
steelseries_arctis_1_parse_status(struct steelseries_device * sd,u8 * data,int size)14449ef6ed1SSriman Achanta static void steelseries_arctis_1_parse_status(struct steelseries_device *sd,
14549ef6ed1SSriman Achanta 					      u8 *data, int size)
14649ef6ed1SSriman Achanta {
14749ef6ed1SSriman Achanta 	/* Only the battery status report echoes the request header. */
14849ef6ed1SSriman Achanta 	if (size < 8 || data[0] != 0x06 || data[1] != 0x12)
14949ef6ed1SSriman Achanta 		return;
15049ef6ed1SSriman Achanta 
15149ef6ed1SSriman Achanta 	sd->headset_connected = (data[2] != 0x01);
15249ef6ed1SSriman Achanta 	sd->battery_capacity = data[3];
15349ef6ed1SSriman Achanta }
15449ef6ed1SSriman Achanta 
steelseries_arctis_9_parse_status(struct steelseries_device * sd,u8 * data,int size)15549ef6ed1SSriman Achanta static void steelseries_arctis_9_parse_status(struct steelseries_device *sd,
15649ef6ed1SSriman Achanta 					      u8 *data, int size)
15749ef6ed1SSriman Achanta {
15849ef6ed1SSriman Achanta 	if (size < 5)
15949ef6ed1SSriman Achanta 		return;
16049ef6ed1SSriman Achanta 
16149ef6ed1SSriman Achanta 	if (data[0] == 0xaa && data[1] == 0x01) {
16249ef6ed1SSriman Achanta 		sd->headset_connected = true;
16349ef6ed1SSriman Achanta 		sd->battery_charging = (data[4] == 0x01);
164b8ae1114SSriman Achanta 		sd->battery_capacity = steelseries_map_capacity(data[3], 0x64, 0x9a);
16549ef6ed1SSriman Achanta 	} else {
16649ef6ed1SSriman Achanta 		/* Device off: 0x55 (no status) or 0x03 (stale status). */
16749ef6ed1SSriman Achanta 		sd->headset_connected = false;
16849ef6ed1SSriman Achanta 		sd->battery_charging = false;
16949ef6ed1SSriman Achanta 	}
17049ef6ed1SSriman Achanta }
17149ef6ed1SSriman Achanta 
steelseries_arctis_nova_parse_status(struct steelseries_device * sd,u8 * data,int size)17283e70cedSSriman Achanta static void steelseries_arctis_nova_parse_status(struct steelseries_device *sd,
17383e70cedSSriman Achanta 						 u8 *data, int size)
17483e70cedSSriman Achanta {
17583e70cedSSriman Achanta 	if (size < 2)
17683e70cedSSriman Achanta 		return;
17783e70cedSSriman Achanta 
17883e70cedSSriman Achanta 	switch (data[0]) {
17983e70cedSSriman Achanta 	case 0xb0:
18083e70cedSSriman Achanta 		if (size < 4)
18183e70cedSSriman Achanta 			return;
18283e70cedSSriman Achanta 		sd->headset_connected = (data[1] == 0x03);
18383e70cedSSriman Achanta 		sd->battery_capacity = data[2];
18483e70cedSSriman Achanta 		sd->battery_charging = (data[3] == 0x01);
18583e70cedSSriman Achanta 		break;
18683e70cedSSriman Achanta 	case 0xb7:
18783e70cedSSriman Achanta 		sd->battery_capacity = data[1];
18883e70cedSSriman Achanta 		break;
18983e70cedSSriman Achanta 	case 0xb9:
19083e70cedSSriman Achanta 		sd->headset_connected = (data[1] == 0x03);
19183e70cedSSriman Achanta 		break;
19283e70cedSSriman Achanta 	case 0xbb:
19383e70cedSSriman Achanta 		sd->battery_charging = (data[1] == 0x01);
19483e70cedSSriman Achanta 		break;
19583e70cedSSriman Achanta 	}
19683e70cedSSriman Achanta }
19783e70cedSSriman Achanta 
steelseries_arctis_nova_7_parse_status(struct steelseries_device * sd,u8 * data,int size)19883e70cedSSriman Achanta static void steelseries_arctis_nova_7_parse_status(struct steelseries_device *sd,
19983e70cedSSriman Achanta 						   u8 *data, int size)
20083e70cedSSriman Achanta {
20183e70cedSSriman Achanta 	if (size < 2)
20283e70cedSSriman Achanta 		return;
20383e70cedSSriman Achanta 
20483e70cedSSriman Achanta 	switch (data[0]) {
20583e70cedSSriman Achanta 	case 0xb0:
20683e70cedSSriman Achanta 		if (size < 4)
20783e70cedSSriman Achanta 			return;
20883e70cedSSriman Achanta 		sd->headset_connected = (data[1] == 0x03);
20983e70cedSSriman Achanta 		sd->battery_capacity = steelseries_map_capacity(data[2], 0, 4);
21083e70cedSSriman Achanta 		sd->battery_charging = (data[3] == 0x01);
21183e70cedSSriman Achanta 		break;
21283e70cedSSriman Achanta 	case 0xb7:
21383e70cedSSriman Achanta 		sd->battery_capacity = steelseries_map_capacity(data[1], 0, 4);
21483e70cedSSriman Achanta 		break;
21583e70cedSSriman Achanta 	case 0xb9:
21683e70cedSSriman Achanta 		sd->headset_connected = (data[1] == 0x03);
21783e70cedSSriman Achanta 		break;
21883e70cedSSriman Achanta 	case 0xbb:
21983e70cedSSriman Achanta 		sd->battery_charging = (data[1] == 0x01);
22083e70cedSSriman Achanta 		break;
22183e70cedSSriman Achanta 	}
22283e70cedSSriman Achanta }
22383e70cedSSriman Achanta 
22449ef6ed1SSriman Achanta /*
22549ef6ed1SSriman Achanta  * Device info definitions
22649ef6ed1SSriman Achanta  */
22749ef6ed1SSriman Achanta 
22849ef6ed1SSriman Achanta static const struct steelseries_device_info arctis_1_info = {
22949ef6ed1SSriman Achanta 	.sync_interface = 3,
23049ef6ed1SSriman Achanta 	.capabilities = SS_CAP_BATTERY,
23149ef6ed1SSriman Achanta 	.request_status = steelseries_arctis_1_request_status,
23249ef6ed1SSriman Achanta 	.parse_status = steelseries_arctis_1_parse_status,
23349ef6ed1SSriman Achanta };
23449ef6ed1SSriman Achanta 
23549ef6ed1SSriman Achanta static const struct steelseries_device_info arctis_9_info = {
23649ef6ed1SSriman Achanta 	.sync_interface = 0,
23749ef6ed1SSriman Achanta 	.capabilities = SS_CAP_BATTERY,
23849ef6ed1SSriman Achanta 	.request_status = steelseries_arctis_9_request_status,
23949ef6ed1SSriman Achanta 	.parse_status = steelseries_arctis_9_parse_status,
24049ef6ed1SSriman Achanta };
24149ef6ed1SSriman Achanta 
24283e70cedSSriman Achanta static const struct steelseries_device_info arctis_nova_info = {
24383e70cedSSriman Achanta 	.sync_interface = 3,
24483e70cedSSriman Achanta 	.async_interface = 5,
24583e70cedSSriman Achanta 	.capabilities = SS_CAP_BATTERY,
24683e70cedSSriman Achanta 	.request_status = steelseries_arctis_nova_request_status,
24783e70cedSSriman Achanta 	.parse_status = steelseries_arctis_nova_parse_status,
24883e70cedSSriman Achanta };
24983e70cedSSriman Achanta 
25083e70cedSSriman Achanta static const struct steelseries_device_info arctis_nova_7_info = {
25183e70cedSSriman Achanta 	.sync_interface = 3,
25283e70cedSSriman Achanta 	.async_interface = 5,
25383e70cedSSriman Achanta 	.capabilities = SS_CAP_BATTERY,
25483e70cedSSriman Achanta 	.request_status = steelseries_arctis_nova_request_status,
25583e70cedSSriman Achanta 	.parse_status = steelseries_arctis_nova_7_parse_status,
25683e70cedSSriman Achanta };
25783e70cedSSriman Achanta 
25849ef6ed1SSriman Achanta /*
25949ef6ed1SSriman Achanta  * Headset wireless status and battery infrastructure
26049ef6ed1SSriman Achanta  */
26149ef6ed1SSriman Achanta 
26249ef6ed1SSriman Achanta #define STEELSERIES_HEADSET_STATUS_TIMEOUT_MS	3000
26349ef6ed1SSriman Achanta 
26449ef6ed1SSriman Achanta static void
steelseries_headset_set_wireless_status(struct hid_device * hdev,bool connected)26549ef6ed1SSriman Achanta steelseries_headset_set_wireless_status(struct hid_device *hdev,
26649ef6ed1SSriman Achanta 					bool connected)
26749ef6ed1SSriman Achanta {
26849ef6ed1SSriman Achanta 	struct usb_interface *intf;
26949ef6ed1SSriman Achanta 
27049ef6ed1SSriman Achanta 	if (!hid_is_usb(hdev))
27149ef6ed1SSriman Achanta 		return;
27249ef6ed1SSriman Achanta 
27349ef6ed1SSriman Achanta 	intf = to_usb_interface(hdev->dev.parent);
27449ef6ed1SSriman Achanta 	usb_set_wireless_status(intf, connected ?
27549ef6ed1SSriman Achanta 				USB_WIRELESS_STATUS_CONNECTED :
27649ef6ed1SSriman Achanta 				USB_WIRELESS_STATUS_DISCONNECTED);
2773d569aafSSriman Achanta }
2783d569aafSSriman Achanta 
2793d569aafSSriman Achanta #define STEELSERIES_PREFIX "SteelSeries "
2803d569aafSSriman Achanta 
steelseries_battery_get_property(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)28149ef6ed1SSriman Achanta static int steelseries_battery_get_property(struct power_supply *psy,
2823d569aafSSriman Achanta 				enum power_supply_property psp,
2833d569aafSSriman Achanta 				union power_supply_propval *val)
2843d569aafSSriman Achanta {
2853d569aafSSriman Achanta 	struct steelseries_device *sd = power_supply_get_drvdata(psy);
28649ef6ed1SSriman Achanta 	size_t prefix_len;
2873d569aafSSriman Achanta 	int ret = 0;
2883d569aafSSriman Achanta 
2893d569aafSSriman Achanta 	switch (psp) {
2903d569aafSSriman Achanta 	case POWER_SUPPLY_PROP_MODEL_NAME:
2913d569aafSSriman Achanta 		val->strval = sd->hdev->name;
29249ef6ed1SSriman Achanta 		while ((prefix_len = str_has_prefix(val->strval, STEELSERIES_PREFIX)))
29349ef6ed1SSriman Achanta 			val->strval += prefix_len;
2943d569aafSSriman Achanta 		break;
2953d569aafSSriman Achanta 	case POWER_SUPPLY_PROP_MANUFACTURER:
2963d569aafSSriman Achanta 		val->strval = "SteelSeries";
2973d569aafSSriman Achanta 		break;
2983d569aafSSriman Achanta 	case POWER_SUPPLY_PROP_PRESENT:
2993d569aafSSriman Achanta 		val->intval = 1;
3003d569aafSSriman Achanta 		break;
3013d569aafSSriman Achanta 	case POWER_SUPPLY_PROP_STATUS:
30249ef6ed1SSriman Achanta 		if (!sd->headset_connected)
3033d569aafSSriman Achanta 			val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
30449ef6ed1SSriman Achanta 		else if (sd->battery_charging)
30506529560SSriman Achanta 			val->intval = sd->battery_capacity >= 100 ?
30606529560SSriman Achanta 				POWER_SUPPLY_STATUS_FULL :
30706529560SSriman Achanta 				POWER_SUPPLY_STATUS_CHARGING;
30849ef6ed1SSriman Achanta 		else
30949ef6ed1SSriman Achanta 			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
3103d569aafSSriman Achanta 		break;
3113d569aafSSriman Achanta 	case POWER_SUPPLY_PROP_SCOPE:
3123d569aafSSriman Achanta 		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
3133d569aafSSriman Achanta 		break;
3143d569aafSSriman Achanta 	case POWER_SUPPLY_PROP_CAPACITY:
3153d569aafSSriman Achanta 		val->intval = sd->battery_capacity;
3163d569aafSSriman Achanta 		break;
3173d569aafSSriman Achanta 	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
3183d569aafSSriman Achanta 		val->intval = battery_capacity_to_level(sd->battery_capacity);
3193d569aafSSriman Achanta 		break;
3203d569aafSSriman Achanta 	default:
3213d569aafSSriman Achanta 		ret = -EINVAL;
3223d569aafSSriman Achanta 		break;
3233d569aafSSriman Achanta 	}
3243d569aafSSriman Achanta 	return ret;
3253d569aafSSriman Achanta }
3263d569aafSSriman Achanta 
32749ef6ed1SSriman Achanta static enum power_supply_property steelseries_battery_props[] = {
3283d569aafSSriman Achanta 	POWER_SUPPLY_PROP_MODEL_NAME,
3293d569aafSSriman Achanta 	POWER_SUPPLY_PROP_MANUFACTURER,
3303d569aafSSriman Achanta 	POWER_SUPPLY_PROP_PRESENT,
3313d569aafSSriman Achanta 	POWER_SUPPLY_PROP_STATUS,
3323d569aafSSriman Achanta 	POWER_SUPPLY_PROP_SCOPE,
3333d569aafSSriman Achanta 	POWER_SUPPLY_PROP_CAPACITY,
3343d569aafSSriman Achanta 	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
3353d569aafSSriman Achanta };
3363d569aafSSriman Achanta 
33749ef6ed1SSriman Achanta /*
33849ef6ed1SSriman Achanta  * Delayed work handlers for status polling
33949ef6ed1SSriman Achanta  */
34049ef6ed1SSriman Achanta 
steelseries_status_timer_work_handler(struct work_struct * work)34149ef6ed1SSriman Achanta static void steelseries_status_timer_work_handler(struct work_struct *work)
34249ef6ed1SSriman Achanta {
34349ef6ed1SSriman Achanta 	struct steelseries_device *sd = container_of(
34449ef6ed1SSriman Achanta 		work, struct steelseries_device, status_work.work);
34549ef6ed1SSriman Achanta 	unsigned long flags;
34649ef6ed1SSriman Achanta 
34749ef6ed1SSriman Achanta 	sd->info->request_status(sd->hdev);
34849ef6ed1SSriman Achanta 
34949ef6ed1SSriman Achanta 	spin_lock_irqsave(&sd->lock, flags);
350bebeca20SSriman Achanta 	/* Async devices push status events themselves; only poll once. */
351bebeca20SSriman Achanta 	if (!sd->removed && !sd->info->async_interface)
35249ef6ed1SSriman Achanta 		schedule_delayed_work(&sd->status_work,
35349ef6ed1SSriman Achanta 				msecs_to_jiffies(STEELSERIES_HEADSET_STATUS_TIMEOUT_MS));
35449ef6ed1SSriman Achanta 	spin_unlock_irqrestore(&sd->lock, flags);
35549ef6ed1SSriman Achanta }
35649ef6ed1SSriman Achanta 
steelseries_battery_register(struct steelseries_device * sd)35749ef6ed1SSriman Achanta static int steelseries_battery_register(struct steelseries_device *sd)
3583d569aafSSriman Achanta {
3593d569aafSSriman Achanta 	struct power_supply_config battery_cfg = { .drv_data = sd, };
3609bc17130SSriman Achanta 	struct power_supply *battery;
3613d569aafSSriman Achanta 	int ret;
3623d569aafSSriman Achanta 
3633d569aafSSriman Achanta 	sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
36449ef6ed1SSriman Achanta 	sd->battery_desc.properties = steelseries_battery_props;
36549ef6ed1SSriman Achanta 	sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_battery_props);
36649ef6ed1SSriman Achanta 	sd->battery_desc.get_property = steelseries_battery_get_property;
3673d569aafSSriman Achanta 	sd->battery_desc.use_for_apm = 0;
3683d569aafSSriman Achanta 	sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL,
3699bc17130SSriman Achanta 					       "steelseries_headset_battery_%s",
3709bc17130SSriman Achanta 					       sd->hdev->uniq[0] ? sd->hdev->uniq :
3719bc17130SSriman Achanta 					       dev_name(&sd->hdev->dev));
3723d569aafSSriman Achanta 	if (!sd->battery_desc.name)
3733d569aafSSriman Achanta 		return -ENOMEM;
3743d569aafSSriman Achanta 
3753d569aafSSriman Achanta 	/* avoid the warning of 0% battery while waiting for the first info */
3763d569aafSSriman Achanta 	sd->battery_capacity = 100;
3773d569aafSSriman Achanta 	sd->battery_charging = false;
37849ef6ed1SSriman Achanta 	sd->headset_connected = false;
37949ef6ed1SSriman Achanta 	steelseries_headset_set_wireless_status(sd->hdev, false);
3803d569aafSSriman Achanta 
3819bc17130SSriman Achanta 	battery = power_supply_register(&sd->hdev->dev,
3823d569aafSSriman Achanta 			&sd->battery_desc, &battery_cfg);
3839bc17130SSriman Achanta 	if (IS_ERR(battery)) {
3849bc17130SSriman Achanta 		ret = PTR_ERR(battery);
3853d569aafSSriman Achanta 		hid_err(sd->hdev,
3863d569aafSSriman Achanta 				"%s:power_supply_register failed with error %d\n",
3873d569aafSSriman Achanta 				__func__, ret);
3883d569aafSSriman Achanta 		return ret;
3893d569aafSSriman Achanta 	}
3909bc17130SSriman Achanta 	power_supply_powers(battery, &sd->hdev->dev);
3919bc17130SSriman Achanta 
3929bc17130SSriman Achanta 	/* Assign on success only, so a concurrent raw_event never sees an ERR_PTR. */
3939bc17130SSriman Achanta 	sd->battery = battery;
3943d569aafSSriman Achanta 
3953d569aafSSriman Achanta 	return 0;
3963d569aafSSriman Achanta }
3973d569aafSSriman Achanta 
398bebeca20SSriman Achanta static struct hid_driver steelseries_arctis_driver;
399bebeca20SSriman Achanta 
400bebeca20SSriman Achanta static struct steelseries_device *
steelseries_get_sibling_sd(struct hid_device * hdev,int interface_num)401bebeca20SSriman Achanta steelseries_get_sibling_sd(struct hid_device *hdev, int interface_num)
402bebeca20SSriman Achanta {
403bebeca20SSriman Achanta 	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
404bebeca20SSriman Achanta 	struct usb_device *usb_dev = interface_to_usbdev(intf);
405bebeca20SSriman Achanta 	struct usb_interface *sibling_intf;
406bebeca20SSriman Achanta 	struct hid_device *sibling_hdev;
407bebeca20SSriman Achanta 	struct steelseries_device *sd = NULL;
408bebeca20SSriman Achanta 
409bebeca20SSriman Achanta 	sibling_intf = usb_ifnum_to_if(usb_dev, interface_num);
410bebeca20SSriman Achanta 	if (!sibling_intf)
411bebeca20SSriman Achanta 		return NULL;
412bebeca20SSriman Achanta 
413bebeca20SSriman Achanta 	/*
414bebeca20SSriman Achanta 	 * usb_get_intfdata() only yields a hid_device when usbhid is bound;
415bebeca20SSriman Achanta 	 * gate on the descriptor class so a non-HID sibling (e.g. a crafted
416bebeca20SSriman Achanta 	 * device exposing storage or audio here) is never treated as one.
417bebeca20SSriman Achanta 	 */
418bebeca20SSriman Achanta 	if (sibling_intf->cur_altsetting->desc.bInterfaceClass != USB_INTERFACE_CLASS_HID)
419bebeca20SSriman Achanta 		return NULL;
420bebeca20SSriman Achanta 
421bebeca20SSriman Achanta 	/*
422bebeca20SSriman Achanta 	 * Take the sibling's device lock across the intfdata read and the
423bebeca20SSriman Achanta 	 * kref_get so a concurrent unbind cannot free the hid_device underneath
424bebeca20SSriman Achanta 	 * us; usbhid leaves intfdata dangling on disconnect, so dev.driver is
425bebeca20SSriman Achanta 	 * the reliable "still bound" test under this lock. Use device_trylock()
426bebeca20SSriman Achanta 	 * to stay off the lockdep chain of the interface being probed and let
427bebeca20SSriman Achanta 	 * the caller retry via -EPROBE_DEFER if the sibling is momentarily busy.
428bebeca20SSriman Achanta 	 */
429bebeca20SSriman Achanta 	if (!device_trylock(&sibling_intf->dev))
430bebeca20SSriman Achanta 		return NULL;
431bebeca20SSriman Achanta 	if (sibling_intf->dev.driver) {
432bebeca20SSriman Achanta 		sibling_hdev = usb_get_intfdata(sibling_intf);
433bebeca20SSriman Achanta 		if (sibling_hdev &&
434bebeca20SSriman Achanta 		    sibling_hdev->driver == &steelseries_arctis_driver) {
435bebeca20SSriman Achanta 			sd = hid_get_drvdata(sibling_hdev);
436bebeca20SSriman Achanta 			if (sd)
437bebeca20SSriman Achanta 				kref_get(&sd->refcnt);
438bebeca20SSriman Achanta 		}
439bebeca20SSriman Achanta 	}
440bebeca20SSriman Achanta 	device_unlock(&sibling_intf->dev);
441bebeca20SSriman Achanta 
442bebeca20SSriman Achanta 	return sd;
443bebeca20SSriman Achanta }
444bebeca20SSriman Achanta 
steelseries_arctis_probe(struct hid_device * hdev,const struct hid_device_id * id)44549ef6ed1SSriman Achanta static int steelseries_arctis_probe(struct hid_device *hdev,
44649ef6ed1SSriman Achanta 				    const struct hid_device_id *id)
4473d569aafSSriman Achanta {
44849ef6ed1SSriman Achanta 	const struct steelseries_device_info *info =
44949ef6ed1SSriman Achanta 		(const struct steelseries_device_info *)id->driver_data;
4503d569aafSSriman Achanta 	struct steelseries_device *sd;
45149ef6ed1SSriman Achanta 	struct usb_interface *intf;
45249ef6ed1SSriman Achanta 	u8 interface_num;
4533d569aafSSriman Achanta 	int ret;
4543d569aafSSriman Achanta 
45549ef6ed1SSriman Achanta 	if (hid_is_usb(hdev)) {
45649ef6ed1SSriman Achanta 		intf = to_usb_interface(hdev->dev.parent);
45749ef6ed1SSriman Achanta 		interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
45849ef6ed1SSriman Achanta 	} else {
45949ef6ed1SSriman Achanta 		return -ENODEV;
46049ef6ed1SSriman Achanta 	}
4613d569aafSSriman Achanta 
4623d569aafSSriman Achanta 	ret = hid_parse(hdev);
4633d569aafSSriman Achanta 	if (ret)
4643d569aafSSriman Achanta 		return ret;
4653d569aafSSriman Achanta 
466bebeca20SSriman Achanta 	/* Let hid-generic handle non-vendor or unknown interfaces */
467bebeca20SSriman Achanta 	if (interface_num != info->sync_interface &&
468bebeca20SSriman Achanta 	    (!info->async_interface || interface_num != info->async_interface))
46949ef6ed1SSriman Achanta 		return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
4703d569aafSSriman Achanta 
471bebeca20SSriman Achanta 	if (interface_num == info->sync_interface) {
472*3a2c4d55SKees Cook 		sd = kzalloc_obj(*sd);
47349ef6ed1SSriman Achanta 		if (!sd)
47449ef6ed1SSriman Achanta 			return -ENOMEM;
47549ef6ed1SSriman Achanta 
4769bc17130SSriman Achanta 		kref_init(&sd->refcnt);
47749ef6ed1SSriman Achanta 		sd->hdev = hdev;
47849ef6ed1SSriman Achanta 		sd->info = info;
4793d569aafSSriman Achanta 		spin_lock_init(&sd->lock);
480bebeca20SSriman Achanta 		INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler);
48149ef6ed1SSriman Achanta 
4823d569aafSSriman Achanta 		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
4833d569aafSSriman Achanta 		if (ret)
484bebeca20SSriman Achanta 			goto err_free;
4853d569aafSSriman Achanta 
4863d569aafSSriman Achanta 		ret = hid_hw_open(hdev);
4873d569aafSSriman Achanta 		if (ret)
4883d569aafSSriman Achanta 			goto err_stop;
4893d569aafSSriman Achanta 
49049ef6ed1SSriman Achanta 		if (info->capabilities & SS_CAP_BATTERY) {
49149ef6ed1SSriman Achanta 			ret = steelseries_battery_register(sd);
49249ef6ed1SSriman Achanta 			if (ret < 0)
49349ef6ed1SSriman Achanta 				hid_warn(hdev, "Failed to register battery: %d\n", ret);
49449ef6ed1SSriman Achanta 		}
49549ef6ed1SSriman Achanta 
496bebeca20SSriman Achanta 		/*
497bebeca20SSriman Achanta 		 * Publish drvdata only once fully initialised: the async sibling
498bebeca20SSriman Achanta 		 * attaches by reading it, so it must never observe a half-built or
499bebeca20SSriman Achanta 		 * failed instance. A failed probe never gets here, so the error
500bebeca20SSriman Achanta 		 * path below has nothing to unpublish.
501bebeca20SSriman Achanta 		 */
502bebeca20SSriman Achanta 		hid_set_drvdata(hdev, sd);
50349ef6ed1SSriman Achanta 		schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100));
5043d569aafSSriman Achanta 
5053d569aafSSriman Achanta 		return 0;
506bebeca20SSriman Achanta 	}
507bebeca20SSriman Achanta 
508bebeca20SSriman Achanta 	/*
509bebeca20SSriman Achanta 	 * The async interface shares the steelseries_device created by the
510bebeca20SSriman Achanta 	 * sync interface. Defer until the sync interface has probed and
511bebeca20SSriman Achanta 	 * published its drvdata.
512bebeca20SSriman Achanta 	 */
513bebeca20SSriman Achanta 	if (info->async_interface && interface_num == info->async_interface) {
514bebeca20SSriman Achanta 		sd = steelseries_get_sibling_sd(hdev, info->sync_interface);
515bebeca20SSriman Achanta 		if (!sd)
516bebeca20SSriman Achanta 			return -EPROBE_DEFER;
517bebeca20SSriman Achanta 
518bebeca20SSriman Achanta 		hid_set_drvdata(hdev, sd);
519bebeca20SSriman Achanta 
520bebeca20SSriman Achanta 		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
521bebeca20SSriman Achanta 		if (ret) {
522bebeca20SSriman Achanta 			kref_put(&sd->refcnt, steelseries_device_release);
523bebeca20SSriman Achanta 			return ret;
524bebeca20SSriman Achanta 		}
525bebeca20SSriman Achanta 
526bebeca20SSriman Achanta 		ret = hid_hw_open(hdev);
527bebeca20SSriman Achanta 		if (ret) {
528bebeca20SSriman Achanta 			hid_hw_stop(hdev);
529bebeca20SSriman Achanta 			kref_put(&sd->refcnt, steelseries_device_release);
530bebeca20SSriman Achanta 			return ret;
531bebeca20SSriman Achanta 		}
532bebeca20SSriman Achanta 		return 0;
533bebeca20SSriman Achanta 	}
534bebeca20SSriman Achanta 
535bebeca20SSriman Achanta 	return -ENODEV;
5363d569aafSSriman Achanta 
5373d569aafSSriman Achanta err_stop:
5383d569aafSSriman Achanta 	hid_hw_stop(hdev);
539bebeca20SSriman Achanta err_free:
540bebeca20SSriman Achanta 	/* drvdata is unpublished until full success, so no sibling can hold sd. */
5419bc17130SSriman Achanta 	kref_put(&sd->refcnt, steelseries_device_release);
5423d569aafSSriman Achanta 	return ret;
5433d569aafSSriman Achanta }
5443d569aafSSriman Achanta 
steelseries_arctis_remove(struct hid_device * hdev)5453d569aafSSriman Achanta static void steelseries_arctis_remove(struct hid_device *hdev)
5463d569aafSSriman Achanta {
5473d569aafSSriman Achanta 	struct steelseries_device *sd;
5489bc17130SSriman Achanta 	struct power_supply *battery;
5493d569aafSSriman Achanta 	unsigned long flags;
55049ef6ed1SSriman Achanta 	struct usb_interface *intf;
55149ef6ed1SSriman Achanta 	u8 interface_num;
55249ef6ed1SSriman Achanta 
55349ef6ed1SSriman Achanta 	if (hid_is_usb(hdev)) {
55449ef6ed1SSriman Achanta 		intf = to_usb_interface(hdev->dev.parent);
55549ef6ed1SSriman Achanta 		interface_num = intf->cur_altsetting->desc.bInterfaceNumber;
55649ef6ed1SSriman Achanta 	} else {
55749ef6ed1SSriman Achanta 		return;
55849ef6ed1SSriman Achanta 	}
5593d569aafSSriman Achanta 
5603d569aafSSriman Achanta 	sd = hid_get_drvdata(hdev);
5613d569aafSSriman Achanta 
56249ef6ed1SSriman Achanta 	if (!sd) {
56349ef6ed1SSriman Achanta 		hid_hw_stop(hdev);
56449ef6ed1SSriman Achanta 		return;
56549ef6ed1SSriman Achanta 	}
56649ef6ed1SSriman Achanta 
56749ef6ed1SSriman Achanta 	if (interface_num == sd->info->sync_interface) {
5683d569aafSSriman Achanta 		spin_lock_irqsave(&sd->lock, flags);
5693d569aafSSriman Achanta 		sd->removed = true;
5709bc17130SSriman Achanta 		battery = sd->battery;
5719bc17130SSriman Achanta 		sd->battery = NULL;
5723d569aafSSriman Achanta 		spin_unlock_irqrestore(&sd->lock, flags);
5733d569aafSSriman Achanta 
57449ef6ed1SSriman Achanta 		cancel_delayed_work_sync(&sd->status_work);
5759bc17130SSriman Achanta 
5769bc17130SSriman Achanta 		if (battery)
5779bc17130SSriman Achanta 			power_supply_unregister(battery);
57849ef6ed1SSriman Achanta 	}
5793d569aafSSriman Achanta 
5803d569aafSSriman Achanta 	hid_hw_close(hdev);
5813d569aafSSriman Achanta 	hid_hw_stop(hdev);
5829bc17130SSriman Achanta 
5839bc17130SSriman Achanta 	kref_put(&sd->refcnt, steelseries_device_release);
5843d569aafSSriman Achanta }
5853d569aafSSriman Achanta 
steelseries_arctis_raw_event(struct hid_device * hdev,struct hid_report * report,u8 * data,int size)5863d569aafSSriman Achanta static int steelseries_arctis_raw_event(struct hid_device *hdev,
58749ef6ed1SSriman Achanta 				 struct hid_report *report, u8 *data, int size)
5883d569aafSSriman Achanta {
5893d569aafSSriman Achanta 	struct steelseries_device *sd = hid_get_drvdata(hdev);
59049ef6ed1SSriman Achanta 	u8 old_capacity;
59149ef6ed1SSriman Achanta 	bool old_connected;
59249ef6ed1SSriman Achanta 	bool old_charging;
593bebeca20SSriman Achanta 	bool is_async_interface;
594bebeca20SSriman Achanta 	unsigned long flags;
5953d569aafSSriman Achanta 
59649ef6ed1SSriman Achanta 	if (!sd)
5973d569aafSSriman Achanta 		return 0;
5983d569aafSSriman Achanta 
599bebeca20SSriman Achanta 	is_async_interface = (hdev != sd->hdev);
600bebeca20SSriman Achanta 
601bebeca20SSriman Achanta 	spin_lock_irqsave(&sd->lock, flags);
602bebeca20SSriman Achanta 
603bebeca20SSriman Achanta 	if (sd->removed) {
604bebeca20SSriman Achanta 		spin_unlock_irqrestore(&sd->lock, flags);
605bebeca20SSriman Achanta 		return 0;
606bebeca20SSriman Achanta 	}
607bebeca20SSriman Achanta 
60849ef6ed1SSriman Achanta 	old_capacity = sd->battery_capacity;
60949ef6ed1SSriman Achanta 	old_connected = sd->headset_connected;
61049ef6ed1SSriman Achanta 	old_charging = sd->battery_charging;
6113d569aafSSriman Achanta 
61249ef6ed1SSriman Achanta 	sd->info->parse_status(sd, data, size);
6133d569aafSSriman Achanta 
61449ef6ed1SSriman Achanta 	if (sd->headset_connected != old_connected) {
61549ef6ed1SSriman Achanta 		hid_dbg(hdev,
6163d569aafSSriman Achanta 			"Connected status changed from %sconnected to %sconnected\n",
61749ef6ed1SSriman Achanta 			old_connected ? "" : "not ",
61849ef6ed1SSriman Achanta 			sd->headset_connected ? "" : "not ");
61949ef6ed1SSriman Achanta 
620bebeca20SSriman Achanta 		if (sd->headset_connected && !old_connected &&
621bebeca20SSriman Achanta 		    sd->info->async_interface && is_async_interface)
622bebeca20SSriman Achanta 			schedule_delayed_work(&sd->status_work, 0);
623bebeca20SSriman Achanta 
62449ef6ed1SSriman Achanta 		if (sd->battery) {
62549ef6ed1SSriman Achanta 			steelseries_headset_set_wireless_status(sd->hdev,
62649ef6ed1SSriman Achanta 							       sd->headset_connected);
62749ef6ed1SSriman Achanta 			power_supply_changed(sd->battery);
62849ef6ed1SSriman Achanta 		}
6293d569aafSSriman Achanta 	}
6303d569aafSSriman Achanta 
63149ef6ed1SSriman Achanta 	if (sd->battery_capacity != old_capacity) {
63249ef6ed1SSriman Achanta 		hid_dbg(hdev, "Battery capacity changed from %d%% to %d%%\n",
63349ef6ed1SSriman Achanta 			old_capacity, sd->battery_capacity);
63449ef6ed1SSriman Achanta 		if (sd->battery)
6353d569aafSSriman Achanta 			power_supply_changed(sd->battery);
6363d569aafSSriman Achanta 	}
6373d569aafSSriman Achanta 
63849ef6ed1SSriman Achanta 	if (sd->battery_charging != old_charging) {
63949ef6ed1SSriman Achanta 		hid_dbg(hdev,
6403d569aafSSriman Achanta 			"Battery charging status changed from %scharging to %scharging\n",
64149ef6ed1SSriman Achanta 			old_charging ? "" : "not ",
64249ef6ed1SSriman Achanta 			sd->battery_charging ? "" : "not ");
64349ef6ed1SSriman Achanta 		if (sd->battery)
6443d569aafSSriman Achanta 			power_supply_changed(sd->battery);
6453d569aafSSriman Achanta 	}
6463d569aafSSriman Achanta 
647bebeca20SSriman Achanta 	spin_unlock_irqrestore(&sd->lock, flags);
648bebeca20SSriman Achanta 
6493d569aafSSriman Achanta 	return 0;
6503d569aafSSriman Achanta }
6513d569aafSSriman Achanta 
6523d569aafSSriman Achanta static const struct hid_device_id steelseries_arctis_devices[] = {
65349ef6ed1SSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
65449ef6ed1SSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X),
65549ef6ed1SSriman Achanta 	  .driver_data = (unsigned long)&arctis_1_info },
65649ef6ed1SSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
65749ef6ed1SSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
65849ef6ed1SSriman Achanta 	  .driver_data = (unsigned long)&arctis_9_info },
65983e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
66083e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X),
66183e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_info },
66283e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
66383e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7),
66483e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_7_info },
66583e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
66683e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X),
66783e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_7_info },
66883e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
66983e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2),
67083e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_7_info },
67183e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
67283e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO),
67383e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_7_info },
67483e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
67583e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW),
67683e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_7_info },
67783e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
67883e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026),
67983e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_info },
68083e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
68183e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026),
68283e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_info },
68383e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
68483e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026),
68583e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_info },
68683e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
68783e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026),
68883e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_info },
68983e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
69083e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2),
69183e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_info },
69283e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
69383e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2),
69483e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_info },
69583e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
69683e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2),
69783e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_info },
69883e70cedSSriman Achanta 	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES,
69983e70cedSSriman Achanta 			 USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3),
70083e70cedSSriman Achanta 	  .driver_data = (unsigned long)&arctis_nova_info },
7013d569aafSSriman Achanta 	{}
7023d569aafSSriman Achanta };
7033d569aafSSriman Achanta MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices);
7043d569aafSSriman Achanta 
7053d569aafSSriman Achanta static struct hid_driver steelseries_arctis_driver = {
7063d569aafSSriman Achanta 	.name = "hid-steelseries-arctis",
7073d569aafSSriman Achanta 	.id_table = steelseries_arctis_devices,
7083d569aafSSriman Achanta 	.probe = steelseries_arctis_probe,
7093d569aafSSriman Achanta 	.remove = steelseries_arctis_remove,
7103d569aafSSriman Achanta 	.raw_event = steelseries_arctis_raw_event,
7113d569aafSSriman Achanta };
7123d569aafSSriman Achanta 
7133d569aafSSriman Achanta module_hid_driver(steelseries_arctis_driver);
7143d569aafSSriman Achanta MODULE_DESCRIPTION("HID driver for Steelseries arctis headsets");
7153d569aafSSriman Achanta MODULE_LICENSE("GPL");
7163d569aafSSriman Achanta MODULE_AUTHOR("Christian Mayer <git@mayer-bgk.de>");
7173d569aafSSriman Achanta MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
71849ef6ed1SSriman Achanta MODULE_AUTHOR("Sriman Achanta <srimanachanta@gmail.com>");
719