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函数的执行流程如下图所示,主要的工作有:

  1. 设置dw_pcie_ops回调函数,主要用于开始链路循环和查询链路训练是否成功
  2. 从设备树中解析并处理资源,如解析并映射"pcie-dbi"、"pcie-apb"地址,获取PERST#的GPIO和延时时间,延时时间默认为200毫秒。
  3. 获取sys中断编号,注册sys中断处理函数。sys中断的处理函数为rk_pcie_sys_irq_handler。
  4. 创建处理INTx中断的irq_domain,将INTx中断映射为虚拟中断。
  5. 获取legacy中断(INTx)编号,注册legacy中断处理函数。同时屏蔽所有INTx中断。legacy中断的处理函数为rk_pcie_legacy_int_handler。
  6. 根据设备树配置的模式,设置PCIe模式(通过PCIE_CLIENT_GENERAL_CON寄存器设置)。RC模式,还需要配置CLKREQ#,disable L1 PM Substates。
  7. RC模式,调用rk_add_pcie_port函数初始化。
  8. EP模式,调用rk_pcie_add_ep函数初始化。
  9. 配置DMA功能。

4、RC的初始化函数 rk_add_pcie_port

当RC的模式为RK_PCIE_EP_TYPE时,平台驱动调用rk_add_pcie_port函数初始化RC,流程如下图所示,具体的工作有:

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

        如下图所示,pci_host_bridge数据结构的分配和桥地址资源的解析在devm_pci_alloc_host_bridge函数中,具体的工作有:

  1. 首先分配pci_host_bridge数据结构。
  2. 设置中断相关的回调函数,pci_common_swizzle用于处理PCI设备INTx#中断引脚路由的函数,将PCI设备中断引脚INTx#通过旋转的方式接到PCI Host主桥上。of_irq_parse_and_map_pci用于将INTx#中断号映射为Linux软件中断号。
  3. 请求资源。
    1. 从设备树中解析"bus-range"属性,即总线编号资源,同时转总线编号范围换成resource,最后添加到pci_host_bridge结构体中的windows链表中。
    2. 从设备树中解析"range"属性,即Outbound地址资源(CPU地址、PCI地址、MEM和IO地址类型),同时将地址资源换成resource,最后添加到pci_host_bridge结构体中的windows链表中。
    3. 从设备树中解析"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的大小。

更多推荐