2015-08-06 13:16:46 +03:00
/*
* Driver for Atmel SAMA5D4 Watchdog Timer
*
* Copyright ( C ) 2015 Atmel Corporation
*
* Licensed under GPLv2 .
*/
2017-03-02 20:31:12 +03:00
# include <linux/delay.h>
2015-08-06 13:16:46 +03:00
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_irq.h>
# include <linux/platform_device.h>
# include <linux/reboot.h>
# include <linux/watchdog.h>
# include "at91sam9_wdt.h"
/* minimum and maximum watchdog timeout, in seconds */
# define MIN_WDT_TIMEOUT 1
# define MAX_WDT_TIMEOUT 16
# define WDT_DEFAULT_TIMEOUT MAX_WDT_TIMEOUT
# define WDT_SEC2TICKS(s) ((s) ? (((s) << 8) - 1) : 0)
struct sama5d4_wdt {
struct watchdog_device wdd ;
void __iomem * reg_base ;
2017-01-30 20:18:47 +03:00
u32 mr ;
2017-03-02 20:31:12 +03:00
unsigned long last_ping ;
2015-08-06 13:16:46 +03:00
} ;
2018-02-11 23:08:41 +03:00
static int wdt_timeout ;
2015-08-06 13:16:46 +03:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( wdt_timeout , int , 0 ) ;
MODULE_PARM_DESC ( wdt_timeout ,
" Watchdog timeout in seconds. (default = "
__MODULE_STRING ( WDT_DEFAULT_TIMEOUT ) " ) " ) ;
module_param ( nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2017-03-02 20:31:11 +03:00
# define wdt_enabled (!(wdt->mr & AT91_WDT_WDDIS))
2015-08-06 13:16:46 +03:00
# define wdt_read(wdt, field) \
readl_relaxed ( ( wdt ) - > reg_base + ( field ) )
2017-03-02 20:31:12 +03:00
/* 4 slow clock periods is 4/32768 = 122.07µs*/
# define WDT_DELAY usecs_to_jiffies(123)
static void wdt_write ( struct sama5d4_wdt * wdt , u32 field , u32 val )
{
/*
* WDT_CR and WDT_MR must not be modified within three slow clock
* periods following a restart of the watchdog performed by a write
* access in WDT_CR .
*/
while ( time_before ( jiffies , wdt - > last_ping + WDT_DELAY ) )
usleep_range ( 30 , 125 ) ;
writel_relaxed ( val , wdt - > reg_base + field ) ;
wdt - > last_ping = jiffies ;
}
static void wdt_write_nosleep ( struct sama5d4_wdt * wdt , u32 field , u32 val )
{
if ( time_before ( jiffies , wdt - > last_ping + WDT_DELAY ) )
udelay ( 123 ) ;
writel_relaxed ( val , wdt - > reg_base + field ) ;
wdt - > last_ping = jiffies ;
}
2015-08-06 13:16:46 +03:00
static int sama5d4_wdt_start ( struct watchdog_device * wdd )
{
struct sama5d4_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
2017-01-30 20:18:47 +03:00
wdt - > mr & = ~ AT91_WDT_WDDIS ;
wdt_write ( wdt , AT91_WDT_MR , wdt - > mr ) ;
2015-08-06 13:16:46 +03:00
return 0 ;
}
static int sama5d4_wdt_stop ( struct watchdog_device * wdd )
{
struct sama5d4_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
2017-01-30 20:18:47 +03:00
wdt - > mr | = AT91_WDT_WDDIS ;
wdt_write ( wdt , AT91_WDT_MR , wdt - > mr ) ;
2015-08-06 13:16:46 +03:00
return 0 ;
}
static int sama5d4_wdt_ping ( struct watchdog_device * wdd )
{
struct sama5d4_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
wdt_write ( wdt , AT91_WDT_CR , AT91_WDT_KEY | AT91_WDT_WDRSTT ) ;
return 0 ;
}
static int sama5d4_wdt_set_timeout ( struct watchdog_device * wdd ,
unsigned int timeout )
{
struct sama5d4_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
u32 value = WDT_SEC2TICKS ( timeout ) ;
2017-01-30 20:18:47 +03:00
wdt - > mr & = ~ AT91_WDT_WDV ;
wdt - > mr & = ~ AT91_WDT_WDD ;
wdt - > mr | = AT91_WDT_SET_WDV ( value ) ;
wdt - > mr | = AT91_WDT_SET_WDD ( value ) ;
2017-03-02 20:31:11 +03:00
/*
* WDDIS has to be 0 when updating WDD / WDV . The datasheet states : When
* setting the WDDIS bit , and while it is set , the fields WDV and WDD
* must not be modified .
* If the watchdog is enabled , then the timeout can be updated . Else ,
* wait that the user enables it .
*/
if ( wdt_enabled )
wdt_write ( wdt , AT91_WDT_MR , wdt - > mr & ~ AT91_WDT_WDDIS ) ;
2015-08-06 13:16:46 +03:00
wdd - > timeout = timeout ;
return 0 ;
}
static const struct watchdog_info sama5d4_wdt_info = {
. options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING ,
. identity = " Atmel SAMA5D4 Watchdog " ,
} ;
2017-01-28 10:41:17 +03:00
static const struct watchdog_ops sama5d4_wdt_ops = {
2015-08-06 13:16:46 +03:00
. owner = THIS_MODULE ,
. start = sama5d4_wdt_start ,
. stop = sama5d4_wdt_stop ,
. ping = sama5d4_wdt_ping ,
. set_timeout = sama5d4_wdt_set_timeout ,
} ;
static irqreturn_t sama5d4_wdt_irq_handler ( int irq , void * dev_id )
{
struct sama5d4_wdt * wdt = platform_get_drvdata ( dev_id ) ;
if ( wdt_read ( wdt , AT91_WDT_SR ) ) {
pr_crit ( " Atmel Watchdog Software Reset \n " ) ;
emergency_restart ( ) ;
pr_crit ( " Reboot didn't succeed \n " ) ;
}
return IRQ_HANDLED ;
}
static int of_sama5d4_wdt_init ( struct device_node * np , struct sama5d4_wdt * wdt )
{
const char * tmp ;
2017-01-30 20:18:47 +03:00
wdt - > mr = AT91_WDT_WDDIS ;
2015-08-06 13:16:46 +03:00
if ( ! of_property_read_string ( np , " atmel,watchdog-type " , & tmp ) & &
! strcmp ( tmp , " software " ) )
2017-01-30 20:18:47 +03:00
wdt - > mr | = AT91_WDT_WDFIEN ;
2015-08-06 13:16:46 +03:00
else
2017-01-30 20:18:47 +03:00
wdt - > mr | = AT91_WDT_WDRSTEN ;
2015-08-06 13:16:46 +03:00
if ( of_property_read_bool ( np , " atmel,idle-halt " ) )
2017-01-30 20:18:47 +03:00
wdt - > mr | = AT91_WDT_WDIDLEHLT ;
2015-08-06 13:16:46 +03:00
if ( of_property_read_bool ( np , " atmel,dbg-halt " ) )
2017-01-30 20:18:47 +03:00
wdt - > mr | = AT91_WDT_WDDBGHLT ;
2015-08-06 13:16:46 +03:00
return 0 ;
}
static int sama5d4_wdt_init ( struct sama5d4_wdt * wdt )
{
u32 reg ;
/*
2017-03-02 20:31:11 +03:00
* When booting and resuming , the bootloader may have changed the
* watchdog configuration .
* If the watchdog is already running , we can safely update it .
* Else , we have to disable it properly .
2015-08-06 13:16:46 +03:00
*/
2017-03-02 20:31:11 +03:00
if ( wdt_enabled ) {
2017-03-02 20:31:12 +03:00
wdt_write_nosleep ( wdt , AT91_WDT_MR , wdt - > mr ) ;
2017-03-02 20:31:11 +03:00
} else {
reg = wdt_read ( wdt , AT91_WDT_MR ) ;
if ( ! ( reg & AT91_WDT_WDDIS ) )
2017-03-02 20:31:12 +03:00
wdt_write_nosleep ( wdt , AT91_WDT_MR ,
reg | AT91_WDT_WDDIS ) ;
2017-03-02 20:31:11 +03:00
}
2015-08-06 13:16:46 +03:00
return 0 ;
}
static int sama5d4_wdt_probe ( struct platform_device * pdev )
{
struct watchdog_device * wdd ;
struct sama5d4_wdt * wdt ;
struct resource * res ;
void __iomem * regs ;
u32 irq = 0 ;
2017-03-02 20:31:11 +03:00
u32 timeout ;
2015-08-06 13:16:46 +03:00
int ret ;
wdt = devm_kzalloc ( & pdev - > dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
if ( ! wdt )
return - ENOMEM ;
wdd = & wdt - > wdd ;
2018-02-11 23:08:41 +03:00
wdd - > timeout = WDT_DEFAULT_TIMEOUT ;
2015-08-06 13:16:46 +03:00
wdd - > info = & sama5d4_wdt_info ;
wdd - > ops = & sama5d4_wdt_ops ;
wdd - > min_timeout = MIN_WDT_TIMEOUT ;
wdd - > max_timeout = MAX_WDT_TIMEOUT ;
2017-03-02 20:31:12 +03:00
wdt - > last_ping = jiffies ;
2015-08-06 13:16:46 +03:00
watchdog_set_drvdata ( wdd , wdt ) ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( regs ) )
return PTR_ERR ( regs ) ;
wdt - > reg_base = regs ;
2017-03-02 20:31:13 +03:00
irq = irq_of_parse_and_map ( pdev - > dev . of_node , 0 ) ;
if ( ! irq )
dev_warn ( & pdev - > dev , " failed to get IRQ from DT \n " ) ;
2015-08-06 13:16:46 +03:00
2017-03-02 20:31:13 +03:00
ret = of_sama5d4_wdt_init ( pdev - > dev . of_node , wdt ) ;
if ( ret )
return ret ;
2015-08-06 13:16:46 +03:00
2017-01-30 20:18:47 +03:00
if ( ( wdt - > mr & AT91_WDT_WDFIEN ) & & irq ) {
2015-08-06 13:16:46 +03:00
ret = devm_request_irq ( & pdev - > dev , irq , sama5d4_wdt_irq_handler ,
IRQF_SHARED | IRQF_IRQPOLL |
IRQF_NO_SUSPEND , pdev - > name , pdev ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" cannot register interrupt handler \n " ) ;
return ret ;
}
}
ret = watchdog_init_timeout ( wdd , wdt_timeout , & pdev - > dev ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " unable to set timeout value \n " ) ;
return ret ;
}
2017-03-02 20:31:11 +03:00
timeout = WDT_SEC2TICKS ( wdd - > timeout ) ;
wdt - > mr | = AT91_WDT_SET_WDD ( timeout ) ;
wdt - > mr | = AT91_WDT_SET_WDV ( timeout ) ;
2015-08-06 13:16:46 +03:00
ret = sama5d4_wdt_init ( wdt ) ;
if ( ret )
return ret ;
watchdog_set_nowayout ( wdd , nowayout ) ;
ret = watchdog_register_device ( wdd ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to register watchdog device \n " ) ;
return ret ;
}
platform_set_drvdata ( pdev , wdt ) ;
dev_info ( & pdev - > dev , " initialized (timeout = %d sec, nowayout = %d) \n " ,
2018-02-11 23:08:41 +03:00
wdd - > timeout , nowayout ) ;
2015-08-06 13:16:46 +03:00
return 0 ;
}
static int sama5d4_wdt_remove ( struct platform_device * pdev )
{
struct sama5d4_wdt * wdt = platform_get_drvdata ( pdev ) ;
sama5d4_wdt_stop ( & wdt - > wdd ) ;
watchdog_unregister_device ( & wdt - > wdd ) ;
return 0 ;
}
static const struct of_device_id sama5d4_wdt_of_match [ ] = {
{ . compatible = " atmel,sama5d4-wdt " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sama5d4_wdt_of_match ) ;
2017-01-30 20:18:48 +03:00
# ifdef CONFIG_PM_SLEEP
static int sama5d4_wdt_resume ( struct device * dev )
{
struct sama5d4_wdt * wdt = dev_get_drvdata ( dev ) ;
2017-03-02 20:31:14 +03:00
/*
* FIXME : writing MR also pings the watchdog which may not be desired .
* This should only be done when the registers are lost on suspend but
* there is no way to get this information right now .
*/
2017-03-02 20:31:11 +03:00
sama5d4_wdt_init ( wdt ) ;
2017-01-30 20:18:48 +03:00
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( sama5d4_wdt_pm_ops , NULL ,
sama5d4_wdt_resume ) ;
2015-08-06 13:16:46 +03:00
static struct platform_driver sama5d4_wdt_driver = {
. probe = sama5d4_wdt_probe ,
. remove = sama5d4_wdt_remove ,
. driver = {
. name = " sama5d4_wdt " ,
2017-01-30 20:18:48 +03:00
. pm = & sama5d4_wdt_pm_ops ,
2015-08-06 13:16:46 +03:00
. of_match_table = sama5d4_wdt_of_match ,
}
} ;
module_platform_driver ( sama5d4_wdt_driver ) ;
MODULE_AUTHOR ( " Atmel Corporation " ) ;
MODULE_DESCRIPTION ( " Atmel SAMA5D4 Watchdog Timer driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;