]> git.proxmox.com Git - mirror_ubuntu-kernels.git/commitdiff
PCI: dwc: Add Root Port and Endpoint controller eDMA engine support
authorSerge Semin <Sergey.Semin@baikalelectronics.ru>
Fri, 13 Jan 2023 17:14:09 +0000 (20:14 +0300)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 22 Feb 2023 19:46:14 +0000 (13:46 -0600)
Since the DW eDMA core now supports eDMA controllers embedded in locally
accessible DW PCIe Root Ports and Endpoints, register these controllers
when possible.

To do that the DW PCIe core driver needs to perform some preparations
first. First of all, it needs to find the eDMA controller CSRs base
address, whether they are accessible over the Port Logic or iATU unrolled
space.  Afterwards it can try to auto-detect the eDMA controller
availability and number of read/write channels.  If none are found the
procedure silently returns without error.

Secondly, the platform is supposed to provide either combined or
per-channel IRQ signals.  If no valid IRQs set is found, the procedure
returns without error to be backward compatible with platforms where DW
PCIe controllers have eDMA but lack the IRQ description.

Finally, before actually probing the eDMA device we need to allocate LLP
items buffers. After that the DW eDMA can be registered. If registration is
successful, a message regarding the number of detected Read/Write eDMA
channels will be printed to the system as is done for the iATU settings.

Note: the DW PCI controller driver (either host or endpoint mode) is
currently always built-in, so if the DW eDMA core is built as a module
(CONFIG_DW_EDMA=m), eDMA controllers will not be registered even if the
dw-edma module is later loaded.

Link: https://lore.kernel.org/r/20230113171409.30470-28-Sergey.Semin@baikalelectronics.ru
Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Acked-by: Vinod Koul <vkoul@kernel.org>
drivers/pci/controller/dwc/pcie-designware-ep.c
drivers/pci/controller/dwc/pcie-designware-host.c
drivers/pci/controller/dwc/pcie-designware.c
drivers/pci/controller/dwc/pcie-designware.h

index d06654895ebae11886ea8c1ddaff00bfd407b266..f9182f8d552f49325fafc7ad321b9ffd8bb26807 100644 (file)
@@ -612,8 +612,11 @@ int dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, u8 func_no,
 
 void dw_pcie_ep_exit(struct dw_pcie_ep *ep)
 {
+       struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
        struct pci_epc *epc = ep->epc;
 
+       dw_pcie_edma_remove(pci);
+
        pci_epc_mem_free_addr(epc, ep->msi_mem_phys, ep->msi_mem,
                              epc->mem->window.page_size);
 
@@ -785,6 +788,10 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep)
                goto err_exit_epc_mem;
        }
 
+       ret = dw_pcie_edma_detect(pci);
+       if (ret)
+               goto err_free_epc_mem;
+
        if (ep->ops->get_features) {
                epc_features = ep->ops->get_features(ep);
                if (epc_features->core_init_notifier)
@@ -793,10 +800,13 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep)
 
        ret = dw_pcie_ep_init_complete(ep);
        if (ret)
-               goto err_free_epc_mem;
+               goto err_remove_edma;
 
        return 0;
 
+err_remove_edma:
+       dw_pcie_edma_remove(pci);
+
 err_free_epc_mem:
        pci_epc_mem_free_addr(epc, ep->msi_mem_phys, ep->msi_mem,
                              epc->mem->window.page_size);
index 14f27985055e6ec83c9531f59681b781c37d9060..9952057c8819c86ac7bb97dfe709f6337ddfa7c6 100644 (file)
@@ -477,14 +477,18 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp)
 
        dw_pcie_iatu_detect(pci);
 
-       ret = dw_pcie_setup_rc(pp);
+       ret = dw_pcie_edma_detect(pci);
        if (ret)
                goto err_free_msi;
 
+       ret = dw_pcie_setup_rc(pp);
+       if (ret)
+               goto err_remove_edma;
+
        if (!dw_pcie_link_up(pci)) {
                ret = dw_pcie_start_link(pci);
                if (ret)
-                       goto err_free_msi;
+                       goto err_remove_edma;
        }
 
        /* Ignore errors, the link may come up later */
@@ -501,6 +505,9 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp)
 err_stop_link:
        dw_pcie_stop_link(pci);
 
+err_remove_edma:
+       dw_pcie_edma_remove(pci);
+
 err_free_msi:
        if (pp->has_msi_ctrl)
                dw_pcie_free_msi(pp);
@@ -522,6 +529,8 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp)
 
        dw_pcie_stop_link(pci);
 
+       dw_pcie_edma_remove(pci);
+
        if (pp->has_msi_ctrl)
                dw_pcie_free_msi(pp);
 
index 6d5d619ab2e9412e0c4cd4376433ed67daba7927..53a16b8b6ac23ae796defd67dbacf56469acfb20 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/bitops.h>
 #include <linux/clk.h>
 #include <linux/delay.h>
+#include <linux/dma/edma.h>
 #include <linux/gpio/consumer.h>
 #include <linux/ioport.h>
 #include <linux/of.h>
@@ -142,6 +143,18 @@ int dw_pcie_get_resources(struct dw_pcie *pci)
        if (!pci->atu_size)
                pci->atu_size = SZ_4K;
 
+       /* eDMA region can be mapped to a custom base address */
+       if (!pci->edma.reg_base) {
+               res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma");
+               if (res) {
+                       pci->edma.reg_base = devm_ioremap_resource(pci->dev, res);
+                       if (IS_ERR(pci->edma.reg_base))
+                               return PTR_ERR(pci->edma.reg_base);
+               } else if (pci->atu_size >= 2 * DEFAULT_DBI_DMA_OFFSET) {
+                       pci->edma.reg_base = pci->atu_base + DEFAULT_DBI_DMA_OFFSET;
+               }
+       }
+
        /* LLDD is supposed to manually switch the clocks and resets state */
        if (dw_pcie_cap_is(pci, REQ_RES)) {
                ret = dw_pcie_get_clocks(pci);
@@ -782,6 +795,188 @@ void dw_pcie_iatu_detect(struct dw_pcie *pci)
                 pci->region_align / SZ_1K, (pci->region_limit + 1) / SZ_1G);
 }
 
