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