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 <a.fat...@pengutronix.de>
---
 .../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 <reset_source.h>
+
+#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"
+       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 <common.h>
+#include <init.h>
+#include <watchdog.h>
+#include <restart.h>
+#include <asm/io.h>
+#include <of_device.h>
+#include <linux/log2.h>
+#include <linux/iopoll.h>
+#include <linux/clk.h>
+#include <mfd/syscon.h>
+#include <mach/reset-reason.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 */
+
+/* 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, &reg);
+       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

Reply via email to