* [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
@ 2025-07-25 0:45 Derek J. Clark
2025-07-25 0:45 ` [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors Derek J. Clark
` (10 more replies)
0 siblings, 11 replies; 17+ messages in thread
From: Derek J. Clark @ 2025-07-25 0:45 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Jean Delvare, Guenter Roeck, Derek J . Clark, platform-driver-x86,
linux-kernel, linux-hwmon
Adds platform driver for Ayn Loki and Tactoy Zeenix lines of handheld
devices. This patch implements a hwmon interface for EC provided manual
PWM fan control and user defined fan curves. A global ACPI lock is used
when reading or writing from the EC.
There are 4 fan modes implemented in this patch. Modes 0-3 act in
accordance with the standard hwmon logic where 0 is 100% fan speed, 1 is
manual control, and 2 is automatic control. As the EC only provides 3
modes by default, mode 0 is implemented by setting the device to manual
and then setting fan speed to 100% directly. In mode 1 the PWM duty cycle
is set in sysfs with values [0-255], which are then scaled to the EC max
of 128. Mode 4 is an automatic mode where the fan curve is user defined.
There are 5 total set points and each set point takes a temperature in
Celsius [0-100] and a PWM duty cycle [0-255]. When the CPU temperature
reaches a given set point, the corresponding duty cycle is automatically
set by the EC.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
MAINTAINERS | 6 +
drivers/platform/x86/Kconfig | 14 +
drivers/platform/x86/Makefile | 3 +
drivers/platform/x86/ayn-ec.c | 596 ++++++++++++++++++++++++++++++++++
4 files changed, 619 insertions(+)
create mode 100644 drivers/platform/x86/ayn-ec.c
diff --git a/MAINTAINERS b/MAINTAINERS
index d61b004005fd..5b816883fe7d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4035,6 +4035,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
+AYN PLATFORM EC DRIVER
+M: Derek J. Clark <derekjohn.clark@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: drivers/platform/x86/ayn-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 6d238e120dce..61391be65a7b 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -304,6 +304,20 @@ 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 AYN_EC
+ tristate "Ayn x86 devices EC platform control"
+ depends on ACPI
+ depends on HWMON
+ select LEDS_CLASS
+ select LEDS_CLASS_MULTICOLOR
+ help
+ This is a driver for Ayn and Tactoy x86 handheld devices. It provides
+ temperature monitoring, manual fan speed control, fan curve control,
+ and chassis RGB settings.
+
+ If you have an x86 Ayn or Tactoy handheld device say M here. The module
+ will be called ayn-platform.
+
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 a0c5848513e3..d32504b89365 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -38,6 +38,9 @@ obj-$(CONFIG_ASUS_TF103C_DOCK) += asus-tf103c-dock.o
obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
+# Ayn
+obj-$(CONFIG_AYN_EC) += ayn-ec.o
+
# Cisco/Meraki
obj-$(CONFIG_MERAKI_MX100) += meraki-mx100.o
diff --git a/drivers/platform/x86/ayn-ec.c b/drivers/platform/x86/ayn-ec.c
new file mode 100644
index 000000000000..06f232bd10fa
--- /dev/null
+++ b/drivers/platform/x86/ayn-ec.c
@@ -0,0 +1,596 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Platform driver for Ayn x86 Handhelds.
+ *
+ * Implements multiple attributes provided by the EC. Fan reading and control,
+ * as well as temperature sensor readings are exposed via hwmon sysfs. EC RGB
+ * control is exposed via an led-class-multicolor interface.
+ *
+ * Fan control is provided via a pwm interface in the range [0-255]. Ayn use
+ * [0-128] as the range in the EC, the written value is scaled to accommodate.
+ * The EC also provides a configurable fan curve with five set points that
+ * associate a temperature in Celcius [0-100] with a fan speed [0-128]. The
+ * auto_point fan speeds are also scaled from the range [0-255]. Temperature
+ * readings are scaled from degrees to millidegrees when read.
+ *
+ * RGB control is provided using 4 registers. One each for the colors red,
+ * green, and blue are [0-255]. There is also a effect register that takes
+ * switches between an EC controlled breathing that cycles through all colors
+ * and fades in/out, and manual, which enables setting a user defined color.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+/* Fan reading and PWM */
+#define AYN_SENSOR_PWM_FAN_ENABLE_REG 0x10 /* PWM operating mode */
+#define AYN_SENSOR_PWM_FAN_SET_REG 0x11 /* PWM duty cycle */
+#define AYN_SENSOR_PWM_FAN_SPEED_REG 0x20 /* Fan speed */
+
+/* EC controlled fan curve registers */
+#define AYN_SENSOR_PWM_FAN_SPEED_1_REG 0x12
+#define AYN_SENSOR_PWM_FAN_SPEED_2_REG 0x14
+#define AYN_SENSOR_PWM_FAN_SPEED_3_REG 0x16
+#define AYN_SENSOR_PWM_FAN_SPEED_4_REG 0x18
+#define AYN_SENSOR_PWM_FAN_SPEED_5_REG 0x1A
+#define AYN_SENSOR_PWM_FAN_TEMP_1_REG 0x13
+#define AYN_SENSOR_PWM_FAN_TEMP_2_REG 0x15
+#define AYN_SENSOR_PWM_FAN_TEMP_3_REG 0x17
+#define AYN_SENSOR_PWM_FAN_TEMP_4_REG 0x19
+#define AYN_SENSOR_PWM_FAN_TEMP_5_REG 0x1B
+
+/* Handle ACPI lock mechanism */
+#define ACPI_LOCK_DELAY_MS 500
+enum ayn_model {
+ ayn_loki_max = 1,
+ ayn_loki_minipro,
+ ayn_loki_zero,
+ tactoy_zeenix_lite,
+};
+
+struct ayn_device {
+ u32 ayn_lock; /* ACPI EC Lock */
+} drvdata;
+
+/* Handle ACPI lock mechanism */
+#define ACPI_LOCK_DELAY_MS 500
+
+static bool lock_global_acpi_lock(void)
+{
+ return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS,
+ &drvdata.ayn_lock));
+}
+
+static bool unlock_global_acpi_lock(void)
+{
+ return ACPI_SUCCESS(acpi_release_global_lock(drvdata.ayn_lock));
+}
+
+/**
+ * read_from_ec() - Reads a value from the embedded controller.
+ *
+ * @reg: The register to start the read from.
+ * @size: The number of sequential registers the data is contained in.
+ * @val: Pointer to return the data with.
+ *
+ * Return: 0, or an error.
+ */
+static int read_from_ec(u8 reg, int size, long *val)
+{
+ int ret, i;
+ u8 buf;
+
+ if (!lock_global_acpi_lock())
+ return -EBUSY;
+
+ *val = 0;
+ for (i = 0; i < size; i++) {
+ ret = ec_read(reg + i, &buf);
+ if (ret)
+ return ret;
+ *val <<= i * 8;
+ *val += buf;
+ }
+
+ if (!unlock_global_acpi_lock())
+ return -EBUSY;
+
+ return 0;
+}
+
+/**
+ * write_to_ec() - Writes a value to the embedded controller.
+ *
+ * @reg: The register to write to.
+ * @val: Value to write
+ *
+ * Return: 0, or an error.
+ */
+static int write_to_ec(u8 reg, u8 val)
+{
+ int ret;
+
+ if (!lock_global_acpi_lock())
+ return -EBUSY;
+
+ pr_info("Writing EC value %d to register %u\n", val, reg);
+ ret = ec_write(reg, val);
+
+ if (!unlock_global_acpi_lock())
+ return -EBUSY;
+
+ return ret;
+}
+
+/**
+ * ayn_pwm_manual() - Enable manual control of the fan.
+ */
+static int ayn_pwm_manual(void)
+{
+ return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x00);
+}
+
+/**
+ * ayn_pwm_full() - Set fan to 100% speed.
+ */
+static int ayn_pwm_full(void)
+{
+ int ret;
+
+ ret = write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x00);
+ if (ret)
+ return ret;
+
+ return write_to_ec(AYN_SENSOR_PWM_FAN_SET_REG, 128);
+}
+
+/**
+ * ayn_pwm_auto() - Enable automatic EC control of the fan.
+ */
+static int ayn_pwm_auto(void)
+{
+ return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x01);
+}
+
+/**
+ * ayn_pwm_auto() - Enable manually setting the fan curve for automatic
+ * EC control of the fan.
+ */
+static int ayn_pwm_user(void)
+{
+ return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x02);
+}
+
+/**
+ * ayn_ec_hwmon_is_visible() - Determines RO or RW for hwmon attribute sysfs.
+ *
+ * @drvdata: Unused void pointer to context data.
+ * @type: The hwmon_sensor_types type.
+ * @attr: The attribute to set RO/RW on.
+ * @channel: HWMON subsystem usage flags for the attribute.
+ *
+ * Return: Permission level.
+ */
+static umode_t ayn_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;
+ }
+}
+
+/**
+ * ayn_pwm_fan_read() - Read from a hwmon pwm or fan attribute.
+ *
+ * @dev: parent device of the given attribute.
+ * @type: The hwmon_sensor_types type.
+ * @attr: The attribute to read from.
+ * @channel: HWMON subsystem usage flags for the attribute.
+ * @val: Pointer to return the read value from.
+ *
+ * Return: 0, or an error.
+ */
+static int ayn_pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ int ret;
+
+ switch (type) {
+ case hwmon_fan:
+ switch (attr) {
+ case hwmon_fan_input:
+ return read_from_ec(AYN_SENSOR_PWM_FAN_SPEED_REG, 2,
+ val);
+ default:
+ break;
+ }
+ break;
+ case hwmon_pwm:
+ switch (attr) {
+ case hwmon_pwm_enable:
+ ret = read_from_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 1,
+ val);
+ if (ret)
+ return ret;
+
+ /* EC uses 0 for manual, 1 for automatic, 2 for user
+ * fan curve. Reflect hwmon usage instead.
+ */
+ if (*val == 1) {
+ *val = 2;
+ return 0;
+ }
+
+ if (*val == 2) {
+ *val = 3;
+ return 0;
+ }
+
+ /* Return 0 when fan at max, otherwise 1 for manual. */
+ ret = read_from_ec(AYN_SENSOR_PWM_FAN_SET_REG, 1, val);
+ if (ret)
+ return ret;
+
+ if (*val == 128)
+ *val = 0;
+ else
+ *val = 1;
+
+ return ret;
+ case hwmon_pwm_input:
+ ret = read_from_ec(AYN_SENSOR_PWM_FAN_SET_REG, 1, val);
+ if (ret)
+ return ret;
+
+ *val = *val << 1; /* Max value is 128, scale to 255 */
+
+ return 0;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+/**
+ * ayn_pwm_fan_write() - Write to a hwmon pwm attribute.
+ *
+ * @dev: parent device of the given attribute.
+ * @type: The hwmon_sensor_types type.
+ * @attr: The attribute to write to.
+ * @channel: HWMON subsystem usage flags for the attribute.
+ * @val: Value to write.
+ *
+ * Return: 0, or an error.
+ */
+static int ayn_pwm_fan_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 0:
+ return ayn_pwm_full();
+ case 1:
+ return ayn_pwm_manual();
+ case 2:
+ return ayn_pwm_auto();
+ case 3:
+ return ayn_pwm_user();
+ default:
+ return -EINVAL;
+ }
+ case hwmon_pwm_input:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ val = val >> 1; /* Max value is 128, scale from 255 */
+
+ return write_to_ec(AYN_SENSOR_PWM_FAN_SET_REG, val);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return -EOPNOTSUPP;
+}
+
+static const struct hwmon_channel_info *ayn_ec_sensors[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+ NULL,
+};
+
+static const struct hwmon_ops ayn_ec_hwmon_ops = {
+ .is_visible = ayn_ec_hwmon_is_visible,
+ .read = ayn_pwm_fan_read,
+ .write = ayn_pwm_fan_write,
+};
+
+static const struct hwmon_chip_info ayn_ec_chip_info = {
+ .ops = &ayn_ec_hwmon_ops,
+ .info = ayn_ec_sensors,
+};
+
+/**
+ * pwm_curve_store() - Write a fan curve speed or temperature value.
+ *
+ * @dev: The attribute's parent device.
+ * @attr: The attribute to read.
+ * @buf: Input value string from sysfs write.
+ *
+ * Return: Number of bytes read, or an error.
+ */
+static ssize_t pwm_curve_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ int ret, i, val;
+ u8 reg;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ i = to_sensor_dev_attr(attr)->index;
+ switch (i) {
+ case 0:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG;
+ break;
+ case 1:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG;
+ break;
+ case 2:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG;
+ break;
+ case 3:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG;
+ break;
+ case 4:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG;
+ break;
+ case 5:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG;
+ break;
+ case 6:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG;
+ break;
+ case 7:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG;
+ break;
+ case 8:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG;
+ break;
+ case 9:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (i) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ if (val < 0 || val > 255)
+ return -EINVAL;
+ val = val >> 1; /* Max EC value is 128, scale from 255 */
+ break;
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ if (val < 0 || val > 100)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = write_to_ec(reg, val);
+ if (ret)
+ return ret;
+ return count;
+}
+
+/**
+ * pwm_curve_show() - Read a fan curve speed or temperature value.
+ *
+ * @dev: The attribute's parent device.
+ * @attr: The attribute to read.
+ * @buf: Buffer to read to.
+ *
+ * Return: Number of bytes read, or an error.
+ */
+static ssize_t pwm_curve_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int i, ret;
+ long val;
+ u8 reg;
+
+ i = to_sensor_dev_attr(attr)->index;
+ switch (i) {
+ case 0:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG;
+ break;
+ case 1:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG;
+ break;
+ case 2:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG;
+ break;
+ case 3:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG;
+ break;
+ case 4:
+ reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG;
+ break;
+ case 5:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG;
+ break;
+ case 6:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG;
+ break;
+ case 7:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG;
+ break;
+ case 8:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG;
+ break;
+ case 9:
+ reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = read_from_ec(reg, 1, &val);
+ if (ret)
+ return ret;
+
+ switch (i) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ val = val << 1; /* Max EC value is 128, scale to 255 */
+ break;
+ default:
+ break;
+ }
+
+ return sysfs_emit(buf, "%ld\n", val);
+}
+
+/* Fan curve attributes */
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_curve, 0);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_pwm, pwm_curve, 1);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_pwm, pwm_curve, 2);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_pwm, pwm_curve, 3);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_pwm, pwm_curve, 4);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_curve, 5);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_curve, 6);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9);
+
+static struct attribute *ayn_sensors_attrs[] = {
+ &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(ayn_sensors);
+
+static int ayn_ec_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device *hwdev;
+ int ret;
+
+ hwdev = devm_hwmon_device_register_with_info(dev, "aynec", NULL,
+ &ayn_ec_chip_info,
+ ayn_sensors_groups);
+ return PTR_ERR_OR_ZERO(hwdev);
+}
+
+static struct platform_driver ayn_ec_driver = {
+ .driver = {
+ .name = "ayn-ec",
+ },
+ .probe = ayn_ec_probe,
+};
+
+static struct platform_device *ayn_ec_device;
+
+static int __init ayn_ec_init(void)
+{
+ ayn_ec_device = platform_create_bundle(&ayn_ec_driver, ayn_ec_probe,
+ NULL, 0, NULL, 0);
+
+ return PTR_ERR_OR_ZERO(ayn_ec_device);
+}
+
+static void __exit ayn_ec_exit(void)
+{
+ platform_device_unregister(ayn_ec_device);
+ platform_driver_unregister(&ayn_ec_driver);
+}
+
+static const struct dmi_system_id ayn_dmi_table[] = {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Max"),
+ },
+ .driver_data = (void *)ayn_loki_max,
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki MiniPro"),
+ },
+ .driver_data = (void *)ayn_loki_minipro,
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Zero"),
+ },
+ .driver_data = (void *)ayn_loki_zero,
+ },
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Tectoy"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "Zeenix Lite"),
+ },
+ .driver_data = (void *)tactoy_zeenix_lite,
+ },
+ {},
+};
+
+MODULE_DEVICE_TABLE(dmi, ayn_dmi_table);
+
+module_init(ayn_ec_init);
+module_exit(ayn_ec_exit);
+
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Platform driver that handles EC sensors of Ayn x86 devices");
+MODULE_LICENSE("GPL");
--
2.50.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
@ 2025-07-25 0:45 ` Derek J. Clark
2025-07-25 15:29 ` ALOK TIWARI
2025-07-26 15:50 ` David Box
2025-07-25 0:45 ` [PATCH 3/4] platform/x86: (ayn-ec) Add RGB Interface Derek J. Clark
` (9 subsequent siblings)
10 siblings, 2 replies; 17+ messages in thread
From: Derek J. Clark @ 2025-07-25 0:45 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Jean Delvare, Guenter Roeck, Derek J . Clark, platform-driver-x86,
linux-kernel, linux-hwmon
Adds temperature sensors to the ayn-ec hwmon interface. These read-only
values include Battery, Motherboard, Charger IC, vCore, and CPU Core, as
well as labels for each entry. The temperature values provided by the EC
are whole numbers in degrees Celsius. As hwmon expects millidegrees, we
scale the raw value up.
`sensors` output after this patch is applied:
aynec-isa-0000
Adapter: ISA adapter
fan1: 1876 RPM
Battery: +29.0°C
Motherboard: +30.0°C
Charger IC: +30.0°C
vCore: +36.0°C
CPU Core: +48.0°C
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/platform/x86/ayn-ec.c | 89 +++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/drivers/platform/x86/ayn-ec.c b/drivers/platform/x86/ayn-ec.c
index 06f232bd10fa..b2054dc2358a 100644
--- a/drivers/platform/x86/ayn-ec.c
+++ b/drivers/platform/x86/ayn-ec.c
@@ -50,8 +50,16 @@
#define AYN_SENSOR_PWM_FAN_TEMP_4_REG 0x19
#define AYN_SENSOR_PWM_FAN_TEMP_5_REG 0x1B
+/* EC Teperature Sensors */
+#define AYN_SENSOR_BAT_TEMP_REG 0x04 /* Battery */
+#define AYN_SENSOR_CHARGE_TEMP_REG 0x07 /* Charger IC */
+#define AYN_SENSOR_MB_TEMP_REG 0x05 /* Motherboard */
+#define AYN_SENSOR_PROC_TEMP_REG 0x09 /* CPU Core */
+#define AYN_SENSOR_VCORE_TEMP_REG 0x08 /* vCore */
+
/* Handle ACPI lock mechanism */
#define ACPI_LOCK_DELAY_MS 500
+
enum ayn_model {
ayn_loki_max = 1,
ayn_loki_minipro,
@@ -63,6 +71,20 @@ struct ayn_device {
u32 ayn_lock; /* ACPI EC Lock */
} drvdata;
+struct thermal_sensor {
+ char *name;
+ int reg;
+};
+
+static struct thermal_sensor thermal_sensors[] = {
+ { "Battery", AYN_SENSOR_BAT_TEMP_REG },
+ { "Motherboard", AYN_SENSOR_MB_TEMP_REG },
+ { "Charger IC", AYN_SENSOR_CHARGE_TEMP_REG },
+ { "vCore", AYN_SENSOR_VCORE_TEMP_REG },
+ { "CPU Core", AYN_SENSOR_PROC_TEMP_REG },
+ {}
+};
+
/* Handle ACPI lock mechanism */
#define ACPI_LOCK_DELAY_MS 500
@@ -503,6 +525,63 @@ static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7);
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8);
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9);
+/**
+ * thermal_sensor_show() - Read a thermal sensor attribute value.
+ *
+ * @dev: The attribute's parent device.
+ * @attr: The attribute to read.
+ * @buf: Buffer to read to.
+ *
+ * Return: Number of bytes read, or an error.
+ */
+static ssize_t thermal_sensor_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ long ret, val;
+ int i;
+
+ i = to_sensor_dev_attr(attr)->index;
+
+ ret = read_from_ec(thermal_sensors[i].reg, 1, &val);
+ if (ret)
+ return ret;
+
+ val = val * 1000L;
+
+ return sysfs_emit(buf, "%ld\n", val);
+}
+
+/**
+ * thermal_sensor_label_show() - Read a thermal sensor attribute label.
+ *
+ * @dev: The attribute's parent device.
+ * @attr: The attribute to read.
+ * @buf: Buffer to read to.
+ *
+ * Return: Number of bytes read, or an error.
+ */
+static ssize_t thermal_sensor_label_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i;
+
+ i = to_sensor_dev_attr(attr)->index;
+
+ return sysfs_emit(buf, "%s\n", thermal_sensors[i].name);
+}
+
+static SENSOR_DEVICE_ATTR_RO(temp1_input, thermal_sensor, 0);
+static SENSOR_DEVICE_ATTR_RO(temp2_input, thermal_sensor, 1);
+static SENSOR_DEVICE_ATTR_RO(temp3_input, thermal_sensor, 2);
+static SENSOR_DEVICE_ATTR_RO(temp4_input, thermal_sensor, 3);
+static SENSOR_DEVICE_ATTR_RO(temp5_input, thermal_sensor, 4);
+static SENSOR_DEVICE_ATTR_RO(temp1_label, thermal_sensor_label, 0);
+static SENSOR_DEVICE_ATTR_RO(temp2_label, thermal_sensor_label, 1);
+static SENSOR_DEVICE_ATTR_RO(temp3_label, thermal_sensor_label, 2);
+static SENSOR_DEVICE_ATTR_RO(temp4_label, thermal_sensor_label, 3);
+static SENSOR_DEVICE_ATTR_RO(temp5_label, thermal_sensor_label, 4);
+
static struct attribute *ayn_sensors_attrs[] = {
&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
@@ -514,6 +593,16 @@ static struct attribute *ayn_sensors_attrs[] = {
&sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_label.dev_attr.attr,
+ &sensor_dev_attr_temp2_input.dev_attr.attr,
+ &sensor_dev_attr_temp2_label.dev_attr.attr,
+ &sensor_dev_attr_temp3_input.dev_attr.attr,
+ &sensor_dev_attr_temp3_label.dev_attr.attr,
+ &sensor_dev_attr_temp4_input.dev_attr.attr,
+ &sensor_dev_attr_temp4_label.dev_attr.attr,
+ &sensor_dev_attr_temp5_input.dev_attr.attr,
+ &sensor_dev_attr_temp5_label.dev_attr.attr,
NULL,
};
--
2.50.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 3/4] platform/x86: (ayn-ec) Add RGB Interface
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
2025-07-25 0:45 ` [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors Derek J. Clark
@ 2025-07-25 0:45 ` Derek J. Clark
2025-07-25 0:45 ` [PATCH 4/4] platform/x86: (ayn-ec) Add Ayn EC Platform Documentation Derek J. Clark
` (8 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Derek J. Clark @ 2025-07-25 0:45 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Jean Delvare, Guenter Roeck, Derek J . Clark, platform-driver-x86,
linux-kernel, linux-hwmon
Adds an EC controlled LED Multicolor Class Device for controlling the
RGB rings around the joysticks.
The EC provides a single register for each of the colors red, green, and
blue, as well as a mode switching register. The EC accepts values
[0-255] for all colors. There are two available effects: breathe, which is
the default when the device is started, and monocolor. When resuming from
sleep the user selected effect will be overwritten by the EC, so the
driver retains the last setting and resets on resume. When setting a
color, each color register is set before a final "write" code is sent to
the device. The EC may briefly reflect the "write" code when writing, but
quickly changes to the "monocolor" value once complete. The driver
interprets both of these values as "monocolor" in _show to simplify the
sysfs exposed to the user.
Two custom attributes are added to the standard LED parent device:
effect, a RW file descriptor used to set the effect, and effect_index,
which enumerates the available valid options.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/platform/x86/ayn-ec.c | 282 ++++++++++++++++++++++++++++++++++
1 file changed, 282 insertions(+)
diff --git a/drivers/platform/x86/ayn-ec.c b/drivers/platform/x86/ayn-ec.c
index b2054dc2358a..5b0931497bcc 100644
--- a/drivers/platform/x86/ayn-ec.c
+++ b/drivers/platform/x86/ayn-ec.c
@@ -28,6 +28,8 @@
#include <linux/hwmon.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>
@@ -57,6 +59,17 @@
#define AYN_SENSOR_PROC_TEMP_REG 0x09 /* CPU Core */
#define AYN_SENSOR_VCORE_TEMP_REG 0x08 /* vCore */
+/* EC Controlled RGB registers */
+#define AYN_LED_MC_RED_REG 0xB0 /* Range 0x00-0xFF */
+#define AYN_LED_MC_GREEN_REG 0xB1 /* Range 0x00-0xFF */
+#define AYN_LED_MC_BLUE_REG 0xB2 /* Range 0x00-0xFF */
+#define AYN_RGB_EFFECT_REG 0xB3
+
+/* RGB effect modes */
+#define AYN_RGB_EFFECT_BREATHE 0x00
+#define AYN_RGB_EFFECT_MONOCOLOR 0x55
+#define AYN_RGB_EFFECT_WRITE 0xAA
+
/* Handle ACPI lock mechanism */
#define ACPI_LOCK_DELAY_MS 500
@@ -68,7 +81,9 @@ enum ayn_model {
};
struct ayn_device {
+ struct led_classdev *led_cdev;
u32 ayn_lock; /* ACPI EC Lock */
+ u8 rgb_effect;
} drvdata;
struct thermal_sensor {
@@ -85,6 +100,30 @@ static struct thermal_sensor thermal_sensors[] = {
{}
};
+/* RGB effect values */
+enum RGB_EFFECT_OPTION {
+ BREATHE,
+ MONOCOLOR,
+};
+
+static const char *const RGB_EFFECT_TEXT[] = {
+ [BREATHE] = "breathe",
+ [MONOCOLOR] = "monocolor",
+};
+
+#define DEVICE_ATTR_RW_NAMED(_name, _attrname) \
+ struct device_attribute dev_attr_##_name = { \
+ .attr = { .name = _attrname, .mode = 0644 }, \
+ .show = _name##_show, \
+ .store = _name##_store, \
+ }
+
+#define DEVICE_ATTR_RO_NAMED(_name, _attrname) \
+ struct device_attribute dev_attr_##_name = { \
+ .attr = { .name = _attrname, .mode = 0444 }, \
+ .show = _name##_show, \
+ }
+
/* Handle ACPI lock mechanism */
#define ACPI_LOCK_DELAY_MS 500
@@ -608,12 +647,254 @@ static struct attribute *ayn_sensors_attrs[] = {
ATTRIBUTE_GROUPS(ayn_sensors);
+/**
+ * rgb_effect_write() - Set the RGB effect stored in drvdata.rgb_effect.
+ */
+static int rgb_effect_write(void)
+{
+ return write_to_ec(AYN_RGB_EFFECT_REG, drvdata.rgb_effect);
+};
+
+/**
+ * rgb_effect_read() - Read the RGB effect and store it in drvdata.rgb_effect.
+ */
+static int rgb_effect_read(void)
+{
+ int ret;
+ long effect;
+
+ ret = read_from_ec(AYN_RGB_EFFECT_REG, 1, &effect);
+ if (ret)
+ return ret;
+
+ switch (effect) {
+ case AYN_RGB_EFFECT_WRITE:
+ case AYN_RGB_EFFECT_MONOCOLOR:
+ drvdata.rgb_effect = AYN_RGB_EFFECT_WRITE;
+ break;
+ default:
+ drvdata.rgb_effect = AYN_RGB_EFFECT_BREATHE;
+ }
+
+ return 0;
+}
+
+/**
+ * rgb_effect_store() - Store the given RGB effect and set it.
+ *
+ * @dev: parent device of the given attribute.
+ * @attr: The attribute to write to.
+ * @buf: Input value string from sysfs write.
+ * @count: The number of bytes written.
+ *
+ * Return: The number of bytes written, or an error.
+ */
+static ssize_t rgb_effect_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ int ret;
+
+ ret = sysfs_match_string(RGB_EFFECT_TEXT, buf);
+ if (ret < 0)
+ return ret;
+
+ if (ret)
+ drvdata.rgb_effect = AYN_RGB_EFFECT_WRITE;
+ else
+ drvdata.rgb_effect = AYN_RGB_EFFECT_BREATHE;
+
+ ret = rgb_effect_write();
+ if (ret)
+ return ret;
+
+ return count;
+};
+
+/**
+ * rgb_effect_show() - Read the current RGB effect.
+ *
+ * @dev: parent device of the given attribute.
+ * @attr: The attribute to read.
+ * @buf: Buffer to read to.
+ *
+ * Return: The number of bytes read, or an error.
+ */
+static ssize_t rgb_effect_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret, i;
+
+ ret = rgb_effect_read();
+ if (ret)
+ return ret;
+
+ switch (drvdata.rgb_effect) {
+ case AYN_RGB_EFFECT_WRITE:
+ case AYN_RGB_EFFECT_MONOCOLOR:
+ i = MONOCOLOR;
+ break;
+ default:
+ i = BREATHE;
+ break;
+ }
+
+ return sysfs_emit(buf, "%s\n", RGB_EFFECT_TEXT[i]);
+};
+
+static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect");
+
+/**
+ * rgb_effect_show() - Display the RGB effects available.
+ *
+ * @dev: parent device of the given attribute.
+ * @attr: The attribute to read.
+ * @buf: Buffer to read to.
+ *
+ * Return: The number of bytes read, or an error.
+ */
+static ssize_t rgb_effect_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ size_t count = 0;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(RGB_EFFECT_TEXT); i++)
+ count += sysfs_emit_at(buf, count, "%s ", RGB_EFFECT_TEXT[i]);
+
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
+static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index");
+
+/**
+ * ayn_led_mc_brightness_set() - Write the brightness for the RGB LED.
+ *
+ * @led_cdev: Parent LED device for the led_classdev_mc.
+ * @brightness: Brightness value to write [0-255].
+ */
+static void ayn_led_mc_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *led_cdev_mc = lcdev_to_mccdev(led_cdev);
+ struct mc_subled s_led;
+ int i, ret, val;
+
+ switch (drvdata.rgb_effect) {
+ case AYN_RGB_EFFECT_WRITE:
+ case AYN_RGB_EFFECT_MONOCOLOR:
+ break;
+ case AYN_RGB_EFFECT_BREATHE:
+ return;
+ }
+
+ led_cdev->brightness = brightness;
+ for (i = 0; i < led_cdev_mc->num_colors; i++) {
+ s_led = led_cdev_mc->subled_info[i];
+ val = brightness * s_led.intensity / led_cdev->max_brightness;
+ ret = write_to_ec(s_led.channel, val);
+ if (ret) {
+ dev_err(led_cdev->dev,
+ "Error setting brightness: %d\n", ret);
+ return;
+ }
+ }
+
+ /* Must write mode again to change to set color */
+ write_to_ec(AYN_RGB_EFFECT_REG, AYN_RGB_EFFECT_WRITE);
+};
+
+/**
+ * ayn_led_mc_brightness_get() - Get the brightness for the RGB LED.
+ *
+ * @led_cdev: Parent LED device for the led_classdev_mc.
+ *
+ * Return: Current brightness.
+ */
+static enum led_brightness ayn_led_mc_brightness_get(struct led_classdev *led_cdev)
+{
+ return led_cdev->brightness;
+};
+
+static struct attribute *ayn_led_mc_attrs[] = {
+ &dev_attr_rgb_effect.attr,
+ &dev_attr_rgb_effect_index.attr,
+ NULL,
+};
+
+static struct attribute_group ayn_led_mc_group = {
+ .attrs = ayn_led_mc_attrs,
+};
+
+struct mc_subled ayn_led_mc_subled_info[] = {
+ {
+ .color_index = LED_COLOR_ID_RED,
+ .brightness = 0,
+ .intensity = 0,
+ .channel = AYN_LED_MC_RED_REG,
+ },
+ {
+ .color_index = LED_COLOR_ID_GREEN,
+ .brightness = 0,
+ .intensity = 0,
+ .channel = AYN_LED_MC_GREEN_REG,
+ },
+ {
+ .color_index = LED_COLOR_ID_BLUE,
+ .brightness = 0,
+ .intensity = 0,
+ .channel = AYN_LED_MC_BLUE_REG,
+ },
+};
+
+struct led_classdev_mc ayn_led_mc = {
+ .led_cdev = {
+ .name = "ayn:rgb:joystick_rings",
+ .brightness = 0,
+ .max_brightness = 255,
+ .brightness_set = ayn_led_mc_brightness_set,
+ .brightness_get = ayn_led_mc_brightness_get,
+ .color = LED_COLOR_ID_RGB,
+ },
+ .num_colors = ARRAY_SIZE(ayn_led_mc_subled_info),
+ .subled_info = ayn_led_mc_subled_info,
+};
+
+static int ayn_ec_resume(struct platform_device *pdev)
+{
+ struct led_classdev *led_cdev = drvdata.led_cdev;
+ int ret;
+
+ ret = rgb_effect_write();
+ if (ret)
+ return ret;
+
+ ayn_led_mc_brightness_set(led_cdev, led_cdev->brightness);
+
+ return 0;
+}
+
static int ayn_ec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device *hwdev;
int ret;
+ ret = devm_led_classdev_multicolor_register(dev, &ayn_led_mc);
+ if (ret)
+ return ret;
+
+ ret = devm_device_add_group(ayn_led_mc.led_cdev.dev, &ayn_led_mc_group);
+ if (ret)
+ return ret;
+
+ drvdata.led_cdev = &ayn_led_mc.led_cdev;
+ ret = rgb_effect_read();
+ if (ret)
+ return ret;
+
hwdev = devm_hwmon_device_register_with_info(dev, "aynec", NULL,
&ayn_ec_chip_info,
ayn_sensors_groups);
@@ -625,6 +906,7 @@ static struct platform_driver ayn_ec_driver = {
.name = "ayn-ec",
},
.probe = ayn_ec_probe,
+ .resume = ayn_ec_resume,
};
static struct platform_device *ayn_ec_device;
--
2.50.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 4/4] platform/x86: (ayn-ec) Add Ayn EC Platform Documentation
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
2025-07-25 0:45 ` [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors Derek J. Clark
2025-07-25 0:45 ` [PATCH 3/4] platform/x86: (ayn-ec) Add RGB Interface Derek J. Clark
@ 2025-07-25 0:45 ` Derek J. Clark
2025-07-25 15:08 ` [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface ALOK TIWARI
` (7 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: Derek J. Clark @ 2025-07-25 0:45 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Jean Delvare, Guenter Roeck, Derek J . Clark, platform-driver-x86,
linux-kernel, linux-hwmon
Adds ABI documentation for the ayn-ec platform driver
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
.../ABI/testing/sysfs-platform-ayn-ec | 59 +++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 60 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-platform-ayn-ec
diff --git a/Documentation/ABI/testing/sysfs-platform-ayn-ec b/Documentation/ABI/testing/sysfs-platform-ayn-ec
new file mode 100644
index 000000000000..cba68b0870ea
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-ayn-ec
@@ -0,0 +1,59 @@
+What: /sys/class/hwmon/hwmon[0-9]/pwm1_enable
+Date: July 2025
+KernelVersion: 6.17
+Contact: "Derek J. Clark" <derekjohn.clark@gmail.com>
+Description:
+ This sets the PWM fan mode of operation. Valid values are [0-3].
+ Values [0-2] conform with standard hwmon operating modes. Value 3
+ enables user defined fan curve settings.
+
+ Applies to Ayn Loki and Toctoy Zeenix lines of handheld devices.
+
+What: /sys/class/hwmon/hwmon[0-9]/pwm1_auto_point[1-5]_pwm
+Date: July 2025
+KernelVersion: 6.17
+Contact: "Derek J. Clark" <derekjohn.clark@gmail.com>
+Description:
+ This sets the PWM fan duty cycle for the given index of the fan curve.
+ When the temperature reaches the corresponding pwm1_auto_point[1-5]_temp,
+ the EC will automatically increase the fan duty cycle to the given value.
+
+ Values are [0-255]
+
+ Applies to Ayn Loki and Toctoy Zeenix lines of handheld devices.
+
+What: /sys/class/hwmon/hwmon[0-9]/pwm1_auto_point[1-5]_temp
+Date: July 2025
+KernelVersion: 6.17
+Contact: "Derek J. Clark" <derekjohn.clark@gmail.com>
+Description:
+ This sets the activation temperature for the given index of the fan curve.
+ When the temperature reaches the given value, the EC will automatically
+ increase the fan duty cycle to the corresponding pwm1_auto_point[1-5]_pwm
+ value.
+
+ Values are [0-100]
+
+ Applies to Ayn Loki and Toctoy Zeenix lines of handheld devices.
+
+What: /sys/class/leds/ayn:rgb:joystick_rings/effect
+Date: July 2025
+KernelVersion: 6.17
+Contact: "Derek J. Clark" <derekjohn.clark@gmail.com>
+Description:
+ This controls the display effect of the RGB interface.
+
+ Values are monocolor or breathe.
+
+ Applies to Ayn Loki and Toctoy Zeenix lines of handheld devices.
+
+What: /sys/class/leds/ayn:rgb:joystick_rings/effect_index
+Date: July 2025
+KernelVersion: 6.17
+Contact: "Derek J. Clark" <derekjohn.clark@gmail.com>
+Description:
+ This displays the available options for the effect attribute.
+
+ Values are monocolor or breathe.
+
+ Applies to Ayn Loki and Toctoy Zeenix lines of handheld devices.
diff --git a/MAINTAINERS b/MAINTAINERS
index 5b816883fe7d..199bebbffa0d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4039,6 +4039,7 @@ AYN PLATFORM EC DRIVER
M: Derek J. Clark <derekjohn.clark@gmail.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
+F: Documentation/ABI/testing/sysfs-platform-ayn-ec
F: drivers/platform/x86/ayn-ec.c
AZ6007 DVB DRIVER
--
2.50.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
` (2 preceding siblings ...)
2025-07-25 0:45 ` [PATCH 4/4] platform/x86: (ayn-ec) Add Ayn EC Platform Documentation Derek J. Clark
@ 2025-07-25 15:08 ` ALOK TIWARI
2025-07-26 2:48 ` Derek J. Clark
2025-07-26 10:57 ` kernel test robot
` (6 subsequent siblings)
10 siblings, 1 reply; 17+ messages in thread
From: ALOK TIWARI @ 2025-07-25 15:08 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Jean Delvare, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
> +/*
> + * Platform driver for Ayn x86 Handhelds.
> + *
> + * Implements multiple attributes provided by the EC. Fan reading and control,
> + * as well as temperature sensor readings are exposed via hwmon sysfs. EC RGB
> + * control is exposed via an led-class-multicolor interface.
> + *
> + * Fan control is provided via a pwm interface in the range [0-255]. Ayn use
> + * [0-128] as the range in the EC, the written value is scaled to accommodate.
> + * The EC also provides a configurable fan curve with five set points that
> + * associate a temperature in Celcius [0-100] with a fan speed [0-128]. The
> + * auto_point fan speeds are also scaled from the range [0-255]. Temperature
> + * readings are scaled from degrees to millidegrees when read.
> + *
> + * RGB control is provided using 4 registers. One each for the colors red,
> + * green, and blue are [0-255]. There is also a effect register that takes
> + * switches between an EC controlled breathing that cycles through all colors
> + * and fades in/out, and manual, which enables setting a user defined color.
> + *
> + * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
[clip]
> + if (val < 0 || val > 255)
> + return -EINVAL;
> + val = val >> 1; /* Max EC value is 128, scale from 255 */
> + break;
> + case 5:
> + case 6:
> + case 7:
> + case 8:
> + case 9:
> + if (val < 0 || val > 100)
> + return -EINVAL;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + ret = write_to_ec(reg, val);
> + if (ret)
> + return ret;
> + return count;
> +}
> +
> +/**
> + * pwm_curve_show() - Read a fan curve speed or temperature value.
> + *
> + * @dev: The attribute's parent device.
> + * @attr: The attribute to read.
> + * @buf: Buffer to read to.
"to read to" is awkward
Output buffer.
> + *
> + * Return: Number of bytes read, or an error.
> + */
> +static ssize_t pwm_curve_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + int i, ret;
> + long val;
> + u8 reg;
> +
> + i = to_sensor_dev_attr(attr)->index;
> + switch (i) {
> + case 0:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG;
> + break;
> + case 1:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG;
> + break;
> + case 2:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG;
> + break;
> + case 3:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG;
> + break;
> + case 4:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG;
> + break;
> + case 5:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG;
> + break;
> + case 6:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG;
> + break;
> + case 7:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG;
> + break;
> + case 8:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG;
> + break;
> + case 9:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + ret = read_from_ec(reg, 1, &val);
> + if (ret)
> + return ret;
> +
> + switch (i) {
> + case 0:
> + case 1:
> + case 2:
> + case 3:
> + case 4:
> + val = val << 1; /* Max EC value is 128, scale to 255 */
> + break;
> + default:
> + break;
> + }
> +
> + return sysfs_emit(buf, "%ld\n", val);
> +}
> +
> +/* Fan curve attributes */
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_curve, 0);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_pwm, pwm_curve, 1);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_pwm, pwm_curve, 2);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_pwm, pwm_curve, 3);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_pwm, pwm_curve, 4);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_curve, 5);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_curve, 6);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9);
> +
> +static struct attribute *ayn_sensors_attrs[] = {
> + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
> + NULL,
> +};
> +
> +ATTRIBUTE_GROUPS(ayn_sensors);
> +
> +static int ayn_ec_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device *hwdev;
> + int ret;
ret is unused.
> +
> + hwdev = devm_hwmon_device_register_with_info(dev, "aynec", NULL,
> + &ayn_ec_chip_info,
> + ayn_sensors_groups);
> + return PTR_ERR_OR_ZERO(hwdev);
> +}
> +
> +static struct platform_driver ayn_ec_driver = {
> + .driver = {
> + .name = "ayn-ec",
> + },
> + .probe = ayn_ec_probe,
> +};
Thanks,
Alok
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors
2025-07-25 0:45 ` [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors Derek J. Clark
@ 2025-07-25 15:29 ` ALOK TIWARI
2025-07-26 15:50 ` David Box
1 sibling, 0 replies; 17+ messages in thread
From: ALOK TIWARI @ 2025-07-25 15:29 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Jean Delvare, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
On 7/25/2025 6:15 AM, Derek J. Clark wrote:
> Adds temperature sensors to the ayn-ec hwmon interface. These read-only
> values include Battery, Motherboard, Charger IC, vCore, and CPU Core, as
> well as labels for each entry. The temperature values provided by the EC
> are whole numbers in degrees Celsius. As hwmon expects millidegrees, we
> scale the raw value up.
>
> `sensors` output after this patch is applied:
> aynec-isa-0000
> Adapter: ISA adapter
> fan1: 1876 RPM
> Battery: +29.0°C
> Motherboard: +30.0°C
> Charger IC: +30.0°C
> vCore: +36.0°C
> CPU Core: +48.0°C
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> drivers/platform/x86/ayn-ec.c | 89 +++++++++++++++++++++++++++++++++++
> 1 file changed, 89 insertions(+)
>
> diff --git a/drivers/platform/x86/ayn-ec.c b/drivers/platform/x86/ayn-ec.c
> index 06f232bd10fa..b2054dc2358a 100644
> --- a/drivers/platform/x86/ayn-ec.c
> +++ b/drivers/platform/x86/ayn-ec.c
> @@ -50,8 +50,16 @@
> #define AYN_SENSOR_PWM_FAN_TEMP_4_REG 0x19
> #define AYN_SENSOR_PWM_FAN_TEMP_5_REG 0x1B
>
> +/* EC Teperature Sensors */
typo Teperature
> +#define AYN_SENSOR_BAT_TEMP_REG 0x04 /* Battery */
> +#define AYN_SENSOR_CHARGE_TEMP_REG 0x07 /* Charger IC */
> +#define AYN_SENSOR_MB_TEMP_REG 0x05 /* Motherboard */
> +#define AYN_SENSOR_PROC_TEMP_REG 0x09 /* CPU Core */
> +#define AYN_SENSOR_VCORE_TEMP_REG 0x08 /* vCore */
> +
> /* Handle ACPI lock mechanism */
> #define ACPI_LOCK_DELAY_MS 500
> +
> enum ayn_model {
> ayn_loki_max = 1,
> ayn_loki_minipro,
> @@ -63,6 +71,20 @@ struct ayn_device {
> u32 ayn_lock; /* ACPI EC Lock */
> } drvdata;
>
> +struct thermal_sensor {
> + char *name;
> + int reg;
> +};
> +
> +static struct thermal_sensor thermal_sensors[] = {
> + { "Battery", AYN_SENSOR_BAT_TEMP_REG },
> + { "Motherboard", AYN_SENSOR_MB_TEMP_REG },
> + { "Charger IC", AYN_SENSOR_CHARGE_TEMP_REG },
> + { "vCore", AYN_SENSOR_VCORE_TEMP_REG },
> + { "CPU Core", AYN_SENSOR_PROC_TEMP_REG },
> + {}
> +};
> +
> /* Handle ACPI lock mechanism */
> #define ACPI_LOCK_DELAY_MS 500
>
> @@ -503,6 +525,63 @@ static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7);
> static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8);
> static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9);
>
> +/**
> + * thermal_sensor_show() - Read a thermal sensor attribute value.
> + *
> + * @dev: The attribute's parent device.
> + * @attr: The attribute to read.
> + * @buf: Buffer to read to.
@buf: Buffer to write the result into.
> + *
> + * Return: Number of bytes read, or an error.
> + */
> +static ssize_t thermal_sensor_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + long ret, val;
> + int i;
> +
> + i = to_sensor_dev_attr(attr)->index;
> +
> + ret = read_from_ec(thermal_sensors[i].reg, 1, &val);
> + if (ret)
> + return ret;
> +
> + val = val * 1000L;
> +
> + return sysfs_emit(buf, "%ld\n", val);
> +}
> +
> +/**
> + * thermal_sensor_label_show() - Read a thermal sensor attribute label.
> + *
> + * @dev: The attribute's parent device.
> + * @attr: The attribute to read.
> + * @buf: Buffer to read to.
> + *
> + * Return: Number of bytes read, or an error.
> + */
> +static ssize_t thermal_sensor_label_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int i;
> +
> + i = to_sensor_dev_attr(attr)->index;
int i = to_sensor_dev_attr(attr)->index;
> +
> + return sysfs_emit(buf, "%s\n", thermal_sensors[i].name);
> +}
> +
> +static SENSOR_DEVICE_ATTR_RO(temp1_input, thermal_sensor, 0);
> +static SENSOR_DEVICE_ATTR_RO(temp2_input, thermal_sensor, 1);
> +static SENSOR_DEVICE_ATTR_RO(temp3_input, thermal_sensor, 2);
> +static SENSOR_DEVICE_ATTR_RO(temp4_input, thermal_sensor, 3);
> +static SENSOR_DEVICE_ATTR_RO(temp5_input, thermal_sensor, 4);
> +static SENSOR_DEVICE_ATTR_RO(temp1_label, thermal_sensor_label, 0);
> +static SENSOR_DEVICE_ATTR_RO(temp2_label, thermal_sensor_label, 1);
> +static SENSOR_DEVICE_ATTR_RO(temp3_label, thermal_sensor_label, 2);
> +static SENSOR_DEVICE_ATTR_RO(temp4_label, thermal_sensor_label, 3);
> +static SENSOR_DEVICE_ATTR_RO(temp5_label, thermal_sensor_label, 4);
> +
> static struct attribute *ayn_sensors_attrs[] = {
> &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
> &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
> @@ -514,6 +593,16 @@ static struct attribute *ayn_sensors_attrs[] = {
> &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
> &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
> &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
> + &sensor_dev_attr_temp1_input.dev_attr.attr,
> + &sensor_dev_attr_temp1_label.dev_attr.attr,
> + &sensor_dev_attr_temp2_input.dev_attr.attr,
> + &sensor_dev_attr_temp2_label.dev_attr.attr,
> + &sensor_dev_attr_temp3_input.dev_attr.attr,
> + &sensor_dev_attr_temp3_label.dev_attr.attr,
> + &sensor_dev_attr_temp4_input.dev_attr.attr,
> + &sensor_dev_attr_temp4_label.dev_attr.attr,
> + &sensor_dev_attr_temp5_input.dev_attr.attr,
> + &sensor_dev_attr_temp5_label.dev_attr.attr,
> NULL,
> };
>
Thanks,
Alok
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-25 15:08 ` [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface ALOK TIWARI
@ 2025-07-26 2:48 ` Derek J. Clark
0 siblings, 0 replies; 17+ messages in thread
From: Derek J. Clark @ 2025-07-26 2:48 UTC (permalink / raw)
To: ALOK TIWARI, Ilpo Järvinen, Hans de Goede
Cc: Jean Delvare, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
On July 25, 2025 8:08:42 AM PDT, ALOK TIWARI <alok.a.tiwari@oracle.com> wrote:
>
>> +/*
>> + * Platform driver for Ayn x86 Handhelds.
>> + *
>> + * Implements multiple attributes provided by the EC. Fan reading and control,
>> + * as well as temperature sensor readings are exposed via hwmon sysfs. EC RGB
>> + * control is exposed via an led-class-multicolor interface.
>> + *
>> + * Fan control is provided via a pwm interface in the range [0-255]. Ayn use
>> + * [0-128] as the range in the EC, the written value is scaled to accommodate.
>> + * The EC also provides a configurable fan curve with five set points that
>> + * associate a temperature in Celcius [0-100] with a fan speed [0-128]. The
>> + * auto_point fan speeds are also scaled from the range [0-255]. Temperature
>> + * readings are scaled from degrees to millidegrees when read.
>> + *
>> + * RGB control is provided using 4 registers. One each for the colors red,
>> + * green, and blue are [0-255]. There is also a effect register that takes
>> + * switches between an EC controlled breathing that cycles through all colors
>> + * and fades in/out, and manual, which enables setting a user defined color.
>> + *
>> + * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>> + */
>> +
>[clip]
>> + if (val < 0 || val > 255)
>> + return -EINVAL;
>> + val = val >> 1; /* Max EC value is 128, scale from 255 */
>> + break;
>> + case 5:
>> + case 6:
>> + case 7:
>> + case 8:
>> + case 9:
>> + if (val < 0 || val > 100)
>> + return -EINVAL;
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + ret = write_to_ec(reg, val);
>> + if (ret)
>> + return ret;
>> + return count;
>> +}
>> +
>> +/**
>> + * pwm_curve_show() - Read a fan curve speed or temperature value.
>> + *
>> + * @dev: The attribute's parent device.
>> + * @attr: The attribute to read.
>> + * @buf: Buffer to read to.
>
>"to read to" is awkward
>Output buffer.
>
>> + *
>> + * Return: Number of bytes read, or an error.
>> + */
>> +static ssize_t pwm_curve_show(struct device *dev, struct device_attribute *attr,
>> + char *buf)
>> +{
>> + int i, ret;
>> + long val;
>> + u8 reg;
>> +
>> + i = to_sensor_dev_attr(attr)->index;
>> + switch (i) {
>> + case 0:
>> + reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG;
>> + break;
>> + case 1:
>> + reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG;
>> + break;
>> + case 2:
>> + reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG;
>> + break;
>> + case 3:
>> + reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG;
>> + break;
>> + case 4:
>> + reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG;
>> + break;
>> + case 5:
>> + reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG;
>> + break;
>> + case 6:
>> + reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG;
>> + break;
>> + case 7:
>> + reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG;
>> + break;
>> + case 8:
>> + reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG;
>> + break;
>> + case 9:
>> + reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG;
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + ret = read_from_ec(reg, 1, &val);
>> + if (ret)
>> + return ret;
>> +
>> + switch (i) {
>> + case 0:
>> + case 1:
>> + case 2:
>> + case 3:
>> + case 4:
>> + val = val << 1; /* Max EC value is 128, scale to 255 */
>> + break;
>> + default:
>> + break;
>> + }
>> +
>> + return sysfs_emit(buf, "%ld\n", val);
>> +}
>> +
>> +/* Fan curve attributes */
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_curve, 0);
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_pwm, pwm_curve, 1);
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_pwm, pwm_curve, 2);
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_pwm, pwm_curve, 3);
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_pwm, pwm_curve, 4);
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_curve, 5);
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_curve, 6);
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7);
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8);
>> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9);
>> +
>> +static struct attribute *ayn_sensors_attrs[] = {
>> + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
>> + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
>> + NULL,
>> +};
>> +
>> +ATTRIBUTE_GROUPS(ayn_sensors);
>> +
>> +static int ayn_ec_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct device *hwdev;
>> + int ret;
>
>ret is unused.
>
Woops, that should have been part of patch 3.
Ack all for 1/4 and 2/4 reviews. Thanks.
Derek
>> +
>> + hwdev = devm_hwmon_device_register_with_info(dev, "aynec", NULL,
>> + &ayn_ec_chip_info,
>> + ayn_sensors_groups);
>> + return PTR_ERR_OR_ZERO(hwdev);
>> +}
>> +
>> +static struct platform_driver ayn_ec_driver = {
>> + .driver = {
>> + .name = "ayn-ec",
>> + },
>> + .probe = ayn_ec_probe,
>> +};
>
>Thanks,
>Alok
- Derek
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
` (3 preceding siblings ...)
2025-07-25 15:08 ` [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface ALOK TIWARI
@ 2025-07-26 10:57 ` kernel test robot
2025-07-26 11:43 ` kernel test robot
` (5 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: kernel test robot @ 2025-07-26 10:57 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: llvm, oe-kbuild-all, Jean Delvare, Guenter Roeck, Derek J . Clark,
platform-driver-x86, linux-kernel, linux-hwmon
Hi Derek,
kernel test robot noticed the following build warnings:
[auto build test WARNING on groeck-staging/hwmon-next]
[also build test WARNING on linus/master v6.16-rc7 next-20250725]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Derek-J-Clark/platform-x86-ayn-ec-Add-Temperature-Sensors/20250725-084850
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link: https://lore.kernel.org/r/20250725004533.63537-1-derekjohn.clark%40gmail.com
patch subject: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
config: x86_64-allyesconfig (https://download.01.org/0day-ci/archive/20250726/202507261841.lk1Z3fqj-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250726/202507261841.lk1Z3fqj-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202507261841.lk1Z3fqj-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/platform/x86/ayn-ec.c:526:6: warning: unused variable 'ret' [-Wunused-variable]
526 | int ret;
| ^~~
>> drivers/platform/x86/ayn-ec.c:557:35: warning: unused variable 'ayn_dmi_table' [-Wunused-const-variable]
557 | static const struct dmi_system_id ayn_dmi_table[] = {
| ^~~~~~~~~~~~~
2 warnings generated.
vim +/ayn_dmi_table +557 drivers/platform/x86/ayn-ec.c
521
522 static int ayn_ec_probe(struct platform_device *pdev)
523 {
524 struct device *dev = &pdev->dev;
525 struct device *hwdev;
> 526 int ret;
527
528 hwdev = devm_hwmon_device_register_with_info(dev, "aynec", NULL,
529 &ayn_ec_chip_info,
530 ayn_sensors_groups);
531 return PTR_ERR_OR_ZERO(hwdev);
532 }
533
534 static struct platform_driver ayn_ec_driver = {
535 .driver = {
536 .name = "ayn-ec",
537 },
538 .probe = ayn_ec_probe,
539 };
540
541 static struct platform_device *ayn_ec_device;
542
543 static int __init ayn_ec_init(void)
544 {
545 ayn_ec_device = platform_create_bundle(&ayn_ec_driver, ayn_ec_probe,
546 NULL, 0, NULL, 0);
547
548 return PTR_ERR_OR_ZERO(ayn_ec_device);
549 }
550
551 static void __exit ayn_ec_exit(void)
552 {
553 platform_device_unregister(ayn_ec_device);
554 platform_driver_unregister(&ayn_ec_driver);
555 }
556
> 557 static const struct dmi_system_id ayn_dmi_table[] = {
558 {
559 .matches = {
560 DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
561 DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Max"),
562 },
563 .driver_data = (void *)ayn_loki_max,
564 },
565 {
566 .matches = {
567 DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
568 DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki MiniPro"),
569 },
570 .driver_data = (void *)ayn_loki_minipro,
571 },
572 {
573 .matches = {
574 DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
575 DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Zero"),
576 },
577 .driver_data = (void *)ayn_loki_zero,
578 },
579 {
580 .matches = {
581 DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Tectoy"),
582 DMI_EXACT_MATCH(DMI_BOARD_NAME, "Zeenix Lite"),
583 },
584 .driver_data = (void *)tactoy_zeenix_lite,
585 },
586 {},
587 };
588
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
` (4 preceding siblings ...)
2025-07-26 10:57 ` kernel test robot
@ 2025-07-26 11:43 ` kernel test robot
2025-07-26 12:06 ` kernel test robot
` (4 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: kernel test robot @ 2025-07-26 11:43 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all, Jean Delvare,
Guenter Roeck, Derek J . Clark, platform-driver-x86, linux-kernel,
linux-hwmon
Hi Derek,
kernel test robot noticed the following build warnings:
[auto build test WARNING on groeck-staging/hwmon-next]
[also build test WARNING on linus/master v6.16-rc7 next-20250725]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Derek-J-Clark/platform-x86-ayn-ec-Add-Temperature-Sensors/20250725-084850
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link: https://lore.kernel.org/r/20250725004533.63537-1-derekjohn.clark%40gmail.com
patch subject: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
config: i386-kismet-CONFIG_LEDS_CLASS-CONFIG_AYN_EC-0-0 (https://download.01.org/0day-ci/archive/20250726/202507261959.HASYxpK8-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250726/202507261959.HASYxpK8-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202507261959.HASYxpK8-lkp@intel.com/
kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for LEDS_CLASS when selected by AYN_EC
WARNING: unmet direct dependencies detected for LEDS_CLASS
Depends on [n]: NEW_LEDS [=n]
Selected by [y]:
- AYN_EC [=y] && X86_PLATFORM_DEVICES [=y] && ACPI [=y] && HWMON [=y]
WARNING: unmet direct dependencies detected for LEDS_CLASS_MULTICOLOR
Depends on [n]: NEW_LEDS [=n] && LEDS_CLASS [=y]
Selected by [y]:
- AYN_EC [=y] && X86_PLATFORM_DEVICES [=y] && ACPI [=y] && HWMON [=y]
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
` (5 preceding siblings ...)
2025-07-26 11:43 ` kernel test robot
@ 2025-07-26 12:06 ` kernel test robot
2025-07-26 13:20 ` kernel test robot
` (3 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: kernel test robot @ 2025-07-26 12:06 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: oe-kbuild-all, Jean Delvare, Guenter Roeck, Derek J . Clark,
platform-driver-x86, linux-kernel, linux-hwmon
Hi Derek,
kernel test robot noticed the following build errors:
[auto build test ERROR on groeck-staging/hwmon-next]
[also build test ERROR on linus/master v6.16-rc7 next-20250725]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Derek-J-Clark/platform-x86-ayn-ec-Add-Temperature-Sensors/20250725-084850
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link: https://lore.kernel.org/r/20250725004533.63537-1-derekjohn.clark%40gmail.com
patch subject: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
config: i386-randconfig-007-20250726 (https://download.01.org/0day-ci/archive/20250726/202507261928.2I5G2r8J-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250726/202507261928.2I5G2r8J-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202507261928.2I5G2r8J-lkp@intel.com/
All errors (new ones prefixed by >>):
ld: drivers/platform/x86/ayn-ec.o: in function `read_from_ec':
>> drivers/platform/x86/ayn-ec.c:99: undefined reference to `ec_read'
ld: drivers/platform/x86/ayn-ec.o: in function `write_to_ec':
>> drivers/platform/x86/ayn-ec.c:128: undefined reference to `ec_write'
vim +99 drivers/platform/x86/ayn-ec.c
79
80 /**
81 * read_from_ec() - Reads a value from the embedded controller.
82 *
83 * @reg: The register to start the read from.
84 * @size: The number of sequential registers the data is contained in.
85 * @val: Pointer to return the data with.
86 *
87 * Return: 0, or an error.
88 */
89 static int read_from_ec(u8 reg, int size, long *val)
90 {
91 int ret, i;
92 u8 buf;
93
94 if (!lock_global_acpi_lock())
95 return -EBUSY;
96
97 *val = 0;
98 for (i = 0; i < size; i++) {
> 99 ret = ec_read(reg + i, &buf);
100 if (ret)
101 return ret;
102 *val <<= i * 8;
103 *val += buf;
104 }
105
106 if (!unlock_global_acpi_lock())
107 return -EBUSY;
108
109 return 0;
110 }
111
112 /**
113 * write_to_ec() - Writes a value to the embedded controller.
114 *
115 * @reg: The register to write to.
116 * @val: Value to write
117 *
118 * Return: 0, or an error.
119 */
120 static int write_to_ec(u8 reg, u8 val)
121 {
122 int ret;
123
124 if (!lock_global_acpi_lock())
125 return -EBUSY;
126
127 pr_info("Writing EC value %d to register %u\n", val, reg);
> 128 ret = ec_write(reg, val);
129
130 if (!unlock_global_acpi_lock())
131 return -EBUSY;
132
133 return ret;
134 }
135
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
` (6 preceding siblings ...)
2025-07-26 12:06 ` kernel test robot
@ 2025-07-26 13:20 ` kernel test robot
2025-07-26 15:34 ` kernel test robot
` (2 subsequent siblings)
10 siblings, 0 replies; 17+ messages in thread
From: kernel test robot @ 2025-07-26 13:20 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all, Jean Delvare,
Guenter Roeck, Derek J . Clark, platform-driver-x86, linux-kernel,
linux-hwmon
Hi Derek,
kernel test robot noticed the following build warnings:
[auto build test WARNING on groeck-staging/hwmon-next]
[also build test WARNING on linus/master v6.16-rc7 next-20250725]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Derek-J-Clark/platform-x86-ayn-ec-Add-Temperature-Sensors/20250725-084850
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link: https://lore.kernel.org/r/20250725004533.63537-1-derekjohn.clark%40gmail.com
patch subject: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
config: i386-kismet-CONFIG_LEDS_CLASS_MULTICOLOR-CONFIG_AYN_EC-0-0 (https://download.01.org/0day-ci/archive/20250726/202507262131.od1xuoCj-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250726/202507262131.od1xuoCj-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202507262131.od1xuoCj-lkp@intel.com/
kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for LEDS_CLASS_MULTICOLOR when selected by AYN_EC
WARNING: unmet direct dependencies detected for LEDS_CLASS
Depends on [n]: NEW_LEDS [=n]
Selected by [y]:
- AYN_EC [=y] && X86_PLATFORM_DEVICES [=y] && ACPI [=y] && HWMON [=y]
WARNING: unmet direct dependencies detected for LEDS_CLASS_MULTICOLOR
Depends on [n]: NEW_LEDS [=n] && LEDS_CLASS [=y]
Selected by [y]:
- AYN_EC [=y] && X86_PLATFORM_DEVICES [=y] && ACPI [=y] && HWMON [=y]
WARNING: unmet direct dependencies detected for LEDS_TRIGGERS
Depends on [n]: NEW_LEDS [=n] && LEDS_CLASS [=y]
Selected by [y]:
- BT_LEDS [=y] && NET [=y] && BT [=y] && LEDS_CLASS [=y]
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
` (7 preceding siblings ...)
2025-07-26 13:20 ` kernel test robot
@ 2025-07-26 15:34 ` kernel test robot
2025-07-26 15:47 ` David Box
2025-07-26 17:21 ` kernel test robot
10 siblings, 0 replies; 17+ messages in thread
From: kernel test robot @ 2025-07-26 15:34 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all, Jean Delvare,
Guenter Roeck, Derek J . Clark, platform-driver-x86, linux-kernel,
linux-hwmon
Hi Derek,
kernel test robot noticed the following build warnings:
[auto build test WARNING on groeck-staging/hwmon-next]
[also build test WARNING on linus/master v6.16-rc7 next-20250725]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Derek-J-Clark/platform-x86-ayn-ec-Add-Temperature-Sensors/20250725-084850
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link: https://lore.kernel.org/r/20250725004533.63537-1-derekjohn.clark%40gmail.com
patch subject: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
config: i386-kismet-CONFIG_LEDS_GPIO-CONFIG_MERAKI_MX100-0-0 (https://download.01.org/0day-ci/archive/20250726/202507262209.8s76hPFo-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250726/202507262209.8s76hPFo-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202507262209.8s76hPFo-lkp@intel.com/
kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for LEDS_GPIO when selected by MERAKI_MX100
WARNING: unmet direct dependencies detected for LEDS_CLASS
Depends on [n]: NEW_LEDS [=n]
Selected by [y]:
- AYN_EC [=y] && X86_PLATFORM_DEVICES [=y] && ACPI [=y] && HWMON [=y]
WARNING: unmet direct dependencies detected for LEDS_CLASS_MULTICOLOR
Depends on [n]: NEW_LEDS [=n] && LEDS_CLASS [=y]
Selected by [y]:
- AYN_EC [=y] && X86_PLATFORM_DEVICES [=y] && ACPI [=y] && HWMON [=y]
WARNING: unmet direct dependencies detected for LEDS_GPIO
Depends on [n]: NEW_LEDS [=n] && LEDS_CLASS [=y] && (GPIOLIB [=y] || COMPILE_TEST [=y])
Selected by [y]:
- MERAKI_MX100 [=y] && X86_PLATFORM_DEVICES [=y] && GPIOLIB [=y] && GPIO_ICH [=y] && LEDS_CLASS [=y]
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
` (8 preceding siblings ...)
2025-07-26 15:34 ` kernel test robot
@ 2025-07-26 15:47 ` David Box
2025-07-26 17:51 ` Derek John Clark
2025-07-26 17:21 ` kernel test robot
10 siblings, 1 reply; 17+ messages in thread
From: David Box @ 2025-07-26 15:47 UTC (permalink / raw)
To: Derek J. Clark
Cc: Ilpo Järvinen, Hans de Goede, Jean Delvare, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
Hi,
On Thu, Jul 24, 2025 at 05:45:30PM -0700, Derek J. Clark wrote:
> Adds platform driver for Ayn Loki and Tactoy Zeenix lines of handheld
> devices. This patch implements a hwmon interface for EC provided manual
> PWM fan control and user defined fan curves. A global ACPI lock is used
> when reading or writing from the EC.
>
> There are 4 fan modes implemented in this patch. Modes 0-3 act in
> accordance with the standard hwmon logic where 0 is 100% fan speed, 1 is
> manual control, and 2 is automatic control. As the EC only provides 3
> modes by default, mode 0 is implemented by setting the device to manual
> and then setting fan speed to 100% directly. In mode 1 the PWM duty cycle
> is set in sysfs with values [0-255], which are then scaled to the EC max
> of 128. Mode 4 is an automatic mode where the fan curve is user defined.
> There are 5 total set points and each set point takes a temperature in
> Celsius [0-100] and a PWM duty cycle [0-255]. When the CPU temperature
> reaches a given set point, the corresponding duty cycle is automatically
> set by the EC.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> MAINTAINERS | 6 +
> drivers/platform/x86/Kconfig | 14 +
> drivers/platform/x86/Makefile | 3 +
> drivers/platform/x86/ayn-ec.c | 596 ++++++++++++++++++++++++++++++++++
> 4 files changed, 619 insertions(+)
> create mode 100644 drivers/platform/x86/ayn-ec.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d61b004005fd..5b816883fe7d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4035,6 +4035,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
>
> +AYN PLATFORM EC DRIVER
> +M: Derek J. Clark <derekjohn.clark@gmail.com>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: drivers/platform/x86/ayn-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 6d238e120dce..61391be65a7b 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -304,6 +304,20 @@ 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 AYN_EC
> + tristate "Ayn x86 devices EC platform control"
> + depends on ACPI
> + depends on HWMON
> + select LEDS_CLASS
> + select LEDS_CLASS_MULTICOLOR
> + help
> + This is a driver for Ayn and Tactoy x86 handheld devices. It provides
> + temperature monitoring, manual fan speed control, fan curve control,
> + and chassis RGB settings.
> +
> + If you have an x86 Ayn or Tactoy handheld device say M here. The module
> + will be called ayn-platform.
> +
> 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 a0c5848513e3..d32504b89365 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -38,6 +38,9 @@ obj-$(CONFIG_ASUS_TF103C_DOCK) += asus-tf103c-dock.o
> obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
> obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
>
> +# Ayn
> +obj-$(CONFIG_AYN_EC) += ayn-ec.o
> +
> # Cisco/Meraki
> obj-$(CONFIG_MERAKI_MX100) += meraki-mx100.o
>
> diff --git a/drivers/platform/x86/ayn-ec.c b/drivers/platform/x86/ayn-ec.c
> new file mode 100644
> index 000000000000..06f232bd10fa
> --- /dev/null
> +++ b/drivers/platform/x86/ayn-ec.c
> @@ -0,0 +1,596 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Platform driver for Ayn x86 Handhelds.
> + *
> + * Implements multiple attributes provided by the EC. Fan reading and control,
> + * as well as temperature sensor readings are exposed via hwmon sysfs. EC RGB
> + * control is exposed via an led-class-multicolor interface.
> + *
> + * Fan control is provided via a pwm interface in the range [0-255]. Ayn use
> + * [0-128] as the range in the EC, the written value is scaled to accommodate.
> + * The EC also provides a configurable fan curve with five set points that
> + * associate a temperature in Celcius [0-100] with a fan speed [0-128]. The
> + * auto_point fan speeds are also scaled from the range [0-255]. Temperature
> + * readings are scaled from degrees to millidegrees when read.
> + *
> + * RGB control is provided using 4 registers. One each for the colors red,
> + * green, and blue are [0-255]. There is also a effect register that takes
> + * switches between an EC controlled breathing that cycles through all colors
> + * and fades in/out, and manual, which enables setting a user defined color.
> + *
> + * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/dmi.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/hwmon.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/sysfs.h>
> +#include <linux/types.h>
> +
> +/* Fan reading and PWM */
> +#define AYN_SENSOR_PWM_FAN_ENABLE_REG 0x10 /* PWM operating mode */
> +#define AYN_SENSOR_PWM_FAN_SET_REG 0x11 /* PWM duty cycle */
> +#define AYN_SENSOR_PWM_FAN_SPEED_REG 0x20 /* Fan speed */
> +
> +/* EC controlled fan curve registers */
> +#define AYN_SENSOR_PWM_FAN_SPEED_1_REG 0x12
> +#define AYN_SENSOR_PWM_FAN_SPEED_2_REG 0x14
> +#define AYN_SENSOR_PWM_FAN_SPEED_3_REG 0x16
> +#define AYN_SENSOR_PWM_FAN_SPEED_4_REG 0x18
> +#define AYN_SENSOR_PWM_FAN_SPEED_5_REG 0x1A
> +#define AYN_SENSOR_PWM_FAN_TEMP_1_REG 0x13
> +#define AYN_SENSOR_PWM_FAN_TEMP_2_REG 0x15
> +#define AYN_SENSOR_PWM_FAN_TEMP_3_REG 0x17
> +#define AYN_SENSOR_PWM_FAN_TEMP_4_REG 0x19
> +#define AYN_SENSOR_PWM_FAN_TEMP_5_REG 0x1B
> +
> +/* Handle ACPI lock mechanism */
> +#define ACPI_LOCK_DELAY_MS 500
> +enum ayn_model {
> + ayn_loki_max = 1,
> + ayn_loki_minipro,
> + ayn_loki_zero,
> + tactoy_zeenix_lite,
> +};
> +
> +struct ayn_device {
> + u32 ayn_lock; /* ACPI EC Lock */
> +} drvdata;
> +
> +/* Handle ACPI lock mechanism */
> +#define ACPI_LOCK_DELAY_MS 500
> +
> +static bool lock_global_acpi_lock(void)
> +{
> + return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS,
> + &drvdata.ayn_lock));
> +}
> +
> +static bool unlock_global_acpi_lock(void)
> +{
> + return ACPI_SUCCESS(acpi_release_global_lock(drvdata.ayn_lock));
> +}
> +
> +/**
> + * read_from_ec() - Reads a value from the embedded controller.
> + *
> + * @reg: The register to start the read from.
> + * @size: The number of sequential registers the data is contained in.
> + * @val: Pointer to return the data with.
> + *
> + * Return: 0, or an error.
> + */
> +static int read_from_ec(u8 reg, int size, long *val)
> +{
> + int ret, i;
> + u8 buf;
> +
> + if (!lock_global_acpi_lock())
> + return -EBUSY;
> +
> + *val = 0;
> + for (i = 0; i < size; i++) {
> + ret = ec_read(reg + i, &buf);
> + if (ret)
> + return ret;
> + *val <<= i * 8;
> + *val += buf;
> + }
> +
> + if (!unlock_global_acpi_lock())
> + return -EBUSY;
> +
> + return 0;
> +}
> +
> +/**
> + * write_to_ec() - Writes a value to the embedded controller.
> + *
> + * @reg: The register to write to.
> + * @val: Value to write
> + *
> + * Return: 0, or an error.
> + */
> +static int write_to_ec(u8 reg, u8 val)
> +{
> + int ret;
> +
> + if (!lock_global_acpi_lock())
> + return -EBUSY;
> +
> + pr_info("Writing EC value %d to register %u\n", val, reg);
pr_debug? You don't want to write out to the log every time.
> + ret = ec_write(reg, val);
> +
> + if (!unlock_global_acpi_lock())
> + return -EBUSY;
> +
> + return ret;
> +}
> +
> +/**
> + * ayn_pwm_manual() - Enable manual control of the fan.
> + */
> +static int ayn_pwm_manual(void)
> +{
> + return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x00);
> +}
> +
> +/**
> + * ayn_pwm_full() - Set fan to 100% speed.
> + */
> +static int ayn_pwm_full(void)
> +{
> + int ret;
> +
> + ret = write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x00);
> + if (ret)
> + return ret;
> +
> + return write_to_ec(AYN_SENSOR_PWM_FAN_SET_REG, 128);
> +}
> +
> +/**
> + * ayn_pwm_auto() - Enable automatic EC control of the fan.
> + */
> +static int ayn_pwm_auto(void)
> +{
> + return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x01);
> +}
> +
> +/**
> + * ayn_pwm_auto() - Enable manually setting the fan curve for automatic
> + * EC control of the fan.
> + */
> +static int ayn_pwm_user(void)
> +{
> + return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x02);
> +}
> +
> +/**
> + * ayn_ec_hwmon_is_visible() - Determines RO or RW for hwmon attribute sysfs.
> + *
> + * @drvdata: Unused void pointer to context data.
> + * @type: The hwmon_sensor_types type.
> + * @attr: The attribute to set RO/RW on.
> + * @channel: HWMON subsystem usage flags for the attribute.
> + *
> + * Return: Permission level.
> + */
> +static umode_t ayn_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;
> + }
> +}
> +
> +/**
> + * ayn_pwm_fan_read() - Read from a hwmon pwm or fan attribute.
> + *
> + * @dev: parent device of the given attribute.
> + * @type: The hwmon_sensor_types type.
> + * @attr: The attribute to read from.
> + * @channel: HWMON subsystem usage flags for the attribute.
> + * @val: Pointer to return the read value from.
> + *
> + * Return: 0, or an error.
> + */
> +static int ayn_pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + int ret;
> +
> + switch (type) {
> + case hwmon_fan:
> + switch (attr) {
> + case hwmon_fan_input:
> + return read_from_ec(AYN_SENSOR_PWM_FAN_SPEED_REG, 2,
> + val);
> + default:
> + break;
> + }
> + break;
> + case hwmon_pwm:
> + switch (attr) {
> + case hwmon_pwm_enable:
> + ret = read_from_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 1,
> + val);
> + if (ret)
> + return ret;
> +
> + /* EC uses 0 for manual, 1 for automatic, 2 for user
> + * fan curve. Reflect hwmon usage instead.
> + */
If values have meaning then use #define.
> + if (*val == 1) {
> + *val = 2;
> + return 0;
> + }
> +
> + if (*val == 2) {
> + *val = 3;
> + return 0;
> + }
> +
> + /* Return 0 when fan at max, otherwise 1 for manual. */
> + ret = read_from_ec(AYN_SENSOR_PWM_FAN_SET_REG, 1, val);
> + if (ret)
> + return ret;
> +
> + if (*val == 128)
> + *val = 0;
> + else
> + *val = 1;
> +
> + return ret;
> + case hwmon_pwm_input:
> + ret = read_from_ec(AYN_SENSOR_PWM_FAN_SET_REG, 1, val);
> + if (ret)
> + return ret;
> +
> + *val = *val << 1; /* Max value is 128, scale to 255 */
> +
> + return 0;
> + default:
> + break;
> + }
> + break;
> + default:
> + break;
> + }
> + return -EOPNOTSUPP;
> +}
> +
> +/**
> + * ayn_pwm_fan_write() - Write to a hwmon pwm attribute.
> + *
> + * @dev: parent device of the given attribute.
> + * @type: The hwmon_sensor_types type.
> + * @attr: The attribute to write to.
> + * @channel: HWMON subsystem usage flags for the attribute.
> + * @val: Value to write.
> + *
> + * Return: 0, or an error.
> + */
> +static int ayn_pwm_fan_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 0:
> + return ayn_pwm_full();
> + case 1:
> + return ayn_pwm_manual();
> + case 2:
> + return ayn_pwm_auto();
> + case 3:
> + return ayn_pwm_user();
> + default:
> + return -EINVAL;
> + }
> + case hwmon_pwm_input:
> + if (val < 0 || val > 255)
> + return -EINVAL;
> +
> + val = val >> 1; /* Max value is 128, scale from 255 */
> +
> + return write_to_ec(AYN_SENSOR_PWM_FAN_SET_REG, val);
> + default:
> + break;
> + }
> + break;
Why a switch for 1 case? If expecting more that's okay, but if not
consider just doing a check for hwmon_pwm at the top,
if (type != hwmon_pwm)
return -EOPNOTSUPP;
> + default:
> + break;
> + }
> + return -EOPNOTSUPP;
> +}
> +
> +static const struct hwmon_channel_info *ayn_ec_sensors[] = {
> + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
> + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
> + NULL,
> +};
> +
> +static const struct hwmon_ops ayn_ec_hwmon_ops = {
> + .is_visible = ayn_ec_hwmon_is_visible,
> + .read = ayn_pwm_fan_read,
> + .write = ayn_pwm_fan_write,
> +};
> +
> +static const struct hwmon_chip_info ayn_ec_chip_info = {
> + .ops = &ayn_ec_hwmon_ops,
> + .info = ayn_ec_sensors,
> +};
> +
> +/**
> + * pwm_curve_store() - Write a fan curve speed or temperature value.
> + *
> + * @dev: The attribute's parent device.
> + * @attr: The attribute to read.
> + * @buf: Input value string from sysfs write.
> + *
> + * Return: Number of bytes read, or an error.
> + */
> +static ssize_t pwm_curve_store(struct device *dev,
> + struct device_attribute *attr, const char *buf,
> + size_t count)
> +{
> + int ret, i, val;
> + u8 reg;
> +
> + ret = kstrtoint(buf, 0, &val);
> + if (ret)
> + return ret;
> +
> + i = to_sensor_dev_attr(attr)->index;
> + switch (i) {
> + case 0:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG;
> + break;
> + case 1:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG;
> + break;
> + case 2:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG;
> + break;
> + case 3:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG;
> + break;
> + case 4:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG;
> + break;
> + case 5:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG;
> + break;
> + case 6:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG;
> + break;
> + case 7:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG;
> + break;
> + case 8:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG;
> + break;
> + case 9:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG;
> + break;
> + default:
> + return -EINVAL;
> + }
If it doesn't break future extentions, you could put these values in an
array and just do,
if (i >= ARRAY_SIZE(ayn_sensor_pwm_mode))
return -EINVAL;
reg = ayn_sensor_pwm_mode[i];
Or you could split into separate speed and temp array and mangage the
offset for temp.
> +
> + switch (i) {
> + case 0:
> + case 1:
> + case 2:
> + case 3:
> + case 4:
> + if (val < 0 || val > 255)
> + return -EINVAL;
> + val = val >> 1; /* Max EC value is 128, scale from 255 */
> + break;
> + case 5:
> + case 6:
> + case 7:
> + case 8:
> + case 9:
> + if (val < 0 || val > 100)
> + return -EINVAL;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + ret = write_to_ec(reg, val);
> + if (ret)
> + return ret;
> + return count;
> +}
> +
> +/**
> + * pwm_curve_show() - Read a fan curve speed or temperature value.
> + *
> + * @dev: The attribute's parent device.
> + * @attr: The attribute to read.
> + * @buf: Buffer to read to.
> + *
> + * Return: Number of bytes read, or an error.
> + */
> +static ssize_t pwm_curve_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + int i, ret;
> + long val;
> + u8 reg;
> +
> + i = to_sensor_dev_attr(attr)->index;
> + switch (i) {
> + case 0:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG;
> + break;
> + case 1:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG;
> + break;
> + case 2:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG;
> + break;
> + case 3:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG;
> + break;
> + case 4:
> + reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG;
> + break;
> + case 5:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG;
> + break;
> + case 6:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG;
> + break;
> + case 7:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG;
> + break;
> + case 8:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG;
> + break;
> + case 9:
> + reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG;
> + break;
> + default:
> + return -EINVAL;
> + }
Same here.
David
> +
> + ret = read_from_ec(reg, 1, &val);
> + if (ret)
> + return ret;
> +
> + switch (i) {
> + case 0:
> + case 1:
> + case 2:
> + case 3:
> + case 4:
> + val = val << 1; /* Max EC value is 128, scale to 255 */
> + break;
> + default:
> + break;
> + }
> +
> + return sysfs_emit(buf, "%ld\n", val);
> +}
> +
> +/* Fan curve attributes */
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_curve, 0);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_pwm, pwm_curve, 1);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_pwm, pwm_curve, 2);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_pwm, pwm_curve, 3);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_pwm, pwm_curve, 4);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_curve, 5);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_curve, 6);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8);
> +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9);
> +
> +static struct attribute *ayn_sensors_attrs[] = {
> + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
> + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
> + NULL,
> +};
> +
> +ATTRIBUTE_GROUPS(ayn_sensors);
> +
> +static int ayn_ec_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device *hwdev;
> + int ret;
> +
> + hwdev = devm_hwmon_device_register_with_info(dev, "aynec", NULL,
> + &ayn_ec_chip_info,
> + ayn_sensors_groups);
> + return PTR_ERR_OR_ZERO(hwdev);
> +}
> +
> +static struct platform_driver ayn_ec_driver = {
> + .driver = {
> + .name = "ayn-ec",
> + },
> + .probe = ayn_ec_probe,
> +};
> +
> +static struct platform_device *ayn_ec_device;
> +
> +static int __init ayn_ec_init(void)
> +{
> + ayn_ec_device = platform_create_bundle(&ayn_ec_driver, ayn_ec_probe,
> + NULL, 0, NULL, 0);
> +
> + return PTR_ERR_OR_ZERO(ayn_ec_device);
> +}
> +
> +static void __exit ayn_ec_exit(void)
> +{
> + platform_device_unregister(ayn_ec_device);
> + platform_driver_unregister(&ayn_ec_driver);
> +}
> +
> +static const struct dmi_system_id ayn_dmi_table[] = {
> + {
> + .matches = {
> + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
> + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Max"),
> + },
> + .driver_data = (void *)ayn_loki_max,
> + },
> + {
> + .matches = {
> + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
> + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki MiniPro"),
> + },
> + .driver_data = (void *)ayn_loki_minipro,
> + },
> + {
> + .matches = {
> + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
> + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Zero"),
> + },
> + .driver_data = (void *)ayn_loki_zero,
> + },
> + {
> + .matches = {
> + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Tectoy"),
> + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Zeenix Lite"),
> + },
> + .driver_data = (void *)tactoy_zeenix_lite,
> + },
> + {},
> +};
> +
> +MODULE_DEVICE_TABLE(dmi, ayn_dmi_table);
> +
> +module_init(ayn_ec_init);
> +module_exit(ayn_ec_exit);
> +
> +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_DESCRIPTION("Platform driver that handles EC sensors of Ayn x86 devices");
> +MODULE_LICENSE("GPL");
> --
> 2.50.0
>
>
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors
2025-07-25 0:45 ` [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors Derek J. Clark
2025-07-25 15:29 ` ALOK TIWARI
@ 2025-07-26 15:50 ` David Box
2025-07-26 17:52 ` Derek John Clark
1 sibling, 1 reply; 17+ messages in thread
From: David Box @ 2025-07-26 15:50 UTC (permalink / raw)
To: Derek J. Clark
Cc: Ilpo Järvinen, Hans de Goede, Jean Delvare, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
On Thu, Jul 24, 2025 at 05:45:31PM -0700, Derek J. Clark wrote:
> Adds temperature sensors to the ayn-ec hwmon interface. These read-only
> values include Battery, Motherboard, Charger IC, vCore, and CPU Core, as
> well as labels for each entry. The temperature values provided by the EC
> are whole numbers in degrees Celsius. As hwmon expects millidegrees, we
> scale the raw value up.
>
> `sensors` output after this patch is applied:
> aynec-isa-0000
> Adapter: ISA adapter
> fan1: 1876 RPM
> Battery: +29.0°C
> Motherboard: +30.0°C
> Charger IC: +30.0°C
> vCore: +36.0°C
> CPU Core: +48.0°C
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> drivers/platform/x86/ayn-ec.c | 89 +++++++++++++++++++++++++++++++++++
> 1 file changed, 89 insertions(+)
>
> diff --git a/drivers/platform/x86/ayn-ec.c b/drivers/platform/x86/ayn-ec.c
> index 06f232bd10fa..b2054dc2358a 100644
> --- a/drivers/platform/x86/ayn-ec.c
> +++ b/drivers/platform/x86/ayn-ec.c
> @@ -50,8 +50,16 @@
> #define AYN_SENSOR_PWM_FAN_TEMP_4_REG 0x19
> #define AYN_SENSOR_PWM_FAN_TEMP_5_REG 0x1B
>
> +/* EC Teperature Sensors */
> +#define AYN_SENSOR_BAT_TEMP_REG 0x04 /* Battery */
> +#define AYN_SENSOR_CHARGE_TEMP_REG 0x07 /* Charger IC */
> +#define AYN_SENSOR_MB_TEMP_REG 0x05 /* Motherboard */
> +#define AYN_SENSOR_PROC_TEMP_REG 0x09 /* CPU Core */
> +#define AYN_SENSOR_VCORE_TEMP_REG 0x08 /* vCore */
> +
> /* Handle ACPI lock mechanism */
> #define ACPI_LOCK_DELAY_MS 500
> +
> enum ayn_model {
> ayn_loki_max = 1,
> ayn_loki_minipro,
> @@ -63,6 +71,20 @@ struct ayn_device {
> u32 ayn_lock; /* ACPI EC Lock */
> } drvdata;
>
> +struct thermal_sensor {
> + char *name;
> + int reg;
> +};
> +
> +static struct thermal_sensor thermal_sensors[] = {
> + { "Battery", AYN_SENSOR_BAT_TEMP_REG },
> + { "Motherboard", AYN_SENSOR_MB_TEMP_REG },
> + { "Charger IC", AYN_SENSOR_CHARGE_TEMP_REG },
> + { "vCore", AYN_SENSOR_VCORE_TEMP_REG },
> + { "CPU Core", AYN_SENSOR_PROC_TEMP_REG },
> + {}
> +};
Use tabs to align the constants for better readability.
David
> +
> /* Handle ACPI lock mechanism */
> #define ACPI_LOCK_DELAY_MS 500
>
> @@ -503,6 +525,63 @@ static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7);
> static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8);
> static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9);
>
> +/**
> + * thermal_sensor_show() - Read a thermal sensor attribute value.
> + *
> + * @dev: The attribute's parent device.
> + * @attr: The attribute to read.
> + * @buf: Buffer to read to.
> + *
> + * Return: Number of bytes read, or an error.
> + */
> +static ssize_t thermal_sensor_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + long ret, val;
> + int i;
> +
> + i = to_sensor_dev_attr(attr)->index;
> +
> + ret = read_from_ec(thermal_sensors[i].reg, 1, &val);
> + if (ret)
> + return ret;
> +
> + val = val * 1000L;
> +
> + return sysfs_emit(buf, "%ld\n", val);
> +}
> +
> +/**
> + * thermal_sensor_label_show() - Read a thermal sensor attribute label.
> + *
> + * @dev: The attribute's parent device.
> + * @attr: The attribute to read.
> + * @buf: Buffer to read to.
> + *
> + * Return: Number of bytes read, or an error.
> + */
> +static ssize_t thermal_sensor_label_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + int i;
> +
> + i = to_sensor_dev_attr(attr)->index;
> +
> + return sysfs_emit(buf, "%s\n", thermal_sensors[i].name);
> +}
> +
> +static SENSOR_DEVICE_ATTR_RO(temp1_input, thermal_sensor, 0);
> +static SENSOR_DEVICE_ATTR_RO(temp2_input, thermal_sensor, 1);
> +static SENSOR_DEVICE_ATTR_RO(temp3_input, thermal_sensor, 2);
> +static SENSOR_DEVICE_ATTR_RO(temp4_input, thermal_sensor, 3);
> +static SENSOR_DEVICE_ATTR_RO(temp5_input, thermal_sensor, 4);
> +static SENSOR_DEVICE_ATTR_RO(temp1_label, thermal_sensor_label, 0);
> +static SENSOR_DEVICE_ATTR_RO(temp2_label, thermal_sensor_label, 1);
> +static SENSOR_DEVICE_ATTR_RO(temp3_label, thermal_sensor_label, 2);
> +static SENSOR_DEVICE_ATTR_RO(temp4_label, thermal_sensor_label, 3);
> +static SENSOR_DEVICE_ATTR_RO(temp5_label, thermal_sensor_label, 4);
> +
> static struct attribute *ayn_sensors_attrs[] = {
> &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
> &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
> @@ -514,6 +593,16 @@ static struct attribute *ayn_sensors_attrs[] = {
> &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
> &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
> &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
> + &sensor_dev_attr_temp1_input.dev_attr.attr,
> + &sensor_dev_attr_temp1_label.dev_attr.attr,
> + &sensor_dev_attr_temp2_input.dev_attr.attr,
> + &sensor_dev_attr_temp2_label.dev_attr.attr,
> + &sensor_dev_attr_temp3_input.dev_attr.attr,
> + &sensor_dev_attr_temp3_label.dev_attr.attr,
> + &sensor_dev_attr_temp4_input.dev_attr.attr,
> + &sensor_dev_attr_temp4_label.dev_attr.attr,
> + &sensor_dev_attr_temp5_input.dev_attr.attr,
> + &sensor_dev_attr_temp5_label.dev_attr.attr,
> NULL,
> };
>
> --
> 2.50.0
>
>
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
` (9 preceding siblings ...)
2025-07-26 15:47 ` David Box
@ 2025-07-26 17:21 ` kernel test robot
10 siblings, 0 replies; 17+ messages in thread
From: kernel test robot @ 2025-07-26 17:21 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Paul Gazzillo, Necip Fazil Yildiran, oe-kbuild-all, Jean Delvare,
Guenter Roeck, Derek J . Clark, platform-driver-x86, linux-kernel,
linux-hwmon
Hi Derek,
kernel test robot noticed the following build warnings:
[auto build test WARNING on groeck-staging/hwmon-next]
[also build test WARNING on linus/master v6.16-rc7 next-20250725]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Derek-J-Clark/platform-x86-ayn-ec-Add-Temperature-Sensors/20250725-084850
base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
patch link: https://lore.kernel.org/r/20250725004533.63537-1-derekjohn.clark%40gmail.com
patch subject: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
config: i386-kismet-CONFIG_LEDS_TRIGGERS-CONFIG_IWLEGACY-0-0 (https://download.01.org/0day-ci/archive/20250727/202507270148.ZrFnYWlb-lkp@intel.com/config)
reproduce: (https://download.01.org/0day-ci/archive/20250727/202507270148.ZrFnYWlb-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202507270148.ZrFnYWlb-lkp@intel.com/
kismet warnings: (new ones prefixed by >>)
>> kismet: WARNING: unmet direct dependencies detected for LEDS_TRIGGERS when selected by IWLEGACY
WARNING: unmet direct dependencies detected for LEDS_CLASS
Depends on [n]: NEW_LEDS [=n]
Selected by [y]:
- AYN_EC [=y] && X86_PLATFORM_DEVICES [=y] && ACPI [=y] && HWMON [=y]
WARNING: unmet direct dependencies detected for LEDS_CLASS_MULTICOLOR
Depends on [n]: NEW_LEDS [=n] && LEDS_CLASS [=y]
Selected by [y]:
- AYN_EC [=y] && X86_PLATFORM_DEVICES [=y] && ACPI [=y] && HWMON [=y]
WARNING: unmet direct dependencies detected for LEDS_TRIGGERS
Depends on [n]: NEW_LEDS [=n] && LEDS_CLASS [=y]
Selected by [y]:
- MAC80211_LEDS [=y] && NET [=y] && WIRELESS [=y] && MAC80211 [=y] && (LEDS_CLASS [=y]=y [=y] || LEDS_CLASS [=y]=MAC80211 [=y])
- IWLEGACY [=y] && NETDEVICES [=y] && WLAN [=y] && WLAN_VENDOR_INTEL [=y]
- IWLWIFI_LEDS [=y] && NETDEVICES [=y] && WLAN [=y] && WLAN_VENDOR_INTEL [=y] && IWLWIFI [=y] && (LEDS_CLASS [=y]=y [=y] || LEDS_CLASS [=y]=MAC80211 [=y]) && (IWLMVM [=n] || IWLDVM [=y])
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface
2025-07-26 15:47 ` David Box
@ 2025-07-26 17:51 ` Derek John Clark
0 siblings, 0 replies; 17+ messages in thread
From: Derek John Clark @ 2025-07-26 17:51 UTC (permalink / raw)
To: David Box
Cc: Ilpo Järvinen, Hans de Goede, Jean Delvare, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
On Sat, Jul 26, 2025 at 8:47 AM David Box <david.e.box@linux.intel.com> wrote:
>
> Hi,
>
> On Thu, Jul 24, 2025 at 05:45:30PM -0700, Derek J. Clark wrote:
> > Adds platform driver for Ayn Loki and Tactoy Zeenix lines of handheld
> > devices. This patch implements a hwmon interface for EC provided manual
> > PWM fan control and user defined fan curves. A global ACPI lock is used
> > when reading or writing from the EC.
> >
> > There are 4 fan modes implemented in this patch. Modes 0-3 act in
> > accordance with the standard hwmon logic where 0 is 100% fan speed, 1 is
> > manual control, and 2 is automatic control. As the EC only provides 3
> > modes by default, mode 0 is implemented by setting the device to manual
> > and then setting fan speed to 100% directly. In mode 1 the PWM duty cycle
> > is set in sysfs with values [0-255], which are then scaled to the EC max
> > of 128. Mode 4 is an automatic mode where the fan curve is user defined.
> > There are 5 total set points and each set point takes a temperature in
> > Celsius [0-100] and a PWM duty cycle [0-255]. When the CPU temperature
> > reaches a given set point, the corresponding duty cycle is automatically
> > set by the EC.
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > MAINTAINERS | 6 +
> > drivers/platform/x86/Kconfig | 14 +
> > drivers/platform/x86/Makefile | 3 +
> > drivers/platform/x86/ayn-ec.c | 596 ++++++++++++++++++++++++++++++++++
> > 4 files changed, 619 insertions(+)
> > create mode 100644 drivers/platform/x86/ayn-ec.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index d61b004005fd..5b816883fe7d 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -4035,6 +4035,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
> >
> > +AYN PLATFORM EC DRIVER
> > +M: Derek J. Clark <derekjohn.clark@gmail.com>
> > +L: platform-driver-x86@vger.kernel.org
> > +S: Maintained
> > +F: drivers/platform/x86/ayn-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 6d238e120dce..61391be65a7b 100644
> > --- a/drivers/platform/x86/Kconfig
> > +++ b/drivers/platform/x86/Kconfig
> > @@ -304,6 +304,20 @@ 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 AYN_EC
> > + tristate "Ayn x86 devices EC platform control"
> > + depends on ACPI
> > + depends on HWMON
> > + select LEDS_CLASS
> > + select LEDS_CLASS_MULTICOLOR
> > + help
> > + This is a driver for Ayn and Tactoy x86 handheld devices. It provides
> > + temperature monitoring, manual fan speed control, fan curve control,
> > + and chassis RGB settings.
> > +
> > + If you have an x86 Ayn or Tactoy handheld device say M here. The module
> > + will be called ayn-platform.
> > +
> > 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 a0c5848513e3..d32504b89365 100644
> > --- a/drivers/platform/x86/Makefile
> > +++ b/drivers/platform/x86/Makefile
> > @@ -38,6 +38,9 @@ obj-$(CONFIG_ASUS_TF103C_DOCK) += asus-tf103c-dock.o
> > obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
> > obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o
> >
> > +# Ayn
> > +obj-$(CONFIG_AYN_EC) += ayn-ec.o
> > +
> > # Cisco/Meraki
> > obj-$(CONFIG_MERAKI_MX100) += meraki-mx100.o
> >
> > diff --git a/drivers/platform/x86/ayn-ec.c b/drivers/platform/x86/ayn-ec.c
> > new file mode 100644
> > index 000000000000..06f232bd10fa
> > --- /dev/null
> > +++ b/drivers/platform/x86/ayn-ec.c
> > @@ -0,0 +1,596 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Platform driver for Ayn x86 Handhelds.
> > + *
> > + * Implements multiple attributes provided by the EC. Fan reading and control,
> > + * as well as temperature sensor readings are exposed via hwmon sysfs. EC RGB
> > + * control is exposed via an led-class-multicolor interface.
> > + *
> > + * Fan control is provided via a pwm interface in the range [0-255]. Ayn use
> > + * [0-128] as the range in the EC, the written value is scaled to accommodate.
> > + * The EC also provides a configurable fan curve with five set points that
> > + * associate a temperature in Celcius [0-100] with a fan speed [0-128]. The
> > + * auto_point fan speeds are also scaled from the range [0-255]. Temperature
> > + * readings are scaled from degrees to millidegrees when read.
> > + *
> > + * RGB control is provided using 4 registers. One each for the colors red,
> > + * green, and blue are [0-255]. There is also a effect register that takes
> > + * switches between an EC controlled breathing that cycles through all colors
> > + * and fades in/out, and manual, which enables setting a user defined color.
> > + *
> > + * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + */
> > +
> > +#include <linux/acpi.h>
> > +#include <linux/device.h>
> > +#include <linux/dmi.h>
> > +#include <linux/hwmon-sysfs.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/init.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/sysfs.h>
> > +#include <linux/types.h>
> > +
> > +/* Fan reading and PWM */
> > +#define AYN_SENSOR_PWM_FAN_ENABLE_REG 0x10 /* PWM operating mode */
> > +#define AYN_SENSOR_PWM_FAN_SET_REG 0x11 /* PWM duty cycle */
> > +#define AYN_SENSOR_PWM_FAN_SPEED_REG 0x20 /* Fan speed */
> > +
> > +/* EC controlled fan curve registers */
> > +#define AYN_SENSOR_PWM_FAN_SPEED_1_REG 0x12
> > +#define AYN_SENSOR_PWM_FAN_SPEED_2_REG 0x14
> > +#define AYN_SENSOR_PWM_FAN_SPEED_3_REG 0x16
> > +#define AYN_SENSOR_PWM_FAN_SPEED_4_REG 0x18
> > +#define AYN_SENSOR_PWM_FAN_SPEED_5_REG 0x1A
> > +#define AYN_SENSOR_PWM_FAN_TEMP_1_REG 0x13
> > +#define AYN_SENSOR_PWM_FAN_TEMP_2_REG 0x15
> > +#define AYN_SENSOR_PWM_FAN_TEMP_3_REG 0x17
> > +#define AYN_SENSOR_PWM_FAN_TEMP_4_REG 0x19
> > +#define AYN_SENSOR_PWM_FAN_TEMP_5_REG 0x1B
> > +
> > +/* Handle ACPI lock mechanism */
> > +#define ACPI_LOCK_DELAY_MS 500
> > +enum ayn_model {
> > + ayn_loki_max = 1,
> > + ayn_loki_minipro,
> > + ayn_loki_zero,
> > + tactoy_zeenix_lite,
> > +};
> > +
> > +struct ayn_device {
> > + u32 ayn_lock; /* ACPI EC Lock */
> > +} drvdata;
> > +
> > +/* Handle ACPI lock mechanism */
> > +#define ACPI_LOCK_DELAY_MS 500
> > +
> > +static bool lock_global_acpi_lock(void)
> > +{
> > + return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS,
> > + &drvdata.ayn_lock));
> > +}
> > +
> > +static bool unlock_global_acpi_lock(void)
> > +{
> > + return ACPI_SUCCESS(acpi_release_global_lock(drvdata.ayn_lock));
> > +}
> > +
> > +/**
> > + * read_from_ec() - Reads a value from the embedded controller.
> > + *
> > + * @reg: The register to start the read from.
> > + * @size: The number of sequential registers the data is contained in.
> > + * @val: Pointer to return the data with.
> > + *
> > + * Return: 0, or an error.
> > + */
> > +static int read_from_ec(u8 reg, int size, long *val)
> > +{
> > + int ret, i;
> > + u8 buf;
> > +
> > + if (!lock_global_acpi_lock())
> > + return -EBUSY;
> > +
> > + *val = 0;
> > + for (i = 0; i < size; i++) {
> > + ret = ec_read(reg + i, &buf);
> > + if (ret)
> > + return ret;
> > + *val <<= i * 8;
> > + *val += buf;
> > + }
> > +
> > + if (!unlock_global_acpi_lock())
> > + return -EBUSY;
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * write_to_ec() - Writes a value to the embedded controller.
> > + *
> > + * @reg: The register to write to.
> > + * @val: Value to write
> > + *
> > + * Return: 0, or an error.
> > + */
> > +static int write_to_ec(u8 reg, u8 val)
> > +{
> > + int ret;
> > +
> > + if (!lock_global_acpi_lock())
> > + return -EBUSY;
> > +
> > + pr_info("Writing EC value %d to register %u\n", val, reg);
>
> pr_debug? You don't want to write out to the log every time.
>
Hi David,
This is no longer needed, I just forgot to remove it.
> > + ret = ec_write(reg, val);
> > +
> > + if (!unlock_global_acpi_lock())
> > + return -EBUSY;
> > +
> > + return ret;
> > +}
> > +
> > +/**
> > + * ayn_pwm_manual() - Enable manual control of the fan.
> > + */
> > +static int ayn_pwm_manual(void)
> > +{
> > + return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x00);
> > +}
> > +
> > +/**
> > + * ayn_pwm_full() - Set fan to 100% speed.
> > + */
> > +static int ayn_pwm_full(void)
> > +{
> > + int ret;
> > +
> > + ret = write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x00);
> > + if (ret)
> > + return ret;
> > +
> > + return write_to_ec(AYN_SENSOR_PWM_FAN_SET_REG, 128);
> > +}
> > +
> > +/**
> > + * ayn_pwm_auto() - Enable automatic EC control of the fan.
> > + */
> > +static int ayn_pwm_auto(void)
> > +{
> > + return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x01);
> > +}
> > +
> > +/**
> > + * ayn_pwm_auto() - Enable manually setting the fan curve for automatic
> > + * EC control of the fan.
> > + */
> > +static int ayn_pwm_user(void)
> > +{
> > + return write_to_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 0x02);
> > +}
> > +
> > +/**
> > + * ayn_ec_hwmon_is_visible() - Determines RO or RW for hwmon attribute sysfs.
> > + *
> > + * @drvdata: Unused void pointer to context data.
> > + * @type: The hwmon_sensor_types type.
> > + * @attr: The attribute to set RO/RW on.
> > + * @channel: HWMON subsystem usage flags for the attribute.
> > + *
> > + * Return: Permission level.
> > + */
> > +static umode_t ayn_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;
> > + }
> > +}
> > +
> > +/**
> > + * ayn_pwm_fan_read() - Read from a hwmon pwm or fan attribute.
> > + *
> > + * @dev: parent device of the given attribute.
> > + * @type: The hwmon_sensor_types type.
> > + * @attr: The attribute to read from.
> > + * @channel: HWMON subsystem usage flags for the attribute.
> > + * @val: Pointer to return the read value from.
> > + *
> > + * Return: 0, or an error.
> > + */
> > +static int ayn_pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
> > + u32 attr, int channel, long *val)
> > +{
> > + int ret;
> > +
> > + switch (type) {
> > + case hwmon_fan:
> > + switch (attr) {
> > + case hwmon_fan_input:
> > + return read_from_ec(AYN_SENSOR_PWM_FAN_SPEED_REG, 2,
> > + val);
> > + default:
> > + break;
> > + }
> > + break;
> > + case hwmon_pwm:
> > + switch (attr) {
> > + case hwmon_pwm_enable:
> > + ret = read_from_ec(AYN_SENSOR_PWM_FAN_ENABLE_REG, 1,
> > + val);
> > + if (ret)
> > + return ret;
> > +
> > + /* EC uses 0 for manual, 1 for automatic, 2 for user
> > + * fan curve. Reflect hwmon usage instead.
> > + */
>
> If values have meaning then use #define.
>
> > + if (*val == 1) {
> > + *val = 2;
> > + return 0;
> > + }
> > +
> > + if (*val == 2) {
> > + *val = 3;
> > + return 0;
> > + }
> > +
> > + /* Return 0 when fan at max, otherwise 1 for manual. */
> > + ret = read_from_ec(AYN_SENSOR_PWM_FAN_SET_REG, 1, val);
> > + if (ret)
> > + return ret;
> > +
> > + if (*val == 128)
> > + *val = 0;
> > + else
> > + *val = 1;
> > +
> > + return ret;
> > + case hwmon_pwm_input:
> > + ret = read_from_ec(AYN_SENSOR_PWM_FAN_SET_REG, 1, val);
> > + if (ret)
> > + return ret;
> > +
> > + *val = *val << 1; /* Max value is 128, scale to 255 */
> > +
> > + return 0;
> > + default:
> > + break;
> > + }
> > + break;
> > + default:
> > + break;
> > + }
> > + return -EOPNOTSUPP;
> > +}
> > +
> > +/**
> > + * ayn_pwm_fan_write() - Write to a hwmon pwm attribute.
> > + *
> > + * @dev: parent device of the given attribute.
> > + * @type: The hwmon_sensor_types type.
> > + * @attr: The attribute to write to.
> > + * @channel: HWMON subsystem usage flags for the attribute.
> > + * @val: Value to write.
> > + *
> > + * Return: 0, or an error.
> > + */
> > +static int ayn_pwm_fan_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 0:
> > + return ayn_pwm_full();
> > + case 1:
> > + return ayn_pwm_manual();
> > + case 2:
> > + return ayn_pwm_auto();
> > + case 3:
> > + return ayn_pwm_user();
> > + default:
> > + return -EINVAL;
> > + }
> > + case hwmon_pwm_input:
> > + if (val < 0 || val > 255)
> > + return -EINVAL;
> > +
> > + val = val >> 1; /* Max value is 128, scale from 255 */
> > +
> > + return write_to_ec(AYN_SENSOR_PWM_FAN_SET_REG, val);
> > + default:
> > + break;
> > + }
> > + break;
>
> Why a switch for 1 case? If expecting more that's okay, but if not
> consider just doing a check for hwmon_pwm at the top,
>
> if (type != hwmon_pwm)
> return -EOPNOTSUPP;
>
I don't expect more, so I'll do this.
> > + default:
> > + break;
> > + }
> > + return -EOPNOTSUPP;
> > +}
> > +
> > +static const struct hwmon_channel_info *ayn_ec_sensors[] = {
> > + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
> > + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
> > + NULL,
> > +};
> > +
> > +static const struct hwmon_ops ayn_ec_hwmon_ops = {
> > + .is_visible = ayn_ec_hwmon_is_visible,
> > + .read = ayn_pwm_fan_read,
> > + .write = ayn_pwm_fan_write,
> > +};
> > +
> > +static const struct hwmon_chip_info ayn_ec_chip_info = {
> > + .ops = &ayn_ec_hwmon_ops,
> > + .info = ayn_ec_sensors,
> > +};
> > +
> > +/**
> > + * pwm_curve_store() - Write a fan curve speed or temperature value.
> > + *
> > + * @dev: The attribute's parent device.
> > + * @attr: The attribute to read.
> > + * @buf: Input value string from sysfs write.
> > + *
> > + * Return: Number of bytes read, or an error.
> > + */
> > +static ssize_t pwm_curve_store(struct device *dev,
> > + struct device_attribute *attr, const char *buf,
> > + size_t count)
> > +{
> > + int ret, i, val;
> > + u8 reg;
> > +
> > + ret = kstrtoint(buf, 0, &val);
> > + if (ret)
> > + return ret;
> > +
> > + i = to_sensor_dev_attr(attr)->index;
> > + switch (i) {
> > + case 0:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG;
> > + break;
> > + case 1:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG;
> > + break;
> > + case 2:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG;
> > + break;
> > + case 3:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG;
> > + break;
> > + case 4:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG;
> > + break;
> > + case 5:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG;
> > + break;
> > + case 6:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG;
> > + break;
> > + case 7:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG;
> > + break;
> > + case 8:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG;
> > + break;
> > + case 9:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
>
> If it doesn't break future extentions, you could put these values in an
> array and just do,
>
> if (i >= ARRAY_SIZE(ayn_sensor_pwm_mode))
> return -EINVAL;
>
> reg = ayn_sensor_pwm_mode[i];
>
> Or you could split into separate speed and temp array and mangage the
> offset for temp.
>
This is a much better idea. Not sure why I didn't think of that myself
since I do that for the temp sensors...
Thanks,
Derek
> > +
> > + switch (i) {
> > + case 0:
> > + case 1:
> > + case 2:
> > + case 3:
> > + case 4:
> > + if (val < 0 || val > 255)
> > + return -EINVAL;
> > + val = val >> 1; /* Max EC value is 128, scale from 255 */
> > + break;
> > + case 5:
> > + case 6:
> > + case 7:
> > + case 8:
> > + case 9:
> > + if (val < 0 || val > 100)
> > + return -EINVAL;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + ret = write_to_ec(reg, val);
> > + if (ret)
> > + return ret;
> > + return count;
> > +}
> > +
> > +/**
> > + * pwm_curve_show() - Read a fan curve speed or temperature value.
> > + *
> > + * @dev: The attribute's parent device.
> > + * @attr: The attribute to read.
> > + * @buf: Buffer to read to.
> > + *
> > + * Return: Number of bytes read, or an error.
> > + */
> > +static ssize_t pwm_curve_show(struct device *dev, struct device_attribute *attr,
> > + char *buf)
> > +{
> > + int i, ret;
> > + long val;
> > + u8 reg;
> > +
> > + i = to_sensor_dev_attr(attr)->index;
> > + switch (i) {
> > + case 0:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_1_REG;
> > + break;
> > + case 1:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_2_REG;
> > + break;
> > + case 2:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_3_REG;
> > + break;
> > + case 3:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_4_REG;
> > + break;
> > + case 4:
> > + reg = AYN_SENSOR_PWM_FAN_SPEED_5_REG;
> > + break;
> > + case 5:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_1_REG;
> > + break;
> > + case 6:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_2_REG;
> > + break;
> > + case 7:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_3_REG;
> > + break;
> > + case 8:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_4_REG;
> > + break;
> > + case 9:
> > + reg = AYN_SENSOR_PWM_FAN_TEMP_5_REG;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
>
> Same here.
>
> David
>
> > +
> > + ret = read_from_ec(reg, 1, &val);
> > + if (ret)
> > + return ret;
> > +
> > + switch (i) {
> > + case 0:
> > + case 1:
> > + case 2:
> > + case 3:
> > + case 4:
> > + val = val << 1; /* Max EC value is 128, scale to 255 */
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + return sysfs_emit(buf, "%ld\n", val);
> > +}
> > +
> > +/* Fan curve attributes */
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_curve, 0);
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_pwm, pwm_curve, 1);
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_pwm, pwm_curve, 2);
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_pwm, pwm_curve, 3);
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_pwm, pwm_curve, 4);
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_curve, 5);
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_curve, 6);
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7);
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8);
> > +static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9);
> > +
> > +static struct attribute *ayn_sensors_attrs[] = {
> > + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
> > + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
> > + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
> > + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
> > + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
> > + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
> > + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
> > + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
> > + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
> > + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
> > + NULL,
> > +};
> > +
> > +ATTRIBUTE_GROUPS(ayn_sensors);
> > +
> > +static int ayn_ec_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + struct device *hwdev;
> > + int ret;
> > +
> > + hwdev = devm_hwmon_device_register_with_info(dev, "aynec", NULL,
> > + &ayn_ec_chip_info,
> > + ayn_sensors_groups);
> > + return PTR_ERR_OR_ZERO(hwdev);
> > +}
> > +
> > +static struct platform_driver ayn_ec_driver = {
> > + .driver = {
> > + .name = "ayn-ec",
> > + },
> > + .probe = ayn_ec_probe,
> > +};
> > +
> > +static struct platform_device *ayn_ec_device;
> > +
> > +static int __init ayn_ec_init(void)
> > +{
> > + ayn_ec_device = platform_create_bundle(&ayn_ec_driver, ayn_ec_probe,
> > + NULL, 0, NULL, 0);
> > +
> > + return PTR_ERR_OR_ZERO(ayn_ec_device);
> > +}
> > +
> > +static void __exit ayn_ec_exit(void)
> > +{
> > + platform_device_unregister(ayn_ec_device);
> > + platform_driver_unregister(&ayn_ec_driver);
> > +}
> > +
> > +static const struct dmi_system_id ayn_dmi_table[] = {
> > + {
> > + .matches = {
> > + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
> > + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Max"),
> > + },
> > + .driver_data = (void *)ayn_loki_max,
> > + },
> > + {
> > + .matches = {
> > + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
> > + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki MiniPro"),
> > + },
> > + .driver_data = (void *)ayn_loki_minipro,
> > + },
> > + {
> > + .matches = {
> > + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
> > + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Zero"),
> > + },
> > + .driver_data = (void *)ayn_loki_zero,
> > + },
> > + {
> > + .matches = {
> > + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Tectoy"),
> > + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Zeenix Lite"),
> > + },
> > + .driver_data = (void *)tactoy_zeenix_lite,
> > + },
> > + {},
> > +};
> > +
> > +MODULE_DEVICE_TABLE(dmi, ayn_dmi_table);
> > +
> > +module_init(ayn_ec_init);
> > +module_exit(ayn_ec_exit);
> > +
> > +MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_DESCRIPTION("Platform driver that handles EC sensors of Ayn x86 devices");
> > +MODULE_LICENSE("GPL");
> > --
> > 2.50.0
> >
> >
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors
2025-07-26 15:50 ` David Box
@ 2025-07-26 17:52 ` Derek John Clark
0 siblings, 0 replies; 17+ messages in thread
From: Derek John Clark @ 2025-07-26 17:52 UTC (permalink / raw)
To: David Box
Cc: Ilpo Järvinen, Hans de Goede, Jean Delvare, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
On Sat, Jul 26, 2025 at 8:50 AM David Box <david.e.box@linux.intel.com> wrote:
>
> On Thu, Jul 24, 2025 at 05:45:31PM -0700, Derek J. Clark wrote:
> > Adds temperature sensors to the ayn-ec hwmon interface. These read-only
> > values include Battery, Motherboard, Charger IC, vCore, and CPU Core, as
> > well as labels for each entry. The temperature values provided by the EC
> > are whole numbers in degrees Celsius. As hwmon expects millidegrees, we
> > scale the raw value up.
> >
> > `sensors` output after this patch is applied:
> > aynec-isa-0000
> > Adapter: ISA adapter
> > fan1: 1876 RPM
> > Battery: +29.0°C
> > Motherboard: +30.0°C
> > Charger IC: +30.0°C
> > vCore: +36.0°C
> > CPU Core: +48.0°C
> >
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > drivers/platform/x86/ayn-ec.c | 89 +++++++++++++++++++++++++++++++++++
> > 1 file changed, 89 insertions(+)
> >
> > diff --git a/drivers/platform/x86/ayn-ec.c b/drivers/platform/x86/ayn-ec.c
> > index 06f232bd10fa..b2054dc2358a 100644
> > --- a/drivers/platform/x86/ayn-ec.c
> > +++ b/drivers/platform/x86/ayn-ec.c
> > @@ -50,8 +50,16 @@
> > #define AYN_SENSOR_PWM_FAN_TEMP_4_REG 0x19
> > #define AYN_SENSOR_PWM_FAN_TEMP_5_REG 0x1B
> >
> > +/* EC Teperature Sensors */
> > +#define AYN_SENSOR_BAT_TEMP_REG 0x04 /* Battery */
> > +#define AYN_SENSOR_CHARGE_TEMP_REG 0x07 /* Charger IC */
> > +#define AYN_SENSOR_MB_TEMP_REG 0x05 /* Motherboard */
> > +#define AYN_SENSOR_PROC_TEMP_REG 0x09 /* CPU Core */
> > +#define AYN_SENSOR_VCORE_TEMP_REG 0x08 /* vCore */
> > +
> > /* Handle ACPI lock mechanism */
> > #define ACPI_LOCK_DELAY_MS 500
> > +
> > enum ayn_model {
> > ayn_loki_max = 1,
> > ayn_loki_minipro,
> > @@ -63,6 +71,20 @@ struct ayn_device {
> > u32 ayn_lock; /* ACPI EC Lock */
> > } drvdata;
> >
> > +struct thermal_sensor {
> > + char *name;
> > + int reg;
> > +};
> > +
> > +static struct thermal_sensor thermal_sensors[] = {
> > + { "Battery", AYN_SENSOR_BAT_TEMP_REG },
> > + { "Motherboard", AYN_SENSOR_MB_TEMP_REG },
> > + { "Charger IC", AYN_SENSOR_CHARGE_TEMP_REG },
> > + { "vCore", AYN_SENSOR_VCORE_TEMP_REG },
> > + { "CPU Core", AYN_SENSOR_PROC_TEMP_REG },
> > + {}
> > +};
>
> Use tabs to align the constants for better readability.
>
> David
>
Will do, Thanks.
Derek
> > +
> > /* Handle ACPI lock mechanism */
> > #define ACPI_LOCK_DELAY_MS 500
> >
> > @@ -503,6 +525,63 @@ static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_curve, 7);
> > static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_curve, 8);
> > static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_curve, 9);
> >
> > +/**
> > + * thermal_sensor_show() - Read a thermal sensor attribute value.
> > + *
> > + * @dev: The attribute's parent device.
> > + * @attr: The attribute to read.
> > + * @buf: Buffer to read to.
> > + *
> > + * Return: Number of bytes read, or an error.
> > + */
> > +static ssize_t thermal_sensor_show(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + long ret, val;
> > + int i;
> > +
> > + i = to_sensor_dev_attr(attr)->index;
> > +
> > + ret = read_from_ec(thermal_sensors[i].reg, 1, &val);
> > + if (ret)
> > + return ret;
> > +
> > + val = val * 1000L;
> > +
> > + return sysfs_emit(buf, "%ld\n", val);
> > +}
> > +
> > +/**
> > + * thermal_sensor_label_show() - Read a thermal sensor attribute label.
> > + *
> > + * @dev: The attribute's parent device.
> > + * @attr: The attribute to read.
> > + * @buf: Buffer to read to.
> > + *
> > + * Return: Number of bytes read, or an error.
> > + */
> > +static ssize_t thermal_sensor_label_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + int i;
> > +
> > + i = to_sensor_dev_attr(attr)->index;
> > +
> > + return sysfs_emit(buf, "%s\n", thermal_sensors[i].name);
> > +}
> > +
> > +static SENSOR_DEVICE_ATTR_RO(temp1_input, thermal_sensor, 0);
> > +static SENSOR_DEVICE_ATTR_RO(temp2_input, thermal_sensor, 1);
> > +static SENSOR_DEVICE_ATTR_RO(temp3_input, thermal_sensor, 2);
> > +static SENSOR_DEVICE_ATTR_RO(temp4_input, thermal_sensor, 3);
> > +static SENSOR_DEVICE_ATTR_RO(temp5_input, thermal_sensor, 4);
> > +static SENSOR_DEVICE_ATTR_RO(temp1_label, thermal_sensor_label, 0);
> > +static SENSOR_DEVICE_ATTR_RO(temp2_label, thermal_sensor_label, 1);
> > +static SENSOR_DEVICE_ATTR_RO(temp3_label, thermal_sensor_label, 2);
> > +static SENSOR_DEVICE_ATTR_RO(temp4_label, thermal_sensor_label, 3);
> > +static SENSOR_DEVICE_ATTR_RO(temp5_label, thermal_sensor_label, 4);
> > +
> > static struct attribute *ayn_sensors_attrs[] = {
> > &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
> > &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
> > @@ -514,6 +593,16 @@ static struct attribute *ayn_sensors_attrs[] = {
> > &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
> > &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
> > &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
> > + &sensor_dev_attr_temp1_input.dev_attr.attr,
> > + &sensor_dev_attr_temp1_label.dev_attr.attr,
> > + &sensor_dev_attr_temp2_input.dev_attr.attr,
> > + &sensor_dev_attr_temp2_label.dev_attr.attr,
> > + &sensor_dev_attr_temp3_input.dev_attr.attr,
> > + &sensor_dev_attr_temp3_label.dev_attr.attr,
> > + &sensor_dev_attr_temp4_input.dev_attr.attr,
> > + &sensor_dev_attr_temp4_label.dev_attr.attr,
> > + &sensor_dev_attr_temp5_input.dev_attr.attr,
> > + &sensor_dev_attr_temp5_label.dev_attr.attr,
> > NULL,
> > };
> >
> > --
> > 2.50.0
> >
> >
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2025-07-26 17:52 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-25 0:45 [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface Derek J. Clark
2025-07-25 0:45 ` [PATCH 2/4] platform/x86: (ayn-ec) Add Temperature Sensors Derek J. Clark
2025-07-25 15:29 ` ALOK TIWARI
2025-07-26 15:50 ` David Box
2025-07-26 17:52 ` Derek John Clark
2025-07-25 0:45 ` [PATCH 3/4] platform/x86: (ayn-ec) Add RGB Interface Derek J. Clark
2025-07-25 0:45 ` [PATCH 4/4] platform/x86: (ayn-ec) Add Ayn EC Platform Documentation Derek J. Clark
2025-07-25 15:08 ` [PATCH 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface ALOK TIWARI
2025-07-26 2:48 ` Derek J. Clark
2025-07-26 10:57 ` kernel test robot
2025-07-26 11:43 ` kernel test robot
2025-07-26 12:06 ` kernel test robot
2025-07-26 13:20 ` kernel test robot
2025-07-26 15:34 ` kernel test robot
2025-07-26 15:47 ` David Box
2025-07-26 17:51 ` Derek John Clark
2025-07-26 17:21 ` kernel test robot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).