|
|
|
|
@@ -0,0 +1,753 @@
|
|
|
|
|
Driver for MAX6620 Fan sensor
|
|
|
|
|
|
|
|
|
|
From: Cumulus Networks <support@cumulusnetworks.com>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
drivers/hwmon/Kconfig | 10 +
|
|
|
|
|
drivers/hwmon/Makefile | 1
|
|
|
|
|
drivers/hwmon/max6620.c | 702 +++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
|
3 files changed, 713 insertions(+)
|
|
|
|
|
create mode 100644 drivers/hwmon/max6620.c
|
|
|
|
|
|
|
|
|
|
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
|
|
|
|
|
index 02d3d85..ca38e05 100644
|
|
|
|
|
--- a/drivers/hwmon/Kconfig
|
|
|
|
|
+++ b/drivers/hwmon/Kconfig
|
|
|
|
|
@@ -784,6 +784,16 @@ config SENSORS_MAX6650
|
|
|
|
|
This driver can also be built as a module. If so, the module
|
|
|
|
|
will be called max6650.
|
|
|
|
|
|
|
|
|
|
+config SENSORS_MAX6620
|
|
|
|
|
+ tristate "Maxim MAX6620 sensor chip"
|
|
|
|
|
+ depends on I2C
|
|
|
|
|
+ help
|
|
|
|
|
+ If you say yes here you get support for the MAX6620
|
|
|
|
|
+ sensor chips.
|
|
|
|
|
+
|
|
|
|
|
+ This driver can also be built as a module. If so, the module
|
|
|
|
|
+ will be called max6620.
|
|
|
|
|
+
|
|
|
|
|
config SENSORS_MAX6697
|
|
|
|
|
tristate "Maxim MAX6697 and compatibles"
|
|
|
|
|
depends on I2C
|
|
|
|
|
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
|
|
|
|
|
index 3dc0f02..8837a7b 100644
|
|
|
|
|
--- a/drivers/hwmon/Makefile
|
|
|
|
|
+++ b/drivers/hwmon/Makefile
|
|
|
|
|
@@ -111,6 +111,7 @@ obj-$(CONFIG_SENSORS_MAX197) += max197.o
|
|
|
|
|
obj-$(CONFIG_SENSORS_MAX6639) += max6639.o
|
|
|
|
|
obj-$(CONFIG_SENSORS_MAX6642) += max6642.o
|
|
|
|
|
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
|
|
|
|
|
+obj-$(CONFIG_SENSORS_MAX6620) += max6620.o
|
|
|
|
|
obj-$(CONFIG_SENSORS_MAX6697) += max6697.o
|
|
|
|
|
obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
|
|
|
|
|
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
|
|
|
|
|
diff --git a/drivers/hwmon/max6620.c b/drivers/hwmon/max6620.c
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000..3c337c7
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/drivers/hwmon/max6620.c
|
|
|
|
|
@@ -0,0 +1,702 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * max6620.c - Linux Kernel module for hardware monitoring.
|
|
|
|
|
+ *
|
|
|
|
|
+ * (C) 2012 by L. Grunenberg <contact@lgrunenberg.de>
|
|
|
|
|
+ *
|
|
|
|
|
+ * based on code written by :
|
|
|
|
|
+ * 2007 by Hans J. Koch <hjk@hansjkoch.de>
|
|
|
|
|
+ * John Morris <john.morris@spirentcom.com>
|
|
|
|
|
+ * Copyright (c) 2003 Spirent Communications
|
|
|
|
|
+ * and Claus Gindhart <claus.gindhart@kontron.com>
|
|
|
|
|
+ *
|
|
|
|
|
+ * This module has only been tested with the MAX6620 chip.
|
|
|
|
|
+ *
|
|
|
|
|
+ * The datasheet was last seen at:
|
|
|
|
|
+ *
|
|
|
|
|
+ * http://pdfserv.maxim-ic.com/en/ds/MAX6620.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, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+#include <linux/module.h>
|
|
|
|
|
+#include <linux/init.h>
|
|
|
|
|
+#include <linux/slab.h>
|
|
|
|
|
+#include <linux/jiffies.h>
|
|
|
|
|
+#include <linux/i2c.h>
|
|
|
|
|
+#include <linux/hwmon.h>
|
|
|
|
|
+#include <linux/hwmon-sysfs.h>
|
|
|
|
|
+#include <linux/err.h>
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Insmod parameters
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/* clock: The clock frequency of the chip the driver should assume */
|
|
|
|
|
+static int clock = 8192;
|
|
|
|
|
+
|
|
|
|
|
+module_param(clock, int, S_IRUGO);
|
|
|
|
|
+
|
|
|
|
|
+static const unsigned short normal_i2c[] = {0x0a, 0x1a, 0x2a, I2C_CLIENT_END};
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * MAX 6620 registers
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+#define MAX6620_REG_CONFIG 0x00
|
|
|
|
|
+#define MAX6620_REG_FAULT 0x01
|
|
|
|
|
+#define MAX6620_REG_CONF_FAN0 0x02
|
|
|
|
|
+#define MAX6620_REG_CONF_FAN1 0x03
|
|
|
|
|
+#define MAX6620_REG_CONF_FAN2 0x04
|
|
|
|
|
+#define MAX6620_REG_CONF_FAN3 0x05
|
|
|
|
|
+#define MAX6620_REG_DYN_FAN0 0x06
|
|
|
|
|
+#define MAX6620_REG_DYN_FAN1 0x07
|
|
|
|
|
+#define MAX6620_REG_DYN_FAN2 0x08
|
|
|
|
|
+#define MAX6620_REG_DYN_FAN3 0x09
|
|
|
|
|
+#define MAX6620_REG_TACH0 0x10
|
|
|
|
|
+#define MAX6620_REG_TACH1 0x12
|
|
|
|
|
+#define MAX6620_REG_TACH2 0x14
|
|
|
|
|
+#define MAX6620_REG_TACH3 0x16
|
|
|
|
|
+#define MAX6620_REG_VOLT0 0x18
|
|
|
|
|
+#define MAX6620_REG_VOLT1 0x1A
|
|
|
|
|
+#define MAX6620_REG_VOLT2 0x1C
|
|
|
|
|
+#define MAX6620_REG_VOLT3 0x1E
|
|
|
|
|
+#define MAX6620_REG_TAR0 0x20
|
|
|
|
|
+#define MAX6620_REG_TAR1 0x22
|
|
|
|
|
+#define MAX6620_REG_TAR2 0x24
|
|
|
|
|
+#define MAX6620_REG_TAR3 0x26
|
|
|
|
|
+#define MAX6620_REG_DAC0 0x28
|
|
|
|
|
+#define MAX6620_REG_DAC1 0x2A
|
|
|
|
|
+#define MAX6620_REG_DAC2 0x2C
|
|
|
|
|
+#define MAX6620_REG_DAC3 0x2E
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Config register bits
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+#define MAX6620_CFG_RUN 0x80
|
|
|
|
|
+#define MAX6620_CFG_POR 0x40
|
|
|
|
|
+#define MAX6620_CFG_TIMEOUT 0x20
|
|
|
|
|
+#define MAX6620_CFG_FULLFAN 0x10
|
|
|
|
|
+#define MAX6620_CFG_OSC 0x08
|
|
|
|
|
+#define MAX6620_CFG_WD_MASK 0x06
|
|
|
|
|
+#define MAX6620_CFG_WD_2 0x02
|
|
|
|
|
+#define MAX6620_CFG_WD_6 0x04
|
|
|
|
|
+#define MAX6620_CFG_WD10 0x06
|
|
|
|
|
+#define MAX6620_CFG_WD 0x01
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Failure status register bits
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+#define MAX6620_FAIL_TACH0 0x10
|
|
|
|
|
+#define MAX6620_FAIL_TACH1 0x20
|
|
|
|
|
+#define MAX6620_FAIL_TACH2 0x40
|
|
|
|
|
+#define MAX6620_FAIL_TACH3 0x80
|
|
|
|
|
+#define MAX6620_FAIL_MASK0 0x01
|
|
|
|
|
+#define MAX6620_FAIL_MASK1 0x02
|
|
|
|
|
+#define MAX6620_FAIL_MASK2 0x04
|
|
|
|
|
+#define MAX6620_FAIL_MASK3 0x08
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/* Minimum and maximum values of the FAN-RPM */
|
|
|
|
|
+#define FAN_RPM_MIN 240
|
|
|
|
|
+#define FAN_RPM_MAX 30000
|
|
|
|
|
+
|
|
|
|
|
+#define DIV_FROM_REG(reg) (1 << ((reg & 0xE0) >> 5))
|
|
|
|
|
+
|
|
|
|
|
+static int max6620_probe(struct i2c_client *client, const struct i2c_device_id *id);
|
|
|
|
|
+static int max6620_init_client(struct i2c_client *client);
|
|
|
|
|
+static int max6620_remove(struct i2c_client *client);
|
|
|
|
|
+static struct max6620_data *max6620_update_device(struct device *dev);
|
|
|
|
|
+
|
|
|
|
|
+static const u8 config_reg[] = {
|
|
|
|
|
+ MAX6620_REG_CONF_FAN0,
|
|
|
|
|
+ MAX6620_REG_CONF_FAN1,
|
|
|
|
|
+ MAX6620_REG_CONF_FAN2,
|
|
|
|
|
+ MAX6620_REG_CONF_FAN3,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static const u8 dyn_reg[] = {
|
|
|
|
|
+ MAX6620_REG_DYN_FAN0,
|
|
|
|
|
+ MAX6620_REG_DYN_FAN1,
|
|
|
|
|
+ MAX6620_REG_DYN_FAN2,
|
|
|
|
|
+ MAX6620_REG_DYN_FAN3,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static const u8 tach_reg[] = {
|
|
|
|
|
+ MAX6620_REG_TACH0,
|
|
|
|
|
+ MAX6620_REG_TACH1,
|
|
|
|
|
+ MAX6620_REG_TACH2,
|
|
|
|
|
+ MAX6620_REG_TACH3,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static const u8 volt_reg[] = {
|
|
|
|
|
+ MAX6620_REG_VOLT0,
|
|
|
|
|
+ MAX6620_REG_VOLT1,
|
|
|
|
|
+ MAX6620_REG_VOLT2,
|
|
|
|
|
+ MAX6620_REG_VOLT3,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static const u8 target_reg[] = {
|
|
|
|
|
+ MAX6620_REG_TAR0,
|
|
|
|
|
+ MAX6620_REG_TAR1,
|
|
|
|
|
+ MAX6620_REG_TAR2,
|
|
|
|
|
+ MAX6620_REG_TAR3,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static const u8 dac_reg[] = {
|
|
|
|
|
+ MAX6620_REG_DAC0,
|
|
|
|
|
+ MAX6620_REG_DAC1,
|
|
|
|
|
+ MAX6620_REG_DAC2,
|
|
|
|
|
+ MAX6620_REG_DAC3,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Driver data (common to all clients)
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+static const struct i2c_device_id max6620_id[] = {
|
|
|
|
|
+ { "max6620", 0 },
|
|
|
|
|
+ { }
|
|
|
|
|
+};
|
|
|
|
|
+MODULE_DEVICE_TABLE(i2c, max6620_id);
|
|
|
|
|
+
|
|
|
|
|
+static struct i2c_driver max6620_driver = {
|
|
|
|
|
+ .class = I2C_CLASS_HWMON,
|
|
|
|
|
+ .driver = {
|
|
|
|
|
+ .name = "max6620",
|
|
|
|
|
+ },
|
|
|
|
|
+ .probe = max6620_probe,
|
|
|
|
|
+ .remove = __devexit_p(max6620_remove),
|
|
|
|
|
+ .id_table = max6620_id,
|
|
|
|
|
+ .address_list = normal_i2c,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Client data (each client gets its own)
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+struct max6620_data {
|
|
|
|
|
+ struct device *hwmon_dev;
|
|
|
|
|
+ struct mutex update_lock;
|
|
|
|
|
+ int nr_fans;
|
|
|
|
|
+ char valid; /* zero until following fields are valid */
|
|
|
|
|
+ unsigned long last_updated; /* in jiffies */
|
|
|
|
|
+
|
|
|
|
|
+ /* register values */
|
|
|
|
|
+ u8 speed[4];
|
|
|
|
|
+ u8 config;
|
|
|
|
|
+ u8 fancfg[4];
|
|
|
|
|
+ u8 fandyn[4];
|
|
|
|
|
+ u8 tach[4];
|
|
|
|
|
+ u8 volt[4];
|
|
|
|
|
+ u8 target[4];
|
|
|
|
|
+ u8 dac[4];
|
|
|
|
|
+ u8 fault;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t get_fan(struct device *dev, struct device_attribute *devattr, char *buf) {
|
|
|
|
|
+
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+ struct max6620_data *data = max6620_update_device(dev);
|
|
|
|
|
+ int rpm;
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Calculation details:
|
|
|
|
|
+ *
|
|
|
|
|
+ * Each tachometer counts over an interval given by the "count"
|
|
|
|
|
+ * register (0.25, 0.5, 1 or 2 seconds). This module assumes
|
|
|
|
|
+ * that the fans produce two pulses per revolution (this seems
|
|
|
|
|
+ * to be the most common).
|
|
|
|
|
+ */
|
|
|
|
|
+ if(data->tach[attr->index] == 0 || data->tach[attr->index] == 255) {
|
|
|
|
|
+ rpm = 0;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ rpm = ((clock / (data->tach[attr->index] << 3)) * 30 * DIV_FROM_REG(data->fandyn[attr->index]));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return sprintf(buf, "%d\n", rpm);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t get_target(struct device *dev, struct device_attribute *devattr, char *buf) {
|
|
|
|
|
+
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+ struct max6620_data *data = max6620_update_device(dev);
|
|
|
|
|
+ int kscale, ktach, rpm;
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Use the datasheet equation:
|
|
|
|
|
+ *
|
|
|
|
|
+ * FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)]
|
|
|
|
|
+ *
|
|
|
|
|
+ * then multiply by 60 to give rpm.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+ kscale = DIV_FROM_REG(data->fandyn[attr->index]);
|
|
|
|
|
+ ktach = data->target[attr->index];
|
|
|
|
|
+ if(ktach == 0) {
|
|
|
|
|
+ rpm = 0;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ rpm = ((60 * kscale * clock) / (ktach << 3));
|
|
|
|
|
+ }
|
|
|
|
|
+ return sprintf(buf, "%d\n", rpm);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t set_target(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) {
|
|
|
|
|
+
|
|
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+ struct max6620_data *data = i2c_get_clientdata(client);
|
|
|
|
|
+ int kscale, ktach;
|
|
|
|
|
+ unsigned long rpm;
|
|
|
|
|
+ int err;
|
|
|
|
|
+
|
|
|
|
|
+ err = kstrtoul(buf, 10, &rpm);
|
|
|
|
|
+ if (err)
|
|
|
|
|
+ return err;
|
|
|
|
|
+
|
|
|
|
|
+ rpm = SENSORS_LIMIT(rpm, FAN_RPM_MIN, FAN_RPM_MAX);
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Divide the required speed by 60 to get from rpm to rps, then
|
|
|
|
|
+ * use the datasheet equation:
|
|
|
|
|
+ *
|
|
|
|
|
+ * KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+ mutex_lock(&data->update_lock);
|
|
|
|
|
+
|
|
|
|
|
+ kscale = DIV_FROM_REG(data->fandyn[attr->index]);
|
|
|
|
|
+ ktach = ((60 * kscale * clock) / rpm);
|
|
|
|
|
+ if (ktach < 0)
|
|
|
|
|
+ ktach = 0;
|
|
|
|
|
+ if (ktach > 255)
|
|
|
|
|
+ ktach = 255;
|
|
|
|
|
+ data->target[attr->index] = ktach;
|
|
|
|
|
+
|
|
|
|
|
+ i2c_smbus_write_byte_data(client, target_reg[attr->index], data->target[attr->index]);
|
|
|
|
|
+ i2c_smbus_write_byte_data(client, target_reg[attr->index]+0x01, 0x00);
|
|
|
|
|
+
|
|
|
|
|
+ mutex_unlock(&data->update_lock);
|
|
|
|
|
+
|
|
|
|
|
+ return count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Get/set the fan speed in open loop mode using pwm1 sysfs file.
|
|
|
|
|
+ * Speed is given as a relative value from 0 to 255, where 255 is maximum
|
|
|
|
|
+ * speed. Note that this is done by writing directly to the chip's DAC,
|
|
|
|
|
+ * it won't change the closed loop speed set by fan1_target.
|
|
|
|
|
+ * Also note that due to rounding errors it is possible that you don't read
|
|
|
|
|
+ * back exactly the value you have set.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t get_pwm(struct device *dev, struct device_attribute *devattr, char *buf) {
|
|
|
|
|
+
|
|
|
|
|
+ int pwm;
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+ struct max6620_data *data = max6620_update_device(dev);
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Useful range for dac is 0-180 for 12V fans and 0-76 for 5V fans.
|
|
|
|
|
+ * Lower DAC values mean higher speeds.
|
|
|
|
|
+ */
|
|
|
|
|
+ pwm = ((int)data->volt[attr->index]);
|
|
|
|
|
+
|
|
|
|
|
+ if (pwm < 0)
|
|
|
|
|
+ pwm = 0;
|
|
|
|
|
+
|
|
|
|
|
+ return sprintf(buf, "%d\n", pwm);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) {
|
|
|
|
|
+
|
|
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+ struct max6620_data *data = i2c_get_clientdata(client);
|
|
|
|
|
+ unsigned long pwm;
|
|
|
|
|
+ int err;
|
|
|
|
|
+
|
|
|
|
|
+ err = kstrtoul(buf, 10, &pwm);
|
|
|
|
|
+ if (err)
|
|
|
|
|
+ return err;
|
|
|
|
|
+
|
|
|
|
|
+ pwm = SENSORS_LIMIT(pwm, 0, 255);
|
|
|
|
|
+
|
|
|
|
|
+ mutex_lock(&data->update_lock);
|
|
|
|
|
+
|
|
|
|
|
+ data->dac[attr->index] = pwm;
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ i2c_smbus_write_byte_data(client, dac_reg[attr->index], data->dac[attr->index]);
|
|
|
|
|
+ i2c_smbus_write_byte_data(client, dac_reg[attr->index]+1, 0x00);
|
|
|
|
|
+
|
|
|
|
|
+ mutex_unlock(&data->update_lock);
|
|
|
|
|
+
|
|
|
|
|
+ return count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Get/Set controller mode:
|
|
|
|
|
+ * Possible values:
|
|
|
|
|
+ * 0 = Fan always on
|
|
|
|
|
+ * 1 = Open loop, Voltage is set according to speed, not regulated.
|
|
|
|
|
+ * 2 = Closed loop, RPM for all fans regulated by fan1 tachometer
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t get_enable(struct device *dev, struct device_attribute *devattr, char *buf) {
|
|
|
|
|
+
|
|
|
|
|
+ struct max6620_data *data = max6620_update_device(dev);
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+ int mode = (data->fancfg[attr->index] & 0x80 ) >> 7;
|
|
|
|
|
+ int sysfs_modes[2] = {1, 2};
|
|
|
|
|
+
|
|
|
|
|
+ return sprintf(buf, "%d\n", sysfs_modes[mode]);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t set_enable(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) {
|
|
|
|
|
+
|
|
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
|
+ struct max6620_data *data = i2c_get_clientdata(client);
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+ int max6620_modes[3] = {0, 1, 0};
|
|
|
|
|
+ unsigned long mode;
|
|
|
|
|
+ int err;
|
|
|
|
|
+
|
|
|
|
|
+ err = kstrtoul(buf, 10, &mode);
|
|
|
|
|
+ if (err)
|
|
|
|
|
+ return err;
|
|
|
|
|
+
|
|
|
|
|
+ if (mode > 2)
|
|
|
|
|
+ return -EINVAL;
|
|
|
|
|
+
|
|
|
|
|
+ mutex_lock(&data->update_lock);
|
|
|
|
|
+
|
|
|
|
|
+ data->fancfg[attr->index] = i2c_smbus_read_byte_data(client, config_reg[attr->index]);
|
|
|
|
|
+ data->fancfg[attr->index] = (data->fancfg[attr->index] & ~0x80)
|
|
|
|
|
+ | (max6620_modes[mode] << 7);
|
|
|
|
|
+
|
|
|
|
|
+ i2c_smbus_write_byte_data(client, config_reg[attr->index], data->fancfg[attr->index]);
|
|
|
|
|
+
|
|
|
|
|
+ mutex_unlock(&data->update_lock);
|
|
|
|
|
+
|
|
|
|
|
+ return count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Read/write functions for fan1_div sysfs file. The MAX6620 has no such
|
|
|
|
|
+ * divider. We handle this by converting between divider and counttime:
|
|
|
|
|
+ *
|
|
|
|
|
+ * (counttime == k) <==> (divider == 2^k), k = 0, 1, 2, 3, 4 or 5
|
|
|
|
|
+ *
|
|
|
|
|
+ * Lower values of k allow to connect a faster fan without the risk of
|
|
|
|
|
+ * counter overflow. The price is lower resolution. You can also set counttime
|
|
|
|
|
+ * using the module parameter. Note that the module parameter "prescaler" also
|
|
|
|
|
+ * influences the behaviour. Unfortunately, there's no sysfs attribute
|
|
|
|
|
+ * defined for that. See the data sheet for details.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t get_div(struct device *dev, struct device_attribute *devattr, char *buf) {
|
|
|
|
|
+
|
|
|
|
|
+ struct max6620_data *data = max6620_update_device(dev);
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+
|
|
|
|
|
+ return sprintf(buf, "%d\n", DIV_FROM_REG(data->fandyn[attr->index]));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t set_div(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) {
|
|
|
|
|
+
|
|
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
|
+ struct max6620_data *data = i2c_get_clientdata(client);
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+ unsigned long div;
|
|
|
|
|
+ int err;
|
|
|
|
|
+ u8 div_bin;
|
|
|
|
|
+
|
|
|
|
|
+ err = kstrtoul(buf, 10, &div);
|
|
|
|
|
+ if (err)
|
|
|
|
|
+ return err;
|
|
|
|
|
+
|
|
|
|
|
+ mutex_lock(&data->update_lock);
|
|
|
|
|
+ switch (div) {
|
|
|
|
|
+ case 1:
|
|
|
|
|
+ div_bin = 0;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 2:
|
|
|
|
|
+ div_bin = 1;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 4:
|
|
|
|
|
+ div_bin = 2;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 8:
|
|
|
|
|
+ div_bin = 3;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 16:
|
|
|
|
|
+ div_bin = 4;
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 32:
|
|
|
|
|
+ div_bin = 5;
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ mutex_unlock(&data->update_lock);
|
|
|
|
|
+ return -EINVAL;
|
|
|
|
|
+ }
|
|
|
|
|
+ data->fandyn[attr->index] &= 0x1F;
|
|
|
|
|
+ data->fandyn[attr->index] |= div_bin << 5;
|
|
|
|
|
+ i2c_smbus_write_byte_data(client, dyn_reg[attr->index], data->fandyn[attr->index]);
|
|
|
|
|
+ mutex_unlock(&data->update_lock);
|
|
|
|
|
+
|
|
|
|
|
+ return count;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Get alarm stati:
|
|
|
|
|
+ * Possible values:
|
|
|
|
|
+ * 0 = no alarm
|
|
|
|
|
+ * 1 = alarm
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+static ssize_t get_alarm(struct device *dev, struct device_attribute *devattr, char *buf) {
|
|
|
|
|
+
|
|
|
|
|
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
|
|
|
|
+ struct max6620_data *data = max6620_update_device(dev);
|
|
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
|
+ int alarm = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (data->fault & (1 << attr->index)) {
|
|
|
|
|
+ mutex_lock(&data->update_lock);
|
|
|
|
|
+ alarm = 1;
|
|
|
|
|
+ data->fault &= ~(1 << attr->index);
|
|
|
|
|
+ data->fault |= i2c_smbus_read_byte_data(client,
|
|
|
|
|
+ MAX6620_REG_FAULT);
|
|
|
|
|
+ mutex_unlock(&data->update_lock);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return sprintf(buf, "%d\n", alarm);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan, NULL, 0);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan, NULL, 1);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, get_fan, NULL, 2);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, get_fan, NULL, 3);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan1_target, S_IWUSR | S_IRUGO, get_target, set_target, 0);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan1_div, S_IWUSR | S_IRUGO, get_div, set_div, 0);
|
|
|
|
|
+// static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, get_enable, set_enable, 0);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 0);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan2_target, S_IWUSR | S_IRUGO, get_target, set_target, 1);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan2_div, S_IWUSR | S_IRUGO, get_div, set_div, 1);
|
|
|
|
|
+// static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, get_enable, set_enable, 1);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 1);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan3_target, S_IWUSR | S_IRUGO, get_target, set_target, 2);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan3_div, S_IWUSR | S_IRUGO, get_div, set_div, 2);
|
|
|
|
|
+// static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, get_enable, set_enable, 2);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 2);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan4_target, S_IWUSR | S_IRUGO, get_target, set_target, 3);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(fan4_div, S_IWUSR | S_IRUGO, get_div, set_div, 3);
|
|
|
|
|
+// static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, get_enable, set_enable, 3);
|
|
|
|
|
+static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, get_pwm, set_pwm, 3);
|
|
|
|
|
+
|
|
|
|
|
+static struct attribute *max6620_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_fan1_target.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_fan1_div.dev_attr.attr,
|
|
|
|
|
+// &sensor_dev_attr_pwm1_enable.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_pwm1.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_fan2_target.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_fan2_div.dev_attr.attr,
|
|
|
|
|
+// &sensor_dev_attr_pwm2_enable.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_pwm2.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_fan3_target.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_fan3_div.dev_attr.attr,
|
|
|
|
|
+// &sensor_dev_attr_pwm3_enable.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_pwm3.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_fan4_target.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_fan4_div.dev_attr.attr,
|
|
|
|
|
+// &sensor_dev_attr_pwm4_enable.dev_attr.attr,
|
|
|
|
|
+ &sensor_dev_attr_pwm4.dev_attr.attr,
|
|
|
|
|
+ NULL
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+static struct attribute_group max6620_attr_grp = {
|
|
|
|
|
+ .attrs = max6620_attrs,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/*
|
|
|
|
|
+ * Real code
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+static int __devinit max6620_probe(struct i2c_client *client, const struct i2c_device_id *id) {
|
|
|
|
|
+
|
|
|
|
|
+ struct max6620_data *data;
|
|
|
|
|
+ int err;
|
|
|
|
|
+
|
|
|
|
|
+ data = devm_kzalloc(&client->dev, sizeof(struct max6620_data), GFP_KERNEL);
|
|
|
|
|
+ if (!data) {
|
|
|
|
|
+ dev_err(&client->dev, "out of memory.\n");
|
|
|
|
|
+ return -ENOMEM;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ i2c_set_clientdata(client, data);
|
|
|
|
|
+ mutex_init(&data->update_lock);
|
|
|
|
|
+ data->nr_fans = id->driver_data;
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Initialize the max6620 chip
|
|
|
|
|
+ */
|
|
|
|
|
+ dev_info(&client->dev, "About to initialize module\n");
|
|
|
|
|
+
|
|
|
|
|
+ err = max6620_init_client(client);
|
|
|
|
|
+ if (err)
|
|
|
|
|
+ return err;
|
|
|
|
|
+ dev_info(&client->dev, "Module initialized\n");
|
|
|
|
|
+
|
|
|
|
|
+ err = sysfs_create_group(&client->dev.kobj, &max6620_attr_grp);
|
|
|
|
|
+ if (err)
|
|
|
|
|
+ return err;
|
|
|
|
|
+dev_info(&client->dev, "Sysfs entries created\n");
|
|
|
|
|
+
|
|
|
|
|
+ data->hwmon_dev = hwmon_device_register(&client->dev);
|
|
|
|
|
+ if (!IS_ERR(data->hwmon_dev))
|
|
|
|
|
+ return 0;
|
|
|
|
|
+
|
|
|
|
|
+ err = PTR_ERR(data->hwmon_dev);
|
|
|
|
|
+ dev_err(&client->dev, "error registering hwmon device.\n");
|
|
|
|
|
+
|
|
|
|
|
+ sysfs_remove_group(&client->dev.kobj, &max6620_attr_grp);
|
|
|
|
|
+ return err;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int __devexit max6620_remove(struct i2c_client *client) {
|
|
|
|
|
+
|
|
|
|
|
+ struct max6620_data *data = i2c_get_clientdata(client);
|
|
|
|
|
+
|
|
|
|
|
+ hwmon_device_unregister(data->hwmon_dev);
|
|
|
|
|
+
|
|
|
|
|
+ sysfs_remove_group(&client->dev.kobj, &max6620_attr_grp);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static int max6620_init_client(struct i2c_client *client) {
|
|
|
|
|
+
|
|
|
|
|
+ struct max6620_data *data = i2c_get_clientdata(client);
|
|
|
|
|
+ int config;
|
|
|
|
|
+ int err = -EIO;
|
|
|
|
|
+ int i;
|
|
|
|
|
+
|
|
|
|
|
+ config = i2c_smbus_read_byte_data(client, MAX6620_REG_CONFIG);
|
|
|
|
|
+
|
|
|
|
|
+ if (config < 0) {
|
|
|
|
|
+ dev_err(&client->dev, "Error reading config, aborting.\n");
|
|
|
|
|
+ return err;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ if (i2c_smbus_write_byte_data(client, MAX6620_REG_CONFIG, config)) {
|
|
|
|
|
+ dev_err(&client->dev, "Config write error, aborting.\n");
|
|
|
|
|
+ return err;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ data->config = config;
|
|
|
|
|
+ for (i = 0; i < 4; i++) {
|
|
|
|
|
+ data->fancfg[i] = i2c_smbus_read_byte_data(client, config_reg[i]);
|
|
|
|
|
+ data->fancfg[i] |= 0x80; // enable TACH monitoring
|
|
|
|
|
+ i2c_smbus_write_byte_data(client, config_reg[i], data->fancfg[i]);
|
|
|
|
|
+ data->fandyn[i] = i2c_smbus_read_byte_data(client, dyn_reg[i]);
|
|
|
|
|
+ data-> fandyn[i] |= 0x1C;
|
|
|
|
|
+ i2c_smbus_write_byte_data(client, dyn_reg[i], data->fandyn[i]);
|
|
|
|
|
+ data->tach[i] = i2c_smbus_read_byte_data(client, tach_reg[i]);
|
|
|
|
|
+ data->volt[i] = i2c_smbus_read_byte_data(client, volt_reg[i]);
|
|
|
|
|
+ data->target[i] = i2c_smbus_read_byte_data(client, target_reg[i]);
|
|
|
|
|
+ data->dac[i] = i2c_smbus_read_byte_data(client, dac_reg[i]);
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+static struct max6620_data *max6620_update_device(struct device *dev)
|
|
|
|
|
+{
|
|
|
|
|
+ int i;
|
|
|
|
|
+ struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
|
+ struct max6620_data *data = i2c_get_clientdata(client);
|
|
|
|
|
+
|
|
|
|
|
+ mutex_lock(&data->update_lock);
|
|
|
|
|
+
|
|
|
|
|
+ if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
|
|
|
|
+
|
|
|
|
|
+ for (i = 0; i < 4; i++) {
|
|
|
|
|
+ data->fancfg[i] = i2c_smbus_read_byte_data(client, config_reg[i]);
|
|
|
|
|
+ data->fandyn[i] = i2c_smbus_read_byte_data(client, dyn_reg[i]);
|
|
|
|
|
+ data->tach[i] = i2c_smbus_read_byte_data(client, tach_reg[i]);
|
|
|
|
|
+ data->volt[i] = i2c_smbus_read_byte_data(client, volt_reg[i]);
|
|
|
|
|
+ data->target[i] = i2c_smbus_read_byte_data(client, target_reg[i]);
|
|
|
|
|
+ data->dac[i] = i2c_smbus_read_byte_data(client, dac_reg[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Alarms are cleared on read in case the condition that
|
|
|
|
|
+ * caused the alarm is removed. Keep the value latched here
|
|
|
|
|
+ * for providing the register through different alarm files.
|
|
|
|
|
+ */
|
|
|
|
|
+ u8 fault_reg;
|
|
|
|
|
+ fault_reg = i2c_smbus_read_byte_data(client, MAX6620_REG_FAULT);
|
|
|
|
|
+ data->fault |= (fault_reg >> 4) & (fault_reg & 0x0F);
|
|
|
|
|
+
|
|
|
|
|
+ data->last_updated = jiffies;
|
|
|
|
|
+ data->valid = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ mutex_unlock(&data->update_lock);
|
|
|
|
|
+
|
|
|
|
|
+ return data;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module_i2c_driver(max6620_driver);
|
|
|
|
|
+
|
|
|
|
|
+static int __init max6620_init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ return i2c_add_driver(&max6620_driver);
|
|
|
|
|
+}
|
|
|
|
|
+module_init(max6620_init);
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * sht21_init() - clean up driver
|
|
|
|
|
+ *
|
|
|
|
|
+ * Called when module is removed.
|
|
|
|
|
+ */
|
|
|
|
|
+static void __exit max6620_exit(void)
|
|
|
|
|
+{
|
|
|
|
|
+ i2c_del_driver(&max6620_driver);
|
|
|
|
|
+}
|
|
|
|
|
+module_exit(max6620_exit);
|
|
|
|
|
+
|
|
|
|
|
+MODULE_AUTHOR("Lucas Grunenberg");
|
|
|
|
|
+MODULE_DESCRIPTION("MAX6620 sensor driver");
|
|
|
|
|
+MODULE_LICENSE("GPL");
|