| From 23769ad113407ceb21a4285a8194c7d788149269 Mon Sep 17 00:00:00 2001 |
| From: Akira Shimahara <akira215corp@gmail.com> |
| Date: Mon, 11 May 2020 22:37:42 +0200 |
| Subject: [PATCH] w1_therm: optimizing temperature read timings |
| |
| commit 67b392f7b8edfa6f427fecd98722acab34c1c99f upstream. |
| |
| Optimizing temperature reading by reducing waiting conversion time |
| according to device resolution settings, as per device specification. |
| This is device dependent as not all the devices supports resolution |
| setting, so it has been added in device family structures. |
| |
| The process to read the temperature on the device has been adapted in a |
| new function 'convert_t()', which replace the former 'read_therm()', is |
| introduce to deal with this timing. Strong pull up is also applied during |
| the required time, according to device power status needs and |
| 'strong_pullup' module parameter. |
| |
| 'temperature_from_RAM()' function is introduced to get the correct |
| temperature computation (device dependent) from device RAM data. |
| |
| An new sysfs entry has been added to ouptut only temperature. The old |
| entry w1_slave has been kept for compatibility, without changing its |
| output format. |
| |
| Updating Documentation/ABI/testing/sysfs-driver-w1_therm accordingly. |
| |
| Signed-off-by: Akira Shimahara <akira215corp@gmail.com> |
| Link: https://lore.kernel.org/r/20200511203742.411039-1-akira215corp@gmail.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| .../ABI/testing/sysfs-driver-w1_therm | 12 + |
| drivers/w1/slaves/w1_therm.c | 286 +++++++++++------- |
| 2 files changed, 197 insertions(+), 101 deletions(-) |
| |
| --- a/Documentation/ABI/testing/sysfs-driver-w1_therm |
| +++ b/Documentation/ABI/testing/sysfs-driver-w1_therm |
| @@ -41,6 +41,18 @@ Users: any user space application which |
| w1_term device |
| |
| |
| +What: /sys/bus/w1/devices/.../temperature |
| +Date: May 2020 |
| +Contact: Akira Shimahara <akira215corp@gmail.com> |
| +Description: |
| + (RO) return the temperature in 1/1000 degC. |
| + Note that the conversion duration depend on the resolution (if |
| + device support this feature). It takes 94ms in 9bits |
| + resolution, 750ms for 12bits. |
| +Users: any user space application which wants to communicate with |
| + w1_term device |
| + |
| + |
| What: /sys/bus/w1/devices/.../w1_slave |
| Date: May 2020 |
| Contact: Akira Shimahara <akira215corp@gmail.com> |
| --- a/drivers/w1/slaves/w1_therm.c |
| +++ b/drivers/w1/slaves/w1_therm.c |
| @@ -93,6 +93,7 @@ module_param_named(strong_pullup, w1_str |
| * @reserved: not used here |
| * @f: pointer to the device binding structure |
| * @convert: pointer to the device conversion function |
| + * @get_conversion_time: pointer to the device conversion time function |
| * @set_resolution: pointer to the device set_resolution function |
| * @get_resolution: pointer to the device get_resolution function |
| */ |
| @@ -101,6 +102,7 @@ struct w1_therm_family_converter { |
| u16 reserved; |
| struct w1_family *f; |
| int (*convert)(u8 rom[9]); |
| + int (*get_conversion_time)(struct w1_slave *sl); |
| int (*set_resolution)(struct w1_slave *sl, int val); |
| int (*get_resolution)(struct w1_slave *sl); |
| }; |
| @@ -154,6 +156,15 @@ struct therm_info { |
| static int reset_select_slave(struct w1_slave *sl); |
| |
| /** |
| + * convert_t() - Query the device for temperature conversion and read |
| + * @sl: pointer to the slave to read |
| + * @info: pointer to a structure to store the read results |
| + * |
| + * Return: 0 if success, -kernel error code otherwise |
| + */ |
| +static int convert_t(struct w1_slave *sl, struct therm_info *info); |
| + |
| +/** |
| * read_scratchpad() - read the data in device RAM |
| * @sl: pointer to the slave to read |
| * @info: pointer to a structure to store the read results |
| @@ -213,6 +224,9 @@ static ssize_t w1_slave_store(struct dev |
| static ssize_t w1_seq_show(struct device *device, |
| struct device_attribute *attr, char *buf); |
| |
| +static ssize_t temperature_show(struct device *device, |
| + struct device_attribute *attr, char *buf); |
| + |
| static ssize_t ext_power_show(struct device *device, |
| struct device_attribute *attr, char *buf); |
| |
| @@ -229,6 +243,7 @@ static ssize_t eeprom_store(struct devic |
| |
| static DEVICE_ATTR_RW(w1_slave); |
| static DEVICE_ATTR_RO(w1_seq); |
| +static DEVICE_ATTR_RO(temperature); |
| static DEVICE_ATTR_RO(ext_power); |
| static DEVICE_ATTR_RW(resolution); |
| static DEVICE_ATTR_WO(eeprom); |
| @@ -259,6 +274,7 @@ static void w1_therm_remove_slave(struct |
| |
| static struct attribute *w1_therm_attrs[] = { |
| &dev_attr_w1_slave.attr, |
| + &dev_attr_temperature.attr, |
| &dev_attr_ext_power.attr, |
| &dev_attr_resolution.attr, |
| &dev_attr_eeprom.attr, |
| @@ -267,6 +283,7 @@ static struct attribute *w1_therm_attrs[ |
| |
| static struct attribute *w1_ds18s20_attrs[] = { |
| &dev_attr_w1_slave.attr, |
| + &dev_attr_temperature.attr, |
| &dev_attr_ext_power.attr, |
| &dev_attr_eeprom.attr, |
| NULL, |
| @@ -275,6 +292,7 @@ static struct attribute *w1_ds18s20_attr |
| static struct attribute *w1_ds28ea00_attrs[] = { |
| &dev_attr_w1_slave.attr, |
| &dev_attr_w1_seq.attr, |
| + &dev_attr_temperature.attr, |
| &dev_attr_ext_power.attr, |
| &dev_attr_resolution.attr, |
| &dev_attr_eeprom.attr, |
| @@ -389,6 +407,37 @@ static struct w1_family w1_therm_family_ |
| |
| /* Device dependent func */ |
| |
| +static inline int w1_DS18B20_convert_time(struct w1_slave *sl) |
| +{ |
| + int ret; |
| + |
| + if (!sl->family_data) |
| + return -ENODEV; /* device unknown */ |
| + |
| + /* return time in ms for conversion operation */ |
| + switch (SLAVE_RESOLUTION(sl)) { |
| + case 9: |
| + ret = 95; |
| + break; |
| + case 10: |
| + ret = 190; |
| + break; |
| + case 11: |
| + ret = 375; |
| + break; |
| + case 12: |
| + default: |
| + ret = 750; |
| + } |
| + return ret; |
| +} |
| + |
| +static inline int w1_DS18S20_convert_time(struct w1_slave *sl) |
| +{ |
| + (void)(sl); |
| + return 750; /* always 750ms for DS18S20 */ |
| +} |
| + |
| static inline int w1_DS18B20_write_data(struct w1_slave *sl, |
| const u8 *data) |
| { |
| @@ -480,8 +529,10 @@ static inline int w1_DS18S20_convert_tem |
| { |
| int t, h; |
| |
| - if (!rom[7]) |
| + if (!rom[7]) { |
| + pr_debug("%s: Invalid argument for conversion\n", __func__); |
| return 0; |
| + } |
| |
| if (rom[1] == 0) |
| t = ((s32)rom[0] >> 1)*1000; |
| @@ -500,34 +551,39 @@ static inline int w1_DS18S20_convert_tem |
| |
| static struct w1_therm_family_converter w1_therm_families[] = { |
| { |
| - .f = &w1_therm_family_DS18S20, |
| - .convert = w1_DS18S20_convert_temp, |
| - .set_resolution = NULL, /* no config register */ |
| - .get_resolution = NULL, /* no config register */ |
| + .f = &w1_therm_family_DS18S20, |
| + .convert = w1_DS18S20_convert_temp, |
| + .get_conversion_time = w1_DS18S20_convert_time, |
| + .set_resolution = NULL, /* no config register */ |
| + .get_resolution = NULL, /* no config register */ |
| }, |
| { |
| - .f = &w1_therm_family_DS1822, |
| - .convert = w1_DS18B20_convert_temp, |
| - .set_resolution = w1_DS18B20_set_resolution, |
| - .get_resolution = w1_DS18B20_get_resolution, |
| + .f = &w1_therm_family_DS1822, |
| + .convert = w1_DS18B20_convert_temp, |
| + .get_conversion_time = w1_DS18B20_convert_time, |
| + .set_resolution = w1_DS18B20_set_resolution, |
| + .get_resolution = w1_DS18B20_get_resolution, |
| }, |
| { |
| - .f = &w1_therm_family_DS18B20, |
| - .convert = w1_DS18B20_convert_temp, |
| - .set_resolution = w1_DS18B20_set_resolution, |
| - .get_resolution = w1_DS18B20_get_resolution, |
| + .f = &w1_therm_family_DS18B20, |
| + .convert = w1_DS18B20_convert_temp, |
| + .get_conversion_time = w1_DS18B20_convert_time, |
| + .set_resolution = w1_DS18B20_set_resolution, |
| + .get_resolution = w1_DS18B20_get_resolution, |
| }, |
| { |
| - .f = &w1_therm_family_DS28EA00, |
| - .convert = w1_DS18B20_convert_temp, |
| - .set_resolution = w1_DS18B20_set_resolution, |
| - .get_resolution = w1_DS18B20_get_resolution, |
| + .f = &w1_therm_family_DS28EA00, |
| + .convert = w1_DS18B20_convert_temp, |
| + .get_conversion_time = w1_DS18B20_convert_time, |
| + .set_resolution = w1_DS18B20_set_resolution, |
| + .get_resolution = w1_DS18B20_get_resolution, |
| }, |
| { |
| - .f = &w1_therm_family_DS1825, |
| - .convert = w1_DS18B20_convert_temp, |
| - .set_resolution = w1_DS18B20_set_resolution, |
| - .get_resolution = w1_DS18B20_get_resolution, |
| + .f = &w1_therm_family_DS1825, |
| + .convert = w1_DS18B20_convert_temp, |
| + .get_conversion_time = w1_DS18B20_convert_time, |
| + .set_resolution = w1_DS18B20_set_resolution, |
| + .get_resolution = w1_DS18B20_get_resolution, |
| } |
| }; |
| |
| @@ -582,24 +638,44 @@ static inline bool bus_mutex_lock(struct |
| } |
| |
| /** |
| - * w1_convert_temp() - temperature conversion binding function |
| - * @rom: data read from device RAM (8 data bytes + 1 CRC byte) |
| - * @fid: device family id |
| + * conversion_time() - get the Tconv for the slave |
| + * @sl: device to get the conversion time |
| * |
| - * The function call the temperature computation function according to |
| - * device family. |
| + * On device supporting resolution settings, conversion time depend |
| + * on the resolution setting. This helper function get the slave timing, |
| + * depending on its current setting. |
| * |
| - * Return: value in millidegrees Celsius. |
| + * Return: conversion time in ms, negative values are kernel error code |
| */ |
| -static inline int w1_convert_temp(u8 rom[9], u8 fid) |
| +static inline int conversion_time(struct w1_slave *sl) |
| { |
| - int i; |
| + if (SLAVE_SPECIFIC_FUNC(sl)) |
| + return SLAVE_SPECIFIC_FUNC(sl)->get_conversion_time(sl); |
| |
| - for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) |
| - if (w1_therm_families[i].f->fid == fid) |
| - return w1_therm_families[i].convert(rom); |
| + dev_info(&sl->dev, |
| + "%s: Device not supported by the driver\n", __func__); |
| |
| - return 0; |
| + return -ENODEV; /* No device family */ |
| +} |
| + |
| +/** |
| + * temperature_from_RAM() - Convert the read info to temperature |
| + * @sl: device that sent the RAM data |
| + * @rom: read value on the slave device RAM |
| + * |
| + * Device dependent, the function bind the correct computation method. |
| + * |
| + * Return: temperature in 1/1000degC, 0 on error. |
| + */ |
| +static inline int temperature_from_RAM(struct w1_slave *sl, u8 rom[9]) |
| +{ |
| + if (SLAVE_SPECIFIC_FUNC(sl)) |
| + return SLAVE_SPECIFIC_FUNC(sl)->convert(rom); |
| + |
| + dev_info(&sl->dev, |
| + "%s: Device not supported by the driver\n", __func__); |
| + |
| + return 0; /* No device family */ |
| } |
| |
| /* Interface Functions */ |
| @@ -679,96 +755,74 @@ static int reset_select_slave(struct w1_ |
| return 0; |
| } |
| |
| -static ssize_t read_therm(struct device *device, |
| - struct w1_slave *sl, struct therm_info *info) |
| +static int convert_t(struct w1_slave *sl, struct therm_info *info) |
| { |
| - struct w1_master *dev = sl->master; |
| - u8 external_power; |
| - int ret, max_trying = 10; |
| - u8 *family_data = sl->family_data; |
| + struct w1_master *dev_master = sl->master; |
| + int max_trying = W1_THERM_MAX_TRY; |
| + int t_conv; |
| + int ret = -ENODEV; |
| + bool strong_pullup; |
| |
| - if (!family_data) { |
| - ret = -ENODEV; |
| + if (!sl->family_data) |
| goto error; |
| - } |
| |
| - /* prevent the slave from going away in sleep */ |
| - atomic_inc(THERM_REFCNT(family_data)); |
| + strong_pullup = (w1_strong_pullup == 2 || |
| + (!SLAVE_POWERMODE(sl) && |
| + w1_strong_pullup)); |
| |
| - ret = mutex_lock_interruptible(&dev->bus_mutex); |
| - if (ret != 0) |
| - goto dec_refcnt; |
| + /* get conversion duration device and id dependent */ |
| + t_conv = conversion_time(sl); |
| |
| memset(info->rom, 0, sizeof(info->rom)); |
| |
| - while (max_trying--) { |
| + /* 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 count = 0; |
| - unsigned int tm = 750; |
| unsigned long sleep_rem; |
| |
| - w1_write_8(dev, W1_READ_PSUPPLY); |
| - external_power = w1_read_8(dev); |
| - |
| - if (reset_select_slave(sl)) |
| - continue; |
| - |
| /* 750ms strong pullup (or delay) after the convert */ |
| - if (w1_strong_pullup == 2 || |
| - (!external_power && w1_strong_pullup)) |
| - w1_next_pullup(dev, tm); |
| - |
| - w1_write_8(dev, W1_CONVERT_TEMP); |
| + if (strong_pullup) |
| + w1_next_pullup(dev_master, t_conv); |
| |
| - if (external_power) { |
| - mutex_unlock(&dev->bus_mutex); |
| + w1_write_8(dev_master, W1_CONVERT_TEMP); |
| |
| - sleep_rem = msleep_interruptible(tm); |
| + if (strong_pullup) { /*some device need pullup */ |
| + sleep_rem = msleep_interruptible(t_conv); |
| if (sleep_rem != 0) { |
| ret = -EINTR; |
| - goto dec_refcnt; |
| + goto mt_unlock; |
| } |
| + mutex_unlock(&dev_master->bus_mutex); |
| + } else { /*no device need pullup */ |
| + mutex_unlock(&dev_master->bus_mutex); |
| |
| - ret = mutex_lock_interruptible(&dev->bus_mutex); |
| - if (ret != 0) |
| - goto dec_refcnt; |
| - } else if (!w1_strong_pullup) { |
| - sleep_rem = msleep_interruptible(tm); |
| + sleep_rem = msleep_interruptible(t_conv); |
| if (sleep_rem != 0) { |
| ret = -EINTR; |
| - goto mt_unlock; |
| + goto dec_refcnt; |
| } |
| } |
| - |
| - if (!reset_select_slave(sl)) { |
| - |
| - w1_write_8(dev, W1_READ_SCRATCHPAD); |
| - count = w1_read_block(dev, info->rom, 9); |
| - if (count != 9) { |
| - dev_warn(device, "w1_read_block() " |
| - "returned %u instead of 9.\n", |
| - count); |
| - } |
| - |
| - info->crc = w1_calc_crc8(info->rom, 8); |
| - |
| - if (info->rom[8] == info->crc) |
| - info->verdict = 1; |
| - } |
| + ret = read_scratchpad(sl, info); |
| + goto dec_refcnt; |
| } |
| |
| - if (info->verdict) |
| - break; |
| } |
| |
| mt_unlock: |
| - mutex_unlock(&dev->bus_mutex); |
| + mutex_unlock(&dev_master->bus_mutex); |
| dec_refcnt: |
| - atomic_dec(THERM_REFCNT(family_data)); |
| + atomic_dec(THERM_REFCNT(sl->family_data)); |
| error: |
| return ret; |
| } |
| @@ -1000,27 +1054,33 @@ static ssize_t w1_slave_show(struct devi |
| u8 *family_data = sl->family_data; |
| int ret, i; |
| ssize_t c = PAGE_SIZE; |
| - u8 fid = sl->family->fid; |
| |
| - ret = read_therm(device, sl, &info); |
| - if (ret) |
| - return ret; |
| + ret = convert_t(sl, &info); |
| + |
| + if (ret < 0) { |
| + dev_dbg(device, |
| + "%s: Temperature data may be corrupted. err=%d\n", |
| + __func__, ret); |
| + return 0; |
| + } |
| |
| for (i = 0; i < 9; ++i) |
| c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", info.rom[i]); |
| c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n", |
| info.crc, (info.verdict) ? "YES" : "NO"); |
| + |
| if (info.verdict) |
| memcpy(family_data, info.rom, sizeof(info.rom)); |
| else |
| - dev_warn(device, "Read failed CRC check\n"); |
| + dev_warn(device, "%s:Read failed CRC check\n", __func__); |
| |
| for (i = 0; i < 9; ++i) |
| c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", |
| ((u8 *)family_data)[i]); |
| |
| c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n", |
| - w1_convert_temp(info.rom, fid)); |
| + temperature_from_RAM(sl, info.rom)); |
| + |
| ret = PAGE_SIZE - c; |
| return ret; |
| } |
| @@ -1063,6 +1123,31 @@ static ssize_t w1_slave_store(struct dev |
| return size; /* always return size to avoid infinite calling */ |
| } |
| |
| +static ssize_t temperature_show(struct device *device, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + struct w1_slave *sl = dev_to_w1_slave(device); |
| + struct therm_info info; |
| + int ret = 0; |
| + |
| + 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 */ |
| + } |
| + |
| + ret = convert_t(sl, &info); |
| + |
| + if (ret < 0) { |
| + dev_dbg(device, |
| + "%s: Temperature data may be corrupted. err=%d\n", |
| + __func__, ret); |
| + return 0; |
| + } |
| + |
| + return sprintf(buf, "%d\n", temperature_from_RAM(sl, info.rom)); |
| +} |
| + |
| static ssize_t ext_power_show(struct device *device, |
| struct device_attribute *attr, char *buf) |
| { |
| @@ -1172,12 +1257,11 @@ static int w1_read_temp(struct device *d |
| { |
| struct w1_slave *sl = dev_get_drvdata(device); |
| struct therm_info info; |
| - u8 fid = sl->family->fid; |
| int ret; |
| |
| switch (attr) { |
| case hwmon_temp_input: |
| - ret = read_therm(device, sl, &info); |
| + ret = convert_t(sl, &info); |
| if (ret) |
| return ret; |
| |
| @@ -1186,7 +1270,7 @@ static int w1_read_temp(struct device *d |
| return ret; |
| } |
| |
| - *val = w1_convert_temp(info.rom, fid); |
| + *val = temperature_from_RAM(sl, info.rom); |
| ret = 0; |
| break; |
| default: |