"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>
542 lines
13 KiB
C
542 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* PCIe controller driver for Renesas R-Car Gen4 Series SoCs
|
|
* Copyright (C) 2022-2023 Renesas Electronics Corporation
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include "../../pci.h"
|
|
#include "pcie-designware.h"
|
|
|
|
/* Renesas-specific */
|
|
/* PCIe Mode Setting Register 0 */
|
|
#define PCIEMSR0 0x0000
|
|
#define BIFUR_MOD_SET_ON BIT(0)
|
|
#define DEVICE_TYPE_EP 0
|
|
#define DEVICE_TYPE_RC BIT(4)
|
|
|
|
/* PCIe Interrupt Status 0 */
|
|
#define PCIEINTSTS0 0x0084
|
|
|
|
/* PCIe Interrupt Status 0 Enable */
|
|
#define PCIEINTSTS0EN 0x0310
|
|
#define MSI_CTRL_INT BIT(26)
|
|
#define SMLH_LINK_UP BIT(7)
|
|
#define RDLH_LINK_UP BIT(6)
|
|
|
|
/* PCIe DMA Interrupt Status Enable */
|
|
#define PCIEDMAINTSTSEN 0x0314
|
|
#define PCIEDMAINTSTSEN_INIT GENMASK(15, 0)
|
|
|
|
/* PCIe Reset Control Register 1 */
|
|
#define PCIERSTCTRL1 0x0014
|
|
#define APP_HOLD_PHY_RST BIT(16)
|
|
#define APP_LTSSM_ENABLE BIT(0)
|
|
|
|
#define RCAR_NUM_SPEED_CHANGE_RETRIES 10
|
|
#define RCAR_MAX_LINK_SPEED 4
|
|
|
|
#define RCAR_GEN4_PCIE_EP_FUNC_DBI_OFFSET 0x1000
|
|
#define RCAR_GEN4_PCIE_EP_FUNC_DBI2_OFFSET 0x800
|
|
|
|
struct rcar_gen4_pcie {
|
|
struct dw_pcie dw;
|
|
void __iomem *base;
|
|
struct platform_device *pdev;
|
|
enum dw_pcie_device_mode mode;
|
|
};
|
|
#define to_rcar_gen4_pcie(_dw) container_of(_dw, struct rcar_gen4_pcie, dw)
|
|
|
|
/* Common */
|
|
static void rcar_gen4_pcie_ltssm_enable(struct rcar_gen4_pcie *rcar,
|
|
bool enable)
|
|
{
|
|
u32 val;
|
|
|
|
val = readl(rcar->base + PCIERSTCTRL1);
|
|
if (enable) {
|
|
val |= APP_LTSSM_ENABLE;
|
|
val &= ~APP_HOLD_PHY_RST;
|
|
} else {
|
|
/*
|
|
* Since the datasheet of R-Car doesn't mention how to assert
|
|
* the APP_HOLD_PHY_RST, don't assert it again. Otherwise,
|
|
* hang-up issue happened in the dw_edma_core_off() when
|
|
* the controller didn't detect a PCI device.
|
|
*/
|
|
val &= ~APP_LTSSM_ENABLE;
|
|
}
|
|
writel(val, rcar->base + PCIERSTCTRL1);
|
|
}
|
|
|
|
static int rcar_gen4_pcie_link_up(struct dw_pcie *dw)
|
|
{
|
|
struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
|
|
u32 val, mask;
|
|
|
|
val = readl(rcar->base + PCIEINTSTS0);
|
|
mask = RDLH_LINK_UP | SMLH_LINK_UP;
|
|
|
|
return (val & mask) == mask;
|
|
}
|
|
|
|
/*
|
|
* Manually initiate the speed change. Return 0 if change succeeded; otherwise
|
|
* -ETIMEDOUT.
|
|
*/
|
|
static int rcar_gen4_pcie_speed_change(struct dw_pcie *dw)
|
|
{
|
|
u32 val;
|
|
int i;
|
|
|
|
val = dw_pcie_readl_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL);
|
|
val &= ~PORT_LOGIC_SPEED_CHANGE;
|
|
dw_pcie_writel_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
|
|
|
|
val = dw_pcie_readl_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL);
|
|
val |= PORT_LOGIC_SPEED_CHANGE;
|
|
dw_pcie_writel_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
|
|
|
|
for (i = 0; i < RCAR_NUM_SPEED_CHANGE_RETRIES; i++) {
|
|
val = dw_pcie_readl_dbi(dw, PCIE_LINK_WIDTH_SPEED_CONTROL);
|
|
if (!(val & PORT_LOGIC_SPEED_CHANGE))
|
|
return 0;
|
|
usleep_range(10000, 11000);
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/*
|
|
* Enable LTSSM of this controller and manually initiate the speed change.
|
|
* Always return 0.
|
|
*/
|
|
static int rcar_gen4_pcie_start_link(struct dw_pcie *dw)
|
|
{
|
|
struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
|
|
int i, changes;
|
|
|
|
rcar_gen4_pcie_ltssm_enable(rcar, true);
|
|
|
|
/*
|
|
* Require direct speed change with retrying here if the link_gen is
|
|
* PCIe Gen2 or higher.
|
|
*/
|
|
changes = min_not_zero(dw->link_gen, RCAR_MAX_LINK_SPEED) - 1;
|
|
|
|
/*
|
|
* Since dw_pcie_setup_rc() sets it once, PCIe Gen2 will be trained.
|
|
* So, this needs remaining times for up to PCIe Gen4 if RC mode.
|
|
*/
|
|
if (changes && rcar->mode == DW_PCIE_RC_TYPE)
|
|
changes--;
|
|
|
|
for (i = 0; i < changes; i++) {
|
|
/* It may not be connected in EP mode yet. So, break the loop */
|
|
if (rcar_gen4_pcie_speed_change(dw))
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rcar_gen4_pcie_stop_link(struct dw_pcie *dw)
|
|
{
|
|
struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
|
|
|
|
rcar_gen4_pcie_ltssm_enable(rcar, false);
|
|
}
|
|
|
|
static int rcar_gen4_pcie_common_init(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
struct dw_pcie *dw = &rcar->dw;
|
|
u32 val;
|
|
int ret;
|
|
|
|
ret = clk_bulk_prepare_enable(DW_PCIE_NUM_CORE_CLKS, dw->core_clks);
|
|
if (ret) {
|
|
dev_err(dw->dev, "Enabling core clocks failed\n");
|
|
return ret;
|
|
}
|
|
|
|
if (!reset_control_status(dw->core_rsts[DW_PCIE_PWR_RST].rstc))
|
|
reset_control_assert(dw->core_rsts[DW_PCIE_PWR_RST].rstc);
|
|
|
|
val = readl(rcar->base + PCIEMSR0);
|
|
if (rcar->mode == DW_PCIE_RC_TYPE) {
|
|
val |= DEVICE_TYPE_RC;
|
|
} else if (rcar->mode == DW_PCIE_EP_TYPE) {
|
|
val |= DEVICE_TYPE_EP;
|
|
} else {
|
|
ret = -EINVAL;
|
|
goto err_unprepare;
|
|
}
|
|
|
|
if (dw->num_lanes < 4)
|
|
val |= BIFUR_MOD_SET_ON;
|
|
|
|
writel(val, rcar->base + PCIEMSR0);
|
|
|
|
ret = reset_control_deassert(dw->core_rsts[DW_PCIE_PWR_RST].rstc);
|
|
if (ret)
|
|
goto err_unprepare;
|
|
|
|
return 0;
|
|
|
|
err_unprepare:
|
|
clk_bulk_disable_unprepare(DW_PCIE_NUM_CORE_CLKS, dw->core_clks);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void rcar_gen4_pcie_common_deinit(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
struct dw_pcie *dw = &rcar->dw;
|
|
|
|
reset_control_assert(dw->core_rsts[DW_PCIE_PWR_RST].rstc);
|
|
clk_bulk_disable_unprepare(DW_PCIE_NUM_CORE_CLKS, dw->core_clks);
|
|
}
|
|
|
|
static int rcar_gen4_pcie_prepare(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
struct device *dev = rcar->dw.dev;
|
|
int err;
|
|
|
|
pm_runtime_enable(dev);
|
|
err = pm_runtime_resume_and_get(dev);
|
|
if (err < 0) {
|
|
dev_err(dev, "Runtime resume failed\n");
|
|
pm_runtime_disable(dev);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void rcar_gen4_pcie_unprepare(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
struct device *dev = rcar->dw.dev;
|
|
|
|
pm_runtime_put(dev);
|
|
pm_runtime_disable(dev);
|
|
}
|
|
|
|
static int rcar_gen4_pcie_get_resources(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
/* Renesas-specific registers */
|
|
rcar->base = devm_platform_ioremap_resource_byname(rcar->pdev, "app");
|
|
|
|
return PTR_ERR_OR_ZERO(rcar->base);
|
|
}
|
|
|
|
static const struct dw_pcie_ops dw_pcie_ops = {
|
|
.start_link = rcar_gen4_pcie_start_link,
|
|
.stop_link = rcar_gen4_pcie_stop_link,
|
|
.link_up = rcar_gen4_pcie_link_up,
|
|
};
|
|
|
|
static struct rcar_gen4_pcie *rcar_gen4_pcie_alloc(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct rcar_gen4_pcie *rcar;
|
|
|
|
rcar = devm_kzalloc(dev, sizeof(*rcar), GFP_KERNEL);
|
|
if (!rcar)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
rcar->dw.ops = &dw_pcie_ops;
|
|
rcar->dw.dev = dev;
|
|
rcar->pdev = pdev;
|
|
dw_pcie_cap_set(&rcar->dw, EDMA_UNROLL);
|
|
dw_pcie_cap_set(&rcar->dw, REQ_RES);
|
|
platform_set_drvdata(pdev, rcar);
|
|
|
|
return rcar;
|
|
}
|
|
|
|
/* Host mode */
|
|
static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *dw = to_dw_pcie_from_pp(pp);
|
|
struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
|
|
int ret;
|
|
u32 val;
|
|
|
|
gpiod_set_value_cansleep(dw->pe_rst, 1);
|
|
|
|
ret = rcar_gen4_pcie_common_init(rcar);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* According to the section 3.5.7.2 "RC Mode" in DWC PCIe Dual Mode
|
|
* Rev.5.20a and 3.5.6.1 "RC mode" in DWC PCIe RC databook v5.20a, we
|
|
* should disable two BARs to avoid unnecessary memory assignment
|
|
* during device enumeration.
|
|
*/
|
|
dw_pcie_writel_dbi2(dw, PCI_BASE_ADDRESS_0, 0x0);
|
|
dw_pcie_writel_dbi2(dw, PCI_BASE_ADDRESS_1, 0x0);
|
|
|
|
/* Enable MSI interrupt signal */
|
|
val = readl(rcar->base + PCIEINTSTS0EN);
|
|
val |= MSI_CTRL_INT;
|
|
writel(val, rcar->base + PCIEINTSTS0EN);
|
|
|
|
msleep(PCIE_T_PVPERL_MS); /* pe_rst requires 100msec delay */
|
|
|
|
gpiod_set_value_cansleep(dw->pe_rst, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rcar_gen4_pcie_host_deinit(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *dw = to_dw_pcie_from_pp(pp);
|
|
struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
|
|
|
|
gpiod_set_value_cansleep(dw->pe_rst, 1);
|
|
rcar_gen4_pcie_common_deinit(rcar);
|
|
}
|
|
|
|
static const struct dw_pcie_host_ops rcar_gen4_pcie_host_ops = {
|
|
.init = rcar_gen4_pcie_host_init,
|
|
.deinit = rcar_gen4_pcie_host_deinit,
|
|
};
|
|
|
|
static int rcar_gen4_add_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
struct dw_pcie_rp *pp = &rcar->dw.pp;
|
|
|
|
if (!IS_ENABLED(CONFIG_PCIE_RCAR_GEN4_HOST))
|
|
return -ENODEV;
|
|
|
|
pp->num_vectors = MAX_MSI_IRQS;
|
|
pp->ops = &rcar_gen4_pcie_host_ops;
|
|
|
|
return dw_pcie_host_init(pp);
|
|
}
|
|
|
|
static void rcar_gen4_remove_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
dw_pcie_host_deinit(&rcar->dw.pp);
|
|
}
|
|
|
|
/* Endpoint mode */
|
|
static void rcar_gen4_pcie_ep_pre_init(struct dw_pcie_ep *ep)
|
|
{
|
|
struct dw_pcie *dw = to_dw_pcie_from_ep(ep);
|
|
struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
|
|
int ret;
|
|
|
|
ret = rcar_gen4_pcie_common_init(rcar);
|
|
if (ret)
|
|
return;
|
|
|
|
writel(PCIEDMAINTSTSEN_INIT, rcar->base + PCIEDMAINTSTSEN);
|
|
}
|
|
|
|
static void rcar_gen4_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 void rcar_gen4_pcie_ep_deinit(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
writel(0, rcar->base + PCIEDMAINTSTSEN);
|
|
rcar_gen4_pcie_common_deinit(rcar);
|
|
}
|
|
|
|
static int rcar_gen4_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
|
|
unsigned int type, u16 interrupt_num)
|
|
{
|
|
struct dw_pcie *dw = 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);
|
|
default:
|
|
dev_err(dw->dev, "Unknown IRQ type\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pci_epc_features rcar_gen4_pcie_epc_features = {
|
|
.linkup_notifier = false,
|
|
.msi_capable = true,
|
|
.msix_capable = false,
|
|
.bar[BAR_1] = { .type = BAR_RESERVED, },
|
|
.bar[BAR_3] = { .type = BAR_RESERVED, },
|
|
.bar[BAR_5] = { .type = BAR_RESERVED, },
|
|
.align = SZ_1M,
|
|
};
|
|
|
|
static const struct pci_epc_features*
|
|
rcar_gen4_pcie_ep_get_features(struct dw_pcie_ep *ep)
|
|
{
|
|
return &rcar_gen4_pcie_epc_features;
|
|
}
|
|
|
|
static unsigned int rcar_gen4_pcie_ep_get_dbi_offset(struct dw_pcie_ep *ep,
|
|
u8 func_no)
|
|
{
|
|
return func_no * RCAR_GEN4_PCIE_EP_FUNC_DBI_OFFSET;
|
|
}
|
|
|
|
static unsigned int rcar_gen4_pcie_ep_get_dbi2_offset(struct dw_pcie_ep *ep,
|
|
u8 func_no)
|
|
{
|
|
return func_no * RCAR_GEN4_PCIE_EP_FUNC_DBI2_OFFSET;
|
|
}
|
|
|
|
static const struct dw_pcie_ep_ops pcie_ep_ops = {
|
|
.pre_init = rcar_gen4_pcie_ep_pre_init,
|
|
.init = rcar_gen4_pcie_ep_init,
|
|
.raise_irq = rcar_gen4_pcie_ep_raise_irq,
|
|
.get_features = rcar_gen4_pcie_ep_get_features,
|
|
.get_dbi_offset = rcar_gen4_pcie_ep_get_dbi_offset,
|
|
.get_dbi2_offset = rcar_gen4_pcie_ep_get_dbi2_offset,
|
|
};
|
|
|
|
static int rcar_gen4_add_dw_pcie_ep(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
struct dw_pcie_ep *ep = &rcar->dw.ep;
|
|
struct device *dev = rcar->dw.dev;
|
|
int ret;
|
|
|
|
if (!IS_ENABLED(CONFIG_PCIE_RCAR_GEN4_EP))
|
|
return -ENODEV;
|
|
|
|
ep->ops = &pcie_ep_ops;
|
|
|
|
ret = dw_pcie_ep_init(ep);
|
|
if (ret) {
|
|
rcar_gen4_pcie_ep_deinit(rcar);
|
|
return ret;
|
|
}
|
|
|
|
ret = dw_pcie_ep_init_registers(ep);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to initialize DWC endpoint registers\n");
|
|
dw_pcie_ep_deinit(ep);
|
|
rcar_gen4_pcie_ep_deinit(rcar);
|
|
}
|
|
|
|
dw_pcie_ep_init_notify(ep);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void rcar_gen4_remove_dw_pcie_ep(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
dw_pcie_ep_deinit(&rcar->dw.ep);
|
|
rcar_gen4_pcie_ep_deinit(rcar);
|
|
}
|
|
|
|
/* Common */
|
|
static int rcar_gen4_add_dw_pcie(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
rcar->mode = (uintptr_t)of_device_get_match_data(&rcar->pdev->dev);
|
|
|
|
switch (rcar->mode) {
|
|
case DW_PCIE_RC_TYPE:
|
|
return rcar_gen4_add_dw_pcie_rp(rcar);
|
|
case DW_PCIE_EP_TYPE:
|
|
return rcar_gen4_add_dw_pcie_ep(rcar);
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int rcar_gen4_pcie_probe(struct platform_device *pdev)
|
|
{
|
|
struct rcar_gen4_pcie *rcar;
|
|
int err;
|
|
|
|
rcar = rcar_gen4_pcie_alloc(pdev);
|
|
if (IS_ERR(rcar))
|
|
return PTR_ERR(rcar);
|
|
|
|
err = rcar_gen4_pcie_get_resources(rcar);
|
|
if (err)
|
|
return err;
|
|
|
|
err = rcar_gen4_pcie_prepare(rcar);
|
|
if (err)
|
|
return err;
|
|
|
|
err = rcar_gen4_add_dw_pcie(rcar);
|
|
if (err)
|
|
goto err_unprepare;
|
|
|
|
return 0;
|
|
|
|
err_unprepare:
|
|
rcar_gen4_pcie_unprepare(rcar);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void rcar_gen4_remove_dw_pcie(struct rcar_gen4_pcie *rcar)
|
|
{
|
|
switch (rcar->mode) {
|
|
case DW_PCIE_RC_TYPE:
|
|
rcar_gen4_remove_dw_pcie_rp(rcar);
|
|
break;
|
|
case DW_PCIE_EP_TYPE:
|
|
rcar_gen4_remove_dw_pcie_ep(rcar);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void rcar_gen4_pcie_remove(struct platform_device *pdev)
|
|
{
|
|
struct rcar_gen4_pcie *rcar = platform_get_drvdata(pdev);
|
|
|
|
rcar_gen4_remove_dw_pcie(rcar);
|
|
rcar_gen4_pcie_unprepare(rcar);
|
|
}
|
|
|
|
static const struct of_device_id rcar_gen4_pcie_of_match[] = {
|
|
{
|
|
.compatible = "renesas,rcar-gen4-pcie",
|
|
.data = (void *)DW_PCIE_RC_TYPE,
|
|
},
|
|
{
|
|
.compatible = "renesas,rcar-gen4-pcie-ep",
|
|
.data = (void *)DW_PCIE_EP_TYPE,
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rcar_gen4_pcie_of_match);
|
|
|
|
static struct platform_driver rcar_gen4_pcie_driver = {
|
|
.driver = {
|
|
.name = "pcie-rcar-gen4",
|
|
.of_match_table = rcar_gen4_pcie_of_match,
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
},
|
|
.probe = rcar_gen4_pcie_probe,
|
|
.remove_new = rcar_gen4_pcie_remove,
|
|
};
|
|
module_platform_driver(rcar_gen4_pcie_driver);
|
|
|
|
MODULE_DESCRIPTION("Renesas R-Car Gen4 PCIe controller driver");
|
|
MODULE_LICENSE("GPL");
|