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/watchdog.h> 33 #include <linux/init.h> 34 #include <linux/platform_device.h> 35 #include <linux/interrupt.h> 36 #include <linux/clk.h> 37 #include <linux/uaccess.h> 38 #include <linux/io.h> 39 #include <linux/cpufreq.h> 40 #include <linux/slab.h> 41 #include <linux/err.h> 42 #include <linux/of.h> 43 #include <linux/mfd/syscon.h> 44 #include <linux/regmap.h> 45 46 #define S3C2410_WTCON 0x00 47 #define S3C2410_WTDAT 0x04 48 #define S3C2410_WTCNT 0x08 49 50 #define S3C2410_WTCON_RSTEN (1 << 0) 51 #define S3C2410_WTCON_INTEN (1 << 2) 52 #define S3C2410_WTCON_ENABLE (1 << 5) 53 54 #define S3C2410_WTCON_DIV16 (0 << 3) 55 #define S3C2410_WTCON_DIV32 (1 << 3) 56 #define S3C2410_WTCON_DIV64 (2 << 3) 57 #define S3C2410_WTCON_DIV128 (3 << 3) 58 59 #define S3C2410_WTCON_PRESCALE(x) ((x) << 8) 60 #define S3C2410_WTCON_PRESCALE_MASK (0xff << 8) 61 62 #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) 63 #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) 64 65 #define EXYNOS5_RST_STAT_REG_OFFSET 0x0404 66 #define EXYNOS5_WDT_DISABLE_REG_OFFSET 0x0408 67 #define EXYNOS5_WDT_MASK_RESET_REG_OFFSET 0x040c 68 #define QUIRK_HAS_PMU_CONFIG (1 << 0) 69 #define QUIRK_HAS_RST_STAT (1 << 1) 70 71 /* These quirks require that we have a PMU register map */ 72 #define QUIRKS_HAVE_PMUREG (QUIRK_HAS_PMU_CONFIG | \ 73 QUIRK_HAS_RST_STAT) 74 75 static bool nowayout = WATCHDOG_NOWAYOUT; 76 static int tmr_margin; 77 static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; 78 static int soft_noboot; 79 static int debug; 80 81 module_param(tmr_margin, int, 0); 82 module_param(tmr_atboot, int, 0); 83 module_param(nowayout, bool, 0); 84 module_param(soft_noboot, int, 0); 85 module_param(debug, int, 0); 86 87 MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default=" 88 __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); 89 MODULE_PARM_DESC(tmr_atboot, 90 "Watchdog is started at boot time if set to 1, default=" 91 __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); 92 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 93 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 94 MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, " 95 "0 to reboot (default 0)"); 96 MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)"); 97 98 /** 99 * struct s3c2410_wdt_variant - Per-variant config data 100 * 101 * @disable_reg: Offset in pmureg for the register that disables the watchdog 102 * timer reset functionality. 103 * @mask_reset_reg: Offset in pmureg for the register that masks the watchdog 104 * timer reset functionality. 105 * @mask_bit: Bit number for the watchdog timer in the disable register and the 106 * mask reset register. 107 * @rst_stat_reg: Offset in pmureg for the register that has the reset status. 108 * @rst_stat_bit: Bit number in the rst_stat register indicating a watchdog 109 * reset. 110 * @quirks: A bitfield of quirks. 111 */ 112 113 struct s3c2410_wdt_variant { 114 int disable_reg; 115 int mask_reset_reg; 116 int mask_bit; 117 int rst_stat_reg; 118 int rst_stat_bit; 119 u32 quirks; 120 }; 121 122 struct s3c2410_wdt { 123 struct device *dev; 124 struct clk *clock; 125 void __iomem *reg_base; 126 unsigned int count; 127 spinlock_t lock; 128 unsigned long wtcon_save; 129 unsigned long wtdat_save; 130 struct watchdog_device wdt_device; 131 struct notifier_block freq_transition; 132 struct s3c2410_wdt_variant *drv_data; 133 struct regmap *pmureg; 134 }; 135 136 static const struct s3c2410_wdt_variant drv_data_s3c2410 = { 137 .quirks = 0 138 }; 139 140 #ifdef CONFIG_OF 141 static const struct s3c2410_wdt_variant drv_data_exynos5250 = { 142 .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET, 143 .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET, 144 .mask_bit = 20, 145 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, 146 .rst_stat_bit = 20, 147 .quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT, 148 }; 149 150 static const struct s3c2410_wdt_variant drv_data_exynos5420 = { 151 .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET, 152 .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET, 153 .mask_bit = 0, 154 .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET, 155 .rst_stat_bit = 9, 156 .quirks = QUIRK_HAS_PMU_CONFIG | QUIRK_HAS_RST_STAT, 157 }; 158 159 static const struct of_device_id s3c2410_wdt_match[] = { 160 { .compatible = "samsung,s3c2410-wdt", 161 .data = &drv_data_s3c2410 }, 162 { .compatible = "samsung,exynos5250-wdt", 163 .data = &drv_data_exynos5250 }, 164 { .compatible = "samsung,exynos5420-wdt", 165 .data = &drv_data_exynos5420 }, 166 {}, 167 }; 168 MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); 169 #endif 170 171 static const struct platform_device_id s3c2410_wdt_ids[] = { 172 { 173 .name = "s3c2410-wdt", 174 .driver_data = (unsigned long)&drv_data_s3c2410, 175 }, 176 {} 177 }; 178 MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids); 179 180 /* watchdog control routines */ 181 182 #define DBG(fmt, ...) \ 183 do { \ 184 if (debug) \ 185 pr_info(fmt, ##__VA_ARGS__); \ 186 } while (0) 187 188 /* functions */ 189 190 static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb) 191 { 192 return container_of(nb, struct s3c2410_wdt, freq_transition); 193 } 194 195 static int s3c2410wdt_mask_and_disable_reset(struct s3c2410_wdt *wdt, bool mask) 196 { 197 int ret; 198 u32 mask_val = 1 << wdt->drv_data->mask_bit; 199 u32 val = 0; 200 201 /* No need to do anything if no PMU CONFIG needed */ 202 if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG)) 203 return 0; 204 205 if (mask) 206 val = mask_val; 207 208 ret = regmap_update_bits(wdt->pmureg, 209 wdt->drv_data->disable_reg, 210 mask_val, val); 211 if (ret < 0) 212 goto error; 213 214 ret = regmap_update_bits(wdt->pmureg, 215 wdt->drv_data->mask_reset_reg, 216 mask_val, val); 217 error: 218 if (ret < 0) 219 dev_err(wdt->dev, "failed to update reg(%d)\n", ret); 220 221 return ret; 222 } 223 224 static int s3c2410wdt_keepalive(struct watchdog_device *wdd) 225 { 226 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 227 228 spin_lock(&wdt->lock); 229 writel(wdt->count, wdt->reg_base + S3C2410_WTCNT); 230 spin_unlock(&wdt->lock); 231 232 return 0; 233 } 234 235 static void __s3c2410wdt_stop(struct s3c2410_wdt *wdt) 236 { 237 unsigned long wtcon; 238 239 wtcon = readl(wdt->reg_base + S3C2410_WTCON); 240 wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); 241 writel(wtcon, wdt->reg_base + S3C2410_WTCON); 242 } 243 244 static int s3c2410wdt_stop(struct watchdog_device *wdd) 245 { 246 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 247 248 spin_lock(&wdt->lock); 249 __s3c2410wdt_stop(wdt); 250 spin_unlock(&wdt->lock); 251 252 return 0; 253 } 254 255 static int s3c2410wdt_start(struct watchdog_device *wdd) 256 { 257 unsigned long wtcon; 258 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 259 260 spin_lock(&wdt->lock); 261 262 __s3c2410wdt_stop(wdt); 263 264 wtcon = readl(wdt->reg_base + S3C2410_WTCON); 265 wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; 266 267 if (soft_noboot) { 268 wtcon |= S3C2410_WTCON_INTEN; 269 wtcon &= ~S3C2410_WTCON_RSTEN; 270 } else { 271 wtcon &= ~S3C2410_WTCON_INTEN; 272 wtcon |= S3C2410_WTCON_RSTEN; 273 } 274 275 DBG("%s: count=0x%08x, wtcon=%08lx\n", 276 __func__, wdt->count, wtcon); 277 278 writel(wdt->count, wdt->reg_base + S3C2410_WTDAT); 279 writel(wdt->count, wdt->reg_base + S3C2410_WTCNT); 280 writel(wtcon, wdt->reg_base + S3C2410_WTCON); 281 spin_unlock(&wdt->lock); 282 283 return 0; 284 } 285 286 static inline int s3c2410wdt_is_running(struct s3c2410_wdt *wdt) 287 { 288 return readl(wdt->reg_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE; 289 } 290 291 static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeout) 292 { 293 struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); 294 unsigned long freq = clk_get_rate(wdt->clock); 295 unsigned int count; 296 unsigned int divisor = 1; 297 unsigned long wtcon; 298 299 if (timeout < 1) 300 return -EINVAL; 301 302 freq = DIV_ROUND_UP(freq, 128); 303 count = timeout * freq; 304 305 DBG("%s: count=%d, timeout=%d, freq=%lu\n", 306 __func__, count, timeout, freq); 307 308 /* if the count is bigger than the watchdog register, 309 then work out what we need to do (and if) we can 310 actually make this value 311 */ 312 313 if (count >= 0x10000) { 314 divisor = DIV_ROUND_UP(count, 0xffff); 315 316 if (divisor > 0x100) { 317 dev_err(wdt->dev, "timeout %d too big\n", timeout); 318 return -EINVAL; 319 } 320 } 321 322 DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", 323 __func__, timeout, divisor, count, DIV_ROUND_UP(count, divisor)); 324 325 count = DIV_ROUND_UP(count, divisor); 326 wdt->count = count; 327 328 /* update the pre-scaler */ 329 wtcon = readl(wdt->reg_base + S3C2410_WTCON); 330 wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; 331 wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); 332 333 writel(count, wdt->reg_base + S3C2410_WTDAT); 334 writel(wtcon, wdt->reg_base + S3C2410_WTCON); 335 336 wdd->timeout = (count * divisor) / freq; 337 338 return 0; 339 } 340 341 #define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) 342 343 static const struct watchdog_info s3c2410_wdt_ident = { 344 .options = OPTIONS, 345 .firmware_version = 0, 346 .identity = "S3C2410 Watchdog", 347 }; 348 349 static struct watchdog_ops s3c2410wdt_ops = { 350 .owner = THIS_MODULE, 351 .start = s3c2410wdt_start, 352 .stop = s3c2410wdt_stop, 353 .ping = s3c2410wdt_keepalive, 354 .set_timeout = s3c2410wdt_set_heartbeat, 355 }; 356 357 static struct watchdog_device s3c2410_wdd = { 358 .info = &s3c2410_wdt_ident, 359 .ops = &s3c2410wdt_ops, 360 .timeout = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME, 361 }; 362 363 /* interrupt handler code */ 364 365 static irqreturn_t s3c2410wdt_irq(int irqno, void *param) 366 { 367 struct s3c2410_wdt *wdt = platform_get_drvdata(param); 368 369 dev_info(wdt->dev, "watchdog timer expired (irq)\n"); 370 371 s3c2410wdt_keepalive(&wdt->wdt_device); 372 return IRQ_HANDLED; 373 } 374 375 #ifdef CONFIG_ARM_S3C24XX_CPUFREQ 376 377 static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb, 378 unsigned long val, void *data) 379 { 380 int ret; 381 struct s3c2410_wdt *wdt = freq_to_wdt(nb); 382 383 if (!s3c2410wdt_is_running(wdt)) 384 goto done; 385 386 if (val == CPUFREQ_PRECHANGE) { 387 /* To ensure that over the change we don't cause the 388 * watchdog to trigger, we perform an keep-alive if 389 * the watchdog is running. 390 */ 391 392 s3c2410wdt_keepalive(&wdt->wdt_device); 393 } else if (val == CPUFREQ_POSTCHANGE) { 394 s3c2410wdt_stop(&wdt->wdt_device); 395 396 ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device, 397 wdt->wdt_device.timeout); 398 399 if (ret >= 0) 400 s3c2410wdt_start(&wdt->wdt_device); 401 else 402 goto err; 403 } 404 405 done: 406 return 0; 407 408 err: 409 dev_err(wdt->dev, "cannot set new value for timeout %d\n", 410 wdt->wdt_device.timeout); 411 return ret; 412 } 413 414 static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt) 415 { 416 wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition; 417 418 return cpufreq_register_notifier(&wdt->freq_transition, 419 CPUFREQ_TRANSITION_NOTIFIER); 420 } 421 422 static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt) 423 { 424 wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition; 425 426 cpufreq_unregister_notifier(&wdt->freq_transition, 427 CPUFREQ_TRANSITION_NOTIFIER); 428 } 429 430 #else 431 432 static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt) 433 { 434 return 0; 435 } 436 437 static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt) 438 { 439 } 440 #endif 441 442 static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt) 443 { 444 unsigned int rst_stat; 445 int ret; 446 447 if (!(wdt->drv_data->quirks & QUIRK_HAS_RST_STAT)) 448 return 0; 449 450 ret = regmap_read(wdt->pmureg, wdt->drv_data->rst_stat_reg, &rst_stat); 451 if (ret) 452 dev_warn(wdt->dev, "Couldn't get RST_STAT register\n"); 453 else if (rst_stat & BIT(wdt->drv_data->rst_stat_bit)) 454 return WDIOF_CARDRESET; 455 456 return 0; 457 } 458 459 /* s3c2410_get_wdt_driver_data */ 460 static inline struct s3c2410_wdt_variant * 461 get_wdt_drv_data(struct platform_device *pdev) 462 { 463 if (pdev->dev.of_node) { 464 const struct of_device_id *match; 465 match = of_match_node(s3c2410_wdt_match, pdev->dev.of_node); 466 return (struct s3c2410_wdt_variant *)match->data; 467 } else { 468 return (struct s3c2410_wdt_variant *) 469 platform_get_device_id(pdev)->driver_data; 470 } 471 } 472 473 static int s3c2410wdt_probe(struct platform_device *pdev) 474 { 475 struct device *dev; 476 struct s3c2410_wdt *wdt; 477 struct resource *wdt_mem; 478 struct resource *wdt_irq; 479 unsigned int wtcon; 480 int started = 0; 481 int ret; 482 483 DBG("%s: probe=%p\n", __func__, pdev); 484 485 dev = &pdev->dev; 486 487 wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 488 if (!wdt) 489 return -ENOMEM; 490 491 wdt->dev = &pdev->dev; 492 spin_lock_init(&wdt->lock); 493 wdt->wdt_device = s3c2410_wdd; 494 495 wdt->drv_data = get_wdt_drv_data(pdev); 496 if (wdt->drv_data->quirks & QUIRKS_HAVE_PMUREG) { 497 wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, 498 "samsung,syscon-phandle"); 499 if (IS_ERR(wdt->pmureg)) { 500 dev_err(dev, "syscon regmap lookup failed.\n"); 501 return PTR_ERR(wdt->pmureg); 502 } 503 } 504 505 wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 506 if (wdt_irq == NULL) { 507 dev_err(dev, "no irq resource specified\n"); 508 ret = -ENOENT; 509 goto err; 510 } 511 512 /* get the memory region for the watchdog timer */ 513 wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 514 wdt->reg_base = devm_ioremap_resource(dev, wdt_mem); 515 if (IS_ERR(wdt->reg_base)) { 516 ret = PTR_ERR(wdt->reg_base); 517 goto err; 518 } 519 520 DBG("probe: mapped reg_base=%p\n", wdt->reg_base); 521 522 wdt->clock = devm_clk_get(dev, "watchdog"); 523 if (IS_ERR(wdt->clock)) { 524 dev_err(dev, "failed to find watchdog clock source\n"); 525 ret = PTR_ERR(wdt->clock); 526 goto err; 527 } 528 529 clk_prepare_enable(wdt->clock); 530 531 ret = s3c2410wdt_cpufreq_register(wdt); 532 if (ret < 0) { 533 dev_err(dev, "failed to register cpufreq\n"); 534 goto err_clk; 535 } 536 537 watchdog_set_drvdata(&wdt->wdt_device, wdt); 538 539 /* see if we can actually set the requested timer margin, and if 540 * not, try the default value */ 541 542 watchdog_init_timeout(&wdt->wdt_device, tmr_margin, &pdev->dev); 543 ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device, 544 wdt->wdt_device.timeout); 545 if (ret) { 546 started = s3c2410wdt_set_heartbeat(&wdt->wdt_device, 547 CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); 548 549 if (started == 0) 550 dev_info(dev, 551 "tmr_margin value out of range, default %d used\n", 552 CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); 553 else 554 dev_info(dev, "default timer value is out of range, " 555 "cannot start\n"); 556 } 557 558 ret = devm_request_irq(dev, wdt_irq->start, s3c2410wdt_irq, 0, 559 pdev->name, pdev); 560 if (ret != 0) { 561 dev_err(dev, "failed to install irq (%d)\n", ret); 562 goto err_cpufreq; 563 } 564 565 watchdog_set_nowayout(&wdt->wdt_device, nowayout); 566 567 wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt); 568 569 ret = watchdog_register_device(&wdt->wdt_device); 570 if (ret) { 571 dev_err(dev, "cannot register watchdog (%d)\n", ret); 572 goto err_cpufreq; 573 } 574 575 ret = s3c2410wdt_mask_and_disable_reset(wdt, false); 576 if (ret < 0) 577 goto err_unregister; 578 579 if (tmr_atboot && started == 0) { 580 dev_info(dev, "starting watchdog timer\n"); 581 s3c2410wdt_start(&wdt->wdt_device); 582 } else if (!tmr_atboot) { 583 /* if we're not enabling the watchdog, then ensure it is 584 * disabled if it has been left running from the bootloader 585 * or other source */ 586 587 s3c2410wdt_stop(&wdt->wdt_device); 588 } 589 590 platform_set_drvdata(pdev, wdt); 591 592 /* print out a statement of readiness */ 593 594 wtcon = readl(wdt->reg_base + S3C2410_WTCON); 595 596 dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n", 597 (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in", 598 (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis", 599 (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis"); 600 601 return 0; 602 603 err_unregister: 604 watchdog_unregister_device(&wdt->wdt_device); 605 606 err_cpufreq: 607 s3c2410wdt_cpufreq_deregister(wdt); 608 609 err_clk: 610 clk_disable_unprepare(wdt->clock); 611 wdt->clock = NULL; 612 613 err: 614 return ret; 615 } 616 617 static int s3c2410wdt_remove(struct platform_device *dev) 618 { 619 int ret; 620 struct s3c2410_wdt *wdt = platform_get_drvdata(dev); 621 622 ret = s3c2410wdt_mask_and_disable_reset(wdt, true); 623 if (ret < 0) 624 return ret; 625 626 watchdog_unregister_device(&wdt->wdt_device); 627 628 s3c2410wdt_cpufreq_deregister(wdt); 629 630 clk_disable_unprepare(wdt->clock); 631 wdt->clock = NULL; 632 633 return 0; 634 } 635 636 static void s3c2410wdt_shutdown(struct platform_device *dev) 637 { 638 struct s3c2410_wdt *wdt = platform_get_drvdata(dev); 639 640 s3c2410wdt_mask_and_disable_reset(wdt, true); 641 642 s3c2410wdt_stop(&wdt->wdt_device); 643 } 644 645 #ifdef CONFIG_PM_SLEEP 646 647 static int s3c2410wdt_suspend(struct device *dev) 648 { 649 int ret; 650 struct s3c2410_wdt *wdt = dev_get_drvdata(dev); 651 652 /* Save watchdog state, and turn it off. */ 653 wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON); 654 wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT); 655 656 ret = s3c2410wdt_mask_and_disable_reset(wdt, true); 657 if (ret < 0) 658 return ret; 659 660 /* Note that WTCNT doesn't need to be saved. */ 661 s3c2410wdt_stop(&wdt->wdt_device); 662 663 return 0; 664 } 665 666 static int s3c2410wdt_resume(struct device *dev) 667 { 668 int ret; 669 struct s3c2410_wdt *wdt = dev_get_drvdata(dev); 670 671 /* Restore watchdog state. */ 672 writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTDAT); 673 writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */ 674 writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON); 675 676 ret = s3c2410wdt_mask_and_disable_reset(wdt, false); 677 if (ret < 0) 678 return ret; 679 680 dev_info(dev, "watchdog %sabled\n", 681 (wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); 682 683 return 0; 684 } 685 #endif 686 687 static SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops, s3c2410wdt_suspend, 688 s3c2410wdt_resume); 689 690 static struct platform_driver s3c2410wdt_driver = { 691 .probe = s3c2410wdt_probe, 692 .remove = s3c2410wdt_remove, 693 .shutdown = s3c2410wdt_shutdown, 694 .id_table = s3c2410_wdt_ids, 695 .driver = { 696 .owner = THIS_MODULE, 697 .name = "s3c2410-wdt", 698 .pm = &s3c2410wdt_pm_ops, 699 .of_match_table = of_match_ptr(s3c2410_wdt_match), 700 }, 701 }; 702 703 module_platform_driver(s3c2410wdt_driver); 704 705 MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, " 706 "Dimitry Andric <dimitry.andric@tomtom.com>"); 707 MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); 708 MODULE_LICENSE("GPL"); 709