MIPS: Octeon: Support 256 MSI on PCIe
authorDavid Daney <ddaney@caviumnetworks.com>
Tue, 27 Jul 2010 01:14:15 +0000 (18:14 -0700)
committerRalf Baechle <ralf@linux-mips.org>
Thu, 5 Aug 2010 12:26:27 +0000 (13:26 +0100)
Signed-off-by: David Daney <ddaney@caviumnetworks.com>
To: linux-mips@linux-mips.org
Patchwork: http://patchwork.linux-mips.org/patch/1507/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/include/asm/mach-cavium-octeon/irq.h
arch/mips/pci/msi-octeon.c

index 783dae74722385995bee257f86efc1365f1d7caa..6ddab8aef64478984164e372d5463bd18b5116c6 100644 (file)
 #ifdef CONFIG_PCI_MSI
 /* 152 - 215 represent the MSI interrupts 0-63 */
 #define OCTEON_IRQ_MSI_BIT0    152
-#define OCTEON_IRQ_MSI_LAST    (OCTEON_IRQ_MSI_BIT0 + 63)
+#define OCTEON_IRQ_MSI_LAST    (OCTEON_IRQ_MSI_BIT0 + 255)
 
 #define OCTEON_IRQ_LAST                (OCTEON_IRQ_MSI_LAST + 1)
 #else
index 83ceb52d6e0e8d39d0fcda7b7f2cb2b39f7a79c7..7c756408b85dc7759df5cb2b6c5e0a7fe90a1683 100644 (file)
@@ -3,7 +3,7 @@
  * License.  See the file "COPYING" in the main directory of this archive
  * for more details.
  *
- * Copyright (C) 2005-2009 Cavium Networks
+ * Copyright (C) 2005-2009, 2010 Cavium Networks
  */
 #include <linux/kernel.h>
 #include <linux/init.h>
@@ -22,7 +22,7 @@
  * Each bit in msi_free_irq_bitmask represents a MSI interrupt that is
  * in use.
  */
-static uint64_t msi_free_irq_bitmask;
+static u64 msi_free_irq_bitmask[4];
 
 /*
  * Each bit in msi_multiple_irq_bitmask tells that the device using
@@ -30,7 +30,7 @@ static uint64_t msi_free_irq_bitmask;
  * is used so we can disable all of the MSI interrupts when a device
  * uses multiple.
  */
-static uint64_t msi_multiple_irq_bitmask;
+static u64 msi_multiple_irq_bitmask[4];
 
 /*
  * This lock controls updates to msi_free_irq_bitmask and
@@ -38,6 +38,11 @@ static uint64_t msi_multiple_irq_bitmask;
  */
 static DEFINE_SPINLOCK(msi_free_irq_bitmask_lock);
 
+/*
+ * Number of MSI IRQs used. This variable is set up in
+ * the module init time.
+ */
+static int msi_irq_size;
 
 /**
  * Called when a driver request MSI interrupts instead of the
@@ -54,12 +59,13 @@ static DEFINE_SPINLOCK(msi_free_irq_bitmask_lock);
 int arch_setup_msi_irq(struct pci_dev *dev, struct msi_desc *desc)
 {
        struct msi_msg msg;
-       uint16_t control;
+       u16 control;
        int configured_private_bits;
        int request_private_bits;
-       int irq;
+       int irq = 0;
        int irq_step;
-       uint64_t search_mask;
+       u64 search_mask;
+       int index;
 
        /*
         * Read the MSI config to figure out how many IRQs this device
@@ -111,29 +117,31 @@ try_only_one:
         * use.
         */
        spin_lock(&msi_free_irq_bitmask_lock);
