diff options
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/adt7461.c | 656 |
3 files changed, 667 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0b62c3c6b7ce..42e077c4dfeb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -179,6 +179,16 @@ config SENSORS_ADT7411 This driver can also be built as a module. If so, the module will be called adt7411. +config SENSORS_ADT7461 + tristate "Analog Devices ADT7461" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the Analog Devices + ADT7461 temperature monitoring chips. + + This driver can also be built as a module. If so, the module + will be called adt7461. + config SENSORS_ADT7462 tristate "Analog Devices ADT7462" depends on I2C && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 3c9ccefea791..665a82fd1f91 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_SENSORS_ADS1015) += ads1015.o obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o obj-$(CONFIG_SENSORS_ADS7871) += ads7871.o obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o +obj-$(CONFIG_SENSORS_ADT7461) += adt7461.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o diff --git a/drivers/hwmon/adt7461.c b/drivers/hwmon/adt7461.c new file mode 100644 index 000000000000..50e50ee9e424 --- /dev/null +++ b/drivers/hwmon/adt7461.c @@ -0,0 +1,656 @@ +/* + * adt7461.c - Linux kernel modules for hardware + * monitoring + * Copyright (C) 2003-2010 Jean Delvare <khali@linux-fr.org> + * + * 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/regulator/consumer.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> +#include <linux/delay.h> + +/* + * The ADT7461 registers + */ + +#define ADT7461_REG_R_MAN_ID 0xFE +#define ADT7461_REG_R_CHIP_ID 0xFF +#define ADT7461_REG_R_CONFIG1 0x03 +#define ADT7461_REG_W_CONFIG1 0x09 +#define ADT7461_REG_R_CONVRATE 0x04 +#define ADT7461_REG_W_CONVRATE 0x0A +#define ADT7461_REG_R_STATUS 0x02 +#define ADT7461_REG_R_LOCAL_TEMP 0x00 +#define ADT7461_REG_R_LOCAL_HIGH 0x05 +#define ADT7461_REG_W_LOCAL_HIGH 0x0B +#define ADT7461_REG_R_LOCAL_LOW 0x06 +#define ADT7461_REG_W_LOCAL_LOW 0x0C +#define ADT7461_REG_R_LOCAL_CRIT 0x20 +#define ADT7461_REG_W_LOCAL_CRIT 0x20 +#define ADT7461_REG_R_REMOTE_TEMPH 0x01 +#define ADT7461_REG_R_REMOTE_TEMPL 0x10 +#define ADT7461_REG_R_REMOTE_OFFSH 0x11 +#define ADT7461_REG_W_REMOTE_OFFSH 0x11 +#define ADT7461_REG_R_REMOTE_OFFSL 0x12 +#define ADT7461_REG_W_REMOTE_OFFSL 0x12 +#define ADT7461_REG_R_REMOTE_HIGHH 0x07 +#define ADT7461_REG_W_REMOTE_HIGHH 0x0D +#define ADT7461_REG_R_REMOTE_HIGHL 0x13 +#define ADT7461_REG_W_REMOTE_HIGHL 0x13 +#define ADT7461_REG_R_REMOTE_LOWH 0x08 +#define ADT7461_REG_W_REMOTE_LOWH 0x0E +#define ADT7461_REG_R_REMOTE_LOWL 0x14 +#define ADT7461_REG_W_REMOTE_LOWL 0x14 +#define ADT7461_REG_R_REMOTE_CRIT 0x19 +#define ADT7461_REG_W_REMOTE_CRIT 0x19 +#define ADT7461_REG_R_TCRIT_HYST 0x21 +#define ADT7461_REG_W_TCRIT_HYST 0x21 + +/* + * Device flags + */ +#define ADT7461_FLAG_ADT7461_EXT 0x01 /* ADT7461 extended mode */ + +/* + * Client data + */ + +struct adt7461_data { + struct device *hwmon_dev; + struct mutex update_lock; + struct regulator *regulator; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + int flags; + + u8 config_orig; /* Original configuration register value */ + u8 alert_alarms; /* Which alarm bits trigger ALERT# */ + + /* registers values */ + s8 temp8[4]; /* 0: local low limit + 1: local high limit + 2: local critical limit + 3: remote critical limit */ + s16 temp11[5]; /* 0: remote input + 1: remote low limit + 2: remote high limit + 3: remote offset + 4: local input */ + u8 temp_hyst; + u8 alarms; /* bitvector */ +}; + +/* + * Conversions + */ + +static inline int temp_from_s8(s8 val) +{ + return val * 1000; +} + +static u8 hyst_to_reg(long val) +{ + if (val <= 0) + return 0; + if (val >= 30500) + return 31; + return (val + 500) / 1000; +} + +/* + * ADT7461 attempts to write values that are outside the range + * 0 < temp < 127 are treated as the boundary value. + * + * ADT7461 in "extended mode" operation uses unsigned integers offset by + * 64 (e.g., 0 -> -64 degC). The range is restricted to -64..191 degC. + */ +static inline int temp_from_u8(struct adt7461_data *data, u8 val) +{ + if (data->flags & ADT7461_FLAG_ADT7461_EXT) + return (val - 64) * 1000; + else + return temp_from_s8(val); +} + +static inline int temp_from_u16(struct adt7461_data *data, u16 val) +{ + if (data->flags & ADT7461_FLAG_ADT7461_EXT) + return (val - 0x4000) / 64 * 250; + else + return val / 32 * 125; +} + +static u8 temp_to_u8(struct adt7461_data *data, long val) +{ + if (data->flags & ADT7461_FLAG_ADT7461_EXT) { + if (val <= -64000) + return 0; + if (val >= 191000) + return 0xFF; + return (val + 500 + 64000) / 1000; + } else { + if (val <= 0) + return 0; + if (val >= 127000) + return 127; + return (val + 500) / 1000; + } +} + +static u16 temp_to_u16(struct adt7461_data *data, long val) +{ + if (data->flags & ADT7461_FLAG_ADT7461_EXT) { + if (val <= -64000) + return 0; + if (val >= 191750) + return 0xFFC0; + return (val + 64000 + 125) / 250 * 64; + } else { + if (val <= 0) + return 0; + if (val >= 127750) + return 0x7FC0; + return (val + 125) / 250 * 64; + } +} + +static int adt7461_read_reg(struct i2c_client* client, u8 reg, u8 *value) +{ + int err; + + err = i2c_smbus_read_byte_data(client, reg); + if (err < 0) { + pr_err("adt7461_read_reg:Register %#02x read failed (%d)\n", + reg, err); + return err; + } + *value = err; + + return 0; +} + +static int adt7461_read16(struct i2c_client *client, u8 regh, u8 regl, + u16 *value) +{ + int err; + u8 oldh, newh, l; + + /* + * There is a trick here. We have to read two registers to have the + * sensor temperature, but we have to beware a conversion could occur + * inbetween the readings. The datasheet says we should either use + * the one-shot conversion register, which we don't want to do + * (disables hardware monitoring) or monitor the busy bit, which is + * impossible (we can't read the values and monitor that bit at the + * exact same time). So the solution used here is to read the high + * byte once, then the low byte, then the high byte again. If the new + * high byte matches the old one, then we have a valid reading. Else + * we have to read the low byte again, and now we believe we have a + * correct reading. + */ + if ((err = adt7461_read_reg(client, regh, &oldh)) + || (err = adt7461_read_reg(client, regl, &l)) + || (err = adt7461_read_reg(client, regh, &newh))) + return err; + if (oldh != newh) { + err = adt7461_read_reg(client, regl, &l); + if (err) + return err; + } + *value = (newh << 8) | l; + + return 0; +} + +static struct adt7461_data *adt7461_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adt7461_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ / 2 + HZ / 10) + || !data->valid) { + u8 h, l; + + pr_err("adt7461_update_device:Updating adt7461 data.\n"); + adt7461_read_reg(client, ADT7461_REG_R_LOCAL_LOW, &data->temp8[0]); + adt7461_read_reg(client, ADT7461_REG_R_LOCAL_HIGH, &data->temp8[1]); + adt7461_read_reg(client, ADT7461_REG_R_LOCAL_CRIT, &data->temp8[2]); + adt7461_read_reg(client, ADT7461_REG_R_REMOTE_CRIT, &data->temp8[3]); + adt7461_read_reg(client, ADT7461_REG_R_TCRIT_HYST, &data->temp_hyst); + + if (adt7461_read_reg(client, ADT7461_REG_R_LOCAL_TEMP, &h) == 0) + data->temp11[4] = h << 8; + + adt7461_read16(client, ADT7461_REG_R_REMOTE_TEMPH, + ADT7461_REG_R_REMOTE_TEMPL, &data->temp11[0]); + + if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_LOWH, &h) == 0) { + data->temp11[1] = h << 8; + if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_LOWL, &l) == 0) + data->temp11[1] |= l; + } + if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_HIGHH, &h) == 0) { + data->temp11[2] = h << 8; + if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_HIGHL, &l) == 0) + data->temp11[2] |= l; + } + + if (adt7461_read_reg(client, ADT7461_REG_R_REMOTE_OFFSH, + &h) == 0 + && adt7461_read_reg(client, ADT7461_REG_R_REMOTE_OFFSL, + &l) == 0) + data->temp11[3] = (h << 8) | l; + adt7461_read_reg(client, ADT7461_REG_R_STATUS, &data->alarms); + + /* Re-enable ALERT# output if it was originally enabled and + * relevant alarms are all clear */ + if ((data->config_orig & 0x80) == 0 + && (data->alarms & data->alert_alarms) == 0) { + u8 config; + + adt7461_read_reg(client, ADT7461_REG_R_CONFIG1, &config); + if (config & 0x80) { + pr_err("adt7461_update_device:Re-enabling ALERT#\n"); + i2c_smbus_write_byte_data(client, + ADT7461_REG_W_CONFIG1, + config & ~0x80); + } + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * 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 adt7461_data *data = adt7461_update_device(dev); + int temp; + + temp = temp_from_u8(data, data->temp8[attr->index]); + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + static const u8 reg[4] = { + ADT7461_REG_W_LOCAL_LOW, + ADT7461_REG_W_LOCAL_HIGH, + ADT7461_REG_W_LOCAL_CRIT, + ADT7461_REG_W_REMOTE_CRIT, + }; + + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7461_data *data = i2c_get_clientdata(client); + long val = simple_strtol(buf, NULL, 10); + int nr = attr->index; + + mutex_lock(&data->update_lock); + data->temp8[nr] = temp_to_u8(data, val); + i2c_smbus_write_byte_data(client, reg[nr], 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 adt7461_data *data = adt7461_update_device(dev); + int temp; + + temp = temp_from_u16(data, data->temp11[attr->index]); + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t set_temp11(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + static const u8 reg[6] = { + ADT7461_REG_W_REMOTE_LOWH, + ADT7461_REG_W_REMOTE_LOWL, + ADT7461_REG_W_REMOTE_HIGHH, + ADT7461_REG_W_REMOTE_HIGHL, + ADT7461_REG_W_REMOTE_OFFSH, + ADT7461_REG_W_REMOTE_OFFSL, + }; + + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7461_data *data = i2c_get_clientdata(client); + long val = simple_strtol(buf, NULL, 10); + int nr = attr->index; + + mutex_lock(&data->update_lock); + data->temp11[nr] = temp_to_u16(data, 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 adt7461_data *data = adt7461_update_device(dev); + int temp; + + temp = temp_from_u8(data, data->temp8[attr->index]); + + return sprintf(buf, "%d\n", temp - temp_from_s8(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 adt7461_data *data = i2c_get_clientdata(client); + long val = simple_strtol(buf, NULL, 10); + int temp; + + mutex_lock(&data->update_lock); + temp = temp_from_u8(data, data->temp8[2]); + data->temp_hyst = hyst_to_reg(temp - val); + i2c_smbus_write_byte_data(client, ADT7461_REG_W_TCRIT_HYST, + data->temp_hyst); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy, + char *buf) +{ + struct adt7461_data *data = adt7461_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 adt7461_data *data = adt7461_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_temp11, NULL, 4); +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, 0); +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, 1); +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, 2); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_temp8, + set_temp8, 3); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, show_temphyst, + set_temphyst, 2); +static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO, show_temphyst, NULL, 3); +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 *adt7461_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, + NULL +}; + +static const struct attribute_group adt7461_group = { + .attrs = adt7461_attributes, +}; + +static void adt7461_regulator_enable(struct i2c_client *client) +{ + struct adt7461_data *data = i2c_get_clientdata(client); + + data->regulator = regulator_get(NULL, "vdd_vcore_temp"); + if (IS_ERR_OR_NULL(data->regulator)) { + pr_err("adt7461_regulator_enable:Couldn't get regulator vdd_vcore_temp\n"); + data->regulator = NULL; + } + else { + regulator_enable(data->regulator); + /* Optimal time to get the regulator turned on + * before initializing adt7461 chip*/ + mdelay(5); + } +} + +static void adt7461_regulator_disable(struct i2c_client *client) +{ + struct adt7461_data *data = i2c_get_clientdata(client); + struct regulator *adt7461_reg = data->regulator; + int ret; + + if (adt7461_reg) { + ret = regulator_is_enabled(adt7461_reg); + if (ret > 0) + regulator_disable(adt7461_reg); + regulator_put(adt7461_reg); + } + data->regulator = NULL; +} + +static void adt7461_init_client(struct i2c_client *client) +{ + u8 config; + struct adt7461_data *data = i2c_get_clientdata(client); + + adt7461_regulator_enable(client); + /* Start the conversions. */ + i2c_smbus_write_byte_data(client, ADT7461_REG_W_CONVRATE, + 5); /* 2 Hz */ + if (adt7461_read_reg(client, ADT7461_REG_R_CONFIG1, &config) < 0) { + pr_err("adt7461_init_client:Initialization failed!\n"); + return; + } + data->config_orig = config; + + /* Check Temperature Range Select */ + if (config & 0x04) + data->flags |= ADT7461_FLAG_ADT7461_EXT; + + config &= 0xBF; /* run */ + if (config != data->config_orig) /* Only write if changed */ + i2c_smbus_write_byte_data(client, ADT7461_REG_W_CONFIG1, config); +} + +static int adt7461_probe(struct i2c_client *new_client, + const struct i2c_device_id *id) +{ + struct adt7461_data *data; + int err; + + data = kzalloc(sizeof(struct adt7461_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(new_client, data); + mutex_init(&data->update_lock); + + data->alert_alarms = 0x7c; + + /* Initialize the ADT7461 chip */ + adt7461_init_client(new_client); + + /* Register sysfs hooks */ + if ((err = sysfs_create_group(&new_client->dev.kobj, &adt7461_group))) + goto exit_free; + if ((err = device_create_file(&new_client->dev, + &sensor_dev_attr_temp2_offset.dev_attr))) + goto exit_remove_files; + + data->hwmon_dev = hwmon_device_register(&new_client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove_files; + } + + return 0; + +exit_remove_files: + sysfs_remove_group(&new_client->dev.kobj, &adt7461_group); +exit_free: + kfree(data); + return err; +} + +static int adt7461_remove(struct i2c_client *client) +{ + struct adt7461_data *data = i2c_get_clientdata(client); + + adt7461_regulator_disable(client); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &adt7461_group); + device_remove_file(&client->dev, + &sensor_dev_attr_temp2_offset.dev_attr); + + /* Restore initial configuration */ + i2c_smbus_write_byte_data(client, ADT7461_REG_W_CONFIG1, + data->config_orig); + + kfree(data); + return 0; +} + +static void adt7461_alert(struct i2c_client *client, unsigned int flag) +{ + struct adt7461_data *data = i2c_get_clientdata(client); + u8 config, alarms; + + adt7461_read_reg(client, ADT7461_REG_R_STATUS, &alarms); + if ((alarms & 0x7f) == 0) { + pr_err("adt7461_alert:Everything OK\n"); + } else { + if (alarms & 0x61) + pr_err("adt7461_alert:temp%d out of range, please check!\n", 1); + if (alarms & 0x1a) + pr_err("adt7461_alert:temp%d out of range, please check!\n", 2); + if (alarms & 0x04) + pr_err("adt7461_alert:temp%d diode open, please check!\n", 2); + + /* Disable ALERT# output, because these chips don't implement + SMBus alert correctly; they should only hold the alert line + low briefly. */ + if (alarms & data->alert_alarms) { + pr_err("adt7461_alert:Disabling ALERT#\n"); + adt7461_read_reg(client, ADT7461_REG_R_CONFIG1, &config); + i2c_smbus_write_byte_data(client, ADT7461_REG_W_CONFIG1, + config | 0x80); + } + } +} + +/* + * Driver data + */ +static const struct i2c_device_id adt7461_id[] = { + { "adt7461", 0 }, +}; + +MODULE_DEVICE_TABLE(i2c, adt7461_id); + +static struct i2c_driver adt7461_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "adt7461", + }, + .probe = adt7461_probe, + .remove = adt7461_remove, + .alert = adt7461_alert, + .id_table = adt7461_id, +}; + +static int __init sensors_adt7461_init(void) +{ + return i2c_add_driver(&adt7461_driver); +} + +static void __exit sensors_adt7461_exit(void) +{ + i2c_del_driver(&adt7461_driver); +} + +MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>"); +MODULE_DESCRIPTION("ADT7461 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_adt7461_init); +module_exit(sensors_adt7461_exit); |