1
0
Fork 0

Merge branch 'irq-pm'

* irq-pm:
  genirq / PM: describe IRQF_COND_SUSPEND
  tty: serial: atmel: rework interrupt and wakeup handling
  watchdog: at91sam9: request the irq with IRQF_NO_SUSPEND
  clk: at91: implement suspend/resume for the PMC irqchip
  rtc: at91rm9200: rework wakeup and interrupt handling
  rtc: at91sam9: rework wakeup and interrupt handling
  PM / wakeup: export pm_system_wakeup symbol
  genirq / PM: Add flag for shared NO_SUSPEND interrupt lines
  genirq / PM: better describe IRQF_NO_SUSPEND semantics
wifi-calibration
Rafael J. Wysocki 2015-03-06 01:29:05 +01:00
commit 79d223646b
12 changed files with 217 additions and 42 deletions

View File

@ -40,8 +40,10 @@ but also to IPIs and to some other special-purpose interrupts.
The IRQF_NO_SUSPEND flag is used to indicate that to the IRQ subsystem when
requesting a special-purpose interrupt. It causes suspend_device_irqs() to
leave the corresponding IRQ enabled so as to allow the interrupt to work all
the time as expected.
leave the corresponding IRQ enabled so as to allow the interrupt to work as
expected during the suspend-resume cycle, but does not guarantee that the
interrupt will wake the system from a suspended state -- for such cases it is
necessary to use enable_irq_wake().
Note that the IRQF_NO_SUSPEND flag affects the entire IRQ and not just one
user of it. Thus, if the IRQ is shared, all of the interrupt handlers installed
@ -110,8 +112,9 @@ any special interrupt handling logic for it to work.
IRQF_NO_SUSPEND and enable_irq_wake()
-------------------------------------
There are no valid reasons to use both enable_irq_wake() and the IRQF_NO_SUSPEND
flag on the same IRQ.
There are very few valid reasons to use both enable_irq_wake() and the
IRQF_NO_SUSPEND flag on the same IRQ, and it is never valid to use both for the
same device.
First of all, if the IRQ is not shared, the rules for handling IRQF_NO_SUSPEND
interrupts (interrupt handlers are invoked after suspend_device_irqs()) are
@ -120,4 +123,13 @@ handlers are not invoked after suspend_device_irqs()).
Second, both enable_irq_wake() and IRQF_NO_SUSPEND apply to entire IRQs and not
to individual interrupt handlers, so sharing an IRQ between a system wakeup
interrupt source and an IRQF_NO_SUSPEND interrupt source does not make sense.
interrupt source and an IRQF_NO_SUSPEND interrupt source does not generally
make sense.
In rare cases an IRQ can be shared between a wakeup device driver and an
IRQF_NO_SUSPEND user. In order for this to be safe, the wakeup device driver
must be able to discern spurious IRQs from genuine wakeup events (signalling
the latter to the core with pm_system_wakeup()), must use enable_irq_wake() to
ensure that the IRQ will function as a wakeup source, and must request the IRQ
with IRQF_COND_SUSPEND to tell the core that it meets these requirements. If
these requirements are not met, it is not valid to use IRQF_COND_SUSPEND.

View File

@ -730,6 +730,7 @@ void pm_system_wakeup(void)
pm_abort_suspend = true;
freeze_wake();
}
EXPORT_SYMBOL_GPL(pm_system_wakeup);
void pm_wakeup_clear(void)
{

View File

@ -89,12 +89,29 @@ static int pmc_irq_set_type(struct irq_data *d, unsigned type)
return 0;
}
static void pmc_irq_suspend(struct irq_data *d)
{
struct at91_pmc *pmc = irq_data_get_irq_chip_data(d);
pmc->imr = pmc_read(pmc, AT91_PMC_IMR);
pmc_write(pmc, AT91_PMC_IDR, pmc->imr);
}
static void pmc_irq_resume(struct irq_data *d)
{
struct at91_pmc *pmc = irq_data_get_irq_chip_data(d);
pmc_write(pmc, AT91_PMC_IER, pmc->imr);
}
static struct irq_chip pmc_irq = {
.name = "PMC",
.irq_disable = pmc_irq_mask,
.irq_mask = pmc_irq_mask,
.irq_unmask = pmc_irq_unmask,
.irq_set_type = pmc_irq_set_type,
.irq_suspend = pmc_irq_suspend,
.irq_resume = pmc_irq_resume,
};
static struct lock_class_key pmc_lock_class;
@ -224,7 +241,8 @@ static struct at91_pmc *__init at91_pmc_init(struct device_node *np,
goto out_free_pmc;
pmc_write(pmc, AT91_PMC_IDR, 0xffffffff);
if (request_irq(pmc->virq, pmc_irq_handler, IRQF_SHARED, "pmc", pmc))
if (request_irq(pmc->virq, pmc_irq_handler,
IRQF_SHARED | IRQF_COND_SUSPEND, "pmc", pmc))
goto out_remove_irqdomain;
return pmc;

View File

@ -33,6 +33,7 @@ struct at91_pmc {
spinlock_t lock;
const struct at91_pmc_caps *caps;
struct irq_domain *irqdomain;
u32 imr;
};
static inline void pmc_lock(struct at91_pmc *pmc)

View File

@ -31,6 +31,7 @@
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/suspend.h>
#include <linux/uaccess.h>
#include "rtc-at91rm9200.h"
@ -54,6 +55,10 @@ static void __iomem *at91_rtc_regs;
static int irq;
static DEFINE_SPINLOCK(at91_rtc_lock);
static u32 at91_rtc_shadow_imr;
static bool suspended;
static DEFINE_SPINLOCK(suspended_lock);
static unsigned long cached_events;
static u32 at91_rtc_imr;
static void at91_rtc_write_ier(u32 mask)
{
@ -290,7 +295,9 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)
struct rtc_device *rtc = platform_get_drvdata(pdev);
unsigned int rtsr;
unsigned long events = 0;
int ret = IRQ_NONE;
spin_lock(&suspended_lock);
rtsr = at91_rtc_read(AT91_RTC_SR) & at91_rtc_read_imr();
if (rtsr) { /* this interrupt is shared! Is it ours? */
if (rtsr & AT91_RTC_ALARM)
@ -304,14 +311,22 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)
at91_rtc_write(AT91_RTC_SCCR, rtsr); /* clear status reg */
rtc_update_irq(rtc, 1, events);
if (!suspended) {
rtc_update_irq(rtc, 1, events);
dev_dbg(&pdev->dev, "%s(): num=%ld, events=0x%02lx\n", __func__,
events >> 8, events & 0x000000FF);
dev_dbg(&pdev->dev, "%s(): num=%ld, events=0x%02lx\n",
__func__, events >> 8, events & 0x000000FF);
} else {
cached_events |= events;
at91_rtc_write_idr(at91_rtc_imr);
pm_system_wakeup();
}
return IRQ_HANDLED;
ret = IRQ_HANDLED;
}
return IRQ_NONE; /* not handled */
spin_lock(&suspended_lock);
return ret;
}
static const struct at91_rtc_config at91rm9200_config = {
@ -401,8 +416,8 @@ static int __init at91_rtc_probe(struct platform_device *pdev)
AT91_RTC_CALEV);
ret = devm_request_irq(&pdev->dev, irq, at91_rtc_interrupt,
IRQF_SHARED,
"at91_rtc", pdev);
IRQF_SHARED | IRQF_COND_SUSPEND,
"at91_rtc", pdev);
if (ret) {
dev_err(&pdev->dev, "IRQ %d already in use.\n", irq);
return ret;
@ -454,8 +469,6 @@ static void at91_rtc_shutdown(struct platform_device *pdev)
/* AT91RM9200 RTC Power management control */
static u32 at91_rtc_imr;
static int at91_rtc_suspend(struct device *dev)
{
/* this IRQ is shared with DBGU and other hardware which isn't
@ -464,21 +477,42 @@ static int at91_rtc_suspend(struct device *dev)
at91_rtc_imr = at91_rtc_read_imr()
& (AT91_RTC_ALARM|AT91_RTC_SECEV);
if (at91_rtc_imr) {
if (device_may_wakeup(dev))
if (device_may_wakeup(dev)) {
unsigned long flags;
enable_irq_wake(irq);
else
spin_lock_irqsave(&suspended_lock, flags);
suspended = true;
spin_unlock_irqrestore(&suspended_lock, flags);
} else {
at91_rtc_write_idr(at91_rtc_imr);
}
}
return 0;
}
static int at91_rtc_resume(struct device *dev)
{
struct rtc_device *rtc = dev_get_drvdata(dev);
if (at91_rtc_imr) {
if (device_may_wakeup(dev))
if (device_may_wakeup(dev)) {
unsigned long flags;
spin_lock_irqsave(&suspended_lock, flags);
if (cached_events) {
rtc_update_irq(rtc, 1, cached_events);
cached_events = 0;
}
suspended = false;
spin_unlock_irqrestore(&suspended_lock, flags);
disable_irq_wake(irq);
else
at91_rtc_write_ier(at91_rtc_imr);
}
at91_rtc_write_ier(at91_rtc_imr);
}
return 0;
}

View File

@ -23,6 +23,7 @@
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/suspend.h>
#include <linux/clk.h>
/*
@ -77,6 +78,9 @@ struct sam9_rtc {
unsigned int gpbr_offset;
int irq;
struct clk *sclk;
bool suspended;
unsigned long events;
spinlock_t lock;
};
#define rtt_readl(rtc, field) \
@ -271,14 +275,9 @@ static int at91_rtc_proc(struct device *dev, struct seq_file *seq)
return 0;
}
/*
* IRQ handler for the RTC
*/
static irqreturn_t at91_rtc_interrupt(int irq, void *_rtc)
static irqreturn_t at91_rtc_cache_events(struct sam9_rtc *rtc)
{
struct sam9_rtc *rtc = _rtc;
u32 sr, mr;
unsigned long events = 0;
/* Shared interrupt may be for another device. Note: reading
* SR clears it, so we must only read it in this irq handler!
@ -290,20 +289,56 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *_rtc)
/* alarm status */
if (sr & AT91_RTT_ALMS)
events |= (RTC_AF | RTC_IRQF);
rtc->events |= (RTC_AF | RTC_IRQF);
/* timer update/increment */
if (sr & AT91_RTT_RTTINC)
events |= (RTC_UF | RTC_IRQF);
rtc_update_irq(rtc->rtcdev, 1, events);
pr_debug("%s: num=%ld, events=0x%02lx\n", __func__,
events >> 8, events & 0x000000FF);
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, rtc->events);
rtc->events = 0;
pr_debug("%s: num=%ld, events=0x%02lx\n", __func__,
rtc->events >> 8, rtc->events & 0x000000FF);
}
/*
* 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 = {
.read_time = at91_rtc_readtime,
.set_time = at91_rtc_settime,
@ -421,7 +456,8 @@ static int at91_rtc_probe(struct platform_device *pdev)
/* register irq handler after we know what name we'll use */
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) {
dev_dbg(&pdev->dev, "can't share IRQ %d?\n", rtc->irq);
return ret;
@ -482,7 +518,12 @@ static int at91_rtc_suspend(struct device *dev)
rtc->imr = mr & (AT91_RTT_ALMIEN | AT91_RTT_RTTINCIEN);
if (rtc->imr) {
if (device_may_wakeup(dev) && (mr & AT91_RTT_ALMIEN)) {
unsigned long flags;
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 */
if (mr & AT91_RTT_RTTINCIEN)
rtt_writel(rtc, MR, mr & ~AT91_RTT_RTTINCIEN);
@ -499,10 +540,18 @@ static int at91_rtc_resume(struct device *dev)
u32 mr;
if (rtc->imr) {
unsigned long flags;
if (device_may_wakeup(dev))
disable_irq_wake(rtc->irq);
mr = rtt_readl(rtc, MR);
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;

View File

@ -47,6 +47,7 @@
#include <linux/gpio/consumer.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <linux/suspend.h>
#include <asm/io.h>
#include <asm/ioctls.h>
@ -173,6 +174,12 @@ struct atmel_uart_port {
bool ms_irq_enabled;
bool is_usart; /* usart or uart */
struct timer_list uart_timer; /* uart timer */
bool suspended;
unsigned int pending;
unsigned int pending_status;
spinlock_t lock_suspended;
int (*prepare_rx)(struct uart_port *port);
int (*prepare_tx)(struct uart_port *port);
void (*schedule_rx)(struct uart_port *port);
@ -1179,12 +1186,15 @@ static irqreturn_t atmel_interrupt(int irq, void *dev_id)
{
struct uart_port *port = dev_id;
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
unsigned int status, pending, pass_counter = 0;
unsigned int status, pending, mask, pass_counter = 0;
bool gpio_handled = false;
spin_lock(&atmel_port->lock_suspended);
do {
status = atmel_get_lines_status(port);
pending = status & UART_GET_IMR(port);
mask = UART_GET_IMR(port);
pending = status & mask;
if (!gpio_handled) {
/*
* Dealing with GPIO interrupt
@ -1206,11 +1216,21 @@ static irqreturn_t atmel_interrupt(int irq, void *dev_id)
if (!pending)
break;
if (atmel_port->suspended) {
atmel_port->pending |= pending;
atmel_port->pending_status = status;
UART_PUT_IDR(port, mask);
pm_system_wakeup();
break;
}
atmel_handle_receive(port, pending);
atmel_handle_status(port, pending, status);
atmel_handle_transmit(port, pending);
} while (pass_counter++ < ATMEL_ISR_PASS_LIMIT);
spin_unlock(&atmel_port->lock_suspended);
return pass_counter ? IRQ_HANDLED : IRQ_NONE;
}
@ -1742,7 +1762,8 @@ static int atmel_startup(struct uart_port *port)
/*
* Allocate the IRQ
*/
retval = request_irq(port->irq, atmel_interrupt, IRQF_SHARED,
retval = request_irq(port->irq, atmel_interrupt,
IRQF_SHARED | IRQF_COND_SUSPEND,
tty ? tty->name : "atmel_serial", port);
if (retval) {
dev_err(port->dev, "atmel_startup - Can't get irq\n");
@ -2513,8 +2534,14 @@ static int atmel_serial_suspend(struct platform_device *pdev,
/* we can not wake up if we're running on slow clock */
atmel_port->may_wakeup = device_may_wakeup(&pdev->dev);
if (atmel_serial_clk_will_stop())
if (atmel_serial_clk_will_stop()) {
unsigned long flags;
spin_lock_irqsave(&atmel_port->lock_suspended, flags);
atmel_port->suspended = true;
spin_unlock_irqrestore(&atmel_port->lock_suspended, flags);
device_set_wakeup_enable(&pdev->dev, 0);
}
uart_suspend_port(&atmel_uart, port);
@ -2525,6 +2552,18 @@ static int atmel_serial_resume(struct platform_device *pdev)
{
struct uart_port *port = platform_get_drvdata(pdev);
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
unsigned long flags;
spin_lock_irqsave(&atmel_port->lock_suspended, flags);
if (atmel_port->pending) {
atmel_handle_receive(port, atmel_port->pending);
atmel_handle_status(port, atmel_port->pending,
atmel_port->pending_status);
atmel_handle_transmit(port, atmel_port->pending);
atmel_port->pending = 0;
}
atmel_port->suspended = false;
spin_unlock_irqrestore(&atmel_port->lock_suspended, flags);
uart_resume_port(&atmel_uart, port);
device_set_wakeup_enable(&pdev->dev, atmel_port->may_wakeup);
@ -2593,6 +2632,8 @@ static int atmel_serial_probe(struct platform_device *pdev)
port->backup_imr = 0;
port->uart.line = ret;
spin_lock_init(&port->lock_suspended);
ret = atmel_init_gpios(port, &pdev->dev);
if (ret < 0)
dev_err(&pdev->dev, "%s",

View File

@ -208,7 +208,8 @@ static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt)
if ((tmp & AT91_WDT_WDFIEN) && wdt->irq) {
err = request_irq(wdt->irq, wdt_interrupt,
IRQF_SHARED | IRQF_IRQPOLL,
IRQF_SHARED | IRQF_IRQPOLL |
IRQF_NO_SUSPEND,
pdev->name, wdt);
if (err)
return err;

View File

@ -52,11 +52,17 @@
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend. Does not guarantee
* that this interrupt will wake the system from a suspended
* state. See Documentation/power/suspend-and-interrupts.txt
* IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
* IRQF_NO_THREAD - Interrupt cannot be threaded
* IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
* resume time.
* IRQF_COND_SUSPEND - If the IRQ is shared with a NO_SUSPEND user, execute this
* interrupt handler after suspending interrupts. For system
* wakeup devices users need to implement wakeup detection in
* their interrupt handlers.
*/
#define IRQF_DISABLED 0x00000020
#define IRQF_SHARED 0x00000080
@ -70,6 +76,7 @@
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
#define IRQF_COND_SUSPEND 0x00040000
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)

View File

@ -78,6 +78,7 @@ struct irq_desc {
#ifdef CONFIG_PM_SLEEP
unsigned int nr_actions;
unsigned int no_suspend_depth;
unsigned int cond_suspend_depth;
unsigned int force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS

View File

@ -1474,8 +1474,13 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler,
* otherwise we'll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc).
*
* Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
* it cannot be set along with IRQF_NO_SUSPEND.
*/
if ((irqflags & IRQF_SHARED) && !dev_id)
if (((irqflags & IRQF_SHARED) && !dev_id) ||
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
return -EINVAL;
desc = irq_to_desc(irq);

View File

@ -43,9 +43,12 @@ void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action)
if (action->flags & IRQF_NO_SUSPEND)
desc->no_suspend_depth++;
else if (action->flags & IRQF_COND_SUSPEND)
desc->cond_suspend_depth++;
WARN_ON_ONCE(desc->no_suspend_depth &&
desc->no_suspend_depth != desc->nr_actions);
(desc->no_suspend_depth +
desc->cond_suspend_depth) != desc->nr_actions);
}
/*
@ -61,6 +64,8 @@ void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action)
if (action->flags & IRQF_NO_SUSPEND)
desc->no_suspend_depth--;
else if (action->flags & IRQF_COND_SUSPEND)
desc->cond_suspend_depth--;
}
static bool suspend_device_irq(struct irq_desc *desc, int irq)