static int __init imx_pcie_drv_init(void){return platform_driver_register(&imx_pcie_pltfm_driver);}在pcie驱动外部加了一层platform driver,下面看imx_pcie_pltfm_driver结构体定义:
static void __exit imx_pcie_drv_exit(void){platform_driver_unregister(&imx_pcie_pltfm_driver);}
module_init(imx_pcie_drv_init);module_exit(imx_pcie_drv_exit);
static struct platform_driver imx_pcie_pltfm_driver = {.driver = {.name= "imx-pcie",.owner= THIS_MODULE,},.probe= imx_pcie_pltfm_probe,};可以看到这里通过.driver.name字段来匹配platform driver,匹配之后调用imx_pcie_pltfm_probe函数进行探测,下面就需要去板极文件找添加"imx-pcie"字段platform device的代码了。 对于i.MX6Q SABRESD平台,板极文件在arch/arm/mach-mx6/board-mx6q_sabresd.c中,而添加pcie设备的代码可以找到在:
/* Add PCIe RC interface support */imx6q_add_pcie(&mx6_sabresd_pcie_data);
而imx6q_add_pcie()这个宏是定义在arch/arm/mach-mx6/devices-imx6q.h中的如下几行:
extern const struct imx_pcie_data imx6q_pcie_data __initconst;#define imx6q_add_pcie(pdata) imx_add_pcie(&imx6q_pcie_data, pdata)而imx_add_pcie是位于arch/arm/plat-mxc/devices/platform-imx-pcie.c中的,相关代码如下:
struct platform_device *__init imx_add_pcie(const struct imx_pcie_data *data,const struct imx_pcie_platform_data *pdata){struct resource res[] = {{.start = data->iobase,.end = data->iobase + data->iosize - 1,.flags = IORESOURCE_MEM,}, {.start = data->irq,.end = data->irq,.flags = IORESOURCE_IRQ,},};看到了才找到了真正需要的代码,resource是用来定义寄存器地址以及中断注册信息的,这里最后几行中我用红色加粗斜体字标出了imx-pcie字段,因为这个函数imx_add_platform_device正式我们要找的添加platform device函数,并且通过函数名匹配,这里可以看到pci的platform设备和platform驱动的名称都是imx-pcie,因此驱动和设备通过platform bus得以匹配。同时还通过struct imx_pcie_platform_data *pdata来传递驱动的platform_data,pdata的结构体声明在arch/arm/plat-mxc/include/mach/pcie.h中:
if (!fuse_dev_is_available(MXC_DEV_PCIE))return ERR_PTR(-ENODEV);
return imx_add_platform_device("imx-pcie", -1,res, ARRAY_SIZE(res),pdata, sizeof(*pdata));}
/** * struct imx_pcie_platform_data - optional platform data for pcie on i.MX * * @pcie_pwr_en:used for enable/disable pcie power (-EINVAL if unused) * @pcie_rst:used for reset pcie ep (-EINVAL if unused) * @pcie_wake_up:used for wake up (-EINVAL if unused) * @pcie_dis:used for disable pcie ep (-EINVAL if unused) */待会在驱动可以看到这个platform_data会在probe函数中被获取,这种在platform device中添加platform_data来向驱动传递额外的信息在内核中常常能见到。到这里,在内核中添加板级信息的部分已经结束了,内核在启动过程中会去执行init_machine()函数,而init_machine是一个函数指针,它指向的就是arch/arm/mach-mx6/board-mx6q_sabresd.c中的mx6_sabresd_board_init函数,可以看到这个结构体初始化语句:
struct imx_pcie_platform_data {unsigned int pcie_pwr_en;unsigned int pcie_rst;unsigned int pcie_wake_up;unsigned int pcie_dis;unsigned int type_ep; /* 1 EP, 0 RC */};#endif /* __ASM_ARCH_IMX_PCIE_H */
/* * initialize __mach_desc_MX6Q_SABRESD data structure. */MACHINE_START(MX6Q_SABRESD, "Freescale i.MX 6Quad/DualLite/Solo Sabre-SD Board")/* Maintainer: Freescale Semiconductor, Inc. */.boot_params = MX6_PHYS_OFFSET + 0x100,.fixup = fixup_mxc_board,.map_io = mx6_map_io,.init_irq = mx6_init_irq,.init_machine = mx6_sabresd_board_init,.timer = &mx6_sabresd_timer,.reserve = mx6q_sabresd_reserve,MACHINE_END到这里应该就能理解添加板级信息的整个过程了,然后就是进行驱动加载了,可以看到驱动加载是在pcie.c中的module_init()中进行的。一切就绪以后就开始跳向probe指针指向的imx_pcie_pltfm_probe,说实话个人觉得这个probe算是比较短的了。大体来分析一下:
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!mem) {dev_err(dev, "no mmio space\n");return -EINVAL;}这里的platform_get_resource()就是获取之前struct resource res[]={...}定义的内容,也就是与寄存器base address以及irq相关的信息。
/* Added for PCI abort handling */hook_fault_code(16 + 6, imx6q_pcie_abort_handler, SIGBUS, 0,"imprecise external abort");这里像是注册一个abort handling function,具体什么作用也不清楚。
base = ioremap_nocache(PCIE_ARB_END_ADDR - SZ_1M + 1, SZ_1M - SZ_16K);if (!base) {pr_err("error with ioremap in function %s\n", __func__);ret = PTR_ERR(base);return ret;}ioremap,为这个区间段的寄存器建立页表映射到内核空间,进行访问。 这里PCIE_ARB_END_ADDR的定义在arch/arm/plat-mxc/include/mach/mx6.h中:
#define PCIE_ARB_BASE_ADDR0x01000000#define PCIE_ARB_END_ADDR0x01FFFFFF显然base访问的是[PCIE_ARB_END_ADDR - SZ_1M + 1, PCIE_ARB_END_ADDR - SZ_16K]这一段
dbi_base = devm_ioremap(dev, mem->start, resource_size(mem));if (!dbi_base) {dev_err(dev, "can't map %pR\n", mem);ret = PTR_ERR(dbi_base);goto err_base;}这里用的devm_ioremap与ioremap很相似,唯一的区别引用一下这个网页上的回复http://www.spinics.net/lists/devicetree/msg07744.html的:In the PCIv3 driver, use devm_ioremap() instead of just ioremap(). when remapping the system controller in the PCIv3 driver, so the mapping will be automatically released on probe failure.而这里dbi_base经过对platform-imx-pcie.c中的分析,访问的是[ PCIE_ARB_END_ADDR - SZ_16K, PCIE_ARB_END_ADDR]这一段
/* FIXME the field name should be aligned to RM */imx_pcie_clrset(IOMUXC_GPR12_APP_LTSSM_ENABLE, 0 << 10, IOMUXC_GPR12);寄存器配置,其中对于PCIE的link成功与否比较关键的就是IOMUXC_GPR8和IOMUXC_GPR12,不过在reference manual中IOMUXC_GPR12的值已经被定死了,所以只能调节IOMUXC_GPR8了。这里imx_pcie_clrset是一个内联函数,定义如下:/* IMX PCIE GPR configure routines */static inline void imx_pcie_clrset(u32 mask, u32 val, void __iomem *addr){writel(((readl(addr) & ~mask) | (val & mask)), addr);}鉴于这里已经可以直接对地址进行直接读写,那么这块内存区域就已经被映射过了。找到IOMUXC_GPR8的定义,在arch/arm/mach-mx6/crm_regs.h文件中:/* IOMUXC */#define MXC_IOMUXC_BASEMX6_IO_ADDRESS(MX6Q_IOMUXC_BASE_ADDR)…………#define IOMUXC_GPR8(MXC_IOMUXC_BASE + 0x20)这里MX6Q_IOMUXC_BASE_ADDR被定义在arch/arm/plat-mxc/include/mach/mx6.h中:#define AIPS1_ARB_BASE_ADDR0x02000000………………#define ATZ1_BASE_ADDRAIPS1_ARB_BASE_ADDR…………#define AIPS1_OFF_BASE_ADDR(ATZ1_BASE_ADDR + 0x80000)…………#define MX6Q_IOMUXC_BASE_ADDR(AIPS1_OFF_BASE_ADDR + 0x60000)最终的值也就是0x0200_0000+0x8_0000+0x6_0000 = 0x020E_0000,参考i.MX6Q reference manual可以找到IOMUXC寄存器范围:[020E_0000, 020E_3FFF] 这里和驱动相对应。而MX6_IO_ADDRESS的定义在arch/arm/plat-mxc/include/mach/mx6.h中,定义如下的:#define PERIPBASE_VIRT0xF2000000…………#define MX6_IO_ADDRESS(x) (void __force __iomem *)((x) + PERIPBASE_VIRT)基本上就是对基地址的一个偏移量。注意:直接这样访问是无效的,首先必须要在MMU中建立页表映射到这段地址,即ioremap以后才可以从内核空间访问(这里能访问是因为这块地址之前肯定已经映射过了),只不过映射之后的关系是物理地址增加一个偏移量而已。
/* configure constant input signal to the pcie ctrl and phy */if (pdata->type_ep & 1)/* EP */imx_pcie_clrset(IOMUXC_GPR12_DEVICE_TYPE,PCI_EXP_TYPE_ENDPOINT<< 12, IOMUXC_GPR12);else/* RC */imx_pcie_clrset(IOMUXC_GPR12_DEVICE_TYPE,PCI_EXP_TYPE_ROOT_PORT << 12, IOMUXC_GPR12);imx_pcie_clrset(IOMUXC_GPR12_LOS_LEVEL, 9 << 4, IOMUXC_GPR12);
imx_pcie_clrset(IOMUXC_GPR8_TX_DEEMPH_GEN1, 0 << 0, IOMUXC_GPR8);imx_pcie_clrset(IOMUXC_GPR8_TX_DEEMPH_GEN2_3P5DB, 0 << 6, IOMUXC_GPR8);imx_pcie_clrset(IOMUXC_GPR8_TX_DEEMPH_GEN2_6DB, 20 << 12, IOMUXC_GPR8);imx_pcie_clrset(IOMUXC_GPR8_TX_SWING_FULL, 127 << 18, IOMUXC_GPR8);imx_pcie_clrset(IOMUXC_GPR8_TX_SWING_LOW, 127 << 25, IOMUXC_GPR8);
/* Enable the pwr, clks and so on */imx_pcie_enable_controller(dev);如注释所言,使能pcie的电源和时钟等,由于i.mx是由fuse100来供电的,因此需要通过GPIO的某个引脚来控制供电开关。这个函数很关键,下面简单介绍一下, 本身也不长。/* Enable PCIE power */
gpio_request(pdata->pcie_pwr_en, "PCIE POWER_EN");获取gpio引脚的使用权,并且将该gpio输出高电平。最后再写一个测试寄存器。
/* activate PCIE_PWR_EN */gpio_direction_output(pdata->pcie_pwr_en, 1);
imx_pcie_clrset(IOMUXC_GPR1_TEST_POWERDOWN, 0 << 18, IOMUXC_GPR1);
/* enable the clks */if (pdata->type_ep) {pcie_clk = clk_get(NULL, "pcie_ep_clk");if (IS_ERR(pcie_clk))pr_err("no pcie_ep clock.\n");这里是获取时钟的函数,有兴趣的可以自己google一下linux的时钟框架
if (clk_enable(pcie_clk)) {pr_err("can't enable pcie_ep clock.\n");clk_put(pcie_clk);}} else {pcie_clk = clk_get(NULL, "pcie_clk");if (IS_ERR(pcie_clk))pr_err("no pcie clock.\n");
if (clk_enable(pcie_clk)) {pr_err("can't enable pcie clock.\n");clk_put(pcie_clk);}}
imx_pcie_clrset(IOMUXC_GPR1_PCIE_REF_CLK_EN, 1 << 16, IOMUXC_GPR1);
这条我猜应该是使能pcie控制器了。/* start link up */imx_pcie_clrset(IOMUXC_GPR12_APP_LTSSM_ENABLE, 1 << 10, IOMUXC_GPR12);开始link up,我的理解应该是类似于以太网中的ping操作。
/* add the pcie port */add_pcie_port(base, dbi_base, pdata);这个函数检验link是否成功并进行相应的操作。
if (imx_pcie_link_up(dbi_base)) {struct imx_pcie_port *pp = &imx_pcie_port[num_pcie_ports++];先看看如果link up成功的话,那么就进行相关的初始化操作,向内核添加信息。
pr_info("IMX PCIe port: link up.\n");
pp->index = 0;pp->root_bus_nr = -1;pp->base = base;pp->dbi_base = dbi_base;spin_lock_init(&pp->conf_lock);memset(pp->res, 0, sizeof(pp->res));}
else {pr_info("IMX PCIe port: link down!\n");如果不成功的话,那么就disable pcie_clk并且释放对pcie_clk的引用,再把pcie控制器disable掉,最后再把PCIE的外部供电通过gpio来disable。
/* Release the clocks, and disable the power */pcie_clk = clk_get(NULL, "pcie_clk");if (IS_ERR(pcie_clk))pr_err("no pcie clock.\n");
clk_disable(pcie_clk);clk_put(pcie_clk);
imx_pcie_clrset(IOMUXC_GPR1_PCIE_REF_CLK_EN, 0 << 16,IOMUXC_GPR1);
/* Disable PCIE power */gpio_request(pdata->pcie_pwr_en, "PCIE POWER_EN");
/* activate PCIE_PWR_EN */gpio_direction_output(pdata->pcie_pwr_en, 0);
imx_pcie_clrset(IOMUXC_GPR1_TEST_POWERDOWN, 1 << 18,IOMUXC_GPR1);}
回到probe函数得最后一部分代码:
这里是内核中pci驱动初始化函数,具体的位置在:arch/arm/kernel/bios32.c,这里不再分析,基本就是做一些添加pci bus之类的初始化工作,对于arm体系的mpu来说是一样的。pci_common_init(&imx_pci);