2005-04-17 02:20:36 +04:00
/*
2010-05-24 05:55:59 +04:00
* drivers / watchdog / shwdt . c
2005-04-17 02:20:36 +04:00
*
* Watchdog driver for integrated watchdog in the SuperH processors .
*
2010-05-24 05:55:59 +04:00
* Copyright ( C ) 2001 - 2010 Paul Mundt < lethal @ linux - sh . org >
2005-04-17 02:20:36 +04:00
*
* 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>
2010-05-25 13:31:12 +04:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
# 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 12:53:59 +04:00
# include <linux/mm.h>
2010-05-25 13:31:12 +04:00
# include <linux/slab.h>
2008-05-19 17:08:55 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2008-08-08 19:39:11 +04:00
# include <asm/watchdog.h>
2005-04-17 02:20:36 +04:00
2010-05-25 13:31:12 +04:00
# define DRV_NAME "sh-wdt"
2005-04-17 02:20:36 +04:00
/*
* 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
2011-03-31 05:57:33 +04:00
* feasible are the 4096 and the 2048 divisors , which yield 5.25 and 2.62 ms
2005-04-17 02:20:36 +04:00
* overflow periods respectively .
*
* Also , since we can ' t really expect userspace to be responsive enough
2008-02-03 18:32:52 +03:00
* before the overflow happens , we maintain two separate timers . . One in
2005-04-17 02:20:36 +04: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 ;
2011-07-20 17:03:39 +04:00
# define next_ping_period(cks) (jiffies + msecs_to_jiffies(cks - 4))
2005-04-17 02:20:36 +04:00
2008-08-08 19:39:11 +04:00
static const struct watchdog_info sh_wdt_info ;
2010-05-25 13:31:12 +04:00
static struct platform_device * sh_wdt_dev ;
2008-05-19 17:08:55 +04:00
static DEFINE_SPINLOCK ( shwdt_lock ) ;
2005-04-17 02:20:36 +04:00
# define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
static int heartbeat = WATCHDOG_HEARTBEAT ; /* in seconds */
2005-07-27 22:43:58 +04:00
static int nowayout = WATCHDOG_NOWAYOUT ;
2010-05-25 13:31:12 +04:00
static unsigned long next_heartbeat ;
struct sh_wdt {
void __iomem * base ;
struct device * dev ;
2005-04-17 02:20:36 +04:00
2010-05-25 13:31:12 +04:00
struct timer_list timer ;
unsigned long enabled ;
char expect_close ;
} ;
static void sh_wdt_start ( struct sh_wdt * wdt )
2005-04-17 02:20:36 +04:00
{
2008-05-19 17:08:55 +04:00
unsigned long flags ;
2010-05-25 13:31:12 +04:00
u8 csr ;
2008-05-19 17:08:55 +04:00
2008-08-08 19:39:11 +04:00
spin_lock_irqsave ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
next_heartbeat = jiffies + ( heartbeat * HZ ) ;
2010-05-25 13:31:12 +04:00
mod_timer ( & wdt - > timer , next_ping_period ( clock_division_ratio ) ) ;
2005-04-17 02:20:36 +04:00
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
csr = sh_wdt_read_rstcsr ( ) ;
csr & = ~ RSTCSR_RSTS ;
sh_wdt_write_rstcsr ( csr ) ;
# endif
2008-08-08 19:39:11 +04:00
spin_unlock_irqrestore ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
}
2010-05-25 13:31:12 +04:00
static void sh_wdt_stop ( struct sh_wdt * wdt )
2005-04-17 02:20:36 +04:00
{
2008-05-19 17:08:55 +04:00
unsigned long flags ;
2010-05-25 13:31:12 +04:00
u8 csr ;
2008-05-19 17:08:55 +04:00
2008-08-08 19:39:11 +04:00
spin_lock_irqsave ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
2010-05-25 13:31:12 +04:00
del_timer ( & wdt - > timer ) ;
2005-04-17 02:20:36 +04:00
csr = sh_wdt_read_csr ( ) ;
csr & = ~ WTCSR_TME ;
sh_wdt_write_csr ( csr ) ;
2010-05-25 13:31:12 +04:00
2008-08-08 19:39:11 +04:00
spin_unlock_irqrestore ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
}
2010-05-25 13:31:12 +04:00
static inline void sh_wdt_keepalive ( struct sh_wdt * wdt )
2005-04-17 02:20:36 +04:00
{
2008-05-19 17:08:55 +04:00
unsigned long flags ;
2008-08-08 19:39:11 +04:00
spin_lock_irqsave ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
next_heartbeat = jiffies + ( heartbeat * HZ ) ;
2008-08-08 19:39:11 +04:00
spin_unlock_irqrestore ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
}
static int sh_wdt_set_heartbeat ( int t )
{
2008-05-19 17:08:55 +04:00
unsigned long flags ;
if ( unlikely ( t < 1 | | t > 3600 ) ) /* arbitrary upper limit */
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2008-08-08 19:39:11 +04:00
spin_lock_irqsave ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
heartbeat = t ;
2008-08-08 19:39:11 +04:00
spin_unlock_irqrestore ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void sh_wdt_ping ( unsigned long data )
{
2010-05-25 13:31:12 +04:00
struct sh_wdt * wdt = ( struct sh_wdt * ) data ;
2008-05-19 17:08:55 +04:00
unsigned long flags ;
2008-08-08 19:39:11 +04:00
spin_lock_irqsave ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
if ( time_before ( jiffies , next_heartbeat ) ) {
2010-05-25 13:31:12 +04:00
u8 csr ;
2005-04-17 02:20:36 +04:00
csr = sh_wdt_read_csr ( ) ;
csr & = ~ WTCSR_IOVF ;
sh_wdt_write_csr ( csr ) ;
sh_wdt_write_cnt ( 0 ) ;
2010-05-25 13:31:12 +04:00
mod_timer ( & wdt - > timer , next_ping_period ( clock_division_ratio ) ) ;
2006-09-27 07:31:01 +04:00
} else
2010-05-25 13:31:12 +04:00
dev_warn ( wdt - > dev , " Heartbeat lost! Will not ping "
" the watchdog \n " ) ;
2008-08-08 19:39:11 +04:00
spin_unlock_irqrestore ( & shwdt_lock , flags ) ;
2005-04-17 02:20:36 +04:00
}
static int sh_wdt_open ( struct inode * inode , struct file * file )
{
2010-05-25 13:31:12 +04:00
struct sh_wdt * wdt = platform_get_drvdata ( sh_wdt_dev ) ;
if ( test_and_set_bit ( 0 , & wdt - > enabled ) )
2005-04-17 02:20:36 +04:00
return - EBUSY ;
if ( nowayout )
__module_get ( THIS_MODULE ) ;
2010-05-25 13:31:12 +04:00
file - > private_data = wdt ;
sh_wdt_start ( wdt ) ;
2005-04-17 02:20:36 +04:00
return nonseekable_open ( inode , file ) ;
}
static int sh_wdt_close ( struct inode * inode , struct file * file )
{
2010-05-25 13:31:12 +04:00
struct sh_wdt * wdt = file - > private_data ;
if ( wdt - > expect_close = = 42 ) {
sh_wdt_stop ( wdt ) ;
2005-04-17 02:20:36 +04:00
} else {
2010-05-25 13:31:12 +04:00
dev_crit ( wdt - > dev , " Unexpected close, not "
" stopping watchdog! \n " ) ;
sh_wdt_keepalive ( wdt ) ;
2005-04-17 02:20:36 +04:00
}
2010-05-25 13:31:12 +04:00
clear_bit ( 0 , & wdt - > enabled ) ;
wdt - > expect_close = 0 ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static ssize_t sh_wdt_write ( struct file * file , const char * buf ,
size_t count , loff_t * ppos )
{
2010-05-25 13:31:12 +04:00
struct sh_wdt * wdt = file - > private_data ;
2005-04-17 02:20:36 +04:00
if ( count ) {
if ( ! nowayout ) {
size_t i ;
2010-05-25 13:31:12 +04:00
wdt - > expect_close = 0 ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
if ( get_user ( c , buf + i ) )
return - EFAULT ;
if ( c = = ' V ' )
2010-05-25 13:31:12 +04:00
wdt - > expect_close = 42 ;
2005-04-17 02:20:36 +04:00
}
}
2010-05-25 13:31:12 +04:00
sh_wdt_keepalive ( wdt ) ;
2005-04-17 02:20:36 +04:00
}
return count ;
}
2008-05-19 17:08:55 +04:00
static long sh_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2010-05-25 13:31:12 +04:00
struct sh_wdt * wdt = file - > private_data ;
2005-04-17 02:20:36 +04:00
int new_heartbeat ;
int options , retval = - EINVAL ;
switch ( cmd ) {
2008-05-19 17:08:55 +04:00
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_SETOPTIONS :
if ( get_user ( options , ( int * ) arg ) )
return - EFAULT ;
if ( options & WDIOS_DISABLECARD ) {
2010-05-25 13:31:12 +04:00
sh_wdt_stop ( wdt ) ;
2008-05-19 17:08:55 +04:00
retval = 0 ;
}
if ( options & WDIOS_ENABLECARD ) {
2010-05-25 13:31:12 +04:00
sh_wdt_start ( wdt ) ;
2008-05-19 17:08:55 +04:00
retval = 0 ;
}
2005-04-17 02:20:36 +04:00
2008-05-19 17:08:55 +04:00
return retval ;
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
2010-05-25 13:31:12 +04:00
sh_wdt_keepalive ( wdt ) ;
2008-07-18 15:41:17 +04:00
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_heartbeat , ( int * ) arg ) )
return - EFAULT ;
if ( sh_wdt_set_heartbeat ( new_heartbeat ) )
return - EINVAL ;
2010-05-25 13:31:12 +04:00
sh_wdt_keepalive ( wdt ) ;
2008-07-18 15:41:17 +04:00
/* Fall */
case WDIOC_GETTIMEOUT :
return put_user ( heartbeat , ( int * ) arg ) ;
2008-05-19 17:08:55 +04:00
default :
return - ENOTTY ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int sh_wdt_notify_sys ( struct notifier_block * this ,
unsigned long code , void * unused )
{
2010-05-25 13:31:12 +04:00
struct sh_wdt * wdt = platform_get_drvdata ( sh_wdt_dev ) ;
2006-09-27 07:31:01 +04:00
if ( code = = SYS_DOWN | | code = = SYS_HALT )
2010-05-25 13:31:12 +04:00
sh_wdt_stop ( wdt ) ;
2005-04-17 02:20:36 +04:00
return NOTIFY_DONE ;
}
2006-07-03 11:24:21 +04:00
static const struct file_operations sh_wdt_fops = {
2005-04-17 02:20:36 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = sh_wdt_write ,
2008-05-19 17:08:55 +04:00
. unlocked_ioctl = sh_wdt_ioctl ,
2005-04-17 02:20:36 +04:00
. open = sh_wdt_open ,
. release = sh_wdt_close ,
} ;
2008-05-19 17:08:55 +04:00
static const struct watchdog_info sh_wdt_info = {
2006-09-27 07:31:01 +04:00
. options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE ,
2005-04-17 02:20:36 +04: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 ,
} ;
2010-05-25 13:31:12 +04:00
static int __devinit sh_wdt_probe ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
2010-05-25 13:31:12 +04:00
struct sh_wdt * wdt ;
struct resource * res ;
2005-04-17 02:20:36 +04:00
int rc ;
2010-05-25 13:31:12 +04:00
/*
* As this driver only covers the global watchdog case , reject
* any attempts to register per - CPU watchdogs .
*/
if ( pdev - > id ! = - 1 )
return - EINVAL ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( unlikely ( ! res ) )
return - EINVAL ;
if ( ! devm_request_mem_region ( & pdev - > dev , res - > start ,
resource_size ( res ) , DRV_NAME ) )
return - EBUSY ;
wdt = devm_kzalloc ( & pdev - > dev , sizeof ( struct sh_wdt ) , GFP_KERNEL ) ;
if ( unlikely ( ! wdt ) ) {
rc = - ENOMEM ;
goto out_release ;
2005-04-17 02:20:36 +04:00
}
2010-05-25 13:31:12 +04:00
wdt - > dev = & pdev - > dev ;
wdt - > base = devm_ioremap ( & pdev - > dev , res - > start , resource_size ( res ) ) ;
if ( unlikely ( ! wdt - > base ) ) {
rc = - ENXIO ;
goto out_err ;
2005-04-17 02:20:36 +04:00
}
rc = register_reboot_notifier ( & sh_wdt_notifier ) ;
2006-09-27 07:31:01 +04:00
if ( unlikely ( rc ) ) {
2010-05-25 13:31:12 +04:00
dev_err ( & pdev - > dev ,
2008-05-19 17:08:55 +04:00
" Can't register reboot notifier (err=%d) \n " , rc ) ;
2010-05-25 13:31:12 +04:00
goto out_unmap ;
2005-04-17 02:20:36 +04:00
}
2010-05-25 13:31:12 +04:00
sh_wdt_miscdev . parent = wdt - > dev ;
2005-04-17 02:20:36 +04:00
rc = misc_register ( & sh_wdt_miscdev ) ;
2006-09-27 07:31:01 +04:00
if ( unlikely ( rc ) ) {
2010-05-25 13:31:12 +04:00
dev_err ( & pdev - > dev ,
2008-05-19 17:08:55 +04:00
" Can't register miscdev on minor=%d (err=%d) \n " ,
sh_wdt_miscdev . minor , rc ) ;
2010-05-25 13:31:12 +04:00
goto out_unreg ;
2005-04-17 02:20:36 +04:00
}
2010-05-25 13:31:12 +04:00
init_timer ( & wdt - > timer ) ;
wdt - > timer . function = sh_wdt_ping ;
wdt - > timer . data = ( unsigned long ) wdt ;
wdt - > timer . expires = next_ping_period ( clock_division_ratio ) ;
platform_set_drvdata ( pdev , wdt ) ;
sh_wdt_dev = pdev ;
dev_info ( & pdev - > dev , " initialized. \n " ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
2010-05-25 13:31:12 +04:00
out_unreg :
unregister_reboot_notifier ( & sh_wdt_notifier ) ;
out_unmap :
devm_iounmap ( & pdev - > dev , wdt - > base ) ;
out_err :
devm_kfree ( & pdev - > dev , wdt ) ;
out_release :
devm_release_mem_region ( & pdev - > dev , res - > start , resource_size ( res ) ) ;
return rc ;
2005-04-17 02:20:36 +04:00
}
2010-05-25 13:31:12 +04:00
static int __devexit sh_wdt_remove ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
2010-05-25 13:31:12 +04:00
struct sh_wdt * wdt = platform_get_drvdata ( pdev ) ;
struct resource * res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
platform_set_drvdata ( pdev , NULL ) ;
2005-04-17 02:20:36 +04:00
misc_deregister ( & sh_wdt_miscdev ) ;
2010-05-25 13:31:12 +04:00
sh_wdt_dev = NULL ;
2005-04-17 02:20:36 +04:00
unregister_reboot_notifier ( & sh_wdt_notifier ) ;
2010-05-25 13:31:12 +04:00
devm_release_mem_region ( & pdev - > dev , res - > start , resource_size ( res ) ) ;
devm_iounmap ( & pdev - > dev , wdt - > base ) ;
devm_kfree ( & pdev - > dev , wdt ) ;
return 0 ;
}
static struct platform_driver sh_wdt_driver = {
. driver = {
. name = DRV_NAME ,
. owner = THIS_MODULE ,
} ,
. probe = sh_wdt_probe ,
. remove = __devexit_p ( sh_wdt_remove ) ,
} ;
static int __init sh_wdt_init ( void )
{
int rc ;
if ( unlikely ( clock_division_ratio < 0x5 | |
clock_division_ratio > 0x7 ) ) {
clock_division_ratio = WTCSR_CKS_4096 ;
pr_info ( " %s: divisor must be 0x5<=x<=0x7, using %d \n " ,
DRV_NAME , clock_division_ratio ) ;
}
rc = sh_wdt_set_heartbeat ( heartbeat ) ;
if ( unlikely ( rc ) ) {
heartbeat = WATCHDOG_HEARTBEAT ;
pr_info ( " %s: heartbeat value must be 1<=x<=3600, using %d \n " ,
DRV_NAME , heartbeat ) ;
}
pr_info ( " %s: configured with heartbeat=%d sec (nowayout=%d) \n " ,
DRV_NAME , heartbeat , nowayout ) ;
return platform_driver_register ( & sh_wdt_driver ) ;
2005-04-17 02:20:36 +04:00
}
2010-05-25 13:31:12 +04:00
static void __exit sh_wdt_exit ( void )
{
platform_driver_unregister ( & sh_wdt_driver ) ;
}
module_init ( sh_wdt_init ) ;
module_exit ( sh_wdt_exit ) ;
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( " Paul Mundt <lethal@linux-sh.org> " ) ;
MODULE_DESCRIPTION ( " SuperH watchdog driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
2010-05-25 13:31:12 +04:00
MODULE_ALIAS ( " platform: " DRV_NAME ) ;
2005-04-17 02:20:36 +04:00
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;
module_param ( clock_division_ratio , int , 0 ) ;
2009-04-15 00:20:07 +04:00
MODULE_PARM_DESC ( clock_division_ratio ,
" Clock division ratio. Valid ranges are from 0x5 (1.31ms) "
2010-05-01 20:46:15 +04:00
" to 0x7 (5.25ms). (default= " __MODULE_STRING ( WTCSR_CKS_4096 ) " ) " ) ;
2005-04-17 02:20:36 +04:00
module_param ( heartbeat , int , 0 ) ;
2008-05-19 17:08:55 +04:00
MODULE_PARM_DESC ( heartbeat ,
" Watchdog heartbeat in seconds. (1 <= heartbeat <= 3600, default= "
__MODULE_STRING ( WATCHDOG_HEARTBEAT ) " ) " ) ;
2005-04-17 02:20:36 +04:00
module_param ( nowayout , int , 0 ) ;
2008-05-19 17:08:55 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;