Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Starfive Watchdog driver
  4 *
  5 * Copyright (C) 2022 StarFive Technology Co., Ltd.
  6 */
  7
  8#include <linux/clk.h>
  9#include <linux/iopoll.h>
 10#include <linux/module.h>
 11#include <linux/of.h>
 12#include <linux/platform_device.h>
 13#include <linux/pm_runtime.h>
 14#include <linux/reset.h>
 15#include <linux/watchdog.h>
 16
 17/* JH7100 Watchdog register define */
 18#define STARFIVE_WDT_JH7100_INTSTAUS	0x000
 19#define STARFIVE_WDT_JH7100_CONTROL	0x104
 20#define STARFIVE_WDT_JH7100_LOAD	0x108
 21#define STARFIVE_WDT_JH7100_EN		0x110
 22#define STARFIVE_WDT_JH7100_RELOAD	0x114	/* Write 0 or 1 to reload preset value */
 23#define STARFIVE_WDT_JH7100_VALUE	0x118
 24#define STARFIVE_WDT_JH7100_INTCLR	0x120	/*
 25						 * [0]: Write 1 to clear interrupt
 26						 * [1]: 1 mean clearing and 0 mean complete
 27						 * [31:2]: reserved.
 28						 */
 29#define STARFIVE_WDT_JH7100_LOCK	0x13c	/* write 0x378f0765 to unlock */
 30
 31/* JH7110 Watchdog register define */
 32#define STARFIVE_WDT_JH7110_LOAD	0x000
 33#define STARFIVE_WDT_JH7110_VALUE	0x004
 34#define STARFIVE_WDT_JH7110_CONTROL	0x008	/*
 35						 * [0]: reset enable;
 36						 * [1]: interrupt enable && watchdog enable
 37						 * [31:2]: reserved.
 38						 */
 39#define STARFIVE_WDT_JH7110_INTCLR	0x00c	/* clear intterupt and reload the counter */
 40#define STARFIVE_WDT_JH7110_IMS		0x014
 41#define STARFIVE_WDT_JH7110_LOCK	0xc00	/* write 0x1ACCE551 to unlock */
 42
 43/* WDOGCONTROL */
 44#define STARFIVE_WDT_ENABLE			0x1
 45#define STARFIVE_WDT_EN_SHIFT			0
 46#define STARFIVE_WDT_RESET_EN			0x1
 47#define STARFIVE_WDT_JH7100_RST_EN_SHIFT	0
 48#define STARFIVE_WDT_JH7110_RST_EN_SHIFT	1
 49
 50/* WDOGLOCK */
 51#define STARFIVE_WDT_JH7100_UNLOCK_KEY		0x378f0765
 52#define STARFIVE_WDT_JH7110_UNLOCK_KEY		0x1acce551
 53
 54/* WDOGINTCLR */
 55#define STARFIVE_WDT_INTCLR			0x1
 56#define STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT	1	/* Watchdog can clear interrupt when 0 */
 57
 58#define STARFIVE_WDT_MAXCNT			0xffffffff
 59#define STARFIVE_WDT_DEFAULT_TIME		(15)
 60#define STARFIVE_WDT_DELAY_US			0
 61#define STARFIVE_WDT_TIMEOUT_US			10000
 62
 63/* module parameter */
 64#define STARFIVE_WDT_EARLY_ENA			0
 65
 66static bool nowayout = WATCHDOG_NOWAYOUT;
 67static int heartbeat;
 68static bool early_enable = STARFIVE_WDT_EARLY_ENA;
 69
 70module_param(heartbeat, int, 0);
 71module_param(early_enable, bool, 0);
 72module_param(nowayout, bool, 0);
 73
 74MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
 75		 __MODULE_STRING(STARFIVE_WDT_DEFAULT_TIME) ")");
 76MODULE_PARM_DESC(early_enable,
 77		 "Watchdog is started at boot time if set to 1, default="
 78		 __MODULE_STRING(STARFIVE_WDT_EARLY_ENA));
 79MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
 80		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 81
 82struct starfive_wdt_variant {
 83	unsigned int control;		/* Watchdog Control Register for reset enable */
 84	unsigned int load;		/* Watchdog Load register */
 85	unsigned int reload;		/* Watchdog Reload Control register */
 86	unsigned int enable;		/* Watchdog Enable Register */
 87	unsigned int value;		/* Watchdog Counter Value Register */
 88	unsigned int int_clr;		/* Watchdog Interrupt Clear Register */
 89	unsigned int unlock;		/* Watchdog Lock Register */
 90	unsigned int int_status;	/* Watchdog Interrupt Status Register */
 91
 92	u32 unlock_key;
 93	char enrst_shift;
 94	char en_shift;
 95	bool intclr_check;		/*  whether need to check it before clearing interrupt */
 96	char intclr_ava_shift;
 97	bool double_timeout;		/* The watchdog need twice timeout to reboot */
 98};
 99
