| // 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; |
| struct resource_entry *entry, *tmp; |
| struct acpi_device *device = ci->bridge; |
| |
| status = acpi_pci_probe_root_resources(ci); |
| if (status > 0) { |
| 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(sizeof(*cfg), GFP_KERNEL); |
| 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 acpi_pci_root_ops *root_ops; |
| int domain = root->segment; |
| int busnum = root->secondary.start; |
| |
| info = kzalloc(sizeof(*info), GFP_KERNEL); |
| if (!info) { |
| pr_warn("pci_bus %04x:%02x: ignored (out of memory)\n", domain, busnum); |
| return NULL; |
| } |
| |
| root_ops = kzalloc(sizeof(*root_ops), GFP_KERNEL); |
| 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); |
| } 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; |
| } |
| |
| pci_bus_size_bridges(bus); |
| pci_bus_assign_resources(bus); |
| list_for_each_entry(child, &bus->children, node) |
| pcie_bus_configure_settings(child); |
| } |
| |
| return bus; |
| } |