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@lxorguk.ukuu.org.uk> 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 26 #include <linux/module.h> 27 #include <linux/moduleparam.h> 28 #include <linux/types.h> 29 #include <linux/timer.h> 30 #include <linux/miscdevice.h> /* for MODULE_ALIAS_MISCDEV */ 31 #include <linux/watchdog.h> 32 #include <linux/init.h> 33 #include <linux/platform_device.h> 34 #include <linux/interrupt.h> 35 #include <linux/clk.h> 36 #include <linux/uaccess.h> 37 #include <linux/io.h> 38 #include <linux/cpufreq.h> 39 #include <linux/slab.h> 40 #include <linux/err.h> 41 42 #include <mach/map.h> 43 44 #undef S3C_VA_WATCHDOG 45 #define S3C_VA_WATCHDOG (0) 46 47 #include <plat/regs-watchdog.h> 48 49 #define PFX "s3c2410-wdt: " 50 51 #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) 52 #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) 53 54 static int nowayout = WATCHDOG_NOWAYOUT; 55 static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; 56 static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; 57 static int soft_noboot; 58 static int debug; 59 60 module_param(tmr_margin, int, 0); 61 module_param(tmr_atboot, int, 0); 62 module_param(nowayout, int, 0); 63 module_param(soft_noboot, int, 0); 64 module_param(debug, int, 0); 65 66 MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default=" 67 __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); 68 MODULE_PARM_DESC(tmr_atboot, 69 "Watchdog is started at boot time if set to 1, default=" 70 __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); 71 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 72 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 73 MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, " 74 "0 to reboot (default 0)"); 75 MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)"); 76 77 static struct device *wdt_dev; /* platform device attached to */ 78 static struct resource *wdt_mem; 79 static struct resource *wdt_irq; 80 static struct clk *wdt_clock; 81 static void __iomem *wdt_base; 82 static unsigned int wdt_count; 83 static DEFINE_SPINLOCK(wdt_lock); 84 85 /* watchdog control routines */ 86 87 #define DBG(msg...) do { \ 88 if (debug) \ 89 printk(KERN_INFO msg); \ 90 } while (0) 91 92 /* functions */ 93 94 static int s3c2410wdt_keepalive(struct watchdog_device *wdd) 95 { 96 spin_lock(&wdt_lock); 97 writel(wdt_count, wdt_base + S3C2410_WTCNT); 98 spin_unlock(&wdt_lock); 99 100 return 0; 101 } 102 103 static void __s3c2410wdt_stop(void) 104 { 105 unsigned long wtcon; 106 107 wtcon = readl(wdt_base + S3C2410_WTCON); 108 wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); 109 writel(wtcon, wdt_base + S3C2410_WTCON); 110 } 111 112 static int s3c2410wdt_stop(struct watchdog_device *wdd) 113 { 114 spin_lock(&wdt_lock); 115 __s3c2410wdt_stop(); 116 spin_unlock(&wdt_lock); 117 118 return 0; 119 } 120 121 static int s3c2410wdt_start(struct watchdog_device *wdd) 122 { 123 unsigned long wtcon; 124 125 spin_lock(&wdt_lock); 126 127 __s3c2410wdt_stop(); 128 129 wtcon = readl(wdt_base + S3C2410_WTCON); 130 wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; 131 132 if (soft_noboot) { 133 wtcon |= S3C2410_WTCON_INTEN; 134 wtcon &= ~S3C2410_WTCON_RSTEN; 135 } else { 136 wtcon &= ~S3C2410_WTCON_INTEN; 137 wtcon |= S3C2410_WTCON_RSTEN; 138 } 139 140 DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n", 141 __func__, wdt_count, wtcon); 142 143 writel(wdt_count, wdt_base + S3C2410_WTDAT); 144 writel(wdt_count, wdt_base + S3C2410_WTCNT); 145 writel(wtcon, wdt_base + S3C2410_WTCON); 146 spin_unlock(&wdt_lock); 147 148 return 0; 149 } 150 151 static inline int s3c2410wdt_is_running(void) 152 { 153 return readl(wdt_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE; 154 } 155 156 static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeout) 157 { 158 unsigned long 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=%lu\n", 170 __func__, 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 DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", 190 __func__, timeout, divisor, count, count/divisor); 191 192 count /= divisor; 193 wdt_count = count; 194 195 /* update the pre-scaler */ 196 wtcon = readl(wdt_base + S3C2410_WTCON); 197 wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; 198 wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); 199 200 writel(count, wdt_base + S3C2410_WTDAT); 201 writel(wtcon, wdt_base + S3C2410_WTCON); 202 203 return 0; 204 } 205 206 #define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) 207 208 static const struct watchdog_info s3c2410_wdt_ident = { 209 .options = OPTIONS, 210 .firmware_version = 0, 211 .identity = "S3C2410 Watchdog", 212 }; 213 214 static struct watchdog_ops s3c2410wdt_ops = { 215 .owner = THIS_MODULE, 216 .start = s3c2410wdt_start, 217 .stop = s3c2410wdt_stop, 218 .ping = s3c2410wdt_keepalive, 219 .set_timeout = s3c2410wdt_set_heartbeat, 220 }; 221 222 static struct watchdog_device s3c2410_wdd = { 223 .info = &s3c2410_wdt_ident, 224 .ops = &s3c2410wdt_ops, 225 }; 226 227 /* interrupt handler code */ 228 229 static irqreturn_t s3c2410wdt_irq(int irqno, void *param) 230 { 231 dev_info(wdt_dev, "watchdog timer expired (irq)\n"); 232 233 s3c2410wdt_keepalive(&s3c2410_wdd); 234 return IRQ_HANDLED; 235 } 236 237 238 #ifdef CONFIG_CPU_FREQ 239 240 static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb, 241 unsigned long val, void *data) 242 { 243 int ret; 244 245 if (!s3c2410wdt_is_running()) 246 goto done; 247 248 if (val == CPUFREQ_PRECHANGE) { 249 /* To ensure that over the change we don't cause the 250 * watchdog to trigger, we perform an keep-alive if 251 * the watchdog is running. 252 */ 253 254 s3c2410wdt_keepalive(&s3c2410_wdd); 255 } else if (val == CPUFREQ_POSTCHANGE) { 256 s3c2410wdt_stop(&s3c2410_wdd); 257 258 ret = s3c2410wdt_set_heartbeat(&s3c2410_wdd, s3c2410_wdd.timeout); 259 260 if (ret >= 0) 261 s3c2410wdt_start(&s3c2410_wdd); 262 else 263 goto err; 264 } 265 266 done: 267 return 0; 268 269 err: 270 dev_err(wdt_dev, "cannot set new value for timeout %d\n", 271 s3c2410_wdd.timeout); 272 return ret; 273 } 274 275 static struct notifier_block s3c2410wdt_cpufreq_transition_nb = { 276 .notifier_call = s3c2410wdt_cpufreq_transition, 277 }; 278 279 static inline int s3c2410wdt_cpufreq_register(void) 280 { 281 return cpufreq_register_notifier(&s3c2410wdt_cpufreq_transition_nb, 282 CPUFREQ_TRANSITION_NOTIFIER); 283 } 284 285 static inline void s3c2410wdt_cpufreq_deregister(void) 286 { 287 cpufreq_unregister_notifier(&s3c2410wdt_cpufreq_transition_nb, 288 CPUFREQ_TRANSITION_NOTIFIER); 289 } 290 291 #else 292 static inline int s3c2410wdt_cpufreq_register(void) 293 { 294 return 0; 295 } 296 297 static inline void s3c2410wdt_cpufreq_deregister(void) 298 { 299 } 300 #endif 301 302 static int __devinit s3c2410wdt_probe(struct platform_device *pdev) 303 { 304 struct device *dev; 305 unsigned int wtcon; 306 int started = 0; 307 int ret; 308 int size; 309 310 DBG("%s: probe=%p\n", __func__, pdev); 311 312 dev = &pdev->dev; 313 wdt_dev = &pdev->dev; 314 315 /* get the memory region for the watchdog timer */ 316 317 wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 318 if (wdt_mem == NULL) { 319 dev_err(dev, "no memory resource specified\n"); 320 return -ENOENT; 321 } 322 323 size = resource_size(wdt_mem); 324 if (!request_mem_region(wdt_mem->start, size, pdev->name)) { 325 dev_err(dev, "failed to get memory region\n"); 326 return -EBUSY; 327 } 328 329 wdt_base = ioremap(wdt_mem->start, size); 330 if (wdt_base == NULL) { 331 dev_err(dev, "failed to ioremap() region\n"); 332 ret = -EINVAL; 333 goto err_req; 334 } 335 336 DBG("probe: mapped wdt_base=%p\n", wdt_base); 337 338 wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 339 if (wdt_irq == NULL) { 340 dev_err(dev, "no irq resource specified\n"); 341 ret = -ENOENT; 342 goto err_map; 343 } 344 345 ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev); 346 if (ret != 0) { 347 dev_err(dev, "failed to install irq (%d)\n", ret); 348 goto err_map; 349 } 350 351 wdt_clock = clk_get(&pdev->dev, "watchdog"); 352 if (IS_ERR(wdt_clock)) { 353 dev_err(dev, "failed to find watchdog clock source\n"); 354 ret = PTR_ERR(wdt_clock); 355 goto err_irq; 356 } 357 358 clk_enable(wdt_clock); 359 360 if (s3c2410wdt_cpufreq_register() < 0) { 361 printk(KERN_ERR PFX "failed to register cpufreq\n"); 362 goto err_clk; 363 } 364 365 /* see if we can actually set the requested timer margin, and if 366 * not, try the default value */ 367 368 if (s3c2410wdt_set_heartbeat(&s3c2410_wdd, tmr_margin)) { 369 started = s3c2410wdt_set_heartbeat(&s3c2410_wdd, 370 CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); 371 372 if (started == 0) 373 dev_info(dev, 374 "tmr_margin value out of range, default %d used\n", 375 CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); 376 else 377 dev_info(dev, "default timer value is out of range, " 378 "cannot start\n"); 379 } 380 381 watchdog_set_nowayout(&s3c2410_wdd, nowayout); 382 383 ret = watchdog_register_device(&s3c2410_wdd); 384 if (ret) { 385 dev_err(dev, "cannot register watchdog (%d)\n", ret); 386 goto err_cpufreq; 387 } 388 389 if (tmr_atboot && started == 0) { 390 dev_info(dev, "starting watchdog timer\n"); 391 s3c2410wdt_start(&s3c2410_wdd); 392 } else if (!tmr_atboot) { 393 /* if we're not enabling the watchdog, then ensure it is 394 * disabled if it has been left running from the bootloader 395 * or other source */ 396 397 s3c2410wdt_stop(&s3c2410_wdd); 398 } 399 400 /* print out a statement of readiness */ 401 402 wtcon = readl(wdt_base + S3C2410_WTCON); 403 404 dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n", 405 (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in", 406 (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis", 407 (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis"); 408 409 return 0; 410 411 err_cpufreq: 412 s3c2410wdt_cpufreq_deregister(); 413 414 err_clk: 415 clk_disable(wdt_clock); 416 clk_put(wdt_clock); 417 418 err_irq: 419 free_irq(wdt_irq->start, pdev); 420 421 err_map: 422 iounmap(wdt_base); 423 424 err_req: 425 release_mem_region(wdt_mem->start, size); 426 wdt_mem = NULL; 427 428 return ret; 429 } 430 431 static int __devexit s3c2410wdt_remove(struct platform_device *dev) 432 { 433 watchdog_unregister_device(&s3c2410_wdd); 434 435 s3c2410wdt_cpufreq_deregister(); 436 437 clk_disable(wdt_clock); 438 clk_put(wdt_clock); 439 wdt_clock = NULL; 440 441 free_irq(wdt_irq->start, dev); 442 wdt_irq = NULL; 443 444 iounmap(wdt_base); 445 446 release_mem_region(wdt_mem->start, resource_size(wdt_mem)); 447 wdt_mem = NULL; 448 return 0; 449 } 450 451 static void s3c2410wdt_shutdown(struct platform_device *dev) 452 { 453 s3c2410wdt_stop(&s3c2410_wdd); 454 } 455 456 #ifdef CONFIG_PM 457 458 static unsigned long wtcon_save; 459 static unsigned long wtdat_save; 460 461 static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state) 462 { 463 /* Save watchdog state, and turn it off. */ 464 wtcon_save = readl(wdt_base + S3C2410_WTCON); 465 wtdat_save = readl(wdt_base + S3C2410_WTDAT); 466 467 /* Note that WTCNT doesn't need to be saved. */ 468 s3c2410wdt_stop(&s3c2410_wdd); 469 470 return 0; 471 } 472 473 static int s3c2410wdt_resume(struct platform_device *dev) 474 { 475 /* Restore watchdog state. */ 476 477 writel(wtdat_save, wdt_base + S3C2410_WTDAT); 478 writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */ 479 writel(wtcon_save, wdt_base + S3C2410_WTCON); 480 481 printk(KERN_INFO PFX "watchdog %sabled\n", 482 (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); 483 484 return 0; 485 } 486 487 #else 488 #define s3c2410wdt_suspend NULL 489 #define s3c2410wdt_resume NULL 490 #endif /* CONFIG_PM */ 491 492 #ifdef CONFIG_OF 493 static const struct of_device_id s3c2410_wdt_match[] = { 494 { .compatible = "samsung,s3c2410-wdt" }, 495 {}, 496 }; 497 MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); 498 #else 499 #define s3c2410_wdt_match NULL 500 #endif 501 502 static struct platform_driver s3c2410wdt_driver = { 503 .probe = s3c2410wdt_probe, 504 .remove = __devexit_p(s3c2410wdt_remove), 505 .shutdown = s3c2410wdt_shutdown, 506 .suspend = s3c2410wdt_suspend, 507 .resume = s3c2410wdt_resume, 508 .driver = { 509 .owner = THIS_MODULE, 510 .name = "s3c2410-wdt", 511 .of_match_table = s3c2410_wdt_match, 512 }, 513 }; 514 515 516 static char banner[] __initdata = 517 KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; 518 519 static int __init watchdog_init(void) 520 { 521 printk(banner); 522 return platform_driver_register(&s3c2410wdt_driver); 523 } 524 525 static void __exit watchdog_exit(void) 526 { 527 platform_driver_unregister(&s3c2410wdt_driver); 528 } 529 530 module_init(watchdog_init); 531 module_exit(watchdog_exit); 532 533 MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, " 534 "Dimitry Andric <dimitry.andric@tomtom.com>"); 535 MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); 536 MODULE_LICENSE("GPL"); 537 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 538 MODULE_ALIAS("platform:s3c2410-wdt"); 539