bus: ti-sysc: Handle some devices in omap_device compatible way
[sfrench/cifs-2.6.git] / drivers / bus / ti-sysc.c
index 50fcb04e817901a6e2ea40c944d993d75cdb9497..5aeab4533b5fb28624da14044d0c73fb292c2fb6 100644 (file)
 #include <linux/io.h>
 #include <linux/clk.h>
 #include <linux/clkdev.h>
+#include <linux/delay.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <linux/pm_domain.h>
 #include <linux/pm_runtime.h>
 #include <linux/of_address.h>
 #include <linux/of_platform.h>
@@ -68,6 +70,7 @@ struct sysc {
        u32 revision;
        bool enabled;
        bool needs_resume;
+       bool child_needs_resume;
        struct delayed_work idle_work;
 };
 
@@ -474,6 +477,14 @@ static int sysc_show_reg(struct sysc *ddata,
        return sprintf(bufp, ":%x", ddata->offsets[reg]);
 }
 
+static int sysc_show_name(char *bufp, struct sysc *ddata)
+{
+       if (!ddata->name)
+               return 0;
+
+       return sprintf(bufp, ":%s", ddata->name);
+}
+
 /**
  * sysc_show_registers - show information about interconnect target module
  * @ddata: device driver data
@@ -488,7 +499,7 @@ static void sysc_show_registers(struct sysc *ddata)
                bufp += sysc_show_reg(ddata, bufp, i);
 
        bufp += sysc_show_rev(bufp, ddata);
-       bufp += sysc_show_rev(bufp, ddata);
+       bufp += sysc_show_name(bufp, ddata);
 
        dev_dbg(ddata->dev, "%llx:%x%s\n",
                ddata->module_pa, ddata->module_size,
@@ -612,11 +623,93 @@ static const struct dev_pm_ops sysc_pm_ops = {
                           NULL)
 };
 
+/* Module revision register based quirks */
+struct sysc_revision_quirk {
+       const char *name;
+       u32 base;
+       int rev_offset;
+       int sysc_offset;
+       int syss_offset;
+       u32 revision;
+       u32 revision_mask;
+       u32 quirks;
+};
+
+#define SYSC_QUIRK(optname, optbase, optrev, optsysc, optsyss,         \
+                  optrev_val, optrevmask, optquirkmask)                \
+       {                                                               \
+               .name = (optname),                                      \
+               .base = (optbase),                                      \
+               .rev_offset = (optrev),                                 \
+               .sysc_offset = (optsysc),                               \
+               .syss_offset = (optsyss),                               \
+               .revision = (optrev_val),                               \
+               .revision_mask = (optrevmask),                          \
+               .quirks = (optquirkmask),                               \
+       }
+
+static const struct sysc_revision_quirk sysc_revision_quirks[] = {
+       /* These drivers need to be fixed to not use pm_runtime_irq_safe() */
+       SYSC_QUIRK("gpio", 0, 0, 0x10, 0x114, 0x50600801, 0xffffffff,
+                  SYSC_QUIRK_LEGACY_IDLE),
+       SYSC_QUIRK("mmu", 0, 0, 0x10, 0x14, 0x00000020, 0xffffffff,
+                  SYSC_QUIRK_LEGACY_IDLE),
+       SYSC_QUIRK("mmu", 0, 0, 0x10, 0x14, 0x00000030, 0xffffffff,
+                  SYSC_QUIRK_LEGACY_IDLE),
+       SYSC_QUIRK("sham", 0, 0x100, 0x110, 0x114, 0x40000c03, 0xffffffff,
+                  SYSC_QUIRK_LEGACY_IDLE),
+       SYSC_QUIRK("smartreflex", 0, -1, 0x24, -1, 0x00000000, 0xffffffff,
+                  SYSC_QUIRK_LEGACY_IDLE),
+       SYSC_QUIRK("smartreflex", 0, -1, 0x38, -1, 0x00000000, 0xffffffff,
+                  SYSC_QUIRK_LEGACY_IDLE),
+       SYSC_QUIRK("timer", 0, 0, 0x10, 0x14, 0x00000015, 0xffffffff,
+                  SYSC_QUIRK_LEGACY_IDLE),
+       SYSC_QUIRK("uart", 0, 0x50, 0x54, 0x58, 0x00000052, 0xffffffff,
+                  SYSC_QUIRK_LEGACY_IDLE),
+};
+
+static void sysc_init_revision_quirks(struct sysc *ddata)
+{
+       const struct sysc_revision_quirk *q;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(sysc_revision_quirks); i++) {
+               q = &sysc_revision_quirks[i];
+
+               if (q->base && q->base != ddata->module_pa)
+                       continue;
+
+               if (q->rev_offset >= 0 &&
+                   q->rev_offset != ddata->offsets[SYSC_REVISION])
+                       continue;
+
+               if (q->sysc_offset >= 0 &&
+                   q->sysc_offset != ddata->offsets[SYSC_SYSCONFIG])
+                       continue;
+
+               if (q->syss_offset >= 0 &&
+                   q->syss_offset != ddata->offsets[SYSC_SYSSTATUS])
+                       continue;
+
+               if (q->revision == ddata->revision ||
+                   (q->revision & q->revision_mask) ==
+                   (ddata->revision & q->revision_mask)) {
+                       ddata->name = q->name;
+                       ddata->cfg.quirks |= q->quirks;
+               }
+       }
+}
+
 /* At this point the module is configured enough to read the revision */
 static int sysc_init_module(struct sysc *ddata)
 {
        int error;
 
+       if (ddata->cfg.quirks & SYSC_QUIRK_NO_IDLE_ON_INIT) {
+               ddata->revision = sysc_read_revision(ddata);
+               goto rev_quirks;
+       }
+
        error = pm_runtime_get_sync(ddata->dev);
        if (error < 0) {
                pm_runtime_put_noidle(ddata->dev);
@@ -626,6 +719,9 @@ static int sysc_init_module(struct sysc *ddata)
        ddata->revision = sysc_read_revision(ddata);
        pm_runtime_put_sync(ddata->dev);
 
+rev_quirks:
+       sysc_init_revision_quirks(ddata);
+
        return 0;
 }
 
@@ -753,6 +849,127 @@ static struct sysc *sysc_child_to_parent(struct device *dev)
        return dev_get_drvdata(parent);
 }
 
