mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-16 19:13:39 -04:00
Merge tag 'pci-v7.2-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci
Pull pci updates from Bjorn Helgaas:
"Enumeration:
- Remove MPS/MRRS Kconfig settings (CONFIG_PCIE_BUS_*) that worked
around a WiFi device defect; use a quirk or boot-time
"pci=pcie_bus_tune_*" kernel parameter instead (Bjorn Helgaas)
- Always lift 2.5GT/s restriction in PCIe failed link retraining to
avoid clamping a link to 2.5GT/s after hot-plug changes the device
(Maciej W. Rozycki)
- Request bus reassignment when not probe-only to fix an enumeration
regression on Marvell CN106XX and possibly other DT-based systems
(Ratheesh Kannoth)
- Fix procfs race between pci_proc_init() and pci_bus_add_device()
that resulted in 'proc_dir_entry ... already registered' warnings
and pointer corruption (Krzysztof Wilczyński)
- Fix sysfs race that causes 'duplicate filename' warnings and boot
panics by converting PCI resource files to static attributes
(Krzysztof Wilczyński)
- Expose sysfs 'resourceN_resize' attributes only on platforms with
PCI mmap (Krzysztof Wilczyński)
- Require CAP_SYS_ADMIN to write to sysfs 'resourceN_resize'
attributes (Krzysztof Wilczyński)
- Add security_locked_down(LOCKDOWN_PCI_ACCESS) to alpha PCI resource
mmap path to match the generic path (Krzysztof Wilczyński)
- Use kstrtobool() to parse the 'rom' attribute input to avoid the
unexpected behavior of enabling the ROM when writing '0' with no
trailing newline (Krzysztof Wilczyński)
Resource management:
- Improve resource claim logging for debuggability (Ilpo Järvinen)
- Clean up several uses of const parameters (Ilpo Järvinen)
- Check option ROM header signatures and lengths before accessing to
avoid page faults and alignment faults (Guixin Liu)
ASPM:
- Don't reconfigure ASPM when entering low-power D-state; only do it
when returning back to D0 (Carlos Bilbao)
Power management:
- During suspend, set power state to 'unknown' for all devices, not
just those with drivers (Lukas Wunner)
- Skip restoring Resizable BARs and VF Resizable BARs if device
doesn't respond to config reads, to avoid invalid array accesses
(Marco Nenciarini)
- Add pci_suspend_retains_context() so drivers can tell whether
devices retain internal state across suspend/resume, since some
platforms reset devices on suspend; use this in nvme to avoid
issues on Qcom RCs (Manivannan Sadhasivam)
Power control:
- Only to power on/off devices that actually support power control to
avoid poking at incompatible devices mentioned in DT (Manivannan
Sadhasivam)
Virtualization and resets:
- Log device readiness timeouts as errors, not warnings, because the
device is likely unusable in this case (Bjorn Helgaas)
- Wait for device readiness after soft reset (D3hot ->
D0uninitialized transition), when the device may respond with
Request Retry Status (RRS) if it needs more time to initialize
(Bjorn Helgaas)
- Drop unnecessary retries when restoring BARs because resets should
now already include all required delays (Lukas Wunner)
- Avoid FLR for MediaTek MT7925 WiFi, where FLR fails after a VM
terminates uncleanly (Jose Ignacio Tornos Martinez)
- Avoid SBR for Qualcomm WCN6855/WCN7850 WiFi, SDX62/SDX65 modems,
which seem not to support it correctly (Jose Ignacio Tornos
Martinez)
Peer-to-peer DMA:
- Prevent P2PDMA as well as CPU access to non-mappable BARs, e.g.,
s390 ISM BARs (Matt Evans)
- Add Intel QAT, DSA, IAA devices to whitelist (Lukas Wunner)
Endpoint framework:
- Add endpoint controller APIs for use by function drivers to
discover auxiliary blocks like DMA engines (Koichiro Den)
- Remember DesignWare eDMA engine base/size and expose them via the
EPC aux-resource API (Koichiro Den)
- Add endpoint embedded doorbell fallback, used if MSI allocation
fails (Koichiro Den)
- Validate BAR index and remove dead BAR read in endpoint doorbell
test (Carlos Bilbao)
- Unwind MSI/MSI-X vectors if NTB initialization fails part-way
through (Koichiro Den)
- Cache sleepable pci_irq_vector() value at ISR setup to avoid
calling it from hardirq context (Koichiro Den)
- Call sleepable pci_epc_raise_irq() from a work item instead of
atomic context, e.g., when setting bits in NTB peer doorbells in
the ntb_peer_db_set() path (Koichiro Den)
- Report 0-based vNTB doorbell vector to account for link event 0 and
historically skipped slot 1 (Koichiro Den)
- Prevent configfs writes to vNTB db_count and other values that are
already in use after EPC attach (Koichiro Den)
- Account for vNTB db_valid reserved slots (link event 0 and
historically skipped slot 1) so they don't appear as valid
doorbells (Koichiro Den)
- Implement vNTB .db_vector_count()/mask() for doorbells so clients
can use multiple vectors and avoid thundering herds (Koichiro Den)
- Report 0-based NTB doorbell vector to account for link event 0 and
historically skipped slot 1 (Koichiro Den)
- Fix doorbell bitmask and IRQ vector handling to clear only
specified bits, use the correct vector for non-contiguous Linux IRQ
numbers, and validate incoming vectors (Koichiro Den)
- Implement NTB .db_vector_count()/mask() for doorbells so clients
can use multiple vectors (Koichiro Den)
Native PCIe controller infrastructure:
- Add pci_host_common_link_train_delay() for the mandatory delay
after > 5GT/s Link training completes and use it for cadence HPA,
j721e, LGA; dwc; aardvark, mediatek-gen3, rzg3s (Hans Zhang)
- Protect root bus removal with rescan lock in altera, brcmstb,
cadence, dwc, iproc, mediatek, plda, rockchip to prevent
use-after-free or crashes when racing with sysfs rescan or hotplug
(Hans Zhang)
- Add pci_host_common_parse_ports() for use by any native driver to
parse Root Port properties (per-Link features like width, speed,
PHY, power and reset control, etc should be described in Root Port
stanzas, not the host bridge; currently only reset GPIOs
implemented) (Sherry Sun)
New native PCIe controller drivers:
- Add DT binding and driver for UltraRISC DP1000 PCIe controller
(Xincheng Zhang, Jia Wang)
Altera PCIe controller driver:
- Do not dispose of the parent IRQ mapping, which belongs to the
parent interrupt controller (Mahesh Vaidya)
- Fix chained IRQ handler ordering issue and resource leaks on probe
failure (Mahesh Vaidya)
AMD MDB PCIe controller driver:
- Assert PERST# on shutdown so any connected Endpoints are held in
reset during shutdown (Sai Krishna Musham)
Amlogic Meson PCIe controller driver:
- Propagate devm_add_action_or_reset() failure to fix probe error
path (Shuvam Pandey)
- Add .remove() callback to deinitialize the host bridge and power
off the PHY (Shuvam Pandey)
Broadcom iProc PCIe controller driver:
- Restore .map_irq() assignment; its removal broke INTx on the iproc
platform bus driver (Mark Tomlinson)
Broadcom STB PCIe controller driver:
- No change, but products using certain WiFi devices may be affected
by removal of CONFIG_PCIE_BUS_* (see above)
Freescale i.MX6 PCIe controller driver:
- Move IMX6SX_GPR12_PCIE_TEST_POWERDOWN handling into the core reset
functions (Richard Zhu)
- Assert PERST# before enabling regulators to ensure that even if
power is enabled, endpoint stays inactive until REFCLK is stable
(Sherry Sun)
- Parse reset properties in Root Port nodes (falling back to host
bridge) to help support Key E connectors and the pwrctrl framework
(Sherry Sun)
- Configure i.MX95 REF_USE_PAD before PHY reset (Richard Zhu)
- Assert i.MX95 ref_clk_en after reference clock stabilizes (Richard
Zhu)
- Integrate new pwrctrl API for DTs with Root Port-level power
supplies (Sherry Sun)
Intel Gateway PCIe controller driver:
- Enable clock before PHY init for correct ordering (Florian Eckert)
- Add .start_link() callback so the driver works again (Florian
Eckert)
- Stop overwriting the ATU base address discovered by
dw_pcie_get_resources() (Florian Eckert)
- Add DT 'atu' region since this is hardware-specific, and fall back
to driver default if lacking (Florian Eckert)
Loongson PCIe controller driver:
- Ignore downstream devices only on internal bridges to avoid
Loongson hardware issue (Rong Zhang)
- Quirk old Loongson-3C6000 bridges that advertise incorrect
supported link speeds (Ziyao Li)
Marvell MVEBU PCIe controller driver:
- Use fixed-width interrupt masks to avoid truncation in 64-bit
builds (Rosen Penev)
MediaTek PCIe controller driver:
- Use FIELD_PREP() to fix incorrect operator precedence in
PCIE_FTS_NUM_L0 (Li RongQing)
- Fix IRQ domain leak when port fails to enable (Manivannan
Sadhasivam)
- Use actual physical address for MSI message address instead of
virt_to_phys() (Manivannan Sadhasivam)
- Add EcoNet EN7528 to DT binding (Caleb James DeLisle)
MediaTek PCIe Gen3 controller driver:
- Deassert PCIE_PHY_RSTB so REFCLK is stable for at least 100ms
(PCIE_T_PVPERL_MS) before deasserting PERST# (Jian Yang)
- Add .shutdown() to assert PERST# before powering down device (Jian
Yang)
- Do full device power down on removal, including asserting PERST#,
when removing driver (Chen-Yu Tsai)
- Fix a 'failed to create pwrctrl devices' error message that was
inadvertently skipped (Chen-Yu Tsai)
NVIDIA Tegra194 PCIe controller driver:
- Program the DesignWare PORT_AFR L1 entrance latency based on the
'aspm-l1-entry-delay-ns' DT property (Manikanta Maddireddy)
Qualcomm PCIe controller driver:
- Add Eliza SoC compatible in DT binding (Krishna Chaitanya Chundru)
- Set max OPP during resume so DBI register accesses don't fail with
NoC errors (Qiang Yu)
- Add pci_host_common_d3cold_possible() to determine whether
downstream devices are already in D3hot and wakeup-enabled devices
are capable of generating PME from D3cold (Krishna Chaitanya
Chundru)
- Add .get_ltssm() callback to get the LTSSM status without DBI,
since DBI may be inaccessible after PME_Turn_Off (Krishna Chaitanya
Chundru)
- Power down PHY via PARF_PHY_CTRL before disabling rails/clocks to
avoid power leakage (Krishna Chaitanya Chundru)
- Decide whether suspend should put the link in L2 and power down
using pci_host_common_d3cold_possible() instead of checking whether
ASPM L1 is enabled (Krishna Chaitanya Chundru)
- Add qcom D3cold support to tear down interconnect bandwidth and OPP
votes (Krishna Chaitanya Chundru)
- Handle unsupported mixed PERST#/PHY DT configurations, e.g., PHY in
RP node while PERST# is in the RC node, but warn about the DT issue
(Qiang Yu)
- Program T_POWER_ON based on DT 't-power-on-us' property in case
hardware advertises incorrect values (Krishna Chaitanya Chundru)
- Disable ASPM L0s for SA8775P (Shawn Guo)
- Initialize DWC MSI lock for firmware-managed ECAM hosts, which
don't use the dw_pcie_host_init() path that initializes the lock
(Yadu M G)
Renesas RZ/G3S PCIe controller driver:
- Add RZ/V2N DT support (Lad Prabhakar)
SOPHGO PCIe controller driver:
- Add 'dma-coherent' DT property for sg2042-pcie driver (Han Gao)
Synopsys DesignWare PCIe controller driver:
- Apply ECRC TLP Digest workaround for all DesignWare cores prior to
5.10a, not just 4.90a and 5.00a (Manikanta Maddireddy)
- Use common struct dw_pcie 'mode' rather than duplicating it in
artpec6, dra7xx, dwc-pcie, and keembay driver structs (Hans Zhang)
- Use DEFINE_SHOW_ATTRIBUTE for ltssm_status debugfs to reduce
boilerplate and fix a seq_file memory leak by including a
.release() callback (Hans Zhang)
- Fix a signedness bug in fault injection test code (Dan Carpenter)
- Avoid NULL pointer dereference when tearing down debugfs for
controller that lacks RAS DES capability (Shuvam Pandey)
MicroSemi Switchtec management driver:
- Add Gen6 Device IDs (Ben Reed)
Miscellaneous:
- Remove unused gpio.h include from amd-mdb, designware-plat, fu740,
visconti drivers (Andy Shevchenko)
- Fix typos in documentation (josh ziegler)
- Use FIELD_MODIFY() instead of open-coding it (Hans Zhang)"
* tag 'pci-v7.2-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci: (168 commits)
PCI/sysfs: Use kstrtobool() to parse the ROM attribute input
PCI/sysfs: Limit BAR resize attribute scope to platforms with PCI mmap
PCI/sysfs: Remove pci_create_legacy_files() and pci_sysfs_init()
PCI/sysfs: Convert legacy I/O and memory attributes to static definitions
PCI/sysfs: Add __weak pci_legacy_has_sparse() helper
alpha/PCI: Compute legacy size in pci_mmap_legacy_page_range()
PCI: Add macros for legacy I/O and memory address space sizes
PCI/sysfs: Remove pci_{create,remove}_sysfs_dev_files()
alpha/PCI: Convert resource files to static attributes
alpha/PCI: Add static PCI resource attribute macros
alpha/PCI: Remove WARN from __pci_mmap_fits() and __legacy_mmap_fits()
alpha/PCI: Fix __pci_mmap_fits() overflow for zero-length BARs
alpha/PCI: Use PCI resource accessor macros
alpha/PCI: Use BAR index in sysfs attr->private instead of resource pointer
alpha/PCI: Add security_locked_down() check to pci_mmap_resource()
PCI/sysfs: Limit pci_sysfs_init() late_initcall compile scope
PCI/sysfs: Add stubs for pci_{create,remove}_sysfs_dev_files()
PCI/sysfs: Warn about BAR resize failure in __resource_resize_store()
PCI/sysfs: Convert PCI resource files to static attributes
PCI/proc: Fix race between pci_proc_init() and pci_bus_add_device()
...
This commit is contained in:
@@ -338,7 +338,7 @@ the PCI_IRQ_MSI and PCI_IRQ_MSIX flags will fail, so try to always
|
||||
specify PCI_IRQ_INTX as well.
|
||||
|
||||
Drivers that have different interrupt handlers for MSI/MSI-X and
|
||||
legacy INTx should chose the right one based on the msi_enabled
|
||||
legacy INTx should choose the right one based on the msi_enabled
|
||||
and msix_enabled flags in the pci_dev structure after calling
|
||||
pci_alloc_irq_vectors.
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ register its service with the PCI Express Port Bus driver (see
|
||||
section 5.2.1 & 5.2.2). It is important that a service driver
|
||||
initializes the pcie_port_service_driver data structure, included in
|
||||
header file /include/linux/pcieport_if.h, before calling these APIs.
|
||||
Failure to do so will result an identity mismatch, which prevents
|
||||
Failure to do so will result in an identity mismatch, which prevents
|
||||
the PCI Express Port Bus driver from loading a service driver.
|
||||
|
||||
pcie_port_service_register
|
||||
|
||||
@@ -66,16 +66,34 @@ properties:
|
||||
- const: dma
|
||||
|
||||
reset-gpio:
|
||||
deprecated: true
|
||||
description: Should specify the GPIO for controlling the PCI bus device
|
||||
reset signal. It's not polarity aware and defaults to active-low reset
|
||||
sequence (L=reset state, H=operation state) (optional required).
|
||||
This property is deprecated, instead of referencing this property from the
|
||||
host bridge node, use the reset-gpios property from the root port node.
|
||||
|
||||
reset-gpio-active-high:
|
||||
deprecated: true
|
||||
description: If present then the reset sequence using the GPIO
|
||||
specified in the "reset-gpio" property is reversed (H=reset state,
|
||||
L=operation state) (optional required).
|
||||
This property is deprecated along with the reset-gpio property above, use
|
||||
the reset-gpios property from the root port node.
|
||||
type: boolean
|
||||
|
||||
pcie@0:
|
||||
description:
|
||||
Describe the i.MX6 PCIe Root Port.
|
||||
type: object
|
||||
$ref: /schemas/pci/pci-pci-bridge.yaml#
|
||||
|
||||
properties:
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
@@ -236,6 +254,7 @@ unevaluatedProperties: false
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/imx6qdl-clock.h>
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
pcie: pcie@1ffc000 {
|
||||
@@ -262,5 +281,18 @@ examples:
|
||||
<&clks IMX6QDL_CLK_LVDS1_GATE>,
|
||||
<&clks IMX6QDL_CLK_PCIE_REF_125M>;
|
||||
clock-names = "pcie", "pcie_bus", "pcie_phy";
|
||||
|
||||
pcie_port0: pcie@0 {
|
||||
compatible = "pciclass,0604";
|
||||
device_type = "pci";
|
||||
reg = <0x0 0x0 0x0 0x0 0x0>;
|
||||
bus-range = <0x01 0xff>;
|
||||
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
ranges;
|
||||
|
||||
reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
};
|
||||
...
|
||||
|
||||
@@ -27,16 +27,20 @@ properties:
|
||||
- const: snps,dw-pcie
|
||||
|
||||
reg:
|
||||
minItems: 3
|
||||
items:
|
||||
- description: Controller control and status registers.
|
||||
- description: PCIe configuration registers.
|
||||
- description: Controller application registers.
|
||||
- description: Internal Address Translation Unit (iATU) registers.
|
||||
|
||||
reg-names:
|
||||
minItems: 3
|
||||
items:
|
||||
- const: dbi
|
||||
- const: config
|
||||
- const: app
|
||||
- const: atu
|
||||
|
||||
ranges:
|
||||
maxItems: 1
|
||||
@@ -95,8 +99,9 @@ examples:
|
||||
#size-cells = <2>;
|
||||
reg = <0xd0e00000 0x1000>,
|
||||
<0xd2000000 0x800000>,
|
||||
<0xd0a41000 0x1000>;
|
||||
reg-names = "dbi", "config", "app";
|
||||
<0xd0a41000 0x1000>,
|
||||
<0xd0ec0000 0x1000>;
|
||||
reg-names = "dbi", "config", "app", "atu";
|
||||
linux,pci-domain = <0>;
|
||||
max-link-speed = <4>;
|
||||
bus-range = <0x00 0x08>;
|
||||
|
||||
@@ -14,6 +14,7 @@ properties:
|
||||
oneOf:
|
||||
- enum:
|
||||
- airoha,an7583-pcie
|
||||
- econet,en7528-pcie
|
||||
- mediatek,mt2712-pcie
|
||||
- mediatek,mt7622-pcie
|
||||
- mediatek,mt7629-pcie
|
||||
@@ -226,6 +227,31 @@ allOf:
|
||||
|
||||
mediatek,pbus-csr: false
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: econet,en7528-pcie
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
clock-names:
|
||||
maxItems: 1
|
||||
|
||||
resets: false
|
||||
|
||||
reset-names: false
|
||||
|
||||
power-domains: false
|
||||
|
||||
mediatek,pbus-csr: false
|
||||
|
||||
required:
|
||||
- phys
|
||||
- phy-names
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
|
||||
@@ -20,6 +20,7 @@ properties:
|
||||
- const: qcom,pcie-sm8550
|
||||
- items:
|
||||
- enum:
|
||||
- qcom,eliza-pcie
|
||||
- qcom,kaanapali-pcie
|
||||
- qcom,sar2130p-pcie
|
||||
- qcom,pcie-sm8650
|
||||
@@ -91,6 +92,55 @@ required:
|
||||
|
||||
allOf:
|
||||
- $ref: qcom,pcie-common.yaml#
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: qcom,eliza-pcie
|
||||
then:
|
||||
properties:
|
||||
reg:
|
||||
minItems: 6
|
||||
reg-names:
|
||||
minItems: 6
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: qcom,eliza-pcie
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 8
|
||||
maxItems: 8
|
||||
clock-names:
|
||||
minItems: 8
|
||||
maxItems: 8
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: qcom,eliza-pcie
|
||||
then:
|
||||
properties:
|
||||
interrupts:
|
||||
minItems: 9
|
||||
interrupt-names:
|
||||
minItems: 9
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: qcom,eliza-pcie
|
||||
then:
|
||||
properties:
|
||||
resets:
|
||||
minItems: 2
|
||||
reset-names:
|
||||
minItems: 2
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
|
||||
@@ -4,21 +4,27 @@
|
||||
$id: http://devicetree.org/schemas/pci/renesas,r9a08g045-pcie.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Renesas RZ/G3S PCIe host controller
|
||||
title: Renesas RZ/G3S PCIe host controller (and similar SoCs)
|
||||
|
||||
maintainers:
|
||||
- Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
|
||||
|
||||
description:
|
||||
Renesas RZ/G3{E,S} PCIe host controllers comply with PCIe
|
||||
Base Specification 4.0 and support up to 5 GT/s (Gen2) for RZ/G3S and
|
||||
up to 8 GT/s (Gen3) for RZ/G3E.
|
||||
description: |
|
||||
PCIe host controller found in Renesas RZ/G3S and similar SoCs complies
|
||||
with PCIe Base Specification 4.0 and supports different link speeds
|
||||
depending on the SoC variant:
|
||||
- Gen2 (5 GT/s): RZ/G3S
|
||||
- Gen3 (8 GT/s): RZ/G3E, RZ/V2N
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- renesas,r9a08g045-pcie # RZ/G3S
|
||||
- renesas,r9a09g047-pcie # RZ/G3E
|
||||
oneOf:
|
||||
- enum:
|
||||
- renesas,r9a08g045-pcie # RZ/G3S
|
||||
- renesas,r9a09g047-pcie # RZ/G3E
|
||||
- items:
|
||||
- const: renesas,r9a09g056-pcie # RZ/V2N
|
||||
- const: renesas,r9a09g047-pcie
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
@@ -152,6 +158,7 @@ patternProperties:
|
||||
enum:
|
||||
- 0x0033
|
||||
- 0x0039
|
||||
- 0x003b
|
||||
|
||||
clocks:
|
||||
items:
|
||||
|
||||
@@ -30,6 +30,8 @@ properties:
|
||||
device-id:
|
||||
const: 0x2042
|
||||
|
||||
dma-coherent: true
|
||||
|
||||
msi-parent: true
|
||||
|
||||
allOf:
|
||||
@@ -60,5 +62,6 @@ examples:
|
||||
vendor-id = <0x1f1c>;
|
||||
device-id = <0x2042>;
|
||||
cdns,no-bar-match-nbits = <48>;
|
||||
dma-coherent;
|
||||
msi-parent = <&msi>;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/pci/ultrarisc,dp1000-pcie.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: UltraRISC DP1000 PCIe Host Controller
|
||||
|
||||
description:
|
||||
UltraRISC DP1000 SoC PCIe host controller is based on the DesignWare PCIe IP.
|
||||
|
||||
maintainers:
|
||||
- Xincheng Zhang <zhangxincheng@ultrarisc.com>
|
||||
- Jia Wang <wangjia@ultrarisc.com>
|
||||
|
||||
allOf:
|
||||
- $ref: /schemas/pci/snps,dw-pcie.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ultrarisc,dp1000-pcie
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: Data Bus Interface (DBI) registers.
|
||||
- description: PCIe configuration space region.
|
||||
|
||||
reg-names:
|
||||
items:
|
||||
- const: dbi
|
||||
- const: config
|
||||
|
||||
num-lanes:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [4, 16]
|
||||
description: Number of lanes to use.
|
||||
|
||||
interrupts:
|
||||
items:
|
||||
- description: MSI interrupt
|
||||
- description: Legacy INTA interrupt
|
||||
- description: Legacy INTB interrupt
|
||||
- description: Legacy INTC interrupt
|
||||
- description: Legacy INTD interrupt
|
||||
|
||||
interrupt-names:
|
||||
items:
|
||||
- const: msi
|
||||
- const: inta
|
||||
- const: intb
|
||||
- const: intc
|
||||
- const: intd
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reg-names
|
||||
- interrupts
|
||||
- interrupt-names
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
soc {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
pcie@21000000 {
|
||||
compatible = "ultrarisc,dp1000-pcie";
|
||||
reg = <0x0 0x21000000 0x0 0x01000000>,
|
||||
<0x0 0x4fff0000 0x0 0x00010000>;
|
||||
reg-names = "dbi", "config";
|
||||
ranges = <0x81000000 0x0 0x4fbf0000 0x0 0x4fbf0000 0x0 0x00400000>,
|
||||
<0x82000000 0x0 0x40000000 0x0 0x40000000 0x0 0x0fbf0000>,
|
||||
<0xc3000000 0x40 0x00000000 0x40 0x00000000 0xd 0x00000000>;
|
||||
#address-cells = <3>;
|
||||
#size-cells = <2>;
|
||||
#interrupt-cells = <1>;
|
||||
device_type = "pci";
|
||||
dma-coherent;
|
||||
bus-range = <0x0 0xff>;
|
||||
num-lanes = <16>;
|
||||
interrupt-parent = <&plic>;
|
||||
interrupts = <43>, <44>, <45>, <46>, <47>;
|
||||
interrupt-names = "msi", "inta", "intb", "intc", "intd";
|
||||
interrupt-map-mask = <0x0 0x0 0x0 0x7>;
|
||||
interrupt-map = <0x0 0x0 0x0 0x1 &plic 44>,
|
||||
<0x0 0x0 0x0 0x2 &plic 45>,
|
||||
<0x0 0x0 0x0 0x3 &plic 46>,
|
||||
<0x0 0x0 0x0 0x4 &plic 47>;
|
||||
};
|
||||
};
|
||||
@@ -21050,6 +21050,14 @@ S: Maintained
|
||||
F: Documentation/devicetree/bindings/pci/starfive,jh7110-pcie.yaml
|
||||
F: drivers/pci/controller/plda/pcie-starfive.c
|
||||
|
||||
PCIE DRIVER FOR ULTRARISC DP1000
|
||||
M: Xincheng Zhang <zhangxincheng@ultrarisc.com>
|
||||
M: Jia Wang <wangjia@ultrarisc.com>
|
||||
L: linux-pci@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/pci/ultrarisc,dp1000-pcie.yaml
|
||||
F: drivers/pci/controller/dwc/pcie-ultrarisc.c
|
||||
|
||||
PCIE ENDPOINT DRIVER FOR QUALCOMM
|
||||
M: Manivannan Sadhasivam <mani@kernel.org>
|
||||
L: linux-pci@vger.kernel.org
|
||||
|
||||
@@ -84,8 +84,17 @@ extern int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val,
|
||||
extern int pci_mmap_legacy_page_range(struct pci_bus *bus,
|
||||
struct vm_area_struct *vma,
|
||||
enum pci_mmap_state mmap_state);
|
||||
extern void pci_adjust_legacy_attr(struct pci_bus *bus,
|
||||
enum pci_mmap_state mmap_type);
|
||||
extern bool pci_legacy_has_sparse(struct pci_bus *bus,
|
||||
enum pci_mmap_state type);
|
||||
#define HAVE_PCI_LEGACY 1
|
||||
|
||||
extern const struct attribute_group pci_dev_resource_attr_group;
|
||||
extern const struct attribute_group pci_dev_resource_sparse_attr_group;
|
||||
extern const struct attribute_group pci_dev_resource_dense_attr_group;
|
||||
|
||||
#define ARCH_PCI_DEV_GROUPS \
|
||||
&pci_dev_resource_attr_group, \
|
||||
&pci_dev_resource_sparse_attr_group, \
|
||||
&pci_dev_resource_dense_attr_group,
|
||||
|
||||
#endif /* __ALPHA_PCI_H */
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
static int hose_mmap_page_range(struct pci_controller *hose,
|
||||
@@ -36,20 +35,18 @@ static int hose_mmap_page_range(struct pci_controller *hose,
|
||||
static int __pci_mmap_fits(struct pci_dev *pdev, int num,
|
||||
struct vm_area_struct *vma, int sparse)
|
||||
{
|
||||
resource_size_t len = pci_resource_len(pdev, num);
|
||||
unsigned long nr, start, size;
|
||||
int shift = sparse ? 5 : 0;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
nr = vma_pages(vma);
|
||||
start = vma->vm_pgoff;
|
||||
size = ((pci_resource_len(pdev, num) - 1) >> (PAGE_SHIFT - shift)) + 1;
|
||||
size = ((len - 1) >> (PAGE_SHIFT - shift)) + 1;
|
||||
|
||||
if (start < size && size - start >= nr)
|
||||
return 1;
|
||||
WARN(1, "process \"%s\" tried to map%s 0x%08lx-0x%08lx on %s BAR %d "
|
||||
"(size 0x%08lx)\n",
|
||||
current->comm, sparse ? " sparse" : "", start, start + nr,
|
||||
pci_name(pdev), num, size);
|
||||
return 0;
|
||||
return start < size && size - start >= nr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,26 +65,25 @@ static int pci_mmap_resource(struct kobject *kobj,
|
||||
struct vm_area_struct *vma, int sparse)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
struct resource *res = attr->private;
|
||||
int barno = (unsigned long)attr->private;
|
||||
enum pci_mmap_state mmap_type;
|
||||
struct pci_bus_region bar;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
for (i = 0; i < PCI_STD_NUM_BARS; i++)
|
||||
if (res == &pdev->resource[i])
|
||||
break;
|
||||
if (i >= PCI_STD_NUM_BARS)
|
||||
return -ENODEV;
|
||||
ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (res->flags & IORESOURCE_MEM && iomem_is_exclusive(res->start))
|
||||
if (pci_resource_is_mem(pdev, barno) &&
|
||||
iomem_is_exclusive(pci_resource_start(pdev, barno)))
|
||||
return -EINVAL;
|
||||
|
||||
if (!__pci_mmap_fits(pdev, i, vma, sparse))
|
||||
if (!__pci_mmap_fits(pdev, barno, vma, sparse))
|
||||
return -EINVAL;
|
||||
|
||||
pcibios_resource_to_bus(pdev->bus, &bar, res);
|
||||
pcibios_resource_to_bus(pdev->bus, &bar, pci_resource_n(pdev, barno));
|
||||
vma->vm_pgoff += bar.start >> (PAGE_SHIFT - (sparse ? 5 : 0));
|
||||
mmap_type = res->flags & IORESOURCE_MEM ? pci_mmap_mem : pci_mmap_io;
|
||||
mmap_type = pci_resource_is_mem(pdev, barno) ? pci_mmap_mem : pci_mmap_io;
|
||||
|
||||
return hose_mmap_page_range(pdev->sysdata, vma, mmap_type, sparse);
|
||||
}
|
||||
@@ -106,34 +102,26 @@ static int pci_mmap_resource_dense(struct file *filp, struct kobject *kobj,
|
||||
return pci_mmap_resource(kobj, attr, vma, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_remove_resource_files - cleanup resource files
|
||||
* @pdev: pci_dev to cleanup
|
||||
*
|
||||
* If we created resource files for @dev, remove them from sysfs and
|
||||
* free their resources.
|
||||
*/
|
||||
void pci_remove_resource_files(struct pci_dev *pdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
|
||||
struct bin_attribute *res_attr;
|
||||
|
||||
res_attr = pdev->res_attr[i];
|
||||
if (res_attr) {
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, res_attr);
|
||||
kfree(res_attr);
|
||||
}
|
||||
|
||||
res_attr = pdev->res_attr_wc[i];
|
||||
if (res_attr) {
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, res_attr);
|
||||
kfree(res_attr);
|
||||
}
|
||||
}
|
||||
#define __pci_dev_resource_attr(_bar, _name, _suffix, _mmap) \
|
||||
static const struct bin_attribute \
|
||||
pci_dev_resource##_bar##_suffix##_attr = { \
|
||||
.attr = { .name = __stringify(_name), .mode = 0600 }, \
|
||||
.private = (void *)(unsigned long)(_bar), \
|
||||
.mmap = (_mmap), \
|
||||
}
|
||||
|
||||
#define pci_dev_resource_attr(_bar) \
|
||||
__pci_dev_resource_attr(_bar, resource##_bar,, \
|
||||
pci_mmap_resource_dense)
|
||||
|
||||
#define pci_dev_resource_sparse_attr(_bar) \
|
||||
__pci_dev_resource_attr(_bar, resource##_bar##_sparse, _sparse, \
|
||||
pci_mmap_resource_sparse)
|
||||
|
||||
#define pci_dev_resource_dense_attr(_bar) \
|
||||
__pci_dev_resource_attr(_bar, resource##_bar##_dense, _dense, \
|
||||
pci_mmap_resource_dense)
|
||||
|
||||
static int sparse_mem_mmap_fits(struct pci_dev *pdev, int num)
|
||||
{
|
||||
struct pci_bus_region bar;
|
||||
@@ -141,7 +129,7 @@ static int sparse_mem_mmap_fits(struct pci_dev *pdev, int num)
|
||||
long dense_offset;
|
||||
unsigned long sparse_size;
|
||||
|
||||
pcibios_resource_to_bus(pdev->bus, &bar, &pdev->resource[num]);
|
||||
pcibios_resource_to_bus(pdev->bus, &bar, pci_resource_n(pdev, num));
|
||||
|
||||
/* All core logic chips have 4G sparse address space, except
|
||||
CIA which has 16G (see xxx_SPARSE_MEM and xxx_DENSE_MEM
|
||||
@@ -153,109 +141,10 @@ static int sparse_mem_mmap_fits(struct pci_dev *pdev, int num)
|
||||
return bar.end < sparse_size;
|
||||
}
|
||||
|
||||
static int pci_create_one_attr(struct pci_dev *pdev, int num, char *name,
|
||||
char *suffix, struct bin_attribute *res_attr,
|
||||
unsigned long sparse)
|
||||
{
|
||||
size_t size = pci_resource_len(pdev, num);
|
||||
|
||||
sprintf(name, "resource%d%s", num, suffix);
|
||||
res_attr->mmap = sparse ? pci_mmap_resource_sparse :
|
||||
pci_mmap_resource_dense;
|
||||
res_attr->attr.name = name;
|
||||
res_attr->attr.mode = S_IRUSR | S_IWUSR;
|
||||
res_attr->size = sparse ? size << 5 : size;
|
||||
res_attr->private = &pdev->resource[num];
|
||||
return sysfs_create_bin_file(&pdev->dev.kobj, res_attr);
|
||||
}
|
||||
|
||||
static int pci_create_attr(struct pci_dev *pdev, int num)
|
||||
{
|
||||
/* allocate attribute structure, piggyback attribute name */
|
||||
int retval, nlen1, nlen2 = 0, res_count = 1;
|
||||
unsigned long sparse_base, dense_base;
|
||||
struct bin_attribute *attr;
|
||||
struct pci_controller *hose = pdev->sysdata;
|
||||
char *suffix, *attr_name;
|
||||
|
||||
suffix = ""; /* Assume bwx machine, normal resourceN files. */
|
||||
nlen1 = 10;
|
||||
|
||||
if (pdev->resource[num].flags & IORESOURCE_MEM) {
|
||||
sparse_base = hose->sparse_mem_base;
|
||||
dense_base = hose->dense_mem_base;
|
||||
if (sparse_base && !sparse_mem_mmap_fits(pdev, num)) {
|
||||
sparse_base = 0;
|
||||
suffix = "_dense";
|
||||
nlen1 = 16; /* resourceN_dense */
|
||||
}
|
||||
} else {
|
||||
sparse_base = hose->sparse_io_base;
|
||||
dense_base = hose->dense_io_base;
|
||||
}
|
||||
|
||||
if (sparse_base) {
|
||||
suffix = "_sparse";
|
||||
nlen1 = 17;
|
||||
if (dense_base) {
|
||||
nlen2 = 16; /* resourceN_dense */
|
||||
res_count = 2;
|
||||
}
|
||||
}
|
||||
|
||||
attr = kzalloc(sizeof(*attr) * res_count + nlen1 + nlen2, GFP_ATOMIC);
|
||||
if (!attr)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Create bwx, sparse or single dense file */
|
||||
attr_name = (char *)(attr + res_count);
|
||||
pdev->res_attr[num] = attr;
|
||||
retval = pci_create_one_attr(pdev, num, attr_name, suffix, attr,
|
||||
sparse_base);
|
||||
if (retval || res_count == 1)
|
||||
return retval;
|
||||
|
||||
/* Create dense file */
|
||||
attr_name += nlen1;
|
||||
attr++;
|
||||
pdev->res_attr_wc[num] = attr;
|
||||
return pci_create_one_attr(pdev, num, attr_name, "_dense", attr, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_create_resource_files - create resource files in sysfs for @pdev
|
||||
* @pdev: pci_dev in question
|
||||
*
|
||||
* Walk the resources in @dev creating files for each resource available.
|
||||
*
|
||||
* Return: %0 on success, or negative error code
|
||||
*/
|
||||
int pci_create_resource_files(struct pci_dev *pdev)
|
||||
{
|
||||
int i;
|
||||
int retval;
|
||||
|
||||
/* Expose the PCI resources from this device as files */
|
||||
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
|
||||
|
||||
/* skip empty resources */
|
||||
if (!pci_resource_len(pdev, i))
|
||||
continue;
|
||||
|
||||
retval = pci_create_attr(pdev, i);
|
||||
if (retval) {
|
||||
pci_remove_resource_files(pdev);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Legacy I/O bus mapping stuff. */
|
||||
|
||||
static int __legacy_mmap_fits(struct pci_controller *hose,
|
||||
struct vm_area_struct *vma,
|
||||
unsigned long res_size, int sparse)
|
||||
static int __legacy_mmap_fits(struct vm_area_struct *vma,
|
||||
unsigned long res_size)
|
||||
{
|
||||
unsigned long nr, start, size;
|
||||
|
||||
@@ -263,13 +152,7 @@ static int __legacy_mmap_fits(struct pci_controller *hose,
|
||||
start = vma->vm_pgoff;
|
||||
size = ((res_size - 1) >> PAGE_SHIFT) + 1;
|
||||
|
||||
if (start < size && size - start >= nr)
|
||||
return 1;
|
||||
WARN(1, "process \"%s\" tried to map%s 0x%08lx-0x%08lx on hose %d "
|
||||
"(size 0x%08lx)\n",
|
||||
current->comm, sparse ? " sparse" : "", start, start + nr,
|
||||
hose->index, size);
|
||||
return 0;
|
||||
return start < size && size - start >= nr;
|
||||
}
|
||||
|
||||
static inline int has_sparse(struct pci_controller *hose,
|
||||
@@ -290,36 +173,22 @@ int pci_mmap_legacy_page_range(struct pci_bus *bus, struct vm_area_struct *vma,
|
||||
int sparse = has_sparse(hose, mmap_type);
|
||||
unsigned long res_size;
|
||||
|
||||
res_size = (mmap_type == pci_mmap_mem) ? bus->legacy_mem->size :
|
||||
bus->legacy_io->size;
|
||||
if (!__legacy_mmap_fits(hose, vma, res_size, sparse))
|
||||
res_size = (mmap_type == pci_mmap_mem) ? PCI_LEGACY_MEM_SIZE :
|
||||
PCI_LEGACY_IO_SIZE;
|
||||
if (sparse)
|
||||
res_size <<= 5;
|
||||
|
||||
if (!__legacy_mmap_fits(vma, res_size))
|
||||
return -EINVAL;
|
||||
|
||||
return hose_mmap_page_range(hose, vma, mmap_type, sparse);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_adjust_legacy_attr - adjustment of legacy file attributes
|
||||
* @bus: bus to create files under
|
||||
* @mmap_type: I/O port or memory
|
||||
*
|
||||
* Adjust file name and size for sparse mappings.
|
||||
*/
|
||||
void pci_adjust_legacy_attr(struct pci_bus *bus, enum pci_mmap_state mmap_type)
|
||||
bool pci_legacy_has_sparse(struct pci_bus *bus, enum pci_mmap_state type)
|
||||
{
|
||||
struct pci_controller *hose = bus->sysdata;
|
||||
|
||||
if (!has_sparse(hose, mmap_type))
|
||||
return;
|
||||
|
||||
if (mmap_type == pci_mmap_mem) {
|
||||
bus->legacy_mem->attr.name = "legacy_mem_sparse";
|
||||
bus->legacy_mem->size <<= 5;
|
||||
} else {
|
||||
bus->legacy_io->attr.name = "legacy_io_sparse";
|
||||
bus->legacy_io->size <<= 5;
|
||||
}
|
||||
return;
|
||||
return has_sparse(hose, type);
|
||||
}
|
||||
|
||||
/* Legacy I/O bus read/write functions */
|
||||
@@ -370,3 +239,166 @@ int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val, size_t size)
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pci_dev_resource_attr(0);
|
||||
pci_dev_resource_attr(1);
|
||||
pci_dev_resource_attr(2);
|
||||
pci_dev_resource_attr(3);
|
||||
pci_dev_resource_attr(4);
|
||||
pci_dev_resource_attr(5);
|
||||
|
||||
pci_dev_resource_sparse_attr(0);
|
||||
pci_dev_resource_sparse_attr(1);
|
||||
pci_dev_resource_sparse_attr(2);
|
||||
pci_dev_resource_sparse_attr(3);
|
||||
pci_dev_resource_sparse_attr(4);
|
||||
pci_dev_resource_sparse_attr(5);
|
||||
|
||||
pci_dev_resource_dense_attr(0);
|
||||
pci_dev_resource_dense_attr(1);
|
||||
pci_dev_resource_dense_attr(2);
|
||||
pci_dev_resource_dense_attr(3);
|
||||
pci_dev_resource_dense_attr(4);
|
||||
pci_dev_resource_dense_attr(5);
|
||||
|
||||
static inline enum pci_mmap_state pci_bar_mmap_type(struct pci_dev *pdev,
|
||||
int bar)
|
||||
{
|
||||
return pci_resource_is_mem(pdev, bar) ? pci_mmap_mem : pci_mmap_io;
|
||||
}
|
||||
|
||||
static inline umode_t __pci_resource_attr_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int bar)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
|
||||
if (!pci_resource_len(pdev, bar))
|
||||
return 0;
|
||||
|
||||
return a->attr.mode;
|
||||
}
|
||||
|
||||
static umode_t pci_dev_resource_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int bar)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
struct pci_controller *hose = pdev->sysdata;
|
||||
|
||||
if (has_sparse(hose, pci_bar_mmap_type(pdev, bar)))
|
||||
return 0;
|
||||
|
||||
return __pci_resource_attr_is_visible(kobj, a, bar);
|
||||
}
|
||||
|
||||
static umode_t pci_dev_resource_sparse_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int bar)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
struct pci_controller *hose = pdev->sysdata;
|
||||
enum pci_mmap_state type = pci_bar_mmap_type(pdev, bar);
|
||||
|
||||
if (!has_sparse(hose, type))
|
||||
return 0;
|
||||
|
||||
if (type == pci_mmap_mem && !sparse_mem_mmap_fits(pdev, bar))
|
||||
return 0;
|
||||
|
||||
return __pci_resource_attr_is_visible(kobj, a, bar);
|
||||
}
|
||||
|
||||
static umode_t pci_dev_resource_dense_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int bar)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
struct pci_controller *hose = pdev->sysdata;
|
||||
enum pci_mmap_state type = pci_bar_mmap_type(pdev, bar);
|
||||
unsigned long dense_base;
|
||||
|
||||
if (!has_sparse(hose, type))
|
||||
return 0;
|
||||
|
||||
if (type == pci_mmap_mem && !sparse_mem_mmap_fits(pdev, bar))
|
||||
return __pci_resource_attr_is_visible(kobj, a, bar);
|
||||
|
||||
dense_base = (type == pci_mmap_mem) ? hose->dense_mem_base :
|
||||
hose->dense_io_base;
|
||||
if (!dense_base)
|
||||
return 0;
|
||||
|
||||
return __pci_resource_attr_is_visible(kobj, a, bar);
|
||||
}
|
||||
|
||||
static inline size_t __pci_dev_resource_bin_size(struct kobject *kobj,
|
||||
int bar, bool sparse)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
size_t size = pci_resource_len(pdev, bar);
|
||||
|
||||
return sparse ? size << 5 : size;
|
||||
}
|
||||
|
||||
static size_t pci_dev_resource_bin_size(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int bar)
|
||||
{
|
||||
return __pci_dev_resource_bin_size(kobj, bar, false);
|
||||
}
|
||||
|
||||
static size_t pci_dev_resource_sparse_bin_size(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int bar)
|
||||
{
|
||||
return __pci_dev_resource_bin_size(kobj, bar, true);
|
||||
}
|
||||
|
||||
static const struct bin_attribute *const pci_dev_resource_attrs[] = {
|
||||
&pci_dev_resource0_attr,
|
||||
&pci_dev_resource1_attr,
|
||||
&pci_dev_resource2_attr,
|
||||
&pci_dev_resource3_attr,
|
||||
&pci_dev_resource4_attr,
|
||||
&pci_dev_resource5_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct bin_attribute *const pci_dev_resource_sparse_attrs[] = {
|
||||
&pci_dev_resource0_sparse_attr,
|
||||
&pci_dev_resource1_sparse_attr,
|
||||
&pci_dev_resource2_sparse_attr,
|
||||
&pci_dev_resource3_sparse_attr,
|
||||
&pci_dev_resource4_sparse_attr,
|
||||
&pci_dev_resource5_sparse_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct bin_attribute *const pci_dev_resource_dense_attrs[] = {
|
||||
&pci_dev_resource0_dense_attr,
|
||||
&pci_dev_resource1_dense_attr,
|
||||
&pci_dev_resource2_dense_attr,
|
||||
&pci_dev_resource3_dense_attr,
|
||||
&pci_dev_resource4_dense_attr,
|
||||
&pci_dev_resource5_dense_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const struct attribute_group pci_dev_resource_attr_group = {
|
||||
.bin_attrs = pci_dev_resource_attrs,
|
||||
.is_bin_visible = pci_dev_resource_is_visible,
|
||||
.bin_size = pci_dev_resource_bin_size,
|
||||
};
|
||||
|
||||
const struct attribute_group pci_dev_resource_sparse_attr_group = {
|
||||
.bin_attrs = pci_dev_resource_sparse_attrs,
|
||||
.is_bin_visible = pci_dev_resource_sparse_is_visible,
|
||||
.bin_size = pci_dev_resource_sparse_bin_size,
|
||||
};
|
||||
|
||||
const struct attribute_group pci_dev_resource_dense_attr_group = {
|
||||
.bin_attrs = pci_dev_resource_dense_attrs,
|
||||
.is_bin_visible = pci_dev_resource_dense_is_visible,
|
||||
.bin_size = pci_dev_resource_bin_size,
|
||||
};
|
||||
|
||||
@@ -169,7 +169,7 @@ struct machdep_calls {
|
||||
|
||||
#ifdef CONFIG_PCI_IOV
|
||||
void (*pcibios_fixup_sriov)(struct pci_dev *pdev);
|
||||
resource_size_t (*pcibios_iov_resource_alignment)(struct pci_dev *, int resno);
|
||||
resource_size_t (*pcibios_iov_resource_alignment)(const struct pci_dev *, int resno);
|
||||
int (*pcibios_sriov_enable)(struct pci_dev *pdev, u16 num_vfs);
|
||||
int (*pcibios_sriov_disable)(struct pci_dev *pdev);
|
||||
#endif /* CONFIG_PCI_IOV */
|
||||
|
||||
@@ -82,8 +82,6 @@ extern int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val,
|
||||
extern int pci_mmap_legacy_page_range(struct pci_bus *bus,
|
||||
struct vm_area_struct *vma,
|
||||
enum pci_mmap_state mmap_state);
|
||||
extern void pci_adjust_legacy_attr(struct pci_bus *bus,
|
||||
enum pci_mmap_state mmap_type);
|
||||
#define HAVE_PCI_LEGACY 1
|
||||
|
||||
extern void pcibios_claim_one_bus(struct pci_bus *b);
|
||||
|
||||
@@ -254,7 +254,7 @@ resource_size_t pcibios_default_alignment(void)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCI_IOV
|
||||
resource_size_t pcibios_iov_resource_alignment(struct pci_dev *pdev, int resno)
|
||||
resource_size_t pcibios_iov_resource_alignment(const struct pci_dev *pdev, int resno)
|
||||
{
|
||||
if (ppc_md.pcibios_iov_resource_alignment)
|
||||
return ppc_md.pcibios_iov_resource_alignment(pdev, resno);
|
||||
|
||||
@@ -244,8 +244,8 @@ void pnv_pci_ioda_fixup_iov(struct pci_dev *pdev)
|
||||
}
|
||||
}
|
||||
|
||||
resource_size_t pnv_pci_iov_resource_alignment(struct pci_dev *pdev,
|
||||
int resno)
|
||||
resource_size_t pnv_pci_iov_resource_alignment(const struct pci_dev *pdev,
|
||||
int resno)
|
||||
{
|
||||
resource_size_t align = pci_iov_resource_size(pdev, resno);
|
||||
struct pnv_phb *phb = pci_bus_to_pnvhb(pdev->bus);
|
||||
|
||||
@@ -251,13 +251,14 @@ struct pnv_iov_data {
|
||||
struct resource holes[PCI_SRIOV_NUM_BARS];
|
||||
};
|
||||
|
||||
static inline struct pnv_iov_data *pnv_iov_get(struct pci_dev *pdev)
|
||||
static inline struct pnv_iov_data *pnv_iov_get(const struct pci_dev *pdev)
|
||||
{
|
||||
return pdev->dev.archdata.iov_data;
|
||||
}
|
||||
|
||||
void pnv_pci_ioda_fixup_iov(struct pci_dev *pdev);
|
||||
resource_size_t pnv_pci_iov_resource_alignment(struct pci_dev *pdev, int resno);
|
||||
resource_size_t pnv_pci_iov_resource_alignment(const struct pci_dev *pdev,
|
||||
int resno);
|
||||
|
||||
int pnv_pcibios_sriov_enable(struct pci_dev *pdev, u16 num_vfs);
|
||||
int pnv_pcibios_sriov_disable(struct pci_dev *pdev);
|
||||
|
||||
@@ -658,7 +658,8 @@ enum get_iov_fw_value_index {
|
||||
WDW_SIZE = 3 /* Get Window Size */
|
||||
};
|
||||
|
||||
static resource_size_t pseries_get_iov_fw_value(struct pci_dev *dev, int resno,
|
||||
static resource_size_t pseries_get_iov_fw_value(const struct pci_dev *dev,
|
||||
int resno,
|
||||
enum get_iov_fw_value_index value)
|
||||
{
|
||||
const int *indexes;
|
||||
@@ -788,7 +789,7 @@ static void pseries_pci_fixup_iov_resources(struct pci_dev *pdev)
|
||||
pseries_disable_sriov_resources(pdev);
|
||||
}
|
||||
|
||||
static resource_size_t pseries_pci_iov_resource_alignment(struct pci_dev *pdev,
|
||||
static resource_size_t pseries_pci_iov_resource_alignment(const struct pci_dev *pdev,
|
||||
int resno)
|
||||
{
|
||||
const __be32 *reg;
|
||||
|
||||
@@ -29,15 +29,10 @@
|
||||
#define ADF_4XXX_DEVICE_NAME "4xxx"
|
||||
#define ADF_420XX_DEVICE_NAME "420xx"
|
||||
#define ADF_6XXX_DEVICE_NAME "6xxx"
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_4XXX 0x4940
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_4XXXIOV 0x4941
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_401XX 0x4942
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_401XXIOV 0x4943
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_402XX 0x4944
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_402XXIOV 0x4945
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_420XX 0x4946
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_420XXIOV 0x4947
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_6XXX 0x4948
|
||||
#define PCI_DEVICE_ID_INTEL_QAT_6XXX_IOV 0x4949
|
||||
|
||||
#define ADF_DEVICE_FUSECTL_OFFSET 0x40
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
#endif
|
||||
|
||||
/* PCI Config */
|
||||
#define PCI_DEVICE_ID_INTEL_DSA_GNRD 0x11fb
|
||||
#define PCI_DEVICE_ID_INTEL_DSA_DMR 0x1212
|
||||
#define PCI_DEVICE_ID_INTEL_IAA_DMR 0x1216
|
||||
#define PCI_DEVICE_ID_INTEL_IAA_PTL 0xb02d
|
||||
#define PCI_DEVICE_ID_INTEL_IAA_WCL 0xfd2d
|
||||
|
||||
|
||||
@@ -1100,7 +1100,6 @@ static int pci_endpoint_test_doorbell(struct pci_endpoint_test *test)
|
||||
|
||||
data = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_DATA);
|
||||
addr = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_OFFSET);
|
||||
bar = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_BAR);
|
||||
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type);
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1);
|
||||
@@ -1108,6 +1107,11 @@ static int pci_endpoint_test_doorbell(struct pci_endpoint_test *test)
|
||||
pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0);
|
||||
|
||||
bar = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_BAR);
|
||||
if (bar < BAR_0 || bar >= PCI_STD_NUM_BARS) {
|
||||
dev_err(dev, "BAR %d reported by endpoint out of range [0, %u]\n",
|
||||
bar, PCI_STD_NUM_BARS - 1);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
writel(data, test->bar[bar] + addr);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
@@ -43,6 +44,18 @@
|
||||
#define NTB_EPF_DB_DATA(n) (0x34 + (n) * 4)
|
||||
#define NTB_EPF_DB_OFFSET(n) (0xB4 + (n) * 4)
|
||||
|
||||
/*
|
||||
* Legacy doorbell slot layout when paired with pci-epf-*ntb:
|
||||
*
|
||||
* slot 0 : reserved for link events
|
||||
* slot 1 : unused (historical extra offset)
|
||||
* slot 2 : DB#0
|
||||
* slot 3 : DB#1
|
||||
* ...
|
||||
*
|
||||
* Thus, NTB_EPF_MIN_DB_COUNT=3 means that we at least create vectors for
|
||||
* doorbells DB#0 and DB#1.
|
||||
*/
|
||||
#define NTB_EPF_MIN_DB_COUNT 3
|
||||
#define NTB_EPF_MAX_DB_COUNT 31
|
||||
|
||||
@@ -69,8 +82,21 @@ enum epf_ntb_bar {
|
||||
NTB_BAR_NUM,
|
||||
};
|
||||
|
||||
enum epf_irq_slot {
|
||||
EPF_IRQ_LINK = 0,
|
||||
EPF_IRQ_RESERVED_DB, /* Historically skipped slot */
|
||||
EPF_IRQ_DB_START,
|
||||
};
|
||||
|
||||
#define NTB_EPF_MAX_MW_COUNT (NTB_BAR_NUM - BAR_MW1)
|
||||
|
||||
struct ntb_epf_dev;
|
||||
|
||||
struct ntb_epf_irq_ctx {
|
||||
struct ntb_epf_dev *ndev;
|
||||
unsigned int irq_no;
|
||||
};
|
||||
|
||||
struct ntb_epf_dev {
|
||||
struct ntb_dev ntb;
|
||||
struct device *dev;
|
||||
@@ -90,8 +116,9 @@ struct ntb_epf_dev {
|
||||
unsigned int self_spad;
|
||||
unsigned int peer_spad;
|
||||
|
||||
int db_val;
|
||||
atomic64_t db_val;
|
||||
u64 db_valid_mask;
|
||||
struct ntb_epf_irq_ctx irq_ctx[NTB_EPF_MAX_DB_COUNT + 1];
|
||||
};
|
||||
|
||||
#define ntb_ndev(__ntb) container_of(__ntb, struct ntb_epf_dev, ntb)
|
||||
@@ -315,16 +342,29 @@ static int ntb_epf_link_disable(struct ntb_dev *ntb)
|
||||
|
||||
static irqreturn_t ntb_epf_vec_isr(int irq, void *dev)
|
||||
{
|
||||
struct ntb_epf_dev *ndev = dev;
|
||||
int irq_no;
|
||||
struct ntb_epf_irq_ctx *ctx = dev;
|
||||
struct ntb_epf_dev *ndev = ctx->ndev;
|
||||
unsigned int db_vector;
|
||||
unsigned int irq_no = ctx->irq_no;
|
||||
|
||||
irq_no = irq - pci_irq_vector(ndev->ntb.pdev, 0);
|
||||
ndev->db_val = irq_no + 1;
|
||||
|
||||
if (irq_no == 0)
|
||||
if (irq_no == EPF_IRQ_LINK) {
|
||||
ntb_link_event(&ndev->ntb);
|
||||
else
|
||||
ntb_db_event(&ndev->ntb, irq_no);
|
||||
} else if (irq_no == EPF_IRQ_RESERVED_DB) {
|
||||
dev_warn_ratelimited(ndev->dev,
|
||||
"Unexpected reserved doorbell slot IRQ received\n");
|
||||
} else {
|
||||
db_vector = irq_no - EPF_IRQ_DB_START;
|
||||
if (ndev->db_count < NTB_EPF_MIN_DB_COUNT ||
|
||||
db_vector >= ndev->db_count - 1) {
|
||||
dev_warn_ratelimited(ndev->dev,
|
||||
"Unexpected doorbell vector %u (db_count %u)\n",
|
||||
db_vector, ndev->db_count);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
atomic64_or(BIT_ULL(db_vector), &ndev->db_val);
|
||||
ntb_db_event(&ndev->ntb, db_vector);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@@ -350,31 +390,30 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max)
|
||||
argument &= ~MSIX_ENABLE;
|
||||
}
|
||||
|
||||
ndev->db_count = irq - 1;
|
||||
for (i = 0; i < irq; i++) {
|
||||
ndev->irq_ctx[i].ndev = ndev;
|
||||
ndev->irq_ctx[i].irq_no = i;
|
||||
ret = request_irq(pci_irq_vector(pdev, i), ntb_epf_vec_isr,
|
||||
0, "ntb_epf", ndev);
|
||||
0, "ntb_epf", &ndev->irq_ctx[i]);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to request irq\n");
|
||||
goto err_request_irq;
|
||||
goto err_free_irq;
|
||||
}
|
||||
}
|
||||
|
||||
ndev->db_count = irq - 1;
|
||||
|
||||
ret = ntb_epf_send_command(ndev, CMD_CONFIGURE_DOORBELL,
|
||||
argument | irq);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to configure doorbell\n");
|
||||
goto err_configure_db;
|
||||
goto err_free_irq;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_configure_db:
|
||||
for (i = 0; i < ndev->db_count + 1; i++)
|
||||
free_irq(pci_irq_vector(pdev, i), ndev);
|
||||
|
||||
err_request_irq:
|
||||
err_free_irq:
|
||||
while (i--)
|
||||
free_irq(pci_irq_vector(pdev, i), &ndev->irq_ctx[i]);
|
||||
pci_free_irq_vectors(pdev);
|
||||
|
||||
return ret;
|
||||
@@ -395,6 +434,36 @@ static u64 ntb_epf_db_valid_mask(struct ntb_dev *ntb)
|
||||
return ntb_ndev(ntb)->db_valid_mask;
|
||||
}
|
||||
|
||||
static int ntb_epf_db_vector_count(struct ntb_dev *ntb)
|
||||
{
|
||||
struct ntb_epf_dev *ndev = ntb_ndev(ntb);
|
||||
unsigned int db_count = ndev->db_count;
|
||||
|
||||
/*
|
||||
* db_count includes an extra skipped slot due to the legacy
|
||||
* doorbell layout. Expose only the real doorbell vectors.
|
||||
*/
|
||||
if (db_count < NTB_EPF_MIN_DB_COUNT)
|
||||
return 0;
|
||||
|
||||
return db_count - 1;
|
||||
}
|
||||
|
||||
static u64 ntb_epf_db_vector_mask(struct ntb_dev *ntb, int db_vector)
|
||||
{
|
||||
int nr_vec;
|
||||
|
||||
/*
|
||||
* db_count includes one skipped slot in the legacy layout. Valid
|
||||
* doorbell vectors are therefore [0 .. (db_count - 2)].
|
||||
*/
|
||||
nr_vec = ntb_epf_db_vector_count(ntb);
|
||||
if (db_vector < 0 || db_vector >= nr_vec)
|
||||
return 0;
|
||||
|
||||
return BIT_ULL(db_vector);
|
||||
}
|
||||
|
||||
static int ntb_epf_db_set_mask(struct ntb_dev *ntb, u64 db_bits)
|
||||
{
|
||||
return 0;
|
||||
@@ -473,6 +542,14 @@ static int ntb_epf_peer_mw_get_addr(struct ntb_dev *ntb, int idx,
|
||||
static int ntb_epf_peer_db_set(struct ntb_dev *ntb, u64 db_bits)
|
||||
{
|
||||
struct ntb_epf_dev *ndev = ntb_ndev(ntb);
|
||||
/*
|
||||
* ffs() returns a 1-based bit index (bit 0 -> 1).
|
||||
*
|
||||
* With slot 0 reserved for link events, DB#0 would naturally map to
|
||||
* slot 1. Historically an extra +1 offset was added, so DB#0 maps to
|
||||
* slot 2 and slot 1 remains unused. Keep this mapping for
|
||||
* backward-compatibility.
|
||||
*/
|
||||
u32 interrupt_num = ffs(db_bits) + 1;
|
||||
struct device *dev = ndev->dev;
|
||||
u32 db_entry_size;
|
||||
@@ -499,7 +576,7 @@ static u64 ntb_epf_db_read(struct ntb_dev *ntb)
|
||||
{
|
||||
struct ntb_epf_dev *ndev = ntb_ndev(ntb);
|
||||
|
||||
return ndev->db_val;
|
||||
return atomic64_read(&ndev->db_val);
|
||||
}
|
||||
|
||||
static int ntb_epf_db_clear_mask(struct ntb_dev *ntb, u64 db_bits)
|
||||
@@ -511,7 +588,7 @@ static int ntb_epf_db_clear(struct ntb_dev *ntb, u64 db_bits)
|
||||
{
|
||||
struct ntb_epf_dev *ndev = ntb_ndev(ntb);
|
||||
|
||||
ndev->db_val = 0;
|
||||
atomic64_and(~db_bits, &ndev->db_val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -521,6 +598,8 @@ static const struct ntb_dev_ops ntb_epf_ops = {
|
||||
.spad_count = ntb_epf_spad_count,
|
||||
.peer_mw_count = ntb_epf_peer_mw_count,
|
||||
.db_valid_mask = ntb_epf_db_valid_mask,
|
||||
.db_vector_count = ntb_epf_db_vector_count,
|
||||
.db_vector_mask = ntb_epf_db_vector_mask,
|
||||
.db_set_mask = ntb_epf_db_set_mask,
|
||||
.mw_set_trans = ntb_epf_mw_set_trans,
|
||||
.mw_clear_trans = ntb_epf_mw_clear_trans,
|
||||
@@ -552,6 +631,12 @@ static int ntb_epf_init_dev(struct ntb_epf_dev *ndev)
|
||||
struct device *dev = ndev->dev;
|
||||
int ret;
|
||||
|
||||
ndev->mw_count = readl(ndev->ctrl_reg + NTB_EPF_MW_COUNT);
|
||||
if (ndev->mw_count > NTB_EPF_MAX_MW_COUNT) {
|
||||
dev_err(dev, "Unsupported MW count: %u\n", ndev->mw_count);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* One Link interrupt and rest doorbell interrupt */
|
||||
ret = ntb_epf_init_isr(ndev, NTB_EPF_MIN_DB_COUNT + 1,
|
||||
NTB_EPF_MAX_DB_COUNT + 1);
|
||||
@@ -560,15 +645,13 @@ static int ntb_epf_init_dev(struct ntb_epf_dev *ndev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ndev->db_valid_mask = BIT_ULL(ndev->db_count) - 1;
|
||||
ndev->mw_count = readl(ndev->ctrl_reg + NTB_EPF_MW_COUNT);
|
||||
/*
|
||||
* ndev->db_count includes an extra skipped slot due to the legacy
|
||||
* doorbell layout, hence -1.
|
||||
*/
|
||||
ndev->db_valid_mask = BIT_ULL(ndev->db_count - 1) - 1;
|
||||
ndev->spad_count = readl(ndev->ctrl_reg + NTB_EPF_SPAD_COUNT);
|
||||
|
||||
if (ndev->mw_count > NTB_EPF_MAX_MW_COUNT) {
|
||||
dev_err(dev, "Unsupported MW count: %u\n", ndev->mw_count);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -662,7 +745,7 @@ static void ntb_epf_cleanup_isr(struct ntb_epf_dev *ndev)
|
||||
ntb_epf_send_command(ndev, CMD_TEARDOWN_DOORBELL, ndev->db_count + 1);
|
||||
|
||||
for (i = 0; i < ndev->db_count + 1; i++)
|
||||
free_irq(pci_irq_vector(pdev, i), ndev);
|
||||
free_irq(pci_irq_vector(pdev, i), &ndev->irq_ctx[i]);
|
||||
pci_free_irq_vectors(pdev);
|
||||
}
|
||||
|
||||
|
||||
@@ -3950,6 +3950,7 @@ static int nvme_suspend(struct device *dev)
|
||||
* use host managed nvme power settings for lowest idle power if
|
||||
* possible. This should have quicker resume latency than a full device
|
||||
* shutdown. But if the firmware is involved after the suspend or the
|
||||
* platform has any limitation in waking from low power states or the
|
||||
* device does not support any non-default power states, shut down the
|
||||
* device fully.
|
||||
*
|
||||
@@ -3958,7 +3959,7 @@ static int nvme_suspend(struct device *dev)
|
||||
* down, so as to allow the platform to achieve its minimum low-power
|
||||
* state (which may not be possible if the link is up).
|
||||
*/
|
||||
if (pm_suspend_via_firmware() || !ctrl->npss ||
|
||||
if (!pci_suspend_retains_context(pdev) || !ctrl->npss ||
|
||||
!pcie_aspm_enabled(pdev) ||
|
||||
(ndev->ctrl.quirks & NVME_QUIRK_SIMPLE_SUSPEND))
|
||||
return nvme_disable_prepare_reset(ndev, true);
|
||||
|
||||
@@ -251,63 +251,6 @@ config PCI_DYNAMIC_OF_NODES
|
||||
Once this option is selected, the device tree nodes will be generated
|
||||
for all PCI bridges.
|
||||
|
||||
choice
|
||||
prompt "PCI Express hierarchy optimization setting"
|
||||
default PCIE_BUS_DEFAULT
|
||||
depends on EXPERT
|
||||
help
|
||||
MPS (Max Payload Size) and MRRS (Max Read Request Size) are PCIe
|
||||
device parameters that affect performance and the ability to
|
||||
support hotplug and peer-to-peer DMA.
|
||||
|
||||
The following choices set the MPS and MRRS optimization strategy
|
||||
at compile-time. The choices are the same as those offered for
|
||||
the kernel command-line parameter 'pci', i.e.,
|
||||
'pci=pcie_bus_tune_off', 'pci=pcie_bus_safe',
|
||||
'pci=pcie_bus_perf', and 'pci=pcie_bus_peer2peer'.
|
||||
|
||||
This is a compile-time setting and can be overridden by the above
|
||||
command-line parameters. If unsure, choose PCIE_BUS_DEFAULT.
|
||||
|
||||
config PCIE_BUS_TUNE_OFF
|
||||
bool "Tune Off"
|
||||
help
|
||||
Use the BIOS defaults; don't touch MPS at all. This is the same
|
||||
as booting with 'pci=pcie_bus_tune_off'.
|
||||
|
||||
config PCIE_BUS_DEFAULT
|
||||
bool "Default"
|
||||
help
|
||||
Default choice; ensure that the MPS matches upstream bridge.
|
||||
|
||||
config PCIE_BUS_SAFE
|
||||
bool "Safe"
|
||||
help
|
||||
Use largest MPS that boot-time devices support. If you have a
|
||||
closed system with no possibility of adding new devices, this
|
||||
will use the largest MPS that's supported by all devices. This
|
||||
is the same as booting with 'pci=pcie_bus_safe'.
|
||||
|
||||
config PCIE_BUS_PERFORMANCE
|
||||
bool "Performance"
|
||||
help
|
||||
Use MPS and MRRS for best performance. Ensure that a given
|
||||
device's MPS is no larger than its parent MPS, which allows us to
|
||||
keep all switches/bridges to the max MPS supported by their
|
||||
parent. This is the same as booting with 'pci=pcie_bus_perf'.
|
||||
|
||||
config PCIE_BUS_PEER2PEER
|
||||
bool "Peer2peer"
|
||||
help
|
||||
Set MPS = 128 for all devices. MPS configuration effected by the
|
||||
other options could cause the MPS on one root port to be
|
||||
different than that of the MPS on another, which may cause
|
||||
hot-added devices or peer-to-peer DMA to fail. Set MPS to the
|
||||
smallest possible value (128B) system-wide to avoid these issues.
|
||||
This is the same as booting with 'pci=pcie_bus_peer2peer'.
|
||||
|
||||
endchoice
|
||||
|
||||
config VGA_ARB
|
||||
bool "VGA Arbitration" if EXPERT
|
||||
default y
|
||||
|
||||
@@ -354,7 +354,6 @@ void pci_bus_add_device(struct pci_dev *dev)
|
||||
pci_fixup_device(pci_fixup_final, dev);
|
||||
if (pci_is_bridge(dev))
|
||||
of_pci_make_dev_node(dev);
|
||||
pci_create_sysfs_dev_files(dev);
|
||||
pci_proc_attach_device(dev);
|
||||
pci_bridge_d3_update(dev);
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ static int j721e_pcie_set_link_speed(struct j721e_pcie *pcie,
|
||||
(pcie_get_link_speed(link_speed) == PCI_SPEED_UNKNOWN))
|
||||
link_speed = 2;
|
||||
|
||||
pcie->cdns_pcie->max_link_speed = link_speed;
|
||||
val = link_speed - 1;
|
||||
ret = regmap_update_bits(syscon, offset, GENERATION_SEL_MASK, val);
|
||||
if (ret)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "pcie-cadence.h"
|
||||
#include "pcie-cadence-host-common.h"
|
||||
#include "../pci-host-common.h"
|
||||
|
||||
#define LINK_RETRAIN_TIMEOUT HZ
|
||||
|
||||
@@ -115,6 +116,9 @@ int cdns_pcie_host_start_link(struct cdns_pcie_rc *rc,
|
||||
if (!ret && rc->quirk_retrain_flag)
|
||||
ret = cdns_pcie_retrain(pcie, pcie_link_up);
|
||||
|
||||
if (!ret)
|
||||
pci_host_common_link_train_delay(pcie->max_link_speed);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_pcie_host_start_link);
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
#include "pcie-cadence.h"
|
||||
#include "pcie-cadence-host-common.h"
|
||||
#include "../pci-host-common.h"
|
||||
#include "../../pci.h"
|
||||
|
||||
static u8 bar_aperture_mask[] = {
|
||||
[RP_BAR0] = 0x3F,
|
||||
@@ -304,6 +306,8 @@ int cdns_pcie_hpa_host_link_setup(struct cdns_pcie_rc *rc)
|
||||
ret = cdns_pcie_host_wait_for_link(pcie, cdns_pcie_hpa_link_up);
|
||||
if (ret)
|
||||
dev_dbg(dev, "PCIe link never came up\n");
|
||||
else
|
||||
pci_host_common_link_train_delay(pcie->max_link_speed);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -313,6 +317,7 @@ int cdns_pcie_hpa_host_setup(struct cdns_pcie_rc *rc)
|
||||
{
|
||||
struct device *dev = rc->pcie.dev;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct device_node *np = dev->of_node;
|
||||
struct pci_host_bridge *bridge;
|
||||
enum cdns_pcie_rp_bar bar;
|
||||
struct cdns_pcie *pcie;
|
||||
@@ -343,6 +348,9 @@ int cdns_pcie_hpa_host_setup(struct cdns_pcie_rc *rc)
|
||||
rc->cfg_res = res;
|
||||
}
|
||||
|
||||
if (pcie->max_link_speed < 1)
|
||||
pcie->max_link_speed = of_pci_get_max_link_speed(np);
|
||||
|
||||
/* Put EROM Bar aperture to 0 */
|
||||
cdns_pcie_hpa_writel(pcie, REG_BANK_IP_CFG_CTRL_REG, CDNS_PCIE_EROM, 0x0);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "pcie-cadence.h"
|
||||
#include "pcie-cadence-host-common.h"
|
||||
#include "../../pci.h"
|
||||
|
||||
static u8 bar_aperture_mask[] = {
|
||||
[RP_BAR0] = 0x1F,
|
||||
@@ -365,8 +366,10 @@ void cdns_pcie_host_disable(struct cdns_pcie_rc *rc)
|
||||
struct pci_host_bridge *bridge;
|
||||
|
||||
bridge = pci_host_bridge_from_priv(rc);
|
||||
pci_lock_rescan_remove();
|
||||
pci_stop_root_bus(bridge->bus);
|
||||
pci_remove_root_bus(bridge->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
|
||||
cdns_pcie_host_deinit(rc);
|
||||
cdns_pcie_host_link_disable(rc);
|
||||
@@ -397,6 +400,9 @@ int cdns_pcie_host_setup(struct cdns_pcie_rc *rc)
|
||||
rc->device_id = 0xffff;
|
||||
of_property_read_u32(np, "device-id", &rc->device_id);
|
||||
|
||||
if (pcie->max_link_speed < 1)
|
||||
pcie->max_link_speed = of_pci_get_max_link_speed(np);
|
||||
|
||||
pcie->reg_base = devm_platform_ioremap_resource_byname(pdev, "reg");
|
||||
if (IS_ERR(pcie->reg_base)) {
|
||||
dev_err(dev, "missing \"reg\"\n");
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <linux/phy/phy.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#include "../../pci.h"
|
||||
|
||||
/* High Performance Architecture (HPA) PCIe controller registers */
|
||||
#define CDNS_PCIE_HPA_IP_REG_BANK 0x01000000
|
||||
#define CDNS_PCIE_HPA_IP_CFG_CTRL_REG_BANK 0x01003C00
|
||||
@@ -119,15 +121,15 @@
|
||||
#define CDNS_PCIE_HPA_AT_OB_REGION_DESC0(r) (0x1008 + ((r) & 0x1F) * 0x0080)
|
||||
#define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK GENMASK(28, 24)
|
||||
#define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MEM \
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x0)
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_MEM_RDWR)
|
||||
#define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_IO \
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x2)
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_IO_RDWR)
|
||||
#define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_CONF_TYPE0 \
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x4)
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_CFG0_RDWR)
|
||||
#define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_CONF_TYPE1 \
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x5)
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_CFG1_RDWR)
|
||||
#define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_NORMAL_MSG \
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x10)
|
||||
FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_MSG)
|
||||
|
||||
/* Region r Outbound PCIe Descriptor Register */
|
||||
#define CDNS_PCIE_HPA_AT_OB_REGION_DESC1(r) (0x100C + ((r) & 0x1F) * 0x0080)
|
||||
|
||||
@@ -86,6 +86,7 @@ struct cdns_plat_pcie_of_data {
|
||||
* @ops: Platform-specific ops to control various inputs from Cadence PCIe
|
||||
* wrapper
|
||||
* @cdns_pcie_reg_offsets: Register bank offsets for different SoC
|
||||
* @max_link_speed: Maximum supported link speed
|
||||
*/
|
||||
struct cdns_pcie {
|
||||
void __iomem *reg_base;
|
||||
@@ -98,6 +99,7 @@ struct cdns_pcie {
|
||||
struct device_link **link;
|
||||
const struct cdns_pcie_ops *ops;
|
||||
const struct cdns_plat_pcie_of_data *cdns_pcie_reg_offsets;
|
||||
int max_link_speed;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,6 +114,7 @@ config PCI_IMX6_HOST
|
||||
depends on PCI_MSI
|
||||
select PCIE_DW_HOST
|
||||
select PCI_IMX6
|
||||
select PCI_PWRCTRL_GENERIC
|
||||
help
|
||||
Enables support for the PCIe controller in the i.MX SoCs to
|
||||
work in Root Complex mode. The PCI controller on i.MX is based
|
||||
@@ -560,4 +561,17 @@ config PCIE_VISCONTI_HOST
|
||||
Say Y here if you want PCIe controller support on Toshiba Visconti SoC.
|
||||
This driver supports TMPV7708 SoC.
|
||||
|
||||
config PCIE_ULTRARISC
|
||||
tristate "UltraRISC PCIe host controller"
|
||||
depends on ARCH_ULTRARISC || COMPILE_TEST
|
||||
select PCIE_DW_HOST
|
||||
select PCI_MSI
|
||||
default y if ARCH_ULTRARISC
|
||||
help
|
||||
Enables support for the PCIe controller in the UltraRISC SoC.
|
||||
This driver supports UR-DP1000 SoC.
|
||||
|
||||
By default, this symbol is enabled when ARCH_ULTRARISC is active,
|
||||
requiring no further configuration on that platform.
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -39,6 +39,7 @@ obj-$(CONFIG_PCIE_RCAR_GEN4) += pcie-rcar-gen4.o
|
||||
obj-$(CONFIG_PCIE_SPACEMIT_K1) += pcie-spacemit-k1.o
|
||||
obj-$(CONFIG_PCIE_STM32_HOST) += pcie-stm32.o
|
||||
obj-$(CONFIG_PCIE_STM32_EP) += pcie-stm32-ep.o
|
||||
obj-$(CONFIG_PCIE_ULTRARISC) += pcie-ultrarisc.o
|
||||
|
||||
# The following drivers are for devices that use the generic ACPI
|
||||
# pci_root.c driver but don't support standard ECAM config access.
|
||||
|
||||
@@ -92,7 +92,6 @@ struct dra7xx_pcie {
|
||||
struct phy **phy;
|
||||
struct irq_domain *irq_domain;
|
||||
struct clk *clk;
|
||||
enum dw_pcie_device_mode mode;
|
||||
};
|
||||
|
||||
struct dra7xx_pcie_of_data {
|
||||
@@ -328,7 +327,7 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg)
|
||||
dev_dbg(dev, "Link Request Reset\n");
|
||||
|
||||
if (reg & LINK_UP_EVT) {
|
||||
if (dra7xx->mode == DW_PCIE_EP_TYPE)
|
||||
if (dra7xx->pci->mode == DW_PCIE_EP_TYPE)
|
||||
dw_pcie_ep_linkup(ep);
|
||||
dev_dbg(dev, "Link-up state change\n");
|
||||
}
|
||||
@@ -828,7 +827,7 @@ static int dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
default:
|
||||
dev_err(dev, "INVALID device type %d\n", mode);
|
||||
}
|
||||
dra7xx->mode = mode;
|
||||
dra7xx->pci->mode = mode;
|
||||
|
||||
ret = devm_request_threaded_irq(dev, irq, NULL, dra7xx_pcie_irq_handler,
|
||||
IRQF_SHARED | IRQF_ONESHOT,
|
||||
@@ -841,7 +840,7 @@ static int dra7xx_pcie_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
|
||||
err_deinit:
|
||||
if (dra7xx->mode == DW_PCIE_RC_TYPE)
|
||||
if (dra7xx->pci->mode == DW_PCIE_RC_TYPE)
|
||||
dw_pcie_host_deinit(&dra7xx->pci->pp);
|
||||
else
|
||||
dw_pcie_ep_deinit(&dra7xx->pci->ep);
|
||||
@@ -865,7 +864,7 @@ static int dra7xx_pcie_suspend(struct device *dev)
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
u32 val;
|
||||
|
||||
if (dra7xx->mode != DW_PCIE_RC_TYPE)
|
||||
if (pci->mode != DW_PCIE_RC_TYPE)
|
||||
return 0;
|
||||
|
||||
/* clear MSE */
|
||||
@@ -882,7 +881,7 @@ static int dra7xx_pcie_resume(struct device *dev)
|
||||
struct dw_pcie *pci = dra7xx->pci;
|
||||
u32 val;
|
||||
|
||||
if (dra7xx->mode != DW_PCIE_RC_TYPE)
|
||||
if (pci->mode != DW_PCIE_RC_TYPE)
|
||||
return 0;
|
||||
|
||||
/* set MSE */
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci-pwrctrl.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
@@ -34,6 +35,7 @@
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "../../pci.h"
|
||||
#include "../pci-host-common.h"
|
||||
#include "pcie-designware.h"
|
||||
|
||||
#define IMX8MQ_GPR_PCIE_REF_USE_PAD BIT(9)
|
||||
@@ -137,6 +139,7 @@ struct imx_pcie_drvdata {
|
||||
const u32 mode_off[IMX_PCIE_MAX_INSTANCES];
|
||||
const u32 mode_mask[IMX_PCIE_MAX_INSTANCES];
|
||||
const struct pci_epc_features *epc_features;
|
||||
int (*select_ref_clk_src)(struct imx_pcie *pcie);
|
||||
int (*init_phy)(struct imx_pcie *pcie);
|
||||
int (*enable_ref_clk)(struct imx_pcie *pcie, bool enable);
|
||||
int (*core_reset)(struct imx_pcie *pcie, bool assert);
|
||||
@@ -152,7 +155,6 @@ struct imx_lut_data {
|
||||
|
||||
struct imx_pcie {
|
||||
struct dw_pcie *pci;
|
||||
struct gpio_desc *reset_gpiod;
|
||||
struct clk_bulk_data *clks;
|
||||
int num_clks;
|
||||
bool supports_clkreq;
|
||||
@@ -168,6 +170,8 @@ struct imx_pcie {
|
||||
u32 tx_swing_full;
|
||||
u32 tx_swing_low;
|
||||
struct regulator *vpcie;
|
||||
struct regulator *vpcie_aux;
|
||||
bool vpcie_aux_enabled;
|
||||
struct regulator *vph;
|
||||
void __iomem *phy_base;
|
||||
|
||||
@@ -247,10 +251,26 @@ static unsigned int imx_pcie_grp_offset(const struct imx_pcie *imx_pcie)
|
||||
return imx_pcie->controller_id == 1 ? IOMUXC_GPR16 : IOMUXC_GPR14;
|
||||
}
|
||||
|
||||
static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie)
|
||||
static int imx95_pcie_select_ref_clk_src(struct imx_pcie *imx_pcie)
|
||||
{
|
||||
bool ext = imx_pcie->enable_ext_refclk;
|
||||
|
||||
/*
|
||||
* Regarding the Signal Descriptions of i.MX95 PCIe PHY, ref_use_pad is
|
||||
* used to select reference clock connected to a pair of pads.
|
||||
*
|
||||
* Any change in this input must be followed by phy_reset assertion.
|
||||
*/
|
||||
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL,
|
||||
IMX95_PCIE_REF_USE_PAD,
|
||||
ext ? IMX95_PCIE_REF_USE_PAD : 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie)
|
||||
{
|
||||
/*
|
||||
* ERR051624: The Controller Without Vaux Cannot Exit L23 Ready
|
||||
* Through Beacon or PERST# De-assertion
|
||||
@@ -269,13 +289,6 @@ static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie)
|
||||
IMX95_PCIE_PHY_CR_PARA_SEL,
|
||||
IMX95_PCIE_PHY_CR_PARA_SEL);
|
||||
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL,
|
||||
IMX95_PCIE_REF_USE_PAD,
|
||||
ext ? IMX95_PCIE_REF_USE_PAD : 0);
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0,
|
||||
IMX95_PCIE_REF_CLKEN,
|
||||
ext ? 0 : IMX95_PCIE_REF_CLKEN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -665,14 +678,6 @@ static int imx_pcie_attach_pd(struct device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx6sx_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable)
|
||||
{
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6SX_GPR12_PCIE_TEST_POWERDOWN,
|
||||
enable ? 0 : IMX6SX_GPR12_PCIE_TEST_POWERDOWN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx6q_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
@@ -732,7 +737,29 @@ static void imx95_pcie_clkreq_override(struct imx_pcie *imx_pcie, bool enable)
|
||||
|
||||
static int imx95_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable)
|
||||
{
|
||||
bool ext = imx_pcie->enable_ext_refclk;
|
||||
|
||||
imx95_pcie_clkreq_override(imx_pcie, enable);
|
||||
/*
|
||||
* The ref_clk_en signal must remain de-asserted until the
|
||||
* reference clock is running at appropriate frequency, at which
|
||||
* point this bit can be asserted. For lower power states where
|
||||
* the reference clock to the PHY is disabled, it may also be
|
||||
* de-asserted.
|
||||
* +------------------- -+--------+----------------+
|
||||
* | External clock mode | Enable | PCIE_REF_CLKEN |
|
||||
* +---------------------+--------+----------------+
|
||||
* | TRUE | X | 1b'0 |
|
||||
* +---------------------+--------+----------------+
|
||||
* | FALSE | TRUE | 1b'1 |
|
||||
* +---------------------+--------+----------------+
|
||||
* | FALSE | FALSE | 1b'0 |
|
||||
* +---------------------+--------+----------------+
|
||||
*/
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0,
|
||||
IMX95_PCIE_REF_CLKEN,
|
||||
ext || !enable ? 0 : IMX95_PCIE_REF_CLKEN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -786,6 +813,9 @@ static int imx6sx_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert)
|
||||
if (assert)
|
||||
regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6SX_GPR12_PCIE_TEST_POWERDOWN);
|
||||
else
|
||||
regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12,
|
||||
IMX6SX_GPR12_PCIE_TEST_POWERDOWN);
|
||||
|
||||
/* Force PCIe PHY reset */
|
||||
regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR5, IMX6SX_GPR5_PCIE_BTNRST_RESET,
|
||||
@@ -1221,16 +1251,70 @@ static void imx_pcie_disable_device(struct pci_host_bridge *bridge,
|
||||
imx_pcie_remove_lut(imx_pcie, pci_dev_id(pdev));
|
||||
}
|
||||
|
||||
static int imx_pcie_parse_legacy_binding(struct imx_pcie *pcie)
|
||||
{
|
||||
struct device *dev = pcie->pci->dev;
|
||||
struct pci_host_bridge *bridge = pcie->pci->pp.bridge;
|
||||
struct pci_host_port *port;
|
||||
struct pci_host_perst *perst;
|
||||
struct gpio_desc *reset;
|
||||
|
||||
reset = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS);
|
||||
if (IS_ERR(reset))
|
||||
return PTR_ERR(reset);
|
||||
|
||||
if (!reset)
|
||||
return 0;
|
||||
|
||||
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
|
||||
if (!port)
|
||||
return -ENOMEM;
|
||||
|
||||
perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL);
|
||||
if (!perst)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&port->perst);
|
||||
perst->desc = reset;
|
||||
INIT_LIST_HEAD(&perst->list);
|
||||
list_add_tail(&perst->list, &port->perst);
|
||||
|
||||
INIT_LIST_HEAD(&port->list);
|
||||
list_add_tail(&port->list, &bridge->ports);
|
||||
|
||||
return devm_add_action_or_reset(dev, pci_host_common_delete_ports,
|
||||
&bridge->ports);
|
||||
}
|
||||
|
||||
static void imx_pcie_vpcie_aux_disable(void *data)
|
||||
{
|
||||
struct regulator *vpcie_aux = data;
|
||||
|
||||
regulator_disable(vpcie_aux);
|
||||
}
|
||||
|
||||
static void imx_pcie_assert_perst(struct imx_pcie *imx_pcie, bool assert)
|
||||
{
|
||||
struct dw_pcie *pci = imx_pcie->pci;
|
||||
struct pci_host_bridge *bridge = pci->pp.bridge;
|
||||
struct pci_host_perst *perst;
|
||||
struct pci_host_port *port;
|
||||
|
||||
if (!bridge || list_empty(&bridge->ports))
|
||||
return;
|
||||
|
||||
if (assert) {
|
||||
gpiod_set_value_cansleep(imx_pcie->reset_gpiod, 1);
|
||||
} else {
|
||||
if (imx_pcie->reset_gpiod) {
|
||||
msleep(PCIE_T_PVPERL_MS);
|
||||
gpiod_set_value_cansleep(imx_pcie->reset_gpiod, 0);
|
||||
msleep(PCIE_RESET_CONFIG_WAIT_MS);
|
||||
list_for_each_entry(port, &bridge->ports, list) {
|
||||
list_for_each_entry(perst, &port->perst, list)
|
||||
gpiod_direction_output(perst->desc, 1);
|
||||
}
|
||||
} else {
|
||||
mdelay(PCIE_T_PVPERL_MS);
|
||||
list_for_each_entry(port, &bridge->ports, list) {
|
||||
list_for_each_entry(perst, &port->perst, list)
|
||||
gpiod_direction_output(perst->desc, 0);
|
||||
}
|
||||
mdelay(PCIE_RESET_CONFIG_WAIT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1239,8 +1323,47 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||||
struct device *dev = pci->dev;
|
||||
struct imx_pcie *imx_pcie = to_imx_pcie(pci);
|
||||
struct pci_host_bridge *bridge = pp->bridge;
|
||||
int ret;
|
||||
|
||||
if (bridge && list_empty(&bridge->ports)) {
|
||||
/* Parse Root Port nodes if present */
|
||||
ret = pci_host_common_parse_ports(dev, bridge);
|
||||
if (ret) {
|
||||
if (ret != -ENODEV) {
|
||||
dev_err(dev, "Failed to parse Root Port nodes: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fall back to legacy binding for DT backwards
|
||||
* compatibility
|
||||
*/
|
||||
ret = imx_pcie_parse_legacy_binding(imx_pcie);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
imx_pcie_assert_perst(imx_pcie, true);
|
||||
|
||||
/* Keep 3.3Vaux supply enabled for entire PCIe controller lifecycle */
|
||||
if (imx_pcie->vpcie_aux && !imx_pcie->vpcie_aux_enabled) {
|
||||
ret = regulator_enable(imx_pcie->vpcie_aux);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable vpcie_aux regulator: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
imx_pcie->vpcie_aux_enabled = true;
|
||||
|
||||
ret = devm_add_action_or_reset(dev, imx_pcie_vpcie_aux_disable,
|
||||
imx_pcie->vpcie_aux);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Legacy regulator handling for DT backward compatibility. */
|
||||
if (imx_pcie->vpcie) {
|
||||
ret = regulator_enable(imx_pcie->vpcie);
|
||||
if (ret) {
|
||||
@@ -1250,25 +1373,39 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
}
|
||||
}
|
||||
|
||||
ret = pci_pwrctrl_create_devices(dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to create pwrctrl devices\n");
|
||||
goto err_reg_disable;
|
||||
}
|
||||
|
||||
ret = pci_pwrctrl_power_on_devices(dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to power on pwrctrl devices\n");
|
||||
goto err_pwrctrl_destroy;
|
||||
}
|
||||
|
||||
ret = imx_pcie_clk_enable(imx_pcie);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to enable pcie clocks: %d\n", ret);
|
||||
goto err_pwrctrl_power_off;
|
||||
}
|
||||
|
||||
if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) {
|
||||
pp->bridge->enable_device = imx_pcie_enable_device;
|
||||
pp->bridge->disable_device = imx_pcie_disable_device;
|
||||
}
|
||||
|
||||
if (imx_pcie->drvdata->select_ref_clk_src)
|
||||
imx_pcie->drvdata->select_ref_clk_src(imx_pcie);
|
||||
|
||||
imx_pcie_assert_core_reset(imx_pcie);
|
||||
imx_pcie_assert_perst(imx_pcie, true);
|
||||
|
||||
if (imx_pcie->drvdata->init_phy)
|
||||
imx_pcie->drvdata->init_phy(imx_pcie);
|
||||
|
||||
imx_pcie_configure_type(imx_pcie);
|
||||
|
||||
ret = imx_pcie_clk_enable(imx_pcie);
|
||||
if (ret) {
|
||||
dev_err(dev, "unable to enable pcie clocks: %d\n", ret);
|
||||
goto err_reg_disable;
|
||||
}
|
||||
|
||||
if (imx_pcie->phy) {
|
||||
ret = phy_init(imx_pcie->phy);
|
||||
if (ret) {
|
||||
@@ -1313,6 +1450,11 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
phy_exit(imx_pcie->phy);
|
||||
err_clk_disable:
|
||||
imx_pcie_clk_disable(imx_pcie);
|
||||
err_pwrctrl_power_off:
|
||||
pci_pwrctrl_power_off_devices(dev);
|
||||
err_pwrctrl_destroy:
|
||||
if (ret != -EPROBE_DEFER)
|
||||
pci_pwrctrl_destroy_devices(dev);
|
||||
err_reg_disable:
|
||||
if (imx_pcie->vpcie)
|
||||
regulator_disable(imx_pcie->vpcie);
|
||||
@@ -1331,6 +1473,7 @@ static void imx_pcie_host_exit(struct dw_pcie_rp *pp)
|
||||
}
|
||||
imx_pcie_clk_disable(imx_pcie);
|
||||
|
||||
pci_pwrctrl_power_off_devices(pci->dev);
|
||||
if (imx_pcie->vpcie)
|
||||
regulator_disable(imx_pcie->vpcie);
|
||||
}
|
||||
@@ -1677,13 +1820,6 @@ static int imx_pcie_probe(struct platform_device *pdev)
|
||||
return PTR_ERR(imx_pcie->phy_base);
|
||||
}
|
||||
|
||||
/* Fetch GPIOs */
|
||||
imx_pcie->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(imx_pcie->reset_gpiod))
|
||||
return dev_err_probe(dev, PTR_ERR(imx_pcie->reset_gpiod),
|
||||
"unable to get reset gpio\n");
|
||||
gpiod_set_consumer_name(imx_pcie->reset_gpiod, "PCIe reset");
|
||||
|
||||
/* Fetch clocks */
|
||||
imx_pcie->num_clks = devm_clk_bulk_get_all(dev, &imx_pcie->clks);
|
||||
if (imx_pcie->num_clks < 0)
|
||||
@@ -1781,9 +1917,13 @@ static int imx_pcie_probe(struct platform_device *pdev)
|
||||
of_property_read_u32(node, "fsl,max-link-speed", &pci->max_link_speed);
|
||||
imx_pcie->supports_clkreq = of_property_read_bool(node, "supports-clkreq");
|
||||
|
||||
ret = devm_regulator_get_enable_optional(&pdev->dev, "vpcie3v3aux");
|
||||
if (ret < 0 && ret != -ENODEV)
|
||||
return dev_err_probe(dev, ret, "failed to enable Vaux supply\n");
|
||||
imx_pcie->vpcie_aux = devm_regulator_get_optional(&pdev->dev,
|
||||
"vpcie3v3aux");
|
||||
if (IS_ERR(imx_pcie->vpcie_aux)) {
|
||||
if (PTR_ERR(imx_pcie->vpcie_aux) != -ENODEV)
|
||||
return PTR_ERR(imx_pcie->vpcie_aux);
|
||||
imx_pcie->vpcie_aux = NULL;
|
||||
}
|
||||
|
||||
imx_pcie->vpcie = devm_regulator_get_optional(&pdev->dev, "vpcie");
|
||||
if (IS_ERR(imx_pcie->vpcie)) {
|
||||
@@ -1845,6 +1985,8 @@ static void imx_pcie_shutdown(struct platform_device *pdev)
|
||||
/* bring down link, so bootloader gets clean state in case of reboot */
|
||||
imx_pcie_assert_core_reset(imx_pcie);
|
||||
imx_pcie_assert_perst(imx_pcie, true);
|
||||
pci_pwrctrl_power_off_devices(&pdev->dev);
|
||||
pci_pwrctrl_destroy_devices(&pdev->dev);
|
||||
}
|
||||
|
||||
static const struct imx_pcie_drvdata drvdata[] = {
|
||||
@@ -1876,7 +2018,6 @@ static const struct imx_pcie_drvdata drvdata[] = {
|
||||
.mode_off[0] = IOMUXC_GPR12,
|
||||
.mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE,
|
||||
.init_phy = imx6sx_pcie_init_phy,
|
||||
.enable_ref_clk = imx6sx_pcie_enable_ref_clk,
|
||||
.core_reset = imx6sx_pcie_core_reset,
|
||||
.ops = &imx_pcie_host_ops,
|
||||
},
|
||||
@@ -1966,6 +2107,7 @@ static const struct imx_pcie_drvdata drvdata[] = {
|
||||
.mode_mask[0] = IMX95_PCIE_DEVICE_TYPE,
|
||||
.core_reset = imx95_pcie_core_reset,
|
||||
.init_phy = imx95_pcie_init_phy,
|
||||
.select_ref_clk_src = imx95_pcie_select_ref_clk_src,
|
||||
.wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock,
|
||||
.enable_ref_clk = imx95_pcie_enable_ref_clk,
|
||||
.clr_clkreq_override = imx95_pcie_clr_clkreq_override,
|
||||
@@ -2021,6 +2163,7 @@ static const struct imx_pcie_drvdata drvdata[] = {
|
||||
.ltssm_mask = IMX95_PCIE_LTSSM_EN,
|
||||
.mode_off[0] = IMX95_PE0_GEN_CTRL_1,
|
||||
.mode_mask[0] = IMX95_PCIE_DEVICE_TYPE,
|
||||
.select_ref_clk_src = imx95_pcie_select_ref_clk_src,
|
||||
.init_phy = imx95_pcie_init_phy,
|
||||
.core_reset = imx95_pcie_core_reset,
|
||||
.wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock,
|
||||
|
||||
@@ -204,7 +204,9 @@ static inline struct clk *meson_pcie_probe_clock(struct device *dev,
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk);
|
||||
ret = devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
return clk;
|
||||
}
|
||||
@@ -451,6 +453,14 @@ static int meson_pcie_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void meson_pcie_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct meson_pcie *mp = platform_get_drvdata(pdev);
|
||||
|
||||
dw_pcie_host_deinit(&mp->pci.pp);
|
||||
meson_pcie_power_off(mp);
|
||||
}
|
||||
|
||||
static const struct of_device_id meson_pcie_of_match[] = {
|
||||
{
|
||||
.compatible = "amlogic,axg-pcie",
|
||||
@@ -464,6 +474,7 @@ MODULE_DEVICE_TABLE(of, meson_pcie_of_match);
|
||||
|
||||
static struct platform_driver meson_pcie_driver = {
|
||||
.probe = meson_pcie_probe,
|
||||
.remove = meson_pcie_remove,
|
||||
.driver = {
|
||||
.name = "meson-pcie",
|
||||
.of_match_table = meson_pcie_of_match,
|
||||
|
||||
@@ -253,7 +253,6 @@ static int al_pcie_config_prepare(struct al_pcie *pcie)
|
||||
u8 subordinate_bus;
|
||||
u8 secondary_bus;
|
||||
u32 cfg_control;
|
||||
u32 reg;
|
||||
|
||||
ft = resource_list_first_type(&pp->bridge->windows, IORESOURCE_BUS);
|
||||
if (!ft)
|
||||
@@ -285,14 +284,9 @@ static int al_pcie_config_prepare(struct al_pcie *pcie)
|
||||
CFG_CONTROL;
|
||||
|
||||
cfg_control = al_pcie_controller_readl(pcie, cfg_control_offset);
|
||||
|
||||
reg = cfg_control &
|
||||
~(CFG_CONTROL_SEC_BUS_MASK | CFG_CONTROL_SUBBUS_MASK);
|
||||
|
||||
reg |= FIELD_PREP(CFG_CONTROL_SUBBUS_MASK, subordinate_bus) |
|
||||
FIELD_PREP(CFG_CONTROL_SEC_BUS_MASK, secondary_bus);
|
||||
|
||||
al_pcie_controller_writel(pcie, cfg_control_offset, reg);
|
||||
FIELD_MODIFY(CFG_CONTROL_SUBBUS_MASK, &cfg_control, subordinate_bus);
|
||||
FIELD_MODIFY(CFG_CONTROL_SEC_BUS_MASK, &cfg_control, secondary_bus);
|
||||
al_pcie_controller_writel(pcie, cfg_control_offset, cfg_control);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/kernel.h>
|
||||
@@ -507,6 +507,13 @@ static int amd_mdb_pcie_probe(struct platform_device *pdev)
|
||||
return amd_mdb_add_pcie_port(pcie, pdev);
|
||||
}
|
||||
|
||||
static void amd_mdb_pcie_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct amd_mdb_pcie *pcie = platform_get_drvdata(pdev);
|
||||
|
||||
gpiod_set_value_cansleep(pcie->perst_gpio, 1);
|
||||
}
|
||||
|
||||
static const struct of_device_id amd_mdb_pcie_of_match[] = {
|
||||
{
|
||||
.compatible = "amd,versal2-mdb-host",
|
||||
@@ -521,6 +528,7 @@ static struct platform_driver amd_mdb_pcie_driver = {
|
||||
.suppress_bind_attrs = true,
|
||||
},
|
||||
.probe = amd_mdb_pcie_probe,
|
||||
.shutdown = amd_mdb_pcie_shutdown,
|
||||
};
|
||||
|
||||
builtin_platform_driver(amd_mdb_pcie_driver);
|
||||
|
||||
@@ -34,7 +34,6 @@ struct artpec6_pcie {
|
||||
struct regmap *regmap; /* DT axis,syscon-pcie */
|
||||
void __iomem *phy_base; /* DT phy */
|
||||
enum artpec_pcie_variants variant;
|
||||
enum dw_pcie_device_mode mode;
|
||||
};
|
||||
|
||||
struct artpec_pcie_of_data {
|
||||
@@ -100,7 +99,7 @@ static u64 artpec6_pcie_cpu_addr_fixup(struct dw_pcie *pci, u64 cpu_addr)
|
||||
struct dw_pcie_rp *pp = &pci->pp;
|
||||
struct dw_pcie_ep *ep = &pci->ep;
|
||||
|
||||
switch (artpec6_pcie->mode) {
|
||||
switch (artpec6_pcie->pci->mode) {
|
||||
case DW_PCIE_RC_TYPE:
|
||||
return cpu_addr - pp->cfg0_base;
|
||||
case DW_PCIE_EP_TYPE:
|
||||
@@ -413,7 +412,7 @@ static int artpec6_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
artpec6_pcie->pci = pci;
|
||||
artpec6_pcie->variant = variant;
|
||||
artpec6_pcie->mode = mode;
|
||||
artpec6_pcie->pci->mode = mode;
|
||||
|
||||
artpec6_pcie->phy_base =
|
||||
devm_platform_ioremap_resource_byname(pdev, "phy");
|
||||
@@ -428,7 +427,7 @@ static int artpec6_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
platform_set_drvdata(pdev, artpec6_pcie);
|
||||
|
||||
switch (artpec6_pcie->mode) {
|
||||
switch (artpec6_pcie->pci->mode) {
|
||||
case DW_PCIE_RC_TYPE:
|
||||
if (!IS_ENABLED(CONFIG_PCIE_ARTPEC6_HOST))
|
||||
return -ENODEV;
|
||||
@@ -464,7 +463,7 @@ static int artpec6_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "INVALID device type %d\n", artpec6_pcie->mode);
|
||||
dev_err(dev, "INVALID device type %d\n", artpec6_pcie->pci->mode);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -265,8 +265,7 @@ static ssize_t lane_detect_write(struct file *file, const char __user *buf,
|
||||
return ret;
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG);
|
||||
val &= ~(LANE_SELECT);
|
||||
val |= FIELD_PREP(LANE_SELECT, lane);
|
||||
FIELD_MODIFY(LANE_SELECT, &val, lane);
|
||||
dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG, val);
|
||||
|
||||
return count;
|
||||
@@ -306,6 +305,7 @@ static ssize_t err_inj_write(struct file *file, const char __user *buf,
|
||||
u32 val, counter, vc_num, err_group, type_mask;
|
||||
int val_diff = 0;
|
||||
char *kern_buf;
|
||||
int ret;
|
||||
|
||||
err_group = err_inj_list[pdata->idx].err_inj_group;
|
||||
type_mask = err_inj_type_mask[err_group];
|
||||
@@ -327,10 +327,10 @@ static ssize_t err_inj_write(struct file *file, const char __user *buf,
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
val = kstrtou32(kern_buf, 0, &counter);
|
||||
if (val) {
|
||||
ret = kstrtou32(kern_buf, 0, &counter);
|
||||
if (ret) {
|
||||
kfree(kern_buf);
|
||||
return val;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,14 +339,10 @@ static ssize_t err_inj_write(struct file *file, const char __user *buf,
|
||||
val |= ((err_inj_list[pdata->idx].err_inj_type << EINJ_TYPE_SHIFT) & type_mask);
|
||||
val |= FIELD_PREP(EINJ_COUNT, counter);
|
||||
|
||||
if (err_group == 1 || err_group == 4) {
|
||||
val &= ~(EINJ_VAL_DIFF);
|
||||
val |= FIELD_PREP(EINJ_VAL_DIFF, val_diff);
|
||||
}
|
||||
if (err_group == 4) {
|
||||
val &= ~(EINJ_VC_NUM);
|
||||
val |= FIELD_PREP(EINJ_VC_NUM, vc_num);
|
||||
}
|
||||
if (err_group == 1 || err_group == 4)
|
||||
FIELD_MODIFY(EINJ_VAL_DIFF, &val, val_diff);
|
||||
if (err_group == 4)
|
||||
FIELD_MODIFY(EINJ_VC_NUM, &val, vc_num);
|
||||
|
||||
dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + ERR_INJ0_OFF + (0x4 * err_group), val);
|
||||
dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + ERR_INJ_ENABLE_REG, (0x1 << err_group));
|
||||
@@ -362,9 +358,8 @@ static void set_event_number(struct dwc_pcie_rasdes_priv *pdata,
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + RAS_DES_EVENT_COUNTER_CTRL_REG);
|
||||
val &= ~EVENT_COUNTER_ENABLE;
|
||||
val &= ~(EVENT_COUNTER_GROUP_SELECT | EVENT_COUNTER_EVENT_SELECT);
|
||||
val |= FIELD_PREP(EVENT_COUNTER_GROUP_SELECT, event_list[pdata->idx].group_no);
|
||||
val |= FIELD_PREP(EVENT_COUNTER_EVENT_SELECT, event_list[pdata->idx].event_no);
|
||||
FIELD_MODIFY(EVENT_COUNTER_GROUP_SELECT, &val, event_list[pdata->idx].group_no);
|
||||
FIELD_MODIFY(EVENT_COUNTER_EVENT_SELECT, &val, event_list[pdata->idx].event_no);
|
||||
dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + RAS_DES_EVENT_COUNTER_CTRL_REG, val);
|
||||
}
|
||||
|
||||
@@ -469,8 +464,7 @@ static ssize_t counter_lane_write(struct file *file, const char __user *buf,
|
||||
mutex_lock(&rinfo->reg_event_lock);
|
||||
set_event_number(pdata, pci, rinfo);
|
||||
val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + RAS_DES_EVENT_COUNTER_CTRL_REG);
|
||||
val &= ~(EVENT_COUNTER_LANE_SELECT);
|
||||
val |= FIELD_PREP(EVENT_COUNTER_LANE_SELECT, lane);
|
||||
FIELD_MODIFY(EVENT_COUNTER_LANE_SELECT, &val, lane);
|
||||
dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + RAS_DES_EVENT_COUNTER_CTRL_REG, val);
|
||||
mutex_unlock(&rinfo->reg_event_lock);
|
||||
|
||||
@@ -507,11 +501,6 @@ static int ltssm_status_show(struct seq_file *s, void *v)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ltssm_status_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, ltssm_status_show, inode->i_private);
|
||||
}
|
||||
|
||||
#define dwc_debugfs_create(name) \
|
||||
debugfs_create_file(#name, 0644, rasdes_debug, pci, \
|
||||
&dbg_ ## name ## _fops)
|
||||
@@ -548,15 +537,15 @@ static const struct file_operations dwc_pcie_counter_value_ops = {
|
||||
.read = counter_value_read,
|
||||
};
|
||||
|
||||
static const struct file_operations dwc_pcie_ltssm_status_ops = {
|
||||
.open = ltssm_status_open,
|
||||
.read = seq_read,
|
||||
};
|
||||
DEFINE_SHOW_ATTRIBUTE(ltssm_status);
|
||||
|
||||
static void dwc_pcie_rasdes_debugfs_deinit(struct dw_pcie *pci)
|
||||
{
|
||||
struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info;
|
||||
|
||||
if (!rinfo)
|
||||
return;
|
||||
|
||||
mutex_destroy(&rinfo->reg_event_lock);
|
||||
}
|
||||
|
||||
@@ -642,7 +631,7 @@ static int dwc_pcie_rasdes_debugfs_init(struct dw_pcie *pci, struct dentry *dir)
|
||||
static void dwc_pcie_ltssm_debugfs_init(struct dw_pcie *pci, struct dentry *dir)
|
||||
{
|
||||
debugfs_create_file("ltssm_status", 0444, dir, pci,
|
||||
&dwc_pcie_ltssm_status_ops);
|
||||
<ssm_status_fops);
|
||||
}
|
||||
|
||||
static int dw_pcie_ptm_check_capability(void *drvdata)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <linux/align.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "pcie-designware.h"
|
||||
@@ -584,9 +585,9 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
|
||||
|
||||
config_atu:
|
||||
if (!(flags & PCI_BASE_ADDRESS_SPACE))
|
||||
type = PCIE_ATU_TYPE_MEM;
|
||||
type = PCIE_TLP_TYPE_MEM_RDWR;
|
||||
else
|
||||
type = PCIE_ATU_TYPE_IO;
|
||||
type = PCIE_TLP_TYPE_IO_RDWR;
|
||||
|
||||
if (epf_bar->num_submap)
|
||||
ret = dw_pcie_ep_ib_atu_addr(ep, func_no, type, epf_bar);
|
||||
@@ -659,7 +660,7 @@ static int dw_pcie_ep_map_addr(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
|
||||
struct dw_pcie_ob_atu_cfg atu = { 0 };
|
||||
|
||||
atu.func_no = func_no;
|
||||
atu.type = PCIE_ATU_TYPE_MEM;
|
||||
atu.type = PCIE_TLP_TYPE_MEM_RDWR;
|
||||
atu.parent_bus_addr = addr - pci->parent_bus_offset;
|
||||
atu.pci_addr = pci_addr;
|
||||
atu.size = size;
|
||||
@@ -707,8 +708,7 @@ static int dw_pcie_ep_set_msi(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
|
||||
|
||||
reg = ep_func->msi_cap + PCI_MSI_FLAGS;
|
||||
val = dw_pcie_ep_readw_dbi(ep, func_no, reg);
|
||||
val &= ~PCI_MSI_FLAGS_QMASK;
|
||||
val |= FIELD_PREP(PCI_MSI_FLAGS_QMASK, mmc);
|
||||
FIELD_MODIFY(PCI_MSI_FLAGS_QMASK, &val, mmc);
|
||||
dw_pcie_dbi_ro_wr_en(pci);
|
||||
dw_pcie_ep_writew_dbi(ep, func_no, reg, val);
|
||||
dw_pcie_dbi_ro_wr_dis(pci);
|
||||
@@ -817,6 +817,122 @@ dw_pcie_ep_get_features(struct pci_epc *epc, u8 func_no, u8 vfunc_no)
|
||||
return ep->ops->get_features(ep);
|
||||
}
|
||||
|
||||
static const struct pci_epc_bar_rsvd_region *
|
||||
dw_pcie_ep_find_bar_rsvd_region(struct dw_pcie_ep *ep,
|
||||
enum pci_epc_bar_rsvd_region_type type,
|
||||
enum pci_barno *bar,
|
||||
resource_size_t *bar_offset)
|
||||
{
|
||||
const struct pci_epc_features *features;
|
||||
const struct pci_epc_bar_desc *bar_desc;
|
||||
const struct pci_epc_bar_rsvd_region *r;
|
||||
int i, j;
|
||||
|
||||
if (!ep->ops->get_features)
|
||||
return NULL;
|
||||
|
||||
features = ep->ops->get_features(ep);
|
||||
if (!features)
|
||||
return NULL;
|
||||
|
||||
for (i = BAR_0; i <= BAR_5; i++) {
|
||||
bar_desc = &features->bar[i];
|
||||
|
||||
if (!bar_desc->nr_rsvd_regions || !bar_desc->rsvd_regions)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < bar_desc->nr_rsvd_regions; j++) {
|
||||
r = &bar_desc->rsvd_regions[j];
|
||||
|
||||
if (r->type != type)
|
||||
continue;
|
||||
|
||||
if (bar)
|
||||
*bar = i;
|
||||
if (bar_offset)
|
||||
*bar_offset = r->offset;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
dw_pcie_ep_get_aux_resources_count(struct pci_epc *epc, u8 func_no,
|
||||
u8 vfunc_no)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
struct dw_edma_chip *edma = &pci->edma;
|
||||
|
||||
if (!pci->edma_reg_size)
|
||||
return 0;
|
||||
|
||||
if (edma->db_offset == ~0)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
dw_pcie_ep_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
|
||||
struct pci_epc_aux_resource *resources,
|
||||
int num_resources)
|
||||
{
|
||||
struct dw_pcie_ep *ep = epc_get_drvdata(epc);
|
||||
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
|
||||
const struct pci_epc_bar_rsvd_region *rsvd;
|
||||
struct dw_edma_chip *edma = &pci->edma;
|
||||
enum pci_barno dma_ctrl_bar = NO_BAR;
|
||||
resource_size_t db_offset = edma->db_offset;
|
||||
resource_size_t dma_ctrl_bar_offset = 0;
|
||||
resource_size_t dma_reg_size;
|
||||
int count;
|
||||
|
||||
count = dw_pcie_ep_get_aux_resources_count(epc, func_no, vfunc_no);
|
||||
if (count < 0)
|
||||
return count;
|
||||
|
||||
if (num_resources < count)
|
||||
return -ENOSPC;
|
||||
|
||||
if (!count)
|
||||
return 0;
|
||||
|
||||
dma_reg_size = pci->edma_reg_size;
|
||||
|
||||
rsvd = dw_pcie_ep_find_bar_rsvd_region(ep,
|
||||
PCI_EPC_BAR_RSVD_DMA_CTRL_MMIO,
|
||||
&dma_ctrl_bar,
|
||||
&dma_ctrl_bar_offset);
|
||||
if (rsvd && rsvd->size < dma_reg_size)
|
||||
dma_reg_size = rsvd->size;
|
||||
|
||||
/*
|
||||
* For interrupt-emulation doorbells, report a standalone resource
|
||||
* instead of bundling it into the DMA controller MMIO resource.
|
||||
*/
|
||||
if (range_end_overflows_t(resource_size_t, db_offset,
|
||||
sizeof(u32), dma_reg_size))
|
||||
return -EINVAL;
|
||||
|
||||
resources[0] = (struct pci_epc_aux_resource) {
|
||||
.type = PCI_EPC_AUX_DOORBELL_MMIO,
|
||||
.phys_addr = pci->edma_reg_phys + db_offset,
|
||||
.size = sizeof(u32),
|
||||
.bar = dma_ctrl_bar,
|
||||
.bar_offset = dma_ctrl_bar != NO_BAR ?
|
||||
dma_ctrl_bar_offset + db_offset : 0,
|
||||
.u.db_mmio = {
|
||||
.irq = edma->db_irq,
|
||||
.data = 0, /* write 0 to assert */
|
||||
},
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pci_epc_ops epc_ops = {
|
||||
.write_header = dw_pcie_ep_write_header,
|
||||
.set_bar = dw_pcie_ep_set_bar,
|
||||
@@ -832,6 +948,8 @@ static const struct pci_epc_ops epc_ops = {
|
||||
.start = dw_pcie_ep_start,
|
||||
.stop = dw_pcie_ep_stop,
|
||||
.get_features = dw_pcie_ep_get_features,
|
||||
.get_aux_resources_count = dw_pcie_ep_get_aux_resources_count,
|
||||
.get_aux_resources = dw_pcie_ep_get_aux_resources,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
#include <linux/msi.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci_regs.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "../pci-host-common.h"
|
||||
#include "../../pci.h"
|
||||
#include "pcie-designware.h"
|
||||
|
||||
@@ -434,7 +436,7 @@ static int dw_pcie_config_ecam_iatu(struct dw_pcie_rp *pp)
|
||||
* remaining buses need type 1 iATU configuration.
|
||||
*/
|
||||
atu.index = 0;
|
||||
atu.type = PCIE_ATU_TYPE_CFG0;
|
||||
atu.type = PCIE_TLP_TYPE_CFG0_RDWR;
|
||||
atu.parent_bus_addr = pp->cfg0_base + SZ_1M;
|
||||
/* 1MiB is to cover 1 (bus) * 32 (devices) * 8 (functions) */
|
||||
atu.size = SZ_1M;
|
||||
@@ -450,7 +452,7 @@ static int dw_pcie_config_ecam_iatu(struct dw_pcie_rp *pp)
|
||||
|
||||
/* Configure remaining buses in type 1 iATU configuration */
|
||||
atu.index = 1;
|
||||
atu.type = PCIE_ATU_TYPE_CFG1;
|
||||
atu.type = PCIE_TLP_TYPE_CFG1_RDWR;
|
||||
atu.parent_bus_addr = pp->cfg0_base + SZ_2M;
|
||||
atu.size = (SZ_1M * bus_range_max) - SZ_2M;
|
||||
atu.ctrl2 = PCIE_ATU_CFG_SHIFT_MODE_ENABLE;
|
||||
@@ -703,8 +705,10 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp)
|
||||
|
||||
dwc_pcie_debugfs_deinit(pci);
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pci_stop_root_bus(pp->bridge->bus);
|
||||
pci_remove_root_bus(pp->bridge->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
|
||||
dw_pcie_stop_link(pci);
|
||||
|
||||
@@ -745,9 +749,9 @@ static void __iomem *dw_pcie_other_conf_map_bus(struct pci_bus *bus,
|
||||
PCIE_ATU_FUNC(PCI_FUNC(devfn));
|
||||
|
||||
if (pci_is_root_bus(bus->parent))
|
||||
type = PCIE_ATU_TYPE_CFG0;
|
||||
type = PCIE_TLP_TYPE_CFG0_RDWR;
|
||||
else
|
||||
type = PCIE_ATU_TYPE_CFG1;
|
||||
type = PCIE_TLP_TYPE_CFG1_RDWR;
|
||||
|
||||
atu.type = type;
|
||||
atu.parent_bus_addr = pp->cfg0_base - pci->parent_bus_offset;
|
||||
@@ -774,7 +778,7 @@ static int dw_pcie_rd_other_conf(struct pci_bus *bus, unsigned int devfn,
|
||||
return ret;
|
||||
|
||||
if (pp->cfg0_io_shared) {
|
||||
atu.type = PCIE_ATU_TYPE_IO;
|
||||
atu.type = PCIE_TLP_TYPE_IO_RDWR;
|
||||
atu.parent_bus_addr = pp->io_base - pci->parent_bus_offset;
|
||||
atu.pci_addr = pp->io_bus_addr;
|
||||
atu.size = pp->io_size;
|
||||
@@ -800,7 +804,7 @@ static int dw_pcie_wr_other_conf(struct pci_bus *bus, unsigned int devfn,
|
||||
return ret;
|
||||
|
||||
if (pp->cfg0_io_shared) {
|
||||
atu.type = PCIE_ATU_TYPE_IO;
|
||||
atu.type = PCIE_TLP_TYPE_IO_RDWR;
|
||||
atu.parent_bus_addr = pp->io_base - pci->parent_bus_offset;
|
||||
atu.pci_addr = pp->io_bus_addr;
|
||||
atu.size = pp->io_size;
|
||||
@@ -908,7 +912,7 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp)
|
||||
if (resource_type(entry->res) != IORESOURCE_MEM)
|
||||
continue;
|
||||
|
||||
atu.type = PCIE_ATU_TYPE_MEM;
|
||||
atu.type = PCIE_TLP_TYPE_MEM_RDWR;
|
||||
atu.parent_bus_addr = entry->res->start - pci->parent_bus_offset;
|
||||
atu.pci_addr = entry->res->start - entry->offset;
|
||||
|
||||
@@ -951,7 +955,7 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp)
|
||||
if (pp->io_size) {
|
||||
if (ob_iatu_index < pci->num_ob_windows) {
|
||||
atu.index = ob_iatu_index;
|
||||
atu.type = PCIE_ATU_TYPE_IO;
|
||||
atu.type = PCIE_TLP_TYPE_IO_RDWR;
|
||||
atu.parent_bus_addr = pp->io_base - pci->parent_bus_offset;
|
||||
atu.pci_addr = pp->io_bus_addr;
|
||||
atu.size = pp->io_size;
|
||||
@@ -1013,7 +1017,7 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp)
|
||||
|
||||
window_size = MIN(pci->region_limit + 1, res_size);
|
||||
ret = dw_pcie_prog_inbound_atu(pci, ib_iatu_index,
|
||||
PCIE_ATU_TYPE_MEM, res_start,
|
||||
PCIE_TLP_TYPE_MEM_RDWR, res_start,
|
||||
res_start - entry->offset, window_size);
|
||||
if (ret) {
|
||||
dev_err(pci->dev, "Failed to set DMA range %pr\n",
|
||||
@@ -1194,7 +1198,7 @@ static int dw_pcie_pme_turn_off(struct dw_pcie *pci)
|
||||
|
||||
atu.code = PCIE_MSG_CODE_PME_TURN_OFF;
|
||||
atu.routing = PCIE_MSG_TYPE_R_BC;
|
||||
atu.type = PCIE_ATU_TYPE_MSG;
|
||||
atu.type = PCIE_TLP_TYPE_MSG;
|
||||
atu.size = resource_size(pci->pp.msg_res);
|
||||
atu.index = pci->pp.msg_atu_index;
|
||||
|
||||
@@ -1218,18 +1222,14 @@ static int dw_pcie_pme_turn_off(struct dw_pcie *pci)
|
||||
|
||||
int dw_pcie_suspend_noirq(struct dw_pcie *pci)
|
||||
{
|
||||
u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
|
||||
bool pme_capable = false;
|
||||
int ret = 0;
|
||||
u32 val;
|
||||
|
||||
if (!dw_pcie_link_up(pci))
|
||||
goto stop_link;
|
||||
|
||||
/*
|
||||
* If L1SS is supported, then do not put the link into L2 as some
|
||||
* devices such as NVMe expect low resume latency.
|
||||
*/
|
||||
if (dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_ASPM_L1)
|
||||
if (!pci_host_common_d3cold_possible(pci->pp.bridge, &pme_capable))
|
||||
return 0;
|
||||
|
||||
if (pci->pp.ops->pme_turn_off) {
|
||||
@@ -1273,6 +1273,15 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci)
|
||||
udelay(1);
|
||||
|
||||
stop_link:
|
||||
/*
|
||||
* TODO: "pme_capable" means some downstream device is wakeup-
|
||||
* enabled and is capable of generating PME from D3cold, which
|
||||
* requires auxiliary power. Instead of always skipping power off
|
||||
* if PME is supported from D3cold, query the pwrctrl core and skip
|
||||
* power off only if device supports PME from D3cold and Vaux is
|
||||
* not supported.
|
||||
*/
|
||||
pci->pp.skip_pwrctrl_off = pme_capable;
|
||||
dw_pcie_stop_link(pci);
|
||||
if (pci->pp.ops->deinit)
|
||||
pci->pp.ops->deinit(&pci->pp);
|
||||
@@ -1290,8 +1299,6 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci)
|
||||
if (!pci->suspended)
|
||||
return 0;
|
||||
|
||||
pci->suspended = false;
|
||||
|
||||
if (pci->pp.ops->init) {
|
||||
ret = pci->pp.ops->init(&pci->pp);
|
||||
if (ret) {
|
||||
@@ -1313,6 +1320,8 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci)
|
||||
if (pci->pp.ops->post_init)
|
||||
pci->pp.ops->post_init(&pci->pp);
|
||||
|
||||
pci->suspended = false;
|
||||
|
||||
return 0;
|
||||
|
||||
err_stop_link:
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
@@ -22,7 +21,6 @@
|
||||
|
||||
struct dw_plat_pcie {
|
||||
struct dw_pcie *pci;
|
||||
enum dw_pcie_device_mode mode;
|
||||
};
|
||||
|
||||
struct dw_plat_pcie_of_data {
|
||||
@@ -118,11 +116,11 @@ static int dw_plat_pcie_probe(struct platform_device *pdev)
|
||||
pci->dev = dev;
|
||||
|
||||
dw_plat_pcie->pci = pci;
|
||||
dw_plat_pcie->mode = mode;
|
||||
dw_plat_pcie->pci->mode = mode;
|
||||
|
||||
platform_set_drvdata(pdev, dw_plat_pcie);
|
||||
|
||||
switch (dw_plat_pcie->mode) {
|
||||
switch (dw_plat_pcie->pci->mode) {
|
||||
case DW_PCIE_RC_TYPE:
|
||||
if (!IS_ENABLED(CONFIG_PCIE_DW_PLAT_HOST))
|
||||
return -ENODEV;
|
||||
@@ -148,7 +146,7 @@ static int dw_plat_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "INVALID device type %d\n", dw_plat_pcie->mode);
|
||||
dev_err(dev, "INVALID device type %d\n", dw_plat_pcie->pci->mode);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "../pci-host-common.h"
|
||||
#include "../../pci.h"
|
||||
#include "pcie-designware.h"
|
||||
|
||||
@@ -162,8 +163,12 @@ int dw_pcie_get_resources(struct dw_pcie *pci)
|
||||
pci->edma.reg_base = devm_ioremap_resource(pci->dev, res);
|
||||
if (IS_ERR(pci->edma.reg_base))
|
||||
return PTR_ERR(pci->edma.reg_base);
|
||||
pci->edma_reg_phys = res->start;
|
||||
pci->edma_reg_size = resource_size(res);
|
||||
} else if (pci->atu_size >= 2 * DEFAULT_DBI_DMA_OFFSET) {
|
||||
pci->edma.reg_base = pci->atu_base + DEFAULT_DBI_DMA_OFFSET;
|
||||
pci->edma_reg_phys = pci->atu_phys_addr + DEFAULT_DBI_DMA_OFFSET;
|
||||
pci->edma_reg_size = pci->atu_size - DEFAULT_DBI_DMA_OFFSET;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,13 +492,13 @@ static inline void dw_pcie_writel_atu_ob(struct dw_pcie *pci, u32 index, u32 reg
|
||||
static inline u32 dw_pcie_enable_ecrc(u32 val)
|
||||
{
|
||||
/*
|
||||
* DWC versions 0x3530302a and 0x3536322a have a design issue where
|
||||
* the 'TD' bit in the Control register-1 of the ATU outbound
|
||||
* region acts like an override for the ECRC setting, i.e., the
|
||||
* presence of TLP Digest (ECRC) in the outgoing TLPs is solely
|
||||
* determined by this bit. This is contrary to the PCIe spec which
|
||||
* says that the enablement of the ECRC is solely determined by the
|
||||
* AER registers.
|
||||
* DesignWare core versions prior to 5.10A have a design issue where the
|
||||
* 'TD' bit in the Control register-1 of the ATU outbound region acts
|
||||
* like an override for the ECRC setting, i.e., the presence of TLP
|
||||
* Digest (ECRC) in the outgoing TLPs is solely determined by this
|
||||
* bit. This is contrary to the PCIe spec which says that the
|
||||
* enablement of the ECRC is solely determined by the AER
|
||||
* registers.
|
||||
*
|
||||
* Because of this, even when the ECRC is enabled through AER
|
||||
* registers, the transactions going through ATU won't have TLP
|
||||
@@ -563,12 +568,12 @@ int dw_pcie_prog_outbound_atu(struct dw_pcie *pci,
|
||||
if (upper_32_bits(limit_addr) > upper_32_bits(parent_bus_addr) &&
|
||||
dw_pcie_ver_is_ge(pci, 460A))
|
||||
val |= PCIE_ATU_INCREASE_REGION_SIZE;
|
||||
if (dw_pcie_ver_is(pci, 490A) || dw_pcie_ver_is(pci, 500A))
|
||||
if (!dw_pcie_ver_is_ge(pci, 510A))
|
||||
val = dw_pcie_enable_ecrc(val);
|
||||
dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_REGION_CTRL1, val);
|
||||
|
||||
val = PCIE_ATU_ENABLE | atu->ctrl2;
|
||||
if (atu->type == PCIE_ATU_TYPE_MSG) {
|
||||
if (atu->type == PCIE_TLP_TYPE_MSG) {
|
||||
/* The data-less messages only for now */
|
||||
val |= PCIE_ATU_INHIBIT_PAYLOAD | atu->code;
|
||||
}
|
||||
@@ -799,13 +804,7 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci)
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/*
|
||||
* As per PCIe r6.0, sec 6.6.1, a Downstream Port that supports Link
|
||||
* speeds greater than 5.0 GT/s, software must wait a minimum of 100 ms
|
||||
* after Link training completes before sending a Configuration Request.
|
||||
*/
|
||||
if (pci->max_link_speed > 2)
|
||||
msleep(PCIE_RESET_CONFIG_WAIT_MS);
|
||||
pci_host_common_link_train_delay(pci->max_link_speed);
|
||||
|
||||
offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
|
||||
val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA);
|
||||
@@ -938,8 +937,7 @@ static void dw_pcie_link_set_max_link_width(struct dw_pcie *pci, u32 num_lanes)
|
||||
|
||||
cap = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
|
||||
lnkcap = dw_pcie_readl_dbi(pci, cap + PCI_EXP_LNKCAP);
|
||||
lnkcap &= ~PCI_EXP_LNKCAP_MLW;
|
||||
lnkcap |= FIELD_PREP(PCI_EXP_LNKCAP_MLW, num_lanes);
|
||||
FIELD_MODIFY(PCI_EXP_LNKCAP_MLW, &lnkcap, num_lanes);
|
||||
dw_pcie_writel_dbi(pci, cap + PCI_EXP_LNKCAP, lnkcap);
|
||||
}
|
||||
|
||||
@@ -1249,6 +1247,34 @@ void dw_pcie_hide_unsupported_l1ss(struct dw_pcie *pci)
|
||||
dw_pcie_writel_dbi(pci, l1ss + PCI_L1SS_CAP, l1ss_cap);
|
||||
}
|
||||
|
||||
/* TODO: Need to handle multi Root Ports */
|
||||
void dw_pcie_program_t_power_on(struct dw_pcie *pci, u32 t_power_on)
|
||||
{
|
||||
u8 scale, value;
|
||||
u16 offset;
|
||||
u32 val;
|
||||
|
||||
if (!t_power_on)
|
||||
return;
|
||||
|
||||
offset = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_L1SS);
|
||||
if (!offset)
|
||||
return;
|
||||
|
||||
pcie_encode_t_power_on(t_power_on, &scale, &value);
|
||||
|
||||
dw_pcie_dbi_ro_wr_en(pci);
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, offset + PCI_L1SS_CAP);
|
||||
val &= ~(PCI_L1SS_CAP_P_PWR_ON_SCALE | PCI_L1SS_CAP_P_PWR_ON_VALUE);
|
||||
FIELD_MODIFY(PCI_L1SS_CAP_P_PWR_ON_SCALE, &val, scale);
|
||||
FIELD_MODIFY(PCI_L1SS_CAP_P_PWR_ON_VALUE, &val, value);
|
||||
|
||||
dw_pcie_writel_dbi(pci, offset + PCI_L1SS_CAP, val);
|
||||
|
||||
dw_pcie_dbi_ro_wr_dis(pci);
|
||||
}
|
||||
|
||||
void dw_pcie_setup(struct dw_pcie *pci)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#define DW_PCIE_VER_480A 0x3438302a
|
||||
#define DW_PCIE_VER_490A 0x3439302a
|
||||
#define DW_PCIE_VER_500A 0x3530302a
|
||||
#define DW_PCIE_VER_510A 0x3531302a
|
||||
#define DW_PCIE_VER_520A 0x3532302a
|
||||
#define DW_PCIE_VER_540A 0x3534302a
|
||||
#define DW_PCIE_VER_562A 0x3536322a
|
||||
@@ -71,6 +72,8 @@
|
||||
|
||||
/* Synopsys-specific PCIe configuration registers */
|
||||
#define PCIE_PORT_FORCE 0x708
|
||||
/* Bit[7:0] LINK_NUM: Link Number. Not used for endpoint */
|
||||
#define PORT_LINK_NUM_MASK GENMASK(7, 0)
|
||||
#define PORT_FORCE_DO_DESKEW_FOR_SRIS BIT(23)
|
||||
|
||||
#define PCIE_PORT_AFR 0x70C
|
||||
@@ -98,6 +101,28 @@
|
||||
#define PCIE_PORT_LANE_SKEW 0x714
|
||||
#define PORT_LANE_SKEW_INSERT_MASK GENMASK(23, 0)
|
||||
|
||||
/*
|
||||
* PCIE_TIMER_CTRL_MAX_FUNC_NUM: Timer Control and Max Function Number
|
||||
* Register.
|
||||
*
|
||||
* This register holds the ack frequency, latency, replay, fast link
|
||||
* scaling timers, and max function number values.
|
||||
*
|
||||
* Bit[30:29] FAST_LINK_SCALING_FACTOR: Fast Link Timer Scaling Factor.
|
||||
* 0x0 (SF_1024): Scaling Factor is 1024 (1ms is 1us).
|
||||
* When the LTSSM is in Config or L12 Entry State, 1ms
|
||||
* timer is 2us, 2ms timer is 4us and 3ms timer is 6us.
|
||||
* 0x1 (SF_256): Scaling Factor is 256 (1ms is 4us)
|
||||
* 0x2 (SF_64): Scaling Factor is 64 (1ms is 16us)
|
||||
* 0x3 (SF_16): Scaling Factor is 16 (1ms is 64us)
|
||||
*/
|
||||
#define PCIE_TIMER_CTRL_MAX_FUNC_NUM 0x718
|
||||
#define PORT_FLT_SF_MASK GENMASK(30, 29)
|
||||
#define PORT_FLT_SF_VAL_1024 0x0
|
||||
#define PORT_FLT_SF_VAL_256 0x1
|
||||
#define PORT_FLT_SF_VAL_64 0x2
|
||||
#define PORT_FLT_SF_VAL_16 0x3
|
||||
|
||||
#define PCIE_PORT_DEBUG0 0x728
|
||||
#define PORT_LOGIC_LTSSM_STATE_MASK 0x3f
|
||||
#define PORT_LOGIC_LTSSM_STATE_L0 0x11
|
||||
@@ -170,11 +195,6 @@
|
||||
#define PCIE_ATU_VIEWPORT_SIZE 0x2C
|
||||
#define PCIE_ATU_REGION_CTRL1 0x000
|
||||
#define PCIE_ATU_INCREASE_REGION_SIZE BIT(13)
|
||||
#define PCIE_ATU_TYPE_MEM 0x0
|
||||
#define PCIE_ATU_TYPE_IO 0x2
|
||||
#define PCIE_ATU_TYPE_CFG0 0x4
|
||||
#define PCIE_ATU_TYPE_CFG1 0x5
|
||||
#define PCIE_ATU_TYPE_MSG 0x10
|
||||
#define PCIE_ATU_TD BIT(8)
|
||||
#define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20)
|
||||
#define PCIE_ATU_REGION_CTRL2 0x004
|
||||
@@ -450,6 +470,7 @@ struct dw_pcie_rp {
|
||||
bool ecam_enabled;
|
||||
bool native_ecam;
|
||||
bool skip_l23_ready;
|
||||
bool skip_pwrctrl_off;
|
||||
};
|
||||
|
||||
struct dw_pcie_ep_ops {
|
||||
@@ -544,6 +565,8 @@ struct dw_pcie {
|
||||
int max_link_speed;
|
||||
u8 n_fts[2];
|
||||
struct dw_edma_chip edma;
|
||||
phys_addr_t edma_reg_phys;
|
||||
resource_size_t edma_reg_size;
|
||||
bool l1ss_support; /* L1 PM Substates support */
|
||||
struct clk_bulk_data app_clks[DW_PCIE_NUM_APP_CLKS];
|
||||
struct clk_bulk_data core_clks[DW_PCIE_NUM_CORE_CLKS];
|
||||
@@ -605,6 +628,7 @@ int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
|
||||
u8 bar, size_t size);
|
||||
void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index);
|
||||
void dw_pcie_hide_unsupported_l1ss(struct dw_pcie *pci);
|
||||
void dw_pcie_program_t_power_on(struct dw_pcie *pci, u32 t_power_on);
|
||||
void dw_pcie_setup(struct dw_pcie *pci);
|
||||
void dw_pcie_iatu_detect(struct dw_pcie *pci);
|
||||
int dw_pcie_edma_detect(struct dw_pcie *pci);
|
||||
|
||||
@@ -211,8 +211,7 @@ static int eswin_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
|
||||
/* Configure Root Port type */
|
||||
val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
|
||||
val &= ~PCIEELBI_CTRL0_DEV_TYPE;
|
||||
val |= FIELD_PREP(PCIEELBI_CTRL0_DEV_TYPE, PCI_EXP_TYPE_ROOT_PORT);
|
||||
FIELD_MODIFY(PCIEELBI_CTRL0_DEV_TYPE, &val, PCI_EXP_TYPE_ROOT_PORT);
|
||||
writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET);
|
||||
|
||||
list_for_each_entry(port, &pcie->ports, list) {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
#define PCIE_APP_IRN_INTD BIT(16)
|
||||
#define PCIE_APP_IRN_MSG_LTR BIT(18)
|
||||
#define PCIE_APP_IRN_SYS_ERR_RC BIT(29)
|
||||
#define PCIE_APP_INTX_OFST 12
|
||||
|
||||
#define PCIE_APP_IRN_INT \
|
||||
(PCIE_APP_IRN_AER_REPORT | PCIE_APP_IRN_PME | \
|
||||
@@ -196,6 +195,13 @@ static void intel_pcie_device_rst_deassert(struct intel_pcie *pcie)
|
||||
gpiod_set_value_cansleep(pcie->reset_gpio, 0);
|
||||
}
|
||||
|
||||
static void intel_pcie_core_irq_enable(struct intel_pcie *pcie)
|
||||
{
|
||||
pcie_app_wr(pcie, PCIE_APP_IRNEN, 0);
|
||||
pcie_app_wr(pcie, PCIE_APP_IRNCR, PCIE_APP_IRN_INT);
|
||||
pcie_app_wr(pcie, PCIE_APP_IRNEN, PCIE_APP_IRN_INT);
|
||||
}
|
||||
|
||||
static void intel_pcie_core_irq_disable(struct intel_pcie *pcie)
|
||||
{
|
||||
pcie_app_wr(pcie, PCIE_APP_IRNEN, 0);
|
||||
@@ -278,6 +284,16 @@ static void intel_pcie_turn_off(struct intel_pcie *pcie)
|
||||
pcie_rc_cfg_wr_mask(pcie, PCI_COMMAND, PCI_COMMAND_MEMORY, 0);
|
||||
}
|
||||
|
||||
static int intel_pcie_start_link(struct dw_pcie *pci)
|
||||
{
|
||||
struct intel_pcie *pcie = dev_get_drvdata(pci->dev);
|
||||
|
||||
intel_pcie_device_rst_deassert(pcie);
|
||||
intel_pcie_ltssm_enable(pcie);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_pcie_host_setup(struct intel_pcie *pcie)
|
||||
{
|
||||
int ret;
|
||||
@@ -285,49 +301,33 @@ static int intel_pcie_host_setup(struct intel_pcie *pcie)
|
||||
|
||||
intel_pcie_core_rst_assert(pcie);
|
||||
intel_pcie_device_rst_assert(pcie);
|
||||
|
||||
ret = phy_init(pcie->phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
intel_pcie_core_rst_deassert(pcie);
|
||||
|
||||
/* Controller clock must be provided earlier than PHY */
|
||||
ret = clk_prepare_enable(pcie->core_clk);
|
||||
if (ret) {
|
||||
dev_err(pcie->pci.dev, "Core clock enable failed: %d\n", ret);
|
||||
goto clk_err;
|
||||
}
|
||||
|
||||
pci->atu_base = pci->dbi_base + 0xC0000;
|
||||
ret = phy_init(pcie->phy);
|
||||
if (ret)
|
||||
goto phy_err;
|
||||
|
||||
intel_pcie_ltssm_disable(pcie);
|
||||
intel_pcie_link_setup(pcie);
|
||||
intel_pcie_init_n_fts(pci);
|
||||
|
||||
ret = dw_pcie_setup_rc(&pci->pp);
|
||||
if (ret)
|
||||
goto app_init_err;
|
||||
|
||||
dw_pcie_upconfig_setup(pci);
|
||||
|
||||
intel_pcie_device_rst_deassert(pcie);
|
||||
intel_pcie_ltssm_enable(pcie);
|
||||
|
||||
ret = dw_pcie_wait_for_link(pci);
|
||||
if (ret)
|
||||
goto app_init_err;
|
||||
|
||||
/* Enable integrated interrupts */
|
||||
pcie_app_wr_mask(pcie, PCIE_APP_IRNEN, PCIE_APP_IRN_INT,
|
||||
PCIE_APP_IRN_INT);
|
||||
intel_pcie_core_irq_enable(pcie);
|
||||
|
||||
return 0;
|
||||
|
||||
app_init_err:
|
||||
phy_err:
|
||||
clk_disable_unprepare(pcie->core_clk);
|
||||
clk_err:
|
||||
intel_pcie_core_rst_assert(pcie);
|
||||
phy_exit(pcie->phy);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -381,6 +381,7 @@ static int intel_pcie_rc_init(struct dw_pcie_rp *pp)
|
||||
}
|
||||
|
||||
static const struct dw_pcie_ops intel_pcie_ops = {
|
||||
.start_link = intel_pcie_start_link,
|
||||
};
|
||||
|
||||
static const struct dw_pcie_host_ops intel_pcie_dw_ops = {
|
||||
@@ -392,6 +393,7 @@ static int intel_pcie_probe(struct platform_device *pdev)
|
||||
struct device *dev = &pdev->dev;
|
||||
struct intel_pcie *pcie;
|
||||
struct dw_pcie_rp *pp;
|
||||
struct resource *res;
|
||||
struct dw_pcie *pci;
|
||||
int ret;
|
||||
|
||||
@@ -416,6 +418,32 @@ static int intel_pcie_probe(struct platform_device *pdev)
|
||||
pci->ops = &intel_pcie_ops;
|
||||
pp->ops = &intel_pcie_dw_ops;
|
||||
|
||||
/*
|
||||
* If the 'atu' region is not available in the devicetree, use the
|
||||
* default offset from DBI region for backwards compatibility. The
|
||||
* 'atu' region should always be specified in the devicetree, as
|
||||
* this is a hardware-specific address that should not be defined
|
||||
* in the driver.
|
||||
*/
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "atu");
|
||||
if (!res) {
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
|
||||
pci->dbi_base = devm_pci_remap_cfg_resource(pci->dev, res);
|
||||
if (IS_ERR(pci->dbi_base))
|
||||
return PTR_ERR(pci->dbi_base);
|
||||
|
||||
pci->dbi_phys_addr = res->start;
|
||||
pci->atu_base = devm_ioremap(dev, res->start + 0xC0000, SZ_4K);
|
||||
if (!pci->atu_base) {
|
||||
dev_err(dev, "failed to remap ATU space\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pci->atu_size = SZ_4K;
|
||||
pci->atu_phys_addr = res->start + 0xC0000;
|
||||
dev_warn(dev, "ATU region not specified in DT. Using default offset\n");
|
||||
}
|
||||
|
||||
ret = dw_pcie_host_init(pp);
|
||||
if (ret) {
|
||||
dev_err(dev, "Cannot initialize host\n");
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
struct keembay_pcie {
|
||||
struct dw_pcie pci;
|
||||
void __iomem *apb_base;
|
||||
enum dw_pcie_device_mode mode;
|
||||
|
||||
struct clk *clk_master;
|
||||
struct clk *clk_aux;
|
||||
@@ -117,7 +116,7 @@ static int keembay_pcie_start_link(struct dw_pcie *pci)
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (pcie->mode == DW_PCIE_EP_TYPE)
|
||||
if (pcie->pci.mode == DW_PCIE_EP_TYPE)
|
||||
return 0;
|
||||
|
||||
keembay_pcie_ltssm_set(pcie, false);
|
||||
@@ -409,7 +408,7 @@ static int keembay_pcie_probe(struct platform_device *pdev)
|
||||
pci->dev = dev;
|
||||
pci->ops = &keembay_pcie_ops;
|
||||
|
||||
pcie->mode = mode;
|
||||
pcie->pci.mode = mode;
|
||||
|
||||
pcie->apb_base = devm_platform_ioremap_resource_byname(pdev, "apb");
|
||||
if (IS_ERR(pcie->apb_base))
|
||||
@@ -417,7 +416,7 @@ static int keembay_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
platform_set_drvdata(pdev, pcie);
|
||||
|
||||
switch (pcie->mode) {
|
||||
switch (pcie->pci.mode) {
|
||||
case DW_PCIE_RC_TYPE:
|
||||
if (!IS_ENABLED(CONFIG_PCIE_KEEMBAY_HOST))
|
||||
return -ENODEV;
|
||||
@@ -443,7 +442,7 @@ static int keembay_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "Invalid device type %d\n", pcie->mode);
|
||||
dev_err(dev, "Invalid device type %d\n", pcie->pci.mode);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,8 +139,7 @@ static int s32g_init_pcie_controller(struct dw_pcie_rp *pp)
|
||||
|
||||
/* Set RP mode */
|
||||
val = s32g_pcie_readl_ctrl(s32g_pp, PCIE_S32G_PE0_GEN_CTRL_1);
|
||||
val &= ~DEVICE_TYPE_MASK;
|
||||
val |= FIELD_PREP(DEVICE_TYPE_MASK, PCI_EXP_TYPE_ROOT_PORT);
|
||||
FIELD_MODIFY(DEVICE_TYPE_MASK, &val, PCI_EXP_TYPE_ROOT_PORT);
|
||||
|
||||
/* Use default CRNS */
|
||||
val &= ~SRIS_MODE;
|
||||
|
||||
@@ -30,20 +30,15 @@ void qcom_pcie_common_set_equalization(struct dw_pcie *pci)
|
||||
|
||||
reg = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF);
|
||||
reg &= ~GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL;
|
||||
reg &= ~GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK;
|
||||
reg |= FIELD_PREP(GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK,
|
||||
speed - PCIE_SPEED_8_0GT);
|
||||
FIELD_MODIFY(GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK, ®,
|
||||
speed - PCIE_SPEED_8_0GT);
|
||||
dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, reg);
|
||||
|
||||
reg = dw_pcie_readl_dbi(pci, GEN3_EQ_FB_MODE_DIR_CHANGE_OFF);
|
||||
reg &= ~(GEN3_EQ_FMDC_T_MIN_PHASE23 |
|
||||
GEN3_EQ_FMDC_N_EVALS |
|
||||
GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA |
|
||||
GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA);
|
||||
reg |= FIELD_PREP(GEN3_EQ_FMDC_T_MIN_PHASE23, 0x1) |
|
||||
FIELD_PREP(GEN3_EQ_FMDC_N_EVALS, 0xd) |
|
||||
FIELD_PREP(GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA, 0x5) |
|
||||
FIELD_PREP(GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA, 0x5);
|
||||
FIELD_MODIFY(GEN3_EQ_FMDC_T_MIN_PHASE23, ®, 0x1);
|
||||
FIELD_MODIFY(GEN3_EQ_FMDC_N_EVALS, ®, 0xd);
|
||||
FIELD_MODIFY(GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA, ®, 0x5);
|
||||
FIELD_MODIFY(GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA, ®, 0x5);
|
||||
dw_pcie_writel_dbi(pci, GEN3_EQ_FB_MODE_DIR_CHANGE_OFF, reg);
|
||||
|
||||
reg = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF);
|
||||
@@ -61,14 +56,10 @@ void qcom_pcie_common_set_16gt_lane_margining(struct dw_pcie *pci)
|
||||
u32 reg;
|
||||
|
||||
reg = dw_pcie_readl_dbi(pci, GEN4_LANE_MARGINING_1_OFF);
|
||||
reg &= ~(MARGINING_MAX_VOLTAGE_OFFSET |
|
||||
MARGINING_NUM_VOLTAGE_STEPS |
|
||||
MARGINING_MAX_TIMING_OFFSET |
|
||||
MARGINING_NUM_TIMING_STEPS);
|
||||
reg |= FIELD_PREP(MARGINING_MAX_VOLTAGE_OFFSET, 0x24) |
|
||||
FIELD_PREP(MARGINING_NUM_VOLTAGE_STEPS, 0x78) |
|
||||
FIELD_PREP(MARGINING_MAX_TIMING_OFFSET, 0x32) |
|
||||
FIELD_PREP(MARGINING_NUM_TIMING_STEPS, 0x10);
|
||||
FIELD_MODIFY(MARGINING_MAX_VOLTAGE_OFFSET, ®, 0x24);
|
||||
FIELD_MODIFY(MARGINING_NUM_VOLTAGE_STEPS, ®, 0x78);
|
||||
FIELD_MODIFY(MARGINING_MAX_TIMING_OFFSET, ®, 0x32);
|
||||
FIELD_MODIFY(MARGINING_NUM_TIMING_STEPS, ®, 0x10);
|
||||
dw_pcie_writel_dbi(pci, GEN4_LANE_MARGINING_1_OFF, reg);
|
||||
|
||||
reg = dw_pcie_readl_dbi(pci, GEN4_LANE_MARGINING_2_OFF);
|
||||
@@ -76,13 +67,10 @@ void qcom_pcie_common_set_16gt_lane_margining(struct dw_pcie *pci)
|
||||
MARGINING_SAMPLE_REPORTING_METHOD |
|
||||
MARGINING_IND_LEFT_RIGHT_TIMING |
|
||||
MARGINING_VOLTAGE_SUPPORTED;
|
||||
reg &= ~(MARGINING_IND_UP_DOWN_VOLTAGE |
|
||||
MARGINING_MAXLANES |
|
||||
MARGINING_SAMPLE_RATE_TIMING |
|
||||
MARGINING_SAMPLE_RATE_VOLTAGE);
|
||||
reg |= FIELD_PREP(MARGINING_MAXLANES, pci->num_lanes) |
|
||||
FIELD_PREP(MARGINING_SAMPLE_RATE_TIMING, 0x3f) |
|
||||
FIELD_PREP(MARGINING_SAMPLE_RATE_VOLTAGE, 0x3f);
|
||||
reg &= ~MARGINING_IND_UP_DOWN_VOLTAGE;
|
||||
FIELD_MODIFY(MARGINING_MAXLANES, ®, pci->num_lanes);
|
||||
FIELD_MODIFY(MARGINING_SAMPLE_RATE_TIMING, ®, 0x3f);
|
||||
FIELD_MODIFY(MARGINING_SAMPLE_RATE_VOLTAGE, ®, 0x3f);
|
||||
dw_pcie_writel_dbi(pci, GEN4_LANE_MARGINING_2_OFF, reg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(qcom_pcie_common_set_16gt_lane_margining);
|
||||
|
||||
@@ -494,15 +494,13 @@ static int qcom_pcie_perst_deassert(struct dw_pcie *pci)
|
||||
/* Set the L0s Exit Latency to 2us-4us = 0x6 */
|
||||
offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
|
||||
val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP);
|
||||
val &= ~PCI_EXP_LNKCAP_L0SEL;
|
||||
val |= FIELD_PREP(PCI_EXP_LNKCAP_L0SEL, 0x6);
|
||||
FIELD_MODIFY(PCI_EXP_LNKCAP_L0SEL, &val, 0x6);
|
||||
dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCAP, val);
|
||||
|
||||
/* Set the L1 Exit Latency to be 32us-64 us = 0x6 */
|
||||
offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
|
||||
val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP);
|
||||
val &= ~PCI_EXP_LNKCAP_L1EL;
|
||||
val |= FIELD_PREP(PCI_EXP_LNKCAP_L1EL, 0x6);
|
||||
FIELD_MODIFY(PCI_EXP_LNKCAP_L1EL, &val, 0x6);
|
||||
dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCAP, val);
|
||||
|
||||
dw_pcie_dbi_ro_wr_dis(pci);
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
|
||||
/* ELBI registers */
|
||||
#define ELBI_SYS_CTRL 0x04
|
||||
#define ELBI_SYS_STTS 0x08
|
||||
|
||||
/* DBI registers */
|
||||
#define AXI_MSTR_RESP_COMP_CTRL0 0x818
|
||||
@@ -131,6 +132,7 @@
|
||||
|
||||
/* PARF_LTSSM register fields */
|
||||
#define LTSSM_EN BIT(8)
|
||||
#define PARF_LTSSM_STATE_MASK GENMASK(5, 0)
|
||||
|
||||
/* PARF_NO_SNOOP_OVERRIDE register fields */
|
||||
#define WR_NO_SNOOP_OVERRIDE_EN BIT(1)
|
||||
@@ -144,6 +146,10 @@
|
||||
|
||||
/* ELBI_SYS_CTRL register fields */
|
||||
#define ELBI_SYS_CTRL_LT_ENABLE BIT(0)
|
||||
#define ELBI_SYS_CTRL_PME_TURNOFF_MSG BIT(4)
|
||||
|
||||
/* ELBI_SYS_STTS register fields */
|
||||
#define ELBI_SYS_STTS_LTSSM_STATE_MASK GENMASK(17, 12)
|
||||
|
||||
/* AXI_MSTR_RESP_COMP_CTRL0 register fields */
|
||||
#define CFG_REMOTE_RD_REQ_BRIDGE_SIZE_2K 0x4
|
||||
@@ -245,6 +251,7 @@ struct qcom_pcie_ops {
|
||||
void (*deinit)(struct qcom_pcie *pcie);
|
||||
void (*ltssm_enable)(struct qcom_pcie *pcie);
|
||||
int (*config_sid)(struct qcom_pcie *pcie);
|
||||
enum dw_pcie_ltssm (*get_ltssm)(struct qcom_pcie *pcie);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -269,6 +276,7 @@ struct qcom_pcie_perst {
|
||||
struct qcom_pcie_port {
|
||||
struct list_head list;
|
||||
struct phy *phy;
|
||||
u32 l1ss_t_power_on;
|
||||
struct list_head perst;
|
||||
};
|
||||
|
||||
@@ -282,7 +290,7 @@ struct qcom_pcie {
|
||||
const struct qcom_pcie_cfg *cfg;
|
||||
struct dentry *debugfs;
|
||||
struct list_head ports;
|
||||
bool suspended;
|
||||
struct gpio_desc *reset;
|
||||
bool use_pm_opp;
|
||||
};
|
||||
|
||||
@@ -428,6 +436,15 @@ static void qcom_pcie_2_1_0_ltssm_enable(struct qcom_pcie *pcie)
|
||||
writel(val, pci->elbi_base + ELBI_SYS_CTRL);
|
||||
}
|
||||
|
||||
static enum dw_pcie_ltssm qcom_pcie_2_1_0_get_ltssm(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct dw_pcie *pci = pcie->pci;
|
||||
u32 val;
|
||||
|
||||
val = readl(pci->elbi_base + ELBI_SYS_STTS);
|
||||
return (enum dw_pcie_ltssm)FIELD_GET(ELBI_SYS_STTS_LTSSM_STATE_MASK, val);
|
||||
}
|
||||
|
||||
static int qcom_pcie_get_resources_2_1_0(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_2_1_0 *res = &pcie->res.v2_1_0;
|
||||
@@ -517,7 +534,7 @@ static int qcom_pcie_post_init_2_1_0(struct qcom_pcie *pcie)
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
/* enable PCIe clocks and resets */
|
||||
/* Force PHY out of lowest power state */
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val &= ~PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
@@ -684,6 +701,12 @@ static int qcom_pcie_get_resources_2_3_2(struct qcom_pcie *pcie)
|
||||
static void qcom_pcie_deinit_2_3_2(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_2_3_2 *res = &pcie->res.v2_3_2;
|
||||
u32 val;
|
||||
|
||||
/* Force PHY to lowest power state*/
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val |= PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
|
||||
clk_bulk_disable_unprepare(res->num_clks, res->clks);
|
||||
regulator_bulk_disable(ARRAY_SIZE(res->supplies), res->supplies);
|
||||
@@ -716,7 +739,7 @@ static int qcom_pcie_post_init_2_3_2(struct qcom_pcie *pcie)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* enable PCIe clocks and resets */
|
||||
/* Force PHY out of lowest power state */
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val &= ~PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
@@ -780,6 +803,12 @@ static int qcom_pcie_get_resources_2_4_0(struct qcom_pcie *pcie)
|
||||
static void qcom_pcie_deinit_2_4_0(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_2_4_0 *res = &pcie->res.v2_4_0;
|
||||
u32 val;
|
||||
|
||||
/* Force PHY to lowest power state*/
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val |= PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
|
||||
reset_control_bulk_assert(res->num_resets, res->resets);
|
||||
clk_bulk_disable_unprepare(res->num_clks, res->clks);
|
||||
@@ -848,6 +877,12 @@ static int qcom_pcie_get_resources_2_3_3(struct qcom_pcie *pcie)
|
||||
static void qcom_pcie_deinit_2_3_3(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_2_3_3 *res = &pcie->res.v2_3_3;
|
||||
u32 val;
|
||||
|
||||
/* Force PHY to lowest power state */
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val |= PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
|
||||
clk_bulk_disable_unprepare(res->num_clks, res->clks);
|
||||
}
|
||||
@@ -903,6 +938,7 @@ static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie)
|
||||
u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
|
||||
u32 val;
|
||||
|
||||
/* Force PHY out of lowest power state */
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val &= ~PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
@@ -998,7 +1034,7 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
|
||||
/* configure PCIe to RC mode */
|
||||
writel(DEVICE_TYPE_RC, pcie->parf + PARF_DEVICE_TYPE);
|
||||
|
||||
/* enable PCIe clocks and resets */
|
||||
/* Force PHY out of lowest power state */
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val &= ~PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
@@ -1069,6 +1105,12 @@ static void qcom_pcie_host_post_init_2_7_0(struct qcom_pcie *pcie)
|
||||
static void qcom_pcie_deinit_2_7_0(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_2_7_0 *res = &pcie->res.v2_7_0;
|
||||
u32 val;
|
||||
|
||||
/* Force PHY to lowest power state */
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val |= PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
|
||||
clk_bulk_disable_unprepare(res->num_clks, res->clks);
|
||||
|
||||
@@ -1173,6 +1215,12 @@ static int qcom_pcie_get_resources_2_9_0(struct qcom_pcie *pcie)
|
||||
static void qcom_pcie_deinit_2_9_0(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_resources_2_9_0 *res = &pcie->res.v2_9_0;
|
||||
u32 val;
|
||||
|
||||
/* Force PHY to lowest power state */
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val |= PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
|
||||
clk_bulk_disable_unprepare(res->num_clks, res->clks);
|
||||
}
|
||||
@@ -1213,6 +1261,7 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie)
|
||||
u32 val;
|
||||
int i;
|
||||
|
||||
/* Force PHY out of lowest power state */
|
||||
val = readl(pcie->parf + PARF_PHY_CTRL);
|
||||
val &= ~PHY_TEST_PWR_DOWN;
|
||||
writel(val, pcie->parf + PARF_PHY_CTRL);
|
||||
@@ -1260,6 +1309,19 @@ static bool qcom_pcie_link_up(struct dw_pcie *pci)
|
||||
return val & PCI_EXP_LNKSTA_DLLLA;
|
||||
}
|
||||
|
||||
static enum dw_pcie_ltssm qcom_pcie_get_ltssm(struct dw_pcie *pci)
|
||||
{
|
||||
struct qcom_pcie *pcie = to_qcom_pcie(pci);
|
||||
u32 val;
|
||||
|
||||
if (pcie->cfg->ops->get_ltssm)
|
||||
return pcie->cfg->ops->get_ltssm(pcie);
|
||||
|
||||
val = readl(pcie->parf + PARF_LTSSM);
|
||||
|
||||
return (enum dw_pcie_ltssm)FIELD_GET(PARF_LTSSM_STATE_MASK, val);
|
||||
}
|
||||
|
||||
static void qcom_pcie_phy_power_off(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_port *port;
|
||||
@@ -1288,6 +1350,14 @@ static int qcom_pcie_phy_power_on(struct qcom_pcie *pcie)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qcom_pcie_configure_ports(struct qcom_pcie *pcie)
|
||||
{
|
||||
struct qcom_pcie_port *port;
|
||||
|
||||
list_for_each_entry(port, &pcie->ports, list)
|
||||
dw_pcie_program_t_power_on(pcie->pci, port->l1ss_t_power_on);
|
||||
}
|
||||
|
||||
static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
{
|
||||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||||
@@ -1304,13 +1374,17 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
if (ret)
|
||||
goto err_deinit;
|
||||
|
||||
ret = pci_pwrctrl_create_devices(pci->dev);
|
||||
if (ret)
|
||||
goto err_disable_phy;
|
||||
if (!pci->suspended) {
|
||||
ret = pci_pwrctrl_create_devices(pci->dev);
|
||||
if (ret)
|
||||
goto err_disable_phy;
|
||||
}
|
||||
|
||||
ret = pci_pwrctrl_power_on_devices(pci->dev);
|
||||
if (ret)
|
||||
goto err_pwrctrl_destroy;
|
||||
if (!pp->skip_pwrctrl_off) {
|
||||
ret = pci_pwrctrl_power_on_devices(pci->dev);
|
||||
if (ret)
|
||||
goto err_pwrctrl_destroy;
|
||||
}
|
||||
|
||||
if (pcie->cfg->ops->post_init) {
|
||||
ret = pcie->cfg->ops->post_init(pcie);
|
||||
@@ -1322,6 +1396,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
dw_pcie_remove_capability(pcie->pci, PCI_CAP_ID_MSIX);
|
||||
dw_pcie_remove_ext_capability(pcie->pci, PCI_EXT_CAP_ID_DPC);
|
||||
|
||||
qcom_pcie_configure_ports(pcie);
|
||||
|
||||
qcom_pcie_perst_deassert(pcie);
|
||||
|
||||
if (pcie->cfg->ops->config_sid) {
|
||||
@@ -1335,9 +1411,10 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
err_assert_reset:
|
||||
qcom_pcie_perst_assert(pcie);
|
||||
err_pwrctrl_power_off:
|
||||
pci_pwrctrl_power_off_devices(pci->dev);
|
||||
if (!pp->skip_pwrctrl_off)
|
||||
pci_pwrctrl_power_off_devices(pci->dev);
|
||||
err_pwrctrl_destroy:
|
||||
if (ret != -EPROBE_DEFER)
|
||||
if (ret != -EPROBE_DEFER && !pci->suspended)
|
||||
pci_pwrctrl_destroy_devices(pci->dev);
|
||||
err_disable_phy:
|
||||
qcom_pcie_phy_power_off(pcie);
|
||||
@@ -1354,11 +1431,14 @@ static void qcom_pcie_host_deinit(struct dw_pcie_rp *pp)
|
||||
|
||||
qcom_pcie_perst_assert(pcie);
|
||||
|
||||
/*
|
||||
* No need to destroy pwrctrl devices as this function only gets called
|
||||
* during system suspend as of now.
|
||||
*/
|
||||
pci_pwrctrl_power_off_devices(pci->dev);
|
||||
if (!pci->pp.skip_pwrctrl_off) {
|
||||
/*
|
||||
* No need to destroy pwrctrl devices as this function only
|
||||
* gets called during system suspend as of now.
|
||||
*/
|
||||
pci_pwrctrl_power_off_devices(pci->dev);
|
||||
}
|
||||
|
||||
qcom_pcie_phy_power_off(pcie);
|
||||
pcie->cfg->ops->deinit(pcie);
|
||||
}
|
||||
@@ -1368,14 +1448,35 @@ static void qcom_pcie_host_post_init(struct dw_pcie_rp *pp)
|
||||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||||
struct qcom_pcie *pcie = to_qcom_pcie(pci);
|
||||
|
||||
/*
|
||||
* During system suspend, the Qcom RC driver may turn off the
|
||||
* analog circuitry of PHY and remove controller votes to save
|
||||
* power. If the link is in L1SS and the endpoint asserts CLKREQ#
|
||||
* to exit L1SS, the time required to wake the system and restore
|
||||
* the PHY/REFCLK may exceed the L1SS exit timing (L10_REFCLK_ON +
|
||||
* T_COMMONMODE), resulting in Link Down (LDn) and a reset of the
|
||||
* endpoint. Set this flag to indicate this limitation to client
|
||||
* drivers so that they can avoid relying on device state being
|
||||
* preserved during system suspend.
|
||||
*/
|
||||
pp->bridge->broken_l1ss_resume = true;
|
||||
|
||||
if (pcie->cfg->ops->host_post_init)
|
||||
pcie->cfg->ops->host_post_init(pcie);
|
||||
}
|
||||
|
||||
static void qcom_pcie_host_pme_turn_off(struct dw_pcie_rp *pp)
|
||||
{
|
||||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||||
|
||||
writel(ELBI_SYS_CTRL_PME_TURNOFF_MSG, pci->elbi_base + ELBI_SYS_CTRL);
|
||||
}
|
||||
|
||||
static const struct dw_pcie_host_ops qcom_pcie_dw_ops = {
|
||||
.init = qcom_pcie_host_init,
|
||||
.deinit = qcom_pcie_host_deinit,
|
||||
.post_init = qcom_pcie_host_post_init,
|
||||
.pme_turn_off = qcom_pcie_host_pme_turn_off,
|
||||
};
|
||||
|
||||
/* Qcom IP rev.: 2.1.0 Synopsys IP rev.: 4.01a */
|
||||
@@ -1385,6 +1486,7 @@ static const struct qcom_pcie_ops ops_2_1_0 = {
|
||||
.post_init = qcom_pcie_post_init_2_1_0,
|
||||
.deinit = qcom_pcie_deinit_2_1_0,
|
||||
.ltssm_enable = qcom_pcie_2_1_0_ltssm_enable,
|
||||
.get_ltssm = qcom_pcie_2_1_0_get_ltssm,
|
||||
};
|
||||
|
||||
/* Qcom IP rev.: 1.0.0 Synopsys IP rev.: 4.11a */
|
||||
@@ -1394,6 +1496,7 @@ static const struct qcom_pcie_ops ops_1_0_0 = {
|
||||
.post_init = qcom_pcie_post_init_1_0_0,
|
||||
.deinit = qcom_pcie_deinit_1_0_0,
|
||||
.ltssm_enable = qcom_pcie_2_1_0_ltssm_enable,
|
||||
.get_ltssm = qcom_pcie_2_1_0_get_ltssm,
|
||||
};
|
||||
|
||||
/* Qcom IP rev.: 2.3.2 Synopsys IP rev.: 4.21a */
|
||||
@@ -1473,6 +1576,7 @@ static const struct qcom_pcie_cfg cfg_1_9_0 = {
|
||||
static const struct qcom_pcie_cfg cfg_1_34_0 = {
|
||||
.ops = &ops_1_9_0,
|
||||
.override_no_snoop = true,
|
||||
.no_l0s = true,
|
||||
};
|
||||
|
||||
static const struct qcom_pcie_cfg cfg_2_1_0 = {
|
||||
@@ -1512,6 +1616,7 @@ static const struct qcom_pcie_cfg cfg_fw_managed = {
|
||||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
.link_up = qcom_pcie_link_up,
|
||||
.start_link = qcom_pcie_start_link,
|
||||
.get_ltssm = qcom_pcie_get_ltssm,
|
||||
};
|
||||
|
||||
static int qcom_pcie_icc_init(struct qcom_pcie *pcie)
|
||||
@@ -1613,6 +1718,22 @@ static void qcom_pcie_icc_opp_update(struct qcom_pcie *pcie)
|
||||
}
|
||||
}
|
||||
|
||||
static int qcom_pcie_set_max_opp(struct device *dev)
|
||||
{
|
||||
unsigned long max_freq = ULONG_MAX;
|
||||
struct dev_pm_opp *opp;
|
||||
int ret;
|
||||
|
||||
opp = dev_pm_opp_find_freq_floor(dev, &max_freq);
|
||||
if (IS_ERR(opp))
|
||||
return PTR_ERR(opp);
|
||||
|
||||
ret = dev_pm_opp_set_opp(dev, opp);
|
||||
dev_pm_opp_put(opp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qcom_pcie_link_transition_count(struct seq_file *s, void *data)
|
||||
{
|
||||
struct qcom_pcie *pcie = (struct qcom_pcie *)dev_get_drvdata(s->private);
|
||||
@@ -1674,6 +1795,12 @@ static int qcom_pcie_ecam_host_init(struct pci_config_window *cfg)
|
||||
pci->dbi_base = cfg->win;
|
||||
pp->num_vectors = MSI_DEF_NUM_VECTORS;
|
||||
|
||||
/*
|
||||
* dw_pcie_msi_host_init() is called directly here, bypassing
|
||||
* dw_pcie_host_init() where pp->lock is normally initialized.
|
||||
*/
|
||||
raw_spin_lock_init(&pp->lock);
|
||||
|
||||
ret = dw_pcie_msi_host_init(pp);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -1703,6 +1830,13 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie,
|
||||
struct gpio_desc *reset;
|
||||
int ret;
|
||||
|
||||
if (pcie->reset) {
|
||||
dev_warn_once(dev,
|
||||
"Reusing PERST# from Root Complex node. DT needs to be fixed!\n");
|
||||
reset = pcie->reset;
|
||||
goto skip_perst_parsing;
|
||||
}
|
||||
|
||||
if (!of_find_property(np, "reset-gpios", NULL))
|
||||
goto parse_child_node;
|
||||
|
||||
@@ -1721,6 +1855,7 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie,
|
||||
return PTR_ERR(reset);
|
||||
}
|
||||
|
||||
skip_perst_parsing:
|
||||
perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL);
|
||||
if (!perst)
|
||||
return -ENOMEM;
|
||||
@@ -1764,6 +1899,9 @@ static int qcom_pcie_parse_port(struct qcom_pcie *pcie, struct device_node *node
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* TODO: Move to DWC core after multi Root Port support is added */
|
||||
of_property_read_u32(node, "t-power-on-us", &port->l1ss_t_power_on);
|
||||
|
||||
port->phy = phy;
|
||||
INIT_LIST_HEAD(&port->list);
|
||||
list_add_tail(&port->list, &pcie->ports);
|
||||
@@ -1778,6 +1916,13 @@ static int qcom_pcie_parse_ports(struct qcom_pcie *pcie)
|
||||
struct device *dev = pcie->pci->dev;
|
||||
int ret = -ENODEV;
|
||||
|
||||
if (of_find_property(dev->of_node, "perst-gpios", NULL)) {
|
||||
pcie->reset = devm_gpiod_get_optional(dev, "perst",
|
||||
GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(pcie->reset))
|
||||
return PTR_ERR(pcie->reset);
|
||||
}
|
||||
|
||||
for_each_available_child_of_node_scoped(dev->of_node, of_port) {
|
||||
if (!of_node_is_type(of_port, "pci"))
|
||||
continue;
|
||||
@@ -1804,7 +1949,6 @@ static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie)
|
||||
struct device *dev = pcie->pci->dev;
|
||||
struct qcom_pcie_perst *perst;
|
||||
struct qcom_pcie_port *port;
|
||||
struct gpio_desc *reset;
|
||||
struct phy *phy;
|
||||
int ret;
|
||||
|
||||
@@ -1812,10 +1956,6 @@ static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie)
|
||||
if (IS_ERR(phy))
|
||||
return PTR_ERR(phy);
|
||||
|
||||
reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(reset))
|
||||
return PTR_ERR(reset);
|
||||
|
||||
ret = phy_init(phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -1832,7 +1972,7 @@ static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie)
|
||||
INIT_LIST_HEAD(&port->list);
|
||||
list_add_tail(&port->list, &pcie->ports);
|
||||
|
||||
perst->desc = reset;
|
||||
perst->desc = pcie->reset;
|
||||
INIT_LIST_HEAD(&port->perst);
|
||||
INIT_LIST_HEAD(&perst->list);
|
||||
list_add_tail(&perst->list, &port->perst);
|
||||
@@ -1845,9 +1985,7 @@ static int qcom_pcie_probe(struct platform_device *pdev)
|
||||
struct qcom_pcie_perst *perst, *tmp_perst;
|
||||
struct qcom_pcie_port *port, *tmp_port;
|
||||
const struct qcom_pcie_cfg *pcie_cfg;
|
||||
unsigned long max_freq = ULONG_MAX;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct dev_pm_opp *opp;
|
||||
struct qcom_pcie *pcie;
|
||||
struct dw_pcie_rp *pp;
|
||||
struct resource *res;
|
||||
@@ -1951,21 +2089,9 @@ static int qcom_pcie_probe(struct platform_device *pdev)
|
||||
* probe(), OPP will be updated using qcom_pcie_icc_opp_update().
|
||||
*/
|
||||
if (!ret) {
|
||||
opp = dev_pm_opp_find_freq_floor(dev, &max_freq);
|
||||
if (IS_ERR(opp)) {
|
||||
ret = PTR_ERR(opp);
|
||||
dev_err_probe(pci->dev, ret,
|
||||
"Unable to find max freq OPP\n");
|
||||
goto err_pm_runtime_put;
|
||||
} else {
|
||||
ret = dev_pm_opp_set_opp(dev, opp);
|
||||
}
|
||||
|
||||
dev_pm_opp_put(opp);
|
||||
ret = qcom_pcie_set_max_opp(dev);
|
||||
if (ret) {
|
||||
dev_err_probe(pci->dev, ret,
|
||||
"Failed to set OPP for freq %lu\n",
|
||||
max_freq);
|
||||
dev_err_probe(dev, ret, "Failed to set max OPP\n");
|
||||
goto err_pm_runtime_put;
|
||||
}
|
||||
|
||||
@@ -2039,53 +2165,51 @@ static int qcom_pcie_suspend_noirq(struct device *dev)
|
||||
if (!pcie)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Set minimum bandwidth required to keep data path functional during
|
||||
* suspend.
|
||||
*/
|
||||
if (pcie->icc_mem) {
|
||||
ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1));
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to set bandwidth for PCIe-MEM interconnect path: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
ret = dw_pcie_suspend_noirq(pcie->pci);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Turn OFF the resources only for controllers without active PCIe
|
||||
* devices. For controllers with active devices, the resources are kept
|
||||
* ON and the link is expected to be in L0/L1 (sub)states.
|
||||
*
|
||||
* Turning OFF the resources for controllers with active PCIe devices
|
||||
* will trigger access violation during the end of the suspend cycle,
|
||||
* as kernel tries to access the PCIe devices config space for masking
|
||||
* MSIs.
|
||||
*
|
||||
* Also, it is not desirable to put the link into L2/L3 state as that
|
||||
* implies VDD supply will be removed and the devices may go into
|
||||
* powerdown state. This will affect the lifetime of the storage devices
|
||||
* like NVMe.
|
||||
*/
|
||||
if (!dw_pcie_link_up(pcie->pci)) {
|
||||
qcom_pcie_host_deinit(&pcie->pci->pp);
|
||||
pcie->suspended = true;
|
||||
}
|
||||
if (pcie->pci->suspended) {
|
||||
ret = icc_disable(pcie->icc_mem);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to disable PCIe-MEM interconnect path: %d\n", ret);
|
||||
|
||||
/*
|
||||
* Only disable CPU-PCIe interconnect path if the suspend is non-S2RAM.
|
||||
* Because on some platforms, DBI access can happen very late during the
|
||||
* S2RAM and a non-active CPU-PCIe interconnect path may lead to NoC
|
||||
* error.
|
||||
*/
|
||||
if (pm_suspend_target_state != PM_SUSPEND_MEM) {
|
||||
ret = icc_disable(pcie->icc_cpu);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n", ret);
|
||||
|
||||
if (pcie->use_pm_opp)
|
||||
dev_pm_opp_set_opp(pcie->pci->dev, NULL);
|
||||
} else {
|
||||
/*
|
||||
* Set minimum bandwidth required to keep data path
|
||||
* functional during suspend.
|
||||
*/
|
||||
if (pcie->icc_mem) {
|
||||
ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1));
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to set bandwidth for PCIe-MEM interconnect path: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Only disable CPU-PCIe interconnect path if the suspend
|
||||
* is non-S2RAM. On some platforms, DBI access can happen
|
||||
* very late during S2RAM and a non-active CPU-PCIe
|
||||
* interconnect path may lead to NoC error.
|
||||
*/
|
||||
if (pm_suspend_target_state != PM_SUSPEND_MEM) {
|
||||
ret = icc_disable(pcie->icc_cpu);
|
||||
if (ret)
|
||||
dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n",
|
||||
ret);
|
||||
|
||||
if (pcie->use_pm_opp)
|
||||
dev_pm_opp_set_opp(pcie->pci->dev, NULL);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -2099,25 +2223,62 @@ static int qcom_pcie_resume_noirq(struct device *dev)
|
||||
if (!pcie)
|
||||
return 0;
|
||||
|
||||
if (pm_suspend_target_state != PM_SUSPEND_MEM) {
|
||||
if (pcie->pci->suspended) {
|
||||
if (pcie->use_pm_opp) {
|
||||
ret = qcom_pcie_set_max_opp(dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to set max OPP: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = icc_enable(pcie->icc_cpu);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (pcie->suspended) {
|
||||
ret = qcom_pcie_host_init(&pcie->pci->pp);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = icc_enable(pcie->icc_mem);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable PCIe-MEM interconnect path: %d\n", ret);
|
||||
goto disable_icc_cpu;
|
||||
}
|
||||
|
||||
pcie->suspended = false;
|
||||
/*
|
||||
* Ignore -ENODEV & -EIO here since it is expected when no
|
||||
* endpoint is connected to the PCIe link.
|
||||
*/
|
||||
ret = dw_pcie_resume_noirq(pcie->pci);
|
||||
if (ret && ret != -ENODEV && ret != -EIO)
|
||||
goto disable_icc_mem;
|
||||
} else {
|
||||
if (pm_suspend_target_state != PM_SUSPEND_MEM) {
|
||||
if (pcie->use_pm_opp) {
|
||||
ret = qcom_pcie_set_max_opp(dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to set max OPP: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = icc_enable(pcie->icc_cpu);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qcom_pcie_icc_opp_update(pcie);
|
||||
|
||||
return 0;
|
||||
disable_icc_mem:
|
||||
icc_disable(pcie->icc_mem);
|
||||
disable_icc_cpu:
|
||||
icc_disable(pcie->icc_cpu);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id qcom_pcie_match[] = {
|
||||
|
||||
@@ -86,11 +86,11 @@ static void __iomem *tegra194_map_bus(struct pci_bus *bus,
|
||||
|
||||
if (bus->parent->number == cfg->busr.start) {
|
||||
if (PCI_SLOT(devfn) == 0)
|
||||
type = PCIE_ATU_TYPE_CFG0;
|
||||
type = PCIE_TLP_TYPE_CFG0_RDWR;
|
||||
else
|
||||
return NULL;
|
||||
} else {
|
||||
type = PCIE_ATU_TYPE_CFG1;
|
||||
type = PCIE_TLP_TYPE_CFG1_RDWR;
|
||||
}
|
||||
|
||||
program_outbound_atu(pcie_ecam, 0, type, cfg->res.start, busdev,
|
||||
|
||||
@@ -272,6 +272,7 @@ struct tegra_pcie_dw {
|
||||
u32 aspm_cmrt;
|
||||
u32 aspm_pwr_on_t;
|
||||
u32 aspm_l0s_enter_lat;
|
||||
u32 aspm_l1_enter_lat;
|
||||
|
||||
struct regulator *pex_ctl_supply;
|
||||
struct regulator *slot_ctl_3v3;
|
||||
@@ -715,6 +716,8 @@ static void init_host_aspm(struct tegra_pcie_dw *pcie)
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_PORT_AFR);
|
||||
val &= ~PORT_AFR_L0S_ENTRANCE_LAT_MASK;
|
||||
val |= (pcie->aspm_l0s_enter_lat << PORT_AFR_L0S_ENTRANCE_LAT_SHIFT);
|
||||
val &= ~PORT_AFR_L1_ENTRANCE_LAT_MASK;
|
||||
val |= (pcie->aspm_l1_enter_lat << PORT_AFR_L1_ENTRANCE_LAT_SHIFT);
|
||||
val |= PORT_AFR_ENTER_ASPM;
|
||||
dw_pcie_writel_dbi(pci, PCIE_PORT_AFR, val);
|
||||
}
|
||||
@@ -872,8 +875,7 @@ static void config_gen3_gen4_eq_presets(struct tegra_pcie_dw *pcie)
|
||||
dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val);
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF);
|
||||
val &= ~GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC;
|
||||
val |= FIELD_PREP(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC, 0x3ff);
|
||||
FIELD_MODIFY(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC, &val, 0x3ff);
|
||||
val &= ~GEN3_EQ_CONTROL_OFF_FB_MODE;
|
||||
dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, val);
|
||||
|
||||
@@ -883,9 +885,8 @@ static void config_gen3_gen4_eq_presets(struct tegra_pcie_dw *pcie)
|
||||
dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val);
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF);
|
||||
val &= ~GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC;
|
||||
val |= FIELD_PREP(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC,
|
||||
pcie->of_data->gen4_preset_vec);
|
||||
FIELD_MODIFY(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC, &val,
|
||||
pcie->of_data->gen4_preset_vec);
|
||||
val &= ~GEN3_EQ_CONTROL_OFF_FB_MODE;
|
||||
dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, val);
|
||||
|
||||
@@ -1115,6 +1116,7 @@ static int tegra_pcie_dw_parse_dt(struct tegra_pcie_dw *pcie)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(pcie->dev);
|
||||
struct device_node *np = pcie->dev->of_node;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
pcie->dbi_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
|
||||
@@ -1141,6 +1143,15 @@ static int tegra_pcie_dw_parse_dt(struct tegra_pcie_dw *pcie)
|
||||
dev_info(pcie->dev,
|
||||
"Failed to read ASPM L0s Entrance latency: %d\n", ret);
|
||||
|
||||
/* Default to max latency of 7. */
|
||||
pcie->aspm_l1_enter_lat = 7;
|
||||
ret = of_property_read_u32(np, "aspm-l1-entry-delay-ns", &val);
|
||||
if (!ret) {
|
||||
u32 us = DIV_ROUND_UP(val, 1000);
|
||||
|
||||
pcie->aspm_l1_enter_lat = min_t(u32, order_base_2(us), 7);
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "num-lanes", &pcie->num_lanes);
|
||||
if (ret < 0) {
|
||||
dev_err(pcie->dev, "Failed to read num-lanes: %d\n", ret);
|
||||
|
||||
175
drivers/pci/controller/dwc/pcie-ultrarisc.c
Normal file
175
drivers/pci/controller/dwc/pcie-ultrarisc.c
Normal file
@@ -0,0 +1,175 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* DWC PCIe RC driver for UltraRISC SoCs
|
||||
*
|
||||
* Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/resource.h>
|
||||
|
||||
#include "pcie-designware.h"
|
||||
|
||||
#define PCIE_CUS_CORE 0x400000
|
||||
|
||||
#define LTSSM_ENABLE BIT(7)
|
||||
#define FAST_LINK_MODE BIT(12)
|
||||
#define HOLD_PHY_RST BIT(14)
|
||||
#define L1SUB_DISABLE BIT(15)
|
||||
|
||||
#define ULTRARISC_PCIE_COMP_TIMEOUT_65_210MS 0x6
|
||||
|
||||
static struct pci_ops ultrarisc_pci_ops = {
|
||||
.map_bus = dw_pcie_own_conf_map_bus,
|
||||
.read = pci_generic_config_read32,
|
||||
.write = pci_generic_config_write32,
|
||||
};
|
||||
|
||||
static int ultrarisc_pcie_host_init(struct dw_pcie_rp *pp)
|
||||
{
|
||||
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
||||
struct pci_host_bridge *bridge = pp->bridge;
|
||||
u8 cap_exp;
|
||||
u32 val;
|
||||
|
||||
bridge->ops = &ultrarisc_pci_ops;
|
||||
|
||||
if (dw_pcie_link_up(pci))
|
||||
return 0;
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_CUS_CORE);
|
||||
val &= ~FAST_LINK_MODE;
|
||||
dw_pcie_writel_dbi(pci, PCIE_CUS_CORE, val);
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_TIMER_CTRL_MAX_FUNC_NUM);
|
||||
FIELD_MODIFY(PORT_FLT_SF_MASK, &val, PORT_FLT_SF_VAL_64);
|
||||
dw_pcie_writel_dbi(pci, PCIE_TIMER_CTRL_MAX_FUNC_NUM, val);
|
||||
|
||||
cap_exp = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
|
||||
val = dw_pcie_readl_dbi(pci, cap_exp + PCI_EXP_LNKCTL2);
|
||||
FIELD_MODIFY(PCI_EXP_LNKCTL2_TLS, &val, PCI_EXP_LNKCTL2_TLS_16_0GT);
|
||||
dw_pcie_writel_dbi(pci, cap_exp + PCI_EXP_LNKCTL2, val);
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_PORT_FORCE);
|
||||
FIELD_MODIFY(PORT_LINK_NUM_MASK, &val, 0);
|
||||
dw_pcie_writel_dbi(pci, PCIE_PORT_FORCE, val);
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, cap_exp + PCI_EXP_DEVCTL2);
|
||||
FIELD_MODIFY(PCI_EXP_DEVCTL2_COMP_TIMEOUT, &val,
|
||||
ULTRARISC_PCIE_COMP_TIMEOUT_65_210MS);
|
||||
dw_pcie_writel_dbi(pci, cap_exp + PCI_EXP_DEVCTL2, val);
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_CUS_CORE);
|
||||
val &= ~(HOLD_PHY_RST | L1SUB_DISABLE);
|
||||
dw_pcie_writel_dbi(pci, PCIE_CUS_CORE, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ultrarisc_pcie_pme_turn_off(struct dw_pcie_rp *pp)
|
||||
{
|
||||
/*
|
||||
* DP1000 does not support sending PME_Turn_Off from the RC.
|
||||
* Keep this callback empty to skip the generic MSG TLP path.
|
||||
*/
|
||||
}
|
||||
|
||||
static const struct dw_pcie_host_ops ultrarisc_pcie_host_ops = {
|
||||
.init = ultrarisc_pcie_host_init,
|
||||
.pme_turn_off = ultrarisc_pcie_pme_turn_off,
|
||||
};
|
||||
|
||||
static int ultrarisc_pcie_start_link(struct dw_pcie *pci)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = dw_pcie_readl_dbi(pci, PCIE_CUS_CORE);
|
||||
val |= LTSSM_ENABLE;
|
||||
dw_pcie_writel_dbi(pci, PCIE_CUS_CORE, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dw_pcie_ops dw_pcie_ops = {
|
||||
.start_link = ultrarisc_pcie_start_link,
|
||||
};
|
||||
|
||||
static int ultrarisc_pcie_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct dw_pcie_rp *pp;
|
||||
struct dw_pcie *pci;
|
||||
int ret;
|
||||
|
||||
pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
|
||||
if (!pci)
|
||||
return -ENOMEM;
|
||||
|
||||
pci->dev = dev;
|
||||
pci->ops = &dw_pcie_ops;
|
||||
|
||||
/* Set a default value suitable for at most 16 in and 16 out windows */
|
||||
pci->atu_size = SZ_8K;
|
||||
|
||||
pp = &pci->pp;
|
||||
|
||||
platform_set_drvdata(pdev, pci);
|
||||
|
||||
pp->num_vectors = MAX_MSI_IRQS;
|
||||
/* No L2/L3 Ready indication is available on this platform */
|
||||
pp->skip_l23_ready = true;
|
||||
pp->ops = &ultrarisc_pcie_host_ops;
|
||||
|
||||
ret = dw_pcie_host_init(pp);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to initialize host\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ultrarisc_pcie_suspend_noirq(struct device *dev)
|
||||
{
|
||||
struct dw_pcie *pci = dev_get_drvdata(dev);
|
||||
|
||||
return dw_pcie_suspend_noirq(pci);
|
||||
}
|
||||
|
||||
static int ultrarisc_pcie_resume_noirq(struct device *dev)
|
||||
{
|
||||
struct dw_pcie *pci = dev_get_drvdata(dev);
|
||||
|
||||
return dw_pcie_resume_noirq(pci);
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops ultrarisc_pcie_pm_ops = {
|
||||
NOIRQ_SYSTEM_SLEEP_PM_OPS(ultrarisc_pcie_suspend_noirq,
|
||||
ultrarisc_pcie_resume_noirq)
|
||||
};
|
||||
|
||||
static const struct of_device_id ultrarisc_pcie_of_match[] = {
|
||||
{
|
||||
.compatible = "ultrarisc,dp1000-pcie",
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ultrarisc_pcie_of_match);
|
||||
|
||||
static struct platform_driver ultrarisc_pcie_driver = {
|
||||
.driver = {
|
||||
.name = "ultrarisc-pcie",
|
||||
.of_match_table = ultrarisc_pcie_of_match,
|
||||
.suppress_bind_attrs = true,
|
||||
.pm = &ultrarisc_pcie_pm_ops,
|
||||
},
|
||||
.probe = ultrarisc_pcie_probe,
|
||||
};
|
||||
builtin_platform_driver(ultrarisc_pcie_driver);
|
||||
|
||||
MODULE_DESCRIPTION("UltraRISC DP1000 DWC PCIe host controller");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
|
||||
#include "pci-host-common.h"
|
||||
#include "../pci.h"
|
||||
#include "../pci-bridge-emul.h"
|
||||
|
||||
@@ -350,8 +351,10 @@ static int advk_pcie_wait_for_link(struct advk_pcie *pcie)
|
||||
|
||||
/* check if the link is up or not */
|
||||
for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
|
||||
if (advk_pcie_link_up(pcie))
|
||||
if (advk_pcie_link_up(pcie)) {
|
||||
pci_host_common_link_train_delay(pcie->link_gen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_pci.h>
|
||||
@@ -17,6 +18,178 @@
|
||||
|
||||
#include "pci-host-common.h"
|
||||
|
||||
/**
|
||||
* pci_host_common_delete_ports - Cleanup function for port list
|
||||
* @data: Pointer to the port list head
|
||||
*/
|
||||
void pci_host_common_delete_ports(void *data)
|
||||
{
|
||||
struct list_head *ports = data;
|
||||
struct pci_host_perst *perst, *tmp_perst;
|
||||
struct pci_host_port *port, *tmp_port;
|
||||
|
||||
list_for_each_entry_safe(port, tmp_port, ports, list) {
|
||||
list_for_each_entry_safe(perst, tmp_perst, &port->perst, list)
|
||||
list_del(&perst->list);
|
||||
list_del(&port->list);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_host_common_delete_ports);
|
||||
|
||||
/**
|
||||
* pci_host_common_parse_perst - Parse PERST# from all nodes, depth first
|
||||
* @dev: Device pointer
|
||||
* @port: PCI host port
|
||||
* @np: Device tree node to start parsing from
|
||||
*
|
||||
* Recursively parse PERST# GPIO from all PCIe bridge nodes starting from
|
||||
* @np in a depth-first manner.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
static int pci_host_common_parse_perst(struct device *dev,
|
||||
struct pci_host_port *port,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct pci_host_perst *perst;
|
||||
struct gpio_desc *reset;
|
||||
int ret;
|
||||
|
||||
if (!of_property_present(np, "reset-gpios"))
|
||||
goto parse_child_node;
|
||||
|
||||
reset = devm_fwnode_gpiod_get(dev, of_fwnode_handle(np), "reset",
|
||||
GPIOD_ASIS, "PERST#");
|
||||
if (IS_ERR(reset)) {
|
||||
/*
|
||||
* FIXME: GPIOLIB currently supports exclusive GPIO access only.
|
||||
* Non exclusive access is broken. But shared PERST# requires
|
||||
* non-exclusive access. So once GPIOLIB properly supports it,
|
||||
* implement it here.
|
||||
*/
|
||||
if (PTR_ERR(reset) == -EBUSY)
|
||||
dev_err(dev, "Shared PERST# is not supported\n");
|
||||
|
||||
return PTR_ERR(reset);
|
||||
}
|
||||
|
||||
perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL);
|
||||
if (!perst)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&perst->list);
|
||||
perst->desc = reset;
|
||||
list_add_tail(&perst->list, &port->perst);
|
||||
|
||||
parse_child_node:
|
||||
for_each_available_child_of_node_scoped(np, child) {
|
||||
ret = pci_host_common_parse_perst(dev, port, child);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_host_common_parse_port - Parse a single Root Port node
|
||||
* @dev: Device pointer
|
||||
* @bridge: PCI host bridge
|
||||
* @node: Device tree node of the Root Port
|
||||
*
|
||||
* Parse Root Port properties from the device tree. Currently it only
|
||||
* handles the PERST# GPIO (including PERST# GPIOs from all PCIe bridge
|
||||
* nodes under this Root Port), which is optional.
|
||||
*
|
||||
* NOTE: This helper fetches resources (like PERST# GPIO) optionally. If a
|
||||
* controller driver has a hard dependency on certain resources (PHY,
|
||||
* clocks, regulators, etc.), those resources MUST be modeled correctly in
|
||||
* the DT binding and validated in DTS. This helper cannot enforce such
|
||||
* dependencies and the driver may fail to operate if required resources
|
||||
* are missing.
|
||||
*
|
||||
* Return: 0 on success, -ENODEV if PERST# found in RC node (legacy binding
|
||||
* should be used), Other negative error codes on failure.
|
||||
*/
|
||||
static int pci_host_common_parse_port(struct device *dev,
|
||||
struct pci_host_bridge *bridge,
|
||||
struct device_node *node)
|
||||
{
|
||||
struct pci_host_port *port;
|
||||
int ret;
|
||||
|
||||
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
|
||||
if (!port)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&port->perst);
|
||||
|
||||
ret = pci_host_common_parse_perst(dev, port, node);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* 1. PERST# found in RP or its child nodes - list is not empty,
|
||||
* continue
|
||||
*
|
||||
* 2. PERST# not found in RP/children, but found in RC node -
|
||||
* return -ENODEV to fallback legacy binding
|
||||
*
|
||||
* 3. PERST# not found anywhere - list is empty, continue (optional
|
||||
* PERST#)
|
||||
*/
|
||||
if (list_empty(&port->perst)) {
|
||||
if (of_property_present(dev->of_node, "reset-gpios") ||
|
||||
of_property_present(dev->of_node, "reset-gpio"))
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&port->list);
|
||||
list_add_tail(&port->list, &bridge->ports);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_host_common_parse_ports - Parse Root Port nodes from device tree
|
||||
* @dev: Device pointer
|
||||
* @bridge: PCI host bridge
|
||||
*
|
||||
* Iterate through child nodes of the host bridge and parse Root Port
|
||||
* properties (currently only reset GPIOs).
|
||||
*
|
||||
* Return: 0 on success, -ENODEV if no ports found or PERST# found in RC
|
||||
* node (legacy binding should be used), Other negative error codes on
|
||||
* failure.
|
||||
*/
|
||||
int pci_host_common_parse_ports(struct device *dev, struct pci_host_bridge *bridge)
|
||||
{
|
||||
int ret = -ENODEV;
|
||||
|
||||
for_each_available_child_of_node_scoped(dev->of_node, of_port) {
|
||||
if (!of_node_is_type(of_port, "pci"))
|
||||
continue;
|
||||
ret = pci_host_common_parse_port(dev, bridge, of_port);
|
||||
if (ret)
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_add_action_or_reset(dev, pci_host_common_delete_ports,
|
||||
&bridge->ports);
|
||||
|
||||
err_cleanup:
|
||||
pci_host_common_delete_ports(&bridge->ports);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_host_common_parse_ports);
|
||||
|
||||
#define PCI_HOST_D3COLD_ALLOWED BIT(0)
|
||||
#define PCI_HOST_PME_D3COLD_CAPABLE BIT(1)
|
||||
|
||||
|
||||
static void gen_pci_unmap_cfg(void *ptr)
|
||||
{
|
||||
pci_ecam_free((struct pci_config_window *)ptr);
|
||||
@@ -68,6 +241,10 @@ int pci_host_common_init(struct platform_device *pdev,
|
||||
if (IS_ERR(cfg))
|
||||
return PTR_ERR(cfg);
|
||||
|
||||
/* Do not reassign bus numbers if probe only */
|
||||
if (!pci_has_flag(PCI_PROBE_ONLY))
|
||||
pci_add_flags(PCI_REASSIGN_ALL_BUS);
|
||||
|
||||
bridge->sysdata = cfg;
|
||||
bridge->ops = (struct pci_ops *)&ops->pci_ops;
|
||||
bridge->enable_device = ops->enable_device;
|
||||
@@ -106,5 +283,64 @@ void pci_host_common_remove(struct platform_device *pdev)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_host_common_remove);
|
||||
|
||||
static int __pci_host_common_d3cold_possible(struct pci_dev *pdev,
|
||||
void *userdata)
|
||||
{
|
||||
u32 *flags = userdata;
|
||||
|
||||
if (!pdev->dev.driver && !pci_is_enabled(pdev))
|
||||
return 0;
|
||||
|
||||
if (pdev->current_state != PCI_D3hot)
|
||||
goto exit;
|
||||
|
||||
if (device_may_wakeup(&pdev->dev)) {
|
||||
if (!pci_pme_capable(pdev, PCI_D3cold))
|
||||
goto exit;
|
||||
else
|
||||
*flags |= PCI_HOST_PME_D3COLD_CAPABLE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit:
|
||||
*flags &= ~PCI_HOST_D3COLD_ALLOWED;
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_host_common_d3cold_possible - Determine whether the host bridge can
|
||||
* transition the devices into D3cold.
|
||||
*
|
||||
* @bridge: PCI host bridge to check
|
||||
* @pme_capable: Pointer to update if there is any device capable of generating
|
||||
* PME from D3cold.
|
||||
*
|
||||
* Walk downstream PCIe endpoint devices and determine whether the host bridge
|
||||
* is permitted to transition the devices into D3cold.
|
||||
*
|
||||
* Devices under host bridge can enter D3cold only if all active PCIe
|
||||
* endpoints are in PCI_D3hot and any wakeup-enabled endpoint is capable of
|
||||
* generating PME from D3cold. Inactive endpoints are ignored.
|
||||
*
|
||||
* The @pme_capable output allows PCIe controller drivers to apply
|
||||
* platform-specific handling to preserve wakeup functionality.
|
||||
*
|
||||
* Return: %true if the host bridge may enter D3cold, otherwise %false.
|
||||
*/
|
||||
bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge,
|
||||
bool *pme_capable)
|
||||
{
|
||||
u32 flags = PCI_HOST_D3COLD_ALLOWED;
|
||||
|
||||
pci_walk_bus(bridge->bus, __pci_host_common_d3cold_possible, &flags);
|
||||
|
||||
*pme_capable = !!(flags & PCI_HOST_PME_D3COLD_CAPABLE);
|
||||
|
||||
return !!(flags & PCI_HOST_D3COLD_ALLOWED);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_host_common_d3cold_possible);
|
||||
|
||||
MODULE_DESCRIPTION("Common library for PCI host controller drivers");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
@@ -10,8 +10,39 @@
|
||||
#ifndef _PCI_HOST_COMMON_H
|
||||
#define _PCI_HOST_COMMON_H
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include "../pci.h"
|
||||
|
||||
struct pci_ecam_ops;
|
||||
|
||||
/**
|
||||
* struct pci_host_perst - PERST# GPIO descriptor
|
||||
* @list: List node for linking multiple PERST# GPIOs
|
||||
* @desc: GPIO descriptor for PERST# signal
|
||||
*
|
||||
* This structure holds a single PERST# GPIO descriptor.
|
||||
*/
|
||||
struct pci_host_perst {
|
||||
struct list_head list;
|
||||
struct gpio_desc *desc;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct pci_host_port - Generic Root Port properties
|
||||
* @list: List node for linking multiple ports
|
||||
* @perst: List of PERST# GPIO descriptors for this port and its children
|
||||
*
|
||||
* This structure contains common properties that can be parsed from
|
||||
* Root Port device tree nodes.
|
||||
*/
|
||||
struct pci_host_port {
|
||||
struct list_head list;
|
||||
struct list_head perst;
|
||||
};
|
||||
|
||||
void pci_host_common_delete_ports(void *data);
|
||||
int pci_host_common_parse_ports(struct device *dev,
|
||||
struct pci_host_bridge *bridge);
|
||||
int pci_host_common_probe(struct platform_device *pdev);
|
||||
int pci_host_common_init(struct platform_device *pdev,
|
||||
struct pci_host_bridge *bridge,
|
||||
@@ -20,4 +51,21 @@ void pci_host_common_remove(struct platform_device *pdev);
|
||||
|
||||
struct pci_config_window *pci_host_common_ecam_create(struct device *dev,
|
||||
struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops);
|
||||
|
||||
bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge,
|
||||
bool *pme_capable);
|
||||
|
||||
/**
|
||||
* pci_host_common_link_train_delay - Wait 100 ms if link speed > 5 GT/s
|
||||
* @max_link_speed: the maximum link speed (2 = 5.0 GT/s, 3 = 8.0 GT/s, ...)
|
||||
*
|
||||
* Must be called after Link training completes and before the first
|
||||
* Configuration Request is sent.
|
||||
*/
|
||||
static inline void pci_host_common_link_train_delay(int max_link_speed)
|
||||
{
|
||||
if (max_link_speed > 2)
|
||||
msleep(PCIE_RESET_CONFIG_WAIT_MS);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -80,6 +80,18 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON,
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON,
|
||||
DEV_LS7A_LPC, system_bus_quirk);
|
||||
|
||||
static const struct pci_device_id loongson_internal_bridge_devids[] = {
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS2K_PCIE_PORT0) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT0) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT1) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT2) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT3) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT4) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT5) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT6) },
|
||||
{ 0, },
|
||||
};
|
||||
|
||||
/*
|
||||
* Some Loongson PCIe ports have hardware limitations on their Maximum Read
|
||||
* Request Size. They can't handle anything larger than this. Sane
|
||||
@@ -92,24 +104,13 @@ static void loongson_set_min_mrrs_quirk(struct pci_dev *pdev)
|
||||
{
|
||||
struct pci_bus *bus = pdev->bus;
|
||||
struct pci_dev *bridge;
|
||||
static const struct pci_device_id bridge_devids[] = {
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS2K_PCIE_PORT0) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT0) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT1) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT2) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT3) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT4) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT5) },
|
||||
{ PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT6) },
|
||||
{ 0, },
|
||||
};
|
||||
|
||||
/* look for the matching bridge */
|
||||
while (!pci_is_root_bus(bus)) {
|
||||
bridge = bus->self;
|
||||
bus = bus->parent;
|
||||
|
||||
if (pci_match_id(bridge_devids, bridge)) {
|
||||
if (pci_match_id(loongson_internal_bridge_devids, bridge)) {
|
||||
if (pcie_get_readrq(pdev) > 256) {
|
||||
pci_info(pdev, "limiting MRRS to 256\n");
|
||||
pcie_set_readrq(pdev, 256);
|
||||
@@ -176,6 +177,42 @@ static void loongson_pci_msi_quirk(struct pci_dev *dev)
|
||||
}
|
||||
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, DEV_LS7A_PCIE_PORT5, loongson_pci_msi_quirk);
|
||||
|
||||
/*
|
||||
* Older steppings of the Loongson-3C6000 series incorrectly report the
|
||||
* supported link speeds on their PCIe bridges (device IDs 0x3c19,
|
||||
* 0x3c29) as only 2.5 GT/s, despite the upstream bus supporting speeds
|
||||
* from 2.5 GT/s up to 16 GT/s.
|
||||
*/
|
||||
static void loongson_pci_bridge_speed_quirk(struct pci_dev *pdev)
|
||||
{
|
||||
u8 old_supported_speeds = pdev->supported_speeds;
|
||||
|
||||
switch (pdev->bus->max_bus_speed) {
|
||||
case PCIE_SPEED_16_0GT:
|
||||
pdev->supported_speeds |= PCI_EXP_LNKCAP2_SLS_16_0GB;
|
||||
fallthrough;
|
||||
case PCIE_SPEED_8_0GT:
|
||||
pdev->supported_speeds |= PCI_EXP_LNKCAP2_SLS_8_0GB;
|
||||
fallthrough;
|
||||
case PCIE_SPEED_5_0GT:
|
||||
pdev->supported_speeds |= PCI_EXP_LNKCAP2_SLS_5_0GB;
|
||||
fallthrough;
|
||||
case PCIE_SPEED_2_5GT:
|
||||
pdev->supported_speeds |= PCI_EXP_LNKCAP2_SLS_2_5GB;
|
||||
break;
|
||||
default:
|
||||
pci_warn(pdev, "unexpected max bus speed");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (pdev->supported_speeds != old_supported_speeds)
|
||||
pci_info(pdev, "fixed up supported link speeds: 0x%x => 0x%x",
|
||||
old_supported_speeds, pdev->supported_speeds);
|
||||
}
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_LOONGSON, 0x3c19, loongson_pci_bridge_speed_quirk);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_LOONGSON, 0x3c29, loongson_pci_bridge_speed_quirk);
|
||||
|
||||
static struct loongson_pci *pci_bus_to_loongson_pci(struct pci_bus *bus)
|
||||
{
|
||||
struct pci_config_window *cfg;
|
||||
@@ -230,11 +267,11 @@ static void __iomem *pci_loongson_map_bus(struct pci_bus *bus,
|
||||
struct loongson_pci *priv = pci_bus_to_loongson_pci(bus);
|
||||
|
||||
/*
|
||||
* Do not read more than one device on the bus other than
|
||||
* the host bus.
|
||||
* Do not read more than one device on the internal bridges.
|
||||
*/
|
||||
if ((priv->data->flags & FLAG_DEV_FIX) && bus->self) {
|
||||
if (!pci_is_root_bus(bus) && (device > 0))
|
||||
if (!pci_is_root_bus(bus) && (device > 0) &&
|
||||
pci_match_id(loongson_internal_bridge_devids, bus->self))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,9 +57,9 @@
|
||||
#define PCIE_CONF_DATA_OFF 0x18fc
|
||||
#define PCIE_INT_CAUSE_OFF 0x1900
|
||||
#define PCIE_INT_UNMASK_OFF 0x1910
|
||||
#define PCIE_INT_INTX(i) BIT(24+i)
|
||||
#define PCIE_INT_PM_PME BIT(28)
|
||||
#define PCIE_INT_ALL_MASK GENMASK(31, 0)
|
||||
#define PCIE_INT_INTX(i) BIT_U32(24 + (i))
|
||||
#define PCIE_INT_PM_PME BIT_U32(28)
|
||||
#define PCIE_INT_ALL_MASK GENMASK_U32(31, 0)
|
||||
#define PCIE_CTRL_OFF 0x1a00
|
||||
#define PCIE_CTRL_X1_MODE 0x0001
|
||||
#define PCIE_CTRL_RC_MODE BIT(1)
|
||||
@@ -263,8 +263,7 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
|
||||
* not set correctly then link with endpoint card is not established.
|
||||
*/
|
||||
lnkcap = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
|
||||
lnkcap &= ~PCI_EXP_LNKCAP_MLW;
|
||||
lnkcap |= FIELD_PREP(PCI_EXP_LNKCAP_MLW, port->is_x4 ? 4 : 1);
|
||||
FIELD_MODIFY(PCI_EXP_LNKCAP_MLW, &lnkcap, port->is_x4 ? 4 : 1);
|
||||
mvebu_writel(port, lnkcap, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP);
|
||||
|
||||
/* Disable Root Bridge I/O space, memory space and bus mastering. */
|
||||
|
||||
@@ -864,11 +864,25 @@ static int altera_pcie_init_irq_domain(struct altera_pcie *pcie)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void altera_pcie_disable_irq(struct altera_pcie *pcie)
|
||||
{
|
||||
if (pcie->pcie_data->version == ALTERA_PCIE_V1 ||
|
||||
pcie->pcie_data->version == ALTERA_PCIE_V2) {
|
||||
/* Disable all P2A interrupts */
|
||||
cra_writel(pcie, 0, P2A_INT_ENABLE);
|
||||
} else if (pcie->pcie_data->version == ALTERA_PCIE_V3) {
|
||||
/* Disable port-level interrupts (CFG_AER, etc.) */
|
||||
writel(0, pcie->hip_base +
|
||||
pcie->pcie_data->port_conf_offset +
|
||||
pcie->pcie_data->port_irq_enable_offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void altera_pcie_irq_teardown(struct altera_pcie *pcie)
|
||||
{
|
||||
altera_pcie_disable_irq(pcie);
|
||||
irq_set_chained_handler_and_data(pcie->irq, NULL, NULL);
|
||||
irq_domain_remove(pcie->irq_domain);
|
||||
irq_dispose_mapping(pcie->irq);
|
||||
}
|
||||
|
||||
static int altera_pcie_parse_dt(struct altera_pcie *pcie)
|
||||
@@ -891,7 +905,6 @@ static int altera_pcie_parse_dt(struct altera_pcie *pcie)
|
||||
if (pcie->irq < 0)
|
||||
return pcie->irq;
|
||||
|
||||
irq_set_chained_handler_and_data(pcie->irq, pcie->pcie_data->ops->rp_isr, pcie);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1020,6 +1033,14 @@ static int altera_pcie_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The chained handler uses pcie->irq_domain, so set it only after the
|
||||
* INTx domain has been created.
|
||||
*/
|
||||
irq_set_chained_handler_and_data(pcie->irq,
|
||||
pcie->pcie_data->ops->rp_isr,
|
||||
pcie);
|
||||
|
||||
if (pcie->pcie_data->version == ALTERA_PCIE_V1 ||
|
||||
pcie->pcie_data->version == ALTERA_PCIE_V2) {
|
||||
/* clear all interrupts */
|
||||
@@ -1037,7 +1058,16 @@ static int altera_pcie_probe(struct platform_device *pdev)
|
||||
bridge->busnr = pcie->root_bus_nr;
|
||||
bridge->ops = &altera_pcie_ops;
|
||||
|
||||
return pci_host_probe(bridge);
|
||||
ret = pci_host_probe(bridge);
|
||||
if (ret)
|
||||
goto err_teardown_irq;
|
||||
|
||||
return 0;
|
||||
|
||||
err_teardown_irq:
|
||||
altera_pcie_irq_teardown(pcie);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void altera_pcie_remove(struct platform_device *pdev)
|
||||
@@ -1045,8 +1075,10 @@ static void altera_pcie_remove(struct platform_device *pdev)
|
||||
struct altera_pcie *pcie = platform_get_drvdata(pdev);
|
||||
struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie);
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pci_stop_root_bus(bridge->bus);
|
||||
pci_remove_root_bus(bridge->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
altera_pcie_irq_teardown(pcie);
|
||||
}
|
||||
|
||||
|
||||
@@ -127,19 +127,19 @@
|
||||
#define CFG0_READ_FMTTYPE \
|
||||
FIELD_PREP(ASPEED_TLP_COMMON_FIELDS, \
|
||||
ASPEED_TLP_FMT_TYPE(PCIE_TLP_FMT_3DW_NO_DATA, \
|
||||
PCIE_TLP_TYPE_CFG0_RD))
|
||||
PCIE_TLP_TYPE_CFG0_RDWR))
|
||||
#define CFG0_WRITE_FMTTYPE \
|
||||
FIELD_PREP(ASPEED_TLP_COMMON_FIELDS, \
|
||||
ASPEED_TLP_FMT_TYPE(PCIE_TLP_FMT_3DW_DATA, \
|
||||
PCIE_TLP_TYPE_CFG0_WR))
|
||||
PCIE_TLP_TYPE_CFG0_RDWR))
|
||||
#define CFG1_READ_FMTTYPE \
|
||||
FIELD_PREP(ASPEED_TLP_COMMON_FIELDS, \
|
||||
ASPEED_TLP_FMT_TYPE(PCIE_TLP_FMT_3DW_NO_DATA, \
|
||||
PCIE_TLP_TYPE_CFG1_RD))
|
||||
PCIE_TLP_TYPE_CFG1_RDWR))
|
||||
#define CFG1_WRITE_FMTTYPE \
|
||||
FIELD_PREP(ASPEED_TLP_COMMON_FIELDS, \
|
||||
ASPEED_TLP_FMT_TYPE(PCIE_TLP_FMT_3DW_DATA, \
|
||||
PCIE_TLP_TYPE_CFG1_WR))
|
||||
PCIE_TLP_TYPE_CFG1_RDWR))
|
||||
#define CFG_PAYLOAD_SIZE 0x01 /* 1 DWORD */
|
||||
#define TLP_HEADER_BYTE_EN(x, y) ((GENMASK((x) - 1, 0) << ((y) % 4)))
|
||||
#define TLP_GET_VALUE(x, y, z) \
|
||||
|
||||
@@ -1894,8 +1894,10 @@ static void brcm_pcie_remove(struct platform_device *pdev)
|
||||
struct brcm_pcie *pcie = platform_get_drvdata(pdev);
|
||||
struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie);
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pci_stop_root_bus(bridge->bus);
|
||||
pci_remove_root_bus(bridge->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
if (pcie->cfg->has_err_report)
|
||||
brcm_unregister_die_notifiers(pcie);
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ static int iproc_bcma_pcie_probe(struct bcma_device *bdev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pcie->map_irq = iproc_bcma_pcie_map_irq;
|
||||
bridge->map_irq = iproc_bcma_pcie_map_irq;
|
||||
|
||||
bcma_set_drvdata(bdev, pcie);
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ static int iproc_pltfm_pcie_probe(struct platform_device *pdev)
|
||||
switch (pcie->type) {
|
||||
case IPROC_PCIE_PAXC:
|
||||
case IPROC_PCIE_PAXC_V2:
|
||||
pcie->map_irq = NULL;
|
||||
bridge->map_irq = NULL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -1502,7 +1502,6 @@ int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res)
|
||||
|
||||
host->ops = &iproc_pcie_ops;
|
||||
host->sysdata = pcie;
|
||||
host->map_irq = pcie->map_irq;
|
||||
|
||||
ret = pci_host_probe(host);
|
||||
if (ret < 0) {
|
||||
@@ -1529,8 +1528,10 @@ void iproc_pcie_remove(struct iproc_pcie *pcie)
|
||||
{
|
||||
struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pci_stop_root_bus(host->bus);
|
||||
pci_remove_root_bus(host->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
|
||||
iproc_pcie_msi_disable(pcie);
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ struct iproc_msi;
|
||||
* @base_addr: PCIe host controller register base physical address
|
||||
* @mem: host bridge memory window resource
|
||||
* @phy: optional PHY device that controls the Serdes
|
||||
* @map_irq: function callback to map interrupts
|
||||
* @ep_is_internal: indicates an internal emulated endpoint device is connected
|
||||
* @iproc_cfg_read: indicates the iProc config read function should be used
|
||||
* @rej_unconfig_pf: indicates the root complex needs to detect and reject
|
||||
@@ -91,7 +90,6 @@ struct iproc_pcie {
|
||||
phys_addr_t base_addr;
|
||||
struct resource mem;
|
||||
struct phy *phy;
|
||||
int (*map_irq)(const struct pci_dev *, u8, u8);
|
||||
bool ep_is_internal;
|
||||
bool iproc_cfg_read;
|
||||
bool rej_unconfig_pf;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include "pci-host-common.h"
|
||||
#include "../pci.h"
|
||||
|
||||
#define PCIE_BASE_CFG_REG 0x14
|
||||
@@ -63,6 +64,12 @@
|
||||
#define PCIE_BRG_RSTB BIT(2)
|
||||
#define PCIE_PE_RSTB BIT(3)
|
||||
|
||||
/*
|
||||
* As described in the datasheet of MediaTek PCIe Gen3 controller, wait 10ms
|
||||
* after setting PCIE_BRG_RSTB, and before accessing PCIe internal registers.
|
||||
*/
|
||||
#define PCIE_BRG_RST_RDY_MS 10
|
||||
|
||||
#define PCIE_LTSSM_STATUS_REG 0x150
|
||||
#define PCIE_LTSSM_STATE_MASK GENMASK(28, 24)
|
||||
#define PCIE_LTSSM_STATE(val) ((val & PCIE_LTSSM_STATE_MASK) >> 24)
|
||||
@@ -430,6 +437,21 @@ static int mtk_pcie_devices_power_up(struct mtk_gen3_pcie *pcie)
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some of MediaTek's chips won't output REFCLK when PCIE_PHY_RSTB is
|
||||
* asserted, we have to de-assert MAC & PHY & BRG reset signals first
|
||||
* to allow the REFCLK to be stable. While PCIE_BRG_RSTB is asserted,
|
||||
* there is a short period during which the PCIe internal register
|
||||
* cannot be accessed, so we need to wait 10ms here.
|
||||
*/
|
||||
msleep(PCIE_BRG_RST_RDY_MS);
|
||||
|
||||
if (!(pcie->soc->flags & SKIP_PCIE_RSTB)) {
|
||||
/* De-assert MAC, PHY and BRG reset signals */
|
||||
val &= ~(PCIE_MAC_RSTB | PCIE_PHY_RSTB | PCIE_BRG_RSTB);
|
||||
writel_relaxed(val, pcie->base + PCIE_RST_CTRL_REG);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in PCIe CEM specification revision 6.0.
|
||||
*
|
||||
@@ -439,9 +461,8 @@ static int mtk_pcie_devices_power_up(struct mtk_gen3_pcie *pcie)
|
||||
msleep(PCIE_T_PVPERL_MS);
|
||||
|
||||
if (!(pcie->soc->flags & SKIP_PCIE_RSTB)) {
|
||||
/* De-assert reset signals */
|
||||
val &= ~(PCIE_MAC_RSTB | PCIE_PHY_RSTB | PCIE_BRG_RSTB |
|
||||
PCIE_PE_RSTB);
|
||||
/* De-assert PERST# signal */
|
||||
val &= ~PCIE_PE_RSTB;
|
||||
writel_relaxed(val, pcie->base + PCIE_RST_CTRL_REG);
|
||||
}
|
||||
|
||||
@@ -494,8 +515,7 @@ static int mtk_pcie_startup_port(struct mtk_gen3_pcie *pcie)
|
||||
/* Set Link Control 2 (LNKCTL2) speed restriction, if any */
|
||||
if (pcie->max_link_speed) {
|
||||
val = readl_relaxed(pcie->base + PCIE_CONF_LINK2_CTL_STS);
|
||||
val &= ~PCIE_CONF_LINK2_LCR2_LINK_SPEED;
|
||||
val |= FIELD_PREP(PCIE_CONF_LINK2_LCR2_LINK_SPEED, pcie->max_link_speed);
|
||||
FIELD_MODIFY(PCIE_CONF_LINK2_LCR2_LINK_SPEED, &val, pcie->max_link_speed);
|
||||
writel_relaxed(val, pcie->base + PCIE_CONF_LINK2_CTL_STS);
|
||||
}
|
||||
|
||||
@@ -570,6 +590,8 @@ static int mtk_pcie_startup_port(struct mtk_gen3_pcie *pcie)
|
||||
goto err_power_down_device;
|
||||
}
|
||||
|
||||
pci_host_common_link_train_delay(pcie->max_link_speed);
|
||||
|
||||
return 0;
|
||||
|
||||
err_power_down_device:
|
||||
@@ -1222,8 +1244,8 @@ static int mtk_pcie_probe(struct platform_device *pdev)
|
||||
|
||||
err = pci_pwrctrl_create_devices(pcie->dev);
|
||||
if (err) {
|
||||
goto err_tear_down_irq;
|
||||
dev_err_probe(dev, err, "failed to create pwrctrl devices\n");
|
||||
goto err_tear_down_irq;
|
||||
}
|
||||
|
||||
err = mtk_pcie_setup(pcie);
|
||||
@@ -1260,12 +1282,20 @@ static void mtk_pcie_remove(struct platform_device *pdev)
|
||||
pci_remove_root_bus(host->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
|
||||
pci_pwrctrl_power_off_devices(pcie->dev);
|
||||
mtk_pcie_devices_power_down(pcie);
|
||||
mtk_pcie_power_down(pcie);
|
||||
pci_pwrctrl_destroy_devices(pcie->dev);
|
||||
mtk_pcie_irq_teardown(pcie);
|
||||
}
|
||||
|
||||
static void mtk_pcie_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct mtk_gen3_pcie *pcie = platform_get_drvdata(pdev);
|
||||
|
||||
mtk_pcie_devices_power_down(pcie);
|
||||
mtk_pcie_power_down(pcie);
|
||||
}
|
||||
|
||||
static void mtk_pcie_irq_save(struct mtk_gen3_pcie *pcie)
|
||||
{
|
||||
int i;
|
||||
@@ -1404,6 +1434,7 @@ MODULE_DEVICE_TABLE(of, mtk_pcie_of_match);
|
||||
static struct platform_driver mtk_pcie_driver = {
|
||||
.probe = mtk_pcie_probe,
|
||||
.remove = mtk_pcie_remove,
|
||||
.shutdown = mtk_pcie_shutdown,
|
||||
.driver = {
|
||||
.name = "mtk-pcie-gen3",
|
||||
.of_match_table = mtk_pcie_of_match,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* Honghui Zhang <honghui.zhang@mediatek.com>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iopoll.h>
|
||||
@@ -61,7 +62,7 @@
|
||||
/* MediaTek specific configuration registers */
|
||||
#define PCIE_FTS_NUM 0x70c
|
||||
#define PCIE_FTS_NUM_MASK GENMASK(15, 8)
|
||||
#define PCIE_FTS_NUM_L0(x) ((x) & 0xff << 8)
|
||||
#define PCIE_FTS_NUM_L0(x) FIELD_PREP(PCIE_FTS_NUM_MASK, x)
|
||||
|
||||
#define PCIE_FC_CREDIT 0x73c
|
||||
#define PCIE_FC_CREDIT_MASK (GENMASK(31, 31) | GENMASK(28, 16))
|
||||
@@ -111,10 +112,6 @@
|
||||
#define APP_CFG_REQ BIT(0)
|
||||
#define APP_CPL_STATUS GENMASK(7, 5)
|
||||
|
||||
#define CFG_WRRD_TYPE_0 4
|
||||
#define CFG_WR_FMT 2
|
||||
#define CFG_RD_FMT 0
|
||||
|
||||
#define CFG_DW0_LENGTH(length) ((length) & GENMASK(9, 0))
|
||||
#define CFG_DW0_TYPE(type) (((type) << 24) & GENMASK(28, 24))
|
||||
#define CFG_DW0_FMT(fmt) (((fmt) << 29) & GENMASK(31, 29))
|
||||
@@ -175,6 +172,7 @@ struct mtk_pcie_soc {
|
||||
/**
|
||||
* struct mtk_pcie_port - PCIe port information
|
||||
* @base: IO mapped register base
|
||||
* @phys_base: Physical address of the I/O register base region
|
||||
* @list: port list
|
||||
* @pcie: pointer to PCIe host info
|
||||
* @reset: pointer to port reset control
|
||||
@@ -196,6 +194,7 @@ struct mtk_pcie_soc {
|
||||
*/
|
||||
struct mtk_pcie_port {
|
||||
void __iomem *base;
|
||||
phys_addr_t phys_base;
|
||||
struct list_head list;
|
||||
struct mtk_pcie *pcie;
|
||||
struct reset_control *reset;
|
||||
@@ -295,7 +294,7 @@ static int mtk_pcie_hw_rd_cfg(struct mtk_pcie_port *port, u32 bus, u32 devfn,
|
||||
u32 tmp;
|
||||
|
||||
/* Write PCIe configuration transaction header for Cfgrd */
|
||||
writel(CFG_HEADER_DW0(CFG_WRRD_TYPE_0, CFG_RD_FMT),
|
||||
writel(CFG_HEADER_DW0(PCIE_TLP_TYPE_CFG0_RDWR, PCIE_TLP_FMT_3DW_NO_DATA),
|
||||
port->base + PCIE_CFG_HEADER0);
|
||||
writel(CFG_HEADER_DW1(where, size), port->base + PCIE_CFG_HEADER1);
|
||||
writel(CFG_HEADER_DW2(where, PCI_FUNC(devfn), PCI_SLOT(devfn), bus),
|
||||
@@ -325,7 +324,7 @@ static int mtk_pcie_hw_wr_cfg(struct mtk_pcie_port *port, u32 bus, u32 devfn,
|
||||
int where, int size, u32 val)
|
||||
{
|
||||
/* Write PCIe configuration transaction header for Cfgwr */
|
||||
writel(CFG_HEADER_DW0(CFG_WRRD_TYPE_0, CFG_WR_FMT),
|
||||
writel(CFG_HEADER_DW0(PCIE_TLP_TYPE_CFG0_RDWR, PCIE_TLP_FMT_3DW_DATA),
|
||||
port->base + PCIE_CFG_HEADER0);
|
||||
writel(CFG_HEADER_DW1(where, size), port->base + PCIE_CFG_HEADER1);
|
||||
writel(CFG_HEADER_DW2(where, PCI_FUNC(devfn), PCI_SLOT(devfn), bus),
|
||||
@@ -405,7 +404,7 @@ static void mtk_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
phys_addr_t addr;
|
||||
|
||||
/* MT2712/MT7622 only support 32-bit MSI addresses */
|
||||
addr = virt_to_phys(port->base + PCIE_MSI_VECTOR);
|
||||
addr = port->phys_base + PCIE_MSI_VECTOR;
|
||||
msg->address_hi = 0;
|
||||
msg->address_lo = lower_32_bits(addr);
|
||||
|
||||
@@ -520,7 +519,7 @@ static void mtk_pcie_enable_msi(struct mtk_pcie_port *port)
|
||||
u32 val;
|
||||
phys_addr_t msg_addr;
|
||||
|
||||
msg_addr = virt_to_phys(port->base + PCIE_MSI_VECTOR);
|
||||
msg_addr = port->phys_base + PCIE_MSI_VECTOR;
|
||||
val = lower_32_bits(msg_addr);
|
||||
writel(val, port->base + PCIE_IMSI_ADDR);
|
||||
|
||||
@@ -529,23 +528,27 @@ static void mtk_pcie_enable_msi(struct mtk_pcie_port *port)
|
||||
writel(val, port->base + PCIE_INT_MASK);
|
||||
}
|
||||
|
||||
static void mtk_pcie_irq_teardown_port(struct mtk_pcie_port *port)
|
||||
{
|
||||
irq_set_chained_handler_and_data(port->irq, NULL, NULL);
|
||||
|
||||
if (port->irq_domain)
|
||||
irq_domain_remove(port->irq_domain);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PCI_MSI)) {
|
||||
if (port->inner_domain)
|
||||
irq_domain_remove(port->inner_domain);
|
||||
}
|
||||
|
||||
irq_dispose_mapping(port->irq);
|
||||
}
|
||||
|
||||
static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie)
|
||||
{
|
||||
struct mtk_pcie_port *port, *tmp;
|
||||
|
||||
list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
|
||||
irq_set_chained_handler_and_data(port->irq, NULL, NULL);
|
||||
|
||||
if (port->irq_domain)
|
||||
irq_domain_remove(port->irq_domain);
|
||||
|
||||
if (IS_ENABLED(CONFIG_PCI_MSI)) {
|
||||
if (port->inner_domain)
|
||||
irq_domain_remove(port->inner_domain);
|
||||
}
|
||||
|
||||
irq_dispose_mapping(port->irq);
|
||||
}
|
||||
list_for_each_entry_safe(port, tmp, &pcie->ports, list)
|
||||
mtk_pcie_irq_teardown_port(port);
|
||||
}
|
||||
|
||||
static int mtk_pcie_intx_map(struct irq_domain *domain, unsigned int irq,
|
||||
@@ -865,7 +868,7 @@ static int mtk_pcie_startup_port_an7583(struct mtk_pcie_port *port)
|
||||
return mtk_pcie_startup_port_v2(port);
|
||||
}
|
||||
|
||||
static void mtk_pcie_enable_port(struct mtk_pcie_port *port)
|
||||
static int mtk_pcie_enable_port(struct mtk_pcie_port *port)
|
||||
{
|
||||
struct mtk_pcie *pcie = port->pcie;
|
||||
struct device *dev = pcie->dev;
|
||||
@@ -874,7 +877,7 @@ static void mtk_pcie_enable_port(struct mtk_pcie_port *port)
|
||||
err = clk_prepare_enable(port->sys_ck);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to enable sys_ck%d clock\n", port->slot);
|
||||
goto err_sys_clk;
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_prepare_enable(port->ahb_ck);
|
||||
@@ -922,11 +925,15 @@ static void mtk_pcie_enable_port(struct mtk_pcie_port *port)
|
||||
goto err_phy_on;
|
||||
}
|
||||
|
||||
if (!pcie->soc->startup(port))
|
||||
return;
|
||||
err = pcie->soc->startup(port);
|
||||
if (err) {
|
||||
dev_info(dev, "Port%d link down\n", port->slot);
|
||||
goto err_soc_startup;
|
||||
}
|
||||
|
||||
dev_info(dev, "Port%d link down\n", port->slot);
|
||||
return 0;
|
||||
|
||||
err_soc_startup:
|
||||
phy_power_off(port->phy);
|
||||
err_phy_on:
|
||||
phy_exit(port->phy);
|
||||
@@ -942,8 +949,8 @@ static void mtk_pcie_enable_port(struct mtk_pcie_port *port)
|
||||
clk_disable_unprepare(port->ahb_ck);
|
||||
err_ahb_clk:
|
||||
clk_disable_unprepare(port->sys_ck);
|
||||
err_sys_clk:
|
||||
mtk_pcie_port_free(port);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int mtk_pcie_parse_port(struct mtk_pcie *pcie,
|
||||
@@ -953,6 +960,7 @@ static int mtk_pcie_parse_port(struct mtk_pcie *pcie,
|
||||
struct mtk_pcie_port *port;
|
||||
struct device *dev = pcie->dev;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct resource *res;
|
||||
char name[20];
|
||||
int err;
|
||||
|
||||
@@ -961,7 +969,14 @@ static int mtk_pcie_parse_port(struct mtk_pcie *pcie,
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(name, sizeof(name), "port%d", slot);
|
||||
port->base = devm_platform_ioremap_resource_byname(pdev, name);
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
|
||||
if (!res) {
|
||||
dev_err(dev, "failed to get port%d base\n", slot);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
port->phys_base = res->start;
|
||||
port->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(port->base)) {
|
||||
dev_err(dev, "failed to map port%d base\n", slot);
|
||||
return PTR_ERR(port->base);
|
||||
@@ -1109,8 +1124,13 @@ static int mtk_pcie_setup(struct mtk_pcie *pcie)
|
||||
return err;
|
||||
|
||||
/* enable each port, and then check link status */
|
||||
list_for_each_entry_safe(port, tmp, &pcie->ports, list)
|
||||
mtk_pcie_enable_port(port);
|
||||
list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
|
||||
err = mtk_pcie_enable_port(port);
|
||||
if (err) {
|
||||
mtk_pcie_irq_teardown_port(port);
|
||||
mtk_pcie_port_free(port);
|
||||
}
|
||||
}
|
||||
|
||||
/* power down PCIe subsys if slots are all empty (link down) */
|
||||
if (list_empty(&pcie->ports))
|
||||
@@ -1172,8 +1192,10 @@ static void mtk_pcie_remove(struct platform_device *pdev)
|
||||
struct mtk_pcie *pcie = platform_get_drvdata(pdev);
|
||||
struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pci_stop_root_bus(host->bus);
|
||||
pci_remove_root_bus(host->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
mtk_pcie_free_resources(pcie);
|
||||
|
||||
mtk_pcie_irq_teardown(pcie);
|
||||
@@ -1209,14 +1231,18 @@ static int mtk_pcie_resume_noirq(struct device *dev)
|
||||
{
|
||||
struct mtk_pcie *pcie = dev_get_drvdata(dev);
|
||||
struct mtk_pcie_port *port, *tmp;
|
||||
int err;
|
||||
|
||||
if (list_empty(&pcie->ports))
|
||||
return 0;
|
||||
|
||||
clk_prepare_enable(pcie->free_ck);
|
||||
|
||||
list_for_each_entry_safe(port, tmp, &pcie->ports, list)
|
||||
mtk_pcie_enable_port(port);
|
||||
list_for_each_entry_safe(port, tmp, &pcie->ports, list) {
|
||||
err = mtk_pcie_enable_port(port);
|
||||
if (err)
|
||||
mtk_pcie_port_free(port);
|
||||
}
|
||||
|
||||
/* In case of EP was removed while system suspend. */
|
||||
if (list_empty(&pcie->ports))
|
||||
|
||||
@@ -346,7 +346,6 @@ static void rcar_pcie_hw_enable(struct rcar_pcie_host *host)
|
||||
struct rcar_pcie *pcie = &host->pcie;
|
||||
struct pci_host_bridge *bridge = pci_host_bridge_from_priv(host);
|
||||
struct resource_entry *win;
|
||||
LIST_HEAD(res);
|
||||
int i = 0;
|
||||
|
||||
/* Try setting 5 GT/s link speed */
|
||||
|
||||
@@ -1012,8 +1012,10 @@ static void rockchip_pcie_remove(struct platform_device *pdev)
|
||||
struct rockchip_pcie *rockchip = dev_get_drvdata(dev);
|
||||
struct pci_host_bridge *bridge = pci_host_bridge_from_priv(rockchip);
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
pci_stop_root_bus(bridge->bus);
|
||||
pci_remove_root_bus(bridge->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
irq_domain_remove(rockchip->irq_domain);
|
||||
|
||||
rockchip_pcie_deinit_phys(rockchip);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#include "pci-host-common.h"
|
||||
#include "../pci.h"
|
||||
|
||||
/* AXI registers */
|
||||
@@ -1663,7 +1664,7 @@ rzg3s_pcie_host_setup(struct rzg3s_pcie_host *host,
|
||||
if (ret)
|
||||
dev_info(dev, "Failed to set max link speed\n");
|
||||
|
||||
msleep(PCIE_RESET_CONFIG_WAIT_MS);
|
||||
pci_host_common_link_train_delay(host->max_link_speed);
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -640,8 +640,10 @@ EXPORT_SYMBOL_GPL(plda_pcie_host_init);
|
||||
|
||||
void plda_pcie_host_deinit(struct plda_pcie_rp *port)
|
||||
{
|
||||
pci_lock_rescan_remove();
|
||||
pci_stop_root_bus(port->bridge->bus);
|
||||
pci_remove_root_bus(port->bridge->bus);
|
||||
pci_unlock_rescan_remove();
|
||||
|
||||
plda_pcie_irq_domain_deinit(port);
|
||||
|
||||
|
||||
@@ -559,12 +559,15 @@ static int epf_ntb_configure_db(struct epf_ntb *ntb,
|
||||
struct pci_epc *epc;
|
||||
int ret;
|
||||
|
||||
if (db_count > MAX_DB_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
ntb_epc = ntb->epc[type];
|
||||
epc = ntb_epc->epc;
|
||||
|
||||
if (!db_count || db_count > MAX_DB_COUNT) {
|
||||
dev_err(&epc->dev, "DB count %d out of range (1 - %d)\n",
|
||||
db_count, MAX_DB_COUNT);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (msix)
|
||||
ret = epf_ntb_configure_msix(ntb, type, db_count);
|
||||
else
|
||||
@@ -1278,7 +1281,6 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb,
|
||||
u8 func_no, vfunc_no;
|
||||
struct pci_epc *epc;
|
||||
struct device *dev;
|
||||
u32 db_count;
|
||||
int ret;
|
||||
|
||||
ntb_epc = ntb->epc[type];
|
||||
@@ -1296,17 +1298,16 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb,
|
||||
func_no = ntb_epc->func_no;
|
||||
vfunc_no = ntb_epc->vfunc_no;
|
||||
|
||||
db_count = ntb->db_count;
|
||||
if (db_count > MAX_DB_COUNT) {
|
||||
dev_err(dev, "DB count cannot be more than %d\n", MAX_DB_COUNT);
|
||||
if (!ntb->db_count || ntb->db_count > MAX_DB_COUNT) {
|
||||
dev_err(dev, "DB count %d out of range (1 - %d)\n",
|
||||
ntb->db_count, MAX_DB_COUNT);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ntb->db_count = db_count;
|
||||
epc = ntb_epc->epc;
|
||||
|
||||
if (msi_capable) {
|
||||
ret = pci_epc_set_msi(epc, func_no, vfunc_no, db_count);
|
||||
ret = pci_epc_set_msi(epc, func_no, vfunc_no, ntb->db_count);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s intf: MSI configuration failed\n",
|
||||
pci_epc_interface_string(type));
|
||||
@@ -1315,7 +1316,7 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb,
|
||||
}
|
||||
|
||||
if (msix_capable) {
|
||||
ret = pci_epc_set_msix(epc, func_no, vfunc_no, db_count,
|
||||
ret = pci_epc_set_msix(epc, func_no, vfunc_no, ntb->db_count,
|
||||
ntb_epc->msix_bar,
|
||||
ntb_epc->msix_table_offset);
|
||||
if (ret) {
|
||||
|
||||
@@ -94,6 +94,7 @@ struct pci_epf_test {
|
||||
bool dma_private;
|
||||
const struct pci_epc_features *epc_features;
|
||||
struct pci_epf_bar db_bar;
|
||||
bool db_bar_programmed;
|
||||
size_t bar_size[PCI_STD_NUM_BARS];
|
||||
};
|
||||
|
||||
@@ -733,7 +734,9 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
|
||||
{
|
||||
u32 status = le32_to_cpu(reg->status);
|
||||
struct pci_epf *epf = epf_test->epf;
|
||||
struct pci_epf_doorbell_msg *db;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
unsigned long irq_flags;
|
||||
struct msi_msg *msg;
|
||||
enum pci_barno bar;
|
||||
size_t offset;
|
||||
@@ -743,13 +746,28 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
|
||||
if (ret)
|
||||
goto set_status_err;
|
||||
|
||||
msg = &epf->db_msg[0].msg;
|
||||
bar = pci_epc_get_next_free_bar(epf_test->epc_features, epf_test->test_reg_bar + 1);
|
||||
if (bar < BAR_0)
|
||||
goto err_doorbell_cleanup;
|
||||
db = &epf->db_msg[0];
|
||||
msg = &db->msg;
|
||||
epf_test->db_bar_programmed = false;
|
||||
|
||||
if (db->bar != NO_BAR) {
|
||||
/*
|
||||
* The doorbell target is already exposed via a platform-owned
|
||||
* fixed BAR
|
||||
*/
|
||||
bar = db->bar;
|
||||
offset = db->offset;
|
||||
} else {
|
||||
bar = pci_epc_get_next_free_bar(epf_test->epc_features,
|
||||
epf_test->test_reg_bar + 1);
|
||||
if (bar < BAR_0)
|
||||
goto err_doorbell_cleanup;
|
||||
}
|
||||
|
||||
irq_flags = epf->db_msg[0].irq_flags | IRQF_ONESHOT;
|
||||
|
||||
ret = request_threaded_irq(epf->db_msg[0].virq, NULL,
|
||||
pci_epf_test_doorbell_handler, IRQF_ONESHOT,
|
||||
pci_epf_test_doorbell_handler, irq_flags,
|
||||
"pci-ep-test-doorbell", epf_test);
|
||||
if (ret) {
|
||||
dev_err(&epf->dev,
|
||||
@@ -761,22 +779,30 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test,
|
||||
reg->doorbell_data = cpu_to_le32(msg->data);
|
||||
reg->doorbell_bar = cpu_to_le32(bar);
|
||||
|
||||
msg = &epf->db_msg[0].msg;
|
||||
ret = pci_epf_align_inbound_addr(epf, bar, ((u64)msg->address_hi << 32) | msg->address_lo,
|
||||
&epf_test->db_bar.phys_addr, &offset);
|
||||
if (db->bar == NO_BAR) {
|
||||
ret = pci_epf_align_inbound_addr(epf, bar,
|
||||
((u64)msg->address_hi << 32) |
|
||||
msg->address_lo,
|
||||
&epf_test->db_bar.phys_addr,
|
||||
&offset);
|
||||
|
||||
if (ret)
|
||||
goto err_free_irq;
|
||||
if (ret)
|
||||
goto err_free_irq;
|
||||
}
|
||||
|
||||
reg->doorbell_offset = cpu_to_le32(offset);
|
||||
|
||||
epf_test->db_bar.barno = bar;
|
||||
epf_test->db_bar.size = epf->bar[bar].size;
|
||||
epf_test->db_bar.flags = epf->bar[bar].flags;
|
||||
if (db->bar == NO_BAR) {
|
||||
epf_test->db_bar.barno = bar;
|
||||
epf_test->db_bar.size = epf->bar[bar].size;
|
||||
epf_test->db_bar.flags = epf->bar[bar].flags;
|
||||
|
||||
ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf_test->db_bar);
|
||||
if (ret)
|
||||
goto err_free_irq;
|
||||
ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf_test->db_bar);
|
||||
if (ret)
|
||||
goto err_free_irq;
|
||||
|
||||
epf_test->db_bar_programmed = true;
|
||||
}
|
||||
|
||||
status |= STATUS_DOORBELL_ENABLE_SUCCESS;
|
||||
reg->status = cpu_to_le32(status);
|
||||
@@ -806,17 +832,23 @@ static void pci_epf_test_disable_doorbell(struct pci_epf_test *epf_test,
|
||||
free_irq(epf->db_msg[0].virq, epf_test);
|
||||
pci_epf_test_doorbell_cleanup(epf_test);
|
||||
|
||||
/*
|
||||
* The doorbell feature temporarily overrides the inbound translation
|
||||
* to point to the address stored in epf_test->db_bar.phys_addr, i.e.,
|
||||
* it calls set_bar() twice without ever calling clear_bar(), as
|
||||
* calling clear_bar() would clear the BAR's PCI address assigned by
|
||||
* the host. Thus, when disabling the doorbell, restore the inbound
|
||||
* translation to point to the memory allocated for the BAR.
|
||||
*/
|
||||
ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf->bar[bar]);
|
||||
if (ret)
|
||||
goto set_status_err;
|
||||
if (epf_test->db_bar_programmed) {
|
||||
/*
|
||||
* The doorbell feature temporarily overrides the inbound
|
||||
* translation to point to the address stored in
|
||||
* epf_test->db_bar.phys_addr, i.e., it calls set_bar()
|
||||
* twice without ever calling clear_bar(), as calling
|
||||
* clear_bar() would clear the BAR's PCI address assigned
|
||||
* by the host. Thus, when disabling the doorbell, restore
|
||||
* the inbound translation to point to the memory allocated
|
||||
* for the BAR.
|
||||
*/
|
||||
ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf->bar[bar]);
|
||||
if (ret)
|
||||
goto set_status_err;
|
||||
|
||||
epf_test->db_bar_programmed = false;
|
||||
}
|
||||
|
||||
status |= STATUS_DOORBELL_DISABLE_SUCCESS;
|
||||
reg->status = cpu_to_le32(status);
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
@@ -66,9 +67,11 @@ static struct workqueue_struct *kpcintb_workqueue;
|
||||
#define NTB_MW_OFFSET 2
|
||||
#define DB_COUNT_MASK GENMASK(15, 0)
|
||||
#define MSIX_ENABLE BIT(16)
|
||||
#define MAX_DB_COUNT 32
|
||||
#define MAX_MW 4
|
||||
|
||||
/* Limit per-work execution to avoid monopolizing kworker on doorbell storms. */
|
||||
#define VNTB_PEER_DB_WORK_BUDGET 5
|
||||
|
||||
enum epf_ntb_bar {
|
||||
BAR_CONFIG,
|
||||
BAR_DB,
|
||||
@@ -79,6 +82,15 @@ enum epf_ntb_bar {
|
||||
VNTB_BAR_NUM,
|
||||
};
|
||||
|
||||
enum epf_irq_slot {
|
||||
EPF_IRQ_LINK = 0,
|
||||
EPF_IRQ_RESERVED_DB, /* Historically skipped slot */
|
||||
EPF_IRQ_DB_START,
|
||||
};
|
||||
|
||||
#define MIN_DB_COUNT (EPF_IRQ_DB_START + 1)
|
||||
#define MAX_DB_COUNT 32
|
||||
|
||||
/*
|
||||
* +--------------------------------------------------+ Base
|
||||
* | |
|
||||
@@ -129,11 +141,18 @@ struct epf_ntb {
|
||||
u32 spad_count;
|
||||
u64 mws_size[MAX_MW];
|
||||
atomic64_t db;
|
||||
atomic64_t peer_db_pending;
|
||||
struct work_struct peer_db_work;
|
||||
u32 vbus_number;
|
||||
u16 vntb_pid;
|
||||
u16 vntb_vid;
|
||||
|
||||
bool linkup;
|
||||
|
||||
/*
|
||||
* True when doorbells are interrupt-driven (MSI or embedded), false
|
||||
* when polled.
|
||||
*/
|
||||
bool msi_doorbell;
|
||||
u32 spad_size;
|
||||
|
||||
@@ -261,10 +280,11 @@ static void epf_ntb_cmd_handler(struct work_struct *work)
|
||||
|
||||
ntb = container_of(work, struct epf_ntb, cmd_handler.work);
|
||||
|
||||
for (i = 1; i < ntb->db_count && !ntb->msi_doorbell; i++) {
|
||||
for (i = EPF_IRQ_DB_START; i < ntb->db_count && !ntb->msi_doorbell;
|
||||
i++) {
|
||||
if (ntb->epf_db[i]) {
|
||||
atomic64_or(1 << (i - 1), &ntb->db);
|
||||
ntb_db_event(&ntb->ntb, i);
|
||||
atomic64_or(1 << (i - EPF_IRQ_DB_START), &ntb->db);
|
||||
ntb_db_event(&ntb->ntb, i - EPF_IRQ_DB_START);
|
||||
ntb->epf_db[i] = 0;
|
||||
}
|
||||
}
|
||||
@@ -330,10 +350,10 @@ static irqreturn_t epf_ntb_doorbell_handler(int irq, void *data)
|
||||
struct epf_ntb *ntb = data;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < ntb->db_count; i++)
|
||||
for (i = EPF_IRQ_DB_START; i < ntb->db_count; i++)
|
||||
if (irq == ntb->epf->db_msg[i].virq) {
|
||||
atomic64_or(1 << (i - 1), &ntb->db);
|
||||
ntb_db_event(&ntb->ntb, i);
|
||||
atomic64_or(1 << (i - EPF_IRQ_DB_START), &ntb->db);
|
||||
ntb_db_event(&ntb->ntb, i - EPF_IRQ_DB_START);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
@@ -483,7 +503,6 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb)
|
||||
{
|
||||
const struct pci_epc_features *epc_features;
|
||||
struct device *dev;
|
||||
u32 db_count;
|
||||
int ret;
|
||||
|
||||
dev = &ntb->epf->dev;
|
||||
@@ -495,14 +514,12 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
db_count = ntb->db_count;
|
||||
if (db_count > MAX_DB_COUNT) {
|
||||
dev_err(dev, "DB count cannot be more than %d\n", MAX_DB_COUNT);
|
||||
if (ntb->db_count < MIN_DB_COUNT || ntb->db_count > MAX_DB_COUNT) {
|
||||
dev_err(dev, "DB count %d out of range (%d - %d)\n",
|
||||
ntb->db_count, MIN_DB_COUNT, MAX_DB_COUNT);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ntb->db_count = db_count;
|
||||
|
||||
if (epc_features->msi_capable) {
|
||||
ret = pci_epc_set_msi(ntb->epf->epc,
|
||||
ntb->epf->func_no,
|
||||
@@ -517,6 +534,17 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool epf_ntb_db_irq_is_duplicated(const struct pci_epf *epf, unsigned int idx)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < idx; i++)
|
||||
if (epf->db_msg[i].virq == epf->db_msg[idx].virq)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
|
||||
struct pci_epf_bar *db_bar,
|
||||
const struct pci_epc_features *epc_features,
|
||||
@@ -533,9 +561,24 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* The doorbell target may already be exposed by a platform-owned fixed
|
||||
* BAR. In that case, we must reuse it and the requested db_bar must
|
||||
* match.
|
||||
*/
|
||||
if (epf->db_msg[0].bar != NO_BAR && epf->db_msg[0].bar != barno) {
|
||||
ret = -EINVAL;
|
||||
goto err_free_doorbell;
|
||||
}
|
||||
|
||||
for (req = 0; req < ntb->db_count; req++) {
|
||||
/* Avoid requesting duplicate handlers */
|
||||
if (epf_ntb_db_irq_is_duplicated(epf, req))
|
||||
continue;
|
||||
|
||||
ret = request_irq(epf->db_msg[req].virq, epf_ntb_doorbell_handler,
|
||||
0, "pci_epf_vntb_db", ntb);
|
||||
epf->db_msg[req].irq_flags, "pci_epf_vntb_db",
|
||||
ntb);
|
||||
|
||||
if (ret) {
|
||||
dev_err(&epf->dev,
|
||||
@@ -545,6 +588,22 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
|
||||
}
|
||||
}
|
||||
|
||||
if (epf->db_msg[0].bar != NO_BAR) {
|
||||
for (i = 0; i < ntb->db_count; i++) {
|
||||
msg = &epf->db_msg[i].msg;
|
||||
|
||||
if (epf->db_msg[i].bar != barno) {
|
||||
ret = -EINVAL;
|
||||
goto err_free_irq;
|
||||
}
|
||||
|
||||
ntb->reg->db_data[i] = msg->data;
|
||||
ntb->reg->db_offset[i] = epf->db_msg[i].offset;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Program inbound mapping for the doorbell */
|
||||
msg = &epf->db_msg[0].msg;
|
||||
|
||||
high = 0;
|
||||
@@ -591,6 +650,7 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
|
||||
ntb->reg->db_offset[i] = offset;
|
||||
}
|
||||
|
||||
out:
|
||||
ntb->reg->db_entry_size = 0;
|
||||
|
||||
ntb->msi_doorbell = true;
|
||||
@@ -598,9 +658,13 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb,
|
||||
return 0;
|
||||
|
||||
err_free_irq:
|
||||
for (req--; req >= 0; req--)
|
||||
for (req--; req >= 0; req--) {
|
||||
if (epf_ntb_db_irq_is_duplicated(epf, req))
|
||||
continue;
|
||||
free_irq(epf->db_msg[req].virq, ntb);
|
||||
}
|
||||
|
||||
err_free_doorbell:
|
||||
pci_epf_free_doorbell(ntb->epf);
|
||||
return ret;
|
||||
}
|
||||
@@ -666,8 +730,11 @@ static void epf_ntb_db_bar_clear(struct epf_ntb *ntb)
|
||||
if (ntb->msi_doorbell) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ntb->db_count; i++)
|
||||
for (i = 0; i < ntb->db_count; i++) {
|
||||
if (epf_ntb_db_irq_is_duplicated(ntb->epf, i))
|
||||
continue;
|
||||
free_irq(ntb->epf->db_msg[i].virq, ntb);
|
||||
}
|
||||
}
|
||||
|
||||
if (ntb->epf->db_msg)
|
||||
@@ -920,6 +987,9 @@ static int epf_ntb_epc_init(struct epf_ntb *ntb)
|
||||
INIT_DELAYED_WORK(&ntb->cmd_handler, epf_ntb_cmd_handler);
|
||||
queue_work(kpcintb_workqueue, &ntb->cmd_handler.work);
|
||||
|
||||
atomic64_set(&ntb->peer_db_pending, 0);
|
||||
enable_work(&ntb->peer_db_work);
|
||||
|
||||
return 0;
|
||||
|
||||
err_write_header:
|
||||
@@ -943,11 +1013,18 @@ static int epf_ntb_epc_init(struct epf_ntb *ntb)
|
||||
static void epf_ntb_epc_cleanup(struct epf_ntb *ntb)
|
||||
{
|
||||
disable_delayed_work_sync(&ntb->cmd_handler);
|
||||
disable_work_sync(&ntb->peer_db_work);
|
||||
atomic64_set(&ntb->peer_db_pending, 0);
|
||||
epf_ntb_mw_bar_clear(ntb, ntb->num_mws);
|
||||
epf_ntb_db_bar_clear(ntb);
|
||||
epf_ntb_config_sspad_bar_clear(ntb);
|
||||
}
|
||||
|
||||
static bool epf_ntb_epc_attached(struct epf_ntb *ntb)
|
||||
{
|
||||
return ntb->epf->epc || ntb->epf->sec_epc;
|
||||
}
|
||||
|
||||
#define EPF_NTB_R(_name) \
|
||||
static ssize_t epf_ntb_##_name##_show(struct config_item *item, \
|
||||
char *page) \
|
||||
@@ -967,6 +1044,9 @@ static ssize_t epf_ntb_##_name##_store(struct config_item *item, \
|
||||
u32 val; \
|
||||
int ret; \
|
||||
\
|
||||
if (epf_ntb_epc_attached(ntb)) \
|
||||
return -EOPNOTSUPP; \
|
||||
\
|
||||
ret = kstrtou32(page, 0, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
@@ -1009,6 +1089,9 @@ static ssize_t epf_ntb_##_name##_store(struct config_item *item, \
|
||||
u64 val; \
|
||||
int ret; \
|
||||
\
|
||||
if (epf_ntb_epc_attached(ntb)) \
|
||||
return -EOPNOTSUPP; \
|
||||
\
|
||||
ret = kstrtou64(page, 0, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
@@ -1047,6 +1130,9 @@ static ssize_t epf_ntb_##_name##_store(struct config_item *item, \
|
||||
int val; \
|
||||
int ret; \
|
||||
\
|
||||
if (epf_ntb_epc_attached(ntb)) \
|
||||
return -EOPNOTSUPP; \
|
||||
\
|
||||
ret = kstrtoint(page, 0, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
@@ -1067,6 +1153,9 @@ static ssize_t epf_ntb_num_mws_store(struct config_item *item,
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (epf_ntb_epc_attached(ntb))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = kstrtou32(page, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -1079,10 +1168,32 @@ static ssize_t epf_ntb_num_mws_store(struct config_item *item,
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t epf_ntb_db_count_store(struct config_item *item,
|
||||
const char *page, size_t len)
|
||||
{
|
||||
struct config_group *group = to_config_group(item);
|
||||
struct epf_ntb *ntb = to_epf_ntb(group);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (epf_ntb_epc_attached(ntb))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = kstrtou32(page, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (val < MIN_DB_COUNT || val > MAX_DB_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
WRITE_ONCE(ntb->db_count, val);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
EPF_NTB_R(spad_count)
|
||||
EPF_NTB_W(spad_count)
|
||||
EPF_NTB_R(db_count)
|
||||
EPF_NTB_W(db_count)
|
||||
EPF_NTB_R(num_mws)
|
||||
EPF_NTB_R(vbus_number)
|
||||
EPF_NTB_W(vbus_number)
|
||||
@@ -1251,9 +1362,48 @@ static int vntb_epf_peer_mw_count(struct ntb_dev *ntb)
|
||||
return ntb_ndev(ntb)->num_mws;
|
||||
}
|
||||
|
||||
static int vntb_epf_db_vector_count(struct ntb_dev *ntb)
|
||||
{
|
||||
struct epf_ntb *ndev = ntb_ndev(ntb);
|
||||
u32 db_count = READ_ONCE(ndev->db_count);
|
||||
|
||||
/*
|
||||
* db_count is the total number of doorbell slots exposed to
|
||||
* the peer, including:
|
||||
* - slot #0 reserved for link events
|
||||
* - slot #1 historically unused (kept for protocol compatibility)
|
||||
*
|
||||
* Report only usable per-vector doorbell interrupts.
|
||||
*/
|
||||
if (db_count < MIN_DB_COUNT || db_count > MAX_DB_COUNT)
|
||||
return 0;
|
||||
|
||||
return db_count - EPF_IRQ_DB_START;
|
||||
}
|
||||
|
||||
static u64 vntb_epf_db_valid_mask(struct ntb_dev *ntb)
|
||||
{
|
||||
return BIT_ULL(ntb_ndev(ntb)->db_count) - 1;
|
||||
int nr_vec = vntb_epf_db_vector_count(ntb);
|
||||
|
||||
if (!nr_vec)
|
||||
return 0;
|
||||
|
||||
return GENMASK_ULL(nr_vec - 1, 0);
|
||||
}
|
||||
|
||||
static u64 vntb_epf_db_vector_mask(struct ntb_dev *ntb, int db_vector)
|
||||
{
|
||||
int nr_vec;
|
||||
|
||||
/*
|
||||
* Doorbell vectors are numbered [0 .. nr_vec - 1], where nr_vec
|
||||
* excludes the two reserved slots described above.
|
||||
*/
|
||||
nr_vec = vntb_epf_db_vector_count(ntb);
|
||||
if (db_vector < 0 || db_vector >= nr_vec)
|
||||
return 0;
|
||||
|
||||
return BIT_ULL(db_vector);
|
||||
}
|
||||
|
||||
static int vntb_epf_db_set_mask(struct ntb_dev *ntb, u64 db_bits)
|
||||
@@ -1357,22 +1507,84 @@ static int vntb_epf_peer_spad_write(struct ntb_dev *ndev, int pidx, int idx, u32
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vntb_epf_peer_db_set(struct ntb_dev *ndev, u64 db_bits)
|
||||
static void vntb_epf_peer_db_work(struct work_struct *work)
|
||||
{
|
||||
u32 interrupt_num = ffs(db_bits) + 1;
|
||||
struct epf_ntb *ntb = ntb_ndev(ndev);
|
||||
struct epf_ntb *ntb = container_of(work, struct epf_ntb, peer_db_work);
|
||||
struct pci_epf *epf = ntb->epf;
|
||||
unsigned int budget = VNTB_PEER_DB_WORK_BUDGET;
|
||||
u8 func_no, vfunc_no;
|
||||
unsigned int db_bit;
|
||||
u32 interrupt_num;
|
||||
u64 db_bits;
|
||||
int ret;
|
||||
|
||||
func_no = ntb->epf->func_no;
|
||||
vfunc_no = ntb->epf->vfunc_no;
|
||||
if (!epf || !epf->epc)
|
||||
return;
|
||||
|
||||
ret = pci_epc_raise_irq(ntb->epf->epc, func_no, vfunc_no,
|
||||
PCI_IRQ_MSI, interrupt_num + 1);
|
||||
if (ret)
|
||||
dev_err(&ntb->ntb.dev, "Failed to raise IRQ\n");
|
||||
func_no = epf->func_no;
|
||||
vfunc_no = epf->vfunc_no;
|
||||
|
||||
return ret;
|
||||
/*
|
||||
* Drain doorbells from peer_db_pending in snapshots (atomic64_xchg()).
|
||||
* Limit the number of snapshots handled per run so we don't monopolize
|
||||
* the workqueue under a doorbell storm.
|
||||
*/
|
||||
while (budget--) {
|
||||
db_bits = atomic64_xchg(&ntb->peer_db_pending, 0);
|
||||
if (!db_bits)
|
||||
return;
|
||||
|
||||
while (db_bits) {
|
||||
/*
|
||||
* pci_epc_raise_irq() for MSI expects a 1-based
|
||||
* interrupt number. The first usable doorbell starts
|
||||
* at EPF_IRQ_DB_START in the legacy slot layout.
|
||||
*
|
||||
* Legacy mapping (kept for compatibility):
|
||||
*
|
||||
* MSI #1 : link event (reserved)
|
||||
* MSI #2 : unused (historical offset)
|
||||
* MSI #3 : doorbell bit 0 (DB#0)
|
||||
* MSI #4 : doorbell bit 1 (DB#1)
|
||||
* ...
|
||||
*
|
||||
* Do not change this mapping to avoid breaking
|
||||
* interoperability with older peers.
|
||||
*/
|
||||
db_bit = __ffs64(db_bits);
|
||||
interrupt_num = db_bit + EPF_IRQ_DB_START + 1;
|
||||
db_bits &= ~BIT_ULL(db_bit);
|
||||
|
||||
ret = pci_epc_raise_irq(epf->epc, func_no, vfunc_no,
|
||||
PCI_IRQ_MSI, interrupt_num);
|
||||
if (ret)
|
||||
dev_err(&ntb->ntb.dev,
|
||||
"Failed to raise IRQ for interrupt_num %u: %d\n",
|
||||
interrupt_num, ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (atomic64_read(&ntb->peer_db_pending))
|
||||
queue_work(kpcintb_workqueue, &ntb->peer_db_work);
|
||||
}
|
||||
|
||||
static int vntb_epf_peer_db_set(struct ntb_dev *ndev, u64 db_bits)
|
||||
{
|
||||
struct epf_ntb *ntb = ntb_ndev(ndev);
|
||||
|
||||
db_bits &= vntb_epf_db_valid_mask(ndev);
|
||||
if (!db_bits)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* .peer_db_set() may be called from atomic context. pci_epc_raise_irq()
|
||||
* can sleep (it takes epc->lock), so defer MSI raising to process
|
||||
* context. Doorbell requests are coalesced in peer_db_pending.
|
||||
*/
|
||||
atomic64_or(db_bits, &ntb->peer_db_pending);
|
||||
queue_work(kpcintb_workqueue, &ntb->peer_db_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 vntb_epf_db_read(struct ntb_dev *ndev)
|
||||
@@ -1441,6 +1653,8 @@ static const struct ntb_dev_ops vntb_epf_ops = {
|
||||
.spad_count = vntb_epf_spad_count,
|
||||
.peer_mw_count = vntb_epf_peer_mw_count,
|
||||
.db_valid_mask = vntb_epf_db_valid_mask,
|
||||
.db_vector_count = vntb_epf_db_vector_count,
|
||||
.db_vector_mask = vntb_epf_db_vector_mask,
|
||||
.db_set_mask = vntb_epf_db_set_mask,
|
||||
.mw_set_trans = vntb_epf_mw_set_trans,
|
||||
.mw_clear_trans = vntb_epf_mw_clear_trans,
|
||||
@@ -1619,6 +1833,10 @@ static int epf_ntb_probe(struct pci_epf *epf,
|
||||
ntb->epf = epf;
|
||||
ntb->vbus_number = 0xff;
|
||||
|
||||
INIT_WORK(&ntb->peer_db_work, vntb_epf_peer_db_work);
|
||||
disable_work(&ntb->peer_db_work);
|
||||
atomic64_set(&ntb->peer_db_pending, 0);
|
||||
|
||||
/* Initially, no bar is assigned */
|
||||
for (i = 0; i < VNTB_BAR_NUM; i++)
|
||||
ntb->epf_ntb_bar[i] = NO_BAR;
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
* Author: Frank Li <Frank.Li@nxp.com>
|
||||
*/
|
||||
|
||||
#include <linux/align.h>
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/msi.h>
|
||||
@@ -35,23 +38,116 @@ static void pci_epf_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
|
||||
pci_epc_put(epc);
|
||||
}
|
||||
|
||||
int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
|
||||
static int pci_epf_alloc_doorbell_embedded(struct pci_epf *epf, u16 num_db)
|
||||
{
|
||||
const struct pci_epc_aux_resource *doorbell = NULL;
|
||||
struct pci_epf_doorbell_msg *msg;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct device *dev = &epf->dev;
|
||||
struct irq_domain *domain;
|
||||
void *msg;
|
||||
int ret;
|
||||
int i;
|
||||
size_t map_size = 0, off = 0;
|
||||
dma_addr_t iova_base = 0;
|
||||
phys_addr_t phys_base;
|
||||
int count, ret, i;
|
||||
u64 addr;
|
||||
|
||||
/* TODO: Multi-EPF support */
|
||||
if (list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list) != epf) {
|
||||
dev_err(dev, "MSI doorbell doesn't support multiple EPF\n");
|
||||
count = pci_epc_get_aux_resources_count(epc, epf->func_no,
|
||||
epf->vfunc_no);
|
||||
if (count < 0)
|
||||
return count;
|
||||
if (!count)
|
||||
return -ENODEV;
|
||||
|
||||
struct pci_epc_aux_resource *res __free(kfree) =
|
||||
kcalloc(count, sizeof(*res), GFP_KERNEL);
|
||||
if (!res)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = pci_epc_get_aux_resources(epc, epf->func_no, epf->vfunc_no,
|
||||
res, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* TODO: Support multiple DOORBELL_MMIO resources per EPC. */
|
||||
for (i = 0; i < count; i++) {
|
||||
if (res[i].type != PCI_EPC_AUX_DOORBELL_MMIO)
|
||||
continue;
|
||||
|
||||
doorbell = &res[i];
|
||||
break;
|
||||
}
|
||||
if (!doorbell)
|
||||
return -ENODEV;
|
||||
addr = doorbell->phys_addr;
|
||||
if (!IS_ALIGNED(addr, sizeof(u32)))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Reuse the pre-exposed BAR window if available. Otherwise map the MMIO
|
||||
* doorbell resource here. Any required IOMMU mapping is handled
|
||||
* internally, matching the MSI doorbell semantics.
|
||||
*/
|
||||
if (doorbell->bar == NO_BAR) {
|
||||
phys_base = addr & PAGE_MASK;
|
||||
off = addr - phys_base;
|
||||
map_size = PAGE_ALIGN(off + sizeof(u32));
|
||||
|
||||
iova_base = dma_map_resource(epc->dev.parent, phys_base,
|
||||
map_size, DMA_FROM_DEVICE, 0);
|
||||
if (dma_mapping_error(epc->dev.parent, iova_base))
|
||||
return -EIO;
|
||||
|
||||
addr = iova_base + off;
|
||||
}
|
||||
|
||||
if (epf->db_msg)
|
||||
return -EBUSY;
|
||||
msg = kcalloc(num_db, sizeof(*msg), GFP_KERNEL);
|
||||
if (!msg) {
|
||||
ret = -ENOMEM;
|
||||
goto err_unmap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Embedded doorbell backends (e.g. DesignWare eDMA interrupt emulation)
|
||||
* typically provide a single IRQ and do not offer per-doorbell
|
||||
* distinguishable address/data pairs. The EPC aux resource therefore
|
||||
* exposes one DOORBELL_MMIO entry (u.db_mmio.irq).
|
||||
*
|
||||
* Still, pci_epf_alloc_doorbell() allows requesting multiple doorbells.
|
||||
* For such backends we replicate the same address/data for each entry
|
||||
* and mark the IRQ as shared (IRQF_SHARED). Consumers must treat them
|
||||
* as equivalent "kick" doorbells.
|
||||
*/
|
||||
for (i = 0; i < num_db; i++)
|
||||
msg[i] = (struct pci_epf_doorbell_msg) {
|
||||
.msg.address_lo = (u32)addr,
|
||||
.msg.address_hi = (u32)(addr >> 32),
|
||||
.msg.data = doorbell->u.db_mmio.data,
|
||||
.virq = doorbell->u.db_mmio.irq,
|
||||
.irq_flags = IRQF_SHARED,
|
||||
.type = PCI_EPF_DOORBELL_EMBEDDED,
|
||||
.bar = doorbell->bar,
|
||||
.offset = (doorbell->bar == NO_BAR) ? 0 :
|
||||
doorbell->bar_offset,
|
||||
.iova_base = iova_base,
|
||||
.iova_size = map_size,
|
||||
};
|
||||
|
||||
epf->num_db = num_db;
|
||||
epf->db_msg = msg;
|
||||
return 0;
|
||||
|
||||
err_unmap:
|
||||
if (map_size)
|
||||
dma_unmap_resource(epc->dev.parent, iova_base, map_size,
|
||||
DMA_FROM_DEVICE, 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pci_epf_alloc_doorbell_msi(struct pci_epf *epf, u16 num_db)
|
||||
{
|
||||
struct pci_epf_doorbell_msg *msg;
|
||||
struct device *dev = &epf->dev;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct irq_domain *domain;
|
||||
int ret, i;
|
||||
|
||||
domain = of_msi_map_get_device_domain(epc->dev.parent, 0,
|
||||
DOMAIN_BUS_PLATFORM_MSI);
|
||||
@@ -74,6 +170,12 @@ int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < num_db; i++)
|
||||
msg[i] = (struct pci_epf_doorbell_msg) {
|
||||
.type = PCI_EPF_DOORBELL_MSI,
|
||||
.bar = NO_BAR,
|
||||
};
|
||||
|
||||
epf->num_db = num_db;
|
||||
epf->db_msg = msg;
|
||||
|
||||
@@ -90,13 +192,60 @@ int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
|
||||
for (i = 0; i < num_db; i++)
|
||||
epf->db_msg[i].virq = msi_get_virq(epc->dev.parent, i);
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db)
|
||||
{
|
||||
struct pci_epc *epc = epf->epc;
|
||||
struct device *dev = &epf->dev;
|
||||
int ret;
|
||||
|
||||
/* TODO: Multi-EPF support */
|
||||
if (list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list) != epf) {
|
||||
dev_err(dev, "Doorbell doesn't support multiple EPF\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (epf->db_msg)
|
||||
return -EBUSY;
|
||||
|
||||
ret = pci_epf_alloc_doorbell_msi(epf, num_db);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Fall back to embedded doorbell only when platform MSI is unavailable
|
||||
* for this EPC.
|
||||
*/
|
||||
if (ret != -ENODEV)
|
||||
return ret;
|
||||
|
||||
ret = pci_epf_alloc_doorbell_embedded(epf, num_db);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to allocate doorbell: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "Using embedded (DMA) doorbell fallback\n");
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epf_alloc_doorbell);
|
||||
|
||||
void pci_epf_free_doorbell(struct pci_epf *epf)
|
||||
{
|
||||
platform_device_msi_free_irqs_all(epf->epc->dev.parent);
|
||||
struct pci_epf_doorbell_msg *msg0;
|
||||
struct pci_epc *epc = epf->epc;
|
||||
|
||||
if (!epf->db_msg)
|
||||
return;
|
||||
|
||||
msg0 = &epf->db_msg[0];
|
||||
if (msg0->type == PCI_EPF_DOORBELL_MSI)
|
||||
platform_device_msi_free_irqs_all(epf->epc->dev.parent);
|
||||
else if (msg0->type == PCI_EPF_DOORBELL_EMBEDDED && msg0->iova_size)
|
||||
dma_unmap_resource(epc->dev.parent, msg0->iova_base,
|
||||
msg0->iova_size, DMA_FROM_DEVICE, 0);
|
||||
|
||||
kfree(epf->db_msg);
|
||||
epf->db_msg = NULL;
|
||||
|
||||
@@ -156,6 +156,86 @@ const struct pci_epc_features *pci_epc_get_features(struct pci_epc *epc,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_get_features);
|
||||
|
||||
/**
|
||||
* pci_epc_get_aux_resources_count() - get the number of EPC-provided auxiliary resources
|
||||
* @epc: EPC device
|
||||
* @func_no: function number
|
||||
* @vfunc_no: virtual function number
|
||||
*
|
||||
* Some EPC backends integrate auxiliary blocks (e.g. DMA engines) whose control
|
||||
* registers and/or descriptor memories can be exposed to the host by mapping
|
||||
* them into BAR space. This helper queries how many such resources the backend
|
||||
* provides.
|
||||
*
|
||||
* Return: the number of available resources on success, -EOPNOTSUPP if the
|
||||
* backend does not support auxiliary resource queries, or another -errno on
|
||||
* failure.
|
||||
*/
|
||||
int pci_epc_get_aux_resources_count(struct pci_epc *epc, u8 func_no,
|
||||
u8 vfunc_no)
|
||||
{
|
||||
int count;
|
||||
|
||||
if (!epc || !epc->ops)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pci_epc_function_is_valid(epc, func_no, vfunc_no))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->get_aux_resources_count)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
mutex_lock(&epc->lock);
|
||||
count = epc->ops->get_aux_resources_count(epc, func_no,
|
||||
vfunc_no);
|
||||
mutex_unlock(&epc->lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_get_aux_resources_count);
|
||||
|
||||
/**
|
||||
* pci_epc_get_aux_resources() - query EPC-provided auxiliary resources
|
||||
* @epc: EPC device
|
||||
* @func_no: function number
|
||||
* @vfunc_no: virtual function number
|
||||
* @resources: output array
|
||||
* @num_resources: size of @resources array in entries
|
||||
*
|
||||
* Some EPC backends integrate auxiliary blocks (e.g. DMA engines) whose control
|
||||
* registers and/or descriptor memories can be exposed to the host by mapping
|
||||
* them into BAR space. This helper queries the backend for such resources.
|
||||
*
|
||||
* Return: 0 on success, -EOPNOTSUPP if the backend does not support auxiliary
|
||||
* resource queries, or another -errno on failure.
|
||||
*/
|
||||
int pci_epc_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
|
||||
struct pci_epc_aux_resource *resources,
|
||||
int num_resources)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!resources || num_resources <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc || !epc->ops)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pci_epc_function_is_valid(epc, func_no, vfunc_no))
|
||||
return -EINVAL;
|
||||
|
||||
if (!epc->ops->get_aux_resources)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
mutex_lock(&epc->lock);
|
||||
ret = epc->ops->get_aux_resources(epc, func_no, vfunc_no, resources,
|
||||
num_resources);
|
||||
mutex_unlock(&epc->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_epc_get_aux_resources);
|
||||
|
||||
/**
|
||||
* pci_epc_stop() - stop the PCI link
|
||||
* @epc: the link of the EPC device that has to be stopped
|
||||
|
||||
@@ -170,8 +170,7 @@ void pci_ide_init(struct pci_dev *pdev)
|
||||
pci_read_config_dword(pdev, pos + PCI_IDE_SEL_CTL, &val);
|
||||
if (val & PCI_IDE_SEL_CTL_EN)
|
||||
continue;
|
||||
val &= ~PCI_IDE_SEL_CTL_ID;
|
||||
val |= FIELD_PREP(PCI_IDE_SEL_CTL_ID, PCI_IDE_RESERVED_STREAM_ID);
|
||||
FIELD_MODIFY(PCI_IDE_SEL_CTL_ID, &val, PCI_IDE_RESERVED_STREAM_ID);
|
||||
pci_write_config_dword(pdev, pos + PCI_IDE_SEL_CTL, val);
|
||||
}
|
||||
|
||||
@@ -182,8 +181,7 @@ void pci_ide_init(struct pci_dev *pdev)
|
||||
pci_read_config_dword(pdev, pos, &val);
|
||||
if (val & PCI_IDE_LINK_CTL_EN)
|
||||
continue;
|
||||
val &= ~PCI_IDE_LINK_CTL_ID;
|
||||
val |= FIELD_PREP(PCI_IDE_LINK_CTL_ID, PCI_IDE_RESERVED_STREAM_ID);
|
||||
FIELD_MODIFY(PCI_IDE_LINK_CTL_ID, &val, PCI_IDE_RESERVED_STREAM_ID);
|
||||
pci_write_config_dword(pdev, pos, val);
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ static void virtfn_remove_bus(struct pci_bus *physbus, struct pci_bus *virtbus)
|
||||
pci_remove_bus(virtbus);
|
||||
}
|
||||
|
||||
resource_size_t pci_iov_resource_size(struct pci_dev *dev, int resno)
|
||||
resource_size_t pci_iov_resource_size(const struct pci_dev *dev, int resno)
|
||||
{
|
||||
if (!dev->is_physfn)
|
||||
return 0;
|
||||
@@ -938,16 +938,21 @@ static void sriov_restore_vf_rebar_state(struct pci_dev *dev)
|
||||
return;
|
||||
|
||||
pci_read_config_dword(dev, pos + PCI_VF_REBAR_CTRL, &ctrl);
|
||||
if (PCI_POSSIBLE_ERROR(ctrl))
|
||||
return;
|
||||
|
||||
nbars = FIELD_GET(PCI_VF_REBAR_CTRL_NBAR_MASK, ctrl);
|
||||
|
||||
for (i = 0; i < nbars; i++, pos += 8) {
|
||||
int bar_idx, size;
|
||||
|
||||
pci_read_config_dword(dev, pos + PCI_VF_REBAR_CTRL, &ctrl);
|
||||
if (PCI_POSSIBLE_ERROR(ctrl))
|
||||
return;
|
||||
|
||||
bar_idx = FIELD_GET(PCI_VF_REBAR_CTRL_BAR_IDX, ctrl);
|
||||
size = pci_rebar_bytes_to_size(dev->sriov->barsz[bar_idx]);
|
||||
ctrl &= ~PCI_VF_REBAR_CTRL_BAR_SIZE;
|
||||
ctrl |= FIELD_PREP(PCI_VF_REBAR_CTRL_BAR_SIZE, size);
|
||||
FIELD_MODIFY(PCI_VF_REBAR_CTRL_BAR_SIZE, &ctrl, size);
|
||||
pci_write_config_dword(dev, pos + PCI_VF_REBAR_CTRL, ctrl);
|
||||
}
|
||||
}
|
||||
@@ -1084,7 +1089,7 @@ void pci_iov_update_resource(struct pci_dev *dev, int resno)
|
||||
}
|
||||
}
|
||||
|
||||
resource_size_t __weak pcibios_iov_resource_alignment(struct pci_dev *dev,
|
||||
resource_size_t __weak pcibios_iov_resource_alignment(const struct pci_dev *dev,
|
||||
int resno)
|
||||
{
|
||||
return pci_iov_resource_size(dev, resno);
|
||||
@@ -1100,7 +1105,8 @@ resource_size_t __weak pcibios_iov_resource_alignment(struct pci_dev *dev,
|
||||
* the VF BAR size multiplied by the number of VFs. The alignment
|
||||
* is just the VF BAR size.
|
||||
*/
|
||||
resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno)
|
||||
resource_size_t pci_sriov_resource_alignment(const struct pci_dev *dev,
|
||||
int resno)
|
||||
{
|
||||
return pcibios_iov_resource_alignment(dev, resno);
|
||||
}
|
||||
|
||||
@@ -201,8 +201,7 @@ static inline void pci_write_msg_msi(struct pci_dev *dev, struct msi_desc *desc,
|
||||
u16 msgctl;
|
||||
|
||||
pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &msgctl);
|
||||
msgctl &= ~PCI_MSI_FLAGS_QSIZE;
|
||||
msgctl |= FIELD_PREP(PCI_MSI_FLAGS_QSIZE, desc->pci.msi_attrib.multiple);
|
||||
FIELD_MODIFY(PCI_MSI_FLAGS_QSIZE, &msgctl, desc->pci.msi_attrib.multiple);
|
||||
pci_write_config_word(dev, pos + PCI_MSI_FLAGS, msgctl);
|
||||
|
||||
pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO, msg->address_lo);
|
||||
@@ -532,9 +531,8 @@ void __pci_restore_msi_state(struct pci_dev *dev)
|
||||
|
||||
pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &control);
|
||||
pci_msi_update_mask(entry, 0, 0);
|
||||
control &= ~PCI_MSI_FLAGS_QSIZE;
|
||||
control |= PCI_MSI_FLAGS_ENABLE |
|
||||
FIELD_PREP(PCI_MSI_FLAGS_QSIZE, entry->pci.msi_attrib.multiple);
|
||||
FIELD_MODIFY(PCI_MSI_FLAGS_QSIZE, &control, entry->pci.msi_attrib.multiple);
|
||||
control |= PCI_MSI_FLAGS_ENABLE;
|
||||
pci_write_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, control);
|
||||
}
|
||||
|
||||
@@ -968,8 +966,7 @@ int pci_msix_write_tph_tag(struct pci_dev *pdev, unsigned int index, u16 tag)
|
||||
if (!msi_desc || msi_desc->pci.msi_attrib.is_virtual)
|
||||
return -ENXIO;
|
||||
|
||||
msi_desc->pci.msix_ctrl &= ~PCI_MSIX_ENTRY_CTRL_ST;
|
||||
msi_desc->pci.msix_ctrl |= FIELD_PREP(PCI_MSIX_ENTRY_CTRL_ST, tag);
|
||||
FIELD_MODIFY(PCI_MSIX_ENTRY_CTRL_ST, &msi_desc->pci.msix_ctrl, tag);
|
||||
pci_msix_write_vector_ctrl(msi_desc, msi_desc->pci.msix_ctrl);
|
||||
/* Flush the write */
|
||||
readl(pci_msix_desc_addr(msi_desc));
|
||||
|
||||
@@ -262,6 +262,9 @@ int pcim_p2pdma_init(struct pci_dev *pdev)
|
||||
struct pci_p2pdma *p2p;
|
||||
int i, ret;
|
||||
|
||||
if (pdev->non_mappable_bars)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
p2p = rcu_dereference_protected(pdev->p2pdma, 1);
|
||||
if (p2p)
|
||||
return 0;
|
||||
@@ -318,7 +321,8 @@ struct p2pdma_provider *pcim_p2pdma_provider(struct pci_dev *pdev, int bar)
|
||||
{
|
||||
struct pci_p2pdma *p2p;
|
||||
|
||||
if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM))
|
||||
if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM) ||
|
||||
pdev->non_mappable_bars)
|
||||
return NULL;
|
||||
|
||||
p2p = rcu_dereference_protected(pdev->p2pdma, 1);
|
||||
@@ -548,6 +552,16 @@ static const struct pci_p2pdma_whitelist_entry {
|
||||
{PCI_VENDOR_ID_INTEL, 0x2033, 0},
|
||||
{PCI_VENDOR_ID_INTEL, 0x2020, 0},
|
||||
{PCI_VENDOR_ID_INTEL, 0x09a2, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_DSA_SPR0, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_IAX_SPR0, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_DSA_GNRD, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_DSA_DMR, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_IAA_DMR, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_4XXX, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_401XX, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_402XX, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_420XX, 0},
|
||||
{PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_6XXX, 0},
|
||||
/* Google SoCs. */
|
||||
{PCI_VENDOR_ID_GOOGLE, PCI_ANY_ID, 0},
|
||||
{}
|
||||
|
||||
@@ -519,13 +519,6 @@ static void pci_device_remove(struct device *dev)
|
||||
/* Undo the runtime PM settings in local_pci_probe() */
|
||||
pm_runtime_put_sync(dev);
|
||||
|
||||
/*
|
||||
* If the device is still on, set the power state as "unknown",
|
||||
* since it might change by the next time we load the driver.
|
||||
*/
|
||||
if (pci_dev->current_state == PCI_D0)
|
||||
pci_dev->current_state = PCI_UNKNOWN;
|
||||
|
||||
/*
|
||||
* We would love to complain here if pci_dev->is_enabled is set, that
|
||||
* the driver should have called pci_disable_device(), but the
|
||||
@@ -900,7 +893,7 @@ static int pci_pm_suspend_noirq(struct device *dev)
|
||||
|
||||
if (!pm) {
|
||||
pci_save_state(pci_dev);
|
||||
goto Fixup;
|
||||
goto set_unknown;
|
||||
}
|
||||
|
||||
if (pm->suspend_noirq) {
|
||||
@@ -952,6 +945,7 @@ static int pci_pm_suspend_noirq(struct device *dev)
|
||||
goto Fixup;
|
||||
}
|
||||
|
||||
set_unknown:
|
||||
pci_pm_set_unknown_state(pci_dev);
|
||||
|
||||
/*
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
#define ARCH_PCI_DEV_GROUPS
|
||||
#endif
|
||||
|
||||
static int sysfs_initialized; /* = 0 */
|
||||
|
||||
/* show configuration fields */
|
||||
#define pci_config_attr(field, format_string) \
|
||||
static ssize_t \
|
||||
@@ -177,7 +175,7 @@ static ssize_t resource_show(struct device *dev, struct device_attribute *attr,
|
||||
max = PCI_BRIDGE_RESOURCES;
|
||||
|
||||
for (i = 0; i < max; i++) {
|
||||
struct resource *res = &pci_dev->resource[i];
|
||||
struct resource *res = pci_resource_n(pci_dev, i);
|
||||
struct resource zerores = {};
|
||||
|
||||
/* For backwards compatibility */
|
||||
@@ -674,11 +672,6 @@ static const struct attribute_group pcibus_group = {
|
||||
.attrs = pcibus_attrs,
|
||||
};
|
||||
|
||||
const struct attribute_group *pcibus_groups[] = {
|
||||
&pcibus_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static ssize_t boot_vga_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
@@ -689,7 +682,7 @@ static ssize_t boot_vga_show(struct device *dev, struct device_attribute *attr,
|
||||
return sysfs_emit(buf, "%u\n", (pdev == vga_dev));
|
||||
|
||||
return sysfs_emit(buf, "%u\n",
|
||||
!!(pdev->resource[PCI_ROM_RESOURCE].flags &
|
||||
!!(pci_resource_flags(pdev, PCI_ROM_RESOURCE) &
|
||||
IORESOURCE_ROM_SHADOW));
|
||||
}
|
||||
static DEVICE_ATTR_RO(boot_vga);
|
||||
@@ -877,19 +870,6 @@ static const struct attribute_group pci_dev_config_attr_group = {
|
||||
.bin_size = pci_dev_config_attr_bin_size,
|
||||
};
|
||||
|
||||
/*
|
||||
* llseek operation for mmappable PCI resources.
|
||||
* May be left unused if the arch doesn't provide them.
|
||||
*/
|
||||
static __maybe_unused loff_t
|
||||
pci_llseek_resource(struct file *filep,
|
||||
struct kobject *kobj __always_unused,
|
||||
const struct bin_attribute *attr,
|
||||
loff_t offset, int whence)
|
||||
{
|
||||
return fixed_size_llseek(filep, offset, whence, attr->size);
|
||||
}
|
||||
|
||||
#ifdef HAVE_PCI_LEGACY
|
||||
/**
|
||||
* pci_read_legacy_io - read byte(s) from legacy I/O port space
|
||||
@@ -981,91 +961,148 @@ static int pci_mmap_legacy_io(struct file *filp, struct kobject *kobj,
|
||||
return pci_mmap_legacy_page_range(bus, vma, pci_mmap_io);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_adjust_legacy_attr - adjustment of legacy file attributes
|
||||
* @b: bus to create files under
|
||||
* @mmap_type: I/O port or memory
|
||||
*
|
||||
* Stub implementation. Can be overridden by arch if necessary.
|
||||
*/
|
||||
void __weak pci_adjust_legacy_attr(struct pci_bus *b,
|
||||
enum pci_mmap_state mmap_type)
|
||||
bool __weak pci_legacy_has_sparse(struct pci_bus *bus,
|
||||
enum pci_mmap_state type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_create_legacy_files - create legacy I/O port and memory files
|
||||
* @b: bus to create files under
|
||||
*
|
||||
* Some platforms allow access to legacy I/O port and ISA memory space on
|
||||
* a per-bus basis. This routine creates the files and ties them into
|
||||
* their associated read, write and mmap files from pci-sysfs.c
|
||||
*
|
||||
* On error unwind, but don't propagate the error to the caller
|
||||
* as it is ok to set up the PCI bus without these files.
|
||||
*/
|
||||
void pci_create_legacy_files(struct pci_bus *b)
|
||||
static inline umode_t __pci_legacy_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
enum pci_mmap_state type,
|
||||
bool sparse)
|
||||
{
|
||||
int error;
|
||||
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
|
||||
|
||||
if (!sysfs_initialized)
|
||||
return;
|
||||
if (pci_legacy_has_sparse(bus, type) != sparse)
|
||||
return 0;
|
||||
|
||||
b->legacy_io = kzalloc_objs(struct bin_attribute, 2, GFP_ATOMIC);
|
||||
if (!b->legacy_io)
|
||||
goto kzalloc_err;
|
||||
|
||||
sysfs_bin_attr_init(b->legacy_io);
|
||||
b->legacy_io->attr.name = "legacy_io";
|
||||
b->legacy_io->size = 0xffff;
|
||||
b->legacy_io->attr.mode = 0600;
|
||||
b->legacy_io->read = pci_read_legacy_io;
|
||||
b->legacy_io->write = pci_write_legacy_io;
|
||||
/* See pci_create_attr() for motivation */
|
||||
b->legacy_io->llseek = pci_llseek_resource;
|
||||
b->legacy_io->mmap = pci_mmap_legacy_io;
|
||||
b->legacy_io->f_mapping = iomem_get_mapping;
|
||||
pci_adjust_legacy_attr(b, pci_mmap_io);
|
||||
error = device_create_bin_file(&b->dev, b->legacy_io);
|
||||
if (error)
|
||||
goto legacy_io_err;
|
||||
|
||||
/* Allocated above after the legacy_io struct */
|
||||
b->legacy_mem = b->legacy_io + 1;
|
||||
sysfs_bin_attr_init(b->legacy_mem);
|
||||
b->legacy_mem->attr.name = "legacy_mem";
|
||||
b->legacy_mem->size = 1024*1024;
|
||||
b->legacy_mem->attr.mode = 0600;
|
||||
b->legacy_mem->mmap = pci_mmap_legacy_mem;
|
||||
/* See pci_create_attr() for motivation */
|
||||
b->legacy_mem->llseek = pci_llseek_resource;
|
||||
b->legacy_mem->f_mapping = iomem_get_mapping;
|
||||
pci_adjust_legacy_attr(b, pci_mmap_mem);
|
||||
error = device_create_bin_file(&b->dev, b->legacy_mem);
|
||||
if (error)
|
||||
goto legacy_mem_err;
|
||||
|
||||
return;
|
||||
|
||||
legacy_mem_err:
|
||||
device_remove_bin_file(&b->dev, b->legacy_io);
|
||||
legacy_io_err:
|
||||
kfree(b->legacy_io);
|
||||
b->legacy_io = NULL;
|
||||
kzalloc_err:
|
||||
dev_warn(&b->dev, "could not create legacy I/O port and ISA memory resources in sysfs\n");
|
||||
return a->attr.mode;
|
||||
}
|
||||
|
||||
void pci_remove_legacy_files(struct pci_bus *b)
|
||||
static umode_t pci_legacy_io_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a, int n)
|
||||
{
|
||||
if (b->legacy_io) {
|
||||
device_remove_bin_file(&b->dev, b->legacy_io);
|
||||
device_remove_bin_file(&b->dev, b->legacy_mem);
|
||||
kfree(b->legacy_io); /* both are allocated here */
|
||||
}
|
||||
return __pci_legacy_is_visible(kobj, a, pci_mmap_io, false);
|
||||
}
|
||||
|
||||
static umode_t pci_legacy_io_sparse_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int n)
|
||||
{
|
||||
return __pci_legacy_is_visible(kobj, a, pci_mmap_io, true);
|
||||
}
|
||||
|
||||
static umode_t pci_legacy_mem_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a, int n)
|
||||
{
|
||||
return __pci_legacy_is_visible(kobj, a, pci_mmap_mem, false);
|
||||
}
|
||||
|
||||
static umode_t pci_legacy_mem_sparse_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int n)
|
||||
{
|
||||
return __pci_legacy_is_visible(kobj, a, pci_mmap_mem, true);
|
||||
}
|
||||
|
||||
static loff_t pci_llseek_resource_legacy(struct file *filep,
|
||||
struct kobject *kobj __always_unused,
|
||||
const struct bin_attribute *attr,
|
||||
loff_t offset, int whence)
|
||||
{
|
||||
return fixed_size_llseek(filep, offset, whence, attr->size);
|
||||
}
|
||||
|
||||
static const struct bin_attribute pci_legacy_io_attr = {
|
||||
.attr = { .name = "legacy_io", .mode = 0600 },
|
||||
.size = PCI_LEGACY_IO_SIZE,
|
||||
.read = pci_read_legacy_io,
|
||||
.write = pci_write_legacy_io,
|
||||
.mmap = pci_mmap_legacy_io,
|
||||
.llseek = pci_llseek_resource_legacy,
|
||||
.f_mapping = iomem_get_mapping,
|
||||
};
|
||||
|
||||
static const struct bin_attribute pci_legacy_io_sparse_attr = {
|
||||
.attr = { .name = "legacy_io_sparse", .mode = 0600 },
|
||||
.size = PCI_LEGACY_IO_SIZE << 5,
|
||||
.read = pci_read_legacy_io,
|
||||
.write = pci_write_legacy_io,
|
||||
.mmap = pci_mmap_legacy_io,
|
||||
.llseek = pci_llseek_resource_legacy,
|
||||
.f_mapping = iomem_get_mapping,
|
||||
};
|
||||
|
||||
static const struct bin_attribute pci_legacy_mem_attr = {
|
||||
.attr = { .name = "legacy_mem", .mode = 0600 },
|
||||
.size = PCI_LEGACY_MEM_SIZE,
|
||||
.mmap = pci_mmap_legacy_mem,
|
||||
.llseek = pci_llseek_resource_legacy,
|
||||
.f_mapping = iomem_get_mapping,
|
||||
};
|
||||
|
||||
static const struct bin_attribute pci_legacy_mem_sparse_attr = {
|
||||
.attr = { .name = "legacy_mem_sparse", .mode = 0600 },
|
||||
.size = PCI_LEGACY_MEM_SIZE << 5,
|
||||
.mmap = pci_mmap_legacy_mem,
|
||||
.llseek = pci_llseek_resource_legacy,
|
||||
.f_mapping = iomem_get_mapping,
|
||||
};
|
||||
|
||||
static const struct bin_attribute *const pci_legacy_io_attrs[] = {
|
||||
&pci_legacy_io_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct bin_attribute *const pci_legacy_io_sparse_attrs[] = {
|
||||
&pci_legacy_io_sparse_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct bin_attribute *const pci_legacy_mem_attrs[] = {
|
||||
&pci_legacy_mem_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct bin_attribute *const pci_legacy_mem_sparse_attrs[] = {
|
||||
&pci_legacy_mem_sparse_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group pci_legacy_io_group = {
|
||||
.bin_attrs = pci_legacy_io_attrs,
|
||||
.is_bin_visible = pci_legacy_io_is_visible,
|
||||
};
|
||||
|
||||
static const struct attribute_group pci_legacy_io_sparse_group = {
|
||||
.bin_attrs = pci_legacy_io_sparse_attrs,
|
||||
.is_bin_visible = pci_legacy_io_sparse_is_visible,
|
||||
};
|
||||
|
||||
static const struct attribute_group pci_legacy_mem_group = {
|
||||
.bin_attrs = pci_legacy_mem_attrs,
|
||||
.is_bin_visible = pci_legacy_mem_is_visible,
|
||||
};
|
||||
|
||||
static const struct attribute_group pci_legacy_mem_sparse_group = {
|
||||
.bin_attrs = pci_legacy_mem_sparse_attrs,
|
||||
.is_bin_visible = pci_legacy_mem_sparse_is_visible,
|
||||
};
|
||||
|
||||
#endif /* HAVE_PCI_LEGACY */
|
||||
|
||||
const struct attribute_group *pcibus_groups[] = {
|
||||
&pcibus_group,
|
||||
#ifdef HAVE_PCI_LEGACY
|
||||
&pci_legacy_io_group,
|
||||
&pci_legacy_io_sparse_group,
|
||||
&pci_legacy_mem_group,
|
||||
&pci_legacy_mem_sparse_group,
|
||||
#endif
|
||||
NULL,
|
||||
};
|
||||
|
||||
#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE)
|
||||
/**
|
||||
* pci_mmap_resource - map a PCI resource into user memory space
|
||||
@@ -1082,20 +1119,24 @@ static int pci_mmap_resource(struct kobject *kobj, const struct bin_attribute *a
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
int bar = (unsigned long)attr->private;
|
||||
enum pci_mmap_state mmap_type;
|
||||
struct resource *res = &pdev->resource[bar];
|
||||
int ret;
|
||||
|
||||
ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (res->flags & IORESOURCE_MEM && iomem_is_exclusive(res->start))
|
||||
if (!pci_resource_is_mem(pdev, bar) &&
|
||||
!(pci_resource_is_io(pdev, bar) && arch_can_pci_mmap_io()))
|
||||
return -EIO;
|
||||
|
||||
if (pci_resource_is_mem(pdev, bar) &&
|
||||
iomem_is_exclusive(pci_resource_start(pdev, bar)))
|
||||
return -EINVAL;
|
||||
|
||||
if (!pci_mmap_fits(pdev, bar, vma, PCI_MMAP_SYSFS))
|
||||
return -EINVAL;
|
||||
|
||||
mmap_type = res->flags & IORESOURCE_MEM ? pci_mmap_mem : pci_mmap_io;
|
||||
mmap_type = pci_resource_is_mem(pdev, bar) ? pci_mmap_mem : pci_mmap_io;
|
||||
|
||||
return pci_mmap_resource_range(pdev, bar, vma, mmap_type, write_combine);
|
||||
}
|
||||
@@ -1123,6 +1164,9 @@ static ssize_t pci_resource_io(struct file *filp, struct kobject *kobj,
|
||||
int bar = (unsigned long)attr->private;
|
||||
unsigned long port = off;
|
||||
|
||||
if (!pci_resource_is_io(pdev, bar))
|
||||
return -EIO;
|
||||
|
||||
port += pci_resource_start(pdev, bar);
|
||||
|
||||
if (port > pci_resource_end(pdev, bar))
|
||||
@@ -1157,14 +1201,14 @@ static ssize_t pci_resource_io(struct file *filp, struct kobject *kobj,
|
||||
#endif
|
||||
}
|
||||
|
||||
static ssize_t pci_read_resource_io(struct file *filp, struct kobject *kobj,
|
||||
static ssize_t pci_read_resource(struct file *filp, struct kobject *kobj,
|
||||
const struct bin_attribute *attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
return pci_resource_io(filp, kobj, attr, buf, off, count, false);
|
||||
}
|
||||
|
||||
static ssize_t pci_write_resource_io(struct file *filp, struct kobject *kobj,
|
||||
static ssize_t pci_write_resource(struct file *filp, struct kobject *kobj,
|
||||
const struct bin_attribute *attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
@@ -1177,127 +1221,192 @@ static ssize_t pci_write_resource_io(struct file *filp, struct kobject *kobj,
|
||||
return pci_resource_io(filp, kobj, attr, buf, off, count, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_remove_resource_files - cleanup resource files
|
||||
* @pdev: dev to cleanup
|
||||
*
|
||||
* If we created resource files for @pdev, remove them from sysfs and
|
||||
* free their resources.
|
||||
*/
|
||||
static void pci_remove_resource_files(struct pci_dev *pdev)
|
||||
static loff_t pci_llseek_resource(struct file *filep,
|
||||
struct kobject *kobj,
|
||||
const struct bin_attribute *attr,
|
||||
loff_t offset, int whence)
|
||||
{
|
||||
int i;
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
int bar = (unsigned long)attr->private;
|
||||
|
||||
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
|
||||
struct bin_attribute *res_attr;
|
||||
|
||||
res_attr = pdev->res_attr[i];
|
||||
if (res_attr) {
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, res_attr);
|
||||
kfree(res_attr);
|
||||
}
|
||||
|
||||
res_attr = pdev->res_attr_wc[i];
|
||||
if (res_attr) {
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, res_attr);
|
||||
kfree(res_attr);
|
||||
}
|
||||
}
|
||||
return fixed_size_llseek(filep, offset, whence,
|
||||
pci_resource_len(pdev, bar));
|
||||
}
|
||||
|
||||
static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine)
|
||||
{
|
||||
/* allocate attribute structure, piggyback attribute name */
|
||||
int name_len = write_combine ? 13 : 10;
|
||||
struct bin_attribute *res_attr;
|
||||
char *res_attr_name;
|
||||
int retval;
|
||||
/*
|
||||
* generic_file_llseek() consults f_mapping->host to determine
|
||||
* the file size. As iomem_inode knows nothing about the
|
||||
* attribute, it's not going to work, so override it as well.
|
||||
*/
|
||||
#if arch_can_pci_mmap_io()
|
||||
# define __PCI_RESOURCE_IO_MMAP_ATTRS \
|
||||
.f_mapping = iomem_get_mapping, \
|
||||
.llseek = pci_llseek_resource, \
|
||||
.mmap = pci_mmap_resource_uc,
|
||||
#else
|
||||
# define __PCI_RESOURCE_IO_MMAP_ATTRS
|
||||
#endif
|
||||
|
||||
res_attr = kzalloc(sizeof(*res_attr) + name_len, GFP_ATOMIC);
|
||||
if (!res_attr)
|
||||
return -ENOMEM;
|
||||
|
||||
res_attr_name = (char *)(res_attr + 1);
|
||||
|
||||
sysfs_bin_attr_init(res_attr);
|
||||
if (write_combine) {
|
||||
sprintf(res_attr_name, "resource%d_wc", num);
|
||||
res_attr->mmap = pci_mmap_resource_wc;
|
||||
} else {
|
||||
sprintf(res_attr_name, "resource%d", num);
|
||||
if (pci_resource_flags(pdev, num) & IORESOURCE_IO) {
|
||||
res_attr->read = pci_read_resource_io;
|
||||
res_attr->write = pci_write_resource_io;
|
||||
if (arch_can_pci_mmap_io())
|
||||
res_attr->mmap = pci_mmap_resource_uc;
|
||||
} else {
|
||||
res_attr->mmap = pci_mmap_resource_uc;
|
||||
}
|
||||
}
|
||||
if (res_attr->mmap) {
|
||||
res_attr->f_mapping = iomem_get_mapping;
|
||||
/*
|
||||
* generic_file_llseek() consults f_mapping->host to determine
|
||||
* the file size. As iomem_inode knows nothing about the
|
||||
* attribute, it's not going to work, so override it as well.
|
||||
*/
|
||||
res_attr->llseek = pci_llseek_resource;
|
||||
}
|
||||
res_attr->attr.name = res_attr_name;
|
||||
res_attr->attr.mode = 0600;
|
||||
res_attr->size = pci_resource_len(pdev, num);
|
||||
res_attr->private = (void *)(unsigned long)num;
|
||||
retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr);
|
||||
if (retval) {
|
||||
kfree(res_attr);
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (write_combine)
|
||||
pdev->res_attr_wc[num] = res_attr;
|
||||
else
|
||||
pdev->res_attr[num] = res_attr;
|
||||
|
||||
return 0;
|
||||
#define pci_dev_resource_io_attr(_bar) \
|
||||
static const struct bin_attribute dev_resource##_bar##_io_attr = { \
|
||||
.attr = { .name = "resource" __stringify(_bar), .mode = 0600 }, \
|
||||
.private = (void *)(unsigned long)(_bar), \
|
||||
.read = pci_read_resource, \
|
||||
.write = pci_write_resource, \
|
||||
__PCI_RESOURCE_IO_MMAP_ATTRS \
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_create_resource_files - create resource files in sysfs for @dev
|
||||
* @pdev: dev in question
|
||||
*
|
||||
* Walk the resources in @pdev creating files for each resource available.
|
||||
*/
|
||||
static int pci_create_resource_files(struct pci_dev *pdev)
|
||||
{
|
||||
int i;
|
||||
int retval;
|
||||
#define pci_dev_resource_uc_attr(_bar) \
|
||||
static const struct bin_attribute dev_resource##_bar##_uc_attr = { \
|
||||
.attr = { .name = "resource" __stringify(_bar), .mode = 0600 }, \
|
||||
.private = (void *)(unsigned long)(_bar), \
|
||||
.f_mapping = iomem_get_mapping, \
|
||||
.llseek = pci_llseek_resource, \
|
||||
.mmap = pci_mmap_resource_uc, \
|
||||
}
|
||||
|
||||
#define pci_dev_resource_wc_attr(_bar) \
|
||||
static const struct bin_attribute dev_resource##_bar##_wc_attr = { \
|
||||
.attr = { .name = "resource" __stringify(_bar) "_wc", .mode = 0600 }, \
|
||||
.private = (void *)(unsigned long)(_bar), \
|
||||
.f_mapping = iomem_get_mapping, \
|
||||
.llseek = pci_llseek_resource, \
|
||||
.mmap = pci_mmap_resource_wc, \
|
||||
}
|
||||
|
||||
static inline umode_t
|
||||
__pci_resource_attr_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int bar, bool write_combine,
|
||||
unsigned long flags)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
|
||||
/* Skip devices with non-mappable BARs */
|
||||
if (pdev->non_mappable_bars)
|
||||
return 0;
|
||||
|
||||
/* Expose the PCI resources from this device as files */
|
||||
for (i = 0; i < PCI_STD_NUM_BARS; i++) {
|
||||
if (!pci_resource_len(pdev, bar))
|
||||
return 0;
|
||||
|
||||
/* skip empty resources */
|
||||
if (!pci_resource_len(pdev, i))
|
||||
continue;
|
||||
if ((pci_resource_flags(pdev, bar) & flags) != flags)
|
||||
return 0;
|
||||
|
||||
retval = pci_create_attr(pdev, i, 0);
|
||||
/* for prefetchable resources, create a WC mappable file */
|
||||
if (!retval && arch_can_pci_mmap_wc() &&
|
||||
pdev->resource[i].flags & IORESOURCE_PREFETCH)
|
||||
retval = pci_create_attr(pdev, i, 1);
|
||||
if (retval) {
|
||||
pci_remove_resource_files(pdev);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
if (write_combine && !arch_can_pci_mmap_wc())
|
||||
return 0;
|
||||
|
||||
return a->attr.mode;
|
||||
}
|
||||
#else /* !(defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE)) */
|
||||
int __weak pci_create_resource_files(struct pci_dev *dev) { return 0; }
|
||||
void __weak pci_remove_resource_files(struct pci_dev *dev) { return; }
|
||||
|
||||
static umode_t pci_dev_resource_io_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int n)
|
||||
{
|
||||
return __pci_resource_attr_is_visible(kobj, a, n, false,
|
||||
IORESOURCE_IO);
|
||||
}
|
||||
|
||||
static umode_t pci_dev_resource_uc_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int n)
|
||||
{
|
||||
return __pci_resource_attr_is_visible(kobj, a, n, false,
|
||||
IORESOURCE_MEM);
|
||||
}
|
||||
|
||||
static umode_t pci_dev_resource_wc_is_visible(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int n)
|
||||
{
|
||||
return __pci_resource_attr_is_visible(kobj, a, n, true,
|
||||
IORESOURCE_MEM | IORESOURCE_PREFETCH);
|
||||
}
|
||||
|
||||
static size_t pci_dev_resource_bin_size(struct kobject *kobj,
|
||||
const struct bin_attribute *a,
|
||||
int n)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
|
||||
return pci_resource_len(pdev, n);
|
||||
}
|
||||
|
||||
pci_dev_resource_io_attr(0);
|
||||
pci_dev_resource_io_attr(1);
|
||||
pci_dev_resource_io_attr(2);
|
||||
pci_dev_resource_io_attr(3);
|
||||
pci_dev_resource_io_attr(4);
|
||||
pci_dev_resource_io_attr(5);
|
||||
|
||||
pci_dev_resource_uc_attr(0);
|
||||
pci_dev_resource_uc_attr(1);
|
||||
pci_dev_resource_uc_attr(2);
|
||||
pci_dev_resource_uc_attr(3);
|
||||
pci_dev_resource_uc_attr(4);
|
||||
pci_dev_resource_uc_attr(5);
|
||||
|
||||
pci_dev_resource_wc_attr(0);
|
||||
pci_dev_resource_wc_attr(1);
|
||||
pci_dev_resource_wc_attr(2);
|
||||
pci_dev_resource_wc_attr(3);
|
||||
pci_dev_resource_wc_attr(4);
|
||||
pci_dev_resource_wc_attr(5);
|
||||
|
||||
static const struct bin_attribute *const pci_dev_resource_io_attrs[] = {
|
||||
&dev_resource0_io_attr,
|
||||
&dev_resource1_io_attr,
|
||||
&dev_resource2_io_attr,
|
||||
&dev_resource3_io_attr,
|
||||
&dev_resource4_io_attr,
|
||||
&dev_resource5_io_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct bin_attribute *const pci_dev_resource_uc_attrs[] = {
|
||||
&dev_resource0_uc_attr,
|
||||
&dev_resource1_uc_attr,
|
||||
&dev_resource2_uc_attr,
|
||||
&dev_resource3_uc_attr,
|
||||
&dev_resource4_uc_attr,
|
||||
&dev_resource5_uc_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct bin_attribute *const pci_dev_resource_wc_attrs[] = {
|
||||
&dev_resource0_wc_attr,
|
||||
&dev_resource1_wc_attr,
|
||||
&dev_resource2_wc_attr,
|
||||
&dev_resource3_wc_attr,
|
||||
&dev_resource4_wc_attr,
|
||||
&dev_resource5_wc_attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group pci_dev_resource_io_attr_group = {
|
||||
.bin_attrs = pci_dev_resource_io_attrs,
|
||||
.is_bin_visible = pci_dev_resource_io_is_visible,
|
||||
.bin_size = pci_dev_resource_bin_size,
|
||||
};
|
||||
|
||||
static const struct attribute_group pci_dev_resource_uc_attr_group = {
|
||||
.bin_attrs = pci_dev_resource_uc_attrs,
|
||||
.is_bin_visible = pci_dev_resource_uc_is_visible,
|
||||
.bin_size = pci_dev_resource_bin_size,
|
||||
};
|
||||
|
||||
static const struct attribute_group pci_dev_resource_wc_attr_group = {
|
||||
.bin_attrs = pci_dev_resource_wc_attrs,
|
||||
.is_bin_visible = pci_dev_resource_wc_is_visible,
|
||||
.bin_size = pci_dev_resource_bin_size,
|
||||
};
|
||||
|
||||
static const struct attribute_group *pci_dev_resource_attr_groups[] = {
|
||||
&pci_dev_resource_io_attr_group,
|
||||
&pci_dev_resource_uc_attr_group,
|
||||
&pci_dev_resource_wc_attr_group,
|
||||
NULL,
|
||||
};
|
||||
#else
|
||||
#define pci_dev_resource_attr_groups NULL
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -1309,18 +1418,19 @@ void __weak pci_remove_resource_files(struct pci_dev *dev) { return; }
|
||||
* @off: file offset
|
||||
* @count: number of byte in input
|
||||
*
|
||||
* writing anything except 0 enables it
|
||||
* Writing a boolean value enables or disables the ROM display.
|
||||
*/
|
||||
static ssize_t pci_write_rom(struct file *filp, struct kobject *kobj,
|
||||
const struct bin_attribute *bin_attr, char *buf,
|
||||
loff_t off, size_t count)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
bool enable;
|
||||
|
||||
if ((off == 0) && (*buf == '0') && (count == 2))
|
||||
pdev->rom_attr_enabled = 0;
|
||||
else
|
||||
pdev->rom_attr_enabled = 1;
|
||||
if (kstrtobool(buf, &enable))
|
||||
return -EINVAL;
|
||||
|
||||
pdev->rom_attr_enabled = enable;
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -1552,6 +1662,7 @@ static const struct attribute_group pci_dev_reset_method_attr_group = {
|
||||
.is_visible = pci_dev_reset_attr_is_visible,
|
||||
};
|
||||
|
||||
#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE)
|
||||
static ssize_t __resource_resize_show(struct device *dev, int n, char *buf)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
@@ -1576,6 +1687,9 @@ static ssize_t __resource_resize_store(struct device *dev, int n,
|
||||
int ret;
|
||||
u16 cmd;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (kstrtoul(buf, 0, &size) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -1598,14 +1712,17 @@ static ssize_t __resource_resize_store(struct device *dev, int n,
|
||||
pci_write_config_word(pdev, PCI_COMMAND,
|
||||
cmd & ~PCI_COMMAND_MEMORY);
|
||||
|
||||
pci_remove_resource_files(pdev);
|
||||
sysfs_remove_groups(&pdev->dev.kobj, pci_dev_resource_attr_groups);
|
||||
|
||||
ret = pci_resize_resource(pdev, n, size, 0);
|
||||
if (ret)
|
||||
pci_warn(pdev, "Failed to resize BAR %d: %pe\n",
|
||||
n, ERR_PTR(ret));
|
||||
|
||||
pci_assign_unassigned_bus_resources(bus);
|
||||
|
||||
if (pci_create_resource_files(pdev))
|
||||
pci_warn(pdev, "Failed to recreate resource files after BAR resizing\n");
|
||||
if (sysfs_create_groups(&pdev->dev.kobj, pci_dev_resource_attr_groups))
|
||||
pci_warn(pdev, "Failed to recreate resource groups after BAR resizing\n");
|
||||
|
||||
pci_write_config_word(pdev, PCI_COMMAND, cmd);
|
||||
pm_put:
|
||||
@@ -1648,7 +1765,7 @@ static struct attribute *resource_resize_attrs[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t resource_resize_is_visible(struct kobject *kobj,
|
||||
static umode_t resource_resize_attr_is_visible(struct kobject *kobj,
|
||||
struct attribute *a, int n)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
|
||||
@@ -1656,54 +1773,11 @@ static umode_t resource_resize_is_visible(struct kobject *kobj,
|
||||
return pci_rebar_get_current_size(pdev, n) < 0 ? 0 : a->mode;
|
||||
}
|
||||
|
||||
static const struct attribute_group pci_dev_resource_resize_group = {
|
||||
static const struct attribute_group pci_dev_resource_resize_attr_group = {
|
||||
.attrs = resource_resize_attrs,
|
||||
.is_visible = resource_resize_is_visible,
|
||||
.is_visible = resource_resize_attr_is_visible,
|
||||
};
|
||||
|
||||
int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev)
|
||||
{
|
||||
if (!sysfs_initialized)
|
||||
return -EACCES;
|
||||
|
||||
return pci_create_resource_files(pdev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_remove_sysfs_dev_files - cleanup PCI specific sysfs files
|
||||
* @pdev: device whose entries we should free
|
||||
*
|
||||
* Cleanup when @pdev is removed from sysfs.
|
||||
*/
|
||||
void pci_remove_sysfs_dev_files(struct pci_dev *pdev)
|
||||
{
|
||||
if (!sysfs_initialized)
|
||||
return;
|
||||
|
||||
pci_remove_resource_files(pdev);
|
||||
}
|
||||
|
||||
static int __init pci_sysfs_init(void)
|
||||
{
|
||||
struct pci_dev *pdev = NULL;
|
||||
struct pci_bus *pbus = NULL;
|
||||
int retval;
|
||||
|
||||
sysfs_initialized = 1;
|
||||
for_each_pci_dev(pdev) {
|
||||
retval = pci_create_sysfs_dev_files(pdev);
|
||||
if (retval) {
|
||||
pci_dev_put(pdev);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
while ((pbus = pci_find_next_bus(pbus)))
|
||||
pci_create_legacy_files(pbus);
|
||||
|
||||
return 0;
|
||||
}
|
||||
late_initcall(pci_sysfs_init);
|
||||
#endif
|
||||
|
||||
static struct attribute *pci_dev_dev_attrs[] = {
|
||||
&dev_attr_boot_vga.attr,
|
||||
@@ -1774,6 +1848,12 @@ static const struct attribute_group pci_dev_group = {
|
||||
|
||||
const struct attribute_group *pci_dev_groups[] = {
|
||||
&pci_dev_group,
|
||||
#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE)
|
||||
&pci_dev_resource_io_attr_group,
|
||||
&pci_dev_resource_uc_attr_group,
|
||||
&pci_dev_resource_wc_attr_group,
|
||||
&pci_dev_resource_resize_attr_group,
|
||||
#endif
|
||||
&pci_dev_config_attr_group,
|
||||
&pci_dev_rom_attr_group,
|
||||
&pci_dev_reset_attr_group,
|
||||
@@ -1785,7 +1865,6 @@ const struct attribute_group *pci_dev_groups[] = {
|
||||
#ifdef CONFIG_ACPI
|
||||
&pci_dev_acpi_attr_group,
|
||||
#endif
|
||||
&pci_dev_resource_resize_group,
|
||||
ARCH_PCI_DEV_GROUPS
|
||||
NULL,
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <asm/dma.h>
|
||||
#include <linux/aer.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/suspend.h>
|
||||
#include "pci.h"
|
||||
|
||||
DEFINE_MUTEX(pci_slot_mutex);
|
||||
@@ -120,17 +121,7 @@ unsigned long pci_hotplug_bus_size = DEFAULT_HOTPLUG_BUS_SIZE;
|
||||
|
||||
|
||||
/* PCIe MPS/MRRS strategy; can be overridden by kernel command-line param */
|
||||
#ifdef CONFIG_PCIE_BUS_TUNE_OFF
|
||||
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_TUNE_OFF;
|
||||
#elif defined CONFIG_PCIE_BUS_SAFE
|
||||
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_SAFE;
|
||||
#elif defined CONFIG_PCIE_BUS_PERFORMANCE
|
||||
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_PERFORMANCE;
|
||||
#elif defined CONFIG_PCIE_BUS_PEER2PEER
|
||||
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_PEER2PEER;
|
||||
#else
|
||||
enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_DEFAULT;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The default CLS is used if arch didn't set CLS explicitly and not
|
||||
@@ -1253,8 +1244,8 @@ static int pci_dev_wait(struct pci_dev *dev, char *reset_type, int timeout)
|
||||
}
|
||||
|
||||
if (delay > timeout) {
|
||||
pci_warn(dev, "not ready %dms after %s; giving up\n",
|
||||
delay - 1, reset_type);
|
||||
pci_err(dev, "not ready %dms after %s; giving up\n",
|
||||
delay - 1, reset_type);
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
@@ -1300,7 +1291,18 @@ int pci_power_up(struct pci_dev *dev)
|
||||
bool need_restore;
|
||||
pci_power_t state;
|
||||
u16 pmcsr;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* When setting power state to D0, platform_pci_set_power_state()
|
||||
* ensures main power is on. If it puts the device in D0, it also
|
||||
* completes any required delays after the transition; if it leaves
|
||||
* the device in D1, D2, or D3hot, we use the PM Capability to
|
||||
* transition to D0.
|
||||
*
|
||||
* In all cases, the device is either Configuration-Ready or
|
||||
* inaccessible upon return.
|
||||
*/
|
||||
platform_pci_set_power_state(dev, PCI_D0);
|
||||
|
||||
if (!dev->pm_cap) {
|
||||
@@ -1341,10 +1343,19 @@ int pci_power_up(struct pci_dev *dev)
|
||||
pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, 0);
|
||||
|
||||
/* Mandatory transition delays; see PCI PM 1.2. */
|
||||
if (state == PCI_D3hot)
|
||||
if (state == PCI_D3hot) {
|
||||
pci_dev_d3_sleep(dev);
|
||||
else if (state == PCI_D2)
|
||||
if (!(pmcsr & PCI_PM_CTRL_NO_SOFT_RESET)) {
|
||||
ret = pci_dev_wait(dev, "power up D3hot->D0uninitialized",
|
||||
PCIE_RESET_READY_POLL_MS);
|
||||
if (ret) {
|
||||
dev->current_state = PCI_D3cold;
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
} else if (state == PCI_D2) {
|
||||
udelay(PCI_PM_D2_DELAY);
|
||||
}
|
||||
|
||||
end:
|
||||
dev->current_state = PCI_D0;
|
||||
@@ -1514,9 +1525,6 @@ static int pci_set_low_power_state(struct pci_dev *dev, pci_power_t state, bool
|
||||
pci_power_name(dev->current_state),
|
||||
pci_power_name(state));
|
||||
|
||||
if (dev->bus->self)
|
||||
pcie_aspm_pm_state_change(dev->bus->self, locked);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1764,7 +1772,7 @@ int pci_save_state(struct pci_dev *dev)
|
||||
EXPORT_SYMBOL(pci_save_state);
|
||||
|
||||
static void pci_restore_config_dword(struct pci_dev *pdev, int offset,
|
||||
u32 saved_val, int retry, bool force)
|
||||
u32 saved_val, bool force)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
@@ -1772,52 +1780,42 @@ static void pci_restore_config_dword(struct pci_dev *pdev, int offset,
|
||||
if (!force && val == saved_val)
|
||||
return;
|
||||
|
||||
for (;;) {
|
||||
pci_dbg(pdev, "restore config %#04x: %#010x -> %#010x\n",
|
||||
offset, val, saved_val);
|
||||
pci_write_config_dword(pdev, offset, saved_val);
|
||||
if (retry-- <= 0)
|
||||
return;
|
||||
pci_dbg(pdev, "restore config %#04x: %#010x -> %#010x\n", offset, val,
|
||||
saved_val);
|
||||
|
||||
pci_read_config_dword(pdev, offset, &val);
|
||||
if (val == saved_val)
|
||||
return;
|
||||
|
||||
mdelay(1);
|
||||
}
|
||||
pci_write_config_dword(pdev, offset, saved_val);
|
||||
}
|
||||
|
||||
static void pci_restore_config_space_range(struct pci_dev *pdev,
|
||||
int start, int end, int retry,
|
||||
bool force)
|
||||
int start, int end, bool force)
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = end; index >= start; index--)
|
||||
pci_restore_config_dword(pdev, 4 * index,
|
||||
pdev->saved_config_space[index],
|
||||
retry, force);
|
||||
force);
|
||||
}
|
||||
|
||||
static void pci_restore_config_space(struct pci_dev *pdev)
|
||||
{
|
||||
if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL) {
|
||||
pci_restore_config_space_range(pdev, 10, 15, 0, false);
|
||||
pci_restore_config_space_range(pdev, 10, 15, false);
|
||||
/* Restore BARs before the command register. */
|
||||
pci_restore_config_space_range(pdev, 4, 9, 10, false);
|
||||
pci_restore_config_space_range(pdev, 0, 3, 0, false);
|
||||
pci_restore_config_space_range(pdev, 4, 9, false);
|
||||
pci_restore_config_space_range(pdev, 0, 3, false);
|
||||
} else if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
|
||||
pci_restore_config_space_range(pdev, 12, 15, 0, false);
|
||||
pci_restore_config_space_range(pdev, 12, 15, false);
|
||||
|
||||
/*
|
||||
* Force rewriting of prefetch registers to avoid S3 resume
|
||||
* issues on Intel PCI bridges that occur when these
|
||||
* registers are not explicitly written.
|
||||
*/
|
||||
pci_restore_config_space_range(pdev, 9, 11, 0, true);
|
||||
pci_restore_config_space_range(pdev, 0, 8, 0, false);
|
||||
pci_restore_config_space_range(pdev, 9, 11, true);
|
||||
pci_restore_config_space_range(pdev, 0, 8, false);
|
||||
} else {
|
||||
pci_restore_config_space_range(pdev, 0, 15, 0, false);
|
||||
pci_restore_config_space_range(pdev, 0, 15, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2899,6 +2897,40 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev)
|
||||
pm_runtime_put_sync(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_suspend_retains_context - Check if the platform can retain the device
|
||||
* context during system suspend
|
||||
* @pdev: PCI device to check
|
||||
*
|
||||
* Return: true if the platform can guarantee to retain the device context,
|
||||
* false otherwise.
|
||||
*/
|
||||
bool pci_suspend_retains_context(struct pci_dev *pdev)
|
||||
{
|
||||
struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus);
|
||||
|
||||
/*
|
||||
* If the platform firmware (like ACPI) is involved at the end of
|
||||
* system suspend, device context may not be retained.
|
||||
*/
|
||||
if (pm_suspend_via_firmware())
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Some host bridges power off the PHY to enter deep low-power
|
||||
* modes during system suspend. Exiting L1SS from this condition
|
||||
* may violate timing requirements and result in Link Down (LDn),
|
||||
* which causes a reset of the device. On such platforms, the
|
||||
* endpoint must be prepared for context loss.
|
||||
*/
|
||||
if (bridge && bridge->broken_l1ss_resume)
|
||||
return false;
|
||||
|
||||
/* Assume that the context is retained by default */
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_suspend_retains_context);
|
||||
|
||||
static const struct dmi_system_id bridge_d3_blacklist[] = {
|
||||
#ifdef CONFIG_X86
|
||||
{
|
||||
@@ -5770,8 +5802,7 @@ int pcix_set_mmrbc(struct pci_dev *dev, int mmrbc)
|
||||
if (v > o && (dev->bus->bus_flags & PCI_BUS_FLAGS_NO_MMRBC))
|
||||
return -EIO;
|
||||
|
||||
cmd &= ~PCI_X_CMD_MAX_READ;
|
||||
cmd |= FIELD_PREP(PCI_X_CMD_MAX_READ, v);
|
||||
FIELD_MODIFY(PCI_X_CMD_MAX_READ, &cmd, v);
|
||||
if (pci_write_config_word(dev, cap + PCI_X_CMD, cmd))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
@@ -71,10 +71,11 @@ struct pcie_tlp_log;
|
||||
#define PCIE_TLP_FMT_4DW_DATA 0x03 /* 4DW header, with data */
|
||||
|
||||
/* Type of TLP; PCIe r7.0, sec 2.2.1 */
|
||||
#define PCIE_TLP_TYPE_CFG0_RD 0x04 /* Config Type 0 Read Request */
|
||||
#define PCIE_TLP_TYPE_CFG0_WR 0x04 /* Config Type 0 Write Request */
|
||||
#define PCIE_TLP_TYPE_CFG1_RD 0x05 /* Config Type 1 Read Request */
|
||||
#define PCIE_TLP_TYPE_CFG1_WR 0x05 /* Config Type 1 Write Request */
|
||||
#define PCIE_TLP_TYPE_MEM_RDWR 0x00 /* Memory Read/Write Request */
|
||||
#define PCIE_TLP_TYPE_IO_RDWR 0x02 /* I/O Read/Write Request */
|
||||
#define PCIE_TLP_TYPE_CFG0_RDWR 0x04 /* Config Type 0 Read/Write Request */
|
||||
#define PCIE_TLP_TYPE_CFG1_RDWR 0x05 /* Config Type 1 Read/Write Request */
|
||||
#define PCIE_TLP_TYPE_MSG 0x10 /* Message With/Without data Request */
|
||||
|
||||
/* Message Routing (r[2:0]); PCIe r6.0, sec 2.2.8 */
|
||||
#define PCIE_MSG_TYPE_R_RC 0
|
||||
@@ -358,14 +359,6 @@ static inline int pci_proc_detach_bus(struct pci_bus *bus) { return 0; }
|
||||
int pci_hp_add_bridge(struct pci_dev *dev);
|
||||
bool pci_hp_spurious_link_change(struct pci_dev *pdev);
|
||||
|
||||
#if defined(CONFIG_SYSFS) && defined(HAVE_PCI_LEGACY)
|
||||
void pci_create_legacy_files(struct pci_bus *bus);
|
||||
void pci_remove_legacy_files(struct pci_bus *bus);
|
||||
#else
|
||||
static inline void pci_create_legacy_files(struct pci_bus *bus) { }
|
||||
static inline void pci_remove_legacy_files(struct pci_bus *bus) { }
|
||||
#endif
|
||||
|
||||
/* Lock for read/write access to pci device and bus lists */
|
||||
extern struct rw_semaphore pci_bus_sem;
|
||||
extern struct mutex pci_slot_mutex;
|
||||
@@ -392,17 +385,17 @@ static inline int pci_no_d1d2(struct pci_dev *dev)
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_PCI_LEGACY
|
||||
bool pci_legacy_has_sparse(struct pci_bus *bus, enum pci_mmap_state type);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SYSFS
|
||||
int pci_create_sysfs_dev_files(struct pci_dev *pdev);
|
||||
void pci_remove_sysfs_dev_files(struct pci_dev *pdev);
|
||||
extern const struct attribute_group *pci_dev_groups[];
|
||||
extern const struct attribute_group *pci_dev_attr_groups[];
|
||||
extern const struct attribute_group *pcibus_groups[];
|
||||
extern const struct attribute_group *pci_bus_groups[];
|
||||
extern const struct attribute_group pci_doe_sysfs_group;
|
||||
#else
|
||||
static inline int pci_create_sysfs_dev_files(struct pci_dev *pdev) { return 0; }
|
||||
static inline void pci_remove_sysfs_dev_files(struct pci_dev *pdev) { }
|
||||
#define pci_dev_groups NULL
|
||||
#define pci_dev_attr_groups NULL
|
||||
#define pcibus_groups NULL
|
||||
@@ -419,7 +412,7 @@ static inline bool pci_is_cardbus_bridge(struct pci_dev *dev)
|
||||
return dev->hdr_type == PCI_HEADER_TYPE_CARDBUS;
|
||||
}
|
||||
#ifdef CONFIG_CARDBUS
|
||||
unsigned long pci_cardbus_resource_alignment(struct resource *res);
|
||||
unsigned long pci_cardbus_resource_alignment(const struct resource *res);
|
||||
int pci_bus_size_cardbus_bridge(struct pci_bus *bus,
|
||||
struct list_head *realloc_head);
|
||||
int pci_cardbus_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
|
||||
@@ -428,7 +421,7 @@ int pci_cardbus_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
|
||||
int pci_setup_cardbus(char *str);
|
||||
|
||||
#else
|
||||
static inline unsigned long pci_cardbus_resource_alignment(struct resource *res)
|
||||
static inline unsigned long pci_cardbus_resource_alignment(const struct resource *res)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -515,7 +508,7 @@ int pci_dev_res_add_to_list(struct list_head *head, struct pci_dev *dev,
|
||||
void __pci_bus_size_bridges(struct pci_bus *bus,
|
||||
struct list_head *realloc_head);
|
||||
void __pci_bus_assign_resources(const struct pci_bus *bus,
|
||||
struct list_head *realloc_head,
|
||||
struct list_head *add_list,
|
||||
struct list_head *fail_head);
|
||||
bool pci_bus_clip_resource(struct pci_dev *dev, int idx);
|
||||
void pci_walk_bus_locked(struct pci_bus *top,
|
||||
@@ -947,7 +940,8 @@ int pci_iov_init(struct pci_dev *dev);
|
||||
void pci_iov_release(struct pci_dev *dev);
|
||||
void pci_iov_remove(struct pci_dev *dev);
|
||||
void pci_iov_update_resource(struct pci_dev *dev, int resno);
|
||||
resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno);
|
||||
resource_size_t pci_sriov_resource_alignment(const struct pci_dev *dev,
|
||||
int resno);
|
||||
void pci_restore_iov_state(struct pci_dev *dev);
|
||||
int pci_iov_bus_range(struct pci_bus *bus);
|
||||
void pci_iov_resource_set_size(struct pci_dev *dev, int resno, int size);
|
||||
@@ -981,7 +975,7 @@ static inline int pci_iov_init(struct pci_dev *dev)
|
||||
static inline void pci_iov_release(struct pci_dev *dev) { }
|
||||
static inline void pci_iov_remove(struct pci_dev *dev) { }
|
||||
static inline void pci_iov_update_resource(struct pci_dev *dev, int resno) { }
|
||||
static inline resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev,
|
||||
static inline resource_size_t pci_sriov_resource_alignment(const struct pci_dev *dev,
|
||||
int resno)
|
||||
{
|
||||
return 0;
|
||||
@@ -1043,17 +1037,8 @@ static inline void pci_suspend_ptm(struct pci_dev *dev) { }
|
||||
static inline void pci_resume_ptm(struct pci_dev *dev) { }
|
||||
#endif
|
||||
|
||||
static inline resource_size_t pci_resource_alignment(struct pci_dev *dev,
|
||||
struct resource *res)
|
||||
{
|
||||
int resno = pci_resource_num(dev, res);
|
||||
|
||||
if (pci_resource_is_iov(resno))
|
||||
return pci_sriov_resource_alignment(dev, resno);
|
||||
if (dev->class >> 8 == PCI_CLASS_BRIDGE_CARDBUS)
|
||||
return pci_cardbus_resource_alignment(res);
|
||||
return resource_alignment(res);
|
||||
}
|
||||
resource_size_t pci_resource_alignment(const struct pci_dev *dev,
|
||||
const struct resource *res);
|
||||
|
||||
resource_size_t pci_min_window_alignment(struct pci_bus *bus,
|
||||
unsigned long type);
|
||||
@@ -1110,6 +1095,7 @@ void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked);
|
||||
void pcie_aspm_powersave_config_link(struct pci_dev *pdev);
|
||||
void pci_configure_ltr(struct pci_dev *pdev);
|
||||
void pci_bridge_reconfigure_ltr(struct pci_dev *pdev);
|
||||
void pcie_encode_t_power_on(u32 t_power_on_us, u8 *scale, u8 *value);
|
||||
#else
|
||||
static inline void pcie_aspm_remove_cap(struct pci_dev *pdev, u32 lnkcap) { }
|
||||
static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) { }
|
||||
@@ -1118,6 +1104,11 @@ static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked)
|
||||
static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) { }
|
||||
static inline void pci_configure_ltr(struct pci_dev *pdev) { }
|
||||
static inline void pci_bridge_reconfigure_ltr(struct pci_dev *pdev) { }
|
||||
static inline void pcie_encode_t_power_on(u32 t_power_on_us, u8 *scale, u8 *value)
|
||||
{
|
||||
*scale = 0;
|
||||
*value = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PCIE_ECRC
|
||||
|
||||
@@ -525,6 +525,46 @@ static u32 calc_l12_pwron(struct pci_dev *pdev, u32 scale, u32 val)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pcie_encode_t_power_on - Encode T_POWER_ON into scale and value fields
|
||||
* @t_power_on_us: T_POWER_ON time in microseconds
|
||||
* @scale: Encoded T_POWER_ON Scale (0..2)
|
||||
* @value: Encoded T_POWER_ON Value
|
||||
*
|
||||
* T_POWER_ON is encoded as:
|
||||
* T_POWER_ON(us) = scale_unit(us) * value
|
||||
*
|
||||
* where scale_unit is selected by @scale:
|
||||
* 0: 2us
|
||||
* 1: 10us
|
||||
* 2: 100us
|
||||
*
|
||||
* If @t_power_on_us exceeds the maximum representable value, the result
|
||||
* is clamped to the largest encodable T_POWER_ON.
|
||||
*
|
||||
* See PCIe r7.0, sec 7.8.3.2.
|
||||
*/
|
||||
void pcie_encode_t_power_on(u32 t_power_on_us, u8 *scale, u8 *value)
|
||||
{
|
||||
u8 maxv = FIELD_MAX(PCI_L1SS_CAP_P_PWR_ON_VALUE);
|
||||
|
||||
/* T_POWER_ON_Value ("value") is a 5-bit field with max value of 31. */
|
||||
if (t_power_on_us <= 2 * maxv) {
|
||||
*scale = 0; /* Value times 2us */
|
||||
*value = DIV_ROUND_UP(t_power_on_us, 2);
|
||||
} else if (t_power_on_us <= 10 * maxv) {
|
||||
*scale = 1; /* Value times 10us */
|
||||
*value = DIV_ROUND_UP(t_power_on_us, 10);
|
||||
} else if (t_power_on_us <= 100 * maxv) {
|
||||
*scale = 2; /* value times 100us */
|
||||
*value = DIV_ROUND_UP(t_power_on_us, 100);
|
||||
} else {
|
||||
*scale = 2;
|
||||
*value = maxv;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(pcie_encode_t_power_on);
|
||||
|
||||
/*
|
||||
* Encode an LTR_L1.2_THRESHOLD value for the L1 PM Substates Control 1
|
||||
* register. Ports enter L1.2 when the most recent LTR value is greater
|
||||
|
||||
@@ -152,8 +152,7 @@ static int __pci_enable_ptm(struct pci_dev *dev)
|
||||
pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, &ctrl);
|
||||
|
||||
ctrl |= PCI_PTM_CTRL_ENABLE;
|
||||
ctrl &= ~PCI_PTM_GRANULARITY_MASK;
|
||||
ctrl |= FIELD_PREP(PCI_PTM_GRANULARITY_MASK, dev->ptm_granularity);
|
||||
FIELD_MODIFY(PCI_PTM_GRANULARITY_MASK, &ctrl, dev->ptm_granularity);
|
||||
if (dev->ptm_root)
|
||||
ctrl |= PCI_PTM_CTRL_ROOT;
|
||||
|
||||
|
||||
@@ -660,6 +660,7 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge)
|
||||
{
|
||||
INIT_LIST_HEAD(&bridge->windows);
|
||||
INIT_LIST_HEAD(&bridge->dma_ranges);
|
||||
INIT_LIST_HEAD(&bridge->ports);
|
||||
|
||||
/*
|
||||
* We assume we can manage these PCIe features. Some systems may
|
||||
@@ -1073,9 +1074,6 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
|
||||
dev_err(&bus->dev, "failed to add bus: %d\n", err);
|
||||
}
|
||||
|
||||
/* Create legacy_io and legacy_mem files for this bus */
|
||||
pci_create_legacy_files(bus);
|
||||
|
||||
if (parent)
|
||||
dev_info(parent, "PCI host bridge to bus %s\n", name);
|
||||
else
|
||||
@@ -1281,9 +1279,6 @@ static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent,
|
||||
dev_err(&child->dev, "failed to add bus: %d\n", ret);
|
||||
}
|
||||
|
||||
/* Create legacy_io and legacy_mem files for this bus */
|
||||
pci_create_legacy_files(child);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "pci.h"
|
||||
|
||||
static int proc_initialized; /* = 0 */
|
||||
static DEFINE_MUTEX(pci_proc_lock);
|
||||
|
||||
static loff_t proc_bus_pci_lseek(struct file *file, loff_t off, int whence)
|
||||
{
|
||||
@@ -416,40 +417,64 @@ static const struct seq_operations proc_bus_pci_devices_op = {
|
||||
|
||||
static struct proc_dir_entry *proc_bus_pci_dir;
|
||||
|
||||
int pci_proc_attach_device(struct pci_dev *dev)
|
||||
static int __pci_proc_attach_bus(struct pci_bus *bus)
|
||||
{
|
||||
struct pci_bus *bus = dev->bus;
|
||||
struct proc_dir_entry *e;
|
||||
struct proc_dir_entry *dir;
|
||||
char name[16];
|
||||
|
||||
lockdep_assert_held(&pci_proc_lock);
|
||||
|
||||
if (!proc_initialized)
|
||||
return -EACCES;
|
||||
|
||||
if (!bus->procdir) {
|
||||
if (pci_proc_domain(bus)) {
|
||||
sprintf(name, "%04x:%02x", pci_domain_nr(bus),
|
||||
bus->number);
|
||||
} else {
|
||||
sprintf(name, "%02x", bus->number);
|
||||
}
|
||||
bus->procdir = proc_mkdir(name, proc_bus_pci_dir);
|
||||
if (!bus->procdir)
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (bus->procdir)
|
||||
return 0;
|
||||
|
||||
if (pci_proc_domain(bus))
|
||||
sprintf(name, "%04x:%02x", pci_domain_nr(bus), bus->number);
|
||||
else
|
||||
sprintf(name, "%02x", bus->number);
|
||||
|
||||
dir = proc_mkdir(name, proc_bus_pci_dir);
|
||||
if (!dir)
|
||||
return -ENOMEM;
|
||||
|
||||
bus->procdir = dir;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pci_proc_attach_device(struct pci_dev *dev)
|
||||
{
|
||||
struct pci_bus *bus = dev->bus;
|
||||
struct proc_dir_entry *entry;
|
||||
char name[16];
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&pci_proc_lock);
|
||||
|
||||
if (dev->procent)
|
||||
return 0;
|
||||
|
||||
ret = __pci_proc_attach_bus(bus);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sprintf(name, "%02x.%x", PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));
|
||||
e = proc_create_data(name, S_IFREG | S_IRUGO | S_IWUSR, bus->procdir,
|
||||
&proc_bus_pci_ops, dev);
|
||||
if (!e)
|
||||
entry = proc_create_data(name, S_IFREG | S_IRUGO | S_IWUSR,
|
||||
bus->procdir, &proc_bus_pci_ops, dev);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
proc_set_size(e, dev->cfg_size);
|
||||
dev->procent = e;
|
||||
|
||||
proc_set_size(entry, dev->cfg_size);
|
||||
dev->procent = entry;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pci_proc_detach_device(struct pci_dev *dev)
|
||||
{
|
||||
guard(mutex)(&pci_proc_lock);
|
||||
proc_remove(dev->procent);
|
||||
dev->procent = NULL;
|
||||
return 0;
|
||||
@@ -457,19 +482,27 @@ int pci_proc_detach_device(struct pci_dev *dev)
|
||||
|
||||
int pci_proc_detach_bus(struct pci_bus *bus)
|
||||
{
|
||||
guard(mutex)(&pci_proc_lock);
|
||||
proc_remove(bus->procdir);
|
||||
bus->procdir = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init pci_proc_init(void)
|
||||
{
|
||||
struct pci_dev *dev = NULL;
|
||||
proc_bus_pci_dir = proc_mkdir("bus/pci", NULL);
|
||||
proc_create_seq("devices", 0, proc_bus_pci_dir,
|
||||
&proc_bus_pci_devices_op);
|
||||
proc_initialized = 1;
|
||||
|
||||
scoped_guard(mutex, &pci_proc_lock) {
|
||||
proc_bus_pci_dir = proc_mkdir("bus/pci", NULL);
|
||||
proc_create_seq("devices", 0, proc_bus_pci_dir,
|
||||
&proc_bus_pci_devices_op);
|
||||
proc_initialized = 1;
|
||||
}
|
||||
|
||||
pci_lock_rescan_remove();
|
||||
for_each_pci_dev(dev)
|
||||
pci_proc_attach_device(dev);
|
||||
pci_unlock_rescan_remove();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -139,6 +139,48 @@ int devm_pci_pwrctrl_device_set_ready(struct device *dev,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_pci_pwrctrl_device_set_ready);
|
||||
|
||||
/*
|
||||
* Check whether the pwrctrl device really needs to be created or not. The
|
||||
* pwrctrl device will only be created if the node satisfies below requirements:
|
||||
*
|
||||
* 1. Presence of compatible property with "pci" prefix to match against the
|
||||
* pwrctrl driver (AND)
|
||||
* 2. At least one of the power supplies defined in the devicetree node of the
|
||||
* device (OR) in the remote endpoint parent node to indicate pwrctrl
|
||||
* requirement.
|
||||
*/
|
||||
static bool pci_pwrctrl_is_required(struct device_node *np)
|
||||
{
|
||||
struct device_node *endpoint;
|
||||
const char *compat;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_string(np, "compatible", &compat);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
|
||||
if (!strstarts(compat, "pci"))
|
||||
return false;
|
||||
|
||||
if (of_pci_supply_present(np))
|
||||
return true;
|
||||
|
||||
if (of_graph_is_present(np)) {
|
||||
for_each_endpoint_of_node(np, endpoint) {
|
||||
struct device_node *remote __free(device_node) =
|
||||
of_graph_get_remote_port_parent(endpoint);
|
||||
if (remote) {
|
||||
if (of_pci_supply_present(remote)) {
|
||||
of_node_put(endpoint);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int __pci_pwrctrl_power_off_device(struct device *dev)
|
||||
{
|
||||
struct pci_pwrctrl *pwrctrl = dev_get_drvdata(dev);
|
||||
@@ -157,14 +199,19 @@ static void pci_pwrctrl_power_off_device(struct device_node *np)
|
||||
for_each_available_child_of_node_scoped(np, child)
|
||||
pci_pwrctrl_power_off_device(child);
|
||||
|
||||
if (!pci_pwrctrl_is_required(np))
|
||||
return;
|
||||
|
||||
pdev = of_find_device_by_node(np);
|
||||
if (!pdev)
|
||||
return;
|
||||
|
||||
if (device_is_bound(&pdev->dev)) {
|
||||
ret = __pci_pwrctrl_power_off_device(&pdev->dev);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "Failed to power off device: %d", ret);
|
||||
scoped_guard(device, &pdev->dev) {
|
||||
if (device_is_bound(&pdev->dev)) {
|
||||
ret = __pci_pwrctrl_power_off_device(&pdev->dev);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "Failed to power off device: %d", ret);
|
||||
}
|
||||
}
|
||||
|
||||
platform_device_put(pdev);
|
||||
@@ -205,7 +252,7 @@ static int __pci_pwrctrl_power_on_device(struct device *dev)
|
||||
static int pci_pwrctrl_power_on_device(struct device_node *np)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
for_each_available_child_of_node_scoped(np, child) {
|
||||
ret = pci_pwrctrl_power_on_device(child);
|
||||
@@ -213,16 +260,21 @@ static int pci_pwrctrl_power_on_device(struct device_node *np)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!pci_pwrctrl_is_required(np))
|
||||
return 0;
|
||||
|
||||
pdev = of_find_device_by_node(np);
|
||||
if (!pdev)
|
||||
return 0;
|
||||
|
||||
if (device_is_bound(&pdev->dev)) {
|
||||
ret = __pci_pwrctrl_power_on_device(&pdev->dev);
|
||||
} else {
|
||||
/* FIXME: Use blocking wait instead of probe deferral */
|
||||
dev_dbg(&pdev->dev, "driver is not bound\n");
|
||||
ret = -EPROBE_DEFER;
|
||||
scoped_guard(device, &pdev->dev) {
|
||||
if (device_is_bound(&pdev->dev)) {
|
||||
ret = __pci_pwrctrl_power_on_device(&pdev->dev);
|
||||
} else {
|
||||
/* FIXME: Use blocking wait instead of probe deferral */
|
||||
dev_dbg(&pdev->dev, "driver is not bound\n");
|
||||
ret = -EPROBE_DEFER;
|
||||
}
|
||||
}
|
||||
|
||||
platform_device_put(pdev);
|
||||
@@ -268,48 +320,6 @@ int pci_pwrctrl_power_on_devices(struct device *parent)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_pwrctrl_power_on_devices);
|
||||
|
||||
/*
|
||||
* Check whether the pwrctrl device really needs to be created or not. The
|
||||
* pwrctrl device will only be created if the node satisfies below requirements:
|
||||
*
|
||||
* 1. Presence of compatible property with "pci" prefix to match against the
|
||||
* pwrctrl driver (AND)
|
||||
* 2. At least one of the power supplies defined in the devicetree node of the
|
||||
* device (OR) in the remote endpoint parent node to indicate pwrctrl
|
||||
* requirement.
|
||||
*/
|
||||
static bool pci_pwrctrl_is_required(struct device_node *np)
|
||||
{
|
||||
struct device_node *endpoint;
|
||||
const char *compat;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_string(np, "compatible", &compat);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
|
||||
if (!strstarts(compat, "pci"))
|
||||
return false;
|
||||
|
||||
if (of_pci_supply_present(np))
|
||||
return true;
|
||||
|
||||
if (of_graph_is_present(np)) {
|
||||
for_each_endpoint_of_node(np, endpoint) {
|
||||
struct device_node *remote __free(device_node) =
|
||||
of_graph_get_remote_port_parent(endpoint);
|
||||
if (remote) {
|
||||
if (of_pci_supply_present(remote)) {
|
||||
of_node_put(endpoint);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int pci_pwrctrl_create_device(struct device_node *np,
|
||||
struct device *parent)
|
||||
{
|
||||
|
||||
@@ -80,11 +80,10 @@ static bool pcie_lbms_seen(struct pci_dev *dev, u16 lnksta)
|
||||
* Restrict the speed to 2.5GT/s then with the Target Link Speed field,
|
||||
* request a retrain and check the result.
|
||||
*
|
||||
* If this turns out successful and we know by the Vendor:Device ID it is
|
||||
* safe to do so, then lift the restriction, letting the devices negotiate
|
||||
* a higher speed. Also check for a similar 2.5GT/s speed restriction the
|
||||
* firmware may have already arranged and lift it with ports that already
|
||||
* report their data link being up.
|
||||
* If this turns out successful, or where a 2.5GT/s speed restriction has
|
||||
* been previously arranged by the firmware and the port reports its link
|
||||
* already being up, lift the restriction, in a hope it is safe to do so,
|
||||
* letting the devices negotiate a higher speed.
|
||||
*
|
||||
* Otherwise revert the speed to the original setting and request a retrain
|
||||
* again to remove any residual state, ignoring the result as it's supposed
|
||||
@@ -95,51 +94,39 @@ static bool pcie_lbms_seen(struct pci_dev *dev, u16 lnksta)
|
||||
*/
|
||||
int pcie_failed_link_retrain(struct pci_dev *dev)
|
||||
{
|
||||
static const struct pci_device_id ids[] = {
|
||||
{ PCI_VDEVICE(ASMEDIA, 0x2824) }, /* ASMedia ASM2824 */
|
||||
{}
|
||||
};
|
||||
u16 lnksta, lnkctl2;
|
||||
u16 lnksta, lnkctl2, oldlnkctl2;
|
||||
enum pci_bus_speed speed_cap;
|
||||
int ret = -ENOTTY;
|
||||
|
||||
if (!pci_is_pcie(dev) || !pcie_downstream_port(dev) ||
|
||||
!pcie_cap_has_lnkctl2(dev) || !dev->link_active_reporting)
|
||||
return ret;
|
||||
|
||||
speed_cap = pcie_get_speed_cap(dev);
|
||||
if (speed_cap <= PCIE_SPEED_2_5GT)
|
||||
return ret;
|
||||
|
||||
pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta);
|
||||
pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2);
|
||||
if (!(lnksta & PCI_EXP_LNKSTA_DLLLA) && pcie_lbms_seen(dev, lnksta)) {
|
||||
u16 oldlnkctl2;
|
||||
|
||||
pci_info(dev, "broken device, retraining non-functional downstream link at 2.5GT/s\n");
|
||||
|
||||
pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2);
|
||||
ret = pcie_set_target_speed(dev, PCIE_SPEED_2_5GT, false);
|
||||
if (ret) {
|
||||
pci_info(dev, "retraining failed\n");
|
||||
pcie_set_target_speed(dev, PCIE_LNKCTL2_TLS2SPEED(oldlnkctl2),
|
||||
true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &lnkctl2);
|
||||
|
||||
if ((lnksta & PCI_EXP_LNKSTA_DLLLA) &&
|
||||
(lnkctl2 & PCI_EXP_LNKCTL2_TLS) == PCI_EXP_LNKCTL2_TLS_2_5GT &&
|
||||
pci_match_id(ids, dev)) {
|
||||
u32 lnkcap;
|
||||
|
||||
if ((lnkctl2 & PCI_EXP_LNKCTL2_TLS) == PCI_EXP_LNKCTL2_TLS_2_5GT) {
|
||||
pci_info(dev, "removing 2.5GT/s downstream link speed restriction\n");
|
||||
pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &lnkcap);
|
||||
ret = pcie_set_target_speed(dev, PCIE_LNKCAP_SLS2SPEED(lnkcap), false);
|
||||
if (ret) {
|
||||
pci_info(dev, "retraining failed\n");
|
||||
return ret;
|
||||
}
|
||||
ret = pcie_set_target_speed(dev, speed_cap, false);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
err:
|
||||
pci_info(dev, "retraining failed\n");
|
||||
pcie_set_target_speed(dev, PCIE_LNKCTL2_TLS2SPEED(oldlnkctl2), true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -3790,6 +3777,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x003c, quirk_no_bus_reset);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x0033, quirk_no_bus_reset);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x0034, quirk_no_bus_reset);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x003e, quirk_no_bus_reset);
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x1103, quirk_no_bus_reset); /* WCN6855 */
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x1107, quirk_no_bus_reset); /* WCN7850 */
|
||||
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x0308, quirk_no_bus_reset); /* SDX62/SDX65 */
|
||||
|
||||
/*
|
||||
* Root port on some Cavium CN8xxx chips do not successfully complete a bus
|
||||
@@ -5605,6 +5595,7 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x443, quirk_intel_qat_vf_cap);
|
||||
* Intel 82579LM Gigabit Ethernet Controller 0x1502
|
||||
* Intel 82579V Gigabit Ethernet Controller 0x1503
|
||||
* Mediatek MT7922 802.11ax PCI Express Wireless Network Adapter
|
||||
* Mediatek MT7925 802.11be PCI Express Wireless Network Adapter
|
||||
*/
|
||||
static void quirk_no_flr(struct pci_dev *dev)
|
||||
{
|
||||
@@ -5619,6 +5610,7 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x17f0, quirk_no_flr);
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1502, quirk_no_flr);
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1503, quirk_no_flr);
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_MEDIATEK, 0x0616, quirk_no_flr);
|
||||
DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_MEDIATEK, 0x7925, quirk_no_flr);
|
||||
|
||||
/* FLR may cause the SolidRun SNET DPU (rev 0x1) to hang */
|
||||
static void quirk_no_flr_snet(struct pci_dev *dev)
|
||||
|
||||
@@ -211,8 +211,7 @@ int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size)
|
||||
return pos;
|
||||
|
||||
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
|
||||
ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
|
||||
ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
|
||||
FIELD_MODIFY(PCI_REBAR_CTRL_BAR_SIZE, &ctrl, size);
|
||||
pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
|
||||
|
||||
if (pci_resource_is_iov(bar))
|
||||
@@ -231,6 +230,9 @@ void pci_restore_rebar_state(struct pci_dev *pdev)
|
||||
return;
|
||||
|
||||
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
|
||||
if (PCI_POSSIBLE_ERROR(ctrl))
|
||||
return;
|
||||
|
||||
nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl);
|
||||
|
||||
for (i = 0; i < nbars; i++, pos += 8) {
|
||||
@@ -238,11 +240,13 @@ void pci_restore_rebar_state(struct pci_dev *pdev)
|
||||
int bar_idx, size;
|
||||
|
||||
pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl);
|
||||
if (PCI_POSSIBLE_ERROR(ctrl))
|
||||
return;
|
||||
|
||||
bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX;
|
||||
res = pci_resource_n(pdev, bar_idx);
|
||||
size = pci_rebar_bytes_to_size(resource_size(res));
|
||||
ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE;
|
||||
ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size);
|
||||
FIELD_MODIFY(PCI_REBAR_CTRL_BAR_SIZE, &ctrl, size);
|
||||
pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ static void pci_stop_dev(struct pci_dev *dev)
|
||||
|
||||
device_release_driver(&dev->dev);
|
||||
pci_proc_detach_device(dev);
|
||||
pci_remove_sysfs_dev_files(dev);
|
||||
of_pci_remove_node(dev);
|
||||
}
|
||||
|
||||
@@ -66,8 +65,6 @@ void pci_remove_bus(struct pci_bus *bus)
|
||||
list_del(&bus->node);
|
||||
pci_bus_release_busn_res(bus);
|
||||
up_write(&pci_bus_sem);
|
||||
pci_remove_legacy_files(bus);
|
||||
|
||||
if (bus->ops->remove_bus)
|
||||
bus->ops->remove_bus(bus);
|
||||
|
||||
|
||||
@@ -5,13 +5,40 @@
|
||||
* (C) Copyright 2004 Jon Smirl <jonsmirl@yahoo.com>
|
||||
* (C) Copyright 2004 Silicon Graphics, Inc. Jesse Barnes <jbarnes@sgi.com>
|
||||
*/
|
||||
|
||||
#include <linux/align.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "pci.h"
|
||||
|
||||
#define PCI_ROM_HEADER_SIZE 0x1A
|
||||
#define PCI_ROM_POINTER_TO_DATA_STRUCT 0x18
|
||||
#define PCI_ROM_LAST_IMAGE_INDICATOR 0x15
|
||||
#define PCI_ROM_LAST_IMAGE_INDICATOR_BIT BIT(7)
|
||||
#define PCI_ROM_IMAGE_LEN 0x10
|
||||
#define PCI_ROM_IMAGE_SECTOR_SIZE SZ_512
|
||||
#define PCI_ROM_IMAGE_SIGNATURE 0xAA55
|
||||
|
||||
/* Data structure signature is "PCIR" in ASCII representation */
|
||||
#define PCI_ROM_DATA_STRUCT_SIGNATURE 0x52494350
|
||||
#define PCI_ROM_DATA_STRUCT_LEN 0x0A
|
||||
|
||||
/*
|
||||
* Per PCI Firmware r3.3, sec 5.1.3, a conformant PCI Data Structure is at
|
||||
* least 24 bytes (0x18), large enough to cover every fixed field this
|
||||
* driver reads (up to the Indicator byte at offset 0x15). Reject smaller
|
||||
* device-claimed lengths so the follow-up readers in pci_get_rom_size()
|
||||
* cannot escape the mapped ROM window.
|
||||
*/
|
||||
#define PCI_ROM_DATA_STRUCT_MIN_LEN 0x18
|
||||
|
||||
/**
|
||||
* pci_enable_rom - enable ROM decoding for a PCI device
|
||||
* @pdev: PCI device to enable
|
||||
@@ -69,6 +96,91 @@ void pci_disable_rom(struct pci_dev *pdev)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_disable_rom);
|
||||
|
||||
static bool pci_rom_header_valid(struct pci_dev *pdev, void __iomem *image,
|
||||
void __iomem *rom, size_t size,
|
||||
bool expect_valid)
|
||||
{
|
||||
unsigned long rom_end = (unsigned long)rom + size - 1;
|
||||
unsigned long header_end;
|
||||
u16 signature;
|
||||
|
||||
/*
|
||||
* Per PCI Firmware r3.3, sec 5.1, each image must start on a
|
||||
* 512-byte boundary and must contain the PCI Expansion ROM header.
|
||||
* Because @rom is page-aligned (returned by ioremap()), checking
|
||||
* 512-byte alignment of @image is equivalent to enforcing the
|
||||
* spec's sector-aligned layout within the ROM. This also
|
||||
* satisfies the natural-alignment requirement of readw() on archs
|
||||
* such as arm64 that disallow unaligned IOMEM access.
|
||||
*/
|
||||
if (!IS_ALIGNED((unsigned long)image, PCI_ROM_IMAGE_SECTOR_SIZE))
|
||||
return false;
|
||||
|
||||
if (check_add_overflow((unsigned long)image, PCI_ROM_HEADER_SIZE - 1,
|
||||
&header_end))
|
||||
return false;
|
||||
|
||||
if (image < rom || header_end > rom_end)
|
||||
return false;
|
||||
|
||||
/* Standard PCI ROMs start out with these bytes 55 AA */
|
||||
signature = readw(image);
|
||||
if (signature != PCI_ROM_IMAGE_SIGNATURE) {
|
||||
if (expect_valid) {
|
||||
pci_info(pdev, "Invalid PCI ROM header signature: expecting %#06x, got %#06x\n",
|
||||
PCI_ROM_IMAGE_SIGNATURE, signature);
|
||||
} else {
|
||||
pci_info(pdev, "No more images in PCI ROM\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pci_rom_data_struct_valid(struct pci_dev *pdev, void __iomem *pds,
|
||||
void __iomem *rom, size_t size)
|
||||
{
|
||||
unsigned long rom_end = (unsigned long)rom + size - 1;
|
||||
unsigned long end;
|
||||
u32 signature;
|
||||
u16 data_len;
|
||||
|
||||
/*
|
||||
* Some CPU architectures require IOMEM access addresses to be
|
||||
* aligned, for example arm64, so since we're about to call
|
||||
* readl(), check here for 4-byte alignment.
|
||||
*/
|
||||
if (!IS_ALIGNED((unsigned long)pds, 4))
|
||||
return false;
|
||||
|
||||
if (check_add_overflow((unsigned long)pds, PCI_ROM_DATA_STRUCT_LEN + 1,
|
||||
&end))
|
||||
return false;
|
||||
|
||||
if (pds < rom || end > rom_end)
|
||||
return false;
|
||||
|
||||
signature = readl(pds);
|
||||
if (signature != PCI_ROM_DATA_STRUCT_SIGNATURE) {
|
||||
pci_info(pdev, "Invalid PCI ROM data signature: expecting %#010x, got %#010x\n",
|
||||
PCI_ROM_DATA_STRUCT_SIGNATURE, signature);
|
||||
return false;
|
||||
}
|
||||
|
||||
data_len = readw(pds + PCI_ROM_DATA_STRUCT_LEN);
|
||||
if (data_len < PCI_ROM_DATA_STRUCT_MIN_LEN || data_len == U16_MAX)
|
||||
return false;
|
||||
|
||||
if (check_add_overflow((unsigned long)pds, data_len - 1, &end))
|
||||
return false;
|
||||
|
||||
if (end > rom_end)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_get_rom_size - obtain the actual size of the ROM image
|
||||
* @pdev: target PCI device
|
||||
@@ -84,37 +196,28 @@ static size_t pci_get_rom_size(struct pci_dev *pdev, void __iomem *rom,
|
||||
size_t size)
|
||||
{
|
||||
void __iomem *image;
|
||||
int last_image;
|
||||
unsigned int length;
|
||||
bool last_image;
|
||||
|
||||
image = rom;
|
||||
do {
|
||||
void __iomem *pds;
|
||||
/* Standard PCI ROMs start out with these bytes 55 AA */
|
||||
if (readw(image) != 0xAA55) {
|
||||
pci_info(pdev, "Invalid PCI ROM header signature: expecting 0xaa55, got %#06x\n",
|
||||
readw(image));
|
||||
if (!pci_rom_header_valid(pdev, image, rom, size, true))
|
||||
break;
|
||||
}
|
||||
/* get the PCI data structure and check its "PCIR" signature */
|
||||
pds = image + readw(image + 24);
|
||||
if (readl(pds) != 0x52494350) {
|
||||
pci_info(pdev, "Invalid PCI ROM data signature: expecting 0x52494350, got %#010x\n",
|
||||
readl(pds));
|
||||
|
||||
/* Get the PCI data structure and check its "PCIR" signature */
|
||||
pds = image + readw(image + PCI_ROM_POINTER_TO_DATA_STRUCT);
|
||||
if (!pci_rom_data_struct_valid(pdev, pds, rom, size))
|
||||
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)
|
||||
|
||||
last_image = readb(pds + PCI_ROM_LAST_IMAGE_INDICATOR) &
|
||||
PCI_ROM_LAST_IMAGE_INDICATOR_BIT;
|
||||
length = readw(pds + PCI_ROM_IMAGE_LEN);
|
||||
image += length * PCI_ROM_IMAGE_SECTOR_SIZE;
|
||||
|
||||
if (!last_image &&
|
||||
!pci_rom_header_valid(pdev, image, rom, size, false))
|
||||
break;
|
||||
if (!last_image) {
|
||||
if (readw(image) != 0xAA55) {
|
||||
pci_info(pdev, "No more image in the PCI ROM\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (length && !last_image);
|
||||
|
||||
/* never return a size larger than the PCI resource window */
|
||||
|
||||
@@ -756,13 +756,13 @@ static void __assign_resources_sorted(struct list_head *head,
|
||||
}
|
||||
|
||||
static void pdev_assign_resources_sorted(struct pci_dev *dev,
|
||||
struct list_head *add_head,
|
||||
struct list_head *add_list,
|
||||
struct list_head *fail_head)
|
||||
{
|
||||
LIST_HEAD(head);
|
||||
|
||||
pdev_sort_resources(dev, &head);
|
||||
__assign_resources_sorted(&head, add_head, fail_head);
|
||||
__assign_resources_sorted(&head, add_list, fail_head);
|
||||
|
||||
}
|
||||
|
||||
@@ -1501,14 +1501,14 @@ static void pdev_assign_fixed_resources(struct pci_dev *dev)
|
||||
}
|
||||
}
|
||||
|
||||
void __pci_bus_assign_resources(const struct pci_bus *bus,
|
||||
struct list_head *realloc_head,
|
||||
struct list_head *fail_head)
|
||||
static void __pci_bus_assign_resources_one(const struct pci_bus *bus,
|
||||
struct list_head *add_list,
|
||||
struct list_head *fail_head)
|
||||
{
|
||||
struct pci_bus *b;
|
||||
struct pci_dev *dev;
|
||||
|
||||
pbus_assign_resources_sorted(bus, realloc_head, fail_head);
|
||||
pbus_assign_resources_sorted(bus, add_list, fail_head);
|
||||
|
||||
list_for_each_entry(dev, &bus->devices, bus_list) {
|
||||
pdev_assign_fixed_resources(dev);
|
||||
@@ -1517,7 +1517,7 @@ void __pci_bus_assign_resources(const struct pci_bus *bus,
|
||||
if (!b)
|
||||
continue;
|
||||
|
||||
__pci_bus_assign_resources(b, realloc_head, fail_head);
|
||||
__pci_bus_assign_resources_one(b, add_list, fail_head);
|
||||
|
||||
switch (dev->hdr_type) {
|
||||
case PCI_HEADER_TYPE_BRIDGE:
|
||||
@@ -1537,6 +1537,16 @@ void __pci_bus_assign_resources(const struct pci_bus *bus,
|
||||
}
|
||||
}
|
||||
|
||||
void __pci_bus_assign_resources(const struct pci_bus *bus,
|
||||
struct list_head *add_list,
|
||||
struct list_head *fail_head)
|
||||
{
|
||||
__pci_bus_assign_resources_one(bus, add_list, fail_head);
|
||||
|
||||
if (WARN_ON_ONCE(add_list && !list_empty(add_list)))
|
||||
pci_dev_res_free_list(add_list);
|
||||
}
|
||||
|
||||
void pci_bus_assign_resources(const struct pci_bus *bus)
|
||||
{
|
||||
__pci_bus_assign_resources(bus, NULL, NULL);
|
||||
@@ -1612,20 +1622,19 @@ void pci_bus_claim_resources(struct pci_bus *b)
|
||||
}
|
||||
EXPORT_SYMBOL(pci_bus_claim_resources);
|
||||
|
||||
static void __pci_bridge_assign_resources(const struct pci_dev *bridge,
|
||||
struct list_head *add_head,
|
||||
static void __pci_bridge_assign_resources(struct pci_dev *bridge,
|
||||
struct list_head *add_list,
|
||||
struct list_head *fail_head)
|
||||
{
|
||||
struct pci_bus *b;
|
||||
|
||||
pdev_assign_resources_sorted((struct pci_dev *)bridge,
|
||||
add_head, fail_head);
|
||||
pdev_assign_resources_sorted(bridge, add_list, fail_head);
|
||||
|
||||
b = bridge->subordinate;
|
||||
if (!b)
|
||||
return;
|
||||
|
||||
__pci_bus_assign_resources(b, add_head, fail_head);
|
||||
__pci_bus_assign_resources(b, add_list, fail_head);
|
||||
|
||||
switch (bridge->class >> 8) {
|
||||
case PCI_CLASS_BRIDGE_PCI:
|
||||
@@ -1641,6 +1650,9 @@ static void __pci_bridge_assign_resources(const struct pci_dev *bridge,
|
||||
pci_domain_nr(b), b->number);
|
||||
break;
|
||||
}
|
||||
|
||||
if (WARN_ON_ONCE(add_list && !list_empty(add_list)))
|
||||
pci_dev_res_free_list(add_list);
|
||||
}
|
||||
|
||||
static void pci_bridge_release_resources(struct pci_bus *bus,
|
||||
@@ -2205,8 +2217,6 @@ void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus)
|
||||
|
||||
/* Depth last, allocate resources and update the hardware. */
|
||||
__pci_bus_assign_resources(bus, add_list, &fail_head);
|
||||
if (WARN_ON_ONCE(add_list && !list_empty(add_list)))
|
||||
pci_dev_res_free_list(add_list);
|
||||
tried_times++;
|
||||
|
||||
/* Any device complain? */
|
||||
@@ -2268,8 +2278,6 @@ void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge)
|
||||
pci_bridge_distribute_available_resources(bridge, &add_list);
|
||||
|
||||
__pci_bridge_assign_resources(bridge, &add_list, &fail_head);
|
||||
if (WARN_ON_ONCE(!list_empty(&add_list)))
|
||||
pci_dev_res_free_list(&add_list);
|
||||
tried_times++;
|
||||
|
||||
if (list_empty(&fail_head))
|
||||
@@ -2303,7 +2311,7 @@ static int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *
|
||||
unsigned long type = res->flags;
|
||||
struct pci_dev_resource *dev_res;
|
||||
struct pci_dev *bridge = NULL;
|
||||
LIST_HEAD(added);
|
||||
LIST_HEAD(add_list);
|
||||
LIST_HEAD(failed);
|
||||
unsigned int i;
|
||||
int ret = 0;
|
||||
@@ -2337,10 +2345,8 @@ static int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource *
|
||||
if (!bridge)
|
||||
return -ENOENT;
|
||||
|
||||
__pci_bus_size_bridges(bridge->subordinate, &added);
|
||||
__pci_bridge_assign_resources(bridge, &added, &failed);
|
||||
if (WARN_ON_ONCE(!list_empty(&added)))
|
||||
pci_dev_res_free_list(&added);
|
||||
__pci_bus_size_bridges(bridge->subordinate, &add_list);
|
||||
__pci_bridge_assign_resources(bridge, &add_list, &failed);
|
||||
|
||||
if (!list_empty(&failed)) {
|
||||
if (pci_required_resource_failed(&failed, type))
|
||||
@@ -2473,7 +2479,5 @@ void pci_assign_unassigned_bus_resources(struct pci_bus *bus)
|
||||
__pci_bus_size_bridges(dev->subordinate, &add_list);
|
||||
up_read(&pci_bus_sem);
|
||||
__pci_bus_assign_resources(bus, &add_list, NULL);
|
||||
if (WARN_ON_ONCE(!list_empty(&add_list)))
|
||||
pci_dev_res_free_list(&add_list);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pci_assign_unassigned_bus_resources);
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
static unsigned long pci_cardbus_io_size = DEFAULT_CARDBUS_IO_SIZE;
|
||||
static unsigned long pci_cardbus_mem_size = DEFAULT_CARDBUS_MEM_SIZE;
|
||||
|
||||
unsigned long pci_cardbus_resource_alignment(struct resource *res)
|
||||
unsigned long pci_cardbus_resource_alignment(const struct resource *res)
|
||||
{
|
||||
if (res->flags & IORESOURCE_IO)
|
||||
return pci_cardbus_io_size;
|
||||
@@ -253,8 +253,7 @@ int pci_cardbus_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
|
||||
* yenta.c forces a secondary latency timer of 176.
|
||||
* Copy that behaviour here.
|
||||
*/
|
||||
buses &= ~PCI_SEC_LATENCY_TIMER_MASK;
|
||||
buses |= FIELD_PREP(PCI_SEC_LATENCY_TIMER_MASK, CARDBUS_LATENCY_TIMER);
|
||||
FIELD_MODIFY(PCI_SEC_LATENCY_TIMER_MASK, &buses, CARDBUS_LATENCY_TIMER);
|
||||
|
||||
/* We need to blast all three values with a single write */
|
||||
pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses);
|
||||
|
||||
@@ -169,6 +169,8 @@ int pci_claim_resource(struct pci_dev *dev, int resource)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
pci_dbg(dev, "%s %pR: claiming\n", res_name, res);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(pci_claim_resource);
|
||||
@@ -246,6 +248,18 @@ static int pci_revert_fw_address(struct resource *res, struct pci_dev *dev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
resource_size_t pci_resource_alignment(const struct pci_dev *dev,
|
||||
const struct resource *res)
|
||||
{
|
||||
int resno = pci_resource_num(dev, res);
|
||||
|
||||
if (pci_resource_is_iov(resno))
|
||||
return pci_sriov_resource_alignment(dev, resno);
|
||||
if (dev->class >> 8 == PCI_CLASS_BRIDGE_CARDBUS)
|
||||
return pci_cardbus_resource_alignment(res);
|
||||
return resource_alignment(res);
|
||||
}
|
||||
|
||||
/*
|
||||
* For mem bridge windows, try to relocate tail remainder space to space
|
||||
* before res->start if there's enough free space there. This enables
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user