[U-Boot] [PATCH V2 3/4] ARM: mx6: Add PCI express driver

Jagan Teki jagannadh.teki at gmail.com
Sat Dec 14 06:23:30 CET 2013


On Sat, Dec 14, 2013 at 10:25 AM, Marek Vasut <marex at denx.de> wrote:
> Add PCIe driver for the Freescale i.MX6 SoC . This driver operates the
> PCIe block in RC mode only, the EP mode is NOT supported. The driver is
> tested with the Intel e1000 NIC driver.
>
> Signed-off-by: Marek Vasut <marex at denx.de>
> Cc: Albert Aribaud <albert.u.boot at aribaud.net>
> Cc: Eric Nelson <eric.nelson at boundarydevices.com>
> Cc: Fabio Estevam <fabio.estevam at freescale.com>
> Cc: Stefano Babic <sbabic at denx.de>
> ---
>  arch/arm/include/asm/arch-mx6/iomux.h |  27 ++
>  drivers/pci/Makefile                  |   1 +
>  drivers/pci/pcie_imx.c                | 565 ++++++++++++++++++++++++++++++++++
>  3 files changed, 593 insertions(+)
>  create mode 100644 drivers/pci/pcie_imx.c
>
> V2: - Check for link_in_training bit while waiting for LTSSM to finish.
>     - Replace udelay(30000) with mdelay(30) in the core reset end seq.
>     - Check if the link-up failed and don't probe the bus in such case
>       to prevent a hang.
>     - Do not assert LTSSM while the DWC core is in reset (thanks for finding
>       this, Richard Zhu)
>
> diff --git a/arch/arm/include/asm/arch-mx6/iomux.h b/arch/arm/include/asm/arch-mx6/iomux.h
> index fe4675e..f9ee0d9 100644
> --- a/arch/arm/include/asm/arch-mx6/iomux.h
> +++ b/arch/arm/include/asm/arch-mx6/iomux.h
> @@ -15,6 +15,33 @@
>  #define IOMUXC_GPR1_OTG_ID_ENET_RX_ERR (0<<13)
>  #define IOMUXC_GPR1_OTG_ID_GPIO1       (1<<13)
>  #define IOMUXC_GPR1_OTG_ID_MASK                (1<<13)
> +#define IOMUXC_GPR1_REF_SSP_EN                 (1 << 16)
> +#define IOMUXC_GPR1_TEST_POWERDOWN             (1 << 18)
> +
> +/*
> + * IOMUXC_GPR8 bit fields
> + */
> +#define IOMUXC_GPR8_PCS_TX_DEEMPH_GEN1_MASK            (0x3f << 0)
> +#define IOMUXC_GPR8_PCS_TX_DEEMPH_GEN1_OFFSET          0
> +#define IOMUXC_GPR8_PCS_TX_DEEMPH_GEN2_3P5DB_MASK      (0x3f << 6)
> +#define IOMUXC_GPR8_PCS_TX_DEEMPH_GEN2_3P5DB_OFFSET    6
> +#define IOMUXC_GPR8_PCS_TX_DEEMPH_GEN2_6DB_MASK                (0x3f << 12)
> +#define IOMUXC_GPR8_PCS_TX_DEEMPH_GEN2_6DB_OFFSET      12
> +#define IOMUXC_GPR8_PCS_TX_SWING_FULL_MASK             (0x7f << 18)
> +#define IOMUXC_GPR8_PCS_TX_SWING_FULL_OFFSET           18
> +#define IOMUXC_GPR8_PCS_TX_SWING_LOW_MASK              (0x7f << 25)
> +#define IOMUXC_GPR8_PCS_TX_SWING_LOW_OFFSET            25
> +
> +/*
> + * IOMUXC_GPR12 bit fields
> + */
> +#define IOMUXC_GPR12_LOS_LEVEL_9               (0x9 << 4)
> +#define IOMUXC_GPR12_LOS_LEVEL_MASK            (0x1f << 4)
> +#define IOMUXC_GPR12_APPS_LTSSM_ENABLE         (1 << 10)
> +#define IOMUXC_GPR12_DEVICE_TYPE_EP            (0x0 << 12)
> +#define IOMUXC_GPR12_DEVICE_TYPE_RC            (0x2 << 12)
> +#define IOMUXC_GPR12_DEVICE_TYPE_MASK          (0xf << 12)
> +
>  /*
>   * IOMUXC_GPR13 bit fields
>   */
> diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
> index 99d51a6..565ff37 100644
> --- a/drivers/pci/Makefile
> +++ b/drivers/pci/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_FSL_PCI_INIT) += fsl_pci_init.o
>  obj-$(CONFIG_PCI) += pci.o pci_auto.o
>  obj-$(CONFIG_PCI_INDIRECT_BRIDGE) += pci_indirect.o
>  obj-$(CONFIG_PCI_GT64120) += pci_gt64120.o
> +obj-$(CONFIG_PCIE_IMX) += pcie_imx.o
>  obj-$(CONFIG_FTPCI100) += pci_ftpci100.o
>  obj-$(CONFIG_IXP_PCI) += pci_ixp.o
>  obj-$(CONFIG_SH4_PCI) += pci_sh4.o
> diff --git a/drivers/pci/pcie_imx.c b/drivers/pci/pcie_imx.c
> new file mode 100644
> index 0000000..0a74867
> --- /dev/null
> +++ b/drivers/pci/pcie_imx.c
> @@ -0,0 +1,565 @@
> +/*
> + * Freescale i.MX6 PCI Express Root-Complex driver
> + *
> + * Copyright (C) 2013 Marek Vasut <marex at denx.de>
> + *
> + * Based on upstream Linux kernel driver:
> + * pci-imx6.c:         Sean Cross <xobs at kosagi.com>
> + * pcie-designware.c:  Jingoo Han <jg1.han at samsung.com>
> + *
> + * SPDX-License-Identifier:    GPL-2.0
> + */
> +
> +#include <common.h>
> +#include <pci.h>
> +#include <asm/arch/clock.h>
> +#include <asm/arch/iomux.h>
> +#include <asm/arch/crm_regs.h>
> +#include <asm/io.h>
> +#include <asm/sizes.h>
> +#include <errno.h>
> +
> +#define PCI_ACCESS_READ  0
> +#define PCI_ACCESS_WRITE 1
> +
> +#define MX6_DBI_ADDR   0x01ffc000
> +#define MX6_DBI_SIZE   0x4000
> +#define MX6_IO_ADDR    0x01000000
> +#define MX6_IO_SIZE    0x100000
> +#define MX6_MEM_ADDR   0x01100000
> +#define MX6_MEM_SIZE   0xe00000
> +#define MX6_ROOT_ADDR  0x01f00000
> +#define MX6_ROOT_SIZE  0xfc000
> +
> +/* PCIe Port Logic registers (memory-mapped) */
> +#define PL_OFFSET 0x700
> +#define PCIE_PHY_DEBUG_R0 (PL_OFFSET + 0x28)
> +#define PCIE_PHY_DEBUG_R1 (PL_OFFSET + 0x2c)
> +#define PCIE_PHY_DEBUG_R1_LINK_UP              (1 << 4)
> +#define PCIE_PHY_DEBUG_R1_LINK_IN_TRAINING     (1 << 29)
> +
> +#define PCIE_PHY_CTRL (PL_OFFSET + 0x114)
> +#define PCIE_PHY_CTRL_DATA_LOC 0
> +#define PCIE_PHY_CTRL_CAP_ADR_LOC 16
> +#define PCIE_PHY_CTRL_CAP_DAT_LOC 17
> +#define PCIE_PHY_CTRL_WR_LOC 18
> +#define PCIE_PHY_CTRL_RD_LOC 19
> +
> +#define PCIE_PHY_STAT (PL_OFFSET + 0x110)
> +#define PCIE_PHY_STAT_DATA_LOC 0
> +#define PCIE_PHY_STAT_ACK_LOC 16
> +
> +/* PHY registers (not memory-mapped) */
> +#define PCIE_PHY_RX_ASIC_OUT 0x100D
> +
> +#define PHY_RX_OVRD_IN_LO 0x1005
> +#define PHY_RX_OVRD_IN_LO_RX_DATA_EN (1 << 5)
> +#define PHY_RX_OVRD_IN_LO_RX_PLL_EN (1 << 3)
> +
> +/* iATU registers */
> +#define PCIE_ATU_VIEWPORT              0x900
> +#define PCIE_ATU_REGION_INBOUND                (0x1 << 31)
> +#define PCIE_ATU_REGION_OUTBOUND       (0x0 << 31)
> +#define PCIE_ATU_REGION_INDEX1         (0x1 << 0)
> +#define PCIE_ATU_REGION_INDEX0         (0x0 << 0)
> +#define PCIE_ATU_CR1                   0x904
> +#define PCIE_ATU_TYPE_MEM              (0x0 << 0)
> +#define PCIE_ATU_TYPE_IO               (0x2 << 0)
> +#define PCIE_ATU_TYPE_CFG0             (0x4 << 0)
> +#define PCIE_ATU_TYPE_CFG1             (0x5 << 0)
> +#define PCIE_ATU_CR2                   0x908
> +#define PCIE_ATU_ENABLE                        (0x1 << 31)
> +#define PCIE_ATU_BAR_MODE_ENABLE       (0x1 << 30)
> +#define PCIE_ATU_LOWER_BASE            0x90C
> +#define PCIE_ATU_UPPER_BASE            0x910
> +#define PCIE_ATU_LIMIT                 0x914
> +#define PCIE_ATU_LOWER_TARGET          0x918
> +#define PCIE_ATU_BUS(x)                        (((x) & 0xff) << 24)
> +#define PCIE_ATU_DEV(x)                        (((x) & 0x1f) << 19)
> +#define PCIE_ATU_FUNC(x)               (((x) & 0x7) << 16)
> +#define PCIE_ATU_UPPER_TARGET          0x91C
> +
> +/*
> + * PHY access functions
> + */
> +static int pcie_phy_poll_ack(void __iomem *dbi_base, int exp_val)
> +{
> +       u32 val;
> +       u32 max_iterations = 10;
> +       u32 wait_counter = 0;
> +
> +       do {
> +               val = readl(dbi_base + PCIE_PHY_STAT);
> +               val = (val >> PCIE_PHY_STAT_ACK_LOC) & 0x1;
> +               wait_counter++;
> +
> +               if (val == exp_val)
> +                       return 0;
> +
> +               udelay(1);
> +       } while (wait_counter < max_iterations);
> +
> +       return -ETIMEDOUT;
> +}
> +
> +static int pcie_phy_wait_ack(void __iomem *dbi_base, int addr)
> +{
> +       u32 val;
> +       int ret;
> +
> +       val = addr << PCIE_PHY_CTRL_DATA_LOC;
> +       writel(val, dbi_base + PCIE_PHY_CTRL);
> +
> +       val |= (0x1 << PCIE_PHY_CTRL_CAP_ADR_LOC);
> +       writel(val, dbi_base + PCIE_PHY_CTRL);
> +
> +       ret = pcie_phy_poll_ack(dbi_base, 1);
> +       if (ret)
> +               return ret;
> +
> +       val = addr << PCIE_PHY_CTRL_DATA_LOC;
> +       writel(val, dbi_base + PCIE_PHY_CTRL);
> +
> +       ret = pcie_phy_poll_ack(dbi_base, 0);
> +       if (ret)
> +               return ret;
> +
> +       return 0;
> +}
> +
> +/* Read from the 16-bit PCIe PHY control registers (not memory-mapped) */
> +static int pcie_phy_read(void __iomem *dbi_base, int addr , int *data)
> +{
> +       u32 val, phy_ctl;
> +       int ret;
> +
> +       ret = pcie_phy_wait_ack(dbi_base, addr);
> +       if (ret)
> +               return ret;
> +
> +       /* assert Read signal */
> +       phy_ctl = 0x1 << PCIE_PHY_CTRL_RD_LOC;
> +       writel(phy_ctl, dbi_base + PCIE_PHY_CTRL);
> +
> +       ret = pcie_phy_poll_ack(dbi_base, 1);
> +       if (ret)
> +               return ret;
> +
> +       val = readl(dbi_base + PCIE_PHY_STAT);
> +       *data = val & 0xffff;
> +
> +       /* deassert Read signal */
> +       writel(0x00, dbi_base + PCIE_PHY_CTRL);
> +
> +       ret = pcie_phy_poll_ack(dbi_base, 0);
> +       if (ret)
> +               return ret;
> +
> +       return 0;
> +}
> +
> +static int pcie_phy_write(void __iomem *dbi_base, int addr, int data)
> +{
> +       u32 var;
> +       int ret;
> +
> +       /* write addr */
> +       /* cap addr */
> +       ret = pcie_phy_wait_ack(dbi_base, addr);
> +       if (ret)
> +               return ret;
> +
> +       var = data << PCIE_PHY_CTRL_DATA_LOC;
> +       writel(var, dbi_base + PCIE_PHY_CTRL);
> +
> +       /* capture data */
> +       var |= (0x1 << PCIE_PHY_CTRL_CAP_DAT_LOC);
> +       writel(var, dbi_base + PCIE_PHY_CTRL);
> +
> +       ret = pcie_phy_poll_ack(dbi_base, 1);
> +       if (ret)
> +               return ret;
> +
> +       /* deassert cap data */
> +       var = data << PCIE_PHY_CTRL_DATA_LOC;
> +       writel(var, dbi_base + PCIE_PHY_CTRL);
> +
> +       /* wait for ack de-assertion */
> +       ret = pcie_phy_poll_ack(dbi_base, 0);
> +       if (ret)
> +               return ret;
> +
> +       /* assert wr signal */
> +       var = 0x1 << PCIE_PHY_CTRL_WR_LOC;
> +       writel(var, dbi_base + PCIE_PHY_CTRL);
> +
> +       /* wait for ack */
> +       ret = pcie_phy_poll_ack(dbi_base, 1);
> +       if (ret)
> +               return ret;
> +
> +       /* deassert wr signal */
> +       var = data << PCIE_PHY_CTRL_DATA_LOC;
> +       writel(var, dbi_base + PCIE_PHY_CTRL);
> +
> +       /* wait for ack de-assertion */
> +       ret = pcie_phy_poll_ack(dbi_base, 0);
> +       if (ret)
> +               return ret;
> +
> +       writel(0x0, dbi_base + PCIE_PHY_CTRL);
> +
> +       return 0;
> +}
> +
> +static int imx6_pcie_link_up(void)
> +{
> +       u32 rc, ltssm;
> +       int rx_valid, temp;
> +
> +       /* link is debug bit 36, debug register 1 starts at bit 32 */
> +       rc = readl(MX6_DBI_ADDR + PCIE_PHY_DEBUG_R1);
> +       if ((rc & PCIE_PHY_DEBUG_R1_LINK_UP) &&
> +           !(rc & PCIE_PHY_DEBUG_R1_LINK_IN_TRAINING))
> +               return -EAGAIN;
> +
> +       /*
> +        * From L0, initiate MAC entry to gen2 if EP/RC supports gen2.
> +        * Wait 2ms (LTSSM timeout is 24ms, PHY lock is ~5us in gen2).
> +        * If (MAC/LTSSM.state == Recovery.RcvrLock)
> +        * && (PHY/rx_valid==0) then pulse PHY/rx_reset. Transition
> +        * to gen2 is stuck
> +        */
> +       pcie_phy_read((void *)MX6_DBI_ADDR, PCIE_PHY_RX_ASIC_OUT, &rx_valid);
> +       ltssm = readl(MX6_DBI_ADDR + PCIE_PHY_DEBUG_R0) & 0x3F;
> +
> +       if (rx_valid & 0x01)
> +               return 0;
> +
> +       if (ltssm != 0x0d)
> +               return 0;
> +
> +       printf("transition to gen2 is stuck, reset PHY!\n");
> +
> +       pcie_phy_read((void *)MX6_DBI_ADDR, PHY_RX_OVRD_IN_LO, &temp);
> +       temp |= (PHY_RX_OVRD_IN_LO_RX_DATA_EN | PHY_RX_OVRD_IN_LO_RX_PLL_EN);
> +       pcie_phy_write((void *)MX6_DBI_ADDR, PHY_RX_OVRD_IN_LO, temp);
> +
> +       udelay(3000);
> +
> +       pcie_phy_read((void *)MX6_DBI_ADDR, PHY_RX_OVRD_IN_LO, &temp);
> +       temp &= ~(PHY_RX_OVRD_IN_LO_RX_DATA_EN | PHY_RX_OVRD_IN_LO_RX_PLL_EN);
> +       pcie_phy_write((void *)MX6_DBI_ADDR, PHY_RX_OVRD_IN_LO, temp);
> +
> +       return 0;
> +}
> +
> +/*
> + * iATU region setup
> + */
> +static int imx_pcie_regions_setup(void)
> +{
> +       /*
> +        * i.MX6 defines 16MB in the AXI address map for PCIe.
> +        *
> +        * That address space excepted the pcie registers is
> +        * split and defined into different regions by iATU,
> +        * with sizes and offsets as follows:
> +        *
> +        * 0x0100_0000 --- 0x010F_FFFF 1MB IORESOURCE_IO
> +        * 0x0110_0000 --- 0x01EF_FFFF 14MB IORESOURCE_MEM
> +        * 0x01F0_0000 --- 0x01FF_FFFF 1MB Cfg + Registers
> +        */
> +
> +       /* CMD reg:I/O space, MEM space, and Bus Master Enable */
> +       setbits_le32(MX6_DBI_ADDR | PCI_COMMAND,
> +                    PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
> +
> +       /* Set the CLASS_REV of RC CFG header to PCI_CLASS_BRIDGE_PCI */
> +       setbits_le32(MX6_DBI_ADDR + PCI_CLASS_REVISION,
> +                    PCI_CLASS_BRIDGE_PCI << 16);
> +
> +       /* Region #0 is used for Outbound CFG space access. */
> +       writel(0, MX6_DBI_ADDR + PCIE_ATU_VIEWPORT);
> +
> +       writel(MX6_ROOT_ADDR, MX6_DBI_ADDR + PCIE_ATU_LOWER_BASE);
> +       writel(0, MX6_DBI_ADDR + PCIE_ATU_UPPER_BASE);
> +       writel(MX6_ROOT_ADDR + MX6_ROOT_SIZE, MX6_DBI_ADDR + PCIE_ATU_LIMIT);
> +
> +       writel(0, MX6_DBI_ADDR + PCIE_ATU_LOWER_TARGET);
> +       writel(0, MX6_DBI_ADDR + PCIE_ATU_UPPER_TARGET);
> +       writel(PCIE_ATU_TYPE_CFG0, MX6_DBI_ADDR + PCIE_ATU_CR1);
> +       writel(PCIE_ATU_ENABLE, MX6_DBI_ADDR + PCIE_ATU_CR2);
> +
> +       return 0;
> +}
> +
> +/*
> + * PCI Express accessors
> + */
> +static uint32_t get_bus_address(pci_dev_t d, int where)
> +{
> +       uint32_t va_address;
> +
> +       /* Reconfigure Region #0 */
> +       writel(0, MX6_DBI_ADDR + PCIE_ATU_VIEWPORT);
> +
> +       if (PCI_BUS(d) < 2)
> +               writel(PCIE_ATU_TYPE_CFG0, MX6_DBI_ADDR + PCIE_ATU_CR1);
> +       else
> +               writel(PCIE_ATU_TYPE_CFG1, MX6_DBI_ADDR + PCIE_ATU_CR1);
> +
> +       if (PCI_BUS(d) == 0) {
> +               va_address = MX6_DBI_ADDR;
> +       } else {
> +               writel(d << 8, MX6_DBI_ADDR + PCIE_ATU_LOWER_TARGET);
> +               va_address = MX6_IO_ADDR + SZ_16M - SZ_1M;
> +       }
> +
> +       va_address += (where & ~0x3);
> +
> +       return va_address;
> +}
> +
> +static int imx_pcie_addr_valid(pci_dev_t d)
> +{
> +       if ((PCI_BUS(d) == 0) && (PCI_DEV(d) > 1))
> +               return -EINVAL;
> +       if ((PCI_BUS(d) == 1) && (PCI_DEV(d) > 0))
> +               return -EINVAL;
> +       return 0;
> +}
> +
> +/*
> + * Replace the original ARM DABT handler with a simple jump-back one.
> + *
> + * The problem here is that if we have a PCIe bridge attached to this PCIe
> + * controller, but no PCIe device is connected to the bridges' downstream
> + * port, the attempt to read/write from/to the config space will produce
> + * a DABT. This is a behavior of the controller and can not be disabled
> + * unfortuatelly.
> + *
> + * To work around the problem, we backup the current DABT handler address
> + * and replace it with our own DABT handler, which only bounces right back
> + * into the code.
> + */
> +static void imx_pcie_fix_dabt_handler(bool set)
> +{
> +       extern uint32_t *_data_abort;

Not a good idea to use extern in .c - any comments?

-- 
Thanks,
Jagan.
--------
Jagannadha Sutradharudu Teki,
E: jagannadh.teki at gmail.com, P: +91-9676773388
Engineer - System Software Hacker
U-boot - SPI Custodian and Zynq APSOC
Ln: http://www.linkedin.com/in/jaganteki


More information about the U-Boot mailing list