diff options
Diffstat (limited to 'drivers/pci/controller/dwc/pcie-al.c')
| -rw-r--r-- | drivers/pci/controller/dwc/pcie-al.c | 93 | 
1 files changed, 93 insertions, 0 deletions
| diff --git a/drivers/pci/controller/dwc/pcie-al.c b/drivers/pci/controller/dwc/pcie-al.c new file mode 100644 index 000000000000..3ab58f0584a8 --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-al.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PCIe host controller driver for Amazon's Annapurna Labs IP (used in chips + * such as Graviton and Alpine) + * + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Author: Jonathan Chocron <[email protected]> + */ + +#include <linux/pci.h> +#include <linux/pci-ecam.h> +#include <linux/pci-acpi.h> +#include "../../pci.h" + +#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS) + +struct al_pcie_acpi  { +	void __iomem *dbi_base; +}; + +static void __iomem *al_pcie_map_bus(struct pci_bus *bus, unsigned int devfn, +				     int where) +{ +	struct pci_config_window *cfg = bus->sysdata; +	struct al_pcie_acpi *pcie = cfg->priv; +	void __iomem *dbi_base = pcie->dbi_base; + +	if (bus->number == cfg->busr.start) { +		/* +		 * The DW PCIe core doesn't filter out transactions to other +		 * devices/functions on the root bus num, so we do this here. +		 */ +		if (PCI_SLOT(devfn) > 0) +			return NULL; +		else +			return dbi_base + where; +	} + +	return pci_ecam_map_bus(bus, devfn, where); +} + +static int al_pcie_init(struct pci_config_window *cfg) +{ +	struct device *dev = cfg->parent; +	struct acpi_device *adev = to_acpi_device(dev); +	struct acpi_pci_root *root = acpi_driver_data(adev); +	struct al_pcie_acpi *al_pcie; +	struct resource *res; +	int ret; + +	al_pcie = devm_kzalloc(dev, sizeof(*al_pcie), GFP_KERNEL); +	if (!al_pcie) +		return -ENOMEM; + +	res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL); +	if (!res) +		return -ENOMEM; + +	ret = acpi_get_rc_resources(dev, "AMZN0001", root->segment, res); +	if (ret) { +		dev_err(dev, "can't get rc dbi base address for SEG %d\n", +			root->segment); +		return ret; +	} + +	dev_dbg(dev, "Root port dbi res: %pR\n", res); + +	al_pcie->dbi_base = devm_pci_remap_cfg_resource(dev, res); +	if (IS_ERR(al_pcie->dbi_base)) { +		long err = PTR_ERR(al_pcie->dbi_base); + +		dev_err(dev, "couldn't remap dbi base %pR (err:%ld)\n", +			res, err); +		return err; +	} + +	cfg->priv = al_pcie; + +	return 0; +} + +struct pci_ecam_ops al_pcie_ops = { +	.bus_shift    = 20, +	.init         =  al_pcie_init, +	.pci_ops      = { +		.map_bus    = al_pcie_map_bus, +		.read       = pci_generic_config_read, +		.write      = pci_generic_config_write, +	} +}; + +#endif /* defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS) */ |