forked from Minki/linux
2e4b62dc5e
In omap2_common_pm_late_init suspend_set_ops was called to set common suspend handling functions for all omap platforms. This created two problems. First, these suspend ops were being set for all platforms, regardless of whether or not suspend support has been integrated so in the case of AM33XX, suspend to mem was presented as available but failed every time. Second, some platforms will need to define a completely separate set of suspend ops, such as AM33XX, due to differences from previous omap platforms so there is no need to always set the common omap ops. This patch moves the suspend_set_ops call from omap2_common_pm_late_init into a separate function that then gets called in the omap*_pm_init functions for each platform. Signed-off-by: Dave Gerlach <d-gerlach@ti.com> Signed-off-by: Tony Lindgren <tony@atomide.com>
267 lines
7.2 KiB
C
267 lines
7.2 KiB
C
/*
|
|
* OMAP4+ Power Management Routines
|
|
*
|
|
* Copyright (C) 2010-2013 Texas Instruments, Inc.
|
|
* Rajendra Nayak <rnayak@ti.com>
|
|
* Santosh Shilimkar <santosh.shilimkar@ti.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/pm.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/module.h>
|
|
#include <linux/list.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <asm/system_misc.h>
|
|
|
|
#include "soc.h"
|
|
#include "common.h"
|
|
#include "clockdomain.h"
|
|
#include "powerdomain.h"
|
|
#include "pm.h"
|
|
|
|
u16 pm44xx_errata;
|
|
|
|
struct power_state {
|
|
struct powerdomain *pwrdm;
|
|
u32 next_state;
|
|
#ifdef CONFIG_SUSPEND
|
|
u32 saved_state;
|
|
u32 saved_logic_state;
|
|
#endif
|
|
struct list_head node;
|
|
};
|
|
|
|
static LIST_HEAD(pwrst_list);
|
|
|
|
#ifdef CONFIG_SUSPEND
|
|
static int omap4_pm_suspend(void)
|
|
{
|
|
struct power_state *pwrst;
|
|
int state, ret = 0;
|
|
u32 cpu_id = smp_processor_id();
|
|
|
|
/* Save current powerdomain state */
|
|
list_for_each_entry(pwrst, &pwrst_list, node) {
|
|
pwrst->saved_state = pwrdm_read_next_pwrst(pwrst->pwrdm);
|
|
pwrst->saved_logic_state = pwrdm_read_logic_retst(pwrst->pwrdm);
|
|
}
|
|
|
|
/* Set targeted power domain states by suspend */
|
|
list_for_each_entry(pwrst, &pwrst_list, node) {
|
|
omap_set_pwrdm_state(pwrst->pwrdm, pwrst->next_state);
|
|
pwrdm_set_logic_retst(pwrst->pwrdm, PWRDM_POWER_OFF);
|
|
}
|
|
|
|
/*
|
|
* For MPUSS to hit power domain retention(CSWR or OSWR),
|
|
* CPU0 and CPU1 power domains need to be in OFF or DORMANT state,
|
|
* since CPU power domain CSWR is not supported by hardware
|
|
* Only master CPU follows suspend path. All other CPUs follow
|
|
* CPU hotplug path in system wide suspend. On OMAP4, CPU power
|
|
* domain CSWR is not supported by hardware.
|
|
* More details can be found in OMAP4430 TRM section 4.3.4.2.
|
|
*/
|
|
omap4_enter_lowpower(cpu_id, PWRDM_POWER_OFF);
|
|
|
|
/* Restore next powerdomain state */
|
|
list_for_each_entry(pwrst, &pwrst_list, node) {
|
|
state = pwrdm_read_prev_pwrst(pwrst->pwrdm);
|
|
if (state > pwrst->next_state) {
|
|
pr_info("Powerdomain (%s) didn't enter target state %d\n",
|
|
pwrst->pwrdm->name, pwrst->next_state);
|
|
ret = -1;
|
|
}
|
|
omap_set_pwrdm_state(pwrst->pwrdm, pwrst->saved_state);
|
|
pwrdm_set_logic_retst(pwrst->pwrdm, pwrst->saved_logic_state);
|
|
}
|
|
if (ret) {
|
|
pr_crit("Could not enter target state in pm_suspend\n");
|
|
/*
|
|
* OMAP4 chip PM currently works only with certain (newer)
|
|
* versions of bootloaders. This is due to missing code in the
|
|
* kernel to properly reset and initialize some devices.
|
|
* Warn the user about the bootloader version being one of the
|
|
* possible causes.
|
|
* http://www.spinics.net/lists/arm-kernel/msg218641.html
|
|
*/
|
|
pr_warn("A possible cause could be an old bootloader - try u-boot >= v2012.07\n");
|
|
} else {
|
|
pr_info("Successfully put all powerdomains to target state\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
#define omap4_pm_suspend NULL
|
|
#endif /* CONFIG_SUSPEND */
|
|
|
|
static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)
|
|
{
|
|
struct power_state *pwrst;
|
|
|
|
if (!pwrdm->pwrsts)
|
|
return 0;
|
|
|
|
/*
|
|
* Skip CPU0 and CPU1 power domains. CPU1 is programmed
|
|
* through hotplug path and CPU0 explicitly programmed
|
|
* further down in the code path
|
|
*/
|
|
if (!strncmp(pwrdm->name, "cpu", 3))
|
|
return 0;
|
|
|
|
pwrst = kmalloc(sizeof(struct power_state), GFP_ATOMIC);
|
|
if (!pwrst)
|
|
return -ENOMEM;
|
|
|
|
pwrst->pwrdm = pwrdm;
|
|
pwrst->next_state = PWRDM_POWER_RET;
|
|
list_add(&pwrst->node, &pwrst_list);
|
|
|
|
return omap_set_pwrdm_state(pwrst->pwrdm, pwrst->next_state);
|
|
}
|
|
|
|
/**
|
|
* omap_default_idle - OMAP4 default ilde routine.'
|
|
*
|
|
* Implements OMAP4 memory, IO ordering requirements which can't be addressed
|
|
* with default cpu_do_idle() hook. Used by all CPUs with !CONFIG_CPU_IDLE and
|
|
* by secondary CPU with CONFIG_CPU_IDLE.
|
|
*/
|
|
static void omap_default_idle(void)
|
|
{
|
|
omap_do_wfi();
|
|
}
|
|
|
|
/**
|
|
* omap4_init_static_deps - Add OMAP4 static dependencies
|
|
*
|
|
* Add needed static clockdomain dependencies on OMAP4 devices.
|
|
* Return: 0 on success or 'err' on failures
|
|
*/
|
|
static inline int omap4_init_static_deps(void)
|
|
{
|
|
struct clockdomain *emif_clkdm, *mpuss_clkdm, *l3_1_clkdm;
|
|
struct clockdomain *ducati_clkdm, *l3_2_clkdm;
|
|
int ret = 0;
|
|
|
|
if (omap_rev() == OMAP4430_REV_ES1_0) {
|
|
WARN(1, "Power Management not supported on OMAP4430 ES1.0\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
pr_err("Power Management for TI OMAP4.\n");
|
|
/*
|
|
* OMAP4 chip PM currently works only with certain (newer)
|
|
* versions of bootloaders. This is due to missing code in the
|
|
* kernel to properly reset and initialize some devices.
|
|
* http://www.spinics.net/lists/arm-kernel/msg218641.html
|
|
*/
|
|
pr_warn("OMAP4 PM: u-boot >= v2012.07 is required for full PM support\n");
|
|
|
|
ret = pwrdm_for_each(pwrdms_setup, NULL);
|
|
if (ret) {
|
|
pr_err("Failed to setup powerdomains\n");
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The dynamic dependency between MPUSS -> MEMIF and
|
|
* MPUSS -> L4_PER/L3_* and DUCATI -> L3_* doesn't work as
|
|
* expected. The hardware recommendation is to enable static
|
|
* dependencies for these to avoid system lock ups or random crashes.
|
|
* The L4 wakeup depedency is added to workaround the OCP sync hardware
|
|
* BUG with 32K synctimer which lead to incorrect timer value read
|
|
* from the 32K counter. The BUG applies for GPTIMER1 and WDT2 which
|
|
* are part of L4 wakeup clockdomain.
|
|
*/
|
|
mpuss_clkdm = clkdm_lookup("mpuss_clkdm");
|
|
emif_clkdm = clkdm_lookup("l3_emif_clkdm");
|
|
l3_1_clkdm = clkdm_lookup("l3_1_clkdm");
|
|
l3_2_clkdm = clkdm_lookup("l3_2_clkdm");
|
|
ducati_clkdm = clkdm_lookup("ducati_clkdm");
|
|
if ((!mpuss_clkdm) || (!emif_clkdm) || (!l3_1_clkdm) ||
|
|
(!l3_2_clkdm) || (!ducati_clkdm))
|
|
return -EINVAL;
|
|
|
|
ret = clkdm_add_wkdep(mpuss_clkdm, emif_clkdm);
|
|
ret |= clkdm_add_wkdep(mpuss_clkdm, l3_1_clkdm);
|
|
ret |= clkdm_add_wkdep(mpuss_clkdm, l3_2_clkdm);
|
|
ret |= clkdm_add_wkdep(ducati_clkdm, l3_1_clkdm);
|
|
ret |= clkdm_add_wkdep(ducati_clkdm, l3_2_clkdm);
|
|
if (ret) {
|
|
pr_err("Failed to add MPUSS -> L3/EMIF/L4PER, DUCATI -> L3 wakeup dependency\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* omap4_pm_init_early - Does early initialization necessary for OMAP4+ devices
|
|
*
|
|
* Initializes basic stuff for power management functionality.
|
|
*/
|
|
int __init omap4_pm_init_early(void)
|
|
{
|
|
if (cpu_is_omap446x())
|
|
pm44xx_errata |= PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* omap4_pm_init - Init routine for OMAP4+ devices
|
|
*
|
|
* Initializes all powerdomain and clockdomain target states
|
|
* and all PRCM settings.
|
|
* Return: Returns the error code returned by called functions.
|
|
*/
|
|
int __init omap4_pm_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (omap_rev() == OMAP4430_REV_ES1_0) {
|
|
WARN(1, "Power Management not supported on OMAP4430 ES1.0\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
pr_info("Power Management for TI OMAP4+ devices.\n");
|
|
|
|
ret = pwrdm_for_each(pwrdms_setup, NULL);
|
|
if (ret) {
|
|
pr_err("Failed to setup powerdomains.\n");
|
|
goto err2;
|
|
}
|
|
|
|
if (cpu_is_omap44xx()) {
|
|
ret = omap4_init_static_deps();
|
|
if (ret)
|
|
goto err2;
|
|
}
|
|
|
|
ret = omap4_mpuss_init();
|
|
if (ret) {
|
|
pr_err("Failed to initialise OMAP4 MPUSS\n");
|
|
goto err2;
|
|
}
|
|
|
|
(void) clkdm_for_each(omap_pm_clkdms_setup, NULL);
|
|
|
|
omap_common_suspend_init(omap4_pm_suspend);
|
|
|
|
/* Overwrite the default cpu_do_idle() */
|
|
arm_pm_idle = omap_default_idle;
|
|
|
|
if (cpu_is_omap44xx())
|
|
omap4_idle_init();
|
|
|
|
err2:
|
|
return ret;
|
|
}
|