Commit 603b1a23 authored by Boris BREZILLON's avatar Boris BREZILLON Committed by Rafael J. Wysocki

rtc: at91sam9: rework wakeup and interrupt handling

The IRQ line used by the RTC device is usually shared with the system timer
(PIT) on at91 platforms.

Since timers are registering their handlers with IRQF_NO_SUSPEND, we should
expect being called in suspended state, and properly wake the system up
when this is the case.

Set IRQF_COND_SUSPEND flag when registering the IRQ handler to inform
irq core that it can safely be called while the system is suspended.
Signed-off-by: default avatarBoris Brezillon <boris.brezillon@free-electrons.com>
Reviewed-by: default avatarAlexandre Belloni <alexandre.belloni@free-electrons.com>
Acked-by: default avatarNicolas Ferre <nicolas.ferre@atmel.com>
Acked-by: default avatarMark Rutland <mark.rutland@arm.com>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent 432ec92b
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <linux/io.h> #include <linux/io.h>
#include <linux/mfd/syscon.h> #include <linux/mfd/syscon.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/suspend.h>
#include <linux/clk.h> #include <linux/clk.h>
/* /*
...@@ -77,6 +78,9 @@ struct sam9_rtc { ...@@ -77,6 +78,9 @@ struct sam9_rtc {
unsigned int gpbr_offset; unsigned int gpbr_offset;
int irq; int irq;
struct clk *sclk; struct clk *sclk;
bool suspended;
unsigned long events;
spinlock_t lock;
}; };
#define rtt_readl(rtc, field) \ #define rtt_readl(rtc, field) \
...@@ -271,14 +275,9 @@ static int at91_rtc_proc(struct device *dev, struct seq_file *seq) ...@@ -271,14 +275,9 @@ static int at91_rtc_proc(struct device *dev, struct seq_file *seq)
return 0; return 0;
} }
/* static irqreturn_t at91_rtc_cache_events(struct sam9_rtc *rtc)
* IRQ handler for the RTC
*/
static irqreturn_t at91_rtc_interrupt(int irq, void *_rtc)
{ {
struct sam9_rtc *rtc = _rtc;
u32 sr, mr; u32 sr, mr;
unsigned long events = 0;
/* Shared interrupt may be for another device. Note: reading /* Shared interrupt may be for another device. Note: reading
* SR clears it, so we must only read it in this irq handler! * SR clears it, so we must only read it in this irq handler!
...@@ -290,18 +289,54 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *_rtc) ...@@ -290,18 +289,54 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *_rtc)
/* alarm status */ /* alarm status */
if (sr & AT91_RTT_ALMS) if (sr & AT91_RTT_ALMS)
events |= (RTC_AF | RTC_IRQF); rtc->events |= (RTC_AF | RTC_IRQF);
/* timer update/increment */ /* timer update/increment */
if (sr & AT91_RTT_RTTINC) if (sr & AT91_RTT_RTTINC)
events |= (RTC_UF | RTC_IRQF); rtc->events |= (RTC_UF | RTC_IRQF);
return IRQ_HANDLED;
}
static void at91_rtc_flush_events(struct sam9_rtc *rtc)
{
if (!rtc->events)
return;
rtc_update_irq(rtc->rtcdev, 1, events); rtc_update_irq(rtc->rtcdev, 1, rtc->events);
rtc->events = 0;
pr_debug("%s: num=%ld, events=0x%02lx\n", __func__, pr_debug("%s: num=%ld, events=0x%02lx\n", __func__,
events >> 8, events & 0x000000FF); rtc->events >> 8, rtc->events & 0x000000FF);
}
return IRQ_HANDLED; /*
* IRQ handler for the RTC
*/
static irqreturn_t at91_rtc_interrupt(int irq, void *_rtc)
{
struct sam9_rtc *rtc = _rtc;
int ret;
spin_lock(&rtc->lock);
ret = at91_rtc_cache_events(rtc);
/* We're called in suspended state */
if (rtc->suspended) {
/* Mask irqs coming from this peripheral */
rtt_writel(rtc, MR,
rtt_readl(rtc, MR) &
~(AT91_RTT_ALMIEN | AT91_RTT_RTTINCIEN));
/* Trigger a system wakeup */
pm_system_wakeup();
} else {
at91_rtc_flush_events(rtc);
}
spin_unlock(&rtc->lock);
return ret;
} }
static const struct rtc_class_ops at91_rtc_ops = { static const struct rtc_class_ops at91_rtc_ops = {
...@@ -421,7 +456,8 @@ static int at91_rtc_probe(struct platform_device *pdev) ...@@ -421,7 +456,8 @@ static int at91_rtc_probe(struct platform_device *pdev)
/* register irq handler after we know what name we'll use */ /* register irq handler after we know what name we'll use */
ret = devm_request_irq(&pdev->dev, rtc->irq, at91_rtc_interrupt, ret = devm_request_irq(&pdev->dev, rtc->irq, at91_rtc_interrupt,
IRQF_SHARED, dev_name(&rtc->rtcdev->dev), rtc); IRQF_SHARED | IRQF_COND_SUSPEND,
dev_name(&rtc->rtcdev->dev), rtc);
if (ret) { if (ret) {
dev_dbg(&pdev->dev, "can't share IRQ %d?\n", rtc->irq); dev_dbg(&pdev->dev, "can't share IRQ %d?\n", rtc->irq);
return ret; return ret;
...@@ -482,7 +518,12 @@ static int at91_rtc_suspend(struct device *dev) ...@@ -482,7 +518,12 @@ static int at91_rtc_suspend(struct device *dev)
rtc->imr = mr & (AT91_RTT_ALMIEN | AT91_RTT_RTTINCIEN); rtc->imr = mr & (AT91_RTT_ALMIEN | AT91_RTT_RTTINCIEN);
if (rtc->imr) { if (rtc->imr) {
if (device_may_wakeup(dev) && (mr & AT91_RTT_ALMIEN)) { if (device_may_wakeup(dev) && (mr & AT91_RTT_ALMIEN)) {
unsigned long flags;
enable_irq_wake(rtc->irq); enable_irq_wake(rtc->irq);
spin_lock_irqsave(&rtc->lock, flags);
rtc->suspended = true;
spin_unlock_irqrestore(&rtc->lock, flags);
/* don't let RTTINC cause wakeups */ /* don't let RTTINC cause wakeups */
if (mr & AT91_RTT_RTTINCIEN) if (mr & AT91_RTT_RTTINCIEN)
rtt_writel(rtc, MR, mr & ~AT91_RTT_RTTINCIEN); rtt_writel(rtc, MR, mr & ~AT91_RTT_RTTINCIEN);
...@@ -499,10 +540,18 @@ static int at91_rtc_resume(struct device *dev) ...@@ -499,10 +540,18 @@ static int at91_rtc_resume(struct device *dev)
u32 mr; u32 mr;
if (rtc->imr) { if (rtc->imr) {
unsigned long flags;
if (device_may_wakeup(dev)) if (device_may_wakeup(dev))
disable_irq_wake(rtc->irq); disable_irq_wake(rtc->irq);
mr = rtt_readl(rtc, MR); mr = rtt_readl(rtc, MR);
rtt_writel(rtc, MR, mr | rtc->imr); rtt_writel(rtc, MR, mr | rtc->imr);
spin_lock_irqsave(&rtc->lock, flags);
rtc->suspended = false;
at91_rtc_cache_events(rtc);
at91_rtc_flush_events(rtc);
spin_unlock_irqrestore(&rtc->lock, flags);
} }
return 0; return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment