Hi, On Wed, Mar 04, 2026 at 08:57:51PM +0200, Svyatoslav Ryhel wrote: > From: Michał Mirosław > > Add support for charger detection capabilities found in the embedded > controller of ASUS Transformer devices. > > Suggested-by: Maxim Schwalm > Suggested-by: Svyatoslav Ryhel > Signed-off-by: Michał Mirosław > Signed-off-by: Svyatoslav Ryhel > --- Reviewed-by: Sebastian Reichel -- Sebastian > 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 > +#include > +#include > +#include > +#include > +#include > + > +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 "); > +MODULE_DESCRIPTION("ASUS Transformer Pad battery charger driver"); > +MODULE_LICENSE("GPL"); > -- > 2.51.0 > >