bus: ti-sysc: Add quirk handling for reinit on context lost
authorTony Lindgren <tony@atomide.com>
Tue, 21 Sep 2021 09:42:25 +0000 (12:42 +0300)
committerTony Lindgren <tony@atomide.com>
Tue, 21 Sep 2021 09:42:28 +0000 (12:42 +0300)
Some interconnect target modules such as otg and gpmc on am335x need a
re-init after resume. As we also have PM runtime cases where the context
may be lost, let's handle these all with cpu_pm.

For the am335x resume path, we already have cpu_pm_resume() call
cpu_pm_cluster_exit().

Signed-off-by: Tony Lindgren <tony@atomide.com>
drivers/bus/ti-sysc.c
include/linux/platform_data/ti-sysc.h

index 9e95834..a73c707 100644 (file)
@@ -6,6 +6,7 @@
 #include <linux/io.h>
 #include <linux/clk.h>
 #include <linux/clkdev.h>
+#include <linux/cpu_pm.h>
 #include <linux/delay.h>
 #include <linux/list.h>
 #include <linux/module.h>
@@ -52,11 +53,18 @@ struct sysc_address {
        struct list_head node;
 };
 
+struct sysc_module {
+       struct sysc *ddata;
+       struct list_head node;
+};
+
 struct sysc_soc_info {
        unsigned long general_purpose:1;
        enum sysc_soc soc;
-       struct mutex list_lock;                 /* disabled modules list lock */
+       struct mutex list_lock; /* disabled and restored modules list lock */
        struct list_head disabled_modules;
+       struct list_head restored_modules;
+       struct notifier_block nb;
 };
 
 enum sysc_clocks {
@@ -2477,6 +2485,79 @@ static struct dev_pm_domain sysc_child_pm_domain = {
        }
 };
 
+/* Caller needs to take list_lock if ever used outside of cpu_pm */
+static void sysc_reinit_modules(struct sysc_soc_info *soc)
+{
+       struct sysc_module *module;
+       struct list_head *pos;
+       struct sysc *ddata;
+       int error = 0;
+
+       list_for_each(pos, &sysc_soc->restored_modules) {
+               module = list_entry(pos, struct sysc_module, node);
+               ddata = module->ddata;
+               error = sysc_reinit_module(ddata, ddata->enabled);
+       }
+}
+
+/**
+ * sysc_context_notifier - optionally reset and restore module after idle
+ * @nb: notifier block
+ * @cmd: unused
+ * @v: unused
+ *
+ * Some interconnect target modules need to be restored, or reset and restored
+ * on CPU_PM CPU_PM_CLUSTER_EXIT notifier. This is needed at least for am335x
+ * OTG and GPMC target modules even if the modules are unused.
+ */
+static int sysc_context_notifier(struct notifier_block *nb, unsigned long cmd,
+                                void *v)
+{
+       struct sysc_soc_info *soc;
+
+       soc = container_of(nb, struct sysc_soc_info, nb);
+
+       switch (cmd) {
+       case CPU_CLUSTER_PM_ENTER:
+               break;
+       case CPU_CLUSTER_PM_ENTER_FAILED:       /* No need to restore context */
+               break;
+       case CPU_CLUSTER_PM_EXIT:
+               sysc_reinit_modules(soc);
+               break;
+       }
+
+       return NOTIFY_OK;
+}
+
+/**
+ * sysc_add_restored - optionally add reset and restore quirk hanlling
+ * @ddata: device data
+ */
+static void sysc_add_restored(struct sysc *ddata)
+{
+       struct sysc_module *restored_module;
+
+       restored_module = kzalloc(sizeof(*restored_module), GFP_KERNEL);
+       if (!restored_module)
+               return;
+
+       restored_module->ddata = ddata;
+
+       mutex_lock(&sysc_soc->list_lock);
+
+       list_add(&restored_module->node, &sysc_soc->restored_modules);
+
+       if (sysc_soc->nb.notifier_call)
+               goto out_unlock;
+
+       sysc_soc->nb.notifier_call = sysc_context_notifier;
+       cpu_pm_register_notifier(&sysc_soc->nb);
+
+out_unlock:
+       mutex_unlock(&sysc_soc->list_lock);
+}
+
 /**
  * sysc_legacy_idle_quirk - handle children in omap_device compatible way
  * @ddata: device driver data
@@ -2976,12 +3057,14 @@ static int sysc_add_disabled(unsigned long base)
 }
 
 /*
- * One time init to detect the booted SoC and disable unavailable features.
+ * One time init to detect the booted SoC, disable unavailable features
+ * and initialize list for optional cpu_pm notifier.
+ *
  * Note that we initialize static data shared across all ti-sysc instances
  * so ddata is only used for SoC type. This can be called from module_init
  * once we no longer need to rely on platform data.
  */
