1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright © 2014-2023 Broadcom |
4 | */ |
5 | |
6 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/device.h> |
10 | #include <linux/err.h> |
11 | #include <linux/init.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/io.h> |
14 | #include <linux/irqreturn.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/pm.h> |
20 | #include <linux/pm_wakeup.h> |
21 | #include <linux/reboot.h> |
22 | #include <linux/rtc.h> |
23 | #include <linux/stat.h> |
24 | #include <linux/suspend.h> |
25 | |
26 | struct brcmstb_waketmr { |
27 | struct rtc_device *rtc; |
28 | struct device *dev; |
29 | void __iomem *base; |
30 | unsigned int wake_irq; |
31 | unsigned int alarm_irq; |
32 | struct notifier_block reboot_notifier; |
33 | struct clk *clk; |
34 | u32 rate; |
35 | unsigned long rtc_alarm; |
36 | bool alarm_en; |
37 | bool alarm_expired; |
38 | }; |
39 | |
40 | #define BRCMSTB_WKTMR_EVENT 0x00 |
41 | #define WKTMR_ALARM_EVENT BIT(0) |
42 | #define BRCMSTB_WKTMR_COUNTER 0x04 |
43 | #define BRCMSTB_WKTMR_ALARM 0x08 |
44 | #define BRCMSTB_WKTMR_PRESCALER 0x0C |
45 | #define BRCMSTB_WKTMR_PRESCALER_VAL 0x10 |
46 | |
47 | #define BRCMSTB_WKTMR_DEFAULT_FREQ 27000000 |
48 | |
49 | static inline bool brcmstb_waketmr_is_pending(struct brcmstb_waketmr *timer) |
50 | { |
51 | u32 reg; |
52 | |
53 | reg = readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT); |
54 | return !!(reg & WKTMR_ALARM_EVENT); |
55 | } |
56 | |
57 | static inline void brcmstb_waketmr_clear_alarm(struct brcmstb_waketmr *timer) |
58 | { |
59 | u32 reg; |
60 | |
61 | if (timer->alarm_en && timer->alarm_irq) |
62 | disable_irq(irq: timer->alarm_irq); |
63 | timer->alarm_en = false; |
64 | reg = readl_relaxed(timer->base + BRCMSTB_WKTMR_COUNTER); |
65 | writel_relaxed(reg - 1, timer->base + BRCMSTB_WKTMR_ALARM); |
66 | writel_relaxed(WKTMR_ALARM_EVENT, timer->base + BRCMSTB_WKTMR_EVENT); |
67 | (void)readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT); |
68 | if (timer->alarm_expired) { |
69 | timer->alarm_expired = false; |
70 | /* maintain call balance */ |
71 | enable_irq(irq: timer->alarm_irq); |
72 | } |
73 | } |
74 | |
75 | static void brcmstb_waketmr_set_alarm(struct brcmstb_waketmr *timer, |
76 | unsigned int secs) |
77 | { |
78 | unsigned int now; |
79 | |
80 | brcmstb_waketmr_clear_alarm(timer); |
81 | |
82 | /* Make sure we are actually counting in seconds */ |
83 | writel_relaxed(timer->rate, timer->base + BRCMSTB_WKTMR_PRESCALER); |
84 | |
85 | writel_relaxed(secs, timer->base + BRCMSTB_WKTMR_ALARM); |
86 | now = readl_relaxed(timer->base + BRCMSTB_WKTMR_COUNTER); |
87 | |
88 | while ((int)(secs - now) <= 0 && |
89 | !brcmstb_waketmr_is_pending(timer)) { |
90 | secs = now + 1; |
91 | writel_relaxed(secs, timer->base + BRCMSTB_WKTMR_ALARM); |
92 | now = readl_relaxed(timer->base + BRCMSTB_WKTMR_COUNTER); |
93 | } |
94 | } |
95 | |
96 | static irqreturn_t brcmstb_waketmr_irq(int irq, void *data) |
97 | { |
98 | struct brcmstb_waketmr *timer = data; |
99 | |
100 | if (!timer->alarm_irq) |
101 | pm_wakeup_event(dev: timer->dev, msec: 0); |
102 | return IRQ_HANDLED; |
103 | } |
104 | |
105 | static irqreturn_t brcmstb_alarm_irq(int irq, void *data) |
106 | { |
107 | struct brcmstb_waketmr *timer = data; |
108 | |
109 | /* Ignore spurious interrupts */ |
110 | if (!brcmstb_waketmr_is_pending(timer)) |
111 | return IRQ_HANDLED; |
112 | |
113 | if (timer->alarm_en) { |
114 | if (device_may_wakeup(dev: timer->dev)) { |
115 | disable_irq_nosync(irq); |
116 | timer->alarm_expired = true; |
117 | } else { |
118 | writel_relaxed(WKTMR_ALARM_EVENT, |
119 | timer->base + BRCMSTB_WKTMR_EVENT); |
120 | } |
121 | rtc_update_irq(rtc: timer->rtc, num: 1, RTC_IRQF | RTC_AF); |
122 | } else { |
123 | writel_relaxed(WKTMR_ALARM_EVENT, |
124 | timer->base + BRCMSTB_WKTMR_EVENT); |
125 | } |
126 | |
127 | return IRQ_HANDLED; |
128 | } |
129 | |
130 | struct wktmr_time { |
131 | u32 sec; |
132 | u32 pre; |
133 | }; |
134 | |
135 | static void wktmr_read(struct brcmstb_waketmr *timer, |
136 | struct wktmr_time *t) |
137 | { |
138 | u32 tmp; |
139 | |
140 | do { |
141 | t->sec = readl_relaxed(timer->base + BRCMSTB_WKTMR_COUNTER); |
142 | tmp = readl_relaxed(timer->base + BRCMSTB_WKTMR_PRESCALER_VAL); |
143 | } while (tmp >= timer->rate); |
144 | |
145 | t->pre = timer->rate - tmp; |
146 | } |
147 | |
148 | static int brcmstb_waketmr_prepare_suspend(struct brcmstb_waketmr *timer) |
149 | { |
150 | struct device *dev = timer->dev; |
151 | int ret; |
152 | |
153 | if (device_may_wakeup(dev)) { |
154 | ret = enable_irq_wake(irq: timer->wake_irq); |
155 | if (ret) { |
156 | dev_err(dev, "failed to enable wake-up interrupt\n" ); |
157 | return ret; |
158 | } |
159 | if (timer->alarm_en && timer->alarm_irq) { |
160 | ret = enable_irq_wake(irq: timer->alarm_irq); |
161 | if (ret) { |
162 | dev_err(dev, "failed to enable rtc interrupt\n" ); |
163 | disable_irq_wake(irq: timer->wake_irq); |
164 | return ret; |
165 | } |
166 | } |
167 | } |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | /* If enabled as a wakeup-source, arm the timer when powering off */ |
173 | static int brcmstb_waketmr_reboot(struct notifier_block *nb, |
174 | unsigned long action, void *data) |
175 | { |
176 | struct brcmstb_waketmr *timer; |
177 | |
178 | timer = container_of(nb, struct brcmstb_waketmr, reboot_notifier); |
179 | |
180 | /* Set timer for cold boot */ |
181 | if (action == SYS_POWER_OFF) |
182 | brcmstb_waketmr_prepare_suspend(timer); |
183 | |
184 | return NOTIFY_DONE; |
185 | } |
186 | |
187 | static int brcmstb_waketmr_gettime(struct device *dev, |
188 | struct rtc_time *tm) |
189 | { |
190 | struct brcmstb_waketmr *timer = dev_get_drvdata(dev); |
191 | struct wktmr_time now; |
192 | |
193 | wktmr_read(timer, t: &now); |
194 | |
195 | rtc_time64_to_tm(time: now.sec, tm); |
196 | |
197 | return 0; |
198 | } |
199 | |
200 | static int brcmstb_waketmr_settime(struct device *dev, |
201 | struct rtc_time *tm) |
202 | { |
203 | struct brcmstb_waketmr *timer = dev_get_drvdata(dev); |
204 | time64_t sec; |
205 | |
206 | sec = rtc_tm_to_time64(tm); |
207 | |
208 | writel_relaxed(sec, timer->base + BRCMSTB_WKTMR_COUNTER); |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static int brcmstb_waketmr_getalarm(struct device *dev, |
214 | struct rtc_wkalrm *alarm) |
215 | { |
216 | struct brcmstb_waketmr *timer = dev_get_drvdata(dev); |
217 | |
218 | alarm->enabled = timer->alarm_en; |
219 | rtc_time64_to_tm(time: timer->rtc_alarm, tm: &alarm->time); |
220 | |
221 | alarm->pending = brcmstb_waketmr_is_pending(timer); |
222 | |
223 | return 0; |
224 | } |
225 | |
226 | static int brcmstb_waketmr_alarm_enable(struct device *dev, |
227 | unsigned int enabled) |
228 | { |
229 | struct brcmstb_waketmr *timer = dev_get_drvdata(dev); |
230 | |
231 | if (enabled && !timer->alarm_en) { |
232 | if ((int)(readl_relaxed(timer->base + BRCMSTB_WKTMR_COUNTER) - |
233 | readl_relaxed(timer->base + BRCMSTB_WKTMR_ALARM)) >= 0 && |
234 | !brcmstb_waketmr_is_pending(timer)) |
235 | return -EINVAL; |
236 | timer->alarm_en = true; |
237 | if (timer->alarm_irq) { |
238 | if (timer->alarm_expired) { |
239 | timer->alarm_expired = false; |
240 | /* maintain call balance */ |
241 | enable_irq(irq: timer->alarm_irq); |
242 | } |
243 | enable_irq(irq: timer->alarm_irq); |
244 | } |
245 | } else if (!enabled && timer->alarm_en) { |
246 | if (timer->alarm_irq) |
247 | disable_irq(irq: timer->alarm_irq); |
248 | timer->alarm_en = false; |
249 | } |
250 | |
251 | return 0; |
252 | } |
253 | |
254 | static int brcmstb_waketmr_setalarm(struct device *dev, |
255 | struct rtc_wkalrm *alarm) |
256 | { |
257 | struct brcmstb_waketmr *timer = dev_get_drvdata(dev); |
258 | |
259 | timer->rtc_alarm = rtc_tm_to_time64(tm: &alarm->time); |
260 | |
261 | brcmstb_waketmr_set_alarm(timer, secs: timer->rtc_alarm); |
262 | |
263 | return brcmstb_waketmr_alarm_enable(dev, enabled: alarm->enabled); |
264 | } |
265 | |
266 | static const struct rtc_class_ops brcmstb_waketmr_ops = { |
267 | .read_time = brcmstb_waketmr_gettime, |
268 | .set_time = brcmstb_waketmr_settime, |
269 | .read_alarm = brcmstb_waketmr_getalarm, |
270 | .set_alarm = brcmstb_waketmr_setalarm, |
271 | .alarm_irq_enable = brcmstb_waketmr_alarm_enable, |
272 | }; |
273 | |
274 | static int brcmstb_waketmr_probe(struct platform_device *pdev) |
275 | { |
276 | struct device *dev = &pdev->dev; |
277 | struct brcmstb_waketmr *timer; |
278 | int ret; |
279 | |
280 | timer = devm_kzalloc(dev, size: sizeof(*timer), GFP_KERNEL); |
281 | if (!timer) |
282 | return -ENOMEM; |
283 | |
284 | platform_set_drvdata(pdev, data: timer); |
285 | timer->dev = dev; |
286 | |
287 | timer->base = devm_platform_ioremap_resource(pdev, index: 0); |
288 | if (IS_ERR(ptr: timer->base)) |
289 | return PTR_ERR(ptr: timer->base); |
290 | |
291 | timer->rtc = devm_rtc_allocate_device(dev); |
292 | if (IS_ERR(ptr: timer->rtc)) |
293 | return PTR_ERR(ptr: timer->rtc); |
294 | |
295 | /* |
296 | * Set wakeup capability before requesting wakeup interrupt, so we can |
297 | * process boot-time "wakeups" (e.g., from S5 soft-off) |
298 | */ |
299 | device_init_wakeup(dev, enable: true); |
300 | |
301 | ret = platform_get_irq(pdev, 0); |
302 | if (ret < 0) |
303 | return -ENODEV; |
304 | timer->wake_irq = (unsigned int)ret; |
305 | |
306 | timer->clk = devm_clk_get(dev, NULL); |
307 | if (!IS_ERR(ptr: timer->clk)) { |
308 | ret = clk_prepare_enable(clk: timer->clk); |
309 | if (ret) |
310 | return ret; |
311 | timer->rate = clk_get_rate(clk: timer->clk); |
312 | if (!timer->rate) |
313 | timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ; |
314 | } else { |
315 | timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ; |
316 | timer->clk = NULL; |
317 | } |
318 | |
319 | ret = devm_request_irq(dev, irq: timer->wake_irq, handler: brcmstb_waketmr_irq, irqflags: 0, |
320 | devname: "brcmstb-waketimer" , dev_id: timer); |
321 | if (ret < 0) |
322 | goto err_clk; |
323 | |
324 | brcmstb_waketmr_clear_alarm(timer); |
325 | |
326 | /* Attempt to initialize non-wake irq */ |
327 | ret = platform_get_irq(pdev, 1); |
328 | if (ret > 0) { |
329 | timer->alarm_irq = (unsigned int)ret; |
330 | ret = devm_request_irq(dev, irq: timer->alarm_irq, handler: brcmstb_alarm_irq, |
331 | IRQF_NO_AUTOEN, devname: "brcmstb-waketimer-rtc" , |
332 | dev_id: timer); |
333 | if (ret < 0) |
334 | timer->alarm_irq = 0; |
335 | } |
336 | |
337 | timer->reboot_notifier.notifier_call = brcmstb_waketmr_reboot; |
338 | register_reboot_notifier(&timer->reboot_notifier); |
339 | |
340 | timer->rtc->ops = &brcmstb_waketmr_ops; |
341 | timer->rtc->range_max = U32_MAX; |
342 | |
343 | ret = devm_rtc_register_device(timer->rtc); |
344 | if (ret) |
345 | goto err_notifier; |
346 | |
347 | return 0; |
348 | |
349 | err_notifier: |
350 | unregister_reboot_notifier(&timer->reboot_notifier); |
351 | |
352 | err_clk: |
353 | clk_disable_unprepare(clk: timer->clk); |
354 | |
355 | return ret; |
356 | } |
357 | |
358 | static void brcmstb_waketmr_remove(struct platform_device *pdev) |
359 | { |
360 | struct brcmstb_waketmr *timer = dev_get_drvdata(dev: &pdev->dev); |
361 | |
362 | unregister_reboot_notifier(&timer->reboot_notifier); |
363 | clk_disable_unprepare(clk: timer->clk); |
364 | } |
365 | |
366 | #ifdef CONFIG_PM_SLEEP |
367 | static int brcmstb_waketmr_suspend(struct device *dev) |
368 | { |
369 | struct brcmstb_waketmr *timer = dev_get_drvdata(dev); |
370 | |
371 | return brcmstb_waketmr_prepare_suspend(timer); |
372 | } |
373 | |
374 | static int brcmstb_waketmr_suspend_noirq(struct device *dev) |
375 | { |
376 | struct brcmstb_waketmr *timer = dev_get_drvdata(dev); |
377 | |
378 | /* Catch any alarms occurring prior to noirq */ |
379 | if (timer->alarm_expired && device_may_wakeup(dev)) |
380 | return -EBUSY; |
381 | |
382 | return 0; |
383 | } |
384 | |
385 | static int brcmstb_waketmr_resume(struct device *dev) |
386 | { |
387 | struct brcmstb_waketmr *timer = dev_get_drvdata(dev); |
388 | int ret; |
389 | |
390 | if (!device_may_wakeup(dev)) |
391 | return 0; |
392 | |
393 | ret = disable_irq_wake(irq: timer->wake_irq); |
394 | if (timer->alarm_en && timer->alarm_irq) |
395 | disable_irq_wake(irq: timer->alarm_irq); |
396 | |
397 | brcmstb_waketmr_clear_alarm(timer); |
398 | |
399 | return ret; |
400 | } |
401 | #else |
402 | #define brcmstb_waketmr_suspend NULL |
403 | #define brcmstb_waketmr_suspend_noirq NULL |
404 | #define brcmstb_waketmr_resume NULL |
405 | #endif /* CONFIG_PM_SLEEP */ |
406 | |
407 | static const struct dev_pm_ops brcmstb_waketmr_pm_ops = { |
408 | .suspend = brcmstb_waketmr_suspend, |
409 | .suspend_noirq = brcmstb_waketmr_suspend_noirq, |
410 | .resume = brcmstb_waketmr_resume, |
411 | }; |
412 | |
413 | static const __maybe_unused struct of_device_id brcmstb_waketmr_of_match[] = { |
414 | { .compatible = "brcm,brcmstb-waketimer" }, |
415 | { /* sentinel */ }, |
416 | }; |
417 | |
418 | static struct platform_driver brcmstb_waketmr_driver = { |
419 | .probe = brcmstb_waketmr_probe, |
420 | .remove_new = brcmstb_waketmr_remove, |
421 | .driver = { |
422 | .name = "brcmstb-waketimer" , |
423 | .pm = &brcmstb_waketmr_pm_ops, |
424 | .of_match_table = of_match_ptr(brcmstb_waketmr_of_match), |
425 | } |
426 | }; |
427 | module_platform_driver(brcmstb_waketmr_driver); |
428 | |
429 | MODULE_LICENSE("GPL v2" ); |
430 | MODULE_AUTHOR("Brian Norris" ); |
431 | MODULE_AUTHOR("Markus Mayer" ); |
432 | MODULE_AUTHOR("Doug Berger" ); |
433 | MODULE_DESCRIPTION("Wake-up timer driver for STB chips" ); |
434 | |