Merge branch 'master' into for_paulus
[linux-drm-fsl-dcu.git] / arch / sparc64 / kernel / irq.c
index 2873835e2626af60633c6d9a116c32d901c760df..b5ff3ee5ace1cc23d3a86cd50878568b505ea7d8 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/seq_file.h>
 #include <linux/bootmem.h>
 #include <linux/irq.h>
+#include <linux/msi.h>
 
 #include <asm/ptrace.h>
 #include <asm/processor.h>
@@ -87,7 +88,6 @@ struct ino_bucket ivector_table[NUM_IVECS] __attribute__ ((aligned (SMP_CACHE_BY
 #define irq_work(__cpu)        &(trap_block[(__cpu)].irq_worklist)
 
 static unsigned int virt_to_real_irq_table[NR_IRQS];
-static unsigned char virt_irq_cur = 1;
 
 static unsigned char virt_irq_alloc(unsigned int real_irq)
 {
@@ -95,26 +95,32 @@ static unsigned char virt_irq_alloc(unsigned int real_irq)
 
        BUILD_BUG_ON(NR_IRQS >= 256);
 
-       ent = virt_irq_cur;
+       for (ent = 1; ent < NR_IRQS; ent++) {
+               if (!virt_to_real_irq_table[ent])
+                       break;
+       }
        if (ent >= NR_IRQS) {
                printk(KERN_ERR "IRQ: Out of virtual IRQs.\n");
                return 0;
        }
 
-       virt_irq_cur = ent + 1;
        virt_to_real_irq_table[ent] = real_irq;
 
        return ent;
 }
 
-#if 0 /* Currently unused. */
-static unsigned char real_to_virt_irq(unsigned int real_irq)
+static void virt_irq_free(unsigned int virt_irq)
 {
-       struct ino_bucket *bucket = __bucket(real_irq);
+       unsigned int real_irq;
 
-       return bucket->virt_irq;
+       if (virt_irq >= NR_IRQS)
+               return;
+
+       real_irq = virt_to_real_irq_table[virt_irq];
+       virt_to_real_irq_table[virt_irq] = 0;
+
+       __bucket(real_irq)->virt_irq = 0;
 }
-#endif
 
 static unsigned int virt_to_real_irq(unsigned char virt_irq)
 {
@@ -341,6 +347,20 @@ static void sun4v_irq_disable(unsigned int virt_irq)
        }
 }
 
+#ifdef CONFIG_PCI_MSI
+static void sun4v_msi_enable(unsigned int virt_irq)
+{
+       sun4v_irq_enable(virt_irq);
+       unmask_msi_irq(virt_irq);
+}
+
+static void sun4v_msi_disable(unsigned int virt_irq)
+{
+       mask_msi_irq(virt_irq);
+       sun4v_irq_disable(virt_irq);
+}
+#endif
+
 static void sun4v_irq_end(unsigned int virt_irq)
 {
        struct ino_bucket *bucket = virt_irq_to_bucket(virt_irq);
@@ -398,6 +418,18 @@ static struct irq_chip sun4v_irq_ack = {
        .end            = sun4v_irq_end,
 };
 
+#ifdef CONFIG_PCI_MSI
+static struct irq_chip sun4v_msi = {
+       .typename       = "sun4v+msi",
+       .mask           = mask_msi_irq,
+       .unmask         = unmask_msi_irq,
+       .enable         = sun4v_msi_enable,
+       .disable        = sun4v_msi_disable,
+       .ack            = run_pre_handler,
+       .end            = sun4v_irq_end,
+};
+#endif
+
 void irq_install_pre_handler(int virt_irq,
                             void (*func)(unsigned int, void *, void *),
                             void *arg1, void *arg2)
@@ -411,7 +443,11 @@ void irq_install_pre_handler(int virt_irq,
 
        chip = get_irq_chip(virt_irq);
        if (chip == &sun4u_irq_ack ||
-           chip == &sun4v_irq_ack)
+           chip == &sun4v_irq_ack
+#ifdef CONFIG_PCI_MSI
+           || chip == &sun4v_msi
+#endif
+           )
                return;
 
        chip = (chip == &sun4u_irq ?
@@ -489,6 +525,56 @@ out:
        return bucket->virt_irq;
 }
 
+#ifdef CONFIG_PCI_MSI
+unsigned int sun4v_build_msi(u32 devhandle, unsigned int *virt_irq_p,
+                            unsigned int msi_start, unsigned int msi_end)
+{
+       struct ino_bucket *bucket;
+       struct irq_handler_data *data;
+       unsigned long sysino;
+       unsigned int devino;
+
+       BUG_ON(tlb_type != hypervisor);
+
+       /* Find a free devino in the given range.  */
+       for (devino = msi_start; devino < msi_end; devino++) {
+               sysino = sun4v_devino_to_sysino(devhandle, devino);
+               bucket = &ivector_table[sysino];
+               if (!bucket->virt_irq)
+                       break;
+       }
+       if (devino >= msi_end)
+               return 0;
+
+       sysino = sun4v_devino_to_sysino(devhandle, devino);
+       bucket = &ivector_table[sysino];
+       bucket->virt_irq = virt_irq_alloc(__irq(bucket));
+       *virt_irq_p = bucket->virt_irq;
+       set_irq_chip(bucket->virt_irq, &sun4v_msi);
+
+       data = get_irq_chip_data(bucket->virt_irq);
+       if (unlikely(data))
+               return devino;
+
+       data = kzalloc(sizeof(struct irq_handler_data), GFP_ATOMIC);
+       if (unlikely(!data)) {
+               prom_printf("IRQ: kzalloc(irq_handler_data) failed.\n");
+               prom_halt();
+       }
+       set_irq_chip_data(bucket->virt_irq, data);
+
+       data->imap = ~0UL;
+       data->iclr = ~0UL;
+
+       return devino;
+}
+
+void sun4v_destroy_msi(unsigned int virt_irq)
+{
+       virt_irq_free(virt_irq);
+}
+#endif
+
 void ack_bad_irq(unsigned int virt_irq)
 {
        struct ino_bucket *bucket = virt_irq_to_bucket(virt_irq);