DW PCIE Linux驱动整理

发布时间 2023-04-21 19:26:18作者: DF11G

1. DTS

以imx6q为例,该SOC的DTS中对PCIE控制器的描述(对应dts文件:linux-4.14.75/arch/arm/boot/dts/imx6qd.dtsi)

        pcie: pcie@1ffc000 {
            compatible = "fsl,imx6q-pcie", "snps,dw-pcie";
            reg = <0x01ffc000 0x04000>,
                  <0x01f00000 0x80000>;
            reg-names = "dbi", "config";
            #address-cells = <3>;
            #size-cells = <2>;
            device_type = "pci";
            bus-range = <0x00 0xff>;
            ranges = <0x81000000 0 0          0x01f80000 0 0x00010000 /* downstream I/O */
                  0x82000000 0 0x01000000 0x01000000 0 0x00f00000>; /* non-prefetchable memory */
            num-lanes = <1>;
            interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
            interrupt-names = "msi";
            #interrupt-cells = <1>;
            interrupt-map-mask = <0 0 0 0x7>;
            interrupt-map = <0 0 0 1 &gpc GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>,
                    <0 0 0 2 &gpc GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>,
                    <0 0 0 3 &gpc GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>,
                    <0 0 0 4 &gpc GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&clks IMX6QDL_CLK_PCIE_AXI>,
                 <&clks IMX6QDL_CLK_LVDS1_GATE>,
                 <&clks IMX6QDL_CLK_PCIE_REF_125M>;
            clock-names = "pcie", "pcie_bus", "pcie_phy";
            status = "disabled";
        };

对应的寄存器手册:

 可以看到,0x1FFC0000是处理器中PCIE控制器的基地址,对应了DTS中<reg-names = "dbi">表项。

2.平台驱动

(1)平台驱动的匹配

static const struct of_device_id imx6_pcie_of_match[] = {
    { .compatible = "fsl,imx6q-pcie",  .data = (void *)IMX6Q,  },
    { .compatible = "fsl,imx6sx-pcie", .data = (void *)IMX6SX, },
    { .compatible = "fsl,imx6qp-pcie", .data = (void *)IMX6QP, },
    { .compatible = "fsl,imx7d-pcie",  .data = (void *)IMX7D,  },
    {},
};

static struct platform_driver imx6_pcie_driver = {
    .driver = {
        .name    = "imx6q-pcie",
        .of_match_table = imx6_pcie_of_match,
        .suppress_bind_attrs = true,
    },
    .probe    = imx6_pcie_probe,
    .shutdown = imx6_pcie_shutdown,
};

static int __init imx6_pcie_init(void)
{
    /*
     * Since probe() can be deferred we need to make sure that
     * hook_fault_code is not called after __init memory is freed
     * by kernel and since imx6q_pcie_abort_handler() is a no-op,
     * we can install the handler here without risking it
     * accessing some uninitialized driver state.
     */
    hook_fault_code(8, imx6q_pcie_abort_handler, SIGBUS, 0,
            "external abort on non-linefetch");

    return platform_driver_register(&imx6_pcie_driver);
}
device_initcall(imx6_pcie_init);

imx6_pcie_of_match匹配表中的"fsl,imx6q-pcie"与dts吻合,即可进入probe函数。

(2)probe函数

linux-4.14.75/drivers/pci/dwc/pci-imx6.c