+static int __maybe_unused sysc_child_runtime_suspend(struct device *dev)
+{
+       struct sysc *ddata;
+       int error;
+
+       ddata = sysc_child_to_parent(dev);
+
+       error = pm_generic_runtime_suspend(dev);
+       if (error)
+               return error;
+
+       if (!ddata->enabled)
+               return 0;
+
+       return sysc_runtime_suspend(ddata->dev);
+}
+
+static int __maybe_unused sysc_child_runtime_resume(struct device *dev)
+{
+       struct sysc *ddata;
+       int error;
+
+       ddata = sysc_child_to_parent(dev);
+
+       if (!ddata->enabled) {
+               error = sysc_runtime_resume(ddata->dev);
+               if (error < 0)
+                       dev_err(ddata->dev,
+                               "%s error: %i\n", __func__, error);
+       }
+
+       return pm_generic_runtime_resume(dev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sysc_child_suspend_noirq(struct device *dev)
+{
+       struct sysc *ddata;
+       int error;
+
+       ddata = sysc_child_to_parent(dev);
+
+       error = pm_generic_suspend_noirq(dev);
+       if (error)
+               return error;
+
+       if (!pm_runtime_status_suspended(dev)) {
+               error = pm_generic_runtime_suspend(dev);
+               if (error)
+                       return error;
+
+               error = sysc_runtime_suspend(ddata->dev);
+               if (error)
+                       return error;
+
+               ddata->child_needs_resume = true;
+       }
+
+       return 0;
+}
+
+static int sysc_child_resume_noirq(struct device *dev)
+{
+       struct sysc *ddata;
+       int error;
+
+       ddata = sysc_child_to_parent(dev);
+
+       if (ddata->child_needs_resume) {
+               ddata->child_needs_resume = false;
+
+               error = sysc_runtime_resume(ddata->dev);
+               if (error)
+                       dev_err(ddata->dev,
+                               "%s runtime resume error: %i\n",
+                               __func__, error);
+
+               error = pm_generic_runtime_resume(dev);
+               if (error)
+                       dev_err(ddata->dev,
+                               "%s generic runtime resume: %i\n",
+                               __func__, error);
+       }
+
+       return pm_generic_resume_noirq(dev);
+}
+#endif
+
+struct dev_pm_domain sysc_child_pm_domain = {
+       .ops = {
+               SET_RUNTIME_PM_OPS(sysc_child_runtime_suspend,
+                                  sysc_child_runtime_resume,
+                                  NULL)
+               USE_PLATFORM_PM_SLEEP_OPS
+               SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(sysc_child_suspend_noirq,
+                                             sysc_child_resume_noirq)
+       }
+};
+
+/**
+ * sysc_legacy_idle_quirk - handle children in omap_device compatible way
+ * @ddata: device driver data
+ * @child: child device driver
+ *
+ * Allow idle for child devices as done with _od_runtime_suspend().
+ * Otherwise many child devices will not idle because of the permanent
+ * parent usecount set in pm_runtime_irq_safe().
+ *
+ * Note that the long term solution is to just modify the child device
+ * drivers to not set pm_runtime_irq_safe() and then this can be just
+ * dropped.
+ */
+static void sysc_legacy_idle_quirk(struct sysc *ddata, struct device *child)
+{
+       if (!ddata->legacy_mode)
+               return;
+
+       if (ddata->cfg.quirks & SYSC_QUIRK_LEGACY_IDLE)
+               dev_pm_domain_set(child, &sysc_child_pm_domain);
+}
+
 static int sysc_notifier_call(struct notifier_block *nb,
                              unsigned long event, void *device)
 {
@@ -770,6 +987,7 @@ static int sysc_notifier_call(struct notifier_block *nb,
                if (error && error != -EEXIST)
                        dev_warn(ddata->dev, "could not add %s fck: %i\n",
                                 dev_name(dev), error);
+               sysc_legacy_idle_quirk(ddata, dev);
                break;
        default:
                break;
@@ -974,7 +1192,8 @@ static const struct sysc_capabilities sysc_34xx_sr = {
        .type = TI_SYSC_OMAP34XX_SR,
        .sysc_mask = SYSC_OMAP2_CLOCKACTIVITY,
        .regbits = &sysc_regbits_omap34xx_sr,
-       .mod_quirks = SYSC_QUIRK_USE_CLOCKACT | SYSC_QUIRK_UNCACHED,
+       .mod_quirks = SYSC_QUIRK_USE_CLOCKACT | SYSC_QUIRK_UNCACHED |
+                     SYSC_QUIRK_LEGACY_IDLE,
 };
 
 /*
@@ -995,12 +1214,13 @@ static const struct sysc_capabilities sysc_36xx_sr = {
        .type = TI_SYSC_OMAP36XX_SR,
        .sysc_mask = SYSC_OMAP3_SR_ENAWAKEUP,
        .regbits = &sysc_regbits_omap36xx_sr,
-       .mod_quirks = SYSC_QUIRK_UNCACHED,
+       .mod_quirks = SYSC_QUIRK_UNCACHED | SYSC_QUIRK_LEGACY_IDLE,
 };
 
 static const struct sysc_capabilities sysc_omap4_sr = {
        .type = TI_SYSC_OMAP4_SR,
        .regbits = &sysc_regbits_omap36xx_sr,
+       .mod_quirks = SYSC_QUIRK_LEGACY_IDLE,
 };
 
 /*