Centos-kernel-stream-9/drivers/pci/controller/dwc/pci-layerscape-ep.c

302 lines
7.7 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* PCIe controller EP driver for Freescale Layerscape SoCs
*
* Copyright (C) 2018 NXP Semiconductor.
*
* Author: Xiaowei Bao <xiaowei.bao@nxp.com>
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of_pci.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/resource.h>
#include "pcie-designware.h"
#define PEX_PF0_CONFIG 0xC0014
#define PEX_PF0_CFG_READY BIT(0)
/* PEX PFa PCIE PME and message interrupt registers*/
#define PEX_PF0_PME_MES_DR 0xC0020
#define PEX_PF0_PME_MES_DR_LUD BIT(7)
#define PEX_PF0_PME_MES_DR_LDD BIT(9)
#define PEX_PF0_PME_MES_DR_HRD BIT(10)
#define PEX_PF0_PME_MES_IER 0xC0028
#define PEX_PF0_PME_MES_IER_LUDIE BIT(7)
#define PEX_PF0_PME_MES_IER_LDDIE BIT(9)
#define PEX_PF0_PME_MES_IER_HRDIE BIT(10)
#define to_ls_pcie_ep(x) dev_get_drvdata((x)->dev)
struct ls_pcie_ep_drvdata {
u32 func_offset;
const struct dw_pcie_ep_ops *ops;
const struct dw_pcie_ops *dw_pcie_ops;
};
struct ls_pcie_ep {
struct dw_pcie *pci;
struct pci_epc_features *ls_epc;
const struct ls_pcie_ep_drvdata *drvdata;
int irq;
u32 lnkcap;
bool big_endian;
};
static u32 ls_pcie_pf_lut_readl(struct ls_pcie_ep *pcie, u32 offset)
{
struct dw_pcie *pci = pcie->pci;
if (pcie->big_endian)
return ioread32be(pci->dbi_base + offset);
else
return ioread32(pci->dbi_base + offset);
}
static void ls_pcie_pf_lut_writel(struct ls_pcie_ep *pcie, u32 offset, u32 value)
{
struct dw_pcie *pci = pcie->pci;
if (pcie->big_endian)
iowrite32be(value, pci->dbi_base + offset);
else
iowrite32(value, pci->dbi_base + offset);
}
static irqreturn_t ls_pcie_ep_event_handler(int irq, void *dev_id)
{
struct ls_pcie_ep *pcie = dev_id;
struct dw_pcie *pci = pcie->pci;
u32 val, cfg;
u8 offset;
val = ls_pcie_pf_lut_readl(pcie, PEX_PF0_PME_MES_DR);
ls_pcie_pf_lut_writel(pcie, PEX_PF0_PME_MES_DR, val);
if (!val)
return IRQ_NONE;
if (val & PEX_PF0_PME_MES_DR_LUD) {
offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
/*
* The values of the Maximum Link Width and Supported Link
* Speed from the Link Capabilities Register will be lost
* during link down or hot reset. Restore initial value
* that configured by the Reset Configuration Word (RCW).
*/
dw_pcie_dbi_ro_wr_en(pci);
dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCAP, pcie->lnkcap);
dw_pcie_dbi_ro_wr_dis(pci);
cfg = ls_pcie_pf_lut_readl(pcie, PEX_PF0_CONFIG);
cfg |= PEX_PF0_CFG_READY;
ls_pcie_pf_lut_writel(pcie, PEX_PF0_CONFIG, cfg);
dw_pcie_ep_linkup(&pci->ep);
dev_dbg(pci->dev, "Link up\n");
} else if (val & PEX_PF0_PME_MES_DR_LDD) {
dev_dbg(pci->dev, "Link down\n");
dw_pcie_ep_linkdown(&pci->ep);
} else if (val & PEX_PF0_PME_MES_DR_HRD) {
dev_dbg(pci->dev, "Hot reset\n");
}
return IRQ_HANDLED;
}
static int ls_pcie_ep_interrupt_init(struct ls_pcie_ep *pcie,
struct platform_device *pdev)
{
u32 val;
int ret;
pcie->irq = platform_get_irq_byname(pdev, "pme");
if (pcie->irq < 0)
return pcie->irq;
ret = devm_request_irq(&pdev->dev, pcie->irq, ls_pcie_ep_event_handler,
IRQF_SHARED, pdev->name, pcie);
if (ret) {
dev_err(&pdev->dev, "Can't register PCIe IRQ\n");
return ret;
}
/* Enable interrupts */
val = ls_pcie_pf_lut_readl(pcie, PEX_PF0_PME_MES_IER);
val |= PEX_PF0_PME_MES_IER_LDDIE | PEX_PF0_PME_MES_IER_HRDIE |
PEX_PF0_PME_MES_IER_LUDIE;
ls_pcie_pf_lut_writel(pcie, PEX_PF0_PME_MES_IER, val);
return 0;
}
static const struct pci_epc_features*
ls_pcie_ep_get_features(struct dw_pcie_ep *ep)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
struct ls_pcie_ep *pcie = to_ls_pcie_ep(pci);
return pcie->ls_epc;
}
static void ls_pcie_ep_init(struct dw_pcie_ep *ep)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
struct ls_pcie_ep *pcie = to_ls_pcie_ep(pci);
struct dw_pcie_ep_func *ep_func;
enum pci_barno bar;
ep_func = dw_pcie_ep_get_func_from_ep(ep, 0);
if (!ep_func)
return;
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++)
dw_pcie_ep_reset_bar(pci, bar);
pcie->ls_epc->msi_capable = ep_func->msi_cap ? true : false;
pcie->ls_epc->msix_capable = ep_func->msix_cap ? true : false;
}
static int ls_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
unsigned int type, u16 interrupt_num)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
switch (type) {
case PCI_IRQ_INTX:
return dw_pcie_ep_raise_intx_irq(ep, func_no);
case PCI_IRQ_MSI:
return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num);
case PCI_IRQ_MSIX:
return dw_pcie_ep_raise_msix_irq_doorbell(ep, func_no,
interrupt_num);
default:
dev_err(pci->dev, "UNKNOWN IRQ type\n");
return -EINVAL;
}
}
static unsigned int ls_pcie_ep_get_dbi_offset(struct dw_pcie_ep *ep, u8 func_no)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
struct ls_pcie_ep *pcie = to_ls_pcie_ep(pci);
WARN_ON(func_no && !pcie->drvdata->func_offset);
return pcie->drvdata->func_offset * func_no;
}
static const struct dw_pcie_ep_ops ls_pcie_ep_ops = {
2024-03-14 17:09:44 +00:00
.init = ls_pcie_ep_init,
.raise_irq = ls_pcie_ep_raise_irq,
.get_features = ls_pcie_ep_get_features,
.get_dbi_offset = ls_pcie_ep_get_dbi_offset,
};
static const struct ls_pcie_ep_drvdata ls1_ep_drvdata = {
.ops = &ls_pcie_ep_ops,
};
static const struct ls_pcie_ep_drvdata ls2_ep_drvdata = {
.func_offset = 0x20000,
.ops = &ls_pcie_ep_ops,
};
static const struct ls_pcie_ep_drvdata lx2_ep_drvdata = {
.func_offset = 0x8000,
.ops = &ls_pcie_ep_ops,
};
static const struct of_device_id ls_pcie_ep_of_match[] = {
{ .compatible = "fsl,ls1028a-pcie-ep", .data = &ls1_ep_drvdata },
{ .compatible = "fsl,ls1046a-pcie-ep", .data = &ls1_ep_drvdata },
{ .compatible = "fsl,ls1088a-pcie-ep", .data = &ls2_ep_drvdata },
{ .compatible = "fsl,ls2088a-pcie-ep", .data = &ls2_ep_drvdata },
{ .compatible = "fsl,lx2160ar2-pcie-ep", .data = &lx2_ep_drvdata },
{ },
};
static int __init ls_pcie_ep_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dw_pcie *pci;
struct ls_pcie_ep *pcie;
struct pci_epc_features *ls_epc;
struct resource *dbi_base;
u8 offset;
int ret;
pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
if (!pcie)
return -ENOMEM;
pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
if (!pci)
return -ENOMEM;
ls_epc = devm_kzalloc(dev, sizeof(*ls_epc), GFP_KERNEL);
if (!ls_epc)
return -ENOMEM;
pcie->drvdata = of_device_get_match_data(dev);
pci->dev = dev;
pci->ops = pcie->drvdata->dw_pcie_ops;
2024-05-05 03:02:19 +00:00
ls_epc->bar[BAR_2].only_64bit = true;
ls_epc->bar[BAR_3].type = BAR_RESERVED;
ls_epc->bar[BAR_4].only_64bit = true;
ls_epc->bar[BAR_5].type = BAR_RESERVED;
ls_epc->linkup_notifier = true;
pcie->pci = pci;
pcie->ls_epc = ls_epc;
dbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
pci->dbi_base = devm_pci_remap_cfg_resource(dev, dbi_base);
if (IS_ERR(pci->dbi_base))
return PTR_ERR(pci->dbi_base);
PCI: dwc: Move "dbi", "dbi2", and "addr_space" resource setup into common code Most DWC drivers use the common register resource names "dbi", "dbi2", and "addr_space", so let's move their setup into the DWC common code. This means 'dbi_base' in particular is setup later, but it looks like no drivers touch DBI registers before dw_pcie_host_init or dw_pcie_ep_init. Link: https://lore.kernel.org/r/20201105211159.1814485-4-robh@kernel.org Tested-by: Marek Szyprowski <m.szyprowski@samsung.com> Signed-off-by: Rob Herring <robh@kernel.org> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Acked-by: Jingoo Han <jingoohan1@gmail.com> Cc: Kishon Vijay Abraham I <kishon@ti.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: Murali Karicheri <m-karicheri2@ti.com> Cc: Minghuan Lian <minghuan.Lian@nxp.com> Cc: Mingkai Hu <mingkai.hu@nxp.com> Cc: Roy Zang <roy.zang@nxp.com> Cc: Jonathan Chocron <jonnyc@amazon.com> Cc: Jesper Nilsson <jesper.nilsson@axis.com> Cc: Gustavo Pimentel <gustavo.pimentel@synopsys.com> Cc: Xiaowei Song <songxiaowei@hisilicon.com> Cc: Binghui Wang <wangbinghui@hisilicon.com> Cc: Andy Gross <agross@kernel.org> Cc: Bjorn Andersson <bjorn.andersson@linaro.org> Cc: Stanimir Varbanov <svarbanov@mm-sol.com> Cc: Pratyush Anand <pratyush.anand@gmail.com> Cc: Thierry Reding <thierry.reding@gmail.com> Cc: Jonathan Hunter <jonathanh@nvidia.com> Cc: Kunihiko Hayashi <hayashi.kunihiko@socionext.com> Cc: Masahiro Yamada <yamada.masahiro@socionext.com> Cc: linux-omap@vger.kernel.org Cc: linuxppc-dev@lists.ozlabs.org Cc: linux-arm-kernel@axis.com Cc: linux-arm-msm@vger.kernel.org Cc: linux-tegra@vger.kernel.org
2020-11-05 21:11:46 +00:00
pci->ep.ops = &ls_pcie_ep_ops;
pcie->big_endian = of_property_read_bool(dev->of_node, "big-endian");
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
platform_set_drvdata(pdev, pcie);
offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
pcie->lnkcap = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP);
ret = dw_pcie_ep_init(&pci->ep);
if (ret)
return ret;
PCI: dwc: ep: Call dw_pcie_ep_init_registers() API directly from all glue drivers JIRA: https://issues.redhat.com/browse/RHEL-50255 Upstream Status: df69e17ccc2f87bd6ce7a862dcdce16ae55a7fd7 commit df69e17ccc2f87bd6ce7a862dcdce16ae55a7fd7 Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Date: Wed Mar 27 14:43:36 2024 +0530 PCI: dwc: ep: Call dw_pcie_ep_init_registers() API directly from all glue drivers Currently, dw_pcie_ep_init_registers() API is directly called by the glue drivers requiring active refclk from host. But for the other drivers, it is getting called implicitly by dw_pcie_ep_init(). This is due to the fact that this API initializes DWC EP specific registers and that requires an active refclk (either from host or generated locally by endpoint itsef). But, this causes a discrepancy among the glue drivers. So to avoid this confusion, let's call this API directly from all glue drivers irrespective of refclk dependency. Only difference here is that the drivers requiring refclk from host will call this API only after the refclk is received and other drivers without refclk dependency will call this API right after dw_pcie_ep_init(). With this change, the check for 'core_init_notifier' flag can now be dropped from dw_pcie_ep_init() API. This will also allow us to remove the 'core_init_notifier' flag completely in the later commits. Link: https://lore.kernel.org/linux-pci/20240327-pci-dbi-rework-v12-7-082625472414@linaro.org Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org> Reviewed-by: Frank Li <Frank.Li@nxp.com> Reviewed-by: Niklas Cassel <cassel@kernel.org> Reviewed-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Myron Stowe <mstowe@redhat.com>
2024-08-15 01:31:05 +00:00
ret = dw_pcie_ep_init_registers(&pci->ep);
if (ret) {
dev_err(dev, "Failed to initialize DWC endpoint registers\n");
dw_pcie_ep_deinit(&pci->ep);
return ret;
}
pci_epc_init_notify(pci->ep.epc);
PCI: endpoint: Remove "core_init_notifier" flag JIRA: https://issues.redhat.com/browse/RHEL-50255 Upstream Status: a01e7214bef904723d26d293eb17586078610379 commit a01e7214bef904723d26d293eb17586078610379 Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Date: Wed Mar 27 14:43:37 2024 +0530 PCI: endpoint: Remove "core_init_notifier" flag "core_init_notifier" flag is set by the glue drivers requiring refclk from the host to complete the DWC core initialization. Also, those drivers will send a notification to the EPF drivers once the initialization is fully completed using the pci_epc_init_notify() API. Only then, the EPF drivers will start functioning. For the rest of the drivers generating refclk locally, EPF drivers will start functioning post binding with them. EPF drivers rely on the 'core_init_notifier' flag to differentiate between the drivers. Unfortunately, this creates two different flows for the EPF drivers. So to avoid that, let's get rid of the "core_init_notifier" flag and follow a single initialization flow for the EPF drivers. This is done by calling the dw_pcie_ep_init_notify() from all glue drivers after the completion of dw_pcie_ep_init_registers() API. This will allow all the glue drivers to send the notification to the EPF drivers once the initialization is fully completed. Only difference here is that, the drivers requiring refclk from host will send the notification once refclk is received, while others will send it during probe time itself. But this also requires the EPC core driver to deliver the notification after EPF driver bind. Because, the glue driver can send the notification before the EPF drivers bind() and in those cases the EPF drivers will miss the event. To accommodate this, EPC core is now caching the state of the EPC initialization in 'init_complete' flag and pci-ep-cfs driver sends the notification to EPF drivers based on that after each EPF driver bind. Link: https://lore.kernel.org/linux-pci/20240327-pci-dbi-rework-v12-8-082625472414@linaro.org Tested-by: Niklas Cassel <cassel@kernel.org> Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org> Reviewed-by: Frank Li <Frank.Li@nxp.com> Reviewed-by: Niklas Cassel <cassel@kernel.org> Signed-off-by: Myron Stowe <mstowe@redhat.com>
2024-08-15 01:31:05 +00:00
return ls_pcie_ep_interrupt_init(pcie, pdev);
}
static struct platform_driver ls_pcie_ep_driver = {
.driver = {
.name = "layerscape-pcie-ep",
.of_match_table = ls_pcie_ep_of_match,
.suppress_bind_attrs = true,
},
};
builtin_platform_driver_probe(ls_pcie_ep_driver, ls_pcie_ep_probe);