linux/drivers/watchdog/sbc_fitpc2_wdt.c
<<
>>
Prefs
   1/*
   2 * Watchdog driver for SBC-FITPC2 board
   3 *
   4 * Author: Denis Turischev <denis@compulab.co.il>
   5 *
   6 * Adapted from the IXP2000 watchdog driver by Deepak Saxena.
   7 *
   8 * This file is licensed under  the terms of the GNU General Public
   9 * License version 2. This program is licensed "as is" without any
  10 * warranty of any kind, whether express or implied.
  11 */
  12
  13#define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt
  14
  15#include <linux/module.h>
  16#include <linux/types.h>
  17#include <linux/miscdevice.h>
  18#include <linux/watchdog.h>
  19#include <linux/ioport.h>
  20#include <linux/delay.h>
  21#include <linux/fs.h>
  22#include <linux/init.h>
  23#include <linux/moduleparam.h>
  24#include <linux/dmi.h>
  25#include <linux/io.h>
  26#include <linux/uaccess.h>
  27
  28#include <asm/system.h>
  29
  30static int nowayout = WATCHDOG_NOWAYOUT;
  31static unsigned int margin = 60;        /* (secs) Default is 1 minute */
  32static unsigned long wdt_status;
  33static DEFINE_MUTEX(wdt_lock);
  34
  35#define WDT_IN_USE              0
  36#define WDT_OK_TO_CLOSE         1
  37
  38#define COMMAND_PORT            0x4c
  39#define DATA_PORT               0x48
  40
  41#define IFACE_ON_COMMAND        1
  42#define REBOOT_COMMAND          2
  43
  44#define WATCHDOG_NAME           "SBC-FITPC2 Watchdog"
  45
  46static void wdt_send_data(unsigned char command, unsigned char data)
  47{
  48        outb(data, DATA_PORT);
  49        msleep(200);
  50        outb(command, COMMAND_PORT);
  51        msleep(100);
  52}
  53
  54static void wdt_enable(void)
  55{
  56        mutex_lock(&wdt_lock);
  57        wdt_send_data(IFACE_ON_COMMAND, 1);
  58        wdt_send_data(REBOOT_COMMAND, margin);
  59        mutex_unlock(&wdt_lock);
  60}
  61
  62static void wdt_disable(void)
  63{
  64        mutex_lock(&wdt_lock);
  65        wdt_send_data(IFACE_ON_COMMAND, 0);
  66        wdt_send_data(REBOOT_COMMAND, 0);
  67        mutex_unlock(&wdt_lock);
  68}
  69
  70static int fitpc2_wdt_open(struct inode *inode, struct file *file)
  71{
  72        if (test_and_set_bit(WDT_IN_USE, &wdt_status))
  73                return -EBUSY;
  74
  75        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  76
  77        wdt_enable();
  78
  79        return nonseekable_open(inode, file);
  80}
  81
  82static ssize_t fitpc2_wdt_write(struct file *file, const char *data,
  83                                                size_t len, loff_t *ppos)
  84{
  85        size_t i;
  86
  87        if (!len)
  88                return 0;
  89
  90        if (nowayout) {
  91                len = 0;
  92                goto out;
  93        }
  94
  95        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  96
  97        for (i = 0; i != len; i++) {
  98                char c;
  99
 100                if (get_user(c, data + i))
 101                        return -EFAULT;
 102
 103                if (c == 'V')
 104                        set_bit(WDT_OK_TO_CLOSE, &wdt_status);
 105        }
 106
 107out:
 108        wdt_enable();
 109
 110        return len;
 111}
 112
 113
 114static const struct watchdog_info ident = {
 115        .options        = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
 116                                WDIOF_KEEPALIVEPING,
 117        .identity       = WATCHDOG_NAME,
 118};
 119
 120
 121static long fitpc2_wdt_ioctl(struct file *file, unsigned int cmd,
 122                                                        unsigned long arg)
 123{
 124        int ret = -ENOTTY;
 125        int time;
 126
 127        switch (cmd) {
 128        case WDIOC_GETSUPPORT:
 129                ret = copy_to_user((struct watchdog_info *)arg, &ident,
 130                                   sizeof(ident)) ? -EFAULT : 0;
 131                break;
 132
 133        case WDIOC_GETSTATUS:
 134                ret = put_user(0, (int *)arg);
 135                break;
 136
 137        case WDIOC_GETBOOTSTATUS:
 138                ret = put_user(0, (int *)arg);
 139                break;
 140
 141        case WDIOC_KEEPALIVE:
 142                wdt_enable();
 143                ret = 0;
 144                break;
 145
 146        case WDIOC_SETTIMEOUT:
 147                ret = get_user(time, (int *)arg);
 148                if (ret)
 149                        break;
 150
 151                if (time < 31 || time > 255) {
 152                        ret = -EINVAL;
 153                        break;
 154                }
 155
 156                margin = time;
 157                wdt_enable();
 158                /* Fall through */
 159
 160        case WDIOC_GETTIMEOUT:
 161                ret = put_user(margin, (int *)arg);
 162                break;
 163        }
 164
 165        return ret;
 166}
 167
 168static int fitpc2_wdt_release(struct inode *inode, struct file *file)
 169{
 170        if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
 171                wdt_disable();
 172                pr_info("Device disabled\n");
 173        } else {
 174                pr_warning("Device closed unexpectedly -"
 175                        " timer will not stop\n");
 176                wdt_enable();
 177        }
 178
 179        clear_bit(WDT_IN_USE, &wdt_status);
 180        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 181
 182        return 0;
 183}
 184
 185
 186static const struct file_operations fitpc2_wdt_fops = {
 187        .owner          = THIS_MODULE,
 188        .llseek         = no_llseek,
 189        .write          = fitpc2_wdt_write,
 190        .unlocked_ioctl = fitpc2_wdt_ioctl,
 191        .open           = fitpc2_wdt_open,
 192        .release        = fitpc2_wdt_release,
 193};
 194
 195static struct miscdevice fitpc2_wdt_miscdev = {
 196        .minor          = WATCHDOG_MINOR,
 197        .name           = "watchdog",
 198        .fops           = &fitpc2_wdt_fops,
 199};
 200
 201static int __init fitpc2_wdt_init(void)
 202{
 203        int err;
 204        const char *brd_name;
 205
 206        brd_name = dmi_get_system_info(DMI_BOARD_NAME);
 207
 208        if (!brd_name || !strstr(brd_name, "SBC-FITPC2"))
 209                return -ENODEV;
 210
 211        pr_info("%s found\n", brd_name);
 212
 213        if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) {
 214                pr_err("I/O address 0x%04x already in use\n", COMMAND_PORT);
 215                return -EIO;
 216        }
 217
 218        if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) {
 219                pr_err("I/O address 0x%04x already in use\n", DATA_PORT);
 220                err = -EIO;
 221                goto err_data_port;
 222        }
 223
 224        if (margin < 31 || margin > 255) {
 225                pr_err("margin must be in range 31 - 255"
 226                       " seconds, you tried to set %d\n", margin);
 227                err = -EINVAL;
 228                goto err_margin;
 229        }
 230
 231        err = misc_register(&fitpc2_wdt_miscdev);
 232        if (err) {
 233                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 234                                                        WATCHDOG_MINOR, err);
 235                goto err_margin;
 236        }
 237
 238        return 0;
 239
 240err_margin:
 241        release_region(DATA_PORT, 1);
 242err_data_port:
 243        release_region(COMMAND_PORT, 1);
 244
 245        return err;
 246}
 247
 248static void __exit fitpc2_wdt_exit(void)
 249{
 250        misc_deregister(&fitpc2_wdt_miscdev);
 251        release_region(DATA_PORT, 1);
 252        release_region(COMMAND_PORT, 1);
 253}
 254
 255module_init(fitpc2_wdt_init);
 256module_exit(fitpc2_wdt_exit);
 257
 258MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
 259MODULE_DESCRIPTION("SBC-FITPC2 Watchdog");
 260
 261module_param(margin, int, 0);
 262MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
 263
 264module_param(nowayout, int, 0);
 265MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
 266
 267MODULE_LICENSE("GPL");
 268MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 269
 270