[RFC] watchdog: Add Allwinner watchdog driver

Message ID 20190419083201.20468-1-jagan@amarulasolutions.com
State New
Headers show
Series
  • [RFC] watchdog: Add Allwinner watchdog driver
Related show

Commit Message

Jagan Teki April 19, 2019, 8:32 a.m. UTC
This patch add watchdog driver for Allwinner SoCs.

Initial non-dm driver has send by 'Chris Blake' So I keep his
author on the driver itself.

Signed-off-by: Chris Blake <chrisrblake93@gmail.com>
Signed-off-by: Jagan Teki <jagan@amarulasolutions.com>
---
 drivers/watchdog/Kconfig     |   7 ++
 drivers/watchdog/Makefile    |   1 +
 drivers/watchdog/sunxi_wdt.c | 207 +++++++++++++++++++++++++++++++++++
 3 files changed, 215 insertions(+)
 create mode 100644 drivers/watchdog/sunxi_wdt.c

Patch

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 115fc4551f..79fd58048e 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -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
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index d901240ad1..16003a8fc6 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -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
diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c
new file mode 100644
index 0000000000..2e7364b7bb
--- /dev/null
+++ b/drivers/watchdog/sunxi_wdt.c
@@ -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,
+};