blob: ee9ec4cdc3a4f586dfed830d4d4e35dab9ab4ffc [file] [log] [blame]
Chanho Parke8a348f2023-11-06 08:13:16 +09001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Starfive Watchdog driver
4 *
5 * Copyright (C) 2022 StarFive Technology Co., Ltd.
6 */
7
8#include <clk.h>
9#include <dm.h>
10#include <reset.h>
11#include <wdt.h>
12#include <linux/iopoll.h>
13
14/* JH7110 Watchdog register define */
15#define STARFIVE_WDT_JH7110_LOAD 0x000
16#define STARFIVE_WDT_JH7110_VALUE 0x004
17#define STARFIVE_WDT_JH7110_CONTROL 0x008 /*
18 * [0]: reset enable;
19 * [1]: interrupt enable && watchdog enable
20 * [31:2]: reserved.
21 */
22#define STARFIVE_WDT_JH7110_INTCLR 0x00c /* clear intterupt and reload the counter */
23#define STARFIVE_WDT_JH7110_IMS 0x014
24#define STARFIVE_WDT_JH7110_LOCK 0xc00 /* write 0x1ACCE551 to unlock */
25
26/* WDOGCONTROL */
27#define STARFIVE_WDT_ENABLE 0x1
28#define STARFIVE_WDT_EN_SHIFT 0
29#define STARFIVE_WDT_RESET_EN 0x1
30#define STARFIVE_WDT_JH7110_RST_EN_SHIFT 1
31
32/* WDOGLOCK */
33#define STARFIVE_WDT_JH7110_UNLOCK_KEY 0x1acce551
34
35/* WDOGINTCLR */
36#define STARFIVE_WDT_INTCLR 0x1
37#define STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT 1 /* Watchdog can clear interrupt when 0 */
38
39#define STARFIVE_WDT_MAXCNT 0xffffffff
40#define STARFIVE_WDT_DEFAULT_TIME (15)
41#define STARFIVE_WDT_DELAY_US 0
42#define STARFIVE_WDT_TIMEOUT_US 10000
43
44/* module parameter */
45#define STARFIVE_WDT_EARLY_ENA 0
46
47struct starfive_wdt_variant {
48 unsigned int control; /* Watchdog Control Resgister for reset enable */
49 unsigned int load; /* Watchdog Load register */
50 unsigned int reload; /* Watchdog Reload Control register */
51 unsigned int enable; /* Watchdog Enable Register */
52 unsigned int value; /* Watchdog Counter Value Register */
53 unsigned int int_clr; /* Watchdog Interrupt Clear Register */
54 unsigned int unlock; /* Watchdog Lock Register */
55 unsigned int int_status; /* Watchdog Interrupt Status Register */
56
57 u32 unlock_key;
58 char enrst_shift;
59 char en_shift;
60 bool intclr_check; /* whether need to check it before clearing interrupt */
61 char intclr_ava_shift;
62 bool double_timeout; /* The watchdog need twice timeout to reboot */
63};
64
65struct starfive_wdt_priv {
66 void __iomem *base;
67 struct clk *core_clk;
68 struct clk *apb_clk;
69 struct reset_ctl_bulk *rst;
70 const struct starfive_wdt_variant *variant;
71 unsigned long freq;
72 u32 count; /* count of timeout */
73 u32 reload; /* restore the count */
74};
75
76/* Register layout and configuration for the JH7110 */
77static const struct starfive_wdt_variant starfive_wdt_jh7110_variant = {
78 .control = STARFIVE_WDT_JH7110_CONTROL,
79 .load = STARFIVE_WDT_JH7110_LOAD,
80 .enable = STARFIVE_WDT_JH7110_CONTROL,
81 .value = STARFIVE_WDT_JH7110_VALUE,
82 .int_clr = STARFIVE_WDT_JH7110_INTCLR,
83 .unlock = STARFIVE_WDT_JH7110_LOCK,
84 .unlock_key = STARFIVE_WDT_JH7110_UNLOCK_KEY,
85 .int_status = STARFIVE_WDT_JH7110_IMS,
86 .enrst_shift = STARFIVE_WDT_JH7110_RST_EN_SHIFT,
87 .en_shift = STARFIVE_WDT_EN_SHIFT,
88 .intclr_check = false,
89 .double_timeout = true,
90};
91
92static int starfive_wdt_enable_clock(struct starfive_wdt_priv *wdt)
93{
94 int ret;
95
96 ret = clk_enable(wdt->apb_clk);
97 if (ret)
98 return ret;
99
100 ret = clk_enable(wdt->core_clk);
101 if (ret) {
102 clk_disable(wdt->apb_clk);
103 return ret;
104 }
105
106 return 0;
107}
108
109static void starfive_wdt_disable_clock(struct starfive_wdt_priv *wdt)
110{
111 clk_disable(wdt->core_clk);
112 clk_disable(wdt->apb_clk);
113}
114
115/* Write unlock-key to unlock. Write other value to lock. */
116static void starfive_wdt_unlock(struct starfive_wdt_priv *wdt)
117{
118 writel(wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
119}
120
121static void starfive_wdt_lock(struct starfive_wdt_priv *wdt)
122{
123 writel(~wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
124}
125
126/* enable watchdog interrupt to reset/reboot */
127static void starfive_wdt_enable_reset(struct starfive_wdt_priv *wdt)
128{
129 u32 val;
130
131 val = readl(wdt->base + wdt->variant->control);
132 val |= STARFIVE_WDT_RESET_EN << wdt->variant->enrst_shift;
133 writel(val, wdt->base + wdt->variant->control);
134}
135
136/* waiting interrupt can be free to clear */
137static int starfive_wdt_wait_int_free(struct starfive_wdt_priv *wdt)
138{
139 u32 value;
140
141 return readl_poll_timeout(wdt->base + wdt->variant->int_clr, value,
142 !(value & BIT(wdt->variant->intclr_ava_shift)),
143 STARFIVE_WDT_TIMEOUT_US);
144}
145
146/* clear interrupt signal before initialization or reload */
147static int starfive_wdt_int_clr(struct starfive_wdt_priv *wdt)
148{
149 int ret;
150
151 if (wdt->variant->intclr_check) {
152 ret = starfive_wdt_wait_int_free(wdt);
153 if (ret)
154 return ret;
155 }
156 writel(STARFIVE_WDT_INTCLR, wdt->base + wdt->variant->int_clr);
157
158 return 0;
159}
160
161static inline void starfive_wdt_set_count(struct starfive_wdt_priv *wdt,
162 u32 val)
163{
164 writel(val, wdt->base + wdt->variant->load);
165}
166
167/* enable watchdog */
168static inline void starfive_wdt_enable(struct starfive_wdt_priv *wdt)
169{
170 u32 val;
171
172 val = readl(wdt->base + wdt->variant->enable);
173 val |= STARFIVE_WDT_ENABLE << wdt->variant->en_shift;
174 writel(val, wdt->base + wdt->variant->enable);
175}
176
177/* disable watchdog */
178static inline void starfive_wdt_disable(struct starfive_wdt_priv *wdt)
179{
180 u32 val;
181
182 val = readl(wdt->base + wdt->variant->enable);
183 val &= ~(STARFIVE_WDT_ENABLE << wdt->variant->en_shift);
184 writel(val, wdt->base + wdt->variant->enable);
185}
186
187static inline void starfive_wdt_set_reload_count(struct starfive_wdt_priv *wdt,
188 u32 count)
189{
190 starfive_wdt_set_count(wdt, count);
191
192 /* 7100 need set any value to reload register and could reload value to counter */
193 if (wdt->variant->reload)
194 writel(0x1, wdt->base + wdt->variant->reload);
195}
196
197static int starfive_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags)
198{
199 int ret;
200 struct starfive_wdt_priv *wdt = dev_get_priv(dev);
201
202 starfive_wdt_unlock(wdt);
203 /* disable watchdog, to be safe */
204 starfive_wdt_disable(wdt);
205
206 starfive_wdt_enable_reset(wdt);
207 ret = starfive_wdt_int_clr(wdt);
208 if (ret)
209 goto exit;
210
211 wdt->count = (timeout_ms / 1000) * wdt->freq;
212 if (wdt->variant->double_timeout)
213 wdt->count /= 2;
214
215 starfive_wdt_set_count(wdt, wdt->count);
216 starfive_wdt_enable(wdt);
217
218exit:
219 starfive_wdt_lock(wdt);
220 return ret;
221}
222
223static int starfive_wdt_stop(struct udevice *dev)
224{
225 struct starfive_wdt_priv *wdt = dev_get_priv(dev);
226
227 starfive_wdt_unlock(wdt);
228 starfive_wdt_disable(wdt);
229 starfive_wdt_lock(wdt);
230
231 return 0;
232}
233
234static int starfive_wdt_reset(struct udevice *dev)
235{
236 int ret;
237 struct starfive_wdt_priv *wdt = dev_get_priv(dev);
238
239 starfive_wdt_unlock(wdt);
240 ret = starfive_wdt_int_clr(wdt);
241 if (ret)
242 goto exit;
243
244 starfive_wdt_set_reload_count(wdt, wdt->count);
245
246exit:
247 starfive_wdt_lock(wdt);
248
249 return ret;
250}
251
252static const struct wdt_ops starfive_wdt_ops = {
253 .start = starfive_wdt_start,
254 .stop = starfive_wdt_stop,
255 .reset = starfive_wdt_reset,
256};
257
258static int starfive_wdt_probe(struct udevice *dev)
259{
260 struct starfive_wdt_priv *wdt = dev_get_priv(dev);
261 int ret;
262
263 ret = starfive_wdt_enable_clock(wdt);
264 if (ret)
265 return ret;
266
267 ret = reset_deassert_bulk(wdt->rst);
268 if (ret)
269 goto err_reset;
270
271 wdt->variant = (const struct starfive_wdt_variant *)dev_get_driver_data(dev);
272
273 wdt->freq = clk_get_rate(wdt->core_clk);
274 if (!wdt->freq) {
275 ret = -EINVAL;
276 goto err_get_freq;
277 }
278
279 return 0;
280
281err_get_freq:
282 reset_assert_bulk(wdt->rst);
283err_reset:
284 starfive_wdt_disable_clock(wdt);
285
286 return ret;
287}
288
289static int starfive_wdt_of_to_plat(struct udevice *dev)
290{
291 struct starfive_wdt_priv *wdt = dev_get_priv(dev);
292
293 wdt->base = (void *)dev_read_addr(dev);
294 if (!wdt->base)
295 return -ENODEV;
296
297 wdt->apb_clk = devm_clk_get(dev, "apb");
298 if (IS_ERR(wdt->apb_clk))
299 return -ENODEV;
300
301 wdt->core_clk = devm_clk_get(dev, "core");
302 if (IS_ERR(wdt->core_clk))
303 return -ENODEV;
304
305 wdt->rst = devm_reset_bulk_get(dev);
306 if (IS_ERR(wdt->rst))
307 return -ENODEV;
308
309 return 0;
310}
311
312static const struct udevice_id starfive_wdt_ids[] = {
313 {
314 .compatible = "starfive,jh7110-wdt",
315 .data = (ulong)&starfive_wdt_jh7110_variant
316 }, {
317 /* sentinel */
318 }
319};
320
321U_BOOT_DRIVER(starfive_wdt) = {
322 .name = "starfive_wdt",
323 .id = UCLASS_WDT,
324 .of_match = starfive_wdt_ids,
325 .priv_auto = sizeof(struct starfive_wdt_priv),
326 .probe = starfive_wdt_probe,
327 .of_to_plat = starfive_wdt_of_to_plat,
328 .ops = &starfive_wdt_ops,
329};