blob: 2ce6f4b6613348b65f09486832dadc26c572df66 [file] [edit]
// 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");