mirror of
https://github.com/torvalds/linux.git
synced 2026-04-26 10:32:25 -04:00
On Microchip PolarFire SoC the PCIe Root Port can be behind one of three general purpose Fabric Interface Controller (FIC) buses that encapsulates an AXI-S bus. Depending on which FIC(s) the Root Port is connected through to CPU space, and what address translation is done by that FIC, the Root Port driver's inbound address translation may vary. For all current supported designs and all future expected designs, inbound address translation done by a FIC on PolarFire SoC varies depending on whether PolarFire SoC is operating in coherent DMA mode or noncoherent DMA mode. The setup of the outbound address translation tables in the Root Port driver only needs to handle these two cases. Setup the inbound address translation tables to one of two address translations, depending on whether the Root Port is being used with coherent DMA or noncoherent DMA. Link: https://lore.kernel.org/r/20241011140043.1250030-3-daire.mcnamara@microchip.com Fixes:6f15a9c9f9("PCI: microchip: Add Microchip PolarFire PCIe controller driver") Signed-off-by: Daire McNamara <daire.mcnamara@microchip.com> [bhelgaas: adapt forac7f53b7e7("PCI: microchip: Add support for using either Root Port 1 or 2")] Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Acked-by: Conor Dooley <conor.dooley@microchip.com>
656 lines
17 KiB
C
656 lines
17 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* PLDA PCIe XpressRich host controller driver
|
|
*
|
|
* Copyright (C) 2023 Microchip Co. Ltd
|
|
* StarFive Co. Ltd
|
|
*
|
|
* Author: Daire McNamara <daire.mcnamara@microchip.com>
|
|
*/
|
|
|
|
#include <linux/align.h>
|
|
#include <linux/bitfield.h>
|
|
#include <linux/irqchip/chained_irq.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/msi.h>
|
|
#include <linux/pci_regs.h>
|
|
#include <linux/pci-ecam.h>
|
|
#include <linux/wordpart.h>
|
|
|
|
#include "pcie-plda.h"
|
|
|
|
void __iomem *plda_pcie_map_bus(struct pci_bus *bus, unsigned int devfn,
|
|
int where)
|
|
{
|
|
struct plda_pcie_rp *pcie = bus->sysdata;
|
|
|
|
return pcie->config_base + PCIE_ECAM_OFFSET(bus->number, devfn, where);
|
|
}
|
|
EXPORT_SYMBOL_GPL(plda_pcie_map_bus);
|
|
|
|
static void plda_handle_msi(struct irq_desc *desc)
|
|
{
|
|
struct plda_pcie_rp *port = irq_desc_get_handler_data(desc);
|
|
struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
struct device *dev = port->dev;
|
|
struct plda_msi *msi = &port->msi;
|
|
void __iomem *bridge_base_addr = port->bridge_addr;
|
|
unsigned long status;
|
|
u32 bit;
|
|
int ret;
|
|
|
|
chained_irq_enter(chip, desc);
|
|
|
|
status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL);
|
|
if (status & PM_MSI_INT_MSI_MASK) {
|
|
writel_relaxed(status & PM_MSI_INT_MSI_MASK,
|
|
bridge_base_addr + ISTATUS_LOCAL);
|
|
status = readl_relaxed(bridge_base_addr + ISTATUS_MSI);
|
|
for_each_set_bit(bit, &status, msi->num_vectors) {
|
|
ret = generic_handle_domain_irq(msi->dev_domain, bit);
|
|
if (ret)
|
|
dev_err_ratelimited(dev, "bad MSI IRQ %d\n",
|
|
bit);
|
|
}
|
|
}
|
|
|
|
chained_irq_exit(chip, desc);
|
|
}
|
|
|
|
static void plda_msi_bottom_irq_ack(struct irq_data *data)
|
|
{
|
|
struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data);
|
|
void __iomem *bridge_base_addr = port->bridge_addr;
|
|
u32 bitpos = data->hwirq;
|
|
|
|
writel_relaxed(BIT(bitpos), bridge_base_addr + ISTATUS_MSI);
|
|
}
|
|
|
|
static void plda_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
|
{
|
|
struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data);
|
|
phys_addr_t addr = port->msi.vector_phy;
|
|
|
|
msg->address_lo = lower_32_bits(addr);
|
|
msg->address_hi = upper_32_bits(addr);
|
|
msg->data = data->hwirq;
|
|
|
|
dev_dbg(port->dev, "msi#%x address_hi %#x address_lo %#x\n",
|
|
(int)data->hwirq, msg->address_hi, msg->address_lo);
|
|
}
|
|
|
|
static struct irq_chip plda_msi_bottom_irq_chip = {
|
|
.name = "PLDA MSI",
|
|
.irq_ack = plda_msi_bottom_irq_ack,
|
|
.irq_compose_msi_msg = plda_compose_msi_msg,
|
|
};
|
|
|
|
static int plda_irq_msi_domain_alloc(struct irq_domain *domain,
|
|
unsigned int virq,
|
|
unsigned int nr_irqs,
|
|
void *args)
|
|
{
|
|
struct plda_pcie_rp *port = domain->host_data;
|
|
struct plda_msi *msi = &port->msi;
|
|
unsigned long bit;
|
|
|
|
mutex_lock(&msi->lock);
|
|
bit = find_first_zero_bit(msi->used, msi->num_vectors);
|
|
if (bit >= msi->num_vectors) {
|
|
mutex_unlock(&msi->lock);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
set_bit(bit, msi->used);
|
|
|
|
irq_domain_set_info(domain, virq, bit, &plda_msi_bottom_irq_chip,
|
|
domain->host_data, handle_edge_irq, NULL, NULL);
|
|
|
|
mutex_unlock(&msi->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void plda_irq_msi_domain_free(struct irq_domain *domain,
|
|
unsigned int virq,
|
|
unsigned int nr_irqs)
|
|
{
|
|
struct irq_data *d = irq_domain_get_irq_data(domain, virq);
|
|
struct plda_pcie_rp *port = irq_data_get_irq_chip_data(d);
|
|
struct plda_msi *msi = &port->msi;
|
|
|
|
mutex_lock(&msi->lock);
|
|
|
|
if (test_bit(d->hwirq, msi->used))
|
|
__clear_bit(d->hwirq, msi->used);
|
|
else
|
|
dev_err(port->dev, "trying to free unused MSI%lu\n", d->hwirq);
|
|
|
|
mutex_unlock(&msi->lock);
|
|
}
|
|
|
|
static const struct irq_domain_ops msi_domain_ops = {
|
|
.alloc = plda_irq_msi_domain_alloc,
|
|
.free = plda_irq_msi_domain_free,
|
|
};
|
|
|
|
static struct irq_chip plda_msi_irq_chip = {
|
|
.name = "PLDA PCIe MSI",
|
|
.irq_ack = irq_chip_ack_parent,
|
|
.irq_mask = pci_msi_mask_irq,
|
|
.irq_unmask = pci_msi_unmask_irq,
|
|
};
|
|
|
|
static struct msi_domain_info plda_msi_domain_info = {
|
|
.flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
|
MSI_FLAG_NO_AFFINITY | MSI_FLAG_PCI_MSIX,
|
|
.chip = &plda_msi_irq_chip,
|
|
};
|
|
|
|
static int plda_allocate_msi_domains(struct plda_pcie_rp *port)
|
|
{
|
|
struct device *dev = port->dev;
|
|
struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);
|
|
struct plda_msi *msi = &port->msi;
|
|
|
|
mutex_init(&port->msi.lock);
|
|
|
|
msi->dev_domain = irq_domain_add_linear(NULL, msi->num_vectors,
|
|
&msi_domain_ops, port);
|
|
if (!msi->dev_domain) {
|
|
dev_err(dev, "failed to create IRQ domain\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
msi->msi_domain = pci_msi_create_irq_domain(fwnode,
|
|
&plda_msi_domain_info,
|
|
msi->dev_domain);
|
|
if (!msi->msi_domain) {
|
|
dev_err(dev, "failed to create MSI domain\n");
|
|
irq_domain_remove(msi->dev_domain);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void plda_handle_intx(struct irq_desc *desc)
|
|
{
|
|
struct plda_pcie_rp *port = irq_desc_get_handler_data(desc);
|
|
struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
struct device *dev = port->dev;
|
|
void __iomem *bridge_base_addr = port->bridge_addr;
|
|
unsigned long status;
|
|
u32 bit;
|
|
int ret;
|
|
|
|
chained_irq_enter(chip, desc);
|
|
|
|
status = readl_relaxed(bridge_base_addr + ISTATUS_LOCAL);
|
|
if (status & PM_MSI_INT_INTX_MASK) {
|
|
status &= PM_MSI_INT_INTX_MASK;
|
|
status >>= PM_MSI_INT_INTX_SHIFT;
|
|
for_each_set_bit(bit, &status, PCI_NUM_INTX) {
|
|
ret = generic_handle_domain_irq(port->intx_domain, bit);
|
|
if (ret)
|
|
dev_err_ratelimited(dev, "bad INTx IRQ %d\n",
|
|
bit);
|
|
}
|
|
}
|
|
|
|
chained_irq_exit(chip, desc);
|
|
}
|
|
|
|
static void plda_ack_intx_irq(struct irq_data *data)
|
|
{
|
|
struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data);
|
|
void __iomem *bridge_base_addr = port->bridge_addr;
|
|
u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT);
|
|
|
|
writel_relaxed(mask, bridge_base_addr + ISTATUS_LOCAL);
|
|
}
|
|
|
|
static void plda_mask_intx_irq(struct irq_data *data)
|
|
{
|
|
struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data);
|
|
void __iomem *bridge_base_addr = port->bridge_addr;
|
|
unsigned long flags;
|
|
u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT);
|
|
u32 val;
|
|
|
|
raw_spin_lock_irqsave(&port->lock, flags);
|
|
val = readl_relaxed(bridge_base_addr + IMASK_LOCAL);
|
|
val &= ~mask;
|
|
writel_relaxed(val, bridge_base_addr + IMASK_LOCAL);
|
|
raw_spin_unlock_irqrestore(&port->lock, flags);
|
|
}
|
|
|
|
static void plda_unmask_intx_irq(struct irq_data *data)
|
|
{
|
|
struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data);
|
|
void __iomem *bridge_base_addr = port->bridge_addr;
|
|
unsigned long flags;
|
|
u32 mask = BIT(data->hwirq + PM_MSI_INT_INTX_SHIFT);
|
|
u32 val;
|
|
|
|
raw_spin_lock_irqsave(&port->lock, flags);
|
|
val = readl_relaxed(bridge_base_addr + IMASK_LOCAL);
|
|
val |= mask;
|
|
writel_relaxed(val, bridge_base_addr + IMASK_LOCAL);
|
|
raw_spin_unlock_irqrestore(&port->lock, flags);
|
|
}
|
|
|
|
static struct irq_chip plda_intx_irq_chip = {
|
|
.name = "PLDA PCIe INTx",
|
|
.irq_ack = plda_ack_intx_irq,
|
|
.irq_mask = plda_mask_intx_irq,
|
|
.irq_unmask = plda_unmask_intx_irq,
|
|
};
|
|
|
|
static int plda_pcie_intx_map(struct irq_domain *domain, unsigned int irq,
|
|
irq_hw_number_t hwirq)
|
|
{
|
|
irq_set_chip_and_handler(irq, &plda_intx_irq_chip, handle_level_irq);
|
|
irq_set_chip_data(irq, domain->host_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct irq_domain_ops intx_domain_ops = {
|
|
.map = plda_pcie_intx_map,
|
|
};
|
|
|
|
static u32 plda_get_events(struct plda_pcie_rp *port)
|
|
{
|
|
u32 events, val, origin;
|
|
|
|
origin = readl_relaxed(port->bridge_addr + ISTATUS_LOCAL);
|
|
|
|
/* MSI event and sys events */
|
|
val = (origin & SYS_AND_MSI_MASK) >> PM_MSI_INT_MSI_SHIFT;
|
|
events = val << (PM_MSI_INT_MSI_SHIFT - PCI_NUM_INTX + 1);
|
|
|
|
/* INTx events */
|
|
if (origin & PM_MSI_INT_INTX_MASK)
|
|
events |= BIT(PM_MSI_INT_INTX_SHIFT);
|
|
|
|
/* remains are same with register */
|
|
events |= origin & GENMASK(P_ATR_EVT_DOORBELL_SHIFT, 0);
|
|
|
|
return events;
|
|
}
|
|
|
|
static irqreturn_t plda_event_handler(int irq, void *dev_id)
|
|
{
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void plda_handle_event(struct irq_desc *desc)
|
|
{
|
|
struct plda_pcie_rp *port = irq_desc_get_handler_data(desc);
|
|
unsigned long events;
|
|
u32 bit;
|
|
struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
|
|
chained_irq_enter(chip, desc);
|
|
|
|
events = port->event_ops->get_events(port);
|
|
|
|
events &= port->events_bitmap;
|
|
for_each_set_bit(bit, &events, port->num_events)
|
|
generic_handle_domain_irq(port->event_domain, bit);
|
|
|
|
chained_irq_exit(chip, desc);
|
|
}
|
|
|
|
static u32 plda_hwirq_to_mask(int hwirq)
|
|
{
|
|
u32 mask;
|
|
|
|
/* hwirq 23 - 0 are the same with register */
|
|
if (hwirq < EVENT_PM_MSI_INT_INTX)
|
|
mask = BIT(hwirq);
|
|
else if (hwirq == EVENT_PM_MSI_INT_INTX)
|
|
mask = PM_MSI_INT_INTX_MASK;
|
|
else
|
|
mask = BIT(hwirq + PCI_NUM_INTX - 1);
|
|
|
|
return mask;
|
|
}
|
|
|
|
static void plda_ack_event_irq(struct irq_data *data)
|
|
{
|
|
struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data);
|
|
|
|
writel_relaxed(plda_hwirq_to_mask(data->hwirq),
|
|
port->bridge_addr + ISTATUS_LOCAL);
|
|
}
|
|
|
|
static void plda_mask_event_irq(struct irq_data *data)
|
|
{
|
|
struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data);
|
|
u32 mask, val;
|
|
|
|
mask = plda_hwirq_to_mask(data->hwirq);
|
|
|
|
raw_spin_lock(&port->lock);
|
|
val = readl_relaxed(port->bridge_addr + IMASK_LOCAL);
|
|
val &= ~mask;
|
|
writel_relaxed(val, port->bridge_addr + IMASK_LOCAL);
|
|
raw_spin_unlock(&port->lock);
|
|
}
|
|
|
|
static void plda_unmask_event_irq(struct irq_data *data)
|
|
{
|
|
struct plda_pcie_rp *port = irq_data_get_irq_chip_data(data);
|
|
u32 mask, val;
|
|
|
|
mask = plda_hwirq_to_mask(data->hwirq);
|
|
|
|
raw_spin_lock(&port->lock);
|
|
val = readl_relaxed(port->bridge_addr + IMASK_LOCAL);
|
|
val |= mask;
|
|
writel_relaxed(val, port->bridge_addr + IMASK_LOCAL);
|
|
raw_spin_unlock(&port->lock);
|
|
}
|
|
|
|
static struct irq_chip plda_event_irq_chip = {
|
|
.name = "PLDA PCIe EVENT",
|
|
.irq_ack = plda_ack_event_irq,
|
|
.irq_mask = plda_mask_event_irq,
|
|
.irq_unmask = plda_unmask_event_irq,
|
|
};
|
|
|
|
static const struct plda_event_ops plda_event_ops = {
|
|
.get_events = plda_get_events,
|
|
};
|
|
|
|
static int plda_pcie_event_map(struct irq_domain *domain, unsigned int irq,
|
|
irq_hw_number_t hwirq)
|
|
{
|
|
struct plda_pcie_rp *port = (void *)domain->host_data;
|
|
|
|
irq_set_chip_and_handler(irq, port->event_irq_chip, handle_level_irq);
|
|
irq_set_chip_data(irq, domain->host_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct irq_domain_ops plda_event_domain_ops = {
|
|
.map = plda_pcie_event_map,
|
|
};
|
|
|
|
static int plda_pcie_init_irq_domains(struct plda_pcie_rp *port)
|
|
{
|
|
struct device *dev = port->dev;
|
|
struct device_node *node = dev->of_node;
|
|
struct device_node *pcie_intc_node;
|
|
|
|
/* Setup INTx */
|
|
pcie_intc_node = of_get_next_child(node, NULL);
|
|
if (!pcie_intc_node) {
|
|
dev_err(dev, "failed to find PCIe Intc node\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
port->event_domain = irq_domain_add_linear(pcie_intc_node,
|
|
port->num_events,
|
|
&plda_event_domain_ops,
|
|
port);
|
|
if (!port->event_domain) {
|
|
dev_err(dev, "failed to get event domain\n");
|
|
of_node_put(pcie_intc_node);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
irq_domain_update_bus_token(port->event_domain, DOMAIN_BUS_NEXUS);
|
|
|
|
port->intx_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
|
|
&intx_domain_ops, port);
|
|
if (!port->intx_domain) {
|
|
dev_err(dev, "failed to get an INTx IRQ domain\n");
|
|
of_node_put(pcie_intc_node);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
irq_domain_update_bus_token(port->intx_domain, DOMAIN_BUS_WIRED);
|
|
|
|
of_node_put(pcie_intc_node);
|
|
raw_spin_lock_init(&port->lock);
|
|
|
|
return plda_allocate_msi_domains(port);
|
|
}
|
|
|
|
int plda_init_interrupts(struct platform_device *pdev,
|
|
struct plda_pcie_rp *port,
|
|
const struct plda_event *event)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
int event_irq, ret;
|
|
u32 i;
|
|
|
|
if (!port->event_ops)
|
|
port->event_ops = &plda_event_ops;
|
|
|
|
if (!port->event_irq_chip)
|
|
port->event_irq_chip = &plda_event_irq_chip;
|
|
|
|
ret = plda_pcie_init_irq_domains(port);
|
|
if (ret) {
|
|
dev_err(dev, "failed creating IRQ domains\n");
|
|
return ret;
|
|
}
|
|
|
|
port->irq = platform_get_irq(pdev, 0);
|
|
if (port->irq < 0)
|
|
return -ENODEV;
|
|
|
|
for_each_set_bit(i, &port->events_bitmap, port->num_events) {
|
|
event_irq = irq_create_mapping(port->event_domain, i);
|
|
if (!event_irq) {
|
|
dev_err(dev, "failed to map hwirq %d\n", i);
|
|
return -ENXIO;
|
|
}
|
|
|
|
if (event->request_event_irq)
|
|
ret = event->request_event_irq(port, event_irq, i);
|
|
else
|
|
ret = devm_request_irq(dev, event_irq,
|
|
plda_event_handler,
|
|
0, NULL, port);
|
|
|
|
if (ret) {
|
|
dev_err(dev, "failed to request IRQ %d\n", event_irq);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
port->intx_irq = irq_create_mapping(port->event_domain,
|
|
event->intx_event);
|
|
if (!port->intx_irq) {
|
|
dev_err(dev, "failed to map INTx interrupt\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Plug the INTx chained handler */
|
|
irq_set_chained_handler_and_data(port->intx_irq, plda_handle_intx, port);
|
|
|
|
port->msi_irq = irq_create_mapping(port->event_domain,
|
|
event->msi_event);
|
|
if (!port->msi_irq)
|
|
return -ENXIO;
|
|
|
|
/* Plug the MSI chained handler */
|
|
irq_set_chained_handler_and_data(port->msi_irq, plda_handle_msi, port);
|
|
|
|
/* Plug the main event chained handler */
|
|
irq_set_chained_handler_and_data(port->irq, plda_handle_event, port);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(plda_init_interrupts);
|
|
|
|
void plda_pcie_setup_window(void __iomem *bridge_base_addr, u32 index,
|
|
phys_addr_t axi_addr, phys_addr_t pci_addr,
|
|
size_t size)
|
|
{
|
|
u32 atr_sz = ilog2(size) - 1;
|
|
u32 val;
|
|
|
|
if (index == 0)
|
|
val = PCIE_CONFIG_INTERFACE;
|
|
else
|
|
val = PCIE_TX_RX_INTERFACE;
|
|
|
|
writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) +
|
|
ATR0_AXI4_SLV0_TRSL_PARAM);
|
|
|
|
val = ALIGN_DOWN(lower_32_bits(axi_addr), SZ_4K);
|
|
val |= FIELD_PREP(ATR_SIZE_MASK, atr_sz);
|
|
val |= ATR_IMPL_ENABLE;
|
|
writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) +
|
|
ATR0_AXI4_SLV0_SRCADDR_PARAM);
|
|
|
|
val = upper_32_bits(axi_addr);
|
|
writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) +
|
|
ATR0_AXI4_SLV0_SRC_ADDR);
|
|
|
|
val = lower_32_bits(pci_addr);
|
|
writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) +
|
|
ATR0_AXI4_SLV0_TRSL_ADDR_LSB);
|
|
|
|
val = upper_32_bits(pci_addr);
|
|
writel(val, bridge_base_addr + (index * ATR_ENTRY_SIZE) +
|
|
ATR0_AXI4_SLV0_TRSL_ADDR_UDW);
|
|
}
|
|
EXPORT_SYMBOL_GPL(plda_pcie_setup_window);
|
|
|
|
void plda_pcie_setup_inbound_address_translation(struct plda_pcie_rp *port)
|
|
{
|
|
void __iomem *bridge_base_addr = port->bridge_addr;
|
|
u32 val;
|
|
|
|
val = readl(bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM);
|
|
val |= (ATR0_PCIE_ATR_SIZE << ATR0_PCIE_ATR_SIZE_SHIFT);
|
|
writel(val, bridge_base_addr + ATR0_PCIE_WIN0_SRCADDR_PARAM);
|
|
writel(0, bridge_base_addr + ATR0_PCIE_WIN0_SRC_ADDR);
|
|
}
|
|
EXPORT_SYMBOL_GPL(plda_pcie_setup_inbound_address_translation);
|
|
|
|
int plda_pcie_setup_iomems(struct pci_host_bridge *bridge,
|
|
struct plda_pcie_rp *port)
|
|
{
|
|
void __iomem *bridge_base_addr = port->bridge_addr;
|
|
struct resource_entry *entry;
|
|
u64 pci_addr;
|
|
u32 index = 1;
|
|
|
|
resource_list_for_each_entry(entry, &bridge->windows) {
|
|
if (resource_type(entry->res) == IORESOURCE_MEM) {
|
|
pci_addr = entry->res->start - entry->offset;
|
|
plda_pcie_setup_window(bridge_base_addr, index,
|
|
entry->res->start, pci_addr,
|
|
resource_size(entry->res));
|
|
index++;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(plda_pcie_setup_iomems);
|
|
|
|
static void plda_pcie_irq_domain_deinit(struct plda_pcie_rp *pcie)
|
|
{
|
|
irq_set_chained_handler_and_data(pcie->irq, NULL, NULL);
|
|
irq_set_chained_handler_and_data(pcie->msi_irq, NULL, NULL);
|
|
irq_set_chained_handler_and_data(pcie->intx_irq, NULL, NULL);
|
|
|
|
irq_domain_remove(pcie->msi.msi_domain);
|
|
irq_domain_remove(pcie->msi.dev_domain);
|
|
|
|
irq_domain_remove(pcie->intx_domain);
|
|
irq_domain_remove(pcie->event_domain);
|
|
}
|
|
|
|
int plda_pcie_host_init(struct plda_pcie_rp *port, struct pci_ops *ops,
|
|
const struct plda_event *plda_event)
|
|
{
|
|
struct device *dev = port->dev;
|
|
struct pci_host_bridge *bridge;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct resource *cfg_res;
|
|
int ret;
|
|
|
|
pdev = to_platform_device(dev);
|
|
|
|
port->bridge_addr =
|
|
devm_platform_ioremap_resource_byname(pdev, "apb");
|
|
|
|
if (IS_ERR(port->bridge_addr))
|
|
return dev_err_probe(dev, PTR_ERR(port->bridge_addr),
|
|
"failed to map reg memory\n");
|
|
|
|
cfg_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg");
|
|
if (!cfg_res)
|
|
return dev_err_probe(dev, -ENODEV,
|
|
"failed to get config memory\n");
|
|
|
|
port->config_base = devm_ioremap_resource(dev, cfg_res);
|
|
if (IS_ERR(port->config_base))
|
|
return dev_err_probe(dev, PTR_ERR(port->config_base),
|
|
"failed to map config memory\n");
|
|
|
|
bridge = devm_pci_alloc_host_bridge(dev, 0);
|
|
if (!bridge)
|
|
return dev_err_probe(dev, -ENOMEM,
|
|
"failed to alloc bridge\n");
|
|
|
|
if (port->host_ops && port->host_ops->host_init) {
|
|
ret = port->host_ops->host_init(port);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
port->bridge = bridge;
|
|
plda_pcie_setup_window(port->bridge_addr, 0, cfg_res->start, 0,
|
|
resource_size(cfg_res));
|
|
plda_pcie_setup_iomems(bridge, port);
|
|
plda_set_default_msi(&port->msi);
|
|
ret = plda_init_interrupts(pdev, port, plda_event);
|
|
if (ret)
|
|
goto err_host;
|
|
|
|
/* Set default bus ops */
|
|
bridge->ops = ops;
|
|
bridge->sysdata = port;
|
|
|
|
ret = pci_host_probe(bridge);
|
|
if (ret < 0) {
|
|
dev_err_probe(dev, ret, "failed to probe pci host\n");
|
|
goto err_probe;
|
|
}
|
|
|
|
return ret;
|
|
|
|
err_probe:
|
|
plda_pcie_irq_domain_deinit(port);
|
|
err_host:
|
|
if (port->host_ops && port->host_ops->host_deinit)
|
|
port->host_ops->host_deinit(port);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(plda_pcie_host_init);
|
|
|
|
void plda_pcie_host_deinit(struct plda_pcie_rp *port)
|
|
{
|
|
pci_stop_root_bus(port->bridge->bus);
|
|
pci_remove_root_bus(port->bridge->bus);
|
|
|
|
plda_pcie_irq_domain_deinit(port);
|
|
|
|
if (port->host_ops && port->host_ops->host_deinit)
|
|
port->host_ops->host_deinit(port);
|
|
}
|
|
EXPORT_SYMBOL_GPL(plda_pcie_host_deinit);
|