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> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/reboot.h> | |
14 | #include <linux/watchdog.h> | |
15 | ||
16 | #define WDOG_CS 0x0 | |
17 | #define WDOG_CS_CMD32EN BIT(13) | |
18 | #define WDOG_CS_ULK BIT(11) | |
19 | #define WDOG_CS_RCS BIT(10) | |
eccb7fe5 FE |
20 | #define LPO_CLK 0x1 |
21 | #define LPO_CLK_SHIFT 8 | |
22 | #define WDOG_CS_CLK (LPO_CLK << LPO_CLK_SHIFT) | |
41b630f4 AH |
23 | #define WDOG_CS_EN BIT(7) |
24 | #define WDOG_CS_UPDATE BIT(5) | |
0cfbe179 AH |
25 | #define WDOG_CS_WAIT BIT(1) |
26 | #define WDOG_CS_STOP BIT(0) | |
41b630f4 AH |
27 | |
28 | #define WDOG_CNT 0x4 | |
29 | #define WDOG_TOVAL 0x8 | |
30 | ||
31 | #define REFRESH_SEQ0 0xA602 | |
32 | #define REFRESH_SEQ1 0xB480 | |
33 | #define REFRESH ((REFRESH_SEQ1 << 16) | REFRESH_SEQ0) | |
34 | ||
35 | #define UNLOCK_SEQ0 0xC520 | |
36 | #define UNLOCK_SEQ1 0xD928 | |
37 | #define UNLOCK ((UNLOCK_SEQ1 << 16) | UNLOCK_SEQ0) | |
38 | ||
39 | #define DEFAULT_TIMEOUT 60 | |
40 | #define MAX_TIMEOUT 128 | |
41 | #define WDOG_CLOCK_RATE 1000 | |
74394946 | 42 | #define WDOG_WAIT_TIMEOUT 20 |
41b630f4 AH |
43 | |
44 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
45 | module_param(nowayout, bool, 0000); | |
46 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
47 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
48 | ||
49 | struct imx7ulp_wdt_device { | |
41b630f4 AH |
50 | struct watchdog_device wdd; |
51 | void __iomem *base; | |
52 | struct clk *clk; | |
53 | }; | |
54 | ||
74394946 AH |
55 | static int imx7ulp_wdt_wait(void __iomem *base, u32 mask) |
56 | { | |
57 | u32 val = readl(base + WDOG_CS); | |
58 | ||
59 | if (!(val & mask) && readl_poll_timeout_atomic(base + WDOG_CS, val, | |
60 | val & mask, 0, | |
61 | WDOG_WAIT_TIMEOUT)) | |
62 | return -ETIMEDOUT; | |
63 | ||
64 | return 0; | |
65 | } | |
66 | ||
67 | static int imx7ulp_wdt_enable(struct watchdog_device *wdog, bool enable) | |
41b630f4 | 68 | { |
747d88a1 | 69 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); |
41b630f4 | 70 | |
747d88a1 | 71 | u32 val = readl(wdt->base + WDOG_CS); |
74394946 | 72 | int ret; |
747d88a1 | 73 | |
74394946 | 74 | local_irq_disable(); |
747d88a1 | 75 | writel(UNLOCK, wdt->base + WDOG_CNT); |
74394946 AH |
76 | ret = imx7ulp_wdt_wait(wdt->base, WDOG_CS_ULK); |
77 | if (ret) | |
78 | goto enable_out; | |
41b630f4 | 79 | if (enable) |
747d88a1 | 80 | writel(val | WDOG_CS_EN, wdt->base + WDOG_CS); |
41b630f4 | 81 | else |
747d88a1 | 82 | writel(val & ~WDOG_CS_EN, wdt->base + WDOG_CS); |
74394946 AH |
83 | imx7ulp_wdt_wait(wdt->base, WDOG_CS_RCS); |
84 | ||
85 | enable_out: | |
86 | local_irq_enable(); | |
87 | ||
88 | return ret; | |
41b630f4 AH |
89 | } |
90 | ||
c37e3581 | 91 | static bool imx7ulp_wdt_is_enabled(void __iomem *base) |
41b630f4 AH |
92 | { |
93 | u32 val = readl(base + WDOG_CS); | |
94 | ||
95 | return val & WDOG_CS_EN; | |
96 | } | |
97 | ||
98 | static int imx7ulp_wdt_ping(struct watchdog_device *wdog) | |
99 | { | |
100 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
101 | ||
102 | writel(REFRESH, wdt->base + WDOG_CNT); | |
103 | ||
104 | return 0; | |
105 | } | |
106 | ||
107 | static int imx7ulp_wdt_start(struct watchdog_device *wdog) | |
108 | { | |
74394946 | 109 | return imx7ulp_wdt_enable(wdog, true); |
41b630f4 AH |
110 | } |
111 | ||
112 | static int imx7ulp_wdt_stop(struct watchdog_device *wdog) | |
113 | { | |
74394946 | 114 | return imx7ulp_wdt_enable(wdog, false); |
41b630f4 AH |
115 | } |
116 | ||
117 | static int imx7ulp_wdt_set_timeout(struct watchdog_device *wdog, | |
118 | unsigned int timeout) | |
119 | { | |
120 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
121 | u32 val = WDOG_CLOCK_RATE * timeout; | |
74394946 | 122 | int ret; |
41b630f4 | 123 | |
74394946 | 124 | local_irq_disable(); |
41b630f4 | 125 | writel(UNLOCK, wdt->base + WDOG_CNT); |
74394946 AH |
126 | ret = imx7ulp_wdt_wait(wdt->base, WDOG_CS_ULK); |
127 | if (ret) | |
128 | goto timeout_out; | |
41b630f4 | 129 | writel(val, wdt->base + WDOG_TOVAL); |
74394946 | 130 | imx7ulp_wdt_wait(wdt->base, WDOG_CS_RCS); |
41b630f4 AH |
131 | |
132 | wdog->timeout = timeout; | |
133 | ||
74394946 AH |
134 | timeout_out: |
135 | local_irq_enable(); | |
136 | ||
137 | return ret; | |
41b630f4 AH |
138 | } |
139 | ||
6083ab7b FE |
140 | static int imx7ulp_wdt_restart(struct watchdog_device *wdog, |
141 | unsigned long action, void *data) | |
142 | { | |
143 | struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); | |
74394946 AH |
144 | int ret; |
145 | ||
146 | ret = imx7ulp_wdt_enable(wdog, true); | |
147 | if (ret) | |
148 | return ret; | |
6083ab7b | 149 | |
74394946 AH |
150 | ret = imx7ulp_wdt_set_timeout(&wdt->wdd, 1); |
151 | if (ret) | |
152 | return ret; | |
6083ab7b FE |
153 | |
154 | /* wait for wdog to fire */ | |
155 | while (true) | |
156 | ; | |
157 | ||
158 | return NOTIFY_DONE; | |
159 | } | |
160 | ||
41b630f4 AH |
161 | static const struct watchdog_ops imx7ulp_wdt_ops = { |
162 | .owner = THIS_MODULE, | |
163 | .start = imx7ulp_wdt_start, | |
164 | .stop = imx7ulp_wdt_stop, | |
165 | .ping = imx7ulp_wdt_ping, | |
166 | .set_timeout = imx7ulp_wdt_set_timeout, | |
6083ab7b | 167 | .restart = imx7ulp_wdt_restart, |
41b630f4 AH |
168 | }; |
169 | ||
170 | static const struct watchdog_info imx7ulp_wdt_info = { | |
171 | .identity = "i.MX7ULP watchdog timer", | |
172 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | | |
173 | WDIOF_MAGICCLOSE, | |
174 | }; | |
175 | ||
74394946 | 176 | static int imx7ulp_wdt_init(void __iomem *base, unsigned int timeout) |
41b630f4 AH |
177 | { |
178 | u32 val; | |
74394946 | 179 | int ret; |
41b630f4 | 180 | |
74394946 | 181 | local_irq_disable(); |
41b630f4 AH |
182 | /* unlock the wdog for reconfiguration */ |
183 | writel_relaxed(UNLOCK_SEQ0, base + WDOG_CNT); | |
184 | writel_relaxed(UNLOCK_SEQ1, base + WDOG_CNT); | |
74394946 AH |
185 | ret = imx7ulp_wdt_wait(base, WDOG_CS_ULK); |
186 | if (ret) | |
187 | goto init_out; | |
41b630f4 AH |
188 | |
189 | /* set an initial timeout value in TOVAL */ | |
190 | writel(timeout, base + WDOG_TOVAL); | |
191 | /* enable 32bit command sequence and reconfigure */ | |
0cfbe179 AH |
192 | val = WDOG_CS_CMD32EN | WDOG_CS_CLK | WDOG_CS_UPDATE | |
193 | WDOG_CS_WAIT | WDOG_CS_STOP; | |
41b630f4 | 194 | writel(val, base + WDOG_CS); |
74394946 AH |
195 | imx7ulp_wdt_wait(base, WDOG_CS_RCS); |
196 | ||
197 | init_out: | |
198 | local_irq_enable(); | |
199 | ||
200 | return ret; | |
41b630f4 AH |
201 | } |
202 | ||
203 | static void imx7ulp_wdt_action(void *data) | |
204 | { | |
205 | clk_disable_unprepare(data); | |
206 | } | |
207 | ||
208 | static int imx7ulp_wdt_probe(struct platform_device *pdev) | |
209 | { | |
210 | struct imx7ulp_wdt_device *imx7ulp_wdt; | |
211 | struct device *dev = &pdev->dev; | |
212 | struct watchdog_device *wdog; | |
213 | int ret; | |
214 | ||
215 | imx7ulp_wdt = devm_kzalloc(dev, sizeof(*imx7ulp_wdt), GFP_KERNEL); | |
216 | if (!imx7ulp_wdt) | |
217 | return -ENOMEM; | |
218 | ||
219 | platform_set_drvdata(pdev, imx7ulp_wdt); | |
220 | ||
221 | imx7ulp_wdt->base = devm_platform_ioremap_resource(pdev, 0); | |
222 | if (IS_ERR(imx7ulp_wdt->base)) | |
223 | return PTR_ERR(imx7ulp_wdt->base); | |
224 | ||
225 | imx7ulp_wdt->clk = devm_clk_get(dev, NULL); | |
226 | if (IS_ERR(imx7ulp_wdt->clk)) { | |
227 | dev_err(dev, "Failed to get watchdog clock\n"); | |
228 | return PTR_ERR(imx7ulp_wdt->clk); | |
229 | } | |
230 | ||
231 | ret = clk_prepare_enable(imx7ulp_wdt->clk); | |
232 | if (ret) | |
233 | return ret; | |
234 | ||
235 | ret = devm_add_action_or_reset(dev, imx7ulp_wdt_action, imx7ulp_wdt->clk); | |
236 | if (ret) | |
237 | return ret; | |
238 | ||
239 | wdog = &imx7ulp_wdt->wdd; | |
240 | wdog->info = &imx7ulp_wdt_info; | |
241 | wdog->ops = &imx7ulp_wdt_ops; | |
242 | wdog->min_timeout = 1; | |
243 | wdog->max_timeout = MAX_TIMEOUT; | |
244 | wdog->parent = dev; | |
245 | wdog->timeout = DEFAULT_TIMEOUT; | |
246 | ||
247 | watchdog_init_timeout(wdog, 0, dev); | |
248 | watchdog_stop_on_reboot(wdog); | |
249 | watchdog_stop_on_unregister(wdog); | |
250 | watchdog_set_drvdata(wdog, imx7ulp_wdt); | |
74394946 AH |
251 | ret = imx7ulp_wdt_init(imx7ulp_wdt->base, wdog->timeout * WDOG_CLOCK_RATE); |
252 | if (ret) | |
253 | return ret; | |
41b630f4 AH |
254 | |
255 | return devm_watchdog_register_device(dev, wdog); | |
256 | } | |
257 | ||
258 | static int __maybe_unused imx7ulp_wdt_suspend(struct device *dev) | |
259 | { | |
260 | struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); | |
261 | ||
262 | if (watchdog_active(&imx7ulp_wdt->wdd)) | |
263 | imx7ulp_wdt_stop(&imx7ulp_wdt->wdd); | |
264 | ||
265 | clk_disable_unprepare(imx7ulp_wdt->clk); | |
266 | ||
267 | return 0; | |
268 | } | |
269 | ||
270 | static int __maybe_unused imx7ulp_wdt_resume(struct device *dev) | |
271 | { | |
272 | struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); | |
273 | u32 timeout = imx7ulp_wdt->wdd.timeout * WDOG_CLOCK_RATE; | |
274 | int ret; | |
275 | ||
276 | ret = clk_prepare_enable(imx7ulp_wdt->clk); | |
277 | if (ret) | |
278 | return ret; | |
279 | ||
280 | if (imx7ulp_wdt_is_enabled(imx7ulp_wdt->base)) | |
281 | imx7ulp_wdt_init(imx7ulp_wdt->base, timeout); | |
282 | ||
283 | if (watchdog_active(&imx7ulp_wdt->wdd)) | |
284 | imx7ulp_wdt_start(&imx7ulp_wdt->wdd); | |
285 | ||
286 | return 0; | |
287 | } | |
288 | ||
289 | static SIMPLE_DEV_PM_OPS(imx7ulp_wdt_pm_ops, imx7ulp_wdt_suspend, | |
290 | imx7ulp_wdt_resume); | |
291 | ||
292 | static const struct of_device_id imx7ulp_wdt_dt_ids[] = { | |
293 | { .compatible = "fsl,imx7ulp-wdt", }, | |
294 | { /* sentinel */ } | |
295 | }; | |
296 | MODULE_DEVICE_TABLE(of, imx7ulp_wdt_dt_ids); | |
297 | ||
298 | static struct platform_driver imx7ulp_wdt_driver = { | |
299 | .probe = imx7ulp_wdt_probe, | |
300 | .driver = { | |
301 | .name = "imx7ulp-wdt", | |
302 | .pm = &imx7ulp_wdt_pm_ops, | |
303 | .of_match_table = imx7ulp_wdt_dt_ids, | |
304 | }, | |
305 | }; | |
306 | module_platform_driver(imx7ulp_wdt_driver); | |
307 | ||
308 | MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>"); | |
309 | MODULE_DESCRIPTION("Freescale i.MX7ULP watchdog driver"); | |
310 | MODULE_LICENSE("GPL v2"); |