@@ -117,6 +117,13 @@ config WDT_MTK
The watchdog timer is stopped when initialized.
It performs full SoC reset.
+config WDT_SUNXI
+ bool "Allwinner SoC watchdog support"
+ depends on WDT
+ default y if ARCH_SUNXI
+ help
+ Select this to enable watchdog timer for Allwinner SoCs.
+
config XILINX_TB_WATCHDOG
bool "Xilinx Axi watchdog timer support"
depends on WDT
@@ -27,3 +27,4 @@ obj-$(CONFIG_WDT_CDNS) += cdns_wdt.o
obj-$(CONFIG_MPC8xx_WATCHDOG) += mpc8xx_wdt.o
obj-$(CONFIG_WDT_MT7621) += mt7621_wdt.o
obj-$(CONFIG_WDT_MTK) += mtk_wdt.o
+obj-$(CONFIG_WDT_SUNXI) += sunxi_wdt.o
new file mode 100644
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Watchdog driver for Allwinner SoCs
+ *
+ * Copyright (C) 2019 Chris Blake <chrisrblake93@gmail.com>
+ * Copyright (C) 2019 Jagan Teki <jagan@amarulasolutions.com>
+ *
+ * Based on the linux/drivers/watchdog/sunxi_wdt.c
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <wdt.h>
+#include <asm/io.h>
+
+#define WDT_TIMEOUT_MASK 0x0F
+#define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1))
+#define WDT_MODE_EN BIT(0)
+
+/*
+ * This structure stores the register offsets for different variants
+ * of Allwinner's watchdog hardware.
+ */
+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;
+};
+
+struct sunxi_wdt_priv {
+ void __iomem *base;
+ const struct sunxi_wdt_reg *wdt_regs;
+};
+
+/*
+ * wdt_timeout_map maps the watchdog timer interval value in seconds to
+ * the value of the register WDT_MODE at bits .wdt_timeout_shift ~ +3
+ *
+ * [timeout seconds] = register value
+ *
+ */
+static const int wdt_timeout_map[] = {
+ [1] = 0x1, /* 1s */
+ [2] = 0x2, /* 2s */
+ [3] = 0x3, /* 3s */
+ [4] = 0x4, /* 4s */
+ [5] = 0x5, /* 5s */
+ [6] = 0x6, /* 6s */
+ [8] = 0x7, /* 8s */
+ [10] = 0x8, /* 10s */
+ [12] = 0x9, /* 12s */
+ [14] = 0xA, /* 14s */
+ [16] = 0xB, /* 16s */
+};
+
+static int sunxi_wdt_reset(struct udevice *dev)
+{
+ struct sunxi_wdt_priv *priv = dev_get_priv(dev);
+
+ writel(WDT_CTRL_RELOAD, priv->base + priv->wdt_regs->wdt_ctrl);
+
+ return 0;
+}
+
+static int sunxi_wdt_stop(struct udevice *dev)
+{
+ struct sunxi_wdt_priv *priv = dev_get_priv(dev);
+
+ writel(0, priv->base + priv->wdt_regs->wdt_mode);
+
+ return 0;
+}
+
+static int sunxi_wdt_expire_now(struct udevice *dev, ulong flags)
+{
+ struct sunxi_wdt_priv *priv = dev_get_priv(dev);
+ u32 val;
+
+ /* Set system reset function */
+ val = readl(priv->base + priv->wdt_regs->wdt_cfg);
+ val &= ~(priv->wdt_regs->wdt_reset_mask);
+ val |= priv->wdt_regs->wdt_reset_val;
+ writel(val, priv->base + priv->wdt_regs->wdt_cfg);
+
+ /* Set lowest timeout and enable watchdog */
+ val = readl(priv->base + priv->wdt_regs->wdt_mode);
+ val &= (WDT_TIMEOUT_MASK << priv->wdt_regs->wdt_timeout_shift);
+ val |= WDT_MODE_EN;
+ writel(val, priv->base + priv->wdt_regs->wdt_mode);
+
+ /*
+ * Restart the watchdog. The default (and lowest) interval
+ * value for the watchdog is 0.5s.
+ */
+ writel(WDT_CTRL_RELOAD, priv->base + priv->wdt_regs->wdt_ctrl);
+
+ while (1) {
+ mdelay(5);
+ val = readl(priv->base + priv->wdt_regs->wdt_mode);
+ val |= WDT_MODE_EN;
+ writel(val, priv->base + priv->wdt_regs->wdt_mode);
+ }
+
+ return 0;
+}
+
+static void sunxi_wdt_set_timeout(struct udevice *dev, unsigned int timeout)
+{
+ struct sunxi_wdt_priv *priv = dev_get_priv(dev);
+ u32 reg;
+
+ if (wdt_timeout_map[timeout] == 0)
+ timeout++;
+
+ reg = readl(priv->base + priv->wdt_regs->wdt_mode);
+ reg &= (WDT_TIMEOUT_MASK << priv->wdt_regs->wdt_timeout_shift);
+ reg |= wdt_timeout_map[timeout] << priv->wdt_regs->wdt_timeout_shift;
+ writel(reg, priv->base + priv->wdt_regs->wdt_mode);
+
+ sunxi_wdt_reset(dev);
+}
+
+static int sunxi_wdt_start(struct udevice *dev, u64 timeout, ulong flags)
+{
+ struct sunxi_wdt_priv *priv = dev_get_priv(dev);
+ u32 reg;
+
+ sunxi_wdt_set_timeout(dev, timeout);
+
+ /* Set system reset function */
+ reg = readl(priv->base + priv->wdt_regs->wdt_cfg);
+ reg &= ~(priv->wdt_regs->wdt_reset_mask);
+ reg |= priv->wdt_regs->wdt_reset_val;
+ writel(reg, priv->base + priv->wdt_regs->wdt_cfg);
+
+ /* Enable watchdog */
+ reg = readl(priv->base + priv->wdt_regs->wdt_mode);
+ reg |= WDT_MODE_EN;
+ writel(reg, priv->base + priv->wdt_regs->wdt_mode);
+
+ return 0;
+}
+
+static int sunxi_wdt_probe(struct udevice *dev)
+{
+ struct sunxi_wdt_priv *priv = dev_get_priv(dev);
+
+ priv->base = dev_read_addr_ptr(dev);
+ if (!priv->base)
+ return -ENOENT;
+
+ priv->wdt_regs = (const struct sunxi_wdt_reg *)dev_get_driver_data(dev);
+ if (!priv->wdt_regs)
+ return -EINVAL;
+
+ return sunxi_wdt_stop(dev);
+}
+
+static const struct wdt_ops sunxi_wdt_ops = {
+ .start = sunxi_wdt_start,
+ .reset = sunxi_wdt_reset,
+ .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 = 0x02,
+ .wdt_reset_val = 0x02,
+};
+
+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 = 0x03,
+ .wdt_reset_val = 0x01,
+};
+
+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
+ },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(sunxi_wdt) = {
+ .name = "sunxi_wdt",
+ .id = UCLASS_WDT,
+ .of_match = sunxi_wdt_ids,
+ .priv_auto_alloc_size = sizeof(struct sunxi_wdt_priv),
+ .probe = sunxi_wdt_probe,
+ .ops = &sunxi_wdt_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};