* [PATCH] hwmon: Add LattePanda Sigma EC driver
@ 2026-03-01 2:37 Mariano Abad
2026-03-02 0:42 ` Guenter Roeck
2026-03-02 18:35 ` [PATCH v2] " Mariano Abad
0 siblings, 2 replies; 5+ messages in thread
From: Mariano Abad @ 2026-03-01 2:37 UTC (permalink / raw)
To: linux-hwmon; +Cc: linux, corbet, linux-doc, linux-kernel, Mariano Abad
The LattePanda Sigma is an x86 single-board computer made by DFRobot,
featuring an Intel Core i5-1340P and an ITE IT8613E Embedded Controller
that manages fan speed and thermal monitoring.
The BIOS declares the ACPI Embedded Controller as disabled (_STA returns
0), so standard Linux hwmon interfaces do not expose the EC sensors.
This driver reads the EC directly via the ACPI EC I/O ports (0x62/0x66)
to provide:
- CPU fan speed (RPM)
- Board temperature
- CPU proximity temperature
The driver uses DMI matching and only loads on verified LattePanda Sigma
hardware. Fan speed is controlled autonomously by the EC firmware and is
read-only from the host.
The EC register map was discovered through firmware reverse engineering
and confirmed by physical testing (stopping the fan, observing RPM drop
to zero).
Signed-off-by: Mariano Abad <weimaraner@gmail.com>
---
Documentation/hwmon/lattepanda-sigma-ec.rst | 64 ++++
MAINTAINERS | 7 +
drivers/hwmon/Kconfig | 17 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/lattepanda-sigma-ec.c | 328 ++++++++++++++++++++
5 files changed, 417 insertions(+)
create mode 100644 Documentation/hwmon/lattepanda-sigma-ec.rst
create mode 100644 drivers/hwmon/lattepanda-sigma-ec.c
diff --git a/Documentation/hwmon/lattepanda-sigma-ec.rst b/Documentation/hwmon/lattepanda-sigma-ec.rst
new file mode 100644
index 000000000..e8bc9a71e
--- /dev/null
+++ b/Documentation/hwmon/lattepanda-sigma-ec.rst
@@ -0,0 +1,64 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver lattepanda-sigma-ec
+=================================
+
+Supported systems:
+
+ * LattePanda Sigma (Intel 13th Gen i5-1340P)
+
+ DMI vendor: LattePanda
+
+ DMI product: LattePanda Sigma
+
+ Datasheet: Not available (EC registers discovered empirically)
+
+Author: Mariano Abad <weimaraner@gmail.com>
+
+Description
+-----------
+
+This driver provides hardware monitoring for the LattePanda Sigma
+single-board computer. The board's Embedded Controller manages a CPU
+cooling fan but does not expose sensor data through standard ACPI
+interfaces.
+
+The BIOS declares the ACPI Embedded Controller (``PNP0C09``) with
+``_STA`` returning 0 (not present), preventing the kernel's ACPI EC
+subsystem from initializing. However, the EC hardware is fully
+functional on the standard ACPI EC I/O ports (``0x62`` data, ``0x66``
+command/status). This driver uses direct port I/O with EC read command
+``0x80`` to access sensor registers.
+
+The EC register map was discovered empirically by dumping all 256
+registers, identifying those that change in real-time, then validating
+by physically stopping the fan and observing the RPM drop to zero.
+
+The driver uses DMI matching and will only load on LattePanda Sigma
+hardware.
+
+Sysfs attributes
+----------------
+
+======================= ===============================================
+``fan1_input`` Fan speed in RPM (EC registers 0x2E:0x2F,
+ 16-bit big-endian)
+``fan1_label`` "CPU Fan"
+``temp1_input`` Board/ambient temperature in millidegrees
+ Celsius (EC register 0x60)
+``temp1_label`` "Board Temp"
+``temp2_input`` CPU proximity temperature in millidegrees
+ Celsius (EC register 0x70)
+``temp2_label`` "CPU Temp"
+======================= ===============================================
+
+Known limitations
+-----------------
+
+* The EC register map was reverse-engineered on a LattePanda Sigma with
+ BIOS version 5.27. Different BIOS versions may use different register
+ offsets.
+* Fan speed control is not supported. The fan is always under EC
+ automatic control.
+* The I/O ports ``0x62``/``0x66`` are shared with the ACPI EC subsystem
+ and are not exclusively reserved by this driver.
diff --git a/MAINTAINERS b/MAINTAINERS
index 96e97d25e..7b0c5bb5d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14414,6 +14414,13 @@ F: drivers/net/wan/framer/
F: drivers/pinctrl/pinctrl-pef2256.c
F: include/linux/framer/
+LATTEPANDA SIGMA EC HARDWARE MONITOR DRIVER
+M: Mariano Abad <weimaraner@gmail.com>
+L: linux-hwmon@vger.kernel.org
+S: Maintained
+F: Documentation/hwmon/lattepanda-sigma-ec.rst
+F: drivers/hwmon/lattepanda-sigma-ec.c
+
LASI 53c700 driver for PARISC
M: "James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>
L: linux-scsi@vger.kernel.org
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 41c381764..f2e2ee96f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -990,6 +990,23 @@ config SENSORS_LAN966X
This driver can also be built as a module. If so, the module
will be called lan966x-hwmon.
+config SENSORS_LATTEPANDA_SIGMA_EC
+ tristate "LattePanda Sigma EC hardware monitoring"
+ depends on X86
+ depends on DMI
+ depends on HAS_IOPORT
+ help
+ If you say yes here you get support for the hardware monitoring
+ features of the Embedded Controller on LattePanda Sigma
+ single-board computers, including CPU fan speed (RPM) and
+ board and CPU temperatures.
+
+ The driver reads the EC directly via ACPI EC I/O ports and
+ uses DMI matching to ensure it only loads on supported hardware.
+
+ This driver can also be built as a module. If so, the module
+ will be called lattepanda-sigma-ec.
+
config SENSORS_LENOVO_EC
tristate "Sensor reader for Lenovo ThinkStations"
depends on X86
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index eade8e3b1..0372fedbb 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -114,6 +114,7 @@ obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
obj-$(CONFIG_SENSORS_KFAN) += kfan.o
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
+obj-$(CONFIG_SENSORS_LATTEPANDA_SIGMA_EC) += lattepanda-sigma-ec.o
obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o
obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o
diff --git a/drivers/hwmon/lattepanda-sigma-ec.c b/drivers/hwmon/lattepanda-sigma-ec.c
new file mode 100644
index 000000000..60558e449
--- /dev/null
+++ b/drivers/hwmon/lattepanda-sigma-ec.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for LattePanda Sigma EC.
+ *
+ * Reads fan RPM and temperatures from the Embedded Controller via
+ * ACPI EC I/O ports (0x62 data, 0x66 cmd/status). The BIOS reports
+ * the ACPI EC as disabled (_STA=0), so direct port I/O is used.
+ *
+ * Copyright (c) 2026 Mariano Abad <weimaraner@gmail.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/dmi.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "lattepanda_sigma_ec"
+
+/* EC I/O ports (standard ACPI EC interface) */
+#define EC_DATA_PORT 0x62
+#define EC_CMD_PORT 0x66 /* also status port */
+
+/* EC commands */
+#define EC_CMD_READ 0x80
+
+/* EC status register bits */
+#define EC_STATUS_OBF 0x01 /* Output Buffer Full */
+#define EC_STATUS_IBF 0x02 /* Input Buffer Full */
+
+/* EC register offsets for LattePanda Sigma */
+#define EC_REG_FAN_RPM_HI 0x2E
+#define EC_REG_FAN_RPM_LO 0x2F
+#define EC_REG_TEMP1 0x60
+#define EC_REG_TEMP2 0x70
+#define EC_REG_FAN_DUTY 0x93
+
+/* Timeout for EC operations (in microseconds) */
+#define EC_TIMEOUT_US 25000
+#define EC_POLL_INTERVAL_US 5
+
+struct lattepanda_sigma_ec_data {
+ struct mutex lock; /* serialize EC access */
+};
+
+static struct platform_device *lps_ec_pdev;
+
+static int ec_wait_ibf_clear(void)
+{
+ int timeout = EC_TIMEOUT_US / EC_POLL_INTERVAL_US;
+
+ while (timeout--) {
+ if (!(inb(EC_CMD_PORT) & EC_STATUS_IBF))
+ return 0;
+ udelay(EC_POLL_INTERVAL_US);
+ }
+ return -ETIMEDOUT;
+}
+
+static int ec_wait_obf_set(void)
+{
+ int timeout = EC_TIMEOUT_US / EC_POLL_INTERVAL_US;
+
+ while (timeout--) {
+ if (inb(EC_CMD_PORT) & EC_STATUS_OBF)
+ return 0;
+ udelay(EC_POLL_INTERVAL_US);
+ }
+ return -ETIMEDOUT;
+}
+
+static int ec_read_reg(struct lattepanda_sigma_ec_data *data, u8 reg, u8 *val)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+
+ ret = ec_wait_ibf_clear();
+ if (ret)
+ goto out;
+
+ outb(EC_CMD_READ, EC_CMD_PORT);
+
+ ret = ec_wait_ibf_clear();
+ if (ret)
+ goto out;
+
+ outb(reg, EC_DATA_PORT);
+
+ ret = ec_wait_obf_set();
+ if (ret)
+ goto out;
+
+ *val = inb(EC_DATA_PORT);
+
+out:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+/*
+ * Read a 16-bit big-endian value from two consecutive EC registers.
+ * Both bytes are read within a single mutex hold to prevent tearing.
+ */
+static int ec_read_reg16(struct lattepanda_sigma_ec_data *data,
+ u8 reg_hi, u8 reg_lo, u16 *val)
+{
+ int ret;
+ u8 hi, lo;
+
+ mutex_lock(&data->lock);
+
+ /* Read high byte */
+ ret = ec_wait_ibf_clear();
+ if (ret)
+ goto out;
+ outb(EC_CMD_READ, EC_CMD_PORT);
+ ret = ec_wait_ibf_clear();
+ if (ret)
+ goto out;
+ outb(reg_hi, EC_DATA_PORT);
+ ret = ec_wait_obf_set();
+ if (ret)
+ goto out;
+ hi = inb(EC_DATA_PORT);
+
+ /* Read low byte */
+ ret = ec_wait_ibf_clear();
+ if (ret)
+ goto out;
+ outb(EC_CMD_READ, EC_CMD_PORT);
+ ret = ec_wait_ibf_clear();
+ if (ret)
+ goto out;
+ outb(reg_lo, EC_DATA_PORT);
+ ret = ec_wait_obf_set();
+ if (ret)
+ goto out;
+ lo = inb(EC_DATA_PORT);
+
+ *val = ((u16)hi << 8) | lo;
+
+out:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int
+lattepanda_sigma_ec_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel,
+ const char **str)
+{
+ switch (type) {
+ case hwmon_fan:
+ *str = "CPU Fan";
+ return 0;
+ case hwmon_temp:
+ *str = channel == 0 ? "Board Temp" : "CPU Temp";
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t
+lattepanda_sigma_ec_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_fan:
+ if (attr == hwmon_fan_input || attr == hwmon_fan_label)
+ return 0444;
+ break;
+ case hwmon_temp:
+ if (attr == hwmon_temp_input || attr == hwmon_temp_label)
+ return 0444;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int
+lattepanda_sigma_ec_read(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct lattepanda_sigma_ec_data *data = dev_get_drvdata(dev);
+ u16 rpm;
+ u8 v;
+ int ret;
+
+ switch (type) {
+ case hwmon_fan:
+ if (attr != hwmon_fan_input)
+ return -EOPNOTSUPP;
+ ret = ec_read_reg16(data, EC_REG_FAN_RPM_HI,
+ EC_REG_FAN_RPM_LO, &rpm);
+ if (ret)
+ return ret;
+ *val = rpm;
+ return 0;
+
+ case hwmon_temp:
+ if (attr != hwmon_temp_input)
+ return -EOPNOTSUPP;
+ ret = ec_read_reg(data,
+ channel == 0 ? EC_REG_TEMP1 : EC_REG_TEMP2,
+ &v);
+ if (ret)
+ return ret;
+ /* hwmon temps are in millidegrees Celsius */
+ *val = (long)v * 1000;
+ return 0;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct hwmon_channel_info * const lattepanda_sigma_ec_info[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops lattepanda_sigma_ec_ops = {
+ .is_visible = lattepanda_sigma_ec_is_visible,
+ .read = lattepanda_sigma_ec_read,
+ .read_string = lattepanda_sigma_ec_read_string,
+};
+
+static const struct hwmon_chip_info lattepanda_sigma_ec_chip_info = {
+ .ops = &lattepanda_sigma_ec_ops,
+ .info = lattepanda_sigma_ec_info,
+};
+
+static int lattepanda_sigma_ec_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lattepanda_sigma_ec_data *data;
+ struct device *hwmon;
+ u8 test;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ mutex_init(&data->lock);
+ platform_set_drvdata(pdev, data);
+
+ /* Sanity check: verify EC is responsive */
+ ret = ec_read_reg(data, EC_REG_FAN_DUTY, &test);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "EC not responding on ports 0x%x/0x%x\n",
+ EC_DATA_PORT, EC_CMD_PORT);
+
+ hwmon = devm_hwmon_device_register_with_info(dev, DRIVER_NAME, data,
+ &lattepanda_sigma_ec_chip_info,
+ NULL);
+ if (IS_ERR(hwmon))
+ return dev_err_probe(dev, PTR_ERR(hwmon),
+ "Failed to register hwmon device\n");
+
+ dev_dbg(dev, "EC hwmon registered (fan duty: %u%%)\n", test);
+ return 0;
+}
+
+static const struct dmi_system_id lattepanda_sigma_ec_dmi_table[] = {
+ {
+ .ident = "LattePanda Sigma",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"),
+ },
+ },
+ { } /* terminator */
+};
+MODULE_DEVICE_TABLE(dmi, lattepanda_sigma_ec_dmi_table);
+
+static struct platform_driver lattepanda_sigma_ec_driver = {
+ .probe = lattepanda_sigma_ec_probe,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init lattepanda_sigma_ec_init(void)
+{
+ int ret;
+
+ if (!dmi_check_system(lattepanda_sigma_ec_dmi_table))
+ return -ENODEV;
+
+ lps_ec_pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
+ if (IS_ERR(lps_ec_pdev))
+ return PTR_ERR(lps_ec_pdev);
+
+ ret = platform_driver_register(&lattepanda_sigma_ec_driver);
+ if (ret) {
+ platform_device_unregister(lps_ec_pdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit lattepanda_sigma_ec_exit(void)
+{
+ platform_driver_unregister(&lattepanda_sigma_ec_driver);
+ platform_device_unregister(lps_ec_pdev);
+}
+
+module_init(lattepanda_sigma_ec_init);
+module_exit(lattepanda_sigma_ec_exit);
+
+MODULE_AUTHOR("Mariano Abad <weimaraner@gmail.com>");
+MODULE_DESCRIPTION("Hardware monitoring driver for LattePanda Sigma EC");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] hwmon: Add LattePanda Sigma EC driver
2026-03-01 2:37 [PATCH] hwmon: Add LattePanda Sigma EC driver Mariano Abad
@ 2026-03-02 0:42 ` Guenter Roeck
2026-03-02 18:35 ` [PATCH v2] " Mariano Abad
1 sibling, 0 replies; 5+ messages in thread
From: Guenter Roeck @ 2026-03-02 0:42 UTC (permalink / raw)
To: Mariano Abad, linux-hwmon; +Cc: corbet, linux-doc, linux-kernel
On 2/28/26 18:37, Mariano Abad wrote:
> The LattePanda Sigma is an x86 single-board computer made by DFRobot,
> featuring an Intel Core i5-1340P and an ITE IT8613E Embedded Controller
> that manages fan speed and thermal monitoring.
>
> The BIOS declares the ACPI Embedded Controller as disabled (_STA returns
> 0), so standard Linux hwmon interfaces do not expose the EC sensors.
> This driver reads the EC directly via the ACPI EC I/O ports (0x62/0x66)
> to provide:
> - CPU fan speed (RPM)
> - Board temperature
> - CPU proximity temperature
>
> The driver uses DMI matching and only loads on verified LattePanda Sigma
> hardware. Fan speed is controlled autonomously by the EC firmware and is
> read-only from the host.
>
> The EC register map was discovered through firmware reverse engineering
> and confirmed by physical testing (stopping the fan, observing RPM drop
> to zero).
>
> Signed-off-by: Mariano Abad <weimaraner@gmail.com>
> ---
> Documentation/hwmon/lattepanda-sigma-ec.rst | 64 ++++
> MAINTAINERS | 7 +
> drivers/hwmon/Kconfig | 17 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/lattepanda-sigma-ec.c | 328 ++++++++++++++++++++
> 5 files changed, 417 insertions(+)
> create mode 100644 Documentation/hwmon/lattepanda-sigma-ec.rst
Needs to be added to Documentation/hwmon/index.rst.
> create mode 100644 drivers/hwmon/lattepanda-sigma-ec.c
>
> diff --git a/Documentation/hwmon/lattepanda-sigma-ec.rst b/Documentation/hwmon/lattepanda-sigma-ec.rst
> new file mode 100644
> index 000000000..e8bc9a71e
> --- /dev/null
> +++ b/Documentation/hwmon/lattepanda-sigma-ec.rst
> @@ -0,0 +1,64 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver lattepanda-sigma-ec
> +=================================
> +
> +Supported systems:
> +
> + * LattePanda Sigma (Intel 13th Gen i5-1340P)
> +
> + DMI vendor: LattePanda
> +
> + DMI product: LattePanda Sigma
> +
> + Datasheet: Not available (EC registers discovered empirically)
> +
> +Author: Mariano Abad <weimaraner@gmail.com>
> +
> +Description
> +-----------
> +
> +This driver provides hardware monitoring for the LattePanda Sigma
> +single-board computer. The board's Embedded Controller manages a CPU
> +cooling fan but does not expose sensor data through standard ACPI
> +interfaces.
> +
> +The BIOS declares the ACPI Embedded Controller (``PNP0C09``) with
> +``_STA`` returning 0 (not present), preventing the kernel's ACPI EC
> +subsystem from initializing. However, the EC hardware is fully
> +functional on the standard ACPI EC I/O ports (``0x62`` data, ``0x66``
> +command/status). This driver uses direct port I/O with EC read command
> +``0x80`` to access sensor registers.
> +
> +The EC register map was discovered empirically by dumping all 256
> +registers, identifying those that change in real-time, then validating
> +by physically stopping the fan and observing the RPM drop to zero.
> +
This should be a comment in the driver source code, not here,
explaining why the ACPI API function (specifically ec_read()) is not
used/usable.
> +The driver uses DMI matching and will only load on LattePanda Sigma
> +hardware.
> +
> +Sysfs attributes
> +----------------
> +
> +======================= ===============================================
> +``fan1_input`` Fan speed in RPM (EC registers 0x2E:0x2F,
> + 16-bit big-endian)
> +``fan1_label`` "CPU Fan"
> +``temp1_input`` Board/ambient temperature in millidegrees
> + Celsius (EC register 0x60)
> +``temp1_label`` "Board Temp"
> +``temp2_input`` CPU proximity temperature in millidegrees
> + Celsius (EC register 0x70)
> +``temp2_label`` "CPU Temp"
> +======================= ===============================================
> +
> +Known limitations
> +-----------------
> +
> +* The EC register map was reverse-engineered on a LattePanda Sigma with
> + BIOS version 5.27. Different BIOS versions may use different register
> + offsets.
That soulds kind of scary. It might be prudent to limit support to that
BIOS version and provide a force module parameter to override it.
> +* Fan speed control is not supported. The fan is always under EC
> + automatic control.
> +* The I/O ports ``0x62``/``0x66`` are shared with the ACPI EC subsystem
> + and are not exclusively reserved by this driver.
That pretty much directly contradicts the information above, which suggests
that ACPI is not active. Please move that comment and this one into the
driver because it is associated with the implementation.
Either case, if ACPI _is_ active, this will likely cause conflicts with
the ACPI code accessing the same registers. Worst case, this could result
in crashes or even damaged hardware if writes into the EC interfer with
operations setting the register address by this driver.
Is there maybe some other ACPI ID (besides PNP0C09) that can be used
(and maybe even is used to access/provide sensor data to Windows) ?
It may also be necessary to find and use an ACPI mutex/lock to prevent
parallel access from ACPI code. The asus-ec-sensors driver may provide
some guidance on how to do that.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 96e97d25e..7b0c5bb5d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14414,6 +14414,13 @@ F: drivers/net/wan/framer/
> F: drivers/pinctrl/pinctrl-pef2256.c
> F: include/linux/framer/
>
> +LATTEPANDA SIGMA EC HARDWARE MONITOR DRIVER
> +M: Mariano Abad <weimaraner@gmail.com>
> +L: linux-hwmon@vger.kernel.org
> +S: Maintained
> +F: Documentation/hwmon/lattepanda-sigma-ec.rst
> +F: drivers/hwmon/lattepanda-sigma-ec.c
> +
> LASI 53c700 driver for PARISC
> M: "James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>
> L: linux-scsi@vger.kernel.org
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 41c381764..f2e2ee96f 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -990,6 +990,23 @@ config SENSORS_LAN966X
> This driver can also be built as a module. If so, the module
> will be called lan966x-hwmon.
>
> +config SENSORS_LATTEPANDA_SIGMA_EC
> + tristate "LattePanda Sigma EC hardware monitoring"
> + depends on X86
> + depends on DMI
> + depends on HAS_IOPORT
> + help
> + If you say yes here you get support for the hardware monitoring
> + features of the Embedded Controller on LattePanda Sigma
> + single-board computers, including CPU fan speed (RPM) and
> + board and CPU temperatures.
> +
> + The driver reads the EC directly via ACPI EC I/O ports and
> + uses DMI matching to ensure it only loads on supported hardware.
> +
> + This driver can also be built as a module. If so, the module
> + will be called lattepanda-sigma-ec.
> +
> config SENSORS_LENOVO_EC
> tristate "Sensor reader for Lenovo ThinkStations"
> depends on X86
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index eade8e3b1..0372fedbb 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -114,6 +114,7 @@ obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
> obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
> obj-$(CONFIG_SENSORS_KFAN) += kfan.o
> obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
> +obj-$(CONFIG_SENSORS_LATTEPANDA_SIGMA_EC) += lattepanda-sigma-ec.o
> obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
> obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o
> obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o
> diff --git a/drivers/hwmon/lattepanda-sigma-ec.c b/drivers/hwmon/lattepanda-sigma-ec.c
> new file mode 100644
> index 000000000..60558e449
> --- /dev/null
> +++ b/drivers/hwmon/lattepanda-sigma-ec.c
> @@ -0,0 +1,328 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Hardware monitoring driver for LattePanda Sigma EC.
> + *
> + * Reads fan RPM and temperatures from the Embedded Controller via
> + * ACPI EC I/O ports (0x62 data, 0x66 cmd/status). The BIOS reports
> + * the ACPI EC as disabled (_STA=0), so direct port I/O is used.
> + *
> + * Copyright (c) 2026 Mariano Abad <weimaraner@gmail.com>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/dmi.h>
> +#include <linux/hwmon.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +
> +#define DRIVER_NAME "lattepanda_sigma_ec"
> +
> +/* EC I/O ports (standard ACPI EC interface) */
> +#define EC_DATA_PORT 0x62
> +#define EC_CMD_PORT 0x66 /* also status port */
> +
> +/* EC commands */
> +#define EC_CMD_READ 0x80
> +
> +/* EC status register bits */
> +#define EC_STATUS_OBF 0x01 /* Output Buffer Full */
> +#define EC_STATUS_IBF 0x02 /* Input Buffer Full */
> +
> +/* EC register offsets for LattePanda Sigma */
> +#define EC_REG_FAN_RPM_HI 0x2E
> +#define EC_REG_FAN_RPM_LO 0x2F
> +#define EC_REG_TEMP1 0x60
> +#define EC_REG_TEMP2 0x70
> +#define EC_REG_FAN_DUTY 0x93
> +
> +/* Timeout for EC operations (in microseconds) */
> +#define EC_TIMEOUT_US 25000
> +#define EC_POLL_INTERVAL_US 5
> +
> +struct lattepanda_sigma_ec_data {
> + struct mutex lock; /* serialize EC access */
> +};
The hardware monitoring subsystem provides locking for drivers using
the with_info API. I do not immediately see why another level of
locking would be needed for this driver.
> +
> +static struct platform_device *lps_ec_pdev;
> +
> +static int ec_wait_ibf_clear(void)
> +{
> + int timeout = EC_TIMEOUT_US / EC_POLL_INTERVAL_US;
> +
> + while (timeout--) {
> + if (!(inb(EC_CMD_PORT) & EC_STATUS_IBF))
> + return 0;
> + udelay(EC_POLL_INTERVAL_US);
> + }
> + return -ETIMEDOUT;
> +}
> +
> +static int ec_wait_obf_set(void)
> +{
> + int timeout = EC_TIMEOUT_US / EC_POLL_INTERVAL_US;
> +
> + while (timeout--) {
> + if (inb(EC_CMD_PORT) & EC_STATUS_OBF)
> + return 0;
> + udelay(EC_POLL_INTERVAL_US);
> + }
This results in up to 25 ms "hot" loop, blocking a CPU core.
If using ec_read() is not possible, you should consider using a longer
per-loop timeout and use usleep_range() to avoid the hot loop.
> + return -ETIMEDOUT;
> +}
> +
> +static int ec_read_reg(struct lattepanda_sigma_ec_data *data, u8 reg, u8 *val)
> +{
> + int ret;
> +
> + mutex_lock(&data->lock);
> +
> + ret = ec_wait_ibf_clear();
> + if (ret)
> + goto out;
> +
> + outb(EC_CMD_READ, EC_CMD_PORT);
> +
> + ret = ec_wait_ibf_clear();
> + if (ret)
> + goto out;
> +
> + outb(reg, EC_DATA_PORT);
> +
> + ret = ec_wait_obf_set();
> + if (ret)
> + goto out;
> +
> + *val = inb(EC_DATA_PORT);
> +
> +out:
> + mutex_unlock(&data->lock);
> + return ret;
> +}
> +
> +/*
> + * Read a 16-bit big-endian value from two consecutive EC registers.
> + * Both bytes are read within a single mutex hold to prevent tearing.
> + */
> +static int ec_read_reg16(struct lattepanda_sigma_ec_data *data,
> + u8 reg_hi, u8 reg_lo, u16 *val)
> +{
> + int ret;
> + u8 hi, lo;
> +
> + mutex_lock(&data->lock);
> +
> + /* Read high byte */
> + ret = ec_wait_ibf_clear();
> + if (ret)
> + goto out;
> + outb(EC_CMD_READ, EC_CMD_PORT);
> + ret = ec_wait_ibf_clear();
> + if (ret)
> + goto out;
> + outb(reg_hi, EC_DATA_PORT);
> + ret = ec_wait_obf_set();
> + if (ret)
> + goto out;
> + hi = inb(EC_DATA_PORT);
> +
> + /* Read low byte */
> + ret = ec_wait_ibf_clear();
> + if (ret)
> + goto out;
> + outb(EC_CMD_READ, EC_CMD_PORT);
> + ret = ec_wait_ibf_clear();
> + if (ret)
> + goto out;
> + outb(reg_lo, EC_DATA_PORT);
> + ret = ec_wait_obf_set();
> + if (ret)
> + goto out;
> + lo = inb(EC_DATA_PORT);
> +
> + *val = ((u16)hi << 8) | lo;
> +
> +out:
> + mutex_unlock(&data->lock);
> + return ret;
> +}
> +
> +static int
> +lattepanda_sigma_ec_read_string(struct device *dev,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel,
> + const char **str)
> +{
> + switch (type) {
> + case hwmon_fan:
> + *str = "CPU Fan";
> + return 0;
> + case hwmon_temp:
> + *str = channel == 0 ? "Board Temp" : "CPU Temp";
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static umode_t
> +lattepanda_sigma_ec_is_visible(const void *drvdata,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + switch (type) {
> + case hwmon_fan:
> + if (attr == hwmon_fan_input || attr == hwmon_fan_label)
> + return 0444;
> + break;
> + case hwmon_temp:
> + if (attr == hwmon_temp_input || attr == hwmon_temp_label)
> + return 0444;
> + break;
> + default:
> + break;
> + }
> + return 0;
> +}
> +
> +static int
> +lattepanda_sigma_ec_read(struct device *dev,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct lattepanda_sigma_ec_data *data = dev_get_drvdata(dev);
> + u16 rpm;
> + u8 v;
> + int ret;
> +
> + switch (type) {
> + case hwmon_fan:
> + if (attr != hwmon_fan_input)
> + return -EOPNOTSUPP;
> + ret = ec_read_reg16(data, EC_REG_FAN_RPM_HI,
> + EC_REG_FAN_RPM_LO, &rpm);
> + if (ret)
> + return ret;
> + *val = rpm;
> + return 0;
> +
> + case hwmon_temp:
> + if (attr != hwmon_temp_input)
> + return -EOPNOTSUPP;
> + ret = ec_read_reg(data,
> + channel == 0 ? EC_REG_TEMP1 : EC_REG_TEMP2,
> + &v);
> + if (ret)
> + return ret;
> + /* hwmon temps are in millidegrees Celsius */
> + *val = (long)v * 1000;
Is the temperature signed or unsigned ?
> + return 0;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static const struct hwmon_channel_info * const lattepanda_sigma_ec_info[] = {
> + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL),
> + HWMON_CHANNEL_INFO(temp,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL),
> + NULL
> +};
> +
> +static const struct hwmon_ops lattepanda_sigma_ec_ops = {
> + .is_visible = lattepanda_sigma_ec_is_visible,
> + .read = lattepanda_sigma_ec_read,
> + .read_string = lattepanda_sigma_ec_read_string,
> +};
> +
> +static const struct hwmon_chip_info lattepanda_sigma_ec_chip_info = {
> + .ops = &lattepanda_sigma_ec_ops,
> + .info = lattepanda_sigma_ec_info,
> +};
> +
> +static int lattepanda_sigma_ec_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct lattepanda_sigma_ec_data *data;
> + struct device *hwmon;
> + u8 test;
> + int ret;
> +
> + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + mutex_init(&data->lock);
> + platform_set_drvdata(pdev, data);
> +
> + /* Sanity check: verify EC is responsive */
> + ret = ec_read_reg(data, EC_REG_FAN_DUTY, &test);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "EC not responding on ports 0x%x/0x%x\n",
> + EC_DATA_PORT, EC_CMD_PORT);
> +
> + hwmon = devm_hwmon_device_register_with_info(dev, DRIVER_NAME, data,
> + &lattepanda_sigma_ec_chip_info,
> + NULL);
> + if (IS_ERR(hwmon))
> + return dev_err_probe(dev, PTR_ERR(hwmon),
> + "Failed to register hwmon device\n");
> +
> + dev_dbg(dev, "EC hwmon registered (fan duty: %u%%)\n", test);
> + return 0;
> +}
> +
> +static const struct dmi_system_id lattepanda_sigma_ec_dmi_table[] = {
> + {
> + .ident = "LattePanda Sigma",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"),
> + },
> + },
> + { } /* terminator */
> +};
> +MODULE_DEVICE_TABLE(dmi, lattepanda_sigma_ec_dmi_table);
> +
> +static struct platform_driver lattepanda_sigma_ec_driver = {
> + .probe = lattepanda_sigma_ec_probe,
> + .driver = {
> + .name = DRIVER_NAME,
> + },
> +};
> +
> +static int __init lattepanda_sigma_ec_init(void)
> +{
> + int ret;
> +
> + if (!dmi_check_system(lattepanda_sigma_ec_dmi_table))
> + return -ENODEV;
> +
> + lps_ec_pdev = platform_device_register_simple(DRIVER_NAME, -1, NULL, 0);
> + if (IS_ERR(lps_ec_pdev))
> + return PTR_ERR(lps_ec_pdev);
> +
> + ret = platform_driver_register(&lattepanda_sigma_ec_driver);
> + if (ret) {
> + platform_device_unregister(lps_ec_pdev);
> + return ret;
> + }
It is quite unusual to register the device first, followed by the driver.
All other hardware monitoring drivers register the driver first, followed
by the device, and that order makes much more sense to me.
Is there a specific reason for reversing the order ? If so, please provide
a detailed explanation as comment. Otherwise please register the driver first.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2] hwmon: Add LattePanda Sigma EC driver
2026-03-01 2:37 [PATCH] hwmon: Add LattePanda Sigma EC driver Mariano Abad
2026-03-02 0:42 ` Guenter Roeck
@ 2026-03-02 18:35 ` Mariano Abad
2026-03-02 20:00 ` Guenter Roeck
2026-03-02 21:45 ` Guenter Roeck
1 sibling, 2 replies; 5+ messages in thread
From: Mariano Abad @ 2026-03-02 18:35 UTC (permalink / raw)
To: linux-hwmon; +Cc: linux, corbet, linux-doc, linux-kernel, Mariano Abad
The LattePanda Sigma is an x86 single-board computer made by DFRobot,
featuring an Intel Core i5-1340P and an ITE IT8613E Embedded Controller
that manages fan speed and thermal monitoring.
The BIOS declares the ACPI Embedded Controller (PNP0C09) with _STA
returning 0 and provides only stub ECRD/ECWT methods. Since the kernel's
ACPI EC subsystem never initializes, ec_read() is not available and
direct port I/O to the standard ACPI EC ports (0x62/0x66) is used. As
ACPI never accesses the EC, no ACPI Global Lock or namespace mutex is
required.
The driver exposes:
- CPU fan speed (RPM, read-only)
- Board temperature (unsigned 8-bit, degrees Celsius)
- CPU proximity temperature (unsigned 8-bit, degrees Celsius)
DMI matching restricts the driver to verified LattePanda Sigma hardware
with BIOS version 5.27. A 'force' module parameter allows loading on
untested BIOS versions while still requiring vendor/product match.
The EC register map was discovered through firmware reverse engineering
and confirmed by physical testing.
Signed-off-by: Mariano Abad <weimaraner@gmail.com>
---
Changes in v2:
- Add entry to Documentation/hwmon/index.rst
- Move reverse engineering notes and ACPI explanation from .rst
into driver source comments
- Restrict DMI match to BIOS version 5.27; add 'force' module
parameter for untested versions
- Document why no ACPI lock is needed: DSDT analysis shows _STA
returns 0 and ECRD/ECWT are stubs, so firmware never accesses
the EC ports
- Remove driver mutex: the hwmon with_info API already serializes
all sysfs callbacks
- Keep udelay() for EC polling: usleep_range() was tested but
caused EC protocol failures; approach matches the kernel ACPI
EC driver (drivers/acpi/ec.c)
- Clarify temperature values are unsigned 8-bit
- Register platform driver before platform device
- Link to v1: https://lore.kernel.org/linux-hwmon/20260301023707.1184592-1-weimaraner@gmail.com/
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/lattepanda-sigma-ec.rst | 61 ++++
MAINTAINERS | 7 +
drivers/hwmon/Kconfig | 17 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/lattepanda-sigma-ec.c | 329 ++++++++++++++++++++
6 files changed, 416 insertions(+)
create mode 100644 Documentation/hwmon/lattepanda-sigma-ec.rst
create mode 100644 drivers/hwmon/lattepanda-sigma-ec.c
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index d91dbb20c..dff283064 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -111,6 +111,7 @@ Hardware Monitoring Kernel Drivers
kbatt
kfan
lan966x
+ lattepanda-sigma-ec
lineage-pem
lm25066
lm63
diff --git a/Documentation/hwmon/lattepanda-sigma-ec.rst b/Documentation/hwmon/lattepanda-sigma-ec.rst
new file mode 100644
index 000000000..8a521ee1f
--- /dev/null
+++ b/Documentation/hwmon/lattepanda-sigma-ec.rst
@@ -0,0 +1,61 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver lattepanda-sigma-ec
+=================================
+
+Supported systems:
+
+ * LattePanda Sigma (Intel 13th Gen i5-1340P)
+
+ DMI vendor: LattePanda
+
+ DMI product: LattePanda Sigma
+
+ BIOS version: 5.27 (verified)
+
+ Datasheet: Not available (EC registers discovered empirically)
+
+Author: Mariano Abad <weimaraner@gmail.com>
+
+Description
+-----------
+
+This driver provides hardware monitoring for the LattePanda Sigma
+single-board computer made by DFRobot. The board uses an ITE IT8613E
+Embedded Controller to manage a CPU cooling fan and thermal sensors.
+
+The BIOS declares the ACPI Embedded Controller (``PNP0C09``) with
+``_STA`` returning 0, preventing the kernel's ACPI EC subsystem from
+initializing. This driver reads the EC directly via the standard ACPI
+EC I/O ports (``0x62`` data, ``0x66`` command/status).
+
+Sysfs attributes
+----------------
+
+======================= ===============================================
+``fan1_input`` Fan speed in RPM (EC registers 0x2E:0x2F,
+ 16-bit big-endian)
+``fan1_label`` "CPU Fan"
+``temp1_input`` Board/ambient temperature in millidegrees
+ Celsius (EC register 0x60, unsigned)
+``temp1_label`` "Board Temp"
+``temp2_input`` CPU proximity temperature in millidegrees
+ Celsius (EC register 0x70, unsigned)
+``temp2_label`` "CPU Temp"
+======================= ===============================================
+
+Module parameters
+-----------------
+
+``force`` (bool, default false)
+ Force loading on BIOS versions other than 5.27. The driver still
+ requires DMI vendor and product name matching.
+
+Known limitations
+-----------------
+
+* Fan speed control is not supported. The fan is always under EC
+ automatic control.
+* The EC register map was verified only on BIOS version 5.27.
+ Other versions may use different register offsets; use the ``force``
+ parameter at your own risk.
diff --git a/MAINTAINERS b/MAINTAINERS
index 96e97d25e..7b0c5bb5d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14414,6 +14414,13 @@ F: drivers/net/wan/framer/
F: drivers/pinctrl/pinctrl-pef2256.c
F: include/linux/framer/
+LATTEPANDA SIGMA EC HARDWARE MONITOR DRIVER
+M: Mariano Abad <weimaraner@gmail.com>
+L: linux-hwmon@vger.kernel.org
+S: Maintained
+F: Documentation/hwmon/lattepanda-sigma-ec.rst
+F: drivers/hwmon/lattepanda-sigma-ec.c
+
LASI 53c700 driver for PARISC
M: "James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>
L: linux-scsi@vger.kernel.org
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 41c381764..f2e2ee96f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -990,6 +990,23 @@ config SENSORS_LAN966X
This driver can also be built as a module. If so, the module
will be called lan966x-hwmon.
+config SENSORS_LATTEPANDA_SIGMA_EC
+ tristate "LattePanda Sigma EC hardware monitoring"
+ depends on X86
+ depends on DMI
+ depends on HAS_IOPORT
+ help
+ If you say yes here you get support for the hardware monitoring
+ features of the Embedded Controller on LattePanda Sigma
+ single-board computers, including CPU fan speed (RPM) and
+ board and CPU temperatures.
+
+ The driver reads the EC directly via ACPI EC I/O ports and
+ uses DMI matching to ensure it only loads on supported hardware.
+
+ This driver can also be built as a module. If so, the module
+ will be called lattepanda-sigma-ec.
+
config SENSORS_LENOVO_EC
tristate "Sensor reader for Lenovo ThinkStations"
depends on X86
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index eade8e3b1..0372fedbb 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -114,6 +114,7 @@ obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
obj-$(CONFIG_SENSORS_KFAN) += kfan.o
obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
+obj-$(CONFIG_SENSORS_LATTEPANDA_SIGMA_EC) += lattepanda-sigma-ec.o
obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o
obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o
diff --git a/drivers/hwmon/lattepanda-sigma-ec.c b/drivers/hwmon/lattepanda-sigma-ec.c
new file mode 100644
index 000000000..2ba51a20d
--- /dev/null
+++ b/drivers/hwmon/lattepanda-sigma-ec.c
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for LattePanda Sigma EC.
+ *
+ * The LattePanda Sigma is an x86 SBC made by DFRobot with an ITE IT8613E
+ * Embedded Controller that manages a CPU fan and thermal sensors.
+ *
+ * The BIOS declares the ACPI Embedded Controller (PNP0C09) with _STA
+ * returning 0 and provides only stub ECRD/ECWT methods that return Zero
+ * for all registers. Since the kernel's ACPI EC subsystem never initializes,
+ * ec_read() is not available and direct port I/O to the standard ACPI EC
+ * ports (0x62/0x66) is used instead.
+ *
+ * Because ACPI never initializes the EC, there is no concurrent firmware
+ * access to these ports, and no ACPI Global Lock or namespace mutex is
+ * required. The hwmon with_info API serializes all sysfs callbacks,
+ * so no additional driver-level locking is needed.
+ *
+ * The EC register map was discovered by dumping all 256 registers,
+ * identifying those that change in real-time, and validating by physically
+ * stopping the fan and observing the RPM register drop to zero. The map
+ * has been verified on BIOS version 5.27; other versions may differ.
+ *
+ * Copyright (c) 2026 Mariano Abad <weimaraner@gmail.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/dmi.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "lattepanda_sigma_ec"
+
+/* EC I/O ports (standard ACPI EC interface) */
+#define EC_DATA_PORT 0x62
+#define EC_CMD_PORT 0x66 /* also status port */
+
+/* EC commands */
+#define EC_CMD_READ 0x80
+
+/* EC status register bits */
+#define EC_STATUS_OBF 0x01 /* Output Buffer Full */
+#define EC_STATUS_IBF 0x02 /* Input Buffer Full */
+
+/* EC register offsets for LattePanda Sigma (BIOS 5.27) */
+#define EC_REG_FAN_RPM_HI 0x2E
+#define EC_REG_FAN_RPM_LO 0x2F
+#define EC_REG_TEMP_BOARD 0x60
+#define EC_REG_TEMP_CPU 0x70
+#define EC_REG_FAN_DUTY 0x93
+
+/*
+ * EC polling uses udelay() because the EC typically responds within a
+ * few microseconds. The kernel's own ACPI EC driver (drivers/acpi/ec.c)
+ * likewise uses udelay() for busy-polling with a per-poll delay of 550us.
+ *
+ * usleep_range() was tested but caused EC protocol failures: the EC
+ * clears its status flags within microseconds, and sleeping for 50-100us
+ * between polls allowed the flags to transition past the expected state.
+ *
+ * The worst-case total busy-wait of 25ms covers EC recovery after errors.
+ * In practice the EC responds within 10us so the loop exits immediately.
+ */
+#define EC_TIMEOUT_US 25000
+#define EC_POLL_US 1
+
+static bool force;
+module_param(force, bool, 0444);
+MODULE_PARM_DESC(force,
+ "Force loading on untested BIOS versions (default: false)");
+
+static struct platform_device *lps_ec_pdev;
+
+static int ec_wait_ibf_clear(void)
+{
+ int i;
+
+ for (i = 0; i < EC_TIMEOUT_US; i++) {
+ if (!(inb(EC_CMD_PORT) & EC_STATUS_IBF))
+ return 0;
+ udelay(EC_POLL_US);
+ }
+ return -ETIMEDOUT;
+}
+
+static int ec_wait_obf_set(void)
+{
+ int i;
+
+ for (i = 0; i < EC_TIMEOUT_US; i++) {
+ if (inb(EC_CMD_PORT) & EC_STATUS_OBF)
+ return 0;
+ udelay(EC_POLL_US);
+ }
+ return -ETIMEDOUT;
+}
+
+static int ec_read_reg(u8 reg, u8 *val)
+{
+ int ret;
+
+ ret = ec_wait_ibf_clear();
+ if (ret)
+ return ret;
+
+ outb(EC_CMD_READ, EC_CMD_PORT);
+
+ ret = ec_wait_ibf_clear();
+ if (ret)
+ return ret;
+
+ outb(reg, EC_DATA_PORT);
+
+ ret = ec_wait_obf_set();
+ if (ret)
+ return ret;
+
+ *val = inb(EC_DATA_PORT);
+ return 0;
+}
+
+/* Read a 16-bit big-endian value from two consecutive EC registers. */
+static int ec_read_reg16(u8 reg_hi, u8 reg_lo, u16 *val)
+{
+ int ret;
+ u8 hi, lo;
+
+ ret = ec_read_reg(reg_hi, &hi);
+ if (ret)
+ return ret;
+
+ ret = ec_read_reg(reg_lo, &lo);
+ if (ret)
+ return ret;
+
+ *val = ((u16)hi << 8) | lo;
+ return 0;
+}
+
+static int
+lps_ec_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel,
+ const char **str)
+{
+ switch (type) {
+ case hwmon_fan:
+ *str = "CPU Fan";
+ return 0;
+ case hwmon_temp:
+ *str = channel == 0 ? "Board Temp" : "CPU Temp";
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t
+lps_ec_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_fan:
+ if (attr == hwmon_fan_input || attr == hwmon_fan_label)
+ return 0444;
+ break;
+ case hwmon_temp:
+ if (attr == hwmon_temp_input || attr == hwmon_temp_label)
+ return 0444;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int
+lps_ec_read(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ u16 rpm;
+ u8 v;
+ int ret;
+
+ switch (type) {
+ case hwmon_fan:
+ if (attr != hwmon_fan_input)
+ return -EOPNOTSUPP;
+ ret = ec_read_reg16(EC_REG_FAN_RPM_HI,
+ EC_REG_FAN_RPM_LO, &rpm);
+ if (ret)
+ return ret;
+ *val = rpm;
+ return 0;
+
+ case hwmon_temp:
+ if (attr != hwmon_temp_input)
+ return -EOPNOTSUPP;
+ ret = ec_read_reg(channel == 0 ? EC_REG_TEMP_BOARD
+ : EC_REG_TEMP_CPU,
+ &v);
+ if (ret)
+ return ret;
+ /* EC reports unsigned 8-bit temperature in degrees Celsius */
+ *val = (unsigned long)v * 1000;
+ return 0;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static const struct hwmon_channel_info * const lps_ec_info[] = {
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops lps_ec_ops = {
+ .is_visible = lps_ec_is_visible,
+ .read = lps_ec_read,
+ .read_string = lps_ec_read_string,
+};
+
+static const struct hwmon_chip_info lps_ec_chip_info = {
+ .ops = &lps_ec_ops,
+ .info = lps_ec_info,
+};
+
+static int lps_ec_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device *hwmon;
+ u8 test;
+ int ret;
+
+ /* Sanity check: verify EC is responsive */
+ ret = ec_read_reg(EC_REG_FAN_DUTY, &test);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "EC not responding on ports 0x%x/0x%x\n",
+ EC_DATA_PORT, EC_CMD_PORT);
+
+ hwmon = devm_hwmon_device_register_with_info(dev, DRIVER_NAME, NULL,
+ &lps_ec_chip_info, NULL);
+ if (IS_ERR(hwmon))
+ return dev_err_probe(dev, PTR_ERR(hwmon),
+ "Failed to register hwmon device\n");
+
+ dev_info(dev, "EC hwmon registered (fan duty: %u%%)\n", test);
+ return 0;
+}
+
+/* DMI table with strict BIOS version match (override with force=1) */
+static const struct dmi_system_id lps_ec_dmi_table[] = {
+ {
+ .ident = "LattePanda Sigma",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"),
+ DMI_MATCH(DMI_BIOS_VERSION, "5.27"),
+ },
+ },
+ { } /* terminator */
+};
+MODULE_DEVICE_TABLE(dmi, lps_ec_dmi_table);
+
+/* Loose table (vendor + product only) for use with force=1 */
+static const struct dmi_system_id lps_ec_dmi_table_force[] = {
+ {
+ .ident = "LattePanda Sigma",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"),
+ },
+ },
+ { } /* terminator */
+};
+
+static struct platform_driver lps_ec_driver = {
+ .probe = lps_ec_probe,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init lps_ec_init(void)
+{
+ int ret;
+
+ if (!dmi_check_system(lps_ec_dmi_table)) {
+ if (!force || !dmi_check_system(lps_ec_dmi_table_force))
+ return -ENODEV;
+ pr_warn("%s: BIOS version not verified, loading due to force=1\n",
+ DRIVER_NAME);
+ }
+
+ ret = platform_driver_register(&lps_ec_driver);
+ if (ret)
+ return ret;
+
+ lps_ec_pdev = platform_device_register_simple(DRIVER_NAME, -1,
+ NULL, 0);
+ if (IS_ERR(lps_ec_pdev)) {
+ platform_driver_unregister(&lps_ec_driver);
+ return PTR_ERR(lps_ec_pdev);
+ }
+
+ return 0;
+}
+
+static void __exit lps_ec_exit(void)
+{
+ platform_device_unregister(lps_ec_pdev);
+ platform_driver_unregister(&lps_ec_driver);
+}
+
+module_init(lps_ec_init);
+module_exit(lps_ec_exit);
+
+MODULE_AUTHOR("Mariano Abad <weimaraner@gmail.com>");
+MODULE_DESCRIPTION("Hardware monitoring driver for LattePanda Sigma EC");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2] hwmon: Add LattePanda Sigma EC driver
2026-03-02 18:35 ` [PATCH v2] " Mariano Abad
@ 2026-03-02 20:00 ` Guenter Roeck
2026-03-02 21:45 ` Guenter Roeck
1 sibling, 0 replies; 5+ messages in thread
From: Guenter Roeck @ 2026-03-02 20:00 UTC (permalink / raw)
To: Mariano Abad, linux-hwmon; +Cc: corbet, linux-doc, linux-kernel
On 3/2/26 10:35, Mariano Abad wrote:
> The LattePanda Sigma is an x86 single-board computer made by DFRobot,
> featuring an Intel Core i5-1340P and an ITE IT8613E Embedded Controller
> that manages fan speed and thermal monitoring.
>
> The BIOS declares the ACPI Embedded Controller (PNP0C09) with _STA
> returning 0 and provides only stub ECRD/ECWT methods. Since the kernel's
> ACPI EC subsystem never initializes, ec_read() is not available and
> direct port I/O to the standard ACPI EC ports (0x62/0x66) is used. As
> ACPI never accesses the EC, no ACPI Global Lock or namespace mutex is
> required.
>
> The driver exposes:
> - CPU fan speed (RPM, read-only)
> - Board temperature (unsigned 8-bit, degrees Celsius)
> - CPU proximity temperature (unsigned 8-bit, degrees Celsius)
>
> DMI matching restricts the driver to verified LattePanda Sigma hardware
> with BIOS version 5.27. A 'force' module parameter allows loading on
> untested BIOS versions while still requiring vendor/product match.
>
> The EC register map was discovered through firmware reverse engineering
> and confirmed by physical testing.
>
This should not be part of the commit message.
Couple of additional comments below. Most importantly, there needs to
be some i/o address protection to help ensure that no other driver
accesses to IO port range. Presumably the ec driver should bail out
and not reserve the addresses for itself.
Thanks,
Guenter
> Signed-off-by: Mariano Abad <weimaraner@gmail.com>
> ---
> Changes in v2:
> - Add entry to Documentation/hwmon/index.rst
> - Move reverse engineering notes and ACPI explanation from .rst
> into driver source comments
> - Restrict DMI match to BIOS version 5.27; add 'force' module
> parameter for untested versions
> - Document why no ACPI lock is needed: DSDT analysis shows _STA
> returns 0 and ECRD/ECWT are stubs, so firmware never accesses
> the EC ports
> - Remove driver mutex: the hwmon with_info API already serializes
> all sysfs callbacks
> - Keep udelay() for EC polling: usleep_range() was tested but
> caused EC protocol failures; approach matches the kernel ACPI
> EC driver (drivers/acpi/ec.c)
> - Clarify temperature values are unsigned 8-bit
> - Register platform driver before platform device
> - Link to v1: https://lore.kernel.org/linux-hwmon/20260301023707.1184592-1-weimaraner@gmail.com/
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/lattepanda-sigma-ec.rst | 61 ++++
> MAINTAINERS | 7 +
> drivers/hwmon/Kconfig | 17 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/lattepanda-sigma-ec.c | 329 ++++++++++++++++++++
> 6 files changed, 416 insertions(+)
> create mode 100644 Documentation/hwmon/lattepanda-sigma-ec.rst
> create mode 100644 drivers/hwmon/lattepanda-sigma-ec.c
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index d91dbb20c..dff283064 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -111,6 +111,7 @@ Hardware Monitoring Kernel Drivers
> kbatt
> kfan
> lan966x
> + lattepanda-sigma-ec
> lineage-pem
> lm25066
> lm63
> diff --git a/Documentation/hwmon/lattepanda-sigma-ec.rst b/Documentation/hwmon/lattepanda-sigma-ec.rst
> new file mode 100644
> index 000000000..8a521ee1f
> --- /dev/null
> +++ b/Documentation/hwmon/lattepanda-sigma-ec.rst
> @@ -0,0 +1,61 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver lattepanda-sigma-ec
> +=================================
> +
> +Supported systems:
> +
> + * LattePanda Sigma (Intel 13th Gen i5-1340P)
> +
> + DMI vendor: LattePanda
> +
> + DMI product: LattePanda Sigma
> +
> + BIOS version: 5.27 (verified)
> +
> + Datasheet: Not available (EC registers discovered empirically)
> +
> +Author: Mariano Abad <weimaraner@gmail.com>
> +
> +Description
> +-----------
> +
> +This driver provides hardware monitoring for the LattePanda Sigma
> +single-board computer made by DFRobot. The board uses an ITE IT8613E
> +Embedded Controller to manage a CPU cooling fan and thermal sensors.
> +
> +The BIOS declares the ACPI Embedded Controller (``PNP0C09``) with
> +``_STA`` returning 0, preventing the kernel's ACPI EC subsystem from
> +initializing. This driver reads the EC directly via the standard ACPI
> +EC I/O ports (``0x62`` data, ``0x66`` command/status).
> +
> +Sysfs attributes
> +----------------
> +
> +======================= ===============================================
> +``fan1_input`` Fan speed in RPM (EC registers 0x2E:0x2F,
> + 16-bit big-endian)
> +``fan1_label`` "CPU Fan"
> +``temp1_input`` Board/ambient temperature in millidegrees
> + Celsius (EC register 0x60, unsigned)
> +``temp1_label`` "Board Temp"
> +``temp2_input`` CPU proximity temperature in millidegrees
> + Celsius (EC register 0x70, unsigned)
> +``temp2_label`` "CPU Temp"
> +======================= ===============================================
> +
> +Module parameters
> +-----------------
> +
> +``force`` (bool, default false)
> + Force loading on BIOS versions other than 5.27. The driver still
> + requires DMI vendor and product name matching.
> +
> +Known limitations
> +-----------------
> +
> +* Fan speed control is not supported. The fan is always under EC
> + automatic control.
> +* The EC register map was verified only on BIOS version 5.27.
> + Other versions may use different register offsets; use the ``force``
> + parameter at your own risk.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 96e97d25e..7b0c5bb5d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14414,6 +14414,13 @@ F: drivers/net/wan/framer/
> F: drivers/pinctrl/pinctrl-pef2256.c
> F: include/linux/framer/
>
> +LATTEPANDA SIGMA EC HARDWARE MONITOR DRIVER
> +M: Mariano Abad <weimaraner@gmail.com>
> +L: linux-hwmon@vger.kernel.org
> +S: Maintained
> +F: Documentation/hwmon/lattepanda-sigma-ec.rst
> +F: drivers/hwmon/lattepanda-sigma-ec.c
> +
> LASI 53c700 driver for PARISC
> M: "James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>
> L: linux-scsi@vger.kernel.org
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 41c381764..f2e2ee96f 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -990,6 +990,23 @@ config SENSORS_LAN966X
> This driver can also be built as a module. If so, the module
> will be called lan966x-hwmon.
>
> +config SENSORS_LATTEPANDA_SIGMA_EC
> + tristate "LattePanda Sigma EC hardware monitoring"
> + depends on X86
> + depends on DMI
> + depends on HAS_IOPORT
> + help
> + If you say yes here you get support for the hardware monitoring
> + features of the Embedded Controller on LattePanda Sigma
> + single-board computers, including CPU fan speed (RPM) and
> + board and CPU temperatures.
> +
> + The driver reads the EC directly via ACPI EC I/O ports and
> + uses DMI matching to ensure it only loads on supported hardware.
> +
> + This driver can also be built as a module. If so, the module
> + will be called lattepanda-sigma-ec.
> +
> config SENSORS_LENOVO_EC
> tristate "Sensor reader for Lenovo ThinkStations"
> depends on X86
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index eade8e3b1..0372fedbb 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -114,6 +114,7 @@ obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
> obj-$(CONFIG_SENSORS_KBATT) += kbatt.o
> obj-$(CONFIG_SENSORS_KFAN) += kfan.o
> obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o
> +obj-$(CONFIG_SENSORS_LATTEPANDA_SIGMA_EC) += lattepanda-sigma-ec.o
> obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o
> obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o
> obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o
> diff --git a/drivers/hwmon/lattepanda-sigma-ec.c b/drivers/hwmon/lattepanda-sigma-ec.c
> new file mode 100644
> index 000000000..2ba51a20d
> --- /dev/null
> +++ b/drivers/hwmon/lattepanda-sigma-ec.c
> @@ -0,0 +1,329 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Hardware monitoring driver for LattePanda Sigma EC.
> + *
> + * The LattePanda Sigma is an x86 SBC made by DFRobot with an ITE IT8613E
> + * Embedded Controller that manages a CPU fan and thermal sensors.
> + *
> + * The BIOS declares the ACPI Embedded Controller (PNP0C09) with _STA
> + * returning 0 and provides only stub ECRD/ECWT methods that return Zero
> + * for all registers. Since the kernel's ACPI EC subsystem never initializes,
> + * ec_read() is not available and direct port I/O to the standard ACPI EC
> + * ports (0x62/0x66) is used instead.
> + *
> + * Because ACPI never initializes the EC, there is no concurrent firmware
> + * access to these ports, and no ACPI Global Lock or namespace mutex is
> + * required. The hwmon with_info API serializes all sysfs callbacks,
> + * so no additional driver-level locking is needed.
> + *
If the io space is only used by this driver, it should be reserved,
either with request_muxed_region() while in use or permanently with
request_region() for both the command and data port to ensure that it
is used exclusively by this driver.
> + * The EC register map was discovered by dumping all 256 registers,
> + * identifying those that change in real-time, and validating by physically
> + * stopping the fan and observing the RPM register drop to zero. The map
> + * has been verified on BIOS version 5.27; other versions may differ.
> + *
> + * Copyright (c) 2026 Mariano Abad <weimaraner@gmail.com>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/dmi.h>
> +#include <linux/hwmon.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +#define DRIVER_NAME "lattepanda_sigma_ec"
> +
> +/* EC I/O ports (standard ACPI EC interface) */
> +#define EC_DATA_PORT 0x62
> +#define EC_CMD_PORT 0x66 /* also status port */
> +
> +/* EC commands */
> +#define EC_CMD_READ 0x80
> +
> +/* EC status register bits */
> +#define EC_STATUS_OBF 0x01 /* Output Buffer Full */
> +#define EC_STATUS_IBF 0x02 /* Input Buffer Full */
> +
> +/* EC register offsets for LattePanda Sigma (BIOS 5.27) */
> +#define EC_REG_FAN_RPM_HI 0x2E
> +#define EC_REG_FAN_RPM_LO 0x2F
> +#define EC_REG_TEMP_BOARD 0x60
> +#define EC_REG_TEMP_CPU 0x70
> +#define EC_REG_FAN_DUTY 0x93
> +
> +/*
> + * EC polling uses udelay() because the EC typically responds within a
> + * few microseconds. The kernel's own ACPI EC driver (drivers/acpi/ec.c)
> + * likewise uses udelay() for busy-polling with a per-poll delay of 550us.
> + *
> + * usleep_range() was tested but caused EC protocol failures: the EC
> + * clears its status flags within microseconds, and sleeping for 50-100us
> + * between polls allowed the flags to transition past the expected state.
> + *
> + * The worst-case total busy-wait of 25ms covers EC recovery after errors.
> + * In practice the EC responds within 10us so the loop exits immediately.
> + */
> +#define EC_TIMEOUT_US 25000
> +#define EC_POLL_US 1
> +
> +static bool force;
> +module_param(force, bool, 0444);
> +MODULE_PARM_DESC(force,
> + "Force loading on untested BIOS versions (default: false)");
> +
> +static struct platform_device *lps_ec_pdev;
> +
> +static int ec_wait_ibf_clear(void)
> +{
> + int i;
> +
> + for (i = 0; i < EC_TIMEOUT_US; i++) {
> + if (!(inb(EC_CMD_PORT) & EC_STATUS_IBF))
> + return 0;
> + udelay(EC_POLL_US);
> + }
> + return -ETIMEDOUT;
> +}
> +
> +static int ec_wait_obf_set(void)
> +{
> + int i;
> +
> + for (i = 0; i < EC_TIMEOUT_US; i++) {
> + if (inb(EC_CMD_PORT) & EC_STATUS_OBF)
> + return 0;
> + udelay(EC_POLL_US);
> + }
> + return -ETIMEDOUT;
> +}
> +
> +static int ec_read_reg(u8 reg, u8 *val)
> +{
> + int ret;
> +
> + ret = ec_wait_ibf_clear();
> + if (ret)
> + return ret;
> +
> + outb(EC_CMD_READ, EC_CMD_PORT);
> +
> + ret = ec_wait_ibf_clear();
> + if (ret)
> + return ret;
> +
> + outb(reg, EC_DATA_PORT);
> +
> + ret = ec_wait_obf_set();
> + if (ret)
> + return ret;
> +
> + *val = inb(EC_DATA_PORT);
> + return 0;
> +}
> +
> +/* Read a 16-bit big-endian value from two consecutive EC registers. */
> +static int ec_read_reg16(u8 reg_hi, u8 reg_lo, u16 *val)
> +{
> + int ret;
> + u8 hi, lo;
> +
> + ret = ec_read_reg(reg_hi, &hi);
> + if (ret)
> + return ret;
> +
> + ret = ec_read_reg(reg_lo, &lo);
> + if (ret)
> + return ret;
> +
> + *val = ((u16)hi << 8) | lo;
> + return 0;
> +}
> +
> +static int
> +lps_ec_read_string(struct device *dev,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel,
> + const char **str)
> +{
> + switch (type) {
> + case hwmon_fan:
> + *str = "CPU Fan";
> + return 0;
> + case hwmon_temp:
> + *str = channel == 0 ? "Board Temp" : "CPU Temp";
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static umode_t
> +lps_ec_is_visible(const void *drvdata,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel)
> +{
> + switch (type) {
> + case hwmon_fan:
> + if (attr == hwmon_fan_input || attr == hwmon_fan_label)
> + return 0444;
> + break;
> + case hwmon_temp:
> + if (attr == hwmon_temp_input || attr == hwmon_temp_label)
> + return 0444;
> + break;
> + default:
> + break;
> + }
> + return 0;
> +}
> +
> +static int
> +lps_ec_read(struct device *dev,
> + enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + u16 rpm;
> + u8 v;
> + int ret;
> +
> + switch (type) {
> + case hwmon_fan:
> + if (attr != hwmon_fan_input)
> + return -EOPNOTSUPP;
> + ret = ec_read_reg16(EC_REG_FAN_RPM_HI,
> + EC_REG_FAN_RPM_LO, &rpm);
> + if (ret)
> + return ret;
> + *val = rpm;
> + return 0;
> +
> + case hwmon_temp:
> + if (attr != hwmon_temp_input)
> + return -EOPNOTSUPP;
> + ret = ec_read_reg(channel == 0 ? EC_REG_TEMP_BOARD
> + : EC_REG_TEMP_CPU,
> + &v);
> + if (ret)
> + return ret;
> + /* EC reports unsigned 8-bit temperature in degrees Celsius */
> + *val = (unsigned long)v * 1000;
> + return 0;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static const struct hwmon_channel_info * const lps_ec_info[] = {
> + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL),
> + HWMON_CHANNEL_INFO(temp,
> + HWMON_T_INPUT | HWMON_T_LABEL,
> + HWMON_T_INPUT | HWMON_T_LABEL),
> + NULL
> +};
> +
> +static const struct hwmon_ops lps_ec_ops = {
> + .is_visible = lps_ec_is_visible,
> + .read = lps_ec_read,
> + .read_string = lps_ec_read_string,
> +};
> +
> +static const struct hwmon_chip_info lps_ec_chip_info = {
> + .ops = &lps_ec_ops,
> + .info = lps_ec_info,
> +};
> +
> +static int lps_ec_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device *hwmon;
> + u8 test;
> + int ret;
> +
> + /* Sanity check: verify EC is responsive */
> + ret = ec_read_reg(EC_REG_FAN_DUTY, &test);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "EC not responding on ports 0x%x/0x%x\n",
> + EC_DATA_PORT, EC_CMD_PORT);
> +
> + hwmon = devm_hwmon_device_register_with_info(dev, DRIVER_NAME, NULL,
> + &lps_ec_chip_info, NULL);
> + if (IS_ERR(hwmon))
> + return dev_err_probe(dev, PTR_ERR(hwmon),
> + "Failed to register hwmon device\n");
> +
> + dev_info(dev, "EC hwmon registered (fan duty: %u%%)\n", test);
> + return 0;
> +}
> +
> +/* DMI table with strict BIOS version match (override with force=1) */
> +static const struct dmi_system_id lps_ec_dmi_table[] = {
> + {
> + .ident = "LattePanda Sigma",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"),
> + DMI_MATCH(DMI_BIOS_VERSION, "5.27"),
> + },
> + },
> + { } /* terminator */
> +};
> +MODULE_DEVICE_TABLE(dmi, lps_ec_dmi_table);
> +
> +/* Loose table (vendor + product only) for use with force=1 */
> +static const struct dmi_system_id lps_ec_dmi_table_force[] = {
> + {
> + .ident = "LattePanda Sigma",
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LattePanda"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "LattePanda Sigma"),
> + },
> + },
> + { } /* terminator */
> +};
> +
> +static struct platform_driver lps_ec_driver = {
> + .probe = lps_ec_probe,
> + .driver = {
> + .name = DRIVER_NAME,
> + },
> +};
> +
> +static int __init lps_ec_init(void)
> +{
> + int ret;
> +
> + if (!dmi_check_system(lps_ec_dmi_table)) {
> + if (!force || !dmi_check_system(lps_ec_dmi_table_force))
> + return -ENODEV;
> + pr_warn("%s: BIOS version not verified, loading due to force=1\n",
> + DRIVER_NAME);
> + }
> +
> + ret = platform_driver_register(&lps_ec_driver);
> + if (ret)
> + return ret;
> +
> + lps_ec_pdev = platform_device_register_simple(DRIVER_NAME, -1,
> + NULL, 0);
CHECK: Alignment should match open parenthesis
#605: FILE: drivers/hwmon/lattepanda-sigma-ec.c:309:
+ lps_ec_pdev = platform_device_register_simple(DRIVER_NAME, -1,
+ NULL, 0);
> + if (IS_ERR(lps_ec_pdev)) {
> + platform_driver_unregister(&lps_ec_driver);
> + return PTR_ERR(lps_ec_pdev);
> + }
> +
> + return 0;
> +}
> +
> +static void __exit lps_ec_exit(void)
> +{
> + platform_device_unregister(lps_ec_pdev);
> + platform_driver_unregister(&lps_ec_driver);
> +}
> +
> +module_init(lps_ec_init);
> +module_exit(lps_ec_exit);
> +
> +MODULE_AUTHOR("Mariano Abad <weimaraner@gmail.com>");
> +MODULE_DESCRIPTION("Hardware monitoring driver for LattePanda Sigma EC");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2] hwmon: Add LattePanda Sigma EC driver
2026-03-02 18:35 ` [PATCH v2] " Mariano Abad
2026-03-02 20:00 ` Guenter Roeck
@ 2026-03-02 21:45 ` Guenter Roeck
1 sibling, 0 replies; 5+ messages in thread
From: Guenter Roeck @ 2026-03-02 21:45 UTC (permalink / raw)
To: Mariano Abad; +Cc: linux-hwmon, corbet, linux-doc, linux-kernel
On Mon, Mar 02, 2026 at 03:35:14PM -0300, Mariano Abad wrote:
> The LattePanda Sigma is an x86 single-board computer made by DFRobot,
> featuring an Intel Core i5-1340P and an ITE IT8613E Embedded Controller
> that manages fan speed and thermal monitoring.
>
> The BIOS declares the ACPI Embedded Controller (PNP0C09) with _STA
> returning 0 and provides only stub ECRD/ECWT methods. Since the kernel's
> ACPI EC subsystem never initializes, ec_read() is not available and
> direct port I/O to the standard ACPI EC ports (0x62/0x66) is used. As
> ACPI never accesses the EC, no ACPI Global Lock or namespace mutex is
> required.
>
> The driver exposes:
> - CPU fan speed (RPM, read-only)
> - Board temperature (unsigned 8-bit, degrees Celsius)
> - CPU proximity temperature (unsigned 8-bit, degrees Celsius)
>
> DMI matching restricts the driver to verified LattePanda Sigma hardware
> with BIOS version 5.27. A 'force' module parameter allows loading on
> untested BIOS versions while still requiring vendor/product match.
>
> The EC register map was discovered through firmware reverse engineering
> and confirmed by physical testing.
>
I had trouble to find this patch again and found that you sent it as reply
to the first version.
Documentation/process/submittingpatches.rst explains why you should
never do that.
Anyway, additional feedback:
> +/* Read a 16-bit big-endian value from two consecutive EC registers. */
> +static int ec_read_reg16(u8 reg_hi, u8 reg_lo, u16 *val)
> +{
> + int ret;
> + u8 hi, lo;
> +
> + ret = ec_read_reg(reg_hi, &hi);
> + if (ret)
> + return ret;
> +
> + ret = ec_read_reg(reg_lo, &lo);
> + if (ret)
> + return ret;
> +
> + *val = ((u16)hi << 8) | lo;
> + return 0;
> +}
Is it possible for the 16-bit value (e.g., fan RPM) to roll over between
reading the high and low bytes? Without latching or verifying that the high
byte did not change, reading a rapidly changing 16-bit value as two
independent 8-bit reads can yield a corrupted result (e.g., if it rolls over
from 0x0100 to 0x00FF between reads, producing 0x01FF).
This is AI generated, but it does have a point. lm90 had a similar problem,
which it kind of solved in lm90_read16(). You might want to consider using
a similar approach unless it is guaranteed that this is not a problem.
If so, please add a comment explaining why it is not a problem.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-03-02 21:45 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-01 2:37 [PATCH] hwmon: Add LattePanda Sigma EC driver Mariano Abad
2026-03-02 0:42 ` Guenter Roeck
2026-03-02 18:35 ` [PATCH v2] " Mariano Abad
2026-03-02 20:00 ` Guenter Roeck
2026-03-02 21:45 ` Guenter Roeck
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox