* [PATCH v2 2/4] soc: mediatek: pwrap: Add support for MT6735 and MT6328 SoC/PMIC pair
From: Yassine Oudjana via B4 Relay @ 2026-05-31 9:10 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Flora Fu, Alexandre Mergnat,
Liam Girdwood, Mark Brown, Dmitry Torokhov
Cc: Yassine Oudjana, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-input
In-Reply-To: <20260531-mt6735-pwrap-mt6328-pmic-v2-0-dac37bf92894@protonmail.com>
From: Yassine Oudjana <y.oudjana@protonmail.com>
Add register definitions and configuration for the MT6735 SoC and the
MT6328 PMIC which are commonly paired and communicate through the PMIC
wrapper.
Note that the PMIC wrapper on MT6735M has a slightly different register
map and is therefore NOT compatible with MT6735.
Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
---
drivers/soc/mediatek/mtk-pmic-wrap.c | 177 ++++++++++++++++++++++++++++++++++-
1 file changed, 174 insertions(+), 3 deletions(-)
diff --git a/drivers/soc/mediatek/mtk-pmic-wrap.c b/drivers/soc/mediatek/mtk-pmic-wrap.c
index 0bcd85826375..0f14dca5609d 100644
--- a/drivers/soc/mediatek/mtk-pmic-wrap.c
+++ b/drivers/soc/mediatek/mtk-pmic-wrap.c
@@ -3,6 +3,7 @@
* Copyright (c) 2014 MediaTek Inc.
* Author: Flora Fu, MediaTek
*/
+
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
@@ -100,7 +101,7 @@ enum dew_regs {
PWRAP_DEW_CIPHER_MODE,
PWRAP_DEW_CIPHER_SWRST,
- /* MT6323 only regs */
+ /* MT6323 and MT6328 only regs */
PWRAP_DEW_CIPHER_EN,
PWRAP_DEW_RDDMY_NO,
@@ -121,8 +122,10 @@ enum dew_regs {
PWRAP_RG_SPI_CON13,
PWRAP_SPISLV_KEY,
- /* MT6359 only regs */
+ /* MT6359 and MT6328 only regs */
PWRAP_DEW_CRC_SWRST,
+
+ /* MT6359 only regs */
PWRAP_DEW_RG_EN_RECORD,
PWRAP_DEW_RECORD_CMD0,
PWRAP_DEW_RECORD_CMD1,
@@ -171,6 +174,23 @@ static const u32 mt6323_regs[] = {
[PWRAP_DEW_RDDMY_NO] = 0x01a4,
};
+static const u32 mt6328_regs[] = {
+ [PWRAP_DEW_DIO_EN] = 0x02d4,
+ [PWRAP_DEW_READ_TEST] = 0x02d6,
+ [PWRAP_DEW_WRITE_TEST] = 0x02d8,
+ [PWRAP_DEW_CRC_SWRST] = 0x02da,
+ [PWRAP_DEW_CRC_EN] = 0x02dc,
+ [PWRAP_DEW_CRC_VAL] = 0x02de,
+ [PWRAP_DEW_MON_GRP_SEL] = 0x02e0,
+ [PWRAP_DEW_CIPHER_KEY_SEL] = 0x02e2,
+ [PWRAP_DEW_CIPHER_IV_SEL] = 0x02e4,
+ [PWRAP_DEW_CIPHER_EN] = 0x02e6,
+ [PWRAP_DEW_CIPHER_RDY] = 0x02e8,
+ [PWRAP_DEW_CIPHER_MODE] = 0x02ea,
+ [PWRAP_DEW_CIPHER_SWRST] = 0x02ec,
+ [PWRAP_DEW_RDDMY_NO] = 0x02ee,
+};
+
static const u32 mt6331_regs[] = {
[PWRAP_DEW_DIO_EN] = 0x018c,
[PWRAP_DEW_READ_TEST] = 0x018e,
@@ -394,7 +414,7 @@ enum pwrap_regs {
PWRAP_ADC_RDATA_ADDR1,
PWRAP_ADC_RDATA_ADDR2,
- /* MT7622 only regs */
+ /* MT7622 and MT6735 only regs */
PWRAP_STA,
PWRAP_CLR,
PWRAP_DVFS_ADR8,
@@ -417,6 +437,8 @@ enum pwrap_regs {
PWRAP_ADC_RDATA_ADDR,
PWRAP_GPS_STA,
PWRAP_SW_RST,
+
+ /* MT7622 only regs */
PWRAP_DVFS_STEP_CTRL0,
PWRAP_DVFS_STEP_CTRL1,
PWRAP_DVFS_STEP_CTRL2,
@@ -481,6 +503,13 @@ enum pwrap_regs {
/* MT8516 only regs */
PWRAP_OP_TYPE,
PWRAP_MSB_FIRST,
+
+ /* MT6735 only regs */
+ PWRAP_WACS3_EN,
+ PWRAP_INIT_DONE3,
+ PWRAP_WACS3_CMD,
+ PWRAP_WACS3_RDATA,
+ PWRAP_WACS3_VLDCLR,
};
static const int mt2701_regs[] = {
@@ -569,6 +598,119 @@ static const int mt2701_regs[] = {
[PWRAP_ADC_RDATA_ADDR2] = 0x154,
};
+static const int mt6735_regs[] = {
+ [PWRAP_MUX_SEL] = 0x0,
+ [PWRAP_WRAP_EN] = 0x4,
+ [PWRAP_DIO_EN] = 0x8,
+ [PWRAP_SIDLY] = 0xc,
+ [PWRAP_RDDMY] = 0x10,
+ [PWRAP_SI_CK_CON] = 0x14,
+ [PWRAP_CSHEXT_WRITE] = 0x18,
+ [PWRAP_CSHEXT_READ] = 0x1c,
+ [PWRAP_CSLEXT_START] = 0x20,
+ [PWRAP_CSLEXT_END] = 0x24,
+ [PWRAP_STAUPD_PRD] = 0x28,
+ [PWRAP_STAUPD_GRPEN] = 0x2c,
+ [PWRAP_EINT_STA0_ADR] = 0x30,
+ [PWRAP_EINT_STA1_ADR] = 0x34,
+ [PWRAP_STA] = 0x38,
+ [PWRAP_CLR] = 0x3c,
+ [PWRAP_STAUPD_MAN_TRIG] = 0x40,
+ [PWRAP_STAUPD_STA] = 0x44,
+ [PWRAP_WRAP_STA] = 0x48,
+ [PWRAP_HARB_INIT] = 0x4c,
+ [PWRAP_HARB_HPRIO] = 0x50,
+ [PWRAP_HIPRIO_ARB_EN] = 0x54,
+ [PWRAP_HARB_STA0] = 0x58,
+ [PWRAP_HARB_STA1] = 0x5c,
+ [PWRAP_MAN_EN] = 0x60,
+ [PWRAP_MAN_CMD] = 0x64,
+ [PWRAP_MAN_RDATA] = 0x68,
+ [PWRAP_MAN_VLDCLR] = 0x6c,
+ [PWRAP_WACS0_EN] = 0x70,
+ [PWRAP_INIT_DONE0] = 0x74,
+ [PWRAP_WACS0_CMD] = 0x78,
+ [PWRAP_WACS0_RDATA] = 0x7c,
+ [PWRAP_WACS0_VLDCLR] = 0x80,
+ [PWRAP_WACS1_EN] = 0x84,
+ [PWRAP_INIT_DONE1] = 0x88,
+ [PWRAP_WACS1_CMD] = 0x8c,
+ [PWRAP_WACS1_RDATA] = 0x90,
+ [PWRAP_WACS1_VLDCLR] = 0x94,
+ [PWRAP_WACS2_EN] = 0x98,
+ [PWRAP_INIT_DONE2] = 0x9c,
+ [PWRAP_WACS2_CMD] = 0xa0,
+ [PWRAP_WACS2_RDATA] = 0xa4,
+ [PWRAP_WACS2_VLDCLR] = 0xa8,
+ [PWRAP_WACS3_EN] = 0xac,
+ [PWRAP_INIT_DONE3] = 0xb0,
+ [PWRAP_WACS3_CMD] = 0xb4,
+ [PWRAP_WACS3_RDATA] = 0xb8,
+ [PWRAP_WACS3_VLDCLR] = 0xbc,
+ [PWRAP_INT_EN] = 0xc0,
+ [PWRAP_INT_FLG_RAW] = 0xc4,
+ [PWRAP_INT_FLG] = 0xc8,
+ [PWRAP_INT_CLR] = 0xcc,
+ [PWRAP_SIG_ADR] = 0xd0,
+ [PWRAP_SIG_MODE] = 0xd4,
+ [PWRAP_SIG_VALUE] = 0xd8,
+ [PWRAP_SIG_ERRVAL] = 0xdc,
+ [PWRAP_CRC_EN] = 0xe0,
+ [PWRAP_TIMER_EN] = 0xe4,
+ [PWRAP_TIMER_STA] = 0xe8,
+ [PWRAP_WDT_UNIT] = 0xec,
+ [PWRAP_WDT_SRC_EN] = 0xf0,
+ [PWRAP_WDT_FLG] = 0xf4,
+ [PWRAP_DEBUG_INT_SEL] = 0xf8,
+ [PWRAP_DVFS_ADR0] = 0xfc,
+ [PWRAP_DVFS_WDATA0] = 0x100,
+ [PWRAP_DVFS_ADR1] = 0x104,
+ [PWRAP_DVFS_WDATA1] = 0x108,
+ [PWRAP_DVFS_ADR2] = 0x10c,
+ [PWRAP_DVFS_WDATA2] = 0x110,
+ [PWRAP_DVFS_ADR3] = 0x114,
+ [PWRAP_DVFS_WDATA3] = 0x118,
+ [PWRAP_DVFS_ADR4] = 0x11c,
+ [PWRAP_DVFS_WDATA4] = 0x120,
+ [PWRAP_DVFS_ADR5] = 0x124,
+ [PWRAP_DVFS_WDATA5] = 0x128,
+ [PWRAP_DVFS_ADR6] = 0x12c,
+ [PWRAP_DVFS_WDATA6] = 0x130,
+ [PWRAP_DVFS_ADR7] = 0x134,
+ [PWRAP_DVFS_WDATA7] = 0x138,
+ [PWRAP_DVFS_ADR8] = 0x13c,
+ [PWRAP_DVFS_WDATA8] = 0x140,
+ [PWRAP_DVFS_ADR9] = 0x144,
+ [PWRAP_DVFS_WDATA9] = 0x148,
+ [PWRAP_DVFS_ADR10] = 0x14c,
+ [PWRAP_DVFS_WDATA10] = 0x150,
+ [PWRAP_DVFS_ADR11] = 0x154,
+ [PWRAP_DVFS_WDATA11] = 0x158,
+ [PWRAP_DVFS_ADR12] = 0x15c,
+ [PWRAP_DVFS_WDATA12] = 0x160,
+ [PWRAP_DVFS_ADR13] = 0x164,
+ [PWRAP_DVFS_WDATA13] = 0x168,
+ [PWRAP_DVFS_ADR14] = 0x16c,
+ [PWRAP_DVFS_WDATA14] = 0x170,
+ [PWRAP_DVFS_ADR15] = 0x174,
+ [PWRAP_DVFS_WDATA15] = 0x178,
+ [PWRAP_SPMINF_STA] = 0x17c,
+ [PWRAP_CIPHER_KEY_SEL] = 0x180,
+ [PWRAP_CIPHER_IV_SEL] = 0x184,
+ [PWRAP_CIPHER_EN] = 0x188,
+ [PWRAP_CIPHER_RDY] = 0x18c,
+ [PWRAP_CIPHER_MODE] = 0x190,
+ [PWRAP_CIPHER_SWRST] = 0x194,
+ [PWRAP_DCM_EN] = 0x198,
+ [PWRAP_DCM_DBC_PRD] = 0x19c,
+ [PWRAP_EXT_CK] = 0x1a0,
+ [PWRAP_ADC_CMD_ADDR] = 0x1a4,
+ [PWRAP_PWRAP_ADC_CMD] = 0x1a8,
+ [PWRAP_ADC_RDATA_ADDR] = 0x1ac,
+ [PWRAP_GPS_STA] = 0x1b0,
+ [PWRAP_SW_RST] = 0x1b4,
+};
+
static const int mt6765_regs[] = {
[PWRAP_MUX_SEL] = 0x0,
[PWRAP_WRAP_EN] = 0x4,
@@ -1302,6 +1444,7 @@ static const int mt8186_regs[] = {
enum pmic_type {
PMIC_MT6323,
+ PMIC_MT6328,
PMIC_MT6331,
PMIC_MT6332,
PMIC_MT6351,
@@ -1314,6 +1457,7 @@ enum pmic_type {
enum pwrap_type {
PWRAP_MT2701,
+ PWRAP_MT6735,
PWRAP_MT6765,
PWRAP_MT6779,
PWRAP_MT6795,
@@ -1733,6 +1877,10 @@ static void pwrap_init_chip_select_ext(struct pmic_wrapper *wrp, u8 hext_write,
static int pwrap_common_init_reg_clock(struct pmic_wrapper *wrp)
{
switch (wrp->master->type) {
+ case PWRAP_MT6735:
+ pwrap_write(wrp, wrp->slave->dew_regs[PWRAP_DEW_RDDMY_NO], 0x8);
+ pwrap_init_chip_select_ext(wrp, 0, 6, 0, 0);
+ break;
case PWRAP_MT6795:
if (wrp->slave->type == PMIC_MT6331) {
const u32 *dew_regs = wrp->slave->dew_regs;
@@ -1839,6 +1987,7 @@ static int pwrap_init_cipher(struct pmic_wrapper *wrp)
pwrap_writel(wrp, 1, PWRAP_CIPHER_START);
break;
case PWRAP_MT2701:
+ case PWRAP_MT6735:
case PWRAP_MT6765:
case PWRAP_MT6779:
case PWRAP_MT6795:
@@ -2194,6 +2343,14 @@ static const struct pwrap_slv_type pmic_mt6323 = {
PWRAP_SLV_CAP_SECURITY,
};
+static const struct pwrap_slv_type pmic_mt6328 = {
+ .dew_regs = mt6328_regs,
+ .type = PMIC_MT6328,
+ .regops = &pwrap_regops16,
+ .caps = PWRAP_SLV_CAP_SPI | PWRAP_SLV_CAP_DUALIO |
+ PWRAP_SLV_CAP_SECURITY,
+};
+
static const struct pwrap_slv_type pmic_mt6331 = {
.dew_regs = mt6331_regs,
.type = PMIC_MT6331,
@@ -2249,6 +2406,7 @@ static const struct pwrap_slv_type pmic_mt6397 = {
static const struct of_device_id of_slave_match_tbl[] = {
{ .compatible = "mediatek,mt6323", .data = &pmic_mt6323 },
+ { .compatible = "mediatek,mt6328", .data = &pmic_mt6328 },
{ .compatible = "mediatek,mt6331", .data = &pmic_mt6331 },
{ .compatible = "mediatek,mt6351", .data = &pmic_mt6351 },
{ .compatible = "mediatek,mt6357", .data = &pmic_mt6357 },
@@ -2277,6 +2435,18 @@ static const struct pmic_wrapper_type pwrap_mt2701 = {
.init_soc_specific = pwrap_mt2701_init_soc_specific,
};
+static const struct pmic_wrapper_type pwrap_mt6735 = {
+ .regs = mt6735_regs,
+ .type = PWRAP_MT6735,
+ .arb_en_all = GENMASK(9, 0),
+ .int_en_all = GENMASK(31, 0),
+ .spi_w = PWRAP_MAN_CMD_SPI_WRITE,
+ .wdt_src = PWRAP_WDT_SRC_MASK_ALL,
+ .caps = PWRAP_CAP_RESET | PWRAP_CAP_DCM,
+ .init_reg_clock = pwrap_common_init_reg_clock,
+ .init_soc_specific = NULL,
+};
+
static const struct pmic_wrapper_type pwrap_mt6765 = {
.regs = mt6765_regs,
.type = PWRAP_MT6765,
@@ -2446,6 +2616,7 @@ static const struct pmic_wrapper_type pwrap_mt8186 = {
static const struct of_device_id of_pwrap_match_tbl[] = {
{ .compatible = "mediatek,mt2701-pwrap", .data = &pwrap_mt2701 },
+ { .compatible = "mediatek,mt6735-pwrap", .data = &pwrap_mt6735 },
{ .compatible = "mediatek,mt6765-pwrap", .data = &pwrap_mt6765 },
{ .compatible = "mediatek,mt6779-pwrap", .data = &pwrap_mt6779 },
{ .compatible = "mediatek,mt6795-pwrap", .data = &pwrap_mt6795 },
--
2.54.0
^ permalink raw reply related
* [PATCH v2 0/4] MediaTek MT6735+MT6328 SoC/PMIC pair base support
From: Yassine Oudjana via B4 Relay @ 2026-05-31 9:10 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Flora Fu, Alexandre Mergnat,
Liam Girdwood, Mark Brown, Dmitry Torokhov
Cc: Yassine Oudjana, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-input
These patches are part of a larger effort to support the MT6735 SoC family in
mainline Linux. More patches (unsent or sent and pending review or revision)
can be found here[1].
This series adds base support for the MediaTek MT6735 SoC and MT6328 PMIC pair.
This includes PMIC wrapper support on the SoC side and regulators and keys on
the PMIC side. The PMIC has other blocks such as an audio codec and battery
charger which can be supported in the future.
Changes since v1:
- Fit regulator definitions in 2 lines and 100 characters whenever possible.
- Remove get_mode and set_mode ops for regulators that don't support them.
[1] https://gitlab.com/mt6735-mainline/linux/-/commits/mt6735-staging
Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
---
Yassine Oudjana (4):
dt-bindings: mediatek: pwrap: Add MT6735 compatible
soc: mediatek: pwrap: Add support for MT6735 and MT6328 SoC/PMIC pair
regulator: Add driver for MediaTek MT6328 PMIC regulators
Input: mtk-pmic-keys - Add support for MT6328
.../bindings/soc/mediatek/mediatek,pwrap.yaml | 1 +
drivers/input/keyboard/mtk-pmic-keys.c | 15 +
drivers/regulator/Kconfig | 9 +
drivers/regulator/Makefile | 1 +
drivers/regulator/mt6328-regulator.c | 500 +++++++++++++++++++++
drivers/soc/mediatek/mtk-pmic-wrap.c | 177 +++++++-
include/linux/regulator/mt6328-regulator.h | 49 ++
7 files changed, 749 insertions(+), 3 deletions(-)
---
base-commit: 7da7f07112610a520567421dd2ffcb51beaefbcc
change-id: 20260531-mt6735-pwrap-mt6328-pmic-de481d5c5e89
Best regards,
--
Yassine Oudjana <y.oudjana@protonmail.com>
^ permalink raw reply
* [PATCH v2 1/4] dt-bindings: mediatek: pwrap: Add MT6735 compatible
From: Yassine Oudjana via B4 Relay @ 2026-05-31 9:10 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Flora Fu, Alexandre Mergnat,
Liam Girdwood, Mark Brown, Dmitry Torokhov
Cc: Yassine Oudjana, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-input
In-Reply-To: <20260531-mt6735-pwrap-mt6328-pmic-v2-0-dac37bf92894@protonmail.com>
From: Yassine Oudjana <y.oudjana@protonmail.com>
Add a compatible string for MT6735 PMIC wrapper.
Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
Documentation/devicetree/bindings/soc/mediatek/mediatek,pwrap.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/soc/mediatek/mediatek,pwrap.yaml b/Documentation/devicetree/bindings/soc/mediatek/mediatek,pwrap.yaml
index e7c4a3984c60..3a5fd46ce282 100644
--- a/Documentation/devicetree/bindings/soc/mediatek/mediatek,pwrap.yaml
+++ b/Documentation/devicetree/bindings/soc/mediatek/mediatek,pwrap.yaml
@@ -31,6 +31,7 @@ properties:
- items:
- enum:
- mediatek,mt2701-pwrap
+ - mediatek,mt6735-pwrap
- mediatek,mt6765-pwrap
- mediatek,mt6779-pwrap
- mediatek,mt6795-pwrap
--
2.54.0
^ permalink raw reply related
* [PATCH v2 3/4] regulator: Add driver for MediaTek MT6328 PMIC regulators
From: Yassine Oudjana via B4 Relay @ 2026-05-31 9:10 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Flora Fu, Alexandre Mergnat,
Liam Girdwood, Mark Brown, Dmitry Torokhov
Cc: Yassine Oudjana, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-input
In-Reply-To: <20260531-mt6735-pwrap-mt6328-pmic-v2-0-dac37bf92894@protonmail.com>
From: Yassine Oudjana <y.oudjana@protonmail.com>
Add a driver for the regulators on the MT6328 PMIC.
Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
---
drivers/regulator/Kconfig | 9 +
drivers/regulator/Makefile | 1 +
drivers/regulator/mt6328-regulator.c | 500 +++++++++++++++++++++++++++++
include/linux/regulator/mt6328-regulator.h | 49 +++
4 files changed, 559 insertions(+)
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index a54a549196fe..a4f0c21aa6ea 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -919,6 +919,15 @@ config REGULATOR_MT6323
This driver supports the control of different power rails of device
through regulator interface.
+config REGULATOR_MT6328
+ tristate "MediaTek MT6328 PMIC"
+ depends on MFD_MT6397
+ help
+ Say y here to select this option to enable the power regulator of
+ MediaTek MT6328 PMIC.
+ This driver supports the control of different power rails of device
+ through regulator interface.
+
config REGULATOR_MT6331
tristate "MediaTek MT6331 PMIC"
depends on MFD_MT6397
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 134eee274dbf..4d1e40f0f911 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -109,6 +109,7 @@ obj-$(CONFIG_REGULATOR_MT6311) += mt6311-regulator.o
obj-$(CONFIG_REGULATOR_MT6315) += mt6315-regulator.o
obj-$(CONFIG_REGULATOR_MT6315) += mt6316-regulator.o
obj-$(CONFIG_REGULATOR_MT6323) += mt6323-regulator.o
+obj-$(CONFIG_REGULATOR_MT6328) += mt6328-regulator.o
obj-$(CONFIG_REGULATOR_MT6331) += mt6331-regulator.o
obj-$(CONFIG_REGULATOR_MT6332) += mt6332-regulator.o
obj-$(CONFIG_REGULATOR_MT6357) += mt6357-regulator.o
diff --git a/drivers/regulator/mt6328-regulator.c b/drivers/regulator/mt6328-regulator.c
new file mode 100644
index 000000000000..a0e4226ffe37
--- /dev/null
+++ b/drivers/regulator/mt6328-regulator.c
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MediaTek MT6328 regulator driver
+ * Based on MT6323 driver.
+ *
+ * Copyright (c) 2016 MediaTek Inc.
+ * Copyright (c) 2022 Yassine Oudjana <y.oudjana@protonmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/mt6397/core.h>
+#include <linux/mfd/mt6328/registers.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/mt6328-regulator.h>
+#include <linux/regulator/of_regulator.h>
+
+#define MT6328_LDO_TABLE_MODE_NORMAL 0
+#define MT6328_LDO_TABLE_MODE_LP 1
+
+/*
+ * MT6328 regulators' information
+ *
+ * @desc: standard fields of regulator description.
+ * @qi: Mask for query enable signal status of regulators
+ * @vselon_reg: Register sections for hardware control mode of bucks
+ * @vselctrl_reg: Register for controlling the buck control mode.
+ * @vselctrl_mask: Mask for query buck's voltage control mode.
+ */
+struct mt6328_regulator_info {
+ struct regulator_desc desc;
+ u32 qi;
+ u32 vselon_reg;
+ u32 vselctrl_reg;
+ u32 vselctrl_mask;
+ u32 modeset_reg;
+ u32 modeset_mask;
+};
+
+#define MT6328_BUCK(match, vreg, min, max, step, volt_ranges, enreg, \
+ vosel, vosel_mask, voselon, vosel_ctrl) \
+[MT6328_ID_##vreg] = { \
+ .desc = { \
+ .name = #vreg, \
+ .of_match = of_match_ptr(match), \
+ .ops = &mt6328_volt_range_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = MT6328_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = (max - min)/step + 1, \
+ .linear_ranges = volt_ranges, \
+ .n_linear_ranges = ARRAY_SIZE(volt_ranges), \
+ .vsel_reg = vosel, \
+ .vsel_mask = vosel_mask, \
+ .enable_reg = enreg, \
+ .enable_mask = BIT(0), \
+ }, \
+ .qi = BIT(13), \
+ .vselon_reg = voselon, \
+ .vselctrl_reg = vosel_ctrl, \
+ .vselctrl_mask = BIT(1), \
+}
+
+#define MT6328_LDO_RANGE(match, vreg, min, max, step, volt_ranges, \
+ enreg, enbit, vosel, vosel_mask, _modeset_reg, \
+ _modeset_mask) \
+[MT6328_ID_##vreg] = { \
+ .desc = { \
+ .name = #vreg, \
+ .of_match = of_match_ptr(match), \
+ .ops = &mt6328_volt_range_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = MT6328_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = (max - min)/step + 1, \
+ .linear_ranges = volt_ranges, \
+ .n_linear_ranges = ARRAY_SIZE(volt_ranges), \
+ .vsel_reg = vosel, \
+ .vsel_mask = vosel_mask, \
+ .enable_reg = enreg, \
+ .enable_mask = BIT(enbit), \
+ }, \
+ .qi = BIT(15), \
+ .modeset_reg = _modeset_reg, \
+ .modeset_mask = _modeset_mask, \
+}
+
+#define MT6328_LDO_TABLE(match, vreg, ldo_volt_table, enreg, enbit, \
+ vosel, vosel_mask, _modeset_reg, _modeset_mask)\
+[MT6328_ID_##vreg] = { \
+ .desc = { \
+ .name = #vreg, \
+ .of_match = of_match_ptr(match), \
+ .ops = &mt6328_volt_table_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = MT6328_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = ARRAY_SIZE(ldo_volt_table), \
+ .volt_table = ldo_volt_table, \
+ .vsel_reg = vosel, \
+ .vsel_mask = vosel_mask, \
+ .enable_reg = enreg, \
+ .enable_mask = BIT(enbit), \
+ }, \
+ .qi = BIT(15), \
+ .modeset_reg = _modeset_reg, \
+ .modeset_mask = _modeset_mask, \
+}
+
+#define MT6328_LDO_TABLE_SINGLE_MODE(match, vreg, ldo_volt_table, enreg,\
+ enbit, vosel, vosel_mask) \
+[MT6328_ID_##vreg] = { \
+ .desc = { \
+ .name = #vreg, \
+ .of_match = of_match_ptr(match), \
+ .ops = &mt6328_volt_table_single_mode_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = MT6328_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = ARRAY_SIZE(ldo_volt_table), \
+ .volt_table = ldo_volt_table, \
+ .vsel_reg = vosel, \
+ .vsel_mask = vosel_mask, \
+ .enable_reg = enreg, \
+ .enable_mask = BIT(enbit), \
+ }, \
+ .qi = BIT(15), \
+ .modeset_reg = 0, \
+ .modeset_mask = 0, \
+}
+
+#define MT6328_REG_FIXED(match, vreg, enreg, enbit, volt, \
+ _modeset_reg, _modeset_mask) \
+[MT6328_ID_##vreg] = { \
+ .desc = { \
+ .name = #vreg, \
+ .of_match = of_match_ptr(match), \
+ .ops = &mt6328_volt_fixed_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = MT6328_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = 1, \
+ .enable_reg = enreg, \
+ .enable_mask = BIT(enbit), \
+ .min_uV = volt, \
+ }, \
+ .qi = BIT(15), \
+ .modeset_reg = _modeset_reg, \
+ .modeset_mask = _modeset_mask, \
+}
+
+#define MT6328_REG_FIXED_SINGLE_MODE(match, vreg, enreg, enbit, volt, \
+ _modeset_reg, _modeset_mask) \
+[MT6328_ID_##vreg] = { \
+ .desc = { \
+ .name = #vreg, \
+ .of_match = of_match_ptr(match), \
+ .ops = &mt6328_volt_fixed_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .id = MT6328_ID_##vreg, \
+ .owner = THIS_MODULE, \
+ .n_voltages = 1, \
+ .enable_reg = enreg, \
+ .enable_mask = BIT(enbit), \
+ .min_uV = volt, \
+ }, \
+ .qi = BIT(15), \
+ .modeset_reg = _modeset_reg, \
+ .modeset_mask = _modeset_mask, \
+}
+
+static const struct linear_range buck_volt_range1[] = {
+ REGULATOR_LINEAR_RANGE(500000, 0, 0x3f, 50000)
+};
+
+static const struct linear_range buck_volt_range2[] = {
+ REGULATOR_LINEAR_RANGE(600000, 0, 0x7f, 6250)
+};
+
+static const struct linear_range buck_volt_range3[] = {
+ REGULATOR_LINEAR_RANGE(1200000, 0, 0x7f, 6250)
+};
+
+static const unsigned int ldo_volt_table1[] = {
+ 1500000, 1800000, 2500000, 2800000
+};
+
+static const unsigned int ldo_volt_table2[] = {
+ 3300000, 3400000, 3500000, 3600000
+};
+
+static const unsigned int ldo_volt_table3[] = {
+ 0, 0, 0, 1800000, 1900000, 2000000, 2100000, 2200000
+};
+
+static const unsigned int ldo_volt_table4[] = {
+ 1700000, 1800000, 1860000, 2760000, 3000000, 3100000
+};
+
+static const unsigned int ldo_volt_table5[] = {
+ 1800000, 2900000, 3000000, 3300000
+};
+
+static const unsigned int ldo_volt_table6[] = {
+ 2900000, 3000000, 3300000
+};
+
+static const unsigned int ldo_volt_table7[] = {
+ 2500000, 2900000, 3000000, 3300000
+};
+
+static const unsigned int ldo_volt_table8[] = {
+ 1300000, 1800000, 2900000, 3300000
+};
+
+static const unsigned int ldo_volt_table9[] = {
+ 1200000, 1300000, 1500000, 1800000, 2000000, 2800000, 3000000, 3300000
+};
+
+static const unsigned int ldo_volt_table10[] = {
+ 1200000, 1300000, 1500000, 1800000, 2500000, 2800000, 3000000, 3300000
+};
+
+static const unsigned int ldo_volt_table11[] = {
+ 900000, 1000000, 1100000, 1200000, 1300000, 1500000
+};
+
+static const unsigned int ldo_volt_table12[] = {
+ 1200000, 1300000, 1500000, 1800000, 1825000
+};
+
+static const unsigned int ldo_volt_table13[] = {
+ 1200000, 1300000, 1500000, 1800000
+};
+
+static const unsigned int ldo_volt_table14[] = {
+ 1240000, 1390000, 1540000
+};
+
+static const struct linear_range ldo_volt_range1[] = {
+ REGULATOR_LINEAR_RANGE(700000, 0, 0x7f, 6250)
+};
+
+static int mt6328_get_status(struct regulator_dev *rdev)
+{
+ int ret;
+ u32 regval;
+ struct mt6328_regulator_info *info = rdev_get_drvdata(rdev);
+
+ ret = regmap_read(rdev->regmap, info->desc.enable_reg, ®val);
+ if (ret != 0) {
+ dev_err(&rdev->dev, "Failed to get enable reg: %d\n", ret);
+ return ret;
+ }
+
+ return (regval & info->qi) ? REGULATOR_STATUS_ON : REGULATOR_STATUS_OFF;
+}
+
+static int mt6328_ldo_table_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+ int ret, val = 0;
+ struct mt6328_regulator_info *info = rdev_get_drvdata(rdev);
+
+ switch (mode) {
+ case REGULATOR_MODE_STANDBY:
+ val = MT6328_LDO_TABLE_MODE_LP;
+ break;
+ case REGULATOR_MODE_NORMAL:
+ val = MT6328_LDO_TABLE_MODE_NORMAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val <<= ffs(info->modeset_mask) - 1;
+
+ ret = regmap_update_bits(rdev->regmap, info->modeset_reg,
+ info->modeset_mask, val);
+
+ return ret;
+}
+
+static unsigned int mt6328_ldo_table_get_mode(struct regulator_dev *rdev)
+{
+ unsigned int val;
+ unsigned int mode;
+ int ret;
+ struct mt6328_regulator_info *info = rdev_get_drvdata(rdev);
+
+ ret = regmap_read(rdev->regmap, info->modeset_reg, &val);
+ if (ret < 0)
+ return ret;
+
+ val &= info->modeset_mask;
+ val >>= ffs(info->modeset_mask) - 1;
+
+ if (val & 0x1)
+ mode = REGULATOR_MODE_STANDBY;
+ else
+ mode = REGULATOR_MODE_NORMAL;
+
+ return mode;
+}
+
+static const struct regulator_ops mt6328_volt_range_ops = {
+ .list_voltage = regulator_list_voltage_linear_range,
+ .map_voltage = regulator_map_voltage_linear_range,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_voltage_time_sel = regulator_set_voltage_time_sel,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .get_status = mt6328_get_status,
+};
+
+static const struct regulator_ops mt6328_volt_table_ops = {
+ .list_voltage = regulator_list_voltage_table,
+ .map_voltage = regulator_map_voltage_iterate,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_voltage_time_sel = regulator_set_voltage_time_sel,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .get_status = mt6328_get_status,
+ .set_mode = mt6328_ldo_table_set_mode,
+ .get_mode = mt6328_ldo_table_get_mode,
+};
+
+static const struct regulator_ops mt6328_volt_table_single_mode_ops = {
+ .list_voltage = regulator_list_voltage_table,
+ .map_voltage = regulator_map_voltage_iterate,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_voltage_time_sel = regulator_set_voltage_time_sel,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .get_status = mt6328_get_status,
+};
+
+static const struct regulator_ops mt6328_volt_fixed_ops = {
+ .list_voltage = regulator_list_voltage_linear,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .get_status = mt6328_get_status,
+ .set_mode = mt6328_ldo_table_set_mode,
+ .get_mode = mt6328_ldo_table_get_mode,
+};
+
+/* The array is indexed by id(MT6328_ID_XXX) */
+static struct mt6328_regulator_info mt6328_regulators[] = {
+ MT6328_BUCK("buck_vpa", VPA, 500000, 3650000, 50000, buck_volt_range1, MT6328_VPA_CON9,
+ MT6328_VPA_CON11, 0x3f, MT6328_VPA_CON12, MT6328_VPA_CON7),
+ MT6328_BUCK("buck_vproc", VPROC, 600000, 1393750, 6250, buck_volt_range2, MT6328_VPROC_CON9,
+ MT6328_VPA_CON11, 0x7f, MT6328_VPROC_CON12, MT6328_VPROC_CON7),
+ MT6328_BUCK("buck_vcore1", VCORE1, 600000, 1393750, 6250, buck_volt_range2,
+ MT6328_VCORE1_CON9, MT6328_VCORE1_CON11, 0x7f, MT6328_VCORE1_CON12,
+ MT6328_VCORE1_CON7),
+ MT6328_BUCK("buck_vsys22", VSYS22, 1200000, 1993750, 6250, buck_volt_range3,
+ MT6328_VSYS22_CON9, MT6328_VSYS22_CON11, 0x7f, MT6328_VSYS22_CON12,
+ MT6328_VSYS22_CON7),
+ MT6328_BUCK("buck_vlte", VLTE, 600000, 1393750, 6250, buck_volt_range2, MT6328_VLTE_CON9,
+ MT6328_VLTE_CON11, 0x7f, MT6328_VLTE_CON12, MT6328_VLTE_CON7),
+ MT6328_REG_FIXED("ldo_vaux18", VAUX18, MT6328_VAUX18_CON0, 1, 1800000, MT6328_VAUX18_CON0,
+ BIT(0)),
+ MT6328_REG_FIXED("ldo_vtcxo_0", VTCXO_0, MT6328_VTCXO_0_CON0, 1, 2800000,
+ MT6328_VTCXO_0_CON0, BIT(0)),
+ MT6328_REG_FIXED("ldo_vtcxo_1", VTCXO_1, MT6328_VTCXO_1_CON0, 1, 2800000,
+ MT6328_VTCXO_1_CON0, BIT(0)),
+ MT6328_REG_FIXED("ldo_vaud28", VAUD28, MT6328_VAUD28_CON0, 1, 2800000, MT6328_VAUD28_CON0,
+ BIT(0)),
+ MT6328_REG_FIXED("ldo_vcn28", VCN28, MT6328_VCN28_CON0, 1, 2800000, MT6328_VCN28_CON0,
+ BIT(0)),
+ MT6328_LDO_TABLE_SINGLE_MODE("ldo_vcama", VCAMA, ldo_volt_table1, MT6328_VCAMA_CON0, 1,
+ MT6328_ADLDO_ANA_CON5, 0x30),
+ MT6328_LDO_TABLE("ldo_vcn33_bt", VCN33_BT, ldo_volt_table2, MT6328_VCN33_CON2, 1,
+ MT6328_ADLDO_ANA_CON6, 0x70, MT6328_VCN33_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vcn33_wifi", VCN33_WIFI, ldo_volt_table2, MT6328_VCN33_CON1, 1,
+ MT6328_ADLDO_ANA_CON6, 0x70, MT6328_VCN33_CON0, BIT(0)),
+ MT6328_REG_FIXED("ldo_vusb33", VUSB33, MT6328_VUSB33_CON0, 1, 3300000, MT6328_VUSB33_CON0,
+ BIT(0)),
+ MT6328_LDO_TABLE("ldo_vefuse", VEFUSE, ldo_volt_table3, MT6328_VEFUSE_CON0, 1,
+ MT6328_ADLDO_ANA_CON8, 0x70, MT6328_VEFUSE_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vsim1", VSIM1, ldo_volt_table4, MT6328_VSIM1_CON0, 1,
+ MT6328_ADLDO_ANA_CON9, 0x70, MT6328_VSIM1_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vsim2", VSIM2, ldo_volt_table4, MT6328_VSIM2_CON0, 1,
+ MT6328_ADLDO_ANA_CON10, 0x70, MT6328_VSIM2_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vemc_3v3", VEMC_3V3, ldo_volt_table5, MT6328_VEMC_3V3_CON0, 1,
+ MT6328_ADLDO_ANA_CON11, 0x30, MT6328_VEMC_3V3_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vmch", VMCH, ldo_volt_table6, MT6328_VMCH_CON0, 1,
+ MT6328_ADLDO_ANA_CON12, 0x30, MT6328_VMCH_CON0, BIT(0)),
+ MT6328_REG_FIXED("ldo_vtref", VTREF, MT6328_VTREF_CON0, 1, 1800000, 0, 0),
+ MT6328_LDO_TABLE("ldo_vmc", VMC, ldo_volt_table5, MT6328_VMC_CON0, 1, MT6328_DLDO_ANA_CON0,
+ 0x30, MT6328_VMC_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vcamaf", VCAMAF, ldo_volt_table9, MT6328_VCAMAF_CON0, 1,
+ MT6328_DLDO_ANA_CON1, 0x70, MT6328_VCAMAF_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vibr", VIBR, ldo_volt_table9, MT6328_VIBR_CON0, 1,
+ MT6328_DLDO_ANA_CON2, 0x70, MT6328_VIBR_CON0, BIT(0)),
+ MT6328_REG_FIXED("ldo_vio28", VIO28, MT6328_VIO28_CON0, 1, 2800000, MT6328_VIO28_CON0,
+ BIT(0)),
+ MT6328_LDO_TABLE("ldo_vgp1", VGP1, ldo_volt_table10, MT6328_VGP1_CON0, 1,
+ MT6328_DLDO_ANA_CON4, 0x70, MT6328_VGP1_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vcamd", VCAMD, ldo_volt_table11, MT6328_VCAM_CON0, 1,
+ MT6328_SLDO_ANA_CON2, 0x70, MT6328_VCAM_CON0, BIT(0)),
+ MT6328_REG_FIXED("ldo_vrf18_0", VRF18_0, MT6328_VRF18_0_CON0, 1, 1825000,
+ MT6328_VRF18_0_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vrf18_1", VRF18_1, ldo_volt_table12, MT6328_VRF18_1_CON0, 1,
+ MT6328_SLDO_ANA_CON4, 0x30, MT6328_VRF18_1_CON0, BIT(0)),
+ MT6328_REG_FIXED("ldo_vio18", VIO18, MT6328_VIO18_CON0, 1, 1800000, MT6328_VIO18_CON0,
+ BIT(0)),
+ MT6328_REG_FIXED("ldo_vcn18", VCN18, MT6328_VCN18_CON0, 1, 1800000, MT6328_VCN18_CON0,
+ BIT(0)),
+ MT6328_LDO_TABLE("ldo_vcamio", VCAMIO, ldo_volt_table13, MT6328_VCAMIO_CON0, 1,
+ MT6328_SLDO_ANA_CON7, 0x30, MT6328_VCAMIO_CON0, BIT(0)),
+ MT6328_LDO_TABLE("ldo_vcamio", VCAMIO, ldo_volt_table13, MT6328_VCAMIO_CON0, 1,
+ MT6328_SLDO_ANA_CON7, 0x30, MT6328_VCAMIO_CON0, BIT(0)),
+ MT6328_LDO_RANGE("ldo_vsram", VSRAM, 700000, 1493750, 6250, ldo_volt_range1,
+ MT6328_VSRAM_CON9, 1, MT6328_SLDO_ANA_CON9, 0x7f, MT6328_LDO_VSRAM_CON0,
+ BIT(0)),
+ MT6328_LDO_TABLE("ldo_vm", VM, ldo_volt_table14, MT6328_VM_CON0, 1, MT6328_SLDO_ANA_CON0,
+ 0x3, MT6328_VM_CON0, BIT(0)),
+};
+
+static int mt6328_set_buck_vosel_reg(struct platform_device *pdev)
+{
+ struct mt6397_chip *mt6328 = dev_get_drvdata(pdev->dev.parent);
+ int i;
+ u32 regval;
+
+ for (i = 0; i < MT6328_MAX_REGULATOR; i++) {
+ if (mt6328_regulators[i].vselctrl_reg) {
+ if (regmap_read(mt6328->regmap,
+ mt6328_regulators[i].vselctrl_reg,
+ ®val) < 0) {
+ dev_err(&pdev->dev,
+ "Failed to read buck ctrl\n");
+ return -EIO;
+ }
+
+ if (regval & mt6328_regulators[i].vselctrl_mask) {
+ mt6328_regulators[i].desc.vsel_reg =
+ mt6328_regulators[i].vselon_reg;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mt6328_regulator_probe(struct platform_device *pdev)
+{
+ struct mt6397_chip *mt6328 = dev_get_drvdata(pdev->dev.parent);
+ struct regulator_config config = {};
+ struct regulator_dev *rdev;
+ int i;
+
+ /* Query buck controller to select activated voltage register part */
+ if (mt6328_set_buck_vosel_reg(pdev))
+ return -EIO;
+
+ for (i = 0; i < MT6328_MAX_REGULATOR; i++) {
+ config.dev = &pdev->dev;
+ config.driver_data = &mt6328_regulators[i];
+ config.regmap = mt6328->regmap;
+ rdev = devm_regulator_register(&pdev->dev,
+ &mt6328_regulators[i].desc, &config);
+ if (IS_ERR(rdev)) {
+ dev_err(&pdev->dev, "failed to register %s\n",
+ mt6328_regulators[i].desc.name);
+ return PTR_ERR(rdev);
+ }
+ }
+ return 0;
+}
+
+static const struct platform_device_id mt6328_platform_ids[] = {
+ { "mt6328-regulator" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, mt6328_platform_ids);
+
+static struct platform_driver mt6328_regulator_driver = {
+ .driver = {
+ .name = "mt6328-regulator",
+ },
+ .probe = mt6328_regulator_probe,
+ .id_table = mt6328_platform_ids,
+};
+
+module_platform_driver(mt6328_regulator_driver);
+
+MODULE_AUTHOR("Yassine Oudjana <y.oudjana@protonmail.com>");
+MODULE_DESCRIPTION("Regulator Driver for MediaTek MT6328 PMIC");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/regulator/mt6328-regulator.h b/include/linux/regulator/mt6328-regulator.h
new file mode 100644
index 000000000000..0f82aa88493b
--- /dev/null
+++ b/include/linux/regulator/mt6328-regulator.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2016 MediaTek Inc.
+ * Copyright (c) 2022 Yassine Oudjana <y.oudjana@protonmail.com>
+ */
+
+#ifndef __LINUX_REGULATOR_MT6328_H
+#define __LINUX_REGULATOR_MT6328_H
+
+enum {
+ MT6328_ID_VPA = 0,
+ MT6328_ID_VPROC,
+ MT6328_ID_VCORE1,
+ MT6328_ID_VSYS22,
+ MT6328_ID_VLTE,
+ MT6328_ID_VAUX18,
+ MT6328_ID_VTCXO_0,
+ MT6328_ID_VTCXO_1,
+ MT6328_ID_VAUD28,
+ MT6328_ID_VCN28,
+ MT6328_ID_VCAMA,
+ MT6328_ID_VCN33_BT,
+ MT6328_ID_VCN33_WIFI,
+ MT6328_ID_VUSB33,
+ MT6328_ID_VEFUSE,
+ MT6328_ID_VSIM1,
+ MT6328_ID_VSIM2,
+ MT6328_ID_VEMC_3V3,
+ MT6328_ID_VMCH,
+ MT6328_ID_VTREF,
+ MT6328_ID_VMC,
+ MT6328_ID_VCAMAF,
+ MT6328_ID_VIBR,
+ MT6328_ID_VIO28,
+ MT6328_ID_VGP1,
+ MT6328_ID_VCAMD,
+ MT6328_ID_VRF18_0,
+ MT6328_ID_VRF18_1,
+ MT6328_ID_VIO18,
+ MT6328_ID_VCN18,
+ MT6328_ID_VCAMIO,
+ MT6328_ID_VSRAM,
+ MT6328_ID_VM,
+ MT6328_ID_RG_MAX,
+};
+
+#define MT6328_MAX_REGULATOR MT6328_ID_RG_MAX
+
+#endif /* __LINUX_REGULATOR_MT6328_H */
--
2.54.0
^ permalink raw reply related
* [PATCH v2 4/4] Input: mtk-pmic-keys - Add support for MT6328
From: Yassine Oudjana via B4 Relay @ 2026-05-31 9:10 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Flora Fu, Alexandre Mergnat,
Liam Girdwood, Mark Brown, Dmitry Torokhov
Cc: Yassine Oudjana, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-input
In-Reply-To: <20260531-mt6735-pwrap-mt6328-pmic-v2-0-dac37bf92894@protonmail.com>
From: Yassine Oudjana <y.oudjana@protonmail.com>
Add a compatible string and related data for the PMIC keys on the
MT6328 PMIC.
Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
---
drivers/input/keyboard/mtk-pmic-keys.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/input/keyboard/mtk-pmic-keys.c b/drivers/input/keyboard/mtk-pmic-keys.c
index c78d9f6d97c4..6aaca67e7703 100644
--- a/drivers/input/keyboard/mtk-pmic-keys.c
+++ b/drivers/input/keyboard/mtk-pmic-keys.c
@@ -9,6 +9,7 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/mt6323/registers.h>
+#include <linux/mfd/mt6328/registers.h>
#include <linux/mfd/mt6331/registers.h>
#include <linux/mfd/mt6357/registers.h>
#include <linux/mfd/mt6358/registers.h>
@@ -80,6 +81,17 @@ static const struct mtk_pmic_regs mt6323_regs = {
.rst_lprst_mask = MTK_PMIC_RST_DU_MASK,
};
+static const struct mtk_pmic_regs mt6328_regs = {
+ .keys_regs[MTK_PMIC_PWRKEY_INDEX] =
+ MTK_PMIC_KEYS_REGS(MT6328_TOPSTATUS,
+ 0x2, MT6328_INT_MISC_CON, 0x4, MTK_PMIC_PWRKEY_RST),
+ .keys_regs[MTK_PMIC_HOMEKEY_INDEX] =
+ MTK_PMIC_KEYS_REGS(MT6328_TOPSTATUS,
+ 0x4, MT6328_INT_MISC_CON, 0x2, MTK_PMIC_HOMEKEY_RST),
+ .pmic_rst_reg = MT6328_TOP_RST_MISC,
+ .rst_lprst_mask = MTK_PMIC_RST_DU_MASK,
+};
+
static const struct mtk_pmic_regs mt6331_regs = {
.keys_regs[MTK_PMIC_PWRKEY_INDEX] =
MTK_PMIC_KEYS_REGS(MT6331_TOPSTATUS, 0x2,
@@ -304,6 +316,9 @@ static const struct of_device_id of_mtk_pmic_keys_match_tbl[] = {
}, {
.compatible = "mediatek,mt6323-keys",
.data = &mt6323_regs,
+ }, {
+ .compatible = "mediatek,mt6328-keys",
+ .data = &mt6328_regs,
}, {
.compatible = "mediatek,mt6331-keys",
.data = &mt6331_regs,
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v2 0/4] MediaTek MT6735+MT6328 SoC/PMIC pair base support
From: Yassine Oudjana @ 2026-05-31 9:16 UTC (permalink / raw)
To: y.oudjana
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Flora Fu, Alexandre Mergnat,
Liam Girdwood, Mark Brown, Dmitry Torokhov, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, linux-input
In-Reply-To: <20260531-mt6735-pwrap-mt6328-pmic-v2-0-dac37bf92894@protonmail.com>
On Sunday, May 31st, 2026 at 11:10 AM, Yassine Oudjana via B4 Relay <devnull+y.oudjana.protonmail.com@kernel.org> wrote:
> These patches are part of a larger effort to support the MT6735 SoC family in
> mainline Linux. More patches (unsent or sent and pending review or revision)
> can be found here[1].
>
> This series adds base support for the MediaTek MT6735 SoC and MT6328 PMIC pair.
> This includes PMIC wrapper support on the SoC side and regulators and keys on
> the PMIC side. The PMIC has other blocks such as an audio codec and battery
> charger which can be supported in the future.
>
> Changes since v1:
> - Fit regulator definitions in 2 lines and 100 characters whenever possible.
> - Remove get_mode and set_mode ops for regulators that don't support them.
Forgot to mention:
- Remove MD register definitions.
>
> [1] https://gitlab.com/mt6735-mainline/linux/-/commits/mt6735-staging
>
> Signed-off-by: Yassine Oudjana <y.oudjana@protonmail.com>
> ---
> Yassine Oudjana (4):
> dt-bindings: mediatek: pwrap: Add MT6735 compatible
> soc: mediatek: pwrap: Add support for MT6735 and MT6328 SoC/PMIC pair
> regulator: Add driver for MediaTek MT6328 PMIC regulators
> Input: mtk-pmic-keys - Add support for MT6328
>
> .../bindings/soc/mediatek/mediatek,pwrap.yaml | 1 +
> drivers/input/keyboard/mtk-pmic-keys.c | 15 +
> drivers/regulator/Kconfig | 9 +
> drivers/regulator/Makefile | 1 +
> drivers/regulator/mt6328-regulator.c | 500 +++++++++++++++++++++
> drivers/soc/mediatek/mtk-pmic-wrap.c | 177 +++++++-
> include/linux/regulator/mt6328-regulator.h | 49 ++
> 7 files changed, 749 insertions(+), 3 deletions(-)
> ---
> base-commit: 7da7f07112610a520567421dd2ffcb51beaefbcc
> change-id: 20260531-mt6735-pwrap-mt6328-pmic-de481d5c5e89
>
> Best regards,
> --
> Yassine Oudjana <y.oudjana@protonmail.com>
>
>
>
^ permalink raw reply
* Re: [PATCH v2 2/4] soc: mediatek: pwrap: Add support for MT6735 and MT6328 SoC/PMIC pair
From: sashiko-bot @ 2026-05-31 11:26 UTC (permalink / raw)
To: Yassine Oudjana via B4 Relay
Cc: conor+dt, devicetree, robh, dmitry.torokhov, linux-input
In-Reply-To: <20260531-mt6735-pwrap-mt6328-pmic-v2-2-dac37bf92894@protonmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] PMIC_MT6328 is missing from the slave cipher initialization switch statement, which will cause the driver probe to time out and fail.
- [Medium] Unsafe write to PMIC register 0x0 if MT6735 is paired with a PMIC lacking `PWRAP_DEW_RDDMY_NO`.
--
commit 21ea916ac0163eee8daf299b310759cc6d675f2a
Author: Yassine Oudjana <y.oudjana@protonmail.com>
soc: mediatek: pwrap: Add support for MT6735 and MT6328 SoC/PMIC pair
This commit adds register definitions and configuration for the MT6735 SoC
and the MT6328 PMIC to the MediaTek PMIC wrapper driver. It defines their
specific register layouts and capabilities to enable communication between
the two chips.
> diff --git a/drivers/soc/mediatek/mtk-pmic-wrap.c b/drivers/soc/mediatek/mtk-pmic-wrap.c
> index 0bcd858263755..0f14dca5609d1 100644
> --- a/drivers/soc/mediatek/mtk-pmic-wrap.c
> +++ b/drivers/soc/mediatek/mtk-pmic-wrap.c
[ ... ]
> @@ -1733,6 +1877,10 @@ static void pwrap_init_chip_select_ext(struct pmic_wrapper *wrp, u8 hext_write,
> static int pwrap_common_init_reg_clock(struct pmic_wrapper *wrp)
> {
> switch (wrp->master->type) {
> + case PWRAP_MT6735:
> + pwrap_write(wrp, wrp->slave->dew_regs[PWRAP_DEW_RDDMY_NO], 0x8);
[Severity: Medium]
Will this write to register 0x0 if MT6735 is paired with a PMIC that lacks
PWRAP_DEW_RDDMY_NO?
If MT6735 is paired with a PMIC like MT6397 that doesn't define this register
in its array, the read will return 0 and the driver will write 0x8 to PMIC
register 0x0. Should this verify the PMIC type first, similar to how it is
handled for MT6795 below?
> + pwrap_init_chip_select_ext(wrp, 0, 6, 0, 0);
> + break;
> case PWRAP_MT6795:
> if (wrp->slave->type == PMIC_MT6331) {
[ ... ]
> @@ -2194,6 +2343,14 @@ static const struct pwrap_slv_type pmic_mt6323 = {
> PWRAP_SLV_CAP_SECURITY,
> };
>
> +static const struct pwrap_slv_type pmic_mt6328 = {
> + .dew_regs = mt6328_regs,
> + .type = PMIC_MT6328,
> + .regops = &pwrap_regops16,
> + .caps = PWRAP_SLV_CAP_SPI | PWRAP_SLV_CAP_DUALIO |
> + PWRAP_SLV_CAP_SECURITY,
[Severity: High]
Does pwrap_init_cipher() need to be updated to handle PMIC_MT6328?
Since PMIC_MT6328 has PWRAP_SLV_CAP_SECURITY, pwrap_init_cipher() will run.
However, PMIC_MT6328 is missing from the switch (wrp->slave->type) block in
pwrap_init_cipher().
Without writing 1 to PWRAP_DEW_CIPHER_EN for this PMIC, the cipher engine
remains disabled, which will cause the subsequent readx_poll_timeout() for
pwrap_is_pmic_cipher_ready to time out and fail the driver probe.
> +};
> +
> static const struct pwrap_slv_type pmic_mt6331 = {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260531-mt6735-pwrap-mt6328-pmic-v2-0-dac37bf92894@protonmail.com?part=2
^ permalink raw reply
* Re: [PATCH v2 3/4] regulator: Add driver for MediaTek MT6328 PMIC regulators
From: sashiko-bot @ 2026-05-31 11:35 UTC (permalink / raw)
To: Yassine Oudjana via B4 Relay
Cc: devicetree, dmitry.torokhov, linux-input, robh, conor+dt
In-Reply-To: <20260531-mt6735-pwrap-mt6328-pmic-v2-3-dac37bf92894@protonmail.com>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] Undefined behavior (negative bitwise shift) in LDO mode accessors for fixed regulators.
- [High] Hardware logic error: `buck_vproc` is incorrectly assigned the voltage selection register for `VPA`.
- [High] Mismatch between voltage table size and hardware mask for `ldo_vrf18_1` leads to silent undervolting.
- [Medium] The driver destructively mutates the global static `mt6328_regulators` array during device probe.
- [Low] The `MT6328_LDO_RANGE` macro assigns mode registers but uses an ops struct lacking mode accessors.
- [Low] Duplicate definition of `ldo_vcamio` regulator in initialization array.
- [Low] Unused static voltage tables `ldo_volt_table7` and `ldo_volt_table8`.
--
commit 1a95db9b71950cc46e1f082052c7c441b06a008e
Author: Yassine Oudjana <y.oudjana@protonmail.com>
regulator: Add driver for MediaTek MT6328 PMIC regulators
This commit adds a new driver for the regulators found on the MT6328 PMIC.
It supports various bucks and LDOs using the regulator framework.
> diff --git a/drivers/regulator/mt6328-regulator.c b/drivers/regulator/mt6328-regulator.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/regulator/mt6328-regulator.c
[ ... ]
> +static const unsigned int ldo_volt_table6[] = {
> + 2900000, 3000000, 3300000
> +};
> +
> +static const unsigned int ldo_volt_table7[] = {
> + 2500000, 2900000, 3000000, 3300000
> +};
> +
> +static const unsigned int ldo_volt_table8[] = {
> + 1300000, 1800000, 2900000, 3300000
> +};
[Severity: Low]
Are these static voltage tables used anywhere in the driver? It looks like
ldo_volt_table7 and ldo_volt_table8 are defined here but are never
referenced in the mt6328_regulators array.
[ ... ]
> +static int mt6328_ldo_table_set_mode(struct regulator_dev *rdev, unsigned int mode)
> +{
> + int ret, val = 0;
> + struct mt6328_regulator_info *info = rdev_get_drvdata(rdev);
> +
> + switch (mode) {
> + case REGULATOR_MODE_STANDBY:
> + val = MT6328_LDO_TABLE_MODE_LP;
> + break;
> + case REGULATOR_MODE_NORMAL:
> + val = MT6328_LDO_TABLE_MODE_NORMAL;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + val <<= ffs(info->modeset_mask) - 1;
> +
> + ret = regmap_update_bits(rdev->regmap, info->modeset_reg,
> + info->modeset_mask, val);
[Severity: High]
Could this bitwise shift result in undefined behavior? If modeset_mask is 0,
ffs(0) evaluates to 0, resulting in a shift by -1.
I noticed ldo_vtref is initialized with a modeset_mask of 0 down in the
mt6328_regulators array, but it uses mt6328_volt_fixed_ops which exposes
both the set_mode and get_mode callbacks.
[ ... ]
> +static const struct regulator_ops mt6328_volt_range_ops = {
> + .list_voltage = regulator_list_voltage_linear_range,
> + .map_voltage = regulator_map_voltage_linear_range,
> + .set_voltage_sel = regulator_set_voltage_sel_regmap,
> + .get_voltage_sel = regulator_get_voltage_sel_regmap,
> + .set_voltage_time_sel = regulator_set_voltage_time_sel,
> + .enable = regulator_enable_regmap,
> + .disable = regulator_disable_regmap,
> + .is_enabled = regulator_is_enabled_regmap,
> + .get_status = mt6328_get_status,
> +};
[Severity: Low]
Should this operations structure include the .set_mode and .get_mode callbacks?
Down below, ldo_vsram is initialized using the MT6328_LDO_RANGE macro which
sets up modeset_reg and modeset_mask, but since it is mapped to
mt6328_volt_range_ops, the mode data will be silently ignored by the
regulator core.
[ ... ]
> +/* The array is indexed by id(MT6328_ID_XXX) */
> +static struct mt6328_regulator_info mt6328_regulators[] = {
> + MT6328_BUCK("buck_vpa", VPA, 500000, 3650000, 50000, buck_volt_range1, MT6328_VPA_CON9,
> + MT6328_VPA_CON11, 0x3f, MT6328_VPA_CON12, MT6328_VPA_CON7),
> + MT6328_BUCK("buck_vproc", VPROC, 600000, 1393750, 6250, buck_volt_range2, MT6328_VPROC_CON9,
> + MT6328_VPA_CON11, 0x7f, MT6328_VPROC_CON12, MT6328_VPROC_CON7),
[Severity: High]
Does buck_vproc use the wrong voltage selection register here?
It looks like MT6328_VPA_CON11 was accidentally copied from buck_vpa. This
would cause voltage scaling for the processor to write to the VPA's voltage
selection register instead, breaking voltage scaling for both regulators.
[ ... ]
> + MT6328_REG_FIXED("ldo_vrf18_0", VRF18_0, MT6328_VRF18_0_CON0, 1, 1825000,
> + MT6328_VRF18_0_CON0, BIT(0)),
> + MT6328_LDO_TABLE("ldo_vrf18_1", VRF18_1, ldo_volt_table12, MT6328_VRF18_1_CON0, 1,
> + MT6328_SLDO_ANA_CON4, 0x30, MT6328_VRF18_1_CON0, BIT(0)),
[Severity: High]
Does this 2-bit mask (0x30) conflict with the size of ldo_volt_table12?
That voltage table contains 5 entries, which requires at least a 3-bit mask.
If the core requests the highest voltage (1.825V, index 4), it will be masked
to 0 when updated, which could silently undervolt the regulator to 1.2V instead.
[ ... ]
> + MT6328_REG_FIXED("ldo_vcn18", VCN18, MT6328_VCN18_CON0, 1, 1800000, MT6328_VCN18_CON0,
> + BIT(0)),
> + MT6328_LDO_TABLE("ldo_vcamio", VCAMIO, ldo_volt_table13, MT6328_VCAMIO_CON0, 1,
> + MT6328_SLDO_ANA_CON7, 0x30, MT6328_VCAMIO_CON0, BIT(0)),
> + MT6328_LDO_TABLE("ldo_vcamio", VCAMIO, ldo_volt_table13, MT6328_VCAMIO_CON0, 1,
> + MT6328_SLDO_ANA_CON7, 0x30, MT6328_VCAMIO_CON0, BIT(0)),
[Severity: Low]
Is this duplicate definition of ldo_vcamio intentional? It appears this
entry was accidentally listed twice.
[ ... ]
> +static int mt6328_set_buck_vosel_reg(struct platform_device *pdev)
> +{
> + struct mt6397_chip *mt6328 = dev_get_drvdata(pdev->dev.parent);
> + int i;
> + u32 regval;
> +
> + for (i = 0; i < MT6328_MAX_REGULATOR; i++) {
> + if (mt6328_regulators[i].vselctrl_reg) {
> + if (regmap_read(mt6328->regmap,
> + mt6328_regulators[i].vselctrl_reg,
> + ®val) < 0) {
> + dev_err(&pdev->dev,
> + "Failed to read buck ctrl\n");
> + return -EIO;
> + }
> +
> + if (regval & mt6328_regulators[i].vselctrl_mask) {
> + mt6328_regulators[i].desc.vsel_reg =
> + mt6328_regulators[i].vselon_reg;
> + }
> + }
> + }
> +
> + return 0;
> +}
[Severity: Medium]
Does modifying mt6328_regulators[i].desc.vsel_reg permanently alter the
global static array?
If the driver is unbound and rebound later, and the hardware condition is no
longer met, the driver will incorrectly retain the mutated vsel_reg value
since there is no restore path for the original value.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260531-mt6735-pwrap-mt6328-pmic-v2-0-dac37bf92894@protonmail.com?part=3
^ permalink raw reply
* Re: [PATCH v5 3/3] HID: nintendo: Add unified report format support
From: Silvan Jegen @ 2026-05-31 17:57 UTC (permalink / raw)
To: sashiko-reviews; +Cc: Vicki Pfau, linux-input, dmitry.torokhov
In-Reply-To: <20260514010628.28F7CC19425@smtp.kernel.org>
sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] Insufficient bounds checking for NS2_REPORT_UNIFIED payload leads to out-of-bounds reads.
> - [High] Divide-by-zero kernel panic in switch2_report_trigger() caused by attacker-controlled calibration data.
> --
>
> commit 6e45b564fde8014fc6ade8452a1b793097b3bee4
> Author: Vicki Pfau <vi@endrift.com>
> Subject: HID: nintendo: Add unified report format support
>
> This commit adds support for the "unified" report format to the nintendo hid
> driver. It introduces new mappings for overlapping button and axis fields
> across different controller types.
>
> > diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> > index 9801799a90758..9e7eda7b70bfb 100644
> > --- a/drivers/hid/hid-nintendo.c
> > +++ b/drivers/hid/hid-nintendo.c
> [ ... ]
> > @@ -3690,11 +3791,48 @@ static int switch2_event(struct hid_device *hdev, struct hid_report *report, uin
> >
> > switch (report->id) {
> > case NS2_REPORT_UNIFIED:
> > - /*
> > - * TODO
> > - * This won't be sent unless the report type gets changed via command
> > - * 03-0A, but we should support it at some point regardless.
> > - */
> > + switch (ns2->ctlr_type) {
> > + case NS2_CTLR_TYPE_JCL:
> > + switch2_report_stick(input, &ns2->stick_calib[0],
> > + ABS_X, false, ABS_Y, true, &raw_data[11]);
> > + switch2_report_buttons(input, &raw_data[5],
> > + ns2_left_joycon_button_unified_mappings);
> > + break;
> > + case NS2_CTLR_TYPE_JCR:
> > + switch2_report_stick(input, &ns2->stick_calib[0],
> > + ABS_X, false, ABS_Y, true, &raw_data[14]);
>
> Is this safe if the report size is exactly 15 bytes?
>
> Earlier in switch2_event(), there is a global size check:
>
> if (size < 15)
> return -EINVAL;
>
> However, switch2_report_stick() reads up to 3 bytes from the provided data
> pointer (indices 0, 1, and 2). Passing &raw_data[14] means it will read
> raw_data[14], raw_data[15], and raw_data[16].
>
> Could this cause an out-of-bounds read if an undersized report is sent?
Yes, I assume we would have to check for a bigger size here.
>
> > + switch2_report_buttons(input, &raw_data[5],
> > + ns2_right_joycon_button_unified_mappings);
> > + break;
> > + case NS2_CTLR_TYPE_GC:
> > + input_report_abs(input, ABS_HAT0X,
> > + !!(raw_data[7] & NS2_BTN_U3_RIGHT) -
> > + !!(raw_data[7] & NS2_BTN_U3_LEFT));
> > + input_report_abs(input, ABS_HAT0Y,
> > + !!(raw_data[7] & NS2_BTN_U3_DOWN) -
> > + !!(raw_data[7] & NS2_BTN_U3_UP));
> > + switch2_report_buttons(input, &raw_data[5], ns2_gccon_unified_mappings);
> > + switch2_report_stick(input, &ns2->stick_calib[0],
> > + ABS_X, false, ABS_Y, true, &raw_data[11]);
> > + switch2_report_stick(input, &ns2->stick_calib[1],
> > + ABS_RX, false, ABS_RY, true, &raw_data[14]);
> > + switch2_report_trigger(input, ns2->lt_zero, ABS_Z, raw_data[0x3d]);
> > + switch2_report_trigger(input, ns2->rt_zero, ABS_RZ, raw_data[0x3e]);
>
> Similarly, does this code read past the end of the buffer?
>
> Accessing raw_data[0x3d] (index 61) and raw_data[0x3e] (index 62) requires
> the report size to be at least 63 bytes, but the global check only guarantees
> 15 bytes.
Same here.
>
> Additionally, could this new path trigger a divide-by-zero panic?
>
> While this issue pre-dates this commit, switch2_report_trigger() contains the
> following calculation:
>
> int value = (NS2_TRIGGER_RANGE + 1) * (data - zero) / (232 - zero);
>
> The zero parameter here comes from the device's factory calibration data
> (ns2->lt_zero and ns2->rt_zero). If a device advertises 232 as its zero
> calibration point, this division evaluates to 0.
>
> Since this patch exposes a new way to call switch2_report_trigger(), might it
> be worth adding validation to prevent a zero divisor?
This one has been reported already in one of the earlier emails.
Cheers,
Silvan
>
> > + break;
> > + case NS2_CTLR_TYPE_PRO:
> [ ... ]
^ permalink raw reply
* [dtor-input:next] BUILD SUCCESS 332fbc03e1a8cb4c0ce6891cae2c286ddf031aca
From: kernel test robot @ 2026-05-31 18:42 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
branch HEAD: 332fbc03e1a8cb4c0ce6891cae2c286ddf031aca Input: iqs5xx - drop unused i2c driver_data
elapsed time: 800m
configs tested: 252
configs skipped: 43
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-19
arc allyesconfig clang-23
arc allyesconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc randconfig-001 gcc-8.5.0
arc randconfig-001-20260531 gcc-11.5.0
arc randconfig-001-20260531 gcc-8.5.0
arc randconfig-002 gcc-8.5.0
arc randconfig-002-20260531 gcc-11.5.0
arc randconfig-002-20260531 gcc-8.5.0
arm allnoconfig clang-23
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm allyesconfig gcc-15.2.0
arm collie_defconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm hisi_defconfig gcc-15.2.0
arm randconfig-001 gcc-8.5.0
arm randconfig-001-20260531 gcc-11.5.0
arm randconfig-002 gcc-8.5.0
arm randconfig-002-20260531 gcc-11.5.0
arm randconfig-003 gcc-8.5.0
arm randconfig-003-20260531 gcc-11.5.0
arm randconfig-003-20260531 gcc-8.5.0
arm randconfig-004 gcc-8.5.0
arm randconfig-004-20260531 gcc-11.5.0
arm randconfig-004-20260531 gcc-8.5.0
arm64 allmodconfig clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260531 clang-23
arm64 randconfig-001-20260531 gcc-13.4.0
arm64 randconfig-002-20260531 clang-23
arm64 randconfig-002-20260531 gcc-13.4.0
arm64 randconfig-003-20260531 clang-23
arm64 randconfig-003-20260531 gcc-13.4.0
arm64 randconfig-004-20260531 clang-23
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260531 clang-23
csky randconfig-001-20260531 gcc-13.4.0
csky randconfig-002-20260531 clang-23
csky randconfig-002-20260531 gcc-13.4.0
hexagon allmodconfig clang-17
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig clang-23
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260531 clang-23
hexagon randconfig-001-20260601 gcc-8.5.0
hexagon randconfig-002-20260531 clang-23
hexagon randconfig-002-20260601 gcc-8.5.0
i386 allmodconfig clang-20
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 allyesconfig gcc-14
i386 buildonly-randconfig-001-20260531 clang-20
i386 buildonly-randconfig-002-20260531 clang-20
i386 buildonly-randconfig-003-20260531 clang-20
i386 buildonly-randconfig-003-20260531 gcc-13
i386 buildonly-randconfig-003-20260531 gcc-14
i386 buildonly-randconfig-004-20260531 clang-20
i386 buildonly-randconfig-005-20260531 clang-20
i386 buildonly-randconfig-005-20260531 gcc-14
i386 buildonly-randconfig-006-20260531 clang-20
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260531 clang-20
i386 randconfig-001-20260531 gcc-13
i386 randconfig-002-20260531 clang-20
i386 randconfig-002-20260531 gcc-13
i386 randconfig-003-20260531 clang-20
i386 randconfig-003-20260531 gcc-13
i386 randconfig-004-20260531 gcc-13
i386 randconfig-005-20260531 clang-20
i386 randconfig-005-20260531 gcc-13
i386 randconfig-006-20260531 gcc-13
i386 randconfig-006-20260531 gcc-14
i386 randconfig-007-20260531 clang-20
i386 randconfig-007-20260531 gcc-13
i386 randconfig-011-20260531 gcc-14
i386 randconfig-012-20260531 gcc-14
i386 randconfig-013-20260531 gcc-14
i386 randconfig-014-20260531 clang-20
i386 randconfig-014-20260531 gcc-14
i386 randconfig-015-20260531 gcc-14
i386 randconfig-016-20260531 gcc-14
i386 randconfig-017-20260531 gcc-14
loongarch allmodconfig clang-19
loongarch allmodconfig clang-23
loongarch allnoconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260531 clang-23
loongarch randconfig-001-20260601 gcc-8.5.0
loongarch randconfig-002-20260531 clang-23
loongarch randconfig-002-20260601 gcc-8.5.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k allyesconfig gcc-15.2.0
m68k defconfig clang-19
m68k defconfig gcc-15.2.0
microblaze alldefconfig gcc-15.2.0
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
microblaze defconfig gcc-15.2.0
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 defconfig gcc-15.2.0
nios2 randconfig-001-20260531 clang-23
nios2 randconfig-001-20260601 gcc-8.5.0
nios2 randconfig-002-20260531 clang-23
nios2 randconfig-002-20260601 gcc-8.5.0
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
openrisc virt_defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc allyesconfig gcc-15.2.0
parisc defconfig gcc-15.2.0
parisc randconfig-001 clang-23
parisc randconfig-001-20260531 clang-23
parisc randconfig-001-20260531 gcc-8.5.0
parisc randconfig-002 clang-23
parisc randconfig-002-20260531 clang-23
parisc randconfig-002-20260531 gcc-12.5.0
parisc64 defconfig clang-19
parisc64 defconfig gcc-15.2.0
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc randconfig-001 clang-23
powerpc randconfig-001-20260531 clang-17
powerpc randconfig-001-20260531 clang-23
powerpc randconfig-002 clang-23
powerpc randconfig-002-20260531 clang-23
powerpc redwood_defconfig clang-23
powerpc tqm8548_defconfig clang-23
powerpc64 randconfig-001 clang-23
powerpc64 randconfig-001-20260531 clang-23
powerpc64 randconfig-002 clang-23
powerpc64 randconfig-002-20260531 clang-23
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260531 gcc-8.5.0
riscv randconfig-002-20260531 gcc-8.5.0
s390 allmodconfig clang-18
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260531 gcc-8.5.0
s390 randconfig-002-20260531 gcc-8.5.0
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh allyesconfig gcc-15.2.0
sh defconfig gcc-14
sh randconfig-001-20260531 gcc-8.5.0
sh randconfig-002-20260531 gcc-8.5.0
sh sh2007_defconfig gcc-15.2.0
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260531 gcc-8.5.0
sparc randconfig-002-20260531 gcc-8.5.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260531 gcc-8.5.0
sparc64 randconfig-002-20260531 gcc-8.5.0
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-14
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260531 gcc-8.5.0
um randconfig-002-20260531 gcc-8.5.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260531 clang-20
x86_64 buildonly-randconfig-001-20260531 gcc-13
x86_64 buildonly-randconfig-002-20260531 clang-20
x86_64 buildonly-randconfig-003-20260531 clang-20
x86_64 buildonly-randconfig-004-20260531 clang-20
x86_64 buildonly-randconfig-005-20260531 clang-20
x86_64 buildonly-randconfig-006-20260531 clang-20
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260531 gcc-14
x86_64 randconfig-002-20260531 gcc-14
x86_64 randconfig-003-20260531 gcc-14
x86_64 randconfig-004-20260531 gcc-14
x86_64 randconfig-005-20260531 gcc-14
x86_64 randconfig-006-20260531 gcc-14
x86_64 randconfig-011-20260531 gcc-14
x86_64 randconfig-012-20260531 gcc-14
x86_64 randconfig-013-20260531 gcc-14
x86_64 randconfig-014-20260531 gcc-14
x86_64 randconfig-015-20260531 gcc-14
x86_64 randconfig-016-20260531 gcc-14
x86_64 randconfig-071-20260531 clang-20
x86_64 randconfig-072-20260531 clang-20
x86_64 randconfig-072-20260531 gcc-14
x86_64 randconfig-073-20260531 clang-20
x86_64 randconfig-074-20260531 clang-20
x86_64 randconfig-075-20260531 clang-20
x86_64 randconfig-075-20260531 gcc-14
x86_64 randconfig-076-20260531 clang-20
x86_64 randconfig-076-20260531 gcc-13
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4 gcc-14
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-func gcc-14
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kselftests gcc-14
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
xtensa randconfig-001-20260531 gcc-8.5.0
xtensa randconfig-002-20260531 gcc-8.5.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [git pull] Input updates for v7.1-rc5
From: pr-tracker-bot @ 2026-05-31 21:13 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Linus Torvalds, linux-kernel, linux-input
In-Reply-To: <ahvBdJ_vd3xuf-Rd@google.com>
The pull request you sent on Sat, 30 May 2026 22:09:55 -0700:
> git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v7.1-rc5
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/227abb780c68687d6800cf9eafaff0cbeca4bf45
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply
* [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: David Glushkov @ 2026-05-31 21:52 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
Limit binding to USB interface 0 and the tested DMI system because the
KLC product ID is shared across MSI laptop designs and the key layout
mapping is model-specific.
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 176 +++++++++++++++++++++++++++++++++-
2 files changed, 176 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 426ff78..83599bd 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1368,6 +1368,8 @@
#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_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f984356..0492814 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,23 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +41,13 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -528,6 +542,140 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ return interface_to_usbdev(to_usb_interface(hdev->dev.parent));
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ return to_usb_interface(hdev->dev.parent) ==
+ usb_ifnum_to_if(steelseries_hid_to_usb_dev(hdev), 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev, struct steelseries_device, mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, 524);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, 524, USB_CTRL_SET_TIMEOUT);
+
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+
+ sd->rgb_buf = devm_kzalloc(&hdev->dev, 524, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ return devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +697,12 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev))
+ return -ENODEV;
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -567,7 +721,15 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
if (ret)
return ret;
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret)
+ hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
+ return ret;
+ }
+
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
@@ -635,7 +797,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
unsigned long flags;
/* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!(sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)))
return 0;
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
@@ -732,6 +894,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = 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),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: sashiko-bot @ 2026-05-31 22:06 UTC (permalink / raw)
To: David Glushkov; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260531215204.172030-1-david.glushkov@sntiq.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Type Confusion / Out-of-Bounds Read due to unsafe cast to `usb_interface`
- [High] `steelseries_remove()` unconditionally calls `cancel_delayed_work_sync()` on an uninitialized `delayed_work` struct.
- [Medium] Missing `hid_hw_stop()` and `hid_hw_close()` on probe error path.
- [Medium] Missing runtime PM management during direct USB control transfer, breaking LED updates when the device is idle.
--
commit 827dd1dcdf0039cad78b73365e7fd12bdc59d84e
Author: David Glushkov <david.glushkov@sntiq.com>
HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
This patch adds support for MSI Raider A18 HX A9WJG internal RGB devices
(KLC and ALC) to the steelseries HID driver. It exposes them as multicolor
LED class devices and sets uniform RGB values across zones.
> diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
> index f98435631aa18..0492814791753 100644
> --- a/drivers/hid/hid-steelseries.c
> +++ b/drivers/hid/hid-steelseries.c
[ ... ]
> @@ -528,6 +542,140 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
> hdev->rdesc[2] == 0xff;
> }
>
> +static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
> + {
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
> + DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
> + DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
> + },
> + },
> + { }
> +};
> +
> +static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
> +{
> + return interface_to_usbdev(to_usb_interface(hdev->dev.parent));
> +}
[Severity: High]
Could this cause an out-of-bounds read if the device is not a USB device?
If a local user injects a virtual HID device with the MSI RGB vendor and
product IDs via the uhid subsystem, the device's parent will be the uhid
device, not a usb_interface.
Calling to_usb_interface() and interface_to_usbdev() without first verifying
the transport using hid_is_usb(hdev) could interpret the uhid parent memory
as a usb_interface, leading to wild pointer dereferences and a panic.
> +
> +static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
> +{
> + return to_usb_interface(hdev->dev.parent) ==
> + usb_ifnum_to_if(steelseries_hid_to_usb_dev(hdev), 0);
> +}
> +
> +#if STEELSERIES_HAS_LEDS_MULTICOLOR
> +
> +static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
> + enum led_brightness brightness)
> +{
[ ... ]
> + if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
> + sd->rgb_buf[2] = 0x66;
> + for (i = 0; i < ARRAY_SIZE(keys); i++) {
> + sd->rgb_buf[4 + i * 4] = keys[i];
> + sd->rgb_buf[5 + i * 4] = r;
> + sd->rgb_buf[6 + i * 4] = g;
> + sd->rgb_buf[7 + i * 4] = b;
> + }
> + } else {
> + sd->rgb_buf[2] = 0x06;
> + for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
> + sd->rgb_buf[4 + i * 4] = alc_zones[i];
> + sd->rgb_buf[5 + i * 4] = r;
> + sd->rgb_buf[6 + i * 4] = g;
> + sd->rgb_buf[7 + i * 4] = b;
> + }
> + }
> +
> + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
> + HID_REQ_SET_REPORT,
> + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
> + 0x0300, 0,
> + sd->rgb_buf, 524, USB_CTRL_SET_TIMEOUT);
[Severity: Medium]
Will this USB control message fail if the device has entered runtime suspend?
Because this directly uses usb_control_msg() and bypasses the HID transport
layer, it doesn't appear to wake the device. Without explicitly waking the
device via hid_hw_power(hdev, PM_HINT_FULLON) or usb_autopm_get_interface(),
the transfer might drop LED updates when the keyboard is idle.
> +
> + mutex_unlock(&sd->rgb_lock);
> + return ret < 0 ? ret : 0;
> +}
[ ... ]
> @@ -567,7 +721,15 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
> if (ret)
> return ret;
>
> - if (steelseries_headset_battery_register(sd) < 0)
> + if (sd->quirks & STEELSERIES_MSI_RGB) {
> + ret = steelseries_msi_rgb_register(sd);
> + if (ret)
> + hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
> + return ret;
> + }
[Severity: Medium]
Does this error path leak the hardware state initialized by hid_hw_start()
and hid_hw_open() called earlier in steelseries_probe()?
Returning directly here skips calling hid_hw_stop() and hid_hw_close(),
which could leave active URBs and orphaned hidinput/hidraw device nodes
exposed to userspace.
> +
> + if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
> + steelseries_headset_battery_register(sd) < 0)
> hid_err(sd->hdev,
> "Failed to register battery for headset\n");
[Severity: High]
Since steelseries_headset_battery_register() and INIT_DELAYED_WORK() are
skipped for MSI RGB devices here, what happens when the device is removed?
If steelseries_remove() unconditionally calls cancel_delayed_work_sync() on
sd->battery_work, passing a zero-initialized work struct will trigger a
debug objects (ODEBUG) warning and splat on device removal.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260531215204.172030-1-david.glushkov@sntiq.com?part=1
^ permalink raw reply
* [PATCH v2] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: David Glushkov @ 2026-05-31 22:21 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <20260531215204.172030-1-david.glushkov@sntiq.com>
---
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 193 +++++++++++++++++++++++++++++++++-
2 files changed, 192 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#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_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..e8910b222 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,23 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +41,13 @@ struct steelseries_device {
uint8_t battery_capacity;
bool headset_connected;
bool battery_charging;
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[3];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -528,6 +542,150 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ return interface_to_usbdev(to_usb_interface(hdev->dev.parent));
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return false;
+
+ return to_usb_interface(hdev->dev.parent) ==
+ usb_ifnum_to_if(steelseries_hid_to_usb_dev(hdev), 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev, struct steelseries_device, mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, 524);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, 524, USB_CTRL_SET_TIMEOUT);
+
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+
+ sd->rgb_buf = devm_kzalloc(&hdev->dev, 524, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ return devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +707,12 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev))
+ return -ENODEV;
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -567,7 +731,17 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
if (ret)
return ret;
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
+ goto err_close;
+ }
+ return 0;
+ }
+
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
@@ -593,7 +767,10 @@ static void steelseries_remove(struct hid_device *hdev)
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ if (sd) {
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9))
+ cancel_delayed_work_sync(&sd->battery_work);
+ }
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -635,7 +812,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
unsigned long flags;
/* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!(sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)))
return 0;
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
@@ -732,6 +909,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = 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),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v2] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: sashiko-bot @ 2026-05-31 22:34 UTC (permalink / raw)
To: David Glushkov; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260531222123.179923-1-david.glushkov@sntiq.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Type confusion and undefined behavior for SRWS1 devices in steelseries_headset_raw_event().
- [High] DMA API violation: devm_kzalloc buffer passed to usb_control_msg() for DMA.
- [Low] Build failure due to missing 'err_close' label on the error path of steelseries_probe().
Pre-existing issues:
- [High] Kernel crash due to uninitialized workqueue object when battery registration fails.
--
commit a675d7bbdbccedaef6c839f514204764ec470211
Author: David Glushkov <david.glushkov@sntiq.com>
HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
This commit adds support for MSI Raider A18 HX A9WJG RGB keyboards via the
steelseries driver. It introduces multicolor LED support using a devres
managed buffer and adds device specific quirks mapping.
> diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
> --- a/drivers/hid/hid-steelseries.c
> +++ b/drivers/hid/hid-steelseries.c
[ ... ]
> +static int steelseries_msi_rgb_register(struct steelseries_device *sd)
> +{
> + struct hid_device *hdev = sd->hdev;
> + struct led_classdev *led_cdev;
> +
> + sd->rgb_buf = devm_kzalloc(&hdev->dev, 524, GFP_KERNEL);
[Severity: High]
Memory allocated with devm allocators includes a devres header, which breaks
cacheline alignment. Does passing this sd->rgb_buf to usb_control_msg() in
steelseries_msi_rgb_set_blocking() cause memory corruption on architectures
with non-coherent DMA?
> + if (!sd->rgb_buf)
> + return -ENOMEM;
[ ... ]
> @@ -567,7 +731,17 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
> if (ret)
> return ret;
>
> - if (steelseries_headset_battery_register(sd) < 0)
> + if (sd->quirks & STEELSERIES_MSI_RGB) {
> + ret = steelseries_msi_rgb_register(sd);
> + if (ret) {
> + hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
> + goto err_close;
[Severity: Low]
Since there is no err_close label defined in steelseries_probe() or the rest
of the file, does this goto statement cause a build failure?
> + }
> + return 0;
> + }
> +
> + if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
> + steelseries_headset_battery_register(sd) < 0)
> hid_err(sd->hdev,
> "Failed to register battery for headset\n");
[Severity: High]
This is a pre-existing issue, but if steelseries_headset_battery_register()
fails, the error is logged but probe successfully returns 0. This leaves
sd->battery_work zero-initialized.
Will a subsequent raw input event or device removal cause a kernel panic when
schedule_delayed_work() or cancel_delayed_work_sync() is called on this
uninitialized structure?
[ ... ]
> @@ -635,7 +812,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
> unsigned long flags;
>
> /* Not a headset */
> - if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
> + if (!(sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9)))
> return 0;
[Severity: High]
For SRWS1 devices, the driver data is a completely different structure, not
a struct steelseries_device. As a result, sd aliases a kernel pointer.
Does reading sd->battery_capacity earlier in the function, and checking
sd->quirks here, lead to type confusion and undefined behavior since it
replaces the safe hdev->product ID check?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260531222123.179923-1-david.glushkov@sntiq.com?part=1
^ permalink raw reply
* [PATCH v3] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: David Glushkov @ 2026-05-31 22:53 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <20260531215204.172030-1-david.glushkov@sntiq.com>
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
Limit binding to USB interface 0 and the tested DMI system because the
KLC product ID is shared across MSI laptop designs and the key layout
mapping is model-specific.
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
v3:
- Fix build failure (added missing err_close label to steelseries_probe).
- Fix DMA API violation (use kzalloc instead of devm_kzalloc for usb transfer buffer).
- Fix C syntax declaration-after-statement warning.
- Fix type confusion for SRWS1 (add early check in raw_event before hid_get_drvdata).
- Fix delayed_work crash (add battery_registered flag).
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 267 ++++++++++++++++++++++++++++++++--
2 files changed, 260 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#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_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..3d3a92611 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,25 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_MSI_RGB_REPORT_LEN 524
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +43,14 @@ struct steelseries_device {
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];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -510,6 +527,7 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
power_supply_powers(sd->battery, &sd->hdev->dev);
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
+ sd->battery_registered = true;
steelseries_headset_fetch_battery(sd->hdev);
if (sd->quirks & STEELSERIES_ARCTIS_9) {
@@ -528,6 +546,188 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return NULL;
+
+ return to_usb_interface(hdev->dev.parent);
+}
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+
+ if (!intf)
+ return NULL;
+
+ return interface_to_usbdev(intf);
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+ struct usb_device *udev;
+
+ if (!intf)
+ return false;
+
+ udev = interface_to_usbdev(intf);
+
+ return intf == usb_ifnum_to_if(udev, 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev, struct steelseries_device, mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+\n if (!udev)
+ return -ENODEV;
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN, USB_CTRL_SET_TIMEOUT);
+
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+
+ int ret;
+
+ sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&hdev->dev,
+ steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ if (ret)
+ return ret;
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ return devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+}
+#else
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +749,12 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev))
+ return -ENODEV;
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -565,12 +771,28 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto err_stop;
+
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
+ goto err_close;
+ }
+ return 0;
+ }
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ steelseries_headset_battery_register(sd) < 0)
hid_err(sd->hdev,
"Failed to register battery for headset\n");
+ return 0;
+
+err_close:
+ hid_hw_close(hdev);
+err_stop:
+ hid_hw_stop(hdev);
return ret;
}
@@ -588,12 +810,15 @@ static void steelseries_remove(struct hid_device *hdev)
}
sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ if (sd->battery_registered)
+ cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -624,20 +849,34 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
return (capacity - min_in) * 100 / (max_in - min_in);
}
+static bool steelseries_is_headset(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ hdev->product == 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 = hid_get_drvdata(hdev);
- int capacity = sd->battery_capacity;
- bool connected = sd->headset_connected;
- bool charging = sd->battery_charging;
+ struct steelseries_device *sd;
+ int capacity;
+ bool connected;
+ bool charging;
unsigned long flags;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!steelseries_is_headset(hdev))
+ return 0;
+
+ sd = hid_get_drvdata(hdev);
+ if (!sd || !sd->battery_registered)
return 0;
+ capacity = sd->battery_capacity;
+ connected = sd->headset_connected;
+ charging = sd->battery_charging;
+
+
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
@@ -732,6 +971,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = 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),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v3] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: sashiko-bot @ 2026-05-31 23:12 UTC (permalink / raw)
To: David Glushkov; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260531225329.201327-1-david.glushkov@sntiq.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Returning `-ENODEV` prevents `hid-generic` fallback, permanently breaking the keyboard on other laptops and breaking unsupported interfaces.
- [Low] Unused static function `steelseries_msi_rgb_free_buf` in the `#else` fallback block causes a compiler warning.
--
commit 6fad0ba2106ff9dc9bf3d9ad4063720dca63e6b1
Author: David Glushkov <david.glushkov@sntiq.com>
HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
This commit adds DMI-gated support for MSI Raider A18 HX A9WJG internal
SteelSeries USB HID devices. The driver exposes them as multicolor LED
class devices on USB interface 0.
> diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
> index f98435631aa18..f0ba59bb28f97 100644
> --- a/drivers/hid/hid-steelseries.c
> +++ b/drivers/hid/hid-steelseries.c
[ ... ]
> @@ -546,6 +546,188 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
[ ... ]
> +#else
> +static void steelseries_msi_rgb_free_buf(void *data)
> +{
> + kfree(data);
> +}
> +
> +static int steelseries_msi_rgb_register(struct steelseries_device *sd)
> +{
> + return -ENODEV;
> +}
> +#endif
[Severity: Low]
Is steelseries_msi_rgb_free_buf() ever called when
CONFIG_LEDS_CLASS_MULTICOLOR is disabled?
Since it is a static function, defining it without a caller in the #else
fallback block will trigger a -Wunused-function compiler warning.
> @@ -549,6 +749,12 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
> sd->hdev = hdev;
> sd->quirks = id->driver_data;
>
> + if (sd->quirks & STEELSERIES_MSI_RGB) {
> + if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
> + !steelseries_msi_rgb_is_interface0(hdev))
> + return -ENODEV;
> + }
[Severity: High]
Does returning -ENODEV here prevent the device from functioning as a standard
keyboard on other laptops or on unsupported interfaces?
Because the device's USB IDs are explicitly listed in the steelseries_devices[]
id_table, the HID core's fallback logic prevents hid-generic from binding to it.
Returning -ENODEV instead of clearing the quirk flag and continuing
initialization abandons the device entirely.
> +
> ret = hid_parse(hdev);
> if (ret)
> return ret;
[ ... ]
> @@ -565,12 +771,28 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
>
> ret = hid_hw_open(hdev);
> if (ret)
> - return ret;
> + goto err_stop;
> +
> + if (sd->quirks & STEELSERIES_MSI_RGB) {
> + ret = steelseries_msi_rgb_register(sd);
> + if (ret) {
> + hid_err(hdev, "Failed to register MSI RGB LEDs: %d\n", ret);
> + goto err_close;
> + }
> + return 0;
> + }
[Severity: High]
Similarly, if LED registration fails (for example, out of memory, or if the LED
subsystem is disabled and returns -ENODEV), does returning an error here also
leave the device completely unbound?
>
> - if (steelseries_headset_battery_register(sd) < 0)
> + if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
> + steelseries_headset_battery_register(sd) < 0)
> hid_err(sd->hdev,
> "Failed to register battery for headset\n");
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260531225329.201327-1-david.glushkov@sntiq.com?part=1
^ permalink raw reply
* [PATCH v4] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: David Glushkov @ 2026-05-31 23:22 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <20260531215204.172030-1-david.glushkov@sntiq.com>
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
Limit RGB support to USB interface 0 and the tested DMI system because
the KLC product ID is shared across MSI laptop designs and the key
layout mapping is model-specific. If the DMI or interface check does
not match, keep the device bound as a regular HID device instead of
failing probe.
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
v4:
- Fix literal \n typo in C code.
- Fix trailing whitespace and style nits.
- Remove unused steelseries_msi_rgb_free_buf from #else block.
- Do not fail probe on unsupported DMI/interface; clear MSI RGB quirk
and continue normal HID initialization.
- Do not fail probe when RGB LED registration fails; keep the HID
device usable without RGB LED support.
- Add explicit linux/slab.h include for kzalloc/kfree.
v3:
- Fix build failure (added missing err_close label to steelseries_probe).
- Fix DMA API violation (use kzalloc instead of devm_kzalloc for usb
transfer buffer).
- Fix C syntax declaration-after-statement warning.
- Fix type confusion for SRWS1 (add early check in raw_event before
hid_get_drvdata).
- Fix delayed_work crash (add battery_registered flag).
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting
cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 265 ++++++++++++++++++++++++++++++++--
2 files changed, 256 insertions(+), 11 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#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_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..3a4f4c2b1 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,26 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_MSI_RGB_REPORT_LEN 524
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +44,14 @@ struct steelseries_device {
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];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -510,6 +528,7 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
power_supply_powers(sd->battery, &sd->hdev->dev);
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
+ sd->battery_registered = true;
steelseries_headset_fetch_battery(sd->hdev);
if (sd->quirks & STEELSERIES_ARCTIS_9) {
@@ -528,6 +547,190 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return NULL;
+
+ return to_usb_interface(hdev->dev.parent);
+}
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+
+ if (!intf)
+ return NULL;
+
+ return interface_to_usbdev(intf);
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+ struct usb_device *udev;
+
+ if (!intf)
+ return false;
+
+ udev = interface_to_usbdev(intf);
+
+ return intf == usb_ifnum_to_if(udev, 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev,
+ struct steelseries_device,
+ mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev;
+ u8 r, g, b;
+ int i, ret, pm_ret;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ udev = steelseries_hid_to_usb_dev(hdev);
+ if (!udev)
+ return -ENODEV;
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
+ USB_CTRL_SET_TIMEOUT);
+
+ pm_ret = hid_hw_power(hdev, PM_HINT_NORMAL);
+ if (ret >= 0)
+ ret = pm_ret;
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+ int ret;
+
+ sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&hdev->dev,
+ steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ if (ret) {
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ return devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +752,14 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev)) {
+ hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -565,12 +776,30 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto err_stop;
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_warn(hdev,
+ "Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
+ ret);
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ return 0;
+ }
+
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ 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;
}
@@ -588,12 +817,15 @@ static void steelseries_remove(struct hid_device *hdev)
}
sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ if (sd->battery_registered)
+ cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -624,20 +856,33 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
return (capacity - min_in) * 100 / (max_in - min_in);
}
+static bool steelseries_is_headset(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ hdev->product == 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 = hid_get_drvdata(hdev);
- int capacity = sd->battery_capacity;
- bool connected = sd->headset_connected;
- bool charging = sd->battery_charging;
+ struct steelseries_device *sd;
+ int capacity;
+ bool connected;
+ bool charging;
unsigned long flags;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!steelseries_is_headset(hdev))
+ return 0;
+
+ sd = hid_get_drvdata(hdev);
+ if (!sd || !sd->battery_registered)
return 0;
+ capacity = sd->battery_capacity;
+ connected = sd->headset_connected;
+ charging = sd->battery_charging;
+
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
@@ -732,6 +977,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = 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),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related
* [PATCH v5] HID: steelseries: Add MSI Raider A18 HX A9WJG RGB support
From: David Glushkov @ 2026-05-31 23:39 UTC (permalink / raw)
To: Jiri Kosina; +Cc: Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <20260531215204.172030-1-david.glushkov@sntiq.com>
The MSI Raider A18 HX A9WJG exposes two internal SteelSeries USB HID
devices for RGB lighting: KLC (1038:1122) for the keyboard and ALC
(1038:1161) for the lightbar/logo zones.
Add DMI-gated support for these devices and expose them as multicolor
LED class devices. The driver sends the same HID class SET_REPORT
control transfer as the tested userspace implementation for this
machine and writes a uniform RGB value to all known keyboard keys or
ALC zones.
Limit RGB support to USB interface 0 and the tested DMI system because
the KLC product ID is shared across MSI laptop designs and the key
layout mapping is model-specific. If the DMI or interface check does
not match, keep the device bound as a regular HID device instead of
failing probe.
Tested on MSI Raider A18 HX A9WJG. Both internal SteelSeries ALC
(1038:1161) and KLC (1038:1122) HID devices bind on interface 0 and
create steelseries::lightbar and steelseries::kbd_backlight. Setting
multi_intensity and brightness changes the keyboard and lightbar
colors.
Signed-off-by: David Glushkov <david.glushkov@sntiq.com>
---
v5:
- Drop pm_ret handling and ignore PM_HINT_NORMAL cleanup errors.
- Fix LED registration error handling to clean up rgb_buf on failure.
- Fix trailing whitespaces and formatting style nits.
- Update commit message to accurately reflect the DMI fallback behavior.
v4:
- Fix literal \n typo in C code.
- Remove unused steelseries_msi_rgb_free_buf from #else block.
- Do not fail probe on unsupported DMI/interface; clear MSI RGB quirk and continue normal HID initialization.
- Do not fail probe when RGB LED registration fails; keep the HID device usable without RGB LED support.
- Add explicit linux/slab.h include for kzalloc/kfree.
v3:
- Fix build failure (added missing err_close label to steelseries_probe).
- Fix DMA API violation (use kzalloc instead of devm_kzalloc for usb transfer buffer).
- Fix C syntax declaration-after-statement warning.
- Fix type confusion for SRWS1 (add early check in raw_event before hid_get_drvdata).
- Fix delayed_work crash (add battery_registered flag).
v2:
- Fixed unsafe to_usb_interface cast by checking hid_is_usb() first.
- Fixed uninitialized delayed_work warning by restricting cancel_delayed_work_sync to headset devices.
- Fixed error path leaks in probe (hid_hw_stop / hid_hw_close).
- Added hid_hw_power PM wrappers around direct usb_control_msg transfers.
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-steelseries.c | 277 ++++++++++++++++++++++++++++++++--
2 files changed, 270 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4657d96fb..4af4397b8 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1367,6 +1367,8 @@
#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_MSI_KLC 0x1122
+#define USB_DEVICE_ID_STEELSERIES_MSI_ALC 0x1161
#define USB_VENDOR_ID_SUN 0x0430
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
index f98435631..c269db06d 100644
--- a/drivers/hid/hid-steelseries.c
+++ b/drivers/hid/hid-steelseries.c
@@ -10,16 +10,26 @@
*/
#include <linux/device.h>
+#include <linux/dmi.h>
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
#include "hid-ids.h"
#define STEELSERIES_SRWS1 BIT(0)
#define STEELSERIES_ARCTIS_1 BIT(1)
#define STEELSERIES_ARCTIS_9 BIT(2)
+#define STEELSERIES_MSI_RGB BIT(3)
+
+#define STEELSERIES_MSI_RGB_REPORT_LEN 524
+
+#define STEELSERIES_HAS_LEDS_MULTICOLOR \
+ (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
+ (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
struct steelseries_device {
struct hid_device *hdev;
@@ -34,6 +44,14 @@ struct steelseries_device {
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];
+ struct mutex rgb_lock; /* protects rgb_buf */
+ u8 *rgb_buf;
+#endif
};
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
@@ -510,6 +528,7 @@ static int steelseries_headset_battery_register(struct steelseries_device *sd)
power_supply_powers(sd->battery, &sd->hdev->dev);
INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick);
+ sd->battery_registered = true;
steelseries_headset_fetch_battery(sd->hdev);
if (sd->quirks & STEELSERIES_ARCTIS_9) {
@@ -528,6 +547,196 @@ static bool steelseries_is_vendor_usage_page(struct hid_device *hdev, uint8_t us
hdev->rdesc[2] == 0xff;
}
+static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
+ DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
+ },
+ },
+ { }
+};
+
+static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
+{
+ if (!hid_is_usb(hdev))
+ return NULL;
+
+ return to_usb_interface(hdev->dev.parent);
+}
+
+static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+
+ if (!intf)
+ return NULL;
+
+ return interface_to_usbdev(intf);
+}
+
+static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
+{
+ struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
+ struct usb_device *udev;
+
+ if (!intf)
+ return false;
+
+ udev = interface_to_usbdev(intf);
+
+ return intf == usb_ifnum_to_if(udev, 0);
+}
+
+#if STEELSERIES_HAS_LEDS_MULTICOLOR
+
+static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+ struct steelseries_device *sd = container_of(mc_cdev,
+ struct steelseries_device,
+ mc_cdev);
+ struct hid_device *hdev = sd->hdev;
+ struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
+ int i, ret;
+ u8 r, g, b;
+
+ static const u8 keys[] = {
+ 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+ 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
+ 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+ 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
+ };
+ static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+ if (!udev)
+ return -ENODEV;
+
+ mutex_lock(&sd->rgb_lock);
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ r = mc_cdev->subled_info[0].brightness;
+ g = mc_cdev->subled_info[1].brightness;
+ b = mc_cdev->subled_info[2].brightness;
+
+ memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
+ sd->rgb_buf[0] = 0x0c;
+ sd->rgb_buf[1] = 0x00;
+ sd->rgb_buf[3] = 0x00;
+
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
+ sd->rgb_buf[2] = 0x66;
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ sd->rgb_buf[4 + i * 4] = keys[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ } else {
+ sd->rgb_buf[2] = 0x06;
+ for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
+ sd->rgb_buf[4 + i * 4] = alc_zones[i];
+ sd->rgb_buf[5 + i * 4] = r;
+ sd->rgb_buf[6 + i * 4] = g;
+ sd->rgb_buf[7 + i * 4] = b;
+ }
+ }
+
+ ret = hid_hw_power(hdev, PM_HINT_FULLON);
+ if (ret < 0)
+ goto out_unlock;
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ HID_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0x0300, 0,
+ sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
+ USB_CTRL_SET_TIMEOUT);
+
+ hid_hw_power(hdev, PM_HINT_NORMAL);
+
+out_unlock:
+ mutex_unlock(&sd->rgb_lock);
+ return ret < 0 ? ret : 0;
+}
+
+static void steelseries_msi_rgb_free_buf(void *data)
+{
+ kfree(data);
+}
+
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ struct hid_device *hdev = sd->hdev;
+ struct led_classdev *led_cdev;
+ int ret;
+
+ sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
+ if (!sd->rgb_buf)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(&hdev->dev,
+ steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ if (ret) {
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ mutex_init(&sd->rgb_lock);
+
+ sd->subled_info[0].color_index = LED_COLOR_ID_RED;
+ sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ sd->subled_info[0].intensity = 255;
+ sd->subled_info[1].intensity = 255;
+ sd->subled_info[2].intensity = 255;
+ sd->subled_info[0].channel = 0;
+ sd->subled_info[1].channel = 1;
+ sd->subled_info[2].channel = 2;
+
+ sd->mc_cdev.subled_info = sd->subled_info;
+ sd->mc_cdev.num_colors = 3;
+
+ led_cdev = &sd->mc_cdev.led_cdev;
+ if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
+ led_cdev->name = "steelseries::kbd_backlight";
+ else
+ led_cdev->name = "steelseries::lightbar";
+
+ led_cdev->max_brightness = 255;
+ led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
+
+ ret = devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
+ if (ret) {
+ devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
+ sd->rgb_buf);
+ kfree(sd->rgb_buf);
+ sd->rgb_buf = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+#else
+static int steelseries_msi_rgb_register(struct steelseries_device *sd)
+{
+ return -ENODEV;
+}
+#endif
+
static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct steelseries_device *sd;
@@ -549,6 +758,14 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
sd->hdev = hdev;
sd->quirks = id->driver_data;
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
+ !steelseries_msi_rgb_is_interface0(hdev)) {
+ hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ }
+
ret = hid_parse(hdev);
if (ret)
return ret;
@@ -565,12 +782,28 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
ret = hid_hw_open(hdev);
if (ret)
- return ret;
+ goto err_stop;
+
+ if (sd->quirks & STEELSERIES_MSI_RGB) {
+ ret = steelseries_msi_rgb_register(sd);
+ if (ret) {
+ hid_warn(hdev,
+ "Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
+ ret);
+ sd->quirks &= ~STEELSERIES_MSI_RGB;
+ }
+ return 0;
+ }
- if (steelseries_headset_battery_register(sd) < 0)
+ if (sd->quirks & (STEELSERIES_ARCTIS_1 | STEELSERIES_ARCTIS_9) &&
+ 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;
}
@@ -588,12 +821,15 @@ static void steelseries_remove(struct hid_device *hdev)
}
sd = hid_get_drvdata(hdev);
+ if (!sd)
+ return;
spin_lock_irqsave(&sd->lock, flags);
sd->removed = true;
spin_unlock_irqrestore(&sd->lock, flags);
- cancel_delayed_work_sync(&sd->battery_work);
+ if (sd->battery_registered)
+ cancel_delayed_work_sync(&sd->battery_work);
hid_hw_close(hdev);
hid_hw_stop(hdev);
@@ -624,20 +860,33 @@ static uint8_t steelseries_headset_map_capacity(uint8_t capacity, uint8_t min_in
return (capacity - min_in) * 100 / (max_in - min_in);
}
+static bool steelseries_is_headset(struct hid_device *hdev)
+{
+ return hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1 ||
+ hdev->product == 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 = hid_get_drvdata(hdev);
- int capacity = sd->battery_capacity;
- bool connected = sd->headset_connected;
- bool charging = sd->battery_charging;
+ struct steelseries_device *sd;
+ int capacity;
+ bool connected;
+ bool charging;
unsigned long flags;
- /* Not a headset */
- if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
+ if (!steelseries_is_headset(hdev))
+ return 0;
+
+ sd = hid_get_drvdata(hdev);
+ if (!sd || !sd->battery_registered)
return 0;
+ capacity = sd->battery_capacity;
+ connected = sd->headset_connected;
+ charging = sd->battery_charging;
+
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
hid_dbg(sd->hdev,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
@@ -732,6 +981,16 @@ static const struct hid_device_id steelseries_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
.driver_data = 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),
+ .driver_data = STEELSERIES_MSI_RGB },
+
+ { /* MSI Raider A18 ALC */
+ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
+ .driver_data = STEELSERIES_MSI_RGB },
+#endif
+
{ }
};
MODULE_DEVICE_TABLE(hid, steelseries_devices);
--
2.54.0
^ permalink raw reply related
* RE: [PATCH] HID: intel-thc-hid: intel-quickspi: reset touch IC on system resume
From: Xu, Even @ 2026-06-01 3:24 UTC (permalink / raw)
To: d3z-the-dev, Sun, Xinpeng, Jiri Kosina, Benjamin Tissoires
Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
Abhishek Tamboli, Sakari Ailus
In-Reply-To: <20260529222210.1532876-1-d3z.the.dev@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 4832 bytes --]
Hi,
Sorry, I cannot get your exact name from this patch email.
From your issue description, I suppose Surface Pro 10 uses "Suspend-to-RAM" instead of "Standby" or "Suspend-to-Idle" for suspend/resume.
As this documents mentioned, driver needs to take care the suspend type then handle resume flow carefully.
Actually, I had a patch for this "Suspend-to-RAM" support several months ago, please find attached file for the patch. This patch is still under our internal final validation.
It will be very appreciated if you can help test attached patch on your Surface device.
If it works, then everything is fine, I will submit this patch with your name after our final validation.
If it doesn't work for your case, then we need to future debug on your surface device to refine your patch to let it more standard.
Thanks!
Best Regards,
Even Xu
> -----Original Message-----
> From: d3z-the-dev <d3z.the.dev@gmail.com>
> Sent: Saturday, May 30, 2026 6:22 AM
> To: Xu, Even <even.xu@intel.com>; Sun, Xinpeng <xinpeng.sun@intel.com>; Jiri
> Kosina <jikos@kernel.org>; Benjamin Tissoires <bentiss@kernel.org>
> Cc: linux-input@vger.kernel.org; linux-kernel@vger.kernel.org; Abhishek Tamboli
> <abhishektamboli9@gmail.com>; Sakari Ailus <sakari.ailus@linux.intel.com>;
> d3z-the-dev <d3z.the.dev@gmail.com>
> Subject: [PATCH] HID: intel-thc-hid: intel-quickspi: reset touch IC on system
> resume
>
> On the Surface Pro 10 (Meteor Lake) the touchscreen stops working after a
> suspend/resume cycle and only recovers after a reboot. The driver logs
> "GET_DEVICE_INFO: recv failed: -11" on resume.
>
> The touch IC loses power during system suspend (s2idle) on this platform, the
> same way it does across hibernation. quickspi_resume() only restores the THC
> port, interrupts and DMA and sends a HIDSPI_ON command, assuming the touch
> IC kept its power and state. When it has actually lost power the HIDSPI_ON
> command is never acknowledged and the descriptor read fails, leaving the
> touchscreen dead until the module is reloaded.
>
> quickspi_restore() already handles this for hibernation by running
> reset_tic() and reconfiguring the THC SPI/LTR settings. Make
> quickspi_resume() do the same: quiesce interrupts, re-select the THC port,
> reconfigure the SPI input/output addresses and read/write parameters, run
> reset_tic() to re-enumerate the device and restore the LTR configuration.
>
> Tested on a Surface Pro 10 across multiple s2idle suspend/resume cycles.
>
> Link: https://github.com/linux-surface/linux-surface/issues/1799
>
> Signed-off-by: d3z-the-dev <d3z.the.dev@gmail.com>
> ---
> .../intel-quickspi/pci-quickspi.c | 38 +++++++++++++++++--
> 1 file changed, 34 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
> b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
> index f669235f1883..d59278a3e5c1 100644
> --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
> +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
> @@ -780,24 +780,54 @@ static int quickspi_resume(struct device *device)
> if (!qsdev)
> return -ENODEV;
>
> + ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
> + if (ret)
> + return ret;
> +
> ret = thc_port_select(qsdev->thc_hw, THC_PORT_TYPE_SPI);
> if (ret)
> return ret;
>
> + thc_spi_input_output_address_config(qsdev->thc_hw,
> + qsdev->input_report_hdr_addr,
> + qsdev->input_report_bdy_addr,
> + qsdev->output_report_addr);
> +
> + ret = thc_spi_read_config(qsdev->thc_hw, qsdev->spi_freq_val,
> + qsdev->spi_read_io_mode,
> + qsdev->spi_read_opcode,
> + qsdev->spi_packet_size);
> + if (ret)
> + return ret;
> +
> + ret = thc_spi_write_config(qsdev->thc_hw, qsdev->spi_freq_val,
> + qsdev->spi_write_io_mode,
> + qsdev->spi_write_opcode,
> + qsdev->spi_packet_size,
> + qsdev->performance_limit);
> + if (ret)
> + return ret;
> +
> thc_interrupt_config(qsdev->thc_hw);
>
> thc_interrupt_enable(qsdev->thc_hw, true);
>
> - ret = thc_dma_configure(qsdev->thc_hw);
> + /* The TIC may lose power across system suspend, reset it to recover */
> + ret = reset_tic(qsdev);
> if (ret)
> return ret;
>
> - ret = thc_interrupt_quiesce(qsdev->thc_hw, false);
> + ret = thc_dma_configure(qsdev->thc_hw);
> if (ret)
> return ret;
>
> - if (!device_may_wakeup(qsdev->dev))
> - return quickspi_set_power(qsdev, HIDSPI_ON);
> + thc_ltr_config(qsdev->thc_hw,
> + qsdev->active_ltr_val,
> + qsdev->low_power_ltr_val);
> +
> + thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_ACTIVE);
> +
> + qsdev->state = QUICKSPI_ENABLED;
>
> return 0;
> }
> --
> 2.54.0
[-- Attachment #2: 0001-Hid-Intel-thc-hid-Intel-quickspi-Fix-non-functional-.patch --]
[-- Type: application/octet-stream, Size: 3337 bytes --]
From 939ec2c491f789b5b740a58936ff0e04cb71d2a8 Mon Sep 17 00:00:00 2001
From: Even Xu <even.xu@intel.com>
Date: Mon, 19 Jan 2026 16:16:12 +0800
Subject: [PATCH] Hid: Intel-thc-hid: Intel-quickspi: Fix non-functional touch
after S3 resume
THC hardware registers lose their content during S3 suspend. Add hardware
reconfiguration on resume from S3 to restore touch functionality.
Signed-off-by: Even Xu <even.xu@intel.com>
---
.../intel-quickspi/pci-quickspi.c | 32 +++++++++++++++++++
.../intel-quickspi/quickspi-dev.h | 4 +++
2 files changed, 36 insertions(+)
diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
index 20e8dbf1d313..0d67a0bb2ee7 100644
--- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
@@ -10,6 +10,7 @@
#include <linux/irqreturn.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
+#include <linux/suspend.h>
#include <linux/gpio/consumer.h>
@@ -765,6 +766,8 @@ static int quickspi_suspend(struct device *device)
thc_dma_unconfigure(qsdev->thc_hw);
+ qsdev->last_suspend_state = pm_suspend_target_state;
+
return 0;
}
@@ -782,6 +785,35 @@ static int quickspi_resume(struct device *device)
if (ret)
return ret;
+ /* If it's resume from S3, need to re-configure SPI port */
+ if (qsdev->last_suspend_state == PM_SUSPEND_MEM) {
+ thc_spi_input_output_address_config(qsdev->thc_hw,
+ qsdev->input_report_hdr_addr,
+ qsdev->input_report_bdy_addr,
+ qsdev->output_report_addr);
+
+ ret = thc_spi_read_config(qsdev->thc_hw, qsdev->spi_freq_val,
+ qsdev->spi_read_io_mode,
+ qsdev->spi_read_opcode,
+ qsdev->spi_packet_size);
+ if (ret)
+ return ret;
+
+ ret = thc_spi_write_config(qsdev->thc_hw, qsdev->spi_freq_val,
+ qsdev->spi_write_io_mode,
+ qsdev->spi_write_opcode,
+ qsdev->spi_packet_size,
+ qsdev->performance_limit);
+ if (ret)
+ return ret;
+
+ thc_ltr_config(qsdev->thc_hw,
+ qsdev->active_ltr_val,
+ qsdev->low_power_ltr_val);
+
+ thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_ACTIVE);
+ }
+
thc_interrupt_config(qsdev->thc_hw);
thc_interrupt_enable(qsdev->thc_hw, true);
diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h
index ef433fe1b45d..3beeec62dd09 100644
--- a/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h
+++ b/drivers/hid/intel-thc-hid/intel-quickspi/quickspi-dev.h
@@ -7,6 +7,7 @@
#include <linux/bits.h>
#include <linux/hid-over-spi.h>
#include <linux/sizes.h>
+#include <linux/suspend.h>
#include <linux/wait.h>
#include "quickspi-protocol.h"
@@ -124,6 +125,7 @@ struct acpi_device;
* @get_feature_cmpl: indicate get feature received or not
* @set_feature_cmpl_wq: workqueue for waiting set feature to device
* @set_feature_cmpl: indicate set feature send complete or not
+ * @last_suspend_state: save last system suspend state
*/
struct quickspi_device {
struct device *dev;
@@ -171,6 +173,8 @@ struct quickspi_device {
wait_queue_head_t set_report_cmpl_wq;
bool set_report_cmpl;
+
+ suspend_state_t last_suspend_state;
};
#endif /* _QUICKSPI_DEV_H_ */
--
2.43.0
^ permalink raw reply related
* RE: [PATCH] HID: intel-thc-hid: intel-quickspi: reset touch IC on system resume
From: Xu, Even @ 2026-06-01 3:26 UTC (permalink / raw)
To: d3z-the-dev, Sun, Xinpeng, Jiri Kosina, Benjamin Tissoires
Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
Abhishek Tamboli, Sakari Ailus
In-Reply-To: <IA1PR11MB60987CB2D72FD1701951DFA4F4152@IA1PR11MB6098.namprd11.prod.outlook.com>
Sorry, missed the doc link: https://docs.kernel.org/admin-guide/pm/sleep-states.html
> -----Original Message-----
> From: Xu, Even
> Sent: Monday, June 1, 2026 11:25 AM
> To: 'd3z-the-dev' <d3z.the.dev@gmail.com>; Sun, Xinpeng
> <Xinpeng.Sun@intel.com>; Jiri Kosina <jikos@kernel.org>; Benjamin Tissoires
> <bentiss@kernel.org>
> Cc: linux-input@vger.kernel.org; linux-kernel@vger.kernel.org; Abhishek Tamboli
> <abhishektamboli9@gmail.com>; Sakari Ailus <sakari.ailus@linux.intel.com>
> Subject: RE: [PATCH] HID: intel-thc-hid: intel-quickspi: reset touch IC on system
> resume
>
> Hi,
>
> Sorry, I cannot get your exact name from this patch email.
>
> From your issue description, I suppose Surface Pro 10 uses "Suspend-to-RAM"
> instead of "Standby" or "Suspend-to-Idle" for suspend/resume.
> As this documents mentioned, driver needs to take care the suspend type then
> handle resume flow carefully.
>
> Actually, I had a patch for this "Suspend-to-RAM" support several months ago,
> please find attached file for the patch. This patch is still under our internal final
> validation.
>
> It will be very appreciated if you can help test attached patch on your Surface
> device.
> If it works, then everything is fine, I will submit this patch with your name after
> our final validation.
> If it doesn't work for your case, then we need to future debug on your surface
> device to refine your patch to let it more standard.
>
> Thanks!
>
> Best Regards,
> Even Xu
>
> > -----Original Message-----
> > From: d3z-the-dev <d3z.the.dev@gmail.com>
> > Sent: Saturday, May 30, 2026 6:22 AM
> > To: Xu, Even <even.xu@intel.com>; Sun, Xinpeng
> > <xinpeng.sun@intel.com>; Jiri Kosina <jikos@kernel.org>; Benjamin
> > Tissoires <bentiss@kernel.org>
> > Cc: linux-input@vger.kernel.org; linux-kernel@vger.kernel.org;
> > Abhishek Tamboli <abhishektamboli9@gmail.com>; Sakari Ailus
> > <sakari.ailus@linux.intel.com>; d3z-the-dev <d3z.the.dev@gmail.com>
> > Subject: [PATCH] HID: intel-thc-hid: intel-quickspi: reset touch IC on
> > system resume
> >
> > On the Surface Pro 10 (Meteor Lake) the touchscreen stops working
> > after a suspend/resume cycle and only recovers after a reboot. The
> > driver logs
> > "GET_DEVICE_INFO: recv failed: -11" on resume.
> >
> > The touch IC loses power during system suspend (s2idle) on this
> > platform, the same way it does across hibernation. quickspi_resume()
> > only restores the THC port, interrupts and DMA and sends a HIDSPI_ON
> > command, assuming the touch IC kept its power and state. When it has
> > actually lost power the HIDSPI_ON command is never acknowledged and
> > the descriptor read fails, leaving the touchscreen dead until the module is
> reloaded.
> >
> > quickspi_restore() already handles this for hibernation by running
> > reset_tic() and reconfiguring the THC SPI/LTR settings. Make
> > quickspi_resume() do the same: quiesce interrupts, re-select the THC
> > port, reconfigure the SPI input/output addresses and read/write
> > parameters, run
> > reset_tic() to re-enumerate the device and restore the LTR configuration.
> >
> > Tested on a Surface Pro 10 across multiple s2idle suspend/resume cycles.
> >
> > Link: https://github.com/linux-surface/linux-surface/issues/1799
> >
> > Signed-off-by: d3z-the-dev <d3z.the.dev@gmail.com>
> > ---
> > .../intel-quickspi/pci-quickspi.c | 38 +++++++++++++++++--
> > 1 file changed, 34 insertions(+), 4 deletions(-)
> >
> > diff --git a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
> > b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
> > index f669235f1883..d59278a3e5c1 100644
> > --- a/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
> > +++ b/drivers/hid/intel-thc-hid/intel-quickspi/pci-quickspi.c
> > @@ -780,24 +780,54 @@ static int quickspi_resume(struct device *device)
> > if (!qsdev)
> > return -ENODEV;
> >
> > + ret = thc_interrupt_quiesce(qsdev->thc_hw, true);
> > + if (ret)
> > + return ret;
> > +
> > ret = thc_port_select(qsdev->thc_hw, THC_PORT_TYPE_SPI);
> > if (ret)
> > return ret;
> >
> > + thc_spi_input_output_address_config(qsdev->thc_hw,
> > + qsdev->input_report_hdr_addr,
> > + qsdev->input_report_bdy_addr,
> > + qsdev->output_report_addr);
> > +
> > + ret = thc_spi_read_config(qsdev->thc_hw, qsdev->spi_freq_val,
> > + qsdev->spi_read_io_mode,
> > + qsdev->spi_read_opcode,
> > + qsdev->spi_packet_size);
> > + if (ret)
> > + return ret;
> > +
> > + ret = thc_spi_write_config(qsdev->thc_hw, qsdev->spi_freq_val,
> > + qsdev->spi_write_io_mode,
> > + qsdev->spi_write_opcode,
> > + qsdev->spi_packet_size,
> > + qsdev->performance_limit);
> > + if (ret)
> > + return ret;
> > +
> > thc_interrupt_config(qsdev->thc_hw);
> >
> > thc_interrupt_enable(qsdev->thc_hw, true);
> >
> > - ret = thc_dma_configure(qsdev->thc_hw);
> > + /* The TIC may lose power across system suspend, reset it to recover */
> > + ret = reset_tic(qsdev);
> > if (ret)
> > return ret;
> >
> > - ret = thc_interrupt_quiesce(qsdev->thc_hw, false);
> > + ret = thc_dma_configure(qsdev->thc_hw);
> > if (ret)
> > return ret;
> >
> > - if (!device_may_wakeup(qsdev->dev))
> > - return quickspi_set_power(qsdev, HIDSPI_ON);
> > + thc_ltr_config(qsdev->thc_hw,
> > + qsdev->active_ltr_val,
> > + qsdev->low_power_ltr_val);
> > +
> > + thc_change_ltr_mode(qsdev->thc_hw, THC_LTR_MODE_ACTIVE);
> > +
> > + qsdev->state = QUICKSPI_ENABLED;
> >
> > return 0;
> > }
> > --
> > 2.54.0
^ permalink raw reply
* [bug report] Potential atomicity bug in drivers/input/joydev.c, between joydev_0x_read() and joydev_ioctl_common()
From: Ginger @ 2026-06-01 7:07 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input
Dear Linux kernel maintainers,
My research-based static analyzer found a potential atomicity bug
within the 'drivers/input' subsystem, more specifically, in
'drivers/input/joydev.c'.
This potential issue is present as of git commit
eb3f4b7426cfd2b79d65b7d37155480b32259a11 of the mainline kernel.
Potential concurrent triggering executions:
T0:
joydev_0x_read
--> spin_lock_irq(&input->event_lock);
--> read from joydev->abs
--> spin_unlock_irq(&input->event_lock);
T1:
joydev_ioctl_common
--> case JSIOCSCORR:
--> write to joydev->abs[i] (no unlocked)
The above trace is meant to demonstrate an illustrative example of the issue:
IMHO, in 'joydev_0x_read', the 'input->event_lock' is adopted to
serialize the read
accesses to joydev's fields like 'abs' and 'keypam' or input's fields
like 'input->key.
However, in either case, the write-side accesses to these fields are
not similarly
serialized.
Please kindly check at your convenience. Thank you for your time and
consideration.
Best regards,
Ginger
^ permalink raw reply
* Re: [PATCH 2/4] HID: core: introduce hid_safe_input_report()
From: Benjamin Tissoires @ 2026-06-01 7:30 UTC (permalink / raw)
To: Carlos Llamas
Cc: Benjamin Tissoires, Bastien Nocera, Jiri Kosina,
Filipe Laíns, Ping Cheng, Jason Gerecke, Viresh Kumar,
Johan Hovold, Alex Elder, Greg Kroah-Hartman, Lee Jones,
linux-input, linux-kernel, greybus-dev, linux-staging, linux-usb,
stable
In-Reply-To: <ahsh0UtTX6e0ZeHa@google.com>
Hi Carlos,
On May 30 2026, Carlos Llamas wrote:
> On Thu, Apr 16, 2026 at 04:46:28PM +0200, Benjamin Tissoires wrote:
> > On Thu, Apr 16, 2026 at 11:41 AM Bastien Nocera <hadess@hadess.net> wrote:
> > >
> > > On Wed, 2026-04-15 at 11:38 +0200, Benjamin Tissoires wrote:
> > > > hid_input_report() is used in too many places to have a commit that
> > > > doesn't cross subsystem borders. Instead of changing the API,
> > > > introduce
> > > > a new one when things matters in the transport layers:
> > > > - usbhid
> > > > - i2chid
> > > >
> > > > This effectively revert to the old behavior for those two transport
> > > > layers.
> > > >
> > > > Fixes: 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing
> > > > bogus memset()")
> > > > Cc: stable@vger.kernel.org
> > > > Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
> > > > ---
[...]
>
> Hi Benjamin, our CI started failing with commit 0a3fe972a7cb ("HID:
> core: Mitigate potential OOB by removing bogus memset()"), so I was
> hoping your patchset would fix this.
>
> However, I just realized our call path goes through uhid precisely,
> which still triggers the EINVAL error since uhid as not converted to
> hid_safe_input_report().
>
> My vague understanding though, is that uhid_event uses a static buffer
> in ev->data[UHID_DATA_MAX], so maybe we can use that through
> uhid_dev_input{2}()?
>
> I ran the following path through our CI and it fixed our issue, so I
> wanted to get your thoughts on this.
Oh, yes, you are correct. Sorry with all the back and forth on this
paritcular topic, my brain assumed that uhid was only allocating the
useful part of the payload and was not safe.
For the future me: the problem with uhid was that we were emultaing
devices that would trigger a bug elsewhere in the stack not in
uhid_dev_input*().
Patch looks good, please send it normally to the ML with your SoB :)
Cheers,
Benjamin
>
> Carlos Llamas
>
> ---
> drivers/hid/uhid.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c
> index 524b53a3c87b..37b60c3aaf66 100644
> --- a/drivers/hid/uhid.c
> +++ b/drivers/hid/uhid.c
> @@ -595,8 +595,8 @@ static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev)
> if (!READ_ONCE(uhid->running))
> return -EINVAL;
>
> - hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data,
> - min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0);
> + hid_safe_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data, UHID_DATA_MAX,
> + min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0);
>
> return 0;
> }
> @@ -606,8 +606,8 @@ static int uhid_dev_input2(struct uhid_device *uhid, struct uhid_event *ev)
> if (!READ_ONCE(uhid->running))
> return -EINVAL;
>
> - hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input2.data,
> - min_t(size_t, ev->u.input2.size, UHID_DATA_MAX), 0);
> + hid_safe_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input2.data, UHID_DATA_MAX,
> + min_t(size_t, ev->u.input2.size, UHID_DATA_MAX), 0);
>
> return 0;
> }
>
^ permalink raw reply
* [linux-6.12.y 1/4] HID: core: Add printk_ratelimited variants to hid_warn() etc
From: Lee Jones @ 2026-06-01 8:36 UTC (permalink / raw)
To: lee, Jiri Kosina, Benjamin Tissoires, Filipe Laíns,
Bastien Nocera, Ping Cheng, Jason Gerecke, Viresh Kumar,
Johan Hovold, Alex Elder, Greg Kroah-Hartman, Sasha Levin,
Mario Limonciello (AMD), Kwok Kin Ming, linux-input, linux-kernel,
linux-usb, greybus-dev, linux-staging, bpf
Cc: stable, Vicki Pfau, Jiri Kosina
From: Vicki Pfau <vi@endrift.com>
hid_warn_ratelimited() is needed. Add the others as part of the block.
Signed-off-by: Vicki Pfau <vi@endrift.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
(cherry picked from commit 1d64624243af8329b4b219d8c39e28ea448f9929)
Signed-off-by: Lee Jones <lee@kernel.org>
---
include/linux/hid.h | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 7d8d09318fa9..bef017d6b440 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -1245,4 +1245,15 @@ void hid_quirks_exit(__u16 bus);
#define hid_dbg_once(hid, fmt, ...) \
dev_dbg_once(&(hid)->dev, fmt, ##__VA_ARGS__)
+#define hid_err_ratelimited(hid, fmt, ...) \
+ dev_err_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
+#define hid_notice_ratelimited(hid, fmt, ...) \
+ dev_notice_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
+#define hid_warn_ratelimited(hid, fmt, ...) \
+ dev_warn_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
+#define hid_info_ratelimited(hid, fmt, ...) \
+ dev_info_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
+#define hid_dbg_ratelimited(hid, fmt, ...) \
+ dev_dbg_ratelimited(&(hid)->dev, fmt, ##__VA_ARGS__)
+
#endif
--
2.54.0.823.g6e5bcc1fc9-goog
^ permalink raw reply related
* [linux-6.12.y 2/4] HID: pass the buffer size to hid_report_raw_event
From: Lee Jones @ 2026-06-01 8:36 UTC (permalink / raw)
To: lee, Jiri Kosina, Benjamin Tissoires, Filipe Laíns,
Bastien Nocera, Ping Cheng, Jason Gerecke, Viresh Kumar,
Johan Hovold, Alex Elder, Greg Kroah-Hartman, Sasha Levin,
Kwok Kin Ming, Mario Limonciello (AMD), linux-input, linux-kernel,
linux-usb, greybus-dev, linux-staging, bpf
Cc: stable, Jiri Kosina
In-Reply-To: <20260601083642.908433-1-lee@kernel.org>
From: Benjamin Tissoires <bentiss@kernel.org>
[ Upstream commit 2c85c61d1332e1e16f020d76951baf167dcb6f7a ]
commit 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing
bogus memset()") enforced the provided data to be at least the size of
the declared buffer in the report descriptor to prevent a buffer
overflow. However, we can try to be smarter by providing both the buffer
size and the data size, meaning that hid_report_raw_event() can make
better decision whether we should plaining reject the buffer (buffer
overflow attempt) or if we can safely memset it to 0 and pass it to the
rest of the stack.
Fixes: 0a3fe972a7cb ("HID: core: Mitigate potential OOB by removing bogus memset()")
Cc: stable@vger.kernel.org
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
Acked-by: Johan Hovold <johan@kernel.org>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
Stable-dep-of: 206342541fc8 ("HID: core: introduce hid_safe_input_report()")
Signed-off-by: Sasha Levin <sashal@kernel.org>
(cherry picked from commit 509c2605065004fc4cd86ee50a9350d402785307)
[Lee: Backported to linux-6.12.y and beyond]
Signed-off-by: Lee Jones <lee@kernel.org>
---
drivers/hid/bpf/hid_bpf_dispatch.c | 6 +++--
drivers/hid/hid-core.c | 37 +++++++++++++++++++++---------
drivers/hid/hid-gfrm.c | 4 ++--
drivers/hid/hid-logitech-hidpp.c | 2 +-
drivers/hid/hid-multitouch.c | 2 +-
drivers/hid/hid-primax.c | 2 +-
drivers/hid/hid-vivaldi-common.c | 2 +-
drivers/hid/wacom_sys.c | 6 ++---
drivers/staging/greybus/hid.c | 2 +-
include/linux/hid.h | 4 ++--
include/linux/hid_bpf.h | 14 +++++++----
11 files changed, 51 insertions(+), 30 deletions(-)
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
index 284861c166d9..b711d83dfde1 100644
--- a/drivers/hid/bpf/hid_bpf_dispatch.c
+++ b/drivers/hid/bpf/hid_bpf_dispatch.c
@@ -24,7 +24,8 @@ EXPORT_SYMBOL(hid_ops);
u8 *
dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
- u32 *size, int interrupt, u64 source, bool from_bpf)
+ size_t *buf_size, u32 *size, int interrupt, u64 source,
+ bool from_bpf)
{
struct hid_bpf_ctx_kern ctx_kern = {
.ctx = {
@@ -74,6 +75,7 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
*size = ret;
}
+ *buf_size = ctx_kern.ctx.allocated_size;
return ctx_kern.data;
}
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
@@ -514,7 +516,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b
if (ret)
return ret;
- return hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (u64)(long)ctx, true,
+ return hid_ops->hid_input_report(ctx->hid, type, buf, size, size, 0, (u64)(long)ctx, true,
lock_already_taken);
}
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 294a25330ed0..ceff91722c3c 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1998,24 +1998,32 @@ int __hid_request(struct hid_device *hid, struct hid_report *report,
}
EXPORT_SYMBOL_GPL(__hid_request);
-int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
- int interrupt)
+int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
+ size_t bufsize, u32 size, int interrupt)
{
struct hid_report_enum *report_enum = hid->report_enum + type;
struct hid_report *report;
struct hid_driver *hdrv;
int max_buffer_size = HID_MAX_BUFFER_SIZE;
u32 rsize, csize = size;
+ size_t bsize = bufsize;
u8 *cdata = data;
int ret = 0;
report = hid_get_report(report_enum, data);
if (!report)
- goto out;
+ return 0;
+
+ if (unlikely(bsize < csize)) {
+ hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %ld)\n",
+ report->id, csize, bsize);
+ return -EINVAL;
+ }
if (report_enum->numbered) {
cdata++;
csize--;
+ bsize--;
}
rsize = hid_compute_report_size(report);
@@ -2028,9 +2036,15 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
else if (rsize > max_buffer_size)
rsize = max_buffer_size;
+ if (bsize < rsize) {
+ hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %ld)\n",
+ report->id, rsize, bsize);
+ return -EINVAL;
+ }
+
if (csize < rsize) {
dbg_hid("report %d is too short, (%d < %d)\n", report->id,
- csize, rsize);
+ csize, rsize);
memset(cdata + csize, 0, rsize - csize);
}
@@ -2039,7 +2053,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
if (hid->claimed & HID_CLAIMED_HIDRAW) {
ret = hidraw_report_event(hid, data, size);
if (ret)
- goto out;
+ return ret;
}
if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) {
@@ -2051,15 +2065,15 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
if (hid->claimed & HID_CLAIMED_INPUT)
hidinput_report_event(hid, report);
-out:
+
return ret;
}
EXPORT_SYMBOL_GPL(hid_report_raw_event);
static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
- u8 *data, u32 size, int interrupt, u64 source, bool from_bpf,
- bool lock_already_taken)
+ u8 *data, size_t bufsize, u32 size, int interrupt, u64 source,
+ bool from_bpf, bool lock_already_taken)
{
struct hid_report_enum *report_enum;
struct hid_driver *hdrv;
@@ -2084,7 +2098,8 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
report_enum = hid->report_enum + type;
hdrv = hid->driver;
- data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt, source, from_bpf);
+ data = dispatch_hid_bpf_device_event(hid, type, data, &bufsize, &size, interrupt,
+ source, from_bpf);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
goto unlock;
@@ -2113,7 +2128,7 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
goto unlock;
}
- ret = hid_report_raw_event(hid, type, data, size, interrupt);
+ ret = hid_report_raw_event(hid, type, data, bufsize, size, interrupt);
unlock:
if (!lock_already_taken)
@@ -2135,7 +2150,7 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
int interrupt)
{
- return __hid_input_report(hid, type, data, size, interrupt, 0,
+ return __hid_input_report(hid, type, data, size, size, interrupt, 0,
false, /* from_bpf */
false /* lock_already_taken */);
}
diff --git a/drivers/hid/hid-gfrm.c b/drivers/hid/hid-gfrm.c
index 699186ff2349..d2a56bf92b41 100644
--- a/drivers/hid/hid-gfrm.c
+++ b/drivers/hid/hid-gfrm.c
@@ -66,7 +66,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
switch (data[1]) {
case GFRM100_SEARCH_KEY_DOWN:
ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_dn,
- sizeof(search_key_dn), 1);
+ sizeof(search_key_dn), sizeof(search_key_dn), 1);
break;
case GFRM100_SEARCH_KEY_AUDIO_DATA:
@@ -74,7 +74,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
case GFRM100_SEARCH_KEY_UP:
ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_up,
- sizeof(search_key_up), 1);
+ sizeof(search_key_up), sizeof(search_key_up), 1);
break;
default:
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index d60cd4379e86..858ac2ab46bd 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -3691,7 +3691,7 @@ static int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp,
memcpy(&consumer_report[1], &data[3], 4);
/* We are called from atomic context */
hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT,
- consumer_report, 5, 1);
+ consumer_report, sizeof(consumer_report), 5, 1);
return 1;
}
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index fcf9a806f109..760f9db44c9e 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -500,7 +500,7 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
}
ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf,
- size, 0);
+ size, size, 0);
if (ret)
dev_warn(&hdev->dev, "failed to report feature\n");
}
diff --git a/drivers/hid/hid-primax.c b/drivers/hid/hid-primax.c
index e44d79dff8de..8db054280afb 100644
--- a/drivers/hid/hid-primax.c
+++ b/drivers/hid/hid-primax.c
@@ -44,7 +44,7 @@ static int px_raw_event(struct hid_device *hid, struct hid_report *report,
data[0] |= (1 << (data[idx] - 0xE0));
data[idx] = 0;
}
- hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, 0);
+ hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, size, 0);
return 1;
default: /* unknown report */
diff --git a/drivers/hid/hid-vivaldi-common.c b/drivers/hid/hid-vivaldi-common.c
index bf734055d4b6..b12bb5cc091a 100644
--- a/drivers/hid/hid-vivaldi-common.c
+++ b/drivers/hid/hid-vivaldi-common.c
@@ -85,7 +85,7 @@ void vivaldi_feature_mapping(struct hid_device *hdev,
}
ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, report_data,
- report_len, 0);
+ report_len, report_len, 0);
if (ret) {
dev_warn(&hdev->dev, "failed to report feature %d\n",
field->report->id);
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index 1b1112772777..ffcf65dcf713 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -74,7 +74,7 @@ static void wacom_wac_queue_flush(struct hid_device *hdev,
int err;
size = kfifo_out(fifo, buf, sizeof(buf));
- err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, false);
+ err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, size, false);
if (err) {
hid_warn(hdev, "%s: unable to flush event due to error %d\n",
__func__, err);
@@ -319,7 +319,7 @@ static void wacom_feature_mapping(struct hid_device *hdev,
data, n, WAC_CMD_RETRIES);
if (ret == n && features->type == HID_GENERIC) {
ret = hid_report_raw_event(hdev,
- HID_FEATURE_REPORT, data, n, 0);
+ HID_FEATURE_REPORT, data, n, n, 0);
} else if (ret == 2 && features->type != HID_GENERIC) {
features->touch_max = data[1];
} else {
@@ -380,7 +380,7 @@ static void wacom_feature_mapping(struct hid_device *hdev,
data, n, WAC_CMD_RETRIES);
if (ret == n) {
ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT,
- data, n, 0);
+ data, n, n, 0);
} else {
hid_warn(hdev, "%s: could not retrieve sensor offsets\n",
__func__);
diff --git a/drivers/staging/greybus/hid.c b/drivers/staging/greybus/hid.c
index 63c77a3df591..afa78c96ede8 100644
--- a/drivers/staging/greybus/hid.c
+++ b/drivers/staging/greybus/hid.c
@@ -201,7 +201,7 @@ static void gb_hid_init_report(struct gb_hid *ghid, struct hid_report *report)
* we just need to setup the input fields, so using
* hid_report_raw_event is safe.
*/
- hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, size, 1);
+ hid_report_raw_event(ghid->hid, report->type, ghid->inbuf, ghid->bufsize, size, 1);
}
static void gb_hid_init_reports(struct gb_hid *ghid)
diff --git a/include/linux/hid.h b/include/linux/hid.h
index bef017d6b440..fdd401e4ebde 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -1213,8 +1213,8 @@ static inline u32 hid_report_len(struct hid_report *report)
return DIV_ROUND_UP(report->size, 8) + (report->id > 0);
}
-int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
- int interrupt);
+int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
+ size_t bufsize, u32 size, int interrupt);
/* HID quirks API */
unsigned long hid_lookup_quirk(const struct hid_device *hdev);
diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h
index 6a47223e6460..aa87513acbcd 100644
--- a/include/linux/hid_bpf.h
+++ b/include/linux/hid_bpf.h
@@ -72,8 +72,8 @@ struct hid_ops {
int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len,
u64 source, bool from_bpf);
int (*hid_input_report)(struct hid_device *hid, enum hid_report_type type,
- u8 *data, u32 size, int interrupt, u64 source, bool from_bpf,
- bool lock_already_taken);
+ u8 *data, size_t bufsize, u32 size, int interrupt, u64 source,
+ bool from_bpf, bool lock_already_taken);
struct module *owner;
const struct bus_type *bus_type;
};
@@ -200,7 +200,8 @@ struct hid_bpf {
#ifdef CONFIG_HID_BPF
u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
- u32 *size, int interrupt, u64 source, bool from_bpf);
+ size_t *buf_size, u32 *size, int interrupt, u64 source,
+ bool from_bpf);
int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
unsigned char reportnum, __u8 *buf,
u32 size, enum hid_report_type rtype,
@@ -215,8 +216,11 @@ int hid_bpf_device_init(struct hid_device *hid);
u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc, unsigned int *size);
#else /* CONFIG_HID_BPF */
static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
- u8 *data, u32 *size, int interrupt,
- u64 source, bool from_bpf) { return data; }
+ u8 *data, size_t *buf_size, u32 *size,
+ int interrupt, u64 source, bool from_bpf)
+{
+ return data;
+}
static inline int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
unsigned char reportnum, u8 *buf,
u32 size, enum hid_report_type rtype,
--
2.54.0.823.g6e5bcc1fc9-goog
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox