* [PATCH v10 0/2] add support in hwmon for MCP998X
@ 2026-02-17 14:06 Victor Duicu
2026-02-17 14:06 ` [PATCH v10 1/2] dt-bindings: hwmon: add support " Victor Duicu
2026-02-17 14:06 ` [PATCH v10 2/2] " Victor Duicu
0 siblings, 2 replies; 10+ messages in thread
From: Victor Duicu @ 2026-02-17 14:06 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, victor.duicu,
marius.cristea
Add support in hwmon for Microchip MCP998X/33 and MCP998XD/33D
Multichannel Automotive Temperature Monitor Family.
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.
The driver supports reading the temperature channels, the temperature
limits, the limit hysteresis and their corresponding alarms. The user can
set the limits, the update interval and the hysteresis value through
the critical limit hysteresis.
Differences related to previous patch:
v10:
- in devicetree rework interrupt-names.
- rework conditionals to disable the channels that are not used.
- in patternProperties remove minItems from reg and change maxItems to 1.
- in mcp9982.rst fix indentation errors and clarify sysfs entries
regarding limit hysteresis.
- add power state in the list of devicetree parameters.
- in driver change default high and critical temperature limit
value to 85000.
- rework driver to not force wait until the next conversion is done.
Only in Standby state wait the wake up time.
- fix typo in comment about mcp9982_priv.
- remove bitwise operations with mask FF.
- remove clamp_val() from limit hysteresis calculation.
- edit comments regarding block read and write.
- in mcp9982_write_limit() replace bulk write with two writes.
- in mcp9982_write() remove pointless assignment to ret.
- in mcp9982_init() do not override update interval.
- define mcp998x_chip_info as static const struct.
- link to v9: https://lore.kernel.org/all/20260127151823.9728-1-victor.duicu@microchip.com/
v9:
- update copyright year.
- add tempX_max_hyst and tempX_crit_hyst attributes and document
them in mcp9982.rst.
- in include list add byteorder/generic.h and remove unaligned.h.
- remove definitions for temperature memory block
and status memory block.
- remove individual definitions for register addresses 1Dh->21h.
- add constants MCP9982_WAKE_UP_TIME_MAX_US and
MCP9982_TIMER_BUFFER_US.
- add checks to ensure that values read from registers are on 8 bits.
- in mcp9982_read_limit() simplify calculation, replace bulk read
with individual operations and add comment.
- in mcp9982_read_limit() add explicit case branches for limits
that are on 16 bits.
- in mcp9982_read() replace mdelay() with usleep_range().
- in mcp9982_read() replace block reading for temperature values with
individual operations, add comment and remove unnecessary
mask variable.
- in regmap_read_poll_timeout() add final timeout.
- in mcp9982_read_label() remove label check.
- in mcp9982_write_limit() replace put_unaligned_be16() with cpu_to_be16().
- in mcp9982_write_limit() add explicit case branches for limits
that are on 16 bits.
- in mcp9982_init() write default value for diode alert mask register.
- in mcp9982_parse_fw_config() replace E2BIG with EINVAL.
- link to v8: https://lore.kernel.org/all/20251120071248.3767-1-victor.duicu@microchip.com/
v8:
- in Kconfig add select REGMAP_I2C.
- in yaml add power state attribute. For chips with "D" in the name
check that Run mode is set in yaml and driver.
- in the include list: remove cleanup.h, add math.h, minmax.h and
util_macros.h.
- add min, max and crit limits for all channels. These attributes can
be read and written. In mcp9982_init() set default values for limits.
- add alarms for limits.
- edit regmap ranges to add the limit registers.
- when writing update interval, don't force the user to set exact value.
Search for closest valid value.
- in mcp9982_parse_fw_config() check value from fwnode_property_read_u32().
- edit coding style and comments.
- remove constant MCP9982_SCALE.
- rename variable sampl_idx from mcp9982_priv to interval_idx.
- in mcp9982_write() rename variable use_previous_freq
to use_previous_interval.
- link to v7: https://lore.kernel.org/all/20251031155831.42763-1-victor.duicu@microchip.com/
v7:
- send driver to hwmon subsystem.
- include index.rst and mcp9982.rst.
- in microchip,mcp9982.yaml set microchip,parasitic-res-on-channel1-2
and 3-4 to required.
- in mcp9982.c rework read raw, read label and write raw functions.
- remove avail parameters.
- rework sampling frequency to update interval.
- lock running average filter to off.
- rework definition of channels.
- add cache type Maple to mcp9982_regmap_config().
- define constants for the numerical values used.
- in include list add: bitops.h, cleanup.h, device.h, hwmon.h,
time64.h, unaligned.h. Remove iio.h, math64.h, string.h, units.h.
- add explicit definitions for beta and ideality registers.
- add definitions for status memory block registers.
- add mcp9982_is_visible() and set visible only the channels
that are enabled.
- in mcp9982_parse_fw_config() add branch with default values for
systems that do not have devicetree or firmware nodes.
- remove mutex.
- link to v6: https://lore.kernel.org/all/20250930133131.13797-1-victor.duicu@microchip.com/
Differences related to the IIO patch:
v6:
- in yaml first condition list part numbers instead
of regular expression. Add ^ to regular expression.
- edit coding style and comments.
- use hex values in defines.
- remove MCP9982_TEMP_MEM_BLOCK_LOW and
MCP9982_TEMP_MEM_BLOCK_HIGH.
- in MCP9982_CHAN() place macro parameters in ().
- move all variable definitions at the start of functions.
- in mcp9982_parse_fw_config() initialise iio_idx to 0.
- remove bit flags.
- in MCP9982_CHAN remove outer ().
- remove variable start in mcp9982_write_raw().
- replace constant in .max_register.
- use get_unaligned_be16 in mcp9982_read_raw().
- link to v5: https://lore.kernel.org/all/20250918111937.5150-1-victor.duicu@microchip.com/
v5:
- in yaml edit description of interrupts.
- add min and maxItems to reg.
- remove ideality parameter.
- use pattern recognition in conditionals.
- group conditions based on the chip.
- correct microchip,parasitic-res-on-channel3-4 to true.
- in driver include bitops.h.
- change name of some variables.
- rename mcp9982_parse_of_config() to mcp9982_parse_fw_config().
- implement bulk reading of temp registers.
- lock ideality parameter to default value.
- implement bit flags.
- add compound literal to MCP9982_CHAN.
- remove hysteresis parameter.
- edit comments.
- change values from int to bool in mcp9982_features.
- remove mcp9982_calc_all_3db_values() and hardcode values.
When filter is OFF the 3db value is equal to frequency.
- add .max_register to regmap_config.
- remove devm_kcalloc().
- in mcp9982_read_avail() add an else branch to hw_thermal_shutdown
check.
- in mcp9982_read_raw use USEC_PER_MSEC and set regmap_read_poll_timeout
to never timeout.
Replace switch with bitmap_weight.
- in mcp9982_read_label() remove unnecessary if.
- in mcp9982_write_raw() remove duplicated code.
- in mcp9982_init add error messages when APDD and RECD are incorrectly
set.
- in mcp9982_parse_fw_config() add default for reg_nr.
- link to v4: https://lore.kernel.org/all/20250829143447.18893-1-victor.duicu@microchip.com/
v4:
- lock beta parameters to default value of beta-autodetect.
Remove beta parameters and checks from devicetree.
- lock temperature range to extended.
This change avoids the issue of the average filter using raw values
with different scales when changing the range.
- change driver to wait an amount of time before reading a raw value
to ensure it is valid.
- change driver to stop calculating the physical temp when reading
in_tempx_raw. Reading from in_tempx_raw will return the raw value.
The physical temp will be calculated with in_tempx_raw, scale and
offset parameters.
- add scale parameter to channel definition.
- initialise chips with "D" to work in Run state and those without
in Standby state.
- when activating the low pass filter for chips without "D",
set the power state to RUN to ensure fresh values for the average.
- add minimum and maximum to microchip,beta1 and microchip,beta2 in yaml.
- rename microchip,resistance-comp-ch1-2-enable and
microchip,resistance-comp-ch3-4-enable to
microchip,parasitic-res-on-channel1-2
and microchip,parasitic-res-on-channel3-4
and edit description in yaml.
- add conditional logic to check if the chip supports APDD
and force default values where necessary in yaml.
- edit comments and coding style.
- replace asm/div64.h with linux/math64.h.
- add delay.h to includes.
- redefine mcp9982_sampl_fr with new structure division.
- in mcp9982_priv remove dev_name,extended_temp_range and beta_values.
Add run_state, wait_before_read, time_limit and pointer to chip
structure to remove all instances of matching strings.
Reorder parameters for memory optimization.
- in mcp9982_features add flags to know if the chip has thermal shutdown
circuitry and supports APDD.
- in mcp9982_read_avail() rework verification of chip type in sampling
frequency case.
- in mcp9982_read_raw() rework switch in low pass filter case.
- in mcp9982_parse_of_config() replace generic -EINVAL code
with -E2BIG and -EOVERFLOW.
- link to v3: https://lore.kernel.org/all/20250613130207.8560-1-victor.duicu@microchip.com/
v3:
- move beta parameters to devicetree.
- change the name of the interrupts and add
check to match them to the device in yaml.
- remove label for device and remove "0x" from
channel registers in example in yaml.
- edit comments in yaml and driver.
- add minItems to interrupts in yaml.
- rename microchip,recd12 and microchip,recd34 to
microchip,resistance-comp-ch1-2-enable
and microchip,resistance-comp-ch3-4-enable.
- rename microchip,apdd-state to microchip,enable-anti-parallel.
- add static to mcp9982_3db_values_map_tbl to fix
kernel test robot warning.
- in mcp9982_init() add check to ensure that hardware
shutdown feature can't be overridden.
- replace div_u64_rem with do_div and add
asm/div64.h to includes.
- remove unused includes.
- add iio_chan_spec in the macro definition of MCP9982_CHAN.
- remove MCP9982_EXT_BETA_ENBL.
- in mcp9982_init() replace regmap_assign_bits
with regmap_write when setting beta compensation.
- remove custom attribute enable_extended_temp_range and
map it to IIO_CHAN_INFO_OFFSET.
- add unsigned to int variables that allow it.
- reorder parameters in mcp9982_priv, change some
from int to bool, add const to labels and add dev_name.
- add check for chips with "D" in the name to not
allow sampling frequencies lower than 1 to
prevent overriding of hardware shutdown.
- remove mcp9982_attributes.
- move mcp9982_calc_all_3db_values() to before
mcp9982_init().
- use MICRO instead of number constant.
- in mcp9982_write_raw replace ">=" with "==".
- rename index2 to idx in mcp9982_read_raw().
- remove i2c_set_clientdata() in mcp9982_probe().
- since there are no more custom ABI attributes
the testing file was removed.
- link to v2: https://lore.kernel.org/all/20250529093628.15042-1-victor.duicu@microchip.com/
v2:
- move hysteresis, extended temperature range and beta parameters
from devicetree into user space.
- edit comments in yaml and driver.
- remove "|" in descpriptions, remove "+" from PatternProperties in yaml.
- add default to microchip,ideality-factor, delete blank lines and wrap to
80 chars in yaml.
- remove variables with upper case.
- add check for microchip,apdd-state and microchip,recd34 in yaml.
- improve coding style in driver code.
- add includes for all functions used.
- rename MCP9982_INT_HIGH_BYTE_ADDR to MCP9982_INT_VALUE_ADDR and
MCP9982_INT_LOW_BYTE_ADDR to MCP9982_FRAC_VALUE_ADDR.
- remove custom attribute running_average_window and
running_average_window_available and map them to a low pass filter.
- update sysfs-bus-iio-temperature-mcp9982 to reflect current
driver attributes and point to next kernel version (6.17).
- use compound literal to define driver channels.
- replace device_property_read_string() with i2c_get_match_data() to read
chip name from devicetree.
- remove MCP9982_DEV_ATTR and mcp9982_prep_custom_attributes().
- remove client, chip_name, iio_info from mcp9982_priv.
- replace sprintf() with sysfs_emit().
- remove error messages which are triggered by keyboard input.
- replace devm_kzalloc() with devm_kcalloc(), array mcp9982_chip_config[]
with individual structures, device_property_present() with
device_property_read_bool().
- reordered parameters in mcp9982_features and mcp9982_priv to optimize
memory allocation.
- remove .endianness from channel properties.
- change name of some parameters in mcp9982_priv.
- add check for reg value 0 from devicetree (channel 0 is for internal
temperature and can't be disabled).
- link to v1: https://lore.kernel.org/all/20250415132623.14913-1-victor.duicu@microchip.com/
v1:
- initial version.
Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
---
Victor Duicu (2):
dt-bindings: hwmon: add support for MCP998X
hwmon: add support for MCP998X
.../bindings/hwmon/microchip,mcp9982.yaml | 223 +++++
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/mcp9982.rst | 111 +++
MAINTAINERS | 8 +
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/mcp9982.c | 954 +++++++++++++++++++++
7 files changed, 1309 insertions(+)
---
base-commit: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
change-id: 20260209-add-mcp9982-hwmon-a118367839c6
Best regards,
--
Victor Duicu <victor.duicu@microchip.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v10 1/2] dt-bindings: hwmon: add support for MCP998X
2026-02-17 14:06 [PATCH v10 0/2] add support in hwmon for MCP998X Victor Duicu
@ 2026-02-17 14:06 ` Victor Duicu
2026-02-17 20:11 ` Krzysztof Kozlowski
2026-02-17 14:06 ` [PATCH v10 2/2] " Victor Duicu
1 sibling, 1 reply; 10+ messages in thread
From: Victor Duicu @ 2026-02-17 14:06 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, victor.duicu,
marius.cristea
This is the devicetree schema for Microchip MCP998X/33 and MCP998XD/33D
Multichannel Automotive Temperature Monitor Family.
Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
---
.../bindings/hwmon/microchip,mcp9982.yaml | 223 +++++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 229 insertions(+)
diff --git a/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml b/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
new file mode 100644
index 000000000000..b59c64e9886d
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
@@ -0,0 +1,223 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/microchip,mcp9982.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip MCP998X/33 and MCP998XD/33D Temperature Monitor
+
+maintainers:
+ - Victor Duicu <victor.duicu@microchip.com>
+
+description: |
+ The MCP998X/33 and MCP998XD/33D family is a high-accuracy 2-wire
+ multichannel automotive temperature monitor.
+ The datasheet can be found here:
+ https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
+
+properties:
+ compatible:
+ enum:
+ - microchip,mcp9933
+ - microchip,mcp9933d
+ - microchip,mcp9982
+ - microchip,mcp9982d
+ - microchip,mcp9983
+ - microchip,mcp9983d
+ - microchip,mcp9984
+ - microchip,mcp9984d
+ - microchip,mcp9985
+ - microchip,mcp9985d
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 2
+
+ interrupt-names:
+ description:
+ The chip family has three different interrupt pins divided among them.
+ The chips without "D" have alert-therm and therm-addr.
+ The chips with "D" have alert-therm and sys-shtdwn.
+ minItems: 1
+ maxItems: 2
+ items:
+ enum: [alert-therm, therm-addr, sys-shtdwn]
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ microchip,enable-anti-parallel:
+ description:
+ Enable anti-parallel diode mode operation.
+ MCP9984/84D/85/85D and MCP9933/33D support reading two external diodes
+ in anti-parallel connection on the same set of pins.
+ type: boolean
+
+ microchip,parasitic-res-on-channel1-2:
+ description:
+ Indicates that the chip and the diodes/transistors are sufficiently far
+ apart that a parasitic resistance is added to the wires, which can affect
+ the measurements. Due to the anti-parallel diode connections, channels
+ 1 and 2 are affected together.
+ type: boolean
+
+ microchip,parasitic-res-on-channel3-4:
+ description:
+ Indicates that the chip and the diodes/transistors are sufficiently far
+ apart that a parasitic resistance is added to the wires, which can affect
+ the measurements. Due to the anti-parallel diode connections, channels
+ 3 and 4 are affected together.
+ type: boolean
+
+ microchip,power-state:
+ description:
+ The chip can be set in Run state or Standby state. In Run state the ADC
+ is converting on all channels at the programmed conversion rate.
+ In Standby state the host must initiate a conversion cycle by writing
+ to the One-Shot register.
+ True value sets Run state.
+ Chips with "D" in the name can only be set in Run mode.
+ type: boolean
+
+ vdd-supply: true
+
+patternProperties:
+ "^channel@[1-4]$":
+ description:
+ Represents the external temperature channels to which
+ a remote diode is connected.
+ type: object
+
+ properties:
+ reg:
+ items:
+ maxItems: 1
+
+ label:
+ description: Unique name to identify which channel this is.
+
+ required:
+ - reg
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - vdd-supply
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - microchip,mcp9982d
+ - microchip,mcp9983d
+ - microchip,mcp9984d
+ - microchip,mcp9985d
+ - microchip,mcp9933d
+ then:
+ properties:
+ interrupt-names:
+ not:
+ contains:
+ const: therm-addr
+ required:
+ - microchip,power-state
+ - microchip,parasitic-res-on-channel1-2
+ - microchip,parasitic-res-on-channel3-4
+ else:
+ properties:
+ microchip,power-state: true
+ interrupt-names:
+ not:
+ contains:
+ const: sys-shtdwn
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - microchip,mcp9982
+ - microchip,mcp9982d
+ then:
+ properties:
+ microchip,enable-anti-parallel: false
+ patternProperties:
+ "^channel@[2-4]$": false
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - microchip,mcp9983
+ - microchip,mcp9983d
+ then:
+ properties:
+ microchip,enable-anti-parallel: false
+ patternProperties:
+ "^channel@[3-4]$": false
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - microchip,mcp9933
+ - microchip,mcp9933d
+ then:
+ patternProperties:
+ "^channel@[3-4]$": false
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - microchip,mcp9984
+ - microchip,mcp9984d
+ then:
+ properties:
+ channel@4: false
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ temperature-sensor@4c {
+ compatible = "microchip,mcp9985";
+ reg = <0x4c>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ microchip,enable-anti-parallel;
+ microchip,parasitic-res-on-channel1-2;
+ microchip,parasitic-res-on-channel3-4;
+ vdd-supply = <&vdd>;
+
+ channel@1 {
+ reg = <1>;
+ label = "Room Temperature";
+ };
+
+ channel@2 {
+ reg = <2>;
+ label = "GPU Temperature";
+ };
+ };
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index e08767323763..026510a4129c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17160,6 +17160,12 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/adc/microchip,mcp3911.yaml
F: drivers/iio/adc/mcp3911.c
+MICROCHIP MCP9982 TEMPERATURE DRIVER
+M: Victor Duicu <victor.duicu@microchip.com>
+L: linux-hwmon@vger.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
+
MICROCHIP MMC/SD/SDIO MCI DRIVER
M: Aubin Constans <aubin.constans@microchip.com>
S: Maintained
--
2.51.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v10 2/2] hwmon: add support for MCP998X
2026-02-17 14:06 [PATCH v10 0/2] add support in hwmon for MCP998X Victor Duicu
2026-02-17 14:06 ` [PATCH v10 1/2] dt-bindings: hwmon: add support " Victor Duicu
@ 2026-02-17 14:06 ` Victor Duicu
2026-03-08 16:56 ` Guenter Roeck
1 sibling, 1 reply; 10+ messages in thread
From: Victor Duicu @ 2026-02-17 14:06 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, victor.duicu,
marius.cristea
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
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v10 1/2] dt-bindings: hwmon: add support for MCP998X
2026-02-17 14:06 ` [PATCH v10 1/2] dt-bindings: hwmon: add support " Victor Duicu
@ 2026-02-17 20:11 ` Krzysztof Kozlowski
2026-02-20 14:58 ` Victor.Duicu
0 siblings, 1 reply; 10+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-17 20:11 UTC (permalink / raw)
To: Victor Duicu
Cc: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, linux-hwmon, devicetree, linux-kernel, linux-doc,
marius.cristea
On Tue, Feb 17, 2026 at 04:06:13PM +0200, Victor Duicu wrote:
> This is the devicetree schema for Microchip MCP998X/33 and MCP998XD/33D
"Add"
See submitting patches.
> Multichannel Automotive Temperature Monitor Family.
...
> +properties:
> + compatible:
> + enum:
> + - microchip,mcp9933
> + - microchip,mcp9933d
> + - microchip,mcp9982
> + - microchip,mcp9982d
> + - microchip,mcp9983
> + - microchip,mcp9983d
> + - microchip,mcp9984
> + - microchip,mcp9984d
> + - microchip,mcp9985
> + - microchip,mcp9985d
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
Your interrupt-names say 1 item is correct, so these are de-synced. They
should be always constrained the same way.
> + maxItems: 2
> +
> + interrupt-names:
> + description:
> + The chip family has three different interrupt pins divided among them.
> + The chips without "D" have alert-therm and therm-addr.
> + The chips with "D" have alert-therm and sys-shtdwn.
> + minItems: 1
> + maxItems: 2
> + items:
> + enum: [alert-therm, therm-addr, sys-shtdwn]
> +
> + "#address-cells":
> + const: 1
> +
> + "#size-cells":
> + const: 0
> +
> + microchip,enable-anti-parallel:
> + description:
> + Enable anti-parallel diode mode operation.
> + MCP9984/84D/85/85D and MCP9933/33D support reading two external diodes
> + in anti-parallel connection on the same set of pins.
> + type: boolean
> +
> + microchip,parasitic-res-on-channel1-2:
> + description:
> + Indicates that the chip and the diodes/transistors are sufficiently far
> + apart that a parasitic resistance is added to the wires, which can affect
> + the measurements. Due to the anti-parallel diode connections, channels
> + 1 and 2 are affected together.
> + type: boolean
> +
> + microchip,parasitic-res-on-channel3-4:
> + description:
> + Indicates that the chip and the diodes/transistors are sufficiently far
> + apart that a parasitic resistance is added to the wires, which can affect
> + the measurements. Due to the anti-parallel diode connections, channels
> + 3 and 4 are affected together.
> + type: boolean
> +
> + microchip,power-state:
> + description:
> + The chip can be set in Run state or Standby state. In Run state the ADC
> + is converting on all channels at the programmed conversion rate.
> + In Standby state the host must initiate a conversion cycle by writing
> + to the One-Shot register.
> + True value sets Run state.
> + Chips with "D" in the name can only be set in Run mode.
> + type: boolean
> +
> + vdd-supply: true
> +
> +patternProperties:
> + "^channel@[1-4]$":
> + description:
> + Represents the external temperature channels to which
> + a remote diode is connected.
> + type: object
> +
> + properties:
> + reg:
> + items:
> + maxItems: 1
> +
> + label:
> + description: Unique name to identify which channel this is.
> +
> + required:
> + - reg
> +
> + additionalProperties: false
> +
> +required:
> + - compatible
> + - reg
> + - vdd-supply
> +
> +allOf:
> + - if:
> + properties:
> + compatible:
> + contains:
> + enum:
> + - microchip,mcp9982d
> + - microchip,mcp9983d
> + - microchip,mcp9984d
> + - microchip,mcp9985d
> + - microchip,mcp9933d
> + then:
> + properties:
Missing constraints for interrupt:
> + interrupt-names:
> + not:
> + contains:
> + const: therm-addr
No, you need to list the items. This *must* be strictly constrained.
It's explicitly requested by writing bindings.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v10 1/2] dt-bindings: hwmon: add support for MCP998X
2026-02-17 20:11 ` Krzysztof Kozlowski
@ 2026-02-20 14:58 ` Victor.Duicu
2026-02-21 14:34 ` Krzysztof Kozlowski
0 siblings, 1 reply; 10+ messages in thread
From: Victor.Duicu @ 2026-02-20 14:58 UTC (permalink / raw)
To: krzk
Cc: corbet, linux, linux-hwmon, devicetree, robh, linux-kernel,
krzk+dt, linux-doc, conor+dt, Marius.Cristea
Hi Krzysztof,
>
>
> ...
>
>
> > +properties:
> > + compatible:
> > + enum:
> > + - microchip,mcp9933
> > + - microchip,mcp9933d
> > + - microchip,mcp9982
> > + - microchip,mcp9982d
> > + - microchip,mcp9983
> > + - microchip,mcp9983d
> > + - microchip,mcp9984
> > + - microchip,mcp9984d
> > + - microchip,mcp9985
> > + - microchip,mcp9985d
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + interrupts:
>
> Your interrupt-names say 1 item is correct, so these are de-synced.
> They
> should be always constrained the same way.
>
We want to allow the user to use none, one or both interrupts.
It was my mistake to set maxItems instead of minItems to interrupts.
Do you find the following approach agreeable?
interrupts:
minItems: 1
interrupt-names:
minItems: 1
items:
- enum: [alert-therm, therm-addr, sys-shtdwn]
- enum: [therm-addr, sys-shtdwn]
...
Thank you for your reply,
Victor
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v10 1/2] dt-bindings: hwmon: add support for MCP998X
2026-02-20 14:58 ` Victor.Duicu
@ 2026-02-21 14:34 ` Krzysztof Kozlowski
2026-02-23 11:09 ` Victor.Duicu
0 siblings, 1 reply; 10+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-21 14:34 UTC (permalink / raw)
To: Victor.Duicu
Cc: corbet, linux, linux-hwmon, devicetree, robh, linux-kernel,
krzk+dt, linux-doc, conor+dt, Marius.Cristea
On 20/02/2026 15:58, Victor.Duicu@microchip.com wrote:
> Hi Krzysztof,
>
>>
>>
>> ...
>>
>>
>>> +properties:
>>> + compatible:
>>> + enum:
>>> + - microchip,mcp9933
>>> + - microchip,mcp9933d
>>> + - microchip,mcp9982
>>> + - microchip,mcp9982d
>>> + - microchip,mcp9983
>>> + - microchip,mcp9983d
>>> + - microchip,mcp9984
>>> + - microchip,mcp9984d
>>> + - microchip,mcp9985
>>> + - microchip,mcp9985d
>>> +
>>> + reg:
>>> + maxItems: 1
>>> +
>>> + interrupts:
>>
>> Your interrupt-names say 1 item is correct, so these are de-synced.
>> They
>> should be always constrained the same way.
>>
>
> We want to allow the user to use none, one or both interrupts.
Who is the "user" here? IOW, can the *hardware* work correctly without
the interrupt line connected anywhere?
> It was my mistake to set maxItems instead of minItems to interrupts.
> Do you find the following approach agreeable?
>
> interrupts:
> minItems: 1
missing maxItems
>
> interrupt-names:
> minItems: 1
> items:
> - enum: [alert-therm, therm-addr, sys-shtdwn]
> - enum: [therm-addr, sys-shtdwn]
If any combination is allowed, then it is correct code.
>
> ...
>
> Thank you for your reply,
> Victor
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v10 1/2] dt-bindings: hwmon: add support for MCP998X
2026-02-21 14:34 ` Krzysztof Kozlowski
@ 2026-02-23 11:09 ` Victor.Duicu
2026-02-23 11:23 ` Krzysztof Kozlowski
0 siblings, 1 reply; 10+ messages in thread
From: Victor.Duicu @ 2026-02-23 11:09 UTC (permalink / raw)
To: krzk
Cc: corbet, linux, linux-hwmon, devicetree, robh, linux-kernel,
krzk+dt, linux-doc, conor+dt, Marius.Cristea
> >
...
> > > > +properties:
> > > > + compatible:
> > > > + enum:
> > > > + - microchip,mcp9933
> > > > + - microchip,mcp9933d
> > > > + - microchip,mcp9982
> > > > + - microchip,mcp9982d
> > > > + - microchip,mcp9983
> > > > + - microchip,mcp9983d
> > > > + - microchip,mcp9984
> > > > + - microchip,mcp9984d
> > > > + - microchip,mcp9985
> > > > + - microchip,mcp9985d
> > > > +
> > > > + reg:
> > > > + maxItems: 1
> > > > +
> > > > + interrupts:
> > >
> > > Your interrupt-names say 1 item is correct, so these are de-
> > > synced.
> > > They
> > > should be always constrained the same way.
> > >
> >
> > We want to allow the user to use none, one or both interrupts.
>
> Who is the "user" here? IOW, can the *hardware* work correctly
> without
> the interrupt line connected anywhere?
>
Yes, the hardware can work while the interrupt lines are not connected.
At the moment the driver does not support interrupts.
...
Best regards,
Victor
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v10 1/2] dt-bindings: hwmon: add support for MCP998X
2026-02-23 11:09 ` Victor.Duicu
@ 2026-02-23 11:23 ` Krzysztof Kozlowski
2026-03-04 8:15 ` Victor.Duicu
0 siblings, 1 reply; 10+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-23 11:23 UTC (permalink / raw)
To: Victor.Duicu
Cc: corbet, linux, linux-hwmon, devicetree, robh, linux-kernel,
krzk+dt, linux-doc, conor+dt, Marius.Cristea
On 23/02/2026 12:09, Victor.Duicu@microchip.com wrote:
>
>>>
> ...
>
>>>>> +properties:
>>>>> + compatible:
>>>>> + enum:
>>>>> + - microchip,mcp9933
>>>>> + - microchip,mcp9933d
>>>>> + - microchip,mcp9982
>>>>> + - microchip,mcp9982d
>>>>> + - microchip,mcp9983
>>>>> + - microchip,mcp9983d
>>>>> + - microchip,mcp9984
>>>>> + - microchip,mcp9984d
>>>>> + - microchip,mcp9985
>>>>> + - microchip,mcp9985d
>>>>> +
>>>>> + reg:
>>>>> + maxItems: 1
>>>>> +
>>>>> + interrupts:
>>>>
>>>> Your interrupt-names say 1 item is correct, so these are de-
>>>> synced.
>>>> They
>>>> should be always constrained the same way.
>>>>
>>>
>>> We want to allow the user to use none, one or both interrupts.
>>
>> Who is the "user" here? IOW, can the *hardware* work correctly
>> without
>> the interrupt line connected anywhere?
>>
>
> Yes, the hardware can work while the interrupt lines are not connected.
Almost there...
> At the moment the driver does not support interrupts.
...and ruined it. This does not matter and using it as argument means I
do not believe you actually checked if hardware can work without
interrupt lines connected. You only checked the driver. Please read your
datasheet carefully.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v10 1/2] dt-bindings: hwmon: add support for MCP998X
2026-02-23 11:23 ` Krzysztof Kozlowski
@ 2026-03-04 8:15 ` Victor.Duicu
0 siblings, 0 replies; 10+ messages in thread
From: Victor.Duicu @ 2026-03-04 8:15 UTC (permalink / raw)
To: krzk
Cc: corbet, linux, linux-hwmon, devicetree, robh, linux-kernel,
krzk+dt, linux-doc, conor+dt, Marius.Cristea
Hi Krzysztof,
...
> > > > > > +properties:
> > > > > > + compatible:
> > > > > > + enum:
> > > > > > + - microchip,mcp9933
> > > > > > + - microchip,mcp9933d
> > > > > > + - microchip,mcp9982
> > > > > > + - microchip,mcp9982d
> > > > > > + - microchip,mcp9983
> > > > > > + - microchip,mcp9983d
> > > > > > + - microchip,mcp9984
> > > > > > + - microchip,mcp9984d
> > > > > > + - microchip,mcp9985
> > > > > > + - microchip,mcp9985d
> > > > > > +
> > > > > > + reg:
> > > > > > + maxItems: 1
> > > > > > +
> > > > > > + interrupts:
> > > > >
> > > > > Your interrupt-names say 1 item is correct, so these are de-
> > > > > synced.
> > > > > They
> > > > > should be always constrained the same way.
> > > > >
> > > >
> > > > We want to allow the user to use none, one or both interrupts.
> > >
> > > Who is the "user" here? IOW, can the *hardware* work correctly
> > > without
> > > the interrupt line connected anywhere?
> > >
> >
> > Yes, the hardware can work while the interrupt lines are not
> > connected.
>
> Almost there...
>
> > At the moment the driver does not support interrupts.
>
>
> ...and ruined it. This does not matter and using it as argument means
> I
> do not believe you actually checked if hardware can work without
> interrupt lines connected. You only checked the driver. Please read
> your
> datasheet carefully.
>
> Best regards,
> Krzysztof
As described on page 11 of the documentation the alert-therm,
therm-addr and sys-shtdwn pins are of type open-drain.
Most of the devices need a pull-up resistor in order to setup the I2C
address(for parts with name ending in "-A") or to control the
hardware thermal shutdown limit(for chips with "D" in the name).
In all other cases it is recommended to connect a pull-up resistor to
the pin, but it is not mandatory. If the hardware does not use that
signal, the pull-up can be missing.
If the interrupt functionality is used then it's mandatory to use a
pull-up resistor.
In my view the "user" is the person doing the design for a system
which includes the MCP998X chip. Please correct me if I'm wrong,
I am under the impression that the device tree is a hardware descriptor
for the driver to know how the hardware is setup/configured but
should not supersede the datasheet for designing new systems.
Kind regards,
Victor
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v10 2/2] hwmon: add support for MCP998X
2026-02-17 14:06 ` [PATCH v10 2/2] " Victor Duicu
@ 2026-03-08 16:56 ` Guenter Roeck
0 siblings, 0 replies; 10+ messages in thread
From: Guenter Roeck @ 2026-03-08 16:56 UTC (permalink / raw)
To: Victor Duicu
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
linux-hwmon, devicetree, linux-kernel, linux-doc, marius.cristea
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");
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-03-08 16:57 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-17 14:06 [PATCH v10 0/2] add support in hwmon for MCP998X Victor Duicu
2026-02-17 14:06 ` [PATCH v10 1/2] dt-bindings: hwmon: add support " Victor Duicu
2026-02-17 20:11 ` Krzysztof Kozlowski
2026-02-20 14:58 ` Victor.Duicu
2026-02-21 14:34 ` Krzysztof Kozlowski
2026-02-23 11:09 ` Victor.Duicu
2026-02-23 11:23 ` Krzysztof Kozlowski
2026-03-04 8:15 ` Victor.Duicu
2026-02-17 14:06 ` [PATCH v10 2/2] " Victor Duicu
2026-03-08 16:56 ` Guenter Roeck
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox