2018-02-10 12:27:01 +03:00
// SPDX-License-Identifier: GPL-2.0
2009-05-06 17:35:40 +04:00
/*
* coh901327_wdt . c
*
* Copyright ( C ) 2008 - 2009 ST - Ericsson AB
* Watchdog driver for the ST - Ericsson AB COH 901 327 IP core
* Author : Linus Walleij < linus . walleij @ stericsson . com >
*/
2019-04-23 18:48:35 +03:00
# include <linux/moduleparam.h>
2018-06-20 08:47:28 +03:00
# include <linux/mod_devicetable.h>
2009-05-06 17:35:40 +04:00
# include <linux/types.h>
# include <linux/watchdog.h>
# include <linux/interrupt.h>
# include <linux/pm.h>
# include <linux/platform_device.h>
# include <linux/io.h>
# include <linux/bitops.h>
# include <linux/clk.h>
2009-07-21 02:40:46 +04:00
# include <linux/delay.h>
2012-03-16 12:14:12 +04:00
# include <linux/err.h>
2009-05-06 17:35:40 +04:00
# define DRV_NAME "WDOG COH 901 327"
/*
* COH 901 327 register definitions
*/
/* WDOG_FEED Register 32bit (-/W) */
# define U300_WDOG_FR 0x00
# define U300_WDOG_FR_FEED_RESTART_TIMER 0xFEEDU
/* WDOG_TIMEOUT Register 32bit (R/W) */
# define U300_WDOG_TR 0x04
# define U300_WDOG_TR_TIMEOUT_MASK 0x7FFFU
/* WDOG_DISABLE1 Register 32bit (-/W) */
# define U300_WDOG_D1R 0x08
# define U300_WDOG_D1R_DISABLE1_DISABLE_TIMER 0x2BADU
/* WDOG_DISABLE2 Register 32bit (R/W) */
# define U300_WDOG_D2R 0x0C
# define U300_WDOG_D2R_DISABLE2_DISABLE_TIMER 0xCAFEU
# define U300_WDOG_D2R_DISABLE_STATUS_DISABLED 0xDABEU
# define U300_WDOG_D2R_DISABLE_STATUS_ENABLED 0x0000U
/* WDOG_STATUS Register 32bit (R/W) */
# define U300_WDOG_SR 0x10
# define U300_WDOG_SR_STATUS_TIMED_OUT 0xCFE8U
# define U300_WDOG_SR_STATUS_NORMAL 0x0000U
# define U300_WDOG_SR_RESET_STATUS_RESET 0xE8B4U
/* WDOG_COUNT Register 32bit (R/-) */
# define U300_WDOG_CR 0x14
# define U300_WDOG_CR_VALID_IND 0x8000U
# define U300_WDOG_CR_VALID_STABLE 0x0000U
# define U300_WDOG_CR_COUNT_VALUE_MASK 0x7FFFU
/* WDOG_JTAGOVR Register 32bit (R/W) */
# define U300_WDOG_JOR 0x18
# define U300_WDOG_JOR_JTAG_MODE_IND 0x0002U
# define U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE 0x0001U
/* WDOG_RESTART Register 32bit (-/W) */
# define U300_WDOG_RR 0x1C
# define U300_WDOG_RR_RESTART_VALUE_RESUME 0xACEDU
/* WDOG_IRQ_EVENT Register 32bit (R/W) */
# define U300_WDOG_IER 0x20
# define U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND 0x0001U
# define U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE 0x0001U
/* WDOG_IRQ_MASK Register 32bit (R/W) */
# define U300_WDOG_IMR 0x24
# define U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE 0x0001U
/* WDOG_IRQ_FORCE Register 32bit (R/W) */
# define U300_WDOG_IFR 0x28
# define U300_WDOG_IFR_WILL_BARK_IRQ_FORCE_ENABLE 0x0001U
/* Default timeout in seconds = 1 minute */
2018-02-11 23:08:47 +03:00
# define U300_WDOG_DEFAULT_TIMEOUT 60
static unsigned int margin ;
2009-05-06 17:35:40 +04:00
static int irq ;
static void __iomem * virtbase ;
static struct device * parent ;
static struct clk * clk ;
/*
* Enabling and disabling functions .
*/
static void coh901327_enable ( u16 timeout )
{
u16 val ;
2009-07-21 02:40:46 +04:00
unsigned long freq ;
unsigned long delay_ns ;
2009-05-06 17:35:40 +04:00
/* Restart timer if it is disabled */
val = readw ( virtbase + U300_WDOG_D2R ) ;
if ( val = = U300_WDOG_D2R_DISABLE_STATUS_DISABLED )
writew ( U300_WDOG_RR_RESTART_VALUE_RESUME ,
virtbase + U300_WDOG_RR ) ;
/* Acknowledge any pending interrupt so it doesn't just fire off */
writew ( U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE ,
virtbase + U300_WDOG_IER ) ;
2009-07-21 02:40:46 +04:00
/*
* The interrupt is cleared in the 32 kHz clock domain .
* Wait 3 32 kHz cycles for it to take effect
*/
freq = clk_get_rate ( clk ) ;
2009-08-10 02:04:35 +04:00
delay_ns = DIV_ROUND_UP ( 1000000000 , freq ) ; /* Freq to ns and round up */
2009-07-21 02:40:46 +04:00
delay_ns = 3 * delay_ns ; /* Wait 3 cycles */
ndelay ( delay_ns ) ;
2009-05-06 17:35:40 +04:00
/* Enable the watchdog interrupt */
writew ( U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE , virtbase + U300_WDOG_IMR ) ;
/* Activate the watchdog timer */
writew ( timeout , virtbase + U300_WDOG_TR ) ;
/* Start the watchdog timer */
writew ( U300_WDOG_FR_FEED_RESTART_TIMER , virtbase + U300_WDOG_FR ) ;
/*
* Extra read so that this change propagate in the watchdog .
*/
( void ) readw ( virtbase + U300_WDOG_CR ) ;
val = readw ( virtbase + U300_WDOG_D2R ) ;
if ( val ! = U300_WDOG_D2R_DISABLE_STATUS_ENABLED )
dev_err ( parent ,
" %s(): watchdog not enabled! D2R value %04x \n " ,
__func__ , val ) ;
}
static void coh901327_disable ( void )
{
u16 val ;
/* Disable the watchdog interrupt if it is active */
writew ( 0x0000U , virtbase + U300_WDOG_IMR ) ;
/* If the watchdog is currently enabled, attempt to disable it */
val = readw ( virtbase + U300_WDOG_D2R ) ;
if ( val ! = U300_WDOG_D2R_DISABLE_STATUS_DISABLED ) {
writew ( U300_WDOG_D1R_DISABLE1_DISABLE_TIMER ,
virtbase + U300_WDOG_D1R ) ;
writew ( U300_WDOG_D2R_DISABLE2_DISABLE_TIMER ,
virtbase + U300_WDOG_D2R ) ;
/* Write this twice (else problems occur) */
writew ( U300_WDOG_D2R_DISABLE2_DISABLE_TIMER ,
virtbase + U300_WDOG_D2R ) ;
}
val = readw ( virtbase + U300_WDOG_D2R ) ;
if ( val ! = U300_WDOG_D2R_DISABLE_STATUS_DISABLED )
dev_err ( parent ,
" %s(): watchdog not disabled! D2R value %04x \n " ,
__func__ , val ) ;
}
2012-03-16 12:14:12 +04:00
static int coh901327_start ( struct watchdog_device * wdt_dev )
2009-05-06 17:35:40 +04:00
{
2012-03-22 23:42:16 +04:00
coh901327_enable ( wdt_dev - > timeout * 100 ) ;
2012-03-16 12:14:12 +04:00
return 0 ;
}
static int coh901327_stop ( struct watchdog_device * wdt_dev )
{
coh901327_disable ( ) ;
return 0 ;
2009-05-06 17:35:40 +04:00
}
2012-03-16 12:14:12 +04:00
static int coh901327_ping ( struct watchdog_device * wdd )
2009-05-06 17:35:40 +04:00
{
/* Feed the watchdog */
writew ( U300_WDOG_FR_FEED_RESTART_TIMER ,
virtbase + U300_WDOG_FR ) ;
2012-03-16 12:14:12 +04:00
return 0 ;
2009-05-06 17:35:40 +04:00
}
2012-03-16 12:14:12 +04:00
static int coh901327_settimeout ( struct watchdog_device * wdt_dev ,
unsigned int time )
2009-05-06 17:35:40 +04:00
{
2012-03-22 23:42:16 +04:00
wdt_dev - > timeout = time ;
2009-05-06 17:35:40 +04:00
/* Set new timeout value */
2012-03-22 23:42:16 +04:00
writew ( time * 100 , virtbase + U300_WDOG_TR ) ;
2009-05-06 17:35:40 +04:00
/* Feed the dog */
writew ( U300_WDOG_FR_FEED_RESTART_TIMER ,
virtbase + U300_WDOG_FR ) ;
return 0 ;
}
2012-03-16 12:14:12 +04:00
static unsigned int coh901327_gettimeleft ( struct watchdog_device * wdt_dev )
{
u16 val ;
/* Read repeatedly until the value is stable! */
val = readw ( virtbase + U300_WDOG_CR ) ;
while ( val & U300_WDOG_CR_VALID_IND )
val = readw ( virtbase + U300_WDOG_CR ) ;
val & = U300_WDOG_CR_COUNT_VALUE_MASK ;
if ( val ! = 0 )
val / = 100 ;
return val ;
}
2009-05-06 17:35:40 +04:00
/*
* This interrupt occurs 10 ms before the watchdog WILL bark .
*/
static irqreturn_t coh901327_interrupt ( int irq , void * data )
{
u16 val ;
/*
* Ack IRQ ? If this occurs we ' re FUBAR anyway , so
* just acknowledge , disable the interrupt and await the imminent end .
* If you at some point need a host of callbacks to be called
* when the system is about to watchdog - reset , add them here !
*
* NOTE : on future versions of this IP - block , it will be possible
* to prevent a watchdog reset by feeding the watchdog at this
* point .
*/
val = readw ( virtbase + U300_WDOG_IER ) ;
if ( val = = U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND )
writew ( U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE ,
virtbase + U300_WDOG_IER ) ;
writew ( 0x0000U , virtbase + U300_WDOG_IMR ) ;
dev_crit ( parent , " watchdog is barking! \n " ) ;
return IRQ_HANDLED ;
}
2012-03-16 12:14:12 +04:00
static const struct watchdog_info coh901327_ident = {
. options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING ,
. identity = DRV_NAME ,
} ;
2009-05-06 17:35:40 +04:00
2017-07-08 03:33:30 +03:00
static const struct watchdog_ops coh901327_ops = {
2012-03-16 12:14:12 +04:00
. owner = THIS_MODULE ,
. start = coh901327_start ,
. stop = coh901327_stop ,
. ping = coh901327_ping ,
. set_timeout = coh901327_settimeout ,
. get_timeleft = coh901327_gettimeleft ,
2009-05-06 17:35:40 +04:00
} ;
2012-03-16 12:14:12 +04:00
static struct watchdog_device coh901327_wdt = {
. info = & coh901327_ident ,
. ops = & coh901327_ops ,
/*
2012-03-22 23:42:16 +04:00
* Max timeout is 327 since the 10 ms
2012-03-16 12:14:12 +04:00
* timeout register is max
* 0x7FFF = 327670 ms ~ = 327 s .
*/
2018-02-11 23:08:47 +03:00
. min_timeout = 1 ,
2012-03-16 12:14:12 +04:00
. max_timeout = 327 ,
2018-02-11 23:08:47 +03:00
. timeout = U300_WDOG_DEFAULT_TIMEOUT ,
2009-05-06 17:35:40 +04:00
} ;
static int __init coh901327_probe ( struct platform_device * pdev )
{
2017-01-04 06:21:37 +03:00
struct device * dev = & pdev - > dev ;
2009-05-06 17:35:40 +04:00
int ret ;
u16 val ;
2017-01-04 06:21:37 +03:00
parent = dev ;
2009-05-06 17:35:40 +04:00
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
virtbase = devm_platform_ioremap_resource ( pdev , 0 ) ;
2017-01-03 14:10:30 +03:00
if ( IS_ERR ( virtbase ) )
return PTR_ERR ( virtbase ) ;
2009-05-06 17:35:40 +04:00
2017-01-04 06:21:37 +03:00
clk = clk_get ( dev , NULL ) ;
2009-05-06 17:35:40 +04:00
if ( IS_ERR ( clk ) ) {
ret = PTR_ERR ( clk ) ;
2017-01-04 06:21:37 +03:00
dev_err ( dev , " could not get clock \n " ) ;
2017-01-03 14:10:30 +03:00
return ret ;
2009-05-06 17:35:40 +04:00
}
2012-06-12 21:19:01 +04:00
ret = clk_prepare_enable ( clk ) ;
2009-05-06 17:35:40 +04:00
if ( ret ) {
2017-01-04 06:21:37 +03:00
dev_err ( dev , " could not prepare and enable clock \n " ) ;
2009-05-06 17:35:40 +04:00
goto out_no_clk_enable ;
}
val = readw ( virtbase + U300_WDOG_SR ) ;
switch ( val ) {
case U300_WDOG_SR_STATUS_TIMED_OUT :
2017-01-04 06:21:37 +03:00
dev_info ( dev , " watchdog timed out since last chip reset! \n " ) ;
2012-03-16 12:14:12 +04:00
coh901327_wdt . bootstatus | = WDIOF_CARDRESET ;
2009-05-06 17:35:40 +04:00
/* Status will be cleared below */
break ;
case U300_WDOG_SR_STATUS_NORMAL :
2017-01-04 06:21:37 +03:00
dev_info ( dev , " in normal status, no timeouts have occurred. \n " ) ;
2009-05-06 17:35:40 +04:00
break ;
default :
2017-01-04 06:21:37 +03:00
dev_info ( dev , " contains an illegal status code (%08x) \n " , val ) ;
2009-05-06 17:35:40 +04:00
break ;
}
val = readw ( virtbase + U300_WDOG_D2R ) ;
switch ( val ) {
case U300_WDOG_D2R_DISABLE_STATUS_DISABLED :
2017-01-04 06:21:37 +03:00
dev_info ( dev , " currently disabled. \n " ) ;
2009-05-06 17:35:40 +04:00
break ;
case U300_WDOG_D2R_DISABLE_STATUS_ENABLED :
2017-01-04 06:21:37 +03:00
dev_info ( dev , " currently enabled! (disabling it now) \n " ) ;
2009-05-06 17:35:40 +04:00
coh901327_disable ( ) ;
break ;
default :
2017-01-04 06:21:37 +03:00
dev_err ( dev , " contains an illegal enable/disable code (%08x) \n " ,
2009-05-06 17:35:40 +04:00
val ) ;
break ;
}
/* Reset the watchdog */
writew ( U300_WDOG_SR_RESET_STATUS_RESET , virtbase + U300_WDOG_SR ) ;
irq = platform_get_irq ( pdev , 0 ) ;
2011-09-07 12:10:55 +04:00
if ( request_irq ( irq , coh901327_interrupt , 0 ,
2009-05-06 17:35:40 +04:00
DRV_NAME " Bark " , pdev ) ) {
ret = - EIO ;
goto out_no_irq ;
}
2018-02-11 23:08:47 +03:00
watchdog_init_timeout ( & coh901327_wdt , margin , dev ) ;
2012-03-22 23:42:16 +04:00
2017-01-04 06:21:37 +03:00
coh901327_wdt . parent = dev ;
2012-03-16 12:14:12 +04:00
ret = watchdog_register_device ( & coh901327_wdt ) ;
2017-01-03 14:22:09 +03:00
if ( ret )
2009-05-06 17:35:40 +04:00
goto out_no_wdog ;
2018-02-11 23:08:47 +03:00
dev_info ( dev , " initialized. (timeout=%d sec) \n " ,
coh901327_wdt . timeout ) ;
2009-05-06 17:35:40 +04:00
return 0 ;
out_no_wdog :
free_irq ( irq , pdev ) ;
out_no_irq :
2012-06-12 21:19:01 +04:00
clk_disable_unprepare ( clk ) ;
2009-05-06 17:35:40 +04:00
out_no_clk_enable :
clk_put ( clk ) ;
return ret ;
}
# ifdef CONFIG_PM
2011-10-03 12:52:58 +04:00
static u16 wdogenablestore ;
static u16 irqmaskstore ;
2009-05-06 17:35:40 +04:00
static int coh901327_suspend ( struct platform_device * pdev , pm_message_t state )
{
irqmaskstore = readw ( virtbase + U300_WDOG_IMR ) & 0x0001U ;
wdogenablestore = readw ( virtbase + U300_WDOG_D2R ) ;
/* If watchdog is on, disable it here and now */
if ( wdogenablestore = = U300_WDOG_D2R_DISABLE_STATUS_ENABLED )
coh901327_disable ( ) ;
return 0 ;
}
static int coh901327_resume ( struct platform_device * pdev )
{
/* Restore the watchdog interrupt */
writew ( irqmaskstore , virtbase + U300_WDOG_IMR ) ;
if ( wdogenablestore = = U300_WDOG_D2R_DISABLE_STATUS_ENABLED ) {
/* Restart the watchdog timer */
writew ( U300_WDOG_RR_RESTART_VALUE_RESUME ,
virtbase + U300_WDOG_RR ) ;
writew ( U300_WDOG_FR_FEED_RESTART_TIMER ,
virtbase + U300_WDOG_FR ) ;
}
return 0 ;
}
# else
# define coh901327_suspend NULL
# define coh901327_resume NULL
# endif
/*
* Mistreating the watchdog is the only way to perform a software reset of the
* system on EMP platforms . So we implement this and export a symbol for it .
*/
void coh901327_watchdog_reset ( void )
{
/* Enable even if on JTAG too */
writew ( U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE ,
virtbase + U300_WDOG_JOR ) ;
/*
* Timeout = 5 s , we have to wait for the watchdog reset to
* actually take place : the watchdog will be reloaded with the
* default value immediately , so we HAVE to reboot and get back
* into the kernel in 30 s , or the device will reboot again !
* The boot loader will typically deactivate the watchdog , so we
* need time enough for the boot loader to get to the point of
* deactivating the watchdog before it is shut down by it .
*
* NOTE : on future versions of the watchdog , this restriction is
tree-wide: fix assorted typos all over the place
That is "success", "unknown", "through", "performance", "[re|un]mapping"
, "access", "default", "reasonable", "[con]currently", "temperature"
, "channel", "[un]used", "application", "example","hierarchy", "therefore"
, "[over|under]flow", "contiguous", "threshold", "enough" and others.
Signed-off-by: André Goddard Rosa <andre.goddard@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2009-11-14 18:09:05 +03:00
* gone : the watchdog will be reloaded with a default value ( 1 min )
2009-05-06 17:35:40 +04:00
* instead of last value , and you can conveniently set the watchdog
* timeout to 10 ms ( value = 1 ) without any problems .
*/
coh901327_enable ( 500 ) ;
/* Return and await doom */
}
2013-04-19 14:56:36 +04:00
static const struct of_device_id coh901327_dt_match [ ] = {
{ . compatible = " stericsson,coh901327 " } ,
{ } ,
} ;
2009-05-06 17:35:40 +04:00
static struct platform_driver coh901327_driver = {
. driver = {
. name = " coh901327_wdog " ,
2013-04-19 14:56:36 +04:00
. of_match_table = coh901327_dt_match ,
2019-04-23 18:48:35 +03:00
. suppress_bind_attrs = true ,
2009-05-06 17:35:40 +04:00
} ,
. suspend = coh901327_suspend ,
. resume = coh901327_resume ,
} ;
2019-04-23 18:48:35 +03:00
builtin_platform_driver_probe ( coh901327_driver , coh901327_probe ) ;
2009-05-06 17:35:40 +04:00
2019-04-23 18:48:35 +03:00
/* not really modular, but ... */
2012-03-22 23:42:16 +04:00
module_param ( margin , uint , 0 ) ;
2009-05-06 17:35:40 +04:00
MODULE_PARM_DESC ( margin , " Watchdog margin in seconds (default 60s) " ) ;