* [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller
@ 2026-05-18 9:59 Svyatoslav Ryhel
2026-05-18 9:59 ` [PATCH v7 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC Svyatoslav Ryhel
` (6 more replies)
0 siblings, 7 replies; 14+ messages in thread
From: Svyatoslav Ryhel @ 2026-05-18 9:59 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
Add support for embedded controller used in Asus Transformers for
managing power and input functions.
---
Changes in v2:
- converted sysfs debug exports into debugfs
- added kernel-doc comments for exposed functions
- fixed minor typos and inconsistencies
Changes in v3:
- dropped DockRAM commits (both schema and driver)
- integrated DockRAM functionality directly into the controller driver
- EC schema moved to embedded controllers folder
- removed all cell descriptions from the schema
- removed all compatibles from the cell drivers
- adjusted naming conventions to better align with the ASUS Transformers
- defined EC variant sets to provide coverage for all known devices
Changes in v4:
- grouped known programming models of EC chronologically (both schema
and driver)
- call debugfs init only if CONFIG_DEBUG_FS is enabled
Changes in v5:
- added asus,tf600t-ec-dock compatible to schema
- rebased on top of v7.0
- kzalloc > kzalloc_obj in keys and kbc
Changes in v6:
- removed asus_ec_handle_smi casting
- asus_dockram_access_ctl > asus_ec_get_ctl in control_reg_*
- fixed battery Kconfig description
Changes in v7:
- added status check of devm_kasprintf where missing
- devm_asus_ec_register_notifier dropped, added .remove where it was used
- removed cell_to_ec, asus_dockram_read, asus_dockram_write, asus_ec_* public API
asus_ec_i2c_command, devm_asus_ec_register_notifier, asus_ec_read, asus_ec_write
- renamed asusec_info > asusec_core
- ec-kbc: added packed size check
ret > error
improved key remap logic
- ec-keys: improve formatting and comments
ret > error
switched to dev_err_probe
- ec-leds: reworked to register both leds via loop
- ec-mfd: adjusted Kconfig description
fixed smbus operation sizes
fixed saving of EC fw model
adjusted IRQ flags
converted to use definer for set cell composition
added factory mode states enum and handling
defined some "magic" values
self > client, info > ec, ecreq > ecreq_gpio, priv > ddata
asus_ec_chip_data data > asus_ec_chip_info info
ec_data > ec_buf, ctl_data > ctl_buf
added and improved comments, added structure descriptions
asus_ec_magic_debug > asus_ec_susb_on_status
removed all dev_dbg and most of dev_info
pronts with model, fw behavior, factory and susb state preserved
switched to MFD_CELL_* macros
removed debugfs
- ec-battery: swithced to BIT macro
lock usage moved to asus_ec_battery_get_value
in asus_ec_battery_poll_work fixed possible rescheduling fail
in asus_ec_battery_poll_work fixed missing not charging
- ec-charger: POWER_SUPPLY_PROP_MODEL_NAME set as the first check
---
Michał Mirosław (6):
mfd: Add driver for ASUS Transformer embedded controller
input: serio: Add driver for ASUS Transformer dock keyboard and
touchpad
input: keyboard: Add driver for ASUS Transformer dock multimedia keys
leds: Add driver for ASUS Transformer LEDs
power: supply: Add driver for ASUS Transformer battery
power: supply: Add charger driver for Asus Transformers
Svyatoslav Ryhel (1):
dt-bindings: embedded-controller: document ASUS Transformer EC
.../asus,tf201-ec-pad.yaml | 119 ++++
drivers/input/keyboard/Kconfig | 10 +
drivers/input/keyboard/Makefile | 1 +
.../input/keyboard/asus-transformer-ec-keys.c | 281 +++++++++
drivers/input/serio/Kconfig | 15 +
drivers/input/serio/Makefile | 1 +
drivers/input/serio/asus-transformer-ec-kbc.c | 157 +++++
drivers/leds/Kconfig | 11 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-asus-transformer-ec.c | 109 ++++
drivers/mfd/Kconfig | 16 +
drivers/mfd/Makefile | 1 +
drivers/mfd/asus-transformer-ec.c | 534 ++++++++++++++++++
drivers/power/supply/Kconfig | 22 +
drivers/power/supply/Makefile | 2 +
.../supply/asus-transformer-ec-battery.c | 286 ++++++++++
.../supply/asus-transformer-ec-charger.c | 207 +++++++
include/linux/mfd/asus-transformer-ec.h | 92 +++
18 files changed, 1865 insertions(+)
create mode 100644 Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
create mode 100644 drivers/input/keyboard/asus-transformer-ec-keys.c
create mode 100644 drivers/input/serio/asus-transformer-ec-kbc.c
create mode 100644 drivers/leds/leds-asus-transformer-ec.c
create mode 100644 drivers/mfd/asus-transformer-ec.c
create mode 100644 drivers/power/supply/asus-transformer-ec-battery.c
create mode 100644 drivers/power/supply/asus-transformer-ec-charger.c
create mode 100644 include/linux/mfd/asus-transformer-ec.h
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v7 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC
2026-05-18 9:59 [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller Svyatoslav Ryhel
@ 2026-05-18 9:59 ` Svyatoslav Ryhel
2026-05-18 10:09 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 2/7] mfd: Add driver for ASUS Transformer embedded controller Svyatoslav Ryhel
` (5 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Svyatoslav Ryhel @ 2026-05-18 9:59 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
Document embedded controller used in ASUS Transformer device series.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../asus,tf201-ec-pad.yaml | 119 ++++++++++++++++++
1 file changed, 119 insertions(+)
create mode 100644 Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
diff --git a/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
new file mode 100644
index 000000000000..60b6375864aa
--- /dev/null
+++ b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
@@ -0,0 +1,119 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/embedded-controller/asus,tf201-ec-pad.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ASUS Transformer's Embedded Controller
+
+description:
+ Several Nuvoton based Embedded Controllers attached to an I2C bus,
+ running a custom ASUS firmware, specific to the ASUS Transformer
+ device series.
+
+maintainers:
+ - Svyatoslav Ryhel <clamor95@gmail.com>
+
+properties:
+ compatible:
+ description:
+ The 'pad' suffix is used for the controller within the tablet, while
+ the 'dock' suffix refers to the controller in the mobile dock keyboard.
+ oneOf:
+ - enum:
+ - asus,sl101-ec-dock
+ - asus,tf101-ec-dock
+ - asus,tf201-ec-pad
+ - asus,tf600t-ec-dock
+ - asus,tf600t-ec-pad
+
+ - items:
+ - enum:
+ - asus,tf101g-ec-dock
+ - asus,tf201-ec-dock
+ - asus,tf300t-ec-dock
+ - asus,tf300tg-ec-dock
+ - asus,tf300tl-ec-dock
+ - asus,tf700t-ec-dock
+ - const: asus,tf101-ec-dock
+
+ - items:
+ - enum:
+ - asus,tf300t-ec-pad
+ - asus,tf300tg-ec-pad
+ - asus,tf300tl-ec-pad
+ - asus,tf700t-ec-pad
+ - const: asus,tf201-ec-pad
+
+ - items:
+ - enum:
+ - asus,tf701t-ec-dock
+ - const: asus,tf600t-ec-dock
+
+ - items:
+ - enum:
+ - asus,p1801-t-ec-pad
+ - asus,tf701t-ec-pad
+ - const: asus,tf600t-ec-pad
+
+ reg:
+ description:
+ The ASUS Transformer EC has a main I2C address and an associated
+ DockRAM device, which provides power-related functions for the
+ embedded controller. Both addresses are required for operation.
+ minItems: 2
+
+ reg-names:
+ items:
+ - const: ec
+ - const: dockram
+
+ interrupts:
+ maxItems: 1
+
+ request-gpios:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+
+allOf:
+ - $ref: /schemas/power/supply/power-supply.yaml
+ - if:
+ properties:
+ compatible:
+ not:
+ contains:
+ const: asus,tf600t-ec-dock
+ then:
+ required:
+ - interrupts
+ - request-gpios
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ embedded-controller@19 {
+ compatible = "asus,tf201-ec-dock", "asus,tf101-ec-dock";
+ reg = <0x19>, <0x1b>;
+ reg-names = "ec", "dockram";
+
+ interrupt-parent = <&gpio>;
+ interrupts = <151 IRQ_TYPE_LEVEL_LOW>;
+
+ request-gpios = <&gpio 134 GPIO_ACTIVE_LOW>;
+
+ monitored-battery = <&dock_battery>;
+ };
+ };
+...
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v7 2/7] mfd: Add driver for ASUS Transformer embedded controller
2026-05-18 9:59 [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller Svyatoslav Ryhel
2026-05-18 9:59 ` [PATCH v7 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC Svyatoslav Ryhel
@ 2026-05-18 9:59 ` Svyatoslav Ryhel
2026-05-18 10:31 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad Svyatoslav Ryhel
` (4 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Svyatoslav Ryhel @ 2026-05-18 9:59 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
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 | 16 +
drivers/mfd/Makefile | 1 +
drivers/mfd/asus-transformer-ec.c | 534 ++++++++++++++++++++++++
include/linux/mfd/asus-transformer-ec.h | 92 ++++
4 files changed, 643 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..e1c32505b97a 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -137,6 +137,22 @@ 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"
+ select MFD_CORE
+ depends on I2C && OF
+ help
+ Select this to enable support for the Embedded Controller (EC)
+ found in Tegra based ASUS Transformer series tablets and mobile
+ docks.
+
+ This driver handles the core I2C communication with the EC and
+ provides support for its sub-devices, including battery management,
+ charger detection, LEDs and keyboard dock functions support.
+
+ 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..18bb3e5f384d
--- /dev/null
+++ b/drivers/mfd/asus-transformer-ec.c
@@ -0,0 +1,534 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.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 (DOCKRAM_ENTRIES / DOCKRAM_ENTRY_SIZE)
+
+#define ASUSEC_RESET 0
+#define ASUSEC_RETRY_MAX 3
+#define ASUSEC_ACCESS_TIMEOUT 300
+
+enum asusec_variant {
+ ASUSEC_SL101_DOCK = 1,
+ ASUSEC_TF101_DOCK,
+ ASUSEC_TF201_PAD,
+ ASUSEC_TF600T_PAD,
+ ASUSEC_MAX
+};
+
+enum asusec_mode {
+ ASUSEC_MODE_NONE,
+ ASUSEC_MODE_NORMAL,
+ ASUSEC_MODE_FACTORY,
+ ASUSEC_MODE_MAX
+};
+
+/**
+ * struct asus_ec_chip_info
+ *
+ * @name: prefix associated with the EC
+ * @variant: id of programming model of EC
+ * @mode: state of Factory Mode bit in EC control register
+ */
+struct asus_ec_chip_info {
+ const char *name;
+ enum asusec_variant variant;
+ enum asusec_mode fmode;
+};
+
+/**
+ * struct asus_ec_data
+ *
+ * @ec: public part shared with all cells (must be first)
+ * @ecreq_lock: prevents simultaneous access to EC
+ * @ecreq_gpio: EC request GPIO
+ * @client: pointer to EC's i2c_client
+ * @info: pointer to EC's version description
+ * @ec_buf: buffer for EC read
+ * @logging_disabled: flag disabling logging on reset events
+ */
+struct asus_ec_data {
+ struct asusec_core ec;
+ struct mutex ecreq_lock;
+ struct gpio_desc *ecreq_gpio;
+ struct i2c_client *client;
+ const struct asus_ec_chip_info *info;
+ u8 ec_buf[DOCKRAM_ENTRY_BUFSIZE];
+ bool logging_disabled;
+};
+
+/**
+ * struct dockram_ec_data
+ *
+ * @ctl_lock: prevent simultaneous access to Dockram
+ * @ctl_buf: buffer for Dockram read
+ */
+struct dockram_ec_data {
+ struct mutex ctl_lock;
+ u8 ctl_buf[DOCKRAM_ENTRY_BUFSIZE];
+};
+
+/**
+ * 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 *ddata = i2c_get_clientdata(client);
+ u8 *buf = ddata->ctl_buf;
+ u64 val;
+ int ret = 0;
+
+ guard(mutex)(&ddata->ctl_lock);
+
+ memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
+ ret = i2c_smbus_read_i2c_block_data(client, ASUSEC_DOCKRAM_CONTROL,
+ DOCKRAM_ENTRY_SIZE, buf);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed to access control buffer: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (buf[0] != ASUSEC_CTL_SIZE) {
+ dev_err(&client->dev, "buffer size exceeds %d: %d\n",
+ ASUSEC_CTL_SIZE, buf[0]);
+ return -EPROTO;
+ }
+
+ val = get_unaligned_le64(buf + 1);
+
+ if (out)
+ *out = val;
+
+ if (mask || xor) {
+ put_unaligned_le64((val & ~mask) ^ xor, buf + 1);
+ ret = i2c_smbus_write_i2c_block_data(client,
+ ASUSEC_DOCKRAM_CONTROL,
+ DOCKRAM_ENTRY_SIZE, buf);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_dockram_access_ctl);
+
+static int asus_ec_signal_request(struct asus_ec_data *ddata)
+{
+ guard(mutex)(&ddata->ecreq_lock);
+
+ gpiod_set_value_cansleep(ddata->ecreq_gpio, 1);
+ msleep(50);
+
+ gpiod_set_value_cansleep(ddata->ecreq_gpio, 0);
+ msleep(200);
+
+ return 0;
+}
+
+static void asus_ec_clear_buffer(struct asus_ec_data *ddata)
+{
+ int ret, retry = ASUSEC_RSP_BUFFER_SIZE;
+
+ /*
+ * Read the buffer till we get valid data by checking ASUSEC_OBF_MASK
+ * of the status byte or till we reach end of the 256 byte buffer.
+ */
+ while (retry--) {
+ ret = i2c_smbus_read_i2c_block_data(ddata->client, ASUSEC_READ_BUF,
+ sizeof(ddata->ec_buf),
+ ddata->ec_buf);
+ if (ret < 0)
+ continue;
+
+ if (ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK)
+ continue;
+
+ break;
+ }
+}
+
+static int asus_ec_log_info(struct asus_ec_data *ddata, unsigned int reg,
+ const char *name, char **out)
+{
+ struct device *dev = &ddata->client->dev;
+ u8 buf[DOCKRAM_ENTRY_BUFSIZE];
+ int ret;
+
+ memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
+ ret = i2c_smbus_read_i2c_block_data(ddata->ec.dockram, reg,
+ DOCKRAM_ENTRY_SIZE, 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;
+ }
+
+ if (!ddata->logging_disabled)
+ dev_info(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 *ddata)
+{
+ int retry, ret;
+
+ guard(mutex)(&ddata->ecreq_lock);
+
+ for (retry = 0; retry < ASUSEC_RETRY_MAX; retry++) {
+ ret = i2c_smbus_write_word_data(ddata->client, ASUSEC_WRITE_BUF,
+ ASUSEC_RESET);
+ if (!ret)
+ return 0;
+
+ msleep(ASUSEC_ACCESS_TIMEOUT);
+ }
+
+ return ret;
+}
+
+static int asus_ec_susb_on_status(struct asus_ec_data *ddata)
+{
+ u64 flag;
+ int ret;
+
+ ret = asus_dockram_access_ctl(ddata->ec.dockram, &flag, 0, 0);
+ if (ret)
+ return ret;
+
+ flag &= ASUSEC_CTL_SUSB_MODE;
+ dev_info(&ddata->client->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 *ddata,
+ enum asusec_mode fmode)
+{
+ dev_info(&ddata->client->dev, "Entering %s mode.\n",
+ fmode == ASUSEC_MODE_FACTORY ? "factory" : "normal");
+
+ return asus_dockram_access_ctl(ddata->ec.dockram, NULL,
+ ASUSEC_CTL_FACTORY_MODE,
+ fmode == ASUSEC_MODE_FACTORY ?
+ ASUSEC_CTL_FACTORY_MODE : 0);
+}
+
+static int asus_ec_detect(struct asus_ec_data *ddata)
+{
+ char *model __free(kfree) = NULL;
+ int ret;
+
+ ret = asus_ec_reset(ddata);
+ if (ret)
+ goto err_exit;
+
+ asus_ec_clear_buffer(ddata);
+
+ ret = asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_MODEL, "Model", &model);
+ if (ret)
+ goto err_exit;
+
+ ret = asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_FW, "FW version", NULL);
+ if (ret)
+ goto err_exit;
+
+ ret = asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_CFGFMT, "Config format", NULL);
+ if (ret)
+ goto err_exit;
+
+ ret = asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_HW, "HW version", NULL);
+ if (ret)
+ goto err_exit;
+
+ /* Disable logging on next EC request */
+ ddata->logging_disabled = true;
+
+ /* Check and inform about EC firmware behavior */
+ ret = asus_ec_susb_on_status(ddata);
+ if (ret)
+ goto err_exit;
+
+ kfree(ddata->ec.model);
+
+ ddata->ec.model = no_free_ptr(model);
+ ddata->ec.name = ddata->info->name;
+
+ /* Some EC require factory mode to be set normal on each request */
+ if (ddata->info->fmode)
+ ret = asus_ec_set_factory_mode(ddata, ddata->info->fmode);
+
+err_exit:
+ if (ret)
+ dev_err(&ddata->client->dev, "failed to access EC: %d\n", ret);
+
+ return ret;
+}
+
+static void asus_ec_handle_smi(struct asus_ec_data *ddata, unsigned int code)
+{
+ switch (code) {
+ case ASUSEC_SMI_HANDSHAKE:
+ case ASUSEC_SMI_RESET:
+ asus_ec_detect(ddata);
+ break;
+ }
+}
+
+static irqreturn_t asus_ec_interrupt(int irq, void *dev_id)
+{
+ struct asus_ec_data *ddata = dev_id;
+ unsigned long notify_action;
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(ddata->client, ASUSEC_READ_BUF,
+ sizeof(ddata->ec_buf), ddata->ec_buf);
+ if (ret <= 0 || !(ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK))
+ return IRQ_NONE;
+
+ notify_action = ddata->ec_buf[ASUSEC_IRQ_STATUS];
+ if (notify_action & ASUSEC_SMI_MASK) {
+ unsigned int code = ddata->ec_buf[ASUSEC_SMI_CODE];
+
+ asus_ec_handle_smi(ddata, code);
+
+ notify_action |= code << 8;
+ }
+
+ blocking_notifier_call_chain(&ddata->ec.notify_list,
+ notify_action, ddata->ec_buf);
+
+ return IRQ_HANDLED;
+}
+
+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 *ddata;
+ 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);
+
+ ddata = devm_kzalloc(&dockram->dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return ERR_PTR(-ENOMEM);
+
+ i2c_set_clientdata(dockram, ddata);
+ mutex_init(&ddata->ctl_lock);
+
+ return dockram;
+}
+
+static const struct mfd_cell asus_ec_sl101_dock_mfd_devices[] = {
+ MFD_CELL_NAME("asus-transformer-ec-kbc"),
+};
+
+static const struct mfd_cell asus_ec_tf101_dock_mfd_devices[] = {
+ MFD_CELL_BASIC("asus-transformer-ec-battery", NULL, NULL, 0, 1),
+ MFD_CELL_BASIC("asus-transformer-ec-charger", NULL, NULL, 0, 1),
+ MFD_CELL_BASIC("asus-transformer-ec-led", NULL, NULL, 0, 1),
+ MFD_CELL_NAME("asus-transformer-ec-keys"),
+ MFD_CELL_NAME("asus-transformer-ec-kbc"),
+};
+
+static const struct mfd_cell asus_ec_tf201_pad_mfd_devices[] = {
+ MFD_CELL_NAME("asus-transformer-ec-battery"),
+ MFD_CELL_NAME("asus-transformer-ec-led"),
+};
+
+static const struct mfd_cell asus_ec_tf600t_pad_mfd_devices[] = {
+ MFD_CELL_NAME("asus-transformer-ec-battery"),
+ MFD_CELL_NAME("asus-transformer-ec-charger"),
+ MFD_CELL_NAME("asus-transformer-ec-led"),
+};
+
+static int asus_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct asus_ec_data *ddata;
+ const struct mfd_cell *cells;
+ unsigned int num_cells;
+ unsigned long irqflags;
+ 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");
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ddata->info = device_get_match_data(dev);
+ if (!ddata->info)
+ return -ENODEV;
+
+ switch (ddata->info->variant) {
+ case ASUSEC_SL101_DOCK:
+ cells = asus_ec_sl101_dock_mfd_devices;
+ num_cells = ARRAY_SIZE(asus_ec_sl101_dock_mfd_devices);
+ break;
+ case ASUSEC_TF101_DOCK:
+ cells = asus_ec_tf101_dock_mfd_devices;
+ num_cells = ARRAY_SIZE(asus_ec_tf101_dock_mfd_devices);
+ break;
+ case ASUSEC_TF201_PAD:
+ cells = asus_ec_tf201_pad_mfd_devices;
+ num_cells = ARRAY_SIZE(asus_ec_tf201_pad_mfd_devices);
+ break;
+ case ASUSEC_TF600T_PAD:
+ cells = asus_ec_tf600t_pad_mfd_devices;
+ num_cells = ARRAY_SIZE(asus_ec_tf600t_pad_mfd_devices);
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "unknown device variant %d\n",
+ ddata->info->variant);
+ }
+
+ i2c_set_clientdata(client, ddata);
+ ddata->client = client;
+
+ ddata->ec.dockram = devm_asus_dockram_get(dev);
+ if (IS_ERR(ddata->ec.dockram))
+ return dev_err_probe(dev, PTR_ERR(ddata->ec.dockram),
+ "failed to get dockram\n");
+
+ ddata->ecreq_gpio = devm_gpiod_get(dev, "request", GPIOD_OUT_LOW);
+ if (IS_ERR(ddata->ecreq_gpio))
+ return dev_err_probe(dev, PTR_ERR(ddata->ecreq_gpio),
+ "failed to get EC request GPIO\n");
+
+ BLOCKING_INIT_NOTIFIER_HEAD(&ddata->ec.notify_list);
+ mutex_init(&ddata->ecreq_lock);
+
+ asus_ec_signal_request(ddata);
+
+ ret = asus_ec_detect(ddata);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to detect EC version\n");
+
+ /*
+ * Systems using device tree should set up interrupt via DTS,
+ * the rest will use the default low interrupt.
+ */
+ irqflags = dev->of_node ? 0 : IRQF_TRIGGER_LOW;
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ &asus_ec_interrupt,
+ IRQF_ONESHOT | irqflags,
+ client->name, ddata);
+ 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;
+
+ return devm_mfd_add_devices(dev, 0, cells, num_cells, NULL, 0, NULL);
+}
+
+static const struct asus_ec_chip_info asus_ec_sl101_dock_data = {
+ .name = "dock",
+ .variant = ASUSEC_SL101_DOCK,
+ .fmode = ASUSEC_MODE_NONE,
+};
+
+static const struct asus_ec_chip_info asus_ec_tf101_dock_data = {
+ .name = "dock",
+ .variant = ASUSEC_TF101_DOCK,
+ .fmode = ASUSEC_MODE_NONE,
+};
+
+static const struct asus_ec_chip_info asus_ec_tf201_pad_data = {
+ .name = "pad",
+ .variant = ASUSEC_TF201_PAD,
+ .fmode = ASUSEC_MODE_NORMAL,
+};
+
+static const struct asus_ec_chip_info asus_ec_tf600t_pad_data = {
+ .name = "pad",
+ .variant = ASUSEC_TF600T_PAD,
+ .fmode = ASUSEC_MODE_NORMAL,
+};
+
+static const struct of_device_id asus_ec_match[] = {
+ {
+ .compatible = "asus,sl101-ec-dock",
+ .data = &asus_ec_sl101_dock_data
+ }, {
+ .compatible = "asus,tf101-ec-dock",
+ .data = &asus_ec_tf101_dock_data
+ }, {
+ .compatible = "asus,tf201-ec-pad",
+ .data = &asus_ec_tf201_pad_data
+ }, {
+ .compatible = "asus,tf600t-ec-pad",
+ .data = &asus_ec_tf600t_pad_data
+ },
+ { /* sentinel */ }
+};
+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..980a31f6b25c
--- /dev/null
+++ b/include/linux/mfd/asus-transformer-ec.h
@@ -0,0 +1,92 @@
+/* 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>
+
+struct i2c_client;
+
+/**
+ * struct asusec_core - public part shared with all cells
+ *
+ * @model: firmware version running on the EC
+ * @name: prefix associated with the EC
+ * @dockram: pointer to Dockram's i2c_client
+ * @notify_list: notify list used by cells
+ */
+struct asusec_core {
+ const char *model;
+ const char *name;
+ struct i2c_client *dockram;
+ 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_IRQ_STATUS 1
+#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_CODE 2
+#define ASUSEC_SMI_POWER_NOTIFY 0x31 /* USB cable plug event */
+#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 /* charger to dock plug event */
+#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
+
+int asus_dockram_access_ctl(struct i2c_client *client,
+ u64 *out, u64 mask, u64 xor);
+
+#endif /* __MFD_ASUS_TRANSFORMER_EC_H */
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v7 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
2026-05-18 9:59 [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller Svyatoslav Ryhel
2026-05-18 9:59 ` [PATCH v7 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC Svyatoslav Ryhel
2026-05-18 9:59 ` [PATCH v7 2/7] mfd: Add driver for ASUS Transformer embedded controller Svyatoslav Ryhel
@ 2026-05-18 9:59 ` Svyatoslav Ryhel
2026-05-18 11:17 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys Svyatoslav Ryhel
` (3 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Svyatoslav Ryhel @ 2026-05-18 9:59 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
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 | 157 ++++++++++++++++++
3 files changed, 173 insertions(+)
create mode 100644 drivers/input/serio/asus-transformer-ec-kbc.c
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index 5f15a6462056..fad29b950309 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -84,6 +84,21 @@ config SERIO_RPCKBD
To compile this driver as a module, choose M here: the
module will be called rpckbd.
+config SERIO_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's Dock keyboard and touchpad controller"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y here if you want to use the keyboard and/or touchpad on
+ Asus Transformed's Mobile Dock.
+
+ For keyboard support you also need atkbd driver.
+
+ For touchpad support you also need psmouse driver with Elantech
+ touchpad option enabled.
+
+ To compile this driver as a module, choose M here: the module will
+ be called asus-transformer-ec-kbc.
+
config SERIO_AMBAKMI
tristate "AMBA KMI keyboard controller"
depends on ARM_AMBA
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 8ab98f4aa28d..fedc37ee102b 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_SERIO_SERPORT) += serport.o
obj-$(CONFIG_SERIO_RPCKBD) += rpckbd.o
obj-$(CONFIG_SERIO_SA1111) += sa1111ps2.o
obj-$(CONFIG_SERIO_AMBAKMI) += ambakmi.o
+obj-$(CONFIG_SERIO_ASUS_TRANSFORMER_EC) += asus-transformer-ec-kbc.o
obj-$(CONFIG_SERIO_Q40KBD) += q40kbd.o
obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o
obj-$(CONFIG_HP_SDC) += hp_sdc.o
diff --git a/drivers/input/serio/asus-transformer-ec-kbc.c b/drivers/input/serio/asus-transformer-ec-kbc.c
new file mode 100644
index 000000000000..c9707c7fbede
--- /dev/null
+++ b/drivers/input/serio/asus-transformer-ec-kbc.c
@@ -0,0 +1,157 @@
+// 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_core *ec;
+ struct i2c_client *parent;
+ 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;
+
+ /*
+ * The data[0] is the length of the packet including itself. It has to
+ * be at least 2 bytes (length + 1 data byte) and must not exceed
+ * the EC buffer size.
+ */
+ if (data[0] < 2 || data[0] > DOCKRAM_ENTRY_BUFSIZE)
+ return NOTIFY_BAD;
+
+ n = data[0] - 1;
+ data += 2;
+
+ if (port_idx == 0) {
+ /*
+ * Remap keyboard key codes to match AT layout:
+ * SEARCH: RIGHT-META [E0 27] -> LEFT-ALT [11]
+ * MENU: COMPOSE [E0 2F] -> RIGHT-META [E0 27]
+ */
+ if ((n == 2 || (n == 3 && data[1] == 0xF0)) && data[0] == 0xE0) {
+ u8 *keycode = &data[n - 1];
+
+ switch (*keycode) {
+ case 0x27:
+ *keycode = 0x11;
+ ++data;
+ --n;
+ break;
+ case 0x2F:
+ *keycode = 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)
+{
+ struct asus_ec_kbc_data *priv = port->port_data;
+
+ return i2c_smbus_write_word_data(priv->parent, ASUSEC_WRITE_BUF,
+ (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 = priv->parent;
+ struct serio *port = kzalloc_obj(*port);
+
+ if (!port)
+ return -ENOMEM;
+
+ priv->sdev[idx] = port;
+ port->dev.parent = &pdev->dev;
+ port->id.type = SERIO_8042;
+ port->id.extra = cmd & 0xFF;
+ port->write = asus_ec_serio_write;
+ port->port_data = (void *)priv;
+ 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_core *ec = dev_get_drvdata(pdev->dev.parent);
+ struct asus_ec_kbc_data *priv;
+ int error;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ priv->ec = ec;
+ priv->parent = to_i2c_client(pdev->dev.parent);
+
+ error = asus_ec_register_serio(pdev, 0, "Keyboard", 0);
+ if (error)
+ return error;
+
+ error = asus_ec_register_serio(pdev, 1, "Touchpad", I8042_CMD_AUX_SEND);
+ if (error)
+ return error;
+
+ priv->nb.notifier_call = asus_ec_kbc_notify;
+
+ return blocking_notifier_chain_register(&ec->notify_list, &priv->nb);
+}
+
+static void asus_ec_kbc_remove(struct platform_device *pdev)
+{
+ struct asus_ec_kbc_data *priv = platform_get_drvdata(pdev);
+ struct asusec_core *ec = priv->ec;
+
+ blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb);
+}
+
+static struct platform_driver asus_ec_kbc_driver = {
+ .driver.name = "asus-transformer-ec-kbc",
+ .probe = asus_ec_kbc_probe,
+ .remove = asus_ec_kbc_remove,
+};
+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 [flat|nested] 14+ messages in thread
* [PATCH v7 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys
2026-05-18 9:59 [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller Svyatoslav Ryhel
` (2 preceding siblings ...)
2026-05-18 9:59 ` [PATCH v7 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad Svyatoslav Ryhel
@ 2026-05-18 9:59 ` Svyatoslav Ryhel
2026-05-18 11:46 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 5/7] leds: Add driver for ASUS Transformer LEDs Svyatoslav Ryhel
` (2 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Svyatoslav Ryhel @ 2026-05-18 9:59 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
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 | 281 ++++++++++++++++++
3 files changed, 292 insertions(+)
create mode 100644 drivers/input/keyboard/asus-transformer-ec-keys.c
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 9d1019ba0245..913cb4900565 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -89,6 +89,16 @@ config KEYBOARD_APPLESPI
To compile this driver as a module, choose M here: the
module will be called applespi.
+config KEYBOARD_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's Mobile Dock multimedia keys"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y here if you want to use multimedia keys present on Asus
+ Transformer's Mobile Dock.
+
+ To compile this driver as a module, choose M here: the
+ module will be called asus-transformer-ec-keys.
+
config KEYBOARD_ATARI
tristate "Atari keyboard"
depends on ATARI
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 60bb7baf802f..0d81096887ad 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_KEYBOARD_ADP5585) += adp5585-keys.o
obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o
obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o
obj-$(CONFIG_KEYBOARD_APPLESPI) += applespi.o
+obj-$(CONFIG_KEYBOARD_ASUS_TRANSFORMER_EC) += asus-transformer-ec-keys.o
obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o
obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o
obj-$(CONFIG_KEYBOARD_BCM) += bcm-keypad.o
diff --git a/drivers/input/keyboard/asus-transformer-ec-keys.c b/drivers/input/keyboard/asus-transformer-ec-keys.c
new file mode 100644
index 000000000000..d30d99177374
--- /dev/null
+++ b/drivers/input/keyboard/asus-transformer-ec-keys.c
@@ -0,0 +1,281 @@
+// 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_core *ec;
+ struct input_dev *xidev;
+ bool special_key_pressed;
+ bool special_key_mode;
+ unsigned short keymap[ASUSEC_EXT_KEY_CODES * 2];
+};
+
+static void asus_ec_input_event(struct input_handle *handle,
+ unsigned int event_type,
+ unsigned int event_code, int value)
+{
+ struct asus_ec_keys_data *priv = handle->handler->private;
+
+ /* Store special key state */
+ if (event_type == EV_KEY && event_code == KEY_RIGHTALT)
+ priv->special_key_pressed = !!value;
+}
+
+static int asus_ec_input_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_handle *handle;
+ int error;
+
+ handle = kzalloc_obj(*handle);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = "asusec-media-handler";
+
+ error = input_register_handle(handle);
+ if (error)
+ goto err_free_handle;
+
+ error = input_open_device(handle);
+ if (error)
+ goto err_unregister_handle;
+
+ return 0;
+
+ err_unregister_handle:
+ input_unregister_handle(handle);
+ err_free_handle:
+ kfree(handle);
+
+ return error;
+}
+
+static void asus_ec_input_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+static const struct input_device_id asus_ec_input_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_KEY) },
+ },
+ { }
+};
+
+static struct input_handler asus_ec_input_handler = {
+ .name = "asusec-media-handler",
+ .event = asus_ec_input_event,
+ .connect = asus_ec_input_connect,
+ .disconnect = asus_ec_input_disconnect,
+ .id_table = asus_ec_input_ids,
+};
+
+static const unsigned short asus_ec_dock_ext_keys[] = {
+ /* Function keys [0x00 - 0x19] */
+ [0x01] = KEY_DELETE,
+ [0x02] = KEY_F1,
+ [0x03] = KEY_F2,
+ [0x04] = KEY_F3,
+ [0x05] = KEY_F4,
+ [0x06] = KEY_F5,
+ [0x07] = KEY_F6,
+ [0x08] = KEY_F7,
+ [0x10] = KEY_F8,
+ [0x11] = KEY_F9,
+ [0x12] = KEY_F10,
+ [0x13] = KEY_F11,
+ [0x14] = KEY_F12,
+ [0x15] = KEY_MUTE,
+ [0x16] = KEY_VOLUMEDOWN,
+ [0x17] = KEY_VOLUMEUP,
+ /* Multimedia keys [0x20 - 0x39] */
+ [0x21] = KEY_SCREENLOCK,
+ [0x22] = KEY_WLAN,
+ [0x23] = KEY_BLUETOOTH,
+ [0x24] = KEY_TOUCHPAD_TOGGLE,
+ [0x25] = KEY_BRIGHTNESSDOWN,
+ [0x26] = KEY_BRIGHTNESSUP,
+ [0x27] = KEY_BRIGHTNESS_AUTO,
+ [0x28] = KEY_PRINT,
+ [0x30] = KEY_WWW,
+ [0x31] = KEY_CONFIG,
+ [0x32] = KEY_PREVIOUSSONG,
+ [0x33] = KEY_PLAYPAUSE,
+ [0x34] = KEY_NEXTSONG,
+ [0x35] = KEY_MUTE,
+ [0x36] = KEY_VOLUMEDOWN,
+ [0x37] = KEY_VOLUMEUP,
+};
+
+static void asus_ec_keys_report_key(struct input_dev *dev, unsigned int code,
+ unsigned int key, bool value)
+{
+ input_event(dev, EV_MSC, MSC_SCAN, code);
+ input_report_key(dev, key, value);
+ input_sync(dev);
+}
+
+static int asus_ec_keys_process_key(struct input_dev *dev, u8 code)
+{
+ struct asus_ec_keys_data *priv = dev_get_drvdata(dev->dev.parent);
+ unsigned int key = 0;
+
+ if (code == 0)
+ return NOTIFY_DONE;
+
+ /* Flip special key mode state when pressing SCREEN LOCK + R ALT */
+ 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 int asus_ec_keys_probe(struct platform_device *pdev)
+{
+ struct i2c_client *parent = to_i2c_client(pdev->dev.parent);
+ struct asusec_core *ec = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct asus_ec_keys_data *priv;
+ int error;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->ec = ec;
+
+ priv->xidev = devm_input_allocate_device(dev);
+ if (!priv->xidev)
+ return -ENOMEM;
+
+ priv->xidev->name = devm_kasprintf(dev, GFP_KERNEL, "%s Keyboard Ext",
+ ec->model);
+ priv->xidev->phys = devm_kasprintf(dev, GFP_KERNEL, "i2c-%u-%04x",
+ i2c_adapter_id(parent->adapter),
+ parent->addr);
+
+ if (!priv->xidev->name || !priv->xidev->phys)
+ return -ENOMEM;
+
+ asus_ec_keys_setup_keymap(priv);
+
+ error = input_register_device(priv->xidev);
+ if (error)
+ return dev_err_probe(dev, error,
+ "failed to register extension keys\n");
+
+ asus_ec_input_handler.private = priv;
+ error = input_register_handler(&asus_ec_input_handler);
+ if (error)
+ return error;
+
+ priv->nb.notifier_call = asus_ec_keys_notify;
+
+ error = blocking_notifier_chain_register(&ec->notify_list, &priv->nb);
+ if (error) {
+ input_unregister_handler(&asus_ec_input_handler);
+ return error;
+ }
+
+ return 0;
+}
+
+static void asus_ec_keys_remove(struct platform_device *pdev)
+{
+ struct asus_ec_keys_data *priv = platform_get_drvdata(pdev);
+ struct asusec_core *ec = priv->ec;
+
+ input_unregister_handler(&asus_ec_input_handler);
+ blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb);
+}
+
+static struct platform_driver asus_ec_keys_driver = {
+ .driver.name = "asus-transformer-ec-keys",
+ .probe = asus_ec_keys_probe,
+ .remove = asus_ec_keys_remove,
+};
+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 [flat|nested] 14+ messages in thread
* [PATCH v7 5/7] leds: Add driver for ASUS Transformer LEDs
2026-05-18 9:59 [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller Svyatoslav Ryhel
` (3 preceding siblings ...)
2026-05-18 9:59 ` [PATCH v7 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys Svyatoslav Ryhel
@ 2026-05-18 9:59 ` Svyatoslav Ryhel
2026-05-18 12:02 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 6/7] power: supply: Add driver for ASUS Transformer battery Svyatoslav Ryhel
2026-05-18 9:59 ` [PATCH v7 7/7] power: supply: Add charger driver for Asus Transformers Svyatoslav Ryhel
6 siblings, 1 reply; 14+ messages in thread
From: Svyatoslav Ryhel @ 2026-05-18 9:59 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
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 | 109 ++++++++++++++++++++++++
3 files changed, 121 insertions(+)
create mode 100644 drivers/leds/leds-asus-transformer-ec.c
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index f4a0a3c8c870..f637d23400a8 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -120,6 +120,17 @@ config LEDS_OSRAM_AMS_AS3668
To compile this driver as a module, choose M here: the module
will be called leds-as3668.
+config LEDS_ASUS_TRANSFORMER_EC
+ tristate "LED Support for Asus Transformer charging LED"
+ depends on LEDS_CLASS
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ This option enables support for charging indicator on
+ Asus Transformer's Pad and it's Dock.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-asus-transformer-ec.
+
config LEDS_AW200XX
tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 8fdb45d5b439..d5395c3f1124 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
obj-$(CONFIG_LEDS_APU) += leds-apu.o
obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
obj-$(CONFIG_LEDS_AS3668) += leds-as3668.o
+obj-$(CONFIG_LEDS_ASUS_TRANSFORMER_EC) += leds-asus-transformer-ec.o
obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
diff --git a/drivers/leds/leds-asus-transformer-ec.c b/drivers/leds/leds-asus-transformer-ec.c
new file mode 100644
index 000000000000..c9bdc46c02e1
--- /dev/null
+++ b/drivers/leds/leds-asus-transformer-ec.c
@@ -0,0 +1,109 @@
+// 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>
+
+enum {
+ ASUSEC_LED_AMBER,
+ ASUSEC_LED_GREEN,
+ ASUSEC_LED_MAX
+};
+
+struct asus_ec_led_config {
+ const char *name;
+ unsigned int color;
+ unsigned long long ctrl_bit;
+};
+
+struct asus_ec_led {
+ struct asus_ec_leds_data *ddata;
+ struct led_classdev cdev;
+ unsigned long long ctrl_bit;
+};
+
+struct asus_ec_leds_data {
+ const struct asusec_core *ec;
+ struct asus_ec_led leds[ASUSEC_LED_MAX];
+};
+
+static const struct asus_ec_led_config asus_ec_leds[] = {
+ [ASUSEC_LED_AMBER] = {
+ .name = "amber",
+ .color = LED_COLOR_ID_AMBER,
+ .ctrl_bit = ASUSEC_CTL_LED_AMBER,
+ },
+ [ASUSEC_LED_GREEN] = {
+ .name = "green",
+ .color = LED_COLOR_ID_GREEN,
+ .ctrl_bit = ASUSEC_CTL_LED_GREEN,
+ },
+};
+
+static int asus_ec_led_set_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct asus_ec_led *led = container_of(cdev, struct asus_ec_led, cdev);
+ const struct asusec_core *ec = led->ddata->ec;
+
+ if (brightness)
+ return asus_dockram_access_ctl(ec->dockram, NULL,
+ led->ctrl_bit, led->ctrl_bit);
+
+ return asus_dockram_access_ctl(ec->dockram, NULL, led->ctrl_bit, 0);
+}
+
+static int asus_ec_led_probe(struct platform_device *pdev)
+{
+ const struct asusec_core *ec = dev_get_drvdata(pdev->dev.parent);
+ struct asus_ec_leds_data *ddata;
+ struct device *dev = &pdev->dev;
+ int i, ret;
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ddata);
+ ddata->ec = ec;
+
+ for (i = 0; i < ASUSEC_LED_MAX; i++) {
+ const struct asus_ec_led_config *cfg = &asus_ec_leds[i];
+ struct asus_ec_led *led = &ddata->leds[i];
+
+ led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s::%s",
+ ddata->ec->name, cfg->name);
+ if (!led->cdev.name)
+ return -ENOMEM;
+
+ led->cdev.max_brightness = 1;
+ led->cdev.color = cfg->color;
+ led->cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ led->cdev.brightness_set_blocking = asus_ec_led_set_brightness;
+
+ led->ddata = ddata;
+ led->ctrl_bit = cfg->ctrl_bit;
+
+ ret = devm_led_classdev_register(dev, &led->cdev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register %s LED\n",
+ cfg->name);
+ }
+
+ 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 [flat|nested] 14+ messages in thread
* [PATCH v7 6/7] power: supply: Add driver for ASUS Transformer battery
2026-05-18 9:59 [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller Svyatoslav Ryhel
` (4 preceding siblings ...)
2026-05-18 9:59 ` [PATCH v7 5/7] leds: Add driver for ASUS Transformer LEDs Svyatoslav Ryhel
@ 2026-05-18 9:59 ` Svyatoslav Ryhel
2026-05-18 12:37 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 7/7] power: supply: Add charger driver for Asus Transformers Svyatoslav Ryhel
6 siblings, 1 reply; 14+ messages in thread
From: Svyatoslav Ryhel @ 2026-05-18 9:59 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
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Driver implements one battery cell per EC controller and supports reading
of battery status for ASUS Transformer's pad and mobile dock.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
.../supply/asus-transformer-ec-battery.c | 286 ++++++++++++++++++
3 files changed, 298 insertions(+)
create mode 100644 drivers/power/supply/asus-transformer-ec-battery.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 83392ed6a8da..1dc3d0b2e021 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -122,6 +122,17 @@ config BATTERY_CHAGALL
This driver can also be built as a module. If so, the module will be
called chagall-battery.
+config BATTERY_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's battery driver"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y to enable support for battery status access on Tegra based
+ ASUS Transformer devices.
+
+ This sub-driver supports battery cells found in Asus Transformer
+ tablets and mobile docks and controlled by a special embedded
+ controller.
+
config BATTERY_CPCAP
tristate "Motorola CPCAP PMIC battery driver"
depends on MFD_CPCAP && IIO
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 7ee839dca7f3..1313f367715c 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_TEST_POWER) += test_power.o
obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
obj-$(CONFIG_CHARGER_ADP5061) += adp5061.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
+obj-$(CONFIG_BATTERY_ASUS_TRANSFORMER_EC) += asus-transformer-ec-battery.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o
diff --git a/drivers/power/supply/asus-transformer-ec-battery.c b/drivers/power/supply/asus-transformer-ec-battery.c
new file mode 100644
index 000000000000..d08d155f72a3
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-battery.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/devm-helpers.h>
+#include <linux/err.h>
+#include <linux/i2c.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 BIT(6)
+#define ASUSEC_BATTERY_FULL_CHARGED BIT(5)
+#define ASUSEC_BATTERY_NOT_CHARGING BIT(4)
+
+#define TEMP_CELSIUS_OFFSET 2731
+
+struct asus_ec_battery_data {
+ struct asusec_core *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)
+{
+ struct i2c_client *client = priv->ec->dockram;
+ struct device *dev = &client->dev;
+ int ret = 0;
+
+ if (time_before(jiffies, priv->batt_data_ts))
+ return ret;
+
+ memset(priv->batt_data, 0, DOCKRAM_ENTRY_BUFSIZE);
+ ret = i2c_smbus_read_i2c_block_data(client, ASUSEC_DOCKRAM_BATT_CTL,
+ DOCKRAM_ENTRY_SIZE, priv->batt_data);
+ if (ret < 0)
+ return ret;
+
+ if (priv->batt_data[0] > DOCKRAM_ENTRY_SIZE) {
+ dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
+ DOCKRAM_ENTRY_BUFSIZE, priv->batt_data, ret);
+ return -EPROTO;
+ }
+
+ 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;
+
+ guard(mutex)(&priv->battery_lock);
+
+ 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)
+ goto reschedule;
+
+ if (state & ASUSEC_BATTERY_FULL_CHARGED)
+ state = POWER_SUPPLY_STATUS_FULL;
+ else if (state & ASUSEC_BATTERY_NOT_CHARGING)
+ state = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ 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);
+ }
+
+reschedule:
+ /* 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 asusec_core *ec = dev_get_drvdata(pdev->dev.parent);
+ 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 = ec;
+ 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 [flat|nested] 14+ messages in thread
* [PATCH v7 7/7] power: supply: Add charger driver for Asus Transformers
2026-05-18 9:59 [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller Svyatoslav Ryhel
` (5 preceding siblings ...)
2026-05-18 9:59 ` [PATCH v7 6/7] power: supply: Add driver for ASUS Transformer battery Svyatoslav Ryhel
@ 2026-05-18 9:59 ` Svyatoslav Ryhel
6 siblings, 0 replies; 14+ messages in thread
From: Svyatoslav Ryhel @ 2026-05-18 9:59 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
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Add support for charger detection capabilities found in the embedded
controller of ASUS Transformer devices.
Suggested-by: Maxim Schwalm <maxim.schwalm@gmail.com>
Suggested-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
.../supply/asus-transformer-ec-charger.c | 207 ++++++++++++++++++
3 files changed, 219 insertions(+)
create mode 100644 drivers/power/supply/asus-transformer-ec-charger.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 1dc3d0b2e021..ebc6d5c01330 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -508,6 +508,17 @@ config CHARGER_88PM860X
help
Say Y here to enable charger for Marvell 88PM860x chip.
+config CHARGER_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's charger driver"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y here to enable support AC plug detection on Asus Transformer
+ Dock.
+
+ This sub-driver supports charger detection mechanism found in Asus
+ Transformer tablets and mobile docks and controlled by special
+ embedded controller.
+
config CHARGER_PF1550
tristate "NXP PF1550 battery charger driver"
depends on MFD_PF1550
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 1313f367715c..93d17d28081e 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_CHARGER_RT9471) += rt9471.o
obj-$(CONFIG_CHARGER_RT9756) += rt9756.o
obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
+obj-$(CONFIG_CHARGER_ASUS_TRANSFORMER_EC) += asus-transformer-ec-charger.o
obj-$(CONFIG_CHARGER_PF1550) += pf1550-charger.o
obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o ab8500_chargalg.o
diff --git a/drivers/power/supply/asus-transformer-ec-charger.c b/drivers/power/supply/asus-transformer-ec-charger.c
new file mode 100644
index 000000000000..e33f4389d337
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-charger.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/err.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+
+struct asus_ec_charger_data {
+ struct notifier_block nb;
+ struct asusec_core *ec;
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+};
+
+static enum power_supply_property asus_ec_charger_properties[] = {
+ POWER_SUPPLY_PROP_USB_TYPE,
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int asus_ec_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
+ enum power_supply_usb_type psu;
+ int ret;
+ u64 ctl;
+
+ /* Check if model name is requested first since it needs no hw access */
+ if (psp == POWER_SUPPLY_PROP_MODEL_NAME) {
+ val->strval = priv->ec->model;
+ return 0;
+ }
+
+ ret = asus_dockram_access_ctl(priv->ec->dockram, &ctl, 0, 0);
+ if (ret)
+ return ret;
+
+ switch (ctl & (ASUSEC_CTL_FULL_POWER_SOURCE | ASUSEC_CTL_DIRECT_POWER_SOURCE)) {
+ case ASUSEC_CTL_FULL_POWER_SOURCE:
+ psu = POWER_SUPPLY_USB_TYPE_CDP; /* DOCK */
+ break;
+ case ASUSEC_CTL_DIRECT_POWER_SOURCE:
+ psu = POWER_SUPPLY_USB_TYPE_SDP; /* USB */
+ break;
+ case 0:
+ psu = POWER_SUPPLY_USB_TYPE_UNKNOWN; /* no power source connected */
+ break;
+ default:
+ psu = POWER_SUPPLY_USB_TYPE_ACA; /* power adapter */
+ break;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = psu != POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ return 0;
+
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = psu;
+ return 0;
+
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ if (ctl & ASUSEC_CTL_TEST_DISCHARGE)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
+ else if (ctl & ASUSEC_CTL_USB_CHARGE)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int asus_ec_charger_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ switch ((enum power_supply_charge_behaviour)val->intval) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ return asus_dockram_access_ctl(priv->ec->dockram, NULL,
+ ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
+ ASUSEC_CTL_USB_CHARGE);
+
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ return asus_dockram_access_ctl(priv->ec->dockram, NULL,
+ ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE, 0);
+
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+ return asus_dockram_access_ctl(priv->ec->dockram, NULL,
+ ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
+ ASUSEC_CTL_TEST_DISCHARGE);
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int asus_ec_charger_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct power_supply_desc asus_ec_charger_desc = {
+ .name = "asus-ec-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) |
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) |
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE),
+ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) |
+ BIT(POWER_SUPPLY_USB_TYPE_SDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_CDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_ACA),
+ .properties = asus_ec_charger_properties,
+ .num_properties = ARRAY_SIZE(asus_ec_charger_properties),
+ .get_property = asus_ec_charger_get_property,
+ .set_property = asus_ec_charger_set_property,
+ .property_is_writeable = asus_ec_charger_property_is_writeable,
+ .no_thermal = true,
+};
+
+static int asus_ec_charger_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct asus_ec_charger_data *priv =
+ container_of(nb, struct asus_ec_charger_data, nb);
+
+ switch (action) {
+ case ASUSEC_SMI_ACTION(POWER_NOTIFY):
+ case ASUSEC_SMI_ACTION(ADAPTER_EVENT):
+ power_supply_changed(priv->psy);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int asus_ec_charger_probe(struct platform_device *pdev)
+{
+ struct asusec_core *ec = dev_get_drvdata(pdev->dev.parent);
+ struct asus_ec_charger_data *priv;
+ struct device *dev = &pdev->dev;
+ struct power_supply_config cfg = { };
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->ec = ec;
+
+ cfg.fwnode = dev_fwnode(dev->parent);
+ cfg.drv_data = priv;
+
+ memcpy(&priv->psy_desc, &asus_ec_charger_desc, sizeof(priv->psy_desc));
+ priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-charger",
+ priv->ec->name);
+ if (!priv->psy_desc.name)
+ return -ENOMEM;
+
+ priv->psy = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
+ if (IS_ERR(priv->psy))
+ return dev_err_probe(dev, PTR_ERR(priv->psy),
+ "Failed to register power supply\n");
+
+ priv->nb.notifier_call = asus_ec_charger_notify;
+
+ return blocking_notifier_chain_register(&ec->notify_list, &priv->nb);
+}
+
+static void asus_ec_charger_remove(struct platform_device *pdev)
+{
+ struct asus_ec_charger_data *priv = platform_get_drvdata(pdev);
+ struct asusec_core *ec = priv->ec;
+
+ blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb);
+}
+
+static struct platform_driver asus_ec_charger_driver = {
+ .driver.name = "asus-transformer-ec-charger",
+ .probe = asus_ec_charger_probe,
+ .remove = asus_ec_charger_remove,
+};
+module_platform_driver(asus_ec_charger_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer Pad battery charger driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v7 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC
2026-05-18 9:59 ` [PATCH v7 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC Svyatoslav Ryhel
@ 2026-05-18 10:09 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-05-18 10:09 UTC (permalink / raw)
To: Svyatoslav Ryhel; +Cc: dmitry.torokhov, linux-input, robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `reg` property specifies `minItems: 2` but is missing an explicit `maxItems: 2` constraint or an `items` array definition.
--
commit 1a4f9a19859ef3d1429d7fbcebe0842f0c3684af
Author: Svyatoslav Ryhel <clamor95@gmail.com>
dt-bindings: embedded-controller: document ASUS Transformer EC
This commit adds device tree binding documentation for the embedded
controller used in the ASUS Transformer device series. The controllers
are attached to an I2C bus and run custom ASUS firmware.
> diff --git a/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
> new file mode 100644
> index 0000000000000..60b6375864aaf
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
[ ... ]
> + 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
Does this property need an explicit maxItems: 2 constraint or an items array
definition to properly bound the schema?
> +
> + reg-names:
> + items:
> + - const: ec
> + - const: dockram
While the reg-names property correctly defines exactly two expected names,
without an explicit upper bound or items array, the reg property definition
might be incomplete according to standard dt-binding conventions.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v7 2/7] mfd: Add driver for ASUS Transformer embedded controller
2026-05-18 9:59 ` [PATCH v7 2/7] mfd: Add driver for ASUS Transformer embedded controller Svyatoslav Ryhel
@ 2026-05-18 10:31 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-05-18 10:31 UTC (permalink / raw)
To: Svyatoslav Ryhel; +Cc: dmitry.torokhov, robh, devicetree, linux-input, conor+dt
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] Unsynchronized reallocation of `ddata->ec.model` causes Use-After-Free during interrupts.
- [High] Stale and uninitialized data is accessed on short I2C reads.
- [Medium] `asus_dockram_access_ctl()` silently ignores I2C write errors.
- [Medium] Memory leak of `ddata->ec.model` upon driver unbind or probe failure.
- [Medium] The original IRQ event payload is corrupted before being passed to child notifiers.
- [Medium] Subsystem Guideline Violation: Mixing `goto` error handling with `__free()` cleanup.
- [Medium] Missing error check for `kstrndup()` allocation failure in `asus_ec_log_info()`.
- [Low] Incorrect length check causes string truncation in `asus_ec_log_info()`.
--
commit d95a94a77b9b37ec021eabccb7ff81e4dd52730e
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>
mfd: Add driver for ASUS Transformer embedded controller
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.
> diff --git a/drivers/mfd/asus-transformer-ec.c b/drivers/mfd/asus-transformer-ec.c
> new file mode 100644
> index 0000000000000..18bb3e5f384d9
> --- /dev/null
> +++ b/drivers/mfd/asus-transformer-ec.c
[ ... ]
> + if (mask || xor) {
> + put_unaligned_le64((val & ~mask) ^ xor, buf + 1);
> + ret = i2c_smbus_write_i2c_block_data(client,
> + ASUSEC_DOCKRAM_CONTROL,
> + DOCKRAM_ENTRY_SIZE, buf);
> + }
> +
> + return 0;
> +}
Does this accidentally hide hardware write failures? Since ret is ignored,
callers might falsely assume the I2C block write succeeded.
[ ... ]
> +static void asus_ec_clear_buffer(struct asus_ec_data *ddata)
> +{
> + int ret, retry = ASUSEC_RSP_BUFFER_SIZE;
> +
> + /*
> + * Read the buffer till we get valid data by checking ASUSEC_OBF_MASK
> + * of the status byte or till we reach end of the 256 byte buffer.
> + */
> + while (retry--) {
> + ret = i2c_smbus_read_i2c_block_data(ddata->client, ASUSEC_READ_BUF,
> + sizeof(ddata->ec_buf),
> + ddata->ec_buf);
> + if (ret < 0)
> + continue;
> +
> + if (ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK)
> + continue;
Can a short read leave stale data in ddata->ec_buf? The function
i2c_smbus_read_i2c_block_data() returns the actual number of bytes read. If it
returns a positive value less than ASUSEC_IRQ_STATUS + 1, the buffer will
retain data from previous iterations.
[ ... ]
> +static int asus_ec_log_info(struct asus_ec_data *ddata, unsigned int reg,
> + const char *name, char **out)
> +{
> + struct device *dev = &ddata->client->dev;
> + u8 buf[DOCKRAM_ENTRY_BUFSIZE];
> + int ret;
> +
> + memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
> + ret = i2c_smbus_read_i2c_block_data(ddata->ec.dockram, reg,
> + DOCKRAM_ENTRY_SIZE, 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;
> + }
Is there a risk of an out-of-bounds read here? The I2C read fills up to 32
bytes (DOCKRAM_ENTRY_SIZE), meaning buf[0] to buf[31] are valid. If buf[0]
equals 32, the kstrndup() below will attempt to copy 32 bytes starting from
buf + 1, reading up to buf[32].
> +
> + if (!ddata->logging_disabled)
> + dev_info(dev, "%-14s: %.*s\n", name, buf[0], buf + 1);
> +
> + if (out)
> + *out = kstrndup(buf + 1, buf[0], GFP_KERNEL);
> +
> + return 0;
> +}
If kstrndup() fails and returns NULL, asus_ec_detect() will unknowingly assign
NULL to ddata->ec.model. Should this return -ENOMEM on allocation failure?
[ ... ]
> +static int asus_ec_detect(struct asus_ec_data *ddata)
> +{
> + char *model __free(kfree) = NULL;
> + int ret;
> +
> + ret = asus_ec_reset(ddata);
> + if (ret)
> + goto err_exit;
This isn't a bug, but mixing goto error handling with __free() cleanup in the
same function violates the subsystem guidelines. Functions are expected to use
either scope-based cleanup or goto-based cleanup exclusively to prevent
confusing ownership semantics.
[ ... ]
> + /* Check and inform about EC firmware behavior */
> + ret = asus_ec_susb_on_status(ddata);
> + if (ret)
> + goto err_exit;
> +
> + kfree(ddata->ec.model);
> +
> + ddata->ec.model = no_free_ptr(model);
Could this cause a use-after-free or a memory leak?
Since asus_ec_detect() can be called from the threaded interrupt handler at
runtime, ddata->ec.model might be freed and replaced without synchronization
while child drivers or sysfs attributes are concurrently accessing it.
Additionally, ddata->ec.model is never freed on driver unbind or probe
failure, causing a memory leak.
[ ... ]
> +static irqreturn_t asus_ec_interrupt(int irq, void *dev_id)
> +{
> + struct asus_ec_data *ddata = dev_id;
> + unsigned long notify_action;
> + int ret;
> +
> + ret = i2c_smbus_read_i2c_block_data(ddata->client, ASUSEC_READ_BUF,
> + sizeof(ddata->ec_buf), ddata->ec_buf);
> + if (ret <= 0 || !(ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK))
> + return IRQ_NONE;
Similar to asus_ec_clear_buffer(), if ret is a positive value smaller than
the required length, this could process stale data from previous interrupt
events.
> +
> + notify_action = ddata->ec_buf[ASUSEC_IRQ_STATUS];
> + if (notify_action & ASUSEC_SMI_MASK) {
> + unsigned int code = ddata->ec_buf[ASUSEC_SMI_CODE];
> +
> + asus_ec_handle_smi(ddata, code);
> +
> + notify_action |= code << 8;
> + }
> +
> + blocking_notifier_call_chain(&ddata->ec.notify_list,
> + notify_action, ddata->ec_buf);
> +
> + return IRQ_HANDLED;
> +}
Does asus_ec_handle_smi() corrupt the event payload before it is passed to
child notifiers?
If asus_ec_handle_smi() triggers a reset, it eventually calls
asus_ec_clear_buffer(), which performs new I2C reads directly into
ddata->ec_buf. This overwrites the original event data, meaning
blocking_notifier_call_chain() will pass a clobbered buffer to child
devices.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v7 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
2026-05-18 9:59 ` [PATCH v7 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad Svyatoslav Ryhel
@ 2026-05-18 11:17 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-05-18 11:17 UTC (permalink / raw)
To: Svyatoslav Ryhel; +Cc: linux-input, robh, devicetree, dmitry.torokhov, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Sleeping in atomic context due to synchronous I2C transfer in serio->write callback.
- [High] Off-by-one error in packet length calculation causes a buffer over-read and breaks keycode remapping.
- [Medium] In-place modification of a shared notifier data buffer.
--
commit 3c9b2bf0acd20a594f17d65b0d6fd9762329b3bc
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>
input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
This commit adds a serio input driver for the ASUS Transformer dock keyboard
and touchpad. It registers serio ports that act as a bridge over I2C and
remaps specific keycodes according to their position.
> 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 0000000000000..c9707c7fbede5
> --- /dev/null
> +++ b/drivers/input/serio/asus-transformer-ec-kbc.c
[ ... ]
> +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;
> +
> + /*
> + * The data[0] is the length of the packet including itself. It has to
> + * be at least 2 bytes (length + 1 data byte) and must not exceed
> + * the EC buffer size.
> + */
> + if (data[0] < 2 || data[0] > DOCKRAM_ENTRY_BUFSIZE)
> + return NOTIFY_BAD;
> +
> + n = data[0] - 1;
> + data += 2;
Is the payload length calculation off by one?
The comment states data[0] is the length of the packet including itself.
The code then skips two bytes (data += 2), which suggests the payload
starts at offset 2. If the payload length is data[0] - 2, will setting
n = data[0] - 1 cause the loop at the end of the function to read one byte
past the actual payload and inject garbage data into the serio port?
If an external device sends a packet with data[0] == DOCKRAM_ENTRY_BUFSIZE,
could this off-by-one error cause an out-of-bounds read past the end of
the EC buffer array?
Additionally, will this artificially inflated n cause the exact-length
validation in the key remapping logic (if (n == 2 ...) and if (n == 3 ...))
to always fail for valid payload sequences, breaking the layout translation?
> +
> + if (port_idx == 0) {
> + /*
> + * Remap keyboard key codes to match AT layout:
> + * SEARCH: RIGHT-META [E0 27] -> LEFT-ALT [11]
> + * MENU: COMPOSE [E0 2F] -> RIGHT-META [E0 27]
> + */
> + if ((n == 2 || (n == 3 && data[1] == 0xF0)) && data[0] == 0xE0) {
> + u8 *keycode = &data[n - 1];
> +
> + switch (*keycode) {
> + case 0x27:
> + *keycode = 0x11;
Is it safe to modify the data_ buffer in place?
Since data_ is passed to this function via the notifier chain, it points to a
shared event buffer that is broadcast to all registered listeners. Will
modifying this payload permanently corrupt the event data for any downstream
notifiers that might inspect the packet subsequently?
> + ++data;
> + --n;
> + break;
> + case 0x2F:
> + *keycode = 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)
> +{
> + struct asus_ec_kbc_data *priv = port->port_data;
> +
> + return i2c_smbus_write_word_data(priv->parent, ASUSEC_WRITE_BUF,
> + (data << 8) | port->id.extra);
Can this sleep while in an atomic context?
i2c_smbus_write_word_data() performs a synchronous I2C transaction which
acquires mutexes and can sleep. However, serio->write callbacks can be
invoked from atomic contexts.
For example, asus_ec_kbc_notify() calls serio_interrupt() which acquires a
spinlock and disables interrupts:
serio_interrupt()
spin_lock_irqsave(&serio->lock, flags);
serio->drv->interrupt()
serio_write()
asus_ec_serio_write()
i2c_smbus_write_word_data()
Will this trigger a scheduling-while-atomic panic if the input driver issues
a command back to the port while the spinlock is held?
> +}
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v7 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys
2026-05-18 9:59 ` [PATCH v7 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys Svyatoslav Ryhel
@ 2026-05-18 11:46 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-05-18 11:46 UTC (permalink / raw)
To: Svyatoslav Ryhel; +Cc: conor+dt, linux-input, devicetree, dmitry.torokhov, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Registering a statically allocated `struct input_handler` in the per-device probe function violates the driver model and causes list corruption.
- [High] The input handler permanently opens all `EV_KEY` input devices in the system without declaring itself as a passive observer, preventing runtime suspend.
--
commit 3ac5537da45198a85ba82452de2c64484baeac36
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>
input: keyboard: Add driver for ASUS Transformer dock multimedia keys
This adds support for the multimedia top button row of the ASUS Transformer
Mobile Dock keyboard. It defaults to function keys (F1-F12) and uses an input
handler to monitor system-wide key events to switch to multimedia keys when
ScreenLock + AltGr are pressed.
> 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 0000000000000..d30d991773744
> --- /dev/null
> +++ b/drivers/input/keyboard/asus-transformer-ec-keys.c
[ ... ]
> +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;
[ ... ]
> + error = input_open_device(handle);
> + if (error)
> + goto err_unregister_handle;
> +
> + return 0;
[ ... ]
> +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,
> +};
Because this handler connects to all EV_KEY devices and calls
input_open_device() without setting .passive_observer = true, will this
increment the dev->users count for every matching input device in the system?
Could this force all EV_KEY input devices to remain open and active
permanently, preventing them from entering runtime suspend?
[ ... ]
> +static int asus_ec_keys_probe(struct platform_device *pdev)
> +{
[ ... ]
> + asus_ec_input_handler.private = priv;
> + error = input_register_handler(&asus_ec_input_handler);
> + if (error)
> + return error;
Since asus_ec_input_handler is a static global structure, what happens if this
driver is probed multiple times (for instance, via sysfs bind/unbind or
multiple ECs)?
If input_register_handler() is called a second time, it will re-initialize the
list head and add an already linked node. Could this corrupt the
input_handler_list?
Should the input_handler be dynamically allocated per-device or embedded within
struct asus_ec_keys_data?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=4
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v7 5/7] leds: Add driver for ASUS Transformer LEDs
2026-05-18 9:59 ` [PATCH v7 5/7] leds: Add driver for ASUS Transformer LEDs Svyatoslav Ryhel
@ 2026-05-18 12:02 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-05-18 12:02 UTC (permalink / raw)
To: Svyatoslav Ryhel; +Cc: dmitry.torokhov, robh, conor+dt, linux-input, devicetree
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Incorrect use of `LED_CORE_SUSPENDRESUME` for charging indicators.
- [Medium] Lack of hardware state synchronization (`brightness_get`) at initialization.
- [Medium] Missing `MODULE_ALIAS` prevents automatic module loading.
- [Low] The driver exposes a physical bi-color LED as two independent single-color LED devices instead of utilizing the LED Multicolor class.
--
commit f02de4369b03c2d6785dea5fb4edfab53036130d
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>
leds: Add driver for ASUS Transformer LEDs
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.
> 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.
Since this describes a physically multi-color LED, shouldn't the driver use
the LED Multicolor class API (CONFIG_LEDS_CLASS_MULTICOLOR) instead of exposing
them as two separate single-color LED devices?
> diff --git a/drivers/leds/leds-asus-transformer-ec.c b/drivers/leds/leds-asus-transformer-ec.c
> new file mode 100644
> index 0000000000000..c9bdc46c02e15
> --- /dev/null
> +++ b/drivers/leds/leds-asus-transformer-ec.c
[ ... ]
> + for (i = 0; i < ASUSEC_LED_MAX; i++) {
> + const struct asus_ec_led_config *cfg = &asus_ec_leds[i];
> + struct asus_ec_led *led = &ddata->leds[i];
> +
> + led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s::%s",
> + ddata->ec->name, cfg->name);
> + if (!led->cdev.name)
> + return -ENOMEM;
> +
> + led->cdev.max_brightness = 1;
> + led->cdev.color = cfg->color;
> + led->cdev.flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
Does LED_CORE_SUSPENDRESUME make sense for a charging indicator?
This flag instructs the LED core to explicitly turn off the LED during suspend,
which turns off the charging indicator while the device is sleeping.
Also, could the Embedded Controller have already turned the LED on before the
driver probes?
Since there is no brightness_get callback to synchronize the initial hardware
state, the LED core will initialize the software brightness to 0. If the device
resumes from suspend, the LED core will restore this saved brightness of 0,
permanently turning off the LED even if it was physically on before.
[ ... ]
> +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");
Will this module load automatically when the platform device is registered?
Since there is no MODULE_ALIAS("platform:asus-transformer-ec-led") declaration,
udev might not be able to automatically load the module during boot when the
MFD core registers the corresponding platform device.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=5
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v7 6/7] power: supply: Add driver for ASUS Transformer battery
2026-05-18 9:59 ` [PATCH v7 6/7] power: supply: Add driver for ASUS Transformer battery Svyatoslav Ryhel
@ 2026-05-18 12:37 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-05-18 12:37 UTC (permalink / raw)
To: Svyatoslav Ryhel; +Cc: dmitry.torokhov, linux-input, robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The driver lacks a MODULE_ALIAS declaration, which breaks module autoloading.
- [Low] Missing NULL check for the devm_kasprintf allocation of the power supply name.
--
commit ccc69cbdb16bd3e704318f7d07abbad8dc370844
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>
power: supply: Add driver for ASUS Transformer battery
This commit introduces a new power supply driver for the ASUS Transformer
battery. It communicates with the embedded controller via I2C to read battery
status for both the tablet and mobile dock.
> 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 0000000000000..d08d155f72a3d
> --- /dev/null
> +++ b/drivers/power/supply/asus-transformer-ec-battery.c
[ ... ]
> +static int asus_ec_battery_probe(struct platform_device *pdev)
> +{
[ ... ]
> + 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");
Is it possible for devm_kasprintf() to fail and return NULL under memory
pressure?
If so, priv->psy_desc.name would become NULL, and devm_power_supply_register()
would return ERR_PTR(-EINVAL) because it checks for !desc->name. This relies
on downstream validation and results in an incorrect error code being
propagated for a memory allocation failure instead of -ENOMEM.
Should there be an explicit NULL check here?
[ ... ]
> +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");
Since this platform driver matches devices purely by name
("asus-transformer-ec-battery") and lacks an id_table or of_match_table,
will userspace tools like udev be able to automatically load this driver
when it is compiled as a module?
Should a MODULE_ALIAS("platform:asus-transformer-ec-battery"); declaration
be added to ensure module autoloading works correctly?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260518095907.36158-1-clamor95@gmail.com?part=6
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-05-18 12:37 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-18 9:59 [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller Svyatoslav Ryhel
2026-05-18 9:59 ` [PATCH v7 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC Svyatoslav Ryhel
2026-05-18 10:09 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 2/7] mfd: Add driver for ASUS Transformer embedded controller Svyatoslav Ryhel
2026-05-18 10:31 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad Svyatoslav Ryhel
2026-05-18 11:17 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys Svyatoslav Ryhel
2026-05-18 11:46 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 5/7] leds: Add driver for ASUS Transformer LEDs Svyatoslav Ryhel
2026-05-18 12:02 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 6/7] power: supply: Add driver for ASUS Transformer battery Svyatoslav Ryhel
2026-05-18 12:37 ` sashiko-bot
2026-05-18 9:59 ` [PATCH v7 7/7] power: supply: Add charger driver for Asus Transformers Svyatoslav Ryhel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox