From b2d7a9cd3ff8ec561348267c2ef7d47b2b91e801 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Mon, 28 Mar 2016 18:45:36 -0300 Subject: [PATCH 01/22] Revert "PCI: imx6: Add support for active-low reset GPIO" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 5c5fb40de8f1 ("PCI: imx6: Add support for active-low reset GPIO") cause regressions on some boards like MX6 Gateworks Ventana, for example. The reason for the breakage is that this commit sets the GPIO polarity in the wrong logic level. Also, the commit log is wrong because active-low reset GPIO is what the driver used to support since the beginning. So keep the old behavior that ignores the GPIO polarity specified in the device tree and treat the PCI reset GPIO as active-low. Reported-by: Krzysztof Hałasa Tested-by: Tim Harvey # Gateworks Ventana Signed-off-by: Fabio Estevam Signed-off-by: Bjorn Helgaas Acked-by: Tim Harvey Acked-by: Lucas Stach CC: stable@vger.kernel.org # v4.5+ --- drivers/pci/host/pci-imx6.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index eb5a2755a164..2f817fa4c661 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -32,7 +32,7 @@ #define to_imx6_pcie(x) container_of(x, struct imx6_pcie, pp) struct imx6_pcie { - struct gpio_desc *reset_gpio; + int reset_gpio; struct clk *pcie_bus; struct clk *pcie_phy; struct clk *pcie; @@ -309,10 +309,10 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) usleep_range(200, 500); /* Some boards don't have PCIe reset GPIO. */ - if (imx6_pcie->reset_gpio) { - gpiod_set_value_cansleep(imx6_pcie->reset_gpio, 0); + if (gpio_is_valid(imx6_pcie->reset_gpio)) { + gpio_set_value_cansleep(imx6_pcie->reset_gpio, 0); msleep(100); - gpiod_set_value_cansleep(imx6_pcie->reset_gpio, 1); + gpio_set_value_cansleep(imx6_pcie->reset_gpio, 1); } return 0; @@ -523,6 +523,7 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) { struct imx6_pcie *imx6_pcie; struct pcie_port *pp; + struct device_node *np = pdev->dev.of_node; struct resource *dbi_base; struct device_node *node = pdev->dev.of_node; int ret; @@ -544,8 +545,15 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) return PTR_ERR(pp->dbi_base); /* Fetch GPIOs */ - imx6_pcie->reset_gpio = devm_gpiod_get_optional(&pdev->dev, "reset", - GPIOD_OUT_LOW); + imx6_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); + if (gpio_is_valid(imx6_pcie->reset_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, imx6_pcie->reset_gpio, + GPIOF_OUT_INIT_LOW, "PCIe reset"); + if (ret) { + dev_err(&pdev->dev, "unable to get reset gpio\n"); + return ret; + } + } /* Fetch clocks */ imx6_pcie->pcie_phy = devm_clk_get(&pdev->dev, "pcie_phy"); From 7e57fd1444bf8f4ba9179f826ed6817c56b801d4 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Wed, 16 Mar 2016 19:40:33 +0800 Subject: [PATCH 02/22] PCI: designware: Move Root Complex setup code to dw_pcie_setup_rc() dw_pcie_host_init() looks up host bridge resources, ioremaps them, creates IRQ domains, and enumerates devices below the bridge. dw_pcie_setup_rc() programs the Root Complex registers. The Root Complex may lose power during suspend-to-RAM, and when we resume, we want to redo the latter but not the former. Move some Root Complex programming from dw_pcie_host_init() to dw_pcie_setup_rc() where it belongs. DesignWare-based drivers can call dw_pcie_setup_rc() in their resume paths. [Niklas Cassel : This change moves outbound ATU programming, which uses pp->mem_base, to dw_pcie_setup_rc(). Apply the dra7xx pp->mem_base update before calling dw_pcie_setup_rc().] [bhelgaas: changelog, fold in dra7xx fix from Niklas] Signed-off-by: Jisheng Zhang Signed-off-by: Bjorn Helgaas Acked-by: Pratyush Anand --- drivers/pci/host/pci-dra7xx.c | 4 +-- drivers/pci/host/pcie-designware.c | 39 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/drivers/pci/host/pci-dra7xx.c b/drivers/pci/host/pci-dra7xx.c index 2ca3a1f30ebf..f441130407e7 100644 --- a/drivers/pci/host/pci-dra7xx.c +++ b/drivers/pci/host/pci-dra7xx.c @@ -142,13 +142,13 @@ static void dra7xx_pcie_enable_interrupts(struct pcie_port *pp) static void dra7xx_pcie_host_init(struct pcie_port *pp) { - dw_pcie_setup_rc(pp); - pp->io_base &= DRA7XX_CPU_TO_BUS_ADDR; pp->mem_base &= DRA7XX_CPU_TO_BUS_ADDR; pp->cfg0_base &= DRA7XX_CPU_TO_BUS_ADDR; pp->cfg1_base &= DRA7XX_CPU_TO_BUS_ADDR; + dw_pcie_setup_rc(pp); + dra7xx_pcie_establish_link(pp); if (IS_ENABLED(CONFIG_PCI_MSI)) dw_pcie_msi_init(pp); diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index a4cccd356304..261e4a1106df 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -434,7 +434,6 @@ int dw_pcie_host_init(struct pcie_port *pp) struct platform_device *pdev = to_platform_device(pp->dev); struct pci_bus *bus, *child; struct resource *cfg_res; - u32 val; int i, ret; LIST_HEAD(res); struct resource_entry *win; @@ -544,25 +543,6 @@ int dw_pcie_host_init(struct pcie_port *pp) if (pp->ops->host_init) pp->ops->host_init(pp); - /* - * If the platform provides ->rd_other_conf, it means the platform - * uses its own address translation component rather than ATU, so - * we should not program the ATU here. - */ - if (!pp->ops->rd_other_conf) - dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, - PCIE_ATU_TYPE_MEM, pp->mem_base, - pp->mem_bus_addr, pp->mem_size); - - dw_pcie_wr_own_conf(pp, PCI_BASE_ADDRESS_0, 4, 0); - - /* program correct class for RC */ - dw_pcie_wr_own_conf(pp, PCI_CLASS_DEVICE, 2, PCI_CLASS_BRIDGE_PCI); - - dw_pcie_rd_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, &val); - val |= PORT_LOGIC_SPEED_CHANGE; - dw_pcie_wr_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, val); - pp->root_bus_nr = pp->busn->start; if (IS_ENABLED(CONFIG_PCI_MSI)) { bus = pci_scan_root_bus_msi(pp->dev, pp->root_bus_nr, @@ -800,6 +780,25 @@ void dw_pcie_setup_rc(struct pcie_port *pp) val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_SERR; dw_pcie_writel_rc(pp, val, PCI_COMMAND); + + /* + * If the platform provides ->rd_other_conf, it means the platform + * uses its own address translation component rather than ATU, so + * we should not program the ATU here. + */ + if (!pp->ops->rd_other_conf) + dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, + PCIE_ATU_TYPE_MEM, pp->mem_base, + pp->mem_bus_addr, pp->mem_size); + + dw_pcie_wr_own_conf(pp, PCI_BASE_ADDRESS_0, 4, 0); + + /* program correct class for RC */ + dw_pcie_wr_own_conf(pp, PCI_CLASS_DEVICE, 2, PCI_CLASS_BRIDGE_PCI); + + dw_pcie_rd_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, &val); + val |= PORT_LOGIC_SPEED_CHANGE; + dw_pcie_wr_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, val); } MODULE_AUTHOR("Jingoo Han "); From 6e9a426d7964703098bde5752ae5d062a596ca99 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Wed, 16 Mar 2016 17:59:40 +0800 Subject: [PATCH 03/22] PCI: mvebu: Constify mvebu_pcie_pm_ops structure The mvebu_pcie_pm_ops structure is never modified, so declare it as const. Signed-off-by: Jisheng Zhang Signed-off-by: Bjorn Helgaas Reviewed-by: Thomas Petazzoni --- drivers/pci/host/pci-mvebu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c index 53b79c5f0559..6c43cb88da12 100644 --- a/drivers/pci/host/pci-mvebu.c +++ b/drivers/pci/host/pci-mvebu.c @@ -1298,7 +1298,7 @@ static const struct of_device_id mvebu_pcie_of_match_table[] = { }; MODULE_DEVICE_TABLE(of, mvebu_pcie_of_match_table); -static struct dev_pm_ops mvebu_pcie_pm_ops = { +static const struct dev_pm_ops mvebu_pcie_pm_ops = { .suspend_noirq = mvebu_pcie_suspend, .resume_noirq = mvebu_pcie_resume, }; From dfc6535a8401b8fd15b18e2a5b8256600dd7e6b6 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Wed, 16 Mar 2016 17:59:41 +0800 Subject: [PATCH 04/22] PCI: mvebu: Use SET_NOIRQ_SYSTEM_SLEEP_PM_OPS for mvebu_pcie_pm_ops Use the SET_NOIRQ_SYSTEM_SLEEP_PM_OPS helper macro for mvebu_pcie_pm_ops. The macro also sets up freeze_noirq, thaw_noirq and poweroff_noirq, restore_noirq accordingly. Signed-off-by: Jisheng Zhang Signed-off-by: Bjorn Helgaas Reviewed-by: Thomas Petazzoni --- drivers/pci/host/pci-mvebu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c index 6c43cb88da12..6b451df6502c 100644 --- a/drivers/pci/host/pci-mvebu.c +++ b/drivers/pci/host/pci-mvebu.c @@ -1003,6 +1003,7 @@ static void mvebu_pcie_msi_enable(struct mvebu_pcie *pcie) pcie->msi->dev = &pcie->pdev->dev; } +#ifdef CONFIG_PM_SLEEP static int mvebu_pcie_suspend(struct device *dev) { struct mvebu_pcie *pcie; @@ -1031,6 +1032,7 @@ static int mvebu_pcie_resume(struct device *dev) return 0; } +#endif static void mvebu_pcie_port_clk_put(void *data) { @@ -1299,8 +1301,7 @@ static const struct of_device_id mvebu_pcie_of_match_table[] = { MODULE_DEVICE_TABLE(of, mvebu_pcie_of_match_table); static const struct dev_pm_ops mvebu_pcie_pm_ops = { - .suspend_noirq = mvebu_pcie_suspend, - .resume_noirq = mvebu_pcie_resume, + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(mvebu_pcie_suspend, mvebu_pcie_resume) }; static struct platform_driver mvebu_pcie_driver = { From 52966bd1c2a09fdd3149f00568cc18f45cc09785 Mon Sep 17 00:00:00 2001 From: Jon Derrick Date: Fri, 8 Apr 2016 14:44:24 -0500 Subject: [PATCH 05/22] PCI/ACPI: Allow all PCIe services on non-ACPI host bridges Host bridges we discover via ACPI, i.e., PNP0A03 and PNP0A08 devices, may have an _OSC method by which the OS can ask the platform for control of PCIe features like native hotplug, power management events, AER, etc. Previously, if we found a bridge without an ACPI device, we assumed we did not have permission to use any of these PCIe features. That seems unreasonably restrictive. If we find no ACPI device, assume we can take control of all PCIe features. The Intel Volume Management Device (VMD) is one such bridge with no ACPI device. Prior to this change, users had to boot with "pcie_ports=native" to get hotplug and other services to work below the VMD Root Port. [bhelgaas: changelog] Suggested-by: Bjorn Helgaas Signed-off-by: Jon Derrick Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/portdrv_acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pcie/portdrv_acpi.c b/drivers/pci/pcie/portdrv_acpi.c index b4d2894ee3fc..f097a73cb291 100644 --- a/drivers/pci/pcie/portdrv_acpi.c +++ b/drivers/pci/pcie/portdrv_acpi.c @@ -43,11 +43,11 @@ int pcie_port_acpi_setup(struct pci_dev *port, int *srv_mask) handle = acpi_find_root_bridge_handle(port); if (!handle) - return -EINVAL; + return 0; root = acpi_pci_find_root(handle); if (!root) - return -ENODEV; + return 0; flags = root->osc_control_set; From 88a97da1eab7587036d8bc937d6bc874b8210df1 Mon Sep 17 00:00:00 2001 From: Jon Derrick Date: Fri, 8 Apr 2016 11:35:51 -0600 Subject: [PATCH 06/22] PCI: Remove return values from pcie_port_platform_notify() and relatives Now that pcie_port_acpi_setup() always returns 0, make it and its callers void functions and stop checking the return values. [bhelgaas: changelog] Signed-off-by: Jon Derrick Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/portdrv.h | 11 ++++------- drivers/pci/pcie/portdrv_acpi.c | 10 ++++------ drivers/pci/pcie/portdrv_core.c | 8 ++------ 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index d525548404d6..463b60975194 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -67,17 +67,14 @@ static inline void pcie_pme_interrupt_enable(struct pci_dev *dev, bool en) {} #endif /* !CONFIG_PCIE_PME */ #ifdef CONFIG_ACPI -int pcie_port_acpi_setup(struct pci_dev *port, int *mask); +void pcie_port_acpi_setup(struct pci_dev *port, int *mask); -static inline int pcie_port_platform_notify(struct pci_dev *port, int *mask) +static inline void pcie_port_platform_notify(struct pci_dev *port, int *mask) { - return pcie_port_acpi_setup(port, mask); + pcie_port_acpi_setup(port, mask); } #else /* !CONFIG_ACPI */ -static inline int pcie_port_platform_notify(struct pci_dev *port, int *mask) -{ - return 0; -} +static inline void pcie_port_platform_notify(struct pci_dev *port, int *mask){} #endif /* !CONFIG_ACPI */ #endif /* _PORTDRV_H_ */ diff --git a/drivers/pci/pcie/portdrv_acpi.c b/drivers/pci/pcie/portdrv_acpi.c index f097a73cb291..9f4ed7172fda 100644 --- a/drivers/pci/pcie/portdrv_acpi.c +++ b/drivers/pci/pcie/portdrv_acpi.c @@ -32,22 +32,22 @@ * NOTE: It turns out that we cannot do that for individual port services * separately, because that would make some systems work incorrectly. */ -int pcie_port_acpi_setup(struct pci_dev *port, int *srv_mask) +void pcie_port_acpi_setup(struct pci_dev *port, int *srv_mask) { struct acpi_pci_root *root; acpi_handle handle; u32 flags; if (acpi_pci_disabled) - return 0; + return; handle = acpi_find_root_bridge_handle(port); if (!handle) - return 0; + return; root = acpi_pci_find_root(handle); if (!root) - return 0; + return; flags = root->osc_control_set; @@ -58,6 +58,4 @@ int pcie_port_acpi_setup(struct pci_dev *port, int *srv_mask) *srv_mask |= PCIE_PORT_SERVICE_PME; if (flags & OSC_PCI_EXPRESS_AER_CONTROL) *srv_mask |= PCIE_PORT_SERVICE_AER; - - return 0; } diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 88122dc2e1b1..de7a85bc8016 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -256,7 +256,6 @@ static int get_port_device_capability(struct pci_dev *dev) int services = 0; u32 reg32; int cap_mask = 0; - int err; if (pcie_ports_disabled) return 0; @@ -266,11 +265,8 @@ static int get_port_device_capability(struct pci_dev *dev) if (pci_aer_available()) cap_mask |= PCIE_PORT_SERVICE_AER; - if (pcie_ports_auto) { - err = pcie_port_platform_notify(dev, &cap_mask); - if (err) - return 0; - } + if (pcie_ports_auto) + pcie_port_platform_notify(dev, &cap_mask); /* Hot-Plug Capable */ if ((cap_mask & PCIE_PORT_SERVICE_HP) && From 025dd3daeda77f61a280da87ae7015a6808dfe1f Mon Sep 17 00:00:00 2001 From: Murali Karicheri Date: Mon, 11 Apr 2016 10:50:30 -0400 Subject: [PATCH 07/22] PCI: keystone: Add error IRQ handler Keystone PCI hardware generates error interrupts at RC using a platform IRQ instead of a standard MSI or legacy IRQ. Add a simple error handler that logs the fatal interrupt status to the console. [bhelgaas: s/node/dev->of_node/, tidy comments, return irqreturn_t directly] Signed-off-by: Murali Karicheri Signed-off-by: Bjorn Helgaas Acked-by: Rob Herring CC: Pawel Moll CC: Mark Rutland CC: Ian Campbell CC: Kumar Gala --- .../devicetree/bindings/pci/pci-keystone.txt | 1 + drivers/pci/host/pci-keystone-dw.c | 38 +++++++++++++++++++ drivers/pci/host/pci-keystone.c | 29 ++++++++++++++ drivers/pci/host/pci-keystone.h | 6 +++ 4 files changed, 74 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/pci-keystone.txt b/Documentation/devicetree/bindings/pci/pci-keystone.txt index 54eae2938174..d08a4d51108f 100644 --- a/Documentation/devicetree/bindings/pci/pci-keystone.txt +++ b/Documentation/devicetree/bindings/pci/pci-keystone.txt @@ -56,6 +56,7 @@ Optional properties:- phy-names: name of the Generic Keystine SerDes phy for PCI - If boot loader already does PCI link establishment, then phys and phy-names shouldn't be present. + interrupts: platform interrupt for error interrupts. Designware DT Properties not applicable for Keystone PCI diff --git a/drivers/pci/host/pci-keystone-dw.c b/drivers/pci/host/pci-keystone-dw.c index 6153853ca9c3..41515092eb0d 100644 --- a/drivers/pci/host/pci-keystone-dw.c +++ b/drivers/pci/host/pci-keystone-dw.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -53,6 +54,21 @@ #define IRQ_STATUS 0x184 #define MSI_IRQ_OFFSET 4 +/* Error IRQ bits */ +#define ERR_AER BIT(5) /* ECRC error */ +#define ERR_AXI BIT(4) /* AXI tag lookup fatal error */ +#define ERR_CORR BIT(3) /* Correctable error */ +#define ERR_NONFATAL BIT(2) /* Non-fatal error */ +#define ERR_FATAL BIT(1) /* Fatal error */ +#define ERR_SYS BIT(0) /* System (fatal, non-fatal, or correctable) */ +#define ERR_IRQ_ALL (ERR_AER | ERR_AXI | ERR_CORR | \ + ERR_NONFATAL | ERR_FATAL | ERR_SYS) +#define ERR_FATAL_IRQ (ERR_FATAL | ERR_AXI) +#define ERR_IRQ_STATUS_RAW 0x1c0 +#define ERR_IRQ_STATUS 0x1c4 +#define ERR_IRQ_ENABLE_SET 0x1c8 +#define ERR_IRQ_ENABLE_CLR 0x1cc + /* Config space registers */ #define DEBUG0 0x728 @@ -243,6 +259,28 @@ void ks_dw_pcie_handle_legacy_irq(struct keystone_pcie *ks_pcie, int offset) writel(offset, ks_pcie->va_app_base + IRQ_EOI); } +void ks_dw_pcie_enable_error_irq(void __iomem *reg_base) +{ + writel(ERR_IRQ_ALL, reg_base + ERR_IRQ_ENABLE_SET); +} + +irqreturn_t ks_dw_pcie_handle_error_irq(struct device *dev, + void __iomem *reg_base) +{ + u32 status; + + status = readl(reg_base + ERR_IRQ_STATUS_RAW) & ERR_IRQ_ALL; + if (!status) + return IRQ_NONE; + + if (status & ERR_FATAL_IRQ) + dev_err(dev, "fatal error (status %#010x)\n", status); + + /* Ack the IRQ; status bits are RW1C */ + writel(status, reg_base + ERR_IRQ_STATUS); + return IRQ_HANDLED; +} + static void ks_dw_pcie_ack_legacy_irq(struct irq_data *d) { } diff --git a/drivers/pci/host/pci-keystone.c b/drivers/pci/host/pci-keystone.c index b71f55bb0315..58120009c819 100644 --- a/drivers/pci/host/pci-keystone.c +++ b/drivers/pci/host/pci-keystone.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -226,6 +227,9 @@ static void ks_pcie_setup_interrupts(struct keystone_pcie *ks_pcie) ks_pcie); } } + + if (ks_pcie->error_irq > 0) + ks_dw_pcie_enable_error_irq(ks_pcie->va_app_base); } /* @@ -289,6 +293,14 @@ static struct pcie_host_ops keystone_pcie_host_ops = { .scan_bus = ks_dw_pcie_v3_65_scan_bus, }; +static irqreturn_t pcie_err_irq_handler(int irq, void *priv) +{ + struct keystone_pcie *ks_pcie = priv; + + return ks_dw_pcie_handle_error_irq(ks_pcie->pp.dev, + ks_pcie->va_app_base); +} + static int __init ks_add_pcie_port(struct keystone_pcie *ks_pcie, struct platform_device *pdev) { @@ -309,6 +321,22 @@ static int __init ks_add_pcie_port(struct keystone_pcie *ks_pcie, return ret; } + /* + * Index 0 is the platform interrupt for error interrupt + * from RC. This is optional. + */ + ks_pcie->error_irq = irq_of_parse_and_map(ks_pcie->np, 0); + if (ks_pcie->error_irq <= 0) + dev_info(&pdev->dev, "no error IRQ defined\n"); + else { + if (request_irq(ks_pcie->error_irq, pcie_err_irq_handler, + IRQF_SHARED, "pcie-error-irq", ks_pcie) < 0) { + dev_err(&pdev->dev, "failed to request error IRQ %d\n", + ks_pcie->error_irq); + return ret; + } + } + pp->root_bus_nr = -1; pp->ops = &keystone_pcie_host_ops; ret = ks_dw_pcie_host_init(ks_pcie, ks_pcie->msi_intc_np); @@ -376,6 +404,7 @@ static int __init ks_pcie_probe(struct platform_device *pdev) devm_release_mem_region(dev, res->start, resource_size(res)); pp->dev = dev; + ks_pcie->np = dev->of_node; platform_set_drvdata(pdev, ks_pcie); ks_pcie->clk = devm_clk_get(dev, "pcie"); if (IS_ERR(ks_pcie->clk)) { diff --git a/drivers/pci/host/pci-keystone.h b/drivers/pci/host/pci-keystone.h index f0944e8c4b02..a5b0cb2ba4d7 100644 --- a/drivers/pci/host/pci-keystone.h +++ b/drivers/pci/host/pci-keystone.h @@ -29,6 +29,9 @@ struct keystone_pcie { int msi_host_irqs[MAX_MSI_HOST_IRQS]; struct device_node *msi_intc_np; struct irq_domain *legacy_irq_domain; + struct device_node *np; + + int error_irq; /* Application register space */ void __iomem *va_app_base; @@ -42,6 +45,9 @@ phys_addr_t ks_dw_pcie_get_msi_addr(struct pcie_port *pp); /* Keystone specific PCI controller APIs */ void ks_dw_pcie_enable_legacy_irqs(struct keystone_pcie *ks_pcie); void ks_dw_pcie_handle_legacy_irq(struct keystone_pcie *ks_pcie, int offset); +void ks_dw_pcie_enable_error_irq(void __iomem *reg_base); +irqreturn_t ks_dw_pcie_handle_error_irq(struct device *dev, + void __iomem *reg_base); int ks_dw_pcie_host_init(struct keystone_pcie *ks_pcie, struct device_node *msi_intc_np); int ks_dw_pcie_wr_other_conf(struct pcie_port *pp, struct pci_bus *bus, From 4d1821e729b5d2060ef8c9825af1dacc2182da38 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 14 Mar 2016 00:30:55 +0100 Subject: [PATCH 08/22] PCI: imx6: Factor out ref clock enable Factor out ref clock enable to make it cleaner to add imx6sx support. No functional change intended. Signed-off-by: Bjorn Helgaas Tested-by: Christoph Fritz --- drivers/pci/host/pci-imx6.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 2f817fa4c661..4357f4d90d0c 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -269,6 +269,23 @@ static int imx6_pcie_assert_core_reset(struct pcie_port *pp) return 0; } +static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) +{ + /* power up core phy and enable ref clock */ + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD, 0 << 18); + /* + * the async reset input need ref clock to sync internally, + * when the ref clock comes after reset, internal synced + * reset time is too short, cannot meet the requirement. + * add one ~10us delay here. + */ + udelay(10); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_REF_CLK_EN, 1 << 16); + return 0; +} + static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) { struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); @@ -292,18 +309,11 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) goto err_pcie; } - /* power up core phy and enable ref clock */ - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_TEST_PD, 0 << 18); - /* - * the async reset input need ref clock to sync internally, - * when the ref clock comes after reset, internal synced - * reset time is too short, cannot meet the requirement. - * add one ~10us delay here. - */ - udelay(10); - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_REF_CLK_EN, 1 << 16); + ret = imx6_pcie_enable_ref_clk(imx6_pcie); + if (ret) { + dev_err(pp->dev, "unable to enable pcie ref clock\n"); + goto err_ref_clk; + } /* allow the clocks to stabilize */ usleep_range(200, 500); @@ -316,6 +326,8 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) } return 0; +err_ref_clk: + clk_disable_unprepare(imx6_pcie->pcie); err_pcie: clk_disable_unprepare(imx6_pcie->pcie_bus); err_pcie_bus: From e3c06cd063d69df741295a50a691d4a2cad3852a Mon Sep 17 00:00:00 2001 From: Christoph Fritz Date: Tue, 5 Apr 2016 16:53:27 -0500 Subject: [PATCH 09/22] PCI: imx6: Add initial imx6sx support Add initial PCIe support for the imx6 SoC derivate imx6sx. PCI MSI support is untested as the necessary suspend/resume quirk is not included in this patch. This patch is heavily based on patches by Richard Zhu. [bhelgaas: factor out refclk enable, fix adjacent typos in imx6q-pcie.txt] Signed-off-by: Christoph Fritz Acked-by: Richard Zhu Acked-by: Lucas Stach --- .../bindings/pci/fsl,imx6q-pcie.txt | 8 ++- drivers/pci/host/pci-imx6.c | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt index 3be80c68941a..0561e88f3b37 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt @@ -4,8 +4,8 @@ This PCIe host controller is based on the Synopsis Designware PCIe IP and thus inherits all the common properties defined in designware-pcie.txt. Required properties: -- compatible: "fsl,imx6q-pcie" -- reg: base addresse and length of the pcie controller +- compatible: "fsl,imx6q-pcie", "fsl,imx6sx-pcie" +- reg: base address and length of the PCIe controller - interrupts: A list of interrupt outputs of the controller. Must contain an entry for each entry in the interrupt-names property. - interrupt-names: Must include the following entries: @@ -20,6 +20,10 @@ Optional properties: - fsl,tx-swing-full: Gen2 TX SWING FULL value. Default: 127 - fsl,tx-swing-low: TX launch amplitude swing_low value. Default: 127 +Additional required properties for imx6sx-pcie: +- clock names: Must include the following additional entries: + - "pcie_inbound_axi" + Example: pcie@0x01000000 { diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 4357f4d90d0c..7cfe00e34f58 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -35,9 +35,11 @@ struct imx6_pcie { int reset_gpio; struct clk *pcie_bus; struct clk *pcie_phy; + struct clk *pcie_inbound_axi; struct clk *pcie; struct pcie_port pp; struct regmap *iomuxc_gpr; + bool is_imx6sx; void __iomem *mem_base; u32 tx_deemph_gen1; u32 tx_deemph_gen2_3p5db; @@ -236,6 +238,17 @@ static int imx6_pcie_assert_core_reset(struct pcie_port *pp) struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); u32 val, gpr1, gpr12; + if (imx6_pcie->is_imx6sx) { + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6SX_GPR12_PCIE_TEST_POWERDOWN, + IMX6SX_GPR12_PCIE_TEST_POWERDOWN); + /* Force PCIe PHY reset */ + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5, + IMX6SX_GPR5_PCIE_BTNRST_RESET, + IMX6SX_GPR5_PCIE_BTNRST_RESET); + return 0; + } + /* * If the bootloader already enabled the link we need some special * handling to get the core back into a state where it is safe to @@ -271,6 +284,21 @@ static int imx6_pcie_assert_core_reset(struct pcie_port *pp) static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) { + struct pcie_port *pp = &imx6_pcie->pp; + int ret; + + if (imx6_pcie->is_imx6sx) { + ret = clk_prepare_enable(imx6_pcie->pcie_inbound_axi); + if (ret) { + dev_err(pp->dev, "unable to enable pcie_axi clock\n"); + return ret; + } + + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6SX_GPR12_PCIE_TEST_POWERDOWN, 0); + return ret; + } + /* power up core phy and enable ref clock */ regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_TEST_PD, 0 << 18); @@ -324,6 +352,11 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) msleep(100); gpio_set_value_cansleep(imx6_pcie->reset_gpio, 1); } + + if (imx6_pcie->is_imx6sx) + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5, + IMX6SX_GPR5_PCIE_BTNRST_RESET, 0); + return 0; err_ref_clk: @@ -341,6 +374,12 @@ static void imx6_pcie_init_phy(struct pcie_port *pp) { struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); + if (imx6_pcie->is_imx6sx) { + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6SX_GPR12_PCIE_RX_EQ_MASK, + IMX6SX_GPR12_PCIE_RX_EQ_2); + } + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6Q_GPR12_PCIE_CTL_2, 0 << 10); @@ -547,6 +586,9 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) pp = &imx6_pcie->pp; pp->dev = &pdev->dev; + imx6_pcie->is_imx6sx = of_device_is_compatible(pp->dev->of_node, + "fsl,imx6sx-pcie"); + /* Added for PCI abort handling */ hook_fault_code(16 + 6, imx6q_pcie_abort_handler, SIGBUS, 0, "imprecise external abort"); @@ -589,6 +631,16 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) return PTR_ERR(imx6_pcie->pcie); } + if (imx6_pcie->is_imx6sx) { + imx6_pcie->pcie_inbound_axi = devm_clk_get(&pdev->dev, + "pcie_inbound_axi"); + if (IS_ERR(imx6_pcie->pcie_inbound_axi)) { + dev_err(&pdev->dev, + "pcie_incbound_axi clock missing or invalid\n"); + return PTR_ERR(imx6_pcie->pcie_inbound_axi); + } + } + /* Grab GPR config register range */ imx6_pcie->iomuxc_gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); @@ -636,6 +688,7 @@ static void imx6_pcie_shutdown(struct platform_device *pdev) static const struct of_device_id imx6_pcie_of_match[] = { { .compatible = "fsl,imx6q-pcie", }, + { .compatible = "fsl,imx6sx-pcie", }, {}, }; MODULE_DEVICE_TABLE(of, imx6_pcie_of_match); From 4f6926e9fdf94208dc37da0cd17835328fa4dbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0tetiar?= Date: Fri, 1 Apr 2016 14:41:47 +0200 Subject: [PATCH 10/22] ARM: dts: imx6: Fix PCIe reset GPIO polarity on Toradex Apalis Ixora MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding reset-gpio-active-high boolean DT binding property, which we need to make PCIe working on Apalis SoMs and not break old DTBs. While at it, I've fixed comment and GPIO polarity. On Apalis SoMs the GPIO1_IO28 used to PCIe reset is not connected directly to PERST# PCIe signal, but it's ORed with RESETBMCU coming off the PMIC, and thus is inverted, active-high. Signed-off-by: Petr Štetiar Signed-off-by: Bjorn Helgaas --- arch/arm/boot/dts/imx6q-apalis-ixora.dts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm/boot/dts/imx6q-apalis-ixora.dts b/arch/arm/boot/dts/imx6q-apalis-ixora.dts index 2cba82d0d859..4b533cb5c82e 100644 --- a/arch/arm/boot/dts/imx6q-apalis-ixora.dts +++ b/arch/arm/boot/dts/imx6q-apalis-ixora.dts @@ -174,8 +174,9 @@ &ldb { }; &pcie { - /* active-low meaning opposite of regular PERST# active-low polarity */ - reset-gpio = <&gpio1 28 GPIO_ACTIVE_LOW>; + /* active-high meaning opposite of regular PERST# active-low polarity */ + reset-gpio = <&gpio1 28 GPIO_ACTIVE_HIGH>; + reset-gpio-active-high; status = "okay"; }; From 3ea8529acc30467331b7b1f5da6cf668fac091c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0tetiar?= Date: Tue, 19 Apr 2016 19:42:07 -0500 Subject: [PATCH 11/22] PCI: imx6: Add reset-gpio-active-high boolean property to DT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the reset-gpio DT property which controls the PCI bus device reset signal defaults to active-low reset sequence (L=reset state, H=operation state) plus the code in reset function isn't GPIO polarity aware - it doesn't matter if the defined reset-gpio is active-low or active-high, it will always result into active-low reset sequence. I've tried to fix it properly and change the reset-gpio reset sequence to be polarity-aware, but this patch has been accepted and then reverted as it has introduced few backward incompatible issues: 1. Some DTBs, for example, imx6qdl-sabresd, don't define reset-gpio polarity correctly: reset-gpio = <&gpio7 12 0>; which means that it's defined as active-high, but in reality it's active-low; thus it wouldn't work without a DTS fix. 2. The logic in the reset function is inverted: gpio_set_value_cansleep(imx6_pcie->reset_gpio, 0) msleep(100); gpio_set_value_cansleep(imx6_pcie->reset_gpio, 1); so even if some of the i.MX6 boards had reset-gpio polarity defined correctly in their DTSes, they would stop working. As we can't break old DTBs, we can't fix them, so we need to introduce this new DT reset-gpio-active-high boolean property so we can support boards with active-high reset sequence. This active-high reset sequence is for example needed on Apalis SoMs, where GPIO1_IO28, used to PCIe reset is not connected directly to PERST# PCIe signal, but it's ORed with RESETBMCU coming off the PMIC, and thus is inverted, active-high. Tested-by: Tim Harvey # Gateworks Ventana boards (which have active-low PERST#) Signed-off-by: Petr Štetiar Signed-off-by: Bjorn Helgaas Reviewed-by: Lucas Stach Acked-by: Rob Herring --- .../devicetree/bindings/pci/fsl,imx6q-pcie.txt | 6 ++++++ drivers/pci/host/pci-imx6.c | 14 +++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt index 0561e88f3b37..636707df0952 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt @@ -19,6 +19,12 @@ Optional properties: - fsl,tx-deemph-gen2-6db: Gen2 (6db) De-emphasis value. Default: 20 - fsl,tx-swing-full: Gen2 TX SWING FULL value. Default: 127 - fsl,tx-swing-low: TX launch amplitude swing_low value. Default: 127 +- reset-gpio: Should specify the GPIO for controlling the PCI bus device reset + signal. It's not polarity aware and defaults to active-low reset sequence + (L=reset state, H=operation state). +- reset-gpio-active-high: If present then the reset sequence using the GPIO + specified in the "reset-gpio" property is reversed (H=reset state, + L=operation state). Additional required properties for imx6sx-pcie: - clock names: Must include the following additional entries: diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 7cfe00e34f58..0b8793d908de 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -33,6 +33,7 @@ struct imx6_pcie { int reset_gpio; + bool gpio_active_high; struct clk *pcie_bus; struct clk *pcie_phy; struct clk *pcie_inbound_axi; @@ -348,9 +349,11 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) /* Some boards don't have PCIe reset GPIO. */ if (gpio_is_valid(imx6_pcie->reset_gpio)) { - gpio_set_value_cansleep(imx6_pcie->reset_gpio, 0); + gpio_set_value_cansleep(imx6_pcie->reset_gpio, + imx6_pcie->gpio_active_high); msleep(100); - gpio_set_value_cansleep(imx6_pcie->reset_gpio, 1); + gpio_set_value_cansleep(imx6_pcie->reset_gpio, + !imx6_pcie->gpio_active_high); } if (imx6_pcie->is_imx6sx) @@ -600,9 +603,14 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) /* Fetch GPIOs */ imx6_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); + imx6_pcie->gpio_active_high = of_property_read_bool(np, + "reset-gpio-active-high"); if (gpio_is_valid(imx6_pcie->reset_gpio)) { ret = devm_gpio_request_one(&pdev->dev, imx6_pcie->reset_gpio, - GPIOF_OUT_INIT_LOW, "PCIe reset"); + imx6_pcie->gpio_active_high ? + GPIOF_OUT_INIT_HIGH : + GPIOF_OUT_INIT_LOW, + "PCIe reset"); if (ret) { dev_err(&pdev->dev, "unable to get reset gpio\n"); return ret; From a5fcec480f25eb5444c0b71ecdf9b18b09236b95 Mon Sep 17 00:00:00 2001 From: Tim Harvey Date: Tue, 19 Apr 2016 19:52:44 -0500 Subject: [PATCH 12/22] PCI: imx6: Add DT property for link gen, default to Gen1 Freescale has stated [1] that the LVDS clock source of the IMX6 does not pass the PCI Gen2 clock jitter test, therefore unless an external Gen2 compliant external clock source is present and supplied back to the IMX6 PCIe core via LVDS CLK1/CLK2 you can not claim Gen2 compliance. Add a DT property to specify Gen1 vs Gen2 and check this before allowing a Gen2 link. We default to Gen1 if the property is not present because at this time there are no IMX6 boards in mainline that 'input' a clock on LVDS CLK1/CLK2. In order to be Gen2 compliant on IMX6 you need to: - Have a Gen2 compliant external clock generator and route that clock back to either LVDS CLK1 or LVDS CLK2 as an input (see IMX6SX-SabreSD reference design). - Specify this clock in the PCIe node in the DT (i.e., IMX6QDL_CLK_LVDS1_IN or IMX6QDL_CLK_LVDS2_IN instead of IMX6QDL_CLK_LVDS1_GATE which configures it as a CLK output). [1] https://community.freescale.com/message/453209 Signed-off-by: Tim Harvey Signed-off-by: Bjorn Helgaas Reviewed-by: Lucas Stach CC: Fabio Estevam CC: Zhu Richard CC: Akshay Bhat CC: Rob Herring CC: Shawn Guo --- .../bindings/pci/fsl,imx6q-pcie.txt | 4 ++++ drivers/pci/host/pci-imx6.c | 24 +++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt index 636707df0952..d742f917505a 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt @@ -19,6 +19,10 @@ Optional properties: - fsl,tx-deemph-gen2-6db: Gen2 (6db) De-emphasis value. Default: 20 - fsl,tx-swing-full: Gen2 TX SWING FULL value. Default: 127 - fsl,tx-swing-low: TX launch amplitude swing_low value. Default: 127 +- fsl,max-link-speed: Specify PCI gen for link capability. Must be '2' for + gen2, otherwise will default to gen1. Note that the IMX6 LVDS clock outputs + do not meet gen2 jitter requirements and thus for gen2 capability a gen2 + compliant clock generator should be used and configured. - reset-gpio: Should specify the GPIO for controlling the PCI bus device reset signal. It's not polarity aware and defaults to active-low reset sequence (L=reset state, H=operation state). diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 0b8793d908de..837d5e0447ac 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -47,6 +47,7 @@ struct imx6_pcie { u32 tx_deemph_gen2_6db; u32 tx_swing_full; u32 tx_swing_low; + int link_gen; }; /* PCIe Root Complex registers (memory-mapped) */ @@ -471,11 +472,15 @@ static int imx6_pcie_establish_link(struct pcie_port *pp) goto err_reset_phy; } - /* Allow Gen2 mode after the link is up. */ - tmp = readl(pp->dbi_base + PCIE_RC_LCR); - tmp &= ~PCIE_RC_LCR_MAX_LINK_SPEEDS_MASK; - tmp |= PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN2; - writel(tmp, pp->dbi_base + PCIE_RC_LCR); + if (imx6_pcie->link_gen == 2) { + /* Allow Gen2 mode after the link is up. */ + tmp = readl(pp->dbi_base + PCIE_RC_LCR); + tmp &= ~PCIE_RC_LCR_MAX_LINK_SPEEDS_MASK; + tmp |= PCIE_RC_LCR_MAX_LINK_SPEEDS_GEN2; + writel(tmp, pp->dbi_base + PCIE_RC_LCR); + } else { + dev_info(pp->dev, "Link: Gen2 disabled\n"); + } /* * Start Directed Speed Change so the best possible speed both link @@ -499,8 +504,7 @@ static int imx6_pcie_establish_link(struct pcie_port *pp) } tmp = readl(pp->dbi_base + PCIE_RC_LCSR); - dev_dbg(pp->dev, "Link up, Gen=%i\n", (tmp >> 16) & 0xf); - + dev_info(pp->dev, "Link up, Gen%i\n", (tmp >> 16) & 0xf); return 0; err_reset_phy: @@ -678,6 +682,12 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) &imx6_pcie->tx_swing_low)) imx6_pcie->tx_swing_low = 127; + /* Limit link speed */ + ret = of_property_read_u32(pp->dev->of_node, "fsl,max-link-speed", + &imx6_pcie->link_gen); + if (ret) + imx6_pcie->link_gen = 1; + ret = imx6_add_pcie_port(pp, pdev); if (ret < 0) return ret; From 1e9f8dcf892ddb183405cb2414bfc37b40300693 Mon Sep 17 00:00:00 2001 From: Murali Karicheri Date: Mon, 11 Apr 2016 10:50:31 -0400 Subject: [PATCH 13/22] PCI: keystone: Remove unnecessary goto statement Fix the misuse of goto statement in ks_pcie_get_irq_controller_info() as simple return is more appropriate for this function. While at it add an error log for absence of interrupt controller node. [bhelgaas: drop "ret" altogether since we always know the return value] Signed-off-by: Murali Karicheri Signed-off-by: Bjorn Helgaas CC: Rob Herring CC: Pawel Moll CC: Mark Rutland CC: Ian Campbell CC: Kumar Gala --- drivers/pci/host/pci-keystone.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/pci/host/pci-keystone.c b/drivers/pci/host/pci-keystone.c index 58120009c819..6b8301ef21ca 100644 --- a/drivers/pci/host/pci-keystone.c +++ b/drivers/pci/host/pci-keystone.c @@ -160,7 +160,7 @@ static void ks_pcie_legacy_irq_handler(struct irq_desc *desc) static int ks_pcie_get_irq_controller_info(struct keystone_pcie *ks_pcie, char *controller, int *num_irqs) { - int temp, max_host_irqs, legacy = 1, *host_irqs, ret = -EINVAL; + int temp, max_host_irqs, legacy = 1, *host_irqs; struct device *dev = ks_pcie->pp.dev; struct device_node *np_pcie = dev->of_node, **np_temp; @@ -181,11 +181,15 @@ static int ks_pcie_get_irq_controller_info(struct keystone_pcie *ks_pcie, *np_temp = of_find_node_by_name(np_pcie, controller); if (!(*np_temp)) { dev_err(dev, "Node for %s is absent\n", controller); - goto out; + return -EINVAL; } + temp = of_irq_count(*np_temp); - if (!temp) - goto out; + if (!temp) { + dev_err(dev, "No IRQ entries in %s\n", controller); + return -EINVAL; + } + if (temp > max_host_irqs) dev_warn(dev, "Too many %s interrupts defined %u\n", (legacy ? "legacy" : "MSI"), temp); @@ -199,12 +203,13 @@ static int ks_pcie_get_irq_controller_info(struct keystone_pcie *ks_pcie, if (!host_irqs[temp]) break; } + if (temp) { *num_irqs = temp; - ret = 0; + return 0; } -out: - return ret; + + return -EINVAL; } static void ks_pcie_setup_interrupts(struct keystone_pcie *ks_pcie) @@ -345,7 +350,7 @@ static int __init ks_add_pcie_port(struct keystone_pcie *ks_pcie, return ret; } - return ret; + return 0; } static const struct of_device_id ks_pcie_of_match[] = { @@ -374,7 +379,7 @@ static int __init ks_pcie_probe(struct platform_device *pdev) struct resource *res; void __iomem *reg_p; struct phy *phy; - int ret = 0; + int ret; ks_pcie = devm_kzalloc(&pdev->dev, sizeof(*ks_pcie), GFP_KERNEL); From 80014ad5d59cc5a141b21f46371b81736de1c44a Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Tue, 26 Apr 2016 10:31:45 +0200 Subject: [PATCH 14/22] dt-bindings: pci: add DT binding for Marvell Armada 7K/8K PCIe controller This commit adds the Device Tree binding documentation that allows to describe the PCIe controller found in Marvell Armada 7K/8K SoCs. Signed-off-by: Thomas Petazzoni Acked-by: Rob Herring Signed-off-by: Bjorn Helgaas --- .../devicetree/bindings/pci/pci-armada8k.txt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/pci-armada8k.txt diff --git a/Documentation/devicetree/bindings/pci/pci-armada8k.txt b/Documentation/devicetree/bindings/pci/pci-armada8k.txt new file mode 100644 index 000000000000..598533a57d79 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/pci-armada8k.txt @@ -0,0 +1,38 @@ +* Marvell Armada 7K/8K PCIe interface + +This PCIe host controller is based on the Synopsis Designware PCIe IP +and thus inherits all the common properties defined in designware-pcie.txt. + +Required properties: +- compatible: "marvell,armada8k-pcie" +- reg: must contain two register regions + - the control register region + - the config space region +- reg-names: + - "ctrl" for the control register region + - "config" for the config space region +- interrupts: Interrupt specifier for the PCIe controler +- clocks: reference to the PCIe controller clock + +Example: + + pcie@f2600000 { + compatible = "marvell,armada8k-pcie", "snps,dw-pcie"; + reg = <0 0xf2600000 0 0x10000>, <0 0xf6f00000 0 0x80000>; + reg-names = "ctrl", "config"; + #address-cells = <3>; + #size-cells = <2>; + #interrupt-cells = <1>; + device_type = "pci"; + dma-coherent; + + bus-range = <0 0xff>; + ranges = <0x81000000 0 0xf9000000 0 0xf9000000 0 0x10000 /* downstream I/O */ + 0x82000000 0 0xf6000000 0 0xf6000000 0 0xf00000>; /* non-prefetchable memory */ + interrupt-map-mask = <0 0 0 0>; + interrupt-map = <0 0 0 0 &gic 0 GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>; + interrupts = ; + num-lanes = <1>; + clocks = <&cpm_syscon0 1 13>; + status = "disabled"; + }; From 1c52a5139fcf3cebfd61be9293bc0da529968c0d Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Tue, 26 Apr 2016 10:31:46 +0200 Subject: [PATCH 15/22] PCI: armada: Add driver for Marvell Armada 7K/8K PCIe controller The Marvell Armada 7K/8K SoCs integrate a PCIe controller from Synopsys. Add a new driver that provides the small glue needed to use the existing Designware driver to make it work on Marvell Armada 7K/8K SoCs. The MSI support will be enabled at a later point. [bhelgaas: use dev_dbg(), dw_pcie_wait_for_link()] Signed-off-by: Thomas Petazzoni Signed-off-by: Bjorn Helgaas --- drivers/pci/host/Kconfig | 11 ++ drivers/pci/host/Makefile | 1 + drivers/pci/host/pcie-armada8k.c | 262 +++++++++++++++++++++++++++++++ 3 files changed, 274 insertions(+) create mode 100644 drivers/pci/host/pcie-armada8k.c diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 7a0780d56d2d..a3b6f24be7c5 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -231,4 +231,15 @@ config PCI_HOST_THUNDER_ECAM help Say Y here if you want ECAM support for CN88XX-Pass-1.x Cavium Thunder SoCs. +config PCIE_ARMADA_8K + bool "Marvell Armada-8K PCIe controller" + depends on ARCH_MVEBU + select PCIE_DW + select PCIEPORTBUS + help + Say Y here if you want to enable PCIe controller support on + Armada-8K SoCs. The PCIe controller on Armada-8K is based on + Designware hardware and therefore the driver re-uses the + Designware core functions to implement the driver. + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index d85b5faf9bbc..a6f85e3987c0 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_PCI_HISI) += pcie-hisi.o obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o obj-$(CONFIG_PCI_HOST_THUNDER_ECAM) += pci-thunder-ecam.o obj-$(CONFIG_PCI_HOST_THUNDER_PEM) += pci-thunder-pem.o +obj-$(CONFIG_PCIE_ARMADA_8K) += pcie-armada8k.o diff --git a/drivers/pci/host/pcie-armada8k.c b/drivers/pci/host/pcie-armada8k.c new file mode 100644 index 000000000000..55723567b5d4 --- /dev/null +++ b/drivers/pci/host/pcie-armada8k.c @@ -0,0 +1,262 @@ +/* + * PCIe host controller driver for Marvell Armada-8K SoCs + * + * Armada-8K PCIe Glue Layer Source Code + * + * Copyright (C) 2016 Marvell Technology Group Ltd. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcie-designware.h" + +struct armada8k_pcie { + void __iomem *base; + struct clk *clk; + struct pcie_port pp; +}; + +#define PCIE_VENDOR_REGS_OFFSET 0x8000 + +#define PCIE_GLOBAL_CONTROL_REG 0x0 +#define PCIE_APP_LTSSM_EN BIT(2) +#define PCIE_DEVICE_TYPE_SHIFT 4 +#define PCIE_DEVICE_TYPE_MASK 0xF +#define PCIE_DEVICE_TYPE_RC 0x4 /* Root complex */ + +#define PCIE_GLOBAL_STATUS_REG 0x8 +#define PCIE_GLB_STS_RDLH_LINK_UP BIT(1) +#define PCIE_GLB_STS_PHY_LINK_UP BIT(9) + +#define PCIE_GLOBAL_INT_CAUSE1_REG 0x1C +#define PCIE_GLOBAL_INT_MASK1_REG 0x20 +#define PCIE_INT_A_ASSERT_MASK BIT(9) +#define PCIE_INT_B_ASSERT_MASK BIT(10) +#define PCIE_INT_C_ASSERT_MASK BIT(11) +#define PCIE_INT_D_ASSERT_MASK BIT(12) + +#define PCIE_ARCACHE_TRC_REG 0x50 +#define PCIE_AWCACHE_TRC_REG 0x54 +#define PCIE_ARUSER_REG 0x5C +#define PCIE_AWUSER_REG 0x60 +/* + * AR/AW Cache defauls: Normal memory, Write-Back, Read / Write + * allocate + */ +#define ARCACHE_DEFAULT_VALUE 0x3511 +#define AWCACHE_DEFAULT_VALUE 0x5311 + +#define DOMAIN_OUTER_SHAREABLE 0x2 +#define AX_USER_DOMAIN_MASK 0x3 +#define AX_USER_DOMAIN_SHIFT 4 + +#define to_armada8k_pcie(x) container_of(x, struct armada8k_pcie, pp) + +static int armada8k_pcie_link_up(struct pcie_port *pp) +{ + struct armada8k_pcie *pcie = to_armada8k_pcie(pp); + u32 reg; + u32 mask = PCIE_GLB_STS_RDLH_LINK_UP | PCIE_GLB_STS_PHY_LINK_UP; + + reg = readl(pcie->base + PCIE_GLOBAL_STATUS_REG); + + if ((reg & mask) == mask) + return 1; + + dev_dbg(pp->dev, "No link detected (Global-Status: 0x%08x).\n", reg); + return 0; +} + +static void armada8k_pcie_establish_link(struct pcie_port *pp) +{ + struct armada8k_pcie *pcie = to_armada8k_pcie(pp); + void __iomem *base = pcie->base; + u32 reg; + + if (!dw_pcie_link_up(pp)) { + /* Disable LTSSM state machine to enable configuration */ + reg = readl(base + PCIE_GLOBAL_CONTROL_REG); + reg &= ~(PCIE_APP_LTSSM_EN); + writel(reg, base + PCIE_GLOBAL_CONTROL_REG); + } + + /* Set the device to root complex mode */ + reg = readl(base + PCIE_GLOBAL_CONTROL_REG); + reg &= ~(PCIE_DEVICE_TYPE_MASK << PCIE_DEVICE_TYPE_SHIFT); + reg |= PCIE_DEVICE_TYPE_RC << PCIE_DEVICE_TYPE_SHIFT; + writel(reg, base + PCIE_GLOBAL_CONTROL_REG); + + /* Set the PCIe master AxCache attributes */ + writel(ARCACHE_DEFAULT_VALUE, base + PCIE_ARCACHE_TRC_REG); + writel(AWCACHE_DEFAULT_VALUE, base + PCIE_AWCACHE_TRC_REG); + + /* Set the PCIe master AxDomain attributes */ + reg = readl(base + PCIE_ARUSER_REG); + reg &= ~(AX_USER_DOMAIN_MASK << AX_USER_DOMAIN_SHIFT); + reg |= DOMAIN_OUTER_SHAREABLE << AX_USER_DOMAIN_SHIFT; + writel(reg, base + PCIE_ARUSER_REG); + + reg = readl(base + PCIE_AWUSER_REG); + reg &= ~(AX_USER_DOMAIN_MASK << AX_USER_DOMAIN_SHIFT); + reg |= DOMAIN_OUTER_SHAREABLE << AX_USER_DOMAIN_SHIFT; + writel(reg, base + PCIE_AWUSER_REG); + + /* Enable INT A-D interrupts */ + reg = readl(base + PCIE_GLOBAL_INT_MASK1_REG); + reg |= PCIE_INT_A_ASSERT_MASK | PCIE_INT_B_ASSERT_MASK | + PCIE_INT_C_ASSERT_MASK | PCIE_INT_D_ASSERT_MASK; + writel(reg, base + PCIE_GLOBAL_INT_MASK1_REG); + + if (!dw_pcie_link_up(pp)) { + /* Configuration done. Start LTSSM */ + reg = readl(base + PCIE_GLOBAL_CONTROL_REG); + reg |= PCIE_APP_LTSSM_EN; + writel(reg, base + PCIE_GLOBAL_CONTROL_REG); + } + + /* Wait until the link becomes active again */ + if (dw_pcie_wait_for_link(pp)) + dev_err(pp->dev, "Link not up after reconfiguration\n"); +} + +static void armada8k_pcie_host_init(struct pcie_port *pp) +{ + dw_pcie_setup_rc(pp); + armada8k_pcie_establish_link(pp); +} + +static irqreturn_t armada8k_pcie_irq_handler(int irq, void *arg) +{ + struct pcie_port *pp = arg; + struct armada8k_pcie *pcie = to_armada8k_pcie(pp); + void __iomem *base = pcie->base; + u32 val; + + /* + * Interrupts are directly handled by the device driver of the + * PCI device. However, they are also latched into the PCIe + * controller, so we simply discard them. + */ + val = readl(base + PCIE_GLOBAL_INT_CAUSE1_REG); + writel(val, base + PCIE_GLOBAL_INT_CAUSE1_REG); + + return IRQ_HANDLED; +} + +static struct pcie_host_ops armada8k_pcie_host_ops = { + .link_up = armada8k_pcie_link_up, + .host_init = armada8k_pcie_host_init, +}; + +static int armada8k_add_pcie_port(struct pcie_port *pp, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret; + + pp->root_bus_nr = -1; + pp->ops = &armada8k_pcie_host_ops; + + pp->irq = platform_get_irq(pdev, 0); + if (!pp->irq) { + dev_err(dev, "failed to get irq for port\n"); + return -ENODEV; + } + + ret = devm_request_irq(dev, pp->irq, armada8k_pcie_irq_handler, + IRQF_SHARED, "armada8k-pcie", pp); + if (ret) { + dev_err(dev, "failed to request irq %d\n", pp->irq); + return ret; + } + + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(dev, "failed to initialize host: %d\n", ret); + return ret; + } + + return 0; +} + +static int armada8k_pcie_probe(struct platform_device *pdev) +{ + struct armada8k_pcie *pcie; + struct pcie_port *pp; + struct device *dev = &pdev->dev; + struct resource *base; + int ret; + + pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->clk = devm_clk_get(dev, NULL); + if (IS_ERR(pcie->clk)) + return PTR_ERR(pcie->clk); + + clk_prepare_enable(pcie->clk); + + pp = &pcie->pp; + pp->dev = dev; + platform_set_drvdata(pdev, pcie); + + /* Get the dw-pcie unit configuration/control registers base. */ + base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ctrl"); + pp->dbi_base = devm_ioremap_resource(dev, base); + if (IS_ERR(pp->dbi_base)) { + dev_err(dev, "couldn't remap regs base %p\n", base); + ret = PTR_ERR(pp->dbi_base); + goto fail; + } + + pcie->base = pp->dbi_base + PCIE_VENDOR_REGS_OFFSET; + + ret = armada8k_add_pcie_port(pp, pdev); + if (ret) + goto fail; + + return 0; + +fail: + if (!IS_ERR(pcie->clk)) + clk_disable_unprepare(pcie->clk); + + return ret; +} + +static const struct of_device_id armada8k_pcie_of_match[] = { + { .compatible = "marvell,armada8k-pcie", }, + {}, +}; +MODULE_DEVICE_TABLE(of, armada8k_pcie_of_match); + +static struct platform_driver armada8k_pcie_driver = { + .probe = armada8k_pcie_probe, + .driver = { + .name = "armada8k-pcie", + .of_match_table = of_match_ptr(armada8k_pcie_of_match), + }, +}; + +module_platform_driver(armada8k_pcie_driver); + +MODULE_DESCRIPTION("Armada 8k PCIe host controller driver"); +MODULE_AUTHOR("Yehuda Yitshak "); +MODULE_AUTHOR("Shadi Ammouri "); +MODULE_LICENSE("GPL v2"); From 76ba8c1f2312e5128ef33e00a29c72c85bb80068 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Sat, 16 Apr 2016 22:15:46 +0200 Subject: [PATCH 16/22] PCI: rcar: Select PCI_MSI_IRQ_DOMAIN The R-Car PCIe driver requires the use of IRQ domains for its MSI code: drivers/pci/host/pcie-rcar.c:635:9: error: implicit declaration of function 'irq_find_mapping' [-Werror=implicit-function-declaration] drivers/pci/host/pcie-rcar.c:666:8: error: implicit declaration of function 'irq_create_mapping' [-Werror=implicit-function-declaration] ... Add a Kconfig select to ensure that the feature is always enabled. This is not consistent with what the other drivers do at the moment, but I have another patch that changes them to do it like this one, which is more logical. Signed-off-by: Arnd Bergmann Signed-off-by: Bjorn Helgaas Acked-by: Simon Horman --- drivers/pci/host/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 7a0780d56d2d..1fca8536daab 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -72,6 +72,8 @@ config PCI_RCAR_GEN2 config PCI_RCAR_GEN2_PCIE bool "Renesas R-Car PCIe controller" depends on ARCH_RENESAS || (ARM && COMPILE_TEST) + select PCI_MSI + select PCI_MSI_IRQ_DOMAIN help Say Y here if you want PCIe controller support on R-Car Gen2 SoCs. From 93bf9073a8a7e844b27327b05ab4dc608354d6fd Mon Sep 17 00:00:00 2001 From: David Daney Date: Mon, 11 Apr 2016 16:29:32 -0700 Subject: [PATCH 17/22] PCI: thunder: Don't clobber read-only bits in bridge config registers The 32-bit addressing modes in the I/O and Prefetchable Memory registers are required to be read-only. Since the underlying access method allows them to be set, emulate their read-only nature and always set them. Signed-off-by: David Daney Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-thunder-pem.c | 42 ++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/drivers/pci/host/pci-thunder-pem.c b/drivers/pci/host/pci-thunder-pem.c index cabb92a514ac..d4a779761472 100644 --- a/drivers/pci/host/pci-thunder-pem.c +++ b/drivers/pci/host/pci-thunder-pem.c @@ -153,11 +153,11 @@ static int thunder_pem_config_read(struct pci_bus *bus, unsigned int devfn, * reserved bits, this makes the code simpler and is OK as the bits * are not affected by writing zeros to them. */ -static u32 thunder_pem_bridge_w1c_bits(int where) +static u32 thunder_pem_bridge_w1c_bits(u64 where_aligned) { u32 w1c_bits = 0; - switch (where & ~3) { + switch (where_aligned) { case 0x04: /* Command/Status */ case 0x1c: /* Base and I/O Limit/Secondary Status */ w1c_bits = 0xff000000; @@ -184,12 +184,34 @@ static u32 thunder_pem_bridge_w1c_bits(int where) return w1c_bits; } +/* Some bits must be written to one so they appear to be read-only. */ +static u32 thunder_pem_bridge_w1_bits(u64 where_aligned) +{ + u32 w1_bits; + + switch (where_aligned) { + case 0x1c: /* I/O Base / I/O Limit, Secondary Status */ + /* Force 32-bit I/O addressing. */ + w1_bits = 0x0101; + break; + case 0x24: /* Prefetchable Memory Base / Prefetchable Memory Limit */ + /* Force 64-bit addressing */ + w1_bits = 0x00010001; + break; + default: + w1_bits = 0; + break; + } + return w1_bits; +} + static int thunder_pem_bridge_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val) { struct gen_pci *pci = bus->sysdata; struct thunder_pem_pci *pem_pci; u64 write_val, read_val; + u64 where_aligned = where & ~3ull; u32 mask = 0; pem_pci = container_of(pci, struct thunder_pem_pci, gen_pci); @@ -205,8 +227,7 @@ static int thunder_pem_bridge_write(struct pci_bus *bus, unsigned int devfn, */ switch (size) { case 1: - read_val = where & ~3ull; - writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD); + writeq(where_aligned, pem_pci->pem_reg_base + PEM_CFG_RD); read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD); read_val >>= 32; mask = ~(0xff << (8 * (where & 3))); @@ -215,8 +236,7 @@ static int thunder_pem_bridge_write(struct pci_bus *bus, unsigned int devfn, val |= (u32)read_val; break; case 2: - read_val = where & ~3ull; - writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD); + writeq(where_aligned, pem_pci->pem_reg_base + PEM_CFG_RD); read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD); read_val >>= 32; mask = ~(0xffff << (8 * (where & 3))); @@ -243,12 +263,18 @@ static int thunder_pem_bridge_write(struct pci_bus *bus, unsigned int devfn, } } + /* + * Some bits must be read-only with value of one. Since the + * access method allows these to be cleared if a zero is + * written, force them to one before writing. + */ + val |= thunder_pem_bridge_w1_bits(where_aligned); + /* * Low order bits are the config address, the high order 32 * bits are the data to be written. */ - write_val = where & ~3ull; - write_val |= (((u64)val) << 32); + write_val = (((u64)val) << 32) | where_aligned; writeq(write_val, pem_pci->pem_reg_base + PEM_CFG_WR); return PCIBIOS_SUCCESSFUL; } From e6f1fef04c253955004efea35aea2e4bc59f49b6 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Mon, 2 May 2016 14:08:21 -0500 Subject: [PATCH 18/22] PCI: imx6: Use enum instead of bool for variant indicator Use enumerated type instead of a boolean flag to specify the variant of the PCIe IP block (6Q, 6SX, etc). This patch has zero functional impact, however it makes the code easier to extend for the case of more than 2 possible variants of an IP block (of which there are). [bhelgaas: rewrap comment, remove extra blank line] Signed-off-by: Andrey Smirnov Signed-off-by: Bjorn Helgaas Reviewed-by: Fabio Estevam --- drivers/pci/host/pci-imx6.c | 129 +++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 59 deletions(-) diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 837d5e0447ac..6b5d8c2fe2f2 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,11 @@ #define to_imx6_pcie(x) container_of(x, struct imx6_pcie, pp) +enum imx6_pcie_variants { + IMX6Q, + IMX6SX +}; + struct imx6_pcie { int reset_gpio; bool gpio_active_high; @@ -40,7 +46,7 @@ struct imx6_pcie { struct clk *pcie; struct pcie_port pp; struct regmap *iomuxc_gpr; - bool is_imx6sx; + enum imx6_pcie_variants variant; void __iomem *mem_base; u32 tx_deemph_gen1; u32 tx_deemph_gen2_3p5db; @@ -240,7 +246,8 @@ static int imx6_pcie_assert_core_reset(struct pcie_port *pp) struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); u32 val, gpr1, gpr12; - if (imx6_pcie->is_imx6sx) { + switch (imx6_pcie->variant) { + case IMX6SX: regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6SX_GPR12_PCIE_TEST_POWERDOWN, IMX6SX_GPR12_PCIE_TEST_POWERDOWN); @@ -248,72 +255,78 @@ static int imx6_pcie_assert_core_reset(struct pcie_port *pp) regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5, IMX6SX_GPR5_PCIE_BTNRST_RESET, IMX6SX_GPR5_PCIE_BTNRST_RESET); - return 0; + break; + case IMX6Q: + /* + * If the bootloader already enabled the link we need some + * special handling to get the core back into a state where + * it is safe to touch it for configuration. As there is + * no dedicated reset signal wired up for MX6QDL, we need + * to manually force LTSSM into "detect" state before + * completely disabling LTSSM, which is a prerequisite for + * core configuration. + * + * If both LTSSM_ENABLE and REF_SSP_ENABLE are active we + * have a strong indication that the bootloader activated + * the link. + */ + regmap_read(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, &gpr1); + regmap_read(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, &gpr12); + + if ((gpr1 & IMX6Q_GPR1_PCIE_REF_CLK_EN) && + (gpr12 & IMX6Q_GPR12_PCIE_CTL_2)) { + val = readl(pp->dbi_base + PCIE_PL_PFLR); + val &= ~PCIE_PL_PFLR_LINK_STATE_MASK; + val |= PCIE_PL_PFLR_FORCE_LINK; + writel(val, pp->dbi_base + PCIE_PL_PFLR); + + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6Q_GPR12_PCIE_CTL_2, 0 << 10); + } + + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD, 1 << 18); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_REF_CLK_EN, 0 << 16); + break; } - /* - * If the bootloader already enabled the link we need some special - * handling to get the core back into a state where it is safe to - * touch it for configuration. As there is no dedicated reset signal - * wired up for MX6QDL, we need to manually force LTSSM into "detect" - * state before completely disabling LTSSM, which is a prerequisite - * for core configuration. - * - * If both LTSSM_ENABLE and REF_SSP_ENABLE are active we have a strong - * indication that the bootloader activated the link. - */ - regmap_read(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, &gpr1); - regmap_read(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, &gpr12); - - if ((gpr1 & IMX6Q_GPR1_PCIE_REF_CLK_EN) && - (gpr12 & IMX6Q_GPR12_PCIE_CTL_2)) { - val = readl(pp->dbi_base + PCIE_PL_PFLR); - val &= ~PCIE_PL_PFLR_LINK_STATE_MASK; - val |= PCIE_PL_PFLR_FORCE_LINK; - writel(val, pp->dbi_base + PCIE_PL_PFLR); - - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX6Q_GPR12_PCIE_CTL_2, 0 << 10); - } - - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_TEST_PD, 1 << 18); - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_REF_CLK_EN, 0 << 16); - return 0; } static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) { struct pcie_port *pp = &imx6_pcie->pp; - int ret; + int ret = 0; - if (imx6_pcie->is_imx6sx) { + switch (imx6_pcie->variant) { + case IMX6SX: ret = clk_prepare_enable(imx6_pcie->pcie_inbound_axi); if (ret) { dev_err(pp->dev, "unable to enable pcie_axi clock\n"); - return ret; + break; } regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6SX_GPR12_PCIE_TEST_POWERDOWN, 0); - return ret; + break; + case IMX6Q: + /* power up core phy and enable ref clock */ + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD, 0 << 18); + /* + * the async reset input need ref clock to sync internally, + * when the ref clock comes after reset, internal synced + * reset time is too short, cannot meet the requirement. + * add one ~10us delay here. + */ + udelay(10); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_REF_CLK_EN, 1 << 16); + break; } - /* power up core phy and enable ref clock */ - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_TEST_PD, 0 << 18); - /* - * the async reset input need ref clock to sync internally, - * when the ref clock comes after reset, internal synced - * reset time is too short, cannot meet the requirement. - * add one ~10us delay here. - */ - udelay(10); - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, - IMX6Q_GPR1_PCIE_REF_CLK_EN, 1 << 16); - return 0; + return ret; } static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) @@ -357,7 +370,7 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) !imx6_pcie->gpio_active_high); } - if (imx6_pcie->is_imx6sx) + if (imx6_pcie->variant == IMX6SX) regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5, IMX6SX_GPR5_PCIE_BTNRST_RESET, 0); @@ -371,18 +384,16 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) clk_disable_unprepare(imx6_pcie->pcie_phy); err_pcie_phy: return ret; - } static void imx6_pcie_init_phy(struct pcie_port *pp) { struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); - if (imx6_pcie->is_imx6sx) { + if (imx6_pcie->variant == IMX6SX) regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6SX_GPR12_PCIE_RX_EQ_MASK, IMX6SX_GPR12_PCIE_RX_EQ_2); - } regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6Q_GPR12_PCIE_CTL_2, 0 << 10); @@ -593,8 +604,8 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) pp = &imx6_pcie->pp; pp->dev = &pdev->dev; - imx6_pcie->is_imx6sx = of_device_is_compatible(pp->dev->of_node, - "fsl,imx6sx-pcie"); + imx6_pcie->variant = + (enum imx6_pcie_variants)of_device_get_match_data(&pdev->dev); /* Added for PCI abort handling */ hook_fault_code(16 + 6, imx6q_pcie_abort_handler, SIGBUS, 0, @@ -643,7 +654,7 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) return PTR_ERR(imx6_pcie->pcie); } - if (imx6_pcie->is_imx6sx) { + if (imx6_pcie->variant == IMX6SX) { imx6_pcie->pcie_inbound_axi = devm_clk_get(&pdev->dev, "pcie_inbound_axi"); if (IS_ERR(imx6_pcie->pcie_inbound_axi)) { @@ -705,8 +716,8 @@ static void imx6_pcie_shutdown(struct platform_device *pdev) } static const struct of_device_id imx6_pcie_of_match[] = { - { .compatible = "fsl,imx6q-pcie", }, - { .compatible = "fsl,imx6sx-pcie", }, + { .compatible = "fsl,imx6q-pcie", .data = (void *)IMX6Q, }, + { .compatible = "fsl,imx6sx-pcie", .data = (void *)IMX6SX, }, {}, }; MODULE_DEVICE_TABLE(of, imx6_pcie_of_match); From 4d31c6109a24892df461b6a98842935e80159a5e Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Mon, 2 May 2016 14:09:10 -0500 Subject: [PATCH 19/22] PCI: imx6: Implement reset sequence for i.MX6+ I.MX6+ has a dedicated bit for resetting PCIe core, which should be used instead of a regular reset sequence since using the latter will hang the SoC. This commit is based on c34068d48273e24d392d9a49a38be807954420ed from http://git.freescale.com/git/cgit.cgi/imx/linux-2.6-imx.git Tested-by: Gary Bisson Signed-off-by: Andrey Smirnov Signed-off-by: Bjorn Helgaas Reviewed-by: Fabio Estevam --- .../bindings/pci/fsl,imx6q-pcie.txt | 2 +- drivers/pci/host/pci-imx6.c | 23 +++++++++++++++++-- include/linux/mfd/syscon/imx6q-iomuxc-gpr.h | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt index d742f917505a..83aeb1f5a645 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt @@ -4,7 +4,7 @@ This PCIe host controller is based on the Synopsis Designware PCIe IP and thus inherits all the common properties defined in designware-pcie.txt. Required properties: -- compatible: "fsl,imx6q-pcie", "fsl,imx6sx-pcie" +- compatible: "fsl,imx6q-pcie", "fsl,imx6sx-pcie", "fsl,imx6qp-pcie" - reg: base address and length of the PCIe controller - interrupts: A list of interrupt outputs of the controller. Must contain an entry for each entry in the interrupt-names property. diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 6b5d8c2fe2f2..b741a36a67f3 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -34,7 +34,8 @@ enum imx6_pcie_variants { IMX6Q, - IMX6SX + IMX6SX, + IMX6QP, }; struct imx6_pcie { @@ -256,6 +257,11 @@ static int imx6_pcie_assert_core_reset(struct pcie_port *pp) IMX6SX_GPR5_PCIE_BTNRST_RESET, IMX6SX_GPR5_PCIE_BTNRST_RESET); break; + case IMX6QP: + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_SW_RST, + IMX6Q_GPR1_PCIE_SW_RST); + break; case IMX6Q: /* * If the bootloader already enabled the link we need some @@ -310,6 +316,7 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6SX_GPR12_PCIE_TEST_POWERDOWN, 0); break; + case IMX6QP: /* FALLTHROUGH */ case IMX6Q: /* power up core phy and enable ref clock */ regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, @@ -370,9 +377,20 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) !imx6_pcie->gpio_active_high); } - if (imx6_pcie->variant == IMX6SX) + switch (imx6_pcie->variant) { + case IMX6SX: regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR5, IMX6SX_GPR5_PCIE_BTNRST_RESET, 0); + break; + case IMX6QP: + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_SW_RST, 0); + + usleep_range(200, 500); + break; + case IMX6Q: /* Nothing to do */ + break; + } return 0; @@ -718,6 +736,7 @@ static void imx6_pcie_shutdown(struct platform_device *pdev) static const struct of_device_id imx6_pcie_of_match[] = { { .compatible = "fsl,imx6q-pcie", .data = (void *)IMX6Q, }, { .compatible = "fsl,imx6sx-pcie", .data = (void *)IMX6SX, }, + { .compatible = "fsl,imx6qp-pcie", .data = (void *)IMX6QP, }, {}, }; MODULE_DEVICE_TABLE(of, imx6_pcie_of_match); diff --git a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h index 238c8db953eb..5b08e3c5325f 100644 --- a/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h +++ b/include/linux/mfd/syscon/imx6q-iomuxc-gpr.h @@ -95,6 +95,7 @@ #define IMX6Q_GPR0_DMAREQ_MUX_SEL0_IOMUX BIT(0) #define IMX6Q_GPR1_PCIE_REQ_MASK (0x3 << 30) +#define IMX6Q_GPR1_PCIE_SW_RST BIT(29) #define IMX6Q_GPR1_PCIE_EXIT_L1 BIT(28) #define IMX6Q_GPR1_PCIE_RDY_L23 BIT(27) #define IMX6Q_GPR1_PCIE_ENTER_L1 BIT(26) From b410f3b68d7ea8ca93abeb3420684c0378b7e134 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Wed, 20 Apr 2016 08:45:31 -0700 Subject: [PATCH 20/22] ARM: dts: imx6qp: Specify imx6qp version of PCIe core I.MX6Quad Plus has a slightly different version of PCIe core than reqular i.MX6Quad. Tested-by: Gary Bisson Signed-off-by: Andrey Smirnov Signed-off-by: Bjorn Helgaas Reviewed-by: Fabio Estevam --- arch/arm/boot/dts/imx6qp.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/boot/dts/imx6qp.dtsi b/arch/arm/boot/dts/imx6qp.dtsi index 1ada71437e49..886dbf2eca49 100644 --- a/arch/arm/boot/dts/imx6qp.dtsi +++ b/arch/arm/boot/dts/imx6qp.dtsi @@ -82,5 +82,8 @@ ipu2: ipu@02800000 { "ldb_di0", "ldb_di1", "prg"; }; + pcie: pcie@0x01000000 { + compatible = "fsl,imx6qp-pcie", "snps,dw-pcie"; + }; }; }; From deb22e5c84c884a129d801cf3bfde7411536998d Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Fri, 29 Apr 2016 11:39:10 +0200 Subject: [PATCH 21/22] PCI: hv: Report resources release after stopping the bus Kernel hang is observed when pci-hyperv module is release with device drivers still attached. E.g., when I do 'rmmod pci_hyperv' with BCM5720 device pass-through-ed (tg3 module) I see the following: NMI watchdog: BUG: soft lockup - CPU#1 stuck for 22s! [rmmod:2104] ... Call Trace: [] tg3_read_mem+0x87/0x100 [tg3] [] ? 0xffffffffa063f000 [] tg3_poll_fw+0x85/0x150 [tg3] [] tg3_chip_reset+0x357/0x8c0 [tg3] [] tg3_halt+0x3b/0x190 [tg3] [] tg3_stop+0x171/0x230 [tg3] ... [] tg3_remove_one+0x90/0x140 [tg3] [] pci_device_remove+0x39/0xc0 [] __device_release_driver+0xa1/0x160 [] device_release_driver+0x23/0x30 [] pci_stop_bus_device+0x8a/0xa0 [] pci_stop_root_bus+0x36/0x60 [] hv_pci_remove+0x238/0x260 [pci_hyperv] The problem seems to be that we report local resources release before stopping the bus and removing devices from it and device drivers may try to perform some operations with these resources on shutdown. Move resources release report after we do pci_stop_root_bus(). Signed-off-by: Vitaly Kuznetsov Signed-off-by: Bjorn Helgaas Acked-by: Jake Oshins --- drivers/pci/host/pci-hyperv.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/pci/host/pci-hyperv.c b/drivers/pci/host/pci-hyperv.c index ed651baa7c50..328c653d7503 100644 --- a/drivers/pci/host/pci-hyperv.c +++ b/drivers/pci/host/pci-hyperv.c @@ -2268,11 +2268,6 @@ static int hv_pci_remove(struct hv_device *hdev) hbus = hv_get_drvdata(hdev); - ret = hv_send_resources_released(hdev); - if (ret) - dev_err(&hdev->device, - "Couldn't send resources released packet(s)\n"); - memset(&pkt.teardown_packet, 0, sizeof(pkt.teardown_packet)); init_completion(&comp_pkt.host_event); pkt.teardown_packet.completion_func = hv_pci_generic_compl; @@ -2295,6 +2290,11 @@ static int hv_pci_remove(struct hv_device *hdev) pci_unlock_rescan_remove(); } + ret = hv_send_resources_released(hdev); + if (ret) + dev_err(&hdev->device, + "Couldn't send resources released packet(s)\n"); + vmbus_close(hdev->channel); /* Delete any children which might still exist. */ From a5cb903aef8c642e6f0f6810d46dacedf666b54a Mon Sep 17 00:00:00 2001 From: Gabriele Paoloni Date: Sat, 16 Apr 2016 12:03:39 +0100 Subject: [PATCH 22/22] PCI: designware: Remove incorrect RC memory base/limit configuration Currently dw_pcie_setup_rc() configures memory base and memory limit in the type1 configuration header for the root complex. In doing so it uses the CPU address (pp->mem_base) rather than the bus address (pp->mem_bus_addr). This is wrong and it is useless since the configuration is overwritten later on when pci_bus_assign_resources() is called. Remove this configuration from dw_pcie_setup_rc(). Signed-off-by: Gabriele Paoloni Signed-off-by: Bjorn Helgaas Acked-by: Pratyush Anand --- drivers/pci/host/pcie-designware.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 261e4a1106df..aafd766546f3 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -708,8 +708,6 @@ static struct pci_ops dw_pcie_ops = { void dw_pcie_setup_rc(struct pcie_port *pp) { u32 val; - u32 membase; - u32 memlimit; /* set the number of lanes */ dw_pcie_readl_rc(pp, PCIE_PORT_LINK_CONTROL, &val); @@ -768,12 +766,6 @@ void dw_pcie_setup_rc(struct pcie_port *pp) val |= 0x00010100; dw_pcie_writel_rc(pp, val, PCI_PRIMARY_BUS); - /* setup memory base, memory limit */ - membase = ((u32)pp->mem_base & 0xfff00000) >> 16; - memlimit = (pp->mem_size + (u32)pp->mem_base) & 0xfff00000; - val = memlimit | membase; - dw_pcie_writel_rc(pp, val, PCI_MEMORY_BASE); - /* setup command register */ dw_pcie_readl_rc(pp, PCI_COMMAND, &val); val &= 0xffff0000;