Skip to content

Commit

Permalink
PCI: microchip: Partition inbound address translation
Browse files Browse the repository at this point in the history
On Microchip PolarFire SoC the PCIe rootport is behind a set of fabric
inter connect (fic) busses that encapsulate busses like ABP/AHP, AXI-S
and AXI-M. Depending on which fic(s) the rootport is wired through to
cpu space, the rootport driver needs to take account of the address
translation done by a parent (e.g. fabric) node before setting up its
own inbound address translation tables from attached devices.

Parse the dma-range properties to determine how much address translation
to perform in the root port and how much is being provided by the
fabric.

Signed-off-by: Daire McNamara <daire.mcnamara@microchip.com>
Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
  • Loading branch information
Daire McNamara authored and intel-lab-lkp committed Nov 16, 2022
1 parent 08e387d commit 41006bd
Showing 1 changed file with 178 additions and 6 deletions.
184 changes: 178 additions & 6 deletions drivers/pci/controller/pcie-microchip-host.c
Expand Up @@ -22,6 +22,9 @@
/* Number of MSI IRQs */
#define MC_MAX_NUM_MSI_IRQS 32

#define MC_MAX_NUM_INBOUND_WINDOWS 8
#define MC_ATT_MASK GENMASK(63, 31)

/* PCIe Bridge Phy and Controller Phy offsets */
#define MC_PCIE1_BRIDGE_ADDR 0x00008000u
#define MC_PCIE1_CTRL_ADDR 0x0000a000u
Expand Down Expand Up @@ -86,10 +89,13 @@
#define ISTATUS_MSI 0x194

#define ATR_WINDOW_DESC_SIZE 32
#define ATR_PCIE_ATR_SIZE 0x25
#define ATR_SIZE_SHIFT 1
#define ATR_IMPL_ENABLE 1

#define ATR_PCIE_WIN0_SRCADDR 0x80000000
#define ATR_PCIE_ATR_SIZE (512 * 1024 * 1024ul)
#define ATR_PCIE_NUM_WINDOWS 8

/* PCIe Master table init defines */
#define ATR0_PCIE_WIN0_SRCADDR_PARAM 0x600u
#define ATR0_PCIE_WIN0_SRC_ADDR 0x604u
Expand Down Expand Up @@ -276,6 +282,12 @@ struct mc_msi {
DECLARE_BITMAP(used, MC_MAX_NUM_MSI_IRQS);
};

struct inbound_windows {
u64 axi_addr;
u64 pci_addr;
u64 size;
};

