mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-27 07:35:12 +07:00
ed22aaaede
Add support to PCIe RC controller on Intel Gateway SoCs. PCIe controller is based of Synopsys DesignWare PCIe core. Intel PCIe driver requires Upconfigure support, Fast Training Sequence and link speed configurations. So adding the respective helper functions in the PCIe DesignWare framework. It also programs hardware autonomous speed during speed configuration so defining it in pci_regs.h. Also, mark Intel PCIe driver depends on MSI IRQ Domain as Synopsys DesignWare framework depends on the PCI_MSI_IRQ_DOMAIN. Signed-off-by: Dilip Kota <eswara.kota@linux.intel.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Andrew Murray <andrew.murray@arm.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com> Acked-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
546 lines
13 KiB
C
546 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* PCIe host controller driver for Intel Gateway SoCs
|
|
*
|
|
* Copyright (c) 2019 Intel Corporation.
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/pci_regs.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include "../../pci.h"
|
|
#include "pcie-designware.h"
|
|
|
|
#define PORT_AFR_N_FTS_GEN12_DFT (SZ_128 - 1)
|
|
#define PORT_AFR_N_FTS_GEN3 180
|
|
#define PORT_AFR_N_FTS_GEN4 196
|
|
|
|
/* PCIe Application logic Registers */
|
|
#define PCIE_APP_CCR 0x10
|
|
#define PCIE_APP_CCR_LTSSM_ENABLE BIT(0)
|
|
|
|
#define PCIE_APP_MSG_CR 0x30
|
|
#define PCIE_APP_MSG_XMT_PM_TURNOFF BIT(0)
|
|
|
|
#define PCIE_APP_PMC 0x44
|
|
#define PCIE_APP_PMC_IN_L2 BIT(20)
|
|
|
|
#define PCIE_APP_IRNEN 0xF4
|
|
#define PCIE_APP_IRNCR 0xF8
|
|
#define PCIE_APP_IRN_AER_REPORT BIT(0)
|
|
#define PCIE_APP_IRN_PME BIT(2)
|
|
#define PCIE_APP_IRN_RX_VDM_MSG BIT(4)
|
|
#define PCIE_APP_IRN_PM_TO_ACK BIT(9)
|
|
#define PCIE_APP_IRN_LINK_AUTO_BW_STAT BIT(11)
|
|
#define PCIE_APP_IRN_BW_MGT BIT(12)
|
|
#define PCIE_APP_IRN_MSG_LTR BIT(18)
|
|
#define PCIE_APP_IRN_SYS_ERR_RC BIT(29)
|
|
#define PCIE_APP_INTX_OFST 12
|
|
|
|
#define PCIE_APP_IRN_INT \
|
|
(PCIE_APP_IRN_AER_REPORT | PCIE_APP_IRN_PME | \
|
|
PCIE_APP_IRN_RX_VDM_MSG | PCIE_APP_IRN_SYS_ERR_RC | \
|
|
PCIE_APP_IRN_PM_TO_ACK | PCIE_APP_IRN_MSG_LTR | \
|
|
PCIE_APP_IRN_BW_MGT | PCIE_APP_IRN_LINK_AUTO_BW_STAT | \
|
|
(PCIE_APP_INTX_OFST + PCI_INTERRUPT_INTA) | \
|
|
(PCIE_APP_INTX_OFST + PCI_INTERRUPT_INTB) | \
|
|
(PCIE_APP_INTX_OFST + PCI_INTERRUPT_INTC) | \
|
|
(PCIE_APP_INTX_OFST + PCI_INTERRUPT_INTD))
|
|
|
|
#define BUS_IATU_OFFSET SZ_256M
|
|
#define RESET_INTERVAL_MS 100
|
|
|
|
struct intel_pcie_soc {
|
|
unsigned int pcie_ver;
|
|
unsigned int pcie_atu_offset;
|
|
u32 num_viewport;
|
|
};
|
|
|
|
struct intel_pcie_port {
|
|
struct dw_pcie pci;
|
|
void __iomem *app_base;
|
|
struct gpio_desc *reset_gpio;
|
|
u32 rst_intrvl;
|
|
u32 max_speed;
|
|
u32 link_gen;
|
|
u32 max_width;
|
|
u32 n_fts;
|
|
struct clk *core_clk;
|
|
struct reset_control *core_rst;
|
|
struct phy *phy;
|
|
u8 pcie_cap_ofst;
|
|
};
|
|
|
|
static void pcie_update_bits(void __iomem *base, u32 ofs, u32 mask, u32 val)
|
|
{
|
|
u32 old;
|
|
|
|
old = readl(base + ofs);
|
|
val = (old & ~mask) | (val & mask);
|
|
|
|
if (val != old)
|
|
writel(val, base + ofs);
|
|
}
|
|
|
|
static inline u32 pcie_app_rd(struct intel_pcie_port *lpp, u32 ofs)
|
|
{
|
|
return readl(lpp->app_base + ofs);
|
|
}
|
|
|
|
static inline void pcie_app_wr(struct intel_pcie_port *lpp, u32 ofs, u32 val)
|
|
{
|
|
writel(val, lpp->app_base + ofs);
|
|
}
|
|
|
|
static void pcie_app_wr_mask(struct intel_pcie_port *lpp, u32 ofs,
|
|
u32 mask, u32 val)
|
|
{
|
|
pcie_update_bits(lpp->app_base, ofs, mask, val);
|
|
}
|
|
|
|
static inline u32 pcie_rc_cfg_rd(struct intel_pcie_port *lpp, u32 ofs)
|
|
{
|
|
return dw_pcie_readl_dbi(&lpp->pci, ofs);
|
|
}
|
|
|
|
static inline void pcie_rc_cfg_wr(struct intel_pcie_port *lpp, u32 ofs, u32 val)
|
|
{
|
|
dw_pcie_writel_dbi(&lpp->pci, ofs, val);
|
|
}
|
|
|
|
static void pcie_rc_cfg_wr_mask(struct intel_pcie_port *lpp, u32 ofs,
|
|
u32 mask, u32 val)
|
|
{
|
|
pcie_update_bits(lpp->pci.dbi_base, ofs, mask, val);
|
|
}
|
|
|
|
static void intel_pcie_ltssm_enable(struct intel_pcie_port *lpp)
|
|
{
|
|
pcie_app_wr_mask(lpp, PCIE_APP_CCR, PCIE_APP_CCR_LTSSM_ENABLE,
|
|
PCIE_APP_CCR_LTSSM_ENABLE);
|
|
}
|
|
|
|
static void intel_pcie_ltssm_disable(struct intel_pcie_port *lpp)
|
|
{
|
|
pcie_app_wr_mask(lpp, PCIE_APP_CCR, PCIE_APP_CCR_LTSSM_ENABLE, 0);
|
|
}
|
|
|
|
static void intel_pcie_link_setup(struct intel_pcie_port *lpp)
|
|
{
|
|
u32 val;
|
|
u8 offset = lpp->pcie_cap_ofst;
|
|
|
|
val = pcie_rc_cfg_rd(lpp, offset + PCI_EXP_LNKCAP);
|
|
lpp->max_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, val);
|
|
lpp->max_width = FIELD_GET(PCI_EXP_LNKCAP_MLW, val);
|
|
|
|
val = pcie_rc_cfg_rd(lpp, offset + PCI_EXP_LNKCTL);
|
|
|
|
val &= ~(PCI_EXP_LNKCTL_LD | PCI_EXP_LNKCTL_ASPMC);
|
|
pcie_rc_cfg_wr(lpp, offset + PCI_EXP_LNKCTL, val);
|
|
}
|
|
|
|
static void intel_pcie_port_logic_setup(struct intel_pcie_port *lpp)
|
|
{
|
|
u32 val, mask;
|
|
|
|
switch (pcie_link_speed[lpp->max_speed]) {
|
|
case PCIE_SPEED_8_0GT:
|
|
lpp->n_fts = PORT_AFR_N_FTS_GEN3;
|
|
break;
|
|
case PCIE_SPEED_16_0GT:
|
|
lpp->n_fts = PORT_AFR_N_FTS_GEN4;
|
|
break;
|
|
default:
|
|
lpp->n_fts = PORT_AFR_N_FTS_GEN12_DFT;
|
|
break;
|
|
}
|
|
|
|
mask = PORT_AFR_N_FTS_MASK | PORT_AFR_CC_N_FTS_MASK;
|
|
val = FIELD_PREP(PORT_AFR_N_FTS_MASK, lpp->n_fts) |
|
|
FIELD_PREP(PORT_AFR_CC_N_FTS_MASK, lpp->n_fts);
|
|
pcie_rc_cfg_wr_mask(lpp, PCIE_PORT_AFR, mask, val);
|
|
|
|
/* Port Link Control Register */
|
|
pcie_rc_cfg_wr_mask(lpp, PCIE_PORT_LINK_CONTROL, PORT_LINK_DLL_LINK_EN,
|
|
PORT_LINK_DLL_LINK_EN);
|
|
}
|
|
|
|
static void intel_pcie_rc_setup(struct intel_pcie_port *lpp)
|
|
{
|
|
intel_pcie_ltssm_disable(lpp);
|
|
intel_pcie_link_setup(lpp);
|
|
dw_pcie_setup_rc(&lpp->pci.pp);
|
|
dw_pcie_upconfig_setup(&lpp->pci);
|
|
intel_pcie_port_logic_setup(lpp);
|
|
dw_pcie_link_set_max_speed(&lpp->pci, lpp->link_gen);
|
|
dw_pcie_link_set_n_fts(&lpp->pci, lpp->n_fts);
|
|
}
|
|
|
|
static int intel_pcie_ep_rst_init(struct intel_pcie_port *lpp)
|
|
{
|
|
struct device *dev = lpp->pci.dev;
|
|
int ret;
|
|
|
|
lpp->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
|
|
if (IS_ERR(lpp->reset_gpio)) {
|
|
ret = PTR_ERR(lpp->reset_gpio);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "Failed to request PCIe GPIO: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Make initial reset last for 100us */
|
|
usleep_range(100, 200);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void intel_pcie_core_rst_assert(struct intel_pcie_port *lpp)
|
|
{
|
|
reset_control_assert(lpp->core_rst);
|
|
}
|
|
|
|
static void intel_pcie_core_rst_deassert(struct intel_pcie_port *lpp)
|
|
{
|
|
/*
|
|
* One micro-second delay to make sure the reset pulse
|
|
* wide enough so that core reset is clean.
|
|
*/
|
|
udelay(1);
|
|
reset_control_deassert(lpp->core_rst);
|
|
|
|
/*
|
|
* Some SoC core reset also reset PHY, more delay needed
|
|
* to make sure the reset process is done.
|
|
*/
|
|
usleep_range(1000, 2000);
|
|
}
|
|
|
|
static void intel_pcie_device_rst_assert(struct intel_pcie_port *lpp)
|
|
{
|
|
gpiod_set_value_cansleep(lpp->reset_gpio, 1);
|
|
}
|
|
|
|
static void intel_pcie_device_rst_deassert(struct intel_pcie_port *lpp)
|
|
{
|
|
msleep(lpp->rst_intrvl);
|
|
gpiod_set_value_cansleep(lpp->reset_gpio, 0);
|
|
}
|
|
|
|
static int intel_pcie_app_logic_setup(struct intel_pcie_port *lpp)
|
|
{
|
|
intel_pcie_device_rst_deassert(lpp);
|
|
intel_pcie_ltssm_enable(lpp);
|
|
|
|
return dw_pcie_wait_for_link(&lpp->pci);
|
|
}
|
|
|
|
static void intel_pcie_core_irq_disable(struct intel_pcie_port *lpp)
|
|
{
|
|
pcie_app_wr(lpp, PCIE_APP_IRNEN, 0);
|
|
pcie_app_wr(lpp, PCIE_APP_IRNCR, PCIE_APP_IRN_INT);
|
|
}
|
|
|
|
static int intel_pcie_get_resources(struct platform_device *pdev)
|
|
{
|
|
struct intel_pcie_port *lpp = platform_get_drvdata(pdev);
|
|
struct dw_pcie *pci = &lpp->pci;
|
|
struct device *dev = pci->dev;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
|
|
pci->dbi_base = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(pci->dbi_base))
|
|
return PTR_ERR(pci->dbi_base);
|
|
|
|
lpp->core_clk = devm_clk_get(dev, NULL);
|
|
if (IS_ERR(lpp->core_clk)) {
|
|
ret = PTR_ERR(lpp->core_clk);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "Failed to get clks: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
lpp->core_rst = devm_reset_control_get(dev, NULL);
|
|
if (IS_ERR(lpp->core_rst)) {
|
|
ret = PTR_ERR(lpp->core_rst);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "Failed to get resets: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = device_property_match_string(dev, "device_type", "pci");
|
|
if (ret) {
|
|
dev_err(dev, "Failed to find pci device type: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = device_property_read_u32(dev, "reset-assert-ms",
|
|
&lpp->rst_intrvl);
|
|
if (ret)
|
|
lpp->rst_intrvl = RESET_INTERVAL_MS;
|
|
|
|
ret = of_pci_get_max_link_speed(dev->of_node);
|
|
lpp->link_gen = ret < 0 ? 0 : ret;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "app");
|
|
lpp->app_base = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(lpp->app_base))
|
|
return PTR_ERR(lpp->app_base);
|
|
|
|
lpp->phy = devm_phy_get(dev, "pcie");
|
|
if (IS_ERR(lpp->phy)) {
|
|
ret = PTR_ERR(lpp->phy);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev, "Couldn't get pcie-phy: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void intel_pcie_deinit_phy(struct intel_pcie_port *lpp)
|
|
{
|
|
phy_exit(lpp->phy);
|
|
}
|
|
|
|
static int intel_pcie_wait_l2(struct intel_pcie_port *lpp)
|
|
{
|
|
u32 value;
|
|
int ret;
|
|
|
|
if (pcie_link_speed[lpp->max_speed] < PCIE_SPEED_8_0GT)
|
|
return 0;
|
|
|
|
/* Send PME_TURN_OFF message */
|
|
pcie_app_wr_mask(lpp, PCIE_APP_MSG_CR, PCIE_APP_MSG_XMT_PM_TURNOFF,
|
|
PCIE_APP_MSG_XMT_PM_TURNOFF);
|
|
|
|
/* Read PMC status and wait for falling into L2 link state */
|
|
ret = readl_poll_timeout(lpp->app_base + PCIE_APP_PMC, value,
|
|
value & PCIE_APP_PMC_IN_L2, 20,
|
|
jiffies_to_usecs(5 * HZ));
|
|
if (ret)
|
|
dev_err(lpp->pci.dev, "PCIe link enter L2 timeout!\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void intel_pcie_turn_off(struct intel_pcie_port *lpp)
|
|
{
|
|
if (dw_pcie_link_up(&lpp->pci))
|
|
intel_pcie_wait_l2(lpp);
|
|
|
|
/* Put endpoint device in reset state */
|
|
intel_pcie_device_rst_assert(lpp);
|
|
pcie_rc_cfg_wr_mask(lpp, PCI_COMMAND, PCI_COMMAND_MEMORY, 0);
|
|
}
|
|
|
|
static int intel_pcie_host_setup(struct intel_pcie_port *lpp)
|
|
{
|
|
struct device *dev = lpp->pci.dev;
|
|
int ret;
|
|
|
|
intel_pcie_core_rst_assert(lpp);
|
|
intel_pcie_device_rst_assert(lpp);
|
|
|
|
ret = phy_init(lpp->phy);
|
|
if (ret)
|
|
return ret;
|
|
|
|
intel_pcie_core_rst_deassert(lpp);
|
|
|
|
ret = clk_prepare_enable(lpp->core_clk);
|
|
if (ret) {
|
|
dev_err(lpp->pci.dev, "Core clock enable failed: %d\n", ret);
|
|
goto clk_err;
|
|
}
|
|
|
|
if (!lpp->pcie_cap_ofst) {
|
|
ret = dw_pcie_find_capability(&lpp->pci, PCI_CAP_ID_EXP);
|
|
if (!ret) {
|
|
ret = -ENXIO;
|
|
dev_err(dev, "Invalid PCIe capability offset\n");
|
|
goto app_init_err;
|
|
}
|
|
|
|
lpp->pcie_cap_ofst = ret;
|
|
}
|
|
|
|
intel_pcie_rc_setup(lpp);
|
|
ret = intel_pcie_app_logic_setup(lpp);
|
|
if (ret)
|
|
goto app_init_err;
|
|
|
|
/* Enable integrated interrupts */
|
|
pcie_app_wr_mask(lpp, PCIE_APP_IRNEN, PCIE_APP_IRN_INT,
|
|
PCIE_APP_IRN_INT);
|
|
|
|
return 0;
|
|
|
|
app_init_err:
|
|
clk_disable_unprepare(lpp->core_clk);
|
|
clk_err:
|
|
intel_pcie_core_rst_assert(lpp);
|
|
intel_pcie_deinit_phy(lpp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __intel_pcie_remove(struct intel_pcie_port *lpp)
|
|
{
|
|
intel_pcie_core_irq_disable(lpp);
|
|
intel_pcie_turn_off(lpp);
|
|
clk_disable_unprepare(lpp->core_clk);
|
|
intel_pcie_core_rst_assert(lpp);
|
|
intel_pcie_deinit_phy(lpp);
|
|
}
|
|
|
|
static int intel_pcie_remove(struct platform_device *pdev)
|
|
{
|
|
struct intel_pcie_port *lpp = platform_get_drvdata(pdev);
|
|
struct pcie_port *pp = &lpp->pci.pp;
|
|
|
|
dw_pcie_host_deinit(pp);
|
|
__intel_pcie_remove(lpp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused intel_pcie_suspend_noirq(struct device *dev)
|
|
{
|
|
struct intel_pcie_port *lpp = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
intel_pcie_core_irq_disable(lpp);
|
|
ret = intel_pcie_wait_l2(lpp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
intel_pcie_deinit_phy(lpp);
|
|
clk_disable_unprepare(lpp->core_clk);
|
|
return ret;
|
|
}
|
|
|
|
static int __maybe_unused intel_pcie_resume_noirq(struct device *dev)
|
|
{
|
|
struct intel_pcie_port *lpp = dev_get_drvdata(dev);
|
|
|
|
return intel_pcie_host_setup(lpp);
|
|
}
|
|
|
|
static int intel_pcie_rc_init(struct pcie_port *pp)
|
|
{
|
|
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
|
|
struct intel_pcie_port *lpp = dev_get_drvdata(pci->dev);
|
|
|
|
return intel_pcie_host_setup(lpp);
|
|
}
|
|
|
|
/*
|
|
* Dummy function so that DW core doesn't configure MSI
|
|
*/
|
|
static int intel_pcie_msi_init(struct pcie_port *pp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
u64 intel_pcie_cpu_addr(struct dw_pcie *pcie, u64 cpu_addr)
|
|
{
|
|
return cpu_addr + BUS_IATU_OFFSET;
|
|
}
|
|
|
|
static const struct dw_pcie_ops intel_pcie_ops = {
|
|
.cpu_addr_fixup = intel_pcie_cpu_addr,
|
|
};
|
|
|
|
static const struct dw_pcie_host_ops intel_pcie_dw_ops = {
|
|
.host_init = intel_pcie_rc_init,
|
|
.msi_host_init = intel_pcie_msi_init,
|
|
};
|
|
|
|
static const struct intel_pcie_soc pcie_data = {
|
|
.pcie_ver = 0x520A,
|
|
.pcie_atu_offset = 0xC0000,
|
|
.num_viewport = 3,
|
|
};
|
|
|
|
static int intel_pcie_probe(struct platform_device *pdev)
|
|
{
|
|
const struct intel_pcie_soc *data;
|
|
struct device *dev = &pdev->dev;
|
|
struct intel_pcie_port *lpp;
|
|
struct pcie_port *pp;
|
|
struct dw_pcie *pci;
|
|
int ret;
|
|
|
|
lpp = devm_kzalloc(dev, sizeof(*lpp), GFP_KERNEL);
|
|
if (!lpp)
|
|
return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, lpp);
|
|
pci = &lpp->pci;
|
|
pci->dev = dev;
|
|
pp = &pci->pp;
|
|
|
|
ret = intel_pcie_get_resources(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = intel_pcie_ep_rst_init(lpp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
data = device_get_match_data(dev);
|
|
if (!data)
|
|
return -ENODEV;
|
|
|
|
pci->ops = &intel_pcie_ops;
|
|
pci->version = data->pcie_ver;
|
|
pci->atu_base = pci->dbi_base + data->pcie_atu_offset;
|
|
pp->ops = &intel_pcie_dw_ops;
|
|
|
|
ret = dw_pcie_host_init(pp);
|
|
if (ret) {
|
|
dev_err(dev, "Cannot initialize host\n");
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Intel PCIe doesn't configure IO region, so set viewport
|
|
* to not perform IO region access.
|
|
*/
|
|
pci->num_viewport = data->num_viewport;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops intel_pcie_pm_ops = {
|
|
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(intel_pcie_suspend_noirq,
|
|
intel_pcie_resume_noirq)
|
|
};
|
|
|
|
static const struct of_device_id of_intel_pcie_match[] = {
|
|
{ .compatible = "intel,lgm-pcie", .data = &pcie_data },
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver intel_pcie_driver = {
|
|
.probe = intel_pcie_probe,
|
|
.remove = intel_pcie_remove,
|
|
.driver = {
|
|
.name = "intel-gw-pcie",
|
|
.of_match_table = of_intel_pcie_match,
|
|
.pm = &intel_pcie_pm_ops,
|
|
},
|
|
};
|
|
builtin_platform_driver(intel_pcie_driver);
|