[PATCH 09/10] hwmon: Add driver for Fan Tray on Juniper I2CS FGPA

From: Pantelis Antoniou
Date: Fri Oct 07 2016 - 11:25:04 EST


From: Avirup Banerjee <abanerjee@xxxxxxxxxxx>

Add a hwmon driver for Fan Trays using Juniper's I2CS FPGA.

Signed-off-by: Avirup Banerjee <abanerjee@xxxxxxxxxxx>
Signed-off-by: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
Signed-off-by: Guenter Roeck <groeck@xxxxxxxxxxx>
Signed-off-by: JawaharBalaji Thirumalaisamy <jawaharb@xxxxxxxxxxx>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@xxxxxxxxxxxx>
---
drivers/hwmon/Kconfig | 11 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/jnx-fan.c | 471 +++++++++++++++++++++++++++++
include/linux/platform_data/jnx-i2cs-fan.h | 13 +
4 files changed, 496 insertions(+)
create mode 100644 drivers/hwmon/jnx-fan.c
create mode 100644 include/linux/platform_data/jnx-i2cs-fan.h

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 45cef3d..b9348d2 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -663,6 +663,17 @@ config SENSORS_JC42
This driver can also be built as a module. If so, the module
will be called jc42.

+config SENSORS_JNX_FAN
+ tristate "Juniper Fan Tray driver"
+ depends on I2C && MFD_JUNIPER_I2CS
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the Juniper Networks
+ Fan Tray Driver.
+
+ This driver can also be built as a module. If so, the module
+ will be called jnx-fan.
+
config SENSORS_POWR1220
tristate "Lattice POWR1220 Power Monitoring"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index aecf4ba..eea631e 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
obj-$(CONFIG_SENSORS_INA3221) += ina3221.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
obj-$(CONFIG_SENSORS_JC42) += jc42.o
+obj-$(CONFIG_SENSORS_JNX_FAN) += jnx-fan.o
obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
diff --git a/drivers/hwmon/jnx-fan.c b/drivers/hwmon/jnx-fan.c
new file mode 100644
index 0000000..d04e3ce
--- /dev/null
+++ b/drivers/hwmon/jnx-fan.c
@@ -0,0 +1,471 @@
+/*
+ * hwmon: Driver for Juniper Fan Tray Controller
+ *
+ * Copyright (c) 2014 Juniper Networks. All rights reserved.
+ * Author: Avirup Banerjee <abanerjee@xxxxxxxxxxx>
+ *
+ * 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.
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/hwmon-vid.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_data/jnx-i2cs-fan.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define DRIVER_NAME "i2cs_fan_hwmon"
+
+/*
+ * Fan fpga register offsets
+ */
+
+#define I2CS_FAN_ADEC_VER 0x01
+#define I2CS_FAN_SELECT 0x40
+#define I2CS_FAN_SPEED_CTRL 0x41
+#define I2CS_FAN_MAX_TACH 0x42
+#define I2CS_FAN_MIN_TACH 0x43
+#define I2CS_FAN_TACH 0x44
+#define I2CS_FAN_INT_SRC 0x45
+#define I2CS_FAN_INT_MASK 0x46
+#define I2CS_FAN_INT_1_7 0x47
+#define I2CS_FAN_INT_8_14 0x48
+#define I2CS_FAN_SPARE_FAN_INT_15_22 0x49
+#define I2CS_FAN_MODE_AND_TEST 0x4A
+#define I2CS_FAN_HW_DEBUG_1 0x4B
+#define I2CS_FAN_HW_DEBUG_2 0x4C
+#define I2CS_FAN_SW_FAN_POWER_SPEED_FAIL 0x4D
+#define I2CS_FAN_ADEC_MASK_WAIT_SECOND 0x4F
+#define I2CS_FAN_RESET_WAIT_CONTROL 0x50
+#define I2CS_FAN_BOARD_STAT_1 0x51
+#define I2CS_FAN_BOARD_STAT_2 0x52
+#define I2CS_FAN_OK_THRESHOLD 0x53
+#define I2CS_FAN_SPARE 0x54
+#define I2CS_FAN_SPARE_OE 0x55
+
+#define FAN_TACH_FACTOR 120
+#define NUM_FANS_PER_TRAY 14
+
+struct jnx_fan_data {
+ struct regmap *regmap;
+ struct device *hwmon_dev;
+ struct mutex update_lock;
+ int fan_index;
+ int num_fans;
+ int factor;
+};
+
+static int jnx_fan_select(struct jnx_fan_data *data, int index)
+{
+ /* Return if fan has already been selected */
+ if (data->fan_index == index)
+ return 0;
+
+ data->fan_index = index;
+
+ return regmap_write(data->regmap, I2CS_FAN_SELECT, index);
+}
+
+static int jnx_fan_read_reg(struct jnx_fan_data *data, u8 reg, int index)
+{
+ unsigned int value;
+ int ret;
+
+ mutex_lock(&data->update_lock);
+
+ ret = jnx_fan_select(data, index);
+ if (ret < 0)
+ goto done;
+
+ ret = regmap_read(data->regmap, reg, &value);
+ if (ret < 0)
+ goto done;
+ ret = value;
+
+done:
+ mutex_unlock(&data->update_lock);
+ return ret;
+}
+
+static int jnx_fan_write_reg(struct jnx_fan_data *data, u8 reg,
+ unsigned int value, int index)
+{
+ int ret;
+
+ mutex_lock(&data->update_lock);
+ ret = jnx_fan_select(data, index);
+ if (ret < 0)
+ goto done;
+
+ ret = regmap_write(data->regmap, reg, value);
+
+done:
+ mutex_unlock(&data->update_lock);
+ return ret;
+}
+
+static ssize_t jnx_fan_set_pwm(struct device *dev,
+ struct device_attribute *da,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ struct jnx_fan_data *data = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ if (kstrtoul(buf, 10, &val) < 0)
+ return -EINVAL;
+ if (val > 255)
+ return -EINVAL;
+
+ ret = jnx_fan_write_reg(data, I2CS_FAN_SPEED_CTRL, val, attr->index);
+ return ret ? ret : count;
+}
+
+static ssize_t jnx_fan_show_pwm(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ struct jnx_fan_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = jnx_fan_read_reg(data, I2CS_FAN_SPEED_CTRL, attr->index);
+ if (ret < 0)
+ return ret;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", ret);
+}
+
+static ssize_t jnx_fan_show(struct device *dev, struct device_attribute *da,
+ char *buf)
+{
+ struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da);
+ struct jnx_fan_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = jnx_fan_read_reg(data, attr->nr, attr->index);
+ if (ret < 0)
+ return ret;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", ret * data->factor);
+}
+
+static ssize_t jnx_fan_set(struct device *dev, struct device_attribute *da,
+ const char *buf, size_t count)
+{
+ struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da);
+ struct jnx_fan_data *data = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ if (kstrtoul(buf, 10, &val) < 0)
+ return -EINVAL;
+
+ DIV_ROUND_CLOSEST(val, data->factor);
+ clamp_val(val, 0, 255);
+
+ ret = jnx_fan_write_reg(data, attr->nr, val, attr->index);
+ return ret ? ret : count;
+}
+
+static umode_t jnx_fan_is_visible(struct kobject *kobj, struct attribute *a,
+ int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct jnx_fan_data *data = dev_get_drvdata(dev);
+ unsigned int index = n % 14;
+
+ if (index < data->num_fans)
+ return a->mode;
+
+ return 0;
+}
+
+static struct regmap_config jnx_fan_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = I2CS_FAN_SPARE_OE,
+};
+
+/* Fan speed */
+static SENSOR_DEVICE_ATTR_2(fan1_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 4);
+static SENSOR_DEVICE_ATTR_2(fan5_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 5);
+static SENSOR_DEVICE_ATTR_2(fan6_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 6);
+static SENSOR_DEVICE_ATTR_2(fan7_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 7);
+static SENSOR_DEVICE_ATTR_2(fan8_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 8);
+static SENSOR_DEVICE_ATTR_2(fan9_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 9);
+static SENSOR_DEVICE_ATTR_2(fan10_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 10);
+static SENSOR_DEVICE_ATTR_2(fan11_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 11);
+static SENSOR_DEVICE_ATTR_2(fan12_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 12);
+static SENSOR_DEVICE_ATTR_2(fan13_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 13);
+static SENSOR_DEVICE_ATTR_2(fan14_input, S_IRUGO, jnx_fan_show, NULL,
+ I2CS_FAN_TACH, 14);
+
+/* PWM values */
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 1);
+static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 2);
+static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 3);
+static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 4);
+static SENSOR_DEVICE_ATTR(pwm5, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 5);
+static SENSOR_DEVICE_ATTR(pwm6, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 6);
+static SENSOR_DEVICE_ATTR(pwm7, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 7);
+static SENSOR_DEVICE_ATTR(pwm8, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 8);
+static SENSOR_DEVICE_ATTR(pwm9, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 9);
+static SENSOR_DEVICE_ATTR(pwm10, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 10);
+static SENSOR_DEVICE_ATTR(pwm11, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 11);
+static SENSOR_DEVICE_ATTR(pwm12, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 12);
+static SENSOR_DEVICE_ATTR(pwm13, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 13);
+static SENSOR_DEVICE_ATTR(pwm14, S_IWUSR | S_IRUGO, jnx_fan_show_pwm,
+ jnx_fan_set_pwm, 14);
+
+/* Fan Thresholds */
+
+/* Min */
+static SENSOR_DEVICE_ATTR_2(fan1_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 4);
+static SENSOR_DEVICE_ATTR_2(fan5_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 5);
+static SENSOR_DEVICE_ATTR_2(fan6_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 6);
+static SENSOR_DEVICE_ATTR_2(fan7_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 7);
+static SENSOR_DEVICE_ATTR_2(fan8_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 8);
+static SENSOR_DEVICE_ATTR_2(fan9_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 9);
+static SENSOR_DEVICE_ATTR_2(fan10_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 10);
+static SENSOR_DEVICE_ATTR_2(fan11_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 11);
+static SENSOR_DEVICE_ATTR_2(fan12_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 12);
+static SENSOR_DEVICE_ATTR_2(fan13_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 13);
+static SENSOR_DEVICE_ATTR_2(fan14_min, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MIN_TACH, 14);
+/* Max */
+static SENSOR_DEVICE_ATTR_2(fan1_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 1);
+static SENSOR_DEVICE_ATTR_2(fan2_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 2);
+static SENSOR_DEVICE_ATTR_2(fan3_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 3);
+static SENSOR_DEVICE_ATTR_2(fan4_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 4);
+static SENSOR_DEVICE_ATTR_2(fan5_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 5);
+static SENSOR_DEVICE_ATTR_2(fan6_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 6);
+static SENSOR_DEVICE_ATTR_2(fan7_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 7);
+static SENSOR_DEVICE_ATTR_2(fan8_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 8);
+static SENSOR_DEVICE_ATTR_2(fan9_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 9);
+static SENSOR_DEVICE_ATTR_2(fan10_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 10);
+static SENSOR_DEVICE_ATTR_2(fan11_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 11);
+static SENSOR_DEVICE_ATTR_2(fan12_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 12);
+static SENSOR_DEVICE_ATTR_2(fan13_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 13);
+static SENSOR_DEVICE_ATTR_2(fan14_max, S_IWUSR | S_IRUGO, jnx_fan_show,
+ jnx_fan_set, I2CS_FAN_MAX_TACH, 14);
+
+static struct attribute *jnx_fan_attrs[] = {
+ &sensor_dev_attr_fan1_input.dev_attr.attr,
+ &sensor_dev_attr_fan2_input.dev_attr.attr,
+ &sensor_dev_attr_fan3_input.dev_attr.attr,
+ &sensor_dev_attr_fan4_input.dev_attr.attr,
+ &sensor_dev_attr_fan5_input.dev_attr.attr,
+ &sensor_dev_attr_fan6_input.dev_attr.attr,
+ &sensor_dev_attr_fan7_input.dev_attr.attr,
+ &sensor_dev_attr_fan8_input.dev_attr.attr,
+ &sensor_dev_attr_fan9_input.dev_attr.attr,
+ &sensor_dev_attr_fan10_input.dev_attr.attr,
+ &sensor_dev_attr_fan11_input.dev_attr.attr,
+ &sensor_dev_attr_fan12_input.dev_attr.attr,
+ &sensor_dev_attr_fan13_input.dev_attr.attr,
+ &sensor_dev_attr_fan14_input.dev_attr.attr,
+ &sensor_dev_attr_pwm1.dev_attr.attr,
+ &sensor_dev_attr_pwm2.dev_attr.attr,
+ &sensor_dev_attr_pwm3.dev_attr.attr,
+ &sensor_dev_attr_pwm4.dev_attr.attr,
+ &sensor_dev_attr_pwm5.dev_attr.attr,
+ &sensor_dev_attr_pwm6.dev_attr.attr,
+ &sensor_dev_attr_pwm7.dev_attr.attr,
+ &sensor_dev_attr_pwm8.dev_attr.attr,
+ &sensor_dev_attr_pwm9.dev_attr.attr,
+ &sensor_dev_attr_pwm10.dev_attr.attr,
+ &sensor_dev_attr_pwm11.dev_attr.attr,
+ &sensor_dev_attr_pwm12.dev_attr.attr,
+ &sensor_dev_attr_pwm13.dev_attr.attr,
+ &sensor_dev_attr_pwm14.dev_attr.attr,
+ &sensor_dev_attr_fan1_min.dev_attr.attr,
+ &sensor_dev_attr_fan2_min.dev_attr.attr,
+ &sensor_dev_attr_fan3_min.dev_attr.attr,
+ &sensor_dev_attr_fan4_min.dev_attr.attr,
+ &sensor_dev_attr_fan5_min.dev_attr.attr,
+ &sensor_dev_attr_fan6_min.dev_attr.attr,
+ &sensor_dev_attr_fan7_min.dev_attr.attr,
+ &sensor_dev_attr_fan8_min.dev_attr.attr,
+ &sensor_dev_attr_fan9_min.dev_attr.attr,
+ &sensor_dev_attr_fan10_min.dev_attr.attr,
+ &sensor_dev_attr_fan11_min.dev_attr.attr,
+ &sensor_dev_attr_fan12_min.dev_attr.attr,
+ &sensor_dev_attr_fan13_min.dev_attr.attr,
+ &sensor_dev_attr_fan14_min.dev_attr.attr,
+ &sensor_dev_attr_fan1_max.dev_attr.attr,
+ &sensor_dev_attr_fan2_max.dev_attr.attr,
+ &sensor_dev_attr_fan3_max.dev_attr.attr,
+ &sensor_dev_attr_fan4_max.dev_attr.attr,
+ &sensor_dev_attr_fan5_max.dev_attr.attr,
+ &sensor_dev_attr_fan6_max.dev_attr.attr,
+ &sensor_dev_attr_fan7_max.dev_attr.attr,
+ &sensor_dev_attr_fan8_max.dev_attr.attr,
+ &sensor_dev_attr_fan9_max.dev_attr.attr,
+ &sensor_dev_attr_fan10_max.dev_attr.attr,
+ &sensor_dev_attr_fan11_max.dev_attr.attr,
+ &sensor_dev_attr_fan12_max.dev_attr.attr,
+ &sensor_dev_attr_fan13_max.dev_attr.attr,
+ &sensor_dev_attr_fan14_max.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group jnx_fan_group = {
+ .attrs = jnx_fan_attrs,
+ .is_visible = jnx_fan_is_visible,
+};
+__ATTRIBUTE_GROUPS(jnx_fan);
+
+static int jnx_fan_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct i2cs_fan_platform_data *pdata = dev_get_platdata(dev);
+ struct i2c_client *client;
+ struct jnx_fan_data *data;
+
+ if (!dev->parent)
+ return -ENODEV;
+
+ client = i2c_verify_client(dev->parent);
+ if (!client)
+ return -ENODEV;
+
+ data = devm_kzalloc(dev, sizeof(struct jnx_fan_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->regmap = devm_regmap_init_i2c(client, &jnx_fan_regmap_config);
+ if (IS_ERR(data->regmap)) {
+ dev_err(dev, "failed to allocate register map\n");
+ return PTR_ERR(data->regmap);
+ }
+
+ if (pdata) {
+ data->num_fans = pdata->num_fans;
+ data->factor = pdata->factor;
+ } else {
+ data->num_fans = NUM_FANS_PER_TRAY;
+ data->factor = FAN_TACH_FACTOR;
+ }
+
+ if (dev->of_node) {
+ of_property_read_u32(dev->of_node,
+ "num-fans", &data->num_fans);
+ of_property_read_u32(dev->of_node,
+ "tach-factor", &data->factor);
+ }
+
+ data->fan_index = -1;
+ mutex_init(&data->update_lock);
+
+ platform_set_drvdata(pdev, data);
+
+ data->hwmon_dev = hwmon_device_register_with_groups(dev->parent,
+ "i2cs_fan", data,
+ jnx_fan_groups);
+ return PTR_ERR_OR_ZERO(data->hwmon_dev);
+}
+
+static int jnx_fan_remove(struct platform_device *pdev)
+{
+ struct jnx_fan_data *data = platform_get_drvdata(pdev);
+
+ hwmon_device_unregister(data->hwmon_dev);
+
+ return 0;
+}
+
+static const struct of_device_id jnx_fan_of_match[] = {
+ { .compatible = "jnx,i2cs-fan-hwmon", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, jnx_fan_of_match);
+
+static struct platform_driver jnx_fan_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = of_match_ptr(jnx_fan_of_match),
+ },
+ .probe = jnx_fan_probe,
+ .remove = jnx_fan_remove,
+};
+
+module_platform_driver(jnx_fan_driver);
+
+MODULE_AUTHOR("Avirup Banerjee <abanerjee@xxxxxxxxxxx>");
+MODULE_DESCRIPTION("JNPR FAN driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/include/linux/platform_data/jnx-i2cs-fan.h b/include/linux/platform_data/jnx-i2cs-fan.h
new file mode 100644
index 0000000..b3fc8c2
--- /dev/null
+++ b/include/linux/platform_data/jnx-i2cs-fan.h
@@ -0,0 +1,13 @@
+/*
+ * i2cs-fan.h
+ */
+
+#ifndef I2CS_FAN_H
+#define I2CS_FAN_H
+
+struct i2cs_fan_platform_data {
+ int num_fans; /* Number of fans in tray */
+ int factor; /* fan speed multiplication factor */
+};
+
+#endif /* I2CS_FAN_H */
--
1.9.1