diff options
Diffstat (limited to 'drivers/pci/controller/dwc/pci-imx6.c')
| -rw-r--r-- | drivers/pci/controller/dwc/pci-imx6.c | 208 | 
1 files changed, 207 insertions, 1 deletions
| diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index c8d5c90aa4d4..6ed56ff390d9 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -55,6 +55,22 @@  #define IMX95_PE0_GEN_CTRL_3			0x1058  #define IMX95_PCIE_LTSSM_EN			BIT(0) +#define IMX95_PE0_LUT_ACSCTRL			0x1008 +#define IMX95_PEO_LUT_RWA			BIT(16) +#define IMX95_PE0_LUT_ENLOC			GENMASK(4, 0) + +#define IMX95_PE0_LUT_DATA1			0x100c +#define IMX95_PE0_LUT_VLD			BIT(31) +#define IMX95_PE0_LUT_DAC_ID			GENMASK(10, 8) +#define IMX95_PE0_LUT_STREAM_ID			GENMASK(5, 0) + +#define IMX95_PE0_LUT_DATA2			0x1010 +#define IMX95_PE0_LUT_REQID			GENMASK(31, 16) +#define IMX95_PE0_LUT_MASK			GENMASK(15, 0) + +#define IMX95_SID_MASK				GENMASK(5, 0) +#define IMX95_MAX_LUT				32 +  #define to_imx_pcie(x)	dev_get_drvdata((x)->dev)  enum imx_pcie_variants { @@ -87,6 +103,7 @@ enum imx_pcie_variants {   * workaround suspend resume on some devices which are affected by this errata.   */  #define IMX_PCIE_FLAG_BROKEN_SUSPEND		BIT(9) +#define IMX_PCIE_FLAG_HAS_LUT			BIT(10)  #define imx_check_flag(pci, val)	(pci->drvdata->flags & val) @@ -139,6 +156,9 @@ struct imx_pcie {  	struct device		*pd_pcie_phy;  	struct phy		*phy;  	const struct imx_pcie_drvdata *drvdata; + +	/* Ensure that only one device's LUT is configured at any given time */ +	struct mutex		lock;  };  /* Parameters for the waiting for PCIe PHY PLL to lock on i.MX7 */ @@ -930,6 +950,184 @@ static void imx_pcie_stop_link(struct dw_pcie *pci)  	imx_pcie_ltssm_disable(dev);  } +static int imx_pcie_add_lut(struct imx_pcie *imx_pcie, u16 rid, u8 sid) +{ +	struct dw_pcie *pci = imx_pcie->pci; +	struct device *dev = pci->dev; +	u32 data1, data2; +	int free = -1; +	int i; + +	if (sid >= 64) { +		dev_err(dev, "Invalid SID for index %d\n", sid); +		return -EINVAL; +	} + +	guard(mutex)(&imx_pcie->lock); + +	/* +	 * Iterate through all LUT entries to check for duplicate RID and +	 * identify the first available entry. Configure this available entry +	 * immediately after verification to avoid rescanning it. +	 */ +	for (i = 0; i < IMX95_MAX_LUT; i++) { +		regmap_write(imx_pcie->iomuxc_gpr, +			     IMX95_PE0_LUT_ACSCTRL, IMX95_PEO_LUT_RWA | i); +		regmap_read(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA1, &data1); + +		if (!(data1 & IMX95_PE0_LUT_VLD)) { +			if (free < 0) +				free = i; +			continue; +		} + +		regmap_read(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA2, &data2); + +		/* Do not add duplicate RID */ +		if (rid == FIELD_GET(IMX95_PE0_LUT_REQID, data2)) { +			dev_warn(dev, "Existing LUT entry available for RID (%d)", rid); +			return 0; +		} +	} + +	if (free < 0) { +		dev_err(dev, "LUT entry is not available\n"); +		return -ENOSPC; +	} + +	data1 = FIELD_PREP(IMX95_PE0_LUT_DAC_ID, 0); +	data1 |= FIELD_PREP(IMX95_PE0_LUT_STREAM_ID, sid); +	data1 |= IMX95_PE0_LUT_VLD; +	regmap_write(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA1, data1); + +	data2 = IMX95_PE0_LUT_MASK; /* Match all bits of RID */ +	data2 |= FIELD_PREP(IMX95_PE0_LUT_REQID, rid); +	regmap_write(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA2, data2); + +	regmap_write(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_ACSCTRL, free); + +	return 0; +} + +static void imx_pcie_remove_lut(struct imx_pcie *imx_pcie, u16 rid) +{ +	u32 data2; +	int i; + +	guard(mutex)(&imx_pcie->lock); + +	for (i = 0; i < IMX95_MAX_LUT; i++) { +		regmap_write(imx_pcie->iomuxc_gpr, +			     IMX95_PE0_LUT_ACSCTRL, IMX95_PEO_LUT_RWA | i); +		regmap_read(imx_pcie->iomuxc_gpr, IMX95_PE0_LUT_DATA2, &data2); +		if (FIELD_GET(IMX95_PE0_LUT_REQID, data2) == rid) { +			regmap_write(imx_pcie->iomuxc_gpr, +				     IMX95_PE0_LUT_DATA1, 0); +			regmap_write(imx_pcie->iomuxc_gpr, +				     IMX95_PE0_LUT_DATA2, 0); +			regmap_write(imx_pcie->iomuxc_gpr, +				     IMX95_PE0_LUT_ACSCTRL, i); + +			break; +		} +	} +} + +static int imx_pcie_enable_device(struct pci_host_bridge *bridge, +				  struct pci_dev *pdev) +{ +	struct imx_pcie *imx_pcie = to_imx_pcie(to_dw_pcie_from_pp(bridge->sysdata)); +	u32 sid_i, sid_m, rid = pci_dev_id(pdev); +	struct device_node *target; +	struct device *dev; +	int err_i, err_m; +	u32 sid = 0; + +	dev = imx_pcie->pci->dev; + +	target = NULL; +	err_i = of_map_id(dev->of_node, rid, "iommu-map", "iommu-map-mask", +			  &target, &sid_i); +	if (target) { +		of_node_put(target); +	} else { +		/* +		 * "target == NULL && err_i == 0" means RID out of map range. +		 * Use 1:1 map RID to streamID. Hardware can't support this +		 * because the streamID is only 6 bits +		 */ +		err_i = -EINVAL; +	} + +	target = NULL; +	err_m = of_map_id(dev->of_node, rid, "msi-map", "msi-map-mask", +			  &target, &sid_m); + +	/* +	 *   err_m      target +	 *	0	NULL		RID out of range. Use 1:1 map RID to +	 *				streamID, Current hardware can't +	 *				support it, so return -EINVAL. +	 *      != 0    NULL		msi-map does not exist, use built-in MSI +	 *	0	!= NULL		Get correct streamID from RID +	 *	!= 0	!= NULL		Invalid combination +	 */ +	if (!err_m && !target) +		return -EINVAL; +	else if (target) +		of_node_put(target); /* Find streamID map entry for RID in msi-map */ + +	/* +	 * msi-map        iommu-map +	 *   N                N            DWC MSI Ctrl +	 *   Y                Y            ITS + SMMU, require the same SID +	 *   Y                N            ITS +	 *   N                Y            DWC MSI Ctrl + SMMU +	 */ +	if (err_i && err_m) +		return 0; + +	if (!err_i && !err_m) { +		/* +		 *	    Glue Layer +		 *          <==========> +		 * ┌─────┐                  ┌──────────┐ +		 * │ LUT │ 6-bit streamID   │          │ +		 * │     │─────────────────►│  MSI     │ +		 * └─────┘   2-bit ctrl ID  │          │ +		 *             ┌───────────►│          │ +		 *  (i.MX95)   │            │          │ +		 *  00 PCIe0   │            │          │ +		 *  01 ENETC   │            │          │ +		 *  10 PCIe1   │            │          │ +		 *             │            └──────────┘ +		 * The MSI glue layer auto adds 2 bits controller ID ahead of +		 * streamID, so mask these 2 bits to get streamID. The +		 * IOMMU glue layer doesn't do that. +		 */ +		if (sid_i != (sid_m & IMX95_SID_MASK)) { +			dev_err(dev, "iommu-map and msi-map entries mismatch!\n"); +			return -EINVAL; +		} +	} + +	if (!err_i) +		sid = sid_i; +	else if (!err_m) +		sid = sid_m & IMX95_SID_MASK; + +	return imx_pcie_add_lut(imx_pcie, rid, sid); +} + +static void imx_pcie_disable_device(struct pci_host_bridge *bridge, +				    struct pci_dev *pdev) +{ +	struct imx_pcie *imx_pcie; + +	imx_pcie = to_imx_pcie(to_dw_pcie_from_pp(bridge->sysdata)); +	imx_pcie_remove_lut(imx_pcie, pci_dev_id(pdev)); +} +  static int imx_pcie_host_init(struct dw_pcie_rp *pp)  {  	struct dw_pcie *pci = to_dw_pcie_from_pp(pp); @@ -946,6 +1144,11 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp)  		}  	} +	if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) { +		pp->bridge->enable_device = imx_pcie_enable_device; +		pp->bridge->disable_device = imx_pcie_disable_device; +	} +  	imx_pcie_assert_core_reset(imx_pcie);  	if (imx_pcie->drvdata->init_phy) @@ -1330,6 +1533,8 @@ static int imx_pcie_probe(struct platform_device *pdev)  	imx_pcie->pci = pci;  	imx_pcie->drvdata = of_device_get_match_data(dev); +	mutex_init(&imx_pcie->lock); +  	/* Find the PHY if one is defined, only imx7d uses it */  	np = of_parse_phandle(node, "fsl,imx7d-pcie-phy", 0);  	if (np) { @@ -1627,7 +1832,8 @@ static const struct imx_pcie_drvdata drvdata[] = {  	},  	[IMX95] = {  		.variant = IMX95, -		.flags = IMX_PCIE_FLAG_HAS_SERDES, +		.flags = IMX_PCIE_FLAG_HAS_SERDES | +			 IMX_PCIE_FLAG_HAS_LUT,  		.clk_names = imx8mq_clks,  		.clks_cnt = ARRAY_SIZE(imx8mq_clks),  		.ltssm_off = IMX95_PE0_GEN_CTRL_3, | 
