Merge tag 'hwmon-for-v5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck...
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 31 Aug 2021 21:37:41 +0000 (14:37 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 31 Aug 2021 21:37:41 +0000 (14:37 -0700)
Pull hwmon updates from Guenter Roeck:
 "New drivers for:

   - Aquacomputer D5 Next

   - SB-RMI power module

  Added chip support to existing drivers:

   - Support for various Zen2 and Zen3 APUs and for Yellow Carp (SMU
     v13) added to k10temp driver

   - Support for Silicom n5010 PAC added to intel-m10-bmc driver

   - Support for BPD-RS600 added to pmbus/bpa-rs600 driver

  Other notable changes:

   - In k10temp, do not display Tdie on Zen CPUs if there is no
     difference between Tdie and Tctl

   - Converted adt7470 and dell-smm drivers to use
     devm_hwmon_device_register_with_info API

   - Support for temperature/pwm tables added to axi-fan-control driver

   - Enabled fan control for Dell Precision 7510 in dell-smm driver

  Various other minor improvements and fixes in several drivers"

* tag 'hwmon-for-v5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (41 commits)
  hwmon: add driver for Aquacomputer D5 Next
  hwmon: (adt7470) Convert to devm_hwmon_device_register_with_info API
  hwmon: (adt7470) Convert to use regmap
  hwmon: (adt7470) Fix some style issues
  hwmon: (k10temp) Add support for yellow carp
  hwmon: (k10temp) Rework the temperature offset calculation
  hwmon: (k10temp) Don't show Tdie for all Zen/Zen2/Zen3 CPU/APU
  hwmon: (k10temp) Add additional missing Zen2 and Zen3 APUs
  hwmon: remove amd_energy driver in Makefile
  hwmon: (dell-smm) Rework SMM function debugging
  hwmon: (dell-smm) Mark i8k_get_fan_nominal_speed as __init
  hwmon: (dell-smm) Mark tables as __initconst
  hwmon: (pmbus/bpa-rs600) Add workaround for incorrect Pin max
  hwmon: (pmbus/bpa-rs600) Don't use rated limits as warn limits
  hwmon: (axi-fan-control) Support temperature vs pwm points
  hwmon: (axi-fan-control) Handle irqs in natural order
  hwmon: (axi-fan-control) Make sure the clock is enabled
  hwmon: (pmbus/ibm-cffps) Fix write bits for LED control
  hwmon: (w83781d) Match on device tree compatibles
  dt-bindings: hwmon: Add bindings for Winbond W83781D
  ...

25 files changed:
Documentation/devicetree/bindings/hwmon/amd,sbrmi.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/hwmon/winbond,w83781d.yaml [new file with mode: 0644]
Documentation/hwmon/aquacomputer_d5next.rst [new file with mode: 0644]
Documentation/hwmon/index.rst
Documentation/hwmon/sbrmi.rst [new file with mode: 0644]
Documentation/hwmon/scpi-hwmon.rst
Documentation/hwmon/sht4x.rst
MAINTAINERS
arch/x86/kernel/amd_nb.c
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/adt7470.c
drivers/hwmon/aquacomputer_d5next.c [new file with mode: 0644]
drivers/hwmon/axi-fan-control.c
drivers/hwmon/dell-smm-hwmon.c
drivers/hwmon/fam15h_power.c
drivers/hwmon/intel-m10-bmc-hwmon.c
drivers/hwmon/k10temp.c
drivers/hwmon/ntc_thermistor.c
drivers/hwmon/pmbus/bpa-rs600.c
drivers/hwmon/pmbus/ibm-cffps.c
drivers/hwmon/sbrmi.c [new file with mode: 0644]
drivers/hwmon/w83627ehf.c
drivers/hwmon/w83781d.c
include/linux/pci_ids.h

diff --git a/Documentation/devicetree/bindings/hwmon/amd,sbrmi.yaml b/Documentation/devicetree/bindings/hwmon/amd,sbrmi.yaml
new file mode 100644 (file)
index 0000000..7598b08
--- /dev/null
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/amd,sbrmi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: >
+  Sideband Remote Management Interface (SB-RMI) compliant
+  AMD SoC power device.
+
+maintainers:
+  - Akshay Gupta <Akshay.Gupta@amd.com>
+
+description: |
+  SB Remote Management Interface (SB-RMI) is an SMBus compatible
+  interface that reports AMD SoC's Power (normalized Power) using,
+  Mailbox Service Request and resembles a typical 8-pin remote power
+  sensor's I2C interface to BMC. The power attributes in hwmon
+  reports power in microwatts.
+
+properties:
+  compatible:
+    enum:
+      - amd,sbrmi
+
+  reg:
+    maxItems: 1
+    description: |
+      I2C bus address of the device as specified in Section SBI SMBus Address
+      of the SoC register reference. The SB-RMI address is normally 78h for
+      socket 0 and 70h for socket 1, but it could vary based on hardware
+      address select pins.
+      \[open source SoC register reference\]
+        https://www.amd.com/en/support/tech-docs?keyword=55898
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c0 {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        sbrmi@3c {
+                compatible = "amd,sbrmi";
+                reg = <0x3c>;
+        };
+    };
+...
diff --git a/Documentation/devicetree/bindings/hwmon/winbond,w83781d.yaml b/Documentation/devicetree/bindings/hwmon/winbond,w83781d.yaml
new file mode 100644 (file)
index 0000000..31ce77a
--- /dev/null
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+
+$id: http://devicetree.org/schemas/hwmon/winbond,w83781d.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Winbond W83781 and compatible hardware monitor IC
+
+maintainers:
+  - Linus Walleij <linus.walleij@linaro.org>
+
+properties:
+  compatible:
+    enum:
+      - winbond,w83781d
+      - winbond,w83781g
+      - winbond,w83782d
+      - winbond,w83783s
+      - asus,as99127f
+
+  reg:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        temperature-sensor@28 {
+            compatible = "winbond,w83781d";
+            reg = <0x28>;
+        };
+    };
diff --git a/Documentation/hwmon/aquacomputer_d5next.rst b/Documentation/hwmon/aquacomputer_d5next.rst
new file mode 100644 (file)
index 0000000..1f4bb4b
--- /dev/null
@@ -0,0 +1,61 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver aquacomputer-d5next
+=================================
+
+Supported devices:
+
+* Aquacomputer D5 Next watercooling pump
+
+Author: Aleksa Savic
+
+Description
+-----------
+
+This driver exposes hardware sensors of the Aquacomputer D5 Next watercooling
+pump, which communicates through a proprietary USB HID protocol.
+
+Available sensors are pump and fan speed, power, voltage and current, as
+well as coolant temperature. Also available through debugfs are the serial
+number, firmware version and power-on count.
+
+Attaching a fan is optional and allows it to be controlled using temperature
+curves directly from the pump. If it's not connected, the fan-related sensors
+will report zeroes.
+
+The pump can be configured either through software or via its physical
+interface. Configuring the pump through this driver is not implemented, as it
+seems to require sending it a complete configuration. That includes addressable
+RGB LEDs, for which there is no standard sysfs interface. Thus, that task is
+better suited for userspace tools.
+
+Usage notes
+-----------
+
+The pump communicates via HID reports. The driver is loaded automatically by
+the kernel and supports hotswapping.
+
+Sysfs entries
+-------------
+
+============ =============================================
+temp1_input  Coolant temperature (in millidegrees Celsius)
+fan1_input   Pump speed (in RPM)
+fan2_input   Fan speed (in RPM)
+power1_input Pump power (in micro Watts)
+power2_input Fan power (in micro Watts)
+in0_input    Pump voltage (in milli Volts)
+in1_input    Fan voltage (in milli Volts)
+in2_input    +5V rail voltage (in milli Volts)
+curr1_input  Pump current (in milli Amperes)
+curr2_input  Fan current (in milli Amperes)
+============ =============================================
+
+Debugfs entries
+---------------
+
+================ ===============================================
+serial_number    Serial number of the pump
+firmware_version Version of installed firmware
+power_cycles     Count of how many times the pump was powered on
+================ ===============================================
index bc01601..f790f12 100644 (file)
@@ -39,6 +39,7 @@ Hardware Monitoring Kernel Drivers
    adt7475
    aht10
    amc6821
+   aquacomputer_d5next
    asb100
    asc7621
    aspeed-pwm-tacho
@@ -160,6 +161,7 @@ Hardware Monitoring Kernel Drivers
    pwm-fan
    q54sj108a2
    raspberrypi-hwmon
+   sbrmi
    sbtsi_temp
    sch5627
    sch5636
diff --git a/Documentation/hwmon/sbrmi.rst b/Documentation/hwmon/sbrmi.rst
new file mode 100644 (file)
index 0000000..296049e
--- /dev/null
@@ -0,0 +1,79 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver sbrmi
+===================
+
+Supported hardware:
+
+  * Sideband Remote Management Interface (SB-RMI) compliant AMD SoC
+    device connected to the BMC via the APML.
+
+    Prefix: 'sbrmi'
+
+    Addresses scanned: This driver doesn't support address scanning.
+
+    To instantiate this driver on an AMD CPU with SB-RMI
+    support, the i2c bus number would be the bus connected from the board
+    management controller (BMC) to the CPU.
+    The SMBus address is really 7 bits. Some vendors and the SMBus
+    specification show the address as 8 bits, left justified with the R/W
+    bit as a write (0) making bit 0. Some vendors use only the 7 bits
+    to describe the address.
+    As mentioned in AMD's APML specification, The SB-RMI address is
+    normally 78h(0111 100W) or 3Ch(011 1100) for socket 0 and 70h(0111 000W)
+    or 38h(011 1000) for socket 1, but it could vary based on hardware
+    address select pins.
+
+    Datasheet: The SB-RMI interface and protocol along with the Advanced
+               Platform Management Link (APML) Specification is available
+               as part of the open source SoC register reference at:
+
+               https://www.amd.com/en/support/tech-docs?keyword=55898
+
+Author: Akshay Gupta <akshay.gupta@amd.com>
+
+Description
+-----------
+
+The APML provides a way to communicate with the SB Remote Management interface
+(SB-RMI) module from the external SMBus master that can be used to report socket
+power on AMD platforms using mailbox command and resembles a typical 8-pin remote
+power sensor's I2C interface to BMC.
+
+This driver implements current power with power cap and power cap max.
+
+sysfs-Interface
+---------------
+Power sensors can be queried and set via the standard ``hwmon`` interface
+on ``sysfs``, under the directory ``/sys/class/hwmon/hwmonX`` for some value
+of ``X`` (search for the ``X`` such that ``/sys/class/hwmon/hwmonX/name`` has
+content ``sbrmi``)
+
+================ ===== ========================================================
+Name             Perm   Description
+================ ===== ========================================================
+power1_input     RO    Current Power consumed
+power1_cap       RW    Power limit can be set between 0 and power1_cap_max
+power1_cap_max   RO    Maximum powerlimit calculated and reported by the SMU FW
+================ ===== ========================================================
+
+The following example show how the 'Power' attribute from the i2c-addresses
+can be monitored using the userspace utilities like ``sensors`` binary::
+
+  # sensors
+  sbrmi-i2c-1-38
+  Adapter: bcm2835 I2C adapter
+  power1:       61.00 W (cap = 225.00 W)
+
+  sbrmi-i2c-1-3c
+  Adapter: bcm2835 I2C adapter
+  power1:       28.39 W (cap = 224.77 W)
+  #
+
+Also, Below shows how get and set the values from sysfs entries individually::
+  # cat /sys/class/hwmon/hwmon1/power1_cap_max
+  225000000
+
+  # echo 180000000 > /sys/class/hwmon/hwmon1/power1_cap
+  # cat /sys/class/hwmon/hwmon1/power1_cap
+  180000000
index eee7022..1e3f83e 100644 (file)
@@ -32,5 +32,5 @@ Usage Notes
 
 The driver relies on device tree node to indicate the presence of SCPI
 support in the kernel. See
-Documentation/devicetree/bindings/arm/arm,scpi.txt for details of the
+Documentation/devicetree/bindings/firmware/arm,scpi.yaml for details of the
 devicetree node.
index 3b37abc..c318e55 100644 (file)
@@ -42,4 +42,4 @@ humidity1_input Measured humidity in %H
 update_interval The minimum interval for polling the sensor,
                 in milliseconds. Writable. Must be at least
                 2000.
-============== =============================================
+==========================================================
index 629a011..142e275 100644 (file)
@@ -1316,6 +1316,13 @@ L:       linux-media@vger.kernel.org
 S:     Maintained
 F:     drivers/media/i2c/aptina-pll.*
 
+AQUACOMPUTER D5 NEXT PUMP SENSOR DRIVER
+M:     Aleksa Savic <savicaleksa83@gmail.com>
+L:     linux-hwmon@vger.kernel.org
+S:     Maintained
+F:     Documentation/hwmon/aquacomputer_d5next.rst
+F:     drivers/hwmon/aquacomputer_d5next.c
+
 AQUANTIA ETHERNET DRIVER (atlantic)
 M:     Igor Russkikh <irusskikh@marvell.com>
 L:     netdev@vger.kernel.org
index 23dda36..c92c9c7 100644 (file)
@@ -25,6 +25,8 @@
 #define PCI_DEVICE_ID_AMD_17H_M60H_DF_F4 0x144c
 #define PCI_DEVICE_ID_AMD_17H_M70H_DF_F4 0x1444
 #define PCI_DEVICE_ID_AMD_19H_DF_F4    0x1654
+#define PCI_DEVICE_ID_AMD_19H_M40H_ROOT        0x14b5
+#define PCI_DEVICE_ID_AMD_19H_M40H_DF_F4 0x167d
 #define PCI_DEVICE_ID_AMD_19H_M50H_DF_F4 0x166e
 
 /* Protect the PCI config register pairs used for SMN and DF indirect access. */
@@ -37,6 +39,7 @@ static const struct pci_device_id amd_root_ids[] = {
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M10H_ROOT) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M30H_ROOT) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_ROOT) },
+       { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_ROOT) },
        {}
 };
 
@@ -58,6 +61,7 @@ static const struct pci_device_id amd_nb_misc_ids[] = {
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F3) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) },
+       { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) },
        {}
 };
@@ -74,6 +78,7 @@ static const struct pci_device_id amd_nb_link_ids[] = {
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F4) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F4) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_DF_F4) },
+       { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F4) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F4) },
        { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CNB17H_F4) },
        {}
index e367537..c4578e8 100644 (file)
@@ -254,6 +254,16 @@ config SENSORS_AHT10
          This driver can also be built as a module. If so, the module
          will be called aht10.
 
+config SENSORS_AQUACOMPUTER_D5NEXT
+       tristate "Aquacomputer D5 Next watercooling pump"
+       depends on USB_HID
+       help
+         If you say yes here you get support for the Aquacomputer D5 Next
+         watercooling pump sensors.
+
+         This driver can also be built as a module. If so, the module
+         will be called aquacomputer_d5next.
+
 config SENSORS_AS370
        tristate "Synaptics AS370 SoC hardware monitoring driver"
        help
@@ -1551,6 +1561,16 @@ config SENSORS_SBTSI
          This driver can also be built as a module. If so, the module will
          be called sbtsi_temp.
 
+config SENSORS_SBRMI
+       tristate "Emulated SB-RMI sensor"
+       depends on I2C
+       help
+         If you say yes here you get support for emulated RMI
+         sensors on AMD SoCs with APML interface connected to a BMC device.
+
+         This driver can also be built as a module. If so, the module will
+         be called sbrmi.
+
 config SENSORS_SHT15
        tristate "Sensiron humidity and temperature sensors. SHT15 and compat."
        depends on GPIOLIB || COMPILE_TEST
index d712c61..1629402 100644 (file)
@@ -45,8 +45,8 @@ obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
 obj-$(CONFIG_SENSORS_ADT7470)  += adt7470.o
 obj-$(CONFIG_SENSORS_ADT7475)  += adt7475.o
 obj-$(CONFIG_SENSORS_AHT10)    += aht10.o
-obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o
 obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
+obj-$(CONFIG_SENSORS_AQUACOMPUTER_D5NEXT) += aquacomputer_d5next.o
 obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
 obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
 obj-$(CONFIG_SENSORS_AS370)    += as370-hwmon.o
@@ -164,6 +164,7 @@ obj-$(CONFIG_SENSORS_PWM_FAN)       += pwm-fan.o
 obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON)        += raspberrypi-hwmon.o
 obj-$(CONFIG_SENSORS_S3C)      += s3c-hwmon.o
 obj-$(CONFIG_SENSORS_SBTSI)    += sbtsi_temp.o
+obj-$(CONFIG_SENSORS_SBRMI)    += sbrmi.o
 obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o
 obj-$(CONFIG_SENSORS_SCH5627)  += sch5627.o
 obj-$(CONFIG_SENSORS_SCH5636)  += sch5636.o
index 2e8feac..d519aca 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/delay.h>
 #include <linux/log2.h>
 #include <linux/kthread.h>
+#include <linux/regmap.h>
 #include <linux/slab.h>
 #include <linux/util_macros.h>
 
@@ -35,7 +36,10 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
 #define ADT7470_REG_PWM_MAX_BASE_ADDR          0x38
 #define ADT7470_REG_PWM_MAX_MAX_ADDR           0x3B
 #define ADT7470_REG_CFG                                0x40
+#define                ADT7470_STRT_MASK               0x01
+#define                ADT7470_TEST_MASK               0x02
 #define                ADT7470_FSPD_MASK               0x04
+#define                ADT7470_T05_STB_MASK            0x80
 #define ADT7470_REG_ALARM1                     0x41
 #define                ADT7470_R1T_ALARM               0x01
 #define                ADT7470_R2T_ALARM               0x02
@@ -137,7 +141,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END };
 #define ADT7470_FREQ_SHIFT     4
 
 struct adt7470_data {
-       struct i2c_client       *client;
+       struct regmap           *regmap;
        struct mutex            lock;
        char                    sensors_valid;
        char                    limits_valid;
@@ -171,51 +175,76 @@ struct adt7470_data {
  * 16-bit registers on the ADT7470 are low-byte first.  The data sheet says
  * that the low byte must be read before the high byte.
  */
-static inline int adt7470_read_word_data(struct i2c_client *client, u8 reg)
+static inline int adt7470_read_word_data(struct adt7470_data *data, unsigned int reg,
+                                        unsigned int *val)
 {
-       u16 foo;
-       foo = i2c_smbus_read_byte_data(client, reg);
-       foo |= ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8);
-       return foo;
+       u8 regval[2];
+       int err;
+
+       err = regmap_bulk_read(data->regmap, reg, &regval, 2);
+       if (err < 0)
+               return err;
+
+       *val = regval[0] | (regval[1] << 8);
+
+       return 0;
 }
 
-static inline int adt7470_write_word_data(struct i2c_client *client, u8 reg,
-                                         u16 value)
+static inline int adt7470_write_word_data(struct adt7470_data *data, unsigned int reg,
+                                         unsigned int val)
 {
-       return i2c_smbus_write_byte_data(client, reg, value & 0xFF)
-              || i2c_smbus_write_byte_data(client, reg + 1, value >> 8);
+       u8 regval[2];
+
+       regval[0] = val & 0xFF;
+       regval[1] = val >> 8;
+
+       return regmap_bulk_write(data->regmap, reg, &regval, 2);
 }
 
 /* Probe for temperature sensors.  Assumes lock is held */
-static int adt7470_read_temperatures(struct i2c_client *client,
-                                    struct adt7470_data *data)
+static int adt7470_read_temperatures(struct adt7470_data *data)
 {
        unsigned long res;
+       unsigned int pwm_cfg[2];
+       int err;
        int i;
-       u8 cfg, pwm[4], pwm_cfg[2];
+       u8 pwm[ADT7470_FAN_COUNT];
 
        /* save pwm[1-4] config register */
-       pwm_cfg[0] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_CFG(0));
-       pwm_cfg[1] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM_CFG(2));
+       err = regmap_read(data->regmap, ADT7470_REG_PWM_CFG(0), &pwm_cfg[0]);
+       if (err < 0)
+               return err;
+       err = regmap_read(data->regmap, ADT7470_REG_PWM_CFG(2), &pwm_cfg[1]);
+       if (err < 0)
+               return err;
 
        /* set manual pwm to whatever it is set to now */
-       for (i = 0; i < ADT7470_FAN_COUNT; i++)
-               pwm[i] = i2c_smbus_read_byte_data(client, ADT7470_REG_PWM(i));
+       err = regmap_bulk_read(data->regmap, ADT7470_REG_PWM(0), &pwm[0],
+                              ADT7470_PWM_COUNT);
+       if (err < 0)
+               return err;
 
        /* put pwm in manual mode */
