Hi, On Wed, Jun 24, 2026 at 11:08:23PM +0200, Daniel Lezcano wrote: > Expose the Lenovo ThinkPad T14s EC environmental sensors through > the hwmon subsystem. > > The driver now registers a hwmon device providing access to six EC > temperature sensors corresponding to the SoC, keyboard area, base > cover, PMIC/charging circuitry, QTM module and SSD. Sensor labels > are exported to allow user space to identify each measurement. > > Additionally, expose the system fan speed by reading the fan RPM > registers from the embedded controller. > > This allows standard monitoring tools such as lm-sensors to report > platform temperatures and fan speed. > > Signed-off-by: Daniel Lezcano daniel.lezcano@oss.qualcomm.com > --- I gave this a try and for me the fan data is always 65535 (i.e. -1): $ cat /sys/class/hwmon/hwmon66/{name,fan1_input} t14s_ec 65535 This is with the fan running: $ cat /sys/class/hwmon/hwmon57/{name,fan1_input} fan-controller 2564 The thermal data looks good. Greetings, -- Sebastian > drivers/platform/arm64/lenovo-thinkpad-t14s.c | 147 ++++++++++++++++++ > 1 file changed, 147 insertions(+) > > diff --git a/drivers/platform/arm64/lenovo-thinkpad-t14s.c b/drivers/platform/arm64/lenovo-thinkpad-t14s.c > index 5590302a5694..142464623f0e 100644 > --- a/drivers/platform/arm64/lenovo-thinkpad-t14s.c > +++ b/drivers/platform/arm64/lenovo-thinkpad-t14s.c > @@ -11,6 +11,7 @@ > #include > #include > #include > +#include > #include > #include > #include > @@ -67,6 +68,16 @@ > #define T14S_EC_EVT_KEY_FN_F11 0x7a > #define T14S_EC_EVT_KEY_FN_G 0x7e > > +#define T14S_EC_SYS_THERM0 0x78 /* SoC (CPU+GPU) */ > +#define T14S_EC_SYS_THERM1 0x79 /* Keyboard */ > +#define T14S_EC_SYS_THERM2 0x7a /* Back cover */ > +#define T14S_EC_SYS_THERM3 0x7b /* Charger / PMIC */ > +#define T14S_EC_SYS_THERM6 0x7c /* QTM West */ > +#define T14S_EC_SYS_THERM7 0x7d /* SSD */ > + > +#define T14S_EC_FAN_RPM_LSB 0x84 > +#define T14S_EC_FAN_RPM_MSB 0x85 > + > /* Hardware LED blink rate is 1 Hz (500ms off, 500ms on) */ > #define T14S_EC_BLINK_RATE_ON_OFF_MS 500 > > @@ -93,9 +104,19 @@ struct t14s_ec_led_classdev { > struct t14s_ec *ec; > }; > > +struct t14s_ec_hwmon_sys_thermx { > + const char *label; > + int reg; > +}; > + > +struct t14s_ec_hwmon { > + struct t14s_ec_hwmon_sys_thermx *sys_thermx; > +}; > + > struct t14s_ec { > struct regmap *regmap; > struct device *dev; > + struct t14s_ec_hwmon ec_hwmon; > struct t14s_ec_led_classdev led_pwr_btn; > struct t14s_ec_led_classdev led_chrg_orange; > struct t14s_ec_led_classdev led_chrg_white; > @@ -555,6 +576,128 @@ static irqreturn_t t14s_ec_irq_handler(int irq, void *data) > return IRQ_HANDLED; > } > > +static umode_t t14s_ec_hwmon_is_visible(const void *drvdata, > + enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + switch (type) { > + case hwmon_temp: > + return 0444; > + case hwmon_fan: > + return 0444; > + default: > + return 0; > + } > +} > + > +static int t14s_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, const char **str) > +{ > + struct t14s_ec *ec = dev_get_drvdata(dev); > + switch (type) { > + case hwmon_temp: > + if (attr == hwmon_temp_label) { > + *str = ec->ec_hwmon.sys_thermx[channel].label; > + return 0; > + } > + break; > + default: > + return -EOPNOTSUPP; > + } > + > + return -EOPNOTSUPP; > +} > + > +static int t14s_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *val) > +{ > + struct t14s_ec *ec = dev_get_drvdata(dev); > + unsigned int value; > + int ret; > + > + switch (type) { > + case hwmon_temp: > + if (attr == hwmon_temp_input) { > + ret = t14s_ec_read(ec, ec->ec_hwmon.sys_thermx[channel].reg, &value); > + if (ret) > + return ret; > + *val = value * 1000; > + > + return 0; > + } > + break; > + > + case hwmon_fan: > + if (attr == hwmon_fan_input) { > + int lsb, msb; > + ret = t14s_ec_read(ec, T14S_EC_FAN_RPM_LSB, &lsb); > + if (ret) > + return ret; > + > + ret = t14s_ec_read(ec, T14S_EC_FAN_RPM_MSB, &msb); > + if (ret) > + return ret; > + > + *val = 0; > + *val = lsb + (msb << 8); > + > + return 0; > + } > + break; > + default: > + break; > + } > + > + return -EOPNOTSUPP; > +} > + > +static const struct hwmon_ops t14s_ec_hwmon_ops = { > + .is_visible = t14s_ec_hwmon_is_visible, > + .read = t14s_ec_hwmon_read, > + .read_string = t14s_ec_hwmon_read_string, > +}; > + > +static const struct hwmon_channel_info *t14s_ec_hwmon_info[] = { > + HWMON_CHANNEL_INFO(temp, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL, > + HWMON_T_INPUT | HWMON_T_LABEL), > + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), > + NULL > +}; > + > +static const struct hwmon_chip_info t14s_ec_chip_info = { > + .ops = &t14s_ec_hwmon_ops, > + .info = t14s_ec_hwmon_info, > +}; > + > +static int t14s_ec_hwmon_probe(struct t14s_ec *ec) > +{ > + struct device *dev; > + struct t14s_ec_hwmon_sys_thermx sys_thermx[] = { > + { T14S_EC_SYS_THERM0, "soc" }, > + { T14S_EC_SYS_THERM1, "keyboard" }, > + { T14S_EC_SYS_THERM2, "base" }, > + { T14S_EC_SYS_THERM3, "pmbm" }, > + { T14S_EC_SYS_THERM6, "qtm" }, > + { T14S_EC_SYS_THERM7, "ssd" }, > + }; > + > + ec->ec_hwmon.sys_thermx = devm_kmemdup_array(ec->dev, sys_thermx, > + ARRAY_SIZE(sys_thermx), > + sizeof(sys_thermx[0]), GFP_KERNEL); > + if (!ec->ec_hwmon.sys_thermx) > + return -ENOMEM; > + > + dev = devm_hwmon_device_register_with_info(ec->dev, "t14s_ec", ec, > + &t14s_ec_chip_info, NULL); > + > + return PTR_ERR_OR_ZERO(dev); > +} > + > static int t14s_ec_probe(struct i2c_client *client) > { > struct device *dev = &client->dev; > @@ -590,6 +733,10 @@ static int t14s_ec_probe(struct i2c_client *client) > if (ret < 0) > return ret; > > + ret = t14s_ec_hwmon_probe(ec); > + if (ret < 0) > + return ret; > + > ret = devm_request_threaded_irq(dev, client->irq, NULL, > t14s_ec_irq_handler, > IRQF_ONESHOT, dev_name(dev), ec); > -- > 2.53.0 >