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