kernel_optimize_test/drivers/pci/dwc/pcie-artpec6.c
Gustavo Pimentel 7c5925afbc PCI: dwc: Move MSI IRQs allocation to IRQ domains hierarchical API
Implement a multiplexed IRQ domain hierarchy API in the pcie-designware
host bridge driver that funnels all MSI IRQs into a single parent
interrupt, moving away from the obsolete struct msi_controller based
API.

Although the old implementation API is still available, pcie-designware
will now use the multiplexed IRQ domains hierarchical API.

Remove all existing dwc based host bridges MSI IRQs handlers, in that the
hierarchical API now handles MSI IRQs through the hierarchical/chained
MSI domain implementation.

Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Tested-by: Niklas Cassel <niklas.cassel@axis.com>
Tested-by: Shawn Guo <shawn.guo@linaro.org>
Acked-by: Jingoo Han <jingoohan1@gmail.com>
Acked-by: Marc Zyngier <marc.zyngier@arm.com>
2018-03-06 14:31:08 +00:00

619 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* PCIe host controller driver for Axis ARTPEC-6 SoC
*
* Author: Niklas Cassel <niklas.cassel@axis.com>
*
* Based on work done by Phil Edworthy <phil@edworthys.org>
*/
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of_device.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/resource.h>
#include <linux/signal.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include "pcie-designware.h"
#define to_artpec6_pcie(x) dev_get_drvdata((x)->dev)
enum artpec_pcie_variants {
ARTPEC6,
ARTPEC7,
};
struct artpec6_pcie {
struct dw_pcie *pci;
struct regmap *regmap; /* DT axis,syscon-pcie */
void __iomem *phy_base; /* DT phy */
enum artpec_pcie_variants variant;
enum dw_pcie_device_mode mode;
};
struct artpec_pcie_of_data {
enum artpec_pcie_variants variant;
enum dw_pcie_device_mode mode;
};
static const struct of_device_id artpec6_pcie_of_match[];
/* PCIe Port Logic registers (memory-mapped) */
#define PL_OFFSET 0x700
#define ACK_F_ASPM_CTRL_OFF (PL_OFFSET + 0xc)
#define ACK_N_FTS_MASK GENMASK(15, 8)
#define ACK_N_FTS(x) (((x) << 8) & ACK_N_FTS_MASK)
#define FAST_TRAINING_SEQ_MASK GENMASK(7, 0)
#define FAST_TRAINING_SEQ(x) (((x) << 0) & FAST_TRAINING_SEQ_MASK)
/* ARTPEC-6 specific registers */
#define PCIECFG 0x18
#define PCIECFG_DBG_OEN BIT(24)
#define PCIECFG_CORE_RESET_REQ BIT(21)
#define PCIECFG_LTSSM_ENABLE BIT(20)
#define PCIECFG_DEVICE_TYPE_MASK GENMASK(19, 16)
#define PCIECFG_CLKREQ_B BIT(11)
#define PCIECFG_REFCLK_ENABLE BIT(10)
#define PCIECFG_PLL_ENABLE BIT(9)
#define PCIECFG_PCLK_ENABLE BIT(8)
#define PCIECFG_RISRCREN BIT(4)
#define PCIECFG_MODE_TX_DRV_EN BIT(3)
#define PCIECFG_CISRREN BIT(2)
#define PCIECFG_MACRO_ENABLE BIT(0)
/* ARTPEC-7 specific fields */
#define PCIECFG_REFCLKSEL BIT(23)
#define PCIECFG_NOC_RESET BIT(3)
#define PCIESTAT 0x1c
/* ARTPEC-7 specific fields */
#define PCIESTAT_EXTREFCLK BIT(3)
#define NOCCFG 0x40
#define NOCCFG_ENABLE_CLK_PCIE BIT(4)
#define NOCCFG_POWER_PCIE_IDLEACK BIT(3)
#define NOCCFG_POWER_PCIE_IDLE BIT(2)
#define NOCCFG_POWER_PCIE_IDLEREQ BIT(1)
#define PHY_STATUS 0x118
#define PHY_COSPLLLOCK BIT(0)
#define PHY_TX_ASIC_OUT 0x4040
#define PHY_TX_ASIC_OUT_TX_ACK BIT(0)
#define PHY_RX_ASIC_OUT 0x405c
#define PHY_RX_ASIC_OUT_ACK BIT(0)
static u32 artpec6_pcie_readl(struct artpec6_pcie *artpec6_pcie, u32 offset)
{
u32 val;
regmap_read(artpec6_pcie->regmap, offset, &val);
return val;
}
static void artpec6_pcie_writel(struct artpec6_pcie *artpec6_pcie, u32 offset, u32 val)
{
regmap_write(artpec6_pcie->regmap, offset, val);
}
static u64 artpec6_pcie_cpu_addr_fixup(struct dw_pcie *pci, u64 pci_addr)
{
struct artpec6_pcie *artpec6_pcie = to_artpec6_pcie(pci);
struct pcie_port *pp = &pci->pp;
struct dw_pcie_ep *ep = &pci->ep;
switch (artpec6_pcie->mode) {
case DW_PCIE_RC_TYPE:
return pci_addr - pp->cfg0_base;
case DW_PCIE_EP_TYPE:
return pci_addr - ep->phys_base;
default:
dev_err(pci->dev, "UNKNOWN device type\n");
}
return pci_addr;
}
static int artpec6_pcie_establish_link(struct dw_pcie *pci)
{
struct artpec6_pcie *artpec6_pcie = to_artpec6_pcie(pci);
u32 val;
val = artpec6_pcie_readl(artpec6_pcie, PCIECFG);
val |= PCIECFG_LTSSM_ENABLE;
artpec6_pcie_writel(artpec6_pcie, PCIECFG, val);
return 0;
}
static void artpec6_pcie_stop_link(struct dw_pcie *pci)
{
struct artpec6_pcie *artpec6_pcie = to_artpec6_pcie(pci);
u32 val;
val = artpec6_pcie_readl(artpec6_pcie, PCIECFG);
val &= ~PCIECFG_LTSSM_ENABLE;
artpec6_pcie_writel(artpec6_pcie, PCIECFG, val);
}
static const struct dw_pcie_ops dw_pcie_ops = {
.cpu_addr_fixup = artpec6_pcie_cpu_addr_fixup,
.start_link = artpec6_pcie_establish_link,
.stop_link = artpec6_pcie_stop_link,
};
static void artpec6_pcie_wait_for_phy_a6(struct artpec6_pcie *artpec6_pcie)
{
struct dw_pcie *pci = artpec6_pcie->pci;
struct device *dev = pci->dev;
u32 val;
unsigned int retries;
retries = 50;
do {
usleep_range(1000, 2000);
val = artpec6_pcie_readl(artpec6_pcie, NOCCFG);
retries--;
} while (retries &&
(val & (NOCCFG_POWER_PCIE_IDLEACK | NOCCFG_POWER_PCIE_IDLE)));
if (!retries)
dev_err(dev, "PCIe clock manager did not leave idle state\n");
retries = 50;
do {
usleep_range(1000, 2000);
val = readl(artpec6_pcie->phy_base + PHY_STATUS);
retries--;
} while (retries && !(val & PHY_COSPLLLOCK));
if (!retries)
dev_err(dev, "PHY PLL did not lock\n");
}
static void artpec6_pcie_wait_for_phy_a7(struct artpec6_pcie *artpec6_pcie)
{
struct dw_pcie *pci = artpec6_pcie->pci;
struct device *dev = pci->dev;
u32 val;
u16 phy_status_tx, phy_status_rx;
unsigned int retries;
retries = 50;
do {
usleep_range(1000, 2000);
val = artpec6_pcie_readl(artpec6_pcie, NOCCFG);
retries--;
} while (retries &&
(val & (NOCCFG_POWER_PCIE_IDLEACK | NOCCFG_POWER_PCIE_IDLE)));
if (!retries)
dev_err(dev, "PCIe clock manager did not leave idle state\n");
retries = 50;
do {
usleep_range(1000, 2000);
phy_status_tx = readw(artpec6_pcie->phy_base + PHY_TX_ASIC_OUT);
phy_status_rx = readw(artpec6_pcie->phy_base + PHY_RX_ASIC_OUT);
retries--;
} while (retries && ((phy_status_tx & PHY_TX_ASIC_OUT_TX_ACK) ||
(phy_status_rx & PHY_RX_ASIC_OUT_ACK)));
if (!retries)
dev_err(dev, "PHY did not enter Pn state\n");
}
static void artpec6_pcie_wait_for_phy(struct artpec6_pcie *artpec6_pcie)
{
switch (artpec6_pcie->variant) {
case ARTPEC6:
artpec6_pcie_wait_for_phy_a6(artpec6_pcie);
break;
case ARTPEC7:
artpec6_pcie_wait_for_phy_a7(artpec6_pcie);
break;
}
}
static void artpec6_pcie_init_phy_a6(struct artpec6_pcie *artpec6_pcie)
{
u32 val;
val = artpec6_pcie_readl(artpec6_pcie, PCIECFG);
val |= PCIECFG_RISRCREN | /* Receiver term. 50 Ohm */
PCIECFG_MODE_TX_DRV_EN |
PCIECFG_CISRREN | /* Reference clock term. 100 Ohm */
PCIECFG_MACRO_ENABLE;
val |= PCIECFG_REFCLK_ENABLE;
val &= ~PCIECFG_DBG_OEN;
val &= ~PCIECFG_CLKREQ_B;
artpec6_pcie_writel(artpec6_pcie, PCIECFG, val);
usleep_range(5000, 6000);
val = artpec6_pcie_readl(artpec6_pcie, NOCCFG);
val |= NOCCFG_ENABLE_CLK_PCIE;
artpec6_pcie_writel(artpec6_pcie, NOCCFG, val);
usleep_range(20, 30);
val = artpec6_pcie_readl(artpec6_pcie, PCIECFG);
val |= PCIECFG_PCLK_ENABLE | PCIECFG_PLL_ENABLE;
artpec6_pcie_writel(artpec6_pcie, PCIECFG, val);
usleep_range(6000, 7000);
val = artpec6_pcie_readl(artpec6_pcie, NOCCFG);
val &= ~NOCCFG_POWER_PCIE_IDLEREQ;
artpec6_pcie_writel(artpec6_pcie, NOCCFG, val);
}
static void artpec6_pcie_init_phy_a7(struct artpec6_pcie *artpec6_pcie)
{
struct dw_pcie *pci = artpec6_pcie->pci;
u32 val;
bool extrefclk;
/* Check if external reference clock is connected */
val = artpec6_pcie_readl(artpec6_pcie, PCIESTAT);
extrefclk = !!(val & PCIESTAT_EXTREFCLK);
dev_dbg(pci->dev, "Using reference clock: %s\n",
extrefclk ? "external" : "internal");
val = artpec6_pcie_readl(artpec6_pcie, PCIECFG);
val |= PCIECFG_RISRCREN | /* Receiver term. 50 Ohm */
PCIECFG_PCLK_ENABLE;
if (extrefclk)
val |= PCIECFG_REFCLKSEL;
else
val &= ~PCIECFG_REFCLKSEL;
artpec6_pcie_writel(artpec6_pcie, PCIECFG, val);
usleep_range(10, 20);
val = artpec6_pcie_readl(artpec6_pcie, NOCCFG);
val |= NOCCFG_ENABLE_CLK_PCIE;
artpec6_pcie_writel(artpec6_pcie, NOCCFG, val);
usleep_range(20, 30);
val = artpec6_pcie_readl(artpec6_pcie, NOCCFG);
val &= ~NOCCFG_POWER_PCIE_IDLEREQ;
artpec6_pcie_writel(artpec6_pcie, NOCCFG, val);
}
static void artpec6_pcie_init_phy(struct artpec6_pcie *artpec6_pcie)
{
switch (artpec6_pcie->variant) {
case ARTPEC6:
artpec6_pcie_init_phy_a6(artpec6_pcie);
break;
case ARTPEC7:
artpec6_pcie_init_phy_a7(artpec6_pcie);
break;
}
}
static void artpec6_pcie_set_nfts(struct artpec6_pcie *artpec6_pcie)
{
struct dw_pcie *pci = artpec6_pcie->pci;
u32 val;
if (artpec6_pcie->variant != ARTPEC7)
return;
/*
* Increase the N_FTS (Number of Fast Training Sequences)
* to be transmitted when transitioning from L0s to L0.
*/
val = dw_pcie_readl_dbi(pci, ACK_F_ASPM_CTRL_OFF);
val &= ~ACK_N_FTS_MASK;
val |= ACK_N_FTS(180);
dw_pcie_writel_dbi(pci, ACK_F_ASPM_CTRL_OFF, val);
/*
* Set the Number of Fast Training Sequences that the core
* advertises as its N_FTS during Gen2 or Gen3 link training.
*/
val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
val &= ~FAST_TRAINING_SEQ_MASK;
val |= FAST_TRAINING_SEQ(180);
dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
}
static void artpec6_pcie_assert_core_reset(struct artpec6_pcie *artpec6_pcie)
{
u32 val;
val = artpec6_pcie_readl(artpec6_pcie, PCIECFG);
switch (artpec6_pcie->variant) {
case ARTPEC6:
val |= PCIECFG_CORE_RESET_REQ;
break;
case ARTPEC7:
val &= ~PCIECFG_NOC_RESET;
break;
}
artpec6_pcie_writel(artpec6_pcie, PCIECFG, val);
}
static void artpec6_pcie_deassert_core_reset(struct artpec6_pcie *artpec6_pcie)
{
u32 val;
val = artpec6_pcie_readl(artpec6_pcie, PCIECFG);
switch (artpec6_pcie->variant) {
case ARTPEC6:
val &= ~PCIECFG_CORE_RESET_REQ;
break;
case ARTPEC7:
val |= PCIECFG_NOC_RESET;
break;
}
artpec6_pcie_writel(artpec6_pcie, PCIECFG, val);
usleep_range(100, 200);
}
static void artpec6_pcie_enable_interrupts(struct artpec6_pcie *artpec6_pcie)
{
struct dw_pcie *pci = artpec6_pcie->pci;
struct pcie_port *pp = &pci->pp;
if (IS_ENABLED(CONFIG_PCI_MSI))
dw_pcie_msi_init(pp);
}
static int artpec6_pcie_host_init(struct pcie_port *pp)
{
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
struct artpec6_pcie *artpec6_pcie = to_artpec6_pcie(pci);
artpec6_pcie_assert_core_reset(artpec6_pcie);
artpec6_pcie_init_phy(artpec6_pcie);
artpec6_pcie_deassert_core_reset(artpec6_pcie);
artpec6_pcie_wait_for_phy(artpec6_pcie);
artpec6_pcie_set_nfts(artpec6_pcie);
dw_pcie_setup_rc(pp);
artpec6_pcie_establish_link(pci);
dw_pcie_wait_for_link(pci);
artpec6_pcie_enable_interrupts(artpec6_pcie);
return 0;
}
static const struct dw_pcie_host_ops artpec6_pcie_host_ops = {
.host_init = artpec6_pcie_host_init,
};
static int artpec6_add_pcie_port(struct artpec6_pcie *artpec6_pcie,
struct platform_device *pdev)
{
struct dw_pcie *pci = artpec6_pcie->pci;
struct pcie_port *pp = &pci->pp;
struct device *dev = pci->dev;
int ret;
if (IS_ENABLED(CONFIG_PCI_MSI)) {
pp->msi_irq = platform_get_irq_byname(pdev, "msi");
if (pp->msi_irq < 0) {
dev_err(dev, "failed to get MSI irq\n");
return pp->msi_irq;
}
}
pp->root_bus_nr = -1;
pp->ops = &artpec6_pcie_host_ops;
ret = dw_pcie_host_init(pp);
if (ret) {
dev_err(dev, "failed to initialize host\n");
return ret;
}
return 0;
}
static void artpec6_pcie_ep_init(struct dw_pcie_ep *ep)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
struct artpec6_pcie *artpec6_pcie = to_artpec6_pcie(pci);
enum pci_barno bar;
artpec6_pcie_assert_core_reset(artpec6_pcie);
artpec6_pcie_init_phy(artpec6_pcie);
artpec6_pcie_deassert_core_reset(artpec6_pcie);
artpec6_pcie_wait_for_phy(artpec6_pcie);
artpec6_pcie_set_nfts(artpec6_pcie);
for (bar = BAR_0; bar <= BAR_5; bar++)
dw_pcie_ep_reset_bar(pci, bar);
}
static int artpec6_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
enum pci_epc_irq_type type, u8 interrupt_num)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
switch (type) {
case PCI_EPC_IRQ_LEGACY:
dev_err(pci->dev, "EP cannot trigger legacy IRQs\n");
return -EINVAL;
case PCI_EPC_IRQ_MSI:
return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num);
default:
dev_err(pci->dev, "UNKNOWN IRQ type\n");
}
return 0;
}
static struct dw_pcie_ep_ops pcie_ep_ops = {
.ep_init = artpec6_pcie_ep_init,
.raise_irq = artpec6_pcie_raise_irq,
};
static int artpec6_add_pcie_ep(struct artpec6_pcie *artpec6_pcie,
struct platform_device *pdev)
{
int ret;
struct dw_pcie_ep *ep;
struct resource *res;
struct device *dev = &pdev->dev;
struct dw_pcie *pci = artpec6_pcie->pci;
ep = &pci->ep;
ep->ops = &pcie_ep_ops;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi2");
pci->dbi_base2 = devm_ioremap(dev, res->start, resource_size(res));
if (!pci->dbi_base2)
return -ENOMEM;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space");
if (!res)
return -EINVAL;
ep->phys_base = res->start;
ep->addr_size = resource_size(res);
ret = dw_pcie_ep_init(ep);
if (ret) {
dev_err(dev, "failed to initialize endpoint\n");
return ret;
}
return 0;
}
static int artpec6_pcie_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dw_pcie *pci;
struct artpec6_pcie *artpec6_pcie;
struct resource *dbi_base;
struct resource *phy_base;
int ret;
const struct of_device_id *match;
const struct artpec_pcie_of_data *data;
enum artpec_pcie_variants variant;
enum dw_pcie_device_mode mode;
match = of_match_device(artpec6_pcie_of_match, dev);
if (!match)
return -EINVAL;
data = (struct artpec_pcie_of_data *)match->data;
variant = (enum artpec_pcie_variants)data->variant;
mode = (enum dw_pcie_device_mode)data->mode;
artpec6_pcie = devm_kzalloc(dev, sizeof(*artpec6_pcie), GFP_KERNEL);
if (!artpec6_pcie)
return -ENOMEM;
pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
if (!pci)
return -ENOMEM;
pci->dev = dev;
pci->ops = &dw_pcie_ops;
artpec6_pcie->pci = pci;
artpec6_pcie->variant = variant;
artpec6_pcie->mode = mode;
dbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
pci->dbi_base = devm_ioremap_resource(dev, dbi_base);
if (IS_ERR(pci->dbi_base))
return PTR_ERR(pci->dbi_base);
phy_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy");
artpec6_pcie->phy_base = devm_ioremap_resource(dev, phy_base);
if (IS_ERR(artpec6_pcie->phy_base))
return PTR_ERR(artpec6_pcie->phy_base);
artpec6_pcie->regmap =
syscon_regmap_lookup_by_phandle(dev->of_node,
"axis,syscon-pcie");
if (IS_ERR(artpec6_pcie->regmap))
return PTR_ERR(artpec6_pcie->regmap);
platform_set_drvdata(pdev, artpec6_pcie);
switch (artpec6_pcie->mode) {
case DW_PCIE_RC_TYPE:
if (!IS_ENABLED(CONFIG_PCIE_ARTPEC6_HOST))
return -ENODEV;
ret = artpec6_add_pcie_port(artpec6_pcie, pdev);
if (ret < 0)
return ret;
break;
case DW_PCIE_EP_TYPE: {
u32 val;
if (!IS_ENABLED(CONFIG_PCIE_ARTPEC6_EP))
return -ENODEV;
val = artpec6_pcie_readl(artpec6_pcie, PCIECFG);
val &= ~PCIECFG_DEVICE_TYPE_MASK;
artpec6_pcie_writel(artpec6_pcie, PCIECFG, val);
ret = artpec6_add_pcie_ep(artpec6_pcie, pdev);
if (ret < 0)
return ret;
break;
}
default:
dev_err(dev, "INVALID device type %d\n", artpec6_pcie->mode);
}
return 0;
}
static const struct artpec_pcie_of_data artpec6_pcie_rc_of_data = {
.variant = ARTPEC6,
.mode = DW_PCIE_RC_TYPE,
};
static const struct artpec_pcie_of_data artpec6_pcie_ep_of_data = {
.variant = ARTPEC6,
.mode = DW_PCIE_EP_TYPE,
};
static const struct artpec_pcie_of_data artpec7_pcie_rc_of_data = {
.variant = ARTPEC7,
.mode = DW_PCIE_RC_TYPE,
};
static const struct artpec_pcie_of_data artpec7_pcie_ep_of_data = {
.variant = ARTPEC7,
.mode = DW_PCIE_EP_TYPE,
};
static const struct of_device_id artpec6_pcie_of_match[] = {
{
.compatible = "axis,artpec6-pcie",
.data = &artpec6_pcie_rc_of_data,
},
{
.compatible = "axis,artpec6-pcie-ep",
.data = &artpec6_pcie_ep_of_data,
},
{
.compatible = "axis,artpec7-pcie",
.data = &artpec7_pcie_rc_of_data,
},
{
.compatible = "axis,artpec7-pcie-ep",
.data = &artpec7_pcie_ep_of_data,
},
{},
};
static struct platform_driver artpec6_pcie_driver = {
.probe = artpec6_pcie_probe,
.driver = {
.name = "artpec6-pcie",
.of_match_table = artpec6_pcie_of_match,
.suppress_bind_attrs = true,
},
};
builtin_platform_driver(artpec6_pcie_driver);