1/* 2 * w1_therm.c 3 * 4 * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net> 5 * 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the therms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21 22#include <asm/types.h> 23 24#include <linux/kernel.h> 25#include <linux/module.h> 26#include <linux/moduleparam.h> 27#include <linux/sched.h> 28#include <linux/device.h> 29#include <linux/types.h> 30#include <linux/slab.h> 31#include <linux/delay.h> 32 33#include "../w1.h" 34#include "../w1_int.h" 35#include "../w1_family.h" 36 37MODULE_LICENSE("GPL"); 38MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); 39MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, temperature family."); 40MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS18S20)); 41MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS1822)); 42MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS18B20)); 43MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS1825)); 44MODULE_ALIAS("w1-family-" __stringify(W1_THERM_DS28EA00)); 45 46/* Allow the strong pullup to be disabled, but default to enabled. 47 * If it was disabled a parasite powered device might not get the require 48 * current to do a temperature conversion. If it is enabled parasite powered 49 * devices have a better chance of getting the current required. 50 * In case the parasite power-detection is not working (seems to be the case 51 * for some DS18S20) the strong pullup can also be forced, regardless of the 52 * power state of the devices. 53 * 54 * Summary of options: 55 * - strong_pullup = 0 Disable strong pullup completely 56 * - strong_pullup = 1 Enable automatic strong pullup detection 57 * - strong_pullup = 2 Force strong pullup 58 */ 59static int w1_strong_pullup = 1; 60module_param_named(strong_pullup, w1_strong_pullup, int, 0); 61 62struct w1_therm_family_data { 63 uint8_t rom[9]; 64 atomic_t refcnt; 65}; 66 67/* return the address of the refcnt in the family data */ 68#define THERM_REFCNT(family_data) \ 69 (&((struct w1_therm_family_data*)family_data)->refcnt) 70 71static int w1_therm_add_slave(struct w1_slave *sl) 72{ 73 sl->family_data = kzalloc(sizeof(struct w1_therm_family_data), 74 GFP_KERNEL); 75 if (!sl->family_data) 76 return -ENOMEM; 77 atomic_set(THERM_REFCNT(sl->family_data), 1); 78 return 0; 79} 80 81static void w1_therm_remove_slave(struct w1_slave *sl) 82{ 83 int refcnt = atomic_sub_return(1, THERM_REFCNT(sl->family_data)); 84 while(refcnt) { 85 msleep(1000); 86 refcnt = atomic_read(THERM_REFCNT(sl->family_data)); 87 } 88 kfree(sl->family_data); 89 sl->family_data = NULL; 90} 91 92static ssize_t w1_slave_show(struct device *device, 93 struct device_attribute *attr, char *buf); 94 95static DEVICE_ATTR_RO(w1_slave); 96 97static struct attribute *w1_therm_attrs[] = { 98 &dev_attr_w1_slave.attr, 99 NULL, 100}; 101ATTRIBUTE_GROUPS(w1_therm); 102 103static struct w1_family_ops w1_therm_fops = { 104 .add_slave = w1_therm_add_slave, 105 .remove_slave = w1_therm_remove_slave, 106 .groups = w1_therm_groups, 107}; 108 109static struct w1_family w1_therm_family_DS18S20 = { 110 .fid = W1_THERM_DS18S20, 111 .fops = &w1_therm_fops, 112}; 113 114static struct w1_family w1_therm_family_DS18B20 = { 115 .fid = W1_THERM_DS18B20, 116 .fops = &w1_therm_fops, 117}; 118 119static struct w1_family w1_therm_family_DS1822 = { 120 .fid = W1_THERM_DS1822, 121 .fops = &w1_therm_fops, 122}; 123 124static struct w1_family w1_therm_family_DS28EA00 = { 125 .fid = W1_THERM_DS28EA00, 126 .fops = &w1_therm_fops, 127}; 128 129static struct w1_family w1_therm_family_DS1825 = { 130 .fid = W1_THERM_DS1825, 131 .fops = &w1_therm_fops, 132}; 133 134struct w1_therm_family_converter 135{ 136 u8 broken; 137 u16 reserved; 138 struct w1_family *f; 139 int (*convert)(u8 rom[9]); 140}; 141 142/* The return value is millidegrees Centigrade. */ 143static inline int w1_DS18B20_convert_temp(u8 rom[9]); 144static inline int w1_DS18S20_convert_temp(u8 rom[9]); 145 146static struct w1_therm_family_converter w1_therm_families[] = { 147 { 148 .f = &w1_therm_family_DS18S20, 149 .convert = w1_DS18S20_convert_temp 150 }, 151 { 152 .f = &w1_therm_family_DS1822, 153 .convert = w1_DS18B20_convert_temp 154 }, 155 { 156 .f = &w1_therm_family_DS18B20, 157 .convert = w1_DS18B20_convert_temp 158 }, 159 { 160 .f = &w1_therm_family_DS28EA00, 161 .convert = w1_DS18B20_convert_temp 162 }, 163 { 164 .f = &w1_therm_family_DS1825, 165 .convert = w1_DS18B20_convert_temp 166 } 167}; 168 169static inline int w1_DS18B20_convert_temp(u8 rom[9]) 170{ 171 s16 t = le16_to_cpup((__le16 *)rom); 172 return t*1000/16; 173} 174 175static inline int w1_DS18S20_convert_temp(u8 rom[9]) 176{ 177 int t, h; 178 179 if (!rom[7]) 180 return 0; 181 182 if (rom[1] == 0) 183 t = ((s32)rom[0] >> 1)*1000; 184 else 185 t = 1000*(-1*(s32)(0x100-rom[0]) >> 1); 186 187 t -= 250; 188 h = 1000*((s32)rom[7] - (s32)rom[6]); 189 h /= (s32)rom[7]; 190 t += h; 191 192 return t; 193} 194 195static inline int w1_convert_temp(u8 rom[9], u8 fid) 196{ 197 int i; 198 199 for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) 200 if (w1_therm_families[i].f->fid == fid) 201 return w1_therm_families[i].convert(rom); 202 203 return 0; 204} 205 206 207static ssize_t w1_slave_show(struct device *device, 208 struct device_attribute *attr, char *buf) 209{ 210 struct w1_slave *sl = dev_to_w1_slave(device); 211 struct w1_master *dev = sl->master; 212 u8 rom[9], crc, verdict, external_power; 213 int i, ret, max_trying = 10; 214 ssize_t c = PAGE_SIZE; 215 u8 *family_data = sl->family_data; 216 217 ret = mutex_lock_interruptible(&dev->bus_mutex); 218 if (ret != 0) 219 goto post_unlock; 220 221 if(!sl->family_data) 222 { 223 ret = -ENODEV; 224 goto pre_unlock; 225 } 226 227 /* prevent the slave from going away in sleep */ 228 atomic_inc(THERM_REFCNT(family_data)); 229 memset(rom, 0, sizeof(rom)); 230 231 while (max_trying--) { 232 233 verdict = 0; 234 crc = 0; 235 236 if (!w1_reset_select_slave(sl)) { 237 int count = 0; 238 unsigned int tm = 750; 239 unsigned long sleep_rem; 240 241 w1_write_8(dev, W1_READ_PSUPPLY); 242 external_power = w1_read_8(dev); 243 244 if (w1_reset_select_slave(sl)) 245 continue; 246 247 /* 750ms strong pullup (or delay) after the convert */ 248 if (w1_strong_pullup == 2 || 249 (!external_power && w1_strong_pullup)) 250 w1_next_pullup(dev, tm); 251 252 w1_write_8(dev, W1_CONVERT_TEMP); 253 254 if (external_power) { 255 mutex_unlock(&dev->bus_mutex); 256 257 sleep_rem = msleep_interruptible(tm); 258 if (sleep_rem != 0) { 259 ret = -EINTR; 260 goto post_unlock; 261 } 262 263 ret = mutex_lock_interruptible(&dev->bus_mutex); 264 if (ret != 0) 265 goto post_unlock; 266 } else if (!w1_strong_pullup) { 267 sleep_rem = msleep_interruptible(tm); 268 if (sleep_rem != 0) { 269 ret = -EINTR; 270 goto pre_unlock; 271 } 272 } 273 274 if (!w1_reset_select_slave(sl)) { 275 276 w1_write_8(dev, W1_READ_SCRATCHPAD); 277 if ((count = w1_read_block(dev, rom, 9)) != 9) { 278 dev_warn(device, "w1_read_block() " 279 "returned %u instead of 9.\n", 280 count); 281 } 282 283 crc = w1_calc_crc8(rom, 8); 284 285 if (rom[8] == crc) 286 verdict = 1; 287 } 288 } 289 290 if (verdict) 291 break; 292 } 293 294 for (i = 0; i < 9; ++i) 295 c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", rom[i]); 296 c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n", 297 crc, (verdict) ? "YES" : "NO"); 298 if (verdict) 299 memcpy(family_data, rom, sizeof(rom)); 300 else 301 dev_warn(device, "Read failed CRC check\n"); 302 303 for (i = 0; i < 9; ++i) 304 c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", 305 ((u8 *)family_data)[i]); 306 307 c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n", 308 w1_convert_temp(rom, sl->family->fid)); 309 ret = PAGE_SIZE - c; 310 311pre_unlock: 312 mutex_unlock(&dev->bus_mutex); 313 314post_unlock: 315 atomic_dec(THERM_REFCNT(family_data)); 316 return ret; 317} 318 319static int __init w1_therm_init(void) 320{ 321 int err, i; 322 323 for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) { 324 err = w1_register_family(w1_therm_families[i].f); 325 if (err) 326 w1_therm_families[i].broken = 1; 327 } 328 329 return 0; 330} 331 332static void __exit w1_therm_fini(void) 333{ 334 int i; 335 336 for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) 337 if (!w1_therm_families[i].broken) 338 w1_unregister_family(w1_therm_families[i].f); 339} 340 341module_init(w1_therm_init); 342module_exit(w1_therm_fini); 343