linux/drivers/watchdog/rc32434_wdt.c
<<
>>
Prefs
   1/*
   2 *  IDT Interprise 79RC32434 watchdog driver
   3 *
   4 *  Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org>
   5 *  Copyright (C) 2008, Florian Fainelli <florian@openwrt.org>
   6 *
   7 *  based on
   8 *  SoftDog 0.05:       A Software Watchdog Device
   9 *
  10 *  (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
  11 *                                      All Rights Reserved.
  12 *
  13 *  This program is free software; you can redistribute it and/or
  14 *  modify it under the terms of the GNU General Public License
  15 *  as published by the Free Software Foundation; either version
  16 *  2 of the License, or (at your option) any later version.
  17 *
  18 */
  19
  20#include <linux/module.h>               /* For module specific items */
  21#include <linux/moduleparam.h>          /* For new moduleparam's */
  22#include <linux/types.h>                /* For standard types (like size_t) */
  23#include <linux/errno.h>                /* For the -ENODEV/... values */
  24#include <linux/kernel.h>               /* For printk/panic/... */
  25#include <linux/fs.h>                   /* For file operations */
  26#include <linux/miscdevice.h>           /* For MODULE_ALIAS_MISCDEV
  27                                                        (WATCHDOG_MINOR) */
  28#include <linux/watchdog.h>             /* For the watchdog specific items */
  29#include <linux/init.h>                 /* For __init/__exit/... */
  30#include <linux/platform_device.h>      /* For platform_driver framework */
  31#include <linux/spinlock.h>             /* For spin_lock/spin_unlock/... */
  32#include <linux/uaccess.h>              /* For copy_to_user/put_user/... */
  33
  34#include <asm/mach-rc32434/integ.h>     /* For the Watchdog registers */
  35
  36#define PFX KBUILD_MODNAME ": "
  37
  38#define VERSION "1.0"
  39
  40static struct {
  41        unsigned long inuse;
  42        spinlock_t io_lock;
  43} rc32434_wdt_device;
  44
  45static struct integ __iomem *wdt_reg;
  46
  47static int expect_close;
  48
  49/* Board internal clock speed in Hz,
  50 * the watchdog timer ticks at. */
  51extern unsigned int idt_cpu_freq;
  52
  53/* translate wtcompare value to seconds and vice versa */
  54#define WTCOMP2SEC(x)   (x / idt_cpu_freq)
  55#define SEC2WTCOMP(x)   (x * idt_cpu_freq)
  56
  57/* Use a default timeout of 20s. This should be
  58 * safe for CPU clock speeds up to 400MHz, as
  59 * ((2 ^ 32) - 1) / (400MHz / 2) = 21s.  */
  60#define WATCHDOG_TIMEOUT 20
  61
  62static int timeout = WATCHDOG_TIMEOUT;
  63module_param(timeout, int, 0);
  64MODULE_PARM_DESC(timeout, "Watchdog timeout value, in seconds (default="
  65                __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
  66
  67static int nowayout = WATCHDOG_NOWAYOUT;
  68module_param(nowayout, int, 0);
  69MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  70        __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  71
  72/* apply or and nand masks to data read from addr and write back */
  73#define SET_BITS(addr, or, nand) \
  74        writel((readl(&addr) | or) & ~nand, &addr)
  75
  76static int rc32434_wdt_set(int new_timeout)
  77{
  78        int max_to = WTCOMP2SEC((u32)-1);
  79
  80        if (new_timeout < 0 || new_timeout > max_to) {
  81                printk(KERN_ERR PFX "timeout value must be between 0 and %d",
  82                        max_to);
  83                return -EINVAL;
  84        }
  85        timeout = new_timeout;
  86        spin_lock(&rc32434_wdt_device.io_lock);
  87        writel(SEC2WTCOMP(timeout), &wdt_reg->wtcompare);
  88        spin_unlock(&rc32434_wdt_device.io_lock);
  89
  90        return 0;
  91}
  92
  93static void rc32434_wdt_start(void)
  94{
  95        u32 or, nand;
  96
  97        spin_lock(&rc32434_wdt_device.io_lock);
  98
  99        /* zero the counter before enabling */
 100        writel(0, &wdt_reg->wtcount);
 101
 102        /* don't generate a non-maskable interrupt,
 103         * do a warm reset instead */
 104        nand = 1 << RC32434_ERR_WNE;
 105        or = 1 << RC32434_ERR_WRE;
 106
 107        /* reset the ERRCS timeout bit in case it's set */
 108        nand |= 1 << RC32434_ERR_WTO;
 109
 110        SET_BITS(wdt_reg->errcs, or, nand);
 111
 112        /* set the timeout (either default or based on module param) */
 113        rc32434_wdt_set(timeout);
 114
 115        /* reset WTC timeout bit and enable WDT */
 116        nand = 1 << RC32434_WTC_TO;
 117        or = 1 << RC32434_WTC_EN;
 118
 119        SET_BITS(wdt_reg->wtc, or, nand);
 120
 121        spin_unlock(&rc32434_wdt_device.io_lock);
 122        printk(KERN_INFO PFX "Started watchdog timer.\n");
 123}
 124
 125static void rc32434_wdt_stop(void)
 126{
 127        spin_lock(&rc32434_wdt_device.io_lock);
 128
 129        /* Disable WDT */
 130        SET_BITS(wdt_reg->wtc, 0, 1 << RC32434_WTC_EN);
 131
 132        spin_unlock(&rc32434_wdt_device.io_lock);
 133        printk(KERN_INFO PFX "Stopped watchdog timer.\n");
 134}
 135
 136static void rc32434_wdt_ping(void)
 137{
 138        spin_lock(&rc32434_wdt_device.io_lock);
 139        writel(0, &wdt_reg->wtcount);
 140        spin_unlock(&rc32434_wdt_device.io_lock);
 141}
 142
 143static int rc32434_wdt_open(struct inode *inode, struct file *file)
 144{
 145        if (test_and_set_bit(0, &rc32434_wdt_device.inuse))
 146                return -EBUSY;
 147
 148        if (nowayout)
 149                __module_get(THIS_MODULE);
 150
 151        rc32434_wdt_start();
 152        rc32434_wdt_ping();
 153
 154        return nonseekable_open(inode, file);
 155}
 156
 157static int rc32434_wdt_release(struct inode *inode, struct file *file)
 158{
 159        if (expect_close == 42) {
 160                rc32434_wdt_stop();
 161                module_put(THIS_MODULE);
 162        } else {
 163                printk(KERN_CRIT PFX
 164                        "device closed unexpectedly. WDT will not stop!\n");
 165                rc32434_wdt_ping();
 166        }
 167        clear_bit(0, &rc32434_wdt_device.inuse);
 168        return 0;
 169}
 170
 171static ssize_t rc32434_wdt_write(struct file *file, const char *data,
 172                                size_t len, loff_t *ppos)
 173{
 174        if (len) {
 175                if (!nowayout) {
 176                        size_t i;
 177
 178                        /* In case it was set long ago */
 179                        expect_close = 0;
 180
 181                        for (i = 0; i != len; i++) {
 182                                char c;
 183                                if (get_user(c, data + i))
 184                                        return -EFAULT;
 185                                if (c == 'V')
 186                                        expect_close = 42;
 187                        }
 188                }
 189                rc32434_wdt_ping();
 190                return len;
 191        }
 192        return 0;
 193}
 194
 195static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,
 196                                unsigned long arg)
 197{
 198        void __user *argp = (void __user *)arg;
 199        int new_timeout;
 200        unsigned int value;
 201        static struct watchdog_info ident = {
 202                .options =              WDIOF_SETTIMEOUT |
 203                                        WDIOF_KEEPALIVEPING |
 204                                        WDIOF_MAGICCLOSE,
 205                .identity =             "RC32434_WDT Watchdog",
 206        };
 207        switch (cmd) {
 208        case WDIOC_GETSUPPORT:
 209                if (copy_to_user(argp, &ident, sizeof(ident)))
 210                        return -EFAULT;
 211                break;
 212        case WDIOC_GETSTATUS:
 213        case WDIOC_GETBOOTSTATUS:
 214                value = 0;
 215                if (copy_to_user(argp, &value, sizeof(int)))
 216                        return -EFAULT;
 217                break;
 218        case WDIOC_SETOPTIONS:
 219                if (copy_from_user(&value, argp, sizeof(int)))
 220                        return -EFAULT;
 221                switch (value) {
 222                case WDIOS_ENABLECARD:
 223                        rc32434_wdt_start();
 224                        break;
 225                case WDIOS_DISABLECARD:
 226                        rc32434_wdt_stop();
 227                        break;
 228                default:
 229                        return -EINVAL;
 230                }
 231                break;
 232        case WDIOC_KEEPALIVE:
 233                rc32434_wdt_ping();
 234                break;
 235        case WDIOC_SETTIMEOUT:
 236                if (copy_from_user(&new_timeout, argp, sizeof(int)))
 237                        return -EFAULT;
 238                if (rc32434_wdt_set(new_timeout))
 239                        return -EINVAL;
 240                /* Fall through */
 241        case WDIOC_GETTIMEOUT:
 242                return copy_to_user(argp, &timeout, sizeof(int));
 243        default:
 244                return -ENOTTY;
 245        }
 246
 247        return 0;
 248}
 249
 250static const struct file_operations rc32434_wdt_fops = {
 251        .owner          = THIS_MODULE,
 252        .llseek         = no_llseek,
 253        .write          = rc32434_wdt_write,
 254        .unlocked_ioctl = rc32434_wdt_ioctl,
 255        .open           = rc32434_wdt_open,
 256        .release        = rc32434_wdt_release,
 257};
 258
 259static struct miscdevice rc32434_wdt_miscdev = {
 260        .minor  = WATCHDOG_MINOR,
 261        .name   = "watchdog",
 262        .fops   = &rc32434_wdt_fops,
 263};
 264
 265static char banner[] __devinitdata = KERN_INFO PFX
 266                "Watchdog Timer version " VERSION ", timer margin: %d sec\n";
 267
 268static int __devinit rc32434_wdt_probe(struct platform_device *pdev)
 269{
 270        int ret;
 271        struct resource *r;
 272
 273        r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb532_wdt_res");
 274        if (!r) {
 275                printk(KERN_ERR PFX "failed to retrieve resources\n");
 276                return -ENODEV;
 277        }
 278
 279        wdt_reg = ioremap_nocache(r->start, resource_size(r));
 280        if (!wdt_reg) {
 281                printk(KERN_ERR PFX "failed to remap I/O resources\n");
 282                return -ENXIO;
 283        }
 284
 285        spin_lock_init(&rc32434_wdt_device.io_lock);
 286
 287        /* Make sure the watchdog is not running */
 288        rc32434_wdt_stop();
 289
 290        /* Check that the heartbeat value is within it's range;
 291         * if not reset to the default */
 292        if (rc32434_wdt_set(timeout)) {
 293                rc32434_wdt_set(WATCHDOG_TIMEOUT);
 294                printk(KERN_INFO PFX
 295                        "timeout value must be between 0 and %d\n",
 296                        WTCOMP2SEC((u32)-1));
 297        }
 298
 299        ret = misc_register(&rc32434_wdt_miscdev);
 300        if (ret < 0) {
 301                printk(KERN_ERR PFX "failed to register watchdog device\n");
 302                goto unmap;
 303        }
 304
 305        printk(banner, timeout);
 306
 307        return 0;
 308
 309unmap:
 310        iounmap(wdt_reg);
 311        return ret;
 312}
 313
 314static int __devexit rc32434_wdt_remove(struct platform_device *pdev)
 315{
 316        misc_deregister(&rc32434_wdt_miscdev);
 317        iounmap(wdt_reg);
 318        return 0;
 319}
 320
 321static void rc32434_wdt_shutdown(struct platform_device *pdev)
 322{
 323        rc32434_wdt_stop();
 324}
 325
 326static struct platform_driver rc32434_wdt_driver = {
 327        .probe          = rc32434_wdt_probe,
 328        .remove         = __devexit_p(rc32434_wdt_remove),
 329        .shutdown       = rc32434_wdt_shutdown,
 330        .driver         = {
 331                        .name = "rc32434_wdt",
 332        }
 333};
 334
 335static int __init rc32434_wdt_init(void)
 336{
 337        return platform_driver_register(&rc32434_wdt_driver);
 338}
 339
 340static void __exit rc32434_wdt_exit(void)
 341{
 342        platform_driver_unregister(&rc32434_wdt_driver);
 343}
 344
 345module_init(rc32434_wdt_init);
 346module_exit(rc32434_wdt_exit);
 347
 348MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>,"
 349                "Florian Fainelli <florian@openwrt.org>");
 350MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog");
 351MODULE_LICENSE("GPL");
 352MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 353