1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/kernel.h> |
7 | #include <linux/module.h> |
8 | #include <linux/interrupt.h> |
9 | #include <linux/io.h> |
10 | #include <linux/of.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/watchdog.h> |
13 | |
14 | /* minimum and maximum watchdog trigger timeout, in seconds */ |
15 | #define MIN_WDT_TIMEOUT 1 |
16 | #define MAX_WDT_TIMEOUT 255 |
17 | |
18 | /* |
19 | * Base of the WDT registers, from the timer base address. There are |
20 | * actually 5 watchdogs that can be configured (by pairing with an available |
21 | * timer), at bases 0x100 + (WDT ID) * 0x20, where WDT ID is 0 through 4. |
22 | * This driver only configures the first watchdog (WDT ID 0). |
23 | */ |
24 | #define WDT_BASE 0x100 |
25 | #define WDT_ID 0 |
26 | |
27 | /* |
28 | * Register base of the timer that's selected for pairing with the watchdog. |
29 | * This driver arbitrarily uses timer 5, which is currently unused by |
30 | * other drivers (in particular, the Tegra clocksource driver). If this |
31 | * needs to change, take care that the new timer is not used by the |
32 | * clocksource driver. |
33 | */ |
34 | #define WDT_TIMER_BASE 0x60 |
35 | #define WDT_TIMER_ID 5 |
36 | |
37 | /* WDT registers */ |
38 | #define WDT_CFG 0x0 |
39 | #define WDT_CFG_PERIOD_SHIFT 4 |
40 | #define WDT_CFG_PERIOD_MASK 0xff |
41 | #define WDT_CFG_INT_EN (1 << 12) |
42 | #define WDT_CFG_PMC2CAR_RST_EN (1 << 15) |
43 | #define WDT_STS 0x4 |
44 | #define WDT_STS_COUNT_SHIFT 4 |
45 | #define WDT_STS_COUNT_MASK 0xff |
46 | #define WDT_STS_EXP_SHIFT 12 |
47 | #define WDT_STS_EXP_MASK 0x3 |
48 | #define WDT_CMD 0x8 |
49 | #define WDT_CMD_START_COUNTER (1 << 0) |
50 | #define WDT_CMD_DISABLE_COUNTER (1 << 1) |
51 | #define WDT_UNLOCK (0xc) |
52 | #define WDT_UNLOCK_PATTERN (0xc45a << 0) |
53 | |
54 | /* Timer registers */ |
55 | #define TIMER_PTV 0x0 |
56 | #define TIMER_EN (1 << 31) |
57 | #define TIMER_PERIODIC (1 << 30) |
58 | |
59 | struct tegra_wdt { |
60 | struct watchdog_device wdd; |
61 | void __iomem *wdt_regs; |
62 | void __iomem *tmr_regs; |
63 | }; |
64 | |
65 | #define WDT_HEARTBEAT 120 |
66 | static int heartbeat = WDT_HEARTBEAT; |
67 | module_param(heartbeat, int, 0); |
68 | MODULE_PARM_DESC(heartbeat, |
69 | "Watchdog heartbeats in seconds. (default = " |
70 | __MODULE_STRING(WDT_HEARTBEAT) ")" ); |
71 | |
72 | static bool nowayout = WATCHDOG_NOWAYOUT; |
73 | module_param(nowayout, bool, 0); |
74 | MODULE_PARM_DESC(nowayout, |
75 | "Watchdog cannot be stopped once started (default=" |
76 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")" ); |
77 | |
78 | static int tegra_wdt_start(struct watchdog_device *wdd) |
79 | { |
80 | struct tegra_wdt *wdt = watchdog_get_drvdata(wdd); |
81 | u32 val; |
82 | |
83 | /* |
84 | * This thing has a fixed 1MHz clock. Normally, we would set the |
85 | * period to 1 second by writing 1000000ul, but the watchdog system |
86 | * reset actually occurs on the 4th expiration of this counter, |
87 | * so we set the period to 1/4 of this amount. |
88 | */ |
89 | val = 1000000ul / 4; |
90 | val |= (TIMER_EN | TIMER_PERIODIC); |
91 | writel(val, addr: wdt->tmr_regs + TIMER_PTV); |
92 | |
93 | /* |
94 | * Set number of periods and start counter. |
95 | * |
96 | * Interrupt handler is not required for user space |
97 | * WDT accesses, since the caller is responsible to ping the |
98 | * WDT to reset the counter before expiration, through ioctls. |
99 | */ |
100 | val = WDT_TIMER_ID | |
101 | (wdd->timeout << WDT_CFG_PERIOD_SHIFT) | |
102 | WDT_CFG_PMC2CAR_RST_EN; |
103 | writel(val, addr: wdt->wdt_regs + WDT_CFG); |
104 | |
105 | writel(WDT_CMD_START_COUNTER, addr: wdt->wdt_regs + WDT_CMD); |
106 | |
107 | return 0; |
108 | } |
109 | |
110 | static int tegra_wdt_stop(struct watchdog_device *wdd) |
111 | { |
112 | struct tegra_wdt *wdt = watchdog_get_drvdata(wdd); |
113 | |
114 | writel(WDT_UNLOCK_PATTERN, addr: wdt->wdt_regs + WDT_UNLOCK); |
115 | writel(WDT_CMD_DISABLE_COUNTER, addr: wdt->wdt_regs + WDT_CMD); |
116 | writel(val: 0, addr: wdt->tmr_regs + TIMER_PTV); |
117 | |
118 | return 0; |
119 | } |
120 | |
121 | static int tegra_wdt_ping(struct watchdog_device *wdd) |
122 | { |
123 | struct tegra_wdt *wdt = watchdog_get_drvdata(wdd); |
124 | |
125 | writel(WDT_CMD_START_COUNTER, addr: wdt->wdt_regs + WDT_CMD); |
126 | |
127 | return 0; |
128 | } |
129 | |
130 | static int tegra_wdt_set_timeout(struct watchdog_device *wdd, |
131 | unsigned int timeout) |
132 | { |
133 | wdd->timeout = timeout; |
134 | |
135 | if (watchdog_active(wdd)) { |
136 | tegra_wdt_stop(wdd); |
137 | return tegra_wdt_start(wdd); |
138 | } |
139 | |
140 | return 0; |
141 | } |
142 | |
143 | static unsigned int tegra_wdt_get_timeleft(struct watchdog_device *wdd) |
144 | { |
145 | struct tegra_wdt *wdt = watchdog_get_drvdata(wdd); |
146 | u32 val; |
147 | int count; |
148 | int exp; |
149 | |
150 | val = readl(addr: wdt->wdt_regs + WDT_STS); |
151 | |
152 | /* Current countdown (from timeout) */ |
153 | count = (val >> WDT_STS_COUNT_SHIFT) & WDT_STS_COUNT_MASK; |
154 | |
155 | /* Number of expirations (we are waiting for the 4th expiration) */ |
156 | exp = (val >> WDT_STS_EXP_SHIFT) & WDT_STS_EXP_MASK; |
157 | |
158 | /* |
159 | * The entire thing is divided by 4 because we are ticking down 4 times |
160 | * faster due to needing to wait for the 4th expiration. |
161 | */ |
162 | return (((3 - exp) * wdd->timeout) + count) / 4; |
163 | } |
164 | |
165 | static const struct watchdog_info tegra_wdt_info = { |
166 | .options = WDIOF_SETTIMEOUT | |
167 | WDIOF_MAGICCLOSE | |
168 | WDIOF_KEEPALIVEPING, |
169 | .firmware_version = 0, |
170 | .identity = "Tegra Watchdog" , |
171 | }; |
172 | |
173 | static const struct watchdog_ops tegra_wdt_ops = { |
174 | .owner = THIS_MODULE, |
175 | .start = tegra_wdt_start, |
176 | .stop = tegra_wdt_stop, |
177 | .ping = tegra_wdt_ping, |
178 | .set_timeout = tegra_wdt_set_timeout, |
179 | .get_timeleft = tegra_wdt_get_timeleft, |
180 | }; |
181 | |
182 | static int tegra_wdt_probe(struct platform_device *pdev) |
183 | { |
184 | struct device *dev = &pdev->dev; |
185 | struct watchdog_device *wdd; |
186 | struct tegra_wdt *wdt; |
187 | void __iomem *regs; |
188 | int ret; |
189 | |
190 | /* This is the timer base. */ |
191 | regs = devm_platform_ioremap_resource(pdev, index: 0); |
192 | if (IS_ERR(ptr: regs)) |
193 | return PTR_ERR(ptr: regs); |
194 | |
195 | /* |
196 | * Allocate our watchdog driver data, which has the |
197 | * struct watchdog_device nested within it. |
198 | */ |
199 | wdt = devm_kzalloc(dev, size: sizeof(*wdt), GFP_KERNEL); |
200 | if (!wdt) |
201 | return -ENOMEM; |
202 | |
203 | /* Initialize struct tegra_wdt. */ |
204 | wdt->wdt_regs = regs + WDT_BASE; |
205 | wdt->tmr_regs = regs + WDT_TIMER_BASE; |
206 | |
207 | /* Initialize struct watchdog_device. */ |
208 | wdd = &wdt->wdd; |
209 | wdd->timeout = heartbeat; |
210 | wdd->info = &tegra_wdt_info; |
211 | wdd->ops = &tegra_wdt_ops; |
212 | wdd->min_timeout = MIN_WDT_TIMEOUT; |
213 | wdd->max_timeout = MAX_WDT_TIMEOUT; |
214 | wdd->parent = dev; |
215 | |
216 | watchdog_set_drvdata(wdd, data: wdt); |
217 | |
218 | watchdog_set_nowayout(wdd, nowayout); |
219 | |
220 | watchdog_stop_on_unregister(wdd); |
221 | ret = devm_watchdog_register_device(dev, wdd); |
222 | if (ret) |
223 | return ret; |
224 | |
225 | platform_set_drvdata(pdev, data: wdt); |
226 | |
227 | dev_info(dev, "initialized (heartbeat = %d sec, nowayout = %d)\n" , |
228 | heartbeat, nowayout); |
229 | |
230 | return 0; |
231 | } |
232 | |
233 | static int tegra_wdt_suspend(struct device *dev) |
234 | { |
235 | struct tegra_wdt *wdt = dev_get_drvdata(dev); |
236 | |
237 | if (watchdog_active(wdd: &wdt->wdd)) |
238 | tegra_wdt_stop(wdd: &wdt->wdd); |
239 | |
240 | return 0; |
241 | } |
242 | |
243 | static int tegra_wdt_resume(struct device *dev) |
244 | { |
245 | struct tegra_wdt *wdt = dev_get_drvdata(dev); |
246 | |
247 | if (watchdog_active(wdd: &wdt->wdd)) |
248 | tegra_wdt_start(wdd: &wdt->wdd); |
249 | |
250 | return 0; |
251 | } |
252 | |
253 | static const struct of_device_id tegra_wdt_of_match[] = { |
254 | { .compatible = "nvidia,tegra30-timer" , }, |
255 | { }, |
256 | }; |
257 | MODULE_DEVICE_TABLE(of, tegra_wdt_of_match); |
258 | |
259 | static DEFINE_SIMPLE_DEV_PM_OPS(tegra_wdt_pm_ops, |
260 | tegra_wdt_suspend, tegra_wdt_resume); |
261 | |
262 | static struct platform_driver tegra_wdt_driver = { |
263 | .probe = tegra_wdt_probe, |
264 | .driver = { |
265 | .name = "tegra-wdt" , |
266 | .pm = pm_sleep_ptr(&tegra_wdt_pm_ops), |
267 | .of_match_table = tegra_wdt_of_match, |
268 | }, |
269 | }; |
270 | module_platform_driver(tegra_wdt_driver); |
271 | |
272 | MODULE_AUTHOR("NVIDIA Corporation" ); |
273 | MODULE_DESCRIPTION("Tegra Watchdog Driver" ); |
274 | MODULE_LICENSE("GPL v2" ); |
275 | |