genirq: Generic chip: Add linear irq domain support
authorThomas Gleixner <tglx@linutronix.de>
Mon, 6 May 2013 14:30:27 +0000 (14:30 +0000)
committerThomas Gleixner <tglx@linutronix.de>
Wed, 29 May 2013 08:57:11 +0000 (10:57 +0200)
Provide infrastructure for irq chip implementations which work on
linear irq domains.

- Interface to allocate multiple generic chips which are associated to
  the irq domain.

- Interface to get the generic chip pointer for a particular hardware
  interrupt in the domain.

- irq domain mapping function to install the chip for a particular
  interrupt.

Note: This lacks a removal function for now.

[ Sebastian Hesselbarth: Mask cache and pointer math fixups ]

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
Cc: Andrew Lunn <andrew@lunn.ch>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Jean-Francois Moine <moinejf@free.fr>
Cc: devicetree-discuss@lists.ozlabs.org
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Jason Gunthorpe <jgunthorpe@obsidianresearch.com>
Cc: Gregory Clement <gregory.clement@free-electrons.com>
Cc: Gerlando Falauto <gerlando.falauto@keymile.com>
Cc: Rob Landley <rob@landley.net>
Acked-by: Grant Likely <grant.likely@linaro.org>
Cc: Maxime Ripard <maxime.ripard@free-electrons.com>
Cc: Ezequiel Garcia <ezequiel.garcia@free-electrons.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
Link: http://lkml.kernel.org/r/20130506142539.450634298@linutronix.de
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
include/linux/irq.h
include/linux/irqdomain.h
kernel/irq/generic-chip.c
kernel/irq/irqdomain.c

index ab8169faaa65bd2b50e81c6c93c8df70112cfcc7..af7052c6a45c67850a476455923808faf318b0b8 100644 (file)
@@ -678,6 +678,8 @@ struct irq_chip_type {
  * @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
+ * @installed:         bitfield to denote installed interrupts
+ * @domain:            irq domain pointer
  * @list:              List head for keeping track of instances
  * @chip_types:                Array of interrupt irq_chip_types
  *
@@ -699,6 +701,8 @@ struct irq_chip_generic {
        u32                     wake_active;
        unsigned int            num_ct;
        void                    *private;
+       unsigned long           installed;
+       struct irq_domain       *domain;
        struct list_head        list;
        struct irq_chip_type    chip_types[0];
 };
@@ -719,6 +723,24 @@ enum irq_gc_flags {
        IRQ_GC_NO_MASK                  = 1 << 3,
 };
 
+/*
+ * struct irq_domain_chip_generic - Generic irq chip data structure for irq domains
+ * @irqs_per_chip:     Number of interrupts per chip
+ * @num_chips:         Number of chips
+ * @irq_flags_to_set:  IRQ* flags to set on irq setup
+ * @irq_flags_to_clear:        IRQ* flags to clear on irq setup
+ * @gc_flags:          Generic chip specific setup flags
+ * @gc:                        Array of pointers to generic interrupt chips
+ */
+struct irq_domain_chip_generic {
+       unsigned int            irqs_per_chip;
+       unsigned int            num_chips;
+       unsigned int            irq_flags_to_clear;
+       unsigned int            irq_flags_to_set;
+       enum irq_gc_flags       gc_flags;
+       struct irq_chip_generic *gc[0];
+};
+
 /* Generic chip callback functions */
 void irq_gc_noop(struct irq_data *d);
 void irq_gc_mask_disable_reg(struct irq_data *d);
@@ -742,6 +764,14 @@ int irq_setup_alt_chip(struct irq_data *d, unsigned int type);
 void irq_remove_generic_chip(struct irq_chip_generic *gc, u32 msk,
                             unsigned int clr, unsigned int set);
 
+struct irq_chip_generic *irq_get_domain_generic_chip(struct irq_domain *d, unsigned int hw_irq);
+int irq_alloc_domain_generic_chips(struct irq_domain *d, int irqs_per_chip,
+                                  int num_ct, const char *name,
+                                  irq_flow_handler_t handler,
+                                  unsigned int clr, unsigned int set,
+                                  enum irq_gc_flags flags);
+
+
 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);
