| From e6585a1d299375740f95f30027b8b3f4b34e7548 Mon Sep 17 00:00:00 2001 |
| From: Akira Shimahara <akira215corp@gmail.com> |
| Date: Mon, 11 May 2020 22:38:20 +0200 |
| Subject: [PATCH] w1_therm: adding bulk read support to trigger |
| multiple conversion on bus |
| |
| commit 57c76221d5af648c8355a55c09b050c5d8d38189 upstream. |
| |
| Adding bulk read support: |
| Sending a 'trigger' command in the dedicated sysfs entry of bus master |
| device send a conversion command for all the slaves on the bus. The sysfs |
| entry is added as soon as at least one device supporting this feature |
| is detected on the bus. |
| |
| The behavior of the sysfs reading temperature on the device is as follow: |
| * If no bulk read pending, trigger a conversion on the device, wait for |
| the conversion to be done, read the temperature in device RAM |
| * If a bulk read has been trigger, access directly the device RAM |
| This behavior is the same on the 2 sysfs entries ('temperature' and |
| 'w1_slave'). |
| |
| Reading the therm_bulk_read sysfs give the status of bulk operations: |
| * '-1': conversion in progress on at least 1 sensor |
| * '1': conversion complete but at least one sensor has not been read yet |
| * '0': no bulk operation. Reading temperature on ecah device will trigger |
| a conversion |
| |
| As not all devices support bulk read feature, it has been added in device |
| family structure. |
| |
| The attribute is set at master level as soon as a supporting device is |
| discover. It is removed when the last supported device leave the bus. |
| The count of supported device is kept with the static counter |
| bulk_read_device_counter. |
| |
| A strong pull up is apply on the line if at least one device required it. |
| The duration of the pull up is the max time required by a device on the |
| line, which depends on the resolution settings of each device. The strong |
| pull up could be adjust with the a module parameter. |
| |
| Updating documentation in Documentation/ABI/testing/sysfs-driver-w1_therm |
| and Documentation/w1/slaves/w1_therm.rst accordingly. |
| |
| Signed-off-by: Akira Shimahara <akira215corp@gmail.com> |
| Link: https://lore.kernel.org/r/20200511203820.411483-1-akira215corp@gmail.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| .../ABI/testing/sysfs-driver-w1_therm | 36 ++- |
| Documentation/w1/slaves/w1_therm.rst | 50 +++- |
| drivers/w1/slaves/w1_therm.c | 251 +++++++++++++++++- |
| 3 files changed, 322 insertions(+), 15 deletions(-) |
| |
| --- a/Documentation/ABI/testing/sysfs-driver-w1_therm |
| +++ b/Documentation/ABI/testing/sysfs-driver-w1_therm |
| @@ -62,9 +62,16 @@ 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. |
| + * If a bulk read has been triggered, it will directly |
| + return the temperature computed when the bulk read |
| + occurred, if available. If not yet available, nothing |
| + is returned (a debug kernel message is sent), you |
| + should retry later on. |
| + * If no bulk read has been triggered, it will trigger |
| + a conversion and send the result. 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 |
| |
| @@ -85,4 +92,25 @@ Description: |
| refer to Documentation/w1/slaves/w1_therm.rst for detailed |
| information. |
| Users: any user space application which wants to communicate with |
| - w1_term device |
| \ No newline at end of file |
| + w1_term device |
| + |
| + |
| +What: /sys/bus/w1/devices/w1_bus_masterXX/therm_bulk_read |
| +Date: May 2020 |
| +Contact: Akira Shimahara <akira215corp@gmail.com> |
| +Description: |
| + (RW) trigger a bulk read conversion. read the status |
| + *read*: |
| + * '-1': conversion in progress on at least 1 sensor |
| + * '1' : conversion complete but at least one sensor |
| + value has not been read yet |
| + * '0' : no bulk operation. Reading temperature will |
| + trigger a conversion on each device |
| + *write*: 'trigger': trigger a bulk read on all supporting |
| + devices on the bus |
| + Note that if a bulk read is sent but one sensor is not read |
| + immediately, the next access to temperature on this device |
| + will return the temperature measured at the time of issue |
| + of the bulk read command (not the current temperature). |
| +Users: any user space application which wants to communicate with |
| + w1_term device |
| --- a/Documentation/w1/slaves/w1_therm.rst |
| +++ b/Documentation/w1/slaves/w1_therm.rst |
| @@ -26,20 +26,31 @@ W1_THERM_DS1825 0x3B |
| W1_THERM_DS28EA00 0x42 |
| ==================== ==== |
| |
| -Support is provided through the sysfs w1_slave file. Each open and |
| +Support is provided through the sysfs w1_slave file. Each open and |
| read sequence will initiate a temperature conversion then provide two |
| -lines of ASCII output. The first line contains the nine hex bytes |
| +lines of ASCII output. The first line contains the nine hex bytes |
| read along with a calculated crc value and YES or NO if it matched. |
| -If the crc matched the returned values are retained. The second line |
| +If the crc matched the returned values are retained. The second line |
| displays the retained values along with a temperature in millidegrees |
| Centigrade after t=. |
| |
| -Parasite powered devices are limited to one slave performing a |
| -temperature conversion at a time. If none of the devices are parasite |
| -powered it would be possible to convert all the devices at the same |
| -time and then go back to read individual sensors. That isn't |
| -currently supported. The driver also doesn't support reduced |
| -precision (which would also reduce the conversion time) when reading values. |
| +Alternatively, temperature can be read using temperature sysfs, it |
| +return only temperature in millidegrees Centigrade. |
| + |
| +A bulk read of all devices on the bus could be done writing 'trigger' |
| +in the therm_bulk_read sysfs entry at w1_bus_master level. This will |
| +sent the convert command on all devices on the bus, and if parasite |
| +powered devices are detected on the bus (and strong pullup is enable |
| +in the module), it will drive the line high during the longer conversion |
| +time required by parasited powered device on the line. Reading |
| +therm_bulk_read will return 0 if no bulk conversion pending, |
| +-1 if at least one sensor still in conversion, 1 if conversion is complete |
| +but at least one sensor value has not been read yet. Result temperature is |
| +then accessed by reading the temperature sysfs entry of each device, which |
| +may return empty if conversion is still in progress. Note that if a bulk |
| +read is sent but one sensor is not read immediately, the next access to |
| +temperature on this device will return the temperature measured at the |
| +time of issue of the bulk read command (not the current temperature). |
| |
| Writing a value between 9 and 12 to the sysfs w1_slave file will change the |
| precision of the sensor for the next readings. This value is in (volatile) |
| @@ -49,6 +60,27 @@ To store the current precision configura |
| has to be written to the sysfs w1_slave file. Since the EEPROM has a limited |
| amount of writes (>50k), this command should be used wisely. |
| |
| +Alternatively, resolution can be set or read (value from 9 to 12) using the |
| +dedicated resolution sysfs entry on each device. This sysfs entry is not |
| +present for devices not supporting this feature. Driver will adjust the |
| +correct conversion time for each device regarding to its resolution setting. |
| +In particular, strong pullup will be applied if required during the conversion |
| +duration. |
| + |
| +The write-only sysfs entry eeprom is an alternative for EEPROM operations: |
| + * 'save': will save device RAM to EEPROM |
| + * 'restore': will restore EEPROM data in device RAM. |
| + |
| +ext_power syfs entry allow tho check the power status of each device. |
| + * '0': device parasite powered |
| + * '1': device externally powered |
| + |
| +sysfs alarms allow read or write TH and TL (Temperature High an Low) alarms. |
| +Values shall be space separated and in the device range (typical -55 degC |
| +to 125 degC). Values are integer as they are store in a 8bit register in |
| +the device. Lowest value is automatically put to TL.Once set, alarms could |
| +be search at master level. |
| + |
| The module parameter strong_pullup can be set to 0 to disable the |
| strong pullup, 1 to enable autodetection or 2 to force strong pullup. |
| In case of autodetection, the driver will use the "READ POWER SUPPLY" |
| --- a/drivers/w1/slaves/w1_therm.c |
| +++ b/drivers/w1/slaves/w1_therm.c |
| @@ -43,6 +43,9 @@ |
| static int w1_strong_pullup = 1; |
| module_param_named(strong_pullup, w1_strong_pullup, int, 0); |
| |
| +/* Counter for devices supporting bulk reading */ |
| +static u16 bulk_read_device_counter; /* =0 as per C standard */ |
| + |
| /* This command should be in public header w1.h but is not */ |
| #define W1_RECALL_EEPROM 0xB8 |
| |
| @@ -57,6 +60,7 @@ module_param_named(strong_pullup, w1_str |
| |
| #define EEPROM_CMD_WRITE "save" /* cmd for write eeprom sysfs */ |
| #define EEPROM_CMD_READ "restore" /* cmd for read eeprom sysfs */ |
| +#define BULK_TRIGGER_CMD "trigger" /* cmd to trigger a bulk read */ |
| |
| #define MIN_TEMP -55 /* min temperature that can be mesured */ |
| #define MAX_TEMP 125 /* max temperature that can be mesured */ |
| @@ -84,6 +88,15 @@ module_param_named(strong_pullup, w1_str |
| #define SLAVE_RESOLUTION(sl) \ |
| (((struct w1_therm_family_data *)(sl->family_data))->resolution) |
| |
| +/* |
| + * return whether or not a converT command has been issued to the slave |
| + * * 0: no bulk read is pending |
| + * * -1: conversion is in progress |
| + * * 1: conversion done, result to be read |
| + */ |
| +#define SLAVE_CONVERT_TRIGGERED(sl) \ |
| + (((struct w1_therm_family_data *)(sl->family_data))->convert_triggered) |
| + |
| /* return the address of the refcnt in the family data */ |
| #define THERM_REFCNT(family_data) \ |
| (&((struct w1_therm_family_data *)family_data)->refcnt) |
| @@ -100,6 +113,7 @@ module_param_named(strong_pullup, w1_str |
| * @set_resolution: pointer to the device set_resolution function |
| * @get_resolution: pointer to the device get_resolution function |
| * @write_data: pointer to the device writing function (2 or 3 bytes) |
| + * @bulk_read: true if device family support bulk read, false otherwise |
| */ |
| struct w1_therm_family_converter { |
| u8 broken; |
| @@ -110,6 +124,7 @@ struct w1_therm_family_converter { |
| int (*set_resolution)(struct w1_slave *sl, int val); |
| int (*get_resolution)(struct w1_slave *sl); |
| int (*write_data)(struct w1_slave *sl, const u8 *data); |
| + bool bulk_read; |
| }; |
| |
| /** |
| @@ -120,6 +135,7 @@ struct w1_therm_family_converter { |
| * 0 device parasite powered, |
| * -x error or undefined |
| * @resolution: current device resolution |
| + * @convert_triggered: conversion state of the device |
| * @specific_functions: pointer to struct of device specific function |
| */ |
| struct w1_therm_family_data { |
| @@ -127,6 +143,7 @@ struct w1_therm_family_data { |
| atomic_t refcnt; |
| int external_powered; |
| int resolution; |
| + int convert_triggered; |
| struct w1_therm_family_converter *specific_functions; |
| }; |
| |
| @@ -218,6 +235,18 @@ static int recall_eeprom(struct w1_slave |
| */ |
| static int read_powermode(struct w1_slave *sl); |
| |
| +/** |
| + * trigger_bulk_read() - function to trigger a bulk read on the bus |
| + * @dev_master: the device master of the bus |
| + * |
| + * Send a SKIP ROM follow by a CONVERT T commmand on the bus. |
| + * It also set the status flag in each slave &struct w1_therm_family_data |
| + * to signal that a conversion is in progress. |
| + * |
| + * Return: 0 if success, -kernel error code otherwise |
| + */ |
| +static int trigger_bulk_read(struct w1_master *dev_master); |
| + |
| /* Sysfs interface declaration */ |
| |
| static ssize_t w1_slave_show(struct device *device, |
| @@ -250,6 +279,12 @@ static ssize_t alarms_store(struct devic |
| static ssize_t alarms_show(struct device *device, |
| struct device_attribute *attr, char *buf); |
| |
| +static ssize_t therm_bulk_read_store(struct device *device, |
| + struct device_attribute *attr, const char *buf, size_t size); |
| + |
| +static ssize_t therm_bulk_read_show(struct device *device, |
| + struct device_attribute *attr, char *buf); |
| + |
| /* Attributes declarations */ |
| |
| static DEVICE_ATTR_RW(w1_slave); |
| @@ -260,6 +295,8 @@ static DEVICE_ATTR_RW(resolution); |
| static DEVICE_ATTR_WO(eeprom); |
| static DEVICE_ATTR_RW(alarms); |
| |
| +static DEVICE_ATTR_RW(therm_bulk_read); /* attribut at master level */ |
| + |
| /* Interface Functions declaration */ |
| |
| /** |
| @@ -572,6 +609,7 @@ static struct w1_therm_family_converter |
| .set_resolution = NULL, /* no config register */ |
| .get_resolution = NULL, /* no config register */ |
| .write_data = w1_DS18S20_write_data, |
| + .bulk_read = true |
| }, |
| { |
| .f = &w1_therm_family_DS1822, |
| @@ -580,6 +618,7 @@ static struct w1_therm_family_converter |
| .set_resolution = w1_DS18B20_set_resolution, |
| .get_resolution = w1_DS18B20_get_resolution, |
| .write_data = w1_DS18B20_write_data, |
| + .bulk_read = true |
| }, |
| { |
| .f = &w1_therm_family_DS18B20, |
| @@ -588,6 +627,7 @@ static struct w1_therm_family_converter |
| .set_resolution = w1_DS18B20_set_resolution, |
| .get_resolution = w1_DS18B20_get_resolution, |
| .write_data = w1_DS18B20_write_data, |
| + .bulk_read = true |
| }, |
| { |
| .f = &w1_therm_family_DS28EA00, |
| @@ -596,6 +636,7 @@ static struct w1_therm_family_converter |
| .set_resolution = w1_DS18B20_set_resolution, |
| .get_resolution = w1_DS18B20_get_resolution, |
| .write_data = w1_DS18B20_write_data, |
| + .bulk_read = false |
| }, |
| { |
| .f = &w1_therm_family_DS1825, |
| @@ -604,6 +645,7 @@ static struct w1_therm_family_converter |
| .set_resolution = w1_DS18B20_set_resolution, |
| .get_resolution = w1_DS18B20_get_resolution, |
| .write_data = w1_DS18B20_write_data, |
| + .bulk_read = true |
| } |
| }; |
| |
| @@ -658,6 +700,23 @@ static inline bool bus_mutex_lock(struct |
| } |
| |
| /** |
| + * support_bulk_read() - check if slave support bulk read |
| + * @sl: device to check the ability |
| + * |
| + * Return: true if bulk read is supported, false if not or error |
| + */ |
| +static inline bool bulk_read_support(struct w1_slave *sl) |
| +{ |
| + if (SLAVE_SPECIFIC_FUNC(sl)) |
| + return SLAVE_SPECIFIC_FUNC(sl)->bulk_read; |
| + |
| + dev_info(&sl->dev, |
| + "%s: Device not supported by the driver\n", __func__); |
| + |
| + return false; /* No device family */ |
| +} |
| + |
| +/** |
| * conversion_time() - get the Tconv for the slave |
| * @sl: device to get the conversion time |
| * |
| @@ -741,6 +800,24 @@ static int w1_therm_add_slave(struct w1_ |
| /* save this pointer to the device structure */ |
| SLAVE_SPECIFIC_FUNC(sl) = sl_family_conv; |
| |
| + if (bulk_read_support(sl)) { |
| + /* |
| + * add the sys entry to trigger bulk_read |
| + * at master level only the 1st time |
| + */ |
| + if (!bulk_read_device_counter) { |
| + int err = device_create_file(&sl->master->dev, |
| + &dev_attr_therm_bulk_read); |
| + |
| + if (err) |
| + dev_warn(&sl->dev, |
| + "%s: Device has been added, but bulk read is unavailable. err=%d\n", |
| + __func__, err); |
| + } |
| + /* Increment the counter */ |
| + bulk_read_device_counter++; |
| + } |
| + |
| /* Getting the power mode of the device {external, parasite} */ |
| SLAVE_POWERMODE(sl) = read_powermode(sl); |
| |
| @@ -763,6 +840,9 @@ static int w1_therm_add_slave(struct w1_ |
| } |
| } |
| |
| + /* Finally initialize convert_triggered flag */ |
| + SLAVE_CONVERT_TRIGGERED(sl) = 0; |
| + |
| return 0; |
| } |
| |
| @@ -770,6 +850,14 @@ static void w1_therm_remove_slave(struct |
| { |
| int refcnt = atomic_sub_return(1, THERM_REFCNT(sl->family_data)); |
| |
| + if (bulk_read_support(sl)) { |
| + bulk_read_device_counter--; |
| + /* Delete the entry if no more device support the feature */ |
| + if (!bulk_read_device_counter) |
| + device_remove_file(&sl->master->dev, |
| + &dev_attr_therm_bulk_read); |
| + } |
| + |
| while (refcnt) { |
| msleep(1000); |
| refcnt = atomic_read(THERM_REFCNT(sl->family_data)); |
| @@ -1084,6 +1172,96 @@ error: |
| return ret; |
| } |
| |
| +static int trigger_bulk_read(struct w1_master *dev_master) |
| +{ |
| + struct w1_slave *sl = NULL; /* used to iterate through slaves */ |
| + int max_trying = W1_THERM_MAX_TRY; |
| + int t_conv = 0; |
| + int ret = -ENODEV; |
| + bool strong_pullup = false; |
| + |
| + /* |
| + * Check whether there are parasite powered device on the bus, |
| + * and compute duration of conversion for these devices |
| + * so we can apply a strong pullup if required |
| + */ |
| + list_for_each_entry(sl, &dev_master->slist, w1_slave_entry) { |
| + if (!sl->family_data) |
| + goto error; |
| + if (bulk_read_support(sl)) { |
| + int t_cur = conversion_time(sl); |
| + |
| + t_conv = t_cur > t_conv ? t_cur : t_conv; |
| + strong_pullup = strong_pullup || |
| + (w1_strong_pullup == 2 || |
| + (!SLAVE_POWERMODE(sl) && |
| + w1_strong_pullup)); |
| + } |
| + } |
| + |
| + /* |
| + * t_conv is the max conversion time required on the bus |
| + * If its 0, no device support the bulk read feature |
| + */ |
| + if (!t_conv) |
| + goto error; |
| + |
| + if (!bus_mutex_lock(&dev_master->bus_mutex)) { |
| + ret = -EAGAIN; /* Didn't acquire the mutex */ |
| + goto error; |
| + } |
| + |
| + while ((max_trying--) && (ret < 0)) { /* ret should be either 0 */ |
| + |
| + if (!w1_reset_bus(dev_master)) { /* Just reset the bus */ |
| + unsigned long sleep_rem; |
| + |
| + w1_write_8(dev_master, W1_SKIP_ROM); |
| + |
| + if (strong_pullup) /* Apply pullup if required */ |
| + w1_next_pullup(dev_master, t_conv); |
| + |
| + w1_write_8(dev_master, W1_CONVERT_TEMP); |
| + |
| + /* set a flag to instruct that converT pending */ |
| + list_for_each_entry(sl, |
| + &dev_master->slist, w1_slave_entry) { |
| + if (bulk_read_support(sl)) |
| + SLAVE_CONVERT_TRIGGERED(sl) = -1; |
| + } |
| + |
| + if (strong_pullup) { /* some device need pullup */ |
| + sleep_rem = msleep_interruptible(t_conv); |
| + if (sleep_rem != 0) { |
| + ret = -EINTR; |
| + goto mt_unlock; |
| + } |
| + mutex_unlock(&dev_master->bus_mutex); |
| + } else { |
| + mutex_unlock(&dev_master->bus_mutex); |
| + sleep_rem = msleep_interruptible(t_conv); |
| + if (sleep_rem != 0) { |
| + ret = -EINTR; |
| + goto set_flag; |
| + } |
| + } |
| + ret = 0; |
| + goto set_flag; |
| + } |
| + } |
| + |
| +mt_unlock: |
| + mutex_unlock(&dev_master->bus_mutex); |
| +set_flag: |
| + /* set a flag to register convsersion is done */ |
| + list_for_each_entry(sl, &dev_master->slist, w1_slave_entry) { |
| + if (bulk_read_support(sl)) |
| + SLAVE_CONVERT_TRIGGERED(sl) = 1; |
| + } |
| +error: |
| + return ret; |
| +} |
| + |
| /* Sysfs Interface definition */ |
| |
| static ssize_t w1_slave_show(struct device *device, |
| @@ -1095,7 +1273,20 @@ static ssize_t w1_slave_show(struct devi |
| int ret, i; |
| ssize_t c = PAGE_SIZE; |
| |
| - ret = convert_t(sl, &info); |
| + if (bulk_read_support(sl)) { |
| + if (SLAVE_CONVERT_TRIGGERED(sl) < 0) { |
| + dev_dbg(device, |
| + "%s: Conversion in progress, retry later\n", |
| + __func__); |
| + return 0; |
| + } else if (SLAVE_CONVERT_TRIGGERED(sl) > 0) { |
| + /* A bulk read has been issued, read the device RAM */ |
| + ret = read_scratchpad(sl, &info); |
| + SLAVE_CONVERT_TRIGGERED(sl) = 0; |
| + } else |
| + ret = convert_t(sl, &info); |
| + } else |
| + ret = convert_t(sl, &info); |
| |
| if (ret < 0) { |
| dev_dbg(device, |
| @@ -1176,7 +1367,20 @@ static ssize_t temperature_show(struct d |
| return 0; /* No device family */ |
| } |
| |
| - ret = convert_t(sl, &info); |
| + if (bulk_read_support(sl)) { |
| + if (SLAVE_CONVERT_TRIGGERED(sl) < 0) { |
| + dev_dbg(device, |
| + "%s: Conversion in progress, retry later\n", |
| + __func__); |
| + return 0; |
| + } else if (SLAVE_CONVERT_TRIGGERED(sl) > 0) { |
| + /* A bulk read has been issued, read the device RAM */ |
| + ret = read_scratchpad(sl, &info); |
| + SLAVE_CONVERT_TRIGGERED(sl) = 0; |
| + } else |
| + ret = convert_t(sl, &info); |
| + } else |
| + ret = convert_t(sl, &info); |
| |
| if (ret < 0) { |
| dev_dbg(device, |
| @@ -1412,6 +1616,49 @@ free_m: |
| return size; |
| } |
| |
| +static ssize_t therm_bulk_read_store(struct device *device, |
| + struct device_attribute *attr, const char *buf, size_t size) |
| +{ |
| + struct w1_master *dev_master = dev_to_w1_master(device); |
| + int ret = -EINVAL; /* Invalid argument */ |
| + |
| + if (size == sizeof(BULK_TRIGGER_CMD)) |
| + if (!strncmp(buf, BULK_TRIGGER_CMD, |
| + sizeof(BULK_TRIGGER_CMD)-1)) |
| + ret = trigger_bulk_read(dev_master); |
| + |
| + if (ret) |
| + dev_info(device, |
| + "%s: unable to trigger a bulk read on the bus. err=%d\n", |
| + __func__, ret); |
| + |
| + return size; |
| +} |
| + |
| +static ssize_t therm_bulk_read_show(struct device *device, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + struct w1_master *dev_master = dev_to_w1_master(device); |
| + struct w1_slave *sl = NULL; |
| + int ret = 0; |
| + |
| + list_for_each_entry(sl, &dev_master->slist, w1_slave_entry) { |
| + if (sl->family_data) { |
| + if (bulk_read_support(sl)) { |
| + if (SLAVE_CONVERT_TRIGGERED(sl) == -1) { |
| + ret = -1; |
| + goto show_result; |
| + } |
| + if (SLAVE_CONVERT_TRIGGERED(sl) == 1) |
| + /* continue to check other slaves */ |
| + ret = 1; |
| + } |
| + } |
| + } |
| +show_result: |
| + return sprintf(buf, "%d\n", ret); |
| +} |
| + |
| #if IS_REACHABLE(CONFIG_HWMON) |
| static int w1_read_temp(struct device *device, u32 attr, int channel, |
| long *val) |