blob: b40a1d29caaad6904cbe975fc000ccff7a0c0046 [file] [log] [blame]
Samuel Holland65c982b2021-08-22 13:53:27 -05001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Derived from linux/drivers/watchdog/sunxi_wdt.c:
4 * Copyright (C) 2013 Carlo Caione
5 * Copyright (C) 2012 Henrik Nordstrom
6 */
7
8#include <dm.h>
9#include <wdt.h>
10#include <asm/io.h>
11#include <linux/delay.h>
12
13#define MSEC_PER_SEC 1000
14
15#define WDT_MAX_TIMEOUT 16
16#define WDT_TIMEOUT_MASK 0xf
17
18#define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1))
19
20#define WDT_MODE_EN BIT(0)
21
22struct sunxi_wdt_reg {
23 u8 wdt_ctrl;
24 u8 wdt_cfg;
25 u8 wdt_mode;
26 u8 wdt_timeout_shift;
27 u8 wdt_reset_mask;
28 u8 wdt_reset_val;
29 u32 wdt_key_val;
30};
31
32struct sunxi_wdt_priv {
33 void __iomem *base;
34 const struct sunxi_wdt_reg *regs;
35};
36
37/* Map of timeout in seconds to register value */
38static const u8 wdt_timeout_map[1 + WDT_MAX_TIMEOUT] = {
39 [0] = 0x0,
40 [1] = 0x1,
41 [2] = 0x2,
42 [3] = 0x3,
43 [4] = 0x4,
44 [5] = 0x5,
45 [6] = 0x6,
46 [7] = 0x7,
47 [8] = 0x7,
48 [9] = 0x8,
49 [10] = 0x8,
50 [11] = 0x9,
51 [12] = 0x9,
52 [13] = 0xa,
53 [14] = 0xa,
54 [15] = 0xb,
55 [16] = 0xb,
56};
57
58static int sunxi_wdt_reset(struct udevice *dev)
59{
60 struct sunxi_wdt_priv *priv = dev_get_priv(dev);
61 const struct sunxi_wdt_reg *regs = priv->regs;
62 void __iomem *base = priv->base;
63
64 writel(WDT_CTRL_RELOAD, base + regs->wdt_ctrl);
65
66 return 0;
67}
68
69static int sunxi_wdt_start(struct udevice *dev, u64 timeout, ulong flags)
70{
71 struct sunxi_wdt_priv *priv = dev_get_priv(dev);
72 const struct sunxi_wdt_reg *regs = priv->regs;
73 void __iomem *base = priv->base;
74 u32 val;
75
76 timeout /= MSEC_PER_SEC;
77 if (timeout > WDT_MAX_TIMEOUT)
78 timeout = WDT_MAX_TIMEOUT;
79
80 /* Set system reset function */
81 val = readl(base + regs->wdt_cfg);
82 val &= ~regs->wdt_reset_mask;
83 val |= regs->wdt_reset_val;
84 val |= regs->wdt_key_val;
85 writel(val, base + regs->wdt_cfg);
86
87 /* Set timeout and enable watchdog */
88 val = readl(base + regs->wdt_mode);
89 val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
90 val |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift;
91 val |= WDT_MODE_EN;
92 val |= regs->wdt_key_val;
93 writel(val, base + regs->wdt_mode);
94
95 return sunxi_wdt_reset(dev);
96}
97
98static int sunxi_wdt_stop(struct udevice *dev)
99{
100 struct sunxi_wdt_priv *priv = dev_get_priv(dev);
101 const struct sunxi_wdt_reg *regs = priv->regs;
102 void __iomem *base = priv->base;
103
104 writel(regs->wdt_key_val, base + regs->wdt_mode);
105
106 return 0;
107}
108
109static int sunxi_wdt_expire_now(struct udevice *dev, ulong flags)
110{
111 int ret;
112
113 ret = sunxi_wdt_start(dev, 0, flags);
114 if (ret)
115 return ret;
116
117 mdelay(500);
118
119 return 0;
120}
121
122static const struct wdt_ops sunxi_wdt_ops = {
123 .reset = sunxi_wdt_reset,
124 .start = sunxi_wdt_start,
125 .stop = sunxi_wdt_stop,
126 .expire_now = sunxi_wdt_expire_now,
127};
128
129static const struct sunxi_wdt_reg sun4i_wdt_reg = {
130 .wdt_ctrl = 0x00,
131 .wdt_cfg = 0x04,
132 .wdt_mode = 0x04,
133 .wdt_timeout_shift = 3,
134 .wdt_reset_mask = 0x2,
135 .wdt_reset_val = 0x2,
136};
137
138static const struct sunxi_wdt_reg sun6i_wdt_reg = {
139 .wdt_ctrl = 0x10,
140 .wdt_cfg = 0x14,
141 .wdt_mode = 0x18,
142 .wdt_timeout_shift = 4,
143 .wdt_reset_mask = 0x3,
144 .wdt_reset_val = 0x1,
145};
146
147static const struct sunxi_wdt_reg sun20i_wdt_reg = {
148 .wdt_ctrl = 0x10,
149 .wdt_cfg = 0x14,
150 .wdt_mode = 0x18,
151 .wdt_timeout_shift = 4,
152 .wdt_reset_mask = 0x03,
153 .wdt_reset_val = 0x01,
154 .wdt_key_val = 0x16aa0000,
155};
156
157static const struct udevice_id sunxi_wdt_ids[] = {
158 { .compatible = "allwinner,sun4i-a10-wdt", .data = (ulong)&sun4i_wdt_reg },
159 { .compatible = "allwinner,sun6i-a31-wdt", .data = (ulong)&sun6i_wdt_reg },
160 { .compatible = "allwinner,sun20i-d1-wdt", .data = (ulong)&sun20i_wdt_reg },
161 { /* sentinel */ }
162};
163
164static int sunxi_wdt_probe(struct udevice *dev)
165{
166 struct sunxi_wdt_priv *priv = dev_get_priv(dev);
167
168 priv->base = dev_remap_addr(dev);
169 if (!priv->base)
170 return -EINVAL;
171
172 priv->regs = (void *)dev_get_driver_data(dev);
173 if (!priv->regs)
174 return -EINVAL;
175
176 sunxi_wdt_stop(dev);
177
178 return 0;
179}
180
181U_BOOT_DRIVER(sunxi_wdt) = {
182 .name = "sunxi_wdt",
183 .id = UCLASS_WDT,
184 .of_match = sunxi_wdt_ids,
185 .probe = sunxi_wdt_probe,
186 .priv_auto = sizeof(struct sunxi_wdt_priv),
187 .ops = &sunxi_wdt_ops,
188};