Re: [PATCH v2] hwmon: Add LattePanda Sigma EC driver

From: Guenter Roeck

Date: Mon Mar 02 2026 - 15:00:52 EST


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@xxxxxxxxx>
---
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@xxxxxxxxx/
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@xxxxxxxxx>
+
+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@xxxxxxxxx>
+L: linux-hwmon@xxxxxxxxxxxxxxx
+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@xxxxxxxxxxxxxxxxxxxxx>
L: linux-scsi@xxxxxxxxxxxxxxx
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@xxxxxxxxx>
+ */
+
+#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@xxxxxxxxx>");
+MODULE_DESCRIPTION("Hardware monitoring driver for LattePanda Sigma EC");
+MODULE_LICENSE("GPL");