[PATCH] Add Nuvoton NCT7904 hwmon driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Tested on Kontron COMe-cSE6 board

Signed-off-by: Vadim V. Vlasov <vvlasov@xxxxxxxxxxxxx>
---
 drivers/hwmon/Kconfig   |  10 +
 drivers/hwmon/Makefile  |   1 +
 drivers/hwmon/nct7904.c | 551
++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 562 insertions(+)
 create mode 100644 drivers/hwmon/nct7904.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 110fade..d0d5988 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1145,6 +1145,16 @@ config SENSORS_NCT7802
 	  This driver can also be built as a module.  If so, the module
 	  will be called nct7802.
 
+config SENSORS_NCT7904
+	tristate "Nuvoton NCT7904"
+	depends on X86 && I2C
+	help
+	  If you say yes here you get support for the Nuvoton NCT7904
+	  hardware monitoring chip, including manual fan speed control.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called nct7904.
+
 config SENSORS_PCF8591
 	tristate "Philips PCF8591 ADC/DAC"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 6c94147..b4a40f1 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -120,6 +120,7 @@ obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) +=
menf21bmc_hwmon.o
 obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
 obj-$(CONFIG_SENSORS_NCT6775)	+= nct6775.o
 obj-$(CONFIG_SENSORS_NCT7802)	+= nct7802.o
+obj-$(CONFIG_SENSORS_NCT7904)	+= nct7904.o
 obj-$(CONFIG_SENSORS_NTC_THERMISTOR)	+= ntc_thermistor.o
 obj-$(CONFIG_SENSORS_PC87360)	+= pc87360.o
 obj-$(CONFIG_SENSORS_PC87427)	+= pc87427.o
