linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCHv3 0/3] Add G762/G763 PWM fan controller
@ 2013-06-02 20:14 Arnaud Ebalard
  2013-06-02 20:14 ` [PATCHv3 1/3] Add support for GMT " Arnaud Ebalard
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Arnaud Ebalard @ 2013-06-02 20:14 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

This series adds support for GMT G762/G763. This work is based on a
basic version for 2.6.31 kernel developed Olivier Mouchet for LaCie
NAS. Updates have been performed to run on recent kernels. Support has
been completed and additional features added: ability to configure
various characteristics from .dts file, better initialization, alarms
and error reporting support, gear mode, polarity, fan pulse per
revolution, fan startup voltage control. The following detailed
datasheet has been used as a basis for this work:

  http://natisbad.org/NAS/refs/GMT_EDS-762_763-080710-0.2.pdf

The patch was developed for and tested against the GMT G762 fan
controller used in a Netgear ReadyNAS Duo v2 (kirkwood 88F6282-based
NAS). This is the main reason for the device tree bindings provided in
first patch. The driver also support init via board file. The patches
are against current ARM tree; tell me if you need me to rebase it
against something else. Patch 2 and 3 provides documentation for the
driver and DT bindings, respectively.

As discussed in a previous email:

 - A macro is defined to pass values of platform data structure in
   board setup files. If one has a better approach ...
 - This v3 still has support for pwm1 sysfs entry in closed-loop mode.
   Guenter, if you still think it is a bad idea, I will change that
   in v4.

note: this should not be an issue but this v3 is based on
      jcooper/mvebu/fixes branch. 

Comments welcome,

Cheers,

a+

Changes since v2:
    set ref_clk value to 32768 if not overloaded
    fixed multi-line comment format in g762.h
    add spaces between operators when missing
    check return value of i2c_smbus_{read,write}_byte_data()
    { } is not needed in single-statement conditionals
    introduced G762_ATTR_VAL() to allow sparse init of platform_data struct
    changed reference to linear mode for DC mode in doc

Changes since v1
    Changed author
    removed bad tabs
    Provide datasheet link w/o fud in g762 documentation
    removed documentation for removed fan_gear_mode sysfs entry
    removed tested-by from patch
    removed FSF address in header file
    removed useless include of <linux/slab.h>
    removed useless parenthesis against HZ in define
    use spaces around binary operators
    use i2c_smbus_{read,write}_byte_data() instead of g762_{read,write}_value()
    use return value of i2c_smbus_write_byte_data()
    use true for initializing boolean
    removed useless blank lines
    do not enforce single return point rule where less readable
    use dev_err() and dev_dbg() instead of dev_info() when it makes sense
    remove leading '&' for function passed as pointers
    allow passing parameter via platform_data struct for non-DT enabled boards
    set data->valid to false when config is modified
    s/linear/DC/ for mode (g762 datasheet uses linear)
    more tests on rpm_from_cnt() and cnt_from_rpm() formula
    dont overload     

Changes since v0
    removed forward declaration 
    use bool for valid field instead of bit field.
    protect macro args
    fixed typo in subject line
    Added mention for G763 support in Kconfig
    fixed typo in driver name in Kconfig
    do not use DRVNAME in i2c_device_id g762_id[] 
    Following discussions, kept DEVICE_ATTR (i.e. no switch to SENSOR_DEVICE_ATTR)
    removed useless casts when flipping bit values
    Sanity check user input value (e.g. to prevent 256 to silenty become 0)
    Added extra lines for multi line comments when needed
    removed various testing knobs
    make removed knobs available via DT
    passed checkpatch script on the patch
    removed useless lock protection againt clk setting
    moved all setter at the beginning of the file
    removed bad (u16) casts in g762_write_value() calls


Arnaud Ebalard (3):
  Add support for GMT G762/G763 PWM fan controller
  Add documentation for g762 driver
  Add DT bindings documentation for g762 driver

 Documentation/devicetree/bindings/hwmon/g762.txt |   58 ++
 Documentation/hwmon/g762                         |   64 ++
 drivers/hwmon/Kconfig                            |   10 +
 drivers/hwmon/Makefile                           |    1 +
 drivers/hwmon/g762.c                             | 1179 ++++++++++++++++++++++
 include/linux/platform_data/g762.h               |   51 +
 6 files changed, 1363 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/g762.txt
 create mode 100644 Documentation/hwmon/g762
 create mode 100644 drivers/hwmon/g762.c
 create mode 100644 include/linux/platform_data/g762.h

-- 
1.7.10.4

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

* [PATCHv3 1/3] Add support for GMT G762/G763 PWM fan controller
  2013-06-02 20:14 [PATCHv3 0/3] Add G762/G763 PWM fan controller Arnaud Ebalard
@ 2013-06-02 20:14 ` Arnaud Ebalard
  2013-06-03  2:34   ` Guenter Roeck
  2013-06-02 20:14 ` [PATCHv3 2/3] Add documentation for g762 driver Arnaud Ebalard
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 8+ messages in thread
From: Arnaud Ebalard @ 2013-06-02 20:14 UTC (permalink / raw)
  To: linux-arm-kernel


Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
 drivers/hwmon/Kconfig              |   10 +
 drivers/hwmon/Makefile             |    1 +
 drivers/hwmon/g762.c               | 1179 ++++++++++++++++++++++++++++++++++++
 include/linux/platform_data/g762.h |   51 ++
 4 files changed, 1241 insertions(+)
 create mode 100644 drivers/hwmon/g762.c
 create mode 100644 include/linux/platform_data/g762.h

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 0428e8a..142bdf8 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -456,6 +456,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
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d17d3e6..4f0fb52 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -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
diff --git a/drivers/hwmon/g762.c b/drivers/hwmon/g762.c
new file mode 100644
index 0000000..6678a60
--- /dev/null
+++ b/drivers/hwmon/g762.c
@@ -0,0 +1,1179 @@
+/*
+ * 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, 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/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 OUT_MODE_PWM            1
+#define OUT_MODE_DC             0
+
+#define FAN_MODE_CLOSED_LOOP    2
+#define FAN_MODE_OPEN_LOOP      1
+
+/* 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 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 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 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;
+
+	/* update mutex */
+	struct mutex update_lock;
+
+	/* board specific parameters. */
+	u32 clk;
+
+	/* 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)
+		      */
+};
+
+/*
+ * sysfs PWM interface uses value from 0 to 255 when g762 FAN_SET_CNT register
+ * uses values from 255 (off) to 0 (full speed). Note that FAN_SET_OUT register
+ * uses values from 0 (off) to 255 (full speed), i.e. does not need translation.
+ */
+#define PWM_FROM_CNT(cnt)       (0xff - (cnt))
+#define PWM_TO_CNT(pwm)         (0xff - (pwm))
+
+/*
+ * Convert count value from fan controller registers (FAN_SET_CNT/FAN_ACT_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, u16 p,
+					u8 clk_div, u8 gear_mult)
+{
+	if (cnt == 0xff)  /* cnt to 255 stops the fan */
+		return 0;
+
+	return (clk * 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, u16 p,
+					 u8 clk_div, u8 gear_mult)
+{
+	if (!rpm)         /* to stop the fan, set cnt to 255 */
+		return 0xff;
+
+	return clamp_val(((clk * 30 * gear_mult)/(rpm * p * clk_div)), 0, 255);
+}
+
+/*
+ * simple wrappers for i2c_smbus_{read,write}_byte_data() providing
+ * additional info on error
+ */
+static inline int g762_read_byte(const struct i2c_client *client, u8 reg,
+				 u8 *dest)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(client, reg);
+	if (ret < 0) {
+		dev_err(&client->dev, "failed to read reg %x02x, err %d\n",
+			(int)reg, ret);
+		return ret;
+	}
+	*dest = ret;
+
+	return 0;
+}
+
+static inline int g762_write_byte(const struct i2c_client *client, u8 reg,
+				  u8 val)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client, reg, val);
+	if (ret < 0)
+		dev_err(&client->dev, "failed to write reg %x02x, err %d\n",
+			(int)reg, ret);
+
+	return ret;
+}
+
+/* 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 = g762_read_byte(client, G762_REG_SET_CNT, &data->set_cnt);
+	if (ret)
+		goto out;
+
+	ret = g762_read_byte(client, G762_REG_ACT_CNT, &data->act_cnt);
+	if (ret < 0)
+		goto out;
+
+	ret = g762_read_byte(client, G762_REG_FAN_STA, &data->fan_sta);
+	if (ret < 0)
+		goto out;
+
+	ret = g762_read_byte(client, G762_REG_SET_OUT, &data->set_out);
+	if (ret < 0)
+		goto out;
+
+	ret = g762_read_byte(client, G762_REG_FAN_CMD1, &data->fan_cmd1);
+	if (ret < 0)
+		goto out;
+
+	ret = g762_read_byte(client, G762_REG_FAN_CMD2, &data->fan_cmd2);
+	if (ret < 0)
+		goto out;
+
+	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 passing hardware characteristics via DT. Some of those
+ * are also used by sysfs handlers (write functions) later in the file.
+ */
+
+/* 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 = -EINVAL;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	mutex_lock(&data->update_lock);
+	switch (val) {
+	case OUT_MODE_PWM:
+		data->fan_cmd1 |=  G762_REG_FAN_CMD1_OUT_MODE;
+		break;
+	case OUT_MODE_DC:
+		data->fan_cmd1 &= ~G762_REG_FAN_CMD1_OUT_MODE;
+		break;
+	default:
+		goto out;
+	}
+	ret = g762_write_byte(client, G762_REG_FAN_CMD1, data->fan_cmd1);
+	data->valid = false;
+ out:
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/*
+ * Set reference clock. Accepted values are between 0 and 0xffffff.
+ * Note that this is a characteristics of the system but an internal
+ * parameter, i.e. value is not passed to the device.
+ */
+static int do_set_pwm_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;
+
+	data->clk = val;
+
+	return 0;
+}
+
+/* Set fan clock divisor. Accepted values are 1, 2, 4 and 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 = -EINVAL;
+
+	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:
+		goto out;
+	}
+	ret = g762_write_byte(client, G762_REG_FAN_CMD1, data->fan_cmd1);
+	data->valid = false;
+ out:
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/* Set fan gear mode. Accepted values are 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 = -EINVAL;
+
+	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:
+		goto out;
+	}
+	ret = g762_write_byte(client, G762_REG_FAN_CMD2, data->fan_cmd2);
+	data->valid = false;
+ out:
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/* Set pulse per revolution value. 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 = -EINVAL;
+
+	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:
+		goto out;
+	}
+	ret = g762_write_byte(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 = -EINVAL;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	mutex_lock(&data->update_lock);
+	switch (val) {
+	case FAN_MODE_OPEN_LOOP:
+		data->fan_cmd1 &= ~G762_REG_FAN_CMD1_FAN_MODE;
+		break;
+	case FAN_MODE_CLOSED_LOOP:
+		data->fan_cmd1 |=  G762_REG_FAN_CMD1_FAN_MODE;
+		break;
+	default:
+		goto out;
+	}
+
+	ret = g762_write_byte(client, G762_REG_FAN_CMD1, data->fan_cmd1);
+	data->valid = false;
+ out:
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/* Set PWM polarity (0 for negative duty, 1 for positive 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 = -EINVAL;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	mutex_lock(&data->update_lock);
+	switch (val) {
+	case 0: /* i.e. negative duty */
+		data->fan_cmd1 &= ~G762_REG_FAN_CMD1_PWM_POLARITY;
+		break;
+	case 1: /* i.e. positive duty */
+		data->fan_cmd1 |=  G762_REG_FAN_CMD1_PWM_POLARITY;
+		break;
+	default:
+		goto out;
+	}
+	ret = g762_write_byte(client, G762_REG_FAN_CMD1, data->fan_cmd1);
+	data->valid = false;
+ out:
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/*
+ * Set pwm value. Accepted values are between 0 (stops the fan) and 255 (full
+ * speed). Note that the chip register used for setting the value depends on
+ * current fan mode of the device (closed or open-loop).
+ */
+static int do_set_pwm(struct device *dev, unsigned long val)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct g762_data *data = g762_update_client(dev);
+	int ret;
+	u8 reg;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	if (val > 255)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+	if (data->fan_cmd1 & G762_REG_FAN_CMD1_FAN_MODE) { /* closed-loop */
+		val = PWM_TO_CNT(val);
+		data->set_cnt = val;
+		reg = G762_REG_SET_CNT;
+	} else {                                           /* open-loop */
+		data->set_out = val;
+		reg = G762_REG_SET_OUT;
+	}
+	ret = g762_write_byte(client, reg, (u8)val);
+	data->valid = false;
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/* Set fan RPM value. This only makes sense in closed-loop mode. */
+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 = -EINVAL;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	mutex_lock(&data->update_lock);
+	if (data->fan_cmd1 & G762_REG_FAN_CMD1_FAN_MODE) { /* closed-loop */
+		data->set_cnt = cnt_from_rpm(val, data->clk,
+					     PULSE_FROM_REG(data->fan_cmd1),
+					     CLKDIV_FROM_REG(data->fan_cmd1),
+					     GEARMULT_FROM_REG(data->fan_cmd2));
+		ret = g762_write_byte(client, G762_REG_SET_CNT, data->set_cnt);
+		data->valid = false;
+	}
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/* Enable/disable fan failure detection. Accepted values are 1 and 0. */
+static int do_fan_failure_detection_toggle(struct device *dev,
+					   unsigned long enable)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct g762_data *data = g762_update_client(dev);
+	int ret = -EINVAL;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	mutex_lock(&data->update_lock);
+	switch (enable) {
+	case 0:
+		data->fan_cmd1 &= ~G762_REG_FAN_CMD1_DET_FAN_FAIL;
+		break;
+	case 1:
+		data->fan_cmd1 |=  G762_REG_FAN_CMD1_DET_FAN_FAIL;
+		break;
+	default:
+		goto out;
+	}
+	ret = g762_write_byte(client, G762_REG_FAN_CMD1, data->fan_cmd1);
+	data->valid = false;
+ out:
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/* Enable/disable fan out of control detection. Accepted values are 1 and 0 */
+static int do_fan_ooc_detection_toggle(struct device *dev, unsigned int enable)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct g762_data *data = g762_update_client(dev);
+	int ret = -EINVAL;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	mutex_lock(&data->update_lock);
+	switch (enable) {
+	case 0:
+		data->fan_cmd1 &= ~G762_REG_FAN_CMD1_DET_FAN_OOC;
+		break;
+	case 1:
+		data->fan_cmd1 |=  G762_REG_FAN_CMD1_DET_FAN_OOC;
+		break;
+	default:
+		goto out;
+	}
+	ret = g762_write_byte(client, G762_REG_FAN_CMD1, data->fan_cmd1);
+ out:
+	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 = -EINVAL;
+
+	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;
+		data->valid = false;
+		break;
+	case 1:
+		data->fan_cmd2 |=  G762_REG_FAN_CMD2_FAN_STARTV_0;
+		data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_1;
+		data->valid = false;
+		break;
+	case 2:
+		data->fan_cmd2 &= ~G762_REG_FAN_CMD2_FAN_STARTV_0;
+		data->fan_cmd2 |=  G762_REG_FAN_CMD2_FAN_STARTV_1;
+		data->valid = false;
+		break;
+	case 3:
+		data->fan_cmd2 |=  G762_REG_FAN_CMD2_FAN_STARTV_0;
+		data->fan_cmd2 |=  G762_REG_FAN_CMD2_FAN_STARTV_1;
+		data->valid = false;
+		break;
+	default:
+		goto out;
+	}
+	ret = g762_write_byte(client, G762_REG_FAN_CMD2, data->fan_cmd2);
+	data->valid = false;
+ out:
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/*
+ * Configuration-related definitions
+ */
+
+static inline int g762_one_prop_commit(struct i2c_client *client,
+				       u32 pval, const char *pname,
+				       int (*psetter)(struct device *dev,
+						      unsigned long val))
+{
+	int ret = 0;
+
+	if (pval & G762_VAL_TEST_BIT) {
+		pval &= G762_VAL_TEST_MASK; /* recover val (clear test bit) */
+		if ((*psetter)(&client->dev, pval)) {
+			dev_err(&client->dev, "unable to set %s (%d)\n",
+				pname, pval);
+			ret = -EINVAL;
+		}
+	}
+
+	return ret;
+}
+
+static int g762_platform_data_commit(struct i2c_client *client,
+				     struct g762_platform_data *pdata)
+{
+	int ret;
+
+	ret = g762_one_prop_commit(client, pdata->fan_startv,
+				   "fan_startv", do_set_fan_startv);
+	if (ret < 0)
+		return ret;
+
+	ret = g762_one_prop_commit(client, pdata->fan_div,
+				   "fan_div", do_set_fan_div);
+	if (ret < 0)
+		return ret;
+
+	ret = g762_one_prop_commit(client, pdata->fan_pulses,
+				   "fan_pulses", do_set_fan_pulses);
+	if (ret < 0)
+		return ret;
+
+	ret = g762_one_prop_commit(client, pdata->pwm_freq,
+				   "pwm_freq", do_set_pwm_freq);
+	if (ret < 0)
+		return ret;
+
+	ret = g762_one_prop_commit(client, pdata->fan_gear_mode,
+				   "fan_gear_mode", do_set_fan_gear_mode);
+	if (ret < 0)
+		return ret;
+
+	ret = g762_one_prop_commit(client, pdata->pwm_polarity,
+				   "pwm_polarity", do_set_pwm_polarity);
+	if (ret < 0)
+		return ret;
+
+	ret = g762_one_prop_commit(client, pdata->pwm_mode,
+				   "pwm_mode", do_set_pwm_mode);
+	if (ret < 0)
+		return ret;
+
+	ret = g762_one_prop_commit(client, pdata->pwm_enable,
+				   "pwm_enable", do_set_pwm_enable);
+	if (ret < 0)
+		return ret;
+
+	ret = g762_one_prop_commit(client, pdata->fan_target,
+				   "fan_target", do_set_fan_target);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * Helpers to import hardware characteristics from .dts file and overload
+ * default platform data values.
+ */
+
+#ifdef CONFIG_OF
+static struct of_device_id g762_dt_match[] = {
+	{ .compatible = "gmt,g762" },
+	{ .compatible = "gmt,g763" },
+	{ },
+};
+
+static inline void g762_of_import_one_prop(struct i2c_client *client,
+					   u32 *dest, const char *pname)
+{
+	const __be32 *prop;
+	int len;
+
+	prop = of_get_property(client->dev.of_node, pname, &len);
+	if (prop && len == sizeof(u32)) {
+		*dest = G762_ATTR_VAL(be32_to_cpu(prop[0]));
+		dev_dbg(&client->dev, "found %s (%d)\n", pname, *dest);
+	}
+}
+
+static void g762_platform_data_of_overload(struct i2c_client *client,
+					   struct g762_platform_data *pdata)
+{
+	if (!client->dev.of_node)
+		return;
+
+	g762_of_import_one_prop(client, &pdata->fan_startv, "fan_startv");
+	g762_of_import_one_prop(client, &pdata->fan_gear_mode, "fan_gear_mode");
+	g762_of_import_one_prop(client, &pdata->fan_div, "fan_div");
+	g762_of_import_one_prop(client, &pdata->fan_pulses, "fan_pulses");
+	g762_of_import_one_prop(client, &pdata->fan_target, "fan_target");
+	g762_of_import_one_prop(client, &pdata->pwm_polarity, "pwm_polarity");
+	g762_of_import_one_prop(client, &pdata->pwm_enable, "pwm_enable");
+	g762_of_import_one_prop(client, &pdata->pwm_freq, "pwm_freq");
+	g762_of_import_one_prop(client, &pdata->pwm_mode, "pwm_mode");
+}
+#else
+static void g762_platform_data_of_overload(struct i2c_client *client,
+				    struct g762_platform_data *pdata) { }
+#endif
+
+/*
+ * helper to overload driver parameters from board init file for those
+ * not already converted to device tree
+ */
+static void g762_platform_data_overload(struct i2c_client *client,
+					struct g762_platform_data *pdata)
+{
+	struct g762_platform_data *opdata = client->dev.platform_data;
+
+	if (!opdata)
+		return;
+
+	if (opdata->fan_startv & G762_VAL_TEST_BIT)
+		pdata->fan_startv = opdata->fan_startv;
+	if (opdata->fan_gear_mode & G762_VAL_TEST_BIT)
+		pdata->fan_gear_mode = opdata->fan_gear_mode;
+	if (opdata->fan_div & G762_VAL_TEST_BIT)
+		pdata->fan_div = opdata->fan_div;
+	if (opdata->fan_pulses & G762_VAL_TEST_BIT)
+		pdata->fan_pulses = opdata->fan_pulses;
+	if (opdata->fan_target & G762_VAL_TEST_BIT)
+		pdata->fan_target = opdata->fan_target;
+	if (opdata->pwm_polarity & G762_VAL_TEST_BIT)
+		pdata->pwm_polarity = opdata->pwm_polarity;
+	if (opdata->pwm_enable & G762_VAL_TEST_BIT)
+		pdata->pwm_enable = opdata->pwm_enable;
+	if (opdata->pwm_freq & G762_VAL_TEST_BIT)
+		pdata->pwm_freq = opdata->pwm_freq;
+	if (opdata->pwm_mode & G762_VAL_TEST_BIT)
+		pdata->pwm_mode = opdata->pwm_mode;
+}
+
+/*
+ * sysfs attributes
+ */
+
+/*
+ * Read function for fan1_input sysfs file. Return current fan RPM value, or
+ * 0 if fan is out of control. The function is available in both closed and
+ * open-loop mode.
+ */
+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,
+				   PULSE_FROM_REG(data->fan_cmd1),
+				   CLKDIV_FROM_REG(data->fan_cmd1),
+				   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", 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 pwm1_enable. Get and set the 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@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 "no-control" mode is not supported by g762,
+ * -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 the fan speed
+ * in both open and closed-loop mode. Speed is given as a relative value
+ * between 0 and 255, where 255 is maximum speed. 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_pwm(struct device *dev, struct device_attribute *da,
+		       char *buf)
+{
+	struct g762_data *data = g762_update_client(dev);
+	int val;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	mutex_lock(&data->update_lock);
+	if (data->fan_cmd1 & G762_REG_FAN_CMD1_FAN_MODE) /* closed-loop */
+		val = PWM_FROM_CNT(data->set_cnt);
+	else                                             /* open-loop */
+		val = data->set_out;
+	mutex_unlock(&data->update_lock);
+
+	return sprintf(buf, "%d\n", val);
+}
+
+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. The functions both return
+ * -EINVAL when called in open-loop mode.
+ *
+ * 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);
+	ssize_t ret = -EINVAL;
+	unsigned int rpm;
+
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	mutex_lock(&data->update_lock);
+	if (data->fan_cmd1 & G762_REG_FAN_CMD1_FAN_MODE) { /* closed-loop */
+		rpm = rpm_from_cnt(data->set_cnt, data->clk,
+				   PULSE_FROM_REG(data->fan_cmd1),
+				   CLKDIV_FROM_REG(data->fan_cmd1),
+				   GEARMULT_FROM_REG(data->fan_cmd2));
+		ret = sprintf(buf, "%u\n", rpm);
+	}
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+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);
+
+/* 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_pwm1.attr,
+	&dev_attr_pwm1_mode.attr,
+	&dev_attr_pwm1_enable.attr,
+	NULL
+};
+
+static const struct attribute_group g762_group = {
+	.attrs = g762_attributes,
+};
+
+static int g762_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct g762_platform_data pdata = { .pwm_freq = G762_ATTR_VAL(32768) };
+	struct g762_data *data;
+	int err;
+
+	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 protection and fan fail detection by default */
+	do_fan_ooc_detection_toggle(&client->dev, 1);
+	do_fan_failure_detection_toggle(&client->dev, 1);
+
+	/*
+	 * Set default configuration values before passing the structure
+	 * first to overload routine used by boards non converted to DT and
+	 * then to OF helper (to overload them using those provided by .dts
+	 * file, if any). Final result is then commited.
+	 */
+	g762_platform_data_overload(client, &pdata);
+	g762_platform_data_of_overload(client, &pdata);
+	err = g762_platform_data_commit(client, &pdata);
+	if (err)
+		return err;
+
+	/* Register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &g762_group);
+	if (err)
+		return err;
+
+	data->hwmon_dev = (struct device *)hwmon_device_register(&client->dev);
+	if (IS_ERR(data->hwmon_dev)) {
+		err = PTR_ERR(data->hwmon_dev);
+		goto err_out;
+	}
+
+	return 0;
+
+ err_out:
+	sysfs_remove_group(&client->dev.kobj, &g762_group);
+	return err;
+}
+
+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);
+
+	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");
diff --git a/include/linux/platform_data/g762.h b/include/linux/platform_data/g762.h
new file mode 100644
index 0000000..a1f9e05
--- /dev/null
+++ b/include/linux/platform_data/g762.h
@@ -0,0 +1,51 @@
+/*
+ * 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. Attributes must *always* be set using G762_ATTR_VAL()
+ * macro defined below, i.e. it is not possible to pass a value directly.
+ * Defining a sparse structure is possible. For instance, if one only needs
+ * to set pwm_freq to 8192 and fan_pulses to 2, the following can be done:
+ *
+ * static struct g762_platform_data pdata = { .pwm_freq = G762_ATTR_VAL(8192),
+ *                                            .fan_pulses = G762_ATTR_VAL(2), };
+ *
+ */
+
+#define G762_VAL_TEST_BIT  (1 << 31)
+#define G762_VAL_TEST_MASK (~G762_VAL_TEST_BIT)
+#define G762_ATTR_VAL(val) ((val) | G762_VAL_TEST_BIT)
+
+struct g762_platform_data {
+	u32 fan_startv;
+	u32 fan_gear_mode;
+	u32 fan_div;
+	u32 fan_pulses;
+	u32 fan_target;
+	u32 pwm_polarity;
+	u32 pwm_enable;
+	u32 pwm_freq;
+	u32 pwm_mode;
+};
+
+#endif /* __LINUX_PLATFORM_DATA_G762_H__ */
-- 
1.7.10.4

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

