2008-06-23 19:05:49 +04:00
/*
* Watchdog driver for Atmel AT91SAM9x processors .
*
* Copyright ( C ) 2008 Renaud CERRATO r . cerrato @ til - technologies . fr
*
* 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 .
*/
/*
* The Watchdog Timer Mode Register can be only written to once . If the
* timeout need to be set from Linux , be sure that the bootstrap or the
* bootloader doesn ' t write to this register .
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2008-06-23 19:05:49 +04:00
# include <linux/errno.h>
# include <linux/init.h>
2013-10-04 11:24:12 +04:00
# include <linux/interrupt.h>
2009-02-11 23:23:10 +03:00
# include <linux/io.h>
2008-06-23 19:05:49 +04:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/platform_device.h>
2013-10-04 11:24:12 +04:00
# include <linux/reboot.h>
2008-06-23 19:05:49 +04:00
# include <linux/types.h>
# include <linux/watchdog.h>
# include <linux/jiffies.h>
# include <linux/timer.h>
# include <linux/bitops.h>
# include <linux/uaccess.h>
2012-11-12 12:37:25 +04:00
# include <linux/of.h>
2013-10-04 11:24:12 +04:00
# include <linux/of_irq.h>
2008-06-23 19:05:49 +04:00
2011-07-15 03:52:05 +04:00
# include "at91sam9_wdt.h"
2008-06-23 19:05:49 +04:00
# define DRV_NAME "AT91SAM9 Watchdog"
2013-10-04 11:24:12 +04:00
# define wdt_read(wdt, field) \
__raw_readl ( ( wdt ) - > base + ( field ) )
# define wdt_write(wtd, field, val) \
__raw_writel ( ( val ) , ( wdt ) - > base + ( field ) )
2011-11-01 21:43:31 +04:00
2008-06-23 19:05:49 +04:00
/* AT91SAM9 watchdog runs a 12bit counter @ 256Hz,
* use this to convert a watchdog
* value from / to milliseconds .
*/
2013-10-04 11:24:12 +04:00
# define ticks_to_hz_rounddown(t) ((((t) + 1) * HZ) >> 8)
# define ticks_to_hz_roundup(t) (((((t) + 1) * HZ) + 255) >> 8)
# define ticks_to_secs(t) (((t) + 1) >> 8)
2013-11-03 21:52:42 +04:00
# define secs_to_ticks(s) ((s) ? (((s) << 8) - 1) : 0)
2013-10-04 11:24:12 +04:00
# define WDT_MR_RESET 0x3FFF2FFF
/* Watchdog max counter value in ticks */
# define WDT_COUNTER_MAX_TICKS 0xFFF
/* Watchdog max delta/value in secs */
# define WDT_COUNTER_MAX_SECS ticks_to_secs(WDT_COUNTER_MAX_TICKS)
2008-06-23 19:05:49 +04:00
/* Hardware timeout in seconds */
# define WDT_HW_TIMEOUT 2
/* Timer heartbeat (500ms) */
# define WDT_TIMEOUT (HZ / 2)
/* User land timeout */
# define WDT_HEARTBEAT 15
2013-02-14 12:14:25 +04:00
static int heartbeat ;
2008-06-23 19:05:49 +04:00
module_param ( heartbeat , int , 0 ) ;
MODULE_PARM_DESC ( heartbeat , " Watchdog heartbeats in seconds. "
" (default = " __MODULE_STRING ( WDT_HEARTBEAT ) " ) " ) ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
2008-06-23 19:05:49 +04:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started "
" (default= " __MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2013-10-04 11:24:12 +04:00
# define to_wdt(wdd) container_of(wdd, struct at91wdt, wdd)
struct at91wdt {
struct watchdog_device wdd ;
2011-11-01 21:43:31 +04:00
void __iomem * base ;
2008-06-23 19:05:49 +04:00
unsigned long next_heartbeat ; /* the next_heartbeat for the timer */
struct timer_list timer ; /* The timer that pings the watchdog */
2013-10-04 11:24:12 +04:00
u32 mr ;
u32 mr_mask ;
unsigned long heartbeat ; /* WDT heartbeat in jiffies */
bool nowayout ;
unsigned int irq ;
} ;
2008-06-23 19:05:49 +04:00
/* ......................................................................... */
2013-10-04 11:24:12 +04:00
static irqreturn_t wdt_interrupt ( int irq , void * dev_id )
{
struct at91wdt * wdt = ( struct at91wdt * ) dev_id ;
if ( wdt_read ( wdt , AT91_WDT_SR ) ) {
pr_crit ( " at91sam9 WDT software reset \n " ) ;
emergency_restart ( ) ;
pr_crit ( " Reboot didn't ????? \n " ) ;
}
return IRQ_HANDLED ;
}
2008-06-23 19:05:49 +04:00
/*
* Reload the watchdog timer . ( ie , pat the watchdog )
*/
2013-10-04 11:24:12 +04:00
static inline void at91_wdt_reset ( struct at91wdt * wdt )
2008-06-23 19:05:49 +04:00
{
2013-10-04 11:24:12 +04:00
wdt_write ( wdt , AT91_WDT_CR , AT91_WDT_KEY | AT91_WDT_WDRSTT ) ;
2008-06-23 19:05:49 +04:00
}
/*
* Timer tick
*/
static void at91_ping ( unsigned long data )
{
2013-10-04 11:24:12 +04:00
struct at91wdt * wdt = ( struct at91wdt * ) data ;
if ( time_before ( jiffies , wdt - > next_heartbeat ) | |
! watchdog_active ( & wdt - > wdd ) ) {
at91_wdt_reset ( wdt ) ;
mod_timer ( & wdt - > timer , jiffies + wdt - > heartbeat ) ;
} else {
2012-02-16 03:06:19 +04:00
pr_crit ( " I will reset your machine ! \n " ) ;
2013-10-04 11:24:12 +04:00
}
2013-02-01 11:06:21 +04:00
}
2008-06-23 19:05:49 +04:00
2013-02-01 11:06:21 +04:00
static int at91_wdt_start ( struct watchdog_device * wdd )
{
2013-10-04 11:24:12 +04:00
struct at91wdt * wdt = to_wdt ( wdd ) ;
/* calculate when the next userspace timeout will be */
wdt - > next_heartbeat = jiffies + wdd - > timeout * HZ ;
2013-02-01 11:06:21 +04:00
return 0 ;
2008-06-23 19:05:49 +04:00
}
2013-02-01 11:06:21 +04:00
static int at91_wdt_stop ( struct watchdog_device * wdd )
2008-06-23 19:05:49 +04:00
{
2013-02-01 11:06:21 +04:00
/* The watchdog timer hardware can not be stopped... */
return 0 ;
}
2008-06-23 19:05:49 +04:00
2013-02-01 11:06:21 +04:00
static int at91_wdt_set_timeout ( struct watchdog_device * wdd , unsigned int new_timeout )
{
wdd - > timeout = new_timeout ;
2013-10-04 11:24:12 +04:00
return at91_wdt_start ( wdd ) ;
2008-06-23 19:05:49 +04:00
}
2013-10-04 11:24:12 +04:00
static int at91_wdt_init ( struct platform_device * pdev , struct at91wdt * wdt )
2008-06-23 19:05:49 +04:00
{
2013-10-04 11:24:12 +04:00
u32 tmp ;
u32 delta ;
u32 value ;
int err ;
u32 mask = wdt - > mr_mask ;
unsigned long min_heartbeat = 1 ;
2013-11-03 21:52:44 +04:00
unsigned long max_heartbeat ;
2013-10-04 11:24:12 +04:00
struct device * dev = & pdev - > dev ;
tmp = wdt_read ( wdt , AT91_WDT_MR ) ;
if ( ( tmp & mask ) ! = ( wdt - > mr & mask ) ) {
if ( tmp = = WDT_MR_RESET ) {
wdt_write ( wdt , AT91_WDT_MR , wdt - > mr ) ;
tmp = wdt_read ( wdt , AT91_WDT_MR ) ;
}
}
if ( tmp & AT91_WDT_WDDIS ) {
if ( wdt - > mr & AT91_WDT_WDDIS )
return 0 ;
dev_err ( dev , " watchdog is disabled \n " ) ;
return - EINVAL ;
}
value = tmp & AT91_WDT_WDV ;
delta = ( tmp & AT91_WDT_WDD ) > > 16 ;
if ( delta < value )
min_heartbeat = ticks_to_hz_roundup ( value - delta ) ;
2013-11-03 21:52:44 +04:00
max_heartbeat = ticks_to_hz_rounddown ( value ) ;
if ( ! max_heartbeat ) {
2013-10-04 11:24:12 +04:00
dev_err ( dev ,
" heartbeat is too small for the system to handle it correctly \n " ) ;
return - EINVAL ;
}
2013-11-03 21:52:44 +04:00
/*
* Try to reset the watchdog counter 4 or 2 times more often than
* actually requested , to avoid spurious watchdog reset .
* If this is not possible because of the min_heartbeat value , reset
* it at the min_heartbeat period .
*/
if ( ( max_heartbeat / 4 ) > = min_heartbeat )
wdt - > heartbeat = max_heartbeat / 4 ;
else if ( ( max_heartbeat / 2 ) > = min_heartbeat )
wdt - > heartbeat = max_heartbeat / 2 ;
else
2013-10-04 11:24:12 +04:00
wdt - > heartbeat = min_heartbeat ;
2013-11-03 21:52:44 +04:00
if ( max_heartbeat < min_heartbeat + 4 )
2013-10-04 11:24:12 +04:00
dev_warn ( dev ,
" min heartbeat and max heartbeat might be too close for the system to handle it correctly \n " ) ;
2008-06-23 19:05:49 +04:00
2013-10-04 11:24:12 +04:00
if ( ( tmp & AT91_WDT_WDFIEN ) & & wdt - > irq ) {
err = request_irq ( wdt - > irq , wdt_interrupt ,
2015-03-02 12:18:17 +03:00
IRQF_SHARED | IRQF_IRQPOLL |
IRQF_NO_SUSPEND ,
2013-10-04 11:24:12 +04:00
pdev - > name , wdt ) ;
if ( err )
return err ;
}
if ( ( tmp & wdt - > mr_mask ) ! = ( wdt - > mr & wdt - > mr_mask ) )
dev_warn ( dev ,
" watchdog already configured differently (mr = %x expecting %x) \n " ,
tmp & wdt - > mr_mask , wdt - > mr & wdt - > mr_mask ) ;
setup_timer ( & wdt - > timer , at91_ping , ( unsigned long ) wdt ) ;
2013-11-03 21:52:43 +04:00
/*
* Use min_heartbeat the first time to avoid spurious watchdog reset :
* we don ' t know for how long the watchdog counter is running , and
* - resetting it right now might trigger a watchdog fault reset
* - waiting for heartbeat time might lead to a watchdog timeout
* reset
*/
mod_timer ( & wdt - > timer , jiffies + min_heartbeat ) ;
2013-10-04 11:24:12 +04:00
/* Try to set timeout from device tree first */
if ( watchdog_init_timeout ( & wdt - > wdd , 0 , dev ) )
watchdog_init_timeout ( & wdt - > wdd , heartbeat , dev ) ;
watchdog_set_nowayout ( & wdt - > wdd , wdt - > nowayout ) ;
err = watchdog_register_device ( & wdt - > wdd ) ;
if ( err )
goto out_stop_timer ;
wdt - > next_heartbeat = jiffies + wdt - > wdd . timeout * HZ ;
2008-06-23 19:05:49 +04:00
return 0 ;
2013-10-04 11:24:12 +04:00
out_stop_timer :
del_timer ( & wdt - > timer ) ;
return err ;
2008-06-23 19:05:49 +04:00
}
2013-02-01 11:06:21 +04:00
/* ......................................................................... */
2008-06-23 19:05:49 +04:00
static const struct watchdog_info at91_wdt_info = {
. identity = DRV_NAME ,
2009-05-11 22:33:00 +04:00
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
2008-06-23 19:05:49 +04:00
} ;
2013-02-01 11:06:21 +04:00
static const struct watchdog_ops at91_wdt_ops = {
. owner = THIS_MODULE ,
. start = at91_wdt_start ,
. stop = at91_wdt_stop ,
. set_timeout = at91_wdt_set_timeout ,
2008-06-23 19:05:49 +04:00
} ;
2013-10-04 11:24:12 +04:00
# if defined(CONFIG_OF)
static int of_at91wdt_init ( struct device_node * np , struct at91wdt * wdt )
{
u32 min = 0 ;
u32 max = WDT_COUNTER_MAX_SECS ;
const char * tmp ;
/* Get the interrupts property */
wdt - > irq = irq_of_parse_and_map ( np , 0 ) ;
if ( ! wdt - > irq )
dev_warn ( wdt - > wdd . parent , " failed to get IRQ from DT \n " ) ;
if ( ! of_property_read_u32_index ( np , " atmel,max-heartbeat-sec " , 0 ,
& max ) ) {
if ( ! max | | max > WDT_COUNTER_MAX_SECS )
max = WDT_COUNTER_MAX_SECS ;
if ( ! of_property_read_u32_index ( np , " atmel,min-heartbeat-sec " ,
0 , & min ) ) {
if ( min > = max )
min = max - 1 ;
}
}
min = secs_to_ticks ( min ) ;
max = secs_to_ticks ( max ) ;
wdt - > mr_mask = 0x3FFFFFFF ;
wdt - > mr = 0 ;
if ( ! of_property_read_string ( np , " atmel,watchdog-type " , & tmp ) & &
! strcmp ( tmp , " software " ) ) {
wdt - > mr | = AT91_WDT_WDFIEN ;
wdt - > mr_mask & = ~ AT91_WDT_WDRPROC ;
} else {
wdt - > mr | = AT91_WDT_WDRSTEN ;
}
if ( ! of_property_read_string ( np , " atmel,reset-type " , & tmp ) & &
! strcmp ( tmp , " proc " ) )
wdt - > mr | = AT91_WDT_WDRPROC ;
if ( of_property_read_bool ( np , " atmel,disable " ) ) {
wdt - > mr | = AT91_WDT_WDDIS ;
wdt - > mr_mask & = AT91_WDT_WDDIS ;
}
if ( of_property_read_bool ( np , " atmel,idle-halt " ) )
wdt - > mr | = AT91_WDT_WDIDLEHLT ;
if ( of_property_read_bool ( np , " atmel,dbg-halt " ) )
wdt - > mr | = AT91_WDT_WDDBGHLT ;
wdt - > mr | = max | ( ( max - min ) < < 16 ) ;
return 0 ;
}
# else
static inline int of_at91wdt_init ( struct device_node * np , struct at91wdt * wdt )
{
return 0 ;
}
# endif
2008-06-23 19:05:49 +04:00
static int __init at91wdt_probe ( struct platform_device * pdev )
{
2011-11-01 21:43:31 +04:00
struct resource * r ;
2013-10-04 11:24:12 +04:00
int err ;
struct at91wdt * wdt ;
2008-06-23 19:05:49 +04:00
2013-10-04 11:24:12 +04:00
wdt = devm_kzalloc ( & pdev - > dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
if ( ! wdt )
2011-11-01 21:43:31 +04:00
return - ENOMEM ;
2013-10-04 11:24:12 +04:00
wdt - > mr = ( WDT_HW_TIMEOUT * 256 ) | AT91_WDT_WDRSTEN | AT91_WDT_WDD |
AT91_WDT_WDDBGHLT | AT91_WDT_WDIDLEHLT ;
wdt - > mr_mask = 0x3FFFFFFF ;
wdt - > nowayout = nowayout ;
wdt - > wdd . parent = & pdev - > dev ;
wdt - > wdd . info = & at91_wdt_info ;
wdt - > wdd . ops = & at91_wdt_ops ;
wdt - > wdd . timeout = WDT_HEARTBEAT ;
wdt - > wdd . min_timeout = 1 ;
wdt - > wdd . max_timeout = 0xFFFF ;
2013-02-01 11:06:21 +04:00
2013-10-04 11:24:12 +04:00
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
wdt - > base = devm_ioremap_resource ( & pdev - > dev , r ) ;
if ( IS_ERR ( wdt - > base ) )
return PTR_ERR ( wdt - > base ) ;
if ( pdev - > dev . of_node ) {
err = of_at91wdt_init ( pdev - > dev . of_node , wdt ) ;
if ( err )
return err ;
}
2008-06-23 19:05:49 +04:00
2013-10-04 11:24:12 +04:00
err = at91_wdt_init ( pdev , wdt ) ;
if ( err )
return err ;
2008-06-23 19:05:49 +04:00
2013-10-04 11:24:12 +04:00
platform_set_drvdata ( pdev , wdt ) ;
2008-06-23 19:05:49 +04:00
2012-02-16 03:06:19 +04:00
pr_info ( " enabled (heartbeat=%d sec, nowayout=%d) \n " ,
2013-10-04 11:24:12 +04:00
wdt - > wdd . timeout , wdt - > nowayout ) ;
2008-06-23 19:05:49 +04:00
return 0 ;
}
static int __exit at91wdt_remove ( struct platform_device * pdev )
{
2013-10-04 11:24:12 +04:00
struct at91wdt * wdt = platform_get_drvdata ( pdev ) ;
watchdog_unregister_device ( & wdt - > wdd ) ;
2008-06-23 19:05:49 +04:00
2013-02-01 11:06:21 +04:00
pr_warn ( " I quit now, hardware will probably reboot! \n " ) ;
2013-10-04 11:24:12 +04:00
del_timer ( & wdt - > timer ) ;
2008-06-23 19:05:49 +04:00
2013-02-01 11:06:21 +04:00
return 0 ;
2008-06-23 19:05:49 +04:00
}
2012-11-12 12:37:25 +04:00
# if defined(CONFIG_OF)
2013-01-25 18:14:27 +04:00
static const struct of_device_id at91_wdt_dt_ids [ ] = {
2012-11-12 12:37:25 +04:00
{ . compatible = " atmel,at91sam9260-wdt " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , at91_wdt_dt_ids ) ;
# endif
2008-06-23 19:05:49 +04:00
static struct platform_driver at91wdt_driver = {
. remove = __exit_p ( at91wdt_remove ) ,
. driver = {
. name = " at91_wdt " ,
2012-11-12 12:37:25 +04:00
. of_match_table = of_match_ptr ( at91_wdt_dt_ids ) ,
2008-06-23 19:05:49 +04:00
} ,
} ;
2013-01-09 15:15:27 +04:00
module_platform_driver_probe ( at91wdt_driver , at91wdt_probe ) ;
2008-06-23 19:05:49 +04:00
MODULE_AUTHOR ( " Renaud CERRATO <r.cerrato@til-technologies.fr> " ) ;
MODULE_DESCRIPTION ( " Watchdog driver for Atmel AT91SAM9x processors " ) ;
MODULE_LICENSE ( " GPL " ) ;