Freesclae i.MX6 Linux PCIE驱动源码分析

     最近需要做一个工具来测试PCIE的link是否成功,但是由于PCIE的驱动都是在内核空间中,因此需要首先分析一下i.MX6 PCIE的驱动源码。首先我不得不吐槽一下驱动源码的存放位置很混乱,在Linux 3.0.35_4.1.0中,驱动居然是在arch/arm/mach-mx6/目录下,通常的情况来说,这里是存放板极信息文件的地方,而pcie的驱动更应该放在drivers/pci等相关目录下,因此menuconfig的时候也是在一个很奇怪的地方配置。索性到了Linux 3.10.17版本,pcie的代码挪到drivers/pci/pcie/目录下了,不然看起来真的是太纠结了。
        这里再说些废话,不想下载LTIB的朋友就自己去clone下面的git源:
git://git.freescale.com/imx/linux-2.6-imx.git
http://git.freescale.com/git/cgit.cgi/imx/linux-2.6-imx.git
不过貌似http的源很慢,git源的速度还行。这里再多说一点,3.0.35之前的内核都是freescale内部维护,并且有一个public git网站, http://git.freescale.com/git/  上面有大部分freescale自己维护的项目,因此也是走相对封闭的路线。到了3.10.17以后freescale开始走社区的路线,基本上所有的代码都与开源社区同步,连编译环境也从LTIB变成了yocto,基本就是走社区开放的路线。3.10.17以后 arch/arm/mach-mx6/目录被删除,取而代之的是设备树,pcie驱动也到了它应该待的地方。
由于3.0.35以及3.10.17都有不少人在用,我决定两个版本都去分析一下,下面先来分析一下3.0.35_4.1.0内核的版本:
git checkout imx_3.0.35_4.1.0
make ARCH=arm imx6_defconfig
make ARCH=arm menuconfig
在内核中使能pcie驱动,具体位置(真是一个非常少见的位置)在:
 │     -> System Type                                                                   │  
  │       -> Freescale MXC Implementations 
              -> PCI Express support
后面的EP mode和RC mode都不需要选(它们对应IMX_PCIE_EP_MODE_IN_EP_RC_SYS以及IMX_PCIE_RC_MODE_IN_EP_RC_SYS的宏定义),默认情况下就是RC (Root Complex) mode,这里只能选择built-in 或者no,没有开启编译模块的选项,要开的话自己改一下Kconfig就可以了。 代码位于arch/arm/mach-mx6/pcie.c, 下面到了我们分析源码的时候了:
首先看到最后几行:
 
   
static int __init imx_pcie_drv_init ( void )
{
return platform_driver_register (& 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 );
在pcie驱动外部加了一层platform driver,下面看imx_pcie_pltfm_driver结构体定义:
 
   
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 ,
},
};
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 ));
}
看到了才找到了真正需要的代码,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中:
 
   
/**
 * 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)
 */
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 */
待会在驱动可以看到这个platform_data会在probe函数中被获取,这种在platform device中添加platform_data来向驱动传递额外的信息在内核中常常能见到。 到这里,在内核中添加板级信息的部分已经结束了,内核在启动过程中会去执行init_machine()函数,而init_machine是一个函数指针,它指向的就是 arch/arm/mach-mx6/ board-mx6q_sabresd.c中的 mx6_sabresd_board_init函数,可以看到这个结构体初始化语句:
 
   
/*
 * 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_ADDR 0x01000000
#define PCIE_ARB_END_ADDR 0x01FFFFFF
显然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 );
/* 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 );
寄存器配置,其中对于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_BASE MX6_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_ADDR 0x02000000
………………
#define ATZ1_BASE_ADDR AIPS1_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_VIRT 0xF2000000
…………
#define MX6_IO_ADDRESS(x) (void __force __iomem *)((x) + PERIPBASE_VIRT)
基本上就是对基地址的一个偏移量。
注意:直接这样访问是无效的,首先必须要在MMU中建立页表映射到这段地址,即ioremap以后才可以从内核空间访问(这里能访问是因为这块地址之前肯定已经映射过了),只不过映射之后的关系是物理地址增加一个偏移量而已。
 
     
/* 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" );
/* activate PCIE_PWR_EN */
gpio_direction_output ( pdata -> pcie_pwr_en , 1 );
imx_pcie_clrset ( IOMUXC_GPR1_TEST_POWERDOWN , 0 << 18 , IOMUXC_GPR1 );
获取gpio引脚的使用权,并且将该gpio输出高电平。最后再写一个测试寄存器。
 
    
/* 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" );
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 );
}
}
这里是获取时钟的函数,有兴趣的可以自己google一下linux的时钟框架
 
     
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 ++];
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 ));
}
先看看如果link up成功的话,那么就进行相关的初始化操作,向内核添加信息。
 
   
else {
pr_info ( "IMX PCIe port: link down!\n" );
/* 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 );
}
如果不成功的话,那么就disable pcie_clk并且释放对pcie_clk的引用,再把pcie控制器disable掉,最后再把PCIE的外部供电通过gpio来disable。

回到probe函数得最后一部分代码:
 
   

pci_common_init(&imx_pci);

这里是内核中pci驱动初始化函数,具体的位置在:arch/arm/kernel/bios32.c,这里不再分析,基本就是做一些添加pci bus之类的初始化工作,对于arm体系的mpu来说是一样的。

你可能感兴趣的:(imx6)