Just had a quick look over the patch and commented on things that caught my attention after applying your previous patch-set. I will also apply this patch later to view the result. And I found some of my typos, that we should not carry on ;-) Lars-Peter Clausen schrieb: > The adt7310/adt7320 is the SPI version of the adt7410/adt7420. The register map > layout is a bit different, i.e. the register addresses differ between the two > variants, but the bit layouts of the individual registers are identical. So both > chip variants can easily be supported by the same driver. The issue of non > matching register address layouts is solved by a simple look-up table which > translates the I2C addresses to the SPI addresses. > > The patch moves the bulk of the adt7410 driver to a common module that will be > shared by the adt7410 and adt7310 drivers. This common module implements the > driver logic and uses a set of virtual functions to perform IO access. The > adt7410 and adt7310 driver modules provide proper implementations of these IO > accessor functions for I2C respective SPI. > > Signed-off-by: Lars-Peter Clausen <lars@xxxxxxxxxx> > > --- > Changes since v1: > * Update the driver documentation to include ADT7310/ADT7320/ADT7420 > * Pass the result of the read methods via the return value instead of a > pointer argument > * Simplify spi read methods by using spi_w8r8 and spi_w8r16 > * Update module description of the shared module > * Fix some typos > --- > Documentation/hwmon/adt7410 | 42 ++-- > drivers/hwmon/Kconfig | 20 ++ > drivers/hwmon/Makefile | 2 + > drivers/hwmon/adt7310.c | 115 +++++++++++ > drivers/hwmon/adt7410.c | 464 +++--------------------------------------- > drivers/hwmon/adt7x10.c | 476 ++++++++++++++++++++++++++++++++++++++++++++ > drivers/hwmon/adt7x10.h | 48 +++++ > 7 files changed, 720 insertions(+), 447 deletions(-) > create mode 100644 drivers/hwmon/adt7310.c > create mode 100644 drivers/hwmon/adt7x10.c > create mode 100644 drivers/hwmon/adt7x10.h > > diff --git a/Documentation/hwmon/adt7410 b/Documentation/hwmon/adt7410 > index 9600400..e452ae0 100644 > --- a/Documentation/hwmon/adt7410 > +++ b/Documentation/hwmon/adt7410 > @@ -7,25 +7,41 @@ Supported chips: > Addresses scanned: I2C 0x48 - 0x4B > Datasheet: Publicly available at the Analog Devices website > http://www.analog.com/static/imported-files/data_sheets/ADT7410.pdf > + * Analog Devices ADT7420 > + Prefix: 'adt7420' > + Addresses scanned: I2C 0x48 - 0x4B > + Datasheet: Publicly available at the Analog Devices website > + http://www.analog.com/static/imported-files/data_sheets/ADT7420.pdf > + * Analog Devices ADT7310 > + Prefix: 'adt7310' > + Addresses scanned: I2C 0x48 - 0x4B > + Datasheet: Publicly available at the Analog Devices website > + http://www.analog.com/static/imported-files/data_sheets/ADT7310.pdf > + * Analog Devices ADT7320 > + Prefix: 'adt7320' > + Addresses scanned: I2C 0x48 - 0x4B > + Datasheet: Publicly available at the Analog Devices website > + http://www.analog.com/static/imported-files/data_sheets/ADT7320.pdf > > Author: Hartmut Knaack <knaack.h@xxxxxx> > > Description > ----------- > > -The ADT7410 is a temperature sensor with rated temperature range of -55°C to > -+150°C. It has a high accuracy of +/-0.5°C and can be operated at a resolution > -of 13 bits (0.0625°C) or 16 bits (0.0078°C). The sensor provides an INT pin to > -indicate that a minimum or maximum temperature set point has been exceeded, as > -well as a critical temperature (CT) pin to indicate that the critical > -temperature set point has been exceeded. Both pins can be set up with a common > -hysteresis of 0°C - 15°C and a fault queue, ranging from 1 to 4 events. Both > -pins can individually set to be active-low or active-high, while the whole > -device can either run in comparator mode or interrupt mode. The ADT7410 > -supports continous temperature sampling, as well as sampling one temperature > -value per second or even justget one sample on demand for power saving. > -Besides, it can completely power down its ADC, if power management is > -required. > +The ADT7410 and similar are a temperature sensors with rated temperature range > +of -55°C to +150°C (ADT7310/ADT7410) or -40°C to +150°C (ADT7320/ADT7420). They > +have a high accuracy of +/-0.5°C (ADT7310/ADT7410) or +/-0.2C (ADT7430/ADT7420) > +and can be operated at a resolution of 13 bits (0.0625°C) or 16 bits (0.0078°C). > +The sensor provides an INT pin to indicate that a minimum or maximum temperature > +set point has been exceeded, as well as a critical temperature (CT) pin to > +indicate that the critical temperature set point has been exceeded. Both pins > +can be set up with a common hysteresis of 0°C - 15°C and a fault queue, ranging > +from 1 to 4 events. Both pins can individually set to be active-low or > +active-high, while the whole device can either run in comparator mode or > +interrupt mode. The ADT7410 supports continous temperature sampling, as well as It is my typo, but since you are already on it: continuous. > +sampling one temperature value per second or even justget one sample on demand Same here: just get. > +for power saving. Besides, it can completely power down its ADC, if power > +management is required. > > Configuration Notes > ------------------- <...> > diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c > new file mode 100644 > index 0000000..eeff198c > --- /dev/null > +++ b/drivers/hwmon/adt7x10.c > @@ -0,0 +1,476 @@ > +/* > + * adt7410.c - Part of lm_sensors, Linux kernel modules for hardware > + * monitoring > + * This driver handles the ADT7410 and compatible digital temperature sensors. > + * Hartmut Knaack <knaack.h@xxxxxx> 2012-07-22 > + * based on lm75.c by Frodo Looijaard <frodol@xxxxxx> > + * and adt7410.c from iio-staging by Sonic Zhang <sonic.zhang@xxxxxxxxxx> > + * > + * 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/hwmon.h> > +#include <linux/hwmon-sysfs.h> > +#include <linux/err.h> > +#include <linux/mutex.h> > +#include <linux/delay.h> > + > +#include "adt7x10.h" > + > +/* > + * ADT7410 status > + */ > +#define ADT7410_STAT_T_LOW (1 << 4) > +#define ADT7410_STAT_T_HIGH (1 << 5) > +#define ADT7410_STAT_T_CRIT (1 << 6) > +#define ADT7410_STAT_NOT_RDY (1 << 7) > + > +/* > + * ADT7410 config > + */ > +#define ADT7410_FAULT_QUEUE_MASK (1 << 0 | 1 << 1) > +#define ADT7410_CT_POLARITY (1 << 2) > +#define ADT7410_INT_POLARITY (1 << 3) > +#define ADT7410_EVENT_MODE (1 << 4) > +#define ADT7410_MODE_MASK (1 << 5 | 1 << 6) > +#define ADT7410_FULL (0 << 5 | 0 << 6) > +#define ADT7410_PD (1 << 5 | 1 << 6) > +#define ADT7410_RESOLUTION (1 << 7) > + > +/* > + * ADT7410 masks > + */ > +#define ADT7410_T13_VALUE_MASK 0xFFF8 > +#define ADT7410_T_HYST_MASK 0xF > + > +/* straight from the datasheet */ > +#define ADT7410_TEMP_MIN (-55000) > +#define ADT7410_TEMP_MAX 150000 > + > +/* Each client has this additional data */ > +struct adt7410_data { > + const struct adt7410_ops *ops; > + const char *name; > + struct device *hwmon_dev; > + struct mutex update_lock; > + u8 config; > + u8 oldconfig; > + bool valid; /* true if registers valid */ > + unsigned long last_updated; /* In jiffies */ > + s16 temp[4]; /* Register values, > + 0 = input > + 1 = high > + 2 = low > + 3 = critical */ > + u8 hyst; /* hysteresis offset */ > +}; > + > +static int adt7410_read_byte(struct device *dev, u8 reg) > +{ > + struct adt7410_data *d = dev_get_drvdata(dev); > + return d->ops->read_byte(dev, reg); > +} > + > +static int adt7410_write_byte(struct device *dev, u8 reg, u8 data) > +{ > + struct adt7410_data *d = dev_get_drvdata(dev); > + return d->ops->write_byte(dev, reg, data); > +} > + > +static int adt7410_read_word(struct device *dev, u8 reg) > +{ > + struct adt7410_data *d = dev_get_drvdata(dev); > + return d->ops->read_word(dev, reg); > +} > + > +static int adt7410_write_word(struct device *dev, u8 reg, u16 data) > +{ > + struct adt7410_data *d = dev_get_drvdata(dev); > + return d->ops->write_word(dev, reg, data); > +} > + > +static const u8 ADT7410_REG_TEMP[4] = { > + ADT7410_TEMPERATURE, /* input */ > + ADT7410_T_ALARM_HIGH, /* high */ > + ADT7410_T_ALARM_LOW, /* low */ > + ADT7410_T_CRIT, /* critical */ > +}; > + > +static int adt7410_temp_ready(struct device *dev) > +{ > + int i, status; > + > + for (i = 0; i < 6; i++) { > + status = adt7410_read_byte(dev, ADT7410_STATUS); > + if (status < 0) > + return status; > + if (!(status & ADT7410_STAT_NOT_RDY)) > + return 0; > + msleep(60); > + } > + return -ETIMEDOUT; > +} > + > +static int adt7410_update_temp(struct device *dev) > +{ > + struct adt7410_data *data = dev_get_drvdata(dev); > + int ret = 0; > + > + mutex_lock(&data->update_lock); > + > + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) > + || !data->valid) { > + int temp; > + > + dev_dbg(dev, "Starting update\n"); > + > + ret = adt7410_temp_ready(dev); /* check for new value */ > + if (ret) > + goto abort; > + > + temp = adt7410_read_word(dev, ADT7410_REG_TEMP[0]); > + if (temp < 0) { > + ret = temp; > + dev_dbg(dev, "Failed to read value: reg %d, error %d\n", > + ADT7410_REG_TEMP[0], ret); > + goto abort; > + } > + data->temp[0] = temp; > + data->last_updated = jiffies; > + data->valid = true; > + } > + > +abort: > + mutex_unlock(&data->update_lock); > + return ret; > +} > + > +static int adt7410_fill_cache(struct device *dev) > +{ > + struct adt7410_data *data = dev_get_drvdata(dev); > + int ret; > + int i; > + > + for (i = 1; i < ARRAY_SIZE(data->temp); i++) { > + ret = adt7410_read_word(dev, ADT7410_REG_TEMP[i]); > + if (ret < 0) { > + dev_dbg(dev, "Failed to read value: reg %d, error %d\n", > + ADT7410_REG_TEMP[0], ret); That should be ADT7410_REG_TEMP[i]. > + return ret; > + } > + data->temp[i] = ret; > + } > + > + ret = adt7410_read_byte(dev, ADT7410_T_HYST); > + if (ret < 0) { > + dev_dbg(dev, "Failed to read value: reg %d, error %d\n", > + ADT7410_T_HYST, ret); > + return ret; > + } > + data->hyst = ret; > + > + return 0; > +} > + > +static s16 ADT7410_TEMP_TO_REG(long temp) > +{ > + return DIV_ROUND_CLOSEST(clamp_val(temp, ADT7410_TEMP_MIN, > + ADT7410_TEMP_MAX) * 128, 1000); > +} clamp_val vs. SENSORS_LIMIT. What are the pros and cons of each macro? Guenther, are there any reasons preventing to drop SENSORS_LIMIT in favor of clamp_val in the whole hwmon subsystem? > + > +static int ADT7410_REG_TO_TEMP(struct adt7410_data *data, s16 reg) > +{ > + /* in 13 bit mode, bits 0-2 are status flags - mask them out */ > + if (!(data->config & ADT7410_RESOLUTION)) > + reg &= ADT7410_T13_VALUE_MASK; > + /* > + * temperature is stored in twos complement format, in steps of > + * 1/128°C > + */ > + return DIV_ROUND_CLOSEST(reg * 1000, 128); > +} > + > +/*-----------------------------------------------------------------------*/ > + > +/* sysfs attributes for hwmon */ > + > +static ssize_t adt7410_show_temp(struct device *dev, > + struct device_attribute *da, char *buf) > +{ > + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); > + struct adt7410_data *data = dev_get_drvdata(dev); > + > + > + if (attr->index == 0) { > + int ret; > + > + ret = adt7410_update_temp(dev); > + if (ret) > + return ret; > + } > + > + return sprintf(buf, "%d\n", ADT7410_REG_TO_TEMP(data, > + data->temp[attr->index])); > +} > + > +static ssize_t adt7410_set_temp(struct device *dev, > + struct device_attribute *da, > + const char *buf, size_t count) > +{ > + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); > + struct adt7410_data *data = dev_get_drvdata(dev); > + int nr = attr->index; > + long temp; > + int ret; > + > + ret = kstrtol(buf, 10, &temp); > + if (ret) > + return ret; > + > + mutex_lock(&data->update_lock); > + data->temp[nr] = ADT7410_TEMP_TO_REG(temp); > + ret = adt7410_write_word(dev, ADT7410_REG_TEMP[nr], data->temp[nr]); > + if (ret) > + count = ret; > + mutex_unlock(&data->update_lock); > + return count; > +} > + > +static ssize_t adt7410_show_t_hyst(struct device *dev, > + struct device_attribute *da, > + char *buf) > +{ > + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); > + struct adt7410_data *data = dev_get_drvdata(dev); > + int nr = attr->index; > + int hyst; > + > + hyst = (data->hyst & ADT7410_T_HYST_MASK) * 1000; > + > + /* > + * hysteresis is stored as a 4 bit offset in the device, convert it > + * to an absolute value > + */ > + if (nr == 2) /* min has positive offset, others have negative */ > + hyst = -hyst; > + return sprintf(buf, "%d\n", > + ADT7410_REG_TO_TEMP(data, data->temp[nr]) - hyst); > +} > + > +static ssize_t adt7410_set_t_hyst(struct device *dev, > + struct device_attribute *da, > + const char *buf, size_t count) > +{ > + struct adt7410_data *data = dev_get_drvdata(dev); > + int limit, ret; > + long hyst; > + > + ret = kstrtol(buf, 10, &hyst); > + if (ret) > + return ret; > + /* convert absolute hysteresis value to a 4 bit delta value */ > + limit = ADT7410_REG_TO_TEMP(data, data->temp[1]); > + hyst = clamp_val(hyst, ADT7410_TEMP_MIN, ADT7410_TEMP_MAX); > + data->hyst = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), > + 0, ADT7410_T_HYST_MASK); > + ret = adt7410_write_byte(dev, ADT7410_T_HYST, data->hyst); > + if (ret) > + return ret; > + > + return count; > +} > + > +static ssize_t adt7410_show_alarm(struct device *dev, > + struct device_attribute *da, > + char *buf) > +{ > + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); > + int ret; > + > + ret = adt7410_read_byte(dev, ADT7410_STATUS); > + if (ret < 0) > + return ret; > + > + return sprintf(buf, "%d\n", !!(ret & attr->index)); > +} > + > +static ssize_t adt7410_show_name(struct device *dev, > + struct device_attribute *da, char *buf) > +{ > + struct adt7410_data *data = dev_get_drvdata(dev); > + > + return sprintf(buf, "%s\n", data->name); > +} > + > +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, adt7410_show_temp, NULL, 0); > +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, > + adt7410_show_temp, adt7410_set_temp, 1); > +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, > + adt7410_show_temp, adt7410_set_temp, 2); > +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, > + adt7410_show_temp, adt7410_set_temp, 3); > +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, > + adt7410_show_t_hyst, adt7410_set_t_hyst, 1); > +static SENSOR_DEVICE_ATTR(temp1_min_hyst, S_IRUGO, > + adt7410_show_t_hyst, NULL, 2); > +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO, > + adt7410_show_t_hyst, NULL, 3); > +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, adt7410_show_alarm, > + NULL, ADT7410_STAT_T_LOW); > +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, adt7410_show_alarm, > + NULL, ADT7410_STAT_T_HIGH); > +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, adt7410_show_alarm, > + NULL, ADT7410_STAT_T_CRIT); > +static DEVICE_ATTR(name, S_IRUGO, adt7410_show_name, NULL); > + > +static struct attribute *adt7410_attributes[] = { > + &sensor_dev_attr_temp1_input.dev_attr.attr, > + &sensor_dev_attr_temp1_max.dev_attr.attr, > + &sensor_dev_attr_temp1_min.dev_attr.attr, > + &sensor_dev_attr_temp1_crit.dev_attr.attr, > + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, > + &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, > + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, > + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, > + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, > + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, > + NULL > +}; > + > +static const struct attribute_group adt7410_group = { > + .attrs = adt7410_attributes, > +}; > + > +int adt7410_probe(struct device *dev, const char *name, > + const struct adt7410_ops *ops) > +{ > + struct adt7410_data *data; > + int ret; > + > + data = devm_kzalloc(dev, sizeof(struct adt7410_data), > + GFP_KERNEL); > + if (!data) > + return -ENOMEM; > + > + data->ops = ops; > + data->name = name; > + > + dev_set_drvdata(dev, data); > + mutex_init(&data->update_lock); > + > + /* configure as specified */ > + ret = adt7410_read_byte(dev, ADT7410_CONFIG); > + if (ret < 0) { > + dev_dbg(dev, "Can't read config? %d\n", ret); > + return ret; > + } > + data->oldconfig = ret; > + > + /* > + * Set to 16 bit resolution, continous conversion and comparator mode. > + */ > + data->config = data->oldconfig; > + data->config &= ~ADT7410_MODE_MASK; > + data->config |= ADT7410_FULL | ADT7410_RESOLUTION | ADT7410_EVENT_MODE; > + if (data->config != data->oldconfig) { > + ret = adt7410_write_byte(dev, ADT7410_CONFIG, data->config); > + if (ret) > + return ret; > + } > + dev_dbg(dev, "Config %02x\n", data->config); > + > + ret = adt7410_fill_cache(dev); > + if (ret) > + goto exit_restore; > + > + /* Register sysfs hooks */ > + ret = sysfs_create_group(&dev->kobj, &adt7410_group); > + if (ret) > + goto exit_restore; > + > + /* > + * The I2C device will already have it's own 'name' attribute, but for > + * the SPI device we need to register it. name will only be non NULL if > + * the device doesn't register the 'name' attribute on its own. > + */ > + if (name) { > + ret = device_create_file(dev, &dev_attr_name); > + if (ret) > + goto exit_remove; > + } > + > + data->hwmon_dev = hwmon_device_register(dev); > + if (IS_ERR(data->hwmon_dev)) { > + ret = PTR_ERR(data->hwmon_dev); > + goto exit_remove_name; > + } > + > + return 0; > + > +exit_remove_name: > + if (name) > + device_remove_file(dev, &dev_attr_name); > +exit_remove: > + sysfs_remove_group(&dev->kobj, &adt7410_group); > +exit_restore: > + adt7410_write_byte(dev, ADT7410_CONFIG, data->oldconfig); > + return ret; > +} > +EXPORT_SYMBOL_GPL(adt7410_probe); > + > +int adt7410_remove(struct device *dev) > +{ > + struct adt7410_data *data = dev_get_drvdata(dev); > + > + hwmon_device_unregister(data->hwmon_dev); > + if (data->name) > + device_remove_file(dev, &dev_attr_name); > + sysfs_remove_group(&dev->kobj, &adt7410_group); > + if (data->oldconfig != data->config) > + adt7410_write_byte(dev, ADT7410_CONFIG, > + data->oldconfig); > + return 0; > +} > +EXPORT_SYMBOL_GPL(adt7410_remove); > + > +#ifdef CONFIG_PM_SLEEP > + > +static int adt7410_suspend(struct device *dev) > +{ > + struct adt7410_data *data = dev_get_drvdata(dev); > + > + return adt7410_write_byte(dev, ADT7410_CONFIG, > + data->config | ADT7410_PD); > +} > + > +static int adt7410_resume(struct device *dev) > +{ > + struct adt7410_data *data = dev_get_drvdata(dev); > + > + return adt7410_write_byte(dev, ADT7410_CONFIG, data->config); > +} > + > +SIMPLE_DEV_PM_OPS(adt7410_dev_pm_ops, adt7410_suspend, adt7410_resume); > +EXPORT_SYMBOL_GPL(adt7410_dev_pm_ops); > + > +#endif /* CONFIG_PM_SLEEP */ > + > +MODULE_AUTHOR("Hartmut Knaack"); > +MODULE_DESCRIPTION("ADT7410/ADT7420, ADT7310/ADT7320 common code"); > +MODULE_LICENSE("GPL"); > <..> -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html