From: Bartosz Golaszewski <bgolaszew...@baylibre.com>

This introduces the core part of support for early platform drivers
and devices.

Signed-off-by: Bartosz Golaszewski <bgolaszew...@baylibre.com>
---
 drivers/base/Kconfig           |   3 +
 drivers/base/Makefile          |   1 +
 drivers/base/early.c           | 332 +++++++++++++++++++++++++++++++++
 drivers/misc/Makefile          |   1 +
 include/linux/early_platform.h |  75 ++++++++
 5 files changed, 412 insertions(+)
 create mode 100644 drivers/base/early.c
 create mode 100644 include/linux/early_platform.h

diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 29b0eb452b3a..ea648daecec1 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -205,6 +205,9 @@ config DEBUG_TEST_DRIVER_REMOVE
          unusable. You should say N here unless you are explicitly looking to
          test this functionality.
 
+config EARLY_PLATFORM
+       def_bool n
+
 source "drivers/base/test/Kconfig"
 
 config SYS_HYPERVISOR
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 7845c95ee1b2..3998ad6719f1 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -7,6 +7,7 @@ obj-y                   := component.o core.o bus.o dd.o 
syscore.o \
                           attribute_container.o transport_class.o \
                           topology.o container.o property.o cacheinfo.o \
                           devcon.o
+obj-$(CONFIG_EARLY_PLATFORM) += early.o
 obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
 obj-$(CONFIG_DMA_CMA) += dma-contiguous.o
 obj-y                  += power/
