brcm2708: organize kernel patches
[openwrt/staging/dedeckeh.git] / target / linux / brcm2708 / patches-4.19 / 950-0482-PCI-brcmstb-Add-MSI-capability.patch
diff --git a/target/linux/brcm2708/patches-4.19/950-0482-PCI-brcmstb-Add-MSI-capability.patch b/target/linux/brcm2708/patches-4.19/950-0482-PCI-brcmstb-Add-MSI-capability.patch
new file mode 100644 (file)
index 0000000..856bf2b
--- /dev/null
@@ -0,0 +1,543 @@
+From cd3af4fa73ab25353f0865ebe8e0d2af1fd2a50b Mon Sep 17 00:00:00 2001
+From: Phil Elwell <phil@raspberrypi.org>
+Date: Tue, 19 Feb 2019 22:06:59 +0000
+Subject: [PATCH] PCI: brcmstb: Add MSI capability
+
+This commit adds MSI to the Broadcom STB PCIe host controller. It does
+not add MSIX since that functionality is not in the HW.  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.
+
+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 <jim2101024@gmail.com>
+---
+ drivers/pci/controller/pcie-brcmstb.c | 374 ++++++++++++++++++++++++--
+ 1 file changed, 353 insertions(+), 21 deletions(-)
+
+--- a/drivers/pci/controller/pcie-brcmstb.c
++++ b/drivers/pci/controller/pcie-brcmstb.c
+@@ -1,6 +1,7 @@
+ // SPDX-License-Identifier: GPL-2.0
+ /* Copyright (C) 2009 - 2017 Broadcom */
++#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>
+@@ -47,6 +50,9 @@
+ #define PCIE_MISC_RC_BAR2_CONFIG_LO                   0x4034
+ #define PCIE_MISC_RC_BAR2_CONFIG_HI                   0x4038
+ #define PCIE_MISC_RC_BAR3_CONFIG_LO                   0x403c
++#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_PCIE_CTRL                           0x4064
+ #define PCIE_MISC_PCIE_STATUS                         0x4068
+ #define PCIE_MISC_REVISION                            0x406c
+@@ -55,6 +61,7 @@
+ #define PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI                0x4084
+ #define PCIE_MISC_HARD_PCIE_HARD_DEBUG                        0x4204
+ #define PCIE_INTR2_CPU_BASE                           0x4300
++#define PCIE_MSI_INTR2_BASE                           0x4500
+ /*
+  * Broadcom Settop Box PCIe Register Field shift and mask info. The
+@@ -115,6 +122,8 @@
+ #define BRCM_NUM_PCIE_OUT_WINS                0x4
+ #define BRCM_MAX_SCB                  0x4
++#define BRCM_INT_PCI_MSI_NR           32
++#define BRCM_PCIE_HW_REV_33           0x0303
+ #define BRCM_MSI_TARGET_ADDR_LT_4GB   0x0fffffffcULL
+ #define BRCM_MSI_TARGET_ADDR_GT_4GB   0xffffffffcULL
+@@ -203,6 +212,33 @@ struct brcm_window {
+       dma_addr_t size;
+ };
++struct brcm_msi {
++      struct device           *dev;
++      void __iomem            *base;
++      struct device_node      *dn;
++      struct irq_domain       *msi_domain;
++      struct irq_domain       *inner_domain;
++      struct mutex            lock; /* guards the alloc/free operations */
++      u64                     target_addr;
++      int                     irq;
++
++      /* intr_base is the base pointer for interrupt status/set/clr regs */
++      void __iomem            *intr_base;
++
++      /* intr_legacy_mask indicates how many bits are MSI interrupts */
++      u32                     intr_legacy_mask;
++
++      /*
++       * intr_legacy_offset indicates bit position of MSI_01. It is
++       * to map the register bit position to a hwirq that starts at 0.
++       */
++      u32                     intr_legacy_offset;
++
++      /* used indicates which MSI interrupts have been alloc'd */
++      unsigned long           used;
++      unsigned int            rev;
++};
++
+ /* Internal PCIe Host Controller Information.*/
+ struct brcm_pcie {
+       struct device           *dev;
+@@ -217,7 +253,10 @@ struct brcm_pcie {
+       int                     num_out_wins;
+       bool                    ssc;
+       int                     gen;
++      u64                     msi_target_addr;
+       struct brcm_window      out_wins[BRCM_NUM_PCIE_OUT_WINS];
++      struct brcm_msi         *msi;
++      bool                    msi_internal;
+       unsigned int            rev;
+       const int               *reg_offsets;
+       const int               *reg_field_info;
+@@ -225,9 +264,9 @@ struct brcm_pcie {
+ };
+ struct pcie_cfg_data {
+-      const int *reg_field_info;
+-      const int *offsets;
+-      const enum pcie_type type;
++      const int               *reg_field_info;
++      const int               *offsets;
++      const enum pcie_type    type;
+ };
+ static const int pcie_reg_field_info[] = {
+@@ -828,6 +867,267 @@ static void brcm_pcie_set_outbound_win(s
+       }
+ }
++static struct irq_chip brcm_msi_irq_chip = {
++      .name = "Brcm_MSI",
++      .irq_mask = pci_msi_mask_irq,
++      .irq_unmask = pci_msi_unmask_irq,
++};
++
++static struct msi_domain_info brcm_msi_domain_info = {
++      .flags  = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
++                 MSI_FLAG_PCI_MSIX),
++      .chip   = &brcm_msi_irq_chip,
++};
++
++static void brcm_pcie_msi_isr(struct irq_desc *desc)
++{
++      struct irq_chip *chip = irq_desc_get_chip(desc);
++      struct brcm_msi *msi;
++      unsigned long status, virq;
++      u32 mask, bit, hwirq;
++      struct device *dev;
++
++      chained_irq_enter(chip, desc);
++      msi = irq_desc_get_handler_data(desc);
++      mask = msi->intr_legacy_mask;
++      dev = msi->dev;
++
++      while ((status = bcm_readl(msi->intr_base + STATUS) & mask)) {
++              for_each_set_bit(bit, &status, BRCM_INT_PCI_MSI_NR) {
++                      /* clear the interrupt */
++                      bcm_writel(1 << bit, msi->intr_base + CLR);
++
++                      /* Account for legacy interrupt offset */
++                      hwirq = bit - msi->intr_legacy_offset;
++
++                      virq = irq_find_mapping(msi->inner_domain, hwirq);
++                      if (virq) {
++                              if (msi->used & (1 << hwirq))
++                                      generic_handle_irq(virq);
++                              else
++                                      dev_info(dev, "unhandled MSI %d\n",
++                                               hwirq);
++                      } else {
++                              /* Unknown MSI, just clear it */
++                              dev_dbg(dev, "unexpected MSI\n");
++                      }
++              }
++      }
++      chained_irq_exit(chip, desc);
++}
++
++static void brcm_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
++{
++      struct brcm_msi *msi = irq_data_get_irq_chip_data(data);
++      u32 temp;
++
++      msg->address_lo = lower_32_bits(msi->target_addr);
++      msg->address_hi = upper_32_bits(msi->target_addr);
++      temp = bcm_readl(msi->base + PCIE_MISC_MSI_DATA_CONFIG);
++      msg->data = ((temp >> 16) & (temp & 0xffff)) | data->hwirq;
++}
++
++static int brcm_msi_set_affinity(struct irq_data *irq_data,
++                               const struct cpumask *mask, bool force)
++{
++      return -EINVAL;
++}
++
++static struct irq_chip brcm_msi_bottom_irq_chip = {
++      .name                   = "Brcm_MSI",
++      .irq_compose_msi_msg    = brcm_compose_msi_msg,
++      .irq_set_affinity       = brcm_msi_set_affinity,
++};
++
++static int brcm_msi_alloc(struct brcm_msi *msi)
++{
++      int bit, hwirq;
++
++      mutex_lock(&msi->lock);
++      bit = ~msi->used ? ffz(msi->used) : -1;
++
++      if (bit >= 0 && bit < BRCM_INT_PCI_MSI_NR) {
++              msi->used |= (1 << bit);
++              hwirq = bit - msi->intr_legacy_offset;
++      } else {
++              hwirq = -ENOSPC;
++      }
++
++      mutex_unlock(&msi->lock);
++      return hwirq;
++}
++
++static void brcm_msi_free(struct brcm_msi *msi, unsigned long hwirq)
++{
++      mutex_lock(&msi->lock);
++      msi->used &= ~(1 << (hwirq + msi->intr_legacy_offset));
++      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_simple_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 void brcm_msi_set_regs(struct brcm_msi *msi)
++{
++      u32 data_val, msi_lo, msi_hi;
++
++      if (msi->rev >= BRCM_PCIE_HW_REV_33) {
++              /*
++               * ffe0 -- least sig 5 bits are 0 indicating 32 msgs
++               * 6540 -- this is our arbitrary unique data value
++               */
++              data_val = 0xffe06540;
++      } else {
++              /*
++               * fff8 -- least sig 3 bits are 0 indicating 8 msgs
++               * 6540 -- this is our arbitrary unique data value
++               */
++              data_val = 0xfff86540;
++      }
++
++      /*
++       * Make sure we are not masking MSIs.  Note that MSIs can be masked,
++       * but that occurs on the PCIe EP device
++       */
++      bcm_writel(0xffffffff & msi->intr_legacy_mask,
++                 msi->intr_base + MASK_CLR);
++
++      msi_lo = lower_32_bits(msi->target_addr);
++      msi_hi = upper_32_bits(msi->target_addr);
++      /*
++       * The 0 bit of PCIE_MISC_MSI_BAR_CONFIG_LO is repurposed to MSI
++       * enable, which we set to 1.
++       */
++      bcm_writel(msi_lo | 1, msi->base + PCIE_MISC_MSI_BAR_CONFIG_LO);
++      bcm_writel(msi_hi, msi->base + PCIE_MISC_MSI_BAR_CONFIG_HI);
++      bcm_writel(data_val, msi->base + PCIE_MISC_MSI_DATA_CONFIG);
++}
++
++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->dn);
++      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 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 intr\n");
++              return -ENODEV;
++      }
++
++      msi = devm_kzalloc(dev, sizeof(struct brcm_msi), GFP_KERNEL);
++      if (!msi)
++              return -ENOMEM;
++
++      msi->dev = dev;
++      msi->base = pcie->base;
++      msi->rev =  pcie->rev;
++      msi->dn = pcie->dn;
++      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);
++
++      if (msi->rev >= BRCM_PCIE_HW_REV_33) {
++              msi->intr_base = msi->base + PCIE_MSI_INTR2_BASE;
++              /*
++               * This version of PCIe hw has only 32 intr bits
++               * starting at bit position 0.
++               */
++              msi->intr_legacy_mask = 0xffffffff;
++              msi->intr_legacy_offset = 0x0;
++              msi->used = 0x0;
++
++      } else {
++              msi->intr_base = msi->base + PCIE_INTR2_CPU_BASE;
++              /*
++               * This version of PCIe hw has only 8 intr bits starting
++               * at bit position 24.
++               */
++              msi->intr_legacy_mask = 0xff000000;
++              msi->intr_legacy_offset = 24;
++              msi->used = 0x00ffffff;
++      }
++
++      brcm_msi_set_regs(msi);
++      pcie->msi = msi;
++
++      return 0;
++}
++
+ /* Configuration space read/write support */
+ static int cfg_index(int busnr, int devfn, int reg)
+ {
+@@ -1072,6 +1372,7 @@ static int brcm_pcie_setup(struct brcm_p
+       u16 nlw, cls, lnksta;
+       bool ssc_good = false;
+       struct device *dev = pcie->dev;
++      u64 msi_target_addr;
+       /* Reset the bridge */
+       brcm_pcie_bridge_sw_init_set(pcie, 1);
+@@ -1116,27 +1417,24 @@ static int brcm_pcie_setup(struct brcm_p
+        * The PCIe host controller by design must set the inbound
+        * viewport to be a contiguous arrangement of all of the
+        * system's memory.  In addition, its size mut be a power of
+-       * two.  To further complicate matters, the viewport must
+-       * start on a pcie-address that is aligned on a multiple of its
+-       * size.  If a portion of the viewport does not represent
+-       * system memory -- e.g. 3GB of memory requires a 4GB viewport
+-       * -- we can map the outbound memory in or after 3GB and even
+-       * though the viewport will overlap the outbound memory the
+-       * controller will know to send outbound memory downstream and
+-       * everything else upstream.
++       * two.  Further, the MSI target address must NOT be placed
++       * inside this region, as the decoding logic will consider its
++       * address to be inbound memory traffic.  To further
++       * complicate matters, the viewport must start on a
++       * pcie-address that is aligned on a multiple of its size.
++       * If a portion of the viewport does not represent system
++       * memory -- e.g. 3GB of memory requires a 4GB viewport --
++       * we can map the outbound memory in or after 3GB and even
++       * though the viewport will overlap the outbound memory
++       * the controller will know to send outbound memory downstream
++       * and everything else upstream.
+        */
+       rc_bar2_size = roundup_pow_of_two_64(total_mem_size);
+-      /*
+-       * Set simple configuration based on memory sizes
+-       * only.  We always start the viewport at address 0.
+-       */
+-      rc_bar2_offset = 0;
+-
+       if (dma_ranges) {
+               /*
+                * The best-case scenario is to place the inbound
+-               * region in the first 4GB of pci-space, as some
++               * region in the first 4GB of pcie-space, as some
+                * legacy devices can only address 32bits.
+                * We would also like to put the MSI under 4GB
+                * as well, since some devices require a 32bit
+@@ -1145,6 +1443,14 @@ static int brcm_pcie_setup(struct brcm_p
+               if (total_mem_size <= 0xc0000000ULL &&
+                   rc_bar2_size <= 0x100000000ULL) {
+                       rc_bar2_offset = 0;
++                      /* If the viewport is less then 4GB we can fit
++                       * the MSI target address under 4GB. Otherwise
++                       * put it right below 64GB.
++                       */
++                      msi_target_addr =
++                              (rc_bar2_size == 0x100000000ULL)
++                              ? BRCM_MSI_TARGET_ADDR_GT_4GB
++                              : BRCM_MSI_TARGET_ADDR_LT_4GB;
+               } else {
+                       /*
+                        * The system memory is 4GB or larger so we
+@@ -1154,8 +1460,12 @@ static int brcm_pcie_setup(struct brcm_p
+                        * start it at the 1x multiple of its size
+                        */
+                       rc_bar2_offset = rc_bar2_size;
+-              }
++                      /* Since we are starting the viewport at 4GB or
++                       * higher, put the MSI target address below 4GB
++                       */
++                      msi_target_addr = BRCM_MSI_TARGET_ADDR_LT_4GB;
++              }
+       } else {
+               /*
+                * Set simple configuration based on memory sizes
+@@ -1163,7 +1473,12 @@ static int brcm_pcie_setup(struct brcm_p
+                * and set the MSI target address accordingly.
+                */
+               rc_bar2_offset = 0;
++
++              msi_target_addr = (rc_bar2_size >= 0x100000000ULL)
++                      ? BRCM_MSI_TARGET_ADDR_GT_4GB
++                      : BRCM_MSI_TARGET_ADDR_LT_4GB;
+       }
++      pcie->msi_target_addr = msi_target_addr;
+       tmp = lower_32_bits(rc_bar2_offset);
+       tmp = INSERT_FIELD(tmp, PCIE_MISC_RC_BAR2_CONFIG_LO, SIZE,
+@@ -1333,6 +1648,9 @@ static int brcm_pcie_resume(struct devic
+       if (ret)
+               return ret;
++      if (pcie->msi && pcie->msi_internal)
++              brcm_msi_set_regs(pcie->msi);
++
+       pcie->suspended = false;
+       return 0;
+@@ -1340,6 +1658,7 @@ static int brcm_pcie_resume(struct devic
+ static void _brcm_pcie_remove(struct brcm_pcie *pcie)
+ {
++      brcm_msi_remove(pcie);
+       turn_off(pcie);
+       clk_disable_unprepare(pcie->clk);
+       clk_put(pcie->clk);
+@@ -1368,7 +1687,7 @@ MODULE_DEVICE_TABLE(of, brcm_pcie_match)
+ static int brcm_pcie_probe(struct platform_device *pdev)
+ {
+-      struct device_node *dn = pdev->dev.of_node;
++      struct device_node *dn = pdev->dev.of_node, *msi_dn;
+       const struct of_device_id *of_id;
+       const struct pcie_cfg_data *data;
+       int ret;
+@@ -1448,6 +1767,20 @@ static int brcm_pcie_probe(struct platfo
+       if (ret)
+               goto fail;
++      msi_dn = of_parse_phandle(pcie->dn, "msi-parent", 0);
++      /* Use the internal MSI if no msi-parent property */
++      if (!msi_dn)
++              msi_dn = pcie->dn;
++
++      if (pci_msi_enabled() && msi_dn == pcie->dn) {
++              ret = brcm_pcie_enable_msi(pcie);
++              if (ret)
++                      dev_err(pcie->dev,
++                              "probe of internal MSI failed: %d)", ret);
++              else
++                      pcie->msi_internal = true;
++      }
++
+       list_splice_init(&pcie->resources, &bridge->windows);
+       bridge->dev.parent = &pdev->dev;
+       bridge->busnr = 0;
+@@ -1470,7 +1803,6 @@ static int brcm_pcie_probe(struct platfo
+       pcie->root_bus = bridge->bus;
+       return 0;
+-
+ fail:
+       _brcm_pcie_remove(pcie);
+       return ret;