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