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