PCI: imx6: Add IOMMU and ITS MSI support for i.MX95

For the i.MX95, the configuration of a LUT is necessary to convert PCIe
Requester IDs (RIDs) to StreamIDs, which are used by both IOMMU and ITS.
This involves checking msi-map and iommu-map device tree properties to
ensure consistent mapping of Requester IDs to the same StreamIDs.

Subsequently, LUT-related registers are configured. If a msi-map isn't
detected, the platform relies on DWC built-in controller for MSIs that
do not need StreamIDs.

Implement PCI bus callback function to handle enable_device() and
disable_device() operations, setting up the LUT whenever a new PCI
device is enabled.

Known limitations:

  - If iommu-map exists in the device tree but the IOMMU controller is
    disabled, StreamIDs are programmed into the LUT. However, if a RID
    is out of range of the iommu-map, enabling the PCI device would
    result in a failure, although the PCI device can work without the
    IOMMU.

  - If msi-map exists in the device tree but the MSI controller is
    disabled, MSIs will not work. The DWC driver skips initializing the
    built-in MSI controller, falling back to legacy PCI INTx only.

Link: https://lore.kernel.org/r/20250114-imx95_lut-v9-2-39f58dbed03a@nxp.com
Signed-off-by: Frank Li <Frank.Li@nxp.com>
[kwilczynski: commit log]
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
[bhelgaas: fix uninitialized "sid" in imx_pcie_enable_device()]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Acked-by: Richard Zhu <hongxing.zhu@nxp.com>
This commit is contained in:
Frank Li
2025-01-14 15:37:09 -05:00
committed by Bjorn Helgaas
parent a3751212a8
commit ce4c430172

View File

@@ -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,