From patchwork Mon Jul 1 07:15:46 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sean Cross X-Patchwork-Id: 256021 Return-Path: X-Original-To: incoming-imx@patchwork.ozlabs.org Delivered-To: patchwork-incoming-imx@bilbo.ozlabs.org Received: from casper.infradead.org (casper.infradead.org [IPv6:2001:770:15f::2]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 1CA672C007E for ; Mon, 1 Jul 2013 17:38:18 +1000 (EST) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1UtYdX-0003Ir-1F; Mon, 01 Jul 2013 07:35:00 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1UtYcv-0001cL-Hw; Mon, 01 Jul 2013 07:34:21 +0000 Received: from mail1.g1.pair.com ([66.39.3.162]) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1UtYcU-0001YX-Iw for linux-arm-kernel@lists.infradead.org; Mon, 01 Jul 2013 07:34:01 +0000 Received: from xobs-novena.novalocal (unknown [210.23.25.254]) by mail1.g1.pair.com (Postfix) with ESMTPSA id 4B6342BF73; Mon, 1 Jul 2013 03:33:36 -0400 (EDT) From: Sean Cross To: devicetree-discuss@lists.ozlabs.org, linux-pci@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [PATCH 3/4] PCI: Add driver for i.MX6 PCI Express Date: Mon, 1 Jul 2013 07:15:46 +0000 Message-Id: <1372662947-27160-4-git-send-email-xobs@kosagi.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1372662947-27160-1-git-send-email-xobs@kosagi.com> References: <1372662947-27160-1-git-send-email-xobs@kosagi.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130701_033355_028585_CF895E07 X-CRM114-Status: GOOD ( 24.51 ) X-Spam-Score: -2.6 (--) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-2.6 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.7 RCVD_IN_DNSWL_LOW RBL: Sender listed at http://www.dnswl.org/, low trust [66.39.3.162 listed in list.dnswl.org] -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: Sean Cross X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+incoming-imx=patchwork.ozlabs.org@lists.infradead.org List-Id: linux-imx-kernel.lists.patchwork.ozlabs.org This adds a PCI Express port driver for the on-chip PCI Express port present on the i.MX6 SoC. It is based on the PCI Express driver available in the Freescale BSP. Signed-off-by: Sean Cross --- .../devicetree/bindings/pci/imx6q-pcie.txt | 20 + arch/arm/mach-imx/Kconfig | 1 + drivers/pci/pcie/Kconfig | 10 + drivers/pci/pcie/Makefile | 2 + drivers/pci/pcie/pcie-imx.c | 1049 ++++++++++++++++++++ 5 files changed, 1082 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/imx6q-pcie.txt create mode 100644 drivers/pci/pcie/pcie-imx.c diff --git a/Documentation/devicetree/bindings/pci/imx6q-pcie.txt b/Documentation/devicetree/bindings/pci/imx6q-pcie.txt new file mode 100644 index 0000000..2dc9eae --- /dev/null +++ b/Documentation/devicetree/bindings/pci/imx6q-pcie.txt @@ -0,0 +1,20 @@ +* Freescale i.MX6Q PCI Express bridge + +Example (i.MX6Q) + pcie: pcie@01ffc000 { + compatible = "fsl,imx6q-pcie"; + reg = <0x01ffc000 0x4000>, + <0x01000000 0x100000>, + <0x01100000 0xe00000>, + <0x01f00000 0xfc000>; + interrupts = <0 122 0x04>; + clocks = <&clks 186>, <&clks 189>, <&clks 196>, + <&clks 198>, <&clks 144>; + clock-names = "sata_ref", "pcie_ref_125m", "lvds1_sel", + "lvds1", "pcie_axi"; + power-enable = <&gpio7 12 0>; + pcie-reset = <&gpio3 29 0>; + wake-up = <&gpio3 22 0>; + disable-endpoint = <&gpio2 16 0>; + }; + diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index ba44328..cad4e5a 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -811,6 +811,7 @@ config SOC_IMX6Q select PL310_ERRATA_588369 if CACHE_PL310 select PL310_ERRATA_727915 if CACHE_PL310 select PL310_ERRATA_769419 if CACHE_PL310 + select MIGHT_HAVE_PCI select PM_OPP if PM help diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index 569f82f..d1d70db 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -83,3 +83,13 @@ endchoice config PCIE_PME def_bool y depends on PCIEPORTBUS && PM_RUNTIME + +# +# Platform driver for i.MX6 +# +config PCIE_IMX + bool "Support for i.MX6" + depends on SOC_IMX6Q + help + Enable support for the 1x PCI Express bus on the Freescale i.MX6 + depends on PCIEPORTBUS && PM_RUNTIME diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile index 00c62df..5393d21 100644 --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -14,3 +14,5 @@ obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o obj-$(CONFIG_PCIEAER) += aer/ obj-$(CONFIG_PCIE_PME) += pme.o + +obj-$(CONFIG_PCIE_IMX) += pcie-imx.o diff --git a/drivers/pci/pcie/pcie-imx.c b/drivers/pci/pcie/pcie-imx.c new file mode 100644 index 0000000..664679e --- /dev/null +++ b/drivers/pci/pcie/pcie-imx.c @@ -0,0 +1,1049 @@ +/* + * drivers/pci/pcie/pcie-imx.c + * + * PCIe host controller driver for IMX6 SOCs + * + * Copyright (C) 2012 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Code originally taken from Freescale linux-2.6.35 BSP. + * + * Other bits taken from arch/arm/mach-dove/pcie.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +/* IOMUXC */ +#define IOMUXC_GPR0 (0x00) +#define IOMUXC_GPR1 (0x04) +#define IOMUXC_GPR2 (0x08) +#define IOMUXC_GPR3 (0x0C) +#define IOMUXC_GPR4 (0x10) +#define IOMUXC_GPR5 (0x14) +#define IOMUXC_GPR6 (0x18) +#define IOMUXC_GPR7 (0x1C) +#define IOMUXC_GPR8 (0x20) +#define IOMUXC_GPR9 (0x24) +#define IOMUXC_GPR10 (0x28) +#define IOMUXC_GPR11 (0x2C) +#define IOMUXC_GPR12 (0x30) +#define IOMUXC_GPR13 (0x34) + + +/* Register Definitions */ +#define PRT_LOG_R_BaseAddress 0x700 + +/* Register DEBUG_R0 */ +/* Debug Register 0 */ +#define DEBUG_R0 (PRT_LOG_R_BaseAddress + 0x28) +#define DEBUG_R0_RegisterSize 32 +#define DEBUG_R0_RegisterResetValue 0x0 +#define DEBUG_R0_RegisterResetMask 0xFFFFFFFF +/* End of Register Definition for DEBUG_R0 */ + +/* Register DEBUG_R1 */ +/* Debug Register 1 */ +#define DEBUG_R1 (PRT_LOG_R_BaseAddress + 0x2c) +#define DEBUG_R1_RegisterSize 32 +#define DEBUG_R1_RegisterResetValue 0x0 +#define DEBUG_R1_RegisterResetMask 0xFFFFFFFF +/* End of Register Definition for DEBUG_R1 */ + +#define ATU_R_BaseAddress 0x900 +#define PCIE_PL_iATUVR (ATU_R_BaseAddress + 0x0) +#define PCIE_PL_iATURC1 (ATU_R_BaseAddress + 0x4) +#define PCIE_PL_iATURC2 (ATU_R_BaseAddress + 0x8) +#define PCIE_PL_iATURLBA (ATU_R_BaseAddress + 0xC) +#define PCIE_PL_iATURUBA (ATU_R_BaseAddress + 0x10) +#define PCIE_PL_iATURLA (ATU_R_BaseAddress + 0x14) +#define PCIE_PL_iATURLTA (ATU_R_BaseAddress + 0x18) +#define PCIE_PL_iATURUTA (ATU_R_BaseAddress + 0x1C) + +/* GPR1: iomuxc_gpr1_pcie_ref_clk_en(iomuxc_gpr1[16]) */ +#define iomuxc_gpr1_pcie_ref_clk_en (1 << 16) +/* GPR1: iomuxc_gpr1_test_powerdown(iomuxc_gpr1_18) */ +#define iomuxc_gpr1_test_powerdown (1 << 18) + +/* GPR12: iomuxc_gpr12_los_level(iomuxc_gpr12[8:4]) */ +#define iomuxc_gpr12_los_level (0x1F << 4) +/* GPR12: iomuxc_gpr12_app_ltssm_enable(iomuxc_gpr12[10]) */ +#define iomuxc_gpr12_app_ltssm_enable (1 << 10) +/* GPR12: iomuxc_gpr12_device_type(iomuxc_gpr12[15:12]) */ +#define iomuxc_gpr12_device_type (0xF << 12) + +/* GPR8: iomuxc_gpr8_tx_deemph_gen1(iomuxc_gpr8[5:0]) */ +#define iomuxc_gpr8_tx_deemph_gen1 (0x3F << 0) +/* GPR8: iomuxc_gpr8_tx_deemph_gen2_3p5db(iomuxc_gpr8[11:6]) */ +#define iomuxc_gpr8_tx_deemph_gen2_3p5db (0x3F << 6) +/* GPR8: iomuxc_gpr8_tx_deemph_gen2_6db(iomuxc_gpr8[17:12]) */ +#define iomuxc_gpr8_tx_deemph_gen2_6db (0x3F << 12) +/* GPR8: iomuxc_gpr8_tx_swing_full(iomuxc_gpr8[24:18]) */ +#define iomuxc_gpr8_tx_swing_full (0x7F << 18) +/* GPR8: iomuxc_gpr8_tx_swing_low(iomuxc_gpr8[31:25]) */ +#define iomuxc_gpr8_tx_swing_low (0x7F << 25) + +/* Registers of PHY */ +/* Register PHY_STS_R */ +/* PHY Status Register */ +#define PHY_STS_R (PRT_LOG_R_BaseAddress + 0x110) + +/* Register PHY_CTRL_R */ +/* PHY Control Register */ +#define PHY_CTRL_R (PRT_LOG_R_BaseAddress + 0x114) + +#define SSP_CR_SUP_DIG_MPLL_OVRD_IN_LO 0x0011 +/* FIELD: RES_ACK_IN_OVRD [15:15] +// FIELD: RES_ACK_IN [14:14] +// FIELD: RES_REQ_IN_OVRD [13:13] +// FIELD: RES_REQ_IN [12:12] +// FIELD: RTUNE_REQ_OVRD [11:11] +// FIELD: RTUNE_REQ [10:10] +// FIELD: MPLL_MULTIPLIER_OVRD [9:9] +// FIELD: MPLL_MULTIPLIER [8:2] +// FIELD: MPLL_EN_OVRD [1:1] +// FIELD: MPLL_EN [0:0] +*/ + +#define SSP_CR_SUP_DIG_ATEOVRD 0x0010 +/* FIELD: ateovrd_en [2:2] +// FIELD: ref_usb2_en [1:1] +// FIELD: ref_clkdiv2 [0:0] +*/ + +#define SSP_CR_LANE0_DIG_RX_OVRD_IN_LO 0x1005 +/* FIELD: RX_LOS_EN_OVRD [13:13] +// FIELD: RX_LOS_EN [12:12] +// FIELD: RX_TERM_EN_OVRD [11:11] +// FIELD: RX_TERM_EN [10:10] +// FIELD: RX_BIT_SHIFT_OVRD [9:9] +// FIELD: RX_BIT_SHIFT [8:8] +// FIELD: RX_ALIGN_EN_OVRD [7:7] +// FIELD: RX_ALIGN_EN [6:6] +// FIELD: RX_DATA_EN_OVRD [5:5] +// FIELD: RX_DATA_EN [4:4] +// FIELD: RX_PLL_EN_OVRD [3:3] +// FIELD: RX_PLL_EN [2:2] +// FIELD: RX_INVERT_OVRD [1:1] +// FIELD: RX_INVERT [0:0] +*/ + +#define SSP_CR_LANE0_DIG_RX_ASIC_OUT 0x100D +/* FIELD: LOS [2:2] +// FIELD: PLL_STATE [1:1] +// FIELD: VALID [0:0] +*/ + +/* control bus bit definition */ +#define PCIE_CR_CTL_DATA_LOC 0 +#define PCIE_CR_CTL_CAP_ADR_LOC 16 +#define PCIE_CR_CTL_CAP_DAT_LOC 17 +#define PCIE_CR_CTL_WR_LOC 18 +#define PCIE_CR_CTL_RD_LOC 19 +#define PCIE_CR_STAT_DATA_LOC 0 +#define PCIE_CR_STAT_ACK_LOC 16 + +/* End of Register Definitions */ + +#define PCIE_CONF_BUS(b) (((b) & 0xFF) << 16) +#define PCIE_CONF_DEV(d) (((d) & 0x1F) << 11) +#define PCIE_CONF_FUNC(f) (((f) & 0x7) << 8) +#define PCIE_CONF_REG(r) ((r) & ~0x3) + + +/* Taken from PCI specs */ +enum { + MemRdWr = 0, + MemRdLk = 1, + IORdWr = 2, + CfgRdWr0 = 4, + CfgRdWr1 = 5 +}; + + +struct imx_pcie_port { + struct device *dev; + u8 index; + u8 root_bus_nr; + int interrupt; + + struct resource *dbi; + struct resource *io; + struct resource *mem; + struct resource *root; + + struct regmap *iomuxc_gpr; + + void __iomem *root_base; + void __iomem *dbi_base; + void __iomem *io_base; + void __iomem *mem_base; + spinlock_t conf_lock; + + char io_space_name[16]; + char mem_space_name[16]; + + struct list_head next; + + struct clk *lvds1_sel; + struct clk *lvds1; + struct clk *pcie_ref_125m; + struct clk *pcie_axi; + struct clk *sata_ref; + + unsigned int pcie_pwr_en; + unsigned int pcie_rst; + unsigned int pcie_wake_up; + + struct rfkill *rfkill; +}; + +static const struct of_device_id pcie_of_match[] = { + { + .compatible = "fsl,imx6q-pcie", + .data = NULL, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, pcie_of_match); + +static struct list_head pcie_port_list; +static struct hw_pci imx_pcie; + +static int pcie_phy_cr_read(void __iomem *dbi_base, int addr, int *data); +static int pcie_phy_cr_write(void __iomem *dbi_base, int addr, int data); + + +/* IMX PCIE GPR configure routines */ +static void imx_pcie_clrset(struct imx_pcie_port *pp, + u32 mask, u32 val, u32 reg) +{ + u32 tmp; + regmap_read(pp->iomuxc_gpr, reg, &tmp); + tmp &= ~mask; + tmp |= (val & mask); + regmap_write(pp->iomuxc_gpr, reg, tmp); +} + +static void change_field(int *in, int start, int end, int val) +{ + int mask; + mask = ((0xFFFFFFFF << start) ^ (0xFFFFFFFF << (end + 1))) & 0xFFFFFFFF; + *in = (*in & ~mask) | (val << start); +} + + +static struct imx_pcie_port *controller_to_port(int index) +{ + struct imx_pcie_port *pp; + + if (index >= imx_pcie.nr_controllers) { + pr_err("%d exceeded number of controllers %d\n", + index, imx_pcie.nr_controllers); + return NULL; + } + + list_for_each_entry(pp, &pcie_port_list, next) { + if (pp->index == index) + return pp; + } + return NULL; +} + +static struct imx_pcie_port *bus_to_port(int bus) +{ + int i; + int rbus; + struct imx_pcie_port *pp; + + for (i = imx_pcie.nr_controllers - 1 ; i >= 0; i--) { + pp = controller_to_port(i); + rbus = pp->root_bus_nr; + if (rbus != -1 && rbus <= bus) + break; + } + + return i >= 0 ? pp : NULL; +} + +static int __init imx_pcie_setup(int nr, struct pci_sys_data *sys) +{ + struct imx_pcie_port *pp; + int ret; + + pp = controller_to_port(nr); + if (!pp) { + pr_err("unable to find port %d\n", nr); + return 0; + } + + pp->root_bus_nr = sys->busnr; + + /* + * IORESOURCE_MEM + */ + snprintf(pp->mem_space_name, sizeof(pp->mem_space_name), + "PCIe %d MEM", pp->index); + + pp->mem_space_name[sizeof(pp->mem_space_name) - 1] = 0; + pp->mem->name = pp->mem_space_name; + pp->mem->flags = IORESOURCE_MEM; + ret = request_resource(&iomem_resource, pp->mem); + if (ret) + dev_err(pp->dev, "Request PCIe Memory resource failed\n"); + pci_add_resource_offset(&sys->resources, pp->mem, sys->mem_offset); + + + snprintf(pp->io_space_name, sizeof(pp->io_space_name), + "PCIe %d I/O", pp->index); + pp->io_space_name[sizeof(pp->io_space_name) - 1] = 0; + pp->io->name = pp->io_space_name; + pp->io->flags = IORESOURCE_IO; + + ret = request_resource(&iomem_resource, pp->io); + if (ret) + dev_err(pp->dev, "Request PCIe IO resource failed\n"); + pci_add_resource_offset(&sys->resources, pp->io, sys->io_offset); + + /* + * IORESOURCE_IO + */ + ret = pci_ioremap_io(PCIBIOS_MIN_IO, pp->io->start); + if (ret) + dev_err(pp->dev, "Request PCIe IO resource failed\n"); + + return 1; +} + +static int imx_pcie_link_up(struct platform_device *pdev) +{ + struct imx_pcie_port *pp = platform_get_drvdata(pdev); + int iterations = 200; + u32 rc, ltssm, rx_valid, temp; + + rc = 0; + for (iterations = 200; iterations > 0 && !rc; iterations--) { + /* link is debug bit 36, debug register 1 starts at bit 32 */ + rc = readl(pp->dbi_base + DEBUG_R1) & (0x1 << (36 - 32)) ; + usleep_range(2000, 3000); + + /* 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_cr_read(pp->dbi_base, SSP_CR_LANE0_DIG_RX_ASIC_OUT, &rx_valid); + ltssm = readl(pp->dbi_base + DEBUG_R0) & 0x3F; + if ((ltssm == 0x0D) && ((rx_valid & 0x01) == 0)) { + dev_err(&pdev->dev, + "transition to gen2 is stuck, reset PHY!\n"); + pcie_phy_cr_read(pp->dbi_base, SSP_CR_LANE0_DIG_RX_OVRD_IN_LO, &temp); + change_field(&temp, 3, 3, 0x1); + change_field(&temp, 5, 5, 0x1); + pcie_phy_cr_write(pp->dbi_base, SSP_CR_LANE0_DIG_RX_OVRD_IN_LO, + 0x0028); + usleep_range(2000, 3000); + pcie_phy_cr_read(pp->dbi_base, SSP_CR_LANE0_DIG_RX_OVRD_IN_LO, &temp); + change_field(&temp, 3, 3, 0x0); + change_field(&temp, 5, 5, 0x0); + pcie_phy_cr_write(pp->dbi_base, SSP_CR_LANE0_DIG_RX_OVRD_IN_LO, + 0x0000); + } + + } + + if (!rc) { + if (iterations <= 0) { + dev_err(&pdev->dev, + "link up failed, DEBUG_R0:0x%08x, DEBUG_R1:0x%08x RX_VALID:0x%x!\n", + readl(pp->dbi_base + DEBUG_R0), + readl(pp->dbi_base + DEBUG_R1), + rx_valid); + return -ETIMEDOUT; + } + return -ENODEV; + } + + return 0; +} + +static int imx_pcie_regions_setup(struct platform_device *pdev, + struct imx_pcie_port *pp) +{ + void __iomem *dbi_base = pp->dbi_base; + /* + * 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 */ + writel(readl(dbi_base + PCI_COMMAND) + | PCI_COMMAND_IO + | PCI_COMMAND_MEMORY + | PCI_COMMAND_MASTER, + dbi_base + PCI_COMMAND); + + /* Set the CLASS_REV of RC CFG header to PCI_CLASS_BRIDGE_PCI */ + writel(readl(dbi_base + PCI_CLASS_REVISION) + | (PCI_CLASS_BRIDGE_PCI << 16), + dbi_base + PCI_CLASS_REVISION); + + /* + * region0 outbound used to access target cfg + */ + writel(0, dbi_base + PCIE_PL_iATUVR); + writel(pp->root->start, dbi_base + PCIE_PL_iATURLBA); + writel(pp->dbi->end, dbi_base + PCIE_PL_iATURLA); + writel(0, dbi_base + PCIE_PL_iATURUBA); + + writel(0, dbi_base + PCIE_PL_iATURLTA); + writel(0, dbi_base + PCIE_PL_iATURUTA); + writel(CfgRdWr0, dbi_base + PCIE_PL_iATURC1); + writel((1<<31), dbi_base + PCIE_PL_iATURC2); + + return 0; +} + + +static int imx_pcie_valid_config(struct imx_pcie_port *pp, + struct pci_bus *bus, int devfn) +{ + if (bus->number >= 2) + return 0; + + if (devfn != 0) + return 0; + + return 1; +} + + +static u32 get_bus_address(struct imx_pcie_port *pp, + struct pci_bus *bus, u32 devfn, int where) +{ + u32 va_address; + if (bus->number == 0) { + va_address = (u32)pp->dbi_base + (where & ~0x3); + } + else { + va_address = (u32)pp->root_base + + (PCIE_CONF_BUS(bus->number - 1) + + PCIE_CONF_DEV(PCI_SLOT(devfn)) + + PCIE_CONF_FUNC(PCI_FUNC(devfn)) + + PCIE_CONF_REG(where)); + } + return va_address; +} + +static int imx_pcie_read_config(struct pci_bus *bus, u32 devfn, int where, + int size, u32 *val) +{ + struct imx_pcie_port *pp = bus_to_port(bus->number); + u32 va_address; + + if (!pp) { + BUG(); + return -EINVAL; + } + + if (imx_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0) { + *val = 0xffffffff; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + va_address = get_bus_address(pp, bus, devfn, where); + + *val = readl((u32 *)va_address); + + if (size == 1) + *val = (*val >> (8 * (where & 3))) & 0xFF; + else if (size == 2) + *val = (*val >> (8 * (where & 3))) & 0xFFFF; + + return PCIBIOS_SUCCESSFUL; +} + +static int imx_pcie_write_config(struct pci_bus *bus, u32 devfn, + int where, int size, u32 val) +{ + struct imx_pcie_port *pp = bus_to_port(bus->number); + u32 va_address = 0, mask = 0, tmp = 0; + int ret = PCIBIOS_SUCCESSFUL; + + if (!pp) { + BUG(); + return -EINVAL; + } + + if (imx_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0) + return PCIBIOS_DEVICE_NOT_FOUND; + + va_address = get_bus_address(pp, bus, devfn, where); + + if (size == 4) { + writel(val, (u32 *)va_address); + goto exit; + } + + if (size == 2) + mask = ~(0xFFFF << ((where & 0x3) * 8)); + else if (size == 1) + mask = ~(0xFF << ((where & 0x3) * 8)); + else + ret = PCIBIOS_BAD_REGISTER_NUMBER; + + tmp = readl((u32 *)va_address) & mask; + tmp |= val << ((where & 0x3) * 8); + writel(tmp, (u32 *)va_address); +exit: + + return ret; +} + + + +static struct pci_ops imx_pcie_ops = { + .read = imx_pcie_read_config, + .write = imx_pcie_write_config, +}; + +static struct pci_bus __init * +imx_pcie_scan_bus(int nr, struct pci_sys_data *sys) +{ + struct imx_pcie_port *pp = controller_to_port(nr); + if (nr > 1) + return NULL; + pp->root_bus_nr = sys->busnr; + + return pci_scan_root_bus(NULL, sys->busnr, &imx_pcie_ops, sys, + &sys->resources); +} + +static int __init imx_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) +{ + struct imx_pcie_port *pp = controller_to_port(0); + return pp->interrupt; +} + +static struct hw_pci imx_pci __initdata = { + .nr_controllers = 1, + .setup = imx_pcie_setup, + .scan = imx_pcie_scan_bus, + .map_irq = imx_pcie_map_irq, +}; + +/* PHY CR bus acess routines */ +static int pcie_phy_cr_ack_polling(void __iomem *dbi_base, int max_iterations, int exp_val) +{ + u32 temp_rd_data, wait_counter = 0; + + do { + temp_rd_data = readl(dbi_base + PHY_STS_R); + temp_rd_data = (temp_rd_data >> PCIE_CR_STAT_ACK_LOC) & 0x1; + wait_counter++; + } while ((wait_counter < max_iterations) && (temp_rd_data != exp_val)); + + if (temp_rd_data != exp_val) + return 0 ; + return 1 ; +} + +static int pcie_phy_cr_cap_addr(void __iomem *dbi_base, int addr) +{ + u32 temp_wr_data; + + /* write addr */ + temp_wr_data = addr << PCIE_CR_CTL_DATA_LOC ; + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* capture addr */ + temp_wr_data |= (0x1 << PCIE_CR_CTL_CAP_ADR_LOC); + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* wait for ack */ + if (!pcie_phy_cr_ack_polling(dbi_base, 100, 1)) + return 0; + + /* deassert cap addr */ + temp_wr_data = addr << PCIE_CR_CTL_DATA_LOC; + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* wait for ack de-assetion */ + if (!pcie_phy_cr_ack_polling(dbi_base, 100, 0)) + return 0 ; + + return 1 ; +} + +static int pcie_phy_cr_read(void __iomem *dbi_base, int addr , int *data) +{ + u32 temp_rd_data, temp_wr_data; + + /* write addr */ + /* cap addr */ + if (!pcie_phy_cr_cap_addr(dbi_base, addr)) + return 0; + + /* assert rd signal */ + temp_wr_data = 0x1 << PCIE_CR_CTL_RD_LOC; + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* wait for ack */ + if (!pcie_phy_cr_ack_polling(dbi_base, 100, 1)) + return 0; + + /* after got ack return data */ + temp_rd_data = readl(dbi_base + PHY_STS_R); + *data = (temp_rd_data & (0xffff << PCIE_CR_STAT_DATA_LOC)) ; + + /* deassert rd signal */ + temp_wr_data = 0x0; + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* wait for ack de-assetion */ + if (!pcie_phy_cr_ack_polling(dbi_base, 100, 0)) + return 0 ; + + return 1 ; + +} + +static int pcie_phy_cr_write(void __iomem *dbi_base, int addr, int data) +{ + u32 temp_wr_data; + + /* write addr */ + /* cap addr */ + if (!pcie_phy_cr_cap_addr(dbi_base, addr)) + return 0 ; + + temp_wr_data = data << PCIE_CR_CTL_DATA_LOC; + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* capture data */ + temp_wr_data |= (0x1 << PCIE_CR_CTL_CAP_DAT_LOC); + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* wait for ack */ + if (!pcie_phy_cr_ack_polling(dbi_base, 100, 1)) + return 0 ; + + /* deassert cap data */ + temp_wr_data = data << PCIE_CR_CTL_DATA_LOC; + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* wait for ack de-assetion */ + if (!pcie_phy_cr_ack_polling(dbi_base, 100, 0)) + return 0; + + /* assert wr signal */ + temp_wr_data = 0x1 << PCIE_CR_CTL_WR_LOC; + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* wait for ack */ + if (!pcie_phy_cr_ack_polling(dbi_base, 100, 1)) + return 0; + + /* deassert wr signal */ + temp_wr_data = data << PCIE_CR_CTL_DATA_LOC; + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + /* wait for ack de-assetion */ + if (!pcie_phy_cr_ack_polling(dbi_base, 100, 0)) + return 0; + + temp_wr_data = 0x0 ; + writel(temp_wr_data, dbi_base + PHY_CTRL_R); + + return 1; +} + +static int imx_pcie_enable_controller(struct platform_device *pdev) +{ + struct imx_pcie_port *pp = platform_get_drvdata(pdev); + int ret; + + /* Enable PCIE power */ + gpio_set_value(pp->pcie_pwr_en, 1); + + imx_pcie_clrset(pp, iomuxc_gpr1_test_powerdown, 0 << 18, IOMUXC_GPR1); + imx_pcie_clrset(pp, iomuxc_gpr1_pcie_ref_clk_en, 1 << 16, IOMUXC_GPR1); + + /* Enable clocks */ + ret = clk_set_parent(pp->lvds1_sel, pp->sata_ref); + if (ret) { + dev_err(&pdev->dev, "unable to set lvds1 parent: %d\n", ret); + return -EINVAL; + } + + ret = clk_prepare_enable(pp->pcie_ref_125m); + if (ret) { + dev_err(&pdev->dev, "unable to enable pcie_ref_125m: %d\n", ret); + return -EINVAL; + } + + ret = clk_prepare_enable(pp->lvds1); + if (ret) { + dev_err(&pdev->dev, "unable to enable lvds1: %d\n", ret); + return -EINVAL; + } + + ret = clk_prepare_enable(pp->pcie_axi); + if (ret) { + dev_err(&pdev->dev, "unable to enable pcie_axi: %d\n", ret); + return -EINVAL; + } + + + return 0; +} + +static void card_reset(struct platform_device *pdev) +{ + struct imx_pcie_port *pp = platform_get_drvdata(pdev); + + imx_pcie_clrset(pp, iomuxc_gpr1_test_powerdown, 1 << 18, IOMUXC_GPR1); + imx_pcie_clrset(pp, iomuxc_gpr12_app_ltssm_enable, 1 << 10, IOMUXC_GPR12); + imx_pcie_clrset(pp, iomuxc_gpr1_pcie_ref_clk_en, 0 << 16, IOMUXC_GPR1); + + gpio_set_value(pp->pcie_rst, 0); + msleep(100); + gpio_set_value(pp->pcie_rst, 1); +} + +static int __init add_pcie_port(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx_pcie_port *pp = platform_get_drvdata(pdev); + int ret; + + ret = imx_pcie_link_up(pdev); + if (ret) { + dev_info(dev, "IMX PCIe port: link down!\n"); + /* Release the clocks, and disable the power */ + + clk_disable(pp->pcie_axi); + clk_put(pp->pcie_axi); + + clk_disable(pp->lvds1); + clk_put(pp->lvds1); + + clk_put(pp->pcie_ref_125m); + clk_put(pp->sata_ref); + + imx_pcie_clrset(pp, iomuxc_gpr1_pcie_ref_clk_en, 0 << 16, + IOMUXC_GPR1); + + /* Disable PCIE power */ + gpio_set_value(pp->pcie_pwr_en, 0); + + imx_pcie_clrset(pp, iomuxc_gpr1_test_powerdown, 1 << 18, + IOMUXC_GPR1); + + return ret; + } + + dev_info(dev, "IMX PCIe port: link up.\n"); + pp->index = 0; + pp->root_bus_nr = -1; + spin_lock_init(&pp->conf_lock); + return 0; +} + + +static int set_pcie_clock_tunings(struct platform_device *pdev) +{ + struct imx_pcie_port *pp = platform_get_drvdata(pdev); + /* FIXME the field name should be aligned to RM */ + imx_pcie_clrset(pp, iomuxc_gpr12_app_ltssm_enable, 0 << 10, IOMUXC_GPR12); + + /* configure constant input signal to the pcie ctrl and phy */ + imx_pcie_clrset(pp, iomuxc_gpr12_device_type, PCI_EXP_TYPE_ROOT_PORT << 12, + IOMUXC_GPR12); + imx_pcie_clrset(pp, iomuxc_gpr12_los_level, 9 << 4, IOMUXC_GPR12); + + imx_pcie_clrset(pp, iomuxc_gpr8_tx_deemph_gen1, 0 << 0, IOMUXC_GPR8); + imx_pcie_clrset(pp, iomuxc_gpr8_tx_deemph_gen2_3p5db, 0 << 6, IOMUXC_GPR8); + imx_pcie_clrset(pp, iomuxc_gpr8_tx_deemph_gen2_6db, 20 << 12, IOMUXC_GPR8); + imx_pcie_clrset(pp, iomuxc_gpr8_tx_swing_full, 127 << 18, IOMUXC_GPR8); + imx_pcie_clrset(pp, iomuxc_gpr8_tx_swing_low, 127 << 25, IOMUXC_GPR8); + return 0; +} + + +static int __init imx_pcie_pltfm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx_pcie_port *pp = devm_kzalloc(dev, sizeof(*pp), GFP_KERNEL); + int ret; + + platform_set_drvdata(pdev, pp); + pp->dev = &pdev->dev; + + pp->pcie_pwr_en = of_get_named_gpio(pdev->dev.of_node, + "power-enable", 0); + if (gpio_is_valid(pp->pcie_pwr_en)) + devm_gpio_request_one(dev, pp->pcie_pwr_en, + GPIOF_OUT_INIT_LOW, + "PCIe power enable"); + + pp->pcie_rst = of_get_named_gpio(pdev->dev.of_node, + "pcie-reset", 0); + if (gpio_is_valid(pp->pcie_rst)) + devm_gpio_request_one(dev, pp->pcie_rst, + GPIOF_OUT_INIT_LOW, + "PCIe reset"); + + pp->pcie_wake_up = of_get_named_gpio(pdev->dev.of_node, + "wake-up", 0); + if (gpio_is_valid(pp->pcie_wake_up)) + devm_gpio_request_one(dev, pp->pcie_wake_up, + GPIOF_IN, + "PCIe wake up"); + + pp->dbi = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!pp->dbi) { + dev_err(dev, "no mmio space\n"); + return -EINVAL; + } + + pp->dbi_base = devm_request_and_ioremap(&pdev->dev, pp->dbi); + if (!pp->dbi_base) { + pr_err("unable to remap dbi\n"); + return -ENOMEM; + } + + + pp->io = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!pp->io) { + dev_err(dev, "no mmio space\n"); + return -EINVAL; + } + + pp->mem = platform_get_resource(pdev, IORESOURCE_MEM, 2); + if (!pp->mem) { + dev_err(dev, "no mmio space\n"); + return -EINVAL; + } + + pp->root = platform_get_resource(pdev, IORESOURCE_MEM, 3); + if (!pp->root) { + dev_err(dev, "no root memory space\n"); + return -EINVAL; + } + + pp->root_base = devm_request_and_ioremap(&pdev->dev, pp->root); + if (!pp->root_base) { + dev_err(&pdev->dev, "unable to remap root mem\n"); + return -ENOMEM; + } + + + pp->interrupt = platform_get_irq(pdev, 0); + + + /* Setup clocks */ + pp->lvds1_sel = clk_get(dev, "lvds1_sel"); + if (IS_ERR(pp->lvds1_sel)) { + dev_err(dev, + "lvds1_sel clock missing or invalid\n"); + ret = -EINVAL; + goto err_out; + } + + pp->lvds1 = clk_get(dev, "lvds1"); + if (IS_ERR(pp->lvds1)) { + dev_err(dev, + "lvds1 clock select missing or invalid\n"); + ret = -EINVAL; + goto err_out; + } + + pp->pcie_ref_125m = clk_get(dev, "pcie_ref_125m"); + if (IS_ERR(pp->pcie_ref_125m)) { + dev_err(dev, + "pcie_ref_125m clock source missing or invalid\n"); + ret = -EINVAL; + goto err_out; + } + + pp->pcie_axi = clk_get(dev, "pcie_axi"); + if (IS_ERR(pp->pcie_axi)) { + dev_err(dev, "pcie_axi clock source missing or invalid\n"); + ret = -EINVAL; + goto err_out; + } + + pp->sata_ref = clk_get(dev, "sata_ref"); + if (IS_ERR(pp->sata_ref)) { + dev_err(dev, "sata_ref clock source missing or invalid\n"); + ret = -EINVAL; + goto err_out; + } + + pp->iomuxc_gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (IS_ERR(pp->iomuxc_gpr)) { + dev_err(dev, "unable to find iomuxc registers\n"); + ret = -EINVAL; + goto err_out; + } + + /* togle the external card's reset */ + card_reset(pdev); + + /* Enable the pwr, clks and so on */ + set_pcie_clock_tunings(pdev); + ret = imx_pcie_enable_controller(pdev); + if (ret) + goto err_out; + + usleep_range(3000, 4000); + imx_pcie_regions_setup(pdev, pp); + usleep_range(3000, 4000); + + /* start link up */ + imx_pcie_clrset(pp, iomuxc_gpr12_app_ltssm_enable, 1 << 10, IOMUXC_GPR12); + + /* add the pcie port */ + ret = add_pcie_port(pdev); + if (ret) + goto err_out; + + pp->index = imx_pcie.nr_controllers; + imx_pcie.nr_controllers++; + list_add_tail(&pp->next, &pcie_port_list); + + pci_common_init(&imx_pci); + + return 0; + +err_out: + if (pp->lvds1_sel) + clk_put(pp->lvds1_sel); + if (pp->lvds1) + clk_put(pp->lvds1); + if (pp->pcie_ref_125m) + clk_put(pp->pcie_ref_125m); + if (pp->pcie_axi) + clk_put(pp->pcie_axi); + if (pp->sata_ref) + clk_put(pp->sata_ref); + return ret; +} + +static int __exit imx_pcie_pltfm_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx_pcie_port *pp = platform_get_drvdata(pdev); + + if (pp->rfkill) { + rfkill_unregister(pp->rfkill); + rfkill_destroy(pp->rfkill); + pp->rfkill = NULL; + } + + imx_pcie_clrset(pp, iomuxc_gpr1_pcie_ref_clk_en, 0 << 16, IOMUXC_GPR1); + imx_pcie_clrset(pp, iomuxc_gpr1_test_powerdown, 1 << 18, IOMUXC_GPR1); + imx_pcie_clrset(pp, iomuxc_gpr12_app_ltssm_enable, 1 << 10, IOMUXC_GPR12); + + /* Release clocks, and disable power */ + if (pp->pcie_axi) { + clk_disable(pp->pcie_axi); + clk_put(pp->pcie_axi); + } + + if (pp->lvds1) { + clk_disable(pp->lvds1); + clk_put(pp->lvds1); + } + + if (pp->pcie_ref_125m) + clk_put(pp->pcie_ref_125m); + + if (pp->sata_ref) + clk_put(pp->sata_ref); + + gpio_set_value(pp->pcie_rst, 0); + gpio_set_value(pp->pcie_pwr_en, 0); + + dev_err(dev, "disabled everything\n"); + msleep(500); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver imx_pcie_pltfm_driver = { + .driver = { + .name = "imx-pcie", + .owner = THIS_MODULE, + .of_match_table = pcie_of_match, + }, + .probe = imx_pcie_pltfm_probe, + .remove = __exit_p(imx_pcie_pltfm_remove), +}; + +/*****************************************************************************\ + * * + * Driver init/exit * + * * +\*****************************************************************************/ + +static int __init imx_pcie_drv_init(void) +{ + INIT_LIST_HEAD(&pcie_port_list); + 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); + +MODULE_DESCRIPTION("i.MX PCIE platform driver"); +MODULE_AUTHOR("Sean Cross "); +MODULE_LICENSE("GPL v2");