summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/sandbox/dts/test.dts8
-rw-r--r--board/alliedtelesis/x530/x530.c5
-rw-r--r--configs/sandbox64_defconfig2
-rw-r--r--configs/sandbox_defconfig2
-rw-r--r--doc/device-tree-bindings/watchdog/gpio-wdt.txt19
-rw-r--r--drivers/watchdog/Kconfig9
-rw-r--r--drivers/watchdog/Makefile1
-rw-r--r--drivers/watchdog/gpio_wdt.c68
-rw-r--r--drivers/watchdog/wdt-uclass.c192
-rw-r--r--include/asm-generic/global_data.h6
-rw-r--r--include/wdt.h8
-rw-r--r--test/dm/wdt.c90
12 files changed, 349 insertions, 61 deletions
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 962bdbe556..1399a14929 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -793,6 +793,13 @@
};
};
+ gpio-wdt {
+ gpios = <&gpio_a 7 0>;
+ compatible = "linux,wdt-gpio";
+ hw_margin_ms = <100>;
+ always-running;
+ };
+
mbox: mbox {
compatible = "sandbox,mbox";
#mbox-cells = <1>;
@@ -1272,6 +1279,7 @@
wdt0: wdt@0 {
compatible = "sandbox,wdt";
+ hw_margin_ms = <200>;
};
axi: axi@0 {
diff --git a/board/alliedtelesis/x530/x530.c b/board/alliedtelesis/x530/x530.c
index 7bcfa828d7..8b31045a07 100644
--- a/board/alliedtelesis/x530/x530.c
+++ b/board/alliedtelesis/x530/x530.c
@@ -121,9 +121,8 @@ int board_init(void)
void arch_preboot_os(void)
{
-#ifdef CONFIG_WATCHDOG
- wdt_stop(gd->watchdog_dev);
-#endif
+ if (CONFIG_IS_ENABLED(WDT))
+ wdt_stop_all();
}
static int led_7seg_init(unsigned int segments)
diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig
index 846417d083..df9633d762 100644
--- a/configs/sandbox64_defconfig
+++ b/configs/sandbox64_defconfig
@@ -225,7 +225,9 @@ CONFIG_OSD=y
CONFIG_SANDBOX_OSD=y
CONFIG_SPLASH_SCREEN_ALIGN=y
CONFIG_VIDEO_BMP_RLE8=y
+# CONFIG_WATCHDOG_AUTOSTART is not set
CONFIG_WDT=y
+CONFIG_WDT_GPIO=y
CONFIG_WDT_SANDBOX=y
CONFIG_FS_CBFS=y
CONFIG_FS_CRAMFS=y
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index dd2f8e9b8e..f1067b9ada 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -283,7 +283,9 @@ CONFIG_W1=y
CONFIG_W1_GPIO=y
CONFIG_W1_EEPROM=y
CONFIG_W1_EEPROM_SANDBOX=y
+# CONFIG_WATCHDOG_AUTOSTART is not set
CONFIG_WDT=y
+CONFIG_WDT_GPIO=y
CONFIG_WDT_SANDBOX=y
CONFIG_FS_CBFS=y
CONFIG_FS_CRAMFS=y
diff --git a/doc/device-tree-bindings/watchdog/gpio-wdt.txt b/doc/device-tree-bindings/watchdog/gpio-wdt.txt
new file mode 100644
index 0000000000..c9a8559a3e
--- /dev/null
+++ b/doc/device-tree-bindings/watchdog/gpio-wdt.txt
@@ -0,0 +1,19 @@
+GPIO watchdog timer
+
+Describes a simple watchdog timer which is reset by toggling a gpio.
+
+Required properties:
+
+- compatible: Must be "linux,wdt-gpio".
+- gpios: gpio to toggle when wdt driver reset method is called.
+- always-running: Boolean property indicating that the watchdog cannot
+ be disabled. At present, U-Boot only supports this kind of GPIO
+ watchdog.
+
+Example:
+
+ gpio-wdt {
+ gpios = <&gpio0 1 0>;
+ compatible = "linux,wdt-gpio";
+ always-running;
+ };
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index f0ff2612a6..6fbb5c1b6d 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -147,6 +147,15 @@ config WDT_CORTINA
This driver support all CPU ISAs supported by Cortina
Access CAxxxx SoCs.
+config WDT_GPIO
+ bool "External gpio watchdog support"
+ depends on WDT
+ depends on DM_GPIO
+ help
+ Support for external watchdog fed by toggling a gpio. See
+ doc/device-tree-bindings/watchdog/gpio-wdt.txt for
+ information on how to describe the watchdog in device tree.
+
config WDT_MPC8xx
bool "MPC8xx watchdog timer support"
depends on WDT && MPC8xx
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 5c7ef593fe..f14415bb8e 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_WDT_BOOKE) += booke_wdt.o
obj-$(CONFIG_WDT_CORTINA) += cortina_wdt.o
obj-$(CONFIG_WDT_ORION) += orion_wdt.o
obj-$(CONFIG_WDT_CDNS) += cdns_wdt.o
+obj-$(CONFIG_WDT_GPIO) += gpio_wdt.o
obj-$(CONFIG_WDT_MPC8xx) += mpc8xx_wdt.o
obj-$(CONFIG_WDT_MT7620) += mt7620_wdt.o
obj-$(CONFIG_WDT_MT7621) += mt7621_wdt.o
diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c
new file mode 100644
index 0000000000..982a66b3f9
--- /dev/null
+++ b/drivers/watchdog/gpio_wdt.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <wdt.h>
+#include <asm/gpio.h>
+
+struct gpio_wdt_priv {
+ struct gpio_desc gpio;
+ bool always_running;
+ int state;
+};
+
+static int gpio_wdt_reset(struct udevice *dev)
+{
+ struct gpio_wdt_priv *priv = dev_get_priv(dev);
+
+ priv->state = !priv->state;
+
+ return dm_gpio_set_value(&priv->gpio, priv->state);
+}
+
+static int gpio_wdt_start(struct udevice *dev, u64 timeout, ulong flags)
+{
+ struct gpio_wdt_priv *priv = dev_get_priv(dev);
+
+ if (priv->always_running)
+ return 0;
+
+ return -ENOSYS;
+}
+
+static int dm_probe(struct udevice *dev)
+{
+ struct gpio_wdt_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ priv->always_running = dev_read_bool(dev, "always-running");
+ ret = gpio_request_by_name(dev, "gpios", 0, &priv->gpio, GPIOD_IS_OUT);
+ if (ret < 0) {
+ dev_err(dev, "Request for wdt gpio failed: %d\n", ret);
+ return ret;
+ }
+
+ if (priv->always_running)
+ ret = gpio_wdt_reset(dev);
+
+ return ret;
+}
+
+static const struct wdt_ops gpio_wdt_ops = {
+ .start = gpio_wdt_start,
+ .reset = gpio_wdt_reset,
+};
+
+static const struct udevice_id gpio_wdt_ids[] = {
+ { .compatible = "linux,wdt-gpio" },
+ {}
+};
+
+U_BOOT_DRIVER(wdt_gpio) = {
+ .name = "wdt_gpio",
+ .id = UCLASS_WDT,
+ .of_match = gpio_wdt_ids,
+ .ops = &gpio_wdt_ops,
+ .probe = dm_probe,
+ .priv_auto = sizeof(struct gpio_wdt_priv),
+};
diff --git a/drivers/watchdog/wdt-uclass.c b/drivers/watchdog/wdt-uclass.c
index 17334dbda6..7570710c4d 100644
--- a/drivers/watchdog/wdt-uclass.c
+++ b/drivers/watchdog/wdt-uclass.c
@@ -20,53 +20,67 @@ DECLARE_GLOBAL_DATA_PTR;
#define WATCHDOG_TIMEOUT_SECS (CONFIG_WATCHDOG_TIMEOUT_MSECS / 1000)
-/*
- * Reset every 1000ms, or however often is required as indicated by a
- * hw_margin_ms property.
- */
-static ulong reset_period = 1000;
+struct wdt_priv {
+ /* Timeout, in seconds, to configure this device to. */
+ u32 timeout;
+ /*
+ * Time, in milliseconds, between calling the device's ->reset()
+ * method from watchdog_reset().
+ */
+ ulong reset_period;
+ /*
+ * Next time (as returned by get_timer(0)) to call
+ * ->reset().
+ */
+ ulong next_reset;
+ /* Whether watchdog_start() has been called on the device. */
+ bool running;
+};
-int initr_watchdog(void)
+static void init_watchdog_dev(struct udevice *dev)
{
- u32 timeout = WATCHDOG_TIMEOUT_SECS;
+ struct wdt_priv *priv;
int ret;
- /*
- * Init watchdog: This will call the probe function of the
- * watchdog driver, enabling the use of the device
- */
- if (uclass_get_device_by_seq(UCLASS_WDT, 0,
- (struct udevice **)&gd->watchdog_dev)) {
- debug("WDT: Not found by seq!\n");
- if (uclass_get_device(UCLASS_WDT, 0,
- (struct udevice **)&gd->watchdog_dev)) {
- printf("WDT: Not found!\n");
- return 0;
- }
- }
-
- if (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)) {
- timeout = dev_read_u32_default(gd->watchdog_dev, "timeout-sec",
- WATCHDOG_TIMEOUT_SECS);
- reset_period = dev_read_u32_default(gd->watchdog_dev,
- "hw_margin_ms",
- 4 * reset_period) / 4;
- }
+ priv = dev_get_uclass_priv(dev);
if (!IS_ENABLED(CONFIG_WATCHDOG_AUTOSTART)) {
- printf("WDT: Not starting\n");
- return 0;
+ printf("WDT: Not starting %s\n", dev->name);
+ return;
}
- ret = wdt_start(gd->watchdog_dev, timeout * 1000, 0);
+ ret = wdt_start(dev, priv->timeout * 1000, 0);
if (ret != 0) {
- printf("WDT: Failed to start\n");
+ printf("WDT: Failed to start %s\n", dev->name);
+ return;
+ }
+
+ printf("WDT: Started %s with%s servicing (%ds timeout)\n", dev->name,
+ IS_ENABLED(CONFIG_WATCHDOG) ? "" : "out", priv->timeout);
+}
+
+int initr_watchdog(void)
+{
+ struct udevice *dev;
+ struct uclass *uc;
+ int ret;
+
+ ret = uclass_get(UCLASS_WDT, &uc);
+ if (ret) {
+ log_debug("Error getting UCLASS_WDT: %d\n", ret);
return 0;
}
- printf("WDT: Started with%s servicing (%ds timeout)\n",
- IS_ENABLED(CONFIG_WATCHDOG) ? "" : "out", timeout);
+ uclass_foreach_dev(dev, uc) {
+ ret = device_probe(dev);
+ if (ret) {
+ log_debug("Error probing %s: %d\n", dev->name, ret);
+ continue;
+ }
+ init_watchdog_dev(dev);
+ }
+ gd->flags |= GD_FLG_WDT_READY;
return 0;
}
@@ -79,8 +93,11 @@ int wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags)
return -ENOSYS;
ret = ops->start(dev, timeout_ms, flags);
- if (ret == 0)
- gd->flags |= GD_FLG_WDT_READY;
+ if (ret == 0) {
+ struct wdt_priv *priv = dev_get_uclass_priv(dev);
+
+ priv->running = true;
+ }
return ret;
}
@@ -94,8 +111,36 @@ int wdt_stop(struct udevice *dev)
return -ENOSYS;
ret = ops->stop(dev);
- if (ret == 0)
- gd->flags &= ~GD_FLG_WDT_READY;
+ if (ret == 0) {
+ struct wdt_priv *priv = dev_get_uclass_priv(dev);
+
+ priv->running = false;
+ }
+
+ return ret;
+}
+
+int wdt_stop_all(void)
+{
+ struct wdt_priv *priv;
+ struct udevice *dev;
+ struct uclass *uc;
+ int ret, err;
+
+ ret = uclass_get(UCLASS_WDT, &uc);
+ if (ret)
+ return ret;
+
+ uclass_foreach_dev(dev, uc) {
+ if (!device_active(dev))
+ continue;
+ priv = dev_get_uclass_priv(dev);
+ if (!priv->running)
+ continue;
+ err = wdt_stop(dev);
+ if (!ret)
+ ret = err;
+ }
return ret;
}
@@ -120,10 +165,8 @@ int wdt_expire_now(struct udevice *dev, ulong flags)
if (ops->expire_now) {
return ops->expire_now(dev, flags);
} else {
- if (!ops->start)
- return -ENOSYS;
+ ret = wdt_start(dev, 1, flags);
- ret = ops->start(dev, 1, flags);
if (ret < 0)
return ret;
@@ -141,18 +184,36 @@ int wdt_expire_now(struct udevice *dev, ulong flags)
*/
void watchdog_reset(void)
{
- static ulong next_reset;
+ struct wdt_priv *priv;
+ struct udevice *dev;
+ struct uclass *uc;
ulong now;
/* Exit if GD is not ready or watchdog is not initialized yet */
if (!gd || !(gd->flags & GD_FLG_WDT_READY))
return;
- /* Do not reset the watchdog too often */
- now = get_timer(0);
- if (time_after_eq(now, next_reset)) {
- next_reset = now + reset_period;
- wdt_reset(gd->watchdog_dev);
+ if (uclass_get(UCLASS_WDT, &uc))
+ return;
+
+ /*
+ * All devices bound to the wdt uclass should have been probed
+ * in initr_watchdog(). But just in case something went wrong,
+ * check device_active() before accessing the uclass private
+ * data.
+ */
+ uclass_foreach_dev(dev, uc) {
+ if (!device_active(dev))
+ continue;
+ priv = dev_get_uclass_priv(dev);
+ if (!priv->running)
+ continue;
+ /* Do not reset the watchdog too often */
+ now = get_timer(0);
+ if (time_after_eq(now, priv->next_reset)) {
+ priv->next_reset = now + priv->reset_period;
+ wdt_reset(dev);
+ }
}
}
#endif
@@ -179,9 +240,38 @@ static int wdt_post_bind(struct udevice *dev)
return 0;
}
+static int wdt_pre_probe(struct udevice *dev)
+{
+ u32 timeout = WATCHDOG_TIMEOUT_SECS;
+ /*
+ * Reset every 1000ms, or however often is required as
+ * indicated by a hw_margin_ms property.
+ */
+ ulong reset_period = 1000;
+ struct wdt_priv *priv;
+
+ if (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)) {
+ timeout = dev_read_u32_default(dev, "timeout-sec", timeout);
+ reset_period = dev_read_u32_default(dev, "hw_margin_ms",
+ 4 * reset_period) / 4;
+ }
+ priv = dev_get_uclass_priv(dev);
+ priv->timeout = timeout;
+ priv->reset_period = reset_period;
+ /*
+ * Pretend this device was last reset "long" ago so the first
+ * watchdog_reset will actually call its ->reset method.
+ */
+ priv->next_reset = get_timer(0);
+
+ return 0;
+}
+
UCLASS_DRIVER(wdt) = {
- .id = UCLASS_WDT,
- .name = "watchdog",
- .flags = DM_UC_FLAG_SEQ_ALIAS,
- .post_bind = wdt_post_bind,
+ .id = UCLASS_WDT,
+ .name = "watchdog",
+ .flags = DM_UC_FLAG_SEQ_ALIAS,
+ .post_bind = wdt_post_bind,
+ .pre_probe = wdt_pre_probe,
+ .per_device_auto = sizeof(struct wdt_priv),
};
diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h
index a4cf7fd58c..16fd305a65 100644
--- a/include/asm-generic/global_data.h
+++ b/include/asm-generic/global_data.h
@@ -447,12 +447,6 @@ struct global_data {
*/
fdt_addr_t translation_offset;
#endif
-#if CONFIG_IS_ENABLED(WDT)
- /**
- * @watchdog_dev: watchdog device
- */
- struct udevice *watchdog_dev;
-#endif
#ifdef CONFIG_GENERATE_ACPI_TABLE
/**
* @acpi_ctx: ACPI context pointer
diff --git a/include/wdt.h b/include/wdt.h
index bc242c2eb2..baaa9db08a 100644
--- a/include/wdt.h
+++ b/include/wdt.h
@@ -38,6 +38,14 @@ int wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags);
int wdt_stop(struct udevice *dev);
/*
+ * Stop all registered watchdog devices.
+ *
+ * @return: 0 if ok, first error encountered otherwise (but wdt_stop()
+ * is still called on following devices)
+ */
+int wdt_stop_all(void);
+
+/*
* Reset the timer, typically restoring the counter to
* the value configured by start()
*
diff --git a/test/dm/wdt.c b/test/dm/wdt.c
index 24b991dff6..ee615f0e14 100644
--- a/test/dm/wdt.c
+++ b/test/dm/wdt.c
@@ -6,11 +6,14 @@
#include <common.h>
#include <dm.h>
#include <wdt.h>
+#include <asm/gpio.h>
#include <asm/state.h>
#include <asm/test.h>
#include <dm/test.h>
#include <test/test.h>
#include <test/ut.h>
+#include <linux/delay.h>
+#include <watchdog.h>
/* Test that watchdog driver functions are called */
static int dm_test_wdt_base(struct unit_test_state *uts)
@@ -19,7 +22,8 @@ static int dm_test_wdt_base(struct unit_test_state *uts)
struct udevice *dev;
const u64 timeout = 42;
- ut_assertok(uclass_get_device(UCLASS_WDT, 0, &dev));
+ ut_assertok(uclass_get_device_by_driver(UCLASS_WDT,
+ DM_DRIVER_GET(wdt_sandbox), &dev));
ut_assertnonnull(dev);
ut_asserteq(0, state->wdt.counter);
ut_asserteq(false, state->wdt.running);
@@ -39,3 +43,87 @@ static int dm_test_wdt_base(struct unit_test_state *uts)
return 0;
}
DM_TEST(dm_test_wdt_base, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT);
+
+static int dm_test_wdt_gpio(struct unit_test_state *uts)
+{
+ /*
+ * The sandbox wdt gpio is "connected" to gpio bank a, offset
+ * 7. Use the sandbox back door to verify that the gpio-wdt
+ * driver behaves as expected.
+ */
+ struct udevice *wdt, *gpio;
+ const u64 timeout = 42;
+ const int offset = 7;
+ int val;
+
+ ut_assertok(uclass_get_device_by_driver(UCLASS_WDT,
+ DM_DRIVER_GET(wdt_gpio), &wdt));
+ ut_assertnonnull(wdt);
+
+ ut_assertok(uclass_get_device_by_name(UCLASS_GPIO, "base-gpios", &gpio));
+ ut_assertnonnull(gpio);
+ ut_assertok(wdt_start(wdt, timeout, 0));
+
+ val = sandbox_gpio_get_value(gpio, offset);
+ ut_assertok(wdt_reset(wdt));
+ ut_asserteq(!val, sandbox_gpio_get_value(gpio, offset));
+ ut_assertok(wdt_reset(wdt));
+ ut_asserteq(val, sandbox_gpio_get_value(gpio, offset));
+
+ ut_asserteq(-ENOSYS, wdt_stop(wdt));
+
+ return 0;
+}
+DM_TEST(dm_test_wdt_gpio, UT_TESTF_SCAN_FDT);
+
+static int dm_test_wdt_watchdog_reset(struct unit_test_state *uts)
+{
+ struct sandbox_state *state = state_get_current();
+ struct udevice *gpio_wdt, *sandbox_wdt;
+ struct udevice *gpio;
+ const u64 timeout = 42;
+ const int offset = 7;
+ uint reset_count;
+ int val;
+
+ ut_assertok(uclass_get_device_by_driver(UCLASS_WDT,
+ DM_DRIVER_GET(wdt_gpio), &gpio_wdt));
+ ut_assertnonnull(gpio_wdt);
+ ut_assertok(uclass_get_device_by_driver(UCLASS_WDT,
+ DM_DRIVER_GET(wdt_sandbox), &sandbox_wdt));
+ ut_assertnonnull(sandbox_wdt);
+ ut_assertok(uclass_get_device_by_name(UCLASS_GPIO, "base-gpios", &gpio));
+ ut_assertnonnull(gpio);
+
+ /* Neither device should be "started", so watchdog_reset() should be a no-op. */
+ reset_count = state->wdt.reset_count;
+ val = sandbox_gpio_get_value(gpio, offset);
+ watchdog_reset();
+ ut_asserteq(reset_count, state->wdt.reset_count);
+ ut_asserteq(val, sandbox_gpio_get_value(gpio, offset));
+
+ /* Start both devices. */
+ ut_assertok(wdt_start(gpio_wdt, timeout, 0));
+ ut_assertok(wdt_start(sandbox_wdt, timeout, 0));
+
+ /* Make sure both devices have just been pinged. */
+ timer_test_add_offset(100);
+ watchdog_reset();
+ reset_count = state->wdt.reset_count;
+ val = sandbox_gpio_get_value(gpio, offset);
+
+ /* The gpio watchdog should be pinged, the sandbox one not. */
+ timer_test_add_offset(30);
+ watchdog_reset();
+ ut_asserteq(reset_count, state->wdt.reset_count);
+ ut_asserteq(!val, sandbox_gpio_get_value(gpio, offset));
+
+ /* After another ~30ms, both devices should get pinged. */
+ timer_test_add_offset(30);
+ watchdog_reset();
+ ut_asserteq(reset_count + 1, state->wdt.reset_count);
+ ut_asserteq(val, sandbox_gpio_get_value(gpio, offset));
+
+ return 0;
+}
+DM_TEST(dm_test_wdt_watchdog_reset, UT_TESTF_SCAN_FDT);