From 60832e756ac040aeb238c3b20c9eb87f71ff0067 Mon Sep 17 00:00:00 2001 From: Sinan Divarci Date: Thu, 25 Sep 2025 06:28:13 -0900 Subject: [PATCH 1/3] drivers: hwmon: add max31732 driver Add support for Analog Devices MAX31732, a five-channel temperature monitor with per-channel alarms, calibration helpers, and optional IRQ handling. Signed-off-by: Sinan Divarci --- drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/max31732.c | 921 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 933 insertions(+) create mode 100644 drivers/hwmon/max31732.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2829351d82761e..baff4af9a1d4f3 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1186,6 +1186,17 @@ config SENSORS_MAX31730 This driver can also be built as a module. If so, the module will be called max31730. +config SENSORS_MAX31732 + tristate "Analog Devices MAX31732" + depends on I2C + select REGMAP_I2C + help + Support for the Analog Devices MAX31732 4-Channel Remote + Temperature Sensor. + + This driver can also be built as a module. If so, the module + will be called max31732. + config SENSORS_MAX31760 tristate "MAX31760 fan speed controller" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 9554d2fdcf7bb5..fe45d31fdbc0f3 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -150,6 +150,7 @@ obj-$(CONFIG_SENSORS_MAX1668) += max1668.o obj-$(CONFIG_SENSORS_MAX197) += max197.o obj-$(CONFIG_SENSORS_MAX31722) += max31722.o obj-$(CONFIG_SENSORS_MAX31730) += max31730.o +obj-$(CONFIG_SENSORS_MAX31732) += max31732.o obj-$(CONFIG_SENSORS_MAX31760) += max31760.o obj-$(CONFIG_SENSORS_MAX6620) += max6620.o obj-$(CONFIG_SENSORS_MAX6621) += max6621.o diff --git a/drivers/hwmon/max31732.c b/drivers/hwmon/max31732.c new file mode 100644 index 00000000000000..8f50fd27a3bd15 --- /dev/null +++ b/drivers/hwmon/max31732.c @@ -0,0 +1,921 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MAX31732 4-Channel Remote Temperature Sensor + */ + +#include +#include +#include +#include +#include +#include +#include + +/* common definitions*/ +#define MAX31732_TEMP_OFFSET_BASELINE 0x77 +#define MAX31732_TEMP_MIN (-128000) +#define MAX31732_TEMP_MAX 127937 +#define MAX31732_TEMP_MIN_EXT (MAX31732_TEMP_MIN + MAX31732_EXTENDED_RANGE_OFFSET) +#define MAX31732_TEMP_MAX_EXT (MAX31732_TEMP_MAX + MAX31732_EXTENDED_RANGE_OFFSET) +#define MAX31732_OFFSET_MIN (-14875) +#define MAX31732_OFFSET_MAX 17000 +#define MAX31732_OFFSET_ZERO 14875 +#define MAX31732_SECOND_TEMP_MIN (-128000) +#define MAX31732_SECOND_TEMP_MAX 127000 +#define MAX31732_SECOND_TEMP_MIN_EXT (MAX31732_SECOND_TEMP_MIN + MAX31732_EXTENDED_RANGE_OFFSET) +#define MAX31732_SECOND_TEMP_MAX_EXT (MAX31732_SECOND_TEMP_MAX + MAX31732_EXTENDED_RANGE_OFFSET) +#define MAX31732_CUSTOM_OFFSET_RES 125 +#define MAX31732_ALL_CHANNEL_MASK 0x1F +#define MAX31732_ALARM_INT_MODE 0 +#define MAX31732_ALARM_COMP_MODE 1 +#define MAX31732_ALARM_FAULT_QUE 1 +#define MAX31732_ALARM_FAULT_QUE_MAX 3 +#define MAX31732_TEMP_STEP_MC 1000 +#define MAX31732_TEMP_DIVISOR 16 +#define MAX31732_EXTENDED_RANGE_OFFSET 64000 + +/* The MAX31732 registers */ +#define MAX31732_REG_TEMP_R 0x02 +#define MAX31732_REG_TEMP_L 0x0A +#define MAX31732_REG_PRIM_HIGH_STATUS 0x0C +#define MAX31732_REG_PRIM_LOW_STATUS 0x0D +#define MAX31732_REG_CHANNEL_ENABLE 0x0E +#define MAX31732_CHANNEL_ENABLE_EN_MTP_PU_LOAD BIT(7) +#define MAX31732_REG_CONF1 0x0F +#define MAX31732_CONF1_STOP BIT(7) +#define MAX31732_CONF1_SOFT_POR BIT(6) +#define MAX31732_CONF1_ALARM_MODE BIT(4) +#define MAX31732_CONF1_ALARM_FAULT_QUEUE GENMASK(3, 2) +#define MAX31732_CONF1_EXTRANGE BIT(1) +#define MAX31732_CONF1_ONE_SHOT BIT(0) +#define MAX31732_REG_CONF2 0x10 +#define MAX31732_CONF2_ALARM_MODE BIT(4) +#define MAX31732_CONF2_ALARM_FAULT_QUEUE GENMASK(3, 2) +#define MAX31732_CONF2_IGNORE_ISC2GND BIT(0) +#define MAX31732_REG_CUSTOM_IDEAL_R1 0x11 +#define MAX31732_REG_CUST_IDEAL_ENABLE 0x15 +#define MAX31732_REG_TEMP_OFFSET 0x16 +#define MAX31732_REG_OFFSET_ENABLE 0x17 +#define MAX31732_REG_FILTER_ENABLE 0x18 +#define MAX31732_REG_BETA_COMP_ENABLE 0x19 +#define MAX31732_REG_HIGHEST_TEMP_ENABLE 0x1A +#define MAX31732_REG_ALARM1_MASK 0x1B +#define MAX31732_REG_ALARM2_MASK 0x1C +#define MAX31732_REG_PRIM_HIGH_TEMP_R 0x1D +#define MAX31732_REG_PRIM_HIGH_TEMP_L 0x25 +#define MAX31732_REG_PRIM_LOW_TEMP 0x27 +#define MAX31732_REG_SECOND_HIGH_TEMP_R 0x29 +#define MAX31732_REG_SECOND_HIGH_TEMP_L 0x2D +#define MAX31732_REG_SECOND_LOW_TEMP 0x2E +#define MAX31732_REG_REFERENCE_TEMP_R1 0x2F +#define MAX31732_REG_REFERENCE_TEMP_R2 0x31 +#define MAX31732_REG_REFERENCE_TEMP_R3 0x33 +#define MAX31732_REG_REFERENCE_TEMP_R4 0x35 +#define MAX31732_REG_REFERENCE_TEMP_L 0x37 +#define MAX31732_REG_MTP_CONF 0x39 +#define MAX31732_MTP_CONF_WR_ENABLE BIT(7) +#define MAX31732_REG_MTP_CONF2 0x3A +#define MAX31732_MTP_CONF2_STORE BIT(7) +#define MAX31732_MTP_CONF2_LOAD BIT(5) +#define MAX31732_MTP_CONF2_MAN_WRITE BIT(4) +#define MAX31732_MTP_CONF2_I2C_READ BIT(3) +#define MAX31732_MTP_ADDRESS 0x3B +#define MAX31732_MTP_DIN 0x3C +#define MAX31732_REG_SECOND_HIGH_STATUS 0x42 +#define MAX31732_REG_SECOND_LOW_STATUS 0x43 +#define MAX31732_REG_TEMP_FAULT 0x44 +#define MAX31732_REG_HIGHEST_TEMP 0x45 +#define MAX31732_REG_BETA_COMPEN_R1 0x47 +#define MAX31732_REG_BETA_COMPEN_R2 0x48 +#define MAX31732_REG_BETA_COMPEN_R3 0x49 +#define MAX31732_REG_BETA_COMPEN_R4 0x4A +#define MAX31732_BETA_COMPEN_OC BIT(7) +#define MAX31732_BETA_COMPEN_SC2GND BIT(6) +#define MAX31732_BETA_COMPEN_SC2VCC BIT(5) +#define MAX31732_BETA_COMPEN_SC2DXP BIT(4) +#define MAX31732_BETA_COMPEN_BETA GENMASK(3, 0) + +/* The MAX31732 MTP Registers */ +#define MAX31732_MTP_REG_USER_SW_REV 0x80 +#define MAX31732_MTP_REG_TEMP_R 0x82 +#define MAX31732_MTP_REG_TEMP_L 0x8A +#define MAX31732_MTP_REG_PRIM_HIGH_STATUS 0x8C +#define MAX31732_MTP_REG_PRIM_LOW_STATUS 0x8D + +enum max31732_temp_type { + MAX31732_TEMP, + MAX31732_PRIM_HIGH, + MAX31732_SECOND_HIGH +}; + +enum max31732_channel { + MAX31732_CHANNEL_LOCAL = 0, + MAX31732_CHANNEL_REMOTE1, + MAX31732_CHANNEL_REMOTE2, + MAX31732_CHANNEL_REMOTE3, + MAX31732_CHANNEL_REMOTE4 +}; + +struct max31732_data { + struct i2c_client *client; + struct device *hwmon_dev; + struct regmap *regmap; + s32 irqs[2]; + bool extended_range; +}; + +static u32 max31732_get_temp_reg(enum max31732_temp_type temp_type, u32 channel) +{ + switch (temp_type) { + case MAX31732_PRIM_HIGH: + if (channel == MAX31732_CHANNEL_LOCAL) + return MAX31732_REG_PRIM_HIGH_TEMP_L; + else + return (MAX31732_REG_PRIM_HIGH_TEMP_R + (channel - 1) * 2); + break; + case MAX31732_SECOND_HIGH: + if (channel == MAX31732_CHANNEL_LOCAL) + return MAX31732_REG_SECOND_HIGH_TEMP_L; + else + return (MAX31732_REG_SECOND_HIGH_TEMP_R + (channel - 1)); + break; + case MAX31732_TEMP: + default: + if (channel == MAX31732_CHANNEL_LOCAL) + return MAX31732_REG_TEMP_L; + else + return (MAX31732_REG_TEMP_R + (channel - 1) * 2); + break; + } +} + +static const struct regmap_range max31732_regmap_volatile_ranges[] = { + regmap_reg_range(MAX31732_REG_TEMP_R, MAX31732_REG_PRIM_LOW_STATUS), + regmap_reg_range(MAX31732_REG_SECOND_HIGH_STATUS, + MAX31732_REG_BETA_COMPEN_R4), + regmap_reg_range(MAX31732_REG_MTP_CONF2, MAX31732_REG_MTP_CONF2), + regmap_reg_range(MAX31732_MTP_REG_USER_SW_REV, MAX31732_MTP_REG_PRIM_LOW_STATUS), +}; + +static bool max31732_volatile_reg(struct device *dev, u32 reg) +{ + return regmap_reg_in_ranges(reg, max31732_regmap_volatile_ranges, + ARRAY_SIZE(max31732_regmap_volatile_ranges)); +} + +static const struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = max31732_volatile_reg, +}; + +static inline long max31732_reg_to_mc(s16 temp, bool extended_range) +{ + if (extended_range) + return DIV_ROUND_CLOSEST((temp / MAX31732_TEMP_DIVISOR) * MAX31732_TEMP_STEP_MC, + MAX31732_TEMP_DIVISOR) + MAX31732_EXTENDED_RANGE_OFFSET; + + return DIV_ROUND_CLOSEST((temp / MAX31732_TEMP_DIVISOR) * MAX31732_TEMP_STEP_MC, + MAX31732_TEMP_DIVISOR); +} + +static int max31732_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, s32 channel, + long *val) +{ + struct max31732_data *data = dev_get_drvdata(dev); + s32 ret; + u32 reg_val, reg_addr; + s16 temp_reg_val; + u8 regs[2]; + + if (type != hwmon_temp) + return -EINVAL; + + switch (attr) { + case hwmon_temp_input: + ret = regmap_test_bits(data->regmap, MAX31732_REG_CHANNEL_ENABLE, BIT(channel)); + if (ret < 0) + return ret; + + if (!ret) + return -ENODATA; + + reg_addr = max31732_get_temp_reg(MAX31732_TEMP, channel); + break; + case hwmon_temp_max: + reg_addr = max31732_get_temp_reg(MAX31732_PRIM_HIGH, channel); + break; + case hwmon_temp_min: + reg_addr = MAX31732_REG_PRIM_LOW_TEMP; + break; + case hwmon_temp_lcrit: + ret = regmap_read(data->regmap, MAX31732_REG_SECOND_LOW_TEMP, ®_val); + if (ret) + return ret; + + *val = data->extended_range ? (((s8)reg_val * MAX31732_TEMP_STEP_MC) + + MAX31732_EXTENDED_RANGE_OFFSET) : + ((s8)reg_val * MAX31732_TEMP_STEP_MC); + return 0; + case hwmon_temp_crit: + reg_addr = max31732_get_temp_reg(MAX31732_SECOND_HIGH, channel); + ret = regmap_read(data->regmap, reg_addr, ®_val); + if (ret) + return ret; + + *val = data->extended_range ? (((s8)reg_val * MAX31732_TEMP_STEP_MC) + + MAX31732_EXTENDED_RANGE_OFFSET) : + ((s8)reg_val * MAX31732_TEMP_STEP_MC); + return 0; + case hwmon_temp_enable: + ret = regmap_test_bits(data->regmap, MAX31732_REG_CHANNEL_ENABLE, BIT(channel)); + if (ret < 0) + return ret; + + *val = ret; + return 0; + case hwmon_temp_offset: + if (channel == 0) + return -EINVAL; + + ret = regmap_test_bits(data->regmap, MAX31732_REG_OFFSET_ENABLE, BIT(channel)); + if (ret < 0) + return ret; + + if (!ret) + return 0; + + ret = regmap_read(data->regmap, MAX31732_REG_TEMP_OFFSET, ®_val); + if (ret) + return ret; + + *val = (long)((reg_val - MAX31732_TEMP_OFFSET_BASELINE) * + MAX31732_CUSTOM_OFFSET_RES); + return 0; + case hwmon_temp_fault: + ret = regmap_test_bits(data->regmap, MAX31732_REG_TEMP_FAULT, BIT(channel)); + if (ret < 0) + return ret; + + *val = ret; + return 0; + case hwmon_temp_lcrit_alarm: + ret = regmap_test_bits(data->regmap, MAX31732_REG_SECOND_LOW_STATUS, BIT(channel)); + if (ret < 0) + return ret; + + *val = ret; + return 0; + case hwmon_temp_min_alarm: + ret = regmap_test_bits(data->regmap, MAX31732_REG_PRIM_LOW_STATUS, BIT(channel)); + if (ret < 0) + return ret; + + *val = ret; + return 0; + case hwmon_temp_max_alarm: + ret = regmap_test_bits(data->regmap, MAX31732_REG_PRIM_HIGH_STATUS, BIT(channel)); + if (ret < 0) + return ret; + + *val = ret; + return 0; + case hwmon_temp_crit_alarm: + ret = regmap_test_bits(data->regmap, MAX31732_REG_SECOND_HIGH_STATUS, BIT(channel)); + if (ret < 0) + return ret; + + *val = ret; + return 0; + default: + return -EINVAL; + } + + ret = regmap_bulk_read(data->regmap, reg_addr, regs, 2); + if (ret < 0) + return ret; + + temp_reg_val = get_unaligned_be16(regs); + *val = max31732_reg_to_mc(temp_reg_val, data->extended_range); + return 0; +} + +static int max31732_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, s32 channel, + long val) +{ + struct max31732_data *data = dev_get_drvdata(dev); + s32 reg_addr, ret; + u16 temp_reg_val; + u8 reg_arr[2]; + long min_temp, max_temp; + + if (type != hwmon_temp) + return -EINVAL; + + switch (attr) { + case hwmon_temp_max: + reg_addr = max31732_get_temp_reg(MAX31732_PRIM_HIGH, channel); + break; + case hwmon_temp_min: + if (channel) + return -EOPNOTSUPP; + reg_addr = MAX31732_REG_PRIM_LOW_TEMP; + break; + case hwmon_temp_enable: + if (val == 0) { + return regmap_clear_bits(data->regmap, MAX31732_REG_CHANNEL_ENABLE, + BIT(channel)); + } else if (val == 1) { + return regmap_set_bits(data->regmap, MAX31732_REG_CHANNEL_ENABLE, + BIT(channel)); + } else { + return -EINVAL; + } + case hwmon_temp_offset: + val = clamp_val(val, MAX31732_OFFSET_MIN, MAX31732_OFFSET_MAX) + + MAX31732_OFFSET_ZERO; + val = DIV_ROUND_CLOSEST(val, 125); + + ret = regmap_update_bits(data->regmap, MAX31732_REG_OFFSET_ENABLE, BIT(channel), + val == MAX31732_TEMP_OFFSET_BASELINE ? 0 : BIT(channel)); + + if (ret) + return ret; + + return regmap_write(data->regmap, MAX31732_REG_TEMP_OFFSET, val); + case hwmon_temp_crit: + min_temp = data->extended_range ? MAX31732_SECOND_TEMP_MIN_EXT : MAX31732_SECOND_TEMP_MIN; + max_temp = data->extended_range ? MAX31732_SECOND_TEMP_MAX_EXT : MAX31732_SECOND_TEMP_MAX; + + val = clamp_val(val, min_temp, max_temp); + + if (data->extended_range) + val -= MAX31732_EXTENDED_RANGE_OFFSET; + + val = DIV_ROUND_CLOSEST(val, MAX31732_TEMP_STEP_MC); + reg_addr = max31732_get_temp_reg(MAX31732_SECOND_HIGH, channel); + return regmap_write(data->regmap, reg_addr, val); + case hwmon_temp_lcrit: + if (channel) + return -EOPNOTSUPP; + + min_temp = data->extended_range ? MAX31732_SECOND_TEMP_MIN_EXT : MAX31732_SECOND_TEMP_MIN; + max_temp = data->extended_range ? MAX31732_SECOND_TEMP_MAX_EXT : MAX31732_SECOND_TEMP_MAX; + + val = clamp_val(val, min_temp, max_temp); + + if (data->extended_range) + val -= MAX31732_EXTENDED_RANGE_OFFSET; + + val = DIV_ROUND_CLOSEST(val, MAX31732_TEMP_STEP_MC); + return regmap_write(data->regmap, MAX31732_REG_SECOND_LOW_TEMP, val); + default: + return -EINVAL; + } + + min_temp = data->extended_range ? MAX31732_TEMP_MIN_EXT : MAX31732_TEMP_MIN; + max_temp = data->extended_range ? MAX31732_TEMP_MAX_EXT : MAX31732_TEMP_MAX; + + val = clamp_val(val, min_temp, max_temp); + + if (data->extended_range) + val -= MAX31732_EXTENDED_RANGE_OFFSET; + + val = DIV_ROUND_CLOSEST(val * MAX31732_TEMP_DIVISOR, MAX31732_TEMP_STEP_MC); + val *= MAX31732_TEMP_DIVISOR; + + temp_reg_val = (u16)val; + put_unaligned_be16(temp_reg_val, reg_arr); + + return regmap_bulk_write(data->regmap, reg_addr, reg_arr, sizeof(reg_arr)); +} + +static umode_t max31732_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, + s32 channel) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_lcrit_alarm: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + case hwmon_temp_fault: + return 0444; + case hwmon_temp_min: + case hwmon_temp_lcrit: + return channel ? 0444 : 0644; + case hwmon_temp_offset: + case hwmon_temp_enable: + case hwmon_temp_max: + case hwmon_temp_crit: + return 0644; + } + break; + default: + break; + } + return 0; +} + +static void notify_event(struct max31732_data *drvdata, u32 attr, u32 reg_val) +{ + s32 channel; + + for (channel = 0; channel <= MAX31732_CHANNEL_REMOTE4; channel++) { + if (reg_val & BIT(channel)) + hwmon_notify_event(drvdata->hwmon_dev, hwmon_temp, attr, channel); + } +} + +static irqreturn_t max31732_irq_handler(s32 irq, void *data) +{ + struct device *dev = data; + struct max31732_data *drvdata = dev_get_drvdata(dev); + s32 ret; + u32 reg_val; + bool reported = false; + + ret = regmap_read(drvdata->regmap, MAX31732_REG_PRIM_HIGH_STATUS, ®_val); + if (ret) { + dev_err_ratelimited(dev, "Failed to read PRIMARY HIGH STATUS register (%pe)\n", + ERR_PTR(ret)); + return IRQ_HANDLED; + } + + if (reg_val != 0) { + dev_crit_ratelimited(dev, "Primary Overtemperature Alarm, R4:%d R3:%d R2:%d R1:%d L:%d.\n", + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE4)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE3)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE2)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE1)), + !!(reg_val & BIT(MAX31732_CHANNEL_LOCAL))); + notify_event(drvdata, hwmon_temp_max_alarm, reg_val); + reported = true; + } + + ret = regmap_read(drvdata->regmap, MAX31732_REG_PRIM_LOW_STATUS, ®_val); + if (ret) { + dev_err_ratelimited(dev, "Failed to read PRIMARY LOW STATUS register (%pe)\n", + ERR_PTR(ret)); + return IRQ_HANDLED; + } + + if (reg_val != 0) { + dev_crit_ratelimited(dev, "Primary Undertemperature Alarm, R4:%d R3:%d R2:%d R1:%d L:%d.\n", + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE4)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE3)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE2)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE1)), + !!(reg_val & BIT(MAX31732_CHANNEL_LOCAL))); + notify_event(drvdata, hwmon_temp_min_alarm, reg_val); + reported = true; + } + + ret = regmap_read(drvdata->regmap, MAX31732_REG_SECOND_HIGH_STATUS, ®_val); + if (ret) { + dev_err_ratelimited(dev, "Failed to read SECONDARY HIGH STATUS register (%pe)\n", + ERR_PTR(ret)); + return IRQ_HANDLED; + } + + if (reg_val != 0) { + dev_crit_ratelimited(dev, "Secondary Overtemperature Alarm, R4:%d R3:%d R2:%d R1:%d L:%d.\n", + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE4)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE3)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE2)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE1)), + !!(reg_val & BIT(MAX31732_CHANNEL_LOCAL))); + notify_event(drvdata, hwmon_temp_crit_alarm, reg_val); + reported = true; + } + + ret = regmap_read(drvdata->regmap, MAX31732_REG_SECOND_LOW_STATUS, ®_val); + if (ret) { + dev_err_ratelimited(dev, "Failed to read SECONDARY LOW STATUS register (%pe)\n", + ERR_PTR(ret)); + return IRQ_HANDLED; + } + + if (reg_val != 0) { + dev_crit_ratelimited(dev, "Secondary Undertemperature Alarm, R4:%d R3:%d R2:%d R1:%d L:%d.\n", + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE4)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE3)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE2)), + !!(reg_val & BIT(MAX31732_CHANNEL_REMOTE1)), + !!(reg_val & BIT(MAX31732_CHANNEL_LOCAL))); + notify_event(drvdata, hwmon_temp_lcrit_alarm, reg_val); + reported = true; + } + + if (!reported) { + if (irq == drvdata->irqs[0]) + dev_err_ratelimited(dev, "ALARM1 interrupt received but status registers not set.\n"); + else if (irq == drvdata->irqs[1]) + dev_err_ratelimited(dev, "ALARM2 interrupt received but status registers not set.\n"); + else + dev_err_ratelimited(dev, "Undefined interrupt source.\n"); + } + + return IRQ_HANDLED; +} + +static const struct hwmon_channel_info *max31732_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_LCRIT | HWMON_T_CRIT | HWMON_T_ENABLE | + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_LCRIT | + HWMON_T_CRIT | HWMON_T_OFFSET | HWMON_T_ENABLE | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM | + HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_LCRIT | + HWMON_T_CRIT | HWMON_T_OFFSET | HWMON_T_ENABLE | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM | + HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_LCRIT | + HWMON_T_CRIT | HWMON_T_OFFSET | HWMON_T_ENABLE | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM | + HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_LCRIT | + HWMON_T_CRIT | HWMON_T_OFFSET | HWMON_T_ENABLE | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_LCRIT_ALARM | + HWMON_T_FAULT + ), + NULL +}; + +static const struct hwmon_ops max31732_hwmon_ops = { + .is_visible = max31732_is_visible, + .read = max31732_read, + .write = max31732_write, +}; + +static const struct hwmon_chip_info max31732_chip_info = { + .ops = &max31732_hwmon_ops, + .info = max31732_info, +}; + +static int max31732_parse_alarms(struct device *dev, struct max31732_data *data) +{ + s32 ret; + u32 alarm_que; + + ret = regmap_update_bits(data->regmap, MAX31732_REG_CONF1, MAX31732_CONF1_ALARM_MODE, + device_property_read_bool(dev, "adi,alarm1-interrupt-mode") ? 0 : MAX31732_CONF1_ALARM_MODE); + + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, MAX31732_REG_CONF2, MAX31732_CONF2_ALARM_MODE, + device_property_read_bool(dev, "adi,alarm2-interrupt-mode") ? 0 : MAX31732_CONF2_ALARM_MODE); + + if (ret) + return ret; + + alarm_que = MAX31732_ALARM_FAULT_QUE; + device_property_read_u32(dev, "adi,alarm1-fault-queue", &alarm_que); + + if ((alarm_que / 2) <= MAX31732_ALARM_FAULT_QUE_MAX) { + ret = regmap_write_bits(data->regmap, MAX31732_REG_CONF1, + MAX31732_CONF1_ALARM_FAULT_QUEUE, + FIELD_PREP(MAX31732_CONF1_ALARM_FAULT_QUEUE, + (alarm_que / 2))); + if (ret) + return ret; + } else { + return dev_err_probe(dev, -EINVAL, "Invalid adi,alarm1-fault-queue.\n"); + } + + alarm_que = MAX31732_ALARM_FAULT_QUE; + device_property_read_u32(dev, "adi,alarm2-fault-queue", &alarm_que); + + if ((alarm_que / 2) <= MAX31732_ALARM_FAULT_QUE_MAX) { + ret = regmap_write_bits(data->regmap, MAX31732_REG_CONF2, + MAX31732_CONF2_ALARM_FAULT_QUEUE, + FIELD_PREP(MAX31732_CONF2_ALARM_FAULT_QUEUE, + (alarm_que / 2))); + } else { + return dev_err_probe(dev, -EINVAL, "Invalid adi,alarm2-fault-queue.\n"); + } + + return ret; +} + +static int max31732_parse_dt_config(struct device *dev, struct max31732_data *data) +{ + s32 ret; + u32 val, i; + u32 filter_channels = 0; + u32 beta_comp_channels = 0; + u32 highest_temp_channels = 0; + + /* Configure ignore short-circuit to ground */ + ret = regmap_update_bits(data->regmap, MAX31732_REG_CONF2, + MAX31732_CONF2_IGNORE_ISC2GND, + device_property_read_bool(dev, "adi,ignore-isc2gnd") ? + MAX31732_CONF2_IGNORE_ISC2GND : 0); + if (ret) + return dev_err_probe(dev, ret, "failed to configure ignore-isc2gnd\n"); + + /* Configure custom ideality for remote channels */ + for (i = 0; i < 4; i++) { + char prop_name[40]; + u32 ideality_factor; + + snprintf(prop_name, sizeof(prop_name), "adi,custom-ideality-factor-r%d", i + 1); + if (!device_property_read_u32(dev, prop_name, &ideality_factor)) { + if (ideality_factor > 0 && ideality_factor <= 255) { + /* Write custom ideality code */ + ret = regmap_write(data->regmap, + MAX31732_REG_CUSTOM_IDEAL_R1 + i, + ideality_factor); + if (ret) + return dev_err_probe(dev, ret, + "failed to set custom ideality for R%d\n", i + 1); + + /* Enable custom ideality for this channel */ + ret = regmap_set_bits(data->regmap, + MAX31732_REG_CUST_IDEAL_ENABLE, + BIT(i + 1)); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable custom ideality for R%d\n", i + 1); + } else { + return dev_err_probe(dev, -EINVAL, + "Invalid custom ideality factor for R%d (must be 1-255)\n", i + 1); + } + } else { + /* Property not specified, disable custom ideality for this channel */ + ret = regmap_clear_bits(data->regmap, + MAX31732_REG_CUST_IDEAL_ENABLE, + BIT(i + 1)); + if (ret) + return dev_err_probe(dev, ret, + "failed to disable custom ideality for R%d\n", i + 1); + } + } + + /* Configure filter enable for remote channels (bit mask) */ + if (!device_property_read_u32(dev, "adi,filter-channels", &filter_channels)) { + /* Mask out local channel (bit 0) and invalid channels */ + filter_channels &= 0x1E; /* Only remote channels 1-4 */ + } + ret = regmap_write(data->regmap, MAX31732_REG_FILTER_ENABLE, filter_channels); + if (ret) + return dev_err_probe(dev, ret, "failed to configure filter channels\n"); + + /* Configure beta compensation enable for remote channels (bit mask) */ + if (!device_property_read_u32(dev, "adi,beta-compensation-channels", + &beta_comp_channels)) { + /* Mask out local channel (bit 0) and invalid channels */ + beta_comp_channels &= 0x1E; /* Only remote channels 1-4 */ + } + ret = regmap_write(data->regmap, MAX31732_REG_BETA_COMP_ENABLE, beta_comp_channels); + if (ret) + return dev_err_probe(dev, ret, "failed to configure beta compensation channels\n"); + + /* Configure highest temperature tracking enable (bit mask) */ + if (!device_property_read_u32(dev, "adi,highest-temp-channels", + &highest_temp_channels)) { + highest_temp_channels &= MAX31732_ALL_CHANNEL_MASK; + } + ret = regmap_write(data->regmap, MAX31732_REG_HIGHEST_TEMP_ENABLE, highest_temp_channels); + if (ret) + return dev_err_probe(dev, ret, "failed to configure highest temp channels\n"); + + /* Configure reference temperatures if provided */ + /* Local reference temperature */ + if (!device_property_read_u32(dev, "adi,reference-temp-local", &val)) { + s16 temp_reg; + u8 regs[2]; + + val = clamp_val((s32)val, + data->extended_range ? MAX31732_TEMP_MIN_EXT : MAX31732_TEMP_MIN, + data->extended_range ? MAX31732_TEMP_MAX_EXT : MAX31732_TEMP_MAX); + + if (data->extended_range) + val -= MAX31732_EXTENDED_RANGE_OFFSET; + + val = DIV_ROUND_CLOSEST((s32)val * MAX31732_TEMP_DIVISOR, MAX31732_TEMP_STEP_MC); + val *= MAX31732_TEMP_DIVISOR; + + temp_reg = (s16)val; + put_unaligned_be16(temp_reg, regs); + + ret = regmap_bulk_write(data->regmap, MAX31732_REG_REFERENCE_TEMP_L, + regs, sizeof(regs)); + if (ret) + return dev_err_probe(dev, ret, + "failed to set local reference temperature\n"); + } + + /* Remote reference temperatures */ + for (i = 0; i < 4; i++) { + char prop_name[32]; + + snprintf(prop_name, sizeof(prop_name), "adi,reference-temp-remote%d", i + 1); + if (!device_property_read_u32(dev, prop_name, &val)) { + s16 temp_reg; + u8 regs[2]; + u32 reg_addr = MAX31732_REG_REFERENCE_TEMP_R1 + (i * 2); + + val = clamp_val((s32)val, + data->extended_range ? MAX31732_TEMP_MIN_EXT : MAX31732_TEMP_MIN, + data->extended_range ? MAX31732_TEMP_MAX_EXT : MAX31732_TEMP_MAX); + + if (data->extended_range) + val -= MAX31732_EXTENDED_RANGE_OFFSET; + + val = DIV_ROUND_CLOSEST((s32)val * MAX31732_TEMP_DIVISOR, + MAX31732_TEMP_STEP_MC); + val *= MAX31732_TEMP_DIVISOR; + + temp_reg = (s16)val; + put_unaligned_be16(temp_reg, regs); + + ret = regmap_bulk_write(data->regmap, reg_addr, regs, sizeof(regs)); + if (ret) + return dev_err_probe(dev, ret, + "failed to set remote%d reference temperature\n", i + 1); + } + } + + return 0; +} + +static int max31732_setup_irq(struct device *dev, + struct regmap *regmap, + const char *irqname, + int *irq, + unsigned int mask_reg, + unsigned int alarm_mask) +{ + int irq_num, ret; + + irq_num = fwnode_irq_get_byname(dev_fwnode(dev), irqname); + if (irq_num == -EPROBE_DEFER) + return dev_err_probe(dev, irq_num, "IRQ probe defer\n"); + + if (irq_num == -ENOENT || irq_num == -ENXIO) + return regmap_set_bits(regmap, mask_reg, MAX31732_ALL_CHANNEL_MASK); + + if (irq_num < 0) + return 0; + + ret = regmap_update_bits(regmap, mask_reg, MAX31732_ALL_CHANNEL_MASK, alarm_mask); + if (ret) + return dev_err_probe(dev, ret, "failed to set alarm mask\n"); + + *irq = irq_num; + + ret = devm_request_threaded_irq(dev, irq_num, NULL, + max31732_irq_handler, + IRQF_ONESHOT, + dev_name(dev), dev); + if (ret) + return dev_err_probe(dev, ret, "cannot request irq\n"); + + return 0; +} + +static int max31732_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max31732_data *data; + s32 ret; + u32 reg_val; + u32 alarm1_mask = MAX31732_ALL_CHANNEL_MASK; + u32 alarm2_mask = MAX31732_ALL_CHANNEL_MASK; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + + data->regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(data->regmap)) + return dev_err_probe(dev, PTR_ERR(data->regmap), "regmap init failed\n"); + + ret = regmap_read(data->regmap, MAX31732_REG_CHANNEL_ENABLE, ®_val); + if (ret) + return dev_err_probe(dev, ret, "failed to read channel enable register\n"); + + ret = regmap_update_bits(data->regmap, MAX31732_REG_CONF1, MAX31732_CONF1_STOP, reg_val == 0 ? MAX31732_CONF1_STOP : 0); + if (ret) + return dev_err_probe(dev, ret, "failed to set STOP bit per channel enable reg\n"); + + ret = max31732_parse_alarms(dev, data); + if (ret) + return dev_err_probe(dev, ret, "failed to parse alarms\n"); + + dev_set_drvdata(dev, data); + + /* Set extended range in hardware and cache it */ + data->extended_range = device_property_read_bool(dev, "adi,extended-range"); + ret = regmap_update_bits(data->regmap, MAX31732_REG_CONF1, MAX31732_CONF1_EXTRANGE, + data->extended_range ? MAX31732_CONF1_EXTRANGE : 0); + + if (ret) + return dev_err_probe(dev, ret, "failed to set extended range\n"); + + /* Parse and apply device tree configuration */ + ret = max31732_parse_dt_config(dev, data); + if (ret) + return ret; + + if (!device_property_read_u32(dev, "adi,alarm1-mask", &alarm1_mask)) { + if (alarm1_mask & ~MAX31732_ALL_CHANNEL_MASK) { + dev_warn(dev, "Invalid alarm1-mask=0x%x, applying 0x%x\n", alarm1_mask, + (alarm1_mask & MAX31732_ALL_CHANNEL_MASK)); + alarm1_mask &= MAX31732_ALL_CHANNEL_MASK; + } + } + + if (!device_property_read_u32(dev, "adi,alarm2-mask", &alarm2_mask)) { + if (alarm2_mask & ~MAX31732_ALL_CHANNEL_MASK) { + dev_warn(dev, "Invalid alarm2-mask=0x%x, applying 0x%x\n", alarm2_mask, + (alarm2_mask & MAX31732_ALL_CHANNEL_MASK)); + alarm2_mask &= MAX31732_ALL_CHANNEL_MASK; + } + } + + data->hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &max31732_chip_info, NULL); + + if (IS_ERR(data->hwmon_dev)) + return dev_err_probe(dev, PTR_ERR(data->hwmon_dev), + "failed to register hwmon device\n"); + + /* Clear any pending status registers before setting up interrupts */ + regmap_read(data->regmap, MAX31732_REG_PRIM_HIGH_STATUS, ®_val); + regmap_read(data->regmap, MAX31732_REG_PRIM_LOW_STATUS, ®_val); + regmap_read(data->regmap, MAX31732_REG_SECOND_HIGH_STATUS, ®_val); + regmap_read(data->regmap, MAX31732_REG_SECOND_LOW_STATUS, ®_val); + + /* Setup interrupts after hwmon device is registered and status cleared */ + ret = max31732_setup_irq(dev, data->regmap, "ALARM1", &data->irqs[0], + MAX31732_REG_ALARM1_MASK, alarm1_mask); + if (ret) + return dev_err_probe(dev, ret, "failed to setup ALARM1 irq\n"); + + ret = max31732_setup_irq(dev, data->regmap, "ALARM2", &data->irqs[1], + MAX31732_REG_ALARM2_MASK, alarm2_mask); + if (ret) + return dev_err_probe(dev, ret, "failed to setup ALARM2 irq\n"); + + return 0; +} + +static const struct i2c_device_id max31732_ids[] = { + { "max31732" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max31732_ids); + +static const struct of_device_id __maybe_unused max31732_of_match[] = { + { .compatible = "adi,max31732", }, + { } +}; + +MODULE_DEVICE_TABLE(of, max31732_of_match); + +static int max31732_suspend(struct device *dev) +{ + struct max31732_data *data = dev_get_drvdata(dev); + + return regmap_set_bits(data->regmap, MAX31732_REG_CONF1, MAX31732_CONF1_STOP); +} + +static int max31732_resume(struct device *dev) +{ + struct max31732_data *data = dev_get_drvdata(dev); + + return regmap_clear_bits(data->regmap, MAX31732_REG_CONF1, MAX31732_CONF1_STOP); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(max31732_pm_ops, max31732_suspend, max31732_resume); + +static struct i2c_driver max31732_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max31732", + .of_match_table = max31732_of_match, + .pm = pm_sleep_ptr(&max31732_pm_ops), + }, + .probe = max31732_probe, + .id_table = max31732_ids, +}; + +module_i2c_driver(max31732_driver); + +MODULE_AUTHOR("Sinan Divarci "); +MODULE_DESCRIPTION("MAX31732 driver"); +MODULE_LICENSE("GPL"); From 85c1f82a93b30802daf1e739f1754292f7b7a837 Mon Sep 17 00:00:00 2001 From: Sinan Divarci Date: Thu, 25 Sep 2025 06:48:15 -0900 Subject: [PATCH 2/3] docs: hwmon: add max31732 documentation Add documentation for the Analog Devices MAX31732 temperature sensor driver under Documentation/hwmon. Signed-off-by: Sinan Divarci --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/max31732.rst | 171 +++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 Documentation/hwmon/max31732.rst diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 0d6a6e7ae45c4c..3161ba5ecc4ecd 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -148,6 +148,7 @@ Hardware Monitoring Kernel Drivers max20751 max31722 max31730 + max31732 max31760 max31785 max31790 diff --git a/Documentation/hwmon/max31732.rst b/Documentation/hwmon/max31732.rst new file mode 100644 index 00000000000000..31b3005d664dec --- /dev/null +++ b/Documentation/hwmon/max31732.rst @@ -0,0 +1,171 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Analog Devices MAX31732 hardware monitor +======================================== + +Supported chips +--------------- + +* Analog Devices MAX31732 - 4-channel remote plus on-chip temperature sensor + + Prefix: ``max31732`` + + I2C addresses: 0x1c - 0x4f (can be selected by connecting a resistor between ADD pin and GND) + + Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max31732.pdf + +Overview +-------- + +The MAX31732 combines a local temperature sensor with four remote +probes. The local sensor monitors the chip's own temperature, while the remote probes monitor external diode-connected transistors. +The device supports comparator or interrupt behaviour on +two open-drain ALARM outputs, implements programmable fault queues, +and exposes calibration features such as custom ideality factors and beta compensation. +The Linux driver uses the regmap abstraction on top of the I2C bus and integrates with the HWMON +subsystem, exposing standard hwmon ``temp`` attributes through the modern hwmon API. + +Key device capabilities +~~~~~~~~~~~~~~~~~~~~~~~ + +- Five measurement channels (local sensor + four remote diodes) +- 12-bit temperature conversion with 0.0625°C resolution +- Extended range option that shifts the readable window by +64°C +- Independent primary (ALARM1) and secondary (ALARM2) alarm thresholds +- Per-channel programmable offsets, digital filtering and calibration +- Comparator or interrupt mode for alarm outputs +- Non-volatile (MTP) storage for alarm/fault configuration (not exposed by driver) + +Temperature data is reported in milli-degree Celsius through standard hwmon sysfs attributes. + +Standard temperature attributes +------------------------------- + +The driver implements standard hwmon temperature attributes. +All attributes follow the standard hwmon sysfs interface. + +===================== == ===================================================== +temp[1-5]_input RO Instantaneous temperature for local (1) and remote + channels (2-5). Returns -ENODATA if channel is + disabled. +temp[1-5]_enable RW Enable (1) or disable (0) a channel. +temp[1-5]_max RW Primary over-temperature limit (asserts ALARM1). +temp[1-5]_max_alarm RO Primary over-temperature alarm status. +temp[1-5]_crit RW Secondary over-temperature limit (asserts ALARM2). +temp[1-5]_crit_alarm RO Secondary over-temperature alarm status. +temp1_min RW Primary under-temperature limit (asserts ALARM1). + This is a shared limit that applies to all channels. +temp[1-5]_min_alarm RO Primary under-temperature alarm status. +temp1_lcrit RW Secondary under-temperature limit (asserts ALARM2). + This is a shared limit that applies to all channels. +temp[1-5]_lcrit_alarm RO Secondary under-temperature alarm status. +temp[2-5]_offset RW Programmable offset for each remote channel + (-14.875°C to +17°C in 0.125°C steps). Writing + a non-zero offset automatically enables the offset + for that channel. +temp[2-5]_fault RO Indicates open/short fault detection on remote + sensors (0=no fault, 1=fault detected). +===================== == ===================================================== + +Notes: + +* temp1 represents the local (on-chip) temperature sensor +* temp2-5 represent remote channels 1-4 respectively +* Only temp1_min and temp1_lcrit are writable; these limits apply globally +* Offsets are only available for remote channels (temp2-5), not the local channel +* All temperatures are in milli-degrees Celsius +* The extended_range mode (configured via device tree) adds a +64°C offset + to all temperature readings, allowing measurements above +127.9375°C + +Device Tree configuration +------------------------- + +The driver accepts several device tree properties to configure the device at +probe time. These properties configure hardware behavior but are not exposed +as runtime sysfs attributes. + +Refer to ``Documentation/devicetree/bindings/hwmon/adi,max31732.yaml`` for the +complete device tree binding specification. + +Configuration properties +~~~~~~~~~~~~~~~~~~~~~~~~ + +``interrupts`` / ``interrupt-names`` + Optional interrupt lines for ALARM1/ALARM2 outputs. When specified, the + driver registers threaded IRQ handlers that clear status registers and + issue hwmon_notify_event() callbacks to userspace. + +``adi,alarm1-interrupt-mode`` / ``adi,alarm2-interrupt-mode`` + Configure ALARM outputs to function in interrupt (latching) mode. + When absent, outputs operate in comparator mode. + +``adi,alarm1-fault-queue`` / ``adi,alarm2-fault-queue`` + Number of consecutive faults (1, 2, 4, or 6) required before the + corresponding ALARM output asserts. Defaults to 1. + +``adi,extended-range`` + Enable extended temperature range at probe time. When set, adds a +64°C + offset to all temperature readings, allowing measurements from -64°C to + +191.9375°C (primary limits) or +191°C (secondary limits). + +``adi,ignore-isc2gnd`` + Mask short-circuit-to-ground faults for remote channels. + +``adi,alarm1-mask`` / ``adi,alarm2-mask`` + 5-bit mask (0x00-0x1f) to disable ALARM assertions for specific channels + at probe time. Bit 0 = local channel, bits 1-4 = remote channels 1-4. + Set bit to 1 to mask (disable) the alarm for that channel. + +``adi,custom-ideality-factor-r[1-4]`` + Custom ideality factor (1-255) for each remote channel. Allows calibration + for non-standard diode characteristics. + +``adi,filter-channels`` + Bitmask (bits 1-4) to enable digital filtering on remote channels. The + filter averages the previous four conversions. + +``adi,beta-compensation-channels`` + Bitmask (bits 1-4) to enable beta compensation on remote channels. + +``adi,highest-temp-channels`` + Bitmask (bits 0-4) to enable highest temperature tracking per channel. + Note: The highest temperature values are not currently exposed via sysfs. + +``adi,reference-temp-local`` + Reference junction temperature for the local channel (in milli-degrees C). + +``adi,reference-temp-remote[1-4]`` + Reference junction temperatures for remote channels (in milli-degrees C). + +Interrupt support +----------------- + +The driver optionally registers threaded IRQ handlers for the ALARM1 and +ALARM2 outputs. Provide ``interrupts`` and matching ``interrupt-names`` +("ALARM1", "ALARM2") in the device tree to enable interrupt handling. + +When an alarm interrupt fires, the driver: + +1. Reads the status registers (PRIMARY/SECONDARY HIGH/LOW STATUS) +2. Logs critical messages identifying which channels triggered the alarm +3. Calls hwmon_notify_event() for each affected channel +4. Returns IRQ_HANDLED + +This allows userspace monitoring tools to react promptly to temperature events +through the standard hwmon notification mechanism. + +If interrupts are not specified in device tree, the corresponding ALARM outputs +are masked (all channels disabled) and polling must be used to detect alarm +conditions via the _alarm sysfs attributes. + +Usage notes +----------- + +* All temperatures are in milli-degree Celsius (m°C) +* The driver uses REGCACHE_MAPLE for register caching with explicit volatile ranges +* Channel enable/disable state is checked at probe; if all channels are disabled, + the STOP bit is set to halt conversions +* The extended_range setting and other hardware configurations are applied once + at probe time and cannot be changed at runtime +* Temperature offset writes automatically enable/disable the offset feature based + on whether a non-zero value is written From 2d1537eeccb12ae8203185b10f31cf98ad3f4a80 Mon Sep 17 00:00:00 2001 From: Sinan Divarci Date: Thu, 25 Sep 2025 06:50:41 -0900 Subject: [PATCH 3/3] dt-bindings: hwmon: add bindings for max31732 Add device tree bindings for the Analog Devices MAX31732 temperature sensor driver under Documentation/devicetree/bindings/hwmon/. Signed-off-by: Sinan Divarci --- .../bindings/hwmon/adi,max31732.yaml | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/adi,max31732.yaml diff --git a/Documentation/devicetree/bindings/hwmon/adi,max31732.yaml b/Documentation/devicetree/bindings/hwmon/adi,max31732.yaml new file mode 100644 index 00000000000000..a12715880e8bcd --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/adi,max31732.yaml @@ -0,0 +1,199 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/adi,max31732.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX31732 Temperature Sensor + +maintainers: + - Sinan Divarci + +description: | + The Analog Devices MAX31732 is a local plus four-channel remote temperature + sensor that exposes two ALARM outputs, programmable limits and calibration + aids. The driver reports readings through the Linux hwmon subsystem and can + optionally use the ALARM pins as interrupts. + +properties: + compatible: + const: adi,max31732 + + reg: + description: I2C address of the device. + maxItems: 1 + + interrupts: + description: Optional ALARM1/ALARM2 interrupt lines. + minItems: 1 + maxItems: 2 + + interrupt-names: + description: Names matching the provided interrupts. + minItems: 1 + maxItems: 2 + items: + - const: ALARM1 + - const: ALARM2 + + adi,alarm1-interrupt-mode: + type: boolean + description: Use the ALARM1 output in interrupt (latching) mode. + + adi,alarm2-interrupt-mode: + type: boolean + description: Use the ALARM2 output in interrupt (latching) mode. + + adi,alarm1-fault-queue: + description: Number of consecutive faults required to assert ALARM1. + enum: [1, 2, 4, 6] + default: 1 + + adi,alarm2-fault-queue: + description: Number of consecutive faults required to assert ALARM2. + enum: [1, 2, 4, 6] + default: 1 + + adi,extended-range: + type: boolean + description: | + Enable the device extended range (+64°C offset) at probe time. When set, + the temperature reading range shifts from [-128°C, +127.9375°C] to + [-64°C, +191.9375°C] for primary limits and [-64°C, +191°C] for + secondary limits. + + adi,ignore-isc2gnd: + type: boolean + description: | + Mask short-circuit-to-ground faults for remote channels. When set, + the device ignores shorts between DXN and ground. + + adi,custom-ideality-factor-r1: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 255 + description: | + Custom ideality factor for remote channel 1. Allows calibration for + non-standard diode characteristics. When specified, overrides the + default ideality factor for this channel. + + adi,custom-ideality-factor-r2: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 255 + description: Custom ideality factor for remote channel 2. + + adi,custom-ideality-factor-r3: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 255 + description: Custom ideality factor for remote channel 3. + + adi,custom-ideality-factor-r4: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 255 + description: Custom ideality factor for remote channel 4. + + adi,filter-channels: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0x1e + description: | + Bitmask to enable digital filtering on remote channels (bits 1-4). + The filter averages the previous four temperature conversions. + Bit 0 (local channel) is ignored. Bit 1 = remote1, bit 2 = remote2, + bit 3 = remote3, bit 4 = remote4. + + adi,beta-compensation-channels: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0x1e + description: | + Bitmask to enable beta compensation on remote channels (bits 1-4). + Bit encoding same as adi,filter-channels. + + adi,highest-temp-channels: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0x1f + description: | + Bitmask to enable highest temperature tracking per channel (bits 0-4). + Bit 0 = local, bits 1-4 = remote channels 1-4. The hardware records + the highest temperature seen on enabled channels. + + adi,reference-temp-local: + $ref: /schemas/types.yaml#/definitions/int32 + description: | + Reference junction temperature for the local channel in milli-degrees + Celsius. Used for calibration purposes. + + adi,reference-temp-remote1: + $ref: /schemas/types.yaml#/definitions/int32 + description: Reference junction temperature for remote channel 1 (in m°C). + + adi,reference-temp-remote2: + $ref: /schemas/types.yaml#/definitions/int32 + description: Reference junction temperature for remote channel 2 (in m°C). + + adi,reference-temp-remote3: + $ref: /schemas/types.yaml#/definitions/int32 + description: Reference junction temperature for remote channel 3 (in m°C). + + adi,reference-temp-remote4: + $ref: /schemas/types.yaml#/definitions/int32 + description: Reference junction temperature for remote channel 4 (in m°C). + + adi,alarm1-mask: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0x1f + description: | + Bitmask to disable ALARM1 assertions for specific channels at probe time. + Bit 0 = local channel, bits 1-4 = remote channels 1-4. + Set bit to 1 to mask (disable) the alarm for that channel. + + adi,alarm2-mask: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 0x1f + description: | + Bitmask to disable ALARM2 assertions for specific channels at probe time. + Same bit encoding as adi,alarm1-mask. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@1c { + compatible = "adi,max31732"; + reg = <0x1c>; + interrupt-parent = <&gpio>; + interrupts = <17 IRQ_TYPE_EDGE_FALLING>, <27 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "ALARM1", "ALARM2"; + adi,alarm1-interrupt-mode; + adi,alarm1-fault-queue = <4>; + adi,alarm2-fault-queue = <2>; + adi,extended-range; + adi,ignore-isc2gnd; + adi,custom-ideality-factor-r1 = <100>; + adi,filter-channels = <0x1e>; /* Enable for all remote channels */ + adi,beta-compensation-channels = <0x06>; /* Enable for remote 1 and 2 */ + adi,highest-temp-channels = <0x1f>; /* Track all channels */ + adi,reference-temp-local = <25000>; /* 25°C in m°C */ + adi,reference-temp-remote1 = <25000>; + adi,alarm1-mask = <0x08>; + adi,alarm2-mask = <0x10>; + }; + };