diff options
author | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2014-11-10 08:56:15 +0000 |
---|---|---|
committer | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2014-11-10 08:56:15 +0000 |
commit | 6b4ca0a3e63d133c89f9dc7b9e9de05d992440e8 (patch) | |
tree | 564bd4777dca39edad21307432d000c2e116b3ee | |
parent | 44125ddf2c16ae05848570842721155efb125bc7 (diff) | |
parent | 7b5268e16e5f4af7a58e17a89428eda2875f114e (diff) |
Merge branch 'tracking-qcomlt-pcie' into integration-linux-qcomlt
* tracking-qcomlt-pcie:
pci: qcom: move device init to subsys_initcall_sync
pci: qcom: add msi support
pci: qcom: Add support to external phy reference clk.
pci: qcom: remove static declaration of functions.
pci: qcom: Add regulator support
pci: qcom: move dt parsing code out of probe
pci: qcom: fix unused variable warning.
pci: qcom: fix a typo in reset gpio
PCI: qcom: Add support for pcie controllers on IPQ8064
-rw-r--r-- | arch/arm/mach-qcom/Kconfig | 2 | ||||
-rw-r--r-- | drivers/pci/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/pci/host/pci-qcom.c | 968 |
3 files changed, 971 insertions, 0 deletions
diff --git a/arch/arm/mach-qcom/Kconfig b/arch/arm/mach-qcom/Kconfig index ee5697ba05bc..3b5539cc907b 100644 --- a/arch/arm/mach-qcom/Kconfig +++ b/arch/arm/mach-qcom/Kconfig @@ -5,6 +5,8 @@ menuconfig ARCH_QCOM select ARM_AMBA select CLKSRC_OF select PINCTRL + select MIGHT_HAVE_PCI + select PCI_DOMAINS if PCI select QCOM_SCM if SMP help Support for Qualcomm's devicetree based systems. diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 26b3461d68d7..7225882bf4b4 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o obj-$(CONFIG_PCI_XGENE) += pci-xgene.o +obj-$(CONFIG_ARCH_QCOM) += pci-qcom.o diff --git a/drivers/pci/host/pci-qcom.c b/drivers/pci/host/pci-qcom.c new file mode 100644 index 000000000000..67d3a497a13a --- /dev/null +++ b/drivers/pci/host/pci-qcom.c @@ -0,0 +1,968 @@ +/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * QCOM MSM PCIe controller driver. + */ + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/of_gpio.h> +#include <linux/msi.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/of_address.h> +#include <linux/clk.h> +#include <linux/reset.h> +#include <linux/delay.h> + +#define INT_PCI_MSI_NR (8 * 32) +#define MSM_PCIE_MSI_PHY 0xa0000000 + +#define PCIE20_MSI_CTRL_ADDR (0x820) +#define PCIE20_MSI_CTRL_UPPER_ADDR (0x824) +#define PCIE20_MSI_CTRL_INTR_EN (0x828) +#define PCIE20_MSI_CTRL_INTR_MASK (0x82C) +#define PCIE20_MSI_CTRL_INTR_STATUS (0x830) + +#define PCIE20_MSI_CTRL_MAX 8 +/* Root Complex Port vendor/device IDs */ +#define PCIE_VENDOR_ID_RCP 0x17cb +#define PCIE_DEVICE_ID_RCP 0x0101 + +#define __set(v, a, b) (((v) << (b)) & GENMASK(a, b)) + +#define PCIE20_PARF_PCS_DEEMPH 0x34 +#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN1(x) __set(x, 21, 16) +#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(x) __set(x, 13, 8) +#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(x) __set(x, 5, 0) + +#define PCIE20_PARF_PCS_SWING 0x38 +#define PCIE20_PARF_PCS_SWING_TX_SWING_FULL(x) __set(x, 14, 8) +#define PCIE20_PARF_PCS_SWING_TX_SWING_LOW(x) __set(x, 6, 0) + +#define PCIE20_PARF_PHY_CTRL 0x40 +#define PCIE20_PARF_PHY_CTRL_PHY_TX0_TERM_OFFST(x) __set(x, 20, 16) +#define PCIE20_PARF_PHY_CTRL_PHY_LOS_LEVEL(x) __set(x, 12, 8) +#define PCIE20_PARF_PHY_CTRL_PHY_RTUNE_REQ (1 << 4) +#define PCIE20_PARF_PHY_CTRL_PHY_TEST_BURNIN (1 << 2) +#define PCIE20_PARF_PHY_CTRL_PHY_TEST_BYPASS (1 << 1) +#define PCIE20_PARF_PHY_CTRL_PHY_TEST_PWR_DOWN (1 << 0) + +#define PCIE20_PARF_PHY_REFCLK 0x4C +#define PCIE20_PARF_CONFIG_BITS 0x50 + +#define PCIE20_ELBI_SYS_CTRL 0x04 +#define PCIE20_ELBI_SYS_CTRL_LTSSM_EN 0x01 + +#define PCIE20_CAP 0x70 +#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) + +#define PCIE20_COMMAND_STATUS 0x04 +#define PCIE20_BUSNUMBERS 0x18 +#define PCIE20_MEMORY_BASE_LIMIT 0x20 + +#define PCIE20_AXI_MSTR_RESP_COMP_CTRL0 0x818 +#define PCIE20_AXI_MSTR_RESP_COMP_CTRL1 0x81c +#define PCIE20_PLR_IATU_VIEWPORT 0x900 +#define PCIE20_PLR_IATU_CTRL1 0x904 +#define PCIE20_PLR_IATU_CTRL2 0x908 +#define PCIE20_PLR_IATU_LBAR 0x90C +#define PCIE20_PLR_IATU_UBAR 0x910 +#define PCIE20_PLR_IATU_LAR 0x914 +#define PCIE20_PLR_IATU_LTAR 0x918 +#define PCIE20_PLR_IATU_UTAR 0x91c + +#define MSM_PCIE_DEV_CFG_ADDR 0x01000000 + +#define RD 0 +#define WR 1 + +#define MAX_RC_NUM 3 +#define PCIE_BUS_PRIV_DATA(pdev) \ + (((struct pci_sys_data *)pdev->bus->sysdata)->private_data) + +/* PCIe TLP types that we are interested in */ +#define PCI_CFG0_RDWR 0x4 +#define PCI_CFG1_RDWR 0x5 + +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \ +({ \ + unsigned long timeout = jiffies + usecs_to_jiffies(timeout_us); \ + might_sleep_if(timeout_us); \ + for (;;) { \ + (val) = readl(addr); \ + if (cond) \ + break; \ + if (timeout_us && time_after(jiffies, timeout)) { \ + (val) = readl(addr); \ + break; \ + } \ + if (sleep_us) \ + usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \ + } \ + (cond) ? 0 : -ETIMEDOUT; \ +}) + +struct qcom_msi { + struct msi_chip chip; + DECLARE_BITMAP(used, INT_PCI_MSI_NR); + struct irq_domain *domain; + unsigned long pages; + struct mutex lock; + int irq; +}; + +struct qcom_pcie { + void __iomem *elbi_base; + void __iomem *parf_base; + void __iomem *dwc_base; + void __iomem *cfg_base; + struct device *dev; + int reset_gpio; + bool ext_phy_ref_clk; + struct clk *iface_clk; + struct clk *bus_clk; + struct clk *phy_clk; + int irq_int[4]; + struct reset_control *axi_reset; + struct reset_control *ahb_reset; + struct reset_control *por_reset; + struct reset_control *pci_reset; + struct reset_control *phy_reset; + + struct resource conf; + struct resource io; + struct resource mem; + + struct regulator *vdd_supply; + struct regulator *avdd_supply; + struct regulator *pcie_clk_supply; + + struct qcom_msi msi; +}; + +static int nr_controllers; +static DEFINE_SPINLOCK(qcom_hw_pci_lock); + +static inline struct qcom_pcie *sys_to_pcie(struct pci_sys_data *sys) +{ + return sys->private_data; +} + +static void qcom_pcie_add_bus(struct pci_bus *bus) +{ + if (IS_ENABLED(CONFIG_PCI_MSI)) { + struct qcom_pcie *pcie = sys_to_pcie(bus->sysdata); + + bus->msi = &pcie->msi.chip; + } +} + +inline int is_msm_pcie_rc(struct pci_bus *bus) +{ + return (bus->number == 0); +} + +static int qcom_pcie_is_link_up(struct qcom_pcie *dev) +{ + return readl_relaxed(dev->dwc_base + PCIE20_CAP_LINKCTRLSTATUS) & + BIT(29); +} + +inline int msm_pcie_get_cfgtype(struct pci_bus *bus) +{ + /* + * http://www.tldp.org/LDP/tlk/dd/pci.html + * Pass it onto the secondary bus interface unchanged if the + * bus number specified is greater than the secondary bus + * number and less than or equal to the subordinate bus + * number. + * + * Read/Write to the RC and Device/Switch connected to the RC + * are CFG0 type transactions. Rest have to be forwarded + * down stream as CFG1 transactions. + * + */ + if (bus->number == 0) + return PCI_CFG0_RDWR; + + return PCI_CFG0_RDWR; +} + +void msm_pcie_config_cfgtype(struct pci_bus *bus, u32 devfn) +{ + uint32_t bdf, cfgtype; + struct qcom_pcie *dev = sys_to_pcie(bus->sysdata); + + cfgtype = msm_pcie_get_cfgtype(bus); + + if (cfgtype == PCI_CFG0_RDWR) { + bdf = MSM_PCIE_DEV_CFG_ADDR; + } else { + /* + * iATU Lower Target Address Register + * Bits Description + * *-1:0 Forms bits [*:0] of the + * start address of the new + * address of the translated + * region. The start address + * must be aligned to a + * CX_ATU_MIN_REGION_SIZE kB + * boundary, so these bits are + * always 0. A write to this + * location is ignored by the + * PCIe core. + * 31:*1 Forms bits [31:*] of the of + * the new address of the + * translated region. + * + * * is log2(CX_ATU_MIN_REGION_SIZE) + */ + bdf = (((bus->number & 0xff) << 24) & 0xff000000) | + (((devfn & 0xff) << 16) & 0x00ff0000); + } + + writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_VIEWPORT); + wmb(); + + /* Program Bdf Address */ + writel_relaxed(bdf, dev->dwc_base + PCIE20_PLR_IATU_LTAR); + wmb(); + + /* Write Config Request Type */ + writel_relaxed(cfgtype, dev->dwc_base + PCIE20_PLR_IATU_CTRL1); + wmb(); +} + +static inline int msm_pcie_oper_conf(struct pci_bus *bus, u32 devfn, int oper, + int where, int size, u32 *val) +{ + uint32_t word_offset, byte_offset, mask; + uint32_t rd_val, wr_val; + struct qcom_pcie *dev = sys_to_pcie(bus->sysdata); + void __iomem *config_base; + int rc; + + rc = is_msm_pcie_rc(bus); + + /* + * For downstream bus, make sure link is up + */ + if (rc && (devfn != 0)) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } else if ((!rc) && (!qcom_pcie_is_link_up(dev))) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + msm_pcie_config_cfgtype(bus, devfn); + + word_offset = where & ~0x3; + byte_offset = where & 0x3; + mask = (~0 >> (8 * (4 - size))) << (8 * byte_offset); + + config_base = (rc) ? dev->dwc_base : dev->cfg_base; + rd_val = readl_relaxed(config_base + word_offset); + + if (oper == RD) { + *val = ((rd_val & mask) >> (8 * byte_offset)); + } else { + wr_val = (rd_val & ~mask) | + ((*val << (8 * byte_offset)) & mask); + writel_relaxed(wr_val, config_base + word_offset); + wmb(); /* ensure config data is written to hardware register */ + } + + return 0; +} + +static int msm_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where, + int size, u32 *val) +{ + return msm_pcie_oper_conf(bus, devfn, RD, where, size, val); +} + +static int msm_pcie_wr_conf(struct pci_bus *bus, u32 devfn, + int where, int size, u32 val) +{ + /* + *Attempt to reset secondary bus is causing PCIE core to reset. + *Disable secondary bus reset functionality. + */ + if ((bus->number == 0) && (where == PCI_BRIDGE_CONTROL) && + (val & PCI_BRIDGE_CTL_BUS_RESET)) { + pr_info("PCIE secondary bus reset not supported\n"); + val &= ~PCI_BRIDGE_CTL_BUS_RESET; + } + + return msm_pcie_oper_conf(bus, devfn, WR, where, size, &val); +} + +static int qcom_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) +{ + struct qcom_pcie *pcie_dev = PCIE_BUS_PRIV_DATA(dev); + + return pcie_dev->irq_int[pin-1]; +} + +static int qcom_pcie_setup(int nr, struct pci_sys_data *sys) +{ + struct qcom_pcie *qcom_pcie = sys->private_data; + + /* + * specify linux PCI framework to allocate device memory (BARs) + * from msm_pcie_dev.dev_mem_res resource. + */ + sys->mem_offset = 0; + sys->io_offset = 0; + + pci_add_resource(&sys->resources, &qcom_pcie->mem); + pci_add_resource(&sys->resources, &qcom_pcie->io); + + return 1; +} + +static struct pci_ops qcom_pcie_ops = { + .read = msm_pcie_rd_conf, + .write = msm_pcie_wr_conf, +}; + +static struct hw_pci qcom_hw_pci[MAX_RC_NUM] = { + { +#ifdef CONFIG_PCI_DOMAINS + .domain = 0, +#endif + .ops = &qcom_pcie_ops, + .nr_controllers = 1, + .swizzle = pci_common_swizzle, + .setup = qcom_pcie_setup, + .map_irq = qcom_pcie_map_irq, + .add_bus = qcom_pcie_add_bus, + }, + { +#ifdef CONFIG_PCI_DOMAINS + .domain = 1, +#endif + .ops = &qcom_pcie_ops, + .nr_controllers = 1, + .swizzle = pci_common_swizzle, + .setup = qcom_pcie_setup, + .map_irq = qcom_pcie_map_irq, + }, + { +#ifdef CONFIG_PCI_DOMAINS + .domain = 2, +#endif + .ops = &qcom_pcie_ops, + .nr_controllers = 1, + .swizzle = pci_common_swizzle, + .setup = qcom_pcie_setup, + .map_irq = qcom_pcie_map_irq, + }, +}; + +static inline void qcom_elbi_writel_relaxed(struct qcom_pcie *pcie, + u32 val, u32 reg) +{ + writel_relaxed(val, pcie->elbi_base + reg); +} + +static inline u32 qcom_elbi_readl_relaxed(struct qcom_pcie *pcie, u32 reg) +{ + return readl_relaxed(pcie->elbi_base + reg); +} + +static inline void qcom_parf_writel_relaxed(struct qcom_pcie *pcie, + u32 val, u32 reg) +{ + writel_relaxed(val, pcie->parf_base + reg); +} + +static inline u32 qcom_parf_readl_relaxed(struct qcom_pcie *pcie, u32 reg) +{ + return readl_relaxed(pcie->parf_base + reg); +} + +static void msm_pcie_write_mask(void __iomem *addr, + uint32_t clear_mask, uint32_t set_mask) +{ + uint32_t val; + + val = (readl_relaxed(addr) & ~clear_mask) | set_mask; + writel_relaxed(val, addr); + wmb(); /* ensure data is written to hardware register */ +} + +static void qcom_pcie_config_controller(struct qcom_pcie *dev) +{ + /* + * program and enable address translation region 0 (device config + * address space); region type config; + * axi config address range to device config address range + */ + writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_VIEWPORT); + /* ensure that hardware locks the region before programming it */ + wmb(); + + writel_relaxed(4, dev->dwc_base + PCIE20_PLR_IATU_CTRL1); + writel_relaxed(BIT(31), dev->dwc_base + PCIE20_PLR_IATU_CTRL2); + writel_relaxed(dev->conf.start, dev->dwc_base + PCIE20_PLR_IATU_LBAR); + writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UBAR); + writel_relaxed(dev->conf.end, dev->dwc_base + PCIE20_PLR_IATU_LAR); + writel_relaxed(MSM_PCIE_DEV_CFG_ADDR, + dev->dwc_base + PCIE20_PLR_IATU_LTAR); + writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UTAR); + /* ensure that hardware registers the configuration */ + wmb(); + + /* + * program and enable address translation region 2 (device resource + * address space); region type memory; + * axi device bar address range to device bar address range + */ + writel_relaxed(2, dev->dwc_base + PCIE20_PLR_IATU_VIEWPORT); + /* ensure that hardware locks the region before programming it */ + wmb(); + + writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_CTRL1); + writel_relaxed(BIT(31), dev->dwc_base + PCIE20_PLR_IATU_CTRL2); + writel_relaxed(dev->mem.start, dev->dwc_base + PCIE20_PLR_IATU_LBAR); + writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UBAR); + writel_relaxed(dev->mem.end, dev->dwc_base + PCIE20_PLR_IATU_LAR); + writel_relaxed(dev->mem.start, + dev->dwc_base + PCIE20_PLR_IATU_LTAR); + writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UTAR); + /* ensure that hardware registers the configuration */ + wmb(); + + /* 1K PCIE buffer setting */ + writel_relaxed(0x3, dev->dwc_base + PCIE20_AXI_MSTR_RESP_COMP_CTRL0); + writel_relaxed(0x1, dev->dwc_base + PCIE20_AXI_MSTR_RESP_COMP_CTRL1); + /* ensure that hardware registers the configuration */ + wmb(); +} + +static int qcom_msi_alloc(struct qcom_msi *chip) +{ + int msi; + + mutex_lock(&chip->lock); + + msi = find_first_zero_bit(chip->used, INT_PCI_MSI_NR); + if (msi < INT_PCI_MSI_NR) + set_bit(msi, chip->used); + else + msi = -ENOSPC; + + mutex_unlock(&chip->lock); + + return msi; +} + +static void qcom_msi_free(struct qcom_msi *chip, unsigned long irq) +{ + struct device *dev = chip->chip.dev; + + mutex_lock(&chip->lock); + + if (!test_bit(irq, chip->used)) + dev_err(dev, "trying to free unused MSI#%lu\n", irq); + else + clear_bit(irq, chip->used); + + mutex_unlock(&chip->lock); +} + + +static irqreturn_t handle_msi_irq(int irq, void *data) +{ + int i, j, index; + unsigned long val; + struct qcom_pcie *dev = data; + void __iomem *ctrl_status; + struct qcom_msi *msi = &dev->msi; + + /* check for set bits, clear it by setting that bit + and trigger corresponding irq */ + for (i = 0; i < PCIE20_MSI_CTRL_MAX; i++) { + ctrl_status = dev->dwc_base + + PCIE20_MSI_CTRL_INTR_STATUS + (i * 12); + + val = readl_relaxed(ctrl_status); + while (val) { + j = find_first_bit(&val, 32); + index = j + (32 * i); + writel_relaxed(BIT(j), ctrl_status); + /* ensure that interrupt is cleared (acked) */ + wmb(); + + irq = irq_find_mapping(msi->domain, index); + if (irq) { + if (test_bit(index, msi->used)) + generic_handle_irq(irq); + else + dev_info(dev->dev, "unhandled MSI\n"); + } + val = readl_relaxed(ctrl_status); + } + } + + return IRQ_HANDLED; +} + +static inline struct qcom_msi *to_qcom_msi(struct msi_chip *chip) +{ + return container_of(chip, struct qcom_msi, chip); +} + +static int qcom_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, + struct msi_desc *desc) +{ + struct qcom_msi *msi = to_qcom_msi(chip); + struct msi_msg msg; + unsigned int irq; + int hwirq; + + hwirq = qcom_msi_alloc(msi); + if (hwirq < 0) + return hwirq; + + irq = irq_create_mapping(msi->domain, hwirq); + if (!irq) + return -EINVAL; + + irq_set_msi_desc(irq, desc); + + msg.address_lo = MSM_PCIE_MSI_PHY; + /* 32 bit address only */ + msg.address_hi = 0; + msg.data = hwirq; + + write_msi_msg(irq, &msg); + + return 0; +} + +static void qcom_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) +{ + struct qcom_msi *msi = to_qcom_msi(chip); + struct irq_data *d = irq_get_irq_data(irq); + + qcom_msi_free(msi, d->hwirq); +} + +static struct irq_chip qcom_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, +}; + + +static int qcom_pcie_msi_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &qcom_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 = qcom_pcie_msi_map, +}; +uint32_t msm_pcie_msi_init(struct qcom_pcie *pcie, struct platform_device *pdev) +{ + int i, rc; + struct qcom_msi *msi = &pcie->msi; + int err; + + mutex_init(&msi->lock); + + msi->chip.dev = pcie->dev; + msi->chip.setup_irq = qcom_msi_setup_irq; + msi->chip.teardown_irq = qcom_msi_teardown_irq; + msi->domain = irq_domain_add_linear(pdev->dev.of_node, INT_PCI_MSI_NR, + &msi_domain_ops, &msi->chip); + if (!msi->domain) { + dev_err(&pdev->dev, "failed to create IRQ domain\n"); + return -ENOMEM; + } + + + err = platform_get_irq_byname(pdev, "msi"); + if (err < 0) { + dev_err(&pdev->dev, "failed to get IRQ: %d\n", err); + return err; + } + + msi->irq = err; + + /* program MSI controller and enable all interrupts */ + writel_relaxed(MSM_PCIE_MSI_PHY, pcie->dwc_base + PCIE20_MSI_CTRL_ADDR); + writel_relaxed(0, pcie->dwc_base + PCIE20_MSI_CTRL_UPPER_ADDR); + + for (i = 0; i < PCIE20_MSI_CTRL_MAX; i++) + writel_relaxed(~0, pcie->dwc_base + + PCIE20_MSI_CTRL_INTR_EN + (i * 12)); + + /* ensure that hardware is configured before proceeding */ + wmb(); + + /* register handler for physical MSI interrupt line */ + rc = request_irq(msi->irq, handle_msi_irq, IRQF_TRIGGER_RISING, + "msm_pcie_msi", pcie); + if (rc) { + pr_err("Unable to allocate msi interrupt\n"); + return rc; + } + + return rc; +} + +static int qcom_pcie_vreg_on(struct qcom_pcie *qcom_pcie) +{ + int err; + /* enable regulators */ + err = regulator_enable(qcom_pcie->vdd_supply); + if (err < 0) { + dev_err(qcom_pcie->dev, "failed to enable VDD regulator\n"); + return err; + } + + err = regulator_enable(qcom_pcie->pcie_clk_supply); + if (err < 0) { + dev_err(qcom_pcie->dev, "failed to enable pcie-clk regulator\n"); + return err; + } + + err = regulator_enable(qcom_pcie->avdd_supply); + if (err < 0) { + dev_err(qcom_pcie->dev, "failed to enable AVDD regulator\n"); + return err; + } + + return err; + +} + +static int qcom_pcie_parse_dt(struct qcom_pcie *qcom_pcie, + struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *elbi_base, *parf_base, *dwc_base; + struct of_pci_range range; + struct of_pci_range_parser parser; + int ret, i; + + qcom_pcie->ext_phy_ref_clk = of_property_read_bool(np, + "qcom,external-phy-refclk"); + + elbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); + qcom_pcie->elbi_base = devm_ioremap_resource(&pdev->dev, elbi_base); + if (IS_ERR(qcom_pcie->elbi_base)) { + dev_err(&pdev->dev, "Failed to ioremap elbi space\n"); + return PTR_ERR(qcom_pcie->elbi_base); + } + + parf_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf"); + qcom_pcie->parf_base = devm_ioremap_resource(&pdev->dev, parf_base); + if (IS_ERR(qcom_pcie->parf_base)) { + dev_err(&pdev->dev, "Failed to ioremap parf space\n"); + return PTR_ERR(qcom_pcie->parf_base); + } + + dwc_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "base"); + qcom_pcie->dwc_base = devm_ioremap_resource(&pdev->dev, dwc_base); + if (IS_ERR(qcom_pcie->dwc_base)) { + dev_err(&pdev->dev, "Failed to ioremap dwc_base space\n"); + return PTR_ERR(qcom_pcie->dwc_base); + } + + if (of_pci_range_parser_init(&parser, np)) { + dev_err(&pdev->dev, "missing ranges property\n"); + return -EINVAL; + } + + /* Get the I/O and memory ranges from DT */ + for_each_of_pci_range(&parser, &range) { + switch (range.pci_space & 0x3) { + case 0: /* cfg */ + of_pci_range_to_resource(&range, np, &qcom_pcie->conf); + qcom_pcie->conf.flags = IORESOURCE_MEM; + break; + case 1: /* io */ + of_pci_range_to_resource(&range, np, &qcom_pcie->io); + break; + default: /* mem */ + of_pci_range_to_resource(&range, np, &qcom_pcie->mem); + break; + } + } + + qcom_pcie->vdd_supply = devm_regulator_get(&pdev->dev, "vdd"); + if (IS_ERR(qcom_pcie->vdd_supply)) { + dev_err(&pdev->dev, "Failed to get vdd supply\n"); + return PTR_ERR(qcom_pcie->vdd_supply); + } + + qcom_pcie->pcie_clk_supply = devm_regulator_get(&pdev->dev, "pcie-clk"); + if (IS_ERR(qcom_pcie->pcie_clk_supply)) { + dev_err(&pdev->dev, "Failed to get pcie clk supply\n"); + return PTR_ERR(qcom_pcie->pcie_clk_supply); + } + qcom_pcie->avdd_supply = devm_regulator_get(&pdev->dev, "avdd"); + if (IS_ERR(qcom_pcie->avdd_supply)) { + dev_err(&pdev->dev, "Failed to get avdd supply\n"); + return PTR_ERR(qcom_pcie->avdd_supply); + } + + + qcom_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); + if (!gpio_is_valid(qcom_pcie->reset_gpio)) { + dev_err(&pdev->dev, "pcie reset gpio is not valid\n"); + return -EINVAL; + } + + ret = devm_gpio_request_one(&pdev->dev, qcom_pcie->reset_gpio, + GPIOF_DIR_OUT, "pcie_reset"); + if (ret) { + dev_err(&pdev->dev, "Failed to request pcie reset gpio\n"); + return ret; + } + + qcom_pcie->iface_clk = devm_clk_get(&pdev->dev, "iface"); + if (IS_ERR(qcom_pcie->iface_clk)) { + dev_err(&pdev->dev, "Failed to get pcie iface clock\n"); + return PTR_ERR(qcom_pcie->iface_clk); + } + + qcom_pcie->phy_clk = devm_clk_get(&pdev->dev, "phy"); + if (IS_ERR(qcom_pcie->phy_clk)) { + dev_err(&pdev->dev, "Failed to get pcie phy clock\n"); + return PTR_ERR(qcom_pcie->phy_clk); + } + + qcom_pcie->bus_clk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(qcom_pcie->bus_clk)) { + dev_err(&pdev->dev, "Failed to get pcie core clock\n"); + return PTR_ERR(qcom_pcie->bus_clk); + } + + qcom_pcie->axi_reset = devm_reset_control_get(&pdev->dev, "axi"); + if (IS_ERR(qcom_pcie->axi_reset)) { + dev_err(&pdev->dev, "Failed to get axi reset\n"); + return PTR_ERR(qcom_pcie->axi_reset); + } + + qcom_pcie->ahb_reset = devm_reset_control_get(&pdev->dev, "ahb"); + if (IS_ERR(qcom_pcie->ahb_reset)) { + dev_err(&pdev->dev, "Failed to get ahb reset\n"); + return PTR_ERR(qcom_pcie->ahb_reset); + } + + qcom_pcie->por_reset = devm_reset_control_get(&pdev->dev, "por"); + if (IS_ERR(qcom_pcie->por_reset)) { + dev_err(&pdev->dev, "Failed to get por reset\n"); + return PTR_ERR(qcom_pcie->por_reset); + } + + qcom_pcie->pci_reset = devm_reset_control_get(&pdev->dev, "pci"); + if (IS_ERR(qcom_pcie->pci_reset)) { + dev_err(&pdev->dev, "Failed to get pci reset\n"); + return PTR_ERR(qcom_pcie->pci_reset); + } + + qcom_pcie->phy_reset = devm_reset_control_get(&pdev->dev, "phy"); + if (IS_ERR(qcom_pcie->phy_reset)) { + dev_err(&pdev->dev, "Failed to get phy reset\n"); + return PTR_ERR(qcom_pcie->phy_reset); + } + + for (i = 0; i < 4; i++) { + qcom_pcie->irq_int[i] = platform_get_irq(pdev, i+1); + if (qcom_pcie->irq_int[i] < 0) { + dev_err(&pdev->dev, "failed to get irq resource\n"); + return qcom_pcie->irq_int[i]; + } + } + + return 0; +} + +static int qcom_pcie_probe(struct platform_device *pdev) +{ + unsigned long flags; + struct qcom_pcie *qcom_pcie; + struct hw_pci *hw; + int ret; + u32 val; + + qcom_pcie = devm_kzalloc(&pdev->dev, sizeof(*qcom_pcie), GFP_KERNEL); + if (!qcom_pcie) { + dev_err(&pdev->dev, "no memory for qcom_pcie\n"); + return -ENOMEM; + } + qcom_pcie->dev = &pdev->dev; + + ret = qcom_pcie_parse_dt(qcom_pcie, pdev); + if (IS_ERR_VALUE(ret)) + return ret; + + qcom_pcie->cfg_base = devm_ioremap_resource(&pdev->dev, + &qcom_pcie->conf); + if (IS_ERR(qcom_pcie->cfg_base)) { + dev_err(&pdev->dev, "Failed to ioremap PCIe cfg space\n"); + return PTR_ERR(qcom_pcie->cfg_base); + } + + gpio_set_value(qcom_pcie->reset_gpio, 0); + usleep_range(10000, 15000); + + /* enable power */ + qcom_pcie_vreg_on(qcom_pcie); + /* assert PCIe PARF reset while powering the core */ + reset_control_assert(qcom_pcie->ahb_reset); + + /* enable clocks */ + ret = clk_prepare_enable(qcom_pcie->iface_clk); + if (ret) + return ret; + ret = clk_prepare_enable(qcom_pcie->phy_clk); + if (ret) + return ret; + ret = clk_prepare_enable(qcom_pcie->bus_clk); + if (ret) + return ret; + + /* + * de-assert PCIe PARF reset; + * wait 1us before accessing PARF registers + */ + reset_control_deassert(qcom_pcie->ahb_reset); + udelay(1); + + /* enable PCIe clocks and resets */ + msm_pcie_write_mask(qcom_pcie->parf_base + PCIE20_PARF_PHY_CTRL, + BIT(0), 0); + + /* Set Tx Termination Offset */ + val = qcom_parf_readl_relaxed(qcom_pcie, PCIE20_PARF_PHY_CTRL); + val |= PCIE20_PARF_PHY_CTRL_PHY_TX0_TERM_OFFST(7); + qcom_parf_writel_relaxed(qcom_pcie, val, PCIE20_PARF_PHY_CTRL); + + /* PARF programming */ + qcom_parf_writel_relaxed(qcom_pcie, + PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN1(0x18) | + PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(0x18) | + PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(0x22), + PCIE20_PARF_PCS_DEEMPH); + qcom_parf_writel_relaxed(qcom_pcie, + PCIE20_PARF_PCS_SWING_TX_SWING_FULL(0x78) | + PCIE20_PARF_PCS_SWING_TX_SWING_LOW(0x78), + PCIE20_PARF_PCS_SWING); + qcom_parf_writel_relaxed(qcom_pcie, (4<<24), PCIE20_PARF_CONFIG_BITS); + /* ensure that hardware registers the PARF configuration */ + wmb(); + + /* enable reference clock */ + msm_pcie_write_mask(qcom_pcie->parf_base + PCIE20_PARF_PHY_REFCLK, + qcom_pcie->ext_phy_ref_clk ? 0 : BIT(12), + BIT(16)); + + /* ensure that access is enabled before proceeding */ + wmb(); + + /* de-assert PICe PHY, Core, POR and AXI clk domain resets */ + reset_control_deassert(qcom_pcie->phy_reset); + reset_control_deassert(qcom_pcie->pci_reset); + reset_control_deassert(qcom_pcie->por_reset); + reset_control_deassert(qcom_pcie->axi_reset); + + /* wait 150ms for clock acquisition */ + usleep_range(10000, 15000); + + /* de-assert PCIe reset link to bring EP out of reset */ + gpio_set_value(qcom_pcie->reset_gpio, 1); + usleep_range(10000, 15000); + + /* enable link training */ + val = qcom_elbi_readl_relaxed(qcom_pcie, PCIE20_ELBI_SYS_CTRL); + val |= PCIE20_ELBI_SYS_CTRL_LTSSM_EN; + qcom_elbi_writel_relaxed(qcom_pcie, val, PCIE20_ELBI_SYS_CTRL); + wmb(); + + /* poll for link to come up for upto 100ms */ + ret = readl_poll_timeout( + (qcom_pcie->dwc_base + PCIE20_CAP_LINKCTRLSTATUS), + val, (val & BIT(29)), 10000, 100000); + + dev_info(&pdev->dev, "link initialized %d\n", ret); + + qcom_pcie_config_controller(qcom_pcie); + + platform_set_drvdata(pdev, qcom_pcie); + + spin_lock_irqsave(&qcom_hw_pci_lock, flags); + qcom_hw_pci[nr_controllers].private_data = (void **)&qcom_pcie; + hw = &qcom_hw_pci[nr_controllers]; + nr_controllers++; + spin_unlock_irqrestore(&qcom_hw_pci_lock, flags); + + pci_common_init(hw); + + msm_pcie_msi_init(qcom_pcie, pdev); + return 0; +} + +static int __exit qcom_pcie_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct of_device_id qcom_pcie_match[] = { + { .compatible = "qcom,pcie-ipq8064", }, + {} +}; + +static struct platform_driver qcom_pcie_driver = { + .probe = qcom_pcie_probe, + .remove = qcom_pcie_remove, + .driver = { + .name = "qcom_pcie", + .owner = THIS_MODULE, + .of_match_table = qcom_pcie_match, + }, +}; + +static int qcom_pcie_init(void) +{ + return platform_driver_register(&qcom_pcie_driver); +} +subsys_initcall_sync(qcom_pcie_init); + +/* RC do not represent the right class; set it to PCI_CLASS_BRIDGE_PCI */ +static void msm_pcie_fixup_early(struct pci_dev *dev) +{ + if (dev->hdr_type == 1) + dev->class = (dev->class & 0xff) | (PCI_CLASS_BRIDGE_PCI << 8); +} +DECLARE_PCI_FIXUP_EARLY(PCIE_VENDOR_ID_RCP, PCIE_DEVICE_ID_RCP, msm_pcie_fixup_early); |