From: Luca Lauro <[email protected]>

Signed-off-by: Luca Lauro <[email protected]>
---
 arch/arm/boards/netgear-rn102/board.c      |   74 +-
 arch/arm/boards/netgear-rn104/board.c      |   89 +--
 drivers/Kconfig                            |    1 +
 drivers/Makefile                           |    1 +
 drivers/fan/Kconfig                        |   35 +
 drivers/fan/Makefile                       |    8 +
 drivers/fan/fan.c                          |  180 +++++
 drivers/fan/i2c_fan_controllers/Kconfig    |    6 +
 drivers/fan/i2c_fan_controllers/Makefile   |    5 +
 drivers/fan/i2c_fan_controllers/g76x.c     | 1184 ++++++++++++++++++++++++++++
 drivers/fan/other_fan_controllers/Kconfig  |    7 +
 drivers/fan/other_fan_controllers/Makefile |    3 +
 drivers/fan/spi_fan_controllers/Kconfig    |    1 +
 drivers/fan/spi_fan_controllers/Makefile   |    3 +
 include/fan/fan.h                          |   94 +++
 include/fan/i2c_fan_controllers/g76x.h     |   37 +
 16 files changed, 1641 insertions(+), 87 deletions(-)

diff --git a/arch/arm/boards/netgear-rn102/board.c 
b/arch/arm/boards/netgear-rn102/board.c
index c6673e9356..12c1833dad 100644
--- a/arch/arm/boards/netgear-rn102/board.c
+++ b/arch/arm/boards/netgear-rn102/board.c
@@ -4,6 +4,7 @@
 #include <init.h>
 #include <gpio.h>
 #include <driver.h>
+#include <of.h>
 #include <linux/device.h>
 #include <linux/mbus.h>
 #include <mach/mvebu/armada-370-xp-regs.h>
@@ -68,8 +69,8 @@ static void gpio0_blink(int pin, int on_ms, int off_ms)
     gpio0_set_output(pin);
 
     /* set blink counter A on and off time in core clok cycles */
-    writel(10*on_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_ON);
-    writel(10*off_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_OFF);
+    writel(10 * on_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_ON);
+    writel(10 * off_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_OFF);
 
     /* use blink counter A for selected pin */
     v = readl(GPIO0_BASE + GPIO_BLINK_CNT_SEL);
@@ -113,7 +114,8 @@ static struct rn102_disk_bay 
rn102_bays[RN102_NUM_DISK_BAYS] = {
     { 10, 11, 14, DISK_ABSENT }, /* Bay 2, default to disk absent */
 };
 