diff --git a/drivers/hwmon/nct7904.c b/drivers/hwmon/nct7904.c
new file mode 100644
index 0000000..84e524b
--- /dev/null
+++ b/drivers/hwmon/nct7904.c
@@ -0,0 +1,551 @@
+/*
+ * nct7904.c - driver for Nuvoton NCT7904D.
+ *
+ * Copyright (c) 2015 Kontron
+ * Author: Vadim V. Vlasov <vvlasov@xxxxxxxxxxxxx>
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+
+#define VENDOR_ID_REG		0x7A	/* Any bank */
+#define NUVOTON_ID		0x50
+#define CHIP_ID_REG		0x7B	/* Any bank */
+#define NCT7904_ID		0xC5
+#define VERSION_ID_REG		0x7C	/* Any bank */
+
+#define BANK_SEL_REG		0xFF
+#define BANK_0			0x00
+#define BANK_1			0x01
+#define BANK_2			0x02
+#define BANK_3			0x03
+#define BANK_4			0x04
+#define BANK_MAX		0x04
+
+#define FANIN_MAX		12	/* Counted from 1 */
+#define VSEN_MAX		21	/* VSEN1..14, 3VDD, VBAT, V3VSB,
+					   LTD (not a voltage), VSEN17..19 */
+#define FANCTL_MAX		4	/* Counted from 1 */
+#define TCPU_MAX		8	/* Counted from 1 */
+#define TEMP_MAX		4	/* Counted from 1 */
+
+#define VT_ADC_CTRL0_REG	0x20	/* Bank 0 */
+#define VT_ADC_CTRL1_REG	0x21	/* Bank 0 */
+#define VT_ADC_CTRL2_REG	0x22	/* Bank 0 */
+#define FANIN_CTRL0_REG		0x24
+#define FANIN_CTRL1_REG		0x25
+#define DTS_T_CTRL0_REG		0x26
+#define DTS_T_CTRL1_REG		0x27
+#define VT_ADC_MD_REG		0x2E
+
+#define VSEN1_HV_REG		0x40	/* Bank 0; 2 regs (HV/LV) per sensor */
+#define TEMP_CH1_HV_REG		0x42	/* Bank 0; same as VSEN2_HV */
+#define FANIN1_HV_REG		0x80	/* Bank 0; 2 regs (HV/LV) per sensor */
+#define T_CPU1_HV_REG		0xA0	/* Bank 0; 2 regs (HV/LV) per sensor */
+
+#define PRTS_REG		0x03	/* Bank 2 */
+#define FANCTL1_FMR_REG		0x00	/* Bank 3; 1 reg per channel */
+#define FANCTL1_OUT_REG		0x10	/* Bank 3; 1 reg per channel */
+
+
+const unsigned short normal_i2c[] = {
+	0x2d, 0x2e, I2C_CLIENT_END
+};
+
+
+struct nct7904_data {
+	struct i2c_client *client;
+	struct device *hwmon_dev;
+	struct mutex lock;
+	int bank_sel;
+};
+
+/*
+ * Implemented features:
+ * - Fan inputs (rpm)
+ * - Voltage sensors (mV)
+ * - DTS/TSI and local temperature sensors (degree Celsius)
+ * - Fan PWM control
+ * - Setting Fan to manual mode
+ *
+ * Not implemented features:
+ * - SmartFan control
+ * - Watchdog
+ * - GPIO
+ * - external temperature sensors
+ * - SMI
+ * - min/max values
+ * - many other...
+ */
+
+/* Access functions */
+/* Read 1-byte register. Returns unsigned reg or -ERRNO on error. */
+static int nct7904_read_reg(struct nct7904_data *data,
+	unsigned bank, unsigned reg)
+{
+	struct i2c_client *client = data->client;
+	int ret;
+
+	if (bank > BANK_MAX || reg > 255)
+		return -EINVAL;
+	mutex_lock(&data->lock);
+	if (data->bank_sel != bank) {
+		ret = i2c_smbus_write_byte_data(client, BANK_SEL_REG, bank);
+		if (ret < 0) {
+			data->bank_sel = -1;
+			goto out;
+		}
+		data->bank_sel = bank;
+	}
+	ret = i2c_smbus_read_byte_data(client, reg);
+
+out:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+/* Read 2-byte register into the buffer. Returns 0 or -ERRNO on error.
*/
+static int nct7904_read_reg16(struct nct7904_data *data,
+	unsigned bank, unsigned reg, u8 *buf)
+{
+	struct i2c_client *client = data->client;
+	int ret;
+
+	if (bank > BANK_MAX || reg > 254)
+		return -EINVAL;
+	buf[0] = buf[1] = 0;
+
+	mutex_lock(&data->lock);
+	if (data->bank_sel != bank) {
+		ret = i2c_smbus_write_byte_data(client, BANK_SEL_REG, bank);
+		if (ret < 0) {
+			data->bank_sel = -1;
+			goto out;
+		}
+		data->bank_sel = bank;
+	}
+	ret = i2c_smbus_read_byte_data(client, reg);
+	if (ret >= 0) {
+		buf[0] = ret;
+		ret = i2c_smbus_read_byte_data(client, reg + 1);
+		if (ret >= 0) {
+			buf[1] = ret;
+			ret = 0;
+		}
+	}
+
+out:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+/* Write 1-byte register. Returns 0 or -ERRNO on error. */
+static int nct7904_write_reg(struct nct7904_data *data,
+	unsigned bank, unsigned reg, u8 val)
+{
+	struct i2c_client *client = data->client;
+	int ret;
+
+	if (bank > BANK_MAX || reg > 255)
+		return -EINVAL;
+	mutex_lock(&data->lock);
+	if (data->bank_sel != bank) {
+		ret = i2c_smbus_write_byte_data(client, BANK_SEL_REG, bank);
+		if (ret < 0) {
+			data->bank_sel = -1;
+			goto out;
+		}
+		data->bank_sel = bank;
+	}
+	ret = i2c_smbus_write_byte_data(client, reg, val);
+
+out:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+/* FANIN ATTR */
+static ssize_t
+show_fan(struct device *dev,
+	struct device_attribute *devattr, char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct nct7904_data *data = dev_get_drvdata(dev);
+	int ret;
+	u8 regs[2];
+	unsigned cnt, rpm;
+
+	ret = nct7904_read_reg16(data, BANK_0, FANIN1_HV_REG + index * 2,
regs);
+	if (ret < 0)
+		return ret;
+	cnt = (regs[0] << 5) | (regs[1] & 0x1f);
+	if (cnt == 0x1fff)
+		rpm = 0;
+	else
+		rpm = 1350000 / cnt;
+	return sprintf(buf, "%d\n", rpm);
+}
+
+#define FANIN_ATTR(i)	\
+	SENSOR_ATTR(fan##i##_input, S_IRUGO, show_fan, NULL, i - 1)
+
+static struct sensor_device_attribute nct7904_fanin_attrs[] = {
+	FANIN_ATTR(1),
+	FANIN_ATTR(2),
+	FANIN_ATTR(3),
+	FANIN_ATTR(4),
+	FANIN_ATTR(5),
+	FANIN_ATTR(6),
+	FANIN_ATTR(7),
+	FANIN_ATTR(8),
+	FANIN_ATTR(9),
+	FANIN_ATTR(10),
+	FANIN_ATTR(11),
+	FANIN_ATTR(12),
+};
+
+
+/* VSEN ATTR */
+static ssize_t
+show_voltage(struct device *dev,
+	struct device_attribute *devattr, char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct nct7904_data *data = dev_get_drvdata(dev);
+	int ret;
+	u8 regs[2];
+	s8 t_hi;
+	unsigned cnt;
+	int volt;
+
+	ret = nct7904_read_reg16(data, BANK_0, VSEN1_HV_REG + index * 2,
regs);
+	if (ret < 0)
+		return ret;
+	cnt = (regs[0] << 3) | (regs[1] & 0x7);
+	if (index < 14)
+		volt = cnt * 2; /* 0.002V scale */
+	else if (index == 17) {
+		/* not a voltage but local temp (signed) */
+		t_hi = regs[0];
+		volt = ((t_hi << 3) | (regs[1] & 0x7)) * 125;
+	} else
+		volt = cnt * 6; /* 0.006V scale */
+
+	return sprintf(buf, "%d\n", volt);
+}
+
+#define VSEN_ATTR(i)	\
+	SENSOR_ATTR(in##i##_input, S_IRUGO, show_voltage, NULL, i - 1)
+
+#define VSEN_ATTR_NAME(name, i)	\
+	SENSOR_ATTR(name##_input, S_IRUGO, show_voltage, NULL, i - 1)
+
+static struct sensor_device_attribute nct7904_vsen_attrs[] = {
+	VSEN_ATTR(1),
+	VSEN_ATTR(2),
+	VSEN_ATTR(3),
+	VSEN_ATTR(4),
+	VSEN_ATTR(5),
+	VSEN_ATTR(6),
+	VSEN_ATTR(7),
+	VSEN_ATTR(8),
+	VSEN_ATTR(9),
+	VSEN_ATTR(10),
+	VSEN_ATTR(11),
+	VSEN_ATTR(12),
+	VSEN_ATTR(13),
+	VSEN_ATTR(14),
+	VSEN_ATTR(15),			/* 3VDD */
+	VSEN_ATTR(16),			/* VBAT */
+	VSEN_ATTR_NAME(in20, 17),	/* 3VSB */
+	VSEN_ATTR_NAME(temp1, 18),	/* local temp */
+	VSEN_ATTR_NAME(in17, 19),
+	VSEN_ATTR_NAME(in18, 20),
+	VSEN_ATTR_NAME(in19, 21),
+};
+
+/* CPU_TEMP ATTR */
+static ssize_t
+show_tcpu(struct device *dev,
+	struct device_attribute *devattr, char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct nct7904_data *data = dev_get_drvdata(dev);
+	int ret;
+	s8 regs[2]; /* Signed! */
+	int temp;
+
+	ret = nct7904_read_reg16(data, BANK_0, T_CPU1_HV_REG + index * 2,
regs);
+	if (ret < 0)
+		return ret;
+	temp = ((regs[0] << 3) | (regs[1] & 0x7)) * 125;
+	return sprintf(buf, "%d\n", temp);
+}
+
+/* "temp1_input" reserved for local temp */
+#define T_CPU_ATTR(i)	\
+	SENSOR_ATTR(temp##i##_input, S_IRUGO, show_tcpu, NULL, i - 2)
+
+static struct sensor_device_attribute nct7904_tcpu_attrs[] = {
+	T_CPU_ATTR(2),
+	T_CPU_ATTR(3),
+	T_CPU_ATTR(4),
+	T_CPU_ATTR(5),
+	T_CPU_ATTR(6),
+	T_CPU_ATTR(7),
+	T_CPU_ATTR(8),
+	T_CPU_ATTR(9),
+};
+
+/************************PWM
ATTR******************************************/
+static ssize_t
+store_pwm(struct device *dev,
+	  struct device_attribute *devattr, const char *buf, size_t count)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct nct7904_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 = nct7904_write_reg(data, BANK_3, FANCTL1_OUT_REG + index, val);
+
+	return ret ? ret : count;
+}
+
+static ssize_t
+show_pwm(struct device *dev, struct device_attribute *devattr, char
*buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct nct7904_data *data = dev_get_drvdata(dev);
+	int val;
+
+	val = nct7904_read_reg(data, BANK_3, FANCTL1_OUT_REG + index);
+	if (val < 0)
+		return val;
+
+	return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t
+store_mode(struct device *dev,
+	  struct device_attribute *devattr, const char *buf, size_t count)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct nct7904_data *data = dev_get_drvdata(dev);
+	unsigned long val;
+	int ret;
+
+	if (kstrtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+	switch (val) {
+	case 0:
+	case 1:
+	case 2:
+	case 4:
+	case 8:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = nct7904_write_reg(data, BANK_3, FANCTL1_FMR_REG + index, val);
+
+	return ret ? ret : count;
+}
+
+static ssize_t
+show_mode(struct device *dev, struct device_attribute *devattr, char
*buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	struct nct7904_data *data = dev_get_drvdata(dev);
+	int val;
+
+	val = nct7904_read_reg(data, BANK_3, FANCTL1_FMR_REG + index);
+	if (val < 0)
+		return val;
+
+	return sprintf(buf, "%d\n", val);
+}
+
+#define FANCTL_ATTRS(index)	\
+	SENSOR_ATTR(fan##index##_pwm, S_IRUGO | S_IWUSR,	\
+		show_pwm, store_pwm, index - 1),		\
+	SENSOR_ATTR(fan##index##_mode, S_IRUGO | S_IWUSR,	\
+		show_mode, store_mode, index - 1)
+
+/* 2 attributes per channel: pwm and mode */
+static struct sensor_device_attribute nct7904_fanctl_attrs[] = {
+	FANCTL_ATTRS(1),
+	FANCTL_ATTRS(2),
+	FANCTL_ATTRS(3),
+	FANCTL_ATTRS(4),
+};
+
+
+static struct attribute *nct7904_attrs
+	[FANIN_MAX + VSEN_MAX + 2*FANCTL_MAX + TCPU_MAX + TEMP_MAX + 1];
+ATTRIBUTE_GROUPS(nct7904);
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int
+nct7904_detect(struct i2c_client *client, struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE)
+	    && !i2c_check_functionality(adapter,
+					I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
+		return -ENODEV;
+
+	/* Determine the chip type. */
+	if (!(i2c_smbus_read_byte_data(client, VENDOR_ID_REG) == NUVOTON_ID)
+	    || !(i2c_smbus_read_byte_data(client, CHIP_ID_REG) == NCT7904_ID))
+		return -ENODEV;
+
+	strlcpy(info->type, "nct7904", I2C_NAME_SIZE);
+
+	return 0;
+}
+
+static int
+nct7904_probe(struct i2c_client *client, const struct i2c_device_id
*id)
+{
+	struct nct7904_data *data;
+	int ret;
+	int i, j;
+	u32 mask;
+	u8 buf[2];
+
+	data = devm_kzalloc(&client->dev,
+			sizeof(struct nct7904_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	mutex_init(&data->lock);
+	data->bank_sel = -1;
+	i2c_set_clientdata(client, data);
+
+	/* Setup attributes. (TODO: skip unused sensors) */
+	/* FANIN attributes */
+	ret = nct7904_read_reg16(data, BANK_0, FANIN_CTRL0_REG, buf);
+	if (ret)
+		goto err1;
+	mask = buf[0] | (buf[1] << 8);
+
+	for (i = 0, j = 0; j < FANIN_MAX; j++) {
+		if (mask & (1 << j))
+			nct7904_attrs[i++] =
+				&nct7904_fanin_attrs[j].dev_attr.attr;
+	}
+
+	/* VSEN attributes */
+	mask = 0;
+	ret = nct7904_read_reg16(data, BANK_0, VT_ADC_CTRL0_REG, buf);
+	if (ret == 0)
+		mask = buf[0] | (buf[1] << 8);
+	ret = nct7904_read_reg(data, BANK_0, VT_ADC_CTRL2_REG);
+	if (ret >= 0)
+		mask |= (ret << 16);
+	for (j = 0; j < VSEN_MAX; j++) {
+		if (mask & (1 << j))
+			nct7904_attrs[i++] =
+				&nct7904_vsen_attrs[j].dev_attr.attr;
+	}
+
+	/* CPU_TEMP attributes */
+	ret = nct7904_read_reg16(data, BANK_0, DTS_T_CTRL0_REG, buf);
+	if (ret)
+		goto err1;
+	mask = (buf[0] & 0xf) | (buf[1] << 4);
+	for (j = 0; j < TCPU_MAX; j++) {
+		if (mask & (1 << j))
+			nct7904_attrs[i++] =
+				&nct7904_tcpu_attrs[j].dev_attr.attr;
+	}
+
+	/* Fan control */
+	for (j = 0; j < FANCTL_MAX; j++) {
+		nct7904_attrs[i++] = &nct7904_fanctl_attrs[2*j].dev_attr.attr;
+		nct7904_attrs[i++] = &nct7904_fanctl_attrs[2*j+1].dev_attr.attr;
+	}
+
+	data->hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev,
+				client->name, data, nct7904_groups);
+	if (IS_ERR(data->hwmon_dev)) {
+		ret = PTR_ERR(data->hwmon_dev);
+		goto err1;
+	}
+
+	dev_info(&client->dev, "NCT7904 chip version 0x%x\n",
+		 nct7904_read_reg(data, 0, VERSION_ID_REG));
+
+	return 0;
+
+err1:
+	return ret;
+}
+
+static int
+nct7904_remove(struct i2c_client *client)
+{
+	return 0;
+}
+
+
+static const struct i2c_device_id nct7904_id[] = {
+	{"nct7904", 0},
+	{}
+};
+
+static struct i2c_driver nct7904_driver = {
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		.name = "nct7904",
+	},
+	.probe = nct7904_probe,
+	.remove = nct7904_remove,
+	.id_table = nct7904_id,
+	.detect = nct7904_detect,
+	.address_list = normal_i2c,
+};
+
+static int __init
+nct7904_init(void)
+{
+	return i2c_add_driver(&nct7904_driver);
+}
+
+static void __exit
+nct7904_exit(void)
+{
+	i2c_del_driver(&nct7904_driver);
+}
+
+module_init(nct7904_init);
+module_exit(nct7904_exit);
+
+MODULE_AUTHOR("Vadim V. Vlasov <vvlasov@xxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Hwmon driver for NUVOTON NCT7904");
+MODULE_LICENSE("GPL");
-- 
1.9.3




_______________________________________________
lm-sensors mailing list
lm-sensors@xxxxxxxxxxxxxx
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors




[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux