lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250726204041.516440-2-derekjohn.clark@gmail.com>
Date: Sat, 26 Jul 2025 13:40:38 -0700
From: "Derek J. Clark" <derekjohn.clark@...il.com>
To: Ilpo Järvinen <ilpo.jarvinen@...ux.intel.com>,
	Hans de Goede <hansg@...nel.org>
Cc: Jean Delvare <jdelvare@...e.com>,
	Guenter Roeck <linux@...ck-us.net>,
	Alok Tiwari <alok.a.tiwari@...cle.com>,
	David Box <david.e.box@...ux.intel.com>,
	"Derek J . Clark" <derekjohn.clark@...il.com>,
	platform-driver-x86@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	linux-hwmon@...r.kernel.org,
	linux-doc@...r.kernel.org
Subject: [PATCH v3 1/4] platform/x86: (ayn-ec) Add PWM Fan HWMON Interface

Adds platform driver for AYN Loki and Tectoy 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@...il.com>

space
---
 MAINTAINERS                   |   6 +
 drivers/platform/x86/Kconfig  |  12 +
 drivers/platform/x86/Makefile |   3 +
 drivers/platform/x86/ayn-ec.c | 520 ++++++++++++++++++++++++++++++++++
 4 files changed, 541 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@...il.com>
+L:	platform-driver-x86@...r.kernel.org
+S:	Maintained
+F:	drivers/platform/x86/ayn-ec.c
+
 AZ6007 DVB DRIVER
 M:	Mauro Carvalho Chehab <mchehab@...nel.org>
 L:	linux-media@...r.kernel.org
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 6d238e120dce..4819bfcffb6b 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -304,6 +304,18 @@ 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
+	help
+	  This is a driver for AYN and Tectoy 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 Tectoy 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..8bd3ed1c69eb
--- /dev/null
+++ b/drivers/platform/x86/ayn-ec.c
@@ -0,0 +1,520 @@
+// 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@...il.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 speed and PWM registers */
+#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
+
+/* AYN EC PWM Fan modes */
+#define AYN_PWM_FAN_MODE_MANUAL	0x00
+#define AYN_PWM_FAN_MODE_AUTO		0x01
+#define AYN_PWM_FAN_MODE_EC_CURVE	0x02
+
+/* hwmon fan modes */
+#define HWMON_PWM_FAN_MODE_FULL	0x00
+#define HWMON_PWM_FAN_MODE_MANUAL	0x01
+#define HWMON_PWM_FAN_MODE_AUTO	0x02
+#define HWMON_PWM_FAN_MODE_EC_CURVE	0x03
+
+/* Handle ACPI lock mechanism */
+#define ACPI_LOCK_DELAY_MS 500
+
+int ayn_pwm_curve_registers[10] = {
+	AYN_SENSOR_PWM_FAN_SPEED_1_REG,
+	AYN_SENSOR_PWM_FAN_SPEED_2_REG,
+	AYN_SENSOR_PWM_FAN_SPEED_3_REG,
+	AYN_SENSOR_PWM_FAN_SPEED_4_REG,
+	AYN_SENSOR_PWM_FAN_SPEED_5_REG,
+	AYN_SENSOR_PWM_FAN_TEMP_1_REG,
+	AYN_SENSOR_PWM_FAN_TEMP_2_REG,
+	AYN_SENSOR_PWM_FAN_TEMP_3_REG,
+	AYN_SENSOR_PWM_FAN_TEMP_4_REG,
+	AYN_SENSOR_PWM_FAN_TEMP_5_REG,
+};
+
+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_ec_curve() - Enable manually setting the fan curve for automatic
+ * EC control of the fan.
+ */
+static int ayn_pwm_ec_curve(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)
+{
+	if (type != hwmon_pwm)
+		return -EOPNOTSUPP;
+	switch (attr) {
+	case hwmon_pwm_enable:
+		switch (val) {
+		case HWMON_PWM_FAN_MODE_FULL:
+			return ayn_pwm_full();
+		case HWMON_PWM_FAN_MODE_MANUAL:
+			return ayn_pwm_manual();
+		case HWMON_PWM_FAN_MODE_AUTO:
+			return ayn_pwm_auto();
+		case HWMON_PWM_FAN_MODE_EC_CURVE:
+			return ayn_pwm_ec_curve();
+		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:
+		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 i = to_sensor_dev_attr(attr)->index;
+	int ret, val;
+	u8 reg;
+
+	ret = kstrtoint(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (i < 5) {
+		if (val < 0 || val > 255)
+			return -EINVAL;
+		val = val >> 1; /* Max EC value is 128, scale from 255 */
+	} else
+		if (val < 0 || val > 100)
+			return -EINVAL;
+
+	reg = ayn_pwm_curve_registers[i];
+
+	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: 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 = to_sensor_dev_attr(attr)->index;
+	long val;
+	int ret;
+	u8 reg;
+
+	reg = ayn_pwm_curve_registers[i];
+
+	ret = read_from_ec(reg, 1, &val);
+	if (ret)
+		return ret;
+
+	if (i < 5)
+		val = val << 1; /* Max EC value is 128, scale to 255 */
+
+	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;
+
+	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[] = {
+	{
+		.ident = "AYN Loki Max",
+		.matches = {
+			DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
+			DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Max"),
+		},
+	},
+	{
+		.ident = "AYN Loki MiniPro",
+		.matches = {
+			DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
+			DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki MiniPro"),
+		},
+	},
+	{
+		.ident = "AYN Loki Zero",
+		.matches = {
+			DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ayn"),
+			DMI_EXACT_MATCH(DMI_BOARD_NAME, "Loki Zero"),
+		},
+	},
+	{
+		.ident = "Tectoy Zeenix Lite",
+		.matches = {
+			DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Tectoy"),
+			DMI_EXACT_MATCH(DMI_BOARD_NAME, "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@...il.com>");
+MODULE_DESCRIPTION("Platform driver that handles EC sensors of AYN x86 devices");
+MODULE_LICENSE("GPL");
-- 
2.50.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