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ádraig 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#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  30
  31#include <linux/module.h>
  32#include <linux/moduleparam.h>
  33#include <linux/types.h>
  34#include <linux/miscdevice.h>
  35#include <linux/watchdog.h>
  36#include <linux/fs.h>
  37#include <linux/ioport.h>
  38#include <linux/notifier.h>
  39#include <linux/reboot.h>
  40#include <linux/init.h>
  41#include <linux/spinlock.h>
  42#include <linux/io.h>
  43#include <linux/uaccess.h>
  44
  45
  46#define WATCHDOG_NAME "w83627hf/thf/hg/dhg WDT"
  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 bool nowayout = WATCHDOG_NOWAYOUT;
  65module_param(nowayout, bool, 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        } else if (c == 0x88 || c == 0xa0) {    /* W83627EHF / W83627DHG */
  93                outb_p(0x2d, WDT_EFER); /* select GPIO5 */
  94                c = inb_p(WDT_EFDR) & ~0x01; /* PIN77 -> WDT0# */
  95                outb_p(0x2d, WDT_EFER);
  96                outb_p(c, WDT_EFDR); /* set GPIO5 to WDT0 */
  97        }
  98
  99        outb_p(0x07, WDT_EFER); /* point to logical device number reg */
 100        outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */
 101        outb_p(0x30, WDT_EFER); /* select CR30 */
 102        outb_p(0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */
 103}
 104
 105static void w83627hf_unselect_wd_register(void)
 106{
 107        outb_p(0xAA, WDT_EFER); /* Leave extended function mode */
 108}
 109
 110/* tyan motherboards seem to set F5 to 0x4C ?
 111 * So explicitly init to appropriate value. */
 112
 113static void w83627hf_init(void)
 114{
 115        unsigned char t;
 116
 117        w83627hf_select_wd_register();
 118
 119        outb_p(0xF6, WDT_EFER); /* Select CRF6 */
 120        t = inb_p(WDT_EFDR);      /* read CRF6 */
 121        if (t != 0) {
 122                pr_info("Watchdog already running. Resetting timeout to %d sec\n",
 123                        timeout);
 124                outb_p(timeout, WDT_EFDR);    /* Write back to CRF6 */
 125        }
 126
 127        outb_p(0xF5, WDT_EFER); /* Select CRF5 */
 128        t = inb_p(WDT_EFDR);      /* read CRF5 */
 129        t &= ~0x0C;               /* set second mode & disable keyboard
 130                                    turning off watchdog */
 131        t |= 0x02;                /* enable the WDTO# output low pulse
 132                                    to the KBRST# pin (PIN60) */
 133        outb_p(t, WDT_EFDR);    /* Write back to CRF5 */
 134
 135        outb_p(0xF7, WDT_EFER); /* Select CRF7 */
 136        t = inb_p(WDT_EFDR);      /* read CRF7 */
 137        t &= ~0xC0;               /* disable keyboard & mouse turning off
 138                                    watchdog */
 139        outb_p(t, WDT_EFDR);    /* Write back to CRF7 */
 140
 141        w83627hf_unselect_wd_register();
 142}
 143
 144static void wdt_set_time(int timeout)
 145{
 146        spin_lock(&io_lock);
 147
 148        w83627hf_select_wd_register();
 149
 150        outb_p(0xF6, WDT_EFER);    /* Select CRF6 */
 151        outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF6 */
 152
 153        w83627hf_unselect_wd_register();
 154
 155        spin_unlock(&io_lock);
 156}
 157
 158static int wdt_ping(void)
 159{
 160        wdt_set_time(timeout);
 161        return 0;
 162}
 163
 164static int wdt_disable(void)
 165{
 166        wdt_set_time(0);
 167        return 0;
 168}
 169
 170static int wdt_set_heartbeat(int t)
 171{
 172        if (t < 1 || t > 255)
 173                return -EINVAL;
 174        timeout = t;
 175        return 0;
 176}
 177
 178static int wdt_get_time(void)
 179{
 180        int timeleft;
 181
 182        spin_lock(&io_lock);
 183
 184        w83627hf_select_wd_register();
 185
 186        outb_p(0xF6, WDT_EFER);    /* Select CRF6 */
 187        timeleft = inb_p(WDT_EFDR); /* Read Timeout counter to CRF6 */
 188
 189        w83627hf_unselect_wd_register();
 190
 191        spin_unlock(&io_lock);
 192
 193        return timeleft;
 194}
 195
 196static ssize_t wdt_write(struct file *file, const char __user *buf,
 197                                                size_t count, loff_t *ppos)
 198{
 199        if (count) {
 200                if (!nowayout) {
 201                        size_t i;
 202
 203                        expect_close = 0;
 204
 205                        for (i = 0; i != count; i++) {
 206                                char c;
 207                                if (get_user(c, buf + i))
 208                                        return -EFAULT;
 209                                if (c == 'V')
 210                                        expect_close = 42;
 211                        }
 212                }
 213                wdt_ping();
 214        }
 215        return count;
 216}
 217
 218static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 219{
 220        void __user *argp = (void __user *)arg;
 221        int __user *p = argp;
 222        int timeval;
 223        static const struct watchdog_info ident = {
 224                .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
 225                                                        WDIOF_MAGICCLOSE,
 226                .firmware_version = 1,
 227                .identity = "W83627HF WDT",
 228        };
 229
 230        switch (cmd) {
 231        case WDIOC_GETSUPPORT:
 232                if (copy_to_user(argp, &ident, sizeof(ident)))
 233                        return -EFAULT;
 234                break;
 235        case WDIOC_GETSTATUS:
 236        case WDIOC_GETBOOTSTATUS:
 237                return put_user(0, p);
 238        case WDIOC_SETOPTIONS:
 239        {
 240                int options, retval = -EINVAL;
 241
 242                if (get_user(options, p))
 243                        return -EFAULT;
 244                if (options & WDIOS_DISABLECARD) {
 245                        wdt_disable();
 246                        retval = 0;
 247                }
 248                if (options & WDIOS_ENABLECARD) {
 249                        wdt_ping();
 250                        retval = 0;
 251                }
 252                return retval;
 253        }
 254        case WDIOC_KEEPALIVE:
 255                wdt_ping();
 256                break;
 257        case WDIOC_SETTIMEOUT:
 258                if (get_user(timeval, p))
 259                        return -EFAULT;
 260                if (wdt_set_heartbeat(timeval))
 261                        return -EINVAL;
 262                wdt_ping();
 263                /* Fall */
 264        case WDIOC_GETTIMEOUT:
 265                return put_user(timeout, p);
 266        case WDIOC_GETTIMELEFT:
 267                timeval = wdt_get_time();
 268                return put_user(timeval, p);
 269        default:
 270                return -ENOTTY;
 271        }
 272        return 0;
 273}
 274
 275static int wdt_open(struct inode *inode, struct file *file)
 276{
 277        if (test_and_set_bit(0, &wdt_is_open))
 278                return -EBUSY;
 279        /*
 280         *      Activate
 281         */
 282
 283        wdt_ping();
 284        return nonseekable_open(inode, file);
 285}
 286
 287static int wdt_close(struct inode *inode, struct file *file)
 288{
 289        if (expect_close == 42)
 290                wdt_disable();
 291        else {
 292                pr_crit("Unexpected close, not stopping watchdog!\n");
 293                wdt_ping();
 294        }
 295        expect_close = 0;
 296        clear_bit(0, &wdt_is_open);
 297        return 0;
 298}
 299
 300/*
 301 *      Notifier for system down
 302 */
 303
 304static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
 305        void *unused)
 306{
 307        if (code == SYS_DOWN || code == SYS_HALT)
 308                wdt_disable();  /* Turn the WDT off */
 309
 310        return NOTIFY_DONE;
 311}
 312
 313/*
 314 *      Kernel Interfaces
 315 */
 316
 317static const struct file_operations wdt_fops = {
 318        .owner          = THIS_MODULE,
 319        .llseek         = no_llseek,
 320        .write          = wdt_write,
 321        .unlocked_ioctl = wdt_ioctl,
 322        .open           = wdt_open,
 323        .release        = wdt_close,
 324};
 325
 326static struct miscdevice wdt_miscdev = {
 327        .minor = WATCHDOG_MINOR,
 328        .name = "watchdog",
 329        .fops = &wdt_fops,
 330};
 331
 332/*
 333 *      The WDT needs to learn about soft shutdowns in order to
 334 *      turn the timebomb registers off.
 335 */
 336
 337static struct notifier_block wdt_notifier = {
 338        .notifier_call = wdt_notify_sys,
 339};
 340
 341static int __init wdt_init(void)
 342{
 343        int ret;
 344
 345        pr_info("WDT driver for the Winbond(TM) W83627HF/THF/HG/DHG Super I/O chip initialising\n");
 346
 347        if (wdt_set_heartbeat(timeout)) {
 348                wdt_set_heartbeat(WATCHDOG_TIMEOUT);
 349                pr_info("timeout value must be 1 <= timeout <= 255, using %d\n",
 350                        WATCHDOG_TIMEOUT);
 351        }
 352
 353        if (!request_region(wdt_io, 1, WATCHDOG_NAME)) {
 354                pr_err("I/O address 0x%04x already in use\n", wdt_io);
 355                ret = -EIO;
 356                goto out;
 357        }
 358
 359        w83627hf_init();
 360
 361        ret = register_reboot_notifier(&wdt_notifier);
 362        if (ret != 0) {
 363                pr_err("cannot register reboot notifier (err=%d)\n", ret);
 364                goto unreg_regions;
 365        }
 366
 367        ret = misc_register(&wdt_miscdev);
 368        if (ret != 0) {
 369                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 370                       WATCHDOG_MINOR, ret);
 371                goto unreg_reboot;
 372        }
 373
 374        pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
 375                timeout, nowayout);
 376
 377out:
 378        return ret;
 379unreg_reboot:
 380        unregister_reboot_notifier(&wdt_notifier);
 381unreg_regions:
 382        release_region(wdt_io, 1);
 383        goto out;
 384}
 385
 386static void __exit wdt_exit(void)
 387{
 388        misc_deregister(&wdt_miscdev);
 389        unregister_reboot_notifier(&wdt_notifier);
 390        release_region(wdt_io, 1);
 391}
 392
 393module_init(wdt_init);
 394module_exit(wdt_exit);
 395
 396MODULE_LICENSE("GPL");
 397MODULE_AUTHOR("Pádraig  Brady <P@draigBrady.com>");
 398MODULE_DESCRIPTION("w83627hf/thf WDT driver");
 399MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 400
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.