diff options
Diffstat (limited to 'target/linux/bcm27xx/patches-5.4/950-0448-PCI-brcmstb-Add-MSI-support.patch')
-rw-r--r-- | target/linux/bcm27xx/patches-5.4/950-0448-PCI-brcmstb-Add-MSI-support.patch | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/target/linux/bcm27xx/patches-5.4/950-0448-PCI-brcmstb-Add-MSI-support.patch b/target/linux/bcm27xx/patches-5.4/950-0448-PCI-brcmstb-Add-MSI-support.patch new file mode 100644 index 0000000000..a27259bd19 --- /dev/null +++ b/target/linux/bcm27xx/patches-5.4/950-0448-PCI-brcmstb-Add-MSI-support.patch @@ -0,0 +1,383 @@ +From 1a90ecdfae1c0cf1b242276f6f0e3d98b5877f14 Mon Sep 17 00:00:00 2001 +From: Jim Quinlan <james.quinlan@broadcom.com> +Date: Mon, 16 Dec 2019 12:01:10 +0100 +Subject: [PATCH] PCI: brcmstb: Add MSI support + +commit 40ca1bf580ef24df30702032ba5e40dfdcaa200b upstream. + +This adds MSI support to the Broadcom STB PCIe host controller. The MSI +controller is physically located within the PCIe block, however, there +is no reason why the MSI controller could not be moved elsewhere in the +future. MSIX is not supported by the HW. + +Since the internal Brcmstb MSI controller is intertwined with the PCIe +controller, it is not its own platform device but rather part of the +PCIe platform device. + +Signed-off-by: Jim Quinlan <james.quinlan@broadcom.com> +Co-developed-by: Nicolas Saenz Julienne <nsaenzjulienne@suse.de> +Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne@suse.de> +Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> +Reviewed-by: Marc Zyngier <maz@kernel.org> +Reviewed-by: Andrew Murray <andrew.murray@arm.com> +--- + drivers/pci/controller/Kconfig | 1 + + drivers/pci/controller/pcie-brcmstb.c | 262 +++++++++++++++++++++++++- + 2 files changed, 262 insertions(+), 1 deletion(-) + +--- a/drivers/pci/controller/Kconfig ++++ b/drivers/pci/controller/Kconfig +@@ -285,6 +285,7 @@ config PCIE_BRCMSTB + tristate "Broadcom Brcmstb PCIe host controller" + depends on ARCH_BCM2835 || COMPILE_TEST + depends on OF ++ depends on PCI_MSI_IRQ_DOMAIN + help + Say Y here to enable PCIe host controller support for + Broadcom STB based SoCs, like the Raspberry Pi 4. +--- a/drivers/pci/controller/pcie-brcmstb.c ++++ b/drivers/pci/controller/pcie-brcmstb.c +@@ -2,6 +2,7 @@ + /* Copyright (C) 2009 - 2019 Broadcom */ + + #include <linux/bitfield.h> ++#include <linux/bitops.h> + #include <linux/clk.h> + #include <linux/compiler.h> + #include <linux/delay.h> +@@ -9,11 +10,13 @@ + #include <linux/interrupt.h> + #include <linux/io.h> + #include <linux/ioport.h> ++#include <linux/irqchip/chained_irq.h> + #include <linux/irqdomain.h> + #include <linux/kernel.h> + #include <linux/list.h> + #include <linux/log2.h> + #include <linux/module.h> ++#include <linux/msi.h> + #include <linux/of_address.h> + #include <linux/of_irq.h> + #include <linux/of_pci.h> +@@ -67,6 +70,12 @@ + #define PCIE_MISC_RC_BAR3_CONFIG_LO 0x403c + #define PCIE_MISC_RC_BAR3_CONFIG_LO_SIZE_MASK 0x1f + ++#define PCIE_MISC_MSI_BAR_CONFIG_LO 0x4044 ++#define PCIE_MISC_MSI_BAR_CONFIG_HI 0x4048 ++ ++#define PCIE_MISC_MSI_DATA_CONFIG 0x404c ++#define PCIE_MISC_MSI_DATA_CONFIG_VAL 0xffe06540 ++ + #define PCIE_MISC_PCIE_CTRL 0x4064 + #define PCIE_MISC_PCIE_CTRL_PCIE_L23_REQUEST_MASK 0x1 + +@@ -114,6 +123,11 @@ + + /* PCIe parameters */ + #define BRCM_NUM_PCIE_OUT_WINS 0x4 ++#define BRCM_INT_PCI_MSI_NR 32 ++ ++/* MSI target adresses */ ++#define BRCM_MSI_TARGET_ADDR_LT_4GB 0x0fffffffcULL ++#define BRCM_MSI_TARGET_ADDR_GT_4GB 0xffffffffcULL + + /* MDIO registers */ + #define MDIO_PORT0 0x0 +@@ -135,6 +149,19 @@ + #define SSC_STATUS_SSC_MASK 0x400 + #define SSC_STATUS_PLL_LOCK_MASK 0x800 + ++struct brcm_msi { ++ struct device *dev; ++ void __iomem *base; ++ struct device_node *np; ++ struct irq_domain *msi_domain; ++ struct irq_domain *inner_domain; ++ struct mutex lock; /* guards the alloc/free operations */ ++ u64 target_addr; ++ int irq; ++ /* used indicates which MSI interrupts have been alloc'd */ ++ unsigned long used; ++}; ++ + /* Internal PCIe Host Controller Information.*/ + struct brcm_pcie { + struct device *dev; +@@ -144,6 +171,8 @@ struct brcm_pcie { + struct device_node *np; + bool ssc; + int gen; ++ u64 msi_target_addr; ++ struct brcm_msi *msi; + }; + + /* +@@ -309,6 +338,215 @@ static void brcm_pcie_set_outbound_win(s + writel(tmp, pcie->base + PCIE_MEM_WIN0_LIMIT_HI(win)); + } + ++static struct irq_chip brcm_msi_irq_chip = { ++ .name = "BRCM STB PCIe MSI", ++ .irq_ack = irq_chip_ack_parent, ++ .irq_mask = pci_msi_mask_irq, ++ .irq_unmask = pci_msi_unmask_irq, ++}; ++ ++static struct msi_domain_info brcm_msi_domain_info = { ++ /* Multi MSI is supported by the controller, but not by this driver */ ++ .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), ++ .chip = &brcm_msi_irq_chip, ++}; ++ ++static void brcm_pcie_msi_isr(struct irq_desc *desc) ++{ ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ unsigned long status, virq; ++ struct brcm_msi *msi; ++ struct device *dev; ++ u32 bit; ++ ++ chained_irq_enter(chip, desc); ++ msi = irq_desc_get_handler_data(desc); ++ dev = msi->dev; ++ ++ status = readl(msi->base + PCIE_MSI_INTR2_STATUS); ++ for_each_set_bit(bit, &status, BRCM_INT_PCI_MSI_NR) { ++ virq = irq_find_mapping(msi->inner_domain, bit); ++ if (virq) ++ generic_handle_irq(virq); ++ else ++ dev_dbg(dev, "unexpected MSI\n"); ++ } ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void brcm_msi_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ++{ ++ struct brcm_msi *msi = irq_data_get_irq_chip_data(data); ++ ++ msg->address_lo = lower_32_bits(msi->target_addr); ++ msg->address_hi = upper_32_bits(msi->target_addr); ++ msg->data = (0xffff & PCIE_MISC_MSI_DATA_CONFIG_VAL) | data->hwirq; ++} ++ ++static int brcm_msi_set_affinity(struct irq_data *irq_data, ++ const struct cpumask *mask, bool force) ++{ ++ return -EINVAL; ++} ++ ++static void brcm_msi_ack_irq(struct irq_data *data) ++{ ++ struct brcm_msi *msi = irq_data_get_irq_chip_data(data); ++ ++ writel(1 << data->hwirq, msi->base + PCIE_MSI_INTR2_CLR); ++} ++ ++ ++static struct irq_chip brcm_msi_bottom_irq_chip = { ++ .name = "BRCM STB MSI", ++ .irq_compose_msi_msg = brcm_msi_compose_msi_msg, ++ .irq_set_affinity = brcm_msi_set_affinity, ++ .irq_ack = brcm_msi_ack_irq, ++}; ++ ++static int brcm_msi_alloc(struct brcm_msi *msi) ++{ ++ int hwirq; ++ ++ mutex_lock(&msi->lock); ++ hwirq = bitmap_find_free_region(&msi->used, BRCM_INT_PCI_MSI_NR, 0); ++ mutex_unlock(&msi->lock); ++ ++ return hwirq; ++} ++ ++static void brcm_msi_free(struct brcm_msi *msi, unsigned long hwirq) ++{ ++ mutex_lock(&msi->lock); ++ bitmap_release_region(&msi->used, hwirq, 0); ++ mutex_unlock(&msi->lock); ++} ++ ++static int brcm_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *args) ++{ ++ struct brcm_msi *msi = domain->host_data; ++ int hwirq; ++ ++ hwirq = brcm_msi_alloc(msi); ++ ++ if (hwirq < 0) ++ return hwirq; ++ ++ irq_domain_set_info(domain, virq, (irq_hw_number_t)hwirq, ++ &brcm_msi_bottom_irq_chip, domain->host_data, ++ handle_edge_irq, NULL, NULL); ++ return 0; ++} ++ ++static void brcm_irq_domain_free(struct irq_domain *domain, ++ unsigned int virq, unsigned int nr_irqs) ++{ ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq); ++ struct brcm_msi *msi = irq_data_get_irq_chip_data(d); ++ ++ brcm_msi_free(msi, d->hwirq); ++} ++ ++static const struct irq_domain_ops msi_domain_ops = { ++ .alloc = brcm_irq_domain_alloc, ++ .free = brcm_irq_domain_free, ++}; ++ ++static int brcm_allocate_domains(struct brcm_msi *msi) ++{ ++ struct fwnode_handle *fwnode = of_node_to_fwnode(msi->np); ++ struct device *dev = msi->dev; ++ ++ msi->inner_domain = irq_domain_add_linear(NULL, BRCM_INT_PCI_MSI_NR, ++ &msi_domain_ops, msi); ++ if (!msi->inner_domain) { ++ dev_err(dev, "failed to create IRQ domain\n"); ++ return -ENOMEM; ++ } ++ ++ msi->msi_domain = pci_msi_create_irq_domain(fwnode, ++ &brcm_msi_domain_info, ++ msi->inner_domain); ++ if (!msi->msi_domain) { ++ dev_err(dev, "failed to create MSI domain\n"); ++ irq_domain_remove(msi->inner_domain); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static void brcm_free_domains(struct brcm_msi *msi) ++{ ++ irq_domain_remove(msi->msi_domain); ++ irq_domain_remove(msi->inner_domain); ++} ++ ++static void brcm_msi_remove(struct brcm_pcie *pcie) ++{ ++ struct brcm_msi *msi = pcie->msi; ++ ++ if (!msi) ++ return; ++ irq_set_chained_handler(msi->irq, NULL); ++ irq_set_handler_data(msi->irq, NULL); ++ brcm_free_domains(msi); ++} ++ ++static void brcm_msi_set_regs(struct brcm_msi *msi) ++{ ++ writel(0xffffffff, msi->base + PCIE_MSI_INTR2_MASK_CLR); ++ ++ /* ++ * The 0 bit of PCIE_MISC_MSI_BAR_CONFIG_LO is repurposed to MSI ++ * enable, which we set to 1. ++ */ ++ writel(lower_32_bits(msi->target_addr) | 0x1, ++ msi->base + PCIE_MISC_MSI_BAR_CONFIG_LO); ++ writel(upper_32_bits(msi->target_addr), ++ msi->base + PCIE_MISC_MSI_BAR_CONFIG_HI); ++ ++ writel(PCIE_MISC_MSI_DATA_CONFIG_VAL, ++ msi->base + PCIE_MISC_MSI_DATA_CONFIG); ++} ++ ++static int brcm_pcie_enable_msi(struct brcm_pcie *pcie) ++{ ++ struct brcm_msi *msi; ++ int irq, ret; ++ struct device *dev = pcie->dev; ++ ++ irq = irq_of_parse_and_map(dev->of_node, 1); ++ if (irq <= 0) { ++ dev_err(dev, "cannot map MSI interrupt\n"); ++ return -ENODEV; ++ } ++ ++ msi = devm_kzalloc(dev, sizeof(struct brcm_msi), GFP_KERNEL); ++ if (!msi) ++ return -ENOMEM; ++ ++ mutex_init(&msi->lock); ++ msi->dev = dev; ++ msi->base = pcie->base; ++ msi->np = pcie->np; ++ msi->target_addr = pcie->msi_target_addr; ++ msi->irq = irq; ++ ++ ret = brcm_allocate_domains(msi); ++ if (ret) ++ return ret; ++ ++ irq_set_chained_handler_and_data(msi->irq, brcm_pcie_msi_isr, msi); ++ ++ brcm_msi_set_regs(msi); ++ pcie->msi = msi; ++ ++ return 0; ++} ++ + /* The controller is capable of serving in both RC and EP roles */ + static bool brcm_pcie_rc_mode(struct brcm_pcie *pcie) + { +@@ -497,6 +735,18 @@ static int brcm_pcie_setup(struct brcm_p + PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK); + writel(tmp, base + PCIE_MISC_MISC_CTRL); + ++ /* ++ * We ideally want the MSI target address to be located in the 32bit ++ * addressable memory area. Some devices might depend on it. This is ++ * possible either when the inbound window is located above the lower ++ * 4GB or when the inbound area is smaller than 4GB (taking into ++ * account the rounding-up we're forced to perform). ++ */ ++ if (rc_bar2_offset >= SZ_4G || (rc_bar2_size + rc_bar2_offset) < SZ_4G) ++ pcie->msi_target_addr = BRCM_MSI_TARGET_ADDR_LT_4GB; ++ else ++ pcie->msi_target_addr = BRCM_MSI_TARGET_ADDR_GT_4GB; ++ + /* disable the PCIe->GISB memory window (RC_BAR1) */ + tmp = readl(base + PCIE_MISC_RC_BAR1_CONFIG_LO); + tmp &= ~PCIE_MISC_RC_BAR1_CONFIG_LO_SIZE_MASK; +@@ -646,6 +896,7 @@ static void brcm_pcie_turn_off(struct br + + static void __brcm_pcie_remove(struct brcm_pcie *pcie) + { ++ brcm_msi_remove(pcie); + brcm_pcie_turn_off(pcie); + clk_disable_unprepare(pcie->clk); + clk_put(pcie->clk); +@@ -664,7 +915,7 @@ static int brcm_pcie_remove(struct platf + + static int brcm_pcie_probe(struct platform_device *pdev) + { +- struct device_node *np = pdev->dev.of_node; ++ struct device_node *np = pdev->dev.of_node, *msi_np; + struct pci_host_bridge *bridge; + struct brcm_pcie *pcie; + struct pci_bus *child; +@@ -708,6 +959,15 @@ static int brcm_pcie_probe(struct platfo + if (ret) + goto fail; + ++ msi_np = of_parse_phandle(pcie->np, "msi-parent", 0); ++ if (pci_msi_enabled() && msi_np == pcie->np) { ++ ret = brcm_pcie_enable_msi(pcie); ++ if (ret) { ++ dev_err(pcie->dev, "probe of internal MSI failed"); ++ goto fail; ++ } ++ } ++ + bridge->dev.parent = &pdev->dev; + bridge->busnr = 0; + bridge->ops = &brcm_pcie_ops; |