* Re: [PATCH] Input: goodix - fix inverted Y coordinate on SUPI S10
From: Hans de Goede @ 2026-01-11 21:30 UTC (permalink / raw)
To: Yajat Kumar, Dmitry Torokhov, open list:GOODIX TOUCHSCREEN,
open list
In-Reply-To: <20251230221639.582406-1-yajatapps3@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 2373 bytes --]
Hi Yajat,
On 30-Dec-25 23:16, Yajat Kumar wrote:
> The touchscreen on the SUPI S10 reports inverted Y coordinates, causing
> touch input to be mirrored vertically relative to the display.
>
> Add a DMI-based quirk to invert the Y coordinate on this device so that
> touch input matches the display orientation.
>
> Tested on SUPI S10 tablet with Goodix touchscreen controller.
>
> Signed-off-by: Yajat Kumar <yajatapps3@gmail.com>
Thank you for your patch. This kind of quirks really belong
in drivers/platform/x86/touchscreen_dmi.c instead of in individual
touchscreen drivers.
The inverted_x_screen[] DMI quirk is a left-over from before we
moved these quirks to touchscreen_dmi.c and unfortunately we cannot
move this because we've not been able to find someone to test this.
I've attached a patch which should fix the issue using
touchscreen_dmi.c . Note you may need to change the GDIX1001 in
the patch to GDIX1002, see "ls /sys/bus/i2c/devices" to see which
ACPI HID your touchscreen is using.
Can you please test the attached patch ?
Regards,
Hans
> ---
> drivers/input/touchscreen/goodix.c | 22 ++++++++++++++++++++++
> 1 file changed, 22 insertions(+)
>
> diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
> index f8798d11ec03..d675a85a9312 100644
> --- a/drivers/input/touchscreen/goodix.c
> +++ b/drivers/input/touchscreen/goodix.c
> @@ -160,6 +160,22 @@ static const struct dmi_system_id inverted_x_screen[] = {
> {}
> };
>
> +/*
> + * Those tablets have their y coordinate inverted
> + */
> +static const struct dmi_system_id inverted_y_screen[] = {
> +#if defined(CONFIG_DMI) && defined(CONFIG_X86)
> + {
> + .ident = "SUPI S10",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "SUPI"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "S10")
> + },
> + },
> +#endif
> + {}
> +};
> +
> /**
> * goodix_i2c_read - read data from a register of the i2c slave device.
> *
> @@ -1212,6 +1228,12 @@ static int goodix_configure_dev(struct goodix_ts_data *ts)
> "Applying 'inverted x screen' quirk\n");
> }
>
> + if (dmi_check_system(inverted_y_screen)) {
> + ts->prop.invert_y = true;
> + dev_dbg(&ts->client->dev,
> + "Applying 'inverted y screen' quirk\n");
> + }
> +
> error = input_mt_init_slots(ts->input_dev, ts->max_touch_num,
> INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
> if (error) {
[-- Attachment #2: 0001-platform-x86-touchscreen_dmi-Add-quirk-for-y-inverte.patch --]
[-- Type: text/x-patch, Size: 2074 bytes --]
From 5df9d895db2ff3b3ec58a6b0c9c3a09d3c852b29 Mon Sep 17 00:00:00 2001
From: Hans de Goede <johannes.goede@oss.qualcomm.com>
Date: Sun, 11 Jan 2026 22:25:58 +0100
Subject: [PATCH] platform/x86: touchscreen_dmi: Add quirk for y-inverted
Goodix touchscreen on SUPI S10
The touchscreen on the SUPI S10 tablet reports inverted Y coordinates,
causing touch input to be mirrored vertically relative to the display.
Add a quirk to set the "touchscreen-inverted-y" boolean device-property
on the touchscreen device, so that the goodix_ts driver will fixup
the coordinates.
Reported-by: Yajat Kumar <yajatapps3@gmail.com>
Closes: https://lore.kernel.org/linux-input/20251230221639.582406-1-yajatapps3@gmail.com/
Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
---
drivers/platform/x86/touchscreen_dmi.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c
index bdc19cd8d3ed..d83c387821ea 100644
--- a/drivers/platform/x86/touchscreen_dmi.c
+++ b/drivers/platform/x86/touchscreen_dmi.c
@@ -410,6 +410,16 @@ static const struct ts_dmi_data gdix1002_upside_down_data = {
.properties = gdix1001_upside_down_props,
};
+static const struct property_entry gdix1001_y_inverted_props[] = {
+ PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"),
+ { }
+};
+
+static const struct ts_dmi_data gdix1001_y_inverted_data = {
+ .acpi_name = "GDIX1001",
+ .properties = gdix1001_y_inverted_props,
+};
+
static const struct property_entry gp_electronic_t701_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 960),
PROPERTY_ENTRY_U32("touchscreen-size-y", 640),
@@ -1658,6 +1668,14 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
DMI_MATCH(DMI_PRODUCT_SKU, "PN20170413488"),
},
},
+ {
+ /* SUPI S10 */
+ .driver_data = (void *)&gdix1001_y_inverted_data,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "SUPI"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "S10"),
+ },
+ },
{
/* Techbite Arc 11.6 */
.driver_data = (void *)&techbite_arc_11_6_data,
--
2.52.0
^ permalink raw reply related
* [PATCH] HID: sony: add support for bluetooth Rock Band 4 PS4 guitars
From: Rosalie Wanders @ 2026-01-11 21:00 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Rosalie Wanders, linux-input, linux-kernel
This commit adds support for the PDP Jaguar and MadCatz Stratocaster.
Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
---
drivers/hid/hid-ids.h | 12 ++++++++----
drivers/hid/hid-sony.c | 24 +++++++++++++++---------
2 files changed, 23 insertions(+), 13 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index d95768c8fe06..43f86061cab2 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1101,6 +1101,11 @@
#define USB_DEVICE_ID_ORTEK_WKB2000 0x2000
#define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S 0x8003
+#define USB_VENDOR_ID_PDP 0x0e6F
+#define USB_DEVICE_ID_PDP_PS4_JAGUAR 0x0173
+#define USB_DEVICE_ID_PDP_PS4_RIFFMASTER 0x024a
+#define USB_DEVICE_ID_PDP_PS5_RIFFMASTER 0x0249
+
#define USB_VENDOR_ID_PLANTRONICS 0x047f
#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3210_SERIES 0xc055
#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES 0xc056
@@ -1149,10 +1154,6 @@
#define USB_VENDOR_ID_POWERCOM 0x0d9f
#define USB_DEVICE_ID_POWERCOM_UPS 0x0002
-#define USB_VENDOR_ID_PDP 0x0e6F
-#define USB_DEVICE_ID_PDP_PS4_RIFFMASTER 0x024a
-#define USB_DEVICE_ID_PDP_PS5_RIFFMASTER 0x0249
-
#define USB_VENDOR_ID_PRODIGE 0x05af
#define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062
@@ -1539,6 +1540,9 @@
#define USB_VENDOR_ID_RISO_KAGAKU 0x1294 /* Riso Kagaku Corp. */
#define USB_DEVICE_ID_RI_KA_WEBMAIL 0x1320 /* Webmail Notifier */
+#define USB_VENDOR_ID_MADCATZ 0x0738
+#define USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER 0x8261
+
#define USB_VENDOR_ID_MULTIPLE_1781 0x1781
#define USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD 0x0a9d
#define USB_DEVICE_ID_PHOENIXRC 0x0898
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index e2f17a99fa42..d0270ffec4f4 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -62,8 +62,9 @@
#define GH_GUITAR_CONTROLLER BIT(14)
#define GHL_GUITAR_PS3WIIU BIT(15)
#define GHL_GUITAR_PS4 BIT(16)
-#define RB4_GUITAR_PS4 BIT(17)
-#define RB4_GUITAR_PS5 BIT(18)
+#define RB4_GUITAR_PS4_USB BIT(17)
+#define RB4_GUITAR_PS4_BT BIT(18)
+#define RB4_GUITAR_PS5 BIT(19)
#define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
#define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
@@ -627,7 +628,6 @@ static int gh_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
static int rb4_guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
-
{
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
unsigned int key = usage->hid & HID_USAGE;
@@ -1041,7 +1041,10 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
} else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02) {
nsg_mrxu_parse_report(sc, rd, size);
return 1;
- } else if ((sc->quirks & RB4_GUITAR_PS4) && rd[0] == 0x01 && size == 64) {
+ } else if ((sc->quirks & RB4_GUITAR_PS4_USB) && rd[0] == 0x01 && size == 64) {
+ rb4_ps4_guitar_parse_report(sc, rd, size);
+ return 1;
+ } else if ((sc->quirks & RB4_GUITAR_PS4_BT) && rd[0] == 0x01 && size == 78) {
rb4_ps4_guitar_parse_report(sc, rd, size);
return 1;
} else if ((sc->quirks & RB4_GUITAR_PS5) && rd[0] == 0x01 && size == 64) {
@@ -1098,7 +1101,7 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
if (sc->quirks & GH_GUITAR_CONTROLLER)
return gh_guitar_mapping(hdev, hi, field, usage, bit, max);
- if (sc->quirks & RB4_GUITAR_PS4)
+ if (sc->quirks & (RB4_GUITAR_PS4_USB | RB4_GUITAR_PS4_BT))
return rb4_guitar_mapping(hdev, hi, field, usage, bit, max);
if (sc->quirks & RB4_GUITAR_PS5)
@@ -2119,8 +2122,6 @@ static int sony_input_configured(struct hid_device *hdev,
} else if (sc->quirks & MOTION_CONTROLLER) {
sony_init_output_report(sc, motion_send_output_report);
- } else if (sc->quirks & (RB4_GUITAR_PS4 | RB4_GUITAR_PS5)) {
- sc->input_dev = hidinput->input;
}
if (sc->quirks & SONY_LED_SUPPORT) {
@@ -2148,6 +2149,7 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_close;
}
+ sc->input_dev = hidinput->input;
return 0;
err_close:
hid_hw_close(hdev);
@@ -2378,9 +2380,13 @@ static const struct hid_device_id sony_devices[] = {
.driver_data = GHL_GUITAR_PS4 | GH_GUITAR_CONTROLLER },
/* Rock Band 4 PS4 guitars */
{ HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER),
- .driver_data = RB4_GUITAR_PS4 },
+ .driver_data = RB4_GUITAR_PS4_USB },
{ HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG),
- .driver_data = RB4_GUITAR_PS4 },
+ .driver_data = RB4_GUITAR_PS4_USB },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_JAGUAR),
+ .driver_data = RB4_GUITAR_PS4_BT },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER),
+ .driver_data = RB4_GUITAR_PS4_BT },
/* Rock Band 4 PS5 guitars */
{ HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS5_RIFFMASTER),
.driver_data = RB4_GUITAR_PS5 },
--
2.52.0
^ permalink raw reply related
* Re: bcm5974 trackpad broken after USB reset
From: Henrik Rydberg @ 2026-01-11 19:59 UTC (permalink / raw)
To: Liam Mitchell, Henrik Rydberg; +Cc: linux-input
In-Reply-To: <CAOQ1CL4+DP1TuLAGNsz5GdFBTHvnTg=5q=Dr2Z1OQc6RXydSYA@mail.gmail.com>
Hi Liam,
> The trackpad on my 2013 MacBook Pro (10,2) stops working after the USB
> device is reset, logging "bad trackpad package, length: 8" when
> touching the trackpad.
This sounds like the device has left wellspring mode, and is not
properly reinitialized after reset. It has been a decade since I worked
on this driver, but what I can see, there is no reset_resume handler in
bcm5974, but there is one in appletouch.
I would look into the code around the wellspring mode switch function,
and see if it gets called after the usb reset. If not, adding the
reset_resume handler might resolve things.
Hope this helps,
Henrik
^ permalink raw reply
* Re: [PATCH v2 3/6] dt-bindings: input: google,goldfish-events-keypad: Convert to DT schema
From: Kuan-Wei Chiu @ 2026-01-11 16:50 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: airlied, simona, maarten.lankhorst, mripard, tzimmermann, robh,
krzk+dt, conor+dt, dmitry.torokhov, sre, gregkh, jirislaby,
lgirdwood, broonie, jserv, eleanor15x, dri-devel, devicetree,
linux-kernel, linux-input, linux-pm, linux-serial, linux-sound
In-Reply-To: <20260109-remarkable-crane-of-exercise-6bc17f@quoll>
Hi Krzysztof,
On Fri, Jan 09, 2026 at 09:23:33AM +0100, Krzysztof Kozlowski wrote:
> On Thu, Jan 08, 2026 at 08:08:33AM +0000, Kuan-Wei Chiu wrote:
> > +
> > +examples:
> > + - |
> > + keypad@9040000 {
> > + compatible = "google,goldfish-events-keypad";
> > + reg = <0x9040000 0x1000>;
> > + interrupts = <0x5>;
>
> Same comment as before. It applies everywhere, btw.
Oops, I actually had a local fixup for this but forgot to apply it
before sending. Sorry about that.
On a side note, none of your replies regarding v2 appeared in my inbox
or spam folder. I had to retrieve this message content from the lore
web interface to reply. However, your replies to v1 arrived without any
issues.
The only difference I noticed is the sender address:
v1 came from <krzk@kernel.org>,
while v2 came from <krzysztof.kozlowski@oss.qualcomm.com>.
I suspect gmail might be silently dropping the latter due to some check?
I'm not familiar with the email protocols, but perhaps it really is time
for me to start looking for a gmail alternative... even though I see many
entries in MAINTAINERS still using gmail.com addresses.
Regards,
Kuan-Wei
^ permalink raw reply
* Proposal: Kernel Driver for Logitech G29/G920 Steering Wheels
From: Ronan Marchal @ 2026-01-11 17:57 UTC (permalink / raw)
To: linux-input
Hi all,
I’m considering developing a Linux kernel driver for the Logitech
G29/G920 steering wheels.
Currently:
- Generic HID support exists, but Force Feedback (FF) is mostly
handled in user-space via SDL/wrappers, which can cause latency or
limited functionality.
- Many Linux gamers own these devices, and a kernel-level solution
would be beneficial.
Proposal:
- Full kernel support for G29/G920:
- Axes and buttons
- Force Feedback via the kernel’s input subsystem
- Compatibility with existing Linux gaming APIs
- I plan to leverage the existing Linux kernel input API for axes,
buttons, and Force Feedback.
Before starting development, I’d like feedback:
- Would this be useful for the community?
- Are there upstream plans for this functionality?
- Any suggestions or pitfalls regarding kernel integration or maintainability?
Note: current Linux kernel includes HID support and
CONFIG_LOGIWHEELS_FF for G29, but users report incomplete Force
Feedback usage in many cases.
Thanks for your advice!
^ permalink raw reply
* bcm5974 trackpad broken after USB reset
From: Liam Mitchell @ 2026-01-11 17:28 UTC (permalink / raw)
To: Henrik Rydberg; +Cc: linux-input
Hi,
The trackpad on my 2013 MacBook Pro (10,2) stops working after the USB
device is reset, logging "bad trackpad package, length: 8" when
touching the trackpad.
The reset occurs often on my machine, caused by USB EPROTO (MMF)
errors handled by drivers/hid/usbhid/hid-core.c:hid_io_error() (HID is
handling the keyboard interface of this device). On encountering two
keyboard EPROTO errors within 1.5 seconds, the driver will reset the
USB device, also resetting bcm5974 and trackpad.
I can also reproduce this with the command:
$ sudo usbreset 05ac:0259
I can get the trackpad back into a working state by reloading the
bcm5974 driver:
$ sudo modprobe -r bcm5974; sudo modprobe bcm5974
I experimented with the driver and found that adding msleep(50) to
drivers/input/mouse/bcm5974.c:bcm5974_probe() was enough to allow the
trackpad to restart reliably after EPROTO errors and the usbreset
command.
Not sure if adding a sleep is the right way to fix this, or if I
should be looking more into the EPROTO error handling in HID or even
the source of the EPROTO errors in EHCI, power management or firmware.
Version/hardware info below, any input appreciated, thanks!
Liam
$ cat /proc/version
Linux version 6.18.4-arch1-1 (linux@archlinux) (gcc (GCC) 15.2.1
20260103, GNU ld (GNU Binutils) 2.45.1) #1 SMP PREEMPT_DYNAMIC Fri, 09
Jan 2026 19:43:48 +0000
https://gitlab.archlinux.org/archlinux/packaging/packages/linux/-/blob/ac8e7e814bb68e6291b4efd862ee9a72d7dc1165/config
$ lsusb -tvvv # only showing root containing kb/trackpad
/: Bus 004.Port 001: Dev 001, Class=root_hub, Driver=ehci-pci/2p, 480M
ID 1d6b:0002 Linux Foundation 2.0 root hub
/sys/bus/usb/devices/usb4 /dev/bus/usb/004/001
|__ Port 001: Dev 002, If 0, Class=Hub, Driver=hub/8p, 480M
ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
/sys/bus/usb/devices/4-1 /dev/bus/usb/004/002
|__ Port 008: Dev 003, If 0, Class=Hub, Driver=hub/2p, 480M
ID 0424:2512 Microchip Technology, Inc. (formerly SMSC) USB 2.0 Hub
/sys/bus/usb/devices/4-1.8 /dev/bus/usb/004/003
|__ Port 001: Dev 004, If 0, Class=Hub, Driver=hub/3p, 12M
ID 0a5c:4500 Broadcom Corp. BCM2046B1 USB 2.0 Hub
(part of BCM2046 Bluetooth)
/sys/bus/usb/devices/4-1.8.1 /dev/bus/usb/004/004
Manufacturer=Apple Inc. Product=BRCM20702 Hub
|__ Port 003: Dev 008, If 0, Class=Vendor Specific
Class, Driver=btusb, 12M
ID 05ac:828c Apple, Inc. Bluetooth Host Controller
/sys/bus/usb/devices/4-1.8.1.3 /dev/bus/usb/004/008
Manufacturer=Apple Inc. Product=Bluetooth USB Host
Controller
|__ Port 003: Dev 008, If 1, Class=Wireless, Driver=btusb, 12M
ID 05ac:828c Apple, Inc. Bluetooth Host Controller
/sys/bus/usb/devices/4-1.8.1.3 /dev/bus/usb/004/008
Manufacturer=Apple Inc. Product=Bluetooth USB Host
Controller
|__ Port 003: Dev 008, If 2, Class=Vendor Specific
Class, Driver=btusb, 12M
ID 05ac:828c Apple, Inc. Bluetooth Host Controller
/sys/bus/usb/devices/4-1.8.1.3 /dev/bus/usb/004/008
Manufacturer=Apple Inc. Product=Bluetooth USB Host
Controller
|__ Port 003: Dev 008, If 3, Class=Application
Specific Interface, Driver=[none], 12M
ID 05ac:828c Apple, Inc. Bluetooth Host Controller
/sys/bus/usb/devices/4-1.8.1.3 /dev/bus/usb/004/008
Manufacturer=Apple Inc. Product=Bluetooth USB Host
Controller
|__ Port 002: Dev 005, If 0, Class=Human Interface Device,
Driver=usbhid, 12M
ID 05ac:0259 Apple, Inc. Internal Keyboard/Trackpad
/sys/bus/usb/devices/4-1.8.2 /dev/bus/usb/004/005
Manufacturer=Apple Inc. Product=Apple Internal
Keyboard / Trackpad
|__ Port 002: Dev 005, If 1, Class=Human Interface Device,
Driver=usbhid, 12M
ID 05ac:0259 Apple, Inc. Internal Keyboard/Trackpad
/sys/bus/usb/devices/4-1.8.2 /dev/bus/usb/004/005
Manufacturer=Apple Inc. Product=Apple Internal
Keyboard / Trackpad
|__ Port 002: Dev 005, If 2, Class=Human Interface Device,
Driver=bcm5974, 12M
ID 05ac:0259 Apple, Inc. Internal Keyboard/Trackpad
/sys/bus/usb/devices/4-1.8.2 /dev/bus/usb/004/005
Manufacturer=Apple Inc. Product=Apple Internal
Keyboard / Trackpad
^ permalink raw reply
* Re: [PATCH v2 2/2] iio: inkern: Use namespaced exports
From: Jonathan Cameron @ 2026-01-11 17:02 UTC (permalink / raw)
To: Romain Gantois
Cc: MyungJoo Ham, Chanwoo Choi, Guenter Roeck, Peter Rosin,
David Lechner, Nuno Sá, Andy Shevchenko, Lars-Peter Clausen,
Michael Hennerich, Mariel Tinaco, Kevin Tsai, Linus Walleij,
Dmitry Torokhov, Eugen Hristev, Vinod Koul,
Kishon Vijay Abraham I, Sebastian Reichel, Chen-Yu Tsai,
Hans de Goede, Support Opensource, Paul Cercueil, Iskren Chernev,
Krzysztof Kozlowski, Marek Szyprowski, Matheus Castello,
Saravanan Sekar, Matthias Brugger, AngeloGioacchino Del Regno,
Casey Connolly, Pali Rohár, Orson Zhai, Baolin Wang,
Chunyan Zhang, Amit Kucheria, Thara Gopinath, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Claudiu Beznea,
Liam Girdwood, Mark Brown, Jaroslav Kysela, Takashi Iwai,
Sylwester Nawrocki, Olivier Moysan, Arnaud Pouliquen,
Maxime Coquelin, Alexandre Torgue, Thomas Petazzoni, linux-kernel,
linux-hwmon, linux-iio, linux-input, linux-phy, linux-pm,
linux-mips, linux-mediatek, linux-arm-msm, linux-sound,
linux-stm32, Sebastian Reichel, Andy Shevchenko
In-Reply-To: <20251209-iio-inkern-use-namespaced-exports-v2-2-9799a33c4b7f@bootlin.com>
On Tue, 09 Dec 2025 09:25:56 +0100
Romain Gantois <romain.gantois@bootlin.com> wrote:
> Use namespaced exports for IIO consumer API functions.
>
> This will make it easier to manage the IIO export surface. Consumer drivers
> will only be provided access to a specific set of functions, thereby
> restricting usage of internal IIO functions by other parts of the kernel.
>
> This change cannot be split into several parts without breaking
> bisectability, thus all of the affected drivers are modified at once.
>
> Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com> # for power-supply
> Acked-by: Guenter Roeck <linux@roeck-us.net>
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
> Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
Ideally looking for a couple more Acks.
If any of the maintainers of other trees who haven't already replied
have time for a quick glance that would be great. I'll spin an
immutable branch but I'm not really expecting any non trivial
conflicts unless there is a new user in flight that I've forgotten
about.
Jonathan
> ---
> drivers/extcon/extcon-adc-jack.c | 1 +
> drivers/hwmon/iio_hwmon.c | 1 +
> drivers/hwmon/ntc_thermistor.c | 1 +
> drivers/iio/adc/envelope-detector.c | 1 +
> drivers/iio/afe/iio-rescale.c | 1 +
> drivers/iio/buffer/industrialio-buffer-cb.c | 1 +
> drivers/iio/buffer/industrialio-hw-consumer.c | 1 +
> drivers/iio/dac/ad8460.c | 1 +
> drivers/iio/dac/dpot-dac.c | 1 +
> drivers/iio/inkern.c | 54 ++++++++++++-------------
> drivers/iio/light/cm3605.c | 1 +
> drivers/iio/light/gp2ap002.c | 1 +
> drivers/iio/multiplexer/iio-mux.c | 1 +
> drivers/iio/potentiostat/lmp91000.c | 1 +
> drivers/input/joystick/adc-joystick.c | 1 +
> drivers/input/keyboard/adc-keys.c | 1 +
> drivers/input/touchscreen/colibri-vf50-ts.c | 1 +
> drivers/input/touchscreen/resistive-adc-touch.c | 1 +
> drivers/phy/motorola/phy-cpcap-usb.c | 1 +
> drivers/power/supply/ab8500_btemp.c | 1 +
> drivers/power/supply/ab8500_charger.c | 1 +
> drivers/power/supply/ab8500_fg.c | 1 +
> drivers/power/supply/axp20x_ac_power.c | 1 +
> drivers/power/supply/axp20x_battery.c | 1 +
> drivers/power/supply/axp20x_usb_power.c | 1 +
> drivers/power/supply/axp288_fuel_gauge.c | 1 +
> drivers/power/supply/cpcap-battery.c | 1 +
> drivers/power/supply/cpcap-charger.c | 1 +
> drivers/power/supply/da9150-charger.c | 1 +
> drivers/power/supply/generic-adc-battery.c | 1 +
> drivers/power/supply/ingenic-battery.c | 1 +
> drivers/power/supply/intel_dc_ti_battery.c | 1 +
> drivers/power/supply/lego_ev3_battery.c | 1 +
> drivers/power/supply/lp8788-charger.c | 1 +
> drivers/power/supply/max17040_battery.c | 1 +
> drivers/power/supply/mp2629_charger.c | 1 +
> drivers/power/supply/mt6370-charger.c | 1 +
> drivers/power/supply/qcom_smbx.c | 1 +
> drivers/power/supply/rn5t618_power.c | 1 +
> drivers/power/supply/rx51_battery.c | 1 +
> drivers/power/supply/sc27xx_fuel_gauge.c | 1 +
> drivers/power/supply/twl4030_charger.c | 1 +
> drivers/power/supply/twl4030_madc_battery.c | 1 +
> drivers/power/supply/twl6030_charger.c | 1 +
> drivers/thermal/qcom/qcom-spmi-adc-tm5.c | 1 +
> drivers/thermal/qcom/qcom-spmi-temp-alarm.c | 1 +
> drivers/thermal/renesas/rzg3s_thermal.c | 1 +
> drivers/thermal/thermal-generic-adc.c | 1 +
> sound/soc/codecs/audio-iio-aux.c | 1 +
> sound/soc/samsung/aries_wm8994.c | 1 +
> sound/soc/samsung/midas_wm1811.c | 1 +
> sound/soc/stm/stm32_adfsdm.c | 1 +
> 52 files changed, 78 insertions(+), 27 deletions(-)
>
> diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c
> index 7e3c9f38297b..e735f43dcdeb 100644
> --- a/drivers/extcon/extcon-adc-jack.c
> +++ b/drivers/extcon/extcon-adc-jack.c
> @@ -210,3 +210,4 @@ module_platform_driver(adc_jack_driver);
> MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
> MODULE_DESCRIPTION("ADC Jack extcon driver");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c
> index e376d4cde5ad..4c7843fbcc50 100644
> --- a/drivers/hwmon/iio_hwmon.c
> +++ b/drivers/hwmon/iio_hwmon.c
> @@ -222,3 +222,4 @@ module_platform_driver(iio_hwmon_driver);
> MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
> MODULE_DESCRIPTION("IIO to hwmon driver");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c
> index d21f7266c411..417807fad80b 100644
> --- a/drivers/hwmon/ntc_thermistor.c
> +++ b/drivers/hwmon/ntc_thermistor.c
> @@ -706,3 +706,4 @@ MODULE_DESCRIPTION("NTC Thermistor Driver");
> MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
> MODULE_LICENSE("GPL");
> MODULE_ALIAS("platform:ntc-thermistor");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/adc/envelope-detector.c b/drivers/iio/adc/envelope-detector.c
> index 5b16fe737659..fea20e7e6cd9 100644
> --- a/drivers/iio/adc/envelope-detector.c
> +++ b/drivers/iio/adc/envelope-detector.c
> @@ -406,3 +406,4 @@ module_platform_driver(envelope_detector_driver);
> MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator");
> MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/afe/iio-rescale.c b/drivers/iio/afe/iio-rescale.c
> index ecaf59278c6f..d7f55109af3e 100644
> --- a/drivers/iio/afe/iio-rescale.c
> +++ b/drivers/iio/afe/iio-rescale.c
> @@ -609,3 +609,4 @@ module_platform_driver(rescale_driver);
> MODULE_DESCRIPTION("IIO rescale driver");
> MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c
> index 3e27385069ed..608ea9afc15a 100644
> --- a/drivers/iio/buffer/industrialio-buffer-cb.c
> +++ b/drivers/iio/buffer/industrialio-buffer-cb.c
> @@ -153,3 +153,4 @@ EXPORT_SYMBOL_GPL(iio_channel_cb_get_iio_dev);
> MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
> MODULE_DESCRIPTION("Industrial I/O callback buffer");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/buffer/industrialio-hw-consumer.c b/drivers/iio/buffer/industrialio-hw-consumer.c
> index 526b2a8d725d..d7ff086ed783 100644
> --- a/drivers/iio/buffer/industrialio-hw-consumer.c
> +++ b/drivers/iio/buffer/industrialio-hw-consumer.c
> @@ -211,3 +211,4 @@ EXPORT_SYMBOL_GPL(iio_hw_consumer_disable);
> MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
> MODULE_DESCRIPTION("Hardware consumer buffer the IIO framework");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/dac/ad8460.c b/drivers/iio/dac/ad8460.c
> index 6e45686902dd..ad654819ca22 100644
> --- a/drivers/iio/dac/ad8460.c
> +++ b/drivers/iio/dac/ad8460.c
> @@ -955,3 +955,4 @@ MODULE_AUTHOR("Mariel Tinaco <mariel.tinaco@analog.com");
> MODULE_DESCRIPTION("AD8460 DAC driver");
> MODULE_LICENSE("GPL");
> MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/dac/dpot-dac.c b/drivers/iio/dac/dpot-dac.c
> index d1b8441051ae..49dbdb7df955 100644
> --- a/drivers/iio/dac/dpot-dac.c
> +++ b/drivers/iio/dac/dpot-dac.c
> @@ -254,3 +254,4 @@ module_platform_driver(dpot_dac_driver);
> MODULE_DESCRIPTION("DAC emulation driver using a digital potentiometer");
> MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
> index 1e5eb5a41271..c75c3a8d233f 100644
> --- a/drivers/iio/inkern.c
> +++ b/drivers/iio/inkern.c
> @@ -281,7 +281,7 @@ struct iio_channel *fwnode_iio_channel_get_by_name(struct fwnode_handle *fwnode,
>
> return ERR_PTR(-ENODEV);
> }
> -EXPORT_SYMBOL_GPL(fwnode_iio_channel_get_by_name);
> +EXPORT_SYMBOL_NS_GPL(fwnode_iio_channel_get_by_name, "IIO_CONSUMER");
>
> static struct iio_channel *fwnode_iio_channel_get_all(struct device *dev)
> {
> @@ -386,7 +386,7 @@ struct iio_channel *iio_channel_get(struct device *dev,
>
> return iio_channel_get_sys(name, channel_name);
> }
> -EXPORT_SYMBOL_GPL(iio_channel_get);
> +EXPORT_SYMBOL_NS_GPL(iio_channel_get, "IIO_CONSUMER");
>
> void iio_channel_release(struct iio_channel *channel)
> {
> @@ -395,7 +395,7 @@ void iio_channel_release(struct iio_channel *channel)
> iio_device_put(channel->indio_dev);
> kfree(channel);
> }
> -EXPORT_SYMBOL_GPL(iio_channel_release);
> +EXPORT_SYMBOL_NS_GPL(iio_channel_release, "IIO_CONSUMER");
>
> static void devm_iio_channel_free(void *iio_channel)
> {
> @@ -418,7 +418,7 @@ struct iio_channel *devm_iio_channel_get(struct device *dev,
>
> return channel;
> }
> -EXPORT_SYMBOL_GPL(devm_iio_channel_get);
> +EXPORT_SYMBOL_NS_GPL(devm_iio_channel_get, "IIO_CONSUMER");
>
> struct iio_channel *devm_fwnode_iio_channel_get_by_name(struct device *dev,
> struct fwnode_handle *fwnode,
> @@ -437,7 +437,7 @@ struct iio_channel *devm_fwnode_iio_channel_get_by_name(struct device *dev,
>
> return channel;
> }
> -EXPORT_SYMBOL_GPL(devm_fwnode_iio_channel_get_by_name);
> +EXPORT_SYMBOL_NS_GPL(devm_fwnode_iio_channel_get_by_name, "IIO_CONSUMER");
>
> struct iio_channel *iio_channel_get_all(struct device *dev)
> {
> @@ -506,7 +506,7 @@ struct iio_channel *iio_channel_get_all(struct device *dev)
> iio_device_put(chans[i].indio_dev);
> return ERR_PTR(ret);
> }
> -EXPORT_SYMBOL_GPL(iio_channel_get_all);
> +EXPORT_SYMBOL_NS_GPL(iio_channel_get_all, "IIO_CONSUMER");
>
> void iio_channel_release_all(struct iio_channel *channels)
> {
> @@ -518,7 +518,7 @@ void iio_channel_release_all(struct iio_channel *channels)
> }
> kfree(channels);
> }
> -EXPORT_SYMBOL_GPL(iio_channel_release_all);
> +EXPORT_SYMBOL_NS_GPL(iio_channel_release_all, "IIO_CONSUMER");
>
> static void devm_iio_channel_free_all(void *iio_channels)
> {
> @@ -541,7 +541,7 @@ struct iio_channel *devm_iio_channel_get_all(struct device *dev)
>
> return channels;
> }
> -EXPORT_SYMBOL_GPL(devm_iio_channel_get_all);
> +EXPORT_SYMBOL_NS_GPL(devm_iio_channel_get_all, "IIO_CONSUMER");
>
> static int iio_channel_read(struct iio_channel *chan, int *val, int *val2,
> enum iio_chan_info_enum info)
> @@ -585,7 +585,7 @@ int iio_read_channel_raw(struct iio_channel *chan, int *val)
>
> return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_RAW);
> }
> -EXPORT_SYMBOL_GPL(iio_read_channel_raw);
> +EXPORT_SYMBOL_NS_GPL(iio_read_channel_raw, "IIO_CONSUMER");
>
> int iio_read_channel_average_raw(struct iio_channel *chan, int *val)
> {
> @@ -597,7 +597,7 @@ int iio_read_channel_average_raw(struct iio_channel *chan, int *val)
>
> return iio_channel_read(chan, val, NULL, IIO_CHAN_INFO_AVERAGE_RAW);
> }
> -EXPORT_SYMBOL_GPL(iio_read_channel_average_raw);
> +EXPORT_SYMBOL_NS_GPL(iio_read_channel_average_raw, "IIO_CONSUMER");
>
> int iio_multiply_value(int *result, s64 multiplier,
> unsigned int type, int val, int val2)
> @@ -701,7 +701,7 @@ int iio_convert_raw_to_processed(struct iio_channel *chan, int raw,
> return iio_convert_raw_to_processed_unlocked(chan, raw, processed,
> scale);
> }
> -EXPORT_SYMBOL_GPL(iio_convert_raw_to_processed);
> +EXPORT_SYMBOL_NS_GPL(iio_convert_raw_to_processed, "IIO_CONSUMER");
>
> int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2,
> enum iio_chan_info_enum attribute)
> @@ -714,13 +714,13 @@ int iio_read_channel_attribute(struct iio_channel *chan, int *val, int *val2,
>
> return iio_channel_read(chan, val, val2, attribute);
> }
> -EXPORT_SYMBOL_GPL(iio_read_channel_attribute);
> +EXPORT_SYMBOL_NS_GPL(iio_read_channel_attribute, "IIO_CONSUMER");
>
> int iio_read_channel_offset(struct iio_channel *chan, int *val, int *val2)
> {
> return iio_read_channel_attribute(chan, val, val2, IIO_CHAN_INFO_OFFSET);
> }
> -EXPORT_SYMBOL_GPL(iio_read_channel_offset);
> +EXPORT_SYMBOL_NS_GPL(iio_read_channel_offset, "IIO_CONSUMER");
>
> int iio_read_channel_processed_scale(struct iio_channel *chan, int *val,
> unsigned int scale)
> @@ -748,20 +748,20 @@ int iio_read_channel_processed_scale(struct iio_channel *chan, int *val,
> scale);
> }
> }
> -EXPORT_SYMBOL_GPL(iio_read_channel_processed_scale);
> +EXPORT_SYMBOL_NS_GPL(iio_read_channel_processed_scale, "IIO_CONSUMER");
>
> int iio_read_channel_processed(struct iio_channel *chan, int *val)
> {
> /* This is just a special case with scale factor 1 */
> return iio_read_channel_processed_scale(chan, val, 1);
> }
> -EXPORT_SYMBOL_GPL(iio_read_channel_processed);
> +EXPORT_SYMBOL_NS_GPL(iio_read_channel_processed, "IIO_CONSUMER");
>
> int iio_read_channel_scale(struct iio_channel *chan, int *val, int *val2)
> {
> return iio_read_channel_attribute(chan, val, val2, IIO_CHAN_INFO_SCALE);
> }
> -EXPORT_SYMBOL_GPL(iio_read_channel_scale);
> +EXPORT_SYMBOL_NS_GPL(iio_read_channel_scale, "IIO_CONSUMER");
>
> static int iio_channel_read_avail(struct iio_channel *chan,
> const int **vals, int *type, int *length,
> @@ -790,7 +790,7 @@ int iio_read_avail_channel_attribute(struct iio_channel *chan,
>
> return iio_channel_read_avail(chan, vals, type, length, attribute);
> }
> -EXPORT_SYMBOL_GPL(iio_read_avail_channel_attribute);
> +EXPORT_SYMBOL_NS_GPL(iio_read_avail_channel_attribute, "IIO_CONSUMER");
>
> int iio_read_avail_channel_raw(struct iio_channel *chan,
> const int **vals, int *length)
> @@ -807,7 +807,7 @@ int iio_read_avail_channel_raw(struct iio_channel *chan,
>
> return ret;
> }
> -EXPORT_SYMBOL_GPL(iio_read_avail_channel_raw);
> +EXPORT_SYMBOL_NS_GPL(iio_read_avail_channel_raw, "IIO_CONSUMER");
>
> static int iio_channel_read_max(struct iio_channel *chan,
> int *val, int *val2, int *type,
> @@ -863,7 +863,7 @@ int iio_read_max_channel_raw(struct iio_channel *chan, int *val)
>
> return iio_channel_read_max(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
> }
> -EXPORT_SYMBOL_GPL(iio_read_max_channel_raw);
> +EXPORT_SYMBOL_NS_GPL(iio_read_max_channel_raw, "IIO_CONSUMER");
>
> static int iio_channel_read_min(struct iio_channel *chan,
> int *val, int *val2, int *type,
> @@ -919,7 +919,7 @@ int iio_read_min_channel_raw(struct iio_channel *chan, int *val)
>
> return iio_channel_read_min(chan, val, NULL, &type, IIO_CHAN_INFO_RAW);
> }
> -EXPORT_SYMBOL_GPL(iio_read_min_channel_raw);
> +EXPORT_SYMBOL_NS_GPL(iio_read_min_channel_raw, "IIO_CONSUMER");
>
> int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type)
> {
> @@ -933,7 +933,7 @@ int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type)
>
> return 0;
> }
> -EXPORT_SYMBOL_GPL(iio_get_channel_type);
> +EXPORT_SYMBOL_NS_GPL(iio_get_channel_type, "IIO_CONSUMER");
>
> static int iio_channel_write(struct iio_channel *chan, int val, int val2,
> enum iio_chan_info_enum info)
> @@ -957,13 +957,13 @@ int iio_write_channel_attribute(struct iio_channel *chan, int val, int val2,
>
> return iio_channel_write(chan, val, val2, attribute);
> }
> -EXPORT_SYMBOL_GPL(iio_write_channel_attribute);
> +EXPORT_SYMBOL_NS_GPL(iio_write_channel_attribute, "IIO_CONSUMER");
>
> int iio_write_channel_raw(struct iio_channel *chan, int val)
> {
> return iio_write_channel_attribute(chan, val, 0, IIO_CHAN_INFO_RAW);
> }
> -EXPORT_SYMBOL_GPL(iio_write_channel_raw);
> +EXPORT_SYMBOL_NS_GPL(iio_write_channel_raw, "IIO_CONSUMER");
>
> unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan)
> {
> @@ -978,7 +978,7 @@ unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan)
>
> return i;
> }
> -EXPORT_SYMBOL_GPL(iio_get_channel_ext_info_count);
> +EXPORT_SYMBOL_NS_GPL(iio_get_channel_ext_info_count, "IIO_CONSUMER");
>
> static const struct iio_chan_spec_ext_info *
> iio_lookup_ext_info(const struct iio_channel *chan, const char *attr)
> @@ -1013,7 +1013,7 @@ ssize_t iio_read_channel_ext_info(struct iio_channel *chan,
> return ext_info->read(chan->indio_dev, ext_info->private,
> chan->channel, buf);
> }
> -EXPORT_SYMBOL_GPL(iio_read_channel_ext_info);
> +EXPORT_SYMBOL_NS_GPL(iio_read_channel_ext_info, "IIO_CONSUMER");
>
> ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
> const char *buf, size_t len)
> @@ -1027,7 +1027,7 @@ ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
> return ext_info->write(chan->indio_dev, ext_info->private,
> chan->channel, buf, len);
> }
> -EXPORT_SYMBOL_GPL(iio_write_channel_ext_info);
> +EXPORT_SYMBOL_NS_GPL(iio_write_channel_ext_info, "IIO_CONSUMER");
>
> ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf)
> {
> @@ -1038,4 +1038,4 @@ ssize_t iio_read_channel_label(struct iio_channel *chan, char *buf)
>
> return do_iio_read_channel_label(chan->indio_dev, chan->channel, buf);
> }
> -EXPORT_SYMBOL_GPL(iio_read_channel_label);
> +EXPORT_SYMBOL_NS_GPL(iio_read_channel_label, "IIO_CONSUMER");
> diff --git a/drivers/iio/light/cm3605.c b/drivers/iio/light/cm3605.c
> index 0c17378e27d1..1bd11292d005 100644
> --- a/drivers/iio/light/cm3605.c
> +++ b/drivers/iio/light/cm3605.c
> @@ -325,3 +325,4 @@ module_platform_driver(cm3605_driver);
> MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
> MODULE_DESCRIPTION("CM3605 ambient light and proximity sensor driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/light/gp2ap002.c b/drivers/iio/light/gp2ap002.c
> index a0d8a58f2704..04b1f6eade0e 100644
> --- a/drivers/iio/light/gp2ap002.c
> +++ b/drivers/iio/light/gp2ap002.c
> @@ -717,3 +717,4 @@ module_i2c_driver(gp2ap002_driver);
> MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
> MODULE_DESCRIPTION("GP2AP002 ambient light and proximity sensor driver");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/multiplexer/iio-mux.c b/drivers/iio/multiplexer/iio-mux.c
> index b742ca9a99d1..e193913f5af7 100644
> --- a/drivers/iio/multiplexer/iio-mux.c
> +++ b/drivers/iio/multiplexer/iio-mux.c
> @@ -464,3 +464,4 @@ module_platform_driver(mux_driver);
> MODULE_DESCRIPTION("IIO multiplexer driver");
> MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/iio/potentiostat/lmp91000.c b/drivers/iio/potentiostat/lmp91000.c
> index eccc2a34358f..7d993f2acda4 100644
> --- a/drivers/iio/potentiostat/lmp91000.c
> +++ b/drivers/iio/potentiostat/lmp91000.c
> @@ -423,3 +423,4 @@ module_i2c_driver(lmp91000_driver);
> MODULE_AUTHOR("Matt Ranostay <matt.ranostay@konsulko.com>");
> MODULE_DESCRIPTION("LMP91000 digital potentiostat");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/input/joystick/adc-joystick.c b/drivers/input/joystick/adc-joystick.c
> index ff44f9978b71..4fa42f88bcfa 100644
> --- a/drivers/input/joystick/adc-joystick.c
> +++ b/drivers/input/joystick/adc-joystick.c
> @@ -329,3 +329,4 @@ module_platform_driver(adc_joystick_driver);
> MODULE_DESCRIPTION("Input driver for joysticks connected over ADC");
> MODULE_AUTHOR("Artur Rojek <contact@artur-rojek.eu>");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/input/keyboard/adc-keys.c b/drivers/input/keyboard/adc-keys.c
> index f1753207429d..d687459a0c80 100644
> --- a/drivers/input/keyboard/adc-keys.c
> +++ b/drivers/input/keyboard/adc-keys.c
> @@ -202,3 +202,4 @@ module_platform_driver(adc_keys_driver);
> MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@free-electrons.com>");
> MODULE_DESCRIPTION("Input driver for resistor ladder connected on ADC");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/input/touchscreen/colibri-vf50-ts.c b/drivers/input/touchscreen/colibri-vf50-ts.c
> index 98d5b2ba63fb..89c4d7b2b89e 100644
> --- a/drivers/input/touchscreen/colibri-vf50-ts.c
> +++ b/drivers/input/touchscreen/colibri-vf50-ts.c
> @@ -372,3 +372,4 @@ module_platform_driver(vf50_touch_driver);
> MODULE_AUTHOR("Sanchayan Maity");
> MODULE_DESCRIPTION("Colibri VF50 Touchscreen driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/input/touchscreen/resistive-adc-touch.c b/drivers/input/touchscreen/resistive-adc-touch.c
> index 7e761ec73273..2fefd652864c 100644
> --- a/drivers/input/touchscreen/resistive-adc-touch.c
> +++ b/drivers/input/touchscreen/resistive-adc-touch.c
> @@ -301,3 +301,4 @@ module_platform_driver(grts_driver);
> MODULE_AUTHOR("Eugen Hristev <eugen.hristev@microchip.com>");
> MODULE_DESCRIPTION("Generic ADC Resistive Touch Driver");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/phy/motorola/phy-cpcap-usb.c b/drivers/phy/motorola/phy-cpcap-usb.c
> index 7cb020dd3423..9591672b0511 100644
> --- a/drivers/phy/motorola/phy-cpcap-usb.c
> +++ b/drivers/phy/motorola/phy-cpcap-usb.c
> @@ -717,3 +717,4 @@ MODULE_ALIAS("platform:cpcap_usb");
> MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
> MODULE_DESCRIPTION("CPCAP usb phy driver");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
> index e5202a7b6209..36b0c52a4b8b 100644
> --- a/drivers/power/supply/ab8500_btemp.c
> +++ b/drivers/power/supply/ab8500_btemp.c
> @@ -829,3 +829,4 @@ MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
> MODULE_ALIAS("platform:ab8500-btemp");
> MODULE_DESCRIPTION("AB8500 battery temperature driver");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
> index 5f4537766e5b..6e49d1b28254 100644
> --- a/drivers/power/supply/ab8500_charger.c
> +++ b/drivers/power/supply/ab8500_charger.c
> @@ -3751,3 +3751,4 @@ MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
> MODULE_ALIAS("platform:ab8500-charger");
> MODULE_DESCRIPTION("AB8500 charger management driver");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
> index 9dd99722667a..5fa559f796aa 100644
> --- a/drivers/power/supply/ab8500_fg.c
> +++ b/drivers/power/supply/ab8500_fg.c
> @@ -3252,3 +3252,4 @@ MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
> MODULE_ALIAS("platform:ab8500-fg");
> MODULE_DESCRIPTION("AB8500 Fuel Gauge driver");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c
> index 5f6ea416fa30..e9049d6229df 100644
> --- a/drivers/power/supply/axp20x_ac_power.c
> +++ b/drivers/power/supply/axp20x_ac_power.c
> @@ -421,3 +421,4 @@ module_platform_driver(axp20x_ac_power_driver);
> MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> MODULE_DESCRIPTION("AXP20X and AXP22X PMICs' AC power supply driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c
> index 50ca8e110085..ee8701a6e907 100644
> --- a/drivers/power/supply/axp20x_battery.c
> +++ b/drivers/power/supply/axp20x_battery.c
> @@ -1155,3 +1155,4 @@ module_platform_driver(axp20x_batt_driver);
> MODULE_DESCRIPTION("Battery power supply driver for AXP20X and AXP22X PMICs");
> MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c
> index e75d1e377ac1..599adcf84968 100644
> --- a/drivers/power/supply/axp20x_usb_power.c
> +++ b/drivers/power/supply/axp20x_usb_power.c
> @@ -1080,3 +1080,4 @@ module_platform_driver(axp20x_usb_power_driver);
> MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
> MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c
> index a3d71fc72064..c6897dd808fc 100644
> --- a/drivers/power/supply/axp288_fuel_gauge.c
> +++ b/drivers/power/supply/axp288_fuel_gauge.c
> @@ -817,3 +817,4 @@ MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
> MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>");
> MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c
> index 8106d1edcbc2..542c3c70e3cb 100644
> --- a/drivers/power/supply/cpcap-battery.c
> +++ b/drivers/power/supply/cpcap-battery.c
> @@ -1176,3 +1176,4 @@ module_platform_driver(cpcap_battery_driver);
> MODULE_LICENSE("GPL v2");
> MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
> MODULE_DESCRIPTION("CPCAP PMIC Battery Driver");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c
> index d0c3008db534..89bc0fc3c9f8 100644
> --- a/drivers/power/supply/cpcap-charger.c
> +++ b/drivers/power/supply/cpcap-charger.c
> @@ -977,3 +977,4 @@ MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
> MODULE_DESCRIPTION("CPCAP Battery Charger Interface driver");
> MODULE_LICENSE("GPL v2");
> MODULE_ALIAS("platform:cpcap-charger");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/da9150-charger.c b/drivers/power/supply/da9150-charger.c
> index 27f36ef5b88d..58449df6068c 100644
> --- a/drivers/power/supply/da9150-charger.c
> +++ b/drivers/power/supply/da9150-charger.c
> @@ -644,3 +644,4 @@ module_platform_driver(da9150_charger_driver);
> MODULE_DESCRIPTION("Charger Driver for DA9150");
> MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c
> index f5f2566b3a32..d18c8ee40405 100644
> --- a/drivers/power/supply/generic-adc-battery.c
> +++ b/drivers/power/supply/generic-adc-battery.c
> @@ -298,3 +298,4 @@ module_platform_driver(gab_driver);
> MODULE_AUTHOR("anish kumar <yesanishhere@gmail.com>");
> MODULE_DESCRIPTION("generic battery driver using IIO");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/ingenic-battery.c b/drivers/power/supply/ingenic-battery.c
> index b111c7ce2be3..5be269f17bff 100644
> --- a/drivers/power/supply/ingenic-battery.c
> +++ b/drivers/power/supply/ingenic-battery.c
> @@ -190,3 +190,4 @@ module_platform_driver(ingenic_battery_driver);
> MODULE_DESCRIPTION("Battery driver for Ingenic JZ47xx SoCs");
> MODULE_AUTHOR("Artur Rojek <contact@artur-rojek.eu>");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/intel_dc_ti_battery.c b/drivers/power/supply/intel_dc_ti_battery.c
> index 56b0c92e9d28..1a16ded563bc 100644
> --- a/drivers/power/supply/intel_dc_ti_battery.c
> +++ b/drivers/power/supply/intel_dc_ti_battery.c
> @@ -387,3 +387,4 @@ MODULE_ALIAS("platform:" DEV_NAME);
> MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
> MODULE_DESCRIPTION("Intel Dollar Cove (TI) battery driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/lego_ev3_battery.c b/drivers/power/supply/lego_ev3_battery.c
> index 28454de05761..414816662b06 100644
> --- a/drivers/power/supply/lego_ev3_battery.c
> +++ b/drivers/power/supply/lego_ev3_battery.c
> @@ -231,3 +231,4 @@ module_platform_driver(lego_ev3_battery_driver);
> MODULE_LICENSE("GPL");
> MODULE_AUTHOR("David Lechner <david@lechnology.com>");
> MODULE_DESCRIPTION("LEGO MINDSTORMS EV3 Battery Driver");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/lp8788-charger.c b/drivers/power/supply/lp8788-charger.c
> index f0a680c155c4..8c6ec98362d0 100644
> --- a/drivers/power/supply/lp8788-charger.c
> +++ b/drivers/power/supply/lp8788-charger.c
> @@ -727,3 +727,4 @@ MODULE_DESCRIPTION("TI LP8788 Charger Driver");
> MODULE_AUTHOR("Milo Kim");
> MODULE_LICENSE("GPL");
> MODULE_ALIAS("platform:lp8788-charger");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c
> index c1640bc6accd..1fe658bfecc1 100644
> --- a/drivers/power/supply/max17040_battery.c
> +++ b/drivers/power/supply/max17040_battery.c
> @@ -635,3 +635,4 @@ module_i2c_driver(max17040_i2c_driver);
> MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>");
> MODULE_DESCRIPTION("MAX17040 Fuel Gauge");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/mp2629_charger.c b/drivers/power/supply/mp2629_charger.c
> index d281c1059629..ed49f9a04c8c 100644
> --- a/drivers/power/supply/mp2629_charger.c
> +++ b/drivers/power/supply/mp2629_charger.c
> @@ -660,3 +660,4 @@ module_platform_driver(mp2629_charger_driver);
> MODULE_AUTHOR("Saravanan Sekar <sravanhome@gmail.com>");
> MODULE_DESCRIPTION("MP2629 Charger driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/mt6370-charger.c b/drivers/power/supply/mt6370-charger.c
> index e6db961d5818..2d02fdf37d70 100644
> --- a/drivers/power/supply/mt6370-charger.c
> +++ b/drivers/power/supply/mt6370-charger.c
> @@ -941,3 +941,4 @@ module_platform_driver(mt6370_chg_driver);
> MODULE_AUTHOR("ChiaEn Wu <chiaen_wu@richtek.com>");
> MODULE_DESCRIPTION("MediaTek MT6370 Charger Driver");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c
> index b1cb925581ec..63b88754155c 100644
> --- a/drivers/power/supply/qcom_smbx.c
> +++ b/drivers/power/supply/qcom_smbx.c
> @@ -1050,3 +1050,4 @@ module_platform_driver(qcom_spmi_smb);
> MODULE_AUTHOR("Casey Connolly <casey.connolly@linaro.org>");
> MODULE_DESCRIPTION("Qualcomm SMB2 Charger Driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c
> index 40dec55a9f73..a3f30e390c11 100644
> --- a/drivers/power/supply/rn5t618_power.c
> +++ b/drivers/power/supply/rn5t618_power.c
> @@ -821,3 +821,4 @@ module_platform_driver(rn5t618_power_driver);
> MODULE_ALIAS("platform:rn5t618-power");
> MODULE_DESCRIPTION("Power supply driver for RICOH RN5T618");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/rx51_battery.c b/drivers/power/supply/rx51_battery.c
> index b0220ec2d926..57266921dc8e 100644
> --- a/drivers/power/supply/rx51_battery.c
> +++ b/drivers/power/supply/rx51_battery.c
> @@ -246,3 +246,4 @@ MODULE_ALIAS("platform:rx51-battery");
> MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
> MODULE_DESCRIPTION("Nokia RX-51 battery driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c
> index a7ed9de8a289..1719ec4173e6 100644
> --- a/drivers/power/supply/sc27xx_fuel_gauge.c
> +++ b/drivers/power/supply/sc27xx_fuel_gauge.c
> @@ -1350,3 +1350,4 @@ module_platform_driver(sc27xx_fgu_driver);
>
> MODULE_DESCRIPTION("Spreadtrum SC27XX PMICs Fual Gauge Unit Driver");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c
> index 04216b2bfb6c..151f7b24e9b9 100644
> --- a/drivers/power/supply/twl4030_charger.c
> +++ b/drivers/power/supply/twl4030_charger.c
> @@ -1144,3 +1144,4 @@ MODULE_AUTHOR("Gražvydas Ignotas");
> MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver");
> MODULE_LICENSE("GPL");
> MODULE_ALIAS("platform:twl4030_bci");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/twl4030_madc_battery.c b/drivers/power/supply/twl4030_madc_battery.c
> index 3935162e350b..9b3785d1643c 100644
> --- a/drivers/power/supply/twl4030_madc_battery.c
> +++ b/drivers/power/supply/twl4030_madc_battery.c
> @@ -237,3 +237,4 @@ MODULE_LICENSE("GPL");
> MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
> MODULE_DESCRIPTION("twl4030_madc battery driver");
> MODULE_ALIAS("platform:twl4030_madc_battery");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/power/supply/twl6030_charger.c b/drivers/power/supply/twl6030_charger.c
> index b4ec26ff257c..82911a811f4e 100644
> --- a/drivers/power/supply/twl6030_charger.c
> +++ b/drivers/power/supply/twl6030_charger.c
> @@ -579,3 +579,4 @@ module_platform_driver(twl6030_charger_driver);
>
> MODULE_DESCRIPTION("TWL6030 Battery Charger Interface driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
> index d7f2e6ca92c2..bb6222c8cc5f 100644
> --- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
> +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
> @@ -1069,3 +1069,4 @@ module_platform_driver(adc_tm5_driver);
>
> MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
> index f39ca0ddd17b..fb003ca96454 100644
> --- a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
> +++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
> @@ -904,3 +904,4 @@ module_platform_driver(qpnp_tm_driver);
> MODULE_ALIAS("platform:spmi-temp-alarm");
> MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c
> index e25e36c99a88..7ced8f76a0ec 100644
> --- a/drivers/thermal/renesas/rzg3s_thermal.c
> +++ b/drivers/thermal/renesas/rzg3s_thermal.c
> @@ -270,3 +270,4 @@ module_platform_driver(rzg3s_thermal_driver);
> MODULE_DESCRIPTION("Renesas RZ/G3S Thermal Sensor Unit Driver");
> MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/drivers/thermal/thermal-generic-adc.c b/drivers/thermal/thermal-generic-adc.c
> index 7c844589b153..cfdb8e674dd2 100644
> --- a/drivers/thermal/thermal-generic-adc.c
> +++ b/drivers/thermal/thermal-generic-adc.c
> @@ -228,3 +228,4 @@ module_platform_driver(gadc_thermal_driver);
> MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
> MODULE_DESCRIPTION("Generic ADC thermal driver using IIO framework with DT");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/sound/soc/codecs/audio-iio-aux.c b/sound/soc/codecs/audio-iio-aux.c
> index 588e48044c13..864a5a676495 100644
> --- a/sound/soc/codecs/audio-iio-aux.c
> +++ b/sound/soc/codecs/audio-iio-aux.c
> @@ -312,3 +312,4 @@ module_platform_driver(audio_iio_aux_driver);
> MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
> MODULE_DESCRIPTION("IIO ALSA SoC aux driver");
> MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/sound/soc/samsung/aries_wm8994.c b/sound/soc/samsung/aries_wm8994.c
> index 3723329b266d..b6f0f3c0d393 100644
> --- a/sound/soc/samsung/aries_wm8994.c
> +++ b/sound/soc/samsung/aries_wm8994.c
> @@ -700,3 +700,4 @@ module_platform_driver(aries_audio_driver);
> MODULE_DESCRIPTION("ALSA SoC ARIES WM8994");
> MODULE_LICENSE("GPL");
> MODULE_ALIAS("platform:aries-audio-wm8994");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/sound/soc/samsung/midas_wm1811.c b/sound/soc/samsung/midas_wm1811.c
> index 239e958b88d3..12c4962f901d 100644
> --- a/sound/soc/samsung/midas_wm1811.c
> +++ b/sound/soc/samsung/midas_wm1811.c
> @@ -773,3 +773,4 @@ module_platform_driver(midas_driver);
> MODULE_AUTHOR("Simon Shields <simon@lineageos.org>");
> MODULE_DESCRIPTION("ASoC support for Midas");
> MODULE_LICENSE("GPL v2");
> +MODULE_IMPORT_NS("IIO_CONSUMER");
> diff --git a/sound/soc/stm/stm32_adfsdm.c b/sound/soc/stm/stm32_adfsdm.c
> index c914d1c46850..dabcd2759187 100644
> --- a/sound/soc/stm/stm32_adfsdm.c
> +++ b/sound/soc/stm/stm32_adfsdm.c
> @@ -407,3 +407,4 @@ MODULE_DESCRIPTION("stm32 DFSDM DAI driver");
> MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
> MODULE_LICENSE("GPL v2");
> MODULE_ALIAS("platform:" STM32_ADFSDM_DRV_NAME);
> +MODULE_IMPORT_NS("IIO_CONSUMER");
>
^ permalink raw reply
* Re: [PATCH v2 1/2] iio: dac: ds4424: drop unused include IIO consumer header
From: Jonathan Cameron @ 2026-01-11 16:57 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Romain Gantois, MyungJoo Ham, Chanwoo Choi, Guenter Roeck,
Peter Rosin, David Lechner, Nuno Sá, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Mariel Tinaco, Kevin Tsai,
Linus Walleij, Dmitry Torokhov, Eugen Hristev, Vinod Koul,
Kishon Vijay Abraham I, Sebastian Reichel, Chen-Yu Tsai,
Hans de Goede, Support Opensource, Paul Cercueil, Iskren Chernev,
Krzysztof Kozlowski, Marek Szyprowski, Matheus Castello,
Saravanan Sekar, Matthias Brugger, AngeloGioacchino Del Regno,
Casey Connolly, Pali Rohár, Orson Zhai, Baolin Wang,
Chunyan Zhang, Amit Kucheria, Thara Gopinath, Rafael J. Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba, Claudiu Beznea,
Liam Girdwood, Mark Brown, Jaroslav Kysela, Takashi Iwai,
Sylwester Nawrocki, Olivier Moysan, Arnaud Pouliquen,
Maxime Coquelin, Alexandre Torgue, Thomas Petazzoni, linux-kernel,
linux-hwmon, linux-iio, linux-input, linux-phy, linux-pm,
linux-mips, linux-mediatek, linux-arm-msm, linux-sound,
linux-stm32
In-Reply-To: <CAHp75VcX_z6q879gmWcb76SeFHtqMvpZ=y9PwNn0=eVFb06wAw@mail.gmail.com>
On Tue, 9 Dec 2025 16:45:29 +0200
Andy Shevchenko <andy.shevchenko@gmail.com> wrote:
> On Tue, Dec 9, 2025 at 10:26 AM Romain Gantois
> <romain.gantois@bootlin.com> wrote:
> >
> > To prepare for the introduction of namespaced exports for the IIO consumer
> > API, remove this include directive which isn't actually used by the driver.
>
> Reviewed-by: Andy Shevchenko <andy@kernel.org>
>
Picked this one up on the togreg branch of iio.git. I'll sort out the other one
separately given I should do an immutable branch etc and that takes a little
more effort and time than I'm putting in today!
Jonathan
^ permalink raw reply
* [PATCH v5 4/4] Input: Add TouchNetix aXiom I2C Touchscreen support
From: Marco Felsch @ 2026-01-11 15:05 UTC (permalink / raw)
To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
Rafael J. Wysocki, Andrew Morton, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
Cc: linux-kernel, devicetree, linux-input, kernel, Marco Felsch
In-Reply-To: <20260111-v6-10-topic-touchscreen-axiom-v5-0-f94e0ae266cb@pengutronix.de>
This adds the initial support for the TouchNetix AX54A touchcontroller
which is part of TouchNetix's aXiom touchscreen controller family.
The TouchNetix aXiom family provides two physical interfaces: SPI and
I2C. This patch covers only the I2C interface.
Apart the input event handling the driver supports firmware updates too.
One firmware interface handles the touchcontroller firmware (AXFW and
ALC) update the other handles the touchcontroller configuration
(TH2CFGBIN) update.
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
.../testing/sysfs-driver-input-touchnetix-axiom | 81 +
drivers/input/touchscreen/Kconfig | 17 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/touchnetix_axiom.c | 2974 ++++++++++++++++++++
4 files changed, 3073 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom b/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom
new file mode 100644
index 0000000000000000000000000000000000000000..31c1c6510c55da80659ddf7bea2d0ce681fde323
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-input-touchnetix-axiom
@@ -0,0 +1,81 @@
+What: /sys/bus/i2c/devices/xxx/fw_major
+Date: Jan 2026
+Contact: linux-input@vger.kernel.org
+Description:
+ Reports the firmware major version provided by the touchscreen.
+
+ Access: Read
+
+ Valid values: Represented as string
+
+What: /sys/bus/i2c/devices/xxx/fw_minor
+Date: Jan 2026
+Contact: linux-input@vger.kernel.org
+Description:
+ Reports the firmware minor version provided by the touchscreen.
+
+ Access: Read
+
+ Valid values: Represented as string
+
+What: /sys/bus/i2c/devices/xxx/fw_rc
+Date: Jan 2026
+Contact: linux-input@vger.kernel.org
+Description:
+ Reports the firmware release canidate version provided by the touchscreen.
+
+ Access: Read
+
+ Valid values: Represented as string
+
+What: /sys/bus/i2c/devices/xxx/fw_status
+Date: Jan 2026
+Contact: linux-input@vger.kernel.org
+Description:
+ Reports the firmware status provided by the touchscreen. It may
+ be either "release" or "engineering".
+
+ Access: Read
+
+ Valid values: Represented as string
+
+What: /sys/bus/i2c/devices/xxx/fw_variant
+Date: Jan 2026
+Contact: linux-input@vger.kernel.org
+Description:
+ Reports the firmware variant provided by the touchscreen. It may
+ be either: "3d", "2d", "force" or "unknown".
+
+ Access: Read
+
+ Valid values: Represented as string
+
+What: /sys/bus/i2c/devices/xxx/device_id
+Date: Jan 2026
+Contact: linux-input@vger.kernel.org
+Description:
+ Reports the touchscreen device id, for example: "54" for the AX54A.
+
+ Access: Read
+
+ Valid values: Represented as string
+
+What: /sys/bus/i2c/devices/xxx/device_state
+Date: Jan 2026
+Contact: linux-input@vger.kernel.org
+Description:
+ Reports the touchscreen device current runtime state. The
+ following values are reported:
+
+ discovery: Device is in discovery mode.
+ tcp: Device is in touch-control-protocol (tcp) mode. This is
+ the normal working mode.
+ th2cfg-update: Device is in configuration update mode.
+ bootloader-pre: Device bootloader mode enter was triggered
+ bootloader: Device is in bootloader mode, used for firmware
+ updates.
+ unknown: Device mode is unknown.
+
+ Access: Read
+
+ Valid values: Represented as string
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 7d5b72ee07fa1313da39a625b5129a0459720865..449ae5e29cb4bb1f5335afdee82e91f0aa30a209 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -828,6 +828,23 @@ config TOUCHSCREEN_MIGOR
To compile this driver as a module, choose M here: the
module will be called migor_ts.
+config TOUCHSCREEN_TOUCHNETIX_AXIOM
+ tristate "TouchNetix aXiom based touchscreen controllers"
+ # We need to call into panel code so if DRM=m, this can't be 'y'
+ depends on DRM || !DRM
+ depends on I2C
+ select CRC16
+ select CRC32
+ select REGMAP_I2C
+ help
+ Say Y here if you have a axiom touchscreen connected to
+ your system.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called touchnetix_axiom.
+
config TOUCHSCREEN_TOUCHRIGHT
tristate "Touchright serial touchscreen"
select SERIO
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index ab9abd151078831a4b22d6998e00ef74fe01c356..540df3ada4b2b6ad05ffeba67f44ff262f93c11f 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -90,6 +90,7 @@ obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o
obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI) += surface3_spi.o
obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) += ti_am335x_tsc.o
obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o
+obj-$(CONFIG_TOUCHSCREEN_TOUCHNETIX_AXIOM) += touchnetix_axiom.o
obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o
obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
obj-$(CONFIG_TOUCHSCREEN_TS4800) += ts4800-ts.o
diff --git a/drivers/input/touchscreen/touchnetix_axiom.c b/drivers/input/touchscreen/touchnetix_axiom.c
new file mode 100644
index 0000000000000000000000000000000000000000..e8f56a8f7e8a83361b04bb858caadf9658fb7e05
--- /dev/null
+++ b/drivers/input/touchscreen/touchnetix_axiom.c
@@ -0,0 +1,2974 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TouchNetix aXiom Touchscreen Driver
+ *
+ * Copyright (C) 2024 Pengutronix
+ *
+ * Marco Felsch <kernel@pengutronix.de>
+ */
+
+#include <drm/drm_panel.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/completion.h>
+#include <linux/crc16.h>
+#include <linux/crc32.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/time.h>
+#include <linux/unaligned.h>
+
+/*
+ * Short introduction for developers:
+ * The programming manual is written based on u(sages):
+ * - Max. 0xff usages possible
+ * - A usage is a group of registers (0x00 ... 0xff)
+ * - The usage base address must be discovered (FW dependent)
+ * - Partial RW usage access is allowed
+ * - Each usage has a revision (FW dependent)
+ * - Only u31 is always at address 0x0 (used for discovery)
+ *
+ * E.x. Reading register 0x01 for usage u03 with baseaddr 0x20 results in the
+ * following physical 16bit I2C address: 0x2001.
+ *
+ * Note the datasheet specifies the usage numbers in hex and the internal
+ * offsets in decimal. Keep it that way to make it more developer friendly.
+ */
+#define AXIOM_U01 0x01
+#define AXIOM_U01_REV1_REPORTTYPE_REG 0
+#define AXIOM_U01_REV1_REPORTTYPE_HELLO 0
+#define AXIOM_U01_REV1_REPORTTYPE_HEARTBEAT 1
+#define AXIOM_U01_REV1_REPORTTYPE_OPCOMPLETE 3
+
+#define AXIOM_U02 0x02
+#define AXIOM_U02_REV1_COMMAND_REG 0
+#define AXIOM_U02_REV1_CMD_HARDRESET 0x0001
+#define AXIOM_U02_REV1_CMD_SOFTRESET 0x0002
+#define AXIOM_U02_REV1_CMD_STOP 0x0005
+#define AXIOM_U02_REV1_CMD_SAVEVLTLCFG2NVM 0x0007
+#define AXIOM_U02_REV1_PARAM1_SAVEVLTLCFG2NVM 0xb10c
+#define AXIOM_U02_REV1_PARAM2_SAVEVLTLCFG2NVM 0xc0de
+#define AXIOM_U02_REV1_CMD_HANDSHAKENVM 0x0008
+#define AXIOM_U02_REV1_CMD_COMPUTECRCS 0x0009
+#define AXIOM_U02_REV1_CMD_FILLCONFIG 0x000a
+#define AXIOM_U02_REV1_PARAM0_FILLCONFIG 0x5555
+#define AXIOM_U02_REV1_PARAM1_FILLCONFIG 0xaaaa
+#define AXIOM_U02_REV1_PARAM2_FILLCONFIG_ZERO 0xa55a
+#define AXIOM_U02_REV1_CMD_ENTERBOOTLOADER 0x000b
+#define AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY1 0x5555
+#define AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY2 0xaaaa
+#define AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY3 0xa55a
+#define AXIOM_U02_REV1_RESP_SUCCESS 0x0000
+
+struct axiom_u02_rev1_system_manager_msg {
+ union {
+ __le16 command;
+ __le16 response;
+ };
+ __le16 parameters[3];
+};
+
+#define AXIOM_U04 0x04
+#define AXIOM_U04_REV1_SIZE_BYTES 128
+
+#define AXIOM_U05 0x05 /* CDU */
+
+#define AXIOM_U22 0x22 /* CDU */
+
+#define AXIOM_U31 0x31
+#define AXIOM_U31_REV1_PAGE0 0x0000
+#define AXIOM_U31_REV1_DEVICE_ID_LOW_REG (AXIOM_U31_REV1_PAGE0 + 0)
+#define AXIOM_U31_REV1_DEVICE_ID_HIGH_REG (AXIOM_U31_REV1_PAGE0 + 1)
+#define AXIOM_U31_REV1_MODE_MASK BIT(7)
+#define AXIOM_U31_REV1_MODE_BLP 1
+#define AXIOM_U31_REV1_DEVICE_ID_HIGH_MASK GENMASK(6, 0)
+#define AXIOM_U31_REV1_RUNTIME_FW_MIN_REG (AXIOM_U31_REV1_PAGE0 + 2)
+#define AXIOM_U31_REV1_RUNTIME_FW_MAJ_REG (AXIOM_U31_REV1_PAGE0 + 3)
+#define AXIOM_U31_REV1_RUNTIME_FW_STATUS_REG (AXIOM_U31_REV1_PAGE0 + 4)
+#define AXIOM_U31_REV1_RUNTIME_FW_STATUS BIT(7)
+#define AXIOM_U31_REV1_RUNTIME_FW_VARIANT GENMASK(6, 0)
+#define AXIOM_U31_REV1_JEDEC_ID_LOW_REG (AXIOM_U31_REV1_PAGE0 + 8)
+#define AXIOM_U31_REV1_JEDEC_ID_HIGH_REG (AXIOM_U31_REV1_PAGE0 + 9)
+#define AXIOM_U31_REV1_NUM_USAGES_REG (AXIOM_U31_REV1_PAGE0 + 10)
+#define AXIOM_U31_REV1_RUNTIME_FW_RC_REG (AXIOM_U31_REV1_PAGE0 + 11)
+#define AXIOM_U31_REV1_RUNTIME_FW_RC_MASK GENMASK(7, 4)
+#define AXIOM_U31_REV1_SILICON_REV_MASK GENMASK(3, 0)
+
+#define AXIOM_U31_REV1_PAGE1 0x0100
+#define AXIOM_U31_REV1_OFFSET_TYPE_MASK BIT(7)
+#define AXIOM_U31_REV1_MAX_OFFSET_MASK GENMASK(6, 0)
+
+#define AXIOM_U32 0x32
+
+struct axiom_u31_usage_table_entry {
+ u8 usage_num;
+ u8 start_page;
+ u8 num_pages;
+ u8 max_offset;
+ u8 uifrevision;
+ u8 reserved;
+} __packed;
+
+#define AXIOM_U33 0x33
+
+struct axiom_u33_rev2 {
+ __le32 runtime_crc;
+ __le32 runtime_nvm_crc;
+ __le32 bootloader_crc;
+ __le32 nvltlusageconfig_crc;
+ __le32 vltusageconfig_crc;
+ __le32 u22_sequencedata_crc;
+ __le32 u43_hotspots_crc;
+ __le32 u93_profiles_crc;
+ __le32 u94_deltascalemap_crc;
+ __le32 runtimehash_crc;
+};
+
+struct axiom_u33_rev3 {
+ __le32 runtime_crc;
+ __le32 runtime_nvm_crc;
+ __le32 bootloader_crc;
+ __le32 nvltlusageconfig_crc;
+ __le32 vltusageconfig_crc;
+ __le32 u22_sequencedata_crc;
+ __le32 u43_hotspots_crc;
+ __le32 u77_dod_data_crc;
+ __le32 u93_profiles_crc;
+ __le32 u94_deltascalemap_crc;
+ __le32 runtimehash_crc;
+};
+
+#define AXIOM_U34 0x34
+#define AXIOM_U34_REV1_OVERFLOW_MASK BIT(7)
+#define AXIOM_U34_REV1_REPORTLENGTH_MASK GENMASK(6, 0)
+#define AXIOM_U34_REV1_PREAMBLE_BYTES 2
+#define AXIOM_U34_REV1_POSTAMBLE_BYTES 4
+
+#define AXIOM_U36 0x36
+
+#define AXIOM_U41 0x41
+#define AXIOM_U41_REV2_TARGETSTATUS_REG 0
+#define AXIOM_U41_REV2_X_REG(id) ((4 * (id)) + 2)
+#define AXIOM_U41_REV2_Y_REG(id) ((4 * (id)) + 4)
+#define AXIOM_U41_REV2_Z_REG(id) ((id) + 42)
+
+#define AXIOM_U42 0x42
+#define AXIOM_U42_REV1_REPORT_ID_CONTAINS(id) ((id) + 2)
+#define AXIOM_U42_REV1_REPORT_ID_TOUCH 1 /* Touch, Proximity, Hover */
+
+#define AXIOM_U42_REV4_REPORT_ID_CONTAINS(id) ((id) + 8)
+#define AXIOM_U42_REV4_REPORT_ID_TOUCH 1 /* Touch, Proximity, Hover */
+
+#define AXIOM_U43 0x43 /* CDU */
+
+#define AXIOM_U64 0x64
+#define AXIOM_U64_REV2_ENABLECDSPROCESSING_REG 0
+#define AXIOM_U64_REV2_ENABLECDSPROCESSING_MASK BIT(0)
+
+#define AXIOM_U77 0x77 /* CDU */
+#define AXIOM_U82 0x82
+#define AXIOM_U93 0x93 /* CDU */
+#define AXIOM_U94 0x94 /* CDU */
+
+/*
+ * Axiom CDU usage structure copied from downstream CDU_Common.py. Downstream
+ * doesn't mention any revision. According downstream all CDU register windows
+ * are 56 byte wide (8 byte header + 48 byte data).
+ */
+#define AXIOM_CDU_CMD_STORE 0x0002
+#define AXIOM_CDU_CMD_COMMIT 0x0003
+#define AXIOM_CDU_PARAM0_COMMIT 0xb10c
+#define AXIOM_CDU_PARAM1_COMMIT 0xc0de
+
+#define AXIOM_CDU_RESP_SUCCESS 0x0000
+#define AXIOM_CDU_MAX_DATA_BYTES 48
+
+struct axiom_cdu_usage {
+ union {
+ __le16 command;
+ __le16 response;
+ };
+ __le16 parameters[3];
+ u8 data[AXIOM_CDU_MAX_DATA_BYTES];
+};
+
+/*
+ * u01 for the bootloader protocol (BLP)
+ *
+ * Values taken from Bootloader.py [1] which had a comment that documentation
+ * values are out dated. The BLP does not have different versions according the
+ * documentation python helper.
+ *
+ * [1] https://github.com/TouchNetix/axiom_pylib
+ */
+#define AXIOM_U01_BLP_COMMAND_REG 0x0100
+#define AXIOM_U01_BLP_COMMAND_RESET BIT(1)
+#define AXIOM_U01_BLP_SATUS_REG 0x0100
+#define AXIOM_U01_BLP_STATUS_BUSY BIT(0)
+#define AXIOM_U01_BLP_FIFO_REG 0x0102
+#define AXIOM_U01_BLP_FIFO_CHK_SIZE_BYTES 255
+
+#define AXIOM_PROX_LEVEL -128
+#define AXIOM_STARTUP_TIME_MS 110
+
+#define AXIOM_USAGE_BASEADDR_MASK GENMASK(15, 8)
+#define AXIOM_MAX_USAGES 256 /* u00 - uFF */
+/*
+ * The devices have a 16bit ADC but Touchnetix used the lower two bits for other
+ * information.
+ */
+#define AXIOM_MAX_XY (65535 - 3)
+#define AXIOM_DEFAULT_POLL_INTERVAL_MS 10
+#define AXIOM_PAGE_BYTE_LEN 256
+#define AXIOM_MAX_XFERLEN 0x7fff
+#define AXIOM_MAX_TOUCHSLOTS 10
+#define AXIOM_MAX_TOUCHSLOTS_MASK GENMASK(9, 0)
+
+/* aXiom firmware (.axfw) */
+#define AXIOM_FW_AXFW_SIGNATURE "AXFW"
+#define AXIOM_FW_AXFW_FILE_FMT_VER 0x0200
+
+struct axiom_fw_axfw_hdr {
+ u8 signature[4];
+ __le32 file_crc32;
+ __le16 file_format_ver;
+ __le16 device_id;
+ u8 variant;
+ u8 minor_ver;
+ u8 major_ver;
+ u8 rc_ver;
+ u8 status;
+ __le16 silicon_ver;
+ u8 silicon_rev;
+ __le32 fw_crc32;
+} __packed;
+
+struct axiom_fw_axfw_chunk_hdr {
+ u8 internal[6]; /* no description */
+ __be16 payload_length;
+};
+
+/* aXiom config (.th2cfgbin) */
+#define AXIOM_FW_CFG_SIGNATURE 0x20071969
+
+struct axiom_fw_cfg_hdr {
+ __be32 signature;
+ __le16 file_format_ver;
+ __le16 tcp_file_rev_major;
+ __le16 tcp_file_rev_minor;
+ __le16 tcp_file_rev_patch;
+ u8 tcp_version;
+} __packed;
+
+struct axiom_fw_cfg_chunk_hdr {
+ u8 usage_num;
+ u8 usage_rev;
+ u8 reserved;
+ __le16 usage_length;
+} __packed;
+
+struct axiom_fw_cfg_chunk {
+ u8 usage_num;
+ u8 usage_rev;
+ u16 usage_length;
+ const u8 *usage_content;
+};
+
+enum axiom_fw_type {
+ AXIOM_FW_AXFW,
+ AXIOM_FW_CFG,
+ AXIOM_FW_NUM
+};
+
+enum axiom_crc_type {
+ AXIOM_CRC_CUR,
+ AXIOM_CRC_NEW,
+ AXIOM_CRC_NUM
+};
+
+struct axiom_data;
+
+struct axiom_usage_info {
+ unsigned char usage_num; /* uXX number (XX in hex) */
+ unsigned int rev_num; /* rev.X (X in dec) */
+ bool is_cdu;
+ bool is_ro;
+
+ /* Optional hooks */
+ int (*process_report)(struct axiom_data *ts, const u8 *buf, size_t bufsize);
+};
+
+enum axiom_runmode {
+ AXIOM_DISCOVERY_MODE,
+ AXIOM_TCP_MODE,
+ AXIOM_TCP_CFG_UPDATE_MODE,
+ AXIOM_BLP_PRE_MODE,
+ AXIOM_BLP_MODE,
+};
+
+struct axiom_data {
+ struct input_dev *input;
+ struct device *dev;
+
+ struct gpio_desc *reset_gpio;
+ struct regulator_bulk_data supplies[2];
+ unsigned int num_supplies;
+
+ struct regmap *regmap;
+ struct touchscreen_properties prop;
+ bool irq_setup_done;
+ u32 poll_interval;
+
+ struct drm_panel_follower panel_follower;
+ bool is_panel_follower;
+
+ enum axiom_runmode mode;
+ /*
+ * Two completion types to support firmware updates
+ * in irq and poll mode.
+ */
+ struct axiom_completion {
+ struct completion completion;
+ bool poll_done;
+ } nvm_write, boot_complete;
+
+ /* Lock to protect both firmware interfaces */
+ struct mutex fwupdate_lock;
+ struct axiom_firmware {
+ /* Lock to protect cancel */
+ struct mutex lock;
+ bool cancel;
+ struct fw_upload *fwl;
+ } fw[AXIOM_FW_NUM];
+
+ unsigned int fw_major;
+ unsigned int fw_minor;
+ unsigned int fw_rc;
+ unsigned int fw_status;
+ unsigned int fw_variant;
+ u16 device_id;
+ u16 jedec_id;
+ u8 silicon_rev;
+
+ /* CRCs we need to check during a config update */
+ struct axiom_crc {
+ u32 runtime;
+ u32 vltusageconfig;
+ u32 nvltlusageconfig;
+ u32 u22_sequencedata;
+ u32 u43_hotspots;
+ u32 u77_dod_data;
+ u32 u93_profiles;
+ u32 u94_deltascalemap;
+ } crc[AXIOM_CRC_NUM];
+
+ bool cds_enabled;
+ unsigned long enabled_slots;
+ unsigned int num_slots;
+
+ unsigned int max_report_byte_len;
+ struct axiom_usage_table_entry {
+ bool populated;
+ unsigned int baseaddr;
+ unsigned int size_bytes;
+ const struct axiom_usage_info *info;
+ } usage_table[AXIOM_MAX_USAGES];
+};
+
+static int axiom_u01_rev1_process_report(struct axiom_data *ts, const u8 *buf,
+ size_t bufsize);
+static int axiom_u34_rev1_process_report(struct axiom_data *ts, const u8 *_buf,
+ size_t bufsize);
+static int axiom_u41_rev2_process_report(struct axiom_data *ts, const u8 *buf,
+ size_t bufsize);
+
+#define AXIOM_USAGE(num, rev) \
+ { \
+ .usage_num = num, \
+ .rev_num = rev, \
+ }
+
+#define AXIOM_RO_USAGE(num, rev) \
+ { \
+ .usage_num = num, \
+ .rev_num = rev, \
+ .is_ro = true, \
+ }
+
+#define AXIOM_CDU_USAGE(num, rev) \
+ { \
+ .usage_num = num, \
+ .rev_num = rev, \
+ .is_cdu = true, \
+ }
+
+#define AXIOM_REPORT_USAGE(num, rev, func) \
+ { \
+ .usage_num = num, \
+ .rev_num = rev, \
+ .process_report = func, \
+ }
+
+#define AXIOM_USAGE_REV_UNUSED (-1)
+
+/*
+ * All usages used by driver must be added to this list to ensure the correct
+ * communictation with the devices. The list can contain multiple entries of the
+ * same usage to handle different usage revisions.
+ *
+ * Note:
+ * During a th2cfgbin update the driver may use usages not listed here.
+ * Therefore the th2cfgbin update compares the current running FW again the
+ * th2cfgbin targets FW.
+ */
+static const struct axiom_usage_info driver_required_usages[] = {
+ AXIOM_REPORT_USAGE(AXIOM_U01, 1, axiom_u01_rev1_process_report),
+ AXIOM_REPORT_USAGE(AXIOM_U01, 3, axiom_u01_rev1_process_report),
+ AXIOM_USAGE(AXIOM_U02, 1),
+ AXIOM_USAGE(AXIOM_U02, 2),
+ AXIOM_USAGE(AXIOM_U04, 1),
+ AXIOM_RO_USAGE(AXIOM_U33, 2),
+ AXIOM_RO_USAGE(AXIOM_U33, 3),
+ AXIOM_REPORT_USAGE(AXIOM_U34, 1, axiom_u34_rev1_process_report),
+ AXIOM_REPORT_USAGE(AXIOM_U41, 2, axiom_u41_rev2_process_report),
+ AXIOM_REPORT_USAGE(AXIOM_U41, 4, axiom_u41_rev2_process_report),
+ AXIOM_USAGE(AXIOM_U42, 1),
+ AXIOM_USAGE(AXIOM_U42, 4),
+ AXIOM_USAGE(AXIOM_U64, 2),
+ AXIOM_USAGE(AXIOM_U64, 4),
+ { /* sentinel */ }
+};
+
+/*
+ * All usages below are unused but the driver needs to know the type (ro, cdu)
+ * to handle them correctly. Unfortunately the type is not discoverable. Once
+ * a usage is actually used, it must be shifted to driver_required_usages and
+ * the revision must be set accordingly.
+ */
+static const struct axiom_usage_info driver_additional_usages[] = {
+ AXIOM_CDU_USAGE(AXIOM_U05, AXIOM_USAGE_REV_UNUSED),
+ AXIOM_CDU_USAGE(AXIOM_U22, AXIOM_USAGE_REV_UNUSED),
+ AXIOM_RO_USAGE(AXIOM_U31, AXIOM_USAGE_REV_UNUSED),
+ AXIOM_RO_USAGE(AXIOM_U32, AXIOM_USAGE_REV_UNUSED),
+ AXIOM_RO_USAGE(AXIOM_U36, AXIOM_USAGE_REV_UNUSED),
+ AXIOM_CDU_USAGE(AXIOM_U43, AXIOM_USAGE_REV_UNUSED),
+ AXIOM_CDU_USAGE(AXIOM_U77, AXIOM_USAGE_REV_UNUSED),
+ AXIOM_RO_USAGE(AXIOM_U82, AXIOM_USAGE_REV_UNUSED),
+ AXIOM_CDU_USAGE(AXIOM_U93, AXIOM_USAGE_REV_UNUSED),
+ AXIOM_CDU_USAGE(AXIOM_U94, AXIOM_USAGE_REV_UNUSED),
+ { /* sentinel */ }
+};
+
+/************************ Common helpers **************************************/
+
+static void axiom_set_runmode(struct axiom_data *ts, enum axiom_runmode mode)
+{
+ ts->mode = mode;
+}
+
+static enum axiom_runmode axiom_get_runmode(struct axiom_data *ts)
+{
+ return ts->mode;
+}
+
+static const char *axiom_runmode_to_string(struct axiom_data *ts)
+{
+ switch (ts->mode) {
+ case AXIOM_DISCOVERY_MODE: return "discovery";
+ case AXIOM_TCP_MODE: return "tcp";
+ case AXIOM_TCP_CFG_UPDATE_MODE: return "th2cfg-update";
+ case AXIOM_BLP_PRE_MODE: return "bootloader-pre";
+ case AXIOM_BLP_MODE: return "bootlaoder";
+ default: return "unknown";
+ }
+}
+
+static bool axiom_skip_usage_check(struct axiom_data *ts)
+{
+ switch (ts->mode) {
+ case AXIOM_TCP_CFG_UPDATE_MODE:
+ case AXIOM_DISCOVERY_MODE:
+ case AXIOM_BLP_MODE:
+ return true;
+ case AXIOM_BLP_PRE_MODE:
+ case AXIOM_TCP_MODE:
+ default:
+ return false;
+ }
+}
+
+static unsigned int
+axiom_usage_baseaddr(struct axiom_data *ts, unsigned char usage_num)
+{
+ return ts->usage_table[usage_num].baseaddr;
+}
+
+static unsigned int
+axiom_usage_size(struct axiom_data *ts, unsigned char usage_num)
+{
+ return ts->usage_table[usage_num].size_bytes;
+}
+
+static int
+axiom_usage_rev(struct axiom_data *ts, unsigned char usage_num)
+{
+ struct axiom_usage_table_entry *entry = &ts->usage_table[usage_num];
+
+ if (!entry->info)
+ return -EINVAL;
+
+ return entry->info->rev_num;
+}
+
+static bool
+axiom_driver_supports_usage(struct axiom_data *ts, unsigned char usage_num)
+{
+ const struct axiom_usage_info *iter = driver_required_usages;
+ struct device *dev = ts->dev;
+ int rev;
+
+ /*
+ * Some features depend on the current running firmware. Don't print an
+ * error if the usage for an optional feature is missing.
+ */
+ if (!ts->usage_table[usage_num].populated) {
+ dev_dbg(dev, "u%02X is not supported by the current firmware\n",
+ usage_num);
+ return false;
+ }
+
+ rev = axiom_usage_rev(ts, usage_num);
+ if (rev < 0) {
+ dev_warn(dev, "Driver doesn't support u%02X yet\n", usage_num);
+ return false;
+ }
+
+ for (; iter; iter++) {
+ if (iter->usage_num != usage_num)
+ continue;
+
+ if (iter->rev_num == rev)
+ return true;
+ }
+
+ dev_warn(dev, "Driver doesn't support u%02X rev.%d yet\n",
+ usage_num, rev);
+
+ return false;
+}
+
+static bool
+axiom_usage_entry_is_report(struct axiom_u31_usage_table_entry *entry)
+{
+ return entry->num_pages == 0;
+}
+
+static unsigned int
+axiom_get_usage_size_bytes(struct axiom_u31_usage_table_entry *entry)
+{
+ unsigned char max_offset;
+
+ max_offset = FIELD_GET(AXIOM_U31_REV1_MAX_OFFSET_MASK,
+ entry->max_offset) + 1;
+ max_offset *= 2;
+
+ if (axiom_usage_entry_is_report(entry))
+ return max_offset;
+
+ if (FIELD_GET(AXIOM_U31_REV1_OFFSET_TYPE_MASK, entry->max_offset))
+ return (entry->num_pages - 1) * AXIOM_PAGE_BYTE_LEN + max_offset;
+
+ return max_offset;
+}
+
+static void axiom_dump_usage_entry(struct device *dev,
+ struct axiom_u31_usage_table_entry *entry)
+{
+ unsigned int page_len, total_len;
+
+ total_len = axiom_get_usage_size_bytes(entry);
+
+ if (total_len > AXIOM_PAGE_BYTE_LEN)
+ page_len = AXIOM_PAGE_BYTE_LEN;
+ else
+ page_len = total_len;
+
+ if (axiom_usage_entry_is_report(entry))
+ dev_dbg(dev,
+ "u%02X rev.%d total-len:%u [REPORT]\n",
+ entry->usage_num, entry->uifrevision, total_len);
+ else
+ dev_dbg(dev,
+ "u%02X rev.%d first-page:%#02x page-len:%u num-pages:%u total-len:%u\n",
+ entry->usage_num, entry->uifrevision, entry->start_page, page_len,
+ entry->num_pages, total_len);
+}
+
+static const struct axiom_usage_info *
+axiom_get_usage_info(struct axiom_u31_usage_table_entry *query)
+{
+ const struct axiom_usage_info *info = driver_required_usages;
+ bool required = false;
+ bool found = false;
+
+ for (; info->usage_num; info++) {
+ /* Skip all usages not used by the driver */
+ if (query->usage_num != info->usage_num)
+ continue;
+
+ /* The usage is used so we need to mark it as required */
+ required = true;
+
+ /* Continue with the next usage if the revision doesn't match */
+ if (query->uifrevision != info->rev_num)
+ continue;
+
+ found = true;
+ break;
+ }
+
+ if (found)
+ return info;
+
+ /* Return an error if not found but required */
+ if (required)
+ return ERR_PTR(-EINVAL);
+
+ info = driver_additional_usages;
+ for (; info->usage_num; info++) {
+ if (query->usage_num != info->usage_num)
+ continue;
+
+ /*
+ * No need to check the revision since these usages are not
+ * used actually but the driver needs the type information.
+ */
+ return info;
+ }
+
+ /* No info found */
+ return NULL;
+}
+
+static bool axiom_usage_supported(struct axiom_data *ts, unsigned int baseaddr)
+{
+ struct axiom_usage_table_entry *entry;
+ unsigned int i;
+
+ if (axiom_skip_usage_check(ts))
+ return true;
+
+ dev_dbg(ts->dev, "Checking support for baseaddr: %#x\n", baseaddr);
+
+ for (i = 0; i < ARRAY_SIZE(ts->usage_table); i++) {
+ entry = &ts->usage_table[i];
+
+ if (!entry->populated)
+ continue;
+
+ if (entry->baseaddr != baseaddr)
+ continue;
+
+ break;
+ }
+
+ if (i == ARRAY_SIZE(ts->usage_table)) {
+ dev_warn(ts->dev, "Usage not found\n");
+ return false;
+ }
+
+ if (!entry->info)
+ dev_warn(ts->dev, "Unsupported usage u%02X used, driver bug!", i);
+
+ return !!entry->info;
+}
+
+static void axiom_poll(struct input_dev *input);
+
+static unsigned long
+axiom_wait_for_completion_timeout(struct axiom_data *ts, struct axiom_completion *x,
+ long timeout)
+{
+ struct i2c_client *client = to_i2c_client(ts->dev);
+ unsigned long poll_timeout;
+
+ if (client->irq)
+ return wait_for_completion_timeout(&x->completion, timeout);
+
+ /*
+ * Only firmware update cases do wait for completion. Since they require
+ * the input device to be closed, the poller is not running. So we need
+ * to do the polling manually.
+ */
+ poll_timeout = timeout / 10;
+
+ /*
+ * Very basic and not very accurate but it does the job because there
+ * are no known timeout constraints.
+ */
+ do {
+ axiom_poll(ts->input);
+ fsleep(jiffies_to_usecs(poll_timeout));
+ if (x->poll_done)
+ break;
+ timeout -= poll_timeout;
+ } while (timeout > 0);
+
+ x->poll_done = false;
+
+ return timeout > 0 ? timeout : 0;
+}
+
+static void axiom_complete(struct axiom_data *ts, struct axiom_completion *x)
+{
+ struct i2c_client *client = to_i2c_client(ts->dev);
+
+ if (client->irq)
+ complete(&x->completion);
+ else
+ x->poll_done = true;
+}
+
+/*************************** Usage handling ***********************************/
+/*
+ * Wrapper functions to handle the usage access. Wrappers are used to add
+ * different revision handling later on more easily.
+ */
+static int axiom_u02_wait_idle(struct axiom_data *ts)
+{
+ unsigned int reg;
+ int ret, _ret;
+ u16 cmd;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+ return -EINVAL;
+
+ reg = axiom_usage_baseaddr(ts, AXIOM_U02);
+ reg += AXIOM_U02_REV1_COMMAND_REG;
+
+ /*
+ * Missing regmap_raw_read_poll_timeout for now. RESP_SUCCESS means that
+ * the last command successfully completed and the device is idle.
+ */
+ ret = read_poll_timeout(regmap_raw_read, _ret,
+ _ret || cmd == AXIOM_U02_REV1_RESP_SUCCESS,
+ 10 * USEC_PER_MSEC, 1 * USEC_PER_SEC, false,
+ ts->regmap, reg, &cmd, 2);
+ if (ret)
+ dev_err(ts->dev, "Poll u02 timedout with: %#x\n", cmd);
+
+ return ret;
+}
+
+static int
+axiom_u02_send_msg(struct axiom_data *ts,
+ const struct axiom_u02_rev1_system_manager_msg *msg,
+ bool validate_response)
+{
+ unsigned int reg;
+ int ret;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+ return -EINVAL;
+
+ reg = axiom_usage_baseaddr(ts, AXIOM_U02);
+ reg += AXIOM_U02_REV1_COMMAND_REG;
+
+ ret = regmap_raw_write(ts->regmap, reg, msg, sizeof(*msg));
+ if (ret)
+ return ret;
+
+ if (!validate_response)
+ return 0;
+
+ return axiom_u02_wait_idle(ts);
+}
+
+static int
+axiom_u02_rev1_send_single_cmd(struct axiom_data *ts, u16 cmd)
+{
+ struct axiom_u02_rev1_system_manager_msg msg = {
+ .command = cpu_to_le16(cmd)
+ };
+
+ return axiom_u02_send_msg(ts, &msg, true);
+}
+
+static int axiom_u02_handshakenvm(struct axiom_data *ts)
+{
+ return axiom_u02_rev1_send_single_cmd(ts, AXIOM_U02_REV1_CMD_HANDSHAKENVM);
+}
+
+static int axiom_u02_computecrc(struct axiom_data *ts)
+{
+ return axiom_u02_rev1_send_single_cmd(ts, AXIOM_U02_REV1_CMD_COMPUTECRCS);
+}
+
+static int axiom_u02_stop(struct axiom_data *ts)
+{
+ return axiom_u02_rev1_send_single_cmd(ts, AXIOM_U02_REV1_CMD_STOP);
+}
+
+static int axiom_u02_save_config(struct axiom_data *ts)
+{
+ struct axiom_u02_rev1_system_manager_msg msg;
+ int ret;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+ return -EINVAL;
+
+ msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_SAVEVLTLCFG2NVM);
+ msg.parameters[0] = 0; /* Don't care */
+ msg.parameters[1] = cpu_to_le16(AXIOM_U02_REV1_PARAM1_SAVEVLTLCFG2NVM);
+ msg.parameters[2] = cpu_to_le16(AXIOM_U02_REV1_PARAM2_SAVEVLTLCFG2NVM);
+
+ ret = axiom_u02_send_msg(ts, &msg, false);
+ if (ret)
+ return ret;
+
+ /* Downstream axcfg.py waits for 2sec without checking U01 response */
+ ret = axiom_wait_for_completion_timeout(ts, &ts->nvm_write,
+ msecs_to_jiffies(2 * MSEC_PER_SEC));
+ if (!ret)
+ dev_err(ts->dev, "Error save volatile config timedout\n");
+
+ return ret ? 0 : -ETIMEDOUT;
+}
+
+static int axiom_u02_swreset(struct axiom_data *ts)
+{
+ struct axiom_u02_rev1_system_manager_msg msg = { };
+ int ret;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+ return -EINVAL;
+
+ msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_SOFTRESET);
+ ret = axiom_u02_send_msg(ts, &msg, false);
+ if (ret)
+ return ret;
+
+ /*
+ * Downstream axcfg.py waits for 1sec without checking U01 hello. Tests
+ * showed that waiting for the hello message isn't enough therefore we
+ * need both to make it robuster.
+ */
+ ret = axiom_wait_for_completion_timeout(ts, &ts->boot_complete,
+ msecs_to_jiffies(1 * MSEC_PER_SEC));
+ if (!ret)
+ dev_err(ts->dev, "Error swreset timedout\n");
+
+ fsleep(USEC_PER_SEC);
+
+ return ret ? 0 : -ETIMEDOUT;
+}
+
+static int axiom_u02_fillconfig(struct axiom_data *ts)
+{
+ struct axiom_u02_rev1_system_manager_msg msg;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+ return -EINVAL;
+
+ msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_FILLCONFIG);
+ msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_FILLCONFIG);
+ msg.parameters[1] = cpu_to_le16(AXIOM_U02_REV1_PARAM1_FILLCONFIG);
+ msg.parameters[2] = cpu_to_le16(AXIOM_U02_REV1_PARAM2_FILLCONFIG_ZERO);
+
+ return axiom_u02_send_msg(ts, &msg, true);
+}
+
+static int axiom_u02_enter_bootloader(struct axiom_data *ts)
+{
+ struct axiom_u02_rev1_system_manager_msg msg = { };
+ struct device *dev = ts->dev;
+ unsigned int val;
+ int ret;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U02))
+ return -EINVAL;
+
+ /*
+ * Enter the bootloader mode requires 3 consecutive messages so we can't
+ * check for the response.
+ */
+ msg.command = cpu_to_le16(AXIOM_U02_REV1_CMD_ENTERBOOTLOADER);
+ msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY1);
+ ret = axiom_u02_send_msg(ts, &msg, false);
+ if (ret) {
+ dev_err(dev, "Failed to send bootloader-key1: %d\n", ret);
+ return ret;
+ }
+
+ msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY2);
+ ret = axiom_u02_send_msg(ts, &msg, false);
+ if (ret) {
+ dev_err(dev, "Failed to send bootloader-key2: %d\n", ret);
+ return ret;
+ }
+
+ msg.parameters[0] = cpu_to_le16(AXIOM_U02_REV1_PARAM0_ENTERBOOLOADER_KEY3);
+ ret = axiom_u02_send_msg(ts, &msg, false);
+ if (ret) {
+ dev_err(dev, "Failed to send bootloader-key3: %d\n", ret);
+ return ret;
+ }
+
+ /* Sleep before the first read to give the device time */
+ fsleep(250 * USEC_PER_MSEC);
+
+ /* Wait till the device reports it is in bootloader mode */
+ return regmap_read_poll_timeout(ts->regmap,
+ AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, val,
+ FIELD_GET(AXIOM_U31_REV1_MODE_MASK, val) ==
+ AXIOM_U31_REV1_MODE_BLP, 250 * USEC_PER_MSEC,
+ USEC_PER_SEC);
+}
+
+static int axiom_u04_get(struct axiom_data *ts, u8 **_buf)
+{
+ u8 buf[AXIOM_U04_REV1_SIZE_BYTES];
+ unsigned int reg;
+ int ret;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U04))
+ return -EINVAL;
+
+ reg = axiom_usage_baseaddr(ts, AXIOM_U04);
+ ret = regmap_raw_read(ts->regmap, reg, buf, sizeof(buf));
+ if (ret)
+ return ret;
+
+ *_buf = kmemdup(buf, sizeof(buf), GFP_KERNEL);
+
+ return sizeof(buf);
+}
+
+static int axiom_u04_set(struct axiom_data *ts, u8 *buf, unsigned int bufsize)
+{
+ unsigned int reg;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U04))
+ return -EINVAL;
+
+ reg = axiom_usage_baseaddr(ts, AXIOM_U04);
+ return regmap_raw_write(ts->regmap, reg, buf, bufsize);
+}
+
+/*
+ * U31 revision must be always rev.1 else the whole self discovery mechanism
+ * fall apart.
+ */
+static int axiom_u31_parse_device_info(struct axiom_data *ts)
+{
+ struct regmap *regmap = ts->regmap;
+ unsigned int id_low, id_high, val;
+ int ret;
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, &id_high);
+ if (ret)
+ return ret;
+ id_high = FIELD_GET(AXIOM_U31_REV1_DEVICE_ID_HIGH_MASK, id_high);
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_LOW_REG, &id_low);
+ if (ret)
+ return ret;
+ ts->device_id = id_high << 8 | id_low;
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_MAJ_REG, &val);
+ if (ret)
+ return ret;
+ ts->fw_major = val;
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_MIN_REG, &val);
+ if (ret)
+ return ret;
+ ts->fw_minor = val;
+
+ /* All other fields are not allowed to be read in BLP mode */
+ if (axiom_get_runmode(ts) == AXIOM_BLP_MODE)
+ return 0;
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_RC_REG, &val);
+ if (ret)
+ return ret;
+ ts->fw_rc = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_RC_MASK, val);
+ ts->silicon_rev = FIELD_GET(AXIOM_U31_REV1_SILICON_REV_MASK, val);
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_RUNTIME_FW_STATUS_REG, &val);
+ if (ret)
+ return ret;
+ ts->fw_status = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_STATUS, val);
+ ts->fw_variant = FIELD_GET(AXIOM_U31_REV1_RUNTIME_FW_VARIANT, val);
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_JEDEC_ID_HIGH_REG, &val);
+ if (ret)
+ return ret;
+ ts->jedec_id = val << 8;
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_JEDEC_ID_LOW_REG, &val);
+ if (ret)
+ return ret;
+ ts->jedec_id |= val;
+
+ return 0;
+}
+
+static int axiom_u33_read(struct axiom_data *ts, struct axiom_crc *crc);
+
+static int axiom_u31_device_discover(struct axiom_data *ts)
+{
+ struct axiom_u31_usage_table_entry *u31_usage_table __free(kfree) = NULL;
+ struct axiom_u31_usage_table_entry *entry;
+ struct regmap *regmap = ts->regmap;
+ unsigned int mode, num_usages;
+ struct device *dev = ts->dev;
+ unsigned int i;
+ int ret;
+
+ axiom_set_runmode(ts, AXIOM_DISCOVERY_MODE);
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG, &mode);
+ if (ret) {
+ dev_err(dev, "Failed to read MODE\n");
+ return ret;
+ }
+
+ /* Abort if the device is in bootloader protocol mode */
+ mode = FIELD_GET(AXIOM_U31_REV1_MODE_MASK, mode);
+ if (mode == AXIOM_U31_REV1_MODE_BLP)
+ axiom_set_runmode(ts, AXIOM_BLP_MODE);
+
+ /* Since we are not in bootloader mode we can parse the device info */
+ ret = axiom_u31_parse_device_info(ts);
+ if (ret) {
+ dev_err(dev, "Failed to parse device info\n");
+ return ret;
+ }
+
+ /* All other fields are not allowed to be read in BLP mode */
+ if (axiom_get_runmode(ts) == AXIOM_BLP_MODE) {
+ dev_info(dev, "Device in Bootloader mode, firmware upload required\n");
+ return -EACCES;
+ }
+
+ ret = regmap_read(regmap, AXIOM_U31_REV1_NUM_USAGES_REG, &num_usages);
+ if (ret) {
+ dev_err(dev, "Failed to read NUM_USAGES\n");
+ return ret;
+ }
+
+ u31_usage_table = kcalloc(num_usages, sizeof(*u31_usage_table),
+ GFP_KERNEL);
+ if (!u31_usage_table)
+ return -ENOMEM;
+
+ ret = regmap_raw_read(regmap, AXIOM_U31_REV1_PAGE1, u31_usage_table,
+ array_size(num_usages, sizeof(*u31_usage_table)));
+ if (ret) {
+ dev_err(dev, "Failed to read NUM_USAGES\n");
+ return ret;
+ }
+
+ /*
+ * axiom_u31_device_discover() is call after fw update too, so ensure
+ * that the usage_table is cleared.
+ */
+ memset(ts->usage_table, 0, sizeof(ts->usage_table));
+
+ for (i = 0, entry = u31_usage_table; i < num_usages; i++, entry++) {
+ unsigned char idx = entry->usage_num;
+ const struct axiom_usage_info *info;
+ unsigned int size_bytes;
+
+ axiom_dump_usage_entry(dev, entry);
+
+ /*
+ * Verify that the driver used usages are supported. Don't abort
+ * yet if a usage isn't supported to allow the user to dump the
+ * actual usage table.
+ */
+ info = axiom_get_usage_info(entry);
+ if (IS_ERR(info)) {
+ dev_info(dev, "Required usage u%02X isn't supported for rev.%u\n",
+ entry->usage_num, entry->uifrevision);
+ ret = -EACCES;
+ }
+
+ size_bytes = axiom_get_usage_size_bytes(entry);
+
+ ts->usage_table[idx].baseaddr = entry->start_page << 8;
+ ts->usage_table[idx].size_bytes = size_bytes;
+ ts->usage_table[idx].populated = true;
+ ts->usage_table[idx].info = info;
+
+ if (axiom_usage_entry_is_report(entry) &&
+ ts->max_report_byte_len < size_bytes)
+ ts->max_report_byte_len = size_bytes;
+ }
+
+ if (ret)
+ return ret;
+
+ /* From now on we are in TCP mode to include usage revision checks */
+ axiom_set_runmode(ts, AXIOM_TCP_MODE);
+
+ return axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
+}
+
+static int axiom_u33_read(struct axiom_data *ts, struct axiom_crc *crc)
+{
+ struct device *dev = ts->dev;
+ unsigned int reg;
+ int ret;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U33))
+ return -EINVAL;
+
+ if (axiom_usage_rev(ts, AXIOM_U33) == 2) {
+ struct axiom_u33_rev2 val;
+
+ reg = axiom_usage_baseaddr(ts, AXIOM_U33);
+ ret = regmap_raw_read(ts->regmap, reg, &val, sizeof(val));
+ if (ret) {
+ dev_err(dev, "Failed to read u33\n");
+ return ret;
+ }
+
+ crc->runtime = le32_to_cpu(val.runtime_crc);
+ crc->vltusageconfig = le32_to_cpu(val.vltusageconfig_crc);
+ crc->nvltlusageconfig = le32_to_cpu(val.nvltlusageconfig_crc);
+ crc->u22_sequencedata = le32_to_cpu(val.u22_sequencedata_crc);
+ crc->u43_hotspots = le32_to_cpu(val.u43_hotspots_crc);
+ crc->u93_profiles = le32_to_cpu(val.u93_profiles_crc);
+ crc->u94_deltascalemap = le32_to_cpu(val.u94_deltascalemap_crc);
+ } else if (axiom_usage_rev(ts, AXIOM_U33) == 3) {
+ struct axiom_u33_rev3 val;
+
+ reg = axiom_usage_baseaddr(ts, AXIOM_U33);
+ ret = regmap_raw_read(ts->regmap, reg, &val, sizeof(val));
+ if (ret) {
+ dev_err(dev, "Failed to read u33\n");
+ return ret;
+ }
+
+ crc->runtime = le32_to_cpu(val.runtime_crc);
+ crc->vltusageconfig = le32_to_cpu(val.vltusageconfig_crc);
+ crc->nvltlusageconfig = le32_to_cpu(val.nvltlusageconfig_crc);
+ crc->u22_sequencedata = le32_to_cpu(val.u22_sequencedata_crc);
+ crc->u43_hotspots = le32_to_cpu(val.u43_hotspots_crc);
+ crc->u77_dod_data = le32_to_cpu(val.u77_dod_data_crc);
+ crc->u93_profiles = le32_to_cpu(val.u93_profiles_crc);
+ crc->u94_deltascalemap = le32_to_cpu(val.u94_deltascalemap_crc);
+ }
+
+ return 0;
+}
+
+static bool axiom_u42_touch_enabled(struct axiom_data *ts, const u8 *buf,
+ unsigned int touch_num)
+{
+ switch (axiom_usage_rev(ts, AXIOM_U42)) {
+ case 1:
+ return buf[AXIOM_U42_REV1_REPORT_ID_CONTAINS(touch_num)] ==
+ AXIOM_U42_REV1_REPORT_ID_TOUCH;
+ case 4:
+ return buf[AXIOM_U42_REV4_REPORT_ID_CONTAINS(touch_num)] ==
+ AXIOM_U42_REV4_REPORT_ID_TOUCH;
+ default:
+ /* Should never happen */
+ return false;
+ }
+}
+
+static void axiom_u42_get_touchslots(struct axiom_data *ts)
+{
+ u8 *buf __free(kfree) = NULL;
+ struct device *dev = ts->dev;
+ unsigned int bufsize;
+ unsigned int reg;
+ int ret, i;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U42)) {
+ dev_warn(dev, "Use default touchslots num\n");
+ goto fallback;
+ }
+
+ bufsize = axiom_usage_size(ts, AXIOM_U42);
+ buf = kzalloc(bufsize, GFP_KERNEL);
+ if (!buf) {
+ dev_warn(dev, "Failed to alloc u42 read buffer, use default value\n");
+ goto fallback;
+ }
+
+ reg = axiom_usage_baseaddr(ts, AXIOM_U42);
+ ret = regmap_raw_read(ts->regmap, reg, buf, bufsize);
+ if (ret) {
+ dev_warn(dev, "Failed to read u42, use default value\n");
+ goto fallback;
+ }
+
+ ts->enabled_slots = 0;
+ ts->num_slots = 0;
+
+ for (i = 0; i < AXIOM_MAX_TOUCHSLOTS; i++) {
+ if (axiom_u42_touch_enabled(ts, buf, i)) {
+ ts->enabled_slots |= BIT(i);
+ ts->num_slots++;
+ }
+ }
+
+ return;
+
+fallback:
+ ts->enabled_slots = AXIOM_MAX_TOUCHSLOTS_MASK;
+ ts->num_slots = AXIOM_MAX_TOUCHSLOTS;
+}
+
+static void axiom_u64_cds_enabled(struct axiom_data *ts)
+{
+ unsigned int reg, val;
+ int ret;
+
+ if (!axiom_driver_supports_usage(ts, AXIOM_U64))
+ goto fallback_out;
+
+ reg = axiom_usage_baseaddr(ts, AXIOM_U64);
+ reg += AXIOM_U64_REV2_ENABLECDSPROCESSING_REG;
+
+ ret = regmap_read(ts->regmap, reg, &val);
+ if (ret)
+ goto fallback_out;
+
+ val = FIELD_GET(AXIOM_U64_REV2_ENABLECDSPROCESSING_MASK, val);
+ ts->cds_enabled = val ? true : false;
+
+ return;
+
+fallback_out:
+ ts->cds_enabled = false;
+}
+
+static int axiom_cdu_wait_idle(struct axiom_data *ts, u8 cdu_usage_num)
+{
+ unsigned int reg;
+ int ret, _ret;
+ u16 cmd;
+
+ reg = axiom_usage_baseaddr(ts, cdu_usage_num);
+
+ /*
+ * Missing regmap_raw_read_poll_timeout for now. RESP_SUCCESS means that
+ * the last command successfully completed and the device is idle.
+ */
+ ret = read_poll_timeout(regmap_raw_read, _ret,
+ _ret || cmd == AXIOM_CDU_RESP_SUCCESS,
+ 10 * USEC_PER_MSEC, 1 * USEC_PER_SEC, false,
+ ts->regmap, reg, &cmd, 2);
+ if (ret)
+ dev_err(ts->dev, "Poll CDU u%02X timedout with: %#x\n",
+ cdu_usage_num, cmd);
+
+ return ret;
+}
+
+/*********************** Report usage handling ********************************/
+
+static int axiom_process_report(struct axiom_data *ts, unsigned char usage_num,
+ const u8 *buf, size_t buflen)
+{
+ struct axiom_usage_table_entry *entry = &ts->usage_table[usage_num];
+
+ /* Skip processing if not in TCP mode */
+ if ((axiom_get_runmode(ts) != AXIOM_TCP_MODE) &&
+ (axiom_get_runmode(ts) != AXIOM_TCP_CFG_UPDATE_MODE))
+ return 0;
+
+ /* May happen if an unsupported usage was requested */
+ if (!entry) {
+ dev_info(ts->dev, "Unsupported usage U%x request\n", usage_num);
+ return 0;
+ }
+
+ /* Supported report usages need to have a process_report hook */
+ if (!entry->info || !entry->info->process_report)
+ return -EINVAL;
+
+ return entry->info->process_report(ts, buf, buflen);
+}
+
+/* Make use of datasheet method 1 - single transfer read */
+static int
+axiom_u34_rev1_process_report(struct axiom_data *ts, const u8 *_buf, size_t bufsize)
+{
+ unsigned int reg = axiom_usage_baseaddr(ts, AXIOM_U34);
+ struct regmap *regmap = ts->regmap;
+ u8 buf[AXIOM_PAGE_BYTE_LEN] = { };
+ struct device *dev = ts->dev;
+ unsigned char report_usage;
+ u16 crc_report, crc_calc;
+ unsigned int len;
+ u8 *payload;
+ int ret;
+
+ ret = regmap_raw_read(regmap, reg, buf, ts->max_report_byte_len);
+ if (ret)
+ return ret;
+
+ /* TODO: Add overflow statistics */
+
+ /* REPORTLENGTH is in uint16 */
+ len = FIELD_GET(AXIOM_U34_REV1_REPORTLENGTH_MASK, buf[0]);
+ len *= 2;
+
+ /*
+ * Downstream ignores zero length reports, extend the check to validate
+ * the upper bound too.
+ */
+ if (len == 0 || len > AXIOM_PAGE_BYTE_LEN) {
+ dev_dbg_ratelimited(dev, "Invalid report length: %u\n", len);
+ return -EINVAL;
+ }
+
+ /*
+ * The CRC16 value can be queried at the last two bytes of the report.
+ * The value itself is covering the complete report excluding the CRC16
+ * value at the end.
+ */
+ crc_report = get_unaligned_le16(&buf[len - 2]);
+ crc_calc = crc16(0, buf, (len - 2));
+
+ if (crc_calc != crc_report) {
+ dev_err_ratelimited(dev, "CRC16 mismatch!\n");
+ return -EINVAL;
+ }
+
+ report_usage = buf[1];
+ payload = &buf[AXIOM_U34_REV1_PREAMBLE_BYTES];
+ len -= AXIOM_U34_REV1_PREAMBLE_BYTES - AXIOM_U34_REV1_POSTAMBLE_BYTES;
+
+ switch (report_usage) {
+ case AXIOM_U01:
+ case AXIOM_U41:
+ return axiom_process_report(ts, report_usage, payload, len);
+ default:
+ dev_dbg(dev, "Unsupported report u%02X received\n",
+ report_usage);
+ }
+
+ return 0;
+}
+
+static void
+axiom_u41_rev2_decode_target(const u8 *buf, u8 id, u16 *x, u16 *y, s8 *z)
+{
+ u16 val;
+
+ val = get_unaligned_le16(&buf[AXIOM_U41_REV2_X_REG(id)]);
+ val &= AXIOM_MAX_XY;
+ *x = val;
+
+ val = get_unaligned_le16(&buf[AXIOM_U41_REV2_Y_REG(id)]);
+ val &= AXIOM_MAX_XY;
+ *y = val;
+
+ *z = buf[AXIOM_U41_REV2_Z_REG(id)];
+}
+
+static int
+axiom_u41_rev2_process_report(struct axiom_data *ts, const u8 *buf, size_t bufsize)
+{
+ struct input_dev *input = ts->input;
+ unsigned char id;
+ u16 targets;
+
+ /*
+ * The input registration can be postponed but the touchscreen FW is
+ * sending u41 reports regardless.
+ */
+ if (!input)
+ return 0;
+
+ targets = get_unaligned_le16(&buf[AXIOM_U41_REV2_TARGETSTATUS_REG]);
+
+ for_each_set_bit(id, &ts->enabled_slots, AXIOM_MAX_TOUCHSLOTS) {
+ bool present;
+ u16 x, y;
+ s8 z;
+
+ axiom_u41_rev2_decode_target(buf, id, &x, &y, &z);
+
+ present = targets & BIT(id);
+ /* Ignore possible jitters */
+ if (z == AXIOM_PROX_LEVEL)
+ present = false;
+
+ dev_dbg(ts->dev, "id:%u x:%u y:%u z:%d present:%u",
+ id, x, y, z, present);
+
+ input_mt_slot(input, id);
+ if (input_mt_report_slot_state(input, MT_TOOL_FINGER, present))
+ touchscreen_report_pos(input, &ts->prop, x, y, true);
+
+ if (!present)
+ continue;
+
+ input_report_abs(input, ABS_MT_DISTANCE, z < 0 ? -z : 0);
+ if (ts->cds_enabled)
+ input_report_abs(input, ABS_MT_PRESSURE, z >= 0 ? z : 0);
+ }
+
+ input_sync(input);
+
+ return 0;
+}
+
+static int
+axiom_u01_rev1_process_report(struct axiom_data *ts, const u8 *buf, size_t bufsize)
+{
+ switch (buf[AXIOM_U01_REV1_REPORTTYPE_REG]) {
+ case AXIOM_U01_REV1_REPORTTYPE_HELLO:
+ dev_dbg(ts->dev, "u01 HELLO received\n");
+ axiom_complete(ts, &ts->boot_complete);
+ return 0;
+ case AXIOM_U01_REV1_REPORTTYPE_HEARTBEAT:
+ dev_dbg_ratelimited(ts->dev, "u01 HEARTBEAT received\n");
+ return 0;
+ case AXIOM_U01_REV1_REPORTTYPE_OPCOMPLETE:
+ dev_dbg(ts->dev, "u01 OPCOMPLETE received\n");
+ axiom_u02_handshakenvm(ts);
+ axiom_complete(ts, &ts->nvm_write);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+/**************************** Regmap handling *********************************/
+
+#define AXIOM_CMD_HDR_DIR_MASK BIT(15)
+#define AXIOM_CMD_HDR_READ 1
+#define AXIOM_CMD_HDR_WRITE 0
+#define AXIOM_CMD_HDR_LEN_MASK GENMASK(14, 0)
+
+struct axiom_cmd_header {
+ __le16 target_address;
+ __le16 xferlen;
+};
+
+/* Custom regmap read/write handling is required due to the aXiom protocol */
+static int axiom_regmap_read(void *context, const void *reg_buf, size_t reg_size,
+ void *val_buf, size_t val_size)
+{
+ struct device *dev = context;
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct axiom_data *ts = i2c_get_clientdata(i2c);
+ struct axiom_cmd_header hdr;
+ u16 xferlen, addr, baseaddr;
+ struct i2c_msg xfer[2];
+ int ret;
+
+ if (val_size > AXIOM_MAX_XFERLEN) {
+ dev_err(ts->dev, "Exceed max xferlen: %zu > %u\n",
+ val_size, AXIOM_MAX_XFERLEN);
+ return -EINVAL;
+ }
+
+ addr = *((u16 *)reg_buf);
+ hdr.target_address = cpu_to_le16(addr);
+ xferlen = FIELD_PREP(AXIOM_CMD_HDR_DIR_MASK, AXIOM_CMD_HDR_READ) |
+ FIELD_PREP(AXIOM_CMD_HDR_LEN_MASK, val_size);
+ hdr.xferlen = cpu_to_le16(xferlen);
+
+ /* Verify that usage including the usage rev is supported */
+ baseaddr = addr & AXIOM_USAGE_BASEADDR_MASK;
+ if (!axiom_usage_supported(ts, baseaddr))
+ return -EINVAL;
+
+ xfer[0].addr = i2c->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = sizeof(hdr);
+ xfer[0].buf = (u8 *)&hdr;
+
+ xfer[1].addr = i2c->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = val_size;
+ xfer[1].buf = val_buf;
+
+ ret = i2c_transfer(i2c->adapter, xfer, 2);
+ if (ret == 2)
+ return 0;
+ else if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+}
+
+static int axiom_regmap_write(void *context, const void *data, size_t count)
+{
+ struct device *dev = context;
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct axiom_data *ts = i2c_get_clientdata(i2c);
+ char *buf __free(kfree) = NULL;
+ struct axiom_cmd_header hdr;
+ u16 xferlen, addr, baseaddr;
+ size_t val_size, msg_size;
+ int ret;
+
+ val_size = count - sizeof(addr);
+ if (val_size > AXIOM_MAX_XFERLEN) {
+ dev_err(ts->dev, "Exceed max xferlen: %zu > %u\n",
+ val_size, AXIOM_MAX_XFERLEN);
+ return -EINVAL;
+ }
+
+ addr = *((u16 *)data);
+ hdr.target_address = cpu_to_le16(addr);
+ xferlen = FIELD_PREP(AXIOM_CMD_HDR_DIR_MASK, AXIOM_CMD_HDR_WRITE) |
+ FIELD_PREP(AXIOM_CMD_HDR_LEN_MASK, val_size);
+ hdr.xferlen = cpu_to_le16(xferlen);
+
+ /* Verify that usage including the usage rev is supported */
+ baseaddr = addr & AXIOM_USAGE_BASEADDR_MASK;
+ if (!axiom_usage_supported(ts, baseaddr))
+ return -EINVAL;
+
+ msg_size = sizeof(hdr) + val_size;
+ buf = kzalloc(msg_size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ memcpy(buf, &hdr, sizeof(hdr));
+ memcpy(&buf[sizeof(hdr)], &((char *)data)[2], val_size);
+
+ ret = i2c_master_send(i2c, buf, msg_size);
+
+ return ret == msg_size ? 0 : ret;
+}
+
+static const struct regmap_config axiom_i2c_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .read = axiom_regmap_read,
+ .write = axiom_regmap_write,
+};
+
+/************************ FW update handling **********************************/
+
+static int axiom_update_input_dev(struct axiom_data *ts);
+
+static enum fw_upload_err
+axiom_axfw_fw_prepare(struct fw_upload *fw_upload, const u8 *data, u32 size)
+{
+ struct axiom_data *ts = fw_upload->dd_handle;
+ struct axiom_firmware *afw = &ts->fw[AXIOM_FW_AXFW];
+ u8 major_ver, minor_ver, rc_ver, status, variant;
+ u32 fw_file_crc32, crc32_calc;
+ struct device *dev = ts->dev;
+ unsigned int signature_len;
+ enum fw_upload_err ret;
+ u16 fw_file_format_ver;
+ u16 fw_file_device_id;
+
+ mutex_lock(&afw->lock);
+ afw->cancel = false;
+ mutex_unlock(&afw->lock);
+
+ mutex_lock(&ts->fwupdate_lock);
+
+ if (size < sizeof(struct axiom_fw_axfw_hdr)) {
+ dev_err(dev, "Invalid AXFW file size\n");
+ ret = FW_UPLOAD_ERR_INVALID_SIZE;
+ goto out;
+ }
+
+ signature_len = strlen(AXIOM_FW_AXFW_SIGNATURE);
+ if (strncmp(data, AXIOM_FW_AXFW_SIGNATURE, signature_len)) {
+ /*
+ * AXFW has a header which can be used to perform validations,
+ * ALC don't. Therefore the AXFW format is preferred.
+ */
+ dev_warn(dev, "No AXFW signature, assume ALC firmware\n");
+ ret = FW_UPLOAD_ERR_NONE;
+ goto out;
+ }
+
+ fw_file_crc32 = get_unaligned_le32(&data[signature_len]);
+ crc32_calc = crc32(~0, &data[8], size - 8) ^ 0xffffffff;
+ if (fw_file_crc32 != crc32_calc) {
+ dev_err(dev, "AXFW CRC32 doesn't match (fw:%#x calc:%#x)\n",
+ fw_file_crc32, crc32_calc);
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto out;
+ }
+
+ data += signature_len + sizeof(fw_file_crc32);
+ fw_file_format_ver = get_unaligned_le16(data);
+ if (fw_file_format_ver != AXIOM_FW_AXFW_FILE_FMT_VER) {
+ dev_err(dev, "Invalid AXFW file format version: %04x",
+ fw_file_format_ver);
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto out;
+ }
+
+ data += sizeof(fw_file_format_ver);
+ fw_file_device_id = get_unaligned_le16(data);
+ if (fw_file_device_id != ts->device_id) {
+ dev_err(dev, "Invalid AXFW target device (fw:%#04x dev:%#04x)\n",
+ fw_file_device_id, ts->device_id);
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto out;
+ }
+
+ /*
+ * This can happen if:
+ * * the device came up in bootloader mode, or
+ * * downloading the firmware failed in between, or
+ * * the following usage discovery failed.
+ *
+ * All cases are crcitical and we need to use any firmware to
+ * bring the device back into a working state which is supported by the
+ * host.
+ */
+ if (axiom_get_runmode(ts) != AXIOM_TCP_MODE)
+ return FW_UPLOAD_ERR_NONE;
+
+ data += sizeof(fw_file_device_id);
+ variant = *data++;
+ minor_ver = *data++;
+ major_ver = *data++;
+ rc_ver = *data++;
+ status = *data++;
+
+ if (major_ver == ts->fw_major && minor_ver == ts->fw_minor &&
+ rc_ver == ts->fw_rc && status == ts->fw_status &&
+ variant == ts->fw_variant) {
+ ret = FW_UPLOAD_ERR_DUPLICATE;
+ goto out;
+ }
+
+ dev_info(dev, "Detected AXFW %02u.%02u.%02u (%s)\n",
+ major_ver, minor_ver, rc_ver,
+ status ? "production" : "engineering");
+
+ mutex_lock(&afw->lock);
+ ret = afw->cancel ? FW_UPLOAD_ERR_CANCELED : FW_UPLOAD_ERR_NONE;
+ mutex_unlock(&afw->lock);
+
+out:
+ /*
+ * In FW_UPLOAD_ERR_NONE case the complete handler will release the
+ * lock.
+ */
+ if (ret != FW_UPLOAD_ERR_NONE)
+ mutex_unlock(&ts->fwupdate_lock);
+
+ return ret;
+}
+
+static int axiom_enter_bootloader_mode(struct axiom_data *ts)
+{
+ struct device *dev = ts->dev;
+ int ret;
+
+ axiom_set_runmode(ts, AXIOM_BLP_PRE_MODE);
+
+ ret = axiom_u02_wait_idle(ts);
+ if (ret)
+ goto err_out;
+
+ ret = axiom_u02_enter_bootloader(ts);
+ if (ret) {
+ dev_err(dev, "Failed to enter bootloader mode\n");
+ goto err_out;
+ }
+
+ axiom_set_runmode(ts, AXIOM_BLP_MODE);
+
+ return 0;
+
+err_out:
+ axiom_set_runmode(ts, AXIOM_TCP_MODE);
+
+ return ret;
+}
+
+static int axoim_blp_wait_ready(struct axiom_data *ts)
+{
+ struct device *dev = ts->dev;
+ unsigned int reg;
+ int tmp, ret;
+ u8 buf[4];
+
+ reg = AXIOM_U01_BLP_SATUS_REG;
+
+ /* BLP busy poll requires to read 4 bytes! */
+ ret = read_poll_timeout(regmap_raw_read, tmp,
+ tmp || !(buf[2] & AXIOM_U01_BLP_STATUS_BUSY),
+ 10 * USEC_PER_MSEC, 5 * USEC_PER_SEC, false,
+ ts->regmap, reg, &buf, 4);
+ if (ret)
+ dev_err(dev, "Bootloader wait processing packets failed %d\n", ret);
+
+ return ret;
+}
+
+static int
+axiom_blp_write_chunk(struct axiom_data *ts, const u8 *data, u16 length)
+{
+ unsigned int chunk_size = AXIOM_U01_BLP_FIFO_CHK_SIZE_BYTES;
+ unsigned int reg = AXIOM_U01_BLP_FIFO_REG;
+ struct device *dev = ts->dev;
+ unsigned int pos = 0;
+ int ret;
+
+ ret = axoim_blp_wait_ready(ts);
+ if (ret)
+ return ret;
+
+ /*
+ * TODO: Downstream does this chunk transfers. Verify if this is
+ * required if one fw-chunk <= AXIOM_MAX_XFERLEN
+ */
+ while (pos < length) {
+ u16 len;
+
+ len = chunk_size;
+ if ((pos + chunk_size) > length)
+ len = length - pos;
+
+ ret = regmap_raw_write(ts->regmap, reg, &data[pos], len);
+ if (ret) {
+ dev_err(dev, "Bootloader download AXFW chunk failed %d\n", ret);
+ return ret;
+ }
+
+ pos += len;
+ ret = axoim_blp_wait_ready(ts);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int axiom_blp_reset(struct axiom_data *ts)
+{
+ __le16 reset_cmd = cpu_to_le16(AXIOM_U01_BLP_COMMAND_RESET);
+ unsigned int reg = AXIOM_U01_BLP_COMMAND_REG;
+ struct device *dev = ts->dev;
+ unsigned int attempts = 20;
+ unsigned int mode;
+ int ret;
+
+ ret = axoim_blp_wait_ready(ts);
+ if (ret)
+ return ret;
+
+ /*
+ * For some reason this write fail with -ENXIO. Skip checking the return
+ * code (which is also done by the downstream axfw.py tool and poll u31
+ * instead.
+ */
+ regmap_raw_write(ts->regmap, reg, &reset_cmd, sizeof(reset_cmd));
+
+ do {
+ ret = regmap_read(ts->regmap, AXIOM_U31_REV1_DEVICE_ID_HIGH_REG,
+ &mode);
+ if (!ret)
+ break;
+
+ fsleep(250 * USEC_PER_MSEC);
+ } while (attempts--);
+
+ if (ret) {
+ dev_err(dev, "Failed to read MODE after BLP reset: %d\n", ret);
+ return ret;
+ }
+
+ mode = FIELD_GET(AXIOM_U31_REV1_MODE_MASK, mode);
+ if (mode == AXIOM_U31_REV1_MODE_BLP) {
+ dev_err(dev, "Device still in BLP mode, abort\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void axiom_lock_input_device(struct axiom_data *ts)
+{
+ if (!ts->input)
+ return;
+
+ mutex_lock(&ts->input->mutex);
+}
+
+static void axiom_unlock_input_device(struct axiom_data *ts)
+{
+ if (!ts->input)
+ return;
+
+ mutex_unlock(&ts->input->mutex);
+}
+
+static void axiom_unregister_input_dev(struct axiom_data *ts)
+{
+ if (ts->input)
+ input_unregister_device(ts->input);
+
+ ts->input = NULL;
+}
+
+static enum fw_upload_err
+axiom_axfw_fw_write(struct fw_upload *fw_upload, const u8 *data, u32 offset,
+ u32 size, u32 *written)
+{
+ struct axiom_data *ts = fw_upload->dd_handle;
+ struct axiom_firmware *afw = &ts->fw[AXIOM_FW_AXFW];
+ struct device *dev = ts->dev;
+ bool cancel;
+ int ret;
+
+ /* Done before cancel check due to cleanup based put */
+ ret = pm_runtime_resume_and_get(ts->dev);
+ if (ret)
+ return FW_UPLOAD_ERR_HW_ERROR;
+
+ mutex_lock(&afw->lock);
+ cancel = afw->cancel;
+ mutex_unlock(&afw->lock);
+
+ if (cancel)
+ return FW_UPLOAD_ERR_CANCELED;
+
+ axiom_lock_input_device(ts);
+
+ if (ts->input && input_device_enabled(ts->input)) {
+ dev_err(dev, "Input device not idle, abort AXFW/ALC update\n");
+ goto err;
+ }
+
+ if (!strncmp(data, AXIOM_FW_AXFW_SIGNATURE,
+ strlen(AXIOM_FW_AXFW_SIGNATURE))) {
+ /* Set the pointer to the first fw chunk */
+ data += sizeof(struct axiom_fw_axfw_hdr);
+ size -= sizeof(struct axiom_fw_axfw_hdr);
+ *written += sizeof(struct axiom_fw_axfw_hdr);
+ }
+
+ if (axiom_enter_bootloader_mode(ts))
+ goto err;
+
+ while (size) {
+ u16 chunk_len, len;
+
+ chunk_len = get_unaligned_be16(&data[6]);
+ len = chunk_len + sizeof(struct axiom_fw_axfw_chunk_hdr);
+
+ /*
+ * The bootlaoder FW can handle the complete chunk incl. the
+ * header.
+ */
+ ret = axiom_blp_write_chunk(ts, data, len);
+ if (ret)
+ goto err;
+
+ size -= len;
+ *written += len;
+ data += len;
+ }
+
+ ret = axiom_blp_reset(ts);
+ if (ret)
+ dev_warn(dev, "BLP reset failed\n");
+
+ ret = axiom_u31_device_discover(ts);
+ if (ret) {
+ /*
+ * This is critical and we need to avoid that the user-space can
+ * still use the input-dev.
+ */
+ axiom_unlock_input_device(ts);
+ axiom_unregister_input_dev(ts);
+ dev_err(dev, "Device discovery failed after AXFW/ALC firmware update\n");
+ goto err;
+ }
+
+ /* Unlock before the input device gets unregistered */
+ axiom_unlock_input_device(ts);
+
+ ret = axiom_update_input_dev(ts);
+ if (ret) {
+ dev_err(dev, "Input device update failed after AXFW/ALC firmware update\n");
+ return FW_UPLOAD_ERR_HW_ERROR;
+ }
+
+ dev_info(dev, "AXFW update successful\n");
+
+ return FW_UPLOAD_ERR_NONE;
+
+err:
+ axiom_unlock_input_device(ts);
+ return FW_UPLOAD_ERR_HW_ERROR;
+}
+
+static enum fw_upload_err axiom_fw_poll_complete(struct fw_upload *fw_upload)
+{
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static void axiom_axfw_fw_cancel(struct fw_upload *fw_upload)
+{
+ struct axiom_data *ts = fw_upload->dd_handle;
+ struct axiom_firmware *afw = &ts->fw[AXIOM_FW_AXFW];
+
+ mutex_lock(&afw->lock);
+ afw->cancel = true;
+ mutex_unlock(&afw->lock);
+}
+
+static void axiom_axfw_fw_cleanup(struct fw_upload *fw_upload)
+{
+ struct axiom_data *ts = fw_upload->dd_handle;
+
+ mutex_unlock(&ts->fwupdate_lock);
+ pm_runtime_mark_last_busy(ts->dev);
+ pm_runtime_put_sync_autosuspend(ts->dev);
+}
+
+static const struct fw_upload_ops axiom_axfw_fw_upload_ops = {
+ .prepare = axiom_axfw_fw_prepare,
+ .write = axiom_axfw_fw_write,
+ .poll_complete = axiom_fw_poll_complete,
+ .cancel = axiom_axfw_fw_cancel,
+ .cleanup = axiom_axfw_fw_cleanup,
+};
+
+static int
+axiom_set_new_crcs(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *cfg)
+{
+ struct axiom_crc *crc = &ts->crc[AXIOM_CRC_NEW];
+ const u32 *u33_data = (const u32 *)cfg->usage_content;
+
+ if (cfg->usage_rev != 2 && cfg->usage_rev != 3) {
+ dev_err(ts->dev, "The driver doesn't support u33 revision %u\n",
+ cfg->usage_rev);
+ return -EINVAL;
+ }
+
+ crc->runtime = get_unaligned_le32(u33_data);
+ crc->nvltlusageconfig = get_unaligned_le32(&u33_data[3]);
+ crc->vltusageconfig = get_unaligned_le32(&u33_data[4]);
+ crc->u22_sequencedata = get_unaligned_le32(&u33_data[5]);
+ crc->u43_hotspots = get_unaligned_le32(&u33_data[6]);
+ if (cfg->usage_rev == 2) {
+ crc->u93_profiles = get_unaligned_le32(&u33_data[7]);
+ crc->u94_deltascalemap = get_unaligned_le32(&u33_data[8]);
+ } else if (cfg->usage_rev == 3) {
+ crc->u77_dod_data = get_unaligned_le32(&u33_data[7]);
+ crc->u93_profiles = get_unaligned_le32(&u33_data[8]);
+ crc->u94_deltascalemap = get_unaligned_le32(&u33_data[9]);
+ }
+
+ return 0;
+}
+
+static unsigned int
+axiom_cfg_fw_prepare_chunk(struct axiom_fw_cfg_chunk *chunk, const u8 *data)
+{
+ chunk->usage_num = data[0];
+ chunk->usage_rev = data[1];
+ chunk->usage_length = get_unaligned_le16(&data[3]);
+ chunk->usage_content = &data[5];
+
+ return chunk->usage_length + sizeof(struct axiom_fw_cfg_chunk_hdr);
+}
+
+/*
+ * To overcome buggy firmware we need to check if a given usage is used by the
+ * current running firmware. Return true if the usage is unused/not populated
+ * by the firmware since we can't perform the actual check.
+ */
+#define axiom_usage_crc_match(_ts, _usage_num, _cur, _new, _field) \
+ (!_ts->usage_table[_usage_num].populated || (_cur->_field == _new->_field))
+
+static bool axiom_cfg_fw_update_required(struct axiom_data *ts)
+{
+ struct axiom_crc *cur, *new;
+
+ cur = &ts->crc[AXIOM_CRC_CUR];
+ new = &ts->crc[AXIOM_CRC_NEW];
+
+ if (cur->nvltlusageconfig != new->nvltlusageconfig ||
+ !axiom_usage_crc_match(ts, AXIOM_U22, cur, new, u22_sequencedata) ||
+ !axiom_usage_crc_match(ts, AXIOM_U43, cur, new, u43_hotspots) ||
+ !axiom_usage_crc_match(ts, AXIOM_U93, cur, new, u93_profiles) ||
+ !axiom_usage_crc_match(ts, AXIOM_U94, cur, new, u94_deltascalemap))
+ return true;
+
+ return false;
+}
+
+static enum fw_upload_err
+axiom_cfg_fw_prepare(struct fw_upload *fw_upload, const u8 *data, u32 size)
+{
+ struct axiom_data *ts = fw_upload->dd_handle;
+ struct axiom_firmware *afw = &ts->fw[AXIOM_FW_CFG];
+ u32 cur_runtime_crc, fw_runtime_crc;
+ struct axiom_fw_cfg_chunk chunk;
+ struct device *dev = ts->dev;
+ enum fw_upload_err ret;
+ u32 signature;
+
+ mutex_lock(&afw->lock);
+ afw->cancel = false;
+ mutex_unlock(&afw->lock);
+
+ mutex_lock(&ts->fwupdate_lock);
+
+ if (axiom_get_runmode(ts) != AXIOM_TCP_MODE) {
+ dev_err(dev, "Device not in TCP mode, abort TH2CFG update\n");
+ ret = FW_UPLOAD_ERR_HW_ERROR;
+ goto out;
+ }
+
+ if (size < sizeof(struct axiom_fw_cfg_hdr)) {
+ dev_err(dev, "Invalid TH2CFG file size\n");
+ ret = FW_UPLOAD_ERR_INVALID_SIZE;
+ goto out;
+ }
+
+ signature = get_unaligned_be32(data);
+ if (signature != AXIOM_FW_CFG_SIGNATURE) {
+ dev_err(dev, "Invalid TH2CFG signature\n");
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto out;
+ }
+
+ /* Skip to the first fw chunk */
+ data += sizeof(struct axiom_fw_cfg_hdr);
+ size -= sizeof(struct axiom_fw_cfg_hdr);
+
+ /*
+ * Search for u33 which contains the CRC information and perform only
+ * the runtime-crc check.
+ */
+ while (size) {
+ unsigned int chunk_len;
+
+ chunk_len = axiom_cfg_fw_prepare_chunk(&chunk, data);
+ if (chunk.usage_num == AXIOM_U33)
+ break;
+
+ data += chunk_len;
+ size -= chunk_len;
+ }
+
+ if (size == 0) {
+ dev_err(dev, "Failed to find the u33 entry in TH2CFG\n");
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto out;
+ }
+
+ ret = axiom_set_new_crcs(ts, &chunk);
+ if (ret) {
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto out;
+ }
+
+ /*
+ * Nothing to do if the CRCs are the same. TODO: Must be extended once
+ * the CDU update is added.
+ */
+ if (!axiom_cfg_fw_update_required(ts)) {
+ ret = FW_UPLOAD_ERR_DUPLICATE;
+ goto out;
+ }
+
+ cur_runtime_crc = ts->crc[AXIOM_CRC_CUR].runtime;
+ fw_runtime_crc = ts->crc[AXIOM_CRC_NEW].runtime;
+ if (cur_runtime_crc != fw_runtime_crc) {
+ dev_err(dev, "TH2CFG and device runtime CRC doesn't match: %#x != %#x\n",
+ fw_runtime_crc, cur_runtime_crc);
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto out;
+ }
+
+ mutex_lock(&afw->lock);
+ ret = afw->cancel ? FW_UPLOAD_ERR_CANCELED : FW_UPLOAD_ERR_NONE;
+ mutex_unlock(&afw->lock);
+
+out:
+ /*
+ * In FW_UPLOAD_ERR_NONE case the complete handler will release the
+ * lock.
+ */
+ if (ret != FW_UPLOAD_ERR_NONE)
+ mutex_unlock(&ts->fwupdate_lock);
+
+ return ret;
+}
+
+static int axiom_zero_volatile_mem(struct axiom_data *ts)
+{
+ int ret, size;
+ u8 *buf;
+
+ /* Zero out the volatile memory except for the user content in u04 */
+ ret = axiom_u04_get(ts, &buf);
+ if (ret < 0)
+ return ret;
+ size = ret;
+
+ ret = axiom_u02_fillconfig(ts);
+ if (ret)
+ goto out;
+
+ ret = axiom_u04_set(ts, buf, size);
+out:
+ kfree(buf);
+ return ret;
+}
+
+static bool
+axiom_skip_cfg_chunk(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *chunk)
+{
+ u8 usage_num = chunk->usage_num;
+
+ if (!ts->usage_table[usage_num].populated) {
+ dev_warn(ts->dev, "Unknown usage chunk for u%02X\n", usage_num);
+ return true;
+ }
+
+ /* Skip read-only usages */
+ if (ts->usage_table[usage_num].info &&
+ ts->usage_table[usage_num].info->is_ro)
+ return true;
+
+ return false;
+}
+
+static int
+axiom_write_cdu_usage(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *chunk)
+{
+ struct axiom_cdu_usage cdu = { };
+ struct device *dev = ts->dev;
+ unsigned int remaining;
+ unsigned int reg;
+ unsigned int pos;
+ int ret;
+
+ pos = 0;
+ remaining = chunk->usage_length;
+ cdu.command = cpu_to_le16(AXIOM_CDU_CMD_STORE);
+ reg = axiom_usage_baseaddr(ts, chunk->usage_num);
+
+ while (remaining) {
+ unsigned int size;
+
+ cdu.parameters[1] = cpu_to_le16(pos);
+
+ size = remaining;
+ if (size > AXIOM_CDU_MAX_DATA_BYTES)
+ size = AXIOM_CDU_MAX_DATA_BYTES;
+
+ memset(cdu.data, 0, sizeof(cdu.data));
+ memcpy(cdu.data, &chunk->usage_content[pos], size);
+
+ ret = regmap_raw_write(ts->regmap, reg, &cdu, sizeof(cdu));
+ if (ret) {
+ dev_err(dev, "Failed to write CDU u%02X\n",
+ chunk->usage_num);
+ return ret;
+ }
+
+ ret = axiom_cdu_wait_idle(ts, chunk->usage_num);
+ if (ret) {
+ dev_err(dev, "CDU write wait-idle failed\n");
+ return ret;
+ }
+
+ remaining -= size;
+ pos += size;
+ }
+
+ /*
+ * TODO: Check if we really need to send 48 zero bytes of data like
+ * downstream does.
+ */
+ memset(&cdu, 0, sizeof(cdu));
+ cdu.command = cpu_to_le16(AXIOM_CDU_CMD_COMMIT);
+ cdu.parameters[0] = cpu_to_le16(AXIOM_CDU_PARAM0_COMMIT);
+ cdu.parameters[1] = cpu_to_le16(AXIOM_CDU_PARAM1_COMMIT);
+
+ ret = regmap_raw_write(ts->regmap, reg, &cdu, sizeof(cdu));
+ if (ret) {
+ dev_err(dev, "Failed to commit CDU u%02X to NVM\n",
+ chunk->usage_num);
+ return ret;
+ }
+
+ ret = axiom_wait_for_completion_timeout(ts, &ts->nvm_write,
+ msecs_to_jiffies(5 * MSEC_PER_SEC));
+ if (!ret) {
+ dev_err(ts->dev, "Error CDU u%02X commit timedout\n",
+ chunk->usage_num);
+ return -ETIMEDOUT;
+ }
+
+ return axiom_cdu_wait_idle(ts, chunk->usage_num);
+}
+
+static int
+axiom_write_cfg_chunk(struct axiom_data *ts, const struct axiom_fw_cfg_chunk *chunk)
+{
+ unsigned int reg;
+ int ret;
+
+ if (ts->usage_table[chunk->usage_num].info &&
+ ts->usage_table[chunk->usage_num].info->is_cdu) {
+ ret = axiom_write_cdu_usage(ts, chunk);
+ if (ret)
+ return ret;
+ goto out;
+ }
+
+ reg = axiom_usage_baseaddr(ts, chunk->usage_num);
+ ret = regmap_raw_write(ts->regmap, reg, chunk->usage_content, chunk->usage_length);
+ if (ret)
+ return ret;
+
+out:
+ return axiom_u02_wait_idle(ts);
+}
+
+static int axiom_verify_volatile_mem(struct axiom_data *ts)
+{
+ int ret;
+
+ ret = axiom_u02_computecrc(ts);
+ if (ret)
+ return ret;
+
+ /* Query the new CRCs after they are re-computed */
+ ret = axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
+ if (ret)
+ return ret;
+
+ return ts->crc[AXIOM_CRC_CUR].vltusageconfig ==
+ ts->crc[AXIOM_CRC_NEW].vltusageconfig ? 0 : -EINVAL;
+}
+
+static int axiom_verify_crcs(struct axiom_data *ts)
+{
+ struct device *dev = ts->dev;
+ struct axiom_crc *cur, *new;
+
+ cur = &ts->crc[AXIOM_CRC_CUR];
+ new = &ts->crc[AXIOM_CRC_NEW];
+
+ if (new->vltusageconfig != cur->vltusageconfig) {
+ dev_err(dev, "VLTUSAGECONFIG CRC32 mismatch (dev:%#x != fw:%#x)\n",
+ cur->vltusageconfig, new->vltusageconfig);
+ return -EINVAL;
+ } else if (new->nvltlusageconfig != cur->nvltlusageconfig) {
+ dev_err(dev, "NVLTUSAGECONFIG CRC32 mismatch (dev:%#x != fw:%#x)\n",
+ cur->nvltlusageconfig, new->nvltlusageconfig);
+ return -EINVAL;
+ } else if (!axiom_usage_crc_match(ts, AXIOM_U22, cur, new, u22_sequencedata)) {
+ dev_err(dev, "U22_SEQUENCEDATA CRC32 mismatch (dev:%#x != fw:%#x)\n",
+ cur->u22_sequencedata, new->u22_sequencedata);
+ return -EINVAL;
+ } else if (!axiom_usage_crc_match(ts, AXIOM_U43, cur, new, u43_hotspots)) {
+ dev_err(dev, "U43_HOTSPOTS CRC32 mismatch (dev:%#x != fw:%#x)\n",
+ cur->u43_hotspots, new->u43_hotspots);
+ return -EINVAL;
+ } else if (!axiom_usage_crc_match(ts, AXIOM_U93, cur, new, u93_profiles)) {
+ dev_err(dev, "U93_PROFILES CRC32 mismatch (dev:%#x != fw:%#x)\n",
+ cur->u93_profiles, new->u93_profiles);
+ return -EINVAL;
+ } else if (!axiom_usage_crc_match(ts, AXIOM_U94, cur, new, u94_deltascalemap)) {
+ dev_err(dev, "U94_DELTASCALEMAP CRC32 mismatch (dev:%#x != fw:%#x)\n",
+ cur->u94_deltascalemap, new->u94_deltascalemap);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum fw_upload_err
+axiom_cfg_fw_write(struct fw_upload *fw_upload, const u8 *data, u32 offset,
+ u32 size, u32 *written)
+{
+ struct axiom_data *ts = fw_upload->dd_handle;
+ struct axiom_firmware *afw = &ts->fw[AXIOM_FW_CFG];
+ struct device *dev = ts->dev;
+ bool cancel;
+ int ret;
+
+ /* Done before cancel check due to cleanup based put */
+ ret = pm_runtime_resume_and_get(ts->dev);
+ if (ret)
+ return FW_UPLOAD_ERR_HW_ERROR;
+
+ mutex_lock(&afw->lock);
+ cancel = afw->cancel;
+ mutex_unlock(&afw->lock);
+
+ if (cancel)
+ return FW_UPLOAD_ERR_CANCELED;
+
+ axiom_lock_input_device(ts);
+
+ if (ts->input && input_device_enabled(ts->input)) {
+ dev_err(dev, "Input device not idle, abort TH2CFG update\n");
+ axiom_unlock_input_device(ts);
+ return FW_UPLOAD_ERR_HW_ERROR;
+ }
+
+ ret = axiom_u02_stop(ts);
+ if (ret)
+ goto err_swreset;
+
+ ret = axiom_zero_volatile_mem(ts);
+ if (ret)
+ goto err_swreset;
+
+ /* Skip to the first fw chunk */
+ data += sizeof(struct axiom_fw_cfg_hdr);
+ size -= sizeof(struct axiom_fw_cfg_hdr);
+ *written += sizeof(struct axiom_fw_cfg_hdr);
+
+ axiom_set_runmode(ts, AXIOM_TCP_CFG_UPDATE_MODE);
+
+ while (size) {
+ struct axiom_fw_cfg_chunk chunk;
+ unsigned int chunk_len;
+
+ chunk_len = axiom_cfg_fw_prepare_chunk(&chunk, data);
+ if (axiom_skip_cfg_chunk(ts, &chunk)) {
+ dev_dbg(dev, "Skip TH2CFG usage u%02X\n", chunk.usage_num);
+ goto next_chunk;
+ }
+
+ ret = axiom_write_cfg_chunk(ts, &chunk);
+ if (ret) {
+ axiom_set_runmode(ts, AXIOM_TCP_MODE);
+ goto err_swreset;
+ }
+
+next_chunk:
+ data += chunk_len;
+ size -= chunk_len;
+ *written += chunk_len;
+ }
+
+ axiom_set_runmode(ts, AXIOM_TCP_MODE);
+
+ /* Ensure that the chunks are written correctly */
+ ret = axiom_verify_volatile_mem(ts);
+ if (ret) {
+ dev_err(dev, "Failed to verify written config, abort\n");
+ goto err_swreset;
+ }
+
+ ret = axiom_u02_save_config(ts);
+ if (ret)
+ goto err_swreset;
+
+ /*
+ * TODO: Check if u02 start would be sufficient to load the new config
+ * values
+ */
+ ret = axiom_u02_swreset(ts);
+ if (ret) {
+ dev_err(dev, "Soft reset failed\n");
+ goto err_unlock;
+ }
+
+ ret = axiom_u33_read(ts, &ts->crc[AXIOM_CRC_CUR]);
+ if (ret)
+ goto err_unlock;
+
+ if (axiom_verify_crcs(ts))
+ goto err_unlock;
+
+ /* Unlock before the input device gets unregistered */
+ axiom_unlock_input_device(ts);
+
+ ret = axiom_update_input_dev(ts);
+ if (ret) {
+ dev_err(dev, "Input device update failed after TH2CFG firmware update\n");
+ goto err_out;
+ }
+
+ dev_info(dev, "TH2CFG update successful\n");
+
+ return FW_UPLOAD_ERR_NONE;
+
+err_swreset:
+ axiom_u02_swreset(ts);
+err_unlock:
+ axiom_unlock_input_device(ts);
+err_out:
+ return ret == -ETIMEDOUT ? FW_UPLOAD_ERR_TIMEOUT : FW_UPLOAD_ERR_HW_ERROR;
+}
+
+static void axiom_cfg_fw_cancel(struct fw_upload *fw_upload)
+{
+ struct axiom_data *ts = fw_upload->dd_handle;
+ struct axiom_firmware *afw = &ts->fw[AXIOM_FW_CFG];
+
+ mutex_lock(&afw->lock);
+ afw->cancel = true;
+ mutex_unlock(&afw->lock);
+}
+
+static void axiom_cfg_fw_cleanup(struct fw_upload *fw_upload)
+{
+ struct axiom_data *ts = fw_upload->dd_handle;
+
+ mutex_unlock(&ts->fwupdate_lock);
+ pm_runtime_mark_last_busy(ts->dev);
+ pm_runtime_put_sync_autosuspend(ts->dev);
+}
+
+static const struct fw_upload_ops axiom_cfg_fw_upload_ops = {
+ .prepare = axiom_cfg_fw_prepare,
+ .write = axiom_cfg_fw_write,
+ .poll_complete = axiom_fw_poll_complete,
+ .cancel = axiom_cfg_fw_cancel,
+ .cleanup = axiom_cfg_fw_cleanup,
+};
+
+static void axiom_remove_axfw_fwl_action(void *data)
+{
+ struct axiom_data *ts = data;
+
+ firmware_upload_unregister(ts->fw[AXIOM_FW_AXFW].fwl);
+}
+
+static void axiom_remove_cfg_fwl_action(void *data)
+{
+ struct axiom_data *ts = data;
+
+ firmware_upload_unregister(ts->fw[AXIOM_FW_CFG].fwl);
+}
+
+static int axiom_register_fwl(struct axiom_data *ts)
+{
+ struct device *dev = ts->dev;
+ struct fw_upload *fwl;
+ char *fw_name;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_FW_UPLOAD)) {
+ dev_dbg(dev, "axfw and th2cfgbin update disabled\n");
+ return 0;
+ }
+
+ mutex_init(&ts->fw[AXIOM_FW_AXFW].lock);
+ fw_name = kasprintf(GFP_KERNEL, "i2c:%s.axfw", dev_name(dev));
+ fwl = firmware_upload_register(THIS_MODULE, ts->dev, fw_name,
+ &axiom_axfw_fw_upload_ops, ts);
+ kfree(fw_name);
+ if (IS_ERR(fwl))
+ return dev_err_probe(dev, PTR_ERR(fwl),
+ "Failed to register firmware upload\n");
+
+ ret = devm_add_action_or_reset(dev, axiom_remove_axfw_fwl_action, ts);
+ if (ret)
+ return ret;
+
+ ts->fw[AXIOM_FW_AXFW].fwl = fwl;
+
+ mutex_init(&ts->fw[AXIOM_FW_CFG].lock);
+ fw_name = kasprintf(GFP_KERNEL, "i2c:%s.th2cfgbin", dev_name(dev));
+ fwl = firmware_upload_register(THIS_MODULE, ts->dev, fw_name,
+ &axiom_cfg_fw_upload_ops, ts);
+ kfree(fw_name);
+ if (IS_ERR(fwl))
+ return dev_err_probe(dev, PTR_ERR(fwl),
+ "Failed to register cfg firmware upload\n");
+
+ ret = devm_add_action_or_reset(dev, axiom_remove_cfg_fwl_action, ts);
+ if (ret)
+ return ret;
+
+ ts->fw[AXIOM_FW_CFG].fwl = fwl;
+
+ return 0;
+}
+
+/************************* Device handlig *************************************/
+
+#define AXIOM_SIMPLE_FW_DEVICE_ATTR(attr) \
+ static ssize_t \
+ fw_ ## attr ## _show(struct device *dev, \
+ struct device_attribute *_attr, char *buf) \
+ { \
+ struct i2c_client *i2c = to_i2c_client(dev); \
+ struct axiom_data *ts = i2c_get_clientdata(i2c); \
+ \
+ return sysfs_emit(buf, "%u\n", ts->fw_##attr); \
+ } \
+ static DEVICE_ATTR_RO(fw_##attr)
+
+AXIOM_SIMPLE_FW_DEVICE_ATTR(major);
+AXIOM_SIMPLE_FW_DEVICE_ATTR(minor);
+AXIOM_SIMPLE_FW_DEVICE_ATTR(rc);
+
+static ssize_t fw_status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct axiom_data *ts = i2c_get_clientdata(i2c);
+ const char *val;
+
+ if (ts->fw_status)
+ val = "production";
+ else
+ val = "engineering";
+
+ return sysfs_emit(buf, "%s\n", val);
+}
+static DEVICE_ATTR_RO(fw_status);
+
+static ssize_t fw_variant_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct axiom_data *ts = i2c_get_clientdata(i2c);
+ const char *val;
+
+ switch (ts->fw_variant) {
+ case 0:
+ val = "3d";
+ break;
+ case 1:
+ val = "2d";
+ break;
+ case 3:
+ val = "force";
+ break;
+ default:
+ val = "unknown";
+ break;
+ }
+
+ return sysfs_emit(buf, "%s\n", val);
+}
+static DEVICE_ATTR_RO(fw_variant);
+
+static ssize_t device_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct axiom_data *ts = i2c_get_clientdata(i2c);
+
+ return sysfs_emit(buf, "%u\n", ts->device_id);
+}
+static DEVICE_ATTR_RO(device_id);
+
+static ssize_t device_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct axiom_data *ts = i2c_get_clientdata(i2c);
+
+ return sysfs_emit(buf, "%s\n", axiom_runmode_to_string(ts));
+}
+static DEVICE_ATTR_RO(device_state);
+
+static struct attribute *axiom_attrs[] = {
+ &dev_attr_fw_major.attr,
+ &dev_attr_fw_minor.attr,
+ &dev_attr_fw_rc.attr,
+ &dev_attr_fw_status.attr,
+ &dev_attr_fw_variant.attr,
+ &dev_attr_device_id.attr,
+ &dev_attr_device_state.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(axiom);
+
+static void axiom_poll(struct input_dev *input)
+{
+ struct axiom_data *ts = input_get_drvdata(input);
+
+ axiom_process_report(ts, AXIOM_U34, NULL, 0);
+}
+
+static irqreturn_t axiom_irq(int irq, void *dev_id)
+{
+ struct axiom_data *ts = dev_id;
+
+ axiom_process_report(ts, AXIOM_U34, NULL, 0);
+
+ return IRQ_HANDLED;
+}
+
+static int axiom_input_open(struct input_dev *dev)
+{
+ struct axiom_data *ts = input_get_drvdata(dev);
+
+ return pm_runtime_resume_and_get(ts->dev);
+}
+
+static void axiom_input_close(struct input_dev *dev)
+{
+ struct axiom_data *ts = input_get_drvdata(dev);
+
+ pm_runtime_mark_last_busy(ts->dev);
+ pm_runtime_put_sync_autosuspend(ts->dev);
+}
+
+static int axiom_register_input_dev(struct axiom_data *ts,
+ bool update_in_process)
+{
+ struct device *dev = ts->dev;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct input_dev *input;
+ int ret;
+
+ input = input_allocate_device();
+ if (!input) {
+ dev_err(dev, "Failed to allocate input driver data\n");
+ return -ENOMEM;
+ }
+
+ input->dev.parent = dev;
+ input->name = "TouchNetix aXiom Touchscreen";
+ input->id.bustype = BUS_I2C;
+ input->id.vendor = ts->jedec_id;
+ input->id.product = ts->device_id;
+ input->id.version = ts->silicon_rev;
+
+ /* Either follow the panel or the open user count, not both */
+ if (!ts->is_panel_follower) {
+ input->open = axiom_input_open;
+ input->close = axiom_input_close;
+ }
+
+ axiom_u64_cds_enabled(ts);
+ input_set_abs_params(input, ABS_MT_POSITION_X, 0, AXIOM_MAX_XY - 1, 0, 0);
+ input_set_abs_params(input, ABS_MT_POSITION_Y, 0, AXIOM_MAX_XY - 1, 0, 0);
+ input_set_abs_params(input, ABS_MT_DISTANCE, 0, 127, 0, 0);
+ if (ts->cds_enabled)
+ input_set_abs_params(input, ABS_MT_PRESSURE, 0, 127, 0, 0);
+
+ touchscreen_parse_properties(input, true, &ts->prop);
+
+ axiom_u42_get_touchslots(ts);
+ if (!ts->num_slots && update_in_process) {
+ input_free_device(input);
+ /*
+ * Skip input device registration but don't throw an error to
+ * not abort the update since some FW updates require a
+ * following CFG update to re-initialize the touchslot handling.
+ */
+ if (update_in_process) {
+ dev_info(dev, "No touchslots found after FW or CFG update, skip registering input device\n");
+ return 0;
+ }
+
+ dev_err(dev, "Error firmware has no touchslots enabled\n");
+ return -EINVAL;
+ }
+
+ ret = input_mt_init_slots(input, ts->num_slots, INPUT_MT_DIRECT);
+ if (ret) {
+ input_free_device(input);
+ dev_err(dev, "Failed to init mt slots\n");
+ return ret;
+ }
+
+ /*
+ * Ensure that the IRQ setup is done only once since the handler belong
+ * to the i2c-dev whereas the input-poller belong to the input-dev. The
+ * input-dev can get unregistered during a firmware update to reflect
+ * the new firmware state. Therefore the input-poller setup must be done
+ * always.
+ */
+ if (!ts->irq_setup_done && client->irq) {
+ ret = devm_request_threaded_irq(dev, client->irq, NULL, axiom_irq,
+ IRQF_ONESHOT, dev_name(dev), ts);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ\n");
+ return ret;
+ }
+ ts->irq_setup_done = true;
+ } else {
+ ret = input_setup_polling(input, axiom_poll);
+ if (ret) {
+ input_free_device(input);
+ dev_err(dev, "Setup polling mode failed\n");
+ return ret;
+ }
+
+ input_set_poll_interval(input, ts->poll_interval);
+ }
+
+ input_set_drvdata(input, ts);
+ ts->input = input;
+
+ ret = input_register_device(input);
+ if (ret) {
+ input_free_device(input);
+ ts->input = NULL;
+ dev_err(dev, "Failed to register input device\n");
+ };
+
+ return ret;
+}
+
+static int axiom_update_input_dev(struct axiom_data *ts)
+{
+ axiom_unregister_input_dev(ts);
+
+ return axiom_register_input_dev(ts, true);
+}
+
+static int axiom_parse_firmware(struct axiom_data *ts)
+{
+ struct device *dev = ts->dev;
+ struct gpio_desc *gpio;
+ int ret;
+
+ ts->supplies[0].supply = "vddi";
+ ts->supplies[1].supply = "vdda";
+ ts->num_supplies = ARRAY_SIZE(ts->supplies);
+
+ ret = devm_regulator_bulk_get(dev, ts->num_supplies, ts->supplies);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get power supplies\n");
+
+ gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(gpio))
+ return dev_err_probe(dev, PTR_ERR(gpio),
+ "Failed to get reset GPIO\n");
+ ts->reset_gpio = gpio;
+
+ ts->poll_interval = AXIOM_DEFAULT_POLL_INTERVAL_MS;
+ device_property_read_u32(dev, "poll-interval", &ts->poll_interval);
+
+ return 0;
+}
+
+static int axiom_power_device(struct axiom_data *ts, unsigned int enable)
+{
+ struct device *dev = ts->dev;
+ int ret;
+
+ if (!enable) {
+ regulator_bulk_disable(ts->num_supplies, ts->supplies);
+ return 0;
+ }
+
+ ret = regulator_bulk_enable(ts->num_supplies, ts->supplies);
+ if (ret) {
+ dev_err(dev, "Failed to enable power supplies\n");
+ return ret;
+ }
+
+ gpiod_set_value_cansleep(ts->reset_gpio, 1);
+ fsleep(2000);
+ gpiod_set_value_cansleep(ts->reset_gpio, 0);
+
+ fsleep(AXIOM_STARTUP_TIME_MS);
+
+ return 0;
+}
+
+static int axiom_panel_prepared(struct drm_panel_follower *follower)
+{
+ struct axiom_data *ts = container_of(follower, struct axiom_data,
+ panel_follower);
+
+ return pm_runtime_resume_and_get(ts->dev);
+}
+
+static int axiom_panel_unpreparing(struct drm_panel_follower *follower)
+{
+ struct axiom_data *ts = container_of(follower, struct axiom_data,
+ panel_follower);
+
+ return pm_runtime_put_sync_suspend(ts->dev);
+}
+
+static const struct drm_panel_follower_funcs axiom_panel_follower_funcs = {
+ .panel_prepared = axiom_panel_prepared,
+ .panel_unpreparing = axiom_panel_unpreparing,
+};
+
+static int axiom_register_panel_follower(struct axiom_data *ts)
+{
+ struct device *dev = ts->dev;
+
+ if (!drm_is_panel_follower(dev))
+ return 0;
+
+ if (device_can_wakeup(dev)) {
+ dev_warn(dev, "Can't follow panel if marked as wakup device\n");
+ return 0;
+ }
+
+ ts->panel_follower.funcs = &axiom_panel_follower_funcs;
+ ts->is_panel_follower = true;
+
+ return devm_drm_panel_add_follower(dev, &ts->panel_follower);
+}
+
+static int axiom_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct axiom_data *ts;
+ int ret;
+
+ ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+ if (!ts)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate driver data\n");
+
+ ts->regmap = devm_regmap_init_i2c(client, &axiom_i2c_regmap_config);
+ if (IS_ERR(ts->regmap))
+ return dev_err_probe(dev, PTR_ERR(ts->regmap),
+ "Failed to initialize regmap\n");
+
+ i2c_set_clientdata(client, ts);
+ ts->dev = dev;
+
+ init_completion(&ts->boot_complete.completion);
+ init_completion(&ts->nvm_write.completion);
+ mutex_init(&ts->fwupdate_lock);
+
+ ret = axiom_register_fwl(ts);
+ if (ret)
+ return ret;
+
+ ret = axiom_parse_firmware(ts);
+ if (ret)
+ return ret;
+
+ ret = axiom_power_device(ts, 1);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to power-on device\n");
+
+ pm_runtime_set_autosuspend_delay(dev, 10 * MSEC_PER_SEC);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_get_noresume(dev);
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable pm-runtime\n");
+
+ ret = axiom_register_panel_follower(ts);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register panel follower\n");
+
+ ret = axiom_u31_device_discover(ts);
+ /*
+ * Register the device to allow FW updates in case that the current FW
+ * doesn't support the required driver usages or if the device is in
+ * bootloader mode.
+ */
+ if (ret && ret == -EACCES && IS_ENABLED(CONFIG_FW_UPLOAD)) {
+ dev_warn(dev, "Device discovery failed, wait for user fw update\n");
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_sync_autosuspend(dev);
+ return 0;
+ } else if (ret) {
+ pm_runtime_put_sync(dev);
+ return dev_err_probe(dev, ret, "Device discovery failed\n");
+ }
+
+ ret = axiom_register_input_dev(ts, false);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_sync_autosuspend(dev);
+ if (ret && IS_ENABLED(CONFIG_FW_UPLOAD))
+ dev_warn(dev, "Failed to register the input device, wait for user fw update\n");
+ else if (ret)
+ return dev_err_probe(dev, ret, "Failed to register input device\n");
+
+ return 0;
+}
+
+static void axiom_i2c_remove(struct i2c_client *client)
+{
+ struct axiom_data *ts = i2c_get_clientdata(client);
+
+ axiom_unregister_input_dev(ts);
+}
+
+static int axiom_runtime_suspend(struct device *dev)
+{
+ struct axiom_data *ts = dev_get_drvdata(dev);
+ struct i2c_client *client = to_i2c_client(dev);
+
+ if (client->irq && ts->irq_setup_done)
+ disable_irq(client->irq);
+
+ return axiom_power_device(ts, 0);
+}
+
+static int axiom_runtime_resume(struct device *dev)
+{
+ struct axiom_data *ts = dev_get_drvdata(dev);
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret;
+
+ ret = axiom_power_device(ts, 1);
+ if (ret)
+ return ret;
+
+ if (client->irq && ts->irq_setup_done)
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(axiom_pm_ops, axiom_runtime_suspend,
+ axiom_runtime_resume, NULL);
+
+static const struct i2c_device_id axiom_i2c_id_table[] = {
+ { "ax54a" },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, axiom_i2c_id_table);
+
+static const struct of_device_id axiom_of_match[] = {
+ { .compatible = "touchnetix,ax54a", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, axiom_of_match);
+
+static struct i2c_driver axiom_i2c_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .dev_groups = axiom_groups,
+ .pm = pm_ptr(&axiom_pm_ops),
+ .of_match_table = axiom_of_match,
+ },
+ .id_table = axiom_i2c_id_table,
+ .probe = axiom_i2c_probe,
+ .remove = axiom_i2c_remove,
+};
+module_i2c_driver(axiom_i2c_driver);
+
+MODULE_DESCRIPTION("TouchNetix aXiom touchscreen I2C bus driver");
+MODULE_LICENSE("GPL");
--
2.47.3
^ permalink raw reply related
* [PATCH v5 0/4] Input: Add support for TouchNetix aXiom touchscreen
From: Marco Felsch @ 2026-01-11 15:05 UTC (permalink / raw)
To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
Rafael J. Wysocki, Andrew Morton, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
Cc: linux-kernel, devicetree, linux-input, kernel, Marco Felsch,
Krzysztof Kozlowski
Hi,
this adds the support for the TouchNetix aXiom touchcontroller family.
The following features are added:
- I2C communication
- Input event handling
- Touchcontroller firmware (AXFW or ALC) updates
- Touchcontroller config (TH2CFGBIN) updates
Regards,
Marco
Changes in v5:
- Link to v4: https://lore.kernel.org/r/20260106-v6-10-topic-touchscreen-axiom-v4-0-9e9b69c84926@pengutronix.de
- fix sysfs documentation description indentation and date
Changes in v4:
- Link to v3: https://lore.kernel.org/r/20250821-v6-10-topic-touchscreen-axiom-v3-0-940ccee6dba3@pengutronix.de
- rebased on top of v6.19-rc1
- collect r-b tags
Changes in v3:
- Link to v2: https://lore.kernel.org/r/20250529-v6-10-topic-touchscreen-axiom-v2-0-a5edb105a600@pengutronix.de
- firmware: fix commit message (Russ)
- dt-bindings: Add ack from Krzysztof
- dt-bindings: make use of GPIO_ACTIVE_LOW (Krzysztof)
- dt-bindings: drop 'panel: true' property (Krzysztof)
- driver: make use of sysfs_emit (Greg)
- driver: s/WARN()/dev_warn()/ to not take down the system (Greg)
- driver: fix build dependency error by adding "depends on DRM || !DRM"
- driver: harmonize usage printing to u%02X
Changes in v2:
- Link to v1: https://lore.kernel.org/r/20241119-v6-10-topic-touchscreen-axiom-v1-0-6124925b9718@pengutronix.de
- Rework the firmware-duplicate handling -> expose the error to the
userspace
- Drop Krzysztof Kozlowski ACK and RB
- Add panel-follower support
- Add sysfs-driver-input-touchnetix-axiom documentation
- Add support for new firmware 4.8.9
- Add support to handle 2D and 3D firmware
---
Kamel Bouhara (2):
dt-bindings: vendor-prefixes: Add TouchNetix AS
dt-bindings: input: Add TouchNetix axiom touchscreen
Marco Felsch (2):
firmware_loader: expand firmware error codes with up-to-date error
Input: Add TouchNetix aXiom I2C Touchscreen support
.../testing/sysfs-driver-input-touchnetix-axiom | 81 +
.../input/touchscreen/touchnetix,ax54a.yaml | 62 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
drivers/base/firmware_loader/sysfs_upload.c | 1 +
drivers/input/touchscreen/Kconfig | 17 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/touchnetix_axiom.c | 2974 ++++++++++++++++++++
include/linux/firmware.h | 2 +
lib/test_firmware.c | 1 +
9 files changed, 3141 insertions(+)
---
base-commit: 8f0b4cce4481fb22653697cced8d0d04027cb1e8
change-id: 20240704-v6-10-topic-touchscreen-axiom-105761e81011
Best regards,
--
Marco Felsch <m.felsch@pengutronix.de>
^ permalink raw reply
* [PATCH v5 3/4] dt-bindings: input: Add TouchNetix axiom touchscreen
From: Marco Felsch @ 2026-01-11 15:05 UTC (permalink / raw)
To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
Rafael J. Wysocki, Andrew Morton, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
Cc: linux-kernel, devicetree, linux-input, kernel, Marco Felsch,
Krzysztof Kozlowski
In-Reply-To: <20260111-v6-10-topic-touchscreen-axiom-v5-0-f94e0ae266cb@pengutronix.de>
From: Kamel Bouhara <kamel.bouhara@bootlin.com>
Add the TouchNetix axiom I2C touchscreen device tree bindings
documentation.
Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
.../input/touchscreen/touchnetix,ax54a.yaml | 62 ++++++++++++++++++++++
1 file changed, 62 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/touchnetix,ax54a.yaml b/Documentation/devicetree/bindings/input/touchscreen/touchnetix,ax54a.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d388c41a75dd4d6d6d0e6de0eaef4d493d439a90
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/touchnetix,ax54a.yaml
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/touchnetix,ax54a.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TouchNetix Axiom series touchscreen controller
+
+maintainers:
+ - Marco Felsch <kernel@pengutronix.de>
+
+allOf:
+ - $ref: /schemas/input/touchscreen/touchscreen.yaml#
+ - $ref: /schemas/input/input.yaml#
+
+properties:
+ compatible:
+ const: touchnetix,ax54a
+
+ reg:
+ enum: [ 0x66, 0x67 ]
+
+ interrupts:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ vdda-supply:
+ description: Analog power supply regulator on VDDA pin
+
+ vddi-supply:
+ description: I/O power supply regulator on VDDI pin
+
+required:
+ - compatible
+ - reg
+ - vdda-supply
+ - vddi-supply
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ touchscreen@66 {
+ compatible = "touchnetix,ax54a";
+ reg = <0x66>;
+ interrupt-parent = <&gpio2>;
+ interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
+ reset-gpios = <&gpio1 1 GPIO_ACTIVE_LOW>;
+ vdda-supply = <&vdda_reg>;
+ vddi-supply = <&vddi_reg>;
+ poll-interval = <20>;
+ };
+ };
+...
--
2.47.3
^ permalink raw reply related
* [PATCH v5 1/4] firmware_loader: expand firmware error codes with up-to-date error
From: Marco Felsch @ 2026-01-11 15:05 UTC (permalink / raw)
To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
Rafael J. Wysocki, Andrew Morton, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
Cc: linux-kernel, devicetree, linux-input, kernel, Marco Felsch
In-Reply-To: <20260111-v6-10-topic-touchscreen-axiom-v5-0-f94e0ae266cb@pengutronix.de>
Add FW_UPLOAD_ERR_DUPLICATE to allow drivers to inform the firmware_loader
framework that the update is not required. This can be the case if the
user provided firmware matches the current running firmware.
Sync lib/test_firmware.c accordingly.
Reviewed-by: Russ Weight <russ.weight@linux.dev>
Reviewed-by: Luis Chamberlain <mcgrof@kernel.org>
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
drivers/base/firmware_loader/sysfs_upload.c | 1 +
include/linux/firmware.h | 2 ++
lib/test_firmware.c | 1 +
3 files changed, 4 insertions(+)
diff --git a/drivers/base/firmware_loader/sysfs_upload.c b/drivers/base/firmware_loader/sysfs_upload.c
index c3797b93c5f5a2ecf2ae34707893c89eb7773154..9e93070b2c24179986b868a24b09cf051776c644 100644
--- a/drivers/base/firmware_loader/sysfs_upload.c
+++ b/drivers/base/firmware_loader/sysfs_upload.c
@@ -28,6 +28,7 @@ static const char * const fw_upload_err_str[] = {
[FW_UPLOAD_ERR_RW_ERROR] = "read-write-error",
[FW_UPLOAD_ERR_WEAROUT] = "flash-wearout",
[FW_UPLOAD_ERR_FW_INVALID] = "firmware-invalid",
+ [FW_UPLOAD_ERR_DUPLICATE] = "firmware-duplicate",
};
static const char *fw_upload_progress(struct device *dev,
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index aae1b85ffc10e20e9c3c9b6009d26b83efd8cb24..fe7797be4c08cd62cdad9617b8f70095d5e0af2f 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -29,6 +29,7 @@ struct firmware {
* @FW_UPLOAD_ERR_RW_ERROR: read or write to HW failed, see kernel log
* @FW_UPLOAD_ERR_WEAROUT: FLASH device is approaching wear-out, wait & retry
* @FW_UPLOAD_ERR_FW_INVALID: invalid firmware file
+ * @FW_UPLOAD_ERR_DUPLICATE: firmware is already up to date (duplicate)
* @FW_UPLOAD_ERR_MAX: Maximum error code marker
*/
enum fw_upload_err {
@@ -41,6 +42,7 @@ enum fw_upload_err {
FW_UPLOAD_ERR_RW_ERROR,
FW_UPLOAD_ERR_WEAROUT,
FW_UPLOAD_ERR_FW_INVALID,
+ FW_UPLOAD_ERR_DUPLICATE,
FW_UPLOAD_ERR_MAX
};
diff --git a/lib/test_firmware.c b/lib/test_firmware.c
index be4f93124901e5faac41f48a66dabe6da56be0ca..952ec1cb03102911dbea9abd648ab9d9e0112a46 100644
--- a/lib/test_firmware.c
+++ b/lib/test_firmware.c
@@ -1134,6 +1134,7 @@ static const char * const fw_upload_err_str[] = {
[FW_UPLOAD_ERR_RW_ERROR] = "read-write-error",
[FW_UPLOAD_ERR_WEAROUT] = "flash-wearout",
[FW_UPLOAD_ERR_FW_INVALID] = "firmware-invalid",
+ [FW_UPLOAD_ERR_DUPLICATE] = "firmware-duplicate",
};
static void upload_err_inject_error(struct test_firmware_upload *tst,
--
2.47.3
^ permalink raw reply related
* [PATCH v5 2/4] dt-bindings: vendor-prefixes: Add TouchNetix AS
From: Marco Felsch @ 2026-01-11 15:05 UTC (permalink / raw)
To: Luis Chamberlain, Russ Weight, Greg Kroah-Hartman,
Rafael J. Wysocki, Andrew Morton, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Kamel Bouhara,
Marco Felsch, Henrik Rydberg, Danilo Krummrich, Danilo Krummrich
Cc: linux-kernel, devicetree, linux-input, kernel, Marco Felsch,
Krzysztof Kozlowski
In-Reply-To: <20260111-v6-10-topic-touchscreen-axiom-v5-0-f94e0ae266cb@pengutronix.de>
From: Kamel Bouhara <kamel.bouhara@bootlin.com>
Add vendor prefix for TouchNetix AS (https://www.touchnetix.com/products/).
Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index c7591b2aec2a74560a4f687fe7a2070ca21b0752..d972d8ba25b4fcb22d5020257f271aa10d8de2c3 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1673,6 +1673,8 @@ patternProperties:
description: Toradex AG
"^toshiba,.*":
description: Toshiba Corporation
+ "^touchnetix,.*":
+ description: TouchNetix AS
"^toumaz,.*":
description: Toumaz
"^tpk,.*":
--
2.47.3
^ permalink raw reply related
* Re: [PATCH] Off-by-one error in CP2112 HID driver affecting SMBus block read output
From: Jiri Kosina @ 2026-01-10 10:09 UTC (permalink / raw)
To: Luis Yanes; +Cc: linux-input, Benjamin Tissoires
In-Reply-To: <68F7C063.5049@yahoo.es>
On Tue, 21 Oct 2025, Luis Yanes wrote:
> Affected file: drivers/hid/hid-cp2112.c
> Kernel version: v5.15.195 (verified) - master (suspected)
> Tool used: i2cget -y 8 0xb 0x78 s <-- reading a battery gauge chip
>
> Observed behavior:
> Incorrect trailing last byte in 32 bytes SMBus block read response.
>
> Expected output:
> 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xaa
> 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x55
>
> Actual SMBus block data read output:
> 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xaa
> 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x00
>
> I2C block data read output (from i2cget -y 8 0xb 0x78 i):
> 0x20 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
> 0xaa 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
>
> Description:
> When performing an SMBus block read using the CP2112 HID USB-to-SMBus
> bridge with the i2cget utility, the last byte of the response for a
> 32 bytes block read is, in this case, always 0x00 instead of the actual
> value. In this sample case shown above the actual last byte is 0x55 but
> the driver returns 0x00 (uninitialized value).
> This happens consistently for any 32 bytes block read on any register.
> Shorter than 32 bytes block transfers are unaffected.
>
> Suspected cause:
> For what I could understand checking the source code this seems an
> off-by-one error in the read request and buffer handling, limited by the
> I2C_SMBUS_BLOCK_MAX 32 bytes read value and the response parsing logic
> within the CP2112 driver since that with a custom hacked module
> (see patch below) I could read that last byte.
>
> Steps to reproduce:
> 1. Connect a CP2112 device and a proper slave device to read from.
> 2. Use i2cget to perform a known 32 bytes block read with this device:
> i.e: i2cget -y 8 0xb 0x78 s
> 3. Compare the output against the expected data from the target
> SMBus device. The last byte probably could appear as any other
> uninitialized buffer value, I guess.
> From 'i2cget -y 8 0xb 0x78 i' the initial 0x20 is the block
> length and the last 32th byte is completely missing.
>
> Impact:
> This bug produces incorrect output and will lead to data corruption
> or data misinterpretation when reading from SMBus devices using this
> driver.
>
> Suggested fix:
> Review the buffer handling and read length in the CP2112 driver SMBus
> and I2C block read and write implementations.
> I have not checked if write operations are affected, but probably would
> be a good idea to verify that the 32 bytes block transfers are handled
> properly.
>
> Additional info:
> The issue is reproducible with different CP2112 devices (silicon rev F04)
> and v5.15.x stock kernel builds. Probably also affects newer versions
> since for what I could understand, despite the additional checking
> changes added to the driver the logic seems basically the same.
>
> Patch: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/drivers/hid/hid-cp2112.c?h=v5.15.195
> --- a/drivers/hid/hid-cp2112.c
> +++ b/drivers/hid/hid-cp2112.c
> @@ -703,7 +703,7 @@
> case I2C_SMBUS_BLOCK_DATA:
> if (I2C_SMBUS_READ == read_write) {
> count = cp2112_write_read_req(buf, addr,
> - I2C_SMBUS_BLOCK_MAX,
> + I2C_SMBUS_BLOCK_MAX + 1,
> command, NULL, 0);
> } else {
> count = cp2112_write_req(buf, addr, command,
> @@ -796,7 +796,7 @@
> memcpy(data->block + 1, buf, read_length);
> break;
> case I2C_SMBUS_BLOCK_DATA:
> - if (read_length > I2C_SMBUS_BLOCK_MAX) {
> + if (read_length > I2C_SMBUS_BLOCK_MAX + 1) {
> ret = -EPROTO;
> goto power_normal;
> }
Your explanation and the fix look good to me. Could you please do a proper
submission (with proper shortlog+changelog, SOB line, etc?)?
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2] HID: logitech-hidpp: Check maxfield in hidpp_get_report_length()
From: Jiri Kosina @ 2026-01-10 9:29 UTC (permalink / raw)
To: Günther Noack
Cc: Filipe Laíns, Bastien Nocera, Benjamin Tissoires, Greg KH,
stable, linux-input, linux-kernel
In-Reply-To: <20260109122557.3166556-3-gnoack@google.com>
On Fri, 9 Jan 2026, Günther Noack wrote:
> Do not crash when a report has no fields.
>
> Fake USB gadgets can send their own HID report descriptors and can define r=
> eport
> structures without valid fields. This can be used to crash the kernel over=
> USB.
Applied, thanks.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: prodikeys: Check presence of pm->input_ep82
From: Jiri Kosina @ 2026-01-10 9:28 UTC (permalink / raw)
To: Günther Noack; +Cc: Benjamin Tissoires, stable, linux-input, linux-kernel
In-Reply-To: <20260109105807.3141618-2-gnoack@google.com>
On Fri, 9 Jan 2026, Günther Noack wrote:
> Fake USB devices can send their own report descriptors for which the
> input_mapping() hook does not get called. In this case, pm->input_ep82
> sta= ys NULL, which leads to a crash later.
>
> This does not happen with the real device, but can be provoked by
> imposing = as one.
Applied. thanks.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: magicmouse: Do not crash on missing msc->input
From: Jiri Kosina @ 2026-01-10 9:27 UTC (permalink / raw)
To: Günther Noack; +Cc: Benjamin Tissoires, stable, linux-input, linux-kernel
In-Reply-To: <20260109105714.3140851-2-gnoack@google.com>
On Fri, 9 Jan 2026, Günther Noack wrote:
> Fake USB devices can send their own report descriptors for which the
> input_mapping() hook does not get called. In this case, msc->input stays N=
> ULL,
> leading to a crash at a later time.
>
> Detect this condition in the input_configured() hook and reject the device.
>
> This is not supposed to happen with actual magic mouse devices, but can be
> provoked by imposing as a magic mouse USB device.
Applied to hid.git#for-6.19/upstream-fixes, thanks Guenther.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: sony: add support for Rock Band 4 PS4 and PS5 guitars
From: Jiri Kosina @ 2026-01-10 9:22 UTC (permalink / raw)
To: Rosalie Wanders; +Cc: Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <20260102211228.82324-2-rosalie@mailbox.org>
On Fri, 2 Jan 2026, Rosalie Wanders wrote:
> This commit adds support for the PDP RiffMaster and the CRKD Gibson SG
> in both their PS4 and PS5 modes.
>
> These devices aren't mapped correctly without these changes, they also
> lack support for their whammy and tilt functionality which this patch
> adds support for by binding them to the left and right triggers.
>
> Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
Applied to hid.git#for-6.20/sony. Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2 RESEND] selftests: hid: tests: test_wacom_generic: add tests for display devices and opaque devices
From: Jiri Kosina @ 2026-01-10 9:20 UTC (permalink / raw)
To: Alex Tran
Cc: Benjamin Tissoires, shuah, linux-input, linux-kselftest,
linux-kernel, Benjamin Tissoires, Ping Cheng, Jason Gerecke
In-Reply-To: <20251220204811.3539836-1-alex.t.tran@gmail.com>
On Sat, 20 Dec 2025, Alex Tran wrote:
> Verify Wacom devices set INPUT_PROP_DIRECT on display devices and
> INPUT_PROP_POINTER on opaque devices. Moved test_prop_pointer into
> TestOpaqueTablet. Created a DirectTabletTest mixin class for
> test_prop_direct that can be inherited by display tablet test classes.
> Used DirectTabletTest for TestDTH2452Tablet case.
>
> Signed-off-by: Alex Tran <alex.t.tran@gmail.com>
Let me CC Benjamin and Wacom folks to take a look here as well.
> ---
> Changes in v2:
> - Removed the tests from the BaseTest class
> - Removed disabling tests for certain subclasses
> - Moved test_prop_pointer under TestOpaqueTablet
> - Created DirectTabletTest mixin class
> - Moved test_prop_direct under TestDTH2452Tablet
> .../selftests/hid/tests/test_wacom_generic.py | 30 +++++++++++--------
> 1 file changed, 17 insertions(+), 13 deletions(-)
>
> diff --git a/tools/testing/selftests/hid/tests/test_wacom_generic.py b/tools/testing/selftests/hid/tests/test_wacom_generic.py
> index 2d6d04f0f..9d0b0802d 100644
> --- a/tools/testing/selftests/hid/tests/test_wacom_generic.py
> +++ b/tools/testing/selftests/hid/tests/test_wacom_generic.py
> @@ -598,18 +598,6 @@ class BaseTest:
> if unit_set:
> assert required[usage].contains(field)
>
> - def test_prop_direct(self):
> - """
> - Todo: Verify that INPUT_PROP_DIRECT is set on display devices.
> - """
> - pass
> -
> - def test_prop_pointer(self):
> - """
> - Todo: Verify that INPUT_PROP_POINTER is set on opaque devices.
> - """
> - pass
> -
>
> class PenTabletTest(BaseTest.TestTablet):
> def assertName(self, uhdev):
> @@ -677,6 +665,13 @@ class TestOpaqueTablet(PenTabletTest):
> uhdev.event(130, 240, pressure=0), [], auto_syn=False, strict=True
> )
>
> + def test_prop_pointer(self):
> + """
> + Verify that INPUT_PROP_POINTER is set on opaque devices.
> + """
> + evdev = self.uhdev.get_evdev()
> + assert libevdev.INPUT_PROP_POINTER in evdev.properties
> +
>
> class TestOpaqueCTLTablet(TestOpaqueTablet):
> def create_device(self):
> @@ -862,7 +857,16 @@ class TestPTHX60_Pen(TestOpaqueCTLTablet):
> )
>
>
> -class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest):
> +class DirectTabletTest():
> + def test_prop_direct(self):
> + """
> + Verify that INPUT_PROP_DIRECT is set on display devices.
> + """
> + evdev = self.uhdev.get_evdev()
> + assert libevdev.INPUT_PROP_DIRECT in evdev.properties
> +
> +
> +class TestDTH2452Tablet(test_multitouch.BaseTest.TestMultitouch, TouchTabletTest, DirectTabletTest):
> ContactIds = namedtuple("ContactIds", "contact_id, tracking_id, slot_num")
>
> def create_device(self):
> --
> 2.51.0
>
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: hid-asus: Implement fn lock for Asus ProArt P16
From: Jiri Kosina @ 2026-01-10 9:18 UTC (permalink / raw)
To: Connor Belli; +Cc: linux-input, bentiss
In-Reply-To: <20251209000008.4479-1-connorbelli2003@gmail.com>
On Mon, 8 Dec 2025, Connor Belli wrote:
> This patch implements support for the fn lock key on the 2025 Asus
> ProArt P16. The implementation for this is based on how fn lock is
> implemented in the hid-lenovo driver.
Applied, thanks ...
> Unfortunately, I am not too experienced with driver development, so I'm
> not sure adding the handler directly in asus_event is the best solution
> for this.
... but I've removed this from the changelog, as (a) I believe there is no
problem with that approach (b) it doesn't belong to the changelog :)
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: EVision: input mapping fix for K552 keyboard
From: Jiri Kosina @ 2026-01-10 9:12 UTC (permalink / raw)
To: PrakarshPanwar; +Cc: Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <20251205-evision-k552-mapping-fix-v1-1-848d086c43ee@gmail.com>
On Fri, 5 Dec 2025, PrakarshPanwar via B4 Relay wrote:
> From: PrakarshPanwar <prakarshpanwar@gmail.com>
>
> Added fix for Media Key in Redragon K552 keyboard
> In Windows, this keyboard F1 Key opens Media Player,
> but in Linux it opens System Settings.
>
> This commit is a fix for that bug it remaps K552 F1 key
> which outputs KEY_CONFIG(171) now outputs KEY_MEDIA(226)
>
> It also restructures the input mapping code in hid-evision
Given the fact that actuall keycode *IS* being emitted to userspace (just
a wrong one), can't we do just with remapping the key in userpace using
systemd-hwdb?
Thanks,
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: pidff: Do not set out of range trigger button
From: Jiri Kosina @ 2026-01-10 9:07 UTC (permalink / raw)
To: Tomasz Pakuła; +Cc: bentiss, oleg, linux-input
In-Reply-To: <20251129184614.1919238-1-tomasz.pakula.oficjalny@gmail.com>
On Sat, 29 Nov 2025, Tomasz Pakuła wrote:
> Some games (mainly observed with Kylotonn's WRC Serises) set trigger
> button to a random value, or always the same one, out of range.
> I observed 307 and other values but, for example, my Moza R9 only
> exposes 128 buttons AND it's trigger button field is 8-bit. This causes
> errors to appear in dmesg.
>
> Only set the trigger button and trigger interval in the trigger button
> is in range of the field.
Applied, thanks.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: multitouch: add quirks for Lenovo Yoga Book 9i
From: Jiri Kosina @ 2026-01-10 9:05 UTC (permalink / raw)
To: Brian Howard
Cc: Andrei Shumailov, Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <468q5375-r032-so88-p263-r663p7646q01@xreary.bet>
On Sat, 10 Jan 2026, Jiri Kosina wrote:
> > Add required quirks for Lenovo Yoga Book 9i Gen 8 to Gen 10 models,
> > including a new quirk providing for custom input device naming and
> > dropping erroneous InRange reports.
> >
> > The Lenovo Yoga Book 9i is a dual-screen laptop, with a single composite
> > USB device providing both touch and tablet interfaces for both screens.
> > All inputs report through a single device, differentiated solely by report
> > numbers. As there is no way for udev to differentiate the inputs based on
> > USB vendor/product ID or interface numbers, custom naming is required to
> > match against for downstream configuration. A firmware bug also results
> > in an erroneous InRange message report (with everything other than X/Y
> > as 0) being received after the stylus leaves proximity, blocking later
> > touch events.
> >
> > Signed-off-by: Brian Howard <blhoward2@gmail.com>
> > Tested-by: Brian Howard <blhoward2@gmail.com>
> > Reported-by: Andrei Shumailov <gentoo1993@gmail.com>
> > Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220386
>
> Applied, thanks.
v2 was actually applied, just the response was improperly threaded, sorry.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: multitouch: add quirks for Lenovo Yoga Book 9i
From: Jiri Kosina @ 2026-01-10 9:03 UTC (permalink / raw)
To: Brian Howard
Cc: Andrei Shumailov, Benjamin Tissoires, linux-input, linux-kernel
In-Reply-To: <20251118020723.6600-1-blhoward2@gmail.com>
On Mon, 17 Nov 2025, Brian Howard wrote:
> Add required quirks for Lenovo Yoga Book 9i Gen 8 to Gen 10 models,
> including a new quirk providing for custom input device naming and
> dropping erroneous InRange reports.
>
> The Lenovo Yoga Book 9i is a dual-screen laptop, with a single composite
> USB device providing both touch and tablet interfaces for both screens.
> All inputs report through a single device, differentiated solely by report
> numbers. As there is no way for udev to differentiate the inputs based on
> USB vendor/product ID or interface numbers, custom naming is required to
> match against for downstream configuration. A firmware bug also results
> in an erroneous InRange message report (with everything other than X/Y
> as 0) being received after the stylus leaves proximity, blocking later
> touch events.
>
> Signed-off-by: Brian Howard <blhoward2@gmail.com>
> Tested-by: Brian Howard <blhoward2@gmail.com>
> Reported-by: Andrei Shumailov <gentoo1993@gmail.com>
> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220386
Applied, thanks.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH] HID: playstation: Add missing check for input_ff_create_memless
From: Jiri Kosina @ 2026-01-10 9:01 UTC (permalink / raw)
To: Haotian Zhang; +Cc: roderick.colenbrander, bentiss, linux-input, linux-kernel
In-Reply-To: <2nn8276r-50nq-5655-o1r2-o610sp1s69q6@xreary.bet>
On Sat, 10 Jan 2026, Jiri Kosina wrote:
> While reviewing it though, I came across the fact that just one line
> below, if input_register_device() fails, I believe we're leaking the
> already allocated struct input_dev.
Actually we're not, it's managed device. Sorry for the noise.
--
Jiri Kosina
SUSE Labs
^ 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