index 0d5b17bf5e5131dfd03dc176a8c7fa4f9cbec9b1..ba2c708adcff7da4b277ae9038c2779229831a8e 100644 (file)
@@ -66,6 +66,10 @@ struct irq_domain_ops {
                     unsigned long *out_hwirq, unsigned int *out_type);
 };
 
+extern struct irq_domain_ops irq_generic_chip_ops;
+
+struct irq_domain_chip_generic;
+
 /**
  * struct irq_domain - Hardware interrupt number translation object
  * @link: Element in global irq_domain list.
@@ -109,8 +113,16 @@ struct irq_domain {
 
        /* Optional device node pointer */
        struct device_node *of_node;
+       /* Optional pointer to generic interrupt chips */
+       struct irq_domain_chip_generic *gc;
 };
 
+#define IRQ_DOMAIN_MAP_LEGACY 0 /* driver allocated fixed range of irqs.
+                                * ie. legacy 8259, gets irqs 1..15 */
+#define IRQ_DOMAIN_MAP_NOMAP 1 /* no fast reverse mapping */
+#define IRQ_DOMAIN_MAP_LINEAR 2 /* linear map of interrupts */
+#define IRQ_DOMAIN_MAP_TREE 3 /* radix tree */
+
 #ifdef CONFIG_IRQ_DOMAIN
 struct irq_domain *irq_domain_add_simple(struct device_node *of_node,
                                         unsigned int size,
index 3deb3333d53e84d900b1ec82f4b0f4967f525305..8743d62fded7922320e0e862fd989377278b8b6a 100644 (file)
@@ -7,6 +7,7 @@
 #include <linux/irq.h>
 #include <linux/slab.h>
 #include <linux/export.h>
+#include <linux/irqdomain.h>
 #include <linux/interrupt.h>
 #include <linux/kernel_stat.h>
 #include <linux/syscore_ops.h>
@@ -244,12 +245,156 @@ irq_gc_init_mask_cache(struct irq_chip_generic *gc, enum irq_gc_flags flags)
        }
 }
 
