summaryrefslogtreecommitdiff
path: root/drivers/pci
diff options
context:
space:
mode:
authorRichard Zhu <richard.zhu@freescale.com>2014-10-17 13:05:03 +0800
committerNitin Garg <nitin.garg@freescale.com>2015-01-15 21:18:48 -0600
commit75054ad46720689a05432dd0ba17f3bfa78836bf (patch)
tree16cddf697c2d04db6fccc7f83617358780ac5945 /drivers/pci
parent1254f7758d2daae5e956fc2354f45a7f615a9045 (diff)
MLK-10005 PCI: imx6:enable pcie ep rc validation system
hw setup: * two imx6q sd (imx6sx sdb) boards, one is used as pcie rc, the other is used as pcie ep. Connected by fsl pcie adap adaptors. sw setup: * when build rc image, make sure that CONFIG_IMX_PCIE=y # CONFIG_EP_MODE_IN_EP_RC_SYS is not set CONFIG_RC_MODE_IN_EP_RC_SYS=y * when build ep image CONFIG_EP_MODE_IN_EP_RC_SYS=y # CONFIG_RC_MODE_IN_EP_RC_SYS is not set features: * set-up link between rc and ep by their stand-alone ref clk running internally. * in ep's system, ep can access the reserved ddr memory (default address:0x4000_0000 on imx6q sd board, and 0xb000_0000 on imx6sx sdb board) of pcie rc's system, by the interconnection between pcie ep and pcie rc. * add the configuration methods in the ep side, used to configure the start address and the size of the reserved rc's memory window. - cat /sys/devices/soc0/soc.1/1ffc000.pcie/rc_memw_info - echo 0x41000000 > /sys/devices/soc0/soc.1/1ffc000.pcie/rc_memw_start_set - echo 0x800000 > /sys/devices/soc0/soc.1/1ffc000.pcie/rc_memw_size_set * provide one example, howto configure the bar# and so on, when pcie ep emaluates one memory ram ep device * setup one new outbound memory region at rc side, used to let imx6 pcie rc can access the memory of imx6 pcie ep in imx6 pcie rc ep validation system. - set the default address of the ddr memory to be 0x4000_0000 on imx6q sd board, and 0xb000_0000 on imx6sx sdb board. NOTE: * boot up ep platform firstly, then boot up rc platform. * make sure that mem=768M is contained in the kernel command line, since the start address of the upper 256mb of the 1g ddr mem is reserved to do the pcie ep rc access operations in default. Signed-off-by: Richard Zhu <richard.zhu@freescale.com>
Diffstat (limited to 'drivers/pci')
-rw-r--r--drivers/pci/host/Kconfig8
-rw-r--r--drivers/pci/host/pci-imx6.c383
2 files changed, 376 insertions, 15 deletions
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
index 8922c376456a..062407805576 100644
--- a/drivers/pci/host/Kconfig
+++ b/drivers/pci/host/Kconfig
@@ -30,6 +30,14 @@ config PCI_IMX6
select PCIEPORTBUS
select PCIE_DW
+config EP_MODE_IN_EP_RC_SYS
+ bool "PCI Express EP mode in the IMX6 RC/EP interconnection system"
+ depends on PCI_IMX6
+
+config RC_MODE_IN_EP_RC_SYS
+ bool "PCI Express RC mode in the IMX6 RC/EP interconnection system"
+ depends on PCI_IMX6 && EP_MODE_IN_EP_RC_SYS!=y
+
config PCI_TEGRA
bool "NVIDIA Tegra PCIe controller"
depends on ARCH_TEGRA
diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c
index d9311d80f8ae..3f0634552137 100644
--- a/drivers/pci/host/pci-imx6.c
+++ b/drivers/pci/host/pci-imx6.c
@@ -1,6 +1,7 @@
/*
* PCIe host controller driver for Freescale i.MX6 SoCs
*
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All Rights Reserved.
* Copyright (C) 2013 Kosagi
* http://www.kosagi.com
*
@@ -14,24 +15,33 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
+#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
+#include <linux/of_address.h>
#include <linux/pci.h>
+#include <linux/pci_regs.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
-#include <linux/resource.h>
-#include <linux/signal.h>
-#include <linux/types.h>
-#include <linux/interrupt.h>
#include "pcie-designware.h"
#define to_imx6_pcie(x) container_of(x, struct imx6_pcie, pp)
+/*
+ * The default value of the reserved ddr memory
+ * used to verify EP/RC memory space access operations.
+ * The layout of the 1G ddr on SD boards
+ * [others]0x1000_0000 ~ 0x4FFF_FFFF
+ * [imx6sx]0x8000_0000 ~ 0xBFFF_FFFF
+ *
+ */
+static u32 ddr_test_region = 0, test_region_size = SZ_2M;
+
struct imx6_pcie {
int reset_gpio;
struct clk *pcie_bus;
@@ -292,10 +302,13 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp)
goto err_pcie_phy;
}
- ret = clk_prepare_enable(imx6_pcie->pcie_bus);
- if (ret) {
- dev_err(pp->dev, "unable to enable pcie_bus clock\n");
- goto err_pcie_bus;
+ if (!IS_ENABLED(CONFIG_EP_MODE_IN_EP_RC_SYS)
+ && !IS_ENABLED(CONFIG_RC_MODE_IN_EP_RC_SYS)) {
+ ret = clk_prepare_enable(imx6_pcie->pcie_bus);
+ if (ret) {
+ dev_err(pp->dev, "unable to enable pcie_bus clock\n");
+ goto err_pcie_bus;
+ }
}
ret = clk_prepare_enable(imx6_pcie->pcie);
@@ -352,7 +365,9 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp)
err_inbound_axi:
clk_disable_unprepare(imx6_pcie->pcie);
err_pcie:
- clk_disable_unprepare(imx6_pcie->pcie_bus);
+ if (!IS_ENABLED(CONFIG_EP_MODE_IN_EP_RC_SYS)
+ && !IS_ENABLED(CONFIG_RC_MODE_IN_EP_RC_SYS))
+ clk_disable_unprepare(imx6_pcie->pcie_bus);
err_pcie_bus:
clk_disable_unprepare(imx6_pcie->pcie_phy);
err_pcie_phy:
@@ -390,8 +405,14 @@ static int imx6_pcie_init_phy(struct pcie_port *pp)
IMX6Q_GPR12_PCIE_CTL_2, 0 << 10);
/* configure constant input signal to the pcie ctrl and phy */
- regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
- IMX6Q_GPR12_DEVICE_TYPE, PCI_EXP_TYPE_ROOT_PORT << 12);
+ if (IS_ENABLED(CONFIG_EP_MODE_IN_EP_RC_SYS))
+ regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+ IMX6Q_GPR12_DEVICE_TYPE,
+ PCI_EXP_TYPE_ENDPOINT << 12);
+ else
+ regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+ IMX6Q_GPR12_DEVICE_TYPE,
+ PCI_EXP_TYPE_ROOT_PORT << 12);
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
IMX6Q_GPR12_LOS_LEVEL, IMX6Q_GPR12_LOS_LEVEL_9);
@@ -638,6 +659,190 @@ static int __init imx6_add_pcie_port(struct pcie_port *pp,
return 0;
}
+static ssize_t imx_pcie_bar0_addr_info(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev);
+ struct pcie_port *pp = &imx6_pcie->pp;
+
+ return sprintf(buf, "imx-pcie-bar0-addr-info start 0x%08x\n",
+ readl(pp->dbi_base + PCI_BASE_ADDRESS_0));
+}
+
+static ssize_t imx_pcie_bar0_addr_start(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ u32 bar_start;
+ struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev);
+ struct pcie_port *pp = &imx6_pcie->pp;
+
+ sscanf(buf, "%x\n", &bar_start);
+ writel(bar_start, pp->dbi_base + PCI_BASE_ADDRESS_0);
+
+ return count;
+}
+
+static void imx_pcie_regions_setup(struct device *dev)
+{
+ struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev);
+ struct pcie_port *pp = &imx6_pcie->pp;
+
+ if (is_imx6sx_pcie(imx6_pcie) && ddr_test_region == 0)
+ ddr_test_region = 0xb0000000;
+ else if (ddr_test_region == 0)
+ ddr_test_region = 0x40000000;
+
+ /*
+ * region2 outbound used to access rc/ep mem
+ * in imx6 pcie ep/rc validation system
+ */
+ writel(2, pp->dbi_base + 0x900);
+ writel(pp->mem_base, pp->dbi_base + 0x90c);
+ writel(0, pp->dbi_base + 0x910);
+ writel(pp->mem_base + test_region_size, pp->dbi_base + 0x914);
+
+ writel(ddr_test_region, pp->dbi_base + 0x918);
+ writel(0, pp->dbi_base + 0x91c);
+ writel(0, pp->dbi_base + 0x904);
+ writel(1 << 31, pp->dbi_base + 0x908);
+}
+
+static ssize_t imx_pcie_memw_info(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ return sprintf(buf, "imx-pcie-rc-memw-info start 0x%08x, size 0x%08x\n",
+ ddr_test_region, test_region_size);
+}
+
+static ssize_t
+imx_pcie_memw_start(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u32 memw_start;
+ struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev);
+
+ sscanf(buf, "%x\n", &memw_start);
+
+ if (is_imx6sx_pcie(imx6_pcie)) {
+ if (memw_start < 0x80000000 || memw_start > 0xb0000000) {
+ dev_err(dev, "Invalid imx6sx sdb memory start addr.\n");
+ dev_info(dev, "e.x: echo 0xb1000000 > /sys/...");
+ return -1;
+ }
+ } else {
+ if (memw_start < 0x10000000 || memw_start > 0x40000000) {
+ dev_err(dev, "Invalid imx6q sd memory start addr.\n");
+ dev_info(dev, "e.x: echo 0xb1000000 > /sys/...");
+ return -1;
+ }
+ }
+
+ if (ddr_test_region != memw_start) {
+ ddr_test_region = memw_start;
+ imx_pcie_regions_setup(dev);
+ }
+
+ return count;
+}
+
+static ssize_t
+imx_pcie_memw_size(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u32 memw_size;
+
+ sscanf(buf, "%x\n", &memw_size);
+
+ if ((memw_size > (SZ_16M - SZ_1M)) || (memw_size < SZ_64K)) {
+ dev_err(dev, "Invalid, should be [SZ_64K,SZ_16M - SZ_1MB].\n");
+ dev_info(dev, "For example: echo 0x800000 > /sys/...");
+ return -1;
+ }
+
+ if (test_region_size != memw_size) {
+ test_region_size = memw_size;
+ imx_pcie_regions_setup(dev);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(memw_info, S_IRUGO, imx_pcie_memw_info, NULL);
+static DEVICE_ATTR(memw_start_set, S_IWUGO, NULL, imx_pcie_memw_start);
+static DEVICE_ATTR(memw_size_set, S_IWUGO, NULL, imx_pcie_memw_size);
+static DEVICE_ATTR(ep_bar0_addr, S_IRWXUGO, imx_pcie_bar0_addr_info,
+ imx_pcie_bar0_addr_start);
+
+static struct attribute *imx_pcie_attrs[] = {
+ /*
+ * The start address, and the limitation (64KB ~ (16MB - 1MB))
+ * of the ddr mem window reserved by RC, and used for EP to access.
+ * BTW, these attrs are only configured at EP side.
+ */
+ &dev_attr_memw_info.attr,
+ &dev_attr_memw_start_set.attr,
+ &dev_attr_memw_size_set.attr,
+ &dev_attr_ep_bar0_addr.attr,
+ NULL
+};
+
+static struct attribute_group imx_pcie_attrgroup = {
+ .attrs = imx_pcie_attrs,
+};
+
+static void imx6_pcie_setup_ep(struct pcie_port *pp)
+{
+ /* CMD reg:I/O space, MEM space, and Bus Master Enable */
+ writel(readl(pp->dbi_base + PCI_COMMAND)
+ | PCI_COMMAND_IO
+ | PCI_COMMAND_MEMORY
+ | PCI_COMMAND_MASTER,
+ pp->dbi_base + PCI_COMMAND);
+
+ /*
+ * configure the class_rev(emaluate one memory ram ep device),
+ * bar0 and bar1 of ep
+ */
+ writel(0xdeadbeaf, pp->dbi_base + PCI_VENDOR_ID);
+ writel(readl(pp->dbi_base + PCI_CLASS_REVISION)
+ | (PCI_CLASS_MEMORY_RAM << 16),
+ pp->dbi_base + PCI_CLASS_REVISION);
+ writel(0xdeadbeaf, pp->dbi_base
+ + PCI_SUBSYSTEM_VENDOR_ID);
+
+ /* 32bit none-prefetchable 8M bytes memory on bar0 */
+ writel(0x0, pp->dbi_base + PCI_BASE_ADDRESS_0);
+ writel(SZ_8M - 1, pp->dbi_base + (1 << 12)
+ + PCI_BASE_ADDRESS_0);
+
+ /* None used bar1 */
+ writel(0x0, pp->dbi_base + PCI_BASE_ADDRESS_1);
+ writel(0, pp->dbi_base + (1 << 12) + PCI_BASE_ADDRESS_1);
+
+ /* 4K bytes IO on bar2 */
+ writel(0x1, pp->dbi_base + PCI_BASE_ADDRESS_2);
+ writel(SZ_4K - 1, pp->dbi_base + (1 << 12) +
+ PCI_BASE_ADDRESS_2);
+
+ /*
+ * 32bit prefetchable 1M bytes memory on bar3
+ * FIXME BAR MASK3 is not changable, the size
+ * is fixed to 256 bytes.
+ */
+ writel(0x8, pp->dbi_base + PCI_BASE_ADDRESS_3);
+ writel(SZ_1M - 1, pp->dbi_base + (1 << 12)
+ + PCI_BASE_ADDRESS_3);
+
+ /*
+ * 64bit prefetchable 1M bytes memory on bar4-5.
+ * FIXME BAR4,5 are not enabled yet
+ */
+ writel(0xc, pp->dbi_base + PCI_BASE_ADDRESS_4);
+ writel(SZ_1M - 1, pp->dbi_base + (1 << 12)
+ + PCI_BASE_ADDRESS_4);
+ writel(0, pp->dbi_base + (1 << 12) + PCI_BASE_ADDRESS_5);
+}
+
#ifdef CONFIG_PM_SLEEP
static int pci_imx_suspend_noirq(struct device *dev)
{
@@ -725,6 +930,13 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
pp = &imx6_pcie->pp;
pp->dev = &pdev->dev;
+ if (IS_ENABLED(CONFIG_EP_MODE_IN_EP_RC_SYS)) {
+ /* add attributes for device */
+ ret = sysfs_create_group(&pdev->dev.kobj, &imx_pcie_attrgroup);
+ if (ret)
+ return -EINVAL;
+ }
+
/* Added for PCI abort handling */
hook_fault_code(16 + 6, imx6q_pcie_abort_handler, SIGBUS, 0,
"imprecise external abort");
@@ -794,12 +1006,153 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
return PTR_ERR(imx6_pcie->iomuxc_gpr);
}
- ret = imx6_add_pcie_port(pp, pdev);
- if (ret < 0)
- return ret;
+ if (IS_ENABLED(CONFIG_EP_MODE_IN_EP_RC_SYS)) {
+ int i;
+ void *test_reg1, *test_reg2;
+ void __iomem *pcie_arb_base_addr;
+ struct timeval tv1, tv2, tv3;
+ u32 tv_count1, tv_count2;
+ struct device_node *np = pp->dev->of_node;
+ struct of_pci_range range;
+ struct of_pci_range_parser parser;
+ unsigned long restype;
+
+ if (of_pci_range_parser_init(&parser, np)) {
+ dev_err(pp->dev, "missing ranges property\n");
+ return -EINVAL;
+ }
+
+ /* Get the memory ranges from DT */
+ for_each_of_pci_range(&parser, &range) {
+ restype = range.flags & IORESOURCE_TYPE_BITS;
+ if (restype == IORESOURCE_MEM) {
+ of_pci_range_to_resource(&range,
+ np, &pp->mem);
+ pp->mem.name = "MEM";
+ }
+ }
+
+ pp->mem_base = pp->mem.start;
+
+ imx6_pcie_assert_core_reset(pp);
+ ret = imx6_pcie_init_phy(pp);
+ ret |= imx6_pcie_deassert_core_reset(pp);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "unable to init pcie ep.\n");
+ return ret;
+ }
+
+ /*
+ * iMX6SX PCIe has the stand-alone power domain.
+ * refer to the initialization for iMX6SX PCIe,
+ * release the PCIe PHY reset here,
+ * before LTSSM enable is set
+ * .
+ */
+ if (is_imx6sx_pcie(imx6_pcie))
+ regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5,
+ BIT(19), 0 << 19);
+
+ /* assert LTSSM enable */
+ regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
+ IMX6Q_GPR12_PCIE_CTL_2, 1 << 10);
+
+ dev_info(&pdev->dev, "PCIe EP: waiting for link up...\n");
+
+ platform_set_drvdata(pdev, imx6_pcie);
+ /* link is indicated by the bit4 of DB_R1 register */
+ do {
+ usleep_range(10, 20);
+ } while ((readl(pp->dbi_base + PCIE_PHY_DEBUG_R1) & 0x10) == 0);
+
+ imx6_pcie_setup_ep(pp);
+
+ imx_pcie_regions_setup(&pdev->dev);
+
+ /* self io test */
+ test_reg1 = devm_kzalloc(&pdev->dev,
+ test_region_size, GFP_KERNEL);
+ if (!test_reg1) {
+ pr_err("pcie ep: can't alloc the test reg1.\n");
+ ret = PTR_ERR(test_reg1);
+ goto err;
+ }
+
+ test_reg2 = devm_kzalloc(&pdev->dev,
+ test_region_size, GFP_KERNEL);
+ if (!test_reg2) {
+ pr_err("pcie ep: can't alloc the test reg2.\n");
+ ret = PTR_ERR(test_reg1);
+ goto err;
+ }
+
+ pcie_arb_base_addr = ioremap_cache(pp->mem_base,
+ test_region_size);
+
+ if (!pcie_arb_base_addr) {
+ pr_err("error with ioremap in ep selftest\n");
+ ret = PTR_ERR(pcie_arb_base_addr);
+ goto err;
+ }
+
+ for (i = 0; i < test_region_size; i = i + 4) {
+ writel(0xE6600D00 + i, test_reg1 + i);
+ writel(0xDEADBEAF, test_reg2 + i);
+ }
+
+ /* PCIe EP start the data transfer after link up */
+ pr_info("pcie ep: Starting data transfer...\n");
+ do_gettimeofday(&tv1);
+
+ memcpy((unsigned long *)pcie_arb_base_addr,
+ (unsigned long *)test_reg1,
+ test_region_size);
+
+ do_gettimeofday(&tv2);
+
+ memcpy((unsigned long *)test_reg2,
+ (unsigned long *)pcie_arb_base_addr,
+ test_region_size);
+
+ do_gettimeofday(&tv3);
+
+ if (memcmp(test_reg2, test_reg1, test_region_size) == 0) {
+ tv_count1 = (tv2.tv_sec - tv1.tv_sec)
+ * USEC_PER_SEC
+ + tv2.tv_usec - tv1.tv_usec;
+ tv_count2 = (tv3.tv_sec - tv2.tv_sec)
+ * USEC_PER_SEC
+ + tv3.tv_usec - tv2.tv_usec;
+
+ pr_info("pcie ep: Data transfer is successful."
+ " tv_count1 %dus,"
+ " tv_count2 %dus.\n",
+ tv_count1, tv_count2);
+ pr_info("pcie ep: Data write speed:%ldMB/s.\n",
+ ((test_region_size/1024)
+ * MSEC_PER_SEC)
+ /(tv_count1));
+ pr_info("pcie ep: Data read speed:%ldMB/s.\n",
+ ((test_region_size/1024)
+ * MSEC_PER_SEC)
+ /(tv_count2));
+ } else {
+ pr_info("pcie ep: Data transfer is failed.\n");
+ } /* end of self io test. */
+ } else {
+ ret = imx6_add_pcie_port(pp, pdev);
+ if (ret < 0)
+ return ret;
+
+ platform_set_drvdata(pdev, imx6_pcie);
+
+ if (IS_ENABLED(CONFIG_RC_MODE_IN_EP_RC_SYS))
+ imx_pcie_regions_setup(&pdev->dev);
+ }
- platform_set_drvdata(pdev, imx6_pcie);
return 0;
+err:
+ return ret;
}
static void imx6_pcie_shutdown(struct platform_device *pdev)