diff mbox

[4/4] emc1403: thermal sensor support

Message ID 20100414125205.23181.15562.stgit@localhost.localdomain (mailing list archive)
State New, archived
Headers show

Commit Message

Alan Cox April 14, 2010, 12:52 p.m. UTC
None
diff mbox

Patch

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 6868b9d..34ee302 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1112,6 +1112,16 @@  config SENSORS_LIS331DL
 	  Device can be configured using sysfs.
 	  x y Z data can be   accessible via sysfs.
 
+config SENSORS_EMC1403
+	tristate "SMSC EMC1403 thermal sensor"
+	depends on I2C
+	help
+	  If you say yes here you get support for the SMSC Devices
+	  EMC1403 temperature monitoring chip.
+
+	  Threshold values can be configured using sysfs.
+	  Data from the different diode are accessible via sysfs.
+
 if ACPI
 
 comment "ACPI drivers"
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index ebeb2a2..a7332ce 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -40,6 +40,7 @@  obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
+obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
 obj-$(CONFIG_SENSORS_F71805F)	+= f71805f.o
 obj-$(CONFIG_SENSORS_F71882FG)	+= f71882fg.o
 obj-$(CONFIG_SENSORS_F75375S)	+= f75375s.o
diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c
new file mode 100644
index 0000000..b71cfa1
--- /dev/null
+++ b/drivers/hwmon/emc1403.c
@@ -0,0 +1,437 @@ 
+/*
+ * emc1403.c - SMSC Thermal Driver
+ *
+ * Copyright (C) 2008 Intel Corp
+ *
+ *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#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/hwmon-vid.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+
+
+/* Limit status reg Therm/High/Low/Fault*/
+static const u8 THM_STAT_REG_TEMP[] = { 0x37, 0x35, 0x36, 0x1B, 0x02};
+
+/* Channel  diode temp set */
+static const u8 THM_CHAN_TEMP[] = { 0x10, 0x08, 0x04, 0x02, 0x01 };
+
+/* Therm Limit reg store values */
+static const u8 THM_LIMIT_REG_TEMP[] = { 0x05, 0x06, 0x07, 0x08, 0x15, 0x16,
+						0x19, 0x1A, 0x20, 0x21 };
+
+/* DATA REGISTERS */
+static const u8 THM_REG_CURR_TEMP[] = { 0x00, 0x01, 0x23 };
+
+#define THERMAL_PID_REG		0xfd
+#define THERMAL_SMSC_ID_REG	0xfe
+#define THERMAL_REVISION_REG	0xff
+#define THERMAL_ADC_UPDATE_BUSY	0x80
+#define I2C_THERMAL_SLAVE_ADDR	0x4C
+#define TEMP1			1
+#define TEMP2			2
+#define TEMP3			4
+#define IRQ_TYPE_MASK		(1 << 15)
+#define HIGH_EVENT		1
+#define LOW_EVENT		2
+#define THERM_EVENT		3
+#define FAULT_EVENT		4
+#define ALERT_EVENT		1
+
+struct thermal_data {
+	struct i2c_client *client;
+	struct device *hwmon_dev;
+	int therm_irq;
+	int alert_irq;
+	struct work_struct therm_handler;
+	struct work_struct alert_handler;
+};
+
+static int calculate_offset(int type, int temp_ofs)
+{
+	int offset = 0;
+
+	switch (type) {
+	case TEMP1:
+		if (temp_ofs == 0)
+			offset = 1;
+		else if (temp_ofs == 1)
+			offset = 0;
+		else if (temp_ofs == 2)
+			offset = 8;
+		break;
+	case TEMP2:
+		if (temp_ofs == 0)
+			offset = 3;
+		else if (temp_ofs == 1)
+			offset = 2;
+		else if (temp_ofs == 2)
+			offset = 6;
+		break;
+	case TEMP3:
+		if (temp_ofs == 0)
+			offset = 5;
+		else if (temp_ofs == 1)
+			offset = 4;
+		else if (temp_ofs == 2)
+			offset = 7;
+		break;
+	default:
+		offset = -1;
+		break;
+	}
+	return offset;
+
+}
+
+
+static ssize_t show_temp_auto_offset(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct sensor_device_attribute_2 *s_attr = to_sensor_dev_attr_2(attr);
+	int temp_index = s_attr->index;
+	int temp_ofs = s_attr->nr;
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret_val;
+	int ret_offset;
+
+	ret_offset = calculate_offset(temp_index, temp_ofs);
+	if (ret_offset == -1)
+		return -EINVAL;
+	ret_val = i2c_smbus_read_byte_data(client,
+				THM_LIMIT_REG_TEMP[ret_offset]);
+	return sprintf(buf, "%d\n", ret_val);
+}
+
+static ssize_t store_temp_auto_offset(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *s_attr = to_sensor_dev_attr_2(attr);
+	int temp_index = s_attr->index;
+	int temp_ofs = s_attr->nr;
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret_offset;
+
+	if (strict_strtoul(buf, 10, &val))
+		return -EINVAL;
+	ret_offset = calculate_offset(temp_index, temp_ofs);
+	if (ret_offset == -1)
+		return -EINVAL;
+
+	i2c_smbus_write_byte_data(client, THM_LIMIT_REG_TEMP[ret_offset], val);
+	return count;
+}
+
+static ssize_t show_temp_hyst(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n",
+		i2c_smbus_read_byte_data(client, THM_LIMIT_REG_TEMP[9]));
+}
+
+static ssize_t store_temp_hyst(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+
+	if (strict_strtoul(buf, 10, &val))
+		return -EINVAL;
+	i2c_smbus_write_byte_data(client, THM_LIMIT_REG_TEMP[9], val);
+	return count;
+}
+
+static ssize_t show_temp1_curr_temp(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret_val;
+
+	ret_val = i2c_smbus_read_byte_data(client, THM_REG_CURR_TEMP[0]);
+	return sprintf(buf, "%d\n", ret_val);
+}
+
+static ssize_t show_temp2_curr_temp(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret_val;
+
+	ret_val = i2c_smbus_read_byte_data(client, THM_REG_CURR_TEMP[1]);
+	return sprintf(buf, "%d\n", ret_val);
+}
+
+static ssize_t show_temp3_curr_temp(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret_val;
+
+	ret_val = i2c_smbus_read_byte_data(client, THM_REG_CURR_TEMP[2]);
+	return sprintf(buf, "%d\n", ret_val);
+}
+
+static ssize_t show_status_reg(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int r1, r2, r3, r4;
+
+	r1 = i2c_smbus_read_byte_data(client, 0x1F);
+	r2 = i2c_smbus_read_byte_data(client, 0x35);
+	r3 = i2c_smbus_read_byte_data(client, 0x36);
+	r4 = i2c_smbus_read_byte_data(client, 0x37);
+	return sprintf(buf, "alarm=%x,High=%x,Low=%x,Therm=%x \n",
+				r1, r2, r3, r4);
+}
+
+static ssize_t show_power_state(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret_val = i2c_smbus_read_byte_data(client, 0x03);
+	return sprintf(buf, "%x", (ret_val >> 6) & 1);
+}
+
+static ssize_t store_power_state(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	char curr_val;
+
+	if (strict_strtoul(buf, 10, &val))
+		return -EINVAL;
+
+	curr_val = i2c_smbus_read_byte_data(client, 0x03);
+	if (val == 1)
+		curr_val &= 0xBF;
+	else if (val == 0)
+		curr_val |= 0x40;
+	else
+		return -EINVAL;
+	i2c_smbus_write_byte_data(client, 0x03, curr_val);
+	return count;
+}
+
+static ssize_t show_mode(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret_val = i2c_smbus_read_byte_data(client, 0x03);
+	return sprintf(buf, "%x", (ret_val >> 7) & 1);
+}
+
+static ssize_t store_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int curr_val;
+
+	if (strict_strtoul(buf, 10, &val))
+		return -EINVAL;
+
+	curr_val = i2c_smbus_read_byte_data(client, 0x03);
+	if (val == 1)
+		curr_val &= 0x7F;
+	else if (val == 0)
+		curr_val |= 0x80;
+	else
+		return -EINVAL;
+	i2c_smbus_write_byte_data(client, 0x03, curr_val);
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR,
+	show_temp_auto_offset, store_temp_auto_offset, 0, 1);
+static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR,
+	show_temp_auto_offset, store_temp_auto_offset, 1, 1);
+static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR,
+	show_temp_auto_offset, store_temp_auto_offset, 2, 1);
+static DEVICE_ATTR(temp1_curr, S_IRUGO, show_temp1_curr_temp, NULL);
+
+static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR,
+	show_temp_auto_offset, store_temp_auto_offset, 0, 2);
+static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR,
+	show_temp_auto_offset, store_temp_auto_offset, 1, 2);
+static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR,
+	show_temp_auto_offset, store_temp_auto_offset, 2, 2);
+static DEVICE_ATTR(temp2_curr, S_IRUGO, show_temp2_curr_temp, NULL);
+
+static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR,
+	show_temp_auto_offset, store_temp_auto_offset, 0, 4);
+static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR,
+	show_temp_auto_offset, store_temp_auto_offset, 1, 4);
+static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR,
+	show_temp_auto_offset, store_temp_auto_offset, 2, 4);
+static DEVICE_ATTR(temp3_curr, S_IRUGO, show_temp3_curr_temp, NULL);
+
+static DEVICE_ATTR(hyster, S_IRUGO | S_IWUSR, show_temp_hyst, store_temp_hyst);
+static DEVICE_ATTR(status, S_IRUGO, show_status_reg, NULL);
+
+static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR,
+	show_power_state, store_power_state);
+static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, show_mode, store_mode);
+
+static struct attribute *mid_att_thermal[] = {
+	&sensor_dev_attr_temp1_min.dev_attr.attr,
+	&sensor_dev_attr_temp1_max.dev_attr.attr,
+	&sensor_dev_attr_temp1_crit.dev_attr.attr,
+	&dev_attr_temp1_curr.attr,
+	&sensor_dev_attr_temp2_min.dev_attr.attr,
+	&sensor_dev_attr_temp2_max.dev_attr.attr,
+	&sensor_dev_attr_temp2_crit.dev_attr.attr,
+	&dev_attr_temp2_curr.attr,
+	&sensor_dev_attr_temp3_min.dev_attr.attr,
+	&sensor_dev_attr_temp3_max.dev_attr.attr,
+	&sensor_dev_attr_temp3_crit.dev_attr.attr,
+	&dev_attr_temp3_curr.attr,
+	&dev_attr_hyster.attr,
+	&dev_attr_status.attr,
+	&dev_attr_power_state.attr,
+	&dev_attr_mode.attr,
+	NULL
+};
+
+static struct attribute_group m_thermal_gr = {
+	.name = "emc1403",
+	.attrs = mid_att_thermal
+};
+
+static void emc1403_set_default_config(struct i2c_client *client)
+{
+	i2c_smbus_write_byte_data(client, 0x03, 0x00);
+	i2c_smbus_write_byte_data(client, 0x04, 0x02);
+	i2c_smbus_write_byte_data(client, 0x22, 0x00);
+}
+
+static int  emc1403_probe(struct i2c_client *new_client,
+			const struct i2c_device_id *id)
+{
+	int res = 0;
+	struct thermal_data *data;
+	u16 pid, smsc_id, revision;
+
+	data = kzalloc(sizeof(struct thermal_data), GFP_KERNEL);
+
+	if (data == NULL) {
+		printk(KERN_WARNING "emc1403: out of memory");
+		return -ENOMEM;
+	}
+	data->client = new_client;
+	i2c_set_clientdata(new_client, data);
+
+	/* Check if thermal chip is SMSC and EMC1403 */
+	smsc_id = i2c_smbus_read_byte_data(new_client,
+					THERMAL_SMSC_ID_REG);
+	if (smsc_id != 0x5d) {
+		printk(KERN_WARNING "emc1403: vendor id mismatch\n");
+		goto thermal_error1;
+	}
+	pid = i2c_smbus_read_byte_data(new_client, THERMAL_PID_REG);
+	if (pid != 0x21)  {
+		printk(KERN_WARNING "emc1403: product id mismatch\n");
+		goto thermal_error1;
+	}
+	revision = i2c_smbus_read_byte_data(new_client,
+				THERMAL_REVISION_REG);
+	if (revision != 0x01) {
+		printk(KERN_WARNING "emc1403: rev id mismatch (is %d)\n",
+								revision);
+		goto thermal_error1;
+	}
+	res = sysfs_create_group(&new_client->dev.kobj, &m_thermal_gr);
+	if (res) {
+		printk(KERN_WARNING "emc1403: create group failed\n");
+		hwmon_device_unregister(data->hwmon_dev);
+		goto thermal_error1;
+	}
+	data->hwmon_dev = hwmon_device_register(&new_client->dev);
+	if (IS_ERR(data->hwmon_dev)) {
+		res = PTR_ERR(data->hwmon_dev);
+		data->hwmon_dev = NULL;
+		printk(KERN_WARNING "emc1403:Register hwmon dev failed\n");
+		goto thermal_error2;
+	}
+	emc1403_set_default_config(new_client);
+	dev_info(&new_client->dev, "%s EMC1403 Thermal chip found\n",
+							new_client->name);
+	return res;
+thermal_error2:
+	sysfs_remove_group(&new_client->dev.kobj, &m_thermal_gr);
+thermal_error1:
+	i2c_set_clientdata(new_client, NULL);
+	kfree(data);
+	return res;
+}
+
+static int emc1403_remove(struct i2c_client *client)
+{
+	struct thermal_data *data = i2c_get_clientdata(client);
+
+	hwmon_device_unregister(data->hwmon_dev);
+	sysfs_remove_group(&client->dev.kobj, &m_thermal_gr);
+	kfree(data);
+	return 0;
+}
+
+static struct i2c_device_id emc1403_idtable[] = {
+	{ "i2c_thermal", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, emc1403_idtable);
+
+static struct i2c_driver sensor_emc1403 = {
+	.driver = {
+		.name = "emc1403",
+	},
+	.probe = emc1403_probe,
+	.remove = emc1403_remove,
+	.id_table = emc1403_idtable,
+};
+
+static int __init sensor_emc1403_init(void)
+{
+	return i2c_add_driver(&sensor_emc1403);
+}
+
+static void  __exit sensor_emc1403_exit(void)
+{
+	i2c_del_driver(&sensor_emc1403);
+}
+
+module_init(sensor_emc1403_init);
+module_exit(sensor_emc1403_exit);
+
+MODULE_AUTHOR("Kalhan Trisal <kalhan.trisal@intel.com");
+MODULE_DESCRIPTION("emc1403 Thermal Driver");
+MODULE_LICENSE("GPL v2");