===================================================================
@@ -101,6 +101,9 @@ typedef irqreturn_t (*irq_handler_t)(int
* @thread_flags: flags related to @thread
* @thread_mask: bitmask for keeping track of @thread activity
* @dir: pointer to the proc/irq/NN/name entry
+ * @s_handler: original interrupt handler for suspend mode interrupts
+ * @s_dev_id: original device identification cookie for suspend mode
+ * @prev_ret: suspend mode return code from the previous action
*/
struct irqaction {
irq_handler_t handler;
@@ -115,6 +118,11 @@ struct irqaction {
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
+#ifdef CONFIG_PM_SLEEP
+ irq_handler_t s_handler;
+ void *s_dev_id;
+ irqreturn_t prev_ret;
+#endif
} ____cacheline_internodealigned_in_smp;
extern irqreturn_t no_action(int cpl, void *dev_id);
===================================================================
@@ -71,6 +71,9 @@ struct irq_desc {
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
+#ifdef CONFIG_PM_SLEEP
+ unsigned int skip_suspend_depth;
+#endif
int parent_irq;
struct module *owner;
const char *name;
===================================================================
@@ -385,7 +385,9 @@ setup_affinity(unsigned int irq, struct
void __disable_irq(struct irq_desc *desc, unsigned int irq, bool suspend)
{
if (suspend) {
- if (!desc->action || (desc->action->flags & IRQF_NO_SUSPEND))
+ if (!desc->action)
+ return;
+ if (irq_pm_suspend_mode(desc))
return;
desc->istate |= IRQS_SUSPENDED;
}
@@ -445,6 +447,7 @@ EXPORT_SYMBOL(disable_irq);
void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume)
{
if (resume) {
+ irq_pm_normal_mode(desc);
if (!(desc->istate & IRQS_SUSPENDED)) {
if (!desc->action)
return;
@@ -1222,6 +1225,8 @@ __setup_irq(unsigned int irq, struct irq
desc->irq_count = 0;
desc->irqs_unhandled = 0;
+ irq_pm_setup(desc, new);
+
/*
* Check whether we disabled the irq via the spurious handler
* before. Reenable it and give it another chance.
@@ -1328,7 +1333,7 @@ static struct irqaction *__free_irq(unsi
return NULL;
}
- if (action->dev_id == dev_id)
+ if (action->dev_id == dev_id || irq_pm_saved_id(action, dev_id))
break;
action_ptr = &action->next;
}
@@ -1336,6 +1341,8 @@ static struct irqaction *__free_irq(unsi
/* Found it - now remove it from the list of entries: */
*action_ptr = action->next;
+ irq_pm_cleanup(desc, action);
+
/* If this was the last handler, shut down the IRQ line: */
if (!desc->action) {
irq_shutdown(desc);
===================================================================
@@ -9,10 +9,96 @@
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/interrupt.h>
+#include <linux/suspend.h>
#include <linux/syscore_ops.h>
#include "internals.h"
+static void irq_pm_restore_handler(struct irqaction *action)
+{
+ if (action->s_handler) {
+ action->handler = action->s_handler;
+ action->s_handler = NULL;
+ action->dev_id = action->s_dev_id;
+ action->s_dev_id = NULL;
+ }
+}
+
+static void irq_pm_disable_and_wakeup(int irq)
+{
+ struct irq_desc *desc = irq_to_desc(irq);
+
+ desc->istate |= IRQS_SUSPENDED;
+ desc->depth++;
+ irq_disable(desc);
+ pm_system_wakeup();
+}
+
+static irqreturn_t irq_suspend_mode_handler(int irq, void *dev_id)
+{
+ struct irqaction *action = dev_id;
+ struct irqaction *next = action->next;
+ irqreturn_t ret = (action->flags & IRQF_NO_SUSPEND) ?
+ action->s_handler(irq, action->s_dev_id) : IRQ_NONE;
+
+ if (next) {
+ /* Propagate the return code. */
+ next->prev_ret = ret | action->prev_ret;
+ } else if ((ret | action->prev_ret) == IRQ_NONE) {
+ /*
+ * This is the last action is this chain, and all of the
+ * handlers returned IRQ_NONE. This means an unhandled
+ * interrupt during system suspend, so disable the IRQ and
+ * trigger wakeup.
+ */
+ pr_err("IRQ %d: Unhandled while suspended\n", irq);
+ irq_pm_disable_and_wakeup(irq);
+ }
+ return ret;
+}
+
+bool irq_pm_suspend_mode(struct irq_desc *desc)
+{
+ struct irqaction *action;
+
+ if (!desc->skip_suspend_depth)
+ return false;
+
+ /* No need to replace the handler if the IRQ is not shared. */
+ if (!desc->action->next)
+ return true;
+
+ for (action = desc->action; action; action = action->next) {
+ action->s_handler = action->handler;
+ action->handler = irq_suspend_mode_handler;
+ action->s_dev_id = action->dev_id;
+ action->dev_id = action;
+ action->prev_ret = IRQ_NONE;
+ }
+ return true;
+}
+
+void irq_pm_normal_mode(struct irq_desc *desc)
+{
+ struct irqaction *action;
+
+ for (action = desc->action; action; action = action->next)
+ irq_pm_restore_handler(action);
+}
+
+void irq_pm_setup(struct irq_desc *desc, struct irqaction *action)
+{
+ if (action->flags & IRQF_NO_SUSPEND)
+ desc->skip_suspend_depth++;
+}
+
+void irq_pm_cleanup(struct irq_desc *desc, struct irqaction *action)
+{
+ irq_pm_restore_handler(action);
+ if (action->flags & IRQF_NO_SUSPEND)
+ desc->skip_suspend_depth--;
+}
+
/**
* suspend_device_irqs - disable all currently enabled interrupt lines
*
@@ -35,8 +121,7 @@ void suspend_device_irqs(void)
}
for_each_irq_desc(irq, desc)
- if (desc->istate & IRQS_SUSPENDED)
- synchronize_irq(irq);
+ synchronize_irq(irq);
}
EXPORT_SYMBOL_GPL(suspend_device_irqs);
===================================================================
@@ -194,3 +194,23 @@ static inline void kstat_incr_irqs_this_
__this_cpu_inc(*desc->kstat_irqs);
__this_cpu_inc(kstat.irqs_sum);
}
+
+#ifdef CONFIG_PM_SLEEP
+static inline bool irq_pm_saved_id(struct irqaction *action, void *dev_id)
+{
+ return action->s_dev_id == dev_id;
+}
+extern void irq_pm_setup(struct irq_desc *desc, struct irqaction *action);
+extern void irq_pm_cleanup(struct irq_desc *desc, struct irqaction *action);
+extern bool irq_pm_suspend_mode(struct irq_desc *desc);
+extern void irq_pm_normal_mode(struct irq_desc *desc);
+#else
+static inline bool irq_pm_saved_id(struct irqaction *action, void *dev_id)
+{
+ return false;
+}
+static inline void irq_pm_setup(struct irq_desc *desc, struct irqaction *action) {}
+static inline void irq_pm_cleanup(struct irq_desc *desc, struct irqaction *action) {}
+static inline bool irq_pm_suspend_mode(struct irq_desc *desc) { return false; }
+static inline void irq_pm_normal_mode(struct irq_desc *desc) {}
+#endif