PCI: rcar: Add MSI support for PCIe
authorPhil Edworthy <phil.edworthy@renesas.com>
Mon, 12 May 2014 10:57:49 +0000 (11:57 +0100)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 28 May 2014 02:39:17 +0000 (20:39 -0600)
Add MSI support to the R-Car PCIe driver.

Signed-off-by: Phil Edworthy <phil.edworthy@renesas.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Lucas Stach <l.stach@pengutronix.de>
Acked-by: Simon Horman <horms+renesas@verge.net.au>
drivers/pci/host/pcie-rcar.c

index 3c524b9e60e52e3ea4eec0e60d803948c69bedf4..8e06124aa80fb99903352cb32e2622971631986f 100644 (file)
 #include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/msi.h>
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
 #include <linux/of_pci.h>
@@ -35,6 +38,7 @@
 #define PCIECDR                        0x000020
 #define PCIEMSR                        0x000028
 #define PCIEINTXR              0x000400
+#define PCIEMSITXR             0x000840
 
 /* Transfer control */
 #define PCIETCTLR              0x02000
 #define  DATA_LINK_ACTIVE      1
 #define PCIEERRFR              0x02020
 #define  UNSUPPORTED_REQUEST   (1 << 4)
+#define PCIEMSIFR              0x02044
+#define PCIEMSIALR             0x02048
+#define  MSIFE                 1
+#define PCIEMSIAUR             0x0204c
+#define PCIEMSIIER             0x02050
 
 /* root port address */
 #define PCIEPRAR(x)            (0x02080 + ((x) * 0x4))
@@ -85,6 +94,8 @@
 #define H1_PCIEPHYDOUTR                0x040014
 #define H1_PCIEPHYSR           0x040018
 
+#define INT_PCI_MSI_NR 32
+
 #define RCONF(x)       (PCICONF(0)+(x))
 #define RPMCAP(x)      (PMCAP(0)+(x))
 #define REXPCAP(x)     (EXPCAP(0)+(x))
 #define PCI_MAX_RESOURCES 4
 #define MAX_NR_INBOUND_MAPS 6
 
+struct rcar_msi {
+       DECLARE_BITMAP(used, INT_PCI_MSI_NR);
+       struct irq_domain *domain;
+       struct msi_chip chip;
+       unsigned long pages;
+       struct mutex lock;
+       int irq1;
+       int irq2;
+};
+
+static inline struct rcar_msi *to_rcar_msi(struct msi_chip *chip)
+{
+       return container_of(chip, struct rcar_msi, chip);
+}
+
 /* Structure representing the PCIe interface */
 struct rcar_pcie {
        struct device           *dev;
@@ -106,6 +132,7 @@ struct rcar_pcie {
        int                     root_bus_nr;
        struct clk              *clk;
        struct clk              *bus_clk;
+       struct                  rcar_msi msi;
 };
 
 static inline struct rcar_pcie *sys_to_pcie(struct pci_sys_data *sys)
@@ -356,10 +383,20 @@ static int rcar_pcie_setup(int nr, struct pci_sys_data *sys)
        return 1;
 }
 
+static void rcar_pcie_add_bus(struct pci_bus *bus)
+{
+       if (IS_ENABLED(CONFIG_PCI_MSI)) {
+               struct rcar_pcie *pcie = sys_to_pcie(bus->sysdata);
+
+               bus->msi = &pcie->msi.chip;
+       }
+}
+
 struct hw_pci rcar_pci = {
        .setup          = rcar_pcie_setup,
        .map_irq        = of_irq_parse_and_map_pci,
        .ops            = &rcar_pcie_ops,
+       .add_bus        = rcar_pcie_add_bus,
 };
 
 static void rcar_pcie_enable(struct rcar_pcie *pcie)
@@ -477,6 +514,10 @@ static int rcar_pcie_hw_init(struct rcar_pcie *pcie)
        /* Enable MAC data scrambling. */
        rcar_rmw32(pcie, MACCTLR, SCRAMBLE_DISABLE, 0);
 
