1*b7e04f8cSWim Van Sebroeck /* 2*b7e04f8cSWim Van Sebroeck * Driver for the MTX-1 Watchdog. 3*b7e04f8cSWim Van Sebroeck * 4*b7e04f8cSWim Van Sebroeck * (C) Copyright 2005 4G Systems <info@4g-systems.biz>, All Rights Reserved. 5*b7e04f8cSWim Van Sebroeck * http://www.4g-systems.biz 6*b7e04f8cSWim Van Sebroeck * 7*b7e04f8cSWim Van Sebroeck * (C) Copyright 2007 OpenWrt.org, Florian Fainelli <florian@openwrt.org> 8*b7e04f8cSWim Van Sebroeck * 9*b7e04f8cSWim Van Sebroeck * This program is free software; you can redistribute it and/or 10*b7e04f8cSWim Van Sebroeck * modify it under the terms of the GNU General Public License 11*b7e04f8cSWim Van Sebroeck * as published by the Free Software Foundation; either version 12*b7e04f8cSWim Van Sebroeck * 2 of the License, or (at your option) any later version. 13*b7e04f8cSWim Van Sebroeck * 14*b7e04f8cSWim Van Sebroeck * Neither Michael Stickel nor 4G Systems admit liability nor provide 15*b7e04f8cSWim Van Sebroeck * warranty for any of this software. This material is provided 16*b7e04f8cSWim Van Sebroeck * "AS-IS" and at no charge. 17*b7e04f8cSWim Van Sebroeck * 18*b7e04f8cSWim Van Sebroeck * (c) Copyright 2005 4G Systems <info@4g-systems.biz> 19*b7e04f8cSWim Van Sebroeck * 20*b7e04f8cSWim Van Sebroeck * Release 0.01. 21*b7e04f8cSWim Van Sebroeck * Author: Michael Stickel michael.stickel@4g-systems.biz 22*b7e04f8cSWim Van Sebroeck * 23*b7e04f8cSWim Van Sebroeck * Release 0.02. 24*b7e04f8cSWim Van Sebroeck * Author: Florian Fainelli florian@openwrt.org 25*b7e04f8cSWim Van Sebroeck * use the Linux watchdog/timer APIs 26*b7e04f8cSWim Van Sebroeck * 27*b7e04f8cSWim Van Sebroeck * The Watchdog is configured to reset the MTX-1 28*b7e04f8cSWim Van Sebroeck * if it is not triggered for 100 seconds. 29*b7e04f8cSWim Van Sebroeck * It should not be triggered more often than 1.6 seconds. 30*b7e04f8cSWim Van Sebroeck * 31*b7e04f8cSWim Van Sebroeck * A timer triggers the watchdog every 5 seconds, until 32*b7e04f8cSWim Van Sebroeck * it is opened for the first time. After the first open 33*b7e04f8cSWim Van Sebroeck * it MUST be triggered every 2..95 seconds. 34*b7e04f8cSWim Van Sebroeck */ 35*b7e04f8cSWim Van Sebroeck 36*b7e04f8cSWim Van Sebroeck #include <linux/module.h> 37*b7e04f8cSWim Van Sebroeck #include <linux/moduleparam.h> 38*b7e04f8cSWim Van Sebroeck #include <linux/types.h> 39*b7e04f8cSWim Van Sebroeck #include <linux/errno.h> 40*b7e04f8cSWim Van Sebroeck #include <linux/miscdevice.h> 41*b7e04f8cSWim Van Sebroeck #include <linux/fs.h> 42*b7e04f8cSWim Van Sebroeck #include <linux/init.h> 43*b7e04f8cSWim Van Sebroeck #include <linux/ioport.h> 44*b7e04f8cSWim Van Sebroeck #include <linux/timer.h> 45*b7e04f8cSWim Van Sebroeck #include <linux/completion.h> 46*b7e04f8cSWim Van Sebroeck #include <linux/jiffies.h> 47*b7e04f8cSWim Van Sebroeck #include <linux/watchdog.h> 48*b7e04f8cSWim Van Sebroeck #include <asm/io.h> 49*b7e04f8cSWim Van Sebroeck #include <asm/uaccess.h> 50*b7e04f8cSWim Van Sebroeck 51*b7e04f8cSWim Van Sebroeck #include <asm/mach-au1x00/au1000.h> 52*b7e04f8cSWim Van Sebroeck 53*b7e04f8cSWim Van Sebroeck #define MTX1_WDT_INTERVAL (5 * HZ) 54*b7e04f8cSWim Van Sebroeck 55*b7e04f8cSWim Van Sebroeck static int ticks = 100 * HZ; 56*b7e04f8cSWim Van Sebroeck 57*b7e04f8cSWim Van Sebroeck static struct { 58*b7e04f8cSWim Van Sebroeck struct completion stop; 59*b7e04f8cSWim Van Sebroeck volatile int running; 60*b7e04f8cSWim Van Sebroeck struct timer_list timer; 61*b7e04f8cSWim Van Sebroeck volatile int queue; 62*b7e04f8cSWim Van Sebroeck int default_ticks; 63*b7e04f8cSWim Van Sebroeck unsigned long inuse; 64*b7e04f8cSWim Van Sebroeck } mtx1_wdt_device; 65*b7e04f8cSWim Van Sebroeck 66*b7e04f8cSWim Van Sebroeck static void mtx1_wdt_trigger(unsigned long unused) 67*b7e04f8cSWim Van Sebroeck { 68*b7e04f8cSWim Van Sebroeck u32 tmp; 69*b7e04f8cSWim Van Sebroeck 70*b7e04f8cSWim Van Sebroeck if (mtx1_wdt_device.running) 71*b7e04f8cSWim Van Sebroeck ticks--; 72*b7e04f8cSWim Van Sebroeck /* 73*b7e04f8cSWim Van Sebroeck * toggle GPIO2_15 74*b7e04f8cSWim Van Sebroeck */ 75*b7e04f8cSWim Van Sebroeck tmp = au_readl(GPIO2_DIR); 76*b7e04f8cSWim Van Sebroeck tmp = (tmp & ~(1<<15)) | ((~tmp) & (1<<15)); 77*b7e04f8cSWim Van Sebroeck au_writel (tmp, GPIO2_DIR); 78*b7e04f8cSWim Van Sebroeck 79*b7e04f8cSWim Van Sebroeck if (mtx1_wdt_device.queue && ticks) 80*b7e04f8cSWim Van Sebroeck mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); 81*b7e04f8cSWim Van Sebroeck else { 82*b7e04f8cSWim Van Sebroeck complete(&mtx1_wdt_device.stop); 83*b7e04f8cSWim Van Sebroeck } 84*b7e04f8cSWim Van Sebroeck } 85*b7e04f8cSWim Van Sebroeck 86*b7e04f8cSWim Van Sebroeck static void mtx1_wdt_reset(void) 87*b7e04f8cSWim Van Sebroeck { 88*b7e04f8cSWim Van Sebroeck ticks = mtx1_wdt_device.default_ticks; 89*b7e04f8cSWim Van Sebroeck } 90*b7e04f8cSWim Van Sebroeck 91*b7e04f8cSWim Van Sebroeck 92*b7e04f8cSWim Van Sebroeck static void mtx1_wdt_start(void) 93*b7e04f8cSWim Van Sebroeck { 94*b7e04f8cSWim Van Sebroeck if (!mtx1_wdt_device.queue) { 95*b7e04f8cSWim Van Sebroeck mtx1_wdt_device.queue = 1; 96*b7e04f8cSWim Van Sebroeck au_writel (au_readl(GPIO2_DIR) | (u32)(1<<15), GPIO2_DIR); 97*b7e04f8cSWim Van Sebroeck mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); 98*b7e04f8cSWim Van Sebroeck } 99*b7e04f8cSWim Van Sebroeck mtx1_wdt_device.running++; 100*b7e04f8cSWim Van Sebroeck } 101*b7e04f8cSWim Van Sebroeck 102*b7e04f8cSWim Van Sebroeck static int mtx1_wdt_stop(void) 103*b7e04f8cSWim Van Sebroeck { 104*b7e04f8cSWim Van Sebroeck if (mtx1_wdt_device.queue) { 105*b7e04f8cSWim Van Sebroeck mtx1_wdt_device.queue = 0; 106*b7e04f8cSWim Van Sebroeck au_writel (au_readl(GPIO2_DIR) & ~((u32)(1<<15)), GPIO2_DIR); 107*b7e04f8cSWim Van Sebroeck } 108*b7e04f8cSWim Van Sebroeck 109*b7e04f8cSWim Van Sebroeck ticks = mtx1_wdt_device.default_ticks; 110*b7e04f8cSWim Van Sebroeck 111*b7e04f8cSWim Van Sebroeck return 0; 112*b7e04f8cSWim Van Sebroeck } 113*b7e04f8cSWim Van Sebroeck 114*b7e04f8cSWim Van Sebroeck /* Filesystem functions */ 115*b7e04f8cSWim Van Sebroeck 116*b7e04f8cSWim Van Sebroeck static int mtx1_wdt_open(struct inode *inode, struct file *file) 117*b7e04f8cSWim Van Sebroeck { 118*b7e04f8cSWim Van Sebroeck if (test_and_set_bit(0, &mtx1_wdt_device.inuse)) 119*b7e04f8cSWim Van Sebroeck return -EBUSY; 120*b7e04f8cSWim Van Sebroeck 121*b7e04f8cSWim Van Sebroeck return nonseekable_open(inode, file); 122*b7e04f8cSWim Van Sebroeck } 123*b7e04f8cSWim Van Sebroeck 124*b7e04f8cSWim Van Sebroeck 125*b7e04f8cSWim Van Sebroeck static int mtx1_wdt_release(struct inode *inode, struct file *file) 126*b7e04f8cSWim Van Sebroeck { 127*b7e04f8cSWim Van Sebroeck clear_bit(0, &mtx1_wdt_device.inuse); 128*b7e04f8cSWim Van Sebroeck return 0; 129*b7e04f8cSWim Van Sebroeck } 130*b7e04f8cSWim Van Sebroeck 131*b7e04f8cSWim Van Sebroeck static int mtx1_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) 132*b7e04f8cSWim Van Sebroeck { 133*b7e04f8cSWim Van Sebroeck void __user *argp = (void __user *)arg; 134*b7e04f8cSWim Van Sebroeck unsigned int value; 135*b7e04f8cSWim Van Sebroeck static struct watchdog_info ident = 136*b7e04f8cSWim Van Sebroeck { 137*b7e04f8cSWim Van Sebroeck .options = WDIOF_CARDRESET, 138*b7e04f8cSWim Van Sebroeck .identity = "MTX-1 WDT", 139*b7e04f8cSWim Van Sebroeck }; 140*b7e04f8cSWim Van Sebroeck 141*b7e04f8cSWim Van Sebroeck switch(cmd) { 142*b7e04f8cSWim Van Sebroeck case WDIOC_KEEPALIVE: 143*b7e04f8cSWim Van Sebroeck mtx1_wdt_reset(); 144*b7e04f8cSWim Van Sebroeck break; 145*b7e04f8cSWim Van Sebroeck case WDIOC_GETSTATUS: 146*b7e04f8cSWim Van Sebroeck case WDIOC_GETBOOTSTATUS: 147*b7e04f8cSWim Van Sebroeck if ( copy_to_user(argp, &value, sizeof(int)) ) 148*b7e04f8cSWim Van Sebroeck return -EFAULT; 149*b7e04f8cSWim Van Sebroeck break; 150*b7e04f8cSWim Van Sebroeck case WDIOC_GETSUPPORT: 151*b7e04f8cSWim Van Sebroeck if ( copy_to_user(argp, &ident, sizeof(ident)) ) 152*b7e04f8cSWim Van Sebroeck return -EFAULT; 153*b7e04f8cSWim Van Sebroeck break; 154*b7e04f8cSWim Van Sebroeck case WDIOC_SETOPTIONS: 155*b7e04f8cSWim Van Sebroeck if ( copy_from_user(&value, argp, sizeof(int)) ) 156*b7e04f8cSWim Van Sebroeck return -EFAULT; 157*b7e04f8cSWim Van Sebroeck switch(value) { 158*b7e04f8cSWim Van Sebroeck case WDIOS_ENABLECARD: 159*b7e04f8cSWim Van Sebroeck mtx1_wdt_start(); 160*b7e04f8cSWim Van Sebroeck break; 161*b7e04f8cSWim Van Sebroeck case WDIOS_DISABLECARD: 162*b7e04f8cSWim Van Sebroeck return mtx1_wdt_stop(); 163*b7e04f8cSWim Van Sebroeck default: 164*b7e04f8cSWim Van Sebroeck return -EINVAL; 165*b7e04f8cSWim Van Sebroeck } 166*b7e04f8cSWim Van Sebroeck break; 167*b7e04f8cSWim Van Sebroeck default: 168*b7e04f8cSWim Van Sebroeck return -ENOTTY; 169*b7e04f8cSWim Van Sebroeck } 170*b7e04f8cSWim Van Sebroeck return 0; 171*b7e04f8cSWim Van Sebroeck } 172*b7e04f8cSWim Van Sebroeck 173*b7e04f8cSWim Van Sebroeck 174*b7e04f8cSWim Van Sebroeck static ssize_t mtx1_wdt_write(struct file *file, const char *buf, size_t count, loff_t *ppos) 175*b7e04f8cSWim Van Sebroeck { 176*b7e04f8cSWim Van Sebroeck if (!count) 177*b7e04f8cSWim Van Sebroeck return -EIO; 178*b7e04f8cSWim Van Sebroeck 179*b7e04f8cSWim Van Sebroeck mtx1_wdt_reset(); 180*b7e04f8cSWim Van Sebroeck return count; 181*b7e04f8cSWim Van Sebroeck } 182*b7e04f8cSWim Van Sebroeck 183*b7e04f8cSWim Van Sebroeck static struct file_operations mtx1_wdt_fops = { 184*b7e04f8cSWim Van Sebroeck .owner = THIS_MODULE, 185*b7e04f8cSWim Van Sebroeck .llseek = no_llseek, 186*b7e04f8cSWim Van Sebroeck .ioctl = mtx1_wdt_ioctl, 187*b7e04f8cSWim Van Sebroeck .open = mtx1_wdt_open, 188*b7e04f8cSWim Van Sebroeck .write = mtx1_wdt_write, 189*b7e04f8cSWim Van Sebroeck .release = mtx1_wdt_release 190*b7e04f8cSWim Van Sebroeck }; 191*b7e04f8cSWim Van Sebroeck 192*b7e04f8cSWim Van Sebroeck 193*b7e04f8cSWim Van Sebroeck static struct miscdevice mtx1_wdt_misc = { 194*b7e04f8cSWim Van Sebroeck .minor = WATCHDOG_MINOR, 195*b7e04f8cSWim Van Sebroeck .name = "watchdog", 196*b7e04f8cSWim Van Sebroeck .fops = &mtx1_wdt_fops 197*b7e04f8cSWim Van Sebroeck }; 198*b7e04f8cSWim Van Sebroeck 199*b7e04f8cSWim Van Sebroeck 200*b7e04f8cSWim Van Sebroeck static int __init mtx1_wdt_init(void) 201*b7e04f8cSWim Van Sebroeck { 202*b7e04f8cSWim Van Sebroeck int ret; 203*b7e04f8cSWim Van Sebroeck 204*b7e04f8cSWim Van Sebroeck if ((ret = misc_register(&mtx1_wdt_misc)) < 0) { 205*b7e04f8cSWim Van Sebroeck printk(KERN_ERR " mtx-1_wdt : failed to register\n"); 206*b7e04f8cSWim Van Sebroeck return ret; 207*b7e04f8cSWim Van Sebroeck } 208*b7e04f8cSWim Van Sebroeck 209*b7e04f8cSWim Van Sebroeck init_completion(&mtx1_wdt_device.stop); 210*b7e04f8cSWim Van Sebroeck mtx1_wdt_device.queue = 0; 211*b7e04f8cSWim Van Sebroeck 212*b7e04f8cSWim Van Sebroeck clear_bit(0, &mtx1_wdt_device.inuse); 213*b7e04f8cSWim Van Sebroeck 214*b7e04f8cSWim Van Sebroeck setup_timer(&mtx1_wdt_device.timer, mtx1_wdt_trigger, 0L); 215*b7e04f8cSWim Van Sebroeck 216*b7e04f8cSWim Van Sebroeck mtx1_wdt_device.default_ticks = ticks; 217*b7e04f8cSWim Van Sebroeck 218*b7e04f8cSWim Van Sebroeck mtx1_wdt_start(); 219*b7e04f8cSWim Van Sebroeck 220*b7e04f8cSWim Van Sebroeck printk(KERN_INFO "MTX-1 Watchdog driver\n"); 221*b7e04f8cSWim Van Sebroeck 222*b7e04f8cSWim Van Sebroeck return 0; 223*b7e04f8cSWim Van Sebroeck } 224*b7e04f8cSWim Van Sebroeck 225*b7e04f8cSWim Van Sebroeck static void __exit mtx1_wdt_exit(void) 226*b7e04f8cSWim Van Sebroeck { 227*b7e04f8cSWim Van Sebroeck if (mtx1_wdt_device.queue) { 228*b7e04f8cSWim Van Sebroeck mtx1_wdt_device.queue = 0; 229*b7e04f8cSWim Van Sebroeck wait_for_completion(&mtx1_wdt_device.stop); 230*b7e04f8cSWim Van Sebroeck } 231*b7e04f8cSWim Van Sebroeck misc_deregister(&mtx1_wdt_misc); 232*b7e04f8cSWim Van Sebroeck } 233*b7e04f8cSWim Van Sebroeck 234*b7e04f8cSWim Van Sebroeck module_init(mtx1_wdt_init); 235*b7e04f8cSWim Van Sebroeck module_exit(mtx1_wdt_exit); 236*b7e04f8cSWim Van Sebroeck 237*b7e04f8cSWim Van Sebroeck MODULE_AUTHOR("Michael Stickel, Florian Fainelli"); 238*b7e04f8cSWim Van Sebroeck MODULE_DESCRIPTION("Driver for the MTX-1 watchdog"); 239*b7e04f8cSWim Van Sebroeck MODULE_LICENSE("GPL"); 240