* [PATCH v5 1/6] platform/x86: ayaneo-ec: Add Ayaneo Embedded Controller platform driver
2025-11-13 21:22 [PATCH v5 0/6] platform/x86: ayaneo-ec: Add Ayaneo Embedded Controller platform driver Antheas Kapenekakis
@ 2025-11-13 21:22 ` Antheas Kapenekakis
2025-11-16 16:02 ` Armin Wolf
2025-11-13 21:22 ` [PATCH v5 2/6] platform/x86: ayaneo-ec: Add hwmon support Antheas Kapenekakis
` (4 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Antheas Kapenekakis @ 2025-11-13 21:22 UTC (permalink / raw)
To: platform-driver-x86
Cc: linux-kernel, linux-hwmon, Hans de Goede, Ilpo Järvinen,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Antheas Kapenekakis
Recent Ayaneo devices feature an ACPI mapped Embedded Controller (EC)
with standard addresses across models that provides access to fan
speed, fan control, battery charge limits, and controller power
controls. Introduce a new driver stub that will handle these driver
features.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
MAINTAINERS | 6 +++
drivers/platform/x86/Kconfig | 10 ++++
drivers/platform/x86/Makefile | 3 ++
drivers/platform/x86/ayaneo-ec.c | 90 ++++++++++++++++++++++++++++++++
4 files changed, 109 insertions(+)
create mode 100644 drivers/platform/x86/ayaneo-ec.c
diff --git a/MAINTAINERS b/MAINTAINERS
index ddecf1ef3bed..c5bf7207c45f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4192,6 +4192,12 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml
F: drivers/pwm/pwm-axi-pwmgen.c
+AYANEO PLATFORM EC DRIVER
+M: Antheas Kapenekakis <lkml@antheas.dev>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: drivers/platform/x86/ayaneo-ec.c
+
AZ6007 DVB DRIVER
M: Mauro Carvalho Chehab <mchehab@kernel.org>
L: linux-media@vger.kernel.org
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index c122016d82f1..8ca95536f8d9 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -316,6 +316,16 @@ config ASUS_TF103C_DOCK
If you have an Asus TF103C tablet say Y or M here, for a generic x86
distro config say M here.
+config AYANEO_EC
+ tristate "Ayaneo EC platform control"
+ depends on DMI
+ help
+ Enables support for the platform EC of Ayaneo devices. This
+ includes fan control, fan speed, charge limit, magic
+ module detection, and controller power control.
+
+ If you have an Ayaneo device, say Y or M here.
+
config MERAKI_MX100
tristate "Cisco Meraki MX100 Platform Driver"
depends on GPIOLIB
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index c7db2a88c11a..274a685eb92d 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -39,6 +39,9 @@ obj-$(CONFIG_ASUS_TF103C_DOCK) += asus-tf103c-dock.o
obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
+# Ayaneo
+obj-$(CONFIG_AYANEO_EC) += ayaneo-ec.o
+
# Cisco/Meraki
obj-$(CONFIG_MERAKI_MX100) += meraki-mx100.o
diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
new file mode 100644
index 000000000000..2fe66c8a89f4
--- /dev/null
+++ b/drivers/platform/x86/ayaneo-ec.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Platform driver for the Embedded Controller (EC) of Ayaneo devices. Handles
+ * hwmon (fan speed, fan control), battery charge limits, and magic module
+ * control (connected modules, controller disconnection).
+ *
+ * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
+ */
+
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+struct ayaneo_ec_quirk {
+};
+
+struct ayaneo_ec_platform_data {
+ struct platform_device *pdev;
+ struct ayaneo_ec_quirk *quirks;
+};
+
+static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
+};
+
+static const struct dmi_system_id dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 3"),
+ },
+ .driver_data = (void *)&quirk_ayaneo3,
+ },
+ {},
+};
+
+static int ayaneo_ec_probe(struct platform_device *pdev)
+{
+ const struct dmi_system_id *dmi_entry;
+ struct ayaneo_ec_platform_data *data;
+
+ dmi_entry = dmi_first_match(dmi_table);
+ if (!dmi_entry)
+ return -ENODEV;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->pdev = pdev;
+ data->quirks = dmi_entry->driver_data;
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static struct platform_driver ayaneo_platform_driver = {
+ .driver = {
+ .name = "ayaneo-ec",
+ },
+ .probe = ayaneo_ec_probe,
+};
+
+static struct platform_device *ayaneo_platform_device;
+
+static int __init ayaneo_ec_init(void)
+{
+ ayaneo_platform_device =
+ platform_create_bundle(&ayaneo_platform_driver,
+ ayaneo_ec_probe, NULL, 0, NULL, 0);
+
+ return PTR_ERR_OR_ZERO(ayaneo_platform_device);
+}
+
+static void __exit ayaneo_ec_exit(void)
+{
+ platform_device_unregister(ayaneo_platform_device);
+ platform_driver_unregister(&ayaneo_platform_driver);
+}
+
+MODULE_DEVICE_TABLE(dmi, dmi_table);
+
+module_init(ayaneo_ec_init);
+module_exit(ayaneo_ec_exit);
+
+MODULE_AUTHOR("Antheas Kapenekakis <lkml@antheas.dev>");
+MODULE_DESCRIPTION("Ayaneo Embedded Controller (EC) platform features");
+MODULE_LICENSE("GPL");
--
2.51.2
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v5 1/6] platform/x86: ayaneo-ec: Add Ayaneo Embedded Controller platform driver
2025-11-13 21:22 ` [PATCH v5 1/6] " Antheas Kapenekakis
@ 2025-11-16 16:02 ` Armin Wolf
0 siblings, 0 replies; 18+ messages in thread
From: Armin Wolf @ 2025-11-16 16:02 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: linux-kernel, linux-hwmon, Hans de Goede, Ilpo Järvinen,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck
Am 13.11.25 um 22:22 schrieb Antheas Kapenekakis:
> Recent Ayaneo devices feature an ACPI mapped Embedded Controller (EC)
> with standard addresses across models that provides access to fan
> speed, fan control, battery charge limits, and controller power
> controls. Introduce a new driver stub that will handle these driver
> features.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> MAINTAINERS | 6 +++
> drivers/platform/x86/Kconfig | 10 ++++
> drivers/platform/x86/Makefile | 3 ++
> drivers/platform/x86/ayaneo-ec.c | 90 ++++++++++++++++++++++++++++++++
> 4 files changed, 109 insertions(+)
> create mode 100644 drivers/platform/x86/ayaneo-ec.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ddecf1ef3bed..c5bf7207c45f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4192,6 +4192,12 @@ W: https://ez.analog.com/linux-software-drivers
> F: Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml
> F: drivers/pwm/pwm-axi-pwmgen.c
>
> +AYANEO PLATFORM EC DRIVER
> +M: Antheas Kapenekakis <lkml@antheas.dev>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: drivers/platform/x86/ayaneo-ec.c
> +
> AZ6007 DVB DRIVER
> M: Mauro Carvalho Chehab <mchehab@kernel.org>
> L: linux-media@vger.kernel.org
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index c122016d82f1..8ca95536f8d9 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -316,6 +316,16 @@ config ASUS_TF103C_DOCK
> If you have an Asus TF103C tablet say Y or M here, for a generic x86
> distro config say M here.
>
> +config AYANEO_EC
> + tristate "Ayaneo EC platform control"
> + depends on DMI
> + help
> + Enables support for the platform EC of Ayaneo devices. This
> + includes fan control, fan speed, charge limit, magic
> + module detection, and controller power control.
> +
> + If you have an Ayaneo device, say Y or M here.
> +
> config MERAKI_MX100
> tristate "Cisco Meraki MX100 Platform Driver"
> depends on GPIOLIB
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index c7db2a88c11a..274a685eb92d 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -39,6 +39,9 @@ obj-$(CONFIG_ASUS_TF103C_DOCK) += asus-tf103c-dock.o
> obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
> obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
>
> +# Ayaneo
> +obj-$(CONFIG_AYANEO_EC) += ayaneo-ec.o
> +
> # Cisco/Meraki
> obj-$(CONFIG_MERAKI_MX100) += meraki-mx100.o
>
> diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> new file mode 100644
> index 000000000000..2fe66c8a89f4
> --- /dev/null
> +++ b/drivers/platform/x86/ayaneo-ec.c
> @@ -0,0 +1,90 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Platform driver for the Embedded Controller (EC) of Ayaneo devices. Handles
> + * hwmon (fan speed, fan control), battery charge limits, and magic module
> + * control (connected modules, controller disconnection).
> + *
> + * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
> + */
> +
> +#include <linux/dmi.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +struct ayaneo_ec_quirk {
> +};
> +
> +struct ayaneo_ec_platform_data {
> + struct platform_device *pdev;
> + struct ayaneo_ec_quirk *quirks;
> +};
> +
> +static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
> +};
> +
> +static const struct dmi_system_id dmi_table[] = {
> + {
> + .matches = {
> + DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
> + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AYANEO 3"),
> + },
> + .driver_data = (void *)&quirk_ayaneo3,
> + },
> + {},
> +};
> +
> +static int ayaneo_ec_probe(struct platform_device *pdev)
> +{
> + const struct dmi_system_id *dmi_entry;
> + struct ayaneo_ec_platform_data *data;
> +
> + dmi_entry = dmi_first_match(dmi_table);
> + if (!dmi_entry)
> + return -ENODEV;
> +
> + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + data->pdev = pdev;
> + data->quirks = dmi_entry->driver_data;
> + platform_set_drvdata(pdev, data);
> +
> + return 0;
> +}
> +
> +static struct platform_driver ayaneo_platform_driver = {
> + .driver = {
> + .name = "ayaneo-ec",
> + },
> + .probe = ayaneo_ec_probe,
> +};
> +
> +static struct platform_device *ayaneo_platform_device;
> +
> +static int __init ayaneo_ec_init(void)
> +{
> + ayaneo_platform_device =
> + platform_create_bundle(&ayaneo_platform_driver,
> + ayaneo_ec_probe, NULL, 0, NULL, 0);
> +
> + return PTR_ERR_OR_ZERO(ayaneo_platform_device);
> +}
> +
> +static void __exit ayaneo_ec_exit(void)
> +{
> + platform_device_unregister(ayaneo_platform_device);
> + platform_driver_unregister(&ayaneo_platform_driver);
> +}
> +
> +MODULE_DEVICE_TABLE(dmi, dmi_table);
> +
> +module_init(ayaneo_ec_init);
> +module_exit(ayaneo_ec_exit);
> +
> +MODULE_AUTHOR("Antheas Kapenekakis <lkml@antheas.dev>");
> +MODULE_DESCRIPTION("Ayaneo Embedded Controller (EC) platform features");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v5 2/6] platform/x86: ayaneo-ec: Add hwmon support
2025-11-13 21:22 [PATCH v5 0/6] platform/x86: ayaneo-ec: Add Ayaneo Embedded Controller platform driver Antheas Kapenekakis
2025-11-13 21:22 ` [PATCH v5 1/6] " Antheas Kapenekakis
@ 2025-11-13 21:22 ` Antheas Kapenekakis
2025-11-17 10:16 ` Ilpo Järvinen
2025-11-13 21:22 ` [PATCH v5 3/6] platform/x86: ayaneo-ec: Add charge control support Antheas Kapenekakis
` (3 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Antheas Kapenekakis @ 2025-11-13 21:22 UTC (permalink / raw)
To: platform-driver-x86
Cc: linux-kernel, linux-hwmon, Hans de Goede, Ilpo Järvinen,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Antheas Kapenekakis, Armin-Wolf
Add hwmon single fan sensor reads and control for Ayaneo devices.
The register and method of access is the same for all devices.
Reviewed-by: Armin-Wolf <W_Armin@gmx.de>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/Kconfig | 2 +
drivers/platform/x86/ayaneo-ec.c | 136 +++++++++++++++++++++++++++++++
2 files changed, 138 insertions(+)
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 8ca95536f8d9..e19850bd2609 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -319,6 +319,8 @@ config ASUS_TF103C_DOCK
config AYANEO_EC
tristate "Ayaneo EC platform control"
depends on DMI
+ depends on ACPI_EC
+ depends on HWMON
help
Enables support for the platform EC of Ayaneo devices. This
includes fan control, fan speed, charge limit, magic
diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
index 2fe66c8a89f4..108a23458a4f 100644
--- a/drivers/platform/x86/ayaneo-ec.c
+++ b/drivers/platform/x86/ayaneo-ec.c
@@ -7,14 +7,24 @@
* Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
*/
+#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/err.h>
+#include <linux/hwmon.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#define AYANEO_PWM_ENABLE_REG 0x4A
+#define AYANEO_PWM_REG 0x4B
+#define AYANEO_PWM_MODE_AUTO 0x00
+#define AYANEO_PWM_MODE_MANUAL 0x01
+
+#define AYANEO_FAN_REG 0x76
+
struct ayaneo_ec_quirk {
+ bool has_fan_control;
};
struct ayaneo_ec_platform_data {
@@ -23,6 +33,7 @@ struct ayaneo_ec_platform_data {
};
static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
+ .has_fan_control = true,
};
static const struct dmi_system_id dmi_table[] = {
@@ -36,10 +47,128 @@ static const struct dmi_system_id dmi_table[] = {
{},
};
+/* Callbacks for hwmon interface */
+static umode_t ayaneo_ec_hwmon_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ switch (type) {
+ case hwmon_fan:
+ return 0444;
+ case hwmon_pwm:
+ return 0644;
+ default:
+ return 0;
+ }
+}
+
+static int ayaneo_ec_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ u8 tmp;
+ int ret;
+
+ switch (type) {
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ ret = ec_read(AYANEO_FAN_REG, &tmp);
+ if (ret)
+ return ret;
+ *val = tmp << 8;
+ ret = ec_read(AYANEO_FAN_REG + 1, &tmp);
+ if (ret)
+ return ret;
+ *val += tmp;
+ return 0;
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_input:
+ ret = ec_read(AYANEO_PWM_REG, &tmp);
+ if (ret)
+ return ret;
+ if (tmp > 100)
+ return -EIO;
+ *val = (255 * tmp) / 100;
+ return 0;
+ case hwmon_pwm_enable:
+ ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
+ if (ret)
+ return ret;
+ if (tmp == AYANEO_PWM_MODE_MANUAL)
+ *val = 1;
+ else if (tmp == AYANEO_PWM_MODE_AUTO)
+ *val = 2;
+ else
+ return -EIO;
+ return 0;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ switch (type) {
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ switch (val) {
+ case 1:
+ return ec_write(AYANEO_PWM_ENABLE_REG,
+ AYANEO_PWM_MODE_MANUAL);
+ case 2:
+ return ec_write(AYANEO_PWM_ENABLE_REG,
+ AYANEO_PWM_MODE_AUTO);
+ default:
+ return -EINVAL;
+ }
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+ return ec_write(AYANEO_PWM_REG, (val * 100) / 255);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_ops ayaneo_ec_hwmon_ops = {
+ .is_visible = ayaneo_ec_hwmon_is_visible,
+ .read = ayaneo_ec_read,
+ .write = ayaneo_ec_write,
+};
+
+static const struct hwmon_channel_info *const ayaneo_ec_sensors[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+ NULL,
+};
+
+static const struct hwmon_chip_info ayaneo_ec_chip_info = {
+ .ops = &ayaneo_ec_hwmon_ops,
+ .info = ayaneo_ec_sensors,
+};
+
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;
dmi_entry = dmi_first_match(dmi_table);
if (!dmi_entry)
@@ -53,6 +182,13 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
data->quirks = dmi_entry->driver_data;
platform_set_drvdata(pdev, data);
+ if (data->quirks->has_fan_control) {
+ hwdev = devm_hwmon_device_register_with_info(&pdev->dev,
+ "ayaneo_ec", NULL, &ayaneo_ec_chip_info, NULL);
+ if (IS_ERR(hwdev))
+ return PTR_ERR(hwdev);
+ }
+
return 0;
}
--
2.51.2
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v5 2/6] platform/x86: ayaneo-ec: Add hwmon support
2025-11-13 21:22 ` [PATCH v5 2/6] platform/x86: ayaneo-ec: Add hwmon support Antheas Kapenekakis
@ 2025-11-17 10:16 ` Ilpo Järvinen
0 siblings, 0 replies; 18+ messages in thread
From: Ilpo Järvinen @ 2025-11-17 10:16 UTC (permalink / raw)
To: Antheas Kapenekakis
Cc: platform-driver-x86, LKML, linux-hwmon, Hans de Goede,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Armin-Wolf
On Thu, 13 Nov 2025, Antheas Kapenekakis wrote:
> Add hwmon single fan sensor reads and control for Ayaneo devices.
> The register and method of access is the same for all devices.
>
> Reviewed-by: Armin-Wolf <W_Armin@gmx.de>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/Kconfig | 2 +
> drivers/platform/x86/ayaneo-ec.c | 136 +++++++++++++++++++++++++++++++
> 2 files changed, 138 insertions(+)
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 8ca95536f8d9..e19850bd2609 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -319,6 +319,8 @@ config ASUS_TF103C_DOCK
> config AYANEO_EC
> tristate "Ayaneo EC platform control"
> depends on DMI
> + depends on ACPI_EC
> + depends on HWMON
> help
> Enables support for the platform EC of Ayaneo devices. This
> includes fan control, fan speed, charge limit, magic
> diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> index 2fe66c8a89f4..108a23458a4f 100644
> --- a/drivers/platform/x86/ayaneo-ec.c
> +++ b/drivers/platform/x86/ayaneo-ec.c
> @@ -7,14 +7,24 @@
> * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
> */
>
> +#include <linux/acpi.h>
> #include <linux/dmi.h>
> #include <linux/err.h>
> +#include <linux/hwmon.h>
> #include <linux/init.h>
> #include <linux/kernel.h>
> #include <linux/module.h>
> #include <linux/platform_device.h>
>
> +#define AYANEO_PWM_ENABLE_REG 0x4A
> +#define AYANEO_PWM_REG 0x4B
> +#define AYANEO_PWM_MODE_AUTO 0x00
> +#define AYANEO_PWM_MODE_MANUAL 0x01
> +
> +#define AYANEO_FAN_REG 0x76
> +
> struct ayaneo_ec_quirk {
> + bool has_fan_control;
> };
>
> struct ayaneo_ec_platform_data {
> @@ -23,6 +33,7 @@ struct ayaneo_ec_platform_data {
> };
>
> static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
> + .has_fan_control = true,
> };
>
> static const struct dmi_system_id dmi_table[] = {
> @@ -36,10 +47,128 @@ static const struct dmi_system_id dmi_table[] = {
> {},
> };
>
> +/* Callbacks for hwmon interface */
> +static umode_t ayaneo_ec_hwmon_is_visible(const void *drvdata,
> + enum hwmon_sensor_types type, u32 attr,
> + int channel)
> +{
> + switch (type) {
> + case hwmon_fan:
> + return 0444;
> + case hwmon_pwm:
> + return 0644;
> + default:
> + return 0;
> + }
> +}
> +
> +static int ayaneo_ec_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + u8 tmp;
> + int ret;
> +
> + switch (type) {
> + case hwmon_fan:
> + switch (attr) {
> + case hwmon_fan_input:
> + ret = ec_read(AYANEO_FAN_REG, &tmp);
> + if (ret)
> + return ret;
> + *val = tmp << 8;
> + ret = ec_read(AYANEO_FAN_REG + 1, &tmp);
> + if (ret)
> + return ret;
> + *val += tmp;
|= as this is combining two u8?
> + return 0;
> + default:
> + break;
> + }
> + break;
> + case hwmon_pwm:
> + switch (attr) {
> + case hwmon_pwm_input:
> + ret = ec_read(AYANEO_PWM_REG, &tmp);
> + if (ret)
> + return ret;
> + if (tmp > 100)
> + return -EIO;
> + *val = (255 * tmp) / 100;
> + return 0;
> + case hwmon_pwm_enable:
> + ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
> + if (ret)
> + return ret;
> + if (tmp == AYANEO_PWM_MODE_MANUAL)
> + *val = 1;
> + else if (tmp == AYANEO_PWM_MODE_AUTO)
> + *val = 2;
> + else
> + return -EIO;
> + return 0;
> + default:
> + break;
> + }
> + break;
> + default:
> + break;
> + }
> + return -EOPNOTSUPP;
> +}
> +
> +static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long val)
> +{
> + switch (type) {
> + case hwmon_pwm:
> + switch (attr) {
> + case hwmon_pwm_enable:
> + switch (val) {
> + case 1:
> + return ec_write(AYANEO_PWM_ENABLE_REG,
> + AYANEO_PWM_MODE_MANUAL);
> + case 2:
> + return ec_write(AYANEO_PWM_ENABLE_REG,
> + AYANEO_PWM_MODE_AUTO);
> + default:
> + return -EINVAL;
> + }
> + case hwmon_pwm_input:
> + if (val < 0 || val > 255)
> + return -EINVAL;
> + return ec_write(AYANEO_PWM_REG, (val * 100) / 255);
> + default:
> + break;
> + }
> + break;
> + default:
> + break;
> + }
> + return -EOPNOTSUPP;
> +}
> +
> +static const struct hwmon_ops ayaneo_ec_hwmon_ops = {
> + .is_visible = ayaneo_ec_hwmon_is_visible,
> + .read = ayaneo_ec_read,
> + .write = ayaneo_ec_write,
> +};
> +
> +static const struct hwmon_channel_info *const ayaneo_ec_sensors[] = {
> + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
> + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
> + NULL,
> +};
> +
> +static const struct hwmon_chip_info ayaneo_ec_chip_info = {
> + .ops = &ayaneo_ec_hwmon_ops,
> + .info = ayaneo_ec_sensors,
> +};
> +
> 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;
>
> dmi_entry = dmi_first_match(dmi_table);
> if (!dmi_entry)
> @@ -53,6 +182,13 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> data->quirks = dmi_entry->driver_data;
> platform_set_drvdata(pdev, data);
>
> + if (data->quirks->has_fan_control) {
> + hwdev = devm_hwmon_device_register_with_info(&pdev->dev,
> + "ayaneo_ec", NULL, &ayaneo_ec_chip_info, NULL);
> + if (IS_ERR(hwdev))
> + return PTR_ERR(hwdev);
> + }
> +
> return 0;
> }
>
>
--
i.
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v5 3/6] platform/x86: ayaneo-ec: Add charge control support
2025-11-13 21:22 [PATCH v5 0/6] platform/x86: ayaneo-ec: Add Ayaneo Embedded Controller platform driver Antheas Kapenekakis
2025-11-13 21:22 ` [PATCH v5 1/6] " Antheas Kapenekakis
2025-11-13 21:22 ` [PATCH v5 2/6] platform/x86: ayaneo-ec: Add hwmon support Antheas Kapenekakis
@ 2025-11-13 21:22 ` Antheas Kapenekakis
2025-11-13 21:22 ` [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes Antheas Kapenekakis
` (2 subsequent siblings)
5 siblings, 0 replies; 18+ messages in thread
From: Antheas Kapenekakis @ 2025-11-13 21:22 UTC (permalink / raw)
To: platform-driver-x86
Cc: linux-kernel, linux-hwmon, Hans de Goede, Ilpo Järvinen,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Antheas Kapenekakis, Armin Wolf
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 e19850bd2609..b049c55057b7 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -320,6 +320,7 @@ config AYANEO_EC
tristate "Ayaneo EC platform control"
depends on DMI
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.2
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes
2025-11-13 21:22 [PATCH v5 0/6] platform/x86: ayaneo-ec: Add Ayaneo Embedded Controller platform driver Antheas Kapenekakis
` (2 preceding siblings ...)
2025-11-13 21:22 ` [PATCH v5 3/6] platform/x86: ayaneo-ec: Add charge control support Antheas Kapenekakis
@ 2025-11-13 21:22 ` Antheas Kapenekakis
2025-11-17 10:29 ` Ilpo Järvinen
2025-11-13 21:22 ` [PATCH v5 5/6] platform/x86: ayaneo-ec: Move Ayaneo devices from oxpec to ayaneo-ec Antheas Kapenekakis
2025-11-13 21:22 ` [PATCH v5 6/6] platform/x86: ayaneo-ec: Add suspend hook Antheas Kapenekakis
5 siblings, 1 reply; 18+ messages in thread
From: Antheas Kapenekakis @ 2025-11-13 21:22 UTC (permalink / raw)
To: platform-driver-x86
Cc: linux-kernel, linux-hwmon, Hans de Goede, Ilpo Järvinen,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Antheas Kapenekakis, Armin Wolf
The Ayaneo 3 features hot-swappable controller modules. The ejection
and management is done through HID. However, after ejecting the modules,
the controller needs to be power cycled via the EC to re-initialize.
For this, the EC provides a variable that holds whether the left or
right modules are connected, and a power control register to turn
the controller on or off. After ejecting the modules, the controller
should be turned off. Then, after both modules are reinserted,
the controller may be powered on again to re-initialize.
This patch introduces two new sysfs attributes:
- `controller_modules`: a read-only attribute that indicates whether
the left and right modules are connected (none, left, right, both).
- `controller_power`: a read-write attribute that allows the user
to turn the controller on or off (with '1'/'0').
Therefore, after ejection is complete, userspace can power off the
controller, then wait until both modules have been reinserted
(`controller_modules` will return 'both') to turn on the controller.
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
.../ABI/testing/sysfs-platform-ayaneo-ec | 19 ++++
MAINTAINERS | 1 +
drivers/platform/x86/ayaneo-ec.c | 107 ++++++++++++++++++
3 files changed, 127 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-platform-ayaneo-ec
diff --git a/Documentation/ABI/testing/sysfs-platform-ayaneo-ec b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
new file mode 100644
index 000000000000..4cffbf5fc7ca
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
@@ -0,0 +1,19 @@
+What: /sys/devices/platform/ayaneo-ec/controller_power
+Date: Nov 2025
+KernelVersion: 6.19
+Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
+Description:
+ Current controller power state. Allows turning on and off
+ the controller power (e.g. for power savings). Write 1 to
+ turn on, 0 to turn off. File is readable and writable.
+
+What: /sys/devices/platform/ayaneo-ec/controller_modules
+Date: Nov 2025
+KernelVersion: 6.19
+Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
+Description:
+ Shows which controller modules are currently connected to
+ the device. Possible values are "left", "right" and "both".
+ File is read-only. The Windows software for this device
+ will only set controller power to 1 if both module sides
+ are connected (i.e. this file returns "both").
diff --git a/MAINTAINERS b/MAINTAINERS
index c5bf7207c45f..f8ab009b6224 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4196,6 +4196,7 @@ AYANEO PLATFORM EC DRIVER
M: Antheas Kapenekakis <lkml@antheas.dev>
L: platform-driver-x86@vger.kernel.org
S: Maintained
+F: Documentation/ABI/testing/sysfs-platform-ayaneo
F: drivers/platform/x86/ayaneo-ec.c
AZ6007 DVB DRIVER
diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
index 697bb053a7d6..a0747e7ee43a 100644
--- a/drivers/platform/x86/ayaneo-ec.c
+++ b/drivers/platform/x86/ayaneo-ec.c
@@ -8,6 +8,7 @@
*/
#include <linux/acpi.h>
+#include <linux/bits.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/hwmon.h>
@@ -16,6 +17,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
+#include <linux/sysfs.h>
#include <acpi/battery.h>
#define AYANEO_PWM_ENABLE_REG 0x4A
@@ -32,9 +34,18 @@
#define AYANEO_CHARGE_VAL_AUTO 0xaa
#define AYANEO_CHARGE_VAL_INHIBIT 0x55
+#define AYANEO_POWER_REG 0x2d
+#define AYANEO_POWER_OFF 0xfe
+#define AYANEO_POWER_ON 0xff
+#define AYANEO_MODULE_REG 0x2f
+#define AYANEO_MODULE_LEFT BIT(0)
+#define AYANEO_MODULE_RIGHT BIT(1)
+#define AYANEO_MODULE_MASK (AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
+
struct ayaneo_ec_quirk {
bool has_fan_control;
bool has_charge_control;
+ bool has_magic_modules;
};
struct ayaneo_ec_platform_data {
@@ -46,6 +57,7 @@ struct ayaneo_ec_platform_data {
static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
.has_fan_control = true,
.has_charge_control = true,
+ .has_magic_modules = true,
};
static const struct dmi_system_id dmi_table[] = {
@@ -266,6 +278,100 @@ static int ayaneo_remove_battery(struct power_supply *battery,
return 0;
}
+static ssize_t controller_power_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ bool value;
+ int ret;
+
+ ret = kstrtobool(buf, &value);
+ if (ret)
+ return ret;
+
+ ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t controller_power_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 val;
+
+ ret = ec_read(AYANEO_POWER_REG, &val);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
+}
+
+static DEVICE_ATTR_RW(controller_power);
+
+static ssize_t controller_modules_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ char *out;
+ int ret;
+ u8 val;
+
+ ret = ec_read(AYANEO_MODULE_REG, &val);
+ if (ret)
+ return ret;
+
+ switch (~val & AYANEO_MODULE_MASK) {
+ case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
+ out = "both";
+ break;
+ case AYANEO_MODULE_LEFT:
+ out = "left";
+ break;
+ case AYANEO_MODULE_RIGHT:
+ out = "right";
+ break;
+ default:
+ out = "none";
+ break;
+ }
+
+ return sysfs_emit(buf, "%s\n", out);
+}
+
+static DEVICE_ATTR_RO(controller_modules);
+
+static struct attribute *aya_mm_attrs[] = {
+ &dev_attr_controller_power.attr,
+ &dev_attr_controller_modules.attr,
+ NULL
+};
+
+static umode_t aya_mm_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
+
+ if (data->quirks->has_magic_modules)
+ return attr->mode;
+ return 0;
+}
+
+static const struct attribute_group aya_mm_attribute_group = {
+ .is_visible = aya_mm_is_visible,
+ .attrs = aya_mm_attrs,
+};
+
+static const struct attribute_group *ayaneo_ec_groups[] = {
+ &aya_mm_attribute_group,
+ NULL
+};
+
static int ayaneo_ec_probe(struct platform_device *pdev)
{
const struct dmi_system_id *dmi_entry;
@@ -307,6 +413,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
static struct platform_driver ayaneo_platform_driver = {
.driver = {
.name = "ayaneo-ec",
+ .dev_groups = ayaneo_ec_groups,
},
.probe = ayaneo_ec_probe,
};
--
2.51.2
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes
2025-11-13 21:22 ` [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes Antheas Kapenekakis
@ 2025-11-17 10:29 ` Ilpo Järvinen
2025-11-17 13:33 ` Antheas Kapenekakis
0 siblings, 1 reply; 18+ messages in thread
From: Ilpo Järvinen @ 2025-11-17 10:29 UTC (permalink / raw)
To: Antheas Kapenekakis
Cc: platform-driver-x86, LKML, linux-hwmon, Hans de Goede,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Armin Wolf
On Thu, 13 Nov 2025, Antheas Kapenekakis wrote:
> The Ayaneo 3 features hot-swappable controller modules. The ejection
> and management is done through HID. However, after ejecting the modules,
> the controller needs to be power cycled via the EC to re-initialize.
>
> For this, the EC provides a variable that holds whether the left or
> right modules are connected, and a power control register to turn
> the controller on or off. After ejecting the modules, the controller
> should be turned off. Then, after both modules are reinserted,
> the controller may be powered on again to re-initialize.
If wonder if the ejecting could/should be made to turn it off without need
for an explicit off command?
> This patch introduces two new sysfs attributes:
> - `controller_modules`: a read-only attribute that indicates whether
> the left and right modules are connected (none, left, right, both).
> - `controller_power`: a read-write attribute that allows the user
> to turn the controller on or off (with '1'/'0').
>
> Therefore, after ejection is complete, userspace can power off the
> controller, then wait until both modules have been reinserted
> (`controller_modules` will return 'both') to turn on the controller.
>
> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> .../ABI/testing/sysfs-platform-ayaneo-ec | 19 ++++
> MAINTAINERS | 1 +
> drivers/platform/x86/ayaneo-ec.c | 107 ++++++++++++++++++
> 3 files changed, 127 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-platform-ayaneo-ec
>
> diff --git a/Documentation/ABI/testing/sysfs-platform-ayaneo-ec b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> new file mode 100644
> index 000000000000..4cffbf5fc7ca
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> @@ -0,0 +1,19 @@
> +What: /sys/devices/platform/ayaneo-ec/controller_power
> +Date: Nov 2025
> +KernelVersion: 6.19
> +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
> +Description:
> + Current controller power state. Allows turning on and off
> + the controller power (e.g. for power savings). Write 1 to
> + turn on, 0 to turn off. File is readable and writable.
> +
> +What: /sys/devices/platform/ayaneo-ec/controller_modules
> +Date: Nov 2025
> +KernelVersion: 6.19
> +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
> +Description:
> + Shows which controller modules are currently connected to
> + the device. Possible values are "left", "right" and "both".
> + File is read-only. The Windows software for this device
> + will only set controller power to 1 if both module sides
> + are connected (i.e. this file returns "both").
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c5bf7207c45f..f8ab009b6224 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4196,6 +4196,7 @@ AYANEO PLATFORM EC DRIVER
> M: Antheas Kapenekakis <lkml@antheas.dev>
> L: platform-driver-x86@vger.kernel.org
> S: Maintained
> +F: Documentation/ABI/testing/sysfs-platform-ayaneo
> F: drivers/platform/x86/ayaneo-ec.c
>
> AZ6007 DVB DRIVER
> diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> index 697bb053a7d6..a0747e7ee43a 100644
> --- a/drivers/platform/x86/ayaneo-ec.c
> +++ b/drivers/platform/x86/ayaneo-ec.c
> @@ -8,6 +8,7 @@
> */
>
> #include <linux/acpi.h>
> +#include <linux/bits.h>
> #include <linux/dmi.h>
> #include <linux/err.h>
> #include <linux/hwmon.h>
> @@ -16,6 +17,7 @@
> #include <linux/module.h>
> #include <linux/platform_device.h>
> #include <linux/power_supply.h>
> +#include <linux/sysfs.h>
> #include <acpi/battery.h>
>
> #define AYANEO_PWM_ENABLE_REG 0x4A
> @@ -32,9 +34,18 @@
> #define AYANEO_CHARGE_VAL_AUTO 0xaa
> #define AYANEO_CHARGE_VAL_INHIBIT 0x55
>
> +#define AYANEO_POWER_REG 0x2d
> +#define AYANEO_POWER_OFF 0xfe
> +#define AYANEO_POWER_ON 0xff
> +#define AYANEO_MODULE_REG 0x2f
> +#define AYANEO_MODULE_LEFT BIT(0)
> +#define AYANEO_MODULE_RIGHT BIT(1)
> +#define AYANEO_MODULE_MASK (AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
> +
> struct ayaneo_ec_quirk {
> bool has_fan_control;
> bool has_charge_control;
> + bool has_magic_modules;
> };
>
> struct ayaneo_ec_platform_data {
> @@ -46,6 +57,7 @@ struct ayaneo_ec_platform_data {
> static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
> .has_fan_control = true,
> .has_charge_control = true,
> + .has_magic_modules = true,
> };
>
> static const struct dmi_system_id dmi_table[] = {
> @@ -266,6 +278,100 @@ static int ayaneo_remove_battery(struct power_supply *battery,
> return 0;
> }
>
> +static ssize_t controller_power_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + bool value;
> + int ret;
> +
> + ret = kstrtobool(buf, &value);
> + if (ret)
> + return ret;
> +
> + ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
> + if (ret)
> + return ret;
> +
> + return count;
> +}
> +
> +static ssize_t controller_power_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int ret;
> + u8 val;
> +
> + ret = ec_read(AYANEO_POWER_REG, &val);
> + if (ret)
> + return ret;
> +
> + return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
> +}
> +
> +static DEVICE_ATTR_RW(controller_power);
> +
> +static ssize_t controller_modules_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + char *out;
> + int ret;
> + u8 val;
> +
> + ret = ec_read(AYANEO_MODULE_REG, &val);
> + if (ret)
> + return ret;
> +
> + switch (~val & AYANEO_MODULE_MASK) {
Thanks for adding the mask.
Now when reading this again, I also suggest changing variable name from
"val" to e.g. "unconnected_modules" as that would make the reason for
inversion more obvious.
> + case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
> + out = "both";
> + break;
> + case AYANEO_MODULE_LEFT:
> + out = "left";
> + break;
> + case AYANEO_MODULE_RIGHT:
> + out = "right";
> + break;
> + default:
> + out = "none";
> + break;
> + }
> +
> + return sysfs_emit(buf, "%s\n", out);
> +}
> +
> +static DEVICE_ATTR_RO(controller_modules);
> +
> +static struct attribute *aya_mm_attrs[] = {
> + &dev_attr_controller_power.attr,
> + &dev_attr_controller_modules.attr,
> + NULL
> +};
> +
> +static umode_t aya_mm_is_visible(struct kobject *kobj,
> + struct attribute *attr, int n)
> +{
> + struct device *dev = kobj_to_dev(kobj);
> + struct platform_device *pdev = to_platform_device(dev);
> + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> +
> + if (data->quirks->has_magic_modules)
> + return attr->mode;
> + return 0;
> +}
> +
> +static const struct attribute_group aya_mm_attribute_group = {
> + .is_visible = aya_mm_is_visible,
> + .attrs = aya_mm_attrs,
> +};
> +
> +static const struct attribute_group *ayaneo_ec_groups[] = {
> + &aya_mm_attribute_group,
> + NULL
> +};
> +
> static int ayaneo_ec_probe(struct platform_device *pdev)
> {
> const struct dmi_system_id *dmi_entry;
> @@ -307,6 +413,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> static struct platform_driver ayaneo_platform_driver = {
> .driver = {
> .name = "ayaneo-ec",
> + .dev_groups = ayaneo_ec_groups,
> },
> .probe = ayaneo_ec_probe,
> };
>
--
i.
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes
2025-11-17 10:29 ` Ilpo Järvinen
@ 2025-11-17 13:33 ` Antheas Kapenekakis
2025-11-18 12:31 ` Ilpo Järvinen
0 siblings, 1 reply; 18+ messages in thread
From: Antheas Kapenekakis @ 2025-11-17 13:33 UTC (permalink / raw)
To: Ilpo Järvinen
Cc: platform-driver-x86, LKML, linux-hwmon, Hans de Goede,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Armin Wolf
On Mon, 17 Nov 2025 at 11:30, Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Thu, 13 Nov 2025, Antheas Kapenekakis wrote:
>
> > The Ayaneo 3 features hot-swappable controller modules. The ejection
> > and management is done through HID. However, after ejecting the modules,
> > the controller needs to be power cycled via the EC to re-initialize.
> >
> > For this, the EC provides a variable that holds whether the left or
> > right modules are connected, and a power control register to turn
> > the controller on or off. After ejecting the modules, the controller
> > should be turned off. Then, after both modules are reinserted,
> > the controller may be powered on again to re-initialize.
>
> If wonder if the ejecting could/should be made to turn it off without need
> for an explicit off command?
Perhaps in the future, this driver leaves the possibility open for it.
However, that'd require a secondary HID driver to handle the full
ejection process, with a shared function hook for this driver.
The eject sequence consists of sending a HID config command to start
the ejection, followed by a secondary config command to turn off the
ejection bits and then waiting for two ready bits to report that the
ejection is finished. Then, the controller is turned off. Apart from
turning off, all of this is done through HID (so HID ready races the
controller_modules value).
The module status of this driver is only used to check when the
controller should be turned on again and to provide visual feedback
about which modules are currently connected while unpowered. When
powered, there is full status reporting over HID, including which
specific modules are connected[1].
The end-to-end sequence is currently in userspace[2]. However, the EC
ports are shielded from userspace so these two specific accesses must
happen through a kernel driver.
Antheas
[1] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py#L90-L117
[2] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py
> > This patch introduces two new sysfs attributes:
> > - `controller_modules`: a read-only attribute that indicates whether
> > the left and right modules are connected (none, left, right, both).
> > - `controller_power`: a read-write attribute that allows the user
> > to turn the controller on or off (with '1'/'0').
> >
> > Therefore, after ejection is complete, userspace can power off the
> > controller, then wait until both modules have been reinserted
> > (`controller_modules` will return 'both') to turn on the controller.
> >
> > Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> > .../ABI/testing/sysfs-platform-ayaneo-ec | 19 ++++
> > MAINTAINERS | 1 +
> > drivers/platform/x86/ayaneo-ec.c | 107 ++++++++++++++++++
> > 3 files changed, 127 insertions(+)
> > create mode 100644 Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> >
> > diff --git a/Documentation/ABI/testing/sysfs-platform-ayaneo-ec b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> > new file mode 100644
> > index 000000000000..4cffbf5fc7ca
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> > @@ -0,0 +1,19 @@
> > +What: /sys/devices/platform/ayaneo-ec/controller_power
> > +Date: Nov 2025
> > +KernelVersion: 6.19
> > +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
> > +Description:
> > + Current controller power state. Allows turning on and off
> > + the controller power (e.g. for power savings). Write 1 to
> > + turn on, 0 to turn off. File is readable and writable.
> > +
> > +What: /sys/devices/platform/ayaneo-ec/controller_modules
> > +Date: Nov 2025
> > +KernelVersion: 6.19
> > +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
> > +Description:
> > + Shows which controller modules are currently connected to
> > + the device. Possible values are "left", "right" and "both".
> > + File is read-only. The Windows software for this device
> > + will only set controller power to 1 if both module sides
> > + are connected (i.e. this file returns "both").
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index c5bf7207c45f..f8ab009b6224 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -4196,6 +4196,7 @@ AYANEO PLATFORM EC DRIVER
> > M: Antheas Kapenekakis <lkml@antheas.dev>
> > L: platform-driver-x86@vger.kernel.org
> > S: Maintained
> > +F: Documentation/ABI/testing/sysfs-platform-ayaneo
> > F: drivers/platform/x86/ayaneo-ec.c
> >
> > AZ6007 DVB DRIVER
> > diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> > index 697bb053a7d6..a0747e7ee43a 100644
> > --- a/drivers/platform/x86/ayaneo-ec.c
> > +++ b/drivers/platform/x86/ayaneo-ec.c
> > @@ -8,6 +8,7 @@
> > */
> >
> > #include <linux/acpi.h>
> > +#include <linux/bits.h>
> > #include <linux/dmi.h>
> > #include <linux/err.h>
> > #include <linux/hwmon.h>
> > @@ -16,6 +17,7 @@
> > #include <linux/module.h>
> > #include <linux/platform_device.h>
> > #include <linux/power_supply.h>
> > +#include <linux/sysfs.h>
> > #include <acpi/battery.h>
> >
> > #define AYANEO_PWM_ENABLE_REG 0x4A
> > @@ -32,9 +34,18 @@
> > #define AYANEO_CHARGE_VAL_AUTO 0xaa
> > #define AYANEO_CHARGE_VAL_INHIBIT 0x55
> >
> > +#define AYANEO_POWER_REG 0x2d
> > +#define AYANEO_POWER_OFF 0xfe
> > +#define AYANEO_POWER_ON 0xff
> > +#define AYANEO_MODULE_REG 0x2f
> > +#define AYANEO_MODULE_LEFT BIT(0)
> > +#define AYANEO_MODULE_RIGHT BIT(1)
> > +#define AYANEO_MODULE_MASK (AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
> > +
> > struct ayaneo_ec_quirk {
> > bool has_fan_control;
> > bool has_charge_control;
> > + bool has_magic_modules;
> > };
> >
> > struct ayaneo_ec_platform_data {
> > @@ -46,6 +57,7 @@ struct ayaneo_ec_platform_data {
> > static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
> > .has_fan_control = true,
> > .has_charge_control = true,
> > + .has_magic_modules = true,
> > };
> >
> > static const struct dmi_system_id dmi_table[] = {
> > @@ -266,6 +278,100 @@ static int ayaneo_remove_battery(struct power_supply *battery,
> > return 0;
> > }
> >
> > +static ssize_t controller_power_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf,
> > + size_t count)
> > +{
> > + bool value;
> > + int ret;
> > +
> > + ret = kstrtobool(buf, &value);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
> > + if (ret)
> > + return ret;
> > +
> > + return count;
> > +}
> > +
> > +static ssize_t controller_power_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + int ret;
> > + u8 val;
> > +
> > + ret = ec_read(AYANEO_POWER_REG, &val);
> > + if (ret)
> > + return ret;
> > +
> > + return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
> > +}
> > +
> > +static DEVICE_ATTR_RW(controller_power);
> > +
> > +static ssize_t controller_modules_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + char *out;
> > + int ret;
> > + u8 val;
> > +
> > + ret = ec_read(AYANEO_MODULE_REG, &val);
> > + if (ret)
> > + return ret;
> > +
> > + switch (~val & AYANEO_MODULE_MASK) {
>
> Thanks for adding the mask.
>
> Now when reading this again, I also suggest changing variable name from
> "val" to e.g. "unconnected_modules" as that would make the reason for
> inversion more obvious.
>
> > + case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
> > + out = "both";
> > + break;
> > + case AYANEO_MODULE_LEFT:
> > + out = "left";
> > + break;
> > + case AYANEO_MODULE_RIGHT:
> > + out = "right";
> > + break;
> > + default:
> > + out = "none";
> > + break;
> > + }
> > +
> > + return sysfs_emit(buf, "%s\n", out);
> > +}
> > +
> > +static DEVICE_ATTR_RO(controller_modules);
> > +
> > +static struct attribute *aya_mm_attrs[] = {
> > + &dev_attr_controller_power.attr,
> > + &dev_attr_controller_modules.attr,
> > + NULL
> > +};
> > +
> > +static umode_t aya_mm_is_visible(struct kobject *kobj,
> > + struct attribute *attr, int n)
> > +{
> > + struct device *dev = kobj_to_dev(kobj);
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> > +
> > + if (data->quirks->has_magic_modules)
> > + return attr->mode;
> > + return 0;
> > +}
> > +
> > +static const struct attribute_group aya_mm_attribute_group = {
> > + .is_visible = aya_mm_is_visible,
> > + .attrs = aya_mm_attrs,
> > +};
> > +
> > +static const struct attribute_group *ayaneo_ec_groups[] = {
> > + &aya_mm_attribute_group,
> > + NULL
> > +};
> > +
> > static int ayaneo_ec_probe(struct platform_device *pdev)
> > {
> > const struct dmi_system_id *dmi_entry;
> > @@ -307,6 +413,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> > static struct platform_driver ayaneo_platform_driver = {
> > .driver = {
> > .name = "ayaneo-ec",
> > + .dev_groups = ayaneo_ec_groups,
> > },
> > .probe = ayaneo_ec_probe,
> > };
> >
>
> --
> i.
>
>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes
2025-11-17 13:33 ` Antheas Kapenekakis
@ 2025-11-18 12:31 ` Ilpo Järvinen
2025-11-18 12:58 ` Antheas Kapenekakis
0 siblings, 1 reply; 18+ messages in thread
From: Ilpo Järvinen @ 2025-11-18 12:31 UTC (permalink / raw)
To: Antheas Kapenekakis
Cc: platform-driver-x86, LKML, linux-hwmon, Hans de Goede,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Armin Wolf
[-- Attachment #1: Type: text/plain, Size: 11006 bytes --]
On Mon, 17 Nov 2025, Antheas Kapenekakis wrote:
> On Mon, 17 Nov 2025 at 11:30, Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
> >
> > On Thu, 13 Nov 2025, Antheas Kapenekakis wrote:
> >
> > > The Ayaneo 3 features hot-swappable controller modules. The ejection
> > > and management is done through HID. However, after ejecting the modules,
> > > the controller needs to be power cycled via the EC to re-initialize.
> > >
> > > For this, the EC provides a variable that holds whether the left or
> > > right modules are connected, and a power control register to turn
> > > the controller on or off. After ejecting the modules, the controller
> > > should be turned off. Then, after both modules are reinserted,
> > > the controller may be powered on again to re-initialize.
> >
> > If wonder if the ejecting could/should be made to turn it off without need
> > for an explicit off command?
>
> Perhaps in the future, this driver leaves the possibility open for it.
> However, that'd require a secondary HID driver to handle the full
> ejection process, with a shared function hook for this driver.
>
> The eject sequence consists of sending a HID config command to start
> the ejection, followed by a secondary config command to turn off the
> ejection bits and then waiting for two ready bits to report that the
> ejection is finished. Then, the controller is turned off. Apart from
> turning off, all of this is done through HID (so HID ready races the
> controller_modules value).
>
> The module status of this driver is only used to check when the
> controller should be turned on again and to provide visual feedback
> about which modules are currently connected while unpowered. When
> powered, there is full status reporting over HID, including which
> specific modules are connected[1].
>
> The end-to-end sequence is currently in userspace[2]. However, the EC
> ports are shielded from userspace so these two specific accesses must
> happen through a kernel driver.
So can the other features be used without this part? I'd prefer to
postpone this power/modules thing and have the ejection done properly.
--
i.
>
> Antheas
>
> [1] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py#L90-L117
> [2] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py
>
> > > This patch introduces two new sysfs attributes:
> > > - `controller_modules`: a read-only attribute that indicates whether
> > > the left and right modules are connected (none, left, right, both).
> > > - `controller_power`: a read-write attribute that allows the user
> > > to turn the controller on or off (with '1'/'0').
> > >
> > > Therefore, after ejection is complete, userspace can power off the
> > > controller, then wait until both modules have been reinserted
> > > (`controller_modules` will return 'both') to turn on the controller.
> > >
> > > Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> > > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > > ---
> > > .../ABI/testing/sysfs-platform-ayaneo-ec | 19 ++++
> > > MAINTAINERS | 1 +
> > > drivers/platform/x86/ayaneo-ec.c | 107 ++++++++++++++++++
> > > 3 files changed, 127 insertions(+)
> > > create mode 100644 Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> > >
> > > diff --git a/Documentation/ABI/testing/sysfs-platform-ayaneo-ec b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> > > new file mode 100644
> > > index 000000000000..4cffbf5fc7ca
> > > --- /dev/null
> > > +++ b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> > > @@ -0,0 +1,19 @@
> > > +What: /sys/devices/platform/ayaneo-ec/controller_power
> > > +Date: Nov 2025
> > > +KernelVersion: 6.19
> > > +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
> > > +Description:
> > > + Current controller power state. Allows turning on and off
> > > + the controller power (e.g. for power savings). Write 1 to
> > > + turn on, 0 to turn off. File is readable and writable.
> > > +
> > > +What: /sys/devices/platform/ayaneo-ec/controller_modules
> > > +Date: Nov 2025
> > > +KernelVersion: 6.19
> > > +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
> > > +Description:
> > > + Shows which controller modules are currently connected to
> > > + the device. Possible values are "left", "right" and "both".
> > > + File is read-only. The Windows software for this device
> > > + will only set controller power to 1 if both module sides
> > > + are connected (i.e. this file returns "both").
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index c5bf7207c45f..f8ab009b6224 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -4196,6 +4196,7 @@ AYANEO PLATFORM EC DRIVER
> > > M: Antheas Kapenekakis <lkml@antheas.dev>
> > > L: platform-driver-x86@vger.kernel.org
> > > S: Maintained
> > > +F: Documentation/ABI/testing/sysfs-platform-ayaneo
> > > F: drivers/platform/x86/ayaneo-ec.c
> > >
> > > AZ6007 DVB DRIVER
> > > diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> > > index 697bb053a7d6..a0747e7ee43a 100644
> > > --- a/drivers/platform/x86/ayaneo-ec.c
> > > +++ b/drivers/platform/x86/ayaneo-ec.c
> > > @@ -8,6 +8,7 @@
> > > */
> > >
> > > #include <linux/acpi.h>
> > > +#include <linux/bits.h>
> > > #include <linux/dmi.h>
> > > #include <linux/err.h>
> > > #include <linux/hwmon.h>
> > > @@ -16,6 +17,7 @@
> > > #include <linux/module.h>
> > > #include <linux/platform_device.h>
> > > #include <linux/power_supply.h>
> > > +#include <linux/sysfs.h>
> > > #include <acpi/battery.h>
> > >
> > > #define AYANEO_PWM_ENABLE_REG 0x4A
> > > @@ -32,9 +34,18 @@
> > > #define AYANEO_CHARGE_VAL_AUTO 0xaa
> > > #define AYANEO_CHARGE_VAL_INHIBIT 0x55
> > >
> > > +#define AYANEO_POWER_REG 0x2d
> > > +#define AYANEO_POWER_OFF 0xfe
> > > +#define AYANEO_POWER_ON 0xff
> > > +#define AYANEO_MODULE_REG 0x2f
> > > +#define AYANEO_MODULE_LEFT BIT(0)
> > > +#define AYANEO_MODULE_RIGHT BIT(1)
> > > +#define AYANEO_MODULE_MASK (AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
> > > +
> > > struct ayaneo_ec_quirk {
> > > bool has_fan_control;
> > > bool has_charge_control;
> > > + bool has_magic_modules;
> > > };
> > >
> > > struct ayaneo_ec_platform_data {
> > > @@ -46,6 +57,7 @@ struct ayaneo_ec_platform_data {
> > > static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
> > > .has_fan_control = true,
> > > .has_charge_control = true,
> > > + .has_magic_modules = true,
> > > };
> > >
> > > static const struct dmi_system_id dmi_table[] = {
> > > @@ -266,6 +278,100 @@ static int ayaneo_remove_battery(struct power_supply *battery,
> > > return 0;
> > > }
> > >
> > > +static ssize_t controller_power_store(struct device *dev,
> > > + struct device_attribute *attr,
> > > + const char *buf,
> > > + size_t count)
> > > +{
> > > + bool value;
> > > + int ret;
> > > +
> > > + ret = kstrtobool(buf, &value);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return count;
> > > +}
> > > +
> > > +static ssize_t controller_power_show(struct device *dev,
> > > + struct device_attribute *attr,
> > > + char *buf)
> > > +{
> > > + int ret;
> > > + u8 val;
> > > +
> > > + ret = ec_read(AYANEO_POWER_REG, &val);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
> > > +}
> > > +
> > > +static DEVICE_ATTR_RW(controller_power);
> > > +
> > > +static ssize_t controller_modules_show(struct device *dev,
> > > + struct device_attribute *attr, char *buf)
> > > +{
> > > + char *out;
> > > + int ret;
> > > + u8 val;
> > > +
> > > + ret = ec_read(AYANEO_MODULE_REG, &val);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + switch (~val & AYANEO_MODULE_MASK) {
> >
> > Thanks for adding the mask.
> >
> > Now when reading this again, I also suggest changing variable name from
> > "val" to e.g. "unconnected_modules" as that would make the reason for
> > inversion more obvious.
> >
> > > + case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
> > > + out = "both";
> > > + break;
> > > + case AYANEO_MODULE_LEFT:
> > > + out = "left";
> > > + break;
> > > + case AYANEO_MODULE_RIGHT:
> > > + out = "right";
> > > + break;
> > > + default:
> > > + out = "none";
> > > + break;
> > > + }
> > > +
> > > + return sysfs_emit(buf, "%s\n", out);
> > > +}
> > > +
> > > +static DEVICE_ATTR_RO(controller_modules);
> > > +
> > > +static struct attribute *aya_mm_attrs[] = {
> > > + &dev_attr_controller_power.attr,
> > > + &dev_attr_controller_modules.attr,
> > > + NULL
> > > +};
> > > +
> > > +static umode_t aya_mm_is_visible(struct kobject *kobj,
> > > + struct attribute *attr, int n)
> > > +{
> > > + struct device *dev = kobj_to_dev(kobj);
> > > + struct platform_device *pdev = to_platform_device(dev);
> > > + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> > > +
> > > + if (data->quirks->has_magic_modules)
> > > + return attr->mode;
> > > + return 0;
> > > +}
> > > +
> > > +static const struct attribute_group aya_mm_attribute_group = {
> > > + .is_visible = aya_mm_is_visible,
> > > + .attrs = aya_mm_attrs,
> > > +};
> > > +
> > > +static const struct attribute_group *ayaneo_ec_groups[] = {
> > > + &aya_mm_attribute_group,
> > > + NULL
> > > +};
> > > +
> > > static int ayaneo_ec_probe(struct platform_device *pdev)
> > > {
> > > const struct dmi_system_id *dmi_entry;
> > > @@ -307,6 +413,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> > > static struct platform_driver ayaneo_platform_driver = {
> > > .driver = {
> > > .name = "ayaneo-ec",
> > > + .dev_groups = ayaneo_ec_groups,
> > > },
> > > .probe = ayaneo_ec_probe,
> > > };
> > >
> >
> > --
> > i.
> >
> >
>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes
2025-11-18 12:31 ` Ilpo Järvinen
@ 2025-11-18 12:58 ` Antheas Kapenekakis
2025-11-18 14:06 ` Armin Wolf
0 siblings, 1 reply; 18+ messages in thread
From: Antheas Kapenekakis @ 2025-11-18 12:58 UTC (permalink / raw)
To: Ilpo Järvinen
Cc: platform-driver-x86, LKML, linux-hwmon, Hans de Goede,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Armin Wolf
On Tue, 18 Nov 2025 at 13:32, Ilpo Järvinen
<ilpo.jarvinen@linux.intel.com> wrote:
>
> On Mon, 17 Nov 2025, Antheas Kapenekakis wrote:
> > On Mon, 17 Nov 2025 at 11:30, Ilpo Järvinen
> > <ilpo.jarvinen@linux.intel.com> wrote:
> > >
> > > On Thu, 13 Nov 2025, Antheas Kapenekakis wrote:
> > >
> > > > The Ayaneo 3 features hot-swappable controller modules. The ejection
> > > > and management is done through HID. However, after ejecting the modules,
> > > > the controller needs to be power cycled via the EC to re-initialize.
> > > >
> > > > For this, the EC provides a variable that holds whether the left or
> > > > right modules are connected, and a power control register to turn
> > > > the controller on or off. After ejecting the modules, the controller
> > > > should be turned off. Then, after both modules are reinserted,
> > > > the controller may be powered on again to re-initialize.
> > >
> > > If wonder if the ejecting could/should be made to turn it off without need
> > > for an explicit off command?
> >
> > Perhaps in the future, this driver leaves the possibility open for it.
> > However, that'd require a secondary HID driver to handle the full
> > ejection process, with a shared function hook for this driver.
> >
> > The eject sequence consists of sending a HID config command to start
> > the ejection, followed by a secondary config command to turn off the
> > ejection bits and then waiting for two ready bits to report that the
> > ejection is finished. Then, the controller is turned off. Apart from
> > turning off, all of this is done through HID (so HID ready races the
> > controller_modules value).
> >
> > The module status of this driver is only used to check when the
> > controller should be turned on again and to provide visual feedback
> > about which modules are currently connected while unpowered. When
> > powered, there is full status reporting over HID, including which
> > specific modules are connected[1].
> >
> > The end-to-end sequence is currently in userspace[2]. However, the EC
> > ports are shielded from userspace so these two specific accesses must
> > happen through a kernel driver.
>
> So can the other features be used without this part?
Yes. The patches are independent.
> I'd prefer to
> postpone this power/modules thing and have the ejection done properly.
The ejection process is done over vendor HID commands, not through the
EC, so it would need a new driver. This new driver would not conflict
with this patch, perhaps a new "auto" value for controller_power could
be added to have that HID driver control this.
But, I do not personally have plans to develop such a driver. I will
not oppose one of course but I cannot justify the engineer effort on
it. The userspace implementation works quite well and this is a niche
device.
Since these two registers are in protected ACPI space, they need to be
accessed through this driver, so this patch is necessary for that.
Antheas
> --
> i.
>
> >
> > Antheas
> >
> > [1] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py#L90-L117
> > [2] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py
> >
> > > > This patch introduces two new sysfs attributes:
> > > > - `controller_modules`: a read-only attribute that indicates whether
> > > > the left and right modules are connected (none, left, right, both).
> > > > - `controller_power`: a read-write attribute that allows the user
> > > > to turn the controller on or off (with '1'/'0').
> > > >
> > > > Therefore, after ejection is complete, userspace can power off the
> > > > controller, then wait until both modules have been reinserted
> > > > (`controller_modules` will return 'both') to turn on the controller.
> > > >
> > > > Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> > > > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > > > ---
> > > > .../ABI/testing/sysfs-platform-ayaneo-ec | 19 ++++
> > > > MAINTAINERS | 1 +
> > > > drivers/platform/x86/ayaneo-ec.c | 107 ++++++++++++++++++
> > > > 3 files changed, 127 insertions(+)
> > > > create mode 100644 Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> > > >
> > > > diff --git a/Documentation/ABI/testing/sysfs-platform-ayaneo-ec b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> > > > new file mode 100644
> > > > index 000000000000..4cffbf5fc7ca
> > > > --- /dev/null
> > > > +++ b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
> > > > @@ -0,0 +1,19 @@
> > > > +What: /sys/devices/platform/ayaneo-ec/controller_power
> > > > +Date: Nov 2025
> > > > +KernelVersion: 6.19
> > > > +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
> > > > +Description:
> > > > + Current controller power state. Allows turning on and off
> > > > + the controller power (e.g. for power savings). Write 1 to
> > > > + turn on, 0 to turn off. File is readable and writable.
> > > > +
> > > > +What: /sys/devices/platform/ayaneo-ec/controller_modules
> > > > +Date: Nov 2025
> > > > +KernelVersion: 6.19
> > > > +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
> > > > +Description:
> > > > + Shows which controller modules are currently connected to
> > > > + the device. Possible values are "left", "right" and "both".
> > > > + File is read-only. The Windows software for this device
> > > > + will only set controller power to 1 if both module sides
> > > > + are connected (i.e. this file returns "both").
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index c5bf7207c45f..f8ab009b6224 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -4196,6 +4196,7 @@ AYANEO PLATFORM EC DRIVER
> > > > M: Antheas Kapenekakis <lkml@antheas.dev>
> > > > L: platform-driver-x86@vger.kernel.org
> > > > S: Maintained
> > > > +F: Documentation/ABI/testing/sysfs-platform-ayaneo
> > > > F: drivers/platform/x86/ayaneo-ec.c
> > > >
> > > > AZ6007 DVB DRIVER
> > > > diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> > > > index 697bb053a7d6..a0747e7ee43a 100644
> > > > --- a/drivers/platform/x86/ayaneo-ec.c
> > > > +++ b/drivers/platform/x86/ayaneo-ec.c
> > > > @@ -8,6 +8,7 @@
> > > > */
> > > >
> > > > #include <linux/acpi.h>
> > > > +#include <linux/bits.h>
> > > > #include <linux/dmi.h>
> > > > #include <linux/err.h>
> > > > #include <linux/hwmon.h>
> > > > @@ -16,6 +17,7 @@
> > > > #include <linux/module.h>
> > > > #include <linux/platform_device.h>
> > > > #include <linux/power_supply.h>
> > > > +#include <linux/sysfs.h>
> > > > #include <acpi/battery.h>
> > > >
> > > > #define AYANEO_PWM_ENABLE_REG 0x4A
> > > > @@ -32,9 +34,18 @@
> > > > #define AYANEO_CHARGE_VAL_AUTO 0xaa
> > > > #define AYANEO_CHARGE_VAL_INHIBIT 0x55
> > > >
> > > > +#define AYANEO_POWER_REG 0x2d
> > > > +#define AYANEO_POWER_OFF 0xfe
> > > > +#define AYANEO_POWER_ON 0xff
> > > > +#define AYANEO_MODULE_REG 0x2f
> > > > +#define AYANEO_MODULE_LEFT BIT(0)
> > > > +#define AYANEO_MODULE_RIGHT BIT(1)
> > > > +#define AYANEO_MODULE_MASK (AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
> > > > +
> > > > struct ayaneo_ec_quirk {
> > > > bool has_fan_control;
> > > > bool has_charge_control;
> > > > + bool has_magic_modules;
> > > > };
> > > >
> > > > struct ayaneo_ec_platform_data {
> > > > @@ -46,6 +57,7 @@ struct ayaneo_ec_platform_data {
> > > > static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
> > > > .has_fan_control = true,
> > > > .has_charge_control = true,
> > > > + .has_magic_modules = true,
> > > > };
> > > >
> > > > static const struct dmi_system_id dmi_table[] = {
> > > > @@ -266,6 +278,100 @@ static int ayaneo_remove_battery(struct power_supply *battery,
> > > > return 0;
> > > > }
> > > >
> > > > +static ssize_t controller_power_store(struct device *dev,
> > > > + struct device_attribute *attr,
> > > > + const char *buf,
> > > > + size_t count)
> > > > +{
> > > > + bool value;
> > > > + int ret;
> > > > +
> > > > + ret = kstrtobool(buf, &value);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + return count;
> > > > +}
> > > > +
> > > > +static ssize_t controller_power_show(struct device *dev,
> > > > + struct device_attribute *attr,
> > > > + char *buf)
> > > > +{
> > > > + int ret;
> > > > + u8 val;
> > > > +
> > > > + ret = ec_read(AYANEO_POWER_REG, &val);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
> > > > +}
> > > > +
> > > > +static DEVICE_ATTR_RW(controller_power);
> > > > +
> > > > +static ssize_t controller_modules_show(struct device *dev,
> > > > + struct device_attribute *attr, char *buf)
> > > > +{
> > > > + char *out;
> > > > + int ret;
> > > > + u8 val;
> > > > +
> > > > + ret = ec_read(AYANEO_MODULE_REG, &val);
> > > > + if (ret)
> > > > + return ret;
> > > > +
> > > > + switch (~val & AYANEO_MODULE_MASK) {
> > >
> > > Thanks for adding the mask.
> > >
> > > Now when reading this again, I also suggest changing variable name from
> > > "val" to e.g. "unconnected_modules" as that would make the reason for
> > > inversion more obvious.
> > >
> > > > + case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
> > > > + out = "both";
> > > > + break;
> > > > + case AYANEO_MODULE_LEFT:
> > > > + out = "left";
> > > > + break;
> > > > + case AYANEO_MODULE_RIGHT:
> > > > + out = "right";
> > > > + break;
> > > > + default:
> > > > + out = "none";
> > > > + break;
> > > > + }
> > > > +
> > > > + return sysfs_emit(buf, "%s\n", out);
> > > > +}
> > > > +
> > > > +static DEVICE_ATTR_RO(controller_modules);
> > > > +
> > > > +static struct attribute *aya_mm_attrs[] = {
> > > > + &dev_attr_controller_power.attr,
> > > > + &dev_attr_controller_modules.attr,
> > > > + NULL
> > > > +};
> > > > +
> > > > +static umode_t aya_mm_is_visible(struct kobject *kobj,
> > > > + struct attribute *attr, int n)
> > > > +{
> > > > + struct device *dev = kobj_to_dev(kobj);
> > > > + struct platform_device *pdev = to_platform_device(dev);
> > > > + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> > > > +
> > > > + if (data->quirks->has_magic_modules)
> > > > + return attr->mode;
> > > > + return 0;
> > > > +}
> > > > +
> > > > +static const struct attribute_group aya_mm_attribute_group = {
> > > > + .is_visible = aya_mm_is_visible,
> > > > + .attrs = aya_mm_attrs,
> > > > +};
> > > > +
> > > > +static const struct attribute_group *ayaneo_ec_groups[] = {
> > > > + &aya_mm_attribute_group,
> > > > + NULL
> > > > +};
> > > > +
> > > > static int ayaneo_ec_probe(struct platform_device *pdev)
> > > > {
> > > > const struct dmi_system_id *dmi_entry;
> > > > @@ -307,6 +413,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> > > > static struct platform_driver ayaneo_platform_driver = {
> > > > .driver = {
> > > > .name = "ayaneo-ec",
> > > > + .dev_groups = ayaneo_ec_groups,
> > > > },
> > > > .probe = ayaneo_ec_probe,
> > > > };
> > > >
> > >
> > > --
> > > i.
> > >
> > >
> >
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes
2025-11-18 12:58 ` Antheas Kapenekakis
@ 2025-11-18 14:06 ` Armin Wolf
2025-11-19 15:36 ` Hans de Goede
0 siblings, 1 reply; 18+ messages in thread
From: Armin Wolf @ 2025-11-18 14:06 UTC (permalink / raw)
To: Antheas Kapenekakis, Ilpo Järvinen
Cc: platform-driver-x86, LKML, linux-hwmon, Hans de Goede,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck
Am 18.11.25 um 13:58 schrieb Antheas Kapenekakis:
> On Tue, 18 Nov 2025 at 13:32, Ilpo Järvinen
> <ilpo.jarvinen@linux.intel.com> wrote:
>> On Mon, 17 Nov 2025, Antheas Kapenekakis wrote:
>>> On Mon, 17 Nov 2025 at 11:30, Ilpo Järvinen
>>> <ilpo.jarvinen@linux.intel.com> wrote:
>>>> On Thu, 13 Nov 2025, Antheas Kapenekakis wrote:
>>>>
>>>>> The Ayaneo 3 features hot-swappable controller modules. The ejection
>>>>> and management is done through HID. However, after ejecting the modules,
>>>>> the controller needs to be power cycled via the EC to re-initialize.
>>>>>
>>>>> For this, the EC provides a variable that holds whether the left or
>>>>> right modules are connected, and a power control register to turn
>>>>> the controller on or off. After ejecting the modules, the controller
>>>>> should be turned off. Then, after both modules are reinserted,
>>>>> the controller may be powered on again to re-initialize.
>>>> If wonder if the ejecting could/should be made to turn it off without need
>>>> for an explicit off command?
>>> Perhaps in the future, this driver leaves the possibility open for it.
>>> However, that'd require a secondary HID driver to handle the full
>>> ejection process, with a shared function hook for this driver.
>>>
>>> The eject sequence consists of sending a HID config command to start
>>> the ejection, followed by a secondary config command to turn off the
>>> ejection bits and then waiting for two ready bits to report that the
>>> ejection is finished. Then, the controller is turned off. Apart from
>>> turning off, all of this is done through HID (so HID ready races the
>>> controller_modules value).
>>>
>>> The module status of this driver is only used to check when the
>>> controller should be turned on again and to provide visual feedback
>>> about which modules are currently connected while unpowered. When
>>> powered, there is full status reporting over HID, including which
>>> specific modules are connected[1].
>>>
>>> The end-to-end sequence is currently in userspace[2]. However, the EC
>>> ports are shielded from userspace so these two specific accesses must
>>> happen through a kernel driver.
>> So can the other features be used without this part?
> Yes. The patches are independent.
>
>> I'd prefer to
>> postpone this power/modules thing and have the ejection done properly.
> The ejection process is done over vendor HID commands, not through the
> EC, so it would need a new driver. This new driver would not conflict
> with this patch, perhaps a new "auto" value for controller_power could
> be added to have that HID driver control this.
>
> But, I do not personally have plans to develop such a driver. I will
> not oppose one of course but I cannot justify the engineer effort on
> it. The userspace implementation works quite well and this is a niche
> device.
>
> Since these two registers are in protected ACPI space, they need to be
> accessed through this driver, so this patch is necessary for that.
>
> Antheas
I am OK with using a userspace application to connect the HID interface
with the EC, as the ejection process already requires userspace intervention.
Thanks,
Armin Wolf
>> --
>> i.
>>
>>> Antheas
>>>
>>> [1] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py#L90-L117
>>> [2] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py
>>>
>>>>> This patch introduces two new sysfs attributes:
>>>>> - `controller_modules`: a read-only attribute that indicates whether
>>>>> the left and right modules are connected (none, left, right, both).
>>>>> - `controller_power`: a read-write attribute that allows the user
>>>>> to turn the controller on or off (with '1'/'0').
>>>>>
>>>>> Therefore, after ejection is complete, userspace can power off the
>>>>> controller, then wait until both modules have been reinserted
>>>>> (`controller_modules` will return 'both') to turn on the controller.
>>>>>
>>>>> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
>>>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>>>> ---
>>>>> .../ABI/testing/sysfs-platform-ayaneo-ec | 19 ++++
>>>>> MAINTAINERS | 1 +
>>>>> drivers/platform/x86/ayaneo-ec.c | 107 ++++++++++++++++++
>>>>> 3 files changed, 127 insertions(+)
>>>>> create mode 100644 Documentation/ABI/testing/sysfs-platform-ayaneo-ec
>>>>>
>>>>> diff --git a/Documentation/ABI/testing/sysfs-platform-ayaneo-ec b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
>>>>> new file mode 100644
>>>>> index 000000000000..4cffbf5fc7ca
>>>>> --- /dev/null
>>>>> +++ b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
>>>>> @@ -0,0 +1,19 @@
>>>>> +What: /sys/devices/platform/ayaneo-ec/controller_power
>>>>> +Date: Nov 2025
>>>>> +KernelVersion: 6.19
>>>>> +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
>>>>> +Description:
>>>>> + Current controller power state. Allows turning on and off
>>>>> + the controller power (e.g. for power savings). Write 1 to
>>>>> + turn on, 0 to turn off. File is readable and writable.
>>>>> +
>>>>> +What: /sys/devices/platform/ayaneo-ec/controller_modules
>>>>> +Date: Nov 2025
>>>>> +KernelVersion: 6.19
>>>>> +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
>>>>> +Description:
>>>>> + Shows which controller modules are currently connected to
>>>>> + the device. Possible values are "left", "right" and "both".
>>>>> + File is read-only. The Windows software for this device
>>>>> + will only set controller power to 1 if both module sides
>>>>> + are connected (i.e. this file returns "both").
>>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>>> index c5bf7207c45f..f8ab009b6224 100644
>>>>> --- a/MAINTAINERS
>>>>> +++ b/MAINTAINERS
>>>>> @@ -4196,6 +4196,7 @@ AYANEO PLATFORM EC DRIVER
>>>>> M: Antheas Kapenekakis <lkml@antheas.dev>
>>>>> L: platform-driver-x86@vger.kernel.org
>>>>> S: Maintained
>>>>> +F: Documentation/ABI/testing/sysfs-platform-ayaneo
>>>>> F: drivers/platform/x86/ayaneo-ec.c
>>>>>
>>>>> AZ6007 DVB DRIVER
>>>>> diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
>>>>> index 697bb053a7d6..a0747e7ee43a 100644
>>>>> --- a/drivers/platform/x86/ayaneo-ec.c
>>>>> +++ b/drivers/platform/x86/ayaneo-ec.c
>>>>> @@ -8,6 +8,7 @@
>>>>> */
>>>>>
>>>>> #include <linux/acpi.h>
>>>>> +#include <linux/bits.h>
>>>>> #include <linux/dmi.h>
>>>>> #include <linux/err.h>
>>>>> #include <linux/hwmon.h>
>>>>> @@ -16,6 +17,7 @@
>>>>> #include <linux/module.h>
>>>>> #include <linux/platform_device.h>
>>>>> #include <linux/power_supply.h>
>>>>> +#include <linux/sysfs.h>
>>>>> #include <acpi/battery.h>
>>>>>
>>>>> #define AYANEO_PWM_ENABLE_REG 0x4A
>>>>> @@ -32,9 +34,18 @@
>>>>> #define AYANEO_CHARGE_VAL_AUTO 0xaa
>>>>> #define AYANEO_CHARGE_VAL_INHIBIT 0x55
>>>>>
>>>>> +#define AYANEO_POWER_REG 0x2d
>>>>> +#define AYANEO_POWER_OFF 0xfe
>>>>> +#define AYANEO_POWER_ON 0xff
>>>>> +#define AYANEO_MODULE_REG 0x2f
>>>>> +#define AYANEO_MODULE_LEFT BIT(0)
>>>>> +#define AYANEO_MODULE_RIGHT BIT(1)
>>>>> +#define AYANEO_MODULE_MASK (AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
>>>>> +
>>>>> struct ayaneo_ec_quirk {
>>>>> bool has_fan_control;
>>>>> bool has_charge_control;
>>>>> + bool has_magic_modules;
>>>>> };
>>>>>
>>>>> struct ayaneo_ec_platform_data {
>>>>> @@ -46,6 +57,7 @@ struct ayaneo_ec_platform_data {
>>>>> static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
>>>>> .has_fan_control = true,
>>>>> .has_charge_control = true,
>>>>> + .has_magic_modules = true,
>>>>> };
>>>>>
>>>>> static const struct dmi_system_id dmi_table[] = {
>>>>> @@ -266,6 +278,100 @@ static int ayaneo_remove_battery(struct power_supply *battery,
>>>>> return 0;
>>>>> }
>>>>>
>>>>> +static ssize_t controller_power_store(struct device *dev,
>>>>> + struct device_attribute *attr,
>>>>> + const char *buf,
>>>>> + size_t count)
>>>>> +{
>>>>> + bool value;
>>>>> + int ret;
>>>>> +
>>>>> + ret = kstrtobool(buf, &value);
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + return count;
>>>>> +}
>>>>> +
>>>>> +static ssize_t controller_power_show(struct device *dev,
>>>>> + struct device_attribute *attr,
>>>>> + char *buf)
>>>>> +{
>>>>> + int ret;
>>>>> + u8 val;
>>>>> +
>>>>> + ret = ec_read(AYANEO_POWER_REG, &val);
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
>>>>> +}
>>>>> +
>>>>> +static DEVICE_ATTR_RW(controller_power);
>>>>> +
>>>>> +static ssize_t controller_modules_show(struct device *dev,
>>>>> + struct device_attribute *attr, char *buf)
>>>>> +{
>>>>> + char *out;
>>>>> + int ret;
>>>>> + u8 val;
>>>>> +
>>>>> + ret = ec_read(AYANEO_MODULE_REG, &val);
>>>>> + if (ret)
>>>>> + return ret;
>>>>> +
>>>>> + switch (~val & AYANEO_MODULE_MASK) {
>>>> Thanks for adding the mask.
>>>>
>>>> Now when reading this again, I also suggest changing variable name from
>>>> "val" to e.g. "unconnected_modules" as that would make the reason for
>>>> inversion more obvious.
>>>>
>>>>> + case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
>>>>> + out = "both";
>>>>> + break;
>>>>> + case AYANEO_MODULE_LEFT:
>>>>> + out = "left";
>>>>> + break;
>>>>> + case AYANEO_MODULE_RIGHT:
>>>>> + out = "right";
>>>>> + break;
>>>>> + default:
>>>>> + out = "none";
>>>>> + break;
>>>>> + }
>>>>> +
>>>>> + return sysfs_emit(buf, "%s\n", out);
>>>>> +}
>>>>> +
>>>>> +static DEVICE_ATTR_RO(controller_modules);
>>>>> +
>>>>> +static struct attribute *aya_mm_attrs[] = {
>>>>> + &dev_attr_controller_power.attr,
>>>>> + &dev_attr_controller_modules.attr,
>>>>> + NULL
>>>>> +};
>>>>> +
>>>>> +static umode_t aya_mm_is_visible(struct kobject *kobj,
>>>>> + struct attribute *attr, int n)
>>>>> +{
>>>>> + struct device *dev = kobj_to_dev(kobj);
>>>>> + struct platform_device *pdev = to_platform_device(dev);
>>>>> + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
>>>>> +
>>>>> + if (data->quirks->has_magic_modules)
>>>>> + return attr->mode;
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct attribute_group aya_mm_attribute_group = {
>>>>> + .is_visible = aya_mm_is_visible,
>>>>> + .attrs = aya_mm_attrs,
>>>>> +};
>>>>> +
>>>>> +static const struct attribute_group *ayaneo_ec_groups[] = {
>>>>> + &aya_mm_attribute_group,
>>>>> + NULL
>>>>> +};
>>>>> +
>>>>> static int ayaneo_ec_probe(struct platform_device *pdev)
>>>>> {
>>>>> const struct dmi_system_id *dmi_entry;
>>>>> @@ -307,6 +413,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
>>>>> static struct platform_driver ayaneo_platform_driver = {
>>>>> .driver = {
>>>>> .name = "ayaneo-ec",
>>>>> + .dev_groups = ayaneo_ec_groups,
>>>>> },
>>>>> .probe = ayaneo_ec_probe,
>>>>> };
>>>>>
>>>> --
>>>> i.
>>>>
>>>>
>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes
2025-11-18 14:06 ` Armin Wolf
@ 2025-11-19 15:36 ` Hans de Goede
0 siblings, 0 replies; 18+ messages in thread
From: Hans de Goede @ 2025-11-19 15:36 UTC (permalink / raw)
To: Armin Wolf, Antheas Kapenekakis, Ilpo Järvinen
Cc: platform-driver-x86, LKML, linux-hwmon, Derek John Clark,
Joaquín Ignacio Aramendía, Jean Delvare, Guenter Roeck
Hi,
Thank you for your work on supporting these handheld gaming devices.
On 18-Nov-25 3:06 PM, Armin Wolf wrote:
> Am 18.11.25 um 13:58 schrieb Antheas Kapenekakis:
>
>> On Tue, 18 Nov 2025 at 13:32, Ilpo Järvinen
>> <ilpo.jarvinen@linux.intel.com> wrote:
>>> On Mon, 17 Nov 2025, Antheas Kapenekakis wrote:
>>>> On Mon, 17 Nov 2025 at 11:30, Ilpo Järvinen
>>>> <ilpo.jarvinen@linux.intel.com> wrote:
>>>>> On Thu, 13 Nov 2025, Antheas Kapenekakis wrote:
>>>>>
>>>>>> The Ayaneo 3 features hot-swappable controller modules. The ejection
>>>>>> and management is done through HID. However, after ejecting the modules,
>>>>>> the controller needs to be power cycled via the EC to re-initialize.
>>>>>>
>>>>>> For this, the EC provides a variable that holds whether the left or
>>>>>> right modules are connected, and a power control register to turn
>>>>>> the controller on or off. After ejecting the modules, the controller
>>>>>> should be turned off. Then, after both modules are reinserted,
>>>>>> the controller may be powered on again to re-initialize.
>>>>> If wonder if the ejecting could/should be made to turn it off without need
>>>>> for an explicit off command?
>>>> Perhaps in the future, this driver leaves the possibility open for it.
>>>> However, that'd require a secondary HID driver to handle the full
>>>> ejection process, with a shared function hook for this driver.
>>>>
>>>> The eject sequence consists of sending a HID config command to start
>>>> the ejection, followed by a secondary config command to turn off the
>>>> ejection bits and then waiting for two ready bits to report that the
>>>> ejection is finished. Then, the controller is turned off. Apart from
>>>> turning off, all of this is done through HID (so HID ready races the
>>>> controller_modules value).
>>>>
>>>> The module status of this driver is only used to check when the
>>>> controller should be turned on again and to provide visual feedback
>>>> about which modules are currently connected while unpowered. When
>>>> powered, there is full status reporting over HID, including which
>>>> specific modules are connected[1].
>>>>
>>>> The end-to-end sequence is currently in userspace[2]. However, the EC
>>>> ports are shielded from userspace so these two specific accesses must
>>>> happen through a kernel driver.
>>> So can the other features be used without this part?
>> Yes. The patches are independent.
>>
>>> I'd prefer to
>>> postpone this power/modules thing and have the ejection done properly.
>> The ejection process is done over vendor HID commands, not through the
>> EC, so it would need a new driver. This new driver would not conflict
>> with this patch, perhaps a new "auto" value for controller_power could
>> be added to have that HID driver control this.
>>
>> But, I do not personally have plans to develop such a driver. I will
>> not oppose one of course but I cannot justify the engineer effort on
>> it. The userspace implementation works quite well and this is a niche
>> device.
>>
>> Since these two registers are in protected ACPI space, they need to be
>> accessed through this driver, so this patch is necessary for that.
>>
>> Antheas
>
> I am OK with using a userspace application to connect the HID interface
> with the EC, as the ejection process already requires userspace intervention.
Ack, I was wondering about this and the controller modules are sunk into
the surface of the handheld and pop out after sending a software command
which tells the hw to do something to release the modules after which they
pop out, see:
https://youtu.be/tO48dzIjMMw?t=240
So this will require a special userspace app to send the eject custom HID
command and that app can then also set the new sysfs power setting correctly.
Since this needs special userspace support anyways I do not see the need
for an in kernel HID driver, especially since interfacing between the pdx86
and HID drivers is going to be tricky inside the kernel. So leaving
coordinating the HID commands vs the power sysfs setting up to the special
userspace app sounds good to me.
Regards,
Hans
>>> --
>>> i.
>>>
>>>> Antheas
>>>>
>>>> [1] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py#L90-L117
>>>> [2] https://github.com/hhd-dev/hhd/blob/8d842e547441600b8adc806bfb10ded5718e4fe3/src/hhd/device/ayaneo/base.py
>>>>
>>>>>> This patch introduces two new sysfs attributes:
>>>>>> - `controller_modules`: a read-only attribute that indicates whether
>>>>>> the left and right modules are connected (none, left, right, both).
>>>>>> - `controller_power`: a read-write attribute that allows the user
>>>>>> to turn the controller on or off (with '1'/'0').
>>>>>>
>>>>>> Therefore, after ejection is complete, userspace can power off the
>>>>>> controller, then wait until both modules have been reinserted
>>>>>> (`controller_modules` will return 'both') to turn on the controller.
>>>>>>
>>>>>> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
>>>>>> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
>>>>>> ---
>>>>>> .../ABI/testing/sysfs-platform-ayaneo-ec | 19 ++++
>>>>>> MAINTAINERS | 1 +
>>>>>> drivers/platform/x86/ayaneo-ec.c | 107 ++++++++++++++++++
>>>>>> 3 files changed, 127 insertions(+)
>>>>>> create mode 100644 Documentation/ABI/testing/sysfs-platform-ayaneo-ec
>>>>>>
>>>>>> diff --git a/Documentation/ABI/testing/sysfs-platform-ayaneo-ec b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
>>>>>> new file mode 100644
>>>>>> index 000000000000..4cffbf5fc7ca
>>>>>> --- /dev/null
>>>>>> +++ b/Documentation/ABI/testing/sysfs-platform-ayaneo-ec
>>>>>> @@ -0,0 +1,19 @@
>>>>>> +What: /sys/devices/platform/ayaneo-ec/controller_power
>>>>>> +Date: Nov 2025
>>>>>> +KernelVersion: 6.19
>>>>>> +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
>>>>>> +Description:
>>>>>> + Current controller power state. Allows turning on and off
>>>>>> + the controller power (e.g. for power savings). Write 1 to
>>>>>> + turn on, 0 to turn off. File is readable and writable.
>>>>>> +
>>>>>> +What: /sys/devices/platform/ayaneo-ec/controller_modules
>>>>>> +Date: Nov 2025
>>>>>> +KernelVersion: 6.19
>>>>>> +Contact: "Antheas Kapenekakis" <lkml@antheas.dev>
>>>>>> +Description:
>>>>>> + Shows which controller modules are currently connected to
>>>>>> + the device. Possible values are "left", "right" and "both".
>>>>>> + File is read-only. The Windows software for this device
>>>>>> + will only set controller power to 1 if both module sides
>>>>>> + are connected (i.e. this file returns "both").
>>>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>>>> index c5bf7207c45f..f8ab009b6224 100644
>>>>>> --- a/MAINTAINERS
>>>>>> +++ b/MAINTAINERS
>>>>>> @@ -4196,6 +4196,7 @@ AYANEO PLATFORM EC DRIVER
>>>>>> M: Antheas Kapenekakis <lkml@antheas.dev>
>>>>>> L: platform-driver-x86@vger.kernel.org
>>>>>> S: Maintained
>>>>>> +F: Documentation/ABI/testing/sysfs-platform-ayaneo
>>>>>> F: drivers/platform/x86/ayaneo-ec.c
>>>>>>
>>>>>> AZ6007 DVB DRIVER
>>>>>> diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
>>>>>> index 697bb053a7d6..a0747e7ee43a 100644
>>>>>> --- a/drivers/platform/x86/ayaneo-ec.c
>>>>>> +++ b/drivers/platform/x86/ayaneo-ec.c
>>>>>> @@ -8,6 +8,7 @@
>>>>>> */
>>>>>>
>>>>>> #include <linux/acpi.h>
>>>>>> +#include <linux/bits.h>
>>>>>> #include <linux/dmi.h>
>>>>>> #include <linux/err.h>
>>>>>> #include <linux/hwmon.h>
>>>>>> @@ -16,6 +17,7 @@
>>>>>> #include <linux/module.h>
>>>>>> #include <linux/platform_device.h>
>>>>>> #include <linux/power_supply.h>
>>>>>> +#include <linux/sysfs.h>
>>>>>> #include <acpi/battery.h>
>>>>>>
>>>>>> #define AYANEO_PWM_ENABLE_REG 0x4A
>>>>>> @@ -32,9 +34,18 @@
>>>>>> #define AYANEO_CHARGE_VAL_AUTO 0xaa
>>>>>> #define AYANEO_CHARGE_VAL_INHIBIT 0x55
>>>>>>
>>>>>> +#define AYANEO_POWER_REG 0x2d
>>>>>> +#define AYANEO_POWER_OFF 0xfe
>>>>>> +#define AYANEO_POWER_ON 0xff
>>>>>> +#define AYANEO_MODULE_REG 0x2f
>>>>>> +#define AYANEO_MODULE_LEFT BIT(0)
>>>>>> +#define AYANEO_MODULE_RIGHT BIT(1)
>>>>>> +#define AYANEO_MODULE_MASK (AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT)
>>>>>> +
>>>>>> struct ayaneo_ec_quirk {
>>>>>> bool has_fan_control;
>>>>>> bool has_charge_control;
>>>>>> + bool has_magic_modules;
>>>>>> };
>>>>>>
>>>>>> struct ayaneo_ec_platform_data {
>>>>>> @@ -46,6 +57,7 @@ struct ayaneo_ec_platform_data {
>>>>>> static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
>>>>>> .has_fan_control = true,
>>>>>> .has_charge_control = true,
>>>>>> + .has_magic_modules = true,
>>>>>> };
>>>>>>
>>>>>> static const struct dmi_system_id dmi_table[] = {
>>>>>> @@ -266,6 +278,100 @@ static int ayaneo_remove_battery(struct power_supply *battery,
>>>>>> return 0;
>>>>>> }
>>>>>>
>>>>>> +static ssize_t controller_power_store(struct device *dev,
>>>>>> + struct device_attribute *attr,
>>>>>> + const char *buf,
>>>>>> + size_t count)
>>>>>> +{
>>>>>> + bool value;
>>>>>> + int ret;
>>>>>> +
>>>>>> + ret = kstrtobool(buf, &value);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + ret = ec_write(AYANEO_POWER_REG, value ? AYANEO_POWER_ON : AYANEO_POWER_OFF);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + return count;
>>>>>> +}
>>>>>> +
>>>>>> +static ssize_t controller_power_show(struct device *dev,
>>>>>> + struct device_attribute *attr,
>>>>>> + char *buf)
>>>>>> +{
>>>>>> + int ret;
>>>>>> + u8 val;
>>>>>> +
>>>>>> + ret = ec_read(AYANEO_POWER_REG, &val);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + return sysfs_emit(buf, "%d\n", val == AYANEO_POWER_ON);
>>>>>> +}
>>>>>> +
>>>>>> +static DEVICE_ATTR_RW(controller_power);
>>>>>> +
>>>>>> +static ssize_t controller_modules_show(struct device *dev,
>>>>>> + struct device_attribute *attr, char *buf)
>>>>>> +{
>>>>>> + char *out;
>>>>>> + int ret;
>>>>>> + u8 val;
>>>>>> +
>>>>>> + ret = ec_read(AYANEO_MODULE_REG, &val);
>>>>>> + if (ret)
>>>>>> + return ret;
>>>>>> +
>>>>>> + switch (~val & AYANEO_MODULE_MASK) {
>>>>> Thanks for adding the mask.
>>>>>
>>>>> Now when reading this again, I also suggest changing variable name from
>>>>> "val" to e.g. "unconnected_modules" as that would make the reason for
>>>>> inversion more obvious.
>>>>>
>>>>>> + case AYANEO_MODULE_LEFT | AYANEO_MODULE_RIGHT:
>>>>>> + out = "both";
>>>>>> + break;
>>>>>> + case AYANEO_MODULE_LEFT:
>>>>>> + out = "left";
>>>>>> + break;
>>>>>> + case AYANEO_MODULE_RIGHT:
>>>>>> + out = "right";
>>>>>> + break;
>>>>>> + default:
>>>>>> + out = "none";
>>>>>> + break;
>>>>>> + }
>>>>>> +
>>>>>> + return sysfs_emit(buf, "%s\n", out);
>>>>>> +}
>>>>>> +
>>>>>> +static DEVICE_ATTR_RO(controller_modules);
>>>>>> +
>>>>>> +static struct attribute *aya_mm_attrs[] = {
>>>>>> + &dev_attr_controller_power.attr,
>>>>>> + &dev_attr_controller_modules.attr,
>>>>>> + NULL
>>>>>> +};
>>>>>> +
>>>>>> +static umode_t aya_mm_is_visible(struct kobject *kobj,
>>>>>> + struct attribute *attr, int n)
>>>>>> +{
>>>>>> + struct device *dev = kobj_to_dev(kobj);
>>>>>> + struct platform_device *pdev = to_platform_device(dev);
>>>>>> + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
>>>>>> +
>>>>>> + if (data->quirks->has_magic_modules)
>>>>>> + return attr->mode;
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static const struct attribute_group aya_mm_attribute_group = {
>>>>>> + .is_visible = aya_mm_is_visible,
>>>>>> + .attrs = aya_mm_attrs,
>>>>>> +};
>>>>>> +
>>>>>> +static const struct attribute_group *ayaneo_ec_groups[] = {
>>>>>> + &aya_mm_attribute_group,
>>>>>> + NULL
>>>>>> +};
>>>>>> +
>>>>>> static int ayaneo_ec_probe(struct platform_device *pdev)
>>>>>> {
>>>>>> const struct dmi_system_id *dmi_entry;
>>>>>> @@ -307,6 +413,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
>>>>>> static struct platform_driver ayaneo_platform_driver = {
>>>>>> .driver = {
>>>>>> .name = "ayaneo-ec",
>>>>>> + .dev_groups = ayaneo_ec_groups,
>>>>>> },
>>>>>> .probe = ayaneo_ec_probe,
>>>>>> };
>>>>>>
>>>>> --
>>>>> i.
>>>>>
>>>>>
>>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v5 5/6] platform/x86: ayaneo-ec: Move Ayaneo devices from oxpec to ayaneo-ec
2025-11-13 21:22 [PATCH v5 0/6] platform/x86: ayaneo-ec: Add Ayaneo Embedded Controller platform driver Antheas Kapenekakis
` (3 preceding siblings ...)
2025-11-13 21:22 ` [PATCH v5 4/6] platform/x86: ayaneo-ec: Add controller power and modules attributes Antheas Kapenekakis
@ 2025-11-13 21:22 ` Antheas Kapenekakis
2025-11-13 21:22 ` [PATCH v5 6/6] platform/x86: ayaneo-ec: Add suspend hook Antheas Kapenekakis
5 siblings, 0 replies; 18+ messages in thread
From: Antheas Kapenekakis @ 2025-11-13 21:22 UTC (permalink / raw)
To: platform-driver-x86
Cc: linux-kernel, linux-hwmon, Hans de Goede, Ilpo Järvinen,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Antheas Kapenekakis, Armin Wolf
Currently, the oxpec driver contains Ayaneo devices. Move them to the
new ayaneo-ec driver, which is dedicated to them.
As this driver supports charge inhibition for Ayaneo, add support for it
for the AIR, AIR 1S, AB05-Medoncino, AIR Pro, and Kun, referenced from
the out-of-tree ayaneo-platform driver.
In addition, update the readmes of oxpec to reflect this change.
Link: https://github.com/ShadowBlip/ayaneo-platform
Tested-by: Derek J. Clark <derekjohn.clark@gmail.com>
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/Kconfig | 4 +-
drivers/platform/x86/ayaneo-ec.c | 65 +++++++++++++++++
drivers/platform/x86/oxpec.c | 115 +------------------------------
3 files changed, 67 insertions(+), 117 deletions(-)
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index b049c55057b7..40f81aef33b8 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -1043,9 +1043,7 @@ config OXP_EC
help
Enables support for the platform EC of OneXPlayer and AOKZOE
handheld devices. This includes fan speed, fan controls, and
- disabling the default TDP behavior of the device. Due to legacy
- reasons, this driver also provides hwmon functionality to Ayaneo
- devices and the OrangePi Neo.
+ disabling the default TDP behavior of the device.
source "drivers/platform/x86/tuxedo/Kconfig"
diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
index a0747e7ee43a..814f7f028710 100644
--- a/drivers/platform/x86/ayaneo-ec.c
+++ b/drivers/platform/x86/ayaneo-ec.c
@@ -54,6 +54,15 @@ struct ayaneo_ec_platform_data {
struct acpi_battery_hook battery_hook;
};
+static const struct ayaneo_ec_quirk quirk_fan = {
+ .has_fan_control = true,
+};
+
+static const struct ayaneo_ec_quirk quirk_charge_limit = {
+ .has_fan_control = true,
+ .has_charge_control = true,
+};
+
static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
.has_fan_control = true,
.has_charge_control = true,
@@ -61,6 +70,62 @@ static const struct ayaneo_ec_quirk quirk_ayaneo3 = {
};
static const struct dmi_system_id dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
+ },
+ .driver_data = (void *)&quirk_fan,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_MATCH(DMI_BOARD_NAME, "FLIP"),
+ },
+ .driver_data = (void *)&quirk_fan,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_MATCH(DMI_BOARD_NAME, "GEEK"),
+ },
+ .driver_data = (void *)&quirk_fan,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"),
+ },
+ .driver_data = (void *)&quirk_charge_limit,
+ },
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c
index 54377b282ff8..144a454103b9 100644
--- a/drivers/platform/x86/oxpec.c
+++ b/drivers/platform/x86/oxpec.c
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Platform driver for OneXPlayer and AOKZOE devices. For the time being,
- * it also exposes fan controls for AYANEO, and OrangePi Handhelds via
- * hwmon sysfs.
+ * Platform driver for OneXPlayer and AOKZOE devices.
*
* Fan control is provided via pwm interface in the range [0-255].
* Old AMD boards use [0-100] as range in the EC, the written value is
@@ -43,14 +41,6 @@ static bool unlock_global_acpi_lock(void)
enum oxp_board {
aok_zoe_a1 = 1,
- aya_neo_2,
- aya_neo_air,
- aya_neo_air_1s,
- aya_neo_air_plus_mendo,
- aya_neo_air_pro,
- aya_neo_flip,
- aya_neo_geek,
- aya_neo_kun,
orange_pi_neo,
oxp_2,
oxp_fly,
@@ -131,62 +121,6 @@ static const struct dmi_system_id dmi_table[] = {
},
.driver_data = (void *)oxp_fly,
},
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_MATCH(DMI_BOARD_NAME, "AYANEO 2"),
- },
- .driver_data = (void *)aya_neo_2,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR"),
- },
- .driver_data = (void *)aya_neo_air,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR 1S"),
- },
- .driver_data = (void *)aya_neo_air_1s,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "AB05-Mendocino"),
- },
- .driver_data = (void *)aya_neo_air_plus_mendo,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "AIR Pro"),
- },
- .driver_data = (void *)aya_neo_air_pro,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_MATCH(DMI_BOARD_NAME, "FLIP"),
- },
- .driver_data = (void *)aya_neo_flip,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_MATCH(DMI_BOARD_NAME, "GEEK"),
- },
- .driver_data = (void *)aya_neo_geek,
- },
- {
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AYANEO"),
- DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"),
- },
- .driver_data = (void *)aya_neo_kun,
- },
{
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "OrangePi"),
@@ -672,13 +606,6 @@ static int oxp_pwm_enable(void)
case orange_pi_neo:
return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_MANUAL);
case aok_zoe_a1:
- case aya_neo_2:
- case aya_neo_air:
- case aya_neo_air_plus_mendo:
- case aya_neo_air_pro:
- case aya_neo_flip:
- case aya_neo_geek:
- case aya_neo_kun:
case oxp_2:
case oxp_fly:
case oxp_mini_amd:
@@ -699,14 +626,6 @@ static int oxp_pwm_disable(void)
case orange_pi_neo:
return write_to_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, PWM_MODE_AUTO);
case aok_zoe_a1:
- case aya_neo_2:
- case aya_neo_air:
- case aya_neo_air_1s:
- case aya_neo_air_plus_mendo:
- case aya_neo_air_pro:
- case aya_neo_flip:
- case aya_neo_geek:
- case aya_neo_kun:
case oxp_2:
case oxp_fly:
case oxp_mini_amd:
@@ -727,14 +646,6 @@ static int oxp_pwm_read(long *val)
case orange_pi_neo:
return read_from_ec(ORANGEPI_SENSOR_PWM_ENABLE_REG, 1, val);
case aok_zoe_a1:
- case aya_neo_2:
- case aya_neo_air:
- case aya_neo_air_1s:
- case aya_neo_air_plus_mendo:
- case aya_neo_air_pro:
- case aya_neo_flip:
- case aya_neo_geek:
- case aya_neo_kun:
case oxp_2:
case oxp_fly:
case oxp_mini_amd:
@@ -774,14 +685,6 @@ static int oxp_pwm_fan_speed(long *val)
case oxp_g1_i:
return read_from_ec(OXP_2_SENSOR_FAN_REG, 2, val);
case aok_zoe_a1:
- case aya_neo_2:
- case aya_neo_air:
- case aya_neo_air_1s:
- case aya_neo_air_plus_mendo:
- case aya_neo_air_pro:
- case aya_neo_flip:
- case aya_neo_geek:
- case aya_neo_kun:
case oxp_fly:
case oxp_mini_amd:
case oxp_mini_amd_a07:
@@ -810,14 +713,6 @@ static int oxp_pwm_input_write(long val)
/* scale to range [0-184] */
val = (val * 184) / 255;
return write_to_ec(OXP_SENSOR_PWM_REG, val);
- case aya_neo_2:
- case aya_neo_air:
- case aya_neo_air_1s:
- case aya_neo_air_plus_mendo:
- case aya_neo_air_pro:
- case aya_neo_flip:
- case aya_neo_geek:
- case aya_neo_kun:
case oxp_mini_amd:
case oxp_mini_amd_a07:
/* scale to range [0-100] */
@@ -854,14 +749,6 @@ static int oxp_pwm_input_read(long *val)
/* scale from range [0-184] */
*val = (*val * 255) / 184;
break;
- case aya_neo_2:
- case aya_neo_air:
- case aya_neo_air_1s:
- case aya_neo_air_plus_mendo:
- case aya_neo_air_pro:
- case aya_neo_flip:
- case aya_neo_geek:
- case aya_neo_kun:
case oxp_mini_amd:
case oxp_mini_amd_a07:
ret = read_from_ec(OXP_SENSOR_PWM_REG, 1, val);
--
2.51.2
^ permalink raw reply related [flat|nested] 18+ messages in thread* [PATCH v5 6/6] platform/x86: ayaneo-ec: Add suspend hook
2025-11-13 21:22 [PATCH v5 0/6] platform/x86: ayaneo-ec: Add Ayaneo Embedded Controller platform driver Antheas Kapenekakis
` (4 preceding siblings ...)
2025-11-13 21:22 ` [PATCH v5 5/6] platform/x86: ayaneo-ec: Move Ayaneo devices from oxpec to ayaneo-ec Antheas Kapenekakis
@ 2025-11-13 21:22 ` Antheas Kapenekakis
2025-11-16 16:05 ` Armin Wolf
5 siblings, 1 reply; 18+ messages in thread
From: Antheas Kapenekakis @ 2025-11-13 21:22 UTC (permalink / raw)
To: platform-driver-x86
Cc: linux-kernel, linux-hwmon, Hans de Goede, Ilpo Järvinen,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck, Antheas Kapenekakis
The Ayaneo EC resets after hibernation, losing the charge control state.
Add a small PM hook to restore this state on hibernation resume.
The fan speed is also lost during hibernation, but since hibernation
failures are common with this class of devices, setting a low fan speed
when the userspace program controlling the fan will potentially not
take over could cause the device to overheat, so it is not restored.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
drivers/platform/x86/ayaneo-ec.c | 84 +++++++++++++++++++++++++++++++-
1 file changed, 83 insertions(+), 1 deletion(-)
diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
index 814f7f028710..3f40429acbd4 100644
--- a/drivers/platform/x86/ayaneo-ec.c
+++ b/drivers/platform/x86/ayaneo-ec.c
@@ -52,6 +52,11 @@ struct ayaneo_ec_platform_data {
struct platform_device *pdev;
struct ayaneo_ec_quirk *quirks;
struct acpi_battery_hook battery_hook;
+
+ // Protects access to restore_pwm
+ struct mutex hwmon_lock;
+ bool restore_charge_limit;
+ bool restore_pwm;
};
static const struct ayaneo_ec_quirk quirk_fan = {
@@ -208,10 +213,16 @@ static int ayaneo_ec_read(struct device *dev, enum hwmon_sensor_types type,
static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
+ struct ayaneo_ec_platform_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ guard(mutex)(&data->hwmon_lock);
+
switch (type) {
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_enable:
+ data->restore_pwm = false;
switch (val) {
case 1:
return ec_write(AYANEO_PWM_ENABLE_REG,
@@ -225,6 +236,17 @@ static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
case hwmon_pwm_input:
if (val < 0 || val > 255)
return -EINVAL;
+ if (data->restore_pwm) {
+ /*
+ * Defer restoring PWM control to after
+ * userspace resumes successfully
+ */
+ ret = ec_write(AYANEO_PWM_ENABLE_REG,
+ AYANEO_PWM_MODE_MANUAL);
+ if (ret)
+ return ret;
+ data->restore_pwm = false;
+ }
return ec_write(AYANEO_PWM_REG, (val * 100) / 255);
default:
break;
@@ -454,11 +476,14 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
data->pdev = pdev;
data->quirks = dmi_entry->driver_data;
+ ret = devm_mutex_init(&pdev->dev, &data->hwmon_lock);
+ if (ret)
+ return ret;
platform_set_drvdata(pdev, data);
if (data->quirks->has_fan_control) {
hwdev = devm_hwmon_device_register_with_info(&pdev->dev,
- "ayaneo_ec", NULL, &ayaneo_ec_chip_info, NULL);
+ "ayaneo_ec", data, &ayaneo_ec_chip_info, NULL);
if (IS_ERR(hwdev))
return PTR_ERR(hwdev);
}
@@ -475,10 +500,67 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
return 0;
}
+static int ayaneo_freeze(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
+ int ret;
+ u8 tmp;
+
+ if (data->quirks->has_charge_control) {
+ ret = ec_read(AYANEO_CHARGE_REG, &tmp);
+ if (ret)
+ return ret;
+
+ data->restore_charge_limit = tmp == AYANEO_CHARGE_VAL_INHIBIT;
+ }
+
+ if (data->quirks->has_fan_control) {
+ ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
+ if (ret)
+ return ret;
+
+ data->restore_pwm = tmp == AYANEO_PWM_MODE_MANUAL;
+
+ /*
+ * Release the fan when entering hibernation to avoid
+ * overheating if hibernation fails and hangs.
+ */
+ if (data->restore_pwm) {
+ ret = ec_write(AYANEO_PWM_ENABLE_REG, AYANEO_PWM_MODE_AUTO);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int ayaneo_restore(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
+ int ret;
+
+ if (data->quirks->has_charge_control && data->restore_charge_limit) {
+ ret = ec_write(AYANEO_CHARGE_REG, AYANEO_CHARGE_VAL_INHIBIT);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops ayaneo_pm_ops = {
+ .freeze = ayaneo_freeze,
+ .restore = ayaneo_restore,
+};
+
static struct platform_driver ayaneo_platform_driver = {
.driver = {
.name = "ayaneo-ec",
.dev_groups = ayaneo_ec_groups,
+ .pm = &ayaneo_pm_ops,
},
.probe = ayaneo_ec_probe,
};
--
2.51.2
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH v5 6/6] platform/x86: ayaneo-ec: Add suspend hook
2025-11-13 21:22 ` [PATCH v5 6/6] platform/x86: ayaneo-ec: Add suspend hook Antheas Kapenekakis
@ 2025-11-16 16:05 ` Armin Wolf
2025-11-16 16:19 ` Antheas Kapenekakis
0 siblings, 1 reply; 18+ messages in thread
From: Armin Wolf @ 2025-11-16 16:05 UTC (permalink / raw)
To: Antheas Kapenekakis, platform-driver-x86
Cc: linux-kernel, linux-hwmon, Hans de Goede, Ilpo Järvinen,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck
Am 13.11.25 um 22:22 schrieb Antheas Kapenekakis:
> The Ayaneo EC resets after hibernation, losing the charge control state.
> Add a small PM hook to restore this state on hibernation resume.
>
> The fan speed is also lost during hibernation, but since hibernation
> failures are common with this class of devices, setting a low fan speed
> when the userspace program controlling the fan will potentially not
> take over could cause the device to overheat, so it is not restored.
>
> Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> ---
> drivers/platform/x86/ayaneo-ec.c | 84 +++++++++++++++++++++++++++++++-
> 1 file changed, 83 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> index 814f7f028710..3f40429acbd4 100644
> --- a/drivers/platform/x86/ayaneo-ec.c
> +++ b/drivers/platform/x86/ayaneo-ec.c
> @@ -52,6 +52,11 @@ struct ayaneo_ec_platform_data {
> struct platform_device *pdev;
> struct ayaneo_ec_quirk *quirks;
> struct acpi_battery_hook battery_hook;
> +
> + // Protects access to restore_pwm
> + struct mutex hwmon_lock;
> + bool restore_charge_limit;
> + bool restore_pwm;
> };
>
> static const struct ayaneo_ec_quirk quirk_fan = {
> @@ -208,10 +213,16 @@ static int ayaneo_ec_read(struct device *dev, enum hwmon_sensor_types type,
> static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
> u32 attr, int channel, long val)
> {
> + struct ayaneo_ec_platform_data *data = dev_get_drvdata(dev);
> + int ret;
> +
> + guard(mutex)(&data->hwmon_lock);
> +
> switch (type) {
> case hwmon_pwm:
> switch (attr) {
> case hwmon_pwm_enable:
> + data->restore_pwm = false;
> switch (val) {
> case 1:
> return ec_write(AYANEO_PWM_ENABLE_REG,
> @@ -225,6 +236,17 @@ static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
> case hwmon_pwm_input:
> if (val < 0 || val > 255)
> return -EINVAL;
> + if (data->restore_pwm) {
> + /*
> + * Defer restoring PWM control to after
> + * userspace resumes successfully
> + */
> + ret = ec_write(AYANEO_PWM_ENABLE_REG,
> + AYANEO_PWM_MODE_MANUAL);
> + if (ret)
> + return ret;
> + data->restore_pwm = false;
> + }
> return ec_write(AYANEO_PWM_REG, (val * 100) / 255);
> default:
> break;
> @@ -454,11 +476,14 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
>
> data->pdev = pdev;
> data->quirks = dmi_entry->driver_data;
> + ret = devm_mutex_init(&pdev->dev, &data->hwmon_lock);
> + if (ret)
> + return ret;
> platform_set_drvdata(pdev, data);
>
> if (data->quirks->has_fan_control) {
> hwdev = devm_hwmon_device_register_with_info(&pdev->dev,
> - "ayaneo_ec", NULL, &ayaneo_ec_chip_info, NULL);
> + "ayaneo_ec", data, &ayaneo_ec_chip_info, NULL);
> if (IS_ERR(hwdev))
> return PTR_ERR(hwdev);
> }
> @@ -475,10 +500,67 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> return 0;
> }
>
> +static int ayaneo_freeze(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> + int ret;
> + u8 tmp;
> +
> + if (data->quirks->has_charge_control) {
> + ret = ec_read(AYANEO_CHARGE_REG, &tmp);
> + if (ret)
> + return ret;
> +
> + data->restore_charge_limit = tmp == AYANEO_CHARGE_VAL_INHIBIT;
> + }
> +
> + if (data->quirks->has_fan_control) {
> + ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
> + if (ret)
> + return ret;
> +
> + data->restore_pwm = tmp == AYANEO_PWM_MODE_MANUAL;
> +
> + /*
> + * Release the fan when entering hibernation to avoid
> + * overheating if hibernation fails and hangs.
> + */
> + if (data->restore_pwm) {
> + ret = ec_write(AYANEO_PWM_ENABLE_REG, AYANEO_PWM_MODE_AUTO);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int ayaneo_restore(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> + int ret;
> +
> + if (data->quirks->has_charge_control && data->restore_charge_limit) {
> + ret = ec_write(AYANEO_CHARGE_REG, AYANEO_CHARGE_VAL_INHIBIT);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops ayaneo_pm_ops = {
> + .freeze = ayaneo_freeze,
> + .restore = ayaneo_restore,
> +};
> +
> static struct platform_driver ayaneo_platform_driver = {
> .driver = {
> .name = "ayaneo-ec",
> .dev_groups = ayaneo_ec_groups,
> + .pm = &ayaneo_pm_ops,
Maybe you should use pm_sleep_ptr() here. With that being fixed:
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> },
> .probe = ayaneo_ec_probe,
> };
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v5 6/6] platform/x86: ayaneo-ec: Add suspend hook
2025-11-16 16:05 ` Armin Wolf
@ 2025-11-16 16:19 ` Antheas Kapenekakis
2025-11-17 9:56 ` Ilpo Järvinen
0 siblings, 1 reply; 18+ messages in thread
From: Antheas Kapenekakis @ 2025-11-16 16:19 UTC (permalink / raw)
To: Armin Wolf
Cc: platform-driver-x86, linux-kernel, linux-hwmon, Hans de Goede,
Ilpo Järvinen, Derek John Clark,
Joaquín Ignacio Aramendía, Jean Delvare, Guenter Roeck
On Sun, 16 Nov 2025 at 17:05, Armin Wolf <W_Armin@gmx.de> wrote:
>
> Am 13.11.25 um 22:22 schrieb Antheas Kapenekakis:
>
> > The Ayaneo EC resets after hibernation, losing the charge control state.
> > Add a small PM hook to restore this state on hibernation resume.
> >
> > The fan speed is also lost during hibernation, but since hibernation
> > failures are common with this class of devices, setting a low fan speed
> > when the userspace program controlling the fan will potentially not
> > take over could cause the device to overheat, so it is not restored.
> >
> > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > ---
> > drivers/platform/x86/ayaneo-ec.c | 84 +++++++++++++++++++++++++++++++-
> > 1 file changed, 83 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> > index 814f7f028710..3f40429acbd4 100644
> > --- a/drivers/platform/x86/ayaneo-ec.c
> > +++ b/drivers/platform/x86/ayaneo-ec.c
> > @@ -52,6 +52,11 @@ struct ayaneo_ec_platform_data {
> > struct platform_device *pdev;
> > struct ayaneo_ec_quirk *quirks;
> > struct acpi_battery_hook battery_hook;
> > +
> > + // Protects access to restore_pwm
> > + struct mutex hwmon_lock;
> > + bool restore_charge_limit;
> > + bool restore_pwm;
> > };
> >
> > static const struct ayaneo_ec_quirk quirk_fan = {
> > @@ -208,10 +213,16 @@ static int ayaneo_ec_read(struct device *dev, enum hwmon_sensor_types type,
> > static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
> > u32 attr, int channel, long val)
> > {
> > + struct ayaneo_ec_platform_data *data = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + guard(mutex)(&data->hwmon_lock);
> > +
> > switch (type) {
> > case hwmon_pwm:
> > switch (attr) {
> > case hwmon_pwm_enable:
> > + data->restore_pwm = false;
> > switch (val) {
> > case 1:
> > return ec_write(AYANEO_PWM_ENABLE_REG,
> > @@ -225,6 +236,17 @@ static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
> > case hwmon_pwm_input:
> > if (val < 0 || val > 255)
> > return -EINVAL;
> > + if (data->restore_pwm) {
> > + /*
> > + * Defer restoring PWM control to after
> > + * userspace resumes successfully
> > + */
> > + ret = ec_write(AYANEO_PWM_ENABLE_REG,
> > + AYANEO_PWM_MODE_MANUAL);
> > + if (ret)
> > + return ret;
> > + data->restore_pwm = false;
> > + }
> > return ec_write(AYANEO_PWM_REG, (val * 100) / 255);
> > default:
> > break;
> > @@ -454,11 +476,14 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> >
> > data->pdev = pdev;
> > data->quirks = dmi_entry->driver_data;
> > + ret = devm_mutex_init(&pdev->dev, &data->hwmon_lock);
> > + if (ret)
> > + return ret;
> > platform_set_drvdata(pdev, data);
> >
> > if (data->quirks->has_fan_control) {
> > hwdev = devm_hwmon_device_register_with_info(&pdev->dev,
> > - "ayaneo_ec", NULL, &ayaneo_ec_chip_info, NULL);
> > + "ayaneo_ec", data, &ayaneo_ec_chip_info, NULL);
> > if (IS_ERR(hwdev))
> > return PTR_ERR(hwdev);
> > }
> > @@ -475,10 +500,67 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> > return 0;
> > }
> >
> > +static int ayaneo_freeze(struct device *dev)
> > +{
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> > + int ret;
> > + u8 tmp;
> > +
> > + if (data->quirks->has_charge_control) {
> > + ret = ec_read(AYANEO_CHARGE_REG, &tmp);
> > + if (ret)
> > + return ret;
> > +
> > + data->restore_charge_limit = tmp == AYANEO_CHARGE_VAL_INHIBIT;
> > + }
> > +
> > + if (data->quirks->has_fan_control) {
> > + ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
> > + if (ret)
> > + return ret;
> > +
> > + data->restore_pwm = tmp == AYANEO_PWM_MODE_MANUAL;
> > +
> > + /*
> > + * Release the fan when entering hibernation to avoid
> > + * overheating if hibernation fails and hangs.
> > + */
> > + if (data->restore_pwm) {
> > + ret = ec_write(AYANEO_PWM_ENABLE_REG, AYANEO_PWM_MODE_AUTO);
> > + if (ret)
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ayaneo_restore(struct device *dev)
> > +{
> > + struct platform_device *pdev = to_platform_device(dev);
> > + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> > + int ret;
> > +
> > + if (data->quirks->has_charge_control && data->restore_charge_limit) {
> > + ret = ec_write(AYANEO_CHARGE_REG, AYANEO_CHARGE_VAL_INHIBIT);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static const struct dev_pm_ops ayaneo_pm_ops = {
> > + .freeze = ayaneo_freeze,
> > + .restore = ayaneo_restore,
> > +};
> > +
> > static struct platform_driver ayaneo_platform_driver = {
> > .driver = {
> > .name = "ayaneo-ec",
> > .dev_groups = ayaneo_ec_groups,
> > + .pm = &ayaneo_pm_ops,
>
> Maybe you should use pm_sleep_ptr() here. With that being fixed:
True, i conflated that with using a sleep define so I skipped it.
Seems it is a ternary that checks CONFIG_PM_SLEEP
@Ilpo lmk if I should resend this before the merge window or it's
small enough to edit inplace
Antheas
> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
>
> > },
> > .probe = ayaneo_ec_probe,
> > };
>
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH v5 6/6] platform/x86: ayaneo-ec: Add suspend hook
2025-11-16 16:19 ` Antheas Kapenekakis
@ 2025-11-17 9:56 ` Ilpo Järvinen
0 siblings, 0 replies; 18+ messages in thread
From: Ilpo Järvinen @ 2025-11-17 9:56 UTC (permalink / raw)
To: Antheas Kapenekakis
Cc: Armin Wolf, platform-driver-x86, LKML, linux-hwmon, Hans de Goede,
Derek John Clark, Joaquín Ignacio Aramendía,
Jean Delvare, Guenter Roeck
On Sun, 16 Nov 2025, Antheas Kapenekakis wrote:
> On Sun, 16 Nov 2025 at 17:05, Armin Wolf <W_Armin@gmx.de> wrote:
> >
> > Am 13.11.25 um 22:22 schrieb Antheas Kapenekakis:
> >
> > > The Ayaneo EC resets after hibernation, losing the charge control state.
> > > Add a small PM hook to restore this state on hibernation resume.
> > >
> > > The fan speed is also lost during hibernation, but since hibernation
> > > failures are common with this class of devices, setting a low fan speed
> > > when the userspace program controlling the fan will potentially not
> > > take over could cause the device to overheat, so it is not restored.
> > >
> > > Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
> > > ---
> > > drivers/platform/x86/ayaneo-ec.c | 84 +++++++++++++++++++++++++++++++-
> > > 1 file changed, 83 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
> > > index 814f7f028710..3f40429acbd4 100644
> > > --- a/drivers/platform/x86/ayaneo-ec.c
> > > +++ b/drivers/platform/x86/ayaneo-ec.c
> > > @@ -52,6 +52,11 @@ struct ayaneo_ec_platform_data {
> > > struct platform_device *pdev;
> > > struct ayaneo_ec_quirk *quirks;
> > > struct acpi_battery_hook battery_hook;
> > > +
> > > + // Protects access to restore_pwm
> > > + struct mutex hwmon_lock;
> > > + bool restore_charge_limit;
> > > + bool restore_pwm;
> > > };
> > >
> > > static const struct ayaneo_ec_quirk quirk_fan = {
> > > @@ -208,10 +213,16 @@ static int ayaneo_ec_read(struct device *dev, enum hwmon_sensor_types type,
> > > static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
> > > u32 attr, int channel, long val)
> > > {
> > > + struct ayaneo_ec_platform_data *data = dev_get_drvdata(dev);
> > > + int ret;
> > > +
> > > + guard(mutex)(&data->hwmon_lock);
> > > +
> > > switch (type) {
> > > case hwmon_pwm:
> > > switch (attr) {
> > > case hwmon_pwm_enable:
> > > + data->restore_pwm = false;
> > > switch (val) {
> > > case 1:
> > > return ec_write(AYANEO_PWM_ENABLE_REG,
> > > @@ -225,6 +236,17 @@ static int ayaneo_ec_write(struct device *dev, enum hwmon_sensor_types type,
> > > case hwmon_pwm_input:
> > > if (val < 0 || val > 255)
> > > return -EINVAL;
> > > + if (data->restore_pwm) {
> > > + /*
> > > + * Defer restoring PWM control to after
> > > + * userspace resumes successfully
> > > + */
> > > + ret = ec_write(AYANEO_PWM_ENABLE_REG,
> > > + AYANEO_PWM_MODE_MANUAL);
> > > + if (ret)
> > > + return ret;
> > > + data->restore_pwm = false;
> > > + }
> > > return ec_write(AYANEO_PWM_REG, (val * 100) / 255);
> > > default:
> > > break;
> > > @@ -454,11 +476,14 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> > >
> > > data->pdev = pdev;
> > > data->quirks = dmi_entry->driver_data;
> > > + ret = devm_mutex_init(&pdev->dev, &data->hwmon_lock);
> > > + if (ret)
> > > + return ret;
> > > platform_set_drvdata(pdev, data);
> > >
> > > if (data->quirks->has_fan_control) {
> > > hwdev = devm_hwmon_device_register_with_info(&pdev->dev,
> > > - "ayaneo_ec", NULL, &ayaneo_ec_chip_info, NULL);
> > > + "ayaneo_ec", data, &ayaneo_ec_chip_info, NULL);
> > > if (IS_ERR(hwdev))
> > > return PTR_ERR(hwdev);
> > > }
> > > @@ -475,10 +500,67 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
> > > return 0;
> > > }
> > >
> > > +static int ayaneo_freeze(struct device *dev)
> > > +{
> > > + struct platform_device *pdev = to_platform_device(dev);
> > > + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> > > + int ret;
> > > + u8 tmp;
> > > +
> > > + if (data->quirks->has_charge_control) {
> > > + ret = ec_read(AYANEO_CHARGE_REG, &tmp);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + data->restore_charge_limit = tmp == AYANEO_CHARGE_VAL_INHIBIT;
> > > + }
> > > +
> > > + if (data->quirks->has_fan_control) {
> > > + ret = ec_read(AYANEO_PWM_ENABLE_REG, &tmp);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + data->restore_pwm = tmp == AYANEO_PWM_MODE_MANUAL;
> > > +
> > > + /*
> > > + * Release the fan when entering hibernation to avoid
> > > + * overheating if hibernation fails and hangs.
> > > + */
> > > + if (data->restore_pwm) {
> > > + ret = ec_write(AYANEO_PWM_ENABLE_REG, AYANEO_PWM_MODE_AUTO);
> > > + if (ret)
> > > + return ret;
> > > + }
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int ayaneo_restore(struct device *dev)
> > > +{
> > > + struct platform_device *pdev = to_platform_device(dev);
> > > + struct ayaneo_ec_platform_data *data = platform_get_drvdata(pdev);
> > > + int ret;
> > > +
> > > + if (data->quirks->has_charge_control && data->restore_charge_limit) {
> > > + ret = ec_write(AYANEO_CHARGE_REG, AYANEO_CHARGE_VAL_INHIBIT);
> > > + if (ret)
> > > + return ret;
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static const struct dev_pm_ops ayaneo_pm_ops = {
> > > + .freeze = ayaneo_freeze,
> > > + .restore = ayaneo_restore,
> > > +};
> > > +
> > > static struct platform_driver ayaneo_platform_driver = {
> > > .driver = {
> > > .name = "ayaneo-ec",
> > > .dev_groups = ayaneo_ec_groups,
> > > + .pm = &ayaneo_pm_ops,
> >
> > Maybe you should use pm_sleep_ptr() here. With that being fixed:
>
> True, i conflated that with using a sleep define so I skipped it.
> Seems it is a ternary that checks CONFIG_PM_SLEEP
>
> @Ilpo lmk if I should resend this before the merge window or it's
> small enough to edit inplace
In general, I prefer new version over having to hand edit things myself.
I might do in-place editing of in patch format on my own volition when
replying is more effort than editing itself; but if you ask, I'll have to
reply anyway so it doesn't save even the time needed for writing the
reply.
Also, this doesn't add any headers despite adding use for struct
dev_pm_ops so you should likely add an include too.
--
i.
^ permalink raw reply [flat|nested] 18+ messages in thread