genirq: Implement a generic interrupt chip
Thomas Gleixner [Sun, 3 Apr 2011 09:42:53 +0000 (11:42 +0200)]
Implement a generic interrupt chip, which is configurable and is able
to handle the most common irq chip implementations.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-arm-kernel@lists.infradead.org
Tested-by: H Hartley Sweeten <hsweeten@visionengravers.com>
Tested-by: Tony Lindgren <tony@atomide.com>
Tested-by; Kevin Hilman <khilman@ti.com>

include/linux/irq.h
kernel/irq/Makefile
kernel/irq/generic-chip.c [new file with mode: 0644]

index 39c2378..2ba2f12 100644 (file)
@@ -568,6 +568,141 @@ static inline int irq_reserve_irq(unsigned int irq)
        return irq_reserve_irqs(irq, 1);
 }
 
+#ifndef irq_reg_writel
+# define irq_reg_writel(val, addr)     writel(val, addr)
+#endif
+#ifndef irq_reg_readl
+# define irq_reg_readl(addr)           readl(addr)
+#endif
+
+/**
+ * struct irq_chip_regs - register offsets for struct irq_gci
+ * @enable:    Enable register offset to reg_base
+ * @disable:   Disable register offset to reg_base
+ * @mask:      Mask register offset to reg_base
+ * @ack:       Ack register offset to reg_base
+ * @eoi:       Eoi register offset to reg_base
+ * @type:      Type configuration register offset to reg_base
+ * @polarity:  Polarity configuration register offset to reg_base
+ */
+struct irq_chip_regs {
+       unsigned long           enable;
+       unsigned long           disable;
+       unsigned long           mask;
+       unsigned long           ack;
+       unsigned long           eoi;
+       unsigned long           type;
+       unsigned long           polarity;
+};
+
+/**
+ * struct irq_chip_type - Generic interrupt chip instance for a flow type
+ * @chip:              The real interrupt chip which provides the callbacks
+ * @regs:              Register offsets for this chip
+ * @handler:           Flow handler associated with this chip
+ * @type:              Chip can handle these flow types
+ *
+ * A irq_generic_chip can have several instances of irq_chip_type when
+ * it requires different functions and register offsets for different
+ * flow types.
+ */
+struct irq_chip_type {
+       struct irq_chip         chip;
+       struct irq_chip_regs    regs;
+       irq_flow_handler_t      handler;
+       u32                     type;
+};
+
+/**
+ * struct irq_chip_generic - Generic irq chip data structure
+ * @lock:              Lock to protect register and cache data access
+ * @reg_base:          Register base address (virtual)
+ * @irq_base:          Interrupt base nr for this chip
+ * @irq_cnt:           Number of interrupts handled by this chip
+ * @mask_cache:                Cached mask register
+ * @type_cache:                Cached type register
+ * @polarity_cache:    Cached polarity register
+ * @wake_enabled:      Interrupt can wakeup from suspend
+ * @wake_active:       Interrupt is marked as an wakeup from suspend source
+ * @num_ct:            Number of available irq_chip_type instances (usually 1)
+ * @private:           Private data for non generic chip callbacks
+ * @chip_types:                Array of interrupt irq_chip_types
+ *
+ * Note, that irq_chip_generic can have multiple irq_chip_type
+ * implementations which can be associated to a particular irq line of
+ * an irq_chip_generic instance. That allows to share and protect
+ * state in an irq_chip_generic instance when we need to implement
+ * different flow mechanisms (level/edge) for it.
+ */
+struct irq_chip_generic {
+       raw_spinlock_t          lock;
+       void __iomem            *reg_base;
+       unsigned int            irq_base;
+       unsigned int            irq_cnt;
+       u32                     mask_cache;
+       u32                     type_cache;
+       u32                     polarity_cache;
+       u32                     wake_enabled;
+       u32                     wake_active;
+       unsigned int            num_ct;
+       void                    *private;
+       struct irq_chip_type    chip_types[0];
+};
+
+/**
+ * enum irq_gc_flags - Initialization flags for generic irq chips
+ * @IRQ_GC_INIT_MASK_CACHE:    Initialize the mask_cache by reading mask reg
+ * @IRQ_GC_INIT_NESTED_LOCK:   Set the lock class of the irqs to nested for
+ *                             irq chips which need to call irq_set_wake() on
+ *                             the parent irq. Usually GPIO implementations
+ */
+enum irq_gc_flags {
+       IRQ_GC_INIT_MASK_CACHE          = 1 << 0,
+       IRQ_GC_INIT_NESTED_LOCK         = 1 << 1,
+};
+
+/* Generic chip callback functions */
+void irq_gc_noop(struct irq_data *d);
+void irq_gc_mask_disable_reg(struct irq_data *d);
+void irq_gc_mask_set_bit(struct irq_data *d);
+void irq_gc_mask_clr_bit(struct irq_data *d);
+void irq_gc_unmask_enable_reg(struct irq_data *d);
+void irq_gc_ack(struct irq_data *d);
+void irq_gc_mask_disable_reg_and_ack(struct irq_data *d);
+void irq_gc_eoi(struct irq_data *d);
+int irq_gc_set_wake(struct irq_data *d, unsigned int on);
+
+/* Setup functions for irq_chip_generic */
+struct irq_chip_generic *
+irq_alloc_generic_chip(const char *name, int nr_ct, unsigned int irq_base,
+                      void __iomem *reg_base, irq_flow_handler_t handler);
+void irq_setup_generic_chip(struct irq_chip_generic *gc, u32 msk,
+                           enum irq_gc_flags flags, unsigned int clr,
+                           unsigned int set);
+int irq_setup_alt_chip(struct irq_data *d, unsigned int type);
+
+static inline struct irq_chip_type *irq_data_get_chip_type(struct irq_data *d)
+{
+       return container_of(d->chip, struct irq_chip_type, chip);
+}
+
+#define IRQ_MSK(n) (u32)((n) < 32 ? ((1 << (n)) - 1) : UINT_MAX)
+
+#ifdef CONFIG_SMP
+static inline void irq_gc_lock(struct irq_chip_generic *gc)
+{
+       raw_spin_lock(&gc->lock);
+}
+
+static inline void irq_gc_unlock(struct irq_chip_generic *gc)
+{
+       raw_spin_unlock(&gc->lock);
+}
+#else
+static inline void irq_gc_lock(struct irq_chip_generic *gc) { }
+static inline void irq_gc_unlock(struct irq_chip_generic *gc) { }
+#endif
+
 #endif /* CONFIG_GENERIC_HARDIRQS */
 
 #endif /* !CONFIG_S390 */
