| From 9da52b385656f7aadbb7a819282652a0e1713393 Mon Sep 17 00:00:00 2001 |
| From: Phil Edworthy <phil.edworthy@renesas.com> |
| Date: Mon, 12 May 2014 11:57:49 +0100 |
| Subject: PCI: rcar: Add MSI support for PCIe |
| |
| 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> |
| (cherry picked from commit 290c1fb35860540223bef1be021944d02232889d) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| --- |
| drivers/pci/host/pcie-rcar.c | 242 ++++++++++++++++++++++++++++++++++++++++++- |
| 1 file changed, 241 insertions(+), 1 deletion(-) |
| |
| diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c |
| index 3c524b9e60e5..8e06124aa80f 100644 |
| --- a/drivers/pci/host/pcie-rcar.c |
| +++ b/drivers/pci/host/pcie-rcar.c |
| @@ -15,8 +15,11 @@ |
| #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 |
| @@ -43,6 +47,11 @@ |
| #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)) |
| @@ -97,6 +108,21 @@ |
| #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(®, 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; |
| -- |
| 2.1.2 |
| |