diff options
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/host/Kconfig | 6 | ||||
-rw-r--r-- | drivers/pci/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/pci/host/pci-exynos.c | 112 | ||||
-rw-r--r-- | drivers/pci/host/pci-imx6.c | 575 | ||||
-rw-r--r-- | drivers/pci/host/pci-tegra.c | 4 | ||||
-rw-r--r-- | drivers/pci/host/pcie-designware.c | 240 | ||||
-rw-r--r-- | drivers/pci/host/pcie-designware.h | 14 | ||||
-rw-r--r-- | drivers/pci/hotplug/acpi_pcihp.c | 2 | ||||
-rw-r--r-- | drivers/pci/hotplug/shpchp.h | 2 | ||||
-rw-r--r-- | drivers/pci/probe.c | 3 |
10 files changed, 953 insertions, 6 deletions
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 3d9504811126..efa24d9a3361 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -15,6 +15,12 @@ config PCI_EXYNOS select PCIEPORTBUS select PCIE_DW +config PCI_IMX6 + bool "Freescale i.MX6 PCIe controller" + depends on SOC_IMX6Q + select PCIEPORTBUS + select PCIE_DW + config PCI_TEGRA bool "NVIDIA Tegra PCIe controller" depends on ARCH_TEGRA diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index c9a997b2690d..287d6a053dda 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_PCIE_DW) += pcie-designware.o obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o +obj-$(CONFIG_PCI_IMX6) += pci-imx6.o obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c index 94e096bb2d0a..ee692c2c3d73 100644 --- a/drivers/pci/host/pci-exynos.c +++ b/drivers/pci/host/pci-exynos.c @@ -48,6 +48,7 @@ struct exynos_pcie { #define PCIE_IRQ_SPECIAL 0x008 #define PCIE_IRQ_EN_PULSE 0x00c #define PCIE_IRQ_EN_LEVEL 0x010 +#define IRQ_MSI_ENABLE (0x1 << 2) #define PCIE_IRQ_EN_SPECIAL 0x014 #define PCIE_PWR_RESET 0x018 #define PCIE_CORE_RESET 0x01c @@ -77,18 +78,28 @@ struct exynos_pcie { #define PCIE_PHY_PLL_BIAS 0x00c #define PCIE_PHY_DCC_FEEDBACK 0x014 #define PCIE_PHY_PLL_DIV_1 0x05c +#define PCIE_PHY_COMMON_POWER 0x064 +#define PCIE_PHY_COMMON_PD_CMN (0x1 << 3) #define PCIE_PHY_TRSV0_EMP_LVL 0x084 #define PCIE_PHY_TRSV0_DRV_LVL 0x088 #define PCIE_PHY_TRSV0_RXCDR 0x0ac +#define PCIE_PHY_TRSV0_POWER 0x0c4 +#define PCIE_PHY_TRSV0_PD_TSV (0x1 << 7) #define PCIE_PHY_TRSV0_LVCC 0x0dc #define PCIE_PHY_TRSV1_EMP_LVL 0x144 #define PCIE_PHY_TRSV1_RXCDR 0x16c +#define PCIE_PHY_TRSV1_POWER 0x184 +#define PCIE_PHY_TRSV1_PD_TSV (0x1 << 7) #define PCIE_PHY_TRSV1_LVCC 0x19c #define PCIE_PHY_TRSV2_EMP_LVL 0x204 #define PCIE_PHY_TRSV2_RXCDR 0x22c +#define PCIE_PHY_TRSV2_POWER 0x244 +#define PCIE_PHY_TRSV2_PD_TSV (0x1 << 7) #define PCIE_PHY_TRSV2_LVCC 0x25c #define PCIE_PHY_TRSV3_EMP_LVL 0x2c4 #define PCIE_PHY_TRSV3_RXCDR 0x2ec +#define PCIE_PHY_TRSV3_POWER 0x304 +#define PCIE_PHY_TRSV3_PD_TSV (0x1 << 7) #define PCIE_PHY_TRSV3_LVCC 0x31c static inline void exynos_elb_writel(struct exynos_pcie *pcie, u32 val, u32 reg) @@ -202,6 +213,58 @@ static void exynos_pcie_deassert_phy_reset(struct pcie_port *pp) exynos_blk_writel(exynos_pcie, 0, PCIE_PHY_TRSV_RESET); } +static void exynos_pcie_power_on_phy(struct pcie_port *pp) +{ + u32 val; + struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_COMMON_POWER); + val &= ~PCIE_PHY_COMMON_PD_CMN; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_COMMON_POWER); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_TRSV0_POWER); + val &= ~PCIE_PHY_TRSV0_PD_TSV; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_TRSV0_POWER); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_TRSV1_POWER); + val &= ~PCIE_PHY_TRSV1_PD_TSV; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_TRSV1_POWER); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_TRSV2_POWER); + val &= ~PCIE_PHY_TRSV2_PD_TSV; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_TRSV2_POWER); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_TRSV3_POWER); + val &= ~PCIE_PHY_TRSV3_PD_TSV; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_TRSV3_POWER); +} + +static void exynos_pcie_power_off_phy(struct pcie_port *pp) +{ + u32 val; + struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_COMMON_POWER); + val |= PCIE_PHY_COMMON_PD_CMN; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_COMMON_POWER); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_TRSV0_POWER); + val |= PCIE_PHY_TRSV0_PD_TSV; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_TRSV0_POWER); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_TRSV1_POWER); + val |= PCIE_PHY_TRSV1_PD_TSV; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_TRSV1_POWER); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_TRSV2_POWER); + val |= PCIE_PHY_TRSV2_PD_TSV; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_TRSV2_POWER); + + val = exynos_phy_readl(exynos_pcie, PCIE_PHY_TRSV3_POWER); + val |= PCIE_PHY_TRSV3_PD_TSV; + exynos_phy_writel(exynos_pcie, val, PCIE_PHY_TRSV3_POWER); +} + static void exynos_pcie_init_phy(struct pcie_port *pp) { struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); @@ -270,6 +333,9 @@ static int exynos_pcie_establish_link(struct pcie_port *pp) /* de-assert phy reset */ exynos_pcie_deassert_phy_reset(pp); + /* power on phy */ + exynos_pcie_power_on_phy(pp); + /* initialize phy */ exynos_pcie_init_phy(pp); @@ -302,6 +368,9 @@ static int exynos_pcie_establish_link(struct pcie_port *pp) PCIE_PHY_PLL_LOCKED); dev_info(pp->dev, "PLL Locked: 0x%x\n", val); } + /* power off phy */ + exynos_pcie_power_off_phy(pp); + dev_err(pp->dev, "PCIe Link Fail\n"); return -EINVAL; } @@ -342,9 +411,36 @@ static irqreturn_t exynos_pcie_irq_handler(int irq, void *arg) return IRQ_HANDLED; } +static irqreturn_t exynos_pcie_msi_irq_handler(int irq, void *arg) +{ + struct pcie_port *pp = arg; + + dw_handle_msi_irq(pp); + + return IRQ_HANDLED; +} + +static void exynos_pcie_msi_init(struct pcie_port *pp) +{ + u32 val; + struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); + + dw_pcie_msi_init(pp); + + /* enable MSI interrupt */ + val = exynos_elb_readl(exynos_pcie, PCIE_IRQ_EN_LEVEL); + val |= IRQ_MSI_ENABLE; + exynos_elb_writel(exynos_pcie, val, PCIE_IRQ_EN_LEVEL); + return; +} + static void exynos_pcie_enable_interrupts(struct pcie_port *pp) { exynos_pcie_enable_irq_pulse(pp); + + if (IS_ENABLED(CONFIG_PCI_MSI)) + exynos_pcie_msi_init(pp); + return; } @@ -430,6 +526,22 @@ static int add_pcie_port(struct pcie_port *pp, struct platform_device *pdev) return ret; } + if (IS_ENABLED(CONFIG_PCI_MSI)) { + pp->msi_irq = platform_get_irq(pdev, 0); + if (!pp->msi_irq) { + dev_err(&pdev->dev, "failed to get msi irq\n"); + return -ENODEV; + } + + ret = devm_request_irq(&pdev->dev, pp->msi_irq, + exynos_pcie_msi_irq_handler, + IRQF_SHARED, "exynos-pcie", pp); + if (ret) { + dev_err(&pdev->dev, "failed to request msi irq\n"); + return ret; + } + } + pp->root_bus_nr = -1; pp->ops = &exynos_pcie_host_ops; diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c new file mode 100644 index 000000000000..5afa9226a080 --- /dev/null +++ b/drivers/pci/host/pci-imx6.c @@ -0,0 +1,575 @@ +/* + * PCIe host controller driver for Freescale i.MX6 SoCs + * + * Copyright (C) 2013 Kosagi + * http://www.kosagi.com + * + * Author: Sean Cross <xobs@kosagi.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio.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/pci.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/resource.h> +#include <linux/signal.h> +#include <linux/types.h> + +#include "pcie-designware.h" + +#define to_imx6_pcie(x) container_of(x, struct imx6_pcie, pp) + +struct imx6_pcie { + int reset_gpio; + int power_on_gpio; + int wake_up_gpio; + int disable_gpio; + struct clk *lvds_gate; + struct clk *sata_ref_100m; + struct clk *pcie_ref_125m; + struct clk *pcie_axi; + struct pcie_port pp; + struct regmap *iomuxc_gpr; + void __iomem *mem_base; +}; + +/* PCIe Port Logic registers (memory-mapped) */ +#define PL_OFFSET 0x700 +#define PCIE_PHY_DEBUG_R0 (PL_OFFSET + 0x28) +#define PCIE_PHY_DEBUG_R1 (PL_OFFSET + 0x2c) + +#define PCIE_PHY_CTRL (PL_OFFSET + 0x114) +#define PCIE_PHY_CTRL_DATA_LOC 0 +#define PCIE_PHY_CTRL_CAP_ADR_LOC 16 +#define PCIE_PHY_CTRL_CAP_DAT_LOC 17 +#define PCIE_PHY_CTRL_WR_LOC 18 +#define PCIE_PHY_CTRL_RD_LOC 19 + +#define PCIE_PHY_STAT (PL_OFFSET + 0x110) +#define PCIE_PHY_STAT_ACK_LOC 16 + +/* PHY registers (not memory-mapped) */ +#define PCIE_PHY_RX_ASIC_OUT 0x100D + +#define PHY_RX_OVRD_IN_LO 0x1005 +#define PHY_RX_OVRD_IN_LO_RX_DATA_EN (1 << 5) +#define PHY_RX_OVRD_IN_LO_RX_PLL_EN (1 << 3) + +static int pcie_phy_poll_ack(void __iomem *dbi_base, int exp_val) +{ + u32 val; + u32 max_iterations = 10; + u32 wait_counter = 0; + + do { + val = readl(dbi_base + PCIE_PHY_STAT); + val = (val >> PCIE_PHY_STAT_ACK_LOC) & 0x1; + wait_counter++; + + if (val == exp_val) + return 0; + + udelay(1); + } while (wait_counter < max_iterations); + + return -ETIMEDOUT; +} + +static int pcie_phy_wait_ack(void __iomem *dbi_base, int addr) +{ + u32 val; + int ret; + + val = addr << PCIE_PHY_CTRL_DATA_LOC; + writel(val, dbi_base + PCIE_PHY_CTRL); + + val |= (0x1 << PCIE_PHY_CTRL_CAP_ADR_LOC); + writel(val, dbi_base + PCIE_PHY_CTRL); + + ret = pcie_phy_poll_ack(dbi_base, 1); + if (ret) + return ret; + + val = addr << PCIE_PHY_CTRL_DATA_LOC; + writel(val, dbi_base + PCIE_PHY_CTRL); + + ret = pcie_phy_poll_ack(dbi_base, 0); + if (ret) + return ret; + + return 0; +} + +/* Read from the 16-bit PCIe PHY control registers (not memory-mapped) */ +static int pcie_phy_read(void __iomem *dbi_base, int addr , int *data) +{ + u32 val, phy_ctl; + int ret; + + ret = pcie_phy_wait_ack(dbi_base, addr); + if (ret) + return ret; + + /* assert Read signal */ + phy_ctl = 0x1 << PCIE_PHY_CTRL_RD_LOC; + writel(phy_ctl, dbi_base + PCIE_PHY_CTRL); + + ret = pcie_phy_poll_ack(dbi_base, 1); + if (ret) + return ret; + + val = readl(dbi_base + PCIE_PHY_STAT); + *data = val & 0xffff; + + /* deassert Read signal */ + writel(0x00, dbi_base + PCIE_PHY_CTRL); + + ret = pcie_phy_poll_ack(dbi_base, 0); + if (ret) + return ret; + + return 0; +} + +static int pcie_phy_write(void __iomem *dbi_base, int addr, int data) +{ + u32 var; + int ret; + + /* write addr */ + /* cap addr */ + ret = pcie_phy_wait_ack(dbi_base, addr); + if (ret) + return ret; + + var = data << PCIE_PHY_CTRL_DATA_LOC; + writel(var, dbi_base + PCIE_PHY_CTRL); + + /* capture data */ + var |= (0x1 << PCIE_PHY_CTRL_CAP_DAT_LOC); + writel(var, dbi_base + PCIE_PHY_CTRL); + + ret = pcie_phy_poll_ack(dbi_base, 1); + if (ret) + return ret; + + /* deassert cap data */ + var = data << PCIE_PHY_CTRL_DATA_LOC; + writel(var, dbi_base + PCIE_PHY_CTRL); + + /* wait for ack de-assertion */ + ret = pcie_phy_poll_ack(dbi_base, 0); + if (ret) + return ret; + + /* assert wr signal */ + var = 0x1 << PCIE_PHY_CTRL_WR_LOC; + writel(var, dbi_base + PCIE_PHY_CTRL); + + /* wait for ack */ + ret = pcie_phy_poll_ack(dbi_base, 1); + if (ret) + return ret; + + /* deassert wr signal */ + var = data << PCIE_PHY_CTRL_DATA_LOC; + writel(var, dbi_base + PCIE_PHY_CTRL); + + /* wait for ack de-assertion */ + ret = pcie_phy_poll_ack(dbi_base, 0); + if (ret) + return ret; + + writel(0x0, dbi_base + PCIE_PHY_CTRL); + + return 0; +} + +/* Added for PCI abort handling */ +static int imx6q_pcie_abort_handler(unsigned long addr, + unsigned int fsr, struct pt_regs *regs) +{ + /* + * If it was an imprecise abort, then we need to correct the + * return address to be _after_ the instruction. + */ + if (fsr & (1 << 10)) + regs->ARM_pc += 4; + return 0; +} + +static int imx6_pcie_assert_core_reset(struct pcie_port *pp) +{ + struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); + + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD, 1 << 18); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6Q_GPR12_PCIE_CTL_2, 1 << 10); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_REF_CLK_EN, 0 << 16); + + gpio_set_value(imx6_pcie->reset_gpio, 0); + msleep(100); + gpio_set_value(imx6_pcie->reset_gpio, 1); + + return 0; +} + +static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) +{ + struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); + int ret; + + if (gpio_is_valid(imx6_pcie->power_on_gpio)) + gpio_set_value(imx6_pcie->power_on_gpio, 1); + + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_TEST_PD, 0 << 18); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, + IMX6Q_GPR1_PCIE_REF_CLK_EN, 1 << 16); + + ret = clk_prepare_enable(imx6_pcie->sata_ref_100m); + if (ret) { + dev_err(pp->dev, "unable to enable sata_ref_100m\n"); + goto err_sata_ref; + } + + ret = clk_prepare_enable(imx6_pcie->pcie_ref_125m); + if (ret) { + dev_err(pp->dev, "unable to enable pcie_ref_125m\n"); + goto err_pcie_ref; + } + + ret = clk_prepare_enable(imx6_pcie->lvds_gate); + if (ret) { + dev_err(pp->dev, "unable to enable lvds_gate\n"); + goto err_lvds_gate; + } + + ret = clk_prepare_enable(imx6_pcie->pcie_axi); + if (ret) { + dev_err(pp->dev, "unable to enable pcie_axi\n"); + goto err_pcie_axi; + } + + /* allow the clocks to stabilize */ + usleep_range(200, 500); + + return 0; + +err_pcie_axi: + clk_disable_unprepare(imx6_pcie->lvds_gate); +err_lvds_gate: + clk_disable_unprepare(imx6_pcie->pcie_ref_125m); +err_pcie_ref: + clk_disable_unprepare(imx6_pcie->sata_ref_100m); +err_sata_ref: + return ret; + +} + +static void imx6_pcie_init_phy(struct pcie_port *pp) +{ + struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); + + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + 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); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6Q_GPR12_LOS_LEVEL, 9 << 4); + + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, + IMX6Q_GPR8_TX_DEEMPH_GEN1, 0 << 0); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, + IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB, 0 << 6); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, + IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB, 20 << 12); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, + IMX6Q_GPR8_TX_SWING_FULL, 127 << 18); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, + IMX6Q_GPR8_TX_SWING_LOW, 127 << 25); +} + +static void imx6_pcie_host_init(struct pcie_port *pp) +{ + int count = 0; + struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); + + imx6_pcie_assert_core_reset(pp); + + imx6_pcie_init_phy(pp); + + imx6_pcie_deassert_core_reset(pp); + + dw_pcie_setup_rc(pp); + + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6Q_GPR12_PCIE_CTL_2, 1 << 10); + + while (!dw_pcie_link_up(pp)) { + usleep_range(100, 1000); + count++; + if (count >= 10) { + dev_err(pp->dev, "phy link never came up\n"); + dev_dbg(pp->dev, + "DEBUG_R0: 0x%08x, DEBUG_R1: 0x%08x\n", + readl(pp->dbi_base + PCIE_PHY_DEBUG_R0), + readl(pp->dbi_base + PCIE_PHY_DEBUG_R1)); + break; + } + } + + return; +} + +static int imx6_pcie_link_up(struct pcie_port *pp) +{ + u32 rc, ltssm, rx_valid, temp; + + /* link is debug bit 36, debug register 1 starts at bit 32 */ + rc = readl(pp->dbi_base + PCIE_PHY_DEBUG_R1) & (0x1 << (36 - 32)); + if (rc) + return -EAGAIN; + + /* + * From L0, initiate MAC entry to gen2 if EP/RC supports gen2. + * Wait 2ms (LTSSM timeout is 24ms, PHY lock is ~5us in gen2). + * If (MAC/LTSSM.state == Recovery.RcvrLock) + * && (PHY/rx_valid==0) then pulse PHY/rx_reset. Transition + * to gen2 is stuck + */ + pcie_phy_read(pp->dbi_base, PCIE_PHY_RX_ASIC_OUT, &rx_valid); + ltssm = readl(pp->dbi_base + PCIE_PHY_DEBUG_R0) & 0x3F; + + if (rx_valid & 0x01) + return 0; + + if (ltssm != 0x0d) + return 0; + + dev_err(pp->dev, "transition to gen2 is stuck, reset PHY!\n"); + + pcie_phy_read(pp->dbi_base, + PHY_RX_OVRD_IN_LO, &temp); + temp |= (PHY_RX_OVRD_IN_LO_RX_DATA_EN + | PHY_RX_OVRD_IN_LO_RX_PLL_EN); + pcie_phy_write(pp->dbi_base, + PHY_RX_OVRD_IN_LO, temp); + + usleep_range(2000, 3000); + + pcie_phy_read(pp->dbi_base, + PHY_RX_OVRD_IN_LO, &temp); + temp &= ~(PHY_RX_OVRD_IN_LO_RX_DATA_EN + | PHY_RX_OVRD_IN_LO_RX_PLL_EN); + pcie_phy_write(pp->dbi_base, + PHY_RX_OVRD_IN_LO, temp); + + return 0; +} + +static struct pcie_host_ops imx6_pcie_host_ops = { + .link_up = imx6_pcie_link_up, + .host_init = imx6_pcie_host_init, +}; + +static int imx6_add_pcie_port(struct pcie_port *pp, + struct platform_device *pdev) +{ + int ret; + + pp->irq = platform_get_irq(pdev, 0); + if (!pp->irq) { + dev_err(&pdev->dev, "failed to get irq\n"); + return -ENODEV; + } + + pp->root_bus_nr = -1; + pp->ops = &imx6_pcie_host_ops; + + spin_lock_init(&pp->conf_lock); + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(&pdev->dev, "failed to initialize host\n"); + return ret; + } + + return 0; +} + +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; + int ret; + + imx6_pcie = devm_kzalloc(&pdev->dev, sizeof(*imx6_pcie), GFP_KERNEL); + if (!imx6_pcie) + return -ENOMEM; + + pp = &imx6_pcie->pp; + pp->dev = &pdev->dev; + + /* Added for PCI abort handling */ + hook_fault_code(16 + 6, imx6q_pcie_abort_handler, SIGBUS, 0, + "imprecise external abort"); + + dbi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!dbi_base) { + dev_err(&pdev->dev, "dbi_base memory resource not found\n"); + return -ENODEV; + } + + pp->dbi_base = devm_ioremap_resource(&pdev->dev, dbi_base); + if (IS_ERR(pp->dbi_base)) { + dev_err(&pdev->dev, "unable to remap dbi_base\n"); + ret = PTR_ERR(pp->dbi_base); + goto err; + } + + /* Fetch GPIOs */ + imx6_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); + if (!gpio_is_valid(imx6_pcie->reset_gpio)) { + dev_err(&pdev->dev, "no reset-gpio defined\n"); + ret = -ENODEV; + } + 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"); + goto err; + } + + imx6_pcie->power_on_gpio = of_get_named_gpio(np, "power-on-gpio", 0); + if (gpio_is_valid(imx6_pcie->power_on_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, + imx6_pcie->power_on_gpio, + GPIOF_OUT_INIT_LOW, + "PCIe power enable"); + if (ret) { + dev_err(&pdev->dev, "unable to get power-on gpio\n"); + goto err; + } + } + + imx6_pcie->wake_up_gpio = of_get_named_gpio(np, "wake-up-gpio", 0); + if (gpio_is_valid(imx6_pcie->wake_up_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, + imx6_pcie->wake_up_gpio, + GPIOF_IN, + "PCIe wake up"); + if (ret) { + dev_err(&pdev->dev, "unable to get wake-up gpio\n"); + goto err; + } + } + + imx6_pcie->disable_gpio = of_get_named_gpio(np, "disable-gpio", 0); + if (gpio_is_valid(imx6_pcie->disable_gpio)) { + ret = devm_gpio_request_one(&pdev->dev, + imx6_pcie->disable_gpio, + GPIOF_OUT_INIT_HIGH, + "PCIe disable endpoint"); + if (ret) { + dev_err(&pdev->dev, "unable to get disable-ep gpio\n"); + goto err; + } + } + + /* Fetch clocks */ + imx6_pcie->lvds_gate = devm_clk_get(&pdev->dev, "lvds_gate"); + if (IS_ERR(imx6_pcie->lvds_gate)) { + dev_err(&pdev->dev, + "lvds_gate clock select missing or invalid\n"); + ret = PTR_ERR(imx6_pcie->lvds_gate); + goto err; + } + + imx6_pcie->sata_ref_100m = devm_clk_get(&pdev->dev, "sata_ref_100m"); + if (IS_ERR(imx6_pcie->sata_ref_100m)) { + dev_err(&pdev->dev, + "sata_ref_100m clock source missing or invalid\n"); + ret = PTR_ERR(imx6_pcie->sata_ref_100m); + goto err; + } + + imx6_pcie->pcie_ref_125m = devm_clk_get(&pdev->dev, "pcie_ref_125m"); + if (IS_ERR(imx6_pcie->pcie_ref_125m)) { + dev_err(&pdev->dev, + "pcie_ref_125m clock source missing or invalid\n"); + ret = PTR_ERR(imx6_pcie->pcie_ref_125m); + goto err; + } + + imx6_pcie->pcie_axi = devm_clk_get(&pdev->dev, "pcie_axi"); + if (IS_ERR(imx6_pcie->pcie_axi)) { + dev_err(&pdev->dev, + "pcie_axi clock source missing or invalid\n"); + ret = PTR_ERR(imx6_pcie->pcie_axi); + goto err; + } + + /* Grab GPR config register range */ + imx6_pcie->iomuxc_gpr = + syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (IS_ERR(imx6_pcie->iomuxc_gpr)) { + dev_err(&pdev->dev, "unable to find iomuxc registers\n"); + ret = PTR_ERR(imx6_pcie->iomuxc_gpr); + goto err; + } + + ret = imx6_add_pcie_port(pp, pdev); + if (ret < 0) + goto err; + + platform_set_drvdata(pdev, imx6_pcie); + return 0; + +err: + return ret; +} + +static const struct of_device_id imx6_pcie_of_match[] = { + { .compatible = "fsl,imx6q-pcie", }, + {}, +}; +MODULE_DEVICE_TABLE(of, imx6_pcie_of_match); + +static struct platform_driver imx6_pcie_driver = { + .driver = { + .name = "imx6q-pcie", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(imx6_pcie_of_match), + }, +}; + +/* Freescale PCIe driver does not allow module unload */ + +static int __init imx6_pcie_init(void) +{ + return platform_driver_probe(&imx6_pcie_driver, imx6_pcie_probe); +} +module_init(imx6_pcie_init); + +MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>"); +MODULE_DESCRIPTION("Freescale i.MX6 PCIe host controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 2e9888a0635a..7c4f38dd42ba 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -408,7 +408,7 @@ static void __iomem *tegra_pcie_bus_map(struct tegra_pcie *pcie, list_for_each_entry(bus, &pcie->busses, list) if (bus->nr == busnr) - return bus->area->addr; + return (void __iomem *)bus->area->addr; bus = tegra_pcie_bus_alloc(pcie, busnr); if (IS_ERR(bus)) @@ -416,7 +416,7 @@ static void __iomem *tegra_pcie_bus_map(struct tegra_pcie *pcie, list_add_tail(&bus->list, &pcie->busses); - return bus->area->addr; + return (void __iomem *)bus->area->addr; } static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus, diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index c10e9ac9bbbc..896301788e9d 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -11,8 +11,11 @@ * published by the Free Software Foundation. */ +#include <linux/irq.h> +#include <linux/irqdomain.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/msi.h> #include <linux/of_address.h> #include <linux/pci.h> #include <linux/pci_regs.h> @@ -142,6 +145,204 @@ int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, return ret; } +static struct irq_chip dw_msi_irq_chip = { + .name = "PCI-MSI", + .irq_enable = unmask_msi_irq, + .irq_disable = mask_msi_irq, + .irq_mask = mask_msi_irq, + .irq_unmask = unmask_msi_irq, +}; + +/* MSI int handler */ +void dw_handle_msi_irq(struct pcie_port *pp) +{ + unsigned long val; + int i, pos; + + for (i = 0; i < MAX_MSI_CTRLS; i++) { + dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4, + (u32 *)&val); + if (val) { + pos = 0; + while ((pos = find_next_bit(&val, 32, pos)) != 32) { + generic_handle_irq(pp->msi_irq_start + + (i * 32) + pos); + pos++; + } + } + dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4, val); + } +} + +void dw_pcie_msi_init(struct pcie_port *pp) +{ + pp->msi_data = __get_free_pages(GFP_KERNEL, 0); + + /* program the msi_data */ + dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, + virt_to_phys((void *)pp->msi_data)); + dw_pcie_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4, 0); +} + +static int find_valid_pos0(struct pcie_port *pp, int msgvec, int pos, int *pos0) +{ + int flag = 1; + + do { + pos = find_next_zero_bit(pp->msi_irq_in_use, + MAX_MSI_IRQS, pos); + /*if you have reached to the end then get out from here.*/ + if (pos == MAX_MSI_IRQS) + return -ENOSPC; + /* + * Check if this position is at correct offset.nvec is always a + * power of two. pos0 must be nvec bit alligned. + */ + if (pos % msgvec) + pos += msgvec - (pos % msgvec); + else + flag = 0; + } while (flag); + + *pos0 = pos; + return 0; +} + +static int assign_irq(int no_irqs, struct msi_desc *desc, int *pos) +{ + int res, bit, irq, pos0, pos1, i; + u32 val; + struct pcie_port *pp = sys_to_pcie(desc->dev->bus->sysdata); + + if (!pp) { + BUG(); + return -EINVAL; + } + + pos0 = find_first_zero_bit(pp->msi_irq_in_use, + MAX_MSI_IRQS); + if (pos0 % no_irqs) { + if (find_valid_pos0(pp, no_irqs, pos0, &pos0)) + goto no_valid_irq; + } + if (no_irqs > 1) { + pos1 = find_next_bit(pp->msi_irq_in_use, + MAX_MSI_IRQS, pos0); + /* there must be nvec number of consecutive free bits */ + while ((pos1 - pos0) < no_irqs) { + if (find_valid_pos0(pp, no_irqs, pos1, &pos0)) + goto no_valid_irq; + pos1 = find_next_bit(pp->msi_irq_in_use, + MAX_MSI_IRQS, pos0); + } + } + + irq = (pp->msi_irq_start + pos0); + + if ((irq + no_irqs) > (pp->msi_irq_start + MAX_MSI_IRQS-1)) + goto no_valid_irq; + + i = 0; + while (i < no_irqs) { + set_bit(pos0 + i, pp->msi_irq_in_use); + irq_alloc_descs((irq + i), (irq + i), 1, 0); + irq_set_msi_desc(irq + i, desc); + /*Enable corresponding interrupt in MSI interrupt controller */ + res = ((pos0 + i) / 32) * 12; + bit = (pos0 + i) % 32; + dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val); + val |= 1 << bit; + dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val); + i++; + } + + *pos = pos0; + return irq; + +no_valid_irq: + *pos = pos0; + return -ENOSPC; +} + +static void clear_irq(unsigned int irq) +{ + int res, bit, val, pos; + struct irq_desc *desc; + struct msi_desc *msi; + struct pcie_port *pp; + + /* get the port structure */ + desc = irq_to_desc(irq); + msi = irq_desc_get_msi_desc(desc); + pp = sys_to_pcie(msi->dev->bus->sysdata); + if (!pp) { + BUG(); + return; + } + + pos = irq - pp->msi_irq_start; + + irq_free_desc(irq); + + clear_bit(pos, pp->msi_irq_in_use); + + /* Disable corresponding interrupt on MSI interrupt controller */ + res = (pos / 32) * 12; + bit = pos % 32; + dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, &val); + val &= ~(1 << bit); + dw_pcie_wr_own_conf(pp, PCIE_MSI_INTR0_ENABLE + res, 4, val); +} + +static int dw_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, + struct msi_desc *desc) +{ + int irq, pos, msgvec; + u16 msg_ctr; + struct msi_msg msg; + struct pcie_port *pp = sys_to_pcie(pdev->bus->sysdata); + + if (!pp) { + BUG(); + return -EINVAL; + } + + pci_read_config_word(pdev, desc->msi_attrib.pos+PCI_MSI_FLAGS, + &msg_ctr); + msgvec = (msg_ctr&PCI_MSI_FLAGS_QSIZE) >> 4; + if (msgvec == 0) + msgvec = (msg_ctr & PCI_MSI_FLAGS_QMASK) >> 1; + if (msgvec > 5) + msgvec = 0; + + irq = assign_irq((1 << msgvec), desc, &pos); + if (irq < 0) + return irq; + + msg_ctr &= ~PCI_MSI_FLAGS_QSIZE; + msg_ctr |= msgvec << 4; + pci_write_config_word(pdev, desc->msi_attrib.pos + PCI_MSI_FLAGS, + msg_ctr); + desc->msi_attrib.multiple = msgvec; + + msg.address_lo = virt_to_phys((void *)pp->msi_data); + msg.address_hi = 0x0; + msg.data = pos; + write_msi_msg(irq, &msg); + + return 0; +} + +static void dw_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) +{ + clear_irq(irq); +} + +static struct msi_chip dw_pcie_msi_chip = { + .setup_irq = dw_msi_setup_irq, + .teardown_irq = dw_msi_teardown_irq, +}; + int dw_pcie_link_up(struct pcie_port *pp) { if (pp->ops->link_up) @@ -150,6 +351,20 @@ int dw_pcie_link_up(struct pcie_port *pp) return 0; } +static int dw_pcie_msi_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &dw_msi_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); + set_irq_flags(irq, IRQF_VALID); + + return 0; +} + +static const struct irq_domain_ops msi_domain_ops = { + .map = dw_pcie_msi_map, +}; + int __init dw_pcie_host_init(struct pcie_port *pp) { struct device_node *np = pp->dev->of_node; @@ -157,6 +372,8 @@ int __init dw_pcie_host_init(struct pcie_port *pp) struct of_pci_range_parser parser; u32 val; + struct irq_domain *irq_domain; + if (of_pci_range_parser_init(&parser, np)) { dev_err(pp->dev, "missing ranges property\n"); return -EINVAL; @@ -223,6 +440,18 @@ int __init dw_pcie_host_init(struct pcie_port *pp) return -EINVAL; } + if (IS_ENABLED(CONFIG_PCI_MSI)) { + irq_domain = irq_domain_add_linear(pp->dev->of_node, + MAX_MSI_IRQS, &msi_domain_ops, + &dw_pcie_msi_chip); + if (!irq_domain) { + dev_err(pp->dev, "irq domain init failed\n"); + return -ENXIO; + } + + pp->msi_irq_start = irq_find_mapping(irq_domain, 0); + } + if (pp->ops->host_init) pp->ops->host_init(pp); @@ -485,10 +714,21 @@ int dw_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) return pp->irq; } +static void dw_pcie_add_bus(struct pci_bus *bus) +{ + if (IS_ENABLED(CONFIG_PCI_MSI)) { + struct pcie_port *pp = sys_to_pcie(bus->sysdata); + + dw_pcie_msi_chip.dev = pp->dev; + bus->msi = &dw_pcie_msi_chip; + } +} + static struct hw_pci dw_pci = { .setup = dw_pcie_setup, .scan = dw_pcie_scan_bus, .map_irq = dw_pcie_map_irq, + .add_bus = dw_pcie_add_bus, }; void dw_pcie_setup_rc(struct pcie_port *pp) diff --git a/drivers/pci/host/pcie-designware.h b/drivers/pci/host/pcie-designware.h index 133820f1da97..faccbbf31907 100644 --- a/drivers/pci/host/pcie-designware.h +++ b/drivers/pci/host/pcie-designware.h @@ -20,6 +20,14 @@ struct pcie_port_info { phys_addr_t mem_bus_addr; }; +/* + * Maximum number of MSI IRQs can be 256 per controller. But keep + * it 32 as of now. Probably we will never need more than 32. If needed, + * then increment it in multiple of 32. + */ +#define MAX_MSI_IRQS 32 +#define MAX_MSI_CTRLS (MAX_MSI_IRQS / 32) + struct pcie_port { struct device *dev; u8 root_bus_nr; @@ -38,6 +46,10 @@ struct pcie_port { int irq; u32 lanes; struct pcie_host_ops *ops; + int msi_irq; + int msi_irq_start; + unsigned long msi_data; + DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS); }; struct pcie_host_ops { @@ -57,6 +69,8 @@ int cfg_read(void __iomem *addr, int where, int size, u32 *val); int cfg_write(void __iomem *addr, int where, int size, u32 val); int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, u32 val); int dw_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val); +void dw_handle_msi_irq(struct pcie_port *pp); +void dw_pcie_msi_init(struct pcie_port *pp); int dw_pcie_link_up(struct pcie_port *pp); void dw_pcie_setup_rc(struct pcie_port *pp); int dw_pcie_host_init(struct pcie_port *pp); diff --git a/drivers/pci/hotplug/acpi_pcihp.c b/drivers/pci/hotplug/acpi_pcihp.c index 2a47e82821da..f8140164ec0b 100644 --- a/drivers/pci/hotplug/acpi_pcihp.c +++ b/drivers/pci/hotplug/acpi_pcihp.c @@ -338,7 +338,7 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev, u32 flags) acpi_handle chandle, handle; struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL }; - flags &= OSC_SHPC_NATIVE_HP_CONTROL; + flags &= OSC_PCI_SHPC_NATIVE_HP_CONTROL; if (!flags) { err("Invalid flags %u specified!\n", flags); return -EINVAL; diff --git a/drivers/pci/hotplug/shpchp.h b/drivers/pci/hotplug/shpchp.h index e260f207a90e..d876e4b3c6a9 100644 --- a/drivers/pci/hotplug/shpchp.h +++ b/drivers/pci/hotplug/shpchp.h @@ -191,7 +191,7 @@ static inline const char *slot_name(struct slot *slot) #include <linux/pci-acpi.h> static inline int get_hp_hw_control_from_firmware(struct pci_dev *dev) { - u32 flags = OSC_SHPC_NATIVE_HP_CONTROL; + u32 flags = OSC_PCI_SHPC_NATIVE_HP_CONTROL; return acpi_get_hp_hw_control_from_firmware(dev, flags); } #else diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 2372babe2fc5..5e14f5a51357 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -641,8 +641,7 @@ static void pci_set_bus_speed(struct pci_bus *bus) return; } - pos = pci_find_capability(bridge, PCI_CAP_ID_EXP); - if (pos) { + if (pci_is_pcie(bridge)) { u32 linkcap; u16 linksta; |