* [PATCHv3 2/3] Add documentation for g762 driver
  2013-06-02 20:14 [PATCHv3 0/3] Add G762/G763 PWM fan controller Arnaud Ebalard
  2013-06-02 20:14 ` [PATCHv3 1/3] Add support for GMT " Arnaud Ebalard
@ 2013-06-02 20:14 ` Arnaud Ebalard
  2013-06-02 20:14 ` [PATCHv3 3/3] Add DT bindings " Arnaud Ebalard
  2013-06-03  2:30 ` [PATCHv3 0/3] Add G762/G763 PWM fan controller Guenter Roeck
  3 siblings, 0 replies; 8+ messages in thread
From: Arnaud Ebalard @ 2013-06-02 20:14 UTC (permalink / raw)
  To: linux-arm-kernel


Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
 Documentation/hwmon/g762 |   64 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)
 create mode 100644 Documentation/hwmon/g762

diff --git a/Documentation/hwmon/g762 b/Documentation/hwmon/g762
new file mode 100644
index 0000000..b749fe1
--- /dev/null
+++ b/Documentation/hwmon/g762
@@ -0,0 +1,64 @@
+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.
+
+
+  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_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 is also usable).
+
+  pwm1_mode: set or get fan driving mode: 1 for PWM mode, 0 for DC mode.
+
+  pwm1: get or set PWM fan control value. This is an integer value
+            between 0 and 255. 0 stops the fan, 255 makes it run at
+            full speed. Note that the fan speed control mode (open vs
+            closed-loop) must be set prior via pwm1_enable prior to
+            passing a value via pwm1.
+
+
+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.
-- 
1.7.10.4

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

