linux/drivers/watchdog/sbc7240_wdt.c
<<
>>
Prefs
   1/*
   2 *      NANO7240 SBC Watchdog device driver
   3 *
   4 *      Based on w83877f.c by Scott Jennings,
   5 *
   6 *      This program is free software; you can redistribute it and/or modify
   7 *      it under the terms of the GNU General Public License version 2 as
   8 *      published by the Free Software Foundation;
   9 *
  10 *      Software distributed under the License is distributed on an "AS IS"
  11 *      basis, WITHOUT WARRANTY OF ANY KIND, either express or
  12 *      implied. See the License for the specific language governing
  13 *      rights and limitations under the License.
  14 *
  15 *      (c) Copyright 2007  Gilles GIGAN <gilles.gigan@jcu.edu.au>
  16 *
  17 */
  18
  19#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  20
  21#include <linux/fs.h>
  22#include <linux/init.h>
  23#include <linux/ioport.h>
  24#include <linux/jiffies.h>
  25#include <linux/module.h>
  26#include <linux/moduleparam.h>
  27#include <linux/miscdevice.h>
  28#include <linux/notifier.h>
  29#include <linux/reboot.h>
  30#include <linux/types.h>
  31#include <linux/watchdog.h>
  32#include <linux/io.h>
  33#include <linux/uaccess.h>
  34#include <linux/atomic.h>
  35
  36#define SBC7240_ENABLE_PORT             0x443
  37#define SBC7240_DISABLE_PORT            0x043
  38#define SBC7240_SET_TIMEOUT_PORT        SBC7240_ENABLE_PORT
  39#define SBC7240_MAGIC_CHAR              'V'
  40
  41#define SBC7240_TIMEOUT         30
  42#define SBC7240_MAX_TIMEOUT             255
  43static int timeout = SBC7240_TIMEOUT;   /* in seconds */
  44module_param(timeout, int, 0);
  45MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<="
  46                 __MODULE_STRING(SBC7240_MAX_TIMEOUT) ", default="
  47                 __MODULE_STRING(SBC7240_TIMEOUT) ")");
  48
  49static bool nowayout = WATCHDOG_NOWAYOUT;
  50module_param(nowayout, bool, 0);
  51MODULE_PARM_DESC(nowayout, "Disable watchdog when closing device file");
  52
  53#define SBC7240_OPEN_STATUS_BIT         0
  54#define SBC7240_ENABLED_STATUS_BIT      1
  55#define SBC7240_EXPECT_CLOSE_STATUS_BIT 2
  56static unsigned long wdt_status;
  57
  58/*
  59 * Utility routines
  60 */
  61
  62static void wdt_disable(void)
  63{
  64        /* disable the watchdog */
  65        if (test_and_clear_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
  66                inb_p(SBC7240_DISABLE_PORT);
  67                pr_info("Watchdog timer is now disabled\n");
  68        }
  69}
  70
  71static void wdt_enable(void)
  72{
  73        /* enable the watchdog */
  74        if (!test_and_set_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
  75                inb_p(SBC7240_ENABLE_PORT);
  76                pr_info("Watchdog timer is now enabled\n");
  77        }
  78}
  79
  80static int wdt_set_timeout(int t)
  81{
  82        if (t < 1 || t > SBC7240_MAX_TIMEOUT) {
  83                pr_err("timeout value must be 1<=x<=%d\n", SBC7240_MAX_TIMEOUT);
  84                return -1;
  85        }
  86        /* set the timeout */
  87        outb_p((unsigned)t, SBC7240_SET_TIMEOUT_PORT);
  88        timeout = t;
  89        pr_info("timeout set to %d seconds\n", t);
  90        return 0;
  91}
  92
  93/* Whack the dog */
  94static inline void wdt_keepalive(void)
  95{
  96        if (test_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status))
  97                inb_p(SBC7240_ENABLE_PORT);
  98}
  99
 100/*
 101 * /dev/watchdog handling
 102 */
 103static ssize_t fop_write(struct file *file, const char __user *buf,
 104                         size_t count, loff_t *ppos)
 105{
 106        size_t i;
 107        char c;
 108
 109        if (count) {
 110                if (!nowayout) {
 111                        clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
 112                                &wdt_status);
 113
 114                        /* is there a magic char ? */
 115                        for (i = 0; i != count; i++) {
 116                                if (get_user(c, buf + i))
 117                                        return -EFAULT;
 118                                if (c == SBC7240_MAGIC_CHAR) {
 119                                        set_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
 120                                                &wdt_status);
 121                                        break;
 122                                }
 123                        }
 124                }
 125
 126                wdt_keepalive();
 127        }
 128
 129        return count;
 130}
 131
 132static int fop_open(struct inode *inode, struct file *file)
 133{
 134        if (test_and_set_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status))
 135                return -EBUSY;
 136
 137        wdt_enable();
 138
 139        return nonseekable_open(inode, file);
 140}
 141
 142static int fop_close(struct inode *inode, struct file *file)
 143{
 144        if (test_and_clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, &wdt_status)
 145            || !nowayout) {
 146                wdt_disable();
 147        } else {
 148                pr_crit("Unexpected close, not stopping watchdog!\n");
 149                wdt_keepalive();
 150        }
 151
 152        clear_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status);
 153        return 0;
 154}
 155
 156static const struct watchdog_info ident = {
 157        .options = WDIOF_KEEPALIVEPING|
 158                   WDIOF_SETTIMEOUT|
 159                   WDIOF_MAGICCLOSE,
 160        .firmware_version = 1,
 161        .identity = "SBC7240",
 162};
 163
 164
 165static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 166{
 167        switch (cmd) {
 168        case WDIOC_GETSUPPORT:
 169                return copy_to_user((void __user *)arg, &ident, sizeof(ident))
 170                                                 ? -EFAULT : 0;
 171        case WDIOC_GETSTATUS:
 172        case WDIOC_GETBOOTSTATUS:
 173                return put_user(0, (int __user *)arg);
 174        case WDIOC_SETOPTIONS:
 175        {
 176                int options;
 177                int retval = -EINVAL;
 178
 179                if (get_user(options, (int __user *)arg))
 180                        return -EFAULT;
 181
 182                if (options & WDIOS_DISABLECARD) {
 183                        wdt_disable();
 184                        retval = 0;
 185                }
 186
 187                if (options & WDIOS_ENABLECARD) {
 188                        wdt_enable();
 189                        retval = 0;
 190                }
 191
 192                return retval;
 193        }
 194        case WDIOC_KEEPALIVE:
 195                wdt_keepalive();
 196                return 0;
 197        case WDIOC_SETTIMEOUT:
 198        {
 199                int new_timeout;
 200
 201                if (get_user(new_timeout, (int __user *)arg))
 202                        return -EFAULT;
 203
 204                if (wdt_set_timeout(new_timeout))
 205                        return -EINVAL;
 206
 207                /* Fall through */
 208        }
 209        case WDIOC_GETTIMEOUT:
 210                return put_user(timeout, (int __user *)arg);
 211        default:
 212                return -ENOTTY;
 213        }
 214}
 215
 216static const struct file_operations wdt_fops = {
 217        .owner = THIS_MODULE,
 218        .llseek = no_llseek,
 219        .write = fop_write,
 220        .open = fop_open,
 221        .release = fop_close,
 222        .unlocked_ioctl = fop_ioctl,
 223};
 224
 225static struct miscdevice wdt_miscdev = {
 226        .minor = WATCHDOG_MINOR,
 227        .name = "watchdog",
 228        .fops = &wdt_fops,
 229};
 230
 231/*
 232 *      Notifier for system down
 233 */
 234
 235static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
 236                          void *unused)
 237{
 238        if (code == SYS_DOWN || code == SYS_HALT)
 239                wdt_disable();
 240        return NOTIFY_DONE;
 241}
 242
 243static struct notifier_block wdt_notifier = {
 244        .notifier_call = wdt_notify_sys,
 245};
 246
 247static void __exit sbc7240_wdt_unload(void)
 248{
 249        pr_info("Removing watchdog\n");
 250        misc_deregister(&wdt_miscdev);
 251
 252        unregister_reboot_notifier(&wdt_notifier);
 253        release_region(SBC7240_ENABLE_PORT, 1);
 254}
 255
 256static int __init sbc7240_wdt_init(void)
 257{
 258        int rc = -EBUSY;
 259
 260        if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) {
 261                pr_err("I/O address 0x%04x already in use\n",
 262                       SBC7240_ENABLE_PORT);
 263                rc = -EIO;
 264                goto err_out;
 265        }
 266
 267        /* The IO port 0x043 used to disable the watchdog
 268         * is already claimed by the system timer, so we
 269         * can't request_region() it ...*/
 270
 271        if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) {
 272                timeout = SBC7240_TIMEOUT;
 273                pr_info("timeout value must be 1<=x<=%d, using %d\n",
 274                        SBC7240_MAX_TIMEOUT, timeout);
 275        }
 276        wdt_set_timeout(timeout);
 277        wdt_disable();
 278
 279        rc = register_reboot_notifier(&wdt_notifier);
 280        if (rc) {
 281                pr_err("cannot register reboot notifier (err=%d)\n", rc);
 282                goto err_out_region;
 283        }
 284
 285        rc = misc_register(&wdt_miscdev);
 286        if (rc) {
 287                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 288                       wdt_miscdev.minor, rc);
 289                goto err_out_reboot_notifier;
 290        }
 291
 292        pr_info("Watchdog driver for SBC7240 initialised (nowayout=%d)\n",
 293                nowayout);
 294
 295        return 0;
 296
 297err_out_reboot_notifier:
 298        unregister_reboot_notifier(&wdt_notifier);
 299err_out_region:
 300        release_region(SBC7240_ENABLE_PORT, 1);
 301err_out:
 302        return rc;
 303}
 304
 305module_init(sbc7240_wdt_init);
 306module_exit(sbc7240_wdt_unload);
 307
 308MODULE_AUTHOR("Gilles Gigan");
 309MODULE_DESCRIPTION("Watchdog device driver for single board"
 310                   " computers EPIC Nano 7240 from iEi");
 311MODULE_LICENSE("GPL");
 312MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 313
 314