-       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(0),
-               pwm_cfg[0] & ~(ADT7470_PWM_AUTO_MASK));
-       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(2),
-               pwm_cfg[1] & ~(ADT7470_PWM_AUTO_MASK));
+       err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(0),
+                                ADT7470_PWM_AUTO_MASK, 0);
+       if (err < 0)
+               return err;
+       err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(2),
+                                ADT7470_PWM_AUTO_MASK, 0);
+       if (err < 0)
+               return err;
 
        /* write pwm control to whatever it was */
-       for (i = 0; i < ADT7470_FAN_COUNT; i++)
-               i2c_smbus_write_byte_data(client, ADT7470_REG_PWM(i), pwm[i]);
+       err = regmap_bulk_write(data->regmap, ADT7470_REG_PWM(0), &pwm[0],
+                               ADT7470_PWM_COUNT);
+       if (err < 0)
+               return err;
 
        /* start reading temperature sensors */
-       cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
-       cfg |= 0x80;
-       i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg);
+       err = regmap_update_bits(data->regmap, ADT7470_REG_CFG,
+                                ADT7470_T05_STB_MASK, ADT7470_T05_STB_MASK);
+       if (err < 0)
+               return err;
 
        /* Delay is 200ms * number of temp sensors. */
        res = msleep_interruptible((data->num_temp_sensors >= 0 ?
@@ -223,26 +252,31 @@ static int adt7470_read_temperatures(struct i2c_client *client,
                                    TEMP_COLLECTION_TIME));
 
        /* done reading temperature sensors */
-       cfg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
-       cfg &= ~0x80;
-       i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, cfg);
+       err = regmap_update_bits(data->regmap, ADT7470_REG_CFG,
+                                ADT7470_T05_STB_MASK, 0);
+       if (err < 0)
+               return err;
 
        /* restore pwm[1-4] config registers */
-       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(0), pwm_cfg[0]);
-       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_CFG(2), pwm_cfg[1]);
-
-       if (res) {
-               pr_err("ha ha, interrupted\n");
+       err = regmap_write(data->regmap, ADT7470_REG_PWM_CFG(0), pwm_cfg[0]);
+       if (err < 0)
+               return err;
+       err = regmap_write(data->regmap, ADT7470_REG_PWM_CFG(2), pwm_cfg[1]);
+       if (err < 0)
+               return err;
+
+       if (res)
                return -EAGAIN;
-       }
 
        /* Only count fans if we have to */
        if (data->num_temp_sensors >= 0)
                return 0;
 
+       err = regmap_bulk_read(data->regmap, ADT7470_TEMP_REG(0), &data->temp[0],
+                              ADT7470_TEMP_COUNT);
+       if (err < 0)
+               return err;
        for (i = 0; i < ADT7470_TEMP_COUNT; i++) {
-               data->temp[i] = i2c_smbus_read_byte_data(client,
-                                               ADT7470_TEMP_REG(i));
                if (data->temp[i])
                        data->num_temp_sensors = i + 1;
        }
@@ -257,7 +291,7 @@ static int adt7470_update_thread(void *p)
 
        while (!kthread_should_stop()) {
                mutex_lock(&data->lock);
-               adt7470_read_temperatures(client, data);
+               adt7470_read_temperatures(data);
                mutex_unlock(&data->lock);
 
                set_current_state(TASK_INTERRUPTIBLE);
@@ -272,89 +306,116 @@ static int adt7470_update_thread(void *p)
 
 static int adt7470_update_sensors(struct adt7470_data *data)
 {
-       struct i2c_client *client = data->client;
-       u8 cfg;
+       unsigned int val;
+       int err;
        int i;
 
        if (!data->temperatures_probed)
-               adt7470_read_temperatures(client, data);
+               err = adt7470_read_temperatures(data);
        else
-               for (i = 0; i < ADT7470_TEMP_COUNT; i++)
-                       data->temp[i] = i2c_smbus_read_byte_data(client,
-                                               ADT7470_TEMP_REG(i));
+               err = regmap_bulk_read(data->regmap, ADT7470_TEMP_REG(0), &data->temp[0],
+                                      ADT7470_TEMP_COUNT);
+       if (err < 0)
+               return err;
 
-       for (i = 0; i < ADT7470_FAN_COUNT; i++)
-               data->fan[i] = adt7470_read_word_data(client,
-                                               ADT7470_REG_FAN(i));
+       for (i = 0; i < ADT7470_FAN_COUNT; i++) {
+               err = adt7470_read_word_data(data, ADT7470_REG_FAN(i), &val);
+               if (err < 0)
+                       return err;
+               data->fan[i] =  val;
+       }
 
-       for (i = 0; i < ADT7470_PWM_COUNT; i++) {
-               int reg;
-               int reg_mask;
+       err = regmap_bulk_read(data->regmap, ADT7470_REG_PWM(0), &data->pwm[0], ADT7470_PWM_COUNT);
+       if (err < 0)
+               return err;
 
-               data->pwm[i] = i2c_smbus_read_byte_data(client,
-                                               ADT7470_REG_PWM(i));
+       for (i = 0; i < ADT7470_PWM_COUNT; i++) {
+               unsigned int mask;
 
                if (i % 2)
-                       reg_mask = ADT7470_PWM2_AUTO_MASK;
+                       mask = ADT7470_PWM2_AUTO_MASK;
                else
-                       reg_mask = ADT7470_PWM1_AUTO_MASK;
+                       mask = ADT7470_PWM1_AUTO_MASK;
 
-               reg = ADT7470_REG_PWM_CFG(i);
-               if (i2c_smbus_read_byte_data(client, reg) & reg_mask)
-                       data->pwm_automatic[i] = 1;
-               else
-                       data->pwm_automatic[i] = 0;
+               err = regmap_read(data->regmap, ADT7470_REG_PWM_CFG(i), &val);
+               if (err < 0)
+                       return err;
+               data->pwm_automatic[i] = !!(val & mask);
 
-               reg = ADT7470_REG_PWM_AUTO_TEMP(i);
-               cfg = i2c_smbus_read_byte_data(client, reg);
+               err = regmap_read(data->regmap, ADT7470_REG_PWM_AUTO_TEMP(i), &val);
+               if (err < 0)
+                       return err;
                if (!(i % 2))
-                       data->pwm_auto_temp[i] = cfg >> 4;
+                       data->pwm_auto_temp[i] = val >> 4;
                else
-                       data->pwm_auto_temp[i] = cfg & 0xF;
+                       data->pwm_auto_temp[i] = val & 0xF;
        }
 
-       if (i2c_smbus_read_byte_data(client, ADT7470_REG_CFG) &
-           ADT7470_FSPD_MASK)
-               data->force_pwm_max = 1;
-       else
-               data->force_pwm_max = 0;
+       err = regmap_read(data->regmap, ADT7470_REG_CFG, &val);
+       if (err < 0)
+               return err;
+       data->force_pwm_max = !!(val & ADT7470_FSPD_MASK);
+
+       err = regmap_read(data->regmap, ADT7470_REG_ALARM1, &val);
+       if (err < 0)
+               return err;
+       data->alarm = val;
+       if (data->alarm & ADT7470_OOL_ALARM) {
+               err = regmap_read(data->regmap, ADT7470_REG_ALARM2, &val);
+               if (err < 0)
+                       return err;
+               data->alarm |= ALARM2(val);
+       }
 
-       data->alarm = i2c_smbus_read_byte_data(client, ADT7470_REG_ALARM1);
-       if (data->alarm & ADT7470_OOL_ALARM)
-               data->alarm |= ALARM2(i2c_smbus_read_byte_data(client,
-                                                       ADT7470_REG_ALARM2));
-       data->alarms_mask = adt7470_read_word_data(client,
-                                                  ADT7470_REG_ALARM1_MASK);
+       err = adt7470_read_word_data(data, ADT7470_REG_ALARM1_MASK, &val);
+       if (err < 0)
+               return err;
+       data->alarms_mask = val;
 
        return 0;
 }
 
 static int adt7470_update_limits(struct adt7470_data *data)
 {
-       struct i2c_client *client = data->client;
+       unsigned int val;
+       int err;
        int i;
 
        for (i = 0; i < ADT7470_TEMP_COUNT; i++) {
-               data->temp_min[i] = i2c_smbus_read_byte_data(client,
-                                               ADT7470_TEMP_MIN_REG(i));
-               data->temp_max[i] = i2c_smbus_read_byte_data(client,
-                                               ADT7470_TEMP_MAX_REG(i));
+               err = regmap_read(data->regmap, ADT7470_TEMP_MIN_REG(i), &val);
+               if (err < 0)
+                       return err;
+               data->temp_min[i] = (s8)val;
+               err = regmap_read(data->regmap, ADT7470_TEMP_MAX_REG(i), &val);
+               if (err < 0)
+                       return err;
+               data->temp_max[i] = (s8)val;
        }
 
        for (i = 0; i < ADT7470_FAN_COUNT; i++) {
-               data->fan_min[i] = adt7470_read_word_data(client,
-                                               ADT7470_REG_FAN_MIN(i));
-               data->fan_max[i] = adt7470_read_word_data(client,
-                                               ADT7470_REG_FAN_MAX(i));
+               err = adt7470_read_word_data(data, ADT7470_REG_FAN_MIN(i), &val);
+               if (err < 0)
+                       return err;
+               data->fan_min[i] = val;
+               err = adt7470_read_word_data(data, ADT7470_REG_FAN_MAX(i), &val);
+               if (err < 0)
+                       return err;
+               data->fan_max[i] = val;
        }
 
        for (i = 0; i < ADT7470_PWM_COUNT; i++) {
-               data->pwm_max[i] = i2c_smbus_read_byte_data(client,
-                                               ADT7470_REG_PWM_MAX(i));
-               data->pwm_min[i] = i2c_smbus_read_byte_data(client,
-                                               ADT7470_REG_PWM_MIN(i));
-               data->pwm_tmin[i] = i2c_smbus_read_byte_data(client,
-                                               ADT7470_REG_PWM_TMIN(i));
+               err = regmap_read(data->regmap, ADT7470_REG_PWM_MAX(i), &val);
+               if (err < 0)
+                       return err;
+               data->pwm_max[i] = val;
+               err = regmap_read(data->regmap, ADT7470_REG_PWM_MIN(i), &val);
+               if (err < 0)
+                       return err;
+               data->pwm_min[i] = val;
+               err = regmap_read(data->regmap, ADT7470_REG_PWM_TMIN(i), &val);
+               if (err < 0)
+                       return err;
+               data->pwm_tmin[i] = (s8)val;
        }
 
        return 0;
@@ -472,93 +533,63 @@ static ssize_t num_temp_sensors_store(struct device *dev,
        return count;
 }
 
-static ssize_t temp_min_show(struct device *dev,
-                            struct device_attribute *devattr, char *buf)
+static int adt7470_temp_read(struct device *dev, u32 attr, int channel, long *val)
 {
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct adt7470_data *data = adt7470_update_device(dev);
 
        if (IS_ERR(data))
                return PTR_ERR(data);
 
-       return sprintf(buf, "%d\n", 1000 * data->temp_min[attr->index]);
-}
-
-static ssize_t temp_min_store(struct device *dev,
-                             struct device_attribute *devattr,
-                             const char *buf, size_t count)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
-       long temp;
-
-       if (kstrtol(buf, 10, &temp))
-               return -EINVAL;
-
-       temp = clamp_val(temp, -128000, 127000);
-       temp = DIV_ROUND_CLOSEST(temp, 1000);
-
-       mutex_lock(&data->lock);
-       data->temp_min[attr->index] = temp;
-       i2c_smbus_write_byte_data(client, ADT7470_TEMP_MIN_REG(attr->index),
-                                 temp);
-       mutex_unlock(&data->lock);
-
-       return count;
-}
-
-static ssize_t temp_max_show(struct device *dev,
-                            struct device_attribute *devattr, char *buf)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = adt7470_update_device(dev);
-
-       if (IS_ERR(data))
-               return PTR_ERR(data);
+       switch (attr) {
+       case hwmon_temp_input:
+               *val = 1000 * data->temp[channel];
+               break;
+       case hwmon_temp_min:
+               *val = 1000 * data->temp_min[channel];
+               break;
+       case hwmon_temp_max:
+               *val = 1000 * data->temp_max[channel];
+               break;
+       case hwmon_temp_alarm:
+               *val = !!(data->alarm & channel);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
 
-       return sprintf(buf, "%d\n", 1000 * data->temp_max[attr->index]);
+       return 0;
 }
 
-static ssize_t temp_max_store(struct device *dev,
-                             struct device_attribute *devattr,
-                             const char *buf, size_t count)
+static int adt7470_temp_write(struct device *dev, u32 attr, int channel, long val)
 {
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
-       long temp;
-
-       if (kstrtol(buf, 10, &temp))
-               return -EINVAL;
-
-       temp = clamp_val(temp, -128000, 127000);
-       temp = DIV_ROUND_CLOSEST(temp, 1000);
-
-       mutex_lock(&data->lock);
-       data->temp_max[attr->index] = temp;
-       i2c_smbus_write_byte_data(client, ADT7470_TEMP_MAX_REG(attr->index),
-                                 temp);
-       mutex_unlock(&data->lock);
-
-       return count;
-}
+       int err;
 
-static ssize_t temp_show(struct device *dev, struct device_attribute *devattr,
-                        char *buf)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = adt7470_update_device(dev);
+       val = clamp_val(val, -128000, 127000);
+       val = DIV_ROUND_CLOSEST(val, 1000);
 
-       if (IS_ERR(data))
-               return PTR_ERR(data);
+       switch (attr) {
+       case hwmon_temp_min:
+               mutex_lock(&data->lock);
+               data->temp_min[channel] = val;
+               err = regmap_write(data->regmap, ADT7470_TEMP_MIN_REG(channel), val);
+               mutex_unlock(&data->lock);
+               break;
+       case hwmon_temp_max:
+               mutex_lock(&data->lock);
+               data->temp_max[channel] = val;
+               err = regmap_write(data->regmap, ADT7470_TEMP_MAX_REG(channel), val);
+               mutex_unlock(&data->lock);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
 
-       return sprintf(buf, "%d\n", 1000 * data->temp[attr->index]);
+       return err;
 }
 
 static ssize_t alarm_mask_show(struct device *dev,
-                          struct device_attribute *devattr,
-                          char *buf)
+                              struct device_attribute *devattr, char *buf)
 {
        struct adt7470_data *data = adt7470_update_device(dev);
 
@@ -574,6 +605,7 @@ static ssize_t alarm_mask_store(struct device *dev,
 {
        struct adt7470_data *data = dev_get_drvdata(dev);
        long mask;
+       int err;
 
        if (kstrtoul(buf, 0, &mask))
                return -EINVAL;
@@ -583,104 +615,74 @@ static ssize_t alarm_mask_store(struct device *dev,
 
        mutex_lock(&data->lock);
        data->alarms_mask = mask;
-       adt7470_write_word_data(data->client, ADT7470_REG_ALARM1_MASK, mask);
+       err = adt7470_write_word_data(data, ADT7470_REG_ALARM1_MASK, mask);
        mutex_unlock(&data->lock);
 
-       return count;
+       return err < 0 ? err : count;
 }
 
-static ssize_t fan_max_show(struct device *dev,
-                           struct device_attribute *devattr, char *buf)
+static int adt7470_fan_read(struct device *dev, u32 attr, int channel, long *val)
 {
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct adt7470_data *data = adt7470_update_device(dev);
 
        if (IS_ERR(data))
                return PTR_ERR(data);
 
-       if (FAN_DATA_VALID(data->fan_max[attr->index]))
-               return sprintf(buf, "%d\n",
-                              FAN_PERIOD_TO_RPM(data->fan_max[attr->index]));
-       else
-               return sprintf(buf, "0\n");
-}
-
-static ssize_t fan_max_store(struct device *dev,
-                            struct device_attribute *devattr,
-                            const char *buf, size_t count)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
-       long temp;
-
-       if (kstrtol(buf, 10, &temp) || !temp)
-               return -EINVAL;
-
-       temp = FAN_RPM_TO_PERIOD(temp);
-       temp = clamp_val(temp, 1, 65534);
-
-       mutex_lock(&data->lock);
-       data->fan_max[attr->index] = temp;
-       adt7470_write_word_data(client, ADT7470_REG_FAN_MAX(attr->index), temp);
-       mutex_unlock(&data->lock);
-
-       return count;
-}
-
-static ssize_t fan_min_show(struct device *dev,
-                           struct device_attribute *devattr, char *buf)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = adt7470_update_device(dev);
-
-       if (IS_ERR(data))
-               return PTR_ERR(data);
+       switch (attr) {
+       case hwmon_fan_input:
+               if (FAN_DATA_VALID(data->fan[channel]))
+                       *val = FAN_PERIOD_TO_RPM(data->fan[channel]);
+               else
+                       *val = 0;
+               break;
+       case hwmon_fan_min:
+               if (FAN_DATA_VALID(data->fan_min[channel]))
+                       *val = FAN_PERIOD_TO_RPM(data->fan_min[channel]);
+               else
+                       *val = 0;
+               break;
+       case hwmon_fan_max:
+               if (FAN_DATA_VALID(data->fan_max[channel]))
+                       *val = FAN_PERIOD_TO_RPM(data->fan_max[channel]);
+               else
+                       *val = 0;
+               break;
+       case hwmon_fan_alarm:
+               *val = !!(data->alarm & (1 << (12 + channel)));
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
 
-       if (FAN_DATA_VALID(data->fan_min[attr->index]))
-               return sprintf(buf, "%d\n",
-                              FAN_PERIOD_TO_RPM(data->fan_min[attr->index]));
-       else
-               return sprintf(buf, "0\n");
+       return 0;
 }
 
-static ssize_t fan_min_store(struct device *dev,
-                            struct device_attribute *devattr,
-                            const char *buf, size_t count)
+static int adt7470_fan_write(struct device *dev, u32 attr, int channel, long val)
 {
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
-       long temp;
-
-       if (kstrtol(buf, 10, &temp) || !temp)
-               return -EINVAL;
-
-       temp = FAN_RPM_TO_PERIOD(temp);
-       temp = clamp_val(temp, 1, 65534);
-
-       mutex_lock(&data->lock);
-       data->fan_min[attr->index] = temp;
-       adt7470_write_word_data(client, ADT7470_REG_FAN_MIN(attr->index), temp);
-       mutex_unlock(&data->lock);
-
-       return count;
-}
+       int err;
 
-static ssize_t fan_show(struct device *dev, struct device_attribute *devattr,
-                       char *buf)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = adt7470_update_device(dev);
+       val = FAN_RPM_TO_PERIOD(val);
+       val = clamp_val(val, 1, 65534);
 
-       if (IS_ERR(data))
-               return PTR_ERR(data);
+       switch (attr) {
+       case hwmon_fan_min:
+               mutex_lock(&data->lock);
+               data->fan_min[channel] = val;
+               err = adt7470_write_word_data(data, ADT7470_REG_FAN_MIN(channel), val);
+               mutex_unlock(&data->lock);
+               break;
+       case hwmon_fan_max:
+               mutex_lock(&data->lock);
+               data->fan_max[channel] = val;
+               err = adt7470_write_word_data(data, ADT7470_REG_FAN_MAX(channel), val);
+               mutex_unlock(&data->lock);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
 
-       if (FAN_DATA_VALID(data->fan[attr->index]))
-               return sprintf(buf, "%d\n",
-                              FAN_PERIOD_TO_RPM(data->fan[attr->index]));
-       else
-               return sprintf(buf, "0\n");
+       return err;
 }
 
 static ssize_t force_pwm_max_show(struct device *dev,
@@ -699,57 +701,20 @@ static ssize_t force_pwm_max_store(struct device *dev,
                                   const char *buf, size_t count)
 {
        struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
        long temp;
-       u8 reg;
+       int err;
 
        if (kstrtol(buf, 10, &temp))
                return -EINVAL;
 
        mutex_lock(&data->lock);
        data->force_pwm_max = temp;
-       reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
-       if (temp)
-               reg |= ADT7470_FSPD_MASK;
-       else
-               reg &= ~ADT7470_FSPD_MASK;
-       i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg);
+       err = regmap_update_bits(data->regmap, ADT7470_REG_CFG,
+                                ADT7470_FSPD_MASK,
+                                temp ? ADT7470_FSPD_MASK : 0);
        mutex_unlock(&data->lock);
 
-       return count;
-}
-
-static ssize_t pwm_show(struct device *dev, struct device_attribute *devattr,
-                       char *buf)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = adt7470_update_device(dev);
-
-       if (IS_ERR(data))
-               return PTR_ERR(data);
-
-       return sprintf(buf, "%d\n", data->pwm[attr->index]);
-}
-
-static ssize_t pwm_store(struct device *dev, struct device_attribute *devattr,
-                        const char *buf, size_t count)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
-       long temp;
-
-       if (kstrtol(buf, 10, &temp))
-               return -EINVAL;
-
-       temp = clamp_val(temp, 0, 255);
-
-       mutex_lock(&data->lock);
-       data->pwm[attr->index] = temp;
-       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM(attr->index), temp);
-       mutex_unlock(&data->lock);
-
-       return count;
+       return err < 0 ? err : count;
 }
 
 /* These are the valid PWM frequencies to the nearest Hz */
