diff options
| -rw-r--r-- | drivers/pci/controller/Kconfig | 1 | ||||
| -rw-r--r-- | drivers/pci/controller/pcie-brcmstb.c | 262 | 
2 files changed, 262 insertions, 1 deletions
diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 27504f108ee5..918e283bbff1 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -257,6 +257,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. diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 3250a2e6b1b4..d20aabc26273 100644 --- 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(struct brcm_pcie *pcie,  	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_pcie *pcie)  			  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 brcm_pcie *pcie)  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 platform_device *pdev)  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 platform_device *pdev)  	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;  | 
