Commit | Line | Data |
---|---|---|
41b630f4 AH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright 2019 NXP. | |
4 | */ | |
5 | ||
6 | #include <linux/clk.h> | |
41b630f4 | 7 | #include <linux/io.h> |
74394946 | 8 | #include <linux/iopoll.h> |
41b630f4 AH |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> | |
11 | #include <linux/of.h> | |
8ed2dc48 | 12 | #include <linux/of_device.h> |
41b630f4 AH |
13 | #include <linux/platform_device.h> |
14 | #include <linux/reboot.h> | |
15 | #include <linux/watchdog.h> | |
16 | ||
17 | #define WDOG_CS 0x0 | |
c32b53f9 | 18 | #define WDOG_CS_FLG BIT(14) |
41b630f4 | 19 | #define WDOG_CS_CMD32EN BIT(13) |
c32b53f9 | 20 | #define WDOG_CS_PRES BIT(12) |
41b630f4 AH |
21 | #define WDOG_CS_ULK BIT(11) |
22 | #define WDOG_CS_RCS BIT(10) | |
eccb7fe5 FE |
23 | #define LPO_CLK 0x1 |
24 | #define LPO_CLK_SHIFT 8 | |
25 | #define WDOG_CS_CLK (LPO_CLK << LPO_CLK_SHIFT) | |
41b630f4 AH |
26 | #define WDOG_CS_EN BIT(7) |
27 | #define WDOG_CS_UPDATE BIT(5) | |
0cfbe179 AH |
28 | #define WDOG_CS_WAIT BIT(1) |
29 | #define WDOG_CS_STOP BIT(0) | |
41b630f4 AH |
30 | |
31 | #define WDOG_CNT 0x4 | |
32 | #define WDOG_TOVAL 0x8 | |
33 | ||
34 | #define REFRESH_SEQ0 0xA602 | |
35 | #define REFRESH_SEQ1 0xB480 | |
36 | #define REFRESH ((REFRESH_SEQ1 << 16) | REFRESH_SEQ0) | |
37 | ||
38 | #define UNLOCK_SEQ0 0xC520 | |
39 | #define UNLOCK_SEQ1 0xD928 | |
40 | #define UNLOCK ((UNLOCK_SEQ1 << 16) | UNLOCK_SEQ0) | |
41 | ||
42 | #define DEFAULT_TIMEOUT 60 | |
43 | #define MAX_TIMEOUT 128 | |
44 | #define WDOG_CLOCK_RATE 1000 | |
c32b53f9 YL |
45 | #define WDOG_ULK_WAIT_TIMEOUT 1000 |
46 | #define WDOG_RCS_WAIT_TIMEOUT 10000 | |
47 | #define WDOG_RCS_POST_WAIT 3000 | |
48 | ||
49 | #define RETRY_MAX 5 | |
41b630f4 AH |
50 | |
51 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
52 | module_param(nowayout, bool, 0000); | |
53 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
54 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
55 | ||
8ed2dc48 AG |
56 | struct imx_wdt_hw_feature { |
57 | bool prescaler_enable; | |
58 | u32 wdog_clock_rate; | |
59 | }; | |
60 | ||
41b630f4 | 61 | struct imx7ulp_wdt_device { |
41b630f4 AH |
62 | struct watchdog_device wdd; |
63 | void __iomem *base; | |
64 | struct clk *clk; | |
c32b53f9 | 65 | bool post_rcs_wait; |
8ed2dc48 | 66 | const struct imx_wdt_hw_feature *hw; |
41b630f4 AH |
67 | }; |
68 | ||
c32b53f9 | 69 | static int imx7ulp_wdt_wait_ulk(void __iomem *base) |
74394946 AH |
70 | { |
71 | u32 val = readl(base + WDOG_CS); | |
72 | ||
c32b53f9 YL |
73 | if (!(val & WDOG_CS_ULK) && |
74 | readl_poll_timeout_atomic(base + WDOG_CS, val, | |
75 | val & WDOG_CS_ULK, 0, | |
76 | WDOG_ULK_WAIT_TIMEOUT)) | |
74394946 AH |
77 | return -ETIMEDOUT; |
78 | ||
79 | return 0; | |
80 | } | |
81 | ||
c32b53f9 | 82 | static int imx7ulp_wdt_wait_rcs(struct imx7ulp_wdt_device *wdt) |
41b630f4 | 83 | { |
c32b53f9 YL |
84 | int ret = 0; |
85 | u32 val = readl(wdt->base + WDOG_CS); | |
86 | u64 timeout = (val & WDOG_CS_PRES) ? | |
87 | WDOG_RCS_WAIT_TIMEOUT * 256 : WDOG_RCS_WAIT_TIMEOUT; | |
88 | unsigned long wait_min = (val & WDOG_CS_PRES) ? | |
89 | WDOG_RCS_POST_WAIT * 256 : WDOG_RCS_POST_WAIT; | |
41b630f4 | 90 | |
c32b53f9 YL |
91 | if (!(val & WDOG_CS_RCS) && |
92 | readl_poll_timeout(wdt->base + WDOG_CS, val, val & WDOG_CS_RCS, 100, | |
93 | timeout)) | |
94 | ret = -ETIMEDOUT; | |
95 | ||
96 | /* Wait 2.5 clocks after RCS done */ | |
97 | if (wdt->post_rcs_wait) | |
98 | usleep_range(wait_min, wait_min + 2000); | |
99 | ||
100 | return ret; | |
101 | } | |
102 | ||
103 | static int _imx7ulp_wdt_enable(struct imx7ulp_wdt_device *wdt, bool enable) | |
104 | { | |
747d88a1 | 105 | u32 val = readl(wdt->base + WDOG_CS); |
74394946 | 106 | int ret; |
747d88a1 | 107 | |
74394946 | 108 | local_irq_disable(); |
747d88a1 | 109 | writel(UNLOCK, wdt->base + WDOG_CNT); |
c32b53f9 | 110 | ret = imx7ulp_wdt_wait_ulk(wdt->base); |
74394946 AH |
111 | if (ret) |
112 | goto enable_out; | |
41b630f4 | 113 | if (enable) |
747d88a1 | 114 | writel(val | WDOG_CS_EN, wdt->base + WDOG_CS); |
41b630f4 | 115 | else |
747d88a1 | 116 | writel(val & ~WDOG_CS_EN, wdt->base + WDOG_CS); |
c32b53f9 YL |
117 | |
118 | local_irq_enable(); | |
119 | ret = imx7ulp_wdt_wait_rcs(wdt); | |
120 | ||
121 | return ret; | |
74394946 AH |
122 | |
123 | enable_out: | |
124 | local_irq_enable(); | |
c32b53f9 YL |
125 | return ret; |
126 | } | |
127 | ||
128 | static int imx7ulp_wdt_enable(struct watchdog_device *wdog, bool enable) | |
129 | { | |
130 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
131 | int ret; | |
132 | u32 val; | |
133 | u32 loop = RETRY_MAX; | |
134 | ||
135 | do { | |
136 | ret = _imx7ulp_wdt_enable(wdt, enable); | |
137 | val = readl(wdt->base + WDOG_CS); | |
138 | } while (--loop > 0 && ((!!(val & WDOG_CS_EN)) != enable || ret)); | |
139 | ||
140 | if (loop == 0) | |
141 | return -EBUSY; | |
74394946 AH |
142 | |
143 | return ret; | |
41b630f4 AH |
144 | } |
145 | ||
41b630f4 AH |
146 | static int imx7ulp_wdt_ping(struct watchdog_device *wdog) |
147 | { | |
148 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
149 | ||
150 | writel(REFRESH, wdt->base + WDOG_CNT); | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
155 | static int imx7ulp_wdt_start(struct watchdog_device *wdog) | |
156 | { | |
74394946 | 157 | return imx7ulp_wdt_enable(wdog, true); |
41b630f4 AH |
158 | } |
159 | ||
160 | static int imx7ulp_wdt_stop(struct watchdog_device *wdog) | |
161 | { | |
74394946 | 162 | return imx7ulp_wdt_enable(wdog, false); |
41b630f4 AH |
163 | } |
164 | ||
c32b53f9 YL |
165 | static int _imx7ulp_wdt_set_timeout(struct imx7ulp_wdt_device *wdt, |
166 | unsigned int toval) | |
41b630f4 | 167 | { |
74394946 | 168 | int ret; |
41b630f4 | 169 | |
74394946 | 170 | local_irq_disable(); |
41b630f4 | 171 | writel(UNLOCK, wdt->base + WDOG_CNT); |
c32b53f9 | 172 | ret = imx7ulp_wdt_wait_ulk(wdt->base); |
74394946 AH |
173 | if (ret) |
174 | goto timeout_out; | |
c32b53f9 YL |
175 | writel(toval, wdt->base + WDOG_TOVAL); |
176 | local_irq_enable(); | |
177 | ret = imx7ulp_wdt_wait_rcs(wdt); | |
178 | return ret; | |
41b630f4 | 179 | |
74394946 AH |
180 | timeout_out: |
181 | local_irq_enable(); | |
c32b53f9 YL |
182 | return ret; |
183 | } | |
74394946 | 184 | |
c32b53f9 YL |
185 | static int imx7ulp_wdt_set_timeout(struct watchdog_device *wdog, |
186 | unsigned int timeout) | |
187 | { | |
188 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
8ed2dc48 | 189 | u32 toval = wdt->hw->wdog_clock_rate * timeout; |
c32b53f9 YL |
190 | u32 val; |
191 | int ret; | |
192 | u32 loop = RETRY_MAX; | |
193 | ||
194 | do { | |
195 | ret = _imx7ulp_wdt_set_timeout(wdt, toval); | |
196 | val = readl(wdt->base + WDOG_TOVAL); | |
197 | } while (--loop > 0 && (val != toval || ret)); | |
198 | ||
199 | if (loop == 0) | |
200 | return -EBUSY; | |
201 | ||
202 | wdog->timeout = timeout; | |
74394946 | 203 | return ret; |
41b630f4 AH |
204 | } |
205 | ||
6083ab7b FE |
206 | static int imx7ulp_wdt_restart(struct watchdog_device *wdog, |
207 | unsigned long action, void *data) | |
208 | { | |
209 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
74394946 AH |
210 | int ret; |
211 | ||
212 | ret = imx7ulp_wdt_enable(wdog, true); | |
213 | if (ret) | |
214 | return ret; | |
6083ab7b | 215 | |
74394946 AH |
216 | ret = imx7ulp_wdt_set_timeout(&wdt->wdd, 1); |
217 | if (ret) | |
218 | return ret; | |
6083ab7b FE |
219 | |
220 | /* wait for wdog to fire */ | |
221 | while (true) | |
222 | ; | |
223 | ||
224 | return NOTIFY_DONE; | |
225 | } | |
226 | ||
41b630f4 AH |
227 | static const struct watchdog_ops imx7ulp_wdt_ops = { |
228 | .owner = THIS_MODULE, | |
229 | .start = imx7ulp_wdt_start, | |
230 | .stop = imx7ulp_wdt_stop, | |
231 | .ping = imx7ulp_wdt_ping, | |
232 | .set_timeout = imx7ulp_wdt_set_timeout, | |
6083ab7b | 233 | .restart = imx7ulp_wdt_restart, |
41b630f4 AH |
234 | }; |
235 | ||
236 | static const struct watchdog_info imx7ulp_wdt_info = { | |
237 | .identity = "i.MX7ULP watchdog timer", | |
238 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | | |
239 | WDIOF_MAGICCLOSE, | |
240 | }; | |
241 | ||
c32b53f9 | 242 | static int _imx7ulp_wdt_init(struct imx7ulp_wdt_device *wdt, unsigned int timeout, unsigned int cs) |
41b630f4 AH |
243 | { |
244 | u32 val; | |
74394946 | 245 | int ret; |
41b630f4 | 246 | |
74394946 | 247 | local_irq_disable(); |
6371593f | 248 | |
c32b53f9 | 249 | val = readl(wdt->base + WDOG_CS); |
e809daec | 250 | if (val & WDOG_CS_CMD32EN) { |
c32b53f9 | 251 | writel(UNLOCK, wdt->base + WDOG_CNT); |
e809daec YL |
252 | } else { |
253 | mb(); | |
254 | /* unlock the wdog for reconfiguration */ | |
c32b53f9 YL |
255 | writel_relaxed(UNLOCK_SEQ0, wdt->base + WDOG_CNT); |
256 | writel_relaxed(UNLOCK_SEQ1, wdt->base + WDOG_CNT); | |
e809daec YL |
257 | mb(); |
258 | } | |
6371593f | 259 | |
c32b53f9 | 260 | ret = imx7ulp_wdt_wait_ulk(wdt->base); |
74394946 AH |
261 | if (ret) |
262 | goto init_out; | |
41b630f4 AH |
263 | |
264 | /* set an initial timeout value in TOVAL */ | |
c32b53f9 YL |
265 | writel(timeout, wdt->base + WDOG_TOVAL); |
266 | writel(cs, wdt->base + WDOG_CS); | |
267 | local_irq_enable(); | |
268 | ret = imx7ulp_wdt_wait_rcs(wdt); | |
269 | ||
270 | return ret; | |
74394946 AH |
271 | |
272 | init_out: | |
273 | local_irq_enable(); | |
c32b53f9 YL |
274 | return ret; |
275 | } | |
276 | ||
277 | static int imx7ulp_wdt_init(struct imx7ulp_wdt_device *wdt, unsigned int timeout) | |
278 | { | |
279 | /* enable 32bit command sequence and reconfigure */ | |
280 | u32 val = WDOG_CS_CMD32EN | WDOG_CS_CLK | WDOG_CS_UPDATE | | |
281 | WDOG_CS_WAIT | WDOG_CS_STOP; | |
282 | u32 cs, toval; | |
283 | int ret; | |
284 | u32 loop = RETRY_MAX; | |
285 | ||
8ed2dc48 AG |
286 | if (wdt->hw->prescaler_enable) |
287 | val |= WDOG_CS_PRES; | |
288 | ||
c32b53f9 YL |
289 | do { |
290 | ret = _imx7ulp_wdt_init(wdt, timeout, val); | |
291 | toval = readl(wdt->base + WDOG_TOVAL); | |
292 | cs = readl(wdt->base + WDOG_CS); | |
293 | cs &= ~(WDOG_CS_FLG | WDOG_CS_ULK | WDOG_CS_RCS); | |
294 | } while (--loop > 0 && (cs != val || toval != timeout || ret)); | |
295 | ||
296 | if (loop == 0) | |
297 | return -EBUSY; | |
74394946 AH |
298 | |
299 | return ret; | |
41b630f4 AH |
300 | } |
301 | ||
302 | static void imx7ulp_wdt_action(void *data) | |
303 | { | |
304 | clk_disable_unprepare(data); | |
305 | } | |
306 | ||
307 | static int imx7ulp_wdt_probe(struct platform_device *pdev) | |
308 | { | |
309 | struct imx7ulp_wdt_device *imx7ulp_wdt; | |
310 | struct device *dev = &pdev->dev; | |
311 | struct watchdog_device *wdog; | |
312 | int ret; | |
313 | ||
314 | imx7ulp_wdt = devm_kzalloc(dev, sizeof(*imx7ulp_wdt), GFP_KERNEL); | |
315 | if (!imx7ulp_wdt) | |
316 | return -ENOMEM; | |
317 | ||
318 | platform_set_drvdata(pdev, imx7ulp_wdt); | |
319 | ||
320 | imx7ulp_wdt->base = devm_platform_ioremap_resource(pdev, 0); | |
321 | if (IS_ERR(imx7ulp_wdt->base)) | |
322 | return PTR_ERR(imx7ulp_wdt->base); | |
323 | ||
324 | imx7ulp_wdt->clk = devm_clk_get(dev, NULL); | |
325 | if (IS_ERR(imx7ulp_wdt->clk)) { | |
326 | dev_err(dev, "Failed to get watchdog clock\n"); | |
327 | return PTR_ERR(imx7ulp_wdt->clk); | |
328 | } | |
329 | ||
c32b53f9 YL |
330 | imx7ulp_wdt->post_rcs_wait = true; |
331 | if (of_device_is_compatible(dev->of_node, | |
332 | "fsl,imx8ulp-wdt")) { | |
333 | dev_info(dev, "imx8ulp wdt probe\n"); | |
334 | imx7ulp_wdt->post_rcs_wait = false; | |
335 | } else { | |
336 | dev_info(dev, "imx7ulp wdt probe\n"); | |
337 | } | |
338 | ||
41b630f4 AH |
339 | ret = clk_prepare_enable(imx7ulp_wdt->clk); |
340 | if (ret) | |
341 | return ret; | |
342 | ||
343 | ret = devm_add_action_or_reset(dev, imx7ulp_wdt_action, imx7ulp_wdt->clk); | |
344 | if (ret) | |
345 | return ret; | |
346 | ||
347 | wdog = &imx7ulp_wdt->wdd; | |
348 | wdog->info = &imx7ulp_wdt_info; | |
349 | wdog->ops = &imx7ulp_wdt_ops; | |
350 | wdog->min_timeout = 1; | |
351 | wdog->max_timeout = MAX_TIMEOUT; | |
352 | wdog->parent = dev; | |
353 | wdog->timeout = DEFAULT_TIMEOUT; | |
354 | ||
355 | watchdog_init_timeout(wdog, 0, dev); | |
356 | watchdog_stop_on_reboot(wdog); | |
357 | watchdog_stop_on_unregister(wdog); | |
358 | watchdog_set_drvdata(wdog, imx7ulp_wdt); | |
8ed2dc48 AG |
359 | |
360 | imx7ulp_wdt->hw = of_device_get_match_data(dev); | |
361 | ret = imx7ulp_wdt_init(imx7ulp_wdt, wdog->timeout * imx7ulp_wdt->hw->wdog_clock_rate); | |
74394946 AH |
362 | if (ret) |
363 | return ret; | |
41b630f4 AH |
364 | |
365 | return devm_watchdog_register_device(dev, wdog); | |
366 | } | |
367 | ||
f1826833 | 368 | static int __maybe_unused imx7ulp_wdt_suspend_noirq(struct device *dev) |
41b630f4 AH |
369 | { |
370 | struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); | |
371 | ||
372 | if (watchdog_active(&imx7ulp_wdt->wdd)) | |
373 | imx7ulp_wdt_stop(&imx7ulp_wdt->wdd); | |
374 | ||
375 | clk_disable_unprepare(imx7ulp_wdt->clk); | |
376 | ||
377 | return 0; | |
378 | } | |
379 | ||
f1826833 | 380 | static int __maybe_unused imx7ulp_wdt_resume_noirq(struct device *dev) |
41b630f4 AH |
381 | { |
382 | struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); | |
8ed2dc48 | 383 | u32 timeout = imx7ulp_wdt->wdd.timeout * imx7ulp_wdt->hw->wdog_clock_rate; |
41b630f4 AH |
384 | int ret; |
385 | ||
386 | ret = clk_prepare_enable(imx7ulp_wdt->clk); | |
387 | if (ret) | |
388 | return ret; | |
389 | ||
cef6bc98 | 390 | if (watchdog_active(&imx7ulp_wdt->wdd)) { |
c32b53f9 | 391 | imx7ulp_wdt_init(imx7ulp_wdt, timeout); |
41b630f4 | 392 | imx7ulp_wdt_start(&imx7ulp_wdt->wdd); |
cef6bc98 JL |
393 | imx7ulp_wdt_ping(&imx7ulp_wdt->wdd); |
394 | } | |
41b630f4 AH |
395 | |
396 | return 0; | |
397 | } | |
398 | ||
f1826833 AH |
399 | static const struct dev_pm_ops imx7ulp_wdt_pm_ops = { |
400 | SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx7ulp_wdt_suspend_noirq, | |
401 | imx7ulp_wdt_resume_noirq) | |
402 | }; | |
41b630f4 | 403 | |
8ed2dc48 AG |
404 | static const struct imx_wdt_hw_feature imx7ulp_wdt_hw = { |
405 | .prescaler_enable = false, | |
406 | .wdog_clock_rate = 1000, | |
407 | }; | |
408 | ||
409 | static const struct imx_wdt_hw_feature imx93_wdt_hw = { | |
410 | .prescaler_enable = true, | |
411 | .wdog_clock_rate = 125, | |
412 | }; | |
413 | ||
41b630f4 | 414 | static const struct of_device_id imx7ulp_wdt_dt_ids[] = { |
8ed2dc48 AG |
415 | { .compatible = "fsl,imx8ulp-wdt", .data = &imx7ulp_wdt_hw, }, |
416 | { .compatible = "fsl,imx7ulp-wdt", .data = &imx7ulp_wdt_hw, }, | |
417 | { .compatible = "fsl,imx93-wdt", .data = &imx93_wdt_hw, }, | |
41b630f4 AH |
418 | { /* sentinel */ } |
419 | }; | |
420 | MODULE_DEVICE_TABLE(of, imx7ulp_wdt_dt_ids); | |
421 | ||
422 | static struct platform_driver imx7ulp_wdt_driver = { | |
423 | .probe = imx7ulp_wdt_probe, | |
424 | .driver = { | |
425 | .name = "imx7ulp-wdt", | |
426 | .pm = &imx7ulp_wdt_pm_ops, | |
427 | .of_match_table = imx7ulp_wdt_dt_ids, | |
428 | }, | |
429 | }; | |
430 | module_platform_driver(imx7ulp_wdt_driver); | |
431 | ||
432 | MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>"); | |
433 | MODULE_DESCRIPTION("Freescale i.MX7ULP watchdog driver"); | |
434 | MODULE_LICENSE("GPL v2"); |