Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e99a187b authored by Ray Jui's avatar Ray Jui Committed by Bjorn Helgaas
Browse files

PCI: iproc: Add outbound mapping support



Certain SoCs require the PCIe outbound mapping to be configured in
software.  Add support for those chips.

[jonmason: Use %pap format when printing size_t to avoid warnings in 32-bit
build.]
[arnd: Use div64_u64() instead of "%" to avoid __aeabi_uldivmod link error
in 32-bit build.]
Signed-off-by: default avatarRay Jui <rjui@broadcom.com>
Signed-off-by: default avatarJon Mason <jonmason@broadcom.com>
Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
parent 8d0afa1a
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
@@ -54,6 +54,33 @@ static int iproc_pcie_pltfm_probe(struct platform_device *pdev)
		return -ENOMEM;
	}

	if (of_property_read_bool(np, "brcm,pcie-ob")) {
		u32 val;

		ret = of_property_read_u32(np, "brcm,pcie-ob-axi-offset",
					   &val);
		if (ret) {
			dev_err(pcie->dev,
				"missing brcm,pcie-ob-axi-offset property\n");
			return ret;
		}
		pcie->ob.axi_offset = val;

		ret = of_property_read_u32(np, "brcm,pcie-ob-window-size",
					   &val);
		if (ret) {
			dev_err(pcie->dev,
				"missing brcm,pcie-ob-window-size property\n");
			return ret;
		}
		pcie->ob.window_size = (resource_size_t)val * SZ_1M;

		if (of_property_read_bool(np, "brcm,pcie-ob-oarr-size"))
			pcie->ob.set_oarr_size = true;

		pcie->need_ob_cfg = true;
	}

	/* PHY use is optional */
	pcie->phy = devm_phy_get(&pdev->dev, "pcie-phy");
	if (IS_ERR(pcie->phy)) {
+115 −0
Original line number Diff line number Diff line
@@ -66,6 +66,18 @@
#define PCIE_DL_ACTIVE_SHIFT         2
#define PCIE_DL_ACTIVE               BIT(PCIE_DL_ACTIVE_SHIFT)

#define OARR_VALID_SHIFT             0
#define OARR_VALID                   BIT(OARR_VALID_SHIFT)
#define OARR_SIZE_CFG_SHIFT          1
#define OARR_SIZE_CFG                BIT(OARR_SIZE_CFG_SHIFT)

#define OARR_LO(window)              (0xd20 + (window) * 8)
#define OARR_HI(window)              (0xd24 + (window) * 8)
#define OMAP_LO(window)              (0xd40 + (window) * 8)
#define OMAP_HI(window)              (0xd44 + (window) * 8)

#define MAX_NUM_OB_WINDOWS           2

static inline struct iproc_pcie *iproc_data(struct pci_bus *bus)
{
	struct iproc_pcie *pcie;
@@ -212,6 +224,101 @@ static void iproc_pcie_enable(struct iproc_pcie *pcie)
	writel(SYS_RC_INTX_MASK, pcie->base + SYS_RC_INTX_EN);
}

/**
 * Some iProc SoCs require the SW to configure the outbound address mapping
 *
 * Outbound address translation:
 *
 * iproc_pcie_address = axi_address - axi_offset
 * OARR = iproc_pcie_address
 * OMAP = pci_addr
 *
 * axi_addr -> iproc_pcie_address -> OARR -> OMAP -> pci_address
 */
static int iproc_pcie_setup_ob(struct iproc_pcie *pcie, u64 axi_addr,
			       u64 pci_addr, resource_size_t size)
{
	struct iproc_pcie_ob *ob = &pcie->ob;
	unsigned i;
	u64 max_size = (u64)ob->window_size * MAX_NUM_OB_WINDOWS;
	u64 remainder;

	if (size > max_size) {
		dev_err(pcie->dev,
			"res size 0x%pap exceeds max supported size 0x%llx\n",
			&size, max_size);
		return -EINVAL;
	}

	div64_u64_rem(size, ob->window_size, &remainder);
	if (remainder) {
		dev_err(pcie->dev,
			"res size %pap needs to be multiple of window size %pap\n",
			&size, &ob->window_size);
		return -EINVAL;
	}

	if (axi_addr < ob->axi_offset) {
		dev_err(pcie->dev,
			"axi address %pap less than offset %pap\n",
			&axi_addr, &ob->axi_offset);
		return -EINVAL;
	}

	/*
	 * Translate the AXI address to the internal address used by the iProc
	 * PCIe core before programming the OARR
	 */
	axi_addr -= ob->axi_offset;

	for (i = 0; i < MAX_NUM_OB_WINDOWS; i++) {
		writel(lower_32_bits(axi_addr) | OARR_VALID |
		       (ob->set_oarr_size ? 1 : 0), pcie->base + OARR_LO(i));
		writel(upper_32_bits(axi_addr), pcie->base + OARR_HI(i));
		writel(lower_32_bits(pci_addr), pcie->base + OMAP_LO(i));
		writel(upper_32_bits(pci_addr), pcie->base + OMAP_HI(i));

		size -= ob->window_size;
		if (size == 0)
			break;

		axi_addr += ob->window_size;
		pci_addr += ob->window_size;
	}

	return 0;
}

static int iproc_pcie_map_ranges(struct iproc_pcie *pcie,
				 struct list_head *resources)
{
	struct resource_entry *window;
	int ret;

	resource_list_for_each_entry(window, resources) {
		struct resource *res = window->res;
		u64 res_type = resource_type(res);

		switch (res_type) {
		case IORESOURCE_IO:
		case IORESOURCE_BUS:
			break;
		case IORESOURCE_MEM:
			ret = iproc_pcie_setup_ob(pcie, res->start,
						  res->start - window->offset,
						  resource_size(res));
			if (ret)
				return ret;
			break;
		default:
			dev_err(pcie->dev, "invalid resource %pR\n", res);
			return -EINVAL;
		}
	}

	return 0;
}

int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res)
{
	int ret;
@@ -235,6 +342,14 @@ int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res)

	iproc_pcie_reset(pcie);

	if (pcie->need_ob_cfg) {
		ret = iproc_pcie_map_ranges(pcie, res);
		if (ret) {
			dev_err(pcie->dev, "map failed\n");
			goto err_power_off_phy;
		}
	}

