From nobody Fri Oct 2 07:45:34 2026 Received: from mail-qt1-f172.google.com (mail-qt1-f172.google.com [209.85.160.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 82490211A14 for ; Mon, 3 Aug 2026 19:15:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784560; cv=none; b=WJ2D5tQobaPKD8BrU6PSb4l8EzVMRAtrPPmWJOOK4IqH/r/iu6sf8yJDCYBBztkF/tyi+5fMqAuvyUtk3qMKuzj9F1WlPmwE3UcC/9e/DyzppbeSs5IsbELHKZ9WipvajSa2K2McvE0sUoy5WM+IA6YFWr5xNAP4G97YMNOr+sE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784560; c=relaxed/simple; bh=PZWvz1oiGO+EiDzGefrVoSQrphvSc4XUZ+9vkyTuOLA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PTomaTzw8IAXqUrTlSaClIxgNPiOnyJUYPR9gfBPcfTEpCUVsoB2LD3ghIjQx35AhEjKi/r6U6scd232QdpxpMpUIaOPYXMMDtHrm1nq1J959D9mjr2VkKzrYOY5le6OygzNQ9u6DtWE0i0S3z0TcQsmvCa46KczHyR/f7XCOmg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=gtEaeE6e; arc=none smtp.client-ip=209.85.160.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="gtEaeE6e" Received: by mail-qt1-f172.google.com with SMTP id d75a77b69052e-527e352a167so24008721cf.1 for ; Mon, 03 Aug 2026 12:15:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785784557; x=1786389357; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=rFCxfeOgVEQCpE619CeSJ8Vcr/viRMH26O07kTeRsnw=; b=gtEaeE6eEm9zAxxENzophgkmw1xIV23LMnAXXcvHMOSaOkuO8NskCmRXAkU8Uxs0T+ YXC0Kguu1FF/nvk/rvANCdbYZ7AP7vfQk4Dra9nCdYYgcFxKKWv1STZWREYIewNdkTzx fC2PMIYKTFYrzmvXtD1g257js5Zu0ezZo7n3EspE8QH45G2nwc/3f/u8N9oiVqmmWfqL o90yPsKx4EJjEGsht8JNgClTooZxdQc1P4CaifhXf7VNACWClitVMKqJVbuzDsqlny+U OXjZeqq6mzSwz54hCg01XUqeH059d+W+Qn8dDs2g7iZmgRG3NU0z3gcO7P6dmXXeVvhM wSkw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785784557; x=1786389357; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=rFCxfeOgVEQCpE619CeSJ8Vcr/viRMH26O07kTeRsnw=; b=O4AKhZUApi3Us66cruLLzHElSh1fLsgDgRfAGW3I4bi0e9DYJPSeDtPnGJnzNLFAe7 aLnZ4I9t6lQITE0ecRZkNXoCwCqYiAJXJWUcVJHxeFlYhl5i1AB5ErHlRchV9A/Uzv9m N4F+uC111M7tuYIHPZZkhsnh5PoSlIuNZmZBS2FyZjD5i0cX764NSV3+xLi5yK6NHDVU v5Cfy8gOshIMIpzyGLvPphEVSAt/aAvqfdewomNWYL9Bj4BKYxPt5TckfNIubwnrivVZ KCFYsyDkzDUM7NL974U0lfC1FkywRsR9SXxZrpyeve4r7depsjgQruxE7q8VHc0WdOKh 3q/w== X-Forwarded-Encrypted: i=1; AHgh+RrNeKwIXTxTMLm88+zRzliDoIANaabBNJ5IWH0Cr02tW+/bLTIb1OVgkShtRbjs3EoZPuLjicnlQeKprhw=@vger.kernel.org X-Gm-Message-State: AOJu0YzhCx+HvEEjnwrEcKcIkDI+Ej1OFR95chuCSX7F2q8vJzZ8GS/V MHu5B/wqkxpyAA3pYYgw3iBtnPMzNM2Y4XfEp3dg3AXDXVJJg0UMlvYu X-Gm-Gg: AR+sD10nq45XKDFGo//xbxr0dj2zTpI0kAIsKRdLIDJhO0p4lpVCQB8vlMGqGcVsVTa Fisbdo/K4CS/HF9i79MhGV3iHdXPPiswiqQ90xG7vlcDHfHehYEIovUhTGqEfOtnoT5uN0YxdmT 6c8s0m2jaPgUwtuwD1wI3GqE8f/CZUZCfGjkseEL3wtE1pC9gU6oCUdVyKxJxXObOOIcK+y6Gmh Mc2eg/WfCFCHmQTiXkFEBlJ7YgZslKEPHEPebjMvPiVfcfocK2o8jcqM7WN1zST+Cfe1VtukKUp vGEl/LHNvJhLfEo0zVT0RNgiCvLOg7kVAPa1KBsuLCAWHiaDrNu/3sA24dp8nUalhAIUvn26KAj 7HPbBcMbk+hzKsY/Mwq2OUuwE5cRkafggcz9FjSiLimdrrdv1Seu8M5KBZmXZz+RNOyqwnUblkv sIxNtlQZ8Hdn4VJ4cFJ0BBqp+CS1THYqmGf7aLdw/a4E4N9jq4MNS4ZTCmEgeXSWwPqOBo61Mo0 x4kpTMrYzGF79+HguQ= X-Received: by 2002:ac8:5c85:0:b0:51c:61a:50e6 with SMTP id d75a77b69052e-52b566f6d04mr218681631cf.3.1785784557192; Mon, 03 Aug 2026 12:15:57 -0700 (PDT) Received: from achantapc.tail227c81.ts.net ([2600:4040:122e:7d00:f04d:e0df:fe04:e789]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-52b4e64df9bsm67803541cf.0.2026.08.03.12.15.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Aug 2026 12:15:56 -0700 (PDT) From: Sriman Achanta To: Jiri Kosina , Benjamin Tissoires Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Bastien Nocera , Simon Wood , Christian Mayer , Sriman Achanta Subject: [PATCH v7 1/8] HID: steelseries: Fix ARCTIS_1_X device mislabeling Date: Mon, 3 Aug 2026 15:15:46 -0400 Message-ID: <20260803191553.66368-2-srimanachanta@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803191553.66368-1-srimanachanta@gmail.com> References: <20260803191553.66368-1-srimanachanta@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The SteelSeries Arctis 1 Wireless for Xbox (0x12b6) was labelled as the plain Arctis 1 Wireless. Rename USB_DEVICE_ID_STEELSERIES_ARCTIS_1 to USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X, along with the matching quirk flag and device table entry. The device ID value is unchanged. Signed-off-by: Sriman Achanta --- drivers/hid/hid-ids.h | 4 ++-- drivers/hid/hid-quirks.c | 2 +- drivers/hid/hid-steelseries.c | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index b70f719b3b07..70b30e13b96f 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1377,8 +1377,8 @@ =20 #define USB_VENDOR_ID_STEELSERIES 0x1038 #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 -#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6 -#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 #define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122 #define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161 =20 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 57d8efdd9b89..f546179858c2 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -747,7 +747,7 @@ static const struct hid_device_id hid_have_special_driv= er[] =3D { #endif #if IS_ENABLED(CONFIG_HID_STEELSERIES) { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRW= S1) }, - { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_1_X) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_9) }, #endif #if IS_ENABLED(CONFIG_HID_SUNPLUS) diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index 73f77dd07110..54efa70d128d 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -21,7 +21,7 @@ #include "hid-ids.h" =20 #define STEELSERIES_SRWS1 BIT(0) -#define STEELSERIES_ARCTIS_1 BIT(1) +#define STEELSERIES_ARCTIS_1_X BIT(1) #define STEELSERIES_ARCTIS_9 BIT(2) #define STEELSERIES_MSI_RGB BIT(3) =20 @@ -396,7 +396,7 @@ static void steelseries_headset_fetch_battery(struct hi= d_device *hdev) { int ret =3D 0; =20 - if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1) + if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) ret =3D steelseries_headset_request_battery(hdev, arctis_1_battery_request, sizeof(arctis_1_battery_request)); else if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_9) @@ -829,7 +829,7 @@ static int steelseries_probe(struct hid_device *hdev, c= onst struct hid_device_id return 0; } =20 - if ((sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)) && + if ((sd->quirks & (STEELSERIES_ARCTIS_1_X | STEELSERIES_ARCTIS_9)) && steelseries_headset_battery_register(sd) < 0) hid_err(sd->hdev, "Failed to register battery for headset\n"); @@ -897,7 +897,7 @@ static uint8_t steelseries_headset_map_capacity(uint8_t= capacity, uint8_t min_in =20 static bool steelseries_is_headset(struct hid_device *hdev) { - return hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1 || + return hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X || hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_9; } =20 @@ -923,7 +923,7 @@ static int steelseries_headset_raw_event(struct hid_dev= ice *hdev, connected =3D sd->headset_connected; charging =3D sd->battery_charging; =20 - if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1) { + if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) { hid_dbg(sd->hdev, "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf); if (size < ARCTIS_1_BATTERY_RESPONSE_LEN || @@ -1010,8 +1010,8 @@ static const struct hid_device_id steelseries_devices= [] =3D { .driver_data =3D STEELSERIES_SRWS1 }, =20 { /* SteelSeries Arctis 1 Wireless for XBox */ - HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_1), - .driver_data =3D STEELSERIES_ARCTIS_1 }, + HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_1_X), + .driver_data =3D STEELSERIES_ARCTIS_1_X }, =20 { /* SteelSeries Arctis 9 Wireless for XBox */ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_9), --=20 2.55.0 From nobody Fri Oct 2 07:45:34 2026 Received: from mail-qt1-f172.google.com (mail-qt1-f172.google.com [209.85.160.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9CFE5435EEC for ; Mon, 3 Aug 2026 19:15:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784565; cv=none; b=PBAka6Q4llFxlnYxP1Lu3L8cOisqyENoXVTWqpjItvMtVfnDtUgmww0w27sR7AsfidHpuiykXyXu/3jQDCSLAhph1tPldv2MuXtMr9fYtawmXOiqT4OnbWJxfNy5ioCSkkcCQTUev6+fD8q9781i8gfOl/hrFf75m9M1CcYyJ4E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784565; c=relaxed/simple; bh=THWVog3unA0NvwTDcfIG0DuOMYJeFfNiywgNl+mtlbI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KFa3WVP3tnnTCV6hi7uNJ0lJQ2Eo+JEZ1bQpUkIDJYOlyDqcCdI/BI12QTNbp28Q6iVofkoHW6HQdDBoNKxsAMKYU39boW/WePmI3yYllq+eqPQfz7gZlyXytPl6EbwA1ZFi39O2CLITqQgg7aJJhYKK24OC9SdbEVIqitj8G6w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=kXzuB/ui; arc=none smtp.client-ip=209.85.160.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="kXzuB/ui" Received: by mail-qt1-f172.google.com with SMTP id d75a77b69052e-51c0cea8883so2044701cf.1 for ; Mon, 03 Aug 2026 12:15:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785784558; x=1786389358; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=iNWKXcGaEITFt6tzp0oyfjfRvSew6Eexkq+ZL5uqIW4=; b=kXzuB/uim3OyoWC/OOxApjicZH0xAoJ9ZUvSMsufcQldbCBr3KHp3XYzKl9oXHkyvi gGBbZAD9fomhidn6yUncS5DaiBMvvc+U5VK4Uh0cAJ1uh+8HK++QyOpWbcm8CifMP3Ce 41TSkCuAUrDrb8ysiEPYPQVV0KvjPe+sn7ZzUqi7tjv0u+1BI7dzG45Lo0F7aRHNDVm5 Bq4kd0F3H+HuGl+15pdXX60WVJcrNNU51fUBHmCibP0NXYf1cH15GJGfnXni+RFuozER hMhzMC2nAG7M/nrTFDLDC/vdEWtIsmu+EC5qmRFCrgwxF76KT619WJKt1Y16N11Vsw+w PgLw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785784558; x=1786389358; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=iNWKXcGaEITFt6tzp0oyfjfRvSew6Eexkq+ZL5uqIW4=; b=RthuOXbuprjE8waBQBN1Wf+RUk2cj568H542pnYqkpG//UZjkZ35F4iOs5VNqM2Zep cRsV1wDJH9Pj+OU3ZMCA2iOGTsUwOuojuPhpJipd6ntgzxTxSiPztGokajp1eQFVn47k eXxbzcoVBzqRpkVyeGRaFExlWFKz66LTF3TTQkZK57hgqc28eVVEfpTDTin+1uGZFOIR fnaOFgi/wWL//qMMtEjETrIH77GIfUBEAgLcJKufxxGrmAkmzqFgCc7Q5xaGYKS3OXIv J1klVipcGASipBfOK+KNoc4KKOF4gL1W/rrNWyWkfiBcA29mL6lhwv/2ozaea3lK5ZFH pCXg== X-Forwarded-Encrypted: i=1; AHgh+RpMIy7VNHfUHGF4ruxsZ/H3cmdg6KVcFvNKzZ8V1nKnYSCJAsV03Ggb2e+/KVQ+tauxNWP8P7/yjbvsV+Q=@vger.kernel.org X-Gm-Message-State: AOJu0YxzElr+5GdnwxRKNMRahW4xR+ZiL/YhpdSpMcOB1muPtQaEVBFR f32cT/Zy79HMQpWKSUgoDvjn95YNV2Uf7oLKdNq8765rvbvY02BDlxTM X-Gm-Gg: AR+sD12dJpup/m5b4J4ehqoBsbGmyNICD28Edoq9kQ8wyk7GNGXDl7aSIciLlop/DYy Gp23MsINrv/US/A1fOL/s3/T3klD8g/FxvgSyxc8BCGKM1mYm9dfDz0zd2vU29nuyxbiqN8pRoN MnSGENnuqxa5FWgqPKY2x6Ruip1CE4qMic+Khl1zFxZ7bBwLz5Go1mwzxQL3GkBopgUgP53wzS5 wwlhRjF4JDAxJDKrI4rD1dh3+KEtQiiFzYQKN2/XO2g91pV7Bae42xwFepNJILB9kxUZqgZC8pM Jf0gNPyrgTxkBBKqYXNJeDNZgtOgQPBDmdI6TpnNulEuLLp5Vi4MnEclXkGOv0st4zhWHPDuPfA 0vM4WBG9it011DhU3P1GxikQR5zJtSHgl15AtQxhNOvQ1p5cANOybjWSQx38LNyzrrET2mZGz/W VReBic/1wEG2gTBARQ6y8nGEuN3V9235SuorqR10JJcCwy+D2ujiDYUOLTc5r5Xluhek/S52RDA ZllbRdqhN2bbvSv4sY= X-Received: by 2002:a05:622a:2992:b0:517:5f04:f23f with SMTP id d75a77b69052e-52cd3468470mr14407751cf.4.1785784558036; Mon, 03 Aug 2026 12:15:58 -0700 (PDT) Received: from achantapc.tail227c81.ts.net ([2600:4040:122e:7d00:f04d:e0df:fe04:e789]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-52b4e64df9bsm67803541cf.0.2026.08.03.12.15.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Aug 2026 12:15:57 -0700 (PDT) From: Sriman Achanta To: Jiri Kosina , Benjamin Tissoires Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Bastien Nocera , Simon Wood , Christian Mayer , Sriman Achanta Subject: [PATCH v7 2/8] HID: steelseries: Split Arctis headset driver into separate module Date: Mon, 3 Aug 2026 15:15:47 -0400 Message-ID: <20260803191553.66368-3-srimanachanta@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803191553.66368-1-srimanachanta@gmail.com> References: <20260803191553.66368-1-srimanachanta@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Move all Arctis headset code (battery monitoring, wireless status, power supply registration, raw event handling) from hid-steelseries.c into the new hid-steelseries-arctis.c driver module. hid-steelseries.c keeps the SRWS1 racing wheel and the MSI RGB LED devices, while hid-steelseries-arctis.c handles the Arctis 1 (Xbox) and Arctis 9 wireless headsets with their own device table, probe, remove, and raw_event implementations. Signed-off-by: Sriman Achanta --- drivers/hid/Makefile | 2 +- drivers/hid/hid-steelseries-arctis.c | 426 +++++++++++++++++++++++++++ drivers/hid/hid-steelseries.c | 366 +---------------------- 3 files changed, 428 insertions(+), 366 deletions(-) create mode 100644 drivers/hid/hid-steelseries-arctis.c diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 23e6e3dd0c56..4a172bd27b11 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -134,7 +134,7 @@ obj-$(CONFIG_HID_SMARTJOYPLUS) +=3D hid-sjoy.o obj-$(CONFIG_HID_SONY) +=3D hid-sony.o obj-$(CONFIG_HID_SPEEDLINK) +=3D hid-speedlink.o obj-$(CONFIG_HID_STEAM) +=3D hid-steam.o -obj-$(CONFIG_HID_STEELSERIES) +=3D hid-steelseries.o +obj-$(CONFIG_HID_STEELSERIES) +=3D hid-steelseries.o hid-steelseries-arcti= s.o obj-$(CONFIG_HID_SUNPLUS) +=3D hid-sunplus.o obj-$(CONFIG_HID_GREENASIA) +=3D hid-gaff.o obj-$(CONFIG_HID_THRUSTMASTER) +=3D hid-tmff.o hid-thrustmaster.o diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelse= ries-arctis.c new file mode 100644 index 000000000000..6cae2e00bd8b --- /dev/null +++ b/drivers/hid/hid-steelseries-arctis.c @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Steelseries arctis headsets + * + * Copyright (c) 2023 Bastien Nocera + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define STEELSERIES_ARCTIS_1_X BIT(0) +#define STEELSERIES_ARCTIS_9 BIT(1) + +struct steelseries_device { + struct hid_device *hdev; + unsigned long quirks; + + struct delayed_work battery_work; + spinlock_t lock; + bool removed; + + struct power_supply_desc battery_desc; + struct power_supply *battery; + uint8_t battery_capacity; + bool headset_connected; + bool battery_charging; + bool battery_registered; +}; + +#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000 + +#define ARCTIS_1_BATTERY_RESPONSE_LEN 8 +#define ARCTIS_9_BATTERY_RESPONSE_LEN 64 +static const char arctis_1_battery_request[] =3D { 0x06, 0x12 }; +static const char arctis_9_battery_request[] =3D { 0x00, 0x20 }; + +static int steelseries_headset_request_battery(struct hid_device *hdev, + const char *request, size_t len) +{ + u8 *write_buf; + int ret; + + /* Request battery information */ + write_buf =3D kmemdup(request, len, GFP_KERNEL); + if (!write_buf) + return -ENOMEM; + + hid_dbg(hdev, "Sending battery request report"); + ret =3D hid_hw_raw_request(hdev, request[0], write_buf, len, + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + if (ret < (int)len) { + hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret); + ret =3D -ENODATA; + } + + kfree(write_buf); + return ret; +} + +static void steelseries_headset_fetch_battery(struct hid_device *hdev) +{ + int ret =3D 0; + + if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) + ret =3D steelseries_headset_request_battery(hdev, + arctis_1_battery_request, sizeof(arctis_1_battery_request)); + else if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_9) + ret =3D steelseries_headset_request_battery(hdev, + arctis_9_battery_request, sizeof(arctis_9_battery_request)); + + if (ret < 0) + hid_dbg(hdev, + "Battery query failed (err: %d)\n", ret); +} + +static int battery_capacity_to_level(int capacity) +{ + if (capacity >=3D 50) + return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + if (capacity >=3D 20) + return POWER_SUPPLY_CAPACITY_LEVEL_LOW; + return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; +} + +static void steelseries_headset_battery_timer_tick(struct work_struct *wor= k) +{ + struct steelseries_device *sd =3D container_of(work, + struct steelseries_device, battery_work.work); + struct hid_device *hdev =3D sd->hdev; + + steelseries_headset_fetch_battery(hdev); +} + +#define STEELSERIES_PREFIX "SteelSeries " +#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX) + +static int steelseries_headset_battery_get_property(struct power_supply *p= sy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct steelseries_device *sd =3D power_supply_get_drvdata(psy); + int ret =3D 0; + + switch (psp) { + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval =3D sd->hdev->name; + while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN)) + val->strval +=3D STEELSERIES_PREFIX_LEN; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval =3D "SteelSeries"; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval =3D 1; + break; + case POWER_SUPPLY_PROP_STATUS: + if (sd->headset_connected) { + val->intval =3D sd->battery_charging ? + POWER_SUPPLY_STATUS_CHARGING : + POWER_SUPPLY_STATUS_DISCHARGING; + } else + val->intval =3D POWER_SUPPLY_STATUS_UNKNOWN; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval =3D POWER_SUPPLY_SCOPE_DEVICE; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval =3D sd->battery_capacity; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + val->intval =3D battery_capacity_to_level(sd->battery_capacity); + break; + default: + ret =3D -EINVAL; + break; + } + return ret; +} + +static void +steelseries_headset_set_wireless_status(struct hid_device *hdev, + bool connected) +{ + struct usb_interface *intf; + + if (!hid_is_usb(hdev)) + return; + + intf =3D to_usb_interface(hdev->dev.parent); + usb_set_wireless_status(intf, connected ? + USB_WIRELESS_STATUS_CONNECTED : + USB_WIRELESS_STATUS_DISCONNECTED); +} + +static enum power_supply_property steelseries_headset_battery_props[] =3D { + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +static int steelseries_headset_battery_register(struct steelseries_device = *sd) +{ + static atomic_t battery_no =3D ATOMIC_INIT(0); + struct power_supply_config battery_cfg =3D { .drv_data =3D sd, }; + unsigned long n; + int ret; + + sd->battery_desc.type =3D POWER_SUPPLY_TYPE_BATTERY; + sd->battery_desc.properties =3D steelseries_headset_battery_props; + sd->battery_desc.num_properties =3D ARRAY_SIZE(steelseries_headset_batter= y_props); + sd->battery_desc.get_property =3D steelseries_headset_battery_get_propert= y; + sd->battery_desc.use_for_apm =3D 0; + n =3D atomic_inc_return(&battery_no) - 1; + sd->battery_desc.name =3D devm_kasprintf(&sd->hdev->dev, GFP_KERNEL, + "steelseries_headset_battery_%ld", n); + if (!sd->battery_desc.name) + return -ENOMEM; + + /* avoid the warning of 0% battery while waiting for the first info */ + steelseries_headset_set_wireless_status(sd->hdev, false); + sd->battery_capacity =3D 100; + sd->battery_charging =3D false; + + sd->battery =3D devm_power_supply_register(&sd->hdev->dev, + &sd->battery_desc, &battery_cfg); + if (IS_ERR(sd->battery)) { + ret =3D PTR_ERR(sd->battery); + hid_err(sd->hdev, + "%s:power_supply_register failed with error %d\n", + __func__, ret); + return ret; + } + power_supply_powers(sd->battery, &sd->hdev->dev); + + INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_ti= ck); + /* Pairs with smp_load_acquire() in raw_event and remove paths */ + smp_store_release(&sd->battery_registered, true); + steelseries_headset_fetch_battery(sd->hdev); + + if (sd->quirks & STEELSERIES_ARCTIS_9) { + /* The first fetch_battery request can remain unanswered in some cases */ + schedule_delayed_work(&sd->battery_work, + msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); + } + + return 0; +} + +static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint= 8_t usage_page) +{ + if (hdev->rsize < 3) + return false; + + return hdev->rdesc[0] =3D=3D 0x06 && + hdev->rdesc[1] =3D=3D usage_page && + hdev->rdesc[2] =3D=3D 0xff; +} + +static int steelseries_arctis_probe(struct hid_device *hdev, const struct = hid_device_id *id) +{ + struct steelseries_device *sd; + int ret; + + sd =3D devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + hid_set_drvdata(hdev, sd); + sd->hdev =3D hdev; + sd->quirks =3D id->driver_data; + + ret =3D hid_parse(hdev); + if (ret) + return ret; + + if (sd->quirks & STEELSERIES_ARCTIS_9 && + !steelseries_is_vendor_usage_page(hdev, 0xc0)) + return -ENODEV; + + spin_lock_init(&sd->lock); + + ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + return ret; + + ret =3D hid_hw_open(hdev); + if (ret) + goto err_stop; + + if (steelseries_headset_battery_register(sd) < 0) + hid_err(sd->hdev, + "Failed to register battery for headset\n"); + + return 0; + +err_stop: + hid_hw_stop(hdev); + return ret; +} + +static void steelseries_arctis_remove(struct hid_device *hdev) +{ + struct steelseries_device *sd; + unsigned long flags; + + sd =3D hid_get_drvdata(hdev); + if (!sd) + return; + + spin_lock_irqsave(&sd->lock, flags); + sd->removed =3D true; + spin_unlock_irqrestore(&sd->lock, flags); + + /* Pairs with smp_store_release() in steelseries_headset_battery_register= () */ + if (smp_load_acquire(&sd->battery_registered)) + cancel_delayed_work_sync(&sd->battery_work); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t = min_in, uint8_t max_in) +{ + if (capacity >=3D max_in) + return 100; + if (capacity <=3D min_in) + return 0; + return (capacity - min_in) * 100 / (max_in - min_in); +} + +static int steelseries_arctis_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *read_buf, + int size) +{ + struct steelseries_device *sd =3D hid_get_drvdata(hdev); + int capacity; + bool connected; + bool charging; + unsigned long flags; + + /* Pairs with smp_store_release() in steelseries_headset_battery_register= () */ + if (!sd || !smp_load_acquire(&sd->battery_registered)) + return 0; + + capacity =3D sd->battery_capacity; + connected =3D sd->headset_connected; + charging =3D sd->battery_charging; + + if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) { + hid_dbg(sd->hdev, + "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf); + if (size < ARCTIS_1_BATTERY_RESPONSE_LEN || + memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_requ= est))) { + if (!delayed_work_pending(&sd->battery_work)) + goto request_battery; + return 0; + } + if (read_buf[2] =3D=3D 0x01) { + connected =3D false; + capacity =3D 100; + } else { + connected =3D true; + capacity =3D read_buf[3]; + } + } + + if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_9) { + hid_dbg(sd->hdev, + "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf); + if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) { + if (!delayed_work_pending(&sd->battery_work)) + goto request_battery; + return 0; + } + + if (read_buf[0] =3D=3D 0xaa && read_buf[1] =3D=3D 0x01) { + connected =3D true; + charging =3D read_buf[4] =3D=3D 0x01; + + /* + * Found no official documentation about min and max. + * Values defined by testing. + */ + capacity =3D steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d); + } else { + /* + * Device is off and sends the last known status read_buf[1] =3D=3D 0x0= 3 or + * there is no known status of the device read_buf[0] =3D=3D 0x55 + */ + connected =3D false; + charging =3D false; + } + } + + if (connected !=3D sd->headset_connected) { + hid_dbg(sd->hdev, + "Connected status changed from %sconnected to %sconnected\n", + sd->headset_connected ? "" : "not ", + connected ? "" : "not "); + sd->headset_connected =3D connected; + steelseries_headset_set_wireless_status(hdev, connected); + } + + if (capacity !=3D sd->battery_capacity) { + hid_dbg(sd->hdev, + "Battery capacity changed from %d%% to %d%%\n", + sd->battery_capacity, capacity); + sd->battery_capacity =3D capacity; + power_supply_changed(sd->battery); + } + + if (charging !=3D sd->battery_charging) { + hid_dbg(sd->hdev, + "Battery charging status changed from %scharging to %scharging\n", + sd->battery_charging ? "" : "not ", + charging ? "" : "not "); + sd->battery_charging =3D charging; + power_supply_changed(sd->battery); + } + +request_battery: + spin_lock_irqsave(&sd->lock, flags); + if (!sd->removed) + schedule_delayed_work(&sd->battery_work, + msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); + spin_unlock_irqrestore(&sd->lock, flags); + + return 0; +} + +static const struct hid_device_id steelseries_arctis_devices[] =3D { + { /* SteelSeries Arctis 1 Wireless for XBox */ + HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCT= IS_1_X), + .driver_data =3D STEELSERIES_ARCTIS_1_X }, + + { /* SteelSeries Arctis 9 Wireless for XBox */ + HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCT= IS_9), + .driver_data =3D STEELSERIES_ARCTIS_9 }, + + { } +}; +MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices); + +static struct hid_driver steelseries_arctis_driver =3D { + .name =3D "hid-steelseries-arctis", + .id_table =3D steelseries_arctis_devices, + .probe =3D steelseries_arctis_probe, + .remove =3D steelseries_arctis_remove, + .raw_event =3D steelseries_arctis_raw_event, +}; + +module_hid_driver(steelseries_arctis_driver); +MODULE_DESCRIPTION("HID driver for Steelseries arctis headsets"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Christian Mayer "); +MODULE_AUTHOR("Bastien Nocera "); diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index 54efa70d128d..7292da0313e1 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -3,7 +3,6 @@ * HID driver for Steelseries devices * * Copyright (c) 2013 Simon Wood - * Copyright (c) 2023 Bastien Nocera */ =20 /* @@ -21,9 +20,7 @@ #include "hid-ids.h" =20 #define STEELSERIES_SRWS1 BIT(0) -#define STEELSERIES_ARCTIS_1_X BIT(1) -#define STEELSERIES_ARCTIS_9 BIT(2) -#define STEELSERIES_MSI_RGB BIT(3) +#define STEELSERIES_MSI_RGB BIT(1) =20 #define STEELSERIES_MSI_RGB_WVALUE 0x0300 /* Feature report, ID 0 */ #define STEELSERIES_MSI_RGB_REPORT_LEN 524 @@ -39,17 +36,6 @@ struct steelseries_device { struct hid_device *hdev; unsigned long quirks; =20 - struct delayed_work battery_work; - spinlock_t lock; - bool removed; - - struct power_supply_desc battery_desc; - struct power_supply *battery; - uint8_t battery_capacity; - bool headset_connected; - bool battery_charging; - bool battery_registered; - #if STEELSERIES_HAS_LEDS_MULTICOLOR struct led_classdev_mc mc_cdev; struct mc_subled subled_info[3]; @@ -362,199 +348,6 @@ static int steelseries_srws1_probe(struct hid_device = *hdev, } #endif =20 -#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000 - -#define ARCTIS_1_BATTERY_RESPONSE_LEN 8 -#define ARCTIS_9_BATTERY_RESPONSE_LEN 64 -static const char arctis_1_battery_request[] =3D { 0x06, 0x12 }; -static const char arctis_9_battery_request[] =3D { 0x00, 0x20 }; - -static int steelseries_headset_request_battery(struct hid_device *hdev, - const char *request, size_t len) -{ - u8 *write_buf; - int ret; - - /* Request battery information */ - write_buf =3D kmemdup(request, len, GFP_KERNEL); - if (!write_buf) - return -ENOMEM; - - hid_dbg(hdev, "Sending battery request report"); - ret =3D hid_hw_raw_request(hdev, request[0], write_buf, len, - HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); - if (ret < (int)len) { - hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret); - ret =3D -ENODATA; - } - - kfree(write_buf); - return ret; -} - -static void steelseries_headset_fetch_battery(struct hid_device *hdev) -{ - int ret =3D 0; - - if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) - ret =3D steelseries_headset_request_battery(hdev, - arctis_1_battery_request, sizeof(arctis_1_battery_request)); - else if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_9) - ret =3D steelseries_headset_request_battery(hdev, - arctis_9_battery_request, sizeof(arctis_9_battery_request)); - - if (ret < 0) - hid_dbg(hdev, - "Battery query failed (err: %d)\n", ret); -} - -static int battery_capacity_to_level(int capacity) -{ - if (capacity >=3D 50) - return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; - if (capacity >=3D 20) - return POWER_SUPPLY_CAPACITY_LEVEL_LOW; - return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; -} - -static void steelseries_headset_battery_timer_tick(struct work_struct *wor= k) -{ - struct steelseries_device *sd =3D container_of(work, - struct steelseries_device, battery_work.work); - struct hid_device *hdev =3D sd->hdev; - - steelseries_headset_fetch_battery(hdev); -} - -#define STEELSERIES_PREFIX "SteelSeries " -#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX) - -static int steelseries_headset_battery_get_property(struct power_supply *p= sy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct steelseries_device *sd =3D power_supply_get_drvdata(psy); - int ret =3D 0; - - switch (psp) { - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval =3D sd->hdev->name; - while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN)) - val->strval +=3D STEELSERIES_PREFIX_LEN; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval =3D "SteelSeries"; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval =3D 1; - break; - case POWER_SUPPLY_PROP_STATUS: - if (sd->headset_connected) { - val->intval =3D sd->battery_charging ? - POWER_SUPPLY_STATUS_CHARGING : - POWER_SUPPLY_STATUS_DISCHARGING; - } else - val->intval =3D POWER_SUPPLY_STATUS_UNKNOWN; - break; - case POWER_SUPPLY_PROP_SCOPE: - val->intval =3D POWER_SUPPLY_SCOPE_DEVICE; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval =3D sd->battery_capacity; - break; - case POWER_SUPPLY_PROP_CAPACITY_LEVEL: - val->intval =3D battery_capacity_to_level(sd->battery_capacity); - break; - default: - ret =3D -EINVAL; - break; - } - return ret; -} - -static void -steelseries_headset_set_wireless_status(struct hid_device *hdev, - bool connected) -{ - struct usb_interface *intf; - - if (!hid_is_usb(hdev)) - return; - - intf =3D to_usb_interface(hdev->dev.parent); - usb_set_wireless_status(intf, connected ? - USB_WIRELESS_STATUS_CONNECTED : - USB_WIRELESS_STATUS_DISCONNECTED); -} - -static enum power_supply_property steelseries_headset_battery_props[] =3D { - POWER_SUPPLY_PROP_MODEL_NAME, - POWER_SUPPLY_PROP_MANUFACTURER, - POWER_SUPPLY_PROP_PRESENT, - POWER_SUPPLY_PROP_STATUS, - POWER_SUPPLY_PROP_SCOPE, - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_LEVEL, -}; - -static int steelseries_headset_battery_register(struct steelseries_device = *sd) -{ - static atomic_t battery_no =3D ATOMIC_INIT(0); - struct power_supply_config battery_cfg =3D { .drv_data =3D sd, }; - unsigned long n; - int ret; - - sd->battery_desc.type =3D POWER_SUPPLY_TYPE_BATTERY; - sd->battery_desc.properties =3D steelseries_headset_battery_props; - sd->battery_desc.num_properties =3D ARRAY_SIZE(steelseries_headset_batter= y_props); - sd->battery_desc.get_property =3D steelseries_headset_battery_get_propert= y; - sd->battery_desc.use_for_apm =3D 0; - n =3D atomic_inc_return(&battery_no) - 1; - sd->battery_desc.name =3D devm_kasprintf(&sd->hdev->dev, GFP_KERNEL, - "steelseries_headset_battery_%ld", n); - if (!sd->battery_desc.name) - return -ENOMEM; - - /* avoid the warning of 0% battery while waiting for the first info */ - steelseries_headset_set_wireless_status(sd->hdev, false); - sd->battery_capacity =3D 100; - sd->battery_charging =3D false; - - sd->battery =3D devm_power_supply_register(&sd->hdev->dev, - &sd->battery_desc, &battery_cfg); - if (IS_ERR(sd->battery)) { - ret =3D PTR_ERR(sd->battery); - hid_err(sd->hdev, - "%s:power_supply_register failed with error %d\n", - __func__, ret); - return ret; - } - power_supply_powers(sd->battery, &sd->hdev->dev); - - INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_ti= ck); - /* Pairs with smp_load_acquire() in raw_event and remove paths */ - smp_store_release(&sd->battery_registered, true); - steelseries_headset_fetch_battery(sd->hdev); - - if (sd->quirks & STEELSERIES_ARCTIS_9) { - /* The first fetch_battery request can remain unanswered in some cases */ - schedule_delayed_work(&sd->battery_work, - msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); - } - - return 0; -} - -static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint= 8_t usage_page) -{ - if (hdev->rsize < 3) - return false; - - return hdev->rdesc[0] =3D=3D 0x06 && - hdev->rdesc[1] =3D=3D usage_page && - hdev->rdesc[2] =3D=3D 0xff; -} - static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] =3D { { .matches =3D { @@ -804,12 +597,6 @@ static int steelseries_probe(struct hid_device *hdev, = const struct hid_device_id if (ret) return ret; =20 - if (sd->quirks & STEELSERIES_ARCTIS_9 && - !steelseries_is_vendor_usage_page(hdev, 0xc0)) - return -ENODEV; - - spin_lock_init(&sd->lock); - ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) return ret; @@ -826,14 +613,8 @@ static int steelseries_probe(struct hid_device *hdev, = const struct hid_device_id ret); sd->quirks &=3D ~STEELSERIES_MSI_RGB; } - return 0; } =20 - if ((sd->quirks & (STEELSERIES_ARCTIS_1_X | STEELSERIES_ARCTIS_9)) && - steelseries_headset_battery_register(sd) < 0) - hid_err(sd->hdev, - "Failed to register battery for headset\n"); - return 0; =20 err_stop: @@ -843,9 +624,6 @@ static int steelseries_probe(struct hid_device *hdev, c= onst struct hid_device_id =20 static void steelseries_remove(struct hid_device *hdev) { - struct steelseries_device *sd; - unsigned long flags; - if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_SRWS1) { #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) @@ -854,18 +632,6 @@ static void steelseries_remove(struct hid_device *hdev) return; } =20 - sd =3D hid_get_drvdata(hdev); - if (!sd) - return; - - spin_lock_irqsave(&sd->lock, flags); - sd->removed =3D true; - spin_unlock_irqrestore(&sd->lock, flags); - - /* Pairs with smp_store_release() in steelseries_headset_battery_register= () */ - if (smp_load_acquire(&sd->battery_registered)) - cancel_delayed_work_sync(&sd->battery_work); - hid_hw_close(hdev); hid_hw_stop(hdev); } @@ -886,137 +652,10 @@ static const __u8 *steelseries_srws1_report_fixup(st= ruct hid_device *hdev, return rdesc; } =20 -static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t = min_in, uint8_t max_in) -{ - if (capacity >=3D max_in) - return 100; - if (capacity <=3D min_in) - return 0; - return (capacity - min_in) * 100 / (max_in - min_in); -} - -static bool steelseries_is_headset(struct hid_device *hdev) -{ - return hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X || - hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_9; -} - -static int steelseries_headset_raw_event(struct hid_device *hdev, - struct hid_report *report, u8 *read_buf, - int size) -{ - struct steelseries_device *sd; - int capacity; - bool connected; - bool charging; - unsigned long flags; - - if (!steelseries_is_headset(hdev)) - return 0; - - sd =3D hid_get_drvdata(hdev); - /* Pairs with smp_store_release() in steelseries_headset_battery_register= () */ - if (!sd || !smp_load_acquire(&sd->battery_registered)) - return 0; - - capacity =3D sd->battery_capacity; - connected =3D sd->headset_connected; - charging =3D sd->battery_charging; - - if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) { - hid_dbg(sd->hdev, - "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf); - if (size < ARCTIS_1_BATTERY_RESPONSE_LEN || - memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_r= equest))) { - if (!delayed_work_pending(&sd->battery_work)) - goto request_battery; - return 0; - } - if (read_buf[2] =3D=3D 0x01) { - connected =3D false; - capacity =3D 100; - } else { - connected =3D true; - capacity =3D read_buf[3]; - } - } - - if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_9) { - hid_dbg(sd->hdev, - "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf); - if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) { - if (!delayed_work_pending(&sd->battery_work)) - goto request_battery; - return 0; - } - - if (read_buf[0] =3D=3D 0xaa && read_buf[1] =3D=3D 0x01) { - connected =3D true; - charging =3D read_buf[4] =3D=3D 0x01; - - /* - * Found no official documentation about min and max. - * Values defined by testing. - */ - capacity =3D steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d); - } else { - /* - * Device is off and sends the last known status read_buf[1] =3D=3D 0x0= 3 or - * there is no known status of the device read_buf[0] =3D=3D 0x55 - */ - connected =3D false; - charging =3D false; - } - } - - if (connected !=3D sd->headset_connected) { - hid_dbg(sd->hdev, - "Connected status changed from %sconnected to %sconnected\n", - sd->headset_connected ? "" : "not ", - connected ? "" : "not "); - sd->headset_connected =3D connected; - steelseries_headset_set_wireless_status(hdev, connected); - } - - if (capacity !=3D sd->battery_capacity) { - hid_dbg(sd->hdev, - "Battery capacity changed from %d%% to %d%%\n", - sd->battery_capacity, capacity); - sd->battery_capacity =3D capacity; - power_supply_changed(sd->battery); - } - - if (charging !=3D sd->battery_charging) { - hid_dbg(sd->hdev, - "Battery charging status changed from %scharging to %scharging\n", - sd->battery_charging ? "" : "not ", - charging ? "" : "not "); - sd->battery_charging =3D charging; - power_supply_changed(sd->battery); - } - -request_battery: - spin_lock_irqsave(&sd->lock, flags); - if (!sd->removed) - schedule_delayed_work(&sd->battery_work, - msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); - spin_unlock_irqrestore(&sd->lock, flags); - - return 0; -} - static const struct hid_device_id steelseries_devices[] =3D { { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRW= S1), .driver_data =3D STEELSERIES_SRWS1 }, =20 - { /* SteelSeries Arctis 1 Wireless for XBox */ - HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_1_X), - .driver_data =3D STEELSERIES_ARCTIS_1_X }, - - { /* SteelSeries Arctis 9 Wireless for XBox */ - HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_9), - .driver_data =3D STEELSERIES_ARCTIS_9 }, - #if STEELSERIES_HAS_LEDS_MULTICOLOR { /* MSI Raider A18 KLC */ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI= _KLC), @@ -1037,12 +676,9 @@ static struct hid_driver steelseries_driver =3D { .probe =3D steelseries_probe, .remove =3D steelseries_remove, .report_fixup =3D steelseries_srws1_report_fixup, - .raw_event =3D steelseries_headset_raw_event, }; =20 module_hid_driver(steelseries_driver); MODULE_DESCRIPTION("HID driver for Steelseries devices"); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Bastien Nocera "); MODULE_AUTHOR("Simon Wood "); -MODULE_AUTHOR("Christian Mayer "); --=20 2.55.0 From nobody Fri Oct 2 07:45:34 2026 Received: from mail-qt1-f171.google.com (mail-qt1-f171.google.com [209.85.160.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A6FE2437107 for ; Mon, 3 Aug 2026 19:16:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784564; cv=none; b=LyLaUiVaN7DLV0MlF4AhlBvHKXXNLmaI56J92bV1vfcq0gRK1WN1Mjmqz3p0pSkJ1YnI27TpYU0iInnEIrT1enDSCzeOCnmo5x2RdClwc0hF159RgWDW5ZONWIAN6P/N4BzpKEIDmQ4NVkqAB9c+HvbSSr6IGIJszullqsHrbqc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784564; c=relaxed/simple; bh=K5gYBDhCNoukJ+t2g1bl8MmQbL9kwf5ndcQPuaMOaJo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=H/aeIeevlVNY/j9Nh7EmNh07SecB+hoKVVbNQcRuw9AK+Q1SkhnrFfm8mo5NaSh/Q7b2cwealK2SPYDL5RgTykvPcMDnCawt/h4+yQw3p450OL/XEFBSqsJsr7Oapf+A/NLlxvFbhCZb7uoHFYAvF/kcY8hBAwWQiIml95ijs+I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=A8uKFxeN; arc=none smtp.client-ip=209.85.160.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="A8uKFxeN" Received: by mail-qt1-f171.google.com with SMTP id d75a77b69052e-5283e3eff60so23518851cf.2 for ; Mon, 03 Aug 2026 12:16:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785784559; x=1786389359; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=9NsUnwWCXiD/A5Z70mNvw81Zu2cH5ZuA+WjIXQlkOnQ=; b=A8uKFxeNim9ocHuL99vHvUyc4FS0XrBnPyKWUnTrlMAJ9JFjarAqe+qar9Ri6Fi9Az ZsYVfR6b+SLBqWoh2qNF8NlymR89UAML1EnQsrWJ+S71S8RQ3YI3AwhLxiAmm/qBr1WY WnyPqb+ZlHzkBlEvx4afwVnFsGiZA9dZFN6ME2DMAnHDiTfGFE7lL6Mkvg7LKpjTIOVk H9N3XC29Hn2gzCodFGlT9rZB71tf7HLROReEprUjyxYa6f55iuVAV0PqSxdXKQ1szmN7 cAb1NMwJGq5E0S9xjLDmjeSl+wOsoPRZYqOixsNlBG/4gENunNOIp+PvvFNYYEhMvBGw Yoog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785784559; x=1786389359; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=9NsUnwWCXiD/A5Z70mNvw81Zu2cH5ZuA+WjIXQlkOnQ=; b=q+y7DRpuZbOGRWARX9keVFbk8FA/ZlLmstMp9SgIrJT3NLU5Y/emzOiQHnheqdJMJ+ yx/4RMu27TAtJrySVF/HrA/nuUnOpK1BjybdYqCtrwS0YuTH5HpBAl3+BiW+UV0fXJ+/ Vf3k5Ug08eIkRD4cHgFvpLOkD6aYl79gUYnMqVKWDUal3y3sRwUrW+/3B2gguKfukkyy r8kG6grxAgJBOmEAw6X2eV3GD1LAaqp2VXb6vbi4JZzGmoXBDqBoMFNCzCLEjYrJO4pf lTCPbd1RKszXBo9bEsJJC13F4jg8P//5N0qwJDm4IrHfVeWQJywXcZPm8ES3E3m9p0KB zJLw== X-Forwarded-Encrypted: i=1; AHgh+Rru+H9/OPrHX9x6HCw4k1csqlr9ReozjqU3Uunmf1JfQvR/8e+oTwvROSPj2vKnfBNZFojkrhToeRMkuRA=@vger.kernel.org X-Gm-Message-State: AOJu0Yye1kpC78DbiYdL2SL63AtsXMRw9XHFT3aAR5BiDYNUZiJchcJo ziePt0+6RFf58HGSLOloETKSDAzAHpil2JGFMB1JCK9PYcDWGRFQ2Ui5 X-Gm-Gg: AR+sD11N9hTC7hIlMJ/at41szjy8ILxMpOsn20TnWnhwq0kKshlSiJlBqwF2wp7FEYP X6DaX/MqavIc4o1u8Oz2cLBTMQL09+YBsY6HBKYrPqAVKEqOp2IfXxsVdyJy8JAvoi42DidtLMy +V35WnseNXhMJaWwlJj+Nw5XsBqH7ZxnXzSc2VxxOQ1H6P4i+PUOukc/XyBGWoWMEwTCUEyT14p MtQdVszrXbcj4z3uZXALI+uZ8tiNU2tvEJxkraKEYbXmm801NDA5qVUQrBMW6IvWXvmh+EODFIk OaPKq7B3R9d0TEP5tcIuGgXp+6s4b+xe4xZzh68Mq7ggjnpGCEFlEXZ9VzNjSjBbmJB6I8Lnw9F Mp0klBQ2gps+npryEugqbfY/aQvlO7EMBEnX+SHfDsmODwW/uE+Ftsb1gkObv0kWNbEkPq4vgLd +EXqVBx4xWMqrTRSCjTbHdvW0LidDk8w9r4lIaTiuLVTNpiDYVHMpYIX692T1ko5Bna16OLpdPU c9GM7eduoO38yyBZYc= X-Received: by 2002:ac8:7f45:0:b0:517:78c7:d93a with SMTP id d75a77b69052e-52b567a8c0emr202737061cf.16.1785784558812; Mon, 03 Aug 2026 12:15:58 -0700 (PDT) Received: from achantapc.tail227c81.ts.net ([2600:4040:122e:7d00:f04d:e0df:fe04:e789]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-52b4e64df9bsm67803541cf.0.2026.08.03.12.15.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Aug 2026 12:15:58 -0700 (PDT) From: Sriman Achanta To: Jiri Kosina , Benjamin Tissoires Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Bastien Nocera , Simon Wood , Christian Mayer , Sriman Achanta Subject: [PATCH v7 3/8] HID: steelseries: Refactor Arctis driver to use device_info framework Date: Mon, 3 Aug 2026 15:15:48 -0400 Message-ID: <20260803191553.66368-4-srimanachanta@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803191553.66368-1-srimanachanta@gmail.com> References: <20260803191553.66368-1-srimanachanta@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Replace the per-product if/else quirk bitmap with a steelseries_device_info struct. Each model provides its capabilities, sync_interface, and request_status/parse_status callbacks. Report sending is folded into steelseries_send_report() and the feature and output wrappers, and the battery identifiers lose their per-model names. This is mostly a refactor, but it changes two things: - Battery status is polled from a periodic delayed work (status_work) instead of being requested from raw_event(). The interval stays at 3s. - Arctis 1 no longer clamps the reported capacity to 100% while disconnected. The connection state already controls how this is shown to userspace. ARCTIS_1_X and ARCTIS_9 keep working. No new devices are added. Signed-off-by: Sriman Achanta --- drivers/hid/hid-steelseries-arctis.c | 476 +++++++++++++++------------ 1 file changed, 260 insertions(+), 216 deletions(-) diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelse= ries-arctis.c index 6cae2e00bd8b..c54c56db9ddd 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -3,6 +3,7 @@ * HID driver for Steelseries arctis headsets * * Copyright (c) 2023 Bastien Nocera + * Copyright (c) 2026 Sriman Achanta */ =20 #include @@ -15,71 +16,89 @@ =20 #include "hid-ids.h" =20 -#define STEELSERIES_ARCTIS_1_X BIT(0) -#define STEELSERIES_ARCTIS_9 BIT(1) +#define SS_CAP_BATTERY BIT(0) + +struct steelseries_device; + +struct steelseries_device_info { + unsigned long capabilities; + + u8 sync_interface; + + int (*request_status)(struct hid_device *hdev); + void (*parse_status)(struct steelseries_device *sd, u8 *data, int size); +}; =20 struct steelseries_device { struct hid_device *hdev; - unsigned long quirks; + const struct steelseries_device_info *info; =20 - struct delayed_work battery_work; - spinlock_t lock; - bool removed; + struct delayed_work status_work; =20 struct power_supply_desc battery_desc; struct power_supply *battery; - uint8_t battery_capacity; bool headset_connected; + u8 battery_capacity; bool battery_charging; - bool battery_registered; -}; =20 -#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000 + spinlock_t lock; + bool removed; +}; =20 -#define ARCTIS_1_BATTERY_RESPONSE_LEN 8 -#define ARCTIS_9_BATTERY_RESPONSE_LEN 64 -static const char arctis_1_battery_request[] =3D { 0x06, 0x12 }; -static const char arctis_9_battery_request[] =3D { 0x00, 0x20 }; +/* + * Headset report helpers + */ =20 -static int steelseries_headset_request_battery(struct hid_device *hdev, - const char *request, size_t len) +static int steelseries_send_report(struct hid_device *hdev, const u8 *data, + int len, enum hid_report_type type) { - u8 *write_buf; + u8 *buf; int ret; =20 - /* Request battery information */ - write_buf =3D kmemdup(request, len, GFP_KERNEL); - if (!write_buf) + buf =3D kmemdup(data, len, GFP_KERNEL); + if (!buf) return -ENOMEM; =20 - hid_dbg(hdev, "Sending battery request report"); - ret =3D hid_hw_raw_request(hdev, request[0], write_buf, len, - HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); - if (ret < (int)len) { - hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret); - ret =3D -ENODATA; - } + ret =3D hid_hw_raw_request(hdev, data[0], buf, len, type, + HID_REQ_SET_REPORT); + kfree(buf); =20 - kfree(write_buf); - return ret; + if (ret < 0) + return ret; + if (ret < len) + return -EIO; + + return 0; } =20 -static void steelseries_headset_fetch_battery(struct hid_device *hdev) +static inline int steelseries_send_output_report(struct hid_device *hdev, + const u8 *data, int len) { - int ret =3D 0; + return steelseries_send_report(hdev, data, len, HID_OUTPUT_REPORT); +} =20 - if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) - ret =3D steelseries_headset_request_battery(hdev, - arctis_1_battery_request, sizeof(arctis_1_battery_request)); - else if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_9) - ret =3D steelseries_headset_request_battery(hdev, - arctis_9_battery_request, sizeof(arctis_9_battery_request)); +/* + * Headset status request functions + */ =20 - if (ret < 0) - hid_dbg(hdev, - "Battery query failed (err: %d)\n", ret); +static int steelseries_arctis_1_request_status(struct hid_device *hdev) +{ + const u8 data[] =3D { 0x06, 0x12 }; + + return steelseries_send_output_report(hdev, data, sizeof(data)); } =20 +static int steelseries_arctis_9_request_status(struct hid_device *hdev) +{ + const u8 data[] =3D { 0x00, 0x20 }; + + return steelseries_send_output_report(hdev, data, sizeof(data)); +} + +/* + * Headset battery helpers + */ + static int battery_capacity_to_level(int capacity) { if (capacity >=3D 50) @@ -89,30 +108,101 @@ static int battery_capacity_to_level(int capacity) return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; } =20 -static void steelseries_headset_battery_timer_tick(struct work_struct *wor= k) +static u8 steelseries_map_capacity(u8 capacity, u8 min_in, u8 max_in) { - struct steelseries_device *sd =3D container_of(work, - struct steelseries_device, battery_work.work); - struct hid_device *hdev =3D sd->hdev; + if (capacity >=3D max_in) + return 100; + if (capacity <=3D min_in) + return 0; + return (capacity - min_in) * 100 / (max_in - min_in); +} + +/* + * Headset status parse functions + */ =20 - steelseries_headset_fetch_battery(hdev); +static void steelseries_arctis_1_parse_status(struct steelseries_device *s= d, + u8 *data, int size) +{ + /* Only the battery status report echoes the request header. */ + if (size < 8 || data[0] !=3D 0x06 || data[1] !=3D 0x12) + return; + + sd->headset_connected =3D (data[2] !=3D 0x01); + sd->battery_capacity =3D data[3]; +} + +static void steelseries_arctis_9_parse_status(struct steelseries_device *s= d, + u8 *data, int size) +{ + if (size < 5) + return; + + if (data[0] =3D=3D 0xaa && data[1] =3D=3D 0x01) { + sd->headset_connected =3D true; + sd->battery_charging =3D (data[4] =3D=3D 0x01); + sd->battery_capacity =3D steelseries_map_capacity(data[3], 0x68, 0x9d); + } else { + /* Device off: 0x55 (no status) or 0x03 (stale status). */ + sd->headset_connected =3D false; + sd->battery_charging =3D false; + } +} + +/* + * Device info definitions + */ + +static const struct steelseries_device_info arctis_1_info =3D { + .sync_interface =3D 3, + .capabilities =3D SS_CAP_BATTERY, + .request_status =3D steelseries_arctis_1_request_status, + .parse_status =3D steelseries_arctis_1_parse_status, +}; + +static const struct steelseries_device_info arctis_9_info =3D { + .sync_interface =3D 0, + .capabilities =3D SS_CAP_BATTERY, + .request_status =3D steelseries_arctis_9_request_status, + .parse_status =3D steelseries_arctis_9_parse_status, +}; + +/* + * Headset wireless status and battery infrastructure + */ + +#define STEELSERIES_HEADSET_STATUS_TIMEOUT_MS 3000 + +static void +steelseries_headset_set_wireless_status(struct hid_device *hdev, + bool connected) +{ + struct usb_interface *intf; + + if (!hid_is_usb(hdev)) + return; + + intf =3D to_usb_interface(hdev->dev.parent); + usb_set_wireless_status(intf, connected ? + USB_WIRELESS_STATUS_CONNECTED : + USB_WIRELESS_STATUS_DISCONNECTED); } =20 #define STEELSERIES_PREFIX "SteelSeries " -#define STEELSERIES_PREFIX_LEN strlen(STEELSERIES_PREFIX) =20 -static int steelseries_headset_battery_get_property(struct power_supply *p= sy, +static int steelseries_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct steelseries_device *sd =3D power_supply_get_drvdata(psy); + size_t prefix_len; int ret =3D 0; =20 switch (psp) { case POWER_SUPPLY_PROP_MODEL_NAME: val->strval =3D sd->hdev->name; - while (!strncmp(val->strval, STEELSERIES_PREFIX, STEELSERIES_PREFIX_LEN)) - val->strval +=3D STEELSERIES_PREFIX_LEN; + while ((prefix_len =3D str_has_prefix(val->strval, STEELSERIES_PREFIX))) + val->strval +=3D prefix_len; break; case POWER_SUPPLY_PROP_MANUFACTURER: val->strval =3D "SteelSeries"; @@ -121,12 +211,12 @@ static int steelseries_headset_battery_get_property(s= truct power_supply *psy, val->intval =3D 1; break; case POWER_SUPPLY_PROP_STATUS: - if (sd->headset_connected) { - val->intval =3D sd->battery_charging ? - POWER_SUPPLY_STATUS_CHARGING : - POWER_SUPPLY_STATUS_DISCHARGING; - } else + if (!sd->headset_connected) val->intval =3D POWER_SUPPLY_STATUS_UNKNOWN; + else if (sd->battery_charging) + val->intval =3D POWER_SUPPLY_STATUS_CHARGING; + else + val->intval =3D POWER_SUPPLY_STATUS_DISCHARGING; break; case POWER_SUPPLY_PROP_SCOPE: val->intval =3D POWER_SUPPLY_SCOPE_DEVICE; @@ -144,22 +234,7 @@ static int steelseries_headset_battery_get_property(st= ruct power_supply *psy, return ret; } =20 -static void -steelseries_headset_set_wireless_status(struct hid_device *hdev, - bool connected) -{ - struct usb_interface *intf; - - if (!hid_is_usb(hdev)) - return; - - intf =3D to_usb_interface(hdev->dev.parent); - usb_set_wireless_status(intf, connected ? - USB_WIRELESS_STATUS_CONNECTED : - USB_WIRELESS_STATUS_DISCONNECTED); -} - -static enum power_supply_property steelseries_headset_battery_props[] =3D { +static enum power_supply_property steelseries_battery_props[] =3D { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_PRESENT, @@ -169,7 +244,26 @@ static enum power_supply_property steelseries_headset_= battery_props[] =3D { POWER_SUPPLY_PROP_CAPACITY_LEVEL, }; =20 -static int steelseries_headset_battery_register(struct steelseries_device = *sd) +/* + * Delayed work handlers for status polling + */ + +static void steelseries_status_timer_work_handler(struct work_struct *work) +{ + struct steelseries_device *sd =3D container_of( + work, struct steelseries_device, status_work.work); + unsigned long flags; + + sd->info->request_status(sd->hdev); + + spin_lock_irqsave(&sd->lock, flags); + if (!sd->removed) + schedule_delayed_work(&sd->status_work, + msecs_to_jiffies(STEELSERIES_HEADSET_STATUS_TIMEOUT_MS)); + spin_unlock_irqrestore(&sd->lock, flags); +} + +static int steelseries_battery_register(struct steelseries_device *sd) { static atomic_t battery_no =3D ATOMIC_INIT(0); struct power_supply_config battery_cfg =3D { .drv_data =3D sd, }; @@ -177,25 +271,27 @@ static int steelseries_headset_battery_register(struc= t steelseries_device *sd) int ret; =20 sd->battery_desc.type =3D POWER_SUPPLY_TYPE_BATTERY; - sd->battery_desc.properties =3D steelseries_headset_battery_props; - sd->battery_desc.num_properties =3D ARRAY_SIZE(steelseries_headset_batter= y_props); - sd->battery_desc.get_property =3D steelseries_headset_battery_get_propert= y; + sd->battery_desc.properties =3D steelseries_battery_props; + sd->battery_desc.num_properties =3D ARRAY_SIZE(steelseries_battery_props); + sd->battery_desc.get_property =3D steelseries_battery_get_property; sd->battery_desc.use_for_apm =3D 0; n =3D atomic_inc_return(&battery_no) - 1; sd->battery_desc.name =3D devm_kasprintf(&sd->hdev->dev, GFP_KERNEL, - "steelseries_headset_battery_%ld", n); + "steelseries_headset_battery_%ld", n); if (!sd->battery_desc.name) return -ENOMEM; =20 /* avoid the warning of 0% battery while waiting for the first info */ - steelseries_headset_set_wireless_status(sd->hdev, false); sd->battery_capacity =3D 100; sd->battery_charging =3D false; + sd->headset_connected =3D false; + steelseries_headset_set_wireless_status(sd->hdev, false); =20 sd->battery =3D devm_power_supply_register(&sd->hdev->dev, &sd->battery_desc, &battery_cfg); if (IS_ERR(sd->battery)) { ret =3D PTR_ERR(sd->battery); + sd->battery =3D NULL; hid_err(sd->hdev, "%s:power_supply_register failed with error %d\n", __func__, ret); @@ -203,52 +299,44 @@ static int steelseries_headset_battery_register(struc= t steelseries_device *sd) } power_supply_powers(sd->battery, &sd->hdev->dev); =20 - INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_ti= ck); - /* Pairs with smp_load_acquire() in raw_event and remove paths */ - smp_store_release(&sd->battery_registered, true); - steelseries_headset_fetch_battery(sd->hdev); - - if (sd->quirks & STEELSERIES_ARCTIS_9) { - /* The first fetch_battery request can remain unanswered in some cases */ - schedule_delayed_work(&sd->battery_work, - msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); - } - return 0; } =20 -static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint= 8_t usage_page) -{ - if (hdev->rsize < 3) - return false; - - return hdev->rdesc[0] =3D=3D 0x06 && - hdev->rdesc[1] =3D=3D usage_page && - hdev->rdesc[2] =3D=3D 0xff; -} - -static int steelseries_arctis_probe(struct hid_device *hdev, const struct = hid_device_id *id) +static int steelseries_arctis_probe(struct hid_device *hdev, + const struct hid_device_id *id) { + const struct steelseries_device_info *info =3D + (const struct steelseries_device_info *)id->driver_data; struct steelseries_device *sd; + struct usb_interface *intf; + u8 interface_num; int ret; =20 - sd =3D devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); - if (!sd) - return -ENOMEM; - hid_set_drvdata(hdev, sd); - sd->hdev =3D hdev; - sd->quirks =3D id->driver_data; + if (hid_is_usb(hdev)) { + intf =3D to_usb_interface(hdev->dev.parent); + interface_num =3D intf->cur_altsetting->desc.bInterfaceNumber; + } else { + return -ENODEV; + } =20 ret =3D hid_parse(hdev); if (ret) return ret; =20 - if (sd->quirks & STEELSERIES_ARCTIS_9 && - !steelseries_is_vendor_usage_page(hdev, 0xc0)) - return -ENODEV; + /* Let hid-generic handle non-sync interfaces */ + if (interface_num !=3D info->sync_interface) + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); =20 + sd =3D devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + + sd->hdev =3D hdev; + sd->info =3D info; spin_lock_init(&sd->lock); =20 + hid_set_drvdata(hdev, sd); + ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) return ret; @@ -257,9 +345,14 @@ static int steelseries_arctis_probe(struct hid_device = *hdev, const struct hid_de if (ret) goto err_stop; =20 - if (steelseries_headset_battery_register(sd) < 0) - hid_err(sd->hdev, - "Failed to register battery for headset\n"); + if (info->capabilities & SS_CAP_BATTERY) { + ret =3D steelseries_battery_register(sd); + if (ret < 0) + hid_warn(hdev, "Failed to register battery: %d\n", ret); + } + + INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler= ); + schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100)); =20 return 0; =20 @@ -272,142 +365,92 @@ static void steelseries_arctis_remove(struct hid_dev= ice *hdev) { struct steelseries_device *sd; unsigned long flags; + struct usb_interface *intf; + u8 interface_num; + + if (hid_is_usb(hdev)) { + intf =3D to_usb_interface(hdev->dev.parent); + interface_num =3D intf->cur_altsetting->desc.bInterfaceNumber; + } else { + return; + } =20 sd =3D hid_get_drvdata(hdev); - if (!sd) + + if (!sd) { + hid_hw_stop(hdev); return; + } =20 - spin_lock_irqsave(&sd->lock, flags); - sd->removed =3D true; - spin_unlock_irqrestore(&sd->lock, flags); + if (interface_num =3D=3D sd->info->sync_interface) { + spin_lock_irqsave(&sd->lock, flags); + sd->removed =3D true; + spin_unlock_irqrestore(&sd->lock, flags); =20 - /* Pairs with smp_store_release() in steelseries_headset_battery_register= () */ - if (smp_load_acquire(&sd->battery_registered)) - cancel_delayed_work_sync(&sd->battery_work); + cancel_delayed_work_sync(&sd->status_work); + } =20 hid_hw_close(hdev); hid_hw_stop(hdev); } =20 -static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t = min_in, uint8_t max_in) -{ - if (capacity >=3D max_in) - return 100; - if (capacity <=3D min_in) - return 0; - return (capacity - min_in) * 100 / (max_in - min_in); -} - static int steelseries_arctis_raw_event(struct hid_device *hdev, - struct hid_report *report, u8 *read_buf, - int size) + struct hid_report *report, u8 *data, int size) { struct steelseries_device *sd =3D hid_get_drvdata(hdev); - int capacity; - bool connected; - bool charging; - unsigned long flags; + u8 old_capacity; + bool old_connected; + bool old_charging; =20 - /* Pairs with smp_store_release() in steelseries_headset_battery_register= () */ - if (!sd || !smp_load_acquire(&sd->battery_registered)) + if (!sd) return 0; =20 - capacity =3D sd->battery_capacity; - connected =3D sd->headset_connected; - charging =3D sd->battery_charging; - - if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X) { - hid_dbg(sd->hdev, - "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf); - if (size < ARCTIS_1_BATTERY_RESPONSE_LEN || - memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_requ= est))) { - if (!delayed_work_pending(&sd->battery_work)) - goto request_battery; - return 0; - } - if (read_buf[2] =3D=3D 0x01) { - connected =3D false; - capacity =3D 100; - } else { - connected =3D true; - capacity =3D read_buf[3]; - } - } - - if (hdev->product =3D=3D USB_DEVICE_ID_STEELSERIES_ARCTIS_9) { - hid_dbg(sd->hdev, - "Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf); - if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) { - if (!delayed_work_pending(&sd->battery_work)) - goto request_battery; - return 0; - } + old_capacity =3D sd->battery_capacity; + old_connected =3D sd->headset_connected; + old_charging =3D sd->battery_charging; =20 - if (read_buf[0] =3D=3D 0xaa && read_buf[1] =3D=3D 0x01) { - connected =3D true; - charging =3D read_buf[4] =3D=3D 0x01; - - /* - * Found no official documentation about min and max. - * Values defined by testing. - */ - capacity =3D steelseries_headset_map_capacity(read_buf[3], 0x68, 0x9d); - } else { - /* - * Device is off and sends the last known status read_buf[1] =3D=3D 0x0= 3 or - * there is no known status of the device read_buf[0] =3D=3D 0x55 - */ - connected =3D false; - charging =3D false; - } - } + sd->info->parse_status(sd, data, size); =20 - if (connected !=3D sd->headset_connected) { - hid_dbg(sd->hdev, + if (sd->headset_connected !=3D old_connected) { + hid_dbg(hdev, "Connected status changed from %sconnected to %sconnected\n", - sd->headset_connected ? "" : "not ", - connected ? "" : "not "); - sd->headset_connected =3D connected; - steelseries_headset_set_wireless_status(hdev, connected); + old_connected ? "" : "not ", + sd->headset_connected ? "" : "not "); + + if (sd->battery) { + steelseries_headset_set_wireless_status(sd->hdev, + sd->headset_connected); + power_supply_changed(sd->battery); + } } =20 - if (capacity !=3D sd->battery_capacity) { - hid_dbg(sd->hdev, - "Battery capacity changed from %d%% to %d%%\n", - sd->battery_capacity, capacity); - sd->battery_capacity =3D capacity; - power_supply_changed(sd->battery); + if (sd->battery_capacity !=3D old_capacity) { + hid_dbg(hdev, "Battery capacity changed from %d%% to %d%%\n", + old_capacity, sd->battery_capacity); + if (sd->battery) + power_supply_changed(sd->battery); } =20 - if (charging !=3D sd->battery_charging) { - hid_dbg(sd->hdev, + if (sd->battery_charging !=3D old_charging) { + hid_dbg(hdev, "Battery charging status changed from %scharging to %scharging\n", - sd->battery_charging ? "" : "not ", - charging ? "" : "not "); - sd->battery_charging =3D charging; - power_supply_changed(sd->battery); + old_charging ? "" : "not ", + sd->battery_charging ? "" : "not "); + if (sd->battery) + power_supply_changed(sd->battery); } =20 -request_battery: - spin_lock_irqsave(&sd->lock, flags); - if (!sd->removed) - schedule_delayed_work(&sd->battery_work, - msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); - spin_unlock_irqrestore(&sd->lock, flags); - return 0; } =20 static const struct hid_device_id steelseries_arctis_devices[] =3D { - { /* SteelSeries Arctis 1 Wireless for XBox */ - HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCT= IS_1_X), - .driver_data =3D STEELSERIES_ARCTIS_1_X }, - - { /* SteelSeries Arctis 9 Wireless for XBox */ - HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCT= IS_9), - .driver_data =3D STEELSERIES_ARCTIS_9 }, - - { } + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X), + .driver_data =3D (unsigned long)&arctis_1_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_9), + .driver_data =3D (unsigned long)&arctis_9_info }, + {} }; MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices); =20 @@ -424,3 +467,4 @@ MODULE_DESCRIPTION("HID driver for Steelseries arctis h= eadsets"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Christian Mayer "); MODULE_AUTHOR("Bastien Nocera "); +MODULE_AUTHOR("Sriman Achanta "); --=20 2.55.0 From nobody Fri Oct 2 07:45:34 2026 Received: from mail-qt1-f180.google.com (mail-qt1-f180.google.com [209.85.160.180]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 00A57433BCA for ; Mon, 3 Aug 2026 19:16:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784563; cv=none; b=LMkMlRW0yf8swIFzcKsUCcNYDUn5VEooyut5wTHlxmm/LmmEaAZfVm972hMcNOoI33WXHg2TpnTPyhg+aIPP82G2d0HvDfvx0SxrlZQHItWp2q4JJYc50+KXrOX9TVMqVBI3tgnocBxetsC3YAuKpBaUDlmT4vAl5Jmu1ekq36A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784563; c=relaxed/simple; bh=mrO+BsSsNgwnuSX3UaLBRdUdLDlXlA6VHErkX/eArA4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ebXWbTC8GJrWdc3WXTaidRPnYbrQs8iIRyG5CHLoKCD9Pi+HgtayA0BL5GXiETD0p4645MFI+ic2CXlGPFC3VeFrxSBxra6zNIfzzxL/msmTgtGY79z+NJV4jYA+eQAruGZwlf7D9lob5AeqyxmdCl5eoXdzgJg1sNXn0eR/EsM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=seFHsTzO; arc=none smtp.client-ip=209.85.160.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="seFHsTzO" Received: by mail-qt1-f180.google.com with SMTP id d75a77b69052e-51c05dcdf49so40029321cf.0 for ; Mon, 03 Aug 2026 12:16:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785784560; x=1786389360; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=W1fduvTpD42ESt1oRP9IHFMVMP1Xuv9K5VEBmDDxT20=; b=seFHsTzO4KIqP42OzJLSHMVVmYETeqNIrJNdWlRPNl1UYrJj+6X4LDMpECq8rAuIuX KcUR5vo2+AZ9eyvDwc3L8idZSoWIYFmnDnqZ/Tk19wGJjVXFq3NixM+eo4dtiIF7355f jJh4NxL9RVd6CC62+G+tmbozR/Bqtj0HeXb3LI7S+j5LEf/7j71smqoTs9tKXz4NTCQk 4RZbBgLXERGnO1mE3zJKTG08mkRhg7SteEx6yUEoui/75xgdZSoekq/GSVxUBuQpcbgJ PDXwBNIF+pRXZaq4pjyWhC4jZPPc7saxNsxo7HYauBhRQ487BBiZf0gbKMfs3brPtFBM wk/g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785784560; x=1786389360; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=W1fduvTpD42ESt1oRP9IHFMVMP1Xuv9K5VEBmDDxT20=; b=jM6qCvVVTMmpnkyvfi48RXpzGe8beodFoC/iHeFpkSLkngcgMg0RWoG0i3AAB+ZfiO NZgT0gf9Wby39EzusB2eh7RDp+7Hg0C7bTq9/7QW1u1HgN8fVLiOlhWPUcDG7x5gVJhE HLWiIFZECRqFle/unDeWea0HlsCI6yA49gTTyXF5IPikzxGY6AAMlDTvD0gZFRROEROi QMrKA1SI3I1hKZJlin60gO/lTREUQ/5BrHewULmbTkASvJk+zrdrK0OHjwy+57/L82y8 RhaDkkvjVw/uDHAghErOab5zFPjlcwcx/lr4TBrX1/Ms3nStP3uU2Rexu0NphbIynb1s qEzQ== X-Forwarded-Encrypted: i=1; AHgh+RqOujCHzspC1qOa7UQ5nwaqYR3Kqx5xGLYBN7TPDdnssJ5zX+12Ue/QxSCkRF5tBDPlrGNWfyj4U/q/fuk=@vger.kernel.org X-Gm-Message-State: AOJu0YzqR9ACo2YJczv5JCVYCv8nIGsc+hPb2tH+KvPzlAfwXx3Z/yGe PvvRYQhOWnKxXDOSxPgME9iKRG1B4hBAPJHJyCuyBljECNBd01B0GCse X-Gm-Gg: AR+sD13dA+n51yMyHWrY7aKeAWNXoUga0tXs5JJ9DKwQ9c2wIgKnuo3qD77d0I1EgU9 VZ2+0Zcxwa3Ve+RVkTXV8MEBEvLl+slza3RRTgZwRCF2OPIAlASvbtatEKrGsaauL7iP46hfUVP hBrsys6PBMAQAz2zEXpppU3Gx+j+JOeU8k1iPqBrGz2qe97jQsCUfyw7HmSPgO0BCysUem9snlt LgfYcg2+OlCkkIAtCV08gDJe2DAApWvjRMo4HA8KVCUVVLtbJHI/95mCUg26gXgSKQ1XnFgTJbP 9jhIB58ff5uUO1rNM+Y/x0GMUwGJUPhe6Q2GpTlCVHKEQUtKdQVE/Zu+L8x/niKhCZvVNTyZABG o2nYXgAANHngrb29elv4xtWox7iHgsFa+9P5uV7B5YoIMQJCTbMkx2MAgReOxDk9uLdJgJIPVwv 2oinBfs19eI8OiUo5M/TFv/YuSjP8/QoocVhBIEbcr4YIhvJDXi2bUPybtGLxGonpx8ZfEvCz2Z iP/v0Q9u66o/a+6N7g= X-Received: by 2002:a05:622a:11d3:b0:517:8d24:64d8 with SMTP id d75a77b69052e-52b566aa82emr208085391cf.13.1785784559571; Mon, 03 Aug 2026 12:15:59 -0700 (PDT) Received: from achantapc.tail227c81.ts.net ([2600:4040:122e:7d00:f04d:e0df:fe04:e789]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-52b4e64df9bsm67803541cf.0.2026.08.03.12.15.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Aug 2026 12:15:59 -0700 (PDT) From: Sriman Achanta To: Jiri Kosina , Benjamin Tissoires Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Bastien Nocera , Simon Wood , Christian Mayer , Sriman Achanta Subject: [PATCH v7 4/8] HID: steelseries: Report POWER_SUPPLY_STATUS_FULL when full Date: Mon, 3 Aug 2026 15:15:49 -0400 Message-ID: <20260803191553.66368-5-srimanachanta@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803191553.66368-1-srimanachanta@gmail.com> References: <20260803191553.66368-1-srimanachanta@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Report POWER_SUPPLY_STATUS_FULL when the headset is connected, charging, and at 100% capacity. It reported CHARGING in that case before. The charging flag doubles as a power-present signal: the headset keeps reporting charging while it sits docked at 100%, and clears the flag as soon as it runs on battery. Gating FULL on the charging flag therefore avoids reporting FULL for an unplugged headset that merely happens to be at 100%, which would otherwise hide the discharge state from userspace. Signed-off-by: Sriman Achanta --- drivers/hid/hid-steelseries-arctis.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelse= ries-arctis.c index c54c56db9ddd..7b8f5f2623b0 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -214,7 +214,9 @@ static int steelseries_battery_get_property(struct powe= r_supply *psy, if (!sd->headset_connected) val->intval =3D POWER_SUPPLY_STATUS_UNKNOWN; else if (sd->battery_charging) - val->intval =3D POWER_SUPPLY_STATUS_CHARGING; + val->intval =3D sd->battery_capacity >=3D 100 ? + POWER_SUPPLY_STATUS_FULL : + POWER_SUPPLY_STATUS_CHARGING; else val->intval =3D POWER_SUPPLY_STATUS_DISCHARGING; break; --=20 2.55.0 From nobody Fri Oct 2 07:45:34 2026 Received: from mail-qt1-f179.google.com (mail-qt1-f179.google.com [209.85.160.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D7BDB43551F for ; Mon, 3 Aug 2026 19:16:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784564; cv=none; b=OymdVLTMs/a0YnFRqGzgGsNa/6WRO41s88wXneBKYGx0brMHeJXr8l+db2KUiyRVBNz5jtGdQAxVpVu+PyYlOdkibfHI2/qfvFhIbJlAJmoZL/R0WdithFGBoq3wzCi8+uOg8s1kRkwL8sayciMCTw2LXUmIb679VNiiZC7oTxk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784564; c=relaxed/simple; bh=7m2bmzqsMIVBLFuL/xrXIgkl9ruapdGRrZFiSZ6VQ6k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=MiY9WYv68EBx8d38Ve/nV9ig8JiE4DhyQazk+KMGkEecHz1TP+Frv+hY0ltkh7fbTWhid1uOlWDzSky78VD6Yeg2mwZ9f4O4s/TNhow8AZ2T3hPkXJ6nMSigr6v/hS4dywWfhO+50jTkYUg5s4WTISv16IdecUTuRuvxpfWEa1c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=g0gy3bm3; arc=none smtp.client-ip=209.85.160.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="g0gy3bm3" Received: by mail-qt1-f179.google.com with SMTP id d75a77b69052e-5218927884fso32642911cf.3 for ; Mon, 03 Aug 2026 12:16:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785784560; x=1786389360; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=4rjPj39P47xaMasHmkHYYs80LcqMLG66q4JfEodsvmM=; b=g0gy3bm3gMk2V7lodbrdRULtTf82+hvuMmG4QXjKTdHfKLsdlqv8VAHRmzCvWeocjY CvAmHyncSQ473tF4rv3dHN8RuBoJmUXQvNjqoxwstLMqn/iEPORAYxd9NG8Voa7daJg+ mDZtV1vE/7LBS8dqOzrAuFzzSZDki7B8b6/OD11cJ/7iwAG2YUrakZBFedYNfiz4D2WA W4umC5NLPXwnl558UUU108njM+kfA5RPBYqJHcjgGGaM8fcKHPYfQ5aI4HCjHR+iWGTs xKSifQfAEWThnn8lHFi54uawmSZm7BJGXefg0W9SdbFHjipvp/dJE2oxUyEPsfIE0XSb wD+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785784560; x=1786389360; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=4rjPj39P47xaMasHmkHYYs80LcqMLG66q4JfEodsvmM=; b=rqlI4YTRdIOLv+4Rr/Ae8syLHDGxLHrQQIqBGn5YEBWVQjWqrdhROuZJqhp7J+Cyhm xVCAw01lLQ/BiHOcyMxygA+CwP4+VlPQImnwg4oOspYORp6YN5v8Ft5kx77lCtYTaTBL Cpewuj/LAVLUjU7gkyG2K43jKMAdIcqs5dNS/MXPygM+S25ryxS8/j/wD99Rb3Eib7IB bp1JwAuTPdviOIuBSC1stKqvjZQesk+VGWu5k7VPU8d29qL9nycyS9eD4yHrZRDD4R2V Ezp4zANGvvSPl/qd81qkpqybr/sN+qFC6xahMdokOvQxkf2dQWEQYHkhh3t5v+6mNEba fJCg== X-Forwarded-Encrypted: i=1; AHgh+RqmI30Bun1Y4OAQRblmXNrb6FUefCL5ix97x0WWvLI2PWURmc550Du5/yAcaHTCJokxd/GkQ64i7LHeIpc=@vger.kernel.org X-Gm-Message-State: AOJu0Ywm8R+z0fQZFb9ehRbK6zoKJ9s3sF2KwVYrYdRhVrOK5JH7ZFIz 3sfU49oyOJ8GPoVcVMZfT3xoykmPLnuky5dwknpz5OuxKZPH7ZJDcm5z X-Gm-Gg: AR+sD12CZjCN4BZkvw0YSmRdHC+Id6KOMPJeWdfTHE7jer8I4Y9Iyw130tvC/2rtMdx lVnhaYGsX4aSenA8J1KD8mKV/3c8ByHnbw5FZ2LUyiAbWk2Z49bFzRRiIs8LjVaxJbAoLJrcfB1 JjikgZf1uLMWBk5oApYQXZnuWmvShodCNY+Nitupe9yPSE2MheSs5I+jI08wMq/azsWNPMY4VX5 hFVpbtq/Dx4kuOhaXB1H7mweCWWBeEwt6ealPAPOYVLhat6QwuEWmphJ61t2OqZaLERYg3cEoyM ulGcWZ0jWoiZkDpqhWvKZvX21UDtIbw0w9fo7XdjjRsUeGP5pZHcBumuTDfkYa+dRjjMPr+efnS AITtycGPY1F332JVUp4YDzo/IIhd7RvdX0oDdjPu3lVD8PZYTYxEMTOjZTAWCdbQh3srwfnEbxt v1I1lFHdaL1olIyO0lzRXUIBYooQyrDbrwaAZa+tGxzlC1CdJzo4m6QDeTMXdLnT/3nMxmwnn+P sNr9+ExNBZq787x2gnRLYb7kftqTA== X-Received: by 2002:ac8:574a:0:b0:51b:e92f:ec5 with SMTP id d75a77b69052e-52b5666d563mr221782141cf.3.1785784560318; Mon, 03 Aug 2026 12:16:00 -0700 (PDT) Received: from achantapc.tail227c81.ts.net ([2600:4040:122e:7d00:f04d:e0df:fe04:e789]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-52b4e64df9bsm67803541cf.0.2026.08.03.12.15.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Aug 2026 12:15:59 -0700 (PDT) From: Sriman Achanta To: Jiri Kosina , Benjamin Tissoires Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Bastien Nocera , Simon Wood , Christian Mayer , Sriman Achanta Subject: [PATCH v7 5/8] HID: steelseries: Correct Arctis 9 battery calibration range Date: Mon, 3 Aug 2026 15:15:50 -0400 Message-ID: <20260803191553.66368-6-srimanachanta@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803191553.66368-1-srimanachanta@gmail.com> References: <20260803191553.66368-1-srimanachanta@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Map the Arctis 9 raw battery value over 0x64 (empty) to 0x9a (full) instead of 0x68 to 0x9d. These values match the HeadsetControl project [1] and fit the calibration points from an independent reverse engineering of the battery tray (about 25% at raw 112, 50% at raw 125) [2]. I do not have this headset. The values come from those references and were not measured directly. [1] https://github.com/Sapd/HeadsetControl/blob/master/lib/devices/steelser= ies_arctis_9.hpp [2] https://magnier.io/reverse-engineering-arctis-9-battery-tray/ Signed-off-by: Sriman Achanta --- drivers/hid/hid-steelseries-arctis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelse= ries-arctis.c index 7b8f5f2623b0..b54f20f081a2 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -141,7 +141,7 @@ static void steelseries_arctis_9_parse_status(struct st= eelseries_device *sd, if (data[0] =3D=3D 0xaa && data[1] =3D=3D 0x01) { sd->headset_connected =3D true; sd->battery_charging =3D (data[4] =3D=3D 0x01); - sd->battery_capacity =3D steelseries_map_capacity(data[3], 0x68, 0x9d); + sd->battery_capacity =3D steelseries_map_capacity(data[3], 0x64, 0x9a); } else { /* Device off: 0x55 (no status) or 0x03 (stale status). */ sd->headset_connected =3D false; --=20 2.55.0 From nobody Fri Oct 2 07:45:34 2026 Received: from mail-qt1-f174.google.com (mail-qt1-f174.google.com [209.85.160.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9355E435EEA for ; Mon, 3 Aug 2026 19:16:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784565; cv=none; b=kcbnPJzFnRE2u39yT1mdqFYMjlejuMeSv08e856yM1l0AndY/cNkdItM/gs0t10xYoiBK3QpB8Q5RXQqkPyl7tOeE9r+jvuNJazNZL4C9iwlvcgcpkJGAxefblzKiNUjdHrPbv6FZjbWsPqkUwAMQiU0G5b9tC3uy4o0K/rzoTo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784565; c=relaxed/simple; bh=HcLY56V4G5qgz15CvOjbSYpkabPXRbcPz2MAIR5hkY0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=eI2yEzJhZWMHmb9ePWNGeR3zB8UxUhqTd2tisDloPo9H3Qg6JEa8StGl6xzjXEJRTWl7dRp+VhtHZO7RuAEHoNugusRgaVySxlBjTikrvCjnlAU3Kk6G5SLMmMlUL/vsxXSEal0Dp2E212oolJSszCw3u7di0aeQUGsxmk1SKZY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=RkKuMmT1; arc=none smtp.client-ip=209.85.160.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="RkKuMmT1" Received: by mail-qt1-f174.google.com with SMTP id d75a77b69052e-51c2a818fc4so22618181cf.2 for ; Mon, 03 Aug 2026 12:16:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785784561; x=1786389361; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=M3ueuoAKdkN9M8HNhpGDUHsXDw7G2MpREUxH5XZmVNo=; b=RkKuMmT1gl4UXrHdFDfQVtgsT57hhVx6tnLOSRXSGeBSOSUMdalfJTDhhnd3u3WbRE dZ9PlnfjqMTCCjCgRi1PSwcEssRO9DAjBNKzavKaaY+7QYqYCMOKnMQO740hpDwmQcLD CGN5FHPvaNPfkfN5qWFJgdL07gPKHBJr13w9R+v4Wckmi1+9x/UkmS2L5QoSksfXX71t acSkMnaYOnvCjlgRKeBhvIinzNFzDrFMd8dSLY+FqMxQM/aPrt01wD1nCb2H66AzAAws PvtiMA7pVUMMm9XZwDL74J+ObuMBXnY/Ux6QDKYVXIedWAbYv/resJimaEM/Fgjeo9T9 7ogw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785784561; x=1786389361; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=M3ueuoAKdkN9M8HNhpGDUHsXDw7G2MpREUxH5XZmVNo=; b=YqRGAItX4/+qIctWFaWoIqhXOwSSBuH2QR4hDjoG+V6aqOpv7kF9M6lEW1Lgp9l/wR eF1BsR9rgDpcAOiEvoWVtmZ4sBQAa/8XJezm86opIb3QHajKGjpitWAZGUhyEXZSAfo9 Nd26um8liuW86HCt1fBj7SlD0Y0h4tWBXqunHiJ5/kD05iBW2IvCSOiGTynWigWqRQDj RFAiwyBo0qSLOvTf1LTzkE5anpKLfcGaQxKntqU/19r9rq5uPU0WNUi1DY2NDIi6FUc2 /X+fTjlzgIM9HAXgWAV3+WasjfXmmOCW1pZsCGFE9oSCybTtZnn5f0ONEfsUZRX1tEl2 aKcQ== X-Forwarded-Encrypted: i=1; AHgh+RrW4mYTFxmvJ0DHNlwpRkPztqCMieKvF9Du4+M4hEIjKryYIH7pkhKgTHB4l6Zg3Zefsub9nj+1LuIy0RY=@vger.kernel.org X-Gm-Message-State: AOJu0Yxx8oQM2IYJUA9ZbRaNrXDpHh+KpyQqam4jbZILkH3Omdx0v+l3 qxxC5NVkbHE5O5y4KQQVjLkkyhQA8BO9XyyCX4sOCx3pXtV9qSezeOJJ+EBY4Q== X-Gm-Gg: AR+sD11N0iJV33QPZNLpoWr01KaTfvWK9Z3OspquZMZvr/krY01Nrm+3v1wTk3ljZmV e6hAYrrEjoBveGVe4yVyhkuN4gOMzfesINJvCvNcttdeoU+LIlll9HbHzaMcsuCqJK4oWwfLEb0 0bFUXQzl2jloKHa6heVqTtxiA9BYhKk8Y8m3rC8SQzdw5uknzIk6svSFbVGJBkQJkqS82TOMLot cygDLwU/L0itCX2Uo55Dq5+xLNd0OJI+MOVZpZlDjzuYkZ5fzhAvecLhpNd4GzbZml8bPlqMNst PnAsC71aELOd+hDSD9kQkOX6ghJVCZCqFJIdNlzUc+ivg1tm/7Cw6U+2GdNVQ94GJGKY6/sgaLV ql/MjrMCoP3WHW3grd9tSr3gfuryY/z/5aYuN27Q8HzYHJUWZBziRY68NzoaQzizPeZpv6P4CjZ UCE2rhyC5nBOOHTRjlYT5Wh0cOKApGUD0MRF2i7ScwdMOj0AUmxlYIMy6PGTqjjFjGJxCiFJvDX 9K/9OFaLrh9EZoGJ6M= X-Received: by 2002:a05:622a:2b44:b0:528:17d9:2d99 with SMTP id d75a77b69052e-52b567f9725mr202750661cf.43.1785784561006; Mon, 03 Aug 2026 12:16:01 -0700 (PDT) Received: from achantapc.tail227c81.ts.net ([2600:4040:122e:7d00:f04d:e0df:fe04:e789]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-52b4e64df9bsm67803541cf.0.2026.08.03.12.16.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Aug 2026 12:16:00 -0700 (PDT) From: Sriman Achanta To: Jiri Kosina , Benjamin Tissoires Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Bastien Nocera , Simon Wood , Christian Mayer , Sriman Achanta Subject: [PATCH v7 6/8] HID: steelseries: Manage battery lifetime with refcounting Date: Mon, 3 Aug 2026 15:15:51 -0400 Message-ID: <20260803191553.66368-7-srimanachanta@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803191553.66368-1-srimanachanta@gmail.com> References: <20260803191553.66368-1-srimanachanta@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The next change shares one steelseries_device between two HID interfaces, so the state can outlive either interface. Stop using devm for it. Reference count the struct with a kref and free it from steelseries_device_release(). Register and unregister the power supply explicitly, and clear sd->battery under sd->lock in remove() so it is not touched after it is unregistered. Drop the global atomic battery counter and name the power supply after the device (hdev->uniq, or dev_name() when empty), as hid-input and the other HID battery drivers do. No functional change for the current single-interface devices. Signed-off-by: Sriman Achanta --- drivers/hid/hid-steelseries-arctis.c | 47 +++++++++++++++++++++------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelse= ries-arctis.c index b54f20f081a2..e534aa44e70a 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -8,6 +8,8 @@ =20 #include #include +#include +#include #include #include #include @@ -30,6 +32,8 @@ struct steelseries_device_info { }; =20 struct steelseries_device { + struct kref refcnt; + struct hid_device *hdev; const struct steelseries_device_info *info; =20 @@ -45,6 +49,14 @@ struct steelseries_device { bool removed; }; =20 +static void steelseries_device_release(struct kref *ref) +{ + struct steelseries_device *sd =3D + container_of(ref, struct steelseries_device, refcnt); + + kfree(sd); +} + /* * Headset report helpers */ @@ -267,9 +279,8 @@ static void steelseries_status_timer_work_handler(struc= t work_struct *work) =20 static int steelseries_battery_register(struct steelseries_device *sd) { - static atomic_t battery_no =3D ATOMIC_INIT(0); struct power_supply_config battery_cfg =3D { .drv_data =3D sd, }; - unsigned long n; + struct power_supply *battery; int ret; =20 sd->battery_desc.type =3D POWER_SUPPLY_TYPE_BATTERY; @@ -277,9 +288,10 @@ static int steelseries_battery_register(struct steelse= ries_device *sd) sd->battery_desc.num_properties =3D ARRAY_SIZE(steelseries_battery_props); sd->battery_desc.get_property =3D steelseries_battery_get_property; sd->battery_desc.use_for_apm =3D 0; - n =3D atomic_inc_return(&battery_no) - 1; sd->battery_desc.name =3D devm_kasprintf(&sd->hdev->dev, GFP_KERNEL, - "steelseries_headset_battery_%ld", n); + "steelseries_headset_battery_%s", + sd->hdev->uniq[0] ? sd->hdev->uniq : + dev_name(&sd->hdev->dev)); if (!sd->battery_desc.name) return -ENOMEM; =20 @@ -289,17 +301,19 @@ static int steelseries_battery_register(struct steels= eries_device *sd) sd->headset_connected =3D false; steelseries_headset_set_wireless_status(sd->hdev, false); =20 - sd->battery =3D devm_power_supply_register(&sd->hdev->dev, + battery =3D power_supply_register(&sd->hdev->dev, &sd->battery_desc, &battery_cfg); - if (IS_ERR(sd->battery)) { - ret =3D PTR_ERR(sd->battery); - sd->battery =3D NULL; + if (IS_ERR(battery)) { + ret =3D PTR_ERR(battery); hid_err(sd->hdev, "%s:power_supply_register failed with error %d\n", __func__, ret); return ret; } - power_supply_powers(sd->battery, &sd->hdev->dev); + power_supply_powers(battery, &sd->hdev->dev); + + /* Assign on success only, so a concurrent raw_event never sees an ERR_PT= R. */ + sd->battery =3D battery; =20 return 0; } @@ -329,10 +343,11 @@ static int steelseries_arctis_probe(struct hid_device= *hdev, if (interface_num !=3D info->sync_interface) return hid_hw_start(hdev, HID_CONNECT_DEFAULT); =20 - sd =3D devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); + sd =3D kzalloc_obj(*sd, GFP_KERNEL); if (!sd) return -ENOMEM; =20 + kref_init(&sd->refcnt); sd->hdev =3D hdev; sd->info =3D info; spin_lock_init(&sd->lock); @@ -341,7 +356,7 @@ static int steelseries_arctis_probe(struct hid_device *= hdev, =20 ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) - return ret; + goto err_put; =20 ret =3D hid_hw_open(hdev); if (ret) @@ -360,12 +375,15 @@ static int steelseries_arctis_probe(struct hid_device= *hdev, =20 err_stop: hid_hw_stop(hdev); +err_put: + kref_put(&sd->refcnt, steelseries_device_release); return ret; } =20 static void steelseries_arctis_remove(struct hid_device *hdev) { struct steelseries_device *sd; + struct power_supply *battery; unsigned long flags; struct usb_interface *intf; u8 interface_num; @@ -387,13 +405,20 @@ static void steelseries_arctis_remove(struct hid_devi= ce *hdev) if (interface_num =3D=3D sd->info->sync_interface) { spin_lock_irqsave(&sd->lock, flags); sd->removed =3D true; + battery =3D sd->battery; + sd->battery =3D NULL; spin_unlock_irqrestore(&sd->lock, flags); =20 cancel_delayed_work_sync(&sd->status_work); + + if (battery) + power_supply_unregister(battery); } =20 hid_hw_close(hdev); hid_hw_stop(hdev); + + kref_put(&sd->refcnt, steelseries_device_release); } =20 static int steelseries_arctis_raw_event(struct hid_device *hdev, --=20 2.55.0 From nobody Fri Oct 2 07:45:34 2026 Received: from mail-qt1-f178.google.com (mail-qt1-f178.google.com [209.85.160.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6DA4E437453 for ; Mon, 3 Aug 2026 19:16:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784566; cv=none; b=fU3W94mQxTAOVdpDzNQymi/97ir6spDFqdymg5XSSmHcIGfdrOQNnhoUk5yzCnA+tAs98y+VrnAyxEMfe0GWEuvWw/MY+7fmovJESCPonWll/cyCRD8LUwpz5WuufvYh/qC/F5PCE1pq0a1wNpHI3lKVkF2a2+YleETiM1T2DG4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784566; c=relaxed/simple; bh=rgiOAxrws7HtpWLSKi4FZUyJEZqLol8aZnFJ5cEkP1Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=t1pGEcDsYZhUjv/a7JfhDuBoV9J6ge8fr/NVrbbAOymtmkXjdsGHbDajfKBI+jrMIG4lY71lQ5CR8cjaigzB5c22+ciGxLTGnWcQRXqM+Pbw5mfLZfRFATa08Of8GRXmxLEStuB/ZUqcjmumqXaP/kZ2KlVxUBE4Pmh0RGTu+fY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=NImpVHgM; arc=none smtp.client-ip=209.85.160.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="NImpVHgM" Received: by mail-qt1-f178.google.com with SMTP id d75a77b69052e-51c2a818fc4so22618311cf.2 for ; Mon, 03 Aug 2026 12:16:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785784562; x=1786389362; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=geHMteasP6UB71yeWgSjp/T6tZ4uWNyLwhqzRYexiv0=; b=NImpVHgMJDPfCn7cTyLmi4Hy6lrosZkm+ripuPY+l6nwVjEfZT+60iLEMYX7asvVh0 vr2SCRmLtJo7mkr21WwRX+YW2POwz4q9n8LvncvRfU2pdyxjwCJk5Rh96P7Okdns3Ay4 fnh9LzkHwRACPT31emJMq3EclblNdq6pZ5PhE72TC9z1VGzwZHim/8a/Wk4NrvxoTtYm l3G7a4s4MNkX7cTWhG6TO8qKcp6d0yZS6Jo+qOunRi/fJwcV/1x1R36VTT7MCEGYF8NK 5Og4hSnYfLlKcjjRau2fjHoGjxl/EojZB4awrA8Gx2kmO2223i4EVO/xP+qZvqqY44/r KaxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785784562; x=1786389362; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=geHMteasP6UB71yeWgSjp/T6tZ4uWNyLwhqzRYexiv0=; b=Ekd3ghUd7R8u+t/ROcws5WcDL+5fp+NtJVP+D/WGB6aiYHxXo11V6+cfmUUozpChAU qYQp8wet8nGa4BX0ILggw8EqBHB2ewX4tqSky1XFbs+FhOCzLQtwBGx3W82GU9cFgZKO rhx2TpLw55ylS8Rr2fObbXG7ylpm2pnLahPKgAbJhkxlIF2sJPzIRZFgYBEJb8Sw68gv Yi4+73sWK5sSLNxMkYImA2nDp7sC+ZlgScTFmTcU4E3PGO3nfS3qk5UBiZJPrMj3GMOR hv2PrFS76r6QcF+95NeIyZgxRWBd+ppcfkYbXlMO45S5dy4jRqAb0Z/lMfmFBLq8ZbDJ n/lg== X-Forwarded-Encrypted: i=1; AHgh+Rrx+h5ViirnWqKGBOdlt5zrPIc+hte4JXaNtkZXYZhtGccF2zam8xLeBk0DSR/ucRIIXZCqR9TCX8fgvuo=@vger.kernel.org X-Gm-Message-State: AOJu0YznXNf9TYc09aY5OOqZkpLSwl7NCqp21nCrUO+5WXe2WetbmLLI vzwfLIIoNJ3uiBJiU2AeorCIEOynQ4Pgb0BhKs4EKpQlpnZnNTLa3bb6Iefv1w== X-Gm-Gg: AR+sD10/gmf089OIENuQN1kJd9L74rsgbf0zr9dJUh3t2yyIHffB8PgMHUyy3gN08Bp fqtGgOINFthPKGAn7LLa3DqrD1TZ5+pm7ddlnLVODqzx4oJ5aDQFyXmhSGEr+2PoVKHGDwqg80i 6dAHEOIN4tGmJvuLOezE/QodCPsmPc1kuHoGnKonCKAtibntI+kEktdLoT84x7BYUSTpTx0cgaj vWjBooroMl+59+jwGi6dQ+bqKpoGFfX9RquQuhRk70aBIwSdnsRvp/B3iA9teHgQsrQox4jxBIm wSAlwJB+GjvIFMZKc7EG8hLIR/hTyRF96h5QBf3g4kSft9SsP3T2iucxEbV7IM609A8nJJTk9Xn /2k0vfq/OintrM2nZwrDJEe4tUx9p9fz6/mYSApXIRlG9XP5GcdOvWG9GgG4wwXfr1lMwwuyKqy 05ZHo7nPCK7T58DIOHo4svX6cl3pxTkooTVz9Ge9T41vAEnRnxzJICz0jcHV2GiBsffiOuYaukP 4/BUV7jwJ0hX/hD+3k= X-Received: by 2002:a05:622a:1e0e:b0:51c:1bb9:137f with SMTP id d75a77b69052e-52b56776470mr219645181cf.24.1785784561685; Mon, 03 Aug 2026 12:16:01 -0700 (PDT) Received: from achantapc.tail227c81.ts.net ([2600:4040:122e:7d00:f04d:e0df:fe04:e789]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-52b4e64df9bsm67803541cf.0.2026.08.03.12.16.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Aug 2026 12:16:01 -0700 (PDT) From: Sriman Achanta To: Jiri Kosina , Benjamin Tissoires Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Bastien Nocera , Simon Wood , Christian Mayer , Sriman Achanta Subject: [PATCH v7 7/8] HID: steelseries: Add async status interface support Date: Mon, 3 Aug 2026 15:15:52 -0400 Message-ID: <20260803191553.66368-8-srimanachanta@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803191553.66368-1-srimanachanta@gmail.com> References: <20260803191553.66368-1-srimanachanta@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Some headsets expose a second HID interface that sends battery and connection updates on its own. Watching that interface lets the driver stop polling the sync interface. Add a steelseries_device_info::async_interface field and the code to handle it: - The driver binds both the sync and async interfaces. The async interface shares the steelseries_device created by the sync interface. It finds the sibling with usb_ifnum_to_if(), and before trusting its intfdata it rejects non-HID siblings by descriptor class and holds the sibling's device lock across the lookup, so a crafted device cannot cause a type-confused read and a concurrent unbind cannot free the hid_device from under it. It then takes a reference and returns -EPROBE_DEFER until the sync interface has probed. If the sync interface never binds, the async interface defers forever, which is fine here. - raw_event() now holds sd->lock and re-checks sd->removed so events on either interface are serialised against removal. - status_work runs once for async devices instead of rearming. A single status request is sent when the headset connects to get the initial battery level. No device sets async_interface yet. This is the infrastructure for the next commit. Signed-off-by: Sriman Achanta --- drivers/hid/hid-steelseries-arctis.c | 158 ++++++++++++++++++++++----- 1 file changed, 131 insertions(+), 27 deletions(-) diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelse= ries-arctis.c index e534aa44e70a..9960c0ec512b 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -26,6 +26,7 @@ struct steelseries_device_info { unsigned long capabilities; =20 u8 sync_interface; + u8 async_interface; =20 int (*request_status)(struct hid_device *hdev); void (*parse_status)(struct steelseries_device *sd, u8 *data, int size); @@ -271,7 +272,8 @@ static void steelseries_status_timer_work_handler(struc= t work_struct *work) sd->info->request_status(sd->hdev); =20 spin_lock_irqsave(&sd->lock, flags); - if (!sd->removed) + /* Async devices push status events themselves; only poll once. */ + if (!sd->removed && !sd->info->async_interface) schedule_delayed_work(&sd->status_work, msecs_to_jiffies(STEELSERIES_HEADSET_STATUS_TIMEOUT_MS)); spin_unlock_irqrestore(&sd->lock, flags); @@ -318,6 +320,53 @@ static int steelseries_battery_register(struct steelse= ries_device *sd) return 0; } =20 +static struct hid_driver steelseries_arctis_driver; + +static struct steelseries_device * +steelseries_get_sibling_sd(struct hid_device *hdev, int interface_num) +{ + struct usb_interface *intf =3D to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev =3D interface_to_usbdev(intf); + struct usb_interface *sibling_intf; + struct hid_device *sibling_hdev; + struct steelseries_device *sd =3D NULL; + + sibling_intf =3D usb_ifnum_to_if(usb_dev, interface_num); + if (!sibling_intf) + return NULL; + + /* + * usb_get_intfdata() only yields a hid_device when usbhid is bound; + * gate on the descriptor class so a non-HID sibling (e.g. a crafted + * device exposing storage or audio here) is never treated as one. + */ + if (sibling_intf->cur_altsetting->desc.bInterfaceClass !=3D USB_INTERFACE= _CLASS_HID) + return NULL; + + /* + * Take the sibling's device lock across the intfdata read and the + * kref_get so a concurrent unbind cannot free the hid_device underneath + * us; usbhid leaves intfdata dangling on disconnect, so dev.driver is + * the reliable "still bound" test under this lock. Use device_trylock() + * to stay off the lockdep chain of the interface being probed and let + * the caller retry via -EPROBE_DEFER if the sibling is momentarily busy. + */ + if (!device_trylock(&sibling_intf->dev)) + return NULL; + if (sibling_intf->dev.driver) { + sibling_hdev =3D usb_get_intfdata(sibling_intf); + if (sibling_hdev && + sibling_hdev->driver =3D=3D &steelseries_arctis_driver) { + sd =3D hid_get_drvdata(sibling_hdev); + if (sd) + kref_get(&sd->refcnt); + } + } + device_unlock(&sibling_intf->dev); + + return sd; +} + static int steelseries_arctis_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -339,43 +388,81 @@ static int steelseries_arctis_probe(struct hid_device= *hdev, if (ret) return ret; =20 - /* Let hid-generic handle non-sync interfaces */ - if (interface_num !=3D info->sync_interface) + /* Let hid-generic handle non-vendor or unknown interfaces */ + if (interface_num !=3D info->sync_interface && + (!info->async_interface || interface_num !=3D info->async_interface)) return hid_hw_start(hdev, HID_CONNECT_DEFAULT); =20 - sd =3D kzalloc_obj(*sd, GFP_KERNEL); - if (!sd) - return -ENOMEM; - - kref_init(&sd->refcnt); - sd->hdev =3D hdev; - sd->info =3D info; - spin_lock_init(&sd->lock); + if (interface_num =3D=3D info->sync_interface) { + sd =3D kzalloc_obj(*sd, GFP_KERNEL); + if (!sd) + return -ENOMEM; + + kref_init(&sd->refcnt); + sd->hdev =3D hdev; + sd->info =3D info; + spin_lock_init(&sd->lock); + INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handle= r); + + ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + goto err_free; + + ret =3D hid_hw_open(hdev); + if (ret) + goto err_stop; + + if (info->capabilities & SS_CAP_BATTERY) { + ret =3D steelseries_battery_register(sd); + if (ret < 0) + hid_warn(hdev, "Failed to register battery: %d\n", ret); + } =20 - hid_set_drvdata(hdev, sd); + /* + * Publish drvdata only once fully initialised: the async sibling + * attaches by reading it, so it must never observe a half-built or + * failed instance. A failed probe never gets here, so the error + * path below has nothing to unpublish. + */ + hid_set_drvdata(hdev, sd); + schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100)); =20 - ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (ret) - goto err_put; + return 0; + } =20 - ret =3D hid_hw_open(hdev); - if (ret) - goto err_stop; + /* + * The async interface shares the steelseries_device created by the + * sync interface. Defer until the sync interface has probed and + * published its drvdata. + */ + if (info->async_interface && interface_num =3D=3D info->async_interface) { + sd =3D steelseries_get_sibling_sd(hdev, info->sync_interface); + if (!sd) + return -EPROBE_DEFER; + + hid_set_drvdata(hdev, sd); + + ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + kref_put(&sd->refcnt, steelseries_device_release); + return ret; + } =20 - if (info->capabilities & SS_CAP_BATTERY) { - ret =3D steelseries_battery_register(sd); - if (ret < 0) - hid_warn(hdev, "Failed to register battery: %d\n", ret); + ret =3D hid_hw_open(hdev); + if (ret) { + hid_hw_stop(hdev); + kref_put(&sd->refcnt, steelseries_device_release); + return ret; + } + return 0; } =20 - INIT_DELAYED_WORK(&sd->status_work, steelseries_status_timer_work_handler= ); - schedule_delayed_work(&sd->status_work, msecs_to_jiffies(100)); - - return 0; + return -ENODEV; =20 err_stop: hid_hw_stop(hdev); -err_put: +err_free: + /* drvdata is unpublished until full success, so no sibling can hold sd. = */ kref_put(&sd->refcnt, steelseries_device_release); return ret; } @@ -428,10 +515,21 @@ static int steelseries_arctis_raw_event(struct hid_de= vice *hdev, u8 old_capacity; bool old_connected; bool old_charging; + bool is_async_interface; + unsigned long flags; =20 if (!sd) return 0; =20 + is_async_interface =3D (hdev !=3D sd->hdev); + + spin_lock_irqsave(&sd->lock, flags); + + if (sd->removed) { + spin_unlock_irqrestore(&sd->lock, flags); + return 0; + } + old_capacity =3D sd->battery_capacity; old_connected =3D sd->headset_connected; old_charging =3D sd->battery_charging; @@ -444,6 +542,10 @@ static int steelseries_arctis_raw_event(struct hid_dev= ice *hdev, old_connected ? "" : "not ", sd->headset_connected ? "" : "not "); =20 + if (sd->headset_connected && !old_connected && + sd->info->async_interface && is_async_interface) + schedule_delayed_work(&sd->status_work, 0); + if (sd->battery) { steelseries_headset_set_wireless_status(sd->hdev, sd->headset_connected); @@ -467,6 +569,8 @@ static int steelseries_arctis_raw_event(struct hid_devi= ce *hdev, power_supply_changed(sd->battery); } =20 + spin_unlock_irqrestore(&sd->lock, flags); + return 0; } =20 --=20 2.55.0 From nobody Fri Oct 2 07:45:34 2026 Received: from mail-qt1-f172.google.com (mail-qt1-f172.google.com [209.85.160.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3F6A7437845 for ; Mon, 3 Aug 2026 19:16:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784566; cv=none; b=H962K4rgXbyLCdIabnXQnznBUV0NrFltP+TURzMj6ZNKBSnTJi0upqNoePyfTeaBMYhkwA/PFd2iZCg1/weMtsyHq6cB8wI1HDuSeJ7Khdm0d8CVCN4vb2qL5EF7UJiUwu3R82+BXU0BcHyulSz/9X4N12b0fs/Zql8UvbwhLeQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785784566; c=relaxed/simple; bh=QTQxcVxUIPL/FxO3Igds3n66KlBgW2V/hK7DCccTnCg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FaiYAsq/PS6pPOTNyF3fF7HasPIn5816Xg45VFaVXRk3wvLIlMdju/j2Vt3WKv9XtccoeaMfOaqxsIRsM6XPDOnNKyfy3LY7+OMOSpxg7D0OQylFTmMXcwo5Pgrf87Scv1TxE6ChybS4YFrRHr4+m+Wu6E378BbkYqxpNOHh6mI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=gy8Vv0yh; arc=none smtp.client-ip=209.85.160.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="gy8Vv0yh" Received: by mail-qt1-f172.google.com with SMTP id d75a77b69052e-51c2a818fc4so22618501cf.2 for ; Mon, 03 Aug 2026 12:16:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785784562; x=1786389362; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=g1Xz+YpJmjqRPPiLSgL2+YLZomKTtVA5e6orURiQ6Hk=; b=gy8Vv0yh+kfaV0bDTfhgRCfWiXhwOilv9EpK4SiT8M/2q7NN+r5cQq3wqK/oD5UccV uC32iQBnYRCULFovoZnY8r8YRP8/p9h9QOBBHLLKpQFr5dJMTH8wJVv3V0LW1EUVVtsE iWRqf+5T6qXasVNF7Sb76KQ+x69sDz9xhtXlcDprXRQRHwWnHDF+B5617VHjjQhNJQ26 g3dSVxLRYPtVG0625HZfxvv/5YP2JnNpRCI/sEdAtyGg9d9y0SHoolAWkJ2dA9okagxe ESMRx3UttEZptAxPVXoQimFgDk62lEI3z1IqaRs3ifnX7ymTTtDvdHGmni6DASJJrBp3 zIAQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785784562; x=1786389362; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=g1Xz+YpJmjqRPPiLSgL2+YLZomKTtVA5e6orURiQ6Hk=; b=lEvpHzxM0aVgRrB++xkT3D2PTXnuwbaZsBGsa99sIOd/eFx4cOL3QVepsajLUThi/D 3PjUpv5qUHNqZ8LYtSZbKt/kouvCdKNe3rSybHQjuSHw0ogu20ncBbsELOHehSAqwdI1 fyIGT74yc7dZ1lOHR07ZWo/dC5FjNBq0O3pnQ33FUZH12Dq9W/leQ+ywLUs7jnrKskrl iPWCbpSJ59Kb0oCdxbwSyJSgxWgzypcxtMHvOvQ3tulkxJGKj7Ko1CKKIzJroAK3lDF0 yW0E6rKV8ckMBUKRp0bLYbEgSr9s41nNoFRRZ8T0sEDo+8tqJ3KD7tspVDHpTMII5Bul RN3w== X-Forwarded-Encrypted: i=1; AHgh+Rphk5sSwGmizTUhHYF1PZhPVMcj0xDBTF3unmmZvFNBA/K2MtMdySRWu29M6sh1Bymo0Hs/lSjlITEn0DM=@vger.kernel.org X-Gm-Message-State: AOJu0YwVcvCKJpYpnyJKMulIwKVDIbwiM3NT7Y9ypfrUUn9EwrCwv3D2 EuJUrFWiIhctXld2AMST/WZyS/TNTdB8QQukuWUUnP91y3A+H1EAreVt X-Gm-Gg: AR+sD12N8g2rf+GzLgoO5XCeAOYD4xED6npiwQYbjeRxE7BxAsERRaLavtcouAa7gRo f3bTIj6+MRl792WPMLJUoM/PpTSaKy0jbcKvssRn83ERFHrZGljTuyeZREDMaiMvrr9o4FTTDlY K1gNfHsywEqBKYBEirJGEldt8hsMPOtAgtbLQwQblyiX3FpEwLYUmI419fJB36KDhpxvIdCp8bF tPsGQ3dL42rJLNpG9YDG41SqaAXvgn/FIZzEL6iba48zGTeoGpn4zzG5GZlUYp8mb2a7FI2NJ7N uGWsW27swyonyDI/JtLDO+3rlczeTwxu0ZNV2WXW7+LOEsAi/kOUTXqlZHKomDhB76XlQxFfRDl n0jWs3NFn/LGLdQ95Nq/nYfmUR58MTnOGFBOFJXozl6vynxLWwsCiKvXzHRPDm8SZRRxZF5noig Z1DWzhqnIUGEb0m19mDFL+bVOf5nuIyL80uGWzbxPO7HRMjCcrNRGbwennHoE63d00vrsIdZ0CE BU4hEX/M/mMv1KCA6NAE+GtVKuMZg== X-Received: by 2002:ac8:7f8e:0:b0:51b:ecbb:206f with SMTP id d75a77b69052e-52b567bff85mr203992041cf.32.1785784562464; Mon, 03 Aug 2026 12:16:02 -0700 (PDT) Received: from achantapc.tail227c81.ts.net ([2600:4040:122e:7d00:f04d:e0df:fe04:e789]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-52b4e64df9bsm67803541cf.0.2026.08.03.12.16.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Aug 2026 12:16:02 -0700 (PDT) From: Sriman Achanta To: Jiri Kosina , Benjamin Tissoires Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Bastien Nocera , Simon Wood , Christian Mayer , Sriman Achanta Subject: [PATCH v7 8/8] HID: steelseries: Add support for Arctis Nova 5X and Nova 7 families Date: Mon, 3 Aug 2026 15:15:53 -0400 Message-ID: <20260803191553.66368-9-srimanachanta@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803191553.66368-1-srimanachanta@gmail.com> References: <20260803191553.66368-1-srimanachanta@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The Arctis Nova 5X, Nova 7 Gen2, and Nova 7 2026 refresh headsets answer status polls, but also send unsolicited battery and connection updates on a second HID interface (interface 5). Use that interface through async_interface so the driver does not have to poll. Add request and parse helpers for the Nova status format (0xb0/0xb7/0xb9/0xbb opcodes). The original (pre-Gen2) Arctis Nova 7 family answers the same status opcodes on the same interfaces, but reports battery capacity as a discrete 0-4 level instead of a raw percentage. Add a second parse function that maps that level through steelseries_map_capacity(), and a separate device_info so the two capacity encodings cannot get mixed up. Add the fourteen USB product IDs that share these protocols: 0x2253 Arctis Nova 5X 0x2202 Arctis Nova 7 0x2206 Arctis Nova 7X 0x22a4 Arctis Nova 7X (alternate PID) 0x223a Arctis Nova 7 Diablo 0x227a Arctis Nova 7 World of Warcraft Edition 0x22a1 Arctis Nova 7 2026 0x22a7 Arctis Nova 7P 2026 0x22a5 Arctis Nova 7X 2026 0x22a9 Arctis Nova 7 Diablo 2026 0x227e Arctis Nova 7 Gen 2 0x2258 Arctis Nova 7X Gen 2 0x229e Arctis Nova 7X Gen 2 (alternate PID) 0x22ad Arctis Nova 7X Gen 2 (alternate PID) Signed-off-by: Sriman Achanta --- drivers/hid/hid-ids.h | 14 ++++ drivers/hid/hid-quirks.c | 14 ++++ drivers/hid/hid-steelseries-arctis.c | 117 +++++++++++++++++++++++++++ 3 files changed, 145 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 70b30e13b96f..5586a03d00a3 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1379,6 +1379,20 @@ #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_1_X 0x12b6 #define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X 0x2253 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7 0x2202 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X 0x2206 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2 0x22a4 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO 0x223a +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW 0x227a +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026 0x22a1 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026 0x22a7 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026 0x22a5 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026 0x22a9 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2 0x227e +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2 0x2258 +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2 0x229e +#define USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3 0x22ad #define USB_DEVICE_ID_STEELSERIES_MSI_KLC 0x1122 #define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161 =20 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index f546179858c2..eef2c2106dd7 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -749,6 +749,20 @@ static const struct hid_device_id hid_have_special_dri= ver[] =3D { { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRW= S1) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_1_X) }, { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_9) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_5_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_X) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_X_2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_DIABLO) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_WOW) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_2026) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_P_2026) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_X_2026) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_DIABLO_2026) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_GEN2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_X_GEN2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_X_GEN2_2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARC= TIS_NOVA_7_X_GEN2_3) }, #endif #if IS_ENABLED(CONFIG_HID_SUNPLUS) { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, diff --git a/drivers/hid/hid-steelseries-arctis.c b/drivers/hid/hid-steelse= ries-arctis.c index 9960c0ec512b..23fb0cebd72a 100644 --- a/drivers/hid/hid-steelseries-arctis.c +++ b/drivers/hid/hid-steelseries-arctis.c @@ -108,6 +108,13 @@ static int steelseries_arctis_9_request_status(struct = hid_device *hdev) return steelseries_send_output_report(hdev, data, sizeof(data)); } =20 +static int steelseries_arctis_nova_request_status(struct hid_device *hdev) +{ + const u8 data[] =3D { 0x00, 0xb0 }; + + return steelseries_send_output_report(hdev, data, sizeof(data)); +} + /* * Headset battery helpers */ @@ -162,6 +169,58 @@ static void steelseries_arctis_9_parse_status(struct s= teelseries_device *sd, } } =20 +static void steelseries_arctis_nova_parse_status(struct steelseries_device= *sd, + u8 *data, int size) +{ + if (size < 2) + return; + + switch (data[0]) { + case 0xb0: + if (size < 4) + return; + sd->headset_connected =3D (data[1] =3D=3D 0x03); + sd->battery_capacity =3D data[2]; + sd->battery_charging =3D (data[3] =3D=3D 0x01); + break; + case 0xb7: + sd->battery_capacity =3D data[1]; + break; + case 0xb9: + sd->headset_connected =3D (data[1] =3D=3D 0x03); + break; + case 0xbb: + sd->battery_charging =3D (data[1] =3D=3D 0x01); + break; + } +} + +static void steelseries_arctis_nova_7_parse_status(struct steelseries_devi= ce *sd, + u8 *data, int size) +{ + if (size < 2) + return; + + switch (data[0]) { + case 0xb0: + if (size < 4) + return; + sd->headset_connected =3D (data[1] =3D=3D 0x03); + sd->battery_capacity =3D steelseries_map_capacity(data[2], 0, 4); + sd->battery_charging =3D (data[3] =3D=3D 0x01); + break; + case 0xb7: + sd->battery_capacity =3D steelseries_map_capacity(data[1], 0, 4); + break; + case 0xb9: + sd->headset_connected =3D (data[1] =3D=3D 0x03); + break; + case 0xbb: + sd->battery_charging =3D (data[1] =3D=3D 0x01); + break; + } +} + /* * Device info definitions */ @@ -180,6 +239,22 @@ static const struct steelseries_device_info arctis_9_i= nfo =3D { .parse_status =3D steelseries_arctis_9_parse_status, }; =20 +static const struct steelseries_device_info arctis_nova_info =3D { + .sync_interface =3D 3, + .async_interface =3D 5, + .capabilities =3D SS_CAP_BATTERY, + .request_status =3D steelseries_arctis_nova_request_status, + .parse_status =3D steelseries_arctis_nova_parse_status, +}; + +static const struct steelseries_device_info arctis_nova_7_info =3D { + .sync_interface =3D 3, + .async_interface =3D 5, + .capabilities =3D SS_CAP_BATTERY, + .request_status =3D steelseries_arctis_nova_request_status, + .parse_status =3D steelseries_arctis_nova_7_parse_status, +}; + /* * Headset wireless status and battery infrastructure */ @@ -581,6 +656,48 @@ static const struct hid_device_id steelseries_arctis_d= evices[] =3D { { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9), .driver_data =3D (unsigned long)&arctis_9_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_5_X), + .driver_data =3D (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7), + .driver_data =3D (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X), + .driver_data =3D (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2), + .driver_data =3D (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO), + .driver_data =3D (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_WOW), + .driver_data =3D (unsigned long)&arctis_nova_7_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_2026), + .driver_data =3D (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_P_2026), + .driver_data =3D (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_2026), + .driver_data =3D (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_DIABLO_2026), + .driver_data =3D (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_GEN2), + .driver_data =3D (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2), + .driver_data =3D (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_2), + .driver_data =3D (unsigned long)&arctis_nova_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, + USB_DEVICE_ID_STEELSERIES_ARCTIS_NOVA_7_X_GEN2_3), + .driver_data =3D (unsigned long)&arctis_nova_info }, {} }; MODULE_DEVICE_TABLE(hid, steelseries_arctis_devices); --=20 2.55.0