This is the driver for Microchip MCP998X/33 and MCP998XD/33D
Multichannel Automotive Temperature Monitor Family.
Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
---
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/mcp9982.rst | 111 +++++
MAINTAINERS | 2 +
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/mcp9982.c | 954 ++++++++++++++++++++++++++++++++++++++++
6 files changed, 1080 insertions(+)
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 85d7a686883e..b02709fc216e 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -173,6 +173,7 @@ Hardware Monitoring Kernel Drivers
mc33xs2410_hwmon
mc34vr500
mcp3021
+ mcp9982
menf21bmc
mlxreg-fan
mp2856
diff --git a/Documentation/hwmon/mcp9982.rst b/Documentation/hwmon/mcp9982.rst
new file mode 100644
index 000000000000..ceff3e69ee78
--- /dev/null
+++ b/Documentation/hwmon/mcp9982.rst
@@ -0,0 +1,111 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Kernel driver MCP998X
+=====================
+
+Supported chips:
+
+ * Microchip Technology MCP998X/MCP9933 and MCP998XD/MCP9933D
+
+ Prefix: 'mcp9982'
+
+ Datasheet:
+ https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
+
+Authors:
+
+ - Victor Duicu <victor.duicu@microchip.com>
+
+Description
+-----------
+
+This driver implements support for the MCP998X family containing: MCP9982,
+MCP9982D, MCP9983, MCP9983D, MCP9984, MCP9984D, MCP9985, MCP9985D,
+MCP9933 and MCP9933D.
+
+The MCP998X Family is a high accuracy 2-wire multichannel automotive
+temperature monitor.
+
+The chips in the family have different numbers of external channels,
+ranging from 1 (MCP9982) to 4 channels (MCP9985). Reading diodes in
+anti-parallel connection is supported by MCP9984/85/33 and
+MCP9984D/85D/33D. Dedicated hardware shutdown circuitry is present
+only in MCP998XD and MCP9933D.
+
+Temperatures are read in millidegrees Celsius, ranging from -64 to
+191.875 with 0.125 precision.
+
+Each channel has a minimum, maximum, and critical limit alongside associated alarms.
+The chips also implement a hysteresis mechanism which applies only to the maximum
+and critical limits. The relative difference between a limit and its hysteresis
+is the same for both and the value is kept in a single register.
+
+The chips measure temperatures with a variable conversion rate.
+Update_interval = Conversion/Second, so the available options are:
+- 16000 (ms) = 1 conv/16 sec
+- 8000 (ms) = 1 conv/8 sec
+- 4000 (ms) = 1 conv/4 sec
+- 2000 (ms) = 1 conv/2 sec
+- 1000 (ms) = 1 conv/sec
+- 500 (ms) = 2 conv/sec
+- 250 (ms) = 4 conv/sec
+- 125 (ms) = 8 conv/sec
+- 64 (ms) = 16 conv/sec
+- 32 (ms) = 32 conv/sec
+- 16 (ms) = 64 conv/sec
+
+Usage Notes
+-----------
+
+Parameters that can be configured in devicetree:
+- anti-parallel diode mode operation
+- resistance error correction on channels 1 and 2
+- resistance error correction on channels 3 and 4
+- power state
+
+Chips 82/83 and 82D/83D do not support anti-parallel diode mode.
+For chips with "D" in the name resistance error correction must be on.
+Please see Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
+for details.
+
+There are two power states:
+- Active state: in which the chip is converting on all channels at the
+programmed rate.
+
+- Standby state: in which the host must initiate a conversion cycle.
+
+Chips with "D" in the name work in Active state only and those without
+can work in either state.
+
+Chips with "D" in the name can't set update interval slower than 1 second.
+
+Among the hysteresis attributes, only the tempX_crit_hyst ones are writeable
+while the others are read only. Setting tempX_crit_hyst writes the difference
+between tempX_crit and tempX_crit_hyst in the hysteresis register. The new value
+applies automatically to the other limits. At power up the device starts with
+the value 10 in the hysteresis register.
+
+Sysfs entries
+-------------
+
+The following attributes are supported. The temperature limits and
+update_interval are read-write. The attribute tempX_crit_hyst is read-write,
+while tempX_max_hyst is read only. All other attributes are read only.
+
+======================= ==================================================
+temp[1-5]_label User name for channel.
+temp[1-5]_input Measured temperature for channel.
+
+temp[1-5]_crit Critical temperature limit.
+temp[1-5]_crit_alarm Critical temperature limit alarm.
+temp[1-5]_crit_hyst Critical temperature limit hysteresis.
+
+temp[1-5]_max High temperature limit.
+temp[1-5]_max_alarm High temperature limit alarm.
+temp[1-5]_max_hyst High temperature limit hysteresis.
+
+temp[1-5]_min Low temperature limit.
+temp[1-5]_min_alarm Low temperature limit alarm.
+
+update_interval The interval at which the chip will update readings.
+======================= ==================================================
diff --git a/MAINTAINERS b/MAINTAINERS
index 026510a4129c..5c6662e10b04 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17165,6 +17165,8 @@ M: Victor Duicu <victor.duicu@microchip.com>
L: linux-hwmon@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
+F: Documentation/hwmon/mcp9982.rst
+F: drivers/hwmon/mcp9982.c
MICROCHIP MMC/SD/SDIO MCI DRIVER
M: Aubin Constans <aubin.constans@microchip.com>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 157678b821fc..c758ab2d5fdf 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1388,6 +1388,17 @@ config SENSORS_MCP3021
This driver can also be built as a module. If so, the module
will be called mcp3021.
+config SENSORS_MCP9982
+ tristate "Microchip Technology MCP9982 driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say yes here to include support for Microchip Technology's MCP998X/33
+ and MCP998XD/33D Multichannel Automotive Temperature Monitor Family.
+
+ This driver can also be built as a module. If so, the module
+ will be called mcp9982.
+
config SENSORS_MLXREG_FAN
tristate "Mellanox FAN driver"
depends on MELLANOX_PLATFORM
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index eade8e3b1bde..cec33da29a68 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -170,6 +170,7 @@ obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o
obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
+obj-$(CONFIG_SENSORS_MCP9982) += mcp9982.o
obj-$(CONFIG_SENSORS_TC654) += tc654.o
obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o
obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
diff --git a/drivers/hwmon/mcp9982.c b/drivers/hwmon/mcp9982.c
new file mode 100644
index 000000000000..f13e80283404
--- /dev/null
+++ b/drivers/hwmon/mcp9982.c
@@ -0,0 +1,954 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HWMON driver for MCP998X/33 and MCP998XD/33D Multichannel Automotive
+ * Temperature Monitor Family
+ *
+ * Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Victor Duicu <victor.duicu@microchip.com>
+ *
+ * Datasheet can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/byteorder/generic.h>
+#include <linux/delay.h>
+#include <linux/device/devres.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/time64.h>
+#include <linux/util_macros.h>
+
+/* MCP9982 Registers */
+#define MCP9982_HIGH_BYTE_ADDR(index) (2 * (index))
+#define MCP9982_ONE_SHOT_ADDR 0x0A
+#define MCP9982_INTERNAL_HIGH_LIMIT_ADDR 0x0B
+#define MCP9982_INTERNAL_LOW_LIMIT_ADDR 0x0C
+#define MCP9982_EXT_HIGH_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0D)
+#define MCP9982_EXT_LOW_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0F)
+#define MCP9982_THERM_LIMIT_ADDR(index) ((index) + 0x1D)
+#define MCP9982_CFG_ADDR 0x22
+#define MCP9982_CONV_ADDR 0x24
+#define MCP9982_HYS_ADDR 0x25
+#define MCP9982_CONSEC_ALRT_ADDR 0x26
+#define MCP9982_ALRT_CFG_ADDR 0x27
+#define MCP9982_RUNNING_AVG_ADDR 0x28
+#define MCP9982_HOTTEST_CFG_ADDR 0x29
+#define MCP9982_STATUS_ADDR 0x2A
+#define MCP9982_EXT_FAULT_STATUS_ADDR 0x2B
+#define MCP9982_HIGH_LIMIT_STATUS_ADDR 0x2C
+#define MCP9982_LOW_LIMIT_STATUS_ADDR 0x2D
+#define MCP9982_THERM_LIMIT_STATUS_ADDR 0x2E
+#define MCP9982_HOTTEST_HIGH_BYTE_ADDR 0x2F
+#define MCP9982_HOTTEST_LOW_BYTE_ADDR 0x30
+#define MCP9982_HOTTEST_STATUS_ADDR 0x31
+#define MCP9982_THERM_SHTDWN_CFG_ADDR 0x32
+#define MCP9982_HRDW_THERM_SHTDWN_LIMIT_ADDR 0x33
+#define MCP9982_EXT_BETA_CFG_ADDR(index) ((index) + 0x33)
+#define MCP9982_EXT_IDEAL_ADDR(index) ((index) + 0x35)
+
+/* MCP9982 Bits */
+#define MCP9982_CFG_MSKAL BIT(7)
+#define MCP9982_CFG_RS BIT(6)
+#define MCP9982_CFG_ATTHM BIT(5)
+#define MCP9982_CFG_RECD12 BIT(4)
+#define MCP9982_CFG_RECD34 BIT(3)
+#define MCP9982_CFG_RANGE BIT(2)
+#define MCP9982_CFG_DA_ENA BIT(1)
+#define MCP9982_CFG_APDD BIT(0)
+
+#define MCP9982_STATUS_BUSY BIT(5)
+
+/* Constants and default values */
+#define MCP9982_MAX_NUM_CHANNELS 5
+#define MCP9982_BETA_AUTODETECT 16
+#define MCP9982_IDEALITY_DEFAULT 18
+#define MCP9982_OFFSET 64
+#define MCP9982_DEFAULT_CONSEC_ALRT_VAL 112
+#define MCP9982_DEFAULT_HYS_VAL 10
+#define MCP9982_DEFAULT_CONV_VAL 6
+#define MCP9982_WAKE_UP_TIME_US 125000
+#define MCP9982_WAKE_UP_TIME_MAX_US 130000
+#define MCP9982_HIGH_LIMIT_DEFAULT 85000
+#define MCP9982_LOW_LIMIT_DEFAULT 0
+
+static const struct hwmon_channel_info * const mcp9985_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+ HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+ HWMON_T_CRIT_HYST,
+ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+ HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+ HWMON_T_CRIT_HYST,
+ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+ HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+ HWMON_T_CRIT_HYST,
+ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+ HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+ HWMON_T_CRIT_HYST,
+ HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+ HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+ HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+ HWMON_T_CRIT_HYST),
+ HWMON_CHANNEL_INFO(chip,
+ HWMON_C_UPDATE_INTERVAL),
+ NULL
+};
+
+/**
+ * struct mcp9982_features - features of a mcp9982 instance
+ * @name: chip's name
+ * @phys_channels: number of physical channels supported by the chip
+ * @hw_thermal_shutdown: presence of hardware thermal shutdown circuitry
+ * @allow_apdd: whether the chip supports enabling APDD
+ */
+struct mcp9982_features {
+ const char *name;
+ u8 phys_channels;
+ bool hw_thermal_shutdown;
+ bool allow_apdd;
+};
+
+static const struct mcp9982_features mcp9933_chip_config = {
+ .name = "mcp9933",
+ .phys_channels = 3,
+ .hw_thermal_shutdown = false,
+ .allow_apdd = true,
+};
+
+static const struct mcp9982_features mcp9933d_chip_config = {
+ .name = "mcp9933d",
+ .phys_channels = 3,
+ .hw_thermal_shutdown = true,
+ .allow_apdd = true,
+};
+
+static const struct mcp9982_features mcp9982_chip_config = {
+ .name = "mcp9982",
+ .phys_channels = 2,
+ .hw_thermal_shutdown = false,
+ .allow_apdd = false,
+};
+
+static const struct mcp9982_features mcp9982d_chip_config = {
+ .name = "mcp9982d",
+ .phys_channels = 2,
+ .hw_thermal_shutdown = true,
+ .allow_apdd = false,
+};
+
+static const struct mcp9982_features mcp9983_chip_config = {
+ .name = "mcp9983",
+ .phys_channels = 3,
+ .hw_thermal_shutdown = false,
+ .allow_apdd = false,
+};
+
+static const struct mcp9982_features mcp9983d_chip_config = {
+ .name = "mcp9983d",
+ .phys_channels = 3,
+ .hw_thermal_shutdown = true,
+ .allow_apdd = false,
+};
+
+static const struct mcp9982_features mcp9984_chip_config = {
+ .name = "mcp9984",
+ .phys_channels = 4,
+ .hw_thermal_shutdown = false,
+ .allow_apdd = true,
+};
+
+static const struct mcp9982_features mcp9984d_chip_config = {
+ .name = "mcp9984d",
+ .phys_channels = 4,
+ .hw_thermal_shutdown = true,
+ .allow_apdd = true,
+};
+
+static const struct mcp9982_features mcp9985_chip_config = {
+ .name = "mcp9985",
+ .phys_channels = 5,
+ .hw_thermal_shutdown = false,
+ .allow_apdd = true,
+};
+
+static const struct mcp9982_features mcp9985d_chip_config = {
+ .name = "mcp9985d",
+ .phys_channels = 5,
+ .hw_thermal_shutdown = true,
+ .allow_apdd = true,
+};
+
+static const unsigned int mcp9982_update_interval[11] = {
+ 16000, 8000, 4000, 2000, 1000, 500, 250, 125, 64, 32, 16
+};
+
+/* MCP9982 regmap configuration */
+static const struct regmap_range mcp9982_regmap_wr_ranges[] = {
+ regmap_reg_range(MCP9982_ONE_SHOT_ADDR, MCP9982_CFG_ADDR),
+ regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_HOTTEST_CFG_ADDR),
+ regmap_reg_range(MCP9982_THERM_SHTDWN_CFG_ADDR, MCP9982_THERM_SHTDWN_CFG_ADDR),
+ regmap_reg_range(MCP9982_EXT_BETA_CFG_ADDR(1), MCP9982_EXT_IDEAL_ADDR(4)),
+};
+
+static const struct regmap_access_table mcp9982_regmap_wr_table = {
+ .yes_ranges = mcp9982_regmap_wr_ranges,
+ .n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_wr_ranges),
+};
+
+static const struct regmap_range mcp9982_regmap_rd_ranges[] = {
+ regmap_reg_range(MCP9982_HIGH_BYTE_ADDR(0), MCP9982_CFG_ADDR),
+ regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_EXT_IDEAL_ADDR(4)),
+};
+
+static const struct regmap_access_table mcp9982_regmap_rd_table = {
+ .yes_ranges = mcp9982_regmap_rd_ranges,
+ .n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_rd_ranges),
+};
+
+static bool mcp9982_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MCP9982_ONE_SHOT_ADDR:
+ case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
+ case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
+ case MCP9982_EXT_LOW_LIMIT_ADDR(1):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(1) + 1:
+ case MCP9982_EXT_LOW_LIMIT_ADDR(2):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(2) + 1:
+ case MCP9982_EXT_LOW_LIMIT_ADDR(3):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(3) + 1:
+ case MCP9982_EXT_LOW_LIMIT_ADDR(4):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(4) + 1:
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(1) + 1:
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(2) + 1:
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(3) + 1:
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(4) + 1:
+ case MCP9982_THERM_LIMIT_ADDR(0):
+ case MCP9982_THERM_LIMIT_ADDR(1):
+ case MCP9982_THERM_LIMIT_ADDR(2):
+ case MCP9982_THERM_LIMIT_ADDR(3):
+ case MCP9982_THERM_LIMIT_ADDR(4):
+ case MCP9982_CFG_ADDR:
+ case MCP9982_CONV_ADDR:
+ case MCP9982_HYS_ADDR:
+ case MCP9982_CONSEC_ALRT_ADDR:
+ case MCP9982_ALRT_CFG_ADDR:
+ case MCP9982_RUNNING_AVG_ADDR:
+ case MCP9982_HOTTEST_CFG_ADDR:
+ case MCP9982_THERM_SHTDWN_CFG_ADDR:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config mcp9982_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &mcp9982_regmap_rd_table,
+ .wr_table = &mcp9982_regmap_wr_table,
+ .volatile_reg = mcp9982_is_volatile_reg,
+ .max_register = MCP9982_EXT_IDEAL_ADDR(4),
+ .cache_type = REGCACHE_MAPLE,
+};
+
+/**
+ * struct mcp9982_priv - information about chip parameters
+ * @regmap: device register map
+ * @chip: pointer to structure holding chip features
+ * @labels: labels of the channels
+ * @interval_idx: index representing the current update interval
+ * @enabled_channel_mask: mask containing which channels should be enabled
+ * @num_channels: number of active physical channels
+ * @recd34_enable: state of Resistance Error Correction(REC) on channels 3 and 4
+ * @recd12_enable: state of Resistance Error Correction(REC) on channels 1 and 2
+ * @apdd_enable: state of anti-parallel diode mode
+ * @run_state: chip is in Run state, otherwise is in Standby state
+ */
+struct mcp9982_priv {
+ struct regmap *regmap;
+ const struct mcp9982_features *chip;
+ const char *labels[MCP9982_MAX_NUM_CHANNELS];
+ unsigned int interval_idx;
+ unsigned long enabled_channel_mask;
+ u8 num_channels;
+ bool recd34_enable;
+ bool recd12_enable;
+ bool apdd_enable;
+ bool run_state;
+};
+
+static int mcp9982_read_limit(struct mcp9982_priv *priv, u8 address, long *val)
+{
+ unsigned int limit, reg_high, reg_low;
+ int ret;
+
+ switch (address) {
+ case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
+ case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
+ case MCP9982_THERM_LIMIT_ADDR(0):
+ case MCP9982_THERM_LIMIT_ADDR(1):
+ case MCP9982_THERM_LIMIT_ADDR(2):
+ case MCP9982_THERM_LIMIT_ADDR(3):
+ case MCP9982_THERM_LIMIT_ADDR(4):
+ ret = regmap_read(priv->regmap, address, &limit);
+ if (ret)
+ return ret;
+
+ *val = ((int)limit - MCP9982_OFFSET) * 1000;
+
+ return 0;
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(1):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(2):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(3):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(4):
+ /*
+ * The MCP998X family is designed so that block reading is allowed
+ * only on the dedicated temperature and status memory blocks.
+ * Reading from those memory areas uses SMbus, while from any other
+ * region I2C is used and only one byte readings are allowed.
+ *
+ * This behavior is described in the documentation at page 26.
+ *
+ * When reading the temperature limits only single byte reads
+ * are allowed.
+ */
+ ret = regmap_read(priv->regmap, address, ®_high);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->regmap, address + 1, ®_low);
+ if (ret)
+ return ret;
+
+ *val = ((reg_high << 8) + reg_low) >> 5;
+ *val = (*val - (MCP9982_OFFSET << 3)) * 125;
+
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long *val)
+{
+ struct mcp9982_priv *priv = dev_get_drvdata(dev);
+ unsigned int reg_high, reg_low;
+ int ret, hyst;
+ u8 addr;
+
+ /* In Standby State the conversion cycle must be initiated manually. */
+ if (!priv->run_state) {
+ ret = regmap_write(priv->regmap, MCP9982_ONE_SHOT_ADDR, 1);
+ if (ret)
+ return ret;
+ usleep_range(MCP9982_WAKE_UP_TIME_US, MCP9982_WAKE_UP_TIME_MAX_US);
+ }
+
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_input:
+ /* Block reading from addresses 0x00->0x09 is not allowed. */
+ ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel), ®_high);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel) + 1,
+ ®_low);
+ if (ret)
+ return ret;
+
+ *val = ((reg_high << 8) + reg_low) >> 5;
+ *val = (*val - (MCP9982_OFFSET << 3)) * 125;
+
+ return 0;
+ case hwmon_temp_max:
+ if (channel)
+ addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
+ else
+ addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
+
+ return mcp9982_read_limit(priv, addr, val);
+ case hwmon_temp_max_alarm:
+ *val = regmap_test_bits(priv->regmap, MCP9982_HIGH_LIMIT_STATUS_ADDR,
+ BIT(channel));
+ if (*val < 0)
+ return *val;
+
+ return 0;
+ case hwmon_temp_max_hyst:
+ if (channel)
+ addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
+ else
+ addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
+ ret = mcp9982_read_limit(priv, addr, val);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
+ if (ret)
+ return ret;
+
+ *val -= hyst * 1000;
+
+ return 0;
+ case hwmon_temp_min:
+ if (channel)
+ addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
+ else
+ addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
+
+ return mcp9982_read_limit(priv, addr, val);
+ case hwmon_temp_min_alarm:
+ *val = regmap_test_bits(priv->regmap, MCP9982_LOW_LIMIT_STATUS_ADDR,
+ BIT(channel));
+ if (*val < 0)
+ return *val;
+
+ return 0;
+ case hwmon_temp_crit:
+ return mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
+ case hwmon_temp_crit_alarm:
+ *val = regmap_test_bits(priv->regmap, MCP9982_THERM_LIMIT_STATUS_ADDR,
+ BIT(channel));
+ if (*val < 0)
+ return *val;
+
+ return 0;
+ case hwmon_temp_crit_hyst:
+ ret = mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
+ if (ret)
+ return ret;
+
+ *val -= hyst * 1000;
+
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ *val = mcp9982_update_interval[priv->interval_idx];
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mcp9982_read_label(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ struct mcp9982_priv *priv = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_label:
+ *str = priv->labels[channel];
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int mcp9982_write_limit(struct mcp9982_priv *priv, u8 address, long val)
+{
+ int ret;
+ unsigned int regh, regl;
+
+ /* Range is always -64 to 191.875°C. */
+ val = clamp_val(val, -64000, 191875);
+ val = (val + MCP9982_OFFSET * 1000) << 5;
+ val = DIV_ROUND_CLOSEST(val, 125);
+
+ switch (address) {
+ case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
+ case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
+ case MCP9982_THERM_LIMIT_ADDR(0):
+ case MCP9982_THERM_LIMIT_ADDR(1):
+ case MCP9982_THERM_LIMIT_ADDR(2):
+ case MCP9982_THERM_LIMIT_ADDR(3):
+ case MCP9982_THERM_LIMIT_ADDR(4):
+ val = val >> 8;
+ ret = regmap_write(priv->regmap, address, val);
+ if (ret)
+ return ret;
+
+ return 0;
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
+ case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(1):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(2):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(3):
+ case MCP9982_EXT_LOW_LIMIT_ADDR(4):
+ regl = val & 0xFF;
+ regh = val >> 8;
+
+ /* Block write to addresses 0x0D->0x1C is not allowed. */
+ ret = regmap_write(priv->regmap, address, regh);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, address + 1, regl);
+ if (ret)
+ return ret;
+
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mcp9982_write_hyst(struct mcp9982_priv *priv, int channel, long val)
+{
+ int hyst, ret;
+ int limit;
+
+ val = clamp_val(val, -64000, 191875);
+ val = (val + MCP9982_OFFSET * 1000) << 5;
+ val = DIV_ROUND_CLOSEST(val, 125);
+ val = val >> 8;
+
+ /* Therm register is 8 bits and so it keeps only the integer part of the temperature. */
+ ret = regmap_read(priv->regmap, MCP9982_THERM_LIMIT_ADDR(channel), &limit);
+ if (ret)
+ return ret;
+
+ hyst = clamp_val(limit - val, 0, 255);
+
+ ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, hyst);
+
+ return ret;
+}
+
+static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long val)
+{
+ struct mcp9982_priv *priv = dev_get_drvdata(dev);
+ unsigned int idx;
+ u8 addr;
+ int ret;
+
+ switch (type) {
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_update_interval:
+
+ /*
+ * For MCP998XD and MCP9933D update interval
+ * can't be slower than 1 second.
+ */
+ if (priv->chip->hw_thermal_shutdown)
+ val = clamp(val, 0, 1000);
+
+ idx = find_closest_descending(val, mcp9982_update_interval,
+ ARRAY_SIZE(mcp9982_update_interval));
+
+ ret = regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx);
+ if (ret)
+ return ret;
+
+ priv->interval_idx = idx;
+
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_max:
+ if (channel)
+ addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
+ else
+ addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
+
+ return mcp9982_write_limit(priv, addr, val);
+ case hwmon_temp_min:
+ if (channel)
+ addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
+ else
+ addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
+
+ return mcp9982_write_limit(priv, addr, val);
+ case hwmon_temp_crit:
+ return mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
+ case hwmon_temp_crit_hyst:
+ return mcp9982_write_hyst(priv, channel, val);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static umode_t mcp9982_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ const struct mcp9982_priv *priv = _data;
+
+ if (!test_bit(channel, &priv->enabled_channel_mask))
+ return 0;
+
+ switch (type) {
+ case hwmon_temp:
+ switch (attr) {
+ case hwmon_temp_label:
+ if (priv->labels[channel])
+ return 0444;
+ else
+ return 0;
+ case hwmon_temp_input:
+ case hwmon_temp_min_alarm:
+ case hwmon_temp_max_alarm:
+ case hwmon_temp_max_hyst:
+ case hwmon_temp_crit_alarm:
+ return 0444;
+ case hwmon_temp_min:
+ case hwmon_temp_max:
+ case hwmon_temp_crit:
+ case hwmon_temp_crit_hyst:
+ return 0644;
+ default:
+ return 0;
+ }
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ return 0644;
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+}
+
+static const struct hwmon_ops mcp9982_hwmon_ops = {
+ .is_visible = mcp9982_is_visible,
+ .read = mcp9982_read,
+ .read_string = mcp9982_read_label,
+ .write = mcp9982_write,
+};
+
+static int mcp9982_init(struct device *dev, struct mcp9982_priv *priv)
+{
+ unsigned int i;
+ int ret;
+ u8 val;
+
+ /* Chips 82/83 and 82D/83D do not support anti-parallel diode mode. */
+ if (!priv->chip->allow_apdd && priv->apdd_enable == 1)
+ return dev_err_probe(dev, -EINVAL, "Incorrect setting of APDD.\n");
+
+ /* Chips with "D" work only in Run state. */
+ if (priv->chip->hw_thermal_shutdown && !priv->run_state)
+ return dev_err_probe(dev, -EINVAL, "Incorrect setting of Power State.\n");
+
+ /*
+ * For chips with "D" in the name, resistance error correction must be
+ * on so that hardware shutdown feature can't be overridden.
+ */
+ if (priv->chip->hw_thermal_shutdown)
+ if (!priv->recd34_enable || !priv->recd12_enable)
+ return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD.\n");
+
+ /*
+ * Set default values in registers.
+ * APDD, RECD12 and RECD34 are active on 0.
+ */
+ val = FIELD_PREP(MCP9982_CFG_MSKAL, 1) |
+ FIELD_PREP(MCP9982_CFG_RS, !priv->run_state) |
+ FIELD_PREP(MCP9982_CFG_ATTHM, 1) |
+ FIELD_PREP(MCP9982_CFG_RECD12, !priv->recd12_enable) |
+ FIELD_PREP(MCP9982_CFG_RECD34, !priv->recd34_enable) |
+ FIELD_PREP(MCP9982_CFG_RANGE, 1) | FIELD_PREP(MCP9982_CFG_DA_ENA, 0) |
+ FIELD_PREP(MCP9982_CFG_APDD, !priv->apdd_enable);
+
+ ret = regmap_write(priv->regmap, MCP9982_CFG_ADDR, val);
+ if (ret)
+ return ret;
+
+ /* Read initial value from register */
+ ret = regmap_read(priv->regmap, MCP9982_CONV_ADDR, &priv->interval_idx);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, MCP9982_DEFAULT_HYS_VAL);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, MCP9982_CONSEC_ALRT_ADDR, MCP9982_DEFAULT_CONSEC_ALRT_VAL);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, MCP9982_ALRT_CFG_ADDR, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, MCP9982_RUNNING_AVG_ADDR, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, MCP9982_HOTTEST_CFG_ADDR, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * Only external channels 1 and 2 support beta compensation.
+ * Set beta auto-detection.
+ */
+ for (i = 1; i < 3; i++)
+ if (test_bit(i, &priv->enabled_channel_mask)) {
+ ret = regmap_write(priv->regmap, MCP9982_EXT_BETA_CFG_ADDR(i),
+ MCP9982_BETA_AUTODETECT);
+ if (ret)
+ return ret;
+ }
+
+ /* Set default values for internal channel limits. */
+ if (test_bit(0, &priv->enabled_channel_mask)) {
+ ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_HIGH_LIMIT_ADDR,
+ MCP9982_HIGH_LIMIT_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_LOW_LIMIT_ADDR,
+ MCP9982_LOW_LIMIT_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(0),
+ MCP9982_HIGH_LIMIT_DEFAULT);
+ if (ret)
+ return ret;
+ }
+
+ /* Set ideality factor and limits to default for external channels. */
+ for (i = 1; i < MCP9982_MAX_NUM_CHANNELS; i++)
+ if (test_bit(i, &priv->enabled_channel_mask)) {
+ ret = regmap_write(priv->regmap, MCP9982_EXT_IDEAL_ADDR(i),
+ MCP9982_IDEALITY_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = mcp9982_write_limit(priv, MCP9982_EXT_HIGH_LIMIT_ADDR(i),
+ MCP9982_HIGH_LIMIT_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = mcp9982_write_limit(priv, MCP9982_EXT_LOW_LIMIT_ADDR(i),
+ MCP9982_LOW_LIMIT_DEFAULT);
+ if (ret)
+ return ret;
+
+ ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(i),
+ MCP9982_HIGH_LIMIT_DEFAULT);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mcp9982_parse_fw_config(struct device *dev, int device_nr_channels)
+{
+ struct mcp9982_priv *priv = dev_get_drvdata(dev);
+ unsigned int reg_nr;
+ int ret;
+
+ /* Initialise internal channel( which is always present ). */
+ priv->labels[0] = "internal diode";
+ priv->enabled_channel_mask = 1;
+
+ /* Default values to work on systems without devicetree or firmware nodes. */
+ if (!dev_fwnode(dev)) {
+ priv->num_channels = device_nr_channels;
+ priv->enabled_channel_mask = BIT(priv->num_channels) - 1;
+ priv->apdd_enable = false;
+ priv->recd12_enable = true;
+ priv->recd34_enable = true;
+ priv->run_state = true;
+ return 0;
+ }
+
+ priv->apdd_enable =
+ device_property_read_bool(dev, "microchip,enable-anti-parallel");
+
+ priv->recd12_enable =
+ device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2");
+
+ priv->recd34_enable =
+ device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4");
+
+ priv->run_state =
+ device_property_read_bool(dev, "microchip,power-state");
+
+ priv->num_channels = device_get_child_node_count(dev) + 1;
+
+ if (priv->num_channels > device_nr_channels)
+ return dev_err_probe(dev, -EINVAL,
+ "More channels than the chip supports.\n");
+
+ /* Read information about the external channels. */
+ device_for_each_child_node_scoped(dev, child) {
+ reg_nr = 0;
+ ret = fwnode_property_read_u32(child, "reg", ®_nr);
+ if (ret || !reg_nr || reg_nr >= device_nr_channels)
+ return dev_err_probe(dev, -EINVAL,
+ "Channel reg is incorrectly set.\n");
+
+ fwnode_property_read_string(child, "label", &priv->labels[reg_nr]);
+ set_bit(reg_nr, &priv->enabled_channel_mask);
+ }
+
+ return 0;
+}
+
+static const struct hwmon_chip_info mcp998x_chip_info = {
+ .ops = &mcp9982_hwmon_ops,
+ .info = mcp9985_info,
+};
+
+static int mcp9982_probe(struct i2c_client *client)
+{
+ const struct mcp9982_features *chip;
+ struct device *dev = &client->dev;
+ struct mcp9982_priv *priv;
+ struct device *hwmon_dev;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(struct mcp9982_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = devm_regmap_init_i2c(client, &mcp9982_regmap_config);
+
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap),
+ "Cannot initialize register map.\n");
+
+ dev_set_drvdata(dev, priv);
+
+ chip = i2c_get_match_data(client);
+ if (!chip)
+ return -EINVAL;
+ priv->chip = chip;
+
+ ret = mcp9982_parse_fw_config(dev, chip->phys_channels);
+ if (ret)
+ return ret;
+
+ ret = mcp9982_init(dev, priv);
+ if (ret)
+ return ret;
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, chip->name, priv,
+ &mcp998x_chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id mcp9982_id[] = {
+ { .name = "mcp9933", .driver_data = (kernel_ulong_t)&mcp9933_chip_config },
+ { .name = "mcp9933d", .driver_data = (kernel_ulong_t)&mcp9933d_chip_config },
+ { .name = "mcp9982", .driver_data = (kernel_ulong_t)&mcp9982_chip_config },
+ { .name = "mcp9982d", .driver_data = (kernel_ulong_t)&mcp9982d_chip_config },
+ { .name = "mcp9983", .driver_data = (kernel_ulong_t)&mcp9983_chip_config },
+ { .name = "mcp9983d", .driver_data = (kernel_ulong_t)&mcp9983d_chip_config },
+ { .name = "mcp9984", .driver_data = (kernel_ulong_t)&mcp9984_chip_config },
+ { .name = "mcp9984d", .driver_data = (kernel_ulong_t)&mcp9984d_chip_config },
+ { .name = "mcp9985", .driver_data = (kernel_ulong_t)&mcp9985_chip_config },
+ { .name = "mcp9985d", .driver_data = (kernel_ulong_t)&mcp9985d_chip_config },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mcp9982_id);
+
+static const struct of_device_id mcp9982_of_match[] = {
+ {
+ .compatible = "microchip,mcp9933",
+ .data = &mcp9933_chip_config,
+ }, {
+ .compatible = "microchip,mcp9933d",
+ .data = &mcp9933d_chip_config,
+ }, {
+ .compatible = "microchip,mcp9982",
+ .data = &mcp9982_chip_config,
+ }, {
+ .compatible = "microchip,mcp9982d",
+ .data = &mcp9982d_chip_config,
+ }, {
+ .compatible = "microchip,mcp9983",
+ .data = &mcp9983_chip_config,
+ }, {
+ .compatible = "microchip,mcp9983d",
+ .data = &mcp9983d_chip_config,
+ }, {
+ .compatible = "microchip,mcp9984",
+ .data = &mcp9984_chip_config,
+ }, {
+ .compatible = "microchip,mcp9984d",
+ .data = &mcp9984d_chip_config,
+ }, {
+ .compatible = "microchip,mcp9985",
+ .data = &mcp9985_chip_config,
+ }, {
+ .compatible = "microchip,mcp9985d",
+ .data = &mcp9985d_chip_config,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mcp9982_of_match);
+
+static struct i2c_driver mcp9982_driver = {
+ .driver = {
+ .name = "mcp9982",
+ .of_match_table = mcp9982_of_match,
+ },
+ .probe = mcp9982_probe,
+ .id_table = mcp9982_id,
+};
+module_i2c_driver(mcp9982_driver);
+
+MODULE_AUTHOR("Victor Duicu <victor.duicu@microchip.com>");
+MODULE_DESCRIPTION("MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Driver");
+MODULE_LICENSE("GPL");
--
2.51.0
On Tue, Feb 17, 2026 at 04:06:14PM +0200, Victor Duicu wrote:
> This is the driver for Microchip MCP998X/33 and MCP998XD/33D
> Multichannel Automotive Temperature Monitor Family.
>
> Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
Review feedback inline.
> ---
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/mcp9982.rst | 111 +++++
> MAINTAINERS | 2 +
> drivers/hwmon/Kconfig | 11 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/mcp9982.c | 954 ++++++++++++++++++++++++++++++++++++++++
> 6 files changed, 1080 insertions(+)
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 85d7a686883e..b02709fc216e 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -173,6 +173,7 @@ Hardware Monitoring Kernel Drivers
> mc33xs2410_hwmon
> mc34vr500
> mcp3021
> + mcp9982
> menf21bmc
> mlxreg-fan
> mp2856
> diff --git a/Documentation/hwmon/mcp9982.rst b/Documentation/hwmon/mcp9982.rst
> new file mode 100644
> index 000000000000..ceff3e69ee78
> --- /dev/null
> +++ b/Documentation/hwmon/mcp9982.rst
> @@ -0,0 +1,111 @@
> +.. SPDX-License-Identifier: GPL-2.0+
> +
> +Kernel driver MCP998X
> +=====================
> +
> +Supported chips:
> +
> + * Microchip Technology MCP998X/MCP9933 and MCP998XD/MCP9933D
> +
> + Prefix: 'mcp9982'
> +
> + Datasheet:
> + https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
> +
> +Authors:
> +
> + - Victor Duicu <victor.duicu@microchip.com>
> +
> +Description
> +-----------
> +
> +This driver implements support for the MCP998X family containing: MCP9982,
> +MCP9982D, MCP9983, MCP9983D, MCP9984, MCP9984D, MCP9985, MCP9985D,
> +MCP9933 and MCP9933D.
> +
> +The MCP998X Family is a high accuracy 2-wire multichannel automotive
> +temperature monitor.
> +
> +The chips in the family have different numbers of external channels,
> +ranging from 1 (MCP9982) to 4 channels (MCP9985). Reading diodes in
> +anti-parallel connection is supported by MCP9984/85/33 and
> +MCP9984D/85D/33D. Dedicated hardware shutdown circuitry is present
> +only in MCP998XD and MCP9933D.
> +
> +Temperatures are read in millidegrees Celsius, ranging from -64 to
> +191.875 with 0.125 precision.
> +
> +Each channel has a minimum, maximum, and critical limit alongside associated alarms.
> +The chips also implement a hysteresis mechanism which applies only to the maximum
> +and critical limits. The relative difference between a limit and its hysteresis
> +is the same for both and the value is kept in a single register.
> +
> +The chips measure temperatures with a variable conversion rate.
> +Update_interval = Conversion/Second, so the available options are:
> +- 16000 (ms) = 1 conv/16 sec
> +- 8000 (ms) = 1 conv/8 sec
> +- 4000 (ms) = 1 conv/4 sec
> +- 2000 (ms) = 1 conv/2 sec
> +- 1000 (ms) = 1 conv/sec
> +- 500 (ms) = 2 conv/sec
> +- 250 (ms) = 4 conv/sec
> +- 125 (ms) = 8 conv/sec
> +- 64 (ms) = 16 conv/sec
> +- 32 (ms) = 32 conv/sec
> +- 16 (ms) = 64 conv/sec
> +
> +Usage Notes
> +-----------
> +
> +Parameters that can be configured in devicetree:
> +- anti-parallel diode mode operation
> +- resistance error correction on channels 1 and 2
> +- resistance error correction on channels 3 and 4
> +- power state
> +
> +Chips 82/83 and 82D/83D do not support anti-parallel diode mode.
> +For chips with "D" in the name resistance error correction must be on.
> +Please see Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
> +for details.
> +
> +There are two power states:
> +- Active state: in which the chip is converting on all channels at the
> +programmed rate.
> +
> +- Standby state: in which the host must initiate a conversion cycle.
> +
> +Chips with "D" in the name work in Active state only and those without
> +can work in either state.
> +
> +Chips with "D" in the name can't set update interval slower than 1 second.
> +
> +Among the hysteresis attributes, only the tempX_crit_hyst ones are writeable
> +while the others are read only. Setting tempX_crit_hyst writes the difference
> +between tempX_crit and tempX_crit_hyst in the hysteresis register. The new value
> +applies automatically to the other limits. At power up the device starts with
> +the value 10 in the hysteresis register.
> +
> +Sysfs entries
> +-------------
> +
> +The following attributes are supported. The temperature limits and
> +update_interval are read-write. The attribute tempX_crit_hyst is read-write,
> +while tempX_max_hyst is read only. All other attributes are read only.
> +
> +======================= ==================================================
> +temp[1-5]_label User name for channel.
> +temp[1-5]_input Measured temperature for channel.
> +
> +temp[1-5]_crit Critical temperature limit.
> +temp[1-5]_crit_alarm Critical temperature limit alarm.
> +temp[1-5]_crit_hyst Critical temperature limit hysteresis.
> +
> +temp[1-5]_max High temperature limit.
> +temp[1-5]_max_alarm High temperature limit alarm.
> +temp[1-5]_max_hyst High temperature limit hysteresis.
> +
> +temp[1-5]_min Low temperature limit.
> +temp[1-5]_min_alarm Low temperature limit alarm.
> +
> +update_interval The interval at which the chip will update readings.
> +======================= ==================================================
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 026510a4129c..5c6662e10b04 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17165,6 +17165,8 @@ M: Victor Duicu <victor.duicu@microchip.com>
> L: linux-hwmon@vger.kernel.org
> S: Supported
> F: Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
> +F: Documentation/hwmon/mcp9982.rst
> +F: drivers/hwmon/mcp9982.c
>
> MICROCHIP MMC/SD/SDIO MCI DRIVER
> M: Aubin Constans <aubin.constans@microchip.com>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 157678b821fc..c758ab2d5fdf 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1388,6 +1388,17 @@ config SENSORS_MCP3021
> This driver can also be built as a module. If so, the module
> will be called mcp3021.
>
> +config SENSORS_MCP9982
> + tristate "Microchip Technology MCP9982 driver"
> + depends on I2C
> + select REGMAP_I2C
> + help
> + Say yes here to include support for Microchip Technology's MCP998X/33
> + and MCP998XD/33D Multichannel Automotive Temperature Monitor Family.
> +
> + This driver can also be built as a module. If so, the module
> + will be called mcp9982.
> +
> config SENSORS_MLXREG_FAN
> tristate "Mellanox FAN driver"
> depends on MELLANOX_PLATFORM
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index eade8e3b1bde..cec33da29a68 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -170,6 +170,7 @@ obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
> obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o
> obj-$(CONFIG_SENSORS_MC34VR500) += mc34vr500.o
> obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
> +obj-$(CONFIG_SENSORS_MCP9982) += mcp9982.o
> obj-$(CONFIG_SENSORS_TC654) += tc654.o
> obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o
> obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
> diff --git a/drivers/hwmon/mcp9982.c b/drivers/hwmon/mcp9982.c
> new file mode 100644
> index 000000000000..f13e80283404
> --- /dev/null
> +++ b/drivers/hwmon/mcp9982.c
> @@ -0,0 +1,954 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * HWMON driver for MCP998X/33 and MCP998XD/33D Multichannel Automotive
> + * Temperature Monitor Family
> + *
> + * Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries
> + *
> + * Author: Victor Duicu <victor.duicu@microchip.com>
> + *
> + * Datasheet can be found here:
> + * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
> + */
> +
> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/bits.h>
> +#include <linux/byteorder/generic.h>
> +#include <linux/delay.h>
> +#include <linux/device/devres.h>
> +#include <linux/device.h>
> +#include <linux/dev_printk.h>
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/math.h>
> +#include <linux/minmax.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/time64.h>
> +#include <linux/util_macros.h>
> +
> +/* MCP9982 Registers */
> +#define MCP9982_HIGH_BYTE_ADDR(index) (2 * (index))
> +#define MCP9982_ONE_SHOT_ADDR 0x0A
> +#define MCP9982_INTERNAL_HIGH_LIMIT_ADDR 0x0B
> +#define MCP9982_INTERNAL_LOW_LIMIT_ADDR 0x0C
> +#define MCP9982_EXT_HIGH_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0D)
> +#define MCP9982_EXT_LOW_LIMIT_ADDR(index) (4 * ((index) - 1) + 0x0F)
> +#define MCP9982_THERM_LIMIT_ADDR(index) ((index) + 0x1D)
> +#define MCP9982_CFG_ADDR 0x22
> +#define MCP9982_CONV_ADDR 0x24
> +#define MCP9982_HYS_ADDR 0x25
> +#define MCP9982_CONSEC_ALRT_ADDR 0x26
> +#define MCP9982_ALRT_CFG_ADDR 0x27
> +#define MCP9982_RUNNING_AVG_ADDR 0x28
> +#define MCP9982_HOTTEST_CFG_ADDR 0x29
> +#define MCP9982_STATUS_ADDR 0x2A
> +#define MCP9982_EXT_FAULT_STATUS_ADDR 0x2B
> +#define MCP9982_HIGH_LIMIT_STATUS_ADDR 0x2C
> +#define MCP9982_LOW_LIMIT_STATUS_ADDR 0x2D
> +#define MCP9982_THERM_LIMIT_STATUS_ADDR 0x2E
> +#define MCP9982_HOTTEST_HIGH_BYTE_ADDR 0x2F
> +#define MCP9982_HOTTEST_LOW_BYTE_ADDR 0x30
> +#define MCP9982_HOTTEST_STATUS_ADDR 0x31
> +#define MCP9982_THERM_SHTDWN_CFG_ADDR 0x32
> +#define MCP9982_HRDW_THERM_SHTDWN_LIMIT_ADDR 0x33
> +#define MCP9982_EXT_BETA_CFG_ADDR(index) ((index) + 0x33)
> +#define MCP9982_EXT_IDEAL_ADDR(index) ((index) + 0x35)
> +
> +/* MCP9982 Bits */
> +#define MCP9982_CFG_MSKAL BIT(7)
> +#define MCP9982_CFG_RS BIT(6)
> +#define MCP9982_CFG_ATTHM BIT(5)
> +#define MCP9982_CFG_RECD12 BIT(4)
> +#define MCP9982_CFG_RECD34 BIT(3)
> +#define MCP9982_CFG_RANGE BIT(2)
> +#define MCP9982_CFG_DA_ENA BIT(1)
> +#define MCP9982_CFG_APDD BIT(0)
> +
> +#define MCP9982_STATUS_BUSY BIT(5)
> +
> +/* Constants and default values */
> +#define MCP9982_MAX_NUM_CHANNELS 5
> +#define MCP9982_BETA_AUTODETECT 16
> +#define MCP9982_IDEALITY_DEFAULT 18
> +#define MCP9982_OFFSET 64
> +#define MCP9982_DEFAULT_CONSEC_ALRT_VAL 112
> +#define MCP9982_DEFAULT_HYS_VAL 10
> +#define MCP9982_DEFAULT_CONV_VAL 6
> +#define MCP9982_WAKE_UP_TIME_US 125000
> +#define MCP9982_WAKE_UP_TIME_MAX_US 130000
> +#define MCP9982_HIGH_LIMIT_DEFAULT 85000
> +#define MCP9982_LOW_LIMIT_DEFAULT 0
> +
> +static const struct hwmon_channel_info * const mcp9985_info[] = {
> + HWMON_CHANNEL_INFO(temp,
> + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
> + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
> + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
> + HWMON_T_CRIT_HYST,
> + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
> + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
> + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
> + HWMON_T_CRIT_HYST,
> + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
> + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
> + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
> + HWMON_T_CRIT_HYST,
> + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
> + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
> + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
> + HWMON_T_CRIT_HYST,
> + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
> + HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
> + HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
> + HWMON_T_CRIT_HYST),
> + HWMON_CHANNEL_INFO(chip,
> + HWMON_C_UPDATE_INTERVAL),
> + NULL
> +};
> +
> +/**
> + * struct mcp9982_features - features of a mcp9982 instance
> + * @name: chip's name
> + * @phys_channels: number of physical channels supported by the chip
> + * @hw_thermal_shutdown: presence of hardware thermal shutdown circuitry
> + * @allow_apdd: whether the chip supports enabling APDD
> + */
> +struct mcp9982_features {
> + const char *name;
> + u8 phys_channels;
> + bool hw_thermal_shutdown;
> + bool allow_apdd;
> +};
> +
> +static const struct mcp9982_features mcp9933_chip_config = {
> + .name = "mcp9933",
> + .phys_channels = 3,
> + .hw_thermal_shutdown = false,
> + .allow_apdd = true,
> +};
> +
> +static const struct mcp9982_features mcp9933d_chip_config = {
> + .name = "mcp9933d",
> + .phys_channels = 3,
> + .hw_thermal_shutdown = true,
> + .allow_apdd = true,
> +};
> +
> +static const struct mcp9982_features mcp9982_chip_config = {
> + .name = "mcp9982",
> + .phys_channels = 2,
> + .hw_thermal_shutdown = false,
> + .allow_apdd = false,
> +};
> +
> +static const struct mcp9982_features mcp9982d_chip_config = {
> + .name = "mcp9982d",
> + .phys_channels = 2,
> + .hw_thermal_shutdown = true,
> + .allow_apdd = false,
> +};
> +
> +static const struct mcp9982_features mcp9983_chip_config = {
> + .name = "mcp9983",
> + .phys_channels = 3,
> + .hw_thermal_shutdown = false,
> + .allow_apdd = false,
> +};
> +
> +static const struct mcp9982_features mcp9983d_chip_config = {
> + .name = "mcp9983d",
> + .phys_channels = 3,
> + .hw_thermal_shutdown = true,
> + .allow_apdd = false,
> +};
> +
> +static const struct mcp9982_features mcp9984_chip_config = {
> + .name = "mcp9984",
> + .phys_channels = 4,
> + .hw_thermal_shutdown = false,
> + .allow_apdd = true,
> +};
> +
> +static const struct mcp9982_features mcp9984d_chip_config = {
> + .name = "mcp9984d",
> + .phys_channels = 4,
> + .hw_thermal_shutdown = true,
> + .allow_apdd = true,
> +};
> +
> +static const struct mcp9982_features mcp9985_chip_config = {
> + .name = "mcp9985",
> + .phys_channels = 5,
> + .hw_thermal_shutdown = false,
> + .allow_apdd = true,
> +};
> +
> +static const struct mcp9982_features mcp9985d_chip_config = {
> + .name = "mcp9985d",
> + .phys_channels = 5,
> + .hw_thermal_shutdown = true,
> + .allow_apdd = true,
> +};
> +
> +static const unsigned int mcp9982_update_interval[11] = {
> + 16000, 8000, 4000, 2000, 1000, 500, 250, 125, 64, 32, 16
> +};
> +
> +/* MCP9982 regmap configuration */
> +static const struct regmap_range mcp9982_regmap_wr_ranges[] = {
> + regmap_reg_range(MCP9982_ONE_SHOT_ADDR, MCP9982_CFG_ADDR),
> + regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_HOTTEST_CFG_ADDR),
> + regmap_reg_range(MCP9982_THERM_SHTDWN_CFG_ADDR, MCP9982_THERM_SHTDWN_CFG_ADDR),
> + regmap_reg_range(MCP9982_EXT_BETA_CFG_ADDR(1), MCP9982_EXT_IDEAL_ADDR(4)),
> +};
> +
> +static const struct regmap_access_table mcp9982_regmap_wr_table = {
> + .yes_ranges = mcp9982_regmap_wr_ranges,
> + .n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_wr_ranges),
> +};
> +
> +static const struct regmap_range mcp9982_regmap_rd_ranges[] = {
> + regmap_reg_range(MCP9982_HIGH_BYTE_ADDR(0), MCP9982_CFG_ADDR),
> + regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_EXT_IDEAL_ADDR(4)),
> +};
> +
> +static const struct regmap_access_table mcp9982_regmap_rd_table = {
> + .yes_ranges = mcp9982_regmap_rd_ranges,
> + .n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_rd_ranges),
> +};
> +
> +static bool mcp9982_is_volatile_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case MCP9982_ONE_SHOT_ADDR:
> + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
> + case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
> + case MCP9982_EXT_LOW_LIMIT_ADDR(1):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(1) + 1:
> + case MCP9982_EXT_LOW_LIMIT_ADDR(2):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(2) + 1:
> + case MCP9982_EXT_LOW_LIMIT_ADDR(3):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(3) + 1:
> + case MCP9982_EXT_LOW_LIMIT_ADDR(4):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(4) + 1:
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(1) + 1:
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(2) + 1:
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(3) + 1:
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(4) + 1:
> + case MCP9982_THERM_LIMIT_ADDR(0):
> + case MCP9982_THERM_LIMIT_ADDR(1):
> + case MCP9982_THERM_LIMIT_ADDR(2):
> + case MCP9982_THERM_LIMIT_ADDR(3):
> + case MCP9982_THERM_LIMIT_ADDR(4):
> + case MCP9982_CFG_ADDR:
> + case MCP9982_CONV_ADDR:
> + case MCP9982_HYS_ADDR:
> + case MCP9982_CONSEC_ALRT_ADDR:
> + case MCP9982_ALRT_CFG_ADDR:
> + case MCP9982_RUNNING_AVG_ADDR:
> + case MCP9982_HOTTEST_CFG_ADDR:
> + case MCP9982_THERM_SHTDWN_CFG_ADDR:
> + return false;
> + default:
> + return true;
> + }
> +}
> +
> +static const struct regmap_config mcp9982_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .rd_table = &mcp9982_regmap_rd_table,
> + .wr_table = &mcp9982_regmap_wr_table,
> + .volatile_reg = mcp9982_is_volatile_reg,
> + .max_register = MCP9982_EXT_IDEAL_ADDR(4),
> + .cache_type = REGCACHE_MAPLE,
> +};
> +
> +/**
> + * struct mcp9982_priv - information about chip parameters
> + * @regmap: device register map
> + * @chip: pointer to structure holding chip features
> + * @labels: labels of the channels
> + * @interval_idx: index representing the current update interval
> + * @enabled_channel_mask: mask containing which channels should be enabled
> + * @num_channels: number of active physical channels
> + * @recd34_enable: state of Resistance Error Correction(REC) on channels 3 and 4
> + * @recd12_enable: state of Resistance Error Correction(REC) on channels 1 and 2
> + * @apdd_enable: state of anti-parallel diode mode
> + * @run_state: chip is in Run state, otherwise is in Standby state
> + */
> +struct mcp9982_priv {
> + struct regmap *regmap;
> + const struct mcp9982_features *chip;
> + const char *labels[MCP9982_MAX_NUM_CHANNELS];
> + unsigned int interval_idx;
> + unsigned long enabled_channel_mask;
> + u8 num_channels;
> + bool recd34_enable;
> + bool recd12_enable;
> + bool apdd_enable;
> + bool run_state;
> +};
> +
> +static int mcp9982_read_limit(struct mcp9982_priv *priv, u8 address, long *val)
> +{
> + unsigned int limit, reg_high, reg_low;
> + int ret;
> +
> + switch (address) {
> + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
> + case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
> + case MCP9982_THERM_LIMIT_ADDR(0):
> + case MCP9982_THERM_LIMIT_ADDR(1):
> + case MCP9982_THERM_LIMIT_ADDR(2):
> + case MCP9982_THERM_LIMIT_ADDR(3):
> + case MCP9982_THERM_LIMIT_ADDR(4):
> + ret = regmap_read(priv->regmap, address, &limit);
> + if (ret)
> + return ret;
> +
> + *val = ((int)limit - MCP9982_OFFSET) * 1000;
> +
> + return 0;
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(1):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(2):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(3):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(4):
> + /*
> + * The MCP998X family is designed so that block reading is allowed
> + * only on the dedicated temperature and status memory blocks.
> + * Reading from those memory areas uses SMbus, while from any other
> + * region I2C is used and only one byte readings are allowed.
> + *
> + * This behavior is described in the documentation at page 26.
> + *
> + * When reading the temperature limits only single byte reads
> + * are allowed.
> + */
> + ret = regmap_read(priv->regmap, address, ®_high);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(priv->regmap, address + 1, ®_low);
> + if (ret)
> + return ret;
> +
> + *val = ((reg_high << 8) + reg_low) >> 5;
> + *val = (*val - (MCP9982_OFFSET << 3)) * 125;
> +
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
> + long *val)
> +{
> + struct mcp9982_priv *priv = dev_get_drvdata(dev);
> + unsigned int reg_high, reg_low;
> + int ret, hyst;
The variable 'hyst' is declared as 'int', but passed to 'regmap_read' which
expects 'unsigned int *'. This should be corrected to 'unsigned int'.
> + u8 addr;
> +
> + /* In Standby State the conversion cycle must be initiated manually. */
> + if (!priv->run_state) {
> + ret = regmap_write(priv->regmap, MCP9982_ONE_SHOT_ADDR, 1);
> + if (ret)
> + return ret;
> + usleep_range(MCP9982_WAKE_UP_TIME_US, MCP9982_WAKE_UP_TIME_MAX_US);
The driver sleeps for 125ms on every read in standby mode. This sleep is
applied even when reading static attributes like update_interval or limits.
Furthermore, 125ms is a fixed value that may not match the actual conversion
time, which depends on the conversion rate. It is better to only trigger
one-shot for temperature input and status reads, and to poll the BUSY bit
in the status register instead of a fixed sleep.
> + }
> +
> + switch (type) {
> + case hwmon_temp:
> + switch (attr) {
> + case hwmon_temp_input:
> + /* Block reading from addresses 0x00->0x09 is not allowed. */
> + ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel), ®_high);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel) + 1,
> + ®_low);
> + if (ret)
> + return ret;
Reading the 11-bit temperature value involves two separate 8-bit register reads.
If the chip updates the temperature between these two reads, the resulting value
may be torn. While some chips latch the low byte upon reading the high byte,
the driver does not explicitly rely on or document this behavior, and it's safer
to use regmap_bulk_read if supported, or at least ensure the correct order and
atomicity if possible.
Note: Maybe the low temperature is latched, but there is no indication in the
datasheet that this would be the case. Even if it is, the code above is
inefficient.
> +
> + *val = ((reg_high << 8) + reg_low) >> 5;
> + *val = (*val - (MCP9982_OFFSET << 3)) * 125;
> +
> + return 0;
> + case hwmon_temp_max:
> + if (channel)
> + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
> + else
> + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
> +
> + return mcp9982_read_limit(priv, addr, val);
> + case hwmon_temp_max_alarm:
> + *val = regmap_test_bits(priv->regmap, MCP9982_HIGH_LIMIT_STATUS_ADDR,
> + BIT(channel));
> + if (*val < 0)
> + return *val;
> +
> + return 0;
> + case hwmon_temp_max_hyst:
> + if (channel)
> + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
> + else
> + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
> + ret = mcp9982_read_limit(priv, addr, val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
> + if (ret)
> + return ret;
> +
> + *val -= hyst * 1000;
> +
> + return 0;
> + case hwmon_temp_min:
> + if (channel)
> + addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
> + else
> + addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
> +
> + return mcp9982_read_limit(priv, addr, val);
> + case hwmon_temp_min_alarm:
> + *val = regmap_test_bits(priv->regmap, MCP9982_LOW_LIMIT_STATUS_ADDR,
> + BIT(channel));
> + if (*val < 0)
> + return *val;
> +
> + return 0;
> + case hwmon_temp_crit:
> + return mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
> + case hwmon_temp_crit_alarm:
> + *val = regmap_test_bits(priv->regmap, MCP9982_THERM_LIMIT_STATUS_ADDR,
> + BIT(channel));
> + if (*val < 0)
> + return *val;
> +
> + return 0;
> + case hwmon_temp_crit_hyst:
> + ret = mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
> + if (ret)
> + return ret;
> +
> + *val -= hyst * 1000;
> +
> + return 0;
> + default:
> + return -EINVAL;
> + }
> + case hwmon_chip:
> + switch (attr) {
> + case hwmon_chip_update_interval:
> + *val = mcp9982_update_interval[priv->interval_idx];
> + return 0;
> + default:
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int mcp9982_read_label(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> + int channel, const char **str)
> +{
> + struct mcp9982_priv *priv = dev_get_drvdata(dev);
> +
> + switch (type) {
> + case hwmon_temp:
> + switch (attr) {
> + case hwmon_temp_label:
> + *str = priv->labels[channel];
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int mcp9982_write_limit(struct mcp9982_priv *priv, u8 address, long val)
> +{
> + int ret;
> + unsigned int regh, regl;
> +
> + /* Range is always -64 to 191.875°C. */
> + val = clamp_val(val, -64000, 191875);
> + val = (val + MCP9982_OFFSET * 1000) << 5;
> + val = DIV_ROUND_CLOSEST(val, 125);
> +
> + switch (address) {
> + case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
> + case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
> + case MCP9982_THERM_LIMIT_ADDR(0):
> + case MCP9982_THERM_LIMIT_ADDR(1):
> + case MCP9982_THERM_LIMIT_ADDR(2):
> + case MCP9982_THERM_LIMIT_ADDR(3):
> + case MCP9982_THERM_LIMIT_ADDR(4):
> + val = val >> 8;
Consider rounding instead of truncating the lower 8 bit
when writing limits.
> + ret = regmap_write(priv->regmap, address, val);
> + if (ret)
> + return ret;
> +
> + return 0;
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
> + case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(1):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(2):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(3):
> + case MCP9982_EXT_LOW_LIMIT_ADDR(4):
> + regl = val & 0xFF;
> + regh = val >> 8;
> +
> + /* Block write to addresses 0x0D->0x1C is not allowed. */
> + ret = regmap_write(priv->regmap, address, regh);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, address + 1, regl);
> + if (ret)
> + return ret;
> +
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int mcp9982_write_hyst(struct mcp9982_priv *priv, int channel, long val)
> +{
> + int hyst, ret;
> + int limit;
> +
> + val = clamp_val(val, -64000, 191875);
> + val = (val + MCP9982_OFFSET * 1000) << 5;
> + val = DIV_ROUND_CLOSEST(val, 125);
> + val = val >> 8;
> +
> + /* Therm register is 8 bits and so it keeps only the integer part of the temperature. */
> + ret = regmap_read(priv->regmap, MCP9982_THERM_LIMIT_ADDR(channel), &limit);
> + if (ret)
> + return ret;
> +
> + hyst = clamp_val(limit - val, 0, 255);
> +
> + ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, hyst);
> +
> + return ret;
> +}
> +
> +static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
> + long val)
> +{
> + struct mcp9982_priv *priv = dev_get_drvdata(dev);
> + unsigned int idx;
> + u8 addr;
> + int ret;
> +
> + switch (type) {
> + case hwmon_chip:
> + switch (attr) {
> + case hwmon_chip_update_interval:
> +
> + /*
> + * For MCP998XD and MCP9933D update interval
> + * can't be slower than 1 second.
> + */
> + if (priv->chip->hw_thermal_shutdown)
> + val = clamp(val, 0, 1000);
> +
> + idx = find_closest_descending(val, mcp9982_update_interval,
> + ARRAY_SIZE(mcp9982_update_interval));
> +
> + ret = regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx);
> + if (ret)
> + return ret;
> +
> + priv->interval_idx = idx;
> +
> + return 0;
> + default:
> + return -EINVAL;
> + }
> + case hwmon_temp:
> + switch (attr) {
> + case hwmon_temp_max:
> + if (channel)
> + addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
> + else
> + addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
> +
> + return mcp9982_write_limit(priv, addr, val);
> + case hwmon_temp_min:
> + if (channel)
> + addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
> + else
> + addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
> +
> + return mcp9982_write_limit(priv, addr, val);
> + case hwmon_temp_crit:
> + return mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
> + case hwmon_temp_crit_hyst:
> + return mcp9982_write_hyst(priv, channel, val);
> + default:
> + return -EINVAL;
> + }
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static umode_t mcp9982_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
> + int channel)
> +{
> + const struct mcp9982_priv *priv = _data;
> +
> + if (!test_bit(channel, &priv->enabled_channel_mask))
> + return 0;
> +
> + switch (type) {
> + case hwmon_temp:
> + switch (attr) {
> + case hwmon_temp_label:
> + if (priv->labels[channel])
> + return 0444;
> + else
> + return 0;
> + case hwmon_temp_input:
> + case hwmon_temp_min_alarm:
> + case hwmon_temp_max_alarm:
> + case hwmon_temp_max_hyst:
> + case hwmon_temp_crit_alarm:
> + return 0444;
> + case hwmon_temp_min:
> + case hwmon_temp_max:
> + case hwmon_temp_crit:
> + case hwmon_temp_crit_hyst:
> + return 0644;
> + default:
> + return 0;
> + }
> + case hwmon_chip:
> + switch (attr) {
> + case hwmon_chip_update_interval:
> + return 0644;
> + default:
> + return 0;
> + }
> + default:
> + return 0;
> + }
> +}
> +
> +static const struct hwmon_ops mcp9982_hwmon_ops = {
> + .is_visible = mcp9982_is_visible,
> + .read = mcp9982_read,
> + .read_string = mcp9982_read_label,
> + .write = mcp9982_write,
> +};
> +
> +static int mcp9982_init(struct device *dev, struct mcp9982_priv *priv)
> +{
> + unsigned int i;
> + int ret;
> + u8 val;
> +
> + /* Chips 82/83 and 82D/83D do not support anti-parallel diode mode. */
> + if (!priv->chip->allow_apdd && priv->apdd_enable == 1)
> + return dev_err_probe(dev, -EINVAL, "Incorrect setting of APDD.\n");
> +
> + /* Chips with "D" work only in Run state. */
> + if (priv->chip->hw_thermal_shutdown && !priv->run_state)
> + return dev_err_probe(dev, -EINVAL, "Incorrect setting of Power State.\n");
> +
> + /*
> + * For chips with "D" in the name, resistance error correction must be
> + * on so that hardware shutdown feature can't be overridden.
> + */
> + if (priv->chip->hw_thermal_shutdown)
> + if (!priv->recd34_enable || !priv->recd12_enable)
> + return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD.\n");
> +
> + /*
> + * Set default values in registers.
> + * APDD, RECD12 and RECD34 are active on 0.
> + */
> + val = FIELD_PREP(MCP9982_CFG_MSKAL, 1) |
> + FIELD_PREP(MCP9982_CFG_RS, !priv->run_state) |
> + FIELD_PREP(MCP9982_CFG_ATTHM, 1) |
> + FIELD_PREP(MCP9982_CFG_RECD12, !priv->recd12_enable) |
> + FIELD_PREP(MCP9982_CFG_RECD34, !priv->recd34_enable) |
> + FIELD_PREP(MCP9982_CFG_RANGE, 1) | FIELD_PREP(MCP9982_CFG_DA_ENA, 0) |
> + FIELD_PREP(MCP9982_CFG_APDD, !priv->apdd_enable);
> +
> + ret = regmap_write(priv->regmap, MCP9982_CFG_ADDR, val);
> + if (ret)
> + return ret;
> +
> + /* Read initial value from register */
> + ret = regmap_read(priv->regmap, MCP9982_CONV_ADDR, &priv->interval_idx);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, MCP9982_DEFAULT_HYS_VAL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MCP9982_CONSEC_ALRT_ADDR, MCP9982_DEFAULT_CONSEC_ALRT_VAL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MCP9982_ALRT_CFG_ADDR, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MCP9982_RUNNING_AVG_ADDR, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MCP9982_HOTTEST_CFG_ADDR, 0);
> + if (ret)
> + return ret;
> +
> + /*
> + * Only external channels 1 and 2 support beta compensation.
> + * Set beta auto-detection.
> + */
> + for (i = 1; i < 3; i++)
> + if (test_bit(i, &priv->enabled_channel_mask)) {
> + ret = regmap_write(priv->regmap, MCP9982_EXT_BETA_CFG_ADDR(i),
> + MCP9982_BETA_AUTODETECT);
> + if (ret)
> + return ret;
> + }
> +
> + /* Set default values for internal channel limits. */
> + if (test_bit(0, &priv->enabled_channel_mask)) {
> + ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_HIGH_LIMIT_ADDR,
> + MCP9982_HIGH_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_LOW_LIMIT_ADDR,
> + MCP9982_LOW_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(0),
> + MCP9982_HIGH_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> + }
> +
> + /* Set ideality factor and limits to default for external channels. */
> + for (i = 1; i < MCP9982_MAX_NUM_CHANNELS; i++)
> + if (test_bit(i, &priv->enabled_channel_mask)) {
> + ret = regmap_write(priv->regmap, MCP9982_EXT_IDEAL_ADDR(i),
> + MCP9982_IDEALITY_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_EXT_HIGH_LIMIT_ADDR(i),
> + MCP9982_HIGH_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_EXT_LOW_LIMIT_ADDR(i),
> + MCP9982_LOW_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(i),
> + MCP9982_HIGH_LIMIT_DEFAULT);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int mcp9982_parse_fw_config(struct device *dev, int device_nr_channels)
> +{
> + struct mcp9982_priv *priv = dev_get_drvdata(dev);
> + unsigned int reg_nr;
> + int ret;
> +
> + /* Initialise internal channel( which is always present ). */
> + priv->labels[0] = "internal diode";
> + priv->enabled_channel_mask = 1;
> +
> + /* Default values to work on systems without devicetree or firmware nodes. */
> + if (!dev_fwnode(dev)) {
> + priv->num_channels = device_nr_channels;
> + priv->enabled_channel_mask = BIT(priv->num_channels) - 1;
> + priv->apdd_enable = false;
> + priv->recd12_enable = true;
> + priv->recd34_enable = true;
> + priv->run_state = true;
> + return 0;
> + }
> +
> + priv->apdd_enable =
> + device_property_read_bool(dev, "microchip,enable-anti-parallel");
> +
> + priv->recd12_enable =
> + device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2");
> +
> + priv->recd34_enable =
> + device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4");
> +
> + priv->run_state =
> + device_property_read_bool(dev, "microchip,power-state");
> +
> + priv->num_channels = device_get_child_node_count(dev) + 1;
> +
> + if (priv->num_channels > device_nr_channels)
> + return dev_err_probe(dev, -EINVAL,
> + "More channels than the chip supports.\n");
> +
> + /* Read information about the external channels. */
> + device_for_each_child_node_scoped(dev, child) {
This assumes that each child node is a channel. It would pe brudent to verify
the node name to ensure that it reflects a channel.
> + reg_nr = 0;
> + ret = fwnode_property_read_u32(child, "reg", ®_nr);
> + if (ret || !reg_nr || reg_nr >= device_nr_channels)
> + return dev_err_probe(dev, -EINVAL,
> + "Channel reg is incorrectly set.\n");
> +
> + fwnode_property_read_string(child, "label", &priv->labels[reg_nr]);
> + set_bit(reg_nr, &priv->enabled_channel_mask);
> + }
> +
> + return 0;
> +}
> +
> +static const struct hwmon_chip_info mcp998x_chip_info = {
> + .ops = &mcp9982_hwmon_ops,
> + .info = mcp9985_info,
> +};
> +
> +static int mcp9982_probe(struct i2c_client *client)
> +{
> + const struct mcp9982_features *chip;
> + struct device *dev = &client->dev;
> + struct mcp9982_priv *priv;
> + struct device *hwmon_dev;
> + int ret;
> +
> + priv = devm_kzalloc(dev, sizeof(struct mcp9982_priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->regmap = devm_regmap_init_i2c(client, &mcp9982_regmap_config);
> +
> + if (IS_ERR(priv->regmap))
> + return dev_err_probe(dev, PTR_ERR(priv->regmap),
> + "Cannot initialize register map.\n");
> +
> + dev_set_drvdata(dev, priv);
> +
> + chip = i2c_get_match_data(client);
> + if (!chip)
> + return -EINVAL;
> + priv->chip = chip;
> +
> + ret = mcp9982_parse_fw_config(dev, chip->phys_channels);
> + if (ret)
> + return ret;
> +
> + ret = mcp9982_init(dev, priv);
> + if (ret)
> + return ret;
> +
> + hwmon_dev = devm_hwmon_device_register_with_info(dev, chip->name, priv,
> + &mcp998x_chip_info, NULL);
> +
> + return PTR_ERR_OR_ZERO(hwmon_dev);
> +}
> +
> +static const struct i2c_device_id mcp9982_id[] = {
> + { .name = "mcp9933", .driver_data = (kernel_ulong_t)&mcp9933_chip_config },
> + { .name = "mcp9933d", .driver_data = (kernel_ulong_t)&mcp9933d_chip_config },
> + { .name = "mcp9982", .driver_data = (kernel_ulong_t)&mcp9982_chip_config },
> + { .name = "mcp9982d", .driver_data = (kernel_ulong_t)&mcp9982d_chip_config },
> + { .name = "mcp9983", .driver_data = (kernel_ulong_t)&mcp9983_chip_config },
> + { .name = "mcp9983d", .driver_data = (kernel_ulong_t)&mcp9983d_chip_config },
> + { .name = "mcp9984", .driver_data = (kernel_ulong_t)&mcp9984_chip_config },
> + { .name = "mcp9984d", .driver_data = (kernel_ulong_t)&mcp9984d_chip_config },
> + { .name = "mcp9985", .driver_data = (kernel_ulong_t)&mcp9985_chip_config },
> + { .name = "mcp9985d", .driver_data = (kernel_ulong_t)&mcp9985d_chip_config },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, mcp9982_id);
> +
> +static const struct of_device_id mcp9982_of_match[] = {
> + {
> + .compatible = "microchip,mcp9933",
> + .data = &mcp9933_chip_config,
> + }, {
> + .compatible = "microchip,mcp9933d",
> + .data = &mcp9933d_chip_config,
> + }, {
> + .compatible = "microchip,mcp9982",
> + .data = &mcp9982_chip_config,
> + }, {
> + .compatible = "microchip,mcp9982d",
> + .data = &mcp9982d_chip_config,
> + }, {
> + .compatible = "microchip,mcp9983",
> + .data = &mcp9983_chip_config,
> + }, {
> + .compatible = "microchip,mcp9983d",
> + .data = &mcp9983d_chip_config,
> + }, {
> + .compatible = "microchip,mcp9984",
> + .data = &mcp9984_chip_config,
> + }, {
> + .compatible = "microchip,mcp9984d",
> + .data = &mcp9984d_chip_config,
> + }, {
> + .compatible = "microchip,mcp9985",
> + .data = &mcp9985_chip_config,
> + }, {
> + .compatible = "microchip,mcp9985d",
> + .data = &mcp9985d_chip_config,
> + },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, mcp9982_of_match);
> +
> +static struct i2c_driver mcp9982_driver = {
> + .driver = {
> + .name = "mcp9982",
> + .of_match_table = mcp9982_of_match,
> + },
> + .probe = mcp9982_probe,
> + .id_table = mcp9982_id,
> +};
> +module_i2c_driver(mcp9982_driver);
> +
> +MODULE_AUTHOR("Victor Duicu <victor.duicu@microchip.com>");
> +MODULE_DESCRIPTION("MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Driver");
> +MODULE_LICENSE("GPL");
Hi Guenter,
...
> > + }
> > +
> > + switch (type) {
> > + case hwmon_temp:
> > + switch (attr) {
> > + case hwmon_temp_input:
> > + /* Block reading from addresses 0x00->0x09 is
> > not allowed. */
> > + ret = regmap_read(priv->regmap,
> > MCP9982_HIGH_BYTE_ADDR(channel), ®_high);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_read(priv->regmap,
> > MCP9982_HIGH_BYTE_ADDR(channel) + 1,
> > + ®_low);
> > + if (ret)
> > + return ret;
>
> Reading the 11-bit temperature value involves two separate 8-bit
> register reads.
> If the chip updates the temperature between these two reads, the
> resulting value
> may be torn. While some chips latch the low byte upon reading the
> high byte,
> the driver does not explicitly rely on or document this behavior, and
> it's safer
> to use regmap_bulk_read if supported, or at least ensure the correct
> order and
> atomicity if possible.
>
> Note: Maybe the low temperature is latched, but there is no
> indication in the
> datasheet that this would be the case. Even if it is, the code above
> is
> inefficient.
The low temperature register is latched. In the documentation at
page 32 it is described that when reading the high byte register,
the value from the low byte register is copied into a 'shadow'
register. In this way it is guaranteed that when we read the low byte,
it will correspond to the high byte.
Regarding the bulk read, the chip has a number of design quirks and
because of that different commands are supported only on some
particular memory regions.
According to the documentation page 26, the only areas of memory that
support SMBus block read are 80h->89h(temperature memory block) and
90h->97h(status memory block). In order to block read the temperatures,
the area of memory targeted has to be the temperature memory block. In
this context the read operation uses SMBus protocol and the first value
returned will be the number of addresses that can be read (in our
particular case a max value of 10 bytes).
In v8 of the driver
https://lore.kernel.org/all/20251120071248.3767-1-victor.duicu@microchip.com/
,
the temperature values were read with regmap_bulk_read(). In that
version, regmap_bulk_read() was also used to read the temperature
limits, without returning count (this is an undocumented feature of the
chip and because of that we could assume is not supported).
In order to avoid this behaviour and avoid mixing the SMBus and I2C
protocols all block readings were removed.
In the hopes of bypassing a long chain of replies, I tested the
behaviour of the chip with different read instructions.
Regmap_bulk_read() when applied to the temperature memory block
(80h->89h) returns count and the high and low bytes. When it is applied
to the 00h->09h memory, it uses I2C. It returns one temperature byte,
but all other bytes are returned as 0xFF. The chip behaves as if
it is at the last register location in the temperature block while the
host continues to ACK.(behaviour described at page 26).
If we set use_single_read in regmap_config and apply regmap_bulk_read()
to the 00h->09h register area the high and low temperature bytes are
read successfully without count.
Regmap_multi_reg_read() reads a number of registers one by one. When
applied to the 00h->09h area, I2C is used and it returns only the high
and low temperature bytes. When applied to the temperature memory block
(80h->89h), because it is not a bulk function, returns the count till
the end of the temperature memory block (aka SMBus count).
I2c_smbus_read_block_data() when applied to the temperature block (80h-
89h) returns the count, the driver replies with an NACK and the
communication is stopped. In our case, the board we are using to test
the driver has an AT91 adapter and supports
I2C_FUNC_SMBUS_READ_BLOCK_DATA. It seems that the I2C driver for AT91
does not modify the buff length of the message, leaving it 1.
I2c_smbus_read_i2c_block_data() when applied to the temperature block
(80h-89h) returns count and the temperature values.
If you are of the opinion that block reading the temperatures is worth
introducing (even in case we need to skip count) then I can add it, but
we should come to an agreement on which function to use.
Please let me know your thoughts.
Kind regards,
Victor
On 3/30/26 05:01, Victor.Duicu@microchip.com wrote:
> Hi Guenter,
>
> ...
>
>>> + }
>>> +
>>> + switch (type) {
>>> + case hwmon_temp:
>>> + switch (attr) {
>>> + case hwmon_temp_input:
>>> + /* Block reading from addresses 0x00->0x09 is
>>> not allowed. */
>>> + ret = regmap_read(priv->regmap,
>>> MCP9982_HIGH_BYTE_ADDR(channel), ®_high);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + ret = regmap_read(priv->regmap,
>>> MCP9982_HIGH_BYTE_ADDR(channel) + 1,
>>> + ®_low);
>>> + if (ret)
>>> + return ret;
>>
>> Reading the 11-bit temperature value involves two separate 8-bit
>> register reads.
>> If the chip updates the temperature between these two reads, the
>> resulting value
>> may be torn. While some chips latch the low byte upon reading the
>> high byte,
>> the driver does not explicitly rely on or document this behavior, and
>> it's safer
>> to use regmap_bulk_read if supported, or at least ensure the correct
>> order and
>> atomicity if possible.
>>
>> Note: Maybe the low temperature is latched, but there is no
>> indication in the
>> datasheet that this would be the case. Even if it is, the code above
>> is
>> inefficient.
>
> The low temperature register is latched. In the documentation at
> page 32 it is described that when reading the high byte register,
> the value from the low byte register is copied into a 'shadow'
> register. In this way it is guaranteed that when we read the low byte,
> it will correspond to the high byte.
>
> Regarding the bulk read, the chip has a number of design quirks and
> because of that different commands are supported only on some
> particular memory regions.
>
> According to the documentation page 26, the only areas of memory that
> support SMBus block read are 80h->89h(temperature memory block) and
> 90h->97h(status memory block). In order to block read the temperatures,
> the area of memory targeted has to be the temperature memory block. In
> this context the read operation uses SMBus protocol and the first value
> returned will be the number of addresses that can be read (in our
> particular case a max value of 10 bytes).
>
> In v8 of the driver
> https://lore.kernel.org/all/20251120071248.3767-1-victor.duicu@microchip.com/
> ,
> the temperature values were read with regmap_bulk_read(). In that
> version, regmap_bulk_read() was also used to read the temperature
> limits, without returning count (this is an undocumented feature of the
> chip and because of that we could assume is not supported).
> In order to avoid this behaviour and avoid mixing the SMBus and I2C
> protocols all block readings were removed.
>
> In the hopes of bypassing a long chain of replies, I tested the
> behaviour of the chip with different read instructions.
> Regmap_bulk_read() when applied to the temperature memory block
> (80h->89h) returns count and the high and low bytes. When it is applied
> to the 00h->09h memory, it uses I2C. It returns one temperature byte,
> but all other bytes are returned as 0xFF. The chip behaves as if
> it is at the last register location in the temperature block while the
> host continues to ACK.(behaviour described at page 26).
> If we set use_single_read in regmap_config and apply regmap_bulk_read()
> to the 00h->09h register area the high and low temperature bytes are
> read successfully without count.
>
> Regmap_multi_reg_read() reads a number of registers one by one. When
> applied to the 00h->09h area, I2C is used and it returns only the high
> and low temperature bytes. When applied to the temperature memory block
> (80h->89h), because it is not a bulk function, returns the count till
> the end of the temperature memory block (aka SMBus count).
>
> I2c_smbus_read_block_data() when applied to the temperature block (80h-
> 89h) returns the count, the driver replies with an NACK and the
> communication is stopped. In our case, the board we are using to test
> the driver has an AT91 adapter and supports
> I2C_FUNC_SMBUS_READ_BLOCK_DATA. It seems that the I2C driver for AT91
> does not modify the buff length of the message, leaving it 1.
>
> I2c_smbus_read_i2c_block_data() when applied to the temperature block
> (80h-89h) returns count and the temperature values.
>
> If you are of the opinion that block reading the temperatures is worth
> introducing (even in case we need to skip count) then I can add it, but
> we should come to an agreement on which function to use.
> Please let me know your thoughts.
>
It is your chip, so I'll let you decide. Please include all the above
as comments into the code.
Thanks,
Guenter
© 2016 - 2026 Red Hat, Inc.