// SPDX-License-Identifier: GPL-2.0 /* * Common library for PCI host controller drivers * * Copyright (C) 2014 ARM Limited * * Author: Will Deacon */ #include #include #include #include #include #include #include #include #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");