watchdog: Add a driver for the sunxi watchdog
authorSamuel Holland <samuel@sholland.org>
Sun, 22 Aug 2021 18:53:27 +0000 (13:53 -0500)
committerAndre Przywara <andre.przywara@arm.com>
Mon, 25 Oct 2021 13:54:26 +0000 (14:54 +0100)
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>
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/sunxi_wdt.c [new file with mode: 0644]

index 7d2d6ea..d306054 100644 (file)
@@ -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
index f14415b..fa7ce58 100644 (file)
@@ -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 (file)
index 0000000..b40a1d2
--- /dev/null
@@ -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,
+};