Some platforms need to get system controller
ready as soon as possible.
The patch provides early_syscon_initialization
which create early mapping for all syscon compatible
devices in early_syscon_probe.
Regmap is get via syscon_early_regmap_lookup_by_phandle()

Regular device probes attach device to regmap
via regmap_attach_dev().

For early syscon initialization is necessary to extend
struct syscon and provide remove function
which unmap all early init structures.

Signed-off-by: Michal Simek <[email protected]>
---

 drivers/mfd/syscon.c       | 126 +++++++++++++++++++++++++++++++++++++++------
 include/linux/mfd/syscon.h |  11 ++++
 2 files changed, 120 insertions(+), 17 deletions(-)

diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c
index 71841f9..5935f02 100644
--- a/drivers/mfd/syscon.c
+++ b/drivers/mfd/syscon.c
@@ -20,12 +20,15 @@
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
+#include <linux/slab.h>
 #include <linux/mfd/syscon.h>

 static struct platform_driver syscon_driver;

 struct syscon {
+       void __iomem *base;
        struct regmap *regmap;
+       struct resource res;
 };

 static int syscon_match_node(struct device *dev, void *data)
@@ -95,6 +98,24 @@ struct regmap *syscon_regmap_lookup_by_pdevname(const char 
*s)
 }
 EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_pdevname);

+struct regmap *syscon_early_regmap_lookup_by_phandle(struct device_node *np,
+                                                    const char *property)
+{
+       struct device_node *syscon_np;
+       struct syscon *syscon;
+
+       syscon_np = of_parse_phandle(np, property, 0);
+       if (!syscon_np)
+               return ERR_PTR(-ENODEV);
+
+       syscon = syscon_np->data;
+
+       of_node_put(syscon_np);
+
+       return syscon->regmap;
+}
+EXPORT_SYMBOL_GPL(syscon_early_regmap_lookup_by_phandle);
+
 struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np,
                                        const char *property)
 {
@@ -128,40 +149,110 @@ static int syscon_probe(struct platform_device *pdev)
        struct device *dev = &pdev->dev;
        struct syscon *syscon;
        struct resource *res;
-       void __iomem *base;

-       syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL);
+       /* Early syscon init */
+       if (pdev->dev.of_node && pdev->dev.of_node->data) {
+               syscon = pdev->dev.of_node->data;
+               res = &syscon->res;
+               regmap_attach_dev(dev, syscon->regmap, &syscon_regmap_config);
+       } else {
+
+               syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL);
+               if (!syscon)
+                       return -ENOMEM;
+
+               res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+               if (!res)
+                       return -ENOENT;
+
+               syscon->base = devm_ioremap(dev, res->start,
+                                           resource_size(res));
+               if (!syscon->base)
+                       return -ENOMEM;
+
+               syscon_regmap_config.max_register = res->end - res->start - 3;
+               syscon->regmap = devm_regmap_init_mmio(dev, syscon->base,
+                                               &syscon_regmap_config);
+               if (IS_ERR(syscon->regmap)) {
+                       dev_err(dev, "regmap init failed\n");
+                       return PTR_ERR(syscon->regmap);
+               }
+       }
+
+       platform_set_drvdata(pdev, syscon);
+
+       dev_info(dev, "regmap %pR registered\n", res);
+
+       return 0;
+}
+
+static const struct platform_device_id syscon_ids[] = {
+       { "syscon", },
+       { }
+};
+
+static int syscon_remove(struct platform_device *pdev)
+{
+       struct syscon *syscon = platform_get_drvdata(pdev);
+
+       if (pdev->dev.of_node && pdev->dev.of_node->data) {
+               iounmap(syscon->base);
+               kfree(syscon);
+       }
+
+       return 0;
+}
+
+static int early_syscon_probe(struct device_node *np)
+{
+       struct syscon *syscon;
+
+       syscon = kzalloc(sizeof(*syscon), GFP_KERNEL);
        if (!syscon)
                return -ENOMEM;

-       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       if (!res)
-               return -ENOENT;
+       if (of_address_to_resource(np, 0, &syscon->res))
+               return -EINVAL;

-       base = devm_ioremap(dev, res->start, resource_size(res));
-       if (!base)
-               return -ENOMEM;
+       syscon->base = ioremap(syscon->res.start, resource_size(&syscon->res));
+       if (!syscon->base) {
+               pr_err("%s: Unable to map I/O memory\n", __func__);
+               return PTR_ERR(syscon->base);
+       }

-       syscon_regmap_config.max_register = res->end - res->start - 3;
-       syscon->regmap = devm_regmap_init_mmio(dev, base,
-                                       &syscon_regmap_config);
+       syscon_regmap_config.max_register = syscon->res.end -
+                                           syscon->res.start - 3;
+       syscon->regmap = regmap_init_mmio(NULL, syscon->base,
+                                         &syscon_regmap_config);
        if (IS_ERR(syscon->regmap)) {
-               dev_err(dev, "regmap init failed\n");
+               pr_err("regmap init failed\n");
                return PTR_ERR(syscon->regmap);
        }

-       platform_set_drvdata(pdev, syscon);
+       np->data = syscon;

-       dev_info(dev, "regmap %pR registered\n", res);
+       of_node_put(np);
+
+       pr_info("%s: regmap %pR registered\n", np->full_name, &syscon->res);

        return 0;
 }

-static const struct platform_device_id syscon_ids[] = {
-       { "syscon", },
-       { }
+static struct of_device_id of_syscon_ids[] = {
+       { .compatible = "syscon" },
+       {},
 };

+void __init early_syscon_init(void)
+{
+       struct device_node *np;
+
+       for_each_matching_node_and_match(np, of_syscon_ids, NULL) {
+               if (!early_syscon_probe(np))
+                       BUG();
+       }
+}
+
 static struct platform_driver syscon_driver = {
        .driver = {
                .name = "syscon",
@@ -169,6 +260,7 @@ static struct platform_driver syscon_driver = {
                .of_match_table = of_syscon_match,
        },
        .probe          = syscon_probe,
+       .remove         = syscon_remove,
        .id_table       = syscon_ids,
 };

diff --git a/include/linux/mfd/syscon.h b/include/linux/mfd/syscon.h
index 8789fa3..465c092 100644
--- a/include/linux/mfd/syscon.h
+++ b/include/linux/mfd/syscon.h
@@ -24,6 +24,10 @@ extern struct regmap *syscon_regmap_lookup_by_pdevname(const 
char *s);
 extern struct regmap *syscon_regmap_lookup_by_phandle(
                                        struct device_node *np,
                                        const char *property);
+extern struct regmap *syscon_early_regmap_lookup_by_phandle(
+                                       struct device_node *np,
+                                       const char *property);
+extern void early_syscon_init(void);
 #else
 static inline struct regmap *syscon_node_to_regmap(struct device_node *np)
 {
@@ -46,6 +50,13 @@ static inline struct regmap *syscon_regmap_lookup_by_phandle(
 {
        return ERR_PTR(-ENOSYS);
 }
+
+static struct regmap *syscon_early_regmap_lookup_by_phandle(
+                                       struct device_node *np,
+                                       const char *property)
+{
+       return ERR_PTR(-ENOSYS);
+}
 #endif

 #endif /* __LINUX_MFD_SYSCON_H__ */
--
1.8.2.3

Attachment: pgpkJsGT6io7v.pgp
Description: PGP signature

Reply via email to