PCI changes for the v4.5 merge window:
Enumeration Simplify config space size computation (Bjorn Helgaas) Avoid iterating through ROM outside the resource window (Edward O'Callaghan) Support PCIe devices with short cfg_size (Jason S. McMullan) Add Netronome vendor and device IDs (Jason S. McMullan) Limit config space size for Netronome NFP6000 family (Jason S. McMullan) Add Netronome NFP4000 PF device ID (Simon Horman) Limit config space size for Netronome NFP4000 (Simon Horman) Print warnings for all invalid expansion ROM headers (Vladis Dronov) Resource management Fix minimum allocation address overwrite (Christoph Biedl) PCI device hotplug acpiphp_ibm: Fix null dereferences on null ibm_slot (Colin Ian King) pciehp: Always protect pciehp_disable_slot() with hotplug mutex (Guenter Roeck) shpchp: Constify hpc_ops structure (Julia Lawall) ibmphp: Remove unneeded NULL test (Julia Lawall) Power management Make ASPM sysfs link_state_store() consistent with link_state_show() (Andy Lutomirski) Virtualization Add function 1 DMA alias quirk for Lite-On/Plextor M6e/Marvell 88SS9183 (Tim Sander) MSI Remove empty pci_msi_init_pci_dev() (Bjorn Helgaas) Mark PCIe/PCI (MSI) IRQ cascade handlers as IRQF_NO_THREAD (Grygorii Strashko) Initialize MSI capability for all architectures (Guilherme G. Piccoli) Relax msi_domain_alloc() to support parentless MSI irqdomains (Liu Jiang) ARM Versatile host bridge driver Remove unused pci_sys_data structures (Lorenzo Pieralisi) Broadcom iProc host bridge driver Hide CONFIG_PCIE_IPROC (Arnd Bergmann) Do not use 0x in front of %pap (Dmitry V. Krivenok) Update iProc PCIe device tree binding (Ray Jui) Add PAXC interface support (Ray Jui) Add iProc PCIe MSI device tree binding (Ray Jui) Add iProc PCIe MSI support (Ray Jui) Freescale i.MX6 host bridge driver Use gpio_set_value_cansleep() (Fabio Estevam) Add support for active-low reset GPIO (Petr Štetiar) HiSilicon host bridge driver Add support for HiSilicon Hip06 PCIe host controllers (Gabriele Paoloni) Intel VMD host bridge driver Export irq_domain_set_info() for module use (Keith Busch) x86/PCI: Allow DMA ops specific to a PCI domain (Keith Busch) Use 32 bit PCI domain numbers (Keith Busch) Add driver for Intel Volume Management Device (VMD) (Keith Busch) Qualcomm host bridge driver Document PCIe devicetree bindings (Stanimir Varbanov) Add Qualcomm PCIe controller driver (Stanimir Varbanov) dts: apq8064: add PCIe devicetree node (Stanimir Varbanov) dts: ifc6410: enable PCIe DT node for this board (Stanimir Varbanov) Renesas R-Car host bridge driver Add support for R-Car H3 to pcie-rcar (Harunobu Kurokawa) Allow DT to override default window settings (Phil Edworthy) Convert to DT resource parsing API (Phil Edworthy) Revert "PCI: rcar: Build pcie-rcar.c only on ARM" (Phil Edworthy) Remove unused pci_sys_data struct from pcie-rcar (Phil Edworthy) Add runtime PM support to pcie-rcar (Phil Edworthy) Add Gen2 PHY setup to pcie-rcar (Phil Edworthy) Add gen2 fallback compatibility string for pci-rcar-gen2 (Simon Horman) Add gen2 fallback compatibility string for pcie-rcar (Simon Horman) Synopsys DesignWare host bridge driver Simplify control flow (Bjorn Helgaas) Make config accessor override checking symmetric (Bjorn Helgaas) Ensure ATU is enabled before IO/conf space accesses (Stanimir Varbanov) Miscellaneous Add of_pci_get_host_bridge_resources() stub (Arnd Bergmann) Check for PCI_HEADER_TYPE_BRIDGE equality, not bitmask (Bjorn Helgaas) Fix all whitespace issues (Bogicevic Sasa) x86/PCI: Simplify pci_bios_{read,write} (Geliang Tang) Use to_pci_dev() instead of open-coding it (Geliang Tang) Use kobj_to_dev() instead of open-coding it (Geliang Tang) Use list_for_each_entry() to simplify code (Geliang Tang) Fix typos in <linux/msi.h> (Thomas Petazzoni) x86/PCI: Clarify AMD Fam10h config access restrictions comment (Tomasz Nowicki) -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJWoQIuAAoJEFmIoMA60/r8ckYP/0ZrkANeN1SB5cQVi2k7aceq kQb1Hk6ifxohJvgpJ/iwmVCHoApyeBfUBfrC+fUpIC2f7ncPsE5HNyjqpAWzFzj2 sYWwY029yjBQ9g4mPhkvjBXfha+lNtLthWc+Xxcat5pdcyG63Dg4SfJKWm2ZYnbN 0GJzyRZXIwAMnNf0KIr61Aqru0nXeHvi5wblyJ08UZ7AcNzCtB0wKLmE3S6SeZVF f2fry35zcGu+TFvQ1hAYemfl3XyDBJ87nPiKzJAwYSaKcWPFWt+72PBDPO6X9squ 6prm4nmAgeG2Oo4Zu0fbkDlB2bEsWUc14/xT0i5Wfs35vcwzF+S1zirJAtVqoNir NgC7fSbEHbsS7FZOz0rBOBIvIkbb6NdfLFuZqUFv0X1M5bRFywjo8lZRfAYoGJzK Mmus0uKbklx5m6RT5adf9+Plev1YJT6XZW9XrDpGnxrwRyPjHmyvuTWsYkumxY7Q CE5Wr3p7q2I2+MtrQVv2D9Nzsb+4zQ6BgHrd2vwR/IxTsfdXLU7+B691wkUDX8No UKFxBd0FiVCn+srG96u7lWQvdoUqoNCogTZSVzGR5gFBv3zAN9gi8HS7NbV558Mg Io3Xw+6dcbG33uvWdU6jHEDLMQsohZcp05Q5esCgRQNV4cGJbPxBDtOZEO/ezvW4 FAI7lfgYTFiQK3NzE3Ng =z9mQ -----END PGP SIGNATURE----- Merge tag 'pci-v4.5-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci Pull PCI updates from Bjorn Helgaas: "PCI changes for the v4.5 merge window: Enumeration: - Simplify config space size computation (Bjorn Helgaas) - Avoid iterating through ROM outside the resource window (Edward O'Callaghan) - Support PCIe devices with short cfg_size (Jason S. McMullan) - Add Netronome vendor and device IDs (Jason S. McMullan) - Limit config space size for Netronome NFP6000 family (Jason S. McMullan) - Add Netronome NFP4000 PF device ID (Simon Horman) - Limit config space size for Netronome NFP4000 (Simon Horman) - Print warnings for all invalid expansion ROM headers (Vladis Dronov) Resource management: - Fix minimum allocation address overwrite (Christoph Biedl) PCI device hotplug: - acpiphp_ibm: Fix null dereferences on null ibm_slot (Colin Ian King) - pciehp: Always protect pciehp_disable_slot() with hotplug mutex (Guenter Roeck) - shpchp: Constify hpc_ops structure (Julia Lawall) - ibmphp: Remove unneeded NULL test (Julia Lawall) Power management: - Make ASPM sysfs link_state_store() consistent with link_state_show() (Andy Lutomirski) Virtualization - Add function 1 DMA alias quirk for Lite-On/Plextor M6e/Marvell 88SS9183 (Tim Sander) MSI: - Remove empty pci_msi_init_pci_dev() (Bjorn Helgaas) - Mark PCIe/PCI (MSI) IRQ cascade handlers as IRQF_NO_THREAD (Grygorii Strashko) - Initialize MSI capability for all architectures (Guilherme G. Piccoli) - Relax msi_domain_alloc() to support parentless MSI irqdomains (Liu Jiang) ARM Versatile host bridge driver: - Remove unused pci_sys_data structures (Lorenzo Pieralisi) Broadcom iProc host bridge driver: - Hide CONFIG_PCIE_IPROC (Arnd Bergmann) - Do not use 0x in front of %pap (Dmitry V. Krivenok) - Update iProc PCIe device tree binding (Ray Jui) - Add PAXC interface support (Ray Jui) - Add iProc PCIe MSI device tree binding (Ray Jui) - Add iProc PCIe MSI support (Ray Jui) Freescale i.MX6 host bridge driver: - Use gpio_set_value_cansleep() (Fabio Estevam) - Add support for active-low reset GPIO (Petr Štetiar) HiSilicon host bridge driver: - Add support for HiSilicon Hip06 PCIe host controllers (Gabriele Paoloni) Intel VMD host bridge driver: - Export irq_domain_set_info() for module use (Keith Busch) - x86/PCI: Allow DMA ops specific to a PCI domain (Keith Busch) - Use 32 bit PCI domain numbers (Keith Busch) - Add driver for Intel Volume Management Device (VMD) (Keith Busch) Qualcomm host bridge driver: - Document PCIe devicetree bindings (Stanimir Varbanov) - Add Qualcomm PCIe controller driver (Stanimir Varbanov) - dts: apq8064: add PCIe devicetree node (Stanimir Varbanov) - dts: ifc6410: enable PCIe DT node for this board (Stanimir Varbanov) Renesas R-Car host bridge driver: - Add support for R-Car H3 to pcie-rcar (Harunobu Kurokawa) - Allow DT to override default window settings (Phil Edworthy) - Convert to DT resource parsing API (Phil Edworthy) - Revert "PCI: rcar: Build pcie-rcar.c only on ARM" (Phil Edworthy) - Remove unused pci_sys_data struct from pcie-rcar (Phil Edworthy) - Add runtime PM support to pcie-rcar (Phil Edworthy) - Add Gen2 PHY setup to pcie-rcar (Phil Edworthy) - Add gen2 fallback compatibility string for pci-rcar-gen2 (Simon Horman) - Add gen2 fallback compatibility string for pcie-rcar (Simon Horman) Synopsys DesignWare host bridge driver: - Simplify control flow (Bjorn Helgaas) - Make config accessor override checking symmetric (Bjorn Helgaas) - Ensure ATU is enabled before IO/conf space accesses (Stanimir Varbanov) Miscellaneous: - Add of_pci_get_host_bridge_resources() stub (Arnd Bergmann) - Check for PCI_HEADER_TYPE_BRIDGE equality, not bitmask (Bjorn Helgaas) - Fix all whitespace issues (Bogicevic Sasa) - x86/PCI: Simplify pci_bios_{read,write} (Geliang Tang) - Use to_pci_dev() instead of open-coding it (Geliang Tang) - Use kobj_to_dev() instead of open-coding it (Geliang Tang) - Use list_for_each_entry() to simplify code (Geliang Tang) - Fix typos in <linux/msi.h> (Thomas Petazzoni) - x86/PCI: Clarify AMD Fam10h config access restrictions comment (Tomasz Nowicki)" * tag 'pci-v4.5-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci: (58 commits) PCI: Add function 1 DMA alias quirk for Lite-On/Plextor M6e/Marvell 88SS9183 PCI: Limit config space size for Netronome NFP4000 PCI: Add Netronome NFP4000 PF device ID x86/PCI: Add driver for Intel Volume Management Device (VMD) PCI/AER: Use 32 bit PCI domain numbers x86/PCI: Allow DMA ops specific to a PCI domain irqdomain: Export irq_domain_set_info() for module use PCI: host: Add of_pci_get_host_bridge_resources() stub genirq/MSI: Relax msi_domain_alloc() to support parentless MSI irqdomains PCI: rcar: Add Gen2 PHY setup to pcie-rcar PCI: rcar: Add runtime PM support to pcie-rcar PCI: designware: Make config accessor override checking symmetric PCI: ibmphp: Remove unneeded NULL test ARM: dts: ifc6410: enable PCIe DT node for this board ARM: dts: apq8064: add PCIe devicetree node PCI: hotplug: Use list_for_each_entry() to simplify code PCI: rcar: Remove unused pci_sys_data struct from pcie-rcar PCI: hisi: Add support for HiSilicon Hip06 PCIe host controllers PCI: Avoid iterating through memory outside the resource window PCI: acpiphp_ibm: Fix null dereferences on null ibm_slot ...
This commit is contained in:
commit
d43421565b
@ -1,7 +1,10 @@
|
||||
* Broadcom iProc PCIe controller with the platform bus interface
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "brcm,iproc-pcie"
|
||||
- compatible: Must be "brcm,iproc-pcie" for PAXB, or "brcm,iproc-pcie-paxc"
|
||||
for PAXC. PAXB-based root complex is used for external endpoint devices.
|
||||
PAXC-based root complex is connected to emulated endpoint devices
|
||||
internal to the ASIC
|
||||
- reg: base address and length of the PCIe controller I/O register space
|
||||
- #interrupt-cells: set to <1>
|
||||
- interrupt-map-mask and interrupt-map, standard PCI properties to define the
|
||||
@ -32,6 +35,28 @@ Optional:
|
||||
- brcm,pcie-ob-oarr-size: Some iProc SoCs need the OARR size bit to be set to
|
||||
increase the outbound window size
|
||||
|
||||
MSI support (optional):
|
||||
|
||||
For older platforms without MSI integrated in the GIC, iProc PCIe core provides
|
||||
an event queue based MSI support. The iProc MSI uses host memories to store
|
||||
MSI posted writes in the event queues
|
||||
|
||||
- msi-parent: Link to the device node of the MSI controller. On newer iProc
|
||||
platforms, the MSI controller may be gicv2m or gicv3-its. On older iProc
|
||||
platforms without MSI support in its interrupt controller, one may use the
|
||||
event queue based MSI support integrated within the iProc PCIe core.
|
||||
|
||||
When the iProc event queue based MSI is used, one needs to define the
|
||||
following properties in the MSI device node:
|
||||
- compatible: Must be "brcm,iproc-msi"
|
||||
- msi-controller: claims itself as an MSI controller
|
||||
- interrupt-parent: Link to its parent interrupt device
|
||||
- interrupts: List of interrupt IDs from its parent interrupt device
|
||||
|
||||
Optional properties:
|
||||
- brcm,pcie-msi-inten: Needs to be present for some older iProc platforms that
|
||||
require the interrupt enable registers to be set explicitly to enable MSI
|
||||
|
||||
Example:
|
||||
pcie0: pcie@18012000 {
|
||||
compatible = "brcm,iproc-pcie";
|
||||
@ -58,6 +83,19 @@ Example:
|
||||
brcm,pcie-ob-oarr-size;
|
||||
brcm,pcie-ob-axi-offset = <0x00000000>;
|
||||
brcm,pcie-ob-window-size = <256>;
|
||||
|
||||
msi-parent = <&msi0>;
|
||||
|
||||
/* iProc event queue based MSI */
|
||||
msi0: msi@18012000 {
|
||||
compatible = "brcm,iproc-msi";
|
||||
msi-controller;
|
||||
interrupt-parent = <&gic>;
|
||||
interrupts = <GIC_SPI 96 IRQ_TYPE_NONE>,
|
||||
<GIC_SPI 97 IRQ_TYPE_NONE>,
|
||||
<GIC_SPI 98 IRQ_TYPE_NONE>,
|
||||
<GIC_SPI 99 IRQ_TYPE_NONE>,
|
||||
};
|
||||
};
|
||||
|
||||
pcie1: pcie@18013000 {
|
||||
|
@ -1,4 +1,4 @@
|
||||
HiSilicon PCIe host bridge DT description
|
||||
HiSilicon Hip05 and Hip06 PCIe host bridge DT description
|
||||
|
||||
HiSilicon PCIe host controller is based on Designware PCI core.
|
||||
It shares common functions with PCIe Designware core driver and inherits
|
||||
@ -7,8 +7,8 @@ Documentation/devicetree/bindings/pci/designware-pci.txt.
|
||||
|
||||
Additional properties are described here:
|
||||
|
||||
Required properties:
|
||||
- compatible: Should contain "hisilicon,hip05-pcie".
|
||||
Required properties
|
||||
- compatible: Should contain "hisilicon,hip05-pcie" or "hisilicon,hip06-pcie".
|
||||
- reg: Should contain rc_dbi, config registers location and length.
|
||||
- reg-names: Must include the following entries:
|
||||
"rc_dbi": controller configuration registers;
|
||||
@ -20,7 +20,7 @@ Optional properties:
|
||||
- status: Either "ok" or "disabled".
|
||||
- dma-coherent: Present if DMA operations are coherent.
|
||||
|
||||
Example:
|
||||
Hip05 Example (note that Hip06 is the same except compatible):
|
||||
pcie@0xb0080000 {
|
||||
compatible = "hisilicon,hip05-pcie", "snps,dw-pcie";
|
||||
reg = <0 0xb0080000 0 0x10000>, <0x220 0x00000000 0 0x2000>;
|
||||
|
@ -8,7 +8,14 @@ OHCI and EHCI controllers.
|
||||
Required properties:
|
||||
- compatible: "renesas,pci-r8a7790" for the R8A7790 SoC;
|
||||
"renesas,pci-r8a7791" for the R8A7791 SoC;
|
||||
"renesas,pci-r8a7794" for the R8A7794 SoC.
|
||||
"renesas,pci-r8a7794" for the R8A7794 SoC;
|
||||
"renesas,pci-rcar-gen2" for a generic R-Car Gen2 compatible device
|
||||
|
||||
|
||||
When compatible with the generic version, nodes must list the
|
||||
SoC-specific version corresponding to the platform first
|
||||
followed by the generic version.
|
||||
|
||||
- reg: A list of physical regions to access the device: the first is
|
||||
the operational registers for the OHCI/EHCI controllers and the
|
||||
second is for the bridge configuration and control registers.
|
||||
@ -24,10 +31,15 @@ Required properties:
|
||||
- interrupt-map-mask: standard property that helps to define the interrupt
|
||||
mapping.
|
||||
|
||||
Optional properties:
|
||||
- dma-ranges: a single range for the inbound memory region. If not supplied,
|
||||
defaults to 1GiB at 0x40000000. Note there are hardware restrictions on the
|
||||
allowed combinations of address and size.
|
||||
|
||||
Example SoC configuration:
|
||||
|
||||
pci0: pci@ee090000 {
|
||||
compatible = "renesas,pci-r8a7790";
|
||||
compatible = "renesas,pci-r8a7790", "renesas,pci-rcar-gen2";
|
||||
clocks = <&mstp7_clks R8A7790_CLK_EHCI>;
|
||||
reg = <0x0 0xee090000 0x0 0xc00>,
|
||||
<0x0 0xee080000 0x0 0x1100>;
|
||||
@ -38,6 +50,7 @@ Example SoC configuration:
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
#interrupt-cells = <1>;
|
||||
dma-ranges = <0x42000000 0 0x40000000 0 0x40000000 0 0x40000000>;
|
||||
interrupt-map-mask = <0xff00 0 0 0x7>;
|
||||
interrupt-map = <0x0000 0 0 1 &gic 0 108 IRQ_TYPE_LEVEL_HIGH
|
||||
0x0800 0 0 1 &gic 0 108 IRQ_TYPE_LEVEL_HIGH
|
||||
|
233
Documentation/devicetree/bindings/pci/qcom,pcie.txt
Normal file
233
Documentation/devicetree/bindings/pci/qcom,pcie.txt
Normal file
@ -0,0 +1,233 @@
|
||||
* Qualcomm PCI express root complex
|
||||
|
||||
- compatible:
|
||||
Usage: required
|
||||
Value type: <stringlist>
|
||||
Definition: Value should contain
|
||||
- "qcom,pcie-ipq8064" for ipq8064
|
||||
- "qcom,pcie-apq8064" for apq8064
|
||||
- "qcom,pcie-apq8084" for apq8084
|
||||
|
||||
- reg:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: Register ranges as listed in the reg-names property
|
||||
|
||||
- reg-names:
|
||||
Usage: required
|
||||
Value type: <stringlist>
|
||||
Definition: Must include the following entries
|
||||
- "parf" Qualcomm specific registers
|
||||
- "dbi" Designware PCIe registers
|
||||
- "elbi" External local bus interface registers
|
||||
- "config" PCIe configuration space
|
||||
|
||||
- device_type:
|
||||
Usage: required
|
||||
Value type: <string>
|
||||
Definition: Should be "pci". As specified in designware-pcie.txt
|
||||
|
||||
- #address-cells:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: Should be 3. As specified in designware-pcie.txt
|
||||
|
||||
- #size-cells:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: Should be 2. As specified in designware-pcie.txt
|
||||
|
||||
- ranges:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: As specified in designware-pcie.txt
|
||||
|
||||
- interrupts:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: MSI interrupt
|
||||
|
||||
- interrupt-names:
|
||||
Usage: required
|
||||
Value type: <stringlist>
|
||||
Definition: Should contain "msi"
|
||||
|
||||
- #interrupt-cells:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: Should be 1. As specified in designware-pcie.txt
|
||||
|
||||
- interrupt-map-mask:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: As specified in designware-pcie.txt
|
||||
|
||||
- interrupt-map:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: As specified in designware-pcie.txt
|
||||
|
||||
- clocks:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: List of phandle and clock specifier pairs as listed
|
||||
in clock-names property
|
||||
|
||||
- clock-names:
|
||||
Usage: required
|
||||
Value type: <stringlist>
|
||||
Definition: Should contain the following entries
|
||||
- "iface" Configuration AHB clock
|
||||
|
||||
- clock-names:
|
||||
Usage: required for ipq/apq8064
|
||||
Value type: <stringlist>
|
||||
Definition: Should contain the following entries
|
||||
- "core" Clocks the pcie hw block
|
||||
- "phy" Clocks the pcie PHY block
|
||||
- clock-names:
|
||||
Usage: required for apq8084
|
||||
Value type: <stringlist>
|
||||
Definition: Should contain the following entries
|
||||
- "aux" Auxiliary (AUX) clock
|
||||
- "bus_master" Master AXI clock
|
||||
- "bus_slave" Slave AXI clock
|
||||
- resets:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: List of phandle and reset specifier pairs as listed
|
||||
in reset-names property
|
||||
|
||||
- reset-names:
|
||||
Usage: required for ipq/apq8064
|
||||
Value type: <stringlist>
|
||||
Definition: Should contain the following entries
|
||||
- "axi" AXI reset
|
||||
- "ahb" AHB reset
|
||||
- "por" POR reset
|
||||
- "pci" PCI reset
|
||||
- "phy" PHY reset
|
||||
|
||||
- reset-names:
|
||||
Usage: required for apq8084
|
||||
Value type: <stringlist>
|
||||
Definition: Should contain the following entries
|
||||
- "core" Core reset
|
||||
|
||||
- power-domains:
|
||||
Usage: required for apq8084
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: A phandle and power domain specifier pair to the
|
||||
power domain which is responsible for collapsing
|
||||
and restoring power to the peripheral
|
||||
|
||||
- vdda-supply:
|
||||
Usage: required
|
||||
Value type: <phandle>
|
||||
Definition: A phandle to the core analog power supply
|
||||
|
||||
- vdda_phy-supply:
|
||||
Usage: required for ipq/apq8064
|
||||
Value type: <phandle>
|
||||
Definition: A phandle to the analog power supply for PHY
|
||||
|
||||
- vdda_refclk-supply:
|
||||
Usage: required for ipq/apq8064
|
||||
Value type: <phandle>
|
||||
Definition: A phandle to the analog power supply for IC which generates
|
||||
reference clock
|
||||
|
||||
- phys:
|
||||
Usage: required for apq8084
|
||||
Value type: <phandle>
|
||||
Definition: List of phandle(s) as listed in phy-names property
|
||||
|
||||
- phy-names:
|
||||
Usage: required for apq8084
|
||||
Value type: <stringlist>
|
||||
Definition: Should contain "pciephy"
|
||||
|
||||
- <name>-gpios:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: List of phandle and gpio specifier pairs. Should contain
|
||||
- "perst-gpios" PCIe endpoint reset signal line
|
||||
- "wake-gpios" PCIe endpoint wake signal line
|
||||
|
||||
* Example for ipq/apq8064
|
||||
pcie@1b500000 {
|
||||
compatible = "qcom,pcie-apq8064", "qcom,pcie-ipq8064", "snps,dw-pcie";
|
||||
reg = <0x1b500000 0x1000
|
||||
0x1b502000 0x80
|
||||
0x1b600000 0x100
|
||||
0x0ff00000 0x100000>;
|
||||
reg-names = "dbi", "elbi", "parf", "config";
|
||||
device_type = "pci";
|
||||
linux,pci-domain = <0>;
|
||||
bus-range = <0x00 0xff>;
|
||||
num-lanes = <1>;
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* I/O */
|
||||
0x82000000 0 0 0x08000000 0 0x07e00000>; /* memory */
|
||||
interrupts = <GIC_SPI 238 IRQ_TYPE_NONE>;
|
||||
interrupt-names = "msi";
|
||||
#interrupt-cells = <1>;
|
||||
interrupt-map-mask = <0 0 0 0x7>;
|
||||
interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
|
||||
<0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
|
||||
<0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
|
||||
<0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
|
||||
clocks = <&gcc PCIE_A_CLK>,
|
||||
<&gcc PCIE_H_CLK>,
|
||||
<&gcc PCIE_PHY_CLK>;
|
||||
clock-names = "core", "iface", "phy";
|
||||
resets = <&gcc PCIE_ACLK_RESET>,
|
||||
<&gcc PCIE_HCLK_RESET>,
|
||||
<&gcc PCIE_POR_RESET>,
|
||||
<&gcc PCIE_PCI_RESET>,
|
||||
<&gcc PCIE_PHY_RESET>;
|
||||
reset-names = "axi", "ahb", "por", "pci", "phy";
|
||||
pinctrl-0 = <&pcie_pins_default>;
|
||||
pinctrl-names = "default";
|
||||
};
|
||||
|
||||
* Example for apq8084
|
||||
pcie0@fc520000 {
|
||||
compatible = "qcom,pcie-apq8084", "snps,dw-pcie";
|
||||
reg = <0xfc520000 0x2000>,
|
||||
<0xff000000 0x1000>,
|
||||
<0xff001000 0x1000>,
|
||||
<0xff002000 0x2000>;
|
||||
reg-names = "parf", "dbi", "elbi", "config";
|
||||
device_type = "pci";
|
||||
linux,pci-domain = <0>;
|
||||
bus-range = <0x00 0xff>;
|
||||
num-lanes = <1>;
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
ranges = <0x81000000 0 0 0xff200000 0 0x00100000 /* I/O */
|
||||
0x82000000 0 0x00300000 0xff300000 0 0x00d00000>; /* memory */
|
||||
interrupts = <GIC_SPI 243 IRQ_TYPE_NONE>;
|
||||
interrupt-names = "msi";
|
||||
#interrupt-cells = <1>;
|
||||
interrupt-map-mask = <0 0 0 0x7>;
|
||||
interrupt-map = <0 0 0 1 &intc 0 244 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
|
||||
<0 0 0 2 &intc 0 245 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
|
||||
<0 0 0 3 &intc 0 247 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
|
||||
<0 0 0 4 &intc 0 248 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
|
||||
clocks = <&gcc GCC_PCIE_0_CFG_AHB_CLK>,
|
||||
<&gcc GCC_PCIE_0_MSTR_AXI_CLK>,
|
||||
<&gcc GCC_PCIE_0_SLV_AXI_CLK>,
|
||||
<&gcc GCC_PCIE_0_AUX_CLK>;
|
||||
clock-names = "iface", "master_bus", "slave_bus", "aux";
|
||||
resets = <&gcc GCC_PCIE_0_BCR>;
|
||||
reset-names = "core";
|
||||
power-domains = <&gcc PCIE0_GDSC>;
|
||||
vdda-supply = <&pma8084_l3>;
|
||||
phys = <&pciephy0>;
|
||||
phy-names = "pciephy";
|
||||
perst-gpio = <&tlmm 70 GPIO_ACTIVE_LOW>;
|
||||
pinctrl-0 = <&pcie0_pins_default>;
|
||||
pinctrl-names = "default";
|
||||
};
|
@ -1,8 +1,16 @@
|
||||
* Renesas RCar PCIe interface
|
||||
|
||||
Required properties:
|
||||
- compatible: should contain one of the following
|
||||
"renesas,pcie-r8a7779", "renesas,pcie-r8a7790", "renesas,pcie-r8a7791"
|
||||
compatible: "renesas,pcie-r8a7779" for the R8A7779 SoC;
|
||||
"renesas,pcie-r8a7790" for the R8A7790 SoC;
|
||||
"renesas,pcie-r8a7791" for the R8A7791 SoC;
|
||||
"renesas,pcie-r8a7795" for the R8A7795 SoC;
|
||||
"renesas,pcie-rcar-gen2" for a generic R-Car Gen2 compatible device.
|
||||
|
||||
When compatible with the generic version, nodes must list the
|
||||
SoC-specific version corresponding to the platform first
|
||||
followed by the generic version.
|
||||
|
||||
- reg: base address and length of the pcie controller registers.
|
||||
- #address-cells: set to <3>
|
||||
- #size-cells: set to <2>
|
||||
@ -25,7 +33,7 @@ Example:
|
||||
SoC specific DT Entry:
|
||||
|
||||
pcie: pcie@fe000000 {
|
||||
compatible = "renesas,pcie-r8a7791";
|
||||
compatible = "renesas,pcie-r8a7791", "renesas,pcie-rcar-gen2";
|
||||
reg = <0 0xfe000000 0 0x80000>;
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
|
14
MAINTAINERS
14
MAINTAINERS
@ -8317,6 +8317,12 @@ S: Maintained
|
||||
F: Documentation/devicetree/bindings/pci/host-generic-pci.txt
|
||||
F: drivers/pci/host/pci-host-generic.c
|
||||
|
||||
PCI DRIVER FOR INTEL VOLUME MANAGEMENT DEVICE (VMD)
|
||||
M: Keith Busch <keith.busch@intel.com>
|
||||
L: linux-pci@vger.kernel.org
|
||||
S: Supported
|
||||
F: arch/x86/pci/vmd.c
|
||||
|
||||
PCIE DRIVER FOR ST SPEAR13XX
|
||||
M: Pratyush Anand <pratyush.anand@gmail.com>
|
||||
L: linux-pci@vger.kernel.org
|
||||
@ -8341,11 +8347,19 @@ F: drivers/pci/host/pci-xgene-msi.c
|
||||
|
||||
PCIE DRIVER FOR HISILICON
|
||||
M: Zhou Wang <wangzhou1@hisilicon.com>
|
||||
M: Gabriele Paoloni <gabriele.paoloni@huawei.com>
|
||||
L: linux-pci@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/pci/hisilicon-pcie.txt
|
||||
F: drivers/pci/host/pcie-hisi.c
|
||||
|
||||
PCIE DRIVER FOR QUALCOMM MSM
|
||||
M: Stanimir Varbanov <svarbanov@mm-sol.com>
|
||||
L: linux-pci@vger.kernel.org
|
||||
L: linux-arm-msm@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/pci/host/*qcom*
|
||||
|
||||
PCMCIA SUBSYSTEM
|
||||
P: Linux PCMCIA Team
|
||||
L: linux-pcmcia@lists.infradead.org
|
||||
|
@ -47,6 +47,18 @@
|
||||
bias-disable;
|
||||
};
|
||||
};
|
||||
|
||||
pcie_pins: pcie_pinmux {
|
||||
mux {
|
||||
pins = "gpio27";
|
||||
function = "gpio";
|
||||
};
|
||||
conf {
|
||||
pins = "gpio27";
|
||||
drive-strength = <12>;
|
||||
bias-disable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
rpm@108000 {
|
||||
@ -123,6 +135,10 @@
|
||||
lvs1 {
|
||||
bias-pull-down;
|
||||
};
|
||||
|
||||
lvs6 {
|
||||
bias-pull-down;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@ -231,6 +247,16 @@
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
pci@1b500000 {
|
||||
status = "ok";
|
||||
vdda-supply = <&pm8921_s3>;
|
||||
vdda_phy-supply = <&pm8921_lvs6>;
|
||||
vdda_refclk-supply = <&ext_3p3v>;
|
||||
pinctrl-0 = <&pcie_pins>;
|
||||
pinctrl-names = "default";
|
||||
perst-gpio = <&tlmm_pinmux 27 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
|
||||
qcom,ssbi@500000 {
|
||||
pmic@0 {
|
||||
gpio@150 {
|
||||
|
@ -785,5 +785,41 @@
|
||||
compatible = "qcom,tcsr-apq8064", "syscon";
|
||||
reg = <0x1a400000 0x100>;
|
||||
};
|
||||
|
||||
pcie: pci@1b500000 {
|
||||
compatible = "qcom,pcie-apq8064", "snps,dw-pcie";
|
||||
reg = <0x1b500000 0x1000
|
||||
0x1b502000 0x80
|
||||
0x1b600000 0x100
|
||||
0x0ff00000 0x100000>;
|
||||
reg-names = "dbi", "elbi", "parf", "config";
|
||||
device_type = "pci";
|
||||
linux,pci-domain = <0>;
|
||||
bus-range = <0x00 0xff>;
|
||||
num-lanes = <1>;
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* I/O */
|
||||
0x82000000 0 0 0x08000000 0 0x07e00000>; /* memory */
|
||||
interrupts = <GIC_SPI 238 IRQ_TYPE_NONE>;
|
||||
interrupt-names = "msi";
|
||||
#interrupt-cells = <1>;
|
||||
interrupt-map-mask = <0 0 0 0x7>;
|
||||
interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
|
||||
<0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
|
||||
<0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
|
||||
<0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
|
||||
clocks = <&gcc PCIE_A_CLK>,
|
||||
<&gcc PCIE_H_CLK>,
|
||||
<&gcc PCIE_PHY_REF_CLK>;
|
||||
clock-names = "core", "iface", "phy";
|
||||
resets = <&gcc PCIE_ACLK_RESET>,
|
||||
<&gcc PCIE_HCLK_RESET>,
|
||||
<&gcc PCIE_POR_RESET>,
|
||||
<&gcc PCIE_PCI_RESET>,
|
||||
<&gcc PCIE_PHY_RESET>;
|
||||
reset-names = "axi", "ahb", "por", "pci", "phy";
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -400,7 +400,7 @@ static void *eeh_rmv_device(void *data, void *userdata)
|
||||
* support EEH. So we just care about PCI devices for
|
||||
* simplicity here.
|
||||
*/
|
||||
if (!dev || (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE))
|
||||
if (!dev || (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
|
@ -187,9 +187,6 @@ struct pci_dev *of_create_pci_dev(struct device_node *node,
|
||||
|
||||
pci_device_add(dev, bus);
|
||||
|
||||
/* Setup MSI caps & disable MSI/MSI-X interrupts */
|
||||
pci_msi_setup_pci_dev(dev);
|
||||
|
||||
return dev;
|
||||
}
|
||||
EXPORT_SYMBOL(of_create_pci_dev);
|
||||
|
@ -2699,6 +2699,19 @@ config PMC_ATOM
|
||||
def_bool y
|
||||
depends on PCI
|
||||
|
||||
config VMD
|
||||
depends on PCI_MSI
|
||||
tristate "Volume Management Device Driver"
|
||||
default N
|
||||
---help---
|
||||
Adds support for the Intel Volume Management Device (VMD). VMD is a
|
||||
secondary PCI host bridge that allows PCI Express root ports,
|
||||
and devices attached to them, to be removed from the default
|
||||
PCI domain and placed within the VMD domain. This provides
|
||||
more bus resources than are otherwise possible with a
|
||||
single domain. If you know your system provides one of these and
|
||||
has devices attached to it, say Y; if you are not sure, say N.
|
||||
|
||||
source "net/Kconfig"
|
||||
|
||||
source "drivers/Kconfig"
|
||||
|
@ -10,6 +10,16 @@ struct dev_archdata {
|
||||
#endif
|
||||
};
|
||||
|
||||
#if defined(CONFIG_X86_DEV_DMA_OPS) && defined(CONFIG_PCI_DOMAINS)
|
||||
struct dma_domain {
|
||||
struct list_head node;
|
||||
struct dma_map_ops *dma_ops;
|
||||
int domain_nr;
|
||||
};
|
||||
void add_dma_domain(struct dma_domain *domain);
|
||||
void del_dma_domain(struct dma_domain *domain);
|
||||
#endif
|
||||
|
||||
struct pdev_archdata {
|
||||
};
|
||||
|
||||
|
@ -129,6 +129,11 @@ struct irq_alloc_info {
|
||||
unsigned long uv_offset;
|
||||
char *uv_name;
|
||||
};
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_VMD)
|
||||
struct {
|
||||
struct msi_desc *desc;
|
||||
};
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
@ -151,11 +151,11 @@ extern struct list_head pci_mmcfg_list;
|
||||
#define PCI_MMCFG_BUS_OFFSET(bus) ((bus) << 20)
|
||||
|
||||
/*
|
||||
* AMD Fam10h CPUs are buggy, and cannot access MMIO config space
|
||||
* on their northbrige except through the * %eax register. As such, you MUST
|
||||
* NOT use normal IOMEM accesses, you need to only use the magic mmio-config
|
||||
* accessor functions.
|
||||
* In fact just use pci_config_*, nothing else please.
|
||||
* On AMD Fam10h CPUs, all PCI MMIO configuration space accesses must use
|
||||
* %eax. No other source or target registers may be used. The following
|
||||
* mmio_config_* accessors enforce this. See "BIOS and Kernel Developer's
|
||||
* Guide (BKDG) For AMD Family 10h Processors", rev. 3.48, sec 2.11.1,
|
||||
* "MMIO Configuration Coding Requirements".
|
||||
*/
|
||||
static inline unsigned char mmio_config_readb(void __iomem *pos)
|
||||
{
|
||||
|
@ -23,6 +23,8 @@ obj-y += bus_numa.o
|
||||
obj-$(CONFIG_AMD_NB) += amd_bus.o
|
||||
obj-$(CONFIG_PCI_CNB20LE_QUIRK) += broadcom_bus.o
|
||||
|
||||
obj-$(CONFIG_VMD) += vmd.o
|
||||
|
||||
ifeq ($(CONFIG_PCI_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
@ -641,6 +641,43 @@ unsigned int pcibios_assign_all_busses(void)
|
||||
return (pci_probe & PCI_ASSIGN_ALL_BUSSES) ? 1 : 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_X86_DEV_DMA_OPS) && defined(CONFIG_PCI_DOMAINS)
|
||||
static LIST_HEAD(dma_domain_list);
|
||||
static DEFINE_SPINLOCK(dma_domain_list_lock);
|
||||
|
||||
void add_dma_domain(struct dma_domain *domain)
|
||||
{
|
||||
spin_lock(&dma_domain_list_lock);
|
||||
list_add(&domain->node, &dma_domain_list);
|
||||
spin_unlock(&dma_domain_list_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(add_dma_domain);
|
||||
|
||||
void del_dma_domain(struct dma_domain *domain)
|
||||
{
|
||||
spin_lock(&dma_domain_list_lock);
|
||||
list_del(&domain->node);
|
||||
spin_unlock(&dma_domain_list_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(del_dma_domain);
|
||||
|
||||
static void set_dma_domain_ops(struct pci_dev *pdev)
|
||||
{
|
||||
struct dma_domain *domain;
|
||||
|
||||
spin_lock(&dma_domain_list_lock);
|
||||
list_for_each_entry(domain, &dma_domain_list, node) {
|
||||
if (pci_domain_nr(pdev->bus) == domain->domain_nr) {
|
||||
pdev->dev.archdata.dma_ops = domain->dma_ops;
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock(&dma_domain_list_lock);
|
||||
}
|
||||
#else
|
||||
static void set_dma_domain_ops(struct pci_dev *pdev) {}
|
||||
#endif
|
||||
|
||||
int pcibios_add_device(struct pci_dev *dev)
|
||||
{
|
||||
struct setup_data *data;
|
||||
@ -670,6 +707,7 @@ int pcibios_add_device(struct pci_dev *dev)
|
||||
pa_data = data->next;
|
||||
iounmap(data);
|
||||
}
|
||||
set_dma_domain_ops(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -180,6 +180,7 @@ static int pci_bios_read(unsigned int seg, unsigned int bus,
|
||||
unsigned long result = 0;
|
||||
unsigned long flags;
|
||||
unsigned long bx = (bus << 8) | devfn;
|
||||
u16 number = 0, mask = 0;
|
||||
|
||||
WARN_ON(seg);
|
||||
if (!value || (bus > 255) || (devfn > 255) || (reg > 255))
|
||||
@ -189,52 +190,34 @@ static int pci_bios_read(unsigned int seg, unsigned int bus,
|
||||
|
||||
switch (len) {
|
||||
case 1:
|
||||
__asm__("lcall *(%%esi); cld\n\t"
|
||||
"jc 1f\n\t"
|
||||
"xor %%ah, %%ah\n"
|
||||
"1:"
|
||||
: "=c" (*value),
|
||||
"=a" (result)
|
||||
: "1" (PCIBIOS_READ_CONFIG_BYTE),
|
||||
"b" (bx),
|
||||
"D" ((long)reg),
|
||||
"S" (&pci_indirect));
|
||||
/*
|
||||
* Zero-extend the result beyond 8 bits, do not trust the
|
||||
* BIOS having done it:
|
||||
*/
|
||||
*value &= 0xff;
|
||||
number = PCIBIOS_READ_CONFIG_BYTE;
|
||||
mask = 0xff;
|
||||
break;
|
||||
case 2:
|
||||
number = PCIBIOS_READ_CONFIG_WORD;
|
||||
mask = 0xffff;
|
||||
break;
|
||||
case 4:
|
||||
number = PCIBIOS_READ_CONFIG_DWORD;
|
||||
break;
|
||||
}
|
||||
|
||||
__asm__("lcall *(%%esi); cld\n\t"
|
||||
"jc 1f\n\t"
|
||||
"xor %%ah, %%ah\n"
|
||||
"1:"
|
||||
: "=c" (*value),
|
||||
"=a" (result)
|
||||
: "1" (PCIBIOS_READ_CONFIG_WORD),
|
||||
: "1" (number),
|
||||
"b" (bx),
|
||||
"D" ((long)reg),
|
||||
"S" (&pci_indirect));
|
||||
/*
|
||||
* Zero-extend the result beyond 16 bits, do not trust the
|
||||
* Zero-extend the result beyond 8 or 16 bits, do not trust the
|
||||
* BIOS having done it:
|
||||
*/
|
||||
*value &= 0xffff;
|
||||
break;
|
||||
case 4:
|
||||
__asm__("lcall *(%%esi); cld\n\t"
|
||||
"jc 1f\n\t"
|
||||
"xor %%ah, %%ah\n"
|
||||
"1:"
|
||||
: "=c" (*value),
|
||||
"=a" (result)
|
||||
: "1" (PCIBIOS_READ_CONFIG_DWORD),
|
||||
"b" (bx),
|
||||
"D" ((long)reg),
|
||||
"S" (&pci_indirect));
|
||||
break;
|
||||
}
|
||||
if (mask)
|
||||
*value &= mask;
|
||||
|
||||
raw_spin_unlock_irqrestore(&pci_config_lock, flags);
|
||||
|
||||
@ -247,6 +230,7 @@ static int pci_bios_write(unsigned int seg, unsigned int bus,
|
||||
unsigned long result = 0;
|
||||
unsigned long flags;
|
||||
unsigned long bx = (bus << 8) | devfn;
|
||||
u16 number = 0;
|
||||
|
||||
WARN_ON(seg);
|
||||
if ((bus > 255) || (devfn > 255) || (reg > 255))
|
||||
@ -256,42 +240,26 @@ static int pci_bios_write(unsigned int seg, unsigned int bus,
|
||||
|
||||
switch (len) {
|
||||
case 1:
|
||||
__asm__("lcall *(%%esi); cld\n\t"
|
||||
"jc 1f\n\t"
|
||||
"xor %%ah, %%ah\n"
|
||||
"1:"
|
||||
: "=a" (result)
|
||||
: "0" (PCIBIOS_WRITE_CONFIG_BYTE),
|
||||
"c" (value),
|
||||
"b" (bx),
|
||||
"D" ((long)reg),
|
||||
"S" (&pci_indirect));
|
||||
number = PCIBIOS_WRITE_CONFIG_BYTE;
|
||||
break;
|
||||
case 2:
|
||||
__asm__("lcall *(%%esi); cld\n\t"
|
||||
"jc 1f\n\t"
|
||||
"xor %%ah, %%ah\n"
|
||||
"1:"
|
||||
: "=a" (result)
|
||||
: "0" (PCIBIOS_WRITE_CONFIG_WORD),
|
||||
"c" (value),
|
||||
"b" (bx),
|
||||
"D" ((long)reg),
|
||||
"S" (&pci_indirect));
|
||||
number = PCIBIOS_WRITE_CONFIG_WORD;
|
||||
break;
|
||||
case 4:
|
||||
number = PCIBIOS_WRITE_CONFIG_DWORD;
|
||||
break;
|
||||
}
|
||||
|
||||
__asm__("lcall *(%%esi); cld\n\t"
|
||||
"jc 1f\n\t"
|
||||
"xor %%ah, %%ah\n"
|
||||
"1:"
|
||||
: "=a" (result)
|
||||
: "0" (PCIBIOS_WRITE_CONFIG_DWORD),
|
||||
: "0" (number),
|
||||
"c" (value),
|
||||
"b" (bx),
|
||||
"D" ((long)reg),
|
||||
"S" (&pci_indirect));
|
||||
break;
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&pci_config_lock, flags);
|
||||
|
||||
|
723
arch/x86/pci/vmd.c
Normal file
723
arch/x86/pci/vmd.c
Normal file
@ -0,0 +1,723 @@
|
||||
/*
|
||||
* Volume Management Device driver
|
||||
* Copyright (c) 2015, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/rculist.h>
|
||||
#include <linux/rcupdate.h>
|
||||
|
||||
#include <asm/irqdomain.h>
|
||||
#include <asm/device.h>
|
||||
#include <asm/msi.h>
|
||||
#include <asm/msidef.h>
|
||||
|
||||
#define VMD_CFGBAR 0
|
||||
#define VMD_MEMBAR1 2
|
||||
#define VMD_MEMBAR2 4
|
||||
|
||||
/*
|
||||
* Lock for manipulating VMD IRQ lists.
|
||||
*/
|
||||
static DEFINE_RAW_SPINLOCK(list_lock);
|
||||
|
||||
/**
|
||||
* struct vmd_irq - private data to map driver IRQ to the VMD shared vector
|
||||
* @node: list item for parent traversal.
|
||||
* @rcu: RCU callback item for freeing.
|
||||
* @irq: back pointer to parent.
|
||||
* @virq: the virtual IRQ value provided to the requesting driver.
|
||||
*
|
||||
* Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to
|
||||
* a VMD IRQ using this structure.
|
||||
*/
|
||||
struct vmd_irq {
|
||||
struct list_head node;
|
||||
struct rcu_head rcu;
|
||||
struct vmd_irq_list *irq;
|
||||
unsigned int virq;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector
|
||||
* @irq_list: the list of irq's the VMD one demuxes to.
|
||||
* @vmd_vector: the h/w IRQ assigned to the VMD.
|
||||
* @index: index into the VMD MSI-X table; used for message routing.
|
||||
* @count: number of child IRQs assigned to this vector; used to track
|
||||
* sharing.
|
||||
*/
|
||||
struct vmd_irq_list {
|
||||
struct list_head irq_list;
|
||||
struct vmd_dev *vmd;
|
||||
unsigned int vmd_vector;
|
||||
unsigned int index;
|
||||
unsigned int count;
|
||||
};
|
||||
|
||||
struct vmd_dev {
|
||||
struct pci_dev *dev;
|
||||
|
||||
spinlock_t cfg_lock;
|
||||
char __iomem *cfgbar;
|
||||
|
||||
int msix_count;
|
||||
struct msix_entry *msix_entries;
|
||||
struct vmd_irq_list *irqs;
|
||||
|
||||
struct pci_sysdata sysdata;
|
||||
struct resource resources[3];
|
||||
struct irq_domain *irq_domain;
|
||||
struct pci_bus *bus;
|
||||
|
||||
#ifdef CONFIG_X86_DEV_DMA_OPS
|
||||
struct dma_map_ops dma_ops;
|
||||
struct dma_domain dma_domain;
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus)
|
||||
{
|
||||
return container_of(bus->sysdata, struct vmd_dev, sysdata);
|
||||
}
|
||||
|
||||
/*
|
||||
* Drivers managing a device in a VMD domain allocate their own IRQs as before,
|
||||
* but the MSI entry for the hardware it's driving will be programmed with a
|
||||
* destination ID for the VMD MSI-X table. The VMD muxes interrupts in its
|
||||
* domain into one of its own, and the VMD driver de-muxes these for the
|
||||
* handlers sharing that VMD IRQ. The vmd irq_domain provides the operations
|
||||
* and irq_chip to set this up.
|
||||
*/
|
||||
static void vmd_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
{
|
||||
struct vmd_irq *vmdirq = data->chip_data;
|
||||
struct vmd_irq_list *irq = vmdirq->irq;
|
||||
|
||||
msg->address_hi = MSI_ADDR_BASE_HI;
|
||||
msg->address_lo = MSI_ADDR_BASE_LO | MSI_ADDR_DEST_ID(irq->index);
|
||||
msg->data = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* We rely on MSI_FLAG_USE_DEF_CHIP_OPS to set the IRQ mask/unmask ops.
|
||||
*/
|
||||
static void vmd_irq_enable(struct irq_data *data)
|
||||
{
|
||||
struct vmd_irq *vmdirq = data->chip_data;
|
||||
|
||||
raw_spin_lock(&list_lock);
|
||||
list_add_tail_rcu(&vmdirq->node, &vmdirq->irq->irq_list);
|
||||
raw_spin_unlock(&list_lock);
|
||||
|
||||
data->chip->irq_unmask(data);
|
||||
}
|
||||
|
||||
static void vmd_irq_disable(struct irq_data *data)
|
||||
{
|
||||
struct vmd_irq *vmdirq = data->chip_data;
|
||||
|
||||
data->chip->irq_mask(data);
|
||||
|
||||
raw_spin_lock(&list_lock);
|
||||
list_del_rcu(&vmdirq->node);
|
||||
raw_spin_unlock(&list_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX: Stubbed until we develop acceptable way to not create conflicts with
|
||||
* other devices sharing the same vector.
|
||||
*/
|
||||
static int vmd_irq_set_affinity(struct irq_data *data,
|
||||
const struct cpumask *dest, bool force)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct irq_chip vmd_msi_controller = {
|
||||
.name = "VMD-MSI",
|
||||
.irq_enable = vmd_irq_enable,
|
||||
.irq_disable = vmd_irq_disable,
|
||||
.irq_compose_msi_msg = vmd_compose_msi_msg,
|
||||
.irq_set_affinity = vmd_irq_set_affinity,
|
||||
};
|
||||
|
||||
static irq_hw_number_t vmd_get_hwirq(struct msi_domain_info *info,
|
||||
msi_alloc_info_t *arg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX: We can be even smarter selecting the best IRQ once we solve the
|
||||
* affinity problem.
|
||||
*/
|
||||
static struct vmd_irq_list *vmd_next_irq(struct vmd_dev *vmd)
|
||||
{
|
||||
int i, best = 0;
|
||||
|
||||
raw_spin_lock(&list_lock);
|
||||
for (i = 1; i < vmd->msix_count; i++)
|
||||
if (vmd->irqs[i].count < vmd->irqs[best].count)
|
||||
best = i;
|
||||
vmd->irqs[best].count++;
|
||||
raw_spin_unlock(&list_lock);
|
||||
|
||||
return &vmd->irqs[best];
|
||||
}
|
||||
|
||||
static int vmd_msi_init(struct irq_domain *domain, struct msi_domain_info *info,
|
||||
unsigned int virq, irq_hw_number_t hwirq,
|
||||
msi_alloc_info_t *arg)
|
||||
{
|
||||
struct vmd_dev *vmd = vmd_from_bus(msi_desc_to_pci_dev(arg->desc)->bus);
|
||||
struct vmd_irq *vmdirq = kzalloc(sizeof(*vmdirq), GFP_KERNEL);
|
||||
|
||||
if (!vmdirq)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&vmdirq->node);
|
||||
vmdirq->irq = vmd_next_irq(vmd);
|
||||
vmdirq->virq = virq;
|
||||
|
||||
irq_domain_set_info(domain, virq, vmdirq->irq->vmd_vector, info->chip,
|
||||
vmdirq, handle_simple_irq, vmd, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vmd_msi_free(struct irq_domain *domain,
|
||||
struct msi_domain_info *info, unsigned int virq)
|
||||
{
|
||||
struct vmd_irq *vmdirq = irq_get_chip_data(virq);
|
||||
|
||||
/* XXX: Potential optimization to rebalance */
|
||||
raw_spin_lock(&list_lock);
|
||||
vmdirq->irq->count--;
|
||||
raw_spin_unlock(&list_lock);
|
||||
|
||||
kfree_rcu(vmdirq, rcu);
|
||||
}
|
||||
|
||||
static int vmd_msi_prepare(struct irq_domain *domain, struct device *dev,
|
||||
int nvec, msi_alloc_info_t *arg)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
struct vmd_dev *vmd = vmd_from_bus(pdev->bus);
|
||||
|
||||
if (nvec > vmd->msix_count)
|
||||
return vmd->msix_count;
|
||||
|
||||
memset(arg, 0, sizeof(*arg));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vmd_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
|
||||
{
|
||||
arg->desc = desc;
|
||||
}
|
||||
|
||||
static struct msi_domain_ops vmd_msi_domain_ops = {
|
||||
.get_hwirq = vmd_get_hwirq,
|
||||
.msi_init = vmd_msi_init,
|
||||
.msi_free = vmd_msi_free,
|
||||
.msi_prepare = vmd_msi_prepare,
|
||||
.set_desc = vmd_set_desc,
|
||||
};
|
||||
|
||||
static struct msi_domain_info vmd_msi_domain_info = {
|
||||
.flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_PCI_MSIX,
|
||||
.ops = &vmd_msi_domain_ops,
|
||||
.chip = &vmd_msi_controller,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_X86_DEV_DMA_OPS
|
||||
/*
|
||||
* VMD replaces the requester ID with its own. DMA mappings for devices in a
|
||||
* VMD domain need to be mapped for the VMD, not the device requiring
|
||||
* the mapping.
|
||||
*/
|
||||
static struct device *to_vmd_dev(struct device *dev)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
struct vmd_dev *vmd = vmd_from_bus(pdev->bus);
|
||||
|
||||
return &vmd->dev->dev;
|
||||
}
|
||||
|
||||
static struct dma_map_ops *vmd_dma_ops(struct device *dev)
|
||||
{
|
||||
return to_vmd_dev(dev)->archdata.dma_ops;
|
||||
}
|
||||
|
||||
static void *vmd_alloc(struct device *dev, size_t size, dma_addr_t *addr,
|
||||
gfp_t flag, struct dma_attrs *attrs)
|
||||
{
|
||||
return vmd_dma_ops(dev)->alloc(to_vmd_dev(dev), size, addr, flag,
|
||||
attrs);
|
||||
}
|
||||
|
||||
static void vmd_free(struct device *dev, size_t size, void *vaddr,
|
||||
dma_addr_t addr, struct dma_attrs *attrs)
|
||||
{
|
||||
return vmd_dma_ops(dev)->free(to_vmd_dev(dev), size, vaddr, addr,
|
||||
attrs);
|
||||
}
|
||||
|
||||
static int vmd_mmap(struct device *dev, struct vm_area_struct *vma,
|
||||
void *cpu_addr, dma_addr_t addr, size_t size,
|
||||
struct dma_attrs *attrs)
|
||||
{
|
||||
return vmd_dma_ops(dev)->mmap(to_vmd_dev(dev), vma, cpu_addr, addr,
|
||||
size, attrs);
|
||||
}
|
||||
|
||||
static int vmd_get_sgtable(struct device *dev, struct sg_table *sgt,
|
||||
void *cpu_addr, dma_addr_t addr, size_t size,
|
||||
struct dma_attrs *attrs)
|
||||
{
|
||||
return vmd_dma_ops(dev)->get_sgtable(to_vmd_dev(dev), sgt, cpu_addr,
|
||||
addr, size, attrs);
|
||||
}
|
||||
|
||||
static dma_addr_t vmd_map_page(struct device *dev, struct page *page,
|
||||
unsigned long offset, size_t size,
|
||||
enum dma_data_direction dir,
|
||||
struct dma_attrs *attrs)
|
||||
{
|
||||
return vmd_dma_ops(dev)->map_page(to_vmd_dev(dev), page, offset, size,
|
||||
dir, attrs);
|
||||
}
|
||||
|
||||
static void vmd_unmap_page(struct device *dev, dma_addr_t addr, size_t size,
|
||||
enum dma_data_direction dir, struct dma_attrs *attrs)
|
||||
{
|
||||
vmd_dma_ops(dev)->unmap_page(to_vmd_dev(dev), addr, size, dir, attrs);
|
||||
}
|
||||
|
||||
static int vmd_map_sg(struct device *dev, struct scatterlist *sg, int nents,
|
||||
enum dma_data_direction dir, struct dma_attrs *attrs)
|
||||
{
|
||||
return vmd_dma_ops(dev)->map_sg(to_vmd_dev(dev), sg, nents, dir, attrs);
|
||||
}
|
||||
|
||||
static void vmd_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
|
||||
enum dma_data_direction dir, struct dma_attrs *attrs)
|
||||
{
|
||||
vmd_dma_ops(dev)->unmap_sg(to_vmd_dev(dev), sg, nents, dir, attrs);
|
||||
}
|
||||
|
||||
static void vmd_sync_single_for_cpu(struct device *dev, dma_addr_t addr,
|
||||
size_t size, enum dma_data_direction dir)
|
||||
{
|
||||
vmd_dma_ops(dev)->sync_single_for_cpu(to_vmd_dev(dev), addr, size, dir);
|
||||
}
|
||||
|
||||
static void vmd_sync_single_for_device(struct device *dev, dma_addr_t addr,
|
||||
size_t size, enum dma_data_direction dir)
|
||||
{
|
||||
vmd_dma_ops(dev)->sync_single_for_device(to_vmd_dev(dev), addr, size,
|
||||
dir);
|
||||
}
|
||||
|
||||
static void vmd_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
|
||||
int nents, enum dma_data_direction dir)
|
||||
{
|
||||
vmd_dma_ops(dev)->sync_sg_for_cpu(to_vmd_dev(dev), sg, nents, dir);
|
||||
}
|
||||
|
||||
static void vmd_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
|
||||
int nents, enum dma_data_direction dir)
|
||||
{
|
||||
vmd_dma_ops(dev)->sync_sg_for_device(to_vmd_dev(dev), sg, nents, dir);
|
||||
}
|
||||
|
||||
static int vmd_mapping_error(struct device *dev, dma_addr_t addr)
|
||||
{
|
||||
return vmd_dma_ops(dev)->mapping_error(to_vmd_dev(dev), addr);
|
||||
}
|
||||
|
||||
static int vmd_dma_supported(struct device *dev, u64 mask)
|
||||
{
|
||||
return vmd_dma_ops(dev)->dma_supported(to_vmd_dev(dev), mask);
|
||||
}
|
||||
|
||||
#ifdef ARCH_HAS_DMA_GET_REQUIRED_MASK
|
||||
static u64 vmd_get_required_mask(struct device *dev)
|
||||
{
|
||||
return vmd_dma_ops(dev)->get_required_mask(to_vmd_dev(dev));
|
||||
}
|
||||
#endif
|
||||
|
||||
static void vmd_teardown_dma_ops(struct vmd_dev *vmd)
|
||||
{
|
||||
struct dma_domain *domain = &vmd->dma_domain;
|
||||
|
||||
if (vmd->dev->dev.archdata.dma_ops)
|
||||
del_dma_domain(domain);
|
||||
}
|
||||
|
||||
#define ASSIGN_VMD_DMA_OPS(source, dest, fn) \
|
||||
do { \
|
||||
if (source->fn) \
|
||||
dest->fn = vmd_##fn; \
|
||||
} while (0)
|
||||
|
||||
static void vmd_setup_dma_ops(struct vmd_dev *vmd)
|
||||
{
|
||||
const struct dma_map_ops *source = vmd->dev->dev.archdata.dma_ops;
|
||||
struct dma_map_ops *dest = &vmd->dma_ops;
|
||||
struct dma_domain *domain = &vmd->dma_domain;
|
||||
|
||||
domain->domain_nr = vmd->sysdata.domain;
|
||||
domain->dma_ops = dest;
|
||||
|
||||
if (!source)
|
||||
return;
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, alloc);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, free);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, mmap);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, get_sgtable);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, map_page);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, unmap_page);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, map_sg);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, unmap_sg);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, sync_single_for_cpu);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, sync_single_for_device);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, sync_sg_for_cpu);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, sync_sg_for_device);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, mapping_error);
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, dma_supported);
|
||||
#ifdef ARCH_HAS_DMA_GET_REQUIRED_MASK
|
||||
ASSIGN_VMD_DMA_OPS(source, dest, get_required_mask);
|
||||
#endif
|
||||
add_dma_domain(domain);
|
||||
}
|
||||
#undef ASSIGN_VMD_DMA_OPS
|
||||
#else
|
||||
static void vmd_teardown_dma_ops(struct vmd_dev *vmd) {}
|
||||
static void vmd_setup_dma_ops(struct vmd_dev *vmd) {}
|
||||
#endif
|
||||
|
||||
static char __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus,
|
||||
unsigned int devfn, int reg, int len)
|
||||
{
|
||||
char __iomem *addr = vmd->cfgbar +
|
||||
(bus->number << 20) + (devfn << 12) + reg;
|
||||
|
||||
if ((addr - vmd->cfgbar) + len >=
|
||||
resource_size(&vmd->dev->resource[VMD_CFGBAR]))
|
||||
return NULL;
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
/*
|
||||
* CPU may deadlock if config space is not serialized on some versions of this
|
||||
* hardware, so all config space access is done under a spinlock.
|
||||
*/
|
||||
static int vmd_pci_read(struct pci_bus *bus, unsigned int devfn, int reg,
|
||||
int len, u32 *value)
|
||||
{
|
||||
struct vmd_dev *vmd = vmd_from_bus(bus);
|
||||
char __iomem *addr = vmd_cfg_addr(vmd, bus, devfn, reg, len);
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
if (!addr)
|
||||
return -EFAULT;
|
||||
|
||||
spin_lock_irqsave(&vmd->cfg_lock, flags);
|
||||
switch (len) {
|
||||
case 1:
|
||||
*value = readb(addr);
|
||||
break;
|
||||
case 2:
|
||||
*value = readw(addr);
|
||||
break;
|
||||
case 4:
|
||||
*value = readl(addr);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&vmd->cfg_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* VMD h/w converts non-posted config writes to posted memory writes. The
|
||||
* read-back in this function forces the completion so it returns only after
|
||||
* the config space was written, as expected.
|
||||
*/
|
||||
static int vmd_pci_write(struct pci_bus *bus, unsigned int devfn, int reg,
|
||||
int len, u32 value)
|
||||
{
|
||||
struct vmd_dev *vmd = vmd_from_bus(bus);
|
||||
char __iomem *addr = vmd_cfg_addr(vmd, bus, devfn, reg, len);
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
if (!addr)
|
||||
return -EFAULT;
|
||||
|
||||
spin_lock_irqsave(&vmd->cfg_lock, flags);
|
||||
switch (len) {
|
||||
case 1:
|
||||
writeb(value, addr);
|
||||
readb(addr);
|
||||
break;
|
||||
case 2:
|
||||
writew(value, addr);
|
||||
readw(addr);
|
||||
break;
|
||||
case 4:
|
||||
writel(value, addr);
|
||||
readl(addr);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
spin_unlock_irqrestore(&vmd->cfg_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct pci_ops vmd_ops = {
|
||||
.read = vmd_pci_read,
|
||||
.write = vmd_pci_write,
|
||||
};
|
||||
|
||||
/*
|
||||
* VMD domains start at 0x1000 to not clash with ACPI _SEG domains.
|
||||
*/
|
||||
static int vmd_find_free_domain(void)
|
||||
{
|
||||
int domain = 0xffff;
|
||||
struct pci_bus *bus = NULL;
|
||||
|
||||
while ((bus = pci_find_next_bus(bus)) != NULL)
|
||||
domain = max_t(int, domain, pci_domain_nr(bus));
|
||||
return domain + 1;
|
||||
}
|
||||
|
||||
static int vmd_enable_domain(struct vmd_dev *vmd)
|
||||
{
|
||||
struct pci_sysdata *sd = &vmd->sysdata;
|
||||
struct resource *res;
|
||||
u32 upper_bits;
|
||||
unsigned long flags;
|
||||
LIST_HEAD(resources);
|
||||
|
||||
res = &vmd->dev->resource[VMD_CFGBAR];
|
||||
vmd->resources[0] = (struct resource) {
|
||||
.name = "VMD CFGBAR",
|
||||
.start = res->start,
|
||||
.end = (resource_size(res) >> 20) - 1,
|
||||
.flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED,
|
||||
};
|
||||
|
||||
res = &vmd->dev->resource[VMD_MEMBAR1];
|
||||
upper_bits = upper_32_bits(res->end);
|
||||
flags = res->flags & ~IORESOURCE_SIZEALIGN;
|
||||
if (!upper_bits)
|
||||
flags &= ~IORESOURCE_MEM_64;
|
||||
vmd->resources[1] = (struct resource) {
|
||||
.name = "VMD MEMBAR1",
|
||||
.start = res->start,
|
||||
.end = res->end,
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
res = &vmd->dev->resource[VMD_MEMBAR2];
|
||||
upper_bits = upper_32_bits(res->end);
|
||||
flags = res->flags & ~IORESOURCE_SIZEALIGN;
|
||||
if (!upper_bits)
|
||||
flags &= ~IORESOURCE_MEM_64;
|
||||
vmd->resources[2] = (struct resource) {
|
||||
.name = "VMD MEMBAR2",
|
||||
.start = res->start + 0x2000,
|
||||
.end = res->end,
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
sd->domain = vmd_find_free_domain();
|
||||
if (sd->domain < 0)
|
||||
return sd->domain;
|
||||
|
||||
sd->node = pcibus_to_node(vmd->dev->bus);
|
||||
|
||||
vmd->irq_domain = pci_msi_create_irq_domain(NULL, &vmd_msi_domain_info,
|
||||
NULL);
|
||||
if (!vmd->irq_domain)
|
||||
return -ENODEV;
|
||||
|
||||
pci_add_resource(&resources, &vmd->resources[0]);
|
||||
pci_add_resource(&resources, &vmd->resources[1]);
|
||||
pci_add_resource(&resources, &vmd->resources[2]);
|
||||
vmd->bus = pci_create_root_bus(&vmd->dev->dev, 0, &vmd_ops, sd,
|
||||
&resources);
|
||||
if (!vmd->bus) {
|
||||
pci_free_resource_list(&resources);
|
||||
irq_domain_remove(vmd->irq_domain);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
vmd_setup_dma_ops(vmd);
|
||||
dev_set_msi_domain(&vmd->bus->dev, vmd->irq_domain);
|
||||
pci_rescan_bus(vmd->bus);
|
||||
|
||||
WARN(sysfs_create_link(&vmd->dev->dev.kobj, &vmd->bus->dev.kobj,
|
||||
"domain"), "Can't create symlink to domain\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t vmd_irq(int irq, void *data)
|
||||
{
|
||||
struct vmd_irq_list *irqs = data;
|
||||
struct vmd_irq *vmdirq;
|
||||
|
||||
rcu_read_lock();
|
||||
list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node)
|
||||
generic_handle_irq(vmdirq->virq);
|
||||
rcu_read_unlock();
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
||||
{
|
||||
struct vmd_dev *vmd;
|
||||
int i, err;
|
||||
|
||||
if (resource_size(&dev->resource[VMD_CFGBAR]) < (1 << 20))
|
||||
return -ENOMEM;
|
||||
|
||||
vmd = devm_kzalloc(&dev->dev, sizeof(*vmd), GFP_KERNEL);
|
||||
if (!vmd)
|
||||
return -ENOMEM;
|
||||
|
||||
vmd->dev = dev;
|
||||
err = pcim_enable_device(dev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
vmd->cfgbar = pcim_iomap(dev, VMD_CFGBAR, 0);
|
||||
if (!vmd->cfgbar)
|
||||
return -ENOMEM;
|
||||
|
||||
pci_set_master(dev);
|
||||
if (dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(64)) &&
|
||||
dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32)))
|
||||
return -ENODEV;
|
||||
|
||||
vmd->msix_count = pci_msix_vec_count(dev);
|
||||
if (vmd->msix_count < 0)
|
||||
return -ENODEV;
|
||||
|
||||
vmd->irqs = devm_kcalloc(&dev->dev, vmd->msix_count, sizeof(*vmd->irqs),
|
||||
GFP_KERNEL);
|
||||
if (!vmd->irqs)
|
||||
return -ENOMEM;
|
||||
|
||||
vmd->msix_entries = devm_kcalloc(&dev->dev, vmd->msix_count,
|
||||
sizeof(*vmd->msix_entries),
|
||||
GFP_KERNEL);
|
||||
if (!vmd->msix_entries)
|
||||
return -ENOMEM;
|
||||
for (i = 0; i < vmd->msix_count; i++)
|
||||
vmd->msix_entries[i].entry = i;
|
||||
|
||||
vmd->msix_count = pci_enable_msix_range(vmd->dev, vmd->msix_entries, 1,
|
||||
vmd->msix_count);
|
||||
if (vmd->msix_count < 0)
|
||||
return vmd->msix_count;
|
||||
|
||||
for (i = 0; i < vmd->msix_count; i++) {
|
||||
INIT_LIST_HEAD(&vmd->irqs[i].irq_list);
|
||||
vmd->irqs[i].vmd_vector = vmd->msix_entries[i].vector;
|
||||
vmd->irqs[i].index = i;
|
||||
|
||||
err = devm_request_irq(&dev->dev, vmd->irqs[i].vmd_vector,
|
||||
vmd_irq, 0, "vmd", &vmd->irqs[i]);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
spin_lock_init(&vmd->cfg_lock);
|
||||
pci_set_drvdata(dev, vmd);
|
||||
err = vmd_enable_domain(vmd);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dev_info(&vmd->dev->dev, "Bound to PCI domain %04x\n",
|
||||
vmd->sysdata.domain);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vmd_remove(struct pci_dev *dev)
|
||||
{
|
||||
struct vmd_dev *vmd = pci_get_drvdata(dev);
|
||||
|
||||
pci_set_drvdata(dev, NULL);
|
||||
sysfs_remove_link(&vmd->dev->dev.kobj, "domain");
|
||||
pci_stop_root_bus(vmd->bus);
|
||||
pci_remove_root_bus(vmd->bus);
|
||||
vmd_teardown_dma_ops(vmd);
|
||||
irq_domain_remove(vmd->irq_domain);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int vmd_suspend(struct device *dev)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
|
||||
pci_save_state(pdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vmd_resume(struct device *dev)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
|
||||
pci_restore_state(pdev);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
static SIMPLE_DEV_PM_OPS(vmd_dev_pm_ops, vmd_suspend, vmd_resume);
|
||||
|
||||
static const struct pci_device_id vmd_ids[] = {
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x201d),},
|
||||
{0,}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, vmd_ids);
|
||||
|
||||
static struct pci_driver vmd_drv = {
|
||||
.name = "vmd",
|
||||
.id_table = vmd_ids,
|
||||
.probe = vmd_probe,
|
||||
.remove = vmd_remove,
|
||||
.driver = {
|
||||
.pm = &vmd_dev_pm_ops,
|
||||
},
|
||||
};
|
||||
module_pci_driver(vmd_drv);
|
||||
|
||||
MODULE_AUTHOR("Intel Corporation");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_VERSION("0.6");
|
@ -140,6 +140,8 @@ static int pci_bus_alloc_from_region(struct pci_bus *bus, struct resource *res,
|
||||
type_mask |= IORESOURCE_TYPE_BITS;
|
||||
|
||||
pci_bus_for_each_resource(bus, r, i) {
|
||||
resource_size_t min_used = min;
|
||||
|
||||
if (!r)
|
||||
continue;
|
||||
|
||||
@ -163,12 +165,12 @@ static int pci_bus_alloc_from_region(struct pci_bus *bus, struct resource *res,
|
||||
* overrides "min".
|
||||
*/
|
||||
if (avail.start)
|
||||
min = avail.start;
|
||||
min_used = avail.start;
|
||||
|
||||
max = avail.end;
|
||||
|
||||
/* Ok, try it out.. */
|
||||
ret = allocate_resource(r, res, size, min, max,
|
||||
ret = allocate_resource(r, res, size, min_used, max,
|
||||
align, alignf, alignf_data);
|
||||
if (ret == 0)
|
||||
return 0;
|
||||
|
@ -49,8 +49,7 @@ config PCI_RCAR_GEN2
|
||||
|
||||
config PCI_RCAR_GEN2_PCIE
|
||||
bool "Renesas R-Car PCIe controller"
|
||||
depends on ARM
|
||||
depends on ARCH_SHMOBILE || COMPILE_TEST
|
||||
depends on ARCH_SHMOBILE || (ARM && COMPILE_TEST)
|
||||
help
|
||||
Say Y here if you want PCIe controller support on R-Car Gen2 SoCs.
|
||||
|
||||
@ -119,13 +118,11 @@ config PCI_VERSATILE
|
||||
depends on ARCH_VERSATILE
|
||||
|
||||
config PCIE_IPROC
|
||||
tristate "Broadcom iProc PCIe controller"
|
||||
depends on OF && (ARM || ARM64)
|
||||
default n
|
||||
tristate
|
||||
help
|
||||
This enables the iProc PCIe core controller support for Broadcom's
|
||||
iProc family of SoCs. An appropriate bus interface driver also needs
|
||||
to be enabled
|
||||
iProc family of SoCs. An appropriate bus interface driver needs
|
||||
to be enabled to select this.
|
||||
|
||||
config PCIE_IPROC_PLATFORM
|
||||
tristate "Broadcom iProc PCIe platform bus driver"
|
||||
@ -148,6 +145,16 @@ config PCIE_IPROC_BCMA
|
||||
Say Y here if you want to use the Broadcom iProc PCIe controller
|
||||
through the BCMA bus interface
|
||||
|
||||
config PCIE_IPROC_MSI
|
||||
bool "Broadcom iProc PCIe MSI support"
|
||||
depends on PCIE_IPROC_PLATFORM || PCIE_IPROC_BCMA
|
||||
depends on PCI_MSI
|
||||
select PCI_MSI_IRQ_DOMAIN
|
||||
default ARCH_BCM_IPROC
|
||||
help
|
||||
Say Y here if you want to enable MSI support for Broadcom's iProc
|
||||
PCIe controller
|
||||
|
||||
config PCIE_ALTERA
|
||||
bool "Altera PCIe controller"
|
||||
depends on ARM || NIOS2
|
||||
@ -167,10 +174,21 @@ config PCIE_ALTERA_MSI
|
||||
|
||||
config PCI_HISI
|
||||
depends on OF && ARM64
|
||||
bool "HiSilicon SoC HIP05 PCIe controller"
|
||||
bool "HiSilicon Hip05 and Hip06 SoCs PCIe controllers"
|
||||
select PCIEPORTBUS
|
||||
select PCIE_DW
|
||||
help
|
||||
Say Y here if you want PCIe controller support on HiSilicon HIP05 SoC
|
||||
Say Y here if you want PCIe controller support on HiSilicon
|
||||
Hip05 and Hip06 SoCs
|
||||
|
||||
config PCIE_QCOM
|
||||
bool "Qualcomm PCIe controller"
|
||||
depends on ARCH_QCOM && OF
|
||||
select PCIE_DW
|
||||
select PCIEPORTBUS
|
||||
help
|
||||
Say Y here to enable PCIe controller support on Qualcomm SoCs. The
|
||||
PCIe controller uses the Designware core plus Qualcomm-specific
|
||||
hardware wrappers.
|
||||
|
||||
endmenu
|
||||
|
@ -15,8 +15,10 @@ obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o
|
||||
obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o
|
||||
obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o
|
||||
obj-$(CONFIG_PCIE_IPROC) += pcie-iproc.o
|
||||
obj-$(CONFIG_PCIE_IPROC_MSI) += pcie-iproc-msi.o
|
||||
obj-$(CONFIG_PCIE_IPROC_PLATFORM) += pcie-iproc-platform.o
|
||||
obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o
|
||||
obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o
|
||||
obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o
|
||||
obj-$(CONFIG_PCI_HISI) += pcie-hisi.o
|
||||
obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o
|
||||
|
@ -302,7 +302,8 @@ static int __init dra7xx_add_pcie_port(struct dra7xx_pcie *dra7xx,
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, pp->irq,
|
||||
dra7xx_pcie_msi_irq_handler, IRQF_SHARED,
|
||||
dra7xx_pcie_msi_irq_handler,
|
||||
IRQF_SHARED | IRQF_NO_THREAD,
|
||||
"dra7-pcie-msi", pp);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to request irq\n");
|
||||
|
@ -522,7 +522,8 @@ static int __init exynos_add_pcie_port(struct pcie_port *pp,
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, pp->msi_irq,
|
||||
exynos_pcie_msi_irq_handler,
|
||||
IRQF_SHARED, "exynos-pcie", pp);
|
||||
IRQF_SHARED | IRQF_NO_THREAD,
|
||||
"exynos-pcie", pp);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to request msi irq\n");
|
||||
return ret;
|
||||
|
@ -38,16 +38,7 @@ struct gen_pci_cfg_windows {
|
||||
struct gen_pci_cfg_bus_ops *ops;
|
||||
};
|
||||
|
||||
/*
|
||||
* ARM pcibios functions expect the ARM struct pci_sys_data as the PCI
|
||||
* sysdata. Add pci_sys_data as the first element in struct gen_pci so
|
||||
* that when we use a gen_pci pointer as sysdata, it is also a pointer to
|
||||
* a struct pci_sys_data.
|
||||
*/
|
||||
struct gen_pci {
|
||||
#ifdef CONFIG_ARM
|
||||
struct pci_sys_data sys;
|
||||
#endif
|
||||
struct pci_host_bridge host;
|
||||
struct gen_pci_cfg_windows cfg;
|
||||
struct list_head resources;
|
||||
|
@ -32,7 +32,7 @@
|
||||
#define to_imx6_pcie(x) container_of(x, struct imx6_pcie, pp)
|
||||
|
||||
struct imx6_pcie {
|
||||
int reset_gpio;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct clk *pcie_bus;
|
||||
struct clk *pcie_phy;
|
||||
struct clk *pcie;
|
||||
@ -287,10 +287,10 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp)
|
||||
usleep_range(200, 500);
|
||||
|
||||
/* Some boards don't have PCIe reset GPIO. */
|
||||
if (gpio_is_valid(imx6_pcie->reset_gpio)) {
|
||||
gpio_set_value(imx6_pcie->reset_gpio, 0);
|
||||
if (imx6_pcie->reset_gpio) {
|
||||
gpiod_set_value_cansleep(imx6_pcie->reset_gpio, 0);
|
||||
msleep(100);
|
||||
gpio_set_value(imx6_pcie->reset_gpio, 1);
|
||||
gpiod_set_value_cansleep(imx6_pcie->reset_gpio, 1);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@ -537,7 +537,8 @@ static int __init imx6_add_pcie_port(struct pcie_port *pp,
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, pp->msi_irq,
|
||||
imx6_pcie_msi_handler,
|
||||
IRQF_SHARED, "mx6-pcie-msi", pp);
|
||||
IRQF_SHARED | IRQF_NO_THREAD,
|
||||
"mx6-pcie-msi", pp);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to request MSI irq\n");
|
||||
return ret;
|
||||
@ -560,7 +561,6 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct imx6_pcie *imx6_pcie;
|
||||
struct pcie_port *pp;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct resource *dbi_base;
|
||||
int ret;
|
||||
|
||||
@ -581,15 +581,8 @@ static int __init imx6_pcie_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(pp->dbi_base);
|
||||
|
||||
/* Fetch GPIOs */
|
||||
imx6_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0);
|
||||
if (gpio_is_valid(imx6_pcie->reset_gpio)) {
|
||||
ret = devm_gpio_request_one(&pdev->dev, imx6_pcie->reset_gpio,
|
||||
GPIOF_OUT_INIT_LOW, "PCIe reset");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "unable to get reset gpio\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
imx6_pcie->reset_gpio = devm_gpiod_get_optional(&pdev->dev, "reset",
|
||||
GPIOD_OUT_LOW);
|
||||
|
||||
/* Fetch clocks */
|
||||
imx6_pcie->pcie_phy = devm_clk_get(&pdev->dev, "pcie_phy");
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
@ -102,6 +103,8 @@ struct rcar_pci_priv {
|
||||
unsigned busnr;
|
||||
int irq;
|
||||
unsigned long window_size;
|
||||
unsigned long window_addr;
|
||||
unsigned long window_pci;
|
||||
};
|
||||
|
||||
/* PCI configuration space operations */
|
||||
@ -239,8 +242,8 @@ static int rcar_pci_setup(int nr, struct pci_sys_data *sys)
|
||||
RCAR_PCI_ARBITER_PCIBP_MODE;
|
||||
iowrite32(val, reg + RCAR_PCI_ARBITER_CTR_REG);
|
||||
|
||||
/* PCI-AHB mapping: 0x40000000 base */
|
||||
iowrite32(0x40000000 | RCAR_PCIAHB_PREFETCH16,
|
||||
/* PCI-AHB mapping */
|
||||
iowrite32(priv->window_addr | RCAR_PCIAHB_PREFETCH16,
|
||||
reg + RCAR_PCIAHB_WIN1_CTR_REG);
|
||||
|
||||
/* AHB-PCI mapping: OHCI/EHCI registers */
|
||||
@ -251,7 +254,7 @@ static int rcar_pci_setup(int nr, struct pci_sys_data *sys)
|
||||
iowrite32(RCAR_AHBPCI_WIN1_HOST | RCAR_AHBPCI_WIN_CTR_CFG,
|
||||
reg + RCAR_AHBPCI_WIN1_CTR_REG);
|
||||
/* Set PCI-AHB Window1 address */
|
||||
iowrite32(0x40000000 | PCI_BASE_ADDRESS_MEM_PREFETCH,
|
||||
iowrite32(priv->window_pci | PCI_BASE_ADDRESS_MEM_PREFETCH,
|
||||
reg + PCI_BASE_ADDRESS_1);
|
||||
/* Set AHB-PCI bridge PCI communication area address */
|
||||
val = priv->cfg_res->start + RCAR_AHBPCI_PCICOM_OFFSET;
|
||||
@ -284,6 +287,64 @@ static struct pci_ops rcar_pci_ops = {
|
||||
.write = pci_generic_config_write,
|
||||
};
|
||||
|
||||
static int pci_dma_range_parser_init(struct of_pci_range_parser *parser,
|
||||
struct device_node *node)
|
||||
{
|
||||
const int na = 3, ns = 2;
|
||||
int rlen;
|
||||
|
||||
parser->node = node;
|
||||
parser->pna = of_n_addr_cells(node);
|
||||
parser->np = parser->pna + na + ns;
|
||||
|
||||
parser->range = of_get_property(node, "dma-ranges", &rlen);
|
||||
if (!parser->range)
|
||||
return -ENOENT;
|
||||
|
||||
parser->end = parser->range + rlen / sizeof(__be32);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rcar_pci_parse_map_dma_ranges(struct rcar_pci_priv *pci,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct of_pci_range range;
|
||||
struct of_pci_range_parser parser;
|
||||
int index = 0;
|
||||
|
||||
/* Failure to parse is ok as we fall back to defaults */
|
||||
if (pci_dma_range_parser_init(&parser, np))
|
||||
return 0;
|
||||
|
||||
/* Get the dma-ranges from DT */
|
||||
for_each_of_pci_range(&parser, &range) {
|
||||
/* Hardware only allows one inbound 32-bit range */
|
||||
if (index)
|
||||
return -EINVAL;
|
||||
|
||||
pci->window_addr = (unsigned long)range.cpu_addr;
|
||||
pci->window_pci = (unsigned long)range.pci_addr;
|
||||
pci->window_size = (unsigned long)range.size;
|
||||
|
||||
/* Catch HW limitations */
|
||||
if (!(range.flags & IORESOURCE_PREFETCH)) {
|
||||
dev_err(pci->dev, "window must be prefetchable\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (pci->window_addr) {
|
||||
u32 lowaddr = 1 << (ffs(pci->window_addr) - 1);
|
||||
|
||||
if (lowaddr < pci->window_size) {
|
||||
dev_err(pci->dev, "invalid window size/addr\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rcar_pci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *cfg_res, *mem_res;
|
||||
@ -329,6 +390,9 @@ static int rcar_pci_probe(struct platform_device *pdev)
|
||||
return priv->irq;
|
||||
}
|
||||
|
||||
/* default window addr and size if not specified in DT */
|
||||
priv->window_addr = 0x40000000;
|
||||
priv->window_pci = 0x40000000;
|
||||
priv->window_size = SZ_1G;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
@ -344,6 +408,12 @@ static int rcar_pci_probe(struct platform_device *pdev)
|
||||
priv->busnr = busnr.start;
|
||||
if (busnr.end != busnr.start)
|
||||
dev_warn(&pdev->dev, "only one bus number supported\n");
|
||||
|
||||
ret = rcar_pci_parse_map_dma_ranges(priv, pdev->dev.of_node);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to parse dma-range\n");
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
priv->busnr = pdev->id;
|
||||
}
|
||||
@ -360,6 +430,7 @@ static int rcar_pci_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
static struct of_device_id rcar_pci_of_match[] = {
|
||||
{ .compatible = "renesas,pci-rcar-gen2", },
|
||||
{ .compatible = "renesas,pci-r8a7790", },
|
||||
{ .compatible = "renesas,pci-r8a7791", },
|
||||
{ .compatible = "renesas,pci-r8a7794", },
|
||||
|
@ -1288,7 +1288,7 @@ static int tegra_pcie_enable_msi(struct tegra_pcie *pcie)
|
||||
|
||||
msi->irq = err;
|
||||
|
||||
err = request_irq(msi->irq, tegra_pcie_msi_irq, 0,
|
||||
err = request_irq(msi->irq, tegra_pcie_msi_irq, IRQF_NO_THREAD,
|
||||
tegra_msi_irq_chip.name, pcie);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
|
||||
|
@ -125,9 +125,6 @@ out_release_res:
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Unused, temporary to satisfy ARM arch code */
|
||||
struct pci_sys_data sys;
|
||||
|
||||
static int versatile_pci_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
@ -208,7 +205,7 @@ static int versatile_pci_probe(struct platform_device *pdev)
|
||||
pci_add_flags(PCI_ENABLE_PROC_DOMAINS);
|
||||
pci_add_flags(PCI_REASSIGN_ALL_BUS | PCI_REASSIGN_ALL_RSRC);
|
||||
|
||||
bus = pci_scan_root_bus(&pdev->dev, 0, &pci_versatile_ops, &sys, &pci_res);
|
||||
bus = pci_scan_root_bus(&pdev->dev, 0, &pci_versatile_ops, NULL, &pci_res);
|
||||
if (!bus)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -128,32 +128,26 @@ static inline void dw_pcie_writel_rc(struct pcie_port *pp, u32 val, u32 reg)
|
||||
static int dw_pcie_rd_own_conf(struct pcie_port *pp, int where, int size,
|
||||
u32 *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (pp->ops->rd_own_conf)
|
||||
ret = pp->ops->rd_own_conf(pp, where, size, val);
|
||||
else
|
||||
ret = dw_pcie_cfg_read(pp->dbi_base + where, size, val);
|
||||
return pp->ops->rd_own_conf(pp, where, size, val);
|
||||
|
||||
return ret;
|
||||
return dw_pcie_cfg_read(pp->dbi_base + where, size, val);
|
||||
}
|
||||
|
||||
static int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size,
|
||||
u32 val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (pp->ops->wr_own_conf)
|
||||
ret = pp->ops->wr_own_conf(pp, where, size, val);
|
||||
else
|
||||
ret = dw_pcie_cfg_write(pp->dbi_base + where, size, val);
|
||||
return pp->ops->wr_own_conf(pp, where, size, val);
|
||||
|
||||
return ret;
|
||||
return dw_pcie_cfg_write(pp->dbi_base + where, size, val);
|
||||
}
|
||||
|
||||
static void dw_pcie_prog_outbound_atu(struct pcie_port *pp, int index,
|
||||
int type, u64 cpu_addr, u64 pci_addr, u32 size)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | index,
|
||||
PCIE_ATU_VIEWPORT);
|
||||
dw_pcie_writel_rc(pp, lower_32_bits(cpu_addr), PCIE_ATU_LOWER_BASE);
|
||||
@ -164,6 +158,12 @@ static void dw_pcie_prog_outbound_atu(struct pcie_port *pp, int index,
|
||||
dw_pcie_writel_rc(pp, upper_32_bits(pci_addr), PCIE_ATU_UPPER_TARGET);
|
||||
dw_pcie_writel_rc(pp, type, PCIE_ATU_CR1);
|
||||
dw_pcie_writel_rc(pp, PCIE_ATU_ENABLE, PCIE_ATU_CR2);
|
||||
|
||||
/*
|
||||
* Make sure ATU enable takes effect before any subsequent config
|
||||
* and I/O accesses.
|
||||
*/
|
||||
dw_pcie_readl_rc(pp, PCIE_ATU_CR2, &val);
|
||||
}
|
||||
|
||||
static struct irq_chip dw_msi_irq_chip = {
|
||||
@ -384,7 +384,7 @@ int dw_pcie_link_up(struct pcie_port *pp)
|
||||
{
|
||||
if (pp->ops->link_up)
|
||||
return pp->ops->link_up(pp);
|
||||
else
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -571,6 +571,9 @@ static int dw_pcie_rd_other_conf(struct pcie_port *pp, struct pci_bus *bus,
|
||||
u64 cpu_addr;
|
||||
void __iomem *va_cfg_base;
|
||||
|
||||
if (pp->ops->rd_other_conf)
|
||||
return pp->ops->rd_other_conf(pp, bus, devfn, where, size, val);
|
||||
|
||||
busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) |
|
||||
PCIE_ATU_FUNC(PCI_FUNC(devfn));
|
||||
|
||||
@ -605,6 +608,9 @@ static int dw_pcie_wr_other_conf(struct pcie_port *pp, struct pci_bus *bus,
|
||||
u64 cpu_addr;
|
||||
void __iomem *va_cfg_base;
|
||||
|
||||
if (pp->ops->wr_other_conf)
|
||||
return pp->ops->wr_other_conf(pp, bus, devfn, where, size, val);
|
||||
|
||||
busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) |
|
||||
PCIE_ATU_FUNC(PCI_FUNC(devfn));
|
||||
|
||||
@ -658,46 +664,30 @@ static int dw_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
|
||||
int size, u32 *val)
|
||||
{
|
||||
struct pcie_port *pp = bus->sysdata;
|
||||
int ret;
|
||||
|
||||
if (dw_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0) {
|
||||
*val = 0xffffffff;
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (bus->number != pp->root_bus_nr)
|
||||
if (pp->ops->rd_other_conf)
|
||||
ret = pp->ops->rd_other_conf(pp, bus, devfn,
|
||||
where, size, val);
|
||||
else
|
||||
ret = dw_pcie_rd_other_conf(pp, bus, devfn,
|
||||
where, size, val);
|
||||
else
|
||||
ret = dw_pcie_rd_own_conf(pp, where, size, val);
|
||||
if (bus->number == pp->root_bus_nr)
|
||||
return dw_pcie_rd_own_conf(pp, where, size, val);
|
||||
|
||||
return ret;
|
||||
return dw_pcie_rd_other_conf(pp, bus, devfn, where, size, val);
|
||||
}
|
||||
|
||||
static int dw_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
|
||||
int where, int size, u32 val)
|
||||
{
|
||||
struct pcie_port *pp = bus->sysdata;
|
||||
int ret;
|
||||
|
||||
if (dw_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0)
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
|
||||
if (bus->number != pp->root_bus_nr)
|
||||
if (pp->ops->wr_other_conf)
|
||||
ret = pp->ops->wr_other_conf(pp, bus, devfn,
|
||||
where, size, val);
|
||||
else
|
||||
ret = dw_pcie_wr_other_conf(pp, bus, devfn,
|
||||
where, size, val);
|
||||
else
|
||||
ret = dw_pcie_wr_own_conf(pp, where, size, val);
|
||||
if (bus->number == pp->root_bus_nr)
|
||||
return dw_pcie_wr_own_conf(pp, where, size, val);
|
||||
|
||||
return ret;
|
||||
return dw_pcie_wr_other_conf(pp, bus, devfn, where, size, val);
|
||||
}
|
||||
|
||||
static struct pci_ops dw_pcie_ops = {
|
||||
|
@ -1,10 +1,11 @@
|
||||
/*
|
||||
* PCIe host controller driver for HiSilicon Hip05 SoC
|
||||
* PCIe host controller driver for HiSilicon SoCs
|
||||
*
|
||||
* Copyright (C) 2015 HiSilicon Co., Ltd. http://www.hisilicon.com
|
||||
*
|
||||
* Author: Zhou Wang <wangzhou1@hisilicon.com>
|
||||
* Authors: Zhou Wang <wangzhou1@hisilicon.com>
|
||||
* Dacai Zhu <zhudacai@hisilicon.com>
|
||||
* Gabriele Paoloni <gabriele.paoloni@huawei.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
@ -16,21 +17,31 @@
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "pcie-designware.h"
|
||||
|
||||
#define PCIE_SUBCTRL_SYS_STATE4_REG 0x6818
|
||||
#define PCIE_LTSSM_LINKUP_STATE 0x11
|
||||
#define PCIE_LTSSM_STATE_MASK 0x3F
|
||||
#define PCIE_SUBCTRL_SYS_STATE4_REG 0x6818
|
||||
#define PCIE_SYS_STATE4 0x31c
|
||||
#define PCIE_HIP06_CTRL_OFF 0x1000
|
||||
|
||||
#define to_hisi_pcie(x) container_of(x, struct hisi_pcie, pp)
|
||||
|
||||
struct hisi_pcie;
|
||||
|
||||
struct pcie_soc_ops {
|
||||
int (*hisi_pcie_link_up)(struct hisi_pcie *pcie);
|
||||
};
|
||||
|
||||
struct hisi_pcie {
|
||||
struct regmap *subctrl;
|
||||
void __iomem *reg_base;
|
||||
u32 port_id;
|
||||
struct pcie_port pp;
|
||||
struct pcie_soc_ops *soc_ops;
|
||||
};
|
||||
|
||||
static inline void hisi_pcie_apb_writel(struct hisi_pcie *pcie,
|
||||
@ -44,7 +55,7 @@ static inline u32 hisi_pcie_apb_readl(struct hisi_pcie *pcie, u32 reg)
|
||||
return readl(pcie->reg_base + reg);
|
||||
}
|
||||
|
||||
/* Hip05 PCIe host only supports 32-bit config access */
|
||||
/* HipXX PCIe host only supports 32-bit config access */
|
||||
static int hisi_pcie_cfg_read(struct pcie_port *pp, int where, int size,
|
||||
u32 *val)
|
||||
{
|
||||
@ -69,7 +80,7 @@ static int hisi_pcie_cfg_read(struct pcie_port *pp, int where, int size,
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
/* Hip05 PCIe host only supports 32-bit config access */
|
||||
/* HipXX PCIe host only supports 32-bit config access */
|
||||
static int hisi_pcie_cfg_write(struct pcie_port *pp, int where, int size,
|
||||
u32 val)
|
||||
{
|
||||
@ -96,10 +107,9 @@ static int hisi_pcie_cfg_write(struct pcie_port *pp, int where, int size,
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
static int hisi_pcie_link_up(struct pcie_port *pp)
|
||||
static int hisi_pcie_link_up_hip05(struct hisi_pcie *hisi_pcie)
|
||||
{
|
||||
u32 val;
|
||||
struct hisi_pcie *hisi_pcie = to_hisi_pcie(pp);
|
||||
|
||||
regmap_read(hisi_pcie->subctrl, PCIE_SUBCTRL_SYS_STATE4_REG +
|
||||
0x100 * hisi_pcie->port_id, &val);
|
||||
@ -107,6 +117,23 @@ static int hisi_pcie_link_up(struct pcie_port *pp)
|
||||
return ((val & PCIE_LTSSM_STATE_MASK) == PCIE_LTSSM_LINKUP_STATE);
|
||||
}
|
||||
|
||||
static int hisi_pcie_link_up_hip06(struct hisi_pcie *hisi_pcie)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = hisi_pcie_apb_readl(hisi_pcie, PCIE_HIP06_CTRL_OFF +
|
||||
PCIE_SYS_STATE4);
|
||||
|
||||
return ((val & PCIE_LTSSM_STATE_MASK) == PCIE_LTSSM_LINKUP_STATE);
|
||||
}
|
||||
|
||||
static int hisi_pcie_link_up(struct pcie_port *pp)
|
||||
{
|
||||
struct hisi_pcie *hisi_pcie = to_hisi_pcie(pp);
|
||||
|
||||
return hisi_pcie->soc_ops->hisi_pcie_link_up(hisi_pcie);
|
||||
}
|
||||
|
||||
static struct pcie_host_ops hisi_pcie_host_ops = {
|
||||
.rd_own_conf = hisi_pcie_cfg_read,
|
||||
.wr_own_conf = hisi_pcie_cfg_write,
|
||||
@ -145,7 +172,9 @@ static int hisi_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct hisi_pcie *hisi_pcie;
|
||||
struct pcie_port *pp;
|
||||
const struct of_device_id *match;
|
||||
struct resource *reg;
|
||||
struct device_driver *driver;
|
||||
int ret;
|
||||
|
||||
hisi_pcie = devm_kzalloc(&pdev->dev, sizeof(*hisi_pcie), GFP_KERNEL);
|
||||
@ -154,6 +183,10 @@ static int hisi_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
pp = &hisi_pcie->pp;
|
||||
pp->dev = &pdev->dev;
|
||||
driver = (pdev->dev).driver;
|
||||
|
||||
match = of_match_device(driver->of_match_table, &pdev->dev);
|
||||
hisi_pcie->soc_ops = (struct pcie_soc_ops *) match->data;
|
||||
|
||||
hisi_pcie->subctrl =
|
||||
syscon_regmap_lookup_by_compatible("hisilicon,pcie-sas-subctrl");
|
||||
@ -182,11 +215,27 @@ static int hisi_pcie_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pcie_soc_ops hip05_ops = {
|
||||
&hisi_pcie_link_up_hip05
|
||||
};
|
||||
|
||||
static struct pcie_soc_ops hip06_ops = {
|
||||
&hisi_pcie_link_up_hip06
|
||||
};
|
||||
|
||||
static const struct of_device_id hisi_pcie_of_match[] = {
|
||||
{.compatible = "hisilicon,hip05-pcie",},
|
||||
{
|
||||
.compatible = "hisilicon,hip05-pcie",
|
||||
.data = (void *) &hip05_ops,
|
||||
},
|
||||
{
|
||||
.compatible = "hisilicon,hip06-pcie",
|
||||
.data = (void *) &hip06_ops,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
|
||||
MODULE_DEVICE_TABLE(of, hisi_pcie_of_match);
|
||||
|
||||
static struct platform_driver hisi_pcie_driver = {
|
||||
@ -198,3 +247,8 @@ static struct platform_driver hisi_pcie_driver = {
|
||||
};
|
||||
|
||||
module_platform_driver(hisi_pcie_driver);
|
||||
|
||||
MODULE_AUTHOR("Zhou Wang <wangzhou1@hisilicon.com>");
|
||||
MODULE_AUTHOR("Dacai Zhu <zhudacai@hisilicon.com>");
|
||||
MODULE_AUTHOR("Gabriele Paoloni <gabriele.paoloni@huawei.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
@ -55,6 +55,7 @@ static int iproc_pcie_bcma_probe(struct bcma_device *bdev)
|
||||
bcma_set_drvdata(bdev, pcie);
|
||||
|
||||
pcie->base = bdev->io_addr;
|
||||
pcie->base_addr = bdev->addr;
|
||||
|
||||
res_mem.start = bdev->addr_s[0];
|
||||
res_mem.end = bdev->addr_s[0] + SZ_128M - 1;
|
||||
|
675
drivers/pci/host/pcie-iproc-msi.c
Normal file
675
drivers/pci/host/pcie-iproc-msi.c
Normal file
@ -0,0 +1,675 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Broadcom Corporation
|
||||
*
|
||||
* 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 version 2.
|
||||
*
|
||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
||||
* kind, whether express or implied; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include "pcie-iproc.h"
|
||||
|
||||
#define IPROC_MSI_INTR_EN_SHIFT 11
|
||||
#define IPROC_MSI_INTR_EN BIT(IPROC_MSI_INTR_EN_SHIFT)
|
||||
#define IPROC_MSI_INT_N_EVENT_SHIFT 1
|
||||
#define IPROC_MSI_INT_N_EVENT BIT(IPROC_MSI_INT_N_EVENT_SHIFT)
|
||||
#define IPROC_MSI_EQ_EN_SHIFT 0
|
||||
#define IPROC_MSI_EQ_EN BIT(IPROC_MSI_EQ_EN_SHIFT)
|
||||
|
||||
#define IPROC_MSI_EQ_MASK 0x3f
|
||||
|
||||
/* Max number of GIC interrupts */
|
||||
#define NR_HW_IRQS 6
|
||||
|
||||
/* Number of entries in each event queue */
|
||||
#define EQ_LEN 64
|
||||
|
||||
/* Size of each event queue memory region */
|
||||
#define EQ_MEM_REGION_SIZE SZ_4K
|
||||
|
||||
/* Size of each MSI address region */
|
||||
#define MSI_MEM_REGION_SIZE SZ_4K
|
||||
|
||||
enum iproc_msi_reg {
|
||||
IPROC_MSI_EQ_PAGE = 0,
|
||||
IPROC_MSI_EQ_PAGE_UPPER,
|
||||
IPROC_MSI_PAGE,
|
||||
IPROC_MSI_PAGE_UPPER,
|
||||
IPROC_MSI_CTRL,
|
||||
IPROC_MSI_EQ_HEAD,
|
||||
IPROC_MSI_EQ_TAIL,
|
||||
IPROC_MSI_INTS_EN,
|
||||
IPROC_MSI_REG_SIZE,
|
||||
};
|
||||
|
||||
struct iproc_msi;
|
||||
|
||||
/**
|
||||
* iProc MSI group
|
||||
*
|
||||
* One MSI group is allocated per GIC interrupt, serviced by one iProc MSI
|
||||
* event queue.
|
||||
*
|
||||
* @msi: pointer to iProc MSI data
|
||||
* @gic_irq: GIC interrupt
|
||||
* @eq: Event queue number
|
||||
*/
|
||||
struct iproc_msi_grp {
|
||||
struct iproc_msi *msi;
|
||||
int gic_irq;
|
||||
unsigned int eq;
|
||||
};
|
||||
|
||||
/**
|
||||
* iProc event queue based MSI
|
||||
*
|
||||
* Only meant to be used on platforms without MSI support integrated into the
|
||||
* GIC.
|
||||
*
|
||||
* @pcie: pointer to iProc PCIe data
|
||||
* @reg_offsets: MSI register offsets
|
||||
* @grps: MSI groups
|
||||
* @nr_irqs: number of total interrupts connected to GIC
|
||||
* @nr_cpus: number of toal CPUs
|
||||
* @has_inten_reg: indicates the MSI interrupt enable register needs to be
|
||||
* set explicitly (required for some legacy platforms)
|
||||
* @bitmap: MSI vector bitmap
|
||||
* @bitmap_lock: lock to protect access to the MSI bitmap
|
||||
* @nr_msi_vecs: total number of MSI vectors
|
||||
* @inner_domain: inner IRQ domain
|
||||
* @msi_domain: MSI IRQ domain
|
||||
* @nr_eq_region: required number of 4K aligned memory region for MSI event
|
||||
* queues
|
||||
* @nr_msi_region: required number of 4K aligned address region for MSI posted
|
||||
* writes
|
||||
* @eq_cpu: pointer to allocated memory region for MSI event queues
|
||||
* @eq_dma: DMA address of MSI event queues
|
||||
* @msi_addr: MSI address
|
||||
*/
|
||||
struct iproc_msi {
|
||||
struct iproc_pcie *pcie;
|
||||
const u16 (*reg_offsets)[IPROC_MSI_REG_SIZE];
|
||||
struct iproc_msi_grp *grps;
|
||||
int nr_irqs;
|
||||
int nr_cpus;
|
||||
bool has_inten_reg;
|
||||
unsigned long *bitmap;
|
||||
struct mutex bitmap_lock;
|
||||
unsigned int nr_msi_vecs;
|
||||
struct irq_domain *inner_domain;
|
||||
struct irq_domain *msi_domain;
|
||||
unsigned int nr_eq_region;
|
||||
unsigned int nr_msi_region;
|
||||
void *eq_cpu;
|
||||
dma_addr_t eq_dma;
|
||||
phys_addr_t msi_addr;
|
||||
};
|
||||
|
||||
static const u16 iproc_msi_reg_paxb[NR_HW_IRQS][IPROC_MSI_REG_SIZE] = {
|
||||
{ 0x200, 0x2c0, 0x204, 0x2c4, 0x210, 0x250, 0x254, 0x208 },
|
||||
{ 0x200, 0x2c0, 0x204, 0x2c4, 0x214, 0x258, 0x25c, 0x208 },
|
||||
{ 0x200, 0x2c0, 0x204, 0x2c4, 0x218, 0x260, 0x264, 0x208 },
|
||||
{ 0x200, 0x2c0, 0x204, 0x2c4, 0x21c, 0x268, 0x26c, 0x208 },
|
||||
{ 0x200, 0x2c0, 0x204, 0x2c4, 0x220, 0x270, 0x274, 0x208 },
|
||||
{ 0x200, 0x2c0, 0x204, 0x2c4, 0x224, 0x278, 0x27c, 0x208 },
|
||||
};
|
||||
|
||||
static const u16 iproc_msi_reg_paxc[NR_HW_IRQS][IPROC_MSI_REG_SIZE] = {
|
||||
{ 0xc00, 0xc04, 0xc08, 0xc0c, 0xc40, 0xc50, 0xc60 },
|
||||
{ 0xc10, 0xc14, 0xc18, 0xc1c, 0xc44, 0xc54, 0xc64 },
|
||||
{ 0xc20, 0xc24, 0xc28, 0xc2c, 0xc48, 0xc58, 0xc68 },
|
||||
{ 0xc30, 0xc34, 0xc38, 0xc3c, 0xc4c, 0xc5c, 0xc6c },
|
||||
};
|
||||
|
||||
static inline u32 iproc_msi_read_reg(struct iproc_msi *msi,
|
||||
enum iproc_msi_reg reg,
|
||||
unsigned int eq)
|
||||
{
|
||||
struct iproc_pcie *pcie = msi->pcie;
|
||||
|
||||
return readl_relaxed(pcie->base + msi->reg_offsets[eq][reg]);
|
||||
}
|
||||
|
||||
static inline void iproc_msi_write_reg(struct iproc_msi *msi,
|
||||
enum iproc_msi_reg reg,
|
||||
int eq, u32 val)
|
||||
{
|
||||
struct iproc_pcie *pcie = msi->pcie;
|
||||
|
||||
writel_relaxed(val, pcie->base + msi->reg_offsets[eq][reg]);
|
||||
}
|
||||
|
||||
static inline u32 hwirq_to_group(struct iproc_msi *msi, unsigned long hwirq)
|
||||
{
|
||||
return (hwirq % msi->nr_irqs);
|
||||
}
|
||||
|
||||
static inline unsigned int iproc_msi_addr_offset(struct iproc_msi *msi,
|
||||
unsigned long hwirq)
|
||||
{
|
||||
if (msi->nr_msi_region > 1)
|
||||
return hwirq_to_group(msi, hwirq) * MSI_MEM_REGION_SIZE;
|
||||
else
|
||||
return hwirq_to_group(msi, hwirq) * sizeof(u32);
|
||||
}
|
||||
|
||||
static inline unsigned int iproc_msi_eq_offset(struct iproc_msi *msi, u32 eq)
|
||||
{
|
||||
if (msi->nr_eq_region > 1)
|
||||
return eq * EQ_MEM_REGION_SIZE;
|
||||
else
|
||||
return eq * EQ_LEN * sizeof(u32);
|
||||
}
|
||||
|
||||
static struct irq_chip iproc_msi_irq_chip = {
|
||||
.name = "iProc-MSI",
|
||||
};
|
||||
|
||||
static struct msi_domain_info iproc_msi_domain_info = {
|
||||
.flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_PCI_MSIX,
|
||||
.chip = &iproc_msi_irq_chip,
|
||||
};
|
||||
|
||||
/*
|
||||
* In iProc PCIe core, each MSI group is serviced by a GIC interrupt and a
|
||||
* dedicated event queue. Each MSI group can support up to 64 MSI vectors.
|
||||
*
|
||||
* The number of MSI groups varies between different iProc SoCs. The total
|
||||
* number of CPU cores also varies. To support MSI IRQ affinity, we
|
||||
* distribute GIC interrupts across all available CPUs. MSI vector is moved
|
||||
* from one GIC interrupt to another to steer to the target CPU.
|
||||
*
|
||||
* Assuming:
|
||||
* - the number of MSI groups is M
|
||||
* - the number of CPU cores is N
|
||||
* - M is always a multiple of N
|
||||
*
|
||||
* Total number of raw MSI vectors = M * 64
|
||||
* Total number of supported MSI vectors = (M * 64) / N
|
||||
*/
|
||||
static inline int hwirq_to_cpu(struct iproc_msi *msi, unsigned long hwirq)
|
||||
{
|
||||
return (hwirq % msi->nr_cpus);
|
||||
}
|
||||
|
||||
static inline unsigned long hwirq_to_canonical_hwirq(struct iproc_msi *msi,
|
||||
unsigned long hwirq)
|
||||
{
|
||||
return (hwirq - hwirq_to_cpu(msi, hwirq));
|
||||
}
|
||||
|
||||
static int iproc_msi_irq_set_affinity(struct irq_data *data,
|
||||
const struct cpumask *mask, bool force)
|
||||
{
|
||||
struct iproc_msi *msi = irq_data_get_irq_chip_data(data);
|
||||
int target_cpu = cpumask_first(mask);
|
||||
int curr_cpu;
|
||||
|
||||
curr_cpu = hwirq_to_cpu(msi, data->hwirq);
|
||||
if (curr_cpu == target_cpu)
|
||||
return IRQ_SET_MASK_OK_DONE;
|
||||
|
||||
/* steer MSI to the target CPU */
|
||||
data->hwirq = hwirq_to_canonical_hwirq(msi, data->hwirq) + target_cpu;
|
||||
|
||||
return IRQ_SET_MASK_OK;
|
||||
}
|
||||
|
||||
static void iproc_msi_irq_compose_msi_msg(struct irq_data *data,
|
||||
struct msi_msg *msg)
|
||||
{
|
||||
struct iproc_msi *msi = irq_data_get_irq_chip_data(data);
|
||||
dma_addr_t addr;
|
||||
|
||||
addr = msi->msi_addr + iproc_msi_addr_offset(msi, data->hwirq);
|
||||
msg->address_lo = lower_32_bits(addr);
|
||||
msg->address_hi = upper_32_bits(addr);
|
||||
msg->data = data->hwirq;
|
||||
}
|
||||
|
||||
static struct irq_chip iproc_msi_bottom_irq_chip = {
|
||||
.name = "MSI",
|
||||
.irq_set_affinity = iproc_msi_irq_set_affinity,
|
||||
.irq_compose_msi_msg = iproc_msi_irq_compose_msi_msg,
|
||||
};
|
||||
|
||||
static int iproc_msi_irq_domain_alloc(struct irq_domain *domain,
|
||||
unsigned int virq, unsigned int nr_irqs,
|
||||
void *args)
|
||||
{
|
||||
struct iproc_msi *msi = domain->host_data;
|
||||
int hwirq;
|
||||
|
||||
mutex_lock(&msi->bitmap_lock);
|
||||
|
||||
/* Allocate 'nr_cpus' number of MSI vectors each time */
|
||||
hwirq = bitmap_find_next_zero_area(msi->bitmap, msi->nr_msi_vecs, 0,
|
||||
msi->nr_cpus, 0);
|
||||
if (hwirq < msi->nr_msi_vecs) {
|
||||
bitmap_set(msi->bitmap, hwirq, msi->nr_cpus);
|
||||
} else {
|
||||
mutex_unlock(&msi->bitmap_lock);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
mutex_unlock(&msi->bitmap_lock);
|
||||
|
||||
irq_domain_set_info(domain, virq, hwirq, &iproc_msi_bottom_irq_chip,
|
||||
domain->host_data, handle_simple_irq, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void iproc_msi_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 iproc_msi *msi = irq_data_get_irq_chip_data(data);
|
||||
unsigned int hwirq;
|
||||
|
||||
mutex_lock(&msi->bitmap_lock);
|
||||
|
||||
hwirq = hwirq_to_canonical_hwirq(msi, data->hwirq);
|
||||
bitmap_clear(msi->bitmap, hwirq, msi->nr_cpus);
|
||||
|
||||
mutex_unlock(&msi->bitmap_lock);
|
||||
|
||||
irq_domain_free_irqs_parent(domain, virq, nr_irqs);
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops msi_domain_ops = {
|
||||
.alloc = iproc_msi_irq_domain_alloc,
|
||||
.free = iproc_msi_irq_domain_free,
|
||||
};
|
||||
|
||||
static inline u32 decode_msi_hwirq(struct iproc_msi *msi, u32 eq, u32 head)
|
||||
{
|
||||
u32 *msg, hwirq;
|
||||
unsigned int offs;
|
||||
|
||||
offs = iproc_msi_eq_offset(msi, eq) + head * sizeof(u32);
|
||||
msg = (u32 *)(msi->eq_cpu + offs);
|
||||
hwirq = *msg & IPROC_MSI_EQ_MASK;
|
||||
|
||||
/*
|
||||
* Since we have multiple hwirq mapped to a single MSI vector,
|
||||
* now we need to derive the hwirq at CPU0. It can then be used to
|
||||
* mapped back to virq.
|
||||
*/
|
||||
return hwirq_to_canonical_hwirq(msi, hwirq);
|
||||
}
|
||||
|
||||
static void iproc_msi_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
struct iproc_msi_grp *grp;
|
||||
struct iproc_msi *msi;
|
||||
struct iproc_pcie *pcie;
|
||||
u32 eq, head, tail, nr_events;
|
||||
unsigned long hwirq;
|
||||
int virq;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
|
||||
grp = irq_desc_get_handler_data(desc);
|
||||
msi = grp->msi;
|
||||
pcie = msi->pcie;
|
||||
eq = grp->eq;
|
||||
|
||||
/*
|
||||
* iProc MSI event queue is tracked by head and tail pointers. Head
|
||||
* pointer indicates the next entry (MSI data) to be consumed by SW in
|
||||
* the queue and needs to be updated by SW. iProc MSI core uses the
|
||||
* tail pointer as the next data insertion point.
|
||||
*
|
||||
* Entries between head and tail pointers contain valid MSI data. MSI
|
||||
* data is guaranteed to be in the event queue memory before the tail
|
||||
* pointer is updated by the iProc MSI core.
|
||||
*/
|
||||
head = iproc_msi_read_reg(msi, IPROC_MSI_EQ_HEAD,
|
||||
eq) & IPROC_MSI_EQ_MASK;
|
||||
do {
|
||||
tail = iproc_msi_read_reg(msi, IPROC_MSI_EQ_TAIL,
|
||||
eq) & IPROC_MSI_EQ_MASK;
|
||||
|
||||
/*
|
||||
* Figure out total number of events (MSI data) to be
|
||||
* processed.
|
||||
*/
|
||||
nr_events = (tail < head) ?
|
||||
(EQ_LEN - (head - tail)) : (tail - head);
|
||||
if (!nr_events)
|
||||
break;
|
||||
|
||||
/* process all outstanding events */
|
||||
while (nr_events--) {
|
||||
hwirq = decode_msi_hwirq(msi, eq, head);
|
||||
virq = irq_find_mapping(msi->inner_domain, hwirq);
|
||||
generic_handle_irq(virq);
|
||||
|
||||
head++;
|
||||
head %= EQ_LEN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now all outstanding events have been processed. Update the
|
||||
* head pointer.
|
||||
*/
|
||||
iproc_msi_write_reg(msi, IPROC_MSI_EQ_HEAD, eq, head);
|
||||
|
||||
/*
|
||||
* Now go read the tail pointer again to see if there are new
|
||||
* oustanding events that came in during the above window.
|
||||
*/
|
||||
} while (true);
|
||||
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static void iproc_msi_enable(struct iproc_msi *msi)
|
||||
{
|
||||
int i, eq;
|
||||
u32 val;
|
||||
|
||||
/* Program memory region for each event queue */
|
||||
for (i = 0; i < msi->nr_eq_region; i++) {
|
||||
dma_addr_t addr = msi->eq_dma + (i * EQ_MEM_REGION_SIZE);
|
||||
|
||||
iproc_msi_write_reg(msi, IPROC_MSI_EQ_PAGE, i,
|
||||
lower_32_bits(addr));
|
||||
iproc_msi_write_reg(msi, IPROC_MSI_EQ_PAGE_UPPER, i,
|
||||
upper_32_bits(addr));
|
||||
}
|
||||
|
||||
/* Program address region for MSI posted writes */
|
||||
for (i = 0; i < msi->nr_msi_region; i++) {
|
||||
phys_addr_t addr = msi->msi_addr + (i * MSI_MEM_REGION_SIZE);
|
||||
|
||||
iproc_msi_write_reg(msi, IPROC_MSI_PAGE, i,
|
||||
lower_32_bits(addr));
|
||||
iproc_msi_write_reg(msi, IPROC_MSI_PAGE_UPPER, i,
|
||||
upper_32_bits(addr));
|
||||
}
|
||||
|
||||
for (eq = 0; eq < msi->nr_irqs; eq++) {
|
||||
/* Enable MSI event queue */
|
||||
val = IPROC_MSI_INTR_EN | IPROC_MSI_INT_N_EVENT |
|
||||
IPROC_MSI_EQ_EN;
|
||||
iproc_msi_write_reg(msi, IPROC_MSI_CTRL, eq, val);
|
||||
|
||||
/*
|
||||
* Some legacy platforms require the MSI interrupt enable
|
||||
* register to be set explicitly.
|
||||
*/
|
||||
if (msi->has_inten_reg) {
|
||||
val = iproc_msi_read_reg(msi, IPROC_MSI_INTS_EN, eq);
|
||||
val |= BIT(eq);
|
||||
iproc_msi_write_reg(msi, IPROC_MSI_INTS_EN, eq, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void iproc_msi_disable(struct iproc_msi *msi)
|
||||
{
|
||||
u32 eq, val;
|
||||
|
||||
for (eq = 0; eq < msi->nr_irqs; eq++) {
|
||||
if (msi->has_inten_reg) {
|
||||
val = iproc_msi_read_reg(msi, IPROC_MSI_INTS_EN, eq);
|
||||
val &= ~BIT(eq);
|
||||
iproc_msi_write_reg(msi, IPROC_MSI_INTS_EN, eq, val);
|
||||
}
|
||||
|
||||
val = iproc_msi_read_reg(msi, IPROC_MSI_CTRL, eq);
|
||||
val &= ~(IPROC_MSI_INTR_EN | IPROC_MSI_INT_N_EVENT |
|
||||
IPROC_MSI_EQ_EN);
|
||||
iproc_msi_write_reg(msi, IPROC_MSI_CTRL, eq, val);
|
||||
}
|
||||
}
|
||||
|
||||
static int iproc_msi_alloc_domains(struct device_node *node,
|
||||
struct iproc_msi *msi)
|
||||
{
|
||||
msi->inner_domain = irq_domain_add_linear(NULL, msi->nr_msi_vecs,
|
||||
&msi_domain_ops, msi);
|
||||
if (!msi->inner_domain)
|
||||
return -ENOMEM;
|
||||
|
||||
msi->msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node),
|
||||
&iproc_msi_domain_info,
|
||||
msi->inner_domain);
|
||||
if (!msi->msi_domain) {
|
||||
irq_domain_remove(msi->inner_domain);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void iproc_msi_free_domains(struct iproc_msi *msi)
|
||||
{
|
||||
if (msi->msi_domain)
|
||||
irq_domain_remove(msi->msi_domain);
|
||||
|
||||
if (msi->inner_domain)
|
||||
irq_domain_remove(msi->inner_domain);
|
||||
}
|
||||
|
||||
static void iproc_msi_irq_free(struct iproc_msi *msi, unsigned int cpu)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = cpu; i < msi->nr_irqs; i += msi->nr_cpus) {
|
||||
irq_set_chained_handler_and_data(msi->grps[i].gic_irq,
|
||||
NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int iproc_msi_irq_setup(struct iproc_msi *msi, unsigned int cpu)
|
||||
{
|
||||
int i, ret;
|
||||
cpumask_var_t mask;
|
||||
struct iproc_pcie *pcie = msi->pcie;
|
||||
|
||||
for (i = cpu; i < msi->nr_irqs; i += msi->nr_cpus) {
|
||||
irq_set_chained_handler_and_data(msi->grps[i].gic_irq,
|
||||
iproc_msi_handler,
|
||||
&msi->grps[i]);
|
||||
/* Dedicate GIC interrupt to each CPU core */
|
||||
if (alloc_cpumask_var(&mask, GFP_KERNEL)) {
|
||||
cpumask_clear(mask);
|
||||
cpumask_set_cpu(cpu, mask);
|
||||
ret = irq_set_affinity(msi->grps[i].gic_irq, mask);
|
||||
if (ret)
|
||||
dev_err(pcie->dev,
|
||||
"failed to set affinity for IRQ%d\n",
|
||||
msi->grps[i].gic_irq);
|
||||
free_cpumask_var(mask);
|
||||
} else {
|
||||
dev_err(pcie->dev, "failed to alloc CPU mask\n");
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
/* Free all configured/unconfigured IRQs */
|
||||
iproc_msi_irq_free(msi, cpu);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iproc_msi_init(struct iproc_pcie *pcie, struct device_node *node)
|
||||
{
|
||||
struct iproc_msi *msi;
|
||||
int i, ret;
|
||||
unsigned int cpu;
|
||||
|
||||
if (!of_device_is_compatible(node, "brcm,iproc-msi"))
|
||||
return -ENODEV;
|
||||
|
||||
if (!of_find_property(node, "msi-controller", NULL))
|
||||
return -ENODEV;
|
||||
|
||||
if (pcie->msi)
|
||||
return -EBUSY;
|
||||
|
||||
msi = devm_kzalloc(pcie->dev, sizeof(*msi), GFP_KERNEL);
|
||||
if (!msi)
|
||||
return -ENOMEM;
|
||||
|
||||
msi->pcie = pcie;
|
||||
pcie->msi = msi;
|
||||
msi->msi_addr = pcie->base_addr;
|
||||
mutex_init(&msi->bitmap_lock);
|
||||
msi->nr_cpus = num_possible_cpus();
|
||||
|
||||
msi->nr_irqs = of_irq_count(node);
|
||||
if (!msi->nr_irqs) {
|
||||
dev_err(pcie->dev, "found no MSI GIC interrupt\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (msi->nr_irqs > NR_HW_IRQS) {
|
||||
dev_warn(pcie->dev, "too many MSI GIC interrupts defined %d\n",
|
||||
msi->nr_irqs);
|
||||
msi->nr_irqs = NR_HW_IRQS;
|
||||
}
|
||||
|
||||
if (msi->nr_irqs < msi->nr_cpus) {
|
||||
dev_err(pcie->dev,
|
||||
"not enough GIC interrupts for MSI affinity\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (msi->nr_irqs % msi->nr_cpus != 0) {
|
||||
msi->nr_irqs -= msi->nr_irqs % msi->nr_cpus;
|
||||
dev_warn(pcie->dev, "Reducing number of interrupts to %d\n",
|
||||
msi->nr_irqs);
|
||||
}
|
||||
|
||||
switch (pcie->type) {
|
||||
case IPROC_PCIE_PAXB:
|
||||
msi->reg_offsets = iproc_msi_reg_paxb;
|
||||
msi->nr_eq_region = 1;
|
||||
msi->nr_msi_region = 1;
|
||||
break;
|
||||
case IPROC_PCIE_PAXC:
|
||||
msi->reg_offsets = iproc_msi_reg_paxc;
|
||||
msi->nr_eq_region = msi->nr_irqs;
|
||||
msi->nr_msi_region = msi->nr_irqs;
|
||||
break;
|
||||
default:
|
||||
dev_err(pcie->dev, "incompatible iProc PCIe interface\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (of_find_property(node, "brcm,pcie-msi-inten", NULL))
|
||||
msi->has_inten_reg = true;
|
||||
|
||||
msi->nr_msi_vecs = msi->nr_irqs * EQ_LEN;
|
||||
msi->bitmap = devm_kcalloc(pcie->dev, BITS_TO_LONGS(msi->nr_msi_vecs),
|
||||
sizeof(*msi->bitmap), GFP_KERNEL);
|
||||
if (!msi->bitmap)
|
||||
return -ENOMEM;
|
||||
|
||||
msi->grps = devm_kcalloc(pcie->dev, msi->nr_irqs, sizeof(*msi->grps),
|
||||
GFP_KERNEL);
|
||||
if (!msi->grps)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < msi->nr_irqs; i++) {
|
||||
unsigned int irq = irq_of_parse_and_map(node, i);
|
||||
|
||||
if (!irq) {
|
||||
dev_err(pcie->dev, "unable to parse/map interrupt\n");
|
||||
ret = -ENODEV;
|
||||
goto free_irqs;
|
||||
}
|
||||
msi->grps[i].gic_irq = irq;
|
||||
msi->grps[i].msi = msi;
|
||||
msi->grps[i].eq = i;
|
||||
}
|
||||
|
||||
/* Reserve memory for event queue and make sure memories are zeroed */
|
||||
msi->eq_cpu = dma_zalloc_coherent(pcie->dev,
|
||||
msi->nr_eq_region * EQ_MEM_REGION_SIZE,
|
||||
&msi->eq_dma, GFP_KERNEL);
|
||||
if (!msi->eq_cpu) {
|
||||
ret = -ENOMEM;
|
||||
goto free_irqs;
|
||||
}
|
||||
|
||||
ret = iproc_msi_alloc_domains(node, msi);
|
||||
if (ret) {
|
||||
dev_err(pcie->dev, "failed to create MSI domains\n");
|
||||
goto free_eq_dma;
|
||||
}
|
||||
|
||||
for_each_online_cpu(cpu) {
|
||||
ret = iproc_msi_irq_setup(msi, cpu);
|
||||
if (ret)
|
||||
goto free_msi_irq;
|
||||
}
|
||||
|
||||
iproc_msi_enable(msi);
|
||||
|
||||
return 0;
|
||||
|
||||
free_msi_irq:
|
||||
for_each_online_cpu(cpu)
|
||||
iproc_msi_irq_free(msi, cpu);
|
||||
iproc_msi_free_domains(msi);
|
||||
|
||||
free_eq_dma:
|
||||
dma_free_coherent(pcie->dev, msi->nr_eq_region * EQ_MEM_REGION_SIZE,
|
||||
msi->eq_cpu, msi->eq_dma);
|
||||
|
||||
free_irqs:
|
||||
for (i = 0; i < msi->nr_irqs; i++) {
|
||||
if (msi->grps[i].gic_irq)
|
||||
irq_dispose_mapping(msi->grps[i].gic_irq);
|
||||
}
|
||||
pcie->msi = NULL;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(iproc_msi_init);
|
||||
|
||||
void iproc_msi_exit(struct iproc_pcie *pcie)
|
||||
{
|
||||
struct iproc_msi *msi = pcie->msi;
|
||||
unsigned int i, cpu;
|
||||
|
||||
if (!msi)
|
||||
return;
|
||||
|
||||
iproc_msi_disable(msi);
|
||||
|
||||
for_each_online_cpu(cpu)
|
||||
iproc_msi_irq_free(msi, cpu);
|
||||
|
||||
iproc_msi_free_domains(msi);
|
||||
|
||||
dma_free_coherent(pcie->dev, msi->nr_eq_region * EQ_MEM_REGION_SIZE,
|
||||
msi->eq_cpu, msi->eq_dma);
|
||||
|
||||
for (i = 0; i < msi->nr_irqs; i++) {
|
||||
if (msi->grps[i].gic_irq)
|
||||
irq_dispose_mapping(msi->grps[i].gic_irq);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(iproc_msi_exit);
|
@ -26,8 +26,21 @@
|
||||
|
||||
#include "pcie-iproc.h"
|
||||
|
||||
static const struct of_device_id iproc_pcie_of_match_table[] = {
|
||||
{
|
||||
.compatible = "brcm,iproc-pcie",
|
||||
.data = (int *)IPROC_PCIE_PAXB,
|
||||
}, {
|
||||
.compatible = "brcm,iproc-pcie-paxc",
|
||||
.data = (int *)IPROC_PCIE_PAXC,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, iproc_pcie_of_match_table);
|
||||
|
||||
static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct of_device_id *of_id;
|
||||
struct iproc_pcie *pcie;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct resource reg;
|
||||
@ -35,11 +48,16 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
|
||||
LIST_HEAD(res);
|
||||
int ret;
|
||||
|
||||
of_id = of_match_device(iproc_pcie_of_match_table, &pdev->dev);
|
||||
if (!of_id)
|
||||
return -EINVAL;
|
||||
|
||||
pcie = devm_kzalloc(&pdev->dev, sizeof(struct iproc_pcie), GFP_KERNEL);
|
||||
if (!pcie)
|
||||
return -ENOMEM;
|
||||
|
||||
pcie->dev = &pdev->dev;
|
||||
pcie->type = (enum iproc_pcie_type)of_id->data;
|
||||
platform_set_drvdata(pdev, pcie);
|
||||
|
||||
ret = of_address_to_resource(np, 0, ®);
|
||||
@ -53,6 +71,7 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
|
||||
dev_err(pcie->dev, "unable to map controller registers\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
pcie->base_addr = reg.start;
|
||||
|
||||
if (of_property_read_bool(np, "brcm,pcie-ob")) {
|
||||
u32 val;
|
||||
@ -114,12 +133,6 @@ static int iproc_pcie_pltfm_remove(struct platform_device *pdev)
|
||||
return iproc_pcie_remove(pcie);
|
||||
}
|
||||
|
||||
static const struct of_device_id iproc_pcie_of_match_table[] = {
|
||||
{ .compatible = "brcm,iproc-pcie", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, iproc_pcie_of_match_table);
|
||||
|
||||
static struct platform_driver iproc_pcie_pltfm_driver = {
|
||||
.driver = {
|
||||
.name = "iproc-pcie",
|
||||
|
@ -30,20 +30,16 @@
|
||||
|
||||
#include "pcie-iproc.h"
|
||||
|
||||
#define CLK_CONTROL_OFFSET 0x000
|
||||
#define EP_PERST_SOURCE_SELECT_SHIFT 2
|
||||
#define EP_PERST_SOURCE_SELECT BIT(EP_PERST_SOURCE_SELECT_SHIFT)
|
||||
#define EP_MODE_SURVIVE_PERST_SHIFT 1
|
||||
#define EP_MODE_SURVIVE_PERST BIT(EP_MODE_SURVIVE_PERST_SHIFT)
|
||||
#define RC_PCIE_RST_OUTPUT_SHIFT 0
|
||||
#define RC_PCIE_RST_OUTPUT BIT(RC_PCIE_RST_OUTPUT_SHIFT)
|
||||
#define PAXC_RESET_MASK 0x7f
|
||||
|
||||
#define CFG_IND_ADDR_OFFSET 0x120
|
||||
#define CFG_IND_ADDR_MASK 0x00001ffc
|
||||
|
||||
#define CFG_IND_DATA_OFFSET 0x124
|
||||
|
||||
#define CFG_ADDR_OFFSET 0x1f8
|
||||
#define CFG_ADDR_BUS_NUM_SHIFT 20
|
||||
#define CFG_ADDR_BUS_NUM_MASK 0x0ff00000
|
||||
#define CFG_ADDR_DEV_NUM_SHIFT 15
|
||||
@ -55,12 +51,8 @@
|
||||
#define CFG_ADDR_CFG_TYPE_SHIFT 0
|
||||
#define CFG_ADDR_CFG_TYPE_MASK 0x00000003
|
||||
|
||||
#define CFG_DATA_OFFSET 0x1fc
|
||||
|
||||
#define SYS_RC_INTX_EN 0x330
|
||||
#define SYS_RC_INTX_MASK 0xf
|
||||
|
||||
#define PCIE_LINK_STATUS_OFFSET 0xf0c
|
||||
#define PCIE_PHYLINKUP_SHIFT 3
|
||||
#define PCIE_PHYLINKUP BIT(PCIE_PHYLINKUP_SHIFT)
|
||||
#define PCIE_DL_ACTIVE_SHIFT 2
|
||||
@ -71,12 +63,54 @@
|
||||
#define OARR_SIZE_CFG_SHIFT 1
|
||||
#define OARR_SIZE_CFG BIT(OARR_SIZE_CFG_SHIFT)
|
||||
|
||||
#define OARR_LO(window) (0xd20 + (window) * 8)
|
||||
#define OARR_HI(window) (0xd24 + (window) * 8)
|
||||
#define OMAP_LO(window) (0xd40 + (window) * 8)
|
||||
#define OMAP_HI(window) (0xd44 + (window) * 8)
|
||||
|
||||
#define MAX_NUM_OB_WINDOWS 2
|
||||
#define MAX_NUM_PAXC_PF 4
|
||||
|
||||
#define IPROC_PCIE_REG_INVALID 0xffff
|
||||
|
||||
enum iproc_pcie_reg {
|
||||
IPROC_PCIE_CLK_CTRL = 0,
|
||||
IPROC_PCIE_CFG_IND_ADDR,
|
||||
IPROC_PCIE_CFG_IND_DATA,
|
||||
IPROC_PCIE_CFG_ADDR,
|
||||
IPROC_PCIE_CFG_DATA,
|
||||
IPROC_PCIE_INTX_EN,
|
||||
IPROC_PCIE_OARR_LO,
|
||||
IPROC_PCIE_OARR_HI,
|
||||
IPROC_PCIE_OMAP_LO,
|
||||
IPROC_PCIE_OMAP_HI,
|
||||
IPROC_PCIE_LINK_STATUS,
|
||||
};
|
||||
|
||||
/* iProc PCIe PAXB registers */
|
||||
static const u16 iproc_pcie_reg_paxb[] = {
|
||||
[IPROC_PCIE_CLK_CTRL] = 0x000,
|
||||
[IPROC_PCIE_CFG_IND_ADDR] = 0x120,
|
||||
[IPROC_PCIE_CFG_IND_DATA] = 0x124,
|
||||
[IPROC_PCIE_CFG_ADDR] = 0x1f8,
|
||||
[IPROC_PCIE_CFG_DATA] = 0x1fc,
|
||||
[IPROC_PCIE_INTX_EN] = 0x330,
|
||||
[IPROC_PCIE_OARR_LO] = 0xd20,
|
||||
[IPROC_PCIE_OARR_HI] = 0xd24,
|
||||
[IPROC_PCIE_OMAP_LO] = 0xd40,
|
||||
[IPROC_PCIE_OMAP_HI] = 0xd44,
|
||||
[IPROC_PCIE_LINK_STATUS] = 0xf0c,
|
||||
};
|
||||
|
||||
/* iProc PCIe PAXC v1 registers */
|
||||
static const u16 iproc_pcie_reg_paxc[] = {
|
||||
[IPROC_PCIE_CLK_CTRL] = 0x000,
|
||||
[IPROC_PCIE_CFG_IND_ADDR] = 0x1f0,
|
||||
[IPROC_PCIE_CFG_IND_DATA] = 0x1f4,
|
||||
[IPROC_PCIE_CFG_ADDR] = 0x1f8,
|
||||
[IPROC_PCIE_CFG_DATA] = 0x1fc,
|
||||
[IPROC_PCIE_INTX_EN] = IPROC_PCIE_REG_INVALID,
|
||||
[IPROC_PCIE_OARR_LO] = IPROC_PCIE_REG_INVALID,
|
||||
[IPROC_PCIE_OARR_HI] = IPROC_PCIE_REG_INVALID,
|
||||
[IPROC_PCIE_OMAP_LO] = IPROC_PCIE_REG_INVALID,
|
||||
[IPROC_PCIE_OMAP_HI] = IPROC_PCIE_REG_INVALID,
|
||||
[IPROC_PCIE_LINK_STATUS] = IPROC_PCIE_REG_INVALID,
|
||||
};
|
||||
|
||||
static inline struct iproc_pcie *iproc_data(struct pci_bus *bus)
|
||||
{
|
||||
@ -91,6 +125,65 @@ static inline struct iproc_pcie *iproc_data(struct pci_bus *bus)
|
||||
return pcie;
|
||||
}
|
||||
|
||||
static inline bool iproc_pcie_reg_is_invalid(u16 reg_offset)
|
||||
{
|
||||
return !!(reg_offset == IPROC_PCIE_REG_INVALID);
|
||||
}
|
||||
|
||||
static inline u16 iproc_pcie_reg_offset(struct iproc_pcie *pcie,
|
||||
enum iproc_pcie_reg reg)
|
||||
{
|
||||
return pcie->reg_offsets[reg];
|
||||
}
|
||||
|
||||
static inline u32 iproc_pcie_read_reg(struct iproc_pcie *pcie,
|
||||
enum iproc_pcie_reg reg)
|
||||
{
|
||||
u16 offset = iproc_pcie_reg_offset(pcie, reg);
|
||||
|
||||
if (iproc_pcie_reg_is_invalid(offset))
|
||||
return 0;
|
||||
|
||||
return readl(pcie->base + offset);
|
||||
}
|
||||
|
||||
static inline void iproc_pcie_write_reg(struct iproc_pcie *pcie,
|
||||
enum iproc_pcie_reg reg, u32 val)
|
||||
{
|
||||
u16 offset = iproc_pcie_reg_offset(pcie, reg);
|
||||
|
||||
if (iproc_pcie_reg_is_invalid(offset))
|
||||
return;
|
||||
|
||||
writel(val, pcie->base + offset);
|
||||
}
|
||||
|
||||
static inline void iproc_pcie_ob_write(struct iproc_pcie *pcie,
|
||||
enum iproc_pcie_reg reg,
|
||||
unsigned window, u32 val)
|
||||
{
|
||||
u16 offset = iproc_pcie_reg_offset(pcie, reg);
|
||||
|
||||
if (iproc_pcie_reg_is_invalid(offset))
|
||||
return;
|
||||
|
||||
writel(val, pcie->base + offset + (window * 8));
|
||||
}
|
||||
|
||||
static inline bool iproc_pcie_device_is_valid(struct iproc_pcie *pcie,
|
||||
unsigned int slot,
|
||||
unsigned int fn)
|
||||
{
|
||||
if (slot > 0)
|
||||
return false;
|
||||
|
||||
/* PAXC can only support limited number of functions */
|
||||
if (pcie->type == IPROC_PCIE_PAXC && fn >= MAX_NUM_PAXC_PF)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note access to the configuration registers are protected at the higher layer
|
||||
* by 'pci_lock' in drivers/pci/access.c
|
||||
@ -104,28 +197,34 @@ static void __iomem *iproc_pcie_map_cfg_bus(struct pci_bus *bus,
|
||||
unsigned fn = PCI_FUNC(devfn);
|
||||
unsigned busno = bus->number;
|
||||
u32 val;
|
||||
u16 offset;
|
||||
|
||||
if (!iproc_pcie_device_is_valid(pcie, slot, fn))
|
||||
return NULL;
|
||||
|
||||
/* root complex access */
|
||||
if (busno == 0) {
|
||||
if (slot >= 1)
|
||||
iproc_pcie_write_reg(pcie, IPROC_PCIE_CFG_IND_ADDR,
|
||||
where & CFG_IND_ADDR_MASK);
|
||||
offset = iproc_pcie_reg_offset(pcie, IPROC_PCIE_CFG_IND_DATA);
|
||||
if (iproc_pcie_reg_is_invalid(offset))
|
||||
return NULL;
|
||||
writel(where & CFG_IND_ADDR_MASK,
|
||||
pcie->base + CFG_IND_ADDR_OFFSET);
|
||||
return (pcie->base + CFG_IND_DATA_OFFSET);
|
||||
else
|
||||
return (pcie->base + offset);
|
||||
}
|
||||
|
||||
if (fn > 1)
|
||||
return NULL;
|
||||
|
||||
/* EP device access */
|
||||
val = (busno << CFG_ADDR_BUS_NUM_SHIFT) |
|
||||
(slot << CFG_ADDR_DEV_NUM_SHIFT) |
|
||||
(fn << CFG_ADDR_FUNC_NUM_SHIFT) |
|
||||
(where & CFG_ADDR_REG_NUM_MASK) |
|
||||
(1 & CFG_ADDR_CFG_TYPE_MASK);
|
||||
writel(val, pcie->base + CFG_ADDR_OFFSET);
|
||||
|
||||
return (pcie->base + CFG_DATA_OFFSET);
|
||||
iproc_pcie_write_reg(pcie, IPROC_PCIE_CFG_ADDR, val);
|
||||
offset = iproc_pcie_reg_offset(pcie, IPROC_PCIE_CFG_DATA);
|
||||
if (iproc_pcie_reg_is_invalid(offset))
|
||||
return NULL;
|
||||
else
|
||||
return (pcie->base + offset);
|
||||
}
|
||||
|
||||
static struct pci_ops iproc_pcie_ops = {
|
||||
@ -138,18 +237,29 @@ static void iproc_pcie_reset(struct iproc_pcie *pcie)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (pcie->type == IPROC_PCIE_PAXC) {
|
||||
val = iproc_pcie_read_reg(pcie, IPROC_PCIE_CLK_CTRL);
|
||||
val &= ~PAXC_RESET_MASK;
|
||||
iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
|
||||
udelay(100);
|
||||
val |= PAXC_RESET_MASK;
|
||||
iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
|
||||
udelay(100);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Select perst_b signal as reset source. Put the device into reset,
|
||||
* and then bring it out of reset
|
||||
*/
|
||||
val = readl(pcie->base + CLK_CONTROL_OFFSET);
|
||||
val = iproc_pcie_read_reg(pcie, IPROC_PCIE_CLK_CTRL);
|
||||
val &= ~EP_PERST_SOURCE_SELECT & ~EP_MODE_SURVIVE_PERST &
|
||||
~RC_PCIE_RST_OUTPUT;
|
||||
writel(val, pcie->base + CLK_CONTROL_OFFSET);
|
||||
iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
|
||||
udelay(250);
|
||||
|
||||
val |= RC_PCIE_RST_OUTPUT;
|
||||
writel(val, pcie->base + CLK_CONTROL_OFFSET);
|
||||
iproc_pcie_write_reg(pcie, IPROC_PCIE_CLK_CTRL, val);
|
||||
msleep(100);
|
||||
}
|
||||
|
||||
@ -160,7 +270,14 @@ static int iproc_pcie_check_link(struct iproc_pcie *pcie, struct pci_bus *bus)
|
||||
u16 pos, link_status;
|
||||
bool link_is_active = false;
|
||||
|
||||
val = readl(pcie->base + PCIE_LINK_STATUS_OFFSET);
|
||||
/*
|
||||
* PAXC connects to emulated endpoint devices directly and does not
|
||||
* have a Serdes. Therefore skip the link detection logic here.
|
||||
*/
|
||||
if (pcie->type == IPROC_PCIE_PAXC)
|
||||
return 0;
|
||||
|
||||
val = iproc_pcie_read_reg(pcie, IPROC_PCIE_LINK_STATUS);
|
||||
if (!(val & PCIE_PHYLINKUP) || !(val & PCIE_DL_ACTIVE)) {
|
||||
dev_err(pcie->dev, "PHY or data link is INACTIVE!\n");
|
||||
return -ENODEV;
|
||||
@ -221,7 +338,7 @@ static int iproc_pcie_check_link(struct iproc_pcie *pcie, struct pci_bus *bus)
|
||||
|
||||
static void iproc_pcie_enable(struct iproc_pcie *pcie)
|
||||
{
|
||||
writel(SYS_RC_INTX_MASK, pcie->base + SYS_RC_INTX_EN);
|
||||
iproc_pcie_write_reg(pcie, IPROC_PCIE_INTX_EN, SYS_RC_INTX_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -245,7 +362,7 @@ static int iproc_pcie_setup_ob(struct iproc_pcie *pcie, u64 axi_addr,
|
||||
|
||||
if (size > max_size) {
|
||||
dev_err(pcie->dev,
|
||||
"res size 0x%pap exceeds max supported size 0x%llx\n",
|
||||
"res size %pap exceeds max supported size 0x%llx\n",
|
||||
&size, max_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
@ -272,11 +389,15 @@ static int iproc_pcie_setup_ob(struct iproc_pcie *pcie, u64 axi_addr,
|
||||
axi_addr -= ob->axi_offset;
|
||||
|
||||
for (i = 0; i < MAX_NUM_OB_WINDOWS; i++) {
|
||||
writel(lower_32_bits(axi_addr) | OARR_VALID |
|
||||
(ob->set_oarr_size ? 1 : 0), pcie->base + OARR_LO(i));
|
||||
writel(upper_32_bits(axi_addr), pcie->base + OARR_HI(i));
|
||||
writel(lower_32_bits(pci_addr), pcie->base + OMAP_LO(i));
|
||||
writel(upper_32_bits(pci_addr), pcie->base + OMAP_HI(i));
|
||||
iproc_pcie_ob_write(pcie, IPROC_PCIE_OARR_LO, i,
|
||||
lower_32_bits(axi_addr) | OARR_VALID |
|
||||
(ob->set_oarr_size ? 1 : 0));
|
||||
iproc_pcie_ob_write(pcie, IPROC_PCIE_OARR_HI, i,
|
||||
upper_32_bits(axi_addr));
|
||||
iproc_pcie_ob_write(pcie, IPROC_PCIE_OMAP_LO, i,
|
||||
lower_32_bits(pci_addr));
|
||||
iproc_pcie_ob_write(pcie, IPROC_PCIE_OMAP_HI, i,
|
||||
upper_32_bits(pci_addr));
|
||||
|
||||
size -= ob->window_size;
|
||||
if (size == 0)
|
||||
@ -319,6 +440,26 @@ static int iproc_pcie_map_ranges(struct iproc_pcie *pcie,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int iproc_pcie_msi_enable(struct iproc_pcie *pcie)
|
||||
{
|
||||
struct device_node *msi_node;
|
||||
|
||||
msi_node = of_parse_phandle(pcie->dev->of_node, "msi-parent", 0);
|
||||
if (!msi_node)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* If another MSI controller is being used, the call below should fail
|
||||
* but that is okay
|
||||
*/
|
||||
return iproc_msi_init(pcie, msi_node);
|
||||
}
|
||||
|
||||
static void iproc_pcie_msi_disable(struct iproc_pcie *pcie)
|
||||
{
|
||||
iproc_msi_exit(pcie);
|
||||
}
|
||||
|
||||
int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res)
|
||||
{
|
||||
int ret;
|
||||
@ -340,6 +481,19 @@ int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res)
|
||||
goto err_exit_phy;
|
||||
}
|
||||
|
||||
switch (pcie->type) {
|
||||
case IPROC_PCIE_PAXB:
|
||||
pcie->reg_offsets = iproc_pcie_reg_paxb;
|
||||
break;
|
||||
case IPROC_PCIE_PAXC:
|
||||
pcie->reg_offsets = iproc_pcie_reg_paxc;
|
||||
break;
|
||||
default:
|
||||
dev_err(pcie->dev, "incompatible iProc PCIe interface\n");
|
||||
ret = -EINVAL;
|
||||
goto err_power_off_phy;
|
||||
}
|
||||
|
||||
iproc_pcie_reset(pcie);
|
||||
|
||||
if (pcie->need_ob_cfg) {
|
||||
@ -373,6 +527,10 @@ int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res)
|
||||
|
||||
iproc_pcie_enable(pcie);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PCI_MSI))
|
||||
if (iproc_pcie_msi_enable(pcie))
|
||||
dev_info(pcie->dev, "not using iProc MSI\n");
|
||||
|
||||
pci_scan_child_bus(bus);
|
||||
pci_assign_unassigned_bus_resources(bus);
|
||||
pci_fixup_irqs(pci_common_swizzle, pcie->map_irq);
|
||||
@ -397,6 +555,8 @@ int iproc_pcie_remove(struct iproc_pcie *pcie)
|
||||
pci_stop_root_bus(pcie->root_bus);
|
||||
pci_remove_root_bus(pcie->root_bus);
|
||||
|
||||
iproc_pcie_msi_disable(pcie);
|
||||
|
||||
phy_power_off(pcie->phy);
|
||||
phy_exit(pcie->phy);
|
||||
|
||||
|
@ -14,6 +14,20 @@
|
||||
#ifndef _PCIE_IPROC_H
|
||||
#define _PCIE_IPROC_H
|
||||
|
||||
/**
|
||||
* iProc PCIe interface type
|
||||
*
|
||||
* PAXB is the wrapper used in root complex that can be connected to an
|
||||
* external endpoint device.
|
||||
*
|
||||
* PAXC is the wrapper used in root complex dedicated for internal emulated
|
||||
* endpoint devices.
|
||||
*/
|
||||
enum iproc_pcie_type {
|
||||
IPROC_PCIE_PAXB = 0,
|
||||
IPROC_PCIE_PAXC,
|
||||
};
|
||||
|
||||
/**
|
||||
* iProc PCIe outbound mapping
|
||||
* @set_oarr_size: indicates the OARR size bit needs to be set
|
||||
@ -27,21 +41,30 @@ struct iproc_pcie_ob {
|
||||
resource_size_t window_size;
|
||||
};
|
||||
|
||||
struct iproc_msi;
|
||||
|
||||
/**
|
||||
* iProc PCIe device
|
||||
*
|
||||
* @dev: pointer to device data structure
|
||||
* @type: iProc PCIe interface type
|
||||
* @reg_offsets: register offsets
|
||||
* @base: PCIe host controller I/O register base
|
||||
* @base_addr: PCIe host controller register base physical address
|
||||
* @sysdata: Per PCI controller data (ARM-specific)
|
||||
* @root_bus: pointer to root bus
|
||||
* @phy: optional PHY device that controls the Serdes
|
||||
* @irqs: interrupt IDs
|
||||
* @map_irq: function callback to map interrupts
|
||||
* @need_ob_cfg: indidates SW needs to configure the outbound mapping window
|
||||
* @need_ob_cfg: indicates SW needs to configure the outbound mapping window
|
||||
* @ob: outbound mapping parameters
|
||||
* @msi: MSI data
|
||||
*/
|
||||
struct iproc_pcie {
|
||||
struct device *dev;
|
||||
enum iproc_pcie_type type;
|
||||
const u16 *reg_offsets;
|
||||
void __iomem *base;
|
||||
phys_addr_t base_addr;
|
||||
#ifdef CONFIG_ARM
|
||||
struct pci_sys_data sysdata;
|
||||
#endif
|
||||
@ -50,9 +73,24 @@ struct iproc_pcie {
|
||||
int (*map_irq)(const struct pci_dev *, u8, u8);
|
||||
bool need_ob_cfg;
|
||||
struct iproc_pcie_ob ob;
|
||||
struct iproc_msi *msi;
|
||||
};
|
||||
|
||||
int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res);
|
||||
int iproc_pcie_remove(struct iproc_pcie *pcie);
|
||||
|
||||
#ifdef CONFIG_PCIE_IPROC_MSI
|
||||
int iproc_msi_init(struct iproc_pcie *pcie, struct device_node *node);
|
||||
void iproc_msi_exit(struct iproc_pcie *pcie);
|
||||
#else
|
||||
static inline int iproc_msi_init(struct iproc_pcie *pcie,
|
||||
struct device_node *node)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
static inline void iproc_msi_exit(struct iproc_pcie *pcie)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _PCIE_IPROC_H */
|
||||
|
616
drivers/pci/host/pcie-qcom.c
Normal file
616
drivers/pci/host/pcie-qcom.c
Normal file
@ -0,0 +1,616 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
|
||||
* Copyright 2015 Linaro Limited.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "pcie-designware.h"
|
||||
|
||||
#define PCIE20_PARF_PHY_CTRL 0x40
|
||||
#define PCIE20_PARF_PHY_REFCLK 0x4C
|
||||
#define PCIE20_PARF_DBI_BASE_ADDR 0x168
|
||||
#define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c
|
||||
#define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178
|
||||
|
||||
#define PCIE20_ELBI_SYS_CTRL 0x04
|
||||
#define PCIE20_ELBI_SYS_CTRL_LT_ENABLE BIT(0)
|
||||
|
||||
#define PCIE20_CAP 0x70
|
||||
|
||||
#define PERST_DELAY_US 1000
|
||||
|
||||
struct qcom_pcie_resources_v0 {
|
||||
struct clk *iface_clk;
|
||||
struct clk *core_clk;
|
||||
struct clk *phy_clk;
|
||||
struct reset_control *pci_reset;
|
||||
struct reset_control *axi_reset;
|
||||
struct reset_control *ahb_reset;
|
||||
struct reset_control *por_reset;
|
||||
struct reset_control *phy_reset;
|
||||
struct regulator *vdda;
|
||||
struct regulator *vdda_phy;
|
||||
struct regulator *vdda_refclk;
|
||||
};
|
||||
|
||||
struct qcom_pcie_resources_v1 {
|
||||
struct clk *iface;
|
||||
struct clk *aux;
|
||||
struct clk *master_bus;
|
||||
struct clk *slave_bus;
|
||||
struct reset_control *core;
|
||||
struct regulator *vdda;
|
||||
};
|
||||
|
||||
union qcom_pcie_resources {
|
||||
struct qcom_pcie_resources_v0 v0;
|
||||
struct qcom_pcie_resources_v1 v1;
|
||||
};
|
||||
|
||||
struct qcom_pcie;
|
||||
|
||||
struct qcom_pcie_ops {
|
||||
int (*get_resources)(struct qcom_pcie *pcie);
|
||||
int (*init)(struct qcom_pcie *pcie);
|
||||
void (*deinit)(struct qcom_pcie *pcie);
|
||||
};
|
||||
|
||||
struct qcom_pcie {
|
||||
struct pcie_port pp;
|
||||
struct device *dev;
|
||||
union qcom_pcie_resources res;
|
||||
void __iomem *parf;
|
||||
void __iomem *dbi;
|
||||
void __iomem *elbi;
|
||||
struct phy *phy;
|
||||
struct gpio_desc *reset;
|
||||
struct qcom_pcie_ops *ops;
|
||||
};
|
||||
|
||||
#define to_qcom_pcie(x) container_of(x, struct qcom_pcie, pp)
|
||||
|
||||
static void qcom_ep_reset_assert(struct qcom_pcie *pcie)
|
||||
{
|
||||
gpiod_set_value(pcie->reset, 1);
|
||||
usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500);
|
||||
}
|
||||
|
||||
static void qcom_ep_reset_deassert(struct qcom_pcie *pcie)
|
||||
{
|
||||
gpiod_set_value(pcie->reset, 0);
|
||||
usleep_range(PERST_DELAY_US, PERST_DELAY_US + 500);
|
||||
}
|
||||
|
||||
static irqreturn_t qcom_pcie_msi_irq_handler(int irq, void *arg)
|
||||
{
|
||||
struct pcie_port *pp = arg;
|
||||
|
||||
return dw_handle_msi_irq(pp);
|
||||
}
|
||||
|
||||
static int qcom_pcie_establish_link(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct device *dev = pcie->dev;
|
||||
unsigned int retries = 0;
|
||||
u32 val;
|
||||
|
||||
if (dw_pcie_link_up(&pcie->pp))
|
||||
return 0;
|
||||
|
||||
/* enable link training */
|
||||
val = readl(pcie->elbi + PCIE20_ELBI_SYS_CTRL);
|
||||
val |= PCIE20_ELBI_SYS_CTRL_LT_ENABLE;
|
||||
writel(val, pcie->elbi + PCIE20_ELBI_SYS_CTRL);
|
||||
|
||||
do {
|
||||
if (dw_pcie_link_up(&pcie->pp))
|
||||
return 0;
|
||||
usleep_range(250, 1000);
|
||||
} while (retries < 200);
|
||||
|
||||
dev_warn(dev, "phy link never came up\n");
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int qcom_pcie_get_resources_v0(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
|
||||
struct device *dev = pcie->dev;
|
||||
|
||||
res->vdda = devm_regulator_get(dev, "vdda");
|
||||
if (IS_ERR(res->vdda))
|
||||
return PTR_ERR(res->vdda);
|
||||
|
||||
res->vdda_phy = devm_regulator_get(dev, "vdda_phy");
|
||||
if (IS_ERR(res->vdda_phy))
|
||||
return PTR_ERR(res->vdda_phy);
|
||||
|
||||
res->vdda_refclk = devm_regulator_get(dev, "vdda_refclk");
|
||||
if (IS_ERR(res->vdda_refclk))
|
||||
return PTR_ERR(res->vdda_refclk);
|
||||
|
||||
res->iface_clk = devm_clk_get(dev, "iface");
|
||||
if (IS_ERR(res->iface_clk))
|
||||
return PTR_ERR(res->iface_clk);
|
||||
|
||||
res->core_clk = devm_clk_get(dev, "core");
|
||||
if (IS_ERR(res->core_clk))
|
||||
return PTR_ERR(res->core_clk);
|
||||
|
||||
res->phy_clk = devm_clk_get(dev, "phy");
|
||||
if (IS_ERR(res->phy_clk))
|
||||
return PTR_ERR(res->phy_clk);
|
||||
|
||||
res->pci_reset = devm_reset_control_get(dev, "pci");
|
||||
if (IS_ERR(res->pci_reset))
|
||||
return PTR_ERR(res->pci_reset);
|
||||
|
||||
res->axi_reset = devm_reset_control_get(dev, "axi");
|
||||
if (IS_ERR(res->axi_reset))
|
||||
return PTR_ERR(res->axi_reset);
|
||||
|
||||
res->ahb_reset = devm_reset_control_get(dev, "ahb");
|
||||
if (IS_ERR(res->ahb_reset))
|
||||
return PTR_ERR(res->ahb_reset);
|
||||
|
||||
res->por_reset = devm_reset_control_get(dev, "por");
|
||||
if (IS_ERR(res->por_reset))
|
||||
return PTR_ERR(res->por_reset);
|
||||
|
||||
res->phy_reset = devm_reset_control_get(dev, "phy");
|
||||
if (IS_ERR(res->phy_reset))
|
||||
return PTR_ERR(res->phy_reset);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_pcie_get_resources_v1(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
|
||||
struct device *dev = pcie->dev;
|
||||
|
||||
res->vdda = devm_regulator_get(dev, "vdda");
|
||||
if (IS_ERR(res->vdda))
|
||||
return PTR_ERR(res->vdda);
|
||||
|
||||
res->iface = devm_clk_get(dev, "iface");
|
||||
if (IS_ERR(res->iface))
|
||||
return PTR_ERR(res->iface);
|
||||
|
||||
res->aux = devm_clk_get(dev, "aux");
|
||||
if (IS_ERR(res->aux))
|
||||
return PTR_ERR(res->aux);
|
||||
|
||||
res->master_bus = devm_clk_get(dev, "master_bus");
|
||||
if (IS_ERR(res->master_bus))
|
||||
return PTR_ERR(res->master_bus);
|
||||
|
||||
res->slave_bus = devm_clk_get(dev, "slave_bus");
|
||||
if (IS_ERR(res->slave_bus))
|
||||
return PTR_ERR(res->slave_bus);
|
||||
|
||||
res->core = devm_reset_control_get(dev, "core");
|
||||
if (IS_ERR(res->core))
|
||||
return PTR_ERR(res->core);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qcom_pcie_deinit_v0(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
|
||||
|
||||
reset_control_assert(res->pci_reset);
|
||||
reset_control_assert(res->axi_reset);
|
||||
reset_control_assert(res->ahb_reset);
|
||||
reset_control_assert(res->por_reset);
|
||||
reset_control_assert(res->pci_reset);
|
||||
clk_disable_unprepare(res->iface_clk);
|
||||
clk_disable_unprepare(res->core_clk);
|
||||
clk_disable_unprepare(res->phy_clk);
|
||||
regulator_disable(res->vdda);
|
||||
regulator_disable(res->vdda_phy);
|
||||
regulator_disable(res->vdda_refclk);
|
||||
}
|
||||
|
||||
static int qcom_pcie_init_v0(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
|
||||
struct device *dev = pcie->dev;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = regulator_enable(res->vdda);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot enable vdda regulator\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regulator_enable(res->vdda_refclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot enable vdda_refclk regulator\n");
|
||||
goto err_refclk;
|
||||
}
|
||||
|
||||
ret = regulator_enable(res->vdda_phy);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot enable vdda_phy regulator\n");
|
||||
goto err_vdda_phy;
|
||||
}
|
||||
|
||||
ret = reset_control_assert(res->ahb_reset);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot assert ahb reset\n");
|
||||
goto err_assert_ahb;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(res->iface_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot prepare/enable iface clock\n");
|
||||
goto err_assert_ahb;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(res->phy_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot prepare/enable phy clock\n");
|
||||
goto err_clk_phy;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(res->core_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot prepare/enable core clock\n");
|
||||
goto err_clk_core;
|
||||
}
|
||||
|
||||
ret = reset_control_deassert(res->ahb_reset);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot deassert ahb reset\n");
|
||||
goto err_deassert_ahb;
|
||||
}
|
||||
|
||||
/* enable PCIe clocks and resets */
|
||||
val = readl(pcie->parf + PCIE20_PARF_PHY_CTRL);
|
||||
val &= ~BIT(0);
|
||||
writel(val, pcie->parf + PCIE20_PARF_PHY_CTRL);
|
||||
|
||||
/* enable external reference clock */
|
||||
val = readl(pcie->parf + PCIE20_PARF_PHY_REFCLK);
|
||||
val |= BIT(16);
|
||||
writel(val, pcie->parf + PCIE20_PARF_PHY_REFCLK);
|
||||
|
||||
ret = reset_control_deassert(res->phy_reset);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot deassert phy reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_control_deassert(res->pci_reset);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot deassert pci reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_control_deassert(res->por_reset);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot deassert por reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_control_deassert(res->axi_reset);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot deassert axi reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* wait for clock acquisition */
|
||||
usleep_range(1000, 1500);
|
||||
|
||||
return 0;
|
||||
|
||||
err_deassert_ahb:
|
||||
clk_disable_unprepare(res->core_clk);
|
||||
err_clk_core:
|
||||
clk_disable_unprepare(res->phy_clk);
|
||||
err_clk_phy:
|
||||
clk_disable_unprepare(res->iface_clk);
|
||||
err_assert_ahb:
|
||||
regulator_disable(res->vdda_phy);
|
||||
err_vdda_phy:
|
||||
regulator_disable(res->vdda_refclk);
|
||||
err_refclk:
|
||||
regulator_disable(res->vdda);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void qcom_pcie_deinit_v1(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
|
||||
|
||||
reset_control_assert(res->core);
|
||||
clk_disable_unprepare(res->slave_bus);
|
||||
clk_disable_unprepare(res->master_bus);
|
||||
clk_disable_unprepare(res->iface);
|
||||
clk_disable_unprepare(res->aux);
|
||||
regulator_disable(res->vdda);
|
||||
}
|
||||
|
||||
static int qcom_pcie_init_v1(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
|
||||
struct device *dev = pcie->dev;
|
||||
int ret;
|
||||
|
||||
ret = reset_control_deassert(res->core);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot deassert core reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(res->aux);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot prepare/enable aux clock\n");
|
||||
goto err_res;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(res->iface);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot prepare/enable iface clock\n");
|
||||
goto err_aux;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(res->master_bus);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot prepare/enable master_bus clock\n");
|
||||
goto err_iface;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(res->slave_bus);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot prepare/enable slave_bus clock\n");
|
||||
goto err_master;
|
||||
}
|
||||
|
||||
ret = regulator_enable(res->vdda);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot enable vdda regulator\n");
|
||||
goto err_slave;
|
||||
}
|
||||
|
||||
/* change DBI base address */
|
||||
writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PCI_MSI)) {
|
||||
u32 val = readl(pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT);
|
||||
|
||||
val |= BIT(31);
|
||||
writel(val, pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT);
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_slave:
|
||||
clk_disable_unprepare(res->slave_bus);
|
||||
err_master:
|
||||
clk_disable_unprepare(res->master_bus);
|
||||
err_iface:
|
||||
clk_disable_unprepare(res->iface);
|
||||
err_aux:
|
||||
clk_disable_unprepare(res->aux);
|
||||
err_res:
|
||||
reset_control_assert(res->core);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qcom_pcie_link_up(struct pcie_port *pp)
|
||||
{
|
||||
struct qcom_pcie *pcie = to_qcom_pcie(pp);
|
||||
u16 val = readw(pcie->dbi + PCIE20_CAP + PCI_EXP_LNKSTA);
|
||||
|
||||
return !!(val & PCI_EXP_LNKSTA_DLLLA);
|
||||
}
|
||||
|
||||
static void qcom_pcie_host_init(struct pcie_port *pp)
|
||||
{
|
||||
struct qcom_pcie *pcie = to_qcom_pcie(pp);
|
||||
int ret;
|
||||
|
||||
qcom_ep_reset_assert(pcie);
|
||||
|
||||
ret = pcie->ops->init(pcie);
|
||||
if (ret)
|
||||
goto err_deinit;
|
||||
|
||||
ret = phy_power_on(pcie->phy);
|
||||
if (ret)
|
||||
goto err_deinit;
|
||||
|
||||
dw_pcie_setup_rc(pp);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PCI_MSI))
|
||||
dw_pcie_msi_init(pp);
|
||||
|
||||
qcom_ep_reset_deassert(pcie);
|
||||
|
||||
ret = qcom_pcie_establish_link(pcie);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
return;
|
||||
err:
|
||||
qcom_ep_reset_assert(pcie);
|
||||
phy_power_off(pcie->phy);
|
||||
err_deinit:
|
||||
pcie->ops->deinit(pcie);
|
||||
}
|
||||
|
||||
static int qcom_pcie_rd_own_conf(struct pcie_port *pp, int where, int size,
|
||||
u32 *val)
|
||||
{
|
||||
/* the device class is not reported correctly from the register */
|
||||
if (where == PCI_CLASS_REVISION && size == 4) {
|
||||
*val = readl(pp->dbi_base + PCI_CLASS_REVISION);
|
||||
*val &= 0xff; /* keep revision id */
|
||||
*val |= PCI_CLASS_BRIDGE_PCI << 16;
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
return dw_pcie_cfg_read(pp->dbi_base + where, size, val);
|
||||
}
|
||||
|
||||
static struct pcie_host_ops qcom_pcie_dw_ops = {
|
||||
.link_up = qcom_pcie_link_up,
|
||||
.host_init = qcom_pcie_host_init,
|
||||
.rd_own_conf = qcom_pcie_rd_own_conf,
|
||||
};
|
||||
|
||||
static const struct qcom_pcie_ops ops_v0 = {
|
||||
.get_resources = qcom_pcie_get_resources_v0,
|
||||
.init = qcom_pcie_init_v0,
|
||||
.deinit = qcom_pcie_deinit_v0,
|
||||
};
|
||||
|
||||
static const struct qcom_pcie_ops ops_v1 = {
|
||||
.get_resources = qcom_pcie_get_resources_v1,
|
||||
.init = qcom_pcie_init_v1,
|
||||
.deinit = qcom_pcie_deinit_v1,
|
||||
};
|
||||
|
||||
static int qcom_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct qcom_pcie *pcie;
|
||||
struct pcie_port *pp;
|
||||
int ret;
|
||||
|
||||
pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
|
||||
if (!pcie)
|
||||
return -ENOMEM;
|
||||
|
||||
pcie->ops = (struct qcom_pcie_ops *)of_device_get_match_data(dev);
|
||||
pcie->dev = dev;
|
||||
|
||||
pcie->reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(pcie->reset))
|
||||
return PTR_ERR(pcie->reset);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf");
|
||||
pcie->parf = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(pcie->parf))
|
||||
return PTR_ERR(pcie->parf);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
|
||||
pcie->dbi = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(pcie->dbi))
|
||||
return PTR_ERR(pcie->dbi);
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi");
|
||||
pcie->elbi = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(pcie->elbi))
|
||||
return PTR_ERR(pcie->elbi);
|
||||
|
||||
pcie->phy = devm_phy_optional_get(dev, "pciephy");
|
||||
if (IS_ERR(pcie->phy))
|
||||
return PTR_ERR(pcie->phy);
|
||||
|
||||
ret = pcie->ops->get_resources(pcie);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pp = &pcie->pp;
|
||||
pp->dev = dev;
|
||||
pp->dbi_base = pcie->dbi;
|
||||
pp->root_bus_nr = -1;
|
||||
pp->ops = &qcom_pcie_dw_ops;
|
||||
|
||||
if (IS_ENABLED(CONFIG_PCI_MSI)) {
|
||||
pp->msi_irq = platform_get_irq_byname(pdev, "msi");
|
||||
if (pp->msi_irq < 0)
|
||||
return pp->msi_irq;
|
||||
|
||||
ret = devm_request_irq(dev, pp->msi_irq,
|
||||
qcom_pcie_msi_irq_handler,
|
||||
IRQF_SHARED, "qcom-pcie-msi", pp);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot request msi irq\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = phy_init(pcie->phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = dw_pcie_host_init(pp);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot initialize host\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, pcie);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qcom_pcie_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct qcom_pcie *pcie = platform_get_drvdata(pdev);
|
||||
|
||||
qcom_ep_reset_assert(pcie);
|
||||
phy_power_off(pcie->phy);
|
||||
phy_exit(pcie->phy);
|
||||
pcie->ops->deinit(pcie);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id qcom_pcie_match[] = {
|
||||
{ .compatible = "qcom,pcie-ipq8064", .data = &ops_v0 },
|
||||
{ .compatible = "qcom,pcie-apq8064", .data = &ops_v0 },
|
||||
{ .compatible = "qcom,pcie-apq8084", .data = &ops_v1 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, qcom_pcie_match);
|
||||
|
||||
static struct platform_driver qcom_pcie_driver = {
|
||||
.probe = qcom_pcie_probe,
|
||||
.remove = qcom_pcie_remove,
|
||||
.driver = {
|
||||
.name = "qcom-pcie",
|
||||
.of_match_table = qcom_pcie_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(qcom_pcie_driver);
|
||||
|
||||
MODULE_AUTHOR("Stanimir Varbanov <svarbanov@mm-sol.com>");
|
||||
MODULE_DESCRIPTION("Qualcomm PCIe root complex driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -26,6 +26,7 @@
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define DRV_NAME "rcar-pcie"
|
||||
@ -94,6 +95,11 @@
|
||||
#define H1_PCIEPHYDOUTR 0x040014
|
||||
#define H1_PCIEPHYSR 0x040018
|
||||
|
||||
/* R-Car Gen2 PHY */
|
||||
#define GEN2_PCIEPHYADDR 0x780
|
||||
#define GEN2_PCIEPHYDATA 0x784
|
||||
#define GEN2_PCIEPHYCTRL 0x78c
|
||||
|
||||
#define INT_PCI_MSI_NR 32
|
||||
|
||||
#define RCONF(x) (PCICONF(0)+(x))
|
||||
@ -108,8 +114,6 @@
|
||||
#define RCAR_PCI_MAX_RESOURCES 4
|
||||
#define MAX_NR_INBOUND_MAPS 6
|
||||
|
||||
static unsigned long global_io_offset;
|
||||
|
||||
struct rcar_msi {
|
||||
DECLARE_BITMAP(used, INT_PCI_MSI_NR);
|
||||
struct irq_domain *domain;
|
||||
@ -126,20 +130,10 @@ static inline struct rcar_msi *to_rcar_msi(struct msi_controller *chip)
|
||||
}
|
||||
|
||||
/* Structure representing the PCIe interface */
|
||||
/*
|
||||
* ARM pcibios functions expect the ARM struct pci_sys_data as the PCI
|
||||
* sysdata. Add pci_sys_data as the first element in struct gen_pci so
|
||||
* that when we use a gen_pci pointer as sysdata, it is also a pointer to
|
||||
* a struct pci_sys_data.
|
||||
*/
|
||||
struct rcar_pcie {
|
||||
#ifdef CONFIG_ARM
|
||||
struct pci_sys_data sys;
|
||||
#endif
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
struct resource res[RCAR_PCI_MAX_RESOURCES];
|
||||
struct resource busn;
|
||||
struct list_head resources;
|
||||
int root_bus_nr;
|
||||
struct clk *clk;
|
||||
struct clk *bus_clk;
|
||||
@ -323,10 +317,9 @@ static struct pci_ops rcar_pcie_ops = {
|
||||
.write = rcar_pcie_write_conf,
|
||||
};
|
||||
|
||||
static void rcar_pcie_setup_window(int win, struct rcar_pcie *pcie)
|
||||
static void rcar_pcie_setup_window(int win, struct rcar_pcie *pcie,
|
||||
struct resource *res)
|
||||
{
|
||||
struct resource *res = &pcie->res[win];
|
||||
|
||||
/* Setup PCIe address space mappings for each resource */
|
||||
resource_size_t size;
|
||||
resource_size_t res_start;
|
||||
@ -359,31 +352,33 @@ static void rcar_pcie_setup_window(int win, struct rcar_pcie *pcie)
|
||||
rcar_pci_write_reg(pcie, mask, PCIEPTCTLR(win));
|
||||
}
|
||||
|
||||
static int rcar_pcie_setup(struct list_head *resource, struct rcar_pcie *pcie)
|
||||
static int rcar_pcie_setup(struct list_head *resource, struct rcar_pcie *pci)
|
||||
{
|
||||
struct resource *res;
|
||||
int i;
|
||||
|
||||
pcie->root_bus_nr = pcie->busn.start;
|
||||
struct resource_entry *win;
|
||||
int i = 0;
|
||||
|
||||
/* Setup PCI resources */
|
||||
for (i = 0; i < RCAR_PCI_MAX_RESOURCES; i++) {
|
||||
resource_list_for_each_entry(win, &pci->resources) {
|
||||
struct resource *res = win->res;
|
||||
|
||||
res = &pcie->res[i];
|
||||
if (!res->flags)
|
||||
continue;
|
||||
|
||||
rcar_pcie_setup_window(i, pcie);
|
||||
|
||||
if (res->flags & IORESOURCE_IO) {
|
||||
phys_addr_t io_start = pci_pio_to_address(res->start);
|
||||
pci_ioremap_io(global_io_offset, io_start);
|
||||
global_io_offset += SZ_64K;
|
||||
switch (resource_type(res)) {
|
||||
case IORESOURCE_IO:
|
||||
case IORESOURCE_MEM:
|
||||
rcar_pcie_setup_window(i, pci, res);
|
||||
i++;
|
||||
break;
|
||||
case IORESOURCE_BUS:
|
||||
pci->root_bus_nr = res->start;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
pci_add_resource(resource, res);
|
||||
}
|
||||
pci_add_resource(resource, &pcie->busn);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -578,6 +573,26 @@ static int rcar_pcie_hw_init_h1(struct rcar_pcie *pcie)
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int rcar_pcie_hw_init_gen2(struct rcar_pcie *pcie)
|
||||
{
|
||||
/*
|
||||
* These settings come from the R-Car Series, 2nd Generation User's
|
||||
* Manual, section 50.3.1 (2) Initialization of the physical layer.
|
||||
*/
|
||||
rcar_pci_write_reg(pcie, 0x000f0030, GEN2_PCIEPHYADDR);
|
||||
rcar_pci_write_reg(pcie, 0x00381203, GEN2_PCIEPHYDATA);
|
||||
rcar_pci_write_reg(pcie, 0x00000001, GEN2_PCIEPHYCTRL);
|
||||
rcar_pci_write_reg(pcie, 0x00000006, GEN2_PCIEPHYCTRL);
|
||||
|
||||
rcar_pci_write_reg(pcie, 0x000f0054, GEN2_PCIEPHYADDR);
|
||||
/* The following value is for DC connection, no termination resistor */
|
||||
rcar_pci_write_reg(pcie, 0x13802007, GEN2_PCIEPHYDATA);
|
||||
rcar_pci_write_reg(pcie, 0x00000001, GEN2_PCIEPHYCTRL);
|
||||
rcar_pci_write_reg(pcie, 0x00000006, GEN2_PCIEPHYCTRL);
|
||||
|
||||
return rcar_pcie_hw_init(pcie);
|
||||
}
|
||||
|
||||
static int rcar_msi_alloc(struct rcar_msi *chip)
|
||||
{
|
||||
int msi;
|
||||
@ -720,14 +735,16 @@ static int rcar_pcie_enable_msi(struct rcar_pcie *pcie)
|
||||
|
||||
/* Two irqs are for MSI, but they are also used for non-MSI irqs */
|
||||
err = devm_request_irq(&pdev->dev, msi->irq1, rcar_pcie_msi_irq,
|
||||
IRQF_SHARED, rcar_msi_irq_chip.name, pcie);
|
||||
IRQF_SHARED | IRQF_NO_THREAD,
|
||||
rcar_msi_irq_chip.name, pcie);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
|
||||
goto err;
|
||||
}
|
||||
|
||||
err = devm_request_irq(&pdev->dev, msi->irq2, rcar_pcie_msi_irq,
|
||||
IRQF_SHARED, rcar_msi_irq_chip.name, pcie);
|
||||
IRQF_SHARED | IRQF_NO_THREAD,
|
||||
rcar_msi_irq_chip.name, pcie);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to request IRQ: %d\n", err);
|
||||
goto err;
|
||||
@ -917,20 +934,71 @@ static int rcar_pcie_parse_map_dma_ranges(struct rcar_pcie *pcie,
|
||||
|
||||
static const struct of_device_id rcar_pcie_of_match[] = {
|
||||
{ .compatible = "renesas,pcie-r8a7779", .data = rcar_pcie_hw_init_h1 },
|
||||
{ .compatible = "renesas,pcie-r8a7790", .data = rcar_pcie_hw_init },
|
||||
{ .compatible = "renesas,pcie-r8a7791", .data = rcar_pcie_hw_init },
|
||||
{ .compatible = "renesas,pcie-rcar-gen2", .data = rcar_pcie_hw_init_gen2 },
|
||||
{ .compatible = "renesas,pcie-r8a7790", .data = rcar_pcie_hw_init_gen2 },
|
||||
{ .compatible = "renesas,pcie-r8a7791", .data = rcar_pcie_hw_init_gen2 },
|
||||
{ .compatible = "renesas,pcie-r8a7795", .data = rcar_pcie_hw_init },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rcar_pcie_of_match);
|
||||
|
||||
static void rcar_pcie_release_of_pci_ranges(struct rcar_pcie *pci)
|
||||
{
|
||||
pci_free_resource_list(&pci->resources);
|
||||
}
|
||||
|
||||
static int rcar_pcie_parse_request_of_pci_ranges(struct rcar_pcie *pci)
|
||||
{
|
||||
int err;
|
||||
struct device *dev = pci->dev;
|
||||
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;
|
||||
break;
|
||||
|
||||
case IORESOURCE_BUS:
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
err = devm_request_resource(dev, parent, res);
|
||||
if (err)
|
||||
goto out_release_res;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_release_res:
|
||||
rcar_pcie_release_of_pci_ranges(pci);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int rcar_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rcar_pcie *pcie;
|
||||
unsigned int data;
|
||||
struct of_pci_range range;
|
||||
struct of_pci_range_parser parser;
|
||||
const struct of_device_id *of_id;
|
||||
int err, win = 0;
|
||||
int err;
|
||||
int (*hw_init_fn)(struct rcar_pcie *);
|
||||
|
||||
pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL);
|
||||
@ -940,16 +1008,9 @@ static int rcar_pcie_probe(struct platform_device *pdev)
|
||||
pcie->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, pcie);
|
||||
|
||||
/* Get the bus range */
|
||||
if (of_pci_parse_bus_range(pdev->dev.of_node, &pcie->busn)) {
|
||||
dev_err(&pdev->dev, "failed to parse bus-range property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
INIT_LIST_HEAD(&pcie->resources);
|
||||
|
||||
if (of_pci_range_parser_init(&parser, pdev->dev.of_node)) {
|
||||
dev_err(&pdev->dev, "missing ranges property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
rcar_pcie_parse_request_of_pci_ranges(pcie);
|
||||
|
||||
err = rcar_pcie_get_resources(pdev, pcie);
|
||||
if (err < 0) {
|
||||
@ -957,46 +1018,55 @@ static int rcar_pcie_probe(struct platform_device *pdev)
|
||||
return err;
|
||||
}
|
||||
|
||||
for_each_of_pci_range(&parser, &range) {
|
||||
err = of_pci_range_to_resource(&range, pdev->dev.of_node,
|
||||
&pcie->res[win++]);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (win > RCAR_PCI_MAX_RESOURCES)
|
||||
break;
|
||||
}
|
||||
|
||||
err = rcar_pcie_parse_map_dma_ranges(pcie, pdev->dev.of_node);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
of_id = of_match_device(rcar_pcie_of_match, pcie->dev);
|
||||
if (!of_id || !of_id->data)
|
||||
return -EINVAL;
|
||||
hw_init_fn = of_id->data;
|
||||
|
||||
pm_runtime_enable(pcie->dev);
|
||||
err = pm_runtime_get_sync(pcie->dev);
|
||||
if (err < 0) {
|
||||
dev_err(pcie->dev, "pm_runtime_get_sync failed\n");
|
||||
goto err_pm_disable;
|
||||
}
|
||||
|
||||
/* Failure to get a link might just be that no cards are inserted */
|
||||
err = hw_init_fn(pcie);
|
||||
if (err) {
|
||||
dev_info(&pdev->dev, "PCIe link down\n");
|
||||
err = 0;
|
||||
goto err_pm_put;
|
||||
}
|
||||
|
||||
data = rcar_pci_read_reg(pcie, MACSR);
|
||||
dev_info(&pdev->dev, "PCIe x%d: link up\n", (data >> 20) & 0x3f);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PCI_MSI)) {
|
||||
err = rcar_pcie_enable_msi(pcie);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to enable MSI support: %d\n",
|
||||
err);
|
||||
return err;
|
||||
goto err_pm_put;
|
||||
}
|
||||
}
|
||||
|
||||
of_id = of_match_device(rcar_pcie_of_match, pcie->dev);
|
||||
if (!of_id || !of_id->data)
|
||||
return -EINVAL;
|
||||
hw_init_fn = of_id->data;
|
||||
err = rcar_pcie_enable(pcie);
|
||||
if (err)
|
||||
goto err_pm_put;
|
||||
|
||||
/* Failure to get a link might just be that no cards are inserted */
|
||||
err = hw_init_fn(pcie);
|
||||
if (err) {
|
||||
dev_info(&pdev->dev, "PCIe link down\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
data = rcar_pci_read_reg(pcie, MACSR);
|
||||
dev_info(&pdev->dev, "PCIe x%d: link up\n", (data >> 20) & 0x3f);
|
||||
err_pm_put:
|
||||
pm_runtime_put(pcie->dev);
|
||||
|
||||
return rcar_pcie_enable(pcie);
|
||||
err_pm_disable:
|
||||
pm_runtime_disable(pcie->dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct platform_driver rcar_pcie_driver = {
|
||||
|
@ -279,7 +279,8 @@ static int spear13xx_add_pcie_port(struct pcie_port *pp,
|
||||
return -ENODEV;
|
||||
}
|
||||
ret = devm_request_irq(dev, pp->irq, spear13xx_pcie_irq_handler,
|
||||
IRQF_SHARED, "spear1340-pcie", pp);
|
||||
IRQF_SHARED | IRQF_NO_THREAD,
|
||||
"spear1340-pcie", pp);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request irq %d\n", pp->irq);
|
||||
return ret;
|
||||
|
@ -781,7 +781,8 @@ static int xilinx_pcie_parse_dt(struct xilinx_pcie_port *port)
|
||||
|
||||
port->irq = irq_of_parse_and_map(node, 0);
|
||||
err = devm_request_irq(dev, port->irq, xilinx_pcie_intr_handler,
|
||||
IRQF_SHARED, "xilinx-pcie", port);
|
||||
IRQF_SHARED | IRQF_NO_THREAD,
|
||||
"xilinx-pcie", port);
|
||||
if (err) {
|
||||
dev_err(dev, "unable to request irq %d\n", port->irq);
|
||||
return err;
|
||||
|
@ -154,6 +154,7 @@ static union apci_descriptor *ibm_slot_from_id(int id)
|
||||
ibm_slot_done:
|
||||
if (ret) {
|
||||
ret = kmalloc(sizeof(union apci_descriptor), GFP_KERNEL);
|
||||
if (ret)
|
||||
memcpy(ret, des, sizeof(union apci_descriptor));
|
||||
}
|
||||
kfree(table);
|
||||
@ -175,8 +176,13 @@ static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status)
|
||||
acpi_status stat;
|
||||
unsigned long long rc;
|
||||
union apci_descriptor *ibm_slot;
|
||||
int id = hpslot_to_sun(slot);
|
||||
|
||||
ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot));
|
||||
ibm_slot = ibm_slot_from_id(id);
|
||||
if (!ibm_slot) {
|
||||
pr_err("APLS null ACPI descriptor for slot %d\n", id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pr_debug("%s: set slot %d (%d) attention status to %d\n", __func__,
|
||||
ibm_slot->slot.slot_num, ibm_slot->slot.slot_id,
|
||||
@ -215,8 +221,13 @@ static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status)
|
||||
static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status)
|
||||
{
|
||||
union apci_descriptor *ibm_slot;
|
||||
int id = hpslot_to_sun(slot);
|
||||
|
||||
ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot));
|
||||
ibm_slot = ibm_slot_from_id(id);
|
||||
if (!ibm_slot) {
|
||||
pr_err("APLS null ACPI descriptor for slot %d\n", id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (ibm_slot->slot.attn & 0xa0 || ibm_slot->slot.status[1] & 0x08)
|
||||
*status = 1;
|
||||
|
@ -116,11 +116,9 @@ static inline int slot_update(struct slot **sl)
|
||||
static int __init get_max_slots(void)
|
||||
{
|
||||
struct slot *slot_cur;
|
||||
struct list_head *tmp;
|
||||
u8 slot_count = 0;
|
||||
|
||||
list_for_each(tmp, &ibmphp_slot_head) {
|
||||
slot_cur = list_entry(tmp, struct slot, ibm_slot_list);
|
||||
list_for_each_entry(slot_cur, &ibmphp_slot_head, ibm_slot_list) {
|
||||
/* sometimes the hot-pluggable slots start with 4 (not always from 1) */
|
||||
slot_count = max(slot_count, slot_cur->number);
|
||||
}
|
||||
@ -501,16 +499,10 @@ static int get_bus_name(struct hotplug_slot *hotplug_slot, char *value)
|
||||
static int __init init_ops(void)
|
||||
{
|
||||
struct slot *slot_cur;
|
||||
struct list_head *tmp;
|
||||
int retval;
|
||||
int rc;
|
||||
|
||||
list_for_each(tmp, &ibmphp_slot_head) {
|
||||
slot_cur = list_entry(tmp, struct slot, ibm_slot_list);
|
||||
|
||||
if (!slot_cur)
|
||||
return -ENODEV;
|
||||
|
||||
list_for_each_entry(slot_cur, &ibmphp_slot_head, ibm_slot_list) {
|
||||
debug("BEFORE GETTING SLOT STATUS, slot # %x\n",
|
||||
slot_cur->number);
|
||||
if (slot_cur->ctrl->revision == 0xFF)
|
||||
@ -669,9 +661,7 @@ static struct pci_func *ibm_slot_find(u8 busno, u8 device, u8 function)
|
||||
{
|
||||
struct pci_func *func_cur;
|
||||
struct slot *slot_cur;
|
||||
struct list_head *tmp;
|
||||
list_for_each(tmp, &ibmphp_slot_head) {
|
||||
slot_cur = list_entry(tmp, struct slot, ibm_slot_list);
|
||||
list_for_each_entry(slot_cur, &ibmphp_slot_head, ibm_slot_list) {
|
||||
if (slot_cur->func) {
|
||||
func_cur = slot_cur->func;
|
||||
while (func_cur) {
|
||||
@ -693,14 +683,12 @@ static struct pci_func *ibm_slot_find(u8 busno, u8 device, u8 function)
|
||||
*************************************************************/
|
||||
static void free_slots(void)
|
||||
{
|
||||
struct slot *slot_cur;
|
||||
struct list_head *tmp;
|
||||
struct list_head *next;
|
||||
struct slot *slot_cur, *next;
|
||||
|
||||
debug("%s -- enter\n", __func__);
|
||||
|
||||
list_for_each_safe(tmp, next, &ibmphp_slot_head) {
|
||||
slot_cur = list_entry(tmp, struct slot, ibm_slot_list);
|
||||
list_for_each_entry_safe(slot_cur, next, &ibmphp_slot_head,
|
||||
ibm_slot_list) {
|
||||
pci_hp_deregister(slot_cur->hotplug_slot);
|
||||
}
|
||||
debug("%s -- exit\n", __func__);
|
||||
@ -1182,7 +1170,7 @@ error_power:
|
||||
* HOT REMOVING ADAPTER CARD *
|
||||
* INPUT: POINTER TO THE HOTPLUG SLOT STRUCTURE *
|
||||
* OUTPUT: SUCCESS 0 ; FAILURE: UNCONFIGURE , VALIDATE *
|
||||
DISABLE POWER , *
|
||||
* DISABLE POWER , *
|
||||
**************************************************************/
|
||||
static int ibmphp_disable_slot(struct hotplug_slot *hotplug_slot)
|
||||
{
|
||||
|
@ -1117,25 +1117,21 @@ int ibmphp_get_bus_index (u8 num)
|
||||
|
||||
void ibmphp_free_bus_info_queue(void)
|
||||
{
|
||||
struct bus_info *bus_info;
|
||||
struct list_head *list;
|
||||
struct list_head *next;
|
||||
struct bus_info *bus_info, *next;
|
||||
|
||||
list_for_each_safe (list, next, &bus_info_head ) {
|
||||
bus_info = list_entry (list, struct bus_info, bus_info_list);
|
||||
list_for_each_entry_safe(bus_info, next, &bus_info_head,
|
||||
bus_info_list) {
|
||||
kfree (bus_info);
|
||||
}
|
||||
}
|
||||
|
||||
void ibmphp_free_ebda_hpc_queue(void)
|
||||
{
|
||||
struct controller *controller = NULL;
|
||||
struct list_head *list;
|
||||
struct list_head *next;
|
||||
struct controller *controller = NULL, *next;
|
||||
int pci_flag = 0;
|
||||
|
||||
list_for_each_safe (list, next, &ebda_hpc_head) {
|
||||
controller = list_entry (list, struct controller, ebda_hpc_list);
|
||||
list_for_each_entry_safe(controller, next, &ebda_hpc_head,
|
||||
ebda_hpc_list) {
|
||||
if (controller->ctlr_type == 0)
|
||||
release_region(controller->u.isa_ctlr.io_start, (controller->u.isa_ctlr.io_end - controller->u.isa_ctlr.io_start + 1));
|
||||
else if ((controller->ctlr_type == 1) && (!pci_flag)) {
|
||||
@ -1148,12 +1144,10 @@ void ibmphp_free_ebda_hpc_queue (void)
|
||||
|
||||
void ibmphp_free_ebda_pci_rsrc_queue(void)
|
||||
{
|
||||
struct ebda_pci_rsrc *resource;
|
||||
struct list_head *list;
|
||||
struct list_head *next;
|
||||
struct ebda_pci_rsrc *resource, *next;
|
||||
|
||||
list_for_each_safe (list, next, &ibmphp_ebda_pci_rsrc_head) {
|
||||
resource = list_entry (list, struct ebda_pci_rsrc, ebda_pci_rsrc_list);
|
||||
list_for_each_entry_safe(resource, next, &ibmphp_ebda_pci_rsrc_head,
|
||||
ebda_pci_rsrc_list) {
|
||||
kfree (resource);
|
||||
resource = NULL;
|
||||
}
|
||||
|
@ -537,7 +537,6 @@ int ibmphp_hpc_readslot (struct slot *pslot, u8 cmd, u8 *pstatus)
|
||||
{
|
||||
void __iomem *wpg_bbar = NULL;
|
||||
struct controller *ctlr_ptr;
|
||||
struct list_head *pslotlist;
|
||||
u8 index, status;
|
||||
int rc = 0;
|
||||
int busindex;
|
||||
@ -628,8 +627,8 @@ int ibmphp_hpc_readslot (struct slot *pslot, u8 cmd, u8 *pstatus)
|
||||
|
||||
// Not used
|
||||
case READ_ALLSLOT:
|
||||
list_for_each (pslotlist, &ibmphp_slot_head) {
|
||||
pslot = list_entry (pslotlist, struct slot, ibm_slot_list);
|
||||
list_for_each_entry(pslot, &ibmphp_slot_head,
|
||||
ibm_slot_list) {
|
||||
index = pslot->ctlr_index;
|
||||
rc = hpc_wait_ctlr_notworking(HPC_CTLR_WORKING_TOUT, ctlr_ptr,
|
||||
wpg_bbar, &status);
|
||||
@ -820,7 +819,6 @@ static int poll_hpc(void *data)
|
||||
{
|
||||
struct slot myslot;
|
||||
struct slot *pslot = NULL;
|
||||
struct list_head *pslotlist;
|
||||
int rc;
|
||||
int poll_state = POLL_LATCH_REGISTER;
|
||||
u8 oldlatchlow = 0x00;
|
||||
@ -838,10 +836,10 @@ static int poll_hpc(void *data)
|
||||
case POLL_LATCH_REGISTER:
|
||||
oldlatchlow = curlatchlow;
|
||||
ctrl_count = 0x00;
|
||||
list_for_each (pslotlist, &ibmphp_slot_head) {
|
||||
list_for_each_entry(pslot, &ibmphp_slot_head,
|
||||
ibm_slot_list) {
|
||||
if (ctrl_count >= ibmphp_get_total_controllers())
|
||||
break;
|
||||
pslot = list_entry (pslotlist, struct slot, ibm_slot_list);
|
||||
if (pslot->ctrl->ctlr_relative_id == ctrl_count) {
|
||||
ctrl_count++;
|
||||
if (READ_SLOT_LATCH(pslot->ctrl)) {
|
||||
@ -859,8 +857,8 @@ static int poll_hpc(void *data)
|
||||
poll_state = POLL_SLEEP;
|
||||
break;
|
||||
case POLL_SLOTS:
|
||||
list_for_each (pslotlist, &ibmphp_slot_head) {
|
||||
pslot = list_entry (pslotlist, struct slot, ibm_slot_list);
|
||||
list_for_each_entry(pslot, &ibmphp_slot_head,
|
||||
ibm_slot_list) {
|
||||
// make a copy of the old status
|
||||
memcpy((void *) &myslot, (void *) pslot,
|
||||
sizeof(struct slot));
|
||||
@ -870,10 +868,10 @@ static int poll_hpc(void *data)
|
||||
process_changeinstatus(pslot, &myslot);
|
||||
}
|
||||
ctrl_count = 0x00;
|
||||
list_for_each (pslotlist, &ibmphp_slot_head) {
|
||||
list_for_each_entry(pslot, &ibmphp_slot_head,
|
||||
ibm_slot_list) {
|
||||
if (ctrl_count >= ibmphp_get_total_controllers())
|
||||
break;
|
||||
pslot = list_entry (pslotlist, struct slot, ibm_slot_list);
|
||||
if (pslot->ctrl->ctlr_relative_id == ctrl_count) {
|
||||
ctrl_count++;
|
||||
if (READ_SLOT_LATCH(pslot->ctrl))
|
||||
|
@ -1119,7 +1119,7 @@ static struct res_needed *scan_behind_bridge (struct pci_func *func, u8 busno)
|
||||
pci_bus_read_config_dword(ibmphp_pci_bus, devfn, PCI_CLASS_REVISION, &class);
|
||||
|
||||
debug("hdr_type behind the bridge is %x\n", hdr_type);
|
||||
if (hdr_type & PCI_HEADER_TYPE_BRIDGE) {
|
||||
if ((hdr_type & 0x7f) == PCI_HEADER_TYPE_BRIDGE) {
|
||||
err("embedded bridges not supported for hot-plugging.\n");
|
||||
amount->not_correct = 1;
|
||||
return amount;
|
||||
|
@ -203,15 +203,13 @@ int __init ibmphp_rsrc_init (void)
|
||||
struct bus_node *newbus = NULL;
|
||||
struct bus_node *bus_cur;
|
||||
struct bus_node *bus_prev;
|
||||
struct list_head *tmp;
|
||||
struct resource_node *new_io = NULL;
|
||||
struct resource_node *new_mem = NULL;
|
||||
struct resource_node *new_pfmem = NULL;
|
||||
int rc;
|
||||
struct list_head *tmp_ebda;
|
||||
|
||||
list_for_each (tmp_ebda, &ibmphp_ebda_pci_rsrc_head) {
|
||||
curr = list_entry (tmp_ebda, struct ebda_pci_rsrc, ebda_pci_rsrc_list);
|
||||
list_for_each_entry(curr, &ibmphp_ebda_pci_rsrc_head,
|
||||
ebda_pci_rsrc_list) {
|
||||
if (!(curr->rsrc_type & PCIDEVMASK)) {
|
||||
/* EBDA still lists non PCI devices, so ignore... */
|
||||
debug("this is not a PCI DEVICE in rsrc_init, please take care\n");
|
||||
@ -369,8 +367,7 @@ int __init ibmphp_rsrc_init (void)
|
||||
}
|
||||
}
|
||||
|
||||
list_for_each (tmp, &gbuses) {
|
||||
bus_cur = list_entry (tmp, struct bus_node, bus_list);
|
||||
list_for_each_entry(bus_cur, &gbuses, bus_list) {
|
||||
/* This is to get info about PPB resources, since EBDA doesn't put this info into the primary bus info */
|
||||
rc = update_bridge_ranges(&bus_cur);
|
||||
if (rc)
|
||||
@ -1571,19 +1568,16 @@ int ibmphp_find_resource (struct bus_node *bus, u32 start_address, struct resour
|
||||
***********************************************************************/
|
||||
void ibmphp_free_resources(void)
|
||||
{
|
||||
struct bus_node *bus_cur = NULL;
|
||||
struct bus_node *bus_cur = NULL, *next;
|
||||
struct bus_node *bus_tmp;
|
||||
struct range_node *range_cur;
|
||||
struct range_node *range_tmp;
|
||||
struct resource_node *res_cur;
|
||||
struct resource_node *res_tmp;
|
||||
struct list_head *tmp;
|
||||
struct list_head *next;
|
||||
int i = 0;
|
||||
flags = 1;
|
||||
|
||||
list_for_each_safe (tmp, next, &gbuses) {
|
||||
bus_cur = list_entry (tmp, struct bus_node, bus_list);
|
||||
list_for_each_entry_safe(bus_cur, next, &gbuses, bus_list) {
|
||||
if (bus_cur->noIORanges) {
|
||||
range_cur = bus_cur->rangeIO;
|
||||
for (i = 0; i < bus_cur->noIORanges; i++) {
|
||||
@ -1691,10 +1685,8 @@ static int __init once_over (void)
|
||||
struct resource_node *pfmem_prev;
|
||||
struct resource_node *mem;
|
||||
struct bus_node *bus_cur;
|
||||
struct list_head *tmp;
|
||||
|
||||
list_for_each (tmp, &gbuses) {
|
||||
bus_cur = list_entry (tmp, struct bus_node, bus_list);
|
||||
list_for_each_entry(bus_cur, &gbuses, bus_list) {
|
||||
if ((!bus_cur->rangePFMem) && (bus_cur->firstPFMem)) {
|
||||
for (pfmem_cur = bus_cur->firstPFMem, pfmem_prev = NULL; pfmem_cur; pfmem_prev = pfmem_cur, pfmem_cur = pfmem_cur->next) {
|
||||
pfmem_cur->fromMem = 1;
|
||||
@ -1767,14 +1759,10 @@ struct bus_node *ibmphp_find_res_bus (u8 bus_number)
|
||||
static struct bus_node *find_bus_wprev(u8 bus_number, struct bus_node **prev, u8 flag)
|
||||
{
|
||||
struct bus_node *bus_cur;
|
||||
struct list_head *tmp;
|
||||
struct list_head *tmp_prev;
|
||||
|
||||
list_for_each (tmp, &gbuses) {
|
||||
tmp_prev = tmp->prev;
|
||||
bus_cur = list_entry (tmp, struct bus_node, bus_list);
|
||||
list_for_each_entry(bus_cur, &gbuses, bus_list) {
|
||||
if (flag)
|
||||
*prev = list_entry (tmp_prev, struct bus_node, bus_list);
|
||||
*prev = list_prev_entry(bus_cur, bus_list);
|
||||
if (bus_cur->busno == bus_number)
|
||||
return bus_cur;
|
||||
}
|
||||
@ -1788,7 +1776,6 @@ void ibmphp_print_test (void)
|
||||
struct bus_node *bus_cur = NULL;
|
||||
struct range_node *range;
|
||||
struct resource_node *res;
|
||||
struct list_head *tmp;
|
||||
|
||||
debug_pci("*****************START**********************\n");
|
||||
|
||||
@ -1797,8 +1784,7 @@ void ibmphp_print_test (void)
|
||||
return;
|
||||
}
|
||||
|
||||
list_for_each (tmp, &gbuses) {
|
||||
bus_cur = list_entry (tmp, struct bus_node, bus_list);
|
||||
list_for_each_entry(bus_cur, &gbuses, bus_list) {
|
||||
debug_pci ("This is bus # %d. There are\n", bus_cur->busno);
|
||||
debug_pci ("IORanges = %d\t", bus_cur->noIORanges);
|
||||
debug_pci ("MemRanges = %d\t", bus_cur->noMemRanges);
|
||||
|
@ -396,10 +396,8 @@ static void fs_remove_slot(struct pci_slot *pci_slot)
|
||||
static struct hotplug_slot *get_slot_from_name(const char *name)
|
||||
{
|
||||
struct hotplug_slot *slot;
|
||||
struct list_head *tmp;
|
||||
|
||||
list_for_each(tmp, &pci_hotplug_slot_list) {
|
||||
slot = list_entry(tmp, struct hotplug_slot, slot_list);
|
||||
list_for_each_entry(slot, &pci_hotplug_slot_list, slot_list) {
|
||||
if (strcmp(hotplug_slot_name(slot), name) == 0)
|
||||
return slot;
|
||||
}
|
||||
|
@ -511,7 +511,9 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot)
|
||||
case STATIC_STATE:
|
||||
p_slot->state = POWEROFF_STATE;
|
||||
mutex_unlock(&p_slot->lock);
|
||||
mutex_lock(&p_slot->hotplug_lock);
|
||||
retval = pciehp_disable_slot(p_slot);
|
||||
mutex_unlock(&p_slot->hotplug_lock);
|
||||
mutex_lock(&p_slot->lock);
|
||||
p_slot->state = STATIC_STATE;
|
||||
break;
|
||||
|
@ -321,17 +321,14 @@ error:
|
||||
|
||||
static void __exit cleanup_slots(void)
|
||||
{
|
||||
struct list_head *tmp;
|
||||
struct list_head *next;
|
||||
struct slot *slot;
|
||||
struct slot *slot, *next;
|
||||
|
||||
/*
|
||||
* Unregister all of our slots with the pci_hotplug subsystem.
|
||||
* Memory will be freed in release_slot() callback after slot's
|
||||
* lifespan is finished.
|
||||
*/
|
||||
list_for_each_safe(tmp, next, &slot_list) {
|
||||
slot = list_entry(tmp, struct slot, slot_list);
|
||||
list_for_each_entry_safe(slot, next, &slot_list, slot_list) {
|
||||
list_del(&slot->slot_list);
|
||||
pci_hp_deregister(slot->hotplug_slot);
|
||||
}
|
||||
|
@ -114,11 +114,10 @@ static struct device_node *find_dlpar_node(char *drc_name, int *node_type)
|
||||
*/
|
||||
static struct slot *find_php_slot(struct device_node *dn)
|
||||
{
|
||||
struct list_head *tmp, *n;
|
||||
struct slot *slot;
|
||||
struct slot *slot, *next;
|
||||
|
||||
list_for_each_safe(tmp, n, &rpaphp_slot_head) {
|
||||
slot = list_entry(tmp, struct slot, rpaphp_slot_list);
|
||||
list_for_each_entry_safe(slot, next, &rpaphp_slot_head,
|
||||
rpaphp_slot_list) {
|
||||
if (slot->dn == dn)
|
||||
return slot;
|
||||
}
|
||||
|
@ -356,8 +356,7 @@ EXPORT_SYMBOL_GPL(rpaphp_add_slot);
|
||||
|
||||
static void __exit cleanup_slots(void)
|
||||
{
|
||||
struct list_head *tmp, *n;
|
||||
struct slot *slot;
|
||||
struct slot *slot, *next;
|
||||
|
||||
/*
|
||||
* Unregister all of our slots with the pci_hotplug subsystem,
|
||||
@ -365,8 +364,8 @@ static void __exit cleanup_slots(void)
|
||||
* memory will be freed in release_slot callback.
|
||||
*/
|
||||
|
||||
list_for_each_safe(tmp, n, &rpaphp_slot_head) {
|
||||
slot = list_entry(tmp, struct slot, rpaphp_slot_list);
|
||||
list_for_each_entry_safe(slot, next, &rpaphp_slot_head,
|
||||
rpaphp_slot_list) {
|
||||
list_del(&slot->rpaphp_slot_list);
|
||||
pci_hp_deregister(slot->hotplug_slot);
|
||||
}
|
||||
|
@ -201,11 +201,10 @@ error:
|
||||
|
||||
void zpci_exit_slot(struct zpci_dev *zdev)
|
||||
{
|
||||
struct list_head *tmp, *n;
|
||||
struct slot *slot;
|
||||
struct slot *slot, *next;
|
||||
|
||||
list_for_each_safe(tmp, n, &s390_hotplug_slot_list) {
|
||||
slot = list_entry(tmp, struct slot, slot_list);
|
||||
list_for_each_entry_safe(slot, next, &s390_hotplug_slot_list,
|
||||
slot_list) {
|
||||
if (slot->zdev != zdev)
|
||||
continue;
|
||||
list_del(&slot->slot_list);
|
||||
|
@ -84,7 +84,7 @@ struct slot {
|
||||
u8 presence_save;
|
||||
u8 pwr_save;
|
||||
struct controller *ctrl;
|
||||
struct hpc_ops *hpc_ops;
|
||||
const struct hpc_ops *hpc_ops;
|
||||
struct hotplug_slot *hotplug_slot;
|
||||
struct list_head slot_list;
|
||||
struct delayed_work work; /* work for button event */
|
||||
@ -106,7 +106,7 @@ struct controller {
|
||||
int slot_num_inc; /* 1 or -1 */
|
||||
struct pci_dev *pci_dev;
|
||||
struct list_head slot_list;
|
||||
struct hpc_ops *hpc_ops;
|
||||
const struct hpc_ops *hpc_ops;
|
||||
wait_queue_head_t queue; /* sleep & wake process */
|
||||
u8 slot_device_offset;
|
||||
u32 pcix_misc2_reg; /* for amd pogo errata */
|
||||
|
@ -178,12 +178,9 @@ error:
|
||||
|
||||
void cleanup_slots(struct controller *ctrl)
|
||||
{
|
||||
struct list_head *tmp;
|
||||
struct list_head *next;
|
||||
struct slot *slot;
|
||||
struct slot *slot, *next;
|
||||
|
||||
list_for_each_safe(tmp, next, &ctrl->slot_list) {
|
||||
slot = list_entry(tmp, struct slot, slot_list);
|
||||
list_for_each_entry_safe(slot, next, &ctrl->slot_list, slot_list) {
|
||||
list_del(&slot->slot_list);
|
||||
cancel_delayed_work(&slot->work);
|
||||
destroy_workqueue(slot->wq);
|
||||
|
@ -910,7 +910,7 @@ static int shpc_get_max_bus_speed(struct controller *ctrl)
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct hpc_ops shpchp_hpc_ops = {
|
||||
static const struct hpc_ops shpchp_hpc_ops = {
|
||||
.power_on_slot = hpc_power_on_slot,
|
||||
.slot_enable = hpc_slot_enable,
|
||||
.slot_disable = hpc_slot_disable,
|
||||
|
@ -43,7 +43,7 @@ static ssize_t show_ctrl (struct device *dev, struct device_attribute *attr, cha
|
||||
struct resource *res;
|
||||
struct pci_bus *bus;
|
||||
|
||||
pdev = container_of (dev, struct pci_dev, dev);
|
||||
pdev = to_pci_dev(dev);
|
||||
bus = pdev->subordinate;
|
||||
|
||||
out += sprintf(buf, "Free resources: memory\n");
|
||||
|
@ -1026,10 +1026,6 @@ int pci_msi_enabled(void)
|
||||
}
|
||||
EXPORT_SYMBOL(pci_msi_enabled);
|
||||
|
||||
void pci_msi_init_pci_dev(struct pci_dev *dev)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_enable_msi_range - configure device's MSI capability structure
|
||||
* @dev: device to configure
|
||||
|
@ -77,7 +77,7 @@ static umode_t smbios_instance_string_exist(struct kobject *kobj,
|
||||
struct device *dev;
|
||||
struct pci_dev *pdev;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
dev = kobj_to_dev(kobj);
|
||||
pdev = to_pci_dev(dev);
|
||||
|
||||
return find_smbios_instance_string(pdev, NULL, SMBIOS_ATTR_NONE) ?
|
||||
@ -221,7 +221,7 @@ static umode_t acpi_index_string_exist(struct kobject *kobj,
|
||||
{
|
||||
struct device *dev;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
dev = kobj_to_dev(kobj);
|
||||
|
||||
if (device_has_dsm(dev))
|
||||
return S_IRUGO;
|
||||
|
@ -630,8 +630,7 @@ static ssize_t pci_read_config(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct pci_dev *dev = to_pci_dev(container_of(kobj, struct device,
|
||||
kobj));
|
||||
struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj));
|
||||
unsigned int size = 64;
|
||||
loff_t init_off = off;
|
||||
u8 *data = (u8 *) buf;
|
||||
@ -707,8 +706,7 @@ static ssize_t pci_write_config(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct pci_dev *dev = to_pci_dev(container_of(kobj, struct device,
|
||||
kobj));
|
||||
struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj));
|
||||
unsigned int size = count;
|
||||
loff_t init_off = off;
|
||||
u8 *data = (u8 *) buf;
|
||||
@ -769,8 +767,7 @@ static ssize_t read_vpd_attr(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct pci_dev *dev =
|
||||
to_pci_dev(container_of(kobj, struct device, kobj));
|
||||
struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj));
|
||||
|
||||
if (off > bin_attr->size)
|
||||
count = 0;
|
||||
@ -784,8 +781,7 @@ static ssize_t write_vpd_attr(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct pci_dev *dev =
|
||||
to_pci_dev(container_of(kobj, struct device, kobj));
|
||||
struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj));
|
||||
|
||||
if (off > bin_attr->size)
|
||||
count = 0;
|
||||
@ -812,8 +808,7 @@ static ssize_t pci_read_legacy_io(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct pci_bus *bus = to_pci_bus(container_of(kobj, struct device,
|
||||
kobj));
|
||||
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
|
||||
|
||||
/* Only support 1, 2 or 4 byte accesses */
|
||||
if (count != 1 && count != 2 && count != 4)
|
||||
@ -838,8 +833,7 @@ static ssize_t pci_write_legacy_io(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct pci_bus *bus = to_pci_bus(container_of(kobj, struct device,
|
||||
kobj));
|
||||
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
|
||||
|
||||
/* Only support 1, 2 or 4 byte accesses */
|
||||
if (count != 1 && count != 2 && count != 4)
|
||||
@ -863,8 +857,7 @@ static int pci_mmap_legacy_mem(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct pci_bus *bus = to_pci_bus(container_of(kobj, struct device,
|
||||
kobj));
|
||||
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
|
||||
|
||||
return pci_mmap_legacy_page_range(bus, vma, pci_mmap_mem);
|
||||
}
|
||||
@ -884,8 +877,7 @@ static int pci_mmap_legacy_io(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct pci_bus *bus = to_pci_bus(container_of(kobj, struct device,
|
||||
kobj));
|
||||
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
|
||||
|
||||
return pci_mmap_legacy_page_range(bus, vma, pci_mmap_io);
|
||||
}
|
||||
@ -1000,8 +992,7 @@ int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vma,
|
||||
static int pci_mmap_resource(struct kobject *kobj, struct bin_attribute *attr,
|
||||
struct vm_area_struct *vma, int write_combine)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(container_of(kobj,
|
||||
struct device, kobj));
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
struct resource *res = attr->private;
|
||||
enum pci_mmap_state mmap_type;
|
||||
resource_size_t start, end;
|
||||
@ -1054,8 +1045,7 @@ static ssize_t pci_resource_io(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf,
|
||||
loff_t off, size_t count, bool write)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(container_of(kobj,
|
||||
struct device, kobj));
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
struct resource *res = attr->private;
|
||||
unsigned long port = off;
|
||||
int i;
|
||||
@ -1225,7 +1215,7 @@ static ssize_t pci_write_rom(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(container_of(kobj, struct device, kobj));
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
|
||||
if ((off == 0) && (*buf == '0') && (count == 2))
|
||||
pdev->rom_attr_enabled = 0;
|
||||
@ -1251,7 +1241,7 @@ static ssize_t pci_read_rom(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(container_of(kobj, struct device, kobj));
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
void __iomem *rom;
|
||||
size_t size;
|
||||
|
||||
@ -1372,10 +1362,10 @@ int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev)
|
||||
if (!sysfs_initialized)
|
||||
return -EACCES;
|
||||
|
||||
if (pdev->cfg_size < PCI_CFG_SPACE_EXP_SIZE)
|
||||
retval = sysfs_create_bin_file(&pdev->dev.kobj, &pci_config_attr);
|
||||
else
|
||||
if (pdev->cfg_size > PCI_CFG_SPACE_SIZE)
|
||||
retval = sysfs_create_bin_file(&pdev->dev.kobj, &pcie_config_attr);
|
||||
else
|
||||
retval = sysfs_create_bin_file(&pdev->dev.kobj, &pci_config_attr);
|
||||
if (retval)
|
||||
goto err;
|
||||
|
||||
@ -1427,10 +1417,10 @@ err_rom_file:
|
||||
err_resource_files:
|
||||
pci_remove_resource_files(pdev);
|
||||
err_config_file:
|
||||
if (pdev->cfg_size < PCI_CFG_SPACE_EXP_SIZE)
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr);
|
||||
else
|
||||
if (pdev->cfg_size > PCI_CFG_SPACE_SIZE)
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &pcie_config_attr);
|
||||
else
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr);
|
||||
err:
|
||||
return retval;
|
||||
}
|
||||
@ -1464,10 +1454,10 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev)
|
||||
|
||||
pci_remove_capabilities_sysfs(pdev);
|
||||
|
||||
if (pdev->cfg_size < PCI_CFG_SPACE_EXP_SIZE)
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr);
|
||||
else
|
||||
if (pdev->cfg_size > PCI_CFG_SPACE_SIZE)
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &pcie_config_attr);
|
||||
else
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr);
|
||||
|
||||
pci_remove_resource_files(pdev);
|
||||
|
||||
@ -1511,7 +1501,7 @@ static struct attribute *pci_dev_dev_attrs[] = {
|
||||
static umode_t pci_dev_attrs_are_visible(struct kobject *kobj,
|
||||
struct attribute *a, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
|
||||
if (a == &vga_attr.attr)
|
||||
@ -1530,7 +1520,7 @@ static struct attribute *pci_dev_hp_attrs[] = {
|
||||
static umode_t pci_dev_hp_attrs_are_visible(struct kobject *kobj,
|
||||
struct attribute *a, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
|
||||
if (pdev->is_virtfn)
|
||||
@ -1554,7 +1544,7 @@ static struct attribute *sriov_dev_attrs[] = {
|
||||
static umode_t sriov_attrs_are_visible(struct kobject *kobj,
|
||||
struct attribute *a, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
|
||||
if (!dev_is_pf(dev))
|
||||
return 0;
|
||||
|
@ -1417,7 +1417,7 @@ struct pci_devres {
|
||||
|
||||
static void pcim_release(struct device *gendev, void *res)
|
||||
{
|
||||
struct pci_dev *dev = container_of(gendev, struct pci_dev, dev);
|
||||
struct pci_dev *dev = to_pci_dev(gendev);
|
||||
struct pci_devres *this = res;
|
||||
int i;
|
||||
|
||||
|
@ -144,10 +144,8 @@ extern unsigned int pci_pm_d3_delay;
|
||||
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
void pci_no_msi(void);
|
||||
void pci_msi_init_pci_dev(struct pci_dev *dev);
|
||||
#else
|
||||
static inline void pci_no_msi(void) { }
|
||||
static inline void pci_msi_init_pci_dev(struct pci_dev *dev) { }
|
||||
#endif
|
||||
|
||||
static inline void pci_msi_set_enable(struct pci_dev *dev, int enable)
|
||||
|
@ -41,12 +41,12 @@ struct aer_error_inj {
|
||||
u32 header_log1;
|
||||
u32 header_log2;
|
||||
u32 header_log3;
|
||||
u16 domain;
|
||||
u32 domain;
|
||||
};
|
||||
|
||||
struct aer_error {
|
||||
struct list_head list;
|
||||
u16 domain;
|
||||
u32 domain;
|
||||
unsigned int bus;
|
||||
unsigned int devfn;
|
||||
int pos_cap_err;
|
||||
@ -74,7 +74,7 @@ static LIST_HEAD(pci_bus_ops_list);
|
||||
/* Protect einjected and pci_bus_ops_list */
|
||||
static DEFINE_SPINLOCK(inject_lock);
|
||||
|
||||
static void aer_error_init(struct aer_error *err, u16 domain,
|
||||
static void aer_error_init(struct aer_error *err, u32 domain,
|
||||
unsigned int bus, unsigned int devfn,
|
||||
int pos_cap_err)
|
||||
{
|
||||
@ -86,7 +86,7 @@ static void aer_error_init(struct aer_error *err, u16 domain,
|
||||
}
|
||||
|
||||
/* inject_lock must be held before calling */
|
||||
static struct aer_error *__find_aer_error(u16 domain, unsigned int bus,
|
||||
static struct aer_error *__find_aer_error(u32 domain, unsigned int bus,
|
||||
unsigned int devfn)
|
||||
{
|
||||
struct aer_error *err;
|
||||
@ -106,7 +106,7 @@ static struct aer_error *__find_aer_error_by_dev(struct pci_dev *dev)
|
||||
int domain = pci_domain_nr(dev->bus);
|
||||
if (domain < 0)
|
||||
return NULL;
|
||||
return __find_aer_error((u16)domain, dev->bus->number, dev->devfn);
|
||||
return __find_aer_error(domain, dev->bus->number, dev->devfn);
|
||||
}
|
||||
|
||||
/* inject_lock must be held before calling */
|
||||
@ -196,7 +196,7 @@ static int pci_read_aer(struct pci_bus *bus, unsigned int devfn, int where,
|
||||
domain = pci_domain_nr(bus);
|
||||
if (domain < 0)
|
||||
goto out;
|
||||
err = __find_aer_error((u16)domain, bus->number, devfn);
|
||||
err = __find_aer_error(domain, bus->number, devfn);
|
||||
if (!err)
|
||||
goto out;
|
||||
|
||||
@ -228,7 +228,7 @@ static int pci_write_aer(struct pci_bus *bus, unsigned int devfn, int where,
|
||||
domain = pci_domain_nr(bus);
|
||||
if (domain < 0)
|
||||
goto out;
|
||||
err = __find_aer_error((u16)domain, bus->number, devfn);
|
||||
err = __find_aer_error(domain, bus->number, devfn);
|
||||
if (!err)
|
||||
goto out;
|
||||
|
||||
@ -329,7 +329,7 @@ static int aer_inject(struct aer_error_inj *einj)
|
||||
u32 sever, cor_mask, uncor_mask, cor_mask_orig = 0, uncor_mask_orig = 0;
|
||||
int ret = 0;
|
||||
|
||||
dev = pci_get_domain_bus_and_slot((int)einj->domain, einj->bus, devfn);
|
||||
dev = pci_get_domain_bus_and_slot(einj->domain, einj->bus, devfn);
|
||||
if (!dev)
|
||||
return -ENODEV;
|
||||
rpdev = pcie_find_root_port(dev);
|
||||
|
@ -246,7 +246,7 @@ static int report_error_detected(struct pci_dev *dev, void *data)
|
||||
!dev->driver->err_handler ||
|
||||
!dev->driver->err_handler->error_detected) {
|
||||
if (result_data->state == pci_channel_io_frozen &&
|
||||
!(dev->hdr_type & PCI_HEADER_TYPE_BRIDGE)) {
|
||||
dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) {
|
||||
/*
|
||||
* In case of fatal recovery, if one of down-
|
||||
* stream device has no driver. We might be
|
||||
@ -269,7 +269,7 @@ static int report_error_detected(struct pci_dev *dev, void *data)
|
||||
* without recovery.
|
||||
*/
|
||||
|
||||
if (!(dev->hdr_type & PCI_HEADER_TYPE_BRIDGE))
|
||||
if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE)
|
||||
vote = PCI_ERS_RESULT_NO_AER_DRIVER;
|
||||
else
|
||||
vote = PCI_ERS_RESULT_NONE;
|
||||
@ -369,7 +369,7 @@ static pci_ers_result_t broadcast_error_message(struct pci_dev *dev,
|
||||
else
|
||||
result_data.result = PCI_ERS_RESULT_RECOVERED;
|
||||
|
||||
if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) {
|
||||
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
|
||||
/*
|
||||
* If the error is reported by a bridge, we think this error
|
||||
* is related to the downstream link of the bridge, so we
|
||||
@ -440,7 +440,7 @@ static pci_ers_result_t reset_link(struct pci_dev *dev)
|
||||
pci_ers_result_t status;
|
||||
struct pcie_port_service_driver *driver;
|
||||
|
||||
if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) {
|
||||
if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
|
||||
/* Reset this port for all subordinates */
|
||||
udev = dev;
|
||||
} else {
|
||||
@ -660,7 +660,7 @@ static int get_device_error_info(struct pci_dev *dev, struct aer_err_info *info)
|
||||
&info->mask);
|
||||
if (!(info->status & ~info->mask))
|
||||
return 0;
|
||||
} else if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE ||
|
||||
} else if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||
|
||||
info->severity == AER_NONFATAL) {
|
||||
|
||||
/* Link is still healthy for IO reads */
|
||||
|
@ -834,21 +834,15 @@ static ssize_t link_state_store(struct device *dev,
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
struct pcie_link_state *link, *root = pdev->link_state->root;
|
||||
u32 val, state = 0;
|
||||
|
||||
if (kstrtouint(buf, 10, &val))
|
||||
return -EINVAL;
|
||||
u32 state;
|
||||
|
||||
if (aspm_disabled)
|
||||
return -EPERM;
|
||||
if (n < 1 || val > 3)
|
||||
return -EINVAL;
|
||||
|
||||
/* Convert requested state to ASPM state */
|
||||
if (val & PCIE_LINK_STATE_L0S)
|
||||
state |= ASPM_STATE_L0S;
|
||||
if (val & PCIE_LINK_STATE_L1)
|
||||
state |= ASPM_STATE_L1;
|
||||
if (kstrtouint(buf, 10, &state))
|
||||
return -EINVAL;
|
||||
if ((state & ~ASPM_STATE_ALL) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
down_read(&pci_bus_sem);
|
||||
mutex_lock(&aspm_lock);
|
||||
|
@ -1109,14 +1109,11 @@ static int pci_cfg_space_size_ext(struct pci_dev *dev)
|
||||
int pos = PCI_CFG_SPACE_SIZE;
|
||||
|
||||
if (pci_read_config_dword(dev, pos, &status) != PCIBIOS_SUCCESSFUL)
|
||||
goto fail;
|
||||
return PCI_CFG_SPACE_SIZE;
|
||||
if (status == 0xffffffff || pci_ext_cfg_is_aliased(dev))
|
||||
goto fail;
|
||||
return PCI_CFG_SPACE_SIZE;
|
||||
|
||||
return PCI_CFG_SPACE_EXP_SIZE;
|
||||
|
||||
fail:
|
||||
return PCI_CFG_SPACE_SIZE;
|
||||
}
|
||||
|
||||
int pci_cfg_space_size(struct pci_dev *dev)
|
||||
@ -1129,25 +1126,23 @@ int pci_cfg_space_size(struct pci_dev *dev)
|
||||
if (class == PCI_CLASS_BRIDGE_HOST)
|
||||
return pci_cfg_space_size_ext(dev);
|
||||
|
||||
if (!pci_is_pcie(dev)) {
|
||||
pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
|
||||
if (!pos)
|
||||
goto fail;
|
||||
|
||||
pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
|
||||
if (!(status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ)))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (pci_is_pcie(dev))
|
||||
return pci_cfg_space_size_ext(dev);
|
||||
|
||||
pos = pci_find_capability(dev, PCI_CAP_ID_PCIX);
|
||||
if (!pos)
|
||||
return PCI_CFG_SPACE_SIZE;
|
||||
|
||||
pci_read_config_dword(dev, pos + PCI_X_STATUS, &status);
|
||||
if (status & (PCI_X_STATUS_266MHZ | PCI_X_STATUS_533MHZ))
|
||||
return pci_cfg_space_size_ext(dev);
|
||||
|
||||
fail:
|
||||
return PCI_CFG_SPACE_SIZE;
|
||||
}
|
||||
|
||||
#define LEGACY_IO_RESOURCE (IORESOURCE_IO | IORESOURCE_PCI_FIXED)
|
||||
|
||||
void pci_msi_setup_pci_dev(struct pci_dev *dev)
|
||||
static void pci_msi_setup_pci_dev(struct pci_dev *dev)
|
||||
{
|
||||
/*
|
||||
* Disable the MSI hardware to avoid screaming interrupts
|
||||
@ -1214,8 +1209,6 @@ int pci_setup_device(struct pci_dev *dev)
|
||||
/* "Unknown power state" */
|
||||
dev->current_state = PCI_UNKNOWN;
|
||||
|
||||
pci_msi_setup_pci_dev(dev);
|
||||
|
||||
/* Early fixups, before probing the BARs */
|
||||
pci_fixup_device(pci_fixup_early, dev);
|
||||
/* device class may be changed after fixup */
|
||||
@ -1605,8 +1598,8 @@ static void pci_init_capabilities(struct pci_dev *dev)
|
||||
/* Enhanced Allocation */
|
||||
pci_ea_init(dev);
|
||||
|
||||
/* MSI/MSI-X list */
|
||||
pci_msi_init_pci_dev(dev);
|
||||
/* Setup MSI caps & disable MSI/MSI-X interrupts */
|
||||
pci_msi_setup_pci_dev(dev);
|
||||
|
||||
/* Buffers for saving PCIe and PCI-X capabilities */
|
||||
pci_allocate_cap_save_buffers(dev);
|
||||
|
@ -287,6 +287,18 @@ static void quirk_citrine(struct pci_dev *dev)
|
||||
}
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_IBM, PCI_DEVICE_ID_IBM_CITRINE, quirk_citrine);
|
||||
|
||||
/*
|
||||
* This chip can cause bus lockups if config addresses above 0x600
|
||||
* are read or written.
|
||||
*/
|
||||
static void quirk_nfp6000(struct pci_dev *dev)
|
||||
{
|
||||
dev->cfg_size = 0x600;
|
||||
}
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NETRONOME, PCI_DEVICE_ID_NETRONOME_NFP4000, quirk_nfp6000);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NETRONOME, PCI_DEVICE_ID_NETRONOME_NFP6000, quirk_nfp6000);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NETRONOME, PCI_DEVICE_ID_NETRONOME_NFP6000_VF, quirk_nfp6000);
|
||||
|
||||
/* On IBM Crocodile ipr SAS adapters, expand BAR to system page size */
|
||||
static void quirk_extend_bar_to_page(struct pci_dev *dev)
|
||||
{
|
||||
@ -3622,6 +3634,10 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TTI, 0x0642,
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_JMICRON,
|
||||
PCI_DEVICE_ID_JMICRON_JMB388_ESD,
|
||||
quirk_dma_func1_alias);
|
||||
/* https://bugzilla.kernel.org/show_bug.cgi?id=42679#c117 */
|
||||
DECLARE_PCI_FIXUP_HEADER(0x1c28, /* Lite-On */
|
||||
0x0122, /* Plextor M6E (Marvell 88SS9183)*/
|
||||
quirk_dma_func1_alias);
|
||||
|
||||
/*
|
||||
* Some devices DMA with the wrong devfn, not just the wrong function.
|
||||
|
@ -77,25 +77,24 @@ size_t pci_get_rom_size(struct pci_dev *pdev, void __iomem *rom, size_t size)
|
||||
do {
|
||||
void __iomem *pds;
|
||||
/* Standard PCI ROMs start out with these bytes 55 AA */
|
||||
if (readb(image) != 0x55) {
|
||||
dev_err(&pdev->dev, "Invalid ROM contents\n");
|
||||
if (readw(image) != 0xAA55) {
|
||||
dev_err(&pdev->dev, "Invalid PCI ROM header signature: expecting 0xaa55, got %#06x\n",
|
||||
readw(image));
|
||||
break;
|
||||
}
|
||||
if (readb(image + 1) != 0xAA)
|
||||
break;
|
||||
/* get the PCI data structure and check its signature */
|
||||
/* get the PCI data structure and check its "PCIR" signature */
|
||||
pds = image + readw(image + 24);
|
||||
if (readb(pds) != 'P')
|
||||
break;
|
||||
if (readb(pds + 1) != 'C')
|
||||
break;
|
||||
if (readb(pds + 2) != 'I')
|
||||
break;
|
||||
if (readb(pds + 3) != 'R')
|
||||
if (readl(pds) != 0x52494350) {
|
||||
dev_err(&pdev->dev, "Invalid PCI ROM data signature: expecting 0x52494350, got %#010x\n",
|
||||
readl(pds));
|
||||
break;
|
||||
}
|
||||
last_image = readb(pds + 21) & 0x80;
|
||||
length = readw(pds + 16);
|
||||
image += length * 512;
|
||||
/* Avoid iterating through memory outside the resource window */
|
||||
if (image > rom + size)
|
||||
break;
|
||||
} while (length && !last_image);
|
||||
|
||||
/* never return a size larger than the PCI resource window */
|
||||
|
@ -187,7 +187,7 @@ struct msi_domain_info;
|
||||
* @msi_free: Domain specific function to free a MSI interrupts
|
||||
* @msi_check: Callback for verification of the domain/info/dev data
|
||||
* @msi_prepare: Prepare the allocation of the interrupts in the domain
|
||||
* @msi_finish: Optional callbacl to finalize the allocation
|
||||
* @msi_finish: Optional callback to finalize the allocation
|
||||
* @set_desc: Set the msi descriptor for an interrupt
|
||||
* @handle_error: Optional error handler if the allocation fails
|
||||
*
|
||||
@ -195,7 +195,7 @@ struct msi_domain_info;
|
||||
* msi_create_irq_domain() and related interfaces
|
||||
*
|
||||
* @msi_check, @msi_prepare, @msi_finish, @set_desc and @handle_error
|
||||
* are callbacks used by msi_irq_domain_alloc_irqs() and related
|
||||
* are callbacks used by msi_domain_alloc_irqs() and related
|
||||
* interfaces which are based on msi_desc.
|
||||
*/
|
||||
struct msi_domain_ops {
|
||||
|
@ -59,6 +59,13 @@ static inline void of_pci_check_probe_only(void) { }
|
||||
int of_pci_get_host_bridge_resources(struct device_node *dev,
|
||||
unsigned char busno, unsigned char bus_max,
|
||||
struct list_head *resources, resource_size_t *io_base);
|
||||
#else
|
||||
static inline int of_pci_get_host_bridge_resources(struct device_node *dev,
|
||||
unsigned char busno, unsigned char bus_max,
|
||||
struct list_head *resources, resource_size_t *io_base)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_OF) && defined(CONFIG_PCI_MSI)
|
||||
|
@ -1257,8 +1257,6 @@ struct msix_entry {
|
||||
u16 entry; /* driver uses to specify entry, OS writes */
|
||||
};
|
||||
|
||||
void pci_msi_setup_pci_dev(struct pci_dev *dev);
|
||||
|
||||
#ifdef CONFIG_PCI_MSI
|
||||
int pci_msi_vec_count(struct pci_dev *dev);
|
||||
void pci_msi_shutdown(struct pci_dev *dev);
|
||||
|
@ -2496,6 +2496,11 @@
|
||||
#define PCI_DEVICE_ID_KORENIX_JETCARDF3 0x17ff
|
||||
|
||||
#define PCI_VENDOR_ID_NETRONOME 0x19ee
|
||||
#define PCI_DEVICE_ID_NETRONOME_NFP3200 0x3200
|
||||
#define PCI_DEVICE_ID_NETRONOME_NFP3240 0x3240
|
||||
#define PCI_DEVICE_ID_NETRONOME_NFP4000 0x4000
|
||||
#define PCI_DEVICE_ID_NETRONOME_NFP6000 0x6000
|
||||
#define PCI_DEVICE_ID_NETRONOME_NFP6000_VF 0x6003
|
||||
|
||||
#define PCI_VENDOR_ID_QMI 0x1a32
|
||||
|
||||
|
@ -1061,6 +1061,7 @@ void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,
|
||||
__irq_set_handler(virq, handler, 0, handler_name);
|
||||
irq_set_handler_data(virq, handler_data);
|
||||
}
|
||||
EXPORT_SYMBOL(irq_domain_set_info);
|
||||
|
||||
/**
|
||||
* irq_domain_reset_irq_data - Clear hwirq, chip and chip_data in @irq_data
|
||||
|
@ -109,9 +109,11 @@ static int msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
if (irq_find_mapping(domain, hwirq) > 0)
|
||||
return -EEXIST;
|
||||
|
||||
if (domain->parent) {
|
||||
ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_irqs; i++) {
|
||||
ret = ops->msi_init(domain, info, virq + i, hwirq + i, arg);
|
||||
|
Loading…
Reference in New Issue
Block a user