aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Holland <samuel@sholland.org>2021-08-22 13:53:27 -0500
committerAndre Przywara <andre.przywara@arm.com>2021-10-25 14:54:26 +0100
commit93d34faeda4cb8bb059a6b6fbff4f9969c6f41a7 (patch)
treecb1162dccf0915e3515bc296fe036e7443544975
parente0c628d728d6f2b0ab01488706b1a9679512a982 (diff)
downloadu-boot-93d34faeda4cb8bb059a6b6fbff4f9969c6f41a7.zip
u-boot-93d34faeda4cb8bb059a6b6fbff4f9969c6f41a7.tar.gz
u-boot-93d34faeda4cb8bb059a6b6fbff4f9969c6f41a7.tar.bz2
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> Signed-off-by: Andre Przywara <andre.przywara@arm.com>
-rw-r--r--drivers/watchdog/Kconfig8
-rw-r--r--drivers/watchdog/Makefile1
-rw-r--r--drivers/watchdog/sunxi_wdt.c188
3 files changed, 197 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 7d2d6ea..d306054 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -27,6 +27,7 @@ config WATCHDOG_TIMEOUT_MSECS
default 128000 if 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
@@ -270,6 +271,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 f14415b..fa7ce58 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -36,5 +36,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 0000000..b40a1d2
--- /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,
+};