Skip to content

Commit 2d63908

Browse files
Tero KristoWim Van Sebroeck
authored andcommitted
watchdog: Add K3 RTI watchdog support
Texas Instruments K3 SoCs contain an RTI (Real Time Interrupt) module which can be used as a watchdog. This IP provides a support for windowed watchdog mode, in which the watchdog must be petted within a certain time window. If it is petted either too soon, or too late, a watchdog error will be triggered. Signed-off-by: Tero Kristo <[email protected]> Reviewed-by: Guenter Roeck <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Guenter Roeck <[email protected]> Signed-off-by: Wim Van Sebroeck <[email protected]>
1 parent 936253d commit 2d63908

File tree

3 files changed

+264
-0
lines changed

3 files changed

+264
-0
lines changed

drivers/watchdog/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,14 @@ config DAVINCI_WATCHDOG
584584
NOTE: once enabled, this timer cannot be disabled.
585585
Say N if you are unsure.
586586

587+
config K3_RTI_WATCHDOG
588+
tristate "Texas Instruments K3 RTI watchdog"
589+
depends on ARCH_K3 || COMPILE_TEST
590+
select WATCHDOG_CORE
591+
help
592+
Say Y here if you want to include support for the K3 watchdog
593+
timer (RTI module) available in the K3 generation of processors.
594+
587595
config ORION_WATCHDOG
588596
tristate "Orion watchdog"
589597
depends on ARCH_ORION5X || ARCH_DOVE || MACH_DOVE || ARCH_MVEBU || (COMPILE_TEST && !ARCH_EBSA110)

drivers/watchdog/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ obj-$(CONFIG_EP93XX_WATCHDOG) += ep93xx_wdt.o
5757
obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o
5858
obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o
5959
obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o
60+
obj-$(CONFIG_K3_RTI_WATCHDOG) += rti_wdt.o
6061
obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o
6162
obj-$(CONFIG_SUNXI_WATCHDOG) += sunxi_wdt.o
6263
obj-$(CONFIG_RN5T618_WATCHDOG) += rn5t618_wdt.o