+/**
+ * irq_alloc_domain_generic_chip - Allocate generic chips for an irq domain
+ * @d:                 irq domain for which to allocate chips
+ * @irqs_per_chip:     Number of interrupts each chip handles
+ * @num_ct:            Number of irq_chip_type instances associated with this
+ * @name:              Name of the irq chip
+ * @handler:           Default flow handler associated with these chips
+ * @clr:               IRQ_* bits to clear in the mapping function
+ * @set:               IRQ_* bits to set in the mapping function
+ */
+int irq_alloc_domain_generic_chips(struct irq_domain *d, int irqs_per_chip,
+                                  int num_ct, const char *name,
+                                  irq_flow_handler_t handler,
+                                  unsigned int clr, unsigned int set,
+                                  enum irq_gc_flags gcflags)
+{
+       struct irq_domain_chip_generic *dgc;
+       struct irq_chip_generic *gc;
+       int numchips, sz, i;
+       unsigned long flags;
+       void *tmp;
+
+       if (d->gc)
+               return -EBUSY;
+
+       if (d->revmap_type != IRQ_DOMAIN_MAP_LINEAR)
+               return -EINVAL;
+
+       numchips = d->revmap_data.linear.size / irqs_per_chip;
+       if (!numchips)
+               return -EINVAL;
+
+       /* Allocate a pointer, generic chip and chiptypes for each chip */
+       sz = sizeof(*dgc) + numchips * sizeof(gc);
+       sz += numchips * (sizeof(*gc) + num_ct * sizeof(struct irq_chip_type));
+
+       tmp = dgc = kzalloc(sz, GFP_KERNEL);
+       if (!dgc)
+               return -ENOMEM;
+       dgc->irqs_per_chip = irqs_per_chip;
+       dgc->num_chips = numchips;
+       dgc->irq_flags_to_set = set;
+       dgc->irq_flags_to_clear = clr;
+       dgc->gc_flags = gcflags;
+       d->gc = dgc;
+
+       /* Calc pointer to the first generic chip */
+       tmp += sizeof(*dgc) + numchips * sizeof(gc);
+       for (i = 0; i < numchips; i++) {
+               /* Store the pointer to the generic chip */
+               dgc->gc[i] = gc = tmp;
+               irq_init_generic_chip(gc, name, num_ct, i * irqs_per_chip,
+                                     NULL, handler);
+               gc->domain = d;
+               raw_spin_lock_irqsave(&gc_lock, flags);
+               list_add_tail(&gc->list, &gc_list);
+               raw_spin_unlock_irqrestore(&gc_lock, flags);
+               /* Calc pointer to the next generic chip */
+               tmp += sizeof(*gc) + num_ct * sizeof(struct irq_chip_type);
+       }
+       return 0;
+}
+EXPORT_SYMBOL_GPL(irq_alloc_domain_generic_chips);
+
+/**
+ * irq_get_domain_generic_chip - Get a pointer to the generic chip of a hw_irq
+ * @d:                 irq domain pointer
+ * @hw_irq:            Hardware interrupt number
+ */
+struct irq_chip_generic *
+irq_get_domain_generic_chip(struct irq_domain *d, unsigned int hw_irq)
+{
+       struct irq_domain_chip_generic *dgc = d->gc;
+       int idx;
+
+       if (!dgc)
+               return NULL;
+       idx = hw_irq / dgc->irqs_per_chip;
+       if (idx >= dgc->num_chips)
+               return NULL;
+       return dgc->gc[idx];
+}
+EXPORT_SYMBOL_GPL(irq_get_domain_generic_chip);
+
 /*
  * Separate lockdep class for interrupt chip which can nest irq_desc
  * lock.
  */
 static struct lock_class_key irq_nested_lock_class;
 