@@ -757,17 +722,20 @@ static const int adt7470_freq_map[] = {
        11, 15, 22, 29, 35, 44, 59, 88, 1400, 22500
 };
 
-static ssize_t pwm1_freq_show(struct device *dev,
-                             struct device_attribute *devattr, char *buf)
+static int pwm1_freq_get(struct device *dev)
 {
-       struct adt7470_data *data = adt7470_update_device(dev);
-       unsigned char cfg_reg_1;
-       unsigned char cfg_reg_2;
+       struct adt7470_data *data = dev_get_drvdata(dev);
+       unsigned int cfg_reg_1, cfg_reg_2;
        int index;
+       int err;
 
        mutex_lock(&data->lock);
-       cfg_reg_1 = i2c_smbus_read_byte_data(data->client, ADT7470_REG_CFG);
-       cfg_reg_2 = i2c_smbus_read_byte_data(data->client, ADT7470_REG_CFG_2);
+       err = regmap_read(data->regmap, ADT7470_REG_CFG, &cfg_reg_1);
+       if (err < 0)
+               goto out;
+       err = regmap_read(data->regmap, ADT7470_REG_CFG_2, &cfg_reg_2);
+       if (err < 0)
+               goto out;
        mutex_unlock(&data->lock);
 
        index = (cfg_reg_2 & ADT7470_FREQ_MASK) >> ADT7470_FREQ_SHIFT;
@@ -776,22 +744,43 @@ static ssize_t pwm1_freq_show(struct device *dev,
        if (index >= ARRAY_SIZE(adt7470_freq_map))
                index = ARRAY_SIZE(adt7470_freq_map) - 1;
 
-       return scnprintf(buf, PAGE_SIZE, "%d\n", adt7470_freq_map[index]);
+       return adt7470_freq_map[index];
+
+out:
+       mutex_unlock(&data->lock);
+       return err;
+}
+
+static int adt7470_pwm_read(struct device *dev, u32 attr, int channel, long *val)
+{
+       struct adt7470_data *data = adt7470_update_device(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       switch (attr) {
+       case hwmon_pwm_input:
+               *val = data->pwm[channel];
+               break;
+       case hwmon_pwm_enable:
+               *val = 1 + data->pwm_automatic[channel];
+               break;
+       case hwmon_pwm_freq:
+               *val = pwm1_freq_get(dev);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
 }
 
-static ssize_t pwm1_freq_store(struct device *dev,
-                              struct device_attribute *devattr,
-                              const char *buf, size_t count)
+static int pwm1_freq_set(struct device *dev, long freq)
 {
        struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
-       long freq;
+       unsigned int low_freq = ADT7470_CFG_LF;
        int index;
-       int low_freq = ADT7470_CFG_LF;
-       unsigned char val;
-
-       if (kstrtol(buf, 10, &freq))
-               return -EINVAL;
+       int err;
 
        /* Round the user value given to the closest available frequency */
        index = find_closest(freq, adt7470_freq_map,
@@ -804,16 +793,61 @@ static ssize_t pwm1_freq_store(struct device *dev,
 
        mutex_lock(&data->lock);
        /* Configuration Register 1 */
-       val = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
-       i2c_smbus_write_byte_data(client, ADT7470_REG_CFG,
-                                 (val & ~ADT7470_CFG_LF) | low_freq);
+       err = regmap_update_bits(data->regmap, ADT7470_REG_CFG,
+                                ADT7470_CFG_LF, low_freq);
+       if (err < 0)
+               goto out;
+
        /* Configuration Register 2 */
-       val = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG_2);
-       i2c_smbus_write_byte_data(client, ADT7470_REG_CFG_2,
-               (val & ~ADT7470_FREQ_MASK) | (index << ADT7470_FREQ_SHIFT));
+       err = regmap_update_bits(data->regmap, ADT7470_REG_CFG_2,
+                                ADT7470_FREQ_MASK,
+                                index << ADT7470_FREQ_SHIFT);
+out:
        mutex_unlock(&data->lock);
 
-       return count;
+       return err;
+}
+
+static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val)
+{
+       struct adt7470_data *data = dev_get_drvdata(dev);
+       unsigned int pwm_auto_reg_mask;
+       int err;
+
+       switch (attr) {
+       case hwmon_pwm_input:
+               val = clamp_val(val, 0, 255);
+               mutex_lock(&data->lock);
+               data->pwm[channel] = val;
+               err = regmap_write(data->regmap, ADT7470_REG_PWM(channel),
+                                  data->pwm[channel]);
+               mutex_unlock(&data->lock);
+               break;
+       case hwmon_pwm_enable:
+               if (channel % 2)
+                       pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK;
+               else
+                       pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK;
+
+               if (val != 2 && val != 1)
+                       return -EINVAL;
+               val--;
+
+               mutex_lock(&data->lock);
+               data->pwm_automatic[channel] = val;
+               err = regmap_update_bits(data->regmap, ADT7470_REG_PWM_CFG(channel),
+                                        pwm_auto_reg_mask,
+                                        val ? pwm_auto_reg_mask : 0);
+               mutex_unlock(&data->lock);
+               break;
+       case hwmon_pwm_freq:
+               err = pwm1_freq_set(dev, val);
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return err;
 }
 
 static ssize_t pwm_max_show(struct device *dev,
@@ -834,8 +868,8 @@ static ssize_t pwm_max_store(struct device *dev,
 {
        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
        long temp;
+       int err;
 
        if (kstrtol(buf, 10, &temp))
                return -EINVAL;
@@ -844,11 +878,11 @@ static ssize_t pwm_max_store(struct device *dev,
 
        mutex_lock(&data->lock);
        data->pwm_max[attr->index] = temp;
-       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MAX(attr->index),
-                                 temp);
+       err = regmap_write(data->regmap, ADT7470_REG_PWM_MAX(attr->index),
+                          temp);
        mutex_unlock(&data->lock);
 
-       return count;
+       return err < 0 ? err : count;
 }
 
 static ssize_t pwm_min_show(struct device *dev,
@@ -869,8 +903,8 @@ static ssize_t pwm_min_store(struct device *dev,
 {
        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
        long temp;
+       int err;
 
        if (kstrtol(buf, 10, &temp))
                return -EINVAL;
@@ -879,11 +913,11 @@ static ssize_t pwm_min_store(struct device *dev,
 
        mutex_lock(&data->lock);
        data->pwm_min[attr->index] = temp;
-       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_MIN(attr->index),
-                                 temp);
+       err = regmap_write(data->regmap, ADT7470_REG_PWM_MIN(attr->index),
+                          temp);
        mutex_unlock(&data->lock);
 
-       return count;
+       return err < 0 ? err : count;
 }
 
 static ssize_t pwm_tmax_show(struct device *dev,
@@ -917,8 +951,8 @@ static ssize_t pwm_tmin_store(struct device *dev,
 {
        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
        long temp;
+       int err;
 
        if (kstrtol(buf, 10, &temp))
                return -EINVAL;
@@ -928,60 +962,11 @@ static ssize_t pwm_tmin_store(struct device *dev,
 
        mutex_lock(&data->lock);
        data->pwm_tmin[attr->index] = temp;
-       i2c_smbus_write_byte_data(client, ADT7470_REG_PWM_TMIN(attr->index),
-                                 temp);
-       mutex_unlock(&data->lock);
-
-       return count;
-}
-
-static ssize_t pwm_auto_show(struct device *dev,
-                            struct device_attribute *devattr, char *buf)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = adt7470_update_device(dev);
-
-       if (IS_ERR(data))
-               return PTR_ERR(data);
-
-       return sprintf(buf, "%d\n", 1 + data->pwm_automatic[attr->index]);
-}
-
-static ssize_t pwm_auto_store(struct device *dev,
-                             struct device_attribute *devattr,
-                             const char *buf, size_t count)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
-       int pwm_auto_reg = ADT7470_REG_PWM_CFG(attr->index);
-       int pwm_auto_reg_mask;
-       long temp;
-       u8 reg;
-
-       if (kstrtol(buf, 10, &temp))
-               return -EINVAL;
-
-       if (attr->index % 2)
-               pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK;
-       else
-               pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK;
-
-       if (temp != 2 && temp != 1)
-               return -EINVAL;
-       temp--;
-
-       mutex_lock(&data->lock);
-       data->pwm_automatic[attr->index] = temp;
-       reg = i2c_smbus_read_byte_data(client, pwm_auto_reg);
-       if (temp)
-               reg |= pwm_auto_reg_mask;
-       else
-               reg &= ~pwm_auto_reg_mask;
-       i2c_smbus_write_byte_data(client, pwm_auto_reg, reg);
+       err = regmap_write(data->regmap, ADT7470_REG_PWM_TMIN(attr->index),
+                          temp);
        mutex_unlock(&data->lock);
 
-       return count;
+       return err < 0 ? err : count;
 }
 
 static ssize_t pwm_auto_temp_show(struct device *dev,
@@ -1016,10 +1001,10 @@ static ssize_t pwm_auto_temp_store(struct device *dev,
 {
        struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
        struct adt7470_data *data = dev_get_drvdata(dev);
-       struct i2c_client *client = data->client;
        int pwm_auto_reg = ADT7470_REG_PWM_AUTO_TEMP(attr->index);
+       unsigned int mask, val;
        long temp;
-       u8 reg;
+       int err;
 
        if (kstrtol(buf, 10, &temp))
                return -EINVAL;
@@ -1030,111 +1015,27 @@ static ssize_t pwm_auto_temp_store(struct device *dev,
 
        mutex_lock(&data->lock);
        data->pwm_automatic[attr->index] = temp;
-       reg = i2c_smbus_read_byte_data(client, pwm_auto_reg);
 
        if (!(attr->index % 2)) {
-               reg &= 0xF;
-               reg |= (temp << 4) & 0xF0;
+               mask = 0xF0;
+               val = (temp << 4) & 0xF0;
        } else {
-               reg &= 0xF0;
-               reg |= temp & 0xF;
+               mask = 0x0F;
+               val = temp & 0x0F;
        }
 
-       i2c_smbus_write_byte_data(client, pwm_auto_reg, reg);
+       err = regmap_update_bits(data->regmap, pwm_auto_reg, mask, val);
        mutex_unlock(&data->lock);
 
-       return count;
-}
-
-static ssize_t alarm_show(struct device *dev,
-                         struct device_attribute *devattr, char *buf)
-{
-       struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
-       struct adt7470_data *data = adt7470_update_device(dev);
-
-       if (data->alarm & attr->index)
-               return sprintf(buf, "1\n");
-       else
-               return sprintf(buf, "0\n");
+       return err < 0 ? err : count;
 }
 
 static DEVICE_ATTR_RW(alarm_mask);
 static DEVICE_ATTR_RW(num_temp_sensors);
 static DEVICE_ATTR_RW(auto_update_interval);
 
-static SENSOR_DEVICE_ATTR_RW(temp1_max, temp_max, 0);
-static SENSOR_DEVICE_ATTR_RW(temp2_max, temp_max, 1);
-static SENSOR_DEVICE_ATTR_RW(temp3_max, temp_max, 2);
-static SENSOR_DEVICE_ATTR_RW(temp4_max, temp_max, 3);
-static SENSOR_DEVICE_ATTR_RW(temp5_max, temp_max, 4);
-static SENSOR_DEVICE_ATTR_RW(temp6_max, temp_max, 5);
-static SENSOR_DEVICE_ATTR_RW(temp7_max, temp_max, 6);
-static SENSOR_DEVICE_ATTR_RW(temp8_max, temp_max, 7);
-static SENSOR_DEVICE_ATTR_RW(temp9_max, temp_max, 8);
-static SENSOR_DEVICE_ATTR_RW(temp10_max, temp_max, 9);
-
-static SENSOR_DEVICE_ATTR_RW(temp1_min, temp_min, 0);
-static SENSOR_DEVICE_ATTR_RW(temp2_min, temp_min, 1);
-static SENSOR_DEVICE_ATTR_RW(temp3_min, temp_min, 2);
-static SENSOR_DEVICE_ATTR_RW(temp4_min, temp_min, 3);
-static SENSOR_DEVICE_ATTR_RW(temp5_min, temp_min, 4);
-static SENSOR_DEVICE_ATTR_RW(temp6_min, temp_min, 5);
-static SENSOR_DEVICE_ATTR_RW(temp7_min, temp_min, 6);
-static SENSOR_DEVICE_ATTR_RW(temp8_min, temp_min, 7);
-static SENSOR_DEVICE_ATTR_RW(temp9_min, temp_min, 8);
-static SENSOR_DEVICE_ATTR_RW(temp10_min, temp_min, 9);
-
-static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0);
-static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1);
-static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2);
-static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3);
-static SENSOR_DEVICE_ATTR_RO(temp5_input, temp, 4);
-static SENSOR_DEVICE_ATTR_RO(temp6_input, temp, 5);
-static SENSOR_DEVICE_ATTR_RO(temp7_input, temp, 6);
-static SENSOR_DEVICE_ATTR_RO(temp8_input, temp, 7);
-static SENSOR_DEVICE_ATTR_RO(temp9_input, temp, 8);
-static SENSOR_DEVICE_ATTR_RO(temp10_input, temp, 9);
-
-static SENSOR_DEVICE_ATTR_RO(temp1_alarm, alarm, ADT7470_R1T_ALARM);
-static SENSOR_DEVICE_ATTR_RO(temp2_alarm, alarm, ADT7470_R2T_ALARM);
-static SENSOR_DEVICE_ATTR_RO(temp3_alarm, alarm, ADT7470_R3T_ALARM);
-static SENSOR_DEVICE_ATTR_RO(temp4_alarm, alarm, ADT7470_R4T_ALARM);
-static SENSOR_DEVICE_ATTR_RO(temp5_alarm, alarm, ADT7470_R5T_ALARM);
-static SENSOR_DEVICE_ATTR_RO(temp6_alarm, alarm, ADT7470_R6T_ALARM);
-static SENSOR_DEVICE_ATTR_RO(temp7_alarm, alarm, ADT7470_R7T_ALARM);
-static SENSOR_DEVICE_ATTR_RO(temp8_alarm, alarm, ALARM2(ADT7470_R8T_ALARM));
-static SENSOR_DEVICE_ATTR_RO(temp9_alarm, alarm, ALARM2(ADT7470_R9T_ALARM));
-static SENSOR_DEVICE_ATTR_RO(temp10_alarm, alarm, ALARM2(ADT7470_R10T_ALARM));
-
-static SENSOR_DEVICE_ATTR_RW(fan1_max, fan_max, 0);
-static SENSOR_DEVICE_ATTR_RW(fan2_max, fan_max, 1);
-static SENSOR_DEVICE_ATTR_RW(fan3_max, fan_max, 2);
-static SENSOR_DEVICE_ATTR_RW(fan4_max, fan_max, 3);
-
-static SENSOR_DEVICE_ATTR_RW(fan1_min, fan_min, 0);
-static SENSOR_DEVICE_ATTR_RW(fan2_min, fan_min, 1);
-static SENSOR_DEVICE_ATTR_RW(fan3_min, fan_min, 2);
-static SENSOR_DEVICE_ATTR_RW(fan4_min, fan_min, 3);
-
-static SENSOR_DEVICE_ATTR_RO(fan1_input, fan, 0);
-static SENSOR_DEVICE_ATTR_RO(fan2_input, fan, 1);
-static SENSOR_DEVICE_ATTR_RO(fan3_input, fan, 2);
-static SENSOR_DEVICE_ATTR_RO(fan4_input, fan, 3);
-
-static SENSOR_DEVICE_ATTR_RO(fan1_alarm, alarm, ALARM2(ADT7470_FAN1_ALARM));
-static SENSOR_DEVICE_ATTR_RO(fan2_alarm, alarm, ALARM2(ADT7470_FAN2_ALARM));
-static SENSOR_DEVICE_ATTR_RO(fan3_alarm, alarm, ALARM2(ADT7470_FAN3_ALARM));
-static SENSOR_DEVICE_ATTR_RO(fan4_alarm, alarm, ALARM2(ADT7470_FAN4_ALARM));
-
 static SENSOR_DEVICE_ATTR_RW(force_pwm_max, force_pwm_max, 0);
 
-static SENSOR_DEVICE_ATTR_RW(pwm1, pwm, 0);
-static SENSOR_DEVICE_ATTR_RW(pwm2, pwm, 1);
-static SENSOR_DEVICE_ATTR_RW(pwm3, pwm, 2);
-static SENSOR_DEVICE_ATTR_RW(pwm4, pwm, 3);
-
-static DEVICE_ATTR_RW(pwm1_freq);
-
 static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_pwm, pwm_min, 0);
 static SENSOR_DEVICE_ATTR_RW(pwm2_auto_point1_pwm, pwm_min, 1);
 static SENSOR_DEVICE_ATTR_RW(pwm3_auto_point1_pwm, pwm_min, 2);
@@ -1155,11 +1056,6 @@ static SENSOR_DEVICE_ATTR_RO(pwm2_auto_point2_temp, pwm_tmax, 1);
 static SENSOR_DEVICE_ATTR_RO(pwm3_auto_point2_temp, pwm_tmax, 2);
 static SENSOR_DEVICE_ATTR_RO(pwm4_auto_point2_temp, pwm_tmax, 3);
 
-static SENSOR_DEVICE_ATTR_RW(pwm1_enable, pwm_auto, 0);
-static SENSOR_DEVICE_ATTR_RW(pwm2_enable, pwm_auto, 1);
-static SENSOR_DEVICE_ATTR_RW(pwm3_enable, pwm_auto, 2);
-static SENSOR_DEVICE_ATTR_RW(pwm4_enable, pwm_auto, 3);
-
 static SENSOR_DEVICE_ATTR_RW(pwm1_auto_channels_temp, pwm_auto_temp, 0);
 static SENSOR_DEVICE_ATTR_RW(pwm2_auto_channels_temp, pwm_auto_temp, 1);
 static SENSOR_DEVICE_ATTR_RW(pwm3_auto_channels_temp, pwm_auto_temp, 2);
@@ -1169,68 +1065,7 @@ static struct attribute *adt7470_attrs[] = {
        &dev_attr_alarm_mask.attr,
        &dev_attr_num_temp_sensors.attr,
        &dev_attr_auto_update_interval.attr,
-       &sensor_dev_attr_temp1_max.dev_attr.attr,
-       &sensor_dev_attr_temp2_max.dev_attr.attr,
-       &sensor_dev_attr_temp3_max.dev_attr.attr,
-       &sensor_dev_attr_temp4_max.dev_attr.attr,
-       &sensor_dev_attr_temp5_max.dev_attr.attr,
-       &sensor_dev_attr_temp6_max.dev_attr.attr,
-       &sensor_dev_attr_temp7_max.dev_attr.attr,
-       &sensor_dev_attr_temp8_max.dev_attr.attr,
-       &sensor_dev_attr_temp9_max.dev_attr.attr,
-       &sensor_dev_attr_temp10_max.dev_attr.attr,
-       &sensor_dev_attr_temp1_min.dev_attr.attr,
-       &sensor_dev_attr_temp2_min.dev_attr.attr,
-       &sensor_dev_attr_temp3_min.dev_attr.attr,
-       &sensor_dev_attr_temp4_min.dev_attr.attr,
-       &sensor_dev_attr_temp5_min.dev_attr.attr,
-       &sensor_dev_attr_temp6_min.dev_attr.attr,
-       &sensor_dev_attr_temp7_min.dev_attr.attr,
-       &sensor_dev_attr_temp8_min.dev_attr.attr,
-       &sensor_dev_attr_temp9_min.dev_attr.attr,
-       &sensor_dev_attr_temp10_min.dev_attr.attr,
-       &sensor_dev_attr_temp1_input.dev_attr.attr,
-       &sensor_dev_attr_temp2_input.dev_attr.attr,
-       &sensor_dev_attr_temp3_input.dev_attr.attr,
-       &sensor_dev_attr_temp4_input.dev_attr.attr,
-       &sensor_dev_attr_temp5_input.dev_attr.attr,
-       &sensor_dev_attr_temp6_input.dev_attr.attr,
-       &sensor_dev_attr_temp7_input.dev_attr.attr,
-       &sensor_dev_attr_temp8_input.dev_attr.attr,
-       &sensor_dev_attr_temp9_input.dev_attr.attr,
-       &sensor_dev_attr_temp10_input.dev_attr.attr,
-       &sensor_dev_attr_temp1_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp2_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp3_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp4_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp5_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp6_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp7_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp8_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp9_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp10_alarm.dev_attr.attr,
-       &sensor_dev_attr_fan1_max.dev_attr.attr,
-       &sensor_dev_attr_fan2_max.dev_attr.attr,
-       &sensor_dev_attr_fan3_max.dev_attr.attr,
-       &sensor_dev_attr_fan4_max.dev_attr.attr,
-       &sensor_dev_attr_fan1_min.dev_attr.attr,
-       &sensor_dev_attr_fan2_min.dev_attr.attr,
-       &sensor_dev_attr_fan3_min.dev_attr.attr,
-       &sensor_dev_attr_fan4_min.dev_attr.attr,
-       &sensor_dev_attr_fan1_input.dev_attr.attr,
-       &sensor_dev_attr_fan2_input.dev_attr.attr,
-       &sensor_dev_attr_fan3_input.dev_attr.attr,
-       &sensor_dev_attr_fan4_input.dev_attr.attr,
-       &sensor_dev_attr_fan1_alarm.dev_attr.attr,
-       &sensor_dev_attr_fan2_alarm.dev_attr.attr,
-       &sensor_dev_attr_fan3_alarm.dev_attr.attr,
-       &sensor_dev_attr_fan4_alarm.dev_attr.attr,
        &sensor_dev_attr_force_pwm_max.dev_attr.attr,
-       &sensor_dev_attr_pwm1.dev_attr.attr,
-       &dev_attr_pwm1_freq.attr,
-       &sensor_dev_attr_pwm2.dev_attr.attr,
-       &sensor_dev_attr_pwm3.dev_attr.attr,
-       &sensor_dev_attr_pwm4.dev_attr.attr,
        &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
        &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
        &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr,
@@ -1247,10 +1082,6 @@ static struct attribute *adt7470_attrs[] = {
        &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
        &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr,
        &sensor_dev_attr_pwm4_auto_point2_temp.dev_attr.attr,
-       &sensor_dev_attr_pwm1_enable.dev_attr.attr,
-       &sensor_dev_attr_pwm2_enable.dev_attr.attr,
-       &sensor_dev_attr_pwm3_enable.dev_attr.attr,
-       &sensor_dev_attr_pwm4_enable.dev_attr.attr,
        &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr,
        &sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr,
        &sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr,
@@ -1260,6 +1091,129 @@ static struct attribute *adt7470_attrs[] = {
 
 ATTRIBUTE_GROUPS(adt7470);
 
+static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+                       int channel, long *val)
+{
+       switch (type) {
+       case hwmon_temp:
+               return adt7470_temp_read(dev, attr, channel, val);
+       case hwmon_fan:
+               return adt7470_fan_read(dev, attr, channel, val);
+       case hwmon_pwm:
+               return adt7470_pwm_read(dev, attr, channel, val);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+                        int channel, long val)
+{
+       switch (type) {
+       case hwmon_temp:
+               return adt7470_temp_write(dev, attr, channel, val);
+       case hwmon_fan:
+               return adt7470_fan_write(dev, attr, channel, val);
+       case hwmon_pwm:
+               return adt7470_pwm_write(dev, attr, channel, val);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type,
+                                 u32 attr, int channel)
+{
+       umode_t mode = 0;
+
+       switch (type) {
+       case hwmon_temp:
+               switch (attr) {
+               case hwmon_temp:
+               case hwmon_temp_alarm:
+                       mode = 0444;
+                       break;
+               case hwmon_temp_min:
+               case hwmon_temp_max:
+                       mode = 0644;
+                       break;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_fan:
+               switch (attr) {
+               case hwmon_fan_input:
+               case hwmon_fan_alarm:
+                       mode = 0444;
+                       break;
+               case hwmon_fan_min:
+               case hwmon_fan_max:
+                       mode = 0644;
+                       break;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_pwm:
+               switch (attr) {
+               case hwmon_pwm_input:
+               case hwmon_pwm_enable:
+                       mode = 0644;
+                       break;
+               case hwmon_pwm_freq:
+                       if (channel == 0)
+                               mode = 0644;
+                       else
+                               mode = 0;
+                       break;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+
+       return mode;
+}
+
+static const struct hwmon_ops adt7470_hwmon_ops = {
+       .is_visible = adt7470_is_visible,
+       .read = adt7470_read,
+       .write = adt7470_write,
+};
+
+static const struct hwmon_channel_info *adt7470_info[] = {
+       HWMON_CHANNEL_INFO(temp,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM,
+                          HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM),
+       HWMON_CHANNEL_INFO(fan,
+                          HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX | HWMON_F_DIV | HWMON_F_ALARM,
+                          HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX | HWMON_F_DIV | HWMON_F_ALARM,
+                          HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX | HWMON_F_DIV | HWMON_F_ALARM,
+                          HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX | HWMON_F_DIV | HWMON_F_ALARM),
+       HWMON_CHANNEL_INFO(pwm,
+                          HWMON_PWM_INPUT | HWMON_PWM_ENABLE | HWMON_PWM_FREQ,
+                          HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+                          HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+                          HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
+       NULL
+};
+
+static const struct hwmon_chip_info adt7470_chip_info = {
+       .ops = &adt7470_hwmon_ops,
+       .info = adt7470_info,
+};
+
 /* Return 0 if detection is successful, -ENODEV otherwise */
 static int adt7470_detect(struct i2c_client *client,
                          struct i2c_board_info *info)
@@ -1282,28 +1236,24 @@ static int adt7470_detect(struct i2c_client *client,
        if (revision != ADT7470_REVISION)
                return -ENODEV;
 
-       strlcpy(info->type, "adt7470", I2C_NAME_SIZE);
+       strscpy(info->type, "adt7470", I2C_NAME_SIZE);
 
        return 0;
 }
 
-static void adt7470_init_client(struct i2c_client *client)
-{
-       int reg = i2c_smbus_read_byte_data(client, ADT7470_REG_CFG);
-
-       if (reg < 0) {
-               dev_err(&client->dev, "cannot read configuration register\n");
-       } else {
-               /* start monitoring (and do a self-test) */
-               i2c_smbus_write_byte_data(client, ADT7470_REG_CFG, reg | 3);
-       }
-}
+static const struct regmap_config adt7470_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .use_single_read = true,
+       .use_single_write = true,
+};
 
 static int adt7470_probe(struct i2c_client *client)
 {
        struct device *dev = &client->dev;
        struct adt7470_data *data;
        struct device *hwmon_dev;
+       int err;
 
        data = devm_kzalloc(dev, sizeof(struct adt7470_data), GFP_KERNEL);
        if (!data)
@@ -1311,29 +1261,34 @@ static int adt7470_probe(struct i2c_client *client)
 
        data->num_temp_sensors = -1;
        data->auto_update_interval = AUTO_UPDATE_INTERVAL;
+       data->regmap = devm_regmap_init_i2c(client, &adt7470_regmap_config);
+       if (IS_ERR(data->regmap))
+               return PTR_ERR(data->regmap);
 
        i2c_set_clientdata(client, data);
-       data->client = client;
        mutex_init(&data->lock);
 
        dev_info(&client->dev, "%s chip found\n", client->name);
 
        /* Initialize the ADT7470 chip */
-       adt7470_init_client(client);
+       err = regmap_update_bits(data->regmap, ADT7470_REG_CFG,
+                                ADT7470_STRT_MASK | ADT7470_TEST_MASK,
+                                ADT7470_STRT_MASK | ADT7470_TEST_MASK);
+       if (err < 0)
+               return err;
 
        /* Register sysfs hooks */
-       hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
-                                                          data,
-                                                          adt7470_groups);
+       hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+                                                        &adt7470_chip_info,
+                                                        adt7470_groups);
 
        if (IS_ERR(hwmon_dev))
                return PTR_ERR(hwmon_dev);
 
        data->auto_update = kthread_run(adt7470_update_thread, client, "%s",
                                        dev_name(hwmon_dev));
-       if (IS_ERR(data->auto_update)) {
+       if (IS_ERR(data->auto_update))
                return PTR_ERR(data->auto_update);
-       }
 
        return 0;
 }
diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c
new file mode 100644 (file)
index 0000000..fb9341a
--- /dev/null
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * hwmon driver for Aquacomputer D5 Next watercooling pump
+ *
+ * The D5 Next sends HID reports (with ID 0x01) every second to report sensor values
+ * (coolant temperature, pump and fan speed, voltage, current and power). It responds to
+ * Get_Report requests, but returns a dummy value of no use.
+ *
+ * Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com>
+ */
+
+#include <asm/unaligned.h>
+#include <linux/debugfs.h>
+#include <linux/hid.h>
+#include <linux/hwmon.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+
+#define DRIVER_NAME                    "aquacomputer-d5next"
+
+#define D5NEXT_STATUS_REPORT_ID        0x01
+#define D5NEXT_STATUS_UPDATE_INTERVAL  (2 * HZ) /* In seconds */
+
+/* Register offsets for the D5 Next pump */
+
+#define D5NEXT_SERIAL_FIRST_PART       3
+#define D5NEXT_SERIAL_SECOND_PART      5
+#define D5NEXT_FIRMWARE_VERSION        13
+#define D5NEXT_POWER_CYCLES            24
+
+#define D5NEXT_COOLANT_TEMP            87
+
+#define D5NEXT_PUMP_SPEED              116
+#define D5NEXT_FAN_SPEED               103
+
+#define D5NEXT_PUMP_POWER              114
+#define D5NEXT_FAN_POWER               101
+
+#define D5NEXT_PUMP_VOLTAGE            110
+#define D5NEXT_FAN_VOLTAGE             97
+#define D5NEXT_5V_VOLTAGE              57
+
+#define D5NEXT_PUMP_CURRENT            112
+#define D5NEXT_FAN_CURRENT             99
+
+/* Labels for provided values */
+
+#define L_COOLANT_TEMP                 "Coolant temp"
+
+#define L_PUMP_SPEED                   "Pump speed"
+#define L_FAN_SPEED                    "Fan speed"
+
+#define L_PUMP_POWER                   "Pump power"
+#define L_FAN_POWER                    "Fan power"
+
+#define L_PUMP_VOLTAGE                 "Pump voltage"
+#define L_FAN_VOLTAGE                  "Fan voltage"
+#define L_5V_VOLTAGE                   "+5V voltage"
+
+#define L_PUMP_CURRENT                 "Pump current"
+#define L_FAN_CURRENT                  "Fan current"
+
+static const char *const label_speeds[] = {
+       L_PUMP_SPEED,
+       L_FAN_SPEED,
+};
+
+static const char *const label_power[] = {
+       L_PUMP_POWER,
+       L_FAN_POWER,
+};
+
+static const char *const label_voltages[] = {
+       L_PUMP_VOLTAGE,
+       L_FAN_VOLTAGE,
+       L_5V_VOLTAGE,
+};
+
+static const char *const label_current[] = {
+       L_PUMP_CURRENT,
+       L_FAN_CURRENT,
+};
+
+struct d5next_data {
+       struct hid_device *hdev;
+       struct device *hwmon_dev;
+       struct dentry *debugfs;
+       s32 temp_input;
+       u16 speed_input[2];
+       u32 power_input[2];
+       u16 voltage_input[3];
+       u16 current_input[2];
+       u32 serial_number[2];
+       u16 firmware_version;
+       u32 power_cycles; /* How many times the device was powered on */
+       unsigned long updated;
+};
+
+static umode_t d5next_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
+                                int channel)
+{
+       return 0444;
+}
+
+static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+                      long *val)
+{
+       struct d5next_data *priv = dev_get_drvdata(dev);
+
+       if (time_after(jiffies, priv->updated + D5NEXT_STATUS_UPDATE_INTERVAL))
+               return -ENODATA;
+
+       switch (type) {
+       case hwmon_temp:
+               *val = priv->temp_input;
+               break;
+       case hwmon_fan:
+               *val = priv->speed_input[channel];
+               break;
+       case hwmon_power:
+               *val = priv->power_input[channel];
+               break;
+       case hwmon_in:
+               *val = priv->voltage_input[channel];
+               break;
+       case hwmon_curr:
+               *val = priv->current_input[channel];
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+                             int channel, const char **str)
+{
+       switch (type) {
+       case hwmon_temp:
+               *str = L_COOLANT_TEMP;
+               break;
+       case hwmon_fan:
+               *str = label_speeds[channel];
+               break;
+       case hwmon_power:
+               *str = label_power[channel];
+               break;
+       case hwmon_in:
+               *str = label_voltages[channel];
+               break;
+       case hwmon_curr:
+               *str = label_current[channel];
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       return 0;
+}
+
+static const struct hwmon_ops d5next_hwmon_ops = {
+       .is_visible = d5next_is_visible,
+       .read = d5next_read,
+       .read_string = d5next_read_string,
+};
+
+static const struct hwmon_channel_info *d5next_info[] = {
+       HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),
+       HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL),
+       HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL),
+       HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL),
+       HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL),
+       NULL
+};
+
+static const struct hwmon_chip_info d5next_chip_info = {
+       .ops = &d5next_hwmon_ops,
+       .info = d5next_info,
+};
+
+static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
+{
+       struct d5next_data *priv;
+
+       if (report->id != D5NEXT_STATUS_REPORT_ID)
+               return 0;
+
+       priv = hid_get_drvdata(hdev);
+
+       /* Info provided with every report */
+
+       priv->serial_number[0] = get_unaligned_be16(data + D5NEXT_SERIAL_FIRST_PART);
+       priv->serial_number[1] = get_unaligned_be16(data + D5NEXT_SERIAL_SECOND_PART);
+
+       priv->firmware_version = get_unaligned_be16(data + D5NEXT_FIRMWARE_VERSION);
+       priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES);
+
+       /* Sensor readings */
+
+       priv->temp_input = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10;
+
+       priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED);
+       priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED);
+
+       priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000;
+       priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000;
+
+       priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10;
+       priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10;
+       priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10;
+
+       priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT);
+       priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT);
+
+       priv->updated = jiffies;
+
+       return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static int serial_number_show(struct seq_file *seqf, void *unused)
+{
+       struct d5next_data *priv = seqf->private;
+
+       seq_printf(seqf, "%05u-%05u\n", priv->serial_number[0], priv->serial_number[1]);
+
+       return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(serial_number);
+
+static int firmware_version_show(struct seq_file *seqf, void *unused)
+{
+       struct d5next_data *priv = seqf->private;
+
+       seq_printf(seqf, "%u\n", priv->firmware_version);
+
+       return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(firmware_version);
+
+static int power_cycles_show(struct seq_file *seqf, void *unused)
+{
+       struct d5next_data *priv = seqf->private;
+
+       seq_printf(seqf, "%u\n", priv->power_cycles);
+
+       return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(power_cycles);
+
+static void d5next_debugfs_init(struct d5next_data *priv)
+{
+       char name[32];
+
+       scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev));
+
+       priv->debugfs = debugfs_create_dir(name, NULL);
+       debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops);
+       debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops);
+       debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops);
+}
+
+#else
+
+static void d5next_debugfs_init(struct d5next_data *priv)
+{
+}
+
+#endif
+
+static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+       struct d5next_data *priv;
+       int ret;
+
+       priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->hdev = hdev;
+       hid_set_drvdata(hdev, priv);
+
+       priv->updated = jiffies - D5NEXT_STATUS_UPDATE_INTERVAL;
+
+       ret = hid_parse(hdev);
+       if (ret)
+               return ret;
+
+       ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+       if (ret)
+               return ret;
+
+       ret = hid_hw_open(hdev);
+       if (ret)
+               goto fail_and_stop;
+
+       priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "d5next", priv,
+                                                         &d5next_chip_info, NULL);
+
+       if (IS_ERR(priv->hwmon_dev)) {
+               ret = PTR_ERR(priv->hwmon_dev);
+               goto fail_and_close;
+       }
+
+       d5next_debugfs_init(priv);
+
+       return 0;
+
+fail_and_close:
+       hid_hw_close(hdev);
+fail_and_stop:
+       hid_hw_stop(hdev);
+       return ret;
+}
+
+static void d5next_remove(struct hid_device *hdev)
+{
+       struct d5next_data *priv = hid_get_drvdata(hdev);
+
+       debugfs_remove_recursive(priv->debugfs);
+       hwmon_device_unregister(priv->hwmon_dev);
+
+       hid_hw_close(hdev);
+       hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id d5next_table[] = {
+       { HID_USB_DEVICE(0x0c70, 0xf00e) }, /* Aquacomputer D5 Next */
+       {},
+};
+
+MODULE_DEVICE_TABLE(hid, d5next_table);
+
+static struct hid_driver d5next_driver = {
+       .name = DRIVER_NAME,
+       .id_table = d5next_table,
+       .probe = d5next_probe,
+       .remove = d5next_remove,
+       .raw_event = d5next_raw_event,
+};
+
+static int __init d5next_init(void)
+{
+       return hid_register_driver(&d5next_driver);
+}
+
+static void __exit d5next_exit(void)
+{
+       hid_unregister_driver(&d5next_driver);
+}
+
+/* Request to initialize after the HID bus to ensure it's not being loaded before */
+
+late_initcall(d5next_init);
+module_exit(d5next_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>");
+MODULE_DESCRIPTION("Hwmon driver for Aquacomputer D5 Next pump");
index e3f6b03..d2092c1 100644 (file)
@@ -8,6 +8,7 @@
 #include <linux/clk.h>
 #include <linux/fpga/adi-axi-common.h>
 #include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
 #define ADI_REG_PWM_PERIOD     0x00c0
 #define ADI_REG_TACH_MEASUR    0x00c4
 #define ADI_REG_TEMPERATURE    0x00c8
+#define ADI_REG_TEMP_00_H      0x0100
+#define ADI_REG_TEMP_25_L      0x0104
+#define ADI_REG_TEMP_25_H      0x0108
+#define ADI_REG_TEMP_50_L      0x010c
+#define ADI_REG_TEMP_50_H      0x0110
+#define ADI_REG_TEMP_75_L      0x0114
+#define ADI_REG_TEMP_75_H      0x0118
+#define ADI_REG_TEMP_100_L     0x011c
 
 #define ADI_REG_IRQ_MASK       0x0040
 #define ADI_REG_IRQ_PENDING    0x0044
@@ -62,6 +71,39 @@ static inline u32 axi_ioread(const u32 reg,
        return ioread32(ctl->base + reg);
 }
 
+/*
+ * The core calculates the temperature as:
+ *     T = /raw * 509.3140064 / 65535) - 280.2308787
+ */
+static ssize_t axi_fan_control_show(struct device *dev, struct device_attribute *da, char *buf)
+{
+       struct axi_fan_control_data *ctl = dev_get_drvdata(dev);
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+       u32 temp = axi_ioread(attr->index, ctl);
+
+       temp = DIV_ROUND_CLOSEST_ULL(temp * 509314ULL, 65535) - 280230;
+
+       return sprintf(buf, "%u\n", temp);
+}
+
+static ssize_t axi_fan_control_store(struct device *dev, struct device_attribute *da,
+                                    const char *buf, size_t count)
+{
+       struct axi_fan_control_data *ctl = dev_get_drvdata(dev);
+       struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+       u32 temp;
+       int ret;
+
+       ret = kstrtou32(buf, 10, &temp);
+       if (ret)
+               return ret;
+
+       temp = DIV_ROUND_CLOSEST_ULL((temp + 280230) * 65535ULL, 509314);
+       axi_iowrite(temp, attr->index, ctl);
+
+       return count;
+}
+
 static long axi_fan_control_get_pwm_duty(const struct axi_fan_control_data *ctl)
 {
        u32 pwm_width = axi_ioread(ADI_REG_PWM_WIDTH, ctl);
@@ -283,18 +325,9 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data)
        u32 irq_pending = axi_ioread(ADI_REG_IRQ_PENDING, ctl);
        u32 clear_mask;
 
-       if (irq_pending & ADI_IRQ_SRC_NEW_MEASUR) {
-               if (ctl->update_tacho_params) {
-                       u32 new_tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl);
-
-                       /* get 25% tolerance */
-                       u32 tach_tol = DIV_ROUND_CLOSEST(new_tach * 25, 100);
-                       /* set new tacho parameters */
-                       axi_iowrite(new_tach, ADI_REG_TACH_PERIOD, ctl);
-                       axi_iowrite(tach_tol, ADI_REG_TACH_TOLERANCE, ctl);
-                       ctl->update_tacho_params = false;
-               }
-       }
+       if (irq_pending & ADI_IRQ_SRC_TEMP_INCREASE)
+               /* hardware requested a new pwm */
+               ctl->hw_pwm_req = true;
 
        if (irq_pending & ADI_IRQ_SRC_PWM_CHANGED) {
                /*
@@ -310,9 +343,18 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data)
                }
        }
 
-       if (irq_pending & ADI_IRQ_SRC_TEMP_INCREASE)
-               /* hardware requested a new pwm */
-               ctl->hw_pwm_req = true;
+       if (irq_pending & ADI_IRQ_SRC_NEW_MEASUR) {
+               if (ctl->update_tacho_params) {
+                       u32 new_tach = axi_ioread(ADI_REG_TACH_MEASUR, ctl);
+                       /* get 25% tolerance */
+                       u32 tach_tol = DIV_ROUND_CLOSEST(new_tach * 25, 100);
+
+                       /* set new tacho parameters */
+                       axi_iowrite(new_tach, ADI_REG_TACH_PERIOD, ctl);
+                       axi_iowrite(tach_tol, ADI_REG_TACH_TOLERANCE, ctl);
+                       ctl->update_tacho_params = false;
+               }
+       }
 
        if (irq_pending & ADI_IRQ_SRC_TACH_ERR)
                ctl->fan_fault = 1;
@@ -351,6 +393,11 @@ static int axi_fan_control_init(struct axi_fan_control_data *ctl,
        return ret;
 }
 
+static void axi_fan_control_clk_disable(void *clk)
+{
+       clk_disable_unprepare(clk);
+}
+
 static const struct hwmon_channel_info *axi_fan_control_info[] = {
        HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT),
        HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_FAULT | HWMON_F_LABEL),
@@ -370,6 +417,36 @@ static const struct hwmon_chip_info axi_chip_info = {
        .info = axi_fan_control_info,
 };
 
+/* temperature threshold below which PWM should be 0% */
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp_hyst, axi_fan_control, ADI_REG_TEMP_00_H);
+/* temperature threshold above which PWM should be 25% */
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, axi_fan_control, ADI_REG_TEMP_25_L);
+/* temperature threshold below which PWM should be 25% */
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp_hyst, axi_fan_control, ADI_REG_TEMP_25_H);
+/* temperature threshold above which PWM should be 50% */
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, axi_fan_control, ADI_REG_TEMP_50_L);
+/* temperature threshold below which PWM should be 50% */
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp_hyst, axi_fan_control, ADI_REG_TEMP_50_H);
+/* temperature threshold above which PWM should be 75% */
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, axi_fan_control, ADI_REG_TEMP_75_L);
+/* temperature threshold below which PWM should be 75% */
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp_hyst, axi_fan_control, ADI_REG_TEMP_75_H);
+/* temperature threshold above which PWM should be 100% */
+static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, axi_fan_control, ADI_REG_TEMP_100_L);
+
+static struct attribute *axi_fan_control_attrs[] = {
+       &sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr,
+       &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
+       &sensor_dev_attr_pwm1_auto_point2_temp_hyst.dev_attr.attr,
+       &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
+       &sensor_dev_attr_pwm1_auto_point3_temp_hyst.dev_attr.attr,
+       &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
+       &sensor_dev_attr_pwm1_auto_point4_temp_hyst.dev_attr.attr,
+       &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
+       NULL,
+};
+ATTRIBUTE_GROUPS(axi_fan_control);
+
 static const u32 version_1_0_0 = ADI_AXI_PCORE_VER(1, 0, 'a');
 
 static const struct of_device_id axi_fan_control_of_match[] = {
@@ -406,6 +483,14 @@ static int axi_fan_control_probe(struct platform_device *pdev)
                return PTR_ERR(clk);
        }
 
