2008-02-25 15:11:31 +03:00
/*
* IDT Interprise 79 RC32434 watchdog driver
*
* Copyright ( C ) 2006 , Ondrej Zajicek < santiago @ crfreenet . org >
* Copyright ( C ) 2008 , Florian Fainelli < florian @ openwrt . org >
*
* based on
* SoftDog 0.05 : A Software Watchdog Device
*
2008-10-27 18:17:56 +03:00
* ( c ) Copyright 1996 Alan Cox < alan @ lxorguk . ukuu . org . uk > ,
* All Rights Reserved .
2008-02-25 15:11:31 +03: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 .
*
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/fs.h>
# include <linux/mm.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/reboot.h>
# include <linux/smp_lock.h>
# include <linux/init.h>
# include <linux/platform_device.h>
# include <linux/uaccess.h>
# include <asm/bootinfo.h>
# include <asm/time.h>
# include <asm/mach-rc32434/integ.h>
# define MAX_TIMEOUT 20
# define RC32434_WDT_INTERVAL (15 * HZ)
# define VERSION "0.2"
static struct {
struct completion stop ;
int running ;
struct timer_list timer ;
int queue ;
int default_ticks ;
unsigned long inuse ;
} rc32434_wdt_device ;
static struct integ __iomem * wdt_reg ;
static int ticks = 100 * HZ ;
static int expect_close ;
static int timeout ;
static int nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , int , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static void rc32434_wdt_start ( void )
{
u32 val ;
if ( ! rc32434_wdt_device . inuse ) {
writel ( 0 , & wdt_reg - > wtcount ) ;
val = RC32434_ERR_WRE ;
writel ( readl ( & wdt_reg - > errcs ) | val , & wdt_reg - > errcs ) ;
val = RC32434_WTC_EN ;
writel ( readl ( & wdt_reg - > wtc ) | val , & wdt_reg - > wtc ) ;
}
rc32434_wdt_device . running + + ;
}
static void rc32434_wdt_stop ( void )
{
u32 val ;
if ( rc32434_wdt_device . running ) {
val = ~ RC32434_WTC_EN ;
writel ( readl ( & wdt_reg - > wtc ) & val , & wdt_reg - > wtc ) ;
val = ~ RC32434_ERR_WRE ;
writel ( readl ( & wdt_reg - > errcs ) & val , & wdt_reg - > errcs ) ;
rc32434_wdt_device . running = 0 ;
}
}
static void rc32434_wdt_set ( int new_timeout )
{
u32 cmp = new_timeout * HZ ;
u32 state , val ;
timeout = new_timeout ;
/*
* store and disable WTC
*/
state = ( u32 ) ( readl ( & wdt_reg - > wtc ) & RC32434_WTC_EN ) ;
val = ~ RC32434_WTC_EN ;
writel ( readl ( & wdt_reg - > wtc ) & val , & wdt_reg - > wtc ) ;
writel ( 0 , & wdt_reg - > wtcount ) ;
writel ( cmp , & wdt_reg - > wtcompare ) ;
/*
* restore WTC
*/
writel ( readl ( & wdt_reg - > wtc ) | state , & wdt_reg ) ;
}
static void rc32434_wdt_reset ( void )
{
ticks = rc32434_wdt_device . default_ticks ;
}
static void rc32434_wdt_update ( unsigned long unused )
{
if ( rc32434_wdt_device . running )
ticks - - ;
writel ( 0 , & wdt_reg - > wtcount ) ;
if ( rc32434_wdt_device . queue & & ticks )
mod_timer ( & rc32434_wdt_device . timer ,
jiffies + RC32434_WDT_INTERVAL ) ;
else
complete ( & rc32434_wdt_device . stop ) ;
}
static int rc32434_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 0 , & rc32434_wdt_device . inuse ) )
return - EBUSY ;
if ( nowayout )
__module_get ( THIS_MODULE ) ;
return nonseekable_open ( inode , file ) ;
}
static int rc32434_wdt_release ( struct inode * inode , struct file * file )
{
if ( expect_close & & nowayout = = 0 ) {
rc32434_wdt_stop ( ) ;
printk ( KERN_INFO KBUILD_MODNAME " : disabling watchdog timer \n " ) ;
module_put ( THIS_MODULE ) ;
} else
printk ( KERN_CRIT KBUILD_MODNAME
" : device closed unexpectedly. WDT will not stop ! \n " ) ;
clear_bit ( 0 , & rc32434_wdt_device . inuse ) ;
return 0 ;
}
static ssize_t rc32434_wdt_write ( struct file * file , const char * data ,
size_t len , loff_t * ppos )
{
if ( len ) {
if ( ! nowayout ) {
size_t i ;
/* In case it was set long ago */
expect_close = 0 ;
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
if ( get_user ( c , data + i ) )
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 1 ;
}
}
rc32434_wdt_update ( 0 ) ;
return len ;
}
return 0 ;
}
2008-09-18 16:26:15 +04:00
static long rc32434_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2008-02-25 15:11:31 +03:00
{
void __user * argp = ( void __user * ) arg ;
int new_timeout ;
unsigned int value ;
static struct watchdog_info ident = {
. options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
. identity = " RC32434_WDT Watchdog " ,
} ;
switch ( cmd ) {
case WDIOC_KEEPALIVE :
rc32434_wdt_reset ( ) ;
break ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
value = readl ( & wdt_reg - > wtcount ) ;
if ( copy_to_user ( argp , & value , sizeof ( int ) ) )
return - EFAULT ;
break ;
case WDIOC_GETSUPPORT :
if ( copy_to_user ( argp , & ident , sizeof ( ident ) ) )
return - EFAULT ;
break ;
case WDIOC_SETOPTIONS :
if ( copy_from_user ( & value , argp , sizeof ( int ) ) )
return - EFAULT ;
switch ( value ) {
case WDIOS_ENABLECARD :
rc32434_wdt_start ( ) ;
break ;
case WDIOS_DISABLECARD :
rc32434_wdt_stop ( ) ;
default :
return - EINVAL ;
}
break ;
case WDIOC_SETTIMEOUT :
if ( copy_from_user ( & new_timeout , argp , sizeof ( int ) ) )
return - EFAULT ;
if ( new_timeout < 1 )
return - EINVAL ;
if ( new_timeout > MAX_TIMEOUT )
return - EINVAL ;
rc32434_wdt_set ( new_timeout ) ;
case WDIOC_GETTIMEOUT :
return copy_to_user ( argp , & timeout , sizeof ( int ) ) ;
default :
return - ENOTTY ;
}
return 0 ;
}
static struct file_operations rc32434_wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = rc32434_wdt_write ,
2008-09-18 16:26:15 +04:00
. unlocked_ioctl = rc32434_wdt_ioctl ,
2008-02-25 15:11:31 +03:00
. open = rc32434_wdt_open ,
. release = rc32434_wdt_release ,
} ;
static struct miscdevice rc32434_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & rc32434_wdt_fops ,
} ;
static char banner [ ] = KERN_INFO KBUILD_MODNAME
" : Watchdog Timer version " VERSION " , timer margin: %d sec \n " ;
static int rc32434_wdt_probe ( struct platform_device * pdev )
{
int ret ;
struct resource * r ;
r = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " rb500_wdt_res " ) ;
if ( ! r ) {
printk ( KERN_ERR KBUILD_MODNAME
" failed to retrieve resources \n " ) ;
return - ENODEV ;
}
wdt_reg = ioremap_nocache ( r - > start , r - > end - r - > start ) ;
if ( ! wdt_reg ) {
printk ( KERN_ERR KBUILD_MODNAME
" failed to remap I/O resources \n " ) ;
return - ENXIO ;
}
ret = misc_register ( & rc32434_wdt_miscdev ) ;
if ( ret < 0 ) {
printk ( KERN_ERR KBUILD_MODNAME
" failed to register watchdog device \n " ) ;
goto unmap ;
}
init_completion ( & rc32434_wdt_device . stop ) ;
rc32434_wdt_device . queue = 0 ;
clear_bit ( 0 , & rc32434_wdt_device . inuse ) ;
setup_timer ( & rc32434_wdt_device . timer , rc32434_wdt_update , 0L ) ;
rc32434_wdt_device . default_ticks = ticks ;
rc32434_wdt_start ( ) ;
printk ( banner , timeout ) ;
return 0 ;
unmap :
iounmap ( wdt_reg ) ;
return ret ;
}
static int rc32434_wdt_remove ( struct platform_device * pdev )
{
if ( rc32434_wdt_device . queue ) {
rc32434_wdt_device . queue = 0 ;
wait_for_completion ( & rc32434_wdt_device . stop ) ;
}
misc_deregister ( & rc32434_wdt_miscdev ) ;
iounmap ( wdt_reg ) ;
return 0 ;
}
static struct platform_driver rc32434_wdt = {
. probe = rc32434_wdt_probe ,
. remove = rc32434_wdt_remove ,
. driver = {
. name = " rc32434_wdt " ,
}
} ;
static int __init rc32434_wdt_init ( void )
{
return platform_driver_register ( & rc32434_wdt ) ;
}
static void __exit rc32434_wdt_exit ( void )
{
platform_driver_unregister ( & rc32434_wdt ) ;
}
module_init ( rc32434_wdt_init ) ;
module_exit ( rc32434_wdt_exit ) ;
MODULE_AUTHOR ( " Ondrej Zajicek <santiago@crfreenet.org>, "
" Florian Fainelli <florian@openwrt.org> " ) ;
MODULE_DESCRIPTION ( " Driver for the IDT RC32434 SoC watchdog " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;