linux/drivers/watchdog/sch311x_wdt.c
<<
>>
Prefs
   1/*
   2 *      sch311x_wdt.c - Driver for the SCH311x Super-I/O chips
   3 *                      integrated watchdog.
   4 *
   5 *      (c) Copyright 2008 Wim Van Sebroeck <wim@iguana.be>.
   6 *
   7 *      This program is free software; you can redistribute it and/or
   8 *      modify it under the terms of the GNU General Public License
   9 *      as published by the Free Software Foundation; either version
  10 *      2 of the License, or (at your option) any later version.
  11 *
  12 *      Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
  13 *      provide warranty for any of this software. This material is
  14 *      provided "AS-IS" and at no charge.
  15 */
  16
  17/*
  18 *      Includes, defines, variables, module parameters, ...
  19 */
  20
  21/* Includes */
  22#include <linux/module.h>               /* For module specific items */
  23#include <linux/moduleparam.h>          /* For new moduleparam's */
  24#include <linux/types.h>                /* For standard types (like size_t) */
  25#include <linux/errno.h>                /* For the -ENODEV/... values */
  26#include <linux/kernel.h>               /* For printk/... */
  27#include <linux/miscdevice.h>           /* For MODULE_ALIAS_MISCDEV
  28                                                        (WATCHDOG_MINOR) */
  29#include <linux/watchdog.h>             /* For the watchdog specific items */
  30#include <linux/init.h>                 /* For __init/__exit/... */
  31#include <linux/fs.h>                   /* For file operations */
  32#include <linux/platform_device.h>      /* For platform_driver framework */
  33#include <linux/ioport.h>               /* For io-port access */
  34#include <linux/spinlock.h>             /* For spin_lock/spin_unlock/... */
  35#include <linux/uaccess.h>              /* For copy_to_user/put_user/... */
  36#include <linux/io.h>                   /* For inb/outb/... */
  37
  38/* Module and version information */
  39#define DRV_NAME        "sch311x_wdt"
  40#define PFX             DRV_NAME ": "
  41
  42/* Runtime registers */
  43#define RESGEN                  0x1d
  44#define GP60                    0x47
  45#define WDT_TIME_OUT            0x65
  46#define WDT_VAL                 0x66
  47#define WDT_CFG                 0x67
  48#define WDT_CTRL                0x68
  49
  50/* internal variables */
  51static unsigned long sch311x_wdt_is_open;
  52static char sch311x_wdt_expect_close;
  53static struct platform_device *sch311x_wdt_pdev;
  54
  55static int sch311x_ioports[] = { 0x2e, 0x4e, 0x162e, 0x164e, 0x00 };
  56
  57static struct { /* The devices private data */
  58        /* the Runtime Register base address */
  59        unsigned short runtime_reg;
  60        /* The card's boot status */
  61        int boot_status;
  62        /* the lock for io operations */
  63        spinlock_t io_lock;
  64} sch311x_wdt_data;
  65
  66/* Module load parameters */
  67static unsigned short force_id;
  68module_param(force_id, ushort, 0);
  69MODULE_PARM_DESC(force_id, "Override the detected device ID");
  70
  71static unsigned short therm_trip;
  72module_param(therm_trip, ushort, 0);
  73MODULE_PARM_DESC(therm_trip, "Should a ThermTrip trigger the reset generator");
  74
  75#define WATCHDOG_TIMEOUT 60             /* 60 sec default timeout */
  76static int timeout = WATCHDOG_TIMEOUT;  /* in seconds */
  77module_param(timeout, int, 0);
  78MODULE_PARM_DESC(timeout,
  79        "Watchdog timeout in seconds. 1<= timeout <=15300, default="
  80                __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
  81
  82static int nowayout = WATCHDOG_NOWAYOUT;
  83module_param(nowayout, int, 0);
  84MODULE_PARM_DESC(nowayout,
  85        "Watchdog cannot be stopped once started (default="
  86                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  87
  88/*
  89 *      Super-IO functions
  90 */
  91
  92static inline void sch311x_sio_enter(int sio_config_port)
  93{
  94        outb(0x55, sio_config_port);
  95}
  96
  97static inline void sch311x_sio_exit(int sio_config_port)
  98{
  99        outb(0xaa, sio_config_port);
 100}
 101
 102static inline int sch311x_sio_inb(int sio_config_port, int reg)
 103{
 104        outb(reg, sio_config_port);
 105        return inb(sio_config_port + 1);
 106}
 107
 108static inline void sch311x_sio_outb(int sio_config_port, int reg, int val)
 109{
 110        outb(reg, sio_config_port);
 111        outb(val, sio_config_port + 1);
 112}
 113
 114/*
 115 *      Watchdog Operations
 116 */
 117
 118static void sch311x_wdt_set_timeout(int t)
 119{
 120        unsigned char timeout_unit = 0x80;
 121
 122        /* When new timeout is bigger then 255 seconds, we will use minutes */
 123        if (t > 255) {
 124                timeout_unit = 0;
 125                t /= 60;
 126        }
 127
 128        /* -- Watchdog Timeout --
 129         * Bit 0-6 (Reserved)
 130         * Bit 7   WDT Time-out Value Units Select
 131         *         (0 = Minutes, 1 = Seconds)
 132         */
 133        outb(timeout_unit, sch311x_wdt_data.runtime_reg + WDT_TIME_OUT);
 134
 135        /* -- Watchdog Timer Time-out Value --
 136         * Bit 0-7 Binary coded units (0=Disabled, 1..255)
 137         */
 138        outb(t, sch311x_wdt_data.runtime_reg + WDT_VAL);
 139}
 140
 141static void sch311x_wdt_start(void)
 142{
 143        spin_lock(&sch311x_wdt_data.io_lock);
 144
 145        /* set watchdog's timeout */
 146        sch311x_wdt_set_timeout(timeout);
 147        /* enable the watchdog */
 148        /* -- General Purpose I/O Bit 6.0 --
 149         * Bit 0,   In/Out: 0 = Output, 1 = Input
 150         * Bit 1,   Polarity: 0 = No Invert, 1 = Invert
 151         * Bit 2-3, Function select: 00 = GPI/O, 01 = LED1, 11 = WDT,
 152         *                           10 = Either Edge Triggered Intr.4
 153         * Bit 4-6  (Reserved)
 154         * Bit 7,   Output Type: 0 = Push Pull Bit, 1 = Open Drain
 155         */
 156        outb(0x0e, sch311x_wdt_data.runtime_reg + GP60);
 157
 158        spin_unlock(&sch311x_wdt_data.io_lock);
 159
 160}
 161
 162static void sch311x_wdt_stop(void)
 163{
 164        spin_lock(&sch311x_wdt_data.io_lock);
 165
 166        /* stop the watchdog */
 167        outb(0x01, sch311x_wdt_data.runtime_reg + GP60);
 168        /* disable timeout by setting it to 0 */
 169        sch311x_wdt_set_timeout(0);
 170
 171        spin_unlock(&sch311x_wdt_data.io_lock);
 172}
 173
 174static void sch311x_wdt_keepalive(void)
 175{
 176        spin_lock(&sch311x_wdt_data.io_lock);
 177        sch311x_wdt_set_timeout(timeout);
 178        spin_unlock(&sch311x_wdt_data.io_lock);
 179}
 180
 181static int sch311x_wdt_set_heartbeat(int t)
 182{
 183        if (t < 1 || t > (255*60))
 184                return -EINVAL;
 185
 186        /* When new timeout is bigger then 255 seconds,
 187         * we will round up to minutes (with a max of 255) */
 188        if (t > 255)
 189                t = (((t - 1) / 60) + 1) * 60;
 190
 191        timeout = t;
 192        return 0;
 193}
 194
 195static void sch311x_wdt_get_status(int *status)
 196{
 197        unsigned char new_status;
 198
 199        *status = 0;
 200
 201        spin_lock(&sch311x_wdt_data.io_lock);
 202
 203        /* -- Watchdog timer control --
 204         * Bit 0   Status Bit: 0 = Timer counting, 1 = Timeout occured
 205         * Bit 1   Reserved
 206         * Bit 2   Force Timeout: 1 = Forces WD timeout event (self-cleaning)
 207         * Bit 3   P20 Force Timeout enabled:
 208         *          0 = P20 activity does not generate the WD timeout event
 209         *          1 = P20 Allows rising edge of P20, from the keyboard
 210         *              controller, to force the WD timeout event.
 211         * Bit 4-7 Reserved
 212         */
 213        new_status = inb(sch311x_wdt_data.runtime_reg + WDT_CTRL);
 214        if (new_status & 0x01)
 215                *status |= WDIOF_CARDRESET;
 216
 217        spin_unlock(&sch311x_wdt_data.io_lock);
 218}
 219
 220/*
 221 *      /dev/watchdog handling
 222 */
 223
 224static ssize_t sch311x_wdt_write(struct file *file, const char __user *buf,
 225                                                size_t count, loff_t *ppos)
 226{
 227        if (count) {
 228                if (!nowayout) {
 229                        size_t i;
 230
 231                        sch311x_wdt_expect_close = 0;
 232
 233                        for (i = 0; i != count; i++) {
 234                                char c;
 235                                if (get_user(c, buf + i))
 236                                        return -EFAULT;
 237                                if (c == 'V')
 238                                        sch311x_wdt_expect_close = 42;
 239                        }
 240                }
 241                sch311x_wdt_keepalive();
 242        }
 243        return count;
 244}
 245
 246static long sch311x_wdt_ioctl(struct file *file, unsigned int cmd,
 247                                                        unsigned long arg)
 248{
 249        int status;
 250        int new_timeout;
 251        void __user *argp = (void __user *)arg;
 252        int __user *p = argp;
 253        static const struct watchdog_info ident = {
 254                .options                = WDIOF_KEEPALIVEPING |
 255                                          WDIOF_SETTIMEOUT |
 256                                          WDIOF_MAGICCLOSE,
 257                .firmware_version       = 1,
 258                .identity               = DRV_NAME,
 259        };
 260
 261        switch (cmd) {
 262        case WDIOC_GETSUPPORT:
 263                if (copy_to_user(argp, &ident, sizeof(ident)))
 264                        return -EFAULT;
 265                break;
 266
 267        case WDIOC_GETSTATUS:
 268        {
 269                sch311x_wdt_get_status(&status);
 270                return put_user(status, p);
 271        }
 272        case WDIOC_GETBOOTSTATUS:
 273                return put_user(sch311x_wdt_data.boot_status, p);
 274
 275        case WDIOC_SETOPTIONS:
 276        {
 277                int options, retval = -EINVAL;
 278
 279                if (get_user(options, p))
 280                        return -EFAULT;
 281                if (options & WDIOS_DISABLECARD) {
 282                        sch311x_wdt_stop();
 283                        retval = 0;
 284                }
 285                if (options & WDIOS_ENABLECARD) {
 286                        sch311x_wdt_start();
 287                        retval = 0;
 288                }
 289                return retval;
 290        }
 291        case WDIOC_KEEPALIVE:
 292                sch311x_wdt_keepalive();
 293                break;
 294
 295        case WDIOC_SETTIMEOUT:
 296                if (get_user(new_timeout, p))
 297                        return -EFAULT;
 298                if (sch311x_wdt_set_heartbeat(new_timeout))
 299                        return -EINVAL;
 300                sch311x_wdt_keepalive();
 301                /* Fall */
 302        case WDIOC_GETTIMEOUT:
 303                return put_user(timeout, p);
 304        default:
 305                return -ENOTTY;
 306        }
 307        return 0;
 308}
 309
 310static int sch311x_wdt_open(struct inode *inode, struct file *file)
 311{
 312        if (test_and_set_bit(0, &sch311x_wdt_is_open))
 313                return -EBUSY;
 314        /*
 315         *      Activate
 316         */
 317        sch311x_wdt_start();
 318        return nonseekable_open(inode, file);
 319}
 320
 321static int sch311x_wdt_close(struct inode *inode, struct file *file)
 322{
 323        if (sch311x_wdt_expect_close == 42) {
 324                sch311x_wdt_stop();
 325        } else {
 326                printk(KERN_CRIT PFX
 327                                "Unexpected close, not stopping watchdog!\n");
 328                sch311x_wdt_keepalive();
 329        }
 330        clear_bit(0, &sch311x_wdt_is_open);
 331        sch311x_wdt_expect_close = 0;
 332        return 0;
 333}
 334
 335/*
 336 *      Kernel Interfaces
 337 */
 338
 339static const struct file_operations sch311x_wdt_fops = {
 340        .owner          = THIS_MODULE,
 341        .llseek         = no_llseek,
 342        .write          = sch311x_wdt_write,
 343        .unlocked_ioctl = sch311x_wdt_ioctl,
 344        .open           = sch311x_wdt_open,
 345        .release        = sch311x_wdt_close,
 346};
 347
 348static struct miscdevice sch311x_wdt_miscdev = {
 349        .minor  = WATCHDOG_MINOR,
 350        .name   = "watchdog",
 351        .fops   = &sch311x_wdt_fops,
 352};
 353
 354/*
 355 *      Init & exit routines
 356 */
 357
 358static int __devinit sch311x_wdt_probe(struct platform_device *pdev)
 359{
 360        struct device *dev = &pdev->dev;
 361        unsigned char val;
 362        int err;
 363
 364        spin_lock_init(&sch311x_wdt_data.io_lock);
 365
 366        if (!request_region(sch311x_wdt_data.runtime_reg + RESGEN, 1,
 367                                                                DRV_NAME)) {
 368                dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n",
 369                        sch311x_wdt_data.runtime_reg + RESGEN,
 370                        sch311x_wdt_data.runtime_reg + RESGEN);
 371                err = -EBUSY;
 372                goto exit;
 373        }
 374
 375        if (!request_region(sch311x_wdt_data.runtime_reg + GP60, 1, DRV_NAME)) {
 376                dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n",
 377                        sch311x_wdt_data.runtime_reg + GP60,
 378                        sch311x_wdt_data.runtime_reg + GP60);
 379                err = -EBUSY;
 380                goto exit_release_region;
 381        }
 382
 383        if (!request_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4,
 384                                                                DRV_NAME)) {
 385                dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n",
 386                        sch311x_wdt_data.runtime_reg + WDT_TIME_OUT,
 387                        sch311x_wdt_data.runtime_reg + WDT_CTRL);
 388                err = -EBUSY;
 389                goto exit_release_region2;
 390        }
 391
 392        /* Make sure that the watchdog is not running */
 393        sch311x_wdt_stop();
 394
 395        /* Disable keyboard and mouse interaction and interrupt */
 396        /* -- Watchdog timer configuration --
 397         * Bit 0   Reserved
 398         * Bit 1   Keyboard enable: 0* = No Reset, 1 = Reset WDT upon KBD Intr.
 399         * Bit 2   Mouse enable: 0* = No Reset, 1 = Reset WDT upon Mouse Intr
 400         * Bit 3   Reserved
 401         * Bit 4-7 WDT Interrupt Mapping: (0000* = Disabled,
 402         *            0001=IRQ1, 0010=(Invalid), 0011=IRQ3 to 1111=IRQ15)
 403         */
 404        outb(0, sch311x_wdt_data.runtime_reg + WDT_CFG);
 405
 406        /* Check that the heartbeat value is within it's range ;
 407         * if not reset to the default */
 408        if (sch311x_wdt_set_heartbeat(timeout)) {
 409                sch311x_wdt_set_heartbeat(WATCHDOG_TIMEOUT);
 410                dev_info(dev, "timeout value must be 1<=x<=15300, using %d\n",
 411                        timeout);
 412        }
 413
 414        /* Get status at boot */
 415        sch311x_wdt_get_status(&sch311x_wdt_data.boot_status);
 416
 417        /* enable watchdog */
 418        /* -- Reset Generator --
 419         * Bit 0   Enable Watchdog Timer Generation: 0* = Enabled, 1 = Disabled
 420         * Bit 1   Thermtrip Source Select: O* = No Source, 1 = Source
 421         * Bit 2   WDT2_CTL: WDT input bit
 422         * Bit 3-7 Reserved
 423         */
 424        outb(0, sch311x_wdt_data.runtime_reg + RESGEN);
 425        val = therm_trip ? 0x06 : 0x04;
 426        outb(val, sch311x_wdt_data.runtime_reg + RESGEN);
 427
 428        sch311x_wdt_miscdev.parent = dev;
 429
 430        err = misc_register(&sch311x_wdt_miscdev);
 431        if (err != 0) {
 432                dev_err(dev, "cannot register miscdev on minor=%d (err=%d)\n",
 433                                                        WATCHDOG_MINOR, err);
 434                goto exit_release_region3;
 435        }
 436
 437        dev_info(dev,
 438                "SMSC SCH311x WDT initialized. timeout=%d sec (nowayout=%d)\n",
 439                timeout, nowayout);
 440
 441        return 0;
 442
 443exit_release_region3:
 444        release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4);
 445exit_release_region2:
 446        release_region(sch311x_wdt_data.runtime_reg + GP60, 1);
 447exit_release_region:
 448        release_region(sch311x_wdt_data.runtime_reg + RESGEN, 1);
 449        sch311x_wdt_data.runtime_reg = 0;
 450exit:
 451        return err;
 452}
 453
 454static int __devexit sch311x_wdt_remove(struct platform_device *pdev)
 455{
 456        /* Stop the timer before we leave */
 457        if (!nowayout)
 458                sch311x_wdt_stop();
 459
 460        /* Deregister */
 461        misc_deregister(&sch311x_wdt_miscdev);
 462        release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4);
 463        release_region(sch311x_wdt_data.runtime_reg + GP60, 1);
 464        release_region(sch311x_wdt_data.runtime_reg + RESGEN, 1);
 465        sch311x_wdt_data.runtime_reg = 0;
 466        return 0;
 467}
 468
 469static void sch311x_wdt_shutdown(struct platform_device *dev)
 470{
 471        /* Turn the WDT off if we have a soft shutdown */
 472        sch311x_wdt_stop();
 473}
 474
 475#define sch311x_wdt_suspend NULL
 476#define sch311x_wdt_resume  NULL
 477
 478static struct platform_driver sch311x_wdt_driver = {
 479        .probe          = sch311x_wdt_probe,
 480        .remove         = __devexit_p(sch311x_wdt_remove),
 481        .shutdown       = sch311x_wdt_shutdown,
 482        .suspend        = sch311x_wdt_suspend,
 483        .resume         = sch311x_wdt_resume,
 484        .driver         = {
 485                .owner = THIS_MODULE,
 486                .name = DRV_NAME,
 487        },
 488};
 489
 490static int __init sch311x_detect(int sio_config_port, unsigned short *addr)
 491{
 492        int err = 0, reg;
 493        unsigned short base_addr;
 494        unsigned char dev_id;
 495
 496        sch311x_sio_enter(sio_config_port);
 497
 498        /* Check device ID. We currently know about:
 499         * SCH3112 (0x7c), SCH3114 (0x7d), and SCH3116 (0x7f). */
 500        reg = force_id ? force_id : sch311x_sio_inb(sio_config_port, 0x20);
 501        if (!(reg == 0x7c || reg == 0x7d || reg == 0x7f)) {
 502                err = -ENODEV;
 503                goto exit;
 504        }
 505        dev_id = reg == 0x7c ? 2 : reg == 0x7d ? 4 : 6;
 506
 507        /* Select logical device A (runtime registers) */
 508        sch311x_sio_outb(sio_config_port, 0x07, 0x0a);
 509
 510        /* Check if Logical Device Register is currently active */
 511        if (sch311x_sio_inb(sio_config_port, 0x30) && 0x01 == 0)
 512                printk(KERN_INFO PFX "Seems that LDN 0x0a is not active...\n");
 513
 514        /* Get the base address of the runtime registers */
 515        base_addr = (sch311x_sio_inb(sio_config_port, 0x60) << 8) |
 516                           sch311x_sio_inb(sio_config_port, 0x61);
 517        if (!base_addr) {
 518                printk(KERN_ERR PFX "Base address not set.\n");
 519                err = -ENODEV;
 520                goto exit;
 521        }
 522        *addr = base_addr;
 523
 524        printk(KERN_INFO PFX "Found an SMSC SCH311%d chip at 0x%04x\n",
 525                dev_id, base_addr);
 526
 527exit:
 528        sch311x_sio_exit(sio_config_port);
 529        return err;
 530}
 531
 532static int __init sch311x_wdt_init(void)
 533{
 534        int err, i, found = 0;
 535        unsigned short addr = 0;
 536
 537        for (i = 0; !found && sch311x_ioports[i]; i++)
 538                if (sch311x_detect(sch311x_ioports[i], &addr) == 0)
 539                        found++;
 540
 541        if (!found)
 542                return -ENODEV;
 543
 544        sch311x_wdt_data.runtime_reg = addr;
 545
 546        err = platform_driver_register(&sch311x_wdt_driver);
 547        if (err)
 548                return err;
 549
 550        sch311x_wdt_pdev = platform_device_register_simple(DRV_NAME, addr,
 551                                                                NULL, 0);
 552
 553        if (IS_ERR(sch311x_wdt_pdev)) {
 554                err = PTR_ERR(sch311x_wdt_pdev);
 555                goto unreg_platform_driver;
 556        }
 557
 558        return 0;
 559
 560unreg_platform_driver:
 561        platform_driver_unregister(&sch311x_wdt_driver);
 562        return err;
 563}
 564
 565static void __exit sch311x_wdt_exit(void)
 566{
 567        platform_device_unregister(sch311x_wdt_pdev);
 568        platform_driver_unregister(&sch311x_wdt_driver);
 569}
 570
 571module_init(sch311x_wdt_init);
 572module_exit(sch311x_wdt_exit);
 573
 574MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
 575MODULE_DESCRIPTION("SMSC SCH311x WatchDog Timer Driver");
 576MODULE_LICENSE("GPL");
 577MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 578
 579