+/**
+ * irq_map_generic_chip - Map a generic chip for an irq domain
+ */
+static int irq_map_generic_chip(struct irq_domain *d, unsigned int virq,
+                               irq_hw_number_t hw_irq)
+{
+       struct irq_data *data = irq_get_irq_data(virq);
+       struct irq_domain_chip_generic *dgc = d->gc;
+       struct irq_chip_generic *gc;
+       struct irq_chip_type *ct;
+       struct irq_chip *chip;
+       unsigned long flags;
+       int idx;
+
+       if (!d->gc)
+               return -ENODEV;
+
+       idx = hw_irq / dgc->irqs_per_chip;
+       if (idx >= dgc->num_chips)
+               return -EINVAL;
+       gc = dgc->gc[idx];
+
+       idx = hw_irq % dgc->irqs_per_chip;
+
+       if (test_bit(idx, &gc->installed))
+               return -EBUSY;
+
+       ct = gc->chip_types;
+       chip = &ct->chip;
+
+       /* We only init the cache for the first mapping of a generic chip */
+       if (!gc->installed) {
+               raw_spin_lock_irqsave(&gc->lock, flags);
+               irq_gc_init_mask_cache(gc, dgc->gc_flags);
+               raw_spin_unlock_irqrestore(&gc->lock, flags);
+       }
+
+       /* Mark the interrupt as installed */
+       set_bit(idx, &gc->installed);
+
+       if (dgc->gc_flags & IRQ_GC_INIT_NESTED_LOCK)
+               irq_set_lockdep_class(virq, &irq_nested_lock_class);
+
+       if (chip->irq_calc_mask)
+               chip->irq_calc_mask(data);
+       else
+               data->mask = 1 << idx;
+
+       irq_set_chip_and_handler(virq, chip, ct->handler);
+       irq_set_chip_data(virq, gc);
+       irq_modify_status(virq, dgc->irq_flags_to_clear, dgc->irq_flags_to_set);
+       return 0;
+}
+
+struct irq_domain_ops irq_generic_chip_ops = {
+       .map    = irq_map_generic_chip,
+       .xlate  = irq_domain_xlate_onetwocell,
+};
+EXPORT_SYMBOL_GPL(irq_generic_chip_ops);
+
 /**
  * irq_setup_generic_chip - Setup a range of interrupts with a generic chip
  * @gc:                Generic irq chip holding all data
@@ -354,6 +499,24 @@ void irq_remove_generic_chip(struct irq_chip_generic *gc, u32 msk,
 }
 EXPORT_SYMBOL_GPL(irq_remove_generic_chip);
 
+static struct irq_data *irq_gc_get_irq_data(struct irq_chip_generic *gc)
+{
+       unsigned int virq;
+
+       if (!gc->domain)
+               return irq_get_irq_data(gc->irq_base);
+
+       /*
+        * We don't know which of the irqs has been actually
+        * installed. Use the first one.
+        */
+       if (!gc->installed)
+               return NULL;
+
+       virq = irq_find_mapping(gc->domain, gc->irq_base + __ffs(gc->installed));
+       return virq ? irq_get_irq_data(virq) : NULL;
+}
+
 #ifdef CONFIG_PM
 static int irq_gc_suspend(void)
 {
@@ -362,8 +525,12 @@ static int irq_gc_suspend(void)
        list_for_each_entry(gc, &gc_list, list) {
                struct irq_chip_type *ct = gc->chip_types;
 
-               if (ct->chip.irq_suspend)
-                       ct->chip.irq_suspend(irq_get_irq_data(gc->irq_base));
+               if (ct->chip.irq_suspend) {
+                       struct irq_data *data = irq_gc_get_irq_data(gc);
+
+                       if (data)
+                               ct->chip.irq_suspend(data);
+               }
        }
        return 0;
 }
@@ -375,8 +542,12 @@ static void irq_gc_resume(void)
        list_for_each_entry(gc, &gc_list, list) {
                struct irq_chip_type *ct = gc->chip_types;
 
-               if (ct->chip.irq_resume)
-                       ct->chip.irq_resume(irq_get_irq_data(gc->irq_base));
+               if (ct->chip.irq_resume) {
+                       struct irq_data *data = irq_gc_get_irq_data(gc);
+
+                       if (data)
+                               ct->chip.irq_resume(data);
+               }
        }
 }
 #else
@@ -391,8 +562,12 @@ static void irq_gc_shutdown(void)
        list_for_each_entry(gc, &gc_list, list) {
                struct irq_chip_type *ct = gc->chip_types;
 
-               if (ct->chip.irq_pm_shutdown)
-                       ct->chip.irq_pm_shutdown(irq_get_irq_data(gc->irq_base));
+               if (ct->chip.irq_pm_shutdown) {
+                       struct irq_data *data = irq_gc_get_irq_data(gc);
+
+                       if (data)
+                               ct->chip.irq_pm_shutdown(data);
+               }
        }
 }
 
index 5a83dde8ca0c22544cead99b7ea3e2e0be813d27..1db9e70f548858bd44b3646487453c828e755f69 100644 (file)
 #include <linux/smp.h>
 #include <linux/fs.h>
 
-#define IRQ_DOMAIN_MAP_LEGACY 0 /* driver allocated fixed range of irqs.
-                                * ie. legacy 8259, gets irqs 1..15 */
-#define IRQ_DOMAIN_MAP_NOMAP 1 /* no fast reverse mapping */
-#define IRQ_DOMAIN_MAP_LINEAR 2 /* linear map of interrupts */
-#define IRQ_DOMAIN_MAP_TREE 3 /* radix tree */
-
 static LIST_HEAD(irq_domain_list);
 static DEFINE_MUTEX(irq_domain_mutex);