2006-03-25 14:06:37 +03:00
/*
* Watchdog driver for Cirrus Logic EP93xx family of devices .
*
* Copyright ( c ) 2004 Ray Lehtiniemi
* Copyright ( c ) 2006 Tower Technologies
* Based on ep93xx driver , bits from alim7101_wdt . c
*
* Authors : Ray Lehtiniemi < rayl @ mail . com > ,
* Alessandro Zummo < a . zummo @ towertech . it >
*
* 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 .
*
* This watchdog fires after 250 msec , which is a too short interval
* for us to rely on the user space daemon alone . So we ping the
* wdt each ~ 200 msec and eventually stop doing it if the user space
* daemon dies .
*
* TODO :
*
* - Test last reset from watchdog status
* - Add a few missing ioctls
*/
# include <linux/module.h>
# include <linux/fs.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/timer.h>
2008-05-19 17:05:35 +04:00
# include <linux/uaccess.h>
2008-08-05 19:14:15 +04:00
# include <mach/hardware.h>
2006-03-25 14:06:37 +03:00
# define WDT_VERSION "0.3"
# define PFX "ep93xx_wdt: "
/* default timeout (secs) */
# define WDT_TIMEOUT 30
static int nowayout = WATCHDOG_NOWAYOUT ;
static int timeout = WDT_TIMEOUT ;
static struct timer_list timer ;
static unsigned long next_heartbeat ;
static unsigned long wdt_status ;
static unsigned long boot_status ;
# define WDT_IN_USE 0
# define WDT_OK_TO_CLOSE 1
# define EP93XX_WDT_REG(x) (EP93XX_WATCHDOG_BASE + (x))
# define EP93XX_WDT_WATCHDOG EP93XX_WDT_REG(0x00)
# define EP93XX_WDT_WDSTATUS EP93XX_WDT_REG(0x04)
/* reset the wdt every ~200ms */
# define WDT_INTERVAL (HZ / 5)
static void wdt_enable ( void )
{
__raw_writew ( 0xaaaa , EP93XX_WDT_WATCHDOG ) ;
}
static void wdt_disable ( void )
{
__raw_writew ( 0xaa55 , EP93XX_WDT_WATCHDOG ) ;
}
static inline void wdt_ping ( void )
{
__raw_writew ( 0x5555 , EP93XX_WDT_WATCHDOG ) ;
}
static void wdt_startup ( void )
{
next_heartbeat = jiffies + ( timeout * HZ ) ;
wdt_enable ( ) ;
mod_timer ( & timer , jiffies + WDT_INTERVAL ) ;
}
static void wdt_shutdown ( void )
{
del_timer_sync ( & timer ) ;
wdt_disable ( ) ;
}
static void wdt_keepalive ( void )
{
/* user land ping */
next_heartbeat = jiffies + ( timeout * HZ ) ;
}
static int ep93xx_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_startup ( ) ;
return nonseekable_open ( inode , file ) ;
}
static ssize_t
ep93xx_wdt_write ( struct file * file , const char __user * data , size_t len ,
loff_t * ppos )
{
if ( len ) {
if ( ! nowayout ) {
size_t i ;
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 ) ;
else
clear_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
}
}
wdt_keepalive ( ) ;
}
return len ;
}
static struct watchdog_info ident = {
. options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE ,
. identity = " EP93xx Watchdog " ,
} ;
2008-05-19 17:05:35 +04:00
static long ep93xx_wdt_ioctl ( struct file * file ,
unsigned int cmd , unsigned long arg )
2006-03-25 14:06:37 +03:00
{
2006-09-09 19:34:31 +04:00
int ret = - ENOTTY ;
2006-03-25 14:06:37 +03:00
switch ( cmd ) {
case WDIOC_GETSUPPORT :
ret = copy_to_user ( ( struct watchdog_info __user * ) arg , & ident ,
sizeof ( ident ) ) ? - EFAULT : 0 ;
break ;
case WDIOC_GETSTATUS :
ret = put_user ( 0 , ( int __user * ) arg ) ;
break ;
case WDIOC_GETBOOTSTATUS :
ret = put_user ( boot_status , ( int __user * ) arg ) ;
break ;
case WDIOC_KEEPALIVE :
wdt_keepalive ( ) ;
ret = 0 ;
break ;
2008-07-18 15:41:17 +04:00
case WDIOC_GETTIMEOUT :
/* actually, it is 0.250 seconds.... */
ret = put_user ( 1 , ( int __user * ) arg ) ;
break ;
2006-03-25 14:06:37 +03:00
}
return ret ;
}
static int ep93xx_wdt_release ( struct inode * inode , struct file * file )
{
if ( test_bit ( WDT_OK_TO_CLOSE , & wdt_status ) )
wdt_shutdown ( ) ;
else
2008-05-19 17:05:35 +04:00
printk ( KERN_CRIT PFX
" Device closed unexpectedly - timer will not stop \n " ) ;
2006-03-25 14:06:37 +03:00
clear_bit ( WDT_IN_USE , & wdt_status ) ;
clear_bit ( WDT_OK_TO_CLOSE , & wdt_status ) ;
return 0 ;
}
2006-07-03 11:24:21 +04:00
static const struct file_operations ep93xx_wdt_fops = {
2006-03-25 14:06:37 +03:00
. owner = THIS_MODULE ,
. write = ep93xx_wdt_write ,
2008-05-19 17:05:35 +04:00
. unlocked_ioctl = ep93xx_wdt_ioctl ,
2006-03-25 14:06:37 +03:00
. open = ep93xx_wdt_open ,
. release = ep93xx_wdt_release ,
} ;
static struct miscdevice ep93xx_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & ep93xx_wdt_fops ,
} ;
static void ep93xx_timer_ping ( unsigned long data )
{
if ( time_before ( jiffies , next_heartbeat ) )
wdt_ping ( ) ;
/* Re-set the timer interval */
mod_timer ( & timer , jiffies + WDT_INTERVAL ) ;
}
static int __init ep93xx_wdt_init ( void )
{
int err ;
err = misc_register ( & ep93xx_wdt_miscdev ) ;
boot_status = __raw_readl ( EP93XX_WDT_WATCHDOG ) & 0x01 ? 1 : 0 ;
printk ( KERN_INFO PFX " EP93XX watchdog, driver version "
WDT_VERSION " %s \n " ,
( __raw_readl ( EP93XX_WDT_WATCHDOG ) & 0x08 )
? " (nCS1 disable detected) " : " " ) ;
if ( timeout < 1 | | timeout > 3600 ) {
timeout = WDT_TIMEOUT ;
printk ( KERN_INFO PFX
" timeout value must be 1<=x<=3600, using %d \n " ,
timeout ) ;
}
setup_timer ( & timer , ep93xx_timer_ping , 1 ) ;
return err ;
}
static void __exit ep93xx_wdt_exit ( void )
{
wdt_shutdown ( ) ;
misc_deregister ( & ep93xx_wdt_miscdev ) ;
}
module_init ( ep93xx_wdt_init ) ;
module_exit ( ep93xx_wdt_exit ) ;
module_param ( nowayout , int , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started " ) ;
module_param ( timeout , int , 0 ) ;
2008-05-19 17:05:35 +04:00
MODULE_PARM_DESC ( timeout ,
" Watchdog timeout in seconds. (1<=timeout<=3600, default= "
__MODULE_STRING ( WATCHDOG_TIMEOUT ) " ) " ) ;
2006-03-25 14:06:37 +03:00
MODULE_AUTHOR ( " Ray Lehtiniemi <rayl@mail.com>, "
" Alessandro Zummo <a.zummo@towertech.it> " ) ;
MODULE_DESCRIPTION ( " EP93xx Watchdog " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( WDT_VERSION ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;