/* * Driver for the MTX-1 Watchdog. * * (C) Copyright 2005 4G Systems , All Rights Reserved. * http://www.4g-systems.biz * * (C) Copyright 2007 OpenWrt.org, Florian Fainelli * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Neither Michael Stickel nor 4G Systems admit liability nor provide * warranty for any of this software. This material is provided * "AS-IS" and at no charge. * * (c) Copyright 2005 4G Systems * * Release 0.01. * Author: Michael Stickel michael.stickel@4g-systems.biz * * Release 0.02. * Author: Florian Fainelli florian@openwrt.org * use the Linux watchdog/timer APIs * * The Watchdog is configured to reset the MTX-1 * if it is not triggered for 100 seconds. * It should not be triggered more often than 1.6 seconds. * * A timer triggers the watchdog every 5 seconds, until * it is opened for the first time. After the first open * it MUST be triggered every 2..95 seconds. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MTX1_WDT_INTERVAL (5 * HZ) static int ticks = 100 * HZ; static struct { struct completion stop; volatile int running; struct timer_list timer; volatile int queue; int default_ticks; unsigned long inuse; } mtx1_wdt_device; static void mtx1_wdt_trigger(unsigned long unused) { u32 tmp; if (mtx1_wdt_device.running) ticks--; /* * toggle GPIO2_15 */ tmp = au_readl(GPIO2_DIR); tmp = (tmp & ~(1<<15)) | ((~tmp) & (1<<15)); au_writel (tmp, GPIO2_DIR); if (mtx1_wdt_device.queue && ticks) mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); else { complete(&mtx1_wdt_device.stop); } } static void mtx1_wdt_reset(void) { ticks = mtx1_wdt_device.default_ticks; } static void mtx1_wdt_start(void) { if (!mtx1_wdt_device.queue) { mtx1_wdt_device.queue = 1; au_writel (au_readl(GPIO2_DIR) | (u32)(1<<15), GPIO2_DIR); mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); } mtx1_wdt_device.running++; } static int mtx1_wdt_stop(void) { if (mtx1_wdt_device.queue) { mtx1_wdt_device.queue = 0; au_writel (au_readl(GPIO2_DIR) & ~((u32)(1<<15)), GPIO2_DIR); } ticks = mtx1_wdt_device.default_ticks; return 0; } /* Filesystem functions */ static int mtx1_wdt_open(struct inode *inode, struct file *file) { if (test_and_set_bit(0, &mtx1_wdt_device.inuse)) return -EBUSY; return nonseekable_open(inode, file); } static int mtx1_wdt_release(struct inode *inode, struct file *file) { clear_bit(0, &mtx1_wdt_device.inuse); return 0; } static int mtx1_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; unsigned int value; static struct watchdog_info ident = { .options = WDIOF_CARDRESET, .identity = "MTX-1 WDT", }; switch(cmd) { case WDIOC_KEEPALIVE: mtx1_wdt_reset(); break; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: if ( copy_to_user(argp, &value, sizeof(int)) ) return -EFAULT; break; case WDIOC_GETSUPPORT: if ( copy_to_user(argp, &ident, sizeof(ident)) ) return -EFAULT; break; case WDIOC_SETOPTIONS: if ( copy_from_user(&value, argp, sizeof(int)) ) return -EFAULT; switch(value) { case WDIOS_ENABLECARD: mtx1_wdt_start(); break; case WDIOS_DISABLECARD: return mtx1_wdt_stop(); default: return -EINVAL; } break; default: return -ENOTTY; } return 0; } static ssize_t mtx1_wdt_write(struct file *file, const char *buf, size_t count, loff_t *ppos) { if (!count) return -EIO; mtx1_wdt_reset(); return count; } static struct file_operations mtx1_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .ioctl = mtx1_wdt_ioctl, .open = mtx1_wdt_open, .write = mtx1_wdt_write, .release = mtx1_wdt_release }; static struct miscdevice mtx1_wdt_misc = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &mtx1_wdt_fops }; static int __init mtx1_wdt_init(void) { int ret; if ((ret = misc_register(&mtx1_wdt_misc)) < 0) { printk(KERN_ERR " mtx-1_wdt : failed to register\n"); return ret; } init_completion(&mtx1_wdt_device.stop); mtx1_wdt_device.queue = 0; clear_bit(0, &mtx1_wdt_device.inuse); setup_timer(&mtx1_wdt_device.timer, mtx1_wdt_trigger, 0L); mtx1_wdt_device.default_ticks = ticks; mtx1_wdt_start(); printk(KERN_INFO "MTX-1 Watchdog driver\n"); return 0; } static void __exit mtx1_wdt_exit(void) { if (mtx1_wdt_device.queue) { mtx1_wdt_device.queue = 0; wait_for_completion(&mtx1_wdt_device.stop); } misc_deregister(&mtx1_wdt_misc); } module_init(mtx1_wdt_init); module_exit(mtx1_wdt_exit); MODULE_AUTHOR("Michael Stickel, Florian Fainelli"); MODULE_DESCRIPTION("Driver for the MTX-1 watchdog"); MODULE_LICENSE("GPL");