w1: add ability to set (SRAM) and store (EEPROM) configuration for temp sensors like...
authorBen Sen <0.x29a.0@gmail.com>
Sun, 1 May 2016 21:23:33 +0000 (23:23 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 1 May 2016 21:37:49 +0000 (14:37 -0700)
Since many temperature sensors come "preconfigured" with a lower
precision, people are stuck at that precision when running on a kernel
based device (unlike the Dallas 1Wire library for e.g. Arduino, which
supports writing the configuration/scratchpad). This patch adds write
support for the scratchpad/precision registers via w1_slave sysfs.

Signed-off-by: Ben Sen <0.x29a.0@gmail.com>
Acked-by: Evgeniy Polyakov <zbr@ioremap.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/w1/slaves/w1_therm
drivers/w1/slaves/w1_therm.c
drivers/w1/w1.h

index 13411fe52f7ff49b2ab5db2e9fca18ffaf98aefc..d1f93af36f385f8e308e6062362eb36ba97b6312 100644 (file)
@@ -33,7 +33,15 @@ 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).
+precision (which would also reduce the conversion time) when reading values.
+
+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)
+SRAM, so it is reset when the sensor gets power-cycled.
+
+To store the current precision configuration into EEPROM, the value 0
+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.
 
 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.
index 2f029e8f4f95d7fbc15a2af11f2b8e28abce7e34..581a300fd6cda40265c5bdde527ced6157c56e06 100644 (file)
@@ -92,10 +92,13 @@ static void w1_therm_remove_slave(struct w1_slave *sl)
 static ssize_t w1_slave_show(struct device *device,
        struct device_attribute *attr, char *buf);
 
+static ssize_t w1_slave_store(struct device *device,
+       struct device_attribute *attr, const char *buf, size_t size);
+
 static ssize_t w1_seq_show(struct device *device,
        struct device_attribute *attr, char *buf);
 
-static DEVICE_ATTR_RO(w1_slave);
+static DEVICE_ATTR_RW(w1_slave);
 static DEVICE_ATTR_RO(w1_seq);
 
 static struct attribute *w1_therm_attrs[] = {
@@ -154,8 +157,17 @@ struct w1_therm_family_converter
        u16                     reserved;
        struct w1_family        *f;
        int                     (*convert)(u8 rom[9]);
+       int                     (*precision)(struct device *device, int val);
+       int                     (*eeprom)(struct device *device);
 };
 
+/* write configuration to eeprom */
+static inline int w1_therm_eeprom(struct device *device);
+
+/* Set precision for conversion */
+static inline int w1_DS18B20_precision(struct device *device, int val);
+static inline int w1_DS18S20_precision(struct device *device, int val);
+
 /* The return value is millidegrees Centigrade. */
 static inline int w1_DS18B20_convert_temp(u8 rom[9]);
 static inline int w1_DS18S20_convert_temp(u8 rom[9]);
@@ -163,26 +175,194 @@ static inline int w1_DS18S20_convert_temp(u8 rom[9]);
 static struct w1_therm_family_converter w1_therm_families[] = {
        {
                .f              = &w1_therm_family_DS18S20,
-               .convert        = w1_DS18S20_convert_temp
+               .convert        = w1_DS18S20_convert_temp,
+               .precision      = w1_DS18S20_precision,
+               .eeprom         = w1_therm_eeprom
        },
        {
                .f              = &w1_therm_family_DS1822,
-               .convert        = w1_DS18B20_convert_temp
+               .convert        = w1_DS18B20_convert_temp,
+               .precision      = w1_DS18S20_precision,
+               .eeprom         = w1_therm_eeprom
        },
        {
                .f              = &w1_therm_family_DS18B20,
-               .convert        = w1_DS18B20_convert_temp
+               .convert        = w1_DS18B20_convert_temp,
+               .precision      = w1_DS18B20_precision,
+               .eeprom         = w1_therm_eeprom
        },
        {
                .f              = &w1_therm_family_DS28EA00,
-               .convert        = w1_DS18B20_convert_temp
+               .convert        = w1_DS18B20_convert_temp,
+               .precision      = w1_DS18S20_precision,
+               .eeprom         = w1_therm_eeprom
        },
        {
                .f              = &w1_therm_family_DS1825,
-               .convert        = w1_DS18B20_convert_temp
+               .convert        = w1_DS18B20_convert_temp,
+               .precision      = w1_DS18S20_precision,
+               .eeprom         = w1_therm_eeprom
        }
 };
 
