bus: ti-sysc: Add quirk handling for reset on re-init
[linux-2.6-microblaze.git] / drivers / bus / ti-sysc.c
index a51c2a8..cacdc4e 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>
@@ -17,6 +18,7 @@
 #include <linux/of_platform.h>
 #include <linux/slab.h>
 #include <linux/sys_soc.h>
+#include <linux/timekeeping.h>
 #include <linux/iopoll.h>
 
 #include <linux/platform_data/ti-sysc.h>
@@ -51,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 {
@@ -131,6 +140,7 @@ struct sysc {
        struct ti_sysc_cookie cookie;
        const char *name;
        u32 revision;
+       u32 sysconfig;
        unsigned int reserved:1;
        unsigned int enabled:1;
        unsigned int needs_resume:1;
@@ -147,6 +157,7 @@ struct sysc {
 
 static void sysc_parse_dts_quirks(struct sysc *ddata, struct device_node *np,
                                  bool is_child);
+static int sysc_reset(struct sysc *ddata);
 
 static void sysc_write(struct sysc *ddata, int offset, u32 value)
 {
@@ -223,37 +234,77 @@ static u32 sysc_read_sysstatus(struct sysc *ddata)
        return sysc_read(ddata, offset);
 }
 
-/* Poll on reset status */
-static int sysc_wait_softreset(struct sysc *ddata)
+static int sysc_poll_reset_sysstatus(struct sysc *ddata)
 {
-       u32 sysc_mask, syss_done, rstval;
-       int syss_offset, error = 0;
-
-       if (ddata->cap->regbits->srst_shift < 0)
-               return 0;
-
-       syss_offset = ddata->offsets[SYSC_SYSSTATUS];
-       sysc_mask = BIT(ddata->cap->regbits->srst_shift);
+       int error, retries;
+       u32 syss_done, rstval;
 
        if (ddata->cfg.quirks & SYSS_QUIRK_RESETDONE_INVERTED)
                syss_done = 0;
        else
                syss_done = ddata->cfg.syss_mask;
 
-       if (syss_offset >= 0) {
+       if (likely(!timekeeping_suspended)) {
                error = readx_poll_timeout_atomic(sysc_read_sysstatus, ddata,
                                rstval, (rstval & ddata->cfg.syss_mask) ==
                                syss_done, 100, MAX_MODULE_SOFTRESET_WAIT);
+       } else {
+               retries = MAX_MODULE_SOFTRESET_WAIT;
+               while (retries--) {
+                       rstval = sysc_read_sysstatus(ddata);
+                       if ((rstval & ddata->cfg.syss_mask) == syss_done)
+                               return 0;
+                       udelay(2); /* Account for udelay flakeyness */
+               }
+               error = -ETIMEDOUT;
+       }
+
+       return error;
+}
+
+static int sysc_poll_reset_sysconfig(struct sysc *ddata)
+{
+       int error, retries;
+       u32 sysc_mask, rstval;
 
-       } else if (ddata->cfg.quirks & SYSC_QUIRK_RESET_STATUS) {
+       sysc_mask = BIT(ddata->cap->regbits->srst_shift);
+
+       if (likely(!timekeeping_suspended)) {
                error = readx_poll_timeout_atomic(sysc_read_sysconfig, ddata,
                                rstval, !(rstval & sysc_mask),
                                100, MAX_MODULE_SOFTRESET_WAIT);
+       } else {
+               retries = MAX_MODULE_SOFTRESET_WAIT;
+               while (retries--) {
+                       rstval = sysc_read_sysconfig(ddata);
+                       if (!(rstval & sysc_mask))
+                               return 0;
+                       udelay(2); /* Account for udelay flakeyness */
+               }
+               error = -ETIMEDOUT;
        }
 
        return error;
 }
 
+/* Poll on reset status */
+static int sysc_wait_softreset(struct sysc *ddata)
+{
+       int syss_offset, error = 0;
+
+       if (ddata->cap->regbits->srst_shift < 0)
+               return 0;
+
+       syss_offset = ddata->offsets[SYSC_SYSSTATUS];
+
+       if (syss_offset >= 0)
+               error = sysc_poll_reset_sysstatus(ddata);
+       else if (ddata->cfg.quirks & SYSC_QUIRK_RESET_STATUS)
+               error = sysc_poll_reset_sysconfig(ddata);
+
+       return error;
+}
+
 static int sysc_add_named_clock_from_child(struct sysc *ddata,
                                           const char *name,
                                           const char *optfck_name)
@@ -1094,7 +1145,8 @@ set_midle:
        best_mode = fls(ddata->cfg.midlemodes) - 1;
        if (best_mode > SYSC_IDLE_MASK) {
                dev_err(dev, "%s: invalid midlemode\n", __func__);
-               return -EINVAL;
+               error = -EINVAL;
+               goto save_context;
        }
 
        if (ddata->cfg.quirks & SYSC_QUIRK_SWSUP_MSTANDBY)
@@ -1112,13 +1164,16 @@ set_autoidle:
                sysc_write_sysconfig(ddata, reg);
        }
 
-       /* Flush posted write */
-       sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]);
+       error = 0;
+
+save_context:
+       /* Save context and flush posted write */
+       ddata->sysconfig = sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]);
 
        if (ddata->module_enable_quirk)
                ddata->module_enable_quirk(ddata);
 
