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 .
*
2012-05-10 09:21:15 +04:00
* Copyright ( C ) 2001 - 2012 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
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 02:20:36 +04:00
# 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>
2012-05-10 10:15:08 +04:00
# include <linux/spinlock.h>
2005-04-17 02:20:36 +04:00
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
2012-05-10 11:08:35 +04:00
# include <linux/pm_runtime.h>
2005-04-17 02:20:36 +04:00
# 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>
2012-05-10 10:46:48 +04:00
# include <linux/clk.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
# define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
static int heartbeat = WATCHDOG_HEARTBEAT ; /* in seconds */
2012-03-05 19:51:11 +04:00
static bool 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 ;
2012-05-10 10:46:48 +04:00
struct clk * clk ;
2012-05-10 10:15:08 +04:00
spinlock_t lock ;
2005-04-17 02:20:36 +04:00
2010-05-25 13:31:12 +04:00
struct timer_list timer ;
} ;
2012-05-10 10:07:53 +04:00
static int sh_wdt_start ( struct watchdog_device * wdt_dev )
2005-04-17 02:20:36 +04:00
{
2012-05-10 10:07:53 +04:00
struct sh_wdt * wdt = watchdog_get_drvdata ( wdt_dev ) ;
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
2012-05-10 11:08:35 +04:00
pm_runtime_get_sync ( wdt - > dev ) ;
2012-05-10 11:14:40 +04:00
clk_enable ( wdt - > clk ) ;
2012-05-10 11:08:35 +04:00
2012-05-10 10:15:08 +04:00
spin_lock_irqsave ( & wdt - > 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
2012-05-10 10:15:08 +04:00
spin_unlock_irqrestore ( & wdt - > lock , flags ) ;
2012-05-10 10:07:53 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2012-05-10 10:07:53 +04:00
static int sh_wdt_stop ( struct watchdog_device * wdt_dev )
2005-04-17 02:20:36 +04:00
{
2012-05-10 10:07:53 +04:00
struct sh_wdt * wdt = watchdog_get_drvdata ( wdt_dev ) ;
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
2012-05-10 10:15:08 +04:00
spin_lock_irqsave ( & wdt - > 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
2012-05-10 10:15:08 +04:00
spin_unlock_irqrestore ( & wdt - > lock , flags ) ;
2012-05-10 10:07:53 +04:00
2012-05-10 11:14:40 +04:00
clk_disable ( wdt - > clk ) ;
2012-05-10 11:08:35 +04:00
pm_runtime_put_sync ( wdt - > dev ) ;
2012-05-10 10:07:53 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2012-05-10 10:07:53 +04:00
static int sh_wdt_keepalive ( struct watchdog_device * wdt_dev )
2005-04-17 02:20:36 +04:00
{
2012-05-10 10:15:08 +04:00
struct sh_wdt * wdt = watchdog_get_drvdata ( wdt_dev ) ;
2008-05-19 17:08:55 +04:00
unsigned long flags ;
2012-05-10 10:15:08 +04:00
spin_lock_irqsave ( & wdt - > lock , flags ) ;
2005-04-17 02:20:36 +04:00
next_heartbeat = jiffies + ( heartbeat * HZ ) ;
2012-05-10 10:15:08 +04:00
spin_unlock_irqrestore ( & wdt - > lock , flags ) ;
2012-05-10 10:07:53 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2012-05-10 10:07:53 +04:00
static int sh_wdt_set_heartbeat ( struct watchdog_device * wdt_dev , unsigned t )
2005-04-17 02:20:36 +04:00
{
2012-05-10 10:15:08 +04:00
struct sh_wdt * wdt = watchdog_get_drvdata ( wdt_dev ) ;
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 ;
2012-05-10 10:15:08 +04:00
spin_lock_irqsave ( & wdt - > lock , flags ) ;
2005-04-17 02:20:36 +04:00
heartbeat = t ;
2012-05-10 10:07:53 +04:00
wdt_dev - > timeout = t ;
2012-05-10 10:15:08 +04:00
spin_unlock_irqrestore ( & wdt - > lock , flags ) ;
2012-05-10 10:07:53 +04:00
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 ;
2012-05-10 10:15:08 +04:00
spin_lock_irqsave ( & wdt - > 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 " ) ;
2012-05-10 10:15:08 +04:00
spin_unlock_irqrestore ( & wdt - > lock , flags ) ;
2005-04-17 02:20:36 +04:00
}
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 " ,
} ;
2012-05-10 10:07:53 +04:00
static const struct watchdog_ops sh_wdt_ops = {
. owner = THIS_MODULE ,
. start = sh_wdt_start ,
. stop = sh_wdt_stop ,
. ping = sh_wdt_keepalive ,
. set_timeout = sh_wdt_set_heartbeat ,
} ;
static struct watchdog_device sh_wdt_dev = {
. info = & sh_wdt_info ,
. ops = & sh_wdt_ops ,
2005-04-17 02:20:36 +04:00
} ;
2012-11-19 22:21:41 +04:00
static int 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 ;
wdt = devm_kzalloc ( & pdev - > dev , sizeof ( struct sh_wdt ) , GFP_KERNEL ) ;
2012-05-10 10:46:48 +04:00
if ( unlikely ( ! wdt ) )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
2010-05-25 13:31:12 +04:00
wdt - > dev = & pdev - > dev ;
2012-05-10 10:46:48 +04:00
wdt - > clk = clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( wdt - > clk ) ) {
/*
* Clock framework support is optional , continue on
* anyways if we don ' t find a matching clock .
*/
wdt - > clk = NULL ;
}
2012-05-10 10:15:08 +04:00
2012-05-10 10:46:48 +04:00
wdt - > base = devm_request_and_ioremap ( wdt - > dev , res ) ;
2010-05-25 13:31:12 +04:00
if ( unlikely ( ! wdt - > base ) ) {
2012-05-10 10:46:48 +04:00
rc = - EADDRNOTAVAIL ;
2012-05-10 11:14:40 +04:00
goto err ;
2005-04-17 02:20:36 +04:00
}
2012-05-10 10:46:48 +04:00
watchdog_set_nowayout ( & sh_wdt_dev , nowayout ) ;
watchdog_set_drvdata ( & sh_wdt_dev , wdt ) ;
2012-05-10 10:15:08 +04:00
spin_lock_init ( & wdt - > lock ) ;
2012-05-10 10:07:53 +04:00
rc = sh_wdt_set_heartbeat ( & sh_wdt_dev , heartbeat ) ;
if ( unlikely ( rc ) ) {
/* Default timeout if invalid */
sh_wdt_set_heartbeat ( & sh_wdt_dev , WATCHDOG_HEARTBEAT ) ;
dev_warn ( & pdev - > dev ,
" heartbeat value must be 1<=x<=3600, using %d \n " ,
sh_wdt_dev . timeout ) ;
}
dev_info ( & pdev - > dev , " configured with heartbeat=%d sec (nowayout=%d) \n " ,
sh_wdt_dev . timeout , nowayout ) ;
2010-05-25 13:31:12 +04:00
2012-05-10 10:07:53 +04:00
rc = watchdog_register_device ( & sh_wdt_dev ) ;
2006-09-27 07:31:01 +04:00
if ( unlikely ( rc ) ) {
2012-05-10 10:07:53 +04:00
dev_err ( & pdev - > dev , " Can't register watchdog (err=%d) \n " , rc ) ;
2012-05-10 11:14:40 +04:00
goto err ;
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 ) ;
dev_info ( & pdev - > dev , " initialized. \n " ) ;
2005-04-17 02:20:36 +04:00
2012-05-10 11:08:35 +04:00
pm_runtime_enable ( & pdev - > dev ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
2010-05-25 13:31:12 +04:00
2012-05-10 11:14:40 +04:00
err :
2012-05-10 10:46:48 +04:00
clk_put ( wdt - > clk ) ;
2010-05-25 13:31:12 +04:00
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 ) ;
platform_set_drvdata ( pdev , NULL ) ;
2012-05-10 10:07:53 +04:00
watchdog_unregister_device ( & sh_wdt_dev ) ;
2010-05-25 13:31:12 +04:00
2012-05-10 11:08:35 +04:00
pm_runtime_disable ( & pdev - > dev ) ;
2012-05-10 10:46:48 +04:00
clk_put ( wdt - > clk ) ;
2010-05-25 13:31:12 +04:00
return 0 ;
}
2012-05-10 09:21:15 +04:00
static void sh_wdt_shutdown ( struct platform_device * pdev )
{
2012-05-10 10:07:53 +04:00
sh_wdt_stop ( & sh_wdt_dev ) ;
2012-05-10 09:21:15 +04:00
}
2010-05-25 13:31:12 +04:00
static struct platform_driver sh_wdt_driver = {
. driver = {
. name = DRV_NAME ,
. owner = THIS_MODULE ,
} ,
2012-05-10 09:21:15 +04:00
. probe = sh_wdt_probe ,
2012-11-19 22:21:12 +04:00
. remove = sh_wdt_remove ,
2012-05-10 09:21:15 +04:00
. shutdown = sh_wdt_shutdown ,
2010-05-25 13:31:12 +04:00
} ;
static int __init sh_wdt_init ( void )
{
if ( unlikely ( clock_division_ratio < 0x5 | |
clock_division_ratio > 0x7 ) ) {
clock_division_ratio = WTCSR_CKS_4096 ;
2012-02-16 03:06:19 +04:00
pr_info ( " divisor must be 0x5<=x<=0x7, using %d \n " ,
clock_division_ratio ) ;
2010-05-25 13:31:12 +04:00
}
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
2012-03-05 19:51:11 +04:00
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:08:55 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;