
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
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, +};