linux之PCIE 的RC驱动加载和初始化
1.简介
RK3588 PCIe RC和EP使用同一个平台驱动,其主要的作用是解析设备树中的资源、初始化中断、使能电源、初始化PHY、使能时钟和释放复位,然后根据compatible属性初始化RC或者EP驱动。
2.入口
平台驱动的定义如下,当compatible属性为"rockchip,rk3588-pcie",则初始化RC驱动,当compatible属性为"rockchip,rk3588-pcie-ep",则初始化EP驱动。入口函数为rk_pcie_probe。
[drivers/pci/controller/dwc/pcie-dw-rockchip.c]
static const struct rk_pcie_of_data rk_pcie_rc_of_data = {
.mode = RK_PCIE_RC_TYPE,
};
static const struct rk_pcie_of_data rk_pcie_ep_of_data = {
.mode = RK_PCIE_EP_TYPE,
};
static const struct of_device_id rk_pcie_of_match[] = {
{
.compatible = "rockchip,rk1808-pcie",
.data = &rk_pcie_rc_of_data,
},
{
.compatible = "rockchip,rk1808-pcie-ep",
.data = &rk_pcie_ep_of_data,
},
{
.compatible = "rockchip,rk3568-pcie",
.data = &rk_pcie_rc_of_data,
},
{
.compatible = "rockchip,rk3568-pcie-ep",
.data = &rk_pcie_ep_of_data,
},
{
.compatible = "rockchip,rk3588-pcie",
.data = &rk_pcie_rc_of_data,
},
{
.compatible = "rockchip,rk3588-pcie-ep",
.data = &rk_pcie_ep_of_data,
},
{},
};
MODULE_DEVICE_TABLE(of, rk_pcie_of_match);
static struct platform_driver rk_plat_pcie_driver = {
.driver = {
.name = "rk-pcie",
.of_match_table = rk_pcie_of_match,
.suppress_bind_attrs = true,
.pm = &rockchip_dw_pcie_pm_ops,
},
.probe = rk_pcie_probe,
};
module_platform_driver(rk_plat_pcie_driver);
3.初始化流程
驱动的初始化函数rk_pcie_probe可以直接调用,也可以放在内核线程执行(配置CONFIG_PCIE_RK_THREADED_INIT选项)。rk_pcie_probe函数的执行流程如下图所示,主要的工作有:
- 设置dw_pcie_ops回调函数,主要用于开始链路循环和查询链路训练是否成功
- 从设备树中解析并处理资源,如解析并映射"pcie-dbi"、"pcie-apb"地址,获取PERST#的GPIO和延时时间,延时时间默认为200毫秒。
- 获取sys中断编号,注册sys中断处理函数。sys中断的处理函数为rk_pcie_sys_irq_handler。
- 创建处理INTx中断的irq_domain,将INTx中断映射为虚拟中断。
- 获取legacy中断(INTx)编号,注册legacy中断处理函数。同时屏蔽所有INTx中断。legacy中断的处理函数为rk_pcie_legacy_int_handler。
- 根据设备树配置的模式,设置PCIe模式(通过PCIE_CLIENT_GENERAL_CON寄存器设置)。RC模式,还需要配置CLKREQ#,disable L1 PM Substates。
- RC模式,调用rk_add_pcie_port函数初始化。
- EP模式,调用rk_pcie_add_ep函数初始化。
- 配置DMA功能。

4、RC的初始化函数 rk_add_pcie_port
当RC的模式为RK_PCIE_EP_TYPE时,平台驱动调用rk_add_pcie_port函数初始化RC,流程如下图所示,具体的工作有:
- 设置Host初始化回调函数结构体rk_pcie_host_ops,该结构体的实现和SoC硬件相关。
- 分配pci_host_bridge数据结构,并解析设备树中的"bus-range"、"ranges"、"dma-ranges"属性,后面详细分析该函数。
- 初始化中断
- 设置MSI/MSI-X中断控制器的irq_chip,该结构体用来响应、屏蔽、解除屏蔽、设置MSI/MSI-X中断功能。
- 创建MSI/MSI-X中断的第一级irq_domain,对应的irq_domain_ops为dw_pcie_msi_domain_ops。
- 创建MSI/MSI-X中断的第二级irq_domain,对应的msi_domain_info为dw_pcie_msi_domain_info,第一级和第二级的irq_domain级联。
- 映射MSI Data的地址。
- 设置Root Bus配置空间访问方法为dw_pcie_ops,其他Bus的配置空间访问方法为dw_child_pcie_ops。
- 初始化RC Port相关的配置和建立link。
- 初始化RC Port相关的配置。
- 设置速度、lanes数量、lanes的编号、link width,使能fast link。
- 关闭和屏蔽MSI中断。
- 设置RC Inbound BAR为64位P-MEM。
- 设置INTx中断为INTA#。
- 设置Primary、Secondary、Subordinate总线编号分别为0、1、0xFF。
- 使能IO、MEM、Bus Master、SERR。
- 配置OUT Bound MEM类型的ATU。主要设置Region的方向和编号、CPU低32位地址、CPU高32位地址、Region的大小、PCIe低32位地址、PCIe高32位地址、匹配的Function Number和TLP类型,最后使能Region。
- 配置OUT Bound IO类型的ATU。配置内容和步骤8一样。
- 设置RC Port的类型为PCI_CLASS_BRIDGE_PCI。
- 关闭RC Inbound的BAR0和BAR1。
- 建立link。
- 拉低PERST#,复位总线上的所有设备。
- 先关闭LTSSM,清除link状态,使能client reset or link down中断,最后使能LTSSM。
- 延时,CEM要求PERST#的时间至少为100ms+100us,这里延时200ms。
- 拉高PERST#,释放复位。
- 等待PCIe链路硬件link训练完成,建立link。
- 初始化RC Port相关的配置。
- 枚举PCIe总线,后面详细分析该函数。