+       /* Enable MSI */
+       if (IS_ENABLED(CONFIG_PCI_MSI))
+               pci_write_reg(pcie, 0x101f0000, PCIEMSITXR);
+
        /* Finish initialization - establish a PCI Express link */
        pci_write_reg(pcie, CFINIT, PCIETCTLR);
 
@@ -530,11 +571,184 @@ static int rcar_pcie_hw_init_h1(struct rcar_pcie *pcie)
        return -ETIMEDOUT;
 }
 
+static int rcar_msi_alloc(struct rcar_msi *chip)
+{
+       int msi;
+
+       mutex_lock(&chip->lock);
+
+       msi = find_first_zero_bit(chip->used, INT_PCI_MSI_NR);
+       if (msi < INT_PCI_MSI_NR)
+               set_bit(msi, chip->used);
+       else
+               msi = -ENOSPC;
+
+       mutex_unlock(&chip->lock);
+
+       return msi;
+}
+
+static void rcar_msi_free(struct rcar_msi *chip, unsigned long irq)
+{
+       mutex_lock(&chip->lock);
+       clear_bit(irq, chip->used);
+       mutex_unlock(&chip->lock);
+}
+
+static irqreturn_t rcar_pcie_msi_irq(int irq, void *data)
+{
+       struct rcar_pcie *pcie = data;
+       struct rcar_msi *msi = &pcie->msi;
+       unsigned long reg;
+
+       reg = pci_read_reg(pcie, PCIEMSIFR);
+
+       /* MSI & INTx share an interrupt - we only handle MSI here */
+       if (!reg)
+               return IRQ_NONE;
+
+       while (reg) {
+               unsigned int index = find_first_bit(&reg, 32);
+               unsigned int irq;
+
+               /* clear the interrupt */
+               pci_write_reg(pcie, 1 << index, PCIEMSIFR);
+
+               irq = irq_find_mapping(msi->domain, index);
+               if (irq) {
+                       if (test_bit(index, msi->used))
+                               generic_handle_irq(irq);
+                       else
+                               dev_info(pcie->dev, "unhandled MSI\n");
+               } else {
+                       /* Unknown MSI, just clear it */
+                       dev_dbg(pcie->dev, "unexpected MSI\n");
+               }
+
+               /* see if there's any more pending in this vector */
+               reg = pci_read_reg(pcie, PCIEMSIFR);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int rcar_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev,
+                             struct msi_desc *desc)
+{
+       struct rcar_msi *msi = to_rcar_msi(chip);
+       struct rcar_pcie *pcie = container_of(chip, struct rcar_pcie, msi.chip);
+       struct msi_msg msg;
+       unsigned int irq;
+       int hwirq;
+
+       hwirq = rcar_msi_alloc(msi);
+       if (hwirq < 0)
+               return hwirq;
+
+       irq = irq_create_mapping(msi->domain, hwirq);
+       if (!irq) {
+               rcar_msi_free(msi, hwirq);
+               return -EINVAL;
+       }
+
+       irq_set_msi_desc(irq, desc);
+
+       msg.address_lo = pci_read_reg(pcie, PCIEMSIALR) & ~MSIFE;
+       msg.address_hi = pci_read_reg(pcie, PCIEMSIAUR);
+       msg.data = hwirq;
+
+       write_msi_msg(irq, &msg);
+
+       return 0;
+}
+
+static void rcar_msi_teardown_irq(struct msi_chip *chip, unsigned int irq)
+{
+       struct rcar_msi *msi = to_rcar_msi(chip);
+       struct irq_data *d = irq_get_irq_data(irq);
+
+       rcar_msi_free(msi, d->hwirq);
+}
+
+static struct irq_chip rcar_msi_irq_chip = {
+       .name = "R-Car PCIe MSI",
+       .irq_enable = unmask_msi_irq,
+       .irq_disable = mask_msi_irq,
+       .irq_mask = mask_msi_irq,
+       .irq_unmask = unmask_msi_irq,
+};
+
+static int rcar_msi_map(struct irq_domain *domain, unsigned int irq,
+                       irq_hw_number_t hwirq)
+{
+       irq_set_chip_and_handler(irq, &rcar_msi_irq_chip, handle_simple_irq);
+       irq_set_chip_data(irq, domain->host_data);
+       set_irq_flags(irq, IRQF_VALID);
+
+       return 0;
+}
+
+static const struct irq_domain_ops msi_domain_ops = {
+       .map = rcar_msi_map,
+};
+
+static int rcar_pcie_enable_msi(struct rcar_pcie *pcie)
+{
+       struct platform_device *pdev = to_platform_device(pcie->dev);
+       struct rcar_msi *msi = &pcie->msi;
+       unsigned long base;
+       int err;
+
+       mutex_init(&msi->lock);
+
+       msi->chip.dev = pcie->dev;
+       msi->chip.setup_irq = rcar_msi_setup_irq;
+       msi->chip.teardown_irq = rcar_msi_teardown_irq;
+
+       msi->domain = irq_domain_add_linear(pcie->dev->of_node, INT_PCI_MSI_NR,
+                                           &msi_domain_ops, &msi->chip);
+       if (!msi->domain) {
+               dev_err(&pdev->dev, "failed to create IRQ domain\n");
+               return -ENOMEM;
+       }
+
+       /* Two irqs are for MSI, but they are also used for non-MSI irqs */
+       err = devm_request_irq(&pdev->dev, msi->irq1, rcar_pcie_msi_irq,
+                              IRQF_SHARED, rcar_msi_irq_chip.name, pcie);
+       if (err < 0) {
+               dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
+               goto err;
+       }
+
+       err = devm_request_irq(&pdev->dev, msi->irq2, rcar_pcie_msi_irq,
+                              IRQF_SHARED, rcar_msi_irq_chip.name, pcie);
+       if (err < 0) {
+               dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
+               goto err;
+       }
+
+       /* setup MSI data target */
+       msi->pages = __get_free_pages(GFP_KERNEL, 0);
+       base = virt_to_phys((void *)msi->pages);
+
+       pci_write_reg(pcie, base | MSIFE, PCIEMSIALR);
+       pci_write_reg(pcie, 0, PCIEMSIAUR);
+
+       /* enable all MSI interrupts */
+       pci_write_reg(pcie, 0xffffffff, PCIEMSIIER);
+
+       return 0;
+
+err:
+       irq_domain_remove(msi->domain);
+       return err;
+}
+
 static int rcar_pcie_get_resources(struct platform_device *pdev,
                                   struct rcar_pcie *pcie)
 {
        struct resource res;
-       int err;
+       int err, i;
 
        err = of_address_to_resource(pdev->dev.of_node, 0, &res);
        if (err)
@@ -559,6 +773,22 @@ static int rcar_pcie_get_resources(struct platform_device *pdev,
        if (err)
                goto err_map_reg;
 
+       i = irq_of_parse_and_map(pdev->dev.of_node, 0);
+       if (i < 0) {
+               dev_err(pcie->dev, "cannot get platform resources for msi interrupt\n");
+               err = -ENOENT;
+               goto err_map_reg;
+       }
+       pcie->msi.irq1 = i;
+
+       i = irq_of_parse_and_map(pdev->dev.of_node, 1);
+       if (i < 0) {
+               dev_err(pcie->dev, "cannot get platform resources for msi interrupt\n");
+               err = -ENOENT;
+               goto err_map_reg;
+       }
+       pcie->msi.irq2 = i;
+
        pcie->base = devm_ioremap_resource(&pdev->dev, &res);
        if (IS_ERR(pcie->base)) {
                err = PTR_ERR(pcie->base);
@@ -732,6 +962,16 @@ static int rcar_pcie_probe(struct platform_device *pdev)
         if (err)
                return err;
 
+       if (IS_ENABLED(CONFIG_PCI_MSI)) {
+               err = rcar_pcie_enable_msi(pcie);
+               if (err < 0) {
+                       dev_err(&pdev->dev,
+                               "failed to enable MSI support: %d\n",
+                               err);
+                       return err;
+               }
+       }
+
        of_id = of_match_device(rcar_pcie_of_match, pcie->dev);
        if (!of_id || !of_id->data)
                return -EINVAL;