diff options
author | Ivan Zaentsev <ivan.zaentsev@wirenboard.ru> | 2020-09-04 19:00:03 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-10-02 11:49:50 +0200 |
commit | 021da53e65fdd0e1b8492c2670dd075c0ea910fc (patch) | |
tree | 1c13a1936d8f43a1d3b7804446b722296c69637b /drivers | |
parent | b31de43c608ffaa83e67a410ba58d678ff405f55 (diff) | |
download | linux-021da53e65fdd0e1b8492c2670dd075c0ea910fc.tar.bz2 |
w1: w1_therm: Add sysfs entries to control conversion time and driver features
The conversion time of common DS18B20 clones deviates from
datasheet specs. Allow adjustment and automatic measure of the
conversion time.
Add 'conv_time' sysfs attribute:
*read*: Current conversion time in milliseconds.
*write*:
'0': Set default conversion time.
'1': Measure and set the conversion time. Make a
single temperature conversion, poll and measure
an actual value. Measured value is increased
by 20% for temperature drift. A new conversion
time is returned by reading the same attribute.
other positive value:
Set the conversion time in milliseconds.
The setting is active until a resolution change. Then it is reset to
default conversion time for a new resolution.
Add 'features' sysfs attribute to control optional driver settings
per device. Bit masks to read/write (logical OR):
1: Enable check for conversion success. If byte 6 of
scratchpad memory is 0xC after conversion, and
temperature reads 85.00 (powerup value) or 127.94
(insufficient power) - return a conversion error.
2: Enable poll for conversion completion. Generate read cycles
after the conversion start and wait for 1's. In parasite
power mode this feature is not available.
There are some clones of DS18B20 with fixed 12 bit resolution. Make the
driver verify the resolution by reading back the device after resolution
change.
Signed-off-by: Ivan Zaentsev <ivan.zaentsev@wirenboard.ru>
Acked-by: Evgeniy Polyakov <zbr@ioremap.net>
Link: https://lore.kernel.org/r/20200904160004.87710-1-ivan.zaentsev@wirenboard.ru
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/w1/slaves/w1_therm.c | 357 |
1 files changed, 331 insertions, 26 deletions
diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c index c1b4eda16719..9b2d96335a70 100644 --- a/drivers/w1/slaves/w1_therm.c +++ b/drivers/w1/slaves/w1_therm.c @@ -17,6 +17,7 @@ #include <linux/delay.h> #include <linux/hwmon.h> #include <linux/string.h> +#include <linux/jiffies.h> #include <linux/w1.h> @@ -65,6 +66,20 @@ static u16 bulk_read_device_counter; /* =0 as per C standard */ #define MIN_TEMP -55 /* min temperature that can be mesured */ #define MAX_TEMP 125 /* max temperature that can be mesured */ +/* Allowed values for sysfs conv_time attribute */ +#define CONV_TIME_DEFAULT 0 +#define CONV_TIME_MEASURE 1 + +/* Bits in sysfs "features" value */ +#define W1_THERM_CHECK_RESULT 1 /* Enable conversion success check */ +#define W1_THERM_POLL_COMPLETION 2 /* Poll for conversion completion */ +#define W1_THERM_FEATURES_MASK 3 /* All values mask */ + +/* Poll period in milliseconds. Should be less then a shortest operation on the device */ +#define W1_POLL_PERIOD 32 +#define W1_POLL_CONVERT_TEMP 2000 /* Timeout for W1_CONVERT_TEMP, ms */ +#define W1_POLL_RECALL_EEPROM 500 /* Timeout for W1_RECALL_EEPROM, ms*/ + /* Helpers Macros */ /* @@ -89,6 +104,20 @@ static u16 bulk_read_device_counter; /* =0 as per C standard */ (((struct w1_therm_family_data *)(sl->family_data))->resolution) /* + * return the conv_time_override of the sl slave + * always test family data existence before using this macro + */ + #define SLAVE_CONV_TIME_OVERRIDE(sl) \ + (((struct w1_therm_family_data *)(sl->family_data))->conv_time_override) + +/* + * return the features of the sl slave + * always test family data existence before using this macro + */ + #define SLAVE_FEATURES(sl) \ + (((struct w1_therm_family_data *)(sl->family_data))->features) + +/* * return whether or not a converT command has been issued to the slave * * 0: no bulk read is pending * * -1: conversion is in progress @@ -136,6 +165,8 @@ struct w1_therm_family_converter { * -x error or undefined * @resolution: current device resolution * @convert_triggered: conversion state of the device + * @conv_time_override: user selected conversion time or CONV_TIME_DEFAULT + * @features: bit mask - enable temperature validity check, poll for completion * @specific_functions: pointer to struct of device specific function */ struct w1_therm_family_data { @@ -144,6 +175,8 @@ struct w1_therm_family_data { int external_powered; int resolution; int convert_triggered; + int conv_time_override; + unsigned int features; struct w1_therm_family_converter *specific_functions; }; @@ -285,6 +318,19 @@ static ssize_t therm_bulk_read_store(struct device *device, static ssize_t therm_bulk_read_show(struct device *device, struct device_attribute *attr, char *buf); +static ssize_t conv_time_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t conv_time_store(struct device *device, + struct device_attribute *attr, const char *buf, + size_t size); + +static ssize_t features_show(struct device *device, + struct device_attribute *attr, char *buf); + +static ssize_t features_store(struct device *device, + struct device_attribute *attr, const char *buf, + size_t size); /* Attributes declarations */ static DEVICE_ATTR_RW(w1_slave); @@ -294,6 +340,8 @@ static DEVICE_ATTR_RO(ext_power); static DEVICE_ATTR_RW(resolution); static DEVICE_ATTR_WO(eeprom); static DEVICE_ATTR_RW(alarms); +static DEVICE_ATTR_RW(conv_time); +static DEVICE_ATTR_RW(features); static DEVICE_ATTR_RW(therm_bulk_read); /* attribut at master level */ @@ -328,6 +376,8 @@ static struct attribute *w1_therm_attrs[] = { &dev_attr_resolution.attr, &dev_attr_eeprom.attr, &dev_attr_alarms.attr, + &dev_attr_conv_time.attr, + &dev_attr_features.attr, NULL, }; @@ -337,6 +387,8 @@ static struct attribute *w1_ds18s20_attrs[] = { &dev_attr_ext_power.attr, &dev_attr_eeprom.attr, &dev_attr_alarms.attr, + &dev_attr_conv_time.attr, + &dev_attr_features.attr, NULL, }; @@ -348,6 +400,8 @@ static struct attribute *w1_ds28ea00_attrs[] = { &dev_attr_resolution.attr, &dev_attr_eeprom.attr, &dev_attr_alarms.attr, + &dev_attr_conv_time.attr, + &dev_attr_features.attr, NULL, }; @@ -466,7 +520,10 @@ static inline int w1_DS18B20_convert_time(struct w1_slave *sl) if (!sl->family_data) return -ENODEV; /* device unknown */ - /* return time in ms for conversion operation */ + if (SLAVE_CONV_TIME_OVERRIDE(sl) != CONV_TIME_DEFAULT) + return SLAVE_CONV_TIME_OVERRIDE(sl); + + /* Return the conversion time from datasheet, depending on resolution */ switch (SLAVE_RESOLUTION(sl)) { case 9: ret = 95; @@ -486,8 +543,13 @@ static inline int w1_DS18B20_convert_time(struct w1_slave *sl) static inline int w1_DS18S20_convert_time(struct w1_slave *sl) { - (void)(sl); - return 750; /* always 750ms for DS18S20 */ + if (!sl->family_data) + return -ENODEV; /* device unknown */ + + if (SLAVE_CONV_TIME_OVERRIDE(sl) == CONV_TIME_DEFAULT) + return 750; /* default for DS18S20 */ + else + return SLAVE_CONV_TIME_OVERRIDE(sl); } static inline int w1_DS18B20_write_data(struct w1_slave *sl, @@ -507,7 +569,7 @@ static inline int w1_DS18B20_set_resolution(struct w1_slave *sl, int val) { int ret; u8 new_config_register[3]; /* array of data to be written */ - struct therm_info info; + struct therm_info info, info2; /* resolution of DS18B20 is in the range [9..12] bits */ if (val < 9 || val > 12) @@ -532,8 +594,18 @@ static inline int w1_DS18B20_set_resolution(struct w1_slave *sl, int val) /* Write data in the device RAM */ ret = w1_DS18B20_write_data(sl, new_config_register); + if (ret) + return ret; - return ret; + /* Some DS18B20 clones don't support resolution change, read back to verify */ + ret = read_scratchpad(sl, &info2); + if (ret) + return ret; + + if ((info2.rom[4] & 0x9F) == (info.rom[4] & 0x9F)) + return 0; + else + return -EIO; } static inline int w1_DS18B20_get_resolution(struct w1_slave *sl) @@ -700,6 +772,22 @@ static inline bool bus_mutex_lock(struct mutex *lock) } /** + * check_family_data() - Check if family data and specific functions are present + * @sl: W1 device data + * + * Return: 0 - OK, negative value - error + */ +static int check_family_data(struct w1_slave *sl) +{ + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(&sl->dev, + "%s: Device is not supported by the driver\n", __func__); + return -EINVAL; /* No device family */ + } + return 0; +} + +/** * support_bulk_read() - check if slave support bulk read * @sl: device to check the ability * @@ -883,6 +971,34 @@ static int reset_select_slave(struct w1_slave *sl) return 0; } +/** + * w1_poll_completion - Poll for operation completion, with timeout + * @dev_master: the device master of the bus + * @tout_ms: timeout in milliseconds + * + * The device is answering 0's while an operation is in progress and 1's after it completes + * Timeout may happen if the previous command was not recognised due to a line noise + * + * Return: 0 - OK, negative error - timeout + */ +int w1_poll_completion(struct w1_master *dev_master, int tout_ms) +{ + int i; + + for (i = 0; i < tout_ms/W1_POLL_PERIOD; i++) { + /* Delay is before poll, for device to recognize a command */ + msleep(W1_POLL_PERIOD); + + /* Compare all 8 bits to mitigate a noise on the bus */ + if (w1_read_8(dev_master) == 0xFF) + break; + } + if (i == tout_ms/W1_POLL_PERIOD) + return -EIO; + + return 0; +} + static int convert_t(struct w1_slave *sl, struct therm_info *info) { struct w1_master *dev_master = sl->master; @@ -898,6 +1014,13 @@ static int convert_t(struct w1_slave *sl, struct therm_info *info) (!SLAVE_POWERMODE(sl) && w1_strong_pullup)); + if (strong_pullup && SLAVE_FEATURES(sl) & W1_THERM_POLL_COMPLETION) { + dev_warn(&sl->dev, + "%s: Disabling W1_THERM_POLL_COMPLETION in parasite power mode.\n", + __func__); + SLAVE_FEATURES(sl) &= ~W1_THERM_POLL_COMPLETION; + } + /* get conversion duration device and id dependent */ t_conv = conversion_time(sl); @@ -933,15 +1056,38 @@ static int convert_t(struct w1_slave *sl, struct therm_info *info) } mutex_unlock(&dev_master->bus_mutex); } else { /*no device need pullup */ - mutex_unlock(&dev_master->bus_mutex); - - sleep_rem = msleep_interruptible(t_conv); - if (sleep_rem != 0) { - ret = -EINTR; - goto dec_refcnt; + if (SLAVE_FEATURES(sl) & W1_THERM_POLL_COMPLETION) { + ret = w1_poll_completion(dev_master, W1_POLL_CONVERT_TEMP); + if (ret) { + dev_dbg(&sl->dev, "%s: Timeout\n", __func__); + goto mt_unlock; + } + mutex_unlock(&dev_master->bus_mutex); + } else { + /* Fixed delay */ + mutex_unlock(&dev_master->bus_mutex); + sleep_rem = msleep_interruptible(t_conv); + if (sleep_rem != 0) { + ret = -EINTR; + goto dec_refcnt; + } } } ret = read_scratchpad(sl, info); + + /* If enabled, check for conversion success */ + if ((SLAVE_FEATURES(sl) & W1_THERM_CHECK_RESULT) && + (info->rom[6] == 0xC) && + ((info->rom[1] == 0x5 && info->rom[0] == 0x50) || + (info->rom[1] == 0x7 && info->rom[0] == 0xFF)) + ) { + /* Invalid reading (scratchpad byte 6 = 0xC) + * due to insufficient conversion time + * or power failure. + */ + ret = -EIO; + } + goto dec_refcnt; } @@ -955,6 +1101,76 @@ error: return ret; } +static int conv_time_measure(struct w1_slave *sl, int *conv_time) +{ + struct therm_info inf, + *info = &inf; + struct w1_master *dev_master = sl->master; + int max_trying = W1_THERM_MAX_TRY; + int ret = -ENODEV; + bool strong_pullup; + + if (!sl->family_data) + goto error; + + strong_pullup = (w1_strong_pullup == 2 || + (!SLAVE_POWERMODE(sl) && + w1_strong_pullup)); + + if (strong_pullup) { + pr_info("%s: Measure with strong_pullup is not supported.\n", __func__); + return -EINVAL; + } + + memset(info->rom, 0, sizeof(info->rom)); + + /* prevent the slave from going away in sleep */ + atomic_inc(THERM_REFCNT(sl->family_data)); + + if (!bus_mutex_lock(&dev_master->bus_mutex)) { + ret = -EAGAIN; /* Didn't acquire the mutex */ + goto dec_refcnt; + } + + while (max_trying-- && ret) { /* ret should be 0 */ + info->verdict = 0; + info->crc = 0; + /* safe version to select slave */ + if (!reset_select_slave(sl)) { + int j_start, j_end; + + /*no device need pullup */ + w1_write_8(dev_master, W1_CONVERT_TEMP); + + j_start = jiffies; + ret = w1_poll_completion(dev_master, W1_POLL_CONVERT_TEMP); + if (ret) { + dev_dbg(&sl->dev, "%s: Timeout\n", __func__); + goto mt_unlock; + } + j_end = jiffies; + /* 1.2x increase for variation and changes over temperature range */ + *conv_time = jiffies_to_msecs(j_end-j_start)*12/10; + pr_debug("W1 Measure complete, conv_time = %d, HZ=%d.\n", + *conv_time, HZ); + if (*conv_time <= CONV_TIME_MEASURE) { + ret = -EIO; + goto mt_unlock; + } + mutex_unlock(&dev_master->bus_mutex); + ret = read_scratchpad(sl, info); + goto dec_refcnt; + } + + } +mt_unlock: + mutex_unlock(&dev_master->bus_mutex); +dec_refcnt: + atomic_dec(THERM_REFCNT(sl->family_data)); +error: + return ret; +} + static int read_scratchpad(struct w1_slave *sl, struct therm_info *info) { struct w1_master *dev_master = sl->master; @@ -1118,10 +1334,7 @@ static int recall_eeprom(struct w1_slave *sl) if (!reset_select_slave(sl)) { w1_write_8(dev_master, W1_RECALL_EEPROM); - - ret = 1; /* Slave will pull line to 0 */ - while (ret) - ret = 1 - w1_touch_bit(dev_master, 1); + ret = w1_poll_completion(dev_master, W1_POLL_RECALL_EEPROM); } } @@ -1345,11 +1558,13 @@ static ssize_t w1_slave_store(struct device *device, } if (ret) { - dev_info(device, - "%s: writing error %d\n", __func__, ret); - /* return size to avoid call back again */ - } else - SLAVE_RESOLUTION(sl) = val; + dev_warn(device, "%s: Set resolution - error %d\n", __func__, ret); + /* Propagate error to userspace */ + return ret; + } + SLAVE_RESOLUTION(sl) = val; + /* Reset the conversion time to default - it depends on resolution */ + SLAVE_CONV_TIME_OVERRIDE(sl) = CONV_TIME_DEFAULT; return size; /* always return size to avoid infinite calling */ } @@ -1465,12 +1680,12 @@ static ssize_t resolution_store(struct device *device, /* get the correct function depending on the device */ ret = SLAVE_SPECIFIC_FUNC(sl)->set_resolution(sl, val); - if (ret) { - dev_info(device, - "%s: writing error %d\n", __func__, ret); - /* return size to avoid call back again */ - } else - SLAVE_RESOLUTION(sl) = val; + if (ret) + return ret; + + SLAVE_RESOLUTION(sl) = val; + /* Reset the conversion time to default because it depends on resolution */ + SLAVE_CONV_TIME_OVERRIDE(sl) = CONV_TIME_DEFAULT; return size; } @@ -1660,6 +1875,96 @@ show_result: return sprintf(buf, "%d\n", ret); } +static ssize_t conv_time_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, + "%s: Device is not supported by the driver\n", __func__); + return 0; /* No device family */ + } + return sprintf(buf, "%d\n", conversion_time(sl)); +} + +static ssize_t conv_time_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size) +{ + int val, ret = 0; + struct w1_slave *sl = dev_to_w1_slave(device); + + if (kstrtoint(buf, 10, &val)) /* converting user entry to int */ + return -EINVAL; + + if (check_family_data(sl)) + return -ENODEV; + + if (val != CONV_TIME_MEASURE) { + if (val >= CONV_TIME_DEFAULT) + SLAVE_CONV_TIME_OVERRIDE(sl) = val; + else + return -EINVAL; + + } else { + int conv_time; + + ret = conv_time_measure(sl, &conv_time); + if (ret) + return -EIO; + SLAVE_CONV_TIME_OVERRIDE(sl) = conv_time; + } + return size; +} + +static ssize_t features_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct w1_slave *sl = dev_to_w1_slave(device); + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, + "%s: Device not supported by the driver\n", __func__); + return 0; /* No device family */ + } + return sprintf(buf, "%u\n", SLAVE_FEATURES(sl)); +} + +static ssize_t features_store(struct device *device, + struct device_attribute *attr, const char *buf, size_t size) +{ + int val, ret = 0; + bool strong_pullup; + struct w1_slave *sl = dev_to_w1_slave(device); + + ret = kstrtouint(buf, 10, &val); /* converting user entry to int */ + if (ret) + return -EINVAL; /* invalid number */ + + if ((!sl->family_data) || (!SLAVE_SPECIFIC_FUNC(sl))) { + dev_info(device, "%s: Device not supported by the driver\n", __func__); + return -ENODEV; + } + + if ((val & W1_THERM_FEATURES_MASK) != val) + return -EINVAL; + + SLAVE_FEATURES(sl) = val; + + strong_pullup = (w1_strong_pullup == 2 || + (!SLAVE_POWERMODE(sl) && + w1_strong_pullup)); + + if (strong_pullup && SLAVE_FEATURES(sl) & W1_THERM_POLL_COMPLETION) { + dev_warn(&sl->dev, + "%s: W1_THERM_POLL_COMPLETION disabled in parasite power mode.\n", + __func__); + SLAVE_FEATURES(sl) &= ~W1_THERM_POLL_COMPLETION; + } + + return size; +} + #if IS_REACHABLE(CONFIG_HWMON) static int w1_read_temp(struct device *device, u32 attr, int channel, long *val) |