[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