"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>
198 lines
4.3 KiB
C
198 lines
4.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* PCIe RC driver for Synopsys DesignWare Core
|
|
*
|
|
* Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
|
|
*
|
|
* Authors: Joao Pinto <Joao.Pinto@synopsys.com>
|
|
*/
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/of.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/resource.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "pcie-designware.h"
|
|
|
|
struct dw_plat_pcie {
|
|
struct dw_pcie *pci;
|
|
enum dw_pcie_device_mode mode;
|
|
};
|
|
|
|
struct dw_plat_pcie_of_data {
|
|
enum dw_pcie_device_mode mode;
|
|
};
|
|
|
|
static const struct dw_pcie_host_ops dw_plat_pcie_host_ops = {
|
|
};
|
|
|
|
static void dw_plat_pcie_ep_init(struct dw_pcie_ep *ep)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
|
enum pci_barno bar;
|
|
|
|
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++)
|
|
dw_pcie_ep_reset_bar(pci, bar);
|
|
}
|
|
|
|
static int dw_plat_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(ep, func_no, interrupt_num);
|
|
default:
|
|
dev_err(pci->dev, "UNKNOWN IRQ type\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pci_epc_features dw_plat_pcie_epc_features = {
|
|
.linkup_notifier = false,
|
|
.msi_capable = true,
|
|
.msix_capable = true,
|
|
};
|
|
|
|
static const struct pci_epc_features*
|
|
dw_plat_pcie_get_features(struct dw_pcie_ep *ep)
|
|
{
|
|
return &dw_plat_pcie_epc_features;
|
|
}
|
|
|
|
static const struct dw_pcie_ep_ops pcie_ep_ops = {
|
|
.init = dw_plat_pcie_ep_init,
|
|
.raise_irq = dw_plat_pcie_ep_raise_irq,
|
|
.get_features = dw_plat_pcie_get_features,
|
|
};
|
|
|
|
static int dw_plat_add_pcie_port(struct dw_plat_pcie *dw_plat_pcie,
|
|
struct platform_device *pdev)
|
|
{
|
|
struct dw_pcie *pci = dw_plat_pcie->pci;
|
|
struct dw_pcie_rp *pp = &pci->pp;
|
|
struct device *dev = &pdev->dev;
|
|
int ret;
|
|
|
|
pp->irq = platform_get_irq(pdev, 1);
|
|
if (pp->irq < 0)
|
|
return pp->irq;
|
|
|
|
pp->num_vectors = MAX_MSI_IRQS;
|
|
pp->ops = &dw_plat_pcie_host_ops;
|
|
|
|
ret = dw_pcie_host_init(pp);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to initialize host\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_plat_pcie_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct dw_plat_pcie *dw_plat_pcie;
|
|
struct dw_pcie *pci;
|
|
int ret;
|
|
const struct dw_plat_pcie_of_data *data;
|
|
enum dw_pcie_device_mode mode;
|
|
|
|
data = of_device_get_match_data(dev);
|
|
if (!data)
|
|
return -EINVAL;
|
|
|
|
mode = (enum dw_pcie_device_mode)data->mode;
|
|
|
|
dw_plat_pcie = devm_kzalloc(dev, sizeof(*dw_plat_pcie), GFP_KERNEL);
|
|
if (!dw_plat_pcie)
|
|
return -ENOMEM;
|
|
|
|
pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
|
|
if (!pci)
|
|
return -ENOMEM;
|
|
|
|
pci->dev = dev;
|
|
|
|
dw_plat_pcie->pci = pci;
|
|
dw_plat_pcie->mode = mode;
|
|
|
|
platform_set_drvdata(pdev, dw_plat_pcie);
|
|
|
|
switch (dw_plat_pcie->mode) {
|
|
case DW_PCIE_RC_TYPE:
|
|
if (!IS_ENABLED(CONFIG_PCIE_DW_PLAT_HOST))
|
|
return -ENODEV;
|
|
|
|
ret = dw_plat_add_pcie_port(dw_plat_pcie, pdev);
|
|
break;
|
|
case DW_PCIE_EP_TYPE:
|
|
if (!IS_ENABLED(CONFIG_PCIE_DW_PLAT_EP))
|
|
return -ENODEV;
|
|
|
|
pci->ep.ops = &pcie_ep_ops;
|
|
ret = dw_pcie_ep_init(&pci->ep);
|
|
if (ret)
|
|
return ret;
|
|
|
|
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);
|
|
}
|
|
|
|
dw_pcie_ep_init_notify(&pci->ep);
|
|
|
|
break;
|
|
default:
|
|
dev_err(dev, "INVALID device type %d\n", dw_plat_pcie->mode);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct dw_plat_pcie_of_data dw_plat_pcie_rc_of_data = {
|
|
.mode = DW_PCIE_RC_TYPE,
|
|
};
|
|
|
|
static const struct dw_plat_pcie_of_data dw_plat_pcie_ep_of_data = {
|
|
.mode = DW_PCIE_EP_TYPE,
|
|
};
|
|
|
|
static const struct of_device_id dw_plat_pcie_of_match[] = {
|
|
{
|
|
.compatible = "snps,dw-pcie",
|
|
.data = &dw_plat_pcie_rc_of_data,
|
|
},
|
|
{
|
|
.compatible = "snps,dw-pcie-ep",
|
|
.data = &dw_plat_pcie_ep_of_data,
|
|
},
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver dw_plat_pcie_driver = {
|
|
.driver = {
|
|
.name = "dw-pcie",
|
|
.of_match_table = dw_plat_pcie_of_match,
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
.probe = dw_plat_pcie_probe,
|
|
};
|
|
builtin_platform_driver(dw_plat_pcie_driver);
|