[PATCH 13/19] drivers: fan: add fan framework and API
Luca Lauro via B4 Relay
devnull+famlauro93l.gmail.com at kernel.org
Thu Jul 23 06:57:51 PDT 2026
From: Luca Lauro <famlauro93l at gmail.com>
---
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 +
12 files changed, 1563 insertions(+)
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