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 + -
显示快捷键?