Merge branches 'pci/host-altera', 'pci/host-imx6', 'pci/host-keystone', 'pci/host-rcar', 'pci/host-tegra', 'pci/host-thunder', 'pci/host-vmd', 'pci/host-xilinx' and 'pci/host-xilinx-nwl' into next
* pci/host-altera: PCI: altera: Fix altera_pcie_link_is_up() * pci/host-imx6: PCI: imx6: Add DT bindings to configure PHY Tx driver settings * pci/host-keystone: PCI: keystone: Defer probing if devm_phy_get() returns -EPROBE_DEFER * pci/host-rcar: PCI: rcar: Depend on ARCH_RENESAS, not ARCH_SHMOBILE * pci/host-tegra: PCI: tegra: Remove misleading PHYS_OFFSET PCI: tegra: Track bus -> CPU mapping PCI: tegra: Remove unused struct tegra_pcie.num_ports field PCI: tegra: Implement ->{add,remove}_bus() callbacks PCI: Add pci_ops.{add,remove}_bus() callbacks * pci/host-thunder: PCI: thunder: Add driver for ThunderX-pass{1,2} on-chip devices PCI: thunder: Add PCIe host driver for ThunderX processors PCI: generic: Expose pci_host_common_probe() for use by other drivers PCI: generic: Add pci_host_common_probe(), based on gen_pci_probe() PCI: generic: Move structure definitions to separate header file * pci/host-vmd: x86/PCI: VMD: Attach VMD resources to parent domain's resource tree x86/PCI: VMD: Set bus resource start to 0 x86/PCI: VMD: Document code for maintainability * pci/host-xilinx: microblaze/PCI: Support generic Xilinx AXI PCIe Host Bridge IP driver PCI: xilinx: Update Zynq binding with Microblaze node PCI: xilinx: Don't call pci_fixup_irqs() on Microblaze PCI: xilinx: Remove dependency on ARM-specific struct hw_pci PCI: xilinx: Use of_pci_get_host_bridge_resources() to parse DT * pci/host-xilinx-nwl: PCI: xilinx-nwl: Add support for Xilinx NWL PCIe Host Controller
This commit is contained in:
parent
18e5e6913b
eff31f4002
28e3abe591
25de15c958
304e6d572b
e32faa303f
7b6e7ba8e8
2c2c5c5cd2
01cf9d524f
ab597d35ef
commit
c334f9c89e
@ -13,6 +13,13 @@ Required properties:
|
||||
- clock-names: Must include the following additional entries:
|
||||
- "pcie_phy"
|
||||
|
||||
Optional properties:
|
||||
- fsl,tx-deemph-gen1: Gen1 De-emphasis value. Default: 0
|
||||
- fsl,tx-deemph-gen2-3p5db: Gen2 (3.5db) De-emphasis value. Default: 0
|
||||
- fsl,tx-deemph-gen2-6db: Gen2 (6db) De-emphasis value. Default: 20
|
||||
- fsl,tx-swing-full: Gen2 TX SWING FULL value. Default: 127
|
||||
- fsl,tx-swing-low: TX launch amplitude swing_low value. Default: 127
|
||||
|
||||
Example:
|
||||
|
||||
pcie@0x01000000 {
|
||||
|
30
Documentation/devicetree/bindings/pci/pci-thunder-ecam.txt
Normal file
30
Documentation/devicetree/bindings/pci/pci-thunder-ecam.txt
Normal file
@ -0,0 +1,30 @@
|
||||
* ThunderX PCI host controller for pass-1.x silicon
|
||||
|
||||
Firmware-initialized PCI host controller to on-chip devices found on
|
||||
some Cavium ThunderX processors. These devices have ECAM-based config
|
||||
access, but the BARs are all at fixed addresses. We handle the fixed
|
||||
addresses by synthesizing Enhanced Allocation (EA) capabilities for
|
||||
these devices.
|
||||
|
||||
The properties and their meanings are identical to those described in
|
||||
host-generic-pci.txt except as listed below.
|
||||
|
||||
Properties of the host controller node that differ from
|
||||
host-generic-pci.txt:
|
||||
|
||||
- compatible : Must be "cavium,pci-host-thunder-ecam"
|
||||
|
||||
Example:
|
||||
|
||||
pcie@84b000000000 {
|
||||
compatible = "cavium,pci-host-thunder-ecam";
|
||||
device_type = "pci";
|
||||
msi-parent = <&its>;
|
||||
msi-map = <0 &its 0x30000 0x10000>;
|
||||
bus-range = <0 31>;
|
||||
#size-cells = <2>;
|
||||
#address-cells = <3>;
|
||||
#stream-id-cells = <1>;
|
||||
reg = <0x84b0 0x00000000 0 0x02000000>; /* Configuration space */
|
||||
ranges = <0x03000000 0x8180 0x00000000 0x8180 0x00000000 0x80 0x00000000>; /* mem ranges */
|
||||
};
|
43
Documentation/devicetree/bindings/pci/pci-thunder-pem.txt
Normal file
43
Documentation/devicetree/bindings/pci/pci-thunder-pem.txt
Normal file
@ -0,0 +1,43 @@
|
||||
* ThunderX PEM PCIe host controller
|
||||
|
||||
Firmware-initialized PCI host controller found on some Cavium
|
||||
ThunderX processors.
|
||||
|
||||
The properties and their meanings are identical to those described in
|
||||
host-generic-pci.txt except as listed below.
|
||||
|
||||
Properties of the host controller node that differ from
|
||||
host-generic-pci.txt:
|
||||
|
||||
- compatible : Must be "cavium,pci-host-thunder-pem"
|
||||
|
||||
- reg : Two entries: First the configuration space for down
|
||||
stream devices base address and size, as accessed
|
||||
from the parent bus. Second, the register bank of
|
||||
the PEM device PCIe bridge.
|
||||
|
||||
Example:
|
||||
|
||||
pci@87e0,c2000000 {
|
||||
compatible = "cavium,pci-host-thunder-pem";
|
||||
device_type = "pci";
|
||||
msi-parent = <&its>;
|
||||
msi-map = <0 &its 0x10000 0x10000>;
|
||||
bus-range = <0x8f 0xc7>;
|
||||
#size-cells = <2>;
|
||||
#address-cells = <3>;
|
||||
|
||||
reg = <0x8880 0x8f000000 0x0 0x39000000>, /* Configuration space */
|
||||
<0x87e0 0xc2000000 0x0 0x00010000>; /* PEM space */
|
||||
ranges = <0x01000000 0x00 0x00020000 0x88b0 0x00020000 0x00 0x00010000>, /* I/O */
|
||||
<0x03000000 0x00 0x10000000 0x8890 0x10000000 0x0f 0xf0000000>, /* mem64 */
|
||||
<0x43000000 0x10 0x00000000 0x88a0 0x00000000 0x10 0x00000000>, /* mem64-pref */
|
||||
<0x03000000 0x87e0 0xc2f00000 0x87e0 0xc2000000 0x00 0x00100000>; /* mem64 PEM BAR4 */
|
||||
|
||||
#interrupt-cells = <1>;
|
||||
interrupt-map-mask = <0 0 0 7>;
|
||||
interrupt-map = <0 0 0 1 &gic0 0 0 0 24 4>, /* INTA */
|
||||
<0 0 0 2 &gic0 0 0 0 25 4>, /* INTB */
|
||||
<0 0 0 3 &gic0 0 0 0 26 4>, /* INTC */
|
||||
<0 0 0 4 &gic0 0 0 0 27 4>; /* INTD */
|
||||
};
|
68
Documentation/devicetree/bindings/pci/xilinx-nwl-pcie.txt
Normal file
68
Documentation/devicetree/bindings/pci/xilinx-nwl-pcie.txt
Normal file
@ -0,0 +1,68 @@
|
||||
* Xilinx NWL PCIe Root Port Bridge DT description
|
||||
|
||||
Required properties:
|
||||
- compatible: Should contain "xlnx,nwl-pcie-2.11"
|
||||
- #address-cells: Address representation for root ports, set to <3>
|
||||
- #size-cells: Size representation for root ports, set to <2>
|
||||
- #interrupt-cells: specifies the number of cells needed to encode an
|
||||
interrupt source. The value must be 1.
|
||||
- reg: Should contain Bridge, PCIe Controller registers location,
|
||||
configuration space, and length
|
||||
- reg-names: Must include the following entries:
|
||||
"breg": bridge registers
|
||||
"pcireg": PCIe controller registers
|
||||
"cfg": configuration space region
|
||||
- device_type: must be "pci"
|
||||
- interrupts: Should contain NWL PCIe interrupt
|
||||
- interrupt-names: Must include the following entries:
|
||||
"msi1, msi0": interrupt asserted when MSI is received
|
||||
"intx": interrupt asserted when a legacy interrupt is received
|
||||
"misc": interrupt asserted when miscellaneous is received
|
||||
- interrupt-map-mask and interrupt-map: standard PCI properties to define the
|
||||
mapping of the PCI interface to interrupt numbers.
|
||||
- ranges: ranges for the PCI memory regions (I/O space region is not
|
||||
supported by hardware)
|
||||
Please refer to the standard PCI bus binding document for a more
|
||||
detailed explanation
|
||||
- msi-controller: indicates that this is MSI controller node
|
||||
- msi-parent: MSI parent of the root complex itself
|
||||
- legacy-interrupt-controller: Interrupt controller device node for Legacy interrupts
|
||||
- interrupt-controller: identifies the node as an interrupt controller
|
||||
- #interrupt-cells: should be set to 1
|
||||
- #address-cells: specifies the number of cells needed to encode an
|
||||
address. The value must be 0.
|
||||
|
||||
|
||||
Example:
|
||||
++++++++
|
||||
|
||||
nwl_pcie: pcie@fd0e0000 {
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
compatible = "xlnx,nwl-pcie-2.11";
|
||||
#interrupt-cells = <1>;
|
||||
msi-controller;
|
||||
device_type = "pci";
|
||||
interrupt-parent = <&gic>;
|
||||
interrupts = <0 114 4>, <0 115 4>, <0 116 4>, <0 117 4>, <0 118 4>;
|
||||
interrupt-names = "msi0", "msi1", "intx", "dummy", "misc";
|
||||
interrupt-map-mask = <0x0 0x0 0x0 0x7>;
|
||||
interrupt-map = <0x0 0x0 0x0 0x1 &pcie_intc 0x1>,
|
||||
<0x0 0x0 0x0 0x2 &pcie_intc 0x2>,
|
||||
<0x0 0x0 0x0 0x3 &pcie_intc 0x3>,
|
||||
<0x0 0x0 0x0 0x4 &pcie_intc 0x4>;
|
||||
|
||||
msi-parent = <&nwl_pcie>;
|
||||
reg = <0x0 0xfd0e0000 0x0 0x1000>,
|
||||
<0x0 0xfd480000 0x0 0x1000>,
|
||||
<0x0 0xe0000000 0x0 0x1000000>;
|
||||
reg-names = "breg", "pcireg", "cfg";
|
||||
ranges = <0x02000000 0x00000000 0xe1000000 0x00000000 0xe1000000 0 0x0f000000>;
|
||||
|
||||
pcie_intc: legacy-interrupt-controller {
|
||||
interrupt-controller;
|
||||
#address-cells = <0>;
|
||||
#interrupt-cells = <1>;
|
||||
};
|
||||
|
||||
};
|
@ -17,7 +17,7 @@ Required properties:
|
||||
Please refer to the standard PCI bus binding document for a more
|
||||
detailed explanation
|
||||
|
||||
Optional properties:
|
||||
Optional properties for Zynq/Microblaze:
|
||||
- bus-range: PCI bus numbers covered
|
||||
|
||||
Interrupt controller child node
|
||||
@ -38,13 +38,13 @@ the four INTx interrupts in ISR and route them to this domain.
|
||||
|
||||
Example:
|
||||
++++++++
|
||||
|
||||
Zynq:
|
||||
pci_express: axi-pcie@50000000 {
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
#interrupt-cells = <1>;
|
||||
compatible = "xlnx,axi-pcie-host-1.00.a";
|
||||
reg = < 0x50000000 0x10000000 >;
|
||||
reg = < 0x50000000 0x1000000 >;
|
||||
device_type = "pci";
|
||||
interrupts = < 0 52 4 >;
|
||||
interrupt-map-mask = <0 0 0 7>;
|
||||
@ -60,3 +60,29 @@ Example:
|
||||
#interrupt-cells = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Microblaze:
|
||||
pci_express: axi-pcie@10000000 {
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
#interrupt-cells = <1>;
|
||||
compatible = "xlnx,axi-pcie-host-1.00.a";
|
||||
reg = <0x10000000 0x4000000>;
|
||||
device_type = "pci";
|
||||
interrupt-parent = <µblaze_0_intc>;
|
||||
interrupts = <1 2>;
|
||||
interrupt-map-mask = <0 0 0 7>;
|
||||
interrupt-map = <0 0 0 1 &pcie_intc 1>,
|
||||
<0 0 0 2 &pcie_intc 2>,
|
||||
<0 0 0 3 &pcie_intc 3>,
|
||||
<0 0 0 4 &pcie_intc 4>;
|
||||
ranges = <0x02000000 0x00000000 0x80000000 0x80000000 0x00000000 0x10000000>;
|
||||
|
||||
pcie_intc: interrupt-controller {
|
||||
interrupt-controller;
|
||||
#address-cells = <0>;
|
||||
#interrupt-cells = <1>;
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -8373,6 +8373,7 @@ L: linux-pci@vger.kernel.org
|
||||
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/pci/host-generic-pci.txt
|
||||
F: drivers/pci/host/pci-host-common.c
|
||||
F: drivers/pci/host/pci-host-generic.c
|
||||
|
||||
PCI DRIVER FOR INTEL VOLUME MANAGEMENT DEVICE (VMD)
|
||||
@ -8418,6 +8419,14 @@ L: linux-arm-msm@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/pci/host/*qcom*
|
||||
|
||||
PCIE DRIVER FOR CAVIUM THUNDERX
|
||||
M: David Daney <david.daney@cavium.com>
|
||||
L: linux-pci@vger.kernel.org
|
||||
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/pci/pci-thunder-*
|
||||
F: drivers/pci/host/pci-thunder-*
|
||||
|
||||
PCMCIA SUBSYSTEM
|
||||
P: Linux PCMCIA Team
|
||||
L: linux-pcmcia@lists.infradead.org
|
||||
|
@ -267,6 +267,9 @@ config PCI
|
||||
config PCI_DOMAINS
|
||||
def_bool PCI
|
||||
|
||||
config PCI_DOMAINS_GENERIC
|
||||
def_bool PCI_DOMAINS
|
||||
|
||||
config PCI_SYSCALL
|
||||
def_bool PCI
|
||||
|
||||
|
@ -123,17 +123,6 @@ unsigned long pci_address_to_pio(phys_addr_t address)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_address_to_pio);
|
||||
|
||||
/*
|
||||
* Return the domain number for this bus.
|
||||
*/
|
||||
int pci_domain_nr(struct pci_bus *bus)
|
||||
{
|
||||
struct pci_controller *hose = pci_bus_to_host(bus);
|
||||
|
||||
return hose->global_number;
|
||||
}
|
||||
EXPORT_SYMBOL(pci_domain_nr);
|
||||
|
||||
/* This routine is meant to be used early during boot, when the
|
||||
* PCI bus numbers have not yet been assigned, and you need to
|
||||
* issue PCI config cycles to an OF device.
|
||||
@ -863,26 +852,10 @@ void pcibios_setup_bus_devices(struct pci_bus *bus)
|
||||
|
||||
void pcibios_fixup_bus(struct pci_bus *bus)
|
||||
{
|
||||
/* When called from the generic PCI probe, read PCI<->PCI bridge
|
||||
* bases. This is -not- called when generating the PCI tree from
|
||||
* the OF device-tree.
|
||||
*/
|
||||
if (bus->self != NULL)
|
||||
pci_read_bridge_bases(bus);
|
||||
|
||||
/* Now fixup the bus bus */
|
||||
pcibios_setup_bus_self(bus);
|
||||
|
||||
/* Now fixup devices on that bus */
|
||||
pcibios_setup_bus_devices(bus);
|
||||
/* nothing to do */
|
||||
}
|
||||
EXPORT_SYMBOL(pcibios_fixup_bus);
|
||||
|
||||
static int skip_isa_ioresource_align(struct pci_dev *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to avoid collisions with `mirrored' VGA ports
|
||||
* and other strange ISA hardware, so we always want the
|
||||
@ -899,20 +872,18 @@ static int skip_isa_ioresource_align(struct pci_dev *dev)
|
||||
resource_size_t pcibios_align_resource(void *data, const struct resource *res,
|
||||
resource_size_t size, resource_size_t align)
|
||||
{
|
||||
struct pci_dev *dev = data;
|
||||
resource_size_t start = res->start;
|
||||
|
||||
if (res->flags & IORESOURCE_IO) {
|
||||
if (skip_isa_ioresource_align(dev))
|
||||
return start;
|
||||
if (start & 0x300)
|
||||
start = (start + 0x3ff) & ~0x3ff;
|
||||
}
|
||||
|
||||
return start;
|
||||
return res->start;
|
||||
}
|
||||
EXPORT_SYMBOL(pcibios_align_resource);
|
||||
|
||||
int pcibios_add_device(struct pci_dev *dev)
|
||||
{
|
||||
dev->irq = of_irq_parse_and_map_pci(dev, 0, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(pcibios_add_device);
|
||||
|
||||
/*
|
||||
* Reparent resource children of pr that conflict with res
|
||||
* under res, and make res replace those children.
|
||||
@ -1333,13 +1304,6 @@ static void pcibios_setup_phb_resources(struct pci_controller *hose,
|
||||
(unsigned long)hose->io_base_virt - _IO_BASE);
|
||||
}
|
||||
|
||||
struct device_node *pcibios_get_phb_of_node(struct pci_bus *bus)
|
||||
{
|
||||
struct pci_controller *hose = bus->sysdata;
|
||||
|
||||
return of_node_get(hose->dn);
|
||||
}
|
||||
|
||||
static void pcibios_scan_phb(struct pci_controller *hose)
|
||||
{
|
||||
LIST_HEAD(resources);
|
||||
|
@ -503,6 +503,18 @@ static struct pci_ops vmd_ops = {
|
||||
.write = vmd_pci_write,
|
||||
};
|
||||
|
||||
static void vmd_attach_resources(struct vmd_dev *vmd)
|
||||
{
|
||||
vmd->dev->resource[VMD_MEMBAR1].child = &vmd->resources[1];
|
||||
vmd->dev->resource[VMD_MEMBAR2].child = &vmd->resources[2];
|
||||
}
|
||||
|
||||
static void vmd_detach_resources(struct vmd_dev *vmd)
|
||||
{
|
||||
vmd->dev->resource[VMD_MEMBAR1].child = NULL;
|
||||
vmd->dev->resource[VMD_MEMBAR2].child = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* VMD domains start at 0x1000 to not clash with ACPI _SEG domains.
|
||||
*/
|
||||
@ -527,11 +539,28 @@ static int vmd_enable_domain(struct vmd_dev *vmd)
|
||||
res = &vmd->dev->resource[VMD_CFGBAR];
|
||||
vmd->resources[0] = (struct resource) {
|
||||
.name = "VMD CFGBAR",
|
||||
.start = res->start,
|
||||
.start = 0,
|
||||
.end = (resource_size(res) >> 20) - 1,
|
||||
.flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED,
|
||||
};
|
||||
|
||||
/*
|
||||
* If the window is below 4GB, clear IORESOURCE_MEM_64 so we can
|
||||
* put 32-bit resources in the window.
|
||||
*
|
||||
* There's no hardware reason why a 64-bit window *couldn't*
|
||||
* contain a 32-bit resource, but pbus_size_mem() computes the
|
||||
* bridge window size assuming a 64-bit window will contain no
|
||||
* 32-bit resources. __pci_assign_resource() enforces that
|
||||
* artificial restriction to make sure everything will fit.
|
||||
*
|
||||
* The only way we could use a 64-bit non-prefechable MEMBAR is
|
||||
* if its address is <4GB so that we can convert it to a 32-bit
|
||||
* resource. To be visible to the host OS, all VMD endpoints must
|
||||
* be initially configured by platform BIOS, which includes setting
|
||||
* up these resources. We can assume the device is configured
|
||||
* according to the platform needs.
|
||||
*/
|
||||
res = &vmd->dev->resource[VMD_MEMBAR1];
|
||||
upper_bits = upper_32_bits(res->end);
|
||||
flags = res->flags & ~IORESOURCE_SIZEALIGN;
|
||||
@ -542,6 +571,7 @@ static int vmd_enable_domain(struct vmd_dev *vmd)
|
||||
.start = res->start,
|
||||
.end = res->end,
|
||||
.flags = flags,
|
||||
.parent = res,
|
||||
};
|
||||
|
||||
res = &vmd->dev->resource[VMD_MEMBAR2];
|
||||
@ -554,6 +584,7 @@ static int vmd_enable_domain(struct vmd_dev *vmd)
|
||||
.start = res->start + 0x2000,
|
||||
.end = res->end,
|
||||
.flags = flags,
|
||||
.parent = res,
|
||||
};
|
||||
|
||||
sd->domain = vmd_find_free_domain();
|
||||
@ -578,6 +609,7 @@ static int vmd_enable_domain(struct vmd_dev *vmd)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
vmd_attach_resources(vmd);
|
||||
vmd_setup_dma_ops(vmd);
|
||||
dev_set_msi_domain(&vmd->bus->dev, vmd->irq_domain);
|
||||
pci_rescan_bus(vmd->bus);
|
||||
@ -674,6 +706,7 @@ static void vmd_remove(struct pci_dev *dev)
|
||||
{
|
||||
struct vmd_dev *vmd = pci_get_drvdata(dev);
|
||||
|
||||
vmd_detach_resources(vmd);
|
||||
pci_set_drvdata(dev, NULL);
|
||||
sysfs_remove_link(&vmd->dev->dev.kobj, "domain");
|
||||
pci_stop_root_bus(vmd->bus);
|
||||
|
@ -16,6 +16,16 @@ config PCI_MVEBU
|
||||
depends on ARCH_MVEBU || ARCH_DOVE
|
||||
depends on OF
|
||||
|
||||
config PCIE_XILINX_NWL
|
||||
bool "NWL PCIe Core"
|
||||
depends on ARCH_ZYNQMP
|
||||
select PCI_MSI_IRQ_DOMAIN if PCI_MSI
|
||||
help
|
||||
Say 'Y' here if you want kernel support for Xilinx
|
||||
NWL PCIe controller. The controller can act as Root Port
|
||||
or End Point. The current option selection will only
|
||||
support root port enabling.
|
||||
|
||||
config PCIE_DW
|
||||
bool
|
||||
|
||||
@ -41,7 +51,7 @@ config PCI_TEGRA
|
||||
config PCI_RCAR_GEN2
|
||||
bool "Renesas R-Car Gen2 Internal PCI controller"
|
||||
depends on ARM
|
||||
depends on ARCH_SHMOBILE || COMPILE_TEST
|
||||
depends on ARCH_RENESAS || COMPILE_TEST
|
||||
help
|
||||
Say Y here if you want internal PCI support on R-Car Gen2 SoC.
|
||||
There are 3 internal PCI controllers available with a single
|
||||
@ -49,13 +59,17 @@ config PCI_RCAR_GEN2
|
||||
|
||||
config PCI_RCAR_GEN2_PCIE
|
||||
bool "Renesas R-Car PCIe controller"
|
||||
depends on ARCH_SHMOBILE || (ARM && COMPILE_TEST)
|
||||
depends on ARCH_RENESAS || (ARM && COMPILE_TEST)
|
||||
help
|
||||
Say Y here if you want PCIe controller support on R-Car Gen2 SoCs.
|
||||
|
||||
config PCI_HOST_COMMON
|
||||
bool
|
||||
|
||||
config PCI_HOST_GENERIC
|
||||
bool "Generic PCI host controller"
|
||||
depends on (ARM || ARM64) && OF
|
||||
select PCI_HOST_COMMON
|
||||
help
|
||||
Say Y here if you want to support a simple generic PCI host
|
||||
controller, such as the one emulated by kvmtool.
|
||||
@ -81,7 +95,7 @@ config PCI_KEYSTONE
|
||||
|
||||
config PCIE_XILINX
|
||||
bool "Xilinx AXI PCIe host bridge support"
|
||||
depends on ARCH_ZYNQ
|
||||
depends on ARCH_ZYNQ || MICROBLAZE
|
||||
help
|
||||
Say 'Y' here if you want kernel to support the Xilinx AXI PCIe
|
||||
Host Bridge driver.
|
||||
@ -191,4 +205,18 @@ config PCIE_QCOM
|
||||
PCIe controller uses the Designware core plus Qualcomm-specific
|
||||
hardware wrappers.
|
||||
|
||||
config PCI_HOST_THUNDER_PEM
|
||||
bool "Cavium Thunder PCIe controller to off-chip devices"
|
||||
depends on OF && ARM64
|
||||
select PCI_HOST_COMMON
|
||||
help
|
||||
Say Y here if you want PCIe support for CN88XX Cavium Thunder SoCs.
|
||||
|
||||
config PCI_HOST_THUNDER_ECAM
|
||||
bool "Cavium Thunder ECAM controller to on-chip devices on pass-1.x silicon"
|
||||
depends on OF && ARM64
|
||||
select PCI_HOST_COMMON
|
||||
help
|
||||
Say Y here if you want ECAM support for CN88XX-Pass-1.x Cavium Thunder SoCs.
|
||||
|
||||
endmenu
|
||||
|
@ -6,10 +6,12 @@ obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o
|
||||
obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o
|
||||
obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o
|
||||
obj-$(CONFIG_PCI_RCAR_GEN2_PCIE) += pcie-rcar.o
|
||||
obj-$(CONFIG_PCI_HOST_COMMON) += pci-host-common.o
|
||||
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_PCIE_XILINX_NWL) += pcie-xilinx-nwl.o
|
||||
obj-$(CONFIG_PCI_XGENE) += pci-xgene.o
|
||||
obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o
|
||||
obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o
|
||||
@ -22,3 +24,5 @@ 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
|
||||
obj-$(CONFIG_PCI_HOST_THUNDER_ECAM) += pci-thunder-ecam.o
|
||||
obj-$(CONFIG_PCI_HOST_THUNDER_PEM) += pci-thunder-pem.o
|
||||
|
194
drivers/pci/host/pci-host-common.c
Normal file
194
drivers/pci/host/pci-host-common.c
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2014 ARM Limited
|
||||
*
|
||||
* Author: Will Deacon <will.deacon@arm.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "pci-host-common.h"
|
||||
|
||||
static void gen_pci_release_of_pci_ranges(struct gen_pci *pci)
|
||||
{
|
||||
pci_free_resource_list(&pci->resources);
|
||||
}
|
||||
|
||||
static int gen_pci_parse_request_of_pci_ranges(struct gen_pci *pci)
|
||||
{
|
||||
int err, res_valid = 0;
|
||||
struct device *dev = pci->host.dev.parent;
|
||||
struct device_node *np = dev->of_node;
|
||||
resource_size_t iobase;
|
||||
struct resource_entry *win;
|
||||
|
||||
err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources,
|
||||
&iobase);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
resource_list_for_each_entry(win, &pci->resources) {
|
||||
struct resource *parent, *res = win->res;
|
||||
|
||||
switch (resource_type(res)) {
|
||||
case IORESOURCE_IO:
|
||||
parent = &ioport_resource;
|
||||
err = pci_remap_iospace(res, iobase);
|
||||
if (err) {
|
||||
dev_warn(dev, "error %d: failed to map resource %pR\n",
|
||||
err, res);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case IORESOURCE_MEM:
|
||||
parent = &iomem_resource;
|
||||
res_valid |= !(res->flags & IORESOURCE_PREFETCH);
|
||||
break;
|
||||
case IORESOURCE_BUS:
|
||||
pci->cfg.bus_range = res;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
err = devm_request_resource(dev, parent, res);
|
||||
if (err)
|
||||
goto out_release_res;
|
||||
}
|
||||
|
||||
if (!res_valid) {
|
||||
dev_err(dev, "non-prefetchable memory resource required\n");
|
||||
err = -EINVAL;
|
||||
goto out_release_res;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_release_res:
|
||||
gen_pci_release_of_pci_ranges(pci);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci)
|
||||
{
|
||||
int err;
|
||||
u8 bus_max;
|
||||
resource_size_t busn;
|
||||
struct resource *bus_range;
|
||||
struct device *dev = pci->host.dev.parent;
|
||||
struct device_node *np = dev->of_node;
|
||||
u32 sz = 1 << pci->cfg.ops->bus_shift;
|
||||
|
||||
err = of_address_to_resource(np, 0, &pci->cfg.res);
|
||||
if (err) {
|
||||
dev_err(dev, "missing \"reg\" property\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Limit the bus-range to fit within reg */
|
||||
bus_max = pci->cfg.bus_range->start +
|
||||
(resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1;
|
||||
pci->cfg.bus_range->end = min_t(resource_size_t,
|
||||
pci->cfg.bus_range->end, bus_max);
|
||||
|
||||
pci->cfg.win = devm_kcalloc(dev, resource_size(pci->cfg.bus_range),
|
||||
sizeof(*pci->cfg.win), GFP_KERNEL);
|
||||
if (!pci->cfg.win)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Map our Configuration Space windows */
|
||||
if (!devm_request_mem_region(dev, pci->cfg.res.start,
|
||||
resource_size(&pci->cfg.res),
|
||||
"Configuration Space"))
|
||||
return -ENOMEM;
|
||||
|
||||
bus_range = pci->cfg.bus_range;
|
||||
for (busn = bus_range->start; busn <= bus_range->end; ++busn) {
|
||||
u32 idx = busn - bus_range->start;
|
||||
|
||||
pci->cfg.win[idx] = devm_ioremap(dev,
|
||||
pci->cfg.res.start + idx * sz,
|
||||
sz);
|
||||
if (!pci->cfg.win[idx])
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pci_host_common_probe(struct platform_device *pdev,
|
||||
struct gen_pci *pci)
|
||||
{
|
||||
int err;
|
||||
const char *type;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct pci_bus *bus, *child;
|
||||
|
||||
type = of_get_property(np, "device_type", NULL);
|
||||
if (!type || strcmp(type, "pci")) {
|
||||
dev_err(dev, "invalid \"device_type\" %s\n", type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
of_pci_check_probe_only();
|
||||
|
||||
pci->host.dev.parent = dev;
|
||||
INIT_LIST_HEAD(&pci->host.windows);
|
||||
INIT_LIST_HEAD(&pci->resources);
|
||||
|
||||
/* Parse our PCI ranges and request their resources */
|
||||
err = gen_pci_parse_request_of_pci_ranges(pci);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Parse and map our Configuration Space windows */
|
||||
err = gen_pci_parse_map_cfg_windows(pci);
|
||||
if (err) {
|
||||
gen_pci_release_of_pci_ranges(pci);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Do not reassign resources if probe only */
|
||||
if (!pci_has_flag(PCI_PROBE_ONLY))
|
||||
pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS);
|
||||
|
||||
|
||||
bus = pci_scan_root_bus(dev, pci->cfg.bus_range->start,
|
||||
&pci->cfg.ops->ops, pci, &pci->resources);
|
||||
if (!bus) {
|
||||
dev_err(dev, "Scanning rootbus failed");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci);
|
||||
|
||||
if (!pci_has_flag(PCI_PROBE_ONLY)) {
|
||||
pci_bus_size_bridges(bus);
|
||||
pci_bus_assign_resources(bus);
|
||||
|
||||
list_for_each_entry(child, &bus->children, node)
|
||||
pcie_bus_configure_settings(child);
|
||||
}
|
||||
|
||||
pci_bus_add_devices(bus);
|
||||
return 0;
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("Generic PCI host driver common code");
|
||||
MODULE_AUTHOR("Will Deacon <will.deacon@arm.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
47
drivers/pci/host/pci-host-common.h
Normal file
47
drivers/pci/host/pci-host-common.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2014 ARM Limited
|
||||
*
|
||||
* Author: Will Deacon <will.deacon@arm.com>
|
||||
*/
|
||||
|
||||
#ifndef _PCI_HOST_COMMON_H
|
||||
#define _PCI_HOST_COMMON_H
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
struct gen_pci_cfg_bus_ops {
|
||||
u32 bus_shift;
|
||||
struct pci_ops ops;
|
||||
};
|
||||
|
||||
struct gen_pci_cfg_windows {
|
||||
struct resource res;
|
||||
struct resource *bus_range;
|
||||
void __iomem **win;
|
||||
|
||||
struct gen_pci_cfg_bus_ops *ops;
|
||||
};
|
||||
|
||||
struct gen_pci {
|
||||
struct pci_host_bridge host;
|
||||
struct gen_pci_cfg_windows cfg;
|
||||
struct list_head resources;
|
||||
};
|
||||
|
||||
int pci_host_common_probe(struct platform_device *pdev,
|
||||
struct gen_pci *pci);
|
||||
|
||||
#endif /* _PCI_HOST_COMMON_H */
|
@ -25,24 +25,7 @@
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
struct gen_pci_cfg_bus_ops {
|
||||
u32 bus_shift;
|
||||
struct pci_ops ops;
|
||||
};
|
||||
|
||||
struct gen_pci_cfg_windows {
|
||||
struct resource res;
|
||||
struct resource *bus_range;
|
||||
void __iomem **win;
|
||||
|
||||
struct gen_pci_cfg_bus_ops *ops;
|
||||
};
|
||||
|
||||
struct gen_pci {
|
||||
struct pci_host_bridge host;
|
||||
struct gen_pci_cfg_windows cfg;
|
||||
struct list_head resources;
|
||||
};
|
||||
#include "pci-host-common.h"
|
||||
|
||||
static void __iomem *gen_pci_map_cfg_bus_cam(struct pci_bus *bus,
|
||||
unsigned int devfn,
|
||||
@ -93,175 +76,19 @@ static const struct of_device_id gen_pci_of_match[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, gen_pci_of_match);
|
||||
|
||||
static void gen_pci_release_of_pci_ranges(struct gen_pci *pci)
|
||||
{
|
||||
pci_free_resource_list(&pci->resources);
|
||||
}
|
||||
|
||||
static int gen_pci_parse_request_of_pci_ranges(struct gen_pci *pci)
|
||||
{
|
||||
int err, res_valid = 0;
|
||||
struct device *dev = pci->host.dev.parent;
|
||||
struct device_node *np = dev->of_node;
|
||||
resource_size_t iobase;
|
||||
struct resource_entry *win;
|
||||
|
||||
err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources,
|
||||
&iobase);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
resource_list_for_each_entry(win, &pci->resources) {
|
||||
struct resource *parent, *res = win->res;
|
||||
|
||||
switch (resource_type(res)) {
|
||||
case IORESOURCE_IO:
|
||||
parent = &ioport_resource;
|
||||
err = pci_remap_iospace(res, iobase);
|
||||
if (err) {
|
||||
dev_warn(dev, "error %d: failed to map resource %pR\n",
|
||||
err, res);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case IORESOURCE_MEM:
|
||||
parent = &iomem_resource;
|
||||
res_valid |= !(res->flags & IORESOURCE_PREFETCH);
|
||||
break;
|
||||
case IORESOURCE_BUS:
|
||||
pci->cfg.bus_range = res;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
err = devm_request_resource(dev, parent, res);
|
||||
if (err)
|
||||
goto out_release_res;
|
||||
}
|
||||
|
||||
if (!res_valid) {
|
||||
dev_err(dev, "non-prefetchable memory resource required\n");
|
||||
err = -EINVAL;
|
||||
goto out_release_res;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_release_res:
|
||||
gen_pci_release_of_pci_ranges(pci);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci)
|
||||
{
|
||||
int err;
|
||||
u8 bus_max;
|
||||
resource_size_t busn;
|
||||
struct resource *bus_range;
|
||||
struct device *dev = pci->host.dev.parent;
|
||||
struct device_node *np = dev->of_node;
|
||||
u32 sz = 1 << pci->cfg.ops->bus_shift;
|
||||
|
||||
err = of_address_to_resource(np, 0, &pci->cfg.res);
|
||||
if (err) {
|
||||
dev_err(dev, "missing \"reg\" property\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Limit the bus-range to fit within reg */
|
||||
bus_max = pci->cfg.bus_range->start +
|
||||
(resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1;
|
||||
pci->cfg.bus_range->end = min_t(resource_size_t,
|
||||
pci->cfg.bus_range->end, bus_max);
|
||||
|
||||
pci->cfg.win = devm_kcalloc(dev, resource_size(pci->cfg.bus_range),
|
||||
sizeof(*pci->cfg.win), GFP_KERNEL);
|
||||
if (!pci->cfg.win)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Map our Configuration Space windows */
|
||||
if (!devm_request_mem_region(dev, pci->cfg.res.start,
|
||||
resource_size(&pci->cfg.res),
|
||||
"Configuration Space"))
|
||||
return -ENOMEM;
|
||||
|
||||
bus_range = pci->cfg.bus_range;
|
||||
for (busn = bus_range->start; busn <= bus_range->end; ++busn) {
|
||||
u32 idx = busn - bus_range->start;
|
||||
|
||||
pci->cfg.win[idx] = devm_ioremap(dev,
|
||||
pci->cfg.res.start + idx * sz,
|
||||
sz);
|
||||
if (!pci->cfg.win[idx])
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gen_pci_probe(struct platform_device *pdev)
|
||||
{
|
||||
int err;
|
||||
const char *type;
|
||||
const struct of_device_id *of_id;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
const struct of_device_id *of_id;
|
||||
struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
|
||||
struct pci_bus *bus, *child;
|
||||
|
||||
if (!pci)
|
||||
return -ENOMEM;
|
||||
|
||||
type = of_get_property(np, "device_type", NULL);
|
||||
if (!type || strcmp(type, "pci")) {
|
||||
dev_err(dev, "invalid \"device_type\" %s\n", type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
of_pci_check_probe_only();
|
||||
|
||||
of_id = of_match_node(gen_pci_of_match, np);
|
||||
of_id = of_match_node(gen_pci_of_match, dev->of_node);
|
||||
pci->cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data;
|
||||
pci->host.dev.parent = dev;
|
||||
INIT_LIST_HEAD(&pci->host.windows);
|
||||
INIT_LIST_HEAD(&pci->resources);
|
||||
|
||||
/* Parse our PCI ranges and request their resources */
|
||||
err = gen_pci_parse_request_of_pci_ranges(pci);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Parse and map our Configuration Space windows */
|
||||
err = gen_pci_parse_map_cfg_windows(pci);
|
||||
if (err) {
|
||||
gen_pci_release_of_pci_ranges(pci);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Do not reassign resources if probe only */
|
||||
if (!pci_has_flag(PCI_PROBE_ONLY))
|
||||
pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS);
|
||||
|
||||
|
||||
bus = pci_scan_root_bus(dev, pci->cfg.bus_range->start,
|
||||
&pci->cfg.ops->ops, pci, &pci->resources);
|
||||
if (!bus) {
|
||||
dev_err(dev, "Scanning rootbus failed");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci);
|
||||
|
||||
if (!pci_has_flag(PCI_PROBE_ONLY)) {
|
||||
pci_bus_size_bridges(bus);
|
||||
pci_bus_assign_resources(bus);
|
||||
|
||||
list_for_each_entry(child, &bus->children, node)
|
||||
pcie_bus_configure_settings(child);
|
||||
}
|
||||
|
||||
pci_bus_add_devices(bus);
|
||||
return 0;
|
||||
return pci_host_common_probe(pdev, pci);
|
||||
}
|
||||
|
||||
static struct platform_driver gen_pci_driver = {
|
||||
|
@ -39,6 +39,11 @@ struct imx6_pcie {
|
||||
struct pcie_port pp;
|
||||
struct regmap *iomuxc_gpr;
|
||||
void __iomem *mem_base;
|
||||
u32 tx_deemph_gen1;
|
||||
u32 tx_deemph_gen2_3p5db;
|
||||
u32 tx_deemph_gen2_6db;
|
||||
u32 tx_swing_full;
|
||||
u32 tx_swing_low;
|
||||
};
|
||||
|
||||
/* PCIe Root Complex registers (memory-mapped) */
|
||||
@ -334,15 +339,20 @@ static void imx6_pcie_init_phy(struct pcie_port *pp)
|
||||
IMX6Q_GPR12_LOS_LEVEL, 9 << 4);
|
||||
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN1, 0 << 0);
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN1,
|
||||
imx6_pcie->tx_deemph_gen1 << 0);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB, 0 << 6);
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB,
|
||||
imx6_pcie->tx_deemph_gen2_3p5db << 6);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB, 20 << 12);
|
||||
IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB,
|
||||
imx6_pcie->tx_deemph_gen2_6db << 12);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_SWING_FULL, 127 << 18);
|
||||
IMX6Q_GPR8_TX_SWING_FULL,
|
||||
imx6_pcie->tx_swing_full << 18);
|
||||
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8,
|
||||
IMX6Q_GPR8_TX_SWING_LOW, 127 << 25);
|
||||
IMX6Q_GPR8_TX_SWING_LOW,
|
||||
imx6_pcie->tx_swing_low << 25);
|
||||
}
|
||||
|
||||
static int imx6_pcie_wait_for_link(struct pcie_port *pp)
|
||||
@ -533,6 +543,7 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
|
||||
struct imx6_pcie *imx6_pcie;
|
||||
struct pcie_port *pp;
|
||||
struct resource *dbi_base;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
int ret;
|
||||
|
||||
imx6_pcie = devm_kzalloc(&pdev->dev, sizeof(*imx6_pcie), GFP_KERNEL);
|
||||
@ -585,6 +596,27 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(imx6_pcie->iomuxc_gpr);
|
||||
}
|
||||
|
||||
/* Grab PCIe PHY Tx Settings */
|
||||
if (of_property_read_u32(node, "fsl,tx-deemph-gen1",
|
||||
&imx6_pcie->tx_deemph_gen1))
|
||||
imx6_pcie->tx_deemph_gen1 = 0;
|
||||
|
||||
if (of_property_read_u32(node, "fsl,tx-deemph-gen2-3p5db",
|
||||
&imx6_pcie->tx_deemph_gen2_3p5db))
|
||||
imx6_pcie->tx_deemph_gen2_3p5db = 0;
|
||||
|
||||
if (of_property_read_u32(node, "fsl,tx-deemph-gen2-6db",
|
||||
&imx6_pcie->tx_deemph_gen2_6db))
|
||||
imx6_pcie->tx_deemph_gen2_6db = 20;
|
||||
|
||||
if (of_property_read_u32(node, "fsl,tx-swing-full",
|
||||
&imx6_pcie->tx_swing_full))
|
||||
imx6_pcie->tx_swing_full = 127;
|
||||
|
||||
if (of_property_read_u32(node, "fsl,tx-swing-low",
|
||||
&imx6_pcie->tx_swing_low))
|
||||
imx6_pcie->tx_swing_low = 127;
|
||||
|
||||
ret = imx6_add_pcie_port(pp, pdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
@ -359,6 +359,9 @@ static int __init ks_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
/* initialize SerDes Phy if present */
|
||||
phy = devm_phy_get(dev, "pcie-phy");
|
||||
if (PTR_ERR_OR_ZERO(phy) == -EPROBE_DEFER)
|
||||
return PTR_ERR(phy);
|
||||
|
||||
if (!IS_ERR_OR_NULL(phy)) {
|
||||
ret = phy_init(phy);
|
||||
if (ret < 0)
|
||||
|
@ -281,6 +281,11 @@ struct tegra_pcie {
|
||||
struct resource prefetch;
|
||||
struct resource busn;
|
||||
|
||||
struct {
|
||||
resource_size_t mem;
|
||||
resource_size_t io;
|
||||
} offset;
|
||||
|
||||
struct clk *pex_clk;
|
||||
struct clk *afi_clk;
|
||||
struct clk *pll_e;
|
||||
@ -295,7 +300,6 @@ struct tegra_pcie {
|
||||
struct tegra_msi msi;
|
||||
|
||||
struct list_head ports;
|
||||
unsigned int num_ports;
|
||||
u32 xbar_config;
|
||||
|
||||
struct regulator_bulk_data *supplies;
|
||||
@ -426,31 +430,38 @@ free:
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up a virtual address mapping for the specified bus number. If no such
|
||||
* mapping exists, try to create one.
|
||||
*/
|
||||
static void __iomem *tegra_pcie_bus_map(struct tegra_pcie *pcie,
|
||||
unsigned int busnr)
|
||||
static int tegra_pcie_add_bus(struct pci_bus *bus)
|
||||
{
|
||||
struct tegra_pcie_bus *bus;
|
||||
struct tegra_pcie *pcie = sys_to_pcie(bus->sysdata);
|
||||
struct tegra_pcie_bus *b;
|
||||
|
||||
list_for_each_entry(bus, &pcie->buses, list)
|
||||
if (bus->nr == busnr)
|
||||
return (void __iomem *)bus->area->addr;
|
||||
b = tegra_pcie_bus_alloc(pcie, bus->number);
|
||||
if (IS_ERR(b))
|
||||
return PTR_ERR(b);
|
||||
|
||||
bus = tegra_pcie_bus_alloc(pcie, busnr);
|
||||
if (IS_ERR(bus))
|
||||
return NULL;
|
||||
list_add_tail(&b->list, &pcie->buses);
|
||||
|
||||
list_add_tail(&bus->list, &pcie->buses);
|
||||
|
||||
return (void __iomem *)bus->area->addr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus,
|
||||
unsigned int devfn,
|
||||
int where)
|
||||
static void tegra_pcie_remove_bus(struct pci_bus *child)
|
||||
{
|
||||
struct tegra_pcie *pcie = sys_to_pcie(child->sysdata);
|
||||
struct tegra_pcie_bus *bus, *tmp;
|
||||
|
||||
list_for_each_entry_safe(bus, tmp, &pcie->buses, list) {
|
||||
if (bus->nr == child->number) {
|
||||
vunmap(bus->area->addr);
|
||||
list_del(&bus->list);
|
||||
kfree(bus);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __iomem *tegra_pcie_map_bus(struct pci_bus *bus,
|
||||
unsigned int devfn,
|
||||
int where)
|
||||
{
|
||||
struct tegra_pcie *pcie = sys_to_pcie(bus->sysdata);
|
||||
void __iomem *addr = NULL;
|
||||
@ -466,7 +477,12 @@ static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addr = tegra_pcie_bus_map(pcie, bus->number);
|
||||
struct tegra_pcie_bus *b;
|
||||
|
||||
list_for_each_entry(b, &pcie->buses, list)
|
||||
if (b->nr == bus->number)
|
||||
addr = (void __iomem *)b->area->addr;
|
||||
|
||||
if (!addr) {
|
||||
dev_err(pcie->dev,
|
||||
"failed to map cfg. space for bus %u\n",
|
||||
@ -481,7 +497,9 @@ static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus,
|
||||
}
|
||||
|
||||
static struct pci_ops tegra_pcie_ops = {
|
||||
.map_bus = tegra_pcie_conf_address,
|
||||
.add_bus = tegra_pcie_add_bus,
|
||||
.remove_bus = tegra_pcie_remove_bus,
|
||||
.map_bus = tegra_pcie_map_bus,
|
||||
.read = pci_generic_config_read32,
|
||||
.write = pci_generic_config_write32,
|
||||
};
|
||||
@ -598,6 +616,17 @@ static int tegra_pcie_setup(int nr, struct pci_sys_data *sys)
|
||||
struct tegra_pcie *pcie = sys_to_pcie(sys);
|
||||
int err;
|
||||
|
||||
sys->mem_offset = pcie->offset.mem;
|
||||
sys->io_offset = pcie->offset.io;
|
||||
|
||||
err = devm_request_resource(pcie->dev, &pcie->all, &pcie->io);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = devm_request_resource(pcie->dev, &ioport_resource, &pcie->pio);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = devm_request_resource(pcie->dev, &pcie->all, &pcie->mem);
|
||||
if (err < 0)
|
||||
return err;
|
||||
@ -606,6 +635,7 @@ static int tegra_pcie_setup(int nr, struct pci_sys_data *sys)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
pci_add_resource_offset(&sys->resources, &pcie->pio, sys->io_offset);
|
||||
pci_add_resource_offset(&sys->resources, &pcie->mem, sys->mem_offset);
|
||||
pci_add_resource_offset(&sys->resources, &pcie->prefetch,
|
||||
sys->mem_offset);
|
||||
@ -741,7 +771,7 @@ static void tegra_pcie_setup_translations(struct tegra_pcie *pcie)
|
||||
afi_writel(pcie, 0, AFI_FPCI_BAR5);
|
||||
|
||||
/* map all upstream transactions as uncached */
|
||||
afi_writel(pcie, PHYS_OFFSET, AFI_CACHE_BAR0_ST);
|
||||
afi_writel(pcie, 0, AFI_CACHE_BAR0_ST);
|
||||
afi_writel(pcie, 0, AFI_CACHE_BAR0_SZ);
|
||||
afi_writel(pcie, 0, AFI_CACHE_BAR1_ST);
|
||||
afi_writel(pcie, 0, AFI_CACHE_BAR1_SZ);
|
||||
@ -1601,6 +1631,9 @@ static int tegra_pcie_parse_dt(struct tegra_pcie *pcie)
|
||||
|
||||
switch (res.flags & IORESOURCE_TYPE_BITS) {
|
||||
case IORESOURCE_IO:
|
||||
/* Track the bus -> CPU I/O mapping offset. */
|
||||
pcie->offset.io = res.start - range.pci_addr;
|
||||
|
||||
memcpy(&pcie->pio, &res, sizeof(res));
|
||||
pcie->pio.name = np->full_name;
|
||||
|
||||
@ -1621,6 +1654,14 @@ static int tegra_pcie_parse_dt(struct tegra_pcie *pcie)
|
||||
break;
|
||||
|
||||
case IORESOURCE_MEM:
|
||||
/*
|
||||
* Track the bus -> CPU memory mapping offset. This
|
||||
* assumes that the prefetchable and non-prefetchable
|
||||
* regions will be the last of type IORESOURCE_MEM in
|
||||
* the ranges property.
|
||||
* */
|
||||
pcie->offset.mem = res.start - range.pci_addr;
|
||||
|
||||
if (res.flags & IORESOURCE_PREFETCH) {
|
||||
memcpy(&pcie->prefetch, &res, sizeof(res));
|
||||
pcie->prefetch.name = "prefetchable";
|
||||
|
403
drivers/pci/host/pci-thunder-ecam.c
Normal file
403
drivers/pci/host/pci-thunder-ecam.c
Normal file
@ -0,0 +1,403 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* Copyright (C) 2015, 2016 Cavium, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "pci-host-common.h"
|
||||
|
||||
/* Mapping is standard ECAM */
|
||||
static void __iomem *thunder_ecam_map_bus(struct pci_bus *bus,
|
||||
unsigned int devfn,
|
||||
int where)
|
||||
{
|
||||
struct gen_pci *pci = bus->sysdata;
|
||||
resource_size_t idx = bus->number - pci->cfg.bus_range->start;
|
||||
|
||||
return pci->cfg.win[idx] + ((devfn << 12) | where);
|
||||
}
|
||||
|
||||
static void set_val(u32 v, int where, int size, u32 *val)
|
||||
{
|
||||
int shift = (where & 3) * 8;
|
||||
|
||||
pr_debug("set_val %04x: %08x\n", (unsigned)(where & ~3), v);
|
||||
v >>= shift;
|
||||
if (size == 1)
|
||||
v &= 0xff;
|
||||
else if (size == 2)
|
||||
v &= 0xffff;
|
||||
*val = v;
|
||||
}
|
||||
|
||||
static int handle_ea_bar(u32 e0, int bar, struct pci_bus *bus,
|
||||
unsigned int devfn, int where, int size, u32 *val)
|
||||
{
|
||||
void __iomem *addr;
|
||||
u32 v;
|
||||
|
||||
/* Entries are 16-byte aligned; bits[2,3] select word in entry */
|
||||
int where_a = where & 0xc;
|
||||
|
||||
if (where_a == 0) {
|
||||
set_val(e0, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0x4) {
|
||||
addr = bus->ops->map_bus(bus, devfn, bar); /* BAR 0 */
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
v = readl(addr);
|
||||
v &= ~0xf;
|
||||
v |= 2; /* EA entry-1. Base-L */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0x8) {
|
||||
u32 barl_orig;
|
||||
u32 barl_rb;
|
||||
|
||||
addr = bus->ops->map_bus(bus, devfn, bar); /* BAR 0 */
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
barl_orig = readl(addr + 0);
|
||||
writel(0xffffffff, addr + 0);
|
||||
barl_rb = readl(addr + 0);
|
||||
writel(barl_orig, addr + 0);
|
||||
/* zeros in unsettable bits */
|
||||
v = ~barl_rb & ~3;
|
||||
v |= 0xc; /* EA entry-2. Offset-L */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xc) {
|
||||
addr = bus->ops->map_bus(bus, devfn, bar + 4); /* BAR 1 */
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
v = readl(addr); /* EA entry-3. Base-H */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
static int thunder_ecam_p2_config_read(struct pci_bus *bus, unsigned int devfn,
|
||||
int where, int size, u32 *val)
|
||||
{
|
||||
struct gen_pci *pci = bus->sysdata;
|
||||
int where_a = where & ~3;
|
||||
void __iomem *addr;
|
||||
u32 node_bits;
|
||||
u32 v;
|
||||
|
||||
/* EA Base[63:32] may be missing some bits ... */
|
||||
switch (where_a) {
|
||||
case 0xa8:
|
||||
case 0xbc:
|
||||
case 0xd0:
|
||||
case 0xe4:
|
||||
break;
|
||||
default:
|
||||
return pci_generic_config_read(bus, devfn, where, size, val);
|
||||
}
|
||||
|
||||
addr = bus->ops->map_bus(bus, devfn, where_a);
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
v = readl(addr);
|
||||
|
||||
/*
|
||||
* Bit 44 of the 64-bit Base must match the same bit in
|
||||
* the config space access window. Since we are working with
|
||||
* the high-order 32 bits, shift everything down by 32 bits.
|
||||
*/
|
||||
node_bits = (pci->cfg.res.start >> 32) & (1 << 12);
|
||||
|
||||
v |= node_bits;
|
||||
set_val(v, where, size, val);
|
||||
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
static int thunder_ecam_config_read(struct pci_bus *bus, unsigned int devfn,
|
||||
int where, int size, u32 *val)
|
||||
{
|
||||
u32 v;
|
||||
u32 vendor_device;
|
||||
u32 class_rev;
|
||||
void __iomem *addr;
|
||||
int cfg_type;
|
||||
int where_a = where & ~3;
|
||||
|
||||
addr = bus->ops->map_bus(bus, devfn, 0xc);
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
v = readl(addr);
|
||||
|
||||
/* Check for non type-00 header */
|
||||
cfg_type = (v >> 16) & 0x7f;
|
||||
|
||||
addr = bus->ops->map_bus(bus, devfn, 8);
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
class_rev = readl(addr);
|
||||
if (class_rev == 0xffffffff)
|
||||
goto no_emulation;
|
||||
|
||||
if ((class_rev & 0xff) >= 8) {
|
||||
/* Pass-2 handling */
|
||||
if (cfg_type)
|
||||
goto no_emulation;
|
||||
return thunder_ecam_p2_config_read(bus, devfn, where,
|
||||
size, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* All BARs have fixed addresses specified by the EA
|
||||
* capability; they must return zero on read.
|
||||
*/
|
||||
if (cfg_type == 0 &&
|
||||
((where >= 0x10 && where < 0x2c) ||
|
||||
(where >= 0x1a4 && where < 0x1bc))) {
|
||||
/* BAR or SR-IOV BAR */
|
||||
*val = 0;
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
addr = bus->ops->map_bus(bus, devfn, 0);
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
vendor_device = readl(addr);
|
||||
if (vendor_device == 0xffffffff)
|
||||
goto no_emulation;
|
||||
|
||||
pr_debug("%04x:%04x - Fix pass#: %08x, where: %03x, devfn: %03x\n",
|
||||
vendor_device & 0xffff, vendor_device >> 16, class_rev,
|
||||
(unsigned) where, devfn);
|
||||
|
||||
/* Check for non type-00 header */
|
||||
if (cfg_type == 0) {
|
||||
bool has_msix;
|
||||
bool is_nic = (vendor_device == 0xa01e177d);
|
||||
bool is_tns = (vendor_device == 0xa01f177d);
|
||||
|
||||
addr = bus->ops->map_bus(bus, devfn, 0x70);
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
/* E_CAP */
|
||||
v = readl(addr);
|
||||
has_msix = (v & 0xff00) != 0;
|
||||
|
||||
if (!has_msix && where_a == 0x70) {
|
||||
v |= 0xbc00; /* next capability is EA at 0xbc */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xb0) {
|
||||
addr = bus->ops->map_bus(bus, devfn, where_a);
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
v = readl(addr);
|
||||
if (v & 0xff00)
|
||||
pr_err("Bad MSIX cap header: %08x\n", v);
|
||||
v |= 0xbc00; /* next capability is EA at 0xbc */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xbc) {
|
||||
if (is_nic)
|
||||
v = 0x40014; /* EA last in chain, 4 entries */
|
||||
else if (is_tns)
|
||||
v = 0x30014; /* EA last in chain, 3 entries */
|
||||
else if (has_msix)
|
||||
v = 0x20014; /* EA last in chain, 2 entries */
|
||||
else
|
||||
v = 0x10014; /* EA last in chain, 1 entry */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a >= 0xc0 && where_a < 0xd0)
|
||||
/* EA entry-0. PP=0, BAR0 Size:3 */
|
||||
return handle_ea_bar(0x80ff0003,
|
||||
0x10, bus, devfn, where,
|
||||
size, val);
|
||||
if (where_a >= 0xd0 && where_a < 0xe0 && has_msix)
|
||||
/* EA entry-1. PP=0, BAR4 Size:3 */
|
||||
return handle_ea_bar(0x80ff0043,
|
||||
0x20, bus, devfn, where,
|
||||
size, val);
|
||||
if (where_a >= 0xe0 && where_a < 0xf0 && is_tns)
|
||||
/* EA entry-2. PP=0, BAR2, Size:3 */
|
||||
return handle_ea_bar(0x80ff0023,
|
||||
0x18, bus, devfn, where,
|
||||
size, val);
|
||||
if (where_a >= 0xe0 && where_a < 0xf0 && is_nic)
|
||||
/* EA entry-2. PP=4, VF_BAR0 (9), Size:3 */
|
||||
return handle_ea_bar(0x80ff0493,
|
||||
0x1a4, bus, devfn, where,
|
||||
size, val);
|
||||
if (where_a >= 0xf0 && where_a < 0x100 && is_nic)
|
||||
/* EA entry-3. PP=4, VF_BAR4 (d), Size:3 */
|
||||
return handle_ea_bar(0x80ff04d3,
|
||||
0x1b4, bus, devfn, where,
|
||||
size, val);
|
||||
} else if (cfg_type == 1) {
|
||||
bool is_rsl_bridge = devfn == 0x08;
|
||||
bool is_rad_bridge = devfn == 0xa0;
|
||||
bool is_zip_bridge = devfn == 0xa8;
|
||||
bool is_dfa_bridge = devfn == 0xb0;
|
||||
bool is_nic_bridge = devfn == 0x10;
|
||||
|
||||
if (where_a == 0x70) {
|
||||
addr = bus->ops->map_bus(bus, devfn, where_a);
|
||||
if (!addr) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
v = readl(addr);
|
||||
if (v & 0xff00)
|
||||
pr_err("Bad PCIe cap header: %08x\n", v);
|
||||
v |= 0xbc00; /* next capability is EA at 0xbc */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xbc) {
|
||||
if (is_nic_bridge)
|
||||
v = 0x10014; /* EA last in chain, 1 entry */
|
||||
else
|
||||
v = 0x00014; /* EA last in chain, no entries */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xc0) {
|
||||
if (is_rsl_bridge || is_nic_bridge)
|
||||
v = 0x0101; /* subordinate:secondary = 1:1 */
|
||||
else if (is_rad_bridge)
|
||||
v = 0x0202; /* subordinate:secondary = 2:2 */
|
||||
else if (is_zip_bridge)
|
||||
v = 0x0303; /* subordinate:secondary = 3:3 */
|
||||
else if (is_dfa_bridge)
|
||||
v = 0x0404; /* subordinate:secondary = 4:4 */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xc4 && is_nic_bridge) {
|
||||
/* Enabled, not-Write, SP=ff, PP=05, BEI=6, ES=4 */
|
||||
v = 0x80ff0564;
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xc8 && is_nic_bridge) {
|
||||
v = 0x00000002; /* Base-L 64-bit */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xcc && is_nic_bridge) {
|
||||
v = 0xfffffffe; /* MaxOffset-L 64-bit */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xd0 && is_nic_bridge) {
|
||||
v = 0x00008430; /* NIC Base-H */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
if (where_a == 0xd4 && is_nic_bridge) {
|
||||
v = 0x0000000f; /* MaxOffset-H */
|
||||
set_val(v, where, size, val);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
}
|
||||
no_emulation:
|
||||
return pci_generic_config_read(bus, devfn, where, size, val);
|
||||
}
|
||||
|
||||
static int thunder_ecam_config_write(struct pci_bus *bus, unsigned int devfn,
|
||||
int where, int size, u32 val)
|
||||
{
|
||||
/*
|
||||
* All BARs have fixed addresses; ignore BAR writes so they
|
||||
* don't get corrupted.
|
||||
*/
|
||||
if ((where >= 0x10 && where < 0x2c) ||
|
||||
(where >= 0x1a4 && where < 0x1bc))
|
||||
/* BAR or SR-IOV BAR */
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
|
||||
return pci_generic_config_write(bus, devfn, where, size, val);
|
||||
}
|
||||
|
||||
static struct gen_pci_cfg_bus_ops thunder_ecam_bus_ops = {
|
||||
.bus_shift = 20,
|
||||
.ops = {
|
||||
.map_bus = thunder_ecam_map_bus,
|
||||
.read = thunder_ecam_config_read,
|
||||
.write = thunder_ecam_config_write,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct of_device_id thunder_ecam_of_match[] = {
|
||||
{ .compatible = "cavium,pci-host-thunder-ecam",
|
||||
.data = &thunder_ecam_bus_ops },
|
||||
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, thunder_ecam_of_match);
|
||||
|
||||
static int thunder_ecam_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
const struct of_device_id *of_id;
|
||||
struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
|
||||
|
||||
if (!pci)
|
||||
return -ENOMEM;
|
||||
|
||||
of_id = of_match_node(thunder_ecam_of_match, dev->of_node);
|
||||
pci->cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data;
|
||||
|
||||
return pci_host_common_probe(pdev, pci);
|
||||
}
|
||||
|
||||
static struct platform_driver thunder_ecam_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.of_match_table = thunder_ecam_of_match,
|
||||
},
|
||||
.probe = thunder_ecam_probe,
|
||||
};
|
||||
module_platform_driver(thunder_ecam_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Thunder ECAM PCI host driver");
|
||||
MODULE_LICENSE("GPL v2");
|
346
drivers/pci/host/pci-thunder-pem.c
Normal file
346
drivers/pci/host/pci-thunder-pem.c
Normal file
@ -0,0 +1,346 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2015 - 2016 Cavium, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "pci-host-common.h"
|
||||
|
||||
#define PEM_CFG_WR 0x28
|
||||
#define PEM_CFG_RD 0x30
|
||||
|
||||
struct thunder_pem_pci {
|
||||
struct gen_pci gen_pci;
|
||||
u32 ea_entry[3];
|
||||
void __iomem *pem_reg_base;
|
||||
};
|
||||
|
||||
static void __iomem *thunder_pem_map_bus(struct pci_bus *bus,
|
||||
unsigned int devfn, int where)
|
||||
{
|
||||
struct gen_pci *pci = bus->sysdata;
|
||||
resource_size_t idx = bus->number - pci->cfg.bus_range->start;
|
||||
|
||||
return pci->cfg.win[idx] + ((devfn << 16) | where);
|
||||
}
|
||||
|
||||
static int thunder_pem_bridge_read(struct pci_bus *bus, unsigned int devfn,
|
||||
int where, int size, u32 *val)
|
||||
{
|
||||
u64 read_val;
|
||||
struct thunder_pem_pci *pem_pci;
|
||||
struct gen_pci *pci = bus->sysdata;
|
||||
|
||||
pem_pci = container_of(pci, struct thunder_pem_pci, gen_pci);
|
||||
|
||||
if (devfn != 0 || where >= 2048) {
|
||||
*val = ~0;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
/*
|
||||
* 32-bit accesses only. Write the address to the low order
|
||||
* bits of PEM_CFG_RD, then trigger the read by reading back.
|
||||
* The config data lands in the upper 32-bits of PEM_CFG_RD.
|
||||
*/
|
||||
read_val = where & ~3ull;
|
||||
writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD);
|
||||
read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD);
|
||||
read_val >>= 32;
|
||||
|
||||
/*
|
||||
* The config space contains some garbage, fix it up. Also
|
||||
* synthesize an EA capability for the BAR used by MSI-X.
|
||||
*/
|
||||
switch (where & ~3) {
|
||||
case 0x40:
|
||||
read_val &= 0xffff00ff;
|
||||
read_val |= 0x00007000; /* Skip MSI CAP */
|
||||
break;
|
||||
case 0x70: /* Express Cap */
|
||||
/* PME interrupt on vector 2*/
|
||||
read_val |= (2u << 25);
|
||||
break;
|
||||
case 0xb0: /* MSI-X Cap */
|
||||
/* TableSize=4, Next Cap is EA */
|
||||
read_val &= 0xc00000ff;
|
||||
read_val |= 0x0003bc00;
|
||||
break;
|
||||
case 0xb4:
|
||||
/* Table offset=0, BIR=0 */
|
||||
read_val = 0x00000000;
|
||||
break;
|
||||
case 0xb8:
|
||||
/* BPA offset=0xf0000, BIR=0 */
|
||||
read_val = 0x000f0000;
|
||||
break;
|
||||
case 0xbc:
|
||||
/* EA, 1 entry, no next Cap */
|
||||
read_val = 0x00010014;
|
||||
break;
|
||||
case 0xc0:
|
||||
/* DW2 for type-1 */
|
||||
read_val = 0x00000000;
|
||||
break;
|
||||
case 0xc4:
|
||||
/* Entry BEI=0, PP=0x00, SP=0xff, ES=3 */
|
||||
read_val = 0x80ff0003;
|
||||
break;
|
||||
case 0xc8:
|
||||
read_val = pem_pci->ea_entry[0];
|
||||
break;
|
||||
case 0xcc:
|
||||
read_val = pem_pci->ea_entry[1];
|
||||
break;
|
||||
case 0xd0:
|
||||
read_val = pem_pci->ea_entry[2];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
read_val >>= (8 * (where & 3));
|
||||
switch (size) {
|
||||
case 1:
|
||||
read_val &= 0xff;
|
||||
break;
|
||||
case 2:
|
||||
read_val &= 0xffff;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
*val = read_val;
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
static int thunder_pem_config_read(struct pci_bus *bus, unsigned int devfn,
|
||||
int where, int size, u32 *val)
|
||||
{
|
||||
struct gen_pci *pci = bus->sysdata;
|
||||
|
||||
if (bus->number < pci->cfg.bus_range->start ||
|
||||
bus->number > pci->cfg.bus_range->end)
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
|
||||
/*
|
||||
* The first device on the bus is the PEM PCIe bridge.
|
||||
* Special case its config access.
|
||||
*/
|
||||
if (bus->number == pci->cfg.bus_range->start)
|
||||
return thunder_pem_bridge_read(bus, devfn, where, size, val);
|
||||
|
||||
return pci_generic_config_read(bus, devfn, where, size, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some of the w1c_bits below also include read-only or non-writable
|
||||
* reserved bits, this makes the code simpler and is OK as the bits
|
||||
* are not affected by writing zeros to them.
|
||||
*/
|
||||
static u32 thunder_pem_bridge_w1c_bits(int where)
|
||||
{
|
||||
u32 w1c_bits = 0;
|
||||
|
||||
switch (where & ~3) {
|
||||
case 0x04: /* Command/Status */
|
||||
case 0x1c: /* Base and I/O Limit/Secondary Status */
|
||||
w1c_bits = 0xff000000;
|
||||
break;
|
||||
case 0x44: /* Power Management Control and Status */
|
||||
w1c_bits = 0xfffffe00;
|
||||
break;
|
||||
case 0x78: /* Device Control/Device Status */
|
||||
case 0x80: /* Link Control/Link Status */
|
||||
case 0x88: /* Slot Control/Slot Status */
|
||||
case 0x90: /* Root Status */
|
||||
case 0xa0: /* Link Control 2 Registers/Link Status 2 */
|
||||
w1c_bits = 0xffff0000;
|
||||
break;
|
||||
case 0x104: /* Uncorrectable Error Status */
|
||||
case 0x110: /* Correctable Error Status */
|
||||
case 0x130: /* Error Status */
|
||||
case 0x160: /* Link Control 4 */
|
||||
w1c_bits = 0xffffffff;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return w1c_bits;
|
||||
}
|
||||
|
||||
static int thunder_pem_bridge_write(struct pci_bus *bus, unsigned int devfn,
|
||||
int where, int size, u32 val)
|
||||
{
|
||||
struct gen_pci *pci = bus->sysdata;
|
||||
struct thunder_pem_pci *pem_pci;
|
||||
u64 write_val, read_val;
|
||||
u32 mask = 0;
|
||||
|
||||
pem_pci = container_of(pci, struct thunder_pem_pci, gen_pci);
|
||||
|
||||
if (devfn != 0 || where >= 2048)
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
|
||||
/*
|
||||
* 32-bit accesses only. If the write is for a size smaller
|
||||
* than 32-bits, we must first read the 32-bit value and merge
|
||||
* in the desired bits and then write the whole 32-bits back
|
||||
* out.
|
||||
*/
|
||||
switch (size) {
|
||||
case 1:
|
||||
read_val = where & ~3ull;
|
||||
writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD);
|
||||
read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD);
|
||||
read_val >>= 32;
|
||||
mask = ~(0xff << (8 * (where & 3)));
|
||||
read_val &= mask;
|
||||
val = (val & 0xff) << (8 * (where & 3));
|
||||
val |= (u32)read_val;
|
||||
break;
|
||||
case 2:
|
||||
read_val = where & ~3ull;
|
||||
writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD);
|
||||
read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD);
|
||||
read_val >>= 32;
|
||||
mask = ~(0xffff << (8 * (where & 3)));
|
||||
read_val &= mask;
|
||||
val = (val & 0xffff) << (8 * (where & 3));
|
||||
val |= (u32)read_val;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* By expanding the write width to 32 bits, we may
|
||||
* inadvertently hit some W1C bits that were not intended to
|
||||
* be written. Calculate the mask that must be applied to the
|
||||
* data to be written to avoid these cases.
|
||||
*/
|
||||
if (mask) {
|
||||
u32 w1c_bits = thunder_pem_bridge_w1c_bits(where);
|
||||
|
||||
if (w1c_bits) {
|
||||
mask &= w1c_bits;
|
||||
val &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Low order bits are the config address, the high order 32
|
||||
* bits are the data to be written.
|
||||
*/
|
||||
write_val = where & ~3ull;
|
||||
write_val |= (((u64)val) << 32);
|
||||
writeq(write_val, pem_pci->pem_reg_base + PEM_CFG_WR);
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
static int thunder_pem_config_write(struct pci_bus *bus, unsigned int devfn,
|
||||
int where, int size, u32 val)
|
||||
{
|
||||
struct gen_pci *pci = bus->sysdata;
|
||||
|
||||
if (bus->number < pci->cfg.bus_range->start ||
|
||||
bus->number > pci->cfg.bus_range->end)
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
/*
|
||||
* The first device on the bus is the PEM PCIe bridge.
|
||||
* Special case its config access.
|
||||
*/
|
||||
if (bus->number == pci->cfg.bus_range->start)
|
||||
return thunder_pem_bridge_write(bus, devfn, where, size, val);
|
||||
|
||||
|
||||
return pci_generic_config_write(bus, devfn, where, size, val);
|
||||
}
|
||||
|
||||
static struct gen_pci_cfg_bus_ops thunder_pem_bus_ops = {
|
||||
.bus_shift = 24,
|
||||
.ops = {
|
||||
.map_bus = thunder_pem_map_bus,
|
||||
.read = thunder_pem_config_read,
|
||||
.write = thunder_pem_config_write,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct of_device_id thunder_pem_of_match[] = {
|
||||
{ .compatible = "cavium,pci-host-thunder-pem",
|
||||
.data = &thunder_pem_bus_ops },
|
||||
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, thunder_pem_of_match);
|
||||
|
||||
static int thunder_pem_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
const struct of_device_id *of_id;
|
||||
resource_size_t bar4_start;
|
||||
struct resource *res_pem;
|
||||
struct thunder_pem_pci *pem_pci;
|
||||
|
||||
pem_pci = devm_kzalloc(dev, sizeof(*pem_pci), GFP_KERNEL);
|
||||
if (!pem_pci)
|
||||
return -ENOMEM;
|
||||
|
||||
of_id = of_match_node(thunder_pem_of_match, dev->of_node);
|
||||
pem_pci->gen_pci.cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data;
|
||||
|
||||
/*
|
||||
* The second register range is the PEM bridge to the PCIe
|
||||
* bus. It has a different config access method than those
|
||||
* devices behind the bridge.
|
||||
*/
|
||||
res_pem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
if (!res_pem) {
|
||||
dev_err(dev, "missing \"reg[1]\"property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pem_pci->pem_reg_base = devm_ioremap(dev, res_pem->start, 0x10000);
|
||||
if (!pem_pci->pem_reg_base)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* The MSI-X BAR for the PEM and AER interrupts is located at
|
||||
* a fixed offset from the PEM register base. Generate a
|
||||
* fragment of the synthesized Enhanced Allocation capability
|
||||
* structure here for the BAR.
|
||||
*/
|
||||
bar4_start = res_pem->start + 0xf00000;
|
||||
pem_pci->ea_entry[0] = (u32)bar4_start | 2;
|
||||
pem_pci->ea_entry[1] = (u32)(res_pem->end - bar4_start) & ~3u;
|
||||
pem_pci->ea_entry[2] = (u32)(bar4_start >> 32);
|
||||
|
||||
return pci_host_common_probe(pdev, &pem_pci->gen_pci);
|
||||
}
|
||||
|
||||
static struct platform_driver thunder_pem_driver = {
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.of_match_table = thunder_pem_of_match,
|
||||
},
|
||||
.probe = thunder_pem_probe,
|
||||
};
|
||||
module_platform_driver(thunder_pem_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Thunder PEM PCIe host driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -40,6 +40,7 @@
|
||||
#define P2A_INT_ENABLE 0x3070
|
||||
#define P2A_INT_ENA_ALL 0xf
|
||||
#define RP_LTSSM 0x3c64
|
||||
#define RP_LTSSM_MASK 0x1f
|
||||
#define LTSSM_L0 0xf
|
||||
|
||||
/* TLP configuration type 0 and 1 */
|
||||
@ -140,7 +141,7 @@ static void tlp_write_tx(struct altera_pcie *pcie,
|
||||
|
||||
static bool altera_pcie_link_is_up(struct altera_pcie *pcie)
|
||||
{
|
||||
return !!(cra_readl(pcie, RP_LTSSM) & LTSSM_L0);
|
||||
return !!((cra_readl(pcie, RP_LTSSM) & RP_LTSSM_MASK) == LTSSM_L0);
|
||||
}
|
||||
|
||||
static bool altera_pcie_valid_config(struct altera_pcie *pcie,
|
||||
|
881
drivers/pci/host/pcie-xilinx-nwl.c
Normal file
881
drivers/pci/host/pcie-xilinx-nwl.c
Normal file
@ -0,0 +1,881 @@
|
||||
/*
|
||||
* PCIe host controller driver for NWL PCIe Bridge
|
||||
* Based on pcie-xilinx.c, pci-tegra.c
|
||||
*
|
||||
* (C) Copyright 2014 - 2015, Xilinx, Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
|
||||
/* Bridge core config registers */
|
||||
#define BRCFG_PCIE_RX0 0x00000000
|
||||
#define BRCFG_INTERRUPT 0x00000010
|
||||
#define BRCFG_PCIE_RX_MSG_FILTER 0x00000020
|
||||
|
||||
/* Egress - Bridge translation registers */
|
||||
#define E_BREG_CAPABILITIES 0x00000200
|
||||
#define E_BREG_CONTROL 0x00000208
|
||||
#define E_BREG_BASE_LO 0x00000210
|
||||
#define E_BREG_BASE_HI 0x00000214
|
||||
#define E_ECAM_CAPABILITIES 0x00000220
|
||||
#define E_ECAM_CONTROL 0x00000228
|
||||
#define E_ECAM_BASE_LO 0x00000230
|
||||
#define E_ECAM_BASE_HI 0x00000234
|
||||
|
||||
/* Ingress - address translations */
|
||||
#define I_MSII_CAPABILITIES 0x00000300
|
||||
#define I_MSII_CONTROL 0x00000308
|
||||
#define I_MSII_BASE_LO 0x00000310
|
||||
#define I_MSII_BASE_HI 0x00000314
|
||||
|
||||
#define I_ISUB_CONTROL 0x000003E8
|
||||
#define SET_ISUB_CONTROL BIT(0)
|
||||
/* Rxed msg fifo - Interrupt status registers */
|
||||
#define MSGF_MISC_STATUS 0x00000400
|
||||
#define MSGF_MISC_MASK 0x00000404
|
||||
#define MSGF_LEG_STATUS 0x00000420
|
||||
#define MSGF_LEG_MASK 0x00000424
|
||||
#define MSGF_MSI_STATUS_LO 0x00000440
|
||||
#define MSGF_MSI_STATUS_HI 0x00000444
|
||||
#define MSGF_MSI_MASK_LO 0x00000448
|
||||
#define MSGF_MSI_MASK_HI 0x0000044C
|
||||
|
||||
/* Msg filter mask bits */
|
||||
#define CFG_ENABLE_PM_MSG_FWD BIT(1)
|
||||
#define CFG_ENABLE_INT_MSG_FWD BIT(2)
|
||||
#define CFG_ENABLE_ERR_MSG_FWD BIT(3)
|
||||
#define CFG_ENABLE_SLT_MSG_FWD BIT(5)
|
||||
#define CFG_ENABLE_VEN_MSG_FWD BIT(7)
|
||||
#define CFG_ENABLE_OTH_MSG_FWD BIT(13)
|
||||
#define CFG_ENABLE_VEN_MSG_EN BIT(14)
|
||||
#define CFG_ENABLE_VEN_MSG_VEN_INV BIT(15)
|
||||
#define CFG_ENABLE_VEN_MSG_VEN_ID GENMASK(31, 16)
|
||||
#define CFG_ENABLE_MSG_FILTER_MASK (CFG_ENABLE_PM_MSG_FWD | \
|
||||
CFG_ENABLE_INT_MSG_FWD | \
|
||||
CFG_ENABLE_ERR_MSG_FWD | \
|
||||
CFG_ENABLE_SLT_MSG_FWD | \
|
||||
CFG_ENABLE_VEN_MSG_FWD | \
|
||||
CFG_ENABLE_OTH_MSG_FWD | \
|
||||
CFG_ENABLE_VEN_MSG_EN | \
|
||||
CFG_ENABLE_VEN_MSG_VEN_INV | \
|
||||
CFG_ENABLE_VEN_MSG_VEN_ID)
|
||||
|
||||
/* Misc interrupt status mask bits */
|
||||
#define MSGF_MISC_SR_RXMSG_AVAIL BIT(0)
|
||||
#define MSGF_MISC_SR_RXMSG_OVER BIT(1)
|
||||
#define MSGF_MISC_SR_SLAVE_ERR BIT(4)
|
||||
#define MSGF_MISC_SR_MASTER_ERR BIT(5)
|
||||
#define MSGF_MISC_SR_I_ADDR_ERR BIT(6)
|
||||
#define MSGF_MISC_SR_E_ADDR_ERR BIT(7)
|
||||
#define MSGF_MISC_SR_UR_DETECT BIT(20)
|
||||
|
||||
#define MSGF_MISC_SR_PCIE_CORE GENMASK(18, 16)
|
||||
#define MSGF_MISC_SR_PCIE_CORE_ERR GENMASK(31, 22)
|
||||
|
||||
#define MSGF_MISC_SR_MASKALL (MSGF_MISC_SR_RXMSG_AVAIL | \
|
||||
MSGF_MISC_SR_RXMSG_OVER | \
|
||||
MSGF_MISC_SR_SLAVE_ERR | \
|
||||
MSGF_MISC_SR_MASTER_ERR | \
|
||||
MSGF_MISC_SR_I_ADDR_ERR | \
|
||||
MSGF_MISC_SR_E_ADDR_ERR | \
|
||||
MSGF_MISC_SR_UR_DETECT | \
|
||||
MSGF_MISC_SR_PCIE_CORE | \
|
||||
MSGF_MISC_SR_PCIE_CORE_ERR)
|
||||
|
||||
/* Legacy interrupt status mask bits */
|
||||
#define MSGF_LEG_SR_INTA BIT(0)
|
||||
#define MSGF_LEG_SR_INTB BIT(1)
|
||||
#define MSGF_LEG_SR_INTC BIT(2)
|
||||
#define MSGF_LEG_SR_INTD BIT(3)
|
||||
#define MSGF_LEG_SR_MASKALL (MSGF_LEG_SR_INTA | MSGF_LEG_SR_INTB | \
|
||||
MSGF_LEG_SR_INTC | MSGF_LEG_SR_INTD)
|
||||
|
||||
/* MSI interrupt status mask bits */
|
||||
#define MSGF_MSI_SR_LO_MASK BIT(0)
|
||||
#define MSGF_MSI_SR_HI_MASK BIT(0)
|
||||
|
||||
#define MSII_PRESENT BIT(0)
|
||||
#define MSII_ENABLE BIT(0)
|
||||
#define MSII_STATUS_ENABLE BIT(15)
|
||||
|
||||
/* Bridge config interrupt mask */
|
||||
#define BRCFG_INTERRUPT_MASK BIT(0)
|
||||
#define BREG_PRESENT BIT(0)
|
||||
#define BREG_ENABLE BIT(0)
|
||||
#define BREG_ENABLE_FORCE BIT(1)
|
||||
|
||||
/* E_ECAM status mask bits */
|
||||
#define E_ECAM_PRESENT BIT(0)
|
||||
#define E_ECAM_CR_ENABLE BIT(0)
|
||||
#define E_ECAM_SIZE_LOC GENMASK(20, 16)
|
||||
#define E_ECAM_SIZE_SHIFT 16
|
||||
#define ECAM_BUS_LOC_SHIFT 20
|
||||
#define ECAM_DEV_LOC_SHIFT 12
|
||||
#define NWL_ECAM_VALUE_DEFAULT 12
|
||||
|
||||
#define CFG_DMA_REG_BAR GENMASK(2, 0)
|
||||
|
||||
#define INT_PCI_MSI_NR (2 * 32)
|
||||
#define INTX_NUM 4
|
||||
|
||||
/* Readin the PS_LINKUP */
|
||||
#define PS_LINKUP_OFFSET 0x00000238
|
||||
#define PCIE_PHY_LINKUP_BIT BIT(0)
|
||||
#define PHY_RDY_LINKUP_BIT BIT(1)
|
||||
|
||||
/* Parameters for the waiting for link up routine */
|
||||
#define LINK_WAIT_MAX_RETRIES 10
|
||||
#define LINK_WAIT_USLEEP_MIN 90000
|
||||
#define LINK_WAIT_USLEEP_MAX 100000
|
||||
|
||||
struct nwl_msi { /* MSI information */
|
||||
struct irq_domain *msi_domain;
|
||||
unsigned long *bitmap;
|
||||
struct irq_domain *dev_domain;
|
||||
struct mutex lock; /* protect bitmap variable */
|
||||
int irq_msi0;
|
||||
int irq_msi1;
|
||||
};
|
||||
|
||||
struct nwl_pcie {
|
||||
struct device *dev;
|
||||
void __iomem *breg_base;
|
||||
void __iomem *pcireg_base;
|
||||
void __iomem *ecam_base;
|
||||
phys_addr_t phys_breg_base; /* Physical Bridge Register Base */
|
||||
phys_addr_t phys_pcie_reg_base; /* Physical PCIe Controller Base */
|
||||
phys_addr_t phys_ecam_base; /* Physical Configuration Base */
|
||||
u32 breg_size;
|
||||
u32 pcie_reg_size;
|
||||
u32 ecam_size;
|
||||
int irq_intx;
|
||||
int irq_misc;
|
||||
u32 ecam_value;
|
||||
u8 last_busno;
|
||||
u8 root_busno;
|
||||
struct nwl_msi msi;
|
||||
struct irq_domain *legacy_irq_domain;
|
||||
};
|
||||
|
||||
static inline u32 nwl_bridge_readl(struct nwl_pcie *pcie, u32 off)
|
||||
{
|
||||
return readl(pcie->breg_base + off);
|
||||
}
|
||||
|
||||
static inline void nwl_bridge_writel(struct nwl_pcie *pcie, u32 val, u32 off)
|
||||
{
|
||||
writel(val, pcie->breg_base + off);
|
||||
}
|
||||
|
||||
static bool nwl_pcie_link_up(struct nwl_pcie *pcie)
|
||||
{
|
||||
if (readl(pcie->pcireg_base + PS_LINKUP_OFFSET) & PCIE_PHY_LINKUP_BIT)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool nwl_phy_link_up(struct nwl_pcie *pcie)
|
||||
{
|
||||
if (readl(pcie->pcireg_base + PS_LINKUP_OFFSET) & PHY_RDY_LINKUP_BIT)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int nwl_wait_for_link(struct nwl_pcie *pcie)
|
||||
{
|
||||
int retries;
|
||||
|
||||
/* check if the link is up or not */
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
|
||||
if (nwl_phy_link_up(pcie))
|
||||
return 0;
|
||||
usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
|
||||
}
|
||||
|
||||
dev_err(pcie->dev, "PHY link never came up\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static bool nwl_pcie_valid_device(struct pci_bus *bus, unsigned int devfn)
|
||||
{
|
||||
struct nwl_pcie *pcie = bus->sysdata;
|
||||
|
||||
/* Check link before accessing downstream ports */
|
||||
if (bus->number != pcie->root_busno) {
|
||||
if (!nwl_pcie_link_up(pcie))
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Only one device down on each root port */
|
||||
if (bus->number == pcie->root_busno && devfn > 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* nwl_pcie_map_bus - Get configuration base
|
||||
*
|
||||
* @bus: Bus structure of current bus
|
||||
* @devfn: Device/function
|
||||
* @where: Offset from base
|
||||
*
|
||||
* Return: Base address of the configuration space needed to be
|
||||
* accessed.
|
||||
*/
|
||||
static void __iomem *nwl_pcie_map_bus(struct pci_bus *bus, unsigned int devfn,
|
||||
int where)
|
||||
{
|
||||
struct nwl_pcie *pcie = bus->sysdata;
|
||||
int relbus;
|
||||
|
||||
if (!nwl_pcie_valid_device(bus, devfn))
|
||||
return NULL;
|
||||
|
||||
relbus = (bus->number << ECAM_BUS_LOC_SHIFT) |
|
||||
(devfn << ECAM_DEV_LOC_SHIFT);
|
||||
|
||||
return pcie->ecam_base + relbus + where;
|
||||
}
|
||||
|
||||
/* PCIe operations */
|
||||
static struct pci_ops nwl_pcie_ops = {
|
||||
.map_bus = nwl_pcie_map_bus,
|
||||
.read = pci_generic_config_read,
|
||||
.write = pci_generic_config_write,
|
||||
};
|
||||
|
||||
static irqreturn_t nwl_pcie_misc_handler(int irq, void *data)
|
||||
{
|
||||
struct nwl_pcie *pcie = data;
|
||||
u32 misc_stat;
|
||||
|
||||
/* Checking for misc interrupts */
|
||||
misc_stat = nwl_bridge_readl(pcie, MSGF_MISC_STATUS) &
|
||||
MSGF_MISC_SR_MASKALL;
|
||||
if (!misc_stat)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (misc_stat & MSGF_MISC_SR_RXMSG_OVER)
|
||||
dev_err(pcie->dev, "Received Message FIFO Overflow\n");
|
||||
|
||||
if (misc_stat & MSGF_MISC_SR_SLAVE_ERR)
|
||||
dev_err(pcie->dev, "Slave error\n");
|
||||
|
||||
if (misc_stat & MSGF_MISC_SR_MASTER_ERR)
|
||||
dev_err(pcie->dev, "Master error\n");
|
||||
|
||||
if (misc_stat & MSGF_MISC_SR_I_ADDR_ERR)
|
||||
dev_err(pcie->dev,
|
||||
"In Misc Ingress address translation error\n");
|
||||
|
||||
if (misc_stat & MSGF_MISC_SR_E_ADDR_ERR)
|
||||
dev_err(pcie->dev,
|
||||
"In Misc Egress address translation error\n");
|
||||
|
||||
if (misc_stat & MSGF_MISC_SR_PCIE_CORE_ERR)
|
||||
dev_err(pcie->dev, "PCIe Core error\n");
|
||||
|
||||
/* Clear misc interrupt status */
|
||||
nwl_bridge_writel(pcie, misc_stat, MSGF_MISC_STATUS);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void nwl_pcie_leg_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
struct nwl_pcie *pcie;
|
||||
unsigned long status;
|
||||
u32 bit;
|
||||
u32 virq;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
pcie = irq_desc_get_handler_data(desc);
|
||||
|
||||
while ((status = nwl_bridge_readl(pcie, MSGF_LEG_STATUS) &
|
||||
MSGF_LEG_SR_MASKALL) != 0) {
|
||||
for_each_set_bit(bit, &status, INTX_NUM) {
|
||||
virq = irq_find_mapping(pcie->legacy_irq_domain,
|
||||
bit + 1);
|
||||
if (virq)
|
||||
generic_handle_irq(virq);
|
||||
}
|
||||
}
|
||||
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static void nwl_pcie_handle_msi_irq(struct nwl_pcie *pcie, u32 status_reg)
|
||||
{
|
||||
struct nwl_msi *msi;
|
||||
unsigned long status;
|
||||
u32 bit;
|
||||
u32 virq;
|
||||
|
||||
msi = &pcie->msi;
|
||||
|
||||
while ((status = nwl_bridge_readl(pcie, status_reg)) != 0) {
|
||||
for_each_set_bit(bit, &status, 32) {
|
||||
nwl_bridge_writel(pcie, 1 << bit, status_reg);
|
||||
virq = irq_find_mapping(msi->dev_domain, bit);
|
||||
if (virq)
|
||||
generic_handle_irq(virq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void nwl_pcie_msi_handler_high(struct irq_desc *desc)
|
||||
{
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
struct nwl_pcie *pcie = irq_desc_get_handler_data(desc);
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
nwl_pcie_handle_msi_irq(pcie, MSGF_MSI_STATUS_HI);
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static void nwl_pcie_msi_handler_low(struct irq_desc *desc)
|
||||
{
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
struct nwl_pcie *pcie = irq_desc_get_handler_data(desc);
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
nwl_pcie_handle_msi_irq(pcie, MSGF_MSI_STATUS_LO);
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static int nwl_legacy_map(struct irq_domain *domain, unsigned int irq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq);
|
||||
irq_set_chip_data(irq, domain->host_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops legacy_domain_ops = {
|
||||
.map = nwl_legacy_map,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
static struct irq_chip nwl_msi_irq_chip = {
|
||||
.name = "nwl_pcie:msi",
|
||||
.irq_enable = unmask_msi_irq,
|
||||
.irq_disable = mask_msi_irq,
|
||||
.irq_mask = mask_msi_irq,
|
||||
.irq_unmask = unmask_msi_irq,
|
||||
|
||||
};
|
||||
|
||||
static struct msi_domain_info nwl_msi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_MULTI_PCI_MSI),
|
||||
.chip = &nwl_msi_irq_chip,
|
||||
};
|
||||
#endif
|
||||
|
||||
static void nwl_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
{
|
||||
struct nwl_pcie *pcie = irq_data_get_irq_chip_data(data);
|
||||
phys_addr_t msi_addr = pcie->phys_pcie_reg_base;
|
||||
|
||||
msg->address_lo = lower_32_bits(msi_addr);
|
||||
msg->address_hi = upper_32_bits(msi_addr);
|
||||
msg->data = data->hwirq;
|
||||
}
|
||||
|
||||
static int nwl_msi_set_affinity(struct irq_data *irq_data,
|
||||
const struct cpumask *mask, bool force)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct irq_chip nwl_irq_chip = {
|
||||
.name = "Xilinx MSI",
|
||||
.irq_compose_msi_msg = nwl_compose_msi_msg,
|
||||
.irq_set_affinity = nwl_msi_set_affinity,
|
||||
};
|
||||
|
||||
static int nwl_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs, void *args)
|
||||
{
|
||||
struct nwl_pcie *pcie = domain->host_data;
|
||||
struct nwl_msi *msi = &pcie->msi;
|
||||
int bit;
|
||||
int i;
|
||||
|
||||
mutex_lock(&msi->lock);
|
||||
bit = bitmap_find_next_zero_area(msi->bitmap, INT_PCI_MSI_NR, 0,
|
||||
nr_irqs, 0);
|
||||
if (bit >= INT_PCI_MSI_NR) {
|
||||
mutex_unlock(&msi->lock);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
bitmap_set(msi->bitmap, bit, nr_irqs);
|
||||
|
||||
for (i = 0; i < nr_irqs; i++) {
|
||||
irq_domain_set_info(domain, virq + i, bit + i, &nwl_irq_chip,
|
||||
domain->host_data, handle_simple_irq,
|
||||
NULL, NULL);
|
||||
}
|
||||
mutex_unlock(&msi->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nwl_irq_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
struct irq_data *data = irq_domain_get_irq_data(domain, virq);
|
||||
struct nwl_pcie *pcie = irq_data_get_irq_chip_data(data);
|
||||
struct nwl_msi *msi = &pcie->msi;
|
||||
|
||||
mutex_lock(&msi->lock);
|
||||
bitmap_clear(msi->bitmap, data->hwirq, nr_irqs);
|
||||
mutex_unlock(&msi->lock);
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops dev_msi_domain_ops = {
|
||||
.alloc = nwl_irq_domain_alloc,
|
||||
.free = nwl_irq_domain_free,
|
||||
};
|
||||
|
||||
static void nwl_msi_free_irq_domain(struct nwl_pcie *pcie)
|
||||
{
|
||||
struct nwl_msi *msi = &pcie->msi;
|
||||
|
||||
if (msi->irq_msi0)
|
||||
irq_set_chained_handler_and_data(msi->irq_msi0, NULL, NULL);
|
||||
if (msi->irq_msi1)
|
||||
irq_set_chained_handler_and_data(msi->irq_msi1, NULL, NULL);
|
||||
|
||||
if (msi->msi_domain)
|
||||
irq_domain_remove(msi->msi_domain);
|
||||
if (msi->dev_domain)
|
||||
irq_domain_remove(msi->dev_domain);
|
||||
|
||||
kfree(msi->bitmap);
|
||||
msi->bitmap = NULL;
|
||||
}
|
||||
|
||||
static void nwl_pcie_free_irq_domain(struct nwl_pcie *pcie)
|
||||
{
|
||||
int i;
|
||||
u32 irq;
|
||||
|
||||
for (i = 0; i < INTX_NUM; i++) {
|
||||
irq = irq_find_mapping(pcie->legacy_irq_domain, i + 1);
|
||||
if (irq > 0)
|
||||
irq_dispose_mapping(irq);
|
||||
}
|
||||
if (pcie->legacy_irq_domain)
|
||||
irq_domain_remove(pcie->legacy_irq_domain);
|
||||
|
||||
nwl_msi_free_irq_domain(pcie);
|
||||
}
|
||||
|
||||
static int nwl_pcie_init_msi_irq_domain(struct nwl_pcie *pcie)
|
||||
{
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
struct fwnode_handle *fwnode = of_node_to_fwnode(pcie->dev->of_node);
|
||||
struct nwl_msi *msi = &pcie->msi;
|
||||
|
||||
msi->dev_domain = irq_domain_add_linear(NULL, INT_PCI_MSI_NR,
|
||||
&dev_msi_domain_ops, pcie);
|
||||
if (!msi->dev_domain) {
|
||||
dev_err(pcie->dev, "failed to create dev IRQ domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
msi->msi_domain = pci_msi_create_irq_domain(fwnode,
|
||||
&nwl_msi_domain_info,
|
||||
msi->dev_domain);
|
||||
if (!msi->msi_domain) {
|
||||
dev_err(pcie->dev, "failed to create msi IRQ domain\n");
|
||||
irq_domain_remove(msi->dev_domain);
|
||||
return -ENOMEM;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nwl_pcie_init_irq_domain(struct nwl_pcie *pcie)
|
||||
{
|
||||
struct device_node *node = pcie->dev->of_node;
|
||||
struct device_node *legacy_intc_node;
|
||||
|
||||
legacy_intc_node = of_get_next_child(node, NULL);
|
||||
if (!legacy_intc_node) {
|
||||
dev_err(pcie->dev, "No legacy intc node found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pcie->legacy_irq_domain = irq_domain_add_linear(legacy_intc_node,
|
||||
INTX_NUM,
|
||||
&legacy_domain_ops,
|
||||
pcie);
|
||||
|
||||
if (!pcie->legacy_irq_domain) {
|
||||
dev_err(pcie->dev, "failed to create IRQ domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
nwl_pcie_init_msi_irq_domain(pcie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nwl_pcie_enable_msi(struct nwl_pcie *pcie, struct pci_bus *bus)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(pcie->dev);
|
||||
struct nwl_msi *msi = &pcie->msi;
|
||||
unsigned long base;
|
||||
int ret;
|
||||
int size = BITS_TO_LONGS(INT_PCI_MSI_NR) * sizeof(long);
|
||||
|
||||
mutex_init(&msi->lock);
|
||||
|
||||
msi->bitmap = kzalloc(size, GFP_KERNEL);
|
||||
if (!msi->bitmap)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Get msi_1 IRQ number */
|
||||
msi->irq_msi1 = platform_get_irq_byname(pdev, "msi1");
|
||||
if (msi->irq_msi1 < 0) {
|
||||
dev_err(&pdev->dev, "failed to get IRQ#%d\n", msi->irq_msi1);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
irq_set_chained_handler_and_data(msi->irq_msi1,
|
||||
nwl_pcie_msi_handler_high, pcie);
|
||||
|
||||
/* Get msi_0 IRQ number */
|
||||
msi->irq_msi0 = platform_get_irq_byname(pdev, "msi0");
|
||||
if (msi->irq_msi0 < 0) {
|
||||
dev_err(&pdev->dev, "failed to get IRQ#%d\n", msi->irq_msi0);
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
irq_set_chained_handler_and_data(msi->irq_msi0,
|
||||
nwl_pcie_msi_handler_low, pcie);
|
||||
|
||||
/* Check for msii_present bit */
|
||||
ret = nwl_bridge_readl(pcie, I_MSII_CAPABILITIES) & MSII_PRESENT;
|
||||
if (!ret) {
|
||||
dev_err(pcie->dev, "MSI not present\n");
|
||||
ret = -EIO;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Enable MSII */
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, I_MSII_CONTROL) |
|
||||
MSII_ENABLE, I_MSII_CONTROL);
|
||||
|
||||
/* Enable MSII status */
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, I_MSII_CONTROL) |
|
||||
MSII_STATUS_ENABLE, I_MSII_CONTROL);
|
||||
|
||||
/* setup AFI/FPCI range */
|
||||
base = pcie->phys_pcie_reg_base;
|
||||
nwl_bridge_writel(pcie, lower_32_bits(base), I_MSII_BASE_LO);
|
||||
nwl_bridge_writel(pcie, upper_32_bits(base), I_MSII_BASE_HI);
|
||||
|
||||
/*
|
||||
* For high range MSI interrupts: disable, clear any pending,
|
||||
* and enable
|
||||
*/
|
||||
nwl_bridge_writel(pcie, (u32)~MSGF_MSI_SR_HI_MASK, MSGF_MSI_MASK_HI);
|
||||
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_MSI_STATUS_HI) &
|
||||
MSGF_MSI_SR_HI_MASK, MSGF_MSI_STATUS_HI);
|
||||
|
||||
nwl_bridge_writel(pcie, MSGF_MSI_SR_HI_MASK, MSGF_MSI_MASK_HI);
|
||||
|
||||
/*
|
||||
* For low range MSI interrupts: disable, clear any pending,
|
||||
* and enable
|
||||
*/
|
||||
nwl_bridge_writel(pcie, (u32)~MSGF_MSI_SR_LO_MASK, MSGF_MSI_MASK_LO);
|
||||
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_MSI_STATUS_LO) &
|
||||
MSGF_MSI_SR_LO_MASK, MSGF_MSI_STATUS_LO);
|
||||
|
||||
nwl_bridge_writel(pcie, MSGF_MSI_SR_LO_MASK, MSGF_MSI_MASK_LO);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
kfree(msi->bitmap);
|
||||
msi->bitmap = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int nwl_pcie_bridge_init(struct nwl_pcie *pcie)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(pcie->dev);
|
||||
u32 breg_val, ecam_val, first_busno = 0;
|
||||
int err;
|
||||
|
||||
breg_val = nwl_bridge_readl(pcie, E_BREG_CAPABILITIES) & BREG_PRESENT;
|
||||
if (!breg_val) {
|
||||
dev_err(pcie->dev, "BREG is not present\n");
|
||||
return breg_val;
|
||||
}
|
||||
|
||||
/* Write bridge_off to breg base */
|
||||
nwl_bridge_writel(pcie, lower_32_bits(pcie->phys_breg_base),
|
||||
E_BREG_BASE_LO);
|
||||
nwl_bridge_writel(pcie, upper_32_bits(pcie->phys_breg_base),
|
||||
E_BREG_BASE_HI);
|
||||
|
||||
/* Enable BREG */
|
||||
nwl_bridge_writel(pcie, ~BREG_ENABLE_FORCE & BREG_ENABLE,
|
||||
E_BREG_CONTROL);
|
||||
|
||||
/* Disable DMA channel registers */
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, BRCFG_PCIE_RX0) |
|
||||
CFG_DMA_REG_BAR, BRCFG_PCIE_RX0);
|
||||
|
||||
/* Enable Ingress subtractive decode translation */
|
||||
nwl_bridge_writel(pcie, SET_ISUB_CONTROL, I_ISUB_CONTROL);
|
||||
|
||||
/* Enable msg filtering details */
|
||||
nwl_bridge_writel(pcie, CFG_ENABLE_MSG_FILTER_MASK,
|
||||
BRCFG_PCIE_RX_MSG_FILTER);
|
||||
|
||||
err = nwl_wait_for_link(pcie);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
ecam_val = nwl_bridge_readl(pcie, E_ECAM_CAPABILITIES) & E_ECAM_PRESENT;
|
||||
if (!ecam_val) {
|
||||
dev_err(pcie->dev, "ECAM is not present\n");
|
||||
return ecam_val;
|
||||
}
|
||||
|
||||
/* Enable ECAM */
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, E_ECAM_CONTROL) |
|
||||
E_ECAM_CR_ENABLE, E_ECAM_CONTROL);
|
||||
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, E_ECAM_CONTROL) |
|
||||
(pcie->ecam_value << E_ECAM_SIZE_SHIFT),
|
||||
E_ECAM_CONTROL);
|
||||
|
||||
nwl_bridge_writel(pcie, lower_32_bits(pcie->phys_ecam_base),
|
||||
E_ECAM_BASE_LO);
|
||||
nwl_bridge_writel(pcie, upper_32_bits(pcie->phys_ecam_base),
|
||||
E_ECAM_BASE_HI);
|
||||
|
||||
/* Get bus range */
|
||||
ecam_val = nwl_bridge_readl(pcie, E_ECAM_CONTROL);
|
||||
pcie->last_busno = (ecam_val & E_ECAM_SIZE_LOC) >> E_ECAM_SIZE_SHIFT;
|
||||
/* Write primary, secondary and subordinate bus numbers */
|
||||
ecam_val = first_busno;
|
||||
ecam_val |= (first_busno + 1) << 8;
|
||||
ecam_val |= (pcie->last_busno << E_ECAM_SIZE_SHIFT);
|
||||
writel(ecam_val, (pcie->ecam_base + PCI_PRIMARY_BUS));
|
||||
|
||||
if (nwl_pcie_link_up(pcie))
|
||||
dev_info(pcie->dev, "Link is UP\n");
|
||||
else
|
||||
dev_info(pcie->dev, "Link is DOWN\n");
|
||||
|
||||
/* Get misc IRQ number */
|
||||
pcie->irq_misc = platform_get_irq_byname(pdev, "misc");
|
||||
if (pcie->irq_misc < 0) {
|
||||
dev_err(&pdev->dev, "failed to get misc IRQ %d\n",
|
||||
pcie->irq_misc);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = devm_request_irq(pcie->dev, pcie->irq_misc,
|
||||
nwl_pcie_misc_handler, IRQF_SHARED,
|
||||
"nwl_pcie:misc", pcie);
|
||||
if (err) {
|
||||
dev_err(pcie->dev, "fail to register misc IRQ#%d\n",
|
||||
pcie->irq_misc);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Disable all misc interrupts */
|
||||
nwl_bridge_writel(pcie, (u32)~MSGF_MISC_SR_MASKALL, MSGF_MISC_MASK);
|
||||
|
||||
/* Clear pending misc interrupts */
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_MISC_STATUS) &
|
||||
MSGF_MISC_SR_MASKALL, MSGF_MISC_STATUS);
|
||||
|
||||
/* Enable all misc interrupts */
|
||||
nwl_bridge_writel(pcie, MSGF_MISC_SR_MASKALL, MSGF_MISC_MASK);
|
||||
|
||||
|
||||
/* Disable all legacy interrupts */
|
||||
nwl_bridge_writel(pcie, (u32)~MSGF_LEG_SR_MASKALL, MSGF_LEG_MASK);
|
||||
|
||||
/* Clear pending legacy interrupts */
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_LEG_STATUS) &
|
||||
MSGF_LEG_SR_MASKALL, MSGF_LEG_STATUS);
|
||||
|
||||
/* Enable all legacy interrupts */
|
||||
nwl_bridge_writel(pcie, MSGF_LEG_SR_MASKALL, MSGF_LEG_MASK);
|
||||
|
||||
/* Enable the bridge config interrupt */
|
||||
nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, BRCFG_INTERRUPT) |
|
||||
BRCFG_INTERRUPT_MASK, BRCFG_INTERRUPT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nwl_pcie_parse_dt(struct nwl_pcie *pcie,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pcie->dev->of_node;
|
||||
struct resource *res;
|
||||
const char *type;
|
||||
|
||||
/* Check for device type */
|
||||
type = of_get_property(node, "device_type", NULL);
|
||||
if (!type || strcmp(type, "pci")) {
|
||||
dev_err(pcie->dev, "invalid \"device_type\" %s\n", type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "breg");
|
||||
pcie->breg_base = devm_ioremap_resource(pcie->dev, res);
|
||||
if (IS_ERR(pcie->breg_base))
|
||||
return PTR_ERR(pcie->breg_base);
|
||||
pcie->phys_breg_base = res->start;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcireg");
|
||||
pcie->pcireg_base = devm_ioremap_resource(pcie->dev, res);
|
||||
if (IS_ERR(pcie->pcireg_base))
|
||||
return PTR_ERR(pcie->pcireg_base);
|
||||
pcie->phys_pcie_reg_base = res->start;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg");
|
||||
pcie->ecam_base = devm_ioremap_resource(pcie->dev, res);
|
||||
if (IS_ERR(pcie->ecam_base))
|
||||
return PTR_ERR(pcie->ecam_base);
|
||||
pcie->phys_ecam_base = res->start;
|
||||
|
||||
/* Get intx IRQ number */
|
||||
pcie->irq_intx = platform_get_irq_byname(pdev, "intx");
|
||||
if (pcie->irq_intx < 0) {
|
||||
dev_err(&pdev->dev, "failed to get intx IRQ %d\n",
|
||||
pcie->irq_intx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
irq_set_chained_handler_and_data(pcie->irq_intx,
|
||||
nwl_pcie_leg_handler, pcie);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id nwl_pcie_of_match[] = {
|
||||
{ .compatible = "xlnx,nwl-pcie-2.11", },
|
||||
{}
|
||||
};
|
||||
|
||||
static int nwl_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct nwl_pcie *pcie;
|
||||
struct pci_bus *bus;
|
||||
struct pci_bus *child;
|
||||
int err;
|
||||
resource_size_t iobase = 0;
|
||||
LIST_HEAD(res);
|
||||
|
||||
pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL);
|
||||
if (!pcie)
|
||||
return -ENOMEM;
|
||||
|
||||
pcie->dev = &pdev->dev;
|
||||
pcie->ecam_value = NWL_ECAM_VALUE_DEFAULT;
|
||||
|
||||
err = nwl_pcie_parse_dt(pcie, pdev);
|
||||
if (err) {
|
||||
dev_err(pcie->dev, "Parsing DT failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = nwl_pcie_bridge_init(pcie);
|
||||
if (err) {
|
||||
dev_err(pcie->dev, "HW Initalization failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = of_pci_get_host_bridge_resources(node, 0, 0xff, &res, &iobase);
|
||||
if (err) {
|
||||
pr_err("Getting bridge resources failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = nwl_pcie_init_irq_domain(pcie);
|
||||
if (err) {
|
||||
dev_err(pcie->dev, "Failed creating IRQ Domain\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
bus = pci_create_root_bus(&pdev->dev, pcie->root_busno,
|
||||
&nwl_pcie_ops, pcie, &res);
|
||||
if (!bus)
|
||||
return -ENOMEM;
|
||||
|
||||
if (IS_ENABLED(CONFIG_PCI_MSI)) {
|
||||
err = nwl_pcie_enable_msi(pcie, bus);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to enable MSI support: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
pci_scan_child_bus(bus);
|
||||
pci_assign_unassigned_bus_resources(bus);
|
||||
list_for_each_entry(child, &bus->children, node)
|
||||
pcie_bus_configure_settings(child);
|
||||
pci_bus_add_devices(bus);
|
||||
platform_set_drvdata(pdev, pcie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nwl_pcie_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct nwl_pcie *pcie = platform_get_drvdata(pdev);
|
||||
|
||||
nwl_pcie_free_irq_domain(pcie);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver nwl_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "nwl-pcie",
|
||||
.of_match_table = nwl_pcie_of_match,
|
||||
},
|
||||
.probe = nwl_pcie_probe,
|
||||
.remove = nwl_pcie_remove,
|
||||
};
|
||||
module_platform_driver(nwl_pcie_driver);
|
||||
|
||||
MODULE_AUTHOR("Xilinx, Inc");
|
||||
MODULE_DESCRIPTION("NWL PCIe driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -94,9 +94,6 @@
|
||||
/* Number of MSI IRQs */
|
||||
#define XILINX_NUM_MSI_IRQS 128
|
||||
|
||||
/* Number of Memory Resources */
|
||||
#define XILINX_MAX_NUM_RESOURCES 3
|
||||
|
||||
/**
|
||||
* struct xilinx_pcie_port - PCIe port information
|
||||
* @reg_base: IO Mapped Register Base
|
||||
@ -105,7 +102,6 @@
|
||||
* @root_busno: Root Bus number
|
||||
* @dev: Device pointer
|
||||
* @irq_domain: IRQ domain pointer
|
||||
* @bus_range: Bus range
|
||||
* @resources: Bus Resources
|
||||
*/
|
||||
struct xilinx_pcie_port {
|
||||
@ -115,17 +111,11 @@ struct xilinx_pcie_port {
|
||||
u8 root_busno;
|
||||
struct device *dev;
|
||||
struct irq_domain *irq_domain;
|
||||
struct resource bus_range;
|
||||
struct list_head resources;
|
||||
};
|
||||
|
||||
static DECLARE_BITMAP(msi_irq_in_use, XILINX_NUM_MSI_IRQS);
|
||||
|
||||
static inline struct xilinx_pcie_port *sys_to_pcie(struct pci_sys_data *sys)
|
||||
{
|
||||
return sys->private_data;
|
||||
}
|
||||
|
||||
static inline u32 pcie_read(struct xilinx_pcie_port *port, u32 reg)
|
||||
{
|
||||
return readl(port->reg_base + reg);
|
||||
@ -167,7 +157,7 @@ static void xilinx_pcie_clear_err_interrupts(struct xilinx_pcie_port *port)
|
||||
*/
|
||||
static bool xilinx_pcie_valid_device(struct pci_bus *bus, unsigned int devfn)
|
||||
{
|
||||
struct xilinx_pcie_port *port = sys_to_pcie(bus->sysdata);
|
||||
struct xilinx_pcie_port *port = bus->sysdata;
|
||||
|
||||
/* Check if link is up when trying to access downstream ports */
|
||||
if (bus->number != port->root_busno)
|
||||
@ -200,7 +190,7 @@ static bool xilinx_pcie_valid_device(struct pci_bus *bus, unsigned int devfn)
|
||||
static void __iomem *xilinx_pcie_map_bus(struct pci_bus *bus,
|
||||
unsigned int devfn, int where)
|
||||
{
|
||||
struct xilinx_pcie_port *port = sys_to_pcie(bus->sysdata);
|
||||
struct xilinx_pcie_port *port = bus->sysdata;
|
||||
int relbus;
|
||||
|
||||
if (!xilinx_pcie_valid_device(bus, devfn))
|
||||
@ -232,7 +222,7 @@ static void xilinx_pcie_destroy_msi(unsigned int irq)
|
||||
|
||||
if (!test_bit(irq, msi_irq_in_use)) {
|
||||
msi = irq_get_msi_desc(irq);
|
||||
port = sys_to_pcie(msi_desc_to_pci_sysdata(msi));
|
||||
port = msi_desc_to_pci_sysdata(msi);
|
||||
dev_err(port->dev, "Trying to free unused MSI#%d\n", irq);
|
||||
} else {
|
||||
clear_bit(irq, msi_irq_in_use);
|
||||
@ -281,7 +271,7 @@ static int xilinx_pcie_msi_setup_irq(struct msi_controller *chip,
|
||||
struct pci_dev *pdev,
|
||||
struct msi_desc *desc)
|
||||
{
|
||||
struct xilinx_pcie_port *port = sys_to_pcie(pdev->bus->sysdata);
|
||||
struct xilinx_pcie_port *port = pdev->bus->sysdata;
|
||||
unsigned int irq;
|
||||
int hwirq;
|
||||
struct msi_msg msg;
|
||||
@ -617,138 +607,6 @@ static void xilinx_pcie_init_port(struct xilinx_pcie_port *port)
|
||||
XILINX_PCIE_REG_RPSC);
|
||||
}
|
||||
|
||||
/**
|
||||
* xilinx_pcie_setup - Setup memory resources
|
||||
* @nr: Bus number
|
||||
* @sys: Per controller structure
|
||||
*
|
||||
* Return: '1' on success and error value on failure
|
||||
*/
|
||||
static int xilinx_pcie_setup(int nr, struct pci_sys_data *sys)
|
||||
{
|
||||
struct xilinx_pcie_port *port = sys_to_pcie(sys);
|
||||
|
||||
list_splice_init(&port->resources, &sys->resources);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* xilinx_pcie_scan_bus - Scan PCIe bus for devices
|
||||
* @nr: Bus number
|
||||
* @sys: Per controller structure
|
||||
*
|
||||
* Return: Valid Bus pointer on success and NULL on failure
|
||||
*/
|
||||
static struct pci_bus *xilinx_pcie_scan_bus(int nr, struct pci_sys_data *sys)
|
||||
{
|
||||
struct xilinx_pcie_port *port = sys_to_pcie(sys);
|
||||
struct pci_bus *bus;
|
||||
|
||||
port->root_busno = sys->busnr;
|
||||
|
||||
if (IS_ENABLED(CONFIG_PCI_MSI))
|
||||
bus = pci_scan_root_bus_msi(port->dev, sys->busnr,
|
||||
&xilinx_pcie_ops, sys,
|
||||
&sys->resources,
|
||||
&xilinx_pcie_msi_chip);
|
||||
else
|
||||
bus = pci_scan_root_bus(port->dev, sys->busnr,
|
||||
&xilinx_pcie_ops, sys, &sys->resources);
|
||||
return bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* xilinx_pcie_parse_and_add_res - Add resources by parsing ranges
|
||||
* @port: PCIe port information
|
||||
*
|
||||
* Return: '0' on success and error value on failure
|
||||
*/
|
||||
static int xilinx_pcie_parse_and_add_res(struct xilinx_pcie_port *port)
|
||||
{
|
||||
struct device *dev = port->dev;
|
||||
struct device_node *node = dev->of_node;
|
||||
struct resource *mem;
|
||||
resource_size_t offset;
|
||||
struct of_pci_range_parser parser;
|
||||
struct of_pci_range range;
|
||||
struct resource_entry *win;
|
||||
int err = 0, mem_resno = 0;
|
||||
|
||||
/* Get the ranges */
|
||||
if (of_pci_range_parser_init(&parser, node)) {
|
||||
dev_err(dev, "missing \"ranges\" property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Parse the ranges and add the resources found to the list */
|
||||
for_each_of_pci_range(&parser, &range) {
|
||||
|
||||
if (mem_resno >= XILINX_MAX_NUM_RESOURCES) {
|
||||
dev_err(dev, "Maximum memory resources exceeded\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mem = devm_kmalloc(dev, sizeof(*mem), GFP_KERNEL);
|
||||
if (!mem) {
|
||||
err = -ENOMEM;
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
of_pci_range_to_resource(&range, node, mem);
|
||||
|
||||
switch (mem->flags & IORESOURCE_TYPE_BITS) {
|
||||
case IORESOURCE_MEM:
|
||||
offset = range.cpu_addr - range.pci_addr;
|
||||
mem_resno++;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (err < 0) {
|
||||
dev_warn(dev, "Invalid resource found %pR\n", mem);
|
||||
continue;
|
||||
}
|
||||
|
||||
err = request_resource(&iomem_resource, mem);
|
||||
if (err)
|
||||
goto free_resources;
|
||||
|
||||
pci_add_resource_offset(&port->resources, mem, offset);
|
||||
}
|
||||
|
||||
/* Get the bus range */
|
||||
if (of_pci_parse_bus_range(node, &port->bus_range)) {
|
||||
u32 val = pcie_read(port, XILINX_PCIE_REG_BIR);
|
||||
u8 last;
|
||||
|
||||
last = (val & XILINX_PCIE_BIR_ECAM_SZ_MASK) >>
|
||||
XILINX_PCIE_BIR_ECAM_SZ_SHIFT;
|
||||
|
||||
port->bus_range = (struct resource) {
|
||||
.name = node->name,
|
||||
.start = 0,
|
||||
.end = last,
|
||||
.flags = IORESOURCE_BUS,
|
||||
};
|
||||
}
|
||||
|
||||
/* Register bus resource */
|
||||
pci_add_resource(&port->resources, &port->bus_range);
|
||||
|
||||
return 0;
|
||||
|
||||
free_resources:
|
||||
release_child_resources(&iomem_resource);
|
||||
resource_list_for_each_entry(win, &port->resources)
|
||||
devm_kfree(dev, win->res);
|
||||
pci_free_resource_list(&port->resources);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* xilinx_pcie_parse_dt - Parse Device tree
|
||||
* @port: PCIe port information
|
||||
@ -800,9 +658,12 @@ static int xilinx_pcie_parse_dt(struct xilinx_pcie_port *port)
|
||||
static int xilinx_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct xilinx_pcie_port *port;
|
||||
struct hw_pci hw;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct pci_bus *bus;
|
||||
|
||||
int err;
|
||||
resource_size_t iobase = 0;
|
||||
LIST_HEAD(res);
|
||||
|
||||
if (!dev->of_node)
|
||||
return -ENODEV;
|
||||
@ -827,34 +688,28 @@ static int xilinx_pcie_probe(struct platform_device *pdev)
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse PCI ranges, configuration bus range and
|
||||
* request their resources
|
||||
*/
|
||||
INIT_LIST_HEAD(&port->resources);
|
||||
err = xilinx_pcie_parse_and_add_res(port);
|
||||
err = of_pci_get_host_bridge_resources(dev->of_node, 0, 0xff, &res,
|
||||
&iobase);
|
||||
if (err) {
|
||||
dev_err(dev, "Failed adding resources\n");
|
||||
dev_err(dev, "Getting bridge resources failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, port);
|
||||
|
||||
/* Register the device */
|
||||
memset(&hw, 0, sizeof(hw));
|
||||
hw = (struct hw_pci) {
|
||||
.nr_controllers = 1,
|
||||
.private_data = (void **)&port,
|
||||
.setup = xilinx_pcie_setup,
|
||||
.map_irq = of_irq_parse_and_map_pci,
|
||||
.scan = xilinx_pcie_scan_bus,
|
||||
.ops = &xilinx_pcie_ops,
|
||||
};
|
||||
bus = pci_create_root_bus(&pdev->dev, 0,
|
||||
&xilinx_pcie_ops, port, &res);
|
||||
if (!bus)
|
||||
return -ENOMEM;
|
||||
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
xilinx_pcie_msi_chip.dev = port->dev;
|
||||
bus->msi = &xilinx_pcie_msi_chip;
|
||||
#endif
|
||||
pci_common_init_dev(dev, &hw);
|
||||
pci_scan_child_bus(bus);
|
||||
pci_assign_unassigned_bus_resources(bus);
|
||||
#ifndef CONFIG_MICROBLAZE
|
||||
pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci);
|
||||
#endif
|
||||
pci_bus_add_devices(bus);
|
||||
platform_set_drvdata(pdev, port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -757,6 +757,12 @@ add_dev:
|
||||
|
||||
pcibios_add_bus(child);
|
||||
|
||||
if (child->ops->add_bus) {
|
||||
ret = child->ops->add_bus(child);
|
||||
if (WARN_ON(ret < 0))
|
||||
dev_err(&child->dev, "failed to add bus: %d\n", ret);
|
||||
}
|
||||
|
||||
/* Create legacy_io and legacy_mem files for this bus */
|
||||
pci_create_legacy_files(child);
|
||||
|
||||
|
@ -54,6 +54,10 @@ void pci_remove_bus(struct pci_bus *bus)
|
||||
pci_bus_release_busn_res(bus);
|
||||
up_write(&pci_bus_sem);
|
||||
pci_remove_legacy_files(bus);
|
||||
|
||||
if (bus->ops->remove_bus)
|
||||
bus->ops->remove_bus(bus);
|
||||
|
||||
pcibios_remove_bus(bus);
|
||||
device_unregister(&bus->dev);
|
||||
}
|
||||
|
@ -572,6 +572,8 @@ static inline int pcibios_err_to_errno(int err)
|
||||
/* Low-level architecture-dependent routines */
|
||||
|
||||
struct pci_ops {
|
||||
int (*add_bus)(struct pci_bus *bus);
|
||||
void (*remove_bus)(struct pci_bus *bus);
|
||||
void __iomem *(*map_bus)(struct pci_bus *bus, unsigned int devfn, int where);
|
||||
int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);
|
||||
int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val);
|
||||
|
Loading…
x
Reference in New Issue
Block a user