-static void setup_bays(void) {
+static void setup_bays(void)
+{
     pr_info("Early disk power-on...\n");
 
     for (int i = 0; i < RN102_NUM_DISK_BAYS; i++) {
@@ -142,7 +144,8 @@ static void setup_bays(void) {
  * After waiting for present disks spinup,
  * we ask explicitly for corresponding ATA devices probe.
  */
-static void init_disks(void) {
+static void init_disks(void)
+{
     pr_info("Waiting for disks spinup...\n");
     mdelay(8000);
 
@@ -182,50 +185,34 @@ static void init_disks(void) {
  * frontal USB 2.0 port of RN102 is connected to the SoC usb0.
  * Here we enable MBUS windows toward all the DRAM using
  * informations already gathered by mvebu_mbus_dram_info().
+ * 
+ * NOTE: Armada 370-XP EHCI controller shows instability when the periodic
+ * schedule is enabled.
  */
-// #define USB0_BRIDGE_BASE   (ARMADA_370_XP_USB_BASE + 0x300)
-// #define USB_WIN_CTRL(n)    (USB0_BRIDGE_BASE + 0x20 + (n) * 0x10)
-// #define USB_WIN_BASE(n)    (USB0_BRIDGE_BASE + 0x24 + (n) * 0x10)
-
-static void setup_usb0(void) {
-    // /* Window0: 512MB @ 0x00000000 */
-    // writel(0x00000000, USB_WIN_BASE(0));
-    // writel(0x1FFF0E01, USB_WIN_CTRL(0));
-
-    // /* Window1: 512MB @ 0xB0000000 (placeholder) */
-    // writel(0xB0000000, USB_WIN_BASE(1));
-    // writel(0x1FFE841, USB_WIN_CTRL(1));
-
+static void setup_usb0(void)
+{
     writel(0x2, 0xf1051404);    /* enable force suspend */
     u32 pwr = readl(0xf1051400);
     pwr &= ~BIT(2);
     writel(pwr, 0xf1051400);    /* force SUSPENDM=0 */
 }
 
-
-/* Early init: setup specific hardware without blocking initialization. */
-static int rn102_early_poweron(void)
+/*
+ * RN102 board driver
+ *
+ * Preferred pattern: board-specific code as a platform driver
+ * matching on the root DT node ("netgear,rn102").
+ */
+static int rn102_probe(struct device *dev)
 {
-    writel(0xC6, 0xf1020228); /* CFU configuration */
+    /* CFU configuration */
+    writel(0xC6, 0xf1020228);
+
     setup_usb0();
     setup_bays();
-
-    return 0;
-}
-postcore_initcall(rn102_early_poweron);
-
-static int rn102_init(void)
-{
     init_disks();
 
-    return 0;
-}
-device_initcall(rn102_init);
-
-
-/* BareBox Update handlers */
-static int rn102_register_bbu(void)
-{
+    /* Barebox Update handlers */
     bbu_register_std_file_update("bootloader", 0,
                      "/dev/nand0.bootloader",
                      filetype_kwbimage_v1);
@@ -240,4 +227,17 @@ static int rn102_register_bbu(void)
 
     return 0;
 }
-late_initcall(rn102_register_bbu);
+
+static const struct of_device_id rn102_of_match[] = {
+    { .compatible = "netgear,rn102" },
+    { }
+};
+MODULE_DEVICE_TABLE(of, rn102_of_match);
+
+static struct driver rn102_driver = {
+    .name = "rn102",
+    .probe = rn102_probe,
+    .of_match_table = rn102_of_match,
+};
+
+device_platform_driver(rn102_driver);
diff --git a/arch/arm/boards/netgear-rn104/board.c 
b/arch/arm/boards/netgear-rn104/board.c
index 29c112ec9e..637e4b07e3 100644
--- a/arch/arm/boards/netgear-rn104/board.c
+++ b/arch/arm/boards/netgear-rn104/board.c
@@ -4,21 +4,17 @@
 #include <init.h>
 #include <gpio.h>
 #include <driver.h>
+#include <of.h>
 #include <linux/device.h>
 #include <linux/mbus.h>
 #include <mach/mvebu/armada-370-xp-regs.h>
 #include <bbu.h>
 
 /*
- *  Early GPIO0 MMIO
- *
- * GPIO driver arrives too late for the disks to be
- * ready in time for AHCI driver probe.
- * So we use GPIO0 direct access for:
- *   - reading disk presence monitoring pins
- *   - enable powerup for bays that detect disk presence
- *   - display bay status with dedicated LEDs
+ * RN104 bay management is PCA9554-based and currently unimplemented.
+ * Placeholder logic is kept for future extension.
  */
+
 #define GPIO0_BASE           (ARMADA_370_XP_INT_REGS_BASE + 0x18100)
 #define GPIO_OUT             0x00
 #define GPIO_OUT_EN          0x04
@@ -68,8 +64,8 @@ static void gpio0_blink(int pin, int on_ms, int off_ms)
     gpio0_set_output(pin);
 
     /* set blink counter A on and off time in core clok cycles */
-    writel(10*on_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_ON);
-    writel(10*off_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_OFF);
+    writel(10 * on_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_ON);
+    writel(10 * off_ms, GPIO0_BASE + GPIO_BLINK_CNT_A_OFF);
 
     /* use blink counter A for selected pin */
     v = readl(GPIO0_BASE + GPIO_BLINK_CNT_SEL);
@@ -106,9 +102,9 @@ struct rn104_disk_bay {
     enum disk_state state;
 };
 
-#define rn104_NUM_DISK_BAYS 1
+#define RN104_NUM_DISK_BAYS 2
 
-static struct rn104_disk_bay rn104_bays[rn104_NUM_DISK_BAYS] = {
+static struct rn104_disk_bay rn104_bays[RN104_NUM_DISK_BAYS] = {
     { -1, -1, -1, DISK_ABSENT }, /* placeholder */
 };
 
@@ -117,10 +113,6 @@ static void setup_bays(void)
     pr_info("RN104: bay management not implemented (PCA9554-based)\n");
 }
 
-/*
- * After waiting for present disks spinup,
- * we ask explicitly for corresponding ATA devices probe.
- */
 static void init_disks(void)
 {
     pr_info("RN104: disk spin-up logic not implemented\n");
@@ -129,53 +121,37 @@ static void init_disks(void)
 /*
  *  USB0 → DRAM MBUS windows
  *
- * frontal USB 2.0 port of rn104 is connected to the SoC usb0.
+ * frontal USB 2.0 port of RN104 is connected to the SoC usb0.
  * Here we enable MBUS windows toward all the DRAM using
  * informations already gathered by mvebu_mbus_dram_info().
+ *
+ * NOTE: Armada 370-XP EHCI controller shows instability when the periodic
+ * schedule is enabled.
  */
-// #define USB0_BRIDGE_BASE   (ARMADA_370_XP_USB_BASE + 0x300)
-// #define USB_WIN_CTRL(n)    (USB0_BRIDGE_BASE + 0x20 + (n) * 0x10)
-// #define USB_WIN_BASE(n)    (USB0_BRIDGE_BASE + 0x24 + (n) * 0x10)
-
-static void setup_usb0(void) {
-    // /* Window0: 512MB @ 0x00000000 */
-    // writel(0x00000000, USB_WIN_BASE(0));
-    // writel(0x1FFF0E01, USB_WIN_CTRL(0));
-
-    // /* Window1: 512MB @ 0xB0000000 (placeholder) */
-    // writel(0xB0000000, USB_WIN_BASE(1));
-    // writel(0x1FFE841, USB_WIN_CTRL(1));
-
+static void setup_usb0(void)
+{
     writel(0x2, 0xf1051404);    /* enable force suspend */
     u32 pwr = readl(0xf1051400);
     pwr &= ~BIT(2);
     writel(pwr, 0xf1051400);    /* force SUSPENDM=0 */
 }
 
-
-/* Early init: setup specific hardware without blocking initialization. */
-static int rn104_early_poweron(void)
+/*
+ * RN104 board driver
+ *
+ * Preferred pattern: board-specific code as a platform driver
+ * matching on the root DT node ("netgear,rn104").
+ */
+static int rn104_probe(struct device *dev)
 {
-    writel(0xC6, 0xf1020228); /* CFU configuration */
+    /* CFU configuration */
+    writel(0xC6, 0xf1020228);
+
     setup_usb0();
     setup_bays();
-
-    return 0;
-}
-postcore_initcall(rn104_early_poweron);
-
-static int rn104_init(void)
-{
     init_disks();
 
-    return 0;
-}
-device_initcall(rn104_init);
-
-
-/* BareBox Update handlers */
-static int rn104_register_bbu(void)
-{
+    /* Barebox Update handlers */
     bbu_register_std_file_update("bootloader", 0,
                      "/dev/nand0.bootloader",
                      filetype_kwbimage_v1);
@@ -190,4 +166,17 @@ static int rn104_register_bbu(void)
 
     return 0;
 }
-late_initcall(rn104_register_bbu);
+
+static const struct of_device_id rn104_of_match[] = {
+    { .compatible = "netgear,rn104" },
+    { }
+};
+MODULE_DEVICE_TABLE(of, rn104_of_match);
+
+static struct driver rn104_driver = {
+    .name = "rn104",
+    .probe = rn104_probe,
+    .of_match_table = rn104_of_match,
+};
+
+coredevice_platform_driver(rn104_board_driver);
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 32910addcd..b1ba73887a 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -50,5 +50,6 @@ source "drivers/power/Kconfig"
 source "drivers/virtio/Kconfig"
 source "drivers/mailbox/Kconfig"
 source "drivers/tee/Kconfig"
+source "drivers/fan/Kconfig"
 
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index db491dad83..4d6071fdc7 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -51,3 +51,4 @@ obj-$(CONFIG_SOUND) += sound/
 obj-y  += virtio/
 obj-y  += mailbox/
 obj-y  += tee/
+obj-$(CONFIG_FAN) += fan/
diff --git a/drivers/fan/Kconfig b/drivers/fan/Kconfig
new file mode 100644
index 0000000000..7b1e795f01
--- /dev/null
+++ b/drivers/fan/Kconfig
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig FAN
+       bool "Fan controller drivers"
+       help
+         Add support for various fan controllers.
+
+if FAN
+
+menuconfig I2C_FAN
+       bool "i2c fan controllers support"
+       depends on I2C
+       help
+         add support for fan controllers based on i2c interface.
+       if I2C_FAN
+       source "drivers/fan/i2c_fan_controllers/Kconfig"
+       endif
+
+menuconfig SPI_FAN
+       bool "spi fan controllers support"
+       depends on SPI
+       help
+         add support fan controllers based on spi interface.
+       if SPI_FAN
+       source "drivers/fan/spi_fan_controllers/Kconfig"
+       endif
+
+menuconfig OTHER_FAN
+       bool "other fan controllers support"
+       help
+         support other fan control methods.
+       if OTHER_FAN
+       source "drivers/fan/other_fan_controllers/Kconfig"
+       endif
+
+endif
diff --git a/drivers/fan/Makefile b/drivers/fan/Makefile
new file mode 100644
index 0000000000..9ff140c0da
--- /dev/null
+++ b/drivers/fan/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+# fan controller interface types
+
+obj-y += fan.o
+obj-$(CONFIG_I2C_FAN) += i2c_fan_controllers/
+obj-$(CONFIG_SPI_FAN) += spi_fan_controllers/
+obj-$(CONFIG_OTHER_FAN) += other_fan_controllers/
diff --git a/drivers/fan/fan.c b/drivers/fan/fan.c
new file mode 100644
index 0000000000..672cda0a71
--- /dev/null
+++ b/drivers/fan/fan.c
@@ -0,0 +1,180 @@
+#include <fan/fan.h>
+
+/* Generic fan driver fan device management */
+
+LIST_HEAD(fan_device_list);
+
+static void fan_set_dev(struct fan_device *fan, struct device *dev)
+{
+    fan->dev = dev;
+}
+/*
+static struct device *fan_get_dev(struct fan_device *fan)
+{
+    return fan->dev;
+}
+*/
+static void fan_set_ops(struct fan_device *fan, struct fan_ops *ops)
+{
+    fan->ops = ops;
+}
+/*
+static const struct fan_ops *fan_get_ops(struct fan_device *fan)
+{
+    return fan->ops;
+}
+*/
+
+/* Generic filesystem fan device management */
+
+static int fan_idx = 0;
+
+static void cdev_set_fan(struct fan_device *fan)
+{
+    struct cdev *cdev = fan->cdev;
+
+    cdev->priv = fan;
+    cdev->dev  = fan->dev;
+    cdev->ops  = NULL;
+    cdev->size = 0;
+    cdev->flags = 0;
+}
+/*
+static struct fan_device *cdev_get_fan(struct cdev *cdev)
+{
+    return cdev->priv;
+}
+*/
+static int cdev_set_name(struct cdev *cdev)
+{
+    char name[32];
+
+    if (snprintf(name, sizeof(name), "fan%d", fan_idx++) >= sizeof(name))
+        return -EINVAL;
+
+    cdev->name = xstrdup(name);
+    if (!cdev->name)
+        return -ENOMEM;
+
+    return 0;
+}
+/*
+static char *cdev_get_name(struct cdev *cdev)
+{
+    return cdev->name;
+}
+*/
+struct fan_device *fan_device_find(const char *devmatch)
+{
+    struct fan_device *fan = NULL;
+    char devpath[32];
+
+    if (!devmatch || strlen(devmatch) > sizeof(devpath) - sizeof("/dev/"))
+        return ERR_PTR(-EINVAL);
+
+    list_for_each_entry(fan, &fan_device_list, list) {
+        /* search by driver-specific fan controller device */
+        if (fan->dev && fan->dev->name) {
+            snprintf(devpath, sizeof(devpath),
+                        "/dev/%s", fan->dev->name);
+
+            /* by path ("/dev/.../ctrlr name") */
+            if (!strcmp(devpath, devmatch))
+                return fan;
+
+            /* or by name ("ctrlr name") */
+            if (!strcmp(fan->dev->name, devmatch))
+                return fan;
+        }
+        /* search by filesystem generic fan device */
+        if (fan->cdev && fan->cdev->name) {
+            snprintf(devpath, sizeof(devpath),
+                        "/dev/%s", fan->cdev->name);
+            /* by path ("/dev/fanX") or by name ("fanX") */
+            if (!strcmp(devpath, devmatch) ||
+                !strcmp(fan->cdev->name, devmatch))
+                return fan;
+        }
+    }
+
+    return ERR_PTR(-ENODEV);
+}
+
+
+/* specific fan controller driver device management */
+
+int fan_device_register(struct device *dev, struct fan_ops *ops)
+{
+    struct fan_device *fan;
+    int ret;
+
+    if (!dev || !ops)
+        return -EINVAL;
+
+       /* allocate and initialize generic fan device structure */
+    fan = xzalloc(sizeof(*fan));
+    if (!fan)
+        return -ENOMEM;
+
+    fan->cdev = xzalloc(sizeof(*fan->cdev));
+    if (!fan->cdev) {
+        free(fan);
+        return -ENOMEM;
+    }
+
+    fan_set_dev(fan, dev);
+    fan_set_ops(fan, ops);
+    dev->type_data = fan;
+
+    list_add(&fan->list, &fan_device_list);
+
+    cdev_set_fan(fan);
+
+    ret = cdev_set_name(fan->cdev);
+    if (ret)
+        goto err;
+
+    ret = devfs_create(fan->cdev);
+    if (ret)
+        goto err;
+
+    // expose fan parameters as global variables
+    // globalvar_add_simple_int(".clk_freq", (int *)fan->ops->get_clk_freq, 
"%u");
+       // globalvar_add_simple_bool(".fail_state", (int 
*)fan->ops->get_failure_state);
+       // globalvar_add_simple_bool(".ooc_state", (int 
*)fan->ops->get_failure_state);
+
+    return 0;
+
+err:
+    if (fan->cdev->name)
+        free(fan->cdev->name);
+    free(fan->cdev);
+    free(fan);
+    dev->type_data = NULL;
+    return ret;
+}
+
+void fan_device_unregister(struct device *dev)
+{
+    /* find the generic fan device associated to the driver-specific fan 
controller device */
+    struct fan_device *fan;
+
+    if (!dev)
+        return;
+
+    fan = dev->type_data;
+    if (!fan)
+        return;
+
+    list_del(&fan->list);
+
+    if (fan->cdev) {
+        devfs_remove(fan->cdev);
+        if (fan->cdev->name)
+            free(fan->cdev->name);
+        free(fan->cdev);
+    }
+
+    free(fan);
+    dev->type_data = NULL;
+}
diff --git a/drivers/fan/i2c_fan_controllers/Kconfig 
b/drivers/fan/i2c_fan_controllers/Kconfig
new file mode 100644
index 0000000000..e258783848
--- /dev/null
+++ b/drivers/fan/i2c_fan_controllers/Kconfig
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config G76x
+       bool "GMT G76x driver"
+       help
+       Support Global Mixed-mode Technology Inc. G762 and G763 fan speed PWM 
controller chips
diff --git a/drivers/fan/i2c_fan_controllers/Makefile 
b/drivers/fan/i2c_fan_controllers/Makefile
new file mode 100644
index 0000000000..ca3c49e8a2
--- /dev/null
+++ b/drivers/fan/i2c_fan_controllers/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+# fan controller interface types
+
+obj-$(CONFIG_G76x) += g76x.o
\ No newline at end of file
diff --git a/drivers/fan/i2c_fan_controllers/g76x.c 
b/drivers/fan/i2c_fan_controllers/g76x.c
new file mode 100644
index 0000000000..c5c67766af
--- /dev/null
+++ b/drivers/fan/i2c_fan_controllers/g76x.c
@@ -0,0 +1,1184 @@
+/*
+ * g76x - Driver for the Global Mixed-mode Technology Inc. fan speed
+ *        PWM controller chips from G76x family, i.e. g76x and G763
+ *
+ * based on g760a code written by Herbert Valerio Riedel <[email protected]>
+ * Copyright (C) 2007  Herbert Valerio Riedel <[email protected]>
+ *
+ * Author: Olivier Mouchet <[email protected]>
+ * Copyright (c) 2009 LaCie
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <[email protected]>
+ *
+ * g76x: 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 <fan/fan.h>
+#include <stdbool.h>
+#include <i2c/i2c.h>
+#include <init.h>
+#include <of.h>
+#include <of_device.h>
+#include <linux/minmax.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/limits.h>
+
+/*
+ * driver-specific layer
+ */
+
+static const struct platform_device_id g76x_id[] = {
+       { "g762", 0 },
+       { "g763", 0 },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i2c, g76x_id);
+
+enum g76x_regs {
+       G76x_REG_SET_CNT  = 0x00,
+       G76x_REG_ACT_CNT  = 0x01,
+       G76x_REG_FAN_STA  = 0x02,
+       G76x_REG_SET_OUT  = 0x03,
+       G76x_REG_FAN_CMD1 = 0x04,
+       G76x_REG_FAN_CMD2 = 0x05,
+};
+
+/* Config register bits */
+#define G76x_REG_FAN_CMD1_DET_FAN_FAIL  0x80 /* enable fan_fail signal */
+#define G76x_REG_FAN_CMD1_DET_FAN_OOC   0x40 /* enable fan_out_of_control */
+#define G76x_REG_FAN_CMD1_OUT_MODE      0x20 /* out mode: PWM or DC */
+#define G76x_REG_FAN_CMD1_CTRL_MODE      0x10 /* fan mode: closed/open-loop */
+#define G76x_REG_FAN_CMD1_CLK_DIV_ID1   0x08 /* clock divisor value */
+#define G76x_REG_FAN_CMD1_CLK_DIV_ID0   0x04
+#define G76x_REG_FAN_CMD1_PWM_POLARITY  0x02 /* PWM polarity */
+#define G76x_REG_FAN_CMD1_PULSE_PER_REV 0x01 /* pulse per fan revolution */
+
+#define G76x_REG_FAN_CMD2_GEAR_MODE_1   0x08 /* fan gear mode */
+#define G76x_REG_FAN_CMD2_GEAR_MODE_0   0x04
+#define G76x_REG_FAN_CMD2_FAN_STARTV_1  0x02 /* fan startup voltage */
+#define G76x_REG_FAN_CMD2_FAN_STARTV_0  0x01
+
+#define G76x_REG_FAN_STA_FAIL           0x02 /* fan fail */
+#define G76x_REG_FAN_STA_OOC            0x01 /* fan out of control */
+
+/* Config register values */
+#define G76x_OUT_MODE_PWM            1
+#define G76x_OUT_MODE_ADC             0
+
+#define G76x_FAN_MODE_CLOSED_LOOP    1
+#define G76x_FAN_MODE_OPEN_LOOP      0
+
+#define G76x_PWM_POLARITY_NEGATIVE   1
+#define G76x_PWM_POLARITY_POSITIVE   0
+
+/* Register data is read (and cached) at most once per second. */
+#define G76x_UPDATE_INTERVAL_NS                1000000
+
+/*
+ * Extract fan startup voltage value (0, 1, 2 or 3) from given
+ * FAN_CMD2 register value.
+ */
+#define G76x_STARTV_FROM_REG(reg) \
+       ((reg) & (G76x_REG_FAN_CMD2_FAN_STARTV_0 |      \
+                        G76x_REG_FAN_CMD2_FAN_STARTV_1))
+
+/*
+ * Extract fan gear mode multiplier value (0, 2 or 4) from given
+ * FAN_CMD2 register value.
+ */
+#define G76x_GEARMULT_FROM_REG(reg) \
+       (1 << (((reg) & (G76x_REG_FAN_CMD2_GEAR_MODE_0 |        \
+                        G76x_REG_FAN_CMD2_GEAR_MODE_1)) >> 2))
+
+/*
+ * Extract pulse count per fan revolution value (2 or 4) from given
+ * FAN_CMD1 register value.
+ */
+#define G76x_PULSE_FROM_REG(reg) \
+       ((((reg) & G76x_REG_FAN_CMD1_PULSE_PER_REV) + 1) << 1)
+
+/*
+ * Extract fan clock divisor (1, 2, 4 or 8) from given FAN_CMD1
+ * register value.
+ */
+#define G76x_CLKDIV_FROM_REG(reg) \
+       (1 << (((reg) & (G76x_REG_FAN_CMD1_CLK_DIV_ID0 |        \
+                        G76x_REG_FAN_CMD1_CLK_DIV_ID1)) >> 2))
+
+struct g76x_data {
+       struct i2c_client *client;
+       struct clk *clk;
+
+       /* update mutex */
+       struct mutex update_lock;
+
+       /* board specific parameters. */
+       uint32_t clk_freq;
+
+       /* g76x register cache */
+       bool valid;
+       uint64_t last_updated; /* in nanoseconds */
+
+       uint8_t set_cnt;  /* controls fan rotation speed in closed-loop mode */
+       uint8_t act_cnt;  /* provides access to current fan RPM value */
+       uint8_t 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
+                     */
+       uint8_t set_out;  /* controls fan rotation speed in open-loop mode */
+       uint8_t 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
+                     */
+       uint8_t 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)
+                     */
+};
+
+/*
+ * Following structure can be used to set g762 driver-specific platform 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 g76x_platform_data {
+       unsigned int fan_startv;
+       unsigned int fan_gear_mode;
+       unsigned int pwm_polarity;
+       unsigned int clk_freq;
+};
+
+/* driver-specific helpers */
+
+/*
+ * 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 g76x_rpm_from_cnt(uint8_t cnt, uint32_t clk_freq, 
uint16_t p,
+                                       uint8_t clk_div, uint8_t gear_mult)
+{
+       return (clk_freq * 30 * gear_mult) / ((cnt ? cnt : 1) * p * clk_div);
+}
+
+/*
+ * Convert fan RPM value into pulse count value for fan controller
+ * register (FAN_SET_CNT).
+ */
+static inline unsigned char g76x_cnt_from_rpm(unsigned long rpm, uint32_t 
clk_freq, uint16_t p,
+                                        uint8_t clk_div, uint8_t gear_mult)
+{
+       unsigned long f1 = clk_freq * 30 * gear_mult;
+       unsigned long f2 = p * clk_div;
+
+       rpm = clamp_val(rpm, f1 / (255 * f2), ULONG_MAX / f2);
+       return DIV_ROUND_CLOSEST(f1, rpm * f2);
+}
+
+/*
+ * Convert count value for fan controller register (FAN_SET_CNT)
+ * into percentage of full speed (0 to 100).
+ */
+static inline unsigned int g76x_level_from_cnt(uint8_t cnt) {
+       uint8_t level = 0;
+       cnt = clamp_val(cnt, 0, 255);
+       level = (cnt * 100) / 255;
+       return level;
+}
+
+/*
+ * Convert percentage of full speed into count value for 
+ * fan controller register (FAN_SET_CNT).
+ */
+static inline unsigned int g76x_cnt_from_level(unsigned char level) {
+       unsigned char cnt = 0;
+       level = clamp_val(level, 0, 100);
+       cnt = (level * 255) / 100;
+       return cnt;
+}
+
+/* helper to manage g76x errors */
+static struct g76x_data *g76x_error_handle(struct g76x_data *data, int err_ret)
+{
+    if (data) {
+        mutex_unlock(&data->update_lock);
+        dev_err(&data->client->dev, "g76x error: %d\n", err_ret);
+    }
+
+    return ERR_PTR(err_ret);
+}
+
+/* helper to grab and cache data, at most one time per second */
+static struct g76x_data *g76x_update_client(struct device *dev)
+{
+       struct g76x_data *data = dev->priv;
+       if (!data) return ERR_PTR(-ENODEV);
+       struct i2c_client *client = data->client;
+       int ret = 0;
+
+       mutex_lock(&data->update_lock);
+
+       if (data->valid && !is_timeout(data->last_updated, 
G76x_UPDATE_INTERVAL_NS)) {
+               mutex_unlock(&data->update_lock);
+               return data;
+       }
+
+       ret = i2c_smbus_read_byte_data(client, G76x_REG_SET_CNT);
+       if (ret < 0) goto err_update;
+       data->set_cnt = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G76x_REG_ACT_CNT);
+       if (ret < 0) goto err_update;
+       data->act_cnt = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G76x_REG_FAN_STA);
+       if (ret < 0) goto err_update;
+       data->fan_sta = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G76x_REG_SET_OUT);
+       if (ret < 0) goto err_update;
+       data->set_out = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G76x_REG_FAN_CMD1);
+       if (ret < 0) goto err_update;
+       data->fan_cmd1 = ret;
+
+       ret = i2c_smbus_read_byte_data(client, G76x_REG_FAN_CMD2);
+       if (ret < 0) goto err_update;
+       data->fan_cmd2 = ret;
+
+       data->last_updated = get_time_ns();
+       data->valid = true;
+
+       mutex_unlock(&data->update_lock);
+       return data;
+
+err_update:
+       data->valid = false;
+       return g76x_error_handle(data, ret);
+}
+
+
+/* driver-specific configuration functions */
+
+/* Get and Set fan startup voltage. Accepted values are either 0, 1, 2 or 3. */
+static int g76x_get_fan_startv(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", G76x_STARTV_FROM_REG(data->fan_cmd2));
+}
+
+static int g76x_set_fan_startv(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_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 &= ~G76x_REG_FAN_CMD2_FAN_STARTV_0;
+               data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_FAN_STARTV_1;
+               break;
+       case 1:
+               data->fan_cmd2 |=  G76x_REG_FAN_CMD2_FAN_STARTV_0;
+               data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_FAN_STARTV_1;
+               break;
+       case 2:
+               data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_FAN_STARTV_0;
+               data->fan_cmd2 |=  G76x_REG_FAN_CMD2_FAN_STARTV_1;
+               break;
+       case 3:
+               data->fan_cmd2 |=  G76x_REG_FAN_CMD2_FAN_STARTV_0;
+               data->fan_cmd2 |=  G76x_REG_FAN_CMD2_FAN_STARTV_1;
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+       ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD2,
+                                       data->fan_cmd2);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Get and Set fan gear multiplier. Accepts either 0, 1 or 2. */
+static int g76x_get_gear_multiplier(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", G76x_GEARMULT_FROM_REG(data->fan_cmd2));
+}
+
+static int g76x_set_gear_multiplier(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_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 &= ~G76x_REG_FAN_CMD2_GEAR_MODE_0;
+               data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_GEAR_MODE_1;
+               break;
+       case 1:
+               data->fan_cmd2 |=  G76x_REG_FAN_CMD2_GEAR_MODE_0;
+               data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_GEAR_MODE_1;
+               break;
+       case 2:
+               data->fan_cmd2 &= ~G76x_REG_FAN_CMD2_GEAR_MODE_0;
+               data->fan_cmd2 |=  G76x_REG_FAN_CMD2_GEAR_MODE_1;
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+       ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD2,
+                                       data->fan_cmd2);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Get and Set number of fan pulses per revolution. Accepts either 2 or 4. */
+static int g76x_get_fan_ppr(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", G76x_PULSE_FROM_REG(data->fan_cmd1));
+}
+
+static int g76x_set_fan_ppr(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_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 &= ~G76x_REG_FAN_CMD1_PULSE_PER_REV;
+               break;
+       case 4:
+               data->fan_cmd1 |=  G76x_REG_FAN_CMD1_PULSE_PER_REV;
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+       ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Get and Set PWM polarity. Accepts either 0 (positive duty) or 1 (negative 
duty) */
+static int g76x_get_pwm_polarity(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", !!(data->fan_cmd1 & 
G76x_REG_FAN_CMD1_PWM_POLARITY));
+}
+
+static int g76x_set_pwm_polarity(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case G76x_PWM_POLARITY_POSITIVE:
+               data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_PWM_POLARITY;
+               break;
+       case G76x_PWM_POLARITY_NEGATIVE:
+               data->fan_cmd1 |=  G76x_REG_FAN_CMD1_PWM_POLARITY;
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+       ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/*
+ * Get and 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
+ * (32768Hz) 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 g76x_get_clk_freq(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", !!(data->clk_freq));
+}
+
+static int g76x_set_clk_freq(struct device *dev, unsigned long val)
+{
+    struct i2c_client *client = to_i2c_client(dev);
+    struct g76x_data *data = i2c_get_clientdata(client);
+
+    if (!data)
+               return 0;
+
+       if (val == 0)
+               val = 32768;
+
+       else if (val > 100000000)
+               return -EINVAL;
+
+    data->clk_freq = val;
+
+    return 0;
+}
+
+/* Get and Set fan clock divisor. Accepts either 1, 2, 4 or 8. */
+static int g76x_get_clk_div(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", G76x_CLKDIV_FROM_REG(data->fan_cmd1));
+}
+
+static int g76x_set_clk_div(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_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 &= ~G76x_REG_FAN_CMD1_CLK_DIV_ID0;
+               data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_CLK_DIV_ID1;
+               break;
+       case 2:
+               data->fan_cmd1 |=  G76x_REG_FAN_CMD1_CLK_DIV_ID0;
+               data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_CLK_DIV_ID1;
+               break;
+       case 4:
+               data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_CLK_DIV_ID0;
+               data->fan_cmd1 |=  G76x_REG_FAN_CMD1_CLK_DIV_ID1;
+               break;
+       case 8:
+               data->fan_cmd1 |=  G76x_REG_FAN_CMD1_CLK_DIV_ID0;
+               data->fan_cmd1 |=  G76x_REG_FAN_CMD1_CLK_DIV_ID1;
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+       ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Get and Set fan control mode. Accepts either 0 (open-loop) or 1 
(closed-loop). */
+static int g76x_get_control_mode(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", !!(data->fan_cmd1 & 
G76x_REG_FAN_CMD1_CTRL_MODE));
+}
+
+static int g76x_set_control_mode(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case G76x_FAN_MODE_CLOSED_LOOP:
+               data->fan_cmd1 |=  G76x_REG_FAN_CMD1_CTRL_MODE;
+               break;
+       case G76x_FAN_MODE_OPEN_LOOP:
+               data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_CTRL_MODE;
+               /*
+                * NOTE: if SET_CNT register value is 255, fan is disarmed,
+                * no matter the value of SET_OUT (to be specific, this seems
+                * to happen only in PWM mode). To workaround this case,
+                * we give SET_CNT value of 0 if it is 255 when switching to 
open-loop.
+                * This means that fan will be armed every time we switch to 
open-loop.
+                */
+               if (data->set_cnt == 0xff)
+                       i2c_smbus_write_byte_data(data->client, 
G76x_REG_SET_CNT, 0);
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+
+       ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Get and Set output mode. Accepts either 0 (ADC) or 1 (PWM) */
+static int g76x_get_output_mode(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", !!(data->fan_cmd1 & 
G76x_REG_FAN_CMD1_OUT_MODE));
+}
+
+static int g76x_set_output_mode(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case G76x_OUT_MODE_PWM:
+               data->fan_cmd1 |=  G76x_REG_FAN_CMD1_OUT_MODE;
+               break;
+       case G76x_OUT_MODE_ADC:
+               data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_OUT_MODE;
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+       ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+                                       data->fan_cmd1);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Get and Set fan out of control detection. Accepts either 0 (disabled) or 1 
(enabled) */
+static int g76x_get_ooc_detection(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", !!(data->fan_cmd1 & 
G76x_REG_FAN_CMD1_DET_FAN_OOC));
+}
+
+static int g76x_set_ooc_detection(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case 0:
+               data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_DET_FAN_OOC;
+               break;
+       case 1:
+               data->fan_cmd1 |= G76x_REG_FAN_CMD1_DET_FAN_OOC;
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+       ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+                                                                       
data->fan_cmd1);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/* Get and Set fan failure detection. Accepts either 0 (disabled) or 1 
(enabled) */
+static int g76x_get_failure_detection(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", !!(data->fan_cmd1 & 
G76x_REG_FAN_CMD1_DET_FAN_FAIL));
+}
+
+static int g76x_set_failure_detection(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (val) {
+       case 0:
+               data->fan_cmd1 &= ~G76x_REG_FAN_CMD1_DET_FAN_FAIL;
+               break;
+       case 1:
+               data->fan_cmd1 |= G76x_REG_FAN_CMD1_DET_FAN_FAIL;
+               break;
+       default:
+               mutex_unlock(&data->update_lock);
+               return -EINVAL;
+       }
+       ret = i2c_smbus_write_byte_data(data->client, G76x_REG_FAN_CMD1,
+                                                                       
data->fan_cmd1);
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/*
+ * Import hardware characteristics from .dts file and push
+ * those to the chip.
+ */
+#ifdef CONFIG_OF
+static const struct of_device_id g76x_dt_match[] = {
+       { .compatible = "gmt,g762" },
+       { .compatible = "gmt,g763" },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, g76x_dt_match);
+/*
+ * 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 g76x_of_clock_disable(). Note that a reference to clock is kept
+ * in our private data structure to be used in this function.
+ */
+static int g76x_of_clock_enable(struct i2c_client *client)
+{
+       struct g76x_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");
+               clk_put(clk);
+               return ret;
+       }
+
+       clk_freq = clk_get_rate(clk);
+       ret = g76x_set_clk_freq(&client->dev, clk_freq);
+       if (ret) {
+               dev_err(&client->dev, "invalid clock freq %lu\n", clk_freq);
+               clk_disable_unprepare(clk);
+               clk_put(clk);
+               return ret;
+       }
+
+       data = i2c_get_clientdata(client);
+       data->clk = clk;
+
+       return 0;
+}
+
+static void g76x_of_clock_disable(struct i2c_client *client)
+{
+       struct g76x_data *data = i2c_get_clientdata(client);
+
+       if (!data->clk)
+               return;
+
+       clk_disable_unprepare(data->clk);
+       clk_put(data->clk);
+}
+
+static int g76x_of_prop_import_one(struct i2c_client *client,
+                                  const char *pname,
+                                  int (*psetter)(struct device *dev,
+                                                 unsigned long val))
+{
+       int ret;
+       uint32_t pval;
+
+       if (of_property_read_u32(client->dev.of_node, pname, &pval))
+               return 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 g76x_of_prop_import(struct i2c_client *client)
+{
+       int ret;
+
+       if (!client->dev.of_node)
+               return 0;
+
+       ret = g76x_of_prop_import_one(client, "fan_gear_mode",
+                                     g76x_set_gear_multiplier);
+       if (ret)
+               return ret;
+
+       ret = g76x_of_prop_import_one(client, "pwm_polarity",
+                                     g76x_set_pwm_polarity);
+       if (ret)
+               return ret;
+
+       return g76x_of_prop_import_one(client, "fan_startv",
+                                      g76x_set_fan_startv);
+}
+
+#else
+static int g76x_of_prop_import(struct i2c_client *client)
+{
+       return 0;
+}
+
+static int g76x_of_clock_enable(struct i2c_client *client)
+{
+       return 0;
+}
+
+static void g76x_of_clock_disable(struct i2c_client *client) { }
+#endif
+
+/*
+ * Import platform data from chip
+ */
+static int g76x_pdata_prop_import(struct i2c_client *client)
+{
+       struct g76x_platform_data *pdata = client->dev.platform_data;
+       int ret;
+
+       if (!pdata)
+               return 0;
+
+       ret = g76x_set_gear_multiplier(&client->dev, pdata->fan_gear_mode);
+       if (ret)
+               return ret;
+
+       ret = g76x_set_pwm_polarity(&client->dev, pdata->pwm_polarity);
+       if (ret)
+               return ret;
+
+       ret = g76x_set_fan_startv(&client->dev, pdata->fan_startv);
+       if (ret)
+               return ret;
+
+       return g76x_set_clk_freq(&client->dev, pdata->clk_freq);
+}
+
+
+/* driver-specific control functions */
+
+/* Get fan failure flag state. 0 (no fault) or 1 (fan failure) */
+static int g76x_get_failure_state(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", !!(data->fan_sta & G76x_REG_FAN_STA_FAIL));
+}
+
+/* Get fan out of control flag state. 0 (fan ooc) or 1 (fan under control) */
+static int g76x_get_ooc_state(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       return sprintf(buf, "%d\n", !!(data->fan_sta & G76x_REG_FAN_STA_OOC));
+}
+
+/*
+ * Get and Set fan speed value. Can be called both in closed and open-loop mode
+ * but effect will only be seen after closed-loop mode is configured.
+ * 
+ * Accepts values between
+ * 0 (stops the fan) and 255 (full speed) for open-loop
+ * 0 (stops the fan) and 254 (full speed) for closed-loop
+ * 255 in closed-loop mode will disarm the fan
+ */
+static int g76x_get_fan_speed(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       switch (data->fan_cmd1 & G76x_REG_FAN_CMD1_CTRL_MODE) {
+       case G76x_FAN_MODE_CLOSED_LOOP:
+               ret = sprintf(buf, "%d\n", data->act_cnt);
+               break;
+       default:
+               ret = sprintf(buf, "%d\n", data->set_out);
+               break;
+       }
+
+       return ret;
+}
+
+static int g76x_set_fan_speed(struct device *dev, unsigned long val)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       int ret;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       val = clamp_val(val, 0, 255);
+
+       mutex_lock(&data->update_lock);
+       switch (data->fan_cmd1 & G76x_REG_FAN_CMD1_CTRL_MODE) {
+       case G76x_FAN_MODE_CLOSED_LOOP:
+               data->set_cnt = val;
+               ret = i2c_smbus_write_byte_data(data->client, G76x_REG_SET_CNT, 
val);
+               break;
+       default:
+               data->set_out = val;
+               ret = i2c_smbus_write_byte_data(data->client, G76x_REG_SET_OUT, 
val);
+               break;
+       }
+       data->valid = false;
+       mutex_unlock(&data->update_lock);
+
+       return ret;
+}
+
+/*
+ * Get/set the fan speed as a RPM value;
+ * in closed-loop mode the chip will regulate the fan speed using
+ * pulses from fan tachometer.
+ *
+ * in open-loop mode fan speed feedback is not available so it will
+ * estimate rpm from open-loop input speed.
+ * 
+ * Also note that due to rounding errors it is possible that you don't read
+ * back exactly the value you have set.
+ */
+ static int g76x_get_fan_rpm(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       unsigned int rpm;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+
+       mutex_lock(&data->update_lock);
+       switch (data->fan_cmd1 & G76x_REG_FAN_CMD1_CTRL_MODE) {
+       case G76x_FAN_MODE_CLOSED_LOOP:
+               if (data->set_cnt == 0xff) /* fan disarmed */
+                       rpm = 0;
+               else
+                       rpm = g76x_rpm_from_cnt(data->act_cnt, data->clk_freq,
+                                                                       
G76x_PULSE_FROM_REG(data->fan_cmd1),
+                                                                       
G76x_CLKDIV_FROM_REG(data->fan_cmd1),
+                                                                       
G76x_GEARMULT_FROM_REG(data->fan_cmd2));
+               break;
+       default:
+               /* estimate rpm from input speed value as a percentage of 
common max RPM */
+               rpm = (data->set_out * 3000) / 255;
+               break;
+       }
+       mutex_unlock(&data->update_lock);
+
+       return sprintf(buf, "%u\n", rpm);
+}
+
+static int g76x_set_fan_rpm(struct device *dev, unsigned long rpm)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       unsigned int speed;
+
+       if (IS_ERR(data))
+           return PTR_ERR(data);
+
+       speed = g76x_cnt_from_rpm(rpm, data->clk_freq,
+                                                       
G76x_PULSE_FROM_REG(data->fan_cmd1),
+                                                       
G76x_CLKDIV_FROM_REG(data->fan_cmd1),
+                                                       
G76x_GEARMULT_FROM_REG(data->fan_cmd2));
+
+       return g76x_set_fan_speed(dev, speed);
+}
+
+/* Get/set the fan speed as a percentage of max speed */
+static int g76x_get_fan_level(struct device *dev, char *buf)
+{
+       struct g76x_data *data = g76x_update_client(dev);
+       unsigned int level;
+
+       if (IS_ERR(data))
+               return PTR_ERR(data);
+       
+       mutex_lock(&data->update_lock);
+
+       switch (data->fan_cmd1 & G76x_REG_FAN_CMD1_CTRL_MODE) {
+       case G76x_FAN_MODE_CLOSED_LOOP:
+               if (data->set_cnt == 0xff) /* fan disarmed */
+                       level = 0;
+               else
+                       level = g76x_level_from_cnt(data->act_cnt);
+               break;
+       default:
+               /* estimate level from input speed value */
+               level = g76x_level_from_cnt(data->set_out);
+               break;
+       }
+       mutex_unlock(&data->update_lock);
+
+       return sprintf(buf, "%d\n", level);
+}
+
+static int g76x_set_fan_level(struct device *dev, unsigned long level)
+{
+       level = clamp_val(level, 0, 100);
+
+       return g76x_set_fan_speed(dev, g76x_cnt_from_level(level));
+}
+
+/* Setup parameters to a default (and safe) known state and arm the fan */
+static int g76x_fan_init(struct device *dev)
+{
+       /*
+        * g76x PowerOn/Reset state:
+        * set_cnt  = 0xff      (fan disarmed)
+        * act_cnt  = 0xff
+        * fan_sta  = 0x01      (no fault, no ooc)
+        * set_out  = 0x0       (open-loop speed set to 0)
+        * fan_cmd1 = 0x10      (2 ppr, positive duty, clk div 1,
+        *                  closed-loop, ADC mode, ooc detection disabled,
+        *                                      failure detection disabled)
+        * fan_cmd2 = 0x01      (fan startv 1, gear mode x1, alert# unmasked)
+        */
+       int ret;
+       /* default clock frequency */
+       ret = g76x_set_clk_freq(dev, 32768);
+       if (ret < 0) goto err_init;
+
+       /* enable ooc detection */
+       ret = g76x_set_ooc_detection(dev, 1);
+       if (ret < 0) goto err_init;
+       
+       /* enable failure detection */
+       ret = g76x_set_failure_detection(dev, 1);
+       if (ret < 0) goto err_init;
+       
+       /* set speed to 0 and arm the fan */
+       ret = g76x_set_fan_speed(dev, 0);
+       if (ret < 0) goto err_init;
+
+       return 0;
+
+err_init:               
+       return -EIO;
+}
+
+
+/*
+ * Generic fan driver layer
+ */
+
+static struct fan_ops g76x_fan_ops = {
+    .get_fan_startv                    =       g76x_get_fan_startv,
+    .set_fan_startv                    =       g76x_set_fan_startv,
+       .get_gear_multiplier    =       g76x_get_gear_multiplier,
+       .set_gear_multiplier    =       g76x_set_gear_multiplier,
+       .get_fan_ppr                    =       g76x_get_fan_ppr,
+       .set_fan_ppr                    =       g76x_set_fan_ppr,
+       .get_pwm_polarity               =       g76x_get_pwm_polarity,
+       .set_pwm_polarity               =       g76x_set_pwm_polarity,
+       .get_clk_freq                   =       g76x_get_clk_freq,
+       .set_clk_freq                   =       g76x_set_clk_freq,
+       .get_clk_div                    =       g76x_get_clk_div,
+       .set_clk_div                    =       g76x_set_clk_div,
+       .get_control_mode               =       g76x_get_control_mode,
+       .set_control_mode               =       g76x_set_control_mode,
+       .get_output_mode                =       g76x_get_output_mode,
+       .set_output_mode                =       g76x_set_output_mode,
+       .get_ooc_detection              =       g76x_get_ooc_detection,
+       .set_ooc_detection              =       g76x_set_ooc_detection,
+       .get_failure_detection  =       g76x_get_failure_detection,
+       .set_failure_detection  =       g76x_set_failure_detection,
+       .get_failure_state              =       g76x_get_failure_state,
+       .get_ooc_state                  =       g76x_get_ooc_state,
+       .get_fan_speed                  =       g76x_get_fan_speed,
+       .set_fan_speed                  =       g76x_set_fan_speed,
+       .get_fan_rpm                    =       g76x_get_fan_rpm,
+       .set_fan_rpm                    =       g76x_set_fan_rpm,
+       .get_fan_level                  =       g76x_get_fan_level,
+       .set_fan_level                  =       g76x_set_fan_level,
+       .fan_init                               =       g76x_fan_init,
+};
+
+static int g76x_fan_probe(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g76x_data *data;
+       int ret;
+
+       /* make sure that chip is present by reading a register */
+       ret = i2c_smbus_read_byte_data(client, G76x_REG_FAN_CMD1);
+       if (ret < 0) {
+               ret = -ENODEV;
+               goto err_ret;
+       }
+
+       /* allocate and initialize driver data structure */
+       data = xzalloc(sizeof(*data));
+       if (!data) {
+               ret = -ENOMEM;
+               goto err_ret;
+       }
+
+       /* pass specific driver data to i2c device */
+       dev->priv = data;
+       i2c_set_clientdata(client, data);
+       data->client = client;
+       mutex_init(&data->update_lock);
+
+       /* Get configuration via DT ... */
+       ret = g76x_of_clock_enable(client);
+       if (ret)
+               goto err_alloc;
+
+       ret = g76x_of_prop_import(client);
+       if (ret)
+               goto err_clk;
+
+       /* ... or platform_data */
+       ret = g76x_pdata_prop_import(client);
+       if (ret)
+               goto err_clk;
+
+       ret = fan_device_register(&client->dev, &g76x_fan_ops);
+       if (ret)
+               goto err_clk;
+
+       return 0;
+
+err_clk:
+    g76x_of_clock_disable(client);
+err_alloc:
+       free(data);
+err_ret:
+       return ret;
+}
+
+static void g76x_fan_remove(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct g76x_data *data = i2c_get_clientdata(client);
+
+       fan_device_unregister(&client->dev);
+       g76x_of_clock_disable(client);
+       free(data);
+}
+
+static struct driver g76x_driver = {
+       .name = "g76x",
+       .probe    = g76x_fan_probe,
+       .remove   = g76x_fan_remove,
+       .id_table = g76x_id,
+       .of_match_table = of_match_ptr(g76x_dt_match),
+};
+
+device_i2c_driver(g76x_driver);
diff --git a/drivers/fan/other_fan_controllers/Kconfig 
b/drivers/fan/other_fan_controllers/Kconfig
new file mode 100644
index 0000000000..8e750fdae5
--- /dev/null
+++ b/drivers/fan/other_fan_controllers/Kconfig
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config GPIO_PWM_FAN
+       bool "Generic GPIO PWM controlled fan driver"
+       depends on PWM
+       help
+       Support fan speed control trough GPIO PWM signal
diff --git a/drivers/fan/other_fan_controllers/Makefile 
b/drivers/fan/other_fan_controllers/Makefile
new file mode 100644
index 0000000000..43ea3ab5b3
--- /dev/null
+++ b/drivers/fan/other_fan_controllers/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+# fan controller interface types
diff --git a/drivers/fan/spi_fan_controllers/Kconfig 
b/drivers/fan/spi_fan_controllers/Kconfig
new file mode 100644
index 0000000000..a4e40e534e
--- /dev/null
+++ b/drivers/fan/spi_fan_controllers/Kconfig
@@ -0,0 +1 @@
+# SPDX-License-Identifier: GPL-2.0-only
diff --git a/drivers/fan/spi_fan_controllers/Makefile 
b/drivers/fan/spi_fan_controllers/Makefile
new file mode 100644
index 0000000000..43ea3ab5b3
--- /dev/null
+++ b/drivers/fan/spi_fan_controllers/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+# fan controller interface types
diff --git a/include/fan/fan.h b/include/fan/fan.h
new file mode 100644
index 0000000000..3289c6d214
--- /dev/null
+++ b/include/fan/fan.h
@@ -0,0 +1,94 @@
+#ifndef __FAN_H__
+#define __FAN_H__
+
+#include <common.h>
+#include <device.h>
+#include <driver.h>
+#include <linux/list.h>
+#include <linux/types.h>
+#include <fs.h>
+#include <malloc.h>
+#include <xfuncs.h>
+#include <globalvar.h>
+#include <errno.h>
+
+
+/* Generic fan driver fan device management */
+
+struct fan_device {             /*!< generic fan driver device */
+    struct list_head list;      /*!< link in generic fan driver devices list */
+    struct device *dev;         /*!< specific fan controller driver device */ 
+    const struct fan_ops *ops;  /*!< specific fan controller driver operations 
*/
+    struct cdev *cdev;          /*!< generic filesystem fan device */
+};
+
+struct fan_ops {
+    int (*get_fan_startv)(struct device *dev, char *buf);
+    int (*set_fan_startv)(struct device *dev, unsigned long val);
+
+    int (*get_gear_multiplier)(struct device *dev, char *buf);
+    int (*set_gear_multiplier)(struct device *dev, unsigned long val);
+
+    int (*get_fan_ppr)(struct device *dev, char *buf);
+    int (*set_fan_ppr)(struct device *dev, unsigned long val);
+
+    int (*get_pwm_polarity)(struct device *dev, char *buf);
+    int (*set_pwm_polarity)(struct device *dev, unsigned long val);
+
+    int (*get_clk_freq)(struct device *dev, char *buf);
+    int (*set_clk_freq)(struct device *dev, unsigned long val);
+
+    int (*get_clk_div)(struct device *dev, char *buf);
+    int (*set_clk_div)(struct device *dev, unsigned long val);
+
+    int (*get_control_mode)(struct device *dev, char *buf);
+    int (*set_control_mode)(struct device *dev, unsigned long val);
+
+    int (*get_output_mode)(struct device *dev, char *buf);
+    int (*set_output_mode)(struct device *dev, unsigned long val);
+
+    int (*get_ooc_detection)(struct device *dev, char *buf);
+    int (*set_ooc_detection)(struct device *dev, unsigned long val);
+
+    int (*get_failure_detection)(struct device *dev, char *buf);
+    int (*set_failure_detection)(struct device *dev, unsigned long val);
+
+    int (*get_failure_state)(struct device *dev, char *buf);
+    int (*get_ooc_state)(struct device *dev, char *buf);
+
+    int (*get_fan_speed)(struct device *dev, char *buf);
+    int (*set_fan_speed)(struct device *dev, unsigned long val);
+
+    int (*get_fan_rpm)(struct device *dev, char *buf);
+    int (*set_fan_rpm)(struct device *dev, unsigned long val);
+
+    int (*get_fan_level)(struct device *dev, char *buf);
+    int (*set_fan_level)(struct device *dev, unsigned long val);
+
+    int (*fan_init)(struct device *dev);
+};
+/*
+static void fan_set_dev(struct fan_device *fan, struct device *dev);
+static struct device *fan_get_dev(struct fan_device *fan);
+
+static void fan_set_ops(struct fan_device *fan, struct fan_ops *ops);
+static const struct fan_ops *fan_get_ops(struct fan_device *fan);
+*/
+
+/* Generic filesystem fan device management */
+/*
+static void cdev_set_fan(struct fan_device *fan);
+static struct fan_device *cdev_get_fan(struct cdev *cdev);
+
+static void cdev_set_name(struct cdev *cdev);
+static char *cdev_get_name(struct cdev *cdev);
+*/
+struct fan_device *fan_device_find(const char *devmatch);
+
+
+/* specific fan controller driver device management */
+
+int fan_device_register(struct device *dev, struct fan_ops *ops);
+void fan_device_unregister(struct device *dev);
+
+#endif /* __FAN_H__ */
\ No newline at end of file
diff --git a/include/fan/i2c_fan_controllers/g76x.h 
b/include/fan/i2c_fan_controllers/g76x.h
new file mode 100644
index 0000000000..e9075a1b5c
--- /dev/null
+++ b/include/fan/i2c_fan_controllers/g76x.h
@@ -0,0 +1,37 @@
+/*
+ * Platform data structure for g762 fan controller driver
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <[email protected]>
+ *
+ * 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 g76x_platform_data {
+       unsigned int fan_startv;
+       unsigned int fan_gear_mode;
+       unsigned int pwm_polarity;
+       unsigned int clk_freq;
+};
+
+#endif /* __LINUX_PLATFORM_DATA_G762_H__ */

-- 
2.47.3



Reply via email to