ChangeSet 1.1337.6.1, 2003/10/09 13:40:07-07:00, khali at linux-fr.org [PATCH] I2C: Add lm83 chip driver drivers/i2c/chips/Kconfig | 11 + drivers/i2c/chips/Makefile | 1 drivers/i2c/chips/lm83.c | 413 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c-id.h | 1 4 files changed, 426 insertions(+) diff -Nru a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig --- a/drivers/i2c/chips/Kconfig Tue Dec 30 12:33:21 2003 +++ b/drivers/i2c/chips/Kconfig Tue Dec 30 12:33:21 2003 @@ -68,6 +68,17 @@ This driver can also be built as a module. If so, the module will be called lm78. +config SENSORS_LM83 + tristate "National Semiconductor LM83" + depends on I2C && EXPERIMENTAL + select I2C_SENSOR + help + If you say yes here you get support for National Semiconductor + LM83 sensor chips. + + This driver can also be built as a module. If so, the module + will be called lm83. + config SENSORS_LM85 tristate "National Semiconductors LM85 and compatibles" depends on I2C && EXPERIMENTAL diff -Nru a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile --- a/drivers/i2c/chips/Makefile Tue Dec 30 12:33:21 2003 +++ b/drivers/i2c/chips/Makefile Tue Dec 30 12:33:21 2003 @@ -10,5 +10,6 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_LM75) += lm75.o obj-$(CONFIG_SENSORS_LM78) += lm78.o +obj-$(CONFIG_SENSORS_LM83) += lm83.o obj-$(CONFIG_SENSORS_LM85) += lm85.o obj-$(CONFIG_SENSORS_VIA686A) += via686a.o diff -Nru a/drivers/i2c/chips/lm83.c b/drivers/i2c/chips/lm83.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/i2c/chips/lm83.c Tue Dec 30 12:33:21 2003 @@ -0,0 +1,413 @@ +/* + * lm83.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003 Jean Delvare <khali at linux-fr.org> + * + * Heavily inspired from the lm78, lm75 and adm1021 drivers. The LM83 is + * a sensor chip made by National Semiconductor. It reports up to four + * temperatures (its own plus up to three external ones) with a 1 deg + * resolution and a 3-4 deg accuracy. Complete datasheet can be obtained + * from National's website at: + * http://www.national.com/pf/LM/LM83.html + * Since the datasheet omits to give the chip stepping code, I give it + * here: 0x03 (at register 0xff). + * + * 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/i2c.h> +#include <linux/i2c-sensor.h> + +/* + * Addresses to scan + * Address is selected using 2 three-level pins, resulting in 9 possible + * addresses. + */ + +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; +static unsigned short normal_i2c_range[] = { 0x18, 0x1a, 0x29, 0x2b, + 0x4c, 0x4e, I2C_CLIENT_END }; +static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END }; +static unsigned int normal_isa_range[] = { I2C_CLIENT_ISA_END }; + +/* + * Insmod parameters + */ + +SENSORS_INSMOD_1(lm83); + +/* + * The LM83 registers + * Manufacturer ID is 0x01 for National Semiconductor. + */ + +#define LM83_REG_R_MAN_ID 0xFE +#define LM83_REG_R_CHIP_ID 0xFF +#define LM83_REG_R_CONFIG 0x03 +#define LM83_REG_W_CONFIG 0x09 +#define LM83_REG_R_STATUS1 0x02 +#define LM83_REG_R_STATUS2 0x35 +#define LM83_REG_R_LOCAL_TEMP 0x00 +#define LM83_REG_R_LOCAL_HIGH 0x05 +#define LM83_REG_W_LOCAL_HIGH 0x0B +#define LM83_REG_R_REMOTE1_TEMP 0x30 +#define LM83_REG_R_REMOTE1_HIGH 0x38 +#define LM83_REG_W_REMOTE1_HIGH 0x50 +#define LM83_REG_R_REMOTE2_TEMP 0x01 +#define LM83_REG_R_REMOTE2_HIGH 0x07 +#define LM83_REG_W_REMOTE2_HIGH 0x0D +#define LM83_REG_R_REMOTE3_TEMP 0x31 +#define LM83_REG_R_REMOTE3_HIGH 0x3A +#define LM83_REG_W_REMOTE3_HIGH 0x52 +#define LM83_REG_R_TCRIT 0x42 +#define LM83_REG_W_TCRIT 0x5A + +/* + * Conversions, initial values and various macros + * The LM83 uses signed 8-bit values. + */ + +#define TEMP_FROM_REG(val) ((val > 127 ? val-256 : val) * 1000) +#define TEMP_TO_REG(val) ((val < 0 ? val+256 : val) / 1000) + +#define LM83_INIT_HIGH 100 +#define LM83_INIT_CRIT 120 + +static const u8 LM83_REG_R_TEMP[] = { + LM83_REG_R_LOCAL_TEMP, + LM83_REG_R_REMOTE1_TEMP, + LM83_REG_R_REMOTE2_TEMP, + LM83_REG_R_REMOTE3_TEMP +}; + +static const u8 LM83_REG_R_HIGH[] = { + LM83_REG_R_LOCAL_HIGH, + LM83_REG_R_REMOTE1_HIGH, + LM83_REG_R_REMOTE2_HIGH, + LM83_REG_R_REMOTE3_HIGH +}; + +static const u8 LM83_REG_W_HIGH[] = { + LM83_REG_W_LOCAL_HIGH, + LM83_REG_W_REMOTE1_HIGH, + LM83_REG_W_REMOTE2_HIGH, + LM83_REG_W_REMOTE3_HIGH +}; + +/* + * Functions declaration + */ + +static int lm83_attach_adapter(struct i2c_adapter *adapter); +static int lm83_detect(struct i2c_adapter *adapter, int address, + int kind); +static void lm83_init_client(struct i2c_client *client); +static int lm83_detach_client(struct i2c_client *client); +static void lm83_update_client(struct i2c_client *client); + +/* + * Driver data (common to all clients) + */ + +static struct i2c_driver lm83_driver = { + .owner = THIS_MODULE, + .name = "lm83", + .id = I2C_DRIVERID_LM83, + .flags = I2C_DF_NOTIFY, + .attach_adapter = lm83_attach_adapter, + .detach_client = lm83_detach_client, +}; + +/* + * Client data (each client gets its own) + */ + +struct lm83_data +{ + struct semaphore update_lock; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + + /* registers values */ + u8 temp_input[4]; + u8 temp_high[4]; + u8 temp_crit; + u16 alarms; /* bitvector, combined */ +}; + +/* + * Internal variables + */ + +static int lm83_id = 0; + +/* + * Sysfs stuff + */ + +#define show_temp(suffix, value) \ +static ssize_t show_temp_##suffix(struct device *dev, char *buf) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct lm83_data *data = i2c_get_clientdata(client); \ + lm83_update_client(client); \ + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->value)); \ +} +show_temp(input1, temp_input[0]); +show_temp(input2, temp_input[1]); +show_temp(input3, temp_input[2]); +show_temp(input4, temp_input[3]); +show_temp(high1, temp_high[0]); +show_temp(high2, temp_high[1]); +show_temp(high3, temp_high[2]); +show_temp(high4, temp_high[3]); +show_temp(crit, temp_crit); + +#define set_temp(suffix, value, reg) \ +static ssize_t set_temp_##suffix(struct device *dev, const char *buf, \ + size_t count) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct lm83_data *data = i2c_get_clientdata(client); \ + data->value = TEMP_TO_REG(simple_strtoul(buf, NULL, 10)); \ + i2c_smbus_write_byte_data(client, reg, data->value); \ + return count; \ +} +set_temp(high1, temp_high[0], LM83_REG_W_LOCAL_HIGH); +set_temp(high2, temp_high[1], LM83_REG_W_REMOTE1_HIGH); +set_temp(high3, temp_high[2], LM83_REG_W_REMOTE2_HIGH); +set_temp(high4, temp_high[3], LM83_REG_W_REMOTE3_HIGH); +set_temp(crit, temp_crit, LM83_REG_W_TCRIT); + +static ssize_t show_alarms(struct device *dev, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lm83_data *data = i2c_get_clientdata(client); + lm83_update_client(client); + return sprintf(buf, "%d\n", data->alarms); +} + +static DEVICE_ATTR(temp_input1, S_IRUGO, show_temp_input1, NULL); +static DEVICE_ATTR(temp_input2, S_IRUGO, show_temp_input2, NULL); +static DEVICE_ATTR(temp_input3, S_IRUGO, show_temp_input3, NULL); +static DEVICE_ATTR(temp_input4, S_IRUGO, show_temp_input4, NULL); +static DEVICE_ATTR(temp_max1, S_IWUSR | S_IRUGO, show_temp_high1, + set_temp_high1); +static DEVICE_ATTR(temp_max2, S_IWUSR | S_IRUGO, show_temp_high2, + set_temp_high2); +static DEVICE_ATTR(temp_max3, S_IWUSR | S_IRUGO, show_temp_high3, + set_temp_high3); +static DEVICE_ATTR(temp_max4, S_IWUSR | S_IRUGO, show_temp_high4, + set_temp_high4); +static DEVICE_ATTR(temp_crit, S_IWUSR | S_IRUGO, show_temp_crit, + set_temp_crit); +static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL); + +/* + * Real code + */ + +static int lm83_attach_adapter(struct i2c_adapter *adapter) +{ + if (!(adapter->class & I2C_ADAP_CLASS_SMBUS)) + return 0; + return i2c_detect(adapter, &addr_data, lm83_detect); +} + +/* + * The following function does more than just detection. If detection + * succeeds, it also registers the new chip. + */ +static int lm83_detect(struct i2c_adapter *adapter, int address, + int kind) +{ + struct i2c_client *new_client; + struct lm83_data *data; + int err = 0; + const char *name = ""; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + goto exit; + + if (!(new_client = kmalloc(sizeof(struct i2c_client) + + sizeof(struct lm83_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit; + } + memset(new_client, 0x00, sizeof(struct i2c_client) + + sizeof(struct lm83_data)); + + /* The LM83-specific data is placed right after the common I2C + * client data. */ + data = (struct lm83_data *) (new_client + 1); + i2c_set_clientdata(new_client, data); + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &lm83_driver; + new_client->flags = 0; + + /* Now we do the detection and identification. A negative kind + * means that the driver was loaded with no force parameter + * (default), so we must both detect and identify the chip + * (actually there is only one possible kind of chip for now, LM83). + * A zero kind means that the driver was loaded with the force + * parameter, the detection step shall be skipped. A positive kind + * means that the driver was loaded with the force parameter and a + * given kind of chip is requested, so both the detection and the + * identification steps are skipped. */ + if (kind < 0) { /* detection */ + if (((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1) + & 0xA8) != 0x00) || + ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2) + & 0x48) != 0x00) || + ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG) + & 0x41) != 0x00)) { + dev_dbg(&client->dev, + "LM83 detection failed at 0x%02x.\n", address); + goto exit_free; + } + } + + if (kind <= 0) { /* identification */ + u8 man_id, chip_id; + + man_id = i2c_smbus_read_byte_data(new_client, + LM83_REG_R_MAN_ID); + chip_id = i2c_smbus_read_byte_data(new_client, + LM83_REG_R_CHIP_ID); + if (man_id == 0x01) { /* National Semiconductor */ + if (chip_id == 0x03) { + kind = lm83; + name = "lm83"; + } + } + + if (kind <= 0) { /* identification failed */ + dev_info(&adapter->dev, + "Unsupported chip (man_id=0x%02X, " + "chip_id=0x%02X).\n", man_id, chip_id); + goto exit_free; + } + } + + /* We can fill in the remaining client fields */ + strlcpy(new_client->name, name, I2C_NAME_SIZE); + new_client->id = lm83_id++; + data->valid = 0; + init_MUTEX(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto exit_free; + + /* Initialize the LM83 chip */ + lm83_init_client(new_client); + + /* Register sysfs hooks */ + device_create_file(&new_client->dev, &dev_attr_temp_input1); + device_create_file(&new_client->dev, &dev_attr_temp_input2); + device_create_file(&new_client->dev, &dev_attr_temp_input3); + device_create_file(&new_client->dev, &dev_attr_temp_input4); + device_create_file(&new_client->dev, &dev_attr_temp_max1); + device_create_file(&new_client->dev, &dev_attr_temp_max2); + device_create_file(&new_client->dev, &dev_attr_temp_max3); + device_create_file(&new_client->dev, &dev_attr_temp_max4); + device_create_file(&new_client->dev, &dev_attr_temp_crit); + device_create_file(&new_client->dev, &dev_attr_alarms); + + return 0; + +exit_free: + kfree(new_client); +exit: + return err; +} + +static void lm83_init_client(struct i2c_client *client) +{ + int nr; + + for (nr = 0; nr < 4; nr++) + i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr], + TEMP_TO_REG(LM83_INIT_HIGH)); + i2c_smbus_write_byte_data(client, LM83_REG_W_TCRIT, + TEMP_TO_REG(LM83_INIT_CRIT)); +} + +static int lm83_detach_client(struct i2c_client *client) +{ + int err; + + if ((err = i2c_detach_client(client))) { + dev_err(&client->dev, + "Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client); + return 0; +} + +static void lm83_update_client(struct i2c_client *client) +{ + struct lm83_data *data = i2c_get_clientdata(client); + + down(&data->update_lock); + + if ((jiffies - data->last_updated > HZ * 2) || + (jiffies < data->last_updated) || + !data->valid) { + int nr; + dev_dbg(&client->dev, "Updating lm83 data.\n"); + for (nr = 0; nr < 4 ; nr++) { + data->temp_input[nr] = + i2c_smbus_read_byte_data(client, + LM83_REG_R_TEMP[nr]); + data->temp_high[nr] = + i2c_smbus_read_byte_data(client, + LM83_REG_R_HIGH[nr]); + } + data->temp_crit = + i2c_smbus_read_byte_data(client, LM83_REG_R_TCRIT); + data->alarms = + i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) + + (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) + << 8); + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +static int __init sensors_lm83_init(void) +{ + return i2c_add_driver(&lm83_driver); +} + +static void __exit sensors_lm83_exit(void) +{ + i2c_del_driver(&lm83_driver); +} + +MODULE_AUTHOR("Jean Delvare <khali at linux-fr.org>"); +MODULE_DESCRIPTION("LM83 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_lm83_init); +module_exit(sensors_lm83_exit); diff -Nru a/include/linux/i2c-id.h b/include/linux/i2c-id.h --- a/include/linux/i2c-id.h Tue Dec 30 12:33:21 2003 +++ b/include/linux/i2c-id.h Tue Dec 30 12:33:21 2003 @@ -153,6 +153,7 @@ #define I2C_DRIVERID_FS451 1037 #define I2C_DRIVERID_W83627HF 1038 #define I2C_DRIVERID_LM85 1039 +#define I2C_DRIVERID_LM83 1040 /* * ---- Adapter types ----------------------------------------------------