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