From mboxrd@z Thu Jan 1 00:00:00 1970 Return-path: Received: from metis.ext.pengutronix.de ([2001:67c:670:201:290:27ff:fe1d:cc33]) by bombadil.infradead.org with esmtps (Exim 4.90_1 #2 (Red Hat Linux)) id 1hYD9s-0006Np-V6 for barebox@lists.infradead.org; Tue, 04 Jun 2019 17:23:39 +0000 Date: Tue, 4 Jun 2019 19:23:33 +0200 From: Roland Hieber Message-ID: <20190604172333.37hfgytxvbq2morf@pengutronix.de> References: <20190603190559.16715-1-a.fatoum@pengutronix.de> <20190603190559.16715-3-a.fatoum@pengutronix.de> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <20190603190559.16715-3-a.fatoum@pengutronix.de> List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: "barebox" Errors-To: barebox-bounces+u.kleine-koenig=pengutronix.de@lists.infradead.org Subject: Re: [PATCH 3/7] watchdog: add stm32 watchdog and reset driver To: Ahmad Fatoum Cc: barebox@lists.infradead.org On Mon, Jun 03, 2019 at 09:05:55PM +0200, Ahmad Fatoum wrote: > The driver supports setting watchdog timeout, system reset > and querying reset reason. Disabling watchdog isn't possible > in hardware, thus users should either enable it before boot > or have the poller take care of feeding it. > > Signed-off-by: Ahmad Fatoum > --- > .../mach-stm32mp/include/mach/reset-reason.h | 28 ++ > drivers/watchdog/Kconfig | 8 + > drivers/watchdog/Makefile | 1 + > drivers/watchdog/stm32_wdt.c | 288 ++++++++++++++++++ > 4 files changed, 325 insertions(+) > create mode 100644 arch/arm/mach-stm32mp/include/mach/reset-reason.h > create mode 100644 drivers/watchdog/stm32_wdt.c > > diff --git a/arch/arm/mach-stm32mp/include/mach/reset-reason.h b/arch/arm/mach-stm32mp/include/mach/reset-reason.h > new file mode 100644 > index 000000000000..1165b347c31f > --- /dev/null > +++ b/arch/arm/mach-stm32mp/include/mach/reset-reason.h > @@ -0,0 +1,28 @@ > +#ifndef __MACH_RESET_REASON_H__ > +#define __MACH_RESET_REASON_H__ > + > +#include > + > +#define RCC_RSTF_POR BIT(0) > +#define RCC_RSTF_BOR BIT(1) > +#define RCC_RSTF_PAD BIT(2) > +#define RCC_RSTF_HCSS BIT(3) > +#define RCC_RSTF_VCORE BIT(4) > + > +#define RCC_RSTF_MPSYS BIT(6) > +#define RCC_RSTF_MCSYS BIT(7) > +#define RCC_RSTF_IWDG1 BIT(8) > +#define RCC_RSTF_IWDG2 BIT(9) > + > +#define RCC_RSTF_STDBY BIT(11) > +#define RCC_RSTF_CSTDBY BIT(12) > +#define RCC_RSTF_MPUP0 BIT(13) > +#define RCC_RSTF_MPUP1 BIT(14) > + > +struct stm32_reset_reason { > + uint32_t mask; > + enum reset_src_type type; > + int instance; > +}; > + > +#endif > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig > index 04efb1a3c866..5a28b530099d 100644 > --- a/drivers/watchdog/Kconfig > +++ b/drivers/watchdog/Kconfig > @@ -81,4 +81,12 @@ config RAVE_SP_WATCHDOG > depends on RAVE_SP_CORE > help > Support for the watchdog on RAVE SP device. > + > +config STM32_IWDG_WATCHDOG > + bool "Enable IWDG watchdog driver for STM32 processors" "STM32 IWDG" should be enough, the other options in that menu are also very short. - Roland > + depends on ARCH_STM32MP > + select MFD_SYSCON > + help > + Enable the STM32 watchdog (IWDG) driver. Enable support to > + configure STM32's on-SoC watchdog. > endif > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile > index 6c8d36c8b805..b2f39fa3719e 100644 > --- a/drivers/watchdog/Makefile > +++ b/drivers/watchdog/Makefile > @@ -11,3 +11,4 @@ obj-$(CONFIG_WATCHDOG_IMX) += imxwd.o > obj-$(CONFIG_WATCHDOG_ORION) += orion_wdt.o > obj-$(CONFIG_ARCH_BCM283X) += bcm2835_wdt.o > obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o > +obj-$(CONFIG_STM32_IWDG_WATCHDOG) += stm32_wdt.o > diff --git a/drivers/watchdog/stm32_wdt.c b/drivers/watchdog/stm32_wdt.c > new file mode 100644 > index 000000000000..a972aade5900 > --- /dev/null > +++ b/drivers/watchdog/stm32_wdt.c > @@ -0,0 +1,288 @@ > +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause > +/* > + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* 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 */ > + > +/* 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 */ > + > +/* IWDG_PR register bit values */ > +#define PR_SHIFT 2 > + > +/* IWDG_RLR register values */ > +#define RLR_MAX GENMASK(11, 0) > + > +/* IWDG_SR register bit mask */ > +#define SR_PVU BIT(0) /* Watchdog prescaler value update */ > +#define SR_RVU BIT(1) /* Watchdog counter reload value update */ > + > +#define RCC_MP_GRSTCSETR 0x404 > +#define RCC_MP_RSTSCLRR 0x408 > +#define RCC_MP_GRSTCSETR_MPSYSRST BIT(0) > + > +/* set timeout to 100 ms */ > +#define TIMEOUT_US 100000 > + > +struct stm32_iwdg { > + struct watchdog wdd; > + struct restart_handler restart; > + void __iomem *iwdg_base; > + struct regmap *rcc_regmap; > + unsigned int timeout; > + unsigned int rate; > +}; > + > +static inline struct stm32_iwdg *to_stm32_iwdg(struct watchdog *wdd) > +{ > + return container_of(wdd, struct stm32_iwdg, wdd); > +} > + > +static void __noreturn stm32_iwdg_restart_handler(struct restart_handler *rst) > +{ > + struct stm32_iwdg *wd = container_of(rst, struct stm32_iwdg, restart); > + > + regmap_update_bits(wd->rcc_regmap, RCC_MP_GRSTCSETR, > + RCC_MP_GRSTCSETR_MPSYSRST, RCC_MP_GRSTCSETR_MPSYSRST); > + > + mdelay(1000); > + hang(); > +} > + > +static void stm32_iwdg_ping(struct stm32_iwdg *wd) > +{ > + writel(KR_KEY_RELOAD, wd->iwdg_base + IWDG_KR); > +} > + > +static int stm32_iwdg_start(struct stm32_iwdg *wd, unsigned int timeout) > +{ > + u32 presc, iwdg_rlr, iwdg_pr, iwdg_sr; > + int ret; > + > + presc = DIV_ROUND_UP(timeout * wd->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 = ((timeout * wd->rate) / presc) - 1; > + > + /* enable write access */ > + writel(KR_KEY_EWA, wd->iwdg_base + IWDG_KR); > + > + /* set prescaler & reload registers */ > + writel(iwdg_pr, wd->iwdg_base + IWDG_PR); > + writel(iwdg_rlr, wd->iwdg_base + IWDG_RLR); > + writel(KR_KEY_ENABLE, wd->iwdg_base + IWDG_KR); > + > + /* wait for the registers to be updated (max 100ms) */ > + ret = readl_poll_timeout(wd->iwdg_base + IWDG_SR, iwdg_sr, > + !(iwdg_sr & (SR_PVU | SR_RVU)), > + TIMEOUT_US); > + if (!ret) > + wd->timeout = timeout; > + > + return ret; > +} > + > + > +static int stm32_iwdg_set_timeout(struct watchdog *wdd, unsigned int timeout) > +{ > + struct stm32_iwdg *wd = to_stm32_iwdg(wdd); > + int ret; > + > + if (!timeout) > + return -EINVAL; /* can't disable */ > + > + if (timeout > wdd->timeout_max) > + return -EINVAL; > + > + if (wd->timeout != timeout) { > + ret = stm32_iwdg_start(wd, timeout); > + if (ret) { > + dev_err(wdd->hwdev, "Fail to (re)start watchdog\n"); > + return ret; > + } > + } > + > + stm32_iwdg_ping(wd); > + return 0; > +} > + > +static const struct stm32_reset_reason stm32_reset_reasons[] = { > + { RCC_RSTF_POR, RESET_POR, 0 }, > + { RCC_RSTF_BOR, RESET_BOR, 0 }, > + { RCC_RSTF_STDBY, RESET_WKE, 0 }, > + { RCC_RSTF_CSTDBY, RESET_WKE, 1 }, > + { RCC_RSTF_MPSYS, RESET_RST, 2 }, > + { RCC_RSTF_MPUP0, RESET_RST, 0 }, > + { RCC_RSTF_MPUP1, RESET_RST, 1 }, > + { RCC_RSTF_IWDG1, RESET_WDG, 0 }, > + { RCC_RSTF_IWDG2, RESET_WDG, 1 }, > + { RCC_RSTF_PAD, RESET_EXT, 1 }, > + { /* sentinel */ } > +}; > + > +static int stm32_set_reset_reason(struct regmap *rcc) > +{ > + enum reset_src_type type = RESET_UKWN; > + u32 reg; > + int ret; > + int i, instance = 0; > + > + /* > + * SRSR register captures ALL reset event that occured since > + * POR, so we need to clear it to make sure we only caputre > + * the latest one. > + */ > + ret = regmap_read(rcc, RCC_MP_RSTSCLRR, ®); > + if (ret) > + return ret; > + > + for (i = 0; stm32_reset_reasons[i].mask; i++) { > + if (reg & stm32_reset_reasons[i].mask) { > + type = stm32_reset_reasons[i].type; > + instance = stm32_reset_reasons[i].instance; > + break; > + } > + } > + > + reset_source_set_priority(type, RESET_SOURCE_DEFAULT_PRIORITY); > + reset_source_set_instance(type, instance); > + > + pr_info("STM32 RCC reset reason %s (MP_RSTSR: 0x%08x)\n", > + reset_source_name(), reg); > + > + return 0; > +} > + > +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, > +}; > + > +static const struct of_device_id stm32_iwdg_of_match[] = { > + { .compatible = "st,stm32-iwdg", .data = &stm32_iwdg_data }, > + { .compatible = "st,stm32mp1-iwdg", .data = &stm32mp1_iwdg_data }, > + { /* sentinel */ } > +}; > + > +static int stm32_iwdg_probe(struct device_d *dev) > +{ > + struct stm32_iwdg_data *data; > + struct stm32_iwdg *wd; > + struct resource *res; > + struct watchdog *wdd; > + struct clk *clk; > + int ret; > + > + wd = xzalloc(sizeof(*wd)); > + > + ret = dev_get_drvdata(dev, (const void **)&data); > + if (ret) > + return -ENODEV; > + > + res = dev_request_mem_resource(dev, 0); > + if (IS_ERR(res)) { > + dev_err(dev, "could not get timer memory region\n"); > + return PTR_ERR(res); > + } > + wd->iwdg_base = IOMEM(res->start); > + > + clk = of_clk_get_by_name(dev->device_node, "lsi"); > + if (IS_ERR(clk)) > + return PTR_ERR(clk); > + > + ret = clk_enable(clk); > + if (ret) > + return ret; > + > + wd->rate = clk_get_rate(clk); > + > + if (data->has_pclk) { > + clk = of_clk_get_by_name(dev->device_node, "pclk"); > + if (IS_ERR(clk)) > + return PTR_ERR(clk); > + > + ret = clk_enable(clk); > + if (ret) > + return ret; > + } > + > + wdd = &wd->wdd; > + wdd->hwdev = dev; > + wdd->set_timeout = stm32_iwdg_set_timeout; > + wdd->timeout_max = (RLR_MAX + 1) * data->max_prescaler * 1000; > + wdd->timeout_max /= wd->rate * 1000; > + wdd->timeout_cur = wdd->timeout_max; > + > + ret = stm32_iwdg_set_timeout(wdd, wdd->timeout_max); > + if (ret) { > + dev_err(dev, "Failed to set initial watchdog timeout\n"); > + return ret; > + } > + > + ret = watchdog_register(wdd); > + if (ret) { > + dev_err(dev, "Failed to register watchdog device\n"); > + return ret; > + } > + > + wd->restart.name = "stm32-iwdg"; > + wd->restart.restart = stm32_iwdg_restart_handler; > + wd->restart.priority = 200; > + > + wd->rcc_regmap = syscon_regmap_lookup_by_compatible("st,stm32mp1-rcc"); > + if (IS_ERR(wd->rcc_regmap)) { > + dev_warn(dev, "Cannot register restart handler\n"); > + goto end; > + } > + > + ret = restart_handler_register(&wd->restart); > + if (ret) { > + dev_warn(dev, "Cannot register restart handler\n"); > + goto end; > + } > + > + ret = stm32_set_reset_reason(wd->rcc_regmap); > + if (ret) { > + dev_warn(dev, "Cannot determine reset reason\n"); > + goto end; > + } > +end: > + dev_info(dev, "probed\n"); > + return 0; > +} > + > +static struct driver_d stm32_iwdg_driver = { > + .name = "stm32-iwdg", > + .probe = stm32_iwdg_probe, > + .of_compatible = DRV_OF_COMPAT(stm32_iwdg_of_match), > +}; > +device_platform_driver(stm32_iwdg_driver); > -- > 2.20.1 > > > _______________________________________________ > barebox mailing list > barebox@lists.infradead.org > http://lists.infradead.org/mailman/listinfo/barebox > -- Roland Hieber | r.hieber@pengutronix.de | Pengutronix e.K. | https://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim | Phone: +49-5121-206917-5086 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 | _______________________________________________ barebox mailing list barebox@lists.infradead.org http://lists.infradead.org/mailman/listinfo/barebox