linux/drivers/watchdog/mpcore_wdt.c
<<
>>
Prefs
   1/*
   2 *      Watchdog driver for the mpcore watchdog timer
   3 *
   4 *      (c) Copyright 2004 ARM Limited
   5 *
   6 *      Based on the SoftDog driver:
   7 *      (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
   8 *                                              All Rights Reserved.
   9 *
  10 *      This program is free software; you can redistribute it and/or
  11 *      modify it under the terms of the GNU General Public License
  12 *      as published by the Free Software Foundation; either version
  13 *      2 of the License, or (at your option) any later version.
  14 *
  15 *      Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
  16 *      warranty for any of this software. This material is provided
  17 *      "AS-IS" and at no charge.
  18 *
  19 *      (c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
  20 *
  21 */
  22#include <linux/module.h>
  23#include <linux/moduleparam.h>
  24#include <linux/types.h>
  25#include <linux/miscdevice.h>
  26#include <linux/watchdog.h>
  27#include <linux/fs.h>
  28#include <linux/reboot.h>
  29#include <linux/init.h>
  30#include <linux/interrupt.h>
  31#include <linux/platform_device.h>
  32#include <linux/uaccess.h>
  33#include <linux/slab.h>
  34#include <linux/io.h>
  35
  36#include <asm/smp_twd.h>
  37
  38struct mpcore_wdt {
  39        unsigned long   timer_alive;
  40        struct device   *dev;
  41        void __iomem    *base;
  42        int             irq;
  43        unsigned int    perturb;
  44        char            expect_close;
  45};
  46
  47static struct platform_device *mpcore_wdt_dev;
  48static DEFINE_SPINLOCK(wdt_lock);
  49
  50#define TIMER_MARGIN    60
  51static int mpcore_margin = TIMER_MARGIN;
  52module_param(mpcore_margin, int, 0);
  53MODULE_PARM_DESC(mpcore_margin,
  54        "MPcore timer margin in seconds. (0 < mpcore_margin < 65536, default="
  55                                __MODULE_STRING(TIMER_MARGIN) ")");
  56
  57static int nowayout = WATCHDOG_NOWAYOUT;
  58module_param(nowayout, int, 0);
  59MODULE_PARM_DESC(nowayout,
  60        "Watchdog cannot be stopped once started (default="
  61                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  62
  63#define ONLY_TESTING    0
  64static int mpcore_noboot = ONLY_TESTING;
  65module_param(mpcore_noboot, int, 0);
  66MODULE_PARM_DESC(mpcore_noboot, "MPcore watchdog action, "
  67        "set to 1 to ignore reboots, 0 to reboot (default="
  68                                        __MODULE_STRING(ONLY_TESTING) ")");
  69
  70/*
  71 *      This is the interrupt handler.  Note that we only use this
  72 *      in testing mode, so don't actually do a reboot here.
  73 */
  74static irqreturn_t mpcore_wdt_fire(int irq, void *arg)
  75{
  76        struct mpcore_wdt *wdt = arg;
  77
  78        /* Check it really was our interrupt */
  79        if (readl(wdt->base + TWD_WDOG_INTSTAT)) {
  80                dev_printk(KERN_CRIT, wdt->dev,
  81                                        "Triggered - Reboot ignored.\n");
  82                /* Clear the interrupt on the watchdog */
  83                writel(1, wdt->base + TWD_WDOG_INTSTAT);
  84                return IRQ_HANDLED;
  85        }
  86        return IRQ_NONE;
  87}
  88
  89/*
  90 *      mpcore_wdt_keepalive - reload the timer
  91 *
  92 *      Note that the spec says a DIFFERENT value must be written to the reload
  93 *      register each time.  The "perturb" variable deals with this by adding 1
  94 *      to the count every other time the function is called.
  95 */
  96static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt)
  97{
  98        unsigned long count;
  99
 100        spin_lock(&wdt_lock);
 101        /* Assume prescale is set to 256 */
 102        count =  __raw_readl(wdt->base + TWD_WDOG_COUNTER);
 103        count = (0xFFFFFFFFU - count) * (HZ / 5);
 104        count = (count / 256) * mpcore_margin;
 105
 106        /* Reload the counter */
 107        writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD);
 108        wdt->perturb = wdt->perturb ? 0 : 1;
 109        spin_unlock(&wdt_lock);
 110}
 111
 112static void mpcore_wdt_stop(struct mpcore_wdt *wdt)
 113{
 114        spin_lock(&wdt_lock);
 115        writel(0x12345678, wdt->base + TWD_WDOG_DISABLE);
 116        writel(0x87654321, wdt->base + TWD_WDOG_DISABLE);
 117        writel(0x0, wdt->base + TWD_WDOG_CONTROL);
 118        spin_unlock(&wdt_lock);
 119}
 120
 121static void mpcore_wdt_start(struct mpcore_wdt *wdt)
 122{
 123        dev_printk(KERN_INFO, wdt->dev, "enabling watchdog.\n");
 124
 125        /* This loads the count register but does NOT start the count yet */
 126        mpcore_wdt_keepalive(wdt);
 127
 128        if (mpcore_noboot) {
 129                /* Enable watchdog - prescale=256, watchdog mode=0, enable=1 */
 130                writel(0x0000FF01, wdt->base + TWD_WDOG_CONTROL);
 131        } else {
 132                /* Enable watchdog - prescale=256, watchdog mode=1, enable=1 */
 133                writel(0x0000FF09, wdt->base + TWD_WDOG_CONTROL);
 134        }
 135}
 136
 137static int mpcore_wdt_set_heartbeat(int t)
 138{
 139        if (t < 0x0001 || t > 0xFFFF)
 140                return -EINVAL;
 141
 142        mpcore_margin = t;
 143        return 0;
 144}
 145
 146/*
 147 *      /dev/watchdog handling
 148 */
 149static int mpcore_wdt_open(struct inode *inode, struct file *file)
 150{
 151        struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev);
 152
 153        if (test_and_set_bit(0, &wdt->timer_alive))
 154                return -EBUSY;
 155
 156        if (nowayout)
 157                __module_get(THIS_MODULE);
 158
 159        file->private_data = wdt;
 160
 161        /*
 162         *      Activate timer
 163         */
 164        mpcore_wdt_start(wdt);
 165
 166        return nonseekable_open(inode, file);
 167}
 168
 169static int mpcore_wdt_release(struct inode *inode, struct file *file)
 170{
 171        struct mpcore_wdt *wdt = file->private_data;
 172
 173        /*
 174         *      Shut off the timer.
 175         *      Lock it in if it's a module and we set nowayout
 176         */
 177        if (wdt->expect_close == 42)
 178                mpcore_wdt_stop(wdt);
 179        else {
 180                dev_printk(KERN_CRIT, wdt->dev,
 181                                "unexpected close, not stopping watchdog!\n");
 182                mpcore_wdt_keepalive(wdt);
 183        }
 184        clear_bit(0, &wdt->timer_alive);
 185        wdt->expect_close = 0;
 186        return 0;
 187}
 188
 189static ssize_t mpcore_wdt_write(struct file *file, const char *data,
 190                                                size_t len, loff_t *ppos)
 191{
 192        struct mpcore_wdt *wdt = file->private_data;
 193
 194        /*
 195         *      Refresh the timer.
 196         */
 197        if (len) {
 198                if (!nowayout) {
 199                        size_t i;
 200
 201                        /* In case it was set long ago */
 202                        wdt->expect_close = 0;
 203
 204                        for (i = 0; i != len; i++) {
 205                                char c;
 206
 207                                if (get_user(c, data + i))
 208                                        return -EFAULT;
 209                                if (c == 'V')
 210                                        wdt->expect_close = 42;
 211                        }
 212                }
 213                mpcore_wdt_keepalive(wdt);
 214        }
 215        return len;
 216}
 217
 218static const struct watchdog_info ident = {
 219        .options                = WDIOF_SETTIMEOUT |
 220                                  WDIOF_KEEPALIVEPING |
 221                                  WDIOF_MAGICCLOSE,
 222        .identity               = "MPcore Watchdog",
 223};
 224
 225static long mpcore_wdt_ioctl(struct file *file, unsigned int cmd,
 226                                                        unsigned long arg)
 227{
 228        struct mpcore_wdt *wdt = file->private_data;
 229        int ret;
 230        union {
 231                struct watchdog_info ident;
 232                int i;
 233        } uarg;
 234
 235        if (_IOC_DIR(cmd) && _IOC_SIZE(cmd) > sizeof(uarg))
 236                return -ENOTTY;
 237
 238        if (_IOC_DIR(cmd) & _IOC_WRITE) {
 239                ret = copy_from_user(&uarg, (void __user *)arg, _IOC_SIZE(cmd));
 240                if (ret)
 241                        return -EFAULT;
 242        }
 243
 244        switch (cmd) {
 245        case WDIOC_GETSUPPORT:
 246                uarg.ident = ident;
 247                ret = 0;
 248                break;
 249
 250        case WDIOC_GETSTATUS:
 251        case WDIOC_GETBOOTSTATUS:
 252                uarg.i = 0;
 253                ret = 0;
 254                break;
 255
 256        case WDIOC_SETOPTIONS:
 257                ret = -EINVAL;
 258                if (uarg.i & WDIOS_DISABLECARD) {
 259                        mpcore_wdt_stop(wdt);
 260                        ret = 0;
 261                }
 262                if (uarg.i & WDIOS_ENABLECARD) {
 263                        mpcore_wdt_start(wdt);
 264                        ret = 0;
 265                }
 266                break;
 267
 268        case WDIOC_KEEPALIVE:
 269                mpcore_wdt_keepalive(wdt);
 270                ret = 0;
 271                break;
 272
 273        case WDIOC_SETTIMEOUT:
 274                ret = mpcore_wdt_set_heartbeat(uarg.i);
 275                if (ret)
 276                        break;
 277
 278                mpcore_wdt_keepalive(wdt);
 279                /* Fall */
 280        case WDIOC_GETTIMEOUT:
 281                uarg.i = mpcore_margin;
 282                ret = 0;
 283                break;
 284
 285        default:
 286                return -ENOTTY;
 287        }
 288
 289        if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) {
 290                ret = copy_to_user((void __user *)arg, &uarg, _IOC_SIZE(cmd));
 291                if (ret)
 292                        ret = -EFAULT;
 293        }
 294        return ret;
 295}
 296
 297/*
 298 *      System shutdown handler.  Turn off the watchdog if we're
 299 *      restarting or halting the system.
 300 */
 301static void mpcore_wdt_shutdown(struct platform_device *dev)
 302{
 303        struct mpcore_wdt *wdt = platform_get_drvdata(dev);
 304
 305        if (system_state == SYSTEM_RESTART || system_state == SYSTEM_HALT)
 306                mpcore_wdt_stop(wdt);
 307}
 308
 309/*
 310 *      Kernel Interfaces
 311 */
 312static const struct file_operations mpcore_wdt_fops = {
 313        .owner          = THIS_MODULE,
 314        .llseek         = no_llseek,
 315        .write          = mpcore_wdt_write,
 316        .unlocked_ioctl = mpcore_wdt_ioctl,
 317        .open           = mpcore_wdt_open,
 318        .release        = mpcore_wdt_release,
 319};
 320
 321static struct miscdevice mpcore_wdt_miscdev = {
 322        .minor          = WATCHDOG_MINOR,
 323        .name           = "watchdog",
 324        .fops           = &mpcore_wdt_fops,
 325};
 326
 327static int __devinit mpcore_wdt_probe(struct platform_device *dev)
 328{
 329        struct mpcore_wdt *wdt;
 330        struct resource *res;
 331        int ret;
 332
 333        /* We only accept one device, and it must have an id of -1 */
 334        if (dev->id != -1)
 335                return -ENODEV;
 336
 337        res = platform_get_resource(dev, IORESOURCE_MEM, 0);
 338        if (!res) {
 339                ret = -ENODEV;
 340                goto err_out;
 341        }
 342
 343        wdt = kzalloc(sizeof(struct mpcore_wdt), GFP_KERNEL);
 344        if (!wdt) {
 345                ret = -ENOMEM;
 346                goto err_out;
 347        }
 348
 349        wdt->dev = &dev->dev;
 350        wdt->irq = platform_get_irq(dev, 0);
 351        if (wdt->irq < 0) {
 352                ret = -ENXIO;
 353                goto err_free;
 354        }
 355        wdt->base = ioremap(res->start, resource_size(res));
 356        if (!wdt->base) {
 357                ret = -ENOMEM;
 358                goto err_free;
 359        }
 360
 361        mpcore_wdt_miscdev.parent = &dev->dev;
 362        ret = misc_register(&mpcore_wdt_miscdev);
 363        if (ret) {
 364                dev_printk(KERN_ERR, wdt->dev,
 365                        "cannot register miscdev on minor=%d (err=%d)\n",
 366                                                        WATCHDOG_MINOR, ret);
 367                goto err_misc;
 368        }
 369
 370        ret = request_irq(wdt->irq, mpcore_wdt_fire, IRQF_DISABLED,
 371                                                        "mpcore_wdt", wdt);
 372        if (ret) {
 373                dev_printk(KERN_ERR, wdt->dev,
 374                        "cannot register IRQ%d for watchdog\n", wdt->irq);
 375                goto err_irq;
 376        }
 377
 378        mpcore_wdt_stop(wdt);
 379        platform_set_drvdata(dev, wdt);
 380        mpcore_wdt_dev = dev;
 381
 382        return 0;
 383
 384err_irq:
 385        misc_deregister(&mpcore_wdt_miscdev);
 386err_misc:
 387        iounmap(wdt->base);
 388err_free:
 389        kfree(wdt);
 390err_out:
 391        return ret;
 392}
 393
 394static int __devexit mpcore_wdt_remove(struct platform_device *dev)
 395{
 396        struct mpcore_wdt *wdt = platform_get_drvdata(dev);
 397
 398        platform_set_drvdata(dev, NULL);
 399
 400        misc_deregister(&mpcore_wdt_miscdev);
 401
 402        mpcore_wdt_dev = NULL;
 403
 404        free_irq(wdt->irq, wdt);
 405        iounmap(wdt->base);
 406        kfree(wdt);
 407        return 0;
 408}
 409
 410/* work with hotplug and coldplug */
 411MODULE_ALIAS("platform:mpcore_wdt");
 412
 413static struct platform_driver mpcore_wdt_driver = {
 414        .probe          = mpcore_wdt_probe,
 415        .remove         = __devexit_p(mpcore_wdt_remove),
 416        .shutdown       = mpcore_wdt_shutdown,
 417        .driver         = {
 418                .owner  = THIS_MODULE,
 419                .name   = "mpcore_wdt",
 420        },
 421};
 422
 423static char banner[] __initdata = KERN_INFO "MPcore Watchdog Timer: 0.1. "
 424                "mpcore_noboot=%d mpcore_margin=%d sec (nowayout= %d)\n";
 425
 426static int __init mpcore_wdt_init(void)
 427{
 428        /*
 429         * Check that the margin value is within it's range;
 430         * if not reset to the default
 431         */
 432        if (mpcore_wdt_set_heartbeat(mpcore_margin)) {
 433                mpcore_wdt_set_heartbeat(TIMER_MARGIN);
 434                printk(KERN_INFO "mpcore_margin value must be 0 < mpcore_margin < 65536, using %d\n",
 435                        TIMER_MARGIN);
 436        }
 437
 438        printk(banner, mpcore_noboot, mpcore_margin, nowayout);
 439
 440        return platform_driver_register(&mpcore_wdt_driver);
 441}
 442
 443static void __exit mpcore_wdt_exit(void)
 444{
 445        platform_driver_unregister(&mpcore_wdt_driver);
 446}
 447
 448module_init(mpcore_wdt_init);
 449module_exit(mpcore_wdt_exit);
 450
 451MODULE_AUTHOR("ARM Limited");
 452MODULE_DESCRIPTION("MPcore Watchdog Device Driver");
 453MODULE_LICENSE("GPL");
 454MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 455