+static u32 dw_pcie_readl_dma(struct dw_pcie *pci, u32 reg)
+{
+       u32 val = 0;
+       int ret;
+
+       if (pci->ops && pci->ops->read_dbi)
+               return pci->ops->read_dbi(pci, pci->edma.reg_base, reg, 4);
+
+       ret = dw_pcie_read(pci->edma.reg_base + reg, 4, &val);
+       if (ret)
+               dev_err(pci->dev, "Read DMA address failed\n");
+
+       return val;
+}
+
+static int dw_pcie_edma_irq_vector(struct device *dev, unsigned int nr)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       char name[6];
+       int ret;
+
+       if (nr >= EDMA_MAX_WR_CH + EDMA_MAX_RD_CH)
+               return -EINVAL;
+
+       ret = platform_get_irq_byname_optional(pdev, "dma");
+       if (ret > 0)
+               return ret;
+
+       snprintf(name, sizeof(name), "dma%u", nr);
+
+       return platform_get_irq_byname_optional(pdev, name);
+}
+
+static struct dw_edma_core_ops dw_pcie_edma_ops = {
+       .irq_vector = dw_pcie_edma_irq_vector,
+};
+
+static int dw_pcie_edma_find_chip(struct dw_pcie *pci)
+{
+       u32 val;
+
+       /*
+        * Indirect eDMA CSRs access has been completely removed since v5.40a
+        * thus no space is now reserved for the eDMA channels viewport and
+        * former DMA CTRL register is no longer fixed to FFs.
+        */
+       if (dw_pcie_ver_is_ge(pci, 540A))
+               val = 0xFFFFFFFF;
+       else
+               val = dw_pcie_readl_dbi(pci, PCIE_DMA_VIEWPORT_BASE + PCIE_DMA_CTRL);
+
+       if (val == 0xFFFFFFFF && pci->edma.reg_base) {
+               pci->edma.mf = EDMA_MF_EDMA_UNROLL;
+
+               val = dw_pcie_readl_dma(pci, PCIE_DMA_CTRL);
+       } else if (val != 0xFFFFFFFF) {
+               pci->edma.mf = EDMA_MF_EDMA_LEGACY;
+
+               pci->edma.reg_base = pci->dbi_base + PCIE_DMA_VIEWPORT_BASE;
+       } else {
+               return -ENODEV;
+       }
+
+       pci->edma.dev = pci->dev;
+
+       if (!pci->edma.ops)
+               pci->edma.ops = &dw_pcie_edma_ops;
+
+       pci->edma.flags |= DW_EDMA_CHIP_LOCAL;
+
+       pci->edma.ll_wr_cnt = FIELD_GET(PCIE_DMA_NUM_WR_CHAN, val);
+       pci->edma.ll_rd_cnt = FIELD_GET(PCIE_DMA_NUM_RD_CHAN, val);
+
+       /* Sanity check the channels count if the mapping was incorrect */
+       if (!pci->edma.ll_wr_cnt || pci->edma.ll_wr_cnt > EDMA_MAX_WR_CH ||
+           !pci->edma.ll_rd_cnt || pci->edma.ll_rd_cnt > EDMA_MAX_RD_CH)
+               return -EINVAL;
+
+       return 0;
+}
+
+static int dw_pcie_edma_irq_verify(struct dw_pcie *pci)
+{
+       struct platform_device *pdev = to_platform_device(pci->dev);
+       u16 ch_cnt = pci->edma.ll_wr_cnt + pci->edma.ll_rd_cnt;
+       char name[6];
+       int ret;
+
+       if (pci->edma.nr_irqs == 1)
+               return 0;
+       else if (pci->edma.nr_irqs > 1)
+               return pci->edma.nr_irqs != ch_cnt ? -EINVAL : 0;
+
+       ret = platform_get_irq_byname_optional(pdev, "dma");
+       if (ret > 0) {
+               pci->edma.nr_irqs = 1;
+               return 0;
+       }
+
+       for (; pci->edma.nr_irqs < ch_cnt; pci->edma.nr_irqs++) {
+               snprintf(name, sizeof(name), "dma%d", pci->edma.nr_irqs);
+
+               ret = platform_get_irq_byname_optional(pdev, name);
+               if (ret <= 0)
+                       return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int dw_pcie_edma_ll_alloc(struct dw_pcie *pci)
+{
+       struct dw_edma_region *ll;
+       dma_addr_t paddr;
+       int i;
+
+       for (i = 0; i < pci->edma.ll_wr_cnt; i++) {
+               ll = &pci->edma.ll_region_wr[i];
+               ll->sz = DMA_LLP_MEM_SIZE;
+               ll->vaddr.mem = dmam_alloc_coherent(pci->dev, ll->sz,
+                                                   &paddr, GFP_KERNEL);
+               if (!ll->vaddr.mem)
+                       return -ENOMEM;
+
+               ll->paddr = paddr;
+       }
+
+       for (i = 0; i < pci->edma.ll_rd_cnt; i++) {
+               ll = &pci->edma.ll_region_rd[i];
+               ll->sz = DMA_LLP_MEM_SIZE;
+               ll->vaddr.mem = dmam_alloc_coherent(pci->dev, ll->sz,
+                                                   &paddr, GFP_KERNEL);
+               if (!ll->vaddr.mem)
+                       return -ENOMEM;
+
+               ll->paddr = paddr;
+       }
+
+       return 0;
+}
+
+int dw_pcie_edma_detect(struct dw_pcie *pci)
+{
+       int ret;
+
+       /* Don't fail if no eDMA was found (for the backward compatibility) */
+       ret = dw_pcie_edma_find_chip(pci);
+       if (ret)
+               return 0;
+
+       /* Don't fail on the IRQs verification (for the backward compatibility) */
+       ret = dw_pcie_edma_irq_verify(pci);
+       if (ret) {
+               dev_err(pci->dev, "Invalid eDMA IRQs found\n");
+               return 0;
+       }
+
+       ret = dw_pcie_edma_ll_alloc(pci);
+       if (ret) {
+               dev_err(pci->dev, "Couldn't allocate LLP memory\n");
+               return ret;
+       }
+
+       /* Don't fail if the DW eDMA driver can't find the device */
+       ret = dw_edma_probe(&pci->edma);
+       if (ret && ret != -ENODEV) {
+               dev_err(pci->dev, "Couldn't register eDMA device\n");
+               return ret;
+       }
+
+       dev_info(pci->dev, "eDMA: unroll %s, %hu wr, %hu rd\n",
+                pci->edma.mf == EDMA_MF_EDMA_UNROLL ? "T" : "F",
+                pci->edma.ll_wr_cnt, pci->edma.ll_rd_cnt);
+
+       return 0;
+}
+
+void dw_pcie_edma_remove(struct dw_pcie *pci)
+{
+       dw_edma_remove(&pci->edma);
+}
+
 void dw_pcie_setup(struct dw_pcie *pci)
 {
        u32 val;
index 393dfb931df6cc66258b47d02683ccec511efa22..79713ce075cc1b6b3c1eff6b8535813c87837076 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/bitops.h>
 #include <linux/clk.h>
 #include <linux/dma-mapping.h>
+#include <linux/dma/edma.h>
 #include <linux/gpio/consumer.h>
 #include <linux/irq.h>
 #include <linux/msi.h>
@@ -31,6 +32,7 @@
 #define DW_PCIE_VER_480A               0x3438302a
 #define DW_PCIE_VER_490A               0x3439302a
 #define DW_PCIE_VER_520A               0x3532302a
+#define DW_PCIE_VER_540A               0x3534302a
 
 #define __dw_pcie_ver_cmp(_pci, _ver, _op) \
        ((_pci)->version _op DW_PCIE_VER_ ## _ver)
 #define PCIE_MSIX_DOORBELL             0x948
 #define PCIE_MSIX_DOORBELL_PF_SHIFT    24
 
+/*
+ * eDMA CSRs. DW PCIe IP-core v4.70a and older had the eDMA registers accessible
+ * over the Port Logic registers space. Afterwards the unrolled mapping was
+ * introduced so eDMA and iATU could be accessed via a dedicated registers
+ * space.
+ */
+#define PCIE_DMA_VIEWPORT_BASE         0x970
+#define PCIE_DMA_UNROLL_BASE           0x80000
+#define PCIE_DMA_CTRL                  0x008
+#define PCIE_DMA_NUM_WR_CHAN           GENMASK(3, 0)
+#define PCIE_DMA_NUM_RD_CHAN           GENMASK(19, 16)
+
 #define PCIE_PL_CHK_REG_CONTROL_STATUS                 0xB20
 #define PCIE_PL_CHK_REG_CHK_REG_START                  BIT(0)
 #define PCIE_PL_CHK_REG_CHK_REG_CONTINUOUS             BIT(1)
  * this offset, if atu_base not set.
  */
 #define DEFAULT_DBI_ATU_OFFSET (0x3 << 20)
+#define DEFAULT_DBI_DMA_OFFSET PCIE_DMA_UNROLL_BASE
 
 #define MAX_MSI_IRQS                   256
 #define MAX_MSI_IRQS_PER_CTRL          32
 #define MAX_IATU_IN                    256
 #define MAX_IATU_OUT                   256
 
+/* Default eDMA LLP memory size */
+#define DMA_LLP_MEM_SIZE               PAGE_SIZE
+
 struct dw_pcie;
 struct dw_pcie_rp;
 struct dw_pcie_ep;
@@ -369,6 +387,7 @@ struct dw_pcie {
        int                     num_lanes;
        int                     link_gen;
        u8                      n_fts[2];
+       struct dw_edma_chip     edma;
        struct clk_bulk_data    app_clks[DW_PCIE_NUM_APP_CLKS];
        struct clk_bulk_data    core_clks[DW_PCIE_NUM_CORE_CLKS];
        struct reset_control_bulk_data  app_rsts[DW_PCIE_NUM_APP_RSTS];
@@ -408,6 +427,8 @@ int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
 void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index);
 void dw_pcie_setup(struct dw_pcie *pci);
 void dw_pcie_iatu_detect(struct dw_pcie *pci);
+int dw_pcie_edma_detect(struct dw_pcie *pci);
+void dw_pcie_edma_remove(struct dw_pcie *pci);
 
 static inline void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val)
 {