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