2010-08-04 10:14:14 +04:00
/*
* drivers / char / watchdog / sp805 - wdt . c
*
* Watchdog driver for ARM SP805 watchdog module
*
* Copyright ( C ) 2010 ST Microelectronics
* Viresh Kumar < viresh . kumar @ st . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2 or later . This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/device.h>
# include <linux/resource.h>
# include <linux/amba/bus.h>
# include <linux/bitops.h>
# include <linux/clk.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/ioport.h>
# include <linux/kernel.h>
# include <linux/math64.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
2012-02-24 13:42:37 +04:00
# include <linux/pm.h>
2010-08-04 10:14:14 +04:00
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/types.h>
# include <linux/watchdog.h>
/* default timeout in seconds */
# define DEFAULT_TIMEOUT 60
# define MODULE_NAME "sp805-wdt"
/* watchdog register offsets and masks */
# define WDTLOAD 0x000
# define LOAD_MIN 0x00000001
# define LOAD_MAX 0xFFFFFFFF
# define WDTVALUE 0x004
# define WDTCONTROL 0x008
/* control register masks */
# define INT_ENABLE (1 << 0)
# define RESET_ENABLE (1 << 1)
# define WDTINTCLR 0x00C
# define WDTRIS 0x010
# define WDTMIS 0x014
# define INT_MASK (1 << 0)
# define WDTLOCK 0xC00
# define UNLOCK 0x1ACCE551
# define LOCK 0x00000001
/**
* struct sp805_wdt : sp805 wdt device structure
2012-03-12 08:22:16 +04:00
* @ wdd : instance of struct watchdog_device
2012-03-12 08:22:13 +04:00
* @ lock : spin lock protecting dev structure and io access
* @ base : base address of wdt
* @ clk : clock structure of wdt
* @ adev : amba device structure of wdt
* @ status : current status of wdt
* @ load_val : load value to be set for current timeout
* @ timeout : current programmed timeout
2010-08-04 10:14:14 +04:00
*/
struct sp805_wdt {
2012-03-12 08:22:16 +04:00
struct watchdog_device wdd ;
2010-08-04 10:14:14 +04:00
spinlock_t lock ;
void __iomem * base ;
struct clk * clk ;
struct amba_device * adev ;
unsigned int load_val ;
unsigned int timeout ;
} ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2012-03-12 08:22:16 +04:00
module_param ( nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout ,
" Set to 1 to keep watchdog running after device release " ) ;
2010-08-04 10:14:14 +04:00
/* This routine finds load value that will reset system in required timout */
2012-03-12 08:22:16 +04:00
static int wdt_setload ( struct watchdog_device * wdd , unsigned int timeout )
2010-08-04 10:14:14 +04:00
{
2012-03-12 08:22:16 +04:00
struct sp805_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
2010-08-04 10:14:14 +04:00
u64 load , rate ;
rate = clk_get_rate ( wdt - > clk ) ;
/*
* sp805 runs counter with given value twice , after the end of first
* counter it gives an interrupt and then starts counter again . If
2011-03-31 05:57:33 +04:00
* interrupt already occurred then it resets the system . This is why
2010-08-04 10:14:14 +04:00
* load is half of what should be required .
*/
load = div_u64 ( rate , 2 ) * timeout - 1 ;
load = ( load > LOAD_MAX ) ? LOAD_MAX : load ;
load = ( load < LOAD_MIN ) ? LOAD_MIN : load ;
spin_lock ( & wdt - > lock ) ;
wdt - > load_val = load ;
/* roundup timeout to closest positive integer value */
wdt - > timeout = div_u64 ( ( load + 1 ) * 2 + ( rate / 2 ) , rate ) ;
spin_unlock ( & wdt - > lock ) ;
2012-03-12 08:22:16 +04:00
return 0 ;
2010-08-04 10:14:14 +04:00
}
/* returns number of seconds left for reset to occur */
2012-03-12 08:22:16 +04:00
static unsigned int wdt_timeleft ( struct watchdog_device * wdd )
2010-08-04 10:14:14 +04:00
{
2012-03-12 08:22:16 +04:00
struct sp805_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
2010-08-04 10:14:14 +04:00
u64 load , rate ;
rate = clk_get_rate ( wdt - > clk ) ;
spin_lock ( & wdt - > lock ) ;
2012-03-12 08:22:14 +04:00
load = readl_relaxed ( wdt - > base + WDTVALUE ) ;
2010-08-04 10:14:14 +04:00
/*If the interrupt is inactive then time left is WDTValue + WDTLoad. */
2012-03-12 08:22:14 +04:00
if ( ! ( readl_relaxed ( wdt - > base + WDTRIS ) & INT_MASK ) )
2010-08-04 10:14:14 +04:00
load + = wdt - > load_val + 1 ;
spin_unlock ( & wdt - > lock ) ;
return div_u64 ( load , rate ) ;
}
2012-03-12 08:22:16 +04:00
static int wdt_config ( struct watchdog_device * wdd , bool ping )
2010-08-04 10:14:14 +04:00
{
2012-03-12 08:22:16 +04:00
struct sp805_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
int ret ;
if ( ! ping ) {
2012-03-12 08:22:17 +04:00
ret = clk_prepare ( wdt - > clk ) ;
if ( ret ) {
dev_err ( & wdt - > adev - > dev , " clock prepare fail " ) ;
return ret ;
}
2012-03-12 08:22:16 +04:00
ret = clk_enable ( wdt - > clk ) ;
if ( ret ) {
dev_err ( & wdt - > adev - > dev , " clock enable fail " ) ;
2012-03-12 08:22:17 +04:00
clk_unprepare ( wdt - > clk ) ;
2012-03-12 08:22:16 +04:00
return ret ;
}
}
2010-08-04 10:14:14 +04:00
spin_lock ( & wdt - > lock ) ;
2012-03-12 08:22:14 +04:00
writel_relaxed ( UNLOCK , wdt - > base + WDTLOCK ) ;
writel_relaxed ( wdt - > load_val , wdt - > base + WDTLOAD ) ;
2010-08-04 10:14:14 +04:00
2012-03-12 08:22:16 +04:00
if ( ! ping ) {
writel_relaxed ( INT_MASK , wdt - > base + WDTINTCLR ) ;
writel_relaxed ( INT_ENABLE | RESET_ENABLE , wdt - > base +
WDTCONTROL ) ;
}
2010-08-04 10:14:14 +04:00
2012-03-12 08:22:14 +04:00
writel_relaxed ( LOCK , wdt - > base + WDTLOCK ) ;
2010-08-04 10:14:14 +04:00
2011-07-15 19:04:02 +04:00
/* Flush posted writes. */
2012-03-12 08:22:14 +04:00
readl_relaxed ( wdt - > base + WDTLOCK ) ;
2010-08-04 10:14:14 +04:00
spin_unlock ( & wdt - > lock ) ;
2012-03-12 08:22:16 +04:00
return 0 ;
2010-08-04 10:14:14 +04:00
}
2012-03-12 08:22:16 +04:00
static int wdt_ping ( struct watchdog_device * wdd )
2010-08-04 10:14:14 +04:00
{
2012-03-12 08:22:16 +04:00
return wdt_config ( wdd , true ) ;
2010-08-04 10:14:14 +04:00
}
2012-03-12 08:22:16 +04:00
/* enables watchdog timers reset */
static int wdt_enable ( struct watchdog_device * wdd )
2010-08-04 10:14:14 +04:00
{
2012-03-12 08:22:16 +04:00
return wdt_config ( wdd , false ) ;
2010-08-04 10:14:14 +04:00
}
2012-03-12 08:22:16 +04:00
/* disables watchdog timers reset */
static int wdt_disable ( struct watchdog_device * wdd )
2010-08-04 10:14:14 +04:00
{
2012-03-12 08:22:16 +04:00
struct sp805_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
2010-08-04 10:14:14 +04:00
2012-03-12 08:22:16 +04:00
spin_lock ( & wdt - > lock ) ;
2010-08-04 10:14:14 +04:00
2012-03-12 08:22:16 +04:00
writel_relaxed ( UNLOCK , wdt - > base + WDTLOCK ) ;
writel_relaxed ( 0 , wdt - > base + WDTCONTROL ) ;
writel_relaxed ( LOCK , wdt - > base + WDTLOCK ) ;
2010-08-04 10:14:14 +04:00
2012-03-12 08:22:16 +04:00
/* Flush posted writes. */
readl_relaxed ( wdt - > base + WDTLOCK ) ;
spin_unlock ( & wdt - > lock ) ;
2010-08-04 10:14:14 +04:00
clk_disable ( wdt - > clk ) ;
2012-03-12 08:22:17 +04:00
clk_unprepare ( wdt - > clk ) ;
2010-08-04 10:14:14 +04:00
return 0 ;
}
2012-03-12 08:22:16 +04:00
static const struct watchdog_info wdt_info = {
. options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
. identity = MODULE_NAME ,
2010-08-04 10:14:14 +04:00
} ;
2012-03-12 08:22:16 +04:00
static const struct watchdog_ops wdt_ops = {
. owner = THIS_MODULE ,
. start = wdt_enable ,
. stop = wdt_disable ,
. ping = wdt_ping ,
. set_timeout = wdt_setload ,
. get_timeleft = wdt_timeleft ,
2010-08-04 10:14:14 +04:00
} ;
static int __devinit
2011-02-19 18:55:00 +03:00
sp805_wdt_probe ( struct amba_device * adev , const struct amba_id * id )
2010-08-04 10:14:14 +04:00
{
2012-03-12 08:22:16 +04:00
struct sp805_wdt * wdt ;
2010-08-04 10:14:14 +04:00
int ret = 0 ;
2012-03-12 08:22:15 +04:00
if ( ! devm_request_mem_region ( & adev - > dev , adev - > res . start ,
resource_size ( & adev - > res ) , " sp805_wdt " ) ) {
2010-08-04 10:14:14 +04:00
dev_warn ( & adev - > dev , " Failed to get memory region resource \n " ) ;
ret = - ENOENT ;
goto err ;
}
2012-03-12 08:22:15 +04:00
wdt = devm_kzalloc ( & adev - > dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
2010-08-04 10:14:14 +04:00
if ( ! wdt ) {
dev_warn ( & adev - > dev , " Kzalloc failed \n " ) ;
ret = - ENOMEM ;
2012-03-12 08:22:15 +04:00
goto err ;
}
wdt - > base = devm_ioremap ( & adev - > dev , adev - > res . start ,
resource_size ( & adev - > res ) ) ;
if ( ! wdt - > base ) {
ret = - ENOMEM ;
dev_warn ( & adev - > dev , " ioremap fail \n " ) ;
goto err ;
2010-08-04 10:14:14 +04:00
}
wdt - > clk = clk_get ( & adev - > dev , NULL ) ;
if ( IS_ERR ( wdt - > clk ) ) {
dev_warn ( & adev - > dev , " Clock not found \n " ) ;
ret = PTR_ERR ( wdt - > clk ) ;
2012-03-12 08:22:15 +04:00
goto err ;
2010-08-04 10:14:14 +04:00
}
wdt - > adev = adev ;
2012-03-12 08:22:16 +04:00
wdt - > wdd . info = & wdt_info ;
wdt - > wdd . ops = & wdt_ops ;
2010-08-04 10:14:14 +04:00
spin_lock_init ( & wdt - > lock ) ;
2012-03-12 08:22:16 +04:00
watchdog_set_nowayout ( & wdt - > wdd , nowayout ) ;
watchdog_set_drvdata ( & wdt - > wdd , wdt ) ;
wdt_setload ( & wdt - > wdd , DEFAULT_TIMEOUT ) ;
2010-08-04 10:14:14 +04:00
2012-03-12 08:22:16 +04:00
ret = watchdog_register_device ( & wdt - > wdd ) ;
if ( ret ) {
dev_err ( & adev - > dev , " watchdog_register_device() failed: %d \n " ,
ret ) ;
goto err_register ;
2010-08-04 10:14:14 +04:00
}
2012-03-12 08:22:16 +04:00
amba_set_drvdata ( adev , wdt ) ;
2010-08-04 10:14:14 +04:00
dev_info ( & adev - > dev , " registration successful \n " ) ;
return 0 ;
2012-03-12 08:22:16 +04:00
err_register :
2010-08-04 10:14:14 +04:00
clk_put ( wdt - > clk ) ;
err :
dev_err ( & adev - > dev , " Probe Failed!!! \n " ) ;
return ret ;
}
static int __devexit sp805_wdt_remove ( struct amba_device * adev )
{
2012-03-12 08:22:16 +04:00
struct sp805_wdt * wdt = amba_get_drvdata ( adev ) ;
watchdog_unregister_device ( & wdt - > wdd ) ;
amba_set_drvdata ( adev , NULL ) ;
watchdog_set_drvdata ( & wdt - > wdd , NULL ) ;
2010-08-04 10:14:14 +04:00
clk_put ( wdt - > clk ) ;
return 0 ;
}
2012-02-24 13:42:37 +04:00
# ifdef CONFIG_PM
static int sp805_wdt_suspend ( struct device * dev )
{
2012-03-12 08:22:16 +04:00
struct sp805_wdt * wdt = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & wdt - > wdd ) )
return wdt_disable ( & wdt - > wdd ) ;
2012-02-24 13:42:37 +04:00
return 0 ;
}
static int sp805_wdt_resume ( struct device * dev )
{
2012-03-12 08:22:16 +04:00
struct sp805_wdt * wdt = dev_get_drvdata ( dev ) ;
2012-02-24 13:42:37 +04:00
2012-03-12 08:22:16 +04:00
if ( watchdog_active ( & wdt - > wdd ) )
return wdt_enable ( & wdt - > wdd ) ;
2012-02-24 13:42:37 +04:00
2012-03-12 08:22:16 +04:00
return 0 ;
2012-02-24 13:42:37 +04:00
}
# endif /* CONFIG_PM */
static SIMPLE_DEV_PM_OPS ( sp805_wdt_dev_pm_ops , sp805_wdt_suspend ,
sp805_wdt_resume ) ;
2011-12-19 20:22:36 +04:00
static struct amba_id sp805_wdt_ids [ ] = {
2010-08-04 10:14:14 +04:00
{
. id = 0x00141805 ,
. mask = 0x00ffffff ,
} ,
{ 0 , 0 } ,
} ;
2011-10-05 18:15:23 +04:00
MODULE_DEVICE_TABLE ( amba , sp805_wdt_ids ) ;
2010-08-04 10:14:14 +04:00
static struct amba_driver sp805_wdt_driver = {
. drv = {
. name = MODULE_NAME ,
2012-02-24 13:42:37 +04:00
. pm = & sp805_wdt_dev_pm_ops ,
2010-08-04 10:14:14 +04:00
} ,
. id_table = sp805_wdt_ids ,
. probe = sp805_wdt_probe ,
. remove = __devexit_p ( sp805_wdt_remove ) ,
} ;
2012-03-15 13:40:38 +04:00
module_amba_driver ( sp805_wdt_driver ) ;
2010-08-04 10:14:14 +04:00
MODULE_AUTHOR ( " Viresh Kumar <viresh.kumar@st.com> " ) ;
MODULE_DESCRIPTION ( " ARM SP805 Watchdog Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;