linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/3] hwmon: add Microchip EMC2101 driver
@ 2025-07-09 16:48 Álvaro Fernández Rojas
  2025-07-09 16:48 ` [PATCH v3 1/3] docs: hwmon: add emc2101.rst to docs Álvaro Fernández Rojas
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Álvaro Fernández Rojas @ 2025-07-09 16:48 UTC (permalink / raw)
  To: jdelvare, linux, robh, krzk+dt, conor+dt, corbet, linux-hwmon,
	devicetree, linux-kernel, linux-doc
  Cc: Álvaro Fernández Rojas

The Microchip EMC2101 is a SMBus 2.0 fan controller with temperature
monitoring.

It supports up to 1 fan, 1 internal temperature sensor, 1 external
temperature sensor and an 8 entry look up table to create a
programmable temperature response.

v3: multiple improvements:
 - rst: drop emc2101-r (same chip, loads config from EEPROM).
 - dt: fix errors, remove patternProperties and drop emc2101-r.
 - Switch to regmap-i2c.
 - Use regmap fields.
 - Add Power Management support.
 - Demote dev_info() to dev_dbg().
 - Remove "emc2101-r" i2c_device_id.
 - Remove "microchip,emc2101-r" of_device_id.
 - Properly implement standby mode.
 - Use milliseconds instead of millihertz for update_interval.
 - Drop mutex except for FAN_LUT_DISABLE and TEMP_EXT_CRIT_UNLOCK.
 - Fix u16 fraction temperature conversions.
 - Other code cleanups and refactors.

v2: multiple improvements:
 - add emc2101.rst to index.rst.
 - add missing documentation properties.
 - Remove FAN_RPM_MIN definition.
 - Rename FAN_FALSE_READ to FAN_MIN_READ.
 - pwm_auto_point_temp_hyst_store(): simplify function.
 - emc2101_fan_min_read(): add missing FAN_MIN_READ condition.
 - emc2101_fan_min_write(): fix tach_count calculation.
 - emc2101_init(): fix REG_TACH_MIN value.

Álvaro Fernández Rojas (3):
  docs: hwmon: add emc2101.rst to docs
  dt-bindings: hwmon: Add Microchip EMC2101 support
  drivers: hwmon: add EMC2101 driver

 .../bindings/hwmon/microchip,emc2101.yaml     |   59 +
 Documentation/hwmon/emc2101.rst               |   61 +
 Documentation/hwmon/index.rst                 |    1 +
 drivers/hwmon/Kconfig                         |   11 +
 drivers/hwmon/Makefile                        |    1 +
 drivers/hwmon/emc2101.c                       | 2176 +++++++++++++++++
 6 files changed, 2309 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/microchip,emc2101.yaml
 create mode 100644 Documentation/hwmon/emc2101.rst
 create mode 100644 drivers/hwmon/emc2101.c

-- 
2.39.5


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3 1/3] docs: hwmon: add emc2101.rst to docs
  2025-07-09 16:48 [PATCH v3 0/3] hwmon: add Microchip EMC2101 driver Álvaro Fernández Rojas
@ 2025-07-09 16:48 ` Álvaro Fernández Rojas
  2025-07-09 16:48 ` [PATCH v3 2/3] dt-bindings: hwmon: Add Microchip EMC2101 support Álvaro Fernández Rojas
  2025-07-09 16:48 ` [PATCH v3 3/3] drivers: hwmon: add EMC2101 driver Álvaro Fernández Rojas
  2 siblings, 0 replies; 7+ messages in thread
From: Álvaro Fernández Rojas @ 2025-07-09 16:48 UTC (permalink / raw)
  To: jdelvare, linux, robh, krzk+dt, conor+dt, corbet, linux-hwmon,
	devicetree, linux-kernel, linux-doc
  Cc: Álvaro Fernández Rojas

Add description of emc2101 driver.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
---
 Documentation/hwmon/emc2101.rst | 61 +++++++++++++++++++++++++++++++++
 Documentation/hwmon/index.rst   |  1 +
 2 files changed, 62 insertions(+)
 create mode 100644 Documentation/hwmon/emc2101.rst

 v3: drop emc2101-r (same chip, loads config from EEPROM).

 v2: add emc2101 to index.rst

diff --git a/Documentation/hwmon/emc2101.rst b/Documentation/hwmon/emc2101.rst
new file mode 100644
index 000000000000..3a8d61fc9de3
--- /dev/null
+++ b/Documentation/hwmon/emc2101.rst
@@ -0,0 +1,61 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver emc2101
+=====================
+
+Supported chips:
+   Microchip EMC2101
+
+   Addresses scanned: I2C 0x4c
+
+   Prefix: 'emc2101'
+
+   Datasheet: Publicly available at the Microchip website :
+      https://www.microchip.com/en-us/product/EMC2101
+
+Description:
+------------
+This driver implements support for Microchip EMC2101 RPM-based PWM Fan Controller.
+The EMC2101 Fan Controller supports up to 1 controlled PWM fan based on an
+external temperature diode.
+Fan rotation speed is reported in RPM.
+The driver supports the eight entries temperature look up table to automatically
+adjust the fan speed.
+
+The driver provides the following sysfs interfaces in hwmon subsystem:
+
+========================= == ===================================================
+fan1_div                  RW file for fan target duty cycle divider (0..255)
+fan1_input                RO file for TACH1 input (in RPM)
+fan1_min                  RW file for TACH1 min RPM
+fan1_min_alarm            RO file for TACH1 min RPM alarm indication
+fan1_spin_up_abort        RW file for fan spin up abort on low RPM
+fan1_spin_up_power        RW file for fan spin up power (in percentage)
+fan1_spin_up_time         RW file for fan spin up time (in ms)
+fan1_standby              RW file for fan standby mode
+pwm1                      RW file for fan target duty cycle (0..63)
+pwm1_auto_channels_temp   RW file for fan temperature sensor (external, force)
+pwm1_auto_point[1-8]_pwm  RW files for look up table fan speed
+pwm1_auto_point[1-8]_temp RW files for look up table temperature
+pwm1_auto_point_temp_hyst RW file for look up table temperature hysteresis
+pwm1_enable               RW file for fan config (manual, look up table)
+pwm1_freq                 RW file for fan target frequency
+pwm1_mode                 RW file for pwm mode (DAC, PWM)
+pwm1_polarity_invert      RW file for fan polarity inversion
+temp[1-3]_label           RO files for temperature labels
+temp1_input               RO file for internal temperature
+temp1_max                 RW file for max internal temperature
+temp1_max_alarm           RO file for max internal temperature alarm indication
+temp2_crit                RW file for crit external temperature
+temp2_crit_alarm          RO file for crit external temperature alarm indication
+temp2_crit_hyst           RW file for crit external temperature hysteresis
+temp2_fault               RO file for external temperature failure indication
+temp2_input               RO file for external temperature
+temp2_max                 RW file for max external temperature
+temp2_max_alarm           RO file for max external temperature alarm indication
+temp2_min                 RW file for min external temperature
+temp2_min_alarm           RO file for min external temperature alarm indication
+temp2_type                RW file for external temperature type (CPU, 2N3904)
+temp3                     RW file for forced temperature
+update_interval           RW file for temperature sensor update interval
+========================= == ===================================================
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index b45bfb4ebf30..c068462764d0 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -69,6 +69,7 @@ Hardware Monitoring Kernel Drivers
    ds1621
    ds620
    emc1403
+   emc2101
    emc2103
    emc2305
    emc6w201
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v3 2/3] dt-bindings: hwmon: Add Microchip EMC2101 support
  2025-07-09 16:48 [PATCH v3 0/3] hwmon: add Microchip EMC2101 driver Álvaro Fernández Rojas
  2025-07-09 16:48 ` [PATCH v3 1/3] docs: hwmon: add emc2101.rst to docs Álvaro Fernández Rojas
@ 2025-07-09 16:48 ` Álvaro Fernández Rojas
  2025-07-10 10:00   ` Krzysztof Kozlowski
  2025-07-09 16:48 ` [PATCH v3 3/3] drivers: hwmon: add EMC2101 driver Álvaro Fernández Rojas
  2 siblings, 1 reply; 7+ messages in thread
From: Álvaro Fernández Rojas @ 2025-07-09 16:48 UTC (permalink / raw)
  To: jdelvare, linux, robh, krzk+dt, conor+dt, corbet, linux-hwmon,
	devicetree, linux-kernel, linux-doc
  Cc: Álvaro Fernández Rojas

Introduce yaml schema for Microchip emc2101 pwm fan controller with
temperature monitoring.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
---
 .../bindings/hwmon/microchip,emc2101.yaml     | 59 +++++++++++++++++++
 1 file changed, 59 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/microchip,emc2101.yaml

 v3: fix errors, remove patternProperties and drop emc2101-r.

 v2: add missing properties.

diff --git a/Documentation/devicetree/bindings/hwmon/microchip,emc2101.yaml b/Documentation/devicetree/bindings/hwmon/microchip,emc2101.yaml
new file mode 100644
index 000000000000..10167747f748
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/microchip,emc2101.yaml
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/microchip,emc2101.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip EMC2101 SMBus compliant PWM fan controller
+
+maintainers:
+  - Álvaro Fernández Rojas <noltari@gmail.com>
+
+description:
+  Microchip EMC2101 pwm controller which supports up to 1 fan, 1 internal
+  temperature sensor, 1 external temperature sensor and an 8 entry look
+  up table to create a programmable temperature response.
+
+properties:
+  compatible:
+    enum:
+      - microchip,emc2101
+
+  reg:
+    maxItems: 1
+
+  fan:
+    $ref: fan-common.yaml#
+    unevaluatedProperties: false
+
+  '#pwm-cells':
+    const: 2
+    description: |
+      Number of cells in a PWM specifier.
+      - cell 0: The PWM frequency
+      - cell 1: The PWM polarity: 0 or PWM_POLARITY_INVERTED
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        fan_controller: fan-controller@4c {
+            compatible = "microchip,emc2101";
+            reg = <0x4c>;
+
+            #pwm-cells = <2>;
+
+            fan {
+                pwms = <&fan_controller 5806 0>;
+            };
+        };
+    };
+...
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v3 3/3] drivers: hwmon: add EMC2101 driver
  2025-07-09 16:48 [PATCH v3 0/3] hwmon: add Microchip EMC2101 driver Álvaro Fernández Rojas
  2025-07-09 16:48 ` [PATCH v3 1/3] docs: hwmon: add emc2101.rst to docs Álvaro Fernández Rojas
  2025-07-09 16:48 ` [PATCH v3 2/3] dt-bindings: hwmon: Add Microchip EMC2101 support Álvaro Fernández Rojas
