* [PATCH v10 0/7] mfd: Add support for Asus Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-07-21 9:52 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
Changes in v8:
- added MODULE_ALIAS
- renamed DOCKRAM_* to ASUSEC_*
- ec-keys: input_handler moved into private structure
- ec-leds: added brightness_get
- ec-mdf: fixed i2c_smbus_* return checks ()
improved model storing
- ec-batt: added status check of devm_kasprintf
Changes in v9:
- fixed i2c_smbus_read_i2c_block_data return check
- blocking_notifier_chain_register moved before serio registration
- adjusted get_unaligned_le16 bounds check
- unsigned long long > u64
- iterator vars made scoped
- removed "magic" values from ec-mfd
- simplified logging, detect split into detect and init
- improved error logs formatting
- adjusted handler in media keys to connect strictly to dock keyboard
Changes in v10:
- ec-kbc: fixed possible out of buffer reads
- ec-kbc: set notifier_call before blocking_notifier_chain_register
- ec-keys: dropped input_unregister_device which should be handled by devm_input_allocate_device
---
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 | 310 ++++++++++
drivers/input/serio/Kconfig | 15 +
drivers/input/serio/Makefile | 1 +
drivers/input/serio/asus-transformer-ec-kbc.c | 168 ++++++
drivers/leds/Kconfig | 11 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-asus-transformer-ec.c | 125 ++++
drivers/mfd/Kconfig | 16 +
drivers/mfd/Makefile | 1 +
drivers/mfd/asus-transformer-ec.c | 549 ++++++++++++++++++
drivers/power/supply/Kconfig | 22 +
drivers/power/supply/Makefile | 2 +
.../supply/asus-transformer-ec-battery.c | 289 +++++++++
.../supply/asus-transformer-ec-charger.c | 208 +++++++
include/linux/mfd/asus-transformer-ec.h | 92 +++
18 files changed, 1940 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.53.0
^ permalink raw reply
* [PATCH v10 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC
From: Svyatoslav Ryhel @ 2026-07-21 9:52 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com>
Document embedded controller used in ASUS Transformer device series.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../asus,tf201-ec-pad.yaml | 119 ++++++++++++++++++
1 file changed, 119 insertions(+)
create mode 100644 Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
diff --git a/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
new file mode 100644
index 0000000000000..60b6375864aaf
--- /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.53.0
^ permalink raw reply related
* [PATCH v10 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-07-21 9:52 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Support Nuvoton NPCE795-based ECs as used in Asus Transformer TF201,
TF300T, TF300TG, TF300TL and TF700T pad and dock, as well as TF101 dock
and TF600T, P1801-T and TF701T pad. This is a glue driver handling
detection and common operations for EC's functions.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
---
drivers/mfd/Kconfig | 16 +
drivers/mfd/Makefile | 1 +
drivers/mfd/asus-transformer-ec.c | 549 ++++++++++++++++++++++++
include/linux/mfd/asus-transformer-ec.h | 92 ++++
4 files changed, 658 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 35f6e9b76d056..7e67d4e4249ff 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 dd4bb7e77c336..79806201bbb24 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 0000000000000..739c66fdaf22f
--- /dev/null
+++ b/drivers/mfd/asus-transformer-ec.c
@@ -0,0 +1,549 @@
+// 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_ACCESS_TIMEOUT 300
+#define ASUSEC_DOCKRAM_OFFSET 2
+#define ASUSEC_ECREQ_DELAY 50
+#define ASUSEC_ECREQ_TIMEOUT 200
+#define ASUSEC_RESET 0
+#define ASUSEC_RETRY_MAX 3
+#define ASUSEC_RSP_BUFFER_SIZE (ASUSEC_ENTRIES / ASUSEC_ENTRY_SIZE)
+
+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[ASUSEC_ENTRY_BUFSIZE];
+};
+
+/**
+ * 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[ASUSEC_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, ASUSEC_ENTRY_BUFSIZE);
+ ret = i2c_smbus_read_i2c_block_data(client, ASUSEC_DOCKRAM_CONTROL,
+ ASUSEC_ENTRY_SIZE, buf);
+ if (ret < ASUSEC_ENTRY_SIZE) {
+ dev_err(&client->dev, "failed to access control buffer: %d\n",
+ ret);
+ return ret < 0 ? ret : -EIO;
+ }
+
+ 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,
+ ASUSEC_ENTRY_SIZE, buf);
+ if (ret)
+ return ret;
+ }
+
+ 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(ASUSEC_ECREQ_DELAY);
+
+ gpiod_set_value_cansleep(ddata->ecreq_gpio, 0);
+ msleep(ASUSEC_ECREQ_TIMEOUT);
+
+ return 0;
+}
+
+static int asus_ec_log_info(struct asus_ec_data *ddata, unsigned int reg,
+ const char *name)
+{
+ struct device *dev = &ddata->client->dev;
+ u8 buf[ASUSEC_ENTRY_BUFSIZE];
+ int ret;
+
+ memset(buf, 0, ASUSEC_ENTRY_BUFSIZE);
+ ret = i2c_smbus_read_i2c_block_data(ddata->ec.dockram, reg,
+ ASUSEC_ENTRY_SIZE, buf);
+ if (ret < ASUSEC_ENTRY_SIZE)
+ return ret < 0 ? ret : -EIO;
+
+ if (buf[0] > ASUSEC_ENTRY_SIZE) {
+ dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
+ ASUSEC_ENTRY_BUFSIZE, buf, ret);
+ return -EPROTO;
+ }
+
+ dev_info(dev, "%-14s: %.*s\n", name, buf[0], buf + 1);
+
+ if (!ddata->ec.model) {
+ ddata->ec.model = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
+ buf[0], buf + 1);
+ if (!ddata->ec.model)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int asus_ec_detect(struct asus_ec_data *ddata)
+{
+ int ret;
+
+ ret = asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_MODEL, "Model");
+ if (ret)
+ return ret;
+
+ ret = asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_FW, "FW version");
+ if (ret)
+ return ret;
+
+ ret = asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_CFGFMT, "Config format");
+ if (ret)
+ return ret;
+
+ ret = asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_HW, "HW version");
+ if (ret)
+ return ret;
+
+ ddata->ec.name = ddata->info->name;
+
+ 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 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,
+ ASUSEC_ENTRY_SIZE,
+ ddata->ec_buf);
+ if (ret < ASUSEC_ENTRY_SIZE)
+ continue;
+
+ if (ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK)
+ continue;
+
+ break;
+ }
+}
+
+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_init(struct asus_ec_data *ddata)
+{
+ int ret;
+
+ ret = asus_ec_reset(ddata);
+ if (ret)
+ goto err_exit;
+
+ asus_ec_clear_buffer(ddata);
+
+ /* Check and inform about EC firmware behavior */
+ ret = asus_ec_susb_on_status(ddata);
+ if (ret)
+ goto err_exit;
+
+ /* 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_init(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,
+ ASUSEC_ENTRY_SIZE, ddata->ec_buf);
+ if (ret < ASUSEC_ENTRY_SIZE)
+ return IRQ_NONE;
+
+ /* Check status byte with ASUSEC_OBF_MASK if data is valid */
+ ret = ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK;
+ if (!ret)
+ 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 + ASUSEC_DOCKRAM_OFFSET);
+ 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-kbc"),
+ MFD_CELL_NAME("asus-transformer-ec-keys"),
+};
+
+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");
+
+ ret = asus_ec_init(ddata);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to init EC\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 0000000000000..1c25c3a18355c
--- /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>
+
+#define ASUSEC_ENTRIES 0x100
+#define ASUSEC_ENTRY_SIZE 32
+#define ASUSEC_ENTRY_BUFSIZE (ASUSEC_ENTRY_SIZE + 1)
+
+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;
+};
+
+/* 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.53.0
^ permalink raw reply related
* [PATCH v10 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
From: Svyatoslav Ryhel @ 2026-07-21 9:52 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Add input driver for ASUS Transformer dock keyboard and touchpad.
Some keys in ASUS Dock report keycodes that don't make sense according to
their position, this patch modifies the incoming data that is sent to
serio to send proper scancodes.
Co-developed-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/input/serio/Kconfig | 15 ++
drivers/input/serio/Makefile | 1 +
drivers/input/serio/asus-transformer-ec-kbc.c | 168 ++++++++++++++++++
3 files changed, 184 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 bacab1f584009..efb5aa59c364b 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 7f5137dc02e26..bed2a7a92a13d 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 0000000000000..c6d12c44449d0
--- /dev/null
+++ b/drivers/input/serio/asus-transformer-ec-kbc.c
@@ -0,0 +1,168 @@
+// 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. The data[]
+ * buffer has to be at least 3 bytes (length + ctrl + 1 data byte) and
+ * must not exceed the EC entry size.
+ */
+ if (data[0] < 2 || data[0] > ASUSEC_ENTRY_SIZE)
+ return NOTIFY_BAD;
+
+ n = data[0] - 2;
+ 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 void asus_ec_notifier_chain_unregister(void *data)
+{
+ struct asus_ec_kbc_data *priv = data;
+ struct asusec_core *ec = priv->ec;
+
+ blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb);
+}
+
+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);
+
+ priv->nb.notifier_call = asus_ec_kbc_notify;
+
+ error = blocking_notifier_chain_register(&ec->notify_list, &priv->nb);
+ if (error)
+ return dev_err_probe(&pdev->dev, error,
+ "failed to register blocking notifier chain");
+
+ error = devm_add_action_or_reset(&pdev->dev,
+ asus_ec_notifier_chain_unregister,
+ priv);
+ if (error)
+ return error;
+
+ 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;
+
+ return 0;
+}
+
+static struct platform_driver asus_ec_kbc_driver = {
+ .driver.name = "asus-transformer-ec-kbc",
+ .probe = asus_ec_kbc_probe,
+};
+module_platform_driver(asus_ec_kbc_driver);
+
+MODULE_ALIAS("platform:asus-transformer-ec-kbc");
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer's Dock keyboard and touchpad driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH v10 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys
From: Svyatoslav Ryhel @ 2026-07-21 9:52 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Add support for multimedia top button row of ASUS Transformer's Mobile
Dock keyboard. Driver is made that function keys (F1-F12) are used by
default which suits average Linux use better and with pressing
ScreenLock + AltGr function keys layout is switched to multimedia keys.
Only Dock keyboard input events are tracked for AltGr pressing.
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 | 310 ++++++++++++++++++
3 files changed, 321 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 9d1019ba0245b..913cb4900565c 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 60bb7baf802f7..0d81096887ada 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 0000000000000..44235e4a7f548
--- /dev/null
+++ b/drivers/input/keyboard/asus-transformer-ec-keys.c
@@ -0,0 +1,310 @@
+// 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;
+ struct input_handler input_handler;
+ unsigned short keymap[ASUSEC_EXT_KEY_CODES * 2];
+ const char *kbc_phys;
+ bool special_key_pressed;
+ bool special_key_mode;
+};
+
+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 asus_ec_keys_data *priv = handler->private;
+ struct input_handle *handle;
+ int error;
+
+ if (!dev->phys || !strstr(dev->phys, priv->kbc_phys))
+ return -ENODEV;
+
+ handle = kzalloc_obj(*handle);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = handler->name;
+
+ 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 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_register_handler(struct device *dev,
+ struct asus_ec_keys_data *priv)
+{
+ struct i2c_client *parent = to_i2c_client(dev->parent);
+ int error;
+
+ priv->input_handler.event = asus_ec_input_event;
+ priv->input_handler.connect = asus_ec_input_connect;
+ priv->input_handler.disconnect = asus_ec_input_disconnect;
+ priv->input_handler.id_table = asus_ec_input_ids;
+ priv->input_handler.passive_observer = true;
+ priv->input_handler.private = priv;
+ priv->input_handler.name = devm_kasprintf(dev, GFP_KERNEL,
+ "%s-media-handler",
+ priv->ec->name);
+ if (!priv->input_handler.name)
+ return -ENOMEM;
+
+ priv->kbc_phys = devm_kasprintf(dev, GFP_KERNEL, "i2c-%u-%04x/serio0",
+ i2c_adapter_id(parent->adapter),
+ parent->addr);
+ if (!priv->kbc_phys)
+ return -ENOMEM;
+
+ error = input_register_handler(&priv->input_handler);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+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");
+
+ error = asus_ec_keys_register_handler(dev, priv);
+ 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(&priv->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;
+
+ blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb);
+ input_unregister_handler(&priv->input_handler);
+}
+
+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_ALIAS("platform:asus-transformer-ec-keys");
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer's multimedia keys driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH v10 5/7] leds: Add driver for ASUS Transformer LEDs
From: Svyatoslav Ryhel @ 2026-07-21 9:52 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
ASUS Transformer tablets have a green and an amber LED on both the Pad
and the Dock. If both LEDs are enabled simultaneously, the emitted light
will be yellow.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
---
drivers/leds/Kconfig | 11 +++
drivers/leds/Makefile | 1 +
drivers/leds/leds-asus-transformer-ec.c | 125 ++++++++++++++++++++++++
3 files changed, 137 insertions(+)
create mode 100644 drivers/leds/leds-asus-transformer-ec.c
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index f4a0a3c8c8705..f637d23400a89 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 7db3768912ca5..56304ab90eb31 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
obj-$(CONFIG_LEDS_APU) += leds-apu.o
obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
+obj-$(CONFIG_LEDS_ASUS_TRANSFORMER_EC) += leds-asus-transformer-ec.o
obj-$(CONFIG_LEDS_OSRAM_AMS_AS3668) += leds-as3668.o
obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
diff --git a/drivers/leds/leds-asus-transformer-ec.c b/drivers/leds/leds-asus-transformer-ec.c
new file mode 100644
index 0000000000000..4421d629911ed
--- /dev/null
+++ b/drivers/leds/leds-asus-transformer-ec.c
@@ -0,0 +1,125 @@
+// 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;
+ u64 ctrl_bit;
+};
+
+struct asus_ec_led {
+ struct asus_ec_leds_data *ddata;
+ struct led_classdev cdev;
+ u64 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 enum led_brightness asus_ec_led_get_brightness(struct led_classdev *cdev)
+{
+ struct asus_ec_led *led = container_of(cdev, struct asus_ec_led, cdev);
+ const struct asusec_core *ec = led->ddata->ec;
+ u64 ctl;
+ int ret;
+
+ ret = asus_dockram_access_ctl(ec->dockram, &ctl, 0, 0);
+ if (ret)
+ return LED_OFF;
+
+ return ctl & led->ctrl_bit ? LED_ON : LED_OFF;
+}
+
+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 ret;
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ddata);
+ ddata->ec = ec;
+
+ for (int 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_get = asus_ec_led_get_brightness;
+ 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_ALIAS("platform:asus-transformer-ec-led");
+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.53.0
^ permalink raw reply related
* [PATCH v10 6/7] power: supply: Add driver for ASUS Transformer battery
From: Svyatoslav Ryhel @ 2026-07-21 9:52 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Driver implements one battery cell per EC controller and supports reading
of battery status for ASUS Transformer's pad and mobile dock.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
.../supply/asus-transformer-ec-battery.c | 289 ++++++++++++++++++
3 files changed, 301 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 f0ede1cecb6a8..24996ee6d90dd 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 31fe4a145929a..05c306817b449 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 0000000000000..4c0c6d4b09e2c
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-battery.c
@@ -0,0 +1,289 @@
+// 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[ASUSEC_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, ASUSEC_ENTRY_BUFSIZE);
+ ret = i2c_smbus_read_i2c_block_data(client, ASUSEC_DOCKRAM_BATT_CTL,
+ ASUSEC_ENTRY_SIZE, priv->batt_data);
+ if (ret < ASUSEC_ENTRY_SIZE)
+ return ret < 0 ? ret : -EIO;
+
+ if (priv->batt_data[0] > ASUSEC_ENTRY_SIZE) {
+ dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
+ ASUSEC_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);
+ if (!priv->psy_desc.name)
+ return -ENOMEM;
+
+ 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_ALIAS("platform:asus-transformer-ec-battery");
+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.53.0
^ permalink raw reply related
* [PATCH v10 7/7] power: supply: Add charger driver for Asus Transformers
From: Svyatoslav Ryhel @ 2026-07-21 9:52 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Lee Jones, Pavel Machek, Sebastian Reichel, Svyatoslav Ryhel,
Ion Agorria, Michał Mirosław
Cc: devicetree, linux-kernel, linux-input, linux-leds, linux-pm
In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Add support for charger detection capabilities found in the embedded
controller of ASUS Transformer devices.
Suggested-by: Maxim Schwalm <maxim.schwalm@gmail.com>
Suggested-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
---
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
.../supply/asus-transformer-ec-charger.c | 208 ++++++++++++++++++
3 files changed, 220 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 24996ee6d90dd..3011591c9d755 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 05c306817b449..9d51fdb76eee8 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 0000000000000..c7a6bd2ba533f
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-charger.c
@@ -0,0 +1,208 @@
+// 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_ALIAS("platform:asus-transformer-ec-charger");
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer Pad battery charger driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v3 1/9] regulator: core: Add "enable and wait" functions
From: Andy Shevchenko @ 2026-07-21 9:54 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Matthias Brugger, AngeloGioacchino Del Regno, Benson Leung,
Tzung-Bi Shih, Dmitry Torokhov, Jiri Kosina, Andi Shyti,
linux-mediatek, devicetree, linux-arm-kernel, chrome-platform,
linux-input, linux-i2c, linux-kernel
In-Reply-To: <20260721075226.2347933-2-wenst@chromium.org>
On Tue, Jul 21, 2026 at 03:52:15PM +0800, Chen-Yu Tsai wrote:
> In device power sequencing and initialization use cases, it is common
> for the driver to enable the regulator and then wait for a certain
> period of time to pass before continuing.
>
> In cases where the regulator supply is always on, or has been turned on
> or left on by another consumer, the driver could shorten the delay or
> skip it altogether, provided that enough time has already passed since
> the regulator was _actually_ turned on.
>
> Tracking this requires support from the regulator core. Introduce a
> "last turned on" timestamp field to the regulator device, and "enable
> and wait" functions to the single and bulk regulator consumer APIs.
> The existing "enable without wait" functions are then converted to
> macros that expand to the new functions.
>
> One case in particular is not optimized yet: a regulator left on either
> by hardware reset default or by the bootloader, but does not have the
> "regulator-boot-on" property set. As the enable timestamp only gets
> updated when enabled by a consumer or by the core, the first enablement
> always needs to wait.
...
> -/* locks held by regulator_enable() */
> -static int _regulator_enable(struct regulator *regulator)
> +/* locks held by regulator_enable_and_wait() */
> +static int _regulator_enable_and_wait(struct regulator *regulator, unsigned int wait_us)
Hmm...
This comment a bit confusing, perhaps adding some lockdep annotations help?
...
> + if (wait_us) {
> + ktime_t end = ktime_add_us(rdev->last_on, wait_us);
> + s64 remaining = ktime_us_delta(end, ktime_get_boottime());
> +
> + if (remaining > 0)
> + fsleep(remaining);
This style is discouraged as it makes maintenance harder. Better
s64 remaining;
remaining = ktime_us_delta(end, ktime_get_boottime());
if (remaining > 0)
fsleep(remaining);
> + }
...
> /* private: Internal use */
> int ret;
> + unsigned int wait_us;
If you want to make it more private (the above is only for kernel-doc) add
__private annotation that will affect how sparse will check this.
...
> -int __must_check regulator_enable(struct regulator *regulator);
> +int __must_check regulator_enable_and_wait(struct regulator *regulator, unsigned int ms);
ms or us? Please, double check all units.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [PATCH v7 0/6] mfd: cpcap: convert documentation to schema and add Mot board support
From: Svyatoslav Ryhel @ 2026-07-21 9:56 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Pavel Machek, Svyatoslav Ryhel
Cc: linux-input, devicetree, linux-kernel, linux-leds
The initial goal was only to add support for the CPCAP used in the Mot
Tegra20 board; however, since the documentation was already partially
converted, I decided to complete the conversion to schema too.
The CPCAP regulator, leds, rtc, pwrbutton and core files were converted
from TXT to YAML while preserving the original structure. Mot board
compatibility was added to the regulator and core schema. Since these
were one-line patches, they were not separated into dedicated commits;
however, the commit message notes this for both cases.
Finally, the CPCAP MFD was slightly refactored to improve support for
multiple subcell compositions.
---
Changes in v2:
- fixed code style
- rtc conversion was picked, so patch dropped
- added audio ports description into mfd schema
- splitted schema conversion and compatible addition
- minor style improvements and typo fixes
Changes in v3:
- added regulator node names list into pattern
- filled spi_device_id with driver data
- ADC patches were picked, so changes dropped
Changes in v4:
- dropped regulator patches (applied)
Changes in v5:
- switched to MFD_CELL_* macros
- switched to use determinator of model
- switched to spi_get_device_match_data
Changes in v6:
- removed address-cells and size-cells from main node
- changed macros formatting and OF matches
- factored out common devices and made device addition staged
- dropped cpcap->variant check for 0
- EINVAL > ENODEV in variant checking switch
Changes in v7:
- adjusted spi_device_id entries (name/driver_data)
---
Svyatoslav Ryhel (6):
dt-bindings: leds: leds-cpcap: convert to DT schema
dt-bindings: input: cpcap-pwrbutton: convert to DT schema
dt-bindings: mfd: motorola-cpcap: convert to DT schema
dt-bindings: mfd: motorola-cpcap: document Mapphone and Mot CPCAP
mfd: motorola-cpcap: diverge configuration per-board
mfd: motorola-cpcap: add support for Mot CPCAP composition
.../bindings/input/cpcap-pwrbutton.txt | 20 -
.../input/motorola,cpcap-pwrbutton.yaml | 32 ++
.../devicetree/bindings/leds/leds-cpcap.txt | 29 --
.../bindings/leds/motorola,cpcap-leds.yaml | 42 ++
.../bindings/mfd/motorola,cpcap.yaml | 408 ++++++++++++++++++
.../bindings/mfd/motorola-cpcap.txt | 78 ----
drivers/mfd/motorola-cpcap.c | 143 +++---
include/linux/mfd/motorola-cpcap.h | 7 +
8 files changed, 571 insertions(+), 188 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/input/cpcap-pwrbutton.txt
create mode 100644 Documentation/devicetree/bindings/input/motorola,cpcap-pwrbutton.yaml
delete mode 100644 Documentation/devicetree/bindings/leds/leds-cpcap.txt
create mode 100644 Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml
create mode 100644 Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
delete mode 100644 Documentation/devicetree/bindings/mfd/motorola-cpcap.txt
--
2.53.0
^ permalink raw reply
* [PATCH v7 1/6] dt-bindings: leds: leds-cpcap: convert to DT schema
From: Svyatoslav Ryhel @ 2026-07-21 9:56 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Pavel Machek, Svyatoslav Ryhel
Cc: linux-input, devicetree, linux-kernel, linux-leds
In-Reply-To: <20260721095654.429346-1-clamor95@gmail.com>
Convert LEDs devicetree bindings for the Motorola CPCAP MFD from TXT to
YAML format. This patch does not change any functionality; the bindings
remain the same.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../devicetree/bindings/leds/leds-cpcap.txt | 29 -------------
.../bindings/leds/motorola,cpcap-leds.yaml | 42 +++++++++++++++++++
2 files changed, 42 insertions(+), 29 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/leds/leds-cpcap.txt
create mode 100644 Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml
diff --git a/Documentation/devicetree/bindings/leds/leds-cpcap.txt b/Documentation/devicetree/bindings/leds/leds-cpcap.txt
deleted file mode 100644
index ebf7cdc7f70c2..0000000000000
--- a/Documentation/devicetree/bindings/leds/leds-cpcap.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-Motorola CPCAP PMIC LEDs
-------------------------
-
-This module is part of the CPCAP. For more details about the whole
-chip see Documentation/devicetree/bindings/mfd/motorola-cpcap.txt.
-
-Requires node properties:
-- compatible: should be one of
- * "motorola,cpcap-led-mdl" (Main Display Lighting)
- * "motorola,cpcap-led-kl" (Keyboard Lighting)
- * "motorola,cpcap-led-adl" (Aux Display Lighting)
- * "motorola,cpcap-led-red" (Red Triode)
- * "motorola,cpcap-led-green" (Green Triode)
- * "motorola,cpcap-led-blue" (Blue Triode)
- * "motorola,cpcap-led-cf" (Camera Flash)
- * "motorola,cpcap-led-bt" (Bluetooth)
- * "motorola,cpcap-led-cp" (Camera Privacy LED)
-- label: see Documentation/devicetree/bindings/leds/common.txt
-- vdd-supply: A phandle to the regulator powering the LED
-
-Example:
-
-&cpcap {
- cpcap_led_red: red-led {
- compatible = "motorola,cpcap-led-red";
- label = "cpcap:red";
- vdd-supply = <&sw5>;
- };
-};
diff --git a/Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml b/Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml
new file mode 100644
index 0000000000000..c8e7b88a05ccf
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/motorola,cpcap-leds.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Motorola CPCAP PMIC LEDs
+
+maintainers:
+ - Svyatoslav Ryhel <clamor95@gmail.com>
+
+description:
+ This module is part of the Motorola CPCAP MFD device. For more details
+ see Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml. LEDs are
+ represented as sub-nodes of the PMIC node on the device tree.
+
+allOf:
+ - $ref: /schemas/leds/common.yaml#
+
+properties:
+ compatible:
+ enum:
+ - motorola,cpcap-led-adl # Display Lighting
+ - motorola,cpcap-led-blue # Blue Triode
+ - motorola,cpcap-led-bt # Bluetooth
+ - motorola,cpcap-led-cf # Camera Flash
+ - motorola,cpcap-led-cp # Camera Privacy LED
+ - motorola,cpcap-led-green # Green Triode
+ - motorola,cpcap-led-kl # Keyboard Lighting
+ - motorola,cpcap-led-mdl # Main Display Lighting
+ - motorola,cpcap-led-red # Red Triode
+
+ vdd-supply: true
+
+required:
+ - compatible
+ - label
+ - vdd-supply
+
+unevaluatedProperties: false
+
+...
--
2.53.0
^ permalink raw reply related
* [PATCH v7 2/6] dt-bindings: input: cpcap-pwrbutton: convert to DT schema
From: Svyatoslav Ryhel @ 2026-07-21 9:56 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Pavel Machek, Svyatoslav Ryhel
Cc: linux-input, devicetree, linux-kernel, linux-leds
In-Reply-To: <20260721095654.429346-1-clamor95@gmail.com>
Convert power button devicetree bindings for the Motorola CPCAP MFD from
TXT to YAML format. This patch does not change any functionality; the
bindings remain the same.
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../bindings/input/cpcap-pwrbutton.txt | 20 ------------
.../input/motorola,cpcap-pwrbutton.yaml | 32 +++++++++++++++++++
2 files changed, 32 insertions(+), 20 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/input/cpcap-pwrbutton.txt
create mode 100644 Documentation/devicetree/bindings/input/motorola,cpcap-pwrbutton.yaml
diff --git a/Documentation/devicetree/bindings/input/cpcap-pwrbutton.txt b/Documentation/devicetree/bindings/input/cpcap-pwrbutton.txt
deleted file mode 100644
index 0dd0076daf71f..0000000000000
--- a/Documentation/devicetree/bindings/input/cpcap-pwrbutton.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-Motorola CPCAP on key
-
-This module is part of the CPCAP. For more details about the whole
-chip see Documentation/devicetree/bindings/mfd/motorola-cpcap.txt.
-
-This module provides a simple power button event via an Interrupt.
-
-Required properties:
-- compatible: should be one of the following
- - "motorola,cpcap-pwrbutton"
-- interrupts: irq specifier for CPCAP's ON IRQ
-
-Example:
-
-&cpcap {
- cpcap_pwrbutton: pwrbutton {
- compatible = "motorola,cpcap-pwrbutton";
- interrupts = <23 IRQ_TYPE_NONE>;
- };
-};
diff --git a/Documentation/devicetree/bindings/input/motorola,cpcap-pwrbutton.yaml b/Documentation/devicetree/bindings/input/motorola,cpcap-pwrbutton.yaml
new file mode 100644
index 0000000000000..77a3e5a47d1ae
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/motorola,cpcap-pwrbutton.yaml
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/motorola,cpcap-pwrbutton.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Motorola CPCAP PMIC power key
+
+maintainers:
+ - Svyatoslav Ryhel <clamor95@gmail.com>
+
+description:
+ This module is part of the Motorola CPCAP MFD device. For more details
+ see Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml. The
+ power key is represented as a sub-node of the PMIC node on the device
+ tree.
+
+properties:
+ compatible:
+ const: motorola,cpcap-pwrbutton
+
+ interrupts:
+ items:
+ - description: CPCAP's ON interrupt
+
+required:
+ - compatible
+ - interrupts
+
+additionalProperties: false
+
+...
--
2.53.0
^ permalink raw reply related
* [PATCH v7 3/6] dt-bindings: mfd: motorola-cpcap: convert to DT schema
From: Svyatoslav Ryhel @ 2026-07-21 9:56 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Pavel Machek, Svyatoslav Ryhel
Cc: linux-input, devicetree, linux-kernel, linux-leds
In-Reply-To: <20260721095654.429346-1-clamor95@gmail.com>
Convert devicetree bindings for the Motorola CPCAP MFD from TXT to YAML.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../bindings/mfd/motorola,cpcap.yaml | 403 ++++++++++++++++++
.../bindings/mfd/motorola-cpcap.txt | 78 ----
2 files changed, 403 insertions(+), 78 deletions(-)
create mode 100644 Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
delete mode 100644 Documentation/devicetree/bindings/mfd/motorola-cpcap.txt
diff --git a/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml b/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
new file mode 100644
index 0000000000000..76705ea56805a
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
@@ -0,0 +1,403 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/motorola,cpcap.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Motorola CPCAP PMIC MFD
+
+maintainers:
+ - Svyatoslav Ryhel <clamor95@gmail.com>
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ items:
+ - const: motorola,cpcap
+ - const: st,6556002
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ interrupt-controller: true
+
+ "#interrupt-cells":
+ const: 2
+
+ spi-max-frequency:
+ maximum: 9600000
+
+ spi-cs-high: true
+ spi-cpol: true
+ spi-cpha: true
+
+ adc:
+ $ref: /schemas/iio/adc/motorola,cpcap-adc.yaml#
+
+ audio-codec:
+ type: object
+ additionalProperties: false
+
+ properties:
+ interrupts:
+ items:
+ - description: headset detect interrupt
+ - description: microphone bias 2 detect interrupt
+
+ interrupt-names:
+ items:
+ - const: hs
+ - const: mb2
+
+ "#sound-dai-cells":
+ const: 1
+
+ VAUDIO-supply:
+ description:
+ Codec power supply, usually VAUDIO regulator of CPCAP.
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: port connected to the Stereo HiFi DAC
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: port connected to the Voice DAC
+
+ required:
+ - port@0
+ - port@1
+
+ required:
+ - interrupts
+ - interrupt-names
+ - "#sound-dai-cells"
+
+ battery:
+ $ref: /schemas/power/supply/cpcap-battery.yaml#
+
+ charger:
+ $ref: /schemas/power/supply/cpcap-charger.yaml#
+
+ key-power:
+ $ref: /schemas/input/motorola,cpcap-pwrbutton.yaml#
+
+ phy:
+ $ref: /schemas/phy/motorola,cpcap-usb-phy.yaml#
+
+ regulator:
+ $ref: /schemas/regulator/motorola,cpcap-regulator.yaml#
+
+ rtc:
+ $ref: /schemas/rtc/motorola,cpcap-rtc.yaml#
+
+patternProperties:
+ "^led(-[a-z]+)?$":
+ $ref: /schemas/leds/motorola,cpcap-leds.yaml#
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-controller
+ - "#interrupt-cells"
+ - spi-max-frequency
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/input/linux-event-codes.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpcap: pmic@0 {
+ compatible = "motorola,cpcap", "st,6556002";
+ reg = <0>; /* cs0 */
+
+ interrupt-parent = <&gpio1>;
+ interrupts = <7 IRQ_TYPE_EDGE_RISING>;
+
+ interrupt-controller;
+ #interrupt-cells = <2>;
+
+ spi-max-frequency = <3000000>;
+ spi-cs-high;
+
+ spi-cpol;
+ spi-cpha;
+
+ cpcap_adc: adc {
+ compatible = "motorola,cpcap-adc";
+
+ interrupt-parent = <&cpcap>;
+ interrupts = <8 IRQ_TYPE_NONE>;
+ interrupt-names = "adcdone";
+
+ #io-channel-cells = <1>;
+ };
+
+ cpcap_audio: audio-codec {
+ interrupt-parent = <&cpcap>;
+ interrupts = <9 IRQ_TYPE_NONE>, <10 IRQ_TYPE_NONE>;
+ interrupt-names = "hs", "mb2";
+
+ VAUDIO-supply = <&vdd_audio>;
+
+ #sound-dai-cells = <1>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* HiFi */
+ port@0 {
+ reg = <0>;
+
+ cpcap_audio_codec0: endpoint {
+ };
+ };
+
+ /* Voice */
+ port@1 {
+ reg = <1>;
+
+ cpcap_audio_codec1: endpoint {
+ };
+ };
+ };
+ };
+
+ cpcap_battery: battery {
+ compatible = "motorola,cpcap-battery";
+
+ interrupt-parent = <&cpcap>;
+ interrupts = <6 IRQ_TYPE_NONE>, <5 IRQ_TYPE_NONE>,
+ <3 IRQ_TYPE_NONE>, <20 IRQ_TYPE_NONE>,
+ <54 IRQ_TYPE_NONE>, <57 IRQ_TYPE_NONE>;
+ interrupt-names = "eol", "lowbph", "lowbpl",
+ "chrgcurr1", "battdetb", "cccal";
+
+ io-channels = <&cpcap_adc 0>, <&cpcap_adc 1>,
+ <&cpcap_adc 5>, <&cpcap_adc 6>;
+ io-channel-names = "battdetb", "battp",
+ "chg_isense", "batti";
+ power-supplies = <&cpcap_charger>;
+ };
+
+ cpcap_charger: charger {
+ compatible = "motorola,mapphone-cpcap-charger";
+
+ interrupt-parent = <&cpcap>;
+ interrupts = <13 IRQ_TYPE_NONE>, <12 IRQ_TYPE_NONE>,
+ <29 IRQ_TYPE_NONE>, <28 IRQ_TYPE_NONE>,
+ <22 IRQ_TYPE_NONE>, <21 IRQ_TYPE_NONE>,
+ <20 IRQ_TYPE_NONE>, <19 IRQ_TYPE_NONE>,
+ <54 IRQ_TYPE_NONE>;
+ interrupt-names = "chrg_det", "rvrs_chrg", "chrg_se1b",
+ "se0conn", "rvrs_mode", "chrgcurr2",
+ "chrgcurr1", "vbusvld", "battdetb";
+
+ mode-gpios = <&gpio3 29 GPIO_ACTIVE_LOW>,
+ <&gpio3 23 GPIO_ACTIVE_LOW>;
+
+ io-channels = <&cpcap_adc 0>, <&cpcap_adc 1>,
+ <&cpcap_adc 2>, <&cpcap_adc 5>,
+ <&cpcap_adc 6>;
+ io-channel-names = "battdetb", "battp",
+ "vbus", "chg_isense",
+ "batti";
+ };
+
+ key-power {
+ compatible = "motorola,cpcap-pwrbutton";
+
+ interrupt-parent = <&cpcap>;
+ interrupts = <23 IRQ_TYPE_NONE>;
+ };
+
+ led-red {
+ compatible = "motorola,cpcap-led-red";
+ vdd-supply = <&vdd_led>;
+ label = "status-led::red";
+ };
+
+ led-green {
+ compatible = "motorola,cpcap-led-green";
+ vdd-supply = <&vdd_led>;
+ label = "status-led::green";
+ };
+
+ led-blue {
+ compatible = "motorola,cpcap-led-blue";
+ vdd-supply = <&vdd_led>;
+ label = "status-led::blue";
+ };
+
+ cpcap_usb2_phy: phy {
+ compatible = "motorola,cpcap-usb-phy";
+
+ pinctrl-0 = <&usb_gpio_mux_sel1>, <&usb_gpio_mux_sel2>;
+ pinctrl-1 = <&usb_ulpi_pins>;
+ pinctrl-2 = <&usb_utmi_pins>;
+ pinctrl-3 = <&uart3_pins>;
+ pinctrl-names = "default", "ulpi", "utmi", "uart";
+ #phy-cells = <0>;
+
+ interrupts-extended =
+ <&cpcap 15 IRQ_TYPE_NONE>, <&cpcap 14 IRQ_TYPE_NONE>,
+ <&cpcap 28 IRQ_TYPE_NONE>, <&cpcap 19 IRQ_TYPE_NONE>,
+ <&cpcap 18 IRQ_TYPE_NONE>, <&cpcap 17 IRQ_TYPE_NONE>,
+ <&cpcap 16 IRQ_TYPE_NONE>, <&cpcap 49 IRQ_TYPE_NONE>,
+ <&cpcap 48 IRQ_TYPE_NONE>;
+ interrupt-names = "id_ground", "id_float", "se0conn",
+ "vbusvld", "sessvld", "sessend",
+ "se1", "dm", "dp";
+
+ mode-gpios = <&gpio2 28 GPIO_ACTIVE_HIGH>,
+ <&gpio1 0 GPIO_ACTIVE_HIGH>;
+
+ io-channels = <&cpcap_adc 2>, <&cpcap_adc 7>;
+ io-channel-names = "vbus", "id";
+
+ vusb-supply = <&avdd_usb>;
+ };
+
+ regulator {
+ compatible = "motorola,cpcap-regulator";
+
+ regulators {
+ vdd_cpu: SW1 {
+ regulator-name = "vdd_cpu";
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <1125000>;
+ regulator-enable-ramp-delay = <1500>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+
+ vdd_core: SW2 {
+ regulator-name = "vdd_core";
+ regulator-min-microvolt = <950000>;
+ regulator-max-microvolt = <1300000>;
+ regulator-enable-ramp-delay = <1500>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+
+ vdd_1v8_vio: SW3 {
+ regulator-name = "vdd_1v8_vio";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-enable-ramp-delay = <0>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+
+ vdd_aon: SW4 {
+ regulator-name = "vdd_aon";
+ regulator-min-microvolt = <950000>;
+ regulator-max-microvolt = <1300000>;
+ regulator-enable-ramp-delay = <1500>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+
+ vdd_led: SW5 {
+ regulator-name = "vdd_led";
+ regulator-min-microvolt = <5050000>;
+ regulator-max-microvolt = <5050000>;
+ regulator-enable-ramp-delay = <1500>;
+ regulator-boot-on;
+ };
+
+ vdd_hvio: VHVIO {
+ regulator-name = "vdd_hvio";
+ regulator-min-microvolt = <2775000>;
+ regulator-max-microvolt = <2775000>;
+ regulator-enable-ramp-delay = <1000>;
+ };
+
+ vcore_emmc: VSDIO {
+ regulator-name = "vcore_emmc";
+ regulator-min-microvolt = <1500000>;
+ regulator-max-microvolt = <3000000>;
+ regulator-enable-ramp-delay = <1000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+
+ avdd_dsi_csi: VCSI {
+ regulator-name = "avdd_dsi_csi";
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-enable-ramp-delay = <1000>;
+ regulator-boot-on;
+ };
+
+ avdd_3v3_periph: VWLAN2 {
+ regulator-name = "avdd_3v3_periph";
+ regulator-min-microvolt = <2775000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-enable-ramp-delay = <1000>;
+ regulator-boot-on;
+ };
+
+ vddio_usd: VSIMCARD {
+ regulator-name = "vddio_usd";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <2900000>;
+ regulator-enable-ramp-delay = <1000>;
+ regulator-boot-on;
+ };
+
+ vdd_haptic: VVIB {
+ regulator-name = "vdd_haptic";
+ regulator-min-microvolt = <1300000>;
+ regulator-max-microvolt = <3000000>;
+ regulator-enable-ramp-delay = <1000>;
+ };
+
+ avdd_usb: VUSB {
+ regulator-name = "avdd_usb";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-enable-ramp-delay = <1000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+
+ vdd_audio: VAUDIO {
+ regulator-name = "vdd_audio";
+ regulator-min-microvolt = <2775000>;
+ regulator-max-microvolt = <2775000>;
+ regulator-enable-ramp-delay = <1000>;
+ regulator-always-on;
+ regulator-boot-on;
+ };
+ };
+ };
+
+ cpcap_rtc: rtc {
+ compatible = "motorola,cpcap-rtc";
+
+ interrupt-parent = <&cpcap>;
+ interrupts = <39 IRQ_TYPE_NONE>, <26 IRQ_TYPE_NONE>;
+ };
+ };
+ };
+
+...
diff --git a/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt b/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt
deleted file mode 100644
index 18c3fc26ca932..0000000000000
--- a/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt
+++ /dev/null
@@ -1,78 +0,0 @@
-Motorola CPCAP PMIC device tree binding
-
-Required properties:
-- compatible : One or both of "motorola,cpcap" or "ste,6556002"
-- reg : SPI chip select
-- interrupts : The interrupt line the device is connected to
-- interrupt-controller : Marks the device node as an interrupt controller
-- #interrupt-cells : The number of cells to describe an IRQ, should be 2
-- #address-cells : Child device offset number of cells, should be 1
-- #size-cells : Child device size number of cells, should be 0
-- spi-max-frequency : Typically set to 3000000
-- spi-cs-high : SPI chip select direction
-
-Optional subnodes:
-
-The sub-functions of CPCAP get their own node with their own compatible values,
-which are described in the following files:
-
-- Documentation/devicetree/bindings/power/supply/cpcap-battery.yaml
-- Documentation/devicetree/bindings/power/supply/cpcap-charger.yaml
-- Documentation/devicetree/bindings/regulator/cpcap-regulator.txt
-- Documentation/devicetree/bindings/phy/motorola,cpcap-usb-phy.yaml
-- Documentation/devicetree/bindings/input/cpcap-pwrbutton.txt
-- Documentation/devicetree/bindings/rtc/cpcap-rtc.txt
-- Documentation/devicetree/bindings/leds/leds-cpcap.txt
-- Documentation/devicetree/bindings/iio/adc/motorola,cpcap-adc.yaml
-
-The only exception is the audio codec. Instead of a compatible value its
-node must be named "audio-codec".
-
-Required properties for the audio-codec subnode:
-
-- #sound-dai-cells = <1>;
-- interrupts : should contain jack detection interrupts, with headset
- detect interrupt matching "hs" and microphone bias 2
- detect interrupt matching "mb2" in interrupt-names.
-- interrupt-names : Contains "hs", "mb2"
-
-The audio-codec provides two DAIs. The first one is connected to the
-Stereo HiFi DAC and the second one is connected to the Voice DAC.
-
-Example:
-
-&mcspi1 {
- cpcap: pmic@0 {
- compatible = "motorola,cpcap", "ste,6556002";
- reg = <0>; /* cs0 */
- interrupt-parent = <&gpio1>;
- interrupts = <7 IRQ_TYPE_EDGE_RISING>;
- interrupt-controller;
- #interrupt-cells = <2>;
- #address-cells = <1>;
- #size-cells = <0>;
- spi-max-frequency = <3000000>;
- spi-cs-high;
-
- audio-codec {
- #sound-dai-cells = <1>;
- interrupts-extended = <&cpcap 9 0>, <&cpcap 10 0>;
- interrupt-names = "hs", "mb2";
-
- /* HiFi */
- port@0 {
- endpoint {
- remote-endpoint = <&cpu_dai1>;
- };
- };
-
- /* Voice */
- port@1 {
- endpoint {
- remote-endpoint = <&cpu_dai2>;
- };
- };
- };
- };
-};
-
--
2.53.0
^ permalink raw reply related
* [PATCH v7 4/6] dt-bindings: mfd: motorola-cpcap: document Mapphone and Mot CPCAP
From: Svyatoslav Ryhel @ 2026-07-21 9:56 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Pavel Machek, Svyatoslav Ryhel
Cc: linux-input, devicetree, linux-kernel, linux-leds
In-Reply-To: <20260721095654.429346-1-clamor95@gmail.com>
Add compatibles for Mapphone and Mot CPCAP subdevice compositions. Both
variations cannot use st,6556002 fallback since they may be based on
different controllers.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../devicetree/bindings/mfd/motorola,cpcap.yaml | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml b/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
index 76705ea56805a..da7abe2ec9126 100644
--- a/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
+++ b/Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml
@@ -14,9 +14,14 @@ allOf:
properties:
compatible:
- items:
- - const: motorola,cpcap
- - const: st,6556002
+ oneOf:
+ - enum:
+ - motorola,mapphone-cpcap
+ - motorola,mot-cpcap
+
+ - items:
+ - const: motorola,cpcap
+ - const: st,6556002
reg:
maxItems: 1
--
2.53.0
^ permalink raw reply related
* [PATCH v7 5/6] mfd: motorola-cpcap: diverge configuration per-board
From: Svyatoslav Ryhel @ 2026-07-21 9:56 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Pavel Machek, Svyatoslav Ryhel
Cc: linux-input, devicetree, linux-kernel, linux-leds
In-Reply-To: <20260721095654.429346-1-clamor95@gmail.com>
MFD have rigid subdevice structure which does not allow flexible dynamic
subdevice linking. Address this by diverging CPCAP subdevice composition
to take into account board specific configuration.
Create a common and default subdevice composition, rename edit existing
subdevice composition into cpcap_mapphone_devices since it targets mainly
Mapphone board.
Removed st,6556002 as it is no longer applicable to all cases and
duplicates motorola,cpcap, which is used as the default composition.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/mfd/motorola-cpcap.c | 125 +++++++++++++++--------------
include/linux/mfd/motorola-cpcap.h | 6 ++
2 files changed, 70 insertions(+), 61 deletions(-)
diff --git a/drivers/mfd/motorola-cpcap.c b/drivers/mfd/motorola-cpcap.c
index 4ed8704c985ab..5feb403f26101 100644
--- a/drivers/mfd/motorola-cpcap.c
+++ b/drivers/mfd/motorola-cpcap.c
@@ -11,6 +11,7 @@
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/sysfs.h>
@@ -29,6 +30,7 @@ struct cpcap_ddata {
struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS];
const struct regmap_config *regmap_conf;
struct regmap *regmap;
+ enum cpcap_variant variant;
};
static int cpcap_sense_irq(struct regmap *regmap, int irq)
@@ -194,20 +196,6 @@ static int cpcap_init_irq(struct cpcap_ddata *cpcap)
return 0;
}
-static const struct of_device_id cpcap_of_match[] = {
- { .compatible = "motorola,cpcap", },
- { .compatible = "st,6556002", },
- {},
-};
-MODULE_DEVICE_TABLE(of, cpcap_of_match);
-
-static const struct spi_device_id cpcap_spi_ids[] = {
- { .name = "cpcap" },
- { .name = "6556002" },
- { }
-};
-MODULE_DEVICE_TABLE(spi, cpcap_spi_ids);
-
static const struct regmap_config cpcap_regmap_config = {
.reg_bits = 16,
.reg_stride = 4,
@@ -240,62 +228,58 @@ static int cpcap_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume);
-static const struct mfd_cell cpcap_mfd_devices[] = {
- {
- .name = "cpcap_adc",
- .of_compatible = "motorola,mapphone-cpcap-adc",
- }, {
- .name = "cpcap_battery",
- .of_compatible = "motorola,cpcap-battery",
- }, {
- .name = "cpcap-charger",
- .of_compatible = "motorola,mapphone-cpcap-charger",
- }, {
- .name = "cpcap-regulator",
- .of_compatible = "motorola,mapphone-cpcap-regulator",
- }, {
- .name = "cpcap-rtc",
- .of_compatible = "motorola,cpcap-rtc",
- }, {
- .name = "cpcap-pwrbutton",
- .of_compatible = "motorola,cpcap-pwrbutton",
- }, {
- .name = "cpcap-usb-phy",
- .of_compatible = "motorola,mapphone-cpcap-usb-phy",
- }, {
- .name = "cpcap-led",
- .id = 0,
- .of_compatible = "motorola,cpcap-led-red",
- }, {
- .name = "cpcap-led",
- .id = 1,
- .of_compatible = "motorola,cpcap-led-green",
- }, {
- .name = "cpcap-led",
- .id = 2,
- .of_compatible = "motorola,cpcap-led-blue",
- }, {
- .name = "cpcap-led",
- .id = 3,
- .of_compatible = "motorola,cpcap-led-adl",
- }, {
- .name = "cpcap-led",
- .id = 4,
- .of_compatible = "motorola,cpcap-led-cp",
- }, {
- .name = "cpcap-codec",
- }
+static const struct mfd_cell cpcap_common_devices[] = {
+ MFD_CELL_OF("cpcap_battery", NULL, NULL, 0, 0, "motorola,cpcap-battery"),
+ MFD_CELL_OF("cpcap-rtc", NULL, NULL, 0, 0, "motorola,cpcap-rtc"),
+ MFD_CELL_OF("cpcap-pwrbutton", NULL, NULL, 0, 0, "motorola,cpcap-pwrbutton"),
+ MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 0, "motorola,cpcap-led-red"),
+ MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 1, "motorola,cpcap-led-green"),
+ MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 2, "motorola,cpcap-led-blue"),
+ MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 3, "motorola,cpcap-led-adl"),
+ MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 4, "motorola,cpcap-led-cp"),
+ MFD_CELL_NAME("cpcap-codec"),
+};
+
+static const struct mfd_cell cpcap_default_devices[] = {
+ MFD_CELL_OF("cpcap_adc", NULL, NULL, 0, 0, "motorola,cpcap-adc"),
+ MFD_CELL_OF("cpcap-regulator", NULL, NULL, 0, 0, "motorola,cpcap-regulator"),
+ MFD_CELL_OF("cpcap-usb-phy", NULL, NULL, 0, 0, "motorola,cpcap-usb-phy"),
+};
+
+static const struct mfd_cell cpcap_mapphone_devices[] = {
+ MFD_CELL_OF("cpcap_adc", NULL, NULL, 0, 0, "motorola,mapphone-cpcap-adc"),
+ MFD_CELL_OF("cpcap-charger", NULL, NULL, 0, 0, "motorola,mapphone-cpcap-charger"),
+ MFD_CELL_OF("cpcap-regulator", NULL, NULL, 0, 0, "motorola,mapphone-cpcap-regulator"),
+ MFD_CELL_OF("cpcap-usb-phy", NULL, NULL, 0, 0, "motorola,mapphone-cpcap-usb-phy"),
};
static int cpcap_probe(struct spi_device *spi)
{
struct cpcap_ddata *cpcap;
+ const struct mfd_cell *cells;
+ unsigned int num_cells;
int ret;
cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL);
if (!cpcap)
return -ENOMEM;
+ cpcap->variant = (enum cpcap_variant)spi_get_device_match_data(spi);
+
+ switch (cpcap->variant) {
+ case CPCAP_DEFAULT:
+ cells = cpcap_default_devices;
+ num_cells = ARRAY_SIZE(cpcap_default_devices);
+ break;
+ case CPCAP_MAPPHONE:
+ cells = cpcap_mapphone_devices;
+ num_cells = ARRAY_SIZE(cpcap_mapphone_devices);
+ break;
+ default:
+ return dev_err_probe(&spi->dev, -ENODEV,
+ "Unknown device %d\n", cpcap->variant);
+ }
+
cpcap->spi = spi;
spi_set_drvdata(spi, cpcap);
@@ -330,10 +314,29 @@ static int cpcap_probe(struct spi_device *spi)
spi->dev.coherent_dma_mask = 0;
spi->dev.dma_mask = &spi->dev.coherent_dma_mask;
- return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
- ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
+ ret = devm_mfd_add_devices(&spi->dev, 0, cpcap_common_devices,
+ ARRAY_SIZE(cpcap_common_devices), NULL, 0, NULL);
+ if (ret)
+ return dev_err_probe(&spi->dev, ret,
+ "Failed to add common child devices\n");
+
+ return devm_mfd_add_devices(&spi->dev, 0, cells, num_cells, NULL, 0, NULL);
}
+static const struct of_device_id cpcap_of_match[] = {
+ { .compatible = "motorola,cpcap", .data = (void *)CPCAP_DEFAULT },
+ { .compatible = "motorola,mapphone-cpcap", .data = (void *)CPCAP_MAPPHONE },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cpcap_of_match);
+
+static const struct spi_device_id cpcap_spi_ids[] = {
+ { .name = "cpcap", .driver_data = CPCAP_DEFAULT },
+ { .name = "mapphone-cpcap", .driver_data = CPCAP_MAPPHONE },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(spi, cpcap_spi_ids);
+
static struct spi_driver cpcap_driver = {
.driver = {
.name = "cpcap-core",
diff --git a/include/linux/mfd/motorola-cpcap.h b/include/linux/mfd/motorola-cpcap.h
index 981e5777deb74..1a85b06272c8f 100644
--- a/include/linux/mfd/motorola-cpcap.h
+++ b/include/linux/mfd/motorola-cpcap.h
@@ -25,6 +25,12 @@
#define CPCAP_REVISION_2_0 0x10
#define CPCAP_REVISION_2_1 0x11
+enum cpcap_variant {
+ CPCAP_DEFAULT = 1,
+ CPCAP_MAPPHONE,
+ CPCAP_MAX
+};
+
/* CPCAP registers */
#define CPCAP_REG_INT1 0x0000 /* Interrupt 1 */
#define CPCAP_REG_INT2 0x0004 /* Interrupt 2 */
--
2.53.0
^ permalink raw reply related
* [PATCH v7 6/6] mfd: motorola-cpcap: add support for Mot CPCAP composition
From: Svyatoslav Ryhel @ 2026-07-21 9:56 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Lee Jones, Pavel Machek, Svyatoslav Ryhel
Cc: linux-input, devicetree, linux-kernel, linux-leds
In-Reply-To: <20260721095654.429346-1-clamor95@gmail.com>
Add a MFD subdevice composition used in Tegra20 based Mot board
(Motorola Atrix 4G and Droid X2).
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/mfd/motorola-cpcap.c | 20 +++++++++++++++++++-
include/linux/mfd/motorola-cpcap.h | 1 +
2 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/drivers/mfd/motorola-cpcap.c b/drivers/mfd/motorola-cpcap.c
index 5feb403f26101..65d5eb18aa004 100644
--- a/drivers/mfd/motorola-cpcap.c
+++ b/drivers/mfd/motorola-cpcap.c
@@ -236,7 +236,6 @@ static const struct mfd_cell cpcap_common_devices[] = {
MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 1, "motorola,cpcap-led-green"),
MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 2, "motorola,cpcap-led-blue"),
MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 3, "motorola,cpcap-led-adl"),
- MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 4, "motorola,cpcap-led-cp"),
MFD_CELL_NAME("cpcap-codec"),
};
@@ -244,6 +243,7 @@ static const struct mfd_cell cpcap_default_devices[] = {
MFD_CELL_OF("cpcap_adc", NULL, NULL, 0, 0, "motorola,cpcap-adc"),
MFD_CELL_OF("cpcap-regulator", NULL, NULL, 0, 0, "motorola,cpcap-regulator"),
MFD_CELL_OF("cpcap-usb-phy", NULL, NULL, 0, 0, "motorola,cpcap-usb-phy"),
+ MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 4, "motorola,cpcap-led-cp"),
};
static const struct mfd_cell cpcap_mapphone_devices[] = {
@@ -251,6 +251,18 @@ static const struct mfd_cell cpcap_mapphone_devices[] = {
MFD_CELL_OF("cpcap-charger", NULL, NULL, 0, 0, "motorola,mapphone-cpcap-charger"),
MFD_CELL_OF("cpcap-regulator", NULL, NULL, 0, 0, "motorola,mapphone-cpcap-regulator"),
MFD_CELL_OF("cpcap-usb-phy", NULL, NULL, 0, 0, "motorola,mapphone-cpcap-usb-phy"),
+ MFD_CELL_OF("cpcap-led", NULL, NULL, 0, 4, "motorola,cpcap-led-cp"),
+};
+
+/*
+ * The Mot board features a USB-PHY and charger similar to the ones in
+ * Mapphone; however, because Mot is based on Tegra20, it is incompatible
+ * with the existing implementation, which is tightly interconnected with
+ * the OMAP USB PHY.
+ */
+static const struct mfd_cell cpcap_mot_devices[] = {
+ MFD_CELL_OF("cpcap_adc", NULL, NULL, 0, 0, "motorola,mot-cpcap-adc"),
+ MFD_CELL_OF("cpcap-regulator", NULL, NULL, 0, 0, "motorola,mot-cpcap-regulator"),
};
static int cpcap_probe(struct spi_device *spi)
@@ -275,6 +287,10 @@ static int cpcap_probe(struct spi_device *spi)
cells = cpcap_mapphone_devices;
num_cells = ARRAY_SIZE(cpcap_mapphone_devices);
break;
+ case CPCAP_MOT:
+ cells = cpcap_mot_devices;
+ num_cells = ARRAY_SIZE(cpcap_mot_devices);
+ break;
default:
return dev_err_probe(&spi->dev, -ENODEV,
"Unknown device %d\n", cpcap->variant);
@@ -326,6 +342,7 @@ static int cpcap_probe(struct spi_device *spi)
static const struct of_device_id cpcap_of_match[] = {
{ .compatible = "motorola,cpcap", .data = (void *)CPCAP_DEFAULT },
{ .compatible = "motorola,mapphone-cpcap", .data = (void *)CPCAP_MAPPHONE },
+ { .compatible = "motorola,mot-cpcap", .data = (void *)CPCAP_MOT },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, cpcap_of_match);
@@ -333,6 +350,7 @@ MODULE_DEVICE_TABLE(of, cpcap_of_match);
static const struct spi_device_id cpcap_spi_ids[] = {
{ .name = "cpcap", .driver_data = CPCAP_DEFAULT },
{ .name = "mapphone-cpcap", .driver_data = CPCAP_MAPPHONE },
+ { .name = "mot-cpcap", .driver_data = CPCAP_MOT },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, cpcap_spi_ids);
diff --git a/include/linux/mfd/motorola-cpcap.h b/include/linux/mfd/motorola-cpcap.h
index 1a85b06272c8f..bb23363eeccdf 100644
--- a/include/linux/mfd/motorola-cpcap.h
+++ b/include/linux/mfd/motorola-cpcap.h
@@ -28,6 +28,7 @@
enum cpcap_variant {
CPCAP_DEFAULT = 1,
CPCAP_MAPPHONE,
+ CPCAP_MOT,
CPCAP_MAX
};
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v10 7/7] power: supply: Add charger driver for Asus Transformers
From: sashiko-bot @ 2026-07-21 9:59 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: linux-input, conor+dt, linux-leds, robh, lee, devicetree,
dmitry.torokhov
In-Reply-To: <20260721095233.420823-8-clamor95@gmail.com>
> From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
>
> Add support for charger detection capabilities found in the embedded
> controller of ASUS Transformer devices.
>
> Suggested-by: Maxim Schwalm <maxim.schwalm@gmail.com>
> Suggested-by: Svyatoslav Ryhel <clamor95@gmail.com>
> Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
> Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721095233.420823-1-clamor95@gmail.com?part=7
^ permalink raw reply
* Re: [PATCH v10 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC
From: sashiko-bot @ 2026-07-21 9:59 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: robh, linux-input, conor+dt, devicetree, dmitry.torokhov,
linux-leds, lee
In-Reply-To: <20260721095233.420823-2-clamor95@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `reg` property lacks a `maxItems` constraint or explicit `items` list, which will cause a meta-schema validation failure.
--
--- Patch [1]: [PATCH v10 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC ---
commit 9a8d6db220adffb6b7bdbf5fea9059f76a4c94f3
Author: Svyatoslav Ryhel <clamor95@gmail.com>
dt-bindings: embedded-controller: document ASUS Transformer EC
This commit documents the embedded controller used in the ASUS Transformer
device series. It adds a new YAML binding describing the Nuvoton-based
controllers attached to an I2C bus.
> 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
[Severity: Low]
Does this reg property also need a maxItems constraint or an explicit items
array list? The device tree meta-schema requires standard array properties to
be fully bounded to avoid implicitly unbounded arrays, which will likely
trigger a failure during make dt_binding_check.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721095233.420823-1-clamor95@gmail.com?part=1
^ permalink raw reply
* Re: [PATCH v10 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys
From: sashiko-bot @ 2026-07-21 10:00 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: devicetree, lee, conor+dt, robh, linux-input, dmitry.torokhov,
linux-leds
In-Reply-To: <20260721095233.420823-5-clamor95@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Concurrent lockless access to the shared state variable `priv->special_key_pressed` across different execution contexts without `READ_ONCE()` / `WRITE_ONCE()` annotations.
--
--- Patch [4]: [PATCH v10 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys ---
commit 258dea9f255ed0dea28a80d3eb88521b79b225d6
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>
input: keyboard: Add driver for ASUS Transformer dock multimedia keys
This patch adds support for the multimedia top button row of the ASUS
Transformer's Mobile Dock keyboard. It enables function keys by default
and allows switching to multimedia keys by pressing ScreenLock + AltGr.
> 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..44235e4a7f548
> --- /dev/null
> +++ b/drivers/input/keyboard/asus-transformer-ec-keys.c
[ ... ]
> +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;
[Severity: Medium]
Could this lockless write cause data races when accessed concurrently?
This executes in the input core's event delivery context, while the
EC notification path reads it. Should this use WRITE_ONCE() to prevent
potential KCSAN warnings about unmarked data races?
> +}
[ ... ]
> +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) {
[Severity: Medium]
Is it possible to read a stale value here due to concurrent modification?
Since priv->special_key_pressed is updated in the input event path and
read here in the blocking notifier thread without synchronization, should
this use READ_ONCE() to ensure the compiler doesn't perform unexpected
optimizations?
> + priv->special_key_mode = !priv->special_key_mode;
> + return NOTIFY_DONE;
> + }
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721095233.420823-1-clamor95@gmail.com?part=4
^ permalink raw reply
* Re: [PATCH v10 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: sashiko-bot @ 2026-07-21 10:01 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: linux-leds, robh, lee, conor+dt, dmitry.torokhov, linux-input,
devicetree
In-Reply-To: <20260721095233.420823-3-clamor95@gmail.com>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] The shared interrupt buffer `ddata->ec_buf` is overwritten during SMI handling, causing event loss and data corruption before broadcast.
- [Medium] The driver exports a bespoke accessor function (`asus_dockram_access_ctl`) for child devices, violating MFD architecture guidelines.
- [Medium] The driver hardcodes MFD cell IDs and the base ID instead of using automatic cell indexing (`PLATFORM_DEVID_AUTO`).
- [Medium] A complex pointer to a structure is passed as match data via the `.data` field in the device tree match table.
- [Low] The commit message prefix does not follow the mandatory format for the MFD subsystem.
- [Low] A driver-specific header file is incorrectly placed in the global MFD include directory.
--
--- Patch [2]: [PATCH v10 2/7] mfd: Add driver for ASUS Transformer embedded controller ---
commit a8d39d11a11a5c2d6e44a0723ad1b451adc8effe
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.
> mfd: Add driver for ASUS Transformer embedded controller
[Severity: Low]
This isn't a bug, but does this commit message prefix follow the standard
MFD subsystem format? I believe it should be formatted as
"mfd: <Driver>: <Capitalized description>", for example:
"mfd: asus-transformer-ec: Add driver...".
[ ... ]
> diff --git a/drivers/mfd/asus-transformer-ec.c b/drivers/mfd/asus-transformer-ec.c
> new file mode 100644
> index 0000000000000..739c66fdaf22f
> --- /dev/null
> +++ b/drivers/mfd/asus-transformer-ec.c
[ ... ]
> +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, ASUSEC_ENTRY_BUFSIZE);
> + ret = i2c_smbus_read_i2c_block_data(client, ASUSEC_DOCKRAM_CONTROL,
> + ASUSEC_ENTRY_SIZE, buf);
[ ... ]
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(asus_dockram_access_ctl);
[Severity: Medium]
Does exporting this bespoke accessor for child devices violate the MFD
layering rules? It looks like it creates tight coupling between the parent
and child drivers instead of using standard APIs like regmap or
dev_get_drvdata().
[ ... ]
> +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,
> + ASUSEC_ENTRY_SIZE,
> + ddata->ec_buf);
> + if (ret < ASUSEC_ENTRY_SIZE)
> + continue;
> +
> + if (ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK)
> + continue;
> +
> + 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,
> + ASUSEC_ENTRY_SIZE, ddata->ec_buf);
> + if (ret < ASUSEC_ENTRY_SIZE)
> + return IRQ_NONE;
> +
> + /* Check status byte with ASUSEC_OBF_MASK if data is valid */
> + ret = ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK;
> + if (!ret)
> + 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;
> +}
[Severity: High]
Can this sequence corrupt the interrupt event payload?
When asus_ec_handle_smi() receives ASUSEC_SMI_HANDSHAKE or ASUSEC_SMI_RESET,
it calls asus_ec_init() which then calls asus_ec_clear_buffer().
Because asus_ec_clear_buffer() repeatedly reads from the bus into the exact
same shared ddata->ec_buf until the queue is empty, won't it overwrite the
original event payload?
If so, the subsequent call to blocking_notifier_call_chain() will broadcast
a clobbered buffer (containing the final empty read) to the notifiers instead
of the actual event data.
[ ... ]
> +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-kbc"),
> + MFD_CELL_NAME("asus-transformer-ec-keys"),
> +};
[ ... ]
> +static int asus_ec_probe(struct i2c_client *client)
> +{
[ ... ]
> + return devm_mfd_add_devices(dev, 0, cells, num_cells, NULL, 0, NULL);
> +}
[Severity: Medium]
Does hardcoding the cell IDs and the base ID lead to device naming collisions?
The MFD subsystem guidelines mandate using PLATFORM_DEVID_AUTO for automatic
cell indexing instead of explicitly passing 0 or statically mapped IDs via the
MFD_CELL_BASIC macro.
[ ... ]
> +static const struct of_device_id asus_ec_match[] = {
> + {
> + .compatible = "asus,sl101-ec-dock",
> + .data = &asus_ec_sl101_dock_data
> + }, {
[Severity: Medium]
Is it safe to pass a complex structure pointer via the match table's .data
field?
Passing complex pointers this way can cause memory safety hazards and
initialization ordering races. Should this store an enum or integer ID
instead, and resolve the struct in the probe function?
[ ... ]
> diff --git a/include/linux/mfd/asus-transformer-ec.h b/include/linux/mfd/asus-transformer-ec.h
> new file mode 100644
> index 0000000000000..1c25c3a18355c
> --- /dev/null
> +++ b/include/linux/mfd/asus-transformer-ec.h
[Severity: Low]
This isn't a bug, but should this driver-specific header be placed in the
global include/linux/mfd/ directory? It looks like it is only used by the
parent and its immediate children, which suggests it should be kept local
to drivers/mfd/ to avoid tight coupling.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721095233.420823-1-clamor95@gmail.com?part=2
^ permalink raw reply
* Re: [PATCH v10 5/7] leds: Add driver for ASUS Transformer LEDs
From: sashiko-bot @ 2026-07-21 10:02 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: linux-input, dmitry.torokhov, linux-leds, robh, lee, conor+dt,
devicetree
In-Reply-To: <20260721095233.420823-6-clamor95@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] The manually constructed LED sysfs name incorrectly places the color in the function field.
- [Medium] The child driver uses a bespoke custom accessor function exposed by the MFD parent, violating MFD API layering guidelines.
- [Low] The commit message subject prefix does not adhere to the mandatory subsystem prefix format.
- [Low] The Kconfig help text contains a grammatical error, using "it's" instead of "its".
--
--- Patch [5]: [PATCH v10 5/7] leds: Add driver for ASUS Transformer LEDs ---
commit 177b0b82c65e4fc0357f15d45f7c0353ef5cb99a
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.
> leds: Add driver for ASUS Transformer LEDs
[Severity: Low]
This isn't a bug, but does the commit subject prefix follow the LED subsystem
guidelines? The subsystem typically expects the format
leds: <Driver>: <Capitalized description>.
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index f4a0a3c8c8705..f637d23400a89 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.
^^^^
[Severity: Low]
This isn't a bug, but should "it's" be "its" here?
[ ... ]
> diff --git a/drivers/leds/leds-asus-transformer-ec.c b/drivers/leds/leds-asus-transformer-ec.c
> new file mode 100644
> index 0000000000000..4421d629911ed
> --- /dev/null
> +++ b/drivers/leds/leds-asus-transformer-ec.c
[ ... ]
> +static enum led_brightness asus_ec_led_get_brightness(struct led_classdev *cdev)
> +{
> + struct asus_ec_led *led = container_of(cdev, struct asus_ec_led, cdev);
> + const struct asusec_core *ec = led->ddata->ec;
> + u64 ctl;
> + int ret;
> +
> + ret = asus_dockram_access_ctl(ec->dockram, &ctl, 0, 0);
[Severity: Medium]
Does using the custom parent accessor asus_dockram_access_ctl() violate
MFD API layering guidelines?
MFD core drivers should usually expose standard shared resources like regmap
rather than bespoke helper functions for child drivers to use, as it creates
tight coupling between the parent and child.
> + if (ret)
> + return LED_OFF;
> +
> + return ctl & led->ctrl_bit ? LED_ON : LED_OFF;
> +}
> +
> +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);
[Severity: Medium]
This is the same custom accessor as above, used again here in
asus_ec_led_set_brightness(). Should this be refactored to use standard
accessors?
[ ... ]
> +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 ret;
> +
> + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
> + if (!ddata)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, ddata);
> + ddata->ec = ec;
> +
> + for (int 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);
[Severity: Medium]
Since cfg->name contains the color (amber or green), does this format string
incorrectly place the color in the function segment of the standard
devicename:color:function naming scheme?
This will generate devicename::color instead of devicename:color:function,
which might confuse userspace LED managers that rely on standard sysfs
directories.
> + if (!led->cdev.name)
> + return -ENOMEM;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721095233.420823-1-clamor95@gmail.com?part=5
^ permalink raw reply
* Re: [PATCH v10 6/7] power: supply: Add driver for ASUS Transformer battery
From: sashiko-bot @ 2026-07-21 10:04 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: linux-input, linux-leds, lee, dmitry.torokhov, devicetree,
conor+dt, robh
In-Reply-To: <20260721095233.420823-7-clamor95@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The bounds check for battery property reading fails to account for the 2-byte read size, allowing the second byte to be read from logically invalid padding data.
- [Medium] Battery data cache is not invalidated on system resume, causing user-space to receive potentially stale battery readings.
--
--- Patch [6]: [PATCH v10 6/7] power: supply: Add driver for ASUS Transformer battery ---
commit 576ecafdda8cdf8e2dbf1964e8a1e9097bcefa55
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>
power: supply: Add driver for ASUS Transformer battery
This commit adds a new battery driver for Asus Transformer tablets and
mobile docks. It implements one battery cell per EC controller and
supports reading battery status via I2C.
> 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..4c0c6d4b09e2c
> --- /dev/null
> +++ b/drivers/power/supply/asus-transformer-ec-battery.c
[ ... ]
> +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);
[Severity: Medium]
Should the bounds check account for the 2-byte read size?
If the EC returns a length where offs == priv->batt_data[0] - 1, the bounds
check offs >= priv->batt_data[0] will pass.
Since get_unaligned_le16() reads two bytes, wouldn't it read a byte past the
logical data length and return corrupted data instead of -ENODATA?
> +}
[ ... ]
> +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;
> +}
[Severity: Medium]
Should the data cache be explicitly invalidated here?
If priv->batt_data_ts is not reset on resume, and jiffies has not
sufficiently advanced past the expiration timestamp during suspend, could the
driver serve stale battery data to user-space from before the sleep?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721095233.420823-1-clamor95@gmail.com?part=6
^ permalink raw reply
* Re: [PATCH v7 1/6] dt-bindings: leds: leds-cpcap: convert to DT schema
From: sashiko-bot @ 2026-07-21 10:06 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: lee, devicetree, dmitry.torokhov, conor+dt, linux-leds,
linux-input, robh
In-Reply-To: <20260721095654.429346-2-clamor95@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Low] The commit message description following the subsystem prefix is not capitalized.
- [Low] The newly added YAML binding points to a non-existent MFD schema, and the parent MFD binding still points to the deleted text file.
- [Low] The YAML binding conversion omitted the `examples` block.
--
--- Patch [1]: [PATCH v7 1/6] dt-bindings: leds: leds-cpcap: convert to DT schema ---
commit 42a8d75469150373ffb675f9ae6fccd8e3d482d6
Author: Svyatoslav Ryhel <clamor95@gmail.com>
dt-bindings: leds: leds-cpcap: convert to DT schema
Convert LEDs devicetree bindings for the Motorola CPCAP MFD from TXT to
YAML format. This patch does not change any functionality; the bindings
remain the same.
> dt-bindings: leds: leds-cpcap: convert to DT schema
[Severity: Low]
This isn't a bug, but should the description following the subsystem prefix
be capitalized? The LED subsystem conventions typically expect a capitalized
description, such as "Convert to DT schema".
> diff --git a/Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml b/Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml
> new file mode 100644
> index 0000000000000..c8e7b88a05ccf
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/motorola,cpcap-leds.yaml
[ ... ]
> +description:
> + This module is part of the Motorola CPCAP MFD device. For more details
> + see Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml. LEDs are
> + represented as sub-nodes of the PMIC node on the device tree.
[Severity: Low]
Does motorola,cpcap.yaml exist yet? It looks like this references a
non-existent MFD schema. Additionally, the parent MFD binding in
Documentation/devicetree/bindings/mfd/motorola-cpcap.txt wasn't updated
to reflect the deletion of the old leds-cpcap.txt file.
[ ... ]
> +required:
> + - compatible
> + - label
> + - vdd-supply
> +
> +unevaluatedProperties: false
> +
> +...
[Severity: Low]
Should this YAML file include an examples block? The YAML conversion
drops the original usage example from the .txt file.
Running dt_binding_check on standalone schemas that lack an examples section
can result in warnings or incomplete schema validation.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721095654.429346-1-clamor95@gmail.com?part=1
^ permalink raw reply
* Re: [PATCH v10 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
From: sashiko-bot @ 2026-07-21 10:07 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: linux-input, lee, conor+dt, devicetree, linux-leds,
dmitry.torokhov, robh
In-Reply-To: <20260721095233.420823-4-clamor95@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Incorrect registration order of the notifier and serio ports leads to NULL pointer dereferences during probe and use-after-free bugs during driver removal.
--
--- Patch [3]: [PATCH v10 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad ---
commit 4e9b70c95d0f43e6f5bf78aeff2f78f72b9b2f82
Author: Michał Mirosław <mirq-linux@rere.qmqm.pl>
input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
Add input driver for ASUS Transformer dock keyboard and touchpad.
> 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..c6d12c44449d0
> --- /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_)
> +{
[ ... ]
> + while (n--)
> + serio_interrupt(priv->sdev[port_idx], *data++, 0);
> +
> + return NOTIFY_OK;
> +}
[ ... ]
> +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);
> +
> + priv->nb.notifier_call = asus_ec_kbc_notify;
> +
> + error = blocking_notifier_chain_register(&ec->notify_list, &priv->nb);
> + if (error)
> + return dev_err_probe(&pdev->dev, error,
> + "failed to register blocking notifier chain");
> +
> + error = devm_add_action_or_reset(&pdev->dev,
> + asus_ec_notifier_chain_unregister,
> + priv);
> + if (error)
> + return error;
> +
> + 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);
[Severity: High]
Does this registration order leave the driver vulnerable to race conditions?
Since the parent MFD device might have already enabled its IRQ, an interrupt
can fire immediately after blocking_notifier_chain_register() completes. If
this occurs before the serio ports are registered, asus_ec_kbc_notify() will
read priv->sdev[port_idx] which is still NULL, and pass it to
serio_interrupt(), leading to a NULL pointer dereference.
Additionally, because devm actions execute in LIFO order during driver
removal or probe failure, the serio ports will be unregistered and freed
before the notifier is unregistered. If an interrupt fires during this
teardown window, could it pass a freed serio pointer to serio_interrupt()
and cause a use-after-free?
Should the notifier be registered only after the serio ports are fully
allocated and registered to prevent these crashes?
> + if (error)
> + return error;
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721095233.420823-1-clamor95@gmail.com?part=3
^ permalink raw reply
* Re: [PATCH v7 2/6] dt-bindings: input: cpcap-pwrbutton: convert to DT schema
From: sashiko-bot @ 2026-07-21 10:08 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: linux-input, dmitry.torokhov, devicetree, lee, robh, conor+dt,
linux-leds
In-Reply-To: <20260721095654.429346-3-clamor95@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The conversion to YAML format creates broken documentation cross-references between the child power button binding and the parent MFD binding.
--
--- Patch [2]: [PATCH v7 2/6] dt-bindings: input: cpcap-pwrbutton: convert to DT schema ---
commit 04f32d0760614b206c7745dbb0c714f9b7720ee6
Author: Svyatoslav Ryhel <clamor95@gmail.com>
dt-bindings: input: cpcap-pwrbutton: convert to DT schema
This commit converts the device tree bindings for the Motorola CPCAP PMIC
power button from plain text to YAML format.
> diff --git a/Documentation/devicetree/bindings/input/motorola,cpcap-pwrbutton.yaml b/Documentation/devicetree/bindings/input/motorola,cpcap-pwrbutton.yaml
> new file mode 100644
> index 0000000000000..77a3e5a47d1ae
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/motorola,cpcap-pwrbutton.yaml
> @@ -0,0 +1,32 @@
[ ... ]
> +maintainers:
> + - Svyatoslav Ryhel <clamor95@gmail.com>
> +
> +description:
> + This module is part of the Motorola CPCAP MFD device. For more details
> + see Documentation/devicetree/bindings/mfd/motorola,cpcap.yaml. The
[Severity: Low]
Are we referencing a file that doesn't exist yet? Since the parent MFD
binding hasn't been converted to YAML yet, the referenced file
motorola,cpcap.yaml doesn't seem to be in the tree.
Additionally, should the parent MFD binding
(Documentation/devicetree/bindings/mfd/motorola-cpcap.txt) be updated in
this patch? It appears it still explicitly lists cpcap-pwrbutton.txt in its
"Optional subnodes:" section, which is being deleted here.
> + power key is represented as a sub-node of the PMIC node on the device
> + tree.
> +
> +properties:
> + compatible:
> + const: motorola,cpcap-pwrbutton
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260721095654.429346-1-clamor95@gmail.com?part=2
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox