* [PATCH v2 5/5] iio: buffer: fix timestamp alignment when quaternion in scan
From: David Lechner @ 2026-03-08 1:44 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, Nuno Sá,
Andy Shevchenko, Lars Möllendorf, Lars-Peter Clausen,
Greg Kroah-Hartman
Cc: Jonathan Cameron, Lixu Zhang, Francesco Lavra, linux-input,
linux-iio, linux-kernel, David Lechner
In-Reply-To: <20260307-iio-fix-timestamp-alignment-v2-0-d1d48fbadbbf@baylibre.com>
Fix timestamp alignment when a scan buffer contains an element larger
than sizeof(int64_t). Currently s32 quaternions are the only such
element, and the one driver that has this (hid-sensor-rotation) has a
workaround in place already so this change does not affect it.
Previously, we assumed that the timestamp would always be 8-byte aligned
relative to the end of the scan buffer, but in the case of a scan buffer
a 16-byte quaternion vector, scan_bytes == 32, but the timestamp needs
to be placed at offset 16, not 24.
ts_offset is now a value in bytes so we have to change how the array
access is done.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v2 changes:
* Use cached timestamp offset instead of largest scan element size.
* Mention change of ts_offset semantics in commit message.
To test this, I used hid-sensor-rotation minus the first patch in the
series so that we can see that the timestamp actually moved to the
correct location.
Before this patch, the timestamp (8 bytes ending with "98 18") is in the
wrong location.
00000000 6a 18 00 00 ac f3 ff ff 83 2d 00 00 02 d3 ff ff |j........-......|
00000010 00 00 00 00 00 00 00 00 5a 17 a0 2a 73 cb 98 18 |........Z..*s...|
00000020 ad 17 00 00 6a f4 ff ff 35 2b 00 00 ca d0 ff ff |....j...5+......|
00000030 00 00 00 00 00 00 00 00 2a a6 bb 30 73 cb 98 18 |........*..0s...|
00000040 92 1e 00 00 50 ec ff ff ea c1 ff ff 78 f0 ff ff |....P.......x...|
00000050 00 00 00 00 00 00 00 00 8f 3b a7 39 77 cb 98 18 |.........;.9w...|
After this patch, timestamp is now in the correct location.
00000000 55 0f 00 00 dd 1f 00 00 af 0b 00 00 ec 3e 00 00 |U............>..|
00000010 c7 17 68 42 6d d0 98 18 00 00 00 00 00 00 00 00 |..hBm...........|
00000020 57 0e 00 00 c8 1f 00 00 d1 0e 00 00 42 3e 00 00 |W...........B>..|
00000030 56 a2 87 48 6d d0 98 18 00 00 00 00 00 00 00 00 |V..Hm...........|
00000040 a3 e2 ff ff d3 1b 00 00 0b c9 ff ff cc 20 00 00 |............. ..|
00000050 27 59 4d b3 72 d0 98 18 00 00 00 00 00 00 00 00 |'YM.r...........|
I also tested this with a different driver not affected by this bug to
make sure that the timestamp is still in the correct location for all
other drivers.
---
include/linux/iio/buffer.h | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/include/linux/iio/buffer.h b/include/linux/iio/buffer.h
index d37f82678f71..8fd0d57d238f 100644
--- a/include/linux/iio/buffer.h
+++ b/include/linux/iio/buffer.h
@@ -34,8 +34,16 @@ static inline int iio_push_to_buffers_with_timestamp(struct iio_dev *indio_dev,
void *data, int64_t timestamp)
{
if (ACCESS_PRIVATE(indio_dev, scan_timestamp)) {
- size_t ts_offset = indio_dev->scan_bytes / sizeof(int64_t) - 1;
- ((int64_t *)data)[ts_offset] = timestamp;
+ size_t ts_offset = ACCESS_PRIVATE(indio_dev, scan_timestamp_offset);
+
+ /*
+ * The size of indio_dev->scan_bytes is always aligned to the
+ * largest scan element's alignment (see iio_compute_scan_bytes()).
+ * So there may be padding after the timestamp. ts_offset contains
+ * the offset in bytes that was already computed for correctly
+ * aligning the timestamp.
+ */
+ *(int64_t *)(data + ts_offset) = timestamp;
}
return iio_push_to_buffers(indio_dev, data);
--
2.43.0
^ permalink raw reply related
* [PATCH v2 4/5] iio: buffer: ensure repeat alignment is a power of two
From: David Lechner @ 2026-03-08 1:44 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, Nuno Sá,
Andy Shevchenko, Lars Möllendorf, Lars-Peter Clausen,
Greg Kroah-Hartman
Cc: Jonathan Cameron, Lixu Zhang, Francesco Lavra, linux-input,
linux-iio, linux-kernel, David Lechner
In-Reply-To: <20260307-iio-fix-timestamp-alignment-v2-0-d1d48fbadbbf@baylibre.com>
Use roundup_pow_of_two() in the calculation of iio_storage_bytes_for_si()
when scan_type->repeat > 1 to ensure that the size is a power of two.
storagebits is always going to be a power of two bytes, so we only need
to apply this to the repeat factor. The storage size is also used for
alignment, and we want to ensure that all alignments are a power of two.
The only repeat in use in the kernel currently is for quaternions, which
have a repeat of 4, so this does not change the result for existing
users.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v2 changes: new patch
In v1, Nuno made the point that if the size isn't a power of two, then
the alignment won't be a power of two either. And this could cause
unexpected problems regarding alignment in general.
This will affect the work Francesco is doing with IIO_MOD_QUATERNION_AXIS
which will have a repeat of 3, so this is a good time to think about this.
---
drivers/iio/industrialio-buffer.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c
index ecfe0c9740e2..c38da24561c0 100644
--- a/drivers/iio/industrialio-buffer.c
+++ b/drivers/iio/industrialio-buffer.c
@@ -748,7 +748,7 @@ static int iio_storage_bytes_for_si(struct iio_dev *indio_dev,
bytes = scan_type->storagebits / 8;
if (scan_type->repeat > 1)
- bytes *= scan_type->repeat;
+ bytes *= roundup_pow_of_two(scan_type->repeat);
return bytes;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v2 3/5] iio: buffer: cache timestamp offset in scan buffer
From: David Lechner @ 2026-03-08 1:44 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, Nuno Sá,
Andy Shevchenko, Lars Möllendorf, Lars-Peter Clausen,
Greg Kroah-Hartman
Cc: Jonathan Cameron, Lixu Zhang, Francesco Lavra, linux-input,
linux-iio, linux-kernel, David Lechner
In-Reply-To: <20260307-iio-fix-timestamp-alignment-v2-0-d1d48fbadbbf@baylibre.com>
Cache the offset (in bytes) for the timestamp element in a scan buffer.
This will be used later to ensure proper alignment of the timestamp
element in the scan buffer.
The new field could not be placed in struct iio_dev_opaque because we
will need to access it in a static inline function later, so we make it
__private instead. It is only intended to be used by core IIO code.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v2 changes:
* Cache the timestamp offset instead of the largest scan element size.
---
drivers/iio/industrialio-buffer.c | 14 +++++++++++---
include/linux/iio/iio.h | 3 +++
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c
index 4e413b4bb073..ecfe0c9740e2 100644
--- a/drivers/iio/industrialio-buffer.c
+++ b/drivers/iio/industrialio-buffer.c
@@ -763,7 +763,8 @@ static int iio_storage_bytes_for_timestamp(struct iio_dev *indio_dev)
static int iio_compute_scan_bytes(struct iio_dev *indio_dev,
const unsigned long *mask, bool timestamp,
- unsigned int *scan_bytes)
+ unsigned int *scan_bytes,
+ unsigned int *timestamp_offset)
{
unsigned int bytes = 0;
int length, i, largest = 0;
@@ -785,6 +786,10 @@ static int iio_compute_scan_bytes(struct iio_dev *indio_dev,
return length;
bytes = ALIGN(bytes, length);
+
+ if (timestamp_offset)
+ *timestamp_offset = bytes;
+
bytes += length;
largest = max(largest, length);
}
@@ -846,7 +851,7 @@ static int iio_buffer_update_bytes_per_datum(struct iio_dev *indio_dev,
return 0;
ret = iio_compute_scan_bytes(indio_dev, buffer->scan_mask,
- buffer->scan_timestamp, &bytes);
+ buffer->scan_timestamp, &bytes, NULL);
if (ret)
return ret;
@@ -890,6 +895,7 @@ struct iio_device_config {
unsigned int watermark;
const unsigned long *scan_mask;
unsigned int scan_bytes;
+ unsigned int scan_timestamp_offset;
bool scan_timestamp;
};
@@ -995,7 +1001,8 @@ static int iio_verify_update(struct iio_dev *indio_dev,
}
ret = iio_compute_scan_bytes(indio_dev, scan_mask, scan_timestamp,
- &config->scan_bytes);
+ &config->scan_bytes,
+ &config->scan_timestamp_offset);
if (ret)
return ret;
@@ -1153,6 +1160,7 @@ static int iio_enable_buffers(struct iio_dev *indio_dev,
indio_dev->active_scan_mask = config->scan_mask;
ACCESS_PRIVATE(indio_dev, scan_timestamp) = config->scan_timestamp;
indio_dev->scan_bytes = config->scan_bytes;
+ ACCESS_PRIVATE(indio_dev, scan_timestamp_offset) = config->scan_timestamp_offset;
iio_dev_opaque->currentmode = config->mode;
iio_update_demux(indio_dev);
diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h
index 2c91b7659ce9..ecbaeecbe0ac 100644
--- a/include/linux/iio/iio.h
+++ b/include/linux/iio/iio.h
@@ -584,6 +584,8 @@ struct iio_buffer_setup_ops {
* and owner
* @buffer: [DRIVER] any buffer present
* @scan_bytes: [INTERN] num bytes captured to be fed to buffer demux
+ * @scan_timestamp_offset: [INTERN] cache of the offset (in bytes) for the
+ * timestamp in the scan buffer
* @available_scan_masks: [DRIVER] optional array of allowed bitmasks. Sort the
* array in order of preference, the most preferred
* masks first.
@@ -610,6 +612,7 @@ struct iio_dev {
struct iio_buffer *buffer;
int scan_bytes;
+ unsigned int __private scan_timestamp_offset;
const unsigned long *available_scan_masks;
unsigned int __private masklength;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/5] iio: buffer: check return value of iio_compute_scan_bytes()
From: David Lechner @ 2026-03-08 1:44 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, Nuno Sá,
Andy Shevchenko, Lars Möllendorf, Lars-Peter Clausen,
Greg Kroah-Hartman
Cc: Jonathan Cameron, Lixu Zhang, Francesco Lavra, linux-input,
linux-iio, linux-kernel, David Lechner
In-Reply-To: <20260307-iio-fix-timestamp-alignment-v2-0-d1d48fbadbbf@baylibre.com>
Check return value of iio_compute_scan_bytes() as it can return an
error.
The result is moved to an output parameter while we are touching this
as we will need to add a second output parameter in a later change.
The return type of iio_buffer_update_bytes_per_datum() also had to be
changed to propagate the error.
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
drivers/iio/industrialio-buffer.c | 36 +++++++++++++++++++++++++-----------
1 file changed, 25 insertions(+), 11 deletions(-)
diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c
index f15a180dc49e..4e413b4bb073 100644
--- a/drivers/iio/industrialio-buffer.c
+++ b/drivers/iio/industrialio-buffer.c
@@ -762,7 +762,8 @@ static int iio_storage_bytes_for_timestamp(struct iio_dev *indio_dev)
}
static int iio_compute_scan_bytes(struct iio_dev *indio_dev,
- const unsigned long *mask, bool timestamp)
+ const unsigned long *mask, bool timestamp,
+ unsigned int *scan_bytes)
{
unsigned int bytes = 0;
int length, i, largest = 0;
@@ -788,8 +789,9 @@ static int iio_compute_scan_bytes(struct iio_dev *indio_dev,
largest = max(largest, length);
}
- bytes = ALIGN(bytes, largest);
- return bytes;
+ *scan_bytes = ALIGN(bytes, largest);
+
+ return 0;
}
static void iio_buffer_activate(struct iio_dev *indio_dev,
@@ -834,18 +836,23 @@ static int iio_buffer_disable(struct iio_buffer *buffer,
return buffer->access->disable(buffer, indio_dev);
}
-static void iio_buffer_update_bytes_per_datum(struct iio_dev *indio_dev,
- struct iio_buffer *buffer)
+static int iio_buffer_update_bytes_per_datum(struct iio_dev *indio_dev,
+ struct iio_buffer *buffer)
{
unsigned int bytes;
+ int ret;
if (!buffer->access->set_bytes_per_datum)
- return;
+ return 0;
- bytes = iio_compute_scan_bytes(indio_dev, buffer->scan_mask,
- buffer->scan_timestamp);
+ ret = iio_compute_scan_bytes(indio_dev, buffer->scan_mask,
+ buffer->scan_timestamp, &bytes);
+ if (ret)
+ return ret;
buffer->access->set_bytes_per_datum(buffer, bytes);
+
+ return 0;
}
static int iio_buffer_request_update(struct iio_dev *indio_dev,
@@ -853,7 +860,10 @@ static int iio_buffer_request_update(struct iio_dev *indio_dev,
{
int ret;
- iio_buffer_update_bytes_per_datum(indio_dev, buffer);
+ ret = iio_buffer_update_bytes_per_datum(indio_dev, buffer);
+ if (ret)
+ return ret;
+
if (buffer->access->request_update) {
ret = buffer->access->request_update(buffer);
if (ret) {
@@ -896,6 +906,7 @@ static int iio_verify_update(struct iio_dev *indio_dev,
struct iio_buffer *buffer;
bool scan_timestamp;
unsigned int modes;
+ int ret;
if (insert_buffer &&
bitmap_empty(insert_buffer->scan_mask, masklength)) {
@@ -983,8 +994,11 @@ static int iio_verify_update(struct iio_dev *indio_dev,
scan_mask = compound_mask;
}
- config->scan_bytes = iio_compute_scan_bytes(indio_dev,
- scan_mask, scan_timestamp);
+ ret = iio_compute_scan_bytes(indio_dev, scan_mask, scan_timestamp,
+ &config->scan_bytes);
+ if (ret)
+ return ret;
+
config->scan_mask = scan_mask;
config->scan_timestamp = scan_timestamp;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 1/5] iio: orientation: hid-sensor-rotation: add timestamp hack to not break userspace
From: David Lechner @ 2026-03-08 1:44 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, Nuno Sá,
Andy Shevchenko, Lars Möllendorf, Lars-Peter Clausen,
Greg Kroah-Hartman
Cc: Jonathan Cameron, Lixu Zhang, Francesco Lavra, linux-input,
linux-iio, linux-kernel, David Lechner
In-Reply-To: <20260307-iio-fix-timestamp-alignment-v2-0-d1d48fbadbbf@baylibre.com>
Add a hack to push two timestamps in the hid-sensor-rotation scan data
to avoid breaking userspace applications that depend on the timestamp
being at the incorrect location in the scan data due to unintentional
misalignment in older kernels.
When this driver was written, the timestamp was in the correct location
because of the way iio_compute_scan_bytes() was implemented at the time.
(Samples were 24 bytes each.) Then commit 883f61653069 ("iio: buffer:
align the size of scan bytes to size of the largest element") changed
the computed scan_bytes to be a different size (32 bytes), which caused
iio_push_to_buffers_with_timestamp() to place the timestamp at an
incorrect offset.
There have been long periods of time (6 years each) where the timestamp
was in either location, so to not break either case, we open-code the
timestamps to be pushed to both locations in the scan data.
Reported-by: Jonathan Cameron <jic23@kernel.org>
Closes: https://lore.kernel.org/linux-iio/20260215162351.79f40b32@jic23-huawei/
Fixes: 883f61653069 ("iio: buffer: align the size of scan bytes to size of the largest element")
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
v2 changes:
- Rebased on fix that also touches the scan struct.
- Improved comments.
I found that I could emulate this thanks to /dev/uhid. And thanks to AI
code generators, I was able to reasonably quickly make a script that
worked for emulating "HID-SENSOR-20008a". See v1 message for test script
source code.
[v1]: https://lore.kernel.org/linux-iio/20260301-iio-fix-timestamp-alignment-v1-1-1a54980bfb90@baylibre.com/
I set up the buffer like this:
cd /sys/bus/iio/devices/iio:device1/buffer0
echo 1 > in_rot_quaternion_en
echo 1 > in_timestamp_en
echo 1 > enable
Before this series is applied, we can see that the timestamp (group of 8
ending in "98 18") is at offset of 24 in the 32-byte data.
hd /dev/iio\:device1
00000000 6a 18 00 00 ac f3 ff ff 83 2d 00 00 02 d3 ff ff |j........-......|
00000010 00 00 00 00 00 00 00 00 5a 17 a0 2a 73 cb 98 18 |........Z..*s...|
00000020 ad 17 00 00 6a f4 ff ff 35 2b 00 00 ca d0 ff ff |....j...5+......|
00000030 00 00 00 00 00 00 00 00 2a a6 bb 30 73 cb 98 18 |........*..0s...|
00000040 92 1e 00 00 50 ec ff ff ea c1 ff ff 78 f0 ff ff |....P.......x...|
00000050 00 00 00 00 00 00 00 00 8f 3b a7 39 77 cb 98 18 |.........;.9w...|
After the first patch, we can see that the timestamp is now repeated at
both the correct and previous incorrect offsets (24 and 32). (Normally,
the last 8 bytes would be all 00 for padding.)
00000000 dd e0 ff ff 0e e0 ff ff 75 07 00 00 90 3f 00 00 |........u....?..|
00000010 f4 38 82 d0 3a cc 98 18 f4 38 82 d0 3a cc 98 18 |.8..:....8..:...|
00000020 a0 e0 ff ff 1d e0 ff ff a0 0a 00 00 1c 3f 00 00 |.............?..|
00000030 3a 29 9f d6 3a cc 98 18 3a 29 9f d6 3a cc 98 18 |:)..:...:)..:...|
00000040 a9 e1 ff ff 1e 14 00 00 6c c1 ff ff 98 f2 ff ff |........l.......|
00000050 39 21 77 11 55 cc 98 18 39 21 77 11 55 cc 98 18 |9!w.U...9!w.U...|
---
drivers/iio/orientation/hid-sensor-rotation.c | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/orientation/hid-sensor-rotation.c b/drivers/iio/orientation/hid-sensor-rotation.c
index 6806481873be..5a5e6e4fbe34 100644
--- a/drivers/iio/orientation/hid-sensor-rotation.c
+++ b/drivers/iio/orientation/hid-sensor-rotation.c
@@ -20,7 +20,12 @@ struct dev_rot_state {
struct hid_sensor_hub_attribute_info quaternion;
struct {
IIO_DECLARE_QUATERNION(s32, sampled_vals);
- aligned_s64 timestamp;
+ /*
+ * ABI regression avoidance: There are two copies of the same
+ * timestamp in case of userspace depending on broken alignment
+ * from older kernels.
+ */
+ aligned_s64 timestamp[2];
} scan;
int scale_pre_decml;
int scale_post_decml;
@@ -154,8 +159,19 @@ static int dev_rot_proc_event(struct hid_sensor_hub_device *hsdev,
if (!rot_state->timestamp)
rot_state->timestamp = iio_get_time_ns(indio_dev);
- iio_push_to_buffers_with_timestamp(indio_dev, &rot_state->scan,
- rot_state->timestamp);
+ /*
+ * ABI regression avoidance: IIO previously had an incorrect
+ * implementation of iio_push_to_buffers_with_timestamp() that
+ * put the timestamp in the last 8 bytes of the buffer, which
+ * was incorrect according to the IIO ABI. To avoid breaking
+ * userspace that may be depending on this broken behavior, we
+ * put the timestamp in both the correct place [0] and the old
+ * incorrect place [1].
+ */
+ rot_state->scan.timestamp[0] = rot_state->timestamp;
+ rot_state->scan.timestamp[1] = rot_state->timestamp;
+
+ iio_push_to_buffers(indio_dev, &rot_state->scan);
rot_state->timestamp = 0;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/5] iio: buffer: fix timestamp alignment (in rare case)
From: David Lechner @ 2026-03-08 1:44 UTC (permalink / raw)
To: Jiri Kosina, Jonathan Cameron, Srinivas Pandruvada, Nuno Sá,
Andy Shevchenko, Lars Möllendorf, Lars-Peter Clausen,
Greg Kroah-Hartman
Cc: Jonathan Cameron, Lixu Zhang, Francesco Lavra, linux-input,
linux-iio, linux-kernel, David Lechner
In [1], it was pointed out that the iio_push_to_buffers_with_timestamp()
function is not putting the timestamp at the correct offset in the scan
buffer in rare cases where the largest scan element size is larger than
sizeof(int64_t).
[1]: https://lore.kernel.org/linux-iio/20260215162351.79f40b32@jic23-huawei/
This only affected one driver, namely hid-sensor-rotation since it is
the only driver that meets the condition. To fix things up, first we
fix the hid-sensor-rotation driver in a way that preserves compatibility
with the broken timestamp alignment. Then we are free to fix the core
IIO code without affecting any users.
The first patch depends on [2] which is now in iio/fixes-togreg. It
should be OK to apply the first patch there and let the rest of the
patches go through iio/togreg (the later patches are just preventing
future bugs).
[2]: https://lore.kernel.org/linux-iio/20260228-iio-fix-repeat-alignment-v2-0-d58bfaa2920d@baylibre.com/
Signed-off-by: David Lechner <dlechner@baylibre.com>
---
Changes in v2:
- Don't say "HACK" in comments.
- Cache timestamp offset instead of largest scan element size.
- New patch to ensure size/alignment is always power of 2 bytes.
- Link to v1: https://lore.kernel.org/r/20260301-iio-fix-timestamp-alignment-v1-0-1a54980bfb90@baylibre.com
---
David Lechner (5):
iio: orientation: hid-sensor-rotation: add timestamp hack to not break userspace
iio: buffer: check return value of iio_compute_scan_bytes()
iio: buffer: cache timestamp offset in scan buffer
iio: buffer: ensure repeat alignment is a power of two
iio: buffer: fix timestamp alignment when quaternion in scan
drivers/iio/industrialio-buffer.c | 46 ++++++++++++++++++++-------
drivers/iio/orientation/hid-sensor-rotation.c | 22 +++++++++++--
include/linux/iio/buffer.h | 12 +++++--
include/linux/iio/iio.h | 3 ++
4 files changed, 66 insertions(+), 17 deletions(-)
---
base-commit: 6f25a6105c41a7d6b12986dbe80ded396a5667f8
change-id: 20260228-iio-fix-timestamp-alignment-89ade1af458b
prerequisite-message-id: <20260228-iio-fix-repeat-alignment-v2-0-d58bfaa2920d@baylibre.com>
prerequisite-patch-id: e155a526d57c5759a2fcfbfca7f544cb419addfd
prerequisite-patch-id: 6c69eaad0dd2ae69bd2745e7d387f739fc1a9ba0
Best regards,
--
David Lechner <dlechner@baylibre.com>
^ permalink raw reply
* Re: [PATCH v2 1/2] iio: add IIO_DECLARE_QUATERNION() macro
From: David Lechner @ 2026-03-08 0:35 UTC (permalink / raw)
To: Francesco Lavra
Cc: linux-iio, linux-kernel, Jonathan Cameron, linux-input,
Jonathan Cameron, Nuno Sá, Andy Shevchenko, Jiri Kosina,
Srinivas Pandruvada
In-Reply-To: <20260228-iio-fix-repeat-alignment-v2-1-d58bfaa2920d@baylibre.com>
On 2/28/26 2:02 PM, David Lechner wrote:
> Add a new IIO_DECLARE_QUATERNION() macro that is used to declare the
> field in an IIO buffer struct that contains a quaternion vector.
>
> Quaternions are currently the only IIO data type that uses the .repeat
> feature of struct iio_scan_type. This has an implicit rule that the
> element in the buffer must be aligned to the entire size of the repeated
> element. This macro will make that requirement explicit. Since this is
> the only user, we just call the macro IIO_DECLARE_QUATERNION() instead
> of something more generic.
>
> Signed-off-by: David Lechner <dlechner@baylibre.com>
> ---
> include/linux/iio/iio.h | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
> diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h
> index a9ecff191bd9..2c91b7659ce9 100644
> --- a/include/linux/iio/iio.h
> +++ b/include/linux/iio/iio.h
> @@ -931,6 +931,18 @@ static inline void *iio_device_get_drvdata(const struct iio_dev *indio_dev)
> #define IIO_DECLARE_DMA_BUFFER_WITH_TS(type, name, count) \
> __IIO_DECLARE_BUFFER_WITH_TS(type, name, count) __aligned(IIO_DMA_MINALIGN)
>
> +/**
> + * IIO_DECLARE_QUATERNION() - Declare a quaternion element
> + * @type: element type of the individual vectors
> + * @name: identifier name
> + *
> + * Quaternions are a vector composed of 4 elements (W, X, Y, Z). Use this macro
> + * to declare a quaternion element in a struct to ensure proper alignment in
> + * an IIO buffer.
> + */
> +#define IIO_DECLARE_QUATERNION(type, name) \
> + type name[4] __aligned(sizeof(type) * 4)
> +
> struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv);
>
> /* The information at the returned address is guaranteed to be cacheline aligned */
>
Hi Francesco,
I should have cc'ed you on this one. We'll want to add another macro
like this for IIO_DECLARE_QUATERNION_AXIS(), I imagine. (This has been
applied to the fixes-togreg branch since it is a dependency to a fix.)
What to do on that for the alignment though is an open question since
we've never had a repeat of 3 before. The question is if it is OK to
have an alignment that isn't a power of two bytes. For your case, since
the data is 3 x 16-bit, it would be 3 * 2 = 6 bytes.
^ permalink raw reply
* [hid:for-7.1/lenovo 12/16] drivers/hid/hid-lenovo-go-s.c:583:21: sparse: sparse: symbol 'gamepad_poll_rate' was not declared. Should it be static?
From: kernel test robot @ 2026-03-07 23:36 UTC (permalink / raw)
To: Derek J. Clark; +Cc: oe-kbuild-all, linux-input, Jiri Kosina, Mark Pearson
tree: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-7.1/lenovo
head: d2c424e80caf8237bda4c94bc2e25398967243f9
commit: 14651777fd67507d19574cd7e7835c16e6174853 [12/16] HID: hid-lenovo-go-s: Add Feature Status Attributes
config: arc-randconfig-r113-20260307 (https://download.01.org/0day-ci/archive/20260308/202603080756.jqm0j0md-lkp@intel.com/config)
compiler: arc-linux-gcc (GCC) 13.4.0
sparse: v0.6.5-rc1
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260308/202603080756.jqm0j0md-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603080756.jqm0j0md-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
drivers/hid/hid-lenovo-go-s.c:406:72: sparse: sparse: Using plain integer as NULL pointer
>> drivers/hid/hid-lenovo-go-s.c:583:21: sparse: sparse: symbol 'gamepad_poll_rate' was not declared. Should it be static?
>> drivers/hid/hid-lenovo-go-s.c:609:21: sparse: sparse: symbol 'imu_sensor_enabled' was not declared. Should it be static?
>> drivers/hid/hid-lenovo-go-s.c:645:21: sparse: sparse: symbol 'mouse_wheel_step' was not declared. Should it be static?
drivers/hid/hid-lenovo-go-s.c:690:72: sparse: sparse: Using plain integer as NULL pointer
drivers/hid/hid-lenovo-go-s.c:697:73: sparse: sparse: Using plain integer as NULL pointer
>> drivers/hid/hid-lenovo-go-s.c:407:21: sparse: sparse: unsigned value that used to be signed checked against zero?
drivers/hid/hid-lenovo-go-s.c:406:33: sparse: signed value source
vim +/gamepad_poll_rate +583 drivers/hid/hid-lenovo-go-s.c
398
399 static ssize_t gamepad_property_show(struct device *dev,
400 struct device_attribute *attr, char *buf,
401 enum feature_status_index index)
402 {
403 size_t count = 0;
404 u8 i;
405
406 count = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, 0, 0);
> 407 if (count < 0)
408 return count;
409
410 switch (index) {
411 case FEATURE_GAMEPAD_MODE:
412 i = drvdata.gp_mode;
413 if (i >= ARRAY_SIZE(gamepad_mode_text))
414 return -EINVAL;
415 count = sysfs_emit(buf, "%s\n", gamepad_mode_text[i]);
416 break;
417 case FEATURE_AUTO_SLEEP_TIME:
418 count = sysfs_emit(buf, "%u\n", drvdata.gp_auto_sleep_time);
419 break;
420 case FEATURE_IMU_ENABLE:
421 i = drvdata.imu_sensor_en;
422 if (i >= ARRAY_SIZE(feature_enabled_text))
423 return -EINVAL;
424 count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]);
425 break;
426 case FEATURE_IMU_BYPASS:
427 i = drvdata.imu_bypass_en;
428 if (i >= ARRAY_SIZE(feature_enabled_text))
429 return -EINVAL;
430 count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]);
431 break;
432 case FEATURE_RGB_ENABLE:
433 i = drvdata.rgb_en;
434 if (i >= ARRAY_SIZE(feature_enabled_text))
435 return -EINVAL;
436 count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]);
437 break;
438 case FEATURE_TOUCHPAD_ENABLE:
439 i = drvdata.tp_en;
440 if (i >= ARRAY_SIZE(feature_enabled_text))
441 return -EINVAL;
442 count = sysfs_emit(buf, "%s\n", feature_enabled_text[i]);
443 break;
444 case FEATURE_OS_MODE:
445 i = drvdata.os_mode;
446 if (i >= ARRAY_SIZE(os_type_text))
447 return -EINVAL;
448 count = sysfs_emit(buf, "%s\n", os_type_text[i]);
449 break;
450 case FEATURE_POLL_RATE:
451 i = drvdata.gp_poll_rate;
452 if (i >= ARRAY_SIZE(poll_rate_text))
453 return -EINVAL;
454 count = sysfs_emit(buf, "%s\n", poll_rate_text[i]);
455 break;
456 case FEATURE_DPAD_MODE:
457 i = drvdata.gp_dpad_mode;
458 if (i >= ARRAY_SIZE(dpad_mode_text))
459 return -EINVAL;
460 count = sysfs_emit(buf, "%s\n", dpad_mode_text[i]);
461 break;
462 case FEATURE_MOUSE_WHEEL_STEP:
463 i = drvdata.mouse_step;
464 if (i < 1 || i > 127)
465 return -EINVAL;
466 count = sysfs_emit(buf, "%u\n", i);
467 break;
468 default:
469 return -EINVAL;
470 }
471
472 return count;
473 }
474
475 static ssize_t gamepad_property_options(struct device *dev,
476 struct device_attribute *attr,
477 char *buf,
478 enum feature_status_index index)
479 {
480 size_t count = 0;
481 unsigned int i;
482
483 switch (index) {
484 case FEATURE_GAMEPAD_MODE:
485 for (i = 0; i < ARRAY_SIZE(gamepad_mode_text); i++) {
486 count += sysfs_emit_at(buf, count, "%s ",
487 gamepad_mode_text[i]);
488 }
489 break;
490 case FEATURE_AUTO_SLEEP_TIME:
491 return sysfs_emit(buf, "0-255\n");
492 case FEATURE_IMU_ENABLE:
493 for (i = 0; i < ARRAY_SIZE(feature_enabled_text); i++) {
494 count += sysfs_emit_at(buf, count, "%s ",
495 feature_enabled_text[i]);
496 }
497 break;
498 case FEATURE_IMU_BYPASS:
499 case FEATURE_RGB_ENABLE:
500 case FEATURE_TOUCHPAD_ENABLE:
501 for (i = 0; i < ARRAY_SIZE(feature_enabled_text); i++) {
502 count += sysfs_emit_at(buf, count, "%s ",
503 feature_enabled_text[i]);
504 }
505 break;
506 case FEATURE_OS_MODE:
507 for (i = 0; i < ARRAY_SIZE(os_type_text); i++) {
508 count += sysfs_emit_at(buf, count, "%s ",
509 os_type_text[i]);
510 }
511 break;
512 case FEATURE_POLL_RATE:
513 for (i = 0; i < ARRAY_SIZE(poll_rate_text); i++) {
514 count += sysfs_emit_at(buf, count, "%s ",
515 poll_rate_text[i]);
516 }
517 break;
518 case FEATURE_DPAD_MODE:
519 for (i = 0; i < ARRAY_SIZE(dpad_mode_text); i++) {
520 count += sysfs_emit_at(buf, count, "%s ",
521 dpad_mode_text[i]);
522 }
523 break;
524 case FEATURE_MOUSE_WHEEL_STEP:
525 return sysfs_emit(buf, "1-127\n");
526 default:
527 return count;
528 }
529
530 if (count)
531 buf[count - 1] = '\n';
532
533 return count;
534 }
535
536 static ssize_t mcu_id_show(struct device *dev, struct device_attribute *attr,
537 char *buf)
538 {
539 return sysfs_emit(buf, "%*phN\n", 12, &drvdata.mcu_id);
540 }
541
542 #define LEGOS_DEVICE_ATTR_RW(_name, _attrname, _rtype, _group) \
543 static ssize_t _name##_store(struct device *dev, \
544 struct device_attribute *attr, \
545 const char *buf, size_t count) \
546 { \
547 return _group##_property_store(dev, attr, buf, count, \
548 _name.index); \
549 } \
550 static ssize_t _name##_show(struct device *dev, \
551 struct device_attribute *attr, char *buf) \
552 { \
553 return _group##_property_show(dev, attr, buf, _name.index); \
554 } \
555 static ssize_t _name##_##_rtype##_show( \
556 struct device *dev, struct device_attribute *attr, char *buf) \
557 { \
558 return _group##_property_options(dev, attr, buf, _name.index); \
559 } \
560 static DEVICE_ATTR_RW_NAMED(_name, _attrname)
561
562 #define LEGOS_DEVICE_ATTR_RO(_name, _attrname, _group) \
563 static ssize_t _name##_show(struct device *dev, \
564 struct device_attribute *attr, char *buf) \
565 { \
566 return _group##_property_show(dev, attr, buf, _name.index); \
567 } \
568 static DEVICE_ATTR_RO_NAMED(_name, _attrname)
569
570 /* Gamepad */
571 struct gos_cfg_attr auto_sleep_time = { FEATURE_AUTO_SLEEP_TIME };
572 LEGOS_DEVICE_ATTR_RW(auto_sleep_time, "auto_sleep_time", range, gamepad);
573 static DEVICE_ATTR_RO(auto_sleep_time_range);
574
575 struct gos_cfg_attr dpad_mode = { FEATURE_DPAD_MODE };
576 LEGOS_DEVICE_ATTR_RW(dpad_mode, "dpad_mode", index, gamepad);
577 static DEVICE_ATTR_RO(dpad_mode_index);
578
579 struct gos_cfg_attr gamepad_mode = { FEATURE_GAMEPAD_MODE };
580 LEGOS_DEVICE_ATTR_RW(gamepad_mode, "mode", index, gamepad);
581 static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index");
582
> 583 struct gos_cfg_attr gamepad_poll_rate = { FEATURE_POLL_RATE };
584 LEGOS_DEVICE_ATTR_RW(gamepad_poll_rate, "poll_rate", index, gamepad);
585 static DEVICE_ATTR_RO_NAMED(gamepad_poll_rate_index, "poll_rate_index");
586
587 static struct attribute *legos_gamepad_attrs[] = {
588 &dev_attr_auto_sleep_time.attr,
589 &dev_attr_auto_sleep_time_range.attr,
590 &dev_attr_dpad_mode.attr,
591 &dev_attr_dpad_mode_index.attr,
592 &dev_attr_gamepad_mode.attr,
593 &dev_attr_gamepad_mode_index.attr,
594 &dev_attr_gamepad_poll_rate.attr,
595 &dev_attr_gamepad_poll_rate_index.attr,
596 NULL,
597 };
598
599 static const struct attribute_group gamepad_attr_group = {
600 .name = "gamepad",
601 .attrs = legos_gamepad_attrs,
602 };
603
604 /* IMU */
605 struct gos_cfg_attr imu_bypass_enabled = { FEATURE_IMU_BYPASS };
606 LEGOS_DEVICE_ATTR_RW(imu_bypass_enabled, "bypass_enabled", index, gamepad);
607 static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_index");
608
> 609 struct gos_cfg_attr imu_sensor_enabled = { FEATURE_IMU_ENABLE };
610 LEGOS_DEVICE_ATTR_RW(imu_sensor_enabled, "sensor_enabled", index, gamepad);
611 static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_index");
612
613 static struct attribute *legos_imu_attrs[] = {
614 &dev_attr_imu_bypass_enabled.attr,
615 &dev_attr_imu_bypass_enabled_index.attr,
616 &dev_attr_imu_sensor_enabled.attr,
617 &dev_attr_imu_sensor_enabled_index.attr,
618 NULL,
619 };
620
621 static const struct attribute_group imu_attr_group = {
622 .name = "imu",
623 .attrs = legos_imu_attrs,
624 };
625
626 /* MCU */
627 static DEVICE_ATTR_RO(mcu_id);
628
629 struct gos_cfg_attr os_mode = { FEATURE_OS_MODE };
630 LEGOS_DEVICE_ATTR_RW(os_mode, "os_mode", index, gamepad);
631 static DEVICE_ATTR_RO(os_mode_index);
632
633 static struct attribute *legos_mcu_attrs[] = {
634 &dev_attr_mcu_id.attr,
635 &dev_attr_os_mode.attr,
636 &dev_attr_os_mode_index.attr,
637 NULL,
638 };
639
640 static const struct attribute_group mcu_attr_group = {
641 .attrs = legos_mcu_attrs,
642 };
643
644 /* Mouse */
> 645 struct gos_cfg_attr mouse_wheel_step = { FEATURE_MOUSE_WHEEL_STEP };
646 LEGOS_DEVICE_ATTR_RW(mouse_wheel_step, "step", range, gamepad);
647 static DEVICE_ATTR_RO_NAMED(mouse_wheel_step_range, "step_range");
648
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v2] usbhid: tolerate intermittent errors
From: Alan Stern @ 2026-03-07 22:52 UTC (permalink / raw)
To: Liam Mitchell
Cc: Jiri Kosina, Benjamin Tissoires, Oliver Neukum, linux-usb,
linux-input, linux-kernel
In-Reply-To: <20260307-usbhid-eproto-v2-1-e5a8abce4652@gmail.com>
On Sat, Mar 07, 2026 at 07:57:09PM +0100, Liam Mitchell wrote:
> Modifies usbhid error handling to tolerate intermittent protocol
> errors, avoiding URB resubmission delay and device reset.
>
> ---
> Protocol errors like EPROTO can occur randomly, sometimes frequently and are often not fixed by a device reset.
>
> The current error handling will only resubmit the URB after at least 13ms delay and may reset the USB device if another error occurs 1-1.5s later, regardless of error type or count.
>
> These delays and device resets increase the chance that input events will be missed and that users see symptoms like missed or sticky keyboard keys.
>
> This patch allows one protocol error per 500ms to be tolerated and have the URB re-submitted immediately.
>
> 500ms was chosen to be well above the error rate of a malfunctioning
> device but low enough to be useful for users with devices noisier than
> mine.
>
> Signed-off-by: Liam Mitchell <mitchell.liam@gmail.com>
> Link: https://lore.kernel.org/linux-input/CAOQ1CL6Q+4GNy=kgisLzs0UBXFT3b02PG8t-0rPuW-Wf6NhQ6g@mail.gmail.com/
> ---
Liam, take a look at
https://bugzilla.kernel.org/show_bug.cgi?id=221184
On Roman's system, these protocol errors occur fairly regularly,
apparently caused by high system load.
Do you think a better approach might be to reduce the 13-ms delay to
just 1 or 2 ms, and perform a reset only there has been no successful
communication for about one second? This might perhaps be _too_ lenient
sometimes, but I don't think such situations will arise in practice.
The reason for having at least a small delay is to avoid getting into a
tight resubmit/error loop in cases where the device has been unplugged.
Alan Stern
^ permalink raw reply
* [hid:for-7.1/lenovo 10/16] drivers/hid/hid-lenovo-go-s.c:164:73: sparse: sparse: Using plain integer as NULL pointer
From: kernel test robot @ 2026-03-07 20:40 UTC (permalink / raw)
To: Derek J. Clark
Cc: oe-kbuild-all, linux-input, Jiri Kosina, Mark Pearson,
Mario Limonciello
tree: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-7.1/lenovo
head: d2c424e80caf8237bda4c94bc2e25398967243f9
commit: 4325fdab5dbbfd467df6797e018c9dd0e5c3ae75 [10/16] HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver
config: arc-randconfig-r113-20260307 (https://download.01.org/0day-ci/archive/20260308/202603080451.lcXQ6ejg-lkp@intel.com/config)
compiler: arc-linux-gcc (GCC) 13.4.0
sparse: v0.6.5-rc1
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260308/202603080451.lcXQ6ejg-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603080451.lcXQ6ejg-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> drivers/hid/hid-lenovo-go-s.c:164:73: sparse: sparse: Using plain integer as NULL pointer
vim +164 drivers/hid/hid-lenovo-go-s.c
159
160 static void cfg_setup(struct work_struct *work)
161 {
162 int ret;
163
> 164 ret = mcu_property_out(drvdata.hdev, GET_VERSION, FEATURE_NONE, 0, 0);
165 if (ret) {
166 dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU Version: %i\n", ret);
167 return;
168 }
169 }
170
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH v2] usbhid: tolerate intermittent errors
From: Liam Mitchell @ 2026-03-07 18:57 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Alan Stern, Oliver Neukum, linux-usb, linux-input, linux-kernel,
Liam Mitchell
Modifies usbhid error handling to tolerate intermittent protocol
errors, avoiding URB resubmission delay and device reset.
---
Protocol errors like EPROTO can occur randomly, sometimes frequently and are often not fixed by a device reset.
The current error handling will only resubmit the URB after at least 13ms delay and may reset the USB device if another error occurs 1-1.5s later, regardless of error type or count.
These delays and device resets increase the chance that input events will be missed and that users see symptoms like missed or sticky keyboard keys.
This patch allows one protocol error per 500ms to be tolerated and have the URB re-submitted immediately.
500ms was chosen to be well above the error rate of a malfunctioning
device but low enough to be useful for users with devices noisier than
mine.
Signed-off-by: Liam Mitchell <mitchell.liam@gmail.com>
Link: https://lore.kernel.org/linux-input/CAOQ1CL6Q+4GNy=kgisLzs0UBXFT3b02PG8t-0rPuW-Wf6NhQ6g@mail.gmail.com/
---
Changes in v2:
- revert changes to hid_io_error
- add more specific fix in hid_irq_in
- Link to v1: https://lore.kernel.org/r/20260208-usbhid-eproto-v1-1-5872c10d90bb@gmail.com
---
drivers/hid/usbhid/hid-core.c | 5 +++++
drivers/hid/usbhid/usbhid.h | 1 +
2 files changed, 6 insertions(+)
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index 758eb21430cd..939e095eddfe 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -313,6 +313,11 @@ static void hid_irq_in(struct urb *urb)
case -ETIME: /* protocol error or unplug */
case -ETIMEDOUT: /* Should never happen, but... */
usbhid_mark_busy(usbhid);
+ /* Tolerate intermittent protocol errors */
+ if (time_after(jiffies, usbhid->last_proto_error + msecs_to_jiffies(500))) {
+ usbhid->last_proto_error = jiffies;
+ break;
+ }
clear_bit(HID_IN_RUNNING, &usbhid->iofl);
hid_io_error(hid);
return;
diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h
index 75fe85d3d27a..6aab9101fe34 100644
--- a/drivers/hid/usbhid/usbhid.h
+++ b/drivers/hid/usbhid/usbhid.h
@@ -86,6 +86,7 @@ struct usbhid_device {
struct timer_list io_retry; /* Retry timer */
unsigned long stop_retry; /* Time to give up, in jiffies */
unsigned int retry_delay; /* Delay length in ms */
+ unsigned long last_proto_error; /* Last protocol error time, in jiffies */
struct work_struct reset_work; /* Task context for resets */
wait_queue_head_t wait; /* For sleeping */
};
---
base-commit: b91e36222ccfb1b0985d1fcc4fb13b68fb99c972
change-id: 20260208-usbhid-eproto-152c7abcb185
Best regards,
--
Liam Mitchell <mitchell.liam@gmail.com>
^ permalink raw reply related
* [PATCH v4 2/2] Input: Add support for Wacom W9000-series penabled touchscreens
From: Hendrik Noack @ 2026-03-07 18:15 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: <20260307181557.66927-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 | 510 ++++++++++++++++++++++++
3 files changed, 523 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..3c7959a28ccb
--- /dev/null
+++ b/drivers/input/touchscreen/wacom_w9000.c
@@ -0,0 +1,510 @@
+// 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;
+
+ struct gpio_desc *flash_mode_gpio;
+ struct gpio_desc *pen_inserted_gpio;
+ struct gpio_desc *reset_gpio;
+
+ unsigned int irq;
+ unsigned int pen_insert_irq;
+
+ bool pen_inserted;
+ 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;
+}
+
+static int wacom_w9000_power_on(struct wacom_w9000_data *wacom_data)
+{
+ int error;
+
+ 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);
+
+ return error;
+}
+
+static void wacom_w9000_power_off(struct wacom_w9000_data *wacom_data)
+{
+ disable_irq(wacom_data->irq);
+ gpiod_set_value_cansleep(wacom_data->reset_gpio, 1);
+ regulator_disable(wacom_data->regulator);
+}
+
+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 irqreturn_t wacom_w9000_interrupt_pen_insert(int irq, void *dev_id)
+{
+ struct wacom_w9000_data *wacom_data = dev_id;
+ struct device *dev = &wacom_data->client->dev;
+ int error;
+ bool pen_inserted;
+
+ pen_inserted = gpiod_get_value_cansleep(wacom_data->pen_inserted_gpio);
+
+ input_report_switch(wacom_data->input_dev, SW_PEN_INSERTED, pen_inserted);
+ input_sync(wacom_data->input_dev);
+
+ if (!pen_inserted && wacom_data->pen_inserted) {
+ error = wacom_w9000_power_on(wacom_data);
+ if (error)
+ return IRQ_HANDLED;
+ } else if (pen_inserted && !wacom_data->pen_inserted) {
+ wacom_w9000_power_off(wacom_data);
+ }
+
+ dev_dbg(dev, "Pen inserted changed from %d to %d", wacom_data->pen_inserted, pen_inserted);
+
+ wacom_data->pen_inserted = pen_inserted;
+
+ return IRQ_HANDLED;
+}
+
+static int wacom_w9000_open(struct input_dev *dev)
+{
+ struct wacom_w9000_data *wacom_data = input_get_drvdata(dev);
+ int error;
+
+ if (wacom_data->pen_inserted_gpio) {
+ wacom_data->pen_inserted = gpiod_get_value_cansleep(wacom_data->pen_inserted_gpio);
+
+ input_report_switch(wacom_data->input_dev, SW_PEN_INSERTED,
+ wacom_data->pen_inserted);
+ input_sync(wacom_data->input_dev);
+ }
+
+ if (!wacom_data->pen_inserted) {
+ error = wacom_w9000_power_on(wacom_data);
+ if (error)
+ return error;
+ }
+
+ if (wacom_data->pen_inserted_gpio)
+ enable_irq(wacom_data->pen_insert_irq);
+
+ return 0;
+}
+
+static void wacom_w9000_close(struct input_dev *dev)
+{
+ struct wacom_w9000_data *wacom_data = input_get_drvdata(dev);
+
+ if (wacom_data->pen_inserted_gpio)
+ disable_irq(wacom_data->pen_insert_irq);
+
+ if (!wacom_data->pen_inserted)
+ 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->pen_inserted_gpio = devm_gpiod_get_optional(dev, "pen-inserted", GPIOD_IN);
+ if (IS_ERR(wacom_data->pen_inserted_gpio))
+ return dev_err_probe(dev, PTR_ERR(wacom_data->pen_inserted_gpio),
+ "Failed to get pen-insert 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);
+
+ 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");
+
+ if (wacom_data->pen_inserted_gpio) {
+ wacom_data->pen_insert_irq = gpiod_to_irq(wacom_data->pen_inserted_gpio);
+ error = devm_request_threaded_irq(dev, wacom_data->pen_insert_irq, NULL,
+ wacom_w9000_interrupt_pen_insert, IRQF_ONESHOT |
+ IRQF_NO_AUTOEN | IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING, "wacom_pen_insert",
+ wacom_data);
+ if (error)
+ return dev_err_probe(dev, error,
+ "Failed to register pen-insert interrupt\n");
+
+ input_set_capability(input_dev, EV_SW, SW_PEN_INSERTED);
+ } else {
+ wacom_data->pen_inserted = false;
+ }
+
+ 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);
+
+ if (wacom_data->pen_inserted_gpio)
+ disable_irq(wacom_data->pen_insert_irq);
+
+ if (!wacom_data->pen_inserted)
+ wacom_w9000_power_off(wacom_data);
+
+ return 0;
+}
+
+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);
+ int error = 0;
+
+ guard(mutex)(&wacom_data->input_dev->mutex);
+
+ if (wacom_data->pen_inserted_gpio) {
+ wacom_data->pen_inserted = gpiod_get_value_cansleep(wacom_data->pen_inserted_gpio);
+
+ input_report_switch(wacom_data->input_dev, SW_PEN_INSERTED,
+ wacom_data->pen_inserted);
+ input_sync(wacom_data->input_dev);
+ }
+
+ if (!wacom_data->pen_inserted)
+ error = wacom_w9000_power_on(wacom_data);
+
+ if (wacom_data->pen_inserted_gpio)
+ enable_irq(wacom_data->pen_insert_irq);
+
+ return error;
+}
+
+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
* [PATCH v4 1/2] dt-bindings: Input: Add Wacom W9000-series penabled touchscreens
From: Hendrik Noack @ 2026-03-07 18:15 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: <20260307181557.66927-1-hendrik-noack@gmx.de>
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 | 86 +++++++++++++++++++
1 file changed, 86 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..feb87f5db39d
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml
@@ -0,0 +1,86 @@
+# 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 chips between devices can differ and with it also
+ how the chips behaves.
+
+allOf:
+ - $ref: touchscreen.yaml#
+
+properties:
+ compatible:
+ enum:
+ - wacom,w9002
+ - wacom,w9007a-lt03
+ - wacom,w9007a-v1
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ vdd-supply:
+ description:
+ Optional regulator for the VDD digital voltage.
+
+ flash-mode-gpios:
+ maxItems: 1
+ description:
+ Optional GPIO specifier for the touchscreen's flash-mode pin.
+
+ pen-inserted-gpios:
+ maxItems: 1
+ description:
+ Optional GPIO specifier for the touchscreen's pen-insert pin.
+
+ reset-gpios:
+ maxItems: 1
+ description:
+ Optional GPIO specifier for the touchscreen's reset pin.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - vdd-supply
+
+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>;
+ pen-inserted-gpios = <&gpx0 0 GPIO_ACTIVE_LOW>;
+ reset-gpios = <&gpx0 1 GPIO_ACTIVE_LOW>;
+
+ touchscreen-x-mm = <216>;
+ touchscreen-y-mm = <135>;
+ touchscreen-inverted-x;
+ };
+ };
--
2.43.0
^ permalink raw reply related
* [PATCH v4 0/2] Add support for Wacom W9000-series penabled touchscreens
From: Hendrik Noack @ 2026-03-07 18:15 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Hendrik Noack, Ferass El Hafidi, linux-input, devicetree,
linux-kernel
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.
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
---
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 | 86 +++
drivers/input/touchscreen/Kconfig | 12 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/wacom_w9000.c | 510 ++++++++++++++++++
4 files changed, 609 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
* [hid:for-7.1/lenovo 10/16] drivers/hid/hid-lenovo-go-s.c:164:73: sparse: sparse: Using plain integer as NULL pointer
From: kernel test robot @ 2026-03-07 15:05 UTC (permalink / raw)
To: Derek J. Clark
Cc: oe-kbuild-all, linux-input, Jiri Kosina, Mark Pearson,
Mario Limonciello
tree: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-7.1/lenovo
head: d2c424e80caf8237bda4c94bc2e25398967243f9
commit: 4325fdab5dbbfd467df6797e018c9dd0e5c3ae75 [10/16] HID: hid-lenovo-go-s: Add Lenovo Legion Go S Series HID Driver
config: arc-randconfig-r113-20260307 (https://download.01.org/0day-ci/archive/20260307/202603072210.hUL7ptt2-lkp@intel.com/config)
compiler: arc-linux-gcc (GCC) 13.4.0
sparse: v0.6.5-rc1
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260307/202603072210.hUL7ptt2-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603072210.hUL7ptt2-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> drivers/hid/hid-lenovo-go-s.c:164:73: sparse: sparse: Using plain integer as NULL pointer
vim +164 drivers/hid/hid-lenovo-go-s.c
159
160 static void cfg_setup(struct work_struct *work)
161 {
162 int ret;
163
> 164 ret = mcu_property_out(drvdata.hdev, GET_VERSION, FEATURE_NONE, 0, 0);
165 if (ret) {
166 dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU Version: %i\n", ret);
167 return;
168 }
169 }
170
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v2 0/7] Add support for mt6392 PMIC
From: Krzysztof Kozlowski @ 2026-03-07 15:04 UTC (permalink / raw)
To: Luca Leonardo Scorcia
Cc: linux-mediatek, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Sen Chu, Sean Wang, Macpaul Lin, Lee Jones,
Matthias Brugger, AngeloGioacchino Del Regno, Liam Girdwood,
Mark Brown, Val Packett, Gary Bisson, Louis-Alexis Eyraud,
Julien Massot, Fabien Parent, Chen Zhong, linux-input, devicetree,
linux-kernel, linux-pm, linux-arm-kernel
In-Reply-To: <20260306120521.163654-1-l.scorcia@gmail.com>
On Fri, Mar 06, 2026 at 12:03:04PM +0000, Luca Leonardo Scorcia wrote:
> The MediaTek mt6392 PMIC is usually found on devices powered by
> the mt8516/mt8167 SoC, and is yet another mt6397 variant.
>
> This series is mostly based around patches submitted a couple
> years ago by Fabien Parent and not merged and from Val Packett's
> submission from Jan 2025 that included extra cleanups, fixes, and a
> new dtsi file similar to ones that exist for other PMICs. Some
> comments weren't addressed and the series was ultimately not merged.
>
> This series only enables three functions: regulators, keys, and RTC.
>
> I have added a handful of device tree improvements to fix some
> dtbs_check errors and addressed the comments from last year's
> reviews. The series has been tested on Xiaomi Mi Smart Clock x04g.
>
> v2: Review feedback - replaced explicit compatibles with fallbacks
>
> Fabien Parent (5):
> dt-bindings: mfd: mt6397: Add bindings for MT6392 PMIC
> dt-bindings: regulator: add support for MT6392
This is incomplete - where is the actual schema for the regulators?
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 2/7] dt-bindings: regulator: add support for MT6392
From: Krzysztof Kozlowski @ 2026-03-07 15:03 UTC (permalink / raw)
To: Luca Leonardo Scorcia
Cc: linux-mediatek, Fabien Parent, Val Packett, Rob Herring,
Dmitry Torokhov, Krzysztof Kozlowski, Conor Dooley, Sen Chu,
Sean Wang, Macpaul Lin, Lee Jones, Matthias Brugger,
AngeloGioacchino Del Regno, Liam Girdwood, Mark Brown,
Gary Bisson, Julien Massot, Louis-Alexis Eyraud, Chen Zhong,
linux-input, devicetree, linux-kernel, linux-pm, linux-arm-kernel
In-Reply-To: <20260306120521.163654-3-l.scorcia@gmail.com>
On Fri, Mar 06, 2026 at 12:03:06PM +0000, Luca Leonardo Scorcia wrote:
> From: Fabien Parent <parent.f@gmail.com>
>
> Add binding documentation of the regulator for MT6392 SoCs.
>
> Signed-off-by: Fabien Parent <parent.f@gmail.com>
> Signed-off-by: Val Packett <val@packett.cool>
> Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
> Acked-by: Rob Herring (Arm) <robh@kernel.org>
> ---
> Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml | 1 +
> 1 file changed, 1 insertion(+)
That's a mfd patch, so should be squashed to the patch changing mfd.
Alone is incomplete thus not really correct.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 1/7] dt-bindings: mfd: mt6397: Add bindings for MT6392 PMIC
From: Krzysztof Kozlowski @ 2026-03-07 15:02 UTC (permalink / raw)
To: Luca Leonardo Scorcia
Cc: linux-mediatek, Fabien Parent, Val Packett, Dmitry Torokhov,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Sen Chu,
Sean Wang, Macpaul Lin, Lee Jones, Matthias Brugger,
AngeloGioacchino Del Regno, Liam Girdwood, Mark Brown,
Gary Bisson, Julien Massot, Louis-Alexis Eyraud, Chen Zhong,
linux-input, devicetree, linux-kernel, linux-pm, linux-arm-kernel
In-Reply-To: <20260306120521.163654-2-l.scorcia@gmail.com>
On Fri, Mar 06, 2026 at 12:03:05PM +0000, Luca Leonardo Scorcia wrote:
> From: Fabien Parent <parent.f@gmail.com>
>
> Add the currently supported bindings for the MT6392 PMIC.
>
> Signed-off-by: Fabien Parent <parent.f@gmail.com>
> Signed-off-by: Val Packett <val@packett.cool>
> Signed-off-by: Luca Leonardo Scorcia <l.scorcia@gmail.com>
> ---
> .../devicetree/bindings/mfd/mediatek,mt6397.yaml | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml
> index 6a89b479d10f..c358b2f8059c 100644
> --- a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml
> +++ b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml
> @@ -40,6 +40,10 @@ properties:
> - mediatek,mt6358
> - mediatek,mt6359
> - mediatek,mt6397
> + - items:
> + - enum:
> + - mediatek,mt6392
> + - const: mediatek,mt6323
> - items:
> - enum:
> - mediatek,mt6366
> @@ -72,6 +76,10 @@ properties:
> - enum:
> - mediatek,mt6366-rtc
> - const: mediatek,mt6358-rtc
> + - items:
> + - enum:
> + - mediatek,mt6392-rtc
> + - const: mediatek,mt6397-rtc
Why not mt6323-rtc?
Mixing different fallbacks should be briefly explained in the commit
msg.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 1/2] iio: hid-sensor-gyro-3d: move iio_device_register() to end of probe()
From: Jonathan Cameron @ 2026-03-07 14:21 UTC (permalink / raw)
To: Bhargav Joshi
Cc: jikos, srinivas.pandruvada, dlechner, nuno.sa, andy, linux-iio,
linux-kernel, linux-input
In-Reply-To: <CAPC4XCRom4ri1QKw_mTnZGc9np5XFD1LskQnd=ssDAKBdOkGmQ@mail.gmail.com>
On Mon, 2 Mar 2026 01:00:06 +0530
Bhargav Joshi <rougueprince47@gmail.com> wrote:
> On Sun, Mar 1, 2026 at 5:15 PM Jonathan Cameron <jic23@kernel.org> wrote:
> >
> > On Sun, 1 Mar 2026 00:43:59 +0530
> > Bhargav Joshi <rougueprince47@gmail.com> wrote:
> >
> > > Currently, calling iio_device_register() before
> > > sensor_hub_register_callback() may create a race condition where the
> > > device is exposed to userspace before callbacks are wired.
> >
> > We needs some more here on 'why' this is a problem.
> > Whilst this is an unusual arrangement, I couldn't immediately find
> > anything that was actually broken. It's possible data will turn up
> > after we tear down the callbacks and before the userspace interfaces
> > are removed, but I think that just results in dropping data. Given it's
> > in a race anyway I don't think we care about that. The callbacks
> > don't seem to be involved in device configuration which can go on whether
> > or not the callbacks are registered. Maybe there is something that
> > won't get acknowledged if they aren't in place in time?
> >
> > For a fix like this we'd normally want to see a clear flow that
> > leads to a bug.
> >
> > Also a fixes tag so we know how far to backport.
> >
>
> Hi Jonathan,
>
> I originally submitted the patch for the driver because calling
> iio_device_register() before the callbacks are wired up violates
> standard IIO LIFO ordering. I assumed this created a dangerous race
> condition with userspace. However, as you correctly pointed out, the
> HID sensor hub safely drops those early events without crashing, even
> if it is unusual behaviour still won't have a hard crash.
>
> My underlying goal was simply a structural cleanup to align the
> probe() and remove() sequences with standard IIO architecture.
>
> Would you like me to send a v2 of this patch with a revised commit
> message classifying it purely as a structural cleanup (and without a
> Fixes tag), or is the current driver behavior acceptable enough that I
> should just drop this patch entirely?
My gut is leave this one alone. It's a rather unusual driver in lots
of ways, so I don't really mind one more ;)
Lets see if anyone else has strong views one way or the other.
Srinivas?
>
> Thanks,
> Bhargav
>
> > >
> > > Move iio_device_register() to the end of the probe() function to prevent
> > > race condition.
> > >
> > > Consequently, update the error handling path in probe() and in remove()
> > > ensuring that iio_device_unregister() is called first to cut off
> > > userspace access before the hardware callbacks are removed.
> > >
> > > Signed-off-by: Bhargav Joshi <rougueprince47@gmail.com>
> > > ---
> > > drivers/iio/gyro/hid-sensor-gyro-3d.c | 20 ++++++++++----------
> > > 1 file changed, 10 insertions(+), 10 deletions(-)
> > >
> > > diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> > > index c43990c518f7..8e3628cd8529 100644
> > > --- a/drivers/iio/gyro/hid-sensor-gyro-3d.c
> > > +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> > > @@ -333,12 +333,6 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
> > > return ret;
> > > }
> > >
> > > - ret = iio_device_register(indio_dev);
> > > - if (ret) {
> > > - dev_err(&pdev->dev, "device register failed\n");
> > > - goto error_remove_trigger;
> > > - }
> > > -
> > > gyro_state->callbacks.send_event = gyro_3d_proc_event;
> > > gyro_state->callbacks.capture_sample = gyro_3d_capture_sample;
> > > gyro_state->callbacks.pdev = pdev;
> > > @@ -346,13 +340,19 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
> > > &gyro_state->callbacks);
> > > if (ret < 0) {
> > > dev_err(&pdev->dev, "callback reg failed\n");
> > > - goto error_iio_unreg;
> > > + goto error_remove_trigger;
> > > + }
> > > +
> > > + ret = iio_device_register(indio_dev);
> > > + if (ret) {
> > > + dev_err(&pdev->dev, "device register failed\n");
> > > + goto error_remove_callback;
> > > }
> > >
> > > return ret;
> > >
> > > -error_iio_unreg:
> > > - iio_device_unregister(indio_dev);
> > > +error_remove_callback:
> > > + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D);
> > > error_remove_trigger:
> > > hid_sensor_remove_trigger(indio_dev, &gyro_state->common_attributes);
> > > return ret;
> > > @@ -365,8 +365,8 @@ static void hid_gyro_3d_remove(struct platform_device *pdev)
> > > struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> > > struct gyro_3d_state *gyro_state = iio_priv(indio_dev);
> > >
> > > - sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D);
> > > iio_device_unregister(indio_dev);
> > > + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D);
> > > hid_sensor_remove_trigger(indio_dev, &gyro_state->common_attributes);
> > > }
> > >
> >
^ permalink raw reply
* [PATCH v2] HID: sony: add battery status support for Rock Band 4 PS5 guitars
From: Rosalie Wanders @ 2026-03-07 9:48 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Rosalie Wanders, linux-input, linux-kernel
This commit adds battery status support for Rock Band 4 PS5 guitars.
The data is reported in the same way as the dualsense in hid-playstation
except it's located at byte 30.
Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
---
drivers/hid/hid-sony.c | 40 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index a89af14e4acc..5a1d41cb3a72 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -72,7 +72,8 @@
NAVIGATION_CONTROLLER_BT)
#define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER | BUZZ_CONTROLLER |\
MOTION_CONTROLLER | NAVIGATION_CONTROLLER)
-#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER)
+#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER |\
+ RB4_GUITAR_PS5)
#define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER)
#define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT)
#define NSG_MRXU_REMOTE (NSG_MR5U_REMOTE_BT | NSG_MR7U_REMOTE_BT)
@@ -991,6 +992,12 @@ static void rb4_ps4_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
{
+ u8 charging_status;
+ u8 battery_data;
+ u8 battery_capacity;
+ u8 battery_status;
+ unsigned long flags;
+
/*
* Rock Band 4 PS5 guitars have whammy and
* tilt functionality, they're located at
@@ -1003,6 +1010,37 @@ static void rb4_ps5_guitar_parse_report(struct sony_sc *sc, u8 *rd, int size)
input_report_abs(sc->input_dev, ABS_Z, rd[41]);
input_report_abs(sc->input_dev, ABS_RZ, rd[42]);
+ /*
+ * Rock Band 4 PS5 guitars also report the
+ * battery status and level at byte 30.
+ */
+ charging_status = (rd[30] >> 4) & 0x0F;
+ battery_data = rd[30] & 0x0F;
+
+ switch (charging_status) {
+ case 0x0:
+ battery_capacity = min(battery_data * 10 + 5, 100);
+ battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case 0x1:
+ battery_capacity = min(battery_data * 10 + 5, 100);
+ battery_status = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case 0x2:
+ battery_capacity = 100;
+ battery_status = POWER_SUPPLY_STATUS_FULL;
+ break;
+ default:
+ battery_capacity = 0;
+ battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ spin_lock_irqsave(&sc->lock, flags);
+ sc->battery_capacity = battery_capacity;
+ sc->battery_status = battery_status;
+ spin_unlock_irqrestore(&sc->lock, flags);
+
input_sync(sc->input_dev);
}
--
2.53.0
^ permalink raw reply related
* [PATCH] Input: atmel_mxt_ts: Allow per-board device config
From: Hendrik Noack @ 2026-03-07 9:26 UTC (permalink / raw)
To: Nick Dyer, Dmitry Torokhov; +Cc: Hendrik Noack, linux-input, linux-kernel
The device config can be device dependent.
Rewrite the code to be able to get a per-board suffixed device config as
well. If this does not exist, fall back to the standard device config.
Signed-off-by: Hendrik Noack <hendrik-noack@gmx.de>
---
drivers/input/touchscreen/atmel_mxt_ts.c | 64 ++++++++++++++++++++++--
1 file changed, 59 insertions(+), 5 deletions(-)
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index dd0544cc1bc1..0d3921482d9f 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -36,7 +36,9 @@
/* Firmware files */
#define MXT_FW_NAME "maxtouch.fw"
-#define MXT_CFG_NAME "maxtouch.cfg"
+#define MXT_CFG_FOLDER "atmel"
+#define MXT_CFG_NAME "maxtouch"
+#define MXT_CFG_EXTENSION ".cfg"
#define MXT_CFG_MAGIC "OBP_RAW V1"
/* Registers */
@@ -2248,6 +2250,60 @@ static void mxt_config_cb(const struct firmware *cfg, void *ctx)
release_firmware(cfg);
}
+static void mxt_board_config_cb(const struct firmware *cfg, void *ctx)
+{
+ if (cfg) {
+ mxt_config_cb(cfg, ctx);
+ } else {
+ struct mxt_data *data = ctx;
+ char *fw_name;
+
+ fw_name = kasprintf(GFP_KERNEL, "%s%s", MXT_CFG_NAME, MXT_CFG_EXTENSION);
+ if (!fw_name)
+ return;
+
+ request_firmware_nowait(THIS_MODULE, true, fw_name, &data->client->dev, GFP_KERNEL,
+ data, mxt_config_cb);
+ }
+}
+
+static int mxt_invoke_config_loader(struct mxt_data *data)
+{
+ struct device_node *root;
+ char *board_type = NULL;
+ char *fw_name;
+ void (*cb)(const struct firmware *fw, void *context);
+
+ root = of_find_node_by_path("/");
+ if (root) {
+ const char *tmp;
+
+ if (!of_property_read_string_index(root, "compatible", 0, &tmp)) {
+ board_type = kstrdup(tmp, GFP_KERNEL);
+
+ /* get rid of '/' in the compatible string to be able to find the FW */
+ if (board_type)
+ strreplace(board_type, '/', '-');
+ }
+ of_node_put(root);
+ }
+
+ if (board_type) {
+ fw_name = kasprintf(GFP_KERNEL, "%s/%s.%s%s", MXT_CFG_FOLDER, MXT_CFG_NAME,
+ board_type, MXT_CFG_EXTENSION);
+ cb = mxt_board_config_cb;
+ kfree(board_type);
+ } else {
+ fw_name = kasprintf(GFP_KERNEL, "%s%s", MXT_CFG_NAME, MXT_CFG_EXTENSION);
+ cb = mxt_config_cb;
+ }
+ if (!fw_name)
+ return -ENOMEM;
+
+ return request_firmware_nowait(THIS_MODULE, true, fw_name, &data->client->dev, GFP_KERNEL,
+ data, cb);
+}
+
static int mxt_initialize(struct mxt_data *data)
{
struct i2c_client *client = data->client;
@@ -2294,11 +2350,9 @@ static int mxt_initialize(struct mxt_data *data)
if (error)
return error;
- error = request_firmware_nowait(THIS_MODULE, true, MXT_CFG_NAME,
- &client->dev, GFP_KERNEL, data,
- mxt_config_cb);
+ error = mxt_invoke_config_loader(data);
if (error) {
- dev_err(&client->dev, "Failed to invoke firmware loader: %d\n",
+ dev_err(&client->dev, "Failed to invoke config loader: %d\n",
error);
return error;
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH 09/12] dt-bindings: input: Document hid-over-spi DT schema
From: Val Packett @ 2026-03-07 7:25 UTC (permalink / raw)
To: Jingyuan Liang, Jiri Kosina, Benjamin Tissoires, Jonathan Corbet,
Mark Brown, Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-input, linux-doc, linux-kernel, linux-spi,
linux-trace-kernel, devicetree, hbarnor, Dmitry Antipov,
Jarrett Schultz
In-Reply-To: <20260303-send-upstream-v1-9-1515ba218f3d@chromium.org>
On 3/3/26 3:13 AM, Jingyuan Liang wrote:
> Documentation describes the required and optional properties for
> implementing Device Tree for a Microsoft G6 Touch Digitizer that
> supports HID over SPI Protocol 1.0 specification.
> […]
> +properties:
> + compatible:
> + oneOf:
> + - items:
> + - enum:
> + - microsoft,g6-touch-digitizer
> + - const: hid-over-spi
> + - description: Just "hid-over-spi" alone is allowed, but not recommended.
> […]
> +required:
> + - compatible
> + - interrupts
> + - reset-gpios
Why is reset required? Is it so implausible on some device implementing
the spec there wouldn't be a reset gpio?
> + - vdd-supply
Linux makes up a dummy regulator if DT doesn't provide one, so can
regulators even be required?
> […]
> + compatible = "hid-over-spi";
Not following your own recommendation from above :)
> + reg = <0x0>;
> + interrupts-extended = <&gpio 42 IRQ_TYPE_EDGE_FALLING>;
> + reset-gpios = <&gpio 27 GPIO_ACTIVE_LOW>;
> + vdd-supply = <&pm8350c_l3>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&ts_d6_reset_assert &ts_d6_int_bias>;
Heh, "reset_assert" is a name implying it would actually set the value
from the pinctrl properties, which is what had to be done before
reset-gpios were supported. But now reset-gpios are supported.
Thanks,
~val
P.S. happy to see work on this happen again!
^ permalink raw reply
* [PATCH v3] HID: winwing: Enable rumble effects
From: Ivan Gorinov @ 2026-03-07 5:22 UTC (permalink / raw)
To: Jiri Kosina; +Cc: linux-input, linux-kernel
Enable rumble motor control on TGRIP-15E and TGRIP-15EX throttle grips
by sending haptic feedback commands (EV_FF events) to the input device.
Signed-off-by: Ivan Gorinov <linux-kernel@altimeter.info>
---
Changes since v2:
- Add comments about USB requests for LED and rumble control
Changes since v1:
- Fix possible NULL pointer dereference
---
drivers/hid/hid-winwing.c | 196 +++++++++++++++++++++++++++++++++++---
1 file changed, 182 insertions(+), 14 deletions(-)
diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c
index ab65dc12d1e0..9cd25a77999e 100644
--- a/drivers/hid/hid-winwing.c
+++ b/drivers/hid/hid-winwing.c
@@ -12,6 +12,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/workqueue.h>
#define MAX_REPORT 16
@@ -35,10 +36,14 @@ static const struct winwing_led_info led_info[3] = {
struct winwing_drv_data {
struct hid_device *hdev;
- __u8 *report_buf;
- struct mutex lock;
- int map_more_buttons;
- unsigned int num_leds;
+ struct mutex lights_lock;
+ __u8 *report_lights;
+ __u8 *report_rumble;
+ struct work_struct rumble_work;
+ struct ff_rumble_effect rumble;
+ int rumble_left;
+ int rumble_right;
+ int has_grip15;
struct winwing_led leds[];
};
@@ -47,11 +52,15 @@ static int winwing_led_write(struct led_classdev *cdev,
{
struct winwing_led *led = (struct winwing_led *) cdev;
struct winwing_drv_data *data = hid_get_drvdata(led->hdev);
- __u8 *buf = data->report_buf;
+ __u8 *buf = data->report_lights;
int ret;
- mutex_lock(&data->lock);
+ mutex_lock(&data->lights_lock);
+ /*
+ * Mimicking requests captured by usbmon when LEDs
+ * are controlled by the vendor's app in a VM.
+ */
buf[0] = 0x02;
buf[1] = 0x60;
buf[2] = 0xbe;
@@ -69,7 +78,7 @@ static int winwing_led_write(struct led_classdev *cdev,
ret = hid_hw_output_report(led->hdev, buf, 14);
- mutex_unlock(&data->lock);
+ mutex_unlock(&data->lights_lock);
return ret;
}
@@ -87,9 +96,9 @@ static int winwing_init_led(struct hid_device *hdev,
if (!data)
return -EINVAL;
- data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
+ data->report_lights = devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
- if (!data->report_buf)
+ if (!data->report_lights)
return -ENOMEM;
for (i = 0; i < 3; i += 1) {
@@ -117,7 +126,7 @@ static int winwing_init_led(struct hid_device *hdev,
return ret;
}
-static int winwing_map_button(int button, int map_more_buttons)
+static int winwing_map_button(int button, int has_grip15)
{
if (button < 1)
return KEY_RESERVED;
@@ -141,7 +150,7 @@ static int winwing_map_button(int button, int map_more_buttons)
return (button - 65) + BTN_TRIGGER_HAPPY17;
}
- if (!map_more_buttons) {
+ if (!has_grip15) {
/*
* Not mapping numbers [33 .. 64] which
* are not assigned to any real buttons
@@ -194,13 +203,149 @@ static int winwing_input_mapping(struct hid_device *hdev,
/* Button numbers start with 1 */
button = usage->hid & HID_USAGE;
- code = winwing_map_button(button, data->map_more_buttons);
+ code = winwing_map_button(button, data->has_grip15);
hid_map_usage(hi, usage, bit, max, EV_KEY, code);
return 1;
}
+/*
+ * If x ≤ 0, return 0;
+ * if x is in [1 .. 65535], return a value in [1 .. 255]
+ */
+static inline int convert_magnitude(int x)
+{
+ if (x < 1)
+ return 0;
+
+ return ((x * 255) >> 16) + 1;
+}
+
+static int winwing_haptic_rumble(struct winwing_drv_data *data)
+{
+ __u8 *buf;
+ __u8 m;
+
+ if (!data)
+ return -EINVAL;
+
+ if (!data->hdev)
+ return -EINVAL;
+
+ buf = data->report_rumble;
+
+ if (!buf)
+ return -EINVAL;
+
+ m = convert_magnitude(data->rumble.strong_magnitude);
+ if (m != data->rumble_left) {
+ int ret;
+
+ /*
+ * Mimicking requests captured by usbmon when rumble
+ * is activated by the vendor's app in a VM.
+ */
+ buf[0] = 0x02;
+ buf[1] = 0x01;
+ buf[2] = 0xbf;
+ buf[3] = 0x00;
+ buf[4] = 0x00;
+ buf[5] = 0x03;
+ buf[6] = 0x49;
+ buf[7] = 0x00;
+ buf[8] = m;
+ buf[9] = 0x00;
+ buf[10] = 0;
+ buf[11] = 0;
+ buf[12] = 0;
+ buf[13] = 0;
+
+ ret = hid_hw_output_report(data->hdev, buf, 14);
+ if (ret < 0) {
+ hid_err(data->hdev, "error %d (%*ph)\n", ret, 14, buf);
+ return ret;
+ }
+ data->rumble_left = m;
+ }
+
+ m = convert_magnitude(data->rumble.weak_magnitude);
+ if (m != data->rumble_right) {
+ int ret;
+
+ /*
+ * Mimicking requests captured by usbmon when rumble
+ * is activated by the vendor's app in a VM.
+ */
+ buf[0] = 0x02;
+ buf[1] = 0x03;
+ buf[2] = 0xbf;
+ buf[3] = 0x00;
+ buf[4] = 0x00;
+ buf[5] = 0x03;
+ buf[6] = 0x49;
+ buf[7] = 0x00;
+ buf[8] = m;
+ buf[9] = 0x00;
+ buf[10] = 0;
+ buf[11] = 0;
+ buf[12] = 0;
+ buf[13] = 0;
+
+ ret = hid_hw_output_report(data->hdev, buf, 14);
+ if (ret < 0) {
+ hid_err(data->hdev, "error %d (%*ph)\n", ret, 14, buf);
+ return ret;
+ }
+ data->rumble_right = m;
+ }
+
+ return 0;
+}
+
+
+static void winwing_haptic_rumble_cb(struct work_struct *work)
+{
+ struct winwing_drv_data *data;
+
+ data = container_of(work, struct winwing_drv_data, rumble_work);
+ winwing_haptic_rumble(data);
+}
+
+static int winwing_play_effect(struct input_dev *dev, void *context,
+ struct ff_effect *effect)
+{
+ struct winwing_drv_data *data = (struct winwing_drv_data *) context;
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ if (!data)
+ return -EINVAL;
+
+ data->rumble = effect->u.rumble;
+
+ return schedule_work(&data->rumble_work);
+}
+
+static int winwing_init_ff(struct hid_device *hdev, struct hid_input *hidinput)
+{
+ struct winwing_drv_data *data;
+
+ data = (struct winwing_drv_data *) hid_get_drvdata(hdev);
+ if (!data)
+ return -EINVAL;
+
+ data->report_rumble = devm_kzalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
+ data->rumble_left = -1;
+ data->rumble_right = -1;
+
+ input_set_capability(hidinput->input, EV_FF, FF_RUMBLE);
+
+ return input_ff_create_memless(hidinput->input, data,
+ winwing_play_effect);
+}
+
static int winwing_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@@ -219,10 +364,12 @@ static int winwing_probe(struct hid_device *hdev,
if (!data)
return -ENOMEM;
- data->map_more_buttons = id->driver_data;
-
+ data->hdev = hdev;
+ data->has_grip15 = id->driver_data;
hid_set_drvdata(hdev, data);
+ INIT_WORK(&data->rumble_work, winwing_haptic_rumble_cb);
+
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
@@ -232,19 +379,39 @@ static int winwing_probe(struct hid_device *hdev,
return 0;
}
+static void winwing_remove(struct hid_device *hdev)
+{
+ struct winwing_drv_data *data;
+
+ data = (struct winwing_drv_data *) hid_get_drvdata(hdev);
+
+ if (data)
+ cancel_work_sync(&data->rumble_work);
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
static int winwing_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
{
+ struct winwing_drv_data *data;
int ret;
+ data = (struct winwing_drv_data *) hid_get_drvdata(hdev);
+
ret = winwing_init_led(hdev, hidinput->input);
if (ret)
hid_err(hdev, "led init failed\n");
+ if (data->has_grip15)
+ winwing_init_ff(hdev, hidinput);
+
return ret;
}
+/* Set driver_data to 1 for grips with rumble motor and more than 32 buttons */
static const struct hid_device_id winwing_devices[] = {
{ HID_USB_DEVICE(0x4098, 0xbd65), .driver_data = 1 }, /* TGRIP-15E */
{ HID_USB_DEVICE(0x4098, 0xbd64), .driver_data = 1 }, /* TGRIP-15EX */
@@ -261,6 +428,7 @@ static struct hid_driver winwing_driver = {
.input_configured = winwing_input_configured,
.input_mapping = winwing_input_mapping,
.probe = winwing_probe,
+ .remove = winwing_remove,
};
module_hid_driver(winwing_driver);
--
2.43.0
^ permalink raw reply related
* [PATCH] HID: core: use flex array allocation
From: Rosen Penev @ 2026-03-07 3:29 UTC (permalink / raw)
To: linux-input
Cc: Jiri Kosina, linux-hardening, gustavoars, Benjamin Tissoires,
open list
Instead of embedding a pointer in the struct, use a flexible array
member to avoid the + 1 trick to point to allocation after the struct.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/hid/hid-core.c | 6 ++----
include/linux/hid.h | 2 +-
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 840a60113868..6bd6cbf26c6d 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -127,15 +127,13 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned
return NULL;
}
- field = kvzalloc((sizeof(struct hid_field) +
- usages * sizeof(struct hid_usage) +
- 3 * usages * sizeof(unsigned int)), GFP_KERNEL);
+ field = kvzalloc(struct_size(field, usage, usages) +
+ 3 * usages * sizeof(unsigned int), GFP_KERNEL);
if (!field)
return NULL;
field->index = report->maxfield++;
report->field[field->index] = field;
- field->usage = (struct hid_usage *)(field + 1);
field->value = (s32 *)(field->usage + usages);
field->new_value = (s32 *)(field->value + usages);
field->usages_priorities = (s32 *)(field->new_value + usages);
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 2990b9f94cb5..e2c3e2528582 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -524,7 +524,6 @@ struct hid_field {
unsigned physical; /* physical usage for this field */
unsigned logical; /* logical usage for this field */
unsigned application; /* application usage for this field */
- struct hid_usage *usage; /* usage table for this function */
unsigned maxusage; /* maximum usage index */
unsigned flags; /* main-item flags (i.e. volatile,array,constant) */
unsigned report_offset; /* bit offset in the report */
@@ -549,6 +548,7 @@ struct hid_field {
struct hid_input *hidinput; /* associated input structure */
__u16 dpad; /* dpad input code */
unsigned int slot_idx; /* slot index in a report */
+ struct hid_usage usage[]; /* usage table for this function */
};
#define HID_MAX_FIELDS 256
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v3 0/3] Input: st1232 - add system wakeup support
From: phucduc.bui @ 2026-03-07 2:53 UTC (permalink / raw)
To: wsa+renesas
Cc: conor+dt, devicetree, dmitry.torokhov, geert+renesas, hechtb,
javier.carrasco, jeff, krzk+dt, linux-input, linux-kernel,
linux-renesas-soc, magnus.damm, phucduc.bui, robh
In-Reply-To: <aaq_Rft0gvVqxmMD@shikoro>
Hi Wolfram,
> Krzysztof already adviced you to not attach new series to old threads.
> Please follow this suggestion:
>
> Do not attach (thread) your patchsets to some other threads (unrelated
> or older versions). This buries them deep in the mailbox and might
> interfere with applying entire sets. See also:
>
> https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830
>
You are right, and I apologize for the duplication of the mistake.
I missed Krzysztof's earlier reply while I was preparing v3, which led to
this incorrect threading again. I have already replied to Krzysztof's
thread to acknowledge the error.
I will follow the proper process by starting a fresh, un-threaded series
for v4.
Thank you for the reminder.
Best regards,
Phuc
^ permalink raw reply
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