2019-05-28 19:57:18 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-08-06 13:16:46 +03:00
/*
* Driver for Atmel SAMA5D4 Watchdog Timer
*
2019-11-18 11:50:36 +03:00
* Copyright ( C ) 2015 - 2019 Microchip Technology Inc . and its subsidiaries
2015-08-06 13:16:46 +03:00
*/
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>
2019-11-18 11:50:36 +03:00
# include <linux/of_device.h>
2015-08-06 13:16:46 +03:00
# 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 ;
2019-11-18 11:50:36 +03:00
u32 ir ;
2017-03-02 20:31:12 +03:00
unsigned long last_ping ;
2019-11-18 11:50:36 +03:00
bool need_irq ;
bool sam9x60_support ;
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 ) ;
2019-11-18 11:50:36 +03:00
if ( wdt - > sam9x60_support ) {
writel_relaxed ( wdt - > ir , wdt - > reg_base + AT91_SAM9X60_IER ) ;
wdt - > mr & = ~ AT91_SAM9X60_WDDIS ;
} else {
wdt - > mr & = ~ AT91_WDT_WDDIS ;
}
2017-01-30 20:18:47 +03:00
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 ) ;
2019-11-18 11:50:36 +03:00
if ( wdt - > sam9x60_support ) {
writel_relaxed ( wdt - > ir , wdt - > reg_base + AT91_SAM9X60_IDR ) ;
wdt - > mr | = AT91_SAM9X60_WDDIS ;
} else {
wdt - > mr | = AT91_WDT_WDDIS ;
}
2017-01-30 20:18:47 +03:00
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 ) ;
2019-11-18 11:50:36 +03:00
if ( wdt - > sam9x60_support ) {
wdt_write ( wdt , AT91_SAM9X60_WLR ,
AT91_SAM9X60_SET_COUNTER ( value ) ) ;
wdd - > timeout = timeout ;
return 0 ;
}
2017-01-30 20:18:47 +03:00
wdt - > mr & = ~ AT91_WDT_WDV ;
wdt - > mr | = AT91_WDT_SET_WDV ( 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 ) ;
2019-11-18 11:50:36 +03:00
u32 reg ;
2015-08-06 13:16:46 +03:00
2019-11-18 11:50:36 +03:00
if ( wdt - > sam9x60_support )
reg = wdt_read ( wdt , AT91_SAM9X60_ISR ) ;
else
reg = wdt_read ( wdt , AT91_WDT_SR ) ;
if ( reg ) {
2015-08-06 13:16:46 +03:00
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 ;
2019-11-18 11:50:36 +03:00
if ( wdt - > sam9x60_support )
wdt - > mr = AT91_SAM9X60_WDDIS ;
else
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 " ) )
2019-11-18 11:50:36 +03:00
wdt - > need_irq = true ;
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 )
{
2019-11-18 11:50:36 +03:00
u32 reg , val ;
val = WDT_SEC2TICKS ( WDT_DEFAULT_TIMEOUT ) ;
2015-08-06 13:16:46 +03:00
/*
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
*/
2019-11-18 11:50:36 +03:00
if ( ! wdt_enabled ) {
2017-03-02 20:31:11 +03:00
reg = wdt_read ( wdt , AT91_WDT_MR ) ;
2019-11-18 11:50:36 +03:00
if ( wdt - > sam9x60_support & & ( ! ( reg & AT91_SAM9X60_WDDIS ) ) )
wdt_write_nosleep ( wdt , AT91_WDT_MR ,
reg | AT91_SAM9X60_WDDIS ) ;
else if ( ! wdt - > sam9x60_support & &
( ! ( 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
}
2019-11-18 11:50:36 +03:00
if ( wdt - > sam9x60_support ) {
if ( wdt - > need_irq )
wdt - > ir = AT91_SAM9X60_PERINT ;
else
wdt - > mr | = AT91_SAM9X60_PERIODRST ;
wdt_write ( wdt , AT91_SAM9X60_IER , wdt - > ir ) ;
wdt_write ( wdt , AT91_SAM9X60_WLR , AT91_SAM9X60_SET_COUNTER ( val ) ) ;
} else {
wdt - > mr | = AT91_WDT_SET_WDD ( WDT_SEC2TICKS ( MAX_WDT_TIMEOUT ) ) ;
wdt - > mr | = AT91_WDT_SET_WDV ( val ) ;
if ( wdt - > need_irq )
wdt - > mr | = AT91_WDT_WDFIEN ;
else
wdt - > mr | = AT91_WDT_WDRSTEN ;
}
wdt_write_nosleep ( wdt , AT91_WDT_MR , wdt - > mr ) ;
2015-08-06 13:16:46 +03:00
return 0 ;
}
static int sama5d4_wdt_probe ( struct platform_device * pdev )
{
2019-04-09 20:23:54 +03:00
struct device * dev = & pdev - > dev ;
2015-08-06 13:16:46 +03:00
struct watchdog_device * wdd ;
struct sama5d4_wdt * wdt ;
void __iomem * regs ;
u32 irq = 0 ;
int ret ;
2019-04-09 20:23:54 +03:00
wdt = devm_kzalloc ( dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
2015-08-06 13:16:46 +03:00
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 ;
2019-11-18 11:50:36 +03:00
wdt - > sam9x60_support = of_device_is_compatible ( dev - > of_node ,
" microchip,sam9x60-wdt " ) ;
2015-08-06 13:16:46 +03:00
watchdog_set_drvdata ( wdd , wdt ) ;
watchdog: Convert to use devm_platform_ioremap_resource
Use devm_platform_ioremap_resource to reduce source code size,
improve readability, and reduce the likelyhood of bugs.
The conversion was done automatically with coccinelle using the
following semantic patch.
@r@
identifier res, pdev;
expression a;
expression index;
expression e;
@@
<+...
- res = platform_get_resource(pdev, IORESOURCE_MEM, index);
- a = devm_ioremap_resource(e, res);
+ a = devm_platform_ioremap_resource(pdev, index);
...+>
@depends on r@
identifier r.res;
@@
- struct resource *res;
... when != res
@@
identifier res, pdev;
expression index;
expression a;
@@
- struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, index);
- a = devm_ioremap_resource(&pdev->dev, res);
+ a = devm_platform_ioremap_resource(pdev, index);
Cc: Joel Stanley <joel@jms.id.au>
Cc: Nicolas Ferre <nicolas.ferre@microchip.com>
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: Florian Fainelli <f.fainelli@gmail.com>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Baruch Siach <baruch@tkos.co.il>
Cc: Keguang Zhang <keguang.zhang@gmail.com>
Cc: Vladimir Zapolskiy <vz@mleia.com>
Cc: Kevin Hilman <khilman@baylibre.com>
Cc: Matthias Brugger <matthias.bgg@gmail.com>
Cc: Avi Fishman <avifishman70@gmail.com>
Cc: Nancy Yuen <yuenn@google.com>
Cc: Brendan Higgins <brendanhiggins@google.com>
Cc: Wan ZongShun <mcuos.com@gmail.com>
Cc: Michal Simek <michal.simek@xilinx.com>
Cc: Sylvain Lemieux <slemieux.tyco@gmail.com>
Cc: Kukjin Kim <kgene@kernel.org>
Cc: Barry Song <baohua@kernel.org>
Cc: Orson Zhai <orsonzhai@gmail.com>
Cc: Patrice Chotard <patrice.chotard@st.com>
Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com>
Cc: Maxime Ripard <maxime.ripard@bootlin.com>
Cc: Chen-Yu Tsai <wens@csie.org>
Cc: Marc Gonzalez <marc.w.gonzalez@free.fr>
Cc: Thierry Reding <thierry.reding@gmail.com>
Cc: Shawn Guo <shawnguo@kernel.org>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Acked-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Tested-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Acked-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Maxime Ripard <maxime.ripard@bootlin.com>
Acked-by: Michal Simek <michal.simek@xilinx.com> (cadence/xilinx wdts)
Acked-by: Thierry Reding <treding@nvidia.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Acked-by: Patrice Chotard <patrice.chotard@st.com>
Acked-by: Vladimir Zapolskiy <vz@mleia.com>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>
2019-04-02 22:01:53 +03:00
regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2015-08-06 13:16:46 +03:00
if ( IS_ERR ( regs ) )
return PTR_ERR ( regs ) ;
wdt - > reg_base = regs ;
2019-04-09 20:23:54 +03:00
ret = of_sama5d4_wdt_init ( dev - > of_node , wdt ) ;
2017-03-02 20:31:13 +03:00
if ( ret )
return ret ;
2015-08-06 13:16:46 +03:00
2019-11-18 11:50:36 +03:00
if ( wdt - > need_irq ) {
irq = irq_of_parse_and_map ( dev - > of_node , 0 ) ;
if ( ! irq ) {
dev_warn ( dev , " failed to get IRQ from DT \n " ) ;
wdt - > need_irq = false ;
}
}
if ( wdt - > need_irq ) {
2019-04-09 20:23:54 +03:00
ret = devm_request_irq ( dev , irq , sama5d4_wdt_irq_handler ,
2015-08-06 13:16:46 +03:00
IRQF_SHARED | IRQF_IRQPOLL |
IRQF_NO_SUSPEND , pdev - > name , pdev ) ;
if ( ret ) {
2019-04-09 20:23:54 +03:00
dev_err ( dev , " cannot register interrupt handler \n " ) ;
2015-08-06 13:16:46 +03:00
return ret ;
}
}
2019-04-09 20:23:54 +03:00
watchdog_init_timeout ( wdd , wdt_timeout , dev ) ;
2015-08-06 13:16:46 +03:00
ret = sama5d4_wdt_init ( wdt ) ;
if ( ret )
return ret ;
watchdog_set_nowayout ( wdd , nowayout ) ;
2019-04-09 20:23:54 +03:00
watchdog_stop_on_unregister ( wdd ) ;
ret = devm_watchdog_register_device ( dev , wdd ) ;
2019-05-19 00:27:51 +03:00
if ( ret )
2015-08-06 13:16:46 +03:00
return ret ;
platform_set_drvdata ( pdev , wdt ) ;
2019-04-09 20:23:54 +03:00
dev_info ( 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 const struct of_device_id sama5d4_wdt_of_match [ ] = {
2019-11-18 11:50:36 +03:00
{
. compatible = " atmel,sama5d4-wdt " ,
} ,
{
. compatible = " microchip,sam9x60-wdt " ,
} ,
2015-08-06 13:16:46 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , sama5d4_wdt_of_match ) ;
2017-01-30 20:18:48 +03:00
# ifdef CONFIG_PM_SLEEP
2019-06-14 15:53:22 +03:00
static int sama5d4_wdt_suspend_late ( struct device * dev )
{
struct sama5d4_wdt * wdt = dev_get_drvdata ( dev ) ;
if ( watchdog_active ( & wdt - > wdd ) )
sama5d4_wdt_stop ( & wdt - > wdd ) ;
return 0 ;
}
static int sama5d4_wdt_resume_early ( struct device * dev )
2017-01-30 20:18:48 +03:00
{
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
2019-06-14 15:53:22 +03:00
if ( watchdog_active ( & wdt - > wdd ) )
sama5d4_wdt_start ( & wdt - > wdd ) ;
2017-01-30 20:18:48 +03:00
return 0 ;
}
# endif
2019-06-14 15:53:22 +03:00
static const struct dev_pm_ops sama5d4_wdt_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS ( sama5d4_wdt_suspend_late ,
sama5d4_wdt_resume_early )
} ;
2017-01-30 20:18:48 +03:00
2015-08-06 13:16:46 +03:00
static struct platform_driver sama5d4_wdt_driver = {
. probe = sama5d4_wdt_probe ,
. 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 " ) ;