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