#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/of.h>
+#include <linux/of_address.h>
#define DRV_DESCRIPTION "i.MX PCIE endpoint device driver"
#define DRV_VERSION "version 0.1"
static int imx_pcie_ep_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
- int ret = 0;
- unsigned int msi_addr = 0;
+ int ret = 0, index = 0, found = 0;
+ unsigned int msi_addr = 0, cpu_base;
+ struct resource cfg_res;
+ const char *name = NULL;
struct device_node *np;
struct device *dev = &pdev->dev;
struct imx_pcie_ep_priv *priv;
priv->hw_base = pci_iomap(pdev, 0, 0);
if (!priv->hw_base) {
ret = -ENODEV;
- goto out;
+ goto err_pci_disable;
}
pr_info("pci_resource_len = 0x%08llx\n",
ret = pci_enable_msi(priv->pci_dev);
if (ret < 0) {
dev_err(dev, "can't enable msi\n");
- return ret;
+ goto err_pci_unmap_mmio;
}
np = of_find_compatible_node(NULL, NULL, "snps,dw-pcie");
+ if (of_property_read_u32(np, "cpu-base-addr", &cpu_base))
+ cpu_base = 0;
+
+ while (!of_property_read_string_index(np, "reg-names", index, &name)) {
+ if (strcmp("config", name)) {
+ index++;
+ continue;
+ }
+
+ /* We have a match and @index is where it's at */
+ found = 1;
+ break;
+ }
+
+ if (!found) {
+ dev_err(dev, "can't find config reg space.\n");
+ ret = -EINVAL;
+ goto err_pci_disable_msi;
+ }
- /*
- * Force to use the hardcode MSI address to do the MSI demo
- */
- if (of_device_is_compatible(np, "fsl,imx7d-pcie"))
- msi_addr = 0x4FFC0000;
- else if (of_device_is_compatible(np, "fsl,imx6sx-pcie"))
- msi_addr = 0x08FF8000;
- else
- msi_addr = 0x01FF8000;
-
- pr_info("pci_msi_addr = 0x%08x\n", msi_addr);
+ ret = of_address_to_resource(np, index, &cfg_res);
+ if (ret) {
+ dev_err(dev, "can't get cfg_res.\n");
+ ret = -EINVAL;
+ goto err_pci_disable_msi;
+ } else {
+ msi_addr = cfg_res.start + resource_size(&cfg_res);
+ }
+
+ pr_info("pci_msi_addr = 0x%08x, cpu_base 0x%08x\n", msi_addr, cpu_base);
pci_bus_write_config_dword(pdev->bus, 0, 0x54, msi_addr);
+ if (cpu_base) {
+ msi_addr = msi_addr & 0xFFFFFFF;
+ msi_addr |= (cpu_base & 0xF0000000);
+ }
pci_bus_write_config_dword(pdev->bus->parent, 0, 0x820, msi_addr);
/* configure rc's msi cap */
return 0;
+err_pci_disable_msi:
+ pci_disable_msi(pdev);
+err_pci_unmap_mmio:
+ pci_iounmap(pdev, priv->hw_base);
+err_pci_disable:
+ pci_disable_device(pdev);
out:
+ kfree(priv);
return ret;
}
u32 ext_osc;
u32 ctrl_id;
u32 cpu_base;
+ u32 hard_wired;
int clkreq_gpio;
int dis_gpio;
int power_on_gpio;
#define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN1 0x1
#define PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN2 0x2
#define PCIE_RC_LCR_MAX_LINK_SPEEDS_MASK 0xf
-
#define PCIE_RC_LCSR 0x80
/* PCIe Port Logic registers (memory-mapped) */
#define PCIE_PL_PFLR (PL_OFFSET + 0x08)
#define PCIE_PL_PFLR_LINK_STATE_MASK (0x3f << 16)
#define PCIE_PL_PFLR_FORCE_LINK (1 << 15)
+#define PCIE_PORT_LINK_CONTROL 0x710
+#define PORT_LINK_MODE_MASK (0x3f << 16)
+#define PORT_LINK_MODE_1_LANES (0x1 << 16)
+#define PORT_LINK_MODE_2_LANES (0x3 << 16)
+
#define PCIE_PHY_DEBUG_R0 (PL_OFFSET + 0x28)
#define PCIE_PHY_DEBUG_R1 (PL_OFFSET + 0x2c)
#define PCIE_PHY_DEBUG_R1_XMLH_LINK_IN_TRAINING (1 << 29)
#define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80C
#define PORT_LOGIC_SPEED_CHANGE (0x1 << 17)
+#define PORT_LOGIC_LINK_WIDTH_MASK (0x1f << 8)
+#define PORT_LOGIC_LINK_WIDTH_1_LANES (0x1 << 8)
+#define PORT_LOGIC_LINK_WIDTH_2_LANES (0x2 << 8)
#define PCIE_MISC_CTRL (PL_OFFSET + 0x1BC)
#define PCIE_MISC_DBI_RO_WR_EN BIT(0)
+#define PCIE_ATU_VIEWPORT 0x900
+
/* PHY registers (not memory-mapped) */
#define PCIE_PHY_RX_ASIC_OUT 0x100D
#define PCIE_PHY_RX_ASIC_OUT_VALID (1 << 0)
if (ret < 0)
return ret;
- dw_pcie_setup_rc(pp);
- ret = imx6_pcie_establish_link(imx6_pcie);
- if (ret < 0)
- return ret;
+ if (!IS_ENABLED(CONFIG_EP_MODE_IN_EP_RC_SYS)) {
+ dw_pcie_setup_rc(pp);
+ ret = imx6_pcie_establish_link(imx6_pcie);
+ if (ret < 0)
+ return ret;
- if (IS_ENABLED(CONFIG_PCI_MSI))
- dw_pcie_msi_init(pp);
+ if (IS_ENABLED(CONFIG_PCI_MSI))
+ dw_pcie_msi_init(pp);
+ }
return 0;
}
struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev);
struct pcie_port *pp = &imx6_pcie->pp;
- if (imx6_pcie->variant == IMX7D && ddr_test_region == 0)
- ddr_test_region = 0xb0000000;
- else if (imx6_pcie->variant == IMX6SX && ddr_test_region == 0)
+ switch (imx6_pcie->variant) {
+ case IMX8QM:
+ case IMX8QXP:
+ case IMX8MQ:
+ /*
+ * RPMSG reserved 4Mbytes, but only used up to 2Mbytes.
+ * The left 2Mbytes can be used here.
+ */
+ ddr_test_region = 0xb8200000;
+ break;
+ case IMX6SX:
+ case IMX7D:
ddr_test_region = 0xb0000000;
- else if (ddr_test_region == 0)
+ break;
+
+ case IMX6Q:
+ case IMX6QP:
ddr_test_region = 0x40000000;
+ break;
+ }
- /*
- * region2 outbound used to access rc/ep mem
- * in imx6 pcie ep/rc validation system
- */
- writel(2, pp->dbi_base + 0x900);
- writel((u32)pp->mem_base, pp->dbi_base + 0x90c);
- writel(0, pp->dbi_base + 0x910);
- writel((u32)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);
+ dw_pcie_prog_outbound_atu(pp, 2, 0, pp->mem_base,
+ ddr_test_region, test_region_size);
}
static ssize_t imx_pcie_memw_info(struct device *dev,
static void imx6_pcie_setup_ep(struct pcie_port *pp)
{
+ int ret;
+ u32 val;
+ struct device_node *np = pp->dev->of_node;
+
+ ret = of_property_read_u32(np, "num-lanes", &pp->lanes);
+ if (ret)
+ pp->lanes = 0;
+
+ /* set the number of lanes */
+ val = dw_pcie_readl_rc(pp, PCIE_PORT_LINK_CONTROL);
+ val &= ~PORT_LINK_MODE_MASK;
+ switch (pp->lanes) {
+ case 1:
+ val |= PORT_LINK_MODE_1_LANES;
+ break;
+ case 2:
+ val |= PORT_LINK_MODE_2_LANES;
+ break;
+ default:
+ dev_err(pp->dev, "num-lanes %u: invalid value\n", pp->lanes);
+ return;
+ }
+ dw_pcie_writel_rc(pp, PCIE_PORT_LINK_CONTROL, val);
+
+ /* set link width speed control register */
+ val = dw_pcie_readl_rc(pp, PCIE_LINK_WIDTH_SPEED_CONTROL);
+ val &= ~PORT_LOGIC_LINK_WIDTH_MASK;
+ switch (pp->lanes) {
+ case 1:
+ val |= PORT_LOGIC_LINK_WIDTH_1_LANES;
+ break;
+ case 2:
+ val |= PORT_LOGIC_LINK_WIDTH_2_LANES;
+ break;
+ }
+ dw_pcie_writel_rc(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
+
+ /* get iATU unroll support */
+ val = dw_pcie_readl_rc(pp, PCIE_ATU_VIEWPORT);
+ if (val == 0xffffffff)
+ pp->iatu_unroll_enabled = 1;
+ dev_info(pp->dev, "iATU unroll: %s\n",
+ pp->iatu_unroll_enabled ? "enabled" : "disabled");
+
/* CMD reg:I/O space, MEM space, and Bus Master Enable */
writel(readl(pp->dbi_base + PCI_COMMAND)
| PCI_COMMAND_IO
* bar0 and bar1 of ep
*/
writel(0xdeadbeaf, pp->dbi_base + PCI_VENDOR_ID);
- writel(readl(pp->dbi_base + PCI_CLASS_REVISION)
+ writel((readl(pp->dbi_base + PCI_CLASS_REVISION) & 0xFFFF)
| (PCI_CLASS_MEMORY_RAM << 16),
pp->dbi_base + PCI_CLASS_REVISION);
writel(0xdeadbeaf, pp->dbi_base
if (of_property_read_u32(node, "cpu-base-addr", &imx6_pcie->cpu_base))
imx6_pcie->cpu_base = 0;
+ if (of_property_read_u32(node, "hard-wired", &imx6_pcie->hard_wired))
+ imx6_pcie->hard_wired = 0;
np = of_find_compatible_node(NULL, NULL, "fsl,imx-pcie-phy");
if (np != NULL) {
platform_set_drvdata(pdev, imx6_pcie);
- if (IS_ENABLED(CONFIG_EP_MODE_IN_EP_RC_SYS)) {
- int i;
+ if (IS_ENABLED(CONFIG_EP_MODE_IN_EP_RC_SYS)
+ && (imx6_pcie->hard_wired == 0)) {
+ int i = 0;
void *test_reg1, *test_reg2;
void __iomem *pcie_arb_base_addr;
struct timeval tv1s, tv1e, tv2s, tv2e;
struct device_node *np = pp->dev->of_node;
LIST_HEAD(res);
struct resource_entry *win, *tmp;
+ unsigned long timeout = jiffies + msecs_to_jiffies(300000);
/* add attributes for device */
ret = sysfs_create_group(&pdev->dev.kobj, &imx_pcie_attrgroup);
pp->mem_base = pp->mem->start;
pp->ops = &imx6_pcie_host_ops;
+ dev_info(dev, " try to initialize pcie ep.\n");
+ ret = imx6_pcie_host_init(pp);
+ if (ret) {
+ dev_info(dev, " fail to initialize pcie ep.\n");
+ return ret;
+ }
- /* enable disp_mix power domain */
- if ((imx6_pcie->variant == IMX7D)
- || (imx6_pcie->variant == IMX8MQ)
- || (imx6_pcie->variant == IMX8QM)
- || (imx6_pcie->variant == IMX8QXP))
- pm_runtime_get_sync(pp->dev);
-
- imx6_pcie_assert_core_reset(imx6_pcie);
- imx6_pcie_init_phy(imx6_pcie);
- imx6_pcie_deassert_core_reset(imx6_pcie);
+ imx6_pcie_setup_ep(pp);
+ platform_set_drvdata(pdev, imx6_pcie);
+ imx_pcie_regions_setup(&pdev->dev);
/*
* iMX6SX PCIe has the stand-alone power domain.
/* link is indicated by the bit4 of DB_R1 register */
do {
usleep_range(10, 20);
+ if (time_after(jiffies, timeout)) {
+ dev_info(&pdev->dev, "PCIe EP: link down.\n");
+ return 0;
+ }
} 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);
return -EINVAL;
ret = imx6_add_pcie_port(imx6_pcie, pdev);
- if (ret < 0)
+ if (ret < 0) {
+ if (IS_ENABLED(CONFIG_PCI_IMX6_COMPLIANCE_TEST)) {
+ /* The PCIE clocks wouldn't be turned off */
+ dev_info(dev, "To do the compliance tests.\n");
+ ret = 0;
+ } else {
+ dev_err(dev, "unable to add pcie port.\n");
+ }
return ret;
-
- if (IS_ENABLED(CONFIG_RC_MODE_IN_EP_RC_SYS))
+ }
+ if (IS_ENABLED(CONFIG_RC_MODE_IN_EP_RC_SYS)
+ && (imx6_pcie->hard_wired == 0))
imx_pcie_regions_setup(&pdev->dev);
}
return 0;