+       ret = clk_prepare_enable(clk);
+       if (ret)
+               return ret;
+
+       ret = devm_add_action_or_reset(&pdev->dev, axi_fan_control_clk_disable, clk);
+       if (ret)
+               return ret;
+
        ctl->clk_rate = clk_get_rate(clk);
        if (!ctl->clk_rate)
                return -EINVAL;
@@ -446,7 +531,7 @@ static int axi_fan_control_probe(struct platform_device *pdev)
                                                         name,
                                                         ctl,
                                                         &axi_chip_info,
-                                                        NULL);
+                                                        axi_fan_control_groups);
 
        return PTR_ERR_OR_ZERO(ctl->hdev);
 }
index f2221ca..774c1b0 100644 (file)
@@ -14,7 +14,9 @@
 
 #include <linux/cpu.h>
 #include <linux/delay.h>
+#include <linux/err.h>
 #include <linux/module.h>
+#include <linux/platform_device.h>
 #include <linux/types.h>
 #include <linux/init.h>
 #include <linux/proc_fs.h>
@@ -23,7 +25,6 @@
 #include <linux/capability.h>
 #include <linux/mutex.h>
 #include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
 #include <linux/uaccess.h>
 #include <linux/io.h>
 #include <linux/sched.h>
 #define I8K_POWER_AC           0x05
 #define I8K_POWER_BATTERY      0x01
 
