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