@@ -1295,3 +1295,26 @@ int clkdm_hwmod_disable(struct clockdomain *clkdm, struct omap_hwmod *oh)
return 0;
}
+int clkdm_control_status(struct clockdomain *clkdm, bool *disable_auto, bool *force_sleep, bool *force_wakeup, bool *enable_auto)
+{
+ /* The clkdm attribute does not exist yet prior OMAP4 */
+ if (cpu_is_omap24xx() || cpu_is_omap34xx())
+ return 0;
+
+ if (!clkdm || !arch_clkdm || !arch_clkdm->clkdm_control_status || !disable_auto || !force_sleep || !force_wakeup || !enable_auto)
+ return -EINVAL;
+
+ return arch_clkdm->clkdm_control_status(clkdm, disable_auto, force_sleep, force_wakeup, enable_auto);
+}
+
+int clkdm_current_status(struct clockdomain *clkdm, struct omap_hwmod *oh, bool *functional, bool *in_transition, bool *if_idle, bool *disabled)
+{
+ /* The clkdm attribute does not exist yet prior OMAP4 */
+ if (cpu_is_omap24xx() || cpu_is_omap34xx())
+ return 0;
+
+ if (!clkdm || !oh || !arch_clkdm || !arch_clkdm->clkdm_current_status || !functional || !in_transition || !if_idle || !disabled)
+ return -EINVAL;
+
+ return arch_clkdm->clkdm_current_status(clkdm, oh, functional, in_transition, if_idle, disabled);
+}
@@ -172,8 +172,13 @@ struct clkdm_ops {
void (*clkdm_deny_idle)(struct clockdomain *clkdm);
int (*clkdm_clk_enable)(struct clockdomain *clkdm);
int (*clkdm_clk_disable)(struct clockdomain *clkdm);
+ int (*clkdm_control_status)(struct clockdomain *clkdm, bool *disable_auto, bool *force_sleep, bool *force_wakeup, bool *enable_auto);
+ int (*clkdm_current_status)(struct clockdomain *clkdm, struct omap_hwmod *oh, bool *functional, bool *intransition, bool *if_idle, bool *disabled);
};
+int clkdm_control_status(struct clockdomain *clkdm, bool *disable_auto, bool *force_sleep, bool *force_wakeup, bool *enable_auto);
+int clkdm_current_status(struct clockdomain *clkdm, struct omap_hwmod *oh, bool *functional, bool *intransition, bool *if_idle, bool *disabled);
+
int clkdm_register_platform_funcs(struct clkdm_ops *co);
int clkdm_register_autodeps(struct clkdm_autodep *ia);
int clkdm_register_clkdms(struct clockdomain **c);
@@ -467,6 +467,44 @@ static int omap4_clkdm_clk_disable(struct clockdomain *clkdm)
return 0;
}
+static int omap4_clkdm_control_status(struct clockdomain *clkdm, bool *disable_auto, bool *force_sleep, bool *force_wakeup, bool *enable_auto)
+{
+ u32 v;
+
+ if (!clkdm->prcm_partition)
+ return -EINVAL;
+ v = omap4_cminst_read_inst_reg(clkdm->prcm_partition, clkdm->cm_inst, clkdm->clkdm_offs + OMAP4_CM_CLKSTCTRL);
+ v &= OMAP4430_CLKTRCTRL_MASK;
+ v >>= OMAP4430_CLKTRCTRL_SHIFT;
+
+ *enable_auto = (v == OMAP34XX_CLKSTCTRL_ENABLE_AUTO) ? true : false;
+ *force_wakeup = (v == OMAP34XX_CLKSTCTRL_FORCE_WAKEUP) ? true : false;
+ *force_sleep = (v == OMAP34XX_CLKSTCTRL_FORCE_SLEEP) ? true : false;
+ *disable_auto = (v == OMAP34XX_CLKSTCTRL_DISABLE_AUTO) ? true : false;
+
+ return 0;
+}
+
+static int omap4_clkdm_current_status(struct clockdomain *clkdm, struct omap_hwmod *oh, bool *functional, bool *intransition, bool *if_idle, bool *disabled)
+{
+ u32 idlest = 0;
+ u16 clkctrl_offs = oh->prcm.omap4.clkctrl_offs;
+
+ if (!clkdm->prcm_partition || !clkctrl_offs)
+ return -EINVAL;
+
+ idlest = _clkctrl_idlest(clkdm->prcm_partition, clkdm->cm_inst, clkdm->clkdm_offs, clkctrl_offs);
+ if (idlest == CLKCTRL_IDLEST_FUNCTIONAL)
+ *functional = true;
+ else if (idlest == CLKCTRL_IDLEST_INTRANSITION)
+ *intransition = true;
+ else if (idlest == CLKCTRL_IDLEST_INTERFACE_IDLE)
+ *if_idle = true;
+ else if (idlest == CLKCTRL_IDLEST_DISABLED)
+ *disabled = true;
+ return 0;
+}
+
struct clkdm_ops omap4_clkdm_operations = {
.clkdm_add_wkdep = omap4_clkdm_add_wkup_sleep_dep,
.clkdm_del_wkdep = omap4_clkdm_del_wkup_sleep_dep,
@@ -482,4 +520,6 @@ struct clkdm_ops omap4_clkdm_operations = {
.clkdm_deny_idle = omap4_clkdm_deny_idle,
.clkdm_clk_enable = omap4_clkdm_clk_enable,
.clkdm_clk_disable = omap4_clkdm_clk_disable,
+ .clkdm_control_status = omap4_clkdm_control_status,
+ .clkdm_current_status = omap4_clkdm_current_status,
};
@@ -39,6 +39,91 @@
u32 enable_off_mode;
+static int _check_hwmod(struct omap_hwmod *oh, void *data)
+{
+ struct clockdomain *clkdm = data;
+ bool functional = false;
+ bool in_transition = false;
+ bool if_idle = false;
+ bool disabled = false;
+ char *state = "unknown";
+ bool print = true;
+
+ /* Skip if we dont have clkdm match */
+ if (clkdm != oh->clkdm)
+ return 0;
+
+ if (!clkdm_current_status(clkdm, oh, &functional, &in_transition, &if_idle, &disabled)) {
+ if (disabled) {
+ print = false;
+ state = "disabled";
+ } else {
+ if (functional)
+ state = "functional";
+ else if (in_transition)
+ state = "stuck in transition";
+ else if (if_idle)
+ state = "i/f clk idled - if iclk = fclk for this module, consider as disabled";
+ }
+ }
+
+ if (print)
+ pr_err("\t\t%s: %s\n", oh->name, state);
+
+ return 0;
+}
+
+static int _check_clkdm_state(struct powerdomain *pwrdm, struct clockdomain *clkdm)
+{
+ char sflags[255] = {0};
+ char hflags[255] = {0};
+ bool disable_auto = false;
+ bool force_sleep = false;
+ bool force_wakeup = false;
+ bool enable_auto = false;
+
+
+ if (clkdm->flags & CLKDM_CAN_HWSUP_SWSUP)
+ strncat(sflags, "HWSUP_SWSUP ", sizeof(sflags));
+
+ else if (clkdm->flags & CLKDM_CAN_HWSUP)
+ strncat(sflags, "HWSUP ", sizeof(sflags));
+ else if (clkdm->flags & CLKDM_CAN_SWSUP)
+ strncat(sflags, "SWSUP ", sizeof(sflags));
+ if (clkdm->flags & CLKDM_NO_AUTODEPS)
+ strncat(sflags, "NO_AUTODEPS ", sizeof(sflags));
+ if (clkdm->flags & CLKDM_ACTIVE_WITH_MPU)
+ strncat(sflags, "ACTIVE_WITH_MPU ", sizeof(sflags));
+ if (clkdm->flags & CLKDM_MISSING_IDLE_REPORTING)
+ strncat(sflags, "MISSING_IDLE_REPORTING ", sizeof(sflags));
+ if (clkdm->_flags & _CLKDM_FLAG_HWSUP_ENABLED)
+ strncat(sflags, "HWSUP_ENABLED(int) ", sizeof(sflags));
+
+ if (!clkdm_control_status(clkdm, &disable_auto, &force_sleep, &force_wakeup, &enable_auto) ){
+ if (disable_auto)
+ strncat(hflags, "disable_auto ", sizeof(hflags));
+ if (enable_auto)
+ strncat(hflags, "enable_auto ", sizeof(hflags));
+ if (force_sleep)
+ strncat(hflags, "force_sleep ", sizeof(hflags));
+ if (force_wakeup)
+ strncat(hflags, "force_wakeup ", sizeof(hflags));
+ }
+
+ pr_info("\t %s: s/w flags(%s) h/w control(%s)\n",
+ clkdm->name, sflags, hflags);
+ /* Need to check per hwmod for match */
+ omap_hwmod_for_each(_check_hwmod, clkdm);
+
+
+ return 0;
+}
+
+void pm_debug_check_pwrdm(struct powerdomain *pwrdm)
+{
+ pwrdm_for_each_clkdm(pwrdm,_check_clkdm_state);
+}
+
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#include <linux/seq_file.h>
@@ -147,4 +147,10 @@ static inline void omap_pm_get_oscillator(u32 *tstart, u32 *tshut) { *tstart = *
static inline void omap_pm_setup_sr_i2c_pcb_length(u32 mm) { }
#endif
+#ifdef CONFIG_PM_DEBUG
+void pm_debug_check_pwrdm(struct powerdomain *pwrdm);
+#else
+static inline void pm_debug_check_pwrdm(struct powerdomain *pwrdm) { };
+#endif
+
#endif
@@ -72,6 +72,7 @@ static int omap4_pm_suspend(void)
if (state > pwrst->next_state) {
pr_info("Powerdomain (%s) didn't enter target state %d\n",
pwrst->pwrdm->name, pwrst->next_state);
+ pm_debug_check_pwrdm(pwrst->pwrdm);
ret = -1;
}
omap_set_pwrdm_state(pwrst->pwrdm, pwrst->saved_state);
@@ -544,7 +544,8 @@ int pwrdm_for_each_clkdm(struct powerdomain *pwrdm,
return -EINVAL;
for (i = 0; i < PWRDM_MAX_CLKDMS && !ret; i++)
- ret = (*fn)(pwrdm, pwrdm->pwrdm_clkdms[i]);
+ if (pwrdm->pwrdm_clkdms[i])
+ ret = (*fn)(pwrdm, pwrdm->pwrdm_clkdms[i]);
return ret;
}
Traditionally on OMAP4+ systems, with complete data properly populated, we should be able to track down which device prevented the power domain from transitioning to low power state. This is rather trivial is the powerdomain to clockdomain to list of clock status registers were easily accessible. However, it is not. we have linkage from clk->clkdm, and hwmod to clkdm, however going from clkdm to clk status involves a search for matches :(. Previously, we just get: Powerdomain (XYZ) didn't enter target state 1 Now, we get: Powerdomain (XYZ) didn't enter target state 1 XYZ: s/w flags(HWSUP_SWSUP HWSUP_ENABLED(int) ) h/w control(enable_auto ) module-X: functional This simplifies our debug when hwmod is properly populated with every device in the SoC and when status bits are properly identified. However, the alternative to doing this is to replicate actual h/w data in some other form which duplicates existing infrastructure for the same. Signed-off-by: Nishanth Menon <nm@ti.com> --- Traditionally, we have add solutions in production kernel that duplicated data such as: 3.0: https://android.googlesource.com/kernel/omap.git/+/android-omap-tuna-3.0-jb-mr2/arch/arm/mach-omap2/prcm-debug.c 3.4: http://git.omapzoom.org/?p=kernel/omap.git;a=blob;f=arch/arm/mach-omap2/prcm-54xx-debug_data.c;h=26d9b1e74368ec387db86fde6208e37ffeb95d5c;hb=p-linux-omap-3.4 Which for obvious reasons dont make sense in an upstream kernel - the rationale for duplicated data was the in-development state of pm code which needed cross verification as well. The code is a bit dirty, but then, this is just an RFC to see if folks are interested in this feature. If folks think this actually adds value, then we can split this up properly and control the prints with some debugfs control flag (instead of spamming every single time). we could discuss even on how to make this prettier, do checkpatch, split into a proper series etc.. just *if* folks think it is worth the work.. Sample logs (using TI vendor kernel): DRA7-evm: http://pastebin.mozilla.org/3606008 OMAP5-uEVM: http://pastebin.mozilla.org/3606015 Obviously, could be extended for more TI platforms as well since it uses the mach-omap2 frameworks - but we could instead decide not to develop this further till we cleanup hwmod instead of adding new usage of the same and make that transition harder.. over to folks for comments.. Applies on kernel tag v3.12 arch/arm/mach-omap2/clockdomain.c | 23 ++++++++++ arch/arm/mach-omap2/clockdomain.h | 5 +++ arch/arm/mach-omap2/cminst44xx.c | 40 +++++++++++++++++ arch/arm/mach-omap2/pm-debug.c | 85 +++++++++++++++++++++++++++++++++++++ arch/arm/mach-omap2/pm.h | 6 +++ arch/arm/mach-omap2/pm44xx.c | 1 + arch/arm/mach-omap2/powerdomain.c | 3 +- 7 files changed, 162 insertions(+), 1 deletion(-)