947 lines
27 KiB
C
947 lines
27 KiB
C
/* arch/arm/mach-msm/pm.c
|
|
*
|
|
* MSM Power Management Routines
|
|
*
|
|
* Copyright (C) 2007 Google, Inc.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/console.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/earlysuspend.h>
|
|
#include <mach/msm_iomap.h>
|
|
#include <mach/system.h>
|
|
#include <asm/io.h>
|
|
#ifdef CONFIG_VFP
|
|
#include <asm/vfp.h>
|
|
#endif
|
|
|
|
#ifdef CONFIG_CACHE_L2X0
|
|
#include <asm/hardware/cache-l2x0.h>
|
|
#endif
|
|
|
|
#include "smd_private.h"
|
|
#include "smd_rpcrouter.h"
|
|
#include "acpuclock.h"
|
|
#include "proc_comm.h"
|
|
#include "pmic_global.h"
|
|
#include "clock.h"
|
|
#include "spm.h"
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
#include <linux/wakelock.h>
|
|
#endif
|
|
#include "board-htcleo.h"
|
|
|
|
enum {
|
|
MSM_PM_DEBUG_SUSPEND = 1U << 0,
|
|
MSM_PM_DEBUG_POWER_COLLAPSE = 1U << 1,
|
|
MSM_PM_DEBUG_STATE = 1U << 2,
|
|
MSM_PM_DEBUG_CLOCK = 1U << 3,
|
|
MSM_PM_DEBUG_RESET_VECTOR = 1U << 4,
|
|
MSM_PM_DEBUG_SMSM_STATE = 1U << 5,
|
|
MSM_PM_DEBUG_IDLE = 1U << 6,
|
|
MSM_PM_DEBUG_SLEEP_LIMIT = 1U << 7,
|
|
MSM_PM_DEBUG_WAKEUP_REASON = 1U << 8,
|
|
};
|
|
static int msm_pm_debug_mask = MSM_PM_DEBUG_SLEEP_LIMIT | MSM_PM_DEBUG_WAKEUP_REASON;
|
|
module_param_named(debug_mask, msm_pm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
|
|
|
|
enum {
|
|
MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND,
|
|
MSM_PM_SLEEP_MODE_POWER_COLLAPSE,
|
|
MSM_PM_SLEEP_MODE_APPS_SLEEP,
|
|
MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT,
|
|
MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT,
|
|
};
|
|
static int msm_pm_sleep_mode = CONFIG_MSM7X00A_SLEEP_MODE;
|
|
module_param_named(sleep_mode, msm_pm_sleep_mode, int, S_IRUGO | S_IWUSR | S_IWGRP);
|
|
static int msm_pm_idle_sleep_mode = CONFIG_MSM7X00A_IDLE_SLEEP_MODE;
|
|
module_param_named(idle_sleep_mode, msm_pm_idle_sleep_mode, int, S_IRUGO | S_IWUSR | S_IWGRP);
|
|
static int msm_pm_idle_sleep_min_time = CONFIG_MSM7X00A_IDLE_SLEEP_MIN_TIME;
|
|
module_param_named(idle_sleep_min_time, msm_pm_idle_sleep_min_time, int, S_IRUGO | S_IWUSR | S_IWGRP);
|
|
static int msm_pm_idle_spin_time = CONFIG_MSM7X00A_IDLE_SPIN_TIME;
|
|
module_param_named(idle_spin_time, msm_pm_idle_spin_time, int, S_IRUGO | S_IWUSR | S_IWGRP);
|
|
|
|
#if defined (CONFIG_ARCH_MSM7X30)
|
|
#define A11S_CLK_SLEEP_EN (MSM_GCC_BASE + 0x020)
|
|
#define A11S_PWRDOWN (MSM_ACC_BASE + 0x01C)
|
|
#define A11S_SECOP (MSM_TCSR_BASE + 0x38)
|
|
#else
|
|
#define A11S_CLK_SLEEP_EN (MSM_CSR_BASE + 0x11c)
|
|
#define A11S_PWRDOWN (MSM_CSR_BASE + 0x440)
|
|
#define A11S_STANDBY_CTL (MSM_CSR_BASE + 0x108)
|
|
#endif
|
|
#define A11RAMBACKBIAS (MSM_CSR_BASE + 0x508)
|
|
|
|
#if defined(CONFIG_MSM_N_WAY_SMD)
|
|
#define DEM_MASTER_BITS_PER_CPU 6
|
|
|
|
/* Power Master State Bits - Per CPU */
|
|
#define DEM_MASTER_SMSM_RUN \
|
|
(0x01UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
|
|
#define DEM_MASTER_SMSM_RSA \
|
|
(0x02UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
|
|
#define DEM_MASTER_SMSM_PWRC_EARLY_EXIT \
|
|
(0x04UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
|
|
#define DEM_MASTER_SMSM_SLEEP_EXIT \
|
|
(0x08UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
|
|
#define DEM_MASTER_SMSM_READY \
|
|
(0x10UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
|
|
#define DEM_MASTER_SMSM_SLEEP \
|
|
(0x20UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
|
|
|
|
/* Power Slave State Bits */
|
|
#define DEM_SLAVE_SMSM_RUN (0x0001)
|
|
#define DEM_SLAVE_SMSM_PWRC (0x0002)
|
|
#define DEM_SLAVE_SMSM_PWRC_DELAY (0x0004)
|
|
#define DEM_SLAVE_SMSM_PWRC_EARLY_EXIT (0x0008)
|
|
#define DEM_SLAVE_SMSM_WFPI (0x0010)
|
|
#define DEM_SLAVE_SMSM_SLEEP (0x0020)
|
|
#define DEM_SLAVE_SMSM_SLEEP_EXIT (0x0040)
|
|
#define DEM_SLAVE_SMSM_MSGS_REDUCED (0x0080)
|
|
#define DEM_SLAVE_SMSM_RESET (0x0100)
|
|
#define DEM_SLAVE_SMSM_PWRC_SUSPEND (0x0200)
|
|
|
|
#define PM_SMSM_WRITE_STATE SMSM_STATE_APPS_DEM
|
|
#define PM_SMSM_READ_STATE SMSM_STATE_POWER_MASTER_DEM
|
|
|
|
#define PM_SMSM_WRITE_RUN DEM_SLAVE_SMSM_RUN
|
|
#define PM_SMSM_READ_RUN DEM_MASTER_SMSM_RUN
|
|
#else
|
|
#define PM_SMSM_WRITE_STATE SMSM_STATE_APPS
|
|
#define PM_SMSM_READ_STATE SMSM_STATE_MODEM
|
|
|
|
#define PM_SMSM_WRITE_RUN SMSM_RUN
|
|
#define PM_SMSM_READ_RUN SMSM_RUN
|
|
#endif
|
|
|
|
int msm_pm_collapse(void);
|
|
int msm_arch_idle(void);
|
|
void msm_pm_collapse_exit(void);
|
|
|
|
int64_t msm_timer_enter_idle(void);
|
|
void msm_timer_exit_idle(int low_power);
|
|
int msm_irq_idle_sleep_allowed(void);
|
|
int msm_irq_pending(void);
|
|
int clks_allow_tcxo_locked_debug(void);
|
|
extern int board_mfg_mode(void);
|
|
extern unsigned long * board_get_mfg_sleep_gpio_table(void);
|
|
extern void gpio_set_diag_gpio_table(unsigned long * dwMFG_gpio_table);
|
|
extern void wait_rmt_final_call_back(int timeout);
|
|
|
|
#ifdef CONFIG_AXI_SCREEN_POLICY
|
|
static int axi_rate;
|
|
static int sleep_axi_rate;
|
|
static struct clk *axi_clk;
|
|
#endif
|
|
static uint32_t *msm_pm_reset_vector;
|
|
|
|
static uint32_t msm_pm_max_sleep_time;
|
|
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
enum msm_pm_time_stats_id {
|
|
MSM_PM_STAT_REQUESTED_IDLE,
|
|
MSM_PM_STAT_IDLE_SPIN,
|
|
MSM_PM_STAT_IDLE_WFI,
|
|
MSM_PM_STAT_IDLE_SLEEP,
|
|
MSM_PM_STAT_IDLE_FAILED_SLEEP,
|
|
MSM_PM_STAT_NOT_IDLE,
|
|
MSM_PM_STAT_COUNT
|
|
};
|
|
|
|
static struct msm_pm_time_stats {
|
|
const char *name;
|
|
int bucket[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
|
|
int64_t min_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
|
|
int64_t max_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
|
|
int count;
|
|
int64_t total_time;
|
|
} msm_pm_stats[MSM_PM_STAT_COUNT] = {
|
|
[MSM_PM_STAT_REQUESTED_IDLE].name = "idle-request",
|
|
[MSM_PM_STAT_IDLE_SPIN].name = "idle-spin",
|
|
[MSM_PM_STAT_IDLE_WFI].name = "idle-wfi",
|
|
[MSM_PM_STAT_IDLE_SLEEP].name = "idle-sleep",
|
|
[MSM_PM_STAT_IDLE_FAILED_SLEEP].name = "idle-failed-sleep",
|
|
[MSM_PM_STAT_NOT_IDLE].name = "not-idle",
|
|
};
|
|
|
|
static void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t)
|
|
{
|
|
int i;
|
|
int64_t bt;
|
|
msm_pm_stats[id].total_time += t;
|
|
msm_pm_stats[id].count++;
|
|
bt = t;
|
|
do_div(bt, CONFIG_MSM_IDLE_STATS_FIRST_BUCKET);
|
|
if (bt < 1ULL << (CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT *
|
|
(CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1)))
|
|
i = DIV_ROUND_UP(fls((uint32_t)bt),
|
|
CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT);
|
|
else
|
|
i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
|
|
msm_pm_stats[id].bucket[i]++;
|
|
if (t < msm_pm_stats[id].min_time[i] || !msm_pm_stats[id].max_time[i])
|
|
msm_pm_stats[id].min_time[i] = t;
|
|
if (t > msm_pm_stats[id].max_time[i])
|
|
msm_pm_stats[id].max_time[i] = t;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
msm_pm_wait_state(uint32_t wait_all_set, uint32_t wait_all_clear,
|
|
uint32_t wait_any_set, uint32_t wait_any_clear)
|
|
{
|
|
int i;
|
|
uint32_t state;
|
|
|
|
for (i = 0; i < 100000; i++) {
|
|
state = smsm_get_state(PM_SMSM_READ_STATE);
|
|
if (((wait_all_set || wait_all_clear) &&
|
|
!(~state & wait_all_set) && !(state & wait_all_clear)) ||
|
|
(state & wait_any_set) || (~state & wait_any_clear))
|
|
return 0;
|
|
udelay(1);
|
|
}
|
|
pr_err("msm_pm_wait_state(%x, %x, %x, %x) failed %x\n", wait_all_set,
|
|
wait_all_clear, wait_any_set, wait_any_clear, state);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/*
|
|
* For speeding up boot time:
|
|
* During booting up, disable entering arch_idle() by disable_hlt()
|
|
* Enable it after booting up BOOT_LOCK_TIMEOUT sec.
|
|
*/
|
|
#define BOOT_LOCK_TIMEOUT (60 * HZ)
|
|
static void do_expire_boot_lock(struct work_struct *work)
|
|
{
|
|
enable_hlt();
|
|
pr_info("Release 'boot-time' halt_lock\n");
|
|
}
|
|
static DECLARE_DELAYED_WORK(work_expire_boot_lock, do_expire_boot_lock);
|
|
|
|
#ifdef CONFIG_MSM_FIQ_SUPPORT
|
|
void msm_fiq_exit_sleep(void);
|
|
#else
|
|
static inline void msm_fiq_exit_sleep(void) { }
|
|
#endif
|
|
|
|
#ifdef CONFIG_HTC_POWER_COLLAPSE_MAGIC
|
|
/* Set magic number in SMEM for power collapse state */
|
|
#define HTC_POWER_COLLAPSE_ADD (MSM_SHARED_RAM_BASE + 0x000F8000 + 0x000007F8)
|
|
#define HTC_POWER_COLLAPSE_MAGIC_NUM (HTC_POWER_COLLAPSE_ADD - 0x04)
|
|
unsigned int magic_num;
|
|
#endif
|
|
|
|
static int msm_sleep(int sleep_mode, uint32_t sleep_delay, int from_idle)
|
|
{
|
|
uint32_t saved_vector[2];
|
|
int collapsed;
|
|
void msm_irq_enter_sleep1(bool arm9_wake, int from_idle);
|
|
int msm_irq_enter_sleep2(bool arm9_wake, int from_idle);
|
|
void msm_irq_exit_sleep1(void);
|
|
void msm_irq_exit_sleep2(void);
|
|
void msm_irq_exit_sleep3(void);
|
|
void msm_gpio_enter_sleep(int from_idle);
|
|
void msm_gpio_exit_sleep(void);
|
|
void smd_sleep_exit(void);
|
|
uint32_t enter_state;
|
|
uint32_t enter_wait_set = 0;
|
|
uint32_t enter_wait_clear = 0;
|
|
uint32_t exit_state;
|
|
uint32_t exit_wait_clear = 0;
|
|
uint32_t exit_wait_any_set = 0;
|
|
unsigned long pm_saved_acpu_clk_rate = 0;
|
|
int ret;
|
|
int rv = -EINTR;
|
|
bool invalid_inital_state = false;
|
|
#if defined(CONFIG_MACH_HTCLEO)
|
|
if(!htcleo_is_nand_boot() && sleep_mode<2)
|
|
sleep_mode=2;
|
|
#endif
|
|
if (board_mfg_mode() == 4) /*power test mode*/
|
|
gpio_set_diag_gpio_table(board_get_mfg_sleep_gpio_table());
|
|
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_SUSPEND)
|
|
printk(KERN_INFO "msm_sleep(): mode %d delay %u idle %d\n",
|
|
sleep_mode, sleep_delay, from_idle);
|
|
#if defined(CONFIG_MSM_N_WAY_SMD)
|
|
switch (sleep_mode) {
|
|
case MSM_PM_SLEEP_MODE_POWER_COLLAPSE:
|
|
enter_state = DEM_SLAVE_SMSM_PWRC;
|
|
enter_wait_set = DEM_MASTER_SMSM_RSA;
|
|
exit_state = DEM_SLAVE_SMSM_WFPI;
|
|
exit_wait_any_set =
|
|
DEM_MASTER_SMSM_RUN | DEM_MASTER_SMSM_PWRC_EARLY_EXIT;
|
|
break;
|
|
case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND:
|
|
enter_state = DEM_SLAVE_SMSM_PWRC_SUSPEND;
|
|
enter_wait_set = DEM_MASTER_SMSM_RSA;
|
|
exit_state = DEM_SLAVE_SMSM_WFPI;
|
|
exit_wait_any_set =
|
|
DEM_MASTER_SMSM_RUN | DEM_MASTER_SMSM_PWRC_EARLY_EXIT;
|
|
break;
|
|
case MSM_PM_SLEEP_MODE_APPS_SLEEP:
|
|
enter_state = DEM_SLAVE_SMSM_SLEEP;
|
|
enter_wait_set = DEM_MASTER_SMSM_SLEEP;
|
|
exit_state = DEM_SLAVE_SMSM_SLEEP_EXIT;
|
|
exit_wait_any_set = DEM_MASTER_SMSM_SLEEP_EXIT;
|
|
break;
|
|
default:
|
|
enter_state = 0;
|
|
exit_state = 0;
|
|
}
|
|
#else
|
|
switch (sleep_mode) {
|
|
case MSM_PM_SLEEP_MODE_POWER_COLLAPSE:
|
|
enter_state = SMSM_PWRC;
|
|
enter_wait_set = SMSM_RSA;
|
|
exit_state = SMSM_WFPI;
|
|
exit_wait_clear = SMSM_RSA;
|
|
break;
|
|
case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND:
|
|
enter_state = SMSM_PWRC_SUSPEND;
|
|
enter_wait_set = SMSM_RSA;
|
|
exit_state = SMSM_WFPI;
|
|
exit_wait_clear = SMSM_RSA;
|
|
break;
|
|
case MSM_PM_SLEEP_MODE_APPS_SLEEP:
|
|
enter_state = SMSM_SLEEP;
|
|
exit_state = SMSM_SLEEPEXIT;
|
|
exit_wait_any_set = SMSM_SLEEPEXIT;
|
|
break;
|
|
default:
|
|
enter_state = 0;
|
|
exit_state = 0;
|
|
}
|
|
#endif
|
|
clk_enter_sleep(from_idle);
|
|
msm_irq_enter_sleep1(!!enter_state, from_idle);
|
|
msm_gpio_enter_sleep(from_idle);
|
|
|
|
if (enter_state) {
|
|
/* Make sure last sleep request did not end with a timeout */
|
|
ret = msm_pm_wait_state(PM_SMSM_READ_RUN, 0, 0, 0);
|
|
if (ret) {
|
|
printk(KERN_ERR "msm_sleep(): invalid inital state\n");
|
|
invalid_inital_state = true;
|
|
}
|
|
|
|
if (sleep_delay == 0 && sleep_mode >= MSM_PM_SLEEP_MODE_APPS_SLEEP)
|
|
sleep_delay = 192000*5; /* APPS_SLEEP does not allow infinite timeout */
|
|
ret = smsm_set_sleep_duration(sleep_delay);
|
|
if (ret) {
|
|
printk(KERN_ERR "msm_sleep(): smsm_set_sleep_duration %x failed\n", enter_state);
|
|
enter_state = 0;
|
|
exit_state = 0;
|
|
}
|
|
if ((!from_idle && msm_pm_debug_mask & MSM_PM_DEBUG_SLEEP_LIMIT) ||
|
|
(from_idle && msm_pm_debug_mask & MSM_PM_DEBUG_IDLE))
|
|
clks_allow_tcxo_locked_debug();
|
|
|
|
ret = smsm_change_state(PM_SMSM_WRITE_STATE, PM_SMSM_WRITE_RUN, enter_state);
|
|
if (ret) {
|
|
printk(KERN_ERR "msm_sleep(): smsm_change_state %x failed\n", enter_state);
|
|
enter_state = 0;
|
|
exit_state = 0;
|
|
}
|
|
ret = msm_pm_wait_state(enter_wait_set, enter_wait_clear, 0, 0);
|
|
if (ret || invalid_inital_state) {
|
|
printk(KERN_INFO "msm_sleep(): msm_pm_wait_state failed, %x\n", smsm_get_state(PM_SMSM_READ_STATE));
|
|
goto enter_failed;
|
|
}
|
|
}
|
|
if (msm_irq_enter_sleep2(!!enter_state, from_idle))
|
|
goto enter_failed;
|
|
|
|
if (enter_state) {
|
|
#if defined (CONFIG_ARCH_MSM7X30)
|
|
writel(1, A11S_PWRDOWN);
|
|
writel(4, A11S_SECOP);
|
|
#else
|
|
writel(0x1f, A11S_CLK_SLEEP_EN);
|
|
writel(1, A11S_PWRDOWN);
|
|
|
|
writel(0, A11S_STANDBY_CTL);
|
|
#endif
|
|
#if !defined(CONFIG_MSM_N_WAY_SMD)
|
|
writel(0, A11RAMBACKBIAS);
|
|
#endif
|
|
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE)
|
|
printk(KERN_INFO "msm_sleep(): enter "
|
|
"A11S_CLK_SLEEP_EN %x, A11S_PWRDOWN %x, "
|
|
"smsm_get_state %x\n", readl(A11S_CLK_SLEEP_EN),
|
|
readl(A11S_PWRDOWN), smsm_get_state(PM_SMSM_READ_STATE));
|
|
}
|
|
|
|
if (sleep_mode <= MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT) {
|
|
pm_saved_acpu_clk_rate = acpuclk_power_collapse(from_idle);
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK)
|
|
printk(KERN_INFO "msm_sleep(): %ld enter power collapse"
|
|
"\n", pm_saved_acpu_clk_rate);
|
|
if (pm_saved_acpu_clk_rate == 0)
|
|
goto ramp_down_failed;
|
|
|
|
#ifdef CONFIG_AXI_SCREEN_POLICY
|
|
/* Drop AXI request when the screen is on */
|
|
if (axi_rate)
|
|
clk_set_rate(axi_clk, sleep_axi_rate);
|
|
#endif
|
|
}
|
|
#ifdef CONFIG_HTC_POWER_COLLAPSE_MAGIC
|
|
magic_num = 0xAAAA1111;
|
|
writel(magic_num, HTC_POWER_COLLAPSE_MAGIC_NUM);
|
|
#endif
|
|
if (sleep_mode < MSM_PM_SLEEP_MODE_APPS_SLEEP) {
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE)
|
|
smsm_print_sleep_info(0);
|
|
saved_vector[0] = msm_pm_reset_vector[0];
|
|
saved_vector[1] = msm_pm_reset_vector[1];
|
|
msm_pm_reset_vector[0] = 0xE51FF004; /* ldr pc, 4 */
|
|
msm_pm_reset_vector[1] = virt_to_phys(msm_pm_collapse_exit);
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_RESET_VECTOR)
|
|
printk(KERN_INFO "msm_sleep(): vector %x %x -> "
|
|
"%x %x\n", saved_vector[0], saved_vector[1],
|
|
msm_pm_reset_vector[0], msm_pm_reset_vector[1]);
|
|
#ifdef CONFIG_VFP
|
|
if (from_idle)
|
|
vfp_flush_context();
|
|
#endif
|
|
|
|
#ifdef CONFIG_CACHE_L2X0
|
|
l2x0_suspend();
|
|
#endif
|
|
if (!from_idle) printk(KERN_INFO "[R] suspend end\n");
|
|
/* reset idle sleep mode when suspend. */
|
|
if (!from_idle) msm_pm_idle_sleep_mode = CONFIG_MSM7X00A_IDLE_SLEEP_MODE;
|
|
collapsed = msm_pm_collapse();
|
|
if (!from_idle) printk(KERN_INFO "[R] resume start\n");
|
|
#ifdef CONFIG_CACHE_L2X0
|
|
l2x0_resume(collapsed);
|
|
#endif
|
|
msm_pm_reset_vector[0] = saved_vector[0];
|
|
msm_pm_reset_vector[1] = saved_vector[1];
|
|
if (collapsed) {
|
|
#ifdef CONFIG_VFP
|
|
if (from_idle)
|
|
vfp_reinit();
|
|
#endif
|
|
cpu_init();
|
|
__asm__("cpsie a");
|
|
msm_fiq_exit_sleep();
|
|
local_fiq_enable();
|
|
rv = 0;
|
|
}
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_POWER_COLLAPSE)
|
|
printk(KERN_INFO "msm_pm_collapse(): returned %d\n",
|
|
collapsed);
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE)
|
|
smsm_print_sleep_info(0);
|
|
} else {
|
|
msm_arch_idle();
|
|
rv = 0;
|
|
}
|
|
#ifdef CONFIG_HTC_POWER_COLLAPSE_MAGIC
|
|
magic_num = 0xBBBB9999;
|
|
writel(magic_num, HTC_POWER_COLLAPSE_MAGIC_NUM);
|
|
#endif
|
|
if (sleep_mode <= MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT) {
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK)
|
|
printk(KERN_INFO "msm_sleep(): exit power collapse %ld"
|
|
"\n", pm_saved_acpu_clk_rate);
|
|
#if defined(CONFIG_ARCH_QSD8X50)
|
|
if (acpuclk_set_rate(pm_saved_acpu_clk_rate, 1) < 0)
|
|
#else
|
|
if (acpuclk_set_rate(pm_saved_acpu_clk_rate,
|
|
from_idle ? SETRATE_PC_IDLE : SETRATE_PC) < 0)
|
|
#endif
|
|
printk(KERN_ERR "msm_sleep(): clk_set_rate %ld "
|
|
"failed\n", pm_saved_acpu_clk_rate);
|
|
|
|
#ifdef CONFIG_AXI_SCREEN_POLICY
|
|
/* Restore axi rate if needed */
|
|
if (axi_rate)
|
|
clk_set_rate(axi_clk, axi_rate);
|
|
#endif
|
|
}
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE)
|
|
printk(KERN_INFO "msm_sleep(): exit A11S_CLK_SLEEP_EN %x, "
|
|
"A11S_PWRDOWN %x, smsm_get_state %x\n",
|
|
readl(A11S_CLK_SLEEP_EN), readl(A11S_PWRDOWN),
|
|
smsm_get_state(PM_SMSM_READ_STATE));
|
|
ramp_down_failed:
|
|
msm_irq_exit_sleep1();
|
|
enter_failed:
|
|
if (enter_state) {
|
|
#if defined (CONFIG_ARCH_MSM7X30)
|
|
writel(0, A11S_SECOP);
|
|
writel(0, A11S_PWRDOWN);
|
|
msm_spm_reinit();
|
|
#else
|
|
writel(0x00, A11S_CLK_SLEEP_EN);
|
|
writel(0, A11S_PWRDOWN);
|
|
#endif
|
|
smsm_change_state(PM_SMSM_WRITE_STATE, enter_state, exit_state);
|
|
msm_pm_wait_state(0, exit_wait_clear, exit_wait_any_set, 0);
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE)
|
|
printk(KERN_INFO "msm_sleep(): sleep exit "
|
|
"A11S_CLK_SLEEP_EN %x, A11S_PWRDOWN %x, "
|
|
"smsm_get_state %x\n", readl(A11S_CLK_SLEEP_EN),
|
|
readl(A11S_PWRDOWN), smsm_get_state(PM_SMSM_READ_STATE));
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE)
|
|
smsm_print_sleep_info(0);
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_WAKEUP_REASON && !from_idle)
|
|
smsm_print_sleep_info(1);
|
|
}
|
|
msm_irq_exit_sleep2();
|
|
if (enter_state) {
|
|
smsm_change_state(PM_SMSM_WRITE_STATE, exit_state, PM_SMSM_WRITE_RUN);
|
|
msm_pm_wait_state(PM_SMSM_READ_RUN, 0, 0, 0);
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE)
|
|
printk(KERN_INFO "msm_sleep(): sleep exit "
|
|
"A11S_CLK_SLEEP_EN %x, A11S_PWRDOWN %x, "
|
|
"smsm_get_state %x\n", readl(A11S_CLK_SLEEP_EN),
|
|
readl(A11S_PWRDOWN), smsm_get_state(PM_SMSM_READ_STATE));
|
|
}
|
|
msm_irq_exit_sleep3();
|
|
msm_gpio_exit_sleep();
|
|
smd_sleep_exit();
|
|
clk_exit_sleep();
|
|
return rv;
|
|
}
|
|
|
|
static int msm_pm_idle_spin(void)
|
|
{
|
|
int spin;
|
|
spin = msm_pm_idle_spin_time >> 10;
|
|
while (spin-- > 0) {
|
|
if (msm_irq_pending()) {
|
|
return -1;
|
|
}
|
|
udelay(1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void arch_idle(void)
|
|
{
|
|
int ret;
|
|
int64_t sleep_time;
|
|
int low_power = 0;
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
int64_t t1;
|
|
static int64_t t2;
|
|
int exit_stat;
|
|
#endif
|
|
int allow_sleep =
|
|
msm_pm_idle_sleep_mode < MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT &&
|
|
#ifdef CONFIG_HAS_WAKELOCK
|
|
!has_wake_lock(WAKE_LOCK_IDLE) &&
|
|
#endif
|
|
msm_irq_idle_sleep_allowed();
|
|
if (msm_pm_reset_vector == NULL)
|
|
return;
|
|
|
|
sleep_time = msm_timer_enter_idle();
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
t1 = ktime_to_ns(ktime_get());
|
|
msm_pm_add_stat(MSM_PM_STAT_NOT_IDLE, t1 - t2);
|
|
msm_pm_add_stat(MSM_PM_STAT_REQUESTED_IDLE, sleep_time);
|
|
#endif
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_IDLE)
|
|
printk(KERN_INFO "arch_idle: sleep time %llu, allow_sleep %d\n",
|
|
sleep_time, allow_sleep);
|
|
if (sleep_time < msm_pm_idle_sleep_min_time || !allow_sleep) {
|
|
unsigned long saved_rate;
|
|
/* only spin while trying wfi ramp down */
|
|
if (acpuclk_get_wfi_rate() && msm_pm_idle_spin() < 0) {
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
exit_stat = MSM_PM_STAT_IDLE_SPIN;
|
|
#endif
|
|
goto abort_idle;
|
|
}
|
|
saved_rate = acpuclk_wait_for_irq();
|
|
|
|
|
|
if (saved_rate && msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK)
|
|
printk(KERN_DEBUG "arch_idle: clk %ld -> swfi\n",
|
|
saved_rate);
|
|
|
|
/*
|
|
* If there is a wfi speed specified and we failed to ramp, do not
|
|
* go into wfi.
|
|
*/
|
|
if (acpuclk_get_wfi_rate() && !saved_rate)
|
|
while (!msm_irq_pending())
|
|
udelay(1);
|
|
else
|
|
msm_arch_idle();
|
|
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK)
|
|
printk(KERN_DEBUG "msm_sleep: clk swfi -> %ld\n",
|
|
saved_rate);
|
|
#if defined(CONFIG_ARCH_QSD8X50)
|
|
if (saved_rate && acpuclk_set_rate(saved_rate, 1) < 0)
|
|
#else
|
|
if (saved_rate
|
|
&& acpuclk_set_rate(saved_rate, SETRATE_SWFI) < 0)
|
|
#endif
|
|
printk(KERN_ERR "msm_sleep(): clk_set_rate %ld "
|
|
"failed\n", saved_rate);
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
exit_stat = MSM_PM_STAT_IDLE_WFI;
|
|
#endif
|
|
} else {
|
|
if (msm_pm_idle_spin() < 0) {
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
exit_stat = MSM_PM_STAT_IDLE_SPIN;
|
|
#endif
|
|
goto abort_idle;
|
|
}
|
|
|
|
low_power = 1;
|
|
do_div(sleep_time, NSEC_PER_SEC / 32768);
|
|
if (sleep_time > 0x6DDD000) {
|
|
printk("sleep_time too big %lld\n", sleep_time);
|
|
sleep_time = 0x6DDD000;
|
|
}
|
|
ret = msm_sleep(msm_pm_idle_sleep_mode, sleep_time, 1);
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
if (ret)
|
|
exit_stat = MSM_PM_STAT_IDLE_FAILED_SLEEP;
|
|
else
|
|
exit_stat = MSM_PM_STAT_IDLE_SLEEP;
|
|
#endif
|
|
}
|
|
abort_idle:
|
|
msm_timer_exit_idle(low_power);
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
t2 = ktime_to_ns(ktime_get());
|
|
msm_pm_add_stat(exit_stat, t2 - t1);
|
|
#endif
|
|
}
|
|
|
|
static int msm_pm_enter(suspend_state_t state)
|
|
{
|
|
msm_sleep(msm_pm_sleep_mode, msm_pm_max_sleep_time, 0);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_suspend_ops msm_pm_ops = {
|
|
.enter = msm_pm_enter,
|
|
.valid = suspend_valid_only_mem,
|
|
};
|
|
|
|
static uint32_t restart_reason = 0x776655AA;
|
|
|
|
static int msm_wakeup_after; /* default, no wakeup by alarm */
|
|
static int msm_power_wakeup_after(const char *val, struct kernel_param *kp)
|
|
{
|
|
int ret;
|
|
//struct uart_port *port;
|
|
//struct msm_port *msm_port;
|
|
|
|
ret = param_set_int(val, kp);
|
|
printk(KERN_INFO "+msm_power_wakeup_after, ret=%d\r\n", ret);
|
|
|
|
if (!ret) {
|
|
printk(KERN_INFO "msm_wakeup_after=%d\r\n", msm_wakeup_after);
|
|
if (msm_wakeup_after < 0)
|
|
msm_wakeup_after = 0; /* invalid, no wakeup */
|
|
}
|
|
printk(KERN_INFO "-msm_power_wakeup_after, msm_wakeup_after=%d\r\n", msm_wakeup_after);
|
|
return ret;
|
|
}
|
|
|
|
module_param_call(wakeup_after, msm_power_wakeup_after, param_get_int,
|
|
&msm_wakeup_after, S_IWUSR | S_IRUGO);
|
|
|
|
static void msm_pm_power_off(void)
|
|
{
|
|
printk(KERN_INFO "msm_pm_power_off:wakeup after %d\r\n", msm_wakeup_after);
|
|
|
|
if (msm_wakeup_after)
|
|
msm_proc_comm(PCOM_SET_RTC_ALARM, &msm_wakeup_after, 0);
|
|
|
|
pmic_glb_power_down();
|
|
|
|
#ifdef CONFIG_MSM_RMT_STORAGE_SERVER
|
|
printk(KERN_INFO "from %s\r\n", __func__);
|
|
wait_rmt_final_call_back(10);
|
|
printk(KERN_INFO "back %s\r\n", __func__);
|
|
#endif
|
|
for (;;) ;
|
|
}
|
|
|
|
static bool console_flushed;
|
|
|
|
void msm_pm_flush_console(void)
|
|
{
|
|
if (console_flushed)
|
|
return;
|
|
console_flushed = true;
|
|
|
|
printk("\n");
|
|
printk(KERN_EMERG "Restarting %s\n", linux_banner);
|
|
if (!try_acquire_console_sem()) {
|
|
release_console_sem();
|
|
return;
|
|
}
|
|
|
|
mdelay(50);
|
|
|
|
local_irq_disable();
|
|
if (try_acquire_console_sem())
|
|
printk(KERN_EMERG "msm_restart: Console was locked! Busting\n");
|
|
else
|
|
printk(KERN_EMERG "msm_restart: Console was locked!\n");
|
|
release_console_sem();
|
|
}
|
|
|
|
#if defined(CONFIG_MACH_HTCLEO)
|
|
static void htcleo_save_reset_reason(void)
|
|
{
|
|
/* save restart_reason to be accesible in bootloader @ ramconsole - 0x1000*/
|
|
uint32_t *bootloader_reset_reason = ioremap(0x2FFB0000, PAGE_SIZE);
|
|
if(bootloader_reset_reason!=NULL)
|
|
{
|
|
printk(KERN_INFO "msm_restart saving reason %x @ 0x2FFB0000 \n", restart_reason);
|
|
bootloader_reset_reason[0]=restart_reason;
|
|
bootloader_reset_reason[1]=restart_reason^0x004b4c63; //XOR with cLK signature so we know is not trash
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void msm_pm_restart(char str, const char *cmd)
|
|
{
|
|
msm_pm_flush_console();
|
|
|
|
#if defined(CONFIG_MACH_HTCLEO)
|
|
htcleo_save_reset_reason();
|
|
#endif
|
|
|
|
/* always reboot device through proc comm */
|
|
if (restart_reason == 0x6f656d99)
|
|
msm_proc_comm(PCOM_RESET_CHIP_IMM, &restart_reason, 0);
|
|
else
|
|
msm_proc_comm(PCOM_RESET_CHIP, &restart_reason, 0);
|
|
|
|
#ifdef CONFIG_MSM_RMT_STORAGE_SERVER
|
|
printk(KERN_INFO "from %s\r\n", __func__);
|
|
wait_rmt_final_call_back(10);
|
|
printk(KERN_INFO "back %s\r\n", __func__);
|
|
/* wait 2 seconds to let radio reset device after the final EFS sync*/
|
|
mdelay(2000);
|
|
#else
|
|
/* In case Radio is dead, reset device after notify Radio 5 seconds */
|
|
mdelay(5000);
|
|
#endif
|
|
|
|
/* hard reboot if possible */
|
|
if (msm_hw_reset_hook) {
|
|
printk(KERN_INFO "%s : Do HW_RESET by APP not by RADIO\r\n", __func__);
|
|
msm_hw_reset_hook();
|
|
}
|
|
|
|
for (;;) ;
|
|
}
|
|
|
|
static int msm_reboot_call(struct notifier_block *this, unsigned long code, void *_cmd)
|
|
{
|
|
if((code == SYS_RESTART) && _cmd) {
|
|
char *cmd = _cmd;
|
|
if (!strcmp(cmd, "bootloader")) {
|
|
restart_reason = 0x77665500;
|
|
} else if (!strcmp(cmd, "recovery")) {
|
|
restart_reason = 0x77665502;
|
|
} else if (!strcmp(cmd, "eraseflash")) {
|
|
restart_reason = 0x776655EF;
|
|
} else if (!strncmp(cmd, "oem-", 4)) {
|
|
unsigned code = simple_strtoul(cmd + 4, 0, 16) & 0xff;
|
|
restart_reason = 0x6f656d00 | code;
|
|
} else if (!strcmp(cmd, "force-hard")) {
|
|
restart_reason = 0x776655AA;
|
|
} else {
|
|
restart_reason = 0x77665501;
|
|
}
|
|
}
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block msm_reboot_notifier =
|
|
{
|
|
.notifier_call = msm_reboot_call,
|
|
};
|
|
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
static int msm_pm_read_proc(char *page, char **start, off_t off,
|
|
int count, int *eof, void *data)
|
|
{
|
|
int len = 0;
|
|
int i, j;
|
|
char *p = page;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(msm_pm_stats); i++) {
|
|
int64_t bucket_time;
|
|
int64_t s;
|
|
uint32_t ns;
|
|
s = msm_pm_stats[i].total_time;
|
|
ns = do_div(s, NSEC_PER_SEC);
|
|
p += sprintf(p,
|
|
"%s:\n"
|
|
" count: %7d\n"
|
|
" total_time: %lld.%09u\n",
|
|
msm_pm_stats[i].name,
|
|
msm_pm_stats[i].count,
|
|
s, ns);
|
|
bucket_time = CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
|
|
for (j = 0; j < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; j++) {
|
|
s = bucket_time;
|
|
ns = do_div(s, NSEC_PER_SEC);
|
|
p += sprintf(p, " <%2lld.%09u: %7d (%lld-%lld)\n",
|
|
s, ns, msm_pm_stats[i].bucket[j],
|
|
msm_pm_stats[i].min_time[j],
|
|
msm_pm_stats[i].max_time[j]);
|
|
bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT;
|
|
}
|
|
p += sprintf(p, " >=%2lld.%09u: %7d (%lld-%lld)\n",
|
|
s, ns, msm_pm_stats[i].bucket[j],
|
|
msm_pm_stats[i].min_time[j],
|
|
msm_pm_stats[i].max_time[j]);
|
|
}
|
|
*start = page + off;
|
|
|
|
len = p - page;
|
|
if (len > off)
|
|
len -= off;
|
|
else
|
|
len = 0;
|
|
|
|
return len < count ? len : count;
|
|
}
|
|
#endif
|
|
|
|
void msm_pm_set_max_sleep_time(int64_t max_sleep_time_ns)
|
|
{
|
|
int64_t max_sleep_time_bs = max_sleep_time_ns;
|
|
|
|
/* Convert from ns -> BS units */
|
|
do_div(max_sleep_time_bs, NSEC_PER_SEC / 32768);
|
|
|
|
if (max_sleep_time_bs > 0x6DDD000)
|
|
msm_pm_max_sleep_time = (uint32_t) 0x6DDD000;
|
|
else
|
|
msm_pm_max_sleep_time = (uint32_t) max_sleep_time_bs;
|
|
|
|
if (msm_pm_debug_mask & MSM_PM_DEBUG_SUSPEND)
|
|
printk("%s: Requested %lldns (%lldbs), Giving %ubs\n",
|
|
__func__, max_sleep_time_ns,
|
|
max_sleep_time_bs,
|
|
msm_pm_max_sleep_time);
|
|
}
|
|
EXPORT_SYMBOL(msm_pm_set_max_sleep_time);
|
|
|
|
#ifdef CONFIG_EARLYSUSPEND
|
|
#ifdef CONFIG_AXI_SCREEN_POLICY
|
|
/* axi 128 screen on, 61mhz screen off */
|
|
static void axi_early_suspend(struct early_suspend *handler)
|
|
{
|
|
axi_rate = 0;
|
|
clk_set_rate(axi_clk, axi_rate);
|
|
}
|
|
|
|
static void axi_late_resume(struct early_suspend *handler)
|
|
{
|
|
axi_rate = 128000000;
|
|
sleep_axi_rate = 117000000;
|
|
clk_set_rate(axi_clk, axi_rate);
|
|
}
|
|
|
|
static struct early_suspend axi_screen_suspend = {
|
|
.suspend = axi_early_suspend,
|
|
.resume = axi_late_resume,
|
|
};
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef CONFIG_AXI_SCREEN_POLICY
|
|
static void __init msm_pm_axi_init(void)
|
|
{
|
|
#ifdef CONFIG_EARLYSUSPEND
|
|
axi_clk = clk_get(NULL, "ebi1_clk");
|
|
if (IS_ERR(axi_clk)) {
|
|
int result = PTR_ERR(axi_clk);
|
|
pr_err("clk_get(ebi1_clk) returned %d\n", result);
|
|
return;
|
|
}
|
|
axi_rate = 128000000;
|
|
sleep_axi_rate = 117000000;
|
|
clk_set_rate(axi_clk, axi_rate);
|
|
register_early_suspend(&axi_screen_suspend);
|
|
#else
|
|
axi_rate = 0;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
static int __init msm_pm_init(void)
|
|
{
|
|
pm_power_off = msm_pm_power_off;
|
|
arm_pm_restart = msm_pm_restart;
|
|
msm_pm_max_sleep_time = 0;
|
|
#ifdef CONFIG_AXI_SCREEN_POLICY
|
|
msm_pm_axi_init();
|
|
#endif
|
|
register_reboot_notifier(&msm_reboot_notifier);
|
|
|
|
msm_pm_reset_vector = ioremap(0x0, PAGE_SIZE);
|
|
|
|
#if defined(CONFIG_MACH_HTCLEO)
|
|
// if cLK is bootloader 0x0 is protected and not writtable but cLK changed reset vecotr to jump at address stored at 0x11800004
|
|
if(htcleo_is_nand_boot()==2){
|
|
pr_info("msm_pm: 0x00000000: %x\n", msm_pm_reset_vector[0]);
|
|
pr_info("msm_pm: 0x00000004: %x\n", msm_pm_reset_vector[1]);
|
|
msm_pm_reset_vector = ioremap(0x11800000, PAGE_SIZE);
|
|
}
|
|
#endif
|
|
|
|
if (msm_pm_reset_vector == NULL) {
|
|
printk(KERN_ERR "msm_pm_init: failed to map reset vector\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
suspend_set_ops(&msm_pm_ops);
|
|
|
|
#ifdef CONFIG_MSM_IDLE_STATS
|
|
create_proc_read_entry("msm_pm_stats", S_IRUGO,
|
|
NULL, msm_pm_read_proc, NULL);
|
|
#endif
|
|
|
|
if ((board_mfg_mode() == 0) || (board_mfg_mode() == 1)) {
|
|
disable_hlt();
|
|
schedule_delayed_work(&work_expire_boot_lock, BOOT_LOCK_TIMEOUT);
|
|
pr_info("Acquire 'boot-time' halt_lock\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
__initcall(msm_pm_init);
|