diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c index d6258c1cffe5..9bf66c256f81 100644 --- a/drivers/pci/controller/pci-host-common.c +++ b/drivers/pci/controller/pci-host-common.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,174 @@ #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); + static void gen_pci_unmap_cfg(void *ptr) { pci_ecam_free((struct pci_config_window *)ptr); diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h index b5075d4bd7eb..0f5fcc02e778 100644 --- a/drivers/pci/controller/pci-host-common.h +++ b/drivers/pci/controller/pci-host-common.h @@ -12,6 +12,34 @@ 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, diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index b63cd0c310bc..6094b6c1fc90 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -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 diff --git a/include/linux/pci.h b/include/linux/pci.h index 2c4454583c11..cb5f3e7e8e48 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -636,6 +636,7 @@ struct pci_host_bridge { int domain_nr; struct list_head windows; /* resource_entry */ struct list_head dma_ranges; /* dma ranges resource list */ + struct list_head ports; /* Root Port list (pci_host_port) */ #ifdef CONFIG_PCI_IDE u16 nr_ide_streams; /* Max streams possibly active in @ide_stream_ida */ struct ida ide_stream_ida;