-static DEFINE_MUTEX(i8k_mutex);
-static char bios_version[4];
-static char bios_machineid[16];
-static struct device *i8k_hwmon_dev;
-static u32 i8k_hwmon_flags;
-static uint i8k_fan_mult = I8K_FAN_MULT;
-static uint i8k_pwm_mult;
-static uint i8k_fan_max = I8K_FAN_HIGH;
-static bool disallow_fan_type_call;
-static bool disallow_fan_support;
-static unsigned int manual_fan;
-static unsigned int auto_fan;
-
-#define I8K_HWMON_HAVE_TEMP1   (1 << 0)
-#define I8K_HWMON_HAVE_TEMP2   (1 << 1)
-#define I8K_HWMON_HAVE_TEMP3   (1 << 2)
-#define I8K_HWMON_HAVE_TEMP4   (1 << 3)
-#define I8K_HWMON_HAVE_TEMP5   (1 << 4)
-#define I8K_HWMON_HAVE_TEMP6   (1 << 5)
-#define I8K_HWMON_HAVE_TEMP7   (1 << 6)
-#define I8K_HWMON_HAVE_TEMP8   (1 << 7)
-#define I8K_HWMON_HAVE_TEMP9   (1 << 8)
-#define I8K_HWMON_HAVE_TEMP10  (1 << 9)
-#define I8K_HWMON_HAVE_FAN1    (1 << 10)
-#define I8K_HWMON_HAVE_FAN2    (1 << 11)
-#define I8K_HWMON_HAVE_FAN3    (1 << 12)
+#define DELL_SMM_NO_TEMP       10
+#define DELL_SMM_NO_FANS       3
+
+struct dell_smm_data {
+       struct mutex i8k_mutex; /* lock for sensors writes */
+       char bios_version[4];
+       char bios_machineid[16];
+       uint i8k_fan_mult;
+       uint i8k_pwm_mult;
+       uint i8k_fan_max;
+       bool disallow_fan_type_call;
+       bool disallow_fan_support;
+       unsigned int manual_fan;
+       unsigned int auto_fan;
+       int temp_type[DELL_SMM_NO_TEMP];
+       bool fan[DELL_SMM_NO_FANS];
+       int fan_type[DELL_SMM_NO_FANS];
+};
 
 MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
 MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
@@ -126,7 +119,34 @@ struct smm_regs {
        unsigned int edi __packed;
 };
 
-static inline const char *i8k_get_dmi_data(int field)
+static const char * const temp_labels[] = {
+       "CPU",
+       "GPU",
+       "SODIMM",
+       "Other",
+       "Ambient",
+       "Other",
+};
+
+static const char * const fan_labels[] = {
+       "Processor Fan",
+       "Motherboard Fan",
+       "Video Fan",
+       "Power Supply Fan",
+       "Chipset Fan",
+       "Other Fan",
+};
+
+static const char * const docking_labels[] = {
+       "Docking Processor Fan",
+       "Docking Motherboard Fan",
+       "Docking Video Fan",
+       "Docking Power Supply Fan",
+       "Docking Chipset Fan",
+       "Docking Other Fan",
+};
+
+static inline const char __init *i8k_get_dmi_data(int field)
 {
        const char *dmi_data = dmi_get_system_info(field);
 
@@ -138,17 +158,12 @@ static inline const char *i8k_get_dmi_data(int field)
  */
 static int i8k_smm_func(void *par)
 {
-       int rc;
+       ktime_t calltime = ktime_get();
        struct smm_regs *regs = par;
        int eax = regs->eax;
-
-#ifdef DEBUG
        int ebx = regs->ebx;
-       unsigned long duration;
-       ktime_t calltime, delta, rettime;
-
-       calltime = ktime_get();
-#endif
+       long long duration;
+       int rc;
 
        /* SMM requires CPU 0 */
        if (smp_processor_id() != 0)
@@ -210,13 +225,9 @@ static int i8k_smm_func(void *par)
        if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
                rc = -EINVAL;
 
-#ifdef DEBUG
-       rettime = ktime_get();
-       delta = ktime_sub(rettime, calltime);
-       duration = ktime_to_ns(delta) >> 10;
-       pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x  (took %7lu usecs)\n", eax, ebx,
-               (rc ? 0xffff : regs->eax & 0xffff), duration);
-#endif
+       duration = ktime_us_delta(ktime_get(), calltime);
+       pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x  (took %7lld usecs)\n", eax, ebx,
+                (rc ? 0xffff : regs->eax & 0xffff), duration);
 
        return rc;
 }
@@ -228,9 +239,9 @@ static int i8k_smm(struct smm_regs *regs)
 {
        int ret;
 
-       get_online_cpus();
+       cpus_read_lock();
        ret = smp_call_on_cpu(0, i8k_smm_func, regs, true);
-       put_online_cpus();
+       cpus_read_unlock();
 
        return ret;
 }
@@ -238,11 +249,11 @@ static int i8k_smm(struct smm_regs *regs)
 /*
  * Read the fan status.
  */
-static int i8k_get_fan_status(int fan)
+static int i8k_get_fan_status(const struct dell_smm_data *data, int fan)
 {
        struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
 
-       if (disallow_fan_support)
+       if (data->disallow_fan_support)
                return -EINVAL;
 
        regs.ebx = fan & 0xff;
@@ -252,87 +263,85 @@ static int i8k_get_fan_status(int fan)
 /*
  * Read the fan speed in RPM.
  */
-static int i8k_get_fan_speed(int fan)
+static int i8k_get_fan_speed(const struct dell_smm_data *data, int fan)
 {
        struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
 
-       if (disallow_fan_support)
+       if (data->disallow_fan_support)
                return -EINVAL;
 
        regs.ebx = fan & 0xff;
-       return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
+       return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
 }
 
 /*
  * Read the fan type.
  */
-static int _i8k_get_fan_type(int fan)
+static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan)
 {
        struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
 
-       if (disallow_fan_support || disallow_fan_type_call)
+       if (data->disallow_fan_support || data->disallow_fan_type_call)
                return -EINVAL;
 
        regs.ebx = fan & 0xff;
        return i8k_smm(&regs) ? : regs.eax & 0xff;
 }
 
-static int i8k_get_fan_type(int fan)
+static int i8k_get_fan_type(struct dell_smm_data *data, int fan)
 {
        /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
-       static int types[3] = { INT_MIN, INT_MIN, INT_MIN };
-
-       if (types[fan] == INT_MIN)
-               types[fan] = _i8k_get_fan_type(fan);
+       if (data->fan_type[fan] == INT_MIN)
+               data->fan_type[fan] = _i8k_get_fan_type(data, fan);
 
-       return types[fan];
+       return data->fan_type[fan];
 }
 
 /*
  * Read the fan nominal rpm for specific fan speed.
  */
-static int i8k_get_fan_nominal_speed(int fan, int speed)
+static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed)
 {
        struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
 
-       if (disallow_fan_support)
+       if (data->disallow_fan_support)
                return -EINVAL;
 
        regs.ebx = (fan & 0xff) | (speed << 8);
-       return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
+       return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
 }
 
 /*
  * Enable or disable automatic BIOS fan control support
  */
-static int i8k_enable_fan_auto_mode(bool enable)
+static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enable)
 {
        struct smm_regs regs = { };
 
-       if (disallow_fan_support)
+       if (data->disallow_fan_support)
                return -EINVAL;
 
-       regs.eax = enable ? auto_fan : manual_fan;
+       regs.eax = enable ? data->auto_fan : data->manual_fan;
        return i8k_smm(&regs);
 }
 
 /*
  * Set the fan speed (off, low, high). Returns the new fan status.
  */
-static int i8k_set_fan(int fan, int speed)
+static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed)
 {
        struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
 
-       if (disallow_fan_support)
+       if (data->disallow_fan_support)
                return -EINVAL;
 
-       speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
+       speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
        regs.ebx = (fan & 0xff) | (speed << 8);
 
-       return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
+       return i8k_smm(&regs) ? : i8k_get_fan_status(data, fan);
 }
 
-static int i8k_get_temp_type(int sensor)
+static int __init i8k_get_temp_type(int sensor)
 {
        struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
 
@@ -382,7 +391,7 @@ static int i8k_get_temp(int sensor)
        return temp;
 }
 