static int imx6_pcie_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct dw_pcie *pci;
    struct imx6_pcie *imx6_pcie;
    struct resource *dbi_base;
    struct device_node *node = dev->of_node;
    int ret;

    imx6_pcie = devm_kzalloc(dev, sizeof(*imx6_pcie), GFP_KERNEL); 
    if (!imx6_pcie)
        return -ENOMEM;

    pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); //分配PCI数据结构
    if (!pci)
        return -ENOMEM;

    pci->dev = dev;
    pci->ops = &dw_pcie_ops; //指向DW通用OPS操作接口

    imx6_pcie->pci = pci;
    imx6_pcie->variant =
        (enum imx6_pcie_variants)of_device_get_match_data(dev);

    dbi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取设备树节点中的第0个reg属性,即0x01ffc000,对应DBI物理基地址
    pci->dbi_base = devm_ioremap_resource(dev, dbi_base);  //将DBI物理基地址通过ioremap映射为虚拟地址,记入pci->dbi_base变量
    if (IS_ERR(pci->dbi_base))
        return PTR_ERR(pci->dbi_base);

    /* Fetch GPIOs */
    imx6_pcie->reset_gpio = of_get_named_gpio(node, "reset-gpio", 0);
    imx6_pcie->gpio_active_high = of_property_read_bool(node,
                        "reset-gpio-active-high");
    if (gpio_is_valid(imx6_pcie->reset_gpio)) {
        ret = devm_gpio_request_one(dev, imx6_pcie->reset_gpio,
                imx6_pcie->gpio_active_high ?
                    GPIOF_OUT_INIT_HIGH :
                    GPIOF_OUT_INIT_LOW,
                "PCIe reset");
        if (ret) {
            dev_err(dev, "unable to get reset gpio\n");
            return ret;
        }
    } else if (imx6_pcie->reset_gpio == -EPROBE_DEFER) {
        return imx6_pcie->reset_gpio;
    }

    /* Fetch clocks */
    imx6_pcie->pcie_phy = devm_clk_get(dev, "pcie_phy");
    if (IS_ERR(imx6_pcie->pcie_phy)) {
        dev_err(dev, "pcie_phy clock source missing or invalid\n");
        return PTR_ERR(imx6_pcie->pcie_phy);
    }

    imx6_pcie->pcie_bus = devm_clk_get(dev, "pcie_bus");
    if (IS_ERR(imx6_pcie->pcie_bus)) {
        dev_err(dev, "pcie_bus clock source missing or invalid\n");
        return PTR_ERR(imx6_pcie->pcie_bus);
    }

    imx6_pcie->pcie = devm_clk_get(dev, "pcie");
    if (IS_ERR(imx6_pcie->pcie)) {
        dev_err(dev, "pcie clock source missing or invalid\n");
        return PTR_ERR(imx6_pcie->pcie);
    }

    switch (imx6_pcie->variant) {
    case IMX6SX:
        imx6_pcie->pcie_inbound_axi = devm_clk_get(dev,
                               "pcie_inbound_axi");
        if (IS_ERR(imx6_pcie->pcie_inbound_axi)) {
            dev_err(dev, "pcie_inbound_axi clock missing or invalid\n");
            return PTR_ERR(imx6_pcie->pcie_inbound_axi);
        }
        break;
    case IMX7D:
        imx6_pcie->pciephy_reset = devm_reset_control_get_exclusive(dev,
                                        "pciephy");
        if (IS_ERR(imx6_pcie->pciephy_reset)) {
            dev_err(dev, "Failed to get PCIEPHY reset control\n");
            return PTR_ERR(imx6_pcie->pciephy_reset);
        }

        imx6_pcie->apps_reset = devm_reset_control_get_exclusive(dev,
                                     "apps");
        if (IS_ERR(imx6_pcie->apps_reset)) {
            dev_err(dev, "Failed to get PCIE APPS reset control\n");
            return PTR_ERR(imx6_pcie->apps_reset);
        }
        break;
    default:
        break;
    }

    /* Grab GPR config register range */
    imx6_pcie->iomuxc_gpr =
         syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr");
    if (IS_ERR(imx6_pcie->iomuxc_gpr)) {
        dev_err(dev, "unable to find iomuxc registers\n");
        return PTR_ERR(imx6_pcie->iomuxc_gpr);
    }

    /* Grab PCIe PHY Tx Settings */
    if (of_property_read_u32(node, "fsl,tx-deemph-gen1",
                 &imx6_pcie->tx_deemph_gen1))
        imx6_pcie->tx_deemph_gen1 = 0;

    if (of_property_read_u32(node, "fsl,tx-deemph-gen2-3p5db",
                 &imx6_pcie->tx_deemph_gen2_3p5db))
        imx6_pcie->tx_deemph_gen2_3p5db = 0;

    if (of_property_read_u32(node, "fsl,tx-deemph-gen2-6db",
                 &imx6_pcie->tx_deemph_gen2_6db))
        imx6_pcie->tx_deemph_gen2_6db = 20;

    if (of_property_read_u32(node, "fsl,tx-swing-full",
                 &imx6_pcie->tx_swing_full))
        imx6_pcie->tx_swing_full = 127;

    if (of_property_read_u32(node, "fsl,tx-swing-low",
                 &imx6_pcie->tx_swing_low))
        imx6_pcie->tx_swing_low = 127;

    /* Limit link speed */
    ret = of_property_read_u32(node, "fsl,max-link-speed",
                   &imx6_pcie->link_gen);
    if (ret)
        imx6_pcie->link_gen = 1;

    imx6_pcie->vpcie = devm_regulator_get_optional(&pdev->dev, "vpcie");
    if (IS_ERR(imx6_pcie->vpcie)) {
        if (PTR_ERR(imx6_pcie->vpcie) == -EPROBE_DEFER)
            return -EPROBE_DEFER;
        imx6_pcie->vpcie = NULL;
    }

    platform_set_drvdata(pdev, imx6_pcie);

    ret = imx6_add_pcie_port(imx6_pcie, pdev); //下面展开
    if (ret < 0)
        return ret;

    return 0;
}

 

