Merge tag 'dt-5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc
[linux-2.6-microblaze.git] / drivers / bus / fsl-mc / fsl-mc-bus.c
index 09c8ab5..8fd4a35 100644 (file)
@@ -63,11 +63,14 @@ struct fsl_mc_addr_translation_range {
 
 #define FSL_MC_GCR1    0x0
 #define GCR1_P1_STOP   BIT(31)
+#define GCR1_P2_STOP   BIT(30)
 
 #define FSL_MC_FAPR    0x28
 #define MC_FAPR_PL     BIT(18)
 #define MC_FAPR_BMT    BIT(17)
 
+static phys_addr_t mc_portal_base_phys_addr;
+
 /**
  * fsl_mc_bus_match - device to driver matching callback
  * @dev: the fsl-mc device to match against
@@ -220,7 +223,7 @@ static int scan_fsl_mc_bus(struct device *dev, void *data)
        root_mc_dev = to_fsl_mc_device(dev);
        root_mc_bus = to_fsl_mc_bus(root_mc_dev);
        mutex_lock(&root_mc_bus->scan_mutex);
-       dprc_scan_objects(root_mc_dev, NULL);
+       dprc_scan_objects(root_mc_dev, false);
        mutex_unlock(&root_mc_bus->scan_mutex);
 
 exit:
@@ -703,14 +706,30 @@ static int fsl_mc_device_get_mmio_regions(struct fsl_mc_device *mc_dev,
                 * If base address is in the region_desc use it otherwise
                 * revert to old mechanism
                 */
-               if (region_desc.base_address)
+               if (region_desc.base_address) {
                        regions[i].start = region_desc.base_address +
                                                region_desc.base_offset;
-               else
+               } else {
                        error = translate_mc_addr(mc_dev, mc_region_type,
                                          region_desc.base_offset,
                                          &regions[i].start);
 
+                       /*
+                        * Some versions of the MC firmware wrongly report
+                        * 0 for register base address of the DPMCP associated
+                        * with child DPRC objects thus rendering them unusable.
+                        * This is particularly troublesome in ACPI boot
+                        * scenarios where the legacy way of extracting this
+                        * base address from the device tree does not apply.
+                        * Given that DPMCPs share the same base address,
+                        * workaround this by using the base address extracted
+                        * from the root DPRC container.
+                        */
+                       if (is_fsl_mc_bus_dprc(mc_dev) &&
+                           regions[i].start == region_desc.base_offset)
+                               regions[i].start += mc_portal_base_phys_addr;
+               }
+
                if (error < 0) {
                        dev_err(parent_dev,
                                "Invalid MC offset: %#x (for %s.%d\'s region %d)\n",
@@ -895,6 +914,8 @@ error_cleanup_dev:
 }
 EXPORT_SYMBOL_GPL(fsl_mc_device_add);
 
+static struct notifier_block fsl_mc_nb;
+
 /**
  * fsl_mc_device_remove - Remove an fsl-mc device from being visible to
  * Linux
@@ -914,7 +935,8 @@ void fsl_mc_device_remove(struct fsl_mc_device *mc_dev)
 }
 EXPORT_SYMBOL_GPL(fsl_mc_device_remove);
 
-struct fsl_mc_device *fsl_mc_get_endpoint(struct fsl_mc_device *mc_dev)
+struct fsl_mc_device *fsl_mc_get_endpoint(struct fsl_mc_device *mc_dev,
+                                         u16 if_id)
 {
        struct fsl_mc_device *mc_bus_dev, *endpoint;
        struct fsl_mc_obj_desc endpoint_desc = {{ 0 }};
@@ -925,6 +947,7 @@ struct fsl_mc_device *fsl_mc_get_endpoint(struct fsl_mc_device *mc_dev)
        mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent);
        strcpy(endpoint1.type, mc_dev->obj_desc.type);
        endpoint1.id = mc_dev->obj_desc.id;
+       endpoint1.if_id = if_id;
 
        err = dprc_get_connection(mc_bus_dev->mc_io, 0,
                                  mc_bus_dev->mc_handle,
@@ -947,10 +970,28 @@ struct fsl_mc_device *fsl_mc_get_endpoint(struct fsl_mc_device *mc_dev)
         * We know that the device has an endpoint because we verified by
         * interrogating the firmware. This is the case when the device was not
         * yet discovered by the fsl-mc bus, thus the lookup returned NULL.
-        * Differentiate this case by returning EPROBE_DEFER.
+        * Force a rescan of the devices in this container and retry the lookup.
+        */
+       if (!endpoint) {
+               struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);
+
+               if (mutex_trylock(&mc_bus->scan_mutex)) {
+                       err = dprc_scan_objects(mc_bus_dev, true);
+                       mutex_unlock(&mc_bus->scan_mutex);
+               }
+
+               if (err < 0)
+                       return ERR_PTR(err);
+       }
+
+       endpoint = fsl_mc_device_lookup(&endpoint_desc, mc_bus_dev);
+       /*
+        * This means that the endpoint might reside in a different isolation
+        * context (DPRC/container). Not much to do, so return a permssion
+        * error.
         */
        if (!endpoint)
-               return ERR_PTR(-EPROBE_DEFER);
+               return ERR_PTR(-EPERM);
 
        return endpoint;
 }
