xref: /linux/drivers/watchdog/rti_wdt.c (revision d2c9a99135da931377240942d44f3dea104cedb8)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Watchdog driver for the K3 RTI module
4  *
5  * (c) Copyright 2019-2020 Texas Instruments Inc.
6  * All rights reserved.
7  */
8 
9 #include <linux/clk.h>
10 #include <linux/device.h>
11 #include <linux/err.h>
12 #include <linux/io.h>
13 #include <linux/kernel.h>
14 #include <linux/module.h>
15 #include <linux/moduleparam.h>
16 #include <linux/of.h>
17 #include <linux/of_reserved_mem.h>
18 #include <linux/platform_device.h>
19 #include <linux/pm_runtime.h>
20 #include <linux/types.h>
21 #include <linux/watchdog.h>
22 
23 #define DEFAULT_HEARTBEAT 60
24 
25 /* Max heartbeat is calculated at 32kHz source clock */
26 #define MAX_HEARTBEAT	1000
27 
28 /* Timer register set definition */
29 #define RTIDWDCTRL	0x90
30 #define RTIDWDPRLD	0x94
31 #define RTIWDSTATUS	0x98
32 #define RTIWDKEY	0x9c
33 #define RTIDWDCNTR	0xa0
34 #define RTIWWDRXCTRL	0xa4
35 #define RTIWWDSIZECTRL	0xa8
36 
37 #define RTIWWDRXN_RST	0x5
38 #define RTIWWDRXN_NMI	0xa
39 
40 #define RTIWWDSIZE_50P		0x50
41 #define RTIWWDSIZE_25P		0x500
42 #define RTIWWDSIZE_12P5		0x5000
43 #define RTIWWDSIZE_6P25		0x50000
44 #define RTIWWDSIZE_3P125	0x500000
45 
46 #define WDENABLE_KEY	0xa98559da
47 
48 #define WDKEY_SEQ0		0xe51a
49 #define WDKEY_SEQ1		0xa35c
50 
51 #define WDT_PRELOAD_SHIFT	13
52 
53 #define WDT_PRELOAD_MAX		0xfff
54 
55 #define DWDST			BIT(1)
56 
57 #define PON_REASON_SOF_NUM	0xBBBBCCCC
58 #define PON_REASON_MAGIC_NUM	0xDDDDDDDD
59 #define PON_REASON_EOF_NUM	0xCCCCBBBB
60 #define RESERVED_MEM_MIN_SIZE	12
61 
62 #define MAX_HW_ERROR		250
63 
64 static int heartbeat;
65 
66 struct rti_wdt_data {
67 	bool nmi;
68 };
69 
70 /*
71  * struct to hold data for each WDT device
72  * @base - base io address of WD device
73  * @freq - source clock frequency of WDT
74  * @wdd  - hold watchdog device as is in WDT core
75  * @nmi - Set if this WDT instance supports generating NMI
76  */
77 struct rti_wdt_device {
78 	void __iomem		*base;
79 	unsigned long		freq;
80 	struct watchdog_device	wdd;
81 	bool			nmi;
82 };
83 
rti_wdt_start(struct watchdog_device * wdd)84 static int rti_wdt_start(struct watchdog_device *wdd)
85 {
86 	u32 timer_margin;
87 	struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
88 	u8 reaction;
89 	int ret;
90 
91 	ret = pm_runtime_resume_and_get(wdd->parent);
92 	if (ret)
93 		return ret;
94 
95 	/* set timeout period */
96 	timer_margin = (u64)wdd->timeout * wdt->freq;
97 	timer_margin >>= WDT_PRELOAD_SHIFT;
98 	if (timer_margin > WDT_PRELOAD_MAX)
99 		timer_margin = WDT_PRELOAD_MAX;
100 	writel_relaxed(timer_margin, wdt->base + RTIDWDPRLD);
101 
102 	/*
103 	 * RTI only supports a windowed mode, where the watchdog can only
104 	 * be petted during the open window; not too early or not too late.
105 	 * The HW configuration options only allow for the open window size
106 	 * to be 50% or less than that; we obviouly want to configure the open
107 	 * window as large as possible so we select the 50% option.
108 	 */
109 	wdd->min_hw_heartbeat_ms = 520 * wdd->timeout + MAX_HW_ERROR;
110 
111 	/* When WDT expires, generate NMI or reset if NMI not supported */
112 	if (wdt->nmi)
113 		reaction = RTIWWDRXN_NMI;
114 	else
115 		reaction = RTIWWDRXN_RST;
116 
117 	writel_relaxed(reaction, wdt->base + RTIWWDRXCTRL);
118 
119 	/* Open window size 50%; this is the largest window size available */
120 	writel_relaxed(RTIWWDSIZE_50P, wdt->base + RTIWWDSIZECTRL);
121 
122 	readl_relaxed(wdt->base + RTIWWDSIZECTRL);
123 
124 	/* enable watchdog */
125 	writel_relaxed(WDENABLE_KEY, wdt->base + RTIDWDCTRL);
126 	return 0;
127 }
128 
rti_wdt_ping(struct watchdog_device * wdd)129 static int rti_wdt_ping(struct watchdog_device *wdd)
130 {
131 	struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
132 
133 	/* put watchdog in service state */
134 	writel_relaxed(WDKEY_SEQ0, wdt->base + RTIWDKEY);
135 	/* put watchdog in active state */
136 	writel_relaxed(WDKEY_SEQ1, wdt->base + RTIWDKEY);
137 
138 	return 0;
139 }
140 
rti_wdt_setup_hw_hb(struct watchdog_device * wdd,u32 wsize)141 static int rti_wdt_setup_hw_hb(struct watchdog_device *wdd, u32 wsize)
142 {
143 	/*
144 	 * RTI only supports a windowed mode, where the watchdog can only
145 	 * be petted during the open window; not too early or not too late.
146 	 * The HW configuration options only allow for the open window size
147 	 * to be 50% or less than that.
148 	 * To avoid any glitches, we accommodate 2% + max hardware error
149 	 * safety margin.
150 	 */
151 	switch (wsize) {
152 	case RTIWWDSIZE_50P:
153 		/* 50% open window => 52% min heartbeat */
154 		wdd->min_hw_heartbeat_ms = 520 * heartbeat + MAX_HW_ERROR;
155 		break;
156 
157 	case RTIWWDSIZE_25P:
158 		/* 25% open window => 77% min heartbeat */
159 		wdd->min_hw_heartbeat_ms = 770 * heartbeat + MAX_HW_ERROR;
160 		break;
161 
162 	case RTIWWDSIZE_12P5:
163 		/* 12.5% open window => 89.5% min heartbeat */
164 		wdd->min_hw_heartbeat_ms = 895 * heartbeat + MAX_HW_ERROR;
165 		break;
166 
167 	case RTIWWDSIZE_6P25:
168 		/* 6.5% open window => 95.5% min heartbeat */
169 		wdd->min_hw_heartbeat_ms = 955 * heartbeat + MAX_HW_ERROR;
170 		break;
171 
172 	case RTIWWDSIZE_3P125:
173 		/* 3.125% open window => 98.9% min heartbeat */
174 		wdd->min_hw_heartbeat_ms = 989 * heartbeat + MAX_HW_ERROR;
175 		break;
176 
177 	default:
178 		return -EINVAL;
179 	}
180 
181 	return 0;
182 }
183 
rti_wdt_get_timeleft_ms(struct watchdog_device * wdd)184 static unsigned int rti_wdt_get_timeleft_ms(struct watchdog_device *wdd)
185 {
186 	u64 timer_counter;
187 	u32 val;
188 	struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
189 
190 	/* if timeout has occurred then return 0 */
191 	val = readl_relaxed(wdt->base + RTIWDSTATUS);
192 	if (val & DWDST)
193 		return 0;
194 
195 	timer_counter = readl_relaxed(wdt->base + RTIDWDCNTR);
196 
197 	timer_counter *= 1000;
198 
199 	do_div(timer_counter, wdt->freq);
200 
201 	return timer_counter;
202 }
203 
rti_wdt_get_timeleft(struct watchdog_device * wdd)204 static unsigned int rti_wdt_get_timeleft(struct watchdog_device *wdd)
205 {
206 	return rti_wdt_get_timeleft_ms(wdd) / 1000;
207 }
208 
209 static const struct watchdog_info rti_wdt_info = {
210 	.options = WDIOF_KEEPALIVEPING,
211 	.identity = "K3 RTI Watchdog",
212 };
213 
214 static const struct watchdog_ops rti_wdt_ops = {
215 	.owner		= THIS_MODULE,
216 	.start		= rti_wdt_start,
217 	.ping		= rti_wdt_ping,
218 	.get_timeleft	= rti_wdt_get_timeleft,
219 };
220 
rti_wdt_probe(struct platform_device * pdev)221 static int rti_wdt_probe(struct platform_device *pdev)
222 {
223 	int ret = 0;
224 	struct device *dev = &pdev->dev;
225 	const struct rti_wdt_data *data;
226 	struct watchdog_device *wdd;
227 	struct rti_wdt_device *wdt;
228 	struct clk *clk;
229 	u32 last_ping = 0;
230 	u32 reserved_mem_size;
231 	struct resource res;
232 	u32 *vaddr;
233 	u64 paddr;
234 
235 	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
236 	if (!wdt)
237 		return -ENOMEM;
238 
239 	clk = clk_get(dev, NULL);
240 	if (IS_ERR(clk))
241 		return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
242 
243 	wdt->freq = clk_get_rate(clk);
244 
245 	clk_put(clk);
246 
247 	if (!wdt->freq) {
248 		dev_err(dev, "Failed to get fck rate.\n");
249 		return -EINVAL;
250 	}
251 
252 	pm_runtime_enable(dev);
253 	ret = pm_runtime_resume_and_get(dev);
254 	if (ret < 0) {
255 		pm_runtime_disable(&pdev->dev);
256 		return dev_err_probe(dev, ret, "runtime pm failed\n");
257 	}
258 
259 	platform_set_drvdata(pdev, wdt);
260 
261 	wdd = &wdt->wdd;
262 	wdd->info = &rti_wdt_info;
263 	wdd->ops = &rti_wdt_ops;
264 	wdd->min_timeout = 1;
265 	wdd->max_hw_heartbeat_ms = (WDT_PRELOAD_MAX << WDT_PRELOAD_SHIFT) /
266 		wdt->freq * 1000;
267 	wdd->timeout = DEFAULT_HEARTBEAT;
268 	wdd->parent = dev;
269 
270 	data = device_get_match_data(dev);
271 	if (!data) {
272 		ret = -ENODEV;
273 		goto err_iomap;
274 	}
275 
276 	wdt->nmi = data->nmi;
277 
278 	watchdog_set_drvdata(wdd, wdt);
279 	watchdog_set_nowayout(wdd, 1);
280 	watchdog_set_restart_priority(wdd, 128);
281 
282 	wdt->base = devm_platform_ioremap_resource(pdev, 0);
283 	if (IS_ERR(wdt->base)) {
284 		ret = PTR_ERR(wdt->base);
285 		goto err_iomap;
286 	}
287 
288 	if (readl(wdt->base + RTIDWDCTRL) == WDENABLE_KEY) {
289 		int preset_heartbeat;
290 		u32 time_left_ms;
291 		u64 heartbeat_ms;
292 		u32 wsize;
293 
294 		set_bit(WDOG_HW_RUNNING, &wdd->status);
295 		time_left_ms = rti_wdt_get_timeleft_ms(wdd);
296 		/* AM62x TRM: texp = (RTIDWDPRLD + 1) * (2^13) / RTICLK1 */
297 		heartbeat_ms = readl(wdt->base + RTIDWDPRLD) + 1;
298 		heartbeat_ms <<= WDT_PRELOAD_SHIFT;
299 		heartbeat_ms *= 1000;
300 		do_div(heartbeat_ms, wdt->freq);
301 		preset_heartbeat = heartbeat_ms + 500;
302 		preset_heartbeat /= 1000;
303 		if (preset_heartbeat != heartbeat)
304 			dev_warn(dev, "watchdog already running, ignoring heartbeat config!\n");
305 
306 		heartbeat = preset_heartbeat;
307 
308 		wsize = readl(wdt->base + RTIWWDSIZECTRL);
309 		ret = rti_wdt_setup_hw_hb(wdd, wsize);
310 		if (ret) {
311 			dev_err(dev, "bad window size.\n");
312 			goto err_iomap;
313 		}
314 
315 		last_ping = heartbeat_ms - time_left_ms;
316 		if (time_left_ms > heartbeat_ms) {
317 			dev_warn(dev, "time_left > heartbeat? Assuming last ping just before now.\n");
318 			last_ping = 0;
319 		}
320 	}
321 
322 	ret = of_reserved_mem_region_to_resource(pdev->dev.of_node, 0, &res);
323 	if (!ret) {
324 		/*
325 		 * If reserved memory is defined for watchdog reset cause.
326 		 * Readout the Power-on(PON) reason and pass to bootstatus.
327 		 */
328 		paddr = res.start;
329 		reserved_mem_size = resource_size(&res);
330 		if (reserved_mem_size < RESERVED_MEM_MIN_SIZE) {
331 			dev_err(dev, "The size of reserved memory is too small.\n");
332 			ret = -EINVAL;
333 			goto err_iomap;
334 		}
335 
336 		vaddr = memremap(paddr, reserved_mem_size, MEMREMAP_WB);
337 		if (!vaddr) {
338 			dev_err(dev, "Failed to map memory-region.\n");
339 			ret = -ENOMEM;
340 			goto err_iomap;
341 		}
342 
343 		if (vaddr[0] == PON_REASON_SOF_NUM &&
344 		    vaddr[1] == PON_REASON_MAGIC_NUM &&
345 		    vaddr[2] == PON_REASON_EOF_NUM) {
346 			wdd->bootstatus |= WDIOF_CARDRESET;
347 		}
348 		memset(vaddr, 0, reserved_mem_size);
349 		memunmap(vaddr);
350 	}
351 
352 	watchdog_init_timeout(wdd, heartbeat, dev);
353 
354 	ret = watchdog_register_device(wdd);
355 	if (ret)
356 		goto err_iomap;
357 
358 	if (last_ping)
359 		watchdog_set_last_hw_keepalive(wdd, last_ping);
360 
361 	if (!watchdog_hw_running(wdd))
362 		pm_runtime_put_sync(&pdev->dev);
363 
364 	return 0;
365 
366 err_iomap:
367 	pm_runtime_put_sync(&pdev->dev);
368 	pm_runtime_disable(&pdev->dev);
369 
370 	return ret;
371 }
372 
rti_wdt_remove(struct platform_device * pdev)373 static void rti_wdt_remove(struct platform_device *pdev)
374 {
375 	struct rti_wdt_device *wdt = platform_get_drvdata(pdev);
376 
377 	watchdog_unregister_device(&wdt->wdd);
378 
379 	if (!pm_runtime_suspended(&pdev->dev))
380 		pm_runtime_put(&pdev->dev);
381 
382 	pm_runtime_disable(&pdev->dev);
383 }
384 
385 static const struct rti_wdt_data rti_wdt_j7_data = {
386 	.nmi = true,
387 };
388 
389 static const struct rti_wdt_data rti_wdt_am62l_data = {
390 	.nmi = false,
391 };
392 
393 static const struct of_device_id rti_wdt_of_match[] = {
394 	{ .compatible = "ti,j7-rti-wdt", .data = &rti_wdt_j7_data },
395 	{ .compatible = "ti,am62l-rti-wdt", .data = &rti_wdt_am62l_data },
396 	{},
397 };
398 MODULE_DEVICE_TABLE(of, rti_wdt_of_match);
399 
400 static struct platform_driver rti_wdt_driver = {
401 	.driver = {
402 		.name = "rti-wdt",
403 		.of_match_table = rti_wdt_of_match,
404 	},
405 	.probe = rti_wdt_probe,
406 	.remove = rti_wdt_remove,
407 };
408 
409 module_platform_driver(rti_wdt_driver);
410 
411 MODULE_AUTHOR("Tero Kristo <t-kristo@ti.com>");
412 MODULE_DESCRIPTION("K3 RTI Watchdog Driver");
413 
414 module_param(heartbeat, int, 0);
415 MODULE_PARM_DESC(heartbeat,
416 		 "Watchdog heartbeat period in seconds from 1 to "
417 		 __MODULE_STRING(MAX_HEARTBEAT) ", default "
418 		 __MODULE_STRING(DEFAULT_HEARTBEAT));
419 
420 MODULE_LICENSE("GPL");
421 MODULE_ALIAS("platform:rti-wdt");
422