362 lines
11 KiB
Diff
362 lines
11 KiB
Diff
From 918e6c75e00bcae7481c4560ebbca237d991b2c0 Mon Sep 17 00:00:00 2001
|
|
From: Lionel VITTE <lionel.vitte@st.com>
|
|
Date: Fri, 8 Nov 2019 16:52:48 +0100
|
|
Subject: [PATCH 27/31] ARM stm32mp1 r3 WATCHDOG
|
|
|
|
---
|
|
drivers/watchdog/Kconfig | 12 ++++
|
|
drivers/watchdog/Makefile | 1 +
|
|
drivers/watchdog/stm32_iwdg.c | 83 ++++++++++++++++--------
|
|
drivers/watchdog/stpmic1_wdt.c | 140 +++++++++++++++++++++++++++++++++++++++++
|
|
4 files changed, 211 insertions(+), 25 deletions(-)
|
|
create mode 100644 drivers/watchdog/stpmic1_wdt.c
|
|
|
|
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
|
|
index b165c46..9790eca 100644
|
|
--- a/drivers/watchdog/Kconfig
|
|
+++ b/drivers/watchdog/Kconfig
|
|
@@ -806,6 +806,18 @@ config STM32_WATCHDOG
|
|
To compile this driver as a module, choose M here: the
|
|
module will be called stm32_iwdg.
|
|
|
|
+config STPMIC1_WATCHDOG
|
|
+ tristate "STPMIC1 PMIC watchdog support"
|
|
+ depends on MFD_STPMIC1
|
|
+ select WATCHDOG_CORE
|
|
+ help
|
|
+ Say Y here to include watchdog support embedded into STPMIC1 PMIC.
|
|
+ If the watchdog timer expires, stpmic1 will shut down all its power
|
|
+ supplies.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called spmic1_wdt.
|
|
+
|
|
config UNIPHIER_WATCHDOG
|
|
tristate "UniPhier watchdog support"
|
|
depends on ARCH_UNIPHIER || COMPILE_TEST
|
|
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
|
|
index bf92e7b..2649cf3 100644
|
|
--- a/drivers/watchdog/Makefile
|
|
+++ b/drivers/watchdog/Makefile
|
|
@@ -217,3 +217,4 @@ obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
|
|
obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
|
|
obj-$(CONFIG_MENZ069_WATCHDOG) += menz69_wdt.o
|
|
obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
|
|
+obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o
|
|
diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c
|
|
index e00e3b3..56a3ffd 100644
|
|
--- a/drivers/watchdog/stm32_iwdg.c
|
|
+++ b/drivers/watchdog/stm32_iwdg.c
|
|
@@ -34,18 +34,10 @@
|
|
#define KR_KEY_EWA 0x5555 /* write access enable */
|
|
#define KR_KEY_DWA 0x0000 /* write access disable */
|
|
|
|
-/* IWDG_PR register bit values */
|
|
-#define PR_4 0x00 /* prescaler set to 4 */
|
|
-#define PR_8 0x01 /* prescaler set to 8 */
|
|
-#define PR_16 0x02 /* prescaler set to 16 */
|
|
-#define PR_32 0x03 /* prescaler set to 32 */
|
|
-#define PR_64 0x04 /* prescaler set to 64 */
|
|
-#define PR_128 0x05 /* prescaler set to 128 */
|
|
-#define PR_256 0x06 /* prescaler set to 256 */
|
|
+#define PR_SHIFT 2
|
|
|
|
/* IWDG_RLR register values */
|
|
-#define RLR_MIN 0x07C /* min value supported by reload register */
|
|
-#define RLR_MAX 0xFFF /* max value supported by reload register */
|
|
+#define RLR_MAX GENMASK(11, 0) /* max value of reload register */
|
|
|
|
/* IWDG_SR register bit mask */
|
|
#define FLAG_PVU BIT(0) /* Watchdog prescaler value update */
|
|
@@ -55,15 +47,28 @@
|
|
#define TIMEOUT_US 100000
|
|
#define SLEEP_US 1000
|
|
|
|
-#define HAS_PCLK true
|
|
+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,
|
|
+};
|
|
|
|
struct stm32_iwdg {
|
|
struct watchdog_device wdd;
|
|
+ const struct stm32_iwdg_data *data;
|
|
void __iomem *regs;
|
|
struct clk *clk_lsi;
|
|
struct clk *clk_pclk;
|
|
unsigned int rate;
|
|
- bool has_pclk;
|
|
};
|
|
|
|
static inline u32 reg_read(void __iomem *base, u32 reg)
|
|
@@ -80,21 +85,30 @@ static int stm32_iwdg_start(struct watchdog_device *wdd)
|
|
{
|
|
struct stm32_iwdg *wdt = watchdog_get_drvdata(wdd);
|
|
u32 val = FLAG_PVU | FLAG_RVU;
|
|
- u32 reload;
|
|
+ u32 timeout, presc, iwdg_rlr, iwdg_pr;
|
|
int ret;
|
|
|
|
dev_dbg(wdd->parent, "%s\n", __func__);
|
|
|
|
- /* prescaler fixed to 256 */
|
|
- reload = clamp_t(unsigned int, ((wdd->timeout * wdt->rate) / 256) - 1,
|
|
- RLR_MIN, RLR_MAX);
|
|
+ timeout = clamp_t(unsigned int, wdd->timeout,
|
|
+ wdd->min_timeout, wdd->max_hw_heartbeat_ms / 1000);
|
|
+
|
|
+ if (timeout != wdd->timeout)
|
|
+ dev_warn(wdd->parent, "timeout skrinked to %d\n", timeout);
|
|
+
|
|
+ presc = DIV_ROUND_UP(timeout * 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 = ((timeout * wdt->rate) / presc) - 1;
|
|
|
|
/* enable write access */
|
|
reg_write(wdt->regs, IWDG_KR, KR_KEY_EWA);
|
|
|
|
/* set prescaler & reload registers */
|
|
- reg_write(wdt->regs, IWDG_PR, PR_256); /* prescaler fix to 256 */
|
|
- reg_write(wdt->regs, IWDG_RLR, reload);
|
|
+ reg_write(wdt->regs, IWDG_PR, iwdg_pr);
|
|
+ reg_write(wdt->regs, IWDG_RLR, iwdg_rlr);
|
|
reg_write(wdt->regs, IWDG_KR, KR_KEY_ENABLE);
|
|
|
|
/* wait for the registers to be updated (max 100ms) */
|
|
@@ -150,7 +164,7 @@ static int stm32_iwdg_clk_init(struct platform_device *pdev,
|
|
}
|
|
|
|
/* optional peripheral clock */
|
|
- if (wdt->has_pclk) {
|
|
+ if (wdt->data->has_pclk) {
|
|
wdt->clk_pclk = devm_clk_get(&pdev->dev, "pclk");
|
|
if (IS_ERR(wdt->clk_pclk)) {
|
|
dev_err(&pdev->dev, "Unable to get pclk clock\n");
|
|
@@ -191,8 +205,8 @@ static const struct watchdog_ops stm32_iwdg_ops = {
|
|
};
|
|
|
|
static const struct of_device_id stm32_iwdg_of_match[] = {
|
|
- { .compatible = "st,stm32-iwdg", .data = (void *)!HAS_PCLK },
|
|
- { .compatible = "st,stm32mp1-iwdg", .data = (void *)HAS_PCLK },
|
|
+ { .compatible = "st,stm32-iwdg", .data = &stm32_iwdg_data },
|
|
+ { .compatible = "st,stm32mp1-iwdg", .data = &stm32mp1_iwdg_data },
|
|
{ /* end node */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, stm32_iwdg_of_match);
|
|
@@ -206,14 +220,14 @@ static int stm32_iwdg_probe(struct platform_device *pdev)
|
|
int ret;
|
|
|
|
match = of_match_device(stm32_iwdg_of_match, &pdev->dev);
|
|
- if (!match)
|
|
+ if (!match || !match->data)
|
|
return -ENODEV;
|
|
|
|
wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
|
|
if (!wdt)
|
|
return -ENOMEM;
|
|
|
|
- wdt->has_pclk = match->data;
|
|
+ wdt->data = match->data;
|
|
|
|
/* This is the timer base. */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
@@ -231,8 +245,9 @@ static int stm32_iwdg_probe(struct platform_device *pdev)
|
|
wdd = &wdt->wdd;
|
|
wdd->info = &stm32_iwdg_info;
|
|
wdd->ops = &stm32_iwdg_ops;
|
|
- wdd->min_timeout = ((RLR_MIN + 1) * 256) / wdt->rate;
|
|
- wdd->max_hw_heartbeat_ms = ((RLR_MAX + 1) * 256 * 1000) / wdt->rate;
|
|
+ wdd->min_timeout = 1;
|
|
+ wdd->max_hw_heartbeat_ms = ((RLR_MAX + 1) * wdt->data->max_prescaler *
|
|
+ 1000) / wdt->rate;
|
|
wdd->parent = &pdev->dev;
|
|
|
|
watchdog_set_drvdata(wdd, wdt);
|
|
@@ -243,6 +258,24 @@ static int stm32_iwdg_probe(struct platform_device *pdev)
|
|
dev_warn(&pdev->dev,
|
|
"unable to set timeout value, using default\n");
|
|
|
|
+ /*
|
|
+ * 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);
|
|
+ }
|
|
+
|
|
ret = watchdog_register_device(wdd);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to register watchdog device\n");
|
|
diff --git a/drivers/watchdog/stpmic1_wdt.c b/drivers/watchdog/stpmic1_wdt.c
|
|
new file mode 100644
|
|
index 0000000..45d0c54
|
|
--- /dev/null
|
|
+++ b/drivers/watchdog/stpmic1_wdt.c
|
|
@@ -0,0 +1,140 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+// Copyright (C) STMicroelectronics 2018
|
|
+// Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics.
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/mfd/stpmic1.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/watchdog.h>
|
|
+
|
|
+/* WATCHDOG CONTROL REGISTER bit */
|
|
+#define WDT_START BIT(0)
|
|
+#define WDT_PING BIT(1)
|
|
+#define WDT_START_MASK BIT(0)
|
|
+#define WDT_PING_MASK BIT(1)
|
|
+#define WDT_STOP 0
|
|
+
|
|
+#define PMIC_WDT_MIN_TIMEOUT 1
|
|
+#define PMIC_WDT_MAX_TIMEOUT 256
|
|
+#define PMIC_WDT_DEFAULT_TIMEOUT 30
|
|
+
|
|
+static bool nowayout = WATCHDOG_NOWAYOUT;
|
|
+module_param(nowayout, bool, 0);
|
|
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
|
|
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
|
+
|
|
+struct stpmic1_wdt {
|
|
+ struct stpmic1 *pmic;
|
|
+ struct watchdog_device wdtdev;
|
|
+};
|
|
+
|
|
+static int pmic_wdt_start(struct watchdog_device *wdd)
|
|
+{
|
|
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
|
|
+
|
|
+ return regmap_update_bits(wdt->pmic->regmap,
|
|
+ WCHDG_CR, WDT_START_MASK, WDT_START);
|
|
+}
|
|
+
|
|
+static int pmic_wdt_stop(struct watchdog_device *wdd)
|
|
+{
|
|
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
|
|
+
|
|
+ return regmap_update_bits(wdt->pmic->regmap,
|
|
+ WCHDG_CR, WDT_START_MASK, WDT_STOP);
|
|
+}
|
|
+
|
|
+static int pmic_wdt_ping(struct watchdog_device *wdd)
|
|
+{
|
|
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
|
|
+
|
|
+ return regmap_update_bits(wdt->pmic->regmap,
|
|
+ WCHDG_CR, WDT_PING_MASK, WDT_PING);
|
|
+}
|
|
+
|
|
+static int pmic_wdt_set_timeout(struct watchdog_device *wdd,
|
|
+ unsigned int timeout)
|
|
+{
|
|
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
|
|
+
|
|
+ wdd->timeout = timeout;
|
|
+ /* timeout is equal to register value + 1 */
|
|
+ return regmap_write(wdt->pmic->regmap, WCHDG_TIMER_CR, timeout - 1);
|
|
+}
|
|
+
|
|
+static const struct watchdog_info pmic_watchdog_info = {
|
|
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
|
+ .identity = "STPMIC1 PMIC Watchdog",
|
|
+};
|
|
+
|
|
+static const struct watchdog_ops pmic_watchdog_ops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .start = pmic_wdt_start,
|
|
+ .stop = pmic_wdt_stop,
|
|
+ .ping = pmic_wdt_ping,
|
|
+ .set_timeout = pmic_wdt_set_timeout,
|
|
+};
|
|
+
|
|
+static int pmic_wdt_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ int ret;
|
|
+ struct stpmic1 *pmic;
|
|
+ struct stpmic1_wdt *wdt;
|
|
+
|
|
+ if (!dev->parent)
|
|
+ return -EINVAL;
|
|
+
|
|
+ pmic = dev_get_drvdata(dev->parent);
|
|
+ if (!pmic)
|
|
+ return -EINVAL;
|
|
+
|
|
+ wdt = devm_kzalloc(dev, sizeof(struct stpmic1_wdt), GFP_KERNEL);
|
|
+ if (!wdt)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ wdt->pmic = pmic;
|
|
+
|
|
+ wdt->wdtdev.info = &pmic_watchdog_info;
|
|
+ wdt->wdtdev.ops = &pmic_watchdog_ops;
|
|
+ wdt->wdtdev.min_timeout = PMIC_WDT_MIN_TIMEOUT;
|
|
+ wdt->wdtdev.max_timeout = PMIC_WDT_MAX_TIMEOUT;
|
|
+ wdt->wdtdev.parent = dev;
|
|
+
|
|
+ wdt->wdtdev.timeout = PMIC_WDT_DEFAULT_TIMEOUT;
|
|
+ watchdog_init_timeout(&wdt->wdtdev, 0, dev);
|
|
+
|
|
+ watchdog_set_nowayout(&wdt->wdtdev, nowayout);
|
|
+ watchdog_set_drvdata(&wdt->wdtdev, wdt);
|
|
+
|
|
+ ret = devm_watchdog_register_device(dev, &wdt->wdtdev);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ dev_dbg(wdt->pmic->dev, "PMIC Watchdog driver probed\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id of_pmic_wdt_match[] = {
|
|
+ { .compatible = "st,stpmic1-wdt" },
|
|
+ { },
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(of, of_pmic_wdt_match);
|
|
+
|
|
+static struct platform_driver stpmic1_wdt_driver = {
|
|
+ .probe = pmic_wdt_probe,
|
|
+ .driver = {
|
|
+ .name = "stpmic1-wdt",
|
|
+ .of_match_table = of_pmic_wdt_match,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(stpmic1_wdt_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("Watchdog driver for STPMIC1 device");
|
|
+MODULE_AUTHOR("Pascal Paillet <p.paillet@st.com>");
|
|
+MODULE_LICENSE("GPL v2");
|
|
--
|
|
2.7.4
|
|
|