* [PATCHv3 3/3] Add DT bindings documentation for g762 driver
  2013-06-02 20:14 [PATCHv3 0/3] Add G762/G763 PWM fan controller Arnaud Ebalard
  2013-06-02 20:14 ` [PATCHv3 1/3] Add support for GMT " Arnaud Ebalard
  2013-06-02 20:14 ` [PATCHv3 2/3] Add documentation for g762 driver Arnaud Ebalard
@ 2013-06-02 20:14 ` Arnaud Ebalard
  2013-06-03  2:30 ` [PATCHv3 0/3] Add G762/G763 PWM fan controller Guenter Roeck
  3 siblings, 0 replies; 8+ messages in thread
From: Arnaud Ebalard @ 2013-06-02 20:14 UTC (permalink / raw)
  To: linux-arm-kernel


Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
---
 Documentation/devicetree/bindings/hwmon/g762.txt |   58 ++++++++++++++++++++++
 1 file changed, 58 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/g762.txt

diff --git a/Documentation/devicetree/bindings/hwmon/g762.txt b/Documentation/devicetree/bindings/hwmon/g762.txt
new file mode 100644
index 0000000..3f7ccbf
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/g762.txt
@@ -0,0 +1,58 @@
+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
+
+Optional properties:
+
+ - "pwm_mode": fan driving mode. 1 for PWM mode, 0 for DC mode.
+
+ - "pwm_enable": fan speed control. 1 for open-loop, 2 for closed-loop.
+
+ - "pwm_freq": reference clock frequency for PWM mode in Hz. If not set,
+               default value is 32768.
+
+ - "fan_pulses": number of pulses per fan revolution. Supported values
+               are 2 and 4.
+
+ - "fan_div": fan clock frequency divisor value. Supported values are 1,
+               2, 4 and 8. Default is 1.
+
+ - "fan_target": initial target fan speed in RPM. Only works in closed-loop
+               fan speed control, i.e. when pwm_enable has already been set
+               to 2.
+
+ - "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:
+
+   g762: g762 at 3e {
+	compatible = "gmt,g762";
+	reg = <0x3e>;
+	pwm_mode = <1>;      /* closed-loop control */
+	pwm_enable = <2>;    /* PWM mode */
+	pwm_freq = <8192>;   /* PWM reference clock freq */
+	fan_pulses = <2>;    /* 2 pulses per rev */
+	fan_div = <2>;       /* fan clock divisor */
+	fan_target = <2000>; /* target fan speed at 2000 RPM */
+	fan_gear_mode = <0>; /* chip default */
+	fan_startv = <1>;    /* chip default */
+	pwm_polarity = <0>;  /* chip default */
+   };
+
-- 
1.7.10.4

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

