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