@ 2025-07-09 16:48 ` Álvaro Fernández Rojas
  2025-07-16 16:05   ` Guenter Roeck
  2 siblings, 1 reply; 7+ messages in thread
From: Álvaro Fernández Rojas @ 2025-07-09 16:48 UTC (permalink / raw)
  To: jdelvare, linux, robh, krzk+dt, conor+dt, corbet, linux-hwmon,
	devicetree, linux-kernel, linux-doc
  Cc: Álvaro Fernández Rojas

The Microchip EMC2101 is a SMBus 2.0 fan controller with temperature
monitoring.
It supports up to 1 fan, 1 internal temperature sensor, 1 external
temperature sensor and an 8 entry look up table to create a
programmable temperature response.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
---
 drivers/hwmon/Kconfig   |   11 +
 drivers/hwmon/Makefile  |    1 +
 drivers/hwmon/emc2101.c | 2176 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 2188 insertions(+)
 create mode 100644 drivers/hwmon/emc2101.c

 v3: multiple improvements:
  - Switch to regmap-i2c.
  - Use regmap fields.
  - Add Power Management support.
  - Demote dev_info() to dev_dbg().
  - Remove "emc2101-r" i2c_device_id.
  - Remove "microchip,emc2101-r" of_device_id.
  - Properly implement standby mode.
  - Use milliseconds instead of millihertz for update_interval.
  - Drop mutex except for FAN_LUT_DISABLE and TEMP_EXT_CRIT_UNLOCK.
  - Fix u16 fraction temperature conversions.
  - Other code cleanups and refactors.

 v2: multiple improvements:
  - Remove FAN_RPM_MIN definition.
  - Rename FAN_FALSE_READ to FAN_MIN_READ.
  - pwm_auto_point_temp_hyst_store(): simplify function.
  - emc2101_fan_min_read(): add missing FAN_MIN_READ condition.
  - emc2101_fan_min_write(): fix tach_count calculation.
  - emc2101_init(): fix REG_TACH_MIN value.

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 079620dd4286..16bcd560276e 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2002,6 +2002,17 @@ config SENSORS_EMC1403
 	  Threshold values can be configured using sysfs.
 	  Data from the different diodes are accessible via sysfs.
 
+config SENSORS_EMC2101
+	tristate "SMSC EMC2101"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  If you say yes here you get support for the SMSC EMC2101
+	  fan controller chips.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called emc2101.
+
 config SENSORS_EMC2103
 	tristate "SMSC EMC2103"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 48e5866c0c9a..70e95096c6f2 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -73,6 +73,7 @@ obj-$(CONFIG_SENSORS_DRIVETEMP)	+= drivetemp.o
 obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
 obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
+obj-$(CONFIG_SENSORS_EMC2101)	+= emc2101.o
 obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
 obj-$(CONFIG_SENSORS_EMC2305)	+= emc2305.o
 obj-$(CONFIG_SENSORS_EMC6W201)	+= emc6w201.o
