From 3a5f90002e9d08e5a6406fc650bfd838bf23bc1b Mon Sep 17 00:00:00 2001 From: Denis Turischev Date: Tue, 21 Jul 2009 13:13:29 +0300 Subject: [PATCH] [WATCHDOG] add SBC-FITPC2 watchdog driver Add support for watchdog found on SBC-FITPC2 board. Signed-off-by: Denis Turischev Signed-off-by: Mike Rapoport Signed-off-by: Wim Van Sebroeck Signed-off-by: Andrew Morton --- drivers/watchdog/Kconfig | 22 ++++ drivers/watchdog/Makefile | 1 + drivers/watchdog/sbc_fitpc2_wdt.c | 267 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 drivers/watchdog/sbc_fitpc2_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b1ccc04..1940a08 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -369,6 +369,28 @@ config SC520_WDT You can compile this driver directly into the kernel, or use it as a module. The module will be called sc520_wdt. +config SBC_FITPC2_WATCHDOG + tristate "Compulab SBC-FITPC2 watchdog" + depends on X86 + ---help--- + This is the driver for the built-in watchdog timer on the fit-PC2 + Single-board computer made by Compulab. + + It`s possible to enable watchdog timer either from BIOS (F2) or from booted Linux. + When "Watchdog Timer Value" enabled one can set 31-255 s operational range. + + Entering BIOS setup temporary disables watchdog operation regardless to current state, + so system will not be restarted while user in BIOS setup. + + Once watchdog was enabled the system will be restarted every + "Watchdog Timer Value" period, so to prevent it user can restart or + disable the watchdog. + + To compile this driver as a module, choose M here: the + module will be called sbc_fitpc2_wdt. + + Most people will say N. + config EUROTECH_WDT tristate "Eurotech CPU-1220/1410 Watchdog Timer" depends on X86 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 3d77429..6c58e7c 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o obj-$(CONFIG_GEODE_WDT) += geodewdt.o obj-$(CONFIG_SC520_WDT) += sc520_wdt.o +obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o obj-$(CONFIG_IB700_WDT) += ib700wdt.o obj-$(CONFIG_IBMASR) += ibmasr.o diff --git a/drivers/watchdog/sbc_fitpc2_wdt.c b/drivers/watchdog/sbc_fitpc2_wdt.c new file mode 100644 index 0000000..852ca19 --- /dev/null +++ b/drivers/watchdog/sbc_fitpc2_wdt.c @@ -0,0 +1,267 @@ +/* + * Watchdog driver for SBC-FITPC2 board + * + * Author: Denis Turischev + * + * Adapted from the IXP2000 watchdog driver by Deepak Saxena. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int nowayout = WATCHDOG_NOWAYOUT; +static unsigned int margin = 60; /* (secs) Default is 1 minute */ +static unsigned long wdt_status; +static DEFINE_SPINLOCK(wdt_lock); + +#define WDT_IN_USE 0 +#define WDT_OK_TO_CLOSE 1 + +#define COMMAND_PORT 0x4c +#define DATA_PORT 0x48 + +#define IFACE_ON_COMMAND 1 +#define REBOOT_COMMAND 2 + +#define WATCHDOG_NAME "SBC-FITPC2 Watchdog" + +static void wdt_send_data(unsigned char command, unsigned char data) +{ + outb(command, COMMAND_PORT); + mdelay(100); + outb(data, DATA_PORT); + mdelay(200); +} + +static void wdt_enable(void) +{ + spin_lock(&wdt_lock); + wdt_send_data(IFACE_ON_COMMAND, 1); + wdt_send_data(REBOOT_COMMAND, margin); + spin_unlock(&wdt_lock); +} + +static void wdt_disable(void) +{ + spin_lock(&wdt_lock); + wdt_send_data(IFACE_ON_COMMAND, 0); + wdt_send_data(REBOOT_COMMAND, 0); + spin_unlock(&wdt_lock); +} + +static int fitpc2_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(WDT_IN_USE, &wdt_status)) + return -EBUSY; + + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + wdt_enable(); + + return nonseekable_open(inode, file); +} + +static ssize_t fitpc2_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + size_t i; + + if (!len) + return 0; + + if (nowayout) { + len = 0; + goto out; + } + + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + + if (c == 'V') + set_bit(WDT_OK_TO_CLOSE, &wdt_status); + } + +out: + wdt_enable(); + + return len; +} + + +static struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING, + .identity = WATCHDOG_NAME, +}; + + +static long fitpc2_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOTTY; + int time; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user((struct watchdog_info *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + wdt_enable(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, (int *)arg); + if (ret) + break; + + if (time < 31 || time > 255) { + ret = -EINVAL; + break; + } + + margin = time; + wdt_enable(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(margin, (int *)arg); + break; + } + + return ret; +} + +static int fitpc2_wdt_release(struct inode *inode, struct file *file) +{ + if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { + wdt_disable(); + pr_info("Device disabled\n"); + } else { + pr_warning("Device closed unexpectedly -" + " timer will not stop\n"); + wdt_enable(); + } + + clear_bit(WDT_IN_USE, &wdt_status); + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + return 0; +} + + +static const struct file_operations fitpc2_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = fitpc2_wdt_write, + .unlocked_ioctl = fitpc2_wdt_ioctl, + .open = fitpc2_wdt_open, + .release = fitpc2_wdt_release, +}; + +static struct miscdevice fitpc2_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &fitpc2_wdt_fops, +}; + +static int __init fitpc2_wdt_init(void) +{ + int err; + + if (strcmp("SBC-FITPC2", dmi_get_system_info(DMI_BOARD_NAME))) { + pr_info("board name is: %s. Should be SBC-FITPC2\n", + dmi_get_system_info(DMI_BOARD_NAME)); + return -ENODEV; + } + + if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", COMMAND_PORT); + return -EIO; + } + + if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", DATA_PORT); + err = -EIO; + goto err_data_port; + } + + if (margin < 31 || margin > 255) { + pr_err("margin must be in range 31 - 255" + " seconds, you tried to set %d\n", margin); + err = -EINVAL; + goto err_margin; + } + + err = misc_register(&fitpc2_wdt_miscdev); + if (!err) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, err); + goto err_margin; + } + + return 0; + +err_margin: + release_region(DATA_PORT, 1); +err_data_port: + release_region(COMMAND_PORT, 1); + + return err; +} + +static void __exit fitpc2_wdt_exit(void) +{ + misc_deregister(&fitpc2_wdt_miscdev); + release_region(DATA_PORT, 1); + release_region(COMMAND_PORT, 1); +} + +module_init(fitpc2_wdt_init); +module_exit(fitpc2_wdt_exit); + +MODULE_AUTHOR("Denis Turischev "); +MODULE_DESCRIPTION("SBC-FITPC2 Watchdog"); + +module_param(margin, int, 0); +MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + -- 2.7.4