[PATCH 3/6] PCI: dwc: fsd: Add FSD PCIe Controller driver support

Rob Herring robh at kernel.org
Wed Nov 30 10:44:24 PST 2022


On Mon, Nov 21, 2022 at 04:22:07PM +0530, Shradha Todi wrote:
> Add PCIe controller driver file for PCIe controller
> found in fsd SoC family. This driver adds support for both RC
> and EP mode.
> 
> Signed-off-by: Niyas Ahmed S T <niyas.ahmed at samsung.com>
> Signed-off-by: Pankaj Dubey <pankaj.dubey at samsung.com>
> Signed-off-by: Padmanabhan Rajanbabu <p.rajanbabu at samsung.com>
> Signed-off-by: Shradha Todi <shradha.t at samsung.com>
> ---
>  drivers/pci/controller/dwc/Kconfig    |   35 +
>  drivers/pci/controller/dwc/Makefile   |    1 +
>  drivers/pci/controller/dwc/pcie-fsd.c | 1021 +++++++++++++++++++++++++
>  3 files changed, 1057 insertions(+)
>  create mode 100644 drivers/pci/controller/dwc/pcie-fsd.c
> 
> diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
> index 62ce3abf0f19..9a3d194c979f 100644
> --- a/drivers/pci/controller/dwc/Kconfig
> +++ b/drivers/pci/controller/dwc/Kconfig
> @@ -14,6 +14,41 @@ config PCIE_DW_EP
>  	bool
>  	select PCIE_DW
>  
> +config PCIE_FSD
> +	bool "Samsung FSD PCIe Controller"
> +	default n
> +	help
> +	  Enables support for the PCIe controller in the FSD SoC. There are
> +	  total three instances of PCIe controller in FSD. This controller
> +	  can work either in RC or EP mode. In order to enable host-specific
> +	  features, PCI_FSD_HOST must be selected and in order to enable
> +	  device-specific feature PCI_FSD_EP must be selected.
> +
> +config PCIE_FSD_HOST
> +	bool "PCIe FSD Host Mode"
> +	depends on PCI
> +	depends on PCI_MSI_IRQ_DOMAIN || PCI_DOMAIN
> +	select PCIE_DW_HOST
> +	select PCIE_FSD
> +	default n
> +	help
> +	  Enables support for the PCIe controller in the FSD SoC to work in
> +	  host (RC) mode. In order to enable host-specific features,
> +	  PCIE_DW_HOST must be selected. PCIE_FSD should be selected for
> +	  fsd controller specific settings.
> +
> +config PCIE_FSD_EP
> +	bool "PCIe FSD Endpoint Mode"
> +	depends on PCI_ENDPOINT
> +	select PCIE_DW_EP
> +	select PCIE_FSD
> +	default n
> +	help
> +	  Enables support for the PCIe controller in the FSD SoC to work in
> +	  endpoint mode. In order to enable device-specific feature
> +	  PCI_FSD_EP must be selected. PCIE_FSD should be selected for
> +	  fsd controller specific settings.
> +
>  config PCI_DRA7XX
>  	tristate
>  
> diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile
> index 8ba7b67f5e50..b76fa6b4e79f 100644
> --- a/drivers/pci/controller/dwc/Makefile
> +++ b/drivers/pci/controller/dwc/Makefile
> @@ -25,6 +25,7 @@ obj-$(CONFIG_PCIE_TEGRA194) += pcie-tegra194.o
>  obj-$(CONFIG_PCIE_UNIPHIER) += pcie-uniphier.o
>  obj-$(CONFIG_PCIE_UNIPHIER_EP) += pcie-uniphier-ep.o
>  obj-$(CONFIG_PCIE_VISCONTI_HOST) += pcie-visconti.o
> +obj-$(CONFIG_PCIE_FSD) += pcie-fsd.o
>  
>  # The following drivers are for devices that use the generic ACPI
>  # pci_root.c driver but don't support standard ECAM config access.
> diff --git a/drivers/pci/controller/dwc/pcie-fsd.c b/drivers/pci/controller/dwc/pcie-fsd.c
> new file mode 100644
> index 000000000000..4531efbfc313
> --- /dev/null
> +++ b/drivers/pci/controller/dwc/pcie-fsd.c
> @@ -0,0 +1,1021 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * PCIe host controller driver for Tesla fsd SoC
> + *
> + * Copyright (C) 2017-2022 Samsung Electronics Co., Ltd. http://www.samsung.com
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>

You shouldn't need this header.

> +#include <linux/of_device.h>
> +#include <linux/of_gpio.h>

You shouldn't need this header.

