2005-04-17 02:20:36 +04:00
/*
* Eurotech CPU - 1220 / 1410 on board WDT driver
*
* ( c ) Copyright 2001 Ascensit < support @ ascensit . com >
* ( c ) Copyright 2001 Rodolfo Giometti < giometti @ ascensit . com >
* ( c ) Copyright 2002 Rob Radez < rob @ osinvestor . com >
*
* Based on wdt . c .
* Original copyright messages :
*
* ( c ) Copyright 1996 - 1997 Alan Cox < alan @ redhat . com > , All Rights Reserved .
* http : //www.redhat.com
*
* 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 Alan Cox nor CymruNet Ltd . admit liability nor provide
* warranty for any of this software . This material is provided
* " AS-IS " and at no charge .
*
* ( c ) Copyright 1995 Alan Cox < alan @ lxorguk . ukuu . org . uk > *
*/
/* Changelog:
*
* 2002 / 04 / 25 - Rob Radez
* clean up # includes
* clean up locking
* make __setup param unique
* proper options in watchdog_info
* add WDIOC_GETSTATUS and WDIOC_SETOPTIONS ioctls
* add expect_close support
*
* 2001 - Rodolfo Giometti
* Initial release
*
* 2002.05 .30 - Joel Becker < joel . becker @ oracle . com >
* Added Matt Domsch ' s nowayout module option .
*/
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/types.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/fs.h>
# include <linux/ioport.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/init.h>
# include <asm/io.h>
# include <asm/uaccess.h>
# include <asm/system.h>
static unsigned long eurwdt_is_open ;
static int eurwdt_timeout ;
static char eur_expect_close ;
/*
* You must set these - there is no sane way to probe for this board .
* You can use eurwdt = x , y to set these now .
*/
static int io = 0x3f0 ;
static int irq = 10 ;
static char * ev = " int " ;
# define WDT_TIMEOUT 60 /* 1 minute */
2005-07-27 22:43:58 +04:00
static int nowayout = WATCHDOG_NOWAYOUT ;
2005-04-17 02:20:36 +04:00
module_param ( nowayout , int , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT) " ) ;
/*
* Some symbolic names
*/
# define WDT_CTRL_REG 0x30
# define WDT_OUTPIN_CFG 0xe2
# define WDT_EVENT_INT 0x00
# define WDT_EVENT_REBOOT 0x08
# define WDT_UNIT_SEL 0xf1
# define WDT_UNIT_SECS 0x80
# define WDT_TIMEOUT_VAL 0xf2
# define WDT_TIMER_CFG 0xf3
module_param ( io , int , 0 ) ;
MODULE_PARM_DESC ( io , " Eurotech WDT io port (default=0x3f0) " ) ;
module_param ( irq , int , 0 ) ;
MODULE_PARM_DESC ( irq , " Eurotech WDT irq (default=10) " ) ;
module_param ( ev , charp , 0 ) ;
MODULE_PARM_DESC ( ev , " Eurotech WDT event type (default is `int') " ) ;
/*
* Programming support
*/
static inline void eurwdt_write_reg ( u8 index , u8 data )
{
outb ( index , io ) ;
outb ( data , io + 1 ) ;
}
static inline void eurwdt_lock_chip ( void )
{
outb ( 0xaa , io ) ;
}
static inline void eurwdt_unlock_chip ( void )
{
outb ( 0x55 , io ) ;
eurwdt_write_reg ( 0x07 , 0x08 ) ; /* set the logical device */
}
static inline void eurwdt_set_timeout ( int timeout )
{
eurwdt_write_reg ( WDT_TIMEOUT_VAL , ( u8 ) timeout ) ;
}
static inline void eurwdt_disable_timer ( void )
{
eurwdt_set_timeout ( 0 ) ;
}
static void eurwdt_activate_timer ( void )
{
eurwdt_disable_timer ( ) ;
eurwdt_write_reg ( WDT_CTRL_REG , 0x01 ) ; /* activate the WDT */
eurwdt_write_reg ( WDT_OUTPIN_CFG , ! strcmp ( " int " , ev ) ? WDT_EVENT_INT : WDT_EVENT_REBOOT ) ;
/* Setting interrupt line */
if ( irq = = 2 | | irq > 15 | | irq < 0 ) {
printk ( KERN_ERR " : invalid irq number \n " ) ;
irq = 0 ; /* if invalid we disable interrupt */
}
if ( irq = = 0 )
printk ( KERN_INFO " : interrupt disabled \n " ) ;
eurwdt_write_reg ( WDT_TIMER_CFG , irq < < 4 ) ;
eurwdt_write_reg ( WDT_UNIT_SEL , WDT_UNIT_SECS ) ; /* we use seconds */
eurwdt_set_timeout ( 0 ) ; /* the default timeout */
}
/*
* Kernel methods .
*/
static irqreturn_t eurwdt_interrupt ( int irq , void * dev_id , struct pt_regs * regs )
{
printk ( KERN_CRIT " timeout WDT timeout \n " ) ;
# ifdef ONLY_TESTING
printk ( KERN_CRIT " Would Reboot. \n " ) ;
# else
printk ( KERN_CRIT " Initiating system reboot. \n " ) ;
2005-07-27 08:41:37 +04:00
emergency_restart ( ) ;
2005-04-17 02:20:36 +04:00
# endif
return IRQ_HANDLED ;
}
/**
* eurwdt_ping :
*
* Reload counter one with the watchdog timeout .
*/
static void eurwdt_ping ( void )
{
/* Write the watchdog default value */
eurwdt_set_timeout ( eurwdt_timeout ) ;
}
/**
* eurwdt_write :
* @ file : file handle to the watchdog
* @ buf : buffer to write ( unused as data does not matter here
* @ count : count of bytes
* @ ppos : pointer to the position to write . No seeks allowed
*
* A write to a watchdog device is defined as a keepalive signal . Any
* write of data will do , as we we don ' t define content meaning .
*/
static ssize_t eurwdt_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
{
if ( count ) {
if ( ! nowayout ) {
size_t i ;
eur_expect_close = 0 ;
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
if ( get_user ( c , buf + i ) )
return - EFAULT ;
if ( c = = ' V ' )
eur_expect_close = 42 ;
}
}
eurwdt_ping ( ) ; /* the default timeout */
}
return count ;
}
/**
* eurwdt_ioctl :
* @ inode : inode of the device
* @ file : file handle to the device
* @ cmd : watchdog command
* @ arg : argument pointer
*
* The watchdog API defines a common set of functions for all watchdogs
* according to their available features .
*/
static int eurwdt_ioctl ( struct inode * inode , struct file * file ,
unsigned int cmd , unsigned long arg )
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
static struct watchdog_info ident = {
. options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE ,
. firmware_version = 1 ,
. identity = " WDT Eurotech CPU-1220/1410 " ,
} ;
int time ;
int options , retval = - EINVAL ;
switch ( cmd ) {
default :
2006-09-09 19:34:31 +04:00
return - ENOTTY ;
2005-04-17 02:20:36 +04:00
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_KEEPALIVE :
eurwdt_ping ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( copy_from_user ( & time , p , sizeof ( int ) ) )
return - EFAULT ;
/* Sanity check */
if ( time < 0 | | time > 255 )
return - EINVAL ;
eurwdt_timeout = time ;
eurwdt_set_timeout ( time ) ;
/* Fall */
case WDIOC_GETTIMEOUT :
return put_user ( eurwdt_timeout , p ) ;
case WDIOC_SETOPTIONS :
if ( get_user ( options , p ) )
return - EFAULT ;
if ( options & WDIOS_DISABLECARD ) {
eurwdt_disable_timer ( ) ;
retval = 0 ;
}
if ( options & WDIOS_ENABLECARD ) {
eurwdt_activate_timer ( ) ;
eurwdt_ping ( ) ;
retval = 0 ;
}
return retval ;
}
}
/**
* eurwdt_open :
* @ inode : inode of device
* @ file : file handle to device
*
* The misc device has been opened . The watchdog device is single
* open and on opening we load the counter .
*/
static int eurwdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 0 , & eurwdt_is_open ) )
return - EBUSY ;
eurwdt_timeout = WDT_TIMEOUT ; /* initial timeout */
/* Activate the WDT */
eurwdt_activate_timer ( ) ;
return nonseekable_open ( inode , file ) ;
}
/**
* eurwdt_release :
* @ inode : inode to board
* @ file : file handle to board
*
* The watchdog has a configurable API . There is a religious dispute
* between people who want their watchdog to be able to shut down and
* those who want to be sure if the watchdog manager dies the machine
* reboots . In the former case we disable the counters , in the latter
* case you have to open it again very soon .
*/
static int eurwdt_release ( struct inode * inode , struct file * file )
{
if ( eur_expect_close = = 42 ) {
eurwdt_disable_timer ( ) ;
} else {
printk ( KERN_CRIT " eurwdt: Unexpected close, not stopping watchdog! \n " ) ;
eurwdt_ping ( ) ;
}
clear_bit ( 0 , & eurwdt_is_open ) ;
eur_expect_close = 0 ;
return 0 ;
}
/**
* eurwdt_notify_sys :
* @ this : our notifier block
* @ code : the event being reported
* @ unused : unused
*
* Our notifier is called on system shutdowns . We want to turn the card
* off at reboot otherwise the machine will reboot again during memory
* test or worse yet during the following fsck . This would suck , in fact
* trust me - if it happens it does suck .
*/
static int eurwdt_notify_sys ( struct notifier_block * this , unsigned long code ,
void * unused )
{
if ( code = = SYS_DOWN | | code = = SYS_HALT ) {
/* Turn the card off */
eurwdt_disable_timer ( ) ;
}
return NOTIFY_DONE ;
}
/*
* Kernel Interfaces
*/
2006-07-03 11:24:21 +04:00
static const struct file_operations eurwdt_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = eurwdt_write ,
. ioctl = eurwdt_ioctl ,
. open = eurwdt_open ,
. release = eurwdt_release ,
} ;
static struct miscdevice eurwdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & eurwdt_fops ,
} ;
/*
* The WDT card needs to learn about soft shutdowns in order to
* turn the timebomb registers off .
*/
static struct notifier_block eurwdt_notifier = {
. notifier_call = eurwdt_notify_sys ,
} ;
/**
* cleanup_module :
*
* Unload the watchdog . You cannot do this with any file handles open .
* If your watchdog is set to continue ticking on close and you unload
* it , well it keeps ticking . We won ' t get the interrupt but the board
* will not touch PC memory so all is fine . You just have to load a new
* module in 60 seconds or reboot .
*/
static void __exit eurwdt_exit ( void )
{
eurwdt_lock_chip ( ) ;
misc_deregister ( & eurwdt_miscdev ) ;
unregister_reboot_notifier ( & eurwdt_notifier ) ;
release_region ( io , 2 ) ;
free_irq ( irq , NULL ) ;
}
/**
* eurwdt_init :
*
* Set up the WDT watchdog board . After grabbing the resources
* we require we need also to unlock the device .
* The open ( ) function will actually kick the board off .
*/
static int __init eurwdt_init ( void )
{
int ret ;
ret = misc_register ( & eurwdt_miscdev ) ;
if ( ret ) {
printk ( KERN_ERR " eurwdt: can't misc_register on minor=%d \n " ,
WATCHDOG_MINOR ) ;
goto out ;
}
2006-07-02 06:29:33 +04:00
ret = request_irq ( irq , eurwdt_interrupt , IRQF_DISABLED , " eurwdt " , NULL ) ;
2005-04-17 02:20:36 +04:00
if ( ret ) {
printk ( KERN_ERR " eurwdt: IRQ %d is not free. \n " , irq ) ;
goto outmisc ;
}
if ( ! request_region ( io , 2 , " eurwdt " ) ) {
printk ( KERN_ERR " eurwdt: IO %X is not free. \n " , io ) ;
ret = - EBUSY ;
goto outirq ;
}
ret = register_reboot_notifier ( & eurwdt_notifier ) ;
if ( ret ) {
printk ( KERN_ERR " eurwdt: can't register reboot notifier (err=%d) \n " , ret ) ;
goto outreg ;
}
eurwdt_unlock_chip ( ) ;
ret = 0 ;
printk ( KERN_INFO " Eurotech WDT driver 0.01 at %X (Interrupt %d) "
" - timeout event: %s \n " ,
io , irq , ( ! strcmp ( " int " , ev ) ? " int " : " reboot " ) ) ;
out :
return ret ;
outreg :
release_region ( io , 2 ) ;
outirq :
free_irq ( irq , NULL ) ;
outmisc :
misc_deregister ( & eurwdt_miscdev ) ;
goto out ;
}
module_init ( eurwdt_init ) ;
module_exit ( eurwdt_exit ) ;
MODULE_AUTHOR ( " Rodolfo Giometti " ) ;
MODULE_DESCRIPTION ( " Driver for Eurotech CPU-1220/1410 on board watchdog " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;