+static inline int w1_therm_eeprom(struct device *device)
+{
+       struct w1_slave *sl = dev_to_w1_slave(device);
+       struct w1_master *dev = sl->master;
+       u8 rom[9], external_power;
+       int ret, max_trying = 10;
+       u8 *family_data = sl->family_data;
+
+       ret = mutex_lock_interruptible(&dev->bus_mutex);
+       if (ret != 0)
+               goto post_unlock;
+
+       if (!sl->family_data) {
+               ret = -ENODEV;
+               goto pre_unlock;
+       }
+
+       /* prevent the slave from going away in sleep */
+       atomic_inc(THERM_REFCNT(family_data));
+       memset(rom, 0, sizeof(rom));
+
+       while (max_trying--) {
+               if (!w1_reset_select_slave(sl)) {
+                       unsigned int tm = 10;
+                       unsigned long sleep_rem;
+
+                       /* check if in parasite mode */
+                       w1_write_8(dev, W1_READ_PSUPPLY);
+                       external_power = w1_read_8(dev);
+
+                       if (w1_reset_select_slave(sl))
+                               continue;
+
+                       /* 10ms strong pullup/delay after the copy command */
+                       if (w1_strong_pullup == 2 ||
+                           (!external_power && w1_strong_pullup))
+                               w1_next_pullup(dev, tm);
+
+                       w1_write_8(dev, W1_COPY_SCRATCHPAD);
+
+                       if (external_power) {
+                               mutex_unlock(&dev->bus_mutex);
+
+                               sleep_rem = msleep_interruptible(tm);
+                               if (sleep_rem != 0) {
+                                       ret = -EINTR;
+                                       goto post_unlock;
+                               }
+
+                               ret = mutex_lock_interruptible(&dev->bus_mutex);
+                               if (ret != 0)
+                                       goto post_unlock;
+                       } else if (!w1_strong_pullup) {
+                               sleep_rem = msleep_interruptible(tm);
+                               if (sleep_rem != 0) {
+                                       ret = -EINTR;
+                                       goto pre_unlock;
+                               }
+                       }
+
+                       break;
+               }
+       }
+
+pre_unlock:
+       mutex_unlock(&dev->bus_mutex);
+
+post_unlock:
+       atomic_dec(THERM_REFCNT(family_data));
+       return ret;
+}
+
+/* DS18S20 does not feature configuration register */
+static inline int w1_DS18S20_precision(struct device *device, int val)
+{
+       return 0;
+}
+
+static inline int w1_DS18B20_precision(struct device *device, int val)
+{
+       struct w1_slave *sl = dev_to_w1_slave(device);
+       struct w1_master *dev = sl->master;
+       u8 rom[9], crc;
+       int ret, max_trying = 10;
+       u8 *family_data = sl->family_data;
+       uint8_t precision_bits;
+       uint8_t mask = 0x60;
+
+       if(val > 12 || val < 9) {
+               pr_warn("Unsupported precision\n");
+               return -1;
+       }
+
+       ret = mutex_lock_interruptible(&dev->bus_mutex);
+       if (ret != 0)
+               goto post_unlock;
+
+       if (!sl->family_data) {
+               ret = -ENODEV;
+               goto pre_unlock;
+       }
+
+       /* prevent the slave from going away in sleep */
+       atomic_inc(THERM_REFCNT(family_data));
+       memset(rom, 0, sizeof(rom));
+
+       /* translate precision to bitmask (see datasheet page 9) */
+       switch (val) {
+       case 9:
+               precision_bits = 0x00;
+               break;
+       case 10:
+               precision_bits = 0x20;
+               break;
+       case 11:
+               precision_bits = 0x40;
+               break;
+       case 12:
+       default:
+               precision_bits = 0x60;
+               break;
+       }
+
+       while (max_trying--) {
+               crc = 0;
+
+               if (!w1_reset_select_slave(sl)) {
+                       int count = 0;
+
+                       /* read values to only alter precision bits */
+                       w1_write_8(dev, W1_READ_SCRATCHPAD);
+                       if ((count = w1_read_block(dev, rom, 9)) != 9)
+                               dev_warn(device, "w1_read_block() returned %u instead of 9.\n", count);
+
+                       crc = w1_calc_crc8(rom, 8);
+                       if (rom[8] == crc) {
+                               rom[4] = (rom[4] & ~mask) | (precision_bits & mask);
+
+                               if (!w1_reset_select_slave(sl)) {
+                                       w1_write_8(dev, W1_WRITE_SCRATCHPAD);
+                                       w1_write_8(dev, rom[2]);
+                                       w1_write_8(dev, rom[3]);
+                                       w1_write_8(dev, rom[4]);
+
+                                       break;
+                               }
+                       }
+               }
+       }
+
+pre_unlock:
+       mutex_unlock(&dev->bus_mutex);
+
+post_unlock:
+       atomic_dec(THERM_REFCNT(family_data));
+       return ret;
+}
+
 static inline int w1_DS18B20_convert_temp(u8 rom[9])
 {
        s16 t = le16_to_cpup((__le16 *)rom);
@@ -220,6 +400,30 @@ static inline int w1_convert_temp(u8 rom[9], u8 fid)
        return 0;
 }
 
+static ssize_t w1_slave_store(struct device *device,
+                             struct device_attribute *attr, const char *buf,
+                             size_t size)
+{
+       int val, ret;
+       struct w1_slave *sl = dev_to_w1_slave(device);
+       int i;
+
+       ret = kstrtoint(buf, 0, &val);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) {
+               if (w1_therm_families[i].f->fid == sl->family->fid) {
+                       /* zero value indicates to write current configuration to eeprom */
+                       if (0 == val)
+                               ret = w1_therm_families[i].eeprom(device);
+                       else
+                               ret = w1_therm_families[i].precision(device, val);
+                       break;
+               }
+       }
+       return ret ? : size;
+}
 
 static ssize_t w1_slave_show(struct device *device,
        struct device_attribute *attr, char *buf)
@@ -311,7 +515,7 @@ static ssize_t w1_slave_show(struct device *device,
        for (i = 0; i < 9; ++i)
                c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", rom[i]);
        c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n",
-                          crc, (verdict) ? "YES" : "NO");
+                     crc, (verdict) ? "YES" : "NO");
        if (verdict)
                memcpy(family_data, rom, sizeof(rom));
        else
index 56a49ba41d83c0945203827946ba35dcb79897b0..129895f562b06630b464f2579dc9e5bdc105f35e 100644 (file)
@@ -58,6 +58,8 @@ struct w1_reg_num
 #define W1_ALARM_SEARCH                0xEC
 #define W1_CONVERT_TEMP                0x44
 #define W1_SKIP_ROM            0xCC
+#define W1_COPY_SCRATCHPAD     0x48
+#define W1_WRITE_SCRATCHPAD    0x4E
 #define W1_READ_SCRATCHPAD     0xBE
 #define W1_READ_ROM            0x33
 #define W1_READ_PSUPPLY                0xB4