2018-03-16 18:14:11 +03:00
// SPDX-License-Identifier: GPL-2.0+
2006-03-14 12:11:04 +03:00
/*
* Watchdog driver for Atmel AT91RM9200 ( Thunder )
*
* Copyright ( C ) 2003 SAN People ( Pty ) Ltd
*
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2007-10-19 10:40:25 +04:00
# include <linux/bitops.h>
2015-03-12 15:07:28 +03:00
# include <linux/delay.h>
2006-03-14 12:11:04 +03:00
# include <linux/errno.h>
# include <linux/fs.h>
# include <linux/init.h>
2009-01-23 12:06:07 +03:00
# include <linux/io.h>
2006-03-14 12:11:04 +03:00
# include <linux/kernel.h>
2015-03-12 15:07:27 +03:00
# include <linux/mfd/syscon.h>
# include <linux/mfd/syscon/atmel-st.h>
2006-03-14 12:11:04 +03:00
# 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>
2015-03-12 15:07:28 +03:00
# include <linux/reboot.h>
2015-03-12 15:07:27 +03:00
# include <linux/regmap.h>
2006-03-14 12:11:04 +03:00
# include <linux/types.h>
# include <linux/watchdog.h>
2008-05-19 17:05:19 +04:00
# include <linux/uaccess.h>
2013-02-15 02:02:29 +04:00
# include <linux/of.h>
# include <linux/of_device.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 ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2015-03-12 15:07:27 +03:00
static struct regmap * regmap_st ;
2006-03-14 12:11:04 +03:00
module_param ( wdt_time , int , 0 ) ;
2008-05-19 17:05:19 +04:00
MODULE_PARM_DESC ( wdt_time , " Watchdog time in seconds. (default= "
__MODULE_STRING ( WDT_DEFAULT_TIME ) " ) " ) ;
2006-03-14 12:11:04 +03:00
2006-05-21 17:32:59 +04:00
# ifdef CONFIG_WATCHDOG_NOWAYOUT
2012-03-05 19:51:11 +04:00
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:05:19 +04:00
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 ;
/* ......................................................................... */
2015-03-12 15:07:28 +03:00
static int at91rm9200_restart ( struct notifier_block * this ,
unsigned long mode , void * cmd )
{
/*
* Perform a hardware reset with the use of the Watchdog timer .
*/
regmap_write ( regmap_st , AT91_ST_WDMR ,
AT91_ST_RSTEN | AT91_ST_EXTEN | 1 ) ;
regmap_write ( regmap_st , AT91_ST_CR , AT91_ST_WDRST ) ;
mdelay ( 2000 ) ;
pr_emerg ( " Unable to restart system \n " ) ;
return NOTIFY_DONE ;
}
static struct notifier_block at91rm9200_restart_nb = {
. notifier_call = at91rm9200_restart ,
. priority = 192 ,
} ;
2006-03-14 12:11:04 +03:00
/*
* Disable the watchdog .
*/
2008-05-19 17:05:19 +04:00
static inline void at91_wdt_stop ( void )
2006-03-14 12:11:04 +03:00
{
2015-03-12 15:07:27 +03:00
regmap_write ( regmap_st , AT91_ST_WDMR , AT91_ST_EXTEN ) ;
2006-03-14 12:11:04 +03:00
}
/*
* Enable and reset the watchdog .
*/
2008-05-19 17:05:19 +04:00
static inline void at91_wdt_start ( void )
2006-03-14 12:11:04 +03:00
{
2015-03-12 15:07:27 +03:00
regmap_write ( regmap_st , AT91_ST_WDMR , AT91_ST_EXTEN | AT91_ST_RSTEN |
2008-05-19 17:05:19 +04:00
( ( ( 65536 * wdt_time ) > > 8 ) & AT91_ST_WDV ) ) ;
2015-03-12 15:07:27 +03:00
regmap_write ( regmap_st , AT91_ST_CR , AT91_ST_WDRST ) ;
2006-03-14 12:11:04 +03:00
}
/*
* Reload the watchdog timer . ( ie , pat the watchdog )
*/
2008-05-19 17:05:19 +04:00
static inline void at91_wdt_reload ( void )
2006-03-14 12:11:04 +03:00
{
2015-03-12 15:07:27 +03:00
regmap_write ( regmap_st , AT91_ST_CR , AT91_ST_WDRST ) ;
2006-03-14 12:11:04 +03:00
}
/* ......................................................................... */
/*
* 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 )
{
2008-05-19 17:05:19 +04:00
/* Disable the watchdog when file is closed */
2006-03-14 12:11:04 +03:00
if ( ! nowayout )
2008-05-19 17:05:19 +04:00
at91_wdt_stop ( ) ;
2006-03-14 12:11:04 +03:00
clear_bit ( 0 , & at91wdt_busy ) ;
return 0 ;
}
/*
* Change the watchdog time interval .
*/
static int at91_wdt_settimeout ( int new_time )
{
/*
2009-02-11 23:23:10 +03:00
* All counting occurs at SLOW_CLOCK / 128 = 256 Hz
2006-03-14 12:11:04 +03:00
*
* Since WDV is a 16 - bit counter , the maximum period is
2009-02-11 23:23:10 +03:00
* 65536 / 256 = 256 seconds .
2006-03-14 12:11:04 +03:00
*/
if ( ( new_time < = 0 ) | | ( new_time > WDT_MAX_TIME ) )
return - EINVAL ;
2008-05-19 17:05:19 +04:00
/* Set new watchdog time. It will be used when
at91_wdt_start ( ) is called . */
2006-03-14 12:11:04 +03:00
wdt_time = new_time ;
return 0 ;
}
2009-12-26 21:55:22 +03:00
static const struct watchdog_info at91_wdt_info = {
2006-03-14 12:11:04 +03:00
. identity = " at91 watchdog " ,
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
} ;
/*
* Handle commands from user - space .
*/
2008-08-08 19:57:45 +04:00
static long at91_wdt_ioctl ( struct file * file ,
2008-05-19 17:05:19 +04:00
unsigned int cmd , unsigned long arg )
2006-03-14 12:11:04 +03:00
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int new_value ;
2008-05-19 17:05:19 +04:00
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & at91_wdt_info ,
sizeof ( at91_wdt_info ) ) ? - EFAULT : 0 ;
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 )
2006-03-14 12:11:04 +03:00
at91_wdt_start ( ) ;
2008-05-19 17:05:19 +04:00
return 0 ;
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
at91_wdt_reload ( ) ; /* pat the watchdog */
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_value , p ) )
return - EFAULT ;
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 ) ;
2008-05-19 17:05:19 +04:00
default :
return - ENOTTY ;
2006-03-14 12:11:04 +03:00
}
}
/*
* Pat the watchdog whenever device is written to .
*/
2008-05-19 17:05:19 +04:00
static ssize_t at91_wdt_write ( struct file * file , const char * data ,
size_t len , loff_t * ppos )
2006-03-14 12:11:04 +03:00
{
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 ,
2008-05-19 17:05:19 +04:00
. unlocked_ioctl = at91_wdt_ioctl ,
2006-03-14 12:11:04 +03:00
. 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 ,
} ;
2012-11-19 22:21:41 +04:00
static int at91wdt_probe ( struct platform_device * pdev )
2006-03-14 12:11:04 +03:00
{
2015-03-12 15:07:27 +03:00
struct device * dev = & pdev - > dev ;
struct device * parent ;
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
2015-03-12 15:07:27 +03:00
parent = dev - > parent ;
if ( ! parent ) {
dev_err ( dev , " no parent \n " ) ;
return - ENODEV ;
}
regmap_st = syscon_node_to_regmap ( parent - > of_node ) ;
2015-08-17 19:19:03 +03:00
if ( IS_ERR ( regmap_st ) )
2015-03-12 15:07:27 +03:00
return - ENODEV ;
2006-03-14 12:11:04 +03:00
res = misc_register ( & at91wdt_miscdev ) ;
if ( res )
return res ;
2015-03-12 15:07:28 +03:00
res = register_restart_handler ( & at91rm9200_restart_nb ) ;
if ( res )
dev_warn ( dev , " failed to register restart handler \n " ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " AT91 Watchdog Timer enabled (%d seconds%s) \n " ,
wdt_time , nowayout ? " , nowayout " : " " ) ;
2006-03-14 12:11:04 +03:00
return 0 ;
}
2012-11-19 22:26:24 +04:00
static int at91wdt_remove ( struct platform_device * pdev )
2006-05-21 17:32:59 +04:00
{
2015-03-12 15:07:28 +03:00
struct device * dev = & pdev - > dev ;
2006-05-21 17:32:59 +04:00
int res ;
2015-03-12 15:07:28 +03:00
res = unregister_restart_handler ( & at91rm9200_restart_nb ) ;
if ( res )
dev_warn ( dev , " failed to unregister restart handler \n " ) ;
2015-07-31 01:59:57 +03:00
misc_deregister ( & at91wdt_miscdev ) ;
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 ( ) ;
2008-08-19 10:01:14 +04:00
return 0 ;
2006-05-21 17:32:59 +04:00
}
# else
# define at91wdt_suspend NULL
# define at91wdt_resume NULL
# endif
2013-02-15 02:02:29 +04:00
static const struct of_device_id at91_wdt_dt_ids [ ] = {
{ . compatible = " atmel,at91rm9200-wdt " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , at91_wdt_dt_ids ) ;
2006-05-21 17:32:59 +04:00
static struct platform_driver at91wdt_driver = {
. probe = at91wdt_probe ,
2012-11-19 22:21:12 +04:00
. remove = at91wdt_remove ,
2006-05-21 17:32:59 +04:00
. shutdown = at91wdt_shutdown ,
. suspend = at91wdt_suspend ,
. resume = at91wdt_resume ,
. driver = {
2015-03-12 15:07:27 +03:00
. name = " atmel_st_watchdog " ,
2013-09-30 08:42:51 +04:00
. of_match_table = at91_wdt_dt_ids ,
2006-05-21 17:32:59 +04:00
} ,
} ;
static int __init at91_wdt_init ( void )
{
2008-05-19 17:05:19 +04:00
/* Check that the heartbeat value is within range;
if not reset to the default */
2006-05-21 17:32:59 +04:00
if ( at91_wdt_settimeout ( wdt_time ) ) {
at91_wdt_settimeout ( WDT_DEFAULT_TIME ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " wdt_time value must be 1 <= wdt_time <= 256, using %d \n " ,
wdt_time ) ;
2006-05-21 17:32:59 +04:00
}
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 " ) ;
2015-03-12 15:07:27 +03:00
MODULE_ALIAS ( " platform:atmel_st_watchdog " ) ;