-       for (irq = 0; irq < 64; irq += irq_step) {
-               if ((msi_free_irq_bitmask & (search_mask << irq)) == 0) {
-                       msi_free_irq_bitmask |= search_mask << irq;
-                       msi_multiple_irq_bitmask |= (search_mask >> 1) << irq;
-                       break;
+       for (index = 0; index < msi_irq_size/64; index++) {
+               for (irq = 0; irq < 64; irq += irq_step) {
+                       if ((msi_free_irq_bitmask[index] & (search_mask << irq)) == 0) {
+                               msi_free_irq_bitmask[index] |= search_mask << irq;
+                               msi_multiple_irq_bitmask[index] |= (search_mask >> 1) << irq;
+                               goto msi_irq_allocated;
+                       }
                }
        }
+msi_irq_allocated:
        spin_unlock(&msi_free_irq_bitmask_lock);
 
        /* Make sure the search for available interrupts didn't fail */
        if (irq >= 64) {
                if (request_private_bits) {
-                       pr_err("arch_setup_msi_irq: Unable to find %d free "
-                              "interrupts, trying just one",
+                       pr_err("arch_setup_msi_irq: Unable to find %d free interrupts, trying just one",
                               1 << request_private_bits);
                        request_private_bits = 0;
                        goto try_only_one;
                } else
-                       panic("arch_setup_msi_irq: Unable to find a free MSI "
-                             "interrupt");
+                       panic("arch_setup_msi_irq: Unable to find a free MSI interrupt");
        }
 
        /* MSI interrupts start at logical IRQ OCTEON_IRQ_MSI_BIT0 */
+       irq += index*64;
        irq += OCTEON_IRQ_MSI_BIT0;
 
        switch (octeon_dma_bar_type) {
@@ -179,12 +187,18 @@ try_only_one:
 void arch_teardown_msi_irq(unsigned int irq)
 {
        int number_irqs;
-       uint64_t bitmask;
+       u64 bitmask;
+       int index = 0;
+       int irq0;
 
-       if ((irq < OCTEON_IRQ_MSI_BIT0) || (irq > OCTEON_IRQ_MSI_LAST))
+       if ((irq < OCTEON_IRQ_MSI_BIT0)
+               || (irq > msi_irq_size + OCTEON_IRQ_MSI_BIT0))
                panic("arch_teardown_msi_irq: Attempted to teardown illegal "
                      "MSI interrupt (%d)", irq);
+
        irq -= OCTEON_IRQ_MSI_BIT0;
+       index = irq / 64;
+       irq0 = irq % 64;
 
        /*
         * Count the number of IRQs we need to free by looking at the
@@ -192,151 +206,197 @@ void arch_teardown_msi_irq(unsigned int irq)
         * IRQ is also owned by this device.
         */
        number_irqs = 0;
-       while ((irq+number_irqs < 64) &&
-              (msi_multiple_irq_bitmask & (1ull << (irq + number_irqs))))
+       while ((irq0 + number_irqs < 64) &&
+              (msi_multiple_irq_bitmask[index]
+               & (1ull << (irq0 + number_irqs))))
                number_irqs++;
        number_irqs++;
        /* Mask with one bit for each IRQ */
        bitmask = (1 << number_irqs) - 1;
        /* Shift the mask to the correct bit location */
-       bitmask <<= irq;
-       if ((msi_free_irq_bitmask & bitmask) != bitmask)
+       bitmask <<= irq0;
+       if ((msi_free_irq_bitmask[index] & bitmask) != bitmask)
                panic("arch_teardown_msi_irq: Attempted to teardown MSI "
                      "interrupt (%d) not in use", irq);
 
        /* Checks are done, update the in use bitmask */
        spin_lock(&msi_free_irq_bitmask_lock);
-       msi_free_irq_bitmask &= ~bitmask;
-       msi_multiple_irq_bitmask &= ~bitmask;
+       msi_free_irq_bitmask[index] &= ~bitmask;
+       msi_multiple_irq_bitmask[index] &= ~bitmask;
        spin_unlock(&msi_free_irq_bitmask_lock);
 }
 
+static DEFINE_RAW_SPINLOCK(octeon_irq_msi_lock);
 
-/*
- * Called by the interrupt handling code when an MSI interrupt
- * occurs.
- */
-static irqreturn_t octeon_msi_interrupt(int cpl, void *dev_id)
+static u64 msi_rcv_reg[4];
+static u64 mis_ena_reg[4];
+
+static void octeon_irq_msi_enable_pcie(unsigned int irq)
 {
-       uint64_t msi_bits;
-       int irq;
+       u64 en;
+       unsigned long flags;
+       int msi_number = irq - OCTEON_IRQ_MSI_BIT0;
+       int irq_index = msi_number >> 6;
+       int irq_bit = msi_number & 0x3f;
+
+       raw_spin_lock_irqsave(&octeon_irq_msi_lock, flags);
+       en = cvmx_read_csr(mis_ena_reg[irq_index]);
+       en |= 1ull << irq_bit;
+       cvmx_write_csr(mis_ena_reg[irq_index], en);
+       cvmx_read_csr(mis_ena_reg[irq_index]);
+       raw_spin_unlock_irqrestore(&octeon_irq_msi_lock, flags);
+}
 
-       if (octeon_dma_bar_type == OCTEON_DMA_BAR_TYPE_PCIE)
-               msi_bits = cvmx_read_csr(CVMX_PEXP_NPEI_MSI_RCV0);
-       else
-               msi_bits = cvmx_read_csr(CVMX_NPI_NPI_MSI_RCV);
-       irq = fls64(msi_bits);
-       if (irq) {
-               irq += OCTEON_IRQ_MSI_BIT0 - 1;
-               if (octeon_has_feature(OCTEON_FEATURE_PCIE)) {
-                       /* These chips have PCIe */
-                       cvmx_write_csr(CVMX_PEXP_NPEI_MSI_RCV0,
-                                      1ull << (irq - OCTEON_IRQ_MSI_BIT0));
-               } else {
-                       /* These chips have PCI */
-                       cvmx_write_csr(CVMX_NPI_NPI_MSI_RCV,
-                                      1ull << (irq - OCTEON_IRQ_MSI_BIT0));
-               }
-               if (irq_desc[irq].action) {
-                       do_IRQ(irq);
-                       return IRQ_HANDLED;
-               } else {
-                       pr_err("Spurious MSI interrupt %d\n", irq);
-               }
-       }
-       return IRQ_NONE;
+static void octeon_irq_msi_disable_pcie(unsigned int irq)
+{
+       u64 en;
+       unsigned long flags;
+       int msi_number = irq - OCTEON_IRQ_MSI_BIT0;
+       int irq_index = msi_number >> 6;
+       int irq_bit = msi_number & 0x3f;
+
+       raw_spin_lock_irqsave(&octeon_irq_msi_lock, flags);
+       en = cvmx_read_csr(mis_ena_reg[irq_index]);
+       en &= ~(1ull << irq_bit);
+       cvmx_write_csr(mis_ena_reg[irq_index], en);
+       cvmx_read_csr(mis_ena_reg[irq_index]);
+       raw_spin_unlock_irqrestore(&octeon_irq_msi_lock, flags);
 }
 
-static DEFINE_RAW_SPINLOCK(octeon_irq_msi_lock);
+static struct irq_chip octeon_irq_chip_msi_pcie = {
+       .name = "MSI",
+       .enable = octeon_irq_msi_enable_pcie,
+       .disable = octeon_irq_msi_disable_pcie,
+};
 
-static void octeon_irq_msi_enable(unsigned int irq)
+static void octeon_irq_msi_enable_pci(unsigned int irq)
 {
-       if (!octeon_has_feature(OCTEON_FEATURE_PCIE)) {
-               /*
-                * Octeon PCI doesn't have the ability to mask/unmask
-                * MSI interrupts individually.  Instead of
-                * masking/unmasking them in groups of 16, we simple
-                * assume MSI devices are well behaved.  MSI
-                * interrupts are always enable and the ACK is assumed
-                * to be enough.
-                */
-       } else {
-               /* These chips have PCIe.  Note that we only support
-                * the first 64 MSI interrupts.  Unfortunately all the
-                * MSI enables are in the same register.  We use
-                * MSI0's lock to control access to them all.
-                */
-               uint64_t en;
-               unsigned long flags;
-               raw_spin_lock_irqsave(&octeon_irq_msi_lock, flags);
-               en = cvmx_read_csr(CVMX_PEXP_NPEI_MSI_ENB0);
-               en |= 1ull << (irq - OCTEON_IRQ_MSI_BIT0);
-               cvmx_write_csr(CVMX_PEXP_NPEI_MSI_ENB0, en);
-               cvmx_read_csr(CVMX_PEXP_NPEI_MSI_ENB0);
-               raw_spin_unlock_irqrestore(&octeon_irq_msi_lock, flags);
-       }
+       /*
+        * Octeon PCI doesn't have the ability to mask/unmask MSI
+        * interrupts individually. Instead of masking/unmasking them
+        * in groups of 16, we simple assume MSI devices are well
+        * behaved. MSI interrupts are always enable and the ACK is
+        * assumed to be enough
+        */
 }
 
-static void octeon_irq_msi_disable(unsigned int irq)
+static void octeon_irq_msi_disable_pci(unsigned int irq)
 {
-       if (!octeon_has_feature(OCTEON_FEATURE_PCIE)) {
-               /* See comment in enable */
-       } else {
-               /*
-                * These chips have PCIe.  Note that we only support
-                * the first 64 MSI interrupts.  Unfortunately all the
-                * MSI enables are in the same register.  We use
-                * MSI0's lock to control access to them all.
-                */
-               uint64_t en;
-               unsigned long flags;
-               raw_spin_lock_irqsave(&octeon_irq_msi_lock, flags);
-               en = cvmx_read_csr(CVMX_PEXP_NPEI_MSI_ENB0);
-               en &= ~(1ull << (irq - OCTEON_IRQ_MSI_BIT0));
-               cvmx_write_csr(CVMX_PEXP_NPEI_MSI_ENB0, en);
-               cvmx_read_csr(CVMX_PEXP_NPEI_MSI_ENB0);
-               raw_spin_unlock_irqrestore(&octeon_irq_msi_lock, flags);
-       }
+       /* See comment in enable */
 }
 
-static struct irq_chip octeon_irq_chip_msi = {
+static struct irq_chip octeon_irq_chip_msi_pci = {
        .name = "MSI",
-       .enable = octeon_irq_msi_enable,
-       .disable = octeon_irq_msi_disable,
+       .enable = octeon_irq_msi_enable_pci,
+       .disable = octeon_irq_msi_disable_pci,
 };
 
 /*
- * Initializes the MSI interrupt handling code
+ * Called by the interrupt handling code when an MSI interrupt
+ * occurs.
  */
-static int __init octeon_msi_initialize(void)
+static irqreturn_t __octeon_msi_do_interrupt(int index, u64 msi_bits)
 {
        int irq;
+       int bit;
 
-       for (irq = OCTEON_IRQ_MSI_BIT0; irq <= OCTEON_IRQ_MSI_LAST; irq++) {
-               set_irq_chip_and_handler(irq, &octeon_irq_chip_msi, handle_simple_irq);
+       bit = fls64(msi_bits);
+       if (bit) {
+               bit--;
+               /* Acknowledge it first. */
+               cvmx_write_csr(msi_rcv_reg[index], 1ull << bit);
+
+               irq = bit + OCTEON_IRQ_MSI_BIT0 + 64 * index;
+               do_IRQ(irq);
+               return IRQ_HANDLED;
        }
+       return IRQ_NONE;
+}
+
+#define OCTEON_MSI_INT_HANDLER_X(x)                                    \
+static irqreturn_t octeon_msi_interrupt##x(int cpl, void *dev_id)      \
+{                                                                      \
+       u64 msi_bits = cvmx_read_csr(msi_rcv_reg[(x)]);                 \
+       return __octeon_msi_do_interrupt((x), msi_bits);                \
+}
+
+/*
+ * Create octeon_msi_interrupt{0-3} function body
+ */
+OCTEON_MSI_INT_HANDLER_X(0);
+OCTEON_MSI_INT_HANDLER_X(1);
+OCTEON_MSI_INT_HANDLER_X(2);
+OCTEON_MSI_INT_HANDLER_X(3);
+
+/*
+ * Initializes the MSI interrupt handling code
+ */
+int __init octeon_msi_initialize(void)
+{
+       int irq;
+       struct irq_chip *msi;
+
+       if (octeon_dma_bar_type == OCTEON_DMA_BAR_TYPE_PCIE) {
+               msi_rcv_reg[0] = CVMX_PEXP_NPEI_MSI_RCV0;
+               msi_rcv_reg[1] = CVMX_PEXP_NPEI_MSI_RCV1;
+               msi_rcv_reg[2] = CVMX_PEXP_NPEI_MSI_RCV2;
+               msi_rcv_reg[3] = CVMX_PEXP_NPEI_MSI_RCV3;
+               mis_ena_reg[0] = CVMX_PEXP_NPEI_MSI_ENB0;
+               mis_ena_reg[1] = CVMX_PEXP_NPEI_MSI_ENB1;
+               mis_ena_reg[2] = CVMX_PEXP_NPEI_MSI_ENB2;
+               mis_ena_reg[3] = CVMX_PEXP_NPEI_MSI_ENB3;
+               msi = &octeon_irq_chip_msi_pcie;
+       } else {
+               msi_rcv_reg[0] = CVMX_NPI_NPI_MSI_RCV;
+#define INVALID_GENERATE_ADE 0x8700000000000000ULL;
+               msi_rcv_reg[1] = INVALID_GENERATE_ADE;
+               msi_rcv_reg[2] = INVALID_GENERATE_ADE;
+               msi_rcv_reg[3] = INVALID_GENERATE_ADE;
+               mis_ena_reg[0] = INVALID_GENERATE_ADE;
+               mis_ena_reg[1] = INVALID_GENERATE_ADE;
+               mis_ena_reg[2] = INVALID_GENERATE_ADE;
+               mis_ena_reg[3] = INVALID_GENERATE_ADE;
+               msi = &octeon_irq_chip_msi_pci;
+       }
+
+       for (irq = OCTEON_IRQ_MSI_BIT0; irq <= OCTEON_IRQ_MSI_LAST; irq++)
+               set_irq_chip_and_handler(irq, msi, handle_simple_irq);
 
        if (octeon_has_feature(OCTEON_FEATURE_PCIE)) {
-               if (request_irq(OCTEON_IRQ_PCI_MSI0, octeon_msi_interrupt,
-                               0, "MSI[0:63]", octeon_msi_interrupt))
+               if (request_irq(OCTEON_IRQ_PCI_MSI0, octeon_msi_interrupt0,
+                               0, "MSI[0:63]", octeon_msi_interrupt0))
                        panic("request_irq(OCTEON_IRQ_PCI_MSI0) failed");
+
+               if (request_irq(OCTEON_IRQ_PCI_MSI1, octeon_msi_interrupt1,
+                               0, "MSI[64:127]", octeon_msi_interrupt1))
+                       panic("request_irq(OCTEON_IRQ_PCI_MSI1) failed");
+
+               if (request_irq(OCTEON_IRQ_PCI_MSI2, octeon_msi_interrupt2,
+                               0, "MSI[127:191]", octeon_msi_interrupt2))
+                       panic("request_irq(OCTEON_IRQ_PCI_MSI2) failed");
+
+               if (request_irq(OCTEON_IRQ_PCI_MSI3, octeon_msi_interrupt3,
+                               0, "MSI[192:255]", octeon_msi_interrupt3))
+                       panic("request_irq(OCTEON_IRQ_PCI_MSI3) failed");
+
+               msi_irq_size = 256;
        } else if (octeon_is_pci_host()) {
-               if (request_irq(OCTEON_IRQ_PCI_MSI0, octeon_msi_interrupt,
-                               0, "MSI[0:15]", octeon_msi_interrupt))
+               if (request_irq(OCTEON_IRQ_PCI_MSI0, octeon_msi_interrupt0,
+                               0, "MSI[0:15]", octeon_msi_interrupt0))
                        panic("request_irq(OCTEON_IRQ_PCI_MSI0) failed");
 
-               if (request_irq(OCTEON_IRQ_PCI_MSI1, octeon_msi_interrupt,
-                               0, "MSI[16:31]", octeon_msi_interrupt))
+               if (request_irq(OCTEON_IRQ_PCI_MSI1, octeon_msi_interrupt0,
+                               0, "MSI[16:31]", octeon_msi_interrupt0))
                        panic("request_irq(OCTEON_IRQ_PCI_MSI1) failed");
 
-               if (request_irq(OCTEON_IRQ_PCI_MSI2, octeon_msi_interrupt,
-                               0, "MSI[32:47]", octeon_msi_interrupt))
+               if (request_irq(OCTEON_IRQ_PCI_MSI2, octeon_msi_interrupt0,
+                               0, "MSI[32:47]", octeon_msi_interrupt0))
                        panic("request_irq(OCTEON_IRQ_PCI_MSI2) failed");
 
-               if (request_irq(OCTEON_IRQ_PCI_MSI3, octeon_msi_interrupt,
-                               0, "MSI[48:63]", octeon_msi_interrupt))
+               if (request_irq(OCTEON_IRQ_PCI_MSI3, octeon_msi_interrupt0,
+                               0, "MSI[48:63]", octeon_msi_interrupt0))
                        panic("request_irq(OCTEON_IRQ_PCI_MSI3) failed");
-
+               msi_irq_size = 64;
        }
        return 0;
 }