linux/drivers/watchdog/ep93xx_wdt.c
<<
>>
Prefs
   1/*
   2 * Watchdog driver for Cirrus Logic EP93xx family of devices.
   3 *
   4 * Copyright (c) 2004 Ray Lehtiniemi
   5 * Copyright (c) 2006 Tower Technologies
   6 * Based on ep93xx driver, bits from alim7101_wdt.c
   7 *
   8 * Authors: Ray Lehtiniemi <rayl@mail.com>,
   9 *      Alessandro Zummo <a.zummo@towertech.it>
  10 *
  11 * This file is licensed under the terms of the GNU General Public
  12 * License version 2. This program is licensed "as is" without any
  13 * warranty of any kind, whether express or implied.
  14 *
  15 * This watchdog fires after 250msec, which is a too short interval
  16 * for us to rely on the user space daemon alone. So we ping the
  17 * wdt each ~200msec and eventually stop doing it if the user space
  18 * daemon dies.
  19 *
  20 * TODO:
  21 *
  22 *      - Test last reset from watchdog status
  23 *      - Add a few missing ioctls
  24 */
  25
  26#include <linux/module.h>
  27#include <linux/fs.h>
  28#include <linux/miscdevice.h>
  29#include <linux/watchdog.h>
  30#include <linux/timer.h>
  31#include <linux/uaccess.h>
  32#include <mach/hardware.h>
  33
  34#define WDT_VERSION     "0.3"
  35#define PFX             "ep93xx_wdt: "
  36
  37/* default timeout (secs) */
  38#define WDT_TIMEOUT 30
  39
  40static int nowayout = WATCHDOG_NOWAYOUT;
  41static int timeout = WDT_TIMEOUT;
  42
  43static struct timer_list timer;
  44static unsigned long next_heartbeat;
  45static unsigned long wdt_status;
  46static unsigned long boot_status;
  47
  48#define WDT_IN_USE              0
  49#define WDT_OK_TO_CLOSE         1
  50
  51#define EP93XX_WDT_REG(x)       (EP93XX_WATCHDOG_BASE + (x))
  52#define EP93XX_WDT_WATCHDOG     EP93XX_WDT_REG(0x00)
  53#define EP93XX_WDT_WDSTATUS     EP93XX_WDT_REG(0x04)
  54
  55/* reset the wdt every ~200ms */
  56#define WDT_INTERVAL (HZ/5)
  57
  58static void wdt_enable(void)
  59{
  60        __raw_writew(0xaaaa, EP93XX_WDT_WATCHDOG);
  61}
  62
  63static void wdt_disable(void)
  64{
  65        __raw_writew(0xaa55, EP93XX_WDT_WATCHDOG);
  66}
  67
  68static inline void wdt_ping(void)
  69{
  70        __raw_writew(0x5555, EP93XX_WDT_WATCHDOG);
  71}
  72
  73static void wdt_startup(void)
  74{
  75        next_heartbeat = jiffies + (timeout * HZ);
  76
  77        wdt_enable();
  78        mod_timer(&timer, jiffies + WDT_INTERVAL);
  79}
  80
  81static void wdt_shutdown(void)
  82{
  83        del_timer_sync(&timer);
  84        wdt_disable();
  85}
  86
  87static void wdt_keepalive(void)
  88{
  89        /* user land ping */
  90        next_heartbeat = jiffies + (timeout * HZ);
  91}
  92
  93static int ep93xx_wdt_open(struct inode *inode, struct file *file)
  94{
  95        if (test_and_set_bit(WDT_IN_USE, &wdt_status))
  96                return -EBUSY;
  97
  98        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  99
 100        wdt_startup();
 101
 102        return nonseekable_open(inode, file);
 103}
 104
 105static ssize_t
 106ep93xx_wdt_write(struct file *file, const char __user *data, size_t len,
 107                 loff_t *ppos)
 108{
 109        if (len) {
 110                if (!nowayout) {
 111                        size_t i;
 112
 113                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 114
 115                        for (i = 0; i != len; i++) {
 116                                char c;
 117
 118                                if (get_user(c, data + i))
 119                                        return -EFAULT;
 120
 121                                if (c == 'V')
 122                                        set_bit(WDT_OK_TO_CLOSE, &wdt_status);
 123                                else
 124                                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 125                        }
 126                }
 127                wdt_keepalive();
 128        }
 129
 130        return len;
 131}
 132
 133static struct watchdog_info ident = {
 134        .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE,
 135        .identity = "EP93xx Watchdog",
 136};
 137
 138static long ep93xx_wdt_ioctl(struct file *file,
 139                                        unsigned int cmd, unsigned long arg)
 140{
 141        int ret = -ENOTTY;
 142
 143        switch (cmd) {
 144        case WDIOC_GETSUPPORT:
 145                ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
 146                                sizeof(ident)) ? -EFAULT : 0;
 147                break;
 148
 149        case WDIOC_GETSTATUS:
 150                ret = put_user(0, (int __user *)arg);
 151                break;
 152
 153        case WDIOC_GETBOOTSTATUS:
 154                ret = put_user(boot_status, (int __user *)arg);
 155                break;
 156
 157        case WDIOC_KEEPALIVE:
 158                wdt_keepalive();
 159                ret = 0;
 160                break;
 161
 162        case WDIOC_GETTIMEOUT:
 163                /* actually, it is 0.250 seconds.... */
 164                ret = put_user(1, (int __user *)arg);
 165                break;
 166        }
 167        return ret;
 168}
 169
 170static int ep93xx_wdt_release(struct inode *inode, struct file *file)
 171{
 172        if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
 173                wdt_shutdown();
 174        else
 175                printk(KERN_CRIT PFX
 176                        "Device closed unexpectedly - timer will not stop\n");
 177
 178        clear_bit(WDT_IN_USE, &wdt_status);
 179        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 180
 181        return 0;
 182}
 183
 184static const struct file_operations ep93xx_wdt_fops = {
 185        .owner          = THIS_MODULE,
 186        .write          = ep93xx_wdt_write,
 187        .unlocked_ioctl = ep93xx_wdt_ioctl,
 188        .open           = ep93xx_wdt_open,
 189        .release        = ep93xx_wdt_release,
 190};
 191
 192static struct miscdevice ep93xx_wdt_miscdev = {
 193        .minor          = WATCHDOG_MINOR,
 194        .name           = "watchdog",
 195        .fops           = &ep93xx_wdt_fops,
 196};
 197
 198static void ep93xx_timer_ping(unsigned long data)
 199{
 200        if (time_before(jiffies, next_heartbeat))
 201                wdt_ping();
 202
 203        /* Re-set the timer interval */
 204        mod_timer(&timer, jiffies + WDT_INTERVAL);
 205}
 206
 207static int __init ep93xx_wdt_init(void)
 208{
 209        int err;
 210
 211        err = misc_register(&ep93xx_wdt_miscdev);
 212
 213        boot_status = __raw_readl(EP93XX_WDT_WATCHDOG) & 0x01 ? 1 : 0;
 214
 215        printk(KERN_INFO PFX "EP93XX watchdog, driver version "
 216                WDT_VERSION "%s\n",
 217                (__raw_readl(EP93XX_WDT_WATCHDOG) & 0x08)
 218                ? " (nCS1 disable detected)" : "");
 219
 220        if (timeout < 1 || timeout > 3600) {
 221                timeout = WDT_TIMEOUT;
 222                printk(KERN_INFO PFX
 223                        "timeout value must be 1<=x<=3600, using %d\n",
 224                        timeout);
 225        }
 226
 227        setup_timer(&timer, ep93xx_timer_ping, 1);
 228        return err;
 229}
 230
 231static void __exit ep93xx_wdt_exit(void)
 232{
 233        wdt_shutdown();
 234        misc_deregister(&ep93xx_wdt_miscdev);
 235}
 236
 237module_init(ep93xx_wdt_init);
 238module_exit(ep93xx_wdt_exit);
 239
 240module_param(nowayout, int, 0);
 241MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
 242
 243module_param(timeout, int, 0);
 244MODULE_PARM_DESC(timeout,
 245        "Watchdog timeout in seconds. (1<=timeout<=3600, default="
 246                                __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
 247
 248MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>,"
 249                "Alessandro Zummo <a.zummo@towertech.it>");
 250MODULE_DESCRIPTION("EP93xx Watchdog");
 251MODULE_LICENSE("GPL");
 252MODULE_VERSION(WDT_VERSION);
 253MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 254