2023-03-14 13:27:45

by Xingyu Wu

[permalink] [raw]
Subject: [PATCH v5 0/3] Add watchdog driver for StarFive JH7100/JH7110 RISC-V SoCs

This patch serises are to add watchdog driver for the StarFive
JH7100 and JH7110 RISC-V SoCs. The first patch adds docunmentation to
describe device tree bindings. The subsequent patch adds watchdog driver
and support JH7100/JH7110 SoCs. And the last patch adds watchdog node in
the JH7100 dts. And the addition of JH7110 device tree node will be
submitted after the JH7110 dts merge. This patchset is based on 6.3-rc1.

The watchdog driver has been tested on the VisionFive 1 and VisionFive 2
boards which equip with JH7100 and JH7110 SoCs respectively and both
works normally.

Changes since v4:
Patch 2:
- Dropped the struct device.
- Renamed the struct watchdog_device.
- Modified the struct reset_control to be local variable.
- Fixed the exit error with spinlock.
- Dropped the function of starfive_wdt_is_locked().
- Changed the macro name of 'OPTIONS'.
- Changed the order in probe to do less work if return '-EPROBE_DEFER'.
- Fixed some function that ignored return value.
Patch 3:
- Dropped the label of watchdog node.

Changes since v3:
- Modified the dt-binding, driver and dts to support JH7100 watchdog.
- Modified the register comments.
- Changed the return value and order when getting clock rate.
- Used dev_err_probe() when getting clocks and resets.
- Improved the codes of setting default timeout.
- Moved the watchdog_register_device() after setting WDOG_HW_RUNNING.
- Changed 'SOC_STARFIVE' to 'ARCH_STARFIVE' in Kconfig file.
- Dropped the struct of platform_device_id.
- Used new functions to enable or disable clock.

Changes since v2:
- Added watchdog.yaml and unevaluatedProperties in the dt-binding.
- Removed some unnecessary include files.
- Changed the 'module_param' name and dropped 'soft_noboot'.
- Rrmoved 'CONFIG_OF'.
- Added a check if clock rate is 0.
- Modified the max_timeout calculation formula.
- Removed restart function.
- Removed duplicate checks on the upper and lower bounds of 'count'.
- Removed 'started' variable.
- Added pm_runtime_get_sync() and pm_runtime_put_sync().
- Removed 'firmware_version = 0' variable.
- Drop the device tree node commit.

Changes since v1:
- Renamed the dt-binding 'starfive,wdt.yaml' to 'starfive,jh7110-wdt.yaml'.
- Dropped the '_clk' and 'rst_' about the 'clock-names' and 'reset-names'
in the dt-binding.
- Updated the example context in the dt-binding 'starfive,jh7110-wdt.yaml'
to be independent of other patchset.
- Deleted unused macros like 'JH7110_WDOG_INT_EN'.
- Changed the type of 'freq' in the struct from u64 to u32.
- Used 'devm_clk_get_enabled()' instead of 'devm_clk_get()' and
'clk_prepare_enable()'.
- Removed the operation to get the frequency from the device tree.
- Added watchdog_stop_on_unregister() and watchdog_stop_on_reboot().
- Removed any operations about interrupt.

v4:
--- https://lore.kernel.org/all/[email protected]/
v3:
--- https://lore.kernel.org/all/[email protected]/
v2:
--- https://lore.kernel.org/all/[email protected]/
v1:
--- https://lore.kernel.org/all/[email protected]/

Xingyu Wu (3):
dt-bindings: watchdog: Add watchdog for StarFive JH7100 and JH7110
drivers: watchdog: Add StarFive Watchdog driver
riscv: dts: starfive: jh7100: Add watchdog node

.../watchdog/starfive,jh7100-wdt.yaml | 71 ++
MAINTAINERS | 7 +
arch/riscv/boot/dts/starfive/jh7100.dtsi | 10 +
drivers/watchdog/Kconfig | 11 +
drivers/watchdog/Makefile | 3 +
drivers/watchdog/starfive-wdt.c | 606 ++++++++++++++++++
6 files changed, 708 insertions(+)
create mode 100644 Documentation/devicetree/bindings/watchdog/starfive,jh7100-wdt.yaml
create mode 100644 drivers/watchdog/starfive-wdt.c


base-commit: 8ca09d5fa3549d142c2080a72a4c70ce389163cd
prerequisite-patch-id: 558a5b43260d13a6f5229b139ccd45f737a4f686
--
2.25.1



2023-03-14 13:27:47

by Xingyu Wu

[permalink] [raw]
Subject: [PATCH v5 1/3] dt-bindings: watchdog: Add watchdog for StarFive JH7100 and JH7110

Add bindings to describe the watchdog for the StarFive JH7100/JH7110 SoC.
And Use JH7100 as first StarFive SoC with watchdog.

Signed-off-by: Xingyu Wu <[email protected]>
Reviewed-by: Krzysztof Kozlowski <[email protected]>
---
.../watchdog/starfive,jh7100-wdt.yaml | 71 +++++++++++++++++++
1 file changed, 71 insertions(+)
create mode 100644 Documentation/devicetree/bindings/watchdog/starfive,jh7100-wdt.yaml

diff --git a/Documentation/devicetree/bindings/watchdog/starfive,jh7100-wdt.yaml b/Documentation/devicetree/bindings/watchdog/starfive,jh7100-wdt.yaml
new file mode 100644
index 000000000000..68f3f6fd08a6
--- /dev/null
+++ b/Documentation/devicetree/bindings/watchdog/starfive,jh7100-wdt.yaml
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/watchdog/starfive,jh7100-wdt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: StarFive Watchdog for JH7100 and JH7110 SoC
+
+maintainers:
+ - Xingyu Wu <[email protected]>
+ - Samin Guo <[email protected]>
+
+description:
+ The JH7100 and JH7110 watchdog both are 32 bit counters. JH7100 watchdog
+ has only one timeout phase and reboots. And JH7110 watchdog has two
+ timeout phases. At the first phase, the signal of watchdog interrupt
+ output(WDOGINT) will rise when counter is 0. The counter will reload
+ the timeout value. And then, if counter decreases to 0 again and WDOGINT
+ isn't cleared, the watchdog will reset the system unless the watchdog
+ reset is disabled.
+
+allOf:
+ - $ref: watchdog.yaml#
+
+properties:
+ compatible:
+ enum:
+ - starfive,jh7100-wdt
+ - starfive,jh7110-wdt
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: APB clock
+ - description: Core clock
+
+ clock-names:
+ items:
+ - const: apb
+ - const: core
+
+ resets:
+ items:
+ - description: APB reset
+ - description: Core reset
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - resets
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ watchdog@12480000 {
+ compatible = "starfive,jh7100-wdt";
+ reg = <0x12480000 0x10000>;
+ clocks = <&clk 171>,
+ <&clk 172>;
+ clock-names = "apb", "core";
+ resets = <&rst 99>,
+ <&rst 100>;
+ };
--
2.25.1


2023-03-14 13:27:51

by Xingyu Wu

[permalink] [raw]
Subject: [PATCH v5 3/3] riscv: dts: starfive: jh7100: Add watchdog node

Add watchdog node for the StarFive JH7100 RISC-V SoC.

Signed-off-by: Xingyu Wu <[email protected]>
Reviewed-by: Emil Renner Berthing <[email protected]>
---
arch/riscv/boot/dts/starfive/jh7100.dtsi | 10 ++++++++++
1 file changed, 10 insertions(+)

diff --git a/arch/riscv/boot/dts/starfive/jh7100.dtsi b/arch/riscv/boot/dts/starfive/jh7100.dtsi
index 000447482aca..4218621ea3b9 100644
--- a/arch/riscv/boot/dts/starfive/jh7100.dtsi
+++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi
@@ -238,5 +238,15 @@ i2c3: i2c@12460000 {
#size-cells = <0>;
status = "disabled";
};
+
+ watchdog@12480000 {
+ compatible = "starfive,jh7100-wdt";
+ reg = <0x0 0x12480000 0x0 0x10000>;
+ clocks = <&clkgen JH7100_CLK_WDTIMER_APB>,
+ <&clkgen JH7100_CLK_WDT_CORE>;
+ clock-names = "apb", "core";
+ resets = <&rstgen JH7100_RSTN_WDTIMER_APB>,
+ <&rstgen JH7100_RSTN_WDT>;
+ };
};
};
--
2.25.1


2023-03-14 13:27:53

by Xingyu Wu

[permalink] [raw]
Subject: [PATCH v5 2/3] drivers: watchdog: Add StarFive Watchdog driver

Add watchdog driver for the StarFive JH7100 and JH7110 SoC.

Signed-off-by: Xingyu Wu <[email protected]>
---
MAINTAINERS | 7 +
drivers/watchdog/Kconfig | 11 +
drivers/watchdog/Makefile | 3 +
drivers/watchdog/starfive-wdt.c | 606 ++++++++++++++++++++++++++++++++
4 files changed, 627 insertions(+)
create mode 100644 drivers/watchdog/starfive-wdt.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 8d5bc223f305..721d0e4e8a0d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19962,6 +19962,13 @@ S: Supported
F: Documentation/devicetree/bindings/rng/starfive*
F: drivers/char/hw_random/jh7110-trng.c

+STARFIVE WATCHDOG DRIVER
+M: Xingyu Wu <[email protected]>
+M: Samin Guo <[email protected]>
+S: Supported
+F: Documentation/devicetree/bindings/watchdog/starfive*
+F: drivers/watchdog/starfive-wdt.c
+
STATIC BRANCH/CALL
M: Peter Zijlstra <[email protected]>
M: Josh Poimboeuf <[email protected]>
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index f0872970daf9..f22138709bf5 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1999,6 +1999,17 @@ config WATCHDOG_RTAS
To compile this driver as a module, choose M here. The module
will be called wdrtas.

+# RISC-V Architecture
+
+config STARFIVE_WATCHDOG
+ tristate "StarFive Watchdog support"
+ depends on ARCH_STARFIVE || COMPILE_TEST
+ select WATCHDOG_CORE
+ default ARCH_STARFIVE
+ help
+ Say Y here to support the watchdog of StarFive JH7100 and JH7110
+ SoC. This driver can also be built as a module if choose M.
+
# S390 Architecture

config DIAG288_WATCHDOG
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 9cbf6580f16c..b4c4ccf2d703 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -192,6 +192,9 @@ obj-$(CONFIG_MEN_A21_WDT) += mena21_wdt.o
obj-$(CONFIG_PSERIES_WDT) += pseries-wdt.o
obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o

+# RISC-V Architecture
+obj-$(CONFIG_STARFIVE_WATCHDOG) += starfive-wdt.o
+
# S390 Architecture
obj-$(CONFIG_DIAG288_WATCHDOG) += diag288_wdt.o

diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c
new file mode 100644
index 000000000000..1995cceca51e
--- /dev/null
+++ b/drivers/watchdog/starfive-wdt.c
@@ -0,0 +1,606 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Starfive Watchdog driver
+ *
+ * Copyright (C) 2022 StarFive Technology Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/watchdog.h>
+
+/* JH7100 Watchdog register define */
+#define STARFIVE_WDT_JH7100_INTSTAUS 0x000
+#define STARFIVE_WDT_JH7100_CONTROL 0x104
+#define STARFIVE_WDT_JH7100_LOAD 0x108
+#define STARFIVE_WDT_JH7100_EN 0x110
+#define STARFIVE_WDT_JH7100_RELOAD 0x114 /* Write 0 or 1 to reload preset value */
+#define STARFIVE_WDT_JH7100_VALUE 0x118
+#define STARFIVE_WDT_JH7100_INTCLR 0x120 /*
+ * [0]: Write 1 to clear interrupt
+ * [1]: 1 mean clearing and 0 mean complete
+ * [31:2]: reserved.
+ */
+#define STARFIVE_WDT_JH7100_LOCK 0x13c /* write 0x378f0765 to unlock */
+
+/* JH7110 Watchdog register define */
+#define STARFIVE_WDT_JH7110_LOAD 0x000
+#define STARFIVE_WDT_JH7110_VALUE 0x004
+#define STARFIVE_WDT_JH7110_CONTROL 0x008 /*
+ * [0]: reset enable;
+ * [1]: interrupt enable && watchdog enable
+ * [31:2]: reserved.
+ */
+#define STARFIVE_WDT_JH7110_INTCLR 0x00c /* clear intterupt and reload the counter */
+#define STARFIVE_WDT_JH7110_IMS 0x014
+#define STARFIVE_WDT_JH7110_LOCK 0xc00 /* write 0x1ACCE551 to unlock */
+
+/* WDOGCONTROL */
+#define STARFIVE_WDT_ENABLE 0x1
+#define STARFIVE_WDT_EN_SHIFT 0
+#define STARFIVE_WDT_RESET_EN 0x1
+#define STARFIVE_WDT_JH7100_RST_EN_SHIFT 0
+#define STARFIVE_WDT_JH7110_RST_EN_SHIFT 1
+
+/* WDOGLOCK */
+#define STARFIVE_WDT_JH7100_UNLOCK_KEY 0x378f0765
+#define STARFIVE_WDT_JH7110_UNLOCK_KEY 0x1acce551
+
+/* WDOGINTCLR */
+#define STARFIVE_WDT_INTCLR 0x1
+#define STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT 1 /* Watchdog can clear interrupt when 0 */
+
+#define STARFIVE_WDT_MAXCNT 0xffffffff
+#define STARFIVE_WDT_DEFAULT_TIME (15)
+#define STARFIVE_WDT_DELAY_US 0
+#define STARFIVE_WDT_TIMEOUT_US 10000
+
+/* module parameter */
+#define STARFIVE_WDT_EARLY_ENA 0
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static int heartbeat;
+static bool early_enable = STARFIVE_WDT_EARLY_ENA;
+
+module_param(heartbeat, int, 0);
+module_param(early_enable, bool, 0);
+module_param(nowayout, bool, 0);
+
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
+ __MODULE_STRING(STARFIVE_WDT_DEFAULT_TIME) ")");
+MODULE_PARM_DESC(early_enable,
+ "Watchdog is started at boot time if set to 1, default="
+ __MODULE_STRING(STARFIVE_WDT_EARLY_ENA));
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct starfive_wdt_variant {
+ unsigned int control; /* Watchdog Control Resgister for reset enable */
+ unsigned int load; /* Watchdog Load register */
+ unsigned int reload; /* Watchdog Reload Control register */
+ unsigned int enable; /* Watchdog Enable Register */
+ unsigned int value; /* Watchdog Counter Value Register */
+ unsigned int int_clr; /* Watchdog Interrupt Clear Register */
+ unsigned int unlock; /* Watchdog Lock Register */
+ unsigned int int_status; /* Watchdog Interrupt Status Register */
+
+ u32 unlock_key;
+ char enrst_shift;
+ char en_shift;
+ bool intclr_check; /* whether need to check it before clearing interrupt */
+ char intclr_ava_shift;
+ bool double_timeout; /* The watchdog need twice timeout to reboot */
+};
+
+struct starfive_wdt {
+ struct watchdog_device wdd;
+ spinlock_t lock; /* spinlock for register handling */
+ void __iomem *base;
+ struct clk *core_clk;
+ struct clk *apb_clk;
+ const struct starfive_wdt_variant *variant;
+ unsigned long freq;
+ u32 count; /* count of timeout */
+ u32 reload; /* restore the count */
+};
+
+/* Register layout and configuration for the JH7100 */
+static const struct starfive_wdt_variant starfive_wdt_jh7100_variant = {
+ .control = STARFIVE_WDT_JH7100_CONTROL,
+ .load = STARFIVE_WDT_JH7100_LOAD,
+ .reload = STARFIVE_WDT_JH7100_RELOAD,
+ .enable = STARFIVE_WDT_JH7100_EN,
+ .value = STARFIVE_WDT_JH7100_VALUE,
+ .int_clr = STARFIVE_WDT_JH7100_INTCLR,
+ .unlock = STARFIVE_WDT_JH7100_LOCK,
+ .unlock_key = STARFIVE_WDT_JH7100_UNLOCK_KEY,
+ .int_status = STARFIVE_WDT_JH7100_INTSTAUS,
+ .enrst_shift = STARFIVE_WDT_JH7100_RST_EN_SHIFT,
+ .en_shift = STARFIVE_WDT_EN_SHIFT,
+ .intclr_check = true,
+ .intclr_ava_shift = STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT,
+ .double_timeout = false,
+};
+
+/* Register layout and configuration for the JH7110 */
+static const struct starfive_wdt_variant starfive_wdt_jh7110_variant = {
+ .control = STARFIVE_WDT_JH7110_CONTROL,
+ .load = STARFIVE_WDT_JH7110_LOAD,
+ .enable = STARFIVE_WDT_JH7110_CONTROL,
+ .value = STARFIVE_WDT_JH7110_VALUE,
+ .int_clr = STARFIVE_WDT_JH7110_INTCLR,
+ .unlock = STARFIVE_WDT_JH7110_LOCK,
+ .unlock_key = STARFIVE_WDT_JH7110_UNLOCK_KEY,
+ .int_status = STARFIVE_WDT_JH7110_IMS,
+ .enrst_shift = STARFIVE_WDT_JH7110_RST_EN_SHIFT,
+ .en_shift = STARFIVE_WDT_EN_SHIFT,
+ .intclr_check = false,
+ .double_timeout = true,
+};
+
+static int starfive_wdt_enable_clock(struct starfive_wdt *wdt)
+{
+ int ret;
+
+ ret = clk_prepare_enable(wdt->apb_clk);
+ if (ret)
+ return dev_err_probe(wdt->wdd.parent, ret, "failed to enable apb clock\n");
+
+ ret = clk_prepare_enable(wdt->core_clk);
+ if (ret)
+ return dev_err_probe(wdt->wdd.parent, ret, "failed to enable core clock\n");
+
+ return 0;
+}
+
+static void starfive_wdt_disable_clock(struct starfive_wdt *wdt)
+{
+ clk_disable_unprepare(wdt->core_clk);
+ clk_disable_unprepare(wdt->apb_clk);
+}
+
+static inline int starfive_wdt_get_clock(struct starfive_wdt *wdt)
+{
+ struct device *dev = wdt->wdd.parent;
+
+ wdt->apb_clk = devm_clk_get(dev, "apb");
+ if (IS_ERR(wdt->apb_clk))
+ return dev_err_probe(dev, PTR_ERR(wdt->apb_clk), "failed to get apb clock\n");
+
+ wdt->core_clk = devm_clk_get(dev, "core");
+ if (IS_ERR(wdt->core_clk))
+ return dev_err_probe(dev, PTR_ERR(wdt->core_clk), "failed to get core clock\n");
+
+ return 0;
+}
+
+static inline int starfive_wdt_reset_init(struct device *dev)
+{
+ struct reset_control *rsts;
+ int ret;
+
+ rsts = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR(rsts))
+ return dev_err_probe(dev, PTR_ERR(rsts), "failed to get resets\n");
+
+ ret = reset_control_deassert(rsts);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to deassert resets\n");
+
+ return 0;
+}
+
+static u32 starfive_wdt_ticks_to_sec(struct starfive_wdt *wdt, u32 ticks)
+{
+ return DIV_ROUND_CLOSEST(ticks, wdt->freq);
+}
+
+/* Write unlock-key to unlock. Write other value to lock. */
+static void starfive_wdt_unlock(struct starfive_wdt *wdt)
+{
+ spin_lock(&wdt->lock);
+ writel(wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
+}
+
+static void starfive_wdt_lock(struct starfive_wdt *wdt)
+{
+ writel(~wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
+ spin_unlock(&wdt->lock);
+}
+
+/* enable watchdog interrupt to reset/reboot */
+static void starfive_wdt_enable_reset(struct starfive_wdt *wdt)
+{
+ u32 val;
+
+ val = readl(wdt->base + wdt->variant->control);
+ val |= STARFIVE_WDT_RESET_EN << wdt->variant->enrst_shift;
+ writel(val, wdt->base + wdt->variant->control);
+}
+
+/* interrupt status whether has been raised from the counter */
+static bool starfive_wdt_raise_irq_status(struct starfive_wdt *wdt)
+{
+ return !!readl(wdt->base + wdt->variant->int_status);
+}
+
+/* waiting interrupt can be free to clear */
+static int starfive_wdt_wait_int_free(struct starfive_wdt *wdt)
+{
+ u32 value;
+
+ return readl_poll_timeout_atomic(wdt->base + wdt->variant->int_clr, value,
+ !(value & BIT(wdt->variant->intclr_ava_shift)),
+ STARFIVE_WDT_DELAY_US, STARFIVE_WDT_TIMEOUT_US);
+}
+
+/* clear interrupt signal before initialization or reload */
+static int starfive_wdt_int_clr(struct starfive_wdt *wdt)
+{
+ int ret;
+
+ if (wdt->variant->intclr_check) {
+ ret = starfive_wdt_wait_int_free(wdt);
+ if (ret)
+ return dev_err_probe(wdt->wdd.parent, ret,
+ "watchdog is not ready to clear interrupt.\n");
+ }
+ writel(STARFIVE_WDT_INTCLR, wdt->base + wdt->variant->int_clr);
+
+ return 0;
+}
+
+static inline void starfive_wdt_set_count(struct starfive_wdt *wdt, u32 val)
+{
+ writel(val, wdt->base + wdt->variant->load);
+}
+
+static inline u32 starfive_wdt_get_count(struct starfive_wdt *wdt)
+{
+ return readl(wdt->base + wdt->variant->value);
+}
+
+/* enable watchdog */
+static inline void starfive_wdt_enable(struct starfive_wdt *wdt)
+{
+ u32 val;
+
+ val = readl(wdt->base + wdt->variant->enable);
+ val |= STARFIVE_WDT_ENABLE << wdt->variant->en_shift;
+ writel(val, wdt->base + wdt->variant->enable);
+}
+
+/* disable watchdog */
+static inline void starfive_wdt_disable(struct starfive_wdt *wdt)
+{
+ u32 val;
+
+ val = readl(wdt->base + wdt->variant->enable);
+ val &= ~(STARFIVE_WDT_ENABLE << wdt->variant->en_shift);
+ writel(val, wdt->base + wdt->variant->enable);
+}
+
+static inline void starfive_wdt_set_reload_count(struct starfive_wdt *wdt, u32 count)
+{
+ starfive_wdt_set_count(wdt, count);
+
+ /* 7100 need set any value to reload register and could reload value to counter */
+ if (wdt->variant->reload)
+ writel(0x1, wdt->base + wdt->variant->reload);
+}
+
+static unsigned int starfive_wdt_max_timeout(struct starfive_wdt *wdt)
+{
+ if (wdt->variant->double_timeout)
+ return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, (wdt->freq / 2)) - 1;
+
+ return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, wdt->freq) - 1;
+}
+
+static unsigned int starfive_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
+ u32 count;
+
+ /*
+ * If the watchdog takes twice timeout and set half count value,
+ * timeleft value should add the count value before first timeout.
+ */
+ count = starfive_wdt_get_count(wdt);
+ if (wdt->variant->double_timeout && !starfive_wdt_raise_irq_status(wdt))
+ count += wdt->count;
+
+ return starfive_wdt_ticks_to_sec(wdt, count);
+}
+
+static int starfive_wdt_keepalive(struct watchdog_device *wdd)
+{
+ struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ starfive_wdt_unlock(wdt);
+ ret = starfive_wdt_int_clr(wdt);
+ if (ret)
+ goto exit;
+
+ starfive_wdt_set_reload_count(wdt, wdt->count);
+
+exit:
+ /* exit with releasing spinlock and locking registers */
+ starfive_wdt_lock(wdt);
+ return ret;
+}
+
+static int starfive_wdt_start(struct starfive_wdt *wdt)
+{
+ int ret;
+
+ starfive_wdt_unlock(wdt);
+ /* disable watchdog, to be safe */
+ starfive_wdt_disable(wdt);
+
+ starfive_wdt_enable_reset(wdt);
+ ret = starfive_wdt_int_clr(wdt);
+ if (ret)
+ goto exit;
+
+ starfive_wdt_set_count(wdt, wdt->count);
+ starfive_wdt_enable(wdt);
+
+exit:
+ starfive_wdt_lock(wdt);
+ return ret;
+}
+
+static void starfive_wdt_stop(struct starfive_wdt *wdt)
+{
+ starfive_wdt_unlock(wdt);
+ starfive_wdt_disable(wdt);
+ starfive_wdt_lock(wdt);
+}
+
+static int starfive_wdt_pm_start(struct watchdog_device *wdd)
+{
+ struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret = pm_runtime_get_sync(wdd->parent);
+
+ if (ret < 0)
+ return ret;
+
+ return starfive_wdt_start(wdt);
+}
+
+static int starfive_wdt_pm_stop(struct watchdog_device *wdd)
+{
+ struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ starfive_wdt_stop(wdt);
+ return pm_runtime_put_sync(wdd->parent);
+}
+
+static int starfive_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
+ unsigned long count = timeout * wdt->freq;
+
+ /* some watchdogs take two timeouts to reset */
+ if (wdt->variant->double_timeout)
+ count /= 2;
+
+ wdt->count = count;
+ wdd->timeout = timeout;
+
+ starfive_wdt_unlock(wdt);
+ starfive_wdt_disable(wdt);
+ starfive_wdt_set_reload_count(wdt, wdt->count);
+ starfive_wdt_enable(wdt);
+ starfive_wdt_lock(wdt);
+
+ return 0;
+}
+
+#define STARFIVE_WDT_OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
+
+static const struct watchdog_info starfive_wdt_info = {
+ .options = STARFIVE_WDT_OPTIONS,
+ .identity = "StarFive Watchdog",
+};
+
+static const struct watchdog_ops starfive_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = starfive_wdt_pm_start,
+ .stop = starfive_wdt_pm_stop,
+ .ping = starfive_wdt_keepalive,
+ .set_timeout = starfive_wdt_set_timeout,
+ .get_timeleft = starfive_wdt_get_timeleft,
+};
+
+static int starfive_wdt_probe(struct platform_device *pdev)
+{
+ struct starfive_wdt *wdt;
+ int ret;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base))
+ return dev_err_probe(&pdev->dev, PTR_ERR(wdt->base), "error mapping registers\n");
+
+ wdt->wdd.parent = &pdev->dev;
+ ret = starfive_wdt_get_clock(wdt);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, wdt);
+ pm_runtime_enable(&pdev->dev);
+ if (pm_runtime_enabled(&pdev->dev)) {
+ ret = pm_runtime_get_sync(&pdev->dev);
+ if (ret < 0)
+ return ret;
+ } else {
+ /* runtime PM is disabled but clocks need to be enabled */
+ ret = starfive_wdt_enable_clock(wdt);
+ if (ret)
+ return ret;
+ }
+
+ ret = starfive_wdt_reset_init(&pdev->dev);
+ if (ret)
+ goto err_exit;
+
+ watchdog_set_drvdata(&wdt->wdd, wdt);
+ wdt->wdd.info = &starfive_wdt_info;
+ wdt->wdd.ops = &starfive_wdt_ops;
+ wdt->variant = of_device_get_match_data(&pdev->dev);
+ spin_lock_init(&wdt->lock);
+
+ wdt->freq = clk_get_rate(wdt->core_clk);
+ if (!wdt->freq) {
+ dev_err(&pdev->dev, "get clock rate failed.\n");
+ ret = -EINVAL;
+ goto err_exit;
+ }
+
+ wdt->wdd.min_timeout = 1;
+ wdt->wdd.max_timeout = starfive_wdt_max_timeout(wdt);
+ wdt->wdd.timeout = STARFIVE_WDT_DEFAULT_TIME;
+ watchdog_init_timeout(&wdt->wdd, heartbeat, &pdev->dev);
+ starfive_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
+
+ watchdog_set_nowayout(&wdt->wdd, nowayout);
+ watchdog_stop_on_reboot(&wdt->wdd);
+ watchdog_stop_on_unregister(&wdt->wdd);
+
+ if (early_enable) {
+ ret = starfive_wdt_start(wdt);
+ if (ret)
+ goto err_exit;
+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+ } else {
+ starfive_wdt_stop(wdt);
+ }
+
+ ret = watchdog_register_device(&wdt->wdd);
+ if (ret)
+ goto err_exit;
+
+ if (!early_enable)
+ return pm_runtime_put_sync(&pdev->dev);
+
+ return 0;
+
+err_exit:
+ starfive_wdt_disable_clock(wdt);
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static int starfive_wdt_remove(struct platform_device *pdev)
+{
+ struct starfive_wdt *wdt = platform_get_drvdata(pdev);
+
+ starfive_wdt_stop(wdt);
+ watchdog_unregister_device(&wdt->wdd);
+
+ if (pm_runtime_enabled(&pdev->dev))
+ pm_runtime_disable(&pdev->dev);
+ else
+ /* disable clock without PM */
+ starfive_wdt_disable_clock(wdt);
+
+ return 0;
+}
+
+static void starfive_wdt_shutdown(struct platform_device *pdev)
+{
+ struct starfive_wdt *wdt = platform_get_drvdata(pdev);
+
+ starfive_wdt_pm_stop(&wdt->wdd);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int starfive_wdt_suspend(struct device *dev)
+{
+ struct starfive_wdt *wdt = dev_get_drvdata(dev);
+
+ /* Save watchdog state, and turn it off. */
+ wdt->reload = starfive_wdt_get_count(wdt);
+
+ /* Note that WTCNT doesn't need to be saved. */
+ starfive_wdt_stop(wdt);
+
+ return pm_runtime_force_suspend(dev);
+}
+
+static int starfive_wdt_resume(struct device *dev)
+{
+ struct starfive_wdt *wdt = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ starfive_wdt_unlock(wdt);
+ /* Restore watchdog state. */
+ starfive_wdt_set_reload_count(wdt, wdt->reload);
+ starfive_wdt_lock(wdt);
+
+ return starfive_wdt_start(wdt);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM
+static int starfive_wdt_runtime_suspend(struct device *dev)
+{
+ struct starfive_wdt *wdt = dev_get_drvdata(dev);
+
+ starfive_wdt_disable_clock(wdt);
+
+ return 0;
+}
+
+static int starfive_wdt_runtime_resume(struct device *dev)
+{
+ struct starfive_wdt *wdt = dev_get_drvdata(dev);
+
+ return starfive_wdt_enable_clock(wdt);
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops starfive_wdt_pm_ops = {
+ SET_RUNTIME_PM_OPS(starfive_wdt_runtime_suspend, starfive_wdt_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(starfive_wdt_suspend, starfive_wdt_resume)
+};
+
+static const struct of_device_id starfive_wdt_match[] = {
+ { .compatible = "starfive,jh7100-wdt", .data = &starfive_wdt_jh7100_variant },
+ { .compatible = "starfive,jh7110-wdt", .data = &starfive_wdt_jh7110_variant },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, starfive_wdt_match);
+
+static struct platform_driver starfive_wdt_driver = {
+ .probe = starfive_wdt_probe,
+ .remove = starfive_wdt_remove,
+ .shutdown = starfive_wdt_shutdown,
+ .driver = {
+ .name = "starfive-wdt",
+ .pm = &starfive_wdt_pm_ops,
+ .of_match_table = of_match_ptr(starfive_wdt_match),
+ },
+};
+module_platform_driver(starfive_wdt_driver);
+
+MODULE_AUTHOR("Xingyu Wu <[email protected]>");
+MODULE_AUTHOR("Samin Guo <[email protected]>");
+MODULE_DESCRIPTION("StarFive Watchdog Device Driver");
+MODULE_LICENSE("GPL");
--
2.25.1


2023-03-29 16:29:14

by Emil Renner Berthing

[permalink] [raw]
Subject: Re: [PATCH v5 2/3] drivers: watchdog: Add StarFive Watchdog driver

On Tue, 14 Mar 2023 at 14:27, Xingyu Wu <[email protected]> wrote:
>
> Add watchdog driver for the StarFive JH7100 and JH7110 SoC.
>
> Signed-off-by: Xingyu Wu <[email protected]>

Reviewed-by: Emil Renner Berthing <[email protected]>

Thanks!

> ---
> MAINTAINERS | 7 +
> drivers/watchdog/Kconfig | 11 +
> drivers/watchdog/Makefile | 3 +
> drivers/watchdog/starfive-wdt.c | 606 ++++++++++++++++++++++++++++++++
> 4 files changed, 627 insertions(+)
> create mode 100644 drivers/watchdog/starfive-wdt.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8d5bc223f305..721d0e4e8a0d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -19962,6 +19962,13 @@ S: Supported
> F: Documentation/devicetree/bindings/rng/starfive*
> F: drivers/char/hw_random/jh7110-trng.c
>
> +STARFIVE WATCHDOG DRIVER
> +M: Xingyu Wu <[email protected]>
> +M: Samin Guo <[email protected]>
> +S: Supported
> +F: Documentation/devicetree/bindings/watchdog/starfive*
> +F: drivers/watchdog/starfive-wdt.c
> +
> STATIC BRANCH/CALL
> M: Peter Zijlstra <[email protected]>
> M: Josh Poimboeuf <[email protected]>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index f0872970daf9..f22138709bf5 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -1999,6 +1999,17 @@ config WATCHDOG_RTAS
> To compile this driver as a module, choose M here. The module
> will be called wdrtas.
>
> +# RISC-V Architecture
> +
> +config STARFIVE_WATCHDOG
> + tristate "StarFive Watchdog support"
> + depends on ARCH_STARFIVE || COMPILE_TEST
> + select WATCHDOG_CORE
> + default ARCH_STARFIVE
> + help
> + Say Y here to support the watchdog of StarFive JH7100 and JH7110
> + SoC. This driver can also be built as a module if choose M.
> +
> # S390 Architecture
>
> config DIAG288_WATCHDOG
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 9cbf6580f16c..b4c4ccf2d703 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -192,6 +192,9 @@ obj-$(CONFIG_MEN_A21_WDT) += mena21_wdt.o
> obj-$(CONFIG_PSERIES_WDT) += pseries-wdt.o
> obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o
>
> +# RISC-V Architecture
> +obj-$(CONFIG_STARFIVE_WATCHDOG) += starfive-wdt.o
> +
> # S390 Architecture
> obj-$(CONFIG_DIAG288_WATCHDOG) += diag288_wdt.o
>
> diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c
> new file mode 100644
> index 000000000000..1995cceca51e
> --- /dev/null
> +++ b/drivers/watchdog/starfive-wdt.c
> @@ -0,0 +1,606 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Starfive Watchdog driver
> + *
> + * Copyright (C) 2022 StarFive Technology Co., Ltd.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +#include <linux/watchdog.h>
> +
> +/* JH7100 Watchdog register define */
> +#define STARFIVE_WDT_JH7100_INTSTAUS 0x000
> +#define STARFIVE_WDT_JH7100_CONTROL 0x104
> +#define STARFIVE_WDT_JH7100_LOAD 0x108
> +#define STARFIVE_WDT_JH7100_EN 0x110
> +#define STARFIVE_WDT_JH7100_RELOAD 0x114 /* Write 0 or 1 to reload preset value */
> +#define STARFIVE_WDT_JH7100_VALUE 0x118
> +#define STARFIVE_WDT_JH7100_INTCLR 0x120 /*
> + * [0]: Write 1 to clear interrupt
> + * [1]: 1 mean clearing and 0 mean complete
> + * [31:2]: reserved.
> + */
> +#define STARFIVE_WDT_JH7100_LOCK 0x13c /* write 0x378f0765 to unlock */
> +
> +/* JH7110 Watchdog register define */
> +#define STARFIVE_WDT_JH7110_LOAD 0x000
> +#define STARFIVE_WDT_JH7110_VALUE 0x004
> +#define STARFIVE_WDT_JH7110_CONTROL 0x008 /*
> + * [0]: reset enable;
> + * [1]: interrupt enable && watchdog enable
> + * [31:2]: reserved.
> + */
> +#define STARFIVE_WDT_JH7110_INTCLR 0x00c /* clear intterupt and reload the counter */
> +#define STARFIVE_WDT_JH7110_IMS 0x014
> +#define STARFIVE_WDT_JH7110_LOCK 0xc00 /* write 0x1ACCE551 to unlock */
> +
> +/* WDOGCONTROL */
> +#define STARFIVE_WDT_ENABLE 0x1
> +#define STARFIVE_WDT_EN_SHIFT 0
> +#define STARFIVE_WDT_RESET_EN 0x1
> +#define STARFIVE_WDT_JH7100_RST_EN_SHIFT 0
> +#define STARFIVE_WDT_JH7110_RST_EN_SHIFT 1
> +
> +/* WDOGLOCK */
> +#define STARFIVE_WDT_JH7100_UNLOCK_KEY 0x378f0765
> +#define STARFIVE_WDT_JH7110_UNLOCK_KEY 0x1acce551
> +
> +/* WDOGINTCLR */
> +#define STARFIVE_WDT_INTCLR 0x1
> +#define STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT 1 /* Watchdog can clear interrupt when 0 */
> +
> +#define STARFIVE_WDT_MAXCNT 0xffffffff
> +#define STARFIVE_WDT_DEFAULT_TIME (15)
> +#define STARFIVE_WDT_DELAY_US 0
> +#define STARFIVE_WDT_TIMEOUT_US 10000
> +
> +/* module parameter */
> +#define STARFIVE_WDT_EARLY_ENA 0
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +static int heartbeat;
> +static bool early_enable = STARFIVE_WDT_EARLY_ENA;
> +
> +module_param(heartbeat, int, 0);
> +module_param(early_enable, bool, 0);
> +module_param(nowayout, bool, 0);
> +
> +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
> + __MODULE_STRING(STARFIVE_WDT_DEFAULT_TIME) ")");
> +MODULE_PARM_DESC(early_enable,
> + "Watchdog is started at boot time if set to 1, default="
> + __MODULE_STRING(STARFIVE_WDT_EARLY_ENA));
> +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
> + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +struct starfive_wdt_variant {
> + unsigned int control; /* Watchdog Control Resgister for reset enable */
> + unsigned int load; /* Watchdog Load register */
> + unsigned int reload; /* Watchdog Reload Control register */
> + unsigned int enable; /* Watchdog Enable Register */
> + unsigned int value; /* Watchdog Counter Value Register */
> + unsigned int int_clr; /* Watchdog Interrupt Clear Register */
> + unsigned int unlock; /* Watchdog Lock Register */
> + unsigned int int_status; /* Watchdog Interrupt Status Register */
> +
> + u32 unlock_key;
> + char enrst_shift;
> + char en_shift;
> + bool intclr_check; /* whether need to check it before clearing interrupt */
> + char intclr_ava_shift;
> + bool double_timeout; /* The watchdog need twice timeout to reboot */
> +};
> +
> +struct starfive_wdt {
> + struct watchdog_device wdd;
> + spinlock_t lock; /* spinlock for register handling */
> + void __iomem *base;
> + struct clk *core_clk;
> + struct clk *apb_clk;
> + const struct starfive_wdt_variant *variant;
> + unsigned long freq;
> + u32 count; /* count of timeout */
> + u32 reload; /* restore the count */
> +};
> +
> +/* Register layout and configuration for the JH7100 */
> +static const struct starfive_wdt_variant starfive_wdt_jh7100_variant = {
> + .control = STARFIVE_WDT_JH7100_CONTROL,
> + .load = STARFIVE_WDT_JH7100_LOAD,
> + .reload = STARFIVE_WDT_JH7100_RELOAD,
> + .enable = STARFIVE_WDT_JH7100_EN,
> + .value = STARFIVE_WDT_JH7100_VALUE,
> + .int_clr = STARFIVE_WDT_JH7100_INTCLR,
> + .unlock = STARFIVE_WDT_JH7100_LOCK,
> + .unlock_key = STARFIVE_WDT_JH7100_UNLOCK_KEY,
> + .int_status = STARFIVE_WDT_JH7100_INTSTAUS,
> + .enrst_shift = STARFIVE_WDT_JH7100_RST_EN_SHIFT,
> + .en_shift = STARFIVE_WDT_EN_SHIFT,
> + .intclr_check = true,
> + .intclr_ava_shift = STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT,
> + .double_timeout = false,
> +};
> +
> +/* Register layout and configuration for the JH7110 */
> +static const struct starfive_wdt_variant starfive_wdt_jh7110_variant = {
> + .control = STARFIVE_WDT_JH7110_CONTROL,
> + .load = STARFIVE_WDT_JH7110_LOAD,
> + .enable = STARFIVE_WDT_JH7110_CONTROL,
> + .value = STARFIVE_WDT_JH7110_VALUE,
> + .int_clr = STARFIVE_WDT_JH7110_INTCLR,
> + .unlock = STARFIVE_WDT_JH7110_LOCK,
> + .unlock_key = STARFIVE_WDT_JH7110_UNLOCK_KEY,
> + .int_status = STARFIVE_WDT_JH7110_IMS,
> + .enrst_shift = STARFIVE_WDT_JH7110_RST_EN_SHIFT,
> + .en_shift = STARFIVE_WDT_EN_SHIFT,
> + .intclr_check = false,
> + .double_timeout = true,
> +};
> +
> +static int starfive_wdt_enable_clock(struct starfive_wdt *wdt)
> +{
> + int ret;
> +
> + ret = clk_prepare_enable(wdt->apb_clk);
> + if (ret)
> + return dev_err_probe(wdt->wdd.parent, ret, "failed to enable apb clock\n");
> +
> + ret = clk_prepare_enable(wdt->core_clk);
> + if (ret)
> + return dev_err_probe(wdt->wdd.parent, ret, "failed to enable core clock\n");
> +
> + return 0;
> +}
> +
> +static void starfive_wdt_disable_clock(struct starfive_wdt *wdt)
> +{
> + clk_disable_unprepare(wdt->core_clk);
> + clk_disable_unprepare(wdt->apb_clk);
> +}
> +
> +static inline int starfive_wdt_get_clock(struct starfive_wdt *wdt)
> +{
> + struct device *dev = wdt->wdd.parent;
> +
> + wdt->apb_clk = devm_clk_get(dev, "apb");
> + if (IS_ERR(wdt->apb_clk))
> + return dev_err_probe(dev, PTR_ERR(wdt->apb_clk), "failed to get apb clock\n");
> +
> + wdt->core_clk = devm_clk_get(dev, "core");
> + if (IS_ERR(wdt->core_clk))
> + return dev_err_probe(dev, PTR_ERR(wdt->core_clk), "failed to get core clock\n");
> +
> + return 0;
> +}
> +
> +static inline int starfive_wdt_reset_init(struct device *dev)
> +{
> + struct reset_control *rsts;
> + int ret;
> +
> + rsts = devm_reset_control_array_get_exclusive(dev);
> + if (IS_ERR(rsts))
> + return dev_err_probe(dev, PTR_ERR(rsts), "failed to get resets\n");
> +
> + ret = reset_control_deassert(rsts);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to deassert resets\n");
> +
> + return 0;
> +}
> +
> +static u32 starfive_wdt_ticks_to_sec(struct starfive_wdt *wdt, u32 ticks)
> +{
> + return DIV_ROUND_CLOSEST(ticks, wdt->freq);
> +}
> +
> +/* Write unlock-key to unlock. Write other value to lock. */
> +static void starfive_wdt_unlock(struct starfive_wdt *wdt)
> +{
> + spin_lock(&wdt->lock);
> + writel(wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
> +}
> +
> +static void starfive_wdt_lock(struct starfive_wdt *wdt)
> +{
> + writel(~wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
> + spin_unlock(&wdt->lock);
> +}
> +
> +/* enable watchdog interrupt to reset/reboot */
> +static void starfive_wdt_enable_reset(struct starfive_wdt *wdt)
> +{
> + u32 val;
> +
> + val = readl(wdt->base + wdt->variant->control);
> + val |= STARFIVE_WDT_RESET_EN << wdt->variant->enrst_shift;
> + writel(val, wdt->base + wdt->variant->control);
> +}
> +
> +/* interrupt status whether has been raised from the counter */
> +static bool starfive_wdt_raise_irq_status(struct starfive_wdt *wdt)
> +{
> + return !!readl(wdt->base + wdt->variant->int_status);
> +}
> +
> +/* waiting interrupt can be free to clear */
> +static int starfive_wdt_wait_int_free(struct starfive_wdt *wdt)
> +{
> + u32 value;
> +
> + return readl_poll_timeout_atomic(wdt->base + wdt->variant->int_clr, value,
> + !(value & BIT(wdt->variant->intclr_ava_shift)),
> + STARFIVE_WDT_DELAY_US, STARFIVE_WDT_TIMEOUT_US);
> +}
> +
> +/* clear interrupt signal before initialization or reload */
> +static int starfive_wdt_int_clr(struct starfive_wdt *wdt)
> +{
> + int ret;
> +
> + if (wdt->variant->intclr_check) {
> + ret = starfive_wdt_wait_int_free(wdt);
> + if (ret)
> + return dev_err_probe(wdt->wdd.parent, ret,
> + "watchdog is not ready to clear interrupt.\n");
> + }
> + writel(STARFIVE_WDT_INTCLR, wdt->base + wdt->variant->int_clr);
> +
> + return 0;
> +}
> +
> +static inline void starfive_wdt_set_count(struct starfive_wdt *wdt, u32 val)
> +{
> + writel(val, wdt->base + wdt->variant->load);
> +}
> +
> +static inline u32 starfive_wdt_get_count(struct starfive_wdt *wdt)
> +{
> + return readl(wdt->base + wdt->variant->value);
> +}
> +
> +/* enable watchdog */
> +static inline void starfive_wdt_enable(struct starfive_wdt *wdt)
> +{
> + u32 val;
> +
> + val = readl(wdt->base + wdt->variant->enable);
> + val |= STARFIVE_WDT_ENABLE << wdt->variant->en_shift;
> + writel(val, wdt->base + wdt->variant->enable);
> +}
> +
> +/* disable watchdog */
> +static inline void starfive_wdt_disable(struct starfive_wdt *wdt)
> +{
> + u32 val;
> +
> + val = readl(wdt->base + wdt->variant->enable);
> + val &= ~(STARFIVE_WDT_ENABLE << wdt->variant->en_shift);
> + writel(val, wdt->base + wdt->variant->enable);
> +}
> +
> +static inline void starfive_wdt_set_reload_count(struct starfive_wdt *wdt, u32 count)
> +{
> + starfive_wdt_set_count(wdt, count);
> +
> + /* 7100 need set any value to reload register and could reload value to counter */
> + if (wdt->variant->reload)
> + writel(0x1, wdt->base + wdt->variant->reload);
> +}
> +
> +static unsigned int starfive_wdt_max_timeout(struct starfive_wdt *wdt)
> +{
> + if (wdt->variant->double_timeout)
> + return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, (wdt->freq / 2)) - 1;
> +
> + return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, wdt->freq) - 1;
> +}
> +
> +static unsigned int starfive_wdt_get_timeleft(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + u32 count;
> +
> + /*
> + * If the watchdog takes twice timeout and set half count value,
> + * timeleft value should add the count value before first timeout.
> + */
> + count = starfive_wdt_get_count(wdt);
> + if (wdt->variant->double_timeout && !starfive_wdt_raise_irq_status(wdt))
> + count += wdt->count;
> +
> + return starfive_wdt_ticks_to_sec(wdt, count);
> +}
> +
> +static int starfive_wdt_keepalive(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + int ret;
> +
> + starfive_wdt_unlock(wdt);
> + ret = starfive_wdt_int_clr(wdt);
> + if (ret)
> + goto exit;
> +
> + starfive_wdt_set_reload_count(wdt, wdt->count);
> +
> +exit:
> + /* exit with releasing spinlock and locking registers */
> + starfive_wdt_lock(wdt);
> + return ret;
> +}
> +
> +static int starfive_wdt_start(struct starfive_wdt *wdt)
> +{
> + int ret;
> +
> + starfive_wdt_unlock(wdt);
> + /* disable watchdog, to be safe */
> + starfive_wdt_disable(wdt);
> +
> + starfive_wdt_enable_reset(wdt);
> + ret = starfive_wdt_int_clr(wdt);
> + if (ret)
> + goto exit;
> +
> + starfive_wdt_set_count(wdt, wdt->count);
> + starfive_wdt_enable(wdt);
> +
> +exit:
> + starfive_wdt_lock(wdt);
> + return ret;
> +}
> +
> +static void starfive_wdt_stop(struct starfive_wdt *wdt)
> +{
> + starfive_wdt_unlock(wdt);
> + starfive_wdt_disable(wdt);
> + starfive_wdt_lock(wdt);
> +}
> +
> +static int starfive_wdt_pm_start(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + int ret = pm_runtime_get_sync(wdd->parent);
> +
> + if (ret < 0)
> + return ret;
> +
> + return starfive_wdt_start(wdt);
> +}
> +
> +static int starfive_wdt_pm_stop(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> +
> + starfive_wdt_stop(wdt);
> + return pm_runtime_put_sync(wdd->parent);
> +}
> +
> +static int starfive_wdt_set_timeout(struct watchdog_device *wdd,
> + unsigned int timeout)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + unsigned long count = timeout * wdt->freq;
> +
> + /* some watchdogs take two timeouts to reset */
> + if (wdt->variant->double_timeout)
> + count /= 2;
> +
> + wdt->count = count;
> + wdd->timeout = timeout;
> +
> + starfive_wdt_unlock(wdt);
> + starfive_wdt_disable(wdt);
> + starfive_wdt_set_reload_count(wdt, wdt->count);
> + starfive_wdt_enable(wdt);
> + starfive_wdt_lock(wdt);
> +
> + return 0;
> +}
> +
> +#define STARFIVE_WDT_OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
> +
> +static const struct watchdog_info starfive_wdt_info = {
> + .options = STARFIVE_WDT_OPTIONS,
> + .identity = "StarFive Watchdog",
> +};
> +
> +static const struct watchdog_ops starfive_wdt_ops = {
> + .owner = THIS_MODULE,
> + .start = starfive_wdt_pm_start,
> + .stop = starfive_wdt_pm_stop,
> + .ping = starfive_wdt_keepalive,
> + .set_timeout = starfive_wdt_set_timeout,
> + .get_timeleft = starfive_wdt_get_timeleft,
> +};
> +
> +static int starfive_wdt_probe(struct platform_device *pdev)
> +{
> + struct starfive_wdt *wdt;
> + int ret;
> +
> + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
> + if (!wdt)
> + return -ENOMEM;
> +
> + wdt->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(wdt->base))
> + return dev_err_probe(&pdev->dev, PTR_ERR(wdt->base), "error mapping registers\n");
> +
> + wdt->wdd.parent = &pdev->dev;
> + ret = starfive_wdt_get_clock(wdt);
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, wdt);
> + pm_runtime_enable(&pdev->dev);
> + if (pm_runtime_enabled(&pdev->dev)) {
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (ret < 0)
> + return ret;
> + } else {
> + /* runtime PM is disabled but clocks need to be enabled */
> + ret = starfive_wdt_enable_clock(wdt);
> + if (ret)
> + return ret;
> + }
> +
> + ret = starfive_wdt_reset_init(&pdev->dev);
> + if (ret)
> + goto err_exit;
> +
> + watchdog_set_drvdata(&wdt->wdd, wdt);
> + wdt->wdd.info = &starfive_wdt_info;
> + wdt->wdd.ops = &starfive_wdt_ops;
> + wdt->variant = of_device_get_match_data(&pdev->dev);
> + spin_lock_init(&wdt->lock);
> +
> + wdt->freq = clk_get_rate(wdt->core_clk);
> + if (!wdt->freq) {
> + dev_err(&pdev->dev, "get clock rate failed.\n");
> + ret = -EINVAL;
> + goto err_exit;
> + }
> +
> + wdt->wdd.min_timeout = 1;
> + wdt->wdd.max_timeout = starfive_wdt_max_timeout(wdt);
> + wdt->wdd.timeout = STARFIVE_WDT_DEFAULT_TIME;
> + watchdog_init_timeout(&wdt->wdd, heartbeat, &pdev->dev);
> + starfive_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
> +
> + watchdog_set_nowayout(&wdt->wdd, nowayout);
> + watchdog_stop_on_reboot(&wdt->wdd);
> + watchdog_stop_on_unregister(&wdt->wdd);
> +
> + if (early_enable) {
> + ret = starfive_wdt_start(wdt);
> + if (ret)
> + goto err_exit;
> + set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
> + } else {
> + starfive_wdt_stop(wdt);
> + }
> +
> + ret = watchdog_register_device(&wdt->wdd);
> + if (ret)
> + goto err_exit;
> +
> + if (!early_enable)
> + return pm_runtime_put_sync(&pdev->dev);
> +
> + return 0;
> +
> +err_exit:
> + starfive_wdt_disable_clock(wdt);
> + pm_runtime_disable(&pdev->dev);
> +
> + return ret;
> +}
> +
> +static int starfive_wdt_remove(struct platform_device *pdev)
> +{
> + struct starfive_wdt *wdt = platform_get_drvdata(pdev);
> +
> + starfive_wdt_stop(wdt);
> + watchdog_unregister_device(&wdt->wdd);
> +
> + if (pm_runtime_enabled(&pdev->dev))
> + pm_runtime_disable(&pdev->dev);
> + else
> + /* disable clock without PM */
> + starfive_wdt_disable_clock(wdt);
> +
> + return 0;
> +}
> +
> +static void starfive_wdt_shutdown(struct platform_device *pdev)
> +{
> + struct starfive_wdt *wdt = platform_get_drvdata(pdev);
> +
> + starfive_wdt_pm_stop(&wdt->wdd);
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int starfive_wdt_suspend(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> +
> + /* Save watchdog state, and turn it off. */
> + wdt->reload = starfive_wdt_get_count(wdt);
> +
> + /* Note that WTCNT doesn't need to be saved. */
> + starfive_wdt_stop(wdt);
> +
> + return pm_runtime_force_suspend(dev);
> +}
> +
> +static int starfive_wdt_resume(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = pm_runtime_force_resume(dev);
> + if (ret)
> + return ret;
> +
> + starfive_wdt_unlock(wdt);
> + /* Restore watchdog state. */
> + starfive_wdt_set_reload_count(wdt, wdt->reload);
> + starfive_wdt_lock(wdt);
> +
> + return starfive_wdt_start(wdt);
> +}
> +#endif /* CONFIG_PM_SLEEP */
> +
> +#ifdef CONFIG_PM
> +static int starfive_wdt_runtime_suspend(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> +
> + starfive_wdt_disable_clock(wdt);
> +
> + return 0;
> +}
> +
> +static int starfive_wdt_runtime_resume(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> +
> + return starfive_wdt_enable_clock(wdt);
> +}
> +#endif /* CONFIG_PM */
> +
> +static const struct dev_pm_ops starfive_wdt_pm_ops = {
> + SET_RUNTIME_PM_OPS(starfive_wdt_runtime_suspend, starfive_wdt_runtime_resume, NULL)
> + SET_SYSTEM_SLEEP_PM_OPS(starfive_wdt_suspend, starfive_wdt_resume)
> +};
> +
> +static const struct of_device_id starfive_wdt_match[] = {
> + { .compatible = "starfive,jh7100-wdt", .data = &starfive_wdt_jh7100_variant },
> + { .compatible = "starfive,jh7110-wdt", .data = &starfive_wdt_jh7110_variant },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, starfive_wdt_match);
> +
> +static struct platform_driver starfive_wdt_driver = {
> + .probe = starfive_wdt_probe,
> + .remove = starfive_wdt_remove,
> + .shutdown = starfive_wdt_shutdown,
> + .driver = {
> + .name = "starfive-wdt",
> + .pm = &starfive_wdt_pm_ops,
> + .of_match_table = of_match_ptr(starfive_wdt_match),
> + },
> +};
> +module_platform_driver(starfive_wdt_driver);
> +
> +MODULE_AUTHOR("Xingyu Wu <[email protected]>");
> +MODULE_AUTHOR("Samin Guo <[email protected]>");
> +MODULE_DESCRIPTION("StarFive Watchdog Device Driver");
> +MODULE_LICENSE("GPL");
> --
> 2.25.1
>
>
> _______________________________________________
> linux-riscv mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-riscv

2023-04-04 10:00:08

by Xingyu Wu

[permalink] [raw]
Subject: Re: [PATCH v5 2/3] drivers: watchdog: Add StarFive Watchdog driver

On 2023/3/14 21:24, Xingyu Wu wrote:
> Add watchdog driver for the StarFive JH7100 and JH7110 SoC.
>
> Signed-off-by: Xingyu Wu <[email protected]>
> ---
> MAINTAINERS | 7 +
> drivers/watchdog/Kconfig | 11 +
> drivers/watchdog/Makefile | 3 +
> drivers/watchdog/starfive-wdt.c | 606 ++++++++++++++++++++++++++++++++
> 4 files changed, 627 insertions(+)
> create mode 100644 drivers/watchdog/starfive-wdt.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8d5bc223f305..721d0e4e8a0d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -19962,6 +19962,13 @@ S: Supported
> F: Documentation/devicetree/bindings/rng/starfive*
> F: drivers/char/hw_random/jh7110-trng.c
>
> +STARFIVE WATCHDOG DRIVER
> +M: Xingyu Wu <[email protected]>
> +M: Samin Guo <[email protected]>
> +S: Supported
> +F: Documentation/devicetree/bindings/watchdog/starfive*
> +F: drivers/watchdog/starfive-wdt.c
> +
> STATIC BRANCH/CALL
> M: Peter Zijlstra <[email protected]>
> M: Josh Poimboeuf <[email protected]>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index f0872970daf9..f22138709bf5 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -1999,6 +1999,17 @@ config WATCHDOG_RTAS
> To compile this driver as a module, choose M here. The module
> will be called wdrtas.
>
> +# RISC-V Architecture
> +
> +config STARFIVE_WATCHDOG
> + tristate "StarFive Watchdog support"
> + depends on ARCH_STARFIVE || COMPILE_TEST
> + select WATCHDOG_CORE
> + default ARCH_STARFIVE
> + help
> + Say Y here to support the watchdog of StarFive JH7100 and JH7110
> + SoC. This driver can also be built as a module if choose M.
> +
> # S390 Architecture
>
> config DIAG288_WATCHDOG
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 9cbf6580f16c..b4c4ccf2d703 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -192,6 +192,9 @@ obj-$(CONFIG_MEN_A21_WDT) += mena21_wdt.o
> obj-$(CONFIG_PSERIES_WDT) += pseries-wdt.o
> obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o
>
> +# RISC-V Architecture
> +obj-$(CONFIG_STARFIVE_WATCHDOG) += starfive-wdt.o
> +
> # S390 Architecture
> obj-$(CONFIG_DIAG288_WATCHDOG) += diag288_wdt.o
>
> diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c
> new file mode 100644
> index 000000000000..1995cceca51e
> --- /dev/null
> +++ b/drivers/watchdog/starfive-wdt.c
> @@ -0,0 +1,606 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Starfive Watchdog driver
> + *
> + * Copyright (C) 2022 StarFive Technology Co., Ltd.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +#include <linux/watchdog.h>
> +
> +/* JH7100 Watchdog register define */
> +#define STARFIVE_WDT_JH7100_INTSTAUS 0x000
> +#define STARFIVE_WDT_JH7100_CONTROL 0x104
> +#define STARFIVE_WDT_JH7100_LOAD 0x108
> +#define STARFIVE_WDT_JH7100_EN 0x110
> +#define STARFIVE_WDT_JH7100_RELOAD 0x114 /* Write 0 or 1 to reload preset value */
> +#define STARFIVE_WDT_JH7100_VALUE 0x118
> +#define STARFIVE_WDT_JH7100_INTCLR 0x120 /*
> + * [0]: Write 1 to clear interrupt
> + * [1]: 1 mean clearing and 0 mean complete
> + * [31:2]: reserved.
> + */
> +#define STARFIVE_WDT_JH7100_LOCK 0x13c /* write 0x378f0765 to unlock */
> +
> +/* JH7110 Watchdog register define */
> +#define STARFIVE_WDT_JH7110_LOAD 0x000
> +#define STARFIVE_WDT_JH7110_VALUE 0x004
> +#define STARFIVE_WDT_JH7110_CONTROL 0x008 /*
> + * [0]: reset enable;
> + * [1]: interrupt enable && watchdog enable
> + * [31:2]: reserved.
> + */
> +#define STARFIVE_WDT_JH7110_INTCLR 0x00c /* clear intterupt and reload the counter */
> +#define STARFIVE_WDT_JH7110_IMS 0x014
> +#define STARFIVE_WDT_JH7110_LOCK 0xc00 /* write 0x1ACCE551 to unlock */
> +
> +/* WDOGCONTROL */
> +#define STARFIVE_WDT_ENABLE 0x1
> +#define STARFIVE_WDT_EN_SHIFT 0
> +#define STARFIVE_WDT_RESET_EN 0x1
> +#define STARFIVE_WDT_JH7100_RST_EN_SHIFT 0
> +#define STARFIVE_WDT_JH7110_RST_EN_SHIFT 1
> +
> +/* WDOGLOCK */
> +#define STARFIVE_WDT_JH7100_UNLOCK_KEY 0x378f0765
> +#define STARFIVE_WDT_JH7110_UNLOCK_KEY 0x1acce551
> +
> +/* WDOGINTCLR */
> +#define STARFIVE_WDT_INTCLR 0x1
> +#define STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT 1 /* Watchdog can clear interrupt when 0 */
> +
> +#define STARFIVE_WDT_MAXCNT 0xffffffff
> +#define STARFIVE_WDT_DEFAULT_TIME (15)
> +#define STARFIVE_WDT_DELAY_US 0
> +#define STARFIVE_WDT_TIMEOUT_US 10000
> +
> +/* module parameter */
> +#define STARFIVE_WDT_EARLY_ENA 0
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +static int heartbeat;
> +static bool early_enable = STARFIVE_WDT_EARLY_ENA;
> +
> +module_param(heartbeat, int, 0);
> +module_param(early_enable, bool, 0);
> +module_param(nowayout, bool, 0);
> +
> +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
> + __MODULE_STRING(STARFIVE_WDT_DEFAULT_TIME) ")");
> +MODULE_PARM_DESC(early_enable,
> + "Watchdog is started at boot time if set to 1, default="
> + __MODULE_STRING(STARFIVE_WDT_EARLY_ENA));
> +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
> + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +struct starfive_wdt_variant {
> + unsigned int control; /* Watchdog Control Resgister for reset enable */
> + unsigned int load; /* Watchdog Load register */
> + unsigned int reload; /* Watchdog Reload Control register */
> + unsigned int enable; /* Watchdog Enable Register */
> + unsigned int value; /* Watchdog Counter Value Register */
> + unsigned int int_clr; /* Watchdog Interrupt Clear Register */
> + unsigned int unlock; /* Watchdog Lock Register */
> + unsigned int int_status; /* Watchdog Interrupt Status Register */
> +
> + u32 unlock_key;
> + char enrst_shift;
> + char en_shift;
> + bool intclr_check; /* whether need to check it before clearing interrupt */
> + char intclr_ava_shift;
> + bool double_timeout; /* The watchdog need twice timeout to reboot */
> +};
> +
> +struct starfive_wdt {
> + struct watchdog_device wdd;
> + spinlock_t lock; /* spinlock for register handling */
> + void __iomem *base;
> + struct clk *core_clk;
> + struct clk *apb_clk;
> + const struct starfive_wdt_variant *variant;
> + unsigned long freq;
> + u32 count; /* count of timeout */
> + u32 reload; /* restore the count */
> +};
> +
> +/* Register layout and configuration for the JH7100 */
> +static const struct starfive_wdt_variant starfive_wdt_jh7100_variant = {
> + .control = STARFIVE_WDT_JH7100_CONTROL,
> + .load = STARFIVE_WDT_JH7100_LOAD,
> + .reload = STARFIVE_WDT_JH7100_RELOAD,
> + .enable = STARFIVE_WDT_JH7100_EN,
> + .value = STARFIVE_WDT_JH7100_VALUE,
> + .int_clr = STARFIVE_WDT_JH7100_INTCLR,
> + .unlock = STARFIVE_WDT_JH7100_LOCK,
> + .unlock_key = STARFIVE_WDT_JH7100_UNLOCK_KEY,
> + .int_status = STARFIVE_WDT_JH7100_INTSTAUS,
> + .enrst_shift = STARFIVE_WDT_JH7100_RST_EN_SHIFT,
> + .en_shift = STARFIVE_WDT_EN_SHIFT,
> + .intclr_check = true,
> + .intclr_ava_shift = STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT,
> + .double_timeout = false,
> +};
> +
> +/* Register layout and configuration for the JH7110 */
> +static const struct starfive_wdt_variant starfive_wdt_jh7110_variant = {
> + .control = STARFIVE_WDT_JH7110_CONTROL,
> + .load = STARFIVE_WDT_JH7110_LOAD,
> + .enable = STARFIVE_WDT_JH7110_CONTROL,
> + .value = STARFIVE_WDT_JH7110_VALUE,
> + .int_clr = STARFIVE_WDT_JH7110_INTCLR,
> + .unlock = STARFIVE_WDT_JH7110_LOCK,
> + .unlock_key = STARFIVE_WDT_JH7110_UNLOCK_KEY,
> + .int_status = STARFIVE_WDT_JH7110_IMS,
> + .enrst_shift = STARFIVE_WDT_JH7110_RST_EN_SHIFT,
> + .en_shift = STARFIVE_WDT_EN_SHIFT,
> + .intclr_check = false,
> + .double_timeout = true,
> +};
> +
> +static int starfive_wdt_enable_clock(struct starfive_wdt *wdt)
> +{
> + int ret;
> +
> + ret = clk_prepare_enable(wdt->apb_clk);
> + if (ret)
> + return dev_err_probe(wdt->wdd.parent, ret, "failed to enable apb clock\n");
> +
> + ret = clk_prepare_enable(wdt->core_clk);
> + if (ret)
> + return dev_err_probe(wdt->wdd.parent, ret, "failed to enable core clock\n");
> +
> + return 0;
> +}
> +
> +static void starfive_wdt_disable_clock(struct starfive_wdt *wdt)
> +{
> + clk_disable_unprepare(wdt->core_clk);
> + clk_disable_unprepare(wdt->apb_clk);
> +}
> +
> +static inline int starfive_wdt_get_clock(struct starfive_wdt *wdt)
> +{
> + struct device *dev = wdt->wdd.parent;
> +
> + wdt->apb_clk = devm_clk_get(dev, "apb");
> + if (IS_ERR(wdt->apb_clk))
> + return dev_err_probe(dev, PTR_ERR(wdt->apb_clk), "failed to get apb clock\n");
> +
> + wdt->core_clk = devm_clk_get(dev, "core");
> + if (IS_ERR(wdt->core_clk))
> + return dev_err_probe(dev, PTR_ERR(wdt->core_clk), "failed to get core clock\n");
> +
> + return 0;
> +}
> +
> +static inline int starfive_wdt_reset_init(struct device *dev)
> +{
> + struct reset_control *rsts;
> + int ret;
> +
> + rsts = devm_reset_control_array_get_exclusive(dev);
> + if (IS_ERR(rsts))
> + return dev_err_probe(dev, PTR_ERR(rsts), "failed to get resets\n");
> +
> + ret = reset_control_deassert(rsts);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to deassert resets\n");
> +
> + return 0;
> +}
> +
> +static u32 starfive_wdt_ticks_to_sec(struct starfive_wdt *wdt, u32 ticks)
> +{
> + return DIV_ROUND_CLOSEST(ticks, wdt->freq);
> +}
> +
> +/* Write unlock-key to unlock. Write other value to lock. */
> +static void starfive_wdt_unlock(struct starfive_wdt *wdt)
> +{
> + spin_lock(&wdt->lock);
> + writel(wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
> +}
> +
> +static void starfive_wdt_lock(struct starfive_wdt *wdt)
> +{
> + writel(~wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
> + spin_unlock(&wdt->lock);
> +}
> +
> +/* enable watchdog interrupt to reset/reboot */
> +static void starfive_wdt_enable_reset(struct starfive_wdt *wdt)
> +{
> + u32 val;
> +
> + val = readl(wdt->base + wdt->variant->control);
> + val |= STARFIVE_WDT_RESET_EN << wdt->variant->enrst_shift;
> + writel(val, wdt->base + wdt->variant->control);
> +}
> +
> +/* interrupt status whether has been raised from the counter */
> +static bool starfive_wdt_raise_irq_status(struct starfive_wdt *wdt)
> +{
> + return !!readl(wdt->base + wdt->variant->int_status);
> +}
> +
> +/* waiting interrupt can be free to clear */
> +static int starfive_wdt_wait_int_free(struct starfive_wdt *wdt)
> +{
> + u32 value;
> +
> + return readl_poll_timeout_atomic(wdt->base + wdt->variant->int_clr, value,
> + !(value & BIT(wdt->variant->intclr_ava_shift)),
> + STARFIVE_WDT_DELAY_US, STARFIVE_WDT_TIMEOUT_US);
> +}
> +
> +/* clear interrupt signal before initialization or reload */
> +static int starfive_wdt_int_clr(struct starfive_wdt *wdt)
> +{
> + int ret;
> +
> + if (wdt->variant->intclr_check) {
> + ret = starfive_wdt_wait_int_free(wdt);
> + if (ret)
> + return dev_err_probe(wdt->wdd.parent, ret,
> + "watchdog is not ready to clear interrupt.\n");
> + }
> + writel(STARFIVE_WDT_INTCLR, wdt->base + wdt->variant->int_clr);
> +
> + return 0;
> +}
> +
> +static inline void starfive_wdt_set_count(struct starfive_wdt *wdt, u32 val)
> +{
> + writel(val, wdt->base + wdt->variant->load);
> +}
> +
> +static inline u32 starfive_wdt_get_count(struct starfive_wdt *wdt)
> +{
> + return readl(wdt->base + wdt->variant->value);
> +}
> +
> +/* enable watchdog */
> +static inline void starfive_wdt_enable(struct starfive_wdt *wdt)
> +{
> + u32 val;
> +
> + val = readl(wdt->base + wdt->variant->enable);
> + val |= STARFIVE_WDT_ENABLE << wdt->variant->en_shift;
> + writel(val, wdt->base + wdt->variant->enable);
> +}
> +
> +/* disable watchdog */
> +static inline void starfive_wdt_disable(struct starfive_wdt *wdt)
> +{
> + u32 val;
> +
> + val = readl(wdt->base + wdt->variant->enable);
> + val &= ~(STARFIVE_WDT_ENABLE << wdt->variant->en_shift);
> + writel(val, wdt->base + wdt->variant->enable);
> +}
> +
> +static inline void starfive_wdt_set_reload_count(struct starfive_wdt *wdt, u32 count)
> +{
> + starfive_wdt_set_count(wdt, count);
> +
> + /* 7100 need set any value to reload register and could reload value to counter */
> + if (wdt->variant->reload)
> + writel(0x1, wdt->base + wdt->variant->reload);
> +}
> +
> +static unsigned int starfive_wdt_max_timeout(struct starfive_wdt *wdt)
> +{
> + if (wdt->variant->double_timeout)
> + return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, (wdt->freq / 2)) - 1;
> +
> + return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, wdt->freq) - 1;
> +}
> +
> +static unsigned int starfive_wdt_get_timeleft(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + u32 count;
> +
> + /*
> + * If the watchdog takes twice timeout and set half count value,
> + * timeleft value should add the count value before first timeout.
> + */
> + count = starfive_wdt_get_count(wdt);
> + if (wdt->variant->double_timeout && !starfive_wdt_raise_irq_status(wdt))
> + count += wdt->count;
> +
> + return starfive_wdt_ticks_to_sec(wdt, count);
> +}
> +
> +static int starfive_wdt_keepalive(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + int ret;
> +
> + starfive_wdt_unlock(wdt);
> + ret = starfive_wdt_int_clr(wdt);
> + if (ret)
> + goto exit;
> +
> + starfive_wdt_set_reload_count(wdt, wdt->count);
> +
> +exit:
> + /* exit with releasing spinlock and locking registers */
> + starfive_wdt_lock(wdt);
> + return ret;
> +}
> +
> +static int starfive_wdt_start(struct starfive_wdt *wdt)
> +{
> + int ret;
> +
> + starfive_wdt_unlock(wdt);
> + /* disable watchdog, to be safe */
> + starfive_wdt_disable(wdt);
> +
> + starfive_wdt_enable_reset(wdt);
> + ret = starfive_wdt_int_clr(wdt);
> + if (ret)
> + goto exit;
> +
> + starfive_wdt_set_count(wdt, wdt->count);
> + starfive_wdt_enable(wdt);
> +
> +exit:
> + starfive_wdt_lock(wdt);
> + return ret;
> +}
> +
> +static void starfive_wdt_stop(struct starfive_wdt *wdt)
> +{
> + starfive_wdt_unlock(wdt);
> + starfive_wdt_disable(wdt);
> + starfive_wdt_lock(wdt);
> +}
> +
> +static int starfive_wdt_pm_start(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + int ret = pm_runtime_get_sync(wdd->parent);
> +
> + if (ret < 0)
> + return ret;
> +
> + return starfive_wdt_start(wdt);
> +}
> +
> +static int starfive_wdt_pm_stop(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> +
> + starfive_wdt_stop(wdt);
> + return pm_runtime_put_sync(wdd->parent);
> +}
> +
> +static int starfive_wdt_set_timeout(struct watchdog_device *wdd,
> + unsigned int timeout)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + unsigned long count = timeout * wdt->freq;
> +
> + /* some watchdogs take two timeouts to reset */
> + if (wdt->variant->double_timeout)
> + count /= 2;
> +
> + wdt->count = count;
> + wdd->timeout = timeout;
> +
> + starfive_wdt_unlock(wdt);
> + starfive_wdt_disable(wdt);
> + starfive_wdt_set_reload_count(wdt, wdt->count);
> + starfive_wdt_enable(wdt);
> + starfive_wdt_lock(wdt);
> +
> + return 0;
> +}
> +
> +#define STARFIVE_WDT_OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
> +
> +static const struct watchdog_info starfive_wdt_info = {
> + .options = STARFIVE_WDT_OPTIONS,
> + .identity = "StarFive Watchdog",
> +};
> +
> +static const struct watchdog_ops starfive_wdt_ops = {
> + .owner = THIS_MODULE,
> + .start = starfive_wdt_pm_start,
> + .stop = starfive_wdt_pm_stop,
> + .ping = starfive_wdt_keepalive,
> + .set_timeout = starfive_wdt_set_timeout,
> + .get_timeleft = starfive_wdt_get_timeleft,
> +};
> +
> +static int starfive_wdt_probe(struct platform_device *pdev)
> +{
> + struct starfive_wdt *wdt;
> + int ret;
> +
> + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
> + if (!wdt)
> + return -ENOMEM;
> +
> + wdt->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(wdt->base))
> + return dev_err_probe(&pdev->dev, PTR_ERR(wdt->base), "error mapping registers\n");
> +
> + wdt->wdd.parent = &pdev->dev;
> + ret = starfive_wdt_get_clock(wdt);
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, wdt);
> + pm_runtime_enable(&pdev->dev);
> + if (pm_runtime_enabled(&pdev->dev)) {
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (ret < 0)
> + return ret;
> + } else {
> + /* runtime PM is disabled but clocks need to be enabled */
> + ret = starfive_wdt_enable_clock(wdt);
> + if (ret)
> + return ret;
> + }
> +
> + ret = starfive_wdt_reset_init(&pdev->dev);
> + if (ret)
> + goto err_exit;
> +
> + watchdog_set_drvdata(&wdt->wdd, wdt);
> + wdt->wdd.info = &starfive_wdt_info;
> + wdt->wdd.ops = &starfive_wdt_ops;
> + wdt->variant = of_device_get_match_data(&pdev->dev);
> + spin_lock_init(&wdt->lock);
> +
> + wdt->freq = clk_get_rate(wdt->core_clk);
> + if (!wdt->freq) {
> + dev_err(&pdev->dev, "get clock rate failed.\n");
> + ret = -EINVAL;
> + goto err_exit;
> + }
> +
> + wdt->wdd.min_timeout = 1;
> + wdt->wdd.max_timeout = starfive_wdt_max_timeout(wdt);
> + wdt->wdd.timeout = STARFIVE_WDT_DEFAULT_TIME;
> + watchdog_init_timeout(&wdt->wdd, heartbeat, &pdev->dev);
> + starfive_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
> +
> + watchdog_set_nowayout(&wdt->wdd, nowayout);
> + watchdog_stop_on_reboot(&wdt->wdd);
> + watchdog_stop_on_unregister(&wdt->wdd);
> +
> + if (early_enable) {
> + ret = starfive_wdt_start(wdt);
> + if (ret)
> + goto err_exit;
> + set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
> + } else {
> + starfive_wdt_stop(wdt);
> + }
> +
> + ret = watchdog_register_device(&wdt->wdd);
> + if (ret)
> + goto err_exit;
> +
> + if (!early_enable)
> + return pm_runtime_put_sync(&pdev->dev);
> +
> + return 0;
> +
> +err_exit:
> + starfive_wdt_disable_clock(wdt);
> + pm_runtime_disable(&pdev->dev);
> +
> + return ret;
> +}
> +
> +static int starfive_wdt_remove(struct platform_device *pdev)
> +{
> + struct starfive_wdt *wdt = platform_get_drvdata(pdev);
> +
> + starfive_wdt_stop(wdt);
> + watchdog_unregister_device(&wdt->wdd);
> +
> + if (pm_runtime_enabled(&pdev->dev))
> + pm_runtime_disable(&pdev->dev);
> + else
> + /* disable clock without PM */
> + starfive_wdt_disable_clock(wdt);
> +
> + return 0;
> +}
> +
> +static void starfive_wdt_shutdown(struct platform_device *pdev)
> +{
> + struct starfive_wdt *wdt = platform_get_drvdata(pdev);
> +
> + starfive_wdt_pm_stop(&wdt->wdd);
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int starfive_wdt_suspend(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> +
> + /* Save watchdog state, and turn it off. */
> + wdt->reload = starfive_wdt_get_count(wdt);
> +
> + /* Note that WTCNT doesn't need to be saved. */
> + starfive_wdt_stop(wdt);
> +
> + return pm_runtime_force_suspend(dev);
> +}
> +
> +static int starfive_wdt_resume(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = pm_runtime_force_resume(dev);
> + if (ret)
> + return ret;
> +
> + starfive_wdt_unlock(wdt);
> + /* Restore watchdog state. */
> + starfive_wdt_set_reload_count(wdt, wdt->reload);
> + starfive_wdt_lock(wdt);
> +
> + return starfive_wdt_start(wdt);
> +}
> +#endif /* CONFIG_PM_SLEEP */
> +
> +#ifdef CONFIG_PM
> +static int starfive_wdt_runtime_suspend(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> +
> + starfive_wdt_disable_clock(wdt);
> +
> + return 0;
> +}
> +
> +static int starfive_wdt_runtime_resume(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> +
> + return starfive_wdt_enable_clock(wdt);
> +}
> +#endif /* CONFIG_PM */
> +
> +static const struct dev_pm_ops starfive_wdt_pm_ops = {
> + SET_RUNTIME_PM_OPS(starfive_wdt_runtime_suspend, starfive_wdt_runtime_resume, NULL)
> + SET_SYSTEM_SLEEP_PM_OPS(starfive_wdt_suspend, starfive_wdt_resume)
> +};
> +
> +static const struct of_device_id starfive_wdt_match[] = {
> + { .compatible = "starfive,jh7100-wdt", .data = &starfive_wdt_jh7100_variant },
> + { .compatible = "starfive,jh7110-wdt", .data = &starfive_wdt_jh7110_variant },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, starfive_wdt_match);
> +
> +static struct platform_driver starfive_wdt_driver = {
> + .probe = starfive_wdt_probe,
> + .remove = starfive_wdt_remove,
> + .shutdown = starfive_wdt_shutdown,
> + .driver = {
> + .name = "starfive-wdt",
> + .pm = &starfive_wdt_pm_ops,
> + .of_match_table = of_match_ptr(starfive_wdt_match),
> + },
> +};
> +module_platform_driver(starfive_wdt_driver);
> +
> +MODULE_AUTHOR("Xingyu Wu <[email protected]>");
> +MODULE_AUTHOR("Samin Guo <[email protected]>");
> +MODULE_DESCRIPTION("StarFive Watchdog Device Driver");
> +MODULE_LICENSE("GPL");

Hi Guenter & Wim,

Could you please help to review and provide comments on these patch series if you have time?
I would like to know what still need to be improved. Thanks.

Best regards,
Xingyu Wu

2023-04-16 16:02:23

by Guenter Roeck

[permalink] [raw]
Subject: Re: [PATCH v5 1/3] dt-bindings: watchdog: Add watchdog for StarFive JH7100 and JH7110

On Tue, Mar 14, 2023 at 09:24:35PM +0800, Xingyu Wu wrote:
> Add bindings to describe the watchdog for the StarFive JH7100/JH7110 SoC.
> And Use JH7100 as first StarFive SoC with watchdog.
>
> Signed-off-by: Xingyu Wu <[email protected]>
> Reviewed-by: Krzysztof Kozlowski <[email protected]>

Reviewed-by: Guenter Roeck <[email protected]>

> ---
> .../watchdog/starfive,jh7100-wdt.yaml | 71 +++++++++++++++++++
> 1 file changed, 71 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/watchdog/starfive,jh7100-wdt.yaml
>
> diff --git a/Documentation/devicetree/bindings/watchdog/starfive,jh7100-wdt.yaml b/Documentation/devicetree/bindings/watchdog/starfive,jh7100-wdt.yaml
> new file mode 100644
> index 000000000000..68f3f6fd08a6
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/watchdog/starfive,jh7100-wdt.yaml
> @@ -0,0 +1,71 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/watchdog/starfive,jh7100-wdt.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: StarFive Watchdog for JH7100 and JH7110 SoC
> +
> +maintainers:
> + - Xingyu Wu <[email protected]>
> + - Samin Guo <[email protected]>
> +
> +description:
> + The JH7100 and JH7110 watchdog both are 32 bit counters. JH7100 watchdog
> + has only one timeout phase and reboots. And JH7110 watchdog has two
> + timeout phases. At the first phase, the signal of watchdog interrupt
> + output(WDOGINT) will rise when counter is 0. The counter will reload
> + the timeout value. And then, if counter decreases to 0 again and WDOGINT
> + isn't cleared, the watchdog will reset the system unless the watchdog
> + reset is disabled.
> +
> +allOf:
> + - $ref: watchdog.yaml#
> +
> +properties:
> + compatible:
> + enum:
> + - starfive,jh7100-wdt
> + - starfive,jh7110-wdt
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + items:
> + - description: APB clock
> + - description: Core clock
> +
> + clock-names:
> + items:
> + - const: apb
> + - const: core
> +
> + resets:
> + items:
> + - description: APB reset
> + - description: Core reset
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> + - resets
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + watchdog@12480000 {
> + compatible = "starfive,jh7100-wdt";
> + reg = <0x12480000 0x10000>;
> + clocks = <&clk 171>,
> + <&clk 172>;
> + clock-names = "apb", "core";
> + resets = <&rst 99>,
> + <&rst 100>;
> + };

2023-04-16 16:03:24

by Guenter Roeck

[permalink] [raw]
Subject: Re: [PATCH v5 2/3] drivers: watchdog: Add StarFive Watchdog driver

On Tue, Mar 14, 2023 at 09:24:36PM +0800, Xingyu Wu wrote:
> Add watchdog driver for the StarFive JH7100 and JH7110 SoC.
>
> Signed-off-by: Xingyu Wu <[email protected]>
> Reviewed-by: Emil Renner Berthing <[email protected]>

Reviewed-by: Guenter Roeck <[email protected]>

> ---
> MAINTAINERS | 7 +
> drivers/watchdog/Kconfig | 11 +
> drivers/watchdog/Makefile | 3 +
> drivers/watchdog/starfive-wdt.c | 606 ++++++++++++++++++++++++++++++++
> 4 files changed, 627 insertions(+)
> create mode 100644 drivers/watchdog/starfive-wdt.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8d5bc223f305..721d0e4e8a0d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -19962,6 +19962,13 @@ S: Supported
> F: Documentation/devicetree/bindings/rng/starfive*
> F: drivers/char/hw_random/jh7110-trng.c
>
> +STARFIVE WATCHDOG DRIVER
> +M: Xingyu Wu <[email protected]>
> +M: Samin Guo <[email protected]>
> +S: Supported
> +F: Documentation/devicetree/bindings/watchdog/starfive*
> +F: drivers/watchdog/starfive-wdt.c
> +
> STATIC BRANCH/CALL
> M: Peter Zijlstra <[email protected]>
> M: Josh Poimboeuf <[email protected]>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index f0872970daf9..f22138709bf5 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -1999,6 +1999,17 @@ config WATCHDOG_RTAS
> To compile this driver as a module, choose M here. The module
> will be called wdrtas.
>
> +# RISC-V Architecture
> +
> +config STARFIVE_WATCHDOG
> + tristate "StarFive Watchdog support"
> + depends on ARCH_STARFIVE || COMPILE_TEST
> + select WATCHDOG_CORE
> + default ARCH_STARFIVE
> + help
> + Say Y here to support the watchdog of StarFive JH7100 and JH7110
> + SoC. This driver can also be built as a module if choose M.
> +
> # S390 Architecture
>
> config DIAG288_WATCHDOG
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 9cbf6580f16c..b4c4ccf2d703 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -192,6 +192,9 @@ obj-$(CONFIG_MEN_A21_WDT) += mena21_wdt.o
> obj-$(CONFIG_PSERIES_WDT) += pseries-wdt.o
> obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o
>
> +# RISC-V Architecture
> +obj-$(CONFIG_STARFIVE_WATCHDOG) += starfive-wdt.o
> +
> # S390 Architecture
> obj-$(CONFIG_DIAG288_WATCHDOG) += diag288_wdt.o
>
> diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c
> new file mode 100644
> index 000000000000..1995cceca51e
> --- /dev/null
> +++ b/drivers/watchdog/starfive-wdt.c
> @@ -0,0 +1,606 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Starfive Watchdog driver
> + *
> + * Copyright (C) 2022 StarFive Technology Co., Ltd.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +#include <linux/watchdog.h>
> +
> +/* JH7100 Watchdog register define */
> +#define STARFIVE_WDT_JH7100_INTSTAUS 0x000
> +#define STARFIVE_WDT_JH7100_CONTROL 0x104
> +#define STARFIVE_WDT_JH7100_LOAD 0x108
> +#define STARFIVE_WDT_JH7100_EN 0x110
> +#define STARFIVE_WDT_JH7100_RELOAD 0x114 /* Write 0 or 1 to reload preset value */
> +#define STARFIVE_WDT_JH7100_VALUE 0x118
> +#define STARFIVE_WDT_JH7100_INTCLR 0x120 /*
> + * [0]: Write 1 to clear interrupt
> + * [1]: 1 mean clearing and 0 mean complete
> + * [31:2]: reserved.
> + */
> +#define STARFIVE_WDT_JH7100_LOCK 0x13c /* write 0x378f0765 to unlock */
> +
> +/* JH7110 Watchdog register define */
> +#define STARFIVE_WDT_JH7110_LOAD 0x000
> +#define STARFIVE_WDT_JH7110_VALUE 0x004
> +#define STARFIVE_WDT_JH7110_CONTROL 0x008 /*
> + * [0]: reset enable;
> + * [1]: interrupt enable && watchdog enable
> + * [31:2]: reserved.
> + */
> +#define STARFIVE_WDT_JH7110_INTCLR 0x00c /* clear intterupt and reload the counter */
> +#define STARFIVE_WDT_JH7110_IMS 0x014
> +#define STARFIVE_WDT_JH7110_LOCK 0xc00 /* write 0x1ACCE551 to unlock */
> +
> +/* WDOGCONTROL */
> +#define STARFIVE_WDT_ENABLE 0x1
> +#define STARFIVE_WDT_EN_SHIFT 0
> +#define STARFIVE_WDT_RESET_EN 0x1
> +#define STARFIVE_WDT_JH7100_RST_EN_SHIFT 0
> +#define STARFIVE_WDT_JH7110_RST_EN_SHIFT 1
> +
> +/* WDOGLOCK */
> +#define STARFIVE_WDT_JH7100_UNLOCK_KEY 0x378f0765
> +#define STARFIVE_WDT_JH7110_UNLOCK_KEY 0x1acce551
> +
> +/* WDOGINTCLR */
> +#define STARFIVE_WDT_INTCLR 0x1
> +#define STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT 1 /* Watchdog can clear interrupt when 0 */
> +
> +#define STARFIVE_WDT_MAXCNT 0xffffffff
> +#define STARFIVE_WDT_DEFAULT_TIME (15)
> +#define STARFIVE_WDT_DELAY_US 0
> +#define STARFIVE_WDT_TIMEOUT_US 10000
> +
> +/* module parameter */
> +#define STARFIVE_WDT_EARLY_ENA 0
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +static int heartbeat;
> +static bool early_enable = STARFIVE_WDT_EARLY_ENA;
> +
> +module_param(heartbeat, int, 0);
> +module_param(early_enable, bool, 0);
> +module_param(nowayout, bool, 0);
> +
> +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
> + __MODULE_STRING(STARFIVE_WDT_DEFAULT_TIME) ")");
> +MODULE_PARM_DESC(early_enable,
> + "Watchdog is started at boot time if set to 1, default="
> + __MODULE_STRING(STARFIVE_WDT_EARLY_ENA));
> +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
> + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +struct starfive_wdt_variant {
> + unsigned int control; /* Watchdog Control Resgister for reset enable */
> + unsigned int load; /* Watchdog Load register */
> + unsigned int reload; /* Watchdog Reload Control register */
> + unsigned int enable; /* Watchdog Enable Register */
> + unsigned int value; /* Watchdog Counter Value Register */
> + unsigned int int_clr; /* Watchdog Interrupt Clear Register */
> + unsigned int unlock; /* Watchdog Lock Register */
> + unsigned int int_status; /* Watchdog Interrupt Status Register */
> +
> + u32 unlock_key;
> + char enrst_shift;
> + char en_shift;
> + bool intclr_check; /* whether need to check it before clearing interrupt */
> + char intclr_ava_shift;
> + bool double_timeout; /* The watchdog need twice timeout to reboot */
> +};
> +
> +struct starfive_wdt {
> + struct watchdog_device wdd;
> + spinlock_t lock; /* spinlock for register handling */
> + void __iomem *base;
> + struct clk *core_clk;
> + struct clk *apb_clk;
> + const struct starfive_wdt_variant *variant;
> + unsigned long freq;
> + u32 count; /* count of timeout */
> + u32 reload; /* restore the count */
> +};
> +
> +/* Register layout and configuration for the JH7100 */
> +static const struct starfive_wdt_variant starfive_wdt_jh7100_variant = {
> + .control = STARFIVE_WDT_JH7100_CONTROL,
> + .load = STARFIVE_WDT_JH7100_LOAD,
> + .reload = STARFIVE_WDT_JH7100_RELOAD,
> + .enable = STARFIVE_WDT_JH7100_EN,
> + .value = STARFIVE_WDT_JH7100_VALUE,
> + .int_clr = STARFIVE_WDT_JH7100_INTCLR,
> + .unlock = STARFIVE_WDT_JH7100_LOCK,
> + .unlock_key = STARFIVE_WDT_JH7100_UNLOCK_KEY,
> + .int_status = STARFIVE_WDT_JH7100_INTSTAUS,
> + .enrst_shift = STARFIVE_WDT_JH7100_RST_EN_SHIFT,
> + .en_shift = STARFIVE_WDT_EN_SHIFT,
> + .intclr_check = true,
> + .intclr_ava_shift = STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT,
> + .double_timeout = false,
> +};
> +
> +/* Register layout and configuration for the JH7110 */
> +static const struct starfive_wdt_variant starfive_wdt_jh7110_variant = {
> + .control = STARFIVE_WDT_JH7110_CONTROL,
> + .load = STARFIVE_WDT_JH7110_LOAD,
> + .enable = STARFIVE_WDT_JH7110_CONTROL,
> + .value = STARFIVE_WDT_JH7110_VALUE,
> + .int_clr = STARFIVE_WDT_JH7110_INTCLR,
> + .unlock = STARFIVE_WDT_JH7110_LOCK,
> + .unlock_key = STARFIVE_WDT_JH7110_UNLOCK_KEY,
> + .int_status = STARFIVE_WDT_JH7110_IMS,
> + .enrst_shift = STARFIVE_WDT_JH7110_RST_EN_SHIFT,
> + .en_shift = STARFIVE_WDT_EN_SHIFT,
> + .intclr_check = false,
> + .double_timeout = true,
> +};
> +
> +static int starfive_wdt_enable_clock(struct starfive_wdt *wdt)
> +{
> + int ret;
> +
> + ret = clk_prepare_enable(wdt->apb_clk);
> + if (ret)
> + return dev_err_probe(wdt->wdd.parent, ret, "failed to enable apb clock\n");
> +
> + ret = clk_prepare_enable(wdt->core_clk);
> + if (ret)
> + return dev_err_probe(wdt->wdd.parent, ret, "failed to enable core clock\n");
> +
> + return 0;
> +}
> +
> +static void starfive_wdt_disable_clock(struct starfive_wdt *wdt)
> +{
> + clk_disable_unprepare(wdt->core_clk);
> + clk_disable_unprepare(wdt->apb_clk);
> +}
> +
> +static inline int starfive_wdt_get_clock(struct starfive_wdt *wdt)
> +{
> + struct device *dev = wdt->wdd.parent;
> +
> + wdt->apb_clk = devm_clk_get(dev, "apb");
> + if (IS_ERR(wdt->apb_clk))
> + return dev_err_probe(dev, PTR_ERR(wdt->apb_clk), "failed to get apb clock\n");
> +
> + wdt->core_clk = devm_clk_get(dev, "core");
> + if (IS_ERR(wdt->core_clk))
> + return dev_err_probe(dev, PTR_ERR(wdt->core_clk), "failed to get core clock\n");
> +
> + return 0;
> +}
> +
> +static inline int starfive_wdt_reset_init(struct device *dev)
> +{
> + struct reset_control *rsts;
> + int ret;
> +
> + rsts = devm_reset_control_array_get_exclusive(dev);
> + if (IS_ERR(rsts))
> + return dev_err_probe(dev, PTR_ERR(rsts), "failed to get resets\n");
> +
> + ret = reset_control_deassert(rsts);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to deassert resets\n");
> +
> + return 0;
> +}
> +
> +static u32 starfive_wdt_ticks_to_sec(struct starfive_wdt *wdt, u32 ticks)
> +{
> + return DIV_ROUND_CLOSEST(ticks, wdt->freq);
> +}
> +
> +/* Write unlock-key to unlock. Write other value to lock. */
> +static void starfive_wdt_unlock(struct starfive_wdt *wdt)
> +{
> + spin_lock(&wdt->lock);
> + writel(wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
> +}
> +
> +static void starfive_wdt_lock(struct starfive_wdt *wdt)
> +{
> + writel(~wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
> + spin_unlock(&wdt->lock);
> +}
> +
> +/* enable watchdog interrupt to reset/reboot */
> +static void starfive_wdt_enable_reset(struct starfive_wdt *wdt)
> +{
> + u32 val;
> +
> + val = readl(wdt->base + wdt->variant->control);
> + val |= STARFIVE_WDT_RESET_EN << wdt->variant->enrst_shift;
> + writel(val, wdt->base + wdt->variant->control);
> +}
> +
> +/* interrupt status whether has been raised from the counter */
> +static bool starfive_wdt_raise_irq_status(struct starfive_wdt *wdt)
> +{
> + return !!readl(wdt->base + wdt->variant->int_status);
> +}
> +
> +/* waiting interrupt can be free to clear */
> +static int starfive_wdt_wait_int_free(struct starfive_wdt *wdt)
> +{
> + u32 value;
> +
> + return readl_poll_timeout_atomic(wdt->base + wdt->variant->int_clr, value,
> + !(value & BIT(wdt->variant->intclr_ava_shift)),
> + STARFIVE_WDT_DELAY_US, STARFIVE_WDT_TIMEOUT_US);
> +}
> +
> +/* clear interrupt signal before initialization or reload */
> +static int starfive_wdt_int_clr(struct starfive_wdt *wdt)
> +{
> + int ret;
> +
> + if (wdt->variant->intclr_check) {
> + ret = starfive_wdt_wait_int_free(wdt);
> + if (ret)
> + return dev_err_probe(wdt->wdd.parent, ret,
> + "watchdog is not ready to clear interrupt.\n");
> + }
> + writel(STARFIVE_WDT_INTCLR, wdt->base + wdt->variant->int_clr);
> +
> + return 0;
> +}
> +
> +static inline void starfive_wdt_set_count(struct starfive_wdt *wdt, u32 val)
> +{
> + writel(val, wdt->base + wdt->variant->load);
> +}
> +
> +static inline u32 starfive_wdt_get_count(struct starfive_wdt *wdt)
> +{
> + return readl(wdt->base + wdt->variant->value);
> +}
> +
> +/* enable watchdog */
> +static inline void starfive_wdt_enable(struct starfive_wdt *wdt)
> +{
> + u32 val;
> +
> + val = readl(wdt->base + wdt->variant->enable);
> + val |= STARFIVE_WDT_ENABLE << wdt->variant->en_shift;
> + writel(val, wdt->base + wdt->variant->enable);
> +}
> +
> +/* disable watchdog */
> +static inline void starfive_wdt_disable(struct starfive_wdt *wdt)
> +{
> + u32 val;
> +
> + val = readl(wdt->base + wdt->variant->enable);
> + val &= ~(STARFIVE_WDT_ENABLE << wdt->variant->en_shift);
> + writel(val, wdt->base + wdt->variant->enable);
> +}
> +
> +static inline void starfive_wdt_set_reload_count(struct starfive_wdt *wdt, u32 count)
> +{
> + starfive_wdt_set_count(wdt, count);
> +
> + /* 7100 need set any value to reload register and could reload value to counter */
> + if (wdt->variant->reload)
> + writel(0x1, wdt->base + wdt->variant->reload);
> +}
> +
> +static unsigned int starfive_wdt_max_timeout(struct starfive_wdt *wdt)
> +{
> + if (wdt->variant->double_timeout)
> + return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, (wdt->freq / 2)) - 1;
> +
> + return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, wdt->freq) - 1;
> +}
> +
> +static unsigned int starfive_wdt_get_timeleft(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + u32 count;
> +
> + /*
> + * If the watchdog takes twice timeout and set half count value,
> + * timeleft value should add the count value before first timeout.
> + */
> + count = starfive_wdt_get_count(wdt);
> + if (wdt->variant->double_timeout && !starfive_wdt_raise_irq_status(wdt))
> + count += wdt->count;
> +
> + return starfive_wdt_ticks_to_sec(wdt, count);
> +}
> +
> +static int starfive_wdt_keepalive(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + int ret;
> +
> + starfive_wdt_unlock(wdt);
> + ret = starfive_wdt_int_clr(wdt);
> + if (ret)
> + goto exit;
> +
> + starfive_wdt_set_reload_count(wdt, wdt->count);
> +
> +exit:
> + /* exit with releasing spinlock and locking registers */
> + starfive_wdt_lock(wdt);
> + return ret;
> +}
> +
> +static int starfive_wdt_start(struct starfive_wdt *wdt)
> +{
> + int ret;
> +
> + starfive_wdt_unlock(wdt);
> + /* disable watchdog, to be safe */
> + starfive_wdt_disable(wdt);
> +
> + starfive_wdt_enable_reset(wdt);
> + ret = starfive_wdt_int_clr(wdt);
> + if (ret)
> + goto exit;
> +
> + starfive_wdt_set_count(wdt, wdt->count);
> + starfive_wdt_enable(wdt);
> +
> +exit:
> + starfive_wdt_lock(wdt);
> + return ret;
> +}
> +
> +static void starfive_wdt_stop(struct starfive_wdt *wdt)
> +{
> + starfive_wdt_unlock(wdt);
> + starfive_wdt_disable(wdt);
> + starfive_wdt_lock(wdt);
> +}
> +
> +static int starfive_wdt_pm_start(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + int ret = pm_runtime_get_sync(wdd->parent);
> +
> + if (ret < 0)
> + return ret;
> +
> + return starfive_wdt_start(wdt);
> +}
> +
> +static int starfive_wdt_pm_stop(struct watchdog_device *wdd)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> +
> + starfive_wdt_stop(wdt);
> + return pm_runtime_put_sync(wdd->parent);
> +}
> +
> +static int starfive_wdt_set_timeout(struct watchdog_device *wdd,
> + unsigned int timeout)
> +{
> + struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
> + unsigned long count = timeout * wdt->freq;
> +
> + /* some watchdogs take two timeouts to reset */
> + if (wdt->variant->double_timeout)
> + count /= 2;
> +
> + wdt->count = count;
> + wdd->timeout = timeout;
> +
> + starfive_wdt_unlock(wdt);
> + starfive_wdt_disable(wdt);
> + starfive_wdt_set_reload_count(wdt, wdt->count);
> + starfive_wdt_enable(wdt);
> + starfive_wdt_lock(wdt);
> +
> + return 0;
> +}
> +
> +#define STARFIVE_WDT_OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
> +
> +static const struct watchdog_info starfive_wdt_info = {
> + .options = STARFIVE_WDT_OPTIONS,
> + .identity = "StarFive Watchdog",
> +};
> +
> +static const struct watchdog_ops starfive_wdt_ops = {
> + .owner = THIS_MODULE,
> + .start = starfive_wdt_pm_start,
> + .stop = starfive_wdt_pm_stop,
> + .ping = starfive_wdt_keepalive,
> + .set_timeout = starfive_wdt_set_timeout,
> + .get_timeleft = starfive_wdt_get_timeleft,
> +};
> +
> +static int starfive_wdt_probe(struct platform_device *pdev)
> +{
> + struct starfive_wdt *wdt;
> + int ret;
> +
> + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
> + if (!wdt)
> + return -ENOMEM;
> +
> + wdt->base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(wdt->base))
> + return dev_err_probe(&pdev->dev, PTR_ERR(wdt->base), "error mapping registers\n");
> +
> + wdt->wdd.parent = &pdev->dev;
> + ret = starfive_wdt_get_clock(wdt);
> + if (ret)
> + return ret;
> +
> + platform_set_drvdata(pdev, wdt);
> + pm_runtime_enable(&pdev->dev);
> + if (pm_runtime_enabled(&pdev->dev)) {
> + ret = pm_runtime_get_sync(&pdev->dev);
> + if (ret < 0)
> + return ret;
> + } else {
> + /* runtime PM is disabled but clocks need to be enabled */
> + ret = starfive_wdt_enable_clock(wdt);
> + if (ret)
> + return ret;
> + }
> +
> + ret = starfive_wdt_reset_init(&pdev->dev);
> + if (ret)
> + goto err_exit;
> +
> + watchdog_set_drvdata(&wdt->wdd, wdt);
> + wdt->wdd.info = &starfive_wdt_info;
> + wdt->wdd.ops = &starfive_wdt_ops;
> + wdt->variant = of_device_get_match_data(&pdev->dev);
> + spin_lock_init(&wdt->lock);
> +
> + wdt->freq = clk_get_rate(wdt->core_clk);
> + if (!wdt->freq) {
> + dev_err(&pdev->dev, "get clock rate failed.\n");
> + ret = -EINVAL;
> + goto err_exit;
> + }
> +
> + wdt->wdd.min_timeout = 1;
> + wdt->wdd.max_timeout = starfive_wdt_max_timeout(wdt);
> + wdt->wdd.timeout = STARFIVE_WDT_DEFAULT_TIME;
> + watchdog_init_timeout(&wdt->wdd, heartbeat, &pdev->dev);
> + starfive_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
> +
> + watchdog_set_nowayout(&wdt->wdd, nowayout);
> + watchdog_stop_on_reboot(&wdt->wdd);
> + watchdog_stop_on_unregister(&wdt->wdd);
> +
> + if (early_enable) {
> + ret = starfive_wdt_start(wdt);
> + if (ret)
> + goto err_exit;
> + set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
> + } else {
> + starfive_wdt_stop(wdt);
> + }
> +
> + ret = watchdog_register_device(&wdt->wdd);
> + if (ret)
> + goto err_exit;
> +
> + if (!early_enable)
> + return pm_runtime_put_sync(&pdev->dev);
> +
> + return 0;
> +
> +err_exit:
> + starfive_wdt_disable_clock(wdt);
> + pm_runtime_disable(&pdev->dev);
> +
> + return ret;
> +}
> +
> +static int starfive_wdt_remove(struct platform_device *pdev)
> +{
> + struct starfive_wdt *wdt = platform_get_drvdata(pdev);
> +
> + starfive_wdt_stop(wdt);
> + watchdog_unregister_device(&wdt->wdd);
> +
> + if (pm_runtime_enabled(&pdev->dev))
> + pm_runtime_disable(&pdev->dev);
> + else
> + /* disable clock without PM */
> + starfive_wdt_disable_clock(wdt);
> +
> + return 0;
> +}
> +
> +static void starfive_wdt_shutdown(struct platform_device *pdev)
> +{
> + struct starfive_wdt *wdt = platform_get_drvdata(pdev);
> +
> + starfive_wdt_pm_stop(&wdt->wdd);
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int starfive_wdt_suspend(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> +
> + /* Save watchdog state, and turn it off. */
> + wdt->reload = starfive_wdt_get_count(wdt);
> +
> + /* Note that WTCNT doesn't need to be saved. */
> + starfive_wdt_stop(wdt);
> +
> + return pm_runtime_force_suspend(dev);
> +}
> +
> +static int starfive_wdt_resume(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = pm_runtime_force_resume(dev);
> + if (ret)
> + return ret;
> +
> + starfive_wdt_unlock(wdt);
> + /* Restore watchdog state. */
> + starfive_wdt_set_reload_count(wdt, wdt->reload);
> + starfive_wdt_lock(wdt);
> +
> + return starfive_wdt_start(wdt);
> +}
> +#endif /* CONFIG_PM_SLEEP */
> +
> +#ifdef CONFIG_PM
> +static int starfive_wdt_runtime_suspend(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> +
> + starfive_wdt_disable_clock(wdt);
> +
> + return 0;
> +}
> +
> +static int starfive_wdt_runtime_resume(struct device *dev)
> +{
> + struct starfive_wdt *wdt = dev_get_drvdata(dev);
> +
> + return starfive_wdt_enable_clock(wdt);
> +}
> +#endif /* CONFIG_PM */
> +
> +static const struct dev_pm_ops starfive_wdt_pm_ops = {
> + SET_RUNTIME_PM_OPS(starfive_wdt_runtime_suspend, starfive_wdt_runtime_resume, NULL)
> + SET_SYSTEM_SLEEP_PM_OPS(starfive_wdt_suspend, starfive_wdt_resume)
> +};
> +
> +static const struct of_device_id starfive_wdt_match[] = {
> + { .compatible = "starfive,jh7100-wdt", .data = &starfive_wdt_jh7100_variant },
> + { .compatible = "starfive,jh7110-wdt", .data = &starfive_wdt_jh7110_variant },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, starfive_wdt_match);
> +
> +static struct platform_driver starfive_wdt_driver = {
> + .probe = starfive_wdt_probe,
> + .remove = starfive_wdt_remove,
> + .shutdown = starfive_wdt_shutdown,
> + .driver = {
> + .name = "starfive-wdt",
> + .pm = &starfive_wdt_pm_ops,
> + .of_match_table = of_match_ptr(starfive_wdt_match),
> + },
> +};
> +module_platform_driver(starfive_wdt_driver);
> +
> +MODULE_AUTHOR("Xingyu Wu <[email protected]>");
> +MODULE_AUTHOR("Samin Guo <[email protected]>");
> +MODULE_DESCRIPTION("StarFive Watchdog Device Driver");
> +MODULE_LICENSE("GPL");