@@ -1089,17 +1130,6 @@ static int fsl_mc_bus_probe(struct platform_device *pdev)
        }
 
        if (mc->fsl_mc_regs) {
-               /*
-                * Some bootloaders pause the MC firmware before booting the
-                * kernel so that MC will not cause faults as soon as the
-                * SMMU probes due to the fact that there's no configuration
-                * in place for MC.
-                * At this point MC should have all its SMMU setup done so make
-                * sure it is resumed.
-                */
-               writel(readl(mc->fsl_mc_regs + FSL_MC_GCR1) & (~GCR1_P1_STOP),
-                      mc->fsl_mc_regs + FSL_MC_GCR1);
-
                if (IS_ENABLED(CONFIG_ACPI) && !dev_of_node(&pdev->dev)) {
                        mc_stream_id = readl(mc->fsl_mc_regs + FSL_MC_FAPR);
                        /*
@@ -1113,11 +1143,25 @@ static int fsl_mc_bus_probe(struct platform_device *pdev)
                        error = acpi_dma_configure_id(&pdev->dev,
                                                      DEV_DMA_COHERENT,
                                                      &mc_stream_id);
+                       if (error == -EPROBE_DEFER)
+                               return error;
                        if (error)
                                dev_warn(&pdev->dev,
                                         "failed to configure dma: %d.\n",
                                         error);
                }
+
+               /*
+                * Some bootloaders pause the MC firmware before booting the
+                * kernel so that MC will not cause faults as soon as the
+                * SMMU probes due to the fact that there's no configuration
+                * in place for MC.
+                * At this point MC should have all its SMMU setup done so make
+                * sure it is resumed.
+                */
+               writel(readl(mc->fsl_mc_regs + FSL_MC_GCR1) &
+                            (~(GCR1_P1_STOP | GCR1_P2_STOP)),
+                      mc->fsl_mc_regs + FSL_MC_GCR1);
        }
 
        /*
@@ -1126,6 +1170,8 @@ static int fsl_mc_bus_probe(struct platform_device *pdev)
        plat_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        mc_portal_phys_addr = plat_res->start;
        mc_portal_size = resource_size(plat_res);
+       mc_portal_base_phys_addr = mc_portal_phys_addr & ~0x3ffffff;
+
        error = fsl_create_mc_io(&pdev->dev, mc_portal_phys_addr,
                                 mc_portal_size, NULL,
                                 FSL_MC_IO_ATOMIC_CONTEXT_PORTAL, &mc_io);
@@ -1199,9 +1245,26 @@ static int fsl_mc_bus_remove(struct platform_device *pdev)
        fsl_destroy_mc_io(mc->root_mc_bus_dev->mc_io);
        mc->root_mc_bus_dev->mc_io = NULL;
 
+       bus_unregister_notifier(&fsl_mc_bus_type, &fsl_mc_nb);
+
+       if (mc->fsl_mc_regs) {
+               /*
+                * Pause the MC firmware so that it doesn't crash in certain
+                * scenarios, such as kexec.
+                */
+               writel(readl(mc->fsl_mc_regs + FSL_MC_GCR1) |
+                      (GCR1_P1_STOP | GCR1_P2_STOP),
+                      mc->fsl_mc_regs + FSL_MC_GCR1);
+       }
+
        return 0;
 }
 
+static void fsl_mc_bus_shutdown(struct platform_device *pdev)
+{
+       fsl_mc_bus_remove(pdev);
+}
+
 static const struct of_device_id fsl_mc_bus_match_table[] = {
        {.compatible = "fsl,qoriq-mc",},
        {},
@@ -1224,6 +1287,45 @@ static struct platform_driver fsl_mc_bus_driver = {
                   },
        .probe = fsl_mc_bus_probe,
        .remove = fsl_mc_bus_remove,
+       .shutdown = fsl_mc_bus_shutdown,
+};
+
+static int fsl_mc_bus_notifier(struct notifier_block *nb,
+                              unsigned long action, void *data)
+{
+       struct device *dev = data;
+       struct resource *res;
+       void __iomem *fsl_mc_regs;
+
+       if (action != BUS_NOTIFY_ADD_DEVICE)
+               return 0;
+
+       if (!of_match_device(fsl_mc_bus_match_table, dev) &&
+           !acpi_match_device(fsl_mc_bus_acpi_match_table, dev))
+               return 0;
+
+       res = platform_get_resource(to_platform_device(dev), IORESOURCE_MEM, 1);
+       if (!res)
+               return 0;
+
+       fsl_mc_regs = ioremap(res->start, resource_size(res));
+       if (!fsl_mc_regs)
+               return 0;
+
+       /*
+        * Make sure that the MC firmware is paused before the IOMMU setup for
+        * it is done or otherwise the firmware will crash right after the SMMU
+        * gets probed and enabled.
+        */
+       writel(readl(fsl_mc_regs + FSL_MC_GCR1) | (GCR1_P1_STOP | GCR1_P2_STOP),
+              fsl_mc_regs + FSL_MC_GCR1);
+       iounmap(fsl_mc_regs);
+
+       return 0;
+}
+
+static struct notifier_block fsl_mc_nb = {
+       .notifier_call = fsl_mc_bus_notifier,
 };
 
 static int __init fsl_mc_bus_driver_init(void)
@@ -1250,7 +1352,7 @@ static int __init fsl_mc_bus_driver_init(void)
        if (error < 0)
                goto error_cleanup_dprc_driver;
 
-       return 0;
+       return bus_register_notifier(&platform_bus_type, &fsl_mc_nb);
 
 error_cleanup_dprc_driver:
        dprc_driver_exit();