* [PATCH 4/6] HID: multitouch: add support for ASUS T3304 media keys
From: James Ye @ 2026-05-03 7:26 UTC (permalink / raw)
To: jikos, bentiss, lee, pavel
Cc: linux-input, linux-leds, linux-kernel, denis.benato, James Ye
In-Reply-To: <20260503072643.2774762-1-jye836@gmail.com>
Touchpad functionality already works with hid-multitouch, but media key
events are emitted from the touchpad interface. Add MT_CLS_ASUS to
handle these, and also add missing key mappings.
Signed-off-by: James Ye <jye836@gmail.com>
---
drivers/hid/hid-multitouch.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index e82a3c4e5b44..a49930496c5b 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -1510,6 +1510,10 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
case 0x35: mt_map_key_clear(KEY_DISPLAY_OFF); break;
case 0x6b: mt_map_key_clear(KEY_F21); break;
case 0x6c: mt_map_key_clear(KEY_SLEEP); break;
+ case 0x7c: mt_map_key_clear(KEY_MICMUTE); break;
+ case 0x4e: mt_map_key_clear(KEY_FN_ESC); break;
+
+ case 0x86: mt_map_key_clear(KEY_PROG1); break; /* MyASUS key */
default:
return -1;
}
@@ -2145,6 +2149,12 @@ static const struct hid_device_id mt_devices[] = {
USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD) },
+ /* Asus T3304 */
+ { .driver_data = MT_CLS_ASUS,
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_ASUSTEK,
+ USB_DEVICE_ID_ASUSTEK_T3304_KEYBOARD) },
+
/* Atmel panels */
{ .driver_data = MT_CLS_SERIAL,
MT_USB_DEVICE(USB_VENDOR_ID_ATMEL,
--
2.54.0
^ permalink raw reply related
* [PATCH 3/6] HID: asus: add support for T3304 detachable keyboard
From: James Ye @ 2026-05-03 7:26 UTC (permalink / raw)
To: jikos, bentiss, lee, pavel
Cc: linux-input, linux-leds, linux-kernel, denis.benato, James Ye
In-Reply-To: <20260503072643.2774762-1-jye836@gmail.com>
ASUSTek Computer, Inc. T3304 Soft Keyboard [0b05:1aad] is the detachable
keyboard of the ASUS Vivobook 13 Slate OLED (T3304). It presents as a
USB device with two interfaces: a keyboard and a pointing device
(touchpad).
Basic keyboard and full touchpad functionality work out-of-the-box with
hid-generic and hid-multitouch, but function key combos e.g. volume,
brightness control, home/end/pgup/pgdown require initialization.
Bind the keyboard interface to hid-asus for initialization. The
OEM-specific report descriptors required for this are present only on
the touchpad interface, not the keyboard, so a quirk is used to add the
required feature descriptor.
Signed-off-by: James Ye <jye836@gmail.com>
---
drivers/hid/hid-asus.c | 58 +++++++++++++++++++++++++++++++++++++++---
drivers/hid/hid-ids.h | 1 +
2 files changed, 56 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index ef9d5eba4dc9..e4c97fddfaf1 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -99,6 +99,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
#define QUIRK_ROG_ALLY_XPAD BIT(13)
#define QUIRK_HID_FN_LOCK BIT(14)
+#define QUIRK_T3304_KEYBOARD BIT(15)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
@@ -494,6 +495,14 @@ static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
return ret;
}
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ /* T3304 keyboard always replies with 16 0xff bytes. Don't check for
+ * acknowledgment.
+ */
+ if (drvdata->quirks & QUIRK_T3304_KEYBOARD)
+ return 0;
+
u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
if (!readbuf)
return -ENOMEM;
@@ -1312,10 +1321,12 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
hid_warn(hdev, "Failed to initialize backlight.\n");
/*
- * For ROG keyboards, skip rename for consistency and ->input check as
- * some devices do not have inputs.
+ * For ROG and T3304 keyboards, skip rename for consistency.
+ * For ROG keyboards, skip ->input check as some devices do not have
+ * inputs.
*/
- if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
+ if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD ||
+ drvdata->quirks & QUIRK_T3304_KEYBOARD)
return 0;
/*
@@ -1369,6 +1380,22 @@ static const __u8 asus_g752_fixed_rdesc[] = {
0x2A, 0xFF, 0x00, /* Usage Maximum (0xFF) */
};
+static const __u8 asus_t3304_fixed_rdesc[] = {
+ 0x06, 0x31, 0xff, // Usage Page (Vendor Usage Page 0xff31)
+ 0x09, 0x76, // Usage (Vendor Usage 0x76)
+ 0xa1, 0x01, // Collection (Application)
+ 0x05, 0xff, // Usage Page (Vendor Usage Page 0xff)
+ 0x85, 0x5a, // Report ID (90)
+ 0x19, 0x00, // Usage Minimum (0)
+ 0x2a, 0xff, 0x00, // Usage Maximum (255)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xff, 0x00, // Logical Maximum (255)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x0f, // Report Count (15)
+ 0xb1, 0x02, // Feature (Data,Var,Abs)
+ 0xc0, // End Collection
+};
+
static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
@@ -1473,6 +1500,28 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
}
}
+ /* T3304 keyboard's vendor descriptors are on the touchpad interface,
+ * not the keyboard. But we need hid-multitouch to handle the touchpad,
+ * Add a descriptor with only the config report so that this driver can
+ * perform initialization.
+ */
+ if (drvdata->quirks & QUIRK_T3304_KEYBOARD) {
+ __u8 *new_rdesc;
+ size_t new_size = *rsize + sizeof(asus_t3304_fixed_rdesc);
+
+ new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
+ if (new_rdesc == NULL)
+ return rdesc;
+
+ hid_info(hdev, "Fixing up Asus T3304 keyboard report descriptor\n");
+ memcpy(new_rdesc, rdesc, *rsize);
+ memcpy(new_rdesc + *rsize, asus_t3304_fixed_rdesc,
+ sizeof(asus_t3304_fixed_rdesc));
+
+ *rsize = new_size;
+ rdesc = new_rdesc;
+ }
+
return rdesc;
}
@@ -1536,6 +1585,9 @@ static const struct hid_device_id asus_devices[] = {
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
+ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T3304_KEYBOARD),
+ QUIRK_T3304_KEYBOARD },
{ }
};
MODULE_DEVICE_TABLE(hid, asus_devices);
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 0cf63742315b..ecf30e36a99d 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -219,6 +219,7 @@
#define USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD 0x8502
#define USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD 0x183d
#define USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD 0x184a
+#define USB_DEVICE_ID_ASUSTEK_T3304_KEYBOARD 0x1aad
#define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD 0x8585
#define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD 0x0101
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
--
2.54.0
^ permalink raw reply related
* [PATCH 2/6] HID: asus: check feature reports when determining is_vendor
From: James Ye @ 2026-05-03 7:26 UTC (permalink / raw)
To: jikos, bentiss, lee, pavel
Cc: linux-input, linux-leds, linux-kernel, denis.benato, James Ye
In-Reply-To: <20260503072643.2774762-1-jye836@gmail.com>
ASUS T3304 Soft Keyboard has these reports on its touchpad interface,
which will be bound to hid_multitouch. To support a quirk allowing
hid_asus to configure the keyboard interface, feature reports should be
checked in addition to input reports, as is already the case in
asus_has_report_id.
Signed-off-by: James Ye <jye836@gmail.com>
---
drivers/hid/hid-asus.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 3f5e96900b67..ef9d5eba4dc9 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -1282,10 +1282,12 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
}
/* Check for vendor for RGB init and handle generic devices properly. */
- rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
- list_for_each_entry(rep, &rep_enum->report_list, list) {
- if ((rep->application & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR)
- is_vendor = true;
+ for (int t = HID_INPUT_REPORT; t <= HID_FEATURE_REPORT; t++) {
+ rep_enum = &hdev->report_enum[t];
+ list_for_each_entry(rep, &rep_enum->report_list, list) {
+ if ((rep->application & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR)
+ is_vendor = true;
+ }
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
--
2.54.0
^ permalink raw reply related
* [PATCH 1/6] HID: input: delete hid_battery on disconnect
From: James Ye @ 2026-05-03 7:26 UTC (permalink / raw)
To: jikos, bentiss, lee, pavel
Cc: linux-input, linux-leds, linux-kernel, denis.benato, James Ye
In-Reply-To: <20260503072643.2774762-1-jye836@gmail.com>
This fixes a use-after-free when an HID device containing a battery is
disconnected then reconnected, such as due to binding to a different
driver.
BUG: KASAN: slab-use-after-free in hidinput_setup_battery.isra.0+0x15a/0x9db [hid]
Signed-off-by: James Ye <jye836@gmail.com>
---
drivers/hid/hid-input.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index d73cfa2e73d3..ae0e11c61eb8 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -2408,6 +2408,7 @@ EXPORT_SYMBOL_GPL(hidinput_connect);
void hidinput_disconnect(struct hid_device *hid)
{
struct hid_input *hidinput, *next;
+ struct hid_battery *bat, *bat_next;
list_for_each_entry_safe(hidinput, next, &hid->inputs, list) {
list_del(&hidinput->list);
@@ -2419,6 +2420,10 @@ void hidinput_disconnect(struct hid_device *hid)
kfree(hidinput);
}
+ list_for_each_entry_safe(bat, bat_next, &hid->batteries, list) {
+ list_del(&bat->list);
+ }
+
/* led_work is spawned by input_dev callbacks, but doesn't access the
* parent input_dev at all. Once all input devices are removed, we
* know that led_work will never get restarted, so we can cancel it
--
2.54.0
^ permalink raw reply related
* [PATCH 0/6] HID: asus: add support for T3304 keyboard
From: James Ye @ 2026-05-03 7:26 UTC (permalink / raw)
To: jikos, bentiss, lee, pavel
Cc: linux-input, linux-leds, linux-kernel, denis.benato, James Ye
This series adds support for the ASUS Vivobook Slate 13 OLED (T3304)
detachable keyboard. The keyboard is a USB device presenting two
interfaces: keyboard and touchpad. Basic functionality already works
with hid-generic and hid-multitouch, but Fn key chords including media
keys and LEDs do not work.
To add support, bind the keyboard interface to hid-asus. However, the
ASUS-specific report descriptors are on the touchpad interface, so fixup
the descriptor so that hid-asus knows about them. Media keys are also
sent via the touchpad interface, so add MT_CLS_ASUS to this device in
hid-multitouch.
Also included is a fix for a use-after-free in hid-input.c discovered
during development, but technically unrelated to the device.
("HID: input: delete hid_battery on disconnect")
James Ye (6):
HID: input: delete hid_battery on disconnect
HID: asus: check feature reports when determining is_vendor
HID: asus: add support for T3304 detachable keyboard
HID: multitouch: add support for ASUS T3304 media keys
HID: asus: add microphone mute LED support for T3304
leds: led-class: mark classdev as unregistering early
drivers/hid/hid-asus.c | 117 ++++++++++++++++++++++++++++++++---
drivers/hid/hid-ids.h | 1 +
drivers/hid/hid-input.c | 5 ++
drivers/hid/hid-multitouch.c | 10 +++
drivers/leds/led-class.c | 4 +-
5 files changed, 126 insertions(+), 11 deletions(-)
--
2.54.0
^ permalink raw reply
* [dtor-input:master] BUILD SUCCESS cde5e7777f2e0b625d576b8725c65ec1f3b12b79
From: kernel test robot @ 2026-05-03 3:42 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git master
branch HEAD: cde5e7777f2e0b625d576b8725c65ec1f3b12b79 Input: pcap_ts - remove unused driver
elapsed time: 2797m
configs tested: 227
configs skipped: 2
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc alldefconfig gcc-15.2.0
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc defconfig gcc-15.2.0
arc randconfig-001-20260503 gcc-10.5.0
arc randconfig-002-20260503 gcc-10.5.0
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm defconfig gcc-15.2.0
arm randconfig-001-20260503 gcc-10.5.0
arm randconfig-002-20260503 gcc-10.5.0
arm randconfig-003-20260503 gcc-10.5.0
arm randconfig-004-20260503 gcc-10.5.0
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001 gcc-10.5.0
arm64 randconfig-001-20260502 gcc-10.5.0
arm64 randconfig-001-20260503 gcc-15.2.0
arm64 randconfig-002 gcc-10.5.0
arm64 randconfig-002-20260502 gcc-10.5.0
arm64 randconfig-002-20260503 gcc-15.2.0
arm64 randconfig-003 gcc-10.5.0
arm64 randconfig-003-20260502 gcc-10.5.0
arm64 randconfig-003-20260503 gcc-15.2.0
arm64 randconfig-004 gcc-10.5.0
arm64 randconfig-004-20260502 gcc-10.5.0
arm64 randconfig-004-20260503 gcc-15.2.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001 gcc-10.5.0
csky randconfig-001-20260502 gcc-10.5.0
csky randconfig-001-20260503 gcc-15.2.0
csky randconfig-002 gcc-10.5.0
csky randconfig-002-20260502 gcc-10.5.0
csky randconfig-002-20260503 gcc-15.2.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260502 clang-23
hexagon randconfig-001-20260503 clang-23
hexagon randconfig-002-20260502 clang-23
hexagon randconfig-002-20260503 clang-23
i386 allmodconfig clang-20
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001 gcc-14
i386 buildonly-randconfig-001-20260502 gcc-14
i386 buildonly-randconfig-002 gcc-14
i386 buildonly-randconfig-002-20260502 gcc-14
i386 buildonly-randconfig-003 gcc-14
i386 buildonly-randconfig-003-20260502 gcc-14
i386 buildonly-randconfig-004 gcc-14
i386 buildonly-randconfig-004-20260502 gcc-14
i386 buildonly-randconfig-005 gcc-14
i386 buildonly-randconfig-005-20260502 gcc-14
i386 buildonly-randconfig-006 gcc-14
i386 buildonly-randconfig-006-20260502 gcc-14
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260502 clang-20
i386 randconfig-002-20260502 clang-20
i386 randconfig-003-20260502 clang-20
i386 randconfig-004-20260502 clang-20
i386 randconfig-005-20260502 clang-20
i386 randconfig-006-20260502 clang-20
i386 randconfig-007-20260502 clang-20
i386 randconfig-011-20260502 clang-20
i386 randconfig-012-20260502 clang-20
i386 randconfig-013-20260502 clang-20
i386 randconfig-014-20260502 clang-20
i386 randconfig-015-20260502 clang-20
i386 randconfig-016-20260502 clang-20
i386 randconfig-017-20260502 clang-20
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260502 clang-23
loongarch randconfig-001-20260503 clang-23
loongarch randconfig-002-20260502 clang-23
loongarch randconfig-002-20260503 clang-23
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 defconfig clang-19
nios2 randconfig-001-20260502 clang-23
nios2 randconfig-001-20260503 clang-23
nios2 randconfig-002-20260502 clang-23
nios2 randconfig-002-20260503 clang-23
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260502 gcc-12.5.0
parisc randconfig-001-20260503 gcc-15.2.0
parisc randconfig-002-20260502 gcc-12.5.0
parisc randconfig-002-20260503 gcc-15.2.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc randconfig-001-20260502 gcc-12.5.0
powerpc randconfig-001-20260503 gcc-15.2.0
powerpc randconfig-002-20260502 gcc-12.5.0
powerpc randconfig-002-20260503 gcc-15.2.0
powerpc64 randconfig-001-20260502 gcc-12.5.0
powerpc64 randconfig-001-20260503 gcc-15.2.0
powerpc64 randconfig-002-20260502 gcc-12.5.0
powerpc64 randconfig-002-20260503 gcc-15.2.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260502 gcc-14.3.0
riscv randconfig-001-20260503 clang-23
riscv randconfig-002-20260502 gcc-14.3.0
riscv randconfig-002-20260503 clang-23
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260502 gcc-14.3.0
s390 randconfig-001-20260503 clang-23
s390 randconfig-002-20260502 gcc-14.3.0
s390 randconfig-002-20260503 clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allyesconfig clang-19
sh defconfig gcc-14
sh randconfig-001-20260502 gcc-14.3.0
sh randconfig-001-20260503 clang-23
sh randconfig-002-20260502 gcc-14.3.0
sh randconfig-002-20260503 clang-23
sh rts7751r2d1_defconfig gcc-15.2.0
sparc allnoconfig clang-23
sparc defconfig gcc-15.2.0
sparc randconfig-001 gcc-8.5.0
sparc randconfig-001-20260502 gcc-8.5.0
sparc randconfig-001-20260503 gcc-15.2.0
sparc randconfig-002 gcc-8.5.0
sparc randconfig-002-20260502 gcc-8.5.0
sparc randconfig-002-20260503 gcc-15.2.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001 gcc-8.5.0
sparc64 randconfig-001-20260502 gcc-8.5.0
sparc64 randconfig-001-20260503 gcc-15.2.0
sparc64 randconfig-002 gcc-8.5.0
sparc64 randconfig-002-20260502 gcc-8.5.0
sparc64 randconfig-002-20260503 gcc-15.2.0
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001 gcc-8.5.0
um randconfig-001-20260502 gcc-8.5.0
um randconfig-001-20260503 gcc-15.2.0
um randconfig-002 gcc-8.5.0
um randconfig-002-20260502 gcc-8.5.0
um randconfig-002-20260503 gcc-15.2.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260502 clang-20
x86_64 buildonly-randconfig-002-20260502 clang-20
x86_64 buildonly-randconfig-003-20260502 clang-20
x86_64 buildonly-randconfig-004-20260502 clang-20
x86_64 buildonly-randconfig-005-20260502 clang-20
x86_64 buildonly-randconfig-006-20260502 clang-20
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260502 clang-20
x86_64 randconfig-001-20260503 clang-20
x86_64 randconfig-002-20260502 clang-20
x86_64 randconfig-002-20260503 clang-20
x86_64 randconfig-003-20260502 clang-20
x86_64 randconfig-003-20260503 clang-20
x86_64 randconfig-004-20260502 clang-20
x86_64 randconfig-004-20260503 clang-20
x86_64 randconfig-005-20260502 clang-20
x86_64 randconfig-005-20260503 clang-20
x86_64 randconfig-006-20260502 clang-20
x86_64 randconfig-006-20260503 clang-20
x86_64 randconfig-011-20260503 clang-20
x86_64 randconfig-012-20260503 clang-20
x86_64 randconfig-013-20260503 clang-20
x86_64 randconfig-014-20260503 clang-20
x86_64 randconfig-015-20260503 clang-20
x86_64 randconfig-016-20260503 clang-20
x86_64 randconfig-071-20260502 gcc-14
x86_64 randconfig-072-20260502 gcc-14
x86_64 randconfig-073-20260502 gcc-14
x86_64 randconfig-074-20260502 gcc-14
x86_64 randconfig-075-20260502 gcc-14
x86_64 randconfig-076-20260502 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allyesconfig clang-23
xtensa randconfig-001 gcc-8.5.0
xtensa randconfig-001-20260502 gcc-8.5.0
xtensa randconfig-001-20260503 gcc-15.2.0
xtensa randconfig-002 gcc-8.5.0
xtensa randconfig-002-20260502 gcc-8.5.0
xtensa randconfig-002-20260503 gcc-15.2.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [dtor-input:for-linus] BUILD SUCCESS 1f6ac0f8441c48c4cc250141e1da8486c13512ba
From: kernel test robot @ 2026-05-02 17:42 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
branch HEAD: 1f6ac0f8441c48c4cc250141e1da8486c13512ba Input: xpad - add "Nova 2 Lite" from GameSir
elapsed time: 2197m
configs tested: 114
configs skipped: 2
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arm allnoconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001 gcc-10.5.0
arm64 randconfig-001-20260502 gcc-10.5.0
arm64 randconfig-002 gcc-10.5.0
arm64 randconfig-002-20260502 gcc-10.5.0
arm64 randconfig-003 gcc-10.5.0
arm64 randconfig-003-20260502 gcc-10.5.0
arm64 randconfig-004 gcc-10.5.0
arm64 randconfig-004-20260502 gcc-10.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001 gcc-10.5.0
csky randconfig-001-20260502 gcc-10.5.0
csky randconfig-002 gcc-10.5.0
csky randconfig-002-20260502 gcc-10.5.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260502 clang-23
hexagon randconfig-002-20260502 clang-23
i386 allnoconfig gcc-15.2.0
i386 buildonly-randconfig-001 gcc-14
i386 buildonly-randconfig-001-20260502 gcc-14
i386 buildonly-randconfig-002 gcc-14
i386 buildonly-randconfig-002-20260502 gcc-14
i386 buildonly-randconfig-003 gcc-14
i386 buildonly-randconfig-003-20260502 gcc-14
i386 buildonly-randconfig-004 gcc-14
i386 buildonly-randconfig-004-20260502 gcc-14
i386 buildonly-randconfig-005 gcc-14
i386 buildonly-randconfig-005-20260502 gcc-14
i386 buildonly-randconfig-006 gcc-14
i386 buildonly-randconfig-006-20260502 gcc-14
i386 defconfig gcc-15.2.0
i386 randconfig-011-20260502 clang-20
i386 randconfig-012-20260502 clang-20
i386 randconfig-013-20260502 clang-20
i386 randconfig-014-20260502 clang-20
i386 randconfig-015-20260502 clang-20
i386 randconfig-016-20260502 clang-20
i386 randconfig-017-20260502 clang-20
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260502 clang-23
loongarch randconfig-002-20260502 clang-23
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 defconfig clang-19
nios2 randconfig-001-20260502 clang-23
nios2 randconfig-002-20260502 clang-23
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc defconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260502 gcc-12.5.0
parisc randconfig-002-20260502 gcc-12.5.0
parisc64 defconfig clang-19
powerpc allnoconfig clang-23
powerpc randconfig-001-20260502 gcc-12.5.0
powerpc randconfig-002-20260502 gcc-12.5.0
powerpc64 randconfig-001-20260502 gcc-12.5.0
powerpc64 randconfig-002-20260502 gcc-12.5.0
riscv allnoconfig clang-23
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260502 gcc-14.3.0
riscv randconfig-002-20260502 gcc-14.3.0
s390 allnoconfig clang-23
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260502 gcc-14.3.0
s390 randconfig-002-20260502 gcc-14.3.0
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh randconfig-001-20260502 gcc-14.3.0
sh randconfig-002-20260502 gcc-14.3.0
sh rts7751r2d1_defconfig gcc-15.2.0
sparc allnoconfig clang-23
sparc defconfig gcc-15.2.0
sparc64 allmodconfig clang-23
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
x86_64 allnoconfig clang-23
x86_64 buildonly-randconfig-001-20260502 clang-20
x86_64 buildonly-randconfig-002-20260502 clang-20
x86_64 buildonly-randconfig-003-20260502 clang-20
x86_64 buildonly-randconfig-004-20260502 clang-20
x86_64 buildonly-randconfig-005-20260502 clang-20
x86_64 buildonly-randconfig-006-20260502 clang-20
x86_64 kexec clang-20
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
xtensa allnoconfig clang-23
xtensa allyesconfig clang-23
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [QUESTION] (Resent) HID: Lenovo Essential Gen2 Fnlock patch (1a81:1026)
From: Annmay Arora @ 2026-05-02 15:09 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel
Resending with corrected recipient headers (previous mail missed LKML).
Hi,
I am working on:
(lsusb output)> 1a81:1026 Holtek Semiconductor, Inc. Lenovo
Essential Wireless Combo Keyboard & Mouse Gen2
to bring the F-Keys functionality to parity as under Windows. The
peripheral was observed with Wireshark (4.6.5) and USBpcap on a windows
11 LTSC install. The SET_REPORT to unlock proper functionality from F-keys
is as follows:
ON -> 06 ce 01 01 00 00 00 00
OFF -> 06 ce 00 01 00 00 00 00
where the ON state allows the user to interact with media keys (Eg. Fn +
F1 allows us to toggle mute).
This was tested successfully with a python script to enable the Fnlock
and later with a out-of-kernel module which is not production ready and
would ideally not be upstreamed. I have a few questions in order to
attempt to send a few patches.
Questions:
1) The device presents itself to be Holtek, but does not seem to match
the manufacturer code. Would another id need to be added to
drivers/hid/hid-ids.h
2) Would it be ideal to create a hid-holtek-lenovo.c or should one of
the existing drivers should be modified? If yes, which one?
TIA,
Annmay Arora
^ permalink raw reply
* [PATCH v6 7/7] power: supply: Add charger driver for Asus Transformers
From: Svyatoslav Ryhel @ 2026-05-02 12:40 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Add support for charger detection capabilities found in the embedded
controller of ASUS Transformer devices.
Suggested-by: Maxim Schwalm <maxim.schwalm@gmail.com>
Suggested-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
.../supply/asus-transformer-ec-charger.c | 193 ++++++++++++++++++
3 files changed, 205 insertions(+)
create mode 100644 drivers/power/supply/asus-transformer-ec-charger.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 1dc3d0b2e021..ebc6d5c01330 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -508,6 +508,17 @@ config CHARGER_88PM860X
help
Say Y here to enable charger for Marvell 88PM860x chip.
+config CHARGER_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's charger driver"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y here to enable support AC plug detection on Asus Transformer
+ Dock.
+
+ This sub-driver supports charger detection mechanism found in Asus
+ Transformer tablets and mobile docks and controlled by special
+ embedded controller.
+
config CHARGER_PF1550
tristate "NXP PF1550 battery charger driver"
depends on MFD_PF1550
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 1313f367715c..93d17d28081e 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_CHARGER_RT9471) += rt9471.o
obj-$(CONFIG_CHARGER_RT9756) += rt9756.o
obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
+obj-$(CONFIG_CHARGER_ASUS_TRANSFORMER_EC) += asus-transformer-ec-charger.o
obj-$(CONFIG_CHARGER_PF1550) += pf1550-charger.o
obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o ab8500_chargalg.o
diff --git a/drivers/power/supply/asus-transformer-ec-charger.c b/drivers/power/supply/asus-transformer-ec-charger.c
new file mode 100644
index 000000000000..de01f0bf2fd7
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-charger.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/err.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+
+struct asus_ec_charger_data {
+ struct notifier_block nb;
+ const struct asusec_info *ec;
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+};
+
+static enum power_supply_property asus_ec_charger_properties[] = {
+ POWER_SUPPLY_PROP_USB_TYPE,
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int asus_ec_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
+ enum power_supply_usb_type psu;
+ int ret;
+ u64 ctl;
+
+ ret = asus_ec_get_ctl(priv->ec, &ctl);
+ if (ret)
+ return ret;
+
+ switch (ctl & (ASUSEC_CTL_FULL_POWER_SOURCE | ASUSEC_CTL_DIRECT_POWER_SOURCE)) {
+ case ASUSEC_CTL_FULL_POWER_SOURCE:
+ psu = POWER_SUPPLY_USB_TYPE_CDP; /* DOCK */
+ break;
+ case ASUSEC_CTL_DIRECT_POWER_SOURCE:
+ psu = POWER_SUPPLY_USB_TYPE_SDP; /* USB */
+ break;
+ case 0:
+ psu = POWER_SUPPLY_USB_TYPE_UNKNOWN; /* no power source connected */
+ break;
+ default:
+ psu = POWER_SUPPLY_USB_TYPE_ACA; /* power adapter */
+ break;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = psu != POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ return 0;
+
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = psu;
+ return 0;
+
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ if (ctl & ASUSEC_CTL_TEST_DISCHARGE)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
+ else if (ctl & ASUSEC_CTL_USB_CHARGE)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ return 0;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = priv->ec->model;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int asus_ec_charger_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ switch ((enum power_supply_charge_behaviour)val->intval) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ return asus_ec_update_ctl(priv->ec,
+ ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
+ ASUSEC_CTL_USB_CHARGE);
+
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ return asus_ec_clear_ctl_bits(priv->ec,
+ ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE);
+
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+ return asus_ec_update_ctl(priv->ec,
+ ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
+ ASUSEC_CTL_TEST_DISCHARGE);
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int asus_ec_charger_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct power_supply_desc asus_ec_charger_desc = {
+ .name = "asus-ec-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) |
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) |
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE),
+ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) |
+ BIT(POWER_SUPPLY_USB_TYPE_SDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_CDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_ACA),
+ .properties = asus_ec_charger_properties,
+ .num_properties = ARRAY_SIZE(asus_ec_charger_properties),
+ .get_property = asus_ec_charger_get_property,
+ .set_property = asus_ec_charger_set_property,
+ .property_is_writeable = asus_ec_charger_property_is_writeable,
+ .no_thermal = true,
+};
+
+static int asus_ec_charger_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct asus_ec_charger_data *priv =
+ container_of(nb, struct asus_ec_charger_data, nb);
+
+ switch (action) {
+ case ASUSEC_SMI_ACTION(POWER_NOTIFY):
+ case ASUSEC_SMI_ACTION(ADAPTER_EVENT):
+ power_supply_changed(priv->psy);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int asus_ec_charger_probe(struct platform_device *pdev)
+{
+ struct asus_ec_charger_data *priv;
+ struct device *dev = &pdev->dev;
+ struct power_supply_config cfg = { };
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->ec = cell_to_ec(pdev);
+
+ cfg.fwnode = dev_fwnode(dev->parent);
+ cfg.drv_data = priv;
+
+ memcpy(&priv->psy_desc, &asus_ec_charger_desc, sizeof(priv->psy_desc));
+ priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-charger",
+ priv->ec->name);
+
+ priv->psy = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
+ if (IS_ERR(priv->psy))
+ return dev_err_probe(dev, PTR_ERR(priv->psy),
+ "Failed to register power supply\n");
+
+ priv->nb.notifier_call = asus_ec_charger_notify;
+
+ return devm_asus_ec_register_notifier(pdev, &priv->nb);
+}
+
+static struct platform_driver asus_ec_charger_driver = {
+ .driver.name = "asus-transformer-ec-charger",
+ .probe = asus_ec_charger_probe,
+};
+module_platform_driver(asus_ec_charger_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer Pad battery charger driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v6 6/7] power: supply: Add driver for ASUS Transformer battery
From: Svyatoslav Ryhel @ 2026-05-02 12:40 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Driver implements one battery cell per EC controller and supports reading
of battery status for ASUS Transformer's pad and mobile dock.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
.../supply/asus-transformer-ec-battery.c | 272 ++++++++++++++++++
3 files changed, 284 insertions(+)
create mode 100644 drivers/power/supply/asus-transformer-ec-battery.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 83392ed6a8da..1dc3d0b2e021 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -122,6 +122,17 @@ config BATTERY_CHAGALL
This driver can also be built as a module. If so, the module will be
called chagall-battery.
+config BATTERY_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's battery driver"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y to enable support for battery status access on Tegra based
+ ASUS Transformer devices.
+
+ This sub-driver supports battery cells found in Asus Transformer
+ tablets and mobile docks and controlled by a special embedded
+ controller.
+
config BATTERY_CPCAP
tristate "Motorola CPCAP PMIC battery driver"
depends on MFD_CPCAP && IIO
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 7ee839dca7f3..1313f367715c 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_TEST_POWER) += test_power.o
obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
obj-$(CONFIG_CHARGER_ADP5061) += adp5061.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
+obj-$(CONFIG_BATTERY_ASUS_TRANSFORMER_EC) += asus-transformer-ec-battery.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o
diff --git a/drivers/power/supply/asus-transformer-ec-battery.c b/drivers/power/supply/asus-transformer-ec-battery.c
new file mode 100644
index 000000000000..aefcd3fed6fe
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-battery.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/devm-helpers.h>
+#include <linux/err.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/unaligned.h>
+
+#define ASUSEC_BATTERY_DATA_FRESH_MSEC 5000
+
+#define ASUSEC_BATTERY_DISCHARGING 0x40
+#define ASUSEC_BATTERY_FULL_CHARGED 0x20
+#define ASUSEC_BATTERY_NOT_CHARGING 0x10
+
+#define TEMP_CELSIUS_OFFSET 2731
+
+struct asus_ec_battery_data {
+ const struct asusec_info *ec;
+ struct power_supply *battery;
+ struct power_supply_desc psy_desc;
+ struct delayed_work poll_work;
+ struct mutex battery_lock; /* for data refresh */
+ unsigned long batt_data_ts;
+ int last_state;
+ u8 batt_data[DOCKRAM_ENTRY_BUFSIZE];
+};
+
+static int asus_ec_battery_refresh(struct asus_ec_battery_data *priv)
+{
+ int ret = 0;
+
+ guard(mutex)(&priv->battery_lock);
+
+ if (time_before(jiffies, priv->batt_data_ts))
+ return ret;
+
+ ret = asus_dockram_read(priv->ec->dockram, ASUSEC_DOCKRAM_BATT_CTL,
+ priv->batt_data);
+ if (ret < 0)
+ return ret;
+
+ priv->batt_data_ts = jiffies +
+ msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC);
+
+ return ret;
+}
+
+static enum power_supply_property asus_ec_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static const unsigned int asus_ec_battery_prop_offs[] = {
+ [POWER_SUPPLY_PROP_STATUS] = 1,
+ [POWER_SUPPLY_PROP_VOLTAGE_MAX] = 3,
+ [POWER_SUPPLY_PROP_CURRENT_MAX] = 5,
+ [POWER_SUPPLY_PROP_TEMP] = 7,
+ [POWER_SUPPLY_PROP_VOLTAGE_NOW] = 9,
+ [POWER_SUPPLY_PROP_CURRENT_NOW] = 11,
+ [POWER_SUPPLY_PROP_CAPACITY] = 13,
+ [POWER_SUPPLY_PROP_CHARGE_NOW] = 15,
+ [POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW] = 17,
+ [POWER_SUPPLY_PROP_TIME_TO_FULL_NOW] = 19,
+};
+
+static int asus_ec_battery_get_value(struct asus_ec_battery_data *priv,
+ enum power_supply_property psp)
+{
+ int ret, offs;
+
+ if (psp >= ARRAY_SIZE(asus_ec_battery_prop_offs))
+ return -EINVAL;
+
+ offs = asus_ec_battery_prop_offs[psp];
+ if (!offs)
+ return -EINVAL;
+
+ ret = asus_ec_battery_refresh(priv);
+ if (ret < 0)
+ return ret;
+
+ if (offs >= priv->batt_data[0])
+ return -ENODATA;
+
+ return get_unaligned_le16(priv->batt_data + offs);
+}
+
+static int asus_ec_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct asus_ec_battery_data *priv = power_supply_get_drvdata(psy);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+
+ default:
+ ret = asus_ec_battery_get_value(priv, psp);
+ if (ret < 0)
+ return ret;
+
+ val->intval = (s16)ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (ret & ASUSEC_BATTERY_FULL_CHARGED)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (ret & ASUSEC_BATTERY_NOT_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (ret & ASUSEC_BATTERY_DISCHARGING)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval -= TEMP_CELSIUS_OFFSET;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval *= 1000;
+ break;
+
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ val->intval *= 60;
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+static void asus_ec_battery_poll_work(struct work_struct *work)
+{
+ struct asus_ec_battery_data *priv =
+ container_of(work, struct asus_ec_battery_data, poll_work.work);
+ int state;
+
+ state = asus_ec_battery_get_value(priv, POWER_SUPPLY_PROP_STATUS);
+ if (state < 0)
+ return;
+
+ if (state & ASUSEC_BATTERY_FULL_CHARGED)
+ state = POWER_SUPPLY_STATUS_FULL;
+ else if (state & ASUSEC_BATTERY_DISCHARGING)
+ state = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ state = POWER_SUPPLY_STATUS_CHARGING;
+
+ if (priv->last_state != state) {
+ priv->last_state = state;
+ power_supply_changed(priv->battery);
+ }
+
+ /* continuously send uevent notification */
+ schedule_delayed_work(&priv->poll_work,
+ msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
+}
+
+static const struct power_supply_desc asus_ec_battery_desc = {
+ .name = "asus-ec-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = asus_ec_battery_properties,
+ .num_properties = ARRAY_SIZE(asus_ec_battery_properties),
+ .get_property = asus_ec_battery_get_property,
+ .external_power_changed = power_supply_changed,
+};
+
+static int asus_ec_battery_probe(struct platform_device *pdev)
+{
+ struct asus_ec_battery_data *priv;
+ struct device *dev = &pdev->dev;
+ struct power_supply_config cfg = { };
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ mutex_init(&priv->battery_lock);
+
+ priv->ec = cell_to_ec(pdev);
+ priv->batt_data_ts = jiffies - 1;
+ priv->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ cfg.fwnode = dev_fwnode(dev->parent);
+ cfg.drv_data = priv;
+
+ memcpy(&priv->psy_desc, &asus_ec_battery_desc, sizeof(priv->psy_desc));
+ priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-battery",
+ priv->ec->name);
+
+ priv->battery = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
+ if (IS_ERR(priv->battery))
+ return dev_err_probe(dev, PTR_ERR(priv->battery),
+ "Failed to register power supply\n");
+
+ ret = devm_delayed_work_autocancel(dev, &priv->poll_work,
+ asus_ec_battery_poll_work);
+ if (ret)
+ return ret;
+
+ schedule_delayed_work(&priv->poll_work,
+ msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
+
+ return 0;
+}
+
+static int __maybe_unused asus_ec_battery_suspend(struct device *dev)
+{
+ struct asus_ec_battery_data *priv = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&priv->poll_work);
+
+ return 0;
+}
+
+static int __maybe_unused asus_ec_battery_resume(struct device *dev)
+{
+ struct asus_ec_battery_data *priv = dev_get_drvdata(dev);
+
+ schedule_delayed_work(&priv->poll_work,
+ msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(asus_ec_battery_pm_ops,
+ asus_ec_battery_suspend, asus_ec_battery_resume);
+
+static struct platform_driver asus_ec_battery_driver = {
+ .driver = {
+ .name = "asus-transformer-ec-battery",
+ .pm = &asus_ec_battery_pm_ops,
+ },
+ .probe = asus_ec_battery_probe,
+};
+module_platform_driver(asus_ec_battery_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("ASUS Transformer's battery driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v6 5/7] leds: Add driver for ASUS Transformer LEDs
From: Svyatoslav Ryhel @ 2026-05-02 12:40 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
ASUS Transformer tablets have a green and an amber LED on both the Pad
and the Dock. If both LEDs are enabled simultaneously, the emitted light
will be yellow.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
---
drivers/leds/Kconfig | 11 ++++
drivers/leds/Makefile | 1 +
drivers/leds/leds-asus-transformer-ec.c | 79 +++++++++++++++++++++++++
3 files changed, 91 insertions(+)
create mode 100644 drivers/leds/leds-asus-transformer-ec.c
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index f4a0a3c8c870..f637d23400a8 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -120,6 +120,17 @@ config LEDS_OSRAM_AMS_AS3668
To compile this driver as a module, choose M here: the module
will be called leds-as3668.
+config LEDS_ASUS_TRANSFORMER_EC
+ tristate "LED Support for Asus Transformer charging LED"
+ depends on LEDS_CLASS
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ This option enables support for charging indicator on
+ Asus Transformer's Pad and it's Dock.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-asus-transformer-ec.
+
config LEDS_AW200XX
tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 8fdb45d5b439..d5395c3f1124 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
obj-$(CONFIG_LEDS_APU) += leds-apu.o
obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
obj-$(CONFIG_LEDS_AS3668) += leds-as3668.o
+obj-$(CONFIG_LEDS_ASUS_TRANSFORMER_EC) += leds-asus-transformer-ec.o
obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
diff --git a/drivers/leds/leds-asus-transformer-ec.c b/drivers/leds/leds-asus-transformer-ec.c
new file mode 100644
index 000000000000..3186038e3be7
--- /dev/null
+++ b/drivers/leds/leds-asus-transformer-ec.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/err.h>
+#include <linux/leds.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+static void asus_ec_led_set_brightness_amber(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ const struct asusec_info *ec = dev_get_drvdata(led->dev->parent);
+
+ if (brightness)
+ asus_ec_set_ctl_bits(ec, ASUSEC_CTL_LED_AMBER);
+ else
+ asus_ec_clear_ctl_bits(ec, ASUSEC_CTL_LED_AMBER);
+}
+
+static void asus_ec_led_set_brightness_green(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ const struct asusec_info *ec = dev_get_drvdata(led->dev->parent);
+
+ if (brightness)
+ asus_ec_set_ctl_bits(ec, ASUSEC_CTL_LED_GREEN);
+ else
+ asus_ec_clear_ctl_bits(ec, ASUSEC_CTL_LED_GREEN);
+}
+
+static int asus_ec_led_probe(struct platform_device *pdev)
+{
+ struct asusec_info *ec = cell_to_ec(pdev);
+ struct device *dev = &pdev->dev;
+ struct led_classdev *amber_led, *green_led;
+ int ret;
+
+ platform_set_drvdata(pdev, ec);
+
+ amber_led = devm_kzalloc(dev, sizeof(*amber_led), GFP_KERNEL);
+ if (!amber_led)
+ return -ENOMEM;
+
+ amber_led->name = devm_kasprintf(dev, GFP_KERNEL, "%s::amber", ec->name);
+ amber_led->max_brightness = 1;
+ amber_led->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ amber_led->brightness_set = asus_ec_led_set_brightness_amber;
+
+ ret = devm_led_classdev_register(dev, amber_led);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register amber LED\n");
+
+ green_led = devm_kzalloc(dev, sizeof(*green_led), GFP_KERNEL);
+ if (!green_led)
+ return -ENOMEM;
+
+ green_led->name = devm_kasprintf(dev, GFP_KERNEL, "%s::green", ec->name);
+ green_led->max_brightness = 1;
+ green_led->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ green_led->brightness_set = asus_ec_led_set_brightness_green;
+
+ ret = devm_led_classdev_register(dev, green_led);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register green LED\n");
+
+ return 0;
+}
+
+static struct platform_driver asus_ec_led_driver = {
+ .driver.name = "asus-transformer-ec-led",
+ .probe = asus_ec_led_probe,
+};
+module_platform_driver(asus_ec_led_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("ASUS Transformer's charging LED driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v6 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys
From: Svyatoslav Ryhel @ 2026-05-02 12:40 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Add support for multimedia top button row of ASUS Transformer's Mobile
Dock keyboard. Driver is made that function keys (F1-F12) are used by
default which suits average Linux use better and with pressing
ScreenLock + AltGr function keys layout is switched to multimedia keys.
Since this only modifies codes sent by asus-ec-keys it doesn't affect
normal keyboards at all.
Co-developed-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/input/keyboard/Kconfig | 10 +
drivers/input/keyboard/Makefile | 1 +
.../input/keyboard/asus-transformer-ec-keys.c | 272 ++++++++++++++++++
3 files changed, 283 insertions(+)
create mode 100644 drivers/input/keyboard/asus-transformer-ec-keys.c
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 9d1019ba0245..913cb4900565 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -89,6 +89,16 @@ config KEYBOARD_APPLESPI
To compile this driver as a module, choose M here: the
module will be called applespi.
+config KEYBOARD_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's Mobile Dock multimedia keys"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y here if you want to use multimedia keys present on Asus
+ Transformer's Mobile Dock.
+
+ To compile this driver as a module, choose M here: the
+ module will be called asus-transformer-ec-keys.
+
config KEYBOARD_ATARI
tristate "Atari keyboard"
depends on ATARI
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 60bb7baf802f..0d81096887ad 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_KEYBOARD_ADP5585) += adp5585-keys.o
obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o
obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o
obj-$(CONFIG_KEYBOARD_APPLESPI) += applespi.o
+obj-$(CONFIG_KEYBOARD_ASUS_TRANSFORMER_EC) += asus-transformer-ec-keys.o
obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o
obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o
obj-$(CONFIG_KEYBOARD_BCM) += bcm-keypad.o
diff --git a/drivers/input/keyboard/asus-transformer-ec-keys.c b/drivers/input/keyboard/asus-transformer-ec-keys.c
new file mode 100644
index 000000000000..02516ccb0b12
--- /dev/null
+++ b/drivers/input/keyboard/asus-transformer-ec-keys.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define ASUSEC_EXT_KEY_CODES 0x20
+
+struct asus_ec_keys_data {
+ struct notifier_block nb;
+ struct asusec_info *ec;
+ struct input_dev *xidev;
+ bool special_key_pressed;
+ bool special_key_mode;
+ unsigned short keymap[ASUSEC_EXT_KEY_CODES * 2];
+};
+
+static void asus_ec_input_event(struct input_handle *handle,
+ unsigned int event_type,
+ unsigned int event_code, int value)
+{
+ struct asus_ec_keys_data *priv = handle->handler->private;
+
+ /* Store special key state */
+ if (event_type == EV_KEY && event_code == KEY_RIGHTALT)
+ priv->special_key_pressed = !!value;
+}
+
+static int asus_ec_input_connect(struct input_handler *handler, struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_handle *handle;
+ int error;
+
+ handle = kzalloc_obj(*handle);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = "asusec-media-handler";
+
+ error = input_register_handle(handle);
+ if (error)
+ goto err_free_handle;
+
+ error = input_open_device(handle);
+ if (error)
+ goto err_unregister_handle;
+
+ return 0;
+
+ err_unregister_handle:
+ input_unregister_handle(handle);
+ err_free_handle:
+ kfree(handle);
+
+ return error;
+}
+
+static void asus_ec_input_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+static const struct input_device_id asus_ec_input_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_KEY) },
+ },
+ { }
+};
+
+static struct input_handler asus_ec_input_handler = {
+ .name = "asusec-media-handler",
+ .event = asus_ec_input_event,
+ .connect = asus_ec_input_connect,
+ .disconnect = asus_ec_input_disconnect,
+ .id_table = asus_ec_input_ids,
+};
+
+static const unsigned short asus_ec_dock_ext_keys[] = {
+ /* Function keys [0x00 - 0x19] */
+ [0x01] = KEY_DELETE,
+ [0x02] = KEY_F1,
+ [0x03] = KEY_F2,
+ [0x04] = KEY_F3,
+ [0x05] = KEY_F4,
+ [0x06] = KEY_F5,
+ [0x07] = KEY_F6,
+ [0x08] = KEY_F7,
+ [0x10] = KEY_F8,
+ [0x11] = KEY_F9,
+ [0x12] = KEY_F10,
+ [0x13] = KEY_F11,
+ [0x14] = KEY_F12,
+ [0x15] = KEY_MUTE,
+ [0x16] = KEY_VOLUMEDOWN,
+ [0x17] = KEY_VOLUMEUP,
+ /* Multimedia keys [0x20 - 0x39] */
+ [0x21] = KEY_SCREENLOCK,
+ [0x22] = KEY_WLAN,
+ [0x23] = KEY_BLUETOOTH,
+ [0x24] = KEY_TOUCHPAD_TOGGLE,
+ [0x25] = KEY_BRIGHTNESSDOWN,
+ [0x26] = KEY_BRIGHTNESSUP,
+ [0x27] = KEY_BRIGHTNESS_AUTO,
+ [0x28] = KEY_PRINT,
+ [0x30] = KEY_WWW,
+ [0x31] = KEY_CONFIG,
+ [0x32] = KEY_PREVIOUSSONG,
+ [0x33] = KEY_PLAYPAUSE,
+ [0x34] = KEY_NEXTSONG,
+ [0x35] = KEY_MUTE,
+ [0x36] = KEY_VOLUMEDOWN,
+ [0x37] = KEY_VOLUMEUP,
+};
+
+static void asus_ec_keys_report_key(struct input_dev *dev, unsigned int code,
+ unsigned int key, bool value)
+{
+ input_event(dev, EV_MSC, MSC_SCAN, code);
+ input_report_key(dev, key, value);
+ input_sync(dev);
+}
+
+static int asus_ec_keys_process_key(struct input_dev *dev, u8 code)
+{
+ struct asus_ec_keys_data *priv = dev_get_drvdata(dev->dev.parent);
+ unsigned int key = 0;
+
+ if (code == 0)
+ return NOTIFY_DONE;
+
+ /* Flip special key mode state when pressing key 1 with special key pressed */
+ if (priv->special_key_pressed && code == 1) {
+ priv->special_key_mode = !priv->special_key_mode;
+ return NOTIFY_DONE;
+ }
+
+ /*
+ * Relocate code to second "page" if pressed state XOR's mode state
+ * This way special key will invert the current mode
+ */
+ if (priv->special_key_mode ^ priv->special_key_pressed)
+ code += ASUSEC_EXT_KEY_CODES;
+
+ if (code < dev->keycodemax) {
+ unsigned short *map = dev->keycode;
+
+ key = map[code];
+ }
+
+ if (!key)
+ key = KEY_UNKNOWN;
+
+ asus_ec_keys_report_key(dev, code, key, 1);
+ asus_ec_keys_report_key(dev, code, key, 0);
+
+ return NOTIFY_OK;
+}
+
+static int asus_ec_keys_notify(struct notifier_block *nb,
+ unsigned long action, void *data_)
+{
+ struct asus_ec_keys_data *priv = container_of(nb, struct asus_ec_keys_data, nb);
+ u8 *data = data_;
+
+ if (action & ASUSEC_SMI_MASK)
+ return NOTIFY_DONE;
+
+ if (action & ASUSEC_SCI_MASK)
+ return asus_ec_keys_process_key(priv->xidev, data[2]);
+
+ return NOTIFY_DONE;
+}
+
+static void asus_ec_keys_setup_keymap(struct asus_ec_keys_data *priv)
+{
+ struct input_dev *dev = priv->xidev;
+ unsigned int i;
+
+ BUILD_BUG_ON(ARRAY_SIZE(priv->keymap) < ARRAY_SIZE(asus_ec_dock_ext_keys));
+
+ dev->keycode = priv->keymap;
+ dev->keycodesize = sizeof(*priv->keymap);
+ dev->keycodemax = ARRAY_SIZE(priv->keymap);
+
+ input_set_capability(dev, EV_MSC, MSC_SCAN);
+ input_set_capability(dev, EV_KEY, KEY_UNKNOWN);
+
+ for (i = 0; i < ARRAY_SIZE(asus_ec_dock_ext_keys); i++) {
+ unsigned int code = asus_ec_dock_ext_keys[i];
+
+ if (!code)
+ continue;
+
+ __set_bit(code, dev->keybit);
+ priv->keymap[i] = code;
+ }
+}
+
+static void asus_ec_input_handler_deregister(void *priv)
+{
+ input_unregister_handler(&asus_ec_input_handler);
+}
+
+static int asus_ec_keys_probe(struct platform_device *pdev)
+{
+ struct asusec_info *ec = cell_to_ec(pdev);
+ struct i2c_client *parent = to_i2c_client(pdev->dev.parent);
+ struct asus_ec_keys_data *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->ec = ec;
+
+ priv->xidev = devm_input_allocate_device(&pdev->dev);
+ if (!priv->xidev)
+ return -ENOMEM;
+
+ priv->xidev->name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "%s Keyboard Ext", ec->model);
+ priv->xidev->phys = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "i2c-%u-%04x",
+ i2c_adapter_id(parent->adapter),
+ parent->addr);
+ asus_ec_keys_setup_keymap(priv);
+
+ ret = input_register_device(priv->xidev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register extension keys: %d\n",
+ ret);
+ return ret;
+ }
+
+ asus_ec_input_handler.private = priv;
+
+ ret = input_register_handler(&asus_ec_input_handler);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(&pdev->dev, asus_ec_input_handler_deregister,
+ priv);
+ if (ret)
+ return ret;
+
+ priv->nb.notifier_call = asus_ec_keys_notify;
+
+ return devm_asus_ec_register_notifier(pdev, &priv->nb);
+}
+
+static struct platform_driver asus_ec_keys_driver = {
+ .driver.name = "asus-transformer-ec-keys",
+ .probe = asus_ec_keys_probe,
+};
+module_platform_driver(asus_ec_keys_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer's multimedia keys driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v6 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
From: Svyatoslav Ryhel @ 2026-05-02 12:40 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Add input driver for ASUS Transformer dock keyboard and touchpad.
Some keys in ASUS Dock report keycodes that don't make sense according to
their position, this patch modifies the incoming data that is sent to
serio to send proper scancodes.
Co-developed-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/input/serio/Kconfig | 15 ++
drivers/input/serio/Makefile | 1 +
drivers/input/serio/asus-transformer-ec-kbc.c | 147 ++++++++++++++++++
3 files changed, 163 insertions(+)
create mode 100644 drivers/input/serio/asus-transformer-ec-kbc.c
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index 5f15a6462056..fad29b950309 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -84,6 +84,21 @@ config SERIO_RPCKBD
To compile this driver as a module, choose M here: the
module will be called rpckbd.
+config SERIO_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's Dock keyboard and touchpad controller"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y here if you want to use the keyboard and/or touchpad on
+ Asus Transformed's Mobile Dock.
+
+ For keyboard support you also need atkbd driver.
+
+ For touchpad support you also need psmouse driver with Elantech
+ touchpad option enabled.
+
+ To compile this driver as a module, choose M here: the module will
+ be called asus-transformer-ec-kbc.
+
config SERIO_AMBAKMI
tristate "AMBA KMI keyboard controller"
depends on ARM_AMBA
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 8ab98f4aa28d..fedc37ee102b 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_SERIO_SERPORT) += serport.o
obj-$(CONFIG_SERIO_RPCKBD) += rpckbd.o
obj-$(CONFIG_SERIO_SA1111) += sa1111ps2.o
obj-$(CONFIG_SERIO_AMBAKMI) += ambakmi.o
+obj-$(CONFIG_SERIO_ASUS_TRANSFORMER_EC) += asus-transformer-ec-kbc.o
obj-$(CONFIG_SERIO_Q40KBD) += q40kbd.o
obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o
obj-$(CONFIG_HP_SDC) += hp_sdc.o
diff --git a/drivers/input/serio/asus-transformer-ec-kbc.c b/drivers/input/serio/asus-transformer-ec-kbc.c
new file mode 100644
index 000000000000..47fd6e48c989
--- /dev/null
+++ b/drivers/input/serio/asus-transformer-ec-kbc.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/i8042.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/serio.h>
+
+struct asus_ec_kbc_data {
+ struct notifier_block nb;
+ struct asusec_info *ec;
+ struct serio *sdev[2];
+};
+
+static int asus_ec_kbc_notify(struct notifier_block *nb,
+ unsigned long action, void *data_)
+{
+ struct asus_ec_kbc_data *priv = container_of(nb, struct asus_ec_kbc_data, nb);
+ unsigned int port_idx, n;
+ u8 *data = data_;
+
+ if (action & (ASUSEC_SMI_MASK | ASUSEC_SCI_MASK))
+ return NOTIFY_DONE;
+ else if (action & ASUSEC_AUX_MASK)
+ port_idx = 1;
+ else if (action & (ASUSEC_KBC_MASK | ASUSEC_KEY_MASK))
+ port_idx = 0;
+ else
+ return NOTIFY_DONE;
+
+ n = data[0] - 1;
+ data += 2;
+
+ /*
+ * We need to replace these incoming data for keys:
+ * RIGHT_META Press 0xE0 0x27 -> LEFT_ALT Press 0x11
+ * RIGHT_META Release 0xE0 0xF0 0x27 -> LEFT_ALT Release 0xF0 0x11
+ * COMPOSE Press 0xE0 0x2F -> RIGHT_META Press 0xE0 0x27
+ * COMPOSE Release 0xE0 0xF0 0x2F -> RIGHT_META Release 0xE0 0xF0 0x27
+ */
+
+ if (port_idx == 0 && n >= 2 && data[0] == 0xE0) {
+ if (n == 3 && data[1] == 0xF0) {
+ switch (data[2]) {
+ case 0x27:
+ data[0] = 0xF0;
+ data[1] = 0x11;
+ n = 2;
+ break;
+ case 0x2F:
+ data[2] = 0x27;
+ break;
+ }
+ } else if (n == 2) {
+ switch (data[1]) {
+ case 0x27:
+ data[0] = 0x11;
+ n = 1;
+ break;
+ case 0x2F:
+ data[1] = 0x27;
+ break;
+ }
+ }
+ }
+
+ while (n--)
+ serio_interrupt(priv->sdev[port_idx], *data++, 0);
+
+ return NOTIFY_OK;
+}
+
+static int asus_ec_serio_write(struct serio *port, unsigned char data)
+{
+ const struct asusec_info *ec = port->port_data;
+
+ return asus_ec_i2c_command(ec, (data << 8) | port->id.extra);
+}
+
+static void asus_ec_serio_remove(void *data)
+{
+ serio_unregister_port(data);
+}
+
+static int asus_ec_register_serio(struct platform_device *pdev, int idx,
+ const char *name, int cmd)
+{
+ struct asus_ec_kbc_data *priv = platform_get_drvdata(pdev);
+ struct i2c_client *parent = to_i2c_client(pdev->dev.parent);
+ struct serio *port = kzalloc_obj(*port);
+
+ if (!port)
+ return -ENOMEM;
+
+ priv->sdev[idx] = port;
+ port->dev.parent = &pdev->dev;
+ port->id.type = SERIO_8042;
+ port->id.extra = cmd & 0xFF;
+ port->write = asus_ec_serio_write;
+ port->port_data = (void *)priv->ec;
+ snprintf(port->name, sizeof(port->name), "%s %s",
+ priv->ec->model, name);
+ snprintf(port->phys, sizeof(port->phys), "i2c-%u-%04x/serio%d",
+ i2c_adapter_id(parent->adapter), parent->addr, idx);
+
+ serio_register_port(port);
+
+ return devm_add_action_or_reset(&pdev->dev, asus_ec_serio_remove, port);
+}
+
+static int asus_ec_kbc_probe(struct platform_device *pdev)
+{
+ struct asusec_info *ec = cell_to_ec(pdev);
+ struct asus_ec_kbc_data *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->ec = ec;
+
+ ret = asus_ec_register_serio(pdev, 0, "Keyboard", 0);
+ if (ret < 0)
+ return ret;
+
+ ret = asus_ec_register_serio(pdev, 1, "Touchpad", I8042_CMD_AUX_SEND);
+ if (ret < 0)
+ return ret;
+
+ priv->nb.notifier_call = asus_ec_kbc_notify;
+
+ return devm_asus_ec_register_notifier(pdev, &priv->nb);
+}
+
+static struct platform_driver asus_ec_kbc_driver = {
+ .driver.name = "asus-transformer-ec-kbc",
+ .probe = asus_ec_kbc_probe,
+};
+module_platform_driver(asus_ec_kbc_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer's Dock keyboard and touchpad controller driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v6 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-05-02 12:40 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Support Nuvoton NPCE795-based ECs as used in Asus Transformer TF201,
TF300T, TF300TG, TF300TL and TF700T pad and dock, as well as TF101 dock
and TF600T, P1801-T and TF701T pad. This is a glue driver handling
detection and common operations for EC's functions.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
---
drivers/mfd/Kconfig | 14 +
drivers/mfd/Makefile | 1 +
drivers/mfd/asus-transformer-ec.c | 762 ++++++++++++++++++++++++
include/linux/mfd/asus-transformer-ec.h | 162 +++++
4 files changed, 939 insertions(+)
create mode 100644 drivers/mfd/asus-transformer-ec.c
create mode 100644 include/linux/mfd/asus-transformer-ec.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..5aa4facfd2df 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -137,6 +137,20 @@ config MFD_AAT2870_CORE
additional drivers must be enabled in order to use the
functionality of the device.
+config MFD_ASUS_TRANSFORMER_EC
+ tristate "ASUS Transformer's embedded controller"
+ depends on I2C && OF
+ help
+ Support ECs found in ASUS Transformer's Pad and Mobile Dock.
+
+ This provides shared glue for functional part drivers:
+ asus-transformer-ec-kbc, asus-transformer-ec-keys,
+ leds-asus-transformer-ec, asus-transformer-ec-battery
+ and asus-transformer-ec-charger.
+
+ This driver can also be built as a module. If so, the module
+ will be called asus-transformer-ec.
+
config MFD_AT91_USART
tristate "AT91 USART Driver"
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..fd80088d8a9a 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o
obj-$(CONFIG_MFD_88PM886_PMIC) += 88pm886.o
obj-$(CONFIG_MFD_ACT8945A) += act8945a.o
obj-$(CONFIG_MFD_SM501) += sm501.o
+obj-$(CONFIG_MFD_ASUS_TRANSFORMER_EC) += asus-transformer-ec.o
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
diff --git a/drivers/mfd/asus-transformer-ec.c b/drivers/mfd/asus-transformer-ec.c
new file mode 100644
index 000000000000..75aa7ab99387
--- /dev/null
+++ b/drivers/mfd/asus-transformer-ec.c
@@ -0,0 +1,762 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/mfd/core.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#define ASUSEC_RSP_BUFFER_SIZE 8
+
+struct asus_ec_chip_data {
+ const char *name;
+ const struct mfd_cell *mfd_devices;
+ unsigned int num_devices;
+ bool clr_fmode; /* clear Factory Mode bit in EC control register */
+};
+
+struct asus_ec_data {
+ struct asusec_info info;
+ struct mutex ecreq_lock; /* prevent simultaneous access */
+ struct gpio_desc *ecreq;
+ struct i2c_client *self;
+ const struct asus_ec_chip_data *data;
+ char ec_data[DOCKRAM_ENTRY_BUFSIZE];
+ bool logging_disabled;
+};
+
+struct dockram_ec_data {
+ struct mutex ctl_lock; /* prevent simultaneous access */
+ char ctl_data[DOCKRAM_ENTRY_BUFSIZE];
+};
+
+#define to_ec_data(ec) \
+ container_of(ec, struct asus_ec_data, info)
+
+/**
+ * asus_dockram_read - Read a register from the DockRAM device.
+ * @client: Handle to the DockRAM device.
+ * @reg: Register to read.
+ * @buf: Byte array into which data will be read; must be large enough to
+ * hold the data returned by the DockRAM.
+ *
+ * This executes the DockRAM read based on the SMBus "block read" protocol
+ * or its emulation. It extracts DOCKRAM_ENTRY_SIZE bytes from the set
+ * register address.
+ *
+ * Returns a negative errno code else zero on success.
+ */
+int asus_dockram_read(struct i2c_client *client, int reg, char *buf)
+{
+ struct device *dev = &client->dev;
+ int ret;
+
+ memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
+ ret = i2c_smbus_read_i2c_block_data(client, reg,
+ DOCKRAM_ENTRY_BUFSIZE, buf);
+ if (ret < 0)
+ return ret;
+
+ if (buf[0] > DOCKRAM_ENTRY_SIZE) {
+ dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
+ DOCKRAM_ENTRY_BUFSIZE, buf, ret);
+ return -EPROTO;
+ }
+
+ dev_dbg(dev, "got data; buffer: %*ph; ret: %d\n",
+ DOCKRAM_ENTRY_BUFSIZE, buf, ret);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_dockram_read);
+
+/**
+ * asus_dockram_write - Write a byte array to a register of the DockRAM device.
+ * @client: Handle to the DockRAM device.
+ * @reg: Register to write to.
+ * @buf: Byte array to be written (up to DOCKRAM_ENTRY_SIZE bytes).
+ *
+ * This executes the DockRAM write based on the SMBus "block write"
+ * protocol or its emulation. It writes DOCKRAM_ENTRY_SIZE bytes to the
+ * specified register address.
+ *
+ * Returns a negative errno code else zero on success.
+ */
+int asus_dockram_write(struct i2c_client *client, int reg, const char *buf)
+{
+ if (buf[0] > DOCKRAM_ENTRY_SIZE)
+ return -EINVAL;
+
+ dev_dbg(&client->dev, "sending data; buffer: %*ph\n", buf[0] + 1, buf);
+
+ return i2c_smbus_write_i2c_block_data(client, reg, buf[0] + 1, buf);
+}
+EXPORT_SYMBOL_GPL(asus_dockram_write);
+
+/**
+ * asus_dockram_access_ctl - Read from or write to the DockRAM control register.
+ * @client: Handle to the DockRAM device.
+ * @out: Pointer to a variable where the register value will be stored.
+ * @mask: Bitmask of bits to be cleared.
+ * @xor: Bitmask of bits to be set (via XOR).
+ *
+ * This performs a control register read if @out is provided and both @mask
+ * and @xor are zero. Otherwise, it performs a control register update if
+ * @mask and @xor are provided.
+ *
+ * Returns a negative errno code else zero on success.
+ */
+int asus_dockram_access_ctl(struct i2c_client *client, u64 *out, u64 mask,
+ u64 xor)
+{
+ struct dockram_ec_data *priv = i2c_get_clientdata(client);
+ char *buf = priv->ctl_data;
+ u64 val;
+ int ret = 0;
+
+ guard(mutex)(&priv->ctl_lock);
+
+ ret = asus_dockram_read(client, ASUSEC_DOCKRAM_CONTROL, buf);
+ if (ret < 0)
+ goto exit;
+
+ if (buf[0] != ASUSEC_CTL_SIZE) {
+ ret = -EPROTO;
+ goto exit;
+ }
+
+ val = get_unaligned_le64(buf + 1);
+
+ if (out)
+ *out = val;
+
+ if (mask || xor) {
+ put_unaligned_le64((val & ~mask) ^ xor, buf + 1);
+ ret = asus_dockram_write(client, ASUSEC_DOCKRAM_CONTROL, buf);
+ }
+
+exit:
+ if (ret < 0)
+ dev_err(&client->dev, "Failed to access control flags: %d\n",
+ ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(asus_dockram_access_ctl);
+
+static void asus_ec_remove_notifier(struct device *dev, void *res)
+{
+ struct asusec_info *ec = dev_get_drvdata(dev->parent);
+ struct notifier_block **nb = res;
+
+ blocking_notifier_chain_unregister(&ec->notify_list, *nb);
+}
+
+/**
+ * devm_asus_ec_register_notifier - Managed registration of notifier to an
+ * ASUS EC blocking notifier chain.
+ * @pdev: Device requesting the notifier (used for resource management).
+ * @nb: Notifier block to be registered.
+ *
+ * Register a notifier to the ASUS EC blocking notifier chain. The notifier
+ * will be automatically unregistered when the requesting device is detached.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int devm_asus_ec_register_notifier(struct platform_device *pdev,
+ struct notifier_block *nb)
+{
+ struct asusec_info *ec = dev_get_drvdata(pdev->dev.parent);
+ struct notifier_block **res;
+ int ret;
+
+ res = devres_alloc(asus_ec_remove_notifier, sizeof(*res), GFP_KERNEL);
+ if (!res)
+ return -ENOMEM;
+
+ *res = nb;
+ ret = blocking_notifier_chain_register(&ec->notify_list, nb);
+ if (ret) {
+ devres_free(res);
+ return ret;
+ }
+
+ devres_add(&pdev->dev, res);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_asus_ec_register_notifier);
+
+static int asus_ec_signal_request(const struct asusec_info *ec)
+{
+ struct asus_ec_data *priv = to_ec_data(ec);
+
+ guard(mutex)(&priv->ecreq_lock);
+
+ dev_dbg(&priv->self->dev, "EC request\n");
+
+ gpiod_set_value_cansleep(priv->ecreq, 1);
+ msleep(50);
+
+ gpiod_set_value_cansleep(priv->ecreq, 0);
+ msleep(200);
+
+ return 0;
+}
+
+static int asus_ec_write(struct asus_ec_data *priv, u16 data)
+{
+ int ret = i2c_smbus_write_word_data(priv->self, ASUSEC_WRITE_BUF, data);
+
+ dev_dbg(&priv->self->dev, "EC write: %04x, ret = %d\n", data, ret);
+
+ return ret;
+}
+
+static int asus_ec_read(struct asus_ec_data *priv, bool in_irq)
+{
+ int ret = i2c_smbus_read_i2c_block_data(priv->self, ASUSEC_READ_BUF,
+ sizeof(priv->ec_data),
+ priv->ec_data);
+
+ dev_dbg(&priv->self->dev, "EC read: %*ph, ret = %d%s\n",
+ sizeof(priv->ec_data), priv->ec_data,
+ ret, in_irq ? "; in irq" : "");
+
+ return ret;
+}
+
+/**
+ * asus_ec_i2c_command - Send a 16-bit command to the ASUS EC.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @data: The 16-bit command (word) to be sent.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int asus_ec_i2c_command(const struct asusec_info *ec, u16 data)
+{
+ return asus_ec_write(to_ec_data(ec), data);
+}
+EXPORT_SYMBOL_GPL(asus_ec_i2c_command);
+
+static void asus_ec_clear_buffer(struct asus_ec_data *priv)
+{
+ int retry = ASUSEC_RSP_BUFFER_SIZE;
+
+ while (retry--) {
+ if (asus_ec_read(priv, false) < 0)
+ continue;
+
+ if (priv->ec_data[1] & ASUSEC_OBF_MASK)
+ continue;
+
+ break;
+ }
+}
+
+static int asus_ec_log_info(struct asus_ec_data *priv, unsigned int reg,
+ const char *name, char **out)
+{
+ char buf[DOCKRAM_ENTRY_BUFSIZE];
+ int ret;
+
+ ret = asus_dockram_read(priv->info.dockram, reg, buf);
+ if (ret < 0)
+ return ret;
+
+ if (!priv->logging_disabled)
+ dev_info(&priv->self->dev, "%-14s: %.*s\n", name,
+ buf[0], buf + 1);
+
+ if (out)
+ *out = kstrndup(buf + 1, buf[0], GFP_KERNEL);
+
+ return 0;
+}
+
+static int asus_ec_reset(struct asus_ec_data *priv)
+{
+ int retry, ret;
+
+ for (retry = 0; retry < 3; retry++) {
+ ret = asus_ec_write(priv, 0);
+ if (!ret)
+ return 0;
+
+ msleep(300);
+ }
+
+ return ret;
+}
+
+static int asus_ec_magic_debug(struct asus_ec_data *priv)
+{
+ u64 flag;
+ int ret;
+
+ ret = asus_ec_get_ctl(&priv->info, &flag);
+ if (ret < 0)
+ return ret;
+
+ flag &= ASUSEC_CTL_SUSB_MODE;
+ dev_info(&priv->self->dev, "EC FW behaviour: %s\n",
+ flag ? "susb on when receive ec_req" :
+ "susb on when system wakeup");
+
+ return 0;
+}
+
+static int asus_ec_set_factory_mode(struct asus_ec_data *priv, bool on)
+{
+ dev_info(&priv->self->dev, "Entering %s mode.\n", on ? "factory" :
+ "normal");
+
+ return asus_ec_update_ctl(&priv->info, ASUSEC_CTL_FACTORY_MODE,
+ on ? ASUSEC_CTL_FACTORY_MODE : 0);
+}
+
+static int asus_ec_detect(struct asus_ec_data *priv)
+{
+ char *model = NULL;
+ int ret;
+
+ ret = asus_ec_reset(priv);
+ if (ret)
+ goto err_exit;
+
+ asus_ec_clear_buffer(priv);
+
+ ret = asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_MODEL, "model", &model);
+ if (ret)
+ goto err_exit;
+
+ ret = asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_FW, "FW version", NULL);
+ if (ret)
+ goto err_exit;
+
+ ret = asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_CFGFMT, "Config format", NULL);
+ if (ret)
+ goto err_exit;
+
+ ret = asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_HW, "HW version", NULL);
+ if (ret)
+ goto err_exit;
+
+ priv->logging_disabled = true;
+
+ ret = asus_ec_magic_debug(priv);
+ if (ret)
+ goto err_exit;
+
+ priv->info.model = model;
+ priv->info.name = priv->data->name;
+
+ if (priv->data->clr_fmode)
+ asus_ec_set_factory_mode(priv, false);
+
+err_exit:
+ if (ret)
+ dev_err(&priv->self->dev, "failed to access EC: %d\n", ret);
+
+ return ret;
+}
+
+static void asus_ec_handle_smi(struct asus_ec_data *priv, unsigned int code)
+{
+ dev_dbg(&priv->self->dev, "SMI interrupt: 0x%02x\n", code);
+
+ switch (code) {
+ case ASUSEC_SMI_HANDSHAKE:
+ case ASUSEC_SMI_RESET:
+ asus_ec_detect(priv);
+ break;
+ }
+}
+
+static irqreturn_t asus_ec_interrupt(int irq, void *dev_id)
+{
+ struct asus_ec_data *priv = dev_id;
+ unsigned long notify_action;
+ int ret;
+
+ ret = asus_ec_read(priv, true);
+ if (ret <= 0 || !(priv->ec_data[1] & ASUSEC_OBF_MASK))
+ return IRQ_NONE;
+
+ notify_action = priv->ec_data[1];
+ if (notify_action & ASUSEC_SMI_MASK) {
+ unsigned int code = priv->ec_data[2];
+
+ asus_ec_handle_smi(priv, code);
+
+ notify_action |= code << 8;
+ dev_dbg(&priv->self->dev, "SMI code: 0x%02x\n", code);
+ }
+
+ blocking_notifier_call_chain(&priv->info.notify_list,
+ notify_action, priv->ec_data);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t dockram_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct i2c_client *client = filp->private_data;
+ unsigned int reg, rsize;
+ ssize_t n_read = 0, val;
+ loff_t off = *ppos;
+ char *data;
+ int ret;
+
+ reg = off / DOCKRAM_ENTRY_SIZE;
+ off %= DOCKRAM_ENTRY_SIZE;
+ rsize = DOCKRAM_ENTRIES * DOCKRAM_ENTRY_SIZE;
+
+ if (!count)
+ return 0;
+
+ data = kmalloc(DOCKRAM_ENTRY_BUFSIZE, GFP_KERNEL);
+
+ while (reg < DOCKRAM_ENTRIES) {
+ unsigned int len = DOCKRAM_ENTRY_SIZE - off;
+
+ if (len > rsize)
+ len = rsize;
+
+ ret = asus_dockram_read(client, reg, data);
+ if (ret < 0) {
+ if (!n_read)
+ n_read = ret;
+ break;
+ }
+
+ val = copy_to_user(buf, data + 1 + off, len);
+ if (val == len)
+ return -EFAULT;
+
+ *ppos += len;
+ n_read += len;
+
+ if (len == rsize)
+ break;
+
+ rsize -= len;
+ buf += len;
+ off = 0;
+ ++reg;
+ }
+
+ kfree(data);
+
+ return n_read;
+}
+
+static int dockram_write_one(struct i2c_client *client, int reg,
+ const char __user *buf, size_t count)
+{
+ struct dockram_ec_data *priv = i2c_get_clientdata(client);
+ int ret;
+
+ if (!count || count > DOCKRAM_ENTRY_SIZE)
+ return -EINVAL;
+ if (buf[0] != count - 1)
+ return -EINVAL;
+
+ guard(mutex)(&priv->ctl_lock);
+
+ priv->ctl_data[0] = (u8)count;
+ memcpy(priv->ctl_data + 1, buf, count);
+ ret = asus_dockram_write(client, reg, priv->ctl_data);
+
+ return ret;
+}
+
+static ssize_t dockram_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct i2c_client *client = filp->private_data;
+ unsigned int reg;
+ loff_t off = *ppos;
+ int ret;
+
+ if (off % DOCKRAM_ENTRY_SIZE != 0)
+ return -EINVAL;
+
+ reg = off / DOCKRAM_ENTRY_SIZE;
+ if (reg >= DOCKRAM_ENTRIES)
+ return -EINVAL;
+
+ ret = dockram_write_one(client, reg, buf, count);
+
+ return ret < 0 ? ret : count;
+}
+
+static const struct debugfs_short_fops dockram_fops = {
+ .read = dockram_read,
+ .write = dockram_write,
+ .llseek = default_llseek,
+};
+
+static int control_reg_get(void *ec, u64 *val)
+{
+ return asus_ec_get_ctl(ec, val);
+}
+
+static int control_reg_set(void *ec, u64 val)
+{
+ return asus_ec_update_ctl(ec, ~0ull, val);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(control_reg_fops, control_reg_get,
+ control_reg_set, "%016llx\n");
+
+static int ec_request_set(void *ec, u64 val)
+{
+ if (val)
+ asus_ec_signal_request(ec);
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(ec_request_fops, NULL, ec_request_set, "%llu\n");
+
+static int ec_irq_set(void *ec, u64 val)
+{
+ struct asus_ec_data *priv = to_ec_data(ec);
+
+ if (val)
+ irq_wake_thread(priv->self->irq, priv);
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(ec_irq_fops, NULL, ec_irq_set, "%llu\n");
+
+static void asus_ec_debugfs_remove(void *debugfs_root)
+{
+ debugfs_remove_recursive(debugfs_root);
+}
+
+static void devm_asus_ec_debugfs_init(struct device *dev)
+{
+ struct asusec_info *ec = dev_get_drvdata(dev);
+ struct asus_ec_data *priv = to_ec_data(ec);
+ struct dentry *debugfs_root, *dockram_dir;
+ char *name = devm_kasprintf(dev, GFP_KERNEL, "asus-ec-%s",
+ priv->data->name);
+
+ debugfs_root = debugfs_create_dir(name, NULL);
+ dockram_dir = debugfs_create_dir("dockram", debugfs_root);
+
+ debugfs_create_file("ec_irq", 0200, debugfs_root, ec,
+ &ec_irq_fops);
+ debugfs_create_file("ec_request", 0200, debugfs_root, ec,
+ &ec_request_fops);
+ debugfs_create_file("control_reg", 0644, dockram_dir, ec,
+ &control_reg_fops);
+ debugfs_create_file("dockram", 0644, dockram_dir,
+ priv->info.dockram, &dockram_fops);
+
+ devm_add_action_or_reset(dev, asus_ec_debugfs_remove, debugfs_root);
+}
+
+static void asus_ec_release_dockram_dev(void *client)
+{
+ i2c_unregister_device(client);
+}
+
+static struct i2c_client *devm_asus_dockram_get(struct device *dev)
+{
+ struct i2c_client *parent = to_i2c_client(dev);
+ struct i2c_client *dockram;
+ struct dockram_ec_data *priv;
+ int ret;
+
+ dockram = i2c_new_ancillary_device(parent, "dockram",
+ parent->addr + 2);
+ if (IS_ERR(dockram))
+ return dockram;
+
+ ret = devm_add_action_or_reset(dev, asus_ec_release_dockram_dev,
+ dockram);
+ if (ret)
+ return ERR_PTR(ret);
+
+ priv = devm_kzalloc(&dockram->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ i2c_set_clientdata(dockram, priv);
+ mutex_init(&priv->ctl_lock);
+
+ return dockram;
+}
+
+static int asus_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct asus_ec_data *priv;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
+ return dev_err_probe(dev, -ENXIO,
+ "I2C bus is missing required SMBus block mode support\n");
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->data = device_get_match_data(dev);
+ if (!priv->data)
+ return -ENODEV;
+
+ i2c_set_clientdata(client, priv);
+ priv->self = client;
+
+ priv->info.dockram = devm_asus_dockram_get(dev);
+ if (IS_ERR(priv->info.dockram))
+ return dev_err_probe(dev, PTR_ERR(priv->info.dockram),
+ "failed to get dockram\n");
+
+ priv->ecreq = devm_gpiod_get(dev, "request", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->ecreq))
+ return dev_err_probe(dev, PTR_ERR(priv->ecreq),
+ "failed to get request GPIO\n");
+
+ BLOCKING_INIT_NOTIFIER_HEAD(&priv->info.notify_list);
+ mutex_init(&priv->ecreq_lock);
+
+ asus_ec_signal_request(&priv->info);
+
+ ret = asus_ec_detect(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to detect EC version\n");
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ &asus_ec_interrupt,
+ IRQF_ONESHOT | IRQF_SHARED,
+ client->name, priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register IRQ\n");
+
+ /* Parent I2C controller uses DMA, ASUS EC and child devices do not */
+ client->dev.coherent_dma_mask = 0;
+ client->dev.dma_mask = &client->dev.coherent_dma_mask;
+
+ if (IS_ENABLED(CONFIG_DEBUG_FS))
+ devm_asus_ec_debugfs_init(dev);
+
+ return devm_mfd_add_devices(dev, 0, priv->data->mfd_devices,
+ priv->data->num_devices, NULL, 0, NULL);
+}
+
+static const struct mfd_cell asus_ec_sl101_dock_mfd_devices[] = {
+ {
+ .name = "asus-transformer-ec-kbc",
+ },
+};
+
+static const struct asus_ec_chip_data asus_ec_sl101_dock_data = {
+ .name = "dock",
+ .mfd_devices = asus_ec_sl101_dock_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_sl101_dock_mfd_devices),
+ .clr_fmode = false,
+};
+
+static const struct mfd_cell asus_ec_tf101_dock_mfd_devices[] = {
+ {
+ .name = "asus-transformer-ec-battery",
+ .id = 1,
+ }, {
+ .name = "asus-transformer-ec-charger",
+ .id = 1,
+ }, {
+ .name = "asus-transformer-ec-led",
+ .id = 1,
+ }, {
+ .name = "asus-transformer-ec-keys",
+ }, {
+ .name = "asus-transformer-ec-kbc",
+ },
+};
+
+static const struct asus_ec_chip_data asus_ec_tf101_dock_data = {
+ .name = "dock",
+ .mfd_devices = asus_ec_tf101_dock_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_tf101_dock_mfd_devices),
+ .clr_fmode = false,
+};
+
+static const struct mfd_cell asus_ec_tf201_pad_mfd_devices[] = {
+ {
+ .name = "asus-transformer-ec-battery",
+ .id = 0,
+ }, {
+ .name = "asus-transformer-ec-led",
+ .id = 0,
+ },
+};
+
+static const struct asus_ec_chip_data asus_ec_tf201_pad_data = {
+ .name = "pad",
+ .mfd_devices = asus_ec_tf201_pad_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_tf201_pad_mfd_devices),
+ .clr_fmode = true,
+};
+
+static const struct mfd_cell asus_ec_tf600t_pad_mfd_devices[] = {
+ {
+ .name = "asus-transformer-ec-battery",
+ .id = 0,
+ }, {
+ .name = "asus-transformer-ec-charger",
+ .id = 0,
+ }, {
+ .name = "asus-transformer-ec-led",
+ .id = 0,
+ },
+};
+
+static const struct asus_ec_chip_data asus_ec_tf600t_pad_data = {
+ .name = "pad",
+ .mfd_devices = asus_ec_tf600t_pad_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_tf600t_pad_mfd_devices),
+ .clr_fmode = true,
+};
+
+static const struct of_device_id asus_ec_match[] = {
+ { .compatible = "asus,sl101-ec-dock", .data = &asus_ec_sl101_dock_data },
+ { .compatible = "asus,tf101-ec-dock", .data = &asus_ec_tf101_dock_data },
+ { .compatible = "asus,tf201-ec-pad", .data = &asus_ec_tf201_pad_data },
+ { .compatible = "asus,tf600t-ec-pad", .data = &asus_ec_tf600t_pad_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, asus_ec_match);
+
+static struct i2c_driver asus_ec_driver = {
+ .driver = {
+ .name = "asus-transformer-ec",
+ .of_match_table = asus_ec_match,
+ },
+ .probe = asus_ec_probe,
+};
+module_i2c_driver(asus_ec_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("ASUS Transformer's EC driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/asus-transformer-ec.h b/include/linux/mfd/asus-transformer-ec.h
new file mode 100644
index 000000000000..0a72de40352e
--- /dev/null
+++ b/include/linux/mfd/asus-transformer-ec.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __MFD_ASUS_TRANSFORMER_EC_H
+#define __MFD_ASUS_TRANSFORMER_EC_H
+
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+
+struct i2c_client;
+
+struct asusec_info {
+ const char *model;
+ const char *name;
+ struct i2c_client *dockram;
+ struct workqueue_struct *wq;
+ struct blocking_notifier_head notify_list;
+};
+
+#define DOCKRAM_ENTRIES 0x100
+#define DOCKRAM_ENTRY_SIZE 32
+#define DOCKRAM_ENTRY_BUFSIZE (DOCKRAM_ENTRY_SIZE + 1)
+
+/* interrupt sources */
+#define ASUSEC_OBF_MASK BIT(0)
+#define ASUSEC_KEY_MASK BIT(2)
+#define ASUSEC_KBC_MASK BIT(3)
+#define ASUSEC_AUX_MASK BIT(5)
+#define ASUSEC_SCI_MASK BIT(6)
+#define ASUSEC_SMI_MASK BIT(7)
+
+/* SMI notification codes */
+#define ASUSEC_SMI_POWER_NOTIFY 0x31 /* [un]plugging USB cable */
+#define ASUSEC_SMI_HANDSHAKE 0x50 /* response to ec_req edge */
+#define ASUSEC_SMI_WAKE 0x53
+#define ASUSEC_SMI_RESET 0x5f
+#define ASUSEC_SMI_ADAPTER_EVENT 0x60 /* [un]plugging charger to dock */
+#define ASUSEC_SMI_BACKLIGHT_ON 0x63
+#define ASUSEC_SMI_AUDIO_DOCK_IN 0x70
+
+#define ASUSEC_SMI_ACTION(code) (ASUSEC_SMI_MASK | ASUSEC_OBF_MASK | \
+ (ASUSEC_SMI_##code << 8))
+
+/* control register [0x0a] layout */
+#define ASUSEC_CTL_SIZE 8
+
+/*
+ * EC reports power from 40-pin connector in the LSB of the control
+ * register. The following values have been observed (xor 0x02):
+ *
+ * PAD-ec no-plug 0x40 / PAD-ec DOCK 0x20 / DOCK-ec no-plug 0x40
+ * PAD-ec AC 0x25 / PAD-ec DOCK+AC 0x24 / DOCK-ec AC 0x25
+ * PAD-ec USB 0x45 / PAD-ec DOCK+USB 0x24 / DOCK-ec USB 0x41
+ */
+
+#define ASUSEC_CTL_DIRECT_POWER_SOURCE BIT_ULL(0)
+#define ASUSEC_STAT_CHARGING BIT_ULL(2)
+#define ASUSEC_CTL_FULL_POWER_SOURCE BIT_ULL(5)
+#define ASUSEC_CTL_SUSB_MODE BIT_ULL(9)
+#define ASUSEC_CMD_SUSPEND_S3 BIT_ULL(33)
+#define ASUSEC_CTL_TEST_DISCHARGE BIT_ULL(35)
+#define ASUSEC_CMD_SUSPEND_INHIBIT BIT_ULL(37)
+#define ASUSEC_CTL_FACTORY_MODE BIT_ULL(38)
+#define ASUSEC_CTL_KEEP_AWAKE BIT_ULL(39)
+#define ASUSEC_CTL_USB_CHARGE BIT_ULL(40)
+#define ASUSEC_CTL_LED_BLINK BIT_ULL(40)
+#define ASUSEC_CTL_LED_AMBER BIT_ULL(41)
+#define ASUSEC_CTL_LED_GREEN BIT_ULL(42)
+#define ASUSEC_CMD_SWITCH_HDMI BIT_ULL(56)
+#define ASUSEC_CMD_WIN_SHUTDOWN BIT_ULL(62)
+
+#define ASUSEC_DOCKRAM_INFO_MODEL 0x01
+#define ASUSEC_DOCKRAM_INFO_FW 0x02
+#define ASUSEC_DOCKRAM_INFO_CFGFMT 0x03
+#define ASUSEC_DOCKRAM_INFO_HW 0x04
+#define ASUSEC_DOCKRAM_CONTROL 0x0a
+#define ASUSEC_DOCKRAM_BATT_CTL 0x14
+
+#define ASUSEC_WRITE_BUF 0x64
+#define ASUSEC_READ_BUF 0x6a
+
+/* dockram comm */
+int asus_dockram_read(struct i2c_client *client, int reg, char *buf);
+int asus_dockram_write(struct i2c_client *client, int reg, const char *buf);
+int asus_dockram_access_ctl(struct i2c_client *client,
+ u64 *out, u64 mask, u64 xor);
+
+/* EC public API */
+
+/**
+ * cell_to_ec - Request the shared ASUS EC structure via a subdevice's pdev.
+ * @pdev: EC subdevice pdev requesting access to the shared ASUS EC structure.
+ *
+ * Returns a pointer to the asusec_info structure.
+ */
+static inline struct asusec_info *cell_to_ec(struct platform_device *pdev)
+{
+ return dev_get_drvdata(pdev->dev.parent);
+}
+
+/**
+ * asus_ec_get_ctl - Read from the DockRAM control register.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @out: Pointer to the variable where the register value will be stored.
+ *
+ * Performs a control register read and stores the value in @out.
+ *
+ * Return: 0 on success, or a negative errno code on failure.
+ */
+static inline int asus_ec_get_ctl(const struct asusec_info *ec, u64 *out)
+{
+ return asus_dockram_access_ctl(ec->dockram, out, 0, 0);
+}
+
+/**
+ * asus_ec_update_ctl - Update the DockRAM control register.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @mask: Bitmask of bits to be cleared.
+ * @xor: Bitmask of bits to be toggled or set (via XOR).
+ *
+ * Performs a read-modify-write update on the control register using
+ * the provided @mask and @xor values.
+ *
+ * Return: 0 on success, or a negative errno code on failure.
+ */
+static inline int asus_ec_update_ctl(const struct asusec_info *ec,
+ u64 mask, u64 xor)
+{
+ return asus_dockram_access_ctl(ec->dockram, NULL, mask, xor);
+}
+
+/**
+ * asus_ec_set_ctl_bits - Sets bits of the DockRAM control register.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @mask: Bitmask of bits to be set.
+ *
+ * Sets bits of the control register using the provided @mask value.
+ *
+ * Return: 0 on success, or a negative errno code on failure.
+ */
+static inline int asus_ec_set_ctl_bits(const struct asusec_info *ec, u64 mask)
+{
+ return asus_dockram_access_ctl(ec->dockram, NULL, mask, mask);
+}
+
+/**
+ * asus_ec_clear_ctl_bits - Clears bits of the DockRAM control register.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @mask: Bitmask of bits to be cleared.
+ *
+ * Clears bits of the control register using the provided @mask value.
+ *
+ * Return: 0 on success, or a negative errno code on failure.
+ */
+static inline int asus_ec_clear_ctl_bits(const struct asusec_info *ec, u64 mask)
+{
+ return asus_dockram_access_ctl(ec->dockram, NULL, mask, 0);
+}
+
+int asus_ec_i2c_command(const struct asusec_info *ec, u16 data);
+int devm_asus_ec_register_notifier(struct platform_device *dev,
+ struct notifier_block *nb);
+#endif /* __MFD_ASUS_TRANSFORMER_EC_H */
--
2.51.0
^ permalink raw reply related
* [PATCH v6 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC
From: Svyatoslav Ryhel @ 2026-05-02 12:40 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com>
Document embedded controller used in ASUS Transformer device series.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../asus,tf201-ec-pad.yaml | 119 ++++++++++++++++++
1 file changed, 119 insertions(+)
create mode 100644 Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
diff --git a/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
new file mode 100644
index 000000000000..60b6375864aa
--- /dev/null
+++ b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
@@ -0,0 +1,119 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/embedded-controller/asus,tf201-ec-pad.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ASUS Transformer's Embedded Controller
+
+description:
+ Several Nuvoton based Embedded Controllers attached to an I2C bus,
+ running a custom ASUS firmware, specific to the ASUS Transformer
+ device series.
+
+maintainers:
+ - Svyatoslav Ryhel <clamor95@gmail.com>
+
+properties:
+ compatible:
+ description:
+ The 'pad' suffix is used for the controller within the tablet, while
+ the 'dock' suffix refers to the controller in the mobile dock keyboard.
+ oneOf:
+ - enum:
+ - asus,sl101-ec-dock
+ - asus,tf101-ec-dock
+ - asus,tf201-ec-pad
+ - asus,tf600t-ec-dock
+ - asus,tf600t-ec-pad
+
+ - items:
+ - enum:
+ - asus,tf101g-ec-dock
+ - asus,tf201-ec-dock
+ - asus,tf300t-ec-dock
+ - asus,tf300tg-ec-dock
+ - asus,tf300tl-ec-dock
+ - asus,tf700t-ec-dock
+ - const: asus,tf101-ec-dock
+
+ - items:
+ - enum:
+ - asus,tf300t-ec-pad
+ - asus,tf300tg-ec-pad
+ - asus,tf300tl-ec-pad
+ - asus,tf700t-ec-pad
+ - const: asus,tf201-ec-pad
+
+ - items:
+ - enum:
+ - asus,tf701t-ec-dock
+ - const: asus,tf600t-ec-dock
+
+ - items:
+ - enum:
+ - asus,p1801-t-ec-pad
+ - asus,tf701t-ec-pad
+ - const: asus,tf600t-ec-pad
+
+ reg:
+ description:
+ The ASUS Transformer EC has a main I2C address and an associated
+ DockRAM device, which provides power-related functions for the
+ embedded controller. Both addresses are required for operation.
+ minItems: 2
+
+ reg-names:
+ items:
+ - const: ec
+ - const: dockram
+
+ interrupts:
+ maxItems: 1
+
+ request-gpios:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+
+allOf:
+ - $ref: /schemas/power/supply/power-supply.yaml
+ - if:
+ properties:
+ compatible:
+ not:
+ contains:
+ const: asus,tf600t-ec-dock
+ then:
+ required:
+ - interrupts
+ - request-gpios
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ embedded-controller@19 {
+ compatible = "asus,tf201-ec-dock", "asus,tf101-ec-dock";
+ reg = <0x19>, <0x1b>;
+ reg-names = "ec", "dockram";
+
+ interrupt-parent = <&gpio>;
+ interrupts = <151 IRQ_TYPE_LEVEL_LOW>;
+
+ request-gpios = <&gpio 134 GPIO_ACTIVE_LOW>;
+
+ monitored-battery = <&dock_battery>;
+ };
+ };
+...
--
2.51.0
^ permalink raw reply related
* [PATCH v6 0/7] mfd: Add support for Asus Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-05-02 12:40 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
Add support for embedded controller used in Asus Transformers for
managing power and input functions.
---
Changes in v2:
- converted sysfs debug exports into debugfs
- added kernel-doc comments for exposed functions
- fixed minor typos and inconsistencies
Changes in v3:
- dropped DockRAM commits (both schema and driver)
- integrated DockRAM functionality directly into the controller driver
- EC schema moved to embedded controllers folder
- removed all cell descriptions from the schema
- removed all compatibles from the cell drivers
- adjusted naming conventions to better align with the ASUS Transformers
- defined EC variant sets to provide coverage for all known devices
Changes in v4:
- grouped known programming models of EC chronologically (both schema
and driver)
- call debugfs init only if CONFIG_DEBUG_FS is enabled
Changes in v5:
- added asus,tf600t-ec-dock compatible to schema
- rebased on top of v7.0
- kzalloc > kzalloc_obj in keys and kbc
Changes in v6:
- removed asus_ec_handle_smi casting
- asus_dockram_access_ctl > asus_ec_get_ctl in control_reg_*
- fixed battery Kconfig description
---
Michał Mirosław (6):
mfd: Add driver for ASUS Transformer embedded controller
input: serio: Add driver for ASUS Transformer dock keyboard and
touchpad
input: keyboard: Add driver for ASUS Transformer dock multimedia keys
leds: Add driver for ASUS Transformer LEDs
power: supply: Add driver for ASUS Transformer battery
power: supply: Add charger driver for Asus Transformers
Svyatoslav Ryhel (1):
dt-bindings: embedded-controller: document ASUS Transformer EC
.../asus,tf201-ec-pad.yaml | 119 +++
drivers/input/keyboard/Kconfig | 10 +
drivers/input/keyboard/Makefile | 1 +
.../input/keyboard/asus-transformer-ec-keys.c | 272 +++++++
drivers/input/serio/Kconfig | 15 +
drivers/input/serio/Makefile | 1 +
drivers/input/serio/asus-transformer-ec-kbc.c | 147 ++++
drivers/leds/Kconfig | 11 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-asus-transformer-ec.c | 79 ++
drivers/mfd/Kconfig | 14 +
drivers/mfd/Makefile | 1 +
drivers/mfd/asus-transformer-ec.c | 762 ++++++++++++++++++
drivers/power/supply/Kconfig | 22 +
drivers/power/supply/Makefile | 2 +
.../supply/asus-transformer-ec-battery.c | 272 +++++++
.../supply/asus-transformer-ec-charger.c | 193 +++++
include/linux/mfd/asus-transformer-ec.h | 162 ++++
18 files changed, 2084 insertions(+)
create mode 100644 Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
create mode 100644 drivers/input/keyboard/asus-transformer-ec-keys.c
create mode 100644 drivers/input/serio/asus-transformer-ec-kbc.c
create mode 100644 drivers/leds/leds-asus-transformer-ec.c
create mode 100644 drivers/mfd/asus-transformer-ec.c
create mode 100644 drivers/power/supply/asus-transformer-ec-battery.c
create mode 100644 drivers/power/supply/asus-transformer-ec-charger.c
create mode 100644 include/linux/mfd/asus-transformer-ec.h
--
2.51.0
^ permalink raw reply
* Re: [PATCH] Input: touchscreen: tsc2007: Reduce I2C transactions for Z2 read
From: Andreas Kemnade @ 2026-05-02 10:53 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Yuki Horii, clamor95, johannes.kirchmair, linux-input
In-Reply-To: <aeVwUKCQmTmn3OnY@google.com>
Hi,
On Sun, 19 Apr 2026 17:18:23 -0700
Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:
> Hi Yuki,
>
> On Fri, Apr 10, 2026 at 04:41:00PM +0900, Yuki Horii wrote:
> > From: Yuki Horii <yuuki198708@gmail.com>
> >
> > The current implementation sends a separate power-down command
> > after reading the Z2 value, resulting in an extra I2C
> > transaction per measurement cycle.
> >
> > The TSC2007 command byte contains a 2-bit power-down mode
> > selection field. By selecting the power-down state in the Z2
> > measurement command, the device powers down after the Z2 A/D
> > conversion completes, eliminating the subsequent power-down
> > transaction.
> >
> > This reduces the number of I2C transactions by one per touch
> > measurement cycle, decreasing I2C bus overhead and improving
> > touch sampling performance.
> >
> > Signed-off-by: Yuki Horii <yuuki198708@gmail.com>
> > ---
> > drivers/input/touchscreen/tsc2007_core.c | 6 ++----
> > 1 file changed, 2 insertions(+), 4 deletions(-)
> >
> > diff --git a/drivers/input/touchscreen/tsc2007_core.c b/drivers/input/touchscreen/tsc2007_core.c
> > index 948935de894b..ff60245baa96 100644
> > --- a/drivers/input/touchscreen/tsc2007_core.c
> > +++ b/drivers/input/touchscreen/tsc2007_core.c
> > @@ -61,10 +61,8 @@ static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
> >
> > /* turn y+ off, x- on; we'll use formula #1 */
> > tc->z1 = tsc2007_xfer(tsc, READ_Z1);
> > - tc->z2 = tsc2007_xfer(tsc, READ_Z2);
> > -
> > - /* Prepare for next touch reading - power down ADC, enable PENIRQ */
> > - tsc2007_xfer(tsc, PWRDOWN);
> > + /* Read Z2 and power down ADC after A/D conversion, enable PENIRQ */
> > + tc->z2 = tsc2007_xfer(tsc, (TSC2007_POWER_OFF_IRQ_EN | TSC2007_MEASURE_Z2));
> > }
> >
> > u32 tsc2007_calculate_resistance(struct tsc2007 *tsc, struct ts_event *tc)
>
> Thank you for the patch.
>
> I'd like people using this part to chime in (CCed).
>
This looks like that there might be some reason behind it. But I cannot find any.
Either by reading the datasheet nor by testing.
I have checked the interrupt counter. Output seems still be same.
Interrupts appear when finger is moving.
So
Tested-by: Andreas Kemnade <andreas@kemnade.info> # GTA04
> Thanks.
>
^ permalink raw reply
* Lenovo T470s touchpad instability
From: Dove Leonce ALOWANOU @ 2026-05-02 9:53 UTC (permalink / raw)
To: linux-input
[-- Attachment #1.1: Type: text/plain, Size: 1040 bytes --]
Hello,
I am reporting a touchpad issue and behavior on my Lenovo ThinkPad T470s.
System:
* Laptop: Lenovo ThinkPad T470s
* OS: Fedora 44
* Kernel: 6.19.14-300.fc44.x86_64
Kernel message:
psmouse serio1: synaptics: Your touchpad (PNP: LEN007f PNP0f13) says it
can support a different bus. If i2c-hid and hid-rmi are not used, you
might want to try setting psmouse.synaptics_intertouch to 1
Observed behavior:
* Without |psmouse.synaptics_intertouch=1|:
The touchpad works initially, but after some time it randomly freezes.
When this happens, the touchpad becomes unresponsive and I need to
log out and log back in to restore functionality.
The issue is intermittent but happens regularly.
* With |psmouse.synaptics_intertouch=1|:
The touchpad works reliably with no freezes so far.
Please let me know if further testing or logs are needed.
Thanks
--
*Dove Leonce ALOWANOU*
📧 Email: leoncealowanou@gmail.com
📱 Téléphone: +228 92091513 <tel:+22892091513>
🌍 Localisation: Baguida, Togo
[-- Attachment #1.2: Type: text/html, Size: 2071 bytes --]
[-- Attachment #2: Screenshot From 2026-05-02 09-52-40.png --]
[-- Type: image/png, Size: 28737 bytes --]
^ permalink raw reply
* [PATCH RESEND] Input: atmel_mxt_ts: Allow per-board device config
From: Hendrik Noack @ 2026-05-02 9:24 UTC (permalink / raw)
To: Nick Dyer, Dmitry Torokhov; +Cc: Hendrik Noack, linux-input, linux-kernel
In-Reply-To: <20260307092641.21623-1-hendrik-noack@gmx.de>
The device config can be device dependent.
Rewrite the code to be able to get a per-board suffixed device config as
well. If this does not exist, fall back to the standard device config.
Signed-off-by: Hendrik Noack <hendrik-noack@gmx.de>
---
drivers/input/touchscreen/atmel_mxt_ts.c | 64 ++++++++++++++++++++++--
1 file changed, 59 insertions(+), 5 deletions(-)
diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 87c6a10381f2..2fe0f17f475b 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -36,7 +36,9 @@
/* Firmware files */
#define MXT_FW_NAME "maxtouch.fw"
-#define MXT_CFG_NAME "maxtouch.cfg"
+#define MXT_CFG_FOLDER "atmel"
+#define MXT_CFG_NAME "maxtouch"
+#define MXT_CFG_EXTENSION ".cfg"
#define MXT_CFG_MAGIC "OBP_RAW V1"
/* Registers */
@@ -2224,6 +2226,60 @@ static void mxt_config_cb(const struct firmware *cfg, void *ctx)
release_firmware(cfg);
}
+static void mxt_board_config_cb(const struct firmware *cfg, void *ctx)
+{
+ if (cfg) {
+ mxt_config_cb(cfg, ctx);
+ } else {
+ struct mxt_data *data = ctx;
+ char *fw_name;
+
+ fw_name = kasprintf(GFP_KERNEL, "%s%s", MXT_CFG_NAME, MXT_CFG_EXTENSION);
+ if (!fw_name)
+ return;
+
+ request_firmware_nowait(THIS_MODULE, true, fw_name, &data->client->dev, GFP_KERNEL,
+ data, mxt_config_cb);
+ }
+}
+
+static int mxt_invoke_config_loader(struct mxt_data *data)
+{
+ struct device_node *root;
+ char *board_type = NULL;
+ char *fw_name;
+ void (*cb)(const struct firmware *fw, void *context);
+
+ root = of_find_node_by_path("/");
+ if (root) {
+ const char *tmp;
+
+ if (!of_property_read_string_index(root, "compatible", 0, &tmp)) {
+ board_type = kstrdup(tmp, GFP_KERNEL);
+
+ /* get rid of '/' in the compatible string to be able to find the FW */
+ if (board_type)
+ strreplace(board_type, '/', '-');
+ }
+ of_node_put(root);
+ }
+
+ if (board_type) {
+ fw_name = kasprintf(GFP_KERNEL, "%s/%s.%s%s", MXT_CFG_FOLDER, MXT_CFG_NAME,
+ board_type, MXT_CFG_EXTENSION);
+ cb = mxt_board_config_cb;
+ kfree(board_type);
+ } else {
+ fw_name = kasprintf(GFP_KERNEL, "%s%s", MXT_CFG_NAME, MXT_CFG_EXTENSION);
+ cb = mxt_config_cb;
+ }
+ if (!fw_name)
+ return -ENOMEM;
+
+ return request_firmware_nowait(THIS_MODULE, true, fw_name, &data->client->dev, GFP_KERNEL,
+ data, cb);
+}
+
static int mxt_initialize(struct mxt_data *data)
{
struct i2c_client *client = data->client;
@@ -2270,11 +2326,9 @@ static int mxt_initialize(struct mxt_data *data)
if (error)
return error;
- error = request_firmware_nowait(THIS_MODULE, true, MXT_CFG_NAME,
- &client->dev, GFP_KERNEL, data,
- mxt_config_cb);
+ error = mxt_invoke_config_loader(data);
if (error) {
- dev_err(&client->dev, "Failed to invoke firmware loader: %d\n",
+ dev_err(&client->dev, "Failed to invoke config loader: %d\n",
error);
return error;
}
--
2.43.0
^ permalink raw reply related
* Fix for Chromecast Remote not working
From: Randy Sommerfeld @ 2026-05-02 4:35 UTC (permalink / raw)
To: linux-input
[-- Attachment #1: Type: text/plain, Size: 737 bytes --]
Hello,
I've attached source code for a kernel module that fixes my Chromecast
Remote. Prior to this module, when pressing buttons on the Chromecast
Remote, I'd get these in the dmesg:
hid-generic 0005:18D1:9450.0004: Event data for report 1 was too short
(2 vs 1)
It seemed like a simple fix (accepting 1 instead of 2 for this specific
remote), and the attached module fixes this.
I'm not sure where this belongs in the main tree (perhaps as a quirk in
drivers/hid/ somewhere), which is why I'm submitting this as a module
instead of a patch.
As an unsophisticated dev I thought I'd bring this to someone's
attention since it might allow these remotes to be useful, and the fix
seems simple. Thank you.
Cheers,
-Randy.
[-- Attachment #2: chromecast_remote.c --]
[-- Type: text/x-csrc, Size: 1160 bytes --]
#include <linux/hid.h>
#include <linux/module.h>
#define USB_VENDOR_ID_GOOGLE 0x18d1
#define USB_DEVICE_ID_CHROMECAST_REMOTE 0x9450
static const __u8 *cc_remote_report_fixup(struct hid_device *hdev,
__u8 *rdesc,
unsigned int *rsize)
{
if (*rsize == 0x39 &&
rdesc[0x32] == 0x95 &&
rdesc[0x33] == 0x02 &&
rdesc[0x34] == 0x75 &&
rdesc[0x35] == 0x08) {
hid_info(hdev, "fixing Chromecast Remote descriptor\n");
rdesc[0x33] = 0x01;
}
return rdesc;
}
static const struct hid_device_id cc_remote_devices[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE,
USB_DEVICE_ID_CHROMECAST_REMOTE) },
{ }
};
MODULE_DEVICE_TABLE(hid, cc_remote_devices);
static struct hid_driver cc_remote_driver = {
.name = "chromecast_remote",
.id_table = cc_remote_devices,
.report_fixup = cc_remote_report_fixup,
};
module_hid_driver(cc_remote_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Chromecast Remote HID descriptor fixup");
^ permalink raw reply
* Re: [PATCH v2 4/4] iio: humidity: drop hid_sensor_remove_trigger() using devm API
From: Andy Shevchenko @ 2026-05-01 18:25 UTC (permalink / raw)
To: Sanjay Chitroda
Cc: Andy Shevchenko, jikos, jic23, srinivas.pandruvada, dlechner,
nuno.sa, andy, sakari.ailus, linux-input, linux-iio, linux-kernel
In-Reply-To: <12C22ED3-20F9-401C-ACC9-D666393B292A@gmail.com>
On Thu, Apr 30, 2026 at 10:21 PM Sanjay Chitroda
<sanjayembeddedse@gmail.com> wrote:
> On 30 April 2026 1:03:56 am IST, Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> >On Wed, Apr 29, 2026 at 11:29:18PM +0530, Sanjay Chitroda wrote:
> >
> >> Use devm_hid_sensor_setup_trigger() to automatically release resource
resources
> >> during fail, unbind or removal of driver using devres framework.
failure
> >>
> >> This simplify the setup, remove goto, avoid manual resource cleanup in
simplifies
removes
and avoids
OR
"This is done in a way to simplify..."
> >> teardown path.
...
> >> + ret = devm_hid_sensor_setup_trigger(&indio_dev->dev, indio_dev, name,
> >> + &humid_st->common_attributes);
> >
> >I believe the first parameter is utterly wrong here.
> >Or other way around, same issue but in the previous patch.
> >
> Thank you Andy for the review comment.
>
> It looks in same humidity probe of hid sensor with devm API two device pointer are used &pdev->dev and &indio_dev->dev; ideally all devm should have same parent device for devres resource framework and over here preferable and consistent device should be &pdev->dev;
>
> I would first update existing devm_* API to have consistent device and on top of that will use same device in devm conversation.
>
> While for gyro change device is consistent as &pdev->dev across all devm API.
The idea is that you have to go deeply understanding the object
lifetimes for the cases of different device instances along with
userspace communication channels (all possible ABIs the driver uses).
With only that the proper parameter may be chosen or even confirmed
that device managed resources must not be used. Yeah, this is one of
the downsides of devm_*() APIs.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH] platform/x86: asus-wmi: fix fn-lock on platforms where WMI DEVS is a no-op
From: Denis Benato @ 2026-05-01 15:03 UTC (permalink / raw)
To: Marcus Grenängen, platform-driver-x86
Cc: linux-input, linux-kernel, luke, hansg, ilpo.jarvinen, jikos,
bentiss, corentin.chary
In-Reply-To: <20260430080527.12754-1-marcus@grenangen.se>
On 4/30/26 10:05, Marcus Grenängen wrote:
> Some ASUS laptops (e.g. ProArt P16, N-Key keyboard 0B05:19B6) advertise
> the WMI fn-lock DEVID (0x00100023) as present but the DEVS call has no
> effect. On these machines fn-lock must be toggled via a HID feature report
> sent directly to the N-Key keyboard, which is handled by hid-asus.
Hi, thanks for your work on this!
> Add a DMI quirk flag fnlock_use_hid for such platforms. When set,
> asus_wmi_has_fnlock_key() checks for a registered hid-asus listener that
> supports fnlock instead of querying DEVS, and asus_wmi_fnlock_update()
> dispatches to asus_hid_set_fnlock().
Do you think there might be something that might make this auto-detected
instead of using a quirk?
> Extend struct asus_hid_listener with a fnlock_set callback and wire it up
> in hid-asus for QUIRK_ROG_NKEY_KEYBOARD + QUIRK_HID_FN_LOCK devices.
> Initialize fn_lock_sync_work and register the listener in the probe path
> for these keyboards (previously the early return for ROG N-Key devices
> skipped asus_input_configured where this was done).
>
> Also add a fnlock_status sysfs attribute (RW) under the asus-wmi platform
> device so userspace (e.g. asusctl) can read and set fn-lock state without
> needing to open /dev/hidraw directly.
I believe the place for this is asus-armoury driver not asus-wmi...
it's easier too to do it in asus-armoury too.
> Fix a NULL pointer dereference in do_kbd_led_set(): brightness_set was
> called unconditionally on all listeners, but listeners that only implement
> fnlock_set leave brightness_set NULL.
This is spelled a bit weird since the issue does not exists until you add another
listener type: can you split up this patch in maybe 3? One where you prepare
asus-wmi for your additions, one where you add to asus-armoury and one where
you actually add the functionality into asus-wmi? (also maybe is worth adding a
4th one with modifications on asus-hid)?
> Signed-off-by: Marcus Grenängen <marcus@grenangen.se>
> ---
> drivers/hid/hid-asus.c | 24 ++++-
> drivers/platform/x86/asus-nb-wmi.c | 13 +++
> drivers/platform/x86/asus-wmi.c | 101 ++++++++++++++++++++-
> drivers/platform/x86/asus-wmi.h | 5 +
> include/linux/platform_data/x86/asus-wmi.h | 7 +-
> 5 files changed, 143 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
> index d34d74df3dc0..71ced03c0f7e 100644
> --- a/drivers/hid/hid-asus.c
> +++ b/drivers/hid/hid-asus.c
> @@ -143,6 +143,7 @@ struct asus_drvdata {
> unsigned long battery_next_query;
> struct work_struct fn_lock_sync_work;
> bool fn_lock;
> + struct asus_hid_listener fn_lock_listener;
> };
>
> static int asus_report_battery(struct asus_drvdata *, u8 *, int);
> @@ -584,6 +585,15 @@ static void asus_sync_fn_lock(struct work_struct *work)
> asus_kbd_set_fn_lock(drvdata->hdev, drvdata->fn_lock);
> }
>
> +static void asus_hid_fnlock_set(struct asus_hid_listener *listener, bool enabled)
> +{
> + struct asus_drvdata *drvdata =
> + container_of(listener, struct asus_drvdata, fn_lock_listener);
> +
> + drvdata->fn_lock = enabled;
> + schedule_work(&drvdata->fn_lock_sync_work);
> +}
> +
> static void asus_schedule_work(struct asus_kbd_leds *led)
> {
> unsigned long flags;
> @@ -1319,8 +1329,16 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
> * For ROG keyboards, skip rename for consistency and ->input check as
> * some devices do not have inputs.
> */
> - if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
> + if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
> + if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
> + drvdata->fn_lock = true;
> + INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock);
> + asus_kbd_set_fn_lock(hdev, true);
> + drvdata->fn_lock_listener.fnlock_set = asus_hid_fnlock_set;
> + asus_hid_register_listener(&drvdata->fn_lock_listener);
> + }
> return 0;
> + }
>
> /*
> * Check that input registration succeeded. Checking that
> @@ -1362,8 +1380,10 @@ static void asus_remove(struct hid_device *hdev)
> cancel_work_sync(&drvdata->kbd_backlight->work);
> }
>
> - if (drvdata->quirks & QUIRK_HID_FN_LOCK)
> + if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
> + asus_hid_unregister_listener(&drvdata->fn_lock_listener);
> cancel_work_sync(&drvdata->fn_lock_sync_work);
> + }
>
> hid_hw_stop(hdev);
> }
> diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c
> index b4677c5bba5b..44e4cf68ff70 100644
> --- a/drivers/platform/x86/asus-nb-wmi.c
> +++ b/drivers/platform/x86/asus-nb-wmi.c
> @@ -155,6 +155,10 @@ static struct quirk_entry quirk_asus_z13 = {
> .tablet_switch_mode = asus_wmi_kbd_dock_devid,
> };
>
> +static struct quirk_entry quirk_asus_proart_p16 = {
> + .fnlock_use_hid = true,
> +};
> +
> static int dmi_matched(const struct dmi_system_id *dmi)
> {
> pr_info("Identified laptop model '%s'\n", dmi->ident);
> @@ -553,6 +557,15 @@ static const struct dmi_system_id asus_quirks[] = {
> },
> .driver_data = &quirk_asus_z13,
> },
> + {
> + .callback = dmi_matched,
> + .ident = "ASUS ProArt P16",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
> + DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"),
> + },
> + .driver_data = &quirk_asus_proart_p16,
> + },
> {},
> };
>
> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> index 80144c412b90..70758a20551d 100644
> --- a/drivers/platform/x86/asus-wmi.c
> +++ b/drivers/platform/x86/asus-wmi.c
> @@ -1759,6 +1759,24 @@ int asus_hid_event(enum asus_hid_event event)
> }
> EXPORT_SYMBOL_GPL(asus_hid_event);
>
> +/*
> + * Called by asus-wmi to propagate an fn-lock state change to all registered
> + * hid-asus listeners. Used on platforms where the WMI DEVS path for fn-lock
> + * (0x00100023) is a no-operation and the HID feature report path must be used
> + * instead (e.g. ASUS ProArt P16, USB keyboard product ID 0x19B6).
> + */
> +void asus_hid_set_fnlock(bool enabled)
> +{
> + struct asus_hid_listener *listener;
> +
> + guard(spinlock_irqsave)(&asus_ref.lock);
> + list_for_each_entry(listener, &asus_ref.listeners, list) {
> + if (listener->fnlock_set)
> + listener->fnlock_set(listener, enabled);
> + }
> +}
> +EXPORT_SYMBOL_GPL(asus_hid_set_fnlock);
> +
> /*
> * These functions actually update the LED's, and are called from a
> * workqueue. By doing this as separate work rather than when the LED
> @@ -1852,7 +1870,8 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
>
> scoped_guard(spinlock_irqsave, &asus_ref.lock) {
> list_for_each_entry(listener, &asus_ref.listeners, list)
> - listener->brightness_set(listener, asus->kbd_led_wk);
> + if (listener->brightness_set)
> + listener->brightness_set(listener, asus->kbd_led_wk);
> }
> }
>
> @@ -4495,21 +4514,64 @@ static void asus_screenpad_exit(struct asus_wmi *asus)
>
> /* Fn-lock ********************************************************************/
>
> +static bool asus_hid_has_fnlock_listener(void)
> +{
> + struct asus_hid_listener *listener;
> +
> + guard(spinlock_irqsave)(&asus_ref.lock);
> + list_for_each_entry(listener, &asus_ref.listeners, list) {
> + if (listener->fnlock_set)
> + return true;
> + }
> + return false;
> +}
> +
> static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus)
> {
> u32 result;
>
> + /* Some platforms have a non-functional WMI path — use HID directly */
> + if (asus->driver->quirks->fnlock_use_hid)
> + return asus_hid_has_fnlock_listener();
> +
> asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FNLOCK, &result);
>
> - return (result & ASUS_WMI_DSTS_PRESENCE_BIT) &&
> - !(result & ASUS_WMI_FNLOCK_BIOS_DISABLED);
> + if ((result & ASUS_WMI_DSTS_PRESENCE_BIT) &&
> + !(result & ASUS_WMI_FNLOCK_BIOS_DISABLED))
> + return true;
> +
> + /*
> + * Some platforms (e.g. ASUS ProArt P16) have a non-functional WMI
> + * DEVS path for fn-lock (DEVS returns One unconditionally with no
> + * side effects). On these platforms fn-lock is controlled via a HID
> + * feature report sent directly to the N-Key keyboard. Return true if
> + * a hid-asus listener with fnlock support has registered, so that
> + * asus-wmi can manage the fn-lock state and expose the sysfs knob.
> + */
> + return asus_hid_has_fnlock_listener();
> }
>
> static void asus_wmi_fnlock_update(struct asus_wmi *asus)
> {
> int mode = asus->fnlock_locked;
> + u32 result;
>
> - asus_wmi_set_devstate(ASUS_WMI_DEVID_FNLOCK, mode, NULL);
> + /* Platform has non-functional WMI DEVS for fn-lock — use HID directly */
> + if (asus->driver->quirks->fnlock_use_hid) {
> + asus_hid_set_fnlock(mode);
> + return;
> + }
> +
> + asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FNLOCK, &result);
> +
> + if ((result & ASUS_WMI_DSTS_PRESENCE_BIT) &&
> + !(result & ASUS_WMI_FNLOCK_BIOS_DISABLED)) {
> + asus_wmi_set_devstate(ASUS_WMI_DEVID_FNLOCK, mode, NULL);
> + return;
> + }
> +
> + /* WMI path absent or non-functional — delegate to hid-asus */
> + asus_hid_set_fnlock(mode);
> }
>
> /* WMI events *****************************************************************/
> @@ -4690,6 +4752,34 @@ static ssize_t cpufv_store(struct device *dev, struct device_attribute *attr,
>
> static DEVICE_ATTR_WO(cpufv);
>
> +static ssize_t fnlock_status_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct asus_wmi *asus = dev_get_drvdata(dev);
> +
> + return sysfs_emit(buf, "%d\n", asus->fnlock_locked);
> +}
> +
> +static ssize_t fnlock_status_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct asus_wmi *asus = dev_get_drvdata(dev);
> + bool enable;
> + int err;
> +
> + err = kstrtobool(buf, &enable);
> + if (err)
> + return err;
> +
> + asus->fnlock_locked = enable;
> + asus_wmi_fnlock_update(asus);
> +
> + return count;
> +}
> +
> +static DEVICE_ATTR_RW(fnlock_status);
> +
> static struct attribute *platform_attributes[] = {
> &dev_attr_cpufv.attr,
> &dev_attr_camera.attr,
> @@ -4698,6 +4788,7 @@ static struct attribute *platform_attributes[] = {
> &dev_attr_lid_resume.attr,
> &dev_attr_als_enable.attr,
> &dev_attr_fan_boost_mode.attr,
> + &dev_attr_fnlock_status.attr,
> #if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
> &dev_attr_charge_mode.attr,
> &dev_attr_egpu_enable.attr,
> @@ -4741,6 +4832,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
> devid = ASUS_WMI_DEVID_ALS_ENABLE;
> else if (attr == &dev_attr_fan_boost_mode.attr)
> ok = asus->fan_boost_mode_available;
> + else if (attr == &dev_attr_fnlock_status.attr)
> + ok = asus_wmi_has_fnlock_key(asus);
>
> #if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
> if (attr == &dev_attr_charge_mode.attr)
> diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h
> index 5cd4392b964e..6c50b11860e8 100644
> --- a/drivers/platform/x86/asus-wmi.h
> +++ b/drivers/platform/x86/asus-wmi.h
> @@ -52,6 +52,11 @@ struct quirk_entry {
> */
> int no_display_toggle;
> u32 xusb2pr;
> + /*
> + * Some platforms report WMI DEVID_FNLOCK as present but the DEVS call
> + * is a no-op. Force the HID feature report path via hid-asus instead.
> + */
> + bool fnlock_use_hid;
> };
>
> struct asus_wmi_driver {
> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> index 554f41b827e1..569289417a51 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -173,10 +173,11 @@ enum asus_ally_mcu_hack {
> ASUS_WMI_ALLY_MCU_HACK_DISABLED,
> };
>
> -/* Used to notify hid-asus when asus-wmi changes keyboard backlight */
> +/* Used to notify hid-asus when asus-wmi changes keyboard backlight or fn-lock */
> struct asus_hid_listener {
> struct list_head list;
> void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
> + void (*fnlock_set)(struct asus_hid_listener *listener, bool enabled);
> };
>
> enum asus_hid_event {
> @@ -196,6 +197,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
> int asus_hid_register_listener(struct asus_hid_listener *cdev);
> void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
> int asus_hid_event(enum asus_hid_event event);
> +void asus_hid_set_fnlock(bool enabled);
> #else
> static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
> {
> @@ -227,6 +229,9 @@ static inline int asus_hid_event(enum asus_hid_event event)
> {
> return -ENODEV;
> }
> +static inline void asus_hid_set_fnlock(bool enabled)
> +{
> +}
> #endif
>
> #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */
^ permalink raw reply
* [PATCH v7 2/2] Input: Add support for Wacom W9000-series penabled touchscreens
From: Hendrik Noack @ 2026-05-01 12:40 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Hendrik Noack, Ferass El Hafidi, linux-input, devicetree,
linux-kernel
In-Reply-To: <20260501124002.132540-1-hendrik-noack@gmx.de>
Add driver for Wacom W9002 and two Wacom W9007A variants. These are
penabled touchscreens supporting passive Wacom Pens and use I2C.
Co-developed-by: Ferass El Hafidi <funderscore@postmarketos.org>
Signed-off-by: Ferass El Hafidi <funderscore@postmarketos.org>
Signed-off-by: Hendrik Noack <hendrik-noack@gmx.de>
---
drivers/input/touchscreen/Kconfig | 12 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/wacom_w9000.c | 446 ++++++++++++++++++++++++
3 files changed, 459 insertions(+)
create mode 100644 drivers/input/touchscreen/wacom_w9000.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index aeaf9a9cbb41..6714c1e451a6 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -610,6 +610,18 @@ config TOUCHSCREEN_WACOM_I2C
To compile this driver as a module, choose M here: the module
will be called wacom_i2c.
+config TOUCHSCREEN_WACOM_W9000
+ tristate "Wacom W9000-series penabled touchscreen (I2C)"
+ depends on I2C
+ help
+ Say Y here if you have a Wacom W9000-series penabled I2C touchscreen.
+ This driver supports models W9002 and W9007A.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the module
+ will be called wacom_w9000.
+
config TOUCHSCREEN_LPC32XX
tristate "LPC32XX touchscreen controller"
depends on ARCH_LPC32XX
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index f2b002abebe8..6db05b4a2ee5 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -101,6 +101,7 @@ tsc2007-$(CONFIG_TOUCHSCREEN_TSC2007_IIO) += tsc2007_iio.o
obj-$(CONFIG_TOUCHSCREEN_TSC2007) += tsc2007.o
obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o
obj-$(CONFIG_TOUCHSCREEN_WACOM_I2C) += wacom_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_WACOM_W9000) += wacom_w9000.o
obj-$(CONFIG_TOUCHSCREEN_WDT87XX_I2C) += wdt87xx_i2c.o
obj-$(CONFIG_TOUCHSCREEN_WM831X) += wm831x-ts.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o
diff --git a/drivers/input/touchscreen/wacom_w9000.c b/drivers/input/touchscreen/wacom_w9000.c
new file mode 100644
index 000000000000..4a671ee1164e
--- /dev/null
+++ b/drivers/input/touchscreen/wacom_w9000.c
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Wacom W9000-series penabled I2C touchscreen driver
+ *
+ * Copyright (c) 2026 Hendrik Noack <hendrik-noack@gmx.de>
+ *
+ * Partially based on vendor driver:
+ * Copyright (C) 2012, Samsung Electronics Co. Ltd.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/touchscreen.h>
+#include <linux/unaligned.h>
+
+/* Some chips have flaky firmware that requires many retries before responding. */
+#define CMD_QUERY_RETRIES 8
+
+/* Message length */
+#define CMD_QUERY_NUM_MAX 9
+#define MSG_COORD_NUM_MAX 12
+
+/* Commands */
+#define CMD_QUERY 0x2a
+
+struct wacom_w9000_variant {
+ const unsigned int cmd_query_num;
+ const unsigned int msg_coord_num;
+ const char *name;
+};
+
+struct wacom_w9000_data {
+ struct i2c_client *client;
+ struct input_dev *input_dev;
+ const struct wacom_w9000_variant *variant;
+ unsigned int fw_version;
+
+ struct touchscreen_properties prop;
+ unsigned int max_pressure;
+
+ struct regulator *regulator;
+ bool powered;
+
+ struct gpio_desc *flash_mode_gpio;
+ struct gpio_desc *reset_gpio;
+
+ unsigned int irq;
+
+ bool pen_proximity;
+};
+
+static int wacom_w9000_read(struct i2c_client *client, u8 command, int len, char *data)
+{
+ int error, res;
+ struct i2c_msg msg[] = {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .buf = &command,
+ .len = sizeof(command),
+ }, {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .buf = data,
+ .len = len,
+ }
+ };
+
+ res = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (res != ARRAY_SIZE(msg)) {
+ error = res < 0 ? res : -EIO;
+ dev_err(&client->dev, "%s: i2c transfer failed: %d (%d)\n", __func__, error, res);
+ return error;
+ }
+
+ return 0;
+}
+
+static int wacom_w9000_query(struct wacom_w9000_data *wacom_data)
+{
+ struct i2c_client *client = wacom_data->client;
+ struct device *dev = &wacom_data->client->dev;
+ int error;
+ int retry = 0;
+ u8 data[CMD_QUERY_NUM_MAX];
+
+ for (; retry < CMD_QUERY_RETRIES; retry++) {
+ error = wacom_w9000_read(client, CMD_QUERY, wacom_data->variant->cmd_query_num,
+ data);
+
+ if (!error && (data[0] == 0x0f))
+ break;
+ }
+
+ if (error || (data[0] != 0x0f))
+ return error ? error : -EIO;
+
+ dev_dbg(dev, "query: %*ph, %d\n", wacom_data->variant->cmd_query_num, data, retry);
+
+ wacom_data->prop.max_x = get_unaligned_be16(&data[1]);
+ wacom_data->prop.max_y = get_unaligned_be16(&data[3]);
+ wacom_data->max_pressure = get_unaligned_be16(&data[5]);
+ wacom_data->fw_version = get_unaligned_be16(&data[7]);
+
+ dev_dbg(dev, "max_x:%d, max_y:%d, max_pressure:%d, fw:%#x", wacom_data->prop.max_x,
+ wacom_data->prop.max_y, wacom_data->max_pressure,
+ wacom_data->fw_version);
+
+ return 0;
+}
+
+/* Must be called with wacom_data->input_dev->mutex held */
+static int wacom_w9000_power_on(struct wacom_w9000_data *wacom_data)
+{
+ int error;
+
+ if (wacom_data->powered)
+ return 0;
+
+ error = regulator_enable(wacom_data->regulator);
+ if (error) {
+ dev_err(&wacom_data->client->dev, "Failed to enable regulators: %d\n", error);
+ return error;
+ }
+
+ msleep(200);
+
+ gpiod_set_value_cansleep(wacom_data->reset_gpio, 0);
+ enable_irq(wacom_data->irq);
+
+ wacom_data->powered = true;
+
+ return error;
+}
+
+/* Must be called with wacom_data->input_dev->mutex held */
+static int wacom_w9000_power_off(struct wacom_w9000_data *wacom_data)
+{
+ if (!wacom_data->powered)
+ return 0;
+
+ disable_irq(wacom_data->irq);
+ gpiod_set_value_cansleep(wacom_data->reset_gpio, 1);
+ regulator_disable(wacom_data->regulator);
+
+ wacom_data->powered = false;
+
+ return 0;
+}
+
+static void wacom_w9000_coord(struct wacom_w9000_data *wacom_data)
+{
+ struct i2c_client *client = wacom_data->client;
+ struct device *dev = &wacom_data->client->dev;
+ int error;
+ u8 data[MSG_COORD_NUM_MAX];
+ bool touch, rubber, side_button;
+ u16 x, y, pressure;
+ u8 distance = 0;
+
+ error = i2c_master_recv(client, data, wacom_data->variant->msg_coord_num);
+ if (error != wacom_data->variant->msg_coord_num) {
+ if (error >= 0)
+ error = -EIO;
+ dev_err_ratelimited(dev, "%s: i2c receive failed (%d)\n", __func__, error);
+ return;
+ }
+
+ dev_dbg(dev, "data: %*ph", wacom_data->variant->msg_coord_num, data);
+
+ if (data[0] & BIT(7)) {
+ wacom_data->pen_proximity = true;
+
+ touch = !!(data[0] & BIT(4));
+ side_button = !!(data[0] & BIT(5));
+ rubber = !!(data[0] & BIT(6));
+
+ x = get_unaligned_be16(&data[1]);
+ y = get_unaligned_be16(&data[3]);
+ pressure = get_unaligned_be16(&data[5]);
+
+ if (wacom_data->variant->msg_coord_num > 7)
+ distance = data[7];
+
+ if (x > wacom_data->prop.max_x || y > wacom_data->prop.max_y) {
+ dev_warn_ratelimited(dev, "Coordinates out of range x=%d, y=%d", x, y);
+ return;
+ }
+
+ if (pressure > wacom_data->max_pressure) {
+ dev_warn_ratelimited(dev, "Pressure out of range %d", pressure);
+ return;
+ }
+
+ touchscreen_report_pos(wacom_data->input_dev, &wacom_data->prop, x, y, false);
+ input_report_abs(wacom_data->input_dev, ABS_PRESSURE, pressure);
+
+ if (wacom_data->variant->msg_coord_num > 7)
+ input_report_abs(wacom_data->input_dev, ABS_DISTANCE, distance);
+
+ input_report_key(wacom_data->input_dev, BTN_STYLUS, side_button);
+ input_report_key(wacom_data->input_dev, BTN_TOUCH, touch);
+ input_report_key(wacom_data->input_dev, BTN_TOOL_PEN, !rubber);
+ input_report_key(wacom_data->input_dev, BTN_TOOL_RUBBER, rubber);
+ input_sync(wacom_data->input_dev);
+ } else if (wacom_data->pen_proximity) {
+ input_report_abs(wacom_data->input_dev, ABS_PRESSURE, 0);
+
+ if (wacom_data->variant->msg_coord_num > 7)
+ input_report_abs(wacom_data->input_dev, ABS_DISTANCE, 255);
+
+ input_report_key(wacom_data->input_dev, BTN_STYLUS, 0);
+ input_report_key(wacom_data->input_dev, BTN_TOUCH, 0);
+ input_report_key(wacom_data->input_dev, BTN_TOOL_PEN, 0);
+ input_report_key(wacom_data->input_dev, BTN_TOOL_RUBBER, 0);
+ input_sync(wacom_data->input_dev);
+
+ wacom_data->pen_proximity = false;
+ }
+}
+
+static irqreturn_t wacom_w9000_interrupt(int irq, void *dev_id)
+{
+ struct wacom_w9000_data *wacom_data = dev_id;
+
+ wacom_w9000_coord(wacom_data);
+
+ return IRQ_HANDLED;
+}
+
+static int wacom_w9000_open(struct input_dev *dev)
+{
+ struct wacom_w9000_data *wacom_data = input_get_drvdata(dev);
+
+ return wacom_w9000_power_on(wacom_data);
+}
+
+static void wacom_w9000_close(struct input_dev *dev)
+{
+ struct wacom_w9000_data *wacom_data = input_get_drvdata(dev);
+
+ wacom_w9000_power_off(wacom_data);
+}
+
+static int wacom_w9000_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct wacom_w9000_data *wacom_data;
+ struct input_dev *input_dev;
+ int error;
+ u32 val;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(dev, "i2c_check_functionality error\n");
+ return -EIO;
+ }
+
+ wacom_data = devm_kzalloc(dev, sizeof(*wacom_data), GFP_KERNEL);
+ if (!wacom_data)
+ return -ENOMEM;
+
+ wacom_data->variant = i2c_get_match_data(client);
+ if (!wacom_data->variant) {
+ dev_err(dev, "No i2c match_data available\n");
+ return -EINVAL;
+ }
+
+ if (wacom_data->variant->cmd_query_num > CMD_QUERY_NUM_MAX ||
+ wacom_data->variant->msg_coord_num > MSG_COORD_NUM_MAX) {
+ dev_err(dev, "Length of message for %s exceeds the maximum\n",
+ wacom_data->variant->name);
+ return -EINVAL;
+ }
+
+ if (wacom_data->variant->msg_coord_num < 7) {
+ dev_err(dev, "Length of coordinates message for %s too short\n",
+ wacom_data->variant->name);
+ return -EINVAL;
+ }
+
+ wacom_data->client = client;
+ wacom_data->irq = client->irq;
+ i2c_set_clientdata(client, wacom_data);
+
+ wacom_data->regulator = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(wacom_data->regulator))
+ return dev_err_probe(dev, PTR_ERR(wacom_data->regulator),
+ "Failed to get regulators\n");
+
+ wacom_data->flash_mode_gpio = devm_gpiod_get_optional(dev, "flash-mode", GPIOD_OUT_LOW);
+ if (IS_ERR(wacom_data->flash_mode_gpio))
+ return dev_err_probe(dev, PTR_ERR(wacom_data->flash_mode_gpio),
+ "Failed to get flash-mode gpio\n");
+
+ wacom_data->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(wacom_data->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(wacom_data->reset_gpio),
+ "Failed to get reset gpio\n");
+
+ error = regulator_enable(wacom_data->regulator);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to enable regulators\n");
+
+ msleep(200);
+
+ gpiod_set_value_cansleep(wacom_data->reset_gpio, 0);
+
+ error = wacom_w9000_query(wacom_data);
+
+ gpiod_set_value_cansleep(wacom_data->reset_gpio, 1);
+ regulator_disable(wacom_data->regulator);
+
+ wacom_data->powered = false;
+
+ if (error)
+ return dev_err_probe(dev, error, "Failed to query\n");
+
+ error = devm_request_threaded_irq(dev, wacom_data->irq, NULL, wacom_w9000_interrupt,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN, client->name, wacom_data);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to register interrupt\n");
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ wacom_data->input_dev = input_dev;
+ input_set_drvdata(input_dev, wacom_data);
+
+ input_dev->name = wacom_data->variant->name;
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = dev;
+ input_dev->id.vendor = 0x56a;
+ input_dev->id.version = wacom_data->fw_version;
+ input_dev->open = wacom_w9000_open;
+ input_dev->close = wacom_w9000_close;
+
+ input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
+ input_set_capability(input_dev, EV_KEY, BTN_TOOL_PEN);
+ input_set_capability(input_dev, EV_KEY, BTN_TOOL_RUBBER);
+ input_set_capability(input_dev, EV_KEY, BTN_STYLUS);
+
+ input_set_abs_params(input_dev, ABS_X, 0, wacom_data->prop.max_x, 4, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, wacom_data->prop.max_y, 4, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, wacom_data->max_pressure, 0, 0);
+
+ if (wacom_data->variant->msg_coord_num > 7)
+ input_set_abs_params(input_dev, ABS_DISTANCE, 0, 255, 0, 0);
+
+ touchscreen_parse_properties(input_dev, false, &wacom_data->prop);
+
+ dev_info(dev, "%s size X%uY%u\n", wacom_data->variant->name,
+ wacom_data->prop.max_x, wacom_data->prop.max_y);
+
+ error = device_property_read_u32(dev, "touchscreen-x-mm", &val);
+ if (!error && val)
+ input_abs_set_res(input_dev, wacom_data->prop.swap_x_y ? ABS_Y : ABS_X,
+ wacom_data->prop.max_x / val);
+ error = device_property_read_u32(dev, "touchscreen-y-mm", &val);
+ if (!error && val)
+ input_abs_set_res(input_dev, wacom_data->prop.swap_x_y ? ABS_X : ABS_Y,
+ wacom_data->prop.max_y / val);
+
+ error = input_register_device(wacom_data->input_dev);
+ if (error)
+ return dev_err_probe(dev, error, "Failed to register input device\n");
+
+ return 0;
+}
+
+static int wacom_w9000_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct wacom_w9000_data *wacom_data = i2c_get_clientdata(client);
+
+ guard(mutex)(&wacom_data->input_dev->mutex);
+
+ return wacom_w9000_power_off(wacom_data);
+}
+
+static int wacom_w9000_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct wacom_w9000_data *wacom_data = i2c_get_clientdata(client);
+
+ guard(mutex)(&wacom_data->input_dev->mutex);
+
+ if (input_device_enabled(wacom_data->input_dev))
+ return wacom_w9000_power_on(wacom_data);
+ else
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(wacom_w9000_pm, wacom_w9000_suspend, wacom_w9000_resume);
+
+static const struct wacom_w9000_variant w9002 = {
+ .cmd_query_num = 9,
+ .msg_coord_num = 7,
+ .name = "Wacom W9002 Digitizer",
+};
+
+static const struct wacom_w9000_variant w9007a_lt03 = {
+ .cmd_query_num = 9,
+ .msg_coord_num = 8,
+ .name = "Wacom W9007A LT03 Digitizer",
+};
+
+static const struct wacom_w9000_variant w9007a_v1 = {
+ .cmd_query_num = 9,
+ .msg_coord_num = 12,
+ .name = "Wacom W9007A V1 Digitizer",
+};
+
+static const struct of_device_id wacom_w9000_of_match[] = {
+ { .compatible = "wacom,w9002", .data = &w9002 },
+ { .compatible = "wacom,w9007a-lt03", .data = &w9007a_lt03, },
+ { .compatible = "wacom,w9007a-v1", .data = &w9007a_v1, },
+ { }
+};
+MODULE_DEVICE_TABLE(of, wacom_w9000_of_match);
+
+static const struct i2c_device_id wacom_w9000_id[] = {
+ { .name = "w9002", .driver_data = (kernel_ulong_t)&w9002 },
+ { .name = "w9007a-lt03", .driver_data = (kernel_ulong_t)&w9007a_lt03 },
+ { .name = "w9007a-v1", .driver_data = (kernel_ulong_t)&w9007a_v1 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wacom_w9000_id);
+
+static struct i2c_driver wacom_w9000_driver = {
+ .driver = {
+ .name = "wacom_w9000",
+ .of_match_table = wacom_w9000_of_match,
+ .pm = pm_sleep_ptr(&wacom_w9000_pm),
+ },
+ .probe = wacom_w9000_probe,
+ .id_table = wacom_w9000_id,
+};
+module_i2c_driver(wacom_w9000_driver);
+
+/* Module information */
+MODULE_AUTHOR("Hendrik Noack <hendrik-noack@gmx.de>");
+MODULE_DESCRIPTION("Wacom W9000-series penabled touchscreen driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v7 1/2] dt-bindings: Input: Add Wacom W9000-series penabled touchscreens
From: Hendrik Noack @ 2026-05-01 12:40 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Hendrik Noack, Ferass El Hafidi, linux-input, devicetree,
linux-kernel, Conor Dooley
In-Reply-To: <20260501124002.132540-1-hendrik-noack@gmx.de>
Add bindings for Wacom W9002 and two Wacom W9007 variants which can be
found in tablets.
W9002, W9007A LT03, and W9007A V1 differ in the length of the return
message containing coordinates, distance, pressure and button status.
Co-developed-by: Ferass El Hafidi <funderscore@postmarketos.org>
Signed-off-by: Ferass El Hafidi <funderscore@postmarketos.org>
Signed-off-by: Hendrik Noack <hendrik-noack@gmx.de>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
.../input/touchscreen/wacom,w9007a-lt03.yaml | 73 +++++++++++++++++++
1 file changed, 73 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml
diff --git a/Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml b/Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml
new file mode 100644
index 000000000000..6d1da6a435d3
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/wacom,w9007a-lt03.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Wacom W9000-series penabled I2C touchscreen
+
+maintainers:
+ - Hendrik Noack <hendrik-noack@gmx.de>
+
+description: |
+ The W9000-series are penabled touchscreen controllers by Wacom.
+
+ The firmware of controllers in different devices may differ. This can also
+ affect the controller's behavior.
+
+allOf:
+ - $ref: touchscreen.yaml#
+
+properties:
+ compatible:
+ enum:
+ - wacom,w9002
+ - wacom,w9007a-lt03
+ - wacom,w9007a-v1
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ vdd-supply: true
+
+ flash-mode-gpios:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ digitizer@56 {
+ compatible = "wacom,w9007a-lt03";
+ reg = <0x56>;
+ interrupt-parent = <&gpd1>;
+ interrupts = <1 IRQ_TYPE_EDGE_RISING>;
+
+ vdd-supply = <&stylus_reg>;
+
+ flash-mode-gpios = <&gpd1 3 GPIO_ACTIVE_HIGH>;
+ reset-gpios = <&gpx0 1 GPIO_ACTIVE_LOW>;
+
+ touchscreen-x-mm = <216>;
+ touchscreen-y-mm = <135>;
+ touchscreen-inverted-x;
+ };
+ };
--
2.43.0
^ permalink raw reply related
* [PATCH v7 0/2] Add support for Wacom W9000-series penabled touchscreens
From: Hendrik Noack @ 2026-05-01 12:40 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Hendrik Noack, Ferass El Hafidi, linux-input, devicetree,
linux-kernel
Add devicetree bindings and a driver for the Wacom W9000-series penabled
touchscreens.
The driver currently only contains the information for the W9002 and
W9007A, which I or Ferass could test on devices. It should also work with
other chips, such as W9001 or W9010. However, I couldn't test it on these
and the message length would need to be added.
Signed-off-by: Hendrik Noack <hendrik-noack@gmx.de>
---
Changes in v2:
- remove pdct-gpios, as it's unnecessary
- fix devicetree example
- adopt to kernel coding style
---
Changes in v3:
- fix missing include (thanks lkp@intel.com)
---
Changes in v4:
- adopt to feedback (thanks dmitry.torokhov@gmail.com)
- add W9002 support (thanks funderscore@postmarketos.org)
- add reset-gpios, necessary for some chips
- remove R-b from krzk due to changes in dt-bindings
---
Changes in v5:
- adopt dt-bindings format to suggestion (thanks krzk@kernel.org)
- remove pen-inserted functionality as suggested (thanks dmitry.torokhov@gmail.com)
---
Changes in v6:
- add info on difference between variants
- add A-b from conor
- add warning for out of range pressure
---
Changes in v7:
- address feedback of sashiko (thanks dmitry.torokhov@gmail.com)
---
Hendrik Noack (2):
dt-bindings: Input: Add Wacom W9000-series penabled touchscreens
Input: Add support for Wacom W9000-series penabled touchscreens
.../input/touchscreen/wacom,w9007a-lt03.yaml | 73 +++
drivers/input/touchscreen/Kconfig | 12 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/wacom_w9000.c | 446 ++++++++++++++++++
4 files changed, 532 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/touchscreen/wacom,w9007a-lt03.yaml
create mode 100644 drivers/input/touchscreen/wacom_w9000.c
--
2.43.0
^ permalink raw reply
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