diff options
| author | Bjorn Helgaas <bhelgaas@google.com> | 2014-10-06 09:59:15 -0600 | 
|---|---|---|
| committer | Bjorn Helgaas <bhelgaas@google.com> | 2014-10-06 09:59:15 -0600 | 
| commit | f92d9ee3ab39841d1f29f2d1aa96ff7c74b36ee1 (patch) | |
| tree | c747c9ce1882b9a08d284603717890c5ef81bbf7 | |
| parent | 55dd4175a4bc10d943c159dabef552baaf459a59 (diff) | |
| parent | 767ebaff4ef7235eb49ddec5d48db97b17c37cf5 (diff) | |
Merge branch 'pci/host-xgene' into next
* pci/host-xgene:
  arm64: dts: Add APM X-Gene PCIe device tree nodes
  PCI: xgene: Add APM X-Gene PCIe driver
Conflicts:
	drivers/pci/host/Kconfig
	drivers/pci/host/Makefile
| -rw-r--r-- | Documentation/devicetree/bindings/pci/xgene-pci.txt | 57 | ||||
| -rw-r--r-- | MAINTAINERS | 8 | ||||
| -rw-r--r-- | arch/arm64/boot/dts/apm-mustang.dts | 8 | ||||
| -rw-r--r-- | arch/arm64/boot/dts/apm-storm.dtsi | 165 | ||||
| -rw-r--r-- | drivers/pci/host/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/pci/host/Makefile | 1 | ||||
| -rw-r--r-- | drivers/pci/host/pci-xgene.c | 659 | 
7 files changed, 908 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/pci/xgene-pci.txt b/Documentation/devicetree/bindings/pci/xgene-pci.txt new file mode 100644 index 000000000000..1070b068c7c6 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/xgene-pci.txt @@ -0,0 +1,57 @@ +* AppliedMicro X-Gene PCIe interface + +Required properties: +- device_type: set to "pci" +- compatible: should contain "apm,xgene-pcie" to identify the core. +- reg: A list of physical base address and length for each set of controller +       registers. Must contain an entry for each entry in the reg-names +       property. +- reg-names: Must include the following entries: +  "csr": controller configuration registers. +  "cfg": pcie configuration space registers. +- #address-cells: set to <3> +- #size-cells: set to <2> +- ranges: ranges for the outbound memory, I/O regions. +- dma-ranges: ranges for the inbound memory regions. +- #interrupt-cells: set to <1> +- interrupt-map-mask and interrupt-map: standard PCI properties +	to define the mapping of the PCIe interface to interrupt +	numbers. +- clocks: from common clock binding: handle to pci clock. + +Optional properties: +- status: Either "ok" or "disabled". +- dma-coherent: Present if dma operations are coherent + +Example: + +SoC specific DT Entry: + +	pcie0: pcie@1f2b0000 { +		status = "disabled"; +		device_type = "pci"; +		compatible = "apm,xgene-storm-pcie", "apm,xgene-pcie"; +		#interrupt-cells = <1>; +		#size-cells = <2>; +		#address-cells = <3>; +		reg = < 0x00 0x1f2b0000 0x0 0x00010000   /* Controller registers */ +			0xe0 0xd0000000 0x0 0x00040000>; /* PCI config space */ +		reg-names = "csr", "cfg"; +		ranges = <0x01000000 0x00 0x00000000 0xe0 0x10000000 0x00 0x00010000   /* io */ +			  0x02000000 0x00 0x80000000 0xe1 0x80000000 0x00 0x80000000>; /* mem */ +		dma-ranges = <0x42000000 0x80 0x00000000 0x80 0x00000000 0x00 0x80000000 +			      0x42000000 0x00 0x00000000 0x00 0x00000000 0x80 0x00000000>; +		interrupt-map-mask = <0x0 0x0 0x0 0x7>; +		interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xc2 0x1 +				 0x0 0x0 0x0 0x2 &gic 0x0 0xc3 0x1 +				 0x0 0x0 0x0 0x3 &gic 0x0 0xc4 0x1 +				 0x0 0x0 0x0 0x4 &gic 0x0 0xc5 0x1>; +		dma-coherent; +		clocks = <&pcie0clk 0>; +	}; + + +Board specific DT Entry: +	&pcie0 { +		status = "ok"; +	}; diff --git a/MAINTAINERS b/MAINTAINERS index 07fd7e279700..fac7057ba67c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6868,6 +6868,14 @@ F:	include/linux/pci*  F:	arch/x86/pci/  F:	arch/x86/kernel/quirks.c +PCI DRIVER FOR APPLIEDMICRO XGENE +M:	Tanmay Inamdar <tinamdar@apm.com> +L:	linux-pci@vger.kernel.org +L:	linux-arm-kernel@lists.infradead.org +S:	Maintained +F:	Documentation/devicetree/bindings/pci/xgene-pci.txt +F:	drivers/pci/host/pci-xgene.c +  PCI DRIVER FOR IMX6  M:	Richard Zhu <r65037@freescale.com>  M:	Lucas Stach <l.stach@pengutronix.de> diff --git a/arch/arm64/boot/dts/apm-mustang.dts b/arch/arm64/boot/dts/apm-mustang.dts index b2f56229aa5e..f64900052f4e 100644 --- a/arch/arm64/boot/dts/apm-mustang.dts +++ b/arch/arm64/boot/dts/apm-mustang.dts @@ -25,6 +25,14 @@  	};  }; +&pcie0clk { +	status = "ok"; +}; + +&pcie0 { +	status = "ok"; +}; +  &serial0 {  	status = "ok";  }; diff --git a/arch/arm64/boot/dts/apm-storm.dtsi b/arch/arm64/boot/dts/apm-storm.dtsi index c0aceef7f5b3..403197a0e621 100644 --- a/arch/arm64/boot/dts/apm-storm.dtsi +++ b/arch/arm64/boot/dts/apm-storm.dtsi @@ -269,6 +269,171 @@  				enable-mask = <0x2>;  				clock-output-names = "rtcclk";  			}; + +			pcie0clk: pcie0clk@1f2bc000 { +				status = "disabled"; +				compatible = "apm,xgene-device-clock"; +				#clock-cells = <1>; +				clocks = <&socplldiv2 0>; +				reg = <0x0 0x1f2bc000 0x0 0x1000>; +				reg-names = "csr-reg"; +				clock-output-names = "pcie0clk"; +			}; + +			pcie1clk: pcie1clk@1f2cc000 { +				status = "disabled"; +				compatible = "apm,xgene-device-clock"; +				#clock-cells = <1>; +				clocks = <&socplldiv2 0>; +				reg = <0x0 0x1f2cc000 0x0 0x1000>; +				reg-names = "csr-reg"; +				clock-output-names = "pcie1clk"; +			}; + +			pcie2clk: pcie2clk@1f2dc000 { +				status = "disabled"; +				compatible = "apm,xgene-device-clock"; +				#clock-cells = <1>; +				clocks = <&socplldiv2 0>; +				reg = <0x0 0x1f2dc000 0x0 0x1000>; +				reg-names = "csr-reg"; +				clock-output-names = "pcie2clk"; +			}; + +			pcie3clk: pcie3clk@1f50c000 { +				status = "disabled"; +				compatible = "apm,xgene-device-clock"; +				#clock-cells = <1>; +				clocks = <&socplldiv2 0>; +				reg = <0x0 0x1f50c000 0x0 0x1000>; +				reg-names = "csr-reg"; +				clock-output-names = "pcie3clk"; +			}; + +			pcie4clk: pcie4clk@1f51c000 { +				status = "disabled"; +				compatible = "apm,xgene-device-clock"; +				#clock-cells = <1>; +				clocks = <&socplldiv2 0>; +				reg = <0x0 0x1f51c000 0x0 0x1000>; +				reg-names = "csr-reg"; +				clock-output-names = "pcie4clk"; +			}; +		}; + +		pcie0: pcie@1f2b0000 { +			status = "disabled"; +			device_type = "pci"; +			compatible = "apm,xgene-storm-pcie", "apm,xgene-pcie"; +			#interrupt-cells = <1>; +			#size-cells = <2>; +			#address-cells = <3>; +			reg = < 0x00 0x1f2b0000 0x0 0x00010000   /* Controller registers */ +				0xe0 0xd0000000 0x0 0x00040000>; /* PCI config space */ +			reg-names = "csr", "cfg"; +			ranges = <0x01000000 0x00 0x00000000 0xe0 0x10000000 0x00 0x00010000   /* io */ +				  0x02000000 0x00 0x80000000 0xe1 0x80000000 0x00 0x80000000>; /* mem */ +			dma-ranges = <0x42000000 0x80 0x00000000 0x80 0x00000000 0x00 0x80000000 +				      0x42000000 0x00 0x00000000 0x00 0x00000000 0x80 0x00000000>; +			interrupt-map-mask = <0x0 0x0 0x0 0x7>; +			interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xc2 0x1 +					 0x0 0x0 0x0 0x2 &gic 0x0 0xc3 0x1 +					 0x0 0x0 0x0 0x3 &gic 0x0 0xc4 0x1 +					 0x0 0x0 0x0 0x4 &gic 0x0 0xc5 0x1>; +			dma-coherent; +			clocks = <&pcie0clk 0>; +		}; + +		pcie1: pcie@1f2c0000 { +			status = "disabled"; +			device_type = "pci"; +			compatible = "apm,xgene-storm-pcie", "apm,xgene-pcie"; +			#interrupt-cells = <1>; +			#size-cells = <2>; +			#address-cells = <3>; +			reg = < 0x00 0x1f2c0000 0x0 0x00010000   /* Controller registers */ +				0xd0 0xd0000000 0x0 0x00040000>; /* PCI config space */ +			reg-names = "csr", "cfg"; +			ranges = <0x01000000 0x0 0x00000000 0xd0 0x10000000 0x00 0x00010000   /* io  */ +				  0x02000000 0x0 0x80000000 0xd1 0x80000000 0x00 0x80000000>; /* mem */ +			dma-ranges = <0x42000000 0x80 0x00000000 0x80 0x00000000 0x00 0x80000000 +				      0x42000000 0x00 0x00000000 0x00 0x00000000 0x80 0x00000000>; +			interrupt-map-mask = <0x0 0x0 0x0 0x7>; +			interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xc8 0x1 +					 0x0 0x0 0x0 0x2 &gic 0x0 0xc9 0x1 +					 0x0 0x0 0x0 0x3 &gic 0x0 0xca 0x1 +					 0x0 0x0 0x0 0x4 &gic 0x0 0xcb 0x1>; +			dma-coherent; +			clocks = <&pcie1clk 0>; +		}; + +		pcie2: pcie@1f2d0000 { +			status = "disabled"; +			device_type = "pci"; +			compatible = "apm,xgene-storm-pcie", "apm,xgene-pcie"; +			#interrupt-cells = <1>; +			#size-cells = <2>; +			#address-cells = <3>; +			reg =  < 0x00 0x1f2d0000 0x0 0x00010000   /* Controller registers */ +				 0x90 0xd0000000 0x0 0x00040000>; /* PCI config space */ +			reg-names = "csr", "cfg"; +			ranges = <0x01000000 0x0 0x00000000 0x90 0x10000000 0x0 0x00010000   /* io  */ +				  0x02000000 0x0 0x80000000 0x91 0x80000000 0x0 0x80000000>; /* mem */ +			dma-ranges = <0x42000000 0x80 0x00000000 0x80 0x00000000 0x00 0x80000000 +				      0x42000000 0x00 0x00000000 0x00 0x00000000 0x80 0x00000000>; +			interrupt-map-mask = <0x0 0x0 0x0 0x7>; +			interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xce 0x1 +					 0x0 0x0 0x0 0x2 &gic 0x0 0xcf 0x1 +					 0x0 0x0 0x0 0x3 &gic 0x0 0xd0 0x1 +					 0x0 0x0 0x0 0x4 &gic 0x0 0xd1 0x1>; +			dma-coherent; +			clocks = <&pcie2clk 0>; +		}; + +		pcie3: pcie@1f500000 { +			status = "disabled"; +			device_type = "pci"; +			compatible = "apm,xgene-storm-pcie", "apm,xgene-pcie"; +			#interrupt-cells = <1>; +			#size-cells = <2>; +			#address-cells = <3>; +			reg = < 0x00 0x1f500000 0x0 0x00010000   /* Controller registers */ +				0xa0 0xd0000000 0x0 0x00040000>; /* PCI config space */ +			reg-names = "csr", "cfg"; +			ranges = <0x01000000 0x0 0x00000000 0xa0 0x10000000 0x0 0x00010000   /* io   */ +				  0x02000000 0x0 0x80000000 0xa1 0x80000000 0x0 0x80000000>; /* mem  */ +			dma-ranges = <0x42000000 0x80 0x00000000 0x80 0x00000000 0x00 0x80000000 +				      0x42000000 0x00 0x00000000 0x00 0x00000000 0x80 0x00000000>; +			interrupt-map-mask = <0x0 0x0 0x0 0x7>; +			interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xd4 0x1 +					 0x0 0x0 0x0 0x2 &gic 0x0 0xd5 0x1 +					 0x0 0x0 0x0 0x3 &gic 0x0 0xd6 0x1 +					 0x0 0x0 0x0 0x4 &gic 0x0 0xd7 0x1>; +			dma-coherent; +			clocks = <&pcie3clk 0>; +		}; + +		pcie4: pcie@1f510000 { +			status = "disabled"; +			device_type = "pci"; +			compatible = "apm,xgene-storm-pcie", "apm,xgene-pcie"; +			#interrupt-cells = <1>; +			#size-cells = <2>; +			#address-cells = <3>; +			reg = < 0x00 0x1f510000 0x0 0x00010000   /* Controller registers */ +				0xc0 0xd0000000 0x0 0x00200000>; /* PCI config space */ +			reg-names = "csr", "cfg"; +			ranges = <0x01000000 0x0 0x00000000 0xc0 0x10000000 0x0 0x00010000   /* io  */ +				  0x02000000 0x0 0x80000000 0xc1 0x80000000 0x0 0x80000000>; /* mem */ +			dma-ranges = <0x42000000 0x80 0x00000000 0x80 0x00000000 0x00 0x80000000 +				      0x42000000 0x00 0x00000000 0x00 0x00000000 0x80 0x00000000>; +			interrupt-map-mask = <0x0 0x0 0x0 0x7>; +			interrupt-map = <0x0 0x0 0x0 0x1 &gic 0x0 0xda 0x1 +					 0x0 0x0 0x0 0x2 &gic 0x0 0xdb 0x1 +					 0x0 0x0 0x0 0x3 &gic 0x0 0xdc 0x1 +					 0x0 0x0 0x0 0x4 &gic 0x0 0xdd 0x1>; +			dma-coherent; +			clocks = <&pcie4clk 0>;  		};  		serial0: serial@1c020000 { diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 34134d64f35a..413c3611886a 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -63,7 +63,6 @@ config PCIE_SPEAR13XX  	help  	  Say Y here if you want PCIe support on SPEAr13XX SoCs. -  config PCI_KEYSTONE  	bool "TI Keystone PCIe controller"  	depends on ARCH_KEYSTONE @@ -82,4 +81,14 @@ config PCIE_XILINX  	  Say 'Y' here if you want kernel to support the Xilinx AXI PCIe  	  Host Bridge driver. +config PCI_XGENE +	bool "X-Gene PCIe controller" +	depends on ARCH_XGENE +	depends on OF +	select PCIEPORTBUS +	help +	  Say Y here if you want internal PCI support on APM X-Gene SoC. +	  There are 5 internal PCIe ports available. Each port is GEN3 capable +	  and have varied lanes from x1 to x8. +  endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 182929cdbcd9..26b3461d68d7 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_PCI_HOST_GENERIC) += pci-host-generic.o  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 diff --git a/drivers/pci/host/pci-xgene.c b/drivers/pci/host/pci-xgene.c new file mode 100644 index 000000000000..9ecabfa8c634 --- /dev/null +++ b/drivers/pci/host/pci-xgene.c @@ -0,0 +1,659 @@ +/** + * APM X-Gene PCIe Driver + * + * Copyright (c) 2014 Applied Micro Circuits Corporation. + * + * Author: Tanmay Inamdar <tinamdar@apm.com>. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 <linux/clk-private.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/memblock.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_pci.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define PCIECORE_CTLANDSTATUS		0x50 +#define PIM1_1L				0x80 +#define IBAR2				0x98 +#define IR2MSK				0x9c +#define PIM2_1L				0xa0 +#define IBAR3L				0xb4 +#define IR3MSKL				0xbc +#define PIM3_1L				0xc4 +#define OMR1BARL			0x100 +#define OMR2BARL			0x118 +#define OMR3BARL			0x130 +#define CFGBARL				0x154 +#define CFGBARH				0x158 +#define CFGCTL				0x15c +#define RTDID				0x160 +#define BRIDGE_CFG_0			0x2000 +#define BRIDGE_CFG_4			0x2010 +#define BRIDGE_STATUS_0			0x2600 + +#define LINK_UP_MASK			0x00000100 +#define AXI_EP_CFG_ACCESS		0x10000 +#define EN_COHERENCY			0xF0000000 +#define EN_REG				0x00000001 +#define OB_LO_IO			0x00000002 +#define XGENE_PCIE_VENDORID		0x10E8 +#define XGENE_PCIE_DEVICEID		0xE004 +#define SZ_1T				(SZ_1G*1024ULL) +#define PIPE_PHY_RATE_RD(src)		((0xc000 & (u32)(src)) >> 0xe) + +struct xgene_pcie_port { +	struct device_node	*node; +	struct device		*dev; +	struct clk		*clk; +	void __iomem		*csr_base; +	void __iomem		*cfg_base; +	unsigned long		cfg_addr; +	bool			link_up; +}; + +static inline u32 pcie_bar_low_val(u32 addr, u32 flags) +{ +	return (addr & PCI_BASE_ADDRESS_MEM_MASK) | flags; +} + +/* PCIe Configuration Out/In */ +static inline void xgene_pcie_cfg_out32(void __iomem *addr, int offset, u32 val) +{ +	writel(val, addr + offset); +} + +static inline void xgene_pcie_cfg_out16(void __iomem *addr, int offset, u16 val) +{ +	u32 val32 = readl(addr + (offset & ~0x3)); + +	switch (offset & 0x3) { +	case 2: +		val32 &= ~0xFFFF0000; +		val32 |= (u32)val << 16; +		break; +	case 0: +	default: +		val32 &= ~0xFFFF; +		val32 |= val; +		break; +	} +	writel(val32, addr + (offset & ~0x3)); +} + +static inline void xgene_pcie_cfg_out8(void __iomem *addr, int offset, u8 val) +{ +	u32 val32 = readl(addr + (offset & ~0x3)); + +	switch (offset & 0x3) { +	case 0: +		val32 &= ~0xFF; +		val32 |= val; +		break; +	case 1: +		val32 &= ~0xFF00; +		val32 |= (u32)val << 8; +		break; +	case 2: +		val32 &= ~0xFF0000; +		val32 |= (u32)val << 16; +		break; +	case 3: +	default: +		val32 &= ~0xFF000000; +		val32 |= (u32)val << 24; +		break; +	} +	writel(val32, addr + (offset & ~0x3)); +} + +static inline void xgene_pcie_cfg_in32(void __iomem *addr, int offset, u32 *val) +{ +	*val = readl(addr + offset); +} + +static inline void xgene_pcie_cfg_in16(void __iomem *addr, int offset, u32 *val) +{ +	*val = readl(addr + (offset & ~0x3)); + +	switch (offset & 0x3) { +	case 2: +		*val >>= 16; +		break; +	} + +	*val &= 0xFFFF; +} + +static inline void xgene_pcie_cfg_in8(void __iomem *addr, int offset, u32 *val) +{ +	*val = readl(addr + (offset & ~0x3)); + +	switch (offset & 0x3) { +	case 3: +		*val = *val >> 24; +		break; +	case 2: +		*val = *val >> 16; +		break; +	case 1: +		*val = *val >> 8; +		break; +	} +	*val &= 0xFF; +} + +/* + * When the address bit [17:16] is 2'b01, the Configuration access will be + * treated as Type 1 and it will be forwarded to external PCIe device. + */ +static void __iomem *xgene_pcie_get_cfg_base(struct pci_bus *bus) +{ +	struct xgene_pcie_port *port = bus->sysdata; + +	if (bus->number >= (bus->primary + 1)) +		return port->cfg_base + AXI_EP_CFG_ACCESS; + +	return port->cfg_base; +} + +/* + * For Configuration request, RTDID register is used as Bus Number, + * Device Number and Function number of the header fields. + */ +static void xgene_pcie_set_rtdid_reg(struct pci_bus *bus, uint devfn) +{ +	struct xgene_pcie_port *port = bus->sysdata; +	unsigned int b, d, f; +	u32 rtdid_val = 0; + +	b = bus->number; +	d = PCI_SLOT(devfn); +	f = PCI_FUNC(devfn); + +	if (!pci_is_root_bus(bus)) +		rtdid_val = (b << 8) | (d << 3) | f; + +	writel(rtdid_val, port->csr_base + RTDID); +	/* read the register back to ensure flush */ +	readl(port->csr_base + RTDID); +} + +/* + * X-Gene PCIe port uses BAR0-BAR1 of RC's configuration space as + * the translation from PCI bus to native BUS.  Entire DDR region + * is mapped into PCIe space using these registers, so it can be + * reached by DMA from EP devices.  The BAR0/1 of bridge should be + * hidden during enumeration to avoid the sizing and resource allocation + * by PCIe core. + */ +static bool xgene_pcie_hide_rc_bars(struct pci_bus *bus, int offset) +{ +	if (pci_is_root_bus(bus) && ((offset == PCI_BASE_ADDRESS_0) || +				     (offset == PCI_BASE_ADDRESS_1))) +		return true; + +	return false; +} + +static int xgene_pcie_read_config(struct pci_bus *bus, unsigned int devfn, +				  int offset, int len, u32 *val) +{ +	struct xgene_pcie_port *port = bus->sysdata; +	void __iomem *addr; + +	if ((pci_is_root_bus(bus) && devfn != 0) || !port->link_up) +		return PCIBIOS_DEVICE_NOT_FOUND; + +	if (xgene_pcie_hide_rc_bars(bus, offset)) { +		*val = 0; +		return PCIBIOS_SUCCESSFUL; +	} + +	xgene_pcie_set_rtdid_reg(bus, devfn); +	addr = xgene_pcie_get_cfg_base(bus); +	switch (len) { +	case 1: +		xgene_pcie_cfg_in8(addr, offset, val); +		break; +	case 2: +		xgene_pcie_cfg_in16(addr, offset, val); +		break; +	default: +		xgene_pcie_cfg_in32(addr, offset, val); +		break; +	} + +	return PCIBIOS_SUCCESSFUL; +} + +static int xgene_pcie_write_config(struct pci_bus *bus, unsigned int devfn, +				   int offset, int len, u32 val) +{ +	struct xgene_pcie_port *port = bus->sysdata; +	void __iomem *addr; + +	if ((pci_is_root_bus(bus) && devfn != 0) || !port->link_up) +		return PCIBIOS_DEVICE_NOT_FOUND; + +	if (xgene_pcie_hide_rc_bars(bus, offset)) +		return PCIBIOS_SUCCESSFUL; + +	xgene_pcie_set_rtdid_reg(bus, devfn); +	addr = xgene_pcie_get_cfg_base(bus); +	switch (len) { +	case 1: +		xgene_pcie_cfg_out8(addr, offset, (u8)val); +		break; +	case 2: +		xgene_pcie_cfg_out16(addr, offset, (u16)val); +		break; +	default: +		xgene_pcie_cfg_out32(addr, offset, val); +		break; +	} + +	return PCIBIOS_SUCCESSFUL; +} + +static struct pci_ops xgene_pcie_ops = { +	.read = xgene_pcie_read_config, +	.write = xgene_pcie_write_config +}; + +static u64 xgene_pcie_set_ib_mask(void __iomem *csr_base, u32 addr, +				  u32 flags, u64 size) +{ +	u64 mask = (~(size - 1) & PCI_BASE_ADDRESS_MEM_MASK) | flags; +	u32 val32 = 0; +	u32 val; + +	val32 = readl(csr_base + addr); +	val = (val32 & 0x0000ffff) | (lower_32_bits(mask) << 16); +	writel(val, csr_base + addr); + +	val32 = readl(csr_base + addr + 0x04); +	val = (val32 & 0xffff0000) | (lower_32_bits(mask) >> 16); +	writel(val, csr_base + addr + 0x04); + +	val32 = readl(csr_base + addr + 0x04); +	val = (val32 & 0x0000ffff) | (upper_32_bits(mask) << 16); +	writel(val, csr_base + addr + 0x04); + +	val32 = readl(csr_base + addr + 0x08); +	val = (val32 & 0xffff0000) | (upper_32_bits(mask) >> 16); +	writel(val, csr_base + addr + 0x08); + +	return mask; +} + +static void xgene_pcie_linkup(struct xgene_pcie_port *port, +				   u32 *lanes, u32 *speed) +{ +	void __iomem *csr_base = port->csr_base; +	u32 val32; + +	port->link_up = false; +	val32 = readl(csr_base + PCIECORE_CTLANDSTATUS); +	if (val32 & LINK_UP_MASK) { +		port->link_up = true; +		*speed = PIPE_PHY_RATE_RD(val32); +		val32 = readl(csr_base + BRIDGE_STATUS_0); +		*lanes = val32 >> 26; +	} +} + +static int xgene_pcie_init_port(struct xgene_pcie_port *port) +{ +	int rc; + +	port->clk = clk_get(port->dev, NULL); +	if (IS_ERR(port->clk)) { +		dev_err(port->dev, "clock not available\n"); +		return -ENODEV; +	} + +	rc = clk_prepare_enable(port->clk); +	if (rc) { +		dev_err(port->dev, "clock enable failed\n"); +		return rc; +	} + +	return 0; +} + +static int xgene_pcie_map_reg(struct xgene_pcie_port *port, +			      struct platform_device *pdev) +{ +	struct resource *res; + +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr"); +	port->csr_base = devm_ioremap_resource(port->dev, res); +	if (IS_ERR(port->csr_base)) +		return PTR_ERR(port->csr_base); + +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg"); +	port->cfg_base = devm_ioremap_resource(port->dev, res); +	if (IS_ERR(port->cfg_base)) +		return PTR_ERR(port->cfg_base); +	port->cfg_addr = res->start; + +	return 0; +} + +static void xgene_pcie_setup_ob_reg(struct xgene_pcie_port *port, +				    struct resource *res, u32 offset, +				    u64 cpu_addr, u64 pci_addr) +{ +	void __iomem *base = port->csr_base + offset; +	resource_size_t size = resource_size(res); +	u64 restype = resource_type(res); +	u64 mask = 0; +	u32 min_size; +	u32 flag = EN_REG; + +	if (restype == IORESOURCE_MEM) { +		min_size = SZ_128M; +	} else { +		min_size = 128; +		flag |= OB_LO_IO; +	} + +	if (size >= min_size) +		mask = ~(size - 1) | flag; +	else +		dev_warn(port->dev, "res size 0x%llx less than minimum 0x%x\n", +			 (u64)size, min_size); + +	writel(lower_32_bits(cpu_addr), base); +	writel(upper_32_bits(cpu_addr), base + 0x04); +	writel(lower_32_bits(mask), base + 0x08); +	writel(upper_32_bits(mask), base + 0x0c); +	writel(lower_32_bits(pci_addr), base + 0x10); +	writel(upper_32_bits(pci_addr), base + 0x14); +} + +static void xgene_pcie_setup_cfg_reg(void __iomem *csr_base, u64 addr) +{ +	writel(lower_32_bits(addr), csr_base + CFGBARL); +	writel(upper_32_bits(addr), csr_base + CFGBARH); +	writel(EN_REG, csr_base + CFGCTL); +} + +static int xgene_pcie_map_ranges(struct xgene_pcie_port *port, +				 struct list_head *res, +				 resource_size_t io_base) +{ +	struct pci_host_bridge_window *window; +	struct device *dev = port->dev; +	int ret; + +	list_for_each_entry(window, res, list) { +		struct resource *res = window->res; +		u64 restype = resource_type(res); + +		dev_dbg(port->dev, "%pR\n", res); + +		switch (restype) { +		case IORESOURCE_IO: +			xgene_pcie_setup_ob_reg(port, res, OMR3BARL, io_base, +						res->start - window->offset); +			ret = pci_remap_iospace(res, io_base); +			if (ret < 0) +				return ret; +			break; +		case IORESOURCE_MEM: +			xgene_pcie_setup_ob_reg(port, res, OMR1BARL, res->start, +						res->start - window->offset); +			break; +		case IORESOURCE_BUS: +			break; +		default: +			dev_err(dev, "invalid resource %pR\n", res); +			return -EINVAL; +		} +	} +	xgene_pcie_setup_cfg_reg(port->csr_base, port->cfg_addr); + +	return 0; +} + +static void xgene_pcie_setup_pims(void *addr, u64 pim, u64 size) +{ +	writel(lower_32_bits(pim), addr); +	writel(upper_32_bits(pim) | EN_COHERENCY, addr + 0x04); +	writel(lower_32_bits(size), addr + 0x10); +	writel(upper_32_bits(size), addr + 0x14); +} + +/* + * X-Gene PCIe support maximum 3 inbound memory regions + * This function helps to select a region based on size of region + */ +static int xgene_pcie_select_ib_reg(u8 *ib_reg_mask, u64 size) +{ +	if ((size > 4) && (size < SZ_16M) && !(*ib_reg_mask & (1 << 1))) { +		*ib_reg_mask |= (1 << 1); +		return 1; +	} + +	if ((size > SZ_1K) && (size < SZ_1T) && !(*ib_reg_mask & (1 << 0))) { +		*ib_reg_mask |= (1 << 0); +		return 0; +	} + +	if ((size > SZ_1M) && (size < SZ_1T) && !(*ib_reg_mask & (1 << 2))) { +		*ib_reg_mask |= (1 << 2); +		return 2; +	} + +	return -EINVAL; +} + +static void xgene_pcie_setup_ib_reg(struct xgene_pcie_port *port, +				    struct of_pci_range *range, u8 *ib_reg_mask) +{ +	void __iomem *csr_base = port->csr_base; +	void __iomem *cfg_base = port->cfg_base; +	void *bar_addr; +	void *pim_addr; +	u64 cpu_addr = range->cpu_addr; +	u64 pci_addr = range->pci_addr; +	u64 size = range->size; +	u64 mask = ~(size - 1) | EN_REG; +	u32 flags = PCI_BASE_ADDRESS_MEM_TYPE_64; +	u32 bar_low; +	int region; + +	region = xgene_pcie_select_ib_reg(ib_reg_mask, range->size); +	if (region < 0) { +		dev_warn(port->dev, "invalid pcie dma-range config\n"); +		return; +	} + +	if (range->flags & IORESOURCE_PREFETCH) +		flags |= PCI_BASE_ADDRESS_MEM_PREFETCH; + +	bar_low = pcie_bar_low_val((u32)cpu_addr, flags); +	switch (region) { +	case 0: +		xgene_pcie_set_ib_mask(csr_base, BRIDGE_CFG_4, flags, size); +		bar_addr = cfg_base + PCI_BASE_ADDRESS_0; +		writel(bar_low, bar_addr); +		writel(upper_32_bits(cpu_addr), bar_addr + 0x4); +		pim_addr = csr_base + PIM1_1L; +		break; +	case 1: +		bar_addr = csr_base + IBAR2; +		writel(bar_low, bar_addr); +		writel(lower_32_bits(mask), csr_base + IR2MSK); +		pim_addr = csr_base + PIM2_1L; +		break; +	case 2: +		bar_addr = csr_base + IBAR3L; +		writel(bar_low, bar_addr); +		writel(upper_32_bits(cpu_addr), bar_addr + 0x4); +		writel(lower_32_bits(mask), csr_base + IR3MSKL); +		writel(upper_32_bits(mask), csr_base + IR3MSKL + 0x4); +		pim_addr = csr_base + PIM3_1L; +		break; +	} + +	xgene_pcie_setup_pims(pim_addr, pci_addr, ~(size - 1)); +} + +static int pci_dma_range_parser_init(struct of_pci_range_parser *parser, +				     struct device_node *node) +{ +	const int na = 3, ns = 2; +	int rlen; + +	parser->node = node; +	parser->pna = of_n_addr_cells(node); +	parser->np = parser->pna + na + ns; + +	parser->range = of_get_property(node, "dma-ranges", &rlen); +	if (!parser->range) +		return -ENOENT; +	parser->end = parser->range + rlen / sizeof(__be32); + +	return 0; +} + +static int xgene_pcie_parse_map_dma_ranges(struct xgene_pcie_port *port) +{ +	struct device_node *np = port->node; +	struct of_pci_range range; +	struct of_pci_range_parser parser; +	struct device *dev = port->dev; +	u8 ib_reg_mask = 0; + +	if (pci_dma_range_parser_init(&parser, np)) { +		dev_err(dev, "missing dma-ranges property\n"); +		return -EINVAL; +	} + +	/* Get the dma-ranges from DT */ +	for_each_of_pci_range(&parser, &range) { +		u64 end = range.cpu_addr + range.size - 1; + +		dev_dbg(port->dev, "0x%08x 0x%016llx..0x%016llx -> 0x%016llx\n", +			range.flags, range.cpu_addr, end, range.pci_addr); +		xgene_pcie_setup_ib_reg(port, &range, &ib_reg_mask); +	} +	return 0; +} + +/* clear BAR configuration which was done by firmware */ +static void xgene_pcie_clear_config(struct xgene_pcie_port *port) +{ +	int i; + +	for (i = PIM1_1L; i <= CFGCTL; i += 4) +		writel(0x0, port->csr_base + i); +} + +static int xgene_pcie_setup(struct xgene_pcie_port *port, +			    struct list_head *res, +			    resource_size_t io_base) +{ +	u32 val, lanes = 0, speed = 0; +	int ret; + +	xgene_pcie_clear_config(port); + +	/* setup the vendor and device IDs correctly */ +	val = (XGENE_PCIE_DEVICEID << 16) | XGENE_PCIE_VENDORID; +	writel(val, port->csr_base + BRIDGE_CFG_0); + +	ret = xgene_pcie_map_ranges(port, res, io_base); +	if (ret) +		return ret; + +	ret = xgene_pcie_parse_map_dma_ranges(port); +	if (ret) +		return ret; + +	xgene_pcie_linkup(port, &lanes, &speed); +	if (!port->link_up) +		dev_info(port->dev, "(rc) link down\n"); +	else +		dev_info(port->dev, "(rc) x%d gen-%d link up\n", +				lanes, speed + 1); +	return 0; +} + +static int xgene_pcie_probe_bridge(struct platform_device *pdev) +{ +	struct device_node *dn = pdev->dev.of_node; +	struct xgene_pcie_port *port; +	resource_size_t iobase = 0; +	struct pci_bus *bus; +	int ret; +	LIST_HEAD(res); + +	port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL); +	if (!port) +		return -ENOMEM; +	port->node = of_node_get(pdev->dev.of_node); +	port->dev = &pdev->dev; + +	ret = xgene_pcie_map_reg(port, pdev); +	if (ret) +		return ret; + +	ret = xgene_pcie_init_port(port); +	if (ret) +		return ret; + +	ret = of_pci_get_host_bridge_resources(dn, 0, 0xff, &res, &iobase); +	if (ret) +		return ret; + +	ret = xgene_pcie_setup(port, &res, iobase); +	if (ret) +		return ret; + +	bus = pci_scan_root_bus(&pdev->dev, 0, &xgene_pcie_ops, port, &res); +	if (!bus) +		return -ENOMEM; + +	platform_set_drvdata(pdev, port); +	return 0; +} + +static const struct of_device_id xgene_pcie_match_table[] = { +	{.compatible = "apm,xgene-pcie",}, +	{}, +}; + +static struct platform_driver xgene_pcie_driver = { +	.driver = { +		   .name = "xgene-pcie", +		   .owner = THIS_MODULE, +		   .of_match_table = of_match_ptr(xgene_pcie_match_table), +	}, +	.probe = xgene_pcie_probe_bridge, +}; +module_platform_driver(xgene_pcie_driver); + +MODULE_AUTHOR("Tanmay Inamdar <tinamdar@apm.com>"); +MODULE_DESCRIPTION("APM X-Gene PCIe driver"); +MODULE_LICENSE("GPL v2");  | 
