2017-11-30 11:48:04 +03:00
// SPDX-License-Identifier: GPL-2.0
2017-04-06 15:19:25 +03:00
/*
* Driver for STM32 Independent Watchdog
*
2017-11-30 11:48:04 +03:00
* Copyright ( C ) STMicroelectronics 2017
* Author : Yannick Fertre < yannick . fertre @ st . com > for STMicroelectronics .
2017-04-06 15:19:25 +03:00
*
* This driver is based on tegra_wdt . c
*
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/iopoll.h>
2018-06-25 18:43:00 +03:00
# include <linux/kernel.h>
# include <linux/module.h>
2017-04-06 15:19:25 +03:00
# include <linux/of.h>
2018-06-25 18:43:00 +03:00
# include <linux/of_device.h>
2017-04-06 15:19:25 +03:00
# include <linux/platform_device.h>
# include <linux/watchdog.h>
/* IWDG registers */
# define IWDG_KR 0x00 /* Key register */
# define IWDG_PR 0x04 /* Prescaler Register */
# define IWDG_RLR 0x08 /* ReLoad Register */
# define IWDG_SR 0x0C /* Status Register */
# define IWDG_WINR 0x10 /* Windows Register */
/* IWDG_KR register bit mask */
# define KR_KEY_RELOAD 0xAAAA /* reload counter enable */
# define KR_KEY_ENABLE 0xCCCC /* peripheral enable */
# define KR_KEY_EWA 0x5555 /* write access enable */
# define KR_KEY_DWA 0x0000 /* write access disable */
2019-05-03 16:48:26 +03:00
/* IWDG_PR register */
# define PR_SHIFT 2
# define PR_MIN BIT(PR_SHIFT)
2017-04-06 15:19:25 +03:00
/* IWDG_RLR register values */
2019-05-03 16:48:26 +03:00
# define RLR_MIN 0x2 /* min value recommended */
# define RLR_MAX GENMASK(11, 0) /* max value of reload register */
2017-04-06 15:19:25 +03:00
/* IWDG_SR register bit mask */
2019-05-03 16:48:26 +03:00
# define SR_PVU BIT(0) /* Watchdog prescaler value update */
# define SR_RVU BIT(1) /* Watchdog counter reload value update */
2017-04-06 15:19:25 +03:00
/* set timeout to 100000 us */
# define TIMEOUT_US 100000
# define SLEEP_US 1000
2019-05-03 16:48:26 +03:00
struct stm32_iwdg_data {
bool has_pclk ;
u32 max_prescaler ;
} ;
static const struct stm32_iwdg_data stm32_iwdg_data = {
. has_pclk = false ,
. max_prescaler = 256 ,
} ;
static const struct stm32_iwdg_data stm32mp1_iwdg_data = {
. has_pclk = true ,
. max_prescaler = 1024 ,
} ;
2018-06-25 18:43:00 +03:00
2017-04-06 15:19:25 +03:00
struct stm32_iwdg {
struct watchdog_device wdd ;
2019-05-03 16:48:26 +03:00
const struct stm32_iwdg_data * data ;
2017-04-06 15:19:25 +03:00
void __iomem * regs ;
2018-06-25 18:43:00 +03:00
struct clk * clk_lsi ;
struct clk * clk_pclk ;
2017-04-06 15:19:25 +03:00
unsigned int rate ;
} ;
static inline u32 reg_read ( void __iomem * base , u32 reg )
{
return readl_relaxed ( base + reg ) ;
}
static inline void reg_write ( void __iomem * base , u32 reg , u32 val )
{
writel_relaxed ( val , base + reg ) ;
}
static int stm32_iwdg_start ( struct watchdog_device * wdd )
{
struct stm32_iwdg * wdt = watchdog_get_drvdata ( wdd ) ;
2019-05-03 16:48:26 +03:00
u32 tout , presc , iwdg_rlr , iwdg_pr , iwdg_sr ;
2017-04-06 15:19:25 +03:00
int ret ;
dev_dbg ( wdd - > parent , " %s \n " , __func__ ) ;
2019-05-03 16:48:26 +03:00
tout = clamp_t ( unsigned int , wdd - > timeout ,
wdd - > min_timeout , wdd - > max_hw_heartbeat_ms / 1000 ) ;
presc = DIV_ROUND_UP ( tout * wdt - > rate , RLR_MAX + 1 ) ;
/* The prescaler is align on power of 2 and start at 2 ^ PR_SHIFT. */
presc = roundup_pow_of_two ( presc ) ;
iwdg_pr = presc < = 1 < < PR_SHIFT ? 0 : ilog2 ( presc ) - PR_SHIFT ;
iwdg_rlr = ( ( tout * wdt - > rate ) / presc ) - 1 ;
2017-04-06 15:19:25 +03:00
/* enable write access */
reg_write ( wdt - > regs , IWDG_KR , KR_KEY_EWA ) ;
/* set prescaler & reload registers */
2019-05-03 16:48:26 +03:00
reg_write ( wdt - > regs , IWDG_PR , iwdg_pr ) ;
reg_write ( wdt - > regs , IWDG_RLR , iwdg_rlr ) ;
2017-04-06 15:19:25 +03:00
reg_write ( wdt - > regs , IWDG_KR , KR_KEY_ENABLE ) ;
/* wait for the registers to be updated (max 100ms) */
2019-05-03 16:48:26 +03:00
ret = readl_relaxed_poll_timeout ( wdt - > regs + IWDG_SR , iwdg_sr ,
! ( iwdg_sr & ( SR_PVU | SR_RVU ) ) ,
2017-04-06 15:19:25 +03:00
SLEEP_US , TIMEOUT_US ) ;
if ( ret ) {
2019-05-03 16:48:26 +03:00
dev_err ( wdd - > parent , " Fail to set prescaler, reload regs \n " ) ;
2017-04-06 15:19:25 +03:00
return ret ;
}
/* reload watchdog */
reg_write ( wdt - > regs , IWDG_KR , KR_KEY_RELOAD ) ;
return 0 ;
}
static int stm32_iwdg_ping ( struct watchdog_device * wdd )
{
struct stm32_iwdg * wdt = watchdog_get_drvdata ( wdd ) ;
dev_dbg ( wdd - > parent , " %s \n " , __func__ ) ;
/* reload watchdog */
reg_write ( wdt - > regs , IWDG_KR , KR_KEY_RELOAD ) ;
return 0 ;
}
static int stm32_iwdg_set_timeout ( struct watchdog_device * wdd ,
unsigned int timeout )
{
dev_dbg ( wdd - > parent , " %s timeout: %d sec \n " , __func__ , timeout ) ;
wdd - > timeout = timeout ;
if ( watchdog_active ( wdd ) )
return stm32_iwdg_start ( wdd ) ;
return 0 ;
}
2019-04-10 19:27:54 +03:00
static void stm32_clk_disable_unprepare ( void * data )
{
clk_disable_unprepare ( data ) ;
}
2018-06-25 18:43:00 +03:00
static int stm32_iwdg_clk_init ( struct platform_device * pdev ,
struct stm32_iwdg * wdt )
{
2019-04-10 19:27:54 +03:00
struct device * dev = & pdev - > dev ;
2018-06-25 18:43:00 +03:00
u32 ret ;
2019-04-10 19:27:54 +03:00
wdt - > clk_lsi = devm_clk_get ( dev , " lsi " ) ;
2020-11-06 17:23:27 +03:00
if ( IS_ERR ( wdt - > clk_lsi ) )
return dev_err_probe ( dev , PTR_ERR ( wdt - > clk_lsi ) , " Unable to get lsi clock \n " ) ;
2018-06-25 18:43:00 +03:00
/* optional peripheral clock */
2019-05-03 16:48:26 +03:00
if ( wdt - > data - > has_pclk ) {
2019-04-10 19:27:54 +03:00
wdt - > clk_pclk = devm_clk_get ( dev , " pclk " ) ;
2020-11-06 17:23:27 +03:00
if ( IS_ERR ( wdt - > clk_pclk ) )
return dev_err_probe ( dev , PTR_ERR ( wdt - > clk_pclk ) ,
" Unable to get pclk clock \n " ) ;
2018-06-25 18:43:00 +03:00
ret = clk_prepare_enable ( wdt - > clk_pclk ) ;
if ( ret ) {
2019-04-10 19:27:54 +03:00
dev_err ( dev , " Unable to prepare pclk clock \n " ) ;
2018-06-25 18:43:00 +03:00
return ret ;
}
2019-04-10 19:27:54 +03:00
ret = devm_add_action_or_reset ( dev ,
stm32_clk_disable_unprepare ,
wdt - > clk_pclk ) ;
if ( ret )
return ret ;
2018-06-25 18:43:00 +03:00
}
ret = clk_prepare_enable ( wdt - > clk_lsi ) ;
if ( ret ) {
2019-04-10 19:27:54 +03:00
dev_err ( dev , " Unable to prepare lsi clock \n " ) ;
2018-06-25 18:43:00 +03:00
return ret ;
}
2019-04-10 19:27:54 +03:00
ret = devm_add_action_or_reset ( dev , stm32_clk_disable_unprepare ,
wdt - > clk_lsi ) ;
if ( ret )
return ret ;
2018-06-25 18:43:00 +03:00
wdt - > rate = clk_get_rate ( wdt - > clk_lsi ) ;
return 0 ;
}
2017-04-06 15:19:25 +03:00
static const struct watchdog_info stm32_iwdg_info = {
. options = WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING ,
. identity = " STM32 Independent Watchdog " ,
} ;
2017-07-08 03:28:57 +03:00
static const struct watchdog_ops stm32_iwdg_ops = {
2017-04-06 15:19:25 +03:00
. owner = THIS_MODULE ,
. start = stm32_iwdg_start ,
. ping = stm32_iwdg_ping ,
. set_timeout = stm32_iwdg_set_timeout ,
} ;
2018-06-25 18:43:00 +03:00
static const struct of_device_id stm32_iwdg_of_match [ ] = {
2019-05-03 16:48:26 +03:00
{ . compatible = " st,stm32-iwdg " , . data = & stm32_iwdg_data } ,
{ . compatible = " st,stm32mp1-iwdg " , . data = & stm32mp1_iwdg_data } ,
2018-06-25 18:43:00 +03:00
{ /* end node */ }
} ;
MODULE_DEVICE_TABLE ( of , stm32_iwdg_of_match ) ;
2017-04-06 15:19:25 +03:00
static int stm32_iwdg_probe ( struct platform_device * pdev )
{
2019-04-10 19:27:54 +03:00
struct device * dev = & pdev - > dev ;
2017-04-06 15:19:25 +03:00
struct watchdog_device * wdd ;
struct stm32_iwdg * wdt ;
int ret ;
2019-04-10 19:27:54 +03:00
wdt = devm_kzalloc ( dev , sizeof ( * wdt ) , GFP_KERNEL ) ;
2018-06-25 18:43:00 +03:00
if ( ! wdt )
return - ENOMEM ;
2019-05-03 16:48:26 +03:00
wdt - > data = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! wdt - > data )
return - ENODEV ;
2018-06-25 18:43:00 +03:00
2017-04-06 15:19:25 +03:00
/* This is the timer base. */
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 ) ;
2021-08-14 17:27:41 +03:00
if ( IS_ERR ( wdt - > regs ) )
2018-06-25 18:43:00 +03:00
return PTR_ERR ( wdt - > regs ) ;
2017-04-06 15:19:25 +03:00
2018-06-25 18:43:00 +03:00
ret = stm32_iwdg_clk_init ( pdev , wdt ) ;
if ( ret )
2017-04-06 15:19:25 +03:00
return ret ;
/* Initialize struct watchdog_device. */
wdd = & wdt - > wdd ;
2019-05-03 16:48:26 +03:00
wdd - > parent = dev ;
2017-04-06 15:19:25 +03:00
wdd - > info = & stm32_iwdg_info ;
wdd - > ops = & stm32_iwdg_ops ;
2019-05-03 16:48:26 +03:00
wdd - > min_timeout = DIV_ROUND_UP ( ( RLR_MIN + 1 ) * PR_MIN , wdt - > rate ) ;
wdd - > max_hw_heartbeat_ms = ( ( RLR_MAX + 1 ) * wdt - > data - > max_prescaler *
1000 ) / wdt - > rate ;
2017-04-06 15:19:25 +03:00
watchdog_set_drvdata ( wdd , wdt ) ;
watchdog_set_nowayout ( wdd , WATCHDOG_NOWAYOUT ) ;
2019-04-19 21:15:59 +03:00
watchdog_init_timeout ( wdd , 0 , dev ) ;
2017-04-06 15:19:25 +03:00
2019-11-22 16:22:46 +03:00
/*
* In case of CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED is set
* ( Means U - Boot / bootloaders leaves the watchdog running )
* When we get here we should make a decision to prevent
* any side effects before user space daemon will take care of it .
* The best option , taking into consideration that there is no
* way to read values back from hardware , is to enforce watchdog
* being run with deterministic values .
*/
if ( IS_ENABLED ( CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED ) ) {
ret = stm32_iwdg_start ( wdd ) ;
if ( ret )
return ret ;
/* Make sure the watchdog is serviced */
set_bit ( WDOG_HW_RUNNING , & wdd - > status ) ;
}
2019-04-10 19:27:54 +03:00
ret = devm_watchdog_register_device ( dev , wdd ) ;
2019-05-19 00:27:56 +03:00
if ( ret )
2019-04-10 19:27:54 +03:00
return ret ;
2017-04-06 15:19:25 +03:00
platform_set_drvdata ( pdev , wdt ) ;
return 0 ;
}
static struct platform_driver stm32_iwdg_driver = {
. probe = stm32_iwdg_probe ,
. driver = {
. name = " iwdg " ,
2018-06-25 18:43:00 +03:00
. of_match_table = of_match_ptr ( stm32_iwdg_of_match ) ,
2017-04-06 15:19:25 +03:00
} ,
} ;
module_platform_driver ( stm32_iwdg_driver ) ;
MODULE_AUTHOR ( " Yannick Fertre <yannick.fertre@st.com> " ) ;
MODULE_DESCRIPTION ( " STMicroelectronics STM32 Independent Watchdog Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;