[PATCH] genirq: {en,dis}able_irq_wake() need refcounting too
authorDavid Brownell <david-b@pacbell.net>
Sun, 30 Jul 2006 10:03:08 +0000 (03:03 -0700)
committerLinus Torvalds <torvalds@g5.osdl.org>
Mon, 31 Jul 2006 20:28:36 +0000 (13:28 -0700)
IRQs need refcounting and a state flag to track whether the the IRQ should
be enabled or disabled as a "normal IRQ" source after a series of calls to
{en,dis}able_irq().  For shared IRQs, the IRQ must be enabled so long as at
least one driver needs it active.

Likewise, IRQs need the same support to track whether the IRQ should be
enabled or disabled as a "wakeup event" source after a series of calls to
{en,dis}able_irq_wake().  For shared IRQs, the IRQ must be enabled as a
wakeup source during sleep so long as at least one driver needs it.  But
right now they _don't have_ that refcounting ...  which means sharing a
wakeup-capable IRQ can't work correctly in some configurations.

This patch adds the refcount and flag mechanisms to set_irq_wake() -- which
is what {en,dis}able_irq_wake() call -- and minimal documentation of what
the irq wake mechanism does.

Drivers relying on the older (broken) "toggle" semantics will trigger a
warning; that'll be a handful of drivers on ARM systems.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Acked-by: Ingo Molnar <mingo@elte.hu>
Acked-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Russell King <rmk@arm.linux.org.uk>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
include/linux/irq.h
kernel/irq/manage.c

index b48eae32dc61c05eb5afd83c1c6c23ab4d10b85f..6e59ac05ef2af46d9a285bb57d5f8a8b81ff3c97 100644 (file)
@@ -58,6 +58,7 @@
 #define IRQ_NOREQUEST          0x04000000      /* IRQ cannot be requested */
 #define IRQ_NOAUTOEN           0x08000000      /* IRQ will not be enabled on request irq */
 #define IRQ_DELAYED_DISABLE    0x10000000      /* IRQ disable (masking) happens delayed. */
+#define IRQ_WAKEUP             0x20000000      /* IRQ triggers system wakeup */
 
 struct proc_dir_entry;
 
@@ -124,6 +125,7 @@ struct irq_chip {
  * @action:            the irq action chain
  * @status:            status information
  * @depth:             disable-depth, for nested irq_disable() calls
+ * @wake_depth:                enable depth, for multiple set_irq_wake() callers
  * @irq_count:         stats field to detect stalled irqs
  * @irqs_unhandled:    stats field for spurious unhandled interrupts
  * @lock:              locking for SMP
@@ -147,6 +149,7 @@ struct irq_desc {
        unsigned int            status;         /* IRQ status */
 
        unsigned int            depth;          /* nested irq disables */
+       unsigned int            wake_depth;     /* nested wake enables */
        unsigned int            irq_count;      /* For detecting broken IRQs */
        unsigned int            irqs_unhandled;
        spinlock_t              lock;
index 4e461438e48bfe687f4ced338a0f2c69791fb33e..92be519eff26fe3fd467eb6ca0e3e063a36f50ce 100644 (file)
@@ -137,16 +137,40 @@ EXPORT_SYMBOL(enable_irq);
  *     @irq:   interrupt to control
  *     @on:    enable/disable power management wakeup
  *
- *     Enable/disable power management wakeup mode
+ *     Enable/disable power management wakeup mode, which is
+ *     disabled by default.  Enables and disables must match,
+ *     just as they match for non-wakeup mode support.
+ *
+ *     Wakeup mode lets this IRQ wake the system from sleep
+ *     states like "suspend to RAM".
  */
 int set_irq_wake(unsigned int irq, unsigned int on)
 {
        struct irq_desc *desc = irq_desc + irq;
        unsigned long flags;
        int ret = -ENXIO;
+       int (*set_wake)(unsigned, unsigned) = desc->chip->set_wake;
 
+       /* wakeup-capable irqs can be shared between drivers that
+        * don't need to have the same sleep mode behaviors.
+        */
        spin_lock_irqsave(&desc->lock, flags);
-       if (desc->chip->set_wake)
+       if (on) {
+               if (desc->wake_depth++ == 0)
+                       desc->status |= IRQ_WAKEUP;
+               else
+                       set_wake = NULL;
+       } else {
+               if (desc->wake_depth == 0) {
+                       printk(KERN_WARNING "Unbalanced IRQ %d "
+                                       "wake disable\n", irq);
+                       WARN_ON(1);
+               } else if (--desc->wake_depth == 0)
+                       desc->status &= ~IRQ_WAKEUP;
+               else
+                       set_wake = NULL;
+       }
+       if (set_wake)
                ret = desc->chip->set_wake(irq, on);
        spin_unlock_irqrestore(&desc->lock, flags);
        return ret;