[PATCH 1/3] watchdog: Add a driver for the sunxi watchdog

This driver supports the sun4i/sun6i/sun20i watchdog timers. They have a maximum timeout of 16 seconds.
Signed-off-by: Samuel Holland samuel@sholland.org ---
drivers/watchdog/Kconfig | 8 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/sunxi_wdt.c | 188 +++++++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 drivers/watchdog/sunxi_wdt.c
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index f0ff2612a6b..59112147343 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -27,6 +27,7 @@ config WATCHDOG_TIMEOUT_MSECS default 128000 if ARCH_MX25 || ARCH_MX31 || ARCH_MX5 || ARCH_MX6 default 128000 if ARCH_MX7 || ARCH_VF610 default 30000 if ARCH_SOCFPGA + default 16000 if ARCH_SUNXI default 60000 help Watchdog timeout in msec @@ -241,6 +242,13 @@ config WDT_STM32MP Enable the STM32 watchdog (IWDG) driver. Enable support to configure STM32's on-SoC watchdog.
+config WDT_SUNXI + bool "Allwinner sunxi watchdog timer support" + depends on WDT && ARCH_SUNXI + default y + help + Enable support for the watchdog timer in Allwinner sunxi SoCs. + config XILINX_TB_WATCHDOG bool "Xilinx Axi watchdog timer support" depends on WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 5c7ef593fe5..20cf4280db0 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -35,5 +35,6 @@ obj-$(CONFIG_WDT_SBSA) += sbsa_gwdt.o obj-$(CONFIG_WDT_K3_RTI) += rti_wdt.o obj-$(CONFIG_WDT_SP805) += sp805_wdt.o obj-$(CONFIG_WDT_STM32MP) += stm32mp_wdt.o +obj-$(CONFIG_WDT_SUNXI) += sunxi_wdt.o obj-$(CONFIG_WDT_TANGIER) += tangier_wdt.o obj-$(CONFIG_WDT_XILINX) += xilinx_wwdt.o diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c new file mode 100644 index 00000000000..b40a1d29caa --- /dev/null +++ b/drivers/watchdog/sunxi_wdt.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Derived from linux/drivers/watchdog/sunxi_wdt.c: + * Copyright (C) 2013 Carlo Caione + * Copyright (C) 2012 Henrik Nordstrom + */ + +#include <dm.h> +#include <wdt.h> +#include <asm/io.h> +#include <linux/delay.h> + +#define MSEC_PER_SEC 1000 + +#define WDT_MAX_TIMEOUT 16 +#define WDT_TIMEOUT_MASK 0xf + +#define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1)) + +#define WDT_MODE_EN BIT(0) + +struct sunxi_wdt_reg { + u8 wdt_ctrl; + u8 wdt_cfg; + u8 wdt_mode; + u8 wdt_timeout_shift; + u8 wdt_reset_mask; + u8 wdt_reset_val; + u32 wdt_key_val; +}; + +struct sunxi_wdt_priv { + void __iomem *base; + const struct sunxi_wdt_reg *regs; +}; + +/* Map of timeout in seconds to register value */ +static const u8 wdt_timeout_map[1 + WDT_MAX_TIMEOUT] = { + [0] = 0x0, + [1] = 0x1, + [2] = 0x2, + [3] = 0x3, + [4] = 0x4, + [5] = 0x5, + [6] = 0x6, + [7] = 0x7, + [8] = 0x7, + [9] = 0x8, + [10] = 0x8, + [11] = 0x9, + [12] = 0x9, + [13] = 0xa, + [14] = 0xa, + [15] = 0xb, + [16] = 0xb, +}; + +static int sunxi_wdt_reset(struct udevice *dev) +{ + struct sunxi_wdt_priv *priv = dev_get_priv(dev); + const struct sunxi_wdt_reg *regs = priv->regs; + void __iomem *base = priv->base; + + writel(WDT_CTRL_RELOAD, base + regs->wdt_ctrl); + + return 0; +} + +static int sunxi_wdt_start(struct udevice *dev, u64 timeout, ulong flags) +{ + struct sunxi_wdt_priv *priv = dev_get_priv(dev); + const struct sunxi_wdt_reg *regs = priv->regs; + void __iomem *base = priv->base; + u32 val; + + timeout /= MSEC_PER_SEC; + if (timeout > WDT_MAX_TIMEOUT) + timeout = WDT_MAX_TIMEOUT; + + /* Set system reset function */ + val = readl(base + regs->wdt_cfg); + val &= ~regs->wdt_reset_mask; + val |= regs->wdt_reset_val; + val |= regs->wdt_key_val; + writel(val, base + regs->wdt_cfg); + + /* Set timeout and enable watchdog */ + val = readl(base + regs->wdt_mode); + val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift); + val |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift; + val |= WDT_MODE_EN; + val |= regs->wdt_key_val; + writel(val, base + regs->wdt_mode); + + return sunxi_wdt_reset(dev); +} + +static int sunxi_wdt_stop(struct udevice *dev) +{ + struct sunxi_wdt_priv *priv = dev_get_priv(dev); + const struct sunxi_wdt_reg *regs = priv->regs; + void __iomem *base = priv->base; + + writel(regs->wdt_key_val, base + regs->wdt_mode); + + return 0; +} + +static int sunxi_wdt_expire_now(struct udevice *dev, ulong flags) +{ + int ret; + + ret = sunxi_wdt_start(dev, 0, flags); + if (ret) + return ret; + + mdelay(500); + + return 0; +} + +static const struct wdt_ops sunxi_wdt_ops = { + .reset = sunxi_wdt_reset, + .start = sunxi_wdt_start, + .stop = sunxi_wdt_stop, + .expire_now = sunxi_wdt_expire_now, +}; + +static const struct sunxi_wdt_reg sun4i_wdt_reg = { + .wdt_ctrl = 0x00, + .wdt_cfg = 0x04, + .wdt_mode = 0x04, + .wdt_timeout_shift = 3, + .wdt_reset_mask = 0x2, + .wdt_reset_val = 0x2, +}; + +static const struct sunxi_wdt_reg sun6i_wdt_reg = { + .wdt_ctrl = 0x10, + .wdt_cfg = 0x14, + .wdt_mode = 0x18, + .wdt_timeout_shift = 4, + .wdt_reset_mask = 0x3, + .wdt_reset_val = 0x1, +}; + +static const struct sunxi_wdt_reg sun20i_wdt_reg = { + .wdt_ctrl = 0x10, + .wdt_cfg = 0x14, + .wdt_mode = 0x18, + .wdt_timeout_shift = 4, + .wdt_reset_mask = 0x03, + .wdt_reset_val = 0x01, + .wdt_key_val = 0x16aa0000, +}; + +static const struct udevice_id sunxi_wdt_ids[] = { + { .compatible = "allwinner,sun4i-a10-wdt", .data = (ulong)&sun4i_wdt_reg }, + { .compatible = "allwinner,sun6i-a31-wdt", .data = (ulong)&sun6i_wdt_reg }, + { .compatible = "allwinner,sun20i-d1-wdt", .data = (ulong)&sun20i_wdt_reg }, + { /* sentinel */ } +}; + +static int sunxi_wdt_probe(struct udevice *dev) +{ + struct sunxi_wdt_priv *priv = dev_get_priv(dev); + + priv->base = dev_remap_addr(dev); + if (!priv->base) + return -EINVAL; + + priv->regs = (void *)dev_get_driver_data(dev); + if (!priv->regs) + return -EINVAL; + + sunxi_wdt_stop(dev); + + return 0; +} + +U_BOOT_DRIVER(sunxi_wdt) = { + .name = "sunxi_wdt", + .id = UCLASS_WDT, + .of_match = sunxi_wdt_ids, + .probe = sunxi_wdt_probe, + .priv_auto = sizeof(struct sunxi_wdt_priv), + .ops = &sunxi_wdt_ops, +};

A watchdog helps recover from hangs or failure to boot an OS. It can also be used by the sysreset framework to intentionally reset the system. Now that a driver is available, let's enable this functionality on sunxi boards.
Signed-off-by: Samuel Holland samuel@sholland.org ---
arch/arm/Kconfig | 1 + 1 file changed, 1 insertion(+)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index d692139199c..35b059c3faa 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1036,6 +1036,7 @@ config ARCH_SUNXI imply SPL_POWER imply SPL_SERIAL_SUPPORT imply USB_GADGET + imply WDT
config ARCH_U8500 bool "ST-Ericsson U8500 Series"

For some reason, the watchdog was disabled in the H616 device tree. Most likely this is a copy-paste from the H6 device tree: the H6 watchdog is disabled because it is broken in some chips. However, there is no evidence of issues with the H616 watchdog.
Enable the watchdog node so it can be used by the driver.
Signed-off-by: Samuel Holland samuel@sholland.org ---
arch/arm/dts/sun50i-h616.dtsi | 1 - 1 file changed, 1 deletion(-)
diff --git a/arch/arm/dts/sun50i-h616.dtsi b/arch/arm/dts/sun50i-h616.dtsi index dd4d2f31111..2f71e853e96 100644 --- a/arch/arm/dts/sun50i-h616.dtsi +++ b/arch/arm/dts/sun50i-h616.dtsi @@ -122,7 +122,6 @@ reg = <0x030090a0 0x20>; interrupts = <GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH>; clocks = <&osc24M>; - status = "disabled"; };
pio: pinctrl@300b000 {
participants (1)
-
Samuel Holland