* [PATCHv3 0/3] Add G762/G763 PWM fan controller
  2013-06-02 20:14 [PATCHv3 0/3] Add G762/G763 PWM fan controller Arnaud Ebalard
                   ` (2 preceding siblings ...)
  2013-06-02 20:14 ` [PATCHv3 3/3] Add DT bindings " Arnaud Ebalard
@ 2013-06-03  2:30 ` Guenter Roeck
  2013-06-03 14:15   ` Jason Cooper
  3 siblings, 1 reply; 8+ messages in thread
From: Guenter Roeck @ 2013-06-03  2:30 UTC (permalink / raw)
  To: linux-arm-kernel

On Sun, Jun 02, 2013 at 10:14:05PM +0200, Arnaud Ebalard wrote:
> Hi,
> 
> This series adds support for GMT G762/G763. This work is based on a
> basic version for 2.6.31 kernel developed Olivier Mouchet for LaCie
> NAS. Updates have been performed to run on recent kernels. Support has
> been completed and additional features added: ability to configure
> various characteristics from .dts file, better initialization, alarms
> and error reporting support, gear mode, polarity, fan pulse per
> revolution, fan startup voltage control. The following detailed
> datasheet has been used as a basis for this work:
> 
>   http://natisbad.org/NAS/refs/GMT_EDS-762_763-080710-0.2.pdf
> 
> The patch was developed for and tested against the GMT G762 fan
> controller used in a Netgear ReadyNAS Duo v2 (kirkwood 88F6282-based
> NAS). This is the main reason for the device tree bindings provided in
> first patch. The driver also support init via board file. The patches
> are against current ARM tree; tell me if you need me to rebase it
> against something else. Patch 2 and 3 provides documentation for the
> driver and DT bindings, respectively.
> 
> As discussed in a previous email:
> 
>  - A macro is defined to pass values of platform data structure in
>    board setup files. If one has a better approach ...
>  - This v3 still has support for pwm1 sysfs entry in closed-loop mode.
>    Guenter, if you still think it is a bad idea, I will change that
>    in v4.
> 