static int imx6_add_pcie_port(struct imx6_pcie *imx6_pcie,
                  struct platform_device *pdev)
{
    struct dw_pcie *pci = imx6_pcie->pci;
    struct pcie_port *pp = &pci->pp;
    struct device *dev = &pdev->dev;
    int ret;

    if (IS_ENABLED(CONFIG_PCI_MSI)) {
        pp->msi_irq = platform_get_irq_byname(pdev, "msi"); //获取中断信息
        if (pp->msi_irq <= 0) {
            dev_err(dev, "failed to get MSI irq\n");
            return -ENODEV;
        }

        ret = devm_request_irq(dev, pp->msi_irq,
                       imx6_pcie_msi_handler,
                       IRQF_SHARED | IRQF_NO_THREAD,
                       "mx6-pcie-msi", imx6_pcie);
        if (ret) {
            dev_err(dev, "failed to request MSI irq\n");
            return ret;
        }
    }

    pp->root_bus_nr = -1;
    pp->ops = &imx6_pcie_host_ops;

    ret = dw_pcie_host_init(pp); //调用DW通用host初始化接口,下面展开
    if (ret) {
        dev_err(dev, "failed to initialize host\n");
        return ret;
    }

    return 0;
}

 

linux-4.14.75/drivers/pci/dwc/pcie-designware-host.c

