* [PATCH 1/2] dt-bindings: input: touchscreen: sitronix,st1232: Add wakeup-source
From: phucduc.bui @ 2026-03-05 11:35 UTC (permalink / raw)
To: dmitry.torokhov, robh, krzk+dt, conor+dt, geert+renesas,
magnus.damm
Cc: javier.carrasco, hechtb, wsa+renesas, linux-input, devicetree,
linux-renesas-soc, linux-kernel, phucduc.bui
In-Reply-To: <20260305113512.227269-1-phucduc.bui@gmail.com>
From: bui duc phuc <phucduc.bui@gmail.com>
Document the 'wakeup-source' property for Sitronix ST1232 touchscreen
controllers to allow the device to wake the system from suspend.
Signed-off-by: bui duc phuc <phucduc.bui@gmail.com>
---
.../bindings/input/touchscreen/sitronix,st1232.yaml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/sitronix,st1232.yaml b/Documentation/devicetree/bindings/input/touchscreen/sitronix,st1232.yaml
index 978afaa4fcef..672544e5a26e 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/sitronix,st1232.yaml
+++ b/Documentation/devicetree/bindings/input/touchscreen/sitronix,st1232.yaml
@@ -32,6 +32,10 @@ properties:
description: A phandle to the reset GPIO
maxItems: 1
+ wakeup-source:
+ description: Device can be used as a wakeup source.
+ type: boolean
+
required:
- compatible
- reg
@@ -51,6 +55,7 @@ examples:
reg = <0x55>;
interrupts = <2 0>;
gpios = <&gpio1 166 0>;
+ wakeup-source;
touch-overlay {
segment-0 {
--
2.43.0
^ permalink raw reply related
* [PATCH 0/2] st1232: Add wakeup-source support
From: phucduc.bui @ 2026-03-05 11:35 UTC (permalink / raw)
To: dmitry.torokhov, robh, krzk+dt, conor+dt, geert+renesas,
magnus.damm
Cc: javier.carrasco, hechtb, wsa+renesas, linux-input, devicetree,
linux-renesas-soc, linux-kernel, phucduc.bui
From: bui duc phuc <phucduc.bui@gmail.com>
This patch series adds support for using the Sitronix ST1232
touchscreen as a wakeup source on the Armadillo800EVA board.
Patch 1 documents the generic wakeup-source property in the
Devicetree binding for the ST1232 touchscreen controller.
Patch 2 enables the wakeup-source property in the ST1232
touchscreen node for the Armadillo800EVA board, allowing touch
events to wake the system from suspend.
This series depends on the following patch which has been
submitted but not yet merged:
drm: shmobile: Fix blank screen after resume when LCDC is stopped
Link: https://lore.kernel.org/all/20260226054035.30330-1-phucduc.bui@gmail.com/
bui duc phuc (2):
dt-bindings: input: touchscreen: sitronix,st1232: Add wakeup-source
arm: dts: renesas: r8a7740-armadillo800eva: Add wakeup-source to
st1232
.../bindings/input/touchscreen/sitronix,st1232.yaml | 5 +++++
arch/arm/boot/dts/renesas/r8a7740-armadillo800eva.dts | 1 +
2 files changed, 6 insertions(+)
--
2.43.0
^ permalink raw reply
* Re: [PATCH] xpad: expose xinput capabilities via sysattr
From: Sanjay Govind @ 2026-03-04 19:58 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Vicki Pfau, Mario Limonciello, Nilton Perim Neto,
Pierre-Loup A. Griffais, linux-input, linux-kernel
In-Reply-To: <CALQgdA0q=+6L2mQBqNPUBzo+HE6=AjhSPv5G_dK9g=V2npSsWA@mail.gmail.com>
On Wed, Mar 4, 2026 at 2:34 PM Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
> You need to start with enumerating what data is currently not available
> through other means and why it is needed. Is this something that other
> gamepad-like devices also lack?
Other controllers do have this sort of thing though, PS4 and PS5
controllers have a capabilities report, but since they are HID based,
SDL just requests the report directly via a hid get feature report
request.
Since xinput isn't HID based, there isn't a way to fetch these
reports, and right now that makes it difficult to discern these
devices in SDL and Wine.
Usually you could also fall back and rely on vids and pids, but 360
wireless devices don't include pids anywhere, so that isn't a usable
method here.
The only other devices I can think of that would have similar problems
would be the Xbox One controllers, but at least there we do have a
workable vid and pid, so we can just rely on that since the descriptor
format is much more complicated in that scenario.
If it was prefered, we could go some route where we put the entire
capabilities blob into a single sysattr as JSON or something, which is
similar to the route unity went with for this sort of thing, or if
there is some way to request data in a similar fashion to how HID has
feature reports, i'd be happy to just implement some sort of
capabilities request, I just didn't see a way to do that when i was
trying to implement this.
On Wed, Mar 4, 2026 at 2:46 PM Sanjay Govind <sanjay.govind9@gmail.com> wrote:
>
> On Wed, Mar 4, 2026 at 2:34 PM Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:
> > You need to start with enumerating what data is currently not available
> > through other means and why it is needed. Is this something that other
> > gamepad-like devices also lack?
>
> This information in particular is important for xinput specifically, as it gives context for determining what kind of controller is in use.
> For most other gamepads, that was done via the vid and pid, but for xinput that isn't enough as the vid and pid isn't exposed over wireless.
> XInput also just doesn't expose the vid and pid on windows, so a lot of controllers encode information into the other capabilities like the sticks,
> and the flags, so you need the entire capabilities blob to properly differentiate every controller, and my goal is to then feed this into Wine and SDL,
> so that they can properly differentiate these controllers.
^ permalink raw reply
* [PATCH v5 7/7] power: supply: Add charger driver for Asus Transformers
From: Svyatoslav Ryhel @ 2026-03-04 18:57 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: <20260304185751.83494-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>
---
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
.../supply/asus-transformer-ec-charger.c | 193 ++++++++++++++++++
3 files changed, 205 insertions(+)
create mode 100644 drivers/power/supply/asus-transformer-ec-charger.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 3c46b412632d..56800aab82f9 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -497,6 +497,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 aa5e6b05b018..24679f09bb61 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -68,6 +68,7 @@ obj-$(CONFIG_CHARGER_RT9471) += rt9471.o
obj-$(CONFIG_CHARGER_RT9756) += rt9756.o
obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o
obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
+obj-$(CONFIG_CHARGER_ASUS_TRANSFORMER_EC) += asus-transformer-ec-charger.o
obj-$(CONFIG_CHARGER_PF1550) += pf1550-charger.o
obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o ab8500_chargalg.o
diff --git a/drivers/power/supply/asus-transformer-ec-charger.c b/drivers/power/supply/asus-transformer-ec-charger.c
new file mode 100644
index 000000000000..de01f0bf2fd7
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-charger.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/err.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+
+struct asus_ec_charger_data {
+ struct notifier_block nb;
+ const struct asusec_info *ec;
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+};
+
+static enum power_supply_property asus_ec_charger_properties[] = {
+ POWER_SUPPLY_PROP_USB_TYPE,
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int asus_ec_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
+ enum power_supply_usb_type psu;
+ int ret;
+ u64 ctl;
+
+ ret = asus_ec_get_ctl(priv->ec, &ctl);
+ if (ret)
+ return ret;
+
+ switch (ctl & (ASUSEC_CTL_FULL_POWER_SOURCE | ASUSEC_CTL_DIRECT_POWER_SOURCE)) {
+ case ASUSEC_CTL_FULL_POWER_SOURCE:
+ psu = POWER_SUPPLY_USB_TYPE_CDP; /* DOCK */
+ break;
+ case ASUSEC_CTL_DIRECT_POWER_SOURCE:
+ psu = POWER_SUPPLY_USB_TYPE_SDP; /* USB */
+ break;
+ case 0:
+ psu = POWER_SUPPLY_USB_TYPE_UNKNOWN; /* no power source connected */
+ break;
+ default:
+ psu = POWER_SUPPLY_USB_TYPE_ACA; /* power adapter */
+ break;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = psu != POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ return 0;
+
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = psu;
+ return 0;
+
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ if (ctl & ASUSEC_CTL_TEST_DISCHARGE)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
+ else if (ctl & ASUSEC_CTL_USB_CHARGE)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ return 0;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = priv->ec->model;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int asus_ec_charger_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct asus_ec_charger_data *priv = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ switch ((enum power_supply_charge_behaviour)val->intval) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ return asus_ec_update_ctl(priv->ec,
+ ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
+ ASUSEC_CTL_USB_CHARGE);
+
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ return asus_ec_clear_ctl_bits(priv->ec,
+ ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE);
+
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+ return asus_ec_update_ctl(priv->ec,
+ ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE,
+ ASUSEC_CTL_TEST_DISCHARGE);
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int asus_ec_charger_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct power_supply_desc asus_ec_charger_desc = {
+ .name = "asus-ec-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) |
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) |
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE),
+ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) |
+ BIT(POWER_SUPPLY_USB_TYPE_SDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_CDP) |
+ BIT(POWER_SUPPLY_USB_TYPE_ACA),
+ .properties = asus_ec_charger_properties,
+ .num_properties = ARRAY_SIZE(asus_ec_charger_properties),
+ .get_property = asus_ec_charger_get_property,
+ .set_property = asus_ec_charger_set_property,
+ .property_is_writeable = asus_ec_charger_property_is_writeable,
+ .no_thermal = true,
+};
+
+static int asus_ec_charger_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct asus_ec_charger_data *priv =
+ container_of(nb, struct asus_ec_charger_data, nb);
+
+ switch (action) {
+ case ASUSEC_SMI_ACTION(POWER_NOTIFY):
+ case ASUSEC_SMI_ACTION(ADAPTER_EVENT):
+ power_supply_changed(priv->psy);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int asus_ec_charger_probe(struct platform_device *pdev)
+{
+ struct asus_ec_charger_data *priv;
+ struct device *dev = &pdev->dev;
+ struct power_supply_config cfg = { };
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->ec = cell_to_ec(pdev);
+
+ cfg.fwnode = dev_fwnode(dev->parent);
+ cfg.drv_data = priv;
+
+ memcpy(&priv->psy_desc, &asus_ec_charger_desc, sizeof(priv->psy_desc));
+ priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-charger",
+ priv->ec->name);
+
+ priv->psy = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
+ if (IS_ERR(priv->psy))
+ return dev_err_probe(dev, PTR_ERR(priv->psy),
+ "Failed to register power supply\n");
+
+ priv->nb.notifier_call = asus_ec_charger_notify;
+
+ return devm_asus_ec_register_notifier(pdev, &priv->nb);
+}
+
+static struct platform_driver asus_ec_charger_driver = {
+ .driver.name = "asus-transformer-ec-charger",
+ .probe = asus_ec_charger_probe,
+};
+module_platform_driver(asus_ec_charger_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer Pad battery charger driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v5 6/7] power: supply: Add driver for ASUS Transformer battery
From: Svyatoslav Ryhel @ 2026-03-04 18:57 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: <20260304185751.83494-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Driver implements one battery cell per EC controller and supports reading
of battery status for ASUS Transformer's pad and mobile dock.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
---
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
.../supply/asus-transformer-ec-battery.c | 272 ++++++++++++++++++
3 files changed, 284 insertions(+)
create mode 100644 drivers/power/supply/asus-transformer-ec-battery.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 81fadb0695a9..3c46b412632d 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -122,6 +122,17 @@ config BATTERY_CHAGALL
This driver can also be built as a module. If so, the module will be
called chagall-battery.
+config BATTERY_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's battery driver"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y here to enable support APM status emulation using
+ battery class devices.
+
+ This sub-driver supports battery cells found in Asus Transformer
+ tablets and mobile docks and controlled by special embedded
+ controller.
+
config BATTERY_CPCAP
tristate "Motorola CPCAP PMIC battery driver"
depends on MFD_CPCAP && IIO
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 41c400bbf022..aa5e6b05b018 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_TEST_POWER) += test_power.o
obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
obj-$(CONFIG_CHARGER_ADP5061) += adp5061.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
+obj-$(CONFIG_BATTERY_ASUS_TRANSFORMER_EC) += asus-transformer-ec-battery.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o
diff --git a/drivers/power/supply/asus-transformer-ec-battery.c b/drivers/power/supply/asus-transformer-ec-battery.c
new file mode 100644
index 000000000000..aefcd3fed6fe
--- /dev/null
+++ b/drivers/power/supply/asus-transformer-ec-battery.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/devm-helpers.h>
+#include <linux/err.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/unaligned.h>
+
+#define ASUSEC_BATTERY_DATA_FRESH_MSEC 5000
+
+#define ASUSEC_BATTERY_DISCHARGING 0x40
+#define ASUSEC_BATTERY_FULL_CHARGED 0x20
+#define ASUSEC_BATTERY_NOT_CHARGING 0x10
+
+#define TEMP_CELSIUS_OFFSET 2731
+
+struct asus_ec_battery_data {
+ const struct asusec_info *ec;
+ struct power_supply *battery;
+ struct power_supply_desc psy_desc;
+ struct delayed_work poll_work;
+ struct mutex battery_lock; /* for data refresh */
+ unsigned long batt_data_ts;
+ int last_state;
+ u8 batt_data[DOCKRAM_ENTRY_BUFSIZE];
+};
+
+static int asus_ec_battery_refresh(struct asus_ec_battery_data *priv)
+{
+ int ret = 0;
+
+ guard(mutex)(&priv->battery_lock);
+
+ if (time_before(jiffies, priv->batt_data_ts))
+ return ret;
+
+ ret = asus_dockram_read(priv->ec->dockram, ASUSEC_DOCKRAM_BATT_CTL,
+ priv->batt_data);
+ if (ret < 0)
+ return ret;
+
+ priv->batt_data_ts = jiffies +
+ msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC);
+
+ return ret;
+}
+
+static enum power_supply_property asus_ec_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static const unsigned int asus_ec_battery_prop_offs[] = {
+ [POWER_SUPPLY_PROP_STATUS] = 1,
+ [POWER_SUPPLY_PROP_VOLTAGE_MAX] = 3,
+ [POWER_SUPPLY_PROP_CURRENT_MAX] = 5,
+ [POWER_SUPPLY_PROP_TEMP] = 7,
+ [POWER_SUPPLY_PROP_VOLTAGE_NOW] = 9,
+ [POWER_SUPPLY_PROP_CURRENT_NOW] = 11,
+ [POWER_SUPPLY_PROP_CAPACITY] = 13,
+ [POWER_SUPPLY_PROP_CHARGE_NOW] = 15,
+ [POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW] = 17,
+ [POWER_SUPPLY_PROP_TIME_TO_FULL_NOW] = 19,
+};
+
+static int asus_ec_battery_get_value(struct asus_ec_battery_data *priv,
+ enum power_supply_property psp)
+{
+ int ret, offs;
+
+ if (psp >= ARRAY_SIZE(asus_ec_battery_prop_offs))
+ return -EINVAL;
+
+ offs = asus_ec_battery_prop_offs[psp];
+ if (!offs)
+ return -EINVAL;
+
+ ret = asus_ec_battery_refresh(priv);
+ if (ret < 0)
+ return ret;
+
+ if (offs >= priv->batt_data[0])
+ return -ENODATA;
+
+ return get_unaligned_le16(priv->batt_data + offs);
+}
+
+static int asus_ec_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct asus_ec_battery_data *priv = power_supply_get_drvdata(psy);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+
+ default:
+ ret = asus_ec_battery_get_value(priv, psp);
+ if (ret < 0)
+ return ret;
+
+ val->intval = (s16)ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (ret & ASUSEC_BATTERY_FULL_CHARGED)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (ret & ASUSEC_BATTERY_NOT_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (ret & ASUSEC_BATTERY_DISCHARGING)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval -= TEMP_CELSIUS_OFFSET;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval *= 1000;
+ break;
+
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ val->intval *= 60;
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+static void asus_ec_battery_poll_work(struct work_struct *work)
+{
+ struct asus_ec_battery_data *priv =
+ container_of(work, struct asus_ec_battery_data, poll_work.work);
+ int state;
+
+ state = asus_ec_battery_get_value(priv, POWER_SUPPLY_PROP_STATUS);
+ if (state < 0)
+ return;
+
+ if (state & ASUSEC_BATTERY_FULL_CHARGED)
+ state = POWER_SUPPLY_STATUS_FULL;
+ else if (state & ASUSEC_BATTERY_DISCHARGING)
+ state = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ state = POWER_SUPPLY_STATUS_CHARGING;
+
+ if (priv->last_state != state) {
+ priv->last_state = state;
+ power_supply_changed(priv->battery);
+ }
+
+ /* continuously send uevent notification */
+ schedule_delayed_work(&priv->poll_work,
+ msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
+}
+
+static const struct power_supply_desc asus_ec_battery_desc = {
+ .name = "asus-ec-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = asus_ec_battery_properties,
+ .num_properties = ARRAY_SIZE(asus_ec_battery_properties),
+ .get_property = asus_ec_battery_get_property,
+ .external_power_changed = power_supply_changed,
+};
+
+static int asus_ec_battery_probe(struct platform_device *pdev)
+{
+ struct asus_ec_battery_data *priv;
+ struct device *dev = &pdev->dev;
+ struct power_supply_config cfg = { };
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ mutex_init(&priv->battery_lock);
+
+ priv->ec = cell_to_ec(pdev);
+ priv->batt_data_ts = jiffies - 1;
+ priv->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ cfg.fwnode = dev_fwnode(dev->parent);
+ cfg.drv_data = priv;
+
+ memcpy(&priv->psy_desc, &asus_ec_battery_desc, sizeof(priv->psy_desc));
+ priv->psy_desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s-battery",
+ priv->ec->name);
+
+ priv->battery = devm_power_supply_register(dev, &priv->psy_desc, &cfg);
+ if (IS_ERR(priv->battery))
+ return dev_err_probe(dev, PTR_ERR(priv->battery),
+ "Failed to register power supply\n");
+
+ ret = devm_delayed_work_autocancel(dev, &priv->poll_work,
+ asus_ec_battery_poll_work);
+ if (ret)
+ return ret;
+
+ schedule_delayed_work(&priv->poll_work,
+ msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
+
+ return 0;
+}
+
+static int __maybe_unused asus_ec_battery_suspend(struct device *dev)
+{
+ struct asus_ec_battery_data *priv = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&priv->poll_work);
+
+ return 0;
+}
+
+static int __maybe_unused asus_ec_battery_resume(struct device *dev)
+{
+ struct asus_ec_battery_data *priv = dev_get_drvdata(dev);
+
+ schedule_delayed_work(&priv->poll_work,
+ msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC));
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(asus_ec_battery_pm_ops,
+ asus_ec_battery_suspend, asus_ec_battery_resume);
+
+static struct platform_driver asus_ec_battery_driver = {
+ .driver = {
+ .name = "asus-transformer-ec-battery",
+ .pm = &asus_ec_battery_pm_ops,
+ },
+ .probe = asus_ec_battery_probe,
+};
+module_platform_driver(asus_ec_battery_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("ASUS Transformer's battery driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v5 5/7] leds: Add driver for ASUS Transformer LEDs
From: Svyatoslav Ryhel @ 2026-03-04 18:57 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: <20260304185751.83494-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
ASUS Transformer tablets have a green and an amber LED on both the Pad
and the Dock. If both LEDs are enabled simultaneously, the emitted light
will be yellow.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
---
drivers/leds/Kconfig | 11 ++++
drivers/leds/Makefile | 1 +
drivers/leds/leds-asus-transformer-ec.c | 79 +++++++++++++++++++++++++
3 files changed, 91 insertions(+)
create mode 100644 drivers/leds/leds-asus-transformer-ec.c
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 597d7a79c988..bda06dc145d7 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -120,6 +120,17 @@ config LEDS_OSRAM_AMS_AS3668
To compile this driver as a module, choose M here: the module
will be called leds-as3668.
+config LEDS_ASUS_TRANSFORMER_EC
+ tristate "LED Support for Asus Transformer charging LED"
+ depends on LEDS_CLASS
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ This option enables support for charging indicator on
+ Asus Transformer's Pad and it's Dock.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-asus-transformer-ec.
+
config LEDS_AW200XX
tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 8fdb45d5b439..d5395c3f1124 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
obj-$(CONFIG_LEDS_APU) += leds-apu.o
obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
obj-$(CONFIG_LEDS_AS3668) += leds-as3668.o
+obj-$(CONFIG_LEDS_ASUS_TRANSFORMER_EC) += leds-asus-transformer-ec.o
obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
diff --git a/drivers/leds/leds-asus-transformer-ec.c b/drivers/leds/leds-asus-transformer-ec.c
new file mode 100644
index 000000000000..3186038e3be7
--- /dev/null
+++ b/drivers/leds/leds-asus-transformer-ec.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/err.h>
+#include <linux/leds.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+static void asus_ec_led_set_brightness_amber(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ const struct asusec_info *ec = dev_get_drvdata(led->dev->parent);
+
+ if (brightness)
+ asus_ec_set_ctl_bits(ec, ASUSEC_CTL_LED_AMBER);
+ else
+ asus_ec_clear_ctl_bits(ec, ASUSEC_CTL_LED_AMBER);
+}
+
+static void asus_ec_led_set_brightness_green(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ const struct asusec_info *ec = dev_get_drvdata(led->dev->parent);
+
+ if (brightness)
+ asus_ec_set_ctl_bits(ec, ASUSEC_CTL_LED_GREEN);
+ else
+ asus_ec_clear_ctl_bits(ec, ASUSEC_CTL_LED_GREEN);
+}
+
+static int asus_ec_led_probe(struct platform_device *pdev)
+{
+ struct asusec_info *ec = cell_to_ec(pdev);
+ struct device *dev = &pdev->dev;
+ struct led_classdev *amber_led, *green_led;
+ int ret;
+
+ platform_set_drvdata(pdev, ec);
+
+ amber_led = devm_kzalloc(dev, sizeof(*amber_led), GFP_KERNEL);
+ if (!amber_led)
+ return -ENOMEM;
+
+ amber_led->name = devm_kasprintf(dev, GFP_KERNEL, "%s::amber", ec->name);
+ amber_led->max_brightness = 1;
+ amber_led->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ amber_led->brightness_set = asus_ec_led_set_brightness_amber;
+
+ ret = devm_led_classdev_register(dev, amber_led);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register amber LED\n");
+
+ green_led = devm_kzalloc(dev, sizeof(*green_led), GFP_KERNEL);
+ if (!green_led)
+ return -ENOMEM;
+
+ green_led->name = devm_kasprintf(dev, GFP_KERNEL, "%s::green", ec->name);
+ green_led->max_brightness = 1;
+ green_led->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ green_led->brightness_set = asus_ec_led_set_brightness_green;
+
+ ret = devm_led_classdev_register(dev, green_led);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register green LED\n");
+
+ return 0;
+}
+
+static struct platform_driver asus_ec_led_driver = {
+ .driver.name = "asus-transformer-ec-led",
+ .probe = asus_ec_led_probe,
+};
+module_platform_driver(asus_ec_led_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("ASUS Transformer's charging LED driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v5 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys
From: Svyatoslav Ryhel @ 2026-03-04 18:57 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: <20260304185751.83494-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Add support for multimedia top button row of ASUS Transformer's Mobile
Dock keyboard. Driver is made that function keys (F1-F12) are used by
default which suits average Linux use better and with pressing
ScreenLock + AltGr function keys layout is switched to multimedia keys.
Since this only modifies codes sent by asus-ec-keys it doesn't affect
normal keyboards at all.
Co-developed-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/input/keyboard/Kconfig | 10 +
drivers/input/keyboard/Makefile | 1 +
.../input/keyboard/asus-transformer-ec-keys.c | 272 ++++++++++++++++++
3 files changed, 283 insertions(+)
create mode 100644 drivers/input/keyboard/asus-transformer-ec-keys.c
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 2ff4fef322c2..4e577e5cf216 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -89,6 +89,16 @@ config KEYBOARD_APPLESPI
To compile this driver as a module, choose M here: the
module will be called applespi.
+config KEYBOARD_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's Mobile Dock multimedia keys"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y here if you want to use multimedia keys present on Asus
+ Transformer's Mobile Dock.
+
+ To compile this driver as a module, choose M here: the
+ module will be called asus-transformer-ec-keys.
+
config KEYBOARD_ATARI
tristate "Atari keyboard"
depends on ATARI
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 2d906e14f3e2..575edb0e8eb4 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_KEYBOARD_ADP5585) += adp5585-keys.o
obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o
obj-$(CONFIG_KEYBOARD_AMIGA) += amikbd.o
obj-$(CONFIG_KEYBOARD_APPLESPI) += applespi.o
+obj-$(CONFIG_KEYBOARD_ASUS_TRANSFORMER_EC) += asus-transformer-ec-keys.o
obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o
obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o
obj-$(CONFIG_KEYBOARD_BCM) += bcm-keypad.o
diff --git a/drivers/input/keyboard/asus-transformer-ec-keys.c b/drivers/input/keyboard/asus-transformer-ec-keys.c
new file mode 100644
index 000000000000..02516ccb0b12
--- /dev/null
+++ b/drivers/input/keyboard/asus-transformer-ec-keys.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define ASUSEC_EXT_KEY_CODES 0x20
+
+struct asus_ec_keys_data {
+ struct notifier_block nb;
+ struct asusec_info *ec;
+ struct input_dev *xidev;
+ bool special_key_pressed;
+ bool special_key_mode;
+ unsigned short keymap[ASUSEC_EXT_KEY_CODES * 2];
+};
+
+static void asus_ec_input_event(struct input_handle *handle,
+ unsigned int event_type,
+ unsigned int event_code, int value)
+{
+ struct asus_ec_keys_data *priv = handle->handler->private;
+
+ /* Store special key state */
+ if (event_type == EV_KEY && event_code == KEY_RIGHTALT)
+ priv->special_key_pressed = !!value;
+}
+
+static int asus_ec_input_connect(struct input_handler *handler, struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_handle *handle;
+ int error;
+
+ handle = kzalloc_obj(*handle);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = "asusec-media-handler";
+
+ error = input_register_handle(handle);
+ if (error)
+ goto err_free_handle;
+
+ error = input_open_device(handle);
+ if (error)
+ goto err_unregister_handle;
+
+ return 0;
+
+ err_unregister_handle:
+ input_unregister_handle(handle);
+ err_free_handle:
+ kfree(handle);
+
+ return error;
+}
+
+static void asus_ec_input_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+static const struct input_device_id asus_ec_input_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_KEY) },
+ },
+ { }
+};
+
+static struct input_handler asus_ec_input_handler = {
+ .name = "asusec-media-handler",
+ .event = asus_ec_input_event,
+ .connect = asus_ec_input_connect,
+ .disconnect = asus_ec_input_disconnect,
+ .id_table = asus_ec_input_ids,
+};
+
+static const unsigned short asus_ec_dock_ext_keys[] = {
+ /* Function keys [0x00 - 0x19] */
+ [0x01] = KEY_DELETE,
+ [0x02] = KEY_F1,
+ [0x03] = KEY_F2,
+ [0x04] = KEY_F3,
+ [0x05] = KEY_F4,
+ [0x06] = KEY_F5,
+ [0x07] = KEY_F6,
+ [0x08] = KEY_F7,
+ [0x10] = KEY_F8,
+ [0x11] = KEY_F9,
+ [0x12] = KEY_F10,
+ [0x13] = KEY_F11,
+ [0x14] = KEY_F12,
+ [0x15] = KEY_MUTE,
+ [0x16] = KEY_VOLUMEDOWN,
+ [0x17] = KEY_VOLUMEUP,
+ /* Multimedia keys [0x20 - 0x39] */
+ [0x21] = KEY_SCREENLOCK,
+ [0x22] = KEY_WLAN,
+ [0x23] = KEY_BLUETOOTH,
+ [0x24] = KEY_TOUCHPAD_TOGGLE,
+ [0x25] = KEY_BRIGHTNESSDOWN,
+ [0x26] = KEY_BRIGHTNESSUP,
+ [0x27] = KEY_BRIGHTNESS_AUTO,
+ [0x28] = KEY_PRINT,
+ [0x30] = KEY_WWW,
+ [0x31] = KEY_CONFIG,
+ [0x32] = KEY_PREVIOUSSONG,
+ [0x33] = KEY_PLAYPAUSE,
+ [0x34] = KEY_NEXTSONG,
+ [0x35] = KEY_MUTE,
+ [0x36] = KEY_VOLUMEDOWN,
+ [0x37] = KEY_VOLUMEUP,
+};
+
+static void asus_ec_keys_report_key(struct input_dev *dev, unsigned int code,
+ unsigned int key, bool value)
+{
+ input_event(dev, EV_MSC, MSC_SCAN, code);
+ input_report_key(dev, key, value);
+ input_sync(dev);
+}
+
+static int asus_ec_keys_process_key(struct input_dev *dev, u8 code)
+{
+ struct asus_ec_keys_data *priv = dev_get_drvdata(dev->dev.parent);
+ unsigned int key = 0;
+
+ if (code == 0)
+ return NOTIFY_DONE;
+
+ /* Flip special key mode state when pressing key 1 with special key pressed */
+ if (priv->special_key_pressed && code == 1) {
+ priv->special_key_mode = !priv->special_key_mode;
+ return NOTIFY_DONE;
+ }
+
+ /*
+ * Relocate code to second "page" if pressed state XOR's mode state
+ * This way special key will invert the current mode
+ */
+ if (priv->special_key_mode ^ priv->special_key_pressed)
+ code += ASUSEC_EXT_KEY_CODES;
+
+ if (code < dev->keycodemax) {
+ unsigned short *map = dev->keycode;
+
+ key = map[code];
+ }
+
+ if (!key)
+ key = KEY_UNKNOWN;
+
+ asus_ec_keys_report_key(dev, code, key, 1);
+ asus_ec_keys_report_key(dev, code, key, 0);
+
+ return NOTIFY_OK;
+}
+
+static int asus_ec_keys_notify(struct notifier_block *nb,
+ unsigned long action, void *data_)
+{
+ struct asus_ec_keys_data *priv = container_of(nb, struct asus_ec_keys_data, nb);
+ u8 *data = data_;
+
+ if (action & ASUSEC_SMI_MASK)
+ return NOTIFY_DONE;
+
+ if (action & ASUSEC_SCI_MASK)
+ return asus_ec_keys_process_key(priv->xidev, data[2]);
+
+ return NOTIFY_DONE;
+}
+
+static void asus_ec_keys_setup_keymap(struct asus_ec_keys_data *priv)
+{
+ struct input_dev *dev = priv->xidev;
+ unsigned int i;
+
+ BUILD_BUG_ON(ARRAY_SIZE(priv->keymap) < ARRAY_SIZE(asus_ec_dock_ext_keys));
+
+ dev->keycode = priv->keymap;
+ dev->keycodesize = sizeof(*priv->keymap);
+ dev->keycodemax = ARRAY_SIZE(priv->keymap);
+
+ input_set_capability(dev, EV_MSC, MSC_SCAN);
+ input_set_capability(dev, EV_KEY, KEY_UNKNOWN);
+
+ for (i = 0; i < ARRAY_SIZE(asus_ec_dock_ext_keys); i++) {
+ unsigned int code = asus_ec_dock_ext_keys[i];
+
+ if (!code)
+ continue;
+
+ __set_bit(code, dev->keybit);
+ priv->keymap[i] = code;
+ }
+}
+
+static void asus_ec_input_handler_deregister(void *priv)
+{
+ input_unregister_handler(&asus_ec_input_handler);
+}
+
+static int asus_ec_keys_probe(struct platform_device *pdev)
+{
+ struct asusec_info *ec = cell_to_ec(pdev);
+ struct i2c_client *parent = to_i2c_client(pdev->dev.parent);
+ struct asus_ec_keys_data *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->ec = ec;
+
+ priv->xidev = devm_input_allocate_device(&pdev->dev);
+ if (!priv->xidev)
+ return -ENOMEM;
+
+ priv->xidev->name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "%s Keyboard Ext", ec->model);
+ priv->xidev->phys = devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "i2c-%u-%04x",
+ i2c_adapter_id(parent->adapter),
+ parent->addr);
+ asus_ec_keys_setup_keymap(priv);
+
+ ret = input_register_device(priv->xidev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register extension keys: %d\n",
+ ret);
+ return ret;
+ }
+
+ asus_ec_input_handler.private = priv;
+
+ ret = input_register_handler(&asus_ec_input_handler);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(&pdev->dev, asus_ec_input_handler_deregister,
+ priv);
+ if (ret)
+ return ret;
+
+ priv->nb.notifier_call = asus_ec_keys_notify;
+
+ return devm_asus_ec_register_notifier(pdev, &priv->nb);
+}
+
+static struct platform_driver asus_ec_keys_driver = {
+ .driver.name = "asus-transformer-ec-keys",
+ .probe = asus_ec_keys_probe,
+};
+module_platform_driver(asus_ec_keys_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer's multimedia keys driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v5 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad
From: Svyatoslav Ryhel @ 2026-03-04 18:57 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: <20260304185751.83494-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Add input driver for ASUS Transformer dock keyboard and touchpad.
Some keys in ASUS Dock report keycodes that don't make sense according to
their position, this patch modifies the incoming data that is sent to
serio to send proper scancodes.
Co-developed-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Ion Agorria <ion@agorria.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
drivers/input/serio/Kconfig | 15 ++
drivers/input/serio/Makefile | 1 +
drivers/input/serio/asus-transformer-ec-kbc.c | 147 ++++++++++++++++++
3 files changed, 163 insertions(+)
create mode 100644 drivers/input/serio/asus-transformer-ec-kbc.c
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index c7ef347a4dff..1ca17ba632cc 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -97,6 +97,21 @@ config SERIO_RPCKBD
To compile this driver as a module, choose M here: the
module will be called rpckbd.
+config SERIO_ASUS_TRANSFORMER_EC
+ tristate "Asus Transformer's Dock keyboard and touchpad controller"
+ depends on MFD_ASUS_TRANSFORMER_EC
+ help
+ Say Y here if you want to use the keyboard and/or touchpad on
+ Asus Transformed's Mobile Dock.
+
+ For keyboard support you also need atkbd driver.
+
+ For touchpad support you also need psmouse driver with Elantech
+ touchpad option enabled.
+
+ To compile this driver as a module, choose M here: the module will
+ be called asus-transformer-ec-kbc.
+
config SERIO_AMBAKMI
tristate "AMBA KMI keyboard controller"
depends on ARM_AMBA
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 6d97bad7b844..9ecf0d011863 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_SERIO_CT82C710) += ct82c710.o
obj-$(CONFIG_SERIO_RPCKBD) += rpckbd.o
obj-$(CONFIG_SERIO_SA1111) += sa1111ps2.o
obj-$(CONFIG_SERIO_AMBAKMI) += ambakmi.o
+obj-$(CONFIG_SERIO_ASUS_TRANSFORMER_EC) += asus-transformer-ec-kbc.o
obj-$(CONFIG_SERIO_Q40KBD) += q40kbd.o
obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o
obj-$(CONFIG_HP_SDC) += hp_sdc.o
diff --git a/drivers/input/serio/asus-transformer-ec-kbc.c b/drivers/input/serio/asus-transformer-ec-kbc.c
new file mode 100644
index 000000000000..47fd6e48c989
--- /dev/null
+++ b/drivers/input/serio/asus-transformer-ec-kbc.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/i8042.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/serio.h>
+
+struct asus_ec_kbc_data {
+ struct notifier_block nb;
+ struct asusec_info *ec;
+ struct serio *sdev[2];
+};
+
+static int asus_ec_kbc_notify(struct notifier_block *nb,
+ unsigned long action, void *data_)
+{
+ struct asus_ec_kbc_data *priv = container_of(nb, struct asus_ec_kbc_data, nb);
+ unsigned int port_idx, n;
+ u8 *data = data_;
+
+ if (action & (ASUSEC_SMI_MASK | ASUSEC_SCI_MASK))
+ return NOTIFY_DONE;
+ else if (action & ASUSEC_AUX_MASK)
+ port_idx = 1;
+ else if (action & (ASUSEC_KBC_MASK | ASUSEC_KEY_MASK))
+ port_idx = 0;
+ else
+ return NOTIFY_DONE;
+
+ n = data[0] - 1;
+ data += 2;
+
+ /*
+ * We need to replace these incoming data for keys:
+ * RIGHT_META Press 0xE0 0x27 -> LEFT_ALT Press 0x11
+ * RIGHT_META Release 0xE0 0xF0 0x27 -> LEFT_ALT Release 0xF0 0x11
+ * COMPOSE Press 0xE0 0x2F -> RIGHT_META Press 0xE0 0x27
+ * COMPOSE Release 0xE0 0xF0 0x2F -> RIGHT_META Release 0xE0 0xF0 0x27
+ */
+
+ if (port_idx == 0 && n >= 2 && data[0] == 0xE0) {
+ if (n == 3 && data[1] == 0xF0) {
+ switch (data[2]) {
+ case 0x27:
+ data[0] = 0xF0;
+ data[1] = 0x11;
+ n = 2;
+ break;
+ case 0x2F:
+ data[2] = 0x27;
+ break;
+ }
+ } else if (n == 2) {
+ switch (data[1]) {
+ case 0x27:
+ data[0] = 0x11;
+ n = 1;
+ break;
+ case 0x2F:
+ data[1] = 0x27;
+ break;
+ }
+ }
+ }
+
+ while (n--)
+ serio_interrupt(priv->sdev[port_idx], *data++, 0);
+
+ return NOTIFY_OK;
+}
+
+static int asus_ec_serio_write(struct serio *port, unsigned char data)
+{
+ const struct asusec_info *ec = port->port_data;
+
+ return asus_ec_i2c_command(ec, (data << 8) | port->id.extra);
+}
+
+static void asus_ec_serio_remove(void *data)
+{
+ serio_unregister_port(data);
+}
+
+static int asus_ec_register_serio(struct platform_device *pdev, int idx,
+ const char *name, int cmd)
+{
+ struct asus_ec_kbc_data *priv = platform_get_drvdata(pdev);
+ struct i2c_client *parent = to_i2c_client(pdev->dev.parent);
+ struct serio *port = kzalloc_obj(*port);
+
+ if (!port)
+ return -ENOMEM;
+
+ priv->sdev[idx] = port;
+ port->dev.parent = &pdev->dev;
+ port->id.type = SERIO_8042;
+ port->id.extra = cmd & 0xFF;
+ port->write = asus_ec_serio_write;
+ port->port_data = (void *)priv->ec;
+ snprintf(port->name, sizeof(port->name), "%s %s",
+ priv->ec->model, name);
+ snprintf(port->phys, sizeof(port->phys), "i2c-%u-%04x/serio%d",
+ i2c_adapter_id(parent->adapter), parent->addr, idx);
+
+ serio_register_port(port);
+
+ return devm_add_action_or_reset(&pdev->dev, asus_ec_serio_remove, port);
+}
+
+static int asus_ec_kbc_probe(struct platform_device *pdev)
+{
+ struct asusec_info *ec = cell_to_ec(pdev);
+ struct asus_ec_kbc_data *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->ec = ec;
+
+ ret = asus_ec_register_serio(pdev, 0, "Keyboard", 0);
+ if (ret < 0)
+ return ret;
+
+ ret = asus_ec_register_serio(pdev, 1, "Touchpad", I8042_CMD_AUX_SEND);
+ if (ret < 0)
+ return ret;
+
+ priv->nb.notifier_call = asus_ec_kbc_notify;
+
+ return devm_asus_ec_register_notifier(pdev, &priv->nb);
+}
+
+static struct platform_driver asus_ec_kbc_driver = {
+ .driver.name = "asus-transformer-ec-kbc",
+ .probe = asus_ec_kbc_probe,
+};
+module_platform_driver(asus_ec_kbc_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ASUS Transformer's Dock keyboard and touchpad controller driver");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH v5 2/7] mfd: Add driver for ASUS Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-03-04 18:57 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: <20260304185751.83494-1-clamor95@gmail.com>
From: Michał Mirosław <mirq-linux@rere.qmqm.pl>
Support Nuvoton NPCE795-based ECs as used in Asus Transformer TF201,
TF300T, TF300TG, TF300TL and TF700T pad and dock, as well as TF101 dock
and TF600T, P1801-T and TF701T pad. This is a glue driver handling
detection and common operations for EC's functions.
Co-developed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
Signed-off-by: Michał Mirosław <mirq-linux@rere.qmqm.pl>
---
drivers/mfd/Kconfig | 14 +
drivers/mfd/Makefile | 1 +
drivers/mfd/asus-transformer-ec.c | 762 ++++++++++++++++++++++++
include/linux/mfd/asus-transformer-ec.h | 162 +++++
4 files changed, 939 insertions(+)
create mode 100644 drivers/mfd/asus-transformer-ec.c
create mode 100644 include/linux/mfd/asus-transformer-ec.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..5aa4facfd2df 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -137,6 +137,20 @@ config MFD_AAT2870_CORE
additional drivers must be enabled in order to use the
functionality of the device.
+config MFD_ASUS_TRANSFORMER_EC
+ tristate "ASUS Transformer's embedded controller"
+ depends on I2C && OF
+ help
+ Support ECs found in ASUS Transformer's Pad and Mobile Dock.
+
+ This provides shared glue for functional part drivers:
+ asus-transformer-ec-kbc, asus-transformer-ec-keys,
+ leds-asus-transformer-ec, asus-transformer-ec-battery
+ and asus-transformer-ec-charger.
+
+ This driver can also be built as a module. If so, the module
+ will be called asus-transformer-ec.
+
config MFD_AT91_USART
tristate "AT91 USART Driver"
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..fd80088d8a9a 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o
obj-$(CONFIG_MFD_88PM886_PMIC) += 88pm886.o
obj-$(CONFIG_MFD_ACT8945A) += act8945a.o
obj-$(CONFIG_MFD_SM501) += sm501.o
+obj-$(CONFIG_MFD_ASUS_TRANSFORMER_EC) += asus-transformer-ec.o
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
diff --git a/drivers/mfd/asus-transformer-ec.c b/drivers/mfd/asus-transformer-ec.c
new file mode 100644
index 000000000000..aa3b9e31f3fc
--- /dev/null
+++ b/drivers/mfd/asus-transformer-ec.c
@@ -0,0 +1,762 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/array_size.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/asus-transformer-ec.h>
+#include <linux/mfd/core.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#define ASUSEC_RSP_BUFFER_SIZE 8
+
+struct asus_ec_chip_data {
+ const char *name;
+ const struct mfd_cell *mfd_devices;
+ unsigned int num_devices;
+ bool clr_fmode; /* clear Factory Mode bit in EC control register */
+};
+
+struct asus_ec_data {
+ struct asusec_info info;
+ struct mutex ecreq_lock; /* prevent simultaneous access */
+ struct gpio_desc *ecreq;
+ struct i2c_client *self;
+ const struct asus_ec_chip_data *data;
+ char ec_data[DOCKRAM_ENTRY_BUFSIZE];
+ bool logging_disabled;
+};
+
+struct dockram_ec_data {
+ struct mutex ctl_lock; /* prevent simultaneous access */
+ char ctl_data[DOCKRAM_ENTRY_BUFSIZE];
+};
+
+#define to_ec_data(ec) \
+ container_of(ec, struct asus_ec_data, info)
+
+/**
+ * asus_dockram_read - Read a register from the DockRAM device.
+ * @client: Handle to the DockRAM device.
+ * @reg: Register to read.
+ * @buf: Byte array into which data will be read; must be large enough to
+ * hold the data returned by the DockRAM.
+ *
+ * This executes the DockRAM read based on the SMBus "block read" protocol
+ * or its emulation. It extracts DOCKRAM_ENTRY_SIZE bytes from the set
+ * register address.
+ *
+ * Returns a negative errno code else zero on success.
+ */
+int asus_dockram_read(struct i2c_client *client, int reg, char *buf)
+{
+ struct device *dev = &client->dev;
+ int ret;
+
+ memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE);
+ ret = i2c_smbus_read_i2c_block_data(client, reg,
+ DOCKRAM_ENTRY_BUFSIZE, buf);
+ if (ret < 0)
+ return ret;
+
+ if (buf[0] > DOCKRAM_ENTRY_SIZE) {
+ dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n",
+ DOCKRAM_ENTRY_BUFSIZE, buf, ret);
+ return -EPROTO;
+ }
+
+ dev_dbg(dev, "got data; buffer: %*ph; ret: %d\n",
+ DOCKRAM_ENTRY_BUFSIZE, buf, ret);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(asus_dockram_read);
+
+/**
+ * asus_dockram_write - Write a byte array to a register of the DockRAM device.
+ * @client: Handle to the DockRAM device.
+ * @reg: Register to write to.
+ * @buf: Byte array to be written (up to DOCKRAM_ENTRY_SIZE bytes).
+ *
+ * This executes the DockRAM write based on the SMBus "block write"
+ * protocol or its emulation. It writes DOCKRAM_ENTRY_SIZE bytes to the
+ * specified register address.
+ *
+ * Returns a negative errno code else zero on success.
+ */
+int asus_dockram_write(struct i2c_client *client, int reg, const char *buf)
+{
+ if (buf[0] > DOCKRAM_ENTRY_SIZE)
+ return -EINVAL;
+
+ dev_dbg(&client->dev, "sending data; buffer: %*ph\n", buf[0] + 1, buf);
+
+ return i2c_smbus_write_i2c_block_data(client, reg, buf[0] + 1, buf);
+}
+EXPORT_SYMBOL_GPL(asus_dockram_write);
+
+/**
+ * asus_dockram_access_ctl - Read from or write to the DockRAM control register.
+ * @client: Handle to the DockRAM device.
+ * @out: Pointer to a variable where the register value will be stored.
+ * @mask: Bitmask of bits to be cleared.
+ * @xor: Bitmask of bits to be set (via XOR).
+ *
+ * This performs a control register read if @out is provided and both @mask
+ * and @xor are zero. Otherwise, it performs a control register update if
+ * @mask and @xor are provided.
+ *
+ * Returns a negative errno code else zero on success.
+ */
+int asus_dockram_access_ctl(struct i2c_client *client, u64 *out, u64 mask,
+ u64 xor)
+{
+ struct dockram_ec_data *priv = i2c_get_clientdata(client);
+ char *buf = priv->ctl_data;
+ u64 val;
+ int ret = 0;
+
+ guard(mutex)(&priv->ctl_lock);
+
+ ret = asus_dockram_read(client, ASUSEC_DOCKRAM_CONTROL, buf);
+ if (ret < 0)
+ goto exit;
+
+ if (buf[0] != ASUSEC_CTL_SIZE) {
+ ret = -EPROTO;
+ goto exit;
+ }
+
+ val = get_unaligned_le64(buf + 1);
+
+ if (out)
+ *out = val;
+
+ if (mask || xor) {
+ put_unaligned_le64((val & ~mask) ^ xor, buf + 1);
+ ret = asus_dockram_write(client, ASUSEC_DOCKRAM_CONTROL, buf);
+ }
+
+exit:
+ if (ret < 0)
+ dev_err(&client->dev, "Failed to access control flags: %d\n",
+ ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(asus_dockram_access_ctl);
+
+static void asus_ec_remove_notifier(struct device *dev, void *res)
+{
+ struct asusec_info *ec = dev_get_drvdata(dev->parent);
+ struct notifier_block **nb = res;
+
+ blocking_notifier_chain_unregister(&ec->notify_list, *nb);
+}
+
+/**
+ * devm_asus_ec_register_notifier - Managed registration of notifier to an
+ * ASUS EC blocking notifier chain.
+ * @pdev: Device requesting the notifier (used for resource management).
+ * @nb: Notifier block to be registered.
+ *
+ * Register a notifier to the ASUS EC blocking notifier chain. The notifier
+ * will be automatically unregistered when the requesting device is detached.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int devm_asus_ec_register_notifier(struct platform_device *pdev,
+ struct notifier_block *nb)
+{
+ struct asusec_info *ec = dev_get_drvdata(pdev->dev.parent);
+ struct notifier_block **res;
+ int ret;
+
+ res = devres_alloc(asus_ec_remove_notifier, sizeof(*res), GFP_KERNEL);
+ if (!res)
+ return -ENOMEM;
+
+ *res = nb;
+ ret = blocking_notifier_chain_register(&ec->notify_list, nb);
+ if (ret) {
+ devres_free(res);
+ return ret;
+ }
+
+ devres_add(&pdev->dev, res);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_asus_ec_register_notifier);
+
+static int asus_ec_signal_request(const struct asusec_info *ec)
+{
+ struct asus_ec_data *priv = to_ec_data(ec);
+
+ guard(mutex)(&priv->ecreq_lock);
+
+ dev_dbg(&priv->self->dev, "EC request\n");
+
+ gpiod_set_value_cansleep(priv->ecreq, 1);
+ msleep(50);
+
+ gpiod_set_value_cansleep(priv->ecreq, 0);
+ msleep(200);
+
+ return 0;
+}
+
+static int asus_ec_write(struct asus_ec_data *priv, u16 data)
+{
+ int ret = i2c_smbus_write_word_data(priv->self, ASUSEC_WRITE_BUF, data);
+
+ dev_dbg(&priv->self->dev, "EC write: %04x, ret = %d\n", data, ret);
+ return ret;
+}
+
+static int asus_ec_read(struct asus_ec_data *priv, bool in_irq)
+{
+ int ret = i2c_smbus_read_i2c_block_data(priv->self, ASUSEC_READ_BUF,
+ sizeof(priv->ec_data),
+ priv->ec_data);
+
+ dev_dbg(&priv->self->dev, "EC read: %*ph, ret = %d%s\n",
+ sizeof(priv->ec_data), priv->ec_data,
+ ret, in_irq ? "; in irq" : "");
+
+ return ret;
+}
+
+/**
+ * asus_ec_i2c_command - Send a 16-bit command to the ASUS EC.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @data: The 16-bit command (word) to be sent.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int asus_ec_i2c_command(const struct asusec_info *ec, u16 data)
+{
+ return asus_ec_write(to_ec_data(ec), data);
+}
+EXPORT_SYMBOL_GPL(asus_ec_i2c_command);
+
+static void asus_ec_clear_buffer(struct asus_ec_data *priv)
+{
+ int retry = ASUSEC_RSP_BUFFER_SIZE;
+
+ while (retry--) {
+ if (asus_ec_read(priv, false) < 0)
+ continue;
+
+ if (priv->ec_data[1] & ASUSEC_OBF_MASK)
+ continue;
+
+ break;
+ }
+}
+
+static int asus_ec_log_info(struct asus_ec_data *priv, unsigned int reg,
+ const char *name, char **out)
+{
+ char buf[DOCKRAM_ENTRY_BUFSIZE];
+ int ret;
+
+ ret = asus_dockram_read(priv->info.dockram, reg, buf);
+ if (ret < 0)
+ return ret;
+
+ if (!priv->logging_disabled)
+ dev_info(&priv->self->dev, "%-14s: %.*s\n", name,
+ buf[0], buf + 1);
+
+ if (out)
+ *out = kstrndup(buf + 1, buf[0], GFP_KERNEL);
+
+ return 0;
+}
+
+static int asus_ec_reset(struct asus_ec_data *priv)
+{
+ int retry, ret;
+
+ for (retry = 0; retry < 3; retry++) {
+ ret = asus_ec_write(priv, 0);
+ if (!ret)
+ return 0;
+
+ msleep(300);
+ }
+
+ return ret;
+}
+
+static int asus_ec_magic_debug(struct asus_ec_data *priv)
+{
+ u64 flag;
+ int ret;
+
+ ret = asus_ec_get_ctl(&priv->info, &flag);
+ if (ret < 0)
+ return ret;
+
+ flag &= ASUSEC_CTL_SUSB_MODE;
+ dev_info(&priv->self->dev, "EC FW behaviour: %s\n",
+ flag ? "susb on when receive ec_req" :
+ "susb on when system wakeup");
+
+ return 0;
+}
+
+static int asus_ec_set_factory_mode(struct asus_ec_data *priv, bool on)
+{
+ dev_info(&priv->self->dev, "Entering %s mode.\n", on ? "factory" :
+ "normal");
+ return asus_ec_update_ctl(&priv->info, ASUSEC_CTL_FACTORY_MODE,
+ on ? ASUSEC_CTL_FACTORY_MODE : 0);
+}
+
+static void asus_ec_handle_smi(struct asus_ec_data *priv, unsigned int code);
+
+static irqreturn_t asus_ec_interrupt(int irq, void *dev_id)
+{
+ struct asus_ec_data *priv = dev_id;
+ unsigned long notify_action;
+ int ret;
+
+ ret = asus_ec_read(priv, true);
+ if (ret <= 0 || !(priv->ec_data[1] & ASUSEC_OBF_MASK))
+ return IRQ_NONE;
+
+ notify_action = priv->ec_data[1];
+ if (notify_action & ASUSEC_SMI_MASK) {
+ unsigned int code = priv->ec_data[2];
+
+ asus_ec_handle_smi(priv, code);
+
+ notify_action |= code << 8;
+ dev_dbg(&priv->self->dev, "SMI code: 0x%02x\n", code);
+ }
+
+ blocking_notifier_call_chain(&priv->info.notify_list,
+ notify_action, priv->ec_data);
+
+ return IRQ_HANDLED;
+}
+
+static int asus_ec_detect(struct asus_ec_data *priv)
+{
+ char *model = NULL;
+ int ret;
+
+ ret = asus_ec_reset(priv);
+ if (ret)
+ goto err_exit;
+
+ asus_ec_clear_buffer(priv);
+
+ ret = asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_MODEL, "model", &model);
+ if (ret)
+ goto err_exit;
+
+ ret = asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_FW, "FW version", NULL);
+ if (ret)
+ goto err_exit;
+
+ ret = asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_CFGFMT, "Config format", NULL);
+ if (ret)
+ goto err_exit;
+
+ ret = asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_HW, "HW version", NULL);
+ if (ret)
+ goto err_exit;
+
+ priv->logging_disabled = true;
+
+ ret = asus_ec_magic_debug(priv);
+ if (ret)
+ goto err_exit;
+
+ priv->info.model = model;
+ priv->info.name = priv->data->name;
+
+ if (priv->data->clr_fmode)
+ asus_ec_set_factory_mode(priv, false);
+
+err_exit:
+ if (ret)
+ dev_err(&priv->self->dev, "failed to access EC: %d\n", ret);
+
+ return ret;
+}
+
+static void asus_ec_handle_smi(struct asus_ec_data *priv, unsigned int code)
+{
+ dev_dbg(&priv->self->dev, "SMI interrupt: 0x%02x\n", code);
+
+ switch (code) {
+ case ASUSEC_SMI_HANDSHAKE:
+ case ASUSEC_SMI_RESET:
+ asus_ec_detect(priv);
+ break;
+ }
+}
+
+static ssize_t dockram_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct i2c_client *client = filp->private_data;
+ unsigned int reg, rsize;
+ ssize_t n_read = 0, val;
+ loff_t off = *ppos;
+ char *data;
+ int ret;
+
+ reg = off / DOCKRAM_ENTRY_SIZE;
+ off %= DOCKRAM_ENTRY_SIZE;
+ rsize = DOCKRAM_ENTRIES * DOCKRAM_ENTRY_SIZE;
+
+ if (!count)
+ return 0;
+
+ data = kmalloc(DOCKRAM_ENTRY_BUFSIZE, GFP_KERNEL);
+
+ while (reg < DOCKRAM_ENTRIES) {
+ unsigned int len = DOCKRAM_ENTRY_SIZE - off;
+
+ if (len > rsize)
+ len = rsize;
+
+ ret = asus_dockram_read(client, reg, data);
+ if (ret < 0) {
+ if (!n_read)
+ n_read = ret;
+ break;
+ }
+
+ val = copy_to_user(buf, data + 1 + off, len);
+ if (val == len)
+ return -EFAULT;
+
+ *ppos += len;
+ n_read += len;
+
+ if (len == rsize)
+ break;
+
+ rsize -= len;
+ buf += len;
+ off = 0;
+ ++reg;
+ }
+
+ kfree(data);
+
+ return n_read;
+}
+
+static int dockram_write_one(struct i2c_client *client, int reg,
+ const char __user *buf, size_t count)
+{
+ struct dockram_ec_data *priv = i2c_get_clientdata(client);
+ int ret;
+
+ if (!count || count > DOCKRAM_ENTRY_SIZE)
+ return -EINVAL;
+ if (buf[0] != count - 1)
+ return -EINVAL;
+
+ guard(mutex)(&priv->ctl_lock);
+
+ priv->ctl_data[0] = (u8)count;
+ memcpy(priv->ctl_data + 1, buf, count);
+ ret = asus_dockram_write(client, reg, priv->ctl_data);
+
+ return ret;
+}
+
+static ssize_t dockram_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct i2c_client *client = filp->private_data;
+ unsigned int reg;
+ loff_t off = *ppos;
+ int ret;
+
+ if (off % DOCKRAM_ENTRY_SIZE != 0)
+ return -EINVAL;
+
+ reg = off / DOCKRAM_ENTRY_SIZE;
+ if (reg >= DOCKRAM_ENTRIES)
+ return -EINVAL;
+
+ ret = dockram_write_one(client, reg, buf, count);
+
+ return ret < 0 ? ret : count;
+}
+
+static const struct debugfs_short_fops dockram_fops = {
+ .read = dockram_read,
+ .write = dockram_write,
+ .llseek = default_llseek,
+};
+
+static int control_reg_get(void *client, u64 *val)
+{
+ return asus_dockram_access_ctl(client, val, 0, 0);
+}
+
+static int control_reg_set(void *client, u64 val)
+{
+ return asus_dockram_access_ctl(client, NULL, ~0ull, val);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(control_reg_fops, control_reg_get,
+ control_reg_set, "%016llx\n");
+
+static int ec_request_set(void *ec, u64 val)
+{
+ if (val)
+ asus_ec_signal_request(ec);
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(ec_request_fops, NULL, ec_request_set, "%llu\n");
+
+static int ec_irq_set(void *ec, u64 val)
+{
+ struct asus_ec_data *priv = to_ec_data(ec);
+
+ if (val)
+ irq_wake_thread(priv->self->irq, priv);
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(ec_irq_fops, NULL, ec_irq_set, "%llu\n");
+
+static void asus_ec_debugfs_remove(void *debugfs_root)
+{
+ debugfs_remove_recursive(debugfs_root);
+}
+
+static void devm_asus_ec_debugfs_init(struct device *dev)
+{
+ struct asusec_info *ec = dev_get_drvdata(dev);
+ struct asus_ec_data *priv = to_ec_data(ec);
+ struct dentry *debugfs_root, *dockram_dir;
+ char *name = devm_kasprintf(dev, GFP_KERNEL, "asus-ec-%s",
+ priv->data->name);
+
+ debugfs_root = debugfs_create_dir(name, NULL);
+ dockram_dir = debugfs_create_dir("dockram", debugfs_root);
+
+ debugfs_create_file("ec_irq", 0200, debugfs_root, ec,
+ &ec_irq_fops);
+ debugfs_create_file("ec_request", 0200, debugfs_root, ec,
+ &ec_request_fops);
+ debugfs_create_file("control_reg", 0644, dockram_dir,
+ priv->info.dockram, &control_reg_fops);
+ debugfs_create_file("dockram", 0644, dockram_dir,
+ priv->info.dockram, &dockram_fops);
+
+ devm_add_action_or_reset(dev, asus_ec_debugfs_remove, debugfs_root);
+}
+
+static void asus_ec_release_dockram_dev(void *client)
+{
+ i2c_unregister_device(client);
+}
+
+static struct i2c_client *devm_asus_dockram_get(struct device *dev)
+{
+ struct i2c_client *parent = to_i2c_client(dev);
+ struct i2c_client *dockram;
+ struct dockram_ec_data *priv;
+ int ret;
+
+ dockram = i2c_new_ancillary_device(parent, "dockram",
+ parent->addr + 2);
+ if (IS_ERR(dockram))
+ return dockram;
+
+ ret = devm_add_action_or_reset(dev, asus_ec_release_dockram_dev,
+ dockram);
+ if (ret)
+ return ERR_PTR(ret);
+
+ priv = devm_kzalloc(&dockram->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
+
+ i2c_set_clientdata(dockram, priv);
+ mutex_init(&priv->ctl_lock);
+
+ return dockram;
+}
+
+static int asus_ec_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct asus_ec_data *priv;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
+ return dev_err_probe(dev, -ENXIO,
+ "I2C bus is missing required SMBus block mode support\n");
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->data = device_get_match_data(dev);
+ if (!priv->data)
+ return -ENODEV;
+
+ i2c_set_clientdata(client, priv);
+ priv->self = client;
+
+ priv->info.dockram = devm_asus_dockram_get(dev);
+ if (IS_ERR(priv->info.dockram))
+ return dev_err_probe(dev, PTR_ERR(priv->info.dockram),
+ "failed to get dockram\n");
+
+ priv->ecreq = devm_gpiod_get(dev, "request", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->ecreq))
+ return dev_err_probe(dev, PTR_ERR(priv->ecreq),
+ "failed to get request GPIO\n");
+
+ BLOCKING_INIT_NOTIFIER_HEAD(&priv->info.notify_list);
+ mutex_init(&priv->ecreq_lock);
+
+ asus_ec_signal_request(&priv->info);
+
+ ret = asus_ec_detect(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to detect EC version\n");
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ &asus_ec_interrupt,
+ IRQF_ONESHOT | IRQF_SHARED,
+ client->name, priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register IRQ\n");
+
+ /* Parent I2C controller uses DMA, ASUS EC and child devices do not */
+ client->dev.coherent_dma_mask = 0;
+ client->dev.dma_mask = &client->dev.coherent_dma_mask;
+
+ if (IS_ENABLED(CONFIG_DEBUG_FS))
+ devm_asus_ec_debugfs_init(dev);
+
+ return devm_mfd_add_devices(dev, 0, priv->data->mfd_devices,
+ priv->data->num_devices, NULL, 0, NULL);
+}
+
+static const struct mfd_cell asus_ec_sl101_dock_mfd_devices[] = {
+ {
+ .name = "asus-transformer-ec-kbc",
+ },
+};
+
+static const struct asus_ec_chip_data asus_ec_sl101_dock_data = {
+ .name = "dock",
+ .mfd_devices = asus_ec_sl101_dock_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_sl101_dock_mfd_devices),
+ .clr_fmode = false,
+};
+
+static const struct mfd_cell asus_ec_tf101_dock_mfd_devices[] = {
+ {
+ .name = "asus-transformer-ec-battery",
+ .id = 1,
+ }, {
+ .name = "asus-transformer-ec-charger",
+ .id = 1,
+ }, {
+ .name = "asus-transformer-ec-led",
+ .id = 1,
+ }, {
+ .name = "asus-transformer-ec-keys",
+ }, {
+ .name = "asus-transformer-ec-kbc",
+ },
+};
+
+static const struct asus_ec_chip_data asus_ec_tf101_dock_data = {
+ .name = "dock",
+ .mfd_devices = asus_ec_tf101_dock_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_tf101_dock_mfd_devices),
+ .clr_fmode = false,
+};
+
+static const struct mfd_cell asus_ec_tf201_pad_mfd_devices[] = {
+ {
+ .name = "asus-transformer-ec-battery",
+ .id = 0,
+ }, {
+ .name = "asus-transformer-ec-led",
+ .id = 0,
+ },
+};
+
+static const struct asus_ec_chip_data asus_ec_tf201_pad_data = {
+ .name = "pad",
+ .mfd_devices = asus_ec_tf201_pad_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_tf201_pad_mfd_devices),
+ .clr_fmode = true,
+};
+
+static const struct mfd_cell asus_ec_tf600t_pad_mfd_devices[] = {
+ {
+ .name = "asus-transformer-ec-battery",
+ .id = 0,
+ }, {
+ .name = "asus-transformer-ec-charger",
+ .id = 0,
+ }, {
+ .name = "asus-transformer-ec-led",
+ .id = 0,
+ },
+};
+
+static const struct asus_ec_chip_data asus_ec_tf600t_pad_data = {
+ .name = "pad",
+ .mfd_devices = asus_ec_tf600t_pad_mfd_devices,
+ .num_devices = ARRAY_SIZE(asus_ec_tf600t_pad_mfd_devices),
+ .clr_fmode = true,
+};
+
+static const struct of_device_id asus_ec_match[] = {
+ { .compatible = "asus,sl101-ec-dock", .data = &asus_ec_sl101_dock_data },
+ { .compatible = "asus,tf101-ec-dock", .data = &asus_ec_tf101_dock_data },
+ { .compatible = "asus,tf201-ec-pad", .data = &asus_ec_tf201_pad_data },
+ { .compatible = "asus,tf600t-ec-pad", .data = &asus_ec_tf600t_pad_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, asus_ec_match);
+
+static struct i2c_driver asus_ec_driver = {
+ .driver = {
+ .name = "asus-transformer-ec",
+ .of_match_table = asus_ec_match,
+ },
+ .probe = asus_ec_probe,
+};
+module_i2c_driver(asus_ec_driver);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
+MODULE_DESCRIPTION("ASUS Transformer's EC driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/asus-transformer-ec.h b/include/linux/mfd/asus-transformer-ec.h
new file mode 100644
index 000000000000..0a72de40352e
--- /dev/null
+++ b/include/linux/mfd/asus-transformer-ec.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __MFD_ASUS_TRANSFORMER_EC_H
+#define __MFD_ASUS_TRANSFORMER_EC_H
+
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+
+struct i2c_client;
+
+struct asusec_info {
+ const char *model;
+ const char *name;
+ struct i2c_client *dockram;
+ struct workqueue_struct *wq;
+ struct blocking_notifier_head notify_list;
+};
+
+#define DOCKRAM_ENTRIES 0x100
+#define DOCKRAM_ENTRY_SIZE 32
+#define DOCKRAM_ENTRY_BUFSIZE (DOCKRAM_ENTRY_SIZE + 1)
+
+/* interrupt sources */
+#define ASUSEC_OBF_MASK BIT(0)
+#define ASUSEC_KEY_MASK BIT(2)
+#define ASUSEC_KBC_MASK BIT(3)
+#define ASUSEC_AUX_MASK BIT(5)
+#define ASUSEC_SCI_MASK BIT(6)
+#define ASUSEC_SMI_MASK BIT(7)
+
+/* SMI notification codes */
+#define ASUSEC_SMI_POWER_NOTIFY 0x31 /* [un]plugging USB cable */
+#define ASUSEC_SMI_HANDSHAKE 0x50 /* response to ec_req edge */
+#define ASUSEC_SMI_WAKE 0x53
+#define ASUSEC_SMI_RESET 0x5f
+#define ASUSEC_SMI_ADAPTER_EVENT 0x60 /* [un]plugging charger to dock */
+#define ASUSEC_SMI_BACKLIGHT_ON 0x63
+#define ASUSEC_SMI_AUDIO_DOCK_IN 0x70
+
+#define ASUSEC_SMI_ACTION(code) (ASUSEC_SMI_MASK | ASUSEC_OBF_MASK | \
+ (ASUSEC_SMI_##code << 8))
+
+/* control register [0x0a] layout */
+#define ASUSEC_CTL_SIZE 8
+
+/*
+ * EC reports power from 40-pin connector in the LSB of the control
+ * register. The following values have been observed (xor 0x02):
+ *
+ * PAD-ec no-plug 0x40 / PAD-ec DOCK 0x20 / DOCK-ec no-plug 0x40
+ * PAD-ec AC 0x25 / PAD-ec DOCK+AC 0x24 / DOCK-ec AC 0x25
+ * PAD-ec USB 0x45 / PAD-ec DOCK+USB 0x24 / DOCK-ec USB 0x41
+ */
+
+#define ASUSEC_CTL_DIRECT_POWER_SOURCE BIT_ULL(0)
+#define ASUSEC_STAT_CHARGING BIT_ULL(2)
+#define ASUSEC_CTL_FULL_POWER_SOURCE BIT_ULL(5)
+#define ASUSEC_CTL_SUSB_MODE BIT_ULL(9)
+#define ASUSEC_CMD_SUSPEND_S3 BIT_ULL(33)
+#define ASUSEC_CTL_TEST_DISCHARGE BIT_ULL(35)
+#define ASUSEC_CMD_SUSPEND_INHIBIT BIT_ULL(37)
+#define ASUSEC_CTL_FACTORY_MODE BIT_ULL(38)
+#define ASUSEC_CTL_KEEP_AWAKE BIT_ULL(39)
+#define ASUSEC_CTL_USB_CHARGE BIT_ULL(40)
+#define ASUSEC_CTL_LED_BLINK BIT_ULL(40)
+#define ASUSEC_CTL_LED_AMBER BIT_ULL(41)
+#define ASUSEC_CTL_LED_GREEN BIT_ULL(42)
+#define ASUSEC_CMD_SWITCH_HDMI BIT_ULL(56)
+#define ASUSEC_CMD_WIN_SHUTDOWN BIT_ULL(62)
+
+#define ASUSEC_DOCKRAM_INFO_MODEL 0x01
+#define ASUSEC_DOCKRAM_INFO_FW 0x02
+#define ASUSEC_DOCKRAM_INFO_CFGFMT 0x03
+#define ASUSEC_DOCKRAM_INFO_HW 0x04
+#define ASUSEC_DOCKRAM_CONTROL 0x0a
+#define ASUSEC_DOCKRAM_BATT_CTL 0x14
+
+#define ASUSEC_WRITE_BUF 0x64
+#define ASUSEC_READ_BUF 0x6a
+
+/* dockram comm */
+int asus_dockram_read(struct i2c_client *client, int reg, char *buf);
+int asus_dockram_write(struct i2c_client *client, int reg, const char *buf);
+int asus_dockram_access_ctl(struct i2c_client *client,
+ u64 *out, u64 mask, u64 xor);
+
+/* EC public API */
+
+/**
+ * cell_to_ec - Request the shared ASUS EC structure via a subdevice's pdev.
+ * @pdev: EC subdevice pdev requesting access to the shared ASUS EC structure.
+ *
+ * Returns a pointer to the asusec_info structure.
+ */
+static inline struct asusec_info *cell_to_ec(struct platform_device *pdev)
+{
+ return dev_get_drvdata(pdev->dev.parent);
+}
+
+/**
+ * asus_ec_get_ctl - Read from the DockRAM control register.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @out: Pointer to the variable where the register value will be stored.
+ *
+ * Performs a control register read and stores the value in @out.
+ *
+ * Return: 0 on success, or a negative errno code on failure.
+ */
+static inline int asus_ec_get_ctl(const struct asusec_info *ec, u64 *out)
+{
+ return asus_dockram_access_ctl(ec->dockram, out, 0, 0);
+}
+
+/**
+ * asus_ec_update_ctl - Update the DockRAM control register.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @mask: Bitmask of bits to be cleared.
+ * @xor: Bitmask of bits to be toggled or set (via XOR).
+ *
+ * Performs a read-modify-write update on the control register using
+ * the provided @mask and @xor values.
+ *
+ * Return: 0 on success, or a negative errno code on failure.
+ */
+static inline int asus_ec_update_ctl(const struct asusec_info *ec,
+ u64 mask, u64 xor)
+{
+ return asus_dockram_access_ctl(ec->dockram, NULL, mask, xor);
+}
+
+/**
+ * asus_ec_set_ctl_bits - Sets bits of the DockRAM control register.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @mask: Bitmask of bits to be set.
+ *
+ * Sets bits of the control register using the provided @mask value.
+ *
+ * Return: 0 on success, or a negative errno code on failure.
+ */
+static inline int asus_ec_set_ctl_bits(const struct asusec_info *ec, u64 mask)
+{
+ return asus_dockram_access_ctl(ec->dockram, NULL, mask, mask);
+}
+
+/**
+ * asus_ec_clear_ctl_bits - Clears bits of the DockRAM control register.
+ * @ec: Pointer to the shared ASUS EC structure.
+ * @mask: Bitmask of bits to be cleared.
+ *
+ * Clears bits of the control register using the provided @mask value.
+ *
+ * Return: 0 on success, or a negative errno code on failure.
+ */
+static inline int asus_ec_clear_ctl_bits(const struct asusec_info *ec, u64 mask)
+{
+ return asus_dockram_access_ctl(ec->dockram, NULL, mask, 0);
+}
+
+int asus_ec_i2c_command(const struct asusec_info *ec, u16 data);
+int devm_asus_ec_register_notifier(struct platform_device *dev,
+ struct notifier_block *nb);
+#endif /* __MFD_ASUS_TRANSFORMER_EC_H */
--
2.51.0
^ permalink raw reply related
* [PATCH v5 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC
From: Svyatoslav Ryhel @ 2026-03-04 18:57 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: <20260304185751.83494-1-clamor95@gmail.com>
Document embedded controller used in ASUS Transformer device series.
Signed-off-by: Svyatoslav Ryhel <clamor95@gmail.com>
---
.../asus,tf201-ec-pad.yaml | 119 ++++++++++++++++++
1 file changed, 119 insertions(+)
create mode 100644 Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
diff --git a/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
new file mode 100644
index 000000000000..60b6375864aa
--- /dev/null
+++ b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
@@ -0,0 +1,119 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/embedded-controller/asus,tf201-ec-pad.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ASUS Transformer's Embedded Controller
+
+description:
+ Several Nuvoton based Embedded Controllers attached to an I2C bus,
+ running a custom ASUS firmware, specific to the ASUS Transformer
+ device series.
+
+maintainers:
+ - Svyatoslav Ryhel <clamor95@gmail.com>
+
+properties:
+ compatible:
+ description:
+ The 'pad' suffix is used for the controller within the tablet, while
+ the 'dock' suffix refers to the controller in the mobile dock keyboard.
+ oneOf:
+ - enum:
+ - asus,sl101-ec-dock
+ - asus,tf101-ec-dock
+ - asus,tf201-ec-pad
+ - asus,tf600t-ec-dock
+ - asus,tf600t-ec-pad
+
+ - items:
+ - enum:
+ - asus,tf101g-ec-dock
+ - asus,tf201-ec-dock
+ - asus,tf300t-ec-dock
+ - asus,tf300tg-ec-dock
+ - asus,tf300tl-ec-dock
+ - asus,tf700t-ec-dock
+ - const: asus,tf101-ec-dock
+
+ - items:
+ - enum:
+ - asus,tf300t-ec-pad
+ - asus,tf300tg-ec-pad
+ - asus,tf300tl-ec-pad
+ - asus,tf700t-ec-pad
+ - const: asus,tf201-ec-pad
+
+ - items:
+ - enum:
+ - asus,tf701t-ec-dock
+ - const: asus,tf600t-ec-dock
+
+ - items:
+ - enum:
+ - asus,p1801-t-ec-pad
+ - asus,tf701t-ec-pad
+ - const: asus,tf600t-ec-pad
+
+ reg:
+ description:
+ The ASUS Transformer EC has a main I2C address and an associated
+ DockRAM device, which provides power-related functions for the
+ embedded controller. Both addresses are required for operation.
+ minItems: 2
+
+ reg-names:
+ items:
+ - const: ec
+ - const: dockram
+
+ interrupts:
+ maxItems: 1
+
+ request-gpios:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+
+allOf:
+ - $ref: /schemas/power/supply/power-supply.yaml
+ - if:
+ properties:
+ compatible:
+ not:
+ contains:
+ const: asus,tf600t-ec-dock
+ then:
+ required:
+ - interrupts
+ - request-gpios
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ embedded-controller@19 {
+ compatible = "asus,tf201-ec-dock", "asus,tf101-ec-dock";
+ reg = <0x19>, <0x1b>;
+ reg-names = "ec", "dockram";
+
+ interrupt-parent = <&gpio>;
+ interrupts = <151 IRQ_TYPE_LEVEL_LOW>;
+
+ request-gpios = <&gpio 134 GPIO_ACTIVE_LOW>;
+
+ monitored-battery = <&dock_battery>;
+ };
+ };
+...
--
2.51.0
^ permalink raw reply related
* [PATCH v5 0/7] mfd: Add support for Asus Transformer embedded controller
From: Svyatoslav Ryhel @ 2026-03-04 18:57 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
---
Michał Mirosław (6):
mfd: Add driver for ASUS Transformer embedded controller
input: serio: Add driver for ASUS Transformer dock keyboard and
touchpad
input: keyboard: Add driver for ASUS Transformer dock multimedia keys
leds: Add driver for ASUS Transformer LEDs
power: supply: Add driver for ASUS Transformer battery
power: supply: Add charger driver for Asus Transformers
Svyatoslav Ryhel (1):
dt-bindings: embedded-controller: document ASUS Transformer EC
.../asus,tf201-ec-pad.yaml | 119 +++
drivers/input/keyboard/Kconfig | 10 +
drivers/input/keyboard/Makefile | 1 +
.../input/keyboard/asus-transformer-ec-keys.c | 272 +++++++
drivers/input/serio/Kconfig | 15 +
drivers/input/serio/Makefile | 1 +
drivers/input/serio/asus-transformer-ec-kbc.c | 147 ++++
drivers/leds/Kconfig | 11 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-asus-transformer-ec.c | 79 ++
drivers/mfd/Kconfig | 14 +
drivers/mfd/Makefile | 1 +
drivers/mfd/asus-transformer-ec.c | 762 ++++++++++++++++++
drivers/power/supply/Kconfig | 22 +
drivers/power/supply/Makefile | 2 +
.../supply/asus-transformer-ec-battery.c | 272 +++++++
.../supply/asus-transformer-ec-charger.c | 193 +++++
include/linux/mfd/asus-transformer-ec.h | 162 ++++
18 files changed, 2084 insertions(+)
create mode 100644 Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-pad.yaml
create mode 100644 drivers/input/keyboard/asus-transformer-ec-keys.c
create mode 100644 drivers/input/serio/asus-transformer-ec-kbc.c
create mode 100644 drivers/leds/leds-asus-transformer-ec.c
create mode 100644 drivers/mfd/asus-transformer-ec.c
create mode 100644 drivers/power/supply/asus-transformer-ec-battery.c
create mode 100644 drivers/power/supply/asus-transformer-ec-charger.c
create mode 100644 include/linux/mfd/asus-transformer-ec.h
--
2.51.0
^ permalink raw reply
* [PATCH v2] HID: quirks: add HID_QUIRK_ALWAYS_POLL for 8BitDo Pro 3
From: leo vriska @ 2026-03-04 18:36 UTC (permalink / raw)
To: linux-input; +Cc: leo vriska, Jiri Kosina, Benjamin Tissoires, linux-kernel
According to a mailing list report [1], this controller's predecessor
has the same issue. However, it uses the xpad driver instead of HID, so
this quirk wouldn't apply.
[1]: https://lore.kernel.org/linux-input/unufo3$det$1@ciao.gmane.io/
Signed-off-by: leo vriska <leo@60228.dev>
---
drivers/hid/hid-ids.h | 3 +++
drivers/hid/hid-quirks.c | 1 +
2 files changed, 4 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4ab7640b119a..8c1f41a9e5f6 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -22,6 +22,9 @@
#define USB_DEVICE_ID_3M2256 0x0502
#define USB_DEVICE_ID_3M3266 0x0506
+#define USB_VENDOR_ID_8BITDO 0x2dc8
+#define USB_DEVICE_ID_8BITDO_PRO_3 0x6009
+
#define USB_VENDOR_ID_A4TECH 0x09da
#define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006
#define USB_DEVICE_ID_A4TECH_X5_005D 0x000a
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 3217e436c052..f6be3ffee023 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -25,6 +25,7 @@
*/
static const struct hid_device_id hid_quirks[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_8BITDO, USB_DEVICE_ID_8BITDO_PRO_3), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD), HID_QUIRK_BADPAD },
{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR), HID_QUIRK_BADPAD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE), HID_QUIRK_ALWAYS_POLL },
--
2.53.0
^ permalink raw reply related
* Re: [PATCH 1/2] HID: quirks: add quirk to always keep device open
From: leo vriska @ 2026-03-04 18:25 UTC (permalink / raw)
To: Benjamin Tissoires; +Cc: linux-input, Jiri Kosina, linux-kernel
In-Reply-To: <aafeCVaf6ebaxqAQ@beelink>
Hi,
> It seems 2/2 is dealing with a USB device. So why not simply make use of
> HID_QUIRK_ALWAYS_POLL?
It seems like I misunderstood what HID_QUIRK_ALWAYS_POLL did when I was
reading the code before writing this patch. Sorry about that! It does
seem like a cleaner solution to this problem, and I'll send an updated
patch that just enables it for this device.
Thanks,
leo vriska
^ permalink raw reply
* Re: [6.18.] ThinkPad T14 Gen 2 AMD (LEN2073) - Synaptics touchpad remains PS/2, intertouch=1 ineffective, lost sync events
From: Rácz Máté @ 2026-03-04 15:02 UTC (permalink / raw)
To: linux-input, linux-i2c
In-Reply-To: <CANkKV92bTD11PCXYzz60WSxcP-0UCM8xtnY4_WraEwQx6TMjaw@mail.gmail.com>
This might be related to the earlier report from 2021:
https://lore.kernel.org/linux-input/3d6f7f74-3214-4c03-b352-a2a0d27ea42b@amd.com/
and a similar report from 2025:
https://lore.kernel.org/linux-input/b2d0af40-876e-4a2d-99a2-236b583e9497@gmail.com/
The 2021 report describes incorrect SMBus address detection with
i2c_piix4 on ThinkPad P14s Gen 2 (AMD), resulting in RMI4/SMBus not
functioning correctly and the device remaining in PS/2 mode.
On my system (LEN2073), the behaviour appears very similar:
- intertouch has no effect
- "SMBus companion is not ready yet" in dmesg
- device stays in PS/2 mode
It seems possible that the same underlying piix4 SMBus detection
issue is still present.
^ permalink raw reply
* [PATCH] HID: amd_sfh: don't log error when device discovery fails with -EOPNOTSUPP
From: Maximilian Pezzullo via B4 Relay @ 2026-03-04 8:25 UTC (permalink / raw)
To: Basavaraj Natikar, Jiri Kosina, Benjamin Tissoires
Cc: Basavaraj Natikar, linux-input, linux-kernel, Casey Croy,
Maximilian Pezzullo
From: Maximilian Pezzullo <maximilianpezzullo@gmail.com>
When sensor discovery fails on systems without AMD SFH sensors, the
code already emits a warning via dev_warn() in amd_sfh_hid_client_init().
The subsequent dev_err() in sfh_init_work() for the same -EOPNOTSUPP
return value is redundant and causes unnecessary alarm.
Suppress the dev_err() for -EOPNOTSUPP to avoid confusing users who
have no AMD SFH sensors.
Fixes: 2105e8e00da4 ("HID: amd_sfh: Improve boot time when SFH is available")
Reported-by: Casey Croy <ccroy@bugzilla.kernel.org>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221099
Signed-off-by: Maximilian Pezzullo <maximilianpezzullo@gmail.com>
---
drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
index 1d9f955573aa..4b81cebdc335 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
@@ -413,7 +413,8 @@ static void sfh_init_work(struct work_struct *work)
rc = amd_sfh_hid_client_init(mp2);
if (rc) {
amd_sfh_clear_intr(mp2);
- dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc);
+ if (rc != -EOPNOTSUPP)
+ dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc);
return;
}
---
base-commit: af4e9ef3d78420feb8fe58cd9a1ab80c501b3c08
change-id: 20260304-amd-sfh-suppress-eopnotsupp-err-0be59878e2fe
Best regards,
--
Maximilian Pezzullo <maximilianpezzullo@gmail.com>
^ permalink raw reply related
* Re: [PATCH 1/2] HID: quirks: add quirk to always keep device open
From: Benjamin Tissoires @ 2026-03-04 7:24 UTC (permalink / raw)
To: leo vriska; +Cc: linux-input, Jiri Kosina, linux-kernel
In-Reply-To: <20260304033245.445671-1-leo@60228.dev>
On Mar 03 2026, leo vriska wrote:
> Some devices expect the host to open the device shortly after it is
> connected. If this does not occur, they may freeze or disconnect. A
> quirk allows these devices to function properly without userspace hacks.
>
> The existing hid-axff driver solves this problem for some generic
> controllers. This implementation is modelled after that driver, which
> still needs to exist for force feedback on the controllers that use it.
It seems 2/2 is dealing with a USB device. So why not simply make use of
HID_QUIRK_ALWAYS_POLL?
Cheers,
Benjamin
>
> Signed-off-by: leo vriska <leo@60228.dev>
> ---
> drivers/hid/hid-generic.c | 23 ++++++++++++++++++++++-
> include/linux/hid.h | 2 ++
> 2 files changed, 24 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
> index c2de916747de..0595e653b7e7 100644
> --- a/drivers/hid/hid-generic.c
> +++ b/drivers/hid/hid-generic.c
> @@ -67,7 +67,27 @@ static int hid_generic_probe(struct hid_device *hdev,
> if (ret)
> return ret;
>
> - return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + if (hdev->quirks & HID_QUIRK_KEEP_OPEN) {
> + ret = hid_hw_open(hdev);
> + if (ret) {
> + hid_hw_stop(hdev);
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static void hid_generic_remove(struct hid_device *hdev)
> +{
> + if (hdev->quirks & HID_QUIRK_KEEP_OPEN)
> + hid_hw_close(hdev);
> +
> + hid_hw_stop(hdev);
> }
>
> static int hid_generic_reset_resume(struct hid_device *hdev)
> @@ -89,6 +109,7 @@ static struct hid_driver hid_generic = {
> .id_table = hid_table,
> .match = hid_generic_match,
> .probe = hid_generic_probe,
> + .remove = hid_generic_remove,
> .reset_resume = hid_generic_reset_resume,
> };
> module_hid_driver(hid_generic);
> diff --git a/include/linux/hid.h b/include/linux/hid.h
> index 2990b9f94cb5..9d0ab7c217f3 100644
> --- a/include/linux/hid.h
> +++ b/include/linux/hid.h
> @@ -388,6 +388,7 @@ struct hid_item {
> * | @HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE:
> * | @HID_QUIRK_IGNORE_SPECIAL_DRIVER
> * | @HID_QUIRK_POWER_ON_AFTER_BACKLIGHT
> + * | @HID_QUIRK_KEEP_OPEN:
> * | @HID_QUIRK_FULLSPEED_INTERVAL:
> * | @HID_QUIRK_NO_INIT_REPORTS:
> * | @HID_QUIRK_NO_IGNORE:
> @@ -416,6 +417,7 @@ struct hid_item {
> #define HID_QUIRK_NOINVERT BIT(21)
> #define HID_QUIRK_IGNORE_SPECIAL_DRIVER BIT(22)
> #define HID_QUIRK_POWER_ON_AFTER_BACKLIGHT BIT(23)
> +#define HID_QUIRK_KEEP_OPEN BIT(24)
> #define HID_QUIRK_FULLSPEED_INTERVAL BIT(28)
> #define HID_QUIRK_NO_INIT_REPORTS BIT(29)
> #define HID_QUIRK_NO_IGNORE BIT(30)
> --
> 2.53.0
>
^ permalink raw reply
* Re: [BUG] HP Pavilion x360: Built-in Keyboard and Touchpad disabled permanently after screen removal (Stuck in Tablet Mode)
From: Dmitry Torokhov @ 2026-03-04 5:16 UTC (permalink / raw)
To: Ngon; +Cc: linux-input
In-Reply-To: <CAF2ktaUNWWED4g+wqa0OuFbXSaVV7Lt-9Kw3co9KE+PYswNW=A@mail.gmail.com>
Hi,
On Tue, Mar 03, 2026 at 03:32:42AM +0700, Ngon wrote:
> Hi Dmitry and Linux-Input developers,
>
> I am reporting a hardware state issue on the HP Pavilion x360
> Convertible 14-cd0xxx that causes the built-in keyboard and
> touchpad to become completely non-functional under Linux.
>
>
> The Situation:
> I have physically removed the original laptop screen (using the
> device as a headless mini-PC). Because the lid/hall sensors and
> rotation sensors are located in the screen assembly, the Embedded
> Controller (EC) or BIOS seems to default to a permanent
> "Tablet Mode" or "Lid Closed" state.
So based on the above I can not see how this is classified as a "BUG".
You modified the hardware to the state it is was not designed to operate
in, so it should not be a surprise that some things are not working.
>
>
> Observations:
> 1. Running libinput debug-events clearly shows:
> event20 SWITCH_TOGGLE switch tablet-mode state 1
> 2. The keyboard (AT Raw Set 2) and touchpad (SynPS/2 Synaptics) are
> detected by the kernel but do not emit any events
> (verified via libinput debug-events).
> 3. On Windows, the keyboard still works despite driver warnings,
> likely due to a proprietary HP override.
> 4. On Linux, common kernel parameters like i8042.nopnp,
> i8042.dumbkbd, or button.lid_init_state=open do not restore
> functionality. Blacklisting intel_vbtn and hp_wmi also does not
> force the system back to "Laptop Mode."
With blacklisted intel_vbtn and hp_wmi do you still see an event device
reporting the tablet mode switch?
>
>
> Question:
> Is there a way to implement a kernel quirk to force-ignore the
> Tablet Mode switch for this specific model, or a way to bypass
> the EC's hard-lock on the i8042 bus when sensors are missing?
Do you see anything from i8042 in dmesg after you do
echo 1 > /sys/module/i8042/parameters/debug
If not then you'll need help from folks at HP to see if there is a
EC command re-enabling keyboard and mouse.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 04/10] Input: stmfts - disable regulators when power on fails
From: Dmitry Torokhov @ 2026-03-04 5:01 UTC (permalink / raw)
To: david
Cc: Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio, Petr Hodina, linux-input,
linux-stm32, linux-arm-kernel, linux-kernel, Krzysztof Kozlowski,
devicetree, linux-arm-msm, phone-devel
In-Reply-To: <20260301-stmfts5-v1-4-22c458b9ac68@ixit.cz>
On Sun, Mar 01, 2026 at 06:51:18PM +0100, David Heidelberg via B4 Relay wrote:
> From: David Heidelberg <david@ixit.cz>
>
> We must power off regulators after failing at power on phase.
>
> Signed-off-by: David Heidelberg <david@ixit.cz>
> ---
> drivers/input/touchscreen/stmfts.c | 13 +++++++++----
> 1 file changed, 9 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
> index db2dd0bb59fcc..f4e5f1b3ce796 100644
> --- a/drivers/input/touchscreen/stmfts.c
> +++ b/drivers/input/touchscreen/stmfts.c
> @@ -558,7 +558,7 @@ static int stmfts_power_on(struct stmfts_data *sdata)
>
> err = stmfts_read_system_info(sdata);
> if (err)
> - return err;
> + goto power_off;
>
> enable_irq(sdata->client->irq);
>
> @@ -566,11 +566,11 @@ static int stmfts_power_on(struct stmfts_data *sdata)
>
> err = stmfts_command(sdata, STMFTS_SYSTEM_RESET);
> if (err)
> - return err;
> + goto power_off;
>
> err = stmfts_command(sdata, STMFTS_SLEEP_OUT);
> if (err)
> - return err;
> + goto power_off;
>
> /* optional tuning */
> err = stmfts_command(sdata, STMFTS_MS_CX_TUNING);
> @@ -586,7 +586,7 @@ static int stmfts_power_on(struct stmfts_data *sdata)
>
> err = stmfts_command(sdata, STMFTS_FULL_FORCE_CALIBRATION);
> if (err)
> - return err;
> + goto power_off;
>
> /*
> * At this point no one is using the touchscreen
> @@ -595,6 +595,11 @@ static int stmfts_power_on(struct stmfts_data *sdata)
> (void) i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
>
> return 0;
> +
> +power_off:
> + regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
> + sdata->supplies);
> + return err;
Maybe wrap everything below enabling the supplies into
stmfts_configute() or something to avoid bunch of gotos to power off on
error?
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 03/10] Input: stmfts - abstract reading information from the firmware
From: Dmitry Torokhov @ 2026-03-04 4:59 UTC (permalink / raw)
To: david
Cc: Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio, Petr Hodina, linux-input,
linux-stm32, linux-arm-kernel, linux-kernel, Krzysztof Kozlowski,
devicetree, linux-arm-msm, phone-devel
In-Reply-To: <20260301-stmfts5-v1-3-22c458b9ac68@ixit.cz>
On Sun, Mar 01, 2026 at 06:51:17PM +0100, David Heidelberg via B4 Relay wrote:
> From: David Heidelberg <david@ixit.cz>
>
> Improves readability and makes splitting power on function in following
> commit easier.
> ---
Missing signed-off-by...
Thanks.
--
Dmitry
^ permalink raw reply
* [hid:for-7.1/lenovo 14/16] mips-linux-ld: drivers/hid/hid-lenovo-go-s.o:(.data.rgb_enabled+0x0): multiple definition of `rgb_enabled'; drivers/hid/hid-lenovo-go.o:(.data.rgb_enabled+0x0): first defined here
From: kernel test robot @ 2026-03-04 4:58 UTC (permalink / raw)
To: Derek J. Clark; +Cc: oe-kbuild-all, linux-input, Jiri Kosina, Mark Pearson
tree: https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git for-7.1/lenovo
head: d2c424e80caf8237bda4c94bc2e25398967243f9
commit: 550752e2c153663c3a374b048535654073007c90 [14/16] HID: hid-lenovo-go-s: Add RGB LED control interface
config: mips-allyesconfig (https://download.01.org/0day-ci/archive/20260304/202603041208.Jh3JVNU2-lkp@intel.com/config)
compiler: mips-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260304/202603041208.Jh3JVNU2-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603041208.Jh3JVNU2-lkp@intel.com/
All errors (new ones prefixed by >>):
mips-linux-ld: drivers/hid/hid-lenovo-go-s.o:(.bss.drvdata+0x0): multiple definition of `drvdata'; drivers/hid/hid-lenovo-go.o:(.bss.drvdata+0x0): first defined here
>> mips-linux-ld: drivers/hid/hid-lenovo-go-s.o:(.data.rgb_enabled+0x0): multiple definition of `rgb_enabled'; drivers/hid/hid-lenovo-go.o:(.data.rgb_enabled+0x0): first defined here
mips-linux-ld: drivers/hid/hid-lenovo-go-s.o:(.data.gamepad_mode+0x0): multiple definition of `gamepad_mode'; drivers/hid/hid-lenovo-go.o:(.data.gamepad_mode+0x0): first defined here
mips-linux-ld: drivers/hid/hid-lenovo-go-s.o:(.data.touchpad_enabled+0x0): multiple definition of `touchpad_enabled'; drivers/hid/hid-lenovo-go.o:(.data.touchpad_enabled+0x0): first defined here
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v4 3/4] Input: aw86938 - add driver for Awinic AW86938
From: Dmitry Torokhov @ 2026-03-04 4:56 UTC (permalink / raw)
To: Griffin Kroah-Hartman
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Luca Weiss, linux-input, devicetree, linux-kernel,
linux-arm-msm
In-Reply-To: <20260302-aw86938-driver-v4-3-92c865df9cca@fairphone.com>
On Mon, Mar 02, 2026 at 11:50:27AM +0100, Griffin Kroah-Hartman wrote:
> Add support for the I2C-connected Awinic AW86938 LRA haptic driver.
>
> The AW86938 has a similar but slightly different register layout. In
> particular, the boost mode register values.
> The AW86938 also has some extra features that aren't implemented
> in this driver yet.
>
> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v4 2/4] dt-bindings: input: awinic,aw86927: Add Awinic AW86938
From: Dmitry Torokhov @ 2026-03-04 4:56 UTC (permalink / raw)
To: Griffin Kroah-Hartman
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Luca Weiss, linux-input, devicetree, linux-kernel,
linux-arm-msm, Krzysztof Kozlowski
In-Reply-To: <20260302-aw86938-driver-v4-2-92c865df9cca@fairphone.com>
On Mon, Mar 02, 2026 at 11:50:26AM +0100, Griffin Kroah-Hartman wrote:
> Add bindings for the Awinic AW86938 haptic chip which can be found in
> smartphones. These two chips require a similar devicetree configuration,
> but have a register layout that's not 100% compatible.
> Still, because chip model is fully detectable via ID register, these
> chips can be documnented in the same file.
>
> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
Applied, thank you.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v4 1/4] Input: aw86927 - respect vibration magnitude levels
From: Dmitry Torokhov @ 2026-03-04 4:56 UTC (permalink / raw)
To: Griffin Kroah-Hartman
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson,
Konrad Dybcio, Luca Weiss, linux-input, devicetree, linux-kernel,
linux-arm-msm
In-Reply-To: <20260302-aw86938-driver-v4-1-92c865df9cca@fairphone.com>
On Mon, Mar 02, 2026 at 11:50:25AM +0100, Griffin Kroah-Hartman wrote:
> Previously the gain value was hardcoded. Take the magnitude passed via
> the input API and configure the gain register accordingly.
>
> Signed-off-by: Griffin Kroah-Hartman <griffin.kroah@fairphone.com>
> ---
> drivers/input/misc/aw86927.c | 14 +++++++-------
> 1 file changed, 7 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/input/misc/aw86927.c b/drivers/input/misc/aw86927.c
> index 8ad361239cfe3a888628b15e4dbdeed0c9ca3d1a..7f8cadda7c456d7b5448d1e23edf6e3f2918ba32 100644
> --- a/drivers/input/misc/aw86927.c
> +++ b/drivers/input/misc/aw86927.c
> @@ -180,7 +180,7 @@ struct aw86927_data {
> struct i2c_client *client;
> struct regmap *regmap;
> struct gpio_desc *reset_gpio;
> - bool running;
> + __u16 level;
Just u16 outside of headers exposed to the userspace.
Adjusted and applied, thank you.
--
Dmitry
^ permalink raw reply
* [PATCH 2/2] HID: quirks: add HID_QUIRK_KEEP_OPEN for 8BitDo Pro 3
From: leo vriska @ 2026-03-04 3:32 UTC (permalink / raw)
To: linux-input; +Cc: leo vriska, Jiri Kosina, Benjamin Tissoires, linux-kernel
In-Reply-To: <20260304033245.445671-1-leo@60228.dev>
According to a mailing list report [1], this controller's predecessor
has the same issue. However, it uses the xpad driver instead of HID, so
this quirk wouldn't apply.
[1]: https://lore.kernel.org/linux-input/unufo3$det$1@ciao.gmane.io/
Signed-off-by: leo vriska <leo@60228.dev>
---
drivers/hid/hid-ids.h | 3 +++
drivers/hid/hid-quirks.c | 1 +
2 files changed, 4 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 4ab7640b119a..8c1f41a9e5f6 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -22,6 +22,9 @@
#define USB_DEVICE_ID_3M2256 0x0502
#define USB_DEVICE_ID_3M3266 0x0506
+#define USB_VENDOR_ID_8BITDO 0x2dc8
+#define USB_DEVICE_ID_8BITDO_PRO_3 0x6009
+
#define USB_VENDOR_ID_A4TECH 0x09da
#define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006
#define USB_DEVICE_ID_A4TECH_X5_005D 0x000a
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 3217e436c052..b935bc9a061e 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -25,6 +25,7 @@
*/
static const struct hid_device_id hid_quirks[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_8BITDO, USB_DEVICE_ID_8BITDO_PRO_3), HID_QUIRK_KEEP_OPEN },
{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD), HID_QUIRK_BADPAD },
{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR), HID_QUIRK_BADPAD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE), HID_QUIRK_ALWAYS_POLL },
--
2.53.0
^ permalink raw reply related
* [PATCH 1/2] HID: quirks: add quirk to always keep device open
From: leo vriska @ 2026-03-04 3:32 UTC (permalink / raw)
To: linux-input; +Cc: leo vriska, Jiri Kosina, Benjamin Tissoires, linux-kernel
Some devices expect the host to open the device shortly after it is
connected. If this does not occur, they may freeze or disconnect. A
quirk allows these devices to function properly without userspace hacks.
The existing hid-axff driver solves this problem for some generic
controllers. This implementation is modelled after that driver, which
still needs to exist for force feedback on the controllers that use it.
Signed-off-by: leo vriska <leo@60228.dev>
---
drivers/hid/hid-generic.c | 23 ++++++++++++++++++++++-
include/linux/hid.h | 2 ++
2 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
index c2de916747de..0595e653b7e7 100644
--- a/drivers/hid/hid-generic.c
+++ b/drivers/hid/hid-generic.c
@@ -67,7 +67,27 @@ static int hid_generic_probe(struct hid_device *hdev,
if (ret)
return ret;
- return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ return ret;
+
+ if (hdev->quirks & HID_QUIRK_KEEP_OPEN) {
+ ret = hid_hw_open(hdev);
+ if (ret) {
+ hid_hw_stop(hdev);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void hid_generic_remove(struct hid_device *hdev)
+{
+ if (hdev->quirks & HID_QUIRK_KEEP_OPEN)
+ hid_hw_close(hdev);
+
+ hid_hw_stop(hdev);
}
static int hid_generic_reset_resume(struct hid_device *hdev)
@@ -89,6 +109,7 @@ static struct hid_driver hid_generic = {
.id_table = hid_table,
.match = hid_generic_match,
.probe = hid_generic_probe,
+ .remove = hid_generic_remove,
.reset_resume = hid_generic_reset_resume,
};
module_hid_driver(hid_generic);
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 2990b9f94cb5..9d0ab7c217f3 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -388,6 +388,7 @@ struct hid_item {
* | @HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE:
* | @HID_QUIRK_IGNORE_SPECIAL_DRIVER
* | @HID_QUIRK_POWER_ON_AFTER_BACKLIGHT
+ * | @HID_QUIRK_KEEP_OPEN:
* | @HID_QUIRK_FULLSPEED_INTERVAL:
* | @HID_QUIRK_NO_INIT_REPORTS:
* | @HID_QUIRK_NO_IGNORE:
@@ -416,6 +417,7 @@ struct hid_item {
#define HID_QUIRK_NOINVERT BIT(21)
#define HID_QUIRK_IGNORE_SPECIAL_DRIVER BIT(22)
#define HID_QUIRK_POWER_ON_AFTER_BACKLIGHT BIT(23)
+#define HID_QUIRK_KEEP_OPEN BIT(24)
#define HID_QUIRK_FULLSPEED_INTERVAL BIT(28)
#define HID_QUIRK_NO_INIT_REPORTS BIT(29)
#define HID_QUIRK_NO_IGNORE BIT(30)
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox