| // 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> |
| #include <linux/time.h> |
| |
| #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, |
| }; |