[PATCH v2 05/10] drivers: fan: add fan framework and API

Luca Lauro via B4 Relay devnull+famlauro93l.gmail.com at kernel.org
Tue Jul 28 10:43:17 PDT 2026


From: Luca Lauro <famlauro93l at gmail.com>

Signed-off-by: Luca Lauro <famlauro93l at gmail.com>
---
 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 <hvr at gnu.org>
+ * Copyright (C) 2007  Herbert Valerio Riedel <hvr at gnu.org>
+ *
+ * Author: Olivier Mouchet <olivier.mouchet at gmail.com>
+ * Copyright (c) 2009 LaCie
+ *
+ * Copyright (C) 2013, Arnaud EBALARD <arno at natisbad.org>
+ *
+ * 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 <arno at natisbad.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef __LINUX_PLATFORM_DATA_G762_H__
+#define __LINUX_PLATFORM_DATA_G762_H__
+
+/*
+ * Following structure can be used to set g762 driver platform specific data
+ * during board init. Note that passing a sparse structure is possible but
+ * will result in non-specified attributes to be set to default value, hence
+ * overloading those installed during boot (e.g. by u-boot).
+ */
+
+struct 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





More information about the barebox mailing list