* [PATCH v5 1/4] hwmon: it87: describe per-chip temperature resources
2026-08-29 17:05 [PATCH v5 0/4] hwmon: it87: add IT8613E support Jerome Tollet
@ 2026-08-29 17:05 ` Jerome Tollet
2026-08-29 17:20 ` sashiko-bot
2026-08-29 17:05 ` [PATCH v5 2/4] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
` (3 subsequent siblings)
4 siblings, 1 reply; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 17:05 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck
Cc: linux-hwmon, Benoit Masson, Jonathan Corbet, Shuah Khan,
Randy Dunlap, linux-doc, linux-kernel, Jerome Tollet
From: "benoit.masson" <yahoo@perenite.com>
Add per-chip temp limit/offset/map counts and wire the driver
to use them.
This keeps existing chips on the previous defaults while allowing newer
chips to advertise larger resources.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
drivers/hwmon/it87.c | 151 ++++++++++++++++++++++++++++++++-----------
1 file changed, 114 insertions(+), 37 deletions(-)
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index 87edb1b60..1c77f6c97 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -247,7 +247,7 @@ static const u8 IT87_REG_FAN[] = { 0x0d, 0x0e, 0x0f, 0x80, 0x82, 0x4c };
static const u8 IT87_REG_FAN_MIN[] = { 0x10, 0x11, 0x12, 0x84, 0x86, 0x4e };
static const u8 IT87_REG_FANX[] = { 0x18, 0x19, 0x1a, 0x81, 0x83, 0x4d };
static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87, 0x4f };
-static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 };
+static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59, 0x5a, 0x90, 0x91 };
#define IT87_REG_FAN_MAIN_CTRL 0x13
#define IT87_REG_FAN_CTL 0x14
@@ -282,7 +282,6 @@ static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 };
#define NUM_VIN_LIMIT 8
#define NUM_TEMP 6
#define NUM_TEMP_OFFSET ARRAY_SIZE(IT87_REG_TEMP_OFFSET)
-#define NUM_TEMP_LIMIT 3
#define NUM_FAN ARRAY_SIZE(IT87_REG_FAN)
#define NUM_FAN_DIV 3
#define NUM_PWM ARRAY_SIZE(IT87_REG_PWM)
@@ -292,6 +291,9 @@ struct it87_devices {
const char *name;
const char * const model;
u32 features;
+ u8 num_temp_limit;
+ u8 num_temp_offset;
+ u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
u8 smbus_bitmap; /* SMBus enable bits in extra config register */
@@ -302,7 +304,6 @@ struct it87_devices {
#define FEAT_NEWER_AUTOPWM BIT(1)
#define FEAT_OLD_AUTOPWM BIT(2)
#define FEAT_16BIT_FANS BIT(3)
-#define FEAT_TEMP_OFFSET BIT(4)
#define FEAT_TEMP_PECI BIT(5)
#define FEAT_TEMP_OLD_PECI BIT(6)
#define FEAT_FAN16_CONFIG BIT(7) /* Need to enable 16-bit fans */
@@ -335,43 +336,61 @@ static const struct it87_devices it87_devices[] = {
.model = "IT87F",
.features = FEAT_OLD_AUTOPWM | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .num_temp_limit = 3,
+ .num_temp_offset = 0,
+ .num_temp_map = 3,
},
[it8712] = {
.name = "it8712",
.model = "IT8712F",
.features = FEAT_OLD_AUTOPWM | FEAT_VID | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .num_temp_limit = 3,
+ .num_temp_offset = 0,
+ .num_temp_map = 3,
},
[it8716] = {
.name = "it8716",
.model = "IT8716F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
+ .features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
},
[it8718] = {
.name = "it8718",
.model = "IT8718F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
+ .features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8720] = {
.name = "it8720",
.model = "IT8720F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
+ .features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8721] = {
.name = "it8721",
.model = "IT8721F",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
+ | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x05,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -379,18 +398,24 @@ static const struct it87_devices it87_devices[] = {
.name = "it8728",
.model = "IT8728F",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
+ | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 6,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8732] = {
.name = "it8732",
.model = "IT8732F",
.features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
+ | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FOUR_FANS
| FEAT_FOUR_PWM | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -398,73 +423,97 @@ static const struct it87_devices it87_devices[] = {
.name = "it8771",
.model = "IT8771E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
/* PECI: guesswork */
/* 12mV ADC (OHM) */
/* 16 bit fans (OHM) */
/* three fans, always 16 bit (guesswork) */
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8772] = {
.name = "it8772",
.model = "IT8772E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
/* PECI (coreboot) */
/* 12mV ADC (HWSensors4, OHM) */
/* 16 bit fans (HWSensors4, OHM) */
/* three fans, always 16 bit (datasheet) */
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8781] = {
.name = "it8781",
.model = "IT8781F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
+ .features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8782] = {
.name = "it8782",
.model = "IT8782F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
+ .features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8783] = {
.name = "it8783",
.model = "IT8783E/F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
+ .features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8786] = {
.name = "it8786",
.model = "IT8786E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8790] = {
.name = "it8790",
.model = "IT8790E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_NOCONF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8792] = {
.name = "it8792",
.model = "IT8792E/IT8795E",
.features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
+ | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -472,54 +521,72 @@ static const struct it87_devices it87_devices[] = {
.name = "it8603",
.model = "IT8603E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_AVCC3 | FEAT_PWM_FREQ2,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 4,
.peci_mask = 0x07,
},
[it8620] = {
.name = "it8620",
.model = "IT8620E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
+ | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8622] = {
.name = "it8622",
.model = "IT8622E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
+ | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_AVCC3 | FEAT_VIN3_5V | FEAT_FOUR_TEMP,
- .peci_mask = 0x07,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 4,
+ .peci_mask = 0x0f,
.smbus_bitmap = BIT(1) | BIT(2),
},
[it8628] = {
.name = "it8628",
.model = "IT8628E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
+ | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 6,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8689] = {
.name = "it8689",
.model = "IT8689E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_SIX_FANS | FEAT_IN7_INTERNAL
+ | FEAT_SIX_FANS | FEAT_IN7_INTERNAL
| FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.smbus_bitmap = BIT(1) | BIT(2),
},
[it87952] = {
.name = "it87952",
.model = "IT87952E",
.features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
+ | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -530,7 +597,6 @@ static const struct it87_devices it87_devices[] = {
#define has_10_9mv_adc(data) ((data)->features & FEAT_10_9MV_ADC)
#define has_newer_autopwm(data) ((data)->features & FEAT_NEWER_AUTOPWM)
#define has_old_autopwm(data) ((data)->features & FEAT_OLD_AUTOPWM)
-#define has_temp_offset(data) ((data)->features & FEAT_TEMP_OFFSET)
#define has_temp_peci(data, nr) (((data)->features & FEAT_TEMP_PECI) && \
((data)->peci_mask & BIT(nr)))
#define has_temp_old_peci(data, nr) \
@@ -589,6 +655,9 @@ struct it87_data {
int sioaddr;
enum chips type;
u32 features;
+ u8 num_temp_limit;
+ u8 num_temp_offset;
+ u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
@@ -937,12 +1006,12 @@ static struct it87_data *it87_update_device(struct device *dev)
data->temp[i][0] =
it87_read_value(data, IT87_REG_TEMP(i));
- if (has_temp_offset(data) && i < NUM_TEMP_OFFSET)
+ if (i < data->num_temp_offset)
data->temp[i][3] =
it87_read_value(data,
IT87_REG_TEMP_OFFSET[i]);
- if (i >= NUM_TEMP_LIMIT)
+ if (i >= data->num_temp_limit)
continue;
data->temp[i][1] =
@@ -1693,16 +1762,18 @@ static ssize_t show_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = it87_update_device(dev);
int nr = sensor_attr->index;
+ u8 num_map;
int map;
if (IS_ERR(data))
return PTR_ERR(data);
+ num_map = data->num_temp_map;
map = data->pwm_temp_map[nr];
- if (map >= 3)
+ if (map >= num_map)
map = 0; /* Should never happen */
- if (nr >= 3) /* pwm channels 3..6 map to temp4..6 */
- map += 3;
+ if (nr >= num_map) /* pwm channels 3..6 map to temp4..6 */
+ map += num_map;
return sprintf(buf, "%d\n", (int)BIT(map));
}
@@ -1714,6 +1785,7 @@ static ssize_t set_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = dev_get_drvdata(dev);
int nr = sensor_attr->index;
+ u8 num_map = data->num_temp_map;
long val;
int err;
u8 reg;
@@ -1721,8 +1793,8 @@ static ssize_t set_pwm_temp_map(struct device *dev,
if (kstrtol(buf, 10, &val) < 0)
return -EINVAL;
- if (nr >= 3)
- val -= 3;
+ if (nr >= num_map)
+ val -= num_map;
switch (val) {
case BIT(0):
@@ -2376,7 +2448,7 @@ static umode_t it87_temp_is_visible(struct kobject *kobj,
return attr->mode;
}
- if (a == 5 && !has_temp_offset(data))
+ if (a == 5 && i >= data->num_temp_offset)
return 0;
if (a == 6 && !data->has_beep)
@@ -3268,7 +3340,7 @@ static void it87_check_limit_regs(struct it87_data *data)
if (reg == 0xff)
it87_write_value(data, IT87_REG_VIN_MIN(i), 0);
}
- for (i = 0; i < NUM_TEMP_LIMIT; i++) {
+ for (i = 0; i < data->num_temp_limit; i++) {
reg = it87_read_value(data, IT87_REG_TEMP_HIGH(i));
if (reg == 0xff)
it87_write_value(data, IT87_REG_TEMP_HIGH(i), 127);
@@ -3461,6 +3533,7 @@ static int it87_probe(struct platform_device *pdev)
struct resource *res;
struct device *dev = &pdev->dev;
struct it87_sio_data *sio_data = dev_get_platdata(dev);
+ const struct it87_devices *chip;
int enable_pwm_interface;
struct device *hwmon_dev;
int err;
@@ -3483,9 +3556,13 @@ static int it87_probe(struct platform_device *pdev)
data->type = sio_data->type;
data->smbus_bitmap = sio_data->smbus_bitmap;
data->ec_special_config = sio_data->ec_special_config;
- data->features = it87_devices[sio_data->type].features;
- data->peci_mask = it87_devices[sio_data->type].peci_mask;
- data->old_peci_mask = it87_devices[sio_data->type].old_peci_mask;
+ chip = &it87_devices[sio_data->type];
+ data->features = chip->features;
+ data->peci_mask = chip->peci_mask;
+ data->old_peci_mask = chip->old_peci_mask;
+ data->num_temp_limit = chip->num_temp_limit;
+ data->num_temp_offset = chip->num_temp_offset;
+ data->num_temp_map = chip->num_temp_map;
/*
* IT8705F Datasheet 0.4.1, 3h == Version G.
* IT8712F Datasheet 0.9.1, section 8.3.5 indicates 8h == Version J.
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH v5 1/4] hwmon: it87: describe per-chip temperature resources
2026-08-29 17:05 ` [PATCH v5 1/4] hwmon: it87: describe per-chip temperature resources Jerome Tollet
@ 2026-08-29 17:20 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-29 17:20 UTC (permalink / raw)
To: Jerome Tollet; +Cc: linux-hwmon
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Unused macro NUM_TEMP_OFFSET.
--
commit 8f31c77fc165d2fa01f14da71c4de721a4613e71
Author: benoit.masson <yahoo@perenite.com>
hwmon: it87: describe per-chip temperature resources
Add per-chip temp limit/offset/map counts and wire the driver to use them.
This keeps existing chips on the previous defaults while allowing newer chips
to advertise larger resources.
> diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
> index 87edb1b6048bb..1c77f6c97fc36 100644
> --- a/drivers/hwmon/it87.c
> +++ b/drivers/hwmon/it87.c
> @@ -282,7 +282,6 @@ static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 };
> #define NUM_VIN_LIMIT 8
> #define NUM_TEMP 6
> #define NUM_TEMP_OFFSET ARRAY_SIZE(IT87_REG_TEMP_OFFSET)
> -#define NUM_TEMP_LIMIT 3
> #define NUM_FAN ARRAY_SIZE(IT87_REG_FAN)
> #define NUM_FAN_DIV 3
> #define NUM_PWM ARRAY_SIZE(IT87_REG_PWM)
[Severity: Low]
Is NUM_TEMP_OFFSET still needed? It appears its only usage was replaced by the
dynamic data->num_temp_offset parameter in it87_update_device(). Should this
be removed along with NUM_TEMP_LIMIT?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260829170545.33401-1-jerome.tollet@gmail.com?part=1
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v5 2/4] hwmon: it87: prepare for extended PWM temp maps
2026-08-29 17:05 [PATCH v5 0/4] hwmon: it87: add IT8613E support Jerome Tollet
2026-08-29 17:05 ` [PATCH v5 1/4] hwmon: it87: describe per-chip temperature resources Jerome Tollet
@ 2026-08-29 17:05 ` Jerome Tollet
2026-08-29 17:20 ` sashiko-bot
2026-08-29 17:05 ` [PATCH v5 3/4] hwmon: it87: expose additional temperature limits Jerome Tollet
` (2 subsequent siblings)
4 siblings, 1 reply; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 17:05 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck
Cc: linux-hwmon, Benoit Masson, Jonathan Corbet, Shuah Khan,
Randy Dunlap, linux-doc, linux-kernel, Jerome Tollet
From: "benoit.masson" <yahoo@perenite.com>
Introduce helper logic for PWM-to-temperature mappings so newer
register layouts can be supported without affecting legacy chips.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
drivers/hwmon/it87.c | 199 +++++++++++++++++++++++++++++++++----------
1 file changed, 154 insertions(+), 45 deletions(-)
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index 1c77f6c97..e077adbb3 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -252,6 +252,7 @@ static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59, 0x5a, 0x90, 0x91 };
#define IT87_REG_FAN_MAIN_CTRL 0x13
#define IT87_REG_FAN_CTL 0x14
static const u8 IT87_REG_PWM[] = { 0x15, 0x16, 0x17, 0x7f, 0xa7, 0xaf };
+static const u8 IT87_REG_PWM_8665[] = { 0x15, 0x16, 0x17, 0x1e, 0x1f, 0x92 };
static const u8 IT87_REG_PWM_DUTY[] = { 0x63, 0x6b, 0x73, 0x7b, 0xa3, 0xab };
static const u8 IT87_REG_VIN[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
@@ -282,6 +283,7 @@ static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 };
#define NUM_VIN_LIMIT 8
#define NUM_TEMP 6
#define NUM_TEMP_OFFSET ARRAY_SIZE(IT87_REG_TEMP_OFFSET)
+#define IT87_PWM_OLD_NUM_TEMP 3
#define NUM_FAN ARRAY_SIZE(IT87_REG_FAN)
#define NUM_FAN_DIV 3
#define NUM_PWM ARRAY_SIZE(IT87_REG_PWM)
@@ -291,6 +293,7 @@ struct it87_devices {
const char *name;
const char * const model;
u32 features;
+ const u8 *reg_pwm;
u8 num_temp_limit;
u8 num_temp_offset;
u8 num_temp_map;
@@ -329,6 +332,7 @@ struct it87_devices {
#define FEAT_FOUR_PWM BIT(21) /* Supports four fan controls */
#define FEAT_FOUR_TEMP BIT(22)
#define FEAT_FANCTL_ONOFF BIT(23) /* chip has FAN_CTL ON/OFF */
+#define FEAT_NEW_TEMPMAP BIT(24) /* PWM uses extended temp map */
static const struct it87_devices it87_devices[] = {
[it87] = {
@@ -336,6 +340,7 @@ static const struct it87_devices it87_devices[] = {
.model = "IT87F",
.features = FEAT_OLD_AUTOPWM | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 0,
.num_temp_map = 3,
@@ -345,6 +350,7 @@ static const struct it87_devices it87_devices[] = {
.model = "IT8712F",
.features = FEAT_OLD_AUTOPWM | FEAT_VID | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 0,
.num_temp_map = 3,
@@ -355,6 +361,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -365,6 +372,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -376,6 +384,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -388,6 +397,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -401,6 +411,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 6,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -413,6 +424,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FOUR_FANS
| FEAT_FOUR_PWM | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -429,6 +441,7 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (OHM) */
/* 16 bit fans (OHM) */
/* three fans, always 16 bit (guesswork) */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -444,6 +457,7 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (HWSensors4, OHM) */
/* 16 bit fans (HWSensors4, OHM) */
/* three fans, always 16 bit (datasheet) */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -455,6 +469,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -466,6 +481,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -477,6 +493,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -488,6 +505,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -499,6 +517,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_NOCONF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -511,6 +530,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -523,6 +543,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_AVCC3 | FEAT_PWM_FREQ2,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 4,
@@ -535,6 +556,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -547,6 +569,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_AVCC3 | FEAT_VIN3_5V | FEAT_FOUR_TEMP,
+ .reg_pwm = IT87_REG_PWM_8665,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 4,
@@ -560,6 +583,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 6,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -572,6 +596,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_SIX_FANS | FEAT_IN7_INTERNAL
| FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -584,6 +609,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -626,6 +652,7 @@ static const struct it87_devices it87_devices[] = {
#define has_scaling(data) ((data)->features & (FEAT_12MV_ADC | \
FEAT_10_9MV_ADC))
#define has_fanctl_onoff(data) ((data)->features & FEAT_FANCTL_ONOFF)
+#define has_new_tempmap(data) ((data)->features & FEAT_NEW_TEMPMAP)
struct it87_sio_data {
int sioaddr;
@@ -655,6 +682,7 @@ struct it87_data {
int sioaddr;
enum chips type;
u32 features;
+ const u8 *reg_pwm;
u8 num_temp_limit;
u8 num_temp_offset;
u8 num_temp_map;
@@ -704,7 +732,9 @@ struct it87_data {
u8 has_pwm; /* Bitfield, pwm control enabled */
u8 pwm_ctrl[NUM_PWM]; /* Register value */
u8 pwm_duty[NUM_PWM]; /* Manual PWM value set by user */
- u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping (bits 1-0) */
+ u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping */
+ u8 pwm_temp_map_mask;
+ u8 pwm_temp_map_shift;
/* Automatic fan speed control registers */
u8 auto_pwm[NUM_AUTO_PWM][4]; /* [nr][3] is hard-coded */
@@ -786,6 +816,71 @@ static int pwm_from_reg(const struct it87_data *data, u8 reg)
return (reg & 0x7f) << 1;
}
+static inline u8 pwm_temp_map_get(const struct it87_data *data, u8 ctrl)
+{
+ return (ctrl >> data->pwm_temp_map_shift) &
+ data->pwm_temp_map_mask;
+}
+
+static inline u8 pwm_temp_map_set(const struct it87_data *data, u8 ctrl,
+ u8 map)
+{
+ ctrl &= ~(data->pwm_temp_map_mask << data->pwm_temp_map_shift);
+ return ctrl | ((map & data->pwm_temp_map_mask)
+ << data->pwm_temp_map_shift);
+}
+
+static inline u8 pwm_num_temp_map(const struct it87_data *data)
+{
+ return data->num_temp_map;
+}
+
+static unsigned int pwm_temp_channel(const struct it87_data *data,
+ int nr, u8 map)
+{
+ if (has_new_tempmap(data)) {
+ u8 num = pwm_num_temp_map(data);
+
+ if (map >= num)
+ map = 0;
+ return map;
+ }
+
+ if (map >= IT87_PWM_OLD_NUM_TEMP)
+ map = 0;
+
+ if (nr >= IT87_PWM_OLD_NUM_TEMP)
+ map += IT87_PWM_OLD_NUM_TEMP;
+
+ return map;
+}
+
+static int pwm_temp_map_from_channel(const struct it87_data *data, int nr,
+ unsigned int channel, u8 *map)
+{
+ if (has_new_tempmap(data)) {
+ u8 num = pwm_num_temp_map(data);
+
+ if (channel >= num)
+ return -EINVAL;
+ *map = channel;
+ return 0;
+ }
+
+ if (nr >= IT87_PWM_OLD_NUM_TEMP) {
+ if (channel < IT87_PWM_OLD_NUM_TEMP ||
+ channel >= 2 * IT87_PWM_OLD_NUM_TEMP)
+ return -EINVAL;
+ channel -= IT87_PWM_OLD_NUM_TEMP;
+ } else {
+ if (channel >= IT87_PWM_OLD_NUM_TEMP)
+ return -EINVAL;
+ }
+
+ *map = channel;
+ return 0;
+}
+
static int DIV_TO_REG(int val)
{
int answer = 0;
@@ -797,6 +892,11 @@ static int DIV_TO_REG(int val)
#define DIV_FROM_REG(val) BIT(val)
+static inline u16 it87_reg_pwm(const struct it87_data *data, int nr)
+{
+ return data->reg_pwm[nr];
+}
+
/*
* PWM base frequencies. The frequency has to be divided by either 128 or 256,
* depending on the chip type, to calculate the actual PWM frequency.
@@ -877,14 +977,22 @@ static void it87_write_value(struct it87_data *data, u8 reg, u8 value)
static void it87_update_pwm_ctrl(struct it87_data *data, int nr)
{
- data->pwm_ctrl[nr] = it87_read_value(data, IT87_REG_PWM[nr]);
+ data->pwm_ctrl[nr] = it87_read_value(data, it87_reg_pwm(data, nr));
if (has_newer_autopwm(data)) {
- data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
+ data->pwm_temp_map[nr] =
+ pwm_temp_map_get(data, data->pwm_ctrl[nr]);
+ if (has_new_tempmap(data) &&
+ data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
+ data->pwm_temp_map[nr] = 0;
data->pwm_duty[nr] = it87_read_value(data,
IT87_REG_PWM_DUTY[nr]);
} else {
if (data->pwm_ctrl[nr] & 0x80) /* Automatic mode */
- data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
+ data->pwm_temp_map[nr] =
+ pwm_temp_map_get(data, data->pwm_ctrl[nr]);
+ if (has_new_tempmap(data) &&
+ data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
+ data->pwm_temp_map[nr] = 0;
else /* Manual mode */
data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;
}
@@ -1617,6 +1725,8 @@ static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr,
if (err)
return err;
+ it87_update_pwm_ctrl(data, nr);
+
if (val == 0) {
if (nr < 3 && has_fanctl_onoff(data)) {
int tmp;
@@ -1636,27 +1746,30 @@ static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr,
data->pwm_duty[nr]);
/* and set manual mode */
if (has_newer_autopwm(data)) {
- ctrl = (data->pwm_ctrl[nr] & 0x7c) |
- data->pwm_temp_map[nr];
+ ctrl = pwm_temp_map_set(data,
+ data->pwm_ctrl[nr] &
+ ~0x80,
+ data->pwm_temp_map[nr]);
} else {
ctrl = data->pwm_duty[nr];
}
data->pwm_ctrl[nr] = ctrl;
- it87_write_value(data, IT87_REG_PWM[nr], ctrl);
+ it87_write_value(data, it87_reg_pwm(data, nr), ctrl);
}
} else {
u8 ctrl;
if (has_newer_autopwm(data)) {
- ctrl = (data->pwm_ctrl[nr] & 0x7c) |
- data->pwm_temp_map[nr];
+ ctrl = pwm_temp_map_set(data,
+ data->pwm_ctrl[nr] & ~0x80,
+ data->pwm_temp_map[nr]);
if (val != 1)
ctrl |= 0x80;
} else {
ctrl = (val == 1 ? data->pwm_duty[nr] : 0x80);
}
data->pwm_ctrl[nr] = ctrl;
- it87_write_value(data, IT87_REG_PWM[nr], ctrl);
+ it87_write_value(data, it87_reg_pwm(data, nr), ctrl);
if (has_fanctl_onoff(data) && nr < 3) {
/* set SmartGuardian mode */
@@ -1707,7 +1820,7 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
*/
if (!(data->pwm_ctrl[nr] & 0x80)) {
data->pwm_ctrl[nr] = data->pwm_duty[nr];
- it87_write_value(data, IT87_REG_PWM[nr],
+ it87_write_value(data, it87_reg_pwm(data, nr),
data->pwm_ctrl[nr]);
}
}
@@ -1762,20 +1875,14 @@ static ssize_t show_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = it87_update_device(dev);
int nr = sensor_attr->index;
- u8 num_map;
- int map;
+ unsigned int channel;
if (IS_ERR(data))
return PTR_ERR(data);
- num_map = data->num_temp_map;
- map = data->pwm_temp_map[nr];
- if (map >= num_map)
- map = 0; /* Should never happen */
- if (nr >= num_map) /* pwm channels 3..6 map to temp4..6 */
- map += num_map;
+ channel = pwm_temp_channel(data, nr, data->pwm_temp_map[nr]);
- return sprintf(buf, "%d\n", (int)BIT(map));
+ return sprintf(buf, "%d\n", (int)BIT(channel));
}
static ssize_t set_pwm_temp_map(struct device *dev,
@@ -1785,45 +1892,34 @@ static ssize_t set_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = dev_get_drvdata(dev);
int nr = sensor_attr->index;
- u8 num_map = data->num_temp_map;
long val;
int err;
- u8 reg;
+ unsigned int channel;
+ u8 map;
- if (kstrtol(buf, 10, &val) < 0)
+ if (kstrtol(buf, 10, &val) < 0 || val <= 0 || !is_power_of_2(val))
return -EINVAL;
- if (nr >= num_map)
- val -= num_map;
-
- switch (val) {
- case BIT(0):
- reg = 0x00;
- break;
- case BIT(1):
- reg = 0x01;
- break;
- case BIT(2):
- reg = 0x02;
- break;
- default:
+ channel = __ffs(val);
+ if (pwm_temp_map_from_channel(data, nr, channel, &map))
return -EINVAL;
- }
err = it87_lock(data);
if (err)
return err;
it87_update_pwm_ctrl(data, nr);
- data->pwm_temp_map[nr] = reg;
+ data->pwm_temp_map[nr] = map;
/*
* If we are in automatic mode, write the temp mapping immediately;
* otherwise, just store it for later use.
*/
if (data->pwm_ctrl[nr] & 0x80) {
- data->pwm_ctrl[nr] = (data->pwm_ctrl[nr] & 0xfc) |
- data->pwm_temp_map[nr];
- it87_write_value(data, IT87_REG_PWM[nr], data->pwm_ctrl[nr]);
+ data->pwm_ctrl[nr] = pwm_temp_map_set(data,
+ data->pwm_ctrl[nr],
+ data->pwm_temp_map[nr]);
+ it87_write_value(data, it87_reg_pwm(data, nr),
+ data->pwm_ctrl[nr]);
}
it87_unlock(data);
return count;
@@ -3422,7 +3518,10 @@ static void it87_init_device(struct platform_device *pdev)
* manual duty cycle.
*/
for (i = 0; i < NUM_AUTO_PWM; i++) {
- data->pwm_temp_map[i] = i;
+ if (has_new_tempmap(data))
+ data->pwm_temp_map[i] = 0;
+ else
+ data->pwm_temp_map[i] = i % IT87_PWM_OLD_NUM_TEMP;
data->pwm_duty[i] = 0x7f; /* Full speed */
data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */
}
@@ -3494,7 +3593,8 @@ static int it87_check_pwm(struct device *dev)
for (i = 0; i < ARRAY_SIZE(pwm); i++)
pwm[i] = it87_read_value(data,
- IT87_REG_PWM[i]);
+ it87_reg_pwm(data,
+ i));
/*
* If any fan is in automatic pwm mode, the polarity
@@ -3509,7 +3609,8 @@ static int it87_check_pwm(struct device *dev)
tmp | 0x87);
for (i = 0; i < 3; i++)
it87_write_value(data,
- IT87_REG_PWM[i],
+ it87_reg_pwm(data,
+ i),
0x7f & ~pwm[i]);
return 1;
}
@@ -3558,11 +3659,19 @@ static int it87_probe(struct platform_device *pdev)
data->ec_special_config = sio_data->ec_special_config;
chip = &it87_devices[sio_data->type];
data->features = chip->features;
+ data->reg_pwm = chip->reg_pwm;
data->peci_mask = chip->peci_mask;
data->old_peci_mask = chip->old_peci_mask;
data->num_temp_limit = chip->num_temp_limit;
data->num_temp_offset = chip->num_temp_offset;
data->num_temp_map = chip->num_temp_map;
+ if (has_new_tempmap(data)) {
+ data->pwm_temp_map_mask = 0x07;
+ data->pwm_temp_map_shift = 3;
+ } else {
+ data->pwm_temp_map_mask = 0x03;
+ data->pwm_temp_map_shift = 0;
+ }
/*
* IT8705F Datasheet 0.4.1, 3h == Version G.
* IT8712F Datasheet 0.9.1, section 8.3.5 indicates 8h == Version J.
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH v5 2/4] hwmon: it87: prepare for extended PWM temp maps
2026-08-29 17:05 ` [PATCH v5 2/4] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
@ 2026-08-29 17:20 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-29 17:20 UTC (permalink / raw)
To: Jerome Tollet; +Cc: linux-hwmon
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] A missing pair of braces in `it87_update_pwm_ctrl` causes a dangling `else` that unconditionally corrupts the manual PWM duty cycle for legacy chips.
- [High] Adding `it87_update_pwm_ctrl` to `set_pwm_enable` clobbers deferred `pwm_temp_map` settings for newer chips.
- [Medium] Replacing `data->num_temp_map` with hardcoded `IT87_PWM_OLD_NUM_TEMP` breaks PWM-to-temperature mapping on chips with larger map groupings (e.g., IT8603E and IT8622E).
--
commit df4c2373d867162752a6dc57c16ff16987dbedff
Author: benoit.masson <yahoo@perenite.com>
hwmon: it87: prepare for extended PWM temp maps
This commit introduces helper logic for PWM-to-temperature mappings,
preparing the driver for newer register layouts with extended maps
while keeping legacy chips unaffected. It replaces the old hardcoded
3-sensor mappings with dynamic lookup helpers based on chip features.
> diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
> index 1c77f6c97fc36..e077adbb30911 100644
> --- a/drivers/hwmon/it87.c
> +++ b/drivers/hwmon/it87.c
[ ... ]
> @@ -786,6 +816,71 @@ static int pwm_from_reg(const struct it87_data *data, u8 reg)
[ ... ]
> +static unsigned int pwm_temp_channel(const struct it87_data *data,
> + int nr, u8 map)
> +{
> + if (has_new_tempmap(data)) {
> + u8 num = pwm_num_temp_map(data);
> +
> + if (map >= num)
> + map = 0;
> + return map;
> + }
> +
> + if (map >= IT87_PWM_OLD_NUM_TEMP)
> + map = 0;
> +
> + if (nr >= IT87_PWM_OLD_NUM_TEMP)
> + map += IT87_PWM_OLD_NUM_TEMP;
[Severity: Medium]
Does replacing data->num_temp_map with the hardcoded IT87_PWM_OLD_NUM_TEMP
here break chips with 4 temperature maps?
Chips like IT8603E and IT8622E have num_temp_map == 4, but lack the
FEAT_NEW_TEMPMAP flag. This logic unconditionally forces an offset of 3 for
pwm4 on legacy chips, ignoring the fact that they might have 4 maps.
> +
> + return map;
> +}
> +
> +static int pwm_temp_map_from_channel(const struct it87_data *data, int nr,
> + unsigned int channel, u8 *map)
> +{
> + if (has_new_tempmap(data)) {
> + u8 num = pwm_num_temp_map(data);
> +
> + if (channel >= num)
> + return -EINVAL;
> + *map = channel;
> + return 0;
> + }
> +
> + if (nr >= IT87_PWM_OLD_NUM_TEMP) {
> + if (channel < IT87_PWM_OLD_NUM_TEMP ||
> + channel >= 2 * IT87_PWM_OLD_NUM_TEMP)
> + return -EINVAL;
> + channel -= IT87_PWM_OLD_NUM_TEMP;
> + } else {
[Severity: Medium]
Will this erroneously reject valid sysfs inputs for pwm4 on chips with
a 4-sensor mapping?
Because IT87_PWM_OLD_NUM_TEMP is hardcoded to 3, this restricts valid
sysfs input for pwm4 to channels 3-5. This rejects channels 0-3, which are
completely valid for a chip with a 4-sensor mapping.
> + if (channel >= IT87_PWM_OLD_NUM_TEMP)
> + return -EINVAL;
> + }
> +
> + *map = channel;
> + return 0;
> +}
[ ... ]
> @@ -877,14 +977,22 @@ static void it87_write_value(struct it87_data *data, u8 reg, u8 value)
>
> static void it87_update_pwm_ctrl(struct it87_data *data, int nr)
> {
> - data->pwm_ctrl[nr] = it87_read_value(data, IT87_REG_PWM[nr]);
> + data->pwm_ctrl[nr] = it87_read_value(data, it87_reg_pwm(data, nr));
> if (has_newer_autopwm(data)) {
> - data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
> + data->pwm_temp_map[nr] =
> + pwm_temp_map_get(data, data->pwm_ctrl[nr]);
> + if (has_new_tempmap(data) &&
> + data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
> + data->pwm_temp_map[nr] = 0;
> data->pwm_duty[nr] = it87_read_value(data,
> IT87_REG_PWM_DUTY[nr]);
> } else {
> if (data->pwm_ctrl[nr] & 0x80) /* Automatic mode */
> - data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
> + data->pwm_temp_map[nr] =
> + pwm_temp_map_get(data, data->pwm_ctrl[nr]);
> + if (has_new_tempmap(data) &&
> + data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
> + data->pwm_temp_map[nr] = 0;
> else /* Manual mode */
> data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;
> }
[Severity: High]
Does this dangling else clause unconditionally corrupt the manual PWM duty
cycle for legacy chips?
Because there are no braces around the first "if (data->pwm_ctrl[nr] & 0x80)",
the "else /* Manual mode */" binds to the second
"if (has_new_tempmap(data)...)".
For legacy chips, has_new_tempmap(data) is always false, causing the else
block to execute unconditionally and corrupt the pwm_duty cache with the
lowest 7 bits of the temperature mapping register when in automatic mode.
When a user subsequently switches back to manual mode, this corrupted near-zero
value is written to the hardware, potentially spinning down the fan and
posing a critical overheating risk.
[ ... ]
> @@ -1617,6 +1725,8 @@ static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr,
> if (err)
> return err;
>
> + it87_update_pwm_ctrl(data, nr);
> +
> if (val == 0) {
> if (nr < 3 && has_fanctl_onoff(data)) {
> int tmp;
[Severity: High]
Can this added call to it87_update_pwm_ctrl() clobber deferred user
settings stored in the software cache?
The driver explicitly allows users to configure pwm_temp_map while in manual
mode, deferring the actual hardware write until the fan is switched to
automatic mode. The added unconditional call to it87_update_pwm_ctrl()
refreshes the software cache from hardware registers right before a mode
transition, permanently overwriting and destroying the user's deferred
configuration.
When automatic mode is enabled, the fan will operate on the old temperature
mapping, potentially reacting to the wrong sensor and causing the hardware
to overheat.
[ ... ]
> @@ -3422,7 +3518,10 @@ static void it87_init_device(struct platform_device *pdev)
> * manual duty cycle.
> */
> for (i = 0; i < NUM_AUTO_PWM; i++) {
> - data->pwm_temp_map[i] = i;
> + if (has_new_tempmap(data))
> + data->pwm_temp_map[i] = 0;
> + else
> + data->pwm_temp_map[i] = i % IT87_PWM_OLD_NUM_TEMP;
> data->pwm_duty[i] = 0x7f; /* Full speed */
> data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */
> }
[Severity: Medium]
Is it intentional that this redefines the default hardware mapping on
boot for chips with 4 temperature maps?
By relying on IT87_PWM_OLD_NUM_TEMP, this breaks the default mapping for
chips like IT8603E and IT8622E which have num_temp_map == 4 but lack the
FEAT_NEW_TEMPMAP flag.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260829170545.33401-1-jerome.tollet@gmail.com?part=2
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v5 3/4] hwmon: it87: expose additional temperature limits
2026-08-29 17:05 [PATCH v5 0/4] hwmon: it87: add IT8613E support Jerome Tollet
2026-08-29 17:05 ` [PATCH v5 1/4] hwmon: it87: describe per-chip temperature resources Jerome Tollet
2026-08-29 17:05 ` [PATCH v5 2/4] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
@ 2026-08-29 17:05 ` Jerome Tollet
2026-08-29 17:17 ` sashiko-bot
2026-08-29 17:05 ` [PATCH v5 4/4] hwmon: it87: add IT8613E support Jerome Tollet
2026-08-29 21:00 ` [PATCH v6 0/3] " Jerome Tollet
4 siblings, 1 reply; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 17:05 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck
Cc: linux-hwmon, Benoit Masson, Jonathan Corbet, Shuah Khan,
Randy Dunlap, linux-doc, linux-kernel, Jerome Tollet
From: "benoit.masson" <yahoo@perenite.com>
Expose extra temp min/max/offset/type/alarm attributes when a chip
reports more than three temperature resources.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
drivers/hwmon/it87.c | 58 ++++++++++++++++++++++++++++++++++++++++----
1 file changed, 53 insertions(+), 5 deletions(-)
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index e077adbb3..e44ec65c7 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -1349,8 +1349,26 @@ static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, set_temp,
static SENSOR_DEVICE_ATTR_2(temp3_offset, S_IRUGO | S_IWUSR, show_temp,
set_temp, 2, 3);
static SENSOR_DEVICE_ATTR_2(temp4_input, S_IRUGO, show_temp, NULL, 3, 0);
+static SENSOR_DEVICE_ATTR_2(temp4_min, 0644, show_temp, set_temp,
+ 3, 1);
+static SENSOR_DEVICE_ATTR_2(temp4_max, 0644, show_temp, set_temp,
+ 3, 2);
+static SENSOR_DEVICE_ATTR_2(temp4_offset, 0644, show_temp,
+ set_temp, 3, 3);
static SENSOR_DEVICE_ATTR_2(temp5_input, S_IRUGO, show_temp, NULL, 4, 0);
+static SENSOR_DEVICE_ATTR_2(temp5_min, 0644, show_temp, set_temp,
+ 4, 1);
+static SENSOR_DEVICE_ATTR_2(temp5_max, 0644, show_temp, set_temp,
+ 4, 2);
+static SENSOR_DEVICE_ATTR_2(temp5_offset, 0644, show_temp,
+ set_temp, 4, 3);
static SENSOR_DEVICE_ATTR_2(temp6_input, S_IRUGO, show_temp, NULL, 5, 0);
+static SENSOR_DEVICE_ATTR_2(temp6_min, 0644, show_temp, set_temp,
+ 5, 1);
+static SENSOR_DEVICE_ATTR_2(temp6_max, 0644, show_temp, set_temp,
+ 5, 2);
+static SENSOR_DEVICE_ATTR_2(temp6_offset, 0644, show_temp,
+ set_temp, 5, 3);
static int get_temp_type(struct it87_data *data, int index)
{
@@ -1476,6 +1494,12 @@ static SENSOR_DEVICE_ATTR(temp2_type, S_IRUGO | S_IWUSR, show_temp_type,
set_temp_type, 1);
static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO | S_IWUSR, show_temp_type,
set_temp_type, 2);
+static SENSOR_DEVICE_ATTR(temp4_type, 0644, show_temp_type,
+ set_temp_type, 3);
+static SENSOR_DEVICE_ATTR(temp5_type, 0644, show_temp_type,
+ set_temp_type, 4);
+static SENSOR_DEVICE_ATTR(temp6_type, 0644, show_temp_type,
+ set_temp_type, 5);
/* 6 Fans */
@@ -2308,6 +2332,9 @@ static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_alarm, NULL, 7);
static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 16);
static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 17);
static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 18);
+static SENSOR_DEVICE_ATTR(temp4_alarm, 0444, show_alarm, NULL, 19);
+static SENSOR_DEVICE_ATTR(temp5_alarm, 0444, show_alarm, NULL, 20);
+static SENSOR_DEVICE_ATTR(temp6_alarm, 0444, show_alarm, NULL, 21);
static SENSOR_DEVICE_ATTR(intrusion0_alarm, S_IRUGO | S_IWUSR,
show_alarm, clear_intrusion, 4);
@@ -2368,6 +2395,9 @@ static SENSOR_DEVICE_ATTR(temp1_beep, S_IRUGO | S_IWUSR,
show_beep, set_beep, 2);
static SENSOR_DEVICE_ATTR(temp2_beep, S_IRUGO, show_beep, NULL, 2);
static SENSOR_DEVICE_ATTR(temp3_beep, S_IRUGO, show_beep, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp4_beep, 0444, show_beep, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp5_beep, 0444, show_beep, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp6_beep, 0444, show_beep, NULL, 2);
static ssize_t vrm_show(struct device *dev, struct device_attribute *attr,
char *buf)
@@ -2530,14 +2560,12 @@ static umode_t it87_temp_is_visible(struct kobject *kobj,
int i = index / 7; /* temperature index */
int a = index % 7; /* attribute index */
- if (index >= 21) {
- i = index - 21 + 3;
- a = 0;
- }
-
if (!(data->has_temp & BIT(i)))
return 0;
+ if (a && i >= data->num_temp_limit)
+ return 0;
+
if (a == 3) {
if (get_temp_type(data, i) == 0)
return 0;
@@ -2579,8 +2607,28 @@ static struct attribute *it87_attributes_temp[] = {
&sensor_dev_attr_temp3_beep.dev_attr.attr,
&sensor_dev_attr_temp4_input.dev_attr.attr, /* 21 */
+ &sensor_dev_attr_temp4_max.dev_attr.attr,
+ &sensor_dev_attr_temp4_min.dev_attr.attr,
+ &sensor_dev_attr_temp4_type.dev_attr.attr,
+ &sensor_dev_attr_temp4_alarm.dev_attr.attr,
+ &sensor_dev_attr_temp4_offset.dev_attr.attr,
+ &sensor_dev_attr_temp4_beep.dev_attr.attr,
+
&sensor_dev_attr_temp5_input.dev_attr.attr,
+ &sensor_dev_attr_temp5_max.dev_attr.attr,
+ &sensor_dev_attr_temp5_min.dev_attr.attr,
+ &sensor_dev_attr_temp5_type.dev_attr.attr,
+ &sensor_dev_attr_temp5_alarm.dev_attr.attr,
+ &sensor_dev_attr_temp5_offset.dev_attr.attr,
+ &sensor_dev_attr_temp5_beep.dev_attr.attr,
+
&sensor_dev_attr_temp6_input.dev_attr.attr,
+ &sensor_dev_attr_temp6_max.dev_attr.attr,
+ &sensor_dev_attr_temp6_min.dev_attr.attr,
+ &sensor_dev_attr_temp6_type.dev_attr.attr,
+ &sensor_dev_attr_temp6_alarm.dev_attr.attr,
+ &sensor_dev_attr_temp6_offset.dev_attr.attr,
+ &sensor_dev_attr_temp6_beep.dev_attr.attr,
NULL
};
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH v5 3/4] hwmon: it87: expose additional temperature limits
2026-08-29 17:05 ` [PATCH v5 3/4] hwmon: it87: expose additional temperature limits Jerome Tollet
@ 2026-08-29 17:17 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-29 17:17 UTC (permalink / raw)
To: Jerome Tollet; +Cc: linux-hwmon
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Writing to newly exposed `temp4_type`, `temp5_type`, or `temp6_type` attributes results in out-of-bounds bit shifts that silently corrupt the configuration of sensors 1-3.
--
commit 799ba3c032a9c2e42d2a88d4c0b5a095915c09f1
Author: benoit.masson <yahoo@perenite.com>
hwmon: it87: expose additional temperature limits
Expose extra temp min/max/offset/type/alarm attributes when a chip
reports more than three temperature resources.
> diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
> index e077adbb30911..e44ec65c76f03 100644
> --- a/drivers/hwmon/it87.c
> +++ b/drivers/hwmon/it87.c
[ ... ]
> @@ -1476,6 +1494,12 @@ static SENSOR_DEVICE_ATTR(temp2_type, S_IRUGO | S_IWUSR, show_temp_type,
> set_temp_type, 1);
> static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO | S_IWUSR, show_temp_type,
> set_temp_type, 2);
> +static SENSOR_DEVICE_ATTR(temp4_type, 0644, show_temp_type,
> + set_temp_type, 3);
> +static SENSOR_DEVICE_ATTR(temp5_type, 0644, show_temp_type,
> + set_temp_type, 4);
> +static SENSOR_DEVICE_ATTR(temp6_type, 0644, show_temp_type,
> + set_temp_type, 5);
[Severity: High]
When userspace writes to the newly exposed temp4_type attribute, nr will be 3.
Looking at set_temp_type(), it seems to destructively modify bits belonging to
sensors 1-3 when nr >= 3:
drivers/hwmon/it87.c:set_temp_type() {
...
reg &= ~(1 << nr);
reg &= ~(8 << nr);
...
}
Can this corrupt the configuration for the other sensors?
Additionally, when configuring PECI (val == 6) for temp4_type:
drivers/hwmon/it87.c:set_temp_type() {
...
else if (has_temp_peci(data, nr) && val == 6)
reg |= (nr + 1) << 6;
...
}
Because reg is an 8-bit u8, does (3 + 1) << 6 (which evaluates to 256)
overflow the register limit?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260829170545.33401-1-jerome.tollet@gmail.com?part=3
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v5 4/4] hwmon: it87: add IT8613E support
2026-08-29 17:05 [PATCH v5 0/4] hwmon: it87: add IT8613E support Jerome Tollet
` (2 preceding siblings ...)
2026-08-29 17:05 ` [PATCH v5 3/4] hwmon: it87: expose additional temperature limits Jerome Tollet
@ 2026-08-29 17:05 ` Jerome Tollet
2026-08-29 17:23 ` sashiko-bot
2026-08-29 21:00 ` [PATCH v6 0/3] " Jerome Tollet
4 siblings, 1 reply; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 17:05 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck
Cc: linux-hwmon, Benoit Masson, Jonathan Corbet, Shuah Khan,
Randy Dunlap, linux-doc, linux-kernel, Jerome Tollet
From: "benoit.masson" <yahoo@perenite.com>
Teach the Super I/O probe path to recognize IT8613E and add its
complete hardware monitoring configuration.
Add feature flags, temperature resource counts, 11 mV ADC scaling,
the IT8665-style PWM register map, and GPIO pin-mux checks. Document
the chip in the hwmon guide.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
Documentation/hwmon/it87.rst | 8 +++++
drivers/hwmon/it87.c | 64 ++++++++++++++++++++++++++++++++++--
2 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/Documentation/hwmon/it87.rst b/Documentation/hwmon/it87.rst
index fc1c90b02..c33ba8a07 100644
--- a/Documentation/hwmon/it87.rst
+++ b/Documentation/hwmon/it87.rst
@@ -11,6 +11,14 @@ Supported chips:
Datasheet: Not publicly available
+ * IT8613E
+
+ Prefix: 'it8613'
+
+ Addresses scanned: from Super I/O config space (8 I/O ports)
+
+ Datasheet: Not publicly available
+
* IT8620E
Prefix: 'it8620'
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index e44ec65c7..67a94761d 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -36,6 +36,7 @@
* IT8790E Super I/O chip w/LPC interface
* IT8792E Super I/O chip w/LPC interface
* IT87952E Super I/O chip w/LPC interface
+ * IT8613E Super I/O chip w/LPC interface
* Sis950 A clone of the IT8705F
*
* Copyright (C) 2001 Chris Gauthron
@@ -65,7 +66,7 @@
enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732,
it8771, it8772, it8781, it8782, it8783, it8786, it8790,
- it8792, it8603, it8620, it8622, it8628, it8689, it87952 };
+ it8792, it8603, it8613, it8620, it8622, it8628, it8689, it87952 };
static struct platform_device *it87_pdev[2];
@@ -159,6 +160,7 @@ static inline void superio_exit(int ioreg, bool noexit)
#define IT8786E_DEVID 0x8786
#define IT8790E_DEVID 0x8790
#define IT8603E_DEVID 0x8603
+#define IT8613E_DEVID 0x8613
#define IT8620E_DEVID 0x8620
#define IT8622E_DEVID 0x8622
#define IT8623E_DEVID 0x8623
@@ -333,6 +335,7 @@ struct it87_devices {
#define FEAT_FOUR_TEMP BIT(22)
#define FEAT_FANCTL_ONOFF BIT(23) /* chip has FAN_CTL ON/OFF */
#define FEAT_NEW_TEMPMAP BIT(24) /* PWM uses extended temp map */
+#define FEAT_11MV_ADC BIT(25)
static const struct it87_devices it87_devices[] = {
[it87] = {
@@ -549,6 +552,19 @@ static const struct it87_devices it87_devices[] = {
.num_temp_map = 4,
.peci_mask = 0x07,
},
+ [it8613] = {
+ .name = "it8613",
+ .model = "IT8613E",
+ .features = FEAT_NEWER_AUTOPWM | FEAT_11MV_ADC | FEAT_16BIT_FANS
+ | FEAT_TEMP_PECI | FEAT_FIVE_FANS
+ | FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
+ | FEAT_AVCC3 | FEAT_NEW_TEMPMAP,
+ .reg_pwm = IT87_REG_PWM_8665,
+ .num_temp_limit = 6,
+ .num_temp_offset = 6,
+ .num_temp_map = 6,
+ .peci_mask = 0x07,
+ },
[it8620] = {
.name = "it8620",
.model = "IT8620E",
@@ -621,6 +637,7 @@ static const struct it87_devices it87_devices[] = {
#define has_16bit_fans(data) ((data)->features & FEAT_16BIT_FANS)
#define has_12mv_adc(data) ((data)->features & FEAT_12MV_ADC)
#define has_10_9mv_adc(data) ((data)->features & FEAT_10_9MV_ADC)
+#define has_11mv_adc(data) ((data)->features & FEAT_11MV_ADC)
#define has_newer_autopwm(data) ((data)->features & FEAT_NEWER_AUTOPWM)
#define has_old_autopwm(data) ((data)->features & FEAT_OLD_AUTOPWM)
#define has_temp_peci(data, nr) (((data)->features & FEAT_TEMP_PECI) && \
@@ -650,7 +667,8 @@ static const struct it87_devices it87_devices[] = {
#define has_vin3_5v(data) ((data)->features & FEAT_VIN3_5V)
#define has_noconf(data) ((data)->features & FEAT_NOCONF)
#define has_scaling(data) ((data)->features & (FEAT_12MV_ADC | \
- FEAT_10_9MV_ADC))
+ FEAT_10_9MV_ADC | \
+ FEAT_11MV_ADC))
#define has_fanctl_onoff(data) ((data)->features & FEAT_FANCTL_ONOFF)
#define has_new_tempmap(data) ((data)->features & FEAT_NEW_TEMPMAP)
@@ -757,6 +775,8 @@ static int adc_lsb(const struct it87_data *data, int nr)
lsb = 120;
else if (has_10_9mv_adc(data))
lsb = 109;
+ else if (has_11mv_adc(data))
+ lsb = 110;
else
lsb = 160;
if (data->in_scaled & BIT(nr))
@@ -3006,6 +3026,9 @@ static int __init it87_find(int sioaddr, unsigned short *address,
case IT8623E_DEVID:
sio_data->type = it8603;
break;
+ case IT8613E_DEVID:
+ sio_data->type = it8613;
+ break;
case IT8620E_DEVID:
sio_data->type = it8620;
break;
@@ -3183,6 +3206,43 @@ static int __init it87_find(int sioaddr, unsigned short *address,
sio_data->skip_in |= BIT(5); /* No VIN5 */
sio_data->skip_in |= BIT(6); /* No VIN6 */
+ sio_data->beep_pin = superio_inb(sioaddr,
+ IT87_SIO_BEEP_PIN_REG) & 0x3f;
+ } else if (sio_data->type == it8613) {
+ int reg27, reg29, reg2a;
+
+ superio_select(sioaddr, GPIO);
+
+ /* Check for pwm3, fan3, pwm5, fan5 */
+ reg27 = superio_inb(sioaddr, IT87_SIO_GPIO3_REG);
+ if (!(reg27 & BIT(1)))
+ sio_data->skip_fan |= BIT(4);
+ if (reg27 & BIT(3))
+ sio_data->skip_pwm |= BIT(4);
+ if (reg27 & BIT(6))
+ sio_data->skip_pwm |= BIT(2);
+ if (reg27 & BIT(7))
+ sio_data->skip_fan |= BIT(2);
+
+ /* Check for pwm2, fan2 */
+ reg29 = superio_inb(sioaddr, IT87_SIO_GPIO5_REG);
+ if (reg29 & BIT(1))
+ sio_data->skip_pwm |= BIT(1);
+ if (reg29 & BIT(2))
+ sio_data->skip_fan |= BIT(1);
+
+ /* Check for pwm4, fan4 */
+ reg2a = superio_inb(sioaddr, IT87_SIO_PINX1_REG);
+ if (!(reg2a & BIT(0)) || (reg29 & BIT(7))) {
+ sio_data->skip_fan |= BIT(3);
+ sio_data->skip_pwm |= BIT(3);
+ }
+
+ sio_data->skip_pwm |= BIT(0); /* No pwm1 */
+ sio_data->skip_fan |= BIT(0); /* No fan1 */
+ sio_data->skip_in |= BIT(3); /* No VIN3 */
+ sio_data->skip_in |= BIT(6); /* No VIN6 */
+
sio_data->beep_pin = superio_inb(sioaddr,
IT87_SIO_BEEP_PIN_REG) & 0x3f;
} else if (sio_data->type == it8620 || sio_data->type == it8628) {
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH v5 4/4] hwmon: it87: add IT8613E support
2026-08-29 17:05 ` [PATCH v5 4/4] hwmon: it87: add IT8613E support Jerome Tollet
@ 2026-08-29 17:23 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-29 17:23 UTC (permalink / raw)
To: Jerome Tollet; +Cc: linux-hwmon
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] The IT8613E configuration specifies support for 6 temperature sensors (num_temp_limit = 6), but omits the FEAT_SIX_TEMP feature flag, silently disabling sensors 4-6.
Pre-existing issues:
- [High] set_temp_type and get_temp_type corrupt and misinterpret IT87_REG_TEMP_ENABLE for temperature sensors 4, 5, and 6 due to incorrect bit-shifts exceeding the intended sensor bounds (0-2).
--
commit 62e828013c359cc9aa023fe24c4b5b0736549b6b
Author: benoit.masson <yahoo@perenite.com>
hwmon: it87: add IT8613E support
Teach the Super I/O probe path to recognize IT8613E and add its complete
hardware monitoring configuration. Add feature flags, temperature resource
counts, 11 mV ADC scaling, the IT8665-style PWM register map, and GPIO
pin-mux checks. Document the chip in the hwmon guide.
> diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
> index e44ec65c76f03..67a94761d1c89 100644
> --- a/drivers/hwmon/it87.c
> +++ b/drivers/hwmon/it87.c
[ ... ]
> @@ -549,6 +552,19 @@ static const struct it87_devices it87_devices[] = {
> .num_temp_map = 4,
> .peci_mask = 0x07,
> },
> + [it8613] = {
> + .name = "it8613",
> + .model = "IT8613E",
> + .features = FEAT_NEWER_AUTOPWM | FEAT_11MV_ADC | FEAT_16BIT_FANS
> + | FEAT_TEMP_PECI | FEAT_FIVE_FANS
> + | FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
> + | FEAT_AVCC3 | FEAT_NEW_TEMPMAP,
[Severity: Medium]
Does this missing feature flag permanently disable sensors 4-6?
The IT8613E configuration specifies support for 6 temperature sensors with
num_temp_limit = 6, but the FEAT_SIX_TEMP feature flag is omitted here.
Without FEAT_SIX_TEMP, the probe logic in it87_probe() will not enable
the extra temperature sensors, leaving sensors 4-6 completely inaccessible
in sysfs:
drivers/hwmon/it87.c:it87_probe() {
...
} else if (has_six_temp(data)) {
u8 reg = it87_read_value(data, IT87_REG_TEMP456_ENABLE);
/* Check for additional temperature sensors */
if ((reg & 0x03) >= 0x02)
data->has_temp |= BIT(3);
...
}
> + .reg_pwm = IT87_REG_PWM_8665,
> + .num_temp_limit = 6,
[Severity: High]
This is a pre-existing issue, but configuring 6 temperature sensors exposes
an out-of-bounds bitwise shift bug in get_temp_type() and set_temp_type().
When querying or setting types for sensors 4-6 (indices 3-5), get_temp_type()
misinterprets the configuration by reading bits 3-8 of IT87_REG_TEMP_ENABLE,
which belong to sensors 1-3:
drivers/hwmon/it87.c:get_temp_type() {
...
else if (reg & BIT(index))
type = 3; /* thermal diode */
else if (reg & BIT(index + 3))
type = 4; /* thermistor */
...
}
If those attributes become visible, set_temp_type() will apply out-of-bounds
bitwise shifts to IT87_REG_TEMP_ENABLE:
drivers/hwmon/it87.c:set_temp_type() {
...
reg = it87_read_value(data, IT87_REG_TEMP_ENABLE);
reg &= ~(1 << nr);
reg &= ~(8 << nr);
...
if (val == 3)
reg |= 1 << nr;
else if (val == 4)
reg |= 8 << nr;
...
}
Can this corrupt the chip configuration? Using unbounded nr indices (3-5)
will overwrite unrelated sensor configuration bits instead of targeting the
correct register, severely breaking thermal monitoring configuration for the
primary sensors and potentially leading to dangerous misbehavior in automated
fan control.
> + .num_temp_offset = 6,
> + .num_temp_map = 6,
> + .peci_mask = 0x07,
> + },
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260829170545.33401-1-jerome.tollet@gmail.com?part=4
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v6 0/3] hwmon: it87: add IT8613E support
2026-08-29 17:05 [PATCH v5 0/4] hwmon: it87: add IT8613E support Jerome Tollet
` (3 preceding siblings ...)
2026-08-29 17:05 ` [PATCH v5 4/4] hwmon: it87: add IT8613E support Jerome Tollet
@ 2026-08-29 21:00 ` Jerome Tollet
2026-08-29 21:00 ` [PATCH v6 1/3] hwmon: it87: describe per-chip temperature resources Jerome Tollet
` (4 more replies)
4 siblings, 5 replies; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 21:00 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck; +Cc: linux-hwmon, Benoit Masson, Jerome Tollet
This is a respin of Benoit Masson's v4 series, with his permission.
It replaces my fan-input-only RFC with the broader hardware monitoring
support requested in review.
The v4 series was dropped after a NULL pointer dereference was reported.
The IT8613E device entry did not initialize reg_pwm, while the probe path
unconditionally dereferences it. It also advertised six temperature
offsets but the register table contained only three entries.
Changes in v6:
- Drop the patch exposing additional temperature attributes. The existing
type register handling only describes temperature inputs 1 to 3, so
exposing temp4_type through temp6_type would not be safe.
- Remove the unused NUM_TEMP_OFFSET macro.
- Retain the legacy two groups of three temperature sources where
appropriate, and honor the four global sources on IT8603E and IT8622E.
- Fix the legacy automatic/manual PWM control flow so the manual duty
cache is not overwritten in automatic mode.
- Do not refresh PWM control state while changing modes. On newer
controllers, write temperature mappings in both manual and automatic
modes because the duty cycle uses a separate register. This keeps a
mapping across cache refreshes and mode changes.
- Clarify that only three IT8613E temperature inputs are currently known.
The six limit, offset, and PWM map resources do not imply six inputs,
so FEAT_SIX_TEMP is intentionally not set.
Tested on an IT8613E at 0xa30, revision 12. The complete series builds as
an external module with W=1 and passes checkpatch --strict with no
warnings or errors.
The module exposes fan2_input and PWM2, PWM4, and PWM5 on this system.
The deferred temperature mapping path was tested by switching PWM2 to
manual mode, changing pwm2_auto_channels_temp from 4 to 1, waiting for a
cache refresh, and switching back to automatic mode. The value remained
1 after both the refresh and mode change. The original mapping, duty, and
automatic mode were then restored.
The PWM2 write path was tested by switching from automatic control to
manual full duty (255). Fan2 increased from 2789 RPM to 4821 RPM. The
original duty (65), automatic mode, and temperature mapping were then
restored.
An S3 suspend/resume cycle with an RTC wakeup completed successfully.
The PWM configuration and sensor readings were preserved, with no it87 or
hwmon warnings in the kernel log.
Link: https://lore.kernel.org/linux-hwmon/20260114221210.98071-1-yahoo@perenite.com/
Link: https://lore.kernel.org/linux-hwmon/20260829170545.33401-1-jerome.tollet@gmail.com/
benoit.masson (3):
hwmon: it87: describe per-chip temperature resources
hwmon: it87: prepare for extended PWM temp maps
hwmon: it87: add IT8613E support
Documentation/hwmon/it87.rst | 8 +
drivers/hwmon/it87.c | 418 ++++++++++++++++++++++++++++-------
2 files changed, 342 insertions(+), 84 deletions(-)
base-commit: 75f2c0b3690702c90863c2e138cb5520670845ea
--
2.55.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v6 1/3] hwmon: it87: describe per-chip temperature resources
2026-08-29 21:00 ` [PATCH v6 0/3] " Jerome Tollet
@ 2026-08-29 21:00 ` Jerome Tollet
2026-08-29 21:17 ` sashiko-bot
2026-08-29 21:00 ` [PATCH v6 2/3] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
` (3 subsequent siblings)
4 siblings, 1 reply; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 21:00 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck; +Cc: linux-hwmon, Benoit Masson, Jerome Tollet
From: "benoit.masson" <yahoo@perenite.com>
Add per-chip temp limit/offset/map counts and wire the driver
to use them.
This keeps existing chips on the previous defaults while allowing newer
chips to advertise larger resources.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
drivers/hwmon/it87.c | 152 ++++++++++++++++++++++++++++++++-----------
1 file changed, 114 insertions(+), 38 deletions(-)
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index 87edb1b60..1af815b5d 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -247,7 +247,7 @@ static const u8 IT87_REG_FAN[] = { 0x0d, 0x0e, 0x0f, 0x80, 0x82, 0x4c };
static const u8 IT87_REG_FAN_MIN[] = { 0x10, 0x11, 0x12, 0x84, 0x86, 0x4e };
static const u8 IT87_REG_FANX[] = { 0x18, 0x19, 0x1a, 0x81, 0x83, 0x4d };
static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87, 0x4f };
-static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 };
+static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59, 0x5a, 0x90, 0x91 };
#define IT87_REG_FAN_MAIN_CTRL 0x13
#define IT87_REG_FAN_CTL 0x14
@@ -281,8 +281,6 @@ static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 };
#define NUM_VIN ARRAY_SIZE(IT87_REG_VIN)
#define NUM_VIN_LIMIT 8
#define NUM_TEMP 6
-#define NUM_TEMP_OFFSET ARRAY_SIZE(IT87_REG_TEMP_OFFSET)
-#define NUM_TEMP_LIMIT 3
#define NUM_FAN ARRAY_SIZE(IT87_REG_FAN)
#define NUM_FAN_DIV 3
#define NUM_PWM ARRAY_SIZE(IT87_REG_PWM)
@@ -292,6 +290,9 @@ struct it87_devices {
const char *name;
const char * const model;
u32 features;
+ u8 num_temp_limit;
+ u8 num_temp_offset;
+ u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
u8 smbus_bitmap; /* SMBus enable bits in extra config register */
@@ -302,7 +303,6 @@ struct it87_devices {
#define FEAT_NEWER_AUTOPWM BIT(1)
#define FEAT_OLD_AUTOPWM BIT(2)
#define FEAT_16BIT_FANS BIT(3)
-#define FEAT_TEMP_OFFSET BIT(4)
#define FEAT_TEMP_PECI BIT(5)
#define FEAT_TEMP_OLD_PECI BIT(6)
#define FEAT_FAN16_CONFIG BIT(7) /* Need to enable 16-bit fans */
@@ -335,43 +335,61 @@ static const struct it87_devices it87_devices[] = {
.model = "IT87F",
.features = FEAT_OLD_AUTOPWM | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .num_temp_limit = 3,
+ .num_temp_offset = 0,
+ .num_temp_map = 3,
},
[it8712] = {
.name = "it8712",
.model = "IT8712F",
.features = FEAT_OLD_AUTOPWM | FEAT_VID | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .num_temp_limit = 3,
+ .num_temp_offset = 0,
+ .num_temp_map = 3,
},
[it8716] = {
.name = "it8716",
.model = "IT8716F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
+ .features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
},
[it8718] = {
.name = "it8718",
.model = "IT8718F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
+ .features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8720] = {
.name = "it8720",
.model = "IT8720F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
+ .features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8721] = {
.name = "it8721",
.model = "IT8721F",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
+ | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x05,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -379,18 +397,24 @@ static const struct it87_devices it87_devices[] = {
.name = "it8728",
.model = "IT8728F",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
+ | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 6,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8732] = {
.name = "it8732",
.model = "IT8732F",
.features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
+ | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FOUR_FANS
| FEAT_FOUR_PWM | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -398,73 +422,97 @@ static const struct it87_devices it87_devices[] = {
.name = "it8771",
.model = "IT8771E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
/* PECI: guesswork */
/* 12mV ADC (OHM) */
/* 16 bit fans (OHM) */
/* three fans, always 16 bit (guesswork) */
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8772] = {
.name = "it8772",
.model = "IT8772E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
/* PECI (coreboot) */
/* 12mV ADC (HWSensors4, OHM) */
/* 16 bit fans (HWSensors4, OHM) */
/* three fans, always 16 bit (datasheet) */
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8781] = {
.name = "it8781",
.model = "IT8781F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
+ .features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8782] = {
.name = "it8782",
.model = "IT8782F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
+ .features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8783] = {
.name = "it8783",
.model = "IT8783E/F",
- .features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
+ .features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8786] = {
.name = "it8786",
.model = "IT8786E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8790] = {
.name = "it8790",
.model = "IT8790E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_NOCONF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8792] = {
.name = "it8792",
.model = "IT8792E/IT8795E",
.features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
+ | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -472,54 +520,72 @@ static const struct it87_devices it87_devices[] = {
.name = "it8603",
.model = "IT8603E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
+ | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_AVCC3 | FEAT_PWM_FREQ2,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8620] = {
.name = "it8620",
.model = "IT8620E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
+ | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8622] = {
.name = "it8622",
.model = "IT8622E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
+ | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_AVCC3 | FEAT_VIN3_5V | FEAT_FOUR_TEMP,
- .peci_mask = 0x07,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
+ .peci_mask = 0x0f,
.smbus_bitmap = BIT(1) | BIT(2),
},
[it8628] = {
.name = "it8628",
.model = "IT8628E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
+ | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 6,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8689] = {
.name = "it8689",
.model = "IT8689E",
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_SIX_FANS | FEAT_IN7_INTERNAL
+ | FEAT_SIX_FANS | FEAT_IN7_INTERNAL
| FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3
| FEAT_FANCTL_ONOFF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.smbus_bitmap = BIT(1) | BIT(2),
},
[it87952] = {
.name = "it87952",
.model = "IT87952E",
.features = FEAT_NEWER_AUTOPWM | FEAT_16BIT_FANS
- | FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
+ | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .num_temp_limit = 3,
+ .num_temp_offset = 3,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -530,7 +596,6 @@ static const struct it87_devices it87_devices[] = {
#define has_10_9mv_adc(data) ((data)->features & FEAT_10_9MV_ADC)
#define has_newer_autopwm(data) ((data)->features & FEAT_NEWER_AUTOPWM)
#define has_old_autopwm(data) ((data)->features & FEAT_OLD_AUTOPWM)
-#define has_temp_offset(data) ((data)->features & FEAT_TEMP_OFFSET)
#define has_temp_peci(data, nr) (((data)->features & FEAT_TEMP_PECI) && \
((data)->peci_mask & BIT(nr)))
#define has_temp_old_peci(data, nr) \
@@ -589,6 +654,9 @@ struct it87_data {
int sioaddr;
enum chips type;
u32 features;
+ u8 num_temp_limit;
+ u8 num_temp_offset;
+ u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
@@ -937,12 +1005,12 @@ static struct it87_data *it87_update_device(struct device *dev)
data->temp[i][0] =
it87_read_value(data, IT87_REG_TEMP(i));
- if (has_temp_offset(data) && i < NUM_TEMP_OFFSET)
+ if (i < data->num_temp_offset)
data->temp[i][3] =
it87_read_value(data,
IT87_REG_TEMP_OFFSET[i]);
- if (i >= NUM_TEMP_LIMIT)
+ if (i >= data->num_temp_limit)
continue;
data->temp[i][1] =
@@ -1693,16 +1761,18 @@ static ssize_t show_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = it87_update_device(dev);
int nr = sensor_attr->index;
+ u8 num_map;
int map;
if (IS_ERR(data))
return PTR_ERR(data);
+ num_map = data->num_temp_map;
map = data->pwm_temp_map[nr];
- if (map >= 3)
+ if (map >= num_map)
map = 0; /* Should never happen */
- if (nr >= 3) /* pwm channels 3..6 map to temp4..6 */
- map += 3;
+ if (nr >= num_map) /* pwm channels 3..6 map to temp4..6 */
+ map += num_map;
return sprintf(buf, "%d\n", (int)BIT(map));
}
@@ -1714,6 +1784,7 @@ static ssize_t set_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = dev_get_drvdata(dev);
int nr = sensor_attr->index;
+ u8 num_map = data->num_temp_map;
long val;
int err;
u8 reg;
@@ -1721,8 +1792,8 @@ static ssize_t set_pwm_temp_map(struct device *dev,
if (kstrtol(buf, 10, &val) < 0)
return -EINVAL;
- if (nr >= 3)
- val -= 3;
+ if (nr >= num_map)
+ val -= num_map;
switch (val) {
case BIT(0):
@@ -2376,7 +2447,7 @@ static umode_t it87_temp_is_visible(struct kobject *kobj,
return attr->mode;
}
- if (a == 5 && !has_temp_offset(data))
+ if (a == 5 && i >= data->num_temp_offset)
return 0;
if (a == 6 && !data->has_beep)
@@ -3268,7 +3339,7 @@ static void it87_check_limit_regs(struct it87_data *data)
if (reg == 0xff)
it87_write_value(data, IT87_REG_VIN_MIN(i), 0);
}
- for (i = 0; i < NUM_TEMP_LIMIT; i++) {
+ for (i = 0; i < data->num_temp_limit; i++) {
reg = it87_read_value(data, IT87_REG_TEMP_HIGH(i));
if (reg == 0xff)
it87_write_value(data, IT87_REG_TEMP_HIGH(i), 127);
@@ -3461,6 +3532,7 @@ static int it87_probe(struct platform_device *pdev)
struct resource *res;
struct device *dev = &pdev->dev;
struct it87_sio_data *sio_data = dev_get_platdata(dev);
+ const struct it87_devices *chip;
int enable_pwm_interface;
struct device *hwmon_dev;
int err;
@@ -3483,9 +3555,13 @@ static int it87_probe(struct platform_device *pdev)
data->type = sio_data->type;
data->smbus_bitmap = sio_data->smbus_bitmap;
data->ec_special_config = sio_data->ec_special_config;
- data->features = it87_devices[sio_data->type].features;
- data->peci_mask = it87_devices[sio_data->type].peci_mask;
- data->old_peci_mask = it87_devices[sio_data->type].old_peci_mask;
+ chip = &it87_devices[sio_data->type];
+ data->features = chip->features;
+ data->peci_mask = chip->peci_mask;
+ data->old_peci_mask = chip->old_peci_mask;
+ data->num_temp_limit = chip->num_temp_limit;
+ data->num_temp_offset = chip->num_temp_offset;
+ data->num_temp_map = chip->num_temp_map;
/*
* IT8705F Datasheet 0.4.1, 3h == Version G.
* IT8712F Datasheet 0.9.1, section 8.3.5 indicates 8h == Version J.
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH v6 1/3] hwmon: it87: describe per-chip temperature resources
2026-08-29 21:00 ` [PATCH v6 1/3] hwmon: it87: describe per-chip temperature resources Jerome Tollet
@ 2026-08-29 21:17 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-29 21:17 UTC (permalink / raw)
To: Jerome Tollet; +Cc: linux-hwmon
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The driver queries extended temperature limits and offsets for channels 4-6 from the hardware but fails to expose them in sysfs, making the newly added feature inaccessible to userspace.
--
commit a00b7b5308a4440e2558cdfeab430801514fd5ce
Author: benoit.masson <yahoo@perenite.com>
hwmon: it87: describe per-chip temperature resources
Add per-chip temp limit/offset/map counts and wire the driver
to use them.
This keeps existing chips on the previous defaults while allowing newer
chips to advertise larger resources.
> diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
> index 87edb1b6048bb..1af815b5d33a9 100644
> --- a/drivers/hwmon/it87.c
> +++ b/drivers/hwmon/it87.c
[ ... ]
> @@ -379,18 +397,24 @@ static const struct it87_devices it87_devices[] = {
> .name = "it8728",
> .model = "IT8728F",
> .features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
> - | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
> + | FEAT_TEMP_PECI | FEAT_FIVE_FANS
> | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
> | FEAT_FANCTL_ONOFF,
> + .num_temp_limit = 6,
[Severity: Medium]
With num_temp_limit updated to 6 for newer chips to support reading
temperature limits and offsets for channels 4-6 from the hardware, do these
new attributes need to be exposed to userspace?
The driver's sysfs mapping array statically lacks limit and offset
attributes for temp4-6:
drivers/hwmon/it87.c:it87_attributes_temp[] {
...
&sensor_dev_attr_temp4_input.dev_attr.attr, /* 21 */
&sensor_dev_attr_temp5_input.dev_attr.attr,
&sensor_dev_attr_temp6_input.dev_attr.attr,
NULL
}
Additionally, the hardcoded logic in it87_temp_is_visible() restricts these
attributes:
drivers/hwmon/it87.c:it87_temp_is_visible() {
...
if (index >= 21) {
i = index - 21 + 3;
a = 0;
}
...
}
This logic forces a = 0 (input) for all attributes beyond index 21, which
structurally prevents the visibility of min, max, or offset attributes for
temp 4-6 even if they were appended to the array.
Does the sysfs attribute mapping and visibility logic need to be updated so
that userspace can access the newly queried hardware limits?
> + .num_temp_offset = 3,
> + .num_temp_map = 3,
> .peci_mask = 0x07,
> },
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260829210036.61136-1-jerome.tollet@gmail.com?part=1
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v6 2/3] hwmon: it87: prepare for extended PWM temp maps
2026-08-29 21:00 ` [PATCH v6 0/3] " Jerome Tollet
2026-08-29 21:00 ` [PATCH v6 1/3] hwmon: it87: describe per-chip temperature resources Jerome Tollet
@ 2026-08-29 21:00 ` Jerome Tollet
2026-08-29 21:14 ` sashiko-bot
2026-08-29 21:00 ` [PATCH v6 3/3] hwmon: it87: add IT8613E support Jerome Tollet
` (2 subsequent siblings)
4 siblings, 1 reply; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 21:00 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck; +Cc: linux-hwmon, Benoit Masson, Jerome Tollet
From: "benoit.masson" <yahoo@perenite.com>
Introduce helper logic for PWM-to-temperature mappings so newer register
layouts can be supported while retaining the legacy two groups of three
temperature sources.
Honor the four global temperature sources on IT8603E and IT8622E instead
of applying the legacy grouping to those chips. Use per-chip masks and
shifts for newer extended mappings.
Newer controllers keep the duty cycle in a separate register, so write
their temperature mapping in both manual and automatic mode. This keeps
the selected mapping across cache refreshes and mode changes. Continue to
defer mapping writes on older controllers while they are in manual mode.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
drivers/hwmon/it87.c | 221 ++++++++++++++++++++++++++++++++-----------
1 file changed, 167 insertions(+), 54 deletions(-)
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index 1af815b5d..980e425fc 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -252,6 +252,7 @@ static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59, 0x5a, 0x90, 0x91 };
#define IT87_REG_FAN_MAIN_CTRL 0x13
#define IT87_REG_FAN_CTL 0x14
static const u8 IT87_REG_PWM[] = { 0x15, 0x16, 0x17, 0x7f, 0xa7, 0xaf };
+static const u8 IT87_REG_PWM_8665[] = { 0x15, 0x16, 0x17, 0x1e, 0x1f, 0x92 };
static const u8 IT87_REG_PWM_DUTY[] = { 0x63, 0x6b, 0x73, 0x7b, 0xa3, 0xab };
static const u8 IT87_REG_VIN[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
@@ -281,6 +282,7 @@ static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 };
#define NUM_VIN ARRAY_SIZE(IT87_REG_VIN)
#define NUM_VIN_LIMIT 8
#define NUM_TEMP 6
+#define IT87_PWM_OLD_NUM_TEMP 3
#define NUM_FAN ARRAY_SIZE(IT87_REG_FAN)
#define NUM_FAN_DIV 3
#define NUM_PWM ARRAY_SIZE(IT87_REG_PWM)
@@ -290,6 +292,7 @@ struct it87_devices {
const char *name;
const char * const model;
u32 features;
+ const u8 *reg_pwm;
u8 num_temp_limit;
u8 num_temp_offset;
u8 num_temp_map;
@@ -328,6 +331,7 @@ struct it87_devices {
#define FEAT_FOUR_PWM BIT(21) /* Supports four fan controls */
#define FEAT_FOUR_TEMP BIT(22)
#define FEAT_FANCTL_ONOFF BIT(23) /* chip has FAN_CTL ON/OFF */
+#define FEAT_NEW_TEMPMAP BIT(24) /* PWM uses extended temp map */
static const struct it87_devices it87_devices[] = {
[it87] = {
@@ -335,6 +339,7 @@ static const struct it87_devices it87_devices[] = {
.model = "IT87F",
.features = FEAT_OLD_AUTOPWM | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 0,
.num_temp_map = 3,
@@ -344,6 +349,7 @@ static const struct it87_devices it87_devices[] = {
.model = "IT8712F",
.features = FEAT_OLD_AUTOPWM | FEAT_VID | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 0,
.num_temp_map = 3,
@@ -354,6 +360,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -364,6 +371,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -375,6 +383,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -387,6 +396,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -400,6 +410,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 6,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -412,6 +423,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FOUR_FANS
| FEAT_FOUR_PWM | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -428,6 +440,7 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (OHM) */
/* 16 bit fans (OHM) */
/* three fans, always 16 bit (guesswork) */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -443,6 +456,7 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (HWSensors4, OHM) */
/* 16 bit fans (HWSensors4, OHM) */
/* three fans, always 16 bit (datasheet) */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -454,6 +468,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -465,6 +480,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -476,6 +492,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -487,6 +504,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -498,6 +516,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_NOCONF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -510,6 +529,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -522,9 +542,10 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_AVCC3 | FEAT_PWM_FREQ2,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
- .num_temp_map = 3,
+ .num_temp_map = 4,
.peci_mask = 0x07,
},
[it8620] = {
@@ -534,6 +555,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -546,9 +568,10 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_AVCC3 | FEAT_VIN3_5V | FEAT_FOUR_TEMP,
+ .reg_pwm = IT87_REG_PWM_8665,
.num_temp_limit = 3,
.num_temp_offset = 3,
- .num_temp_map = 3,
+ .num_temp_map = 4,
.peci_mask = 0x0f,
.smbus_bitmap = BIT(1) | BIT(2),
},
@@ -559,6 +582,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 6,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -571,6 +595,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_SIX_FANS | FEAT_IN7_INTERNAL
| FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -583,6 +608,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_limit = 3,
.num_temp_offset = 3,
.num_temp_map = 3,
@@ -625,6 +651,7 @@ static const struct it87_devices it87_devices[] = {
#define has_scaling(data) ((data)->features & (FEAT_12MV_ADC | \
FEAT_10_9MV_ADC))
#define has_fanctl_onoff(data) ((data)->features & FEAT_FANCTL_ONOFF)
+#define has_new_tempmap(data) ((data)->features & FEAT_NEW_TEMPMAP)
struct it87_sio_data {
int sioaddr;
@@ -654,6 +681,7 @@ struct it87_data {
int sioaddr;
enum chips type;
u32 features;
+ const u8 *reg_pwm;
u8 num_temp_limit;
u8 num_temp_offset;
u8 num_temp_map;
@@ -703,7 +731,9 @@ struct it87_data {
u8 has_pwm; /* Bitfield, pwm control enabled */
u8 pwm_ctrl[NUM_PWM]; /* Register value */
u8 pwm_duty[NUM_PWM]; /* Manual PWM value set by user */
- u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping (bits 1-0) */
+ u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping */
+ u8 pwm_temp_map_mask;
+ u8 pwm_temp_map_shift;
/* Automatic fan speed control registers */
u8 auto_pwm[NUM_AUTO_PWM][4]; /* [nr][3] is hard-coded */
@@ -785,6 +815,77 @@ static int pwm_from_reg(const struct it87_data *data, u8 reg)
return (reg & 0x7f) << 1;
}
+static inline u8 pwm_temp_map_get(const struct it87_data *data, u8 ctrl)
+{
+ return (ctrl >> data->pwm_temp_map_shift) &
+ data->pwm_temp_map_mask;
+}
+
+static inline u8 pwm_temp_map_set(const struct it87_data *data, u8 ctrl,
+ u8 map)
+{
+ ctrl &= ~(data->pwm_temp_map_mask << data->pwm_temp_map_shift);
+ return ctrl | ((map & data->pwm_temp_map_mask)
+ << data->pwm_temp_map_shift);
+}
+
+static inline u8 pwm_num_temp_map(const struct it87_data *data)
+{
+ return data->num_temp_map;
+}
+
+static inline bool uses_global_temp_map(const struct it87_data *data)
+{
+ return has_new_tempmap(data) ||
+ pwm_num_temp_map(data) != IT87_PWM_OLD_NUM_TEMP;
+}
+
+static unsigned int pwm_temp_channel(const struct it87_data *data,
+ int nr, u8 map)
+{
+ if (uses_global_temp_map(data)) {
+ u8 num = pwm_num_temp_map(data);
+
+ if (map >= num)
+ map = 0;
+ return map;
+ }
+
+ if (map >= IT87_PWM_OLD_NUM_TEMP)
+ map = 0;
+
+ if (nr >= IT87_PWM_OLD_NUM_TEMP)
+ map += IT87_PWM_OLD_NUM_TEMP;
+
+ return map;
+}
+
+static int pwm_temp_map_from_channel(const struct it87_data *data, int nr,
+ unsigned int channel, u8 *map)
+{
+ if (uses_global_temp_map(data)) {
+ u8 num = pwm_num_temp_map(data);
+
+ if (channel >= num)
+ return -EINVAL;
+ *map = channel;
+ return 0;
+ }
+
+ if (nr >= IT87_PWM_OLD_NUM_TEMP) {
+ if (channel < IT87_PWM_OLD_NUM_TEMP ||
+ channel >= 2 * IT87_PWM_OLD_NUM_TEMP)
+ return -EINVAL;
+ channel -= IT87_PWM_OLD_NUM_TEMP;
+ } else {
+ if (channel >= IT87_PWM_OLD_NUM_TEMP)
+ return -EINVAL;
+ }
+
+ *map = channel;
+ return 0;
+}
+
static int DIV_TO_REG(int val)
{
int answer = 0;
@@ -796,6 +897,11 @@ static int DIV_TO_REG(int val)
#define DIV_FROM_REG(val) BIT(val)
+static inline u16 it87_reg_pwm(const struct it87_data *data, int nr)
+{
+ return data->reg_pwm[nr];
+}
+
/*
* PWM base frequencies. The frequency has to be divided by either 128 or 256,
* depending on the chip type, to calculate the actual PWM frequency.
@@ -876,16 +982,23 @@ static void it87_write_value(struct it87_data *data, u8 reg, u8 value)
static void it87_update_pwm_ctrl(struct it87_data *data, int nr)
{
- data->pwm_ctrl[nr] = it87_read_value(data, IT87_REG_PWM[nr]);
+ data->pwm_ctrl[nr] = it87_read_value(data, it87_reg_pwm(data, nr));
if (has_newer_autopwm(data)) {
- data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
+ data->pwm_temp_map[nr] =
+ pwm_temp_map_get(data, data->pwm_ctrl[nr]);
+ if (uses_global_temp_map(data) &&
+ data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
+ data->pwm_temp_map[nr] = 0;
data->pwm_duty[nr] = it87_read_value(data,
IT87_REG_PWM_DUTY[nr]);
- } else {
- if (data->pwm_ctrl[nr] & 0x80) /* Automatic mode */
- data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
- else /* Manual mode */
- data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;
+ } else if (data->pwm_ctrl[nr] & 0x80) { /* Automatic mode */
+ data->pwm_temp_map[nr] =
+ pwm_temp_map_get(data, data->pwm_ctrl[nr]);
+ if (uses_global_temp_map(data) &&
+ data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
+ data->pwm_temp_map[nr] = 0;
+ } else { /* Manual mode */
+ data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;
}
if (has_old_autopwm(data)) {
@@ -1635,27 +1748,30 @@ static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr,
data->pwm_duty[nr]);
/* and set manual mode */
if (has_newer_autopwm(data)) {
- ctrl = (data->pwm_ctrl[nr] & 0x7c) |
- data->pwm_temp_map[nr];
+ ctrl = pwm_temp_map_set(data,
+ data->pwm_ctrl[nr] &
+ ~0x80,
+ data->pwm_temp_map[nr]);
} else {
ctrl = data->pwm_duty[nr];
}
data->pwm_ctrl[nr] = ctrl;
- it87_write_value(data, IT87_REG_PWM[nr], ctrl);
+ it87_write_value(data, it87_reg_pwm(data, nr), ctrl);
}
} else {
u8 ctrl;
if (has_newer_autopwm(data)) {
- ctrl = (data->pwm_ctrl[nr] & 0x7c) |
- data->pwm_temp_map[nr];
+ ctrl = pwm_temp_map_set(data,
+ data->pwm_ctrl[nr] & ~0x80,
+ data->pwm_temp_map[nr]);
if (val != 1)
ctrl |= 0x80;
} else {
ctrl = (val == 1 ? data->pwm_duty[nr] : 0x80);
}
data->pwm_ctrl[nr] = ctrl;
- it87_write_value(data, IT87_REG_PWM[nr], ctrl);
+ it87_write_value(data, it87_reg_pwm(data, nr), ctrl);
if (has_fanctl_onoff(data) && nr < 3) {
/* set SmartGuardian mode */
@@ -1706,7 +1822,7 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
*/
if (!(data->pwm_ctrl[nr] & 0x80)) {
data->pwm_ctrl[nr] = data->pwm_duty[nr];
- it87_write_value(data, IT87_REG_PWM[nr],
+ it87_write_value(data, it87_reg_pwm(data, nr),
data->pwm_ctrl[nr]);
}
}
@@ -1761,20 +1877,14 @@ static ssize_t show_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = it87_update_device(dev);
int nr = sensor_attr->index;
- u8 num_map;
- int map;
+ unsigned int channel;
if (IS_ERR(data))
return PTR_ERR(data);
- num_map = data->num_temp_map;
- map = data->pwm_temp_map[nr];
- if (map >= num_map)
- map = 0; /* Should never happen */
- if (nr >= num_map) /* pwm channels 3..6 map to temp4..6 */
- map += num_map;
+ channel = pwm_temp_channel(data, nr, data->pwm_temp_map[nr]);
- return sprintf(buf, "%d\n", (int)BIT(map));
+ return sprintf(buf, "%d\n", (int)BIT(channel));
}
static ssize_t set_pwm_temp_map(struct device *dev,
@@ -1784,45 +1894,35 @@ static ssize_t set_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = dev_get_drvdata(dev);
int nr = sensor_attr->index;
- u8 num_map = data->num_temp_map;
long val;
int err;
- u8 reg;
+ unsigned int channel;
+ u8 map;
- if (kstrtol(buf, 10, &val) < 0)
+ if (kstrtol(buf, 10, &val) < 0 || val <= 0 || !is_power_of_2(val))
return -EINVAL;
- if (nr >= num_map)
- val -= num_map;
-
- switch (val) {
- case BIT(0):
- reg = 0x00;
- break;
- case BIT(1):
- reg = 0x01;
- break;
- case BIT(2):
- reg = 0x02;
- break;
- default:
+ channel = __ffs(val);
+ if (pwm_temp_map_from_channel(data, nr, channel, &map))
return -EINVAL;
- }
err = it87_lock(data);
if (err)
return err;
it87_update_pwm_ctrl(data, nr);
- data->pwm_temp_map[nr] = reg;
+ data->pwm_temp_map[nr] = map;
/*
- * If we are in automatic mode, write the temp mapping immediately;
- * otherwise, just store it for later use.
+ * Newer controllers keep the duty cycle in a separate register, so
+ * their temperature mapping can be updated in any mode. On older
+ * controllers, defer the update until automatic mode is enabled.
*/
- if (data->pwm_ctrl[nr] & 0x80) {
- data->pwm_ctrl[nr] = (data->pwm_ctrl[nr] & 0xfc) |
- data->pwm_temp_map[nr];
- it87_write_value(data, IT87_REG_PWM[nr], data->pwm_ctrl[nr]);
+ if (has_newer_autopwm(data) || (data->pwm_ctrl[nr] & 0x80)) {
+ data->pwm_ctrl[nr] = pwm_temp_map_set(data,
+ data->pwm_ctrl[nr],
+ data->pwm_temp_map[nr]);
+ it87_write_value(data, it87_reg_pwm(data, nr),
+ data->pwm_ctrl[nr]);
}
it87_unlock(data);
return count;
@@ -3421,7 +3521,10 @@ static void it87_init_device(struct platform_device *pdev)
* manual duty cycle.
*/
for (i = 0; i < NUM_AUTO_PWM; i++) {
- data->pwm_temp_map[i] = i;
+ if (uses_global_temp_map(data))
+ data->pwm_temp_map[i] = 0;
+ else
+ data->pwm_temp_map[i] = i % IT87_PWM_OLD_NUM_TEMP;
data->pwm_duty[i] = 0x7f; /* Full speed */
data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */
}
@@ -3493,7 +3596,8 @@ static int it87_check_pwm(struct device *dev)
for (i = 0; i < ARRAY_SIZE(pwm); i++)
pwm[i] = it87_read_value(data,
- IT87_REG_PWM[i]);
+ it87_reg_pwm(data,
+ i));
/*
* If any fan is in automatic pwm mode, the polarity
@@ -3508,7 +3612,8 @@ static int it87_check_pwm(struct device *dev)
tmp | 0x87);
for (i = 0; i < 3; i++)
it87_write_value(data,
- IT87_REG_PWM[i],
+ it87_reg_pwm(data,
+ i),
0x7f & ~pwm[i]);
return 1;
}
@@ -3557,11 +3662,19 @@ static int it87_probe(struct platform_device *pdev)
data->ec_special_config = sio_data->ec_special_config;
chip = &it87_devices[sio_data->type];
data->features = chip->features;
+ data->reg_pwm = chip->reg_pwm;
data->peci_mask = chip->peci_mask;
data->old_peci_mask = chip->old_peci_mask;
data->num_temp_limit = chip->num_temp_limit;
data->num_temp_offset = chip->num_temp_offset;
data->num_temp_map = chip->num_temp_map;
+ if (has_new_tempmap(data)) {
+ data->pwm_temp_map_mask = 0x07;
+ data->pwm_temp_map_shift = 3;
+ } else {
+ data->pwm_temp_map_mask = 0x03;
+ data->pwm_temp_map_shift = 0;
+ }
/*
* IT8705F Datasheet 0.4.1, 3h == Version G.
* IT8712F Datasheet 0.9.1, section 8.3.5 indicates 8h == Version J.
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH v6 2/3] hwmon: it87: prepare for extended PWM temp maps
2026-08-29 21:00 ` [PATCH v6 2/3] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
@ 2026-08-29 21:14 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-29 21:14 UTC (permalink / raw)
To: Jerome Tollet; +Cc: linux-hwmon
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] it87_check_pwm corrupts the PWM control register on newer controllers when fixing broken BIOS polarity.
- [High] User-configured temperature mappings are silently discarded when switching legacy controllers to automatic PWM mode.
--
commit e4890144d7762af5309a78a9545773b57261f124
Author: benoit.masson <yahoo@perenite.com>
hwmon: it87: prepare for extended PWM temp maps
Introduce helper logic for PWM-to-temperature mappings so newer register
layouts can be supported while retaining the legacy two groups of three
temperature sources.
> diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
> index 1af815b5d33a9..980e425fcc28b 100644
> --- a/drivers/hwmon/it87.c
> +++ b/drivers/hwmon/it87.c
[ ... ]
> @@ -1635,27 +1748,30 @@ static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr,
[ ... ]
> } else {
> u8 ctrl;
>
> if (has_newer_autopwm(data)) {
> - ctrl = (data->pwm_ctrl[nr] & 0x7c) |
> - data->pwm_temp_map[nr];
> + ctrl = pwm_temp_map_set(data,
> + data->pwm_ctrl[nr] & ~0x80,
> + data->pwm_temp_map[nr]);
> if (val != 1)
> ctrl |= 0x80;
> } else {
> ctrl = (val == 1 ? data->pwm_duty[nr] : 0x80);
> }
> data->pwm_ctrl[nr] = ctrl;
> - it87_write_value(data, IT87_REG_PWM[nr], ctrl);
> + it87_write_value(data, it87_reg_pwm(data, nr), ctrl);
[Severity: High]
This is a pre-existing issue, but does switching legacy controllers to
automatic pwm mode permanently wipe out the user's deferred temperature
mapping?
When set_pwm_enable() executes the legacy controller branch for automatic
mode (when val is 2), it unconditionally sets ctrl to 0x80. Writing this
value to the pwm control register clears bits 0-1, which discards any
user-configured temperature mappings previously deferred in
data->pwm_temp_map[nr]. Can this logic error lead to fans monitoring the
wrong temperature sensor (defaulting to 0) and potentially causing
hardware overheating?
[ ... ]
> @@ -3493,7 +3596,8 @@ static int it87_check_pwm(struct device *dev)
[ ... ]
> if (!((pwm[0] | pwm[1] | pwm[2]) & 0x80)) {
> dev_info(dev,
> "Reconfiguring PWM to active high polarity\n");
> it87_write_value(data, IT87_REG_FAN_CTL,
> tmp | 0x87);
> for (i = 0; i < 3; i++)
> it87_write_value(data,
> - IT87_REG_PWM[i],
> + it87_reg_pwm(data,
> + i),
> 0x7f & ~pwm[i]);
[Severity: High]
This isn't a bug introduced by this patch, but does this write corrupt
the temperature mapping on newer controllers?
When fixing broken BIOS polarity in it87_check_pwm(), writing 0x7f & ~pwm[i]
inverts the manual duty cycle bits for older controllers. However, on newer
controllers the duty cycle is in a separate register, and the pwm control
register instead holds the temperature mapping. Does inverting these bits
corrupt the temperature mapping and cause incorrect fan behavior?
> return 1;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260829210036.61136-1-jerome.tollet@gmail.com?part=2
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v6 3/3] hwmon: it87: add IT8613E support
2026-08-29 21:00 ` [PATCH v6 0/3] " Jerome Tollet
2026-08-29 21:00 ` [PATCH v6 1/3] hwmon: it87: describe per-chip temperature resources Jerome Tollet
2026-08-29 21:00 ` [PATCH v6 2/3] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
@ 2026-08-29 21:00 ` Jerome Tollet
2026-08-29 21:15 ` sashiko-bot
2026-08-29 22:01 ` [PATCH v7 0/3] " Jerome Tollet
2026-08-29 22:53 ` [PATCH v6 0/3] " Guenter Roeck
4 siblings, 1 reply; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 21:00 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck; +Cc: linux-hwmon, Benoit Masson, Jerome Tollet
From: "benoit.masson" <yahoo@perenite.com>
Teach the Super I/O probe path to recognize IT8613E and add its hardware
monitoring configuration.
Add feature flags, temperature resource counts, 11 mV ADC scaling,
the IT8665-style PWM register map, and GPIO pin-mux checks. Only three
temperature inputs are currently known; the six limit, offset, and PWM
map resources do not imply six temperature inputs. Document the chip in
the hwmon guide.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
Documentation/hwmon/it87.rst | 8 +++++
drivers/hwmon/it87.c | 65 ++++++++++++++++++++++++++++++++++--
2 files changed, 71 insertions(+), 2 deletions(-)
diff --git a/Documentation/hwmon/it87.rst b/Documentation/hwmon/it87.rst
index fc1c90b02..c33ba8a07 100644
--- a/Documentation/hwmon/it87.rst
+++ b/Documentation/hwmon/it87.rst
@@ -11,6 +11,14 @@ Supported chips:
Datasheet: Not publicly available
+ * IT8613E
+
+ Prefix: 'it8613'
+
+ Addresses scanned: from Super I/O config space (8 I/O ports)
+
+ Datasheet: Not publicly available
+
* IT8620E
Prefix: 'it8620'
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index 980e425fc..376d20e72 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -36,6 +36,7 @@
* IT8790E Super I/O chip w/LPC interface
* IT8792E Super I/O chip w/LPC interface
* IT87952E Super I/O chip w/LPC interface
+ * IT8613E Super I/O chip w/LPC interface
* Sis950 A clone of the IT8705F
*
* Copyright (C) 2001 Chris Gauthron
@@ -65,7 +66,7 @@
enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732,
it8771, it8772, it8781, it8782, it8783, it8786, it8790,
- it8792, it8603, it8620, it8622, it8628, it8689, it87952 };
+ it8792, it8603, it8613, it8620, it8622, it8628, it8689, it87952 };
static struct platform_device *it87_pdev[2];
@@ -159,6 +160,7 @@ static inline void superio_exit(int ioreg, bool noexit)
#define IT8786E_DEVID 0x8786
#define IT8790E_DEVID 0x8790
#define IT8603E_DEVID 0x8603
+#define IT8613E_DEVID 0x8613
#define IT8620E_DEVID 0x8620
#define IT8622E_DEVID 0x8622
#define IT8623E_DEVID 0x8623
@@ -332,6 +334,7 @@ struct it87_devices {
#define FEAT_FOUR_TEMP BIT(22)
#define FEAT_FANCTL_ONOFF BIT(23) /* chip has FAN_CTL ON/OFF */
#define FEAT_NEW_TEMPMAP BIT(24) /* PWM uses extended temp map */
+#define FEAT_11MV_ADC BIT(25)
static const struct it87_devices it87_devices[] = {
[it87] = {
@@ -548,6 +551,20 @@ static const struct it87_devices it87_devices[] = {
.num_temp_map = 4,
.peci_mask = 0x07,
},
+ [it8613] = {
+ .name = "it8613",
+ .model = "IT8613E",
+ /* Only three temperature inputs are currently known. */
+ .features = FEAT_NEWER_AUTOPWM | FEAT_11MV_ADC | FEAT_16BIT_FANS
+ | FEAT_TEMP_PECI | FEAT_FIVE_FANS
+ | FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
+ | FEAT_AVCC3 | FEAT_NEW_TEMPMAP,
+ .reg_pwm = IT87_REG_PWM_8665,
+ .num_temp_limit = 6,
+ .num_temp_offset = 6,
+ .num_temp_map = 6,
+ .peci_mask = 0x07,
+ },
[it8620] = {
.name = "it8620",
.model = "IT8620E",
@@ -620,6 +637,7 @@ static const struct it87_devices it87_devices[] = {
#define has_16bit_fans(data) ((data)->features & FEAT_16BIT_FANS)
#define has_12mv_adc(data) ((data)->features & FEAT_12MV_ADC)
#define has_10_9mv_adc(data) ((data)->features & FEAT_10_9MV_ADC)
+#define has_11mv_adc(data) ((data)->features & FEAT_11MV_ADC)
#define has_newer_autopwm(data) ((data)->features & FEAT_NEWER_AUTOPWM)
#define has_old_autopwm(data) ((data)->features & FEAT_OLD_AUTOPWM)
#define has_temp_peci(data, nr) (((data)->features & FEAT_TEMP_PECI) && \
@@ -649,7 +667,8 @@ static const struct it87_devices it87_devices[] = {
#define has_vin3_5v(data) ((data)->features & FEAT_VIN3_5V)
#define has_noconf(data) ((data)->features & FEAT_NOCONF)
#define has_scaling(data) ((data)->features & (FEAT_12MV_ADC | \
- FEAT_10_9MV_ADC))
+ FEAT_10_9MV_ADC | \
+ FEAT_11MV_ADC))
#define has_fanctl_onoff(data) ((data)->features & FEAT_FANCTL_ONOFF)
#define has_new_tempmap(data) ((data)->features & FEAT_NEW_TEMPMAP)
@@ -756,6 +775,8 @@ static int adc_lsb(const struct it87_data *data, int nr)
lsb = 120;
else if (has_10_9mv_adc(data))
lsb = 109;
+ else if (has_11mv_adc(data))
+ lsb = 110;
else
lsb = 160;
if (data->in_scaled & BIT(nr))
@@ -2961,6 +2982,9 @@ static int __init it87_find(int sioaddr, unsigned short *address,
case IT8623E_DEVID:
sio_data->type = it8603;
break;
+ case IT8613E_DEVID:
+ sio_data->type = it8613;
+ break;
case IT8620E_DEVID:
sio_data->type = it8620;
break;
@@ -3138,6 +3162,43 @@ static int __init it87_find(int sioaddr, unsigned short *address,
sio_data->skip_in |= BIT(5); /* No VIN5 */
sio_data->skip_in |= BIT(6); /* No VIN6 */
+ sio_data->beep_pin = superio_inb(sioaddr,
+ IT87_SIO_BEEP_PIN_REG) & 0x3f;
+ } else if (sio_data->type == it8613) {
+ int reg27, reg29, reg2a;
+
+ superio_select(sioaddr, GPIO);
+
+ /* Check for pwm3, fan3, pwm5, fan5 */
+ reg27 = superio_inb(sioaddr, IT87_SIO_GPIO3_REG);
+ if (!(reg27 & BIT(1)))
+ sio_data->skip_fan |= BIT(4);
+ if (reg27 & BIT(3))
+ sio_data->skip_pwm |= BIT(4);
+ if (reg27 & BIT(6))
+ sio_data->skip_pwm |= BIT(2);
+ if (reg27 & BIT(7))
+ sio_data->skip_fan |= BIT(2);
+
+ /* Check for pwm2, fan2 */
+ reg29 = superio_inb(sioaddr, IT87_SIO_GPIO5_REG);
+ if (reg29 & BIT(1))
+ sio_data->skip_pwm |= BIT(1);
+ if (reg29 & BIT(2))
+ sio_data->skip_fan |= BIT(1);
+
+ /* Check for pwm4, fan4 */
+ reg2a = superio_inb(sioaddr, IT87_SIO_PINX1_REG);
+ if (!(reg2a & BIT(0)) || (reg29 & BIT(7))) {
+ sio_data->skip_fan |= BIT(3);
+ sio_data->skip_pwm |= BIT(3);
+ }
+
+ sio_data->skip_pwm |= BIT(0); /* No pwm1 */
+ sio_data->skip_fan |= BIT(0); /* No fan1 */
+ sio_data->skip_in |= BIT(3); /* No VIN3 */
+ sio_data->skip_in |= BIT(6); /* No VIN6 */
+
sio_data->beep_pin = superio_inb(sioaddr,
IT87_SIO_BEEP_PIN_REG) & 0x3f;
} else if (sio_data->type == it8620 || sio_data->type == it8628) {
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH v7 0/3] hwmon: it87: add IT8613E support
2026-08-29 21:00 ` [PATCH v6 0/3] " Jerome Tollet
` (2 preceding siblings ...)
2026-08-29 21:00 ` [PATCH v6 3/3] hwmon: it87: add IT8613E support Jerome Tollet
@ 2026-08-29 22:01 ` Jerome Tollet
2026-08-29 22:01 ` [PATCH v7 1/3] hwmon: it87: describe per-chip PWM temperature maps Jerome Tollet
` (2 more replies)
2026-08-29 22:53 ` [PATCH v6 0/3] " Guenter Roeck
4 siblings, 3 replies; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 22:01 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck; +Cc: linux-hwmon, Benoit Masson
This is a respin of Benoit Masson's v4 series, with his permission.
It replaces my fan-input-only RFC with the broader hardware monitoring
support requested in review.
The v4 series was dropped after a NULL pointer dereference was reported.
The IT8613E device entry did not initialize reg_pwm, while the probe path
unconditionally dereferences it. It also advertised six temperature
offsets but the register table contained only three entries.
Changes in v7:
- Keep the existing three temperature limit and offset resources. Drop
the unused per-chip limit and offset counts and the extended offset
register table, so the driver no longer accesses unexposed resources.
- Limit the first patch to describing per-chip PWM temperature maps.
- Apply a deferred legacy temperature mapping when switching from manual
to automatic PWM mode instead of clearing it.
- Leave the existing PWM polarity repair path unchanged. The first three
PWM control register addresses are common to the register maps used by
this series.
- Clarify that IT8613E has three known temperature inputs and six PWM
temperature mapping sources.
Tested on an IT8613E at 0xa30, revision 12. Every intermediate commit and
the complete series build as external modules with W=1. All three patches
pass checkpatch --strict with no warnings or errors.
The module exposes fan2_input and PWM2, PWM4, and PWM5 on this system.
The PWM2 temperature mapping was changed from 4 to 1 in manual mode. It
remained 1 after a cache refresh and after switching back to automatic
mode. The original mapping, duty, and automatic mode were then restored.
The PWM2 write path was tested by switching from automatic control to
manual full duty (255). Fan2 increased from 2636 RPM to 6553 RPM. The
original duty (65), automatic mode, and temperature mapping were restored.
An S3 suspend/resume cycle with an RTC wakeup completed successfully. The
PWM configuration and sensor readings were preserved, with no it87 or
hwmon warnings in the kernel log. The legacy mapping correction was
compile-tested but could not be exercised on this newer controller.
Link: https://lore.kernel.org/linux-hwmon/20260114221210.98071-1-yahoo@perenite.com/
Link: https://lore.kernel.org/linux-hwmon/20260829210036.61136-1-jerome.tollet@gmail.com/
benoit.masson (3):
hwmon: it87: describe per-chip PWM temperature maps
hwmon: it87: prepare for extended PWM temp maps
hwmon: it87: add IT8613E support
Documentation/hwmon/it87.rst | 8 +
drivers/hwmon/it87.c | 308 +++++++++++++++++++++++++++++------
2 files changed, 263 insertions(+), 53 deletions(-)
base-commit: 75f2c0b3690702c90863c2e138cb5520670845ea
--
2.55.0
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v7 1/3] hwmon: it87: describe per-chip PWM temperature maps
2026-08-29 22:01 ` [PATCH v7 0/3] " Jerome Tollet
@ 2026-08-29 22:01 ` Jerome Tollet
2026-08-29 22:08 ` sashiko-bot
2026-08-30 0:01 ` Guenter Roeck
2026-08-29 22:01 ` [PATCH v7 2/3] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
2026-08-29 22:01 ` [PATCH v7 3/3] hwmon: it87: add IT8613E support Jerome Tollet
2 siblings, 2 replies; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 22:01 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck; +Cc: linux-hwmon, Benoit Masson
From: "benoit.masson" <yahoo@perenite.com>
Add a per-chip count for PWM temperature mapping sources and use it
when reporting and validating mappings.
Keep existing chips on their previous three-source defaults. This
prepares the driver for chips with a different number of mapping sources.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
drivers/hwmon/it87.c | 46 ++++++++++++++++++++++++++++++++++++--------
1 file changed, 38 insertions(+), 8 deletions(-)
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index 87edb1b60..5cb1c0029 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -292,6 +292,7 @@ struct it87_devices {
const char *name;
const char * const model;
u32 features;
+ u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
u8 smbus_bitmap; /* SMBus enable bits in extra config register */
@@ -335,12 +336,14 @@ static const struct it87_devices it87_devices[] = {
.model = "IT87F",
.features = FEAT_OLD_AUTOPWM | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .num_temp_map = 3,
},
[it8712] = {
.name = "it8712",
.model = "IT8712F",
.features = FEAT_OLD_AUTOPWM | FEAT_VID | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .num_temp_map = 3,
},
[it8716] = {
.name = "it8716",
@@ -348,6 +351,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
},
[it8718] = {
.name = "it8718",
@@ -355,6 +359,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8720] = {
@@ -363,6 +368,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8721] = {
@@ -372,6 +378,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.peci_mask = 0x05,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -382,6 +389,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8732] = {
@@ -391,6 +399,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FOUR_FANS
| FEAT_FOUR_PWM | FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -404,6 +413,7 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (OHM) */
/* 16 bit fans (OHM) */
/* three fans, always 16 bit (guesswork) */
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8772] = {
@@ -416,6 +426,7 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (HWSensors4, OHM) */
/* 16 bit fans (HWSensors4, OHM) */
/* three fans, always 16 bit (datasheet) */
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8781] = {
@@ -424,6 +435,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8782] = {
@@ -432,6 +444,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8783] = {
@@ -440,6 +453,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.old_peci_mask = 0x4,
},
[it8786] = {
@@ -448,6 +462,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8790] = {
@@ -456,6 +471,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_NOCONF,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8792] = {
@@ -465,6 +481,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -474,6 +491,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_AVCC3 | FEAT_PWM_FREQ2,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8620] = {
@@ -483,6 +501,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8622] = {
@@ -492,6 +511,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_AVCC3 | FEAT_VIN3_5V | FEAT_FOUR_TEMP,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.smbus_bitmap = BIT(1) | BIT(2),
},
@@ -502,6 +522,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.peci_mask = 0x07,
},
[it8689] = {
@@ -511,6 +532,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_SIX_FANS | FEAT_IN7_INTERNAL
| FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3
| FEAT_FANCTL_ONOFF,
+ .num_temp_map = 3,
.smbus_bitmap = BIT(1) | BIT(2),
},
[it87952] = {
@@ -520,6 +542,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
},
@@ -589,6 +612,7 @@ struct it87_data {
int sioaddr;
enum chips type;
u32 features;
+ u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
@@ -1693,16 +1717,18 @@ static ssize_t show_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = it87_update_device(dev);
int nr = sensor_attr->index;
+ u8 num_map;
int map;
if (IS_ERR(data))
return PTR_ERR(data);
+ num_map = data->num_temp_map;
map = data->pwm_temp_map[nr];
- if (map >= 3)
+ if (map >= num_map)
map = 0; /* Should never happen */
- if (nr >= 3) /* pwm channels 3..6 map to temp4..6 */
- map += 3;
+ if (nr >= num_map) /* pwm channels 3..6 map to temp4..6 */
+ map += num_map;
return sprintf(buf, "%d\n", (int)BIT(map));
}
@@ -1714,6 +1740,7 @@ static ssize_t set_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = dev_get_drvdata(dev);
int nr = sensor_attr->index;
+ u8 num_map = data->num_temp_map;
long val;
int err;
u8 reg;
@@ -1721,8 +1748,8 @@ static ssize_t set_pwm_temp_map(struct device *dev,
if (kstrtol(buf, 10, &val) < 0)
return -EINVAL;
- if (nr >= 3)
- val -= 3;
+ if (nr >= num_map)
+ val -= num_map;
switch (val) {
case BIT(0):
@@ -3461,6 +3488,7 @@ static int it87_probe(struct platform_device *pdev)
struct resource *res;
struct device *dev = &pdev->dev;
struct it87_sio_data *sio_data = dev_get_platdata(dev);
+ const struct it87_devices *chip;
int enable_pwm_interface;
struct device *hwmon_dev;
int err;
@@ -3483,9 +3511,11 @@ static int it87_probe(struct platform_device *pdev)
data->type = sio_data->type;
data->smbus_bitmap = sio_data->smbus_bitmap;
data->ec_special_config = sio_data->ec_special_config;
- data->features = it87_devices[sio_data->type].features;
- data->peci_mask = it87_devices[sio_data->type].peci_mask;
- data->old_peci_mask = it87_devices[sio_data->type].old_peci_mask;
+ chip = &it87_devices[sio_data->type];
+ data->features = chip->features;
+ data->peci_mask = chip->peci_mask;
+ data->old_peci_mask = chip->old_peci_mask;
+ data->num_temp_map = chip->num_temp_map;
/*
* IT8705F Datasheet 0.4.1, 3h == Version G.
* IT8712F Datasheet 0.9.1, section 8.3.5 indicates 8h == Version J.
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH v7 2/3] hwmon: it87: prepare for extended PWM temp maps
2026-08-29 22:01 ` [PATCH v7 0/3] " Jerome Tollet
2026-08-29 22:01 ` [PATCH v7 1/3] hwmon: it87: describe per-chip PWM temperature maps Jerome Tollet
@ 2026-08-29 22:01 ` Jerome Tollet
2026-08-29 22:14 ` sashiko-bot
2026-08-30 0:01 ` Guenter Roeck
2026-08-29 22:01 ` [PATCH v7 3/3] hwmon: it87: add IT8613E support Jerome Tollet
2 siblings, 2 replies; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 22:01 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck; +Cc: linux-hwmon, Benoit Masson
From: "benoit.masson" <yahoo@perenite.com>
Introduce helper logic for PWM-to-temperature mappings so newer
register layouts can be supported while retaining the legacy two groups
of three temperature sources.
Honor the four global temperature sources on IT8603E and IT8622E instead
of applying the legacy grouping to those chips. Use per-chip masks and
shifts for newer extended mappings.
Newer controllers keep the duty cycle in a separate register, so write
their temperature mapping in both manual and automatic mode. This keeps
the selected mapping across cache refreshes and mode changes.
On older controllers, defer mapping writes while in manual mode and apply
the cached mapping when switching to automatic mode.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
drivers/hwmon/it87.c | 219 ++++++++++++++++++++++++++++++++-----------
1 file changed, 166 insertions(+), 53 deletions(-)
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index 5cb1c0029..232cc8586 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -252,6 +252,7 @@ static const u8 IT87_REG_TEMP_OFFSET[] = { 0x56, 0x57, 0x59 };
#define IT87_REG_FAN_MAIN_CTRL 0x13
#define IT87_REG_FAN_CTL 0x14
static const u8 IT87_REG_PWM[] = { 0x15, 0x16, 0x17, 0x7f, 0xa7, 0xaf };
+static const u8 IT87_REG_PWM_8665[] = { 0x15, 0x16, 0x17, 0x1e, 0x1f, 0x92 };
static const u8 IT87_REG_PWM_DUTY[] = { 0x63, 0x6b, 0x73, 0x7b, 0xa3, 0xab };
static const u8 IT87_REG_VIN[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
@@ -283,6 +284,7 @@ static const u8 IT87_REG_AUTO_BASE[] = { 0x60, 0x68, 0x70, 0x78, 0xa0, 0xa8 };
#define NUM_TEMP 6
#define NUM_TEMP_OFFSET ARRAY_SIZE(IT87_REG_TEMP_OFFSET)
#define NUM_TEMP_LIMIT 3
+#define IT87_PWM_OLD_NUM_TEMP 3
#define NUM_FAN ARRAY_SIZE(IT87_REG_FAN)
#define NUM_FAN_DIV 3
#define NUM_PWM ARRAY_SIZE(IT87_REG_PWM)
@@ -292,6 +294,7 @@ struct it87_devices {
const char *name;
const char * const model;
u32 features;
+ const u8 *reg_pwm;
u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
@@ -329,6 +332,7 @@ struct it87_devices {
#define FEAT_FOUR_PWM BIT(21) /* Supports four fan controls */
#define FEAT_FOUR_TEMP BIT(22)
#define FEAT_FANCTL_ONOFF BIT(23) /* chip has FAN_CTL ON/OFF */
+#define FEAT_NEW_TEMPMAP BIT(24) /* PWM uses extended temp map */
static const struct it87_devices it87_devices[] = {
[it87] = {
@@ -336,6 +340,7 @@ static const struct it87_devices it87_devices[] = {
.model = "IT87F",
.features = FEAT_OLD_AUTOPWM | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
},
[it8712] = {
@@ -343,6 +348,7 @@ static const struct it87_devices it87_devices[] = {
.model = "IT8712F",
.features = FEAT_OLD_AUTOPWM | FEAT_VID | FEAT_FANCTL_ONOFF,
/* may need to overwrite */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
},
[it8716] = {
@@ -351,6 +357,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
},
[it8718] = {
@@ -359,6 +366,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
@@ -368,6 +376,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET | FEAT_VID
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_FIVE_FANS
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
@@ -378,6 +387,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_FAN16_CONFIG | FEAT_FIVE_FANS | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x05,
.old_peci_mask = 0x02, /* Actually reports PCH */
@@ -389,6 +399,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
@@ -399,6 +410,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FOUR_FANS
| FEAT_FOUR_PWM | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
@@ -413,6 +425,7 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (OHM) */
/* 16 bit fans (OHM) */
/* three fans, always 16 bit (guesswork) */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
@@ -426,6 +439,7 @@ static const struct it87_devices it87_devices[] = {
/* 12mV ADC (HWSensors4, OHM) */
/* 16 bit fans (HWSensors4, OHM) */
/* three fans, always 16 bit (datasheet) */
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
@@ -435,6 +449,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
@@ -444,6 +459,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
@@ -453,6 +469,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_16BIT_FANS | FEAT_TEMP_OFFSET
| FEAT_TEMP_OLD_PECI | FEAT_FAN16_CONFIG | FEAT_PWM_FREQ2
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.old_peci_mask = 0x4,
},
@@ -462,6 +479,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
@@ -471,6 +489,7 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_PWM_FREQ2 | FEAT_FANCTL_ONOFF | FEAT_NOCONF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
@@ -481,6 +500,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
@@ -491,7 +511,8 @@ static const struct it87_devices it87_devices[] = {
.features = FEAT_NEWER_AUTOPWM | FEAT_12MV_ADC | FEAT_16BIT_FANS
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_IN7_INTERNAL
| FEAT_AVCC3 | FEAT_PWM_FREQ2,
- .num_temp_map = 3,
+ .reg_pwm = IT87_REG_PWM,
+ .num_temp_map = 4,
.peci_mask = 0x07,
},
[it8620] = {
@@ -501,6 +522,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
@@ -511,7 +533,8 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
| FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
| FEAT_AVCC3 | FEAT_VIN3_5V | FEAT_FOUR_TEMP,
- .num_temp_map = 3,
+ .reg_pwm = IT87_REG_PWM_8665,
+ .num_temp_map = 4,
.peci_mask = 0x07,
.smbus_bitmap = BIT(1) | BIT(2),
},
@@ -522,6 +545,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_SIX_FANS
| FEAT_IN7_INTERNAL | FEAT_SIX_PWM | FEAT_PWM_FREQ2
| FEAT_SIX_TEMP | FEAT_VIN3_5V | FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
},
@@ -532,6 +556,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_SIX_FANS | FEAT_IN7_INTERNAL
| FEAT_SIX_PWM | FEAT_PWM_FREQ2 | FEAT_SIX_TEMP | FEAT_AVCC3
| FEAT_FANCTL_ONOFF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.smbus_bitmap = BIT(1) | BIT(2),
},
@@ -542,6 +567,7 @@ static const struct it87_devices it87_devices[] = {
| FEAT_TEMP_OFFSET | FEAT_TEMP_OLD_PECI | FEAT_TEMP_PECI
| FEAT_10_9MV_ADC | FEAT_IN7_INTERNAL | FEAT_FANCTL_ONOFF
| FEAT_NOCONF,
+ .reg_pwm = IT87_REG_PWM,
.num_temp_map = 3,
.peci_mask = 0x07,
.old_peci_mask = 0x02, /* Actually reports PCH */
@@ -583,6 +609,7 @@ static const struct it87_devices it87_devices[] = {
#define has_scaling(data) ((data)->features & (FEAT_12MV_ADC | \
FEAT_10_9MV_ADC))
#define has_fanctl_onoff(data) ((data)->features & FEAT_FANCTL_ONOFF)
+#define has_new_tempmap(data) ((data)->features & FEAT_NEW_TEMPMAP)
struct it87_sio_data {
int sioaddr;
@@ -612,6 +639,7 @@ struct it87_data {
int sioaddr;
enum chips type;
u32 features;
+ const u8 *reg_pwm;
u8 num_temp_map;
u8 peci_mask;
u8 old_peci_mask;
@@ -659,7 +687,9 @@ struct it87_data {
u8 has_pwm; /* Bitfield, pwm control enabled */
u8 pwm_ctrl[NUM_PWM]; /* Register value */
u8 pwm_duty[NUM_PWM]; /* Manual PWM value set by user */
- u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping (bits 1-0) */
+ u8 pwm_temp_map[NUM_PWM];/* PWM to temp. chan. mapping */
+ u8 pwm_temp_map_mask;
+ u8 pwm_temp_map_shift;
/* Automatic fan speed control registers */
u8 auto_pwm[NUM_AUTO_PWM][4]; /* [nr][3] is hard-coded */
@@ -741,6 +771,77 @@ static int pwm_from_reg(const struct it87_data *data, u8 reg)
return (reg & 0x7f) << 1;
}
+static inline u8 pwm_temp_map_get(const struct it87_data *data, u8 ctrl)
+{
+ return (ctrl >> data->pwm_temp_map_shift) &
+ data->pwm_temp_map_mask;
+}
+
+static inline u8 pwm_temp_map_set(const struct it87_data *data, u8 ctrl,
+ u8 map)
+{
+ ctrl &= ~(data->pwm_temp_map_mask << data->pwm_temp_map_shift);
+ return ctrl | ((map & data->pwm_temp_map_mask)
+ << data->pwm_temp_map_shift);
+}
+
+static inline u8 pwm_num_temp_map(const struct it87_data *data)
+{
+ return data->num_temp_map;
+}
+
+static inline bool uses_global_temp_map(const struct it87_data *data)
+{
+ return has_new_tempmap(data) ||
+ pwm_num_temp_map(data) != IT87_PWM_OLD_NUM_TEMP;
+}
+
+static unsigned int pwm_temp_channel(const struct it87_data *data,
+ int nr, u8 map)
+{
+ if (uses_global_temp_map(data)) {
+ u8 num = pwm_num_temp_map(data);
+
+ if (map >= num)
+ map = 0;
+ return map;
+ }
+
+ if (map >= IT87_PWM_OLD_NUM_TEMP)
+ map = 0;
+
+ if (nr >= IT87_PWM_OLD_NUM_TEMP)
+ map += IT87_PWM_OLD_NUM_TEMP;
+
+ return map;
+}
+
+static int pwm_temp_map_from_channel(const struct it87_data *data, int nr,
+ unsigned int channel, u8 *map)
+{
+ if (uses_global_temp_map(data)) {
+ u8 num = pwm_num_temp_map(data);
+
+ if (channel >= num)
+ return -EINVAL;
+ *map = channel;
+ return 0;
+ }
+
+ if (nr >= IT87_PWM_OLD_NUM_TEMP) {
+ if (channel < IT87_PWM_OLD_NUM_TEMP ||
+ channel >= 2 * IT87_PWM_OLD_NUM_TEMP)
+ return -EINVAL;
+ channel -= IT87_PWM_OLD_NUM_TEMP;
+ } else {
+ if (channel >= IT87_PWM_OLD_NUM_TEMP)
+ return -EINVAL;
+ }
+
+ *map = channel;
+ return 0;
+}
+
static int DIV_TO_REG(int val)
{
int answer = 0;
@@ -752,6 +853,11 @@ static int DIV_TO_REG(int val)
#define DIV_FROM_REG(val) BIT(val)
+static inline u16 it87_reg_pwm(const struct it87_data *data, int nr)
+{
+ return data->reg_pwm[nr];
+}
+
/*
* PWM base frequencies. The frequency has to be divided by either 128 or 256,
* depending on the chip type, to calculate the actual PWM frequency.
@@ -832,16 +938,23 @@ static void it87_write_value(struct it87_data *data, u8 reg, u8 value)
static void it87_update_pwm_ctrl(struct it87_data *data, int nr)
{
- data->pwm_ctrl[nr] = it87_read_value(data, IT87_REG_PWM[nr]);
+ data->pwm_ctrl[nr] = it87_read_value(data, it87_reg_pwm(data, nr));
if (has_newer_autopwm(data)) {
- data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
+ data->pwm_temp_map[nr] =
+ pwm_temp_map_get(data, data->pwm_ctrl[nr]);
+ if (uses_global_temp_map(data) &&
+ data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
+ data->pwm_temp_map[nr] = 0;
data->pwm_duty[nr] = it87_read_value(data,
IT87_REG_PWM_DUTY[nr]);
- } else {
- if (data->pwm_ctrl[nr] & 0x80) /* Automatic mode */
- data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03;
- else /* Manual mode */
- data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;
+ } else if (data->pwm_ctrl[nr] & 0x80) { /* Automatic mode */
+ data->pwm_temp_map[nr] =
+ pwm_temp_map_get(data, data->pwm_ctrl[nr]);
+ if (uses_global_temp_map(data) &&
+ data->pwm_temp_map[nr] >= pwm_num_temp_map(data))
+ data->pwm_temp_map[nr] = 0;
+ } else { /* Manual mode */
+ data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f;
}
if (has_old_autopwm(data)) {
@@ -1591,27 +1704,32 @@ static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr,
data->pwm_duty[nr]);
/* and set manual mode */
if (has_newer_autopwm(data)) {
- ctrl = (data->pwm_ctrl[nr] & 0x7c) |
- data->pwm_temp_map[nr];
+ ctrl = pwm_temp_map_set(data,
+ data->pwm_ctrl[nr] &
+ ~0x80,
+ data->pwm_temp_map[nr]);
} else {
ctrl = data->pwm_duty[nr];
}
data->pwm_ctrl[nr] = ctrl;
- it87_write_value(data, IT87_REG_PWM[nr], ctrl);
+ it87_write_value(data, it87_reg_pwm(data, nr), ctrl);
}
} else {
u8 ctrl;
if (has_newer_autopwm(data)) {
- ctrl = (data->pwm_ctrl[nr] & 0x7c) |
- data->pwm_temp_map[nr];
+ ctrl = pwm_temp_map_set(data,
+ data->pwm_ctrl[nr] & ~0x80,
+ data->pwm_temp_map[nr]);
if (val != 1)
ctrl |= 0x80;
} else {
- ctrl = (val == 1 ? data->pwm_duty[nr] : 0x80);
+ ctrl = val == 1 ? data->pwm_duty[nr] :
+ pwm_temp_map_set(data, 0x80,
+ data->pwm_temp_map[nr]);
}
data->pwm_ctrl[nr] = ctrl;
- it87_write_value(data, IT87_REG_PWM[nr], ctrl);
+ it87_write_value(data, it87_reg_pwm(data, nr), ctrl);
if (has_fanctl_onoff(data) && nr < 3) {
/* set SmartGuardian mode */
@@ -1662,7 +1780,7 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
*/
if (!(data->pwm_ctrl[nr] & 0x80)) {
data->pwm_ctrl[nr] = data->pwm_duty[nr];
- it87_write_value(data, IT87_REG_PWM[nr],
+ it87_write_value(data, it87_reg_pwm(data, nr),
data->pwm_ctrl[nr]);
}
}
@@ -1717,20 +1835,14 @@ static ssize_t show_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = it87_update_device(dev);
int nr = sensor_attr->index;
- u8 num_map;
- int map;
+ unsigned int channel;
if (IS_ERR(data))
return PTR_ERR(data);
- num_map = data->num_temp_map;
- map = data->pwm_temp_map[nr];
- if (map >= num_map)
- map = 0; /* Should never happen */
- if (nr >= num_map) /* pwm channels 3..6 map to temp4..6 */
- map += num_map;
+ channel = pwm_temp_channel(data, nr, data->pwm_temp_map[nr]);
- return sprintf(buf, "%d\n", (int)BIT(map));
+ return sprintf(buf, "%d\n", (int)BIT(channel));
}
static ssize_t set_pwm_temp_map(struct device *dev,
@@ -1740,45 +1852,35 @@ static ssize_t set_pwm_temp_map(struct device *dev,
struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
struct it87_data *data = dev_get_drvdata(dev);
int nr = sensor_attr->index;
- u8 num_map = data->num_temp_map;
long val;
int err;
- u8 reg;
+ unsigned int channel;
+ u8 map;
- if (kstrtol(buf, 10, &val) < 0)
+ if (kstrtol(buf, 10, &val) < 0 || val <= 0 || !is_power_of_2(val))
return -EINVAL;
- if (nr >= num_map)
- val -= num_map;
-
- switch (val) {
- case BIT(0):
- reg = 0x00;
- break;
- case BIT(1):
- reg = 0x01;
- break;
- case BIT(2):
- reg = 0x02;
- break;
- default:
+ channel = __ffs(val);
+ if (pwm_temp_map_from_channel(data, nr, channel, &map))
return -EINVAL;
- }
err = it87_lock(data);
if (err)
return err;
it87_update_pwm_ctrl(data, nr);
- data->pwm_temp_map[nr] = reg;
+ data->pwm_temp_map[nr] = map;
/*
- * If we are in automatic mode, write the temp mapping immediately;
- * otherwise, just store it for later use.
+ * Newer controllers keep the duty cycle in a separate register, so
+ * their temperature mapping can be updated in any mode. On older
+ * controllers, defer the update until automatic mode is enabled.
*/
- if (data->pwm_ctrl[nr] & 0x80) {
- data->pwm_ctrl[nr] = (data->pwm_ctrl[nr] & 0xfc) |
- data->pwm_temp_map[nr];
- it87_write_value(data, IT87_REG_PWM[nr], data->pwm_ctrl[nr]);
+ if (has_newer_autopwm(data) || (data->pwm_ctrl[nr] & 0x80)) {
+ data->pwm_ctrl[nr] = pwm_temp_map_set(data,
+ data->pwm_ctrl[nr],
+ data->pwm_temp_map[nr]);
+ it87_write_value(data, it87_reg_pwm(data, nr),
+ data->pwm_ctrl[nr]);
}
it87_unlock(data);
return count;
@@ -3377,7 +3479,10 @@ static void it87_init_device(struct platform_device *pdev)
* manual duty cycle.
*/
for (i = 0; i < NUM_AUTO_PWM; i++) {
- data->pwm_temp_map[i] = i;
+ if (uses_global_temp_map(data))
+ data->pwm_temp_map[i] = 0;
+ else
+ data->pwm_temp_map[i] = i % IT87_PWM_OLD_NUM_TEMP;
data->pwm_duty[i] = 0x7f; /* Full speed */
data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */
}
@@ -3513,9 +3618,17 @@ static int it87_probe(struct platform_device *pdev)
data->ec_special_config = sio_data->ec_special_config;
chip = &it87_devices[sio_data->type];
data->features = chip->features;
+ data->reg_pwm = chip->reg_pwm;
data->peci_mask = chip->peci_mask;
data->old_peci_mask = chip->old_peci_mask;
data->num_temp_map = chip->num_temp_map;
+ if (has_new_tempmap(data)) {
+ data->pwm_temp_map_mask = 0x07;
+ data->pwm_temp_map_shift = 3;
+ } else {
+ data->pwm_temp_map_mask = 0x03;
+ data->pwm_temp_map_shift = 0;
+ }
/*
* IT8705F Datasheet 0.4.1, 3h == Version G.
* IT8712F Datasheet 0.9.1, section 8.3.5 indicates 8h == Version J.
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH v7 2/3] hwmon: it87: prepare for extended PWM temp maps
2026-08-29 22:01 ` [PATCH v7 2/3] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
@ 2026-08-29 22:14 ` sashiko-bot
2026-08-30 0:01 ` Guenter Roeck
1 sibling, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-29 22:14 UTC (permalink / raw)
To: Jerome Tollet; +Cc: linux-hwmon
> From: "benoit.masson" <yahoo@perenite.com>
>
> Introduce helper logic for PWM-to-temperature mappings so newer
> register layouts can be supported while retaining the legacy two groups
> of three temperature sources.
>
> Honor the four global temperature sources on IT8603E and IT8622E instead
> of applying the legacy grouping to those chips. Use per-chip masks and
> shifts for newer extended mappings.
>
> Newer controllers keep the duty cycle in a separate register, so write
> their temperature mapping in both manual and automatic mode. This keeps
> the selected mapping across cache refreshes and mode changes.
>
> On older controllers, defer mapping writes while in manual mode and apply
> the cached mapping when switching to automatic mode.
>
> Signed-off-by: benoit.masson <yahoo@perenite.com>
> Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1788040385.git.jerome.tollet@gmail.com?part=2
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v7 2/3] hwmon: it87: prepare for extended PWM temp maps
2026-08-29 22:01 ` [PATCH v7 2/3] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
2026-08-29 22:14 ` sashiko-bot
@ 2026-08-30 0:01 ` Guenter Roeck
1 sibling, 0 replies; 28+ messages in thread
From: Guenter Roeck @ 2026-08-30 0:01 UTC (permalink / raw)
To: Jerome Tollet; +Cc: Jean Delvare, linux-hwmon, Benoit Masson
On Sun, Aug 30, 2026 at 12:01:48AM +0200, Jerome Tollet wrote:
> From: "benoit.masson" <yahoo@perenite.com>
>
> Introduce helper logic for PWM-to-temperature mappings so newer
> register layouts can be supported while retaining the legacy two groups
> of three temperature sources.
>
> Honor the four global temperature sources on IT8603E and IT8622E instead
> of applying the legacy grouping to those chips. Use per-chip masks and
> shifts for newer extended mappings.
>
> Newer controllers keep the duty cycle in a separate register, so write
> their temperature mapping in both manual and automatic mode. This keeps
> the selected mapping across cache refreshes and mode changes.
>
> On older controllers, defer mapping writes while in manual mode and apply
> the cached mapping when switching to automatic mode.
>
> Signed-off-by: benoit.masson <yahoo@perenite.com>
> Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
Applied.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v7 3/3] hwmon: it87: add IT8613E support
2026-08-29 22:01 ` [PATCH v7 0/3] " Jerome Tollet
2026-08-29 22:01 ` [PATCH v7 1/3] hwmon: it87: describe per-chip PWM temperature maps Jerome Tollet
2026-08-29 22:01 ` [PATCH v7 2/3] hwmon: it87: prepare for extended PWM temp maps Jerome Tollet
@ 2026-08-29 22:01 ` Jerome Tollet
2026-08-29 22:11 ` sashiko-bot
2026-08-30 0:02 ` Guenter Roeck
2 siblings, 2 replies; 28+ messages in thread
From: Jerome Tollet @ 2026-08-29 22:01 UTC (permalink / raw)
To: Jean Delvare, Guenter Roeck; +Cc: linux-hwmon, Benoit Masson
From: "benoit.masson" <yahoo@perenite.com>
Teach the Super I/O probe path to recognize IT8613E and add its
hardware monitoring configuration.
Add feature flags, 11 mV ADC scaling, the IT8665-style PWM register map,
six PWM temperature mapping sources, and GPIO pin-mux checks.
Only three temperature inputs are currently known, so retain the existing
three temperature limit and offset resources. Document the chip in the
hwmon guide.
Signed-off-by: benoit.masson <yahoo@perenite.com>
Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
---
Documentation/hwmon/it87.rst | 8 +++++
drivers/hwmon/it87.c | 63 ++++++++++++++++++++++++++++++++++--
2 files changed, 69 insertions(+), 2 deletions(-)
diff --git a/Documentation/hwmon/it87.rst b/Documentation/hwmon/it87.rst
index fc1c90b02..c33ba8a07 100644
--- a/Documentation/hwmon/it87.rst
+++ b/Documentation/hwmon/it87.rst
@@ -11,6 +11,14 @@ Supported chips:
Datasheet: Not publicly available
+ * IT8613E
+
+ Prefix: 'it8613'
+
+ Addresses scanned: from Super I/O config space (8 I/O ports)
+
+ Datasheet: Not publicly available
+
* IT8620E
Prefix: 'it8620'
diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c
index 232cc8586..2757d428a 100644
--- a/drivers/hwmon/it87.c
+++ b/drivers/hwmon/it87.c
@@ -36,6 +36,7 @@
* IT8790E Super I/O chip w/LPC interface
* IT8792E Super I/O chip w/LPC interface
* IT87952E Super I/O chip w/LPC interface
+ * IT8613E Super I/O chip w/LPC interface
* Sis950 A clone of the IT8705F
*
* Copyright (C) 2001 Chris Gauthron
@@ -65,7 +66,7 @@
enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8732,
it8771, it8772, it8781, it8782, it8783, it8786, it8790,
- it8792, it8603, it8620, it8622, it8628, it8689, it87952 };
+ it8792, it8603, it8613, it8620, it8622, it8628, it8689, it87952 };
static struct platform_device *it87_pdev[2];
@@ -159,6 +160,7 @@ static inline void superio_exit(int ioreg, bool noexit)
#define IT8786E_DEVID 0x8786
#define IT8790E_DEVID 0x8790
#define IT8603E_DEVID 0x8603
+#define IT8613E_DEVID 0x8613
#define IT8620E_DEVID 0x8620
#define IT8622E_DEVID 0x8622
#define IT8623E_DEVID 0x8623
@@ -333,6 +335,7 @@ struct it87_devices {
#define FEAT_FOUR_TEMP BIT(22)
#define FEAT_FANCTL_ONOFF BIT(23) /* chip has FAN_CTL ON/OFF */
#define FEAT_NEW_TEMPMAP BIT(24) /* PWM uses extended temp map */
+#define FEAT_11MV_ADC BIT(25)
static const struct it87_devices it87_devices[] = {
[it87] = {
@@ -515,6 +518,18 @@ static const struct it87_devices it87_devices[] = {
.num_temp_map = 4,
.peci_mask = 0x07,
},
+ [it8613] = {
+ .name = "it8613",
+ .model = "IT8613E",
+ /* Only three temperature inputs are currently known. */
+ .features = FEAT_NEWER_AUTOPWM | FEAT_11MV_ADC | FEAT_16BIT_FANS
+ | FEAT_TEMP_OFFSET | FEAT_TEMP_PECI | FEAT_FIVE_FANS
+ | FEAT_FIVE_PWM | FEAT_IN7_INTERNAL | FEAT_PWM_FREQ2
+ | FEAT_AVCC3 | FEAT_NEW_TEMPMAP,
+ .reg_pwm = IT87_REG_PWM_8665,
+ .num_temp_map = 6,
+ .peci_mask = 0x07,
+ },
[it8620] = {
.name = "it8620",
.model = "IT8620E",
@@ -577,6 +592,7 @@ static const struct it87_devices it87_devices[] = {
#define has_16bit_fans(data) ((data)->features & FEAT_16BIT_FANS)
#define has_12mv_adc(data) ((data)->features & FEAT_12MV_ADC)
#define has_10_9mv_adc(data) ((data)->features & FEAT_10_9MV_ADC)
+#define has_11mv_adc(data) ((data)->features & FEAT_11MV_ADC)
#define has_newer_autopwm(data) ((data)->features & FEAT_NEWER_AUTOPWM)
#define has_old_autopwm(data) ((data)->features & FEAT_OLD_AUTOPWM)
#define has_temp_offset(data) ((data)->features & FEAT_TEMP_OFFSET)
@@ -607,7 +623,8 @@ static const struct it87_devices it87_devices[] = {
#define has_vin3_5v(data) ((data)->features & FEAT_VIN3_5V)
#define has_noconf(data) ((data)->features & FEAT_NOCONF)
#define has_scaling(data) ((data)->features & (FEAT_12MV_ADC | \
- FEAT_10_9MV_ADC))
+ FEAT_10_9MV_ADC | \
+ FEAT_11MV_ADC))
#define has_fanctl_onoff(data) ((data)->features & FEAT_FANCTL_ONOFF)
#define has_new_tempmap(data) ((data)->features & FEAT_NEW_TEMPMAP)
@@ -712,6 +729,8 @@ static int adc_lsb(const struct it87_data *data, int nr)
lsb = 120;
else if (has_10_9mv_adc(data))
lsb = 109;
+ else if (has_11mv_adc(data))
+ lsb = 110;
else
lsb = 160;
if (data->in_scaled & BIT(nr))
@@ -2919,6 +2938,9 @@ static int __init it87_find(int sioaddr, unsigned short *address,
case IT8623E_DEVID:
sio_data->type = it8603;
break;
+ case IT8613E_DEVID:
+ sio_data->type = it8613;
+ break;
case IT8620E_DEVID:
sio_data->type = it8620;
break;
@@ -3096,6 +3118,43 @@ static int __init it87_find(int sioaddr, unsigned short *address,
sio_data->skip_in |= BIT(5); /* No VIN5 */
sio_data->skip_in |= BIT(6); /* No VIN6 */
+ sio_data->beep_pin = superio_inb(sioaddr,
+ IT87_SIO_BEEP_PIN_REG) & 0x3f;
+ } else if (sio_data->type == it8613) {
+ int reg27, reg29, reg2a;
+
+ superio_select(sioaddr, GPIO);
+
+ /* Check for pwm3, fan3, pwm5, fan5 */
+ reg27 = superio_inb(sioaddr, IT87_SIO_GPIO3_REG);
+ if (!(reg27 & BIT(1)))
+ sio_data->skip_fan |= BIT(4);
+ if (reg27 & BIT(3))
+ sio_data->skip_pwm |= BIT(4);
+ if (reg27 & BIT(6))
+ sio_data->skip_pwm |= BIT(2);
+ if (reg27 & BIT(7))
+ sio_data->skip_fan |= BIT(2);
+
+ /* Check for pwm2, fan2 */
+ reg29 = superio_inb(sioaddr, IT87_SIO_GPIO5_REG);
+ if (reg29 & BIT(1))
+ sio_data->skip_pwm |= BIT(1);
+ if (reg29 & BIT(2))
+ sio_data->skip_fan |= BIT(1);
+
+ /* Check for pwm4, fan4 */
+ reg2a = superio_inb(sioaddr, IT87_SIO_PINX1_REG);
+ if (!(reg2a & BIT(0)) || (reg29 & BIT(7))) {
+ sio_data->skip_fan |= BIT(3);
+ sio_data->skip_pwm |= BIT(3);
+ }
+
+ sio_data->skip_pwm |= BIT(0); /* No pwm1 */
+ sio_data->skip_fan |= BIT(0); /* No fan1 */
+ sio_data->skip_in |= BIT(3); /* No VIN3 */
+ sio_data->skip_in |= BIT(6); /* No VIN6 */
+
sio_data->beep_pin = superio_inb(sioaddr,
IT87_SIO_BEEP_PIN_REG) & 0x3f;
} else if (sio_data->type == it8620 || sio_data->type == it8628) {
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH v7 3/3] hwmon: it87: add IT8613E support
2026-08-29 22:01 ` [PATCH v7 3/3] hwmon: it87: add IT8613E support Jerome Tollet
@ 2026-08-29 22:11 ` sashiko-bot
2026-08-30 0:02 ` Guenter Roeck
1 sibling, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-29 22:11 UTC (permalink / raw)
To: Jerome Tollet; +Cc: linux-hwmon
> From: "benoit.masson" <yahoo@perenite.com>
>
> Teach the Super I/O probe path to recognize IT8613E and add its
> hardware monitoring configuration.
>
> Add feature flags, 11 mV ADC scaling, the IT8665-style PWM register map,
> six PWM temperature mapping sources, and GPIO pin-mux checks.
>
> Only three temperature inputs are currently known, so retain the existing
> three temperature limit and offset resources. Document the chip in the
> hwmon guide.
>
> Signed-off-by: benoit.masson <yahoo@perenite.com>
> Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1788040385.git.jerome.tollet@gmail.com?part=3
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v7 3/3] hwmon: it87: add IT8613E support
2026-08-29 22:01 ` [PATCH v7 3/3] hwmon: it87: add IT8613E support Jerome Tollet
2026-08-29 22:11 ` sashiko-bot
@ 2026-08-30 0:02 ` Guenter Roeck
1 sibling, 0 replies; 28+ messages in thread
From: Guenter Roeck @ 2026-08-30 0:02 UTC (permalink / raw)
To: Jerome Tollet; +Cc: Jean Delvare, linux-hwmon, Benoit Masson
On Sun, Aug 30, 2026 at 12:01:49AM +0200, Jerome Tollet wrote:
> From: "benoit.masson" <yahoo@perenite.com>
>
> Teach the Super I/O probe path to recognize IT8613E and add its
> hardware monitoring configuration.
>
> Add feature flags, 11 mV ADC scaling, the IT8665-style PWM register map,
> six PWM temperature mapping sources, and GPIO pin-mux checks.
>
> Only three temperature inputs are currently known, so retain the existing
> three temperature limit and offset resources. Document the chip in the
> hwmon guide.
>
> Signed-off-by: benoit.masson <yahoo@perenite.com>
> Signed-off-by: Jerome Tollet <jerome.tollet@gmail.com>
Applied.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v6 0/3] hwmon: it87: add IT8613E support
2026-08-29 21:00 ` [PATCH v6 0/3] " Jerome Tollet
` (3 preceding siblings ...)
2026-08-29 22:01 ` [PATCH v7 0/3] " Jerome Tollet
@ 2026-08-29 22:53 ` Guenter Roeck
2026-08-30 5:19 ` Jerome Tollet
4 siblings, 1 reply; 28+ messages in thread
From: Guenter Roeck @ 2026-08-29 22:53 UTC (permalink / raw)
To: Jerome Tollet, Jean Delvare; +Cc: linux-hwmon, Benoit Masson
On 8/29/26 14:00, Jerome Tollet wrote:
> This is a respin of Benoit Masson's v4 series, with his permission.
> It replaces my fan-input-only RFC with the broader hardware monitoring
> support requested in review.
>
> The v4 series was dropped after a NULL pointer dereference was reported.
> The IT8613E device entry did not initialize reg_pwm, while the probe path
> unconditionally dereferences it. It also advertised six temperature
> offsets but the register table contained only three entries.
>
From Documentation/process/submitting-patches.rst:
However, for a multi-patch series, it is generally
best to avoid using In-Reply-To: to link to older versions of the
series. This way multiple versions of the patch don't become an
unmanageable forest of references in email clients.
Yet, sending new version of patch series as response to previous
versions is becoming more and more prevalent.
I am curious: Where is this promoted ?
Thanks,
Guenter
^ permalink raw reply [flat|nested] 28+ messages in thread