If you want me to accept your code, I would suggest to change it such that
pwm1 sets the FAN_SET_OUT register, and fan1_target sets the SET_CNT register,
both unconditionally. Redefining the pwm1 attribute to set the fan target
speed instead of a pwm / voltage value is not acceptable.

> note: this should not be an issue but this v3 is based on
>       jcooper/mvebu/fixes branch. 
> 
I don't know about this branch/repository. If there are hwmon fixes in it,
they should be submitted to the lm-sensors mailing list.

Thanks,
Guenter

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

* [PATCHv3 1/3] Add support for GMT G762/G763 PWM fan controller
  2013-06-02 20:14 ` [PATCHv3 1/3] Add support for GMT " Arnaud Ebalard
@ 2013-06-03  2:34   ` Guenter Roeck
  2013-06-04  6:54     ` Arnaud Ebalard
  0 siblings, 1 reply; 8+ messages in thread
From: Guenter Roeck @ 2013-06-03  2:34 UTC (permalink / raw)
  To: linux-arm-kernel

On Sun, Jun 02, 2013 at 10:14:19PM +0200, Arnaud Ebalard wrote:
> 
> Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
> ---
>  drivers/hwmon/Kconfig              |   10 +
>  drivers/hwmon/Makefile             |    1 +
>  drivers/hwmon/g762.c               | 1179 ++++++++++++++++++++++++++++++++++++
>  include/linux/platform_data/g762.h |   51 ++
>  4 files changed, 1241 insertions(+)
>  create mode 100644 drivers/hwmon/g762.c
>  create mode 100644 include/linux/platform_data/g762.h
> 
[ ... ]

> +static inline int g762_read_byte(const struct i2c_client *client, u8 reg,
> +				 u8 *dest)
> +{
> +	int ret;
> +
> +	ret = i2c_smbus_read_byte_data(client, reg);
> +	if (ret < 0) {
> +		dev_err(&client->dev, "failed to read reg %x02x, err %d\n",
> +			(int)reg, ret);

I would suggest to write a piece of test code for the above statement and
observe its output. "printf("%x02x\n", 0x1234);" should do.

Is the error message really needed ? If there is a persistent error,
it will clog the log.

Thanks,
Guenter

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

* [PATCHv3 0/3] Add G762/G763 PWM fan controller
  2013-06-03  2:30 ` [PATCHv3 0/3] Add G762/G763 PWM fan controller Guenter Roeck