#ifdef CONFIG_ARM
	pcie->sysdata.private_data = pcie;
	sysdata = &pcie->sysdata;
+17 −0
Original line number Diff line number Diff line
@@ -14,6 +14,19 @@
#ifndef _PCIE_IPROC_H
#define _PCIE_IPROC_H

/**
 * iProc PCIe outbound mapping
 * @set_oarr_size: indicates the OARR size bit needs to be set
 * @axi_offset: offset from the AXI address to the internal address used by
 * the iProc PCIe core
 * @window_size: outbound window size
 */
struct iproc_pcie_ob {
	bool set_oarr_size;
	resource_size_t axi_offset;
	resource_size_t window_size;
};

/**
 * iProc PCIe device
 * @dev: pointer to device data structure
@@ -23,6 +36,8 @@
 * @phy: optional PHY device that controls the Serdes
 * @irqs: interrupt IDs
 * @map_irq: function callback to map interrupts
 * @need_ob_cfg: indidates SW needs to configure the outbound mapping window
 * @ob: outbound mapping parameters
 */
struct iproc_pcie {
	struct device *dev;
@@ -33,6 +48,8 @@ struct iproc_pcie {
	struct pci_bus *root_bus;
	struct phy *phy;
	int (*map_irq)(const struct pci_dev *, u8, u8);
	bool need_ob_cfg;
	struct iproc_pcie_ob ob;
};

int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res);