| From 5737e27d3c5e2f743421e68f926c05e8581ae21f Mon Sep 17 00:00:00 2001 |
| From: Akira Shimahara <akira215corp@gmail.com> |
| Date: Mon, 11 May 2020 22:36:50 +0200 |
| Subject: [PATCH] w1_therm: adding ext_power sysfs entry |
| |
| commit b7bb6ca17a90f47c2fe2848531b5bbaf27a65ba7 upstream. |
| |
| Adding ext_power sysfs entry (RO). Return the power status of the device: |
| - 0: device parasite powered |
| - 1: device externally powered |
| - xx: xx is kernel error |
| |
| The power status of each device is check when the device is |
| discover by the bus master, in 'w1_therm_add_slave(struct w1_slave *)'. |
| The status is stored in the device structure w1_therm_family_data so |
| that the driver always knows the power state of each device, which could |
| be used later to determine the required strong pull up to apply on the |
| line. |
| |
| The power status is re evaluate each time the sysfs ext_power read by |
| a user. |
| |
| The hardware function 'read_powermode(struct w1_slave *sl)' act just as |
| per device specifications, sending W1_READ_PSUPPLY command on the bus, |
| and issue a read time slot, reading only one bit. |
| |
| A helper function 'bool bus_mutex_lock(struct mutex *lock)' is introduced. |
| It try to aquire the bus mutex several times (W1_THERM_MAX_TRY), waiting |
| W1_THERM_RETRY_DELAY between two attempt. |
| |
| Updating Documentation/ABI/testing/sysfs-driver-w1_therm accordingly. |
| |
| Signed-off-by: Akira Shimahara <akira215corp@gmail.com> |
| Link: https://lore.kernel.org/r/20200511203650.410439-1-akira215corp@gmail.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| .../ABI/testing/sysfs-driver-w1_therm | 29 ++++ |
| drivers/w1/slaves/w1_therm.c | 137 ++++++++++++++++++ |
| 2 files changed, 166 insertions(+) |
| create mode 100644 Documentation/ABI/testing/sysfs-driver-w1_therm |
| |
| --- /dev/null |
| +++ b/Documentation/ABI/testing/sysfs-driver-w1_therm |
| @@ -0,0 +1,29 @@ |
| +What: /sys/bus/w1/devices/.../ext_power |
| +Date: May 2020 |
| +Contact: Akira Shimahara <akira215corp@gmail.com> |
| +Description: |
| + (RO) return the power status by asking the device |
| + * '0': device parasite powered |
| + * '1': device externally powered |
| + * '-xx': xx is kernel error when reading power status |
| +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> |
| +Description: |
| + (RW) return the temperature in 1/1000 degC. |
| + *read*: return 2 lines with the hexa output data sent on the |
| + bus, return the CRC check and temperature in 1/1000 degC |
| + *write* : |
| + * '0' : save the 2 or 3 bytes to the device EEPROM |
| + (i.e. TH, TL and config register) |
| + * '9..12' : set the device resolution in RAM |
| + (if supported) |
| + * Anything else: do nothing |
| + 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 |
| --- a/drivers/w1/slaves/w1_therm.c |
| +++ b/drivers/w1/slaves/w1_therm.c |
| @@ -43,8 +43,21 @@ |
| static int w1_strong_pullup = 1; |
| module_param_named(strong_pullup, w1_strong_pullup, int, 0); |
| |
| +/* Nb of try for an operation */ |
| +#define W1_THERM_MAX_TRY 5 |
| + |
| +/* ms delay to retry bus mutex */ |
| +#define W1_THERM_RETRY_DELAY 20 |
| + |
| /* Helpers Macros */ |
| |
| +/* |
| + * return the power mode of the sl slave : 1-ext, 0-parasite, <0 unknown |
| + * always test family data existence before using this macro |
| + */ |
| +#define SLAVE_POWERMODE(sl) \ |
| + (((struct w1_therm_family_data *)(sl->family_data))->external_powered) |
| + |
| /* return the address of the refcnt in the family data */ |
| #define THERM_REFCNT(family_data) \ |
| (&((struct w1_therm_family_data *)family_data)->refcnt) |
| @@ -73,10 +86,14 @@ struct w1_therm_family_converter { |
| * struct w1_therm_family_data - device data |
| * @rom: ROM device id (64bit Lasered ROM code + 1 CRC byte) |
| * @refcnt: ref count |
| + * @external_powered: 1 device powered externally, |
| + * 0 device parasite powered, |
| + * -x error or undefined |
| */ |
| struct w1_therm_family_data { |
| uint8_t rom[9]; |
| atomic_t refcnt; |
| + int external_powered; |
| }; |
| |
| /** |
| @@ -109,6 +126,20 @@ struct therm_info { |
| */ |
| static int reset_select_slave(struct w1_slave *sl); |
| |
| +/** |
| + * read_powermode() - Query the power mode of the slave |
| + * @sl: slave to retrieve the power mode |
| + * |
| + * Ask the device to get its power mode (external or parasite) |
| + * and store the power status in the &struct w1_therm_family_data. |
| + * |
| + * Return: |
| + * * 0 parasite powered device |
| + * * 1 externally powered device |
| + * * <0 kernel error code |
| + */ |
| +static int read_powermode(struct w1_slave *sl); |
| + |
| /* Sysfs interface declaration */ |
| |
| static ssize_t w1_slave_show(struct device *device, |
| @@ -120,10 +151,14 @@ 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 ext_power_show(struct device *device, |
| + struct device_attribute *attr, char *buf); |
| + |
| /* Attributes declarations */ |
| |
| static DEVICE_ATTR_RW(w1_slave); |
| static DEVICE_ATTR_RO(w1_seq); |
| +static DEVICE_ATTR_RO(ext_power); |
| |
| /* Interface Functions declaration */ |
| |
| @@ -151,12 +186,14 @@ static void w1_therm_remove_slave(struct |
| |
| static struct attribute *w1_therm_attrs[] = { |
| &dev_attr_w1_slave.attr, |
| + &dev_attr_ext_power.attr, |
| NULL, |
| }; |
| |
| static struct attribute *w1_ds28ea00_attrs[] = { |
| &dev_attr_w1_slave.attr, |
| &dev_attr_w1_seq.attr, |
| + &dev_attr_ext_power.attr, |
| NULL, |
| }; |
| |
| @@ -433,6 +470,34 @@ static struct w1_therm_family_converter |
| /* Helpers Functions */ |
| |
| /** |
| + * bus_mutex_lock() - Acquire the mutex |
| + * @lock: w1 bus mutex to acquire |
| + * |
| + * It try to acquire the mutex W1_THERM_MAX_TRY times and wait |
| + * W1_THERM_RETRY_DELAY between 2 attempts. |
| + * |
| + * Return: true is mutex is acquired and lock, false otherwise |
| + */ |
| +static inline bool bus_mutex_lock(struct mutex *lock) |
| +{ |
| + int max_trying = W1_THERM_MAX_TRY; |
| + |
| + /* try to acquire the mutex, if not, sleep retry_delay before retry) */ |
| + while (mutex_lock_interruptible(lock) != 0 && max_trying > 0) { |
| + unsigned long sleep_rem; |
| + |
| + sleep_rem = msleep_interruptible(W1_THERM_RETRY_DELAY); |
| + if (!sleep_rem) |
| + max_trying--; |
| + } |
| + |
| + if (!max_trying) |
| + return false; /* Didn't acquire the bus mutex */ |
| + |
| + return true; |
| +} |
| + |
| +/** |
| * w1_convert_temp() - temperature conversion binding function |
| * @rom: data read from device RAM (8 data bytes + 1 CRC byte) |
| * @fid: device family id |
| @@ -461,7 +526,19 @@ static int w1_therm_add_slave(struct w1_ |
| GFP_KERNEL); |
| if (!sl->family_data) |
| return -ENOMEM; |
| + |
| atomic_set(THERM_REFCNT(sl->family_data), 1); |
| + |
| + /* Getting the power mode of the device {external, parasite} */ |
| + SLAVE_POWERMODE(sl) = read_powermode(sl); |
| + |
| + if (SLAVE_POWERMODE(sl) < 0) { |
| + /* no error returned as device has been added */ |
| + dev_warn(&sl->dev, |
| + "%s: Device has been added, but power_mode may be corrupted. err=%d\n", |
| + __func__, SLAVE_POWERMODE(sl)); |
| + } |
| + |
| return 0; |
| } |
| |
| @@ -661,6 +738,44 @@ error: |
| return ret; |
| } |
| |
| +static int read_powermode(struct w1_slave *sl) |
| +{ |
| + struct w1_master *dev_master = sl->master; |
| + int max_trying = W1_THERM_MAX_TRY; |
| + int ret = -ENODEV; |
| + |
| + if (!sl->family_data) |
| + goto error; |
| + |
| + /* 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 < 0)) { |
| + /* safe version to select slave */ |
| + if (!reset_select_slave(sl)) { |
| + w1_write_8(dev_master, W1_READ_PSUPPLY); |
| + /* |
| + * Emit a read time slot and read only one bit, |
| + * 1 is externally powered, |
| + * 0 is parasite powered |
| + */ |
| + ret = w1_touch_bit(dev_master, 1); |
| + /* ret should be either 1 either 0 */ |
| + } |
| + } |
| + mutex_unlock(&dev_master->bus_mutex); |
| + |
| +dec_refcnt: |
| + atomic_dec(THERM_REFCNT(sl->family_data)); |
| +error: |
| + return ret; |
| +} |
| + |
| /* Sysfs Interface definition */ |
| |
| static ssize_t w1_slave_show(struct device *device, |
| @@ -722,6 +837,28 @@ static ssize_t w1_slave_store(struct dev |
| return ret ? : size; |
| } |
| |
| +static ssize_t ext_power_show(struct device *device, |
| + struct device_attribute *attr, char *buf) |
| +{ |
| + struct w1_slave *sl = dev_to_w1_slave(device); |
| + |
| + if (!sl->family_data) { |
| + dev_info(device, |
| + "%s: Device not supported by the driver\n", __func__); |
| + return 0; /* No device family */ |
| + } |
| + |
| + /* Getting the power mode of the device {external, parasite} */ |
| + SLAVE_POWERMODE(sl) = read_powermode(sl); |
| + |
| + if (SLAVE_POWERMODE(sl) < 0) { |
| + dev_dbg(device, |
| + "%s: Power_mode may be corrupted. err=%d\n", |
| + __func__, SLAVE_POWERMODE(sl)); |
| + } |
| + return sprintf(buf, "%d\n", SLAVE_POWERMODE(sl)); |
| +} |
| + |
| #if IS_REACHABLE(CONFIG_HWMON) |
| static int w1_read_temp(struct device *device, u32 attr, int channel, |
| long *val) |