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
  32#include <asm/hardware.h>
  33#include <asm/uaccess.h>
  34
  35#define WDT_VERSION     "0.3"
  36#define PFX             "ep93xx_wdt: "
  37
  38/* default timeout (secs) */
  39#define WDT_TIMEOUT 30
  40
  41static int nowayout = WATCHDOG_NOWAYOUT;
  42static int timeout = WDT_TIMEOUT;
  43
  44static struct timer_list timer;
  45static unsigned long next_heartbeat;
  46static unsigned long wdt_status;
  47static unsigned long boot_status;
  48
  49#define WDT_IN_USE              0
  50#define WDT_OK_TO_CLOSE         1
  51
  52#define EP93XX_WDT_REG(x)       (EP93XX_WATCHDOG_BASE + (x))
  53#define EP93XX_WDT_WATCHDOG     EP93XX_WDT_REG(0x00)
  54#define EP93XX_WDT_WDSTATUS     EP93XX_WDT_REG(0x04)
  55
  56/* reset the wdt every ~200ms */
  57#define WDT_INTERVAL (HZ/5)
  58
  59static void wdt_enable(void)
  60{
  61        __raw_writew(0xaaaa, EP93XX_WDT_WATCHDOG);
  62}
  63
  64static void wdt_disable(void)
  65{
  66        __raw_writew(0xaa55, EP93XX_WDT_WATCHDOG);
  67}
  68
  69static inline void wdt_ping(void)
  70{
  71        __raw_writew(0x5555, EP93XX_WDT_WATCHDOG);
  72}
  73
  74static void wdt_startup(void)
  75{
  76        next_heartbeat = jiffies + (timeout * HZ);
  77
  78        wdt_enable();
  79        mod_timer(&timer, jiffies + WDT_INTERVAL);
  80}
  81
  82static void wdt_shutdown(void)
  83{
  84        del_timer_sync(&timer);
  85        wdt_disable();
  86}
  87
  88static void wdt_keepalive(void)
  89{
  90        /* user land ping */
  91        next_heartbeat = jiffies + (timeout * HZ);
  92}
  93
  94static int ep93xx_wdt_open(struct inode *inode, struct file *file)
  95{
  96        if (test_and_set_bit(WDT_IN_USE, &wdt_status))
  97                return -EBUSY;
  98
  99        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 100
 101        wdt_startup();
 102
 103        return nonseekable_open(inode, file);
 104}
 105
 106static ssize_t
 107ep93xx_wdt_write(struct file *file, const char __user *data, size_t len,
 108                 loff_t *ppos)
 109{
 110        if (len) {
 111                if (!nowayout) {
 112                        size_t i;
 113
 114                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 115
 116                        for (i = 0; i != len; i++) {
 117                                char c;
 118
 119                                if (get_user(c, data + i))
 120                                        return -EFAULT;
 121
 122                                if (c == 'V')
 123                                        set_bit(WDT_OK_TO_CLOSE, &wdt_status);
 124                                else
 125                                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 126                        }
 127                }
 128                wdt_keepalive();
 129        }
 130
 131        return len;
 132}
 133
 134static struct watchdog_info ident = {
 135        .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE,
 136        .identity = "EP93xx Watchdog",
 137};
 138
 139static int
 140ep93xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
 141                 unsigned long arg)
 142{
 143        int ret = -ENOTTY;
 144
 145        switch (cmd) {
 146        case WDIOC_GETSUPPORT:
 147                ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
 148                                sizeof(ident)) ? -EFAULT : 0;
 149                break;
 150
 151        case WDIOC_GETSTATUS:
 152                ret = put_user(0, (int __user *)arg);
 153                break;
 154
 155        case WDIOC_GETBOOTSTATUS:
 156                ret = put_user(boot_status, (int __user *)arg);
 157                break;
 158
 159        case WDIOC_GETTIMEOUT:
 160                /* actually, it is 0.250 seconds.... */
 161                ret = put_user(1, (int __user *)arg);
 162                break;
 163
 164        case WDIOC_KEEPALIVE:
 165                wdt_keepalive();
 166                ret = 0;
 167                break;
 168        }
 169        return ret;
 170}
 171
 172static int ep93xx_wdt_release(struct inode *inode, struct file *file)
 173{
 174        if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
 175                wdt_shutdown();
 176        else
 177                printk(KERN_CRIT PFX "Device closed unexpectedly - "
 178                        "timer will not stop\n");
 179
 180        clear_bit(WDT_IN_USE, &wdt_status);
 181        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 182
 183        return 0;
 184}
 185
 186static const struct file_operations ep93xx_wdt_fops = {
 187        .owner          = THIS_MODULE,
 188        .write          = ep93xx_wdt_write,
 189        .ioctl          = ep93xx_wdt_ioctl,
 190        .open           = ep93xx_wdt_open,
 191        .release        = ep93xx_wdt_release,
 192};
 193
 194static struct miscdevice ep93xx_wdt_miscdev = {
 195        .minor          = WATCHDOG_MINOR,
 196        .name           = "watchdog",
 197        .fops           = &ep93xx_wdt_fops,
 198};
 199
 200static void ep93xx_timer_ping(unsigned long data)
 201{
 202        if (time_before(jiffies, next_heartbeat))
 203                wdt_ping();
 204
 205        /* Re-set the timer interval */
 206        mod_timer(&timer, jiffies + WDT_INTERVAL);
 207}
 208
 209static int __init ep93xx_wdt_init(void)
 210{
 211        int err;
 212
 213        err = misc_register(&ep93xx_wdt_miscdev);
 214
 215        boot_status = __raw_readl(EP93XX_WDT_WATCHDOG) & 0x01 ? 1 : 0;
 216
 217        printk(KERN_INFO PFX "EP93XX watchdog, driver version "
 218                WDT_VERSION "%s\n",
 219                (__raw_readl(EP93XX_WDT_WATCHDOG) & 0x08)
 220                ? " (nCS1 disable detected)" : "");
 221
 222        if (timeout < 1 || timeout > 3600) {
 223                timeout = WDT_TIMEOUT;
 224                printk(KERN_INFO PFX
 225                        "timeout value must be 1<=x<=3600, using %d\n",
 226                        timeout);
 227        }
 228
 229        setup_timer(&timer, ep93xx_timer_ping, 1);
 230        return err;
 231}
 232
 233static void __exit ep93xx_wdt_exit(void)
 234{
 235        wdt_shutdown();
 236        misc_deregister(&ep93xx_wdt_miscdev);
 237}
 238
 239module_init(ep93xx_wdt_init);
 240module_exit(ep93xx_wdt_exit);
 241
 242module_param(nowayout, int, 0);
 243MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
 244
 245module_param(timeout, int, 0);
 246MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __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