-static int i8k_get_dell_signature(int req_fn)
+static int __init i8k_get_dell_signature(int req_fn)
 {
        struct smm_regs regs = { .eax = req_fn, };
        int rc;
@@ -440,7 +449,7 @@ static int i8k_get_power_status(void)
  */
 
 static int
-i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
+i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg)
 {
        int val = 0;
        int speed;
@@ -452,12 +461,12 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
 
        switch (cmd) {
        case I8K_BIOS_VERSION:
-               if (!isdigit(bios_version[0]) || !isdigit(bios_version[1]) ||
-                   !isdigit(bios_version[2]))
+               if (!isdigit(data->bios_version[0]) || !isdigit(data->bios_version[1]) ||
+                   !isdigit(data->bios_version[2]))
                        return -EINVAL;
 
-               val = (bios_version[0] << 16) |
-                               (bios_version[1] << 8) | bios_version[2];
+               val = (data->bios_version[0] << 16) |
+                               (data->bios_version[1] << 8) | data->bios_version[2];
                break;
 
        case I8K_MACHINE_ID:
@@ -465,7 +474,7 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
                        return -EPERM;
 
                memset(buff, 0, sizeof(buff));
-               strlcpy(buff, bios_machineid, sizeof(buff));
+               strscpy(buff, data->bios_machineid, sizeof(buff));
                break;
 
        case I8K_FN_STATUS:
@@ -484,14 +493,14 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
                if (copy_from_user(&val, argp, sizeof(int)))
                        return -EFAULT;
 
-               val = i8k_get_fan_speed(val);
+               val = i8k_get_fan_speed(data, val);
                break;
 
        case I8K_GET_FAN:
                if (copy_from_user(&val, argp, sizeof(int)))
                        return -EFAULT;
 
-               val = i8k_get_fan_status(val);
+               val = i8k_get_fan_status(data, val);
                break;
 
        case I8K_SET_FAN:
@@ -504,7 +513,7 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
                if (copy_from_user(&speed, argp + 1, sizeof(int)))
                        return -EFAULT;
 
-               val = i8k_set_fan(val, speed);
+               val = i8k_set_fan(data, val, speed);
                break;
 
        default:
@@ -537,11 +546,12 @@ i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
 
 static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
 {
+       struct dell_smm_data *data = PDE_DATA(file_inode(fp));
        long ret;
 
-       mutex_lock(&i8k_mutex);
-       ret = i8k_ioctl_unlocked(fp, cmd, arg);
-       mutex_unlock(&i8k_mutex);
+       mutex_lock(&data->i8k_mutex);
+       ret = i8k_ioctl_unlocked(fp, data, cmd, arg);
+       mutex_unlock(&data->i8k_mutex);
 
        return ret;
 }
@@ -551,17 +561,18 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
  */
 static int i8k_proc_show(struct seq_file *seq, void *offset)
 {
+       struct dell_smm_data *data = seq->private;
        int fn_key, cpu_temp, ac_power;
        int left_fan, right_fan, left_speed, right_speed;
 
-       cpu_temp        = i8k_get_temp(0);                      /* 11100 Âµs */
-       left_fan        = i8k_get_fan_status(I8K_FAN_LEFT);     /*   580 Âµs */
-       right_fan       = i8k_get_fan_status(I8K_FAN_RIGHT);    /*   580 Âµs */
-       left_speed      = i8k_get_fan_speed(I8K_FAN_LEFT);      /*   580 Âµs */
-       right_speed     = i8k_get_fan_speed(I8K_FAN_RIGHT);     /*   580 Âµs */
-       fn_key          = i8k_get_fn_status();                  /*   750 Âµs */
+       cpu_temp        = i8k_get_temp(0);                              /* 11100 Âµs */
+       left_fan        = i8k_get_fan_status(data, I8K_FAN_LEFT);       /*   580 Âµs */
+       right_fan       = i8k_get_fan_status(data, I8K_FAN_RIGHT);      /*   580 Âµs */
+       left_speed      = i8k_get_fan_speed(data, I8K_FAN_LEFT);        /*   580 Âµs */
+       right_speed     = i8k_get_fan_speed(data, I8K_FAN_RIGHT);       /*   580 Âµs */
+       fn_key          = i8k_get_fn_status();                          /*   750 Âµs */
        if (power_status)
-               ac_power = i8k_get_power_status();              /* 14700 Âµs */
+               ac_power = i8k_get_power_status();                      /* 14700 Âµs */
        else
                ac_power = -1;
 
@@ -581,8 +592,8 @@ static int i8k_proc_show(struct seq_file *seq, void *offset)
         */
        seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
                   I8K_PROC_FMT,
-                  bios_version,
-                  (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : bios_machineid,
+                  data->bios_version,
+                  (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : data->bios_machineid,
                   cpu_temp,
                   left_fan, right_fan, left_speed, right_speed,
                   ac_power, fn_key);
@@ -592,7 +603,7 @@ static int i8k_proc_show(struct seq_file *seq, void *offset)
 
 static int i8k_open_fs(struct inode *inode, struct file *file)
 {
-       return single_open(file, i8k_proc_show, NULL);
+       return single_open(file, i8k_proc_show, PDE_DATA(inode));
 }
 
 static const struct proc_ops i8k_proc_ops = {
@@ -603,24 +614,24 @@ static const struct proc_ops i8k_proc_ops = {
        .proc_ioctl     = i8k_ioctl,
 };
 
-static void __init i8k_init_procfs(void)
+static void i8k_exit_procfs(void *param)
 {
-       /* Register the proc entry */
-       proc_create("i8k", 0, NULL, &i8k_proc_ops);
+       remove_proc_entry("i8k", NULL);
 }
 
-static void __exit i8k_exit_procfs(void)
+static void __init i8k_init_procfs(struct device *dev)
 {
-       remove_proc_entry("i8k", NULL);
-}
+       struct dell_smm_data *data = dev_get_drvdata(dev);
 
-#else
+       /* Register the proc entry */
+       proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data);
 
-static inline void __init i8k_init_procfs(void)
-{
+       devm_add_action_or_reset(dev, i8k_exit_procfs, NULL);
 }
 
-static inline void __exit i8k_exit_procfs(void)
+#else
+
+static void __init i8k_init_procfs(struct device *dev)
 {
 }
 
@@ -630,341 +641,299 @@ static inline void __exit i8k_exit_procfs(void)
  * Hwmon interface
  */
 
-static ssize_t i8k_hwmon_temp_label_show(struct device *dev,
-                                        struct device_attribute *devattr,
-                                        char *buf)
+static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
+                                  int channel)
 {
-       static const char * const labels[] = {
-               "CPU",
-               "GPU",
-               "SODIMM",
-               "Other",
-               "Ambient",
-               "Other",
-       };
-       int index = to_sensor_dev_attr(devattr)->index;
-       int type;
+       const struct dell_smm_data *data = drvdata;
 
-       type = i8k_get_temp_type(index);
-       if (type < 0)
-               return type;
-       if (type >= ARRAY_SIZE(labels))
-               type = ARRAY_SIZE(labels) - 1;
-       return sprintf(buf, "%s\n", labels[type]);
+       switch (type) {
+       case hwmon_temp:
+               switch (attr) {
+               case hwmon_temp_input:
+               case hwmon_temp_label:
+                       if (data->temp_type[channel] >= 0)
+                               return 0444;
+
+                       break;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_fan:
+               if (data->disallow_fan_support)
+                       break;
+
+               switch (attr) {
+               case hwmon_fan_input:
+                       if (data->fan[channel])
+                               return 0444;
+
+                       break;
+               case hwmon_fan_label:
+                       if (data->fan[channel] && !data->disallow_fan_type_call)
+                               return 0444;
+
+                       break;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_pwm:
+               if (data->disallow_fan_support)
+                       break;
+
+               switch (attr) {
+               case hwmon_pwm_input:
+                       if (data->fan[channel])
+                               return 0644;
+
+                       break;
+               case hwmon_pwm_enable:
+                       if (data->auto_fan)
+                               /*
+                                * There is no command for retrieve the current status
+                                * from BIOS, and userspace/firmware itself can change
+                                * it.
+                                * Thus we can only provide write-only access for now.
+                                */
+                               return 0200;
+
+                       break;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+
+       return 0;
 }
 
-static ssize_t i8k_hwmon_temp_show(struct device *dev,
-                                  struct device_attribute *devattr,
-                                  char *buf)
+static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+                        long *val)
 {
-       int index = to_sensor_dev_attr(devattr)->index;
-       int temp;
+       struct dell_smm_data *data = dev_get_drvdata(dev);
+       int ret;
+
+       switch (type) {
+       case hwmon_temp:
+               switch (attr) {
+               case hwmon_temp_input:
+                       ret = i8k_get_temp(channel);
+                       if (ret < 0)
+                               return ret;
+
+                       *val = ret * 1000;
+
+                       return 0;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_fan:
+               switch (attr) {
+               case hwmon_fan_input:
+                       ret = i8k_get_fan_speed(data, channel);
+                       if (ret < 0)
+                               return ret;
 
-       temp = i8k_get_temp(index);
-       if (temp < 0)
-               return temp;
-       return sprintf(buf, "%d\n", temp * 1000);
+                       *val = ret;
+
+                       return 0;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_pwm:
+               switch (attr) {
+               case hwmon_pwm_input:
+                       ret = i8k_get_fan_status(data, channel);
+                       if (ret < 0)
+                               return ret;
+
+                       *val = clamp_val(ret * data->i8k_pwm_mult, 0, 255);
+
+                       return 0;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+
+       return -EOPNOTSUPP;
 }
 
-static ssize_t i8k_hwmon_fan_label_show(struct device *dev,
-                                       struct device_attribute *devattr,
-                                       char *buf)
+static const char *dell_smm_fan_label(struct dell_smm_data *data, int channel)
 {
-       static const char * const labels[] = {
-               "Processor Fan",
-               "Motherboard Fan",
-               "Video Fan",
-               "Power Supply Fan",
-               "Chipset Fan",
-               "Other Fan",
-       };
-       int index = to_sensor_dev_attr(devattr)->index;
        bool dock = false;
-       int type;
+       int type = i8k_get_fan_type(data, channel);
 
-       type = i8k_get_fan_type(index);
        if (type < 0)
-               return type;
+               return ERR_PTR(type);
 
        if (type & 0x10) {
                dock = true;
                type &= 0x0F;
        }
 
-       if (type >= ARRAY_SIZE(labels))
-               type = (ARRAY_SIZE(labels) - 1);
-
-       return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
-}
-
-static ssize_t i8k_hwmon_fan_show(struct device *dev,
-                                 struct device_attribute *devattr, char *buf)
-{
-       int index = to_sensor_dev_attr(devattr)->index;
-       int fan_speed;
+       if (type >= ARRAY_SIZE(fan_labels))
+               type = ARRAY_SIZE(fan_labels) - 1;
 
-       fan_speed = i8k_get_fan_speed(index);
-       if (fan_speed < 0)
-               return fan_speed;
-       return sprintf(buf, "%d\n", fan_speed);
+       return dock ? docking_labels[type] : fan_labels[type];
 }
 
-static ssize_t i8k_hwmon_pwm_show(struct device *dev,
-                                 struct device_attribute *devattr, char *buf)
+static int dell_smm_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+                               int channel, const char **str)
 {
-       int index = to_sensor_dev_attr(devattr)->index;
-       int status;
+       struct dell_smm_data *data = dev_get_drvdata(dev);
+
+       switch (type) {
+       case hwmon_temp:
+               switch (attr) {
+               case hwmon_temp_label:
+                       *str = temp_labels[data->temp_type[channel]];
+                       return 0;
+               default:
+                       break;
+               }
+               break;
+       case hwmon_fan:
+               switch (attr) {
+               case hwmon_fan_label:
+                       *str = dell_smm_fan_label(data, channel);
+                       return PTR_ERR_OR_ZERO(*str);
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
 
-       status = i8k_get_fan_status(index);
-       if (status < 0)
-               return -EIO;
-       return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
+       return -EOPNOTSUPP;
 }
 
-static ssize_t i8k_hwmon_pwm_store(struct device *dev,
-                                  struct device_attribute *attr,
-                                  const char *buf, size_t count)
+static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+                         long val)
 {
-       int index = to_sensor_dev_attr(attr)->index;
-       unsigned long val;
+       struct dell_smm_data *data = dev_get_drvdata(dev);
+       unsigned long pwm;
+       bool enable;
        int err;
 
-       err = kstrtoul(buf, 10, &val);
-       if (err)
-               return err;
-       val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
+       switch (type) {
+       case hwmon_pwm:
+               switch (attr) {
+               case hwmon_pwm_input:
+                       pwm = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0,
+                                       data->i8k_fan_max);
 
-       mutex_lock(&i8k_mutex);
-       err = i8k_set_fan(index, val);
-       mutex_unlock(&i8k_mutex);
+                       mutex_lock(&data->i8k_mutex);
+                       err = i8k_set_fan(data, channel, pwm);
+                       mutex_unlock(&data->i8k_mutex);
 
-       return err < 0 ? -EIO : count;
-}
+                       if (err < 0)
+                               return err;
 
-static ssize_t i8k_hwmon_pwm_enable_store(struct device *dev,
-                                         struct device_attribute *attr,
-                                         const char *buf, size_t count)
-{
-       int err;
-       bool enable;
-       unsigned long val;
+                       return 0;
+               case hwmon_pwm_enable:
+                       if (!val)
+                               return -EINVAL;
 
-       if (!auto_fan)
-               return -ENODEV;
+                       if (val == 1)
+                               enable = false;
+                       else
+                               enable = true;
 
-       err = kstrtoul(buf, 10, &val);
-       if (err)
-               return err;
+                       mutex_lock(&data->i8k_mutex);
+                       err = i8k_enable_fan_auto_mode(data, enable);
+                       mutex_unlock(&data->i8k_mutex);
 
-       if (val == 1)
-               enable = false;
-       else if (val == 2)
-               enable = true;
-       else
-               return -EINVAL;
+                       if (err < 0)
+                               return err;
 
-       mutex_lock(&i8k_mutex);
-       err = i8k_enable_fan_auto_mode(enable);
-       mutex_unlock(&i8k_mutex);
+                       return 0;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
 
-       return err ? err : count;
+       return -EOPNOTSUPP;
 }
 
-static SENSOR_DEVICE_ATTR_RO(temp1_input, i8k_hwmon_temp, 0);
-static SENSOR_DEVICE_ATTR_RO(temp1_label, i8k_hwmon_temp_label, 0);
-static SENSOR_DEVICE_ATTR_RO(temp2_input, i8k_hwmon_temp, 1);
-static SENSOR_DEVICE_ATTR_RO(temp2_label, i8k_hwmon_temp_label, 1);
-static SENSOR_DEVICE_ATTR_RO(temp3_input, i8k_hwmon_temp, 2);
-static SENSOR_DEVICE_ATTR_RO(temp3_label, i8k_hwmon_temp_label, 2);
-static SENSOR_DEVICE_ATTR_RO(temp4_input, i8k_hwmon_temp, 3);
-static SENSOR_DEVICE_ATTR_RO(temp4_label, i8k_hwmon_temp_label, 3);
-static SENSOR_DEVICE_ATTR_RO(temp5_input, i8k_hwmon_temp, 4);
-static SENSOR_DEVICE_ATTR_RO(temp5_label, i8k_hwmon_temp_label, 4);
-static SENSOR_DEVICE_ATTR_RO(temp6_input, i8k_hwmon_temp, 5);
-static SENSOR_DEVICE_ATTR_RO(temp6_label, i8k_hwmon_temp_label, 5);
-static SENSOR_DEVICE_ATTR_RO(temp7_input, i8k_hwmon_temp, 6);
-static SENSOR_DEVICE_ATTR_RO(temp7_label, i8k_hwmon_temp_label, 6);
-static SENSOR_DEVICE_ATTR_RO(temp8_input, i8k_hwmon_temp, 7);
-static SENSOR_DEVICE_ATTR_RO(temp8_label, i8k_hwmon_temp_label, 7);
-static SENSOR_DEVICE_ATTR_RO(temp9_input, i8k_hwmon_temp, 8);
-static SENSOR_DEVICE_ATTR_RO(temp9_label, i8k_hwmon_temp_label, 8);
-static SENSOR_DEVICE_ATTR_RO(temp10_input, i8k_hwmon_temp, 9);
-static SENSOR_DEVICE_ATTR_RO(temp10_label, i8k_hwmon_temp_label, 9);
-static SENSOR_DEVICE_ATTR_RO(fan1_input, i8k_hwmon_fan, 0);
-static SENSOR_DEVICE_ATTR_RO(fan1_label, i8k_hwmon_fan_label, 0);
-static SENSOR_DEVICE_ATTR_RW(pwm1, i8k_hwmon_pwm, 0);
-static SENSOR_DEVICE_ATTR_WO(pwm1_enable, i8k_hwmon_pwm_enable, 0);
-static SENSOR_DEVICE_ATTR_RO(fan2_input, i8k_hwmon_fan, 1);
-static SENSOR_DEVICE_ATTR_RO(fan2_label, i8k_hwmon_fan_label, 1);
-static SENSOR_DEVICE_ATTR_RW(pwm2, i8k_hwmon_pwm, 1);
-static SENSOR_DEVICE_ATTR_RO(fan3_input, i8k_hwmon_fan, 2);
-static SENSOR_DEVICE_ATTR_RO(fan3_label, i8k_hwmon_fan_label, 2);
-static SENSOR_DEVICE_ATTR_RW(pwm3, i8k_hwmon_pwm, 2);
-
-static struct attribute *i8k_attrs[] = {
-       &sensor_dev_attr_temp1_input.dev_attr.attr,     /* 0 */
-       &sensor_dev_attr_temp1_label.dev_attr.attr,     /* 1 */
-       &sensor_dev_attr_temp2_input.dev_attr.attr,     /* 2 */
-       &sensor_dev_attr_temp2_label.dev_attr.attr,     /* 3 */
-       &sensor_dev_attr_temp3_input.dev_attr.attr,     /* 4 */
-       &sensor_dev_attr_temp3_label.dev_attr.attr,     /* 5 */
-       &sensor_dev_attr_temp4_input.dev_attr.attr,     /* 6 */
-       &sensor_dev_attr_temp4_label.dev_attr.attr,     /* 7 */
-       &sensor_dev_attr_temp5_input.dev_attr.attr,     /* 8 */
-       &sensor_dev_attr_temp5_label.dev_attr.attr,     /* 9 */
-       &sensor_dev_attr_temp6_input.dev_attr.attr,     /* 10 */
-       &sensor_dev_attr_temp6_label.dev_attr.attr,     /* 11 */
-       &sensor_dev_attr_temp7_input.dev_attr.attr,     /* 12 */
-       &sensor_dev_attr_temp7_label.dev_attr.attr,     /* 13 */
-       &sensor_dev_attr_temp8_input.dev_attr.attr,     /* 14 */
-       &sensor_dev_attr_temp8_label.dev_attr.attr,     /* 15 */
-       &sensor_dev_attr_temp9_input.dev_attr.attr,     /* 16 */
-       &sensor_dev_attr_temp9_label.dev_attr.attr,     /* 17 */
-       &sensor_dev_attr_temp10_input.dev_attr.attr,    /* 18 */
-       &sensor_dev_attr_temp10_label.dev_attr.attr,    /* 19 */
-       &sensor_dev_attr_fan1_input.dev_attr.attr,      /* 20 */
-       &sensor_dev_attr_fan1_label.dev_attr.attr,      /* 21 */
-       &sensor_dev_attr_pwm1.dev_attr.attr,            /* 22 */
-       &sensor_dev_attr_pwm1_enable.dev_attr.attr,     /* 23 */
-       &sensor_dev_attr_fan2_input.dev_attr.attr,      /* 24 */
-       &sensor_dev_attr_fan2_label.dev_attr.attr,      /* 25 */
-       &sensor_dev_attr_pwm2.dev_attr.attr,            /* 26 */
-       &sensor_dev_attr_fan3_input.dev_attr.attr,      /* 27 */
-       &sensor_dev_attr_fan3_label.dev_attr.attr,      /* 28 */
-       &sensor_dev_attr_pwm3.dev_attr.attr,            /* 29 */
+static const struct hwmon_ops dell_smm_ops = {
+       .is_visible = dell_smm_is_visible,
+       .read = dell_smm_read,
+       .read_string = dell_smm_read_string,
+       .write = dell_smm_write,
+};
+
+static const struct hwmon_channel_info *dell_smm_info[] = {
+       HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
+       HWMON_CHANNEL_INFO(temp,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL
+                          ),
+       HWMON_CHANNEL_INFO(fan,
+                          HWMON_F_INPUT | HWMON_F_LABEL,
+                          HWMON_F_INPUT | HWMON_F_LABEL,
+                          HWMON_F_INPUT | HWMON_F_LABEL
+                          ),
+       HWMON_CHANNEL_INFO(pwm,
+                          HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+                          HWMON_PWM_INPUT,
+                          HWMON_PWM_INPUT
+                          ),
        NULL
 };
 
-static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
-                             int index)
-{
-       if (disallow_fan_support && index >= 20)
-               return 0;
-       if (disallow_fan_type_call &&
-           (index == 21 || index == 25 || index == 28))
-               return 0;
-       if (index >= 0 && index <= 1 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
-               return 0;
-       if (index >= 2 && index <= 3 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
-               return 0;
-       if (index >= 4 && index <= 5 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
-               return 0;
-       if (index >= 6 && index <= 7 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
-               return 0;
-       if (index >= 8 && index <= 9 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP5))
-               return 0;
-       if (index >= 10 && index <= 11 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP6))
-               return 0;
-       if (index >= 12 && index <= 13 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP7))
-               return 0;
-       if (index >= 14 && index <= 15 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP8))
-               return 0;
-       if (index >= 16 && index <= 17 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP9))
-               return 0;
-       if (index >= 18 && index <= 19 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP10))
-               return 0;
+static const struct hwmon_chip_info dell_smm_chip_info = {
+       .ops = &dell_smm_ops,
+       .info = dell_smm_info,
+};
 
-       if (index >= 20 && index <= 23 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
-               return 0;
-       if (index >= 24 && index <= 26 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
-               return 0;
-       if (index >= 27 && index <= 29 &&
-           !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3))
-               return 0;
+static int __init dell_smm_init_hwmon(struct device *dev)
+{
+       struct dell_smm_data *data = dev_get_drvdata(dev);
+       struct device *dell_smm_hwmon_dev;
+       int i, err;
 
-       if (index == 23 && !auto_fan)
-               return 0;
+       for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
+               data->temp_type[i] = i8k_get_temp_type(i);
+               if (data->temp_type[i] < 0)
+                       continue;
 
-       return attr->mode;
-}
+               if (data->temp_type[i] >= ARRAY_SIZE(temp_labels))
+                       data->temp_type[i] = ARRAY_SIZE(temp_labels) - 1;
+       }
 
-static const struct attribute_group i8k_group = {
-       .attrs = i8k_attrs,
-       .is_visible = i8k_is_visible,
-};
-__ATTRIBUTE_GROUPS(i8k);
+       for (i = 0; i < DELL_SMM_NO_FANS; i++) {
+               data->fan_type[i] = INT_MIN;
+               err = i8k_get_fan_status(data, i);
+               if (err < 0)
+                       err = i8k_get_fan_type(data, i);
+               if (err >= 0)
+                       data->fan[i] = true;
+       }
 
-static int __init i8k_init_hwmon(void)
-{
-       int err;
+       dell_smm_hwmon_dev = devm_hwmon_device_register_with_info(dev, "dell_smm", data,
+                                                                 &dell_smm_chip_info, NULL);
 
-       i8k_hwmon_flags = 0;
-
-       /* CPU temperature attributes, if temperature type is OK */
-       err = i8k_get_temp_type(0);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
-       /* check for additional temperature sensors */
-       err = i8k_get_temp_type(1);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
-       err = i8k_get_temp_type(2);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
-       err = i8k_get_temp_type(3);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
-       err = i8k_get_temp_type(4);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP5;
-       err = i8k_get_temp_type(5);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP6;
-       err = i8k_get_temp_type(6);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP7;
-       err = i8k_get_temp_type(7);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP8;
-       err = i8k_get_temp_type(8);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP9;
-       err = i8k_get_temp_type(9);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP10;
-
-       /* First fan attributes, if fan status or type is OK */
-       err = i8k_get_fan_status(0);
-       if (err < 0)
-               err = i8k_get_fan_type(0);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
-
-       /* Second fan attributes, if fan status or type is OK */
-       err = i8k_get_fan_status(1);
-       if (err < 0)
-               err = i8k_get_fan_type(1);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
-
-       /* Third fan attributes, if fan status or type is OK */
-       err = i8k_get_fan_status(2);
-       if (err < 0)
-               err = i8k_get_fan_type(2);
-       if (err >= 0)
-               i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3;
-
-       i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm",
-                                                         NULL, i8k_groups);
-       if (IS_ERR(i8k_hwmon_dev)) {
-               err = PTR_ERR(i8k_hwmon_dev);
-               i8k_hwmon_dev = NULL;
-               pr_err("hwmon registration failed (%d)\n", err);
-               return err;
-       }
-       return 0;
+       return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev);
 }
 
 struct i8k_config_data {
@@ -979,7 +948,7 @@ enum i8k_configs {
        DELL_XPS,
 };
 
-static const struct i8k_config_data i8k_config_data[] = {
+static const struct i8k_config_data i8k_config_data[] __initconst = {
        [DELL_LATITUDE_D520] = {
                .fan_mult = 1,
                .fan_max = I8K_FAN_TURBO,
@@ -1137,7 +1106,7 @@ static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst
  * support for affected blacklisted Dell machines stay disabled.
  * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751
  */
-static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = {
+static const struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initconst = {
        {
                .ident = "Dell Inspiron 7720",
                .matches = {
@@ -1178,22 +1147,14 @@ enum i8k_fan_controls {
        I8K_FAN_34A3_35A3,
 };
 
-static const struct i8k_fan_control_data i8k_fan_control_data[] = {
+static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = {
        [I8K_FAN_34A3_35A3] = {
                .manual_fan = 0x34a3,
                .auto_fan = 0x35a3,
        },
 };
 
-static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = {
-       {
-               .ident = "Dell Precision 5530",
-               .matches = {
-                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-                       DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"),
-               },
-               .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
-       },
+static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
        {
                .ident = "Dell Latitude 5480",
                .matches = {
@@ -1218,57 +1179,56 @@ static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = {
                },
                .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
        },
+       {
+               .ident = "Dell Precision 5530",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"),
+               },
+               .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
+       },
+       {
+               .ident = "Dell Precision 7510",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+                       DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 7510"),
+               },
+               .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
+       },
        { }
 };
 
-/*
- * Probe for the presence of a supported laptop.
- */
-static int __init i8k_probe(void)
+static int __init dell_smm_probe(struct platform_device *pdev)
 {
+       struct dell_smm_data *data;
        const struct dmi_system_id *id, *fan_control;
        int fan, ret;
 
-       /*
-        * Get DMI information
-        */
-       if (!dmi_check_system(i8k_dmi_table)) {
-               if (!ignore_dmi && !force)
-                       return -ENODEV;
+       data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
 
-               pr_info("not running on a supported Dell system.\n");
-               pr_info("vendor=%s, model=%s, version=%s\n",
-                       i8k_get_dmi_data(DMI_SYS_VENDOR),
-                       i8k_get_dmi_data(DMI_PRODUCT_NAME),
-                       i8k_get_dmi_data(DMI_BIOS_VERSION));
-       }
+       mutex_init(&data->i8k_mutex);
+       data->i8k_fan_mult = I8K_FAN_MULT;
+       data->i8k_fan_max = I8K_FAN_HIGH;
+       platform_set_drvdata(pdev, data);
 
        if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
-               pr_warn("broken Dell BIOS detected, disallow fan support\n");
+               dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan support\n");
                if (!force)
-                       disallow_fan_support = true;
+                       data->disallow_fan_support = true;
        }
 
        if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
-               pr_warn("broken Dell BIOS detected, disallow fan type call\n");
+               dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan type call\n");
                if (!force)
-                       disallow_fan_type_call = true;
+                       data->disallow_fan_type_call = true;
        }
 
-       strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
-               sizeof(bios_version));
-       strlcpy(bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
-               sizeof(bios_machineid));
-
-       /*
-        * Get SMM Dell signature
-        */
-       if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
-           i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
-               pr_err("unable to get SMM Dell signature\n");
-               if (!force)
-                       return -ENODEV;
-       }
+       strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
+               sizeof(data->bios_version));
+       strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
+               sizeof(data->bios_machineid));
 
        /*
         * Set fan multiplier and maximal fan speed from dmi config
@@ -1277,22 +1237,24 @@ static int __init i8k_probe(void)
        id = dmi_first_match(i8k_dmi_table);
        if (id && id->driver_data) {
                const struct i8k_config_data *conf = id->driver_data;
+
                if (!fan_mult && conf->fan_mult)
                        fan_mult = conf->fan_mult;
+
                if (!fan_max && conf->fan_max)
                        fan_max = conf->fan_max;
        }
 
-       i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */
-       i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
+       data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;   /* Must not be 0 */
+       data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
 
        fan_control = dmi_first_match(i8k_whitelist_fan_control);
        if (fan_control && fan_control->driver_data) {
-               const struct i8k_fan_control_data *data = fan_control->driver_data;
+               const struct i8k_fan_control_data *control = fan_control->driver_data;
 
-               manual_fan = data->manual_fan;
-               auto_fan = data->auto_fan;
-               pr_info("enabling support for setting automatic/manual fan control\n");
+               data->manual_fan = control->manual_fan;
+               data->auto_fan = control->auto_fan;
+               dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n");
        }
 
        if (!fan_mult) {
@@ -1300,42 +1262,76 @@ static int __init i8k_probe(void)
                 * Autodetect fan multiplier based on nominal rpm
                 * If fan reports rpm value too high then set multiplier to 1
                 */
-               for (fan = 0; fan < 2; ++fan) {
-                       ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
+               for (fan = 0; fan < DELL_SMM_NO_FANS; ++fan) {
+                       ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max);
                        if (ret < 0)
                                continue;
+
                        if (ret > I8K_FAN_MAX_RPM)
-                               i8k_fan_mult = 1;
+                               data->i8k_fan_mult = 1;
                        break;
                }
        } else {
                /* Fan multiplier was specified in module param or in dmi */
-               i8k_fan_mult = fan_mult;
+               data->i8k_fan_mult = fan_mult;
        }
 
+       ret = dell_smm_init_hwmon(&pdev->dev);
+       if (ret)
+               return ret;
+
+       i8k_init_procfs(&pdev->dev);
+
        return 0;
 }
 
+static struct platform_driver dell_smm_driver = {
+       .driver         = {
+               .name   = KBUILD_MODNAME,
+       },
+};
+
+static struct platform_device *dell_smm_device;
+
+/*
+ * Probe for the presence of a supported laptop.
+ */
 static int __init i8k_init(void)
 {
-       int err;
+       /*
+        * Get DMI information
+        */
+       if (!dmi_check_system(i8k_dmi_table)) {
+               if (!ignore_dmi && !force)
+                       return -ENODEV;
 
-       /* Are we running on an supported laptop? */
-       if (i8k_probe())
-               return -ENODEV;
+               pr_info("not running on a supported Dell system.\n");
+               pr_info("vendor=%s, model=%s, version=%s\n",
+                       i8k_get_dmi_data(DMI_SYS_VENDOR),
+                       i8k_get_dmi_data(DMI_PRODUCT_NAME),
+                       i8k_get_dmi_data(DMI_BIOS_VERSION));
+       }
 
-       err = i8k_init_hwmon();
-       if (err)
-               return err;
+       /*
+        * Get SMM Dell signature
+        */
+       if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
+           i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
+               pr_err("unable to get SMM Dell signature\n");
+               if (!force)
+                       return -ENODEV;
+       }
 
-       i8k_init_procfs();
-       return 0;
+       dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL,
+                                                0);
+
+       return PTR_ERR_OR_ZERO(dell_smm_device);
 }
 
 static void __exit i8k_exit(void)
 {
-       hwmon_device_unregister(i8k_hwmon_dev);
-       i8k_exit_procfs();
+       platform_device_unregister(dell_smm_device);
+       platform_driver_unregister(&dell_smm_driver);
 }
 
 module_init(i8k_init);
index 29f5fed..521534d 100644 (file)
@@ -166,7 +166,7 @@ static int read_registers(struct fam15h_power_data *data)
 
        memset(data->cu_on, 0, sizeof(int) * MAX_CUS);
 
-       get_online_cpus();
+       cpus_read_lock();
 
        /*
         * Choose the first online core of each compute unit, and then
@@ -190,7 +190,7 @@ static int read_registers(struct fam15h_power_data *data)
 
        on_each_cpu_mask(mask, do_read_registers_on_cu, data, true);
 
-       put_online_cpus();
+       cpus_read_unlock();
        free_cpumask_var(mask);
 
        return 0;
index bd7ed2e..7a08e4c 100644 (file)
@@ -228,6 +228,118 @@ static const struct m10bmc_hwmon_board_data d5005bmc_hwmon_bdata = {
        .hinfo = d5005bmc_hinfo,
 };
 
+static const struct m10bmc_sdata n5010bmc_temp_tbl[] = {
+       { 0x100, 0x0, 0x104, 0x0, 0x0, 1000, "Board Local Temperature" },
+       { 0x108, 0x0, 0x10c, 0x0, 0x0, 1000, "FPGA 1 Temperature" },
+       { 0x110, 0x0, 0x114, 0x0, 0x0, 1000, "FPGA 2 Temperature" },
+       { 0x118, 0x0, 0x0, 0x0, 0x0, 1000, "Card Top Temperature" },
+       { 0x11c, 0x0, 0x0, 0x0, 0x0, 1000, "Card Bottom Temperature" },
+       { 0x128, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 1.2V Temperature" },
+       { 0x134, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 5V Temperature" },
+       { 0x140, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 0.9V Temperature" },
+       { 0x14c, 0x0, 0x0, 0x0, 0x0, 1000, "FPGA 0.85V Temperature" },
+       { 0x158, 0x0, 0x0, 0x0, 0x0, 1000, "AUX 12V Temperature" },
+       { 0x164, 0x0, 0x0, 0x0, 0x0, 1000, "Backplane 12V Temperature" },
+       { 0x1a8, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-1 Temperature" },
+       { 0x1ac, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-2 Temperature" },
+       { 0x1b0, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-3 Temperature" },
+       { 0x1b4, 0x0, 0x0, 0x0, 0x0, 1000, "QSFP28-4 Temperature" },
+       { 0x1b8, 0x0, 0x0, 0x0, 0x0, 1000, "CVL1 Internal Temperature" },
+       { 0x1bc, 0x0, 0x0, 0x0, 0x0, 1000, "CVL2 Internal Temperature" },
+};
+
+static const struct m10bmc_sdata n5010bmc_in_tbl[] = {
+       { 0x120, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.2V Voltage" },
+       { 0x12c, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 5V Voltage" },
+       { 0x138, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.9V Voltage" },
+       { 0x144, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.85V Voltage" },
+       { 0x150, 0x0, 0x0, 0x0, 0x0, 1, "AUX 12V Voltage" },
+       { 0x15c, 0x0, 0x0, 0x0, 0x0, 1, "Backplane 12V Voltage" },
+       { 0x16c, 0x0, 0x0, 0x0, 0x0, 1, "DDR4 1.2V Voltage" },
+       { 0x17c, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.8V Voltage" },
+       { 0x184, 0x0, 0x0, 0x0, 0x0, 1, "QDR 1.3V Voltage" },
+       { 0x18c, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 0.8V Voltage" },
+       { 0x194, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 1.05V Voltage" },
+       { 0x19c, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 1.05V Voltage" },
+       { 0x1a4, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 0.8V Voltage" },
+};
+
+static const struct m10bmc_sdata n5010bmc_curr_tbl[] = {
+       { 0x124, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.2V Current" },
+       { 0x130, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 5V Current" },
+       { 0x13c, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.9V Current" },
+       { 0x148, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 0.85V Current" },
+       { 0x154, 0x0, 0x0, 0x0, 0x0, 1, "AUX 12V Current" },
+       { 0x160, 0x0, 0x0, 0x0, 0x0, 1, "Backplane 12V Current" },
+       { 0x168, 0x0, 0x0, 0x0, 0x0, 1, "DDR4 1.2V Current" },
+       { 0x178, 0x0, 0x0, 0x0, 0x0, 1, "FPGA 1.8V Current" },
+       { 0x180, 0x0, 0x0, 0x0, 0x0, 1, "QDR 1.3V Current" },
+       { 0x188, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 0.8V Current" },
+       { 0x190, 0x0, 0x0, 0x0, 0x0, 1, "CVL1 1.05V Current" },
+       { 0x198, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 1.05V Current" },
+       { 0x1a0, 0x0, 0x0, 0x0, 0x0, 1, "CVL2 0.8V Current" },
+};
+
+static const struct hwmon_channel_info *n5010bmc_hinfo[] = {
+       HWMON_CHANNEL_INFO(temp,
+                          HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL,
+                          HWMON_T_INPUT | HWMON_T_LABEL),
+       HWMON_CHANNEL_INFO(in,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL,
+                          HWMON_I_INPUT | HWMON_I_LABEL),
+       HWMON_CHANNEL_INFO(curr,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL,
+                          HWMON_C_INPUT | HWMON_C_LABEL),
+       NULL
+};
+
+static const struct m10bmc_hwmon_board_data n5010bmc_hwmon_bdata = {
+       .tables = {
+               [hwmon_temp] = n5010bmc_temp_tbl,
+               [hwmon_in] = n5010bmc_in_tbl,
+               [hwmon_curr] = n5010bmc_curr_tbl,
+       },
+
+       .hinfo = n5010bmc_hinfo,
+};
+
 static umode_t
 m10bmc_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
                        u32 attr, int channel)
@@ -438,6 +550,10 @@ static const struct platform_device_id intel_m10bmc_hwmon_ids[] = {
                .name = "d5005bmc-hwmon",
                .driver_data = (unsigned long)&d5005bmc_hwmon_bdata,
        },
+       {
+               .name = "n5010bmc-hwmon",
+               .driver_data = (unsigned long)&n5010bmc_hwmon_bdata,
+       },
        { }
 };
 
index 5ff3669..38bc35a 100644 (file)
@@ -65,10 +65,11 @@ static DEFINE_MUTEX(nb_smu_ind_mutex);
 #define F15H_M60H_HARDWARE_TEMP_CTRL_OFFSET    0xd8200c64
 #define F15H_M60H_REPORTED_TEMP_CTRL_OFFSET    0xd8200ca4
 
-/* Common for Zen CPU families (Family 17h and 18h) */
-#define ZEN_REPORTED_TEMP_CTRL_OFFSET          0x00059800
+/* Common for Zen CPU families (Family 17h and 18h and 19h) */
+#define ZEN_REPORTED_TEMP_CTRL_BASE            0x00059800
 
-#define ZEN_CCD_TEMP(x)                                (0x00059954 + ((x) * 4))
+#define ZEN_CCD_TEMP(offset, x)                        (ZEN_REPORTED_TEMP_CTRL_BASE + \
+                                                (offset) + ((x) * 4))
 #define ZEN_CCD_TEMP_VALID                     BIT(11)
 #define ZEN_CCD_TEMP_MASK                      GENMASK(10, 0)
 
@@ -103,6 +104,7 @@ struct k10temp_data {
        u32 temp_adjust_mask;
        u32 show_temp;
        bool is_zen;
+       u32 ccd_offset;
 };
 
 #define TCTL_BIT       0
@@ -163,7 +165,7 @@ static void read_tempreg_nb_f15(struct pci_dev *pdev, u32 *regval)
 static void read_tempreg_nb_zen(struct pci_dev *pdev, u32 *regval)
 {
        amd_smn_read(amd_pci_dev_to_node_id(pdev),
-                    ZEN_REPORTED_TEMP_CTRL_OFFSET, regval);
+                    ZEN_REPORTED_TEMP_CTRL_BASE, regval);
 }
 
 static long get_raw_temp(struct k10temp_data *data)
@@ -226,7 +228,8 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel,
                        break;
                case 2 ... 9:           /* Tccd{1-8} */
                        amd_smn_read(amd_pci_dev_to_node_id(data->pdev),
-                                    ZEN_CCD_TEMP(channel - 2), &regval);
+                                    ZEN_CCD_TEMP(data->ccd_offset, channel - 2),
+                                                 &regval);
                        *val = (regval & ZEN_CCD_TEMP_MASK) * 125 - 49000;
                        break;
                default:
@@ -387,7 +390,7 @@ static void k10temp_get_ccd_support(struct pci_dev *pdev,
 
        for (i = 0; i < limit; i++) {
                amd_smn_read(amd_pci_dev_to_node_id(pdev),
-                            ZEN_CCD_TEMP(i), &regval);
+                            ZEN_CCD_TEMP(data->ccd_offset, i), &regval);
                if (regval & ZEN_CCD_TEMP_VALID)
                        data->show_temp |= BIT(TCCD_BIT(i));
        }
@@ -426,7 +429,6 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
        } else if (boot_cpu_data.x86 == 0x17 || boot_cpu_data.x86 == 0x18) {
                data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK;
                data->read_tempreg = read_tempreg_nb_zen;
-               data->show_temp |= BIT(TDIE_BIT);       /* show Tdie */
                data->is_zen = true;
 
                switch (boot_cpu_data.x86_model) {
@@ -434,22 +436,31 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
                case 0x8:       /* Zen+ */
                case 0x11:      /* Zen APU */
                case 0x18:      /* Zen+ APU */
+                       data->ccd_offset = 0x154;
                        k10temp_get_ccd_support(pdev, data, 4);
                        break;
                case 0x31:      /* Zen2 Threadripper */
+               case 0x60:      /* Renoir */
+               case 0x68:      /* Lucienne */
                case 0x71:      /* Zen2 */
+                       data->ccd_offset = 0x154;
                        k10temp_get_ccd_support(pdev, data, 8);
                        break;
                }
        } else if (boot_cpu_data.x86 == 0x19) {
                data->temp_adjust_mask = ZEN_CUR_TEMP_RANGE_SEL_MASK;
                data->read_tempreg = read_tempreg_nb_zen;
-               data->show_temp |= BIT(TDIE_BIT);
                data->is_zen = true;
 
                switch (boot_cpu_data.x86_model) {
                case 0x0 ... 0x1:       /* Zen3 SP3/TR */
                case 0x21:              /* Zen3 Ryzen Desktop */
+               case 0x50 ... 0x5f:     /* Green Sardine */
+                       data->ccd_offset = 0x154;
+                       k10temp_get_ccd_support(pdev, data, 8);
+                       break;
+               case 0x40 ... 0x4f:     /* Yellow Carp */
+                       data->ccd_offset = 0x300;
                        k10temp_get_ccd_support(pdev, data, 8);
                        break;
                }
@@ -463,6 +474,7 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 
                if (boot_cpu_data.x86 == entry->model &&
                    strstr(boot_cpu_data.x86_model_id, entry->id)) {
+                       data->show_temp |= BIT(TDIE_BIT);       /* show Tdie */
                        data->temp_offset = entry->offset;
                        break;
                }
@@ -491,6 +503,8 @@ static const struct pci_device_id k10temp_id_table[] = {
        { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F3) },
        { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) },
        { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) },
+       { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) },
+       { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) },
        { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) },
        {}
 };
