]>
Commit | Line | Data |
---|---|---|
bb2fd8a8 WS |
1 | /* |
2 | * Watchdog driver for IMX2 and later processors | |
3 | * | |
4 | * Copyright (C) 2010 Wolfram Sang, Pengutronix e.K. <w.sang@pengutronix.de> | |
1a9c5efa | 5 | * Copyright (C) 2014 Freescale Semiconductor, Inc. |
bb2fd8a8 WS |
6 | * |
7 | * some parts adapted by similar drivers from Darius Augulis and Vladimir | |
8 | * Zapolskiy, additional improvements by Wim Van Sebroeck. | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify it | |
11 | * under the terms of the GNU General Public License version 2 as published by | |
12 | * the Free Software Foundation. | |
13 | * | |
14 | * NOTE: MX1 has a slightly different Watchdog than MX2 and later: | |
15 | * | |
16 | * MX1: MX2+: | |
17 | * ---- ----- | |
18 | * Registers: 32-bit 16-bit | |
19 | * Stopable timer: Yes No | |
20 | * Need to enable clk: No Yes | |
21 | * Halt on suspend: Manual Can be automatic | |
22 | */ | |
23 | ||
30cb042a | 24 | #include <linux/clk.h> |
334a9d81 | 25 | #include <linux/delay.h> |
bb2fd8a8 | 26 | #include <linux/init.h> |
30cb042a | 27 | #include <linux/io.h> |
bb2fd8a8 | 28 | #include <linux/kernel.h> |
bb2fd8a8 WS |
29 | #include <linux/module.h> |
30 | #include <linux/moduleparam.h> | |
f728f4bf | 31 | #include <linux/of_address.h> |
bb2fd8a8 | 32 | #include <linux/platform_device.h> |
a7977003 | 33 | #include <linux/regmap.h> |
30cb042a | 34 | #include <linux/watchdog.h> |
bb2fd8a8 WS |
35 | |
36 | #define DRIVER_NAME "imx2-wdt" | |
37 | ||
38 | #define IMX2_WDT_WCR 0x00 /* Control Register */ | |
39 | #define IMX2_WDT_WCR_WT (0xFF << 8) /* -> Watchdog Timeout Field */ | |
40 | #define IMX2_WDT_WCR_WRE (1 << 3) /* -> WDOG Reset Enable */ | |
41 | #define IMX2_WDT_WCR_WDE (1 << 2) /* -> Watchdog Enable */ | |
1a9c5efa | 42 | #define IMX2_WDT_WCR_WDZST (1 << 0) /* -> Watchdog timer Suspend */ |
bb2fd8a8 WS |
43 | |
44 | #define IMX2_WDT_WSR 0x02 /* Service Register */ | |
45 | #define IMX2_WDT_SEQ1 0x5555 /* -> service sequence 1 */ | |
46 | #define IMX2_WDT_SEQ2 0xAAAA /* -> service sequence 2 */ | |
47 | ||
474ef121 OS |
48 | #define IMX2_WDT_WRSR 0x04 /* Reset Status Register */ |
49 | #define IMX2_WDT_WRSR_TOUT (1 << 1) /* -> Reset due to Timeout */ | |
50 | ||
5fe65ce7 MP |
51 | #define IMX2_WDT_WMCR 0x08 /* Misc Register */ |
52 | ||
bb2fd8a8 WS |
53 | #define IMX2_WDT_MAX_TIME 128 |
54 | #define IMX2_WDT_DEFAULT_TIME 60 /* in seconds */ | |
55 | ||
56 | #define WDOG_SEC_TO_COUNT(s) ((s * 2 - 1) << 8) | |
57 | ||
faad5de0 | 58 | struct imx2_wdt_device { |
bb2fd8a8 | 59 | struct clk *clk; |
a7977003 | 60 | struct regmap *regmap; |
faad5de0 AG |
61 | struct watchdog_device wdog; |
62 | }; | |
bb2fd8a8 | 63 | |
86a1e189 WVS |
64 | static bool nowayout = WATCHDOG_NOWAYOUT; |
65 | module_param(nowayout, bool, 0); | |
bb2fd8a8 WS |
66 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
67 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
68 | ||
69 | ||
70 | static unsigned timeout = IMX2_WDT_DEFAULT_TIME; | |
71 | module_param(timeout, uint, 0); | |
72 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" | |
73 | __MODULE_STRING(IMX2_WDT_DEFAULT_TIME) ")"); | |
74 | ||
75 | static const struct watchdog_info imx2_wdt_info = { | |
76 | .identity = "imx2+ watchdog", | |
77 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, | |
78 | }; | |
79 | ||
4d8b229d GR |
80 | static int imx2_wdt_restart(struct watchdog_device *wdog, unsigned long action, |
81 | void *data) | |
334a9d81 | 82 | { |
2d9d2475 | 83 | struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); |
334a9d81 | 84 | unsigned int wcr_enable = IMX2_WDT_WCR_WDE; |
2d9d2475 | 85 | |
334a9d81 | 86 | /* Assert SRS signal */ |
9493c0d8 | 87 | regmap_write(wdev->regmap, IMX2_WDT_WCR, wcr_enable); |
334a9d81 JL |
88 | /* |
89 | * Due to imx6q errata ERR004346 (WDOG: WDOG SRS bit requires to be | |
90 | * written twice), we add another two writes to ensure there must be at | |
91 | * least two writes happen in the same one 32kHz clock period. We save | |
92 | * the target check here, since the writes shouldn't be a huge burden | |
93 | * for other platforms. | |
94 | */ | |
9493c0d8 FE |
95 | regmap_write(wdev->regmap, IMX2_WDT_WCR, wcr_enable); |
96 | regmap_write(wdev->regmap, IMX2_WDT_WCR, wcr_enable); | |
334a9d81 JL |
97 | |
98 | /* wait for reset to assert... */ | |
99 | mdelay(500); | |
100 | ||
2d9d2475 | 101 | return 0; |
334a9d81 JL |
102 | } |
103 | ||
faad5de0 | 104 | static inline void imx2_wdt_setup(struct watchdog_device *wdog) |
bb2fd8a8 | 105 | { |
faad5de0 | 106 | struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); |
a7977003 XL |
107 | u32 val; |
108 | ||
faad5de0 | 109 | regmap_read(wdev->regmap, IMX2_WDT_WCR, &val); |
bb2fd8a8 | 110 | |
1a9c5efa AH |
111 | /* Suspend timer in low power mode, write once-only */ |
112 | val |= IMX2_WDT_WCR_WDZST; | |
bb2fd8a8 WS |
113 | /* Strip the old watchdog Time-Out value */ |
114 | val &= ~IMX2_WDT_WCR_WT; | |
115 | /* Generate reset if WDOG times out */ | |
116 | val &= ~IMX2_WDT_WCR_WRE; | |
117 | /* Keep Watchdog Disabled */ | |
118 | val &= ~IMX2_WDT_WCR_WDE; | |
119 | /* Set the watchdog's Time-Out value */ | |
faad5de0 | 120 | val |= WDOG_SEC_TO_COUNT(wdog->timeout); |
bb2fd8a8 | 121 | |
faad5de0 | 122 | regmap_write(wdev->regmap, IMX2_WDT_WCR, val); |
bb2fd8a8 WS |
123 | |
124 | /* enable the watchdog */ | |
125 | val |= IMX2_WDT_WCR_WDE; | |
faad5de0 | 126 | regmap_write(wdev->regmap, IMX2_WDT_WCR, val); |
bb2fd8a8 WS |
127 | } |
128 | ||
faad5de0 | 129 | static inline bool imx2_wdt_is_running(struct imx2_wdt_device *wdev) |
bb2fd8a8 | 130 | { |
faad5de0 | 131 | u32 val; |
bb2fd8a8 | 132 | |
faad5de0 AG |
133 | regmap_read(wdev->regmap, IMX2_WDT_WCR, &val); |
134 | ||
135 | return val & IMX2_WDT_WCR_WDE; | |
bb2fd8a8 WS |
136 | } |
137 | ||
faad5de0 | 138 | static int imx2_wdt_ping(struct watchdog_device *wdog) |
bb2fd8a8 | 139 | { |
faad5de0 | 140 | struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); |
bb2fd8a8 | 141 | |
faad5de0 AG |
142 | regmap_write(wdev->regmap, IMX2_WDT_WSR, IMX2_WDT_SEQ1); |
143 | regmap_write(wdev->regmap, IMX2_WDT_WSR, IMX2_WDT_SEQ2); | |
144 | return 0; | |
bb2fd8a8 WS |
145 | } |
146 | ||
faad5de0 AG |
147 | static int imx2_wdt_set_timeout(struct watchdog_device *wdog, |
148 | unsigned int new_timeout) | |
bb2fd8a8 | 149 | { |
faad5de0 AG |
150 | struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); |
151 | ||
30dd4a8f MG |
152 | wdog->timeout = new_timeout; |
153 | ||
faad5de0 | 154 | regmap_update_bits(wdev->regmap, IMX2_WDT_WCR, IMX2_WDT_WCR_WT, |
a7977003 | 155 | WDOG_SEC_TO_COUNT(new_timeout)); |
faad5de0 | 156 | return 0; |
bb2fd8a8 WS |
157 | } |
158 | ||
faad5de0 | 159 | static int imx2_wdt_start(struct watchdog_device *wdog) |
bb2fd8a8 | 160 | { |
faad5de0 | 161 | struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); |
bb2fd8a8 | 162 | |
11d7aba9 | 163 | if (imx2_wdt_is_running(wdev)) |
faad5de0 | 164 | imx2_wdt_set_timeout(wdog, wdog->timeout); |
11d7aba9 | 165 | else |
faad5de0 AG |
166 | imx2_wdt_setup(wdog); |
167 | ||
11d7aba9 | 168 | set_bit(WDOG_HW_RUNNING, &wdog->status); |
bb2fd8a8 | 169 | |
11d7aba9 | 170 | return imx2_wdt_ping(wdog); |
bb2fd8a8 WS |
171 | } |
172 | ||
4bd8ce33 | 173 | static const struct watchdog_ops imx2_wdt_ops = { |
bb2fd8a8 | 174 | .owner = THIS_MODULE, |
faad5de0 | 175 | .start = imx2_wdt_start, |
faad5de0 AG |
176 | .ping = imx2_wdt_ping, |
177 | .set_timeout = imx2_wdt_set_timeout, | |
2d9d2475 | 178 | .restart = imx2_wdt_restart, |
bb2fd8a8 WS |
179 | }; |
180 | ||
4bd8ce33 | 181 | static const struct regmap_config imx2_wdt_regmap_config = { |
a7977003 XL |
182 | .reg_bits = 16, |
183 | .reg_stride = 2, | |
184 | .val_bits = 16, | |
185 | .max_register = 0x8, | |
186 | }; | |
187 | ||
bb2fd8a8 WS |
188 | static int __init imx2_wdt_probe(struct platform_device *pdev) |
189 | { | |
faad5de0 AG |
190 | struct imx2_wdt_device *wdev; |
191 | struct watchdog_device *wdog; | |
bb2fd8a8 | 192 | struct resource *res; |
a7977003 XL |
193 | void __iomem *base; |
194 | int ret; | |
faad5de0 AG |
195 | u32 val; |
196 | ||
197 | wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL); | |
198 | if (!wdev) | |
199 | return -ENOMEM; | |
bb2fd8a8 WS |
200 | |
201 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
a7977003 XL |
202 | base = devm_ioremap_resource(&pdev->dev, res); |
203 | if (IS_ERR(base)) | |
204 | return PTR_ERR(base); | |
205 | ||
faad5de0 AG |
206 | wdev->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base, |
207 | &imx2_wdt_regmap_config); | |
208 | if (IS_ERR(wdev->regmap)) { | |
a7977003 | 209 | dev_err(&pdev->dev, "regmap init failed\n"); |
faad5de0 | 210 | return PTR_ERR(wdev->regmap); |
a7977003 | 211 | } |
bb2fd8a8 | 212 | |
faad5de0 AG |
213 | wdev->clk = devm_clk_get(&pdev->dev, NULL); |
214 | if (IS_ERR(wdev->clk)) { | |
bb2fd8a8 | 215 | dev_err(&pdev->dev, "can't get Watchdog clock\n"); |
faad5de0 | 216 | return PTR_ERR(wdev->clk); |
bb2fd8a8 WS |
217 | } |
218 | ||
faad5de0 AG |
219 | wdog = &wdev->wdog; |
220 | wdog->info = &imx2_wdt_info; | |
221 | wdog->ops = &imx2_wdt_ops; | |
222 | wdog->min_timeout = 1; | |
11d7aba9 | 223 | wdog->max_hw_heartbeat_ms = IMX2_WDT_MAX_TIME * 1000; |
8135193c | 224 | wdog->parent = &pdev->dev; |
bb2fd8a8 | 225 | |
aefb163c FE |
226 | ret = clk_prepare_enable(wdev->clk); |
227 | if (ret) | |
228 | return ret; | |
bb2fd8a8 | 229 | |
faad5de0 AG |
230 | regmap_read(wdev->regmap, IMX2_WDT_WRSR, &val); |
231 | wdog->bootstatus = val & IMX2_WDT_WRSR_TOUT ? WDIOF_CARDRESET : 0; | |
bb2fd8a8 | 232 | |
faad5de0 AG |
233 | wdog->timeout = clamp_t(unsigned, timeout, 1, IMX2_WDT_MAX_TIME); |
234 | if (wdog->timeout != timeout) | |
235 | dev_warn(&pdev->dev, "Initial timeout out of range! Clamped from %u to %u\n", | |
236 | timeout, wdog->timeout); | |
237 | ||
238 | platform_set_drvdata(pdev, wdog); | |
239 | watchdog_set_drvdata(wdog, wdev); | |
240 | watchdog_set_nowayout(wdog, nowayout); | |
2d9d2475 | 241 | watchdog_set_restart_priority(wdog, 128); |
faad5de0 AG |
242 | watchdog_init_timeout(wdog, timeout, &pdev->dev); |
243 | ||
11d7aba9 GR |
244 | if (imx2_wdt_is_running(wdev)) { |
245 | imx2_wdt_set_timeout(wdog, wdog->timeout); | |
246 | set_bit(WDOG_HW_RUNNING, &wdog->status); | |
247 | } | |
faad5de0 | 248 | |
5fe65ce7 MP |
249 | /* |
250 | * Disable the watchdog power down counter at boot. Otherwise the power | |
251 | * down counter will pull down the #WDOG interrupt line for one clock | |
252 | * cycle. | |
253 | */ | |
254 | regmap_write(wdev->regmap, IMX2_WDT_WMCR, 0); | |
255 | ||
faad5de0 AG |
256 | ret = watchdog_register_device(wdog); |
257 | if (ret) { | |
258 | dev_err(&pdev->dev, "cannot register watchdog device\n"); | |
db11cba2 | 259 | goto disable_clk; |
faad5de0 AG |
260 | } |
261 | ||
262 | dev_info(&pdev->dev, "timeout %d sec (nowayout=%d)\n", | |
263 | wdog->timeout, nowayout); | |
264 | ||
265 | return 0; | |
db11cba2 FE |
266 | |
267 | disable_clk: | |
268 | clk_disable_unprepare(wdev->clk); | |
269 | return ret; | |
bb2fd8a8 WS |
270 | } |
271 | ||
272 | static int __exit imx2_wdt_remove(struct platform_device *pdev) | |
273 | { | |
faad5de0 AG |
274 | struct watchdog_device *wdog = platform_get_drvdata(pdev); |
275 | struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); | |
bb2fd8a8 | 276 | |
faad5de0 | 277 | watchdog_unregister_device(wdog); |
bb2fd8a8 | 278 | |
faad5de0 | 279 | if (imx2_wdt_is_running(wdev)) { |
faad5de0 AG |
280 | imx2_wdt_ping(wdog); |
281 | dev_crit(&pdev->dev, "Device removed: Expect reboot!\n"); | |
bdf49574 | 282 | } |
bb2fd8a8 WS |
283 | return 0; |
284 | } | |
285 | ||
286 | static void imx2_wdt_shutdown(struct platform_device *pdev) | |
287 | { | |
faad5de0 AG |
288 | struct watchdog_device *wdog = platform_get_drvdata(pdev); |
289 | struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); | |
290 | ||
291 | if (imx2_wdt_is_running(wdev)) { | |
292 | /* | |
11d7aba9 GR |
293 | * We are running, configure max timeout before reboot |
294 | * will take place. | |
faad5de0 | 295 | */ |
faad5de0 AG |
296 | imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME); |
297 | imx2_wdt_ping(wdog); | |
298 | dev_crit(&pdev->dev, "Device shutdown: Expect reboot!\n"); | |
bb2fd8a8 WS |
299 | } |
300 | } | |
301 | ||
aefbaf3a | 302 | #ifdef CONFIG_PM_SLEEP |
bbd59009 | 303 | /* Disable watchdog if it is active or non-active but still running */ |
aefbaf3a XL |
304 | static int imx2_wdt_suspend(struct device *dev) |
305 | { | |
306 | struct watchdog_device *wdog = dev_get_drvdata(dev); | |
307 | struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); | |
308 | ||
bbd59009 XL |
309 | /* The watchdog IP block is running */ |
310 | if (imx2_wdt_is_running(wdev)) { | |
311 | imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME); | |
312 | imx2_wdt_ping(wdog); | |
bbd59009 | 313 | } |
aefbaf3a XL |
314 | |
315 | clk_disable_unprepare(wdev->clk); | |
316 | ||
317 | return 0; | |
318 | } | |
319 | ||
320 | /* Enable watchdog and configure it if necessary */ | |
321 | static int imx2_wdt_resume(struct device *dev) | |
322 | { | |
323 | struct watchdog_device *wdog = dev_get_drvdata(dev); | |
324 | struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); | |
aefb163c | 325 | int ret; |
aefbaf3a | 326 | |
aefb163c FE |
327 | ret = clk_prepare_enable(wdev->clk); |
328 | if (ret) | |
329 | return ret; | |
aefbaf3a XL |
330 | |
331 | if (watchdog_active(wdog) && !imx2_wdt_is_running(wdev)) { | |
bbd59009 XL |
332 | /* |
333 | * If the watchdog is still active and resumes | |
334 | * from deep sleep state, need to restart the | |
335 | * watchdog again. | |
aefbaf3a XL |
336 | */ |
337 | imx2_wdt_setup(wdog); | |
11d7aba9 GR |
338 | } |
339 | if (imx2_wdt_is_running(wdev)) { | |
aefbaf3a XL |
340 | imx2_wdt_set_timeout(wdog, wdog->timeout); |
341 | imx2_wdt_ping(wdog); | |
aefbaf3a XL |
342 | } |
343 | ||
344 | return 0; | |
345 | } | |
346 | #endif | |
347 | ||
348 | static SIMPLE_DEV_PM_OPS(imx2_wdt_pm_ops, imx2_wdt_suspend, | |
349 | imx2_wdt_resume); | |
350 | ||
f5a427ee SG |
351 | static const struct of_device_id imx2_wdt_dt_ids[] = { |
352 | { .compatible = "fsl,imx21-wdt", }, | |
353 | { /* sentinel */ } | |
354 | }; | |
813296a1 | 355 | MODULE_DEVICE_TABLE(of, imx2_wdt_dt_ids); |
f5a427ee | 356 | |
bb2fd8a8 | 357 | static struct platform_driver imx2_wdt_driver = { |
bb2fd8a8 WS |
358 | .remove = __exit_p(imx2_wdt_remove), |
359 | .shutdown = imx2_wdt_shutdown, | |
360 | .driver = { | |
361 | .name = DRIVER_NAME, | |
aefbaf3a | 362 | .pm = &imx2_wdt_pm_ops, |
f5a427ee | 363 | .of_match_table = imx2_wdt_dt_ids, |
bb2fd8a8 WS |
364 | }, |
365 | }; | |
366 | ||
1cb9204c | 367 | module_platform_driver_probe(imx2_wdt_driver, imx2_wdt_probe); |
bb2fd8a8 WS |
368 | |
369 | MODULE_AUTHOR("Wolfram Sang"); | |
370 | MODULE_DESCRIPTION("Watchdog driver for IMX2 and later"); | |
371 | MODULE_LICENSE("GPL v2"); | |
bb2fd8a8 | 372 | MODULE_ALIAS("platform:" DRIVER_NAME); |