linux/drivers/watchdog/w83697hf_wdt.c
<<
>>
Prefs
   1/*
   2 *      w83697hf/hg WDT driver
   3 *
   4 *      (c) Copyright 2006 Samuel Tardieu <sam@rfc1149.net>
   5 *      (c) Copyright 2006 Marcus Junker <junker@anduras.de>
   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 2003 Pádraig Brady <P@draigBrady.com>
  12 *
  13 *      (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
  14 *
  15 *      (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
  16 *                                              All Rights Reserved.
  17 *
  18 *      This program is free software; you can redistribute it and/or
  19 *      modify it under the terms of the GNU General Public License
  20 *      as published by the Free Software Foundation; either version
  21 *      2 of the License, or (at your option) any later version.
  22 *
  23 *      Neither Marcus Junker nor ANDURAS AG admit liability nor provide
  24 *      warranty for any of this software. This material is provided
  25 *      "AS-IS" and at no charge.
  26 */
  27
  28#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  29
  30#include <linux/module.h>
  31#include <linux/moduleparam.h>
  32#include <linux/types.h>
  33#include <linux/miscdevice.h>
  34#include <linux/watchdog.h>
  35#include <linux/fs.h>
  36#include <linux/ioport.h>
  37#include <linux/notifier.h>
  38#include <linux/reboot.h>
  39#include <linux/init.h>
  40#include <linux/spinlock.h>
  41#include <linux/io.h>
  42#include <linux/uaccess.h>
  43
  44
  45#define WATCHDOG_NAME "w83697hf/hg WDT"
  46#define WATCHDOG_TIMEOUT 60             /* 60 sec default timeout */
  47#define WATCHDOG_EARLY_DISABLE 1        /* Disable until userland kicks in */
  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,
  57                "w83697hf/hg WDT io port (default 0x2e, 0 = autodetect)");
  58
  59static int timeout = WATCHDOG_TIMEOUT;  /* in seconds */
  60module_param(timeout, int, 0);
  61MODULE_PARM_DESC(timeout,
  62        "Watchdog timeout in seconds. 1<= timeout <=255 (default="
  63                                __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
  64
  65static bool nowayout = WATCHDOG_NOWAYOUT;
  66module_param(nowayout, bool, 0);
  67MODULE_PARM_DESC(nowayout,
  68        "Watchdog cannot be stopped once started (default="
  69                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  70
  71static int early_disable = WATCHDOG_EARLY_DISABLE;
  72module_param(early_disable, int, 0);
  73MODULE_PARM_DESC(early_disable,
  74        "Watchdog gets disabled at boot time (default="
  75                                __MODULE_STRING(WATCHDOG_EARLY_DISABLE) ")");
  76
  77/*
  78 *      Kernel methods.
  79 */
  80
  81#define W83697HF_EFER (wdt_io + 0)  /* Extended Function Enable Register */
  82#define W83697HF_EFIR (wdt_io + 0)  /* Extended Function Index Register
  83                                                        (same as EFER) */
  84#define W83697HF_EFDR (wdt_io + 1)  /* Extended Function Data Register */
  85
  86static inline void w83697hf_unlock(void)
  87{
  88        outb_p(0x87, W83697HF_EFER);    /* Enter extended function mode */
  89        outb_p(0x87, W83697HF_EFER);    /* Again according to manual */
  90}
  91
  92static inline void w83697hf_lock(void)
  93{
  94        outb_p(0xAA, W83697HF_EFER);    /* Leave extended function mode */
  95}
  96
  97/*
  98 *      The three functions w83697hf_get_reg(), w83697hf_set_reg() and
  99 *      w83697hf_write_timeout() must be called with the device unlocked.
 100 */
 101
 102static unsigned char w83697hf_get_reg(unsigned char reg)
 103{
 104        outb_p(reg, W83697HF_EFIR);
 105        return inb_p(W83697HF_EFDR);
 106}
 107
 108static void w83697hf_set_reg(unsigned char reg, unsigned char data)
 109{
 110        outb_p(reg, W83697HF_EFIR);
 111        outb_p(data, W83697HF_EFDR);
 112}
 113
 114static void w83697hf_write_timeout(int timeout)
 115{
 116        /* Write Timeout counter to CRF4 */
 117        w83697hf_set_reg(0xF4, timeout);
 118}
 119
 120static void w83697hf_select_wdt(void)
 121{
 122        w83697hf_unlock();
 123        w83697hf_set_reg(0x07, 0x08);   /* Switch to logic device 8 (GPIO2) */
 124}
 125
 126static inline void w83697hf_deselect_wdt(void)
 127{
 128        w83697hf_lock();
 129}
 130
 131static void w83697hf_init(void)
 132{
 133        unsigned char bbuf;
 134
 135        w83697hf_select_wdt();
 136
 137        bbuf = w83697hf_get_reg(0x29);
 138        bbuf &= ~0x60;
 139        bbuf |= 0x20;
 140
 141        /* Set pin 119 to WDTO# mode (= CR29, WDT0) */
 142        w83697hf_set_reg(0x29, bbuf);
 143
 144        bbuf = w83697hf_get_reg(0xF3);
 145        bbuf &= ~0x04;
 146        w83697hf_set_reg(0xF3, bbuf);   /* Count mode is seconds */
 147
 148        w83697hf_deselect_wdt();
 149}
 150
 151static void wdt_ping(void)
 152{
 153        spin_lock(&io_lock);
 154        w83697hf_select_wdt();
 155
 156        w83697hf_write_timeout(timeout);
 157
 158        w83697hf_deselect_wdt();
 159        spin_unlock(&io_lock);
 160}
 161
 162static void wdt_enable(void)
 163{
 164        spin_lock(&io_lock);
 165        w83697hf_select_wdt();
 166
 167        w83697hf_write_timeout(timeout);
 168        w83697hf_set_reg(0x30, 1);      /* Enable timer */
 169
 170        w83697hf_deselect_wdt();
 171        spin_unlock(&io_lock);
 172}
 173
 174static void wdt_disable(void)
 175{
 176        spin_lock(&io_lock);
 177        w83697hf_select_wdt();
 178
 179        w83697hf_set_reg(0x30, 0);      /* Disable timer */
 180        w83697hf_write_timeout(0);
 181
 182        w83697hf_deselect_wdt();
 183        spin_unlock(&io_lock);
 184}
 185
 186static unsigned char wdt_running(void)
 187{
 188        unsigned char t;
 189
 190        spin_lock(&io_lock);
 191        w83697hf_select_wdt();
 192
 193        t = w83697hf_get_reg(0xF4);     /* Read timer */
 194
 195        w83697hf_deselect_wdt();
 196        spin_unlock(&io_lock);
 197
 198        return t;
 199}
 200
 201static int wdt_set_heartbeat(int t)
 202{
 203        if (t < 1 || t > 255)
 204                return -EINVAL;
 205
 206        timeout = t;
 207        return 0;
 208}
 209
 210static ssize_t wdt_write(struct file *file, const char __user *buf,
 211                                                size_t count, loff_t *ppos)
 212{
 213        if (count) {
 214                if (!nowayout) {
 215                        size_t i;
 216
 217                        expect_close = 0;
 218
 219                        for (i = 0; i != count; i++) {
 220                                char c;
 221                                if (get_user(c, buf + i))
 222                                        return -EFAULT;
 223                                if (c == 'V')
 224                                        expect_close = 42;
 225                        }
 226                }
 227                wdt_ping();
 228        }
 229        return count;
 230}
 231
 232static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 233{
 234        void __user *argp = (void __user *)arg;
 235        int __user *p = argp;
 236        int new_timeout;
 237        static const struct watchdog_info ident = {
 238                .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
 239                                                        | WDIOF_MAGICCLOSE,
 240                .firmware_version = 1,
 241                .identity = "W83697HF WDT",
 242        };
 243
 244        switch (cmd) {
 245        case WDIOC_GETSUPPORT:
 246                if (copy_to_user(argp, &ident, sizeof(ident)))
 247                        return -EFAULT;
 248                break;
 249
 250        case WDIOC_GETSTATUS:
 251        case WDIOC_GETBOOTSTATUS:
 252                return put_user(0, p);
 253
 254        case WDIOC_SETOPTIONS:
 255        {
 256                int options, retval = -EINVAL;
 257
 258                if (get_user(options, p))
 259                        return -EFAULT;
 260
 261                if (options & WDIOS_DISABLECARD) {
 262                        wdt_disable();
 263                        retval = 0;
 264                }
 265
 266                if (options & WDIOS_ENABLECARD) {
 267                        wdt_enable();
 268                        retval = 0;
 269                }
 270
 271                return retval;
 272        }
 273
 274        case WDIOC_KEEPALIVE:
 275                wdt_ping();
 276                break;
 277
 278        case WDIOC_SETTIMEOUT:
 279                if (get_user(new_timeout, p))
 280                        return -EFAULT;
 281                if (wdt_set_heartbeat(new_timeout))
 282                        return -EINVAL;
 283                wdt_ping();
 284                /* Fall */
 285
 286        case WDIOC_GETTIMEOUT:
 287                return put_user(timeout, p);
 288
 289        default:
 290                return -ENOTTY;
 291        }
 292        return 0;
 293}
 294
 295static int wdt_open(struct inode *inode, struct file *file)
 296{
 297        if (test_and_set_bit(0, &wdt_is_open))
 298                return -EBUSY;
 299        /*
 300         *      Activate
 301         */
 302
 303        wdt_enable();
 304        return nonseekable_open(inode, file);
 305}
 306
 307static int wdt_close(struct inode *inode, struct file *file)
 308{
 309        if (expect_close == 42)
 310                wdt_disable();
 311        else {
 312                pr_crit("Unexpected close, not stopping watchdog!\n");
 313                wdt_ping();
 314        }
 315        expect_close = 0;
 316        clear_bit(0, &wdt_is_open);
 317        return 0;
 318}
 319
 320/*
 321 *      Notifier for system down
 322 */
 323
 324static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
 325        void *unused)
 326{
 327        if (code == SYS_DOWN || code == SYS_HALT)
 328                wdt_disable();  /* Turn the WDT off */
 329
 330        return NOTIFY_DONE;
 331}
 332
 333/*
 334 *      Kernel Interfaces
 335 */
 336
 337static const struct file_operations wdt_fops = {
 338        .owner          = THIS_MODULE,
 339        .llseek         = no_llseek,
 340        .write          = wdt_write,
 341        .unlocked_ioctl = wdt_ioctl,
 342        .open           = wdt_open,
 343        .release        = wdt_close,
 344};
 345
 346static struct miscdevice wdt_miscdev = {
 347        .minor = WATCHDOG_MINOR,
 348        .name = "watchdog",
 349        .fops = &wdt_fops,
 350};
 351
 352/*
 353 *      The WDT needs to learn about soft shutdowns in order to
 354 *      turn the timebomb registers off.
 355 */
 356
 357static struct notifier_block wdt_notifier = {
 358        .notifier_call = wdt_notify_sys,
 359};
 360
 361static int w83697hf_check_wdt(void)
 362{
 363        if (!request_region(wdt_io, 2, WATCHDOG_NAME)) {
 364                pr_err("I/O address 0x%x already in use\n", wdt_io);
 365                return -EIO;
 366        }
 367
 368        pr_debug("Looking for watchdog at address 0x%x\n", wdt_io);
 369        w83697hf_unlock();
 370        if (w83697hf_get_reg(0x20) == 0x60) {
 371                pr_info("watchdog found at address 0x%x\n", wdt_io);
 372                w83697hf_lock();
 373                return 0;
 374        }
 375        /* Reprotect in case it was a compatible device */
 376        w83697hf_lock();
 377
 378        pr_info("watchdog not found at address 0x%x\n", wdt_io);
 379        release_region(wdt_io, 2);
 380        return -EIO;
 381}
 382
 383static int w83697hf_ioports[] = { 0x2e, 0x4e, 0x00 };
 384
 385static int __init wdt_init(void)
 386{
 387        int ret, i, found = 0;
 388
 389        pr_info("WDT driver for W83697HF/HG initializing\n");
 390
 391        if (wdt_io == 0) {
 392                /* we will autodetect the W83697HF/HG watchdog */
 393                for (i = 0; ((!found) && (w83697hf_ioports[i] != 0)); i++) {
 394                        wdt_io = w83697hf_ioports[i];
 395                        if (!w83697hf_check_wdt())
 396                                found++;
 397                }
 398        } else {
 399                if (!w83697hf_check_wdt())
 400                        found++;
 401        }
 402
 403        if (!found) {
 404                pr_err("No W83697HF/HG could be found\n");
 405                ret = -EIO;
 406                goto out;
 407        }
 408
 409        w83697hf_init();
 410        if (early_disable) {
 411                if (wdt_running())
 412                        pr_warn("Stopping previously enabled watchdog until userland kicks in\n");
 413                wdt_disable();
 414        }
 415
 416        if (wdt_set_heartbeat(timeout)) {
 417                wdt_set_heartbeat(WATCHDOG_TIMEOUT);
 418                pr_info("timeout value must be 1 <= timeout <= 255, using %d\n",
 419                        WATCHDOG_TIMEOUT);
 420        }
 421
 422        ret = register_reboot_notifier(&wdt_notifier);
 423        if (ret != 0) {
 424                pr_err("cannot register reboot notifier (err=%d)\n", ret);
 425                goto unreg_regions;
 426        }
 427
 428        ret = misc_register(&wdt_miscdev);
 429        if (ret != 0) {
 430                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 431                       WATCHDOG_MINOR, ret);
 432                goto unreg_reboot;
 433        }
 434
 435        pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
 436                timeout, nowayout);
 437
 438out:
 439        return ret;
 440unreg_reboot:
 441        unregister_reboot_notifier(&wdt_notifier);
 442unreg_regions:
 443        release_region(wdt_io, 2);
 444        goto out;
 445}
 446
 447static void __exit wdt_exit(void)
 448{
 449        misc_deregister(&wdt_miscdev);
 450        unregister_reboot_notifier(&wdt_notifier);
 451        release_region(wdt_io, 2);
 452}
 453
 454module_init(wdt_init);
 455module_exit(wdt_exit);
 456
 457MODULE_LICENSE("GPL");
 458MODULE_AUTHOR("Marcus Junker <junker@anduras.de>, "
 459                "Samuel Tardieu <sam@rfc1149.net>");
 460MODULE_DESCRIPTION("w83697hf/hg WDT driver");
 461MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 462