lm90.c

来自「linux 内核源代码」· C语言 代码 · 共 814 行 · 第 1/2 页

C
814
字号
/* * lm90.c - Part of lm_sensors, Linux kernel modules for hardware *          monitoring * Copyright (C) 2003-2006  Jean Delvare <khali@linux-fr.org> * * Based on the lm83 driver. The LM90 is a sensor chip made by National * Semiconductor. It reports up to two temperatures (its own plus up to * one external one) with a 0.125 deg resolution (1 deg for local * temperature) and a 3-4 deg accuracy. Complete datasheet can be * obtained from National's website at: *   http://www.national.com/pf/LM/LM90.html * * This driver also supports the LM89 and LM99, two other sensor chips * made by National Semiconductor. Both have an increased remote * temperature measurement accuracy (1 degree), and the LM99 * additionally shifts remote temperatures (measured and limits) by 16 * degrees, which allows for higher temperatures measurement. The * driver doesn't handle it since it can be done easily in user-space. * Complete datasheets can be obtained from National's website at: *   http://www.national.com/pf/LM/LM89.html *   http://www.national.com/pf/LM/LM99.html * Note that there is no way to differentiate between both chips. * * This driver also supports the LM86, another sensor chip made by * National Semiconductor. It is exactly similar to the LM90 except it * has a higher accuracy. * Complete datasheet can be obtained from National's website at: *   http://www.national.com/pf/LM/LM86.html * * This driver also supports the ADM1032, a sensor chip made by Analog * Devices. That chip is similar to the LM90, with a few differences * that are not handled by this driver. Complete datasheet can be * obtained from Analog's website at: *   http://www.analog.com/en/prod/0,2877,ADM1032,00.html * Among others, it has a higher accuracy than the LM90, much like the * LM86 does. * * This driver also supports the MAX6657, MAX6658 and MAX6659 sensor * chips made by Maxim. These chips are similar to the LM86. Complete * datasheet can be obtained at Maxim's website at: *   http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2578 * Note that there is no easy way to differentiate between the three * variants. The extra address and features of the MAX6659 are not * supported by this driver. These chips lack the remote temperature * offset feature. * * This driver also supports the MAX6680 and MAX6681, two other sensor * chips made by Maxim. These are quite similar to the other Maxim * chips. Complete datasheet can be obtained at: *   http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3370 * The MAX6680 and MAX6681 only differ in the pinout so they can be * treated identically. * * This driver also supports the ADT7461 chip from Analog Devices but * only in its "compatability mode". If an ADT7461 chip is found but * is configured in non-compatible mode (where its temperature * register values are decoded differently) it is ignored by this * driver. Complete datasheet can be obtained from Analog's website * at: *   http://www.analog.com/en/prod/0,2877,ADT7461,00.html * * Since the LM90 was the first chipset supported by this driver, most * comments will refer to this chipset, but are actually general and * concern all supported chipsets, unless mentioned otherwise. * * 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-sysfs.h>#include <linux/hwmon.h>#include <linux/err.h>#include <linux/mutex.h>#include <linux/sysfs.h>/* * Addresses to scan * Address is fully defined internally and cannot be changed except for * MAX6659, MAX6680 and MAX6681. * LM86, LM89, LM90, LM99, ADM1032, ADM1032-1, ADT7461, MAX6657 and MAX6658 * have address 0x4c. * ADM1032-2, ADT7461-2, LM89-1, and LM99-1 have address 0x4d. * MAX6659 can have address 0x4c, 0x4d or 0x4e (unsupported). * MAX6680 and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, * 0x4c, 0x4d or 0x4e. */static unsigned short normal_i2c[] = { 0x18, 0x19, 0x1a,				       0x29, 0x2a, 0x2b,				       0x4c, 0x4d, 0x4e,				       I2C_CLIENT_END };/* * Insmod parameters */I2C_CLIENT_INSMOD_7(lm90, adm1032, lm99, lm86, max6657, adt7461, max6680);/* * The LM90 registers */#define LM90_REG_R_MAN_ID		0xFE#define LM90_REG_R_CHIP_ID		0xFF#define LM90_REG_R_CONFIG1		0x03#define LM90_REG_W_CONFIG1		0x09#define LM90_REG_R_CONFIG2		0xBF#define LM90_REG_W_CONFIG2		0xBF#define LM90_REG_R_CONVRATE		0x04#define LM90_REG_W_CONVRATE		0x0A#define LM90_REG_R_STATUS		0x02#define LM90_REG_R_LOCAL_TEMP		0x00#define LM90_REG_R_LOCAL_HIGH		0x05#define LM90_REG_W_LOCAL_HIGH		0x0B#define LM90_REG_R_LOCAL_LOW		0x06#define LM90_REG_W_LOCAL_LOW		0x0C#define LM90_REG_R_LOCAL_CRIT		0x20#define LM90_REG_W_LOCAL_CRIT		0x20#define LM90_REG_R_REMOTE_TEMPH		0x01#define LM90_REG_R_REMOTE_TEMPL		0x10#define LM90_REG_R_REMOTE_OFFSH		0x11#define LM90_REG_W_REMOTE_OFFSH		0x11#define LM90_REG_R_REMOTE_OFFSL		0x12#define LM90_REG_W_REMOTE_OFFSL		0x12#define LM90_REG_R_REMOTE_HIGHH		0x07#define LM90_REG_W_REMOTE_HIGHH		0x0D#define LM90_REG_R_REMOTE_HIGHL		0x13#define LM90_REG_W_REMOTE_HIGHL		0x13#define LM90_REG_R_REMOTE_LOWH		0x08#define LM90_REG_W_REMOTE_LOWH		0x0E#define LM90_REG_R_REMOTE_LOWL		0x14#define LM90_REG_W_REMOTE_LOWL		0x14#define LM90_REG_R_REMOTE_CRIT		0x19#define LM90_REG_W_REMOTE_CRIT		0x19#define LM90_REG_R_TCRIT_HYST		0x21#define LM90_REG_W_TCRIT_HYST		0x21/* * Conversions and various macros * For local temperatures and limits, critical limits and the hysteresis * value, the LM90 uses signed 8-bit values with LSB = 1 degree Celsius. * For remote temperatures and limits, it uses signed 11-bit values with * LSB = 0.125 degree Celsius, left-justified in 16-bit registers. */#define TEMP1_FROM_REG(val)	((val) * 1000)#define TEMP1_TO_REG(val)	((val) <= -128000 ? -128 : \				 (val) >= 127000 ? 127 : \				 (val) < 0 ? ((val) - 500) / 1000 : \				 ((val) + 500) / 1000)#define TEMP2_FROM_REG(val)	((val) / 32 * 125)#define TEMP2_TO_REG(val)	((val) <= -128000 ? 0x8000 : \				 (val) >= 127875 ? 0x7FE0 : \				 (val) < 0 ? ((val) - 62) / 125 * 32 : \				 ((val) + 62) / 125 * 32)#define HYST_TO_REG(val)	((val) <= 0 ? 0 : (val) >= 30500 ? 31 : \				 ((val) + 500) / 1000)/*  * ADT7461 is almost identical to LM90 except that attempts to write * values that are outside the range 0 < temp < 127 are treated as * the boundary value.  */#define TEMP1_TO_REG_ADT7461(val) ((val) <= 0 ? 0 : \				 (val) >= 127000 ? 127 : \				 ((val) + 500) / 1000)#define TEMP2_TO_REG_ADT7461(val) ((val) <= 0 ? 0 : \				 (val) >= 127750 ? 0x7FC0 : \				 ((val) + 125) / 250 * 64)/* * Functions declaration */static int lm90_attach_adapter(struct i2c_adapter *adapter);static int lm90_detect(struct i2c_adapter *adapter, int address,	int kind);static void lm90_init_client(struct i2c_client *client);static int lm90_detach_client(struct i2c_client *client);static struct lm90_data *lm90_update_device(struct device *dev);/* * Driver data (common to all clients) */static struct i2c_driver lm90_driver = {	.driver = {		.name	= "lm90",	},	.id		= I2C_DRIVERID_LM90,	.attach_adapter	= lm90_attach_adapter,	.detach_client	= lm90_detach_client,};/* * Client data (each client gets its own) */struct lm90_data {	struct i2c_client client;	struct device *hwmon_dev;	struct mutex update_lock;	char valid; /* zero until following fields are valid */	unsigned long last_updated; /* in jiffies */	int kind;	/* registers values */	s8 temp8[5];	/* 0: local input			   1: local low limit			   2: local high limit			   3: local critical limit			   4: remote critical limit */	s16 temp11[4];	/* 0: remote input			   1: remote low limit			   2: remote high limit			   3: remote offset (except max6657) */	u8 temp_hyst;	u8 alarms; /* bitvector */};/* * Sysfs stuff */static ssize_t show_temp8(struct device *dev, struct device_attribute *devattr,			  char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct lm90_data *data = lm90_update_device(dev);	return sprintf(buf, "%d\n", TEMP1_FROM_REG(data->temp8[attr->index]));}static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr,			 const char *buf, size_t count){	static const u8 reg[4] = {		LM90_REG_W_LOCAL_LOW,		LM90_REG_W_LOCAL_HIGH,		LM90_REG_W_LOCAL_CRIT,		LM90_REG_W_REMOTE_CRIT,	};	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct i2c_client *client = to_i2c_client(dev);	struct lm90_data *data = i2c_get_clientdata(client);	long val = simple_strtol(buf, NULL, 10);	int nr = attr->index;	mutex_lock(&data->update_lock);	if (data->kind == adt7461)		data->temp8[nr] = TEMP1_TO_REG_ADT7461(val);	else		data->temp8[nr] = TEMP1_TO_REG(val);	i2c_smbus_write_byte_data(client, reg[nr - 1], data->temp8[nr]);	mutex_unlock(&data->update_lock);	return count;}static ssize_t show_temp11(struct device *dev, struct device_attribute *devattr,			   char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct lm90_data *data = lm90_update_device(dev);	return sprintf(buf, "%d\n", TEMP2_FROM_REG(data->temp11[attr->index]));}static ssize_t set_temp11(struct device *dev, struct device_attribute *devattr,			  const char *buf, size_t count){	static const u8 reg[6] = {		LM90_REG_W_REMOTE_LOWH,		LM90_REG_W_REMOTE_LOWL,		LM90_REG_W_REMOTE_HIGHH,		LM90_REG_W_REMOTE_HIGHL,		LM90_REG_W_REMOTE_OFFSH,		LM90_REG_W_REMOTE_OFFSL,	};	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct i2c_client *client = to_i2c_client(dev);	struct lm90_data *data = i2c_get_clientdata(client);	long val = simple_strtol(buf, NULL, 10);	int nr = attr->index;	mutex_lock(&data->update_lock);	if (data->kind == adt7461)		data->temp11[nr] = TEMP2_TO_REG_ADT7461(val);	else		data->temp11[nr] = TEMP2_TO_REG(val);	i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2],				  data->temp11[nr] >> 8);	i2c_smbus_write_byte_data(client, reg[(nr - 1) * 2 + 1],				  data->temp11[nr] & 0xff);	mutex_unlock(&data->update_lock);	return count;}static ssize_t show_temphyst(struct device *dev, struct device_attribute *devattr,			     char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct lm90_data *data = lm90_update_device(dev);	return sprintf(buf, "%d\n", TEMP1_FROM_REG(data->temp8[attr->index])		       - TEMP1_FROM_REG(data->temp_hyst));}static ssize_t set_temphyst(struct device *dev, struct device_attribute *dummy,			    const char *buf, size_t count){	struct i2c_client *client = to_i2c_client(dev);	struct lm90_data *data = i2c_get_clientdata(client);	long val = simple_strtol(buf, NULL, 10);	long hyst;	mutex_lock(&data->update_lock);	hyst = TEMP1_FROM_REG(data->temp8[3]) - val;	i2c_smbus_write_byte_data(client, LM90_REG_W_TCRIT_HYST,				  HYST_TO_REG(hyst));	mutex_unlock(&data->update_lock);	return count;}static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy,			   char *buf){	struct lm90_data *data = lm90_update_device(dev);	return sprintf(buf, "%d\n", data->alarms);}static ssize_t show_alarm(struct device *dev, struct device_attribute			  *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct lm90_data *data = lm90_update_device(dev);	int bitnr = attr->index;	return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);}static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp8, NULL, 0);static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp11, NULL, 0);static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp8,	set_temp8, 1);static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp11,	set_temp11, 1);static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp8,	set_temp8, 2);static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp11,	set_temp11, 2);static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp8,	set_temp8, 3);static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_temp8,	set_temp8, 4);static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temphyst,	set_temphyst, 3);static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_temphyst, NULL, 4);static SENSOR_DEVICE_ATTR(temp2_offset, S_IWUSR | S_IRUGO, show_temp11,	set_temp11, 3);/* Individual alarm files */static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 0);static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 1);static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_alarm, NULL, 2);static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_alarm, NULL, 3);static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4);static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, 5);static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6);/* Raw alarm file for compatibility */static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);static struct attribute *lm90_attributes[] = {	&sensor_dev_attr_temp1_input.dev_attr.attr,	&sensor_dev_attr_temp2_input.dev_attr.attr,	&sensor_dev_attr_temp1_min.dev_attr.attr,	&sensor_dev_attr_temp2_min.dev_attr.attr,	&sensor_dev_attr_temp1_max.dev_attr.attr,	&sensor_dev_attr_temp2_max.dev_attr.attr,	&sensor_dev_attr_temp1_crit.dev_attr.attr,	&sensor_dev_attr_temp2_crit.dev_attr.attr,	&sensor_dev_attr_temp1_crit_hyst.dev_attr.attr,	&sensor_dev_attr_temp2_crit_hyst.dev_attr.attr,	&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,	&sensor_dev_attr_temp2_crit_alarm.dev_attr.attr,	&sensor_dev_attr_temp2_fault.dev_attr.attr,	&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,	&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,	&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,	&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,	&dev_attr_alarms.attr,

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?