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