diff options
-rw-r--r-- | arch/sandbox/dts/test.dts | 8 | ||||
-rw-r--r-- | board/alliedtelesis/x530/x530.c | 5 | ||||
-rw-r--r-- | configs/sandbox64_defconfig | 2 | ||||
-rw-r--r-- | configs/sandbox_defconfig | 2 | ||||
-rw-r--r-- | doc/device-tree-bindings/watchdog/gpio-wdt.txt | 19 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 9 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/gpio_wdt.c | 68 | ||||
-rw-r--r-- | drivers/watchdog/wdt-uclass.c | 192 | ||||
-rw-r--r-- | include/asm-generic/global_data.h | 6 | ||||
-rw-r--r-- | include/wdt.h | 8 | ||||
-rw-r--r-- | test/dm/wdt.c | 90 |
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); |