struct mc_pcie {
void __iomem *axi_base_addr;
struct device *dev;
Expand All @@ -284,6 +296,8 @@ struct mc_pcie {
raw_spinlock_t lock;
struct mc_msi msi;
u64 outbound_range_offset;
u32 num_inbound_windows;
struct inbound_windows inbound_windows[MC_MAX_NUM_INBOUND_WINDOWS];
};

struct cause {
Expand Down Expand Up @@ -940,6 +954,46 @@ static int mc_pcie_init_irq_domains(struct mc_pcie *port)
return mc_allocate_msi_domains(port);
}

static int mc_pcie_setup_inbound_ranges(struct platform_device *pdev, struct mc_pcie *port)
{
void __iomem *bridge_base_addr = port->axi_base_addr + MC_PCIE_BRIDGE_ADDR;
phys_addr_t pcie_addr;
phys_addr_t axi_addr;
u32 atr_size;
u32 val;
int i;

for (i = 0; i < port->num_inbound_windows; i++) {
atr_size = ilog2(port->inbound_windows[i].size) - 1;
atr_size &= GENMASK(5, 0);

pcie_addr = port->inbound_windows[i].pci_addr;

val = lower_32_bits(pcie_addr) & GENMASK(31, 12);
val |= (atr_size << ATR_SIZE_SHIFT);
val |= ATR_IMPL_ENABLE;
writel(val, bridge_base_addr +
ATR0_PCIE_WIN0_SRCADDR_PARAM + (i * ATR_WINDOW_DESC_SIZE));
writel(upper_32_bits(pcie_addr), bridge_base_addr +
ATR0_PCIE_WIN0_SRC_ADDR + (i * ATR_WINDOW_DESC_SIZE));

axi_addr = port->inbound_windows[i].axi_addr;

writel(lower_32_bits(axi_addr), bridge_base_addr +
ATR0_PCIE_WIN0_TRSL_ADDR_LSB + (i * ATR_WINDOW_DESC_SIZE));
writel(upper_32_bits(axi_addr), bridge_base_addr +
ATR0_PCIE_WIN0_TRSL_ADDR_UDW + (i * ATR_WINDOW_DESC_SIZE));

writel(TRSL_ID_AXI4_MASTER_0, bridge_base_addr +
ATR0_PCIE_WIN0_TRSL_PARAM + (i * ATR_WINDOW_DESC_SIZE));

dev_dbg(&pdev->dev, "0x%010llx..0x%010llx -> 0x%010llx\n",
pcie_addr, pcie_addr + port->inbound_windows[i].size - 1, axi_addr);
}

return 0;
}

static void mc_pcie_setup_window(void __iomem *bridge_base_addr, u32 index,
phys_addr_t axi_addr, phys_addr_t pci_addr,
size_t size)
Expand Down Expand Up @@ -971,11 +1025,6 @@ static void mc_pcie_setup_window(void __iomem *bridge_base_addr, u32 index,
val = upper_32_bits(pci_addr);
writel(val, bridge_base_addr + (index * ATR_WINDOW_DESC_SIZE) +
ATR0_AXI4_SLV0_TRSL_ADDR_UDW);

val = readl(bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM);
val |= (ATR_PCIE_ATR_SIZE << ATR_SIZE_SHIFT);
writel(val, bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM);
writel(0, bridge_base_addr + ATR0_PCIE_WIN0_SRC_ADDR);
}

static int mc_pcie_setup_windows(struct platform_device *pdev,
Expand Down Expand Up @@ -1147,6 +1196,119 @@ static int mc_check_for_parent_range_handling(struct platform_device *pdev, stru
return 0;
}

static int mc_check_for_parent_dma_range_handling(struct platform_device *pdev,
struct mc_pcie *port)
{
struct device *dev = &pdev->dev;
struct device_node *dn = dev->of_node;
struct of_range_parser parser;
struct of_range range;
int num_parent_ranges = 0;
int num_ranges = 0;
struct inbound_windows ranges[MC_MAX_NUM_INBOUND_WINDOWS] = { 0 };
u64 start_axi = GENMASK(63, 0);
u64 end_axi = 0;
u64 start_pci = GENMASK(63, 0);
u64 end_pci = 0;
s64 size;
u64 window_size;
int i;

/* find all dma-ranges */
if (of_pci_dma_range_parser_init(&parser, dn)) {
dev_err(dev, "missing dma-ranges property\n");
return -EINVAL;
}

for_each_of_range(&parser, &range) {
if (num_ranges > MC_MAX_NUM_INBOUND_WINDOWS) {
dev_err(dev, "too many inbound ranges; %d available tables\n",
MC_MAX_NUM_INBOUND_WINDOWS);
return -EINVAL;
}
ranges[num_ranges].axi_addr = range.cpu_addr;
ranges[num_ranges].pci_addr = range.pci_addr;
ranges[num_ranges].size = range.size;

num_ranges++;
}

/*
* check for one level up; will need to adjust
* address translation tables for these
*/
dn = of_get_parent(dn);
if (dn) {
of_pci_dma_range_parser_init(&parser, dn);

for_each_of_range(&parser, &range) {
if (num_parent_ranges > MC_MAX_NUM_INBOUND_WINDOWS) {
dev_err(dev, "too many parent inbound ranges; %d available tables\n",
MC_MAX_NUM_INBOUND_WINDOWS);
return -EINVAL;
}
ranges[num_parent_ranges].axi_addr = range.pci_addr;
num_parent_ranges++;
}
}

if (num_parent_ranges) {
if (num_ranges != num_parent_ranges) {
dev_err(dev, "num parent inbound ranges must be 0 or match num inbound ranges\n");
return -EINVAL;
}
}

/* merge ranges */
for (i = 0; i < num_ranges; i++) {
struct inbound_windows *range = &ranges[i];

if (range->axi_addr < start_axi) {
start_axi = range->axi_addr;
start_pci = range->pci_addr;
}

if (range->axi_addr + range->size > end_axi) {
end_axi = range->axi_addr + range->size;
end_pci = range->pci_addr + range->size;
}
}

/* move starts back as far as possible */
start_axi &= MC_ATT_MASK;
start_pci &= MC_ATT_MASK;

/* adjust size to take account of that change */
size = end_axi - start_axi;

/* may need to adjust size up to the next largest power of 2 */
if (size < 1ull << ilog2(size))
size = 1ull << (ilog2(size) + 1);

window_size = 1ull << (ilog2(size) - 1);

/* divide merged range into windows */
i = 0;
while (size > 0 && i < MC_MAX_NUM_INBOUND_WINDOWS) {
port->inbound_windows[i].axi_addr = start_axi;
port->inbound_windows[i].pci_addr = start_pci;
port->inbound_windows[i].size = window_size;

size -= window_size;
start_axi += window_size;
start_pci += window_size;
i++;
port->num_inbound_windows = i;
}

if (size < 0) {
dev_err(dev, "insufficient windows to map inbound ranges\n");
return -EINVAL;
}

return 0;
}

static int mc_platform_init(struct pci_config_window *cfg)
{
struct device *dev = cfg->parent;
Expand All @@ -1164,6 +1326,11 @@ static int mc_platform_init(struct pci_config_window *cfg)
if (ret)
return ret;

/* and similarly, check for inbound address translation */
ret = mc_check_for_parent_dma_range_handling(pdev, port);
if (ret)
return ret;

/* Configure address translation table 0 for PCIe config space */
mc_pcie_setup_window(bridge_base_addr, 0, cfg->res.start - port->outbound_range_offset,
cfg->res.start - port->outbound_range_offset,
Expand All @@ -1177,6 +1344,11 @@ static int mc_platform_init(struct pci_config_window *cfg)
if (ret)
return ret;

/* configure inbound translation tables */
ret = mc_pcie_setup_inbound_ranges(pdev, port);
if (ret)
return ret;

/* address translation is up; safe to enable interrupts */
ret = mc_init_interrupts(pdev, port);
if (ret)
Expand Down

0 comments on commit 41006bd

Please sign in to comment.