| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Generic PCI host driver common code | |
| * | |
| * Copyright (C) 2014 ARM Limited | |
| * | |
| * Author: Will Deacon <will.deacon@arm.com> | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/of_address.h> | |
| #include <linux/of_pci.h> | |
| #include <linux/pci-ecam.h> | |
| #include <linux/platform_device.h> | |
| static void gen_pci_unmap_cfg(void *ptr) | |
| { | |
| pci_ecam_free((struct pci_config_window *)ptr); | |
| } | |
| static struct pci_config_window *gen_pci_init(struct device *dev, | |
| struct list_head *resources, struct pci_ecam_ops *ops) | |
| { | |
| int err; | |
| struct resource cfgres; | |
| struct resource *bus_range = NULL; | |
| struct pci_config_window *cfg; | |
| /* Parse our PCI ranges and request their resources */ | |
| err = pci_parse_request_of_pci_ranges(dev, resources, NULL, &bus_range); | |
| if (err) | |
| return ERR_PTR(err); | |
| err = of_address_to_resource(dev->of_node, 0, &cfgres); | |
| if (err) { | |
| dev_err(dev, "missing \"reg\" property\n"); | |
| goto err_out; | |
| } | |
| cfg = pci_ecam_create(dev, &cfgres, bus_range, ops); | |
| if (IS_ERR(cfg)) { | |
| err = PTR_ERR(cfg); | |
| goto err_out; | |
| } | |
| err = devm_add_action_or_reset(dev, gen_pci_unmap_cfg, cfg); | |
| if (err) { | |
| goto err_out; | |
| } | |
| return cfg; | |
| err_out: | |
| pci_free_resource_list(resources); | |
| return ERR_PTR(err); | |
| } | |
| int pci_host_common_probe(struct platform_device *pdev, | |
| struct pci_ecam_ops *ops) | |
| { | |
| struct device *dev = &pdev->dev; | |
| struct pci_host_bridge *bridge; | |
| struct pci_config_window *cfg; | |
| struct list_head resources; | |
| int ret; | |
| bridge = devm_pci_alloc_host_bridge(dev, 0); | |
| if (!bridge) | |
| return -ENOMEM; | |
| of_pci_check_probe_only(); | |
| /* Parse and map our Configuration Space windows */ | |
| cfg = gen_pci_init(dev, &resources, ops); | |
| if (IS_ERR(cfg)) | |
| return PTR_ERR(cfg); | |
| /* Do not reassign resources if probe only */ | |
| if (!pci_has_flag(PCI_PROBE_ONLY)) | |
| pci_add_flags(PCI_REASSIGN_ALL_BUS); | |
| list_splice_init(&resources, &bridge->windows); | |
| bridge->dev.parent = dev; | |
| bridge->sysdata = cfg; | |
| bridge->busnr = cfg->busr.start; | |
| bridge->ops = &ops->pci_ops; | |
| bridge->map_irq = of_irq_parse_and_map_pci; | |
| bridge->swizzle_irq = pci_common_swizzle; | |
| ret = pci_host_probe(bridge); | |
| if (ret < 0) { | |
| pci_free_resource_list(&resources); | |
| return ret; | |
| } | |
| platform_set_drvdata(pdev, bridge->bus); | |
| return 0; | |
| } | |
| int pci_host_common_remove(struct platform_device *pdev) | |
| { | |
| struct pci_bus *bus = platform_get_drvdata(pdev); | |
| pci_lock_rescan_remove(); | |
| pci_stop_root_bus(bus); | |
| pci_remove_root_bus(bus); | |
| pci_unlock_rescan_remove(); | |
| return 0; | |
| } |