ARM: OMAP2+: omap_hwmod: Introduce HWMOD_NEEDS_REIDLE
[rpmsg/hwspinlock.git] / arch / arm / mach-omap2 / omap_hwmod.c
index cd65ea4e9c54e633bd66a0178ca3f06ad16e8db9..56c452db430a3c237e7e439e8afae2336b8029a7 100644 (file)
 #include <linux/cpu.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
+#include <linux/suspend.h>
 #include <linux/bootmem.h>
 
 #include <linux/platform_data/ti-sysc.h>
@@ -236,6 +237,9 @@ static struct omap_hwmod_soc_ops soc_ops;
 /* omap_hwmod_list contains all registered struct omap_hwmods */
 static LIST_HEAD(omap_hwmod_list);
 
+/* oh_reidle_list contains all omap_hwmods with HWMOD_NEEDS_REIDLE set */
+static LIST_HEAD(oh_reidle_list);
+
 /* mpu_oh: used to add/remove MPU initiator from sleepdep list */
 static struct omap_hwmod *mpu_oh;
 
@@ -2266,6 +2270,28 @@ int omap_hwmod_parse_module_range(struct omap_hwmod *oh,
        return 0;
 }
 
+/**
+ * _setup_reidle- check hwmod @oh and add to reidle list
+ * @oh: struct omap_hwmod *
+ * @n: (unused)
+ *
+ * Check hwmod for HWMOD_NEEDS_REIDLE flag and add to list if
+ * necessary. Return 0 on success.
+ */
+static int _setup_reidle(struct omap_hwmod *oh, void *data)
+{
+       int ret;
+
+       if (oh->flags & HWMOD_NEEDS_REIDLE) {
+               ret = omap_hwmod_enable_reidle(oh);
+
+               if (!ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
 /**
  * _init_mpu_rt_base - populate the virtual address for a hwmod
  * @oh: struct omap_hwmod * to locate the virtual address
@@ -2915,6 +2941,54 @@ static int _am33xx_deassert_hardreset(struct omap_hwmod *oh,
                                           oh->prcm.omap4.rstst_offs);
 }
 
+/**
+ * _reidle - enable then idle a single hwmod
+ *
+ * enables and then immediately reidles an hwmod, as certain hwmods may
+ * not have their sysconfig registers programmed in an idle friendly state
+ * by default
+ */
+static void _reidle(struct omap_hwmod *oh)
+{
+       pr_debug("omap_hwmod: %s: %s\n", oh->name, __func__);
+
+       omap_hwmod_enable(oh);
+       omap_hwmod_softreset(oh);
+       omap_hwmod_idle(oh);
+}
+
+/**
+ * _reidle_all - enable then idle all hwmods in oh_reidle_list
+ *
+ * Called by pm_notifier to make sure flagged modules do not block suspend
+ * after context loss.
+ */
+static int _reidle_all(void)
+{
+       struct omap_hwmod_list *oh_list_item = NULL;
+
+       list_for_each_entry(oh_list_item, &oh_reidle_list, oh_list) {
+               _reidle(oh_list_item->oh);
+       }
+
+       return 0;
+}
+
+static int _omap_device_pm_notifier(struct notifier_block *self,
+                                   unsigned long action, void *dev)
+{
+       switch (action) {
+       case PM_POST_SUSPEND:
+               _reidle_all();
+       }
+
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block pm_nb = {
+       .notifier_call = _omap_device_pm_notifier,
+};
+
 /* Public functions */
 
 u32 omap_hwmod_read(struct omap_hwmod *oh, u16 reg_offs)
@@ -3566,6 +3640,52 @@ static int __init omap_hwmod_setup_all(void)
 }
 omap_postcore_initcall(omap_hwmod_setup_all);
 
+/**
+ * omap_hwmod_enable_reidle - add an omap_hwmod to reidle list
+ * @oh: struct omap_hwmod *
+ *
+ * Adds the omap_hwmod to the oh_reidle_list so it will gets enabled then idled
+ * after each suspend cycle. Returns 0 on success.
+ */
+int omap_hwmod_enable_reidle(struct omap_hwmod *oh)
+{
+       struct omap_hwmod_list *oh_list_item = NULL;
+
+       oh_list_item = kzalloc(sizeof(*oh_list_item), GFP_KERNEL);
+
+       if (!oh_list_item)
+               return -ENOMEM;
+
+       oh_list_item->oh = oh;
+       list_add(&oh_list_item->oh_list, &oh_reidle_list);
+
+       pr_debug("omap_hwmod: %s: added to reidle list\n", oh->name);
+
+       return 0;
+}
+
+/**
+ * omap_hwmod_disable_reidle - remove an omap_hwmod from reidle list
+ * @oh: struct omap_hwmod *
+ *
+ * Remove the omap_hwmod from the oh_reidle_list. Returns 0 on success.
+ */
+int omap_hwmod_disable_reidle(struct omap_hwmod *oh)
+{
+       struct omap_hwmod_list *li, *oh_list_item = NULL;
+
+       list_for_each_entry_safe(oh_list_item, li, &oh_reidle_list, oh_list) {
+               if (oh_list_item->oh == oh) {
+                       list_del(&oh_list_item->oh_list);
+                       pr_debug("omap_hwmod: %s: removed from reidle list\n",
+                                oh->name);
+                       kfree(oh_list_item);
+               }
+       }
+
+       return 0;
+}
+
 /**
  * omap_hwmod_enable - enable an omap_hwmod
  * @oh: struct omap_hwmod *
@@ -3985,6 +4105,21 @@ void __init omap_hwmod_init(void)
        inited = true;
 }
 
+/**
+ * omap_hwmod_setup_reidle - add hwmods to reidle list and register notifier
+ *
+ * Returns 0 on success.
+ */
+int omap_hwmod_setup_reidle(void)
+{
+       omap_hwmod_for_each(_setup_reidle, NULL);
+
+       if (!list_empty(&oh_reidle_list))
+               register_pm_notifier(&pm_nb);
+
+       return 0;
+}
+
 /**
  * omap_hwmod_get_main_clk - get pointer to main clock name
  * @oh: struct omap_hwmod *