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