* Re: [PATCH v2 08/11] dt-bindings: mfd: motorola-cpcap: convert to DT schema
From: Svyatoslav Ryhel @ 2026-02-13 7:23 UTC (permalink / raw)
To: Rob Herring
Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov, Lee Jones,
Pavel Machek, Liam Girdwood, Mark Brown, Dixit Parmar,
Tony Lindgren, linux-iio, devicetree, linux-kernel, linux-input,
linux-leds
In-Reply-To: <CAL_JsqLcFAP7oP0KzLp7DfcCpz0DjsjoAW3PcfnWu2nc-dG1CQ@mail.gmail.com>
чт, 12 лют. 2026 р. о 22:19 Rob Herring <robh@kernel.org> пише:
>
> On Wed, Feb 11, 2026 at 11:30 PM Svyatoslav Ryhel <clamor95@gmail.com> wrote:
> >
> >
> >
> > 11 лютого 2026 р. 23:20:16 GMT+02:00, Rob Herring <robh@kernel.org> пише:
> > >On Fri, Feb 06, 2026 at 07:28:42PM +0200, Svyatoslav Ryhel wrote:
> > >> Convert devicetree bindings for the Motorola CPCAP MFD from TXT to YAML.
> > >>
> > >> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
> > >> ---
> > >> .../bindings/mfd/motorola,cpcap.yaml | 411 ++++++++++++++++++
> > >> .../bindings/mfd/motorola-cpcap.txt | 78 ----
> > >> 2 files changed, 411 insertions(+), 78 deletions(-)
> > >> create mode 100644 Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
> > >> delete mode 100644 Documentation/devicetree/bindings/mfd/motorola-cpcap.txt
> > >>
> > >> diff --git a/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml b/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
> > >> new file mode 100644
> > >> index 000000000000..7e350721d9f6
> > >> --- /dev/null
> > >> +++ b/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
> > >> @@ -0,0 +1,411 @@
> > >> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > >> +%YAML 1.2
> > >> +---
> > >> +$id: http://devicetree.org/schemas/mfd/motorola,cpcap.yaml#
> > >> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > >> +
> > >> +title: Motorola CPCAP PMIC MFD
> > >> +
> > >> +maintainers:
> > >> + - Svyatoslav Ryhel <clamor95@gmail.com>
> > >> +
> > >> +allOf:
> > >> + - $ref: /schemas/spi/spi-peripheral-props.yaml#
> > >> +
> > >> +properties:
> > >> + compatible:
> > >> + enum:
> > >> + - motorola,cpcap
> > >> + - st,6556002
> > >
> > >This does not match the only user in the kernel .dts files as
> > >"st,6556002" should be a fallback.
> > >
> >
> > Honestly, I would remove st,6556002 since it is not the only cpcap model which can be used, hence it cannot serve as a universal fallback. Some devices use ST produced cpcap chips, others use TI and range of models varies too. I guess I have to multiply commits.
>
> The "cap" name goes way back to at least 2G Motorola phones. There
> were whitecap and redcap chips for different 2G technologies which got
> replaced by patriot (red+white+blue I guess) for 2.5G.
>
> Dropping is fine with me, but then you have to update the .dtsi. If it
> was me, I'd just make the binding match because that's 1 patch instead
> of 2. You can still drop it on the new compatibles you are adding.
>
Yeah, you are right. I will preserve st,6556002 fallback for
motorola,cpcap while Mapphone and Mot would not require this fallback.
Thank you for suggestion and for the cpcap's origin.
> Rob
^ permalink raw reply
* [PATCH v2] bcm5974: recover from failed mode switch
From: Liam Mitchell @ 2026-02-13 9:25 UTC (permalink / raw)
To: Henrik Rydberg, Dmitry Torokhov; +Cc: linux-input, linux-kernel, Liam Mitchell
Mode switches sent before control response are ignored.
On receiving unknown 8-byte packets, assume that mode switch was ignored
and reset by switching to normal mode, waiting then switching back to
wellspring mode.
---
This patch addresses an issue where the bcm5974 driver switches modes
before the device is ready, resulting in an unresponsive trackpad and
"bcm5974: bad trackpad package, length: 8" repeated in logs.
Discussion of issue in the thread:
https://lore.kernel.org/linux-input/CAOQ1CL4+DP1TuLAGNsz5GdFBTHvnTg=5q=Dr2Z1OQc6RXydSYA@mail.gmail.com/
This fix is conservative, avoiding changing existing mode-switch
behavior because I cannot test all variations of hardware.
On receiving an unknown 8-byte packet, we assume the device is not in
wellspring mode and schedule an asynchronous mode reset.
Signed-off-by: Liam Mitchell <mitchell.liam@gmail.com>
Link: https://lore.kernel.org/linux-input/CAOQ1CL4+DP1TuLAGNsz5GdFBTHvnTg=5q=Dr2Z1OQc6RXydSYA@mail.gmail.com/
---
Changes in v2:
- mutex_lock -> guard(mutex)
- dprintk -> dev_err
- msleep -> fsleep
- removed 0 init
- cancel_work_sync -> disable_delayed_work_sync
- work_struct -> delayed_work
- Link to v1: https://lore.kernel.org/r/20260207-bcm5974-reset-v1-1-af7163903fa6@gmail.com
---
drivers/input/mouse/bcm5974.c | 40 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)
diff --git a/drivers/input/mouse/bcm5974.c b/drivers/input/mouse/bcm5974.c
index dfdfb59cc8b5..6ee766ed8402 100644
--- a/drivers/input/mouse/bcm5974.c
+++ b/drivers/input/mouse/bcm5974.c
@@ -286,6 +286,8 @@ struct bcm5974 {
const struct tp_finger *index[MAX_FINGERS]; /* finger index data */
struct input_mt_pos pos[MAX_FINGERS]; /* position array */
int slots[MAX_FINGERS]; /* slot assignments */
+ struct delayed_work mode_reset_work;
+ unsigned long last_mode_reset;
};
/* trackpad finger block data, le16-aligned */
@@ -696,6 +698,32 @@ static int bcm5974_wellspring_mode(struct bcm5974 *dev, bool on)
return retval;
}
+/*
+ * Mode switches sent before the control response are ignored.
+ * Fixing this state requires switching to normal mode and waiting
+ * about 1ms before switching back to wellspring mode.
+ */
+static void bcm5974_mode_reset_work(struct work_struct *work)
+{
+ int error;
+ struct bcm5974 *dev = container_of(work, struct bcm5974, mode_reset_work.work);
+
+ guard(mutex)(&dev->pm_mutex);
+ dev->last_mode_reset = jiffies;
+
+ error = bcm5974_wellspring_mode(dev, false);
+ if (error) {
+ dev_err(&dev->intf->dev, "reset to normal mode failed\n");
+ return;
+ }
+
+ fsleep(1000);
+
+ error = bcm5974_wellspring_mode(dev, true);
+ if (error)
+ dev_err(&dev->intf->dev, "mode switch after reset failed\n");
+}
+
static void bcm5974_irq_button(struct urb *urb)
{
struct bcm5974 *dev = urb->context;
@@ -752,10 +780,18 @@ static void bcm5974_irq_trackpad(struct urb *urb)
if (dev->tp_urb->actual_length == 2)
goto exit;
- if (report_tp_state(dev, dev->tp_urb->actual_length))
+ if (report_tp_state(dev, dev->tp_urb->actual_length)) {
dprintk(1, "bcm5974: bad trackpad package, length: %d\n",
dev->tp_urb->actual_length);
+ /* HID packet means we aren't in wellspring mode */
+ /* If we haven't tried a reset in the last second, try now */
+ if (dev->tp_urb->actual_length == 8 &&
+ time_after(jiffies, dev->last_mode_reset + msecs_to_jiffies(1000))) {
+ schedule_delayed_work(&dev->mode_reset_work, 0);
+ }
+ }
+
exit:
error = usb_submit_urb(dev->tp_urb, GFP_ATOMIC);
if (error)
@@ -906,6 +942,7 @@ static int bcm5974_probe(struct usb_interface *iface,
dev->intf = iface;
dev->input = input_dev;
dev->cfg = *cfg;
+ INIT_DELAYED_WORK(&dev->mode_reset_work, bcm5974_mode_reset_work);
mutex_init(&dev->pm_mutex);
/* setup urbs */
@@ -998,6 +1035,7 @@ static void bcm5974_disconnect(struct usb_interface *iface)
{
struct bcm5974 *dev = usb_get_intfdata(iface);
+ disable_delayed_work_sync(&dev->mode_reset_work);
usb_set_intfdata(iface, NULL);
input_unregister_device(dev->input);
---
base-commit: 7d0a66e4bb9081d75c82ec4957c50034cb0ea449
change-id: 20260207-bcm5974-reset-85ccdfca9641
Best regards,
--
Liam Mitchell <mitchell.liam@gmail.com>
^ permalink raw reply related
* Re: [PATCH 1/2] dt-bindings: input: add GPIO charlieplex keypad
From: Hugo Villeneuve @ 2026-02-13 16:26 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: hvilleneuve, robh, krzk+dt, conor+dt, linux-input, devicetree,
linux-kernel
In-Reply-To: <aYRg2vP4Lfqm61qH@google.com>
On Thu, 5 Feb 2026 01:24:05 -0800
Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:
Hi Dmitry,
> Hi Hugo,
>
> On Tue, Feb 03, 2026 at 10:49:47AM -0500, Hugo Villeneuve wrote:
> > From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> >
> > Add DT bindings for GPIO charlieplex keypad.
> >
>
> ...
>
> > + linux,no-autorepeat:
> > + type: boolean
> > + description: Do not enable autorepeat feature.
>
> Let's use standard "autorepeat" property to enable autorepeat when
> needed.
Ok, I based my binding/driver on gpio-matrix-keypad, which had
linux,no-autorepeat.
I will change that in V2 (along with driver source code changes).
>
> > +
> > + gpio-activelow:
> > + type: boolean
> > + description:
> > + Force GPIO polarity to active low.
> > + In the absence of this property GPIOs are treated as active high.
>
> No, the polarity should be encoded in individual GPIOs, not via a
> separate property.
You are right, again "contamination" from gpio-matrix-keypad :)
I will also change that in V2 (along with driver source code changes).
Thank you,
Hugo.
^ permalink raw reply
* [PATCH v2 0/2] input: add GPIO-based charlieplex keypad
From: Hugo Villeneuve @ 2026-02-13 17:14 UTC (permalink / raw)
To: hvilleneuve, dmitry.torokhov, robh, krzk+dt, conor+dt
Cc: linux-input, devicetree, linux-kernel, hugo
From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
Hello,
this patch series add a new GPIO charlieplex keypad driver.
I have tested the driver on a custom board with a Solidrun RZ/G2LC SOM
with three charlieplex keyboards, all connected thru a a PCA9416 I2C GPIO
expander.
Link: [v1] https://lore.kernel.org/linux-serial/20250924153740.806444-1-hugo@hugovil.com/raw
Changes for v2:
- Fix yamllint error for example
- Remove unused debug variable (nkeys)
- Remove support for custom linux,no-autorepeat DT property
- Remove support for custom gpio-activelow DT property
Thank you.
Hugo Villeneuve (2):
dt-bindings: input: add GPIO charlieplex keypad
Input: charlieplex_keypad: add GPIO charlieplex keypad
.../input/gpio-charlieplex-keypad.yaml | 82 +++++++
MAINTAINERS | 7 +
drivers/input/keyboard/Kconfig | 14 ++
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/charlieplex_keypad.c | 213 ++++++++++++++++++
5 files changed, 317 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml
create mode 100644 drivers/input/keyboard/charlieplex_keypad.c
base-commit: ed8a4ef29da3821ee3155d3b1925fa67fc92aae2
--
2.47.3
^ permalink raw reply
* [PATCH v2 1/2] dt-bindings: input: add GPIO charlieplex keypad
From: Hugo Villeneuve @ 2026-02-13 17:14 UTC (permalink / raw)
To: hvilleneuve, dmitry.torokhov, robh, krzk+dt, conor+dt
Cc: linux-input, devicetree, linux-kernel, hugo
In-Reply-To: <20260213171431.2228814-1-hugo@hugovil.com>
From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
Add DT bindings for GPIO charlieplex keypad.
Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
---
.../input/gpio-charlieplex-keypad.yaml | 82 +++++++++++++++++++
1 file changed, 82 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml
diff --git a/Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml b/Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml
new file mode 100644
index 0000000000000..1672491a75a85
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml
@@ -0,0 +1,82 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+
+$id: http://devicetree.org/schemas/input/gpio-charlieplex-keypad.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: GPIO charlieplex keypad
+
+maintainers:
+ - Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+description:
+ The charlieplex keypad supports N^2)-N different key combinations (where N is
+ the number of lines). Key presses and releases are detected by configuring
+ only one line as output at a time, and reading other line states. This process
+ is repeated for each line.
+ This mechanism doesn't allow to detect simultaneous key presses.
+
+allOf:
+ - $ref: input.yaml#
+ - $ref: /schemas/input/matrix-keymap.yaml#
+
+properties:
+ compatible:
+ const: gpio-charlieplex-keypad
+
+ autorepeat: true
+
+ line-scan-delay-us:
+ description:
+ Delay, measured in microseconds, that is needed
+ before we can scan keypad after activating one line.
+ default: 0
+
+ line-gpios:
+ description:
+ List of GPIOs used as lines. The gpio specifier for this property
+ depends on the gpio controller to which these lines are connected.
+
+ linux,keymap: true
+
+ debounce-delay-ms:
+ description: Debounce interval in milliseconds.
+ default: 5
+
+ poll-interval: true
+
+ wakeup-source: true
+
+required:
+ - compatible
+ - line-gpios
+ - linux,keymap
+ - poll-interval
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/input/input.h>
+
+ charlieplex-keypad {
+ compatible = "gpio-charlieplex-keypad";
+ debounce-delay-ms = <20>;
+ poll-interval = <5>;
+ line-scan-delay-us = <2>;
+
+ line-gpios = <&gpio2 25 0
+ &gpio2 26 0
+ &gpio2 27 0>;
+
+ /* MATRIX_KEY(output, input, key-code) */
+ linux,keymap = <
+ MATRIX_KEY(0, 1, KEY_F1)
+ MATRIX_KEY(0, 2, KEY_F2)
+ MATRIX_KEY(1, 0, KEY_F3)
+ MATRIX_KEY(1, 2, KEY_F4)
+ MATRIX_KEY(2, 0, KEY_F5)
+ MATRIX_KEY(2, 1, KEY_F6)
+ >;
+ };
--
2.47.3
^ permalink raw reply related
* [PATCH v2 2/2] Input: charlieplex_keypad: add GPIO charlieplex keypad
From: Hugo Villeneuve @ 2026-02-13 17:14 UTC (permalink / raw)
To: hvilleneuve, dmitry.torokhov, robh, krzk+dt, conor+dt
Cc: linux-input, devicetree, linux-kernel, hugo
In-Reply-To: <20260213171431.2228814-1-hugo@hugovil.com>
From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
Add support for GPIO-based charlieplex keypad, allowing to control
N^2-N keys using N GPIO lines.
Reuse matrix keypad keymap to simplify, even if there is no concept
of rows and columns in this type of keyboard.
Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
---
MAINTAINERS | 7 +
drivers/input/keyboard/Kconfig | 14 ++
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/charlieplex_keypad.c | 213 ++++++++++++++++++++
4 files changed, 235 insertions(+)
create mode 100644 drivers/input/keyboard/charlieplex_keypad.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9ed6d11a77466..0b2d71f32b400 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5765,6 +5765,13 @@ S: Maintained
F: Documentation/hwmon/powerz.rst
F: drivers/hwmon/powerz.c
+CHARLIEPLEX KEYPAD DRIVER
+M: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+S: Supported
+W: http://www.mosaic-industries.com/embedded-systems/microcontroller-projects/electronic-circuits/matrix-keypad-scan-decode
+F: Documentation/devicetree/bindings/input/gpio-charlieplex-keypad.yaml
+F: drivers/input/keyboard/charlieplex_keypad.c
+
CHECKPATCH
M: Andy Whitcroft <apw@canonical.com>
M: Joe Perches <joe@perches.com>
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 2ff4fef322c24..ae54b4b7e2d8a 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -289,6 +289,20 @@ config KEYBOARD_MATRIX
To compile this driver as a module, choose M here: the
module will be called matrix_keypad.
+config KEYBOARD_CHARLIEPLEX
+ tristate "GPIO driven chearlieplex keypad support"
+ depends on GPIOLIB || COMPILE_TEST
+ select INPUT_MATRIXKMAP
+ help
+ Enable support for GPIO driven charlieplex keypad. A charlieplex
+ keypad allows to use fewer GPIO lines to interface to key switches.
+ For example, an N lines charlieplex keypad can be used to interface
+ to N^2-N different key switches. However, this type of keypad
+ cannot detect more than one key press at a time.
+
+ To compile this driver as a module, choose M here: the
+ module will be called charlieplex_keypad.
+
config KEYBOARD_HIL_OLD
tristate "HP HIL keyboard support (simple driver)"
depends on GSC || HP300
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 2d906e14f3e27..40b5cf5d374d2 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_KEYBOARD_LOCOMO) += locomokbd.o
obj-$(CONFIG_KEYBOARD_LPC32XX) += lpc32xx-keys.o
obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o
obj-$(CONFIG_KEYBOARD_MATRIX) += matrix_keypad.o
+obj-$(CONFIG_KEYBOARD_CHARLIEPLEX) += charlieplex_keypad.o
obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o
obj-$(CONFIG_KEYBOARD_MAX7360) += max7360-keypad.o
obj-$(CONFIG_KEYBOARD_MPR121) += mpr121_touchkey.o
diff --git a/drivers/input/keyboard/charlieplex_keypad.c b/drivers/input/keyboard/charlieplex_keypad.c
new file mode 100644
index 0000000000000..81e8b6b96dab1
--- /dev/null
+++ b/drivers/input/keyboard/charlieplex_keypad.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * GPIO driven charlieplex keypad driver
+ *
+ * Copyright (c) 2025 Hugo Villeneuve <hvilleneuve@dimonoff.com>
+ *
+ * Based on matrix_keyboard.c
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/input.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/types.h>
+
+struct charlieplex_keypad {
+ struct input_dev *input_dev;
+ struct gpio_descs *line_gpios;
+ unsigned int nlines;
+ unsigned int line_scan_delay_us;
+ unsigned int debounce_threshold;
+ unsigned int debounce_count;
+ int debounce_code;
+ int current_code;
+};
+
+static void charlieplex_keypad_report_key(struct input_dev *input)
+{
+ struct charlieplex_keypad *keypad = input_get_drvdata(input);
+ const unsigned short *keycodes = input->keycode;
+
+ if (keypad->current_code > 0) {
+ input_event(input, EV_MSC, MSC_SCAN, keypad->current_code);
+ input_report_key(input, keycodes[keypad->current_code], 0);
+ }
+
+ if (keypad->debounce_code) {
+ input_event(input, EV_MSC, MSC_SCAN, keypad->debounce_code);
+ input_report_key(input, keycodes[keypad->debounce_code], 1);
+ }
+
+ input_sync(input);
+ keypad->current_code = keypad->debounce_code;
+}
+
+static void charlieplex_keypad_check_switch_change(struct input_dev *input,
+ int code)
+{
+ struct charlieplex_keypad *keypad = input_get_drvdata(input);
+
+ if (code != keypad->debounce_code) {
+ keypad->debounce_count = 0;
+ keypad->debounce_code = code;
+ } else if (keypad->debounce_count < keypad->debounce_threshold) {
+ keypad->debounce_count++;
+
+ if (keypad->debounce_count >= keypad->debounce_threshold &&
+ keypad->debounce_code != keypad->current_code)
+ charlieplex_keypad_report_key(input);
+ }
+}
+
+static void charlieplex_keypad_poll(struct input_dev *input)
+{
+ struct charlieplex_keypad *keypad = input_get_drvdata(input);
+ int oline;
+ int code;
+
+ for (code = 0, oline = 0; oline < keypad->nlines; oline++) {
+ DECLARE_BITMAP(values, MATRIX_MAX_ROWS);
+ int iline;
+ int rc;
+
+ /* Activate only one line as output at a time. */
+ gpiod_direction_output(keypad->line_gpios->desc[oline], 1);
+
+ if (keypad->line_scan_delay_us)
+ fsleep(keypad->line_scan_delay_us);
+
+ /* Read input on all other lines. */
+ rc = gpiod_get_array_value_cansleep(keypad->line_gpios->ndescs,
+ keypad->line_gpios->desc,
+ keypad->line_gpios->info, values);
+ if (rc)
+ return;
+
+ for (iline = 0; iline < keypad->nlines; iline++) {
+ if (iline == oline)
+ continue; /* Do not read active output line. */
+
+ /* Check if GPIO is asserted. */
+ if (test_bit(iline, values)) {
+ code = MATRIX_SCAN_CODE(oline, iline,
+ get_count_order(keypad->nlines));
+ /*
+ * Exit loop immediately since we cannot detect
+ * more than one key press at a time.
+ */
+ break;
+ }
+ }
+
+ gpiod_direction_input(keypad->line_gpios->desc[oline]);
+
+ if (code)
+ break;
+ }
+
+ charlieplex_keypad_check_switch_change(input, code);
+}
+
+static int charlieplex_keypad_init_gpio(struct platform_device *pdev,
+ struct charlieplex_keypad *keypad)
+{
+ int i;
+
+ keypad->line_gpios = devm_gpiod_get_array(&pdev->dev, "line", GPIOD_IN);
+ if (IS_ERR(keypad->line_gpios))
+ return PTR_ERR(keypad->line_gpios);
+
+ keypad->nlines = keypad->line_gpios->ndescs;
+
+ if (keypad->nlines > MATRIX_MAX_ROWS)
+ return -EINVAL;
+
+ for (i = 0; i < keypad->nlines; i++)
+ gpiod_set_consumer_name(keypad->line_gpios->desc[i], "charlieplex_kbd_line");
+
+ return 0;
+}
+
+static int charlieplex_keypad_probe(struct platform_device *pdev)
+{
+ struct charlieplex_keypad *keypad;
+ unsigned int debounce_interval_ms;
+ unsigned int poll_interval_ms;
+ struct input_dev *input_dev;
+ int err;
+
+ keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad), GFP_KERNEL);
+ if (!keypad)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&pdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ keypad->input_dev = input_dev;
+
+ device_property_read_u32(&pdev->dev, "poll-interval", &poll_interval_ms);
+ device_property_read_u32(&pdev->dev, "debounce-delay-ms", &debounce_interval_ms);
+ device_property_read_u32(&pdev->dev, "line-scan-delay-us", &keypad->line_scan_delay_us);
+
+ keypad->current_code = -1;
+ keypad->debounce_code = -1;
+ keypad->debounce_threshold = DIV_ROUND_UP(debounce_interval_ms, poll_interval_ms);
+
+ err = charlieplex_keypad_init_gpio(pdev, keypad);
+ if (err)
+ return err;
+
+ input_dev->name = pdev->name;
+ input_dev->id.bustype = BUS_HOST;
+
+ err = matrix_keypad_build_keymap(NULL, NULL, keypad->nlines,
+ keypad->nlines, NULL, input_dev);
+ if (err)
+ dev_err_probe(&pdev->dev, -ENOMEM, "failed to build keymap\n");
+
+ if (device_property_read_bool(&pdev->dev, "autorepeat"))
+ __set_bit(EV_REP, input_dev->evbit);
+
+ input_set_capability(input_dev, EV_MSC, MSC_SCAN);
+
+ err = input_setup_polling(input_dev, charlieplex_keypad_poll);
+ if (err)
+ dev_err_probe(&pdev->dev, err, "unable to set up polling\n");
+
+ input_set_poll_interval(input_dev, poll_interval_ms);
+
+ input_set_drvdata(input_dev, keypad);
+
+ err = input_register_device(keypad->input_dev);
+ if (err)
+ return err;
+
+ platform_set_drvdata(pdev, keypad);
+
+ return 0;
+}
+
+static const struct of_device_id charlieplex_keypad_dt_match[] = {
+ { .compatible = "gpio-charlieplex-keypad" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, charlieplex_keypad_dt_match);
+
+static struct platform_driver charlieplex_keypad_driver = {
+ .probe = charlieplex_keypad_probe,
+ .driver = {
+ .name = "charlieplex-keypad",
+ .of_match_table = of_match_ptr(charlieplex_keypad_dt_match),
+ },
+};
+module_platform_driver(charlieplex_keypad_driver);
+
+MODULE_AUTHOR("Hugo Villeneuve <hvilleneuve@dimonoff.com>");
+MODULE_DESCRIPTION("GPIO driven charlieplex keypad driver");
+MODULE_LICENSE("GPL");
--
2.47.3
^ permalink raw reply related
* Re: [PATCH v2 1/3] dt-bindings: power: reset: qcom-pon: Add new compatible PMM8654AU
From: Dmitry Baryshkov @ 2026-02-13 18:17 UTC (permalink / raw)
To: Rakesh Kota
Cc: Krzysztof Kozlowski, Sebastian Reichel, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Vinod Koul, Dmitry Torokhov,
Courtney Cavin, Bjorn Andersson, Konrad Dybcio, linux-pm,
devicetree, linux-kernel, linux-arm-msm, linux-input
In-Reply-To: <20260210082612.3xakor2yo4h3dbra@hu-kotarake-hyd.qualcomm.com>
On Tue, Feb 10, 2026 at 01:56:12PM +0530, Rakesh Kota wrote:
> On Mon, Feb 09, 2026 at 02:49:24PM +0100, Krzysztof Kozlowski wrote:
> > On 09/02/2026 14:23, Rakesh Kota wrote:
> > > Add the compatible string "qcom,pmm8654au-pon" for the PMM8654AU PMIC.
> > > The PON peripheral on PMM8654AU is compatible with PMK8350, so it is
> > > documented as a fallback to "qcom,pmk8350-pon".
> >
> > Drop everything after ,. Do not explain WHAT you did. We see it.
> >
> > >
> > > While PMM8654AU supports additional registers compared to the baseline,
I can't find PMM8654AU either on Qualcomm.com or in the catalog. Is it
an actual name for the chip?
> >
> > full stop.
> >
> > > there is currently no active use case for these features. This specific
> > > compatible string reserves the identifier for future hardware-specific
> > > handling if required.
> >
> > All the rest is irrelevant or even wrong. We do not reserve identifiers.
> > If you want to reserve something, then I need to reject the patch.
> >
--
With best wishes
Dmitry
^ permalink raw reply
* Re: [PATCH 2/5] input: drv260x: Add support for ACPI-enumerated devices
From: Yauhen Kharuzhy @ 2026-02-13 20:48 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <aY4MqXG2zr01Pk4Q@google.com>
On Thu, Feb 12, 2026 at 09:26:24AM -0800, Dmitry Torokhov wrote:
> Hi Yauhen,
>
> On Thu, Feb 12, 2026 at 01:46:52AM +0200, Yauhen Kharuzhy wrote:
> > Add ACPI ids and GPIO lookup mapping for drv2604 haptics device.
> > Found in Lenovo Yoga Book YB1-X91L tablet.
> >
> > Signed-off-by: Yauhen Kharuzhy <jekhor@gmail.com>
> > ---
> > drivers/input/misc/drv260x.c | 30 ++++++++++++++++++++++++++++--
> > 1 file changed, 28 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/input/misc/drv260x.c b/drivers/input/misc/drv260x.c
> > index 18360bdfe877..f613c81fa2ba 100644
> > --- a/drivers/input/misc/drv260x.c
> > +++ b/drivers/input/misc/drv260x.c
> > @@ -15,6 +15,7 @@
> > #include <linux/delay.h>
> > #include <linux/gpio/consumer.h>
> > #include <linux/regulator/consumer.h>
> > +#include <linux/acpi.h>
>
> Sort alphabetically please.
Sure.
>
> >
> > #include <dt-bindings/input/ti-drv260x.h>
> >
> > @@ -419,6 +420,12 @@ static const struct regmap_config drv260x_regmap_config = {
> > .cache_type = REGCACHE_NONE,
> > };
> >
> > +static const struct acpi_gpio_params enable_gpio = { 0, 0, false };
> > +static const struct acpi_gpio_mapping acpi_drv260x_default_gpios[] = {
> > + { "enable-gpio", &enable_gpio, 1 },
> > + { }
> > +};
>
> I'd rather move this ACPI/device-specifin handling into drivers/platform/x86/x86-android-tablets/lenovo.c
I am not sure if this is device-specific, but since there are no other
devices with a DRV260x ACPI entry, I may agree. Will move it.
>
> > +
> > static int drv260x_probe(struct i2c_client *client)
> > {
> > struct device *dev = &client->dev;
> > @@ -426,6 +433,14 @@ static int drv260x_probe(struct i2c_client *client)
> > u32 voltage;
> > int error;
> >
> > + if (has_acpi_companion(dev)) {
> > + error = devm_acpi_dev_add_driver_gpios(dev, acpi_drv260x_default_gpios);
> > + if (error) {
> > + dev_err(dev, "can't add GPIO ACPI mapping\n");
> > + return error;
> > + }
> > + }
> > +
> > haptics = devm_kzalloc(dev, sizeof(*haptics), GFP_KERNEL);
> > if (!haptics)
> > return -ENOMEM;
> > @@ -484,8 +499,10 @@ static int drv260x_probe(struct i2c_client *client)
> > return error;
> > }
> >
> > - haptics->enable_gpio = devm_gpiod_get_optional(dev, "enable",
> > - GPIOD_OUT_HIGH);
> > + haptics->enable_gpio = devm_gpiod_get_optional(dev,
> > + "enable", GPIOD_OUT_HIGH);
>
> Why this change?
A good question. Looks like artifact from previous development
iterations, will remove it.
>
> > +
> > + dev_dbg(dev, "Enable gpio = 0x%p\n", haptics->enable_gpio);
>
> ?
Hmm, looks like a forgotten debug stuff, removing it.
--
Yauhen Kharuzhy
^ permalink raw reply
* Re: [PATCH 3/5] input: drv260x: Check the device ID at initialization
From: Yauhen Kharuzhy @ 2026-02-13 20:53 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <aY4NfZm80ofkxzNW@google.com>
On Thu, Feb 12, 2026 at 09:28:51AM -0800, Dmitry Torokhov wrote:
> On Thu, Feb 12, 2026 at 01:46:53AM +0200, Yauhen Kharuzhy wrote:
> > To ensure that the device is accessible on the I2C bus, read the status
> > register and check the Device ID field in drv260x_init().
> >
> > Signed-off-by: Yauhen Kharuzhy <jekhor@gmail.com>
> > ---
> > drivers/input/misc/drv260x.c | 44 ++++++++++++++++++++++++++++++++++++
> > 1 file changed, 44 insertions(+)
> >
> > diff --git a/drivers/input/misc/drv260x.c b/drivers/input/misc/drv260x.c
> > index f613c81fa2ba..f08a3d6c3ed8 100644
> > --- a/drivers/input/misc/drv260x.c
> > +++ b/drivers/input/misc/drv260x.c
> > @@ -56,6 +56,13 @@
> > #define DRV260X_LRA_RES_PERIOD 0x22
> > #define DRV260X_MAX_REG 0x23
> >
> > +#define DRV260X_STATUS_ID_MASK 0xe0
> > +#define DRV260X_STATUS_ID_SHIFT 5
> > +#define DRV260X_ID_DRV2605 3
> > +#define DRV260X_ID_DRV2604 4
> > +#define DRV260X_ID_DRV2604L 6
> > +#define DRV260X_ID_DRV2605L 7
> > +
> > #define DRV260X_GO_BIT 0x01
> >
> > /* Library Selection */
> > @@ -305,10 +312,47 @@ static const struct reg_sequence drv260x_erm_cal_regs[] = {
> > { DRV260X_CTRL4, DRV260X_AUTOCAL_TIME_500MS },
> > };
> >
> > +struct drv260x_id_map {
> > + u8 id;
> > + char *name;
> > +};
> > +
> > +static const struct drv260x_id_map drv_260x_devids[] = {
> > + { DRV260X_ID_DRV2605, "DRV2605"},
> > + { DRV260X_ID_DRV2604, "DRV2604"},
> > + { DRV260X_ID_DRV2604L, "DRV2604L"},
> > + { DRV260X_ID_DRV2605L, "DRV2605L"},
> > +};
> > +
> > +static char *drv260x_get_model(u8 id)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < ARRAY_SIZE(drv_260x_devids); i++)
> > + if (id == drv_260x_devids[i].id)
> > + return drv_260x_devids[i].name;
> > +
> > + return NULL;
> > +}
> > +
> > static int drv260x_init(struct drv260x_data *haptics)
> > {
> > int error;
> > unsigned int cal_buf;
> > + u8 id;
> > +
> > + error = regmap_read(haptics->regmap, DRV260X_STATUS, &cal_buf);
> > + if (error) {
> > + dev_err(&haptics->client->dev,
> > + "Failed to read DRV260X_status register: %d\n",
> > + error);
> > + return error;
> > + }
> > +
> > + id = (cal_buf & DRV260X_STATUS_ID_MASK) >> DRV260X_STATUS_ID_SHIFT;
> > +
> > + dev_info(&haptics->client->dev, "ID: %u (%s)\n", id,
> > + drv260x_get_model(id));
> >
> > error = regmap_write(haptics->regmap,
> > DRV260X_RATED_VOLT, haptics->rated_voltage);
>
> If the device is not available this regmap_write() will fail so I am not
> sure why we need all this new code.
Mostly for informational purposes about the detected model. Maybe you are
right, and this is unnecessary.
>
> Thanks.
>
> --
> Dmitry
--
Yauhen Kharuzhy
^ permalink raw reply
* Re: [PATCH 4/5] input: drv260x: Stop waiting for GO bit clearing after timeout
From: Yauhen Kharuzhy @ 2026-02-13 21:00 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <aY4OAw_IMeta1z9e@google.com>
On Thu, Feb 12, 2026 at 09:34:51AM -0800, Dmitry Torokhov wrote:
> On Thu, Feb 12, 2026 at 01:46:54AM +0200, Yauhen Kharuzhy wrote:
> > If something goes wrong during effect playing or calibration, the GO bit
> > may not be cleared after some time, and the driver will get stuck.
> > To prevent this, add a timeout to the waiting loop.
> >
> > Signed-off-by: Yauhen Kharuzhy <jekhor@gmail.com>
> > ---
> > drivers/input/misc/drv260x.c | 12 ++++++++++++
> > 1 file changed, 12 insertions(+)
> >
> > diff --git a/drivers/input/misc/drv260x.c b/drivers/input/misc/drv260x.c
> > index f08a3d6c3ed8..f7bfac6d3973 100644
> > --- a/drivers/input/misc/drv260x.c
> > +++ b/drivers/input/misc/drv260x.c
> > @@ -173,6 +173,12 @@
> > #define DRV260X_AUTOCAL_TIME_500MS (2 << 4)
> > #define DRV260X_AUTOCAL_TIME_1000MS (3 << 4)
> >
> > +/*
> > + * Timeout for waiting for the GO status bit, in seconds. Should be reasonably
> > + * large to allow long-duration effects and a calibration cycle
> > + */
> > +#define DRV260X_GO_TIMEOUT_S 5
> > +
> > /**
> > * struct drv260x_data -
> > * @input_dev: Pointer to the input device
> > @@ -339,6 +345,7 @@ static int drv260x_init(struct drv260x_data *haptics)
> > {
> > int error;
> > unsigned int cal_buf;
> > + unsigned long timeout;
> > u8 id;
> >
> > error = regmap_read(haptics->regmap, DRV260X_STATUS, &cal_buf);
> > @@ -442,6 +449,7 @@ static int drv260x_init(struct drv260x_data *haptics)
> > return error;
> > }
> >
> > + timeout = jiffies + DRV260X_GO_TIMEOUT_S * HZ;
> > do {
> > usleep_range(15000, 15500);
> > error = regmap_read(haptics->regmap, DRV260X_GO, &cal_buf);
> > @@ -451,6 +459,10 @@ static int drv260x_init(struct drv260x_data *haptics)
> > error);
> > return error;
> > }
> > + if (jiffies - timeout <= 0) {
>
> time_after()
>
> > + dev_err(&haptics->client->dev, "GO timeout\n");
>
> This should be a warning, not error, since we are continuing.
>
> But actually, shouldn't we signal an error? This is probe path and if
> the controller does not ever signal readiness I do not think we should
> pretend that it will work.
Sounds reasonable. I got such a hang when selecting invalid mode/library
properties, and yes, it doesn't look like we can expect correct
functioning afterward. Will change to error.
>
> > + break;
> > + }
> > } while (cal_buf == DRV260X_GO_BIT);
>
> Thanks.
>
> --
> Dmitry
--
Yauhen Kharuzhy
^ permalink raw reply
* Re: [PATCH 5/5] input: drv260x: Don't try to disable dummy regulator
From: Yauhen Kharuzhy @ 2026-02-13 21:56 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <aY4QBqz4tadR2-Yt@google.com>
On Thu, Feb 12, 2026 at 09:41:04AM -0800, Dmitry Torokhov wrote:
> On Thu, Feb 12, 2026 at 01:46:55AM +0200, Yauhen Kharuzhy wrote:
> > Don't use a dummy regulator for 'vbat' because it cannot be disabled
> > during suspending.
>
> Why? Dummy regulator is supposed to be a placeholder that allows all the
> regular operations (enable/disable/etc) without having actually supply
> attached. Optional regulators are supposed to only be users when there
> are parts of a chip that are powered separately and may be not in use in
> a given design.
Hmm, I definitely had an issue with this. Will re-check.
Thanks for the review.
>
> This change is counter to the regulator framework.
>
> Thanks.
>
> --
> Dmitry
--
Yauhen Kharuzhy
^ permalink raw reply
* [patch 1/1] drivers/input/misc/pf1550-onkey.c: fix build with CONFIG_PM_SLEEP=n
From: Andrew Morton @ 2026-02-14 0:38 UTC (permalink / raw)
To: sean, Frank.Li, akpm, linux-input, samuel.kayode, dmitry.torokhov,
akpm
From: Andrew Morton <akpm@linux-foundation.org>
Subject: drivers/input/misc/pf1550-onkey.c: fix build with CONFIG_PM_SLEEP=n
Date: Fri Feb 13 02:39:04 PM PST 2026
riscv randconfig:
drivers/input/misc/pf1550-onkey.c:154:12: error: 'pf1550_onkey_resume' defined but not used [-Werror=unused-function]
154 | static int pf1550_onkey_resume(struct device *dev)
| ^~~~~~~~~~~~~~~~~~~
drivers/input/misc/pf1550-onkey.c:133:12: error: 'pf1550_onkey_suspend' defined but not used [-Werror=unused-function]
133 | static int pf1550_onkey_suspend(struct device *dev)
| ^~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
Cc: Samuel Kayode <samuel.kayode@savoirfairelinux.com>
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Frank Li <Frank.Li@nxp.com>
Cc: Sean Nyekjaer <sean@geanix.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
---
drivers/input/misc/pf1550-onkey.c | 2 ++
1 file changed, 2 insertions(+)
--- a/drivers/input/misc/pf1550-onkey.c~drivers-input-misc-pf1550-onkeyc-fix-build-with-config_pm_sleep=n
+++ a/drivers/input/misc/pf1550-onkey.c
@@ -130,6 +130,7 @@ static int pf1550_onkey_probe(struct pla
return 0;
}
+#ifdef CONFIG_PM_SLEEP
static int pf1550_onkey_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
@@ -175,6 +176,7 @@ static int pf1550_onkey_resume(struct de
static SIMPLE_DEV_PM_OPS(pf1550_onkey_pm_ops, pf1550_onkey_suspend,
pf1550_onkey_resume);
+#endif CONFIG_PM_SLEEP
static const struct platform_device_id pf1550_onkey_id[] = {
{ "pf1550-onkey", },
_
^ permalink raw reply
* [PATCH AUTOSEL 6.19-5.15] HID: elecom: Add support for ELECOM HUGE Plus M-HT1MRBK
From: Sasha Levin @ 2026-02-14 0:58 UTC (permalink / raw)
To: patches, stable
Cc: David Phillips, Jiri Kosina, Sasha Levin, jikos, bentiss,
linux-input
In-Reply-To: <20260214010245.3671907-1-sashal@kernel.org>
From: David Phillips <david@profile.sh>
[ Upstream commit b8e5fdf0bd022cd5493a5987ef66f5a24f8352d8 ]
New model in the ELECOM HUGE trackball line that has 8 buttons but the
report descriptor specifies only 5. The HUGE Plus supports connecting via
Bluetooth, 2.4GHz wireless USB dongle, and directly via a USB-C cable.
Each connection type reports a different device id, 01AA for cable,
01AB for USB dongle, and 01AC for Bluetooth.
This patch adds these device IDs and applies the fixups similar to the
other ELECOM devices to get all 8 buttons working for all 3 connection
types.
For reference, the usbhid-dump output:
001:013:001:DESCRIPTOR 1769085639.598405
05 01 09 02 A1 01 85 01 09 01 A1 00 05 09 19 01
29 05 15 00 25 01 75 01 95 05 81 02 75 03 95 01
81 01 05 01 09 30 09 31 16 01 80 26 FF 7F 75 10
95 02 81 06 09 38 15 81 25 7F 75 08 95 01 81 06
05 0C 0A 38 02 15 81 25 7F 75 08 95 01 81 06 C0
C0 05 0C 09 01 A1 01 85 02 15 01 26 8C 02 19 01
2A 8C 02 75 10 95 01 81 00 C0 05 01 09 80 A1 01
85 03 09 82 09 81 09 83 15 00 25 01 19 01 29 03
75 01 95 03 81 02 95 05 81 01 C0 06 01 FF 09 00
A1 01 85 08 09 00 15 00 26 FF 00 75 08 95 07 81
02 C0 06 02 FF 09 02 A1 01 85 06 09 02 15 00 26
FF 00 75 08 95 07 B1 02 C0
Signed-off-by: David Phillips <david@profile.sh>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
## Analysis
### Commit Message Analysis
This commit adds support for the ELECOM HUGE Plus trackball (M-HT1MRBK)
across three connection types (USB-C cable, 2.4GHz USB dongle,
Bluetooth). The device has 8 buttons but the HID report descriptor only
reports 5, so the driver applies a report descriptor fixup to expose all
8 buttons — the same pattern used for other ELECOM devices already
supported by this driver.
### Code Change Analysis
The changes span 4 files:
1. **`drivers/hid/hid-ids.h`**: Adds 3 new device ID defines (0x01aa,
0x01ab, 0x01ac) for the three connection types.
2. **`drivers/hid/hid-elecom.c`**:
- Adds a new case block in `elecom_report_fixup()` for the three new
device IDs, calling `mouse_button_fixup()` with appropriate offsets
(24, 28, 22, 16, 8). Note the offsets differ slightly from the
existing HUGE (M-HT1DRBK) which uses (22, 30, 24, 16, 8) — this is
because the HUGE Plus has a different report descriptor format as
shown in the commit message.
- Adds the three device IDs to `elecom_devices[]` table (USB for 01AA
and 01AB, Bluetooth for 01AC).
3. **`drivers/hid/hid-quirks.c`**: Adds the three device IDs to
`hid_have_special_driver[]` so the HID core knows to route these
devices to the elecom driver.
4. **`drivers/hid/Kconfig`**: Updates help text to mention the new
device.
### Classification
This is a **new device ID addition with report descriptor fixup** — one
of the explicitly allowed exception categories for stable backports. The
ELECOM HID driver already exists in stable trees, and this commit adds
IDs for a new variant of an existing product line (HUGE Plus vs HUGE),
applying the same `mouse_button_fixup()` mechanism already used by other
ELECOM devices.
### Scope and Risk Assessment
- **Lines changed**: Small — ~30 lines of meaningful additions across 4
files.
- **Risk**: Very low. The changes are purely additive (new device IDs +
new case in switch statement). They cannot affect any existing device
since they only trigger for the new device IDs. The
`mouse_button_fixup()` function is already well-tested with other
ELECOM devices.
- **Pattern**: Follows the exact same pattern as previous ELECOM device
additions (e.g., the HUGE M-HT1DRBK, DEFT M-DT2DRBK, etc.).
### User Impact
Without this patch, users of the ELECOM HUGE Plus trackball can only use
5 of 8 buttons. This is a real hardware functionality issue — the
device's report descriptor is broken (reports 5 buttons when 8 exist),
and the driver fixup is needed to make all buttons work. This affects
real users who purchased this trackball.
### Stability and Dependencies
- No dependencies on other commits.
- The `mouse_button_fixup()` function and the entire ELECOM driver
infrastructure exist in stable trees already.
- The patch is self-contained and applies cleanly.
- The different report descriptor offsets (24, 28, 22, 16 vs 22, 30, 24,
16 for older HUGE) are correctly derived from the actual descriptor
dump provided in the commit message.
### Conclusion
This is a textbook device ID addition to an existing driver with a
hardware quirk/fixup. It follows the same pattern as all other ELECOM
devices in the driver, is small and self-contained, fixes a real
hardware issue (only 5 of 8 buttons work), and carries essentially zero
risk of regression to existing devices. This falls squarely into the
"new device IDs / hardware quirks" exception category that is explicitly
allowed in stable.
**YES**
drivers/hid/Kconfig | 1 +
drivers/hid/hid-elecom.c | 16 ++++++++++++++++
drivers/hid/hid-ids.h | 3 +++
drivers/hid/hid-quirks.c | 3 +++
4 files changed, 23 insertions(+)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 920a64b66b25b..6ff4a3ad34cbf 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -369,6 +369,7 @@ config HID_ELECOM
- EX-G Trackballs (M-XT3DRBK, M-XT3URBK)
- DEFT Trackballs (M-DT1DRBK, M-DT1URBK, M-DT2DRBK, M-DT2URBK)
- HUGE Trackballs (M-HT1DRBK, M-HT1URBK)
+ - HUGE Plus Trackball (M-HT1MRBK)
config HID_ELO
tristate "ELO USB 4000/4500 touchscreen"
diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c
index 2003d2dcda7cc..37d88ce57f671 100644
--- a/drivers/hid/hid-elecom.c
+++ b/drivers/hid/hid-elecom.c
@@ -5,6 +5,7 @@
* - EX-G Trackballs (M-XT3DRBK, M-XT3URBK, M-XT4DRBK)
* - DEFT Trackballs (M-DT1DRBK, M-DT1URBK, M-DT2DRBK, M-DT2URBK)
* - HUGE Trackballs (M-HT1DRBK, M-HT1URBK)
+ * - HUGE Plus Trackball (M-HT1MRBK)
*
* Copyright (c) 2010 Richard Nauber <Richard.Nauber@gmail.com>
* Copyright (c) 2016 Yuxuan Shui <yshuiv7@gmail.com>
@@ -123,12 +124,25 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
*/
mouse_button_fixup(hdev, rdesc, *rsize, 22, 30, 24, 16, 8);
break;
+ case USB_DEVICE_ID_ELECOM_M_HT1MRBK:
+ case USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB:
+ case USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC:
+ /*
+ * Report descriptor format:
+ * 24: button bit count
+ * 28: padding bit count
+ * 22: button report size
+ * 16: button usage maximum
+ */
+ mouse_button_fixup(hdev, rdesc, *rsize, 24, 28, 22, 16, 8);
+ break;
}
return rdesc;
}
static const struct hid_device_id elecom_devices[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) },
@@ -142,6 +156,8 @@ static const struct hid_device_id elecom_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_019B) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB) },
{ }
};
MODULE_DEVICE_TABLE(hid, elecom_devices);
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 6d8b64872cefe..85ab1ac511096 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -466,6 +466,9 @@
#define USB_DEVICE_ID_ELECOM_M_HT1URBK_019B 0x019b
#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D 0x010d
#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C 0x011c
+#define USB_DEVICE_ID_ELECOM_M_HT1MRBK 0x01aa
+#define USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB 0x01ab
+#define USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC 0x01ac
#define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34
#define USB_DEVICE_ID_DREAM_CHEEKY_WN 0x0004
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 11438039cdb7f..3217e436c052c 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -420,6 +420,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
#if IS_ENABLED(CONFIG_HID_ELECOM)
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) },
@@ -432,6 +433,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_019B) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB) },
#endif
#if IS_ENABLED(CONFIG_HID_ELO)
{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009) },
--
2.51.0
^ permalink raw reply related
* [PATCH AUTOSEL 6.19-5.10] HID: multitouch: add eGalaxTouch EXC3188 support
From: Sasha Levin @ 2026-02-14 0:58 UTC (permalink / raw)
To: patches, stable
Cc: Thorsten Schmelzer, Michael Tretter, Jiri Kosina, Sasha Levin,
jikos, bentiss, linux-input
In-Reply-To: <20260214010245.3671907-1-sashal@kernel.org>
From: Thorsten Schmelzer <tschmelzer@topcon.com>
[ Upstream commit 8e4ac86b2ddd36fe501e20ecfcc080e536df1f48 ]
Add support for the for the EXC3188 touchscreen from eGalaxy.
Signed-off-by: Thorsten Schmelzer <tschmelzer@topcon.com>
Signed-off-by: Michael Tretter <m.tretter@pengutronix.de>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
## Analysis
### Commit Message Analysis
The commit adds support for the eGalaxTouch EXC3188 touchscreen device
by adding a new USB device ID (0xC000) to the existing HID multitouch
driver. The commit message is straightforward: "Add support for the
EXC3188 touchscreen from eGalaxy."
### Code Change Analysis
The changes are minimal and confined to two files:
1. **`drivers/hid/hid-ids.h`**: Adds a single `#define` for the new
device ID:
```c
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000 0xc000
```
2. **`drivers/hid/hid-multitouch.c`**: Adds a 3-line entry to the
`mt_devices[]` device ID table:
```c
{ .driver_data = MT_CLS_EGALAX_SERIAL,
MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000) },
```
The new device uses `MT_CLS_EGALAX_SERIAL`, which is an existing device
class already used by several other eGalax devices (A001, 73F7, 7349).
No new code paths, no new driver logic, no behavioral changes.
### Classification: New Device ID Addition
This falls squarely into the **"New Device IDs"** exception category for
stable backports:
- The driver (`hid-multitouch`) already exists in stable trees
- Only the device ID is new
- It uses an existing device class (`MT_CLS_EGALAX_SERIAL`)
- The pattern is identical to many other eGalax entries already in the
table
### Risk Assessment
- **Risk: Extremely low** — This is a pure device ID addition to an
existing table. It cannot affect any existing device. The new entry
only matches the specific USB vendor/product ID pair (0x0aec/0xc000).
- **Scope: Minimal** — 4 lines added across 2 files (1 define + 3 lines
in device table)
- **Dependencies: None** — Completely self-contained, no other commits
needed
### User Impact
Without this patch, users with the eGalaxTouch EXC3188 touchscreen have
a non-functional device. With this patch, the touchscreen works using
the existing, well-tested eGalax serial multitouch driver class. This is
exactly the kind of hardware enablement that stable users need — their
hardware simply doesn't work without it.
### Stability Indicators
- Uses existing driver infrastructure (no new code paths)
- Follows the exact same pattern as ~20 other eGalax device entries
- Signed off by the HID subsystem maintainer (Jiri Kosina)
- Has been reviewed and accepted through the normal HID subsystem
process
### Conclusion
This is a textbook example of a device ID addition that should be
backported to stable. It enables real hardware for real users, has zero
risk of regression, requires no dependencies, and follows established
patterns in the driver. These types of commits are explicitly called out
in stable kernel rules as appropriate for backporting.
**YES**
drivers/hid/hid-ids.h | 1 +
drivers/hid/hid-multitouch.c | 3 +++
2 files changed, 4 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 5a18cb41e6d79..6d8b64872cefe 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -437,6 +437,7 @@
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349 0x7349
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_73F7 0x73f7
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001 0xa001
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000 0xc000
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002 0xc002
#define USB_VENDOR_ID_EDIFIER 0x2d99
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index f21850f7d89e4..7daa8f6d81870 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -2212,6 +2212,9 @@ static const struct hid_device_id mt_devices[] = {
{ .driver_data = MT_CLS_EGALAX_SERIAL,
MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001) },
+ { .driver_data = MT_CLS_EGALAX_SERIAL,
+ MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+ USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000) },
{ .driver_data = MT_CLS_EGALAX,
MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002) },
--
2.51.0
^ permalink raw reply related
* [PATCH AUTOSEL 6.19-6.12] HID: logitech-hidpp: Add support for Logitech K980
From: Sasha Levin @ 2026-02-14 0:58 UTC (permalink / raw)
To: patches, stable
Cc: Bastien Nocera, Jiri Kosina, Sasha Levin, jikos, bentiss,
linux-input
In-Reply-To: <20260214010245.3671907-1-sashal@kernel.org>
From: Bastien Nocera <hadess@hadess.net>
[ Upstream commit af4fe07a9d963a72438ade96cf090e84b3399d0c ]
Add support for the solar-charging Logitech K980 keyboard, over
Bluetooth. Bolt traffic doesn't get routed through logitech-dj, so
this code isn't triggered when Bolt is used.
Signed-off-by: Bastien Nocera <hadess@hadess.net>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
## Analysis of HID: logitech-hidpp: Add support for Logitech K980
### Commit Message Analysis
The commit adds support for the Logitech K980 (Slim Solar+ keyboard)
over Bluetooth by adding its device ID to the `hidpp_devices` table in
the logitech-hidpp HID driver.
### Code Change Analysis
The change is a **two-line addition** of a new `HID_BLUETOOTH_DEVICE`
entry to the existing `hidpp_devices[]` table:
```c
{ /* Slim Solar+ K980 Keyboard over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb391) },
```
This is the textbook definition of a **new device ID addition** to an
existing driver. The driver (`hid-logitech-hidpp`) already exists in all
stable trees, and this just adds a Bluetooth product ID (0xb391) so the
kernel recognizes and properly handles this keyboard.
### Exception Category: New Device ID
This falls squarely into the "NEW DEVICE IDs" exception category for
stable backports:
- The driver already exists in stable trees
- Only a device ID is being added (no new code paths, no new functions)
- It's a trivial two-line addition to an ID table
- It enables an existing, well-tested driver to work with a specific
piece of hardware
### Risk Assessment
- **Risk: Extremely low.** Adding an entry to a device ID table cannot
break any existing functionality. The new entry only matches a
specific Logitech Bluetooth device (vendor 0x046d, product 0xb391). No
existing device matching is affected.
- **Scope: Minimal.** Two lines added, one file changed.
- **Dependencies: None.** This is completely self-contained.
### User Impact
Without this patch, users of the Logitech K980 keyboard over Bluetooth
won't get the hidpp driver's features (which for a solar keyboard
includes battery/charging status reporting). With this patch, the
keyboard is properly recognized and handled by the specialized Logitech
HID++ driver.
### Stability Considerations
- The commit was reviewed and applied by the HID subsystem maintainer
(Jiri Kosina)
- The pattern is identical to dozens of other device ID entries in the
same table
- Zero chance of regression for existing users
### Conclusion
This is a classic device ID addition — one of the most common and safest
types of stable backports. It enables hardware support for a specific
Logitech keyboard with zero risk to existing functionality. It meets all
stable criteria: obviously correct, small, tested, and fixes a real
issue (device not working with the appropriate driver).
**YES**
drivers/hid/hid-logitech-hidpp.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index e871f1729d4b3..ca96102121b85 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -4666,6 +4666,8 @@ static const struct hid_device_id hidpp_devices[] = {
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb037) },
{ /* MX Anywhere 3SB mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) },
+ { /* Slim Solar+ K980 Keyboard over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb391) },
{}
};
--
2.51.0
^ permalink raw reply related
* [PATCH AUTOSEL 6.19-6.18] HID: multitouch: add quirks for Lenovo Yoga Book 9i
From: Sasha Levin @ 2026-02-14 0:59 UTC (permalink / raw)
To: patches, stable
Cc: Brian Howard, Kris Fredrick, Andrei Shumailov, Jiri Kosina,
Sasha Levin, jikos, bentiss, linux-input
In-Reply-To: <20260214010245.3671907-1-sashal@kernel.org>
From: Brian Howard <blhoward2@gmail.com>
[ Upstream commit 822bc5b3744b0b2c2c9678aa1d80b2cf04fdfabf ]
The Lenovo Yoga Book 9i is a dual-screen laptop, with a single composite
USB device providing both touch and tablet interfaces for both screens.
All inputs report through a single device, differentiated solely by report
numbers. As there is no way for udev to differentiate the inputs based on
USB vendor/product ID or interface numbers, custom naming is required to
match against for downstream configuration. A firmware bug also results
in an erroneous InRange message report being received after the stylus
leaves proximity, blocking later touch events. Add required quirks for
Gen 8 to Gen 10 models, including a new quirk providing for custom input
device naming and dropping erroneous InRange reports.
Signed-off-by: Brian Howard <blhoward2@gmail.com>
Tested-by: Brian Howard <blhoward2@gmail.com>
Tested-by: Kris Fredrick <linux.baguette800@slmail.me>
Reported-by: Andrei Shumailov <gentoo1993@gmail.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220386
Signed-off-by: Jiri Kosina <jkosina@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
Here is my complete analysis:
---
## Commit Analysis: HID: multitouch: add quirks for Lenovo Yoga Book 9i
### 1. COMMIT MESSAGE ANALYSIS
The commit message is clear and well-structured. Key signals:
- **"firmware bug"** explicitly mentioned — erroneous InRange reports
block touch events
- **Closes: bugzilla.kernel.org/show_bug.cgi?id=220386** — tracked
kernel bug
- **Reported-by: Andrei Shumailov** — user-reported real-world issue
- **Tested-by** from both the author (Brian Howard) and another user
(Kris Fredrick) — multiple testers
- **Signed-off-by: Jiri Kosina** — accepted by HID subsystem maintainer
The Lenovo Yoga Book 9i is a dual-screen laptop shipping to consumers.
All touch/stylus inputs are multiplexed through a single USB composite
device.
### 2. CODE CHANGE ANALYSIS
The commit makes changes in **two files** with **73 lines added**:
**a) `drivers/hid/hid-ids.h` — 1 line:**
Adds `USB_DEVICE_ID_LENOVO_YOGABOOK9I 0x6161`. Standard device ID
addition.
**b) `drivers/hid/hid-multitouch.c` — 72 lines across 5 insertion
points:**
1. **New quirk flag** (`MT_QUIRK_YOGABOOK9I BIT(24)`) — follows the
existing pattern (BIT(23) is `MT_QUIRK_APPLE_TOUCHBAR`).
2. **New device class** (`MT_CLS_YOGABOOK9I 0x0115`) — standard pattern,
same as `MT_CLS_APPLE_TOUCHBAR 0x0114`, `MT_CLS_SIS 0x0457`, etc.
3. **Class definition in `mt_classes[]`** (~8 lines) — combines existing
quirk flags (`MT_QUIRK_ALWAYS_VALID`, `MT_QUIRK_FORCE_MULTI_INPUT`,
`MT_QUIRK_SEPARATE_APP_REPORT`, `MT_QUIRK_HOVERING`) plus the device-
specific `MT_QUIRK_YOGABOOK9I`. Sets `export_all_inputs = true`. This
is the exact same pattern used by many existing classes (e.g.,
`MT_CLS_WIN_8_FORCE_MULTI_INPUT`).
4. **Firmware bug workaround in `mt_report()`** (~30 lines) — **THE
CRITICAL FIX**: After the stylus leaves proximity, the device
firmware erroneously sends an all-zero report with InRange set. This
report is consumed by the multitouch stack and blocks subsequent
touch events. The fix detects these bogus reports (all relevant
digitizer fields — InRange, TipSwitch, BarrelSwitch, BarrelSwitch2,
ContactID, TiltX, TiltY — are zero) and drops them by returning
early. Without this fix, **touch input becomes non-functional** after
stylus use.
5. **Custom naming in `mt_input_configured()`** (~20 lines) — Maps
report IDs to human-readable names (e.g., report 48→"Touchscreen
Top", 56→"Touchscreen Bottom", 20→"Stylus Top", 40→"Stylus Bottom",
80→"Emulated Touchpad"). Since all inputs come through a single USB
device with no interface differentiation, udev has **no other way**
to distinguish which input corresponds to which physical screen.
Without this, the dual-screen touchscreen/stylus configuration is
effectively impossible.
6. **Device ID table entry** (~6 lines) — Standard `HID_DEVICE()` entry
mapping `USB_VENDOR_ID_LENOVO` + `USB_DEVICE_ID_LENOVO_YOGABOOK9I` to
`MT_CLS_YOGABOOK9I`.
### 3. CLASSIFICATION
This is a **hardware quirk/workaround**, which falls squarely under the
"QUIRKS and WORKAROUNDS" stable exception:
- Fixes firmware-induced bug that blocks touch events (bug fix)
- Adds device-specific naming for a multiplexed USB device (hardware
workaround for udev differentiation)
- Uses the well-established `hid-multitouch` quirk framework
- Pattern is identical to Apple Touch Bar, ASUS, SIS, Razer Blade
Stealth, Smart Tech, and many other device-specific quirk additions
### 4. SCOPE AND RISK ASSESSMENT
**Scope**: 73 lines added across 2 files. All changes are behind a
**device-specific quirk flag** (`MT_QUIRK_YOGABOOK9I`) that is **only
activated** for USB VID:PID `0x17ef:0x6161` (Lenovo Yoga Book 9i).
**Risk**: **EXTREMELY LOW**. The changes:
- Are only triggered for one specific USB device ID
- Do not modify any common code paths
- Do not change behavior for any other hardware
- Use only existing framework constructs (`mt_classes[]` entries, quirk
flags, `mt_report()` filtering, `mt_input_configured()` naming)
- Are well-tested by multiple users
**Dependencies**: The commit is self-contained. It uses standard HID
definitions (`HID_DG_INRANGE`, `HID_DG_TIPSWITCH`, etc.) and `hid-
multitouch` structures that exist in all current stable trees (v6.1,
v6.6, v6.12). The `for (int ...)` C11 syntax is valid in all stable
trees (kernel uses `-std=gnu11` since v5.18+). The only backport
adjustment needed is the context around BIT(23)/BIT(24) numbering — in
stable trees without `MT_QUIRK_APPLE_TOUCHBAR` (v6.12 and earlier), the
BIT number would need adjustment from 24 to 23. This is a trivial
context fixup.
### 5. USER IMPACT
**HIGH for affected users**: The Lenovo Yoga Book 9i (Gen 8-10) is a
commercially available dual-screen laptop. Without this fix:
- Touch input **stops working** after stylus use (firmware InRange bug)
- Both screens cannot be independently configured (no input
differentiation)
The bugzilla (#220386) and multiple Reported-by/Tested-by tags confirm
this affects real users.
### 6. STABILITY INDICATORS
- Tested-by from author (Brian Howard) and second tester (Kris Fredrick)
- Signed-off by HID subsystem maintainer (Jiri Kosina)
- Kernel bugzilla with tracking
- Follows well-established pattern in hid-multitouch with many precedent
quirk additions
### 7. DEPENDENCY CHECK
No dependencies on other patches. The commit is completely self-
contained. All referenced symbols (`HID_DG_INRANGE`, `HID_DG_TIPSWITCH`,
`mt_find_report_data`, `rdata->application->quirks`, `hi->report->id`,
etc.) exist in all current stable kernel trees. Minor context adjustment
may be needed for older stable trees lacking the Apple Touch Bar quirk.
### Verdict
This commit fixes a real firmware bug that **blocks touch events** on
the Lenovo Yoga Book 9i, making the device partially non-functional. It
also provides necessary input naming for the dual-screen device to be
properly configurable. All changes are device-specific hardware quirks
behind a USB VID:PID-gated quirk flag with zero risk to other devices.
The commit follows the well-established hid-multitouch quirk pattern, is
tested by multiple users, and is accepted by the subsystem maintainer.
This is textbook "hardware quirk for a broken device" — exactly the type
of change stable trees are designed to accept.
**YES**
drivers/hid/hid-ids.h | 1 +
drivers/hid/hid-multitouch.c | 72 ++++++++++++++++++++++++++++++++++++
2 files changed, 73 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 9c2bf584d9f6f..5a18cb41e6d79 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -841,6 +841,7 @@
#define USB_DEVICE_ID_LENOVO_X1_TAB3 0x60b5
#define USB_DEVICE_ID_LENOVO_X12_TAB 0x60fe
#define USB_DEVICE_ID_LENOVO_X12_TAB2 0x61ae
+#define USB_DEVICE_ID_LENOVO_YOGABOOK9I 0x6161
#define USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E 0x600e
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D 0x608d
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019 0x6019
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index b1c3ef1290587..f21850f7d89e4 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -76,6 +76,7 @@ MODULE_LICENSE("GPL");
#define MT_QUIRK_DISABLE_WAKEUP BIT(21)
#define MT_QUIRK_ORIENTATION_INVERT BIT(22)
#define MT_QUIRK_APPLE_TOUCHBAR BIT(23)
+#define MT_QUIRK_YOGABOOK9I BIT(24)
#define MT_INPUTMODE_TOUCHSCREEN 0x02
#define MT_INPUTMODE_TOUCHPAD 0x03
@@ -231,6 +232,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app);
#define MT_CLS_RAZER_BLADE_STEALTH 0x0112
#define MT_CLS_SMART_TECH 0x0113
#define MT_CLS_APPLE_TOUCHBAR 0x0114
+#define MT_CLS_YOGABOOK9I 0x0115
#define MT_CLS_SIS 0x0457
#define MT_DEFAULT_MAXCONTACT 10
@@ -427,6 +429,14 @@ static const struct mt_class mt_classes[] = {
.quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
MT_QUIRK_ALWAYS_VALID |
MT_QUIRK_CONTACT_CNT_ACCURATE,
+ },
+ { .name = MT_CLS_YOGABOOK9I,
+ .quirks = MT_QUIRK_ALWAYS_VALID |
+ MT_QUIRK_FORCE_MULTI_INPUT |
+ MT_QUIRK_SEPARATE_APP_REPORT |
+ MT_QUIRK_HOVERING |
+ MT_QUIRK_YOGABOOK9I,
+ .export_all_inputs = true
},
{ }
};
@@ -1576,6 +1586,38 @@ static void mt_report(struct hid_device *hid, struct hid_report *report)
if (rdata && rdata->is_mt_collection)
return mt_touch_report(hid, rdata);
+ /* Lenovo Yoga Book 9i requires consuming and dropping certain bogus reports */
+ if (rdata && rdata->application &&
+ (rdata->application->quirks & MT_QUIRK_YOGABOOK9I)) {
+
+ bool all_zero_report = true;
+
+ for (int f = 0; f < report->maxfield && all_zero_report; f++) {
+ struct hid_field *fld = report->field[f];
+
+ for (int i = 0; i < fld->report_count; i++) {
+ unsigned int usage = fld->usage[i].hid;
+
+ if (usage == HID_DG_INRANGE ||
+ usage == HID_DG_TIPSWITCH ||
+ usage == HID_DG_BARRELSWITCH ||
+ usage == HID_DG_BARRELSWITCH2 ||
+ usage == HID_DG_CONTACTID ||
+ usage == HID_DG_TILT_X ||
+ usage == HID_DG_TILT_Y) {
+
+ if (fld->value[i] != 0) {
+ all_zero_report = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (all_zero_report)
+ return;
+ }
+
if (field && field->hidinput && field->hidinput->input)
input_sync(field->hidinput->input);
}
@@ -1772,6 +1814,30 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
break;
}
+ /* Lenovo Yoga Book 9i requires custom naming to allow differentiation in udev */
+ if (hi->report && td->mtclass.quirks & MT_QUIRK_YOGABOOK9I) {
+ switch (hi->report->id) {
+ case 48:
+ suffix = "Touchscreen Top";
+ break;
+ case 56:
+ suffix = "Touchscreen Bottom";
+ break;
+ case 20:
+ suffix = "Stylus Top";
+ break;
+ case 40:
+ suffix = "Stylus Bottom";
+ break;
+ case 80:
+ suffix = "Emulated Touchpad";
+ break;
+ default:
+ suffix = "";
+ break;
+ }
+ }
+
if (suffix) {
hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
"%s %s", hdev->name, suffix);
@@ -2277,6 +2343,12 @@ static const struct hid_device_id mt_devices[] = {
USB_VENDOR_ID_LENOVO,
USB_DEVICE_ID_LENOVO_X12_TAB2) },
+ /* Lenovo Yoga Book 9i */
+ { .driver_data = MT_CLS_YOGABOOK9I,
+ HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_LENOVO,
+ USB_DEVICE_ID_LENOVO_YOGABOOK9I) },
+
/* Logitech devices */
{ .driver_data = MT_CLS_NSMU,
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_MULTITOUCH_WIN_8,
--
2.51.0
^ permalink raw reply related
* [PATCH AUTOSEL 6.19-6.1] HID: apple: Add "SONiX KN85 Keyboard" to the list of non-apple keyboards
From: Sasha Levin @ 2026-02-14 0:59 UTC (permalink / raw)
To: patches, stable
Cc: Joey Bednar, Jiri Kosina, Sasha Levin, jikos, bentiss,
linux-input
In-Reply-To: <20260214010245.3671907-1-sashal@kernel.org>
From: Joey Bednar <linux@joeybednar.com>
[ Upstream commit 7273acfd0aef106093a8ffa3b4973eb70e5a3799 ]
The SoNiX KN85 keyboard identifies as the "Apple, Inc. Aluminium
Keyboard" and is not recognized as a non-apple keyboard. Adding "SoNiX
KN85 Keyboard" to the list of non-apple keyboards fixes the function
keys.
Signed-off-by: Joey Bednar <linux@joeybednar.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
This confirms the exact same pattern. The "SONiX KN85 Keyboard"
identifies itself as an Apple keyboard (via vendor/product IDs matching
Apple's Aluminium Keyboard) but has a different HID name string than
"SONiX USB DEVICE", so the existing entry doesn't match it. The
`strncmp` prefix match means "SONiX USB DEVICE" would only match devices
whose name starts with exactly that string — "SONiX KN85 Keyboard"
starts with "SONiX KN" which doesn't match "SONiX USB".
---
## Complete Analysis
### 1. COMMIT MESSAGE ANALYSIS
The commit message is clear and concise:
- **Problem**: The SoNiX KN85 keyboard identifies itself (via USB
VID:PID) as the "Apple, Inc. Aluminium Keyboard" but is not an Apple
keyboard.
- **Consequence**: Without the fix, the HID apple driver applies Apple-
specific function key mapping (`fnmode=1`, fkeyslast), making the
function keys behave as media keys by default. This is wrong for a
non-Apple keyboard.
- **Fix**: Adding the keyboard's name string to the
`non_apple_keyboards[]` list so it gets properly detected and uses
`fnmode=2` (fkeysfirst) by default.
- The commit was signed off by the HID subsystem maintainer Jiri Kosina,
confirming it was accepted through the proper review process.
### 2. CODE CHANGE ANALYSIS
The change is a **single line addition** to a static constant array:
```356:367:drivers/hid/hid-apple.c
static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
{ "SONiX USB DEVICE" },
{ "SONiX AK870 PRO" },
// ... rest of array
};
```
The new entry `{ "SONiX KN85 Keyboard" }` is added at the top. The
matching mechanism (`apple_is_non_apple_keyboard()` at line 369) does a
`strncmp` prefix comparison, so the full name "SONiX KN85 Keyboard"
would need to start with "SONiX KN85 Keyboard" to match (which it does
exactly). The existing "SONiX USB DEVICE" entry does NOT match because
the KN85 keyboard reports its name starting with "SONiX KN85", not
"SONiX USB".
### 3. CLASSIFICATION
This is a **hardware quirk/workaround**. The SoNiX KN85 keyboard is a
non-Apple keyboard that falsely presents Apple USB vendor/product IDs (a
common practice for cheap third-party keyboards), causing it to be
handled by the `hid-apple` driver. The driver then applies Apple-
specific key translation logic to a keyboard that doesn't need or want
it, breaking the function keys for the user.
This falls squarely into the "QUIRKS and WORKAROUNDS" exception category
for stable backporting:
- It's a hardware-specific fix for a real device with broken behavior
- It uses an existing mechanism (the `non_apple_keyboards` array)
- It fixes a real user-facing issue (broken function keys)
### 4. SCOPE AND RISK ASSESSMENT
- **Size**: 1 line added to a data array. Cannot be smaller.
- **Files touched**: 1 (`drivers/hid/hid-apple.c`)
- **Complexity**: Zero — it's a string literal added to an array
- **Risk of regression**: Effectively zero. The string match only
triggers for devices reporting "SONiX KN85 Keyboard" as their name. No
other devices are affected. The matching is by HID device name, not
vendor/product ID, so it's extremely targeted.
- **Dependencies**: None. The `non_apple_keyboards[]` mechanism exists
in all relevant stable trees (introduced in v6.0-rc1).
### 5. USER IMPACT
- **Who is affected**: Users of the SoNiX KN85 keyboard running Linux.
These are real-world hardware owners.
- **Severity**: Without this fix, the function keys (F1-F12) on the KN85
keyboard are mapped to media keys by default (brightness, volume,
etc.), which is wrong for this keyboard. This makes the keyboard
effectively unusable for standard function key operations unless the
user manually overrides `fnmode` via a module parameter.
- **Workaround exists**: Users can set `fnmode=2` manually, but this is
not discoverable and should not be needed.
### 6. STABILITY INDICATORS
- The commit follows the exact same pattern as **5+ previous similar
commits** (SONiX AK870 PRO, hfd.cn/WKB603, Jamesdonkey/A3R, Hailuck,
the original non-apple keyboards mechanism).
- Signed off by the HID subsystem maintainer (Jiri Kosina).
- The pattern has been in the kernel since v6.0 and is well understood.
### 7. DEPENDENCY CHECK
- **No dependencies**. The `non_apple_keyboards[]` array and
`apple_is_non_apple_keyboard()` function exist in all relevant stable
trees.
- The patch applies cleanly to any tree that already has the "SONiX
AK870 PRO" entry (or can trivially be adjusted if that entry isn't
present — it's just adding a line to an array).
### 8. PRECEDENT
This is identical in nature to the "SONiX AK870 PRO" (743c81cdc98fd),
"hfd.cn/WKB603" (df83a0df820b9), "Hailuck" (c4444d8749f69), and
"Jamesdonkey/A3R" (113f736655e4f) commits. All follow the same one-line-
addition pattern to the same array.
### Summary
This is a textbook hardware quirk addition — a single line of data added
to an existing mechanism that fixes broken function keys for a specific
keyboard model. It has zero risk of regression, zero dependencies beyond
existing infrastructure, fixes a real user-facing problem, and follows a
well-established pattern with multiple precedents. This is exactly the
kind of commit that stable trees exist to carry.
**YES**
drivers/hid/hid-apple.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 57da4f86a9fa7..233e367cce1d1 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -354,6 +354,7 @@ static const struct apple_key_translation swapped_fn_leftctrl_keys[] = {
};
static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
+ { "SONiX KN85 Keyboard" },
{ "SONiX USB DEVICE" },
{ "SONiX AK870 PRO" },
{ "Keychron" },
--
2.51.0
^ permalink raw reply related
* Re: [patch 1/1] drivers/input/misc/pf1550-onkey.c: fix build with CONFIG_PM_SLEEP=n
From: Dmitry Torokhov @ 2026-02-14 2:56 UTC (permalink / raw)
To: Andrew Morton; +Cc: sean, Frank.Li, linux-input, samuel.kayode
In-Reply-To: <20260214003828.BD1A5C116C6@smtp.kernel.org>
Hi Andrew,
On Fri, Feb 13, 2026 at 04:38:28PM -0800, Andrew Morton wrote:
> From: Andrew Morton <akpm@linux-foundation.org>
> Subject: drivers/input/misc/pf1550-onkey.c: fix build with CONFIG_PM_SLEEP=n
> Date: Fri Feb 13 02:39:04 PM PST 2026
>
> riscv randconfig:
>
> drivers/input/misc/pf1550-onkey.c:154:12: error: 'pf1550_onkey_resume' defined but not used [-Werror=unused-function]
> 154 | static int pf1550_onkey_resume(struct device *dev)
> | ^~~~~~~~~~~~~~~~~~~
> drivers/input/misc/pf1550-onkey.c:133:12: error: 'pf1550_onkey_suspend' defined but not used [-Werror=unused-function]
> 133 | static int pf1550_onkey_suspend(struct device *dev)
> | ^~~~~~~~~~~~~~~~~~~~
> cc1: all warnings being treated as errors
Picked the following fix for this:
https://patch.msgid.link/20251210211149.543928-1-vaibhavgupta40@gmail.com
Thanks.
--
Dmitry
^ permalink raw reply
* [PATCH v3 0/7] mfd: Add support for Asus Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-02-14 18:09 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
---
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,transformer-ec.yaml | 98 +++
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 | 763 ++++++++++++++++++
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, 2064 insertions(+)
create mode 100644 Documentation/devicetree/bindings/embedded-controller/asus,transformer-ec.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
* [PATCH v3 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC
From: Svyatoslav Ryhel @ 2026-02-14 18:09 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: <20260214180959.30714-1-clamor95@gmail.com>
Document embedded controller used in ASUS Transformer device series.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
.../asus,transformer-ec.yaml | 98 +++++++++++++++++++
1 file changed, 98 insertions(+)
create mode 100644 Documentation/devicetree/bindings/embedded-controller/asus,transformer-ec.yaml
diff --git a/Documentation/devicetree/bindings/embedded-controller/asus,transformer-ec.yaml b/Documentation/devicetree/bindings/embedded-controller/asus,transformer-ec.yaml
new file mode 100644
index 000000000000..670c4c2d339d
--- /dev/null
+++ b/Documentation/devicetree/bindings/embedded-controller/asus,transformer-ec.yaml
@@ -0,0 +1,98 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/embedded-controller/asus,transformer-ec.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>
+
+allOf:
+ - $ref: /schemas/power/supply/power-supply.yaml
+
+properties:
+ compatible:
+ oneOf:
+ - enum:
+ - asus,p1801-t-ec-pad
+ - asus,sl101-ec-dock
+ - asus,tf600t-ec-pad
+ - asus,tf701t-ec-pad
+
+ - items:
+ - enum:
+ - asus,tf101-ec-dock
+ - 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,transformer-ec-dock
+
+ - items:
+ - enum:
+ - asus,tf201-ec-pad
+ - asus,tf300t-ec-pad
+ - asus,tf300tg-ec-pad
+ - asus,tf300tl-ec-pad
+ - asus,tf700t-ec-pad
+ - const: asus,transformer-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
+ - 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,transformer-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 v3 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
From: Svyatoslav Ryhel @ 2026-02-14 18:09 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: <20260214180959.30714-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 c7ef347a4dff..1ca17ba632cc 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -97,6 +97,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 6d97bad7b844..9ecf0d011863 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_SERIO_CT82C710) += ct82c710.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..af755cd0ae38
--- /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(sizeof(*port), GFP_KERNEL);
+
+ 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 v3 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-02-14 18:09 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: <20260214180959.30714-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 | 763 ++++++++++++++++++++++++
include/linux/mfd/asus-transformer-ec.h | 162 +++++
4 files changed, 940 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..9bc5dfb2dfad
--- /dev/null
+++ b/drivers/mfd/asus-transformer-ec.c
@@ -0,0 +1,763 @@
+// 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 void asus_ec_handle_smi(struct asus_ec_data *priv, unsigned int code);
+
+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 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 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 *client, u64 *val)
+{
+ return asus_dockram_access_ctl(client, val, 0, 0);
+}
+
+static int control_reg_set(void *client, u64 val)
+{
+ return asus_dockram_access_ctl(client, NULL, ~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,
+ priv->info.dockram, &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;
+
+ 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_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_pad_data = {
+ .name = "pad",
+ .mfd_devices = asus_ec_pad_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_pad_mfd_devices),
+ .clr_fmode = true,
+};
+
+static const struct mfd_cell asus_ec_pad_charger_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_pad_charger_data = {
+ .name = "pad",
+ .mfd_devices = asus_ec_pad_charger_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_pad_charger_mfd_devices),
+ .clr_fmode = true,
+};
+
+static const struct mfd_cell asus_ec_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_dock_data = {
+ .name = "dock",
+ .mfd_devices = asus_ec_dock_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_dock_mfd_devices),
+ .clr_fmode = false,
+};
+
+static const struct mfd_cell asus_ec_dock_sl101_mfd_devices[] = {
+ {
+ .name = "asus-transformer-ec-kbc",
+ },
+};
+
+static const struct asus_ec_chip_data asus_ec_dock_sl101_data = {
+ .name = "dock",
+ .mfd_devices = asus_ec_dock_sl101_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_dock_sl101_mfd_devices),
+ .clr_fmode = false,
+};
+
+static const struct of_device_id asus_ec_match[] = {
+ { .compatible = "asus,transformer-ec-pad", .data = &asus_ec_pad_data },
+ { .compatible = "asus,p1801-t-ec-pad", .data = &asus_ec_pad_charger_data },
+ { .compatible = "asus,tf600t-ec-pad", .data = &asus_ec_pad_charger_data },
+ { .compatible = "asus,tf701t-ec-pad", .data = &asus_ec_pad_charger_data },
+ { .compatible = "asus,transformer-ec-dock", .data = &asus_ec_dock_data },
+ { .compatible = "asus,sl101-ec-dock", .data = &asus_ec_dock_sl101_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 v3 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys
From: Svyatoslav Ryhel @ 2026-02-14 18:09 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: <20260214180959.30714-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 2ff4fef322c2..4e577e5cf216 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 2d906e14f3e2..575edb0e8eb4 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..4cdebce58988
--- /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(sizeof(*handle), GFP_KERNEL);
+ 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 v3 5/7] leds: Add driver for ASUS Transformer LEDs
From: Svyatoslav Ryhel @ 2026-02-14 18:09 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: <20260214180959.30714-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 597d7a79c988..bda06dc145d7 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 v3 6/7] power: supply: Add driver for ASUS Transformer battery
From: Svyatoslav Ryhel @ 2026-02-14 18:09 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: <20260214180959.30714-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>
---
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 81fadb0695a9..3c46b412632d 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 here to enable support APM status emulation using
+ battery class devices.
+
+ This sub-driver supports battery cells found in Asus Transformer
+ tablets and mobile docks and controlled by 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 41c400bbf022..aa5e6b05b018 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
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