diff --git a/Documentation/devicetree/bindings/pci/hisilicon-pcie.txt b/Documentation/devicetree/bindings/pci/hisilicon-pcie.txt index 17c6ed9c6059f50b1959fbdc408fa14b172467a6..b721beacfe4dae6c0bff3f429365080e46043d4a 100644 --- a/Documentation/devicetree/bindings/pci/hisilicon-pcie.txt +++ b/Documentation/devicetree/bindings/pci/hisilicon-pcie.txt @@ -1,4 +1,4 @@ -HiSilicon PCIe host bridge DT description +HiSilicon Hip05 and Hip06 PCIe host bridge DT description HiSilicon PCIe host controller is based on Designware PCI core. It shares common functions with PCIe Designware core driver and inherits @@ -7,8 +7,8 @@ Documentation/devicetree/bindings/pci/designware-pci.txt. Additional properties are described here: -Required properties: -- compatible: Should contain "hisilicon,hip05-pcie". +Required properties +- compatible: Should contain "hisilicon,hip05-pcie" or "hisilicon,hip06-pcie". - reg: Should contain rc_dbi, config registers location and length. - reg-names: Must include the following entries: "rc_dbi": controller configuration registers; @@ -20,7 +20,7 @@ Optional properties: - status: Either "ok" or "disabled". - dma-coherent: Present if DMA operations are coherent. -Example: +Hip05 Example (note that Hip06 is the same except compatible): pcie@0xb0080000 { compatible = "hisilicon,hip05-pcie", "snps,dw-pcie"; reg = <0 0xb0080000 0 0x10000>, <0x220 0x00000000 0 0x2000>; diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie.txt b/Documentation/devicetree/bindings/pci/qcom,pcie.txt new file mode 100644 index 0000000000000000000000000000000000000000..4059a6f89bc108ae42efb7a54c08e7a18d2d0936 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/qcom,pcie.txt @@ -0,0 +1,233 @@ +* Qualcomm PCI express root complex + +- compatible: + Usage: required + Value type: + Definition: Value should contain + - "qcom,pcie-ipq8064" for ipq8064 + - "qcom,pcie-apq8064" for apq8064 + - "qcom,pcie-apq8084" for apq8084 + +- reg: + Usage: required + Value type: + Definition: Register ranges as listed in the reg-names property + +- reg-names: + Usage: required + Value type: + Definition: Must include the following entries + - "parf" Qualcomm specific registers + - "dbi" Designware PCIe registers + - "elbi" External local bus interface registers + - "config" PCIe configuration space + +- device_type: + Usage: required + Value type: + Definition: Should be "pci". As specified in designware-pcie.txt + +- #address-cells: + Usage: required + Value type: + Definition: Should be 3. As specified in designware-pcie.txt + +- #size-cells: + Usage: required + Value type: + Definition: Should be 2. As specified in designware-pcie.txt + +- ranges: + Usage: required + Value type: + Definition: As specified in designware-pcie.txt + +- interrupts: + Usage: required + Value type: + Definition: MSI interrupt + +- interrupt-names: + Usage: required + Value type: + Definition: Should contain "msi" + +- #interrupt-cells: + Usage: required + Value type: + Definition: Should be 1. As specified in designware-pcie.txt + +- interrupt-map-mask: + Usage: required + Value type: + Definition: As specified in designware-pcie.txt + +- interrupt-map: + Usage: required + Value type: + Definition: As specified in designware-pcie.txt + +- clocks: + Usage: required + Value type: + Definition: List of phandle and clock specifier pairs as listed + in clock-names property + +- clock-names: + Usage: required + Value type: + Definition: Should contain the following entries + - "iface" Configuration AHB clock + +- clock-names: + Usage: required for ipq/apq8064 + Value type: + Definition: Should contain the following entries + - "core" Clocks the pcie hw block + - "phy" Clocks the pcie PHY block +- clock-names: + Usage: required for apq8084 + Value type: + Definition: Should contain the following entries + - "aux" Auxiliary (AUX) clock + - "bus_master" Master AXI clock + - "bus_slave" Slave AXI clock +- resets: + Usage: required + Value type: + Definition: List of phandle and reset specifier pairs as listed + in reset-names property + +- reset-names: + Usage: required for ipq/apq8064 + Value type: + Definition: Should contain the following entries + - "axi" AXI reset + - "ahb" AHB reset + - "por" POR reset + - "pci" PCI reset + - "phy" PHY reset + +- reset-names: + Usage: required for apq8084 + Value type: + Definition: Should contain the following entries + - "core" Core reset + +- power-domains: + Usage: required for apq8084 + Value type: + Definition: A phandle and power domain specifier pair to the + power domain which is responsible for collapsing + and restoring power to the peripheral + +- vdda-supply: + Usage: required + Value type: + Definition: A phandle to the core analog power supply + +- vdda_phy-supply: + Usage: required for ipq/apq8064 + Value type: + Definition: A phandle to the analog power supply for PHY + +- vdda_refclk-supply: + Usage: required for ipq/apq8064 + Value type: + Definition: A phandle to the analog power supply for IC which generates + reference clock + +- phys: + Usage: required for apq8084 + Value type: + Definition: List of phandle(s) as listed in phy-names property + +- phy-names: + Usage: required for apq8084 + Value type: + Definition: Should contain "pciephy" + +- -gpios: + Usage: optional + Value type: + Definition: List of phandle and gpio specifier pairs. Should contain + - "perst-gpios" PCIe endpoint reset signal line + - "wake-gpios" PCIe endpoint wake signal line + +* Example for ipq/apq8064 + pcie@1b500000 { + compatible = "qcom,pcie-apq8064", "qcom,pcie-ipq8064", "snps,dw-pcie"; + reg = <0x1b500000 0x1000 + 0x1b502000 0x80 + 0x1b600000 0x100 + 0x0ff00000 0x100000>; + reg-names = "dbi", "elbi", "parf", "config"; + device_type = "pci"; + linux,pci-domain = <0>; + bus-range = <0x00 0xff>; + num-lanes = <1>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* I/O */ + 0x82000000 0 0 0x08000000 0 0x07e00000>; /* memory */ + interrupts = ; + interrupt-names = "msi"; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0x7>; + interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ + <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ + <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ + <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ + clocks = <&gcc PCIE_A_CLK>, + <&gcc PCIE_H_CLK>, + <&gcc PCIE_PHY_CLK>; + clock-names = "core", "iface", "phy"; + resets = <&gcc PCIE_ACLK_RESET>, + <&gcc PCIE_HCLK_RESET>, + <&gcc PCIE_POR_RESET>, + <&gcc PCIE_PCI_RESET>, + <&gcc PCIE_PHY_RESET>; + reset-names = "axi", "ahb", "por", "pci", "phy"; + pinctrl-0 = <&pcie_pins_default>; + pinctrl-names = "default"; + }; + +* Example for apq8084 + pcie0@fc520000 { + compatible = "qcom,pcie-apq8084", "snps,dw-pcie"; + reg = <0xfc520000 0x2000>, + <0xff000000 0x1000>, + <0xff001000 0x1000>, + <0xff002000 0x2000>; + reg-names = "parf", "dbi", "elbi", "config"; + device_type = "pci"; + linux,pci-domain = <0>; + bus-range = <0x00 0xff>; + num-lanes = <1>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x81000000 0 0 0xff200000 0 0x00100000 /* I/O */ + 0x82000000 0 0x00300000 0xff300000 0 0x00d00000>; /* memory */ + interrupts = ; + interrupt-names = "msi"; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0x7>; + interrupt-map = <0 0 0 1 &intc 0 244 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ + <0 0 0 2 &intc 0 245 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ + <0 0 0 3 &intc 0 247 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ + <0 0 0 4 &intc 0 248 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ + clocks = <&gcc GCC_PCIE_0_CFG_AHB_CLK>, + <&gcc GCC_PCIE_0_MSTR_AXI_CLK>, + <&gcc GCC_PCIE_0_SLV_AXI_CLK>, + <&gcc GCC_PCIE_0_AUX_CLK>; + clock-names = "iface", "master_bus", "slave_bus", "aux"; + resets = <&gcc GCC_PCIE_0_BCR>; + reset-names = "core"; + power-domains = <&gcc PCIE0_GDSC>; + vdda-supply = <&pma8084_l3>; + phys = <&pciephy0>; + phy-names = "pciephy"; + perst-gpio = <&tlmm 70 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&pcie0_pins_default>; + pinctrl-names = "default"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 050d0e77a2cf00f7d6a33e6d11bccf88c22ad761..9ea78171b43c767aa13a2f21698041a90fad6622 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8240,11 +8240,19 @@ F: drivers/pci/host/pci-xgene-msi.c PCIE DRIVER FOR HISILICON M: Zhou Wang +M: Gabriele Paoloni L: linux-pci@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/pci/hisilicon-pcie.txt F: drivers/pci/host/pcie-hisi.c +PCIE DRIVER FOR QUALCOMM MSM +M: Stanimir Varbanov +L: linux-pci@vger.kernel.org +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: drivers/pci/host/*qcom* + PCMCIA SUBSYSTEM P: Linux PCMCIA Team L: linux-pcmcia@lists.infradead.org diff --git a/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts b/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts index 11ac608b6d50e716e6fc2aabf47839ce1b871d96..955c697b4dd4fba88d7c2dd39e1186c9b3a1f3ba 100644 --- a/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts +++ b/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts @@ -47,6 +47,18 @@ bias-disable; }; }; + + pcie_pins: pcie_pinmux { + mux { + pins = "gpio27"; + function = "gpio"; + }; + conf { + pins = "gpio27"; + drive-strength = <12>; + bias-disable; + }; + }; }; rpm@108000 { @@ -123,6 +135,10 @@ pm8921_lvs1: lvs1 { bias-pull-down; }; + + lvs6 { + bias-pull-down; + }; }; }; @@ -231,6 +247,16 @@ status = "okay"; }; + pci@1b500000 { + status = "ok"; + vdda-supply = <&pm8921_s3>; + vdda_phy-supply = <&pm8921_lvs6>; + vdda_refclk-supply = <&ext_3p3v>; + pinctrl-0 = <&pcie_pins>; + pinctrl-names = "default"; + perst-gpio = <&tlmm_pinmux 27 GPIO_ACTIVE_LOW>; + }; + qcom,ssbi@500000 { pmic@0 { gpio@150 { diff --git a/arch/arm/boot/dts/qcom-apq8064.dtsi b/arch/arm/boot/dts/qcom-apq8064.dtsi index a4c1762b53ea3712a46e5f8a04cff5783d93b14d..847150fbfdbf74a30fcbca608cf216f536ab3010 100644 --- a/arch/arm/boot/dts/qcom-apq8064.dtsi +++ b/arch/arm/boot/dts/qcom-apq8064.dtsi @@ -659,5 +659,41 @@ compatible = "qcom,tcsr-apq8064", "syscon"; reg = <0x1a400000 0x100>; }; + + pcie: pci@1b500000 { + compatible = "qcom,pcie-apq8064", "snps,dw-pcie"; + reg = <0x1b500000 0x1000 + 0x1b502000 0x80 + 0x1b600000 0x100 + 0x0ff00000 0x100000>; + reg-names = "dbi", "elbi", "parf", "config"; + device_type = "pci"; + linux,pci-domain = <0>; + bus-range = <0x00 0xff>; + num-lanes = <1>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* I/O */ + 0x82000000 0 0 0x08000000 0 0x07e00000>; /* memory */ + interrupts = ; + interrupt-names = "msi"; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0x7>; + interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ + <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ + <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ + <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ + clocks = <&gcc PCIE_A_CLK>, + <&gcc PCIE_H_CLK>, + <&gcc PCIE_PHY_REF_CLK>; + clock-names = "core", "iface", "phy"; + resets = <&gcc PCIE_ACLK_RESET>, + <&gcc PCIE_HCLK_RESET>, + <&gcc PCIE_POR_RESET>, + <&gcc PCIE_PCI_RESET>, + <&gcc PCIE_PHY_RESET>; + reset-names = "axi", "ahb", "por", "pci", "phy"; + status = "disabled"; + }; }; }; diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 98ce5474d26ec63142a8a07576668188bfd5ffd3..6d181ba09ccd5f0d496f1f0787b2dac85eb48474 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -173,10 +173,21 @@ config PCIE_ALTERA_MSI config PCI_HISI depends on OF && ARM64 - bool "HiSilicon SoC HIP05 PCIe controller" + bool "HiSilicon Hip05 and Hip06 SoCs PCIe controllers" select PCIEPORTBUS select PCIE_DW help - Say Y here if you want PCIe controller support on HiSilicon HIP05 SoC + Say Y here if you want PCIe controller support on HiSilicon + Hip05 and Hip06 SoCs + +config PCIE_QCOM + bool "Qualcomm PCIe controller" + depends on ARCH_QCOM && OF + select PCIE_DW + select PCIEPORTBUS + help + Say Y here to enable PCIe controller support on Qualcomm SoCs. The + PCIe controller uses the Designware core plus Qualcomm-specific + hardware wrappers. endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 0e4e95e39c21e287111e68d06160e85757e492fc..7b2f20c6ccc62bca6317d35e85579eba7429f85a 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o obj-$(CONFIG_PCI_HISI) += pcie-hisi.o +obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o diff --git a/drivers/pci/host/pci-dra7xx.c b/drivers/pci/host/pci-dra7xx.c index 8c3688046c02ff40446833f1a8f3eecebfd9713f..923607bdabc5c63ef940453f3ffe42180b29b02b 100644 --- a/drivers/pci/host/pci-dra7xx.c +++ b/drivers/pci/host/pci-dra7xx.c @@ -302,7 +302,8 @@ static int __init dra7xx_add_pcie_port(struct dra7xx_pcie *dra7xx, } ret = devm_request_irq(&pdev->dev, pp->irq, - dra7xx_pcie_msi_irq_handler, IRQF_SHARED, + dra7xx_pcie_msi_irq_handler, + IRQF_SHARED | IRQF_NO_THREAD, "dra7-pcie-msi", pp); if (ret) { dev_err(&pdev->dev, "failed to request irq\n"); diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c index 01095e1160a474b414112cdc1dddae1d8f3b9434..d997d22d4231da6de08cab29dd101d699dbb4fe0 100644 --- a/drivers/pci/host/pci-exynos.c +++ b/drivers/pci/host/pci-exynos.c @@ -522,7 +522,8 @@ static int __init exynos_add_pcie_port(struct pcie_port *pp, ret = devm_request_irq(&pdev->dev, pp->msi_irq, exynos_pcie_msi_irq_handler, - IRQF_SHARED, "exynos-pcie", pp); + IRQF_SHARED | IRQF_NO_THREAD, + "exynos-pcie", pp); if (ret) { dev_err(&pdev->dev, "failed to request msi irq\n"); return ret; diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 8e9afa5bd7deb50ce610951682ea3742bb6a029d..b039f0c6c8ff8d8e7df348d1ffac962523d94657 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -537,7 +537,8 @@ static int __init imx6_add_pcie_port(struct pcie_port *pp, ret = devm_request_irq(&pdev->dev, pp->msi_irq, imx6_pcie_msi_handler, - IRQF_SHARED, "mx6-pcie-msi", pp); + IRQF_SHARED | IRQF_NO_THREAD, + "mx6-pcie-msi", pp); if (ret) { dev_err(&pdev->dev, "failed to request MSI irq\n"); return ret; diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 3018ae52e0923d4e2e8c8c2dc75f07d5528598bc..30323114c53cca80b0c4f7bee7f361d65565fc26 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -1288,7 +1288,7 @@ static int tegra_pcie_enable_msi(struct tegra_pcie *pcie) msi->irq = err; - err = request_irq(msi->irq, tegra_pcie_msi_irq, 0, + err = request_irq(msi->irq, tegra_pcie_msi_irq, IRQF_NO_THREAD, tegra_msi_irq_chip.name, pcie); if (err < 0) { dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 540f077c37eae7b59b983c31fa6ddce51d8bda6e..e28bc0bd3a7da773c744152af79cbc2726dc30a0 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -128,32 +128,26 @@ static inline void dw_pcie_writel_rc(struct pcie_port *pp, u32 val, u32 reg) static int dw_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val) { - int ret; - if (pp->ops->rd_own_conf) - ret = pp->ops->rd_own_conf(pp, where, size, val); - else - ret = dw_pcie_cfg_read(pp->dbi_base + where, size, val); + return pp->ops->rd_own_conf(pp, where, size, val); - return ret; + return dw_pcie_cfg_read(pp->dbi_base + where, size, val); } static int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, u32 val) { - int ret; - if (pp->ops->wr_own_conf) - ret = pp->ops->wr_own_conf(pp, where, size, val); - else - ret = dw_pcie_cfg_write(pp->dbi_base + where, size, val); + return pp->ops->wr_own_conf(pp, where, size, val); - return ret; + return dw_pcie_cfg_write(pp->dbi_base + where, size, val); } static void dw_pcie_prog_outbound_atu(struct pcie_port *pp, int index, int type, u64 cpu_addr, u64 pci_addr, u32 size) { + u32 val; + dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | index, PCIE_ATU_VIEWPORT); dw_pcie_writel_rc(pp, lower_32_bits(cpu_addr), PCIE_ATU_LOWER_BASE); @@ -164,6 +158,12 @@ static void dw_pcie_prog_outbound_atu(struct pcie_port *pp, int index, dw_pcie_writel_rc(pp, upper_32_bits(pci_addr), PCIE_ATU_UPPER_TARGET); dw_pcie_writel_rc(pp, type, PCIE_ATU_CR1); dw_pcie_writel_rc(pp, PCIE_ATU_ENABLE, PCIE_ATU_CR2); + + /* + * Make sure ATU enable takes effect before any subsequent config + * and I/O accesses. + */ + dw_pcie_readl_rc(pp, PCIE_ATU_CR2, &val); } static struct irq_chip dw_msi_irq_chip = { @@ -384,8 +384,8 @@ int dw_pcie_link_up(struct pcie_port *pp) { if (pp->ops->link_up) return pp->ops->link_up(pp); - else - return 0; + + return 0; } static int dw_pcie_msi_map(struct irq_domain *domain, unsigned int irq, @@ -572,6 +572,9 @@ static int dw_pcie_rd_other_conf(struct pcie_port *pp, struct pci_bus *bus, u64 cpu_addr; void __iomem *va_cfg_base; + if (pp->ops->rd_other_conf) + return pp->ops->rd_other_conf(pp, bus, devfn, where, size, val); + busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | PCIE_ATU_FUNC(PCI_FUNC(devfn)); @@ -606,6 +609,9 @@ static int dw_pcie_wr_other_conf(struct pcie_port *pp, struct pci_bus *bus, u64 cpu_addr; void __iomem *va_cfg_base; + if (pp->ops->wr_other_conf) + return pp->ops->wr_other_conf(pp, bus, devfn, where, size, val); + busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | PCIE_ATU_FUNC(PCI_FUNC(devfn)); @@ -659,46 +665,30 @@ static int dw_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where, int size, u32 *val) { struct pcie_port *pp = bus->sysdata; - int ret; if (dw_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0) { *val = 0xffffffff; return PCIBIOS_DEVICE_NOT_FOUND; } - if (bus->number != pp->root_bus_nr) - if (pp->ops->rd_other_conf) - ret = pp->ops->rd_other_conf(pp, bus, devfn, - where, size, val); - else - ret = dw_pcie_rd_other_conf(pp, bus, devfn, - where, size, val); - else - ret = dw_pcie_rd_own_conf(pp, where, size, val); + if (bus->number == pp->root_bus_nr) + return dw_pcie_rd_own_conf(pp, where, size, val); - return ret; + return dw_pcie_rd_other_conf(pp, bus, devfn, where, size, val); } static int dw_pcie_wr_conf(struct pci_bus *bus, u32 devfn, int where, int size, u32 val) { struct pcie_port *pp = bus->sysdata; - int ret; if (dw_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0) return PCIBIOS_DEVICE_NOT_FOUND; - if (bus->number != pp->root_bus_nr) - if (pp->ops->wr_other_conf) - ret = pp->ops->wr_other_conf(pp, bus, devfn, - where, size, val); - else - ret = dw_pcie_wr_other_conf(pp, bus, devfn, - where, size, val); - else - ret = dw_pcie_wr_own_conf(pp, where, size, val); + if (bus->number == pp->root_bus_nr) + return dw_pcie_wr_own_conf(pp, where, size, val); - return ret; + return dw_pcie_wr_other_conf(pp, bus, devfn, where, size, val); } static struct pci_ops dw_pcie_ops = { diff --git a/drivers/pci/host/pcie-hisi.c b/drivers/pci/host/pcie-hisi.c index 35457ecd8e70f4d6f856573f45f8bbc2991d1f6d..4bad6954019b1e8d2a6bafd4f880c553eac5022c 100644 --- a/drivers/pci/host/pcie-hisi.c +++ b/drivers/pci/host/pcie-hisi.c @@ -1,10 +1,11 @@ /* - * PCIe host controller driver for HiSilicon Hip05 SoC + * PCIe host controller driver for HiSilicon SoCs * * Copyright (C) 2015 HiSilicon Co., Ltd. http://www.hisilicon.com * - * Author: Zhou Wang - * Dacai Zhu + * Authors: Zhou Wang + * Dacai Zhu + * Gabriele Paoloni * * 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 @@ -16,21 +17,31 @@ #include #include #include +#include #include #include "pcie-designware.h" -#define PCIE_SUBCTRL_SYS_STATE4_REG 0x6818 -#define PCIE_LTSSM_LINKUP_STATE 0x11 -#define PCIE_LTSSM_STATE_MASK 0x3F +#define PCIE_LTSSM_LINKUP_STATE 0x11 +#define PCIE_LTSSM_STATE_MASK 0x3F +#define PCIE_SUBCTRL_SYS_STATE4_REG 0x6818 +#define PCIE_SYS_STATE4 0x31c +#define PCIE_HIP06_CTRL_OFF 0x1000 #define to_hisi_pcie(x) container_of(x, struct hisi_pcie, pp) +struct hisi_pcie; + +struct pcie_soc_ops { + int (*hisi_pcie_link_up)(struct hisi_pcie *pcie); +}; + struct hisi_pcie { struct regmap *subctrl; void __iomem *reg_base; u32 port_id; struct pcie_port pp; + struct pcie_soc_ops *soc_ops; }; static inline void hisi_pcie_apb_writel(struct hisi_pcie *pcie, @@ -44,7 +55,7 @@ static inline u32 hisi_pcie_apb_readl(struct hisi_pcie *pcie, u32 reg) return readl(pcie->reg_base + reg); } -/* Hip05 PCIe host only supports 32-bit config access */ +/* HipXX PCIe host only supports 32-bit config access */ static int hisi_pcie_cfg_read(struct pcie_port *pp, int where, int size, u32 *val) { @@ -67,7 +78,7 @@ static int hisi_pcie_cfg_read(struct pcie_port *pp, int where, int size, return PCIBIOS_SUCCESSFUL; } -/* Hip05 PCIe host only supports 32-bit config access */ +/* HipXX PCIe host only supports 32-bit config access */ static int hisi_pcie_cfg_write(struct pcie_port *pp, int where, int size, u32 val) { @@ -94,10 +105,9 @@ static int hisi_pcie_cfg_write(struct pcie_port *pp, int where, int size, return PCIBIOS_SUCCESSFUL; } -static int hisi_pcie_link_up(struct pcie_port *pp) +static int hisi_pcie_link_up_hip05(struct hisi_pcie *hisi_pcie) { u32 val; - struct hisi_pcie *hisi_pcie = to_hisi_pcie(pp); regmap_read(hisi_pcie->subctrl, PCIE_SUBCTRL_SYS_STATE4_REG + 0x100 * hisi_pcie->port_id, &val); @@ -105,6 +115,23 @@ static int hisi_pcie_link_up(struct pcie_port *pp) return ((val & PCIE_LTSSM_STATE_MASK) == PCIE_LTSSM_LINKUP_STATE); } +static int hisi_pcie_link_up_hip06(struct hisi_pcie *hisi_pcie) +{ + u32 val; + + val = hisi_pcie_apb_readl(hisi_pcie, PCIE_HIP06_CTRL_OFF + + PCIE_SYS_STATE4); + + return ((val & PCIE_LTSSM_STATE_MASK) == PCIE_LTSSM_LINKUP_STATE); +} + +static int hisi_pcie_link_up(struct pcie_port *pp) +{ + struct hisi_pcie *hisi_pcie = to_hisi_pcie(pp); + + return hisi_pcie->soc_ops->hisi_pcie_link_up(hisi_pcie); +} + static struct pcie_host_ops hisi_pcie_host_ops = { .rd_own_conf = hisi_pcie_cfg_read, .wr_own_conf = hisi_pcie_cfg_write, @@ -143,7 +170,9 @@ static int __init hisi_pcie_probe(struct platform_device *pdev) { struct hisi_pcie *hisi_pcie; struct pcie_port *pp; + const struct of_device_id *match; struct resource *reg; + struct device_driver *driver; int ret; hisi_pcie = devm_kzalloc(&pdev->dev, sizeof(*hisi_pcie), GFP_KERNEL); @@ -152,6 +181,10 @@ static int __init hisi_pcie_probe(struct platform_device *pdev) pp = &hisi_pcie->pp; pp->dev = &pdev->dev; + driver = (pdev->dev).driver; + + match = of_match_device(driver->of_match_table, &pdev->dev); + hisi_pcie->soc_ops = (struct pcie_soc_ops *) match->data; hisi_pcie->subctrl = syscon_regmap_lookup_by_compatible("hisilicon,pcie-sas-subctrl"); @@ -180,11 +213,27 @@ static int __init hisi_pcie_probe(struct platform_device *pdev) return 0; } +static struct pcie_soc_ops hip05_ops = { + &hisi_pcie_link_up_hip05 +}; + +static struct pcie_soc_ops hip06_ops = { + &hisi_pcie_link_up_hip06 +}; + static const struct of_device_id hisi_pcie_of_match[] = { - {.compatible = "hisilicon,hip05-pcie",}, + { + .compatible = "hisilicon,hip05-pcie", + .data = (void *) &hip05_ops, + }, + { + .compatible = "hisilicon,hip06-pcie", + .data = (void *) &hip06_ops, + }, {}, }; + MODULE_DEVICE_TABLE(of, hisi_pcie_of_match); static struct platform_driver hisi_pcie_driver = { @@ -196,3 +245,8 @@ static struct platform_driver hisi_pcie_driver = { }; module_platform_driver(hisi_pcie_driver); + +MODULE_AUTHOR("Zhou Wang "); +MODULE_AUTHOR("Dacai Zhu "); +MODULE_AUTHOR("Gabriele Paoloni "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pcie-qcom.c b/drivers/pci/host/pcie-qcom.c new file mode 100644 index 0000000000000000000000000000000000000000..e845fba19632d8518cb6af9a622f301668f5aa0c --- /dev/null +++ b/drivers/pci/host/pcie-qcom.c @@ -0,0 +1,616 @@ +/* + * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * Copyright 2015 Linaro Limited. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcie-designware.h" + +#define PCIE20_PARF_PHY_CTRL 0x40 +#define PCIE20_PARF_PHY_REFCLK 0x4C +#define PCIE20_PARF_DBI_BASE_ADDR 0x168 +#define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c +#define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178 + +#define PCIE20_ELBI_SYS_CTRL 0x04 +#define PCIE20_ELBI_SYS_CTRL_LT_ENABLE BIT(0) + +#define PCIE20_CAP 0x70 + +#define PERST_DELAY_US 1000 + +struct qcom_pcie_resources_v0 { + struct clk *iface_clk; + struct clk *core_clk; + struct clk *phy_clk; + struct reset_control *pci_reset; + struct reset_control *axi_reset; + struct reset_control *ahb_reset; + struct reset_control *por_reset; + struct reset_control *phy_reset; + struct regulator *vdda; + struct regulator *vdda_phy; + struct regulator *vdda_refclk; +}; + +struct qcom_pcie_resources_v1 { + struct clk *iface; + struct clk *aux; + struct clk *master_bus; + struct clk *slave_bus; + struct reset_control *core; + struct regulator *vdda; +}; + +union qcom_pcie_resources { + struct qcom_pcie_resources_v0 v0; + struct qcom_pcie_resources_v1 v1; +}; + +struct qcom_pcie; + +struct qcom_pcie_ops { + int (*get_resources)(struct qcom_pcie *pcie); + int (*init)(struct qcom_pcie *pcie); + void (*deinit)(struct qcom_pcie *pcie); +}; + +struct qcom_pcie { + struct pcie_port pp; + struct device *dev; + union qcom_pcie_resources res; + void __iomem *parf; + void __iomem *dbi; + void __iomem *elbi; + struct phy *phy; + struct gpio_desc *reset; + struct qcom_pcie_ops *ops; +}; + +#define to_qcom_pcie(x) container_of(x, struct qcom_pcie, pp) + +static void qcom_ep_reset_assert(struct qcom_pcie *pcie) +{ + gpiod_set_value(pcie->reset, 1); + usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500); +} + +static void qcom_ep_reset_deassert(struct qcom_pcie *pcie) +{ + gpiod_set_value(pcie->reset, 0); + usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500); +} + +static irqreturn_t qcom_pcie_msi_irq_handler(int irq, void *arg) +{ + struct pcie_port *pp = arg; + + return dw_handle_msi_irq(pp); +} + +static int qcom_pcie_establish_link(struct qcom_pcie *pcie) +{ + struct device *dev = pcie->dev; + unsigned int retries = 0; + u32 val; + + if (dw_pcie_link_up(&pcie->pp)) + return 0; + + /* enable link training */ + val = readl(pcie->elbi + PCIE20_ELBI_SYS_CTRL); + val |= PCIE20_ELBI_SYS_CTRL_LT_ENABLE; + writel(val, pcie->elbi + PCIE20_ELBI_SYS_CTRL); + + do { + if (dw_pcie_link_up(&pcie->pp)) + return 0; + usleep_range(250, 1000); + } while (retries < 200); + + dev_warn(dev, "phy link never came up\n"); + + return -ETIMEDOUT; +} + +static int qcom_pcie_get_resources_v0(struct qcom_pcie *pcie) +{ + struct qcom_pcie_resources_v0 *res = &pcie->res.v0; + struct device *dev = pcie->dev; + + res->vdda = devm_regulator_get(dev, "vdda"); + if (IS_ERR(res->vdda)) + return PTR_ERR(res->vdda); + + res->vdda_phy = devm_regulator_get(dev, "vdda_phy"); + if (IS_ERR(res->vdda_phy)) + return PTR_ERR(res->vdda_phy); + + res->vdda_refclk = devm_regulator_get(dev, "vdda_refclk"); + if (IS_ERR(res->vdda_refclk)) + return PTR_ERR(res->vdda_refclk); + + res->iface_clk = devm_clk_get(dev, "iface"); + if (IS_ERR(res->iface_clk)) + return PTR_ERR(res->iface_clk); + + res->core_clk = devm_clk_get(dev, "core"); + if (IS_ERR(res->core_clk)) + return PTR_ERR(res->core_clk); + + res->phy_clk = devm_clk_get(dev, "phy"); + if (IS_ERR(res->phy_clk)) + return PTR_ERR(res->phy_clk); + + res->pci_reset = devm_reset_control_get(dev, "pci"); + if (IS_ERR(res->pci_reset)) + return PTR_ERR(res->pci_reset); + + res->axi_reset = devm_reset_control_get(dev, "axi"); + if (IS_ERR(res->axi_reset)) + return PTR_ERR(res->axi_reset); + + res->ahb_reset = devm_reset_control_get(dev, "ahb"); + if (IS_ERR(res->ahb_reset)) + return PTR_ERR(res->ahb_reset); + + res->por_reset = devm_reset_control_get(dev, "por"); + if (IS_ERR(res->por_reset)) + return PTR_ERR(res->por_reset); + + res->phy_reset = devm_reset_control_get(dev, "phy"); + if (IS_ERR(res->phy_reset)) + return PTR_ERR(res->phy_reset); + + return 0; +} + +static int qcom_pcie_get_resources_v1(struct qcom_pcie *pcie) +{ + struct qcom_pcie_resources_v1 *res = &pcie->res.v1; + struct device *dev = pcie->dev; + + res->vdda = devm_regulator_get(dev, "vdda"); + if (IS_ERR(res->vdda)) + return PTR_ERR(res->vdda); + + res->iface = devm_clk_get(dev, "iface"); + if (IS_ERR(res->iface)) + return PTR_ERR(res->iface); + + res->aux = devm_clk_get(dev, "aux"); + if (IS_ERR(res->aux)) + return PTR_ERR(res->aux); + + res->master_bus = devm_clk_get(dev, "master_bus"); + if (IS_ERR(res->master_bus)) + return PTR_ERR(res->master_bus); + + res->slave_bus = devm_clk_get(dev, "slave_bus"); + if (IS_ERR(res->slave_bus)) + return PTR_ERR(res->slave_bus); + + res->core = devm_reset_control_get(dev, "core"); + if (IS_ERR(res->core)) + return PTR_ERR(res->core); + + return 0; +} + +static void qcom_pcie_deinit_v0(struct qcom_pcie *pcie) +{ + struct qcom_pcie_resources_v0 *res = &pcie->res.v0; + + reset_control_assert(res->pci_reset); + reset_control_assert(res->axi_reset); + reset_control_assert(res->ahb_reset); + reset_control_assert(res->por_reset); + reset_control_assert(res->pci_reset); + clk_disable_unprepare(res->iface_clk); + clk_disable_unprepare(res->core_clk); + clk_disable_unprepare(res->phy_clk); + regulator_disable(res->vdda); + regulator_disable(res->vdda_phy); + regulator_disable(res->vdda_refclk); +} + +static int qcom_pcie_init_v0(struct qcom_pcie *pcie) +{ + struct qcom_pcie_resources_v0 *res = &pcie->res.v0; + struct device *dev = pcie->dev; + u32 val; + int ret; + + ret = regulator_enable(res->vdda); + if (ret) { + dev_err(dev, "cannot enable vdda regulator\n"); + return ret; + } + + ret = regulator_enable(res->vdda_refclk); + if (ret) { + dev_err(dev, "cannot enable vdda_refclk regulator\n"); + goto err_refclk; + } + + ret = regulator_enable(res->vdda_phy); + if (ret) { + dev_err(dev, "cannot enable vdda_phy regulator\n"); + goto err_vdda_phy; + } + + ret = reset_control_assert(res->ahb_reset); + if (ret) { + dev_err(dev, "cannot assert ahb reset\n"); + goto err_assert_ahb; + } + + ret = clk_prepare_enable(res->iface_clk); + if (ret) { + dev_err(dev, "cannot prepare/enable iface clock\n"); + goto err_assert_ahb; + } + + ret = clk_prepare_enable(res->phy_clk); + if (ret) { + dev_err(dev, "cannot prepare/enable phy clock\n"); + goto err_clk_phy; + } + + ret = clk_prepare_enable(res->core_clk); + if (ret) { + dev_err(dev, "cannot prepare/enable core clock\n"); + goto err_clk_core; + } + + ret = reset_control_deassert(res->ahb_reset); + if (ret) { + dev_err(dev, "cannot deassert ahb reset\n"); + goto err_deassert_ahb; + } + + /* enable PCIe clocks and resets */ + val = readl(pcie->parf + PCIE20_PARF_PHY_CTRL); + val &= ~BIT(0); + writel(val, pcie->parf + PCIE20_PARF_PHY_CTRL); + + /* enable external reference clock */ + val = readl(pcie->parf + PCIE20_PARF_PHY_REFCLK); + val |= BIT(16); + writel(val, pcie->parf + PCIE20_PARF_PHY_REFCLK); + + ret = reset_control_deassert(res->phy_reset); + if (ret) { + dev_err(dev, "cannot deassert phy reset\n"); + return ret; + } + + ret = reset_control_deassert(res->pci_reset); + if (ret) { + dev_err(dev, "cannot deassert pci reset\n"); + return ret; + } + + ret = reset_control_deassert(res->por_reset); + if (ret) { + dev_err(dev, "cannot deassert por reset\n"); + return ret; + } + + ret = reset_control_deassert(res->axi_reset); + if (ret) { + dev_err(dev, "cannot deassert axi reset\n"); + return ret; + } + + /* wait for clock acquisition */ + usleep_range(1000, 1500); + + return 0; + +err_deassert_ahb: + clk_disable_unprepare(res->core_clk); +err_clk_core: + clk_disable_unprepare(res->phy_clk); +err_clk_phy: + clk_disable_unprepare(res->iface_clk); +err_assert_ahb: + regulator_disable(res->vdda_phy); +err_vdda_phy: + regulator_disable(res->vdda_refclk); +err_refclk: + regulator_disable(res->vdda); + + return ret; +} + +static void qcom_pcie_deinit_v1(struct qcom_pcie *pcie) +{ + struct qcom_pcie_resources_v1 *res = &pcie->res.v1; + + reset_control_assert(res->core); + clk_disable_unprepare(res->slave_bus); + clk_disable_unprepare(res->master_bus); + clk_disable_unprepare(res->iface); + clk_disable_unprepare(res->aux); + regulator_disable(res->vdda); +} + +static int qcom_pcie_init_v1(struct qcom_pcie *pcie) +{ + struct qcom_pcie_resources_v1 *res = &pcie->res.v1; + struct device *dev = pcie->dev; + int ret; + + ret = reset_control_deassert(res->core); + if (ret) { + dev_err(dev, "cannot deassert core reset\n"); + return ret; + } + + ret = clk_prepare_enable(res->aux); + if (ret) { + dev_err(dev, "cannot prepare/enable aux clock\n"); + goto err_res; + } + + ret = clk_prepare_enable(res->iface); + if (ret) { + dev_err(dev, "cannot prepare/enable iface clock\n"); + goto err_aux; + } + + ret = clk_prepare_enable(res->master_bus); + if (ret) { + dev_err(dev, "cannot prepare/enable master_bus clock\n"); + goto err_iface; + } + + ret = clk_prepare_enable(res->slave_bus); + if (ret) { + dev_err(dev, "cannot prepare/enable slave_bus clock\n"); + goto err_master; + } + + ret = regulator_enable(res->vdda); + if (ret) { + dev_err(dev, "cannot enable vdda regulator\n"); + goto err_slave; + } + + /* change DBI base address */ + writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR); + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + u32 val = readl(pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT); + + val |= BIT(31); + writel(val, pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT); + } + + return 0; +err_slave: + clk_disable_unprepare(res->slave_bus); +err_master: + clk_disable_unprepare(res->master_bus); +err_iface: + clk_disable_unprepare(res->iface); +err_aux: + clk_disable_unprepare(res->aux); +err_res: + reset_control_assert(res->core); + + return ret; +} + +static int qcom_pcie_link_up(struct pcie_port *pp) +{ + struct qcom_pcie *pcie = to_qcom_pcie(pp); + u16 val = readw(pcie->dbi + PCIE20_CAP + PCI_EXP_LNKSTA); + + return !!(val & PCI_EXP_LNKSTA_DLLLA); +} + +static void qcom_pcie_host_init(struct pcie_port *pp) +{ + struct qcom_pcie *pcie = to_qcom_pcie(pp); + int ret; + + qcom_ep_reset_assert(pcie); + + ret = pcie->ops->init(pcie); + if (ret) + goto err_deinit; + + ret = phy_power_on(pcie->phy); + if (ret) + goto err_deinit; + + dw_pcie_setup_rc(pp); + + if (IS_ENABLED(CONFIG_PCI_MSI)) + dw_pcie_msi_init(pp); + + qcom_ep_reset_deassert(pcie); + + ret = qcom_pcie_establish_link(pcie); + if (ret) + goto err; + + return; +err: + qcom_ep_reset_assert(pcie); + phy_power_off(pcie->phy); +err_deinit: + pcie->ops->deinit(pcie); +} + +static int qcom_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, + u32 *val) +{ + /* the device class is not reported correctly from the register */ + if (where == PCI_CLASS_REVISION && size == 4) { + *val = readl(pp->dbi_base + PCI_CLASS_REVISION); + *val &= 0xff; /* keep revision id */ + *val |= PCI_CLASS_BRIDGE_PCI << 16; + return PCIBIOS_SUCCESSFUL; + } + + return dw_pcie_cfg_read(pp->dbi_base + where, size, val); +} + +static struct pcie_host_ops qcom_pcie_dw_ops = { + .link_up = qcom_pcie_link_up, + .host_init = qcom_pcie_host_init, + .rd_own_conf = qcom_pcie_rd_own_conf, +}; + +static const struct qcom_pcie_ops ops_v0 = { + .get_resources = qcom_pcie_get_resources_v0, + .init = qcom_pcie_init_v0, + .deinit = qcom_pcie_deinit_v0, +}; + +static const struct qcom_pcie_ops ops_v1 = { + .get_resources = qcom_pcie_get_resources_v1, + .init = qcom_pcie_init_v1, + .deinit = qcom_pcie_deinit_v1, +}; + +static int qcom_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct qcom_pcie *pcie; + struct pcie_port *pp; + int ret; + + pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->ops = (struct qcom_pcie_ops *)of_device_get_match_data(dev); + pcie->dev = dev; + + pcie->reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_LOW); + if (IS_ERR(pcie->reset)) + return PTR_ERR(pcie->reset); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf"); + pcie->parf = devm_ioremap_resource(dev, res); + if (IS_ERR(pcie->parf)) + return PTR_ERR(pcie->parf); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); + pcie->dbi = devm_ioremap_resource(dev, res); + if (IS_ERR(pcie->dbi)) + return PTR_ERR(pcie->dbi); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); + pcie->elbi = devm_ioremap_resource(dev, res); + if (IS_ERR(pcie->elbi)) + return PTR_ERR(pcie->elbi); + + pcie->phy = devm_phy_optional_get(dev, "pciephy"); + if (IS_ERR(pcie->phy)) + return PTR_ERR(pcie->phy); + + ret = pcie->ops->get_resources(pcie); + if (ret) + return ret; + + pp = &pcie->pp; + pp->dev = dev; + pp->dbi_base = pcie->dbi; + pp->root_bus_nr = -1; + pp->ops = &qcom_pcie_dw_ops; + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + pp->msi_irq = platform_get_irq_byname(pdev, "msi"); + if (pp->msi_irq < 0) + return pp->msi_irq; + + ret = devm_request_irq(dev, pp->msi_irq, + qcom_pcie_msi_irq_handler, + IRQF_SHARED, "qcom-pcie-msi", pp); + if (ret) { + dev_err(dev, "cannot request msi irq\n"); + return ret; + } + } + + ret = phy_init(pcie->phy); + if (ret) + return ret; + + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(dev, "cannot initialize host\n"); + return ret; + } + + platform_set_drvdata(pdev, pcie); + + return 0; +} + +static int qcom_pcie_remove(struct platform_device *pdev) +{ + struct qcom_pcie *pcie = platform_get_drvdata(pdev); + + qcom_ep_reset_assert(pcie); + phy_power_off(pcie->phy); + phy_exit(pcie->phy); + pcie->ops->deinit(pcie); + + return 0; +} + +static const struct of_device_id qcom_pcie_match[] = { + { .compatible = "qcom,pcie-ipq8064", .data = &ops_v0 }, + { .compatible = "qcom,pcie-apq8064", .data = &ops_v0 }, + { .compatible = "qcom,pcie-apq8084", .data = &ops_v1 }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_pcie_match); + +static struct platform_driver qcom_pcie_driver = { + .probe = qcom_pcie_probe, + .remove = qcom_pcie_remove, + .driver = { + .name = "qcom-pcie", + .of_match_table = qcom_pcie_match, + }, +}; + +module_platform_driver(qcom_pcie_driver); + +MODULE_AUTHOR("Stanimir Varbanov "); +MODULE_DESCRIPTION("Qualcomm PCIe root complex driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index 5c2962646b175ceb9257abf0cb971683c766d366..4edb5181f4e2ca64dc24d8c2cc1ebec46b2d7384 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #define DRV_NAME "rcar-pcie" @@ -94,6 +95,11 @@ #define H1_PCIEPHYDOUTR 0x040014 #define H1_PCIEPHYSR 0x040018 +/* R-Car Gen2 PHY */ +#define GEN2_PCIEPHYADDR 0x780 +#define GEN2_PCIEPHYDATA 0x784 +#define GEN2_PCIEPHYCTRL 0x78c + #define INT_PCI_MSI_NR 32 #define RCONF(x) (PCICONF(0)+(x)) @@ -124,16 +130,7 @@ static inline struct rcar_msi *to_rcar_msi(struct msi_controller *chip) } /* Structure representing the PCIe interface */ -/* - * ARM pcibios functions expect the ARM struct pci_sys_data as the PCI - * sysdata. Add pci_sys_data as the first element in struct gen_pci so - * that when we use a gen_pci pointer as sysdata, it is also a pointer to - * a struct pci_sys_data. - */ struct rcar_pcie { -#ifdef CONFIG_ARM - struct pci_sys_data sys; -#endif struct device *dev; void __iomem *base; struct list_head resources; @@ -576,6 +573,26 @@ static int rcar_pcie_hw_init_h1(struct rcar_pcie *pcie) return -ETIMEDOUT; } +static int rcar_pcie_hw_init_gen2(struct rcar_pcie *pcie) +{ + /* + * These settings come from the R-Car Series, 2nd Generation User's + * Manual, section 50.3.1 (2) Initialization of the physical layer. + */ + rcar_pci_write_reg(pcie, 0x000f0030, GEN2_PCIEPHYADDR); + rcar_pci_write_reg(pcie, 0x00381203, GEN2_PCIEPHYDATA); + rcar_pci_write_reg(pcie, 0x00000001, GEN2_PCIEPHYCTRL); + rcar_pci_write_reg(pcie, 0x00000006, GEN2_PCIEPHYCTRL); + + rcar_pci_write_reg(pcie, 0x000f0054, GEN2_PCIEPHYADDR); + /* The following value is for DC connection, no termination resistor */ + rcar_pci_write_reg(pcie, 0x13802007, GEN2_PCIEPHYDATA); + rcar_pci_write_reg(pcie, 0x00000001, GEN2_PCIEPHYCTRL); + rcar_pci_write_reg(pcie, 0x00000006, GEN2_PCIEPHYCTRL); + + return rcar_pcie_hw_init(pcie); +} + static int rcar_msi_alloc(struct rcar_msi *chip) { int msi; @@ -718,14 +735,16 @@ static int rcar_pcie_enable_msi(struct rcar_pcie *pcie) /* Two irqs are for MSI, but they are also used for non-MSI irqs */ err = devm_request_irq(&pdev->dev, msi->irq1, rcar_pcie_msi_irq, - IRQF_SHARED, rcar_msi_irq_chip.name, pcie); + IRQF_SHARED | IRQF_NO_THREAD, + rcar_msi_irq_chip.name, pcie); if (err < 0) { dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); goto err; } err = devm_request_irq(&pdev->dev, msi->irq2, rcar_pcie_msi_irq, - IRQF_SHARED, rcar_msi_irq_chip.name, pcie); + IRQF_SHARED | IRQF_NO_THREAD, + rcar_msi_irq_chip.name, pcie); if (err < 0) { dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); goto err; @@ -915,9 +934,9 @@ static int rcar_pcie_parse_map_dma_ranges(struct rcar_pcie *pcie, static const struct of_device_id rcar_pcie_of_match[] = { { .compatible = "renesas,pcie-r8a7779", .data = rcar_pcie_hw_init_h1 }, - { .compatible = "renesas,pcie-rcar-gen2", .data = rcar_pcie_hw_init }, - { .compatible = "renesas,pcie-r8a7790", .data = rcar_pcie_hw_init }, - { .compatible = "renesas,pcie-r8a7791", .data = rcar_pcie_hw_init }, + { .compatible = "renesas,pcie-rcar-gen2", .data = rcar_pcie_hw_init_gen2 }, + { .compatible = "renesas,pcie-r8a7790", .data = rcar_pcie_hw_init_gen2 }, + { .compatible = "renesas,pcie-r8a7791", .data = rcar_pcie_hw_init_gen2 }, { .compatible = "renesas,pcie-r8a7795", .data = rcar_pcie_hw_init }, {}, }; @@ -1003,32 +1022,51 @@ static int rcar_pcie_probe(struct platform_device *pdev) if (err) return err; - if (IS_ENABLED(CONFIG_PCI_MSI)) { - err = rcar_pcie_enable_msi(pcie); - if (err < 0) { - dev_err(&pdev->dev, - "failed to enable MSI support: %d\n", - err); - return err; - } - } - of_id = of_match_device(rcar_pcie_of_match, pcie->dev); if (!of_id || !of_id->data) return -EINVAL; hw_init_fn = of_id->data; + pm_runtime_enable(pcie->dev); + err = pm_runtime_get_sync(pcie->dev); + if (err < 0) { + dev_err(pcie->dev, "pm_runtime_get_sync failed\n"); + goto err_pm_disable; + } + /* Failure to get a link might just be that no cards are inserted */ err = hw_init_fn(pcie); if (err) { dev_info(&pdev->dev, "PCIe link down\n"); - return 0; + err = 0; + goto err_pm_put; } data = rcar_pci_read_reg(pcie, MACSR); dev_info(&pdev->dev, "PCIe x%d: link up\n", (data >> 20) & 0x3f); - return rcar_pcie_enable(pcie); + if (IS_ENABLED(CONFIG_PCI_MSI)) { + err = rcar_pcie_enable_msi(pcie); + if (err < 0) { + dev_err(&pdev->dev, + "failed to enable MSI support: %d\n", + err); + goto err_pm_put; + } + } + + err = rcar_pcie_enable(pcie); + if (err) + goto err_pm_put; + + return 0; + +err_pm_put: + pm_runtime_put(pcie->dev); + +err_pm_disable: + pm_runtime_disable(pcie->dev); + return err; } static struct platform_driver rcar_pcie_driver = { diff --git a/drivers/pci/host/pcie-spear13xx.c b/drivers/pci/host/pcie-spear13xx.c index b95b7563c052135bbad5407073685d9ff8930df6..a6cd8233e8c08c8fee6e1df3a1a7d40fdc2250a8 100644 --- a/drivers/pci/host/pcie-spear13xx.c +++ b/drivers/pci/host/pcie-spear13xx.c @@ -279,7 +279,8 @@ static int spear13xx_add_pcie_port(struct pcie_port *pp, return -ENODEV; } ret = devm_request_irq(dev, pp->irq, spear13xx_pcie_irq_handler, - IRQF_SHARED, "spear1340-pcie", pp); + IRQF_SHARED | IRQF_NO_THREAD, + "spear1340-pcie", pp); if (ret) { dev_err(dev, "failed to request irq %d\n", pp->irq); return ret; diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index 3c7a0d580b1ed990d9978efc5927c3a8d2abc8af..4cfa46360d1229976533f3a0c42152c30a282099 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -781,7 +781,8 @@ static int xilinx_pcie_parse_dt(struct xilinx_pcie_port *port) port->irq = irq_of_parse_and_map(node, 0); err = devm_request_irq(dev, port->irq, xilinx_pcie_intr_handler, - IRQF_SHARED, "xilinx-pcie", port); + IRQF_SHARED | IRQF_NO_THREAD, + "xilinx-pcie", port); if (err) { dev_err(dev, "unable to request irq %d\n", port->irq); return err; diff --git a/include/linux/of_pci.h b/include/linux/of_pci.h index 2c51ee78b1c0e31074ee17eb9a341021ee3162e0..f6e9e85164e8bd8dc3c110d01fdca23a504201ba 100644 --- a/include/linux/of_pci.h +++ b/include/linux/of_pci.h @@ -59,6 +59,13 @@ static inline void of_pci_check_probe_only(void) { } int of_pci_get_host_bridge_resources(struct device_node *dev, unsigned char busno, unsigned char bus_max, struct list_head *resources, resource_size_t *io_base); +#else +static inline int of_pci_get_host_bridge_resources(struct device_node *dev, + unsigned char busno, unsigned char bus_max, + struct list_head *resources, resource_size_t *io_base) +{ + return -EINVAL; +} #endif #if defined(CONFIG_OF) && defined(CONFIG_PCI_MSI)