linux/drivers/watchdog/alim1535_wdt.c
<<
>>
Prefs
   1/*
   2 *      Watchdog for the 7101 PMU version found in the ALi M1535 chipsets
   3 *
   4 *      This program is free software; you can redistribute it and/or
   5 *      modify it under the terms of the GNU General Public License
   6 *      as published by the Free Software Foundation; either version
   7 *      2 of the License, or (at your option) any later version.
   8 */
   9
  10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  11
  12#include <linux/module.h>
  13#include <linux/moduleparam.h>
  14#include <linux/types.h>
  15#include <linux/miscdevice.h>
  16#include <linux/watchdog.h>
  17#include <linux/ioport.h>
  18#include <linux/notifier.h>
  19#include <linux/reboot.h>
  20#include <linux/init.h>
  21#include <linux/fs.h>
  22#include <linux/pci.h>
  23#include <linux/uaccess.h>
  24#include <linux/io.h>
  25
  26#define WATCHDOG_NAME "ALi_M1535"
  27#define WATCHDOG_TIMEOUT 60     /* 60 sec default timeout */
  28
  29/* internal variables */
  30static unsigned long ali_is_open;
  31static char ali_expect_release;
  32static struct pci_dev *ali_pci;
  33static u32 ali_timeout_bits;            /* stores the computed timeout */
  34static DEFINE_SPINLOCK(ali_lock);       /* Guards the hardware */
  35
  36/* module parameters */
  37static int timeout = WATCHDOG_TIMEOUT;
  38module_param(timeout, int, 0);
  39MODULE_PARM_DESC(timeout,
  40                "Watchdog timeout in seconds. (0 < timeout < 18000, default="
  41                                __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
  42
  43static bool nowayout = WATCHDOG_NOWAYOUT;
  44module_param(nowayout, bool, 0);
  45MODULE_PARM_DESC(nowayout,
  46                "Watchdog cannot be stopped once started (default="
  47                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  48
  49/*
  50 *      ali_start       -       start watchdog countdown
  51 *
  52 *      Starts the timer running providing the timer has a counter
  53 *      configuration set.
  54 */
  55
  56static void ali_start(void)
  57{
  58        u32 val;
  59
  60        spin_lock(&ali_lock);
  61
  62        pci_read_config_dword(ali_pci, 0xCC, &val);
  63        val &= ~0x3F;   /* Mask count */
  64        val |= (1 << 25) | ali_timeout_bits;
  65        pci_write_config_dword(ali_pci, 0xCC, val);
  66
  67        spin_unlock(&ali_lock);
  68}
  69
  70/*
  71 *      ali_stop        -       stop the timer countdown
  72 *
  73 *      Stop the ALi watchdog countdown
  74 */
  75
  76static void ali_stop(void)
  77{
  78        u32 val;
  79
  80        spin_lock(&ali_lock);
  81
  82        pci_read_config_dword(ali_pci, 0xCC, &val);
  83        val &= ~0x3F;           /* Mask count to zero (disabled) */
  84        val &= ~(1 << 25);      /* and for safety mask the reset enable */
  85        pci_write_config_dword(ali_pci, 0xCC, val);
  86
  87        spin_unlock(&ali_lock);
  88}
  89
  90/*
  91 *      ali_keepalive   -       send a keepalive to the watchdog
  92 *
  93 *      Send a keepalive to the timer (actually we restart the timer).
  94 */
  95
  96static void ali_keepalive(void)
  97{
  98        ali_start();
  99}
 100
 101/*
 102 *      ali_settimer    -       compute the timer reload value
 103 *      @t: time in seconds
 104 *
 105 *      Computes the timeout values needed
 106 */
 107
 108static int ali_settimer(int t)
 109{
 110        if (t < 0)
 111                return -EINVAL;
 112        else if (t < 60)
 113                ali_timeout_bits = t|(1 << 6);
 114        else if (t < 3600)
 115                ali_timeout_bits = (t / 60)|(1 << 7);
 116        else if (t < 18000)
 117                ali_timeout_bits = (t / 300)|(1 << 6)|(1 << 7);
 118        else
 119                return -EINVAL;
 120
 121        timeout = t;
 122        return 0;
 123}
 124
 125/*
 126 *      /dev/watchdog handling
 127 */
 128
 129/*
 130 *      ali_write       -       writes to ALi watchdog
 131 *      @file: file from VFS
 132 *      @data: user address of data
 133 *      @len: length of data
 134 *      @ppos: pointer to the file offset
 135 *
 136 *      Handle a write to the ALi watchdog. Writing to the file pings
 137 *      the watchdog and resets it. Writing the magic 'V' sequence allows
 138 *      the next close to turn off the watchdog.
 139 */
 140
 141static ssize_t ali_write(struct file *file, const char __user *data,
 142                                                size_t len, loff_t *ppos)
 143{
 144        /* See if we got the magic character 'V' and reload the timer */
 145        if (len) {
 146                if (!nowayout) {
 147                        size_t i;
 148
 149                        /* note: just in case someone wrote the
 150                           magic character five months ago... */
 151                        ali_expect_release = 0;
 152
 153                        /* scan to see whether or not we got
 154                           the magic character */
 155                        for (i = 0; i != len; i++) {
 156                                char c;
 157                                if (get_user(c, data + i))
 158                                        return -EFAULT;
 159                                if (c == 'V')
 160                                        ali_expect_release = 42;
 161                        }
 162                }
 163
 164                /* someone wrote to us, we should reload the timer */
 165                ali_start();
 166        }
 167        return len;
 168}
 169
 170/*
 171 *      ali_ioctl       -       handle watchdog ioctls
 172 *      @file: VFS file pointer
 173 *      @cmd: ioctl number
 174 *      @arg: arguments to the ioctl
 175 *
 176 *      Handle the watchdog ioctls supported by the ALi driver. Really
 177 *      we want an extension to enable irq ack monitoring and the like
 178 */
 179
 180static long ali_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 181{
 182        void __user *argp = (void __user *)arg;
 183        int __user *p = argp;
 184        static const struct watchdog_info ident = {
 185                .options =              WDIOF_KEEPALIVEPING |
 186                                        WDIOF_SETTIMEOUT |
 187                                        WDIOF_MAGICCLOSE,
 188                .firmware_version =     0,
 189                .identity =             "ALi M1535 WatchDog Timer",
 190        };
 191
 192        switch (cmd) {
 193        case WDIOC_GETSUPPORT:
 194                return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
 195
 196        case WDIOC_GETSTATUS:
 197        case WDIOC_GETBOOTSTATUS:
 198                return put_user(0, p);
 199        case WDIOC_SETOPTIONS:
 200        {
 201                int new_options, retval = -EINVAL;
 202
 203                if (get_user(new_options, p))
 204                        return -EFAULT;
 205                if (new_options & WDIOS_DISABLECARD) {
 206                        ali_stop();
 207                        retval = 0;
 208                }
 209                if (new_options & WDIOS_ENABLECARD) {
 210                        ali_start();
 211                        retval = 0;
 212                }
 213                return retval;
 214        }
 215        case WDIOC_KEEPALIVE:
 216                ali_keepalive();
 217                return 0;
 218        case WDIOC_SETTIMEOUT:
 219        {
 220                int new_timeout;
 221                if (get_user(new_timeout, p))
 222                        return -EFAULT;
 223                if (ali_settimer(new_timeout))
 224                        return -EINVAL;
 225                ali_keepalive();
 226                /* Fall */
 227        }
 228        case WDIOC_GETTIMEOUT:
 229                return put_user(timeout, p);
 230        default:
 231                return -ENOTTY;
 232        }
 233}
 234
 235/*
 236 *      ali_open        -       handle open of ali watchdog
 237 *      @inode: inode from VFS
 238 *      @file: file from VFS
 239 *
 240 *      Open the ALi watchdog device. Ensure only one person opens it
 241 *      at a time. Also start the watchdog running.
 242 */
 243
 244static int ali_open(struct inode *inode, struct file *file)
 245{
 246        /* /dev/watchdog can only be opened once */
 247        if (test_and_set_bit(0, &ali_is_open))
 248                return -EBUSY;
 249
 250        /* Activate */
 251        ali_start();
 252        return nonseekable_open(inode, file);
 253}
 254
 255/*
 256 *      ali_release     -       close an ALi watchdog
 257 *      @inode: inode from VFS
 258 *      @file: file from VFS
 259 *
 260 *      Close the ALi watchdog device. Actual shutdown of the timer
 261 *      only occurs if the magic sequence has been set.
 262 */
 263
 264static int ali_release(struct inode *inode, struct file *file)
 265{
 266        /*
 267         *      Shut off the timer.
 268         */
 269        if (ali_expect_release == 42)
 270                ali_stop();
 271        else {
 272                pr_crit("Unexpected close, not stopping watchdog!\n");
 273                ali_keepalive();
 274        }
 275        clear_bit(0, &ali_is_open);
 276        ali_expect_release = 0;
 277        return 0;
 278}
 279
 280/*
 281 *      ali_notify_sys  -       System down notifier
 282 *
 283 *      Notifier for system down
 284 */
 285
 286
 287static int ali_notify_sys(struct notifier_block *this,
 288                                        unsigned long code, void *unused)
 289{
 290        if (code == SYS_DOWN || code == SYS_HALT)
 291                ali_stop();             /* Turn the WDT off */
 292        return NOTIFY_DONE;
 293}
 294
 295/*
 296 *      Data for PCI driver interface
 297 *
 298 *      This data only exists for exporting the supported
 299 *      PCI ids via MODULE_DEVICE_TABLE.  We do not actually
 300 *      register a pci_driver, because someone else might one day
 301 *      want to register another driver on the same PCI id.
 302 */
 303
 304static DEFINE_PCI_DEVICE_TABLE(ali_pci_tbl) __used = {
 305        { PCI_VENDOR_ID_AL, 0x1533, PCI_ANY_ID, PCI_ANY_ID,},
 306        { PCI_VENDOR_ID_AL, 0x1535, PCI_ANY_ID, PCI_ANY_ID,},
 307        { 0, },
 308};
 309MODULE_DEVICE_TABLE(pci, ali_pci_tbl);
 310
 311/*
 312 *      ali_find_watchdog       -       find a 1535 and 7101
 313 *
 314 *      Scans the PCI hardware for a 1535 series bridge and matching 7101
 315 *      watchdog device. This may be overtight but it is better to be safe
 316 */
 317
 318static int __init ali_find_watchdog(void)
 319{
 320        struct pci_dev *pdev;
 321        u32 wdog;
 322
 323        /* Check for a 1533/1535 series bridge */
 324        pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x1535, NULL);
 325        if (pdev == NULL)
 326                pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x1533, NULL);
 327        if (pdev == NULL)
 328                return -ENODEV;
 329        pci_dev_put(pdev);
 330
 331        /* Check for the a 7101 PMU */
 332        pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x7101, NULL);
 333        if (pdev == NULL)
 334                return -ENODEV;
 335
 336        if (pci_enable_device(pdev)) {
 337                pci_dev_put(pdev);
 338                return -EIO;
 339        }
 340
 341        ali_pci = pdev;
 342
 343        /*
 344         *      Initialize the timer bits
 345         */
 346        pci_read_config_dword(pdev, 0xCC, &wdog);
 347
 348        /* Timer bits */
 349        wdog &= ~0x3F;
 350        /* Issued events */
 351        wdog &= ~((1 << 27)|(1 << 26)|(1 << 25)|(1 << 24));
 352        /* No monitor bits */
 353        wdog &= ~((1 << 16)|(1 << 13)|(1 << 12)|(1 << 11)|(1 << 10)|(1 << 9));
 354
 355        pci_write_config_dword(pdev, 0xCC, wdog);
 356
 357        return 0;
 358}
 359
 360/*
 361 *      Kernel Interfaces
 362 */
 363
 364static const struct file_operations ali_fops = {
 365        .owner          =       THIS_MODULE,
 366        .llseek         =       no_llseek,
 367        .write          =       ali_write,
 368        .unlocked_ioctl =       ali_ioctl,
 369        .open           =       ali_open,
 370        .release        =       ali_release,
 371};
 372
 373static struct miscdevice ali_miscdev = {
 374        .minor =        WATCHDOG_MINOR,
 375        .name =         "watchdog",
 376        .fops =         &ali_fops,
 377};
 378
 379static struct notifier_block ali_notifier = {
 380        .notifier_call =        ali_notify_sys,
 381};
 382
 383/*
 384 *      watchdog_init   -       module initialiser
 385 *
 386 *      Scan for a suitable watchdog and if so initialize it. Return an error
 387 *      if we cannot, the error causes the module to unload
 388 */
 389
 390static int __init watchdog_init(void)
 391{
 392        int ret;
 393
 394        /* Check whether or not the hardware watchdog is there */
 395        if (ali_find_watchdog() != 0)
 396                return -ENODEV;
 397
 398        /* Check that the timeout value is within it's range;
 399           if not reset to the default */
 400        if (timeout < 1 || timeout >= 18000) {
 401                timeout = WATCHDOG_TIMEOUT;
 402                pr_info("timeout value must be 0 < timeout < 18000, using %d\n",
 403                        timeout);
 404        }
 405
 406        /* Calculate the watchdog's timeout */
 407        ali_settimer(timeout);
 408
 409        ret = register_reboot_notifier(&ali_notifier);
 410        if (ret != 0) {
 411                pr_err("cannot register reboot notifier (err=%d)\n", ret);
 412                goto out;
 413        }
 414
 415        ret = misc_register(&ali_miscdev);
 416        if (ret != 0) {
 417                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 418                       WATCHDOG_MINOR, ret);
 419                goto unreg_reboot;
 420        }
 421
 422        pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
 423                timeout, nowayout);
 424
 425out:
 426        return ret;
 427unreg_reboot:
 428        unregister_reboot_notifier(&ali_notifier);
 429        goto out;
 430}
 431
 432/*
 433 *      watchdog_exit   -       module de-initialiser
 434 *
 435 *      Called while unloading a successfully installed watchdog module.
 436 */
 437
 438static void __exit watchdog_exit(void)
 439{
 440        /* Stop the timer before we leave */
 441        ali_stop();
 442
 443        /* Deregister */
 444        misc_deregister(&ali_miscdev);
 445        unregister_reboot_notifier(&ali_notifier);
 446        pci_dev_put(ali_pci);
 447}
 448
 449module_init(watchdog_init);
 450module_exit(watchdog_exit);
 451
 452MODULE_AUTHOR("Alan Cox");
 453MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver");
 454MODULE_LICENSE("GPL");
 455MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 456
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.