2006-03-14 12:11:04 +03:00
/*
* Watchdog driver for Atmel AT91RM9200 ( Thunder )
*
* Copyright ( C ) 2003 SAN People ( Pty ) Ltd
*
* 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/errno.h>
# include <linux/fs.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
2006-05-21 17:32:59 +04:00
# include <linux/platform_device.h>
2006-03-14 12:11:04 +03:00
# include <linux/types.h>
# include <linux/watchdog.h>
# include <asm/bitops.h>
# include <asm/uaccess.h>
2006-11-30 19:16:43 +03:00
# include <asm/arch/at91_st.h>
2006-03-14 12:11:04 +03:00
2006-05-21 17:32:59 +04:00
# define WDT_DEFAULT_TIME 5 /* seconds */
# define WDT_MAX_TIME 256 /* seconds */
2006-03-14 12:11:04 +03:00
static int wdt_time = WDT_DEFAULT_TIME ;
static int nowayout = WATCHDOG_NOWAYOUT ;
module_param ( wdt_time , int , 0 ) ;
MODULE_PARM_DESC ( wdt_time , " Watchdog time in seconds. (default= " __MODULE_STRING ( WDT_DEFAULT_TIME ) " ) " ) ;
2006-05-21 17:32:59 +04:00
# ifdef CONFIG_WATCHDOG_NOWAYOUT
2006-03-14 12:11:04 +03:00
module_param ( nowayout , int , 0 ) ;
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2006-05-21 17:32:59 +04:00
# endif
2006-03-14 12:11:04 +03:00
static unsigned long at91wdt_busy ;
/* ......................................................................... */
/*
* Disable the watchdog .
*/
static void inline at91_wdt_stop ( void )
{
at91_sys_write ( AT91_ST_WDMR , AT91_ST_EXTEN ) ;
}
/*
* Enable and reset the watchdog .
*/
static void inline at91_wdt_start ( void )
{
at91_sys_write ( AT91_ST_WDMR , AT91_ST_EXTEN | AT91_ST_RSTEN | ( ( ( 65536 * wdt_time ) > > 8 ) & AT91_ST_WDV ) ) ;
at91_sys_write ( AT91_ST_CR , AT91_ST_WDRST ) ;
}
/*
* Reload the watchdog timer . ( ie , pat the watchdog )
*/
static void inline at91_wdt_reload ( void )
{
at91_sys_write ( AT91_ST_CR , AT91_ST_WDRST ) ;
}
/* ......................................................................... */
/*
* Watchdog device is opened , and watchdog starts running .
*/
static int at91_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 0 , & at91wdt_busy ) )
return - EBUSY ;
at91_wdt_start ( ) ;
return nonseekable_open ( inode , file ) ;
}
/*
* Close the watchdog device .
* If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also
* disabled .
*/
static int at91_wdt_close ( struct inode * inode , struct file * file )
{
if ( ! nowayout )
at91_wdt_stop ( ) ; /* Disable the watchdog when file is closed */
clear_bit ( 0 , & at91wdt_busy ) ;
return 0 ;
}
/*
* Change the watchdog time interval .
*/
static int at91_wdt_settimeout ( int new_time )
{
/*
* All counting occurs at SLOW_CLOCK / 128 = 0.256 Hz
*
* Since WDV is a 16 - bit counter , the maximum period is
* 65536 / 0.256 = 256 seconds .
*/
if ( ( new_time < = 0 ) | | ( new_time > WDT_MAX_TIME ) )
return - EINVAL ;
/* Set new watchdog time. It will be used when at91_wdt_start() is called. */
wdt_time = new_time ;
return 0 ;
}
static struct watchdog_info at91_wdt_info = {
. identity = " at91 watchdog " ,
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
} ;
/*
* Handle commands from user - space .
*/
static int at91_wdt_ioctl ( struct inode * inode , struct file * file ,
unsigned int cmd , unsigned long arg )
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int new_value ;
switch ( cmd ) {
case WDIOC_KEEPALIVE :
at91_wdt_reload ( ) ; /* pat the watchdog */
return 0 ;
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & at91_wdt_info , sizeof ( at91_wdt_info ) ) ? - EFAULT : 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_value , p ) )
return - EFAULT ;
2006-05-21 17:32:59 +04:00
2006-03-14 12:11:04 +03:00
if ( at91_wdt_settimeout ( new_value ) )
return - EINVAL ;
/* Enable new time value */
at91_wdt_start ( ) ;
/* Return current value */
return put_user ( wdt_time , p ) ;
case WDIOC_GETTIMEOUT :
return put_user ( wdt_time , p ) ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_SETOPTIONS :
if ( get_user ( new_value , p ) )
return - EFAULT ;
if ( new_value & WDIOS_DISABLECARD )
at91_wdt_stop ( ) ;
if ( new_value & WDIOS_ENABLECARD )
at91_wdt_start ( ) ;
return 0 ;
default :
2006-09-09 19:34:31 +04:00
return - ENOTTY ;
2006-03-14 12:11:04 +03:00
}
}
/*
* Pat the watchdog whenever device is written to .
*/
static ssize_t at91_wdt_write ( struct file * file , const char * data , size_t len , loff_t * ppos )
{
at91_wdt_reload ( ) ; /* pat the watchdog */
return len ;
}
/* ......................................................................... */
2006-07-03 11:24:21 +04:00
static const struct file_operations at91wdt_fops = {
2006-03-14 12:11:04 +03:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. ioctl = at91_wdt_ioctl ,
. open = at91_wdt_open ,
. release = at91_wdt_close ,
. write = at91_wdt_write ,
} ;
static struct miscdevice at91wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & at91wdt_fops ,
} ;
2006-05-21 17:32:59 +04:00
static int __init at91wdt_probe ( struct platform_device * pdev )
2006-03-14 12:11:04 +03:00
{
int res ;
2006-12-04 16:56:18 +03:00
if ( at91wdt_miscdev . parent )
2006-05-21 17:32:59 +04:00
return - EBUSY ;
2006-12-04 16:56:18 +03:00
at91wdt_miscdev . parent = & pdev - > dev ;
2006-03-14 12:11:04 +03:00
res = misc_register ( & at91wdt_miscdev ) ;
if ( res )
return res ;
2006-05-21 17:32:59 +04:00
printk ( " AT91 Watchdog Timer enabled (%d seconds%s) \n " , wdt_time , nowayout ? " , nowayout " : " " ) ;
2006-03-14 12:11:04 +03:00
return 0 ;
}
2006-05-21 17:32:59 +04:00
static int __exit at91wdt_remove ( struct platform_device * pdev )
{
int res ;
res = misc_deregister ( & at91wdt_miscdev ) ;
if ( ! res )
2006-12-04 16:56:18 +03:00
at91wdt_miscdev . parent = NULL ;
2006-05-21 17:32:59 +04:00
return res ;
}
static void at91wdt_shutdown ( struct platform_device * pdev )
{
at91_wdt_stop ( ) ;
}
# ifdef CONFIG_PM
static int at91wdt_suspend ( struct platform_device * pdev , pm_message_t message )
{
at91_wdt_stop ( ) ;
return 0 ;
}
static int at91wdt_resume ( struct platform_device * pdev )
{
if ( at91wdt_busy )
at91_wdt_start ( ) ;
return 0 ;
}
# else
# define at91wdt_suspend NULL
# define at91wdt_resume NULL
# endif
static struct platform_driver at91wdt_driver = {
. probe = at91wdt_probe ,
. remove = __exit_p ( at91wdt_remove ) ,
. shutdown = at91wdt_shutdown ,
. suspend = at91wdt_suspend ,
. resume = at91wdt_resume ,
. driver = {
. name = " at91_wdt " ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init at91_wdt_init ( void )
{
/* Check that the heartbeat value is within range; if not reset to the default */
if ( at91_wdt_settimeout ( wdt_time ) ) {
at91_wdt_settimeout ( WDT_DEFAULT_TIME ) ;
pr_info ( " at91_wdt: wdt_time value must be 1 <= wdt_time <= 256, using %d \n " , wdt_time ) ;
}
return platform_driver_register ( & at91wdt_driver ) ;
}
2006-03-14 12:11:04 +03:00
static void __exit at91_wdt_exit ( void )
{
2006-05-21 17:32:59 +04:00
platform_driver_unregister ( & at91wdt_driver ) ;
2006-03-14 12:11:04 +03:00
}
module_init ( at91_wdt_init ) ;
module_exit ( at91_wdt_exit ) ;
MODULE_AUTHOR ( " Andrew Victor " ) ;
MODULE_DESCRIPTION ( " Watchdog driver for Atmel AT91RM9200 " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;