Merge tag 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 4 Jul 2013 02:56:35 +0000 (19:56 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 4 Jul 2013 02:56:35 +0000 (19:56 -0700)
Pull hwmon updates from Guenter Roeck:
 - new driver to support GMT G762/G763 pwm fan controllers
 - add support for DS1631, DS1721, and DS1731 to ds1621 driver
 - remove detect function from ds1621 driver as unreliable
 - bug fixes in nct6775, iio_hwmon, and adm1021 drivers
 - remove redundant platform_set_drvdata in various drivers
 - add device tree support to ina2xx driver

* tag 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging:
  hwmon: (ds1621) Fix temperature rounding operations
  hwmon: (nct6775) Drop unsupported fan alarm attributes for NCT6775
  hwmon: (nct6775) Fix temperature alarm attributes
  Add support for GMT G762/G763 PWM fan controllers
  hwmon: (ina2xx) Add device tree support to pass the shunt resistor
  hwmon: (ds1621) Update documentation
  hwmon: (ds1621) Add DS1731 chip support to ds1621 driver
  hwmon: (iio_hwmon) add alias table
  hwmon: (adm1021) Do not create min sysfs attributes for LM84
  hwmon: (ds1621) Remove detect function
  hwmon: (ds1621) Add ds1631 chip support to ds1621 driver and documentation
  hwmon: (ds1621) Add ds1721 update interval sysfs attribute
  hwmon: (ds1621) Add ds1721 chip support
  hwmon: (w83627ehf) Remove redundant platform_set_drvdata()
  hwmon: (ntc_thermistor) Remove redundant platform_set_drvdata()
  hwmon: (i5k_amb) Remove redundant platform_set_drvdata()
  hwmon: (coretemp) Remove redundant platform_set_drvdata()
  hwmon: (abituguru3) Remove redundant platform_set_drvdata()

19 files changed:
Documentation/devicetree/bindings/hwmon/g762.txt [new file with mode: 0644]
Documentation/devicetree/bindings/i2c/ina2xx.txt [new file with mode: 0644]
Documentation/hwmon/ds1621
Documentation/hwmon/g762 [new file with mode: 0644]
Documentation/hwmon/ina2xx
drivers/hwmon/Kconfig
drivers/hwmon/Makefile
drivers/hwmon/abituguru3.c
drivers/hwmon/adm1021.c
drivers/hwmon/coretemp.c
drivers/hwmon/ds1621.c
drivers/hwmon/g762.c [new file with mode: 0644]
drivers/hwmon/i5k_amb.c
drivers/hwmon/iio_hwmon.c
drivers/hwmon/ina2xx.c
drivers/hwmon/nct6775.c
drivers/hwmon/ntc_thermistor.c
drivers/hwmon/w83627ehf.c
include/linux/platform_data/g762.h [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/hwmon/g762.txt b/Documentation/devicetree/bindings/hwmon/g762.txt
new file mode 100644 (file)
index 0000000..25cc6d8
--- /dev/null
@@ -0,0 +1,47 @@
+GMT G762/G763 PWM Fan controller
+
+Required node properties:
+
+ - "compatible": must be either "gmt,g762" or "gmt,g763"
+ - "reg": I2C bus address of the device
+ - "clocks": a fixed clock providing input clock frequency
+            on CLK pin of the chip.
+
+Optional properties:
+
+ - "fan_startv": fan startup voltage. Accepted values are 0, 1, 2 and 3.
+              The higher the more.
+
+ - "pwm_polarity": pwm polarity. Accepted values are 0 (positive duty)
+              and 1 (negative duty).
+
+ - "fan_gear_mode": fan gear mode. Supported values are 0, 1 and 2.
+
+If an optional property is not set in .dts file, then current value is kept
+unmodified (e.g. u-boot installed value).
+
+Additional information on operational parameters for the device is available
+in Documentation/hwmon/g762. A detailed datasheet for the device is available
+at http://natisbad.org/NAS/refs/GMT_EDS-762_763-080710-0.2.pdf.
+
+Example g762 node:
+
+   clocks {
+       #address-cells = <1>;
+       #size-cells = <0>;
+
+       g762_clk: fixedclk {
+                compatible = "fixed-clock";
+                #clock-cells = <0>;
+                clock-frequency = <8192>;
+       }
+   }
+
+   g762: g762@3e {
+       compatible = "gmt,g762";
+       reg = <0x3e>;
+       clocks = <&g762_clk>
+       fan_gear_mode = <0>; /* chip default */
+       fan_startv = <1>;    /* chip default */
+       pwm_polarity = <0>;  /* chip default */
+   };
diff --git a/Documentation/devicetree/bindings/i2c/ina2xx.txt b/Documentation/devicetree/bindings/i2c/ina2xx.txt
new file mode 100644 (file)
index 0000000..a2ad85d
--- /dev/null
@@ -0,0 +1,22 @@
+ina2xx properties
+
+Required properties:
+- compatible: Must be one of the following:
+       - "ti,ina219" for ina219
+       - "ti,ina220" for ina220
+       - "ti,ina226" for ina226
+       - "ti,ina230" for ina230
+- reg: I2C address
+
+Optional properties:
+
+- shunt-resistor
+       Shunt resistor value in micro-Ohm
+
+Example:
+
+ina220@44 {
+       compatible = "ti,ina220";
+       reg = <0x44>;
+       shunt-resistor = <1000>;
+};
index 5e97f33..896cdc9 100644 (file)
@@ -2,16 +2,30 @@ Kernel driver ds1621
 ====================
 
 Supported chips:
-  * Dallas Semiconductor DS1621
+  * Dallas Semiconductor / Maxim Integrated DS1621
     Prefix: 'ds1621'
-    Addresses scanned: I2C 0x48 - 0x4f
-    Datasheet: Publicly available at the Dallas Semiconductor website
-               http://www.dalsemi.com/
+    Addresses scanned: none
+    Datasheet: Publicly available from www.maximintegrated.com
+
   * Dallas Semiconductor DS1625
-    Prefix: 'ds1621'
-    Addresses scanned: I2C 0x48 - 0x4f
-    Datasheet: Publicly available at the Dallas Semiconductor website
-               http://www.dalsemi.com/
+    Prefix: 'ds1625'
+    Addresses scanned: none
+    Datasheet: Publicly available from www.datasheetarchive.com
+
+  * Maxim Integrated DS1631
+    Prefix: 'ds1631'
+    Addresses scanned: none
+    Datasheet: Publicly available from www.maximintegrated.com
+
+  * Maxim Integrated DS1721
+    Prefix: 'ds1721'
+    Addresses scanned: none
+    Datasheet: Publicly available from www.maximintegrated.com
+
+  * Maxim Integrated DS1731
+    Prefix: 'ds1731'
+    Addresses scanned: none
+    Datasheet: Publicly available from www.maximintegrated.com
 
 Authors:
         Christian W. Zuckschwerdt <zany@triq.net>
@@ -59,5 +73,115 @@ any of the limits have ever been met or exceeded since last power-up or
 reset. Be aware: When testing, it showed that the status of Tout can change
 with neither of the alarms set.
 
-Temperature conversion of the DS1621 takes up to 1000ms; internal access to
-non-volatile registers may last for 10ms or below.
+Since there is no version or vendor identification register, there is
+no unique identification for these devices. Therefore, explicit device
+instantiation is required for correct device identification and functionality
+(one device per address in this address range: 0x48..0x4f).
+
+The DS1625 is pin compatible and functionally equivalent with the DS1621,
+but the DS1621 is meant to replace it. The DS1631, DS1721, and DS1731 are
+also pin compatible with the DS1621 and provide multi-resolution support.
+
+Additionally, the DS1721 data sheet says the temperature flags (THF and TLF)
+are used internally, however, these flags do get set and cleared as the actual
+temperature crosses the min or max settings (which by default are set to 75
+and 80 degrees respectively).
+
+Temperature Conversion:
+-----------------------
+DS1621 - 750ms (older devices may take up to 1000ms)
+DS1625 - 500ms
+DS1631 - 93ms..750ms for 9..12 bits resolution, respectively.
+DS1721 - 93ms..750ms for 9..12 bits resolution, respectively.
+DS1731 - 93ms..750ms for 9..12 bits resolution, respectively.
+
+Note:
+On the DS1621, internal access to non-volatile registers may last for 10ms
+or less (unverified on the other devices).
+
+Temperature Accuracy:
+---------------------
+DS1621: +/- 0.5 degree Celsius (from 0 to +70 degrees)
+DS1625: +/- 0.5 degree Celsius (from 0 to +70 degrees)
+DS1631: +/- 0.5 degree Celsius (from 0 to +70 degrees)
+DS1721: +/- 1.0 degree Celsius (from -10 to +85 degrees)
+DS1731: +/- 1.0 degree Celsius (from -10 to +85 degrees)
+
+Note:
+Please refer to the device datasheets for accuracy at other temperatures.
+
+Temperature Resolution:
+-----------------------
+As mentioned above, the DS1631, DS1721, and DS1731 provide multi-resolution
+support, which is achieved via the R0 and R1 config register bits, where:
+
+R0..R1
+------
+ 0  0 => 9 bits, 0.5 degrees Celcius
+ 1  0 => 10 bits, 0.25 degrees Celcius
+ 0  1 => 11 bits, 0.125 degrees Celcius
+ 1  1 => 12 bits, 0.0625 degrees Celcius
+
+Note:
+At initial device power-on, the default resolution is set to 12-bits.
+
+The resolution mode for the DS1631, DS1721, or DS1731 can be changed from
+userspace, via the device 'update_interval' sysfs attribute. This attribute
+will normalize the range of input values to the device maximum resolution
+values defined in the datasheet as follows:
+
+Resolution    Conversion Time    Input Range
+ (C/LSB)       (msec)             (msec)
+------------------------------------------------
+0.5             93.75              0....94
+0.25            187.5              95...187
+0.125           375                188..375
+0.0625          750                376..infinity
+------------------------------------------------
+
+The following examples show how the 'update_interval' attribute can be
+used to change the conversion time:
+
+$ cat update_interval
+750
+$ cat temp1_input
+22062
+$
+$ echo 300 > update_interval
+$ cat update_interval
+375
+$ cat temp1_input
+22125
+$
+$ echo 150 > update_interval
+$ cat update_interval
+188
+$ cat temp1_input
+22250
+$
+$ echo 1 > update_interval
+$ cat update_interval
+94
+$ cat temp1_input
+22000
+$
+$ echo 1000 > update_interval
+$ cat update_interval
+750
+$ cat temp1_input
+22062
+$
+
+As shown, the ds1621 driver automatically adjusts the 'update_interval'
+user input, via a step function. Reading back the 'update_interval' value
+after a write operation provides the conversion time used by the device.
+
+Mathematically, the resolution can be derived from the conversion time
+via the following function:
+
+   g(x) = 0.5 * [minimum_conversion_time/x]
+
+where:
+ -> 'x' = the output from 'update_interval'
+ -> 'g(x)' = the resolution in degrees C per LSB.
+ -> 93.75ms = minimum conversion time
diff --git a/Documentation/hwmon/g762 b/Documentation/hwmon/g762
new file mode 100644 (file)
index 0000000..923db9c
--- /dev/null
@@ -0,0 +1,65 @@
+Kernel driver g762
+==================
+
+The GMT G762 Fan Speed PWM Controller is connected directly to a fan
+and performs closed-loop or open-loop control of the fan speed. Two
+modes - PWM or DC - are supported by the device.
+
+For additional information, a detailed datasheet is available at
+http://natisbad.org/NAS/ref/GMT_EDS-762_763-080710-0.2.pdf. sysfs
+bindings are described in Documentation/hwmon/sysfs-interface.
+
+The following entries are available to the user in a subdirectory of
+/sys/bus/i2c/drivers/g762/ to control the operation of the device.
+This can be done manually using the following entries but is usually
+done via a userland daemon like fancontrol.
+
+Note that those entries do not provide ways to setup the specific
+hardware characteristics of the system (reference clock, pulses per
+fan revolution, ...); Those can be modified via devicetree bindings
+documented in Documentation/devicetree/bindings/hwmon/g762.txt or
+using a specific platform_data structure in board initialization
+file (see include/linux/platform_data/g762.h).
+
+  fan1_target: set desired fan speed. This only makes sense in closed-loop
+            fan speed control (i.e. when pwm1_enable is set to 2).
+
+  fan1_input: provide current fan rotation value in RPM as reported by
+            the fan to the device.
+
+  fan1_div: fan clock divisor. Supported value are 1, 2, 4 and 8.
+
+  fan1_pulses: number of pulses per fan revolution. Supported values
+            are 2 and 4.
+
+  fan1_fault: reports fan failure, i.e. no transition on fan gear pin for
+            about 0.7s (if the fan is not voluntarily set off).
+
+  fan1_alarm: in closed-loop control mode, if fan RPM value is 25% out
+            of the programmed value for over 6 seconds 'fan1_alarm' is
+            set to 1.
+
+  pwm1_enable: set current fan speed control mode i.e. 1 for manual fan
+            speed control (open-loop) via pwm1 described below, 2 for
+            automatic fan speed control (closed-loop) via fan1_target
+            above.
+
+  pwm1_mode: set or get fan driving mode: 1 for PWM mode, 0 for DC mode.
+
+  pwm1: get or set PWM fan control value in open-loop mode. This is an
+            integer value between 0 and 255. 0 stops the fan, 255 makes
+            it run at full speed.
+
+Both in PWM mode ('pwm1_mode' set to 1) and DC mode ('pwm1_mode' set to 0),
+when current fan speed control mode is open-loop ('pwm1_enable' set to 1),
+the fan speed is programmed by setting a value between 0 and 255 via 'pwm1'
+entry (0 stops the fan, 255 makes it run at full speed). In closed-loop mode
+('pwm1_enable' set to 2), the expected rotation speed in RPM can be passed to
+the chip via 'fan1_target'. In closed-loop mode, the target speed is compared
+with current speed (available via 'fan1_input') by the device and a feedback
+is performed to match that target value. The fan speed value is computed
+based on the parameters associated with the physical characteristics of the
+system: a reference clock source frequency, a number of pulses per fan
+revolution, etc.
+
+Note that the driver will update its values at most once per second.
index 03444f9..4223c2d 100644 (file)
@@ -44,4 +44,6 @@ The INA226 monitors both a shunt voltage drop and bus supply voltage.
 The INA230 is a high or low side current shunt and power monitor with an I2C
 interface. The INA230 monitors both a shunt voltage drop and bus supply voltage.
 
-The shunt value in micro-ohms can be set via platform data.
+The shunt value in micro-ohms can be set via platform data or device tree.
+Please refer to the Documentation/devicetree/bindings/i2c/ina2xx.txt for bindings
+if the device tree is used.
index 0428e8a..e989f7f 100644 (file)
@@ -348,11 +348,16 @@ config SENSORS_DS620
          will be called ds620.
 
 config SENSORS_DS1621
-       tristate "Dallas Semiconductor DS1621 and DS1625"
+       tristate "Dallas Semiconductor DS1621 and compatibles"
        depends on I2C
        help
-         If you say yes here you get support for Dallas Semiconductor
-         DS1621 and DS1625 sensor chips.
+         If you say yes here you get support for Dallas Semiconductor/Maxim
+         Integrated DS1621 sensor chips and compatible models including:
+
+         - Dallas Semiconductor DS1625
+         - Maxim Integrated DS1631
+         - Maxim Integrated DS1721
+         - Maxim Integrated DS1731
 
          This driver can also be built as a module.  If so, the module
          will be called ds1621.
@@ -456,6 +461,16 @@ config SENSORS_G760A
          This driver can also be built as a module.  If so, the module
          will be called g760a.
 
+config SENSORS_G762
+       tristate "GMT G762 and G763"
+       depends on I2C
+       help
+         If you say yes here you get support for Global Mixed-mode
+         Technology Inc G762 and G763 fan speed PWM controller chips.
+
+         This driver can also be built as a module.  If so, the module
+         will be called g762.
+
 config SENSORS_GL518SM
        tristate "Genesys Logic GL518SM"
        depends on I2C
index d17d3e6..4f0fb52 100644 (file)
@@ -60,6 +60,7 @@ obj-$(CONFIG_SENSORS_F75375S) += f75375s.o
 obj-$(CONFIG_SENSORS_FAM15H_POWER) += fam15h_power.o
 obj-$(CONFIG_SENSORS_FSCHMD)   += fschmd.o
 obj-$(CONFIG_SENSORS_G760A)    += g760a.o
+obj-$(CONFIG_SENSORS_G762)     += g762.o
 obj-$(CONFIG_SENSORS_GL518SM)  += gl518sm.o
 obj-$(CONFIG_SENSORS_GL520SM)  += gl520sm.o
 obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o
index 1d2da31..0cac8c0 100644 (file)
@@ -1079,7 +1079,6 @@ static int abituguru3_remove(struct platform_device *pdev)
        int i;
        struct abituguru3_data *data = platform_get_drvdata(pdev);
 
-       platform_set_drvdata(pdev, NULL);
        hwmon_device_unregister(data->hwmon_dev);
        for (i = 0; data->sysfs_attr[i].dev_attr.attr.name; i++)
                device_remove_file(&pdev->dev, &data->sysfs_attr[i].dev_attr);
index f920619..29dd9f7 100644 (file)
@@ -284,15 +284,11 @@ static DEVICE_ATTR(low_power, S_IWUSR | S_IRUGO, show_low_power, set_low_power);
 
 static struct attribute *adm1021_attributes[] = {
        &sensor_dev_attr_temp1_max.dev_attr.attr,
-       &sensor_dev_attr_temp1_min.dev_attr.attr,
        &sensor_dev_attr_temp1_input.dev_attr.attr,
        &sensor_dev_attr_temp2_max.dev_attr.attr,
-       &sensor_dev_attr_temp2_min.dev_attr.attr,
        &sensor_dev_attr_temp2_input.dev_attr.attr,
        &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
        &sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
-       &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
        &sensor_dev_attr_temp2_fault.dev_attr.attr,
        &dev_attr_alarms.attr,
        &dev_attr_low_power.attr,
@@ -303,6 +299,18 @@ static const struct attribute_group adm1021_group = {
        .attrs = adm1021_attributes,
 };
 
+static struct attribute *adm1021_min_attributes[] = {
+       &sensor_dev_attr_temp1_min.dev_attr.attr,
+       &sensor_dev_attr_temp2_min.dev_attr.attr,
+       &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
+       &sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
+       NULL
+};
+
+static const struct attribute_group adm1021_min_group = {
+       .attrs = adm1021_min_attributes,
+};
+
 /* Return 0 if detection is successful, -ENODEV otherwise */
 static int adm1021_detect(struct i2c_client *client,
                          struct i2c_board_info *info)
@@ -425,6 +433,12 @@ static int adm1021_probe(struct i2c_client *client,
        if (err)
                return err;
 
+       if (data->type != lm84) {
+               err = sysfs_create_group(&client->dev.kobj, &adm1021_min_group);
+               if (err)
+                       goto error;
+       }
+
        data->hwmon_dev = hwmon_device_register(&client->dev);
        if (IS_ERR(data->hwmon_dev)) {
                err = PTR_ERR(data->hwmon_dev);
@@ -434,6 +448,7 @@ static int adm1021_probe(struct i2c_client *client,
        return 0;
 
 error:
+       sysfs_remove_group(&client->dev.kobj, &adm1021_min_group);
        sysfs_remove_group(&client->dev.kobj, &adm1021_group);
        return err;
 }
@@ -452,6 +467,7 @@ static int adm1021_remove(struct i2c_client *client)
        struct adm1021_data *data = i2c_get_clientdata(client);
 
        hwmon_device_unregister(data->hwmon_dev);
+       sysfs_remove_group(&client->dev.kobj, &adm1021_min_group);
        sysfs_remove_group(&client->dev.kobj, &adm1021_group);
 
        return 0;
@@ -477,9 +493,11 @@ static struct adm1021_data *adm1021_update_device(struct device *dev)
                        data->temp_max[i] = 1000 *
                                (s8) i2c_smbus_read_byte_data(
                                        client, ADM1021_REG_TOS_R(i));
-                       data->temp_min[i] = 1000 *
-                               (s8) i2c_smbus_read_byte_data(
-                                       client, ADM1021_REG_THYST_R(i));
+                       if (data->type != lm84) {
+                               data->temp_min[i] = 1000 *
+                                 (s8) i2c_smbus_read_byte_data(client,
+                                                       ADM1021_REG_THYST_R(i));
+                       }
                }
                data->alarms = i2c_smbus_read_byte_data(client,
                                                ADM1021_REG_STATUS) & 0x7c;
index 658ce3a..ade35cf 100644 (file)
@@ -578,7 +578,6 @@ static int coretemp_probe(struct platform_device *pdev)
 
 exit_name:
        device_remove_file(&pdev->dev, &pdata->name_attr);
-       platform_set_drvdata(pdev, NULL);
 exit_free:
        kfree(pdata);
        return err;
@@ -595,7 +594,6 @@ static int coretemp_remove(struct platform_device *pdev)
 
        device_remove_file(&pdev->dev, &pdata->name_attr);
        hwmon_device_unregister(pdata->hwmon_dev);
-       platform_set_drvdata(pdev, NULL);
        kfree(pdata);
        return 0;
 }
index 1c56873..a26ba7a 100644 (file)
@@ -6,6 +6,19 @@
  * Ported to Linux 2.6 by Aurelien Jarno <aurelien@aurel32.net> with
  * the help of Jean Delvare <khali@linux-fr.org>
  *
+ * The DS1621 device is a digital temperature/thermometer with 9-bit
+ * resolution, a thermal alarm output (Tout), and user-defined minimum
+ * and maximum temperature thresholds (TH and TL).
+ *
+ * The DS1625, DS1631, DS1721, and DS1731 are pin compatible with the DS1621
+ * and similar in operation, with slight variations as noted in the device
+ * datasheets (please refer to www.maximintegrated.com for specific
+ * device information).
+ *
+ * Since the DS1621 was the first chipset supported by this driver,
+ * most comments will refer to this chipset, but are actually general
+ * and concern all supported chipsets, unless mentioned otherwise.
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
 #include <linux/err.h>
 #include <linux/mutex.h>
 #include <linux/sysfs.h>
-#include "lm75.h"
+#include <linux/kernel.h>
 
-/* Addresses to scan */
-static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c,
-                                       0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
+/* Supported devices */
+enum chips { ds1621, ds1625, ds1631, ds1721, ds1731 };
 
 /* Insmod parameters */
 static int polarity = -1;
 module_param(polarity, int, 0);
 MODULE_PARM_DESC(polarity, "Output's polarity: 0 = active high, 1 = active low");
 
-/* Many DS1621 constants specified below */
-/* Config register used for detection         */
-/*  7    6    5    4    3    2    1    0      */
-/* |Done|THF |TLF |NVB | X  | X  |POL |1SHOT| */
+/*
+ * The Configuration/Status register
+ *
+ * - DS1621:
+ *   7    6    5    4    3    2    1    0
+ * |Done|THF |TLF |NVB | X  | X  |POL |1SHOT|
+ *
+ * - DS1625:
+ *   7    6    5    4    3    2    1    0
+ * |Done|THF |TLF |NVB | 1  | 0  |POL |1SHOT|
+ *
+ * - DS1631, DS1731:
+ *   7    6    5    4    3    2    1    0
+ * |Done|THF |TLF |NVB | R1 | R0 |POL |1SHOT|
+ *
+ * - DS1721:
+ *   7    6    5    4    3    2    1    0
+ * |Done| X  | X  | U  | R1 | R0 |POL |1SHOT|
+ *
+ * Where:
+ * - 'X' is Reserved
+ * - 'U' is Undefined
+ */
 #define DS1621_REG_CONFIG_NVB          0x10
+#define DS1621_REG_CONFIG_RESOL                0x0C
 #define DS1621_REG_CONFIG_POLARITY     0x02
 #define DS1621_REG_CONFIG_1SHOT                0x01
 #define DS1621_REG_CONFIG_DONE         0x80
 
-/* The DS1621 registers */
+#define DS1621_REG_CONFIG_RESOL_SHIFT  2
+
+/* ds1721 conversion rates: {C/LSB, time(ms), resolution bit setting} */
+static const unsigned short ds1721_convrates[] = {
+       94,     /*  9-bits (0.5,  93.75, RES[0..1] = 0 */
+       188,    /* 10-bits (0.25, 187.5, RES[0..1] = 1 */
+       375,    /* 11-bits (0.125,  375, RES[0..1] = 2 */
+       750,    /* 12-bits (0.0625, 750, RES[0..1] = 3 */
+};
+
+#define DS1621_CONVERSION_MAX  750
+#define DS1625_CONVERSION_MAX  500
+
+#define DS1621_TEMP_MAX        125000
+#define DS1621_TEMP_MIN        (-55000)
+
+/* The DS1621 temperature registers */
 static const u8 DS1621_REG_TEMP[3] = {
        0xAA,           /* input, word, RO */
        0xA2,           /* min, word, RW */
@@ -59,6 +107,7 @@ static const u8 DS1621_REG_TEMP[3] = {
 };
 #define DS1621_REG_CONF                        0xAC /* byte, RW */
 #define DS1621_COM_START               0xEE /* no data */
+#define DS1721_COM_START               0x51 /* no data */
 #define DS1621_COM_STOP                        0x22 /* no data */
 
 /* The DS1621 configuration register */
@@ -75,14 +124,37 @@ struct ds1621_data {
        struct mutex update_lock;
        char valid;                     /* !=0 if following fields are valid */
        unsigned long last_updated;     /* In jiffies */
+       enum chips kind;                /* device type */
 
        u16 temp[3];                    /* Register values, word */
        u8 conf;                        /* Register encoding, combined */
+       u8 zbits;                       /* Resolution encoded as number of
+                                        * zero bits */
+       u16 update_interval;            /* Conversion rate in milliseconds */
 };
 
+static inline int DS1621_TEMP_FROM_REG(u16 reg)
+{
+       return DIV_ROUND_CLOSEST(((s16)reg / 16) * 625, 10);
+}
+
+/*
+ * TEMP: 0.001C/bit (-55C to +125C)
+ * REG:
+ *  - 1621, 1625: 0.5C/bit, 7 zero-bits
+ *  - 1631, 1721, 1731: 0.0625C/bit, 4 zero-bits
+ */
+static inline u16 DS1621_TEMP_TO_REG(long temp, u8 zbits)
+{
+       temp = clamp_val(temp, DS1621_TEMP_MIN, DS1621_TEMP_MAX);
+       temp = DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits;
+       return temp;
+}
+
 static void ds1621_init_client(struct i2c_client *client)
 {
-       u8 conf, new_conf;
+       u8 conf, new_conf, sreg, resol;
+       struct ds1621_data *data = i2c_get_clientdata(client);
 
        new_conf = conf = i2c_smbus_read_byte_data(client, DS1621_REG_CONF);
        /* switch to continuous conversion mode */
@@ -97,8 +169,30 @@ static void ds1621_init_client(struct i2c_client *client)
        if (conf != new_conf)
                i2c_smbus_write_byte_data(client, DS1621_REG_CONF, new_conf);
 
+       switch (data->kind) {
+       case ds1625:
+               data->update_interval = DS1625_CONVERSION_MAX;
+               data->zbits = 7;
+               sreg = DS1621_COM_START;
+               break;
+       case ds1631:
+       case ds1721:
+       case ds1731:
+               resol = (new_conf & DS1621_REG_CONFIG_RESOL) >>
+                        DS1621_REG_CONFIG_RESOL_SHIFT;
+               data->update_interval = ds1721_convrates[resol];
+               data->zbits = 7 - resol;
+               sreg = DS1721_COM_START;
+               break;
+       default:
+               data->update_interval = DS1621_CONVERSION_MAX;
+               data->zbits = 7;
+               sreg = DS1621_COM_START;
+               break;
+       }
+
        /* start conversion */
-       i2c_smbus_write_byte(client, DS1621_COM_START);
+       i2c_smbus_write_byte(client, sreg);
 }
 
 static struct ds1621_data *ds1621_update_client(struct device *dev)
@@ -109,8 +203,8 @@ static struct ds1621_data *ds1621_update_client(struct device *dev)
 
        mutex_lock(&data->update_lock);
 
-       if (time_after(jiffies, data->last_updated + HZ + HZ / 2)
-           || !data->valid) {
+       if (time_after(jiffies, data->last_updated + data->update_interval) ||
+           !data->valid) {
                int i;
 
                dev_dbg(&client->dev, "Starting ds1621 update\n");
@@ -146,7 +240,7 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *da,
        struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
        struct ds1621_data *data = ds1621_update_client(dev);
        return sprintf(buf, "%d\n",
-                      LM75_TEMP_FROM_REG(data->temp[attr->index]));
+                      DS1621_TEMP_FROM_REG(data->temp[attr->index]));
 }
 
 static ssize_t set_temp(struct device *dev, struct device_attribute *da,
@@ -163,7 +257,7 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da,
                return err;
 
        mutex_lock(&data->update_lock);
-       data->temp[attr->index] = LM75_TEMP_TO_REG(val);
+       data->temp[attr->index] = DS1621_TEMP_TO_REG(val, data->zbits);
        i2c_smbus_write_word_swapped(client, DS1621_REG_TEMP[attr->index],
                                     data->temp[attr->index]);
        mutex_unlock(&data->update_lock);
@@ -185,7 +279,47 @@ static ssize_t show_alarm(struct device *dev, struct device_attribute *da,
        return sprintf(buf, "%d\n", !!(data->conf & attr->index));
 }
 
+static ssize_t show_convrate(struct device *dev, struct device_attribute *da,
+                         char *buf)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct ds1621_data *data = i2c_get_clientdata(client);
+       return scnprintf(buf, PAGE_SIZE, "%hu\n", data->update_interval);
+}
+
+static ssize_t set_convrate(struct device *dev, struct device_attribute *da,
+                           const char *buf, size_t count)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct ds1621_data *data = i2c_get_clientdata(client);
+       unsigned long convrate;
+       s32 err;
+       int resol = 0;
+
+       err = kstrtoul(buf, 10, &convrate);
+       if (err)
+               return err;
+
+       /* Convert rate into resolution bits */
+       while (resol < (ARRAY_SIZE(ds1721_convrates) - 1) &&
+              convrate > ds1721_convrates[resol])
+               resol++;
+
+       mutex_lock(&data->update_lock);
+       data->conf = i2c_smbus_read_byte_data(client, DS1621_REG_CONF);
+       data->conf &= ~DS1621_REG_CONFIG_RESOL;
+       data->conf |= (resol << DS1621_REG_CONFIG_RESOL_SHIFT);
+       i2c_smbus_write_byte_data(client, DS1621_REG_CONF, data->conf);
+       data->update_interval = ds1721_convrates[resol];
+       mutex_unlock(&data->update_lock);
+
+       return count;
+}
+
 static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
+static DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO, show_convrate,
+                  set_convrate);
+
 static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
 static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp, set_temp, 1);
 static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp, set_temp, 2);
