Since the DW eDMA core now supports eDMA controllers embedded in locally accessible DW PCIe Root Ports and Endpoints, register these controllers when possible. To do that the DW PCIe core driver needs to perform some preparations first. First of all, it needs to find the eDMA controller CSRs base address, whether they are accessible over the Port Logic or iATU unrolled space. Afterwards it can try to auto-detect the eDMA controller availability and number of read/write channels. If none are found the procedure silently returns without error. Secondly, the platform is supposed to provide either combined or per-channel IRQ signals. If no valid IRQs set is found, the procedure returns without error to be backward compatible with platforms where DW PCIe controllers have eDMA but lack the IRQ description. Finally, before actually probing the eDMA device we need to allocate LLP items buffers. After that the DW eDMA can be registered. If registration is successful, a message regarding the number of detected Read/Write eDMA channels will be printed to the system as is done for the iATU settings. Note: the DW PCI controller driver (either host or endpoint mode) is currently always built-in, so if the DW eDMA core is built as a module (CONFIG_DW_EDMA=m), eDMA controllers will not be registered even if the dw-edma module is later loaded. Link: https://lore.kernel.org/r/20230113171409.30470-28-Sergey.Semin@baikalelectronics.ru Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru> Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> Acked-by: Vinod Koul <vkoul@kernel.org>
810 lines
20 KiB
C
810 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Synopsys DesignWare PCIe host controller driver
|
|
*
|
|
* Copyright (C) 2013 Samsung Electronics Co., Ltd.
|
|
* https://www.samsung.com
|
|
*
|
|
* Author: Jingoo Han <jg1.han@samsung.com>
|
|
*/
|
|
|
|
#include <linux/irqchip/chained_irq.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/msi.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_pci.h>
|
|
#include <linux/pci_regs.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include "pcie-designware.h"
|
|
|
|
static struct pci_ops dw_pcie_ops;
|
|
static struct pci_ops dw_child_pcie_ops;
|
|
|
|
static void dw_msi_ack_irq(struct irq_data *d)
|
|
{
|
|
irq_chip_ack_parent(d);
|
|
}
|
|
|
|
static void dw_msi_mask_irq(struct irq_data *d)
|
|
{
|
|
pci_msi_mask_irq(d);
|
|
irq_chip_mask_parent(d);
|
|
}
|
|
|
|
static void dw_msi_unmask_irq(struct irq_data *d)
|
|
{
|
|
pci_msi_unmask_irq(d);
|
|
irq_chip_unmask_parent(d);
|
|
}
|
|
|
|
static struct irq_chip dw_pcie_msi_irq_chip = {
|
|
.name = "PCI-MSI",
|
|
.irq_ack = dw_msi_ack_irq,
|
|
.irq_mask = dw_msi_mask_irq,
|
|
.irq_unmask = dw_msi_unmask_irq,
|
|
};
|
|
|
|
static struct msi_domain_info dw_pcie_msi_domain_info = {
|
|
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
|
|
MSI_FLAG_PCI_MSIX | MSI_FLAG_MULTI_PCI_MSI),
|
|
.chip = &dw_pcie_msi_irq_chip,
|
|
};
|
|
|
|
/* MSI int handler */
|
|
irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
|
|
{
|
|
int i, pos;
|
|
unsigned long val;
|
|
u32 status, num_ctrls;
|
|
irqreturn_t ret = IRQ_NONE;
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
|
|
num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
|
|
|
|
for (i = 0; i < num_ctrls; i++) {
|
|
status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS +
|
|
(i * MSI_REG_CTRL_BLOCK_SIZE));
|
|
if (!status)
|
|
continue;
|
|
|
|
ret = IRQ_HANDLED;
|
|
val = status;
|
|
pos = 0;
|
|
while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL,
|
|
pos)) != MAX_MSI_IRQS_PER_CTRL) {
|
|
generic_handle_domain_irq(pp->irq_domain,
|
|
(i * MAX_MSI_IRQS_PER_CTRL) +
|
|
pos);
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Chained MSI interrupt service routine */
|
|
static void dw_chained_msi_isr(struct irq_desc *desc)
|
|
{
|
|
struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
struct dw_pcie_rp *pp;
|
|
|
|
chained_irq_enter(chip, desc);
|
|
|
|
pp = irq_desc_get_handler_data(desc);
|
|
dw_handle_msi_irq(pp);
|
|
|
|
chained_irq_exit(chip, desc);
|
|
}
|
|
|
|
static void dw_pci_setup_msi_msg(struct irq_data *d, struct msi_msg *msg)
|
|
{
|
|
struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
u64 msi_target;
|
|
|
|
msi_target = (u64)pp->msi_data;
|
|
|
|
msg->address_lo = lower_32_bits(msi_target);
|
|
msg->address_hi = upper_32_bits(msi_target);
|
|
|
|
msg->data = d->hwirq;
|
|
|
|
dev_dbg(pci->dev, "msi#%d address_hi %#x address_lo %#x\n",
|
|
(int)d->hwirq, msg->address_hi, msg->address_lo);
|
|
}
|
|
|
|
static int dw_pci_msi_set_affinity(struct irq_data *d,
|
|
const struct cpumask *mask, bool force)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void dw_pci_bottom_mask(struct irq_data *d)
|
|
{
|
|
struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
unsigned int res, bit, ctrl;
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&pp->lock, flags);
|
|
|
|
ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
|
|
res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
|
|
bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
|
|
|
|
pp->irq_mask[ctrl] |= BIT(bit);
|
|
dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);
|
|
|
|
raw_spin_unlock_irqrestore(&pp->lock, flags);
|
|
}
|
|
|
|
static void dw_pci_bottom_unmask(struct irq_data *d)
|
|
{
|
|
struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
unsigned int res, bit, ctrl;
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&pp->lock, flags);
|
|
|
|
ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
|
|
res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
|
|
bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
|
|
|
|
pp->irq_mask[ctrl] &= ~BIT(bit);
|
|
dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + res, pp->irq_mask[ctrl]);
|
|
|
|
raw_spin_unlock_irqrestore(&pp->lock, flags);
|
|
}
|
|
|
|
static void dw_pci_bottom_ack(struct irq_data *d)
|
|
{
|
|
struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
unsigned int res, bit, ctrl;
|
|
|
|
ctrl = d->hwirq / MAX_MSI_IRQS_PER_CTRL;
|
|
res = ctrl * MSI_REG_CTRL_BLOCK_SIZE;
|
|
bit = d->hwirq % MAX_MSI_IRQS_PER_CTRL;
|
|
|
|
dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_STATUS + res, BIT(bit));
|
|
}
|
|
|
|
static struct irq_chip dw_pci_msi_bottom_irq_chip = {
|
|
.name = "DWPCI-MSI",
|
|
.irq_ack = dw_pci_bottom_ack,
|
|
.irq_compose_msi_msg = dw_pci_setup_msi_msg,
|
|
.irq_set_affinity = dw_pci_msi_set_affinity,
|
|
.irq_mask = dw_pci_bottom_mask,
|
|
.irq_unmask = dw_pci_bottom_unmask,
|
|
};
|
|
|
|
static int dw_pcie_irq_domain_alloc(struct irq_domain *domain,
|
|
unsigned int virq, unsigned int nr_irqs,
|
|
void *args)
|
|
{
|
|
struct dw_pcie_rp *pp = domain->host_data;
|
|
unsigned long flags;
|
|
u32 i;
|
|
int bit;
|
|
|
|
raw_spin_lock_irqsave(&pp->lock, flags);
|
|
|
|
bit = bitmap_find_free_region(pp->msi_irq_in_use, pp->num_vectors,
|
|
order_base_2(nr_irqs));
|
|
|
|
raw_spin_unlock_irqrestore(&pp->lock, flags);
|
|
|
|
if (bit < 0)
|
|
return -ENOSPC;
|
|
|
|
for (i = 0; i < nr_irqs; i++)
|
|
irq_domain_set_info(domain, virq + i, bit + i,
|
|
pp->msi_irq_chip,
|
|
pp, handle_edge_irq,
|
|
NULL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dw_pcie_irq_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 dw_pcie_rp *pp = domain->host_data;
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&pp->lock, flags);
|
|
|
|
bitmap_release_region(pp->msi_irq_in_use, d->hwirq,
|
|
order_base_2(nr_irqs));
|
|
|
|
raw_spin_unlock_irqrestore(&pp->lock, flags);
|
|
}
|
|
|
|
static const struct irq_domain_ops dw_pcie_msi_domain_ops = {
|
|
.alloc = dw_pcie_irq_domain_alloc,
|
|
.free = dw_pcie_irq_domain_free,
|
|
};
|
|
|
|
int dw_pcie_allocate_domains(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
struct fwnode_handle *fwnode = of_node_to_fwnode(pci->dev->of_node);
|
|
|
|
pp->irq_domain = irq_domain_create_linear(fwnode, pp->num_vectors,
|
|
&dw_pcie_msi_domain_ops, pp);
|
|
if (!pp->irq_domain) {
|
|
dev_err(pci->dev, "Failed to create IRQ domain\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
irq_domain_update_bus_token(pp->irq_domain, DOMAIN_BUS_NEXUS);
|
|
|
|
pp->msi_domain = pci_msi_create_irq_domain(fwnode,
|
|
&dw_pcie_msi_domain_info,
|
|
pp->irq_domain);
|
|
if (!pp->msi_domain) {
|
|
dev_err(pci->dev, "Failed to create MSI domain\n");
|
|
irq_domain_remove(pp->irq_domain);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dw_pcie_free_msi(struct dw_pcie_rp *pp)
|
|
{
|
|
u32 ctrl;
|
|
|
|
for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
|
|
if (pp->msi_irq[ctrl] > 0)
|
|
irq_set_chained_handler_and_data(pp->msi_irq[ctrl],
|
|
NULL, NULL);
|
|
}
|
|
|
|
irq_domain_remove(pp->msi_domain);
|
|
irq_domain_remove(pp->irq_domain);
|
|
}
|
|
|
|
static void dw_pcie_msi_init(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
u64 msi_target = (u64)pp->msi_data;
|
|
|
|
if (!pci_msi_enabled() || !pp->has_msi_ctrl)
|
|
return;
|
|
|
|
/* Program the msi_data */
|
|
dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_LO, lower_32_bits(msi_target));
|
|
dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_HI, upper_32_bits(msi_target));
|
|
}
|
|
|
|
static int dw_pcie_parse_split_msi_irq(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
struct device *dev = pci->dev;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
u32 ctrl, max_vectors;
|
|
int irq;
|
|
|
|
/* Parse any "msiX" IRQs described in the devicetree */
|
|
for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
|
|
char msi_name[] = "msiX";
|
|
|
|
msi_name[3] = '0' + ctrl;
|
|
irq = platform_get_irq_byname_optional(pdev, msi_name);
|
|
if (irq == -ENXIO)
|
|
break;
|
|
if (irq < 0)
|
|
return dev_err_probe(dev, irq,
|
|
"Failed to parse MSI IRQ '%s'\n",
|
|
msi_name);
|
|
|
|
pp->msi_irq[ctrl] = irq;
|
|
}
|
|
|
|
/* If no "msiX" IRQs, caller should fallback to "msi" IRQ */
|
|
if (ctrl == 0)
|
|
return -ENXIO;
|
|
|
|
max_vectors = ctrl * MAX_MSI_IRQS_PER_CTRL;
|
|
if (pp->num_vectors > max_vectors) {
|
|
dev_warn(dev, "Exceeding number of MSI vectors, limiting to %u\n",
|
|
max_vectors);
|
|
pp->num_vectors = max_vectors;
|
|
}
|
|
if (!pp->num_vectors)
|
|
pp->num_vectors = max_vectors;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_pcie_msi_host_init(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
struct device *dev = pci->dev;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
u64 *msi_vaddr;
|
|
int ret;
|
|
u32 ctrl, num_ctrls;
|
|
|
|
for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++)
|
|
pp->irq_mask[ctrl] = ~0;
|
|
|
|
if (!pp->msi_irq[0]) {
|
|
ret = dw_pcie_parse_split_msi_irq(pp);
|
|
if (ret < 0 && ret != -ENXIO)
|
|
return ret;
|
|
}
|
|
|
|
if (!pp->num_vectors)
|
|
pp->num_vectors = MSI_DEF_NUM_VECTORS;
|
|
num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
|
|
|
|
if (!pp->msi_irq[0]) {
|
|
pp->msi_irq[0] = platform_get_irq_byname_optional(pdev, "msi");
|
|
if (pp->msi_irq[0] < 0) {
|
|
pp->msi_irq[0] = platform_get_irq(pdev, 0);
|
|
if (pp->msi_irq[0] < 0)
|
|
return pp->msi_irq[0];
|
|
}
|
|
}
|
|
|
|
dev_dbg(dev, "Using %d MSI vectors\n", pp->num_vectors);
|
|
|
|
pp->msi_irq_chip = &dw_pci_msi_bottom_irq_chip;
|
|
|
|
ret = dw_pcie_allocate_domains(pp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
|
|
if (pp->msi_irq[ctrl] > 0)
|
|
irq_set_chained_handler_and_data(pp->msi_irq[ctrl],
|
|
dw_chained_msi_isr, pp);
|
|
}
|
|
|
|
/*
|
|
* Even though the iMSI-RX Module supports 64-bit addresses some
|
|
* peripheral PCIe devices may lack 64-bit message support. In
|
|
* order not to miss MSI TLPs from those devices the MSI target
|
|
* address has to be within the lowest 4GB.
|
|
*
|
|
* Note until there is a better alternative found the reservation is
|
|
* done by allocating from the artificially limited DMA-coherent
|
|
* memory.
|
|
*/
|
|
ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
|
|
if (ret)
|
|
dev_warn(dev, "Failed to set DMA mask to 32-bit. Devices with only 32-bit MSI support may not work properly\n");
|
|
|
|
msi_vaddr = dmam_alloc_coherent(dev, sizeof(u64), &pp->msi_data,
|
|
GFP_KERNEL);
|
|
if (!msi_vaddr) {
|
|
dev_err(dev, "Failed to alloc and map MSI data\n");
|
|
dw_pcie_free_msi(pp);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dw_pcie_host_init(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
struct device *dev = pci->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct resource_entry *win;
|
|
struct pci_host_bridge *bridge;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
raw_spin_lock_init(&pp->lock);
|
|
|
|
ret = dw_pcie_get_resources(pci);
|
|
if (ret)
|
|
return ret;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config");
|
|
if (res) {
|
|
pp->cfg0_size = resource_size(res);
|
|
pp->cfg0_base = res->start;
|
|
|
|
pp->va_cfg0_base = devm_pci_remap_cfg_resource(dev, res);
|
|
if (IS_ERR(pp->va_cfg0_base))
|
|
return PTR_ERR(pp->va_cfg0_base);
|
|
} else {
|
|
dev_err(dev, "Missing *config* reg space\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
bridge = devm_pci_alloc_host_bridge(dev, 0);
|
|
if (!bridge)
|
|
return -ENOMEM;
|
|
|
|
pp->bridge = bridge;
|
|
|
|
/* Get the I/O range from DT */
|
|
win = resource_list_first_type(&bridge->windows, IORESOURCE_IO);
|
|
if (win) {
|
|
pp->io_size = resource_size(win->res);
|
|
pp->io_bus_addr = win->res->start - win->offset;
|
|
pp->io_base = pci_pio_to_address(win->res->start);
|
|
}
|
|
|
|
/* Set default bus ops */
|
|
bridge->ops = &dw_pcie_ops;
|
|
bridge->child_ops = &dw_child_pcie_ops;
|
|
|
|
if (pp->ops->host_init) {
|
|
ret = pp->ops->host_init(pp);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (pci_msi_enabled()) {
|
|
pp->has_msi_ctrl = !(pp->ops->msi_host_init ||
|
|
of_property_read_bool(np, "msi-parent") ||
|
|
of_property_read_bool(np, "msi-map"));
|
|
|
|
/*
|
|
* For the has_msi_ctrl case the default assignment is handled
|
|
* in the dw_pcie_msi_host_init().
|
|
*/
|
|
if (!pp->has_msi_ctrl && !pp->num_vectors) {
|
|
pp->num_vectors = MSI_DEF_NUM_VECTORS;
|
|
} else if (pp->num_vectors > MAX_MSI_IRQS) {
|
|
dev_err(dev, "Invalid number of vectors\n");
|
|
ret = -EINVAL;
|
|
goto err_deinit_host;
|
|
}
|
|
|
|
if (pp->ops->msi_host_init) {
|
|
ret = pp->ops->msi_host_init(pp);
|
|
if (ret < 0)
|
|
goto err_deinit_host;
|
|
} else if (pp->has_msi_ctrl) {
|
|
ret = dw_pcie_msi_host_init(pp);
|
|
if (ret < 0)
|
|
goto err_deinit_host;
|
|
}
|
|
}
|
|
|
|
dw_pcie_version_detect(pci);
|
|
|
|
dw_pcie_iatu_detect(pci);
|
|
|
|
ret = dw_pcie_edma_detect(pci);
|
|
if (ret)
|
|
goto err_free_msi;
|
|
|
|
ret = dw_pcie_setup_rc(pp);
|
|
if (ret)
|
|
goto err_remove_edma;
|
|
|
|
if (!dw_pcie_link_up(pci)) {
|
|
ret = dw_pcie_start_link(pci);
|
|
if (ret)
|
|
goto err_remove_edma;
|
|
}
|
|
|
|
/* Ignore errors, the link may come up later */
|
|
dw_pcie_wait_for_link(pci);
|
|
|
|
bridge->sysdata = pp;
|
|
|
|
ret = pci_host_probe(bridge);
|
|
if (ret)
|
|
goto err_stop_link;
|
|
|
|
return 0;
|
|
|
|
err_stop_link:
|
|
dw_pcie_stop_link(pci);
|
|
|
|
err_remove_edma:
|
|
dw_pcie_edma_remove(pci);
|
|
|
|
err_free_msi:
|
|
if (pp->has_msi_ctrl)
|
|
dw_pcie_free_msi(pp);
|
|
|
|
err_deinit_host:
|
|
if (pp->ops->host_deinit)
|
|
pp->ops->host_deinit(pp);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_pcie_host_init);
|
|
|
|
void dw_pcie_host_deinit(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
|
|
pci_stop_root_bus(pp->bridge->bus);
|
|
pci_remove_root_bus(pp->bridge->bus);
|
|
|
|
dw_pcie_stop_link(pci);
|
|
|
|
dw_pcie_edma_remove(pci);
|
|
|
|
if (pp->has_msi_ctrl)
|
|
dw_pcie_free_msi(pp);
|
|
|
|
if (pp->ops->host_deinit)
|
|
pp->ops->host_deinit(pp);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_pcie_host_deinit);
|
|
|
|
static void __iomem *dw_pcie_other_conf_map_bus(struct pci_bus *bus,
|
|
unsigned int devfn, int where)
|
|
{
|
|
struct dw_pcie_rp *pp = bus->sysdata;
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
int type, ret;
|
|
u32 busdev;
|
|
|
|
/*
|
|
* Checking whether the link is up here is a last line of defense
|
|
* against platforms that forward errors on the system bus as
|
|
* SError upon PCI configuration transactions issued when the link
|
|
* is down. This check is racy by definition and does not stop
|
|
* the system from triggering an SError if the link goes down
|
|
* after this check is performed.
|
|
*/
|
|
if (!dw_pcie_link_up(pci))
|
|
return NULL;
|
|
|
|
busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) |
|
|
PCIE_ATU_FUNC(PCI_FUNC(devfn));
|
|
|
|
if (pci_is_root_bus(bus->parent))
|
|
type = PCIE_ATU_TYPE_CFG0;
|
|
else
|
|
type = PCIE_ATU_TYPE_CFG1;
|
|
|
|
ret = dw_pcie_prog_outbound_atu(pci, 0, type, pp->cfg0_base, busdev,
|
|
pp->cfg0_size);
|
|
if (ret)
|
|
return NULL;
|
|
|
|
return pp->va_cfg0_base + where;
|
|
}
|
|
|
|
static int dw_pcie_rd_other_conf(struct pci_bus *bus, unsigned int devfn,
|
|
int where, int size, u32 *val)
|
|
{
|
|
struct dw_pcie_rp *pp = bus->sysdata;
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
int ret;
|
|
|
|
ret = pci_generic_config_read(bus, devfn, where, size, val);
|
|
if (ret != PCIBIOS_SUCCESSFUL)
|
|
return ret;
|
|
|
|
if (pp->cfg0_io_shared) {
|
|
ret = dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO,
|
|
pp->io_base, pp->io_bus_addr,
|
|
pp->io_size);
|
|
if (ret)
|
|
return PCIBIOS_SET_FAILED;
|
|
}
|
|
|
|
return PCIBIOS_SUCCESSFUL;
|
|
}
|
|
|
|
static int dw_pcie_wr_other_conf(struct pci_bus *bus, unsigned int devfn,
|
|
int where, int size, u32 val)
|
|
{
|
|
struct dw_pcie_rp *pp = bus->sysdata;
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
int ret;
|
|
|
|
ret = pci_generic_config_write(bus, devfn, where, size, val);
|
|
if (ret != PCIBIOS_SUCCESSFUL)
|
|
return ret;
|
|
|
|
if (pp->cfg0_io_shared) {
|
|
ret = dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO,
|
|
pp->io_base, pp->io_bus_addr,
|
|
pp->io_size);
|
|
if (ret)
|
|
return PCIBIOS_SET_FAILED;
|
|
}
|
|
|
|
return PCIBIOS_SUCCESSFUL;
|
|
}
|
|
|
|
static struct pci_ops dw_child_pcie_ops = {
|
|
.map_bus = dw_pcie_other_conf_map_bus,
|
|
.read = dw_pcie_rd_other_conf,
|
|
.write = dw_pcie_wr_other_conf,
|
|
};
|
|
|
|
void __iomem *dw_pcie_own_conf_map_bus(struct pci_bus *bus, unsigned int devfn, int where)
|
|
{
|
|
struct dw_pcie_rp *pp = bus->sysdata;
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
|
|
if (PCI_SLOT(devfn) > 0)
|
|
return NULL;
|
|
|
|
return pci->dbi_base + where;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_pcie_own_conf_map_bus);
|
|
|
|
static struct pci_ops dw_pcie_ops = {
|
|
.map_bus = dw_pcie_own_conf_map_bus,
|
|
.read = pci_generic_config_read,
|
|
.write = pci_generic_config_write,
|
|
};
|
|
|
|
static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
struct resource_entry *entry;
|
|
int i, ret;
|
|
|
|
/* Note the very first outbound ATU is used for CFG IOs */
|
|
if (!pci->num_ob_windows) {
|
|
dev_err(pci->dev, "No outbound iATU found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Ensure all out/inbound windows are disabled before proceeding with
|
|
* the MEM/IO (dma-)ranges setups.
|
|
*/
|
|
for (i = 0; i < pci->num_ob_windows; i++)
|
|
dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_OB, i);
|
|
|
|
for (i = 0; i < pci->num_ib_windows; i++)
|
|
dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, i);
|
|
|
|
i = 0;
|
|
resource_list_for_each_entry(entry, &pp->bridge->windows) {
|
|
if (resource_type(entry->res) != IORESOURCE_MEM)
|
|
continue;
|
|
|
|
if (pci->num_ob_windows <= ++i)
|
|
break;
|
|
|
|
ret = dw_pcie_prog_outbound_atu(pci, i, PCIE_ATU_TYPE_MEM,
|
|
entry->res->start,
|
|
entry->res->start - entry->offset,
|
|
resource_size(entry->res));
|
|
if (ret) {
|
|
dev_err(pci->dev, "Failed to set MEM range %pr\n",
|
|
entry->res);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (pp->io_size) {
|
|
if (pci->num_ob_windows > ++i) {
|
|
ret = dw_pcie_prog_outbound_atu(pci, i, PCIE_ATU_TYPE_IO,
|
|
pp->io_base,
|
|
pp->io_bus_addr,
|
|
pp->io_size);
|
|
if (ret) {
|
|
dev_err(pci->dev, "Failed to set IO range %pr\n",
|
|
entry->res);
|
|
return ret;
|
|
}
|
|
} else {
|
|
pp->cfg0_io_shared = true;
|
|
}
|
|
}
|
|
|
|
if (pci->num_ob_windows <= i)
|
|
dev_warn(pci->dev, "Ranges exceed outbound iATU size (%d)\n",
|
|
pci->num_ob_windows);
|
|
|
|
i = 0;
|
|
resource_list_for_each_entry(entry, &pp->bridge->dma_ranges) {
|
|
if (resource_type(entry->res) != IORESOURCE_MEM)
|
|
continue;
|
|
|
|
if (pci->num_ib_windows <= i)
|
|
break;
|
|
|
|
ret = dw_pcie_prog_inbound_atu(pci, i++, PCIE_ATU_TYPE_MEM,
|
|
entry->res->start,
|
|
entry->res->start - entry->offset,
|
|
resource_size(entry->res));
|
|
if (ret) {
|
|
dev_err(pci->dev, "Failed to set DMA range %pr\n",
|
|
entry->res);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (pci->num_ib_windows <= i)
|
|
dev_warn(pci->dev, "Dma-ranges exceed inbound iATU size (%u)\n",
|
|
pci->num_ib_windows);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dw_pcie_setup_rc(struct dw_pcie_rp *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
u32 val, ctrl, num_ctrls;
|
|
int ret;
|
|
|
|
/*
|
|
* Enable DBI read-only registers for writing/updating configuration.
|
|
* Write permission gets disabled towards the end of this function.
|
|
*/
|
|
dw_pcie_dbi_ro_wr_en(pci);
|
|
|
|
dw_pcie_setup(pci);
|
|
|
|
if (pp->has_msi_ctrl) {
|
|
num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
|
|
|
|
/* Initialize IRQ Status array */
|
|
for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
|
|
dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK +
|
|
(ctrl * MSI_REG_CTRL_BLOCK_SIZE),
|
|
pp->irq_mask[ctrl]);
|
|
dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE +
|
|
(ctrl * MSI_REG_CTRL_BLOCK_SIZE),
|
|
~0);
|
|
}
|
|
}
|
|
|
|
dw_pcie_msi_init(pp);
|
|
|
|
/* Setup RC BARs */
|
|
dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x00000004);
|
|
dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_1, 0x00000000);
|
|
|
|
/* Setup interrupt pins */
|
|
val = dw_pcie_readl_dbi(pci, PCI_INTERRUPT_LINE);
|
|
val &= 0xffff00ff;
|
|
val |= 0x00000100;
|
|
dw_pcie_writel_dbi(pci, PCI_INTERRUPT_LINE, val);
|
|
|
|
/* Setup bus numbers */
|
|
val = dw_pcie_readl_dbi(pci, PCI_PRIMARY_BUS);
|
|
val &= 0xff000000;
|
|
val |= 0x00ff0100;
|
|
dw_pcie_writel_dbi(pci, PCI_PRIMARY_BUS, val);
|
|
|
|
/* Setup command register */
|
|
val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
|
|
val &= 0xffff0000;
|
|
val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
|
|
PCI_COMMAND_MASTER | PCI_COMMAND_SERR;
|
|
dw_pcie_writel_dbi(pci, PCI_COMMAND, val);
|
|
|
|
/*
|
|
* If the platform provides its own child bus config accesses, it means
|
|
* the platform uses its own address translation component rather than
|
|
* ATU, so we should not program the ATU here.
|
|
*/
|
|
if (pp->bridge->child_ops == &dw_child_pcie_ops) {
|
|
ret = dw_pcie_iatu_setup(pp);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0);
|
|
|
|
/* Program correct class for RC */
|
|
dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE, PCI_CLASS_BRIDGE_PCI);
|
|
|
|
val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
|
|
val |= PORT_LOGIC_SPEED_CHANGE;
|
|
dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
|
|
|
|
dw_pcie_dbi_ro_wr_dis(pci);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_pcie_setup_rc);
|