]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
PCI: imx6: Add IOMMU and ITS MSI support for i.MX95
authorFrank Li <Frank.Li@nxp.com>
Tue, 14 Jan 2025 20:37:09 +0000 (15:37 -0500)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 15 Jan 2025 20:47:24 +0000 (14:47 -0600)
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>
drivers/pci/controller/dwc/pci-imx6.c

index c8d5c90aa4d45b05b3ebea520a061bbd87464b6f..6ed56ff390d9f903939e2ee0283f417758cb32ed 100644 (file)
 #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,