Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

w1: add ability to set (SRAM) and store (EEPROM) configuration for temp sensors like DS18B20

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>

authored by

Ben Sen and committed by
Greg Kroah-Hartman
0a19f129 ae53e374

+222 -8
+9 -1
Documentation/w1/slaves/w1_therm
··· 33 33 powered it would be possible to convert all the devices at the same 34 34 time and then go back to read individual sensors. That isn't 35 35 currently supported. The driver also doesn't support reduced 36 - precision (which would also reduce the conversion time). 36 + precision (which would also reduce the conversion time) when reading values. 37 + 38 + Writing a value between 9 and 12 to the sysfs w1_slave file will change the 39 + precision of the sensor for the next readings. This value is in (volatile) 40 + SRAM, so it is reset when the sensor gets power-cycled. 41 + 42 + To store the current precision configuration into EEPROM, the value 0 43 + has to be written to the sysfs w1_slave file. Since the EEPROM has a limited 44 + amount of writes (>50k), this command should be used wisely. 37 45 38 46 The module parameter strong_pullup can be set to 0 to disable the 39 47 strong pullup, 1 to enable autodetection or 2 to force strong pullup.
+211 -7
drivers/w1/slaves/w1_therm.c
··· 92 92 static ssize_t w1_slave_show(struct device *device, 93 93 struct device_attribute *attr, char *buf); 94 94 95 + static ssize_t w1_slave_store(struct device *device, 96 + struct device_attribute *attr, const char *buf, size_t size); 97 + 95 98 static ssize_t w1_seq_show(struct device *device, 96 99 struct device_attribute *attr, char *buf); 97 100 98 - static DEVICE_ATTR_RO(w1_slave); 101 + static DEVICE_ATTR_RW(w1_slave); 99 102 static DEVICE_ATTR_RO(w1_seq); 100 103 101 104 static struct attribute *w1_therm_attrs[] = { ··· 157 154 u16 reserved; 158 155 struct w1_family *f; 159 156 int (*convert)(u8 rom[9]); 157 + int (*precision)(struct device *device, int val); 158 + int (*eeprom)(struct device *device); 160 159 }; 160 + 161 + /* write configuration to eeprom */ 162 + static inline int w1_therm_eeprom(struct device *device); 163 + 164 + /* Set precision for conversion */ 165 + static inline int w1_DS18B20_precision(struct device *device, int val); 166 + static inline int w1_DS18S20_precision(struct device *device, int val); 161 167 162 168 /* The return value is millidegrees Centigrade. */ 163 169 static inline int w1_DS18B20_convert_temp(u8 rom[9]); ··· 175 163 static struct w1_therm_family_converter w1_therm_families[] = { 176 164 { 177 165 .f = &w1_therm_family_DS18S20, 178 - .convert = w1_DS18S20_convert_temp 166 + .convert = w1_DS18S20_convert_temp, 167 + .precision = w1_DS18S20_precision, 168 + .eeprom = w1_therm_eeprom 179 169 }, 180 170 { 181 171 .f = &w1_therm_family_DS1822, 182 - .convert = w1_DS18B20_convert_temp 172 + .convert = w1_DS18B20_convert_temp, 173 + .precision = w1_DS18S20_precision, 174 + .eeprom = w1_therm_eeprom 183 175 }, 184 176 { 185 177 .f = &w1_therm_family_DS18B20, 186 - .convert = w1_DS18B20_convert_temp 178 + .convert = w1_DS18B20_convert_temp, 179 + .precision = w1_DS18B20_precision, 180 + .eeprom = w1_therm_eeprom 187 181 }, 188 182 { 189 183 .f = &w1_therm_family_DS28EA00, 190 - .convert = w1_DS18B20_convert_temp 184 + .convert = w1_DS18B20_convert_temp, 185 + .precision = w1_DS18S20_precision, 186 + .eeprom = w1_therm_eeprom 191 187 }, 192 188 { 193 189 .f = &w1_therm_family_DS1825, 194 - .convert = w1_DS18B20_convert_temp 190 + .convert = w1_DS18B20_convert_temp, 191 + .precision = w1_DS18S20_precision, 192 + .eeprom = w1_therm_eeprom 195 193 } 196 194 }; 195 + 196 + static inline int w1_therm_eeprom(struct device *device) 197 + { 198 + struct w1_slave *sl = dev_to_w1_slave(device); 199 + struct w1_master *dev = sl->master; 200 + u8 rom[9], external_power; 201 + int ret, max_trying = 10; 202 + u8 *family_data = sl->family_data; 203 + 204 + ret = mutex_lock_interruptible(&dev->bus_mutex); 205 + if (ret != 0) 206 + goto post_unlock; 207 + 208 + if (!sl->family_data) { 209 + ret = -ENODEV; 210 + goto pre_unlock; 211 + } 212 + 213 + /* prevent the slave from going away in sleep */ 214 + atomic_inc(THERM_REFCNT(family_data)); 215 + memset(rom, 0, sizeof(rom)); 216 + 217 + while (max_trying--) { 218 + if (!w1_reset_select_slave(sl)) { 219 + unsigned int tm = 10; 220 + unsigned long sleep_rem; 221 + 222 + /* check if in parasite mode */ 223 + w1_write_8(dev, W1_READ_PSUPPLY); 224 + external_power = w1_read_8(dev); 225 + 226 + if (w1_reset_select_slave(sl)) 227 + continue; 228 + 229 + /* 10ms strong pullup/delay after the copy command */ 230 + if (w1_strong_pullup == 2 || 231 + (!external_power && w1_strong_pullup)) 232 + w1_next_pullup(dev, tm); 233 + 234 + w1_write_8(dev, W1_COPY_SCRATCHPAD); 235 + 236 + if (external_power) { 237 + mutex_unlock(&dev->bus_mutex); 238 + 239 + sleep_rem = msleep_interruptible(tm); 240 + if (sleep_rem != 0) { 241 + ret = -EINTR; 242 + goto post_unlock; 243 + } 244 + 245 + ret = mutex_lock_interruptible(&dev->bus_mutex); 246 + if (ret != 0) 247 + goto post_unlock; 248 + } else if (!w1_strong_pullup) { 249 + sleep_rem = msleep_interruptible(tm); 250 + if (sleep_rem != 0) { 251 + ret = -EINTR; 252 + goto pre_unlock; 253 + } 254 + } 255 + 256 + break; 257 + } 258 + } 259 + 260 + pre_unlock: 261 + mutex_unlock(&dev->bus_mutex); 262 + 263 + post_unlock: 264 + atomic_dec(THERM_REFCNT(family_data)); 265 + return ret; 266 + } 267 + 268 + /* DS18S20 does not feature configuration register */ 269 + static inline int w1_DS18S20_precision(struct device *device, int val) 270 + { 271 + return 0; 272 + } 273 + 274 + static inline int w1_DS18B20_precision(struct device *device, int val) 275 + { 276 + struct w1_slave *sl = dev_to_w1_slave(device); 277 + struct w1_master *dev = sl->master; 278 + u8 rom[9], crc; 279 + int ret, max_trying = 10; 280 + u8 *family_data = sl->family_data; 281 + uint8_t precision_bits; 282 + uint8_t mask = 0x60; 283 + 284 + if(val > 12 || val < 9) { 285 + pr_warn("Unsupported precision\n"); 286 + return -1; 287 + } 288 + 289 + ret = mutex_lock_interruptible(&dev->bus_mutex); 290 + if (ret != 0) 291 + goto post_unlock; 292 + 293 + if (!sl->family_data) { 294 + ret = -ENODEV; 295 + goto pre_unlock; 296 + } 297 + 298 + /* prevent the slave from going away in sleep */ 299 + atomic_inc(THERM_REFCNT(family_data)); 300 + memset(rom, 0, sizeof(rom)); 301 + 302 + /* translate precision to bitmask (see datasheet page 9) */ 303 + switch (val) { 304 + case 9: 305 + precision_bits = 0x00; 306 + break; 307 + case 10: 308 + precision_bits = 0x20; 309 + break; 310 + case 11: 311 + precision_bits = 0x40; 312 + break; 313 + case 12: 314 + default: 315 + precision_bits = 0x60; 316 + break; 317 + } 318 + 319 + while (max_trying--) { 320 + crc = 0; 321 + 322 + if (!w1_reset_select_slave(sl)) { 323 + int count = 0; 324 + 325 + /* read values to only alter precision bits */ 326 + w1_write_8(dev, W1_READ_SCRATCHPAD); 327 + if ((count = w1_read_block(dev, rom, 9)) != 9) 328 + dev_warn(device, "w1_read_block() returned %u instead of 9.\n", count); 329 + 330 + crc = w1_calc_crc8(rom, 8); 331 + if (rom[8] == crc) { 332 + rom[4] = (rom[4] & ~mask) | (precision_bits & mask); 333 + 334 + if (!w1_reset_select_slave(sl)) { 335 + w1_write_8(dev, W1_WRITE_SCRATCHPAD); 336 + w1_write_8(dev, rom[2]); 337 + w1_write_8(dev, rom[3]); 338 + w1_write_8(dev, rom[4]); 339 + 340 + break; 341 + } 342 + } 343 + } 344 + } 345 + 346 + pre_unlock: 347 + mutex_unlock(&dev->bus_mutex); 348 + 349 + post_unlock: 350 + atomic_dec(THERM_REFCNT(family_data)); 351 + return ret; 352 + } 197 353 198 354 static inline int w1_DS18B20_convert_temp(u8 rom[9]) 199 355 { ··· 400 220 return 0; 401 221 } 402 222 223 + static ssize_t w1_slave_store(struct device *device, 224 + struct device_attribute *attr, const char *buf, 225 + size_t size) 226 + { 227 + int val, ret; 228 + struct w1_slave *sl = dev_to_w1_slave(device); 229 + int i; 230 + 231 + ret = kstrtoint(buf, 0, &val); 232 + if (ret) 233 + return ret; 234 + 235 + for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) { 236 + if (w1_therm_families[i].f->fid == sl->family->fid) { 237 + /* zero value indicates to write current configuration to eeprom */ 238 + if (0 == val) 239 + ret = w1_therm_families[i].eeprom(device); 240 + else 241 + ret = w1_therm_families[i].precision(device, val); 242 + break; 243 + } 244 + } 245 + return ret ? : size; 246 + } 403 247 404 248 static ssize_t w1_slave_show(struct device *device, 405 249 struct device_attribute *attr, char *buf) ··· 515 311 for (i = 0; i < 9; ++i) 516 312 c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", rom[i]); 517 313 c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n", 518 - crc, (verdict) ? "YES" : "NO"); 314 + crc, (verdict) ? "YES" : "NO"); 519 315 if (verdict) 520 316 memcpy(family_data, rom, sizeof(rom)); 521 317 else
+2
drivers/w1/w1.h
··· 58 58 #define W1_ALARM_SEARCH 0xEC 59 59 #define W1_CONVERT_TEMP 0x44 60 60 #define W1_SKIP_ROM 0xCC 61 + #define W1_COPY_SCRATCHPAD 0x48 62 + #define W1_WRITE_SCRATCHPAD 0x4E 61 63 #define W1_READ_SCRATCHPAD 0xBE 62 64 #define W1_READ_ROM 0x33 63 65 #define W1_READ_PSUPPLY 0xB4