1 /* linux/drivers/char/watchdog/s3c2410_wdt.c 2 * 3 * Copyright (c) 2004 Simtec Electronics 4 * Ben Dooks <ben@simtec.co.uk> 5 * 6 * S3C2410 Watchdog Timer Support 7 * 8 * Based on, softdog.c by Alan Cox, 9 * (c) Copyright 1996 Alan Cox <alan@redhat.com> 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; if not, write to the Free Software 23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24 * 25 * Changelog: 26 * 05-Oct-2004 BJD Added semaphore init to stop crashes on open 27 * Fixed tmr_count / wdt_count confusion 28 * Added configurable debug 29 * 30 * 11-Jan-2005 BJD Fixed divide-by-2 in timeout code 31 * 32 * 25-Jan-2005 DA Added suspend/resume support 33 * Replaced reboot notifier with .shutdown method 34 * 35 * 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA 36 */ 37 38 #include <linux/module.h> 39 #include <linux/moduleparam.h> 40 #include <linux/types.h> 41 #include <linux/timer.h> 42 #include <linux/miscdevice.h> 43 #include <linux/watchdog.h> 44 #include <linux/fs.h> 45 #include <linux/init.h> 46 #include <linux/platform_device.h> 47 #include <linux/interrupt.h> 48 #include <linux/clk.h> 49 50 #include <asm/uaccess.h> 51 #include <asm/io.h> 52 53 #include <asm/arch/map.h> 54 55 #undef S3C_VA_WATCHDOG 56 #define S3C_VA_WATCHDOG (0) 57 58 #include <asm/plat-s3c/regs-watchdog.h> 59 60 #define PFX "s3c2410-wdt: " 61 62 #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) 63 #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) 64 65 static int nowayout = WATCHDOG_NOWAYOUT; 66 static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; 67 static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; 68 static int soft_noboot = 0; 69 static int debug = 0; 70 71 module_param(tmr_margin, int, 0); 72 module_param(tmr_atboot, int, 0); 73 module_param(nowayout, int, 0); 74 module_param(soft_noboot, int, 0); 75 module_param(debug, int, 0); 76 77 MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); 78 79 MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); 80 81 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 82 83 MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); 84 85 MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)"); 86 87 88 typedef enum close_state { 89 CLOSE_STATE_NOT, 90 CLOSE_STATE_ALLOW=0x4021 91 } close_state_t; 92 93 static DECLARE_MUTEX(open_lock); 94 95 static struct device *wdt_dev; /* platform device attached to */ 96 static struct resource *wdt_mem; 97 static struct resource *wdt_irq; 98 static struct clk *wdt_clock; 99 static void __iomem *wdt_base; 100 static unsigned int wdt_count; 101 static close_state_t allow_close; 102 103 /* watchdog control routines */ 104 105 #define DBG(msg...) do { \ 106 if (debug) \ 107 printk(KERN_INFO msg); \ 108 } while(0) 109 110 /* functions */ 111 112 static int s3c2410wdt_keepalive(void) 113 { 114 writel(wdt_count, wdt_base + S3C2410_WTCNT); 115 return 0; 116 } 117 118 static int s3c2410wdt_stop(void) 119 { 120 unsigned long wtcon; 121 122 wtcon = readl(wdt_base + S3C2410_WTCON); 123 wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); 124 writel(wtcon, wdt_base + S3C2410_WTCON); 125 126 return 0; 127 } 128 129 static int s3c2410wdt_start(void) 130 { 131 unsigned long wtcon; 132 133 s3c2410wdt_stop(); 134 135 wtcon = readl(wdt_base + S3C2410_WTCON); 136 wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; 137 138 if (soft_noboot) { 139 wtcon |= S3C2410_WTCON_INTEN; 140 wtcon &= ~S3C2410_WTCON_RSTEN; 141 } else { 142 wtcon &= ~S3C2410_WTCON_INTEN; 143 wtcon |= S3C2410_WTCON_RSTEN; 144 } 145 146 DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n", 147 __FUNCTION__, wdt_count, wtcon); 148 149 writel(wdt_count, wdt_base + S3C2410_WTDAT); 150 writel(wdt_count, wdt_base + S3C2410_WTCNT); 151 writel(wtcon, wdt_base + S3C2410_WTCON); 152 153 return 0; 154 } 155 156 static int s3c2410wdt_set_heartbeat(int timeout) 157 { 158 unsigned int freq = clk_get_rate(wdt_clock); 159 unsigned int count; 160 unsigned int divisor = 1; 161 unsigned long wtcon; 162 163 if (timeout < 1) 164 return -EINVAL; 165 166 freq /= 128; 167 count = timeout * freq; 168 169 DBG("%s: count=%d, timeout=%d, freq=%d\n", 170 __FUNCTION__, count, timeout, freq); 171 172 /* if the count is bigger than the watchdog register, 173 then work out what we need to do (and if) we can 174 actually make this value 175 */ 176 177 if (count >= 0x10000) { 178 for (divisor = 1; divisor <= 0x100; divisor++) { 179 if ((count / divisor) < 0x10000) 180 break; 181 } 182 183 if ((count / divisor) >= 0x10000) { 184 dev_err(wdt_dev, "timeout %d too big\n", timeout); 185 return -EINVAL; 186 } 187 } 188 189 tmr_margin = timeout; 190 191 DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", 192 __FUNCTION__, timeout, divisor, count, count/divisor); 193 194 count /= divisor; 195 wdt_count = count; 196 197 /* update the pre-scaler */ 198 wtcon = readl(wdt_base + S3C2410_WTCON); 199 wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; 200 wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); 201 202 writel(count, wdt_base + S3C2410_WTDAT); 203 writel(wtcon, wdt_base + S3C2410_WTCON); 204 205 return 0; 206 } 207 208 /* 209 * /dev/watchdog handling 210 */ 211 212 static int s3c2410wdt_open(struct inode *inode, struct file *file) 213 { 214 if(down_trylock(&open_lock)) 215 return -EBUSY; 216 217 if (nowayout) 218 __module_get(THIS_MODULE); 219 220 allow_close = CLOSE_STATE_NOT; 221 222 /* start the timer */ 223 s3c2410wdt_start(); 224 return nonseekable_open(inode, file); 225 } 226 227 static int s3c2410wdt_release(struct inode *inode, struct file *file) 228 { 229 /* 230 * Shut off the timer. 231 * Lock it in if it's a module and we set nowayout 232 */ 233 234 if (allow_close == CLOSE_STATE_ALLOW) { 235 s3c2410wdt_stop(); 236 } else { 237 dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n"); 238 s3c2410wdt_keepalive(); 239 } 240 241 allow_close = CLOSE_STATE_NOT; 242 up(&open_lock); 243 return 0; 244 } 245 246 static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, 247 size_t len, loff_t *ppos) 248 { 249 /* 250 * Refresh the timer. 251 */ 252 if(len) { 253 if (!nowayout) { 254 size_t i; 255 256 /* In case it was set long ago */ 257 allow_close = CLOSE_STATE_NOT; 258 259 for (i = 0; i != len; i++) { 260 char c; 261 262 if (get_user(c, data + i)) 263 return -EFAULT; 264 if (c == 'V') 265 allow_close = CLOSE_STATE_ALLOW; 266 } 267 } 268 269 s3c2410wdt_keepalive(); 270 } 271 return len; 272 } 273 274 #define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE 275 276 static struct watchdog_info s3c2410_wdt_ident = { 277 .options = OPTIONS, 278 .firmware_version = 0, 279 .identity = "S3C2410 Watchdog", 280 }; 281 282 283 static int s3c2410wdt_ioctl(struct inode *inode, struct file *file, 284 unsigned int cmd, unsigned long arg) 285 { 286 void __user *argp = (void __user *)arg; 287 int __user *p = argp; 288 int new_margin; 289 290 switch (cmd) { 291 default: 292 return -ENOTTY; 293 294 case WDIOC_GETSUPPORT: 295 return copy_to_user(argp, &s3c2410_wdt_ident, 296 sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0; 297 298 case WDIOC_GETSTATUS: 299 case WDIOC_GETBOOTSTATUS: 300 return put_user(0, p); 301 302 case WDIOC_KEEPALIVE: 303 s3c2410wdt_keepalive(); 304 return 0; 305 306 case WDIOC_SETTIMEOUT: 307 if (get_user(new_margin, p)) 308 return -EFAULT; 309 310 if (s3c2410wdt_set_heartbeat(new_margin)) 311 return -EINVAL; 312 313 s3c2410wdt_keepalive(); 314 return put_user(tmr_margin, p); 315 316 case WDIOC_GETTIMEOUT: 317 return put_user(tmr_margin, p); 318 } 319 } 320 321 /* kernel interface */ 322 323 static const struct file_operations s3c2410wdt_fops = { 324 .owner = THIS_MODULE, 325 .llseek = no_llseek, 326 .write = s3c2410wdt_write, 327 .ioctl = s3c2410wdt_ioctl, 328 .open = s3c2410wdt_open, 329 .release = s3c2410wdt_release, 330 }; 331 332 static struct miscdevice s3c2410wdt_miscdev = { 333 .minor = WATCHDOG_MINOR, 334 .name = "watchdog", 335 .fops = &s3c2410wdt_fops, 336 }; 337 338 /* interrupt handler code */ 339 340 static irqreturn_t s3c2410wdt_irq(int irqno, void *param) 341 { 342 dev_info(wdt_dev, "watchdog timer expired (irq)\n"); 343 344 s3c2410wdt_keepalive(); 345 return IRQ_HANDLED; 346 } 347 /* device interface */ 348 349 static int s3c2410wdt_probe(struct platform_device *pdev) 350 { 351 struct resource *res; 352 struct device *dev; 353 unsigned int wtcon; 354 int started = 0; 355 int ret; 356 int size; 357 358 DBG("%s: probe=%p\n", __FUNCTION__, pdev); 359 360 dev = &pdev->dev; 361 wdt_dev = &pdev->dev; 362 363 /* get the memory region for the watchdog timer */ 364 365 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 366 if (res == NULL) { 367 dev_err(dev, "no memory resource specified\n"); 368 return -ENOENT; 369 } 370 371 size = (res->end-res->start)+1; 372 wdt_mem = request_mem_region(res->start, size, pdev->name); 373 if (wdt_mem == NULL) { 374 dev_err(dev, "failed to get memory region\n"); 375 ret = -ENOENT; 376 goto err_req; 377 } 378 379 wdt_base = ioremap(res->start, size); 380 if (wdt_base == 0) { 381 dev_err(dev, "failed to ioremap() region\n"); 382 ret = -EINVAL; 383 goto err_req; 384 } 385 386 DBG("probe: mapped wdt_base=%p\n", wdt_base); 387 388 wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 389 if (wdt_irq == NULL) { 390 dev_err(dev, "no irq resource specified\n"); 391 ret = -ENOENT; 392 goto err_map; 393 } 394 395 ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev); 396 if (ret != 0) { 397 dev_err(dev, "failed to install irq (%d)\n", ret); 398 goto err_map; 399 } 400 401 wdt_clock = clk_get(&pdev->dev, "watchdog"); 402 if (IS_ERR(wdt_clock)) { 403 dev_err(dev, "failed to find watchdog clock source\n"); 404 ret = PTR_ERR(wdt_clock); 405 goto err_irq; 406 } 407 408 clk_enable(wdt_clock); 409 410 /* see if we can actually set the requested timer margin, and if 411 * not, try the default value */ 412 413 if (s3c2410wdt_set_heartbeat(tmr_margin)) { 414 started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); 415 416 if (started == 0) { 417 dev_info(dev,"tmr_margin value out of range, default %d used\n", 418 CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); 419 } else { 420 dev_info(dev, "default timer value is out of range, cannot start\n"); 421 } 422 } 423 424 ret = misc_register(&s3c2410wdt_miscdev); 425 if (ret) { 426 dev_err(dev, "cannot register miscdev on minor=%d (%d)\n", 427 WATCHDOG_MINOR, ret); 428 goto err_clk; 429 } 430 431 if (tmr_atboot && started == 0) { 432 dev_info(dev, "starting watchdog timer\n"); 433 s3c2410wdt_start(); 434 } else if (!tmr_atboot) { 435 /* if we're not enabling the watchdog, then ensure it is 436 * disabled if it has been left running from the bootloader 437 * or other source */ 438 439 s3c2410wdt_stop(); 440 } 441 442 /* print out a statement of readiness */ 443 444 wtcon = readl(wdt_base + S3C2410_WTCON); 445 446 dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n", 447 (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in", 448 (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis", 449 (wtcon & S3C2410_WTCON_INTEN) ? "" : "en"); 450 451 return 0; 452 453 err_clk: 454 clk_disable(wdt_clock); 455 clk_put(wdt_clock); 456 457 err_irq: 458 free_irq(wdt_irq->start, pdev); 459 460 err_map: 461 iounmap(wdt_base); 462 463 err_req: 464 release_resource(wdt_mem); 465 kfree(wdt_mem); 466 467 return ret; 468 } 469 470 static int s3c2410wdt_remove(struct platform_device *dev) 471 { 472 release_resource(wdt_mem); 473 kfree(wdt_mem); 474 wdt_mem = NULL; 475 476 free_irq(wdt_irq->start, dev); 477 wdt_irq = NULL; 478 479 clk_disable(wdt_clock); 480 clk_put(wdt_clock); 481 wdt_clock = NULL; 482 483 iounmap(wdt_base); 484 misc_deregister(&s3c2410wdt_miscdev); 485 return 0; 486 } 487 488 static void s3c2410wdt_shutdown(struct platform_device *dev) 489 { 490 s3c2410wdt_stop(); 491 } 492 493 #ifdef CONFIG_PM 494 495 static unsigned long wtcon_save; 496 static unsigned long wtdat_save; 497 498 static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state) 499 { 500 /* Save watchdog state, and turn it off. */ 501 wtcon_save = readl(wdt_base + S3C2410_WTCON); 502 wtdat_save = readl(wdt_base + S3C2410_WTDAT); 503 504 /* Note that WTCNT doesn't need to be saved. */ 505 s3c2410wdt_stop(); 506 507 return 0; 508 } 509 510 static int s3c2410wdt_resume(struct platform_device *dev) 511 { 512 /* Restore watchdog state. */ 513 514 writel(wtdat_save, wdt_base + S3C2410_WTDAT); 515 writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */ 516 writel(wtcon_save, wdt_base + S3C2410_WTCON); 517 518 printk(KERN_INFO PFX "watchdog %sabled\n", 519 (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); 520 521 return 0; 522 } 523 524 #else 525 #define s3c2410wdt_suspend NULL 526 #define s3c2410wdt_resume NULL 527 #endif /* CONFIG_PM */ 528 529 530 static struct platform_driver s3c2410wdt_driver = { 531 .probe = s3c2410wdt_probe, 532 .remove = s3c2410wdt_remove, 533 .shutdown = s3c2410wdt_shutdown, 534 .suspend = s3c2410wdt_suspend, 535 .resume = s3c2410wdt_resume, 536 .driver = { 537 .owner = THIS_MODULE, 538 .name = "s3c2410-wdt", 539 }, 540 }; 541 542 543 static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; 544 545 static int __init watchdog_init(void) 546 { 547 printk(banner); 548 return platform_driver_register(&s3c2410wdt_driver); 549 } 550 551 static void __exit watchdog_exit(void) 552 { 553 platform_driver_unregister(&s3c2410wdt_driver); 554 } 555 556 module_init(watchdog_init); 557 module_exit(watchdog_exit); 558 559 MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, " 560 "Dimitry Andric <dimitry.andric@tomtom.com>"); 561 MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); 562 MODULE_LICENSE("GPL"); 563 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 564