mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
- 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 a .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) - Add pcie_encode_t_power_on() to encode L1SS T_POWER_ON fields (Krishna Chaitanya Chundru) - Add dw_pcie_program_t_power_on() to program T_POWER_ON (Krishna Chaitanya Chundru) - Program qcom 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) * pci/controller/dwc-qcom: PCI: qcom: Initialize DWC MSI lock for firmware-managed ECAM hosts PCI: qcom: Disable ASPM L0s for SA8775P PCI: qcom: Program T_POWER_ON PCI: dwc: Add dw_pcie_program_t_power_on() to program T_POWER_ON PCI/ASPM: Add pcie_encode_t_power_on() helper to encode L1SS T_POWER_ON fields PCI: qcom: Handle mixed PERST#/PHY DT configuration PCI: qcom: Add D3cold support PCI: dwc: Use common D3cold eligibility helper in suspend path PCI: qcom: Power down PHY via PARF_PHY_CTRL before disabling rails/clocks PCI: qcom: Add .get_ltssm() callback to query LTSSM status PCI: host-common: Add pci_host_common_d3cold_possible() helper PCI: qcom: Set max OPP before DBI access during resume # Conflicts: # drivers/pci/controller/pci-host-common.c
347 lines
9.1 KiB
C
347 lines
9.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Common library for PCI host controller drivers
|
|
*
|
|
* Copyright (C) 2014 ARM Limited
|
|
*
|
|
* Author: Will Deacon <will.deacon@arm.com>
|
|
*/
|
|
|
|
#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>
|
|
#include <linux/pci-ecam.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#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);
|
|
}
|
|
|
|
struct pci_config_window *pci_host_common_ecam_create(struct device *dev,
|
|
struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops)
|
|
{
|
|
int err;
|
|
struct resource cfgres;
|
|
struct resource_entry *bus;
|
|
struct pci_config_window *cfg;
|
|
|
|
err = of_address_to_resource(dev->of_node, 0, &cfgres);
|
|
if (err) {
|
|
dev_err(dev, "missing or malformed \"reg\" property\n");
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
bus = resource_list_first_type(&bridge->windows, IORESOURCE_BUS);
|
|
if (!bus)
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
cfg = pci_ecam_create(dev, &cfgres, bus->res, ops);
|
|
if (IS_ERR(cfg))
|
|
return cfg;
|
|
|
|
err = devm_add_action_or_reset(dev, gen_pci_unmap_cfg, cfg);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
|
|
return cfg;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pci_host_common_ecam_create);
|
|
|
|
int pci_host_common_init(struct platform_device *pdev,
|
|
struct pci_host_bridge *bridge,
|
|
const struct pci_ecam_ops *ops)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct pci_config_window *cfg;
|
|
|
|
of_pci_check_probe_only();
|
|
|
|
platform_set_drvdata(pdev, bridge);
|
|
|
|
/* Parse and map our Configuration Space windows */
|
|
cfg = pci_host_common_ecam_create(dev, bridge, ops);
|
|
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;
|
|
bridge->disable_device = ops->disable_device;
|
|
bridge->msi_domain = true;
|
|
|
|
return pci_host_probe(bridge);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pci_host_common_init);
|
|
|
|
int pci_host_common_probe(struct platform_device *pdev)
|
|
{
|
|
const struct pci_ecam_ops *ops;
|
|
struct pci_host_bridge *bridge;
|
|
|
|
ops = of_device_get_match_data(&pdev->dev);
|
|
if (!ops)
|
|
return -ENODEV;
|
|
|
|
bridge = devm_pci_alloc_host_bridge(&pdev->dev, 0);
|
|
if (!bridge)
|
|
return -ENOMEM;
|
|
|
|
return pci_host_common_init(pdev, bridge, ops);
|
|
}
|
|
EXPORT_SYMBOL_GPL(pci_host_common_probe);
|
|
|
|
void pci_host_common_remove(struct platform_device *pdev)
|
|
{
|
|
struct pci_host_bridge *bridge = platform_get_drvdata(pdev);
|
|
|
|
pci_lock_rescan_remove();
|
|
pci_stop_root_bus(bridge->bus);
|
|
pci_remove_root_bus(bridge->bus);
|
|
pci_unlock_rescan_remove();
|
|
}
|
|
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");
|