int dw_pcie_host_init(struct pcie_port *pp)
{
    struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
    struct device *dev = pci->dev;
    struct device_node *np = dev->of_node;
    struct platform_device *pdev = to_platform_device(dev);
    struct pci_bus *bus, *child;
    struct pci_host_bridge *bridge;
    struct resource *cfg_res;
    int i, ret;
    struct resource_entry *win, *tmp;

    cfg_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config"); //获取设备树节点中的config reg属性,即0x01f00000,对应cfg基地址,即配置空间基地址
    if (cfg_res) {
        pp->cfg0_size = resource_size(cfg_res) / 2; //将cfg空间一分为二
        pp->cfg1_size = resource_size(cfg_res) / 2;
        pp->cfg0_base = cfg_res->start;
        pp->cfg1_base = cfg_res->start + pp->cfg0_size;
    } else if (!pp->va_cfg0_base) {
        dev_err(dev, "missing *config* reg space\n");
    }

    bridge = pci_alloc_host_bridge(0);
    if (!bridge)
        return -ENOMEM;

    ret = of_pci_get_host_bridge_resources(np, 0, 0xff,
                    &bridge->windows, &pp->io_base);
    if (ret)
        return ret;

    ret = devm_request_pci_bus_resources(dev, &bridge->windows);
    if (ret)
        goto error;

    /* Get the I/O and memory ranges from DT */
    resource_list_for_each_entry_safe(win, tmp, &bridge->windows) {
        switch (resource_type(win->res)) {
        case IORESOURCE_IO:
            ret = pci_remap_iospace(win->res, pp->io_base);
            if (ret) {
                dev_warn(dev, "error %d: failed to map resource %pR\n",
                     ret, win->res);
                resource_list_destroy_entry(win);
            } else {
                pp->io = win->res;
                pp->io->name = "I/O";
                pp->io_size = resource_size(pp->io);
                pp->io_bus_addr = pp->io->start - win->offset;
            }
            break;
        case IORESOURCE_MEM:
            pp->mem = win->res;
            pp->mem->name = "MEM";
            pp->mem_size = resource_size(pp->mem);
            pp->mem_bus_addr = pp->mem->start - win->offset;
            break;
        case 0:
            pp->cfg = win->res;
            pp->cfg0_size = resource_size(pp->cfg) / 2;
            pp->cfg1_size = resource_size(pp->cfg) / 2;
            pp->cfg0_base = pp->cfg->start;
            pp->cfg1_base = pp->cfg->start + pp->cfg0_size;
            break;
        case IORESOURCE_BUS:
            pp->busn = win->res;
            break;
        }
    }

    if (!pci->dbi_base) {
        pci->dbi_base = devm_pci_remap_cfgspace(dev,  //如果dbi_base地址没有被映射,重新进行映射
                        pp->cfg->start,
                        resource_size(pp->cfg));
        if (!pci->dbi_base) {
            dev_err(dev, "error with ioremap\n");
            ret = -ENOMEM;
            goto error;
        }
    }

    pp->mem_base = pp->mem->start;

    if (!pp->va_cfg0_base) {
        pp->va_cfg0_base = devm_pci_remap_cfgspace(dev,   //将cfg0物理基地址通过ioremap映射为虚拟地址,记入pp->va_cfg0_base变量
                    pp->cfg0_base, pp->cfg0_size);
        if (!pp->va_cfg0_base) {
            dev_err(dev, "error with ioremap in function\n");
            ret = -ENOMEM;
            goto error;
        }
    }

    if (!pp->va_cfg1_base) {
        pp->va_cfg1_base = devm_pci_remap_cfgspace(dev,   //将cfg0物理基地址通过ioremap映射为虚拟地址,记入pp->va_cfg1_base变量
                        pp->cfg1_base,
                        pp->cfg1_size);
        if (!pp->va_cfg1_base) {
            dev_err(dev, "error with ioremap\n");
            ret = -ENOMEM;
            goto error;
        }
    }

    ret = of_property_read_u32(np, "num-viewport", &pci->num_viewport);
    if (ret)
        pci->num_viewport = 2;

    if (IS_ENABLED(CONFIG_PCI_MSI)) {
        if (!pp->ops->msi_host_init) {
            pp->irq_domain = irq_domain_add_linear(dev->of_node,
                        MAX_MSI_IRQS, &msi_domain_ops,
                        &dw_pcie_msi_chip);
            if (!pp->irq_domain) {
                dev_err(dev, "irq domain init failed\n");
                ret = -ENXIO;
                goto error;
            }

            for (i = 0; i < MAX_MSI_IRQS; i++)
                irq_create_mapping(pp->irq_domain, i);
        } else {
            ret = pp->ops->msi_host_init(pp, &dw_pcie_msi_chip);
            if (ret < 0)
                goto error;
        }
    }

    if (pp->ops->host_init) {
        ret = pp->ops->host_init(pp);
        if (ret)
            goto error;
    }

    pp->root_bus_nr = pp->busn->start;

    bridge->dev.parent = dev;
    bridge->sysdata = pp;
    bridge->busnr = pp->root_bus_nr;
    bridge->ops = &dw_pcie_ops;
    bridge->map_irq = of_irq_parse_and_map_pci;
    bridge->swizzle_irq = pci_common_swizzle;
    if (IS_ENABLED(CONFIG_PCI_MSI)) {
        bridge->msi = &dw_pcie_msi_chip;
        dw_pcie_msi_chip.dev = dev;
    }

    ret = pci_scan_root_bus_bridge(bridge); //开始扫描PCI总线
    if (ret)
        goto error;

    bus = bridge->bus;

    if (pp->ops->scan_bus)
        pp->ops->scan_bus(pp);

    pci_bus_size_bridges(bus);
    pci_bus_assign_resources(bus);

    list_for_each_entry(child, &bus->children, node)
        pcie_bus_configure_settings(child);

    pci_bus_add_devices(bus);  //添加PCI设备
    return 0;

error:
    pci_free_host_bridge(bridge);
    return ret;
}