[WATCHDOG] ep93xx: implemented watchdog timer driver for TS-72xx SBCs

Technologic Systems TS-72xx SBCs have external glue logic
CPLD which includes watchdog timer. This driver implements
kernel support for that.

Signed-off-by: Mika Westerberg <mika.westerberg@iki.fi>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>

authored by Mika Westerberg and committed by Wim Van Sebroeck c90bf2aa 64096c17

+528
+11
drivers/watchdog/Kconfig
··· 289 289 Say Y here if you want support for the watchdog timer on Avionic 290 290 Design Xanthos boards. 291 291 292 + config TS72XX_WATCHDOG 293 + tristate "TS-72XX SBC Watchdog" 294 + depends on MACH_TS72XX 295 + help 296 + Technologic Systems TS-7200, TS-7250 and TS-7260 boards have 297 + watchdog timer implemented in a external CPLD chip. Say Y here 298 + if you want to support for the watchdog timer on TS-72XX boards. 299 + 300 + To compile this driver as a module, choose M here: the 301 + module will be called ts72xx_wdt. 302 + 292 303 # AVR32 Architecture 293 304 294 305 config AT32AP700X_WDT
+1
drivers/watchdog/Makefile
··· 46 46 obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o 47 47 obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o 48 48 obj-$(CONFIG_ADX_WATCHDOG) += adx_wdt.o 49 + obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o 49 50 50 51 # AVR32 Architecture 51 52 obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
+516
drivers/watchdog/ts72xx_wdt.c
··· 1 + /* 2 + * Watchdog driver for Technologic Systems TS-72xx based SBCs 3 + * (TS-7200, TS-7250 and TS-7260). These boards have external 4 + * glue logic CPLD chip, which includes programmable watchdog 5 + * timer. 6 + * 7 + * Copyright (c) 2009 Mika Westerberg <mika.westerberg@iki.fi> 8 + * 9 + * This driver is based on ep93xx_wdt and wm831x_wdt drivers. 10 + * 11 + * This file is licensed under the terms of the GNU General Public 12 + * License version 2. This program is licensed "as is" without any 13 + * warranty of any kind, whether express or implied. 14 + */ 15 + 16 + #include <linux/fs.h> 17 + #include <linux/io.h> 18 + #include <linux/module.h> 19 + #include <linux/moduleparam.h> 20 + #include <linux/miscdevice.h> 21 + #include <linux/mutex.h> 22 + #include <linux/platform_device.h> 23 + #include <linux/watchdog.h> 24 + #include <linux/uaccess.h> 25 + 26 + #define TS72XX_WDT_FEED_VAL 0x05 27 + #define TS72XX_WDT_DEFAULT_TIMEOUT 8 28 + 29 + static int timeout = TS72XX_WDT_DEFAULT_TIMEOUT; 30 + module_param(timeout, int, 0); 31 + MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. " 32 + "(1 <= timeout <= 8, default=" 33 + __MODULE_STRING(TS72XX_WDT_DEFAULT_TIMEOUT) 34 + ")"); 35 + 36 + static int nowayout = WATCHDOG_NOWAYOUT; 37 + module_param(nowayout, int, 0); 38 + MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); 39 + 40 + /** 41 + * struct ts72xx_wdt - watchdog control structure 42 + * @lock: lock that protects this structure 43 + * @regval: watchdog timeout value suitable for control register 44 + * @flags: flags controlling watchdog device state 45 + * @control_reg: watchdog control register 46 + * @feed_reg: watchdog feed register 47 + * @pdev: back pointer to platform dev 48 + */ 49 + struct ts72xx_wdt { 50 + struct mutex lock; 51 + int regval; 52 + 53 + #define TS72XX_WDT_BUSY_FLAG 1 54 + #define TS72XX_WDT_EXPECT_CLOSE_FLAG 2 55 + int flags; 56 + 57 + void __iomem *control_reg; 58 + void __iomem *feed_reg; 59 + 60 + struct platform_device *pdev; 61 + }; 62 + 63 + struct platform_device *ts72xx_wdt_pdev; 64 + 65 + /* 66 + * TS-72xx Watchdog supports following timeouts (value written 67 + * to control register): 68 + * value description 69 + * ------------------------- 70 + * 0x00 watchdog disabled 71 + * 0x01 250ms 72 + * 0x02 500ms 73 + * 0x03 1s 74 + * 0x04 reserved 75 + * 0x05 2s 76 + * 0x06 4s 77 + * 0x07 8s 78 + * 79 + * Timeouts below 1s are not very usable so we don't 80 + * allow them at all. 81 + * 82 + * We provide two functions that convert between these: 83 + * timeout_to_regval() and regval_to_timeout(). 84 + */ 85 + static const struct { 86 + int timeout; 87 + int regval; 88 + } ts72xx_wdt_map[] = { 89 + { 1, 3 }, 90 + { 2, 5 }, 91 + { 4, 6 }, 92 + { 8, 7 }, 93 + }; 94 + 95 + /** 96 + * timeout_to_regval() - converts given timeout to control register value 97 + * @new_timeout: timeout in seconds to be converted 98 + * 99 + * Function converts given @new_timeout into valid value that can 100 + * be programmed into watchdog control register. When conversion is 101 + * not possible, function returns %-EINVAL. 102 + */ 103 + static int timeout_to_regval(int new_timeout) 104 + { 105 + int i; 106 + 107 + /* first limit it to 1 - 8 seconds */ 108 + new_timeout = clamp_val(new_timeout, 1, 8); 109 + 110 + for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) { 111 + if (ts72xx_wdt_map[i].timeout >= new_timeout) 112 + return ts72xx_wdt_map[i].regval; 113 + } 114 + 115 + return -EINVAL; 116 + } 117 + 118 + /** 119 + * regval_to_timeout() - converts control register value to timeout 120 + * @regval: control register value to be converted 121 + * 122 + * Function converts given @regval to timeout in seconds (1, 2, 4 or 8). 123 + * If @regval cannot be converted, function returns %-EINVAL. 124 + */ 125 + static int regval_to_timeout(int regval) 126 + { 127 + int i; 128 + 129 + for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) { 130 + if (ts72xx_wdt_map[i].regval == regval) 131 + return ts72xx_wdt_map[i].timeout; 132 + } 133 + 134 + return -EINVAL; 135 + } 136 + 137 + /** 138 + * ts72xx_wdt_kick() - kick the watchdog 139 + * @wdt: watchdog to be kicked 140 + * 141 + * Called with @wdt->lock held. 142 + */ 143 + static inline void ts72xx_wdt_kick(struct ts72xx_wdt *wdt) 144 + { 145 + __raw_writeb(TS72XX_WDT_FEED_VAL, wdt->feed_reg); 146 + } 147 + 148 + /** 149 + * ts72xx_wdt_start() - starts the watchdog timer 150 + * @wdt: watchdog to be started 151 + * 152 + * This function programs timeout to watchdog timer 153 + * and starts it. 154 + * 155 + * Called with @wdt->lock held. 156 + */ 157 + static void ts72xx_wdt_start(struct ts72xx_wdt *wdt) 158 + { 159 + /* 160 + * To program the wdt, it first must be "fed" and 161 + * only after that (within 30 usecs) the configuration 162 + * can be changed. 163 + */ 164 + ts72xx_wdt_kick(wdt); 165 + __raw_writeb((u8)wdt->regval, wdt->control_reg); 166 + } 167 + 168 + /** 169 + * ts72xx_wdt_stop() - stops the watchdog timer 170 + * @wdt: watchdog to be stopped 171 + * 172 + * Called with @wdt->lock held. 173 + */ 174 + static void ts72xx_wdt_stop(struct ts72xx_wdt *wdt) 175 + { 176 + ts72xx_wdt_kick(wdt); 177 + __raw_writeb(0, wdt->control_reg); 178 + } 179 + 180 + static int ts72xx_wdt_open(struct inode *inode, struct file *file) 181 + { 182 + struct ts72xx_wdt *wdt = platform_get_drvdata(ts72xx_wdt_pdev); 183 + int regval; 184 + 185 + /* 186 + * Try to convert default timeout to valid register 187 + * value first. 188 + */ 189 + regval = timeout_to_regval(timeout); 190 + if (regval < 0) { 191 + dev_err(&wdt->pdev->dev, 192 + "failed to convert timeout (%d) to register value\n", 193 + timeout); 194 + return -EINVAL; 195 + } 196 + 197 + if (mutex_lock_interruptible(&wdt->lock)) 198 + return -ERESTARTSYS; 199 + 200 + if ((wdt->flags & TS72XX_WDT_BUSY_FLAG) != 0) { 201 + mutex_unlock(&wdt->lock); 202 + return -EBUSY; 203 + } 204 + 205 + wdt->flags = TS72XX_WDT_BUSY_FLAG; 206 + wdt->regval = regval; 207 + file->private_data = wdt; 208 + 209 + ts72xx_wdt_start(wdt); 210 + 211 + mutex_unlock(&wdt->lock); 212 + return nonseekable_open(inode, file); 213 + } 214 + 215 + static int ts72xx_wdt_release(struct inode *inode, struct file *file) 216 + { 217 + struct ts72xx_wdt *wdt = file->private_data; 218 + 219 + if (mutex_lock_interruptible(&wdt->lock)) 220 + return -ERESTARTSYS; 221 + 222 + if ((wdt->flags & TS72XX_WDT_EXPECT_CLOSE_FLAG) != 0) { 223 + ts72xx_wdt_stop(wdt); 224 + } else { 225 + dev_warn(&wdt->pdev->dev, 226 + "TS-72XX WDT device closed unexpectly. " 227 + "Watchdog timer will not stop!\n"); 228 + /* 229 + * Kick it one more time, to give userland some time 230 + * to recover (for example, respawning the kicker 231 + * daemon). 232 + */ 233 + ts72xx_wdt_kick(wdt); 234 + } 235 + 236 + wdt->flags = 0; 237 + 238 + mutex_unlock(&wdt->lock); 239 + return 0; 240 + } 241 + 242 + static ssize_t ts72xx_wdt_write(struct file *file, 243 + const char __user *data, 244 + size_t len, 245 + loff_t *ppos) 246 + { 247 + struct ts72xx_wdt *wdt = file->private_data; 248 + 249 + if (!len) 250 + return 0; 251 + 252 + if (mutex_lock_interruptible(&wdt->lock)) 253 + return -ERESTARTSYS; 254 + 255 + ts72xx_wdt_kick(wdt); 256 + 257 + /* 258 + * Support for magic character closing. User process 259 + * writes 'V' into the device, just before it is closed. 260 + * This means that we know that the wdt timer can be 261 + * stopped after user closes the device. 262 + */ 263 + if (!nowayout) { 264 + int i; 265 + 266 + for (i = 0; i < len; i++) { 267 + char c; 268 + 269 + /* In case it was set long ago */ 270 + wdt->flags &= ~TS72XX_WDT_EXPECT_CLOSE_FLAG; 271 + 272 + if (get_user(c, data + i)) { 273 + mutex_unlock(&wdt->lock); 274 + return -EFAULT; 275 + } 276 + if (c == 'V') { 277 + wdt->flags |= TS72XX_WDT_EXPECT_CLOSE_FLAG; 278 + break; 279 + } 280 + } 281 + } 282 + 283 + mutex_unlock(&wdt->lock); 284 + return len; 285 + } 286 + 287 + static const struct watchdog_info winfo = { 288 + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | 289 + WDIOF_MAGICCLOSE, 290 + .firmware_version = 1, 291 + .identity = "TS-72XX WDT", 292 + }; 293 + 294 + static long ts72xx_wdt_ioctl(struct file *file, unsigned int cmd, 295 + unsigned long arg) 296 + { 297 + struct ts72xx_wdt *wdt = file->private_data; 298 + void __user *argp = (void __user *)arg; 299 + int __user *p = (int __user *)argp; 300 + int error = 0; 301 + 302 + if (mutex_lock_interruptible(&wdt->lock)) 303 + return -ERESTARTSYS; 304 + 305 + switch (cmd) { 306 + case WDIOC_GETSUPPORT: 307 + error = copy_to_user(argp, &winfo, sizeof(winfo)); 308 + break; 309 + 310 + case WDIOC_KEEPALIVE: 311 + ts72xx_wdt_kick(wdt); 312 + break; 313 + 314 + case WDIOC_SETOPTIONS: { 315 + int options; 316 + 317 + if (get_user(options, p)) { 318 + error = -EFAULT; 319 + break; 320 + } 321 + 322 + error = -EINVAL; 323 + 324 + if ((options & WDIOS_DISABLECARD) != 0) { 325 + ts72xx_wdt_stop(wdt); 326 + error = 0; 327 + } 328 + if ((options & WDIOS_ENABLECARD) != 0) { 329 + ts72xx_wdt_start(wdt); 330 + error = 0; 331 + } 332 + 333 + break; 334 + } 335 + 336 + case WDIOC_SETTIMEOUT: { 337 + int new_timeout; 338 + 339 + if (get_user(new_timeout, p)) { 340 + error = -EFAULT; 341 + } else { 342 + int regval; 343 + 344 + regval = timeout_to_regval(new_timeout); 345 + if (regval < 0) { 346 + error = -EINVAL; 347 + } else { 348 + ts72xx_wdt_stop(wdt); 349 + wdt->regval = regval; 350 + ts72xx_wdt_start(wdt); 351 + } 352 + } 353 + if (error) 354 + break; 355 + 356 + /*FALLTHROUGH*/ 357 + } 358 + 359 + case WDIOC_GETTIMEOUT: 360 + if (put_user(regval_to_timeout(wdt->regval), p)) 361 + error = -EFAULT; 362 + break; 363 + 364 + default: 365 + error = -ENOTTY; 366 + break; 367 + } 368 + 369 + mutex_unlock(&wdt->lock); 370 + return error; 371 + } 372 + 373 + static const struct file_operations ts72xx_wdt_fops = { 374 + .owner = THIS_MODULE, 375 + .llseek = no_llseek, 376 + .open = ts72xx_wdt_open, 377 + .release = ts72xx_wdt_release, 378 + .write = ts72xx_wdt_write, 379 + .unlocked_ioctl = ts72xx_wdt_ioctl, 380 + }; 381 + 382 + static struct miscdevice ts72xx_wdt_miscdev = { 383 + .minor = WATCHDOG_MINOR, 384 + .name = "watchdog", 385 + .fops = &ts72xx_wdt_fops, 386 + }; 387 + 388 + static __devinit int ts72xx_wdt_probe(struct platform_device *pdev) 389 + { 390 + struct ts72xx_wdt *wdt; 391 + struct resource *r1, *r2; 392 + int error = 0; 393 + 394 + wdt = kzalloc(sizeof(struct ts72xx_wdt), GFP_KERNEL); 395 + if (!wdt) { 396 + dev_err(&pdev->dev, "failed to allocate memory\n"); 397 + return -ENOMEM; 398 + } 399 + 400 + r1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); 401 + if (!r1) { 402 + dev_err(&pdev->dev, "failed to get memory resource\n"); 403 + error = -ENODEV; 404 + goto fail; 405 + } 406 + 407 + r1 = request_mem_region(r1->start, resource_size(r1), pdev->name); 408 + if (!r1) { 409 + dev_err(&pdev->dev, "cannot request memory region\n"); 410 + error = -EBUSY; 411 + goto fail; 412 + } 413 + 414 + wdt->control_reg = ioremap(r1->start, resource_size(r1)); 415 + if (!wdt->control_reg) { 416 + dev_err(&pdev->dev, "failed to map memory\n"); 417 + error = -ENODEV; 418 + goto fail_free_control; 419 + } 420 + 421 + r2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); 422 + if (!r2) { 423 + dev_err(&pdev->dev, "failed to get memory resource\n"); 424 + error = -ENODEV; 425 + goto fail_unmap_control; 426 + } 427 + 428 + r2 = request_mem_region(r2->start, resource_size(r2), pdev->name); 429 + if (!r2) { 430 + dev_err(&pdev->dev, "cannot request memory region\n"); 431 + error = -EBUSY; 432 + goto fail_unmap_control; 433 + } 434 + 435 + wdt->feed_reg = ioremap(r2->start, resource_size(r2)); 436 + if (!wdt->feed_reg) { 437 + dev_err(&pdev->dev, "failed to map memory\n"); 438 + error = -ENODEV; 439 + goto fail_free_feed; 440 + } 441 + 442 + platform_set_drvdata(pdev, wdt); 443 + ts72xx_wdt_pdev = pdev; 444 + wdt->pdev = pdev; 445 + mutex_init(&wdt->lock); 446 + 447 + error = misc_register(&ts72xx_wdt_miscdev); 448 + if (error) { 449 + dev_err(&pdev->dev, "failed to register miscdev\n"); 450 + goto fail_unmap_feed; 451 + } 452 + 453 + dev_info(&pdev->dev, "TS-72xx Watchdog driver\n"); 454 + 455 + return 0; 456 + 457 + fail_unmap_feed: 458 + platform_set_drvdata(pdev, NULL); 459 + iounmap(wdt->feed_reg); 460 + fail_free_feed: 461 + release_mem_region(r2->start, resource_size(r2)); 462 + fail_unmap_control: 463 + iounmap(wdt->control_reg); 464 + fail_free_control: 465 + release_mem_region(r1->start, resource_size(r1)); 466 + fail: 467 + kfree(wdt); 468 + return error; 469 + } 470 + 471 + static __devexit int ts72xx_wdt_remove(struct platform_device *pdev) 472 + { 473 + struct ts72xx_wdt *wdt = platform_get_drvdata(pdev); 474 + struct resource *res; 475 + int error; 476 + 477 + error = misc_deregister(&ts72xx_wdt_miscdev); 478 + platform_set_drvdata(pdev, NULL); 479 + 480 + iounmap(wdt->control_reg); 481 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 482 + release_mem_region(res->start, resource_size(res)); 483 + 484 + iounmap(wdt->feed_reg); 485 + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 486 + release_mem_region(res->start, resource_size(res)); 487 + 488 + kfree(wdt); 489 + return error; 490 + } 491 + 492 + static struct platform_driver ts72xx_wdt_driver = { 493 + .probe = ts72xx_wdt_probe, 494 + .remove = __devexit_p(ts72xx_wdt_remove), 495 + .driver = { 496 + .name = "ts72xx-wdt", 497 + .owner = THIS_MODULE, 498 + }, 499 + }; 500 + 501 + static __init int ts72xx_wdt_init(void) 502 + { 503 + return platform_driver_register(&ts72xx_wdt_driver); 504 + } 505 + module_init(ts72xx_wdt_init); 506 + 507 + static __exit void ts72xx_wdt_exit(void) 508 + { 509 + platform_driver_unregister(&ts72xx_wdt_driver); 510 + } 511 + module_exit(ts72xx_wdt_exit); 512 + 513 + MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>"); 514 + MODULE_DESCRIPTION("TS-72xx SBC Watchdog"); 515 + MODULE_LICENSE("GPL"); 516 + MODULE_ALIAS("platform:ts72xx-wdt");