@@ -201,48 +335,29 @@ static struct attribute *ds1621_attributes[] = {
        &sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
        &sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
        &dev_attr_alarms.attr,
+       &dev_attr_update_interval.attr,
        NULL
 };
 
-static const struct attribute_group ds1621_group = {
-       .attrs = ds1621_attributes,
-};
-
-
-/* Return 0 if detection is successful, -ENODEV otherwise */
-static int ds1621_detect(struct i2c_client *client,
-                        struct i2c_board_info *info)
+static umode_t ds1621_attribute_visible(struct kobject *kobj,
+                                       struct attribute *attr, int index)
 {
-       struct i2c_adapter *adapter = client->adapter;
-       int conf, temp;
-       int i;
-
-       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
-                                    | I2C_FUNC_SMBUS_WORD_DATA
-                                    | I2C_FUNC_SMBUS_WRITE_BYTE))
-               return -ENODEV;
-
-       /*
-        * Now, we do the remaining detection. It is lousy.
-        *
-        * The NVB bit should be low if no EEPROM write has been requested
-        * during the latest 10ms, which is highly improbable in our case.
-        */
-       conf = i2c_smbus_read_byte_data(client, DS1621_REG_CONF);
-       if (conf < 0 || conf & DS1621_REG_CONFIG_NVB)
-               return -ENODEV;
-       /* The 7 lowest bits of a temperature should always be 0. */
-       for (i = 0; i < ARRAY_SIZE(DS1621_REG_TEMP); i++) {
-               temp = i2c_smbus_read_word_data(client, DS1621_REG_TEMP[i]);
-               if (temp < 0 || (temp & 0x7f00))
-                       return -ENODEV;
-       }
-
-       strlcpy(info->type, "ds1621", I2C_NAME_SIZE);
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct i2c_client *client = to_i2c_client(dev);
+       struct ds1621_data *data = i2c_get_clientdata(client);
 