-       return 0;
+       return error;
 }
 
 static int sysc_best_idle_mode(u32 idlemodes, u32 *best_mode)
@@ -1175,8 +1230,10 @@ static int sysc_disable_module(struct device *dev)
 set_sidle:
        /* Set SIDLE mode */
        idlemodes = ddata->cfg.sidlemodes;
-       if (!idlemodes || regbits->sidle_shift < 0)
-               return 0;
+       if (!idlemodes || regbits->sidle_shift < 0) {
+               ret = 0;
+               goto save_context;
+       }
 
        if (ddata->cfg.quirks & SYSC_QUIRK_SWSUP_SIDLE) {
                best_mode = SYSC_IDLE_FORCE;
@@ -1184,7 +1241,8 @@ set_sidle:
                ret = sysc_best_idle_mode(idlemodes, &best_mode);
                if (ret) {
                        dev_err(dev, "%s: invalid sidlemode\n", __func__);
-                       return ret;
+                       ret = -EINVAL;
+                       goto save_context;
                }
        }
 
@@ -1195,10 +1253,13 @@ set_sidle:
                reg |= 1 << regbits->autoidle_shift;
        sysc_write_sysconfig(ddata, reg);
 
-       /* Flush posted write */
-       sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]);
+       ret = 0;
 
-       return 0;
+save_context:
+       /* Save context and flush posted write */
+       ddata->sysconfig = sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]);
+
+       return ret;
 }
 
 static int __maybe_unused sysc_runtime_suspend_legacy(struct device *dev,
@@ -1336,13 +1397,40 @@ err_allow_idle:
        return error;
 }
 
+/*
+ * Checks if device context was lost. Assumes the sysconfig register value
+ * after lost context is different from the configured value. Only works for
+ * enabled devices.
+ *
+ * Eventually we may want to also add support to using the context lost
+ * registers that some SoCs have.
+ */
+static int sysc_check_context(struct sysc *ddata)
+{
+       u32 reg;
+
+       if (!ddata->enabled)
+               return -ENODATA;
+
+       reg = sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]);
+       if (reg == ddata->sysconfig)
+               return 0;
+
+       return -EACCES;
+}
+
 static int sysc_reinit_module(struct sysc *ddata, bool leave_enabled)
 {
        struct device *dev = ddata->dev;
        int error;
 
-       /* Disable target module if it is enabled */
        if (ddata->enabled) {
+               /* Nothing to do if enabled and context not lost */
+               error = sysc_check_context(ddata);
+               if (!error)
+                       return 0;
+
+               /* Disable target module if it is enabled */
                error = sysc_runtime_suspend(dev);
                if (error)
                        dev_warn(dev, "reinit suspend failed: %i\n", error);
@@ -1353,6 +1441,15 @@ static int sysc_reinit_module(struct sysc *ddata, bool leave_enabled)
        if (error)
                dev_warn(dev, "reinit resume failed: %i\n", error);
 
+       /* Some modules like am335x gpmc need reset and restore of sysconfig */
+       if (ddata->cfg.quirks & SYSC_QUIRK_RESET_ON_CTX_LOST) {
+               error = sysc_reset(ddata);
+               if (error)
+                       dev_warn(dev, "reinit reset failed: %i\n", error);
+
+               sysc_write_sysconfig(ddata, ddata->sysconfig);
+       }
+
        if (leave_enabled)
                return error;
 
@@ -2398,6 +2495,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
@@ -2897,12 +3067,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;
@@ -2918,6 +3090,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);
@@ -2981,15 +3154,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);
@@ -3057,7 +3239,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;
 
@@ -3155,6 +3337,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:
@@ -3236,7 +3421,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);