[PATCH v2 3/3] dwc: PCI: intel: Intel PCIe RC controller driver

Dilip Kota
2019-08-20T09:40:49Z

Add support to PCIe RC controller on Intel Universal
Gateway SoC. PCIe controller is based of Synopsys
Designware pci core.

Signed-off-by: Dilip Kota <eswara.kota@linux.intel.com>
---
drivers/pci/controller/dwc/Kconfig | 13 +
drivers/pci/controller/dwc/Makefile | 1 +
drivers/pci/controller/dwc/pcie-intel-axi.c | 900 ++++++++++++++++++++++++++++
3 files changed, 914 insertions(+)
create mode 100644 drivers/pci/controller/dwc/pcie-intel-axi.c

diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
index 6ea778ae4877..e44b9b6a6390 100644
--- a/drivers/pci/controller/dwc/Kconfig
+++ b/drivers/pci/controller/dwc/Kconfig
@@ -82,6 +82,19 @@ config PCIE_DW_PLAT_EP
order to enable device-specific features PCI_DW_PLAT_EP must be
selected.

+config PCIE_INTEL_AXI
+ bool "Intel AHB/AXI PCIe host controller support"
+ depends on PCI_MSI
+ depends on PCI
+ depends on OF
+ select PCIE_DW_HOST
+ help
+ Say 'Y' here to enable support for Intel AHB/AXI PCIe Host
+ controller driver.
+ The Intel PCIe controller is based on the Synopsys Designware
+ pcie core and therefore uses the Designware core functions to
+ implement the driver.
+
config PCI_EXYNOS
bool "Samsung Exynos PCIe controller"
depends on SOC_EXYNOS5440 || COMPILE_TEST
diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
index b085dfd4fab7..46e656ebdf90 100644
--- a/drivers/pci/controller/dwc/Makefile
+++ b/drivers/pci/controller/dwc/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_PCIE_DW) += pcie-designware.o
obj-$(CONFIG_PCIE_DW_HOST) += pcie-designware-host.o
obj-$(CONFIG_PCIE_DW_EP) += pcie-designware-ep.o
obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o
+obj-$(CONFIG_PCIE_INTEL_AXI) += pcie-intel-axi.o
obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o
obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o
obj-$(CONFIG_PCI_IMX6) += pci-imx6.o
diff --git a/drivers/pci/controller/dwc/pcie-intel-axi.c b/drivers/pci/controller/dwc/pcie-intel-axi.c
new file mode 100644
index 000000000000..2085b580add3
--- /dev/null
+++ b/drivers/pci/controller/dwc/pcie-intel-axi.c
@@ -0,0 +1,900 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PCIe host controller driver for Intel AXI PCIe Bridge
+ *
+ * Copyright (c) 2019 Intel Corporation.
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/of_irq.h>
+#include <linux/of_pci.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include "../../pci.h"
+#include "pcie-designware.h"
+
+#define PCIE_CCRID 0x8
+
+#define PCIE_LCAP 0x7C
+#define PCIE_LCAP_MAX_LINK_SPEED GENMASK(3, 0)
+#define PCIE_LCAP_MAX_LENGTH_WIDTH GENMASK(9, 4)
+
+/* Link Control and Status Register */
+#define PCIE_LCTLSTS 0x80
+#define PCIE_LCTLSTS_ASPM_ENABLE GENMASK(1, 0)
+#define PCIE_LCTLSTS_RCB128 BIT(3)
+#define PCIE_LCTLSTS_LINK_DISABLE BIT(4)
+#define PCIE_LCTLSTS_COM_CLK_CFG BIT(6)
+#define PCIE_LCTLSTS_HW_AW_DIS BIT(9)
+#define PCIE_LCTLSTS_LINK_SPEED GENMASK(19, 16)
+#define PCIE_LCTLSTS_NEGOTIATED_LINK_WIDTH GENMASK(25, 20)
+#define PCIE_LCTLSTS_SLOT_CLK_CFG BIT(28)
+
+#define PCIE_LCTLSTS2 0xA0
+#define PCIE_LCTLSTS2_TGT_LINK_SPEED GENMASK(3, 0)
+#define PCIE_LCTLSTS2_TGT_LINK_SPEED_25GT 0x1
+#define PCIE_LCTLSTS2_TGT_LINK_SPEED_5GT 0x2
+#define PCIE_LCTLSTS2_TGT_LINK_SPEED_8GT 0x3
+#define PCIE_LCTLSTS2_TGT_LINK_SPEED_16GT 0x4
+#define PCIE_LCTLSTS2_HW_AUTO_DIS BIT(5)
+
+/* Ack Frequency Register */
+#define PCIE_AFR 0x70C
+#define PCIE_AFR_FTS_NUM GENMASK(15, 8)
+#define PCIE_AFR_COM_FTS_NUM GENMASK(23, 16)
+#define PCIE_AFR_GEN12_FTS_NUM_DFT (SZ_128 - 1)
+#define PCIE_AFR_GEN3_FTS_NUM_DFT 180
+#define PCIE_AFR_GEN4_FTS_NUM_DFT 196
+
+#define PCIE_PLCR_DLL_LINK_EN BIT(5)
+#define PCIE_PORT_LOGIC_FTS GENMASK(7, 0)
+#define PCIE_PORT_LOGIC_DFT_FTS_NUM (SZ_128 - 1)
+
+#define PCIE_MISC_CTRL 0x8BC
+#define PCIE_MISC_CTRL_DBI_RO_WR_EN BIT(0)
+
+#define PCIE_MULTI_LANE_CTRL 0x8C0
+#define PCIE_UPCONFIG_SUPPORT BIT(7)
+#define PCIE_DIRECT_LINK_WIDTH_CHANGE BIT(6)
+#define PCIE_TARGET_LINK_WIDTH GENMASK(5, 0)
+
+#define PCIE_IOP_CTRL 0x8C4
+#define PCIE_IOP_RX_STANDBY_CTRL GENMASK(6, 0)
+
+/* APP RC Core Control Register */
+#define PCIE_RC_CCR 0x10
+#define PCIE_RC_CCR_LTSSM_ENABLE BIT(0)
+#define PCIE_DEVICE_TYPE GENMASK(7, 4)
+#define PCIE_RC_CCR_RC_MODE BIT(2)
+
+/* PCIe Message Control */
+#define PCIE_MSG_CR 0x30
+#define PCIE_XMT_PM_TURNOFF BIT(0)
+
+/* PCIe Power Management Control */
+#define PCIE_PMC 0x44
+#define PCIE_PM_IN_L2 BIT(20)
+
+/* Interrupt Enable Register */
+#define PCIE_IRNEN 0xF4
+#define PCIE_IRNCR 0xF8
+#define PCIE_IRN_AER_REPORT BIT(0)
+#define PCIE_IRN_PME BIT(2)
+#define PCIE_IRN_HOTPLUG BIT(3)
+#define PCIE_IRN_RX_VDM_MSG BIT(4)
+#define PCIE_IRN_PM_TO_ACK BIT(9)
+#define PCIE_IRN_PM_TURNOFF_ACK BIT(10)
+#define PCIE_IRN_LINK_AUTO_BW_STATUS BIT(11)
+#define PCIE_IRN_BW_MGT BIT(12)
+#define PCIE_IRN_WAKEUP BIT(17)
+#define PCIE_IRN_MSG_LTR BIT(18)
+#define PCIE_IRN_SYS_INT BIT(28)
+#define PCIE_IRN_SYS_ERR_RC BIT(29)
+
+#define PCIE_IRN_IR_INT (PCIE_IRN_AER_REPORT | PCIE_IRN_PME | \
+ PCIE_IRN_RX_VDM_MSG | PCIE_IRN_SYS_ERR_RC | \
+ PCIE_IRN_PM_TO_ACK | PCIE_IRN_LINK_AUTO_BW_STATUS | \
+ PCIE_IRN_BW_MGT | PCIE_IRN_MSG_LTR)
+
+#define PCIE_INTX_OFFSET 12
+#define BUS_IATU_OFFS SZ_256M
+#define RST_INTRVL_DFT_MS 100
+enum {
+ PCIE_LINK_SPEED_AUTO = 0,
+ PCIE_LINK_SPEED_GEN1,
+ PCIE_LINK_SPEED_GEN2,
+ PCIE_LINK_SPEED_GEN3,
+ PCIE_LINK_SPEED_GEN4,
+};
+
+struct intel_pcie_soc {
+ unsigned int pcie_ver;
+ unsigned int pcie_atu_offset;
+ u32 num_viewport;
+};
+
+struct intel_pcie_port {
+ struct dw_pcie *pci;
+ unsigned int id; /* Physical RC Index */
+ void __iomem *app_base;
+ struct gpio_desc *reset_gpio;
+ u32 rst_interval;
+ u32 max_speed;
+ u32 link_gen;
+ u32 max_width;
+ u32 lanes;
+ struct clk *core_clk;
+ struct reset_control *core_rst;
+ struct phy *phy;
+};
+
+static void pcie_update_bits(void __iomem *base, u32 mask, u32 val, u32 ofs)
+{
+ u32 orig, tmp;
+
+ orig = readl(base + ofs);
+
+ tmp = (orig & ~mask) | (val & mask);
+
+ if (tmp != orig)
+ writel(tmp, 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 val, u32 ofs)
+{
+ writel(val, lpp->app_base + ofs);
+}
+
+static void pcie_app_wr_mask(struct intel_pcie_port *lpp,
+ u32 mask, u32 val, u32 ofs)
+{
+ pcie_update_bits(lpp->app_base, mask, val, ofs);
+}
+
+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 val, u32 ofs)
+{
+ dw_pcie_writel_dbi(lpp->pci, ofs, val);
+}
+
+static void pcie_rc_cfg_wr_mask(struct intel_pcie_port *lpp,
+ u32 mask, u32 val, u32 ofs)
+{
+ pcie_update_bits(lpp->pci->dbi_base, mask, val, ofs);
+}
+
+static void intel_pcie_mem_iatu(struct intel_pcie_port *lpp)
+{
+ struct pcie_port *pp = &lpp->pci->pp;
+ phys_addr_t cpu_addr = pp->mem_base;
+
+ dw_pcie_prog_outbound_atu(lpp->pci, PCIE_ATU_REGION_INDEX0,
+ PCIE_ATU_TYPE_MEM, cpu_addr,
+ pp->mem_base, pp->mem_size);
+}
+
+static int intel_pcie_bios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+{
+
+ struct pcie_port *pp = dev->bus->sysdata;
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+ struct intel_pcie_port *lpp = dev_get_drvdata(pci->dev);
+ struct device *pdev = lpp->pci->dev;
+ u32 irq_bit;
+ int irq;
+
+ if (pin == PCI_INTERRUPT_UNKNOWN || pin > PCI_NUM_INTX) {
+ dev_warn(pdev, "WARNING: dev %s: invalid interrupt pin %d\n",
+ pci_name(dev), pin);
+ return -1;
+ }
+ irq = of_irq_parse_and_map_pci(dev, slot, pin);
+ if (!irq) {
+ dev_err(pdev, "trying to map irq for unknown slot:%d pin:%d\n",
+ slot, pin);
+ return -1;
+ }
+ /* Pin to irq offset bit position */
+ irq_bit = BIT(pin + PCIE_INTX_OFFSET);
+
+ /* Clear possible pending interrupts first */
+ pcie_app_wr(lpp, irq_bit, PCIE_IRNCR);
+
+ pcie_app_wr_mask(lpp, irq_bit, irq_bit, PCIE_IRNEN);
+ return irq;
+}
+
+static void intel_pcie_bridge_class_code_setup(struct intel_pcie_port *lpp)
+{
+ pcie_rc_cfg_wr_mask(lpp, PCIE_MISC_CTRL_DBI_RO_WR_EN,
+ PCIE_MISC_CTRL_DBI_RO_WR_EN, PCIE_MISC_CTRL);
+ pcie_rc_cfg_wr_mask(lpp, 0xffffff00, PCI_CLASS_BRIDGE_PCI << 16,
+ PCIE_CCRID);
+ pcie_rc_cfg_wr_mask(lpp, PCIE_MISC_CTRL_DBI_RO_WR_EN, 0,
+ PCIE_MISC_CTRL);
+}
+
+static void intel_pcie_ltssm_enable(struct intel_pcie_port *lpp)
+{
+ pcie_app_wr_mask(lpp, PCIE_RC_CCR_LTSSM_ENABLE,
+ PCIE_RC_CCR_LTSSM_ENABLE, PCIE_RC_CCR);
+}
+
+static void intel_pcie_ltssm_disable(struct intel_pcie_port *lpp)
+{
+ pcie_app_wr_mask(lpp, PCIE_RC_CCR_LTSSM_ENABLE, 0, PCIE_RC_CCR);
+}
+
+static const char *pcie_link_gen_to_str(int gen)
+{
+ switch (gen) {
+ case PCIE_LINK_SPEED_GEN1:
+ return "2.5";
+ case PCIE_LINK_SPEED_GEN2:
+ return "5.0";
+ case PCIE_LINK_SPEED_GEN3:
+ return "8.0";
+ case PCIE_LINK_SPEED_GEN4:
+ return "16.0";
+ default:
+ return "???";
+ }
+}
+
+static void intel_pcie_link_setup(struct intel_pcie_port *lpp)
+{
+ u32 val;
+
+ val = pcie_rc_cfg_rd(lpp, PCIE_LCAP);
+ lpp->max_speed = FIELD_GET(PCIE_LCAP_MAX_LINK_SPEED, val);
+ lpp->max_width = FIELD_GET(PCIE_LCAP_MAX_LENGTH_WIDTH, val);
+
+ val = pcie_rc_cfg_rd(lpp, PCIE_LCTLSTS);
+
+ val &= ~(PCIE_LCTLSTS_LINK_DISABLE | PCIE_LCTLSTS_ASPM_ENABLE);
+ val |= (PCIE_LCTLSTS_SLOT_CLK_CFG | PCIE_LCTLSTS_COM_CLK_CFG |
+ PCIE_LCTLSTS_RCB128);
+ pcie_rc_cfg_wr(lpp, val, PCIE_LCTLSTS);
+}
+
+static void intel_pcie_max_speed_setup(struct intel_pcie_port *lpp)
+{
+ u32 reg, val;
+
+ reg = pcie_rc_cfg_rd(lpp, PCIE_LCTLSTS2);
+ switch (lpp->link_gen) {
+ case PCIE_LINK_SPEED_GEN1:
+ reg &= ~PCIE_LCTLSTS2_TGT_LINK_SPEED;
+ reg |= PCIE_LCTLSTS2_HW_AUTO_DIS |
+ PCIE_LCTLSTS2_TGT_LINK_SPEED_25GT;
+ break;
+ case PCIE_LINK_SPEED_GEN2:
+ reg &= ~PCIE_LCTLSTS2_TGT_LINK_SPEED;
+ reg |= PCIE_LCTLSTS2_HW_AUTO_DIS |
+ PCIE_LCTLSTS2_TGT_LINK_SPEED_5GT;
+ break;
+ case PCIE_LINK_SPEED_GEN3:
+ reg &= ~PCIE_LCTLSTS2_TGT_LINK_SPEED;
+ reg |= PCIE_LCTLSTS2_HW_AUTO_DIS |
+ PCIE_LCTLSTS2_TGT_LINK_SPEED_8GT;
+ break;
+ case PCIE_LINK_SPEED_GEN4:
+ reg &= ~PCIE_LCTLSTS2_TGT_LINK_SPEED;
+ reg |= PCIE_LCTLSTS2_HW_AUTO_DIS |
+ PCIE_LCTLSTS2_TGT_LINK_SPEED_16GT;
+ break;
+ default:
+ /* Use hardware capability */
+ val = pcie_rc_cfg_rd(lpp, PCIE_LCAP);
+ val = FIELD_GET(PCIE_LCAP_MAX_LINK_SPEED, val);
+ reg &= ~PCIE_LCTLSTS2_HW_AUTO_DIS;
+ reg |= val;
+ break;
+ }
+ pcie_rc_cfg_wr(lpp, reg, PCIE_LCTLSTS2);
+}
+
+static void intel_pcie_speed_change_enable(struct intel_pcie_port *lpp)
+{
+ u32 mask, val;
+
+ mask = PORT_LOGIC_SPEED_CHANGE | PCIE_PORT_LOGIC_FTS;
+ val = PORT_LOGIC_SPEED_CHANGE | PCIE_PORT_LOGIC_DFT_FTS_NUM;
+
+ pcie_rc_cfg_wr_mask(lpp, mask, val, PCIE_LINK_WIDTH_SPEED_CONTROL);
+}
+
+static void intel_pcie_speed_change_disable(struct intel_pcie_port *lpp)
+{
+ pcie_rc_cfg_wr_mask(lpp, PORT_LOGIC_SPEED_CHANGE, 0,
+ PCIE_LINK_WIDTH_SPEED_CONTROL);
+}
+
+static void intel_pcie_max_link_width_setup(struct intel_pcie_port *lpp)
+{
+ u32 mask, val;
+
+ /* HW auto bandwidth negotiation must be enabled */
+ pcie_rc_cfg_wr_mask(lpp, PCIE_LCTLSTS_HW_AW_DIS, 0, PCIE_LCTLSTS);
+
+ mask = PCIE_DIRECT_LINK_WIDTH_CHANGE | PCIE_TARGET_LINK_WIDTH;
+ val = PCIE_DIRECT_LINK_WIDTH_CHANGE | lpp->lanes;
+ pcie_rc_cfg_wr_mask(lpp, mask, val, PCIE_MULTI_LANE_CTRL);
+}
+
+static void intel_pcie_port_logic_setup(struct intel_pcie_port *lpp)
+{
+ u32 val, mask, fts;
+
+ switch (lpp->max_speed) {
+ case PCIE_LINK_SPEED_GEN1:
+ case PCIE_LINK_SPEED_GEN2:
+ fts = PCIE_AFR_GEN12_FTS_NUM_DFT;
+ break;
+ case PCIE_LINK_SPEED_GEN3:
+ fts = PCIE_AFR_GEN3_FTS_NUM_DFT;
+ break;
+ case PCIE_LINK_SPEED_GEN4:
+ fts = PCIE_AFR_GEN4_FTS_NUM_DFT;
+ break;
+ default:
+ fts = PCIE_AFR_GEN12_FTS_NUM_DFT;
+ break;
+ }
+ mask = PCIE_AFR_FTS_NUM | PCIE_AFR_COM_FTS_NUM;
+ val = FIELD_PREP(PCIE_AFR_FTS_NUM, fts) |
+ FIELD_PREP(PCIE_AFR_COM_FTS_NUM, fts);
+ pcie_rc_cfg_wr_mask(lpp, mask, val, PCIE_AFR);
+
+ /* Port Link Control Register */
+ pcie_rc_cfg_wr_mask(lpp, PCIE_PLCR_DLL_LINK_EN,
+ PCIE_PLCR_DLL_LINK_EN, PCIE_PORT_LINK_CONTROL);
+}
+
+static void intel_pcie_upconfig_setup(struct intel_pcie_port *lpp)
+{
+ pcie_rc_cfg_wr_mask(lpp, PCIE_UPCONFIG_SUPPORT,
+ PCIE_UPCONFIG_SUPPORT, PCIE_MULTI_LANE_CTRL);
+
+ pcie_rc_cfg_wr_mask(lpp, PCIE_IOP_RX_STANDBY_CTRL, 0, PCIE_IOP_CTRL);
+}
+
+static void intel_pcie_rc_setup(struct intel_pcie_port *lpp)
+{
+ u32 val;
+
+ val = PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
+ pcie_rc_cfg_wr_mask(lpp, val, val, PCI_COMMAND);
+
+ intel_pcie_ltssm_disable(lpp);
+ intel_pcie_link_setup(lpp);
+ dw_pcie_setup(lpp->pci);
+ intel_pcie_upconfig_setup(lpp);
+ intel_pcie_bridge_class_code_setup(lpp);
+ intel_pcie_max_speed_setup(lpp);
+ intel_pcie_speed_change_enable(lpp);
+ intel_pcie_port_logic_setup(lpp);
+ intel_pcie_mem_iatu(lpp);
+}
+
+static int intel_pcie_ep_rst_init(struct intel_pcie_port *lpp)
+{
+ struct device *dev = lpp->pci->dev;
+ int ret = 0;
+
+ 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 100ms */
+ msleep(100);
+
+ return ret;
+}
+
+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_interval);
+ 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 irqreturn_t intel_pcie_core_isr(int irq, void *arg)
+{
+ struct intel_pcie_port *lpp = arg;
+ u32 val, reg;
+
+ reg = pcie_app_rd(lpp, PCIE_IRNCR);
+ val = reg & PCIE_IRN_IR_INT;
+
+ pcie_app_wr(lpp, val, PCIE_IRNCR);
+
+ trace_printk("PCIe misc interrupt status 0x%x\n", reg);
+ return IRQ_HANDLED;
+}
+
+static int intel_pcie_setup_irq(struct intel_pcie_port *lpp)
+{
+ struct device *dev = lpp->pci->dev;
+ struct platform_device *pdev;
+ char *irq_name;
+ int irq, ret;
+
+ pdev = to_platform_device(dev);
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "missing sys integrated irq resource\n");
+ return irq;
+ }
+
+ irq_name = devm_kasprintf(dev, GFP_KERNEL, "pcie_misc%d", lpp->id);
+ if (!irq_name) {
+ dev_err(dev, "failed to alloc irq name\n");
+ return -ENOMEM;
+ }
+
+ ret = devm_request_irq(dev, irq, intel_pcie_core_isr,
+ IRQF_SHARED, irq_name, lpp);
+ if (ret) {
+ dev_err(dev, "request irq %d failed\n", irq);
+ return ret;
+ }
+ /* Enable integrated interrupts */
+ pcie_app_wr_mask(lpp, PCIE_IRN_IR_INT, PCIE_IRN_IR_INT, PCIE_IRNEN);
+
+ return ret;
+}
+
+static void intel_pcie_core_irq_disable(struct intel_pcie_port *lpp)
+{
+ pcie_app_wr(lpp, 0, PCIE_IRNEN);
+ pcie_app_wr(lpp, PCIE_IRN_IR_INT, PCIE_IRNCR);
+}
+
+static void intel_pcie_disable_clks(struct intel_pcie_port *lpp)
+{
+ clk_disable_unprepare(lpp->core_clk);
+}
+
+static int intel_pcie_enable_clks(struct intel_pcie_port *lpp)
+{
+ int ret = clk_prepare_enable(lpp->core_clk);
+
+ if (ret)
+ dev_err(lpp->pci->dev, "Core clock enable failed: %d\n", ret);
+
+ return ret;
+}
+
+static int intel_pcie_get_resources(struct platform_device *pdev)
+{
+ struct intel_pcie_port *lpp;
+ struct device *dev;
+ int ret;
+
+ lpp = platform_get_drvdata(pdev);
+ dev = lpp->pci->dev;
+
+ 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;
+ }
+
+ if (device_property_read_u32(dev, "intel,rst-interval",
+ &lpp->rst_interval))
+ lpp->rst_interval = RST_INTRVL_DFT_MS;
+
+ if (device_property_read_u32(dev, "max-link-speed", &lpp->link_gen))
+ lpp->link_gen = 0; /* Fallback to auto */
+
+ lpp->app_base = devm_platform_ioremap_resource(pdev, 2);
+ if (IS_ERR(lpp->app_base))
+ return PTR_ERR(lpp->app_base);
+
+ ret = intel_pcie_ep_rst_init(lpp);
+ if (ret)
+ return ret;
+
+ lpp->phy = devm_phy_get(dev, "phy");
+ 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 (lpp->max_speed < PCIE_LINK_SPEED_GEN3)
+ return 0;
+
+ /* Send PME_TURN_OFF message */
+ pcie_app_wr_mask(lpp, PCIE_XMT_PM_TURNOFF,
+ PCIE_XMT_PM_TURNOFF, PCIE_MSG_CR);
+
+ /* Read PMC status and wait for falling into L2 link state */
+ ret = readl_poll_timeout(lpp->app_base + PCIE_PMC, value,
+ (value & PCIE_PM_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 EP in reset state */
+ intel_pcie_device_rst_assert(lpp);
+ pcie_rc_cfg_wr_mask(lpp, PCI_COMMAND_MEMORY, 0, PCI_COMMAND);
+}
+
+static int intel_pcie_host_setup(struct intel_pcie_port *lpp)
+{
+ 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 = intel_pcie_enable_clks(lpp);
+ if (ret)
+ goto clk_err;
+
+ intel_pcie_rc_setup(lpp);
+ ret = intel_pcie_app_logic_setup(lpp);
+ if (ret)
+ goto app_init_err;
+
+ ret = intel_pcie_setup_irq(lpp);
+ if (!ret)
+ return ret;
+
+ intel_pcie_turn_off(lpp);
+app_init_err:
+ intel_pcie_disable_clks(lpp);
+clk_err:
+ intel_pcie_core_rst_assert(lpp);
+ intel_pcie_deinit_phy(lpp);
+ return ret;
+}
+
+static ssize_t
+pcie_link_status_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ u32 reg, width, gen;
+ struct intel_pcie_port *lpp;
+
+ lpp = dev_get_drvdata(dev);
+
+ reg = pcie_rc_cfg_rd(lpp, PCIE_LCTLSTS);
+ width = FIELD_GET(PCIE_LCTLSTS_NEGOTIATED_LINK_WIDTH, reg);
+ gen = FIELD_GET(PCIE_LCTLSTS_LINK_SPEED, reg);
+ if (gen > lpp->max_speed)
+ return -EINVAL;
+
+ return sprintf(buf, "Port %2u Width x%u Speed %s GT/s\n", lpp->id,
+ width, pcie_link_gen_to_str(gen));
+}
+static DEVICE_ATTR_RO(pcie_link_status);
+
+static ssize_t pcie_speed_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct intel_pcie_port *lpp;
+ unsigned long val;
+ int ret;
+
+ lpp = dev_get_drvdata(dev);
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ if (val > lpp->max_speed)
+ return -EINVAL;
+
+ lpp->link_gen = val;
+ intel_pcie_max_speed_setup(lpp);
+ intel_pcie_speed_change_disable(lpp);
+ intel_pcie_speed_change_enable(lpp);
+
+ return len;
+}
+static DEVICE_ATTR_WO(pcie_speed);
+
+/*
+ * Link width change on the fly is not always successful.
+ * It also depends on the partner.
+ */
+static ssize_t pcie_width_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct intel_pcie_port *lpp;
+ unsigned long val;
+
+ lpp = dev_get_drvdata(dev);
+
+ if (kstrtoul(buf, 10, &val))
+ return -EINVAL;
+
+ if (val > lpp->max_width)
+ return -EINVAL;
+
+ lpp->lanes = val;
+ intel_pcie_max_link_width_setup(lpp);
+
+ return len;
+}
+static DEVICE_ATTR_WO(pcie_width);
+
+static struct attribute *pcie_cfg_attrs[] = {
+ &dev_attr_pcie_link_status.attr,
+ &dev_attr_pcie_speed.attr,
+ &dev_attr_pcie_width.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(pcie_cfg);
+
+static int intel_pcie_sysfs_init(struct intel_pcie_port *lpp)
+{
+ return devm_device_add_groups(lpp->pci->dev, pcie_cfg_groups);
+}
+
+static void __intel_pcie_remove(struct intel_pcie_port *lpp)
+{
+ pcie_rc_cfg_wr_mask(lpp, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER,
+ 0, PCI_COMMAND);
+ intel_pcie_core_irq_disable(lpp);
+ intel_pcie_turn_off(lpp);
+ intel_pcie_disable_clks(lpp);
+ 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;
+
+ pci_stop_root_bus(pp->root_bus);
+ pci_remove_root_bus(pp->root_bus);
+ __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);
+ intel_pcie_disable_clks(lpp);
+ 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);
+ int ret;
+
+ /* RC/host initialization */
+ ret = intel_pcie_host_setup(lpp);
+ if (ret)
+ return ret;
+ ret = intel_pcie_sysfs_init(lpp);
+ if (ret)
+ __intel_pcie_remove(lpp);
+ return ret;
+}
+
+int intel_pcie_msi_init(struct pcie_port *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+
+ dev_dbg(pci->dev, "MSI is handled in x86 arch\n");
+ return 0;
+}
+
+u64 intel_pcie_cpu_addr(struct dw_pcie *pcie, u64 cpu_addr)
+{
+ return cpu_addr + BUS_IATU_OFFS;
+}
+
+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)
+{
+ struct device *dev = &pdev->dev;
+ const struct intel_pcie_soc *data;
+ 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;
+
+ pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+ if (!pci)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, lpp);
+ lpp->pci = pci;
+ pci->dev = dev;
+ pp = &pci->pp;
+
+ ret = device_property_read_u32(dev, "linux,pci-domain", &lpp->id);
+ if (ret) {
+ dev_err(dev, "failed to get domain id, errno %d\n", ret);
+ return ret;
+ }
+
+ pci->dbi_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(pci->dbi_base))
+ return PTR_ERR(pci->dbi_base);
+
+ ret = intel_pcie_get_resources(pdev);
+ if (ret)
+ return ret;
+
+ data = device_get_match_data(dev);
+ 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;
+ pp->map_irq = intel_pcie_bios_map_irq;
+
+ 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 configure
+ * viewport to not to access IO region during register
+ * read write operations.
+ */
+ pci->num_viewport = data->num_viewport;
+ dev_info(dev,
+ "Intel AXI PCIe Root Complex Port %d Init Done\n", lpp->id);
+ return ret;
+}
+
+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-lgm-pcie",
+ .of_match_table = of_intel_pcie_match,
+ .pm = &intel_pcie_pm_ops,
+ },
+};
+builtin_platform_driver(intel_pcie_driver);
--
2.11.0