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/fs.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/ioport.h>
# include <linux/kernel.h>
# include <linux/math64.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/types.h>
# include <linux/uaccess.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
*
* lock : spin lock protecting dev structure and io access
* base : base address of wdt
* clk : clock structure of wdt
* dev : amba device structure of wdt
* status : current status of wdt
* load_val : load value to be set for current timeout
* timeout : current programmed timeout
*/
struct sp805_wdt {
spinlock_t lock ;
void __iomem * base ;
struct clk * clk ;
struct amba_device * adev ;
unsigned long status ;
# define WDT_BUSY 0
# define WDT_CAN_BE_CLOSED 1
unsigned int load_val ;
unsigned int timeout ;
} ;
/* local variables */
static struct sp805_wdt * wdt ;
static int nowayout = WATCHDOG_NOWAYOUT ;
/* This routine finds load value that will reset system in required timout */
static void wdt_setload ( unsigned int timeout )
{
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
* interrupt already occured then it resets the system . This is why
* 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 ) ;
}
/* returns number of seconds left for reset to occur */
static u32 wdt_timeleft ( void )
{
u64 load , rate ;
rate = clk_get_rate ( wdt - > clk ) ;
spin_lock ( & wdt - > lock ) ;
load = readl ( wdt - > base + WDTVALUE ) ;
/*If the interrupt is inactive then time left is WDTValue + WDTLoad. */
if ( ! ( readl ( wdt - > base + WDTRIS ) & INT_MASK ) )
load + = wdt - > load_val + 1 ;
spin_unlock ( & wdt - > lock ) ;
return div_u64 ( load , rate ) ;
}
/* enables watchdog timers reset */
static void wdt_enable ( void )
{
spin_lock ( & wdt - > lock ) ;
writel ( UNLOCK , wdt - > base + WDTLOCK ) ;
writel ( wdt - > load_val , wdt - > base + WDTLOAD ) ;
writel ( INT_MASK , wdt - > base + WDTINTCLR ) ;
writel ( INT_ENABLE | RESET_ENABLE , wdt - > base + WDTCONTROL ) ;
writel ( LOCK , wdt - > base + WDTLOCK ) ;
spin_unlock ( & wdt - > lock ) ;
}
/* disables watchdog timers reset */
static void wdt_disable ( void )
{
spin_lock ( & wdt - > lock ) ;
writel ( UNLOCK , wdt - > base + WDTLOCK ) ;
writel ( 0 , wdt - > base + WDTCONTROL ) ;
writel ( 0 , wdt - > base + WDTLOAD ) ;
writel ( LOCK , wdt - > base + WDTLOCK ) ;
spin_unlock ( & wdt - > lock ) ;
}
static ssize_t sp805_wdt_write ( struct file * file , const char * data ,
size_t len , loff_t * ppos )
{
if ( len ) {
if ( ! nowayout ) {
size_t i ;
clear_bit ( WDT_CAN_BE_CLOSED , & wdt - > status ) ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
/* Check for Magic Close character */
if ( c = = ' V ' ) {
set_bit ( WDT_CAN_BE_CLOSED ,
& wdt - > status ) ;
break ;
}
}
}
wdt_enable ( ) ;
}
return len ;
}
static const struct watchdog_info ident = {
. options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
. identity = MODULE_NAME ,
} ;
static long sp805_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
int ret = - ENOTTY ;
unsigned int timeout ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
ret = copy_to_user ( ( struct watchdog_info * ) arg , & ident ,
sizeof ( ident ) ) ? - EFAULT : 0 ;
break ;
case WDIOC_GETSTATUS :
ret = put_user ( 0 , ( int * ) arg ) ;
break ;
case WDIOC_KEEPALIVE :
wdt_enable ( ) ;
ret = 0 ;
break ;
case WDIOC_SETTIMEOUT :
ret = get_user ( timeout , ( unsigned int * ) arg ) ;
if ( ret )
break ;
wdt_setload ( timeout ) ;
wdt_enable ( ) ;
/* Fall through */
case WDIOC_GETTIMEOUT :
ret = put_user ( wdt - > timeout , ( unsigned int * ) arg ) ;
break ;
case WDIOC_GETTIMELEFT :
ret = put_user ( wdt_timeleft ( ) , ( unsigned int * ) arg ) ;
break ;
}
return ret ;
}
static int sp805_wdt_open ( struct inode * inode , struct file * file )
{
int ret = 0 ;
if ( test_and_set_bit ( WDT_BUSY , & wdt - > status ) )
return - EBUSY ;
ret = clk_enable ( wdt - > clk ) ;
if ( ret ) {
dev_err ( & wdt - > adev - > dev , " clock enable fail " ) ;
goto err ;
}
wdt_enable ( ) ;
/* can not be closed, once enabled */
clear_bit ( WDT_CAN_BE_CLOSED , & wdt - > status ) ;
return nonseekable_open ( inode , file ) ;
err :
clear_bit ( WDT_BUSY , & wdt - > status ) ;
return ret ;
}
static int sp805_wdt_release ( struct inode * inode , struct file * file )
{
if ( ! test_bit ( WDT_CAN_BE_CLOSED , & wdt - > status ) ) {
clear_bit ( WDT_BUSY , & wdt - > status ) ;
dev_warn ( & wdt - > adev - > dev , " Device closed unexpectedly \n " ) ;
return 0 ;
}
wdt_disable ( ) ;
clk_disable ( wdt - > clk ) ;
clear_bit ( WDT_BUSY , & wdt - > status ) ;
return 0 ;
}
static const struct file_operations sp805_wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = sp805_wdt_write ,
. unlocked_ioctl = sp805_wdt_ioctl ,
. open = sp805_wdt_open ,
. release = sp805_wdt_release ,
} ;
static struct miscdevice sp805_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & sp805_wdt_fops ,
} ;
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
{
int ret = 0 ;
if ( ! request_mem_region ( adev - > res . start , resource_size ( & adev - > res ) ,
" sp805_wdt " ) ) {
dev_warn ( & adev - > dev , " Failed to get memory region resource \n " ) ;
ret = - ENOENT ;
goto err ;
}
wdt = kzalloc ( sizeof ( * wdt ) , GFP_KERNEL ) ;
if ( ! wdt ) {
dev_warn ( & adev - > dev , " Kzalloc failed \n " ) ;
ret = - ENOMEM ;
goto err_kzalloc ;
}
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 ) ;
goto err_clk_get ;
}
wdt - > base = ioremap ( adev - > res . start , resource_size ( & adev - > res ) ) ;
if ( ! wdt - > base ) {
ret = - ENOMEM ;
dev_warn ( & adev - > dev , " ioremap fail \n " ) ;
goto err_ioremap ;
}
wdt - > adev = adev ;
spin_lock_init ( & wdt - > lock ) ;
wdt_setload ( DEFAULT_TIMEOUT ) ;
ret = misc_register ( & sp805_wdt_miscdev ) ;
if ( ret < 0 ) {
dev_warn ( & adev - > dev , " cannot register misc device \n " ) ;
goto err_misc_register ;
}
dev_info ( & adev - > dev , " registration successful \n " ) ;
return 0 ;
err_misc_register :
iounmap ( wdt - > base ) ;
err_ioremap :
clk_put ( wdt - > clk ) ;
err_clk_get :
kfree ( wdt ) ;
wdt = NULL ;
err_kzalloc :
release_mem_region ( adev - > res . start , resource_size ( & adev - > res ) ) ;
err :
dev_err ( & adev - > dev , " Probe Failed!!! \n " ) ;
return ret ;
}
static int __devexit sp805_wdt_remove ( struct amba_device * adev )
{
misc_deregister ( & sp805_wdt_miscdev ) ;
iounmap ( wdt - > base ) ;
clk_put ( wdt - > clk ) ;
kfree ( wdt ) ;
release_mem_region ( adev - > res . start , resource_size ( & adev - > res ) ) ;
return 0 ;
}
static struct amba_id sp805_wdt_ids [ ] __initdata = {
{
. id = 0x00141805 ,
. mask = 0x00ffffff ,
} ,
{ 0 , 0 } ,
} ;
static struct amba_driver sp805_wdt_driver = {
. drv = {
. name = MODULE_NAME ,
} ,
. id_table = sp805_wdt_ids ,
. probe = sp805_wdt_probe ,
. remove = __devexit_p ( sp805_wdt_remove ) ,
} ;
static int __init sp805_wdt_init ( void )
{
return amba_driver_register ( & sp805_wdt_driver ) ;
}
module_init ( sp805_wdt_init ) ;
static void __exit sp805_wdt_exit ( void )
{
amba_driver_unregister ( & sp805_wdt_driver ) ;
}
module_exit ( sp805_wdt_exit ) ;
module_param ( nowayout , int , 0 ) ;
MODULE_PARM_DESC ( nowayout ,
" Set to 1 to keep watchdog running after device release " ) ;
MODULE_AUTHOR ( " Viresh Kumar <viresh.kumar@st.com> " ) ;
MODULE_DESCRIPTION ( " ARM SP805 Watchdog Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;