Add basic support for the ADT7411. Reads out all conversion results (via I2C, SPI yet missing) and allows some on-the-fly configuration. Tested with a custom board. Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> Signed-off-by: Wolfram Sang <w.sang@xxxxxxxxxxxxxx> Cc: Jean Delvare <khali@xxxxxxxxxxxx> --- Changes since V2: * put all cache handling into adt7411_show_input() * removed adt7411_read_vdd which became obsolete * now vref is cached instead of vdd to minimize calculations * forced update when configuration bits change * fixed minor issues pointed out by Jean Changes since V1: * simplified reading 10-bit, no retries * modifying a configuration bit is now protected * no initial configuration is performed except START (which is what the other ADT drivers do) * when vdd is the reference, use a cached value for it * added a detect mechanism * style issues and typos found by Jean were fixed Documentation/hwmon/adt7411 | 42 +++++ drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/adt7411.c | 357 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 410 insertions(+), 0 deletions(-) create mode 100644 Documentation/hwmon/adt7411 create mode 100644 drivers/hwmon/adt7411.c diff --git a/Documentation/hwmon/adt7411 b/Documentation/hwmon/adt7411 new file mode 100644 index 0000000..1632960 --- /dev/null +++ b/Documentation/hwmon/adt7411 @@ -0,0 +1,42 @@ +Kernel driver adt7411 +===================== + +Supported chips: + * Analog Devices ADT7411 + Prefix: 'adt7411' + Addresses scanned: 0x48, 0x4a, 0x4b + Datasheet: Publicly available at the Analog Devices website + +Author: Wolfram Sang (based on adt7470 by Darrick J. Wong) + +Description +----------- + +This driver implements support for the Analog Devices ADT7411 chip. There may +be other chips that implement this interface. + +The ADT7411 can use an I2C/SMBus compatible 2-wire interface or an +SPI-compatible 4-wire interface. It provides a 10-bit analog to digital +converter which measures 1 temperature, vdd and 8 input voltages. It has an +internal temperature sensor, but an external one can also be connected (one +loses 2 inputs then). There are high- and low-limit registers for all inputs. + +Check the datasheet for details. + +sysfs-Interface +--------------- + +in0_input - vdd voltage input +in[1-8]_input - analog 1-8 input +temp1_input - temperature input + +Besides standard interfaces, this driver adds (0 = off, 1 = on): + + adc_ref_vdd - Use vdd as reference instead of 2.25 V + fast_sampling - Sample at 22.5 kHz instead of 1.4 kHz, but drop filters + no_average - Turn off averaging over 16 samples + +Notes +----- + +SPI, external temperature sensor and limit registers are not supported yet. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 68cf877..c91dc98 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -170,6 +170,16 @@ config SENSORS_ADM9240 This driver can also be built as a module. If so, the module will be called adm9240. +config SENSORS_ADT7411 + tristate "Analog Devices ADT7411" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the Analog Devices + ADT7411 voltage and temperature monitoring chip. + + This driver can also be built as a module. If so, the module + will be called adt7411. + config SENSORS_ADT7462 tristate "Analog Devices ADT7462" depends on I2C && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 4bc215c..5fe67bf 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o +obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7473) += adt7473.o diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c new file mode 100644 index 0000000..2a86314 --- /dev/null +++ b/drivers/hwmon/adt7411.c @@ -0,0 +1,357 @@ +/* + * Driver for the ADT7411 (I2C/SPI 8 channel 10 bit ADC & temperature-sensor) + * + * Copyright (C) 2008, 2010 Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: SPI, support for external temperature sensor + * use power-down mode for suspend?, interrupt handling? + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +#define ADT7411_REG_INT_TEMP_VDD_LSB 0x03 +#define ADT7411_REG_EXT_TEMP_AIN14_LSB 0x04 +#define ADT7411_REG_VDD_MSB 0x06 +#define ADT7411_REG_INT_TEMP_MSB 0x07 +#define ADT7411_REG_EXT_TEMP_AIN1_MSB 0x08 + +#define ADT7411_REG_CFG1 0x18 +#define ADT7411_CFG1_START_MONITOR (1 << 0) + +#define ADT7411_REG_CFG2 0x19 +#define ADT7411_CFG2_DISABLE_AVG (1 << 5) + +#define ADT7411_REG_CFG3 0x1a +#define ADT7411_CFG3_ADC_CLK_225 (1 << 0) +#define ADT7411_CFG3_REF_VDD (1 << 4) + +#define ADT7411_REG_DEVICE_ID 0x4d +#define ADT7411_REG_MANUFACTURER_ID 0x4e + +#define ADT7411_DEVICE_ID 0x2 +#define ADT7411_MANUFACTURER_ID 0x41 + +static const unsigned short normal_i2c[] = { 0x48, 0x4a, 0x4b, I2C_CLIENT_END }; + +struct adt7411_data { + struct mutex device_lock; /* for "atomic" device accesses */ + unsigned long next_update; + int vref_cached; + bool ref_is_vdd; + struct device *hwmon_dev; +}; + +/* + * When reading a register containing (up to 4) lsb, all associated + * msb-registers get locked by the hardware. After _one_ of those msb is read, + * _all_ are unlocked. In order to use this locking correctly, reading lsb/msb + * is protected here with a mutex, too. + */ +static int adt7411_read_10_bit(struct i2c_client *client, u8 lsb_reg, + u8 msb_reg, u8 lsb_shift) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + int val, tmp; + + mutex_lock(&data->device_lock); + + val = i2c_smbus_read_byte_data(client, lsb_reg); + if (val < 0) + goto exit_unlock; + + tmp = (val >> lsb_shift) & 3; + val = i2c_smbus_read_byte_data(client, msb_reg); + + if (val >= 0) + val = (val << 2) | tmp; + + exit_unlock: + mutex_unlock(&data->device_lock); + + return val; +} + +static int adt7411_modify_bit(struct i2c_client *client, u8 reg, u8 bit, + bool flag) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + int ret, val; + + mutex_lock(&data->device_lock); + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + goto exit_unlock; + + if (flag) + val = ret | bit; + else + val = ret & ~bit; + + ret = i2c_smbus_write_byte_data(client, reg, val); + + exit_unlock: + mutex_unlock(&data->device_lock); + return ret; +} + +static ssize_t adt7411_show_vdd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_VDD_MSB, 2); + + return ret < 0 ? ret : sprintf(buf, "%u\n", ret * 7000 / 1024); +} + +static ssize_t adt7411_show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int val = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_INT_TEMP_MSB, 0); + + if (val < 0) + return val; + + val = val & 0x200 ? val - 0x400 : val; /* 10 bit signed */ + + return sprintf(buf, "%d\n", val * 250); +} + +static ssize_t adt7411_show_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct adt7411_data *data = i2c_get_clientdata(client); + int val; + u8 lsb_reg, lsb_shift; + + if (time_after_eq(jiffies, data->next_update)) { + val = i2c_smbus_read_byte_data(client, ADT7411_REG_CFG3); + if (val < 0) + return val; + data->ref_is_vdd = val & ADT7411_CFG3_REF_VDD; + + if (data->ref_is_vdd) { + val = adt7411_read_10_bit(client, + ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_VDD_MSB, 2); + if (val < 0) + return val; + + data->vref_cached = val * 7000 / 1024; + } else { + data->vref_cached = 2250; + } + + data->next_update = jiffies + HZ; + } + + lsb_reg = ADT7411_REG_EXT_TEMP_AIN14_LSB + (nr >> 2); + lsb_shift = 2 * (nr & 0x03); + val = adt7411_read_10_bit(client, lsb_reg, + ADT7411_REG_EXT_TEMP_AIN1_MSB + nr, lsb_shift); + + return val < 0 ? val : + sprintf(buf, "%u\n", val * data->vref_cached / 1024); +} + +static ssize_t adt7411_show_bit(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + int ret = i2c_smbus_read_byte_data(client, attr2->index); + + return ret < 0 ? ret : sprintf(buf, "%u\n", !!(ret & attr2->nr)); +} + +static ssize_t adt7411_set_bit(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *s_attr2 = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7411_data *data = i2c_get_clientdata(client); + int ret; + unsigned long flag; + + ret = strict_strtoul(buf, 0, &flag); + if (ret || flag > 1) + return -EINVAL; + + ret = adt7411_modify_bit(client, s_attr2->index, s_attr2->nr, flag); + + /* force update */ + data->next_update = jiffies; + + return ret < 0 ? ret : count; +} + +#define ADT7411_BIT_ATTR(__name, __reg, __bit) \ + SENSOR_DEVICE_ATTR_2(__name, S_IRUGO | S_IWUSR, adt7411_show_bit, \ + adt7411_set_bit, __bit, __reg) + +static DEVICE_ATTR(temp1_input, S_IRUGO, adt7411_show_temp, NULL); +static DEVICE_ATTR(in0_input, S_IRUGO, adt7411_show_vdd, NULL); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, adt7411_show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, adt7411_show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, adt7411_show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, adt7411_show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, adt7411_show_input, NULL, 4); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, adt7411_show_input, NULL, 5); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, adt7411_show_input, NULL, 6); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, adt7411_show_input, NULL, 7); +static ADT7411_BIT_ATTR(no_average, ADT7411_REG_CFG2, ADT7411_CFG2_DISABLE_AVG); +static ADT7411_BIT_ATTR(fast_sampling, ADT7411_REG_CFG3, ADT7411_CFG3_ADC_CLK_225); +static ADT7411_BIT_ATTR(adc_ref_vdd, ADT7411_REG_CFG3, ADT7411_CFG3_REF_VDD); + +static struct attribute *adt7411_attrs[] = { + &dev_attr_temp1_input.attr, + &dev_attr_in0_input.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_no_average.dev_attr.attr, + &sensor_dev_attr_fast_sampling.dev_attr.attr, + &sensor_dev_attr_adc_ref_vdd.dev_attr.attr, + NULL +}; + +static const struct attribute_group adt7411_attr_grp = { + .attrs = adt7411_attrs, +}; + +static int adt7411_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + int val; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, ADT7411_REG_MANUFACTURER_ID); + if (val < 0 || val != ADT7411_MANUFACTURER_ID) { + dev_dbg(&client->dev, "Wrong manufacturer ID. Got %d, " + "expected %d\n", val, ADT7411_MANUFACTURER_ID); + return -ENODEV; + } + + val = i2c_smbus_read_byte_data(client, ADT7411_REG_DEVICE_ID); + if (val < 0 || val != ADT7411_DEVICE_ID) { + dev_dbg(&client->dev, "Wrong device ID. Got %d, " + "expected %d\n", val, ADT7411_DEVICE_ID); + return -ENODEV; + } + + strlcpy(info->type, "adt7411", I2C_NAME_SIZE); + + return 0; +} + +static int __devinit adt7411_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adt7411_data *data; + int ret; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->device_lock); + + ret = adt7411_modify_bit(client, ADT7411_REG_CFG1, + ADT7411_CFG1_START_MONITOR, 1); + if (ret < 0) + goto exit_free; + + /* force update on first occasion */ + data->next_update = jiffies; + + ret = sysfs_create_group(&client->dev.kobj, &adt7411_attr_grp); + if (ret) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + dev_info(&client->dev, "successfully registered\n"); + + return 0; + + exit_remove: + sysfs_remove_group(&client->dev.kobj, &adt7411_attr_grp); + exit_free: + i2c_set_clientdata(client, NULL); + kfree(data); + return ret; +} + +static int __devexit adt7411_remove(struct i2c_client *client) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &adt7411_attr_grp); + i2c_set_clientdata(client, NULL); + kfree(data); + return 0; +} + +static const struct i2c_device_id adt7411_id[] = { + { "adt7411", 0 }, + { } +}; + +static struct i2c_driver adt7411_driver = { + .driver = { + .name = "adt7411", + }, + .probe = adt7411_probe, + .remove = __devexit_p(adt7411_remove), + .id_table = adt7411_id, + .detect = adt7411_detect, + .address_list = normal_i2c, + .class = I2C_CLASS_HWMON, +}; + +static int __init sensors_adt7411_init(void) +{ + return i2c_add_driver(&adt7411_driver); +} +module_init(sensors_adt7411_init) + +static void __exit sensors_adt7411_exit(void) +{ + i2c_del_driver(&adt7411_driver); +} +module_exit(sensors_adt7411_exit) + +MODULE_AUTHOR("Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> and " + "Wolfram Sang <w.sang@xxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("ADT7411 driver"); +MODULE_LICENSE("GPL v2"); -- 1.6.5 _______________________________________________ lm-sensors mailing list lm-sensors@xxxxxxxxxxxxxx http://lists.lm-sensors.org/mailman/listinfo/lm-sensors