mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-29 10:08:59 +00:00
384 lines
11 KiB
Diff
384 lines
11 KiB
Diff
|
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;
|