2018-03-16 18:14:11 +03:00
// SPDX-License-Identifier: GPL-2.0+
2014-08-22 13:28:01 +04:00
/*
* Cadence WDT driver - Used by Xilinx Zynq
*
* Copyright ( C ) 2010 - 2014 Xilinx , Inc .
*
*/
# include <linux/clk.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
# define CDNS_WDT_DEFAULT_TIMEOUT 10
/* Supports 1 - 516 sec */
# define CDNS_WDT_MIN_TIMEOUT 1
# define CDNS_WDT_MAX_TIMEOUT 516
/* Restart key */
# define CDNS_WDT_RESTART_KEY 0x00001999
/* Counter register access key */
# define CDNS_WDT_REGISTER_ACCESS_KEY 0x00920000
/* Counter value divisor */
# define CDNS_WDT_COUNTER_VALUE_DIVISOR 0x1000
/* Clock prescaler value and selection */
# define CDNS_WDT_PRESCALE_64 64
# define CDNS_WDT_PRESCALE_512 512
# define CDNS_WDT_PRESCALE_4096 4096
# define CDNS_WDT_PRESCALE_SELECT_64 1
# define CDNS_WDT_PRESCALE_SELECT_512 2
# define CDNS_WDT_PRESCALE_SELECT_4096 3
/* Input clock frequency */
# define CDNS_WDT_CLK_10MHZ 10000000
# define CDNS_WDT_CLK_75MHZ 75000000
/* Counter maximum value */
# define CDNS_WDT_COUNTER_MAX 0xFFF
2017-03-20 10:29:31 +03:00
static int wdt_timeout ;
2014-08-22 13:28:01 +04:00
static int nowayout = WATCHDOG_NOWAYOUT ;
2017-08-04 10:39:16 +03:00
module_param ( wdt_timeout , int , 0644 ) ;
2014-08-22 13:28:01 +04:00
MODULE_PARM_DESC ( wdt_timeout ,
" Watchdog time in seconds. (default= "
__MODULE_STRING ( CDNS_WDT_DEFAULT_TIMEOUT ) " ) " ) ;
2017-08-04 10:39:16 +03:00
module_param ( nowayout , int , 0644 ) ;
2014-08-22 13:28:01 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
/**
* struct cdns_wdt - Watchdog device structure
* @ regs : baseaddress of device
* @ rst : reset flag
* @ clk : struct clk * of a clock source
* @ prescaler : for saving prescaler value
* @ ctrl_clksel : counter clock prescaler selection
* @ io_lock : spinlock for IO register access
* @ cdns_wdt_device : watchdog device structure
*
* Structure containing parameters specific to cadence watchdog .
*/
struct cdns_wdt {
void __iomem * regs ;
bool rst ;
struct clk * clk ;
u32 prescaler ;
u32 ctrl_clksel ;
spinlock_t io_lock ;
struct watchdog_device cdns_wdt_device ;
} ;
/* Write access to Registers */
static inline void cdns_wdt_writereg ( struct cdns_wdt * wdt , u32 offset , u32 val )
{
writel_relaxed ( val , wdt - > regs + offset ) ;
}
/*************************Register Map**************************************/
/* Register Offsets for the WDT */
# define CDNS_WDT_ZMR_OFFSET 0x0 /* Zero Mode Register */
# define CDNS_WDT_CCR_OFFSET 0x4 /* Counter Control Register */
# define CDNS_WDT_RESTART_OFFSET 0x8 /* Restart Register */
# define CDNS_WDT_SR_OFFSET 0xC /* Status Register */
/*
* Zero Mode Register - This register controls how the time out is indicated
* and also contains the access code to allow writes to the register ( 0xABC ) .
*/
# define CDNS_WDT_ZMR_WDEN_MASK 0x00000001 /* Enable the WDT */
# define CDNS_WDT_ZMR_RSTEN_MASK 0x00000002 /* Enable the reset output */
# define CDNS_WDT_ZMR_IRQEN_MASK 0x00000004 /* Enable IRQ output */
# define CDNS_WDT_ZMR_RSTLEN_16 0x00000030 /* Reset pulse of 16 pclk cycles */
# define CDNS_WDT_ZMR_ZKEY_VAL 0x00ABC000 /* Access key, 0xABC << 12 */
/*
* Counter Control register - This register controls how fast the timer runs
* and the reset value and also contains the access code to allow writes to
* the register .
*/
# define CDNS_WDT_CCR_CRV_MASK 0x00003FFC /* Counter reset value */
/**
* cdns_wdt_stop - Stop the watchdog .
*
* @ wdd : watchdog device
*
* Read the contents of the ZMR register , clear the WDEN bit
* in the register and set the access key for successful write .
*
* Return : always 0
*/
static int cdns_wdt_stop ( struct watchdog_device * wdd )
{
struct cdns_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
spin_lock ( & wdt - > io_lock ) ;
cdns_wdt_writereg ( wdt , CDNS_WDT_ZMR_OFFSET ,
CDNS_WDT_ZMR_ZKEY_VAL & ( ~ CDNS_WDT_ZMR_WDEN_MASK ) ) ;
spin_unlock ( & wdt - > io_lock ) ;
return 0 ;
}
/**
* cdns_wdt_reload - Reload the watchdog timer ( i . e . pat the watchdog ) .
*
* @ wdd : watchdog device
*
* Write the restart key value ( 0x00001999 ) to the restart register .
*
* Return : always 0
*/
static int cdns_wdt_reload ( struct watchdog_device * wdd )
{
struct cdns_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
spin_lock ( & wdt - > io_lock ) ;
cdns_wdt_writereg ( wdt , CDNS_WDT_RESTART_OFFSET ,
CDNS_WDT_RESTART_KEY ) ;
spin_unlock ( & wdt - > io_lock ) ;
return 0 ;
}
/**
* cdns_wdt_start - Enable and start the watchdog .
*
* @ wdd : watchdog device
*
* The counter value is calculated according to the formula :
* calculated count = ( timeout * clock ) / prescaler + 1.
* The calculated count is divided by 0x1000 to obtain the field value
* to write to counter control register .
* Clears the contents of prescaler and counter reset value . Sets the
* prescaler to 4096 and the calculated count and access key
* to write to CCR Register .
* Sets the WDT ( WDEN bit ) and either the Reset signal ( RSTEN bit )
* or Interrupt signal ( IRQEN ) with a specified cycles and the access
* key to write to ZMR Register .
*
* Return : always 0
*/
static int cdns_wdt_start ( struct watchdog_device * wdd )
{
struct cdns_wdt * wdt = watchdog_get_drvdata ( wdd ) ;
unsigned int data = 0 ;
unsigned short count ;
unsigned long clock_f = clk_get_rate ( wdt - > clk ) ;
/*
* Counter value divisor to obtain the value of
* counter reset to be written to control register .
*/
count = ( wdd - > timeout * ( clock_f / wdt - > prescaler ) ) /
CDNS_WDT_COUNTER_VALUE_DIVISOR + 1 ;
if ( count > CDNS_WDT_COUNTER_MAX )
count = CDNS_WDT_COUNTER_MAX ;
spin_lock ( & wdt - > io_lock ) ;
cdns_wdt_writereg ( wdt , CDNS_WDT_ZMR_OFFSET ,
CDNS_WDT_ZMR_ZKEY_VAL ) ;
count = ( count < < 2 ) & CDNS_WDT_CCR_CRV_MASK ;
/* Write counter access key first to be able write to register */
data = count | CDNS_WDT_REGISTER_ACCESS_KEY | wdt - > ctrl_clksel ;
cdns_wdt_writereg ( wdt , CDNS_WDT_CCR_OFFSET , data ) ;
data = CDNS_WDT_ZMR_WDEN_MASK | CDNS_WDT_ZMR_RSTLEN_16 |
CDNS_WDT_ZMR_ZKEY_VAL ;
/* Reset on timeout if specified in device tree. */
if ( wdt - > rst ) {
data | = CDNS_WDT_ZMR_RSTEN_MASK ;
data & = ~ CDNS_WDT_ZMR_IRQEN_MASK ;
} else {
data & = ~ CDNS_WDT_ZMR_RSTEN_MASK ;
data | = CDNS_WDT_ZMR_IRQEN_MASK ;
}
cdns_wdt_writereg ( wdt , CDNS_WDT_ZMR_OFFSET , data ) ;
cdns_wdt_writereg ( wdt , CDNS_WDT_RESTART_OFFSET ,
CDNS_WDT_RESTART_KEY ) ;
spin_unlock ( & wdt - > io_lock ) ;
return 0 ;
}
/**
* cdns_wdt_settimeout - Set a new timeout value for the watchdog device .
*
* @ wdd : watchdog device
* @ new_time : new timeout value that needs to be set
* Return : 0 on success
*
* Update the watchdog_device timeout with new value which is used when
* cdns_wdt_start is called .
*/
static int cdns_wdt_settimeout ( struct watchdog_device * wdd ,
unsigned int new_time )
{
wdd - > timeout = new_time ;
return cdns_wdt_start ( wdd ) ;
}
/**
* cdns_wdt_irq_handler - Notifies of watchdog timeout .
*
* @ irq : interrupt number
* @ dev_id : pointer to a platform device structure
* Return : IRQ_HANDLED
*
* The handler is invoked when the watchdog times out and a
* reset on timeout has not been enabled .
*/
static irqreturn_t cdns_wdt_irq_handler ( int irq , void * dev_id )
{
struct platform_device * pdev = dev_id ;
dev_info ( & pdev - > dev ,
" Watchdog timed out. Internal reset not enabled \n " ) ;
return IRQ_HANDLED ;
}
/*
* Info structure used to indicate the features supported by the device
* to the upper layers . This is defined in watchdog . h header file .
*/
2016-12-26 20:05:11 +03:00
static const struct watchdog_info cdns_wdt_info = {
2014-08-22 13:28:01 +04:00
. identity = " cdns_wdt watchdog " ,
. options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
} ;
/* Watchdog Core Ops */
2016-09-01 20:35:26 +03:00
static const struct watchdog_ops cdns_wdt_ops = {
2014-08-22 13:28:01 +04:00
. owner = THIS_MODULE ,
. start = cdns_wdt_start ,
. stop = cdns_wdt_stop ,
. ping = cdns_wdt_reload ,
. set_timeout = cdns_wdt_settimeout ,
} ;
2019-04-08 22:38:32 +03:00
static void cdns_clk_disable_unprepare ( void * data )
{
clk_disable_unprepare ( data ) ;
}
2014-08-22 13:28:01 +04:00
/************************Platform Operations*****************************/
/**
* cdns_wdt_probe - Probe call for the device .
*
* @ pdev : handle to the platform device structure .
* Return : 0 on success , negative error otherwise .
*
* It does all the memory allocation and registration for the device .
*/
static int cdns_wdt_probe ( struct platform_device * pdev )
{
2019-04-08 22:38:32 +03:00
struct device * dev = & pdev - > dev ;
2014-08-22 13:28:01 +04:00
int ret , irq ;
unsigned long clock_f ;
struct cdns_wdt * wdt ;
struct watchdog_device * cdns_wdt_device ;
2019-04-08 22:38:32 +03:00
wdt = devm_kzalloc ( dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
2014-08-22 13:28:01 +04:00
if ( ! wdt )
return - ENOMEM ;
cdns_wdt_device = & wdt - > cdns_wdt_device ;
cdns_wdt_device - > info = & cdns_wdt_info ;
cdns_wdt_device - > ops = & cdns_wdt_ops ;
cdns_wdt_device - > timeout = CDNS_WDT_DEFAULT_TIMEOUT ;
cdns_wdt_device - > min_timeout = CDNS_WDT_MIN_TIMEOUT ;
cdns_wdt_device - > max_timeout = CDNS_WDT_MAX_TIMEOUT ;
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
wdt - > regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2014-08-22 13:28:01 +04:00
if ( IS_ERR ( wdt - > regs ) )
return PTR_ERR ( wdt - > regs ) ;
/* Register the interrupt */
2019-04-08 22:38:32 +03:00
wdt - > rst = of_property_read_bool ( dev - > of_node , " reset-on-timeout " ) ;
2014-08-22 13:28:01 +04:00
irq = platform_get_irq ( pdev , 0 ) ;
if ( ! wdt - > rst & & irq > = 0 ) {
2019-04-08 22:38:32 +03:00
ret = devm_request_irq ( dev , irq , cdns_wdt_irq_handler , 0 ,
2014-08-22 13:28:01 +04:00
pdev - > name , pdev ) ;
if ( ret ) {
2019-04-08 22:38:32 +03:00
dev_err ( dev ,
2014-08-22 13:28:01 +04:00
" cannot register interrupt handler err=%d \n " ,
ret ) ;
return ret ;
}
}
/* Initialize the members of cdns_wdt structure */
2019-04-08 22:38:32 +03:00
cdns_wdt_device - > parent = dev ;
2014-08-22 13:28:01 +04:00
2019-04-19 21:15:49 +03:00
watchdog_init_timeout ( cdns_wdt_device , wdt_timeout , dev ) ;
2014-08-22 13:28:01 +04:00
watchdog_set_nowayout ( cdns_wdt_device , nowayout ) ;
2015-11-21 00:54:53 +03:00
watchdog_stop_on_reboot ( cdns_wdt_device ) ;
2014-08-22 13:28:01 +04:00
watchdog_set_drvdata ( cdns_wdt_device , wdt ) ;
2019-04-08 22:38:32 +03:00
wdt - > clk = devm_clk_get ( dev , NULL ) ;
2020-09-01 18:31:39 +03:00
if ( IS_ERR ( wdt - > clk ) )
return dev_err_probe ( dev , PTR_ERR ( wdt - > clk ) ,
" input clock not found \n " ) ;
2014-08-22 13:28:01 +04:00
ret = clk_prepare_enable ( wdt - > clk ) ;
if ( ret ) {
2019-04-08 22:38:32 +03:00
dev_err ( dev , " unable to enable clock \n " ) ;
2014-08-22 13:28:01 +04:00
return ret ;
}
2019-04-08 22:38:32 +03:00
ret = devm_add_action_or_reset ( dev , cdns_clk_disable_unprepare ,
wdt - > clk ) ;
if ( ret )
return ret ;
2014-08-22 13:28:01 +04:00
clock_f = clk_get_rate ( wdt - > clk ) ;
if ( clock_f < = CDNS_WDT_CLK_75MHZ ) {
wdt - > prescaler = CDNS_WDT_PRESCALE_512 ;
wdt - > ctrl_clksel = CDNS_WDT_PRESCALE_SELECT_512 ;
} else {
wdt - > prescaler = CDNS_WDT_PRESCALE_4096 ;
wdt - > ctrl_clksel = CDNS_WDT_PRESCALE_SELECT_4096 ;
}
spin_lock_init ( & wdt - > io_lock ) ;
2019-04-08 22:38:32 +03:00
watchdog_stop_on_reboot ( cdns_wdt_device ) ;
watchdog_stop_on_unregister ( cdns_wdt_device ) ;
ret = devm_watchdog_register_device ( dev , cdns_wdt_device ) ;
2019-05-19 00:27:22 +03:00
if ( ret )
2019-04-08 22:38:32 +03:00
return ret ;
2014-08-22 13:28:01 +04:00
platform_set_drvdata ( pdev , wdt ) ;
2019-12-20 09:58:16 +03:00
dev_info ( dev , " Xilinx Watchdog Timer with timeout %ds%s \n " ,
cdns_wdt_device - > timeout , nowayout ? " , nowayout " : " " ) ;
2014-08-22 13:28:01 +04:00
return 0 ;
}
/**
* cdns_wdt_suspend - Stop the device .
*
* @ dev : handle to the device structure .
* Return : 0 always .
*/
static int __maybe_unused cdns_wdt_suspend ( struct device * dev )
{
2018-04-19 17:06:29 +03:00
struct cdns_wdt * wdt = dev_get_drvdata ( dev ) ;
2014-08-22 13:28:01 +04:00
2016-09-12 10:23:49 +03:00
if ( watchdog_active ( & wdt - > cdns_wdt_device ) ) {
cdns_wdt_stop ( & wdt - > cdns_wdt_device ) ;
clk_disable_unprepare ( wdt - > clk ) ;
}
2014-08-22 13:28:01 +04:00
return 0 ;
}
/**
* cdns_wdt_resume - Resume the device .
*
* @ dev : handle to the device structure .
* Return : 0 on success , errno otherwise .
*/
static int __maybe_unused cdns_wdt_resume ( struct device * dev )
{
int ret ;
2018-04-19 17:06:29 +03:00
struct cdns_wdt * wdt = dev_get_drvdata ( dev ) ;
2014-08-22 13:28:01 +04:00
2016-09-12 10:23:49 +03:00
if ( watchdog_active ( & wdt - > cdns_wdt_device ) ) {
ret = clk_prepare_enable ( wdt - > clk ) ;
if ( ret ) {
dev_err ( dev , " unable to enable clock \n " ) ;
return ret ;
}
cdns_wdt_start ( & wdt - > cdns_wdt_device ) ;
2014-08-22 13:28:01 +04:00
}
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( cdns_wdt_pm_ops , cdns_wdt_suspend , cdns_wdt_resume ) ;
2017-06-20 08:34:26 +03:00
static const struct of_device_id cdns_wdt_of_match [ ] = {
2014-08-22 13:28:01 +04:00
{ . compatible = " cdns,wdt-r1p2 " , } ,
{ /* end of table */ }
} ;
MODULE_DEVICE_TABLE ( of , cdns_wdt_of_match ) ;
/* Driver Structure */
static struct platform_driver cdns_wdt_driver = {
. probe = cdns_wdt_probe ,
. driver = {
. name = " cdns-wdt " ,
. of_match_table = cdns_wdt_of_match ,
. pm = & cdns_wdt_pm_ops ,
} ,
} ;
module_platform_driver ( cdns_wdt_driver ) ;
MODULE_AUTHOR ( " Xilinx, Inc. " ) ;
MODULE_DESCRIPTION ( " Watchdog driver for Cadence WDT " ) ;
MODULE_LICENSE ( " GPL " ) ;