如下图所示,pci_host_bridge数据结构的分配和桥地址资源的解析在devm_pci_alloc_host_bridge函数中,具体的工作有:
- 首先分配pci_host_bridge数据结构。
- 设置中断相关的回调函数,pci_common_swizzle用于处理PCI设备INTx#中断引脚路由的函数,将PCI设备中断引脚INTx#通过旋转的方式接到PCI Host主桥上。of_irq_parse_and_map_pci用于将INTx#中断号映射为Linux软件中断号。
- 请求资源。
- 从设备树中解析"bus-range"属性,即总线编号资源,同时转总线编号范围换成resource,最后添加到pci_host_bridge结构体中的windows链表中。
- 从设备树中解析"range"属性,即Outbound地址资源(CPU地址、PCI地址、MEM和IO地址类型),同时将地址资源换成resource,最后添加到pci_host_bridge结构体中的windows链表中。
- 从设备树中解析"dma-ranges"属性,即Inbound地址资源(CPU地址、PCI地址、MEM地址类型),同时将地址资源换成resource,最后添加到pci_host_bridge结构体中的windows链表中。

桥地址资源使用下面的回调函数解析:
[drivers/of/address.c]
static struct of_bus of_busses[] = {
#ifdef CONFIG_PCI
/* PCI */
{
.name = "pci",
.addresses = "assigned-addresses",
.match = of_bus_pci_match,
.count_cells = of_bus_pci_count_cells,
.map = of_bus_pci_map,
.translate = of_bus_pci_translate,
.has_flags = true,
.get_flags = of_bus_pci_get_flags,
},
......
};
static unsigned int of_bus_pci_get_flags(const __be32 *addr)
{
unsigned int flags = 0;
u32 w = be32_to_cpup(addr);
if (!IS_ENABLED(CONFIG_PCI))
return 0;
switch((w >> 24) & 0x03) {
case 0x01:
flags |= IORESOURCE_IO; // IO
break;
case 0x02: /* 32 bits */
case 0x03: /* 64 bits */
flags |= IORESOURCE_MEM; // MEM
break;
}
if (w & 0x40000000)
flags |= IORESOURCE_PREFETCH; // P-MEM
return flags;
}
5.ATU
RC初始化的时候,会调用dw_pcie_prog_outbound_atu函数配置Outbound ATU region。每个ATU region有7组寄存器,分别是1个VIEWPORT寄存器、3组CTRL寄存器、1组BASE寄存器、1组TARGET寄存器、1组LIMIT寄存器。
5.1.VIEWPORT寄存器
ATU region的7组寄存器地址相同,设置某个region,由IATU_VIEWPORT_OFF寄存器设置。如下图所示,REGION_INDEX设置region的编号,REGION_DIR设置region的方向。

5.2.CTRL寄存器
下图画出了两组CTRL寄存器,第三组用于配置Virtual Function,这里不介绍。第一组CTRL寄存器用于配置Outbound/Inbound的传输的类型、TC、ATTR、Function编号等。第二组CTRL寄存器用于配置Outbound/Inbound的MSG_CODE、BAR编号、使能Region等。

5.3.BASE寄存器
如下图所示,BASE寄存器共有2两个,一个配置Outbound/Inbound的低32的BASE地址,另一个配置Outbound/Inbound的高32的BASE地址。LWR_BASE_HW为硬件设置,表示该region地址的最小范围(4kB、8kB、16kB、32kB、64kB,默认为64kB),Outbound方向时LWR_BASE_RW表示存储器域基地址,Inbound方向时LWR_BASE_RW表示PCIe域基地址,UPPER_BASE_RW表示对应基地址的高32位。

5.4.TARGET寄存器
如下图所示,TARGET寄存器共有2两个,一个配置Outbound/Inbound的低32的TARGET地址,另一个配置Outbound/Inbound的高32的TARGET地址。LWR_TARGET_HW为硬件设置,表示该region地址的最小范围(4kB、8kB、16kB、32kB、64kB,默认为64kB),Outbound方向时LWR_TARGET_HW表示PCIe域基地址,Inbound方向时LWR_TARGET_HW表示存储器域基地址,UPPER_TARGET_RW表示对应基地址的高32位。

5.5.LIMIT寄存器
如下图所示,LIMIT寄存器用于配置region的大小。

更多推荐

所有评论(0)