2005-04-17 06:20:36 +08:00
/*
* drivers / char / watchdog / shwdt . c
*
* Watchdog driver for integrated watchdog in the SuperH processors .
*
* Copyright ( C ) 2001 , 2002 , 2003 Paul Mundt < lethal @ linux - sh . org >
*
* 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 .
*
* 14 - Dec - 2001 Matt Domsch < Matt_Domsch @ dell . com >
* Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
*
* 19 - Apr - 2002 Rob Radez < rob @ osinvestor . com >
* Added expect close support , made emulated timeout runtime changeable
* general cleanups , add some ioctls
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/reboot.h>
# include <linux/notifier.h>
# include <linux/ioport.h>
# include <linux/fs.h>
2006-09-27 16:53:59 +08:00
# include <linux/mm.h>
2005-04-17 06:20:36 +08:00
# include <asm/io.h>
# include <asm/uaccess.h>
# include <asm/watchdog.h>
# define PFX "shwdt: "
/*
* Default clock division ratio is 5.25 msecs . For an additional table of
* values , consult the asm - sh / watchdog . h . Overload this at module load
* time .
*
* In order for this to work reliably we need to have HZ set to 1000 or
* something quite higher than 100 ( or we need a proper high - res timer
* implementation that will deal with this properly ) , otherwise the 10 ms
* resolution of a jiffy is enough to trigger the overflow . For things like
* the SH - 4 and SH - 5 , this isn ' t necessarily that big of a problem , though
* for the SH - 2 and SH - 3 , this isn ' t recommended unless the WDT is absolutely
* necssary .
*
* As a result of this timing problem , the only modes that are particularly
* feasible are the 4096 and the 2048 divisors , which yeild 5.25 and 2.62 ms
* overflow periods respectively .
*
* Also , since we can ' t really expect userspace to be responsive enough
2008-02-03 23:32:52 +08:00
* before the overflow happens , we maintain two separate timers . . One in
2005-04-17 06:20:36 +08:00
* the kernel for clearing out WOVF every 2 ms or so ( again , this depends on
* HZ = = 1000 ) , and another for monitoring userspace writes to the WDT device .
*
* As such , we currently use a configurable heartbeat interval which defaults
* to 30 s . In this case , the userspace daemon is only responsible for periodic
* writes to the device before the next heartbeat is scheduled . If the daemon
* misses its deadline , the kernel timer will allow the WDT to overflow .
*/
static int clock_division_ratio = WTCSR_CKS_4096 ;
# define next_ping_period(cks) msecs_to_jiffies(cks - 4)
2007-02-09 01:39:36 +08:00
static void sh_wdt_ping ( unsigned long data ) ;
2005-04-17 06:20:36 +08:00
static unsigned long shwdt_is_open ;
static struct watchdog_info sh_wdt_info ;
static char shwdt_expect_close ;
2007-02-09 01:39:36 +08:00
static DEFINE_TIMER ( timer , sh_wdt_ping , 0 , 0 ) ;
2005-04-17 06:20:36 +08:00
static unsigned long next_heartbeat ;
# define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
static int heartbeat = WATCHDOG_HEARTBEAT ; /* in seconds */
2005-07-28 02:43:58 +08:00
static int nowayout = WATCHDOG_NOWAYOUT ;
2005-04-17 06:20:36 +08:00
/**
* sh_wdt_start - Start the Watchdog
*
* Starts the watchdog .
*/
static void sh_wdt_start ( void )
{
__u8 csr ;
next_heartbeat = jiffies + ( heartbeat * HZ ) ;
mod_timer ( & timer , next_ping_period ( clock_division_ratio ) ) ;
csr = sh_wdt_read_csr ( ) ;
csr | = WTCSR_WT | clock_division_ratio ;
sh_wdt_write_csr ( csr ) ;
sh_wdt_write_cnt ( 0 ) ;
/*
* These processors have a bit of an inconsistent initialization
* process . . starting with SH - 3 , RSTS was moved to WTCSR , and the
* RSTCSR register was removed .
*
* On the SH - 2 however , in addition with bits being in different
* locations , we must deal with RSTCSR outright . .
*/
csr = sh_wdt_read_csr ( ) ;
csr | = WTCSR_TME ;
csr & = ~ WTCSR_RSTS ;
sh_wdt_write_csr ( csr ) ;
# ifdef CONFIG_CPU_SH2
/*
* Whoever came up with the RSTCSR semantics must ' ve been smoking
* some of the good stuff , since in addition to the WTCSR / WTCNT write
* brain - damage , it ' s managed to fuck things up one step further . .
*
* If we need to clear the WOVF bit , the upper byte has to be 0xa5 . .
* but if we want to touch RSTE or RSTS , the upper byte has to be
* 0x5a . .
*/
csr = sh_wdt_read_rstcsr ( ) ;
csr & = ~ RSTCSR_RSTS ;
sh_wdt_write_rstcsr ( csr ) ;
# endif
}
/**
* sh_wdt_stop - Stop the Watchdog
* Stops the watchdog .
*/
static void sh_wdt_stop ( void )
{
__u8 csr ;
del_timer ( & timer ) ;
csr = sh_wdt_read_csr ( ) ;
csr & = ~ WTCSR_TME ;
sh_wdt_write_csr ( csr ) ;
}
/**
* sh_wdt_keepalive - Keep the Userspace Watchdog Alive
* The Userspace watchdog got a KeepAlive : schedule the next heartbeat .
*/
2006-09-27 11:31:01 +08:00
static inline void sh_wdt_keepalive ( void )
2005-04-17 06:20:36 +08:00
{
next_heartbeat = jiffies + ( heartbeat * HZ ) ;
}
/**
* sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat
* Set the Userspace Watchdog heartbeat
*/
static int sh_wdt_set_heartbeat ( int t )
{
2006-09-27 11:31:01 +08:00
if ( unlikely ( ( t < 1 ) | | ( t > 3600 ) ) ) /* arbitrary upper limit */
2005-04-17 06:20:36 +08:00
return - EINVAL ;
heartbeat = t ;
return 0 ;
}
/**
* sh_wdt_ping - Ping the Watchdog
* @ data : Unused
*
* Clears overflow bit , resets timer counter .
*/
static void sh_wdt_ping ( unsigned long data )
{
if ( time_before ( jiffies , next_heartbeat ) ) {
__u8 csr ;
csr = sh_wdt_read_csr ( ) ;
csr & = ~ WTCSR_IOVF ;
sh_wdt_write_csr ( csr ) ;
sh_wdt_write_cnt ( 0 ) ;
mod_timer ( & timer , next_ping_period ( clock_division_ratio ) ) ;
2006-09-27 11:31:01 +08:00
} else
printk ( KERN_WARNING PFX " Heartbeat lost! Will not ping "
" the watchdog \n " ) ;
2005-04-17 06:20:36 +08:00
}
/**
* sh_wdt_open - Open the Device
* @ inode : inode of device
* @ file : file handle of device
*
* Watchdog device is opened and started .
*/
static int sh_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 0 , & shwdt_is_open ) )
return - EBUSY ;
if ( nowayout )
__module_get ( THIS_MODULE ) ;
sh_wdt_start ( ) ;
return nonseekable_open ( inode , file ) ;
}
/**
* sh_wdt_close - Close the Device
* @ inode : inode of device
* @ file : file handle of device
*
* Watchdog device is closed and stopped .
*/
static int sh_wdt_close ( struct inode * inode , struct file * file )
{
if ( shwdt_expect_close = = 42 ) {
sh_wdt_stop ( ) ;
} else {
2006-09-27 11:31:01 +08:00
printk ( KERN_CRIT PFX " Unexpected close, not "
" stopping watchdog! \n " ) ;
2005-04-17 06:20:36 +08:00
sh_wdt_keepalive ( ) ;
}
clear_bit ( 0 , & shwdt_is_open ) ;
shwdt_expect_close = 0 ;
return 0 ;
}
/**
* sh_wdt_write - Write to Device
* @ file : file handle of device
* @ buf : buffer to write
* @ count : length of buffer
* @ ppos : offset
*
* Pings the watchdog on write .
*/
static ssize_t sh_wdt_write ( struct file * file , const char * buf ,
size_t count , loff_t * ppos )
{
if ( count ) {
if ( ! nowayout ) {
size_t i ;
shwdt_expect_close = 0 ;
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
if ( get_user ( c , buf + i ) )
return - EFAULT ;
if ( c = = ' V ' )
shwdt_expect_close = 42 ;
}
}
sh_wdt_keepalive ( ) ;
}
return count ;
}
2006-09-27 16:53:59 +08:00
/**
* sh_wdt_mmap - map WDT / CPG registers into userspace
* @ file : file structure for the device
* @ vma : VMA to map the registers into
*
* A simple mmap ( ) implementation for the corner cases where the counter
* needs to be mapped in userspace directly . Due to the relatively small
* size of the area , neighbouring registers not necessarily tied to the
* CPG will also be accessible through the register page , so this remains
* configurable for users that really know what they ' re doing .
*
* Additionaly , the register page maps in the CPG register base relative
* to the nearest page - aligned boundary , which requires that userspace do
* the appropriate CPU subtype math for calculating the page offset for
* the counter value .
*/
static int sh_wdt_mmap ( struct file * file , struct vm_area_struct * vma )
{
int ret = - ENOSYS ;
# ifdef CONFIG_SH_WDT_MMAP
unsigned long addr ;
/* Only support the simple cases where we map in a register page. */
if ( ( ( vma - > vm_end - vma - > vm_start ) ! = PAGE_SIZE ) | | vma - > vm_pgoff )
return - EINVAL ;
/*
* Pick WTCNT as the start , it ' s usually the first register after the
* FRQCR , and neither one are generally page - aligned out of the box .
*/
addr = WTCNT & ~ ( PAGE_SIZE - 1 ) ;
vma - > vm_flags | = VM_IO ;
vma - > vm_page_prot = pgprot_noncached ( vma - > vm_page_prot ) ;
if ( io_remap_pfn_range ( vma , vma - > vm_start , addr > > PAGE_SHIFT ,
PAGE_SIZE , vma - > vm_page_prot ) ) {
printk ( KERN_ERR PFX " %s: io_remap_pfn_range failed \n " ,
2008-03-06 10:24:58 +08:00
__func__ ) ;
2006-09-27 16:53:59 +08:00
return - EAGAIN ;
}
ret = 0 ;
# endif
return ret ;
}
2005-04-17 06:20:36 +08:00
/**
* sh_wdt_ioctl - Query Device
* @ inode : inode of device
* @ file : file handle of device
* @ cmd : watchdog command
* @ arg : argument
*
* Query basic information from the device or ping it , as outlined by the
* watchdog API .
*/
static int sh_wdt_ioctl ( struct inode * inode , struct file * file ,
unsigned int cmd , unsigned long arg )
{
int new_heartbeat ;
int options , retval = - EINVAL ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( ( struct watchdog_info * ) arg ,
& sh_wdt_info ,
sizeof ( sh_wdt_info ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , ( int * ) arg ) ;
case WDIOC_KEEPALIVE :
sh_wdt_keepalive ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_heartbeat , ( int * ) arg ) )
return - EFAULT ;
if ( sh_wdt_set_heartbeat ( new_heartbeat ) )
return - EINVAL ;
sh_wdt_keepalive ( ) ;
/* Fall */
case WDIOC_GETTIMEOUT :
return put_user ( heartbeat , ( int * ) arg ) ;
case WDIOC_SETOPTIONS :
if ( get_user ( options , ( int * ) arg ) )
return - EFAULT ;
if ( options & WDIOS_DISABLECARD ) {
sh_wdt_stop ( ) ;
retval = 0 ;
}
if ( options & WDIOS_ENABLECARD ) {
sh_wdt_start ( ) ;
retval = 0 ;
}
return retval ;
default :
2006-09-09 23:34:31 +08:00
return - ENOTTY ;
2005-04-17 06:20:36 +08:00
}
return 0 ;
}
/**
* sh_wdt_notify_sys - Notifier Handler
* @ this : notifier block
* @ code : notifier event
* @ unused : unused
*
* Handles specific events , such as turning off the watchdog during a
* shutdown event .
*/
static int sh_wdt_notify_sys ( struct notifier_block * this ,
unsigned long code , void * unused )
{
2006-09-27 11:31:01 +08:00
if ( code = = SYS_DOWN | | code = = SYS_HALT )
2005-04-17 06:20:36 +08:00
sh_wdt_stop ( ) ;
return NOTIFY_DONE ;
}
2006-07-03 15:24:21 +08:00
static const struct file_operations sh_wdt_fops = {
2005-04-17 06:20:36 +08:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = sh_wdt_write ,
. ioctl = sh_wdt_ioctl ,
. open = sh_wdt_open ,
. release = sh_wdt_close ,
2006-09-27 16:53:59 +08:00
. mmap = sh_wdt_mmap ,
2005-04-17 06:20:36 +08:00
} ;
static struct watchdog_info sh_wdt_info = {
2006-09-27 11:31:01 +08:00
. options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE ,
2005-04-17 06:20:36 +08:00
. firmware_version = 1 ,
. identity = " SH WDT " ,
} ;
static struct notifier_block sh_wdt_notifier = {
. notifier_call = sh_wdt_notify_sys ,
} ;
static struct miscdevice sh_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & sh_wdt_fops ,
} ;
/**
* sh_wdt_init - Initialize module
* Registers the device and notifier handler . Actual device
* initialization is handled by sh_wdt_open ( ) .
*/
static int __init sh_wdt_init ( void )
{
int rc ;
if ( ( clock_division_ratio < 0x5 ) | | ( clock_division_ratio > 0x7 ) ) {
clock_division_ratio = WTCSR_CKS_4096 ;
2006-09-27 11:31:01 +08:00
printk ( KERN_INFO PFX " clock_division_ratio value must "
" be 0x5<=x<=0x7, using %d \n " , clock_division_ratio ) ;
2005-04-17 06:20:36 +08:00
}
2006-09-27 11:31:01 +08:00
rc = sh_wdt_set_heartbeat ( heartbeat ) ;
if ( unlikely ( rc ) ) {
2005-04-17 06:20:36 +08:00
heartbeat = WATCHDOG_HEARTBEAT ;
2006-09-27 11:31:01 +08:00
printk ( KERN_INFO PFX " heartbeat value must "
" be 1<=x<=3600, using %d \n " , heartbeat ) ;
2005-04-17 06:20:36 +08:00
}
rc = register_reboot_notifier ( & sh_wdt_notifier ) ;
2006-09-27 11:31:01 +08:00
if ( unlikely ( rc ) ) {
printk ( KERN_ERR PFX " Can't register reboot notifier (err=%d) \n " ,
rc ) ;
2005-04-17 06:20:36 +08:00
return rc ;
}
rc = misc_register ( & sh_wdt_miscdev ) ;
2006-09-27 11:31:01 +08:00
if ( unlikely ( rc ) ) {
printk ( KERN_ERR PFX " Can't register miscdev on "
" minor=%d (err=%d) \n " , sh_wdt_miscdev . minor , rc ) ;
2005-04-17 06:20:36 +08:00
unregister_reboot_notifier ( & sh_wdt_notifier ) ;
return rc ;
}
printk ( KERN_INFO PFX " initialized. heartbeat=%d sec (nowayout=%d) \n " ,
heartbeat , nowayout ) ;
return 0 ;
}
/**
* sh_wdt_exit - Deinitialize module
* Unregisters the device and notifier handler . Actual device
* deinitialization is handled by sh_wdt_close ( ) .
*/
static void __exit sh_wdt_exit ( void )
{
misc_deregister ( & sh_wdt_miscdev ) ;
unregister_reboot_notifier ( & sh_wdt_notifier ) ;
}
MODULE_AUTHOR ( " Paul Mundt <lethal@linux-sh.org> " ) ;
MODULE_DESCRIPTION ( " SuperH watchdog driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
module_param ( clock_division_ratio , int , 0 ) ;
2006-09-27 11:31:01 +08:00
MODULE_PARM_DESC ( clock_division_ratio , " Clock division ratio. Valid ranges are from 0x5 (1.31ms) to 0x7 (5.25ms). (default= " __MODULE_STRING ( clock_division_ratio ) " ) " ) ;
2005-04-17 06:20:36 +08:00
module_param ( heartbeat , int , 0 ) ;
MODULE_PARM_DESC ( heartbeat , " Watchdog heartbeat in seconds. (1<=heartbeat<=3600, default= " __MODULE_STRING ( WATCHDOG_HEARTBEAT ) " ) " ) ;
module_param ( nowayout , int , 0 ) ;
2006-09-27 11:31:01 +08:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2005-04-17 06:20:36 +08:00
module_init ( sh_wdt_init ) ;
module_exit ( sh_wdt_exit ) ;