index 18fd6f1..cf26c44 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/err.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/fixp-arith.h>
 
 #include <linux/platform_data/ntc_thermistor.h>
 
@@ -549,15 +550,16 @@ static int get_temp_mc(struct ntc_data *data, unsigned int ohm)
        int temp;
 
        lookup_comp(data, ohm, &low, &high);
-       if (low == high) {
-               /* Unable to use linear approximation */
-               temp = data->comp[low].temp_c * 1000;
-       } else {
-               temp = data->comp[low].temp_c * 1000 +
-                       ((data->comp[high].temp_c - data->comp[low].temp_c) *
-                        1000 * ((int)ohm - (int)data->comp[low].ohm)) /
-                       ((int)data->comp[high].ohm - (int)data->comp[low].ohm);
-       }
+       /*
+        * First multiplying the table temperatures with 1000 to get to
+        * millicentigrades (which is what we want) and then interpolating
+        * will give the best precision.
+        */
+       temp = fixp_linear_interpolate(data->comp[low].ohm,
+                                      data->comp[low].temp_c * 1000,
+                                      data->comp[high].ohm,
+                                      data->comp[high].temp_c * 1000,
+                                      ohm);
        return temp;
 }
 
index 2be69fe..f2d4e37 100644 (file)
 #include <linux/pmbus.h>
 #include "pmbus.h"
 
-#define BPARS600_MFR_VIN_MIN   0xa0
-#define BPARS600_MFR_VIN_MAX   0xa1
-#define BPARS600_MFR_IIN_MAX   0xa2
-#define BPARS600_MFR_PIN_MAX   0xa3
-#define BPARS600_MFR_VOUT_MIN  0xa4
-#define BPARS600_MFR_VOUT_MAX  0xa5
-#define BPARS600_MFR_IOUT_MAX  0xa6
-#define BPARS600_MFR_POUT_MAX  0xa7
+enum chips { bpa_rs600, bpd_rs600 };
 
 static int bpa_rs600_read_byte_data(struct i2c_client *client, int page, int reg)
 {
@@ -72,6 +65,26 @@ static int bpa_rs600_read_vin(struct i2c_client *client)
        return ret;
 }
 
+/*
+ * Firmware V5.70 incorrectly reports 1640W for MFR_PIN_MAX.
+ * Deal with this by returning a sensible value.
+ */
+static int bpa_rs600_read_pin_max(struct i2c_client *client)
+{
+       int ret;
+
+       ret = pmbus_read_word_data(client, 0, 0xff, PMBUS_MFR_PIN_MAX);
+       if (ret < 0)
+               return ret;
+
+       /* Detect invalid 1640W (linear encoding) */
+       if (ret == 0x0b34)
+               /* Report 700W (linear encoding) */
+               return 0x095e;
+
+       return ret;
+}
+
 static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int phase, int reg)
 {
        int ret;
@@ -81,29 +94,13 @@ static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int pha
 
        switch (reg) {
        case PMBUS_VIN_UV_WARN_LIMIT:
-               ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VIN_MIN);
-               break;
        case PMBUS_VIN_OV_WARN_LIMIT:
-               ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VIN_MAX);
-               break;
        case PMBUS_VOUT_UV_WARN_LIMIT:
-               ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VOUT_MIN);
-               break;
        case PMBUS_VOUT_OV_WARN_LIMIT:
-               ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_VOUT_MAX);
-               break;
        case PMBUS_IIN_OC_WARN_LIMIT:
-               ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_IIN_MAX);
-               break;
        case PMBUS_IOUT_OC_WARN_LIMIT:
-               ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_IOUT_MAX);
-               break;
        case PMBUS_PIN_OP_WARN_LIMIT:
-               ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_PIN_MAX);
-               break;
        case PMBUS_POUT_OP_WARN_LIMIT:
-               ret = pmbus_read_word_data(client, 0, 0xff, BPARS600_MFR_POUT_MAX);
-               break;
        case PMBUS_VIN_UV_FAULT_LIMIT:
        case PMBUS_VIN_OV_FAULT_LIMIT:
        case PMBUS_VOUT_UV_FAULT_LIMIT:
@@ -114,6 +111,9 @@ static int bpa_rs600_read_word_data(struct i2c_client *client, int page, int pha
        case PMBUS_READ_VIN:
                ret = bpa_rs600_read_vin(client);
                break;
+       case PMBUS_MFR_PIN_MAX:
+               ret = bpa_rs600_read_pin_max(client);
+               break;
        default:
                if (reg >= PMBUS_VIRT_BASE)
                        ret = -ENXIO;
@@ -146,11 +146,19 @@ static struct pmbus_driver_info bpa_rs600_info = {
        .read_word_data = bpa_rs600_read_word_data,
 };
 
+static const struct i2c_device_id bpa_rs600_id[] = {
+       { "bpa-rs600", bpa_rs600 },
+       { "bpd-rs600", bpd_rs600 },
+       {},
+};
+MODULE_DEVICE_TABLE(i2c, bpa_rs600_id);
+
 static int bpa_rs600_probe(struct i2c_client *client)
 {
        struct device *dev = &client->dev;
        u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
        int ret;
+       const struct i2c_device_id *mid;
 
        if (!i2c_check_functionality(client->adapter,
                                     I2C_FUNC_SMBUS_READ_BYTE_DATA
@@ -164,7 +172,11 @@ static int bpa_rs600_probe(struct i2c_client *client)
                return ret;
        }
 
-       if (strncmp(buf, "BPA-RS600", 8)) {
+       for (mid = bpa_rs600_id; mid->name[0]; mid++) {
+               if (!strncasecmp(buf, mid->name, strlen(mid->name)))
+                       break;
+       }
+       if (!mid->name[0]) {
                buf[ret] = '\0';
                dev_err(dev, "Unsupported Manufacturer Model '%s'\n", buf);
                return -ENODEV;
@@ -173,12 +185,6 @@ static int bpa_rs600_probe(struct i2c_client *client)
        return pmbus_do_probe(client, &bpa_rs600_info);
 }
 
-static const struct i2c_device_id bpa_rs600_id[] = {
-       { "bpars600", 0 },
-       {},
-};
-MODULE_DEVICE_TABLE(i2c, bpa_rs600_id);
-
 static const struct of_device_id __maybe_unused bpa_rs600_of_match[] = {
        { .compatible = "blutek,bpa-rs600" },
        {},
index 5668d83..df712ce 100644 (file)
@@ -50,9 +50,9 @@
 #define CFFPS_MFR_VAUX_FAULT                   BIT(6)
 #define CFFPS_MFR_CURRENT_SHARE_WARNING                BIT(7)
 
-#define CFFPS_LED_BLINK                                BIT(0)
-#define CFFPS_LED_ON                           BIT(1)
-#define CFFPS_LED_OFF                          BIT(2)
+#define CFFPS_LED_BLINK                                (BIT(0) | BIT(6))
+#define CFFPS_LED_ON                           (BIT(1) | BIT(6))
+#define CFFPS_LED_OFF                          (BIT(2) | BIT(6))
 #define CFFPS_BLINK_RATE_MS                    250
 
 enum {
diff --git a/drivers/hwmon/sbrmi.c b/drivers/hwmon/sbrmi.c
new file mode 100644 (file)
index 0000000..7bf0c3f
--- /dev/null
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * sbrmi.c - hwmon driver for a SB-RMI mailbox
+ *           compliant AMD SoC device.
+ *
+ * Copyright (C) 2020-2021 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+
+/* Do not allow setting negative power limit */
+#define SBRMI_PWR_MIN  0
+/* Mask for Status Register bit[1] */
+#define SW_ALERT_MASK  0x2
+
+/* Software Interrupt for triggering */
+#define START_CMD      0x80
+#define TRIGGER_MAILBOX        0x01
+
+/*
+ * SB-RMI supports soft mailbox service request to MP1 (power management
+ * firmware) through SBRMI inbound/outbound message registers.
+ * SB-RMI message IDs
+ */
+enum sbrmi_msg_id {
+       SBRMI_READ_PKG_PWR_CONSUMPTION = 0x1,
+       SBRMI_WRITE_PKG_PWR_LIMIT,
+       SBRMI_READ_PKG_PWR_LIMIT,
+       SBRMI_READ_PKG_MAX_PWR_LIMIT,
+};
+
+/* SB-RMI registers */
+enum sbrmi_reg {
+       SBRMI_CTRL              = 0x01,
+       SBRMI_STATUS,
+       SBRMI_OUTBNDMSG0        = 0x30,
+       SBRMI_OUTBNDMSG1,
+       SBRMI_OUTBNDMSG2,
+       SBRMI_OUTBNDMSG3,
+       SBRMI_OUTBNDMSG4,
+       SBRMI_OUTBNDMSG5,
+       SBRMI_OUTBNDMSG6,
+       SBRMI_OUTBNDMSG7,
+       SBRMI_INBNDMSG0,
+       SBRMI_INBNDMSG1,
+       SBRMI_INBNDMSG2,
+       SBRMI_INBNDMSG3,
+       SBRMI_INBNDMSG4,
+       SBRMI_INBNDMSG5,
+       SBRMI_INBNDMSG6,
+       SBRMI_INBNDMSG7,
+       SBRMI_SW_INTERRUPT,
+};
+
+/* Each client has this additional data */
+struct sbrmi_data {
+       struct i2c_client *client;
+       struct mutex lock;
+       u32 pwr_limit_max;
+};
+
+struct sbrmi_mailbox_msg {
+       u8 cmd;
+       bool read;
+       u32 data_in;
+       u32 data_out;
+};
+
+static int sbrmi_enable_alert(struct i2c_client *client)
+{
+       int ctrl;
+
+       /*
+        * Enable the SB-RMI Software alert status
+        * by writing 0 to bit 4 of Control register(0x1)
+        */
+       ctrl = i2c_smbus_read_byte_data(client, SBRMI_CTRL);
+       if (ctrl < 0)
+               return ctrl;
+
+       if (ctrl & 0x10) {
+               ctrl &= ~0x10;
+               return i2c_smbus_write_byte_data(client,
+                                                SBRMI_CTRL, ctrl);
+       }
+
+       return 0;
+}
+
+static int rmi_mailbox_xfer(struct sbrmi_data *data,
+                           struct sbrmi_mailbox_msg *msg)
+{
+       int i, ret, retry = 10;
+       int sw_status;
+       u8 byte;
+
+       mutex_lock(&data->lock);
+
+       /* Indicate firmware a command is to be serviced */
+       ret = i2c_smbus_write_byte_data(data->client,
+                                       SBRMI_INBNDMSG7, START_CMD);
+       if (ret < 0)
+               goto exit_unlock;
+
+       /* Write the command to SBRMI::InBndMsg_inst0 */
+       ret = i2c_smbus_write_byte_data(data->client,
+                                       SBRMI_INBNDMSG0, msg->cmd);
+       if (ret < 0)
+               goto exit_unlock;
+
+       /*
+        * For both read and write the initiator (BMC) writes
+        * Command Data In[31:0] to SBRMI::InBndMsg_inst[4:1]
+        * SBRMI_x3C(MSB):SBRMI_x39(LSB)
+        */
+       for (i = 0; i < 4; i++) {
+               byte = (msg->data_in >> i * 8) & 0xff;
+               ret = i2c_smbus_write_byte_data(data->client,
+                                               SBRMI_INBNDMSG1 + i, byte);
+               if (ret < 0)
+                       goto exit_unlock;
+       }
+
+       /*
+        * Write 0x01 to SBRMI::SoftwareInterrupt to notify firmware to
+        * perform the requested read or write command
+        */
+       ret = i2c_smbus_write_byte_data(data->client,
+                                       SBRMI_SW_INTERRUPT, TRIGGER_MAILBOX);
+       if (ret < 0)
+               goto exit_unlock;
+
+       /*
+        * Firmware will write SBRMI::Status[SwAlertSts]=1 to generate
+        * an ALERT (if enabled) to initiator (BMC) to indicate completion
+        * of the requested command
+        */
+       do {
+               sw_status = i2c_smbus_read_byte_data(data->client,
+                                                    SBRMI_STATUS);
+               if (sw_status < 0) {
+                       ret = sw_status;
+                       goto exit_unlock;
+               }
+               if (sw_status & SW_ALERT_MASK)
+                       break;
+               usleep_range(50, 100);
+       } while (retry--);
+
+       if (retry < 0) {
+               dev_err(&data->client->dev,
+                       "Firmware fail to indicate command completion\n");
+               ret = -EIO;
+               goto exit_unlock;
+       }
+
+       /*
+        * For a read operation, the initiator (BMC) reads the firmware
+        * response Command Data Out[31:0] from SBRMI::OutBndMsg_inst[4:1]
+        * {SBRMI_x34(MSB):SBRMI_x31(LSB)}.
+        */
+       if (msg->read) {
+               for (i = 0; i < 4; i++) {
+                       ret = i2c_smbus_read_byte_data(data->client,
+                                                      SBRMI_OUTBNDMSG1 + i);
+                       if (ret < 0)
+                               goto exit_unlock;
+                       msg->data_out |= ret << i * 8;
+               }
+       }
+
+       /*
+        * BMC must write 1'b1 to SBRMI::Status[SwAlertSts] to clear the
+        * ALERT to initiator
+        */
+       ret = i2c_smbus_write_byte_data(data->client, SBRMI_STATUS,
+                                       sw_status | SW_ALERT_MASK);
+
+exit_unlock:
+       mutex_unlock(&data->lock);
+       return ret;
+}
+
+static int sbrmi_read(struct device *dev, enum hwmon_sensor_types type,
+                     u32 attr, int channel, long *val)
+{
+       struct sbrmi_data *data = dev_get_drvdata(dev);
+       struct sbrmi_mailbox_msg msg = { 0 };
+       int ret;
+
+       if (type != hwmon_power)
+               return -EINVAL;
+
+       msg.read = true;
+       switch (attr) {
+       case hwmon_power_input:
+               msg.cmd = SBRMI_READ_PKG_PWR_CONSUMPTION;
+               ret = rmi_mailbox_xfer(data, &msg);
+               break;
+       case hwmon_power_cap:
+               msg.cmd = SBRMI_READ_PKG_PWR_LIMIT;
+               ret = rmi_mailbox_xfer(data, &msg);
+               break;
+       case hwmon_power_cap_max:
+               msg.data_out = data->pwr_limit_max;
+               ret = 0;
+               break;
+       default:
+               return -EINVAL;
+       }
+       if (ret < 0)
+               return ret;
+       /* hwmon power attributes are in microWatt */
+       *val = (long)msg.data_out * 1000;
+       return ret;
+}
+
+static int sbrmi_write(struct device *dev, enum hwmon_sensor_types type,
+                      u32 attr, int channel, long val)
+{
+       struct sbrmi_data *data = dev_get_drvdata(dev);
+       struct sbrmi_mailbox_msg msg = { 0 };
+
+       if (type != hwmon_power && attr != hwmon_power_cap)
+               return -EINVAL;
+       /*
+        * hwmon power attributes are in microWatt
+        * mailbox read/write is in mWatt
+        */
+       val /= 1000;
+
+       val = clamp_val(val, SBRMI_PWR_MIN, data->pwr_limit_max);
+
+       msg.cmd = SBRMI_WRITE_PKG_PWR_LIMIT;
+       msg.data_in = val;
+       msg.read = false;
+
+       return rmi_mailbox_xfer(data, &msg);
+}
+
+static umode_t sbrmi_is_visible(const void *data,
+                               enum hwmon_sensor_types type,
+                               u32 attr, int channel)
+{
+       switch (type) {
+       case hwmon_power:
+               switch (attr) {
+               case hwmon_power_input:
+               case hwmon_power_cap_max:
+                       return 0444;
+               case hwmon_power_cap:
+                       return 0644;
+               }
+               break;
+       default:
+               break;
+       }
+       return 0;
+}
+
+static const struct hwmon_channel_info *sbrmi_info[] = {
+       HWMON_CHANNEL_INFO(power,
+                          HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CAP_MAX),
+       NULL
+};
+
+static const struct hwmon_ops sbrmi_hwmon_ops = {
+       .is_visible = sbrmi_is_visible,
+       .read = sbrmi_read,
+       .write = sbrmi_write,
+};
+
+static const struct hwmon_chip_info sbrmi_chip_info = {
+       .ops = &sbrmi_hwmon_ops,
+       .info = sbrmi_info,
+};
+
+static int sbrmi_get_max_pwr_limit(struct sbrmi_data *data)
+{
+       struct sbrmi_mailbox_msg msg = { 0 };
+       int ret;
+
+       msg.cmd = SBRMI_READ_PKG_MAX_PWR_LIMIT;
+       msg.read = true;
+       ret = rmi_mailbox_xfer(data, &msg);
+       if (ret < 0)
+               return ret;
+       data->pwr_limit_max = msg.data_out;
+
+       return ret;
+}
+
+static int sbrmi_probe(struct i2c_client *client,
+                      const struct i2c_device_id *id)
+{
+       struct device *dev = &client->dev;
+       struct device *hwmon_dev;
+       struct sbrmi_data *data;
+       int ret;
+
+       data = devm_kzalloc(dev, sizeof(struct sbrmi_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->client = client;
+       mutex_init(&data->lock);
+
+       /* Enable alert for SB-RMI sequence */
+       ret = sbrmi_enable_alert(client);
+       if (ret < 0)
+               return ret;
+
+       /* Cache maximum power limit */
+       ret = sbrmi_get_max_pwr_limit(data);
+       if (ret < 0)
+               return ret;
+
+       hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+                                                        &sbrmi_chip_info, NULL);
+
+       return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id sbrmi_id[] = {
+       {"sbrmi", 0},
+       {}
+};
+MODULE_DEVICE_TABLE(i2c, sbrmi_id);
+
+static const struct of_device_id __maybe_unused sbrmi_of_match[] = {
+       {
+               .compatible = "amd,sbrmi",
+       },
+       { },
+};
+MODULE_DEVICE_TABLE(of, sbrmi_of_match);
+
+static struct i2c_driver sbrmi_driver = {
+       .class = I2C_CLASS_HWMON,
+       .driver = {
+               .name = "sbrmi",
+               .of_match_table = of_match_ptr(sbrmi_of_match),
+       },
+       .probe = sbrmi_probe,
+       .id_table = sbrmi_id,
+};
+
+module_i2c_driver(sbrmi_driver);
+
+MODULE_AUTHOR("Akshay Gupta <akshay.gupta@amd.com>");
+MODULE_DESCRIPTION("Hwmon driver for AMD SB-RMI emulated sensor");
+MODULE_LICENSE("GPL");
index 8618aaf..705a596 100644 (file)
@@ -372,12 +372,10 @@ struct w83627ehf_data {
        u8 temp3_val_only:1;
        u8 have_vid:1;
 
-#ifdef CONFIG_PM
        /* Remember extra register values over suspend/resume */
        u8 vbat;
        u8 fandiv1;
        u8 fandiv2;
-#endif
 };
 
 struct w83627ehf_sio_data {
@@ -1083,7 +1081,7 @@ cpu0_vid_show(struct device *dev, struct device_attribute *attr, char *buf)
        struct w83627ehf_data *data = dev_get_drvdata(dev);
        return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm));
 }
-DEVICE_ATTR_RO(cpu0_vid);
+static DEVICE_ATTR_RO(cpu0_vid);
 
 
 /* Case open detection */
@@ -1694,7 +1692,7 @@ static const struct hwmon_chip_info w83627ehf_chip_info = {
        .info = w83627ehf_info,
 };
 
-static int w83627ehf_probe(struct platform_device *pdev)
+static int __init w83627ehf_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
        struct w83627ehf_sio_data *sio_data = dev_get_platdata(dev);
@@ -1705,20 +1703,12 @@ static int w83627ehf_probe(struct platform_device *pdev)
        struct device *hwmon_dev;
 
        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
-       if (!request_region(res->start, IOREGION_LENGTH, DRVNAME)) {
-               err = -EBUSY;
-               dev_err(dev, "Failed to request region 0x%lx-0x%lx\n",
-                       (unsigned long)res->start,
-                       (unsigned long)res->start + IOREGION_LENGTH - 1);
-               goto exit;
-       }
+       if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME))
+               return -EBUSY;
 
-       data = devm_kzalloc(&pdev->dev, sizeof(struct w83627ehf_data),
-                           GFP_KERNEL);
-       if (!data) {
-               err = -ENOMEM;
-               goto exit_release;
-       }
+       data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
 
        data->addr = res->start;
        mutex_init(&data->lock);
@@ -1882,7 +1872,7 @@ static int w83627ehf_probe(struct platform_device *pdev)
 
        err = superio_enter(sio_data->sioreg);
        if (err)
-               goto exit_release;
+               return err;
 
        /* Read VID value */
        if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) {
@@ -1951,30 +1941,10 @@ static int w83627ehf_probe(struct platform_device *pdev)
                                                         data,
                                                         &w83627ehf_chip_info,
                                                         w83627ehf_groups);
-       if (IS_ERR(hwmon_dev)) {
-               err = PTR_ERR(hwmon_dev);
-               goto exit_release;
-       }
-
-       return 0;
-
-exit_release:
-       release_region(res->start, IOREGION_LENGTH);
-exit:
-       return err;
+       return PTR_ERR_OR_ZERO(hwmon_dev);
 }
 
-static int w83627ehf_remove(struct platform_device *pdev)
-{
-       struct w83627ehf_data *data = platform_get_drvdata(pdev);
-
-       release_region(data->addr, IOREGION_LENGTH);
-
-       return 0;
-}
-
-#ifdef CONFIG_PM
-static int w83627ehf_suspend(struct device *dev)
+static int __maybe_unused w83627ehf_suspend(struct device *dev)
 {
        struct w83627ehf_data *data = w83627ehf_update_device(dev);
 
@@ -1985,7 +1955,7 @@ static int w83627ehf_suspend(struct device *dev)
        return 0;
 }
 
-static int w83627ehf_resume(struct device *dev)
+static int __maybe_unused w83627ehf_resume(struct device *dev)
 {
        struct w83627ehf_data *data = dev_get_drvdata(dev);
        int i;
@@ -2040,25 +2010,13 @@ static int w83627ehf_resume(struct device *dev)
        return 0;
 }
 
-static const struct dev_pm_ops w83627ehf_dev_pm_ops = {
-       .suspend = w83627ehf_suspend,
-       .resume = w83627ehf_resume,
-       .freeze = w83627ehf_suspend,
-       .restore = w83627ehf_resume,
-};
-
-#define W83627EHF_DEV_PM_OPS   (&w83627ehf_dev_pm_ops)
-#else
-#define W83627EHF_DEV_PM_OPS   NULL
-#endif /* CONFIG_PM */
+static SIMPLE_DEV_PM_OPS(w83627ehf_dev_pm_ops, w83627ehf_suspend, w83627ehf_resume);
 
 static struct platform_driver w83627ehf_driver = {
        .driver = {
                .name   = DRVNAME,
-               .pm     = W83627EHF_DEV_PM_OPS,
+               .pm     = &w83627ehf_dev_pm_ops,
        },
-       .probe          = w83627ehf_probe,
-       .remove         = w83627ehf_remove,
 };
 
 /* w83627ehf_find() looks for a '627 in the Super-I/O config space */
@@ -2150,8 +2108,7 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr,
 /*
  * when Super-I/O functions move to a separate file, the Super-I/O
  * bus will manage the lifetime of the device and this module will only keep
- * track of the w83627ehf driver. But since we platform_device_alloc(), we
- * must keep track of the device
+ * track of the w83627ehf driver.
  */
 static struct platform_device *pdev;
 
@@ -2159,7 +2116,10 @@ static int __init sensors_w83627ehf_init(void)
 {
        int err;
        unsigned short address;
-       struct resource res;
+       struct resource res = {
+               .name   = DRVNAME,
+               .flags  = IORESOURCE_IO,
+       };
        struct w83627ehf_sio_data sio_data;
 
        /*
@@ -2173,55 +2133,17 @@ static int __init sensors_w83627ehf_init(void)
            w83627ehf_find(0x4e, &address, &sio_data))
                return -ENODEV;
 
-       err = platform_driver_register(&w83627ehf_driver);
-       if (err)
-               goto exit;
-
-       pdev = platform_device_alloc(DRVNAME, address);
-       if (!pdev) {
-               err = -ENOMEM;
-               pr_err("Device allocation failed\n");
-               goto exit_unregister;
-       }
-
-       err = platform_device_add_data(pdev, &sio_data,
-                                      sizeof(struct w83627ehf_sio_data));
-       if (err) {
-               pr_err("Platform data allocation failed\n");
-               goto exit_device_put;
-       }
-
-       memset(&res, 0, sizeof(res));
-       res.name = DRVNAME;
        res.start = address + IOREGION_OFFSET;
        res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1;
-       res.flags = IORESOURCE_IO;
 
        err = acpi_check_resource_conflict(&res);
        if (err)
-               goto exit_device_put;
-
-       err = platform_device_add_resources(pdev, &res, 1);
-       if (err) {
-               pr_err("Device resource addition failed (%d)\n", err);
-               goto exit_device_put;
-       }
-
-       /* platform_device_add calls probe() */
-       err = platform_device_add(pdev);
-       if (err) {
-               pr_err("Device addition failed (%d)\n", err);
-               goto exit_device_put;
-       }
+               return err;
 
-       return 0;
+       pdev = platform_create_bundle(&w83627ehf_driver, w83627ehf_probe, &res, 1, &sio_data,
+                                     sizeof(struct w83627ehf_sio_data));
 
-exit_device_put:
-       platform_device_put(pdev);
-exit_unregister:
-       platform_driver_unregister(&w83627ehf_driver);
-exit:
-       return err;
+       return PTR_ERR_OR_ZERO(pdev);
 }
 
 static void __exit sensors_w83627ehf_exit(void)
index e84aa56..ce8e2c1 100644 (file)
@@ -1571,10 +1571,21 @@ static const struct i2c_device_id w83781d_ids[] = {
 };
 MODULE_DEVICE_TABLE(i2c, w83781d_ids);
 
+static const struct of_device_id w83781d_of_match[] = {
+       { .compatible = "winbond,w83781d" },
+       { .compatible = "winbond,w83781g" },
+       { .compatible = "winbond,w83782d" },
+       { .compatible = "winbond,w83783s" },
+       { .compatible = "asus,as99127f" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, w83781d_of_match);
+
 static struct i2c_driver w83781d_driver = {
        .class          = I2C_CLASS_HWMON,
        .driver = {
                .name = "w83781d",
+               .of_match_table = w83781d_of_match,
        },
        .probe_new      = w83781d_probe,
        .remove         = w83781d_remove,
index 60e2101..06eccef 100644 (file)
 #define PCI_DEVICE_ID_AMD_17H_M60H_DF_F3 0x144b
 #define PCI_DEVICE_ID_AMD_17H_M70H_DF_F3 0x1443
 #define PCI_DEVICE_ID_AMD_19H_DF_F3    0x1653
+#define PCI_DEVICE_ID_AMD_19H_M40H_DF_F3 0x167c
 #define PCI_DEVICE_ID_AMD_19H_M50H_DF_F3 0x166d
 #define PCI_DEVICE_ID_AMD_CNB17H_F3    0x1703
 #define PCI_DEVICE_ID_AMD_LANCE                0x2000