index 54329cd..e7a13bd 100644 (file)
@@ -1,5 +1,6 @@
 
 obj-y := irqdesc.o handle.o manage.o spurious.o resend.o chip.o dummychip.o devres.o
+obj-y += generic-chip.o
 obj-$(CONFIG_GENERIC_IRQ_PROBE) += autoprobe.o
 obj-$(CONFIG_PROC_FS) += proc.o
 obj-$(CONFIG_GENERIC_PENDING_IRQ) += migration.o
diff --git a/kernel/irq/generic-chip.c b/kernel/irq/generic-chip.c
new file mode 100644 (file)
index 0000000..eb23e59
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+ * Library implementing the most common irq chip callback functions
+ *
+ * Copyright (C) 2011, Thomas Gleixner
+ */
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/kernel_stat.h>
+
+#include "internals.h"
+
+static inline struct irq_chip_regs *cur_regs(struct irq_data *d)
+{
+       return &container_of(d->chip, struct irq_chip_type, chip)->regs;
+}
+
+/**
+ * irq_gc_noop - NOOP function
+ * @d: irq_data
+ */
+void irq_gc_noop(struct irq_data *d)
+{
+}
+
+/**
+ * irq_gc_mask_disable_reg - Mask chip via disable register
+ * @d: irq_data
+ *
+ * Chip has separate enable/disable registers instead of a single mask
+ * register.
+ */
+void irq_gc_mask_disable_reg(struct irq_data *d)
+{
+       struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+       u32 mask = 1 << (d->irq - gc->irq_base);
+
+       irq_gc_lock(gc);
+       irq_reg_writel(mask, gc->reg_base + cur_regs(d)->disable);
+       gc->mask_cache &= ~mask;
+       irq_gc_unlock(gc);
+}
+
+/**
+ * irq_gc_mask_set_mask_bit - Mask chip via setting bit in mask register
+ * @d: irq_data
+ *
+ * Chip has a single mask register. Values of this register are cached
+ * and protected by gc->lock
+ */
+void irq_gc_mask_set_bit(struct irq_data *d)
+{
+       struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+       u32 mask = 1 << (d->irq - gc->irq_base);
+
+       irq_gc_lock(gc);
+       gc->mask_cache |= mask;
+       irq_reg_writel(gc->mask_cache, gc->reg_base + cur_regs(d)->mask);
+       irq_gc_unlock(gc);
+}
+
+/**
+ * irq_gc_mask_set_mask_bit - Mask chip via clearing bit in mask register
+ * @d: irq_data
+ *
+ * Chip has a single mask register. Values of this register are cached
+ * and protected by gc->lock
+ */
+void irq_gc_mask_clr_bit(struct irq_data *d)
+{
+       struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+       u32 mask = 1 << (d->irq - gc->irq_base);
+
+       irq_gc_lock(gc);
+       gc->mask_cache &= ~mask;
+       irq_reg_writel(gc->mask_cache, gc->reg_base + cur_regs(d)->mask);
+       irq_gc_unlock(gc);
+}
+
+/**
+ * irq_gc_unmask_enable_reg - Unmask chip via enable register
+ * @d: irq_data
+ *
+ * Chip has separate enable/disable registers instead of a single mask
+ * register.
+ */
+void irq_gc_unmask_enable_reg(struct irq_data *d)
+{
+       struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+       u32 mask = 1 << (d->irq - gc->irq_base);
+
+       irq_gc_lock(gc);
+       irq_reg_writel(mask, gc->reg_base + cur_regs(d)->enable);
+       gc->mask_cache |= mask;
+       irq_gc_unlock(gc);
+}
+
+/**
+ * irq_gc_ack - Ack pending interrupt
+ * @d: irq_data
+ */
+void irq_gc_ack(struct irq_data *d)
+{
+       struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+       u32 mask = 1 << (d->irq - gc->irq_base);
+
+       irq_gc_lock(gc);
+       irq_reg_writel(mask, gc->reg_base + cur_regs(d)->ack);
+       irq_gc_unlock(gc);
+}
+
+/**
+ * irq_gc_mask_disable_reg_and_ack- Mask and ack pending interrupt
+ * @d: irq_data
+ */
+void irq_gc_mask_disable_reg_and_ack(struct irq_data *d)
+{
+       struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+       u32 mask = 1 << (d->irq - gc->irq_base);
+
+       irq_gc_lock(gc);
+       irq_reg_writel(mask, gc->reg_base + cur_regs(d)->mask);
+       irq_reg_writel(mask, gc->reg_base + cur_regs(d)->ack);
+       irq_gc_unlock(gc);
+}
+
+/**
+ * irq_gc_eoi - EOI interrupt
+ * @d: irq_data
+ */
+void irq_gc_eoi(struct irq_data *d)
+{
+       struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+       u32 mask = 1 << (d->irq - gc->irq_base);
+
+       irq_gc_lock(gc);
+       irq_reg_writel(mask, gc->reg_base + cur_regs(d)->eoi);
+       irq_gc_unlock(gc);
+}
+
+/**
+ * irq_gc_set_wake - Set/clr wake bit for an interrupt
+ * @d: irq_data
+ *
+ * For chips where the wake from suspend functionality is not
+ * configured in a separate register and the wakeup active state is
+ * just stored in a bitmask.
+ */
+int irq_gc_set_wake(struct irq_data *d, unsigned int on)
+{
+       struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+       u32 mask = 1 << (d->irq - gc->irq_base);
+
+       if (!(mask & gc->wake_enabled))
+               return -EINVAL;
+
+       irq_gc_lock(gc);
+       if (on)
+               gc->wake_active |= mask;
+       else
+               gc->wake_active &= ~mask;
+       irq_gc_unlock(gc);
+       return 0;
+}
+
+/**
+ * irq_alloc_generic_chip - Allocate a generic chip and initialize it
+ * @name:      Name of the irq chip
+ * @num_ct:    Number of irq_chip_type instances associated with this
+ * @irq_base:  Interrupt base nr for this chip
+ * @reg_base:  Register base address (virtual)
+ * @handler:   Default flow handler associated with this chip
+ *
+ * Returns an initialized irq_chip_generic structure. The chip defaults
+ * to the primary (index 0) irq_chip_type and @handler
+ */
+struct irq_chip_generic *
+irq_alloc_generic_chip(const char *name, int num_ct, unsigned int irq_base,
+                      void __iomem *reg_base, irq_flow_handler_t handler)
+{
+       struct irq_chip_generic *gc;
+       unsigned long sz = sizeof(*gc) + num_ct * sizeof(struct irq_chip_type);
+
+       gc = kzalloc(sz, GFP_KERNEL);
+       if (gc) {
+               raw_spin_lock_init(&gc->lock);
+               gc->num_ct = num_ct;
+               gc->irq_base = irq_base;
+               gc->reg_base = reg_base;
+               gc->chip_types->chip.name = name;
+               gc->chip_types->handler = handler;
+       }
+       return gc;
+}
+
+/*
+ * Separate lockdep class for interrupt chip which can nest irq_desc
+ * lock.
+ */
+static struct lock_class_key irq_nested_lock_class;
+
+/**
+ * irq_setup_generic_chip - Setup a range of interrupts with a generic chip
+ * @gc:                Generic irq chip holding all data
+ * @msk:       Bitmask holding the irqs to initialize relative to gc->irq_base
+ * @flags:     Flags for initialization
+ * @clr:       IRQ_* bits to clear
+ * @set:       IRQ_* bits to set
+ *
+ * Set up max. 32 interrupts starting from gc->irq_base. Note, this
+ * initializes all interrupts to the primary irq_chip_type and its
+ * associated handler.
+ */
+void irq_setup_generic_chip(struct irq_chip_generic *gc, u32 msk,
+                           enum irq_gc_flags flags, unsigned int clr,
+                           unsigned int set)
+{
+       struct irq_chip_type *ct = gc->chip_types;
+       unsigned int i;
+
+       /* Init mask cache ? */
+       if (flags & IRQ_GC_INIT_MASK_CACHE)
+               gc->mask_cache = irq_reg_readl(gc->reg_base + ct->regs.mask);
+
+       for (i = gc->irq_base; msk; msk >>= 1, i++) {
+               if (!msk & 0x01)
+                       continue;
+
+               if (flags & IRQ_GC_INIT_NESTED_LOCK)
+                       irq_set_lockdep_class(i, &irq_nested_lock_class);
+
+               irq_set_chip_and_handler(i, &ct->chip, ct->handler);
+               irq_set_chip_data(i, gc);
+               irq_modify_status(i, clr, set);
+       }
+       gc->irq_cnt = i - gc->irq_base;
+}
+
+/**
+ * irq_setup_alt_chip - Switch to alternative chip
+ * @d:         irq_data for this interrupt
+ * @type       Flow type to be initialized
+ *
+ * Only to be called from chip->irq_set_type() callbacks.
+ */
+int irq_setup_alt_chip(struct irq_data *d, unsigned int type)
+{
+       struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
+       struct irq_chip_type *ct = gc->chip_types;
+       unsigned int i;
+
+       for (i = 0; i < gc->num_ct; i++, ct++) {
+               if (ct->type & type) {
+                       d->chip = &ct->chip;
+                       irq_data_to_desc(d)->handle_irq = ct->handler;
+                       return 0;
+               }
+       }
+       return -EINVAL;
+}