> +#include <linux/pci.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/resource.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/types.h>
> +
> +#include "pcie-designware.h"
> +
> +#define to_fsd_pcie(x)	dev_get_drvdata((x)->dev)
> +
> +/* PCIe ELBI registers */
> +#define PCIE_APP_LTSSM_ENABLE		0x054
> +#define PCIE_ELBI_LTSSM_ENABLE		0x1
> +#define PCIE_ELBI_LTSSM_DISABLE		0x0
> +#define PCIE_ELBI_CXPL_DEBUG_00_31	0x2C8
> +#define LTSSM_STATE_MASK		0x3f
> +#define LTSSM_STATE_L0			0x11
> +#define PCIE_FSD_DEVICE_TYPE		0x080
> +#define DEVICE_TYPE_RC			0x4
> +#define DEVICE_TYPE_EP			0x0
> +#define IRQ_MSI_ENABLE			BIT(17)
> +#define IRQ0_EN				0x10
> +#define IRQ1_EN				0x14
> +#define IRQ2_EN				0x18
> +#define IRQ5_EN				0x1c
> +#define IRQ0_STS			0x0
> +#define IRQ1_STS			0x4
> +#define IRQ2_STS			0x8
> +#define IRQ5_STS			0xc
> +
> +/* Gen3 Control Register */
> +#define PCIE_GEN3_RELATED_OFF		0x890
> +/* Parameters for equalization feature */
> +#define PCIE_GEN3_EQUALIZATION_DISABLE	BIT(16)
> +#define PCIE_GEN3_EQ_PHASE_2_3		BIT(9)
> +#define PCIE_GEN3_RXEQ_PH01_EN		BIT(12)
> +#define PCIE_GEN3_RXEQ_RGRDLESS_RXTS	BIT(13)
> +
> +/**
> + * struct fsd_pcie - representation of the pci controller
> + * @pci: representation of dwc pcie device structure
> + * @aux_clk: auxiliary clock for the pci block
> + * @dbi_clk: DBI clock
> + * @mstr_clk: master clock
> + * @slv_clk: slave clock
> + * @pdata: private data to determine the oprations supported by device
> + * @appl_base: represent the appl base
> + * @sysreg: represent the system register base
> + * @sysreg_base: represents the offset of the system register required
> + * @phy: represents the phy device associated for the controller
> + */
> +struct fsd_pcie {
> +	struct dw_pcie *pci;
> +	struct clk *aux_clk;
> +	struct clk *dbi_clk;
> +	struct clk *mstr_clk;
> +	struct clk *slv_clk;
> +	const struct fsd_pcie_pdata *pdata;
> +	void __iomem *appl_base;
> +	struct regmap *sysreg;
> +	unsigned int sysreg_base;
> +	struct phy *phy;
> +};
> +
> +enum fsd_pcie_addr_type {
> +	ADDR_TYPE_DBI = 0x0,
> +	ADDR_TYPE_DBI2 = 0x32,
> +	ADDR_TYPE_ATU = 0x36,
> +	ADDR_TYPE_DMA = 0x37,
> +};
> +
> +enum IRQ0_ERR_BITS {
> +	APP_PARITY_ERRS_0,
> +	APP_PARITY_ERRS_1,
> +	APP_PARITY_ERRS_2,
> +	CFG_BW_MGT_INT = 4,
> +	CFG_LINK_AUTO_BW_INT,
> +	CFG_SYS_ERR_RC = 7,
> +	DPA_SUBSTATE_UPDATE,
> +	FLUSH_DONE,
> +	RADM_CORRECTABLE_ERR = 12,
> +	RADM_FATAL_ERR,
> +	RADM_MSG_CPU_ACTIVE = 22,
> +	RADM_MSG_IDLE,
> +	RADM_MSG_LTR,
> +	RADM_MSG_OBFF,
> +	RADM_MSG_UNLOCK,
> +	RADM_NONFATAL_ERR,
> +	RADM_PM_PME,
> +	RADM_PM_TO_ACK,
> +	RADM_PM_TURNOFF,
> +	RADM_VENDOR_MSG,
> +};
> +
> +enum IRQ1_ERR_BITS {
> +	TRGT_CPL_TIMEOUT = 0,
> +	VEN_MSG_GRANT,
> +	VEN_MSI_GRANT,
> +};
> +
> +enum IRQ2_ERR_BITS {
> +	APP_LTR_MSG_GRANT = 0,
> +	APP_OBFF_MSG_GRANT,
> +	CFG_AER_RC_ERR_INT,
> +	CFG_BUS_MASTER_EN,
> +	CFG_LINK_EQ_REQ_INT,
> +	CFG_PME_INT,
> +	EDMA_INT_0,
> +	EDMA_INT_1,
> +	EDMA_INT_2,
> +	EDMA_INT_3,
> +	EDMA_INT_4,
> +	EDMA_INT_5,
> +	EDMA_INT_6,
> +	EDMA_INT_7,
> +	PM_LINKST_IN_L0S = 18,
> +	PM_LINKST_IN_L1,
> +	PM_LINKST_IN_L1SUB_0,
> +	PM_LINKST_IN_L2,
> +	PM_LINKST_L2_EXIT,
> +	PM_XTLH_BLOCK_TLP,
> +	RADM_CPL_TIMEOUT,
> +	RADM_Q_NOT_EMPTY,
> +	RDLH_LINK_UP_0,
> +	SMLH_LINK_UP = 29,
> +	WAKE,
> +	COMPARE_END_CHECKER,
> +};
> +
> +enum IRQ5_ERR_BITS {
> +	LINK_REQ_RST_NOT,
> +	PM_LINKST_IN_L1SUB_1,
> +	RDLH_LINK_UP_1,
> +	SMLH_REQ_RST_NOT,
> +};
> +
> +struct fsd_pcie_res_ops {
> +	int (*get_mem_resources)(struct platform_device *pdev,
> +				 struct fsd_pcie *fsd_ctrl);
> +	int (*get_clk_resources)(struct platform_device *pdev,
> +				 struct fsd_pcie *fsd_ctrl);
> +	int (*init_clk_resources)(struct fsd_pcie *fsd_ctrl);
> +	void (*deinit_clk_resources)(struct fsd_pcie *fsd_ctrl);
> +};
> +
> +struct fsd_pcie_irq {
> +	irqreturn_t (*pcie_msi_irq_handler)(int irq, void *arg);
> +	void (*pcie_msi_init)(struct fsd_pcie *fsd_ctrl);
> +	irqreturn_t (*pcie_sub_ctrl_handler)(int irq, void *arg);
> +};

Why the indirection? You only have 1 version of all these functions.