100struct starfive_wdt {
101	struct watchdog_device wdd;
102	spinlock_t lock;		/* spinlock for register handling */
103	void __iomem *base;
104	struct clk *core_clk;
105	struct clk *apb_clk;
106	const struct starfive_wdt_variant *variant;
107	unsigned long freq;
108	u32 count;			/* count of timeout */
109	u32 reload;			/* restore the count */
110};
111
112/* Register layout and configuration for the JH7100 */
113static const struct starfive_wdt_variant starfive_wdt_jh7100_variant = {
114	.control = STARFIVE_WDT_JH7100_CONTROL,
115	.load = STARFIVE_WDT_JH7100_LOAD,
116	.reload = STARFIVE_WDT_JH7100_RELOAD,
117	.enable = STARFIVE_WDT_JH7100_EN,
118	.value = STARFIVE_WDT_JH7100_VALUE,
119	.int_clr = STARFIVE_WDT_JH7100_INTCLR,
120	.unlock = STARFIVE_WDT_JH7100_LOCK,
121	.unlock_key = STARFIVE_WDT_JH7100_UNLOCK_KEY,
122	.int_status = STARFIVE_WDT_JH7100_INTSTAUS,
123	.enrst_shift = STARFIVE_WDT_JH7100_RST_EN_SHIFT,
124	.en_shift = STARFIVE_WDT_EN_SHIFT,
125	.intclr_check = true,
126	.intclr_ava_shift = STARFIVE_WDT_JH7100_INTCLR_AVA_SHIFT,
127	.double_timeout = false,
128};
129
130/* Register layout and configuration for the JH7110 */
131static const struct starfive_wdt_variant starfive_wdt_jh7110_variant = {
132	.control = STARFIVE_WDT_JH7110_CONTROL,
133	.load = STARFIVE_WDT_JH7110_LOAD,
134	.enable = STARFIVE_WDT_JH7110_CONTROL,
135	.value = STARFIVE_WDT_JH7110_VALUE,
136	.int_clr = STARFIVE_WDT_JH7110_INTCLR,
137	.unlock = STARFIVE_WDT_JH7110_LOCK,
138	.unlock_key = STARFIVE_WDT_JH7110_UNLOCK_KEY,
139	.int_status = STARFIVE_WDT_JH7110_IMS,
140	.enrst_shift = STARFIVE_WDT_JH7110_RST_EN_SHIFT,
141	.en_shift = STARFIVE_WDT_EN_SHIFT,
142	.intclr_check = false,
143	.double_timeout = true,
144};
145
146static int starfive_wdt_enable_clock(struct starfive_wdt *wdt)
147{
148	int ret;
149
150	ret = clk_prepare_enable(wdt->apb_clk);
151	if (ret)
152		return dev_err_probe(wdt->wdd.parent, ret, "failed to enable apb clock\n");
153
154	ret = clk_prepare_enable(wdt->core_clk);
155	if (ret) {
156		clk_disable_unprepare(wdt->apb_clk);
157		return dev_err_probe(wdt->wdd.parent, ret, "failed to enable core clock\n");
158	}
159
160	return 0;
161}
162
163static void starfive_wdt_disable_clock(struct starfive_wdt *wdt)
164{
165	clk_disable_unprepare(wdt->core_clk);
166	clk_disable_unprepare(wdt->apb_clk);
167}
168
169static inline int starfive_wdt_get_clock(struct starfive_wdt *wdt)
170{
171	struct device *dev = wdt->wdd.parent;
172
173	wdt->apb_clk = devm_clk_get(dev, "apb");
174	if (IS_ERR(wdt->apb_clk))
175		return dev_err_probe(dev, PTR_ERR(wdt->apb_clk), "failed to get apb clock\n");
176
177	wdt->core_clk = devm_clk_get(dev, "core");
178	if (IS_ERR(wdt->core_clk))
179		return dev_err_probe(dev, PTR_ERR(wdt->core_clk), "failed to get core clock\n");
180
181	return 0;
182}
183
184static inline int starfive_wdt_reset_init(struct device *dev)
185{
186	struct reset_control *rsts;
187	int ret;
188
189	rsts = devm_reset_control_array_get_exclusive(dev);
190	if (IS_ERR(rsts))
191		return dev_err_probe(dev, PTR_ERR(rsts), "failed to get resets\n");
192
193	ret = reset_control_deassert(rsts);
194	if (ret)
195		return dev_err_probe(dev, ret, "failed to deassert resets\n");
196
197	return 0;
198}
199
200static u32 starfive_wdt_ticks_to_sec(struct starfive_wdt *wdt, u32 ticks)
201{
202	return DIV_ROUND_CLOSEST(ticks, wdt->freq);
203}
204
205/* Write unlock-key to unlock. Write other value to lock. */
206static void starfive_wdt_unlock(struct starfive_wdt *wdt)
207	__acquires(&wdt->lock)
208{
209	spin_lock(&wdt->lock);
210	writel(wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
211}
212
213static void starfive_wdt_lock(struct starfive_wdt *wdt)
214	__releases(&wdt->lock)
215{
216	writel(~wdt->variant->unlock_key, wdt->base + wdt->variant->unlock);
217	spin_unlock(&wdt->lock);
218}
219
220/* enable watchdog interrupt to reset/reboot */
221static void starfive_wdt_enable_reset(struct starfive_wdt *wdt)
222{
223	u32 val;
224
225	val = readl(wdt->base + wdt->variant->control);
226	val |= STARFIVE_WDT_RESET_EN << wdt->variant->enrst_shift;
227	writel(val, wdt->base + wdt->variant->control);
228}
229
230/* interrupt status whether has been raised from the counter */
231static bool starfive_wdt_raise_irq_status(struct starfive_wdt *wdt)
232{
233	return !!readl(wdt->base + wdt->variant->int_status);
234}
235
236/* waiting interrupt can be free to clear */
237static int starfive_wdt_wait_int_free(struct starfive_wdt *wdt)
238{
239	u32 value;
240
241	return readl_poll_timeout_atomic(wdt->base + wdt->variant->int_clr, value,
242					 !(value & BIT(wdt->variant->intclr_ava_shift)),
243					 STARFIVE_WDT_DELAY_US, STARFIVE_WDT_TIMEOUT_US);
244}
245
246/* clear interrupt signal before initialization or reload */
247static int starfive_wdt_int_clr(struct starfive_wdt *wdt)
248{
249	int ret;
250
251	if (wdt->variant->intclr_check) {
252		ret = starfive_wdt_wait_int_free(wdt);
253		if (ret)
254			return dev_err_probe(wdt->wdd.parent, ret,
255					     "watchdog is not ready to clear interrupt.\n");
256	}
257	writel(STARFIVE_WDT_INTCLR, wdt->base + wdt->variant->int_clr);
258
259	return 0;
260}
261
262static inline void starfive_wdt_set_count(struct starfive_wdt *wdt, u32 val)
263{
264	writel(val, wdt->base + wdt->variant->load);
265}
266
267static inline u32 starfive_wdt_get_count(struct starfive_wdt *wdt)
268{
269	return readl(wdt->base + wdt->variant->value);
270}
271
272/* enable watchdog */
273static inline void starfive_wdt_enable(struct starfive_wdt *wdt)
274{
275	u32 val;
276
277	val = readl(wdt->base + wdt->variant->enable);
278	val |= STARFIVE_WDT_ENABLE << wdt->variant->en_shift;
279	writel(val, wdt->base + wdt->variant->enable);
280}
281
282/* disable watchdog */
283static inline void starfive_wdt_disable(struct starfive_wdt *wdt)
284{
285	u32 val;
286
287	val = readl(wdt->base + wdt->variant->enable);
288	val &= ~(STARFIVE_WDT_ENABLE << wdt->variant->en_shift);
289	writel(val, wdt->base + wdt->variant->enable);
290}
291
292static inline void starfive_wdt_set_reload_count(struct starfive_wdt *wdt, u32 count)
293{
294	starfive_wdt_set_count(wdt, count);
295
296	/* 7100 need set any value to reload register and could reload value to counter */
297	if (wdt->variant->reload)
298		writel(0x1, wdt->base + wdt->variant->reload);
299}
300
301static unsigned int starfive_wdt_max_timeout(struct starfive_wdt *wdt)
302{
303	if (wdt->variant->double_timeout)
304		return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, (wdt->freq / 2)) - 1;
305
306	return DIV_ROUND_UP(STARFIVE_WDT_MAXCNT, wdt->freq) - 1;
307}
308
309static unsigned int starfive_wdt_get_timeleft(struct watchdog_device *wdd)
310{
311	struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
312	u32 count;
313
314	/*
315	 * If the watchdog takes twice timeout and set half count value,
316	 * timeleft value should add the count value before first timeout.
317	 */
318	count = starfive_wdt_get_count(wdt);
319	if (wdt->variant->double_timeout && !starfive_wdt_raise_irq_status(wdt))
320		count += wdt->count;
321
322	return starfive_wdt_ticks_to_sec(wdt, count);
323}
324
325static int starfive_wdt_keepalive(struct watchdog_device *wdd)
326{
327	struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
328	int ret;
329
330	starfive_wdt_unlock(wdt);
331	ret = starfive_wdt_int_clr(wdt);
332	if (ret)
333		goto exit;
334
335	starfive_wdt_set_reload_count(wdt, wdt->count);
336
337exit:
338	/* exit with releasing spinlock and locking registers */
339	starfive_wdt_lock(wdt);
340	return ret;
341}
342
343static int starfive_wdt_start(struct starfive_wdt *wdt)
344{
345	int ret;
346
347	starfive_wdt_unlock(wdt);
348	/* disable watchdog, to be safe */
349	starfive_wdt_disable(wdt);
350
351	starfive_wdt_enable_reset(wdt);
352	ret = starfive_wdt_int_clr(wdt);
353	if (ret)
354		goto exit;
355
356	starfive_wdt_set_count(wdt, wdt->count);
357	starfive_wdt_enable(wdt);
358
359exit:
360	starfive_wdt_lock(wdt);
361	return ret;
362}
363
364static void starfive_wdt_stop(struct starfive_wdt *wdt)
365{
366	starfive_wdt_unlock(wdt);
367	starfive_wdt_disable(wdt);
368	starfive_wdt_lock(wdt);
369}
370
371static int starfive_wdt_pm_start(struct watchdog_device *wdd)
372{
373	struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
374	int ret = pm_runtime_get_sync(wdd->parent);
375
376	if (ret < 0)
377		return ret;
378
379	return starfive_wdt_start(wdt);
380}
381
382static int starfive_wdt_pm_stop(struct watchdog_device *wdd)
383{
384	struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
385
386	starfive_wdt_stop(wdt);
387	return pm_runtime_put_sync(wdd->parent);
388}
389
390static int starfive_wdt_set_timeout(struct watchdog_device *wdd,
391				    unsigned int timeout)
392{
393	struct starfive_wdt *wdt = watchdog_get_drvdata(wdd);
394	unsigned long count = timeout * wdt->freq;
395
396	/* some watchdogs take two timeouts to reset */
397	if (wdt->variant->double_timeout)
398		count /= 2;
399
400	wdt->count = count;
401	wdd->timeout = timeout;
402
403	starfive_wdt_unlock(wdt);
404	starfive_wdt_disable(wdt);
405	starfive_wdt_set_reload_count(wdt, wdt->count);
406	starfive_wdt_enable(wdt);
407	starfive_wdt_lock(wdt);
408
409	return 0;
410}
411
412#define STARFIVE_WDT_OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
413
414static const struct watchdog_info starfive_wdt_info = {
415	.options = STARFIVE_WDT_OPTIONS,
416	.identity = "StarFive Watchdog",
417};
418
419static const struct watchdog_ops starfive_wdt_ops = {
420	.owner = THIS_MODULE,
421	.start = starfive_wdt_pm_start,
422	.stop = starfive_wdt_pm_stop,
423	.ping = starfive_wdt_keepalive,
424	.set_timeout = starfive_wdt_set_timeout,
425	.get_timeleft = starfive_wdt_get_timeleft,
426};
427
428static int starfive_wdt_probe(struct platform_device *pdev)
429{
430	struct starfive_wdt *wdt;
431	int ret;
432
433	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
434	if (!wdt)
435		return -ENOMEM;
436
437	wdt->base = devm_platform_ioremap_resource(pdev, 0);
438	if (IS_ERR(wdt->base))
439		return dev_err_probe(&pdev->dev, PTR_ERR(wdt->base), "error mapping registers\n");
440
441	wdt->wdd.parent = &pdev->dev;
442	ret = starfive_wdt_get_clock(wdt);
443	if (ret)
444		return ret;
445
446	platform_set_drvdata(pdev, wdt);
447	pm_runtime_enable(&pdev->dev);
448	if (pm_runtime_enabled(&pdev->dev)) {
449		ret = pm_runtime_get_sync(&pdev->dev);
450		if (ret < 0)
451			return ret;
452	} else {
453		/* runtime PM is disabled but clocks need to be enabled */
454		ret = starfive_wdt_enable_clock(wdt);
455		if (ret)
456			return ret;
457	}
458
459	ret = starfive_wdt_reset_init(&pdev->dev);
460	if (ret)
461		goto err_exit;
462
463	watchdog_set_drvdata(&wdt->wdd, wdt);
464	wdt->wdd.info = &starfive_wdt_info;
465	wdt->wdd.ops = &starfive_wdt_ops;
466	wdt->variant = of_device_get_match_data(&pdev->dev);
467	spin_lock_init(&wdt->lock);
468
469	wdt->freq = clk_get_rate(wdt->core_clk);
470	if (!wdt->freq) {
471		dev_err(&pdev->dev, "get clock rate failed.\n");
472		ret = -EINVAL;
473		goto err_exit;
474	}
475
476	wdt->wdd.min_timeout = 1;
477	wdt->wdd.max_timeout = starfive_wdt_max_timeout(wdt);
478	wdt->wdd.timeout = STARFIVE_WDT_DEFAULT_TIME;
479	watchdog_init_timeout(&wdt->wdd, heartbeat, &pdev->dev);
480	starfive_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
481
482	watchdog_set_nowayout(&wdt->wdd, nowayout);
483	watchdog_stop_on_reboot(&wdt->wdd);
484	watchdog_stop_on_unregister(&wdt->wdd);
485
486	if (early_enable) {
487		ret = starfive_wdt_start(wdt);
488		if (ret)
489			goto err_exit;
490		set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
491	} else {
492		starfive_wdt_stop(wdt);
493	}
494
495	ret = watchdog_register_device(&wdt->wdd);
496	if (ret)
497		goto err_exit;
498
499	if (!early_enable) {
500		if (pm_runtime_enabled(&pdev->dev)) {
501			ret = pm_runtime_put_sync(&pdev->dev);
502			if (ret)
503				goto err_exit;
504		}
505	}
506
507	return 0;
508
509err_exit:
510	starfive_wdt_disable_clock(wdt);
511	pm_runtime_disable(&pdev->dev);
512
513	return ret;
514}
515
516static void starfive_wdt_remove(struct platform_device *pdev)
517{
518	struct starfive_wdt *wdt = platform_get_drvdata(pdev);
519
520	starfive_wdt_stop(wdt);
521	watchdog_unregister_device(&wdt->wdd);
522
523	if (pm_runtime_enabled(&pdev->dev))
524		pm_runtime_disable(&pdev->dev);
525	else
526		/* disable clock without PM */
527		starfive_wdt_disable_clock(wdt);
528}
529
530static void starfive_wdt_shutdown(struct platform_device *pdev)
531{
532	struct starfive_wdt *wdt = platform_get_drvdata(pdev);
533
534	starfive_wdt_pm_stop(&wdt->wdd);
535}
536
537static int starfive_wdt_suspend(struct device *dev)
538{
539	struct starfive_wdt *wdt = dev_get_drvdata(dev);
540
541	/* Save watchdog state, and turn it off. */
542	wdt->reload = starfive_wdt_get_count(wdt);
543
544	/* Note that WTCNT doesn't need to be saved. */
545	starfive_wdt_stop(wdt);
546
547	return pm_runtime_force_suspend(dev);
548}
549
550static int starfive_wdt_resume(struct device *dev)
551{
552	struct starfive_wdt *wdt = dev_get_drvdata(dev);
553	int ret;
554
555	ret = pm_runtime_force_resume(dev);
556	if (ret)
557		return ret;
558
559	starfive_wdt_unlock(wdt);
560	/* Restore watchdog state. */
561	starfive_wdt_set_reload_count(wdt, wdt->reload);
562	starfive_wdt_lock(wdt);
563
564	if (watchdog_active(&wdt->wdd))
565		return starfive_wdt_start(wdt);
566
567	return 0;
568}
569
570static int starfive_wdt_runtime_suspend(struct device *dev)
571{
572	struct starfive_wdt *wdt = dev_get_drvdata(dev);
573
574	starfive_wdt_disable_clock(wdt);
575
576	return 0;
577}
578
579static int starfive_wdt_runtime_resume(struct device *dev)
580{
581	struct starfive_wdt *wdt = dev_get_drvdata(dev);
582
583	return starfive_wdt_enable_clock(wdt);
584}
585
586static const struct dev_pm_ops starfive_wdt_pm_ops = {
587	RUNTIME_PM_OPS(starfive_wdt_runtime_suspend, starfive_wdt_runtime_resume, NULL)
588	SYSTEM_SLEEP_PM_OPS(starfive_wdt_suspend, starfive_wdt_resume)
589};
590
591static const struct of_device_id starfive_wdt_match[] = {
592	{ .compatible = "starfive,jh7100-wdt", .data = &starfive_wdt_jh7100_variant },
593	{ .compatible = "starfive,jh7110-wdt", .data = &starfive_wdt_jh7110_variant },
594	{ /* sentinel */ }
595};
596MODULE_DEVICE_TABLE(of, starfive_wdt_match);
597
598static struct platform_driver starfive_wdt_driver = {
599	.probe = starfive_wdt_probe,
600	.remove = starfive_wdt_remove,
601	.shutdown = starfive_wdt_shutdown,
602	.driver = {
603		.name = "starfive-wdt",
604		.pm = pm_ptr(&starfive_wdt_pm_ops),
605		.of_match_table = starfive_wdt_match,
606	},
607};
608module_platform_driver(starfive_wdt_driver);
609
610MODULE_AUTHOR("Xingyu Wu <xingyu.wu@starfivetech.com>");
611MODULE_AUTHOR("Samin Guo <samin.guo@starfivetech.com>");
612MODULE_DESCRIPTION("StarFive Watchdog Device Driver");
613MODULE_LICENSE("GPL");