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