linux/drivers/watchdog/w83627hf_wdt.c
<<
>>
Prefs
   1/*
   2 *      w83627hf/thf WDT driver
   3 *
   4 *      (c) Copyright 2007 Vlad Drukker <vlad@storewiz.com>
   5 *              added support for W83627THF.
   6 *
   7 *      (c) Copyright 2003,2007 P\xE1draig Brady <P@draigBrady.com>
   8 *
   9 *      Based on advantechwdt.c which is based on wdt.c.
  10 *      Original copyright messages:
  11 *
  12 *      (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
  13 *
  14 *      (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
  15 *                                              All Rights Reserved.
  16 *
  17 *      This program is free software; you can redistribute it and/or
  18 *      modify it under the terms of the GNU General Public License
  19 *      as published by the Free Software Foundation; either version
  20 *      2 of the License, or (at your option) any later version.
  21 *
  22 *      Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
  23 *      warranty for any of this software. This material is provided
  24 *      "AS-IS" and at no charge.
  25 *
  26 *      (c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
  27 */
  28
  29#include <linux/module.h>
  30#include <linux/moduleparam.h>
  31#include <linux/types.h>
  32#include <linux/miscdevice.h>
  33#include <linux/watchdog.h>
  34#include <linux/fs.h>
  35#include <linux/ioport.h>
  36#include <linux/notifier.h>
  37#include <linux/reboot.h>
  38#include <linux/init.h>
  39#include <linux/spinlock.h>
  40#include <linux/io.h>
  41#include <linux/uaccess.h>
  42
  43#include <asm/system.h>
  44
  45#define WATCHDOG_NAME "w83627hf/thf/hg WDT"
  46#define PFX WATCHDOG_NAME ": "
  47#define WATCHDOG_TIMEOUT 60             /* 60 sec default timeout */
  48
  49static unsigned long wdt_is_open;
  50static char expect_close;
  51static DEFINE_SPINLOCK(io_lock);
  52
  53/* You must set this - there is no sane way to probe for this board. */
  54static int wdt_io = 0x2E;
  55module_param(wdt_io, int, 0);
  56MODULE_PARM_DESC(wdt_io, "w83627hf/thf WDT io port (default 0x2E)");
  57
  58static int timeout = WATCHDOG_TIMEOUT;  /* in seconds */
  59module_param(timeout, int, 0);
  60MODULE_PARM_DESC(timeout,
  61                "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
  62                                __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
  63
  64static int nowayout = WATCHDOG_NOWAYOUT;
  65module_param(nowayout, int, 0);
  66MODULE_PARM_DESC(nowayout,
  67                "Watchdog cannot be stopped once started (default="
  68                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  69
  70/*
  71 *      Kernel methods.
  72 */
  73
  74#define WDT_EFER (wdt_io+0)   /* Extended Function Enable Registers */
  75#define WDT_EFIR (wdt_io+0)   /* Extended Function Index Register
  76                                                        (same as EFER) */
  77#define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */
  78
  79static void w83627hf_select_wd_register(void)
  80{
  81        unsigned char c;
  82        outb_p(0x87, WDT_EFER); /* Enter extended function mode */
  83        outb_p(0x87, WDT_EFER); /* Again according to manual */
  84
  85        outb(0x20, WDT_EFER);   /* check chip version   */
  86        c = inb(WDT_EFDR);
  87        if (c == 0x82) {        /* W83627THF            */
  88                outb_p(0x2b, WDT_EFER); /* select GPIO3 */
  89                c = ((inb_p(WDT_EFDR) & 0xf7) | 0x04); /* select WDT0 */
  90                outb_p(0x2b, WDT_EFER);
  91                outb_p(c, WDT_EFDR);    /* set GPIO3 to WDT0 */
  92        }
  93
  94        outb_p(0x07, WDT_EFER); /* point to logical device number reg */
  95        outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */
  96        outb_p(0x30, WDT_EFER); /* select CR30 */
  97        outb_p(0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */
  98}
  99
 100static void w83627hf_unselect_wd_register(void)
 101{
 102        outb_p(0xAA, WDT_EFER); /* Leave extended function mode */
 103}
 104
 105/* tyan motherboards seem to set F5 to 0x4C ?
 106 * So explicitly init to appropriate value. */
 107
 108static void w83627hf_init(void)
 109{
 110        unsigned char t;
 111
 112        w83627hf_select_wd_register();
 113
 114        outb_p(0xF6, WDT_EFER); /* Select CRF6 */
 115        t = inb_p(WDT_EFDR);      /* read CRF6 */
 116        if (t != 0) {
 117                printk(KERN_INFO PFX
 118                     "Watchdog already running. Resetting timeout to %d sec\n",
 119                                                                timeout);
 120                outb_p(timeout, WDT_EFDR);    /* Write back to CRF6 */
 121        }
 122
 123        outb_p(0xF5, WDT_EFER); /* Select CRF5 */
 124        t = inb_p(WDT_EFDR);      /* read CRF5 */
 125        t &= ~0x0C;               /* set second mode & disable keyboard
 126                                    turning off watchdog */
 127        outb_p(t, WDT_EFDR);    /* Write back to CRF5 */
 128
 129        outb_p(0xF7, WDT_EFER); /* Select CRF7 */
 130        t = inb_p(WDT_EFDR);      /* read CRF7 */
 131        t &= ~0xC0;               /* disable keyboard & mouse turning off
 132                                    watchdog */
 133        outb_p(t, WDT_EFDR);    /* Write back to CRF7 */
 134
 135        w83627hf_unselect_wd_register();
 136}
 137
 138static void wdt_ctrl(int timeout)
 139{
 140        spin_lock(&io_lock);
 141
 142        w83627hf_select_wd_register();
 143
 144        outb_p(0xF6, WDT_EFER);    /* Select CRF6 */
 145        outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF6 */
 146
 147        w83627hf_unselect_wd_register();
 148
 149        spin_unlock(&io_lock);
 150}
 151
 152static int wdt_ping(void)
 153{
 154        wdt_ctrl(timeout);
 155        return 0;
 156}
 157
 158static int wdt_disable(void)
 159{
 160        wdt_ctrl(0);
 161        return 0;
 162}
 163
 164static int wdt_set_heartbeat(int t)
 165{
 166        if (t < 1 || t > 255)
 167                return -EINVAL;
 168        timeout = t;
 169        return 0;
 170}
 171
 172static ssize_t wdt_write(struct file *file, const char __user *buf,
 173                                                size_t count, loff_t *ppos)
 174{
 175        if (count) {
 176                if (!nowayout) {
 177                        size_t i;
 178
 179                        expect_close = 0;
 180
 181                        for (i = 0; i != count; i++) {
 182                                char c;
 183                                if (get_user(c, buf + i))
 184                                        return -EFAULT;
 185                                if (c == 'V')
 186                                        expect_close = 42;
 187                        }
 188                }
 189                wdt_ping();
 190        }
 191        return count;
 192}
 193
 194static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 195{
 196        void __user *argp = (void __user *)arg;
 197        int __user *p = argp;
 198        int new_timeout;
 199        static struct watchdog_info ident = {
 200                .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
 201                                                        WDIOF_MAGICCLOSE,
 202                .firmware_version = 1,
 203                .identity = "W83627HF WDT",
 204        };
 205
 206        switch (cmd) {
 207        case WDIOC_GETSUPPORT:
 208                if (copy_to_user(argp, &ident, sizeof(ident)))
 209                        return -EFAULT;
 210                break;
 211        case WDIOC_GETSTATUS:
 212        case WDIOC_GETBOOTSTATUS:
 213                return put_user(0, p);
 214        case WDIOC_SETOPTIONS:
 215        {
 216                int options, retval = -EINVAL;
 217
 218                if (get_user(options, p))
 219                        return -EFAULT;
 220                if (options & WDIOS_DISABLECARD) {
 221                        wdt_disable();
 222                        retval = 0;
 223                }
 224                if (options & WDIOS_ENABLECARD) {
 225                        wdt_ping();
 226                        retval = 0;
 227                }
 228                return retval;
 229        }
 230        case WDIOC_KEEPALIVE:
 231                wdt_ping();
 232                break;
 233        case WDIOC_SETTIMEOUT:
 234                if (get_user(new_timeout, p))
 235                        return -EFAULT;
 236                if (wdt_set_heartbeat(new_timeout))
 237                        return -EINVAL;
 238                wdt_ping();
 239                /* Fall */
 240        case WDIOC_GETTIMEOUT:
 241                return put_user(timeout, p);
 242        default:
 243                return -ENOTTY;
 244        }
 245        return 0;
 246}
 247
 248static int wdt_open(struct inode *inode, struct file *file)
 249{
 250        if (test_and_set_bit(0, &wdt_is_open))
 251                return -EBUSY;
 252        /*
 253         *      Activate
 254         */
 255
 256        wdt_ping();
 257        return nonseekable_open(inode, file);
 258}
 259
 260static int wdt_close(struct inode *inode, struct file *file)
 261{
 262        if (expect_close == 42)
 263                wdt_disable();
 264        else {
 265                printk(KERN_CRIT PFX
 266                        "Unexpected close, not stopping watchdog!\n");
 267                wdt_ping();
 268        }
 269        expect_close = 0;
 270        clear_bit(0, &wdt_is_open);
 271        return 0;
 272}
 273
 274/*
 275 *      Notifier for system down
 276 */
 277
 278static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
 279        void *unused)
 280{
 281        if (code == SYS_DOWN || code == SYS_HALT)
 282                wdt_disable();  /* Turn the WDT off */
 283
 284        return NOTIFY_DONE;
 285}
 286
 287/*
 288 *      Kernel Interfaces
 289 */
 290
 291static const struct file_operations wdt_fops = {
 292        .owner          = THIS_MODULE,
 293        .llseek         = no_llseek,
 294        .write          = wdt_write,
 295        .unlocked_ioctl = wdt_ioctl,
 296        .open           = wdt_open,
 297        .release        = wdt_close,
 298};
 299
 300static struct miscdevice wdt_miscdev = {
 301        .minor = WATCHDOG_MINOR,
 302        .name = "watchdog",
 303        .fops = &wdt_fops,
 304};
 305
 306/*
 307 *      The WDT needs to learn about soft shutdowns in order to
 308 *      turn the timebomb registers off.
 309 */
 310
 311static struct notifier_block wdt_notifier = {
 312        .notifier_call = wdt_notify_sys,
 313};
 314
 315static int __init wdt_init(void)
 316{
 317        int ret;
 318
 319        printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG Super I/O chip initialising.\n");
 320
 321        if (wdt_set_heartbeat(timeout)) {
 322                wdt_set_heartbeat(WATCHDOG_TIMEOUT);
 323                printk(KERN_INFO PFX
 324                     "timeout value must be 1 <= timeout <= 255, using %d\n",
 325                                WATCHDOG_TIMEOUT);
 326        }
 327
 328        if (!request_region(wdt_io, 1, WATCHDOG_NAME)) {
 329                printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
 330                        wdt_io);
 331                ret = -EIO;
 332                goto out;
 333        }
 334
 335        w83627hf_init();
 336
 337        ret = register_reboot_notifier(&wdt_notifier);
 338        if (ret != 0) {
 339                printk(KERN_ERR PFX
 340                        "cannot register reboot notifier (err=%d)\n", ret);
 341                goto unreg_regions;
 342        }
 343
 344        ret = misc_register(&wdt_miscdev);
 345        if (ret != 0) {
 346                printk(KERN_ERR PFX
 347                        "cannot register miscdev on minor=%d (err=%d)\n",
 348                                                        WATCHDOG_MINOR, ret);
 349                goto unreg_reboot;
 350        }
 351
 352        printk(KERN_INFO PFX
 353                        "initialized. timeout=%d sec (nowayout=%d)\n",
 354                                                        timeout, nowayout);
 355
 356out:
 357        return ret;
 358unreg_reboot:
 359        unregister_reboot_notifier(&wdt_notifier);
 360unreg_regions:
 361        release_region(wdt_io, 1);
 362        goto out;
 363}
 364
 365static void __exit wdt_exit(void)
 366{
 367        misc_deregister(&wdt_miscdev);
 368        unregister_reboot_notifier(&wdt_notifier);
 369        release_region(wdt_io, 1);
 370}
 371
 372module_init(wdt_init);
 373module_exit(wdt_exit);
 374
 375MODULE_LICENSE("GPL");
 376MODULE_AUTHOR("P\xE1draig Brady <P@draigBrady.com>");
 377MODULE_DESCRIPTION("w83627hf/thf WDT driver");
 378MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 379