Ayaneo devices support charge inhibition via the EC. This inhibition
only works while the device is powered on, and resets between restarts.
However, it is maintained across suspend/resume cycles.
The EC does not support charge threshold control. Instead, userspace
software on Windows manually toggles charge inhibition depending on
battery level.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/Kconfig | 1 +
drivers/platform/x86/ayaneo-ec.c | 112 +++++++++++++++++++++++++++++++
2 files changed, 113 insertions(+)
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index b3beaff4b03a..a45449ae83f8 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -319,6 +319,7 @@ config ASUS_TF103C_DOCK
config AYANEO_EC
tristate "Ayaneo EC platform control"
depends on ACPI_EC
+ depends on ACPI_BATTERY
depends on HWMON
help
Enables support for the platform EC of Ayaneo devices. This
diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
index 108a23458a4f..697bb053a7d6 100644
--- a/drivers/platform/x86/ayaneo-ec.c
+++ b/drivers/platform/x86/ayaneo-ec.c
@@ -15,6 +15,8 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <acpi/battery.h>
#define AYANEO_PWM_ENABLE_REG 0x4A
#define AYANEO_PWM_REG 0x4B
@@ -23,17 +25,27 @@
#define AYANEO_FAN_REG 0x76
+#define EC_CHARGE_CONTROL_BEHAVIOURS \
+ (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))
+#define AYANEO_CHARGE_REG 0x1e
+#define AYANEO_CHARGE_VAL_AUTO 0xaa
+#define AYANEO_CHARGE_VAL_INHIBIT 0x55
+
struct ayaneo_ec_quirk {
bool has_fan_control;
+ bool has_charge_control;
};
struct ayaneo_ec_platform_data {
struct platform_device *pdev;
struct ayaneo_ec_quirk *quirks;
+ struct acpi_battery_hook battery_hook;
};
static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
.has_fan_control = true,
+ .has_charge_control = true,
};
static const struct dmi_system_id dmi_table[] = {
@@ -164,11 +176,102 @@ static const struct hwmon_chip_info ayaneo_ec_chip_info = {
.info = ayaneo_ec_sensors,
};
+static int ayaneo_psy_ext_get_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret;
+ u8 tmp;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ ret = ec_read(AYANEO_CHARGE_REG, &tmp);
+ if (ret)
+ return ret;
+
+ if (tmp == AYANEO_CHARGE_VAL_INHIBIT)
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ayaneo_psy_ext_set_prop(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ u8 raw_val;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ raw_val = AYANEO_CHARGE_VAL_AUTO;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+ raw_val = AYANEO_CHARGE_VAL_INHIBIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ec_write(AYANEO_CHARGE_REG, raw_val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ayaneo_psy_prop_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp)
+{
+ return true;
+}
+
+static const enum power_supply_property ayaneo_psy_ext_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+};
+
+static const struct power_supply_ext ayaneo_psy_ext = {
+ .name = "ayaneo-charge-control",
+ .properties = ayaneo_psy_ext_props,
+ .num_properties = ARRAY_SIZE(ayaneo_psy_ext_props),
+ .charge_behaviours = EC_CHARGE_CONTROL_BEHAVIOURS,
+ .get_property = ayaneo_psy_ext_get_prop,
+ .set_property = ayaneo_psy_ext_set_prop,
+ .property_is_writeable = ayaneo_psy_prop_is_writeable,
+};
+
+static int ayaneo_add_battery(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ struct ayaneo_ec_platform_data *data =
+ container_of(hook, struct ayaneo_ec_platform_data, battery_hook);
+
+ return power_supply_register_extension(battery, &ayaneo_psy_ext,
+ &data->pdev->dev, NULL);
+}
+
+static int ayaneo_remove_battery(struct power_supply *battery,
+ struct acpi_battery_hook *hook)
+{
+ power_supply_unregister_extension(battery, &ayaneo_psy_ext);
+ return 0;
+}
+
static int ayaneo_ec_probe(struct platform_device *pdev)
{
const struct dmi_system_id *dmi_entry;
struct ayaneo_ec_platform_data *data;
struct device *hwdev;
+ int ret;
dmi_entry = dmi_first_match(dmi_table);
if (!dmi_entry)
@@ -189,6 +292,15 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
return PTR_ERR(hwdev);
}
+ if (data->quirks->has_charge_control) {
+ data->battery_hook.add_battery = ayaneo_add_battery;
+ data->battery_hook.remove_battery = ayaneo_remove_battery;
+ data->battery_hook.name = "Ayaneo Battery";
+ ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
--
2.51.1
Am 31.10.25 um 17:36 schrieb Antheas Kapenekakis:
> Ayaneo devices support charge inhibition via the EC. This inhibition
> only works while the device is powered on, and resets between restarts.
> However, it is maintained across suspend/resume cycles.
>
> The EC does not support charge threshold control. Instead, userspace
> software on Windows manually toggles charge inhibition depending on
> battery level.
>
> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/Kconfig | 1 +
> drivers/platform/x86/ayaneo-ec.c | 112 +++++++++++++++++++++++++++++++
> 2 files changed, 113 insertions(+)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index b3beaff4b03a..a45449ae83f8 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -319,6 +319,7 @@ config ASUS_TF103C_DOCK
> config AYANEO_EC
> tristate "Ayaneo EC platform control"
> depends on ACPI_EC
> + depends on ACPI_BATTERY
> depends on HWMON
> help
> Enables support for the platform EC of Ayaneo devices. This
> diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> index 108a23458a4f..697bb053a7d6 100644
> --- a/drivers/platform/x86/ayaneo-ec.c
> +++ b/drivers/platform/x86/ayaneo-ec.c
> @@ -15,6 +15,8 @@
> #include <linux/kernel.h>
> #include <linux/module.h>
> #include <linux/platform_device.h>
> +#include <linux/power_supply.h>
> +#include <acpi/battery.h>
>
> #define AYANEO_PWM_ENABLE_REG 0x4A
> #define AYANEO_PWM_REG 0x4B
> @@ -23,17 +25,27 @@
>
> #define AYANEO_FAN_REG 0x76
>
> +#define EC_CHARGE_CONTROL_BEHAVIOURS \
> + (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
> + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))
I think POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE_AWAKE would be more suitable here.
Other than that:
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> +#define AYANEO_CHARGE_REG 0x1e
> +#define AYANEO_CHARGE_VAL_AUTO 0xaa
> +#define AYANEO_CHARGE_VAL_INHIBIT 0x55
> +
> struct ayaneo_ec_quirk {
> bool has_fan_control;
> + bool has_charge_control;
> };
>
> struct ayaneo_ec_platform_data {
> struct platform_device *pdev;
> struct ayaneo_ec_quirk *quirks;
> + struct acpi_battery_hook battery_hook;
> };
>
> static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
> .has_fan_control = true,
> + .has_charge_control = true,
> };
>
> static const struct dmi_system_id dmi_table[] = {
> @@ -164,11 +176,102 @@ static const struct hwmon_chip_info ayaneo_ec_chip_info = {
> .info = ayaneo_ec_sensors,
> };
>
> +static int ayaneo_psy_ext_get_prop(struct power_supply *psy,
> + const struct power_supply_ext *ext,
> + void *data,
> + enum power_supply_property psp,
> + union power_supply_propval *val)
> +{
> + int ret;
> + u8 tmp;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
> + ret = ec_read(AYANEO_CHARGE_REG, &tmp);
> + if (ret)
> + return ret;
> +
> + if (tmp == AYANEO_CHARGE_VAL_INHIBIT)
> + val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
> + else
> + val->intval = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ayaneo_psy_ext_set_prop(struct power_supply *psy,
> + const struct power_supply_ext *ext,
> + void *data,
> + enum power_supply_property psp,
> + const union power_supply_propval *val)
> +{
> + u8 raw_val;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
> + switch (val->intval) {
> + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
> + raw_val = AYANEO_CHARGE_VAL_AUTO;
> + break;
> + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
> + raw_val = AYANEO_CHARGE_VAL_INHIBIT;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return ec_write(AYANEO_CHARGE_REG, raw_val);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ayaneo_psy_prop_is_writeable(struct power_supply *psy,
> + const struct power_supply_ext *ext,
> + void *data,
> + enum power_supply_property psp)
> +{
> + return true;
> +}
> +
> +static const enum power_supply_property ayaneo_psy_ext_props[] = {
> + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
> +};
> +
> +static const struct power_supply_ext ayaneo_psy_ext = {
> + .name = "ayaneo-charge-control",
> + .properties = ayaneo_psy_ext_props,
> + .num_properties = ARRAY_SIZE(ayaneo_psy_ext_props),
> + .charge_behaviours = EC_CHARGE_CONTROL_BEHAVIOURS,
> + .get_property = ayaneo_psy_ext_get_prop,
> + .set_property = ayaneo_psy_ext_set_prop,
> + .property_is_writeable = ayaneo_psy_prop_is_writeable,
> +};
> +
> +static int ayaneo_add_battery(struct power_supply *battery,
> + struct acpi_battery_hook *hook)
> +{
> + struct ayaneo_ec_platform_data *data =
> + container_of(hook, struct ayaneo_ec_platform_data, battery_hook);
> +
> + return power_supply_register_extension(battery, &ayaneo_psy_ext,
> + &data->pdev->dev, NULL);
> +}
> +
> +static int ayaneo_remove_battery(struct power_supply *battery,
> + struct acpi_battery_hook *hook)
> +{
> + power_supply_unregister_extension(battery, &ayaneo_psy_ext);
> + return 0;
> +}
> +
> static int ayaneo_ec_probe(struct platform_device *pdev)
> {
> const struct dmi_system_id *dmi_entry;
> struct ayaneo_ec_platform_data *data;
> struct device *hwdev;
> + int ret;
>
> dmi_entry = dmi_first_match(dmi_table);
> if (!dmi_entry)
> @@ -189,6 +292,15 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> return PTR_ERR(hwdev);
> }
>
> + if (data->quirks->has_charge_control) {
> + data->battery_hook.add_battery = ayaneo_add_battery;
> + data->battery_hook.remove_battery = ayaneo_remove_battery;
> + data->battery_hook.name = "Ayaneo Battery";
> + ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook);
> + if (ret)
> + return ret;
> + }
> +
> return 0;
> }
>
© 2016 - 2026 Red Hat, Inc.