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#include <linux/module.h>
  27#include <linux/moduleparam.h>
  28#include <linux/types.h>
  29#include <linux/timer.h>
  30#include <linux/miscdevice.h>
  31#include <linux/watchdog.h>
  32#include <linux/fs.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
  40#include <mach/map.h>
  41
  42#undef S3C_VA_WATCHDOG
  43#define S3C_VA_WATCHDOG (0)
  44
  45#include <plat/regs-watchdog.h>
  46
  47#define PFX "s3c2410-wdt: "
  48
  49#define CONFIG_S3C2410_WATCHDOG_ATBOOT          (0)
  50#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME    (15)
  51
  52static int nowayout     = WATCHDOG_NOWAYOUT;
  53static int tmr_margin   = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;
  54static int tmr_atboot   = CONFIG_S3C2410_WATCHDOG_ATBOOT;
  55static int soft_noboot;
  56static int debug;
  57
  58module_param(tmr_margin,  int, 0);
  59module_param(tmr_atboot,  int, 0);
  60module_param(nowayout,    int, 0);
  61module_param(soft_noboot, int, 0);
  62module_param(debug,       int, 0);
  63
  64MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default="
  65                __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")");
  66MODULE_PARM_DESC(tmr_atboot,
  67                "Watchdog is started at boot time if set to 1, default="
  68                        __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT));
  69MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  70                        __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  71MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, "
  72                        "0 to reboot (default depends on ONLY_TESTING)");
  73MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)");
  74
  75static unsigned long open_lock;
  76static struct device    *wdt_dev;       /* platform device attached to */
  77static struct resource  *wdt_mem;
  78static struct resource  *wdt_irq;
  79static struct clk       *wdt_clock;
  80static void __iomem     *wdt_base;
  81static unsigned int      wdt_count;
  82static char              expect_close;
  83static DEFINE_SPINLOCK(wdt_lock);
  84
  85/* watchdog control routines */
  86
  87#define DBG(msg...) do { \
  88        if (debug) \
  89                printk(KERN_INFO msg); \
  90        } while (0)
  91
  92/* functions */
  93
  94static void s3c2410wdt_keepalive(void)
  95{
  96        spin_lock(&wdt_lock);
  97        writel(wdt_count, wdt_base + S3C2410_WTCNT);
  98        spin_unlock(&wdt_lock);
  99}
 100
 101static void __s3c2410wdt_stop(void)
 102{
 103        unsigned long wtcon;
 104
 105        wtcon = readl(wdt_base + S3C2410_WTCON);
 106        wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
 107        writel(wtcon, wdt_base + S3C2410_WTCON);
 108}
 109
 110static void s3c2410wdt_stop(void)
 111{
 112        spin_lock(&wdt_lock);
 113        __s3c2410wdt_stop();
 114        spin_unlock(&wdt_lock);
 115}
 116
 117static void s3c2410wdt_start(void)
 118{
 119        unsigned long wtcon;
 120
 121        spin_lock(&wdt_lock);
 122
 123        __s3c2410wdt_stop();
 124
 125        wtcon = readl(wdt_base + S3C2410_WTCON);
 126        wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
 127
 128        if (soft_noboot) {
 129                wtcon |= S3C2410_WTCON_INTEN;
 130                wtcon &= ~S3C2410_WTCON_RSTEN;
 131        } else {
 132                wtcon &= ~S3C2410_WTCON_INTEN;
 133                wtcon |= S3C2410_WTCON_RSTEN;
 134        }
 135
 136        DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
 137            __func__, wdt_count, wtcon);
 138
 139        writel(wdt_count, wdt_base + S3C2410_WTDAT);
 140        writel(wdt_count, wdt_base + S3C2410_WTCNT);
 141        writel(wtcon, wdt_base + S3C2410_WTCON);
 142        spin_unlock(&wdt_lock);
 143}
 144
 145static int s3c2410wdt_set_heartbeat(int timeout)
 146{
 147        unsigned int freq = clk_get_rate(wdt_clock);
 148        unsigned int count;
 149        unsigned int divisor = 1;
 150        unsigned long wtcon;
 151
 152        if (timeout < 1)
 153                return -EINVAL;
 154
 155        freq /= 128;
 156        count = timeout * freq;
 157
 158        DBG("%s: count=%d, timeout=%d, freq=%d\n",
 159            __func__, count, timeout, freq);
 160
 161        /* if the count is bigger than the watchdog register,
 162           then work out what we need to do (and if) we can
 163           actually make this value
 164        */
 165
 166        if (count >= 0x10000) {
 167                for (divisor = 1; divisor <= 0x100; divisor++) {
 168                        if ((count / divisor) < 0x10000)
 169                                break;
 170                }
 171
 172                if ((count / divisor) >= 0x10000) {
 173                        dev_err(wdt_dev, "timeout %d too big\n", timeout);
 174                        return -EINVAL;
 175                }
 176        }
 177
 178        tmr_margin = timeout;
 179
 180        DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
 181            __func__, timeout, divisor, count, count/divisor);
 182
 183        count /= divisor;
 184        wdt_count = count;
 185
 186        /* update the pre-scaler */
 187        wtcon = readl(wdt_base + S3C2410_WTCON);
 188        wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
 189        wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
 190
 191        writel(count, wdt_base + S3C2410_WTDAT);
 192        writel(wtcon, wdt_base + S3C2410_WTCON);
 193
 194        return 0;
 195}
 196
 197/*
 198 *      /dev/watchdog handling
 199 */
 200
 201static int s3c2410wdt_open(struct inode *inode, struct file *file)
 202{
 203        if (test_and_set_bit(0, &open_lock))
 204                return -EBUSY;
 205
 206        if (nowayout)
 207                __module_get(THIS_MODULE);
 208
 209        expect_close = 0;
 210
 211        /* start the timer */
 212        s3c2410wdt_start();
 213        return nonseekable_open(inode, file);
 214}
 215
 216static int s3c2410wdt_release(struct inode *inode, struct file *file)
 217{
 218        /*
 219         *      Shut off the timer.
 220         *      Lock it in if it's a module and we set nowayout
 221         */
 222
 223        if (expect_close == 42)
 224                s3c2410wdt_stop();
 225        else {
 226                dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
 227                s3c2410wdt_keepalive();
 228        }
 229        expect_close = 0;
 230        clear_bit(0, &open_lock);
 231        return 0;
 232}
 233
 234static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
 235                                size_t len, loff_t *ppos)
 236{
 237        /*
 238         *      Refresh the timer.
 239         */
 240        if (len) {
 241                if (!nowayout) {
 242                        size_t i;
 243
 244                        /* In case it was set long ago */
 245                        expect_close = 0;
 246
 247                        for (i = 0; i != len; i++) {
 248                                char c;
 249
 250                                if (get_user(c, data + i))
 251                                        return -EFAULT;
 252                                if (c == 'V')
 253                                        expect_close = 42;
 254                        }
 255                }
 256                s3c2410wdt_keepalive();
 257        }
 258        return len;
 259}
 260
 261#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
 262
 263static const struct watchdog_info s3c2410_wdt_ident = {
 264        .options          =     OPTIONS,
 265        .firmware_version =     0,
 266        .identity         =     "S3C2410 Watchdog",
 267};
 268
 269
 270static long s3c2410wdt_ioctl(struct file *file, unsigned int cmd,
 271                                                        unsigned long arg)
 272{
 273        void __user *argp = (void __user *)arg;
 274        int __user *p = argp;
 275        int new_margin;
 276
 277        switch (cmd) {
 278        case WDIOC_GETSUPPORT:
 279                return copy_to_user(argp, &s3c2410_wdt_ident,
 280                        sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
 281        case WDIOC_GETSTATUS:
 282        case WDIOC_GETBOOTSTATUS:
 283                return put_user(0, p);
 284        case WDIOC_KEEPALIVE:
 285                s3c2410wdt_keepalive();
 286                return 0;
 287        case WDIOC_SETTIMEOUT:
 288                if (get_user(new_margin, p))
 289                        return -EFAULT;
 290                if (s3c2410wdt_set_heartbeat(new_margin))
 291                        return -EINVAL;
 292                s3c2410wdt_keepalive();
 293                return put_user(tmr_margin, p);
 294        case WDIOC_GETTIMEOUT:
 295                return put_user(tmr_margin, p);
 296        default:
 297                return -ENOTTY;
 298        }
 299}
 300
 301/* kernel interface */
 302
 303static const struct file_operations s3c2410wdt_fops = {
 304        .owner          = THIS_MODULE,
 305        .llseek         = no_llseek,
 306        .write          = s3c2410wdt_write,
 307        .unlocked_ioctl = s3c2410wdt_ioctl,
 308        .open           = s3c2410wdt_open,
 309        .release        = s3c2410wdt_release,
 310};
 311
 312static struct miscdevice s3c2410wdt_miscdev = {
 313        .minor          = WATCHDOG_MINOR,
 314        .name           = "watchdog",
 315        .fops           = &s3c2410wdt_fops,
 316};
 317
 318/* interrupt handler code */
 319
 320static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
 321{
 322        dev_info(wdt_dev, "watchdog timer expired (irq)\n");
 323
 324        s3c2410wdt_keepalive();
 325        return IRQ_HANDLED;
 326}
 327/* device interface */
 328
 329static int __devinit s3c2410wdt_probe(struct platform_device *pdev)
 330{
 331        struct resource *res;
 332        struct device *dev;
 333        unsigned int wtcon;
 334        int started = 0;
 335        int ret;
 336        int size;
 337
 338        DBG("%s: probe=%p\n", __func__, pdev);
 339
 340        dev = &pdev->dev;
 341        wdt_dev = &pdev->dev;
 342
 343        /* get the memory region for the watchdog timer */
 344
 345        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 346        if (res == NULL) {
 347                dev_err(dev, "no memory resource specified\n");
 348                return -ENOENT;
 349        }
 350
 351        size = (res->end - res->start) + 1;
 352        wdt_mem = request_mem_region(res->start, size, pdev->name);
 353        if (wdt_mem == NULL) {
 354                dev_err(dev, "failed to get memory region\n");
 355                ret = -ENOENT;
 356                goto err_req;
 357        }
 358
 359        wdt_base = ioremap(res->start, size);
 360        if (wdt_base == NULL) {
 361                dev_err(dev, "failed to ioremap() region\n");
 362                ret = -EINVAL;
 363                goto err_req;
 364        }
 365
 366        DBG("probe: mapped wdt_base=%p\n", wdt_base);
 367
 368        wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 369        if (wdt_irq == NULL) {
 370                dev_err(dev, "no irq resource specified\n");
 371                ret = -ENOENT;
 372                goto err_map;
 373        }
 374
 375        ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
 376        if (ret != 0) {
 377                dev_err(dev, "failed to install irq (%d)\n", ret);
 378                goto err_map;
 379        }
 380
 381        wdt_clock = clk_get(&pdev->dev, "watchdog");
 382        if (IS_ERR(wdt_clock)) {
 383                dev_err(dev, "failed to find watchdog clock source\n");
 384                ret = PTR_ERR(wdt_clock);
 385                goto err_irq;
 386        }
 387
 388        clk_enable(wdt_clock);
 389
 390        /* see if we can actually set the requested timer margin, and if
 391         * not, try the default value */
 392
 393        if (s3c2410wdt_set_heartbeat(tmr_margin)) {
 394                started = s3c2410wdt_set_heartbeat(
 395                                        CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
 396
 397                if (started == 0)
 398                        dev_info(dev,
 399                           "tmr_margin value out of range, default %d used\n",
 400                               CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
 401                else
 402                        dev_info(dev, "default timer value is out of range, "
 403                                                        "cannot start\n");
 404        }
 405
 406        ret = misc_register(&s3c2410wdt_miscdev);
 407        if (ret) {
 408                dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
 409                        WATCHDOG_MINOR, ret);
 410                goto err_clk;
 411        }
 412
 413        if (tmr_atboot && started == 0) {
 414                dev_info(dev, "starting watchdog timer\n");
 415                s3c2410wdt_start();
 416        } else if (!tmr_atboot) {
 417                /* if we're not enabling the watchdog, then ensure it is
 418                 * disabled if it has been left running from the bootloader
 419                 * or other source */
 420
 421                s3c2410wdt_stop();
 422        }
 423
 424        /* print out a statement of readiness */
 425
 426        wtcon = readl(wdt_base + S3C2410_WTCON);
 427
 428        dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
 429                 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",
 430                 (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",
 431                 (wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
 432
 433        return 0;
 434
 435 err_clk:
 436        clk_disable(wdt_clock);
 437        clk_put(wdt_clock);
 438
 439 err_irq:
 440        free_irq(wdt_irq->start, pdev);
 441
 442 err_map:
 443        iounmap(wdt_base);
 444
 445 err_req:
 446        release_resource(wdt_mem);
 447        kfree(wdt_mem);
 448
 449        return ret;
 450}
 451
 452static int __devexit s3c2410wdt_remove(struct platform_device *dev)
 453{
 454        release_resource(wdt_mem);
 455        kfree(wdt_mem);
 456        wdt_mem = NULL;
 457
 458        free_irq(wdt_irq->start, dev);
 459        wdt_irq = NULL;
 460
 461        clk_disable(wdt_clock);
 462        clk_put(wdt_clock);
 463        wdt_clock = NULL;
 464
 465        iounmap(wdt_base);
 466        misc_deregister(&s3c2410wdt_miscdev);
 467        return 0;
 468}
 469
 470static void s3c2410wdt_shutdown(struct platform_device *dev)
 471{
 472        s3c2410wdt_stop();
 473}
 474
 475#ifdef CONFIG_PM
 476
 477static unsigned long wtcon_save;
 478static unsigned long wtdat_save;
 479
 480static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state)
 481{
 482        /* Save watchdog state, and turn it off. */
 483        wtcon_save = readl(wdt_base + S3C2410_WTCON);
 484        wtdat_save = readl(wdt_base + S3C2410_WTDAT);
 485
 486        /* Note that WTCNT doesn't need to be saved. */
 487        s3c2410wdt_stop();
 488
 489        return 0;
 490}
 491
 492static int s3c2410wdt_resume(struct platform_device *dev)
 493{
 494        /* Restore watchdog state. */
 495
 496        writel(wtdat_save, wdt_base + S3C2410_WTDAT);
 497        writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */
 498        writel(wtcon_save, wdt_base + S3C2410_WTCON);
 499
 500        printk(KERN_INFO PFX "watchdog %sabled\n",
 501               (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
 502
 503        return 0;
 504}
 505
 506#else
 507#define s3c2410wdt_suspend NULL
 508#define s3c2410wdt_resume  NULL
 509#endif /* CONFIG_PM */
 510
 511
 512static struct platform_driver s3c2410wdt_driver = {
 513        .probe          = s3c2410wdt_probe,
 514        .remove         = __devexit_p(s3c2410wdt_remove),
 515        .shutdown       = s3c2410wdt_shutdown,
 516        .suspend        = s3c2410wdt_suspend,
 517        .resume         = s3c2410wdt_resume,
 518        .driver         = {
 519                .owner  = THIS_MODULE,
 520                .name   = "s3c2410-wdt",
 521        },
 522};
 523
 524
 525static char banner[] __initdata =
 526        KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
 527
 528static int __init watchdog_init(void)
 529{
 530        printk(banner);
 531        return platform_driver_register(&s3c2410wdt_driver);
 532}
 533
 534static void __exit watchdog_exit(void)
 535{
 536        platform_driver_unregister(&s3c2410wdt_driver);
 537}
 538
 539module_init(watchdog_init);
 540module_exit(watchdog_exit);
 541
 542MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, "
 543              "Dimitry Andric <dimitry.andric@tomtom.com>");
 544MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
 545MODULE_LICENSE("GPL");
 546MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 547MODULE_ALIAS("platform:s3c2410-wdt");
 548