drivers/watchdog/rti_wdt.c

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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/mod_devicetable.h>
15+
#include <linux/module.h>
16+
#include <linux/moduleparam.h>
17+
#include <linux/platform_device.h>
18+
#include <linux/pm_runtime.h>
19+
#include <linux/types.h>
20+
#include <linux/watchdog.h>
21+
22+
#define DEFAULT_HEARTBEAT 60
23+
24+
/* Max heartbeat is calculated at 32kHz source clock */
25+
#define MAX_HEARTBEAT 1000
26+
27+
/* Timer register set definition */
28+
#define RTIDWDCTRL 0x90
29+
#define RTIDWDPRLD 0x94
30+
#define RTIWDSTATUS 0x98
31+
#define RTIWDKEY 0x9c
32+
#define RTIDWDCNTR 0xa0
33+
#define RTIWWDRXCTRL 0xa4
34+
#define RTIWWDSIZECTRL 0xa8
35+
36+
#define RTIWWDRX_NMI 0xa
37+
38+
#define RTIWWDSIZE_50P 0x50
39+
40+
#define WDENABLE_KEY 0xa98559da
41+
42+
#define WDKEY_SEQ0 0xe51a
43+
#define WDKEY_SEQ1 0xa35c
44+
45+
#define WDT_PRELOAD_SHIFT 13
46+
47+
#define WDT_PRELOAD_MAX 0xfff
48+
49+
#define DWDST BIT(1)
50+
51+
static int heartbeat;
52+
53+
/*
54+
* struct to hold data for each WDT device
55+
* @base - base io address of WD device
56+
* @freq - source clock frequency of WDT
57+
* @wdd - hold watchdog device as is in WDT core
58+
*/
59+
struct rti_wdt_device {
60+
void __iomem *base;
61+
unsigned long freq;
62+
struct watchdog_device wdd;
63+
};
64+
65+
static int rti_wdt_start(struct watchdog_device *wdd)
66+
{
67+
u32 timer_margin;
68+
struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
69+
70+
/* set timeout period */
71+
timer_margin = (u64)wdd->timeout * wdt->freq;
72+
timer_margin >>= WDT_PRELOAD_SHIFT;
73+
if (timer_margin > WDT_PRELOAD_MAX)
74+
timer_margin = WDT_PRELOAD_MAX;
75+
writel_relaxed(timer_margin, wdt->base + RTIDWDPRLD);
76+
77+
/*
78+
* RTI only supports a windowed mode, where the watchdog can only
79+
* be petted during the open window; not too early or not too late.
80+
* The HW configuration options only allow for the open window size
81+
* to be 50% or less than that; we obviouly want to configure the open
82+
* window as large as possible so we select the 50% option. To avoid
83+
* any glitches, we accommodate 5% safety margin also, so we setup
84+
* the min_hw_hearbeat at 55% of the timeout period.
85+
*/
86+
wdd->min_hw_heartbeat_ms = 11 * wdd->timeout * 1000 / 20;
87+
88+
/* Generate NMI when wdt expires */
89+
writel_relaxed(RTIWWDRX_NMI, wdt->base + RTIWWDRXCTRL);
90+
91+
/* Open window size 50%; this is the largest window size available */
92+
writel_relaxed(RTIWWDSIZE_50P, wdt->base + RTIWWDSIZECTRL);
93+
94+
readl_relaxed(wdt->base + RTIWWDSIZECTRL);
95+
96+
/* enable watchdog */
97+
writel_relaxed(WDENABLE_KEY, wdt->base + RTIDWDCTRL);
98+
return 0;
99+
}
100+
101+
static int rti_wdt_ping(struct watchdog_device *wdd)
102+
{
103+
struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
104+
105+
/* put watchdog in service state */
106+
writel_relaxed(WDKEY_SEQ0, wdt->base + RTIWDKEY);
107+
/* put watchdog in active state */
108+
writel_relaxed(WDKEY_SEQ1, wdt->base + RTIWDKEY);
109+
110+
return 0;
111+
}
112+
113+
static unsigned int rti_wdt_get_timeleft(struct watchdog_device *wdd)
114+
{
115+
u64 timer_counter;
116+
u32 val;
117+
struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
118+
119+
/* if timeout has occurred then return 0 */
120+
val = readl_relaxed(wdt->base + RTIWDSTATUS);
121+
if (val & DWDST)
122+
return 0;
123+
124+
timer_counter = readl_relaxed(wdt->base + RTIDWDCNTR);
125+
126+
do_div(timer_counter, wdt->freq);
127+
128+
return timer_counter;
129+
}
130+
131+
static const struct watchdog_info rti_wdt_info = {
132+
.options = WDIOF_KEEPALIVEPING,
133+
.identity = "K3 RTI Watchdog",
134+
};
135+
136+
static const struct watchdog_ops rti_wdt_ops = {
137+
.owner = THIS_MODULE,
138+
.start = rti_wdt_start,
139+
.ping = rti_wdt_ping,
140+
.get_timeleft = rti_wdt_get_timeleft,
141+
};
142+
143+
static int rti_wdt_probe(struct platform_device *pdev)
144+
{
145+
int ret = 0;
146+
struct device *dev = &pdev->dev;
147+
struct resource *wdt_mem;
148+
struct watchdog_device *wdd;
149+
struct rti_wdt_device *wdt;
150+
struct clk *clk;
151+
152+
wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
153+
if (!wdt)
154+
return -ENOMEM;
155+
156+
clk = clk_get(dev, NULL);
157+
if (IS_ERR(clk)) {
158+
if (PTR_ERR(clk) != -EPROBE_DEFER)
159+
dev_err(dev, "failed to get clock\n");
160+
return PTR_ERR(clk);
161+
}
162+
163+
wdt->freq = clk_get_rate(clk);
164+
165+
clk_put(clk);
166+
167+
if (!wdt->freq) {
168+
dev_err(dev, "Failed to get fck rate.\n");
169+
return -EINVAL;
170+
}
171+
172+
pm_runtime_enable(dev);
173+
ret = pm_runtime_get_sync(dev);
174+
if (ret) {
175+
if (ret != -EPROBE_DEFER)
176+
dev_err(&pdev->dev, "runtime pm failed\n");
177+
return ret;
178+
}
179+
180+
platform_set_drvdata(pdev, wdt);
181+
182+
wdd = &wdt->wdd;
183+
wdd->info = &rti_wdt_info;
184+
wdd->ops = &rti_wdt_ops;
185+
wdd->min_timeout = 1;
186+
wdd->max_hw_heartbeat_ms = (WDT_PRELOAD_MAX << WDT_PRELOAD_SHIFT) /
187+
wdt->freq * 1000;
188+
wdd->timeout = DEFAULT_HEARTBEAT;
189+
wdd->parent = dev;
190+
191+
watchdog_init_timeout(wdd, heartbeat, dev);
192+
193+
watchdog_set_drvdata(wdd, wdt);
194+
watchdog_set_nowayout(wdd, 1);
195+
watchdog_set_restart_priority(wdd, 128);
196+
197+
wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
198+
wdt->base = devm_ioremap_resource(dev, wdt_mem);
199+
if (IS_ERR(wdt->base)) {
200+
ret = PTR_ERR(wdt->base);
201+
goto err_iomap;
202+
}
203+
204+
ret = watchdog_register_device(wdd);
205+
if (ret) {
206+
dev_err(dev, "cannot register watchdog device\n");
207+
goto err_iomap;
208+
}
209+
210+
return 0;
211+
212+
err_iomap:
213+
pm_runtime_put_sync(&pdev->dev);
214+
215+
return ret;
216+
}
217+
218+
static int rti_wdt_remove(struct platform_device *pdev)
219+
{
220+
struct rti_wdt_device *wdt = platform_get_drvdata(pdev);
221+
222+
watchdog_unregister_device(&wdt->wdd);
223+
pm_runtime_put(&pdev->dev);
224+
225+
return 0;
226+
}
227+
228+
static const struct of_device_id rti_wdt_of_match[] = {
229+
{ .compatible = "ti,j7-rti-wdt", },
230+
{},
231+
};
232+
MODULE_DEVICE_TABLE(of, rti_wdt_of_match);
233+
234+
static struct platform_driver rti_wdt_driver = {
235+
.driver = {
236+
.name = "rti-wdt",
237+
.of_match_table = rti_wdt_of_match,
238+
},
239+
.probe = rti_wdt_probe,
240+
.remove = rti_wdt_remove,
241+
};
242+
243+
module_platform_driver(rti_wdt_driver);
244+
245+
MODULE_AUTHOR("Tero Kristo <[email protected]>");
246+
MODULE_DESCRIPTION("K3 RTI Watchdog Driver");
247+
248+
module_param(heartbeat, int, 0);
249+
MODULE_PARM_DESC(heartbeat,
250+
"Watchdog heartbeat period in seconds from 1 to "
251+
__MODULE_STRING(MAX_HEARTBEAT) ", default "
252+
__MODULE_STRING(DEFAULT_HEARTBEAT));
253+
254+
MODULE_LICENSE("GPL");
255+
MODULE_ALIAS("platform:rti-wdt");

0 commit comments

Comments
 (0)