@@ -208,6 +208,27 @@ static int omap_device_build_from_dt(struct platform_device *pdev)
return ret;
}
+/**
+ * _omap_device_check_reidle_hwmods - check all hwmods in device for reidle flag
+ * @od: struct omap_device *od
+ *
+ * Checks underlying hwmods for reidle flag, if present, remove from hwmod
+ * list and set flag in omap_device to keep track. Returns 0.
+ */
+static int _omap_device_check_reidle_hwmods(struct omap_device *od)
+{
+ int i;
+
+ for (i = 0; i < od->hwmods_cnt; i++) {
+ if (od->hwmods[i]->flags & HWMOD_NEEDS_REIDLE) {
+ od->flags |= OMAP_DEVICE_HAS_REIDLE_HWMODS;
+ omap_hwmod_disable_reidle(od->hwmods[i]);
+ }
+ }
+
+ return 0;
+}
+
static int _omap_device_notifier_call(struct notifier_block *nb,
unsigned long event, void *dev)
{
@@ -237,6 +258,13 @@ static int _omap_device_notifier_call(struct notifier_block *nb,
pm_runtime_set_active(dev);
}
break;
+ case BUS_NOTIFY_BOUND_DRIVER:
+ od = to_omap_device(pdev);
+ if (od) {
+ od->_driver_status = BUS_NOTIFY_BOUND_DRIVER;
+ _omap_device_check_reidle_hwmods(od);
+ }
+ break;
case BUS_NOTIFY_ADD_DEVICE:
if (pdev->dev.of_node)
omap_device_build_from_dt(pdev);
@@ -285,6 +313,24 @@ static int _omap_device_idle_hwmods(struct omap_device *od)
return ret;
}
+/**
+ * _omap_device_reidle_hwmods - call omap_hwmod_enable_reidle on all hwmods
+ * @od: struct omap_device *od
+ *
+ * Add all underlying hwmods to hwmod reidle list. Returns 0.
+ */
+static int _omap_device_reidle_hwmods(struct omap_device *od)
+{
+ int i;
+
+ for (i = 0; i < od->hwmods_cnt; i++)
+ if (od->hwmods[i]->flags | HWMOD_NEEDS_REIDLE)
+ omap_hwmod_enable_reidle(od->hwmods[i]);
+
+ /* XXX pass along return value here? */
+ return 0;
+}
+
/* Public functions for use by core code */
/**
@@ -370,6 +416,9 @@ void omap_device_delete(struct omap_device *od)
if (!od)
return;
+ if (od->flags & OMAP_DEVICE_HAS_REIDLE_HWMODS)
+ _omap_device_reidle_hwmods(od);
+
od->pdev->archdata.od = NULL;
kfree(od->hwmods);
kfree(od);
@@ -821,6 +870,7 @@ struct device *omap_device_get_by_hwmod_name(const char *oh_name)
static int __init omap_device_init(void)
{
+ omap_hwmod_setup_reidle();
bus_register_notifier(&platform_bus_type, &platform_nb);
return 0;
}
@@ -39,6 +39,7 @@
/* omap_device.flags values */
#define OMAP_DEVICE_SUSPENDED BIT(0)
+#define OMAP_DEVICE_HAS_REIDLE_HWMODS BIT(1)
/**
* struct omap_device - omap_device wrapper for platform_devices
@@ -141,6 +141,7 @@
#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 @@ struct omap_hwmod_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;
@@ -2230,6 +2234,28 @@ int omap_hwmod_parse_module_range(struct omap_hwmod *oh,
}
/**
+ * _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
* @data: (unused, caller should pass NULL)
@@ -2878,6 +2904,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)
@@ -3529,6 +3603,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 *
*
@@ -3948,6 +4068,21 @@ void __init omap_hwmod_init(void)
}
/**
+ * 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 *
*
@@ -445,6 +445,10 @@ struct omap_hwmod_omap4_prcm {
* entering HW_AUTO while hwmod is active. This is needed to workaround
* some modules which don't function correctly with HW_AUTO. For example,
* DCAN on DRA7x SoC needs this to workaround errata i893.
+ * HWMOD_NEEDS_REIDLE: Some devices do not assert their MSTANDBY signal by
+ * default after losing context if no driver is present and using the
+ * hwmod. This will break subsequent suspend cycles but can be fixed by
+ * enabling then idling the unused hwmod after each suspend cycle.
*/
#define HWMOD_SWSUP_SIDLE (1 << 0)
#define HWMOD_SWSUP_MSTANDBY (1 << 1)
@@ -463,6 +467,7 @@ struct omap_hwmod_omap4_prcm {
#define HWMOD_OPT_CLKS_NEEDED (1 << 14)
#define HWMOD_NO_IDLE (1 << 15)
#define HWMOD_CLKDM_NOAUTO (1 << 16)
+#define HWMOD_NEEDS_REIDLE (1 << 17)
/*
* omap_hwmod._int_flags definitions
@@ -611,6 +616,14 @@ struct omap_hwmod {
struct device_node;
+/*
+ * omap_hwmod_list - simple generic container for omap_hwmod lists
+ */
+struct omap_hwmod_list {
+ struct omap_hwmod *oh;
+ struct list_head oh_list;
+};
+
struct omap_hwmod *omap_hwmod_lookup(const char *name);
int omap_hwmod_for_each(int (*fn)(struct omap_hwmod *oh, void *data),
void *data);
@@ -649,6 +662,10 @@ int omap_hwmod_get_resource_byname(struct omap_hwmod *oh, unsigned int type,
int omap_hwmod_enable_wakeup(struct omap_hwmod *oh);
int omap_hwmod_disable_wakeup(struct omap_hwmod *oh);
+int omap_hwmod_setup_reidle(void);
+int omap_hwmod_enable_reidle(struct omap_hwmod *oh);
+int omap_hwmod_disable_reidle(struct omap_hwmod *oh);
+
int omap_hwmod_for_each_by_class(const char *classname,
int (*fn)(struct omap_hwmod *oh,
void *user),
@@ -375,7 +375,8 @@ struct omap_hwmod am33xx_cpgmac0_hwmod = {
.name = "cpgmac0",
.class = &am33xx_cpgmac0_hwmod_class,
.clkdm_name = "cpsw_125mhz_clkdm",
- .flags = (HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY),
+ .flags = HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY |
+ HWMOD_NEEDS_REIDLE,
.main_clk = "cpsw_125mhz_gclk",
.mpu_rt_idx = 1,
.prcm = {
@@ -618,7 +619,7 @@ struct omap_hwmod am33xx_gpmc_hwmod = {
.class = &am33xx_gpmc_hwmod_class,
.clkdm_name = "l3s_clkdm",
/* Skip reset for CONFIG_OMAP_GPMC_DEBUG for bootloader timings */
- .flags = DEBUG_OMAP_GPMC_HWMOD_FLAGS,
+ .flags = DEBUG_OMAP_GPMC_HWMOD_FLAGS | HWMOD_NEEDS_REIDLE,
.main_clk = "l3s_gclk",
.prcm = {
.omap4 = {
@@ -1094,7 +1095,8 @@ struct omap_hwmod am33xx_tptc0_hwmod = {
.name = "tptc0",
.class = &am33xx_tptc_hwmod_class,
.clkdm_name = "l3_clkdm",
- .flags = HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY,
+ .flags = HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY |
+ HWMOD_NEEDS_REIDLE,
.main_clk = "l3_gclk",
.prcm = {
.omap4 = {
@@ -1108,7 +1110,8 @@ struct omap_hwmod am33xx_tptc1_hwmod = {
.name = "tptc1",
.class = &am33xx_tptc_hwmod_class,
.clkdm_name = "l3_clkdm",
- .flags = (HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY),
+ .flags = HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY |
+ HWMOD_NEEDS_REIDLE,
.main_clk = "l3_gclk",
.prcm = {
.omap4 = {
@@ -1122,7 +1125,8 @@ struct omap_hwmod am33xx_tptc2_hwmod = {
.name = "tptc2",
.class = &am33xx_tptc_hwmod_class,
.clkdm_name = "l3_clkdm",
- .flags = (HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY),
+ .flags = HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY |
+ HWMOD_NEEDS_REIDLE,
.main_clk = "l3_gclk",
.prcm = {
.omap4 = {
@@ -301,7 +301,8 @@
.name = "usb_otg_hs",
.class = &am33xx_usbotg_class,
.clkdm_name = "l3s_clkdm",
- .flags = HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY,
+ .flags = HWMOD_SWSUP_SIDLE | HWMOD_SWSUP_MSTANDBY |
+ HWMOD_NEEDS_REIDLE,
.main_clk = "usbotg_fck",
.prcm = {
.omap4 = {