diff --git a/drivers/hwmon/emc2101.c b/drivers/hwmon/emc2101.c
new file mode 100644
index 000000000000..13a7860ef161
--- /dev/null
+++ b/drivers/hwmon/emc2101.c
@@ -0,0 +1,2176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Microchip EMC2101 fan controller.
+ *
+ * Copyright 2025 Álvaro Fernández Rojas <noltari@gmail.com>
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/util_macros.h>
+
+#define REG_TEMP_INT			0x00
+#define REG_TEMP_EXT_HI			0x01
+#define REG_STATUS			0x02
+#define  ADC_BUSY			BIT(7)
+#define  TEMP_INT_HIGH			BIT(6)
+#define  EEPROM_ERROR			BIT(5)
+#define  TEMP_EXT_HIGH			BIT(4)
+#define  TEMP_EXT_LOW			BIT(3)
+#define  TEMP_EXT_FAULT			BIT(2)
+#define  TEMP_EXT_CRIT			BIT(1)
+#define  TACH_LOW			BIT(0)
+#define REG_CONFIG			0x03
+#define  ALERT_IRQ_ACK			BIT(7)
+#define  FAN_STANDBY_ENABLE		BIT(6)
+#define  FAN_STANDBY_MODE		BIT(5)
+#define  FAN_MODE_DAC			BIT(4)
+#define  SMBUS_TOUT_DISABLE		BIT(3)
+#define  PIN_FUNC_TACH			BIT(2)
+#define  TEMP_EXT_CRIT_UNLOCK		BIT(1)
+#define  PIN_ASSERT_3_EXC		BIT(0)
+#define REG_CONV_RATE			0x04
+#define  CONV_RATE_SHIFT		0
+#define  CONV_RATE_16000		0
+#define  CONV_RATE_8000			1
+#define  CONV_RATE_4000			2
+#define  CONV_RATE_2000			3
+#define  CONV_RATE_1000			4
+#define  CONV_RATE_500			5
+#define  CONV_RATE_250			6
+#define  CONV_RATE_125			7
+#define  CONV_RATE_62			8
+#define  CONV_RATE_31			9
+#define  CONV_RATE_MASK			0xf
+#define REG_TEMP_INT_MAX		0x05
+#define REG_TEMP_EXT_MAX_HI		0x07
+#define REG_TEMP_EXT_MIN_HI		0x08
+#define REG_TEMP_EXT_FORCE		0x0c
+#define REG_ONE_SHOT			0x0f
+#define REG_TEMP_EXT_LO			0x10
+#define REG_SCRATCHPAD_1		0x11
+#define REG_SCRATCHPAD_2		0x12
+#define REG_TEMP_EXT_MAX_LO		0x13
+#define REG_TEMP_EXT_MIN_LO		0x14
+#define REG_ALERT_MASK			0x16
+#define  IRQ_TEMP_INT_MAX_DISABLE	BIT(6)
+#define  IRQ_TEMP_EXT_MAX_DISABLE	BIT(4)
+#define  IRQ_TEMP_EXT_MIN_DISABLE	BIT(3)
+#define  IRQ_TEMP_EXT_CRIT_DISABLE	BIT(1)
+#define  IRQ_TACH_MIN_DISABLE		BIT(0)
+#define REG_EXT_IDEALITY		0x17
+#define  EXT_IDEALITY_SHIFT		0
+#define  EXT_IDEALITY_START		9846
+#define  EXT_IDEALITY_STEP		13
+#define  EXT_IDEALITY_VAL(x)		(EXT_IDEALITY_START + \
+					 ((x) * EXT_IDEALITY_STEP))
+#define  EXT_IDEALITY_MASK		0x3f
+#define REG_BETA_COMP			0x18
+#define  BETA_COMP_AUTO			BIT(3)
+#define  BETA_COMP_SHIFT		0
+#define  BETA_COMP_DISABLE		7
+#define  BETA_COMP_2_33			6
+#define  BETA_COMP_1_00			5
+#define  BETA_COMP_0_43			4
+#define  BETA_COMP_0_33			3
+#define  BETA_COMP_0_25			2
+#define  BETA_COMP_0_18			1
+#define  BETA_COMP_0_11			0
+#define  BETA_COMP_MASK			0x7
+#define REG_TEMP_EXT_CRIT		0x19
+/* Can only be written once */
+#define REG_TEMP_EXT_CRIT_HYST		0x21
+#define REG_TACH_LO			0x46
+#define REG_TACH_HI			0x47
+#define REG_TACH_MIN_LO			0x48
+#define REG_TACH_MIN_HI			0x49
+#define REG_FAN_CONFIG			0x4a
+#define  FAN_EXT_TEMP_FORCE		BIT(6)
+#define  FAN_LUT_DISABLE		BIT(5)
+#define  FAN_POL_INV			BIT(4)
+#define  FAN_CLK_SEL			BIT(3)
+#define  FAN_CLK_OVR			BIT(2)
+#define  TACH_FALSE_READ_SHIFT		0
+#define  TACH_FALSE_READ_ENABLE		0
+#define  TACH_FALSE_READ_DISABLE	3
+#define  TACH_FALSE_READ_MASK		0x3
+#define REG_FAN_SPIN			0x4b
+#define  FAN_SPIN_UP_ABORT		BIT(5)
+#define  FAN_SPIN_UP_POWER_SHIFT	3
+#define  FAN_SPIN_UP_POWER_100		3
+#define  FAN_SPIN_UP_POWER_75		2
+#define  FAN_SPIN_UP_POWER_50		1
+#define  FAN_SPIN_UP_POWER_0		0
+#define  FAN_SPIN_UP_POWER_MASK		0x3
+#define  FAN_SPIN_UP_TIME_SHIFT		0
+#define  FAN_SPIN_UP_TIME_3200		7
+#define  FAN_SPIN_UP_TIME_1600		6
+#define  FAN_SPIN_UP_TIME_800		5
+#define  FAN_SPIN_UP_TIME_400		4
+#define  FAN_SPIN_UP_TIME_200		3
+#define  FAN_SPIN_UP_TIME_100		2
+#define  FAN_SPIN_UP_TIME_50		1
+#define  FAN_SPIN_UP_TIME_0		0
+#define  FAN_SPIN_UP_TIME_MASK		0x7
+#define REG_FAN_SET			0x4c
+#define  FAN_SET_SHIFT			0
+#define  FAN_SET_MASK			0x3f
+#define REG_PWM_FREQ			0x4d
+#define  PWM_FREQ_SHIFT			0
+#define  PWM_FREQ_MASK			0x1f
+#define REG_PWM_FREQ_DIV		0x4e
+#define REG_FAN_LUT_HYST		0x4f
+#define  FAN_LUT_HYST_SHIFT		0
+#define  FAN_LUT_HYST_MASK		0x1f
+#define REG_FAN_LUT_TEMP(x)		(0x50 + (0x2 * (x)))
+/* Write only with FAN_LUT_DISABLE */
+#define  FAN_LUT_TEMP_SHIFT		0
+#define  FAN_LUT_TEMP_MASK		0x7f
+#define REG_FAN_LUT_SPEED(x)		(0x51 + (0x2 * (x)))
+/* Write only with FAN_LUT_DISABLE */
+#define  FAN_LUT_SPEED_SHIFT		0
+#define  FAN_LUT_SPEED_MASK		0x3f
+#define REG_AVG_FILTER			0xbf
+#define  FILTER_SHIFT			1
+#define  FILTER_L2			3
+#define  FILTER_L1			1
+#define  FILTER_NONE			0
+#define  FILTER_MASK			0x3
+#define  ALERT_PIN_TEMP_COMP		BIT(0)
+#define REG_PRODUCT_ID			0xfd
+#define REG_MANUFACTURER_ID		0xfe
+#define REG_REVISION			0xff
+
+#define CLK_FREQ_ALT			1400
+#define CLK_FREQ_BASE			360000
+
+#define FAN_LUT_COUNT			8
+#define FAN_LUT_HYST_MIN		0
+#define FAN_LUT_HYST_MAX		31
+#define FAN_MIN_READ			0xffff
+#define FAN_RPM_FACTOR			5400000
+
+#define MANUFACTURER_ID			0x5d
+
+#define PWM_MASK			0x3f
+
+#define TEMP_FAULT_OPEN			0x7f00
+#define TEMP_FAULT_SHORT		0x7fe0
+#define TEMP_LO_FRAC			125
+#define TEMP_LO_SHIFT			5
+#define TEMP_LO_MASK			0x7
+
+#define TEMP_MIN			-64
+#define TEMP_MAX			127
+#define TEMP_MAX_FRAC			750
+
+enum emc2101_auto_channels_temp {
+	EMC2101_ACT_EXT = 2,
+	EMC2101_ACT_FORCE = 3
+};
+
+enum emc2101_mode {
+	EMC2101_MODE_PWM = 0,
+	EMC2101_MODE_DAC = 1
+};
+
+enum ecm2101_product_id {
+	EMC2101 = 0x16,
+	EMC2101_R = 0x28
+};
+
+enum emc2101_pwm {
+	EMC2101_PWM_MANUAL = 1,
+	EMC2101_PWM_LUT = 2
+};
+
+enum emc2101_temp_channels {
+	EMC2101_TC_INT = 0,
+	EMC2101_TC_EXT,
+	EMC2101_TC_FORCE,
+	EMC2101_TC_NUM
+};
+
+enum emc2101_temp_diode {
+	EMC2101_TD_CPU = 1,
+	EMC2101_TD_2N3904 = 2
+};
+
+enum emc2101_fields {
+	/* BETA_COMP */
+	F_BETA_COMP,
+	F_BETA_COMP_AUTO,
+
+	/* CONFIG */
+	F_TEMP_EXT_CRIT_UNLOCK,
+	F_PIN_FUNC_TACH,
+	F_SMBUS_TOUT_DISABLE,
+	F_FAN_MODE_DAC,
+	F_FAN_STBY,
+	F_STBY_MODE,
+
+	/* CONV_RATE */
+	F_CONV_RATE,
+
+	/* EXT_IDEALITY */
+	F_EXT_IDEALITY,
+
+	/* FAN_CONFIG */
+	F_TACH_FALSE_READ,
+	F_FAN_CLK_OVR,
+	F_FAN_CLK_SEL,
+	F_FAN_POL_INV,
+	F_FAN_LUT_DISABLE,
+	F_FAN_EXT_TEMP_FORCE,
+
+	/* FAN_LUT */
+	F_FAN_LUT_HYST,
+	F_FAN_LUT_SPEED_1,
+	F_FAN_LUT_SPEED_2,
+	F_FAN_LUT_SPEED_3,
+	F_FAN_LUT_SPEED_4,
+	F_FAN_LUT_SPEED_5,
+	F_FAN_LUT_SPEED_6,
+	F_FAN_LUT_SPEED_7,
+	F_FAN_LUT_SPEED_8,
+	F_FAN_LUT_TEMP_1,
+	F_FAN_LUT_TEMP_2,
+	F_FAN_LUT_TEMP_3,
+	F_FAN_LUT_TEMP_4,
+	F_FAN_LUT_TEMP_5,
+	F_FAN_LUT_TEMP_6,
+	F_FAN_LUT_TEMP_7,
+	F_FAN_LUT_TEMP_8,
+
+	/* FAN_SET */
+	F_FAN_SET,
+
+	/* FAN_SPIN */
+	F_SPIN_UP_TIME,
+	F_SPIN_UP_POWER,
+	F_SPIN_UP_ABORT,
+
+	/* PWM_FREQ */
+	F_PWM_FREQ,
+	F_PWM_FREQ_DIV,
+
+	/* STATUS */
+	F_TACH_LOW_ALARM,
+	F_TEMP_EXT_CRIT_ALARM,
+	F_TEMP_EXT_FAULT,
+	F_TEMP_EXT_LOW_ALARM,
+	F_TEMP_EXT_HIGH_ALARM,
+	F_TEMP_INT_HIGH_ALARM,
+
+	/* TEMP_INT */
+	F_TEMP_INT,
+	F_TEMP_INT_MAX,
+
+	/* TEMP_EXT */
+	F_TEMP_EXT_CRIT,
+	F_TEMP_EXT_CRIT_HYST,
+
+	/* TEMP_EXT_FORCE */
+	F_TEMP_EXT_FORCE,
+
+	/* sentinel */
+	F_MAX_FIELDS
+};
+
+#define F_FAN_LUT_SPEED(x) (F_FAN_LUT_SPEED_1 + (x))
+#define F_FAN_LUT_TEMP(x) (F_FAN_LUT_TEMP_1 + (x))
+
+static const struct reg_field emc2101_reg_fields[] = {
+	/* BETA_COMP */
+	[F_BETA_COMP] = REG_FIELD(REG_BETA_COMP, 0, 2),
+	[F_BETA_COMP_AUTO] = REG_FIELD(REG_BETA_COMP, 3, 3),
+
+	/* CONFIG */
+	[F_TEMP_EXT_CRIT_UNLOCK] = REG_FIELD(REG_CONFIG, 1, 1),
+	[F_PIN_FUNC_TACH] = REG_FIELD(REG_CONFIG, 2, 2),
+	[F_SMBUS_TOUT_DISABLE] = REG_FIELD(REG_CONFIG, 3, 3),
+	[F_FAN_MODE_DAC] = REG_FIELD(REG_CONFIG, 4, 4),
+	[F_FAN_STBY] = REG_FIELD(REG_CONFIG, 5, 5),
+	[F_STBY_MODE] = REG_FIELD(REG_CONFIG, 6, 6),
+
+	/* CONV_RATE */
+	[F_CONV_RATE] = REG_FIELD(REG_CONV_RATE, 0, 3),
+
+	/* EXT_IDEALITY */
+	[F_EXT_IDEALITY] = REG_FIELD(REG_EXT_IDEALITY, 0, 5),
+
+	/* FAN_CONFIG */
+	[F_TACH_FALSE_READ] = REG_FIELD(REG_FAN_CONFIG, 0, 1),
+	[F_FAN_CLK_OVR] = REG_FIELD(REG_FAN_CONFIG, 2, 2),
+	[F_FAN_CLK_SEL] = REG_FIELD(REG_FAN_CONFIG, 3, 3),
+	[F_FAN_POL_INV] = REG_FIELD(REG_FAN_CONFIG, 4, 4),
+	[F_FAN_LUT_DISABLE] = REG_FIELD(REG_FAN_CONFIG, 5, 5),
+	[F_FAN_EXT_TEMP_FORCE] = REG_FIELD(REG_FAN_CONFIG, 6, 6),
+
+	/* FAN_LUT */
+	[F_FAN_LUT_HYST] = REG_FIELD(REG_FAN_LUT_HYST, 0, 4),
+	[F_FAN_LUT_SPEED_1] = REG_FIELD(REG_FAN_LUT_SPEED(0), 0, 5),
+	[F_FAN_LUT_SPEED_2] = REG_FIELD(REG_FAN_LUT_SPEED(1), 0, 5),
+	[F_FAN_LUT_SPEED_3] = REG_FIELD(REG_FAN_LUT_SPEED(2), 0, 5),
+	[F_FAN_LUT_SPEED_4] = REG_FIELD(REG_FAN_LUT_SPEED(3), 0, 5),
+	[F_FAN_LUT_SPEED_5] = REG_FIELD(REG_FAN_LUT_SPEED(4), 0, 5),
+	[F_FAN_LUT_SPEED_6] = REG_FIELD(REG_FAN_LUT_SPEED(5), 0, 5),
+	[F_FAN_LUT_SPEED_7] = REG_FIELD(REG_FAN_LUT_SPEED(6), 0, 5),
+	[F_FAN_LUT_SPEED_8] = REG_FIELD(REG_FAN_LUT_SPEED(7), 0, 5),
+	[F_FAN_LUT_TEMP_1] = REG_FIELD(REG_FAN_LUT_TEMP(0), 0, 6),
+	[F_FAN_LUT_TEMP_2] = REG_FIELD(REG_FAN_LUT_TEMP(1), 0, 6),
+	[F_FAN_LUT_TEMP_3] = REG_FIELD(REG_FAN_LUT_TEMP(2), 0, 6),
+	[F_FAN_LUT_TEMP_4] = REG_FIELD(REG_FAN_LUT_TEMP(3), 0, 6),
+	[F_FAN_LUT_TEMP_5] = REG_FIELD(REG_FAN_LUT_TEMP(4), 0, 6),
+	[F_FAN_LUT_TEMP_6] = REG_FIELD(REG_FAN_LUT_TEMP(5), 0, 6),
+	[F_FAN_LUT_TEMP_7] = REG_FIELD(REG_FAN_LUT_TEMP(6), 0, 6),
+	[F_FAN_LUT_TEMP_8] = REG_FIELD(REG_FAN_LUT_TEMP(7), 0, 6),
+
+	/* FAN_SET */
+	[F_FAN_SET] = REG_FIELD(REG_FAN_SET, 0, 5),
+
+	/* FAN_SPIN */
+	[F_SPIN_UP_TIME] = REG_FIELD(REG_FAN_SPIN, 0, 2),
+	[F_SPIN_UP_POWER] = REG_FIELD(REG_FAN_SPIN, 3, 4),
+	[F_SPIN_UP_ABORT] = REG_FIELD(REG_FAN_SPIN, 5, 5),
+
+	/* PWM_FREQ */
+	[F_PWM_FREQ] = REG_FIELD(REG_PWM_FREQ, 0, 4),
+	[F_PWM_FREQ_DIV] = REG_FIELD(REG_PWM_FREQ_DIV, 0, 7),
+
+	/* STATUS */
+	[F_TACH_LOW_ALARM] = REG_FIELD(REG_STATUS, 0, 0),
+	[F_TEMP_EXT_CRIT_ALARM] = REG_FIELD(REG_STATUS, 1, 1),
+	[F_TEMP_EXT_FAULT] = REG_FIELD(REG_STATUS, 2, 2),
+	[F_TEMP_EXT_LOW_ALARM] = REG_FIELD(REG_STATUS, 3, 3),
+	[F_TEMP_EXT_HIGH_ALARM] = REG_FIELD(REG_STATUS, 4, 4),
+	[F_TEMP_INT_HIGH_ALARM] = REG_FIELD(REG_STATUS, 6, 6),
+
+	/* TEMP_INT */
+	[F_TEMP_INT] = REG_FIELD(REG_TEMP_INT, 0, 7),
+	[F_TEMP_INT_MAX] = REG_FIELD(REG_TEMP_INT_MAX, 0, 6),
+
+	/* TEMP_EXT */
+	[F_TEMP_EXT_CRIT] = REG_FIELD(REG_TEMP_EXT_CRIT, 0, 6),
+	[F_TEMP_EXT_CRIT_HYST] = REG_FIELD(REG_TEMP_EXT_CRIT_HYST, 0, 6),
+
+	/* TEMP_EXT_FORCE */
+	[F_TEMP_EXT_FORCE] = REG_FIELD(REG_TEMP_EXT_FORCE, 0, 7),
+};
+
+struct emc2101_data {
+	struct regmap *regmap;
+	struct regmap_field *fields[F_MAX_FIELDS];
+	struct device *dev;
+	struct mutex mutex; /* serializes FAN_LUT_DISABLE and TEMP_EXT_CRIT_UNLOCK */
+};
+
+static const u16 emc2101_conv_time[] = {
+	16000, 8000, 4000, 2000, 1000, 500, 250, 125, 62, 31
+};
+
+static const u8 emc2101_fan_spin_up_power[] = {
+	0, 50, 75, 100
+};
+
+static const u16 emc2101_fan_spin_up_time[] = {
+	0, 50, 100, 200, 400, 800, 1600, 3200
+};
+
+static const unsigned int regs_tach[2] = {REG_TACH_HI, REG_TACH_LO};
+static const unsigned int regs_tach_min[2] = {REG_TACH_MIN_HI, REG_TACH_MIN_LO};
+static const unsigned int regs_temp_ext[2] = {REG_TEMP_EXT_HI, REG_TEMP_EXT_LO};
+static const unsigned int regs_temp_ext_max[2] = {REG_TEMP_EXT_MAX_HI, REG_TEMP_EXT_MAX_LO};
+static const unsigned int regs_temp_ext_min[2] = {REG_TEMP_EXT_MIN_HI, REG_TEMP_EXT_MIN_LO};
+
+static inline int emc2101_read_u16(struct emc2101_data *data, const unsigned int *regs, u16 *val)
+{
+	u8 read_seq[2];
+	int ret;
+
+	ret = regmap_multi_reg_read(data->regmap, regs, read_seq, 2);
+	if (!ret) {
+		*val = (read_seq[0] & 0xff) << 8;
+		*val |= read_seq[1] & 0xff;
+	}
+
+	return ret;
+}
+
+static inline int emc2101_write_u16(struct emc2101_data *data, const unsigned int *regs, u16 val)
+{
+	const struct reg_sequence write_seq[2] = {
+		{ regs[0], (val >> 8) & 0xff },
+		{ regs[1], val & 0xff },
+	};
+
+	return regmap_multi_reg_write(data->regmap, write_seq, 2);
+}
+
+static inline u16 emc2101_rpm_to_u16(long rpm)
+{
+	u16 val;
+
+	if (rpm > 0)
+		val = clamp_val(FAN_RPM_FACTOR / rpm, 1, FAN_MIN_READ);
+	else
+		val = FAN_MIN_READ;
+
+	return val;
+}
+
+static inline long emc2101_u16_to_rpm(u16 val)
+{
+	long rpm;
+
+	val = clamp_val(val, 1, FAN_MIN_READ);
+	if (val < FAN_MIN_READ)
+		rpm = FAN_RPM_FACTOR / val;
+	else
+		rpm = 0;
+
+	return rpm;
+}
+
+static inline u16 emc2101_temp_to_u16(long temp)
+{
+	s8 val_hi = clamp_val(temp / 1000, TEMP_MIN, TEMP_MAX);
+	long rem = temp % 1000;
+	u8 val_lo;
+
+	if (val_hi == TEMP_MIN)
+		rem = 0;
+	else if (val_hi == TEMP_MAX)
+		rem = TEMP_MAX_FRAC;
+
+	if (rem < 0) {
+		val_hi -= 1;
+		rem = (1000 + rem);
+	}
+
+	rem /= TEMP_LO_FRAC;
+	val_lo = (rem & TEMP_LO_MASK) << TEMP_LO_SHIFT;
+
+	return (val_hi << 8) | val_lo;
+}
+
+static inline long emc2101_u16_to_temp(u16 val)
+{
+	const s8 val_hi = (val >> 8) & 0xff;
+	const u8 val_lo = (val >> TEMP_LO_SHIFT) & TEMP_LO_MASK;
+	long temp;
+
+	temp = val_hi * 1000;
+	temp += val_lo * TEMP_LO_FRAC;
+
+	return temp;
+}
+
+static inline bool emc2101_lut_edit(struct emc2101_data *data, bool *disabled)
+{
+	unsigned int lut_disabled;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_LUT_DISABLE], &lut_disabled);
+	if (ret)
+		return ret;
+
+	*disabled = lut_disabled;
+
+	return regmap_field_write(data->fields[F_FAN_LUT_DISABLE], 1);
+}
+
+static inline bool emc2101_lut_restore(struct emc2101_data *data, bool disabled)
+{
+	if (!disabled)
+		return regmap_field_write(data->fields[F_FAN_LUT_DISABLE], 0);
+
+	return 0;
+}
+
+static int emc2101_lut_hyst_write(struct regmap_field *field, long temp)
+{
+	const unsigned int val = clamp_val(temp / 1000, FAN_LUT_HYST_MIN, FAN_LUT_HYST_MAX);
+
+	return regmap_field_write(field, val);
+}
+
+static int emc2101_temp_neg_read(struct regmap_field *field, long *temp)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_field_read(field, &val);
+	if (!ret)
+		*temp = ((s8) val) * 1000;
+
+	return ret;
+}
+
+static int emc2101_temp_neg_write(struct regmap_field *field, long temp)
+{
+	const s8 val = clamp_val(temp / 1000, TEMP_MIN, TEMP_MAX);
+
+	return regmap_field_write(field, val);
+}
+
+static int emc2101_temp_pos_read(struct regmap_field *field, long *temp)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_field_read(field, &val);
+	if (!ret)
+		*temp = val * 1000;
+
+	return ret;
+}
+
+static int emc2101_temp_pos_write(struct regmap_field *field, long temp)
+{
+	const u8 val = clamp_val(temp / 1000, 0, TEMP_MAX);
+
+	return regmap_field_write(field, val);
+}
+
+static int emc2101_temp_frac_read(struct emc2101_data *data, const unsigned int *regs, long *temp)
+{
+	u16 temp_frac;
+	int ret;
+
+	ret = emc2101_read_u16(data, regs, &temp_frac);
+	if (ret)
+		return ret;
+
+	switch (temp_frac) {
+	case TEMP_FAULT_OPEN:
+		dev_warn(data->dev, "[%02x, %02x]: diode fault (open)", regs[0], regs[1]);
+		return -ENODATA;
+	case TEMP_FAULT_SHORT:
+		dev_warn(data->dev, "[%02x, %02x]: diode fault (short)", regs[0], regs[1]);
+		return -ENODATA;
+	default:
+		break;
+	}
+
+	*temp = emc2101_u16_to_temp(temp_frac);
+
+	return ret;
+}
+
+static int emc2101_temp_frac_write(struct emc2101_data *data, const unsigned int *regs, long temp)
+{
+	long temp_frac = emc2101_temp_to_u16(temp);
+
+	return emc2101_write_u16(data, regs, temp_frac);
+}
+
+static int emc2101_pwm_write(struct regmap_field *field, long pwm)
+{
+	const unsigned int val = clamp_val(pwm, 0, PWM_MASK);
+
+	return regmap_field_write(field, val);
+}
+
+static ssize_t fan_spin_up_abort_show(struct device *dev, struct device_attribute *devattr,
+				      char *buf)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_spin_abort;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_SPIN_UP_ABORT], &fan_spin_abort);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", fan_spin_abort);
+}
+
+static ssize_t fan_spin_up_abort_store(struct device *dev, struct device_attribute *devattr,
+				       const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_spin_abort;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &fan_spin_abort);
+	if (ret)
+		return ret;
+
+	switch (fan_spin_abort) {
+	case 0:
+	case 1:
+		ret = regmap_field_write(data->fields[F_SPIN_UP_ABORT], fan_spin_abort);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return !ret ? count : ret;
+}
+
+static ssize_t fan_spin_up_time_show(struct device *dev, struct device_attribute *devattr,
+				     char *buf)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_spin_time;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_SPIN_UP_TIME], &fan_spin_time);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", emc2101_fan_spin_up_time[fan_spin_time]);
+}
+
+static ssize_t fan_spin_up_time_store(struct device *dev, struct device_attribute *devattr,
+				      const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_spin_time, val;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &fan_spin_time);
+	if (ret)
+		return ret;
+
+	val = find_closest(fan_spin_time, emc2101_fan_spin_up_time,
+			   ARRAY_SIZE(emc2101_fan_spin_up_time));
+
+	ret = regmap_field_write(data->fields[F_SPIN_UP_TIME], val);
+
+	return !ret ? count : ret;
+}
+
+static ssize_t fan_spin_up_power_show(struct device *dev, struct device_attribute *devattr,
+				      char *buf)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_spin_power;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_SPIN_UP_POWER], &fan_spin_power);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", emc2101_fan_spin_up_power[fan_spin_power]);
+}
+
+static ssize_t fan_spin_up_power_store(struct device *dev, struct device_attribute *devattr,
+				       const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_spin_power, val;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &fan_spin_power);
+	if (ret)
+		return ret;
+
+	val = find_closest(fan_spin_power, emc2101_fan_spin_up_power,
+			   ARRAY_SIZE(emc2101_fan_spin_up_power));
+
+	ret = regmap_field_write(data->fields[F_SPIN_UP_POWER], val);
+
+	return !ret ? count : ret;
+}
+
+static ssize_t fan_standby_show(struct device *dev, struct device_attribute *devattr,
+				char *buf)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_standby;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_STBY], &fan_standby);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", fan_standby);
+}
+
+static ssize_t fan_standby_store(struct device *dev, struct device_attribute *devattr,
+				 const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_standby;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &fan_standby);
+	if (ret)
+		return ret;
+
+	switch (fan_standby) {
+	case 0:
+	case 1:
+		ret = regmap_field_write(data->fields[F_FAN_STBY], fan_standby);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return !ret ? count : ret;
+}
+
+static ssize_t pwm_auto_point_pwm_show(struct device *dev, struct device_attribute *devattr,
+				       char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int lut_pwm;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_LUT_SPEED(attr->index)], &lut_pwm);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", lut_pwm);
+}
+
+static ssize_t __pwm_auto_point_pwm_store(struct emc2101_data *data,
+					  struct device_attribute *devattr, unsigned int lut_pwm)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	bool lut_disable;
+	int ret;
+
+	ret = emc2101_lut_edit(data, &lut_disable);
+	if (ret)
+		return ret;
+
+	ret = emc2101_pwm_write(data->fields[F_FAN_LUT_SPEED(attr->index)], lut_pwm);
+	if (ret)
+		return ret;
+
+	return emc2101_lut_restore(data, lut_disable);
+}
+
+static ssize_t pwm_auto_point_pwm_store(struct device *dev, struct device_attribute *devattr,
+					const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int lut_pwm;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &lut_pwm);
+	if (ret)
+		return ret;
+
+	mutex_lock(&data->mutex);
+	ret = __pwm_auto_point_pwm_store(data, devattr, lut_pwm);
+	mutex_unlock(&data->mutex);
+
+	return !ret ? count : ret;
+}
+
+static ssize_t pwm_auto_point_temp_show(struct device *dev, struct device_attribute *devattr,
+					char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	long lut_temp;
+	int ret;
+
+	ret = emc2101_temp_pos_read(data->fields[F_FAN_LUT_TEMP(attr->index)], &lut_temp);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%lu\n", lut_temp);
+}
+
+static ssize_t __pwm_auto_point_temp_store(struct emc2101_data *data,
+					   struct device_attribute *devattr, unsigned int lut_temp)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	bool lut_disable;
+	unsigned int i;
+	long cur_temp;
+	int ret;
+
+	ret = emc2101_lut_edit(data, &lut_disable);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < FAN_LUT_COUNT; i++) {
+		struct regmap_field *field = data->fields[F_FAN_LUT_TEMP(i)];
+
+		ret = emc2101_temp_pos_read(field, &cur_temp);
+		if (ret)
+			return ret;
+
+		if (i < attr->index) {
+			if (cur_temp > lut_temp)
+				ret = emc2101_temp_pos_write(field, lut_temp);
+		} else if (i > attr->index) {
+			if (cur_temp < lut_temp)
+				ret = emc2101_temp_pos_write(field, lut_temp);
+		} else {
+			ret = emc2101_temp_pos_write(field, lut_temp);
+		}
+
+		if (ret)
+			return ret;
+	}
+
+	return emc2101_lut_restore(data, lut_disable);
+}
+
+static ssize_t pwm_auto_point_temp_store(struct device *dev, struct device_attribute *devattr,
+					 const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int lut_temp;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &lut_temp);
+	if (ret)
+		return ret;
+
+	mutex_lock(&data->mutex);
+	ret = __pwm_auto_point_temp_store(data, devattr, lut_temp);
+	mutex_unlock(&data->mutex);
+
+	return !ret ? count : ret;
+}
+
+static ssize_t pwm_auto_point_temp_hyst_show(struct device *dev, struct device_attribute *devattr,
+					     char *buf)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	long lut_hyst;
+	int ret;
+
+	ret = emc2101_temp_pos_read(data->fields[F_FAN_LUT_HYST], &lut_hyst);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%lu\n", lut_hyst);
+}
+
+static ssize_t pwm_auto_point_temp_hyst_store(struct device *dev, struct device_attribute *devattr,
+					      const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int lut_hyst;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &lut_hyst);
+	if (ret)
+		return ret;
+
+	ret = emc2101_lut_hyst_write(data->fields[F_FAN_LUT_HYST], lut_hyst);
+
+	return !ret ? count : ret;
+}
+
+static ssize_t pwm_polarity_invert_show(struct device *dev, struct device_attribute *devattr,
+					char *buf)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int polarity_inverted;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_POL_INV], &polarity_inverted);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", polarity_inverted);
+}
+
+static ssize_t pwm_polarity_invert_store(struct device *dev, struct device_attribute *devattr,
+					 const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int polarity_inverted;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &polarity_inverted);
+	if (ret)
+		return ret;
+
+	switch (polarity_inverted) {
+	case 0:
+	case 1:
+		ret = regmap_field_write(data->fields[F_FAN_POL_INV], polarity_inverted);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return !ret ? count : ret;
+}
+
+static ssize_t temp_external_force_show(struct device *dev, struct device_attribute *devattr,
+					char *buf)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	long temp_force;
+	int ret;
+
+	ret = emc2101_temp_neg_read(data->fields[F_TEMP_EXT_FORCE], &temp_force);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%ld\n", temp_force);
+}
+
+static ssize_t temp_external_force_store(struct device *dev, struct device_attribute *devattr,
+					 const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	long temp_force;
+	int ret;
+
+	ret = kstrtol(buf, 10, &temp_force);
+	if (ret)
+		return ret;
+
+	ret = emc2101_temp_neg_write(data->fields[F_TEMP_EXT_FORCE], temp_force);
+
+	return !ret ? count : ret;
+}
+
+static ssize_t temp_external_ideality_show(struct device *dev, struct device_attribute *devattr,
+					   char *buf)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int ext_ideality;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_EXT_IDEALITY], &ext_ideality);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", EXT_IDEALITY_VAL(ext_ideality));
+}
+
+static ssize_t temp_external_ideality_store(struct device *dev, struct device_attribute *devattr,
+					    const char *buf, size_t count)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int ext_ideality_factor, val;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &ext_ideality_factor);
+	if (ret)
+		return ret;
+
+	ext_ideality_factor = clamp_val(ext_ideality_factor, EXT_IDEALITY_START,
+					EXT_IDEALITY_VAL(EXT_IDEALITY_MASK));
+	val = (ext_ideality_factor - EXT_IDEALITY_START) / EXT_IDEALITY_STEP;
+
+	ret = regmap_field_write(data->fields[F_EXT_IDEALITY], val);
+
+	return !ret ? count : ret;
+}
+
+static SENSOR_DEVICE_ATTR_RW(fan1_spin_up_abort, fan_spin_up_abort, 0);
+static SENSOR_DEVICE_ATTR_RW(fan1_spin_up_power, fan_spin_up_power, 0);
+static SENSOR_DEVICE_ATTR_RW(fan1_spin_up_time, fan_spin_up_time, 0);
+static SENSOR_DEVICE_ATTR_RW(fan1_standby, fan_standby, 0);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_auto_point_pwm, 0);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_auto_point_temp, 0);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_pwm, pwm_auto_point_pwm, 1);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_auto_point_temp, 1);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_pwm, pwm_auto_point_pwm, 2);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_auto_point_temp, 2);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_pwm, pwm_auto_point_pwm, 3);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_auto_point_temp, 3);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_pwm, pwm_auto_point_pwm, 4);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_auto_point_temp, 4);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point6_pwm, pwm_auto_point_pwm, 5);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point6_temp, pwm_auto_point_temp, 5);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point7_pwm, pwm_auto_point_pwm, 6);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point7_temp, pwm_auto_point_temp, 6);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point8_pwm, pwm_auto_point_pwm, 7);
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point8_temp, pwm_auto_point_temp, 7);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point_temp_hyst, pwm_auto_point_temp_hyst, 0);
+
+static SENSOR_DEVICE_ATTR_RW(pwm1_polarity_invert, pwm_polarity_invert, 0);
+
+static SENSOR_DEVICE_ATTR_RW(temp2_external_ideality, temp_external_ideality, 0);
+
+static SENSOR_DEVICE_ATTR_RW(temp3, temp_external_force, 0);
+
+static struct attribute *emc2101_hwmon_attributes[] = {
+	&sensor_dev_attr_fan1_spin_up_abort.dev_attr.attr,
+	&sensor_dev_attr_fan1_spin_up_power.dev_attr.attr,
+	&sensor_dev_attr_fan1_spin_up_time.dev_attr.attr,
+	&sensor_dev_attr_fan1_standby.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point7_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point7_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point8_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point8_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point_temp_hyst.dev_attr.attr,
+	&sensor_dev_attr_pwm1_polarity_invert.dev_attr.attr,
+	&sensor_dev_attr_temp2_external_ideality.dev_attr.attr,
+	&sensor_dev_attr_temp3.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group emc2101_hwmon_group = {
+	.attrs = emc2101_hwmon_attributes,
+};
+__ATTRIBUTE_GROUPS(emc2101_hwmon);
+
+static int emc2101_chip_update_interval_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int conv_rate;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_CONV_RATE], &conv_rate);
+	if (!ret) {
+		if (conv_rate < ARRAY_SIZE(emc2101_conv_time))
+			*val = emc2101_conv_time[conv_rate];
+		else
+			*val = emc2101_conv_time[CONV_RATE_31];
+	}
+
+	return ret;
+}
+
+static int emc2101_chip_update_interval_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int conv_rate;
+
+	conv_rate = find_closest_descending(val, emc2101_conv_time, ARRAY_SIZE(emc2101_conv_time));
+
+	return regmap_field_write(data->fields[F_CONV_RATE], conv_rate);
+}
+
+static int emc2101_fan_div_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int pwm_freq_div;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_PWM_FREQ_DIV], &pwm_freq_div);
+	if (!ret)
+		*val = pwm_freq_div;
+
+	return ret;
+}
+
+static int emc2101_fan_div_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	struct regmap_field *field = data->fields[F_PWM_FREQ_DIV];
+	unsigned int pwm_freq_div;
+
+	pwm_freq_div = clamp_val(val, 1, 0xff);
+
+	return regmap_field_write(field, pwm_freq_div);
+}
+
+static int emc2101_fan_input_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	u16 tach_count;
+	int ret;
+
+	ret = emc2101_read_u16(data, regs_tach, &tach_count);
+	if (ret)
+		return ret;
+
+	*val = emc2101_u16_to_rpm(tach_count);
+
+	return 0;
+}
+
+static int emc2101_fan_min_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	u16 tach_count;
+	int ret;
+
+	ret = emc2101_read_u16(data, regs_tach_min, &tach_count);
+	if (ret)
+		return ret;
+
+	*val = emc2101_u16_to_rpm(tach_count);
+
+	return 0;
+}
+
+static int emc2101_fan_min_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	u16 tach_count = emc2101_rpm_to_u16(val);
+
+	return emc2101_write_u16(data, regs_tach_min, tach_count);
+}
+
+static int emc2101_fan_min_alarm_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int tach_low;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_TACH_LOW_ALARM], &tach_low);
+	if (ret)
+		return ret;
+
+	*val = tach_low;
+
+	return 0;
+}
+
+static int emc2101_pwm_auto_channels_temp_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int temp_ext_force;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_EXT_TEMP_FORCE], &temp_ext_force);
+	if (ret)
+		return ret;
+
+	*val = temp_ext_force ? EMC2101_ACT_FORCE : EMC2101_ACT_EXT;
+
+	return 0;
+}
+
+static int emc2101_pwm_auto_channels_temp_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	int ret;
+
+	switch (val) {
+	case EMC2101_ACT_EXT:
+		ret = regmap_field_write(data->fields[F_FAN_EXT_TEMP_FORCE], 0);
+		break;
+	case EMC2101_ACT_FORCE:
+		ret = regmap_field_write(data->fields[F_FAN_EXT_TEMP_FORCE], 1);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return ret;
+}
+
+static int emc2101_pwm_enable_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int lut_disable;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_LUT_DISABLE], &lut_disable);
+	if (ret)
+		return ret;
+
+	*val = lut_disable ? EMC2101_PWM_MANUAL : EMC2101_PWM_LUT;
+
+	return 0;
+}
+
+static int emc2101_pwm_enable_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int lut_disable;
+	int ret;
+
+	switch (val) {
+	case EMC2101_PWM_MANUAL:
+		lut_disable = 1;
+		break;
+	case EMC2101_PWM_LUT:
+		lut_disable = 0;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	mutex_lock(&data->mutex);
+	ret = regmap_field_write(data->fields[F_FAN_LUT_DISABLE], lut_disable);
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static int emc2101_pwm_freq_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_clk_ovr, fan_clk_sel;
+	unsigned int pwm_freq, pwm_freq_div;
+	unsigned int base_clk, div;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_CLK_OVR], &fan_clk_ovr);
+	if (ret)
+		return ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_CLK_SEL], &fan_clk_sel);
+	if (ret)
+		return ret;
+
+	ret = regmap_field_read(data->fields[F_PWM_FREQ], &pwm_freq);
+	if (ret)
+		return ret;
+
+	if (fan_clk_ovr) {
+		ret = regmap_field_read(data->fields[F_PWM_FREQ_DIV], &pwm_freq_div);
+		if (ret)
+			return ret;
+	} else {
+		pwm_freq_div = 1;
+	}
+
+	if (fan_clk_sel)
+		base_clk = CLK_FREQ_ALT;
+	else
+		base_clk = CLK_FREQ_BASE;
+
+	div = 2 * pwm_freq * pwm_freq_div;
+	if (div)
+		*val = base_clk / div;
+	else
+		*val = 0;
+
+	return ret;
+}
+
+static int emc2101_pwm_freq_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_clk_ovr, fan_clk_sel;
+	unsigned int pwm_freq, pwm_freq_div;
+	unsigned int base_clk;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_CLK_OVR], &fan_clk_ovr);
+	if (ret)
+		return ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_CLK_SEL], &fan_clk_sel);
+	if (ret)
+		return ret;
+
+	if (fan_clk_ovr) {
+		ret = regmap_field_read(data->fields[F_PWM_FREQ_DIV], &pwm_freq_div);
+		if (ret)
+			return ret;
+	} else {
+		pwm_freq_div = 1;
+	}
+
+	if (fan_clk_sel)
+		base_clk = CLK_FREQ_ALT;
+	else
+		base_clk = CLK_FREQ_BASE;
+
+	pwm_freq = base_clk / (2 * pwm_freq_div * val);
+
+	return emc2101_pwm_write(data->fields[F_PWM_FREQ], pwm_freq);
+}
+
+static int emc2101_pwm_input_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_set;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_SET], &fan_set);
+	if (!ret)
+		*val = fan_set;
+
+	return ret;
+}
+
+static int emc2101_pwm_input_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_pwm_write(data->fields[F_FAN_SET], val);
+}
+
+static int emc2101_pwm_mode_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int fan_mode_dac;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_FAN_MODE_DAC], &fan_mode_dac);
+	if (ret)
+		return ret;
+
+	*val = fan_mode_dac ? EMC2101_MODE_DAC : EMC2101_MODE_PWM;
+
+	return ret;
+}
+
+static int emc2101_pwm_mode_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	int ret;
+
+	switch (val) {
+	case EMC2101_MODE_DAC:
+	case EMC2101_MODE_PWM:
+		ret = regmap_field_write(data->fields[F_FAN_MODE_DAC], val);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return ret;
+}
+
+static int emc2101_temp_ext_crit_alarm_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int temp_ext_crit;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_TEMP_EXT_CRIT_ALARM], &temp_ext_crit);
+	if (ret)
+		return ret;
+
+	*val = temp_ext_crit;
+
+	return 0;
+}
+
+static int emc2101_temp_ext_crit_hyst_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_pos_read(data->fields[F_TEMP_EXT_CRIT_HYST], val);
+}
+
+static int emc2101_temp_ext_crit_hyst_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_pos_write(data->fields[F_TEMP_EXT_CRIT_HYST], val);
+}
+
+static int emc2101_temp_ext_crit_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_pos_read(data->fields[F_TEMP_EXT_CRIT], val);
+}
+
+static int __emc2101_temp_ext_crit_write(struct emc2101_data *data, long val)
+{
+	unsigned int temp_ext_crit_unlock;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_TEMP_EXT_CRIT_UNLOCK], &temp_ext_crit_unlock);
+	if (ret)
+		return ret;
+
+	if (temp_ext_crit_unlock) {
+		dev_err(data->dev, "critical temperature can only be updated once");
+		return -EIO;
+	}
+
+	ret = regmap_field_write(data->fields[F_TEMP_EXT_CRIT_UNLOCK], 1);
+	if (ret)
+		return ret;
+
+	return emc2101_temp_pos_write(data->fields[F_TEMP_EXT_CRIT], val);
+}
+
+static int emc2101_temp_ext_crit_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&data->mutex);
+	ret = __emc2101_temp_ext_crit_write(data, val);
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+
+static int emc2101_temp_ext_fault_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int temp_ext_fault;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_TEMP_EXT_FAULT], &temp_ext_fault);
+	if (ret)
+		return ret;
+
+	*val = temp_ext_fault;
+
+	return 0;
+}
+
+static int emc2101_temp_ext_max_alarm_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int temp_ext_high;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_TEMP_EXT_HIGH_ALARM], &temp_ext_high);
+	if (ret)
+		return ret;
+
+	*val = temp_ext_high;
+
+	return 0;
+}
+
+static int emc2101_temp_ext_max_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_frac_read(data, regs_temp_ext_max, val);
+}
+
+static int emc2101_temp_ext_max_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_frac_write(data, regs_temp_ext_max, val);
+}
+
+static int emc2101_temp_ext_min_alarm_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int temp_ext_low;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_TEMP_EXT_LOW_ALARM], &temp_ext_low);
+	if (ret)
+		return ret;
+
+	*val = temp_ext_low;
+
+	return 0;
+}
+
+static int emc2101_temp_ext_min_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_frac_read(data, regs_temp_ext_min, val);
+}
+
+static int emc2101_temp_ext_min_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_frac_write(data, regs_temp_ext_min, val);
+}
+
+static int emc2101_temp_ext_type_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int beta_comp, beta_comp_auto;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_BETA_COMP], &beta_comp);
+	if (ret)
+		return ret;
+
+	ret = regmap_field_read(data->fields[F_BETA_COMP], &beta_comp_auto);
+	if (ret)
+		return ret;
+
+	if (beta_comp == BETA_COMP_DISABLE && !beta_comp_auto)
+		*val = EMC2101_TD_2N3904;
+	else
+		*val = EMC2101_TD_CPU;
+
+	return 0;
+}
+
+static int emc2101_temp_ext_type_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int beta_comp, beta_comp_auto;
+	int ret;
+
+	switch (val) {
+	case EMC2101_TD_CPU:
+		beta_comp = 0;
+		beta_comp_auto = 1;
+		break;
+	case EMC2101_TD_2N3904:
+		beta_comp = BETA_COMP_DISABLE;
+		beta_comp_auto = 0;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	ret = regmap_field_write(data->fields[F_BETA_COMP_AUTO], beta_comp_auto);
+	if (ret)
+		return ret;
+
+	return regmap_field_write(data->fields[F_BETA_COMP], beta_comp);
+}
+
+static int emc2101_temp_ext_input_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_frac_read(data, regs_temp_ext, val);
+}
+
+static int emc2101_temp_int_max_alarm_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+	unsigned int temp_int_high;
+	int ret;
+
+	ret = regmap_field_read(data->fields[F_TEMP_INT_HIGH_ALARM], &temp_int_high);
+	if (ret)
+		return ret;
+
+	*val = temp_int_high;
+
+	return 0;
+}
+
+static int emc2101_temp_int_max_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_pos_read(data->fields[F_TEMP_INT_MAX], val);
+}
+
+static int emc2101_temp_int_max_write(struct device *dev, long val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_pos_write(data->fields[F_TEMP_INT_MAX], val);
+}
+
+static int emc2101_temp_int_input_read(struct device *dev, long *val)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return emc2101_temp_neg_read(data->fields[F_TEMP_INT], val);
+}
+
+static umode_t emc2101_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
+				  int channel)
+{
+	int max_channels;
+
+	if (type == hwmon_temp)
+		max_channels = EMC2101_TC_NUM;
+	else
+		max_channels = 1;
+
+	if (channel >= max_channels)
+		return 0;
+
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return 0644;
+		default:
+			break;
+		}
+		break;
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_input:
+		case hwmon_fan_min_alarm:
+			return 0444;
+		case hwmon_fan_div:
+		case hwmon_fan_min:
+			return 0644;
+		default:
+			break;
+		}
+		break;
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_auto_channels_temp:
+		case hwmon_pwm_enable:
+		case hwmon_pwm_freq:
+		case hwmon_pwm_input:
+		case hwmon_pwm_mode:
+			return 0644;
+		default:
+			break;
+		}
+		break;
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_crit_alarm:
+		case hwmon_temp_fault:
+		case hwmon_temp_input:
+		case hwmon_temp_label:
+		case hwmon_temp_max_alarm:
+		case hwmon_temp_min_alarm:
+			return 0444;
+		case hwmon_temp_crit:
+		case hwmon_temp_crit_hyst:
+		case hwmon_temp_max:
+		case hwmon_temp_min:
+		case hwmon_temp_type:
+			return 0644;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+};
+
+static int emc2101_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+			long *val)
+{
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return emc2101_chip_update_interval_read(dev, val);
+		default:
+			break;
+		}
+		break;
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_div:
+			return emc2101_fan_div_read(dev, val);
+		case hwmon_fan_input:
+			return emc2101_fan_input_read(dev, val);
+		case hwmon_fan_min:
+			return emc2101_fan_min_read(dev, val);
+		case hwmon_fan_min_alarm:
+			return emc2101_fan_min_alarm_read(dev, val);
+		default:
+			break;
+		}
+		break;
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_auto_channels_temp:
+			return emc2101_pwm_auto_channels_temp_read(dev, val);
+		case hwmon_pwm_enable:
+			return emc2101_pwm_enable_read(dev, val);
+		case hwmon_pwm_freq:
+			return emc2101_pwm_freq_read(dev, val);
+		case hwmon_pwm_input:
+			return emc2101_pwm_input_read(dev, val);
+		case hwmon_pwm_mode:
+			return emc2101_pwm_mode_read(dev, val);
+		default:
+			break;
+		}
+		break;
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_crit:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_crit_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_crit_alarm:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_crit_alarm_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_crit_hyst:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_crit_hyst_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_fault:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_fault_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_input:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_input_read(dev, val);
+			case EMC2101_TC_INT:
+				return emc2101_temp_int_input_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_max:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_max_read(dev, val);
+			case EMC2101_TC_INT:
+				return emc2101_temp_int_max_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_max_alarm:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_max_alarm_read(dev, val);
+			case EMC2101_TC_INT:
+				return emc2101_temp_int_max_alarm_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_min:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_min_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_min_alarm:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_min_alarm_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_type:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_type_read(dev, val);
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return -EOPNOTSUPP;
+};
+
+static int emc2101_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			       int channel, const char **str)
+{
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_label:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				*str = "external";
+				return 0;
+			case EMC2101_TC_FORCE:
+				*str = "force";
+				return 0;
+			case EMC2101_TC_INT:
+				*str = "internal";
+				return 0;
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return -EOPNOTSUPP;
+};
+
+static int emc2101_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+			 long val)
+{
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return emc2101_chip_update_interval_write(dev, val);
+		default:
+			break;
+		}
+		break;
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_div:
+			return emc2101_fan_div_write(dev, val);
+		case hwmon_fan_min:
+			return emc2101_fan_min_write(dev, val);
+		default:
+			break;
+		}
+		break;
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_auto_channels_temp:
+			return emc2101_pwm_auto_channels_temp_write(dev, val);
+		case hwmon_pwm_enable:
+			return emc2101_pwm_enable_write(dev, val);
+		case hwmon_pwm_freq:
+			return emc2101_pwm_freq_write(dev, val);
+		case hwmon_pwm_input:
+			return emc2101_pwm_input_write(dev, val);
+		case hwmon_pwm_mode:
+			return emc2101_pwm_mode_write(dev, val);
+		default:
+			break;
+		}
+		break;
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_crit:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_crit_write(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_crit_hyst:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_crit_hyst_write(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_max:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_max_write(dev, val);
+			case EMC2101_TC_INT:
+				return emc2101_temp_int_max_write(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_min:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_min_write(dev, val);
+			default:
+				break;
+			}
+			break;
+		case hwmon_temp_type:
+			switch (channel) {
+			case EMC2101_TC_EXT:
+				return emc2101_temp_ext_type_write(dev, val);
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+#define EMC2101_CHIP_CFG	HWMON_C_UPDATE_INTERVAL
+#define EMC2101_FAN_CFG		(HWMON_F_DIV |\
+				 HWMON_F_INPUT |\
+				 HWMON_F_MIN |\
+				 HWMON_F_MIN_ALARM)
+#define EMC2101_PWM_CFG		(HWMON_PWM_AUTO_CHANNELS_TEMP |\
+				 HWMON_PWM_ENABLE |\
+				 HWMON_PWM_FREQ |\
+				 HWMON_PWM_INPUT |\
+				 HWMON_PWM_MODE)
+#define EMC2101_TEMP_INT_CFG	(HWMON_T_INPUT |\
+				 HWMON_T_LABEL |\
+				 HWMON_T_MAX |\
+				 HWMON_T_MAX_ALARM)
+#define EMC2101_TEMP_EXT_CFG	(HWMON_T_CRIT |\
+				 HWMON_T_CRIT_ALARM |\
+				 HWMON_T_CRIT_HYST |\
+				 HWMON_T_FAULT |\
+				 HWMON_T_INPUT |\
+				 HWMON_T_LABEL |\
+				 HWMON_T_MAX |\
+				 HWMON_T_MAX_ALARM |\
+				 HWMON_T_MIN |\
+				 HWMON_T_MIN_ALARM |\
+				 HWMON_T_TYPE)
+#define EMC2101_TEMP_FORCE_CFG	HWMON_T_LABEL
+
+static const struct hwmon_channel_info * const emc2101_info[] = {
+	HWMON_CHANNEL_INFO(chip, EMC2101_CHIP_CFG),
+	HWMON_CHANNEL_INFO(fan, EMC2101_FAN_CFG),
+	HWMON_CHANNEL_INFO(pwm, EMC2101_PWM_CFG),
+	HWMON_CHANNEL_INFO(temp, EMC2101_TEMP_INT_CFG,
+			   EMC2101_TEMP_EXT_CFG,
+			   EMC2101_TEMP_FORCE_CFG),
+	NULL
+};
+
+static const struct hwmon_ops emc2101_ops = {
+	.is_visible = emc2101_is_visible,
+	.read = emc2101_read,
+	.read_string = emc2101_read_string,
+	.write = emc2101_write,
+};
+
+static const struct hwmon_chip_info emc2101_chip_info = {
+	.info = emc2101_info,
+	.ops = &emc2101_ops,
+};
+
+static int emc2101_resume(struct device *dev)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return regmap_field_write(data->fields[F_STBY_MODE], 0);
+}
+
+static int emc2101_suspend(struct device *dev)
+{
+	struct emc2101_data *data = dev_get_drvdata(dev);
+
+	return regmap_field_write(data->fields[F_STBY_MODE], 1);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(emc2101_pm, emc2101_suspend, emc2101_resume);
+
+static int emc2101_init(struct emc2101_data *data)
+{
+	static const u8 lut_t[FAN_LUT_COUNT] = {  35,   40,   45,   50,   55,   60,   70,   75};
+	static const u8 lut_s[FAN_LUT_COUNT] = {0x12, 0x19, 0x1f, 0x25, 0x2c, 0x32, 0x38, 0x3f};
+	unsigned int i;
+	u16 tach_count;
+	int ret;
+
+	/* CONFIG */
+	ret = regmap_field_write(data->fields[F_PIN_FUNC_TACH], 1);
+	if (ret)
+		return ret;
+	ret = regmap_field_write(data->fields[F_SMBUS_TOUT_DISABLE], 1);
+	if (ret)
+		return ret;
+
+	/* FAN_CONFIG */
+	ret = regmap_field_write(data->fields[F_TACH_FALSE_READ], TACH_FALSE_READ_DISABLE);
+	if (ret)
+		return ret;
+	ret = regmap_field_write(data->fields[F_FAN_CLK_OVR], 1);
+	if (ret)
+		return ret;
+	ret = regmap_field_write(data->fields[F_FAN_CLK_SEL], 0);
+	if (ret)
+		return ret;
+
+	/* FAN_LUT */
+	ret = regmap_field_write(data->fields[F_FAN_LUT_DISABLE], 1);
+	if (ret)
+		return ret;
+	for (i = 0; i < FAN_LUT_COUNT; i++) {
+		ret = regmap_field_write(data->fields[F_FAN_LUT_TEMP(i)], lut_t[i]);
+		if (ret)
+			return ret;
+		ret = regmap_field_write(data->fields[F_FAN_LUT_SPEED(i)], lut_s[i]);
+		if (ret)
+			return ret;
+	}
+	ret = regmap_field_write(data->fields[F_FAN_LUT_DISABLE], 0);
+	if (ret)
+		return ret;
+
+	/* PWM_FREQ */
+	ret = regmap_field_write(data->fields[F_PWM_FREQ], PWM_FREQ_MASK);
+	if (ret)
+		return ret;
+	ret = regmap_field_write(data->fields[F_PWM_FREQ_DIV], 1);
+	if (ret)
+		return ret;
+
+	/* TACH gives invalid value on first reading */
+	return emc2101_read_u16(data, regs_tach, &tach_count);
+}
+
+static bool emc2101_regmap_is_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case REG_TEMP_INT:	/* internal diode */
+	case REG_TEMP_EXT_HI:	/* external diode high byte */
+	case REG_STATUS:	/* status */
+	case REG_TEMP_EXT_LO:	/* external diode low byte */
+	case REG_TACH_LO:	/* tach input low byte */
+	case REG_TACH_HI:	/* tach input high byte */
+	case REG_FAN_SET:	/* fan pwm */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config emc2101_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.cache_type = REGCACHE_MAPLE,
+	.volatile_reg = emc2101_regmap_is_volatile,
+};
+
+static int emc2101_probe(struct i2c_client *client)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	struct device *dev = &client->dev;
+	struct emc2101_data *data;
+	struct device *hwmon_dev;
+	unsigned int i;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EIO;
+
+	data = devm_kzalloc(dev, sizeof(struct emc2101_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->regmap = devm_regmap_init_i2c(client, &emc2101_regmap_config);
+	if (IS_ERR(data->regmap))
+		return PTR_ERR(data->regmap);
+
+	for (i = 0; i < F_MAX_FIELDS; i++) {
+		data->fields[i] = devm_regmap_field_alloc(dev, data->regmap,
+							  emc2101_reg_fields[i]);
+		if (IS_ERR(data->fields[i])) {
+			dev_err(dev, "Unable to allocate regmap fields\n");
+			return PTR_ERR(data->fields[i]);
+		}
+	}
+
+	data->dev = dev;
+	mutex_init(&data->mutex);
+
+	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+							 &emc2101_chip_info,
+							 emc2101_hwmon_groups);
+	if (IS_ERR(hwmon_dev))
+		return PTR_ERR(hwmon_dev);
+
+	dev_dbg(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
+
+	return emc2101_init(data);
+}
+
+static int emc2101_detect(struct i2c_client *client, struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	s32 manufacturer, product, revision;
+	struct device *dev = &adapter->dev;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -ENODEV;
+
+	manufacturer = i2c_smbus_read_byte_data(client, REG_MANUFACTURER_ID);
+	if (manufacturer != MANUFACTURER_ID)
+		return -ENODEV;
+
+	product = i2c_smbus_read_byte_data(client, REG_PRODUCT_ID);
+	switch (product) {
+	case EMC2101:
+		strscpy(info->type, "emc2101", I2C_NAME_SIZE);
+		break;
+	case EMC2101_R:
+		strscpy(info->type, "emc2101-r", I2C_NAME_SIZE);
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	revision = i2c_smbus_read_byte_data(client, REG_REVISION);
+
+	dev_dbg(dev, "Found %s at 0x%02x (rev 0x%02x).\n", info->type, client->addr, revision);
+
+	return 0;
+}
+
+static const struct i2c_device_id emc2101_ids[] = {
+	{ "emc2101" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, emc2101_ids);
+
+static const struct of_device_id emc2101_of_match_table[] = {
+	{ .compatible = "microchip,emc2101", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, emc2101_of_match_table);
+
+static const unsigned short emc2101_address_list[] = {
+	0x4c, I2C_CLIENT_END
+};
+
+static struct i2c_driver emc2101_driver = {
+	.address_list = emc2101_address_list,
+	.class = I2C_CLASS_HWMON,
+	.detect = emc2101_detect,
+	.driver = {
+		.name = "emc2101",
+		.of_match_table = emc2101_of_match_table,
+		.pm = pm_sleep_ptr(&emc2101_pm),
+	},
+	.id_table = emc2101_ids,
+	.probe = emc2101_probe,
+};
+module_i2c_driver(emc2101_driver);
+
+MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
+MODULE_DESCRIPTION("Microchip EMC2101 hwmon driver");
+MODULE_LICENSE("GPL");
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v3 2/3] dt-bindings: hwmon: Add Microchip EMC2101 support
  2025-07-09 16:48 ` [PATCH v3 2/3] dt-bindings: hwmon: Add Microchip EMC2101 support Álvaro Fernández Rojas
@ 2025-07-10 10:00   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 7+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-10 10:00 UTC (permalink / raw)
  To: Álvaro Fernández Rojas
  Cc: jdelvare, linux, robh, krzk+dt, conor+dt, corbet, linux-hwmon,
	devicetree, linux-kernel, linux-doc

On Wed, Jul 09, 2025 at 06:48:28PM +0200, Álvaro Fernández Rojas wrote:
> Introduce yaml schema for Microchip emc2101 pwm fan controller with
> temperature monitoring.
> 
> Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
> ---

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>

<form letter>
This is an automated instruction, just in case, because many review
tags are being ignored. If you know the process, just skip it entirely
(please do not feel offended by me posting it here - no bad intentions
intended, no patronizing, I just want to avoid wasted efforts). If you
do not know the process, here is a short explanation:

Please add Acked-by/Reviewed-by/Tested-by tags when posting new
versions of patchset, under or above your Signed-off-by tag, unless
patch changed significantly (e.g. new properties added to the DT
bindings). Tag is "received", when provided in a message replied to you
on the mailing list. Tools like b4 can help here ('b4 trailers -u ...').
However, there's no need to repost patches *only* to add the tags. The
upstream maintainer will do that for tags received on the version they
apply.

https://elixir.bootlin.com/linux/v6.15/source/Documentation/process/submitting-patches.rst#L591
</form letter>

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v3 3/3] drivers: hwmon: add EMC2101 driver
  2025-07-09 16:48 ` [PATCH v3 3/3] drivers: hwmon: add EMC2101 driver Álvaro Fernández Rojas
@ 2025-07-16 16:05   ` Guenter Roeck
  2025-07-17  1:30     ` Guenter Roeck
  0 siblings, 1 reply; 7+ messages in thread
From: Guenter Roeck @ 2025-07-16 16:05 UTC (permalink / raw)
  To: Álvaro Fernández Rojas, jdelvare, robh, krzk+dt,
	conor+dt, corbet, linux-hwmon, devicetree, linux-kernel,
	linux-doc

On 7/9/25 09:48, Álvaro Fernández Rojas wrote:
> The Microchip EMC2101 is a SMBus 2.0 fan controller with temperature
> monitoring.
> It supports up to 1 fan, 1 internal temperature sensor, 1 external
> temperature sensor and an 8 entry look up table to create a
> programmable temperature response.
> 
> Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>

This looks very much like a clone of LM63. Why does it warrant a new driver ?

Guenter


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v3 3/3] drivers: hwmon: add EMC2101 driver
  2025-07-16 16:05   ` Guenter Roeck
@ 2025-07-17  1:30     ` Guenter Roeck
  0 siblings, 0 replies; 7+ messages in thread
From: Guenter Roeck @ 2025-07-17  1:30 UTC (permalink / raw)
  To: Álvaro Fernández Rojas, jdelvare, robh, krzk+dt,
	conor+dt, corbet, linux-hwmon, devicetree, linux-kernel,
	linux-doc

On 7/16/25 09:05, Guenter Roeck wrote:
> On 7/9/25 09:48, Álvaro Fernández Rojas wrote:
>> The Microchip EMC2101 is a SMBus 2.0 fan controller with temperature
>> monitoring.
>> It supports up to 1 fan, 1 internal temperature sensor, 1 external
>> temperature sensor and an 8 entry look up table to create a
>> programmable temperature response.
>>
>> Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
> 
> This looks very much like a clone of LM63. Why does it warrant a new driver ?
> 

I had another look at the two chips. EMC2101 is indeed - with a few exceptions -
almost identical to LM63. There are minor differences; for example, EMC2101 does
not (or not officially) support temperature offset registers. That does not
warrant a separate driver.

Please add support for EMC2101 to the lm63 driver. If you like, feel free
to "modernize" the lm63 driver to use the with_info API and/or to use regmap,
but a separate driver for what is essentially the same chip is not acceptable.

Thanks,
Guenter


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2025-07-17  1:30 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-09 16:48 [PATCH v3 0/3] hwmon: add Microchip EMC2101 driver Álvaro Fernández Rojas
2025-07-09 16:48 ` [PATCH v3 1/3] docs: hwmon: add emc2101.rst to docs Álvaro Fernández Rojas
2025-07-09 16:48 ` [PATCH v3 2/3] dt-bindings: hwmon: Add Microchip EMC2101 support Álvaro Fernández Rojas
2025-07-10 10:00   ` Krzysztof Kozlowski
2025-07-09 16:48 ` [PATCH v3 3/3] drivers: hwmon: add EMC2101 driver Álvaro Fernández Rojas
2025-07-16 16:05   ` Guenter Roeck
2025-07-17  1:30     ` Guenter Roeck

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).