diff --git a/drivers/base/early.c b/drivers/base/early.c
new file mode 100644
index 000000000000..7d0b7fb85f58
--- /dev/null
+++ b/drivers/base/early.c
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments, Inc.
+ *
+ * Author:
+ *     Bartosz Golaszewski <bgolaszew...@baylibre.com>
+ */
+
+#include <linux/early_platform.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+#include "base.h"
+
+extern struct early_platform_driver *__early_platform_drivers_table[];
+extern struct early_platform_driver *__early_platform_drivers_table_end[];
+
+static bool early_platform_done;
+
+static LIST_HEAD(early_platform_drivers);
+static LIST_HEAD(early_platform_devices);
+
+static int early_platform_device_set_name(struct early_platform_device *edev)
+{
+       switch (edev->pdev.id) {
+       case PLATFORM_DEVID_AUTO:
+               pr_warn("auto device ID not supported in early platform 
devices\n");
+               /* fallthrough */
+       case PLATFORM_DEVID_NONE:
+               edev->pdev.dev.init_name = kasprintf(GFP_KERNEL,
+                                                    "%s", edev->pdev.name);
+               break;
+       default:
+               edev->pdev.dev.init_name = kasprintf(GFP_KERNEL, "%s.%d",
+                                                    edev->pdev.name,
+                                                    edev->pdev.id);
+               break;
+       }
+
+       if (!edev->pdev.dev.init_name)
+               return -ENOMEM;
+
+       return 0;
+}
+
+static void early_platform_device_add(struct early_platform_device *edev)
+{
+       edev->pdev.dev.early = true;
+       INIT_LIST_HEAD(&edev->list);
+       list_add_tail(&edev->list, &early_platform_devices);
+}
+
+static void early_platform_probe_deferred(void)
+{
+       struct early_platform_device *edev;
+       int rv;
+
+       list_for_each_entry(edev, &early_platform_devices, list) {
+               if (!edev->deferred || !edev->deferred_drv->early_probe)
+                       continue;
+
+               rv = edev->deferred_drv->early_probe(&edev->pdev);
+               if (rv && rv != -EPROBE_DEFER) {
+                       dev_err(&edev->pdev.dev,
+                               "early platform driver probe failed: %d\n",
+                               rv);
+               }
+       }
+}
+
+static void early_platform_try_probe(struct early_platform_driver *edrv,
+                                    struct early_platform_device *edev)
+{
+       int rv;
+
+       rv = early_platform_device_set_name(edev);
+       if (rv)
+               pr_warn("unable to set the early platform device name\n");
+
+       if (edrv->early_probe) {
+               rv = edrv->early_probe(&edev->pdev);
+               if (rv && rv != -EPROBE_DEFER &&
+                   rv != -ENODEV && rv != -ENXIO) {
+                       dev_err(&edev->pdev.dev,
+                               "early platform driver probe failed: %d\n",
+                               rv);
+                       return;
+               } else if (rv == -EPROBE_DEFER) {
+                       edev->deferred = true;
+                       edev->deferred_drv = edrv;
+               } else {
+                       early_platform_probe_deferred();
+               }
+       }
+}
+
+/**
+ * of_early_to_platform_device - return the platform device with which this
+ *                               device node is associated
+ * @np - device node to look up
+ *
+ * If a device node was populated early, the corresponding platform device
+ * already exists. Instead of allocating a new object, we need to retrieve
+ * the previous one. This routine enables it.
+ */
+struct platform_device *of_early_to_platform_device(struct device_node *np)
+{
+       struct early_platform_device *edev;
+
+       list_for_each_entry(edev, &early_platform_devices, list) {
+               if (np == edev->pdev.dev.of_node)
+                       return &edev->pdev;
+       }
+
+       return ERR_PTR(-ENOENT);
+}
+
+static int of_early_platform_device_create(struct device_node *node,
+                                          struct early_platform_driver *edrv)
+{
+       struct early_platform_device *edev;
+       int rc;
+
+       edev = kzalloc(sizeof(*edev), GFP_KERNEL);
+       if (!edev)
+               return -ENOMEM;
+
+       platform_device_init(&edev->pdev, "", PLATFORM_DEVID_NONE);
+       /*
+        * We can safely use platform_device_release since the platform_device
+        * struct is the first member of early_platform_device.
+        */
+       edev->pdev.dev.release = platform_device_release;
+
+       rc = of_device_init_resources(&edev->pdev, node);
+       if (rc) {
+               kfree(edev);
+               return rc;
+       }
+
+       of_node_set_flag(node, OF_POPULATED_EARLY);
+       edev->pdev.name = edrv->pdrv.driver.name;
+       edev->pdev.dev.of_node = of_node_get(node);
+       edev->pdev.dev.fwnode = &node->fwnode;
+       early_platform_device_add(edev);
+       early_platform_try_probe(edrv, edev);
+
+       return 0;
+}
+
+static int of_early_platform_populate(struct device_node *root)
+{
+       struct early_platform_driver *edrv;
+       const struct of_device_id *match;
+       struct device_node *child;
+       int rv;
+
+       if (!root)
+               return 0;
+
+       list_for_each_entry(edrv, &early_platform_drivers, list) {
+               if (!edrv->pdrv.driver.of_match_table)
+                       continue;
+
+               match = of_match_node(edrv->pdrv.driver.of_match_table, root);
+               if (!match)
+                       continue;
+
+               rv = of_early_platform_device_create(root, edrv);
+               if (rv)
+                       return rv;
+       }
+
+       for_each_child_of_node(root, child) {
+               rv = of_early_platform_populate(child);
+               if (rv) {
+                       of_node_put(child);
+                       return rv;
+               }
+       }
+
+       return 0;
+}
+
+/**
+ * early_platform_start - start handling early devices
+ *
+ * This should be called by the architecture code early in the boot sequence
+ * to register all early platform drivers, populate the early devices from DT
+ * and start matching platform devices specified in machine code.
+ */
+void early_platform_start(void)
+{
+       struct early_platform_driver **edrv;
+       struct device_node *root;
+       int rv;
+
+       WARN_ONCE(!slab_is_available(), "slab is required for early devices\n");
+
+       pr_debug("%s(): registering pending early platform drivers\n",
+                __func__);
+
+       for (edrv = __early_platform_drivers_table;
+            edrv < __early_platform_drivers_table_end; edrv++) {
+               rv = early_platform_driver_register(*edrv);
+               if (rv)
+                       pr_warn("error registering early platform driver: %d\n",
+                               rv);
+       }
+
+       if (of_have_populated_dt()) {
+               pr_debug("%s(): populating early_platform devices from DT\n",
+                        __func__);
+
+               root = of_find_node_by_path("/");
+
+               rv = of_early_platform_populate(root);
+               if (rv)
+                       pr_warn("error populating early devices from DT: %d\n",
+                               rv);
+
+               of_node_put(root);
+       }
+}
+EXPORT_SYMBOL_GPL(early_platform_start);
+
+/**
+ * early_platform_driver_register - register an early platform driver
+ * @edrv: early platform driver to register
+ *
+ * If we're past postcore initcall, this works exactly as
+ * platform_device_register().
+ */
+int early_platform_driver_register(struct early_platform_driver *edrv)
+{
+       struct early_platform_device *edev;
+
+       if (early_platform_done)
+               return platform_driver_register(&edrv->pdrv);
+
+       INIT_LIST_HEAD(&edrv->list);
+       list_add_tail(&edrv->list, &early_platform_drivers);
+
+       list_for_each_entry(edev, &early_platform_devices, list) {
+               if (platform_match(&edev->pdev.dev, &edrv->pdrv.driver)) {
+                       early_platform_try_probe(edrv, edev);
+                       break;
+               }
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(early_platform_driver_register);
+
+/**
+ * early_platform_device_register - register an early platform device
+ * @edev: early platform device to register
+ *
+ * If we're past postcore initcall, this works exactly as
+ * platform_device_register().
+ */
+int early_platform_device_register(struct early_platform_device *edev)
+{
+       struct early_platform_driver *edrv;
+
+       if (early_platform_done)
+               return platform_device_register(&edev->pdev);
+
+       device_initialize(&edev->pdev.dev);
+       early_platform_device_add(edev);
+
+       list_for_each_entry(edrv, &early_platform_drivers, list) {
+               if (platform_match(&edev->pdev.dev, &edrv->pdrv.driver)) {
+                       early_platform_try_probe(edrv, edev);
+                       break;
+               }
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(early_platform_device_register);
+
+/*
+ * This is called once the entire device model infrastructure is in place to
+ * seamlessly convert all early platform devices & drivers to regular ones.
+ *
+ * From this point forward all early platform devices work exactly like normal
+ * platform devices.
+ */
+static int early_platform_finalize(void)
+{
+       struct early_platform_driver *edrv;
+       struct early_platform_device *edev;
+       int rv;
+
+       early_platform_done = true;
+
+       pr_debug("%s(): converting early platform drivers to real platform 
drivers\n",
+                __func__);
+
+       list_for_each_entry(edrv, &early_platform_drivers, list) {
+               rv = platform_driver_register(&edrv->pdrv);
+               if (rv)
+                       pr_warn("%s: error converting early platform driver to 
real platform driver\n",
+                               edrv->pdrv.driver.name);
+       }
+
+       pr_debug("%s(): converting early platform devices to real platform 
devices\n",
+                __func__);
+
+       list_for_each_entry(edev, &early_platform_devices, list) {
+               if (edev->pdev.dev.of_node)
+                       /* This will be handled by of_platform_populate(). */
+                       continue;
+
+               kfree(edev->pdev.dev.init_name);
+
+               /*
+                * We don't want to reinitialize the associated struct device
+                * so we must not call platform_device_register().
+                */
+               rv = platform_device_add(&edev->pdev);
+               if (rv)
+                       pr_warn("%s: error converting early platform device to 
real platform device\n",
+                               dev_name(&edev->pdev.dev));
+       }
+
+       return 0;
+}
+postcore_initcall(early_platform_finalize);
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 20be70c3f118..d0a8788d5151 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -57,3 +57,4 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP)        += aspeed-lpc-snoop.o
 obj-$(CONFIG_PCI_ENDPOINT_TEST)        += pci_endpoint_test.o
 obj-$(CONFIG_OCXL)             += ocxl/
 obj-$(CONFIG_MISC_RTSX)                += cardreader/
+CFLAGS_dummy-early.o := -DDEBUG
diff --git a/include/linux/early_platform.h b/include/linux/early_platform.h
new file mode 100644
index 000000000000..fd3fd4db8322
--- /dev/null
+++ b/include/linux/early_platform.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments, Inc.
+ *
+ * Author:
+ *     Bartosz Golaszewski <bgolaszew...@baylibre.com>
+ */
+
+#ifndef __EARLY_PLATFORM_H__
+#define __EARLY_PLATFORM_H__
+
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+/**
+ * struct early_platform_driver
+ *
+ * @pdrv: real platform driver associated with this early platform driver
+ * @list: list head for the list of early platform drivers
+ * @early_probe: early probe callback
+ */
+struct early_platform_driver {
+       struct platform_driver pdrv;
+       struct list_head list;
+       int (*early_probe)(struct platform_device *);
+};
+
+/**
+ * struct early_platform_device
+ *
+ * @pdev: real platform device associated with this early platform device
+ * @list: list head for the list of early platform devices
+ * @deferred: true if this device's early probe was deferred
+ * @deferred_drv: early platform driver with which this device was matched
+ */
+struct early_platform_device {
+       struct platform_device pdev;
+       struct list_head list;
+       bool deferred;
+       struct early_platform_driver *deferred_drv;
+};
+
+#ifdef CONFIG_EARLY_PLATFORM
+extern void early_platform_start(void);
+extern int early_platform_driver_register(struct early_platform_driver *edrv);
+extern int early_platform_device_register(struct early_platform_device *edev);
+#else /* CONFIG_EARLY_PLATFORM */
+static inline void early_platform_start(void) {}
+static int void
+early_platform_driver_register(struct early_platform_driver *edrv) {}
+static int void
+early_platform_device_register(struct early_platform_device *edev) {}
+#endif /* CONFIG_EARLY_PLATFORM */
+
+#if defined(CONFIG_EARLY_PLATFORM) && defined(CONFIG_OF)
+extern struct platform_device *
+of_early_to_platform_device(struct device_node *np);
+#else
+static inline struct platform_device *
+of_early_to_platform_device(struct device_node *np)
+{
+       return ERR_PTR(-ENOSYS);
+}
+#endif /* defined(CONFIG_EARLY_PLATFORM) && defined(CONFIG_OF) */
+
+#ifdef CONFIG_EARLY_PLATFORM
+#define module_early_platform_driver(_edrv)                            \
+       static const struct early_platform_driver *__##_edrv##_entry    \
+               __used __section(__early_platform_drivers_table)        \
+               = &(_edrv)
+#else /* CONFIG_EARLY_PLATFORM */
+#define module_early_platform_driver(_edrv)
+#endif /* CONFIG_EARLY_PLATFORM */
+
+#endif /* __EARLY_PLATFORM_H__ */
-- 
2.17.0

Reply via email to