[PATCH 3/4] PCI: Add driver for i.MX6 PCI Express
Sean Cross
xobs at kosagi.com
Mon Jul 1 03:15:46 EDT 2013
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 <xobs at kosagi.com>
---
.../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 at 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 <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of_device.h>
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+#include <linux/rfkill.h>
+
+#include <asm/sizes.h>
+#include <asm/io.h>
+
+
+/* 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 <xobs at kosagi.com>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
More information about the linux-arm-kernel
mailing list