-       return 0;
+       if (attr == &dev_attr_update_interval.attr)
+               if (data->kind == ds1621 || data->kind == ds1625)
+                       /* shhh, we're hiding update_interval */
+                       return 0;
+       return attr->mode;
 }
 
+static const struct attribute_group ds1621_group = {
+       .attrs = ds1621_attributes,
+       .is_visible = ds1621_attribute_visible
+};
+
 static int ds1621_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
 {
@@ -257,6 +372,8 @@ static int ds1621_probe(struct i2c_client *client,
        i2c_set_clientdata(client, data);
        mutex_init(&data->update_lock);
 
+       data->kind = id->driver_data;
+
        /* Initialize the DS1621 chip */
        ds1621_init_client(client);
 
@@ -289,8 +406,11 @@ static int ds1621_remove(struct i2c_client *client)
 }
 
 static const struct i2c_device_id ds1621_id[] = {
-       { "ds1621", 0 },
-       { "ds1625", 0 },
+       { "ds1621", ds1621 },
+       { "ds1625", ds1625 },
+       { "ds1631", ds1631 },
+       { "ds1721", ds1721 },
+       { "ds1731", ds1731 },
        { }
 };
 MODULE_DEVICE_TABLE(i2c, ds1621_id);
@@ -304,8 +424,6 @@ static struct i2c_driver ds1621_driver = {
        .probe          = ds1621_probe,
        .remove         = ds1621_remove,
        .id_table       = ds1621_id,
-       .detect         = ds1621_detect,
-       .address_list   = normal_i2c,
 };
 
 module_i2c_driver(ds1621_driver);
diff --git a/drivers/hwmon/g762.c b/drivers/hwmon/g762.c
new file mode 100644 (file)
index 0000000..73adf01
--- /dev/null
@@ -0,0 +1,1149 @@
+/*
+ * g762 - Driver for the Global Mixed-mode Technology Inc. fan speed
+ *        PWM controller chips from G762 family, i.e. G762 and G763
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org>
+ *
+ * This work is based on a basic version for 2.6.31 kernel developed
+ * by Olivier Mouchet for LaCie. Updates and correction have been
+ * performed to run on recent kernels. Additional features, like the
+ * ability to configure various characteristics via .dts file or
+ * board init file have been added. Detailed datasheet on which this
+ * development is based is available here:
+ *
+ *  http://natisbad.org/NAS/refs/GMT_EDS-762_763-080710-0.2.pdf
+ *
+ * Headers from previous developments have been kept below:
+ *
+ * Copyright (c) 2009 LaCie
+ *
+ * Author: Olivier Mouchet <olivier.mouchet@gmail.com>
+ *
+ * based on g760a code written by Herbert Valerio Riedel <hvr@gnu.org>
+ * Copyright (C) 2007  Herbert Valerio Riedel <hvr@gnu.org>
+ *
+ * g762: minimal datasheet available at:
+ *       http://www.gmt.com.tw/product/datasheet/EDS-762_3.pdf
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_data/g762.h>
+
+#define DRVNAME "g762"
+
+static const struct i2c_device_id g762_id[] = {
+       { "g762", 0 },
+       { "g763", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, g762_id);
+
+enum g762_regs {
+       G762_REG_SET_CNT  = 0x00,
+       G762_REG_ACT_CNT  = 0x01,
+       G762_REG_FAN_STA  = 0x02,
+       G762_REG_SET_OUT  = 0x03,
+       G762_REG_FAN_CMD1 = 0x04,
+       G762_REG_FAN_CMD2 = 0x05,
+};
+
+/* Config register bits */
+#define G762_REG_FAN_CMD1_DET_FAN_FAIL  0x80 /* enable fan_fail signal */
+#define G762_REG_FAN_CMD1_DET_FAN_OOC   0x40 /* enable fan_out_of_control */
+#define G762_REG_FAN_CMD1_OUT_MODE      0x20 /* out mode: PWM or DC */
+#define G762_REG_FAN_CMD1_FAN_MODE      0x10 /* fan mode: closed/open-loop */
+#define G762_REG_FAN_CMD1_CLK_DIV_ID1   0x08 /* clock divisor value */
+#define G762_REG_FAN_CMD1_CLK_DIV_ID0   0x04
+#define G762_REG_FAN_CMD1_PWM_POLARITY  0x02 /* PWM polarity */
+#define G762_REG_FAN_CMD1_PULSE_PER_REV 0x01 /* pulse per fan revolution */
+
+#define G762_REG_FAN_CMD2_GEAR_MODE_1   0x08 /* fan gear mode */
+#define G762_REG_FAN_CMD2_GEAR_MODE_0   0x04
+#define G762_REG_FAN_CMD2_FAN_STARTV_1  0x02 /* fan startup voltage */
+#define G762_REG_FAN_CMD2_FAN_STARTV_0  0x01
+
+#define G762_REG_FAN_STA_FAIL           0x02 /* fan fail */
+#define G762_REG_FAN_STA_OOC            0x01 /* fan out of control */
+
+/* Config register values */
+#define G762_OUT_MODE_PWM            1
+#define G762_OUT_MODE_DC             0
+
+#define G762_FAN_MODE_CLOSED_LOOP    2
+#define G762_FAN_MODE_OPEN_LOOP      1
+
+#define G762_PWM_POLARITY_NEGATIVE   1
+#define G762_PWM_POLARITY_POSITIVE   0
+
+/* Register data is read (and cached) at most once per second. */
+#define G762_UPDATE_INTERVAL    HZ
+
+/*
+ * Extract pulse count per fan revolution value (2 or 4) from given
+ * FAN_CMD1 register value.
+ */
+#define G762_PULSE_FROM_REG(reg) \
+       ((((reg) & G762_REG_FAN_CMD1_PULSE_PER_REV) + 1) << 1)
+
+/*
+ * Extract fan clock divisor (1, 2, 4 or 8) from given FAN_CMD1
+ * register value.
+ */
+#define G762_CLKDIV_FROM_REG(reg) \
+       (1 << (((reg) & (G762_REG_FAN_CMD1_CLK_DIV_ID0 |        \
+                        G762_REG_FAN_CMD1_CLK_DIV_ID1)) >> 2))
+
+/*
+ * Extract fan gear mode multiplier value (0, 2 or 4) from given
+ * FAN_CMD2 register value.
+ */
+#define G762_GEARMULT_FROM_REG(reg) \
+       (1 << (((reg) & (G762_REG_FAN_CMD2_GEAR_MODE_0 |        \
+                        G762_REG_FAN_CMD2_GEAR_MODE_1)) >> 2))
+
+struct g762_data {
+       struct i2c_client *client;
+       struct device *hwmon_dev;
+       struct clk *clk;
+
+       /* update mutex */
+       struct mutex update_lock;
+
+       /* board specific parameters. */
+       u32 clk_freq;
+
+       /* g762 register cache */
+       bool valid;
+       unsigned long last_updated; /* in jiffies */
+
+       u8 set_cnt;  /* controls fan rotation speed in closed-loop mode */
+       u8 act_cnt;  /* provides access to current fan RPM value */
+       u8 fan_sta;  /* bit 0: set when actual fan speed is more than
+                     *        25% outside requested fan speed
+                     * bit 1: set when no transition occurs on fan
+                     *        pin for 0.7s
+                     */
+       u8 set_out;  /* controls fan rotation speed in open-loop mode */
+       u8 fan_cmd1; /*   0: FG_PLS_ID0 FG pulses count per revolution
+                     *      0: 2 counts per revolution
+                     *      1: 4 counts per revolution
+                     *   1: PWM_POLARITY 1: negative_duty
+                     *                   0: positive_duty
+                     * 2,3: [FG_CLOCK_ID0, FG_CLK_ID1]
+                     *         00: Divide fan clock by 1
+                     *         01: Divide fan clock by 2
+                     *         10: Divide fan clock by 4
+                     *         11: Divide fan clock by 8
+                     *   4: FAN_MODE 1:closed-loop, 0:open-loop
+                     *   5: OUT_MODE 1:PWM, 0:DC
+                     *   6: DET_FAN_OOC enable "fan ooc" status
+                     *   7: DET_FAN_FAIL enable "fan fail" status
+                     */
+       u8 fan_cmd2; /* 0,1: FAN_STARTV 0,1,2,3 -> 0,32,64,96 dac_code
+                     * 2,3: FG_GEAR_MODE
+                     *         00: multiplier = 1
+                     *         01: multiplier = 2
+                     *         10: multiplier = 4
+                     *   4: Mask ALERT# (g763 only)
+                     */
+};
+
+/*
+ * Convert count value from fan controller register (FAN_SET_CNT) into fan
+ * speed RPM value. Note that the datasheet documents a basic formula;
+ * influence of additional parameters (fan clock divisor, fan gear mode)
+ * have been infered from examples in the datasheet and tests.
+ */
+static inline unsigned int rpm_from_cnt(u8 cnt, u32 clk_freq, u16 p,
+                                       u8 clk_div, u8 gear_mult)
+{
+       if (cnt == 0xff)  /* setting cnt to 255 stops the fan */
+               return 0;
+
+       return (clk_freq * 30 * gear_mult) / ((cnt ? cnt : 1) * p * clk_div);
+}
+
+/*
+ * Convert fan RPM value from sysfs into count value for fan controller
+ * register (FAN_SET_CNT).
+ */
+static inline unsigned char cnt_from_rpm(u32 rpm, u32 clk_freq, u16 p,
+                                        u8 clk_div, u8 gear_mult)
+{
+       if (!rpm)         /* to stop the fan, set cnt to 255 */
+               return 0xff;
+
+       return clamp_val(((clk_freq * 30 * gear_mult) / (rpm * p * clk_div)),
+                        0, 255);
+}
+
+/* helper to grab and cache data, at most one time per second */
+static struct g762_data *g762_update_client(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = i2c_get_clientdata(client);
+       int ret = 0;
+
+       mutex_lock(&data->update_lock);
+       if (time_before(jiffies, data->last_updated + G762_UPDATE_INTERVAL) &&
+           likely(data->valid))
+               goto out;
+
+       ret = i2c_smbus_read_byte_data(client, G762_REG_SET_CNT);
+       if (ret < 0)
+               goto out;
+       data->set_cnt = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G762_REG_ACT_CNT);
+       if (ret < 0)
+               goto out;
+       data->act_cnt = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G762_REG_FAN_STA);
+       if (ret < 0)
+               goto out;
+       data->fan_sta = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G762_REG_SET_OUT);
+       if (ret < 0)
+               goto out;
+       data->set_out = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G762_REG_FAN_CMD1);
+       if (ret < 0)
+               goto out;
+       data->fan_cmd1 = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G762_REG_FAN_CMD2);
+       if (ret < 0)
+               goto out;
+       data->fan_cmd2 = ret;
+
+       data->last_updated = jiffies;
+       data->valid = true;
+ out:
+       mutex_unlock(&data->update_lock);
+
+       if (ret < 0) /* upon error, encode it in return value */
+               data = ERR_PTR(ret);
+
+       return data;
+}
+
+/* helpers for writing hardware parameters */
+
+/*
+ * Set input clock frequency received on CLK pin of the chip. Accepted values
+ * are between 0 and 0xffffff. If zero is given, then default frequency
+ * (32,768Hz) is used. Note that clock frequency is a characteristic of the
+ * system but an internal parameter, i.e. value is not passed to the device.
+ */
+static int do_set_clk_freq(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = i2c_get_clientdata(client);
+
+       if (val > 0xffffff)
+               return -EINVAL;
+       if (!val)
+               val = 32768;
+
+       data->clk_freq = val;
+
+       return 0;
+}
+
+/* Set pwm mode. Accepts either 0 (PWM mode) or 1 (DC mode) */
+static int do_set_pwm_mode(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = g762_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case G762_OUT_MODE_PWM:
+               data->fan_cmd1 |=  G762_REG_FAN_CMD1_OUT_MODE;
+               break;
+       case G762_OUT_MODE_DC:
+               data->fan_cmd1 &= ~G762_REG_FAN_CMD1_OUT_MODE;
+               break;
+       default:
+               ret = -EINVAL;
+               goto out;
+       }
+       ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+ out:
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Set fan clock divisor. Accepts either 1, 2, 4 or 8. */
+static int do_set_fan_div(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = g762_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case 1:
+               data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID0;
+               data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID1;
+               break;
+       case 2:
+               data->fan_cmd1 |=  G762_REG_FAN_CMD1_CLK_DIV_ID0;
+               data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID1;
+               break;
+       case 4:
+               data->fan_cmd1 &= ~G762_REG_FAN_CMD1_CLK_DIV_ID0;
+               data->fan_cmd1 |=  G762_REG_FAN_CMD1_CLK_DIV_ID1;
+               break;
+       case 8:
+               data->fan_cmd1 |=  G762_REG_FAN_CMD1_CLK_DIV_ID0;
+               data->fan_cmd1 |=  G762_REG_FAN_CMD1_CLK_DIV_ID1;
+               break;
+       default:
+               ret = -EINVAL;
+               goto out;
+       }
+       ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+ out:
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Set fan gear mode. Accepts either 0, 1 or 2. */
+static int do_set_fan_gear_mode(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = g762_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case 0:
+               data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_0;
+               data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_1;
+               break;
+       case 1:
+               data->fan_cmd2 |=  G762_REG_FAN_CMD2_GEAR_MODE_0;
+               data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_1;
+               break;
+       case 2:
+               data->fan_cmd2 &= ~G762_REG_FAN_CMD2_GEAR_MODE_0;
+               data->fan_cmd2 |=  G762_REG_FAN_CMD2_GEAR_MODE_1;
+               break;
+       default:
+               ret = -EINVAL;
+               goto out;
+       }
+       ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD2,
+                                       data->fan_cmd2);
+       data->valid = false;
+ out:
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Set number of fan pulses per revolution. Accepts either 2 or 4. */
+static int do_set_fan_pulses(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = g762_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case 2:
+               data->fan_cmd1 &= ~G762_REG_FAN_CMD1_PULSE_PER_REV;
+               break;
+       case 4:
+               data->fan_cmd1 |=  G762_REG_FAN_CMD1_PULSE_PER_REV;
+               break;
+       default:
+               ret = -EINVAL;
+               goto out;
+       }
+       ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+ out:
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Set fan mode. Accepts either 1 (open-loop) or 2 (closed-loop). */
+static int do_set_pwm_enable(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = g762_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case G762_FAN_MODE_CLOSED_LOOP:
+               data->fan_cmd1 |=  G762_REG_FAN_CMD1_FAN_MODE;
+               break;
+       case G762_FAN_MODE_OPEN_LOOP:
+               data->fan_cmd1 &= ~G762_REG_FAN_CMD1_FAN_MODE;
+               /*
+                * BUG FIX: if SET_CNT register value is 255 then, for some
+                * unknown reason, fan will not rotate as expected, no matter
+                * the value of SET_OUT (to be specific, this seems to happen
+                * only in PWM mode). To workaround this bug, we give SET_CNT
+                * value of 254 if it is 255 when switching to open-loop.
+                */
+               if (data->set_cnt == 0xff)
+                       i2c_smbus_write_byte_data(client, G762_REG_SET_CNT,
+                                                 254);
+               break;
+       default:
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+ out:
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Set PWM polarity. Accepts either 0 (positive duty) or 1 (negative duty) */
+static int do_set_pwm_polarity(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = g762_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case G762_PWM_POLARITY_POSITIVE:
+               data->fan_cmd1 &= ~G762_REG_FAN_CMD1_PWM_POLARITY;
+               break;
+       case G762_PWM_POLARITY_NEGATIVE:
+               data->fan_cmd1 |=  G762_REG_FAN_CMD1_PWM_POLARITY;
+               break;
+       default:
+               ret = -EINVAL;
+               goto out;
+       }
+       ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+ out:
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/*
+ * Set pwm value. Accepts values between 0 (stops the fan) and
+ * 255 (full speed). This only makes sense in open-loop mode.
+ */
+static int do_set_pwm(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = i2c_get_clientdata(client);
+       int ret;
+
+       if (val > 255)
+               return -EINVAL;
+
+       mutex_lock(&data->update_lock);
+       ret = i2c_smbus_write_byte_data(client, G762_REG_SET_OUT, val);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/*
+ * Set fan RPM value. Can be called both in closed and open-loop mode
+ * but effect will only be seen after closed-loop mode is configured.
+ */
+static int do_set_fan_target(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = g762_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       data->set_cnt = cnt_from_rpm(val, data->clk_freq,
+                                    G762_PULSE_FROM_REG(data->fan_cmd1),
+                                    G762_CLKDIV_FROM_REG(data->fan_cmd1),
+                                    G762_GEARMULT_FROM_REG(data->fan_cmd2));
+       ret = i2c_smbus_write_byte_data(client, G762_REG_SET_CNT,
+                                       data->set_cnt);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Set fan startup voltage. Accepted values are either 0, 1, 2 or 3. */
+static int do_set_fan_startv(struct device *dev, unsigned long val)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = g762_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case 0:
+               data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_0;
+               data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_1;
+               break;
+       case 1:
+               data->fan_cmd2 |=  G762_REG_FAN_CMD2_FAN_STARTV_0;
+               data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_1;
+               break;
+       case 2:
+               data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_0;
+               data->fan_cmd2 |=  G762_REG_FAN_CMD2_FAN_STARTV_1;
+               break;
+       case 3:
+               data->fan_cmd2 |=  G762_REG_FAN_CMD2_FAN_STARTV_0;
+               data->fan_cmd2 |=  G762_REG_FAN_CMD2_FAN_STARTV_1;
+               break;
+       default:
+               ret = -EINVAL;
+               goto out;
+       }
+       ret = i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD2,
+                                       data->fan_cmd2);
+       data->valid = false;
+ out:
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/*
+ * Helper to import hardware characteristics from .dts file and push
+ * those to the chip.
+ */
+
+#ifdef CONFIG_OF
+static struct of_device_id g762_dt_match[] = {
+       { .compatible = "gmt,g762" },
+       { .compatible = "gmt,g763" },
+       { },
+};
+
+/*
+ * Grab clock (a required property), enable it, get (fixed) clock frequency
+ * and store it. Note: upon success, clock has been prepared and enabled; it
+ * must later be unprepared and disabled (e.g. during module unloading) by a
+ * call to g762_of_clock_disable(). Note that a reference to clock is kept
+ * in our private data structure to be used in this function.
+ */
+static int g762_of_clock_enable(struct i2c_client *client)
+{
+       struct g762_data *data;
+       unsigned long clk_freq;
+       struct clk *clk;
+       int ret;
+
+       if (!client->dev.of_node)
+               return 0;
+
+       clk = of_clk_get(client->dev.of_node, 0);
+       if (IS_ERR(clk)) {
+               dev_err(&client->dev, "failed to get clock\n");
+               return PTR_ERR(clk);
+       }
+
+       ret = clk_prepare_enable(clk);
+       if (ret) {
+               dev_err(&client->dev, "failed to enable clock\n");
+               goto clk_put;
+       }
+
+       clk_freq = clk_get_rate(clk);
+       ret = do_set_clk_freq(&client->dev, clk_freq);
+       if (ret) {
+               dev_err(&client->dev, "invalid clock freq %lu\n", clk_freq);
+               goto clk_unprep;
+       }
+
+       data = i2c_get_clientdata(client);
+       data->clk = clk;
+
+       return 0;
+
+ clk_unprep:
+       clk_disable_unprepare(clk);
+
+ clk_put:
+       clk_put(clk);
+
+       return ret;
+}
+
+static void g762_of_clock_disable(struct i2c_client *client)
+{
+       struct g762_data *data = i2c_get_clientdata(client);
+
+       if (!data->clk)
+               return;
+
+       clk_disable_unprepare(data->clk);
+       clk_put(data->clk);
+}
+
+static int g762_of_prop_import_one(struct i2c_client *client,
+                                  const char *pname,
+                                  int (*psetter)(struct device *dev,
+                                                 unsigned long val))
+{
+       const __be32 *prop;
+       int len, ret;
+       u32 pval;
+
+       prop = of_get_property(client->dev.of_node, pname, &len);
+       if (!prop || len != sizeof(u32))
+               return 0;
+
+       pval = be32_to_cpu(prop[0]);
+       dev_dbg(&client->dev, "found %s (%d)\n", pname, pval);
+       ret = (*psetter)(&client->dev, pval);
+       if (ret)
+               dev_err(&client->dev, "unable to set %s (%d)\n", pname, pval);
+
+       return ret;
+}
+
+static int g762_of_prop_import(struct i2c_client *client)
+{
+       int ret;
+
+       if (!client->dev.of_node)
+               return 0;
+
+       ret = g762_of_prop_import_one(client, "fan_gear_mode",
+                                     do_set_fan_gear_mode);
+       if (ret)
+               return ret;
+
+       ret = g762_of_prop_import_one(client, "pwm_polarity",
+                                     do_set_pwm_polarity);
+       if (ret)
+               return ret;
+
+       return g762_of_prop_import_one(client, "fan_startv",
+                                      do_set_fan_startv);
+}
+
+#else
+static int g762_of_prop_import(struct i2c_client *client)
+{
+       return 0;
+}
+
+static int g762_of_clock_enable(struct i2c_client *client)
+{
+       return 0;
+}
+
+static void g762_of_clock_disable(struct i2c_client *client) { }
+#endif
+
+/*
+ * Helper to import hardware characteristics from .dts file and push
+ * those to the chip.
+ */
+
+static int g762_pdata_prop_import(struct i2c_client *client)
+{
+       struct g762_platform_data *pdata = client->dev.platform_data;
+       int ret;
+
+       if (!pdata)
+               return 0;
+
+       ret = do_set_fan_gear_mode(&client->dev, pdata->fan_gear_mode);
+       if (ret)
+               return ret;
+
+       ret = do_set_pwm_polarity(&client->dev, pdata->pwm_polarity);
+       if (ret)
+               return ret;
+
+       ret = do_set_fan_startv(&client->dev, pdata->fan_startv);
+       if (ret)
+               return ret;
+
+       return do_set_clk_freq(&client->dev, pdata->clk_freq);
+}
+
+/*
+ * sysfs attributes
+ */
+
+/*
+ * Read function for fan1_input sysfs file. Return current fan RPM value, or
+ * 0 if fan is out of control.
+ */
+static ssize_t get_fan_rpm(struct device *dev, struct device_attribute *da,
+                          char *buf)
+{
+       struct g762_data *data = g762_update_client(dev);
+       unsigned int rpm = 0;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       /* reverse logic: fan out of control reporting is enabled low */
+       if (data->fan_sta & G762_REG_FAN_STA_OOC) {
+               rpm = rpm_from_cnt(data->act_cnt, data->clk_freq,
+                                  G762_PULSE_FROM_REG(data->fan_cmd1),
+                                  G762_CLKDIV_FROM_REG(data->fan_cmd1),
+                                  G762_GEARMULT_FROM_REG(data->fan_cmd2));
+       }
+       mutex_unlock(&data->update_lock);
+
+       return sprintf(buf, "%u\n", rpm);
+}
+
+/*
+ * Read and write functions for pwm1_mode sysfs file. Get and set fan speed
+ * control mode i.e. PWM (1) or DC (0).
+ */
+static ssize_t get_pwm_mode(struct device *dev, struct device_attribute *da,
+                           char *buf)
+{
+       struct g762_data *data = g762_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n",
+                      !!(data->fan_cmd1 & G762_REG_FAN_CMD1_OUT_MODE));
+}
+
+static ssize_t set_pwm_mode(struct device *dev, struct device_attribute *da,
+                           const char *buf, size_t count)
+{
+       unsigned long val;
+       int ret;
+
+       if (kstrtoul(buf, 10, &val))
+               return -EINVAL;
+
+       ret = do_set_pwm_mode(dev, val);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+/*
+ * Read and write functions for fan1_div sysfs file. Get and set fan
+ * controller prescaler value
+ */
+static ssize_t get_fan_div(struct device *dev,
+                          struct device_attribute *da, char *buf)
+{
+       struct g762_data *data = g762_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", G762_CLKDIV_FROM_REG(data->fan_cmd1));
+}
+
+static ssize_t set_fan_div(struct device *dev,
+                          struct device_attribute *da,
+                          const char *buf, size_t count)
+{
+       unsigned long val;
+       int ret;
+
+       if (kstrtoul(buf, 10, &val))
+               return -EINVAL;
+
+       ret = do_set_fan_div(dev, val);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+/*
+ * Read and write functions for fan1_pulses sysfs file. Get and set number
+ * of tachometer pulses per fan revolution.
+ */
+static ssize_t get_fan_pulses(struct device *dev,
+                             struct device_attribute *da, char *buf)
+{
+       struct g762_data *data = g762_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", G762_PULSE_FROM_REG(data->fan_cmd1));
+}
+
+static ssize_t set_fan_pulses(struct device *dev,
+                             struct device_attribute *da,
+                             const char *buf, size_t count)
+{
+       unsigned long val;
+       int ret;
+
+       if (kstrtoul(buf, 10, &val))
+               return -EINVAL;
+
+       ret = do_set_fan_pulses(dev, val);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+/*
+ * Read and write functions for pwm1_enable. Get and set fan speed control mode
+ * (i.e. closed or open-loop).
+ *
+ * Following documentation about hwmon's sysfs interface, a pwm1_enable node
+ * should accept followings:
+ *
+ *  0 : no fan speed control (i.e. fan at full speed)
+ *  1 : manual fan speed control enabled (use pwm[1-*]) (open-loop)
+ *  2+: automatic fan speed control enabled (use fan[1-*]_target) (closed-loop)
+ *
+ * but we do not accept 0 as this mode is not natively supported by the chip
+ * and it is not emulated by g762 driver. -EINVAL is returned in this case.
+ */
+static ssize_t get_pwm_enable(struct device *dev,
+                             struct device_attribute *da, char *buf)
+{
+       struct g762_data *data = g762_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n",
+                      (!!(data->fan_cmd1 & G762_REG_FAN_CMD1_FAN_MODE)) + 1);
+}
+
+static ssize_t set_pwm_enable(struct device *dev,
+                             struct device_attribute *da,
+                             const char *buf, size_t count)
+{
+       unsigned long val;
+       int ret;
+
+       if (kstrtoul(buf, 10, &val))
+               return -EINVAL;
+
+       ret = do_set_pwm_enable(dev, val);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+/*
+ * Read and write functions for pwm1 sysfs file. Get and set pwm value
+ * (which affects fan speed) in open-loop mode. 0 stops the fan and 255
+ * makes it run at full speed.
+ */
+static ssize_t get_pwm(struct device *dev, struct device_attribute *da,
+                      char *buf)
+{
+       struct g762_data *data = g762_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", data->set_out);
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *da,
+                      const char *buf, size_t count)
+{
+       unsigned long val;
+       int ret;
+
+       if (kstrtoul(buf, 10, &val))
+               return -EINVAL;
+
+       ret = do_set_pwm(dev, val);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+/*
+ * Read and write function for fan1_target sysfs file. Get/set the fan speed in
+ * closed-loop mode. Speed is given as a RPM value; then the chip will regulate
+ * the fan speed using pulses from fan tachometer.
+ *
+ * Refer to rpm_from_cnt() implementation above to get info about count number
+ * calculation.
+ *
+ * Also note that due to rounding errors it is possible that you don't read
+ * back exactly the value you have set.
+ */
+static ssize_t get_fan_target(struct device *dev, struct device_attribute *da,
+                             char *buf)
+{
+       struct g762_data *data = g762_update_client(dev);
+       unsigned int rpm;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       rpm = rpm_from_cnt(data->set_cnt, data->clk_freq,
+                          G762_PULSE_FROM_REG(data->fan_cmd1),
+                          G762_CLKDIV_FROM_REG(data->fan_cmd1),
+                          G762_GEARMULT_FROM_REG(data->fan_cmd2));
+       mutex_unlock(&data->update_lock);
+
+       return sprintf(buf, "%u\n", rpm);
+}
+
+static ssize_t set_fan_target(struct device *dev, struct device_attribute *da,
+                             const char *buf, size_t count)
+{
+       unsigned long val;
+       int ret;
+
+       if (kstrtoul(buf, 10, &val))
+               return -EINVAL;
+
+       ret = do_set_fan_target(dev, val);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+/* read function for fan1_fault sysfs file. */
+static ssize_t get_fan_failure(struct device *dev, struct device_attribute *da,
+                              char *buf)
+{
+       struct g762_data *data = g762_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%u\n", !!(data->fan_sta & G762_REG_FAN_STA_FAIL));
+}
+
+/*
+ * read function for fan1_alarm sysfs file. Note that OOC condition is
+ * enabled low
+ */
+static ssize_t get_fan_ooc(struct device *dev, struct device_attribute *da,
+                          char *buf)
+{
+       struct g762_data *data = g762_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%u\n", !(data->fan_sta & G762_REG_FAN_STA_OOC));
+}
+
+static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm);
+static DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO, get_pwm_mode, set_pwm_mode);
+static DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
+                  get_pwm_enable, set_pwm_enable);
+static DEVICE_ATTR(fan1_input, S_IRUGO, get_fan_rpm, NULL);
+static DEVICE_ATTR(fan1_alarm, S_IRUGO, get_fan_ooc, NULL);
+static DEVICE_ATTR(fan1_fault, S_IRUGO, get_fan_failure, NULL);
+static DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO,
+                  get_fan_target, set_fan_target);
+static DEVICE_ATTR(fan1_div, S_IWUSR | S_IRUGO, get_fan_div, set_fan_div);
+static DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO,
+                  get_fan_pulses, set_fan_pulses);
+
+/* Driver data */
+static struct attribute *g762_attributes[] = {
+       &dev_attr_fan1_input.attr,
+       &dev_attr_fan1_alarm.attr,
+       &dev_attr_fan1_fault.attr,
+       &dev_attr_fan1_target.attr,
+       &dev_attr_fan1_div.attr,
+       &dev_attr_fan1_pulses.attr,
+       &dev_attr_pwm1.attr,
+       &dev_attr_pwm1_mode.attr,
+       &dev_attr_pwm1_enable.attr,
+       NULL
+};
+
+static const struct attribute_group g762_group = {
+       .attrs = g762_attributes,
+};
+
+/*
+ * Enable both fan failure detection and fan out of control protection. The
+ * function does not protect change/access to data structure; it must thus
+ * only be called during initialization.
+ */
+static inline int g762_fan_init(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g762_data *data = g762_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       data->fan_cmd1 |= G762_REG_FAN_CMD1_DET_FAN_FAIL;
+       data->fan_cmd1 |= G762_REG_FAN_CMD1_DET_FAN_OOC;
+       data->valid = false;
+
+       return i2c_smbus_write_byte_data(client, G762_REG_FAN_CMD1,
+                                        data->fan_cmd1);
+}
+
+static int g762_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+       struct g762_data *data;
+       int ret;
+
+       if (!i2c_check_functionality(client->adapter,
+                                    I2C_FUNC_SMBUS_BYTE_DATA))
+               return -ENODEV;
+
+       data = devm_kzalloc(&client->dev, sizeof(struct g762_data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, data);
+       data->client = client;
+       mutex_init(&data->update_lock);
+
+       /* Enable fan failure detection and fan out of control protection */
+       ret = g762_fan_init(&client->dev);
+       if (ret)
+               return ret;
+
+       /* Get configuration via DT ... */
+       ret = g762_of_clock_enable(client);
+       if (ret)
+               return ret;
+       ret = g762_of_prop_import(client);
+       if (ret)
+               goto clock_dis;
+       /* ... or platform_data */
+       ret = g762_pdata_prop_import(client);
+       if (ret)
+               goto clock_dis;
+
+       /* Register sysfs hooks */
+       ret = sysfs_create_group(&client->dev.kobj, &g762_group);
+       if (ret)
+               goto clock_dis;
+
+       data->hwmon_dev = hwmon_device_register(&client->dev);
+       if (IS_ERR(data->hwmon_dev)) {
+               ret = PTR_ERR(data->hwmon_dev);
+               goto sysfs_rem;
+       }
+
+       return 0;
+
+ sysfs_rem:
+       sysfs_remove_group(&client->dev.kobj, &g762_group);
+
+ clock_dis:
+       g762_of_clock_disable(client);
+
+       return ret;
+}
+
+static int g762_remove(struct i2c_client *client)
+{
+       struct g762_data *data = i2c_get_clientdata(client);
+
+       hwmon_device_unregister(data->hwmon_dev);
+       sysfs_remove_group(&client->dev.kobj, &g762_group);
+       g762_of_clock_disable(client);
+
+       return 0;
+}
+
+static struct i2c_driver g762_driver = {
+       .driver = {
+               .name = DRVNAME,
+               .owner = THIS_MODULE,
+               .of_match_table = of_match_ptr(g762_dt_match),
+       },
+       .probe    = g762_probe,
+       .remove   = g762_remove,
+       .id_table = g762_id,
+};
+
+module_i2c_driver(g762_driver);
+
+MODULE_AUTHOR("Arnaud EBALARD <arno@natisbad.org>");
+MODULE_DESCRIPTION("GMT G762/G763 driver");
+MODULE_LICENSE("GPL");
index b87c2cc..de058c2 100644 (file)
@@ -556,7 +556,6 @@ static int i5k_amb_probe(struct platform_device *pdev)
 
 err_init_failed:
        iounmap(data->amb_mmio);
