Commit | Line | Data |
---|---|---|
47760346 EL |
1 | /* |
2 | * Spreadtrum watchdog driver | |
3 | * Copyright (C) 2017 Spreadtrum - http://www.spreadtrum.com | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or | |
6 | * modify it under the terms of the GNU General Public License | |
7 | * version 2 as published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, but | |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * General Public License for more details. | |
13 | */ | |
14 | ||
15 | #include <linux/bitops.h> | |
16 | #include <linux/clk.h> | |
17 | #include <linux/device.h> | |
18 | #include <linux/err.h> | |
19 | #include <linux/interrupt.h> | |
20 | #include <linux/io.h> | |
21 | #include <linux/kernel.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/of.h> | |
24 | #include <linux/of_address.h> | |
25 | #include <linux/platform_device.h> | |
26 | #include <linux/watchdog.h> | |
27 | ||
28 | #define SPRD_WDT_LOAD_LOW 0x0 | |
29 | #define SPRD_WDT_LOAD_HIGH 0x4 | |
30 | #define SPRD_WDT_CTRL 0x8 | |
31 | #define SPRD_WDT_INT_CLR 0xc | |
32 | #define SPRD_WDT_INT_RAW 0x10 | |
33 | #define SPRD_WDT_INT_MSK 0x14 | |
34 | #define SPRD_WDT_CNT_LOW 0x18 | |
35 | #define SPRD_WDT_CNT_HIGH 0x1c | |
36 | #define SPRD_WDT_LOCK 0x20 | |
37 | #define SPRD_WDT_IRQ_LOAD_LOW 0x2c | |
38 | #define SPRD_WDT_IRQ_LOAD_HIGH 0x30 | |
39 | ||
40 | /* WDT_CTRL */ | |
41 | #define SPRD_WDT_INT_EN_BIT BIT(0) | |
42 | #define SPRD_WDT_CNT_EN_BIT BIT(1) | |
43 | #define SPRD_WDT_NEW_VER_EN BIT(2) | |
44 | #define SPRD_WDT_RST_EN_BIT BIT(3) | |
45 | ||
46 | /* WDT_INT_CLR */ | |
47 | #define SPRD_WDT_INT_CLEAR_BIT BIT(0) | |
48 | #define SPRD_WDT_RST_CLEAR_BIT BIT(3) | |
49 | ||
50 | /* WDT_INT_RAW */ | |
51 | #define SPRD_WDT_INT_RAW_BIT BIT(0) | |
52 | #define SPRD_WDT_RST_RAW_BIT BIT(3) | |
53 | #define SPRD_WDT_LD_BUSY_BIT BIT(4) | |
54 | ||
55 | /* 1s equal to 32768 counter steps */ | |
56 | #define SPRD_WDT_CNT_STEP 32768 | |
57 | ||
58 | #define SPRD_WDT_UNLOCK_KEY 0xe551 | |
59 | #define SPRD_WDT_MIN_TIMEOUT 3 | |
60 | #define SPRD_WDT_MAX_TIMEOUT 60 | |
61 | ||
62 | #define SPRD_WDT_CNT_HIGH_SHIFT 16 | |
63 | #define SPRD_WDT_LOW_VALUE_MASK GENMASK(15, 0) | |
64 | #define SPRD_WDT_LOAD_TIMEOUT 1000 | |
65 | ||
66 | struct sprd_wdt { | |
67 | void __iomem *base; | |
68 | struct watchdog_device wdd; | |
69 | struct clk *enable; | |
70 | struct clk *rtc_enable; | |
71 | int irq; | |
72 | }; | |
73 | ||
74 | static inline struct sprd_wdt *to_sprd_wdt(struct watchdog_device *wdd) | |
75 | { | |
76 | return container_of(wdd, struct sprd_wdt, wdd); | |
77 | } | |
78 | ||
79 | static inline void sprd_wdt_lock(void __iomem *addr) | |
80 | { | |
81 | writel_relaxed(0x0, addr + SPRD_WDT_LOCK); | |
82 | } | |
83 | ||
84 | static inline void sprd_wdt_unlock(void __iomem *addr) | |
85 | { | |
86 | writel_relaxed(SPRD_WDT_UNLOCK_KEY, addr + SPRD_WDT_LOCK); | |
87 | } | |
88 | ||
89 | static irqreturn_t sprd_wdt_isr(int irq, void *dev_id) | |
90 | { | |
91 | struct sprd_wdt *wdt = (struct sprd_wdt *)dev_id; | |
92 | ||
93 | sprd_wdt_unlock(wdt->base); | |
94 | writel_relaxed(SPRD_WDT_INT_CLEAR_BIT, wdt->base + SPRD_WDT_INT_CLR); | |
95 | sprd_wdt_lock(wdt->base); | |
96 | watchdog_notify_pretimeout(&wdt->wdd); | |
97 | return IRQ_HANDLED; | |
98 | } | |
99 | ||
100 | static u32 sprd_wdt_get_cnt_value(struct sprd_wdt *wdt) | |
101 | { | |
102 | u32 val; | |
103 | ||
104 | val = readl_relaxed(wdt->base + SPRD_WDT_CNT_HIGH) << | |
105 | SPRD_WDT_CNT_HIGH_SHIFT; | |
106 | val |= readl_relaxed(wdt->base + SPRD_WDT_CNT_LOW) & | |
107 | SPRD_WDT_LOW_VALUE_MASK; | |
108 | ||
109 | return val; | |
110 | } | |
111 | ||
112 | static int sprd_wdt_load_value(struct sprd_wdt *wdt, u32 timeout, | |
113 | u32 pretimeout) | |
114 | { | |
115 | u32 val, delay_cnt = 0; | |
116 | u32 tmr_step = timeout * SPRD_WDT_CNT_STEP; | |
117 | u32 prtmr_step = pretimeout * SPRD_WDT_CNT_STEP; | |
118 | ||
119 | sprd_wdt_unlock(wdt->base); | |
120 | writel_relaxed((tmr_step >> SPRD_WDT_CNT_HIGH_SHIFT) & | |
121 | SPRD_WDT_LOW_VALUE_MASK, wdt->base + SPRD_WDT_LOAD_HIGH); | |
122 | writel_relaxed((tmr_step & SPRD_WDT_LOW_VALUE_MASK), | |
123 | wdt->base + SPRD_WDT_LOAD_LOW); | |
124 | writel_relaxed((prtmr_step >> SPRD_WDT_CNT_HIGH_SHIFT) & | |
125 | SPRD_WDT_LOW_VALUE_MASK, | |
126 | wdt->base + SPRD_WDT_IRQ_LOAD_HIGH); | |
127 | writel_relaxed(prtmr_step & SPRD_WDT_LOW_VALUE_MASK, | |
128 | wdt->base + SPRD_WDT_IRQ_LOAD_LOW); | |
129 | sprd_wdt_lock(wdt->base); | |
130 | ||
131 | /* | |
132 | * Waiting the load value operation done, | |
133 | * it needs two or three RTC clock cycles. | |
134 | */ | |
135 | do { | |
136 | val = readl_relaxed(wdt->base + SPRD_WDT_INT_RAW); | |
137 | if (!(val & SPRD_WDT_LD_BUSY_BIT)) | |
138 | break; | |
139 | ||
140 | cpu_relax(); | |
141 | } while (delay_cnt++ < SPRD_WDT_LOAD_TIMEOUT); | |
142 | ||
143 | if (delay_cnt >= SPRD_WDT_LOAD_TIMEOUT) | |
144 | return -EBUSY; | |
145 | return 0; | |
146 | } | |
147 | ||
148 | static int sprd_wdt_enable(struct sprd_wdt *wdt) | |
149 | { | |
150 | u32 val; | |
151 | int ret; | |
152 | ||
153 | ret = clk_prepare_enable(wdt->enable); | |
154 | if (ret) | |
155 | return ret; | |
156 | ret = clk_prepare_enable(wdt->rtc_enable); | |
3c578cd4 AK |
157 | if (ret) { |
158 | clk_disable_unprepare(wdt->enable); | |
47760346 | 159 | return ret; |
3c578cd4 | 160 | } |
47760346 EL |
161 | |
162 | sprd_wdt_unlock(wdt->base); | |
163 | val = readl_relaxed(wdt->base + SPRD_WDT_CTRL); | |
164 | val |= SPRD_WDT_NEW_VER_EN; | |
165 | writel_relaxed(val, wdt->base + SPRD_WDT_CTRL); | |
166 | sprd_wdt_lock(wdt->base); | |
167 | return 0; | |
168 | } | |
169 | ||
170 | static void sprd_wdt_disable(void *_data) | |
171 | { | |
172 | struct sprd_wdt *wdt = _data; | |
173 | ||
174 | sprd_wdt_unlock(wdt->base); | |
175 | writel_relaxed(0x0, wdt->base + SPRD_WDT_CTRL); | |
176 | sprd_wdt_lock(wdt->base); | |
177 | ||
178 | clk_disable_unprepare(wdt->rtc_enable); | |
179 | clk_disable_unprepare(wdt->enable); | |
180 | } | |
181 | ||
182 | static int sprd_wdt_start(struct watchdog_device *wdd) | |
183 | { | |
184 | struct sprd_wdt *wdt = to_sprd_wdt(wdd); | |
185 | u32 val; | |
186 | int ret; | |
187 | ||
188 | ret = sprd_wdt_load_value(wdt, wdd->timeout, wdd->pretimeout); | |
189 | if (ret) | |
190 | return ret; | |
191 | ||
192 | sprd_wdt_unlock(wdt->base); | |
193 | val = readl_relaxed(wdt->base + SPRD_WDT_CTRL); | |
194 | val |= SPRD_WDT_CNT_EN_BIT | SPRD_WDT_INT_EN_BIT | SPRD_WDT_RST_EN_BIT; | |
195 | writel_relaxed(val, wdt->base + SPRD_WDT_CTRL); | |
196 | sprd_wdt_lock(wdt->base); | |
197 | set_bit(WDOG_HW_RUNNING, &wdd->status); | |
198 | ||
199 | return 0; | |
200 | } | |
201 | ||
202 | static int sprd_wdt_stop(struct watchdog_device *wdd) | |
203 | { | |
204 | struct sprd_wdt *wdt = to_sprd_wdt(wdd); | |
205 | u32 val; | |
206 | ||
207 | sprd_wdt_unlock(wdt->base); | |
208 | val = readl_relaxed(wdt->base + SPRD_WDT_CTRL); | |
209 | val &= ~(SPRD_WDT_CNT_EN_BIT | SPRD_WDT_RST_EN_BIT | | |
210 | SPRD_WDT_INT_EN_BIT); | |
211 | writel_relaxed(val, wdt->base + SPRD_WDT_CTRL); | |
212 | sprd_wdt_lock(wdt->base); | |
213 | return 0; | |
214 | } | |
215 | ||
216 | static int sprd_wdt_set_timeout(struct watchdog_device *wdd, | |
217 | u32 timeout) | |
218 | { | |
219 | struct sprd_wdt *wdt = to_sprd_wdt(wdd); | |
220 | ||
221 | if (timeout == wdd->timeout) | |
222 | return 0; | |
223 | ||
224 | wdd->timeout = timeout; | |
225 | ||
226 | return sprd_wdt_load_value(wdt, timeout, wdd->pretimeout); | |
227 | } | |
228 | ||
229 | static int sprd_wdt_set_pretimeout(struct watchdog_device *wdd, | |
230 | u32 new_pretimeout) | |
231 | { | |
232 | struct sprd_wdt *wdt = to_sprd_wdt(wdd); | |
233 | ||
234 | if (new_pretimeout < wdd->min_timeout) | |
235 | return -EINVAL; | |
236 | ||
237 | wdd->pretimeout = new_pretimeout; | |
238 | ||
239 | return sprd_wdt_load_value(wdt, wdd->timeout, new_pretimeout); | |
240 | } | |
241 | ||
242 | static u32 sprd_wdt_get_timeleft(struct watchdog_device *wdd) | |
243 | { | |
244 | struct sprd_wdt *wdt = to_sprd_wdt(wdd); | |
245 | u32 val; | |
246 | ||
247 | val = sprd_wdt_get_cnt_value(wdt); | |
248 | val = val / SPRD_WDT_CNT_STEP; | |
249 | ||
250 | return val; | |
251 | } | |
252 | ||
253 | static const struct watchdog_ops sprd_wdt_ops = { | |
254 | .owner = THIS_MODULE, | |
255 | .start = sprd_wdt_start, | |
256 | .stop = sprd_wdt_stop, | |
257 | .set_timeout = sprd_wdt_set_timeout, | |
258 | .set_pretimeout = sprd_wdt_set_pretimeout, | |
259 | .get_timeleft = sprd_wdt_get_timeleft, | |
260 | }; | |
261 | ||
262 | static const struct watchdog_info sprd_wdt_info = { | |
263 | .options = WDIOF_SETTIMEOUT | | |
264 | WDIOF_PRETIMEOUT | | |
265 | WDIOF_MAGICCLOSE | | |
266 | WDIOF_KEEPALIVEPING, | |
267 | .identity = "Spreadtrum Watchdog Timer", | |
268 | }; | |
269 | ||
270 | static int sprd_wdt_probe(struct platform_device *pdev) | |
271 | { | |
272 | struct resource *wdt_res; | |
273 | struct sprd_wdt *wdt; | |
274 | int ret; | |
275 | ||
276 | wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); | |
277 | if (!wdt) | |
278 | return -ENOMEM; | |
279 | ||
280 | wdt_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
281 | wdt->base = devm_ioremap_resource(&pdev->dev, wdt_res); | |
28e65edd | 282 | if (IS_ERR(wdt->base)) |
47760346 | 283 | return PTR_ERR(wdt->base); |
47760346 EL |
284 | |
285 | wdt->enable = devm_clk_get(&pdev->dev, "enable"); | |
286 | if (IS_ERR(wdt->enable)) { | |
287 | dev_err(&pdev->dev, "can't get the enable clock\n"); | |
288 | return PTR_ERR(wdt->enable); | |
289 | } | |
290 | ||
291 | wdt->rtc_enable = devm_clk_get(&pdev->dev, "rtc_enable"); | |
292 | if (IS_ERR(wdt->rtc_enable)) { | |
293 | dev_err(&pdev->dev, "can't get the rtc enable clock\n"); | |
294 | return PTR_ERR(wdt->rtc_enable); | |
295 | } | |
296 | ||
297 | wdt->irq = platform_get_irq(pdev, 0); | |
298 | if (wdt->irq < 0) { | |
299 | dev_err(&pdev->dev, "failed to get IRQ resource\n"); | |
300 | return wdt->irq; | |
301 | } | |
302 | ||
303 | ret = devm_request_irq(&pdev->dev, wdt->irq, sprd_wdt_isr, | |
304 | IRQF_NO_SUSPEND, "sprd-wdt", (void *)wdt); | |
305 | if (ret) { | |
306 | dev_err(&pdev->dev, "failed to register irq\n"); | |
307 | return ret; | |
308 | } | |
309 | ||
310 | wdt->wdd.info = &sprd_wdt_info; | |
311 | wdt->wdd.ops = &sprd_wdt_ops; | |
312 | wdt->wdd.parent = &pdev->dev; | |
313 | wdt->wdd.min_timeout = SPRD_WDT_MIN_TIMEOUT; | |
314 | wdt->wdd.max_timeout = SPRD_WDT_MAX_TIMEOUT; | |
315 | wdt->wdd.timeout = SPRD_WDT_MAX_TIMEOUT; | |
316 | ||
317 | ret = sprd_wdt_enable(wdt); | |
318 | if (ret) { | |
319 | dev_err(&pdev->dev, "failed to enable wdt\n"); | |
320 | return ret; | |
321 | } | |
322 | ret = devm_add_action(&pdev->dev, sprd_wdt_disable, wdt); | |
323 | if (ret) { | |
324 | sprd_wdt_disable(wdt); | |
325 | dev_err(&pdev->dev, "Failed to add wdt disable action\n"); | |
326 | return ret; | |
327 | } | |
328 | ||
329 | watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT); | |
330 | watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev); | |
331 | ||
332 | ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdd); | |
333 | if (ret) { | |
334 | sprd_wdt_disable(wdt); | |
335 | dev_err(&pdev->dev, "failed to register watchdog\n"); | |
336 | return ret; | |
337 | } | |
338 | platform_set_drvdata(pdev, wdt); | |
339 | ||
340 | return 0; | |
341 | } | |
342 | ||
343 | static int __maybe_unused sprd_wdt_pm_suspend(struct device *dev) | |
344 | { | |
345 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
346 | struct sprd_wdt *wdt = dev_get_drvdata(dev); | |
347 | ||
348 | if (watchdog_active(wdd)) | |
349 | sprd_wdt_stop(&wdt->wdd); | |
350 | sprd_wdt_disable(wdt); | |
351 | ||
352 | return 0; | |
353 | } | |
354 | ||
355 | static int __maybe_unused sprd_wdt_pm_resume(struct device *dev) | |
356 | { | |
357 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
358 | struct sprd_wdt *wdt = dev_get_drvdata(dev); | |
359 | int ret; | |
360 | ||
361 | ret = sprd_wdt_enable(wdt); | |
362 | if (ret) | |
363 | return ret; | |
364 | ||
365 | if (watchdog_active(wdd)) { | |
366 | ret = sprd_wdt_start(&wdt->wdd); | |
367 | if (ret) { | |
368 | sprd_wdt_disable(wdt); | |
369 | return ret; | |
370 | } | |
371 | } | |
372 | ||
373 | return 0; | |
374 | } | |
375 | ||
376 | static const struct dev_pm_ops sprd_wdt_pm_ops = { | |
377 | SET_SYSTEM_SLEEP_PM_OPS(sprd_wdt_pm_suspend, | |
378 | sprd_wdt_pm_resume) | |
379 | }; | |
380 | ||
381 | static const struct of_device_id sprd_wdt_match_table[] = { | |
382 | { .compatible = "sprd,sp9860-wdt", }, | |
383 | {}, | |
384 | }; | |
385 | MODULE_DEVICE_TABLE(of, sprd_wdt_match_table); | |
386 | ||
387 | static struct platform_driver sprd_watchdog_driver = { | |
388 | .probe = sprd_wdt_probe, | |
389 | .driver = { | |
390 | .name = "sprd-wdt", | |
391 | .of_match_table = sprd_wdt_match_table, | |
392 | .pm = &sprd_wdt_pm_ops, | |
393 | }, | |
394 | }; | |
395 | module_platform_driver(sprd_watchdog_driver); | |
396 | ||
397 | MODULE_AUTHOR("Eric Long <eric.long@spreadtrum.com>"); | |
398 | MODULE_DESCRIPTION("Spreadtrum Watchdog Timer Controller Driver"); | |
399 | MODULE_LICENSE("GPL v2"); |