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 <linux/io.h>
  33#include <mach/hardware.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 const struct watchdog_info ident = {
 135        .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE,
 136        .identity = "EP93xx Watchdog",
 137};
 138
 139static long ep93xx_wdt_ioctl(struct file *file,
 140                                        unsigned int cmd, unsigned long arg)
 141{
 142        int ret = -ENOTTY;
 143
 144        switch (cmd) {
 145        case WDIOC_GETSUPPORT:
 146                ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
 147                                sizeof(ident)) ? -EFAULT : 0;
 148                break;
 149
 150        case WDIOC_GETSTATUS:
 151                ret = put_user(0, (int __user *)arg);
 152                break;
 153
 154        case WDIOC_GETBOOTSTATUS:
 155                ret = put_user(boot_status, (int __user *)arg);
 156                break;
 157
 158        case WDIOC_KEEPALIVE:
 159                wdt_keepalive();
 160                ret = 0;
 161                break;
 162
 163        case WDIOC_GETTIMEOUT:
 164                /* actually, it is 0.250 seconds.... */
 165                ret = put_user(1, (int __user *)arg);
 166                break;
 167        }
 168        return ret;
 169}
 170
 171static int ep93xx_wdt_release(struct inode *inode, struct file *file)
 172{
 173        if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
 174                wdt_shutdown();
 175        else
 176                printk(KERN_CRIT PFX
 177                        "Device closed unexpectedly - timer will not stop\n");
 178
 179        clear_bit(WDT_IN_USE, &wdt_status);
 180        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 181
 182        return 0;
 183}
 184
 185static const struct file_operations ep93xx_wdt_fops = {
 186        .owner          = THIS_MODULE,
 187        .write          = ep93xx_wdt_write,
 188        .unlocked_ioctl = ep93xx_wdt_ioctl,
 189        .open           = ep93xx_wdt_open,
 190        .release        = ep93xx_wdt_release,
 191        .llseek         = no_llseek,
 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,
 247        "Watchdog timeout in seconds. (1<=timeout<=3600, default="
 248                                __MODULE_STRING(WDT_TIMEOUT) ")");
 249
 250MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>,"
 251                "Alessandro Zummo <a.zummo@towertech.it>");
 252MODULE_DESCRIPTION("EP93xx Watchdog");
 253MODULE_LICENSE("GPL");
 254MODULE_VERSION(WDT_VERSION);
 255MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 256