-       platform_set_drvdata(pdev, NULL);
 err_map_failed:
        release_mem_region(data->amb_base, data->amb_len);
 err:
@@ -576,7 +575,6 @@ static int i5k_amb_remove(struct platform_device *pdev)
        kfree(data->attrs);
        iounmap(data->amb_mmio);
        release_mem_region(data->amb_base, data->amb_len);
-       platform_set_drvdata(pdev, NULL);
        kfree(data);
        return 0;
 }
index 52b77af..708081b 100644 (file)
@@ -180,6 +180,7 @@ static struct of_device_id iio_hwmon_of_match[] = {
        { .compatible = "iio-hwmon", },
        { }
 };
+MODULE_DEVICE_TABLE(of, iio_hwmon_of_match);
 
 static struct platform_driver __refdata iio_hwmon_driver = {
        .driver = {
index 4958b2f..d917a2d 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/jiffies.h>
+#include <linux/of.h>
 
 #include <linux/platform_data/ina2xx.h>
 
@@ -221,6 +222,7 @@ static int ina2xx_probe(struct i2c_client *client,
        struct ina2xx_data *data;
        struct ina2xx_platform_data *pdata;
        int ret;
+       u32 val;
        long shunt = 10000; /* default shunt value 10mOhms */
 
        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
@@ -234,6 +236,9 @@ static int ina2xx_probe(struct i2c_client *client,
                pdata =
                  (struct ina2xx_platform_data *)client->dev.platform_data;
                shunt = pdata->shunt_uohms;
+       } else if (!of_property_read_u32(client->dev.of_node,
+                               "shunt-resistor", &val)) {
+                       shunt = val;
        }
 
        if (shunt <= 0)
index 04638ae..99cec18 100644 (file)
@@ -199,7 +199,7 @@ static const s8 NCT6775_ALARM_BITS[] = {
        0, 1, 2, 3, 8, 21, 20, 16,      /* in0.. in7 */
        17, -1, -1, -1, -1, -1, -1,     /* in8..in14 */
        -1,                             /* unused */
-       6, 7, 11, 10, 23,               /* fan1..fan5 */
+       6, 7, 11, -1, -1,               /* fan1..fan5 */
        -1, -1, -1,                     /* unused */
        4, 5, 13, -1, -1, -1,           /* temp1..temp6 */
        12, -1 };                       /* intrusion0, intrusion1 */
@@ -625,6 +625,7 @@ struct nct6775_data {
        u8 has_fan_min;         /* some fans don't have min register */
        bool has_fan_div;
 
+       u8 num_temp_alarms;     /* 2 or 3 */
        u8 temp_fixed_num;      /* 3 or 6 */
        u8 temp_type[NUM_TEMP_FIXED];
        s8 temp_offset[NUM_TEMP_FIXED];
@@ -1193,6 +1194,42 @@ show_alarm(struct device *dev, struct device_attribute *attr, char *buf)
                       (unsigned int)((data->alarms >> nr) & 0x01));
 }
 
+static int find_temp_source(struct nct6775_data *data, int index, int count)
+{
+       int source = data->temp_src[index];
+       int nr;
+
+       for (nr = 0; nr < count; nr++) {
+               int src;
+
+               src = nct6775_read_value(data,
+                                        data->REG_TEMP_SOURCE[nr]) & 0x1f;
+               if (src == source)
+                       return nr;
+       }
+       return -1;
+}
+
+static ssize_t
+show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+       struct nct6775_data *data = nct6775_update_device(dev);
+       unsigned int alarm = 0;
+       int nr;
+
+       /*
+        * For temperatures, there is no fixed mapping from registers to alarm
+        * bits. Alarm bits are determined by the temperature source mapping.
+        */
+       nr = find_temp_source(data, sattr->index, data->num_temp_alarms);
+       if (nr >= 0) {
+               int bit = data->ALARM_BITS[nr + TEMP_ALARM_BASE];
+               alarm = (data->alarms >> bit) & 0x01;
+       }
+       return sprintf(buf, "%u\n", alarm);
+}
+
 static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_in_reg, NULL, 0, 0);
 static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_in_reg, NULL, 1, 0);
 static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_in_reg, NULL, 2, 0);
