| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2020-2022 Loongson Technology Corporation Limited | |
| */ | |
| #include <linux/pci.h> | |
| #include <linux/acpi.h> | |
| #include <linux/init.h> | |
| #include <linux/irq.h> | |
| #include <linux/slab.h> | |
| #include <linux/pci-acpi.h> | |
| #include <linux/pci-ecam.h> | |
| #include <asm/pci.h> | |
| #include <asm/numa.h> | |
| #include <asm/loongson.h> | |
| struct pci_root_info { | |
| struct acpi_pci_root_info common; | |
| struct pci_config_window *cfg; | |
| }; | |
| void pcibios_add_bus(struct pci_bus *bus) | |
| { | |
| acpi_pci_add_bus(bus); | |
| } | |
| int pcibios_root_bridge_prepare(struct pci_host_bridge *bridge) | |
| { | |
| struct acpi_device *adev = NULL; | |
| struct device *bus_dev = &bridge->bus->dev; | |
| struct pci_config_window *cfg = bridge->bus->sysdata; | |
| if (!acpi_disabled) | |
| adev = to_acpi_device(cfg->parent); | |
| ACPI_COMPANION_SET(&bridge->dev, adev); | |
| set_dev_node(bus_dev, pa_to_nid(cfg->res.start)); | |
| return 0; | |
| } | |
| int acpi_pci_bus_find_domain_nr(struct pci_bus *bus) | |
| { | |
| struct pci_config_window *cfg = bus->sysdata; | |
| struct acpi_device *adev = to_acpi_device(cfg->parent); | |
| struct acpi_pci_root *root = acpi_driver_data(adev); | |
| return root->segment; | |
| } | |
| static void acpi_release_root_info(struct acpi_pci_root_info *ci) | |
| { | |
| struct pci_root_info *info; | |
| info = container_of(ci, struct pci_root_info, common); | |
| pci_ecam_free(info->cfg); | |
| kfree(ci->ops); | |
| kfree(info); | |
| } | |
| static int acpi_prepare_root_resources(struct acpi_pci_root_info *ci) | |
| { | |
| int status; | |
| unsigned long long pci_h = 0; | |
| struct resource_entry *entry, *tmp; | |
| struct acpi_device *device = ci->bridge; | |
| acpi_remove_early_pio(); | |
| status = acpi_pci_probe_root_resources(ci); | |
| if (status > 0) { | |
| acpi_evaluate_integer(device->handle, "PCIH", NULL, &pci_h); | |
| if (pci_h) | |
| return status; | |
| resource_list_for_each_entry_safe(entry, tmp, &ci->resources) { | |
| if (entry->res->flags & IORESOURCE_MEM) { | |
| entry->offset = ci->root->mcfg_addr & GENMASK_ULL(63, 40); | |
| entry->res->start |= entry->offset; | |
| entry->res->end |= entry->offset; | |
| } | |
| } | |
| return status; | |
| } | |
| resource_list_for_each_entry_safe(entry, tmp, &ci->resources) { | |
| dev_dbg(&device->dev, | |
| "host bridge window %pR (ignored)\n", entry->res); | |
| resource_list_destroy_entry(entry); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Create a PCI config space window | |
| * - reserve mem region | |
| * - alloc struct pci_config_window with space for all mappings | |
| * - ioremap the config space | |
| */ | |
| static struct pci_config_window *arch_pci_ecam_create(struct device *dev, | |
| struct resource *cfgres, struct resource *busr, const struct pci_ecam_ops *ops) | |
| { | |
| int bsz, bus_range, err; | |
| struct resource *conflict; | |
| struct pci_config_window *cfg; | |
| if (busr->start > busr->end) | |
| return ERR_PTR(-EINVAL); | |
| cfg = kzalloc_obj(*cfg); | |
| if (!cfg) | |
| return ERR_PTR(-ENOMEM); | |
| cfg->parent = dev; | |
| cfg->ops = ops; | |
| cfg->busr.start = busr->start; | |
| cfg->busr.end = busr->end; | |
| cfg->busr.flags = IORESOURCE_BUS; | |
| bus_range = resource_size(cfgres) >> ops->bus_shift; | |
| bsz = 1 << ops->bus_shift; | |
| cfg->res.start = cfgres->start; | |
| cfg->res.end = cfgres->end; | |
| cfg->res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; | |
| cfg->res.name = "PCI ECAM"; | |
| conflict = request_resource_conflict(&iomem_resource, &cfg->res); | |
| if (conflict) { | |
| err = -EBUSY; | |
| dev_err(dev, "can't claim ECAM area %pR: address conflict with %s %pR\n", | |
| &cfg->res, conflict->name, conflict); | |
| goto err_exit; | |
| } | |
| cfg->win = pci_remap_cfgspace(cfgres->start, bus_range * bsz); | |
| if (!cfg->win) | |
| goto err_exit_iomap; | |
| if (ops->init) { | |
| err = ops->init(cfg); | |
| if (err) | |
| goto err_exit; | |
| } | |
| dev_info(dev, "ECAM at %pR for %pR\n", &cfg->res, &cfg->busr); | |
| return cfg; | |
| err_exit_iomap: | |
| err = -ENOMEM; | |
| dev_err(dev, "ECAM ioremap failed\n"); | |
| err_exit: | |
| pci_ecam_free(cfg); | |
| return ERR_PTR(err); | |
| } | |
| /* | |
| * Lookup the bus range for the domain in MCFG, and set up config space | |
| * mapping. | |
| */ | |
| static struct pci_config_window * | |
| pci_acpi_setup_ecam_mapping(struct acpi_pci_root *root) | |
| { | |
| int ret, bus_shift; | |
| u16 seg = root->segment; | |
| struct device *dev = &root->device->dev; | |
| struct resource cfgres; | |
| struct resource *bus_res = &root->secondary; | |
| struct pci_config_window *cfg; | |
| const struct pci_ecam_ops *ecam_ops; | |
| ret = pci_mcfg_lookup(root, &cfgres, &ecam_ops); | |
| if (ret < 0) { | |
| dev_err(dev, "%04x:%pR ECAM region not found, use default value\n", seg, bus_res); | |
| ecam_ops = &loongson_pci_ecam_ops; | |
| root->mcfg_addr = mcfg_addr_init(0); | |
| } | |
| bus_shift = ecam_ops->bus_shift ? : 20; | |
| if (bus_shift == 20) | |
| cfg = pci_ecam_create(dev, &cfgres, bus_res, ecam_ops); | |
| else { | |
| cfgres.start = root->mcfg_addr + (bus_res->start << bus_shift); | |
| cfgres.end = cfgres.start + (resource_size(bus_res) << bus_shift) - 1; | |
| cfgres.end |= BIT(28) + (((PCI_CFG_SPACE_EXP_SIZE - 1) & 0xf00) << 16); | |
| cfgres.flags = IORESOURCE_MEM; | |
| cfg = arch_pci_ecam_create(dev, &cfgres, bus_res, ecam_ops); | |
| } | |
| if (IS_ERR(cfg)) { | |
| dev_err(dev, "%04x:%pR error %ld mapping ECAM\n", seg, bus_res, PTR_ERR(cfg)); | |
| return NULL; | |
| } | |
| return cfg; | |
| } | |
| struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) | |
| { | |
| struct pci_bus *bus; | |
| struct pci_root_info *info; | |
| struct pci_host_bridge *host; | |
| struct acpi_pci_root_ops *root_ops; | |
| int domain = root->segment; | |
| int busnum = root->secondary.start; | |
| info = kzalloc_obj(*info); | |
| if (!info) { | |
| pr_warn("pci_bus %04x:%02x: ignored (out of memory)\n", domain, busnum); | |
| return NULL; | |
| } | |
| root_ops = kzalloc_obj(*root_ops); | |
| if (!root_ops) { | |
| kfree(info); | |
| return NULL; | |
| } | |
| info->cfg = pci_acpi_setup_ecam_mapping(root); | |
| if (!info->cfg) { | |
| kfree(info); | |
| kfree(root_ops); | |
| return NULL; | |
| } | |
| root_ops->release_info = acpi_release_root_info; | |
| root_ops->prepare_resources = acpi_prepare_root_resources; | |
| root_ops->pci_ops = (struct pci_ops *)&info->cfg->ops->pci_ops; | |
| bus = pci_find_bus(domain, busnum); | |
| if (bus) { | |
| memcpy(bus->sysdata, info->cfg, sizeof(struct pci_config_window)); | |
| kfree(info); | |
| kfree(root_ops); | |
| } else { | |
| struct pci_bus *child; | |
| bus = acpi_pci_root_create(root, root_ops, | |
| &info->common, info->cfg); | |
| if (!bus) { | |
| kfree(info); | |
| kfree(root_ops); | |
| return NULL; | |
| } | |
| /* If we must preserve the resource configuration, claim now */ | |
| host = pci_find_host_bridge(bus); | |
| if (host->preserve_config) | |
| pci_bus_claim_resources(bus); | |
| /* | |
| * Assign whatever was left unassigned. If we didn't claim above, | |
| * this will reassign everything. | |
| */ | |
| pci_assign_unassigned_root_bus_resources(bus); | |
| list_for_each_entry(child, &bus->children, node) | |
| pcie_bus_configure_settings(child); | |
| } | |
| return bus; | |
| } |