| // 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.h> | |
| #include <linux/pci-ecam.h> | |
| #include <linux/platform_device.h> | |
| #include "../pci.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, 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; | |
| 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 or ports not found, negative error codes on failure. | |
| */ | |
| int pci_host_common_parse_ports(struct device *dev, struct pci_host_bridge *bridge) | |
| { | |
| int ret = 0; | |
| 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 (list_empty(&bridge->ports)) | |
| return 0; | |
| 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); | |
| static pci_ers_result_t pci_host_reset_root_port(struct pci_dev *dev) | |
| { | |
| int ret; | |
| pci_lock_rescan_remove(); | |
| ret = pci_bus_error_reset(dev); | |
| pci_unlock_rescan_remove(); | |
| if (ret) { | |
| pci_err(dev, "Failed to reset Root Port: %d\n", ret); | |
| return PCI_ERS_RESULT_DISCONNECT; | |
| } | |
| pci_info(dev, "Root Port has been reset\n"); | |
| return PCI_ERS_RESULT_RECOVERED; | |
| } | |
| static void pci_host_recover_root_port(struct pci_dev *port) | |
| { | |
| #if IS_ENABLED(CONFIG_PCIEAER) | |
| pcie_do_recovery(port, pci_channel_io_frozen, pci_host_reset_root_port); | |
| #else | |
| pci_host_reset_root_port(port); | |
| #endif | |
| } | |
| void pci_host_handle_link_down(struct pci_dev *port) | |
| { | |
| pci_info(port, "Recovering Root Port due to Link Down\n"); | |
| pci_host_recover_root_port(port); | |
| } | |
| EXPORT_SYMBOL_GPL(pci_host_handle_link_down); | |
| MODULE_DESCRIPTION("Common library for PCI host controller drivers"); | |
| MODULE_LICENSE("GPL v2"); |