162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2020-2022 Loongson Technology Corporation Limited 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci#include <linux/pci.h> 662306a36Sopenharmony_ci#include <linux/acpi.h> 762306a36Sopenharmony_ci#include <linux/init.h> 862306a36Sopenharmony_ci#include <linux/irq.h> 962306a36Sopenharmony_ci#include <linux/slab.h> 1062306a36Sopenharmony_ci#include <linux/pci-acpi.h> 1162306a36Sopenharmony_ci#include <linux/pci-ecam.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <asm/pci.h> 1462306a36Sopenharmony_ci#include <asm/numa.h> 1562306a36Sopenharmony_ci#include <asm/loongson.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistruct pci_root_info { 1862306a36Sopenharmony_ci struct acpi_pci_root_info common; 1962306a36Sopenharmony_ci struct pci_config_window *cfg; 2062306a36Sopenharmony_ci}; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_civoid pcibios_add_bus(struct pci_bus *bus) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci acpi_pci_add_bus(bus); 2562306a36Sopenharmony_ci} 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ciint pcibios_root_bridge_prepare(struct pci_host_bridge *bridge) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci struct acpi_device *adev = NULL; 3062306a36Sopenharmony_ci struct device *bus_dev = &bridge->bus->dev; 3162306a36Sopenharmony_ci struct pci_config_window *cfg = bridge->bus->sysdata; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci if (!acpi_disabled) 3462306a36Sopenharmony_ci adev = to_acpi_device(cfg->parent); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci ACPI_COMPANION_SET(&bridge->dev, adev); 3762306a36Sopenharmony_ci set_dev_node(bus_dev, pa_to_nid(cfg->res.start)); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci return 0; 4062306a36Sopenharmony_ci} 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ciint acpi_pci_bus_find_domain_nr(struct pci_bus *bus) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci struct pci_config_window *cfg = bus->sysdata; 4562306a36Sopenharmony_ci struct acpi_device *adev = to_acpi_device(cfg->parent); 4662306a36Sopenharmony_ci struct acpi_pci_root *root = acpi_driver_data(adev); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci return root->segment; 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic void acpi_release_root_info(struct acpi_pci_root_info *ci) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci struct pci_root_info *info; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci info = container_of(ci, struct pci_root_info, common); 5662306a36Sopenharmony_ci pci_ecam_free(info->cfg); 5762306a36Sopenharmony_ci kfree(ci->ops); 5862306a36Sopenharmony_ci kfree(info); 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic int acpi_prepare_root_resources(struct acpi_pci_root_info *ci) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci int status; 6462306a36Sopenharmony_ci struct resource_entry *entry, *tmp; 6562306a36Sopenharmony_ci struct acpi_device *device = ci->bridge; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci status = acpi_pci_probe_root_resources(ci); 6862306a36Sopenharmony_ci if (status > 0) { 6962306a36Sopenharmony_ci resource_list_for_each_entry_safe(entry, tmp, &ci->resources) { 7062306a36Sopenharmony_ci if (entry->res->flags & IORESOURCE_MEM) { 7162306a36Sopenharmony_ci entry->offset = ci->root->mcfg_addr & GENMASK_ULL(63, 40); 7262306a36Sopenharmony_ci entry->res->start |= entry->offset; 7362306a36Sopenharmony_ci entry->res->end |= entry->offset; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci } 7662306a36Sopenharmony_ci return status; 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci resource_list_for_each_entry_safe(entry, tmp, &ci->resources) { 8062306a36Sopenharmony_ci dev_dbg(&device->dev, 8162306a36Sopenharmony_ci "host bridge window %pR (ignored)\n", entry->res); 8262306a36Sopenharmony_ci resource_list_destroy_entry(entry); 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci/* 8962306a36Sopenharmony_ci * Create a PCI config space window 9062306a36Sopenharmony_ci * - reserve mem region 9162306a36Sopenharmony_ci * - alloc struct pci_config_window with space for all mappings 9262306a36Sopenharmony_ci * - ioremap the config space 9362306a36Sopenharmony_ci */ 9462306a36Sopenharmony_cistatic struct pci_config_window *arch_pci_ecam_create(struct device *dev, 9562306a36Sopenharmony_ci struct resource *cfgres, struct resource *busr, const struct pci_ecam_ops *ops) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci int bsz, bus_range, err; 9862306a36Sopenharmony_ci struct resource *conflict; 9962306a36Sopenharmony_ci struct pci_config_window *cfg; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci if (busr->start > busr->end) 10262306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); 10562306a36Sopenharmony_ci if (!cfg) 10662306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci cfg->parent = dev; 10962306a36Sopenharmony_ci cfg->ops = ops; 11062306a36Sopenharmony_ci cfg->busr.start = busr->start; 11162306a36Sopenharmony_ci cfg->busr.end = busr->end; 11262306a36Sopenharmony_ci cfg->busr.flags = IORESOURCE_BUS; 11362306a36Sopenharmony_ci bus_range = resource_size(cfgres) >> ops->bus_shift; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci bsz = 1 << ops->bus_shift; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci cfg->res.start = cfgres->start; 11862306a36Sopenharmony_ci cfg->res.end = cfgres->end; 11962306a36Sopenharmony_ci cfg->res.flags = IORESOURCE_MEM | IORESOURCE_BUSY; 12062306a36Sopenharmony_ci cfg->res.name = "PCI ECAM"; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci conflict = request_resource_conflict(&iomem_resource, &cfg->res); 12362306a36Sopenharmony_ci if (conflict) { 12462306a36Sopenharmony_ci err = -EBUSY; 12562306a36Sopenharmony_ci dev_err(dev, "can't claim ECAM area %pR: address conflict with %s %pR\n", 12662306a36Sopenharmony_ci &cfg->res, conflict->name, conflict); 12762306a36Sopenharmony_ci goto err_exit; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci cfg->win = pci_remap_cfgspace(cfgres->start, bus_range * bsz); 13162306a36Sopenharmony_ci if (!cfg->win) 13262306a36Sopenharmony_ci goto err_exit_iomap; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if (ops->init) { 13562306a36Sopenharmony_ci err = ops->init(cfg); 13662306a36Sopenharmony_ci if (err) 13762306a36Sopenharmony_ci goto err_exit; 13862306a36Sopenharmony_ci } 13962306a36Sopenharmony_ci dev_info(dev, "ECAM at %pR for %pR\n", &cfg->res, &cfg->busr); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci return cfg; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cierr_exit_iomap: 14462306a36Sopenharmony_ci err = -ENOMEM; 14562306a36Sopenharmony_ci dev_err(dev, "ECAM ioremap failed\n"); 14662306a36Sopenharmony_cierr_exit: 14762306a36Sopenharmony_ci pci_ecam_free(cfg); 14862306a36Sopenharmony_ci return ERR_PTR(err); 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci/* 15262306a36Sopenharmony_ci * Lookup the bus range for the domain in MCFG, and set up config space 15362306a36Sopenharmony_ci * mapping. 15462306a36Sopenharmony_ci */ 15562306a36Sopenharmony_cistatic struct pci_config_window * 15662306a36Sopenharmony_cipci_acpi_setup_ecam_mapping(struct acpi_pci_root *root) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci int ret, bus_shift; 15962306a36Sopenharmony_ci u16 seg = root->segment; 16062306a36Sopenharmony_ci struct device *dev = &root->device->dev; 16162306a36Sopenharmony_ci struct resource cfgres; 16262306a36Sopenharmony_ci struct resource *bus_res = &root->secondary; 16362306a36Sopenharmony_ci struct pci_config_window *cfg; 16462306a36Sopenharmony_ci const struct pci_ecam_ops *ecam_ops; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci ret = pci_mcfg_lookup(root, &cfgres, &ecam_ops); 16762306a36Sopenharmony_ci if (ret < 0) { 16862306a36Sopenharmony_ci dev_err(dev, "%04x:%pR ECAM region not found, use default value\n", seg, bus_res); 16962306a36Sopenharmony_ci ecam_ops = &loongson_pci_ecam_ops; 17062306a36Sopenharmony_ci root->mcfg_addr = mcfg_addr_init(0); 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci bus_shift = ecam_ops->bus_shift ? : 20; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci if (bus_shift == 20) 17662306a36Sopenharmony_ci cfg = pci_ecam_create(dev, &cfgres, bus_res, ecam_ops); 17762306a36Sopenharmony_ci else { 17862306a36Sopenharmony_ci cfgres.start = root->mcfg_addr + (bus_res->start << bus_shift); 17962306a36Sopenharmony_ci cfgres.end = cfgres.start + (resource_size(bus_res) << bus_shift) - 1; 18062306a36Sopenharmony_ci cfgres.end |= BIT(28) + (((PCI_CFG_SPACE_EXP_SIZE - 1) & 0xf00) << 16); 18162306a36Sopenharmony_ci cfgres.flags = IORESOURCE_MEM; 18262306a36Sopenharmony_ci cfg = arch_pci_ecam_create(dev, &cfgres, bus_res, ecam_ops); 18362306a36Sopenharmony_ci } 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci if (IS_ERR(cfg)) { 18662306a36Sopenharmony_ci dev_err(dev, "%04x:%pR error %ld mapping ECAM\n", seg, bus_res, PTR_ERR(cfg)); 18762306a36Sopenharmony_ci return NULL; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci return cfg; 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistruct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci struct pci_bus *bus; 19662306a36Sopenharmony_ci struct pci_root_info *info; 19762306a36Sopenharmony_ci struct acpi_pci_root_ops *root_ops; 19862306a36Sopenharmony_ci int domain = root->segment; 19962306a36Sopenharmony_ci int busnum = root->secondary.start; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci info = kzalloc(sizeof(*info), GFP_KERNEL); 20262306a36Sopenharmony_ci if (!info) { 20362306a36Sopenharmony_ci pr_warn("pci_bus %04x:%02x: ignored (out of memory)\n", domain, busnum); 20462306a36Sopenharmony_ci return NULL; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci root_ops = kzalloc(sizeof(*root_ops), GFP_KERNEL); 20862306a36Sopenharmony_ci if (!root_ops) { 20962306a36Sopenharmony_ci kfree(info); 21062306a36Sopenharmony_ci return NULL; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci info->cfg = pci_acpi_setup_ecam_mapping(root); 21462306a36Sopenharmony_ci if (!info->cfg) { 21562306a36Sopenharmony_ci kfree(info); 21662306a36Sopenharmony_ci kfree(root_ops); 21762306a36Sopenharmony_ci return NULL; 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci root_ops->release_info = acpi_release_root_info; 22162306a36Sopenharmony_ci root_ops->prepare_resources = acpi_prepare_root_resources; 22262306a36Sopenharmony_ci root_ops->pci_ops = (struct pci_ops *)&info->cfg->ops->pci_ops; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci bus = pci_find_bus(domain, busnum); 22562306a36Sopenharmony_ci if (bus) { 22662306a36Sopenharmony_ci memcpy(bus->sysdata, info->cfg, sizeof(struct pci_config_window)); 22762306a36Sopenharmony_ci kfree(info); 22862306a36Sopenharmony_ci } else { 22962306a36Sopenharmony_ci struct pci_bus *child; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci bus = acpi_pci_root_create(root, root_ops, 23262306a36Sopenharmony_ci &info->common, info->cfg); 23362306a36Sopenharmony_ci if (!bus) { 23462306a36Sopenharmony_ci kfree(info); 23562306a36Sopenharmony_ci kfree(root_ops); 23662306a36Sopenharmony_ci return NULL; 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci pci_bus_size_bridges(bus); 24062306a36Sopenharmony_ci pci_bus_assign_resources(bus); 24162306a36Sopenharmony_ci list_for_each_entry(child, &bus->children, node) 24262306a36Sopenharmony_ci pcie_bus_configure_settings(child); 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci return bus; 24662306a36Sopenharmony_ci} 247