@@ -1874,22 +1911,18 @@ static struct sensor_device_attribute sda_temp_type[] = {
 };
 
 static struct sensor_device_attribute sda_temp_alarm[] = {
-       SENSOR_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL,
-                   TEMP_ALARM_BASE),
-       SENSOR_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL,
-                   TEMP_ALARM_BASE + 1),
-       SENSOR_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL,
-                   TEMP_ALARM_BASE + 2),
-       SENSOR_ATTR(temp4_alarm, S_IRUGO, show_alarm, NULL,
-                   TEMP_ALARM_BASE + 3),
-       SENSOR_ATTR(temp5_alarm, S_IRUGO, show_alarm, NULL,
-                   TEMP_ALARM_BASE + 4),
-       SENSOR_ATTR(temp6_alarm, S_IRUGO, show_alarm, NULL,
-                   TEMP_ALARM_BASE + 5),
+       SENSOR_ATTR(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0),
+       SENSOR_ATTR(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 1),
+       SENSOR_ATTR(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 2),
+       SENSOR_ATTR(temp4_alarm, S_IRUGO, show_temp_alarm, NULL, 3),
+       SENSOR_ATTR(temp5_alarm, S_IRUGO, show_temp_alarm, NULL, 4),
+       SENSOR_ATTR(temp6_alarm, S_IRUGO, show_temp_alarm, NULL, 5),
+       SENSOR_ATTR(temp7_alarm, S_IRUGO, show_temp_alarm, NULL, 6),
+       SENSOR_ATTR(temp8_alarm, S_IRUGO, show_temp_alarm, NULL, 7),
+       SENSOR_ATTR(temp9_alarm, S_IRUGO, show_temp_alarm, NULL, 8),
+       SENSOR_ATTR(temp10_alarm, S_IRUGO, show_temp_alarm, NULL, 9),
 };
 
