Commit | Line | Data |
---|---|---|
54e3d9b5 MB |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Watchdog driver for Marvell Armada 37xx SoCs | |
4 | * | |
5 | * Author: Marek Behun <marek.behun@nic.cz> | |
6 | */ | |
7 | ||
8 | #include <linux/clk.h> | |
9 | #include <linux/err.h> | |
10 | #include <linux/interrupt.h> | |
11 | #include <linux/io.h> | |
12 | #include <linux/kernel.h> | |
13 | #include <linux/mfd/syscon.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/moduleparam.h> | |
16 | #include <linux/of.h> | |
17 | #include <linux/of_device.h> | |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/regmap.h> | |
20 | #include <linux/types.h> | |
21 | #include <linux/watchdog.h> | |
22 | ||
23 | /* | |
24 | * There are four counters that can be used for watchdog on Armada 37xx. | |
25 | * The addresses for counter control registers are register base plus ID*0x10, | |
26 | * where ID is 0, 1, 2 or 3. | |
27 | * | |
28 | * In this driver we use IDs 0 and 1. Counter ID 1 is used as watchdog counter, | |
29 | * while counter ID 0 is used to implement pinging the watchdog: counter ID 1 is | |
30 | * set to restart counting from initial value on counter ID 0 end count event. | |
31 | * Pinging is done by forcing immediate end count event on counter ID 0. | |
32 | * If only one counter was used, pinging would have to be implemented by | |
33 | * disabling and enabling the counter, leaving the system in a vulnerable state | |
34 | * for a (really) short period of time. | |
35 | * | |
36 | * Counters ID 2 and 3 are enabled by default even before U-Boot loads, | |
37 | * therefore this driver does not provide a way to use them, eg. by setting a | |
38 | * property in device tree. | |
39 | */ | |
40 | ||
41 | #define CNTR_ID_RETRIGGER 0 | |
42 | #define CNTR_ID_WDOG 1 | |
43 | ||
44 | /* relative to cpu_misc */ | |
45 | #define WDT_TIMER_SELECT 0x64 | |
46 | #define WDT_TIMER_SELECT_MASK 0xf | |
47 | #define WDT_TIMER_SELECT_VAL BIT(CNTR_ID_WDOG) | |
48 | ||
49 | /* relative to reg */ | |
50 | #define CNTR_CTRL(id) ((id) * 0x10) | |
51 | #define CNTR_CTRL_ENABLE 0x0001 | |
52 | #define CNTR_CTRL_ACTIVE 0x0002 | |
53 | #define CNTR_CTRL_MODE_MASK 0x000c | |
54 | #define CNTR_CTRL_MODE_ONESHOT 0x0000 | |
55 | #define CNTR_CTRL_MODE_HWSIG 0x000c | |
56 | #define CNTR_CTRL_TRIG_SRC_MASK 0x00f0 | |
57 | #define CNTR_CTRL_TRIG_SRC_PREV_CNTR 0x0050 | |
58 | #define CNTR_CTRL_PRESCALE_MASK 0xff00 | |
59 | #define CNTR_CTRL_PRESCALE_MIN 2 | |
60 | #define CNTR_CTRL_PRESCALE_SHIFT 8 | |
61 | ||
62 | #define CNTR_COUNT_LOW(id) (CNTR_CTRL(id) + 0x4) | |
63 | #define CNTR_COUNT_HIGH(id) (CNTR_CTRL(id) + 0x8) | |
64 | ||
65 | #define WATCHDOG_TIMEOUT 120 | |
66 | ||
67 | static unsigned int timeout; | |
68 | module_param(timeout, int, 0); | |
69 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); | |
70 | ||
71 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
72 | module_param(nowayout, bool, 0); | |
73 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
74 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
75 | ||
76 | struct armada_37xx_watchdog { | |
77 | struct watchdog_device wdt; | |
78 | struct regmap *cpu_misc; | |
79 | void __iomem *reg; | |
80 | u64 timeout; /* in clock ticks */ | |
81 | unsigned long clk_rate; | |
82 | struct clk *clk; | |
83 | }; | |
84 | ||
85 | static u64 get_counter_value(struct armada_37xx_watchdog *dev, int id) | |
86 | { | |
87 | u64 val; | |
88 | ||
89 | /* | |
90 | * when low is read, high is latched into flip-flops so that it can be | |
91 | * read consistently without using software debouncing | |
92 | */ | |
93 | val = readl(dev->reg + CNTR_COUNT_LOW(id)); | |
94 | val |= ((u64)readl(dev->reg + CNTR_COUNT_HIGH(id))) << 32; | |
95 | ||
96 | return val; | |
97 | } | |
98 | ||
99 | static void set_counter_value(struct armada_37xx_watchdog *dev, int id, u64 val) | |
100 | { | |
101 | writel(val & 0xffffffff, dev->reg + CNTR_COUNT_LOW(id)); | |
102 | writel(val >> 32, dev->reg + CNTR_COUNT_HIGH(id)); | |
103 | } | |
104 | ||
105 | static void counter_enable(struct armada_37xx_watchdog *dev, int id) | |
106 | { | |
107 | u32 reg; | |
108 | ||
109 | reg = readl(dev->reg + CNTR_CTRL(id)); | |
110 | reg |= CNTR_CTRL_ENABLE; | |
111 | writel(reg, dev->reg + CNTR_CTRL(id)); | |
112 | } | |
113 | ||
114 | static void counter_disable(struct armada_37xx_watchdog *dev, int id) | |
115 | { | |
116 | u32 reg; | |
117 | ||
118 | reg = readl(dev->reg + CNTR_CTRL(id)); | |
119 | reg &= ~CNTR_CTRL_ENABLE; | |
120 | writel(reg, dev->reg + CNTR_CTRL(id)); | |
121 | } | |
122 | ||
123 | static void init_counter(struct armada_37xx_watchdog *dev, int id, u32 mode, | |
124 | u32 trig_src) | |
125 | { | |
126 | u32 reg; | |
127 | ||
128 | reg = readl(dev->reg + CNTR_CTRL(id)); | |
129 | ||
130 | reg &= ~(CNTR_CTRL_MODE_MASK | CNTR_CTRL_PRESCALE_MASK | | |
131 | CNTR_CTRL_TRIG_SRC_MASK); | |
132 | ||
133 | /* set mode */ | |
134 | reg |= mode & CNTR_CTRL_MODE_MASK; | |
135 | ||
136 | /* set prescaler to the min value */ | |
137 | reg |= CNTR_CTRL_PRESCALE_MIN << CNTR_CTRL_PRESCALE_SHIFT; | |
138 | ||
139 | /* set trigger source */ | |
140 | reg |= trig_src & CNTR_CTRL_TRIG_SRC_MASK; | |
141 | ||
142 | writel(reg, dev->reg + CNTR_CTRL(id)); | |
143 | } | |
144 | ||
145 | static int armada_37xx_wdt_ping(struct watchdog_device *wdt) | |
146 | { | |
147 | struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); | |
148 | ||
149 | /* counter 1 is retriggered by forcing end count on counter 0 */ | |
150 | counter_disable(dev, CNTR_ID_RETRIGGER); | |
151 | counter_enable(dev, CNTR_ID_RETRIGGER); | |
152 | ||
153 | return 0; | |
154 | } | |
155 | ||
156 | static unsigned int armada_37xx_wdt_get_timeleft(struct watchdog_device *wdt) | |
157 | { | |
158 | struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); | |
c8ca6e70 | 159 | u64 res; |
54e3d9b5 | 160 | |
c8ca6e70 MB |
161 | res = get_counter_value(dev, CNTR_ID_WDOG) * CNTR_CTRL_PRESCALE_MIN; |
162 | do_div(res, dev->clk_rate); | |
54e3d9b5 MB |
163 | |
164 | return res; | |
165 | } | |
166 | ||
167 | static int armada_37xx_wdt_set_timeout(struct watchdog_device *wdt, | |
168 | unsigned int timeout) | |
169 | { | |
170 | struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); | |
171 | ||
172 | wdt->timeout = timeout; | |
173 | ||
174 | /* | |
175 | * Compute the timeout in clock rate. We use smallest possible | |
176 | * prescaler, which divides the clock rate by 2 | |
177 | * (CNTR_CTRL_PRESCALE_MIN). | |
178 | */ | |
c8ca6e70 MB |
179 | dev->timeout = (u64)dev->clk_rate * timeout; |
180 | do_div(dev->timeout, CNTR_CTRL_PRESCALE_MIN); | |
54e3d9b5 MB |
181 | |
182 | return 0; | |
183 | } | |
184 | ||
185 | static bool armada_37xx_wdt_is_running(struct armada_37xx_watchdog *dev) | |
186 | { | |
187 | u32 reg; | |
188 | ||
189 | regmap_read(dev->cpu_misc, WDT_TIMER_SELECT, ®); | |
190 | if ((reg & WDT_TIMER_SELECT_MASK) != WDT_TIMER_SELECT_VAL) | |
191 | return false; | |
192 | ||
193 | reg = readl(dev->reg + CNTR_CTRL(CNTR_ID_WDOG)); | |
194 | return !!(reg & CNTR_CTRL_ACTIVE); | |
195 | } | |
196 | ||
197 | static int armada_37xx_wdt_start(struct watchdog_device *wdt) | |
198 | { | |
199 | struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); | |
200 | ||
201 | /* select counter 1 as watchdog counter */ | |
202 | regmap_write(dev->cpu_misc, WDT_TIMER_SELECT, WDT_TIMER_SELECT_VAL); | |
203 | ||
204 | /* init counter 0 as retrigger counter for counter 1 */ | |
205 | init_counter(dev, CNTR_ID_RETRIGGER, CNTR_CTRL_MODE_ONESHOT, 0); | |
206 | set_counter_value(dev, CNTR_ID_RETRIGGER, 0); | |
207 | ||
208 | /* init counter 1 to be retriggerable by counter 0 end count */ | |
209 | init_counter(dev, CNTR_ID_WDOG, CNTR_CTRL_MODE_HWSIG, | |
210 | CNTR_CTRL_TRIG_SRC_PREV_CNTR); | |
211 | set_counter_value(dev, CNTR_ID_WDOG, dev->timeout); | |
212 | ||
213 | /* enable counter 1 */ | |
214 | counter_enable(dev, CNTR_ID_WDOG); | |
215 | ||
216 | /* start counter 1 by forcing immediate end count on counter 0 */ | |
217 | counter_enable(dev, CNTR_ID_RETRIGGER); | |
218 | ||
219 | return 0; | |
220 | } | |
221 | ||
222 | static int armada_37xx_wdt_stop(struct watchdog_device *wdt) | |
223 | { | |
224 | struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); | |
225 | ||
226 | counter_disable(dev, CNTR_ID_WDOG); | |
227 | counter_disable(dev, CNTR_ID_RETRIGGER); | |
228 | regmap_write(dev->cpu_misc, WDT_TIMER_SELECT, 0); | |
229 | ||
230 | return 0; | |
231 | } | |
232 | ||
233 | static const struct watchdog_info armada_37xx_wdt_info = { | |
234 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | |
235 | .identity = "Armada 37xx Watchdog", | |
236 | }; | |
237 | ||
238 | static const struct watchdog_ops armada_37xx_wdt_ops = { | |
239 | .owner = THIS_MODULE, | |
240 | .start = armada_37xx_wdt_start, | |
241 | .stop = armada_37xx_wdt_stop, | |
242 | .ping = armada_37xx_wdt_ping, | |
243 | .set_timeout = armada_37xx_wdt_set_timeout, | |
244 | .get_timeleft = armada_37xx_wdt_get_timeleft, | |
245 | }; | |
246 | ||
247 | static int armada_37xx_wdt_probe(struct platform_device *pdev) | |
248 | { | |
249 | struct armada_37xx_watchdog *dev; | |
250 | struct resource *res; | |
251 | struct regmap *regmap; | |
252 | int ret; | |
253 | ||
254 | dev = devm_kzalloc(&pdev->dev, sizeof(struct armada_37xx_watchdog), | |
255 | GFP_KERNEL); | |
256 | if (!dev) | |
257 | return -ENOMEM; | |
258 | ||
259 | dev->wdt.info = &armada_37xx_wdt_info; | |
260 | dev->wdt.ops = &armada_37xx_wdt_ops; | |
261 | ||
262 | regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | |
263 | "marvell,system-controller"); | |
264 | if (IS_ERR(regmap)) | |
265 | return PTR_ERR(regmap); | |
266 | dev->cpu_misc = regmap; | |
267 | ||
268 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
269 | if (!res) | |
270 | return -ENODEV; | |
271 | dev->reg = devm_ioremap(&pdev->dev, res->start, resource_size(res)); | |
272 | ||
273 | /* init clock */ | |
274 | dev->clk = devm_clk_get(&pdev->dev, NULL); | |
275 | if (IS_ERR(dev->clk)) | |
276 | return PTR_ERR(dev->clk); | |
277 | ||
278 | ret = clk_prepare_enable(dev->clk); | |
279 | if (ret) | |
280 | return ret; | |
281 | ||
282 | dev->clk_rate = clk_get_rate(dev->clk); | |
283 | if (!dev->clk_rate) { | |
284 | ret = -EINVAL; | |
285 | goto disable_clk; | |
286 | } | |
287 | ||
288 | /* | |
289 | * Since the timeout in seconds is given as 32 bit unsigned int, and | |
290 | * the counters hold 64 bit values, even after multiplication by clock | |
291 | * rate the counter can hold timeout of UINT_MAX seconds. | |
292 | */ | |
293 | dev->wdt.min_timeout = 1; | |
294 | dev->wdt.max_timeout = UINT_MAX; | |
295 | dev->wdt.parent = &pdev->dev; | |
296 | ||
297 | /* default value, possibly override by module parameter or dtb */ | |
298 | dev->wdt.timeout = WATCHDOG_TIMEOUT; | |
299 | watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev); | |
300 | ||
301 | platform_set_drvdata(pdev, &dev->wdt); | |
302 | watchdog_set_drvdata(&dev->wdt, dev); | |
303 | ||
304 | armada_37xx_wdt_set_timeout(&dev->wdt, dev->wdt.timeout); | |
305 | ||
306 | if (armada_37xx_wdt_is_running(dev)) | |
307 | set_bit(WDOG_HW_RUNNING, &dev->wdt.status); | |
308 | ||
309 | watchdog_set_nowayout(&dev->wdt, nowayout); | |
310 | ret = watchdog_register_device(&dev->wdt); | |
311 | if (ret) | |
312 | goto disable_clk; | |
313 | ||
314 | dev_info(&pdev->dev, "Initial timeout %d sec%s\n", | |
315 | dev->wdt.timeout, nowayout ? ", nowayout" : ""); | |
316 | ||
317 | return 0; | |
318 | ||
319 | disable_clk: | |
320 | clk_disable_unprepare(dev->clk); | |
321 | return ret; | |
322 | } | |
323 | ||
324 | static int armada_37xx_wdt_remove(struct platform_device *pdev) | |
325 | { | |
326 | struct watchdog_device *wdt = platform_get_drvdata(pdev); | |
327 | struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); | |
328 | ||
329 | watchdog_unregister_device(wdt); | |
330 | clk_disable_unprepare(dev->clk); | |
331 | return 0; | |
332 | } | |
333 | ||
334 | static void armada_37xx_wdt_shutdown(struct platform_device *pdev) | |
335 | { | |
336 | struct watchdog_device *wdt = platform_get_drvdata(pdev); | |
337 | ||
338 | armada_37xx_wdt_stop(wdt); | |
339 | } | |
340 | ||
341 | static int __maybe_unused armada_37xx_wdt_suspend(struct device *dev) | |
342 | { | |
343 | struct watchdog_device *wdt = dev_get_drvdata(dev); | |
344 | ||
345 | return armada_37xx_wdt_stop(wdt); | |
346 | } | |
347 | ||
348 | static int __maybe_unused armada_37xx_wdt_resume(struct device *dev) | |
349 | { | |
350 | struct watchdog_device *wdt = dev_get_drvdata(dev); | |
351 | ||
352 | if (watchdog_active(wdt)) | |
353 | return armada_37xx_wdt_start(wdt); | |
354 | ||
355 | return 0; | |
356 | } | |
357 | ||
358 | static const struct dev_pm_ops armada_37xx_wdt_dev_pm_ops = { | |
359 | SET_SYSTEM_SLEEP_PM_OPS(armada_37xx_wdt_suspend, | |
360 | armada_37xx_wdt_resume) | |
361 | }; | |
362 | ||
363 | #ifdef CONFIG_OF | |
364 | static const struct of_device_id armada_37xx_wdt_match[] = { | |
365 | { .compatible = "marvell,armada-3700-wdt", }, | |
366 | {}, | |
367 | }; | |
368 | MODULE_DEVICE_TABLE(of, armada_37xx_wdt_match); | |
369 | #endif | |
370 | ||
371 | static struct platform_driver armada_37xx_wdt_driver = { | |
372 | .probe = armada_37xx_wdt_probe, | |
373 | .remove = armada_37xx_wdt_remove, | |
374 | .shutdown = armada_37xx_wdt_shutdown, | |
375 | .driver = { | |
376 | .name = "armada_37xx_wdt", | |
377 | .of_match_table = of_match_ptr(armada_37xx_wdt_match), | |
378 | .pm = &armada_37xx_wdt_dev_pm_ops, | |
379 | }, | |
380 | }; | |
381 | ||
382 | module_platform_driver(armada_37xx_wdt_driver); | |
383 | ||
384 | MODULE_AUTHOR("Marek Behun <marek.behun@nic.cz>"); | |
385 | MODULE_DESCRIPTION("Armada 37xx CPU Watchdog"); | |
386 | ||
387 | MODULE_LICENSE("GPL v2"); | |
388 | MODULE_ALIAS("platform:armada_37xx_wdt"); |