Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

rtc: brcmstb-waketimer: support level alarm_irq

Some devices (e.g. BCM72112) use an alarm_irq interrupt that is
connected to a level interrupt controller rather than an edge
interrupt controller. In this case, the interrupt cannot be left
enabled by the irq handler while preserving the hardware wake-up
signal on wake capable devices or an interrupt storm will occur.

The alarm_expired flag is introduced to allow the disabling of
the interrupt when an alarm expires and to support balancing the
calls to disable_irq() and enable_irq() in accordance with the
existing design.

Fixes: 24304a87158a ("rtc: brcmstb-waketimer: allow use as non-wake alarm")
Signed-off-by: Doug Berger <opendmb@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
Link: https://lore.kernel.org/r/20230830224747.1663044-1-opendmb@gmail.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>

authored by

Doug Berger and committed by
Alexandre Belloni
e005a9b3 d4277fa4

+42 -5
+42 -5
drivers/rtc/rtc-brcmstb-waketimer.c
··· 1 1 // SPDX-License-Identifier: GPL-2.0-only 2 2 /* 3 - * Copyright © 2014-2017 Broadcom 3 + * Copyright © 2014-2023 Broadcom 4 4 */ 5 5 6 6 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ··· 34 34 u32 rate; 35 35 unsigned long rtc_alarm; 36 36 bool alarm_en; 37 + bool alarm_expired; 37 38 }; 38 39 39 40 #define BRCMSTB_WKTMR_EVENT 0x00 ··· 65 64 writel_relaxed(reg - 1, timer->base + BRCMSTB_WKTMR_ALARM); 66 65 writel_relaxed(WKTMR_ALARM_EVENT, timer->base + BRCMSTB_WKTMR_EVENT); 67 66 (void)readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT); 67 + if (timer->alarm_expired) { 68 + timer->alarm_expired = false; 69 + /* maintain call balance */ 70 + enable_irq(timer->alarm_irq); 71 + } 68 72 } 69 73 70 74 static void brcmstb_waketmr_set_alarm(struct brcmstb_waketmr *timer, ··· 111 105 return IRQ_HANDLED; 112 106 113 107 if (timer->alarm_en) { 114 - if (!device_may_wakeup(timer->dev)) 108 + if (device_may_wakeup(timer->dev)) { 109 + disable_irq_nosync(irq); 110 + timer->alarm_expired = true; 111 + } else { 115 112 writel_relaxed(WKTMR_ALARM_EVENT, 116 113 timer->base + BRCMSTB_WKTMR_EVENT); 114 + } 117 115 rtc_update_irq(timer->rtc, 1, RTC_IRQF | RTC_AF); 116 + } else { 117 + writel_relaxed(WKTMR_ALARM_EVENT, 118 + timer->base + BRCMSTB_WKTMR_EVENT); 118 119 } 119 120 120 121 return IRQ_HANDLED; ··· 234 221 !brcmstb_waketmr_is_pending(timer)) 235 222 return -EINVAL; 236 223 timer->alarm_en = true; 237 - if (timer->alarm_irq) 224 + if (timer->alarm_irq) { 225 + if (timer->alarm_expired) { 226 + timer->alarm_expired = false; 227 + /* maintain call balance */ 228 + enable_irq(timer->alarm_irq); 229 + } 238 230 enable_irq(timer->alarm_irq); 231 + } 239 232 } else if (!enabled && timer->alarm_en) { 240 233 if (timer->alarm_irq) 241 234 disable_irq(timer->alarm_irq); ··· 371 352 return brcmstb_waketmr_prepare_suspend(timer); 372 353 } 373 354 355 + static int brcmstb_waketmr_suspend_noirq(struct device *dev) 356 + { 357 + struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 358 + 359 + /* Catch any alarms occurring prior to noirq */ 360 + if (timer->alarm_expired && device_may_wakeup(dev)) 361 + return -EBUSY; 362 + 363 + return 0; 364 + } 365 + 374 366 static int brcmstb_waketmr_resume(struct device *dev) 375 367 { 376 368 struct brcmstb_waketmr *timer = dev_get_drvdata(dev); ··· 398 368 399 369 return ret; 400 370 } 371 + #else 372 + #define brcmstb_waketmr_suspend NULL 373 + #define brcmstb_waketmr_suspend_noirq NULL 374 + #define brcmstb_waketmr_resume NULL 401 375 #endif /* CONFIG_PM_SLEEP */ 402 376 403 - static SIMPLE_DEV_PM_OPS(brcmstb_waketmr_pm_ops, 404 - brcmstb_waketmr_suspend, brcmstb_waketmr_resume); 377 + static const struct dev_pm_ops brcmstb_waketmr_pm_ops = { 378 + .suspend = brcmstb_waketmr_suspend, 379 + .suspend_noirq = brcmstb_waketmr_suspend_noirq, 380 + .resume = brcmstb_waketmr_resume, 381 + }; 405 382 406 383 static const __maybe_unused struct of_device_id brcmstb_waketmr_of_match[] = { 407 384 { .compatible = "brcm,brcmstb-waketimer" },