@ 2013-06-03 14:15   ` Jason Cooper
  0 siblings, 0 replies; 8+ messages in thread
From: Jason Cooper @ 2013-06-03 14:15 UTC (permalink / raw)
  To: linux-arm-kernel

On Sun, Jun 02, 2013 at 07:30:53PM -0700, Guenter Roeck wrote:
> On Sun, Jun 02, 2013 at 10:14:05PM +0200, Arnaud Ebalard wrote:
...
> > note: this should not be an issue but this v3 is based on
> >       jcooper/mvebu/fixes branch. 
> > 
> I don't know about this branch/repository. If there are hwmon fixes in it,
> they should be submitted to the lm-sensors mailing list.

Yes, please, unless you *need* a patch to build/boot that isn't in
mainline yet, always base off of one of Linus' tags, eg v3.10-rc4.  Then,
if asked, rebase as needed.

thx,

Jason.

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

* [PATCHv3 1/3] Add support for GMT G762/G763 PWM fan controller
  2013-06-03  2:34   ` Guenter Roeck
@ 2013-06-04  6:54     ` Arnaud Ebalard
  0 siblings, 0 replies; 8+ messages in thread
From: Arnaud Ebalard @ 2013-06-04  6:54 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

Guenter Roeck <linux@roeck-us.net> writes:

> On Sun, Jun 02, 2013 at 10:14:19PM +0200, Arnaud Ebalard wrote:
>> 
>> Signed-off-by: Arnaud Ebalard <arno@natisbad.org>
>> ---
>>  drivers/hwmon/Kconfig              |   10 +
>>  drivers/hwmon/Makefile             |    1 +
>>  drivers/hwmon/g762.c               | 1179 ++++++++++++++++++++++++++++++++++++
>>  include/linux/platform_data/g762.h |   51 ++
>>  4 files changed, 1241 insertions(+)
>>  create mode 100644 drivers/hwmon/g762.c
>>  create mode 100644 include/linux/platform_data/g762.h
>> 
> [ ... ]
>
>> +static inline int g762_read_byte(const struct i2c_client *client, u8 reg,
>> +				 u8 *dest)
>> +{
>> +	int ret;
>> +
>> +	ret = i2c_smbus_read_byte_data(client, reg);
>> +	if (ret < 0) {
>> +		dev_err(&client->dev, "failed to read reg %x02x, err %d\n",
>> +			(int)reg, ret);
>
> I would suggest to write a piece of test code for the above statement and
> observe its output. "printf("%x02x\n", 0x1234);" should do.

good catch. s/%x02x/0x%02x/.

> Is the error message really needed ? If there is a persistent error,
> it will clog the log.

I removed it in v4 to come.

Cheers,

a+

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

end of thread, other threads:[~2013-06-04  6:54 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-06-02 20:14 [PATCHv3 0/3] Add G762/G763 PWM fan controller Arnaud Ebalard
2013-06-02 20:14 ` [PATCHv3 1/3] Add support for GMT " Arnaud Ebalard
2013-06-03  2:34   ` Guenter Roeck
2013-06-04  6:54     ` Arnaud Ebalard
2013-06-02 20:14 ` [PATCHv3 2/3] Add documentation for g762 driver Arnaud Ebalard
2013-06-02 20:14 ` [PATCHv3 3/3] Add DT bindings " Arnaud Ebalard
2013-06-03  2:30 ` [PATCHv3 0/3] Add G762/G763 PWM fan controller Guenter Roeck
2013-06-03 14:15   ` Jason Cooper

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