> +
> +struct fsd_pcie_pdata {
> +	const struct dw_pcie_ops *dwc_ops;
> +	struct dw_pcie_host_ops	*host_ops;
> +	const struct fsd_pcie_res_ops *res_ops;
> +	const struct fsd_pcie_irq *irq_data;
> +	unsigned int appl_cxpl_debug_00_31;
> +	int op_mode;
> +};
> +
> +static int fsd_pcie_get_mem_resources(struct platform_device *pdev,
> +					  struct fsd_pcie *fsd_ctrl)
> +{
> +	struct resource *res;
> +	struct device *dev = &pdev->dev;
> +	int ret;
> +
> +	/* External Local Bus interface(ELBI) Register */
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "appl");
> +	if (!res)
> +		return -EINVAL;
> +	fsd_ctrl->appl_base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(fsd_ctrl->appl_base)) {
> +		dev_err(dev, "Failed to map appl_base\n");
> +		return PTR_ERR(fsd_ctrl->appl_base);
> +	}
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");

The DW core code takes care of this.

> +	if (!res)
> +		return -EINVAL;
> +	fsd_ctrl->pci->dbi_base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(fsd_ctrl->pci->dbi_base)) {
> +		dev_err(dev, "failed to map dbi_base\n");
> +		return PTR_ERR(fsd_ctrl->pci->dbi_base);
> +	}
> +
> +	/* sysreg regmap handle */
> +	fsd_ctrl->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
> +			"tesla,pcie-sysreg");
> +	if (IS_ERR(fsd_ctrl->sysreg)) {
> +		dev_err(dev, "Sysreg regmap lookup failed.\n");
> +		return PTR_ERR(fsd_ctrl->sysreg);
> +	}
> +
> +	ret = of_property_read_u32_index(dev->of_node, "tesla,pcie-sysreg", 1,
> +					 &fsd_ctrl->sysreg_base);
> +	if (ret) {
> +		dev_err(dev, "Couldn't get the register offset for syscon!\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int fsd_pcie_get_clk_resources(struct platform_device *pdev,
> +				       struct fsd_pcie *fsd_ctrl)
> +{
> +	struct device *dev = &pdev->dev;
> +
> +	fsd_ctrl->aux_clk = devm_clk_get(dev, "aux_clk");
> +	if (IS_ERR(fsd_ctrl->aux_clk)) {
> +		dev_err(dev, "couldn't get aux clock\n");
> +		return -EINVAL;
> +	}
> +
> +	fsd_ctrl->dbi_clk = devm_clk_get(dev, "dbi_clk");
> +	if (IS_ERR(fsd_ctrl->dbi_clk)) {
> +		dev_err(dev, "couldn't get dbi clk\n");
> +		return -EINVAL;
> +	}
> +
> +	fsd_ctrl->slv_clk = devm_clk_get(dev, "slv_clk");
> +	if (IS_ERR(fsd_ctrl->slv_clk)) {
> +		dev_err(dev, "couldn't get slave clock\n");
> +		return -EINVAL;
> +	}
> +
> +	fsd_ctrl->mstr_clk = devm_clk_get(dev, "mstr_clk");
> +	if (IS_ERR(fsd_ctrl->mstr_clk)) {
> +		dev_err(dev, "couldn't get master clk\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int fsd_pcie_init_clk_resources(struct fsd_pcie *fsd_ctrl)
> +{
> +	clk_prepare_enable(fsd_ctrl->aux_clk);
> +	clk_prepare_enable(fsd_ctrl->dbi_clk);
> +	clk_prepare_enable(fsd_ctrl->mstr_clk);
> +	clk_prepare_enable(fsd_ctrl->slv_clk);
> +
> +	return 0;
> +}
> +
> +static void fsd_pcie_deinit_clk_resources(struct fsd_pcie *fsd_ctrl)
> +{
> +	clk_disable_unprepare(fsd_ctrl->slv_clk);
> +	clk_disable_unprepare(fsd_ctrl->mstr_clk);
> +	clk_disable_unprepare(fsd_ctrl->dbi_clk);
> +	clk_disable_unprepare(fsd_ctrl->aux_clk);
> +}
> +
> +static const struct fsd_pcie_res_ops fsd_pcie_res_ops_data = {
> +	.get_mem_resources	= fsd_pcie_get_mem_resources,
> +	.get_clk_resources	= fsd_pcie_get_clk_resources,
> +	.init_clk_resources	= fsd_pcie_init_clk_resources,
> +	.deinit_clk_resources	= fsd_pcie_deinit_clk_resources,
> +};
> +
> +static void fsd_pcie_stop_link(struct dw_pcie *pci)
> +{
> +	u32 reg;
> +	struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci);
> +
> +	reg = readl(fsd_ctrl->appl_base + PCIE_APP_LTSSM_ENABLE);
> +	reg &= ~PCIE_ELBI_LTSSM_ENABLE;
> +	writel(reg, fsd_ctrl->appl_base + PCIE_APP_LTSSM_ENABLE);
> +}
> +
> +static int fsd_pcie_establish_link(struct dw_pcie *pci)
> +{
> +	struct device *dev = pci->dev;
> +	struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci);
> +	struct dw_pcie_ep *ep;
> +
> +	if (dw_pcie_link_up(pci)) {
> +		dev_info(dev, "Link already up\n");

Print messages on failure, not normal operation.

> +		return 0;
> +	}
> +
> +	/* assert LTSSM enable */
> +	writel(PCIE_ELBI_LTSSM_ENABLE, fsd_ctrl->appl_base +
> +			PCIE_APP_LTSSM_ENABLE);
> +
> +	/* check if the link is up or not */
> +	if (!dw_pcie_wait_for_link(pci)) {

IIRC, the DW core will do the wait for you.

> +		dev_info(dev, "Link up done successfully\n");
> +		if (fsd_ctrl->pdata->op_mode == DEVICE_TYPE_EP) {
> +			ep = &pci->ep;
> +			dw_pcie_ep_linkup(ep);
> +		}
> +		return 0;
> +	}
> +
> +	if (fsd_ctrl->pdata->op_mode == DEVICE_TYPE_RC) {
> +		/* Return success as link might come up later */
> +		return 0;
> +	}
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static void handle_irq0_interrupts(u32 val, u32 is_en)
> +{
> +	u32 bit_off = 0;
> +
> +	if (val) {
> +		while (bit_off < 32) {
> +			if ((val & (0x1 << bit_off)) == 0 || (is_en &
> +						(0x1 << bit_off)) == 0) {
> +				bit_off++;
> +				continue;
> +			}
> +			switch (bit_off) {
> +			case RADM_VENDOR_MSG:
> +				pr_info("Interrupt received for\n");

Printing messages is not handling an interrupt. Remove all these.

> +				break;
> +			case RADM_PM_TURNOFF:
> +				pr_info("Interrupt received for RADM_PM_TURNOFF\n");
> +				break;
> +			case RADM_PM_TO_ACK:
> +				pr_info("Interrupt received for RADM_PM_TO_ACK\n");
> +				break;
> +			case RADM_PM_PME:
> +				pr_info("Interrupt received for RADM_PM_PME\n");
> +				break;
> +			case RADM_NONFATAL_ERR:
> +				pr_info("Interrupt received for RADM_NONFATAL_ERR\n");
> +				break;
> +			case RADM_MSG_UNLOCK:
> +				pr_info("Interrupt received for RADM_MSG_UNLOCK\n");
> +				break;
> +			case RADM_MSG_OBFF:
> +				pr_info("Interrupt received for RADM_MSG_OBFF\n");
> +				break;
> +			case RADM_MSG_LTR:
> +				pr_info("Interrupt received for RADM_MSG_LTR\n");
> +				break;
> +			case RADM_MSG_IDLE:
> +				pr_info("Interrupt received for RADM_MSG_IDLE\n");
> +				break;
> +			case RADM_MSG_CPU_ACTIVE:
> +				pr_info("Interrupt received for RADM_MSG_CPU_ACTIVE\n");
> +				break;
> +			case RADM_FATAL_ERR:
> +				pr_info("Interrupt received for RADM_FATAL_ERR\n");
> +				break;
> +			case RADM_CORRECTABLE_ERR:
> +				pr_info("Interrupt received for RADM_CORRECTABLE_ERR\n");
> +				break;
> +			case FLUSH_DONE:
> +				pr_info("Interrupt received for FLUSH_DONE\n");
> +				break;
> +			case DPA_SUBSTATE_UPDATE:
> +				pr_info("Interrupt received for DPA_SUBSTATE_UPDATE\n");
> +				break;
> +			case CFG_SYS_ERR_RC:
> +				pr_info("Interrupt received for CFG_SYS_ERR_RC\n");
> +				break;
> +			case CFG_LINK_AUTO_BW_INT:
> +				pr_info("Interrupt received for CFG_LINK_AUTO_BW_INT\n");
> +				break;
> +			case CFG_BW_MGT_INT:
> +				pr_info("Interrupt received for CFG_BW_MGT_INT\n");
> +				break;
> +			case APP_PARITY_ERRS_2:
> +				pr_info("Interrupt received for APP_PARITY_ERRS_2\n");
> +				break;
> +			case APP_PARITY_ERRS_1:
> +				pr_info("Interrupt received for APP_PARITY_ERRS_1\n");
> +				break;
> +			case APP_PARITY_ERRS_0:
> +				pr_info("Interrupt received for APP_PARITY_ERRS_0\n");
> +				break;
> +			default:
> +				pr_info("Unknown Interrupt in IRQ0[%d]\n", bit_off);
> +				break;
> +			}
> +			bit_off++;
> +		}
> +	}
> +}
> +
> +static void handle_irq1_interrupts(u32 val, u32 is_en)
> +{
> +	u32 bit_off = 0;
> +
> +	if (val) {
> +		while (bit_off < 32) {
> +			if ((val & (0x1 << bit_off)) == 0 || (is_en &
> +						(0x1 << bit_off)) == 0) {
> +				bit_off++;
> +				continue;
> +			}
> +			switch (bit_off) {
> +			case TRGT_CPL_TIMEOUT:
> +				pr_info("Interrupt for TRGT_CPL_TIMEOUT\n");
> +				break;
> +			case VEN_MSG_GRANT:
> +				pr_info("Interrupt for VEN_MSG_GRANT\n");
> +				break;
> +			case VEN_MSI_GRANT:
> +				pr_info("Interrupt for VEN_MSI_GRANT\n");
> +				break;
> +			default:
> +				pr_info("Unknown Interrupt in IRQ1[%d]\n", bit_off);
> +				break;
> +			}
> +			bit_off++;
> +		}
> +	}
> +}
> +
> +static void handle_irq2_interrupts(u32 val, u32 is_en)
> +{
> +	u32 bit_off = 0;
> +
> +	if (val) {
> +		while (bit_off < 32) {
> +			if ((val & (0x1 << bit_off)) == 0 || (is_en &
> +						(0x1 << bit_off)) == 0) {
> +				bit_off++;
> +				continue;
> +			}
> +			switch (bit_off) {
> +			/* To indicate that controller has accepted to send
> +			 * Latency Tolerance reporting message
> +			 */
> +			case APP_LTR_MSG_GRANT:
> +				pr_info("Interrupt for APP_LTR_MSG_GRANT\n");
> +				break;
> +			case APP_OBFF_MSG_GRANT:
> +				pr_info("Interrupt for APP_OBFF_MSG_GRANT\n");
> +				break;
> +			case CFG_AER_RC_ERR_INT:
> +				pr_info("Interrupt for CFG_AER_RC_ERR_INT\n");
> +				break;
> +			/* IRQ when bus master is enabled */
> +			case CFG_BUS_MASTER_EN:
> +				pr_info("Interrupt for CFG_BUS_MASTER_EN\n");
> +				break;
> +			/* IRQ to indicate that link Equalization request has been set */
> +			case CFG_LINK_EQ_REQ_INT:
> +				pr_info("Interrupt for CFG_LINK_EQ_REQ_INT\n");
> +				break;
> +			case CFG_PME_INT:
> +				pr_info("Interrupt for CFG_PME_INIT\n");
> +				break;
> +			case EDMA_INT_0:
> +			case EDMA_INT_1:
> +			case EDMA_INT_2:
> +			case EDMA_INT_3:
> +			case EDMA_INT_4:
> +			case EDMA_INT_5:
> +			case EDMA_INT_6:
> +			case EDMA_INT_7:
> +				pr_info("Interrupt for DMA\n");
> +				break;
> +			/* IRQ when link entres L0s */
> +			case PM_LINKST_IN_L0S:
> +				pr_info("Interrupt for PM_LINKST_IN_L0S\n");
> +				break;
> +			/* IRQ when link enters L1 */
> +			case PM_LINKST_IN_L1:
> +				pr_info("Interrupt for PM_LINKST_IN_L1\n");
> +				break;
> +			/* IRQ when link enters L1 substate */
> +			case PM_LINKST_IN_L1SUB_0:
> +				pr_info("Interrupt for PM_LINKST_IN_L1SUB_0\n");
> +				break;
> +			/* IRQ when link enters L2 */
> +			case PM_LINKST_IN_L2:
> +				pr_info("Interrupt for PM_LINKST_IN_L2\n");
> +				break;
> +			/* IRQ when link exits L2 */
> +			case PM_LINKST_L2_EXIT:
> +				pr_info("Interrupt for PM_LINKST_L2_EXIT\n");
> +				break;
> +			/* Indicates that application must stop sending new
> +			 * outbound TLP requests due to current power state
> +			 */
> +			case PM_XTLH_BLOCK_TLP:
> +				pr_info("Interrupt for PM_XTLH_BLOCK_TLP\n");
> +				break;
> +			/* Request failed to complete in time */
> +			case RADM_CPL_TIMEOUT:
> +				pr_info("Interrupt for RADM_CPL_TIMEOUT\n");
> +				break;
> +			/* Level indicating that receive queues contain TLP header/data */
> +			case RADM_Q_NOT_EMPTY:
> +				pr_info("Interrupt for RADM_Q_NOT_EMPTY\n");
> +				break;
> +			/* Data link layer up/down indicator */
> +			case RDLH_LINK_UP_0:
> +				pr_info("Interrupt for RDLH_LINK_UP_0\n");
> +				break;
> +			/* Phy link up/down indicator */
> +			case SMLH_LINK_UP:
> +				pr_info("Interrupt for SMLH_LINK_UP\n");
> +				break;
> +			case WAKE:
> +				pr_info("Interrupt for WAKE\n");
> +				break;
> +			case COMPARE_END_CHECKER:
> +				pr_info("Interrupt for COMPARE_END_CHECKER\n");
> +				break;
> +			default:
> +				pr_info("Unknown Interrupt in IRQ2[%d]\n", bit_off);
> +				break;
> +			}
> +			bit_off++;
> +		}
> +	}
> +}
> +
> +static void handle_irq5_interrupts(u32 val, u32 is_en)
> +{
> +	u32 bit_off = 0;
> +
> +	if (val) {
> +		while (bit_off < 32) {
> +			if ((val & (0x1 << bit_off)) == 0 || (is_en &
> +						(0x1 << bit_off)) == 0) {
> +				bit_off++;
> +				continue;
> +			}
> +			switch (bit_off) {
> +			case LINK_REQ_RST_NOT:
> +				pr_info("Interrupt for LINK_REQ_RST_NOT\n");
> +				break;
> +			case PM_LINKST_IN_L1SUB_1:
> +				pr_info("Interrupt for L1 SUB state Exit\n");
> +				break;
> +			case RDLH_LINK_UP_1:
> +				pr_info("Interrupt for RDLH_LINK_UP_1\n");
> +				break;
> +			/* Reset request because PHY link went down/ or got hot reset */
> +			case SMLH_REQ_RST_NOT:
> +				pr_info("Interrupt for SMLH_REQ_RST_NOT\n");
> +				break;
> +			default:
> +				pr_info("Unknown Interrupt in IRQ5[%d]\n", bit_off);
> +				break;
> +			}
> +			bit_off++;
> +		}
> +	}
> +}
> +
> +/*
> + * fsd_pcie_sub_ctrl_handler : Interrupt handler for all PCIe interrupts.
> + *
> + * These interrupts trigger on different events happening in the PCIe
> + * controller like link status, link entering and exiting low power
> + * states like L0s, L1, DMA completion/abort interrupts, wake being
> + * triggered and other information.
> + *
> + * IRQ_0: (offset 0x0): IRQ for pulse output 1
> + *	Enable these interrupts at offset 0x10
> + * IRQ_1: (offset 0x4): IRQ for pulse output 2
> + *	Enable these interrupts at offset 0x14
> + * IRQ_2: (offset 0x8): IRQ for level output, rising edge
> + *	Enable these interrupts at offset 0x18
> + * IRQ_5: (offset 0xC): IRQ for level output, falling edge
> + *	Enable these interrupts at offset 0x1C
> + */
> +
> +static irqreturn_t fsd_pcie_sub_ctrl_handler(int irq, void *arg)
> +{
> +	u32 irq0, irq1, irq2, irq5;
> +	struct fsd_pcie *fsd_ctrl = arg;
> +	u32 irq0_en, irq1_en, irq2_en, irq5_en;
> +
> +	/* Read IRQ0 status */
> +	irq0 = readl(fsd_ctrl->appl_base + IRQ0_STS);
> +	/* Clear IRQ0 status after storing status value */
> +	writel(irq0, fsd_ctrl->appl_base + IRQ0_STS);
> +
> +	/* Read IRQ1 status */
> +	irq1 = readl(fsd_ctrl->appl_base + IRQ1_STS);
> +	/* Clear IRQ1 status after storing status value */
> +	writel(irq1, fsd_ctrl->appl_base + IRQ1_STS);
> +
> +	/* Read IRQ2 status */
> +	irq2 = readl(fsd_ctrl->appl_base + IRQ2_STS);
> +	/* Clear IRQ2 status after storing status value */
> +	writel(irq2, fsd_ctrl->appl_base + IRQ2_STS);
> +
> +	/* Read IRQ5 status */
> +	irq5 = readl(fsd_ctrl->appl_base + IRQ5_STS);
> +	/* Clear IRQ5 status after storing status value */
> +	writel(irq5, fsd_ctrl->appl_base + IRQ5_STS);
> +
> +	irq0_en = readl(fsd_ctrl->appl_base + IRQ0_EN);
> +	irq1_en = readl(fsd_ctrl->appl_base + IRQ1_EN);
> +	irq2_en = readl(fsd_ctrl->appl_base + IRQ2_EN);
> +	irq5_en = readl(fsd_ctrl->appl_base + IRQ5_EN);
> +	/* Handle all interrupts */
> +	handle_irq0_interrupts(irq0, irq0_en);
> +	handle_irq1_interrupts(irq1, irq1_en);
> +	handle_irq2_interrupts(irq2, irq2_en);
> +	handle_irq5_interrupts(irq5, irq5_en);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t fsd_pcie_msi_irq_handler(int irq, void *arg)
> +{
> +	u32 val;
> +	struct fsd_pcie *fsd_ctrl = arg;
> +	struct dw_pcie *pci = fsd_ctrl->pci;
> +	struct dw_pcie_rp *pp = &pci->pp;
> +
> +	val = readl(fsd_ctrl->appl_base + IRQ2_STS);
> +
> +	if ((val & IRQ_MSI_ENABLE) == IRQ_MSI_ENABLE) {
> +		val &= IRQ_MSI_ENABLE;
> +		writel(val, fsd_ctrl->appl_base + IRQ2_STS);
> +		dw_handle_msi_irq(pp);
> +	} else {
> +		fsd_pcie_sub_ctrl_handler(irq, arg);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void fsd_pcie_msi_init(struct fsd_pcie *fsd_ctrl)
> +{
> +	int val;
> +
> +	/* enable MSI interrupt */
> +	val = readl(fsd_ctrl->appl_base + IRQ2_EN);
> +	val |= IRQ_MSI_ENABLE;
> +	writel(val, fsd_ctrl->appl_base + IRQ2_EN);
> +}
> +
> +static void fsd_pcie_enable_interrupts(struct fsd_pcie *fsd_ctrl)
> +{
> +	if (IS_ENABLED(CONFIG_PCI_MSI))
> +		fsd_ctrl->pdata->irq_data->pcie_msi_init(fsd_ctrl);
> +}
> +
> +static u32 fsd_pcie_read_dbi(struct dw_pcie *pci, void __iomem *base,
> +				u32 reg, size_t size)
> +{
> +	bool is_atu = false;
> +	struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci);
> +	u32 val;
> +
> +	if (pci->atu_base) {
> +		if (base >= pci->atu_base) {
> +
> +			is_atu = true;
> +			regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base,
> +					ADDR_TYPE_ATU);
> +			base = base - DEFAULT_DBI_ATU_OFFSET;
> +		}
> +	}
> +
> +	dw_pcie_read(base + reg, size, &val);
> +
> +	if (is_atu)
> +		regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_DBI);

You've got the same code twice. Rework this to be a common function 
that does just:

if (ATU)
    regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_ATU);
else
    regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_DBI);

Then you just call it followed by dw_pcie_read or dw_pcie_write.

> +
> +	return val;
> +}
> +
> +static void fsd_pcie_write_dbi(struct dw_pcie *pci, void __iomem *base,
> +				u32 reg, size_t size, u32 val)
> +{
> +	struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci);
> +	bool is_atu = false;
> +
> +	if (pci->atu_base) {
> +		if (base >= pci->atu_base) {
> +			is_atu = true;
> +			regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base,
> +					ADDR_TYPE_ATU);
> +			base = base - DEFAULT_DBI_ATU_OFFSET;
> +		}
> +	}
> +
> +	dw_pcie_write(base + reg, size, val);
> +
> +	if (is_atu)
> +		regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_DBI);
> +}
> +
> +static void fsd_pcie_write_dbi2(struct dw_pcie *pci, void __iomem *base,
> +				u32 reg, size_t size, u32 val)
> +{
> +	struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci);
> +
> +	regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_DBI2);
> +	dw_pcie_write(pci->dbi_base + reg, size, val);
> +	regmap_write(fsd_ctrl->sysreg, fsd_ctrl->sysreg_base, ADDR_TYPE_DBI);
> +}
> +
> +static int fsd_pcie_link_up(struct dw_pcie *pci)
> +{
> +	u32 val;
> +	struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci);
> +
> +	val = readl(fsd_ctrl->appl_base +
> +			fsd_ctrl->pdata->appl_cxpl_debug_00_31);
> +
> +	return (val & LTSSM_STATE_MASK) == LTSSM_STATE_L0;
> +}
> +
> +static int fsd_pcie_host_init(struct dw_pcie_rp *pp)
> +{
> +	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> +	struct fsd_pcie *fsd_ctrl = to_fsd_pcie(pci);
> +
> +	dw_pcie_writel_dbi(pci, PCIE_GEN3_RELATED_OFF,
> +				(PCIE_GEN3_EQ_PHASE_2_3 |
> +				 PCIE_GEN3_RXEQ_PH01_EN |
> +				 PCIE_GEN3_RXEQ_RGRDLESS_RXTS));
> +
> +	fsd_pcie_enable_interrupts(fsd_ctrl);
> +
> +	return 0;
> +}
> +
> +static struct dw_pcie_host_ops fsd_pcie_host_ops = {
> +	.host_init = fsd_pcie_host_init,
> +};
> +
> +static int fsd_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
> +				 enum pci_epc_irq_type type, u16 interrupt_num)
> +{
> +	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
> +
> +	switch (type) {
> +	case PCI_EPC_IRQ_LEGACY:
> +		dev_err(pci->dev, "EP does not support 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 const struct pci_epc_features fsd_pcie_epc_features = {
> +	.linkup_notifier = false,
> +	.msi_capable = true,
> +	.msix_capable = false,
> +};
> +
> +static const struct pci_epc_features*
> +	fsd_pcie_get_features(struct dw_pcie_ep *ep)
> +{
> +	return &fsd_pcie_epc_features;
> +}
> +
> +static struct dw_pcie_ep_ops fsd_dw_pcie_ep_ops = {
> +	.raise_irq	= fsd_pcie_raise_irq,
> +	.get_features	= fsd_pcie_get_features,
> +};
> +
> +static const struct fsd_pcie_irq fsd_pcie_irq_data = {
> +	.pcie_msi_irq_handler	= fsd_pcie_msi_irq_handler,
> +	.pcie_msi_init		= fsd_pcie_msi_init,
> +	.pcie_sub_ctrl_handler	= fsd_pcie_sub_ctrl_handler,
> +};
> +
> +static int __init fsd_add_pcie_ep(struct fsd_pcie *fsd_ctrl,
> +		struct platform_device *pdev)
> +{
> +	struct dw_pcie_ep *ep;
> +	struct dw_pcie *pci = fsd_ctrl->pci;
> +	int ret;
> +	struct device *dev = &pdev->dev;
> +
> +	ep = &pci->ep;
> +	ep->ops = &fsd_dw_pcie_ep_ops;
> +
> +	dw_pcie_writel_dbi(pci, PCIE_GEN3_RELATED_OFF,
> +				(PCIE_GEN3_EQUALIZATION_DISABLE |
> +				 PCIE_GEN3_RXEQ_PH01_EN |
> +				 PCIE_GEN3_RXEQ_RGRDLESS_RXTS));
> +
> +	ret = dw_pcie_ep_init(ep);
> +	if (ret)
> +		dev_err(dev, "failed to initialize endpoint\n");
> +
> +	return ret;
> +}
> +
> +static int __init fsd_add_pcie_port(struct fsd_pcie *fsd_ctrl,
> +					struct platform_device *pdev)
> +{
> +	int irq;
> +	struct device *dev = &pdev->dev;
> +	int irq_flags;
> +	int ret;
> +	struct dw_pcie *pci = fsd_ctrl->pci;
> +	struct dw_pcie_rp *pp = &pci->pp;
> +
> +	if (IS_ENABLED(CONFIG_PCI_MSI)) {
> +		irq = platform_get_irq_byname(pdev, "msi");

Wasn't in the binding. And the DWC core does this for you.

> +		if (!irq) {
> +			dev_err(dev, "failed to get msi irq\n");
> +			return -ENODEV;
> +		}
> +
> +		irq_flags = IRQF_TRIGGER_RISING | IRQF_SHARED | IRQF_NO_THREAD;
> +
> +		ret = devm_request_irq(dev, irq,
> +					fsd_ctrl->pdata->irq_data->pcie_msi_irq_handler,
> +					irq_flags, "fsd-pcie", fsd_ctrl);
> +		if (ret) {
> +			dev_err(dev, "failed to request msi irq\n");
> +			return ret;
> +		}
> +		pp->msi_irq[0] = -ENODEV;
> +	}
> +
> +	ret = dw_pcie_host_init(pp);
> +	if (ret)
> +		dev_err(dev, "failed to initialize host\n");
> +
> +	return ret;
> +}
> +
> +static const struct dw_pcie_ops fsd_dw_pcie_ops = {
> +	.read_dbi	= fsd_pcie_read_dbi,
> +	.write_dbi	= fsd_pcie_write_dbi,
> +	.write_dbi2	= fsd_pcie_write_dbi2,
> +	.start_link	= fsd_pcie_establish_link,
> +	.stop_link	= fsd_pcie_stop_link,
> +	.link_up	= fsd_pcie_link_up,
> +};
> +
> +static int fsd_pcie_probe(struct platform_device *pdev)
> +{
> +	int ret;
> +	int irq, irq_flags;
> +	struct dw_pcie *pci;
> +	struct dw_pcie_rp *pp;
> +	struct fsd_pcie *fsd_ctrl;
> +	struct device *dev = &pdev->dev;
> +	const struct fsd_pcie_pdata *pdata;
> +	struct device_node *np = dev->of_node;
> +
> +	fsd_ctrl = devm_kzalloc(dev, sizeof(*fsd_ctrl), GFP_KERNEL);
> +	if (!fsd_ctrl)
> +		return -ENOMEM;
> +
> +	pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
> +	if (!pci)
> +		return -ENOMEM;
> +
> +	pdata = (const struct fsd_pcie_pdata *) of_device_get_match_data(dev);
> +
> +	fsd_ctrl->pci = pci;
> +	fsd_ctrl->pdata = pdata;
> +
> +	pci->dev = dev;
> +	pci->ops = pdata->dwc_ops;
> +	pci->dbi_base2 = NULL;
> +	pci->dbi_base = NULL;
> +	pci->atu_base = NULL;
> +	pp = &pci->pp;
> +	pp->ops = fsd_ctrl->pdata->host_ops;
> +
> +	fsd_ctrl->phy = devm_of_phy_get(dev, np, NULL);

Use the non-DT version.

> +	if (IS_ERR(fsd_ctrl->phy)) {
> +		if (PTR_ERR(fsd_ctrl->phy) == -EPROBE_DEFER)
> +			return PTR_ERR(fsd_ctrl->phy);
> +	}
> +
> +	phy_init(fsd_ctrl->phy);
> +
> +	if (pdata->res_ops && pdata->res_ops->get_mem_resources) {
> +		ret = pdata->res_ops->get_mem_resources(pdev, fsd_ctrl);

Again, get rid of the indirection. If it does vary, don't invent your 
own ops. We only need 1 level of abstraction.

> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (pdata->res_ops && pdata->res_ops->get_clk_resources) {
> +		ret = pdata->res_ops->get_clk_resources(pdev, fsd_ctrl);
> +		if (ret)
> +			return ret;
> +		ret = pdata->res_ops->init_clk_resources(fsd_ctrl);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	platform_set_drvdata(pdev, fsd_ctrl);
> +
> +	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
> +	if (ret)
> +		goto fail_dma_set;
> +
> +	switch (fsd_ctrl->pdata->op_mode) {
> +	case DEVICE_TYPE_RC:
> +		writel(DEVICE_TYPE_RC, fsd_ctrl->appl_base +
> +					PCIE_FSD_DEVICE_TYPE);
> +		ret = fsd_add_pcie_port(fsd_ctrl, pdev);
> +		if (ret)
> +			goto fail_add_pcie_port;
> +		break;
> +	case DEVICE_TYPE_EP:
> +		writel(DEVICE_TYPE_EP, fsd_ctrl->appl_base +
> +				PCIE_FSD_DEVICE_TYPE);
> +
> +		ret = fsd_add_pcie_ep(fsd_ctrl, pdev);
> +		if (ret)
> +			goto fail_add_pcie_ep;
> +		break;
> +	}
> +
> +	irq = platform_get_irq_byname(pdev, "sub_ctrl_intr");
> +	if (irq > 0) {
> +
> +		irq_flags = IRQF_TRIGGER_RISING | IRQF_SHARED | IRQF_NO_THREAD;
> +
> +		ret = devm_request_irq(dev, irq,
> +				fsd_ctrl->pdata->irq_data->pcie_sub_ctrl_handler,
> +				irq_flags, "fsd-sub-ctrl-pcie", fsd_ctrl);
> +		if (ret)
> +			dev_err(dev, "failed to request sub ctrl irq\n");
> +	}
> +
> +	dev_info(dev, "FSD PCIe probe completed successfully\n");
> +
> +	return 0;
> +
> +fail_dma_set:
> +	dev_err(dev, "PCIe Failed to set 36 bit dma mask\n");
> +fail_add_pcie_port:
> +	phy_exit(fsd_ctrl->phy);
> +fail_add_pcie_ep:
> +	if (pdata->res_ops && pdata->res_ops->deinit_clk_resources)
> +		pdata->res_ops->deinit_clk_resources(fsd_ctrl);
> +	return ret;
> +}
> +
> +static int __exit fsd_pcie_remove(struct platform_device *pdev)
> +{
> +	struct fsd_pcie *fsd_ctrl = platform_get_drvdata(pdev);
> +	const struct fsd_pcie_pdata *pdata = fsd_ctrl->pdata;
> +
> +	if (pdata->res_ops && pdata->res_ops->deinit_clk_resources)
> +		pdata->res_ops->deinit_clk_resources(fsd_ctrl);
> +
> +	return 0;
> +}
> +
> +static const struct fsd_pcie_pdata fsd_pcie_rc_pdata = {
> +	.dwc_ops		= &fsd_dw_pcie_ops,
> +	.host_ops		= &fsd_pcie_host_ops,
> +	.res_ops		= &fsd_pcie_res_ops_data,
> +	.irq_data		= &fsd_pcie_irq_data,
> +	.appl_cxpl_debug_00_31	= PCIE_ELBI_CXPL_DEBUG_00_31,
> +	.op_mode		= DEVICE_TYPE_RC,
> +};
> +
> +static const struct fsd_pcie_pdata fsd_pcie_ep_pdata = {
> +	.dwc_ops		= &fsd_dw_pcie_ops,
> +	.host_ops		= &fsd_pcie_host_ops,
> +	.res_ops		= &fsd_pcie_res_ops_data,
> +	.irq_data		= &fsd_pcie_irq_data,
> +	.appl_cxpl_debug_00_31	= PCIE_ELBI_CXPL_DEBUG_00_31,
> +	.op_mode		= DEVICE_TYPE_EP,
> +};
> +
> +static const struct of_device_id fsd_pcie_of_match[] = {
> +	{
> +		.compatible = "tesla,fsd-pcie",
> +		.data = (void *) &fsd_pcie_rc_pdata,
> +	},
> +	{
> +		.compatible = "tesla,fsd-pcie-ep",
> +		.data = (void *) &fsd_pcie_ep_pdata,
> +	},
> +
> +	{},
> +};
> +
> +static struct platform_driver fsd_pcie_driver = {
> +	.probe		= fsd_pcie_probe,
> +	.remove		= __exit_p(fsd_pcie_remove),
> +	.driver		= {
> +		.name	= "fsd-pcie",
> +		.of_match_table = fsd_pcie_of_match,
> +	},
> +};
> +
> +static int __init fsd_pcie_init(void)
> +{
> +	return platform_driver_register(&fsd_pcie_driver);
> +}
> +module_init(fsd_pcie_init);
> -- 
> 2.17.1
> 
> 



More information about the linux-phy mailing list