* Re: [PATCH v7 2/7] Input: synaptics-rmi4 - handle duplicate/unknown PDT entries
From: David Heidelberg @ 2026-03-20 17:12 UTC (permalink / raw)
To: Casey Connolly, Kaustabh Chakraborty, Dmitry Torokhov,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jason A. Donenfeld, Matthias Schiffer, Vincent Huang
Cc: linux-input, devicetree, linux-kernel, phone-devel
In-Reply-To: <55dab1d8-87af-4285-9ab1-924bf392c78d@postmarketos.org>
On 20/03/2026 18:03, Casey Connolly wrote:
>
>
> On 20/03/2026 17:54, David Heidelberg wrote:
>> On 20/03/2026 17:49, Casey Connolly wrote:
>>> Hi David,
>>>
>>> Nice timing with the series, I hit an OOB access (found it when I
>>> enabled UBSAN) with this patch the other day.
>>>
>>> The pdt_scan_state->pdts array should actually be of size
>>> (RMI_PDT_MAX+1).
>>>
>>> Additionally, I think rmi_pdt_entry_is_valid() is missing a bounds check.
>>>
>>> Kind regards,
>>
>>
>> Thanks a lot for catching this and for the detailed notes — that’s very
>> helpful.
>>
>> Since you’re the original author of the commit, I’m completely fine with
>> you taking over the b4 series if you’d prefer. Alternatively, if it’s
>> easier, feel free to just send me a fixed patch and I can incorporate it.
>>
>
> Uh sure, not sure this will apply cleanly I just edited inline it's a
> 3-line delta. Also figured we can drop pdt_count since it's unused.
The pdt_count is used in
Input: synaptics-rmi4 - support fallback values for PDT descriptor bytes
thus should be moved there I assume, but can be dropped here.
David
>
> ---
>
> diff --git a/drivers/input/rmi4/rmi_driver.c
> b/drivers/input/rmi4/rmi_driver.c
> index ccd9338a44dbe..c7d2f68e65487 100644
> --- a/drivers/input/rmi4/rmi_driver.c
> +++ b/drivers/input/rmi4/rmi_driver.c
> @@ -494,12 +494,39 @@ static void rmi_driver_copy_pdt_to_fd(const struct
> pdt_entry *pdt,
> fd->function_version = pdt->function_version;
> }
>
> +static bool rmi_pdt_entry_is_valid(struct rmi_device *rmi_dev,
> + struct pdt_scan_state *state, u8 fn)
> +{
> + if (fn > RMI_PDT_MAX)
> + return false;
> +
> + switch (fn) {
> + case 0x01:
> + case 0x03:
> + case 0x11:
> + case 0x12:
> + case 0x30:
> + case 0x34:
> + case 0x3a:
> + case 0x54:
> + case 0x55:
> + if (state->pdts[fn] == true)
> + return false;
> + break;
> + default:
> + rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev,
> + "PDT has unknown function number %#02x\n", fn);
> + return false;
> + }
> +
> + state->pdts[fn] = true;
> + return true;
> +}
> +
> #define RMI_SCAN_CONTINUE 0
> #define RMI_SCAN_DONE 1
>
> static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
> int page,
> - int *empty_pages,
> + struct pdt_scan_state *state,
> void *ctx,
> int (*callback)(struct rmi_device *rmi_dev,
> void *ctx,
> @@ -522,6 +549,9 @@ static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
> if (RMI4_END_OF_PDT(pdt_entry.function_number))
> break;
>
> + if (!rmi_pdt_entry_is_valid(rmi_dev, state, pdt_entry.function_number))
> + continue;
> +
> retval = callback(rmi_dev, ctx, &pdt_entry);
> if (retval != RMI_SCAN_CONTINUE)
> return retval;
> @@ -532,11 +562,11 @@ static int rmi_scan_pdt_page(struct rmi_device
> *rmi_dev,
> * or more is found, stop scanning.
> */
> if (addr == pdt_start)
> - ++*empty_pages;
> + ++state->empty_pages;
> else
> - *empty_pages = 0;
> + state->empty_pages = 0;
>
> - return (data->bootloader_mode || *empty_pages >= 2) ?
> + return (data->bootloader_mode || state->empty_pages >= 2) ?
> RMI_SCAN_DONE : RMI_SCAN_CONTINUE;
> }
>
> @@ -545,11 +575,11 @@ int rmi_scan_pdt(struct rmi_device *rmi_dev, void
> *ctx,
> void *ctx, const struct pdt_entry *entry))
> {
> int page;
> - int empty_pages = 0;
> + struct pdt_scan_state state = {0, {0}};
> int retval = RMI_SCAN_DONE;
>
> for (page = 0; page <= RMI4_MAX_PAGE; page++) {
> - retval = rmi_scan_pdt_page(rmi_dev, page, &empty_pages,
> + retval = rmi_scan_pdt_page(rmi_dev, page, &state,
> ctx, callback);
> if (retval != RMI_SCAN_CONTINUE)
> break;
> diff --git a/drivers/input/rmi4/rmi_driver.h
> b/drivers/input/rmi4/rmi_driver.h
> index e84495caab151..a4ae2af93ce3a 100644
> --- a/drivers/input/rmi4/rmi_driver.h
> +++ b/drivers/input/rmi4/rmi_driver.h
> @@ -46,6 +46,14 @@ struct pdt_entry {
> u8 function_number;
> };
>
> +#define RMI_PDT_MAX 0x55
> +
> +struct pdt_scan_state {
> + u8 empty_pages;
> + bool pdts[RMI_PDT_MAX + 1];
> +};
> +
> #define RMI_REG_DESC_PRESENSE_BITS (32 * BITS_PER_BYTE)
> #define RMI_REG_DESC_SUBPACKET_BITS (37 * BITS_PER_BYTE)
>
>
--
David Heidelberg
^ permalink raw reply
* Re: [PATCH v7 2/7] Input: synaptics-rmi4 - handle duplicate/unknown PDT entries
From: Casey Connolly @ 2026-03-20 17:03 UTC (permalink / raw)
To: David Heidelberg, Kaustabh Chakraborty, Dmitry Torokhov,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jason A. Donenfeld, Matthias Schiffer, Vincent Huang
Cc: linux-input, devicetree, linux-kernel, phone-devel
In-Reply-To: <3dca0fc0-fe1c-4f84-b336-856f55a6e3da@ixit.cz>
On 20/03/2026 17:54, David Heidelberg wrote:
> On 20/03/2026 17:49, Casey Connolly wrote:
>> Hi David,
>>
>> Nice timing with the series, I hit an OOB access (found it when I
>> enabled UBSAN) with this patch the other day.
>>
>> The pdt_scan_state->pdts array should actually be of size
>> (RMI_PDT_MAX+1).
>>
>> Additionally, I think rmi_pdt_entry_is_valid() is missing a bounds check.
>>
>> Kind regards,
>
>
> Thanks a lot for catching this and for the detailed notes — that’s very
> helpful.
>
> Since you’re the original author of the commit, I’m completely fine with
> you taking over the b4 series if you’d prefer. Alternatively, if it’s
> easier, feel free to just send me a fixed patch and I can incorporate it.
>
Uh sure, not sure this will apply cleanly I just edited inline it's a
3-line delta. Also figured we can drop pdt_count since it's unused.
---
diff --git a/drivers/input/rmi4/rmi_driver.c
b/drivers/input/rmi4/rmi_driver.c
index ccd9338a44dbe..c7d2f68e65487 100644
--- a/drivers/input/rmi4/rmi_driver.c
+++ b/drivers/input/rmi4/rmi_driver.c
@@ -494,12 +494,39 @@ static void rmi_driver_copy_pdt_to_fd(const struct
pdt_entry *pdt,
fd->function_version = pdt->function_version;
}
+static bool rmi_pdt_entry_is_valid(struct rmi_device *rmi_dev,
+ struct pdt_scan_state *state, u8 fn)
+{
+ if (fn > RMI_PDT_MAX)
+ return false;
+
+ switch (fn) {
+ case 0x01:
+ case 0x03:
+ case 0x11:
+ case 0x12:
+ case 0x30:
+ case 0x34:
+ case 0x3a:
+ case 0x54:
+ case 0x55:
+ if (state->pdts[fn] == true)
+ return false;
+ break;
+ default:
+ rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev,
+ "PDT has unknown function number %#02x\n", fn);
+ return false;
+ }
+
+ state->pdts[fn] = true;
+ return true;
+}
+
#define RMI_SCAN_CONTINUE 0
#define RMI_SCAN_DONE 1
static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
int page,
- int *empty_pages,
+ struct pdt_scan_state *state,
void *ctx,
int (*callback)(struct rmi_device *rmi_dev,
void *ctx,
@@ -522,6 +549,9 @@ static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
if (RMI4_END_OF_PDT(pdt_entry.function_number))
break;
+ if (!rmi_pdt_entry_is_valid(rmi_dev, state, pdt_entry.function_number))
+ continue;
+
retval = callback(rmi_dev, ctx, &pdt_entry);
if (retval != RMI_SCAN_CONTINUE)
return retval;
@@ -532,11 +562,11 @@ static int rmi_scan_pdt_page(struct rmi_device
*rmi_dev,
* or more is found, stop scanning.
*/
if (addr == pdt_start)
- ++*empty_pages;
+ ++state->empty_pages;
else
- *empty_pages = 0;
+ state->empty_pages = 0;
- return (data->bootloader_mode || *empty_pages >= 2) ?
+ return (data->bootloader_mode || state->empty_pages >= 2) ?
RMI_SCAN_DONE : RMI_SCAN_CONTINUE;
}
@@ -545,11 +575,11 @@ int rmi_scan_pdt(struct rmi_device *rmi_dev, void
*ctx,
void *ctx, const struct pdt_entry *entry))
{
int page;
- int empty_pages = 0;
+ struct pdt_scan_state state = {0, {0}};
int retval = RMI_SCAN_DONE;
for (page = 0; page <= RMI4_MAX_PAGE; page++) {
- retval = rmi_scan_pdt_page(rmi_dev, page, &empty_pages,
+ retval = rmi_scan_pdt_page(rmi_dev, page, &state,
ctx, callback);
if (retval != RMI_SCAN_CONTINUE)
break;
diff --git a/drivers/input/rmi4/rmi_driver.h
b/drivers/input/rmi4/rmi_driver.h
index e84495caab151..a4ae2af93ce3a 100644
--- a/drivers/input/rmi4/rmi_driver.h
+++ b/drivers/input/rmi4/rmi_driver.h
@@ -46,6 +46,14 @@ struct pdt_entry {
u8 function_number;
};
+#define RMI_PDT_MAX 0x55
+
+struct pdt_scan_state {
+ u8 empty_pages;
+ bool pdts[RMI_PDT_MAX + 1];
+};
+
#define RMI_REG_DESC_PRESENSE_BITS (32 * BITS_PER_BYTE)
#define RMI_REG_DESC_SUBPACKET_BITS (37 * BITS_PER_BYTE)
--
2.53.0
--
// Casey (she/her)
^ permalink raw reply related
* Re: [PATCH v7 2/7] Input: synaptics-rmi4 - handle duplicate/unknown PDT entries
From: David Heidelberg @ 2026-03-20 16:54 UTC (permalink / raw)
To: Casey Connolly, Kaustabh Chakraborty, Dmitry Torokhov,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jason A. Donenfeld, Matthias Schiffer, Vincent Huang
Cc: linux-input, devicetree, linux-kernel, phone-devel
In-Reply-To: <fba73d66-4300-4c4d-9bf6-4b38a4e847d7@postmarketos.org>
On 20/03/2026 17:49, Casey Connolly wrote:
> Hi David,
>
> Nice timing with the series, I hit an OOB access (found it when I
> enabled UBSAN) with this patch the other day.
>
> The pdt_scan_state->pdts array should actually be of size (RMI_PDT_MAX+1).
>
> Additionally, I think rmi_pdt_entry_is_valid() is missing a bounds check.
>
> Kind regards,
Thanks a lot for catching this and for the detailed notes — that’s very helpful.
Since you’re the original author of the commit, I’m completely fine with you
taking over the b4 series if you’d prefer. Alternatively, if it’s easier, feel
free to just send me a fixed patch and I can incorporate it.
Whichever works best for you.
David>
> On 20/03/2026 17:44, David Heidelberg via B4 Relay wrote:
>> From: Casey Connolly <casey.connolly@linaro.org>
>>
>> Some third party rmi4-compatible ICs don't expose their PDT entries
>> very well. Add a few checks to skip duplicate entries as well as entries
>> for unsupported functions.
>>
>> This is required to support some phones with third party displays.
>>
>> Validated on a stock OnePlus 6T (original parts):
>> manufacturer: Synaptics, product: S3706B, fw id: 2852315
>>
>> Co-developed-by: Kaustabh Chakraborty <kauschluss@disroot.org>
>> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
>> Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
>> Co-developed-by: David Heidelberg <david@ixit.cz>
>> Signed-off-by: David Heidelberg <david@ixit.cz>
[...]
^ permalink raw reply
* Re: [PATCH v7 2/7] Input: synaptics-rmi4 - handle duplicate/unknown PDT entries
From: Casey Connolly @ 2026-03-20 16:49 UTC (permalink / raw)
To: david, Kaustabh Chakraborty, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason A. Donenfeld,
Matthias Schiffer, Vincent Huang
Cc: linux-input, devicetree, linux-kernel, phone-devel
In-Reply-To: <20260320-synaptics-rmi4-v7-2-379360de18d0@ixit.cz>
Hi David,
Nice timing with the series, I hit an OOB access (found it when I
enabled UBSAN) with this patch the other day.
The pdt_scan_state->pdts array should actually be of size (RMI_PDT_MAX+1).
Additionally, I think rmi_pdt_entry_is_valid() is missing a bounds check.
Kind regards,
On 20/03/2026 17:44, David Heidelberg via B4 Relay wrote:
> From: Casey Connolly <casey.connolly@linaro.org>
>
> Some third party rmi4-compatible ICs don't expose their PDT entries
> very well. Add a few checks to skip duplicate entries as well as entries
> for unsupported functions.
>
> This is required to support some phones with third party displays.
>
> Validated on a stock OnePlus 6T (original parts):
> manufacturer: Synaptics, product: S3706B, fw id: 2852315
>
> Co-developed-by: Kaustabh Chakraborty <kauschluss@disroot.org>
> Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
> Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
> Co-developed-by: David Heidelberg <david@ixit.cz>
> Signed-off-by: David Heidelberg <david@ixit.cz>
> ---
> drivers/input/rmi4/rmi_driver.c | 42 +++++++++++++++++++++++++++++++++++------
> drivers/input/rmi4/rmi_driver.h | 8 ++++++++
> 2 files changed, 44 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
> index ccd9338a44dbe..c7d2f68e65487 100644
> --- a/drivers/input/rmi4/rmi_driver.c
> +++ b/drivers/input/rmi4/rmi_driver.c
> @@ -494,12 +494,39 @@ static void rmi_driver_copy_pdt_to_fd(const struct pdt_entry *pdt,
> fd->function_version = pdt->function_version;
> }
>
> +static bool rmi_pdt_entry_is_valid(struct rmi_device *rmi_dev,
> + struct pdt_scan_state *state, u8 fn)
> +{
> + switch (fn) {
> + case 0x01:
> + case 0x03:
> + case 0x11:
> + case 0x12:
> + case 0x30:
> + case 0x34:
> + case 0x3a:
> + case 0x54:
> + case 0x55:
> + if (state->pdts[fn] == true)
> + return false;
> + break;
> + default:
> + rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev,
> + "PDT has unknown function number %#02x\n", fn);
> + return false;
> + }
> +
> + state->pdts[fn] = true;
> + state->pdt_count++;
> + return true;
> +}
> +
> #define RMI_SCAN_CONTINUE 0
> #define RMI_SCAN_DONE 1
>
> static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
> int page,
> - int *empty_pages,
> + struct pdt_scan_state *state,
> void *ctx,
> int (*callback)(struct rmi_device *rmi_dev,
> void *ctx,
> @@ -522,6 +549,9 @@ static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
> if (RMI4_END_OF_PDT(pdt_entry.function_number))
> break;
>
> + if (!rmi_pdt_entry_is_valid(rmi_dev, state, pdt_entry.function_number))
> + continue;
> +
> retval = callback(rmi_dev, ctx, &pdt_entry);
> if (retval != RMI_SCAN_CONTINUE)
> return retval;
> @@ -532,11 +562,11 @@ static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
> * or more is found, stop scanning.
> */
> if (addr == pdt_start)
> - ++*empty_pages;
> + ++state->empty_pages;
> else
> - *empty_pages = 0;
> + state->empty_pages = 0;
>
> - return (data->bootloader_mode || *empty_pages >= 2) ?
> + return (data->bootloader_mode || state->empty_pages >= 2) ?
> RMI_SCAN_DONE : RMI_SCAN_CONTINUE;
> }
>
> @@ -545,11 +575,11 @@ int rmi_scan_pdt(struct rmi_device *rmi_dev, void *ctx,
> void *ctx, const struct pdt_entry *entry))
> {
> int page;
> - int empty_pages = 0;
> + struct pdt_scan_state state = {0, 0, {0}};
> int retval = RMI_SCAN_DONE;
>
> for (page = 0; page <= RMI4_MAX_PAGE; page++) {
> - retval = rmi_scan_pdt_page(rmi_dev, page, &empty_pages,
> + retval = rmi_scan_pdt_page(rmi_dev, page, &state,
> ctx, callback);
> if (retval != RMI_SCAN_CONTINUE)
> break;
> diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h
> index e84495caab151..a4ae2af93ce3a 100644
> --- a/drivers/input/rmi4/rmi_driver.h
> +++ b/drivers/input/rmi4/rmi_driver.h
> @@ -46,6 +46,14 @@ struct pdt_entry {
> u8 function_number;
> };
>
> +#define RMI_PDT_MAX 0x55
> +
> +struct pdt_scan_state {
> + u8 empty_pages;
> + u8 pdt_count;
> + bool pdts[RMI_PDT_MAX];
> +};
> +
> #define RMI_REG_DESC_PRESENSE_BITS (32 * BITS_PER_BYTE)
> #define RMI_REG_DESC_SUBPACKET_BITS (37 * BITS_PER_BYTE)
>
>
--
// Casey (she/her)
^ permalink raw reply
* [PATCH v7 7/7] Input: synaptics-rmi4 - support fallback values for PDT descriptor bytes
From: David Heidelberg via B4 Relay @ 2026-03-20 16:44 UTC (permalink / raw)
To: Kaustabh Chakraborty, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason A. Donenfeld,
Matthias Schiffer, Vincent Huang
Cc: David Heidelberg, linux-input, devicetree, linux-kernel,
Casey Connolly, phone-devel
In-Reply-To: <20260320-synaptics-rmi4-v7-0-379360de18d0@ixit.cz>
From: Kaustabh Chakraborty <kauschluss@disroot.org>
Some replacement displays include third-party touch ICs which do not
expose the function number and the interrupt status in its PDT entries.
OnePlus 6 (original touch IC)
rmi4_i2c 12-0020: read 6 bytes at 0x00e3: 0 (2b 22 0d 06 01 01)
OnePlus 6 (aftermarket touch IC)
rmi4_i2c 12-0020: read 6 bytes at 0x00e3: 0 (2c 23 0d 06 00 00)
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
[codeflow adjustments, checkpatch fixes, wording]
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/rmi4/rmi_driver.c | 62 +++++++++++++++++++++++++++++++++++------
drivers/input/rmi4/rmi_driver.h | 2 ++
include/linux/rmi.h | 3 ++
3 files changed, 59 insertions(+), 8 deletions(-)
diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
index 93a190e333c66..bb1db5bbb3abb 100644
--- a/drivers/input/rmi4/rmi_driver.c
+++ b/drivers/input/rmi4/rmi_driver.c
@@ -462,9 +462,10 @@ static int rmi_driver_reset_handler(struct rmi_device *rmi_dev)
return 0;
}
-static int rmi_read_pdt_entry(struct rmi_device *rmi_dev,
- struct pdt_entry *entry, u16 pdt_address)
+static int rmi_read_pdt_entry(struct rmi_device *rmi_dev, struct pdt_entry *entry,
+ struct pdt_scan_state *state, u16 pdt_address)
{
+ const struct rmi_device_platform_data *pdata = rmi_get_platform_data(rmi_dev);
u8 buf[RMI_PDT_ENTRY_SIZE];
int error;
@@ -475,6 +476,21 @@ static int rmi_read_pdt_entry(struct rmi_device *rmi_dev,
return error;
}
+ if (pdata->pdt_fallback_size > state->pdt_count * RMI_OF_PDT_DESC_CELLS + 1) {
+ /* Use the description bytes from the driver */
+ buf[5] = pdata->pdt_fallback_desc[state->pdt_count * RMI_OF_PDT_DESC_CELLS];
+ buf[4] = pdata->pdt_fallback_desc[state->pdt_count * RMI_OF_PDT_DESC_CELLS + 1];
+
+ error = rmi_read_block(rmi_dev, pdt_address, buf,
+ RMI_PDT_ENTRY_SIZE - 2);
+ if (error) {
+ dev_err(&rmi_dev->dev,
+ "Read PDT entry at %#06x failed, code: %d.\n",
+ pdt_address, error);
+ return error;
+ }
+ }
+
entry->page_start = pdt_address & RMI4_PAGE_MASK;
entry->query_base_addr = buf[0];
entry->command_base_addr = buf[1];
@@ -547,7 +563,7 @@ static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
int retval;
for (addr = pdt_start; addr >= pdt_end; addr -= RMI_PDT_ENTRY_SIZE) {
- error = rmi_read_pdt_entry(rmi_dev, &pdt_entry, addr);
+ error = rmi_read_pdt_entry(rmi_dev, &pdt_entry, state, addr);
if (error)
return error;
@@ -1024,9 +1040,13 @@ static int rmi_driver_remove(struct device *dev)
}
#ifdef CONFIG_OF
-static int rmi_driver_of_probe(struct device *dev,
- struct rmi_device_platform_data *pdata)
+static const u8 rmi_s3706_fallback_pdt[] = {34, 41, 01, 01, 12, 01};
+
+static int rmi_driver_of_probe(struct rmi_device *rmi_dev,
+ struct rmi_device_platform_data *pdata)
{
+ struct device *dev = rmi_dev->xport->dev;
+ u8 buf[RMI_PDT_ENTRY_SIZE];
int retval;
retval = rmi_of_property_read_u32(dev, &pdata->reset_delay_ms,
@@ -1034,11 +1054,37 @@ static int rmi_driver_of_probe(struct device *dev,
if (retval)
return retval;
+ /*
+ * In some aftermerket touch ICs, the first PDT entry is empty and
+ * the function number register is 0. If so, the driver
+ * may have provide backup PDT entries.
+ */
+
+ retval = rmi_read_block(rmi_dev, PDT_START_SCAN_LOCATION,
+ buf, RMI_PDT_ENTRY_SIZE);
+ if (retval) {
+ dev_err(dev, "Read PDT entry at %#06x failed, code: %d.\n",
+ PDT_START_SCAN_LOCATION, retval);
+ return retval;
+ }
+
+ if (!RMI4_END_OF_PDT(buf[5]))
+ return 0;
+
+ /* List of known PDT entries per compatible. */
+ if (of_device_is_compatible(dev->of_node, "syna,rmi4-s3706b")) {
+ pdata->pdt_fallback_desc = rmi_s3706_fallback_pdt;
+ pdata->pdt_fallback_size = ARRAY_SIZE(rmi_s3706_fallback_pdt);
+ } else {
+ dev_err(dev, "First PDT entry is empty and no backup values provided.\n");
+ return -EINVAL;
+ }
+
return 0;
}
#else
-static inline int rmi_driver_of_probe(struct device *dev,
- struct rmi_device_platform_data *pdata)
+static inline int rmi_driver_of_probe(struct rmi_device *rmi_dev,
+ struct rmi_device_platform_data *pdata)
{
return -ENODEV;
}
@@ -1159,7 +1205,7 @@ static int rmi_driver_probe(struct device *dev)
pdata = rmi_get_platform_data(rmi_dev);
if (rmi_dev->xport->dev->of_node) {
- retval = rmi_driver_of_probe(rmi_dev->xport->dev, pdata);
+ retval = rmi_driver_of_probe(rmi_dev, pdata);
if (retval)
return retval;
}
diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h
index a4ae2af93ce3a..b931f428713bf 100644
--- a/drivers/input/rmi4/rmi_driver.h
+++ b/drivers/input/rmi4/rmi_driver.h
@@ -31,6 +31,8 @@
#define RMI_PDT_FUNCTION_VERSION_MASK 0x60
#define RMI_PDT_INT_SOURCE_COUNT_MASK 0x07
+#define RMI_OF_PDT_DESC_CELLS 2
+
#define PDT_START_SCAN_LOCATION 0x00e9
#define PDT_END_SCAN_LOCATION 0x0005
#define RMI4_END_OF_PDT(id) ((id) == 0x00 || (id) == 0xff)
diff --git a/include/linux/rmi.h b/include/linux/rmi.h
index ab7eea01ab427..4ba2cefac8558 100644
--- a/include/linux/rmi.h
+++ b/include/linux/rmi.h
@@ -214,6 +214,9 @@ struct rmi_device_platform_data {
int reset_delay_ms;
int irq;
+ unsigned int pdt_fallback_size;
+ const u8 *pdt_fallback_desc;
+
struct rmi_device_platform_data_spi spi_data;
/* function handler pdata */
--
2.53.0
^ permalink raw reply related
* [PATCH v7 6/7] Input: synaptics-rmi4 - read product ID on aftermarket touch ICs
From: David Heidelberg via B4 Relay @ 2026-03-20 16:44 UTC (permalink / raw)
To: Kaustabh Chakraborty, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason A. Donenfeld,
Matthias Schiffer, Vincent Huang
Cc: David Heidelberg, linux-input, devicetree, linux-kernel,
Casey Connolly, phone-devel
In-Reply-To: <20260320-synaptics-rmi4-v7-0-379360de18d0@ixit.cz>
From: Kaustabh Chakraborty <kauschluss@disroot.org>
Some replacement displays include third-party touch ICs which do not
report the product ID correctly unless we read directly from the
product ID register. Add a check and a fallback read to handle this.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/rmi4/rmi_f01.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/input/rmi4/rmi_f01.c b/drivers/input/rmi4/rmi_f01.c
index 47be64284b25e..2278e9b6a9207 100644
--- a/drivers/input/rmi4/rmi_f01.c
+++ b/drivers/input/rmi4/rmi_f01.c
@@ -250,6 +250,20 @@ static int rmi_f01_read_properties(struct rmi_device *rmi_dev,
}
}
+ /*
+ * Some aftermarket ICs put garbage into the product id field unless
+ * we read directly from the product id register.
+ */
+ if (props->product_id[0] < 0x20) {
+ ret = rmi_read_block(rmi_dev, query_base_addr + 11,
+ props->product_id, RMI_PRODUCT_ID_LENGTH);
+ if (ret) {
+ dev_err(&rmi_dev->dev,
+ "Failed to read product id: %d\n", ret);
+ return ret;
+ }
+ }
+
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v7 5/7] Input: synaptics-rmi4 - don't do unaligned reads in IRQ context
From: David Heidelberg via B4 Relay @ 2026-03-20 16:44 UTC (permalink / raw)
To: Kaustabh Chakraborty, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason A. Donenfeld,
Matthias Schiffer, Vincent Huang
Cc: David Heidelberg, linux-input, devicetree, linux-kernel,
Casey Connolly, phone-devel
In-Reply-To: <20260320-synaptics-rmi4-v7-0-379360de18d0@ixit.cz>
From: Kaustabh Chakraborty <kauschluss@disroot.org>
Some replacement displays include third-party touch ICs which incur a
significant penalty (1-2 seconds) when doing certain unaligned reads.
This is enough to break functionality when it happens in the hot path,
so adjust the interrupt handler to not read from an unaligned address.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/rmi4/rmi_driver.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
index c7d2f68e65487..93a190e333c66 100644
--- a/drivers/input/rmi4/rmi_driver.c
+++ b/drivers/input/rmi4/rmi_driver.c
@@ -137,9 +137,14 @@ static int rmi_process_interrupt_requests(struct rmi_device *rmi_dev)
return 0;
if (!data->attn_data.data) {
+ /*
+ * Read the device status register as well and ignore it.
+ * Some aftermarket ICs have issues with interrupt requests
+ * otherwise.
+ */
error = rmi_read_block(rmi_dev,
- data->f01_container->fd.data_base_addr + 1,
- data->irq_status, data->num_of_irq_regs);
+ data->f01_container->fd.data_base_addr,
+ (u8 *)data->irq_status - 1, data->num_of_irq_regs + 1);
if (error < 0) {
dev_err(dev, "Failed to read irqs, code=%d\n", error);
return error;
@@ -1079,16 +1084,17 @@ int rmi_probe_interrupts(struct rmi_driver_data *data)
data->num_of_irq_regs = (data->irq_count + 7) / 8;
size = BITS_TO_LONGS(data->irq_count) * sizeof(unsigned long);
- data->irq_memory = devm_kcalloc(dev, size, 4, GFP_KERNEL);
+ data->irq_memory = devm_kzalloc(dev, size * 4 + 1, GFP_KERNEL);
if (!data->irq_memory) {
dev_err(dev, "Failed to allocate memory for irq masks.\n");
return -ENOMEM;
}
- data->irq_status = data->irq_memory + size * 0;
- data->fn_irq_bits = data->irq_memory + size * 1;
- data->current_irq_mask = data->irq_memory + size * 2;
- data->new_irq_mask = data->irq_memory + size * 3;
+ /* The first byte is reserved for the device status register */
+ data->irq_status = data->irq_memory + size * 0 + 1;
+ data->fn_irq_bits = data->irq_memory + size * 1 + 1;
+ data->current_irq_mask = data->irq_memory + size * 2 + 1;
+ data->new_irq_mask = data->irq_memory + size * 3 + 1;
return retval;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v7 4/7] Input: synaptics-rmi4 - f55: handle zero electrode count
From: David Heidelberg via B4 Relay @ 2026-03-20 16:44 UTC (permalink / raw)
To: Kaustabh Chakraborty, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason A. Donenfeld,
Matthias Schiffer, Vincent Huang
Cc: David Heidelberg, linux-input, devicetree, linux-kernel,
Casey Connolly, phone-devel
In-Reply-To: <20260320-synaptics-rmi4-v7-0-379360de18d0@ixit.cz>
From: Kaustabh Chakraborty <kauschluss@disroot.org>
Some third party ICs claim to support f55 but report an electrode count
of 0. Catch this and bail out early so that we don't confuse the i2c bus
with 0 sized reads.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
[simplify code, adjust wording]
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/rmi4/rmi_f55.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/input/rmi4/rmi_f55.c b/drivers/input/rmi4/rmi_f55.c
index 488adaca4dd00..776c915b82e72 100644
--- a/drivers/input/rmi4/rmi_f55.c
+++ b/drivers/input/rmi4/rmi_f55.c
@@ -52,6 +52,11 @@ static int rmi_f55_detect(struct rmi_function *fn)
f55->num_rx_electrodes = f55->qry[F55_NUM_RX_OFFSET];
f55->num_tx_electrodes = f55->qry[F55_NUM_TX_OFFSET];
+ if (!f55->num_rx_electrodes || !f55->num_tx_electrodes) {
+ dev_err(&fn->dev, "%s: F55 query returned no electrodes, giving up\n",
+ __func__);
+ return -EINVAL;
+ }
f55->cfg_num_rx_electrodes = f55->num_rx_electrodes;
f55->cfg_num_tx_electrodes = f55->num_rx_electrodes;
--
2.53.0
^ permalink raw reply related
* [PATCH v7 3/7] Input: synaptics-rmi4 - f12: use hardcoded values for aftermarket touch ICs
From: David Heidelberg via B4 Relay @ 2026-03-20 16:44 UTC (permalink / raw)
To: Kaustabh Chakraborty, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason A. Donenfeld,
Matthias Schiffer, Vincent Huang
Cc: David Heidelberg, linux-input, devicetree, linux-kernel,
Casey Connolly, phone-devel
In-Reply-To: <20260320-synaptics-rmi4-v7-0-379360de18d0@ixit.cz>
From: Kaustabh Chakraborty <kauschluss@disroot.org>
Some replacement displays include third-party touch ICs which are
devoid of register descriptors. Create a fake data register descriptor
for such ICs and provide hardcoded default values.
It isn't possible to reliably determine if the touch IC is original or
not, so these fallback values are offered as an alternative to the error
path when register descriptors aren't available.
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
[changes for readability / codeflow, checkpatch fixes]
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/rmi4/rmi_f12.c | 117 +++++++++++++++++++++++++++++++++----------
1 file changed, 91 insertions(+), 26 deletions(-)
diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c
index 8246fe77114bb..1a103cc5f2235 100644
--- a/drivers/input/rmi4/rmi_f12.c
+++ b/drivers/input/rmi4/rmi_f12.c
@@ -218,6 +218,41 @@ static void rmi_f12_process_objects(struct f12_data *f12, u8 *data1, int size)
rmi_2d_sensor_abs_report(sensor, &sensor->objs[i], i);
}
+static void rmi_f12_set_hardcoded_desc(struct rmi_function *fn, struct f12_data *f12)
+{
+ struct rmi_2d_sensor *sensor = &f12->sensor;
+ struct rmi_register_desc_item *reg_desc;
+
+ /* We have no f12->data_reg_desc, so the pkt_size is 0, override it with
+ * a somewhat sensible default (this corresponds to 10 fingers).
+ */
+ sensor->pkt_size = 88;
+
+ /*
+ * There are no register descriptors to get these values from.
+ * We set them to high values to either be overwritten by the clip
+ * properties from devicetree, or to just not get in the way.
+ */
+ sensor->max_x = 65535;
+ sensor->max_y = 65535;
+
+ /*
+ * Create the Data1 register descriptor so that touch events
+ * can work properly.
+ */
+ reg_desc = devm_kcalloc(&fn->dev, 1,
+ sizeof(struct rmi_register_desc_item), GFP_KERNEL);
+ reg_desc->reg = 1;
+ reg_desc->reg_size = 80;
+ reg_desc->num_subpackets = 10;
+
+ f12->data1 = reg_desc;
+ f12->data1_offset = 0;
+ sensor->nbr_fingers = reg_desc->num_subpackets;
+ sensor->report_abs = 1;
+ sensor->attn_size += reg_desc->reg_size;
+}
+
static irqreturn_t rmi_f12_attention(int irq, void *ctx)
{
int retval;
@@ -338,6 +373,40 @@ static int rmi_f12_config(struct rmi_function *fn)
return 0;
}
+static int rmi_f12_sensor_init(struct rmi_function *fn, struct f12_data *f12)
+{
+ struct rmi_2d_sensor *sensor = &f12->sensor;
+
+ sensor->fn = fn;
+ f12->data_addr = fn->fd.data_base_addr;
+
+ /* On quirky devices that don't have a data_reg_desc we hardcode the packet
+ * in rmi_f12_set_hardcoded_desc(). Make sure not to set it to 0 here.
+ */
+ if (!sensor->pkt_size)
+ sensor->pkt_size = rmi_register_desc_calc_size(&f12->data_reg_desc);
+
+ sensor->axis_align =
+ f12->sensor_pdata.axis_align;
+
+ sensor->x_mm = f12->sensor_pdata.x_mm;
+ sensor->y_mm = f12->sensor_pdata.y_mm;
+ sensor->dribble = f12->sensor_pdata.dribble;
+
+ if (sensor->sensor_type == rmi_sensor_default)
+ sensor->sensor_type =
+ f12->sensor_pdata.sensor_type;
+
+ rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: data packet size: %d\n", __func__,
+ sensor->pkt_size);
+
+ sensor->data_pkt = devm_kzalloc(&fn->dev, sensor->pkt_size, GFP_KERNEL);
+ if (!sensor->data_pkt)
+ return -ENOMEM;
+
+ return 0;
+}
+
static int rmi_f12_probe(struct rmi_function *fn)
{
struct f12_data *f12;
@@ -351,6 +420,7 @@ static int rmi_f12_probe(struct rmi_function *fn)
struct rmi_driver_data *drvdata = dev_get_drvdata(&rmi_dev->dev);
u16 data_offset = 0;
int mask_size;
+ bool hardcoded_desc_quirk = false;
rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s\n", __func__);
@@ -365,9 +435,9 @@ static int rmi_f12_probe(struct rmi_function *fn)
++query_addr;
if (!(buf & BIT(0))) {
- dev_err(&fn->dev,
- "Behavior of F12 without register descriptors is undefined.\n");
- return -ENODEV;
+ rmi_dbg(RMI_DEBUG_FN, &fn->dev,
+ "No register descriptors defined for F12, using fallback\n");
+ hardcoded_desc_quirk = true;
}
f12 = devm_kzalloc(&fn->dev, sizeof(struct f12_data) + mask_size * 2,
@@ -375,6 +445,8 @@ static int rmi_f12_probe(struct rmi_function *fn)
if (!f12)
return -ENOMEM;
+ dev_set_drvdata(&fn->dev, f12);
+
f12->abs_mask = (unsigned long *)((char *)f12
+ sizeof(struct f12_data));
f12->rel_mask = (unsigned long *)((char *)f12
@@ -393,6 +465,18 @@ static int rmi_f12_probe(struct rmi_function *fn)
f12->sensor_pdata = pdata->sensor_pdata;
}
+ sensor = &f12->sensor;
+
+ if (hardcoded_desc_quirk) {
+ rmi_f12_set_hardcoded_desc(fn, f12);
+
+ ret = rmi_f12_sensor_init(fn, f12);
+ if (ret)
+ return ret;
+
+ goto skip_register_desc;
+ }
+
ret = rmi_read_register_desc(rmi_dev, query_addr,
&f12->query_reg_desc);
if (ret) {
@@ -423,29 +507,9 @@ static int rmi_f12_probe(struct rmi_function *fn)
}
query_addr += 3;
- sensor = &f12->sensor;
- sensor->fn = fn;
- f12->data_addr = fn->fd.data_base_addr;
- sensor->pkt_size = rmi_register_desc_calc_size(&f12->data_reg_desc);
-
- sensor->axis_align =
- f12->sensor_pdata.axis_align;
-
- sensor->x_mm = f12->sensor_pdata.x_mm;
- sensor->y_mm = f12->sensor_pdata.y_mm;
- sensor->dribble = f12->sensor_pdata.dribble;
-
- if (sensor->sensor_type == rmi_sensor_default)
- sensor->sensor_type =
- f12->sensor_pdata.sensor_type;
-
- rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: data packet size: %d\n", __func__,
- sensor->pkt_size);
- sensor->data_pkt = devm_kzalloc(&fn->dev, sensor->pkt_size, GFP_KERNEL);
- if (!sensor->data_pkt)
- return -ENOMEM;
-
- dev_set_drvdata(&fn->dev, f12);
+ ret = rmi_f12_sensor_init(fn, f12);
+ if (ret)
+ return ret;
ret = rmi_f12_read_sensor_tuning(f12);
if (ret)
@@ -543,6 +607,7 @@ static int rmi_f12_probe(struct rmi_function *fn)
data_offset += item->reg_size;
}
+skip_register_desc:
/* allocate the in-kernel tracking buffers */
sensor->tracking_pos = devm_kcalloc(&fn->dev,
sensor->nbr_fingers, sizeof(struct input_mt_pos),
--
2.53.0
^ permalink raw reply related
* [PATCH v7 2/7] Input: synaptics-rmi4 - handle duplicate/unknown PDT entries
From: David Heidelberg via B4 Relay @ 2026-03-20 16:44 UTC (permalink / raw)
To: Kaustabh Chakraborty, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason A. Donenfeld,
Matthias Schiffer, Vincent Huang
Cc: David Heidelberg, linux-input, devicetree, linux-kernel,
Casey Connolly, phone-devel
In-Reply-To: <20260320-synaptics-rmi4-v7-0-379360de18d0@ixit.cz>
From: Casey Connolly <casey.connolly@linaro.org>
Some third party rmi4-compatible ICs don't expose their PDT entries
very well. Add a few checks to skip duplicate entries as well as entries
for unsupported functions.
This is required to support some phones with third party displays.
Validated on a stock OnePlus 6T (original parts):
manufacturer: Synaptics, product: S3706B, fw id: 2852315
Co-developed-by: Kaustabh Chakraborty <kauschluss@disroot.org>
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/rmi4/rmi_driver.c | 42 +++++++++++++++++++++++++++++++++++------
drivers/input/rmi4/rmi_driver.h | 8 ++++++++
2 files changed, 44 insertions(+), 6 deletions(-)
diff --git a/drivers/input/rmi4/rmi_driver.c b/drivers/input/rmi4/rmi_driver.c
index ccd9338a44dbe..c7d2f68e65487 100644
--- a/drivers/input/rmi4/rmi_driver.c
+++ b/drivers/input/rmi4/rmi_driver.c
@@ -494,12 +494,39 @@ static void rmi_driver_copy_pdt_to_fd(const struct pdt_entry *pdt,
fd->function_version = pdt->function_version;
}
+static bool rmi_pdt_entry_is_valid(struct rmi_device *rmi_dev,
+ struct pdt_scan_state *state, u8 fn)
+{
+ switch (fn) {
+ case 0x01:
+ case 0x03:
+ case 0x11:
+ case 0x12:
+ case 0x30:
+ case 0x34:
+ case 0x3a:
+ case 0x54:
+ case 0x55:
+ if (state->pdts[fn] == true)
+ return false;
+ break;
+ default:
+ rmi_dbg(RMI_DEBUG_CORE, &rmi_dev->dev,
+ "PDT has unknown function number %#02x\n", fn);
+ return false;
+ }
+
+ state->pdts[fn] = true;
+ state->pdt_count++;
+ return true;
+}
+
#define RMI_SCAN_CONTINUE 0
#define RMI_SCAN_DONE 1
static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
int page,
- int *empty_pages,
+ struct pdt_scan_state *state,
void *ctx,
int (*callback)(struct rmi_device *rmi_dev,
void *ctx,
@@ -522,6 +549,9 @@ static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
if (RMI4_END_OF_PDT(pdt_entry.function_number))
break;
+ if (!rmi_pdt_entry_is_valid(rmi_dev, state, pdt_entry.function_number))
+ continue;
+
retval = callback(rmi_dev, ctx, &pdt_entry);
if (retval != RMI_SCAN_CONTINUE)
return retval;
@@ -532,11 +562,11 @@ static int rmi_scan_pdt_page(struct rmi_device *rmi_dev,
* or more is found, stop scanning.
*/
if (addr == pdt_start)
- ++*empty_pages;
+ ++state->empty_pages;
else
- *empty_pages = 0;
+ state->empty_pages = 0;
- return (data->bootloader_mode || *empty_pages >= 2) ?
+ return (data->bootloader_mode || state->empty_pages >= 2) ?
RMI_SCAN_DONE : RMI_SCAN_CONTINUE;
}
@@ -545,11 +575,11 @@ int rmi_scan_pdt(struct rmi_device *rmi_dev, void *ctx,
void *ctx, const struct pdt_entry *entry))
{
int page;
- int empty_pages = 0;
+ struct pdt_scan_state state = {0, 0, {0}};
int retval = RMI_SCAN_DONE;
for (page = 0; page <= RMI4_MAX_PAGE; page++) {
- retval = rmi_scan_pdt_page(rmi_dev, page, &empty_pages,
+ retval = rmi_scan_pdt_page(rmi_dev, page, &state,
ctx, callback);
if (retval != RMI_SCAN_CONTINUE)
break;
diff --git a/drivers/input/rmi4/rmi_driver.h b/drivers/input/rmi4/rmi_driver.h
index e84495caab151..a4ae2af93ce3a 100644
--- a/drivers/input/rmi4/rmi_driver.h
+++ b/drivers/input/rmi4/rmi_driver.h
@@ -46,6 +46,14 @@ struct pdt_entry {
u8 function_number;
};
+#define RMI_PDT_MAX 0x55
+
+struct pdt_scan_state {
+ u8 empty_pages;
+ u8 pdt_count;
+ bool pdts[RMI_PDT_MAX];
+};
+
#define RMI_REG_DESC_PRESENSE_BITS (32 * BITS_PER_BYTE)
#define RMI_REG_DESC_SUBPACKET_BITS (37 * BITS_PER_BYTE)
--
2.53.0
^ permalink raw reply related
* [PATCH v7 0/7] Input: synaptics-rmi4 - add quirks for third party touchscreen controllers
From: David Heidelberg via B4 Relay @ 2026-03-20 16:44 UTC (permalink / raw)
To: Kaustabh Chakraborty, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason A. Donenfeld,
Matthias Schiffer, Vincent Huang
Cc: David Heidelberg, linux-input, devicetree, linux-kernel,
Casey Connolly, phone-devel, Krzysztof Kozlowski
With the growing popularity of running upstream Linux on mobile devices,
we're beginning to run into more and more edgecases. The OnePlus 6 is a
fairly well supported 2018 era smartphone, selling over a million units
in it's first 22 days. With this level of popularity, it's almost
inevitable that we get third party replacement displays, and as a
result, replacement touchscreen controllers.
The OnePlus 6 shipped with an extremely usecase specific touchscreen
driver, it implemented only the bare minimum parts of the highly generic
rmi4 protocol, instead hardcoding most of the register addresses.
As a result, the third party touchscreen controllers that are often
found in replacement screens, implement only the registers that the
downstream driver reads from. They additionally have other restrictions
such as heavy penalties on unaligned reads.
This series attempts to implement the necessary workaround to support
some of these chips with the rmi4 driver. Although it's worth noting
that at the time of writing there are other unofficial controllers in
the wild that don't work even with these patches.
We have been shipping these patches in postmarketOS for the last several
years, and they are known to not cause any regressions on the OnePlus
6/6T (with the official Synaptics controller), however I don't own any
other rmi4 hardware to further validate this.
The series is also available (until merged) at
https://gitlab.com/sdm845/sdm845-next/-/commits/b4/synaptics-rmi4
---
Changes in v7:
- Rebased on top of next-20260320, no other changes.
- Link to v6: https://lore.kernel.org/r/20251113-synaptics-rmi4-v6-0-d9836afab801@ixit.cz
Changes in v6:
- Rebased on top of next-20251113.
- No other change since the Rob Herring comment.
- Link to v5: https://lore.kernel.org/r/20250410-synaptics-rmi4-v5-0-b41bb90f78b9@ixit.cz
Changes in v5:
- Removed -i2c suffix from rmi4-s3706b-i2c (Krzysztof).
- Link to v4: https://lore.kernel.org/r/20250402-synaptics-rmi4-v4-0-1bb95959e564@ixit.cz
Changes in v4:
- Replaced patch "dt-bindings: input: syna,rmi4: document syna,pdt-fallback-desc"
with patch documenting specific touchscreen model used in OnePlus 6 and 6T.
- Fixed zero electrode return code (Dmitry).
- Switched the duplicate detection algo to bitmap (Dmitry).
- Optimized rmi_device_platform_data struct to avoid unnecessary
padding.
- Changed fallback_size from int to unsigned int.
- Changed SoB from nickname and old address (methanal <baclofen@tuta.io>) to
Kaustabh Chakraborty <kauschluss@disroot.org>.
Verified ownership through the sdm845 chatroom on Matrix.
- Link to v3: https://lore.kernel.org/r/20250308-synaptics-rmi4-v3-0-215d3e7289a2@ixit.cz
Changes in v3:
- reworded dt-bindings property description
- fixed the rmi_driver_of_probe definition for non device-tree builds.
- fixed some indentation issues reported by checkpatch
- change rmi_pdt_entry_is_valid() variable to unsigned
- Link to v2: https://lore.kernel.org/all/20230929-caleb-rmi4-quirks-v2-0-b227ac498d88@linaro.org
Changes in v2:
- Improve dt-bindings patch (thanks Rob)
- Add missing cast in patch 5 to fix the pointer arithmetic
- Link to v1: https://lore.kernel.org/r/20230929-caleb-rmi4-quirks-v1-0-cc3c703f022d@linaro.org
---
Casey Connolly (1):
Input: synaptics-rmi4 - handle duplicate/unknown PDT entries
David Heidelberg (1):
dt-bindings: input: syna,rmi4: Document syna,rmi4-s3706b
Kaustabh Chakraborty (5):
Input: synaptics-rmi4 - f12: use hardcoded values for aftermarket touch ICs
Input: synaptics-rmi4 - f55: handle zero electrode count
Input: synaptics-rmi4 - don't do unaligned reads in IRQ context
Input: synaptics-rmi4 - read product ID on aftermarket touch ICs
Input: synaptics-rmi4 - support fallback values for PDT descriptor bytes
.../devicetree/bindings/input/syna,rmi4.yaml | 11 +-
drivers/input/rmi4/rmi_driver.c | 124 +++++++++++++++++----
drivers/input/rmi4/rmi_driver.h | 10 ++
drivers/input/rmi4/rmi_f01.c | 14 +++
drivers/input/rmi4/rmi_f12.c | 117 ++++++++++++++-----
drivers/input/rmi4/rmi_f55.c | 5 +
include/linux/rmi.h | 3 +
7 files changed, 234 insertions(+), 50 deletions(-)
---
base-commit: 785f0eb2f85decbe7c1ef9ae922931f0194ffc2e
change-id: 20250308-synaptics-rmi4-c832b2f73ceb
Best regards,
--
David Heidelberg <david@ixit.cz>
^ permalink raw reply
* [PATCH v7 1/7] dt-bindings: input: syna,rmi4: Document syna,rmi4-s3706b
From: David Heidelberg via B4 Relay @ 2026-03-20 16:44 UTC (permalink / raw)
To: Kaustabh Chakraborty, Dmitry Torokhov, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason A. Donenfeld,
Matthias Schiffer, Vincent Huang
Cc: David Heidelberg, linux-input, devicetree, linux-kernel,
Casey Connolly, phone-devel, Krzysztof Kozlowski
In-Reply-To: <20260320-synaptics-rmi4-v7-0-379360de18d0@ixit.cz>
From: David Heidelberg <david@ixit.cz>
Mostly irrelevant for authentic Synaptics touchscreens, but very important
for applying workarounds to cheap TS knockoffs.
These knockoffs work well with the downstream driver, and since the user
has no way to distinguish them, later in this patch set, we introduce
workarounds to ensure they function as well as possible.
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
Documentation/devicetree/bindings/input/syna,rmi4.yaml | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/input/syna,rmi4.yaml b/Documentation/devicetree/bindings/input/syna,rmi4.yaml
index 8685ef4481f4a..fb4804ac3544d 100644
--- a/Documentation/devicetree/bindings/input/syna,rmi4.yaml
+++ b/Documentation/devicetree/bindings/input/syna,rmi4.yaml
@@ -18,9 +18,14 @@ description: |
properties:
compatible:
- enum:
- - syna,rmi4-i2c
- - syna,rmi4-spi
+ oneOf:
+ - enum:
+ - syna,rmi4-i2c
+ - syna,rmi4-spi
+ - items:
+ - enum:
+ - syna,rmi4-s3706b # OnePlus 6/6T
+ - const: syna,rmi4-i2c
reg:
maxItems: 1
--
2.53.0
^ permalink raw reply related
* [PATCH] Input: gpio-keys - add full support of EV_REL and EV_ABS
From: Xiong Nandi @ 2026-03-20 14:52 UTC (permalink / raw)
To: dmitry.torokhov
Cc: linux-input, linux-kernel, Xiong Nandi, Gatien Chevallier,
Ingo Molnar, Thomas Gleixner, Marco Crivellari, Fabrice Gasnier
gpio_keys_gpio_report_event() handled EV_ABS but silently ignored EV_REL,
while the polled driver supports both. Extend the interrupt-driven driver
to fire an EV_REL event on button press.
For EV_ABS, use a shared atomic counter per (type, code) pair so that
a zero-value reset is sent only when the last active button on an axis
is released, avoiding premature axis resets when multiple buttons share
the same axis code.
Add gpio_keys_set_abs_params() to call input_set_abs_params() at setup
time, deriving the axis min/max from the configured button values.
Without this the input subsystem reports unbounded axis ranges.
Signed-off-by: Xiong Nandi <xndchn@gmail.com>
---
drivers/input/keyboard/gpio_keys.c | 65 +++++++++++++++++++++++++++++-
include/linux/gpio_keys.h | 4 +-
2 files changed, 65 insertions(+), 4 deletions(-)
diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c
index e19617485679..652a6932c52f 100644
--- a/drivers/input/keyboard/gpio_keys.c
+++ b/drivers/input/keyboard/gpio_keys.c
@@ -51,8 +51,10 @@ struct gpio_button_data {
spinlock_t lock;
bool disabled;
bool key_pressed;
+ bool axis_active;
bool suspended;
bool debounce_use_hrtimer;
+ atomic_t *axis_count;
};
struct gpio_keys_drvdata {
@@ -373,9 +375,16 @@ static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
return;
}
- if (type == EV_ABS) {
- if (state)
+ if (type == EV_ABS || type == EV_REL) {
+ if (state && !bdata->axis_active) {
+ bdata->axis_active = true;
+ atomic_inc(bdata->axis_count);
input_event(input, type, button->code, button->value);
+ } else if (!state && bdata->axis_active) {
+ bdata->axis_active = false;
+ if (atomic_dec_and_test(bdata->axis_count))
+ input_event(input, type, button->code, 0);
+ }
} else {
input_event(input, type, *bdata->code, state);
}
@@ -493,6 +502,27 @@ static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static void gpio_keys_set_abs_params(struct input_dev *input,
+ struct gpio_keys_drvdata *ddata,
+ unsigned int code)
+{
+ int i, min = 0, max = 0;
+
+ for (i = 0; i < ddata->pdata->nbuttons; i++) {
+ const struct gpio_keys_button *button = &ddata->pdata->buttons[i];
+
+ if (button->type != EV_ABS || button->code != code)
+ continue;
+
+ if (button->value < min)
+ min = button->value;
+ if (button->value > max)
+ max = button->value;
+ }
+
+ input_set_abs_params(input, code, min, max, 0, 0);
+}
+
static int gpio_keys_setup_key(struct platform_device *pdev,
struct input_dev *input,
struct gpio_keys_drvdata *ddata,
@@ -651,6 +681,8 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
bdata->code = &ddata->keymap[idx];
*bdata->code = button->code;
input_set_capability(input, button->type ?: EV_KEY, *bdata->code);
+ if ((button->type ?: EV_KEY) == EV_ABS)
+ gpio_keys_set_abs_params(input, ddata, button->code);
/*
* Install custom action to cancel release timer and
@@ -928,6 +960,35 @@ static int gpio_keys_probe(struct platform_device *pdev)
fwnode_handle_put(child);
+ /* Allocate shared axis counters for EV_ABS/EV_REL buttons */
+ for (i = 0; i < pdata->nbuttons; i++) {
+ struct gpio_button_data *bdata = &ddata->data[i];
+ unsigned int type = bdata->button->type ?: EV_KEY;
+ int j;
+
+ if (type != EV_ABS && type != EV_REL)
+ continue;
+
+ /* Reuse counter from an earlier button with same (type, code) */
+ for (j = 0; j < i; j++) {
+ struct gpio_button_data *prev = &ddata->data[j];
+ unsigned int prev_type = prev->button->type ?: EV_KEY;
+
+ if (prev_type == type &&
+ prev->button->code == bdata->button->code) {
+ bdata->axis_count = prev->axis_count;
+ break;
+ }
+ }
+
+ if (!bdata->axis_count) {
+ bdata->axis_count = devm_kzalloc(dev,
+ sizeof(*bdata->axis_count), GFP_KERNEL);
+ if (!bdata->axis_count)
+ return -ENOMEM;
+ }
+ }
+
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
diff --git a/include/linux/gpio_keys.h b/include/linux/gpio_keys.h
index 80fa930b04c6..75a745a32fe1 100644
--- a/include/linux/gpio_keys.h
+++ b/include/linux/gpio_keys.h
@@ -13,13 +13,13 @@ struct device;
* @active_low: %true indicates that button is considered
* depressed when gpio is low
* @desc: label that will be attached to button's gpio
- * @type: input event type (%EV_KEY, %EV_SW, %EV_ABS)
+ * @type: input event type (%EV_KEY, %EV_SW, %EV_ABS, %EV_REL)
* @wakeup: configure the button as a wake-up source
* @wakeup_event_action: event action to trigger wakeup
* @debounce_interval: debounce ticks interval in msecs
* @can_disable: %true indicates that userspace is allowed to
* disable button via sysfs
- * @value: axis value for %EV_ABS
+ * @value: axis value for %EV_ABS/%EV_REL
* @irq: Irq number in case of interrupt keys
* @wakeirq: Optional dedicated wake-up interrupt
*/
--
2.25.1
^ permalink raw reply related
* Re: [PATCH] HID: pulsar: add driver for Pulsar gaming mice
From: Leo @ 2026-03-20 13:53 UTC (permalink / raw)
To: Nikolas Koesling, Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel
In-Reply-To: <2344095.iZASKD2KPV@nk-eos>
Hi,
> With three vendors sharing the same protocol, a vendor-neutral name would make
> sense. Any suggestions? Something like hid-paw3395-battery (sensor),
> hid-nrf52840-mouse (SoC), or is there a better identifier for this protocol?
I did some digging into the protocol, and from what I can tell it is
not necessarily associated with the specific sensor, as multiple mice
with differing sensors speak the same protocol (PAW3950 to name one).
The nrf52840 also does not seem to be limited to this protocol. I can't
find a public name for the protocol. Given that and that I'm not sure
what the subsystem's policy on module naming is, but I would assume
that changing them after the fact is not ideal. Probably (?) the
optimal solution would be to integrate your patch (thank you!) with the
existing hid-kysona driver; maybe a maintainer should weigh in on that.
I added my mouse's ID (3554:F58F) to the list and tested the patch.
Everything behaved as expected, UPower output looks reasonable. If you
like, you can amend the device ID to your patch. The `model_id` for it
is reported as 0x0220, and the model format string should be
"ATK VXE R1 SE+ (%s)". My 2.4 GHz receiver seems to not report a model
ID; the web configuration utility uses a different command to identify
the mouse model. Given that it uses a different idVendor anyway, I'm
not worried about that for now.
Tested-by: Leo <leo@managarm.org>
Cheers,
Leo
^ permalink raw reply
* Re: [BUG] HID: Apple Magic Mouse 2 (BT 0x004C:0x0323) reports wrong battery percentage -- hidinput_query_battery_capacity() reads buf[1] (status byte) instead of buf[2] (AbsoluteStateOfCharge) ---
From: Benjamin Tissoires @ 2026-03-20 9:29 UTC (permalink / raw)
To: William MacKinnon; +Cc: linux-input, jikos
In-Reply-To: <CABWzDrqH8Z5qEjCHOAb5f68DUiTLUTjj-sdav7Y7+1+tFvnc4w@mail.gmail.com>
Hi William,
On Fri, Mar 20, 2026 at 12:04 AM William MacKinnon <wh6cyy@gmail.com> wrote:
>
> Hi,
>
> I am reporting a confirmed bug in the HID battery subsystem that causes the
> Apple Magic Mouse 2 to permanently show 4% battery at boot when connected via
> Bluetooth. The root cause has been isolated to a hardcoded byte-index
> assumption in hidinput_query_battery_capacity() (drivers/hid/hid-input.c)
> that is incompatible with the Magic Mouse 2's multi-field report 0x90 structure.
Good finding. Indeed, hidinput_query_battery_capacity() makes overly
strong assumptions about the report's content (like why a report of
size 4 only?). :(
Good thing is that I was planning today to write HID battery tests and
then improve that code slightly (I want it to be aware of new HID
usages).
Anyway, I'll work on that over the next days/weeks if I'm not
preempted by something else.
Cheers,
Benjamin
>
>
> --- System information ---
>
> Kernel: 6.18.15-400.asahi.fc43.aarch64+16k (Asahi Linux on Apple Silicon)
> Device: Apple Magic Mouse 2, "William's Magic Mouse #2"
> BT MAC: D0:C0:50:CF:DF:F1
> HID IDs: Bus=0x0005 (Bluetooth), Vendor=0x004C (Apple BT),
> Product=0x0323
> Driver: hid-magicmouse (BT path)
>
>
> --- Observed vs. expected behaviour ---
>
> /sys/class/power_supply/hid-d0:c0:50:cf:df:f1-battery/capacity
> reports: 4
>
> macOS reports 94% for the same mouse simultaneously (confirmed ground truth).
> Occasional spontaneous correct readings (~95%) are observed at boot before
> the first sysfs read (see Race Condition section below), confirming the
> device transmits the correct value.
>
> UPower sysfs snapshot:
>
> POWER_SUPPLY_NAME=hid-d0:c0:50:cf:df:f1-battery
> POWER_SUPPLY_TYPE=Battery
> POWER_SUPPLY_STATUS=Discharging
> POWER_SUPPLY_CAPACITY=4
> POWER_SUPPLY_SCOPE=Device
> POWER_SUPPLY_MODEL_NAME=William's Magic Mouse #2
>
>
> --- HID Report Descriptor (battery collection) ---
>
> Full raw descriptor (from hid-recorder):
> R: 135 05 01 09 02 a1 01 85 12 05 09 19 01 29 02 15 00 25 01 95 02 75 01
> 81 02 95 01 75 06 81 03 05 01 09 01 a1 00 16 01 f8 26 ff 07 36 01 fb 46
> ff 04 65 13 55 0d 09 30 09 31 75 10 95 02 81 06 75 08 95 02 81 01 c0 06
> 02 ff 09 55 85 55 15 00 26 ff 00 75 08 95 40 b1 a2 c0 06 00 ff 09 14 a1
> 01 85 90 05 84 75 01 95 03 15 00 25 01 09 61 05 85 09 44 09 46 81 02 95
> 05 81 01 75 08 95 01 15 00 26 ff 00 09 65 81 02 c0
>
> Battery Application Collection (report 0x90):
>
> 85 90 -- Report ID 0x90
> 05 84 -- Usage Page: Power Device (0x84)
> 75 01 95 03 -- Report Size 1, Count 3
> 15 00 25 01 -- Logical Min 0, Max 1
> 09 61 -- Usage: PresentStatus (0x61) [bit 0]
> 05 85 -- Usage Page: Battery System (0x85)
> 09 44 -- Usage: Charging (0x44) [bit 1]
> 09 46 -- Usage: Discharging (0x46) [bit 2]
> 81 02 -- Input (FIELD 0: 3 x 1-bit status)
> 95 05 81 01 -- 5-bit padding
> 75 08 95 01 -- Report Size 8, Count 1
> 15 00 26 ff 00 -- Logical Min 0, Max 255
> 09 65 -- Usage: AbsoluteStateOfCharge (0x65)
> 81 02 -- Input (FIELD 1: 8-bit charge)
> c0 -- End Collection
>
> Raw buffer layout for report 0x90 (as seen by the kernel):
>
> buf[0] = 0x90 -- report ID
> buf[1] = 0x04 -- FIELD 0: status byte (bit2=Discharging=1, rest=0)
> buf[2] = 0x5F -- FIELD 1: AbsoluteStateOfCharge = 95 decimal
>
>
> --- Bluetooth traffic capture (btmon) ---
>
> The kernel queries: 41 90 (GET_REPORT, INPUT type, report ID 0x90)
>
> The mouse consistently responds: a1 90 04 5f
>
> a1 = BT HID DATA INPUT header
> 90 = report ID
> 04 = status byte = 0b00000100 (bit2=Discharging)
> 5f = AbsoluteStateOfCharge = 95 decimal ← matches macOS 94%
>
> The value 0x5f=95 is stable across every captured report.
>
>
> --- Root cause ---
>
> hidinput_query_battery_capacity() in drivers/hid/hid-input.c:
>
> ret = hidinput_scale_battery_capacity(dev, buf[1]); /* BUG */
>
> It hardcodes buf[1] as the battery byte. For reports where the battery
> field is the first (and only) data field, buf[1] is correct. For report
> 0x90 on the Magic Mouse 2, buf[1] is the status byte (0x04 = 4) and the
> AbsoluteStateOfCharge is at buf[2] (0x5F = 95).
>
> Arithmetic confirming the bug:
>
> Observed: buf[1] = 0x04 = 4 → scale(4, 0, 100) = 4% ✓ matches sysfs
> Correct: buf[2] = 0x5F = 95 → scale(95, 0, 100) = 95% ✓ matches macOS
>
> The correct byte offset is deterministic from the descriptor:
> 1 + (field->report_offset / 8) = 1 + (8/8) = 2 → buf[2]
>
> The leading 1 accounts for the report-ID byte prepended by hid_hw_raw_request().
>
>
> --- Race condition / intermittent correct readings ---
>
> hidinput_get_battery_property() has two paths:
>
> A. Query path (buggy): when battery_status != HID_BATTERY_REPORTED, calls
> hidinput_query_battery_capacity() → reads buf[1] = 4 → returns 4%.
>
> B. Event-driven path (correct): after the first spontaneous HID INPUT
> event for report 0x90 processes the AbsoluteStateOfCharge usage via
> hid_input_var_field() (which correctly bit-extracts each usage's value
> per the descriptor), hidinput_update_battery() caches 95 in
> dev->battery_capacity and sets battery_status = HID_BATTERY_REPORTED.
> Subsequent sysfs reads return 95%.
>
> The race: if UPower reads sysfs before the first mouse input event (which is
> typical at boot), path A fires → 4%. If the user moves the mouse before
> UPower reads → path B fires first → 95%.
>
> This explains the "sometimes correct at boot" behaviour: it is a genuine
> race between UPower's enumeration and the first spontaneous HID report.
>
>
> --- Why magicmouse_fetch_battery() does not apply ---
>
> magicmouse_fetch_battery() checks is_usb_magicmouse2() which tests:
> vendor == USB_VENDOR_ID_APPLE /* 0x05AC */
>
> A BT-connected Magic Mouse 2 presents vendor=0x004C (Apple BT vendor ID),
> not 0x05AC. is_usb_magicmouse2() returns false; the battery timer is never
> armed; all battery handling falls through to hid-input.c where the bug lives.
>
>
> --- Proposed fix direction ---
>
> Option A (general fix): Store the AbsoluteStateOfCharge field's byte offset
> at hidinput_configure_usage() time (e.g., in a new hdev->battery_field_offset
> member) and use it in hidinput_query_battery_capacity() instead of the
> hardcoded 1.
>
> Option B (device-specific): Extend is_usb_magicmouse2() to also accept
> BT_VENDOR_ID_APPLE (0x004C), arming the existing battery timer for BT-path
> devices so magicmouse_fetch_battery() runs and primes battery_status before
> the first sysfs read, bypassing the hid-input.c query path entirely.
>
> I do not have a tested patch yet. I am happy to test patches.
>
> Regards,
> Will
>
> --
> Kernel: 6.18.15-400.asahi.fc43.aarch64+16k
> Platform: Apple Silicon (Asahi Linux / Fedora 43)
>
>
^ permalink raw reply
* Re: [PATCH] HID: wiimote: Fix table layout in comment
From: David Rheinsberg @ 2026-03-20 8:48 UTC (permalink / raw)
To: j.ne, Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel
In-Reply-To: <20260316-wiimod-table-v1-1-c1f91be05137@posteo.net>
Hi
On Mon, Mar 16, 2026, at 3:45 PM, J. Neuschäfer via B4 Relay wrote:
> From: "J. Neuschäfer" <j.ne@posteo.net>
>
> Some tab characters snuck into the data layout table for turntable
> extensions, which resulted in the table only looking right at a tabstop
> of 4, which is uncommon in the kernel. Change them to the equivalent
> amount of spaces, which should look correct in any editor.
>
> Signed-off-by: J. Neuschäfer <j.ne@posteo.net>
Reviewed-by: David Rheinsberg <david@readahead.eu>
This came in via 05086f3db530, which, looking at it, also left some trailing whitespace. Feel free to include those as well.
Thanks a lot!
> ---
> drivers/hid/hid-wiimote-modules.c | 12 ++++++------
> 1 file changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/hid/hid-wiimote-modules.c
> b/drivers/hid/hid-wiimote-modules.c
> index dbccdfa6391672..d9e1b4313d6465 100644
> --- a/drivers/hid/hid-wiimote-modules.c
> +++ b/drivers/hid/hid-wiimote-modules.c
> @@ -2442,12 +2442,12 @@ static void wiimod_turntable_in_ext(struct
> wiimote_data *wdata, const __u8 *ext)
> /*
> * Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
> *------+------+-----+-----+-----+-----+------+------+--------+
> - * 0 | RTT<4:3> | SX <5:0> |
> - * 1 | RTT<2:1> | SY <5:0> |
> + * 0 | RTT<4:3> | SX <5:0> |
> + * 1 | RTT<2:1> | SY <5:0> |
> *------+------+-----+-----+-----+-----+------+------+--------+
> * 2 |RTT<0>| ED<4:3> | CS<3:0> | RTT<5> |
> *------+------+-----+-----+-----+-----+------+------+--------+
> - * 3 | ED<2:0> | LTT<4:0> |
> + * 3 | ED<2:0> | LTT<4:0> |
> *------+------+-----+-----+-----+-----+------+------+--------+
> * 4 | 0 | 0 | LBR | B- | 0 | B+ | RBR | LTT<5> |
> *------+------+-----+-----+-----+-----+------+------+--------+
> @@ -2458,12 +2458,12 @@ static void wiimod_turntable_in_ext(struct
> wiimote_data *wdata, const __u8 *ext)
> * With Motion+ enabled, it will look like this:
> * Byte | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
> *------+------+-----+-----+-----+-----+------+------+--------+
> - * 1 | RTT<4:3> | SX <5:1> | 0 |
> - * 2 | RTT<2:1> | SY <5:1> | 0 |
> + * 1 | RTT<4:3> | SX <5:1> | 0 |
> + * 2 | RTT<2:1> | SY <5:1> | 0 |
> *------+------+-----+-----+-----+-----+------+------+--------+
> * 3 |RTT<0>| ED<4:3> | CS<3:0> | RTT<5> |
> *------+------+-----+-----+-----+-----+------+------+--------+
> - * 4 | ED<2:0> | LTT<4:0> |
> + * 4 | ED<2:0> | LTT<4:0> |
> *------+------+-----+-----+-----+-----+------+------+--------+
> * 5 | 0 | 0 | LBR | B- | 0 | B+ | RBR | XXXX |
> *------+------+-----+-----+-----+-----+------+------+--------+
>
> ---
> base-commit: f338e77383789c0cae23ca3d48adcc5e9e137e3c
> change-id: 20260316-wiimod-table-cd9abc0a2a98
>
> Best regards,
> --
> J. Neuschäfer <j.ne@posteo.net>
^ permalink raw reply
* Re: [PATCH] HID: pulsar: add driver for Pulsar gaming mice
From: Nikolas Koesling @ 2026-03-19 19:27 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Leo; +Cc: linux-input, linux-kernel
In-Reply-To: <8590f544-9146-4e8e-9ea6-a3ed9ed9fa1f@managarm.org>
Hi Leo,
thanks for the review.
> While this driver looks fine at a glance, this does seem to use the
> same protocol as the hid-kysona driver. It might be more appropriate to
> extend that driver instead of rolling a new one? The HID identifiers in
> hid-ids.h already have a vendor constant for USB_VENDOR_ID_KYSONA,
> which takes the same value as this patch's USB_VENDOR_ID_PULSAR.
Same vendor ID, same protocol. I should have caught the overlap with
hid-kysona.
I don't think simply adding Pulsar IDs to hid-kysona as-is would be sufficient.
The current driver has some issues I addressed in my patch:
1. No locking: raw_event writes battery state concurrently with get_property
reads
2. raw_event always returns 0, so battery/online status responses are never
consumed by the driver. They leak through to userspace as spurious input
events. hid-pulsar uses pending_event + completion to match commands to
responses and consumes responses to driver requests.
3. the protocol includes a checksum, but hid-kysona never validates it on
incoming data.
4. hid-kysona does not react in any way to the power change event messages
from the device.
> For context, I was investigating exactly this battery reporting for
> another mouse, the ATK VXE R1 SE+. It does seem to use the exact same
> protocol; in wired mode, that takes the device ID 3554:F58F, while the
> wireless dongle has a device ID of 373B:1085. I plan on sending a patch
> to add these IDs after some testing.
With three vendors sharing the same protocol, a vendor-neutral name would make
sense. Any suggestions? Something like hid-paw3395-battery (sensor),
hid-nrf52840-mouse (SoC), or is there a better identifier for this protocol?
What would you prefer: rework and rename hid-kysona in place, or rename and
extend hid-pulsar while keeping hid-kysona as-is for compatibility?
Kind regards,
Nikolas
^ permalink raw reply
* Re: [PATCH v5 1/2] dt-bindings: Input: Add Wacom W9000-series penabled touchscreens
From: Conor Dooley @ 2026-03-19 17:09 UTC (permalink / raw)
To: Hendrik Noack
Cc: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ferass El Hafidi, linux-input, devicetree, linux-kernel
In-Reply-To: <20260319095303.19927-2-hendrik-noack@gmx.de>
[-- Attachment #1: Type: text/plain, Size: 2988 bytes --]
On Thu, Mar 19, 2026 at 10:53:02AM +0100, Hendrik Noack wrote:
> Add bindings for Wacom W9002 and two Wacom W9007 variants which can be
> found in tablets.
>
> Co-developed-by: Ferass El Hafidi <funderscore@postmarketos.org>
> Signed-off-by: Ferass El Hafidi <funderscore@postmarketos.org>
> Signed-off-by: Hendrik Noack <hendrik-noack@gmx.de>
> ---
> .../input/touchscreen/wacom,w9007a-lt03.yaml | 73 +++++++++++++++++++
> 1 file changed, 73 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml
>
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml b/Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml
> new file mode 100644
> index 000000000000..6d1da6a435d3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml
> @@ -0,0 +1,73 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/input/touchscreen/wacom,w9007a-lt03.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Wacom W9000-series penabled I2C touchscreen
> +
> +maintainers:
> + - Hendrik Noack <hendrik-noack@gmx.de>
> +
> +description: |
> + The W9000-series are penabled touchscreen controllers by Wacom.
> +
> + The firmware of controllers in different devices may differ. This can also
> + affect the controller's behavior.
> +
> +allOf:
> + - $ref: touchscreen.yaml#
> +
> +properties:
> + compatible:
> + enum:
> + - wacom,w9002
> + - wacom,w9007a-lt03
> + - wacom,w9007a-v1
Please provide information in your commit message as to why these
devices are not compatible with one another.
With that,
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: changes-requested
Cheers,
Conor.
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + vdd-supply: true
> +
> + flash-mode-gpios:
> + maxItems: 1
> +
> + reset-gpios:
> + maxItems: 1
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + digitizer@56 {
> + compatible = "wacom,w9007a-lt03";
> + reg = <0x56>;
> + interrupt-parent = <&gpd1>;
> + interrupts = <1 IRQ_TYPE_EDGE_RISING>;
> +
> + vdd-supply = <&stylus_reg>;
> +
> + flash-mode-gpios = <&gpd1 3 GPIO_ACTIVE_HIGH>;
> + reset-gpios = <&gpx0 1 GPIO_ACTIVE_LOW>;
> +
> + touchscreen-x-mm = <216>;
> + touchscreen-y-mm = <135>;
> + touchscreen-inverted-x;
> + };
> + };
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v7 0/3] HID: Add support for multiple batteries per device
From: Benjamin Tissoires @ 2026-03-19 15:01 UTC (permalink / raw)
To: linux-input, Lucas Zampieri
Cc: linux-kernel, Jiri Kosina, Sebastian Reichel, Bastien Nocera,
linux-pm
In-Reply-To: <20260314010533.110278-1-lcasmz54@gmail.com>
On Sat, 14 Mar 2026 01:05:27 +0000, Lucas Zampieri wrote:
> This series adds support for HID devices with multiple batteries.
>
> Currently, the HID battery reporting subsystem only supports one battery per
> device. There are several devices with multiple batteries that would benefit
> from this support:
> - Gaming headsets with batteries in both the headset and charging dock
> - Wireless earbuds with per-earbud batteries plus charging case
> - Split keyboards with per-side batteries
>
> [...]
Applied to https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git (for-7.1/core-v2), thanks!
[1/3] HID: input: Convert battery code to devm_*
https://git.kernel.org/hid/hid/c/5a9df498581a
[2/3] HID: input: Introduce struct hid_battery and refactor battery code
https://git.kernel.org/hid/hid/c/7a3ac62473f2
[3/3] HID: input: Add support for multiple batteries per device
https://git.kernel.org/hid/hid/c/4a58ae85c3f9
Cheers,
--
Benjamin Tissoires <bentiss@kernel.org>
^ permalink raw reply
* Re: [PATCH][next] HID: mcp2221: Fix spelling mistake "Enfore" -> "Enforce"
From: Benjamin Tissoires @ 2026-03-19 14:43 UTC (permalink / raw)
To: Rishi Gupta, Jiri Kosina, linux-i2c, linux-input, Linus Walleij,
Colin Ian King
Cc: kernel-janitors, linux-kernel
In-Reply-To: <20260314170225.1260440-1-colin.i.king@gmail.com>
On Sat, 14 Mar 2026 17:02:25 +0000, Colin Ian King wrote:
> There is a spelling mistake in a module description. Fix it.
>
>
Applied to https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git (for-7.1/mcp2221), thanks!
[1/1] HID: mcp2221: Fix spelling mistake "Enfore" -> "Enforce"
https://git.kernel.org/hid/hid/c/ac977b900907
Cheers,
--
Benjamin Tissoires <bentiss@kernel.org>
^ permalink raw reply
* Re: [PATCH v5 0/2] Add support for Wacom W9000-series penabled touchscreens
From: Ferass El Hafidi @ 2026-03-19 14:28 UTC (permalink / raw)
To: Hendrik Noack, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: Hendrik Noack, Ferass El Hafidi, linux-input, devicetree,
linux-kernel
In-Reply-To: <20260319095303.19927-1-hendrik-noack@gmx.de>
On Thu, 19 Mar 2026 09:53, Hendrik Noack <hendrik-noack@gmx.de> wrote:
>Add devicetree bindings and a driver for the Wacom W9000-series penabled
>touchscreens.
>
>The driver currently only contains the information for the W9002 and
>W9007A, which I or Ferass could test on devices. It should also work with
>other chips, such as W9001 or W9010. However, I couldn't test it on these
>and the message length would need to be added.
>
>The pen-inserted-gpios is used to get if the pen is inserted in the device
>or not. It's also used as an interrupt so that the power state of the chip
>itself can be controlled depending on a change of the insertion state of
>the pen.
FYI the pen-inserted-gpios is removed in this revision.
>
>Signed-off-by: Hendrik Noack <hendrik-noack@gmx.de>
>---
>Changes in v2:
>- remove pdct-gpios, as it's unnecessary
>- fix devicetree example
>- adopt to kernel coding style
>
>---
>Changes in v3:
>- fix missing include (thanks lkp@intel.com)
>
>---
>Changes in v4:
>- adopt to feedback (thanks dmitry.torokhov@gmail.com)
>- add W9002 support (thanks funderscore@postmarketos.org)
>- add reset-gpios, necessary for some chips
>- remove R-b from krzk due to changes in dt-bindings
>
>---
>Changes in v5:
>- adopt dt-bindings format to suggestion (thanks krzk@kernel.org)
>- remove pen-inserted functionality as suggested (thanks dmitry.torokhov@gmail.com)
>
>---
>Hendrik Noack (2):
> dt-bindings: Input: Add Wacom W9000-series penabled touchscreens
> Input: Add support for Wacom W9000-series penabled touchscreens
>
> .../input/touchscreen/wacom,w9007a-lt03.yaml | 73 +++
> drivers/input/touchscreen/Kconfig | 12 +
> drivers/input/touchscreen/Makefile | 1 +
> drivers/input/touchscreen/wacom_w9000.c | 433 ++++++++++++++++++
> 4 files changed, 519 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml
> create mode 100644 drivers/input/touchscreen/wacom_w9000.c
>
>--
>2.43.0
>
^ permalink raw reply
* Re: [PATCH 12/61] quota: Prefer IS_ERR_OR_NULL over manual NULL check
From: Jan Kara @ 2026-03-19 14:13 UTC (permalink / raw)
To: Philipp Hahn
Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
linux-sctp, linux-security-module, linux-sh, linux-sound,
linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
netdev, ntfs3, samba-technical, sched-ext, target-devel,
tipc-discussion, v9fs, Jan Kara
In-Reply-To: <20260310-b4-is_err_or_null-v1-12-bd63b656022d@avm.de>
On Tue 10-03-26 12:48:38, Philipp Hahn wrote:
> Prefer using IS_ERR_OR_NULL() over using IS_ERR() and a manual NULL
> check.
>
> Change generated with coccinelle.
>
> To: Jan Kara <jack@suse.com>
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Philipp Hahn <phahn-oss@avm.de>
Thanks for the patch but frankly I find the original variant clearer wrt
what is going on. So I prefer to keep the code as is.
Honza
> ---
> fs/quota/quota.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/fs/quota/quota.c b/fs/quota/quota.c
> index 33bacd70758007129e0375bab44d7431195ec441..2e09fc247d0cf45b9e83a4f8a0be7ea694c8c2a1 100644
> --- a/fs/quota/quota.c
> +++ b/fs/quota/quota.c
> @@ -965,7 +965,7 @@ SYSCALL_DEFINE4(quotactl, unsigned int, cmd, const char __user *, special,
> else
> drop_super_exclusive(sb);
> out:
> - if (pathp && !IS_ERR(pathp))
> + if (!IS_ERR_OR_NULL(pathp))
> path_put(pathp);
> return ret;
> }
>
> --
> 2.43.0
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply
* Re: [PATCH][next] HID: mcp2221: Fix spelling mistake "Enfore" -> "Enforce"
From: Linus Walleij @ 2026-03-19 12:57 UTC (permalink / raw)
To: Colin Ian King
Cc: Rishi Gupta, Jiri Kosina, Benjamin Tissoires, linux-i2c,
linux-input, kernel-janitors, linux-kernel
In-Reply-To: <20260314170225.1260440-1-colin.i.king@gmail.com>
On Sat, Mar 14, 2026 at 6:03 PM Colin Ian King <colin.i.king@gmail.com> wrote:
> There is a spelling mistake in a module description. Fix it.
>
> Signed-off-by: Colin Ian King <colin.i.king@gmail.com>
My bad! Thanks Colin.
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
^ permalink raw reply
* Re: [PATCH] HID: pulsar: add driver for Pulsar gaming mice
From: Leo @ 2026-03-19 11:03 UTC (permalink / raw)
To: Nikolas Koesling, Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel
In-Reply-To: <20260318205503.64420-1-nikolas@koesling.info>
Hi,
While this driver looks fine at a glance, this does seem to use the
same protocol as the hid-kysona driver. It might be more appropriate to
extend that driver instead of rolling a new one? The HID identifiers in
hid-ids.h already have a vendor constant for USB_VENDOR_ID_KYSONA,
which takes the same value as this patch's USB_VENDOR_ID_PULSAR.
For context, I was investigating exactly this battery reporting for
another mouse, the ATK VXE R1 SE+. It does seem to use the exact same
protocol; in wired mode, that takes the device ID 3554:F58F, while the
wireless dongle has a device ID of 373B:1085. I plan on sending a patch
to add these IDs after some testing.
Cheers,
Leo
^ permalink raw reply
* [PATCH v5 2/2] Input: Add support for Wacom W9000-series penabled touchscreens
From: Hendrik Noack @ 2026-03-19 9:53 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Hendrik Noack, Ferass El Hafidi, linux-input, devicetree,
linux-kernel
In-Reply-To: <20260319095303.19927-1-hendrik-noack@gmx.de>
Add driver for Wacom W9002 and two Wacom W9007A variants. These are
penabled touchscreens supporting passive Wacom Pens and use I2C.
Co-developed-by: Ferass El Hafidi <funderscore@postmarketos.org>
Signed-off-by: Ferass El Hafidi <funderscore@postmarketos.org>
Signed-off-by: Hendrik Noack <hendrik-noack@gmx.de>
---
drivers/input/touchscreen/Kconfig | 12 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/wacom_w9000.c | 433 ++++++++++++++++++++++++
3 files changed, 446 insertions(+)
create mode 100644 drivers/input/touchscreen/wacom_w9000.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 7d5b72ee07fa..a28328fb7648 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -610,6 +610,18 @@ config TOUCHSCREEN_WACOM_I2C
To compile this driver as a module, choose M here: the module
will be called wacom_i2c.
+config TOUCHSCREEN_WACOM_W9000
+ tristate "Wacom W9000-series penabled touchscreen (I2C)"
+ depends on I2C
+ help
+ Say Y here if you have a Wacom W9000-series penabled I2C touchscreen.
+ This driver supports models W9002 and W9007A.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the module
+ will be called wacom_w9000.
+
config TOUCHSCREEN_LPC32XX
tristate "LPC32XX touchscreen controller"
depends on ARCH_LPC32XX
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index ab9abd151078..aa3915df83b2 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -102,6 +102,7 @@ tsc2007-$(CONFIG_TOUCHSCREEN_TSC2007_IIO) += tsc2007_iio.o
obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o
obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o
obj-$(CONFIG_TOUCHSCREEN_WACOM_I2C) += wacom_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_WACOM_W9000) += wacom_w9000.o
obj-$(CONFIG_TOUCHSCREEN_WDT87XX_I2C) += wdt87xx_i2c.o
obj-$(CONFIG_TOUCHSCREEN_WM831X) += wm831x-ts.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o
diff --git a/drivers/input/touchscreen/wacom_w9000.c b/drivers/input/touchscreen/wacom_w9000.c
new file mode 100644
index 000000000000..f431e03a2b7a
--- /dev/null
+++ b/drivers/input/touchscreen/wacom_w9000.c
@@ -0,0 +1,433 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Wacom W9000-series penabled I2C touchscreen driver
+ *
+ * Copyright (c) 2026 Hendrik Noack <hendrik-noack@gmx.de>
+ *
+ * Partially based on vendor driver:
+ * Copyright (C) 2012, Samsung Electronics Co. Ltd.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/unaligned.h>
+
+/* Some chips have flaky firmware that requires many retries before responding. */
+#define CMD_QUERY_RETRIES 8
+
+/* Message length */
+#define CMD_QUERY_NUM_MAX 9
+#define MSG_COORD_NUM_MAX 12
+
+/* Commands */
+#define CMD_QUERY 0x2a
+
+struct wacom_w9000_variant {
+ const unsigned int cmd_query_num;
+ const unsigned int msg_coord_num;
+ const char *name;
+};
+
+struct wacom_w9000_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ const struct wacom_w9000_variant *variant;
+ unsigned int fw_version;
+
+ struct touchscreen_properties prop;
+ unsigned int max_pressure;
+
+ struct regulator *regulator;
+ bool powered;
+
+ struct gpio_desc *flash_mode_gpio;
+ struct gpio_desc *reset_gpio;
+
+ unsigned int irq;
+
+ bool pen_proximity;
+};
+
+static int wacom_w9000_read(struct i2c_client *client, u8 command, int len, char *data)
+{
+ int error, res;
+ struct i2c_msg msg[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .buf = &command,
+ .len = sizeof(command),
+ }, {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .buf = data,
+ .len = len,
+ }
+ };
+
+ res = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (res != ARRAY_SIZE(msg)) {
+ error = res < 0 ? res : -EIO;
+ dev_err(&client->dev, "%s: i2c transfer failed: %d (%d)\n", __func__, error, res);
+ return error;
+ }
+
+ return 0;
+}
+
+static int wacom_w9000_query(struct wacom_w9000_data *wacom_data)
+{
+ struct i2c_client *client = wacom_data->client;
+ struct device *dev = &wacom_data->client->dev;
+ int error;
+ int retry = 0;
+ u8 data[CMD_QUERY_NUM_MAX];
+
+ for (; retry < CMD_QUERY_RETRIES; retry++) {
+ error = wacom_w9000_read(client, CMD_QUERY, wacom_data->variant->cmd_query_num,
+ data);
+
+ if (!error && (data[0] == 0x0f))
+ break;
+ }
+
+ if (error)
+ return error;
+
+ dev_dbg(dev, "query: %*ph, %d\n", wacom_data->variant->cmd_query_num, data, retry);
+
+ wacom_data->prop.max_x = get_unaligned_be16(&data[1]);
+ wacom_data->prop.max_y = get_unaligned_be16(&data[3]);
+ wacom_data->max_pressure = get_unaligned_be16(&data[5]);
+ wacom_data->fw_version = get_unaligned_be16(&data[7]);
+
+ dev_dbg(dev, "max_x:%d, max_y:%d, max_pressure:%d, fw:%#x", wacom_data->prop.max_x,
+ wacom_data->prop.max_y, wacom_data->max_pressure,
+ wacom_data->fw_version);
+
+ return 0;
+}
+
+/* Must be called with wacom_data->input_dev->mutex held */
+static int wacom_w9000_power_on(struct wacom_w9000_data *wacom_data)
+{
+ int error;
+
+ if (wacom_data->powered)
+ return 0;
+
+ error = regulator_enable(wacom_data->regulator);
+ if (error) {
+ dev_err(&wacom_data->client->dev, "Failed to enable regulators: %d\n", error);
+ return error;
+ }
+
+ msleep(200);
+
+ gpiod_set_value_cansleep(wacom_data->reset_gpio, 0);
+ enable_irq(wacom_data->irq);
+
+ wacom_data->powered = true;
+
+ return error;
+}
+
+/* Must be called with wacom_data->input_dev->mutex held */
+static int wacom_w9000_power_off(struct wacom_w9000_data *wacom_data)
+{
+ if (!wacom_data->powered)
+ return 0;
+
+ disable_irq(wacom_data->irq);
+ gpiod_set_value_cansleep(wacom_data->reset_gpio, 1);
+ regulator_disable(wacom_data->regulator);
+
+ wacom_data->powered = false;
+
+ return 0;
+}
+
+static void wacom_w9000_coord(struct wacom_w9000_data *wacom_data)
+{
+ struct i2c_client *client = wacom_data->client;
+ struct device *dev = &wacom_data->client->dev;
+ int error;
+ u8 data[MSG_COORD_NUM_MAX];
+ bool touch, rubber, side_button;
+ u16 x, y, pressure;
+ u8 distance = 0;
+
+ error = i2c_master_recv(client, data, wacom_data->variant->msg_coord_num);
+ if (error != wacom_data->variant->msg_coord_num) {
+ if (error >= 0)
+ error = -EIO;
+ dev_err(dev, "%s: i2c receive failed (%d)\n", __func__, error);
+ return;
+ }
+
+ dev_dbg(dev, "data: %*ph", wacom_data->variant->msg_coord_num, data);
+
+ if (data[0] & BIT(7)) {
+ wacom_data->pen_proximity = true;
+
+ touch = !!(data[0] & BIT(4));
+ side_button = !!(data[0] & BIT(5));
+ rubber = !!(data[0] & BIT(6));
+
+ x = get_unaligned_be16(&data[1]);
+ y = get_unaligned_be16(&data[3]);
+ pressure = get_unaligned_be16(&data[5]);
+
+ if (wacom_data->variant->msg_coord_num > 7)
+ distance = data[7];
+
+ if (x > wacom_data->prop.max_x || y > wacom_data->prop.max_y) {
+ dev_warn(dev, "Coordinates out of range x=%d, y=%d", x, y);
+ return;
+ }
+
+ touchscreen_report_pos(wacom_data->input_dev, &wacom_data->prop, x, y, false);
+ input_report_abs(wacom_data->input_dev, ABS_PRESSURE, pressure);
+
+ if (wacom_data->variant->msg_coord_num > 7)
+ input_report_abs(wacom_data->input_dev, ABS_DISTANCE, distance);
+
+ input_report_key(wacom_data->input_dev, BTN_STYLUS, side_button);
+ input_report_key(wacom_data->input_dev, BTN_TOUCH, touch);
+ input_report_key(wacom_data->input_dev, BTN_TOOL_PEN, !rubber);
+ input_report_key(wacom_data->input_dev, BTN_TOOL_RUBBER, rubber);
+ input_sync(wacom_data->input_dev);
+ } else if (wacom_data->pen_proximity) {
+ input_report_abs(wacom_data->input_dev, ABS_PRESSURE, 0);
+
+ if (wacom_data->variant->msg_coord_num > 7)
+ input_report_abs(wacom_data->input_dev, ABS_DISTANCE, 255);
+
+ input_report_key(wacom_data->input_dev, BTN_STYLUS, 0);
+ input_report_key(wacom_data->input_dev, BTN_TOUCH, 0);
+ input_report_key(wacom_data->input_dev, BTN_TOOL_PEN, 0);
+ input_report_key(wacom_data->input_dev, BTN_TOOL_RUBBER, 0);
+ input_sync(wacom_data->input_dev);
+
+ wacom_data->pen_proximity = false;
+ }
+}
+
+static irqreturn_t wacom_w9000_interrupt(int irq, void *dev_id)
+{
+ struct wacom_w9000_data *wacom_data = dev_id;
+
+ wacom_w9000_coord(wacom_data);
+
+ return IRQ_HANDLED;
+}
+
+static int wacom_w9000_open(struct input_dev *dev)
+{
+ struct wacom_w9000_data *wacom_data = input_get_drvdata(dev);
+
+ return wacom_w9000_power_on(wacom_data);
+}
+
+static void wacom_w9000_close(struct input_dev *dev)
+{
+ struct wacom_w9000_data *wacom_data = input_get_drvdata(dev);
+
+ wacom_w9000_power_off(wacom_data);
+}
+
+static int wacom_w9000_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct wacom_w9000_data *wacom_data;
+ struct input_dev *input_dev;
+ int error;
+ u32 val;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "i2c_check_functionality error\n");
+ return -EIO;
+ }
+
+ wacom_data = devm_kzalloc(dev, sizeof(*wacom_data), GFP_KERNEL);
+ if (!wacom_data)
+ return -ENOMEM;
+
+ wacom_data->variant = i2c_get_match_data(client);
+
+ if (wacom_data->variant->cmd_query_num > CMD_QUERY_NUM_MAX ||
+ wacom_data->variant->msg_coord_num > MSG_COORD_NUM_MAX) {
+ dev_err(dev, "Length of message for %s exceeds the maximum\n",
+ wacom_data->variant->name);
+ return -EINVAL;
+ }
+
+ if (wacom_data->variant->msg_coord_num < 7) {
+ dev_err(dev, "Length of coordinates message for %s too short\n",
+ wacom_data->variant->name);
+ return -EINVAL;
+ }
+
+ wacom_data->client = client;
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ wacom_data->input_dev = input_dev;
+ input_set_drvdata(input_dev, wacom_data);
+
+ wacom_data->irq = client->irq;
+ i2c_set_clientdata(client, wacom_data);
+
+ wacom_data->regulator = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(wacom_data->regulator))
+ return dev_err_probe(dev, PTR_ERR(wacom_data->regulator),
+ "Failed to get regulators\n");
+
+ wacom_data->flash_mode_gpio = devm_gpiod_get_optional(dev, "flash-mode", GPIOD_OUT_LOW);
+ if (IS_ERR(wacom_data->flash_mode_gpio))
+ return dev_err_probe(dev, PTR_ERR(wacom_data->flash_mode_gpio),
+ "Failed to get flash-mode gpio\n");
+
+ wacom_data->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(wacom_data->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(wacom_data->reset_gpio),
+ "Failed to get reset gpio\n");
+
+ error = regulator_enable(wacom_data->regulator);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to enable regulators\n");
+
+ msleep(200);
+
+ gpiod_set_value_cansleep(wacom_data->reset_gpio, 0);
+
+ error = wacom_w9000_query(wacom_data);
+
+ gpiod_set_value_cansleep(wacom_data->reset_gpio, 1);
+ regulator_disable(wacom_data->regulator);
+
+ wacom_data->powered = false;
+
+ if (error)
+ return dev_err_probe(dev, error, "Failed to query\n");
+
+ input_dev->name = wacom_data->variant->name;
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = dev;
+ input_dev->id.vendor = 0x56a;
+ input_dev->id.version = wacom_data->fw_version;
+ input_dev->open = wacom_w9000_open;
+ input_dev->close = wacom_w9000_close;
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+ input_set_capability(input_dev, EV_KEY, BTN_TOOL_PEN);
+ input_set_capability(input_dev, EV_KEY, BTN_TOOL_RUBBER);
+ input_set_capability(input_dev, EV_KEY, BTN_STYLUS);
+
+ input_set_abs_params(input_dev, ABS_X, 0, wacom_data->prop.max_x, 4, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, wacom_data->prop.max_y, 4, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, wacom_data->max_pressure, 0, 0);
+
+ if (wacom_data->variant->msg_coord_num > 7)
+ input_set_abs_params(input_dev, ABS_DISTANCE, 0, 255, 0, 0);
+
+ touchscreen_parse_properties(input_dev, false, &wacom_data->prop);
+
+ dev_info(dev, "%s size X%uY%u\n", wacom_data->variant->name,
+ wacom_data->prop.max_x, wacom_data->prop.max_y);
+
+ error = device_property_read_u32(dev, "touchscreen-x-mm", &val);
+ if (!error)
+ input_abs_set_res(input_dev, ABS_X, wacom_data->prop.max_x / val);
+ error = device_property_read_u32(dev, "touchscreen-y-mm", &val);
+ if (!error)
+ input_abs_set_res(input_dev, ABS_Y, wacom_data->prop.max_y / val);
+
+ error = devm_request_threaded_irq(dev, wacom_data->irq, NULL, wacom_w9000_interrupt,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN, client->name, wacom_data);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to register interrupt\n");
+
+ error = input_register_device(wacom_data->input_dev);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to register input device\n");
+
+ return 0;
+}
+
+static int wacom_w9000_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct wacom_w9000_data *wacom_data = i2c_get_clientdata(client);
+
+ guard(mutex)(&wacom_data->input_dev->mutex);
+
+ return wacom_w9000_power_off(wacom_data);
+}
+
+static int wacom_w9000_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct wacom_w9000_data *wacom_data = i2c_get_clientdata(client);
+
+ guard(mutex)(&wacom_data->input_dev->mutex);
+
+ return wacom_w9000_power_on(wacom_data);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(wacom_w9000_pm, wacom_w9000_suspend, wacom_w9000_resume);
+
+static const struct wacom_w9000_variant w9002 = {
+ .cmd_query_num = 9,
+ .msg_coord_num = 7,
+ .name = "Wacom W9002 Digitizer",
+};
+
+static const struct wacom_w9000_variant w9007a_lt03 = {
+ .cmd_query_num = 9,
+ .msg_coord_num = 8,
+ .name = "Wacom W9007A LT03 Digitizer",
+};
+
+static const struct wacom_w9000_variant w9007a_v1 = {
+ .cmd_query_num = 9,
+ .msg_coord_num = 12,
+ .name = "Wacom W9007A V1 Digitizer",
+};
+
+static const struct of_device_id wacom_w9000_of_match[] = {
+ { .compatible = "wacom,w9002", .data = &w9002 },
+ { .compatible = "wacom,w9007a-lt03", .data = &w9007a_lt03, },
+ { .compatible = "wacom,w9007a-v1", .data = &w9007a_v1, },
+ { }
+};
+MODULE_DEVICE_TABLE(of, wacom_w9000_of_match);
+
+static const struct i2c_device_id wacom_w9000_id[] = {
+ { .name = "w9002", .driver_data = (kernel_ulong_t)&w9002 },
+ { .name = "w9007a-lt03", .driver_data = (kernel_ulong_t)&w9007a_lt03 },
+ { .name = "w9007a-v1", .driver_data = (kernel_ulong_t)&w9007a_v1 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wacom_w9000_id);
+
+static struct i2c_driver wacom_w9000_driver = {
+ .driver = {
+ .name = "wacom_w9000",
+ .of_match_table = wacom_w9000_of_match,
+ .pm = pm_sleep_ptr(&wacom_w9000_pm),
+ },
+ .probe = wacom_w9000_probe,
+ .id_table = wacom_w9000_id,
+};
+module_i2c_driver(wacom_w9000_driver);
+
+/* Module information */
+MODULE_AUTHOR("Hendrik Noack <hendrik-noack@gmx.de>");
+MODULE_DESCRIPTION("Wacom W9000-series penabled touchscreen driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ 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