-static int sysc_init_soc(struct sysc *ddata)
+static int sysc_init_static_data(struct sysc *ddata)
 {
        const struct soc_device_attribute *match;
        struct ti_sysc_platform_data *pdata;
@@ -2997,6 +3080,7 @@ static int sysc_init_soc(struct sysc *ddata)
 
        mutex_init(&sysc_soc->list_lock);
        INIT_LIST_HEAD(&sysc_soc->disabled_modules);
+       INIT_LIST_HEAD(&sysc_soc->restored_modules);
        sysc_soc->general_purpose = true;
 
        pdata = dev_get_platdata(ddata->dev);
@@ -3060,15 +3144,24 @@ static int sysc_init_soc(struct sysc *ddata)
        return 0;
 }
 
-static void sysc_cleanup_soc(void)
+static void sysc_cleanup_static_data(void)
 {
+       struct sysc_module *restored_module;
        struct sysc_address *disabled_module;
        struct list_head *pos, *tmp;
 
        if (!sysc_soc)
                return;
 
+       if (sysc_soc->nb.notifier_call)
+               cpu_pm_unregister_notifier(&sysc_soc->nb);
+
        mutex_lock(&sysc_soc->list_lock);
+       list_for_each_safe(pos, tmp, &sysc_soc->restored_modules) {
+               restored_module = list_entry(pos, struct sysc_module, node);
+               list_del(pos);
+               kfree(restored_module);
+       }
        list_for_each_safe(pos, tmp, &sysc_soc->disabled_modules) {
                disabled_module = list_entry(pos, struct sysc_address, node);
                list_del(pos);
@@ -3136,7 +3229,7 @@ static int sysc_probe(struct platform_device *pdev)
        ddata->dev = &pdev->dev;
        platform_set_drvdata(pdev, ddata);
 
-       error = sysc_init_soc(ddata);
+       error = sysc_init_static_data(ddata);
        if (error)
                return error;
 
@@ -3234,6 +3327,9 @@ static int sysc_probe(struct platform_device *pdev)
                pm_runtime_put(&pdev->dev);
        }
 
+       if (ddata->cfg.quirks & SYSC_QUIRK_REINIT_ON_CTX_LOST)
+               sysc_add_restored(ddata);
+
        return 0;
 
 err:
@@ -3315,7 +3411,7 @@ static void __exit sysc_exit(void)
 {
        bus_unregister_notifier(&platform_bus_type, &sysc_nb);
        platform_driver_unregister(&sysc_driver);
-       sysc_cleanup_soc();
+       sysc_cleanup_static_data();
 }
 module_exit(sysc_exit);
 
index 9837fb0..989aa30 100644 (file)
@@ -50,6 +50,7 @@ struct sysc_regbits {
        s8 emufree_shift;
 };
 
+#define SYSC_QUIRK_REINIT_ON_CTX_LOST  BIT(28)
 #define SYSC_QUIRK_REINIT_ON_RESUME    BIT(27)
 #define SYSC_QUIRK_GPMC_DEBUG          BIT(26)
 #define SYSC_MODULE_QUIRK_ENA_RESETDONE        BIT(25)