-#define NUM_TEMP_ALARM ARRAY_SIZE(sda_temp_alarm)
-
 static ssize_t
 show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf)
 {
@@ -3215,13 +3248,11 @@ static void nct6775_device_remove_files(struct device *dev)
                device_remove_file(dev, &sda_temp_max[i].dev_attr);
                device_remove_file(dev, &sda_temp_max_hyst[i].dev_attr);
                device_remove_file(dev, &sda_temp_crit[i].dev_attr);
+               device_remove_file(dev, &sda_temp_alarm[i].dev_attr);
                if (!(data->have_temp_fixed & (1 << i)))
                        continue;
                device_remove_file(dev, &sda_temp_type[i].dev_attr);
                device_remove_file(dev, &sda_temp_offset[i].dev_attr);
-               if (i >= NUM_TEMP_ALARM)
-                       continue;
-               device_remove_file(dev, &sda_temp_alarm[i].dev_attr);
        }
 
        device_remove_file(dev, &sda_caseopen[0].dev_attr);
@@ -3419,6 +3450,7 @@ static int nct6775_probe(struct platform_device *pdev)
                data->auto_pwm_num = 6;
                data->has_fan_div = true;
                data->temp_fixed_num = 3;
+               data->num_temp_alarms = 3;
 
                data->ALARM_BITS = NCT6775_ALARM_BITS;
 
