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