linux/drivers/watchdog/sbc7240_wdt.c
<<
>>
Prefs
   1/*
   2 *      NANO7240 SBC Watchdog device driver
   3 *
   4 *      Based on w83877f.c by Scott Jennings,
   5 *
   6 *      This program is free software; you can redistribute it and/or modify
   7 *      it under the terms of the GNU General Public License version 2 as
   8 *      published by the Free Software Foundation;
   9 *
  10 *      Software distributed under the License is distributed on an "AS IS"
  11 *      basis, WITHOUT WARRANTY OF ANY KIND, either express or
  12 *      implied. See the License for the specific language governing
  13 *      rights and limitations under the License.
  14 *
  15 *      (c) Copyright 2007  Gilles GIGAN <gilles.gigan@jcu.edu.au>
  16 *
  17 */
  18
  19#include <linux/fs.h>
  20#include <linux/init.h>
  21#include <linux/ioport.h>
  22#include <linux/jiffies.h>
  23#include <linux/module.h>
  24#include <linux/moduleparam.h>
  25#include <linux/miscdevice.h>
  26#include <linux/notifier.h>
  27#include <linux/reboot.h>
  28#include <linux/types.h>
  29#include <linux/watchdog.h>
  30#include <asm/atomic.h>
  31#include <asm/io.h>
  32#include <asm/system.h>
  33#include <asm/uaccess.h>
  34
  35#define SBC7240_PREFIX "sbc7240_wdt: "
  36
  37#define SBC7240_ENABLE_PORT             0x443
  38#define SBC7240_DISABLE_PORT            0x043
  39#define SBC7240_SET_TIMEOUT_PORT        SBC7240_ENABLE_PORT
  40#define SBC7240_MAGIC_CHAR              'V'
  41
  42#define SBC7240_TIMEOUT         30
  43#define SBC7240_MAX_TIMEOUT             255
  44static int timeout = SBC7240_TIMEOUT;   /* in seconds */
  45module_param(timeout, int, 0);
  46MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<="
  47                 __MODULE_STRING(SBC7240_MAX_TIMEOUT) ", default="
  48                 __MODULE_STRING(SBC7240_TIMEOUT) ")");
  49
  50static int nowayout = WATCHDOG_NOWAYOUT;
  51module_param(nowayout, int, 0);
  52MODULE_PARM_DESC(nowayout, "Disable watchdog when closing device file");
  53
  54#define SBC7240_OPEN_STATUS_BIT         0
  55#define SBC7240_ENABLED_STATUS_BIT      1
  56#define SBC7240_EXPECT_CLOSE_STATUS_BIT 2
  57static unsigned long wdt_status;
  58
  59/*
  60 * Utility routines
  61 */
  62
  63static void wdt_disable(void)
  64{
  65        /* disable the watchdog */
  66        if (test_and_clear_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
  67                inb_p(SBC7240_DISABLE_PORT);
  68                printk(KERN_INFO SBC7240_PREFIX
  69                       "Watchdog timer is now disabled.\n");
  70        }
  71}
  72
  73static void wdt_enable(void)
  74{
  75        /* enable the watchdog */
  76        if (!test_and_set_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
  77                inb_p(SBC7240_ENABLE_PORT);
  78                printk(KERN_INFO SBC7240_PREFIX
  79                       "Watchdog timer is now enabled.\n");
  80        }
  81}
  82
  83static int wdt_set_timeout(int t)
  84{
  85        if (t < 1 || t > SBC7240_MAX_TIMEOUT) {
  86                printk(KERN_ERR SBC7240_PREFIX
  87                       "timeout value must be 1<=x<=%d\n",
  88                       SBC7240_MAX_TIMEOUT);
  89                return -1;
  90        }
  91        /* set the timeout */
  92        outb_p((unsigned)t, SBC7240_SET_TIMEOUT_PORT);
  93        timeout = t;
  94        printk(KERN_INFO SBC7240_PREFIX "timeout set to %d seconds\n", t);
  95        return 0;
  96}
  97
  98/* Whack the dog */
  99static inline void wdt_keepalive(void)
 100{
 101        if (test_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status))
 102                inb_p(SBC7240_ENABLE_PORT);
 103}
 104
 105/*
 106 * /dev/watchdog handling
 107 */
 108static ssize_t fop_write(struct file *file, const char __user *buf,
 109                         size_t count, loff_t *ppos)
 110{
 111        size_t i;
 112        char c;
 113
 114        if (count) {
 115                if (!nowayout) {
 116                        clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
 117                                &wdt_status);
 118
 119                        /* is there a magic char ? */
 120                        for (i = 0; i != count; i++) {
 121                                if (get_user(c, buf + i))
 122                                        return -EFAULT;
 123                                if (c == SBC7240_MAGIC_CHAR) {
 124                                        set_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
 125                                                &wdt_status);
 126                                        break;
 127                                }
 128                        }
 129                }
 130
 131                wdt_keepalive();
 132        }
 133
 134        return count;
 135}
 136
 137static int fop_open(struct inode *inode, struct file *file)
 138{
 139        if (test_and_set_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status))
 140                return -EBUSY;
 141
 142        wdt_enable();
 143
 144        return nonseekable_open(inode, file);
 145}
 146
 147static int fop_close(struct inode *inode, struct file *file)
 148{
 149        if (test_and_clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, &wdt_status)
 150            || !nowayout) {
 151                wdt_disable();
 152        } else {
 153                printk(KERN_CRIT SBC7240_PREFIX
 154                       "Unexpected close, not stopping watchdog!\n");
 155                wdt_keepalive();
 156        }
 157
 158        clear_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status);
 159        return 0;
 160}
 161
 162static struct watchdog_info ident = {
 163        .options = WDIOF_KEEPALIVEPING|
 164                   WDIOF_SETTIMEOUT|
 165                   WDIOF_MAGICCLOSE,
 166        .firmware_version = 1,
 167        .identity = "SBC7240",
 168};
 169
 170
 171static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
 172                     unsigned long arg)
 173{
 174        switch (cmd) {
 175        case WDIOC_GETSUPPORT:
 176                return copy_to_user
 177                        ((void __user *)arg, &ident, sizeof(ident))
 178                         ? -EFAULT : 0;
 179        case WDIOC_GETSTATUS:
 180        case WDIOC_GETBOOTSTATUS:
 181                return put_user(0, (int __user *)arg);
 182        case WDIOC_KEEPALIVE:
 183                wdt_keepalive();
 184                return 0;
 185        case WDIOC_SETOPTIONS:{
 186                        int options;
 187                        int retval = -EINVAL;
 188
 189                        if (get_user(options, (int __user *)arg))
 190                                return -EFAULT;
 191
 192                        if (options & WDIOS_DISABLECARD) {
 193                                wdt_disable();
 194                                retval = 0;
 195                        }
 196
 197                        if (options & WDIOS_ENABLECARD) {
 198                                wdt_enable();
 199                                retval = 0;
 200                        }
 201
 202                        return retval;
 203                }
 204        case WDIOC_SETTIMEOUT:{
 205                        int new_timeout;
 206
 207                        if (get_user(new_timeout, (int __user *)arg))
 208                                return -EFAULT;
 209
 210                        if (wdt_set_timeout(new_timeout))
 211                                return -EINVAL;
 212
 213                        /* Fall through */
 214                }
 215        case WDIOC_GETTIMEOUT:
 216                return put_user(timeout, (int __user *)arg);
 217        default:
 218                return -ENOTTY;
 219        }
 220}
 221
 222static const struct file_operations wdt_fops = {
 223        .owner = THIS_MODULE,
 224        .llseek = no_llseek,
 225        .write = fop_write,
 226        .open = fop_open,
 227        .release = fop_close,
 228        .ioctl = fop_ioctl,
 229};
 230
 231static struct miscdevice wdt_miscdev = {
 232        .minor = WATCHDOG_MINOR,
 233        .name = "watchdog",
 234        .fops = &wdt_fops,
 235};
 236
 237/*
 238 *      Notifier for system down
 239 */
 240
 241static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
 242                          void *unused)
 243{
 244        if (code == SYS_DOWN || code == SYS_HALT)
 245                wdt_disable();
 246        return NOTIFY_DONE;
 247}
 248
 249static struct notifier_block wdt_notifier = {
 250        .notifier_call = wdt_notify_sys,
 251};
 252
 253static void __exit sbc7240_wdt_unload(void)
 254{
 255        printk(KERN_INFO SBC7240_PREFIX "Removing watchdog\n");
 256        misc_deregister(&wdt_miscdev);
 257
 258        unregister_reboot_notifier(&wdt_notifier);
 259        release_region(SBC7240_ENABLE_PORT, 1);
 260}
 261
 262static int __init sbc7240_wdt_init(void)
 263{
 264        int rc = -EBUSY;
 265
 266        if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) {
 267                printk(KERN_ERR SBC7240_PREFIX
 268                       "I/O address 0x%04x already in use\n",
 269                       SBC7240_ENABLE_PORT);
 270                rc = -EIO;
 271                goto err_out;
 272        }
 273
 274        /* The IO port 0x043 used to disable the watchdog
 275         * is already claimed by the system timer, so we
 276         * cant request_region() it ...*/
 277
 278        if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) {
 279                timeout = SBC7240_TIMEOUT;
 280                printk(KERN_INFO SBC7240_PREFIX
 281                       "timeout value must be 1<=x<=%d, using %d\n",
 282                       SBC7240_MAX_TIMEOUT, timeout);
 283        }
 284        wdt_set_timeout(timeout);
 285        wdt_disable();
 286
 287        rc = register_reboot_notifier(&wdt_notifier);
 288        if (rc) {
 289                printk(KERN_ERR SBC7240_PREFIX
 290                       "cannot register reboot notifier (err=%d)\n", rc);
 291                goto err_out_region;
 292        }
 293
 294        rc = misc_register(&wdt_miscdev);
 295        if (rc) {
 296                printk(KERN_ERR SBC7240_PREFIX
 297                       "cannot register miscdev on minor=%d (err=%d)\n",
 298                       wdt_miscdev.minor, rc);
 299                goto err_out_reboot_notifier;
 300        }
 301
 302        printk(KERN_INFO SBC7240_PREFIX
 303               "Watchdog driver for SBC7240 initialised (nowayout=%d)\n",
 304               nowayout);
 305
 306        return 0;
 307
 308err_out_reboot_notifier:
 309        unregister_reboot_notifier(&wdt_notifier);
 310err_out_region:
 311        release_region(SBC7240_ENABLE_PORT, 1);
 312err_out:
 313        return rc;
 314}
 315
 316module_init(sbc7240_wdt_init);
 317module_exit(sbc7240_wdt_unload);
 318
 319MODULE_AUTHOR("Gilles Gigan");
 320MODULE_DESCRIPTION("Watchdog device driver for single board"
 321                   " computers EPIC Nano 7240 from iEi");
 322MODULE_LICENSE("GPL");
 323MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 324
 325