freesclae i.mx6 Linux PCIe Driver source code Analysis
Turn from:
Http://www.lai18.com/content/2232856.html
Recently, a tool was needed to test whether PCIe link was successful, but since PCIe drivers are in kernel space, it is necessary to first analyze the I. MX6 PCIe Drive source code. First I had to spit out the location of the driver source code is very confusing, in the Linux 3.0.35_4.1.0, the driver is actually in the arch/arm/mach-mx6/directory, usually, this is the place where the information file of the plate is stored, and PCIe driver should be placed in DRIVERS/PCI and other related directories, so menuconfig is also in a very strange place to configure. Simply to the Linux version 3.10.17, PCIe code moved to the drivers/pci/pcie/directory, otherwise it looks really too tangled.
Here to say some nonsense, do not want to download Ltib friend to clone the following git source:
Git://git.freescale.com/imx/linux-2.6-imx.git Http://git.freescale.com/git/cgit.cgi/imx/linux-2.6-imx.git
But seemingly HTTP source is very slow, git source speed is OK. Here's a little bit more, 3.0.35 before the kernel is Freescale internal maintenance, and there is a public git web site, http://git.freescale.com/git/above the most Freescale their own maintenance projects, So it's also a relatively closed route. After the 3.10.17 Freescale began to walk the community route, basically all the code with the open source community synchronization, even the compilation environment from Ltib into a yocto, the basic is to take the community open route. 3.10.17 after the arch/arm/mach-mx6/directory was deleted, replaced by the device tree, PCIe Drive also to where it should be.
Because 3.0.35 and 3.10.17 have a lot of people in use, I decided to analyze the two versions, the following first to analyze the version of the 3.0.35_4.1.0 kernel:
git checkout imx_3.0.35_4.1.0
Make Arch=arm Imx6_defconfig
Make Arch=arm Menuconfig
In the kernel to enable PCIe drive, specific location (really a very rare position) in:
│-> System type│
│-> Freescale MXC Implementations
│-> PCI Express Support
The rear EP mode and RC mode do not need to be selected (they correspond to Imx_pcie_ep_mode_in_ep_rc_sys and Imx_pcie_rc_mode_in_ep_rc_sys macro definitions), and by default they are RC (Root Complex mode, where you can only choose built-in or no, there is no option to open the compilation module, to open the words themselves to change the kconfig on it. The code is located in Arch/arm/mach-mx6/pcie.c, the following is the time we analyze the source:
First see the last few lines:
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);
Add a layer of platform driver to the PCIe drive, and see the Imx_pcie_pltfm_driver structure definition below:
static struct Platform_driver Imx_pcie_pltfm_driver = { . Driver = { . Name = "Imx-pcie", . Owner = This_module, }, . Probe = Imx_pcie_pltfm_probe,};
You can see here through the. driver.name field to match the platform driver, after the match call the Imx_pcie_pltfm_probe function to detect, the following need to go to the plate pole file to find Add "imx-pcie" field platform Device's code.
For I. Mx6q SABRESD Platform, plate-pole file in Arch/arm/mach-mx6/board-mx6q_sabresd.c, and the code to add PCIe device can be found in:
/* ADD PCIe RC Interface Support * * Imx6q_add_pcie (&mx6_sabresd_pcie_data);
The Imx6q_add_pcie () is defined as the following lines in 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)
And Imx_add_pcie is located in Arch/arm/plat-mxc/devices/platform-imx-pcie.c, the relevant code is as follows:
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->ios Ize-1, . Flags = Ioresource_mem, }, { . start = Data->irq, . End = DATA->IRQ, . Flags = Ioresour CE_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));
See the real need to find the code, resource is used to define the register address and interrupt registration information, here in the last few lines in the red bold italic character marked the Imx-pcie field, because this function imx_add_platform_ Device officially we're looking for Add platform device function and match by function name, here you can see PCI platform device and platform driver name are Imx-pcie, so drive and device through platform The bus was able to match. It also passes through the struct imx_pcie_platform_data *pdata to pass the drive Platform_data,pdata structure declaration in Arch/arm/plat-mxc/include/mach/pcie.h:
/** * struct imx_pcie_platform_data-optional platform data for PCIe in i.mx * * @pcie_pwr_en: used for ENABLE/DISABL E PCIe Power (-einval if unused) * @pcie_rst: used to 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 * *
This platform_data will be captured in the driver's probe function, which can be seen in the kernel by adding platform_data to the platform device to transmit additional information to the driver. Here, the part of adding board level information in the kernel is over, and the kernel is going to execute the Init_machine () function during the boot process, and init_machine is a function pointer that points to arch/arm/mach-mx6/board-mx6q_ The Mx6_sabresd_board_init function in SABRESD.C, you can see this structure initialization statement:
* * 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
You should be able to understand the whole process of adding board level information, and then you can see that the driver loading is done in the Module_init () in pcie.c. After everything is ready to start to jump to the probe pointer to the Imx_pcie_pltfm_probe, to tell the truth personally think this probe is relatively short. Roughly to analyze:
MEM = Platform_get_resource (Pdev, Ioresource_mem, 0); if (!mem) { dev_err (dev, "no Mmio space\n"); Return-einval;}
Here's Platform_get_resource () is to get before struct resource res[]={...} The content defined, that is, information related to the Register base address and IRQ.
/* Added for PCI abort handling * * Hook_fault_code (+ 6, Imx6q_pcie_abort_handler, Sigbus, 0, "imprecise ex Ternal abort ");
This is like registering a abort handling function, and it's not clear what it does.
Base = Ioremap_nocache (pcie_arb_end_addr-sz_1m + 1, sz_1m-sz_16k); if (!base) { pr_err ("Error with Ioremap in funct Ion%s\n ", __func__); ret = Ptr_err (base); return ret;}
Ioremap, a page table is mapped to kernel space for the registers of this interval segment to be accessed.
Here the definition of pcie_arb_end_addr is in Arch/arm/plat-mxc/include/mach/mx6.h:
#define PCIE_ARB_BASE_ADDR 0x01000000#define pcie_arb_end_addr 0x01ffffff
Obviously base access is [pcie_arb_end_addr-sz_1m + 1, pcie_arb_end_addr-sz_16k] This section
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;}
The Devm_ioremap is very similar to Ioremap, the only difference is to refer to the reply on this page 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" PCIv3 driver, so the mapping would be automatically released on probe.
And here dbi_base through the analysis of the PLATFORM-IMX-PCIE.C, the visit is [pcie_arb_end_addr-sz_16k, pcie_arb_end_addr] This section
/* fixme The field name should is aligned to RM */imx_pcie_clrset (iomuxc_gpr12_app_ltssm_enable, 0 <<, IOMUXC_GPR );
/* Configure constant input signal to the PCIe CTRL and PHY */if (PDATA->TYPE_EP & 1)/ * EP/ IMX_PCIE_CLR Set (Iomuxc_gpr12_device_type, pci_exp_type_endpoint <<, iomuxc_gpr12); else/ * RC/ Imx_ Pcie_clrset (Iomuxc_gpr12_device_type, pci_exp_type_root_port <<, 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, <<, IOMUXC_GPR8); imx_ Pcie_clrset (iomuxc_gpr8_tx_swing_full, 127 << iomuxc_gpr8); Imx_pcie_clrset (Iomuxc_gpr8_tx_swing_low, 127 <<, IOMUXC_GPR8);
Register configuration, which for PCIe link success is more critical is iomuxc_gpr8 and iomuxc_gpr12, but in reference manual Iomuxc_gpr12 value has been set dead, so can only adjust iomuxc_ It's GPR8.
Here Imx_pcie_clrset is an inline function, defined as follows:
/* 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);
}
Given that the address can be directly read and written directly, the area of memory has been mapped. Find the definition of IOMUXC_GPR8 in the Arch/arm/mach-mx6/crm_regs.h file:
* IOMUXC * *
#define Mxc_iomuxc_base
Mx6_io_address (MX6Q_IOMUXC_BASE_ADDR)
............
#define IOMUXC_GPR8
(Mxc_iomuxc_base + 0x20)
Here mx6q_iomuxc_base_addr is defined in the 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)
The final value is 0x0200_0000+0x8_0000+0x6_0000 = 0x020e_0000, reference i. MX6Q Reference Manual can find the IOMUXC register range: [020e_0000, 020E_3FFF] here and the driver correspond.
And the definition of mx6_io_address is defined in Arch/arm/plat-mxc/include/mach/mx6.h as follows:
#define Peripbase_virt
0xf2000000
............
#define MX6_IO_ADDRESS (x) (void __force __iomem *) ((x) + Peripbase_virt)
is basically an offset to the base address.
Note: Direct such access is not valid, the first thing you have to do is map the page table in MMU to this address, that is, Ioremap can be accessed from kernel space later (this can be accessed because the address must have been mapped before), except that the relationship after mapping is a physical address that adds an offset.
/* Enable the PWR, clks and so on */imx_pcie_enable_controller (dev);
As noted, so that can pcie power and clocks, because the i.mx is powered by the fuse100, it is necessary to pass a gpio pin to control the power switch. This function is very critical, the following simple introduction, itself is not long.
* 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 <<, IOMUXC_GPR1);
Gets the right to use the GPIO pin and outputs the GPIO to a high level. Finally, write a test register.
* 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); }
Here is the function to get the clock, interested in Google can own the clock frame Linux
Imx_pcie_clrset (iomuxc_gpr1_pcie_ref_clk_en, 1 <<, IOMUXC_GPR1);
This one I guess should be able to pcie the controller.
/* Start link up/imx_pcie_clrset (iomuxc_gpr12_app_ltssm_enable, 1 <<, iomuxc_gpr12);
Start link up, my understanding should be similar to the ping operation in Ethernet.
/* Add the PCIe port * * Add_pcie_port (base, Dbi_base, pdata);
This function verifies that link is successful and does the appropriate action.
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));}
Let's see if link up succeeds, then do the related initialization and add the information to the kernel.
else { pr_info ("IMX PCIe port:link down!\n");
/* Release of 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 <<, 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 <<, iomuxc_gpr1);
If unsuccessful, then disable PCIE_CLK and release the reference to the PCIE_CLK, then disable the PCIe controller, and finally PCIE the external power supply through Gpio to disable.
Back to the last part of the probe function code:
Pci_common_init (&IMX_PCI);
Here is the kernel of PCI driver initialization function, the specific location in: arch/arm/kernel/bios32.c, here no longer analysis, the basic is to do some add PCI bus, such as initialization work, for the arm system MPU is the same.