@@ -3483,6 +3515,7 @@ static int nct6775_probe(struct platform_device *pdev)
                data->auto_pwm_num = 4;
                data->has_fan_div = false;
                data->temp_fixed_num = 3;
+               data->num_temp_alarms = 3;
 
                data->ALARM_BITS = NCT6776_ALARM_BITS;
 
@@ -3547,6 +3580,7 @@ static int nct6775_probe(struct platform_device *pdev)
                data->auto_pwm_num = 4;
                data->has_fan_div = false;
                data->temp_fixed_num = 6;
+               data->num_temp_alarms = 2;
 
                data->ALARM_BITS = NCT6779_ALARM_BITS;
 
@@ -3843,10 +3877,12 @@ static int nct6775_probe(struct platform_device *pdev)
                                                 &sda_fan_input[i].dev_attr);
                        if (err)
                                goto exit_remove;
-                       err = device_create_file(dev,
-                                                &sda_fan_alarm[i].dev_attr);
-                       if (err)
-                               goto exit_remove;
+                       if (data->ALARM_BITS[FAN_ALARM_BASE + i] >= 0) {
+                               err = device_create_file(dev,
+                                               &sda_fan_alarm[i].dev_attr);
+                               if (err)
+                                       goto exit_remove;
+                       }
                        if (data->kind != nct6776 &&
                            data->kind != nct6779) {
                                err = device_create_file(dev,
@@ -3897,6 +3933,12 @@ static int nct6775_probe(struct platform_device *pdev)
                        if (err)
                                goto exit_remove;
                }
+               if (find_temp_source(data, i, data->num_temp_alarms) >= 0) {
+                       err = device_create_file(dev,
+                                                &sda_temp_alarm[i].dev_attr);
+                       if (err)
+                               goto exit_remove;
+               }
                if (!(data->have_temp_fixed & (1 << i)))
                        continue;
                err = device_create_file(dev, &sda_temp_type[i].dev_attr);
@@ -3905,12 +3947,6 @@ static int nct6775_probe(struct platform_device *pdev)
                err = device_create_file(dev, &sda_temp_offset[i].dev_attr);
                if (err)
                        goto exit_remove;
-               if (i >= NUM_TEMP_ALARM ||
-                   data->ALARM_BITS[TEMP_ALARM_BASE + i] < 0)
-                       continue;
-               err = device_create_file(dev, &sda_temp_alarm[i].dev_attr);
-               if (err)
-                       goto exit_remove;
        }
 
        for (i = 0; i < ARRAY_SIZE(sda_caseopen); i++) {
index d6d640a..830a842 100644 (file)
@@ -514,7 +514,6 @@ static int ntc_thermistor_remove(struct platform_device *pdev)
        hwmon_device_unregister(data->hwmon_dev);
        sysfs_remove_group(&data->dev->kobj, &ntc_attr_group);
        ntc_iio_channel_release(pdata);
-       platform_set_drvdata(pdev, NULL);
 
        return 0;
 }
index 0160272..004801e 100644 (file)
@@ -2598,7 +2598,6 @@ static int w83627ehf_probe(struct platform_device *pdev)
 exit_remove:
        w83627ehf_device_remove_files(dev);
 exit_release:
-       platform_set_drvdata(pdev, NULL);
        release_region(res->start, IOREGION_LENGTH);
 exit:
        return err;
@@ -2611,7 +2610,6 @@ static int w83627ehf_remove(struct platform_device *pdev)
        hwmon_device_unregister(data->hwmon_dev);
        w83627ehf_device_remove_files(&pdev->dev);
        release_region(data->addr, IOREGION_LENGTH);
-       platform_set_drvdata(pdev, NULL);
 
        return 0;
 }
diff --git a/include/linux/platform_data/g762.h b/include/linux/platform_data/g762.h
new file mode 100644 (file)
index 0000000..d3c5128
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Platform data structure for g762 fan controller driver
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <arno@natisbad.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef __LINUX_PLATFORM_DATA_G762_H__
+#define __LINUX_PLATFORM_DATA_G762_H__
+
+/*
+ * Following structure can be used to set g762 driver platform specific data
+ * during board init. Note that passing a sparse structure is possible but
+ * will result in non-specified attributes to be set to default value, hence
+ * overloading those installed during boot (e.g. by u-boot).
+ */
+
+struct g762_platform_data {
+       u32 fan_startv;
+       u32 fan_gear_mode;
+       u32 pwm_polarity;
+       u32 clk_freq;
+};
+
+#endif /* __LINUX_PLATFORM_DATA_G762_H__ */