diff options
Diffstat (limited to 'arch/arm/mach-msm/pm.c')
-rw-r--r-- | arch/arm/mach-msm/pm.c | 954 |
1 files changed, 954 insertions, 0 deletions
diff --git a/arch/arm/mach-msm/pm.c b/arch/arm/mach-msm/pm.c new file mode 100644 index 000000000000..d5d26cc021ed --- /dev/null +++ b/arch/arm/mach-msm/pm.c @@ -0,0 +1,954 @@ +/* arch/arm/mach-msm/pm.c + * + * MSM Power Management Routines + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * 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/delay.h> +#include <linux/init.h> +#include <linux/pm.h> +#include <linux/pm_qos_params.h> +#include <linux/proc_fs.h> +#include <linux/suspend.h> +#include <linux/reboot.h> +#include <linux/uaccess.h> +#include <mach/msm_iomap.h> +#include <mach/system.h> +#include <asm/io.h> + +#ifdef CONFIG_HAS_WAKELOCK +#include <linux/wakelock.h> +#endif + +#include "smd_private.h" +#include "acpuclock.h" +#include "clock.h" +#include "proc_comm.h" +#include "idle.h" +#include "irq.h" +#include "gpio.h" +#include "timer.h" +#include "pm.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, +}; +static int msm_pm_debug_mask; +module_param_named(debug_mask, msm_pm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#ifdef CONFIG_MSM_SLEEP_TIME_OVERRIDE +static int msm_pm_sleep_time_override; +module_param_named(sleep_time_override, + msm_pm_sleep_time_override, int, S_IRUGO | S_IWUSR | S_IWGRP); +#endif + +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); + +#define A11S_CLK_SLEEP_EN (MSM_CSR_BASE + 0x11c) +#define A11S_PWRDOWN (MSM_CSR_BASE + 0x440) +#define A11S_STANDBY_CTL (MSM_CSR_BASE + 0x108) +#define A11RAMBACKBIAS (MSM_CSR_BASE + 0x508) + +enum { + SLEEP_LIMIT_NONE = 0, + SLEEP_LIMIT_NO_TCXO_SHUTDOWN = 2 +}; + +static atomic_t msm_pm_init_done = ATOMIC_INIT(0); +struct smsm_interrupt_info_ext { + uint32_t aArm_en_mask; + uint32_t aArm_interrupts_pending; + uint32_t aArm_wakeup_reason; + uint32_t aArm_rpc_prog; + uint32_t aArm_rpc_proc; + char aArm_smd_port_name[20]; + uint32_t aArm_gpio_info; +}; +static struct msm_pm_smem_addr_t { + uint32_t *sleep_delay; + uint32_t *limit_sleep; + struct smsm_interrupt_info *int_info; + struct smsm_interrupt_info_ext *int_info_ext; +} msm_pm_sma; + +static uint32_t *msm_pm_reset_vector; +static uint32_t msm_pm_max_sleep_time; +static struct msm_pm_platform_data *msm_pm_modes; + +#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_IDLE_POWER_COLLAPSE, + MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE, + MSM_PM_STAT_SUSPEND, + MSM_PM_STAT_FAILED_SUSPEND, + MSM_PM_STAT_NOT_IDLE, + MSM_PM_STAT_COUNT +}; + +static struct msm_pm_time_stats { + const char *name; + int64_t first_bucket_time; + 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_REQUESTED_IDLE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_SPIN].name = "idle-spin", + [MSM_PM_STAT_IDLE_SPIN].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_WFI].name = "idle-wfi", + [MSM_PM_STAT_IDLE_WFI].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_SLEEP].name = "idle-sleep", + [MSM_PM_STAT_IDLE_SLEEP].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_FAILED_SLEEP].name = "idle-failed-sleep", + [MSM_PM_STAT_IDLE_FAILED_SLEEP].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_POWER_COLLAPSE].name = "idle-power-collapse", + [MSM_PM_STAT_IDLE_POWER_COLLAPSE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].name = + "idle-failed-power-collapse", + [MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_SUSPEND].name = "suspend", + [MSM_PM_STAT_SUSPEND].first_bucket_time = + CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_FAILED_SUSPEND].name = "failed-suspend", + [MSM_PM_STAT_FAILED_SUSPEND].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_NOT_IDLE].name = "not-idle", + [MSM_PM_STAT_NOT_IDLE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, +}; + +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, msm_pm_stats[id].first_bucket_time); + 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; +} + +static uint32_t msm_pm_sleep_limit = SLEEP_LIMIT_NONE; +static DECLARE_BITMAP(msm_pm_clocks_no_tcxo_shutdown, NR_CLKS); +#endif + +static int +msm_pm_wait_state(uint32_t wait_state_all_set, uint32_t wait_state_all_clear, + uint32_t wait_state_any_set, uint32_t wait_state_any_clear) +{ + int i; + uint32_t state; + + for (i = 0; i < 2000000; i++) { + state = smsm_get_state(SMSM_MODEM_STATE); + if (((state & wait_state_all_set) == wait_state_all_set) && + ((~state & wait_state_all_clear) == wait_state_all_clear) && + (wait_state_any_set == 0 || (state & wait_state_any_set) || + wait_state_any_clear == 0 || (state & wait_state_any_clear))) + return 0; + } + printk(KERN_ERR "msm_pm_wait_state(%x, %x, %x, %x) failed %x\n", + wait_state_all_set, wait_state_all_clear, + wait_state_any_set, wait_state_any_clear, state); + return -ETIMEDOUT; +} + +/* + * Respond to timing out waiting for Modem + * + * NOTE: The function never returns. + */ +static void msm_pm_timeout(void) +{ +#if defined(CONFIG_MSM_PM_TIMEOUT_RESET_CHIP) + printk(KERN_EMERG "%s(): resetting chip\n", __func__); + msm_proc_comm(PCOM_RESET_CHIP_IMM, NULL, NULL); +#elif defined(CONFIG_MSM_PM_TIMEOUT_RESET_MODEM) + printk(KERN_EMERG "%s(): resetting modem\n", __func__); + msm_proc_comm_reset_modem_now(); +#elif defined(CONFIG_MSM_PM_TIMEOUT_HALT) + printk(KERN_EMERG "%s(): halting\n", __func__); +#endif + for (;;) + ; +} + +static int msm_sleep(int sleep_mode, uint32_t sleep_delay, + uint32_t sleep_limit, int from_idle) +{ + uint32_t saved_vector[2]; + int collapsed; + 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_set = 0; + unsigned long pm_saved_acpu_clk_rate = 0; + int ret; + int rv = -EINTR; + + if (msm_pm_debug_mask & MSM_PM_DEBUG_SUSPEND) + printk(KERN_INFO "msm_sleep(): " + "mode %d delay %u limit %u idle %d\n", + sleep_mode, sleep_delay, sleep_limit, from_idle); + + 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_set = SMSM_SLEEPEXIT; + break; + default: + enter_state = 0; + exit_state = 0; + } + + if (enter_state && !(smsm_get_state(SMSM_MODEM_STATE) & SMSM_RUN)) { + if ((MSM_PM_DEBUG_POWER_COLLAPSE | MSM_PM_DEBUG_SUSPEND) & + msm_pm_debug_mask) + printk(KERN_INFO "msm_sleep(): modem not ready\n"); + rv = -EBUSY; + goto check_failed; + } + + memset(msm_pm_sma.int_info, 0, sizeof(*msm_pm_sma.int_info)); + msm_irq_enter_sleep1(!!enter_state, from_idle, + &msm_pm_sma.int_info->aArm_en_mask); + msm_gpio_enter_sleep(from_idle); + + if (enter_state) { + if (sleep_delay == 0 && sleep_mode >= MSM_PM_SLEEP_MODE_APPS_SLEEP) + sleep_delay = 192000*5; /* APPS_SLEEP does not allow infinite timeout */ + + *msm_pm_sma.sleep_delay = sleep_delay; + *msm_pm_sma.limit_sleep = sleep_limit; + ret = smsm_change_state(SMSM_APPS_STATE, SMSM_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) { + printk(KERN_EMERG "msm_sleep(): power collapse entry " + "timed out waiting for Modem's response\n"); + msm_pm_timeout(); + } + } + if (msm_irq_enter_sleep2(!!enter_state, from_idle)) + goto enter_failed; + + if (enter_state) { + writel(0x1f, A11S_CLK_SLEEP_EN); + writel(1, A11S_PWRDOWN); + + writel(0, A11S_STANDBY_CTL); + writel(0, A11RAMBACKBIAS); + + 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(SMSM_MODEM_STATE)); + } + + if (sleep_mode <= MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT) { + pm_saved_acpu_clk_rate = acpuclk_power_collapse(); + 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; + } + if (sleep_mode < MSM_PM_SLEEP_MODE_APPS_SLEEP) { +#ifdef CONFIG_MSM_ADM_OFF_AT_POWER_COLLAPSE + /* XXX: Temp workaround that needs to be removed soon. The + * right fix will probably involve the DMA driver taking + * ownership of the ADM clock. */ + /* id is set to denote ADM clock. */ + unsigned id = 1; + msm_proc_comm(PCOM_CLKCTL_RPC_DISABLE, &id, NULL); +#endif + if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE) + smsm_print_sleep_info(*msm_pm_sma.sleep_delay, + *msm_pm_sma.limit_sleep, + msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); + 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]); + collapsed = msm_pm_collapse(); + msm_pm_reset_vector[0] = saved_vector[0]; + msm_pm_reset_vector[1] = saved_vector[1]; + if (collapsed) { + cpu_init(); + 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(*msm_pm_sma.sleep_delay, + *msm_pm_sma.limit_sleep, + msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); +#ifdef CONFIG_MSM_ADM_OFF_AT_POWER_COLLAPSE + /* id is set to denote ADM clock. */ + id = 1; + if (msm_proc_comm(PCOM_CLKCTL_RPC_ENABLE, &id, NULL) < 0 || + id < 0) + printk(KERN_ERR + "msm_sleep(): failed to turn on ADM clock\n"); +#endif + } else { + msm_arch_idle(); + rv = 0; + } + + 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 (acpuclk_set_rate(pm_saved_acpu_clk_rate, SETRATE_PC) < 0) + printk(KERN_ERR "msm_sleep(): clk_set_rate %ld " + "failed\n", pm_saved_acpu_clk_rate); + } + 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(SMSM_MODEM_STATE)); +ramp_down_failed: + msm_irq_exit_sleep1(msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); +enter_failed: + if (enter_state) { + writel(0x00, A11S_CLK_SLEEP_EN); + writel(0, A11S_PWRDOWN); + smsm_change_state(SMSM_APPS_STATE, enter_state, exit_state); + if (msm_pm_wait_state(exit_wait_set, exit_wait_clear, 0, 0)) { + printk(KERN_EMERG "msm_sleep(): power collapse exit " + "timed out waiting for Modem's response\n"); + msm_pm_timeout(); + } + 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(SMSM_MODEM_STATE)); + if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE) + smsm_print_sleep_info(*msm_pm_sma.sleep_delay, + *msm_pm_sma.limit_sleep, + msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); + } + msm_irq_exit_sleep2(msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); + if (enter_state) { + smsm_change_state(SMSM_APPS_STATE, exit_state, SMSM_RUN); + 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(SMSM_MODEM_STATE)); + } + msm_irq_exit_sleep3(msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); + msm_gpio_exit_sleep(); + smd_sleep_exit(); + +check_failed: + return rv; +} + +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(KERN_INFO "%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); + +void arch_idle(void) +{ + int ret; + int spin; + int64_t sleep_time; + int low_power = 0; + struct msm_pm_platform_data *mode; +#ifdef CONFIG_MSM_IDLE_STATS + DECLARE_BITMAP(clk_ids, NR_CLKS); + int64_t t1; + static int64_t t2; + int exit_stat; +#endif + int latency_qos = pm_qos_requirement(PM_QOS_CPU_DMA_LATENCY); + uint32_t sleep_limit = SLEEP_LIMIT_NONE; + 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 (!atomic_read(&msm_pm_init_done)) + 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 + + mode = &msm_pm_modes[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]; + if (mode->latency >= latency_qos) + sleep_limit = SLEEP_LIMIT_NO_TCXO_SHUTDOWN; + + mode = &msm_pm_modes[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]; + if (mode->latency >= latency_qos) + allow_sleep = false; + + mode = &msm_pm_modes[ + MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT]; + if (mode->latency >= latency_qos) { + /* no time even for SWFI */ + while (!msm_irq_pending()) + udelay(1); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif + goto abort_idle; + } + + 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); + spin = msm_pm_idle_spin_time >> 10; + while (spin-- > 0) { + if (msm_irq_pending()) { +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif + goto abort_idle; + } + udelay(1); + } + if (sleep_time < msm_pm_idle_sleep_min_time || !allow_sleep) { + unsigned long saved_rate; + saved_rate = acpuclk_wait_for_irq(); + if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK) + printk(KERN_DEBUG "arch_idle: clk %ld -> swfi\n", + saved_rate); + if (saved_rate) { + msm_arch_idle(); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_WFI; +#endif + } else { + while (!msm_irq_pending()) + udelay(1); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif + } + if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK) + printk(KERN_DEBUG "msm_sleep: clk swfi -> %ld\n", + saved_rate); + if (saved_rate + && acpuclk_set_rate(saved_rate, SETRATE_SWFI) < 0) + printk(KERN_ERR "msm_sleep(): clk_set_rate %ld " + "failed\n", saved_rate); + } else { +#ifdef CONFIG_MSM_IDLE_STATS + ret = msm_clock_require_tcxo(clk_ids, NR_CLKS); +#elif defined(CONFIG_CLOCK_BASED_SLEEP_LIMIT) + ret = msm_clock_require_tcxo(NULL, 0); +#endif + +#ifdef CONFIG_CLOCK_BASED_SLEEP_LIMIT + if (ret) + sleep_limit = SLEEP_LIMIT_NO_TCXO_SHUTDOWN; +#endif + + 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, + sleep_limit, 1); +#ifdef CONFIG_MSM_IDLE_STATS + switch (msm_pm_idle_sleep_mode) { + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND: + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: + if (ret) + exit_stat = + MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE; + else { + exit_stat = MSM_PM_STAT_IDLE_POWER_COLLAPSE; + msm_pm_sleep_limit = sleep_limit; + bitmap_copy(msm_pm_clocks_no_tcxo_shutdown, + clk_ids, NR_CLKS); + } + break; + case MSM_PM_SLEEP_MODE_APPS_SLEEP: + if (ret) + exit_stat = MSM_PM_STAT_IDLE_FAILED_SLEEP; + else + exit_stat = MSM_PM_STAT_IDLE_SLEEP; + break; + default: + exit_stat = MSM_PM_STAT_IDLE_WFI; + } +#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) +{ + uint32_t sleep_limit; + int ret; +#ifdef CONFIG_MSM_IDLE_STATS + DECLARE_BITMAP(clk_ids, NR_CLKS); + int64_t period = 0; + int64_t time = 0; + + time = msm_timer_get_sclk_time(&period); + ret = msm_clock_require_tcxo(clk_ids, NR_CLKS); +#elif defined(CONFIG_CLOCK_BASED_SLEEP_LIMIT) + ret = msm_clock_require_tcxo(NULL, 0); +#endif /* CONFIG_MSM_IDLE_STATS */ + +#ifdef CONFIG_CLOCK_BASED_SLEEP_LIMIT + sleep_limit = ret ? SLEEP_LIMIT_NO_TCXO_SHUTDOWN : SLEEP_LIMIT_NONE; +#else + sleep_limit = SLEEP_LIMIT_NONE; +#endif + +#ifdef CONFIG_MSM_SLEEP_TIME_OVERRIDE + if (msm_pm_sleep_time_override > 0) { + int64_t ns = NSEC_PER_SEC * (int64_t)msm_pm_sleep_time_override; + msm_pm_set_max_sleep_time(ns); + msm_pm_sleep_time_override = 0; + } +#endif + + ret = msm_sleep(msm_pm_sleep_mode, + msm_pm_max_sleep_time, sleep_limit, 0); + +#ifdef CONFIG_MSM_IDLE_STATS + if (msm_pm_sleep_mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND || + msm_pm_sleep_mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE) { + enum msm_pm_time_stats_id id; + int64_t end_time; + + if (ret) + id = MSM_PM_STAT_FAILED_SUSPEND; + else { + id = MSM_PM_STAT_SUSPEND; + msm_pm_sleep_limit = sleep_limit; + bitmap_copy(msm_pm_clocks_no_tcxo_shutdown, clk_ids, + NR_CLKS); + } + + if (time != 0) { + end_time = msm_timer_get_sclk_time(NULL); + if (end_time != 0) { + time = end_time - time; + if (time < 0) + time += period; + } else + time = 0; + } + + msm_pm_add_stat(id, time); + } +#endif + + 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 void msm_pm_power_off(void) +{ + msm_proc_comm(PCOM_POWER_DOWN, 0, 0); + for (;;) ; +} + +static void msm_pm_restart(char str, const char *cmd) +{ + msm_proc_comm(PCOM_RESET_CHIP, &restart_reason, 0); + + 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 { + restart_reason = 0x77665501; + } + } + return NOTIFY_DONE; +} + +static struct notifier_block msm_reboot_notifier = +{ + .notifier_call = msm_reboot_call, +}; + +#ifdef CONFIG_MSM_IDLE_STATS +/* + * Helper function of snprintf where buf is auto-incremented, size is auto- + * decremented, and there is no return value. + * + * NOTE: buf and size must be l-values (e.g. variables) + */ +#define SNPRINTF(buf, size, format, ...) \ + do { \ + if (size > 0) { \ + int ret; \ + ret = snprintf(buf, size, format, ## __VA_ARGS__); \ + if (ret > size) { \ + buf += size; \ + size = 0; \ + } else { \ + buf += ret; \ + size -= ret; \ + } \ + } \ + } while (0) + +/* + * Write out the power management statistics. + */ +static int msm_pm_read_proc( + char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int i; + char *p = page; + char clk_name[16]; + + if (count < 1024) { + *start = (char *) 0; + *eof = 0; + return 0; + } + + if (!off) { + SNPRINTF(p, count, "Clocks against last TCXO shutdown:\n"); + for_each_bit(i, msm_pm_clocks_no_tcxo_shutdown, NR_CLKS) { + clk_name[0] = '\0'; + msm_clock_get_name(i, clk_name, sizeof(clk_name)); + SNPRINTF(p, count, " %s (id=%d)\n", clk_name, i); + } + + SNPRINTF(p, count, "Last power collapse voted "); + if (msm_pm_sleep_limit == SLEEP_LIMIT_NONE) + SNPRINTF(p, count, "for TCXO shutdown\n\n"); + else + SNPRINTF(p, count, "against TCXO shutdown\n\n"); + + *start = (char *) 1; + *eof = 0; + } else if (--off < ARRAY_SIZE(msm_pm_stats)) { + int64_t bucket_time; + int64_t s; + uint32_t ns; + + s = msm_pm_stats[off].total_time; + ns = do_div(s, NSEC_PER_SEC); + SNPRINTF(p, count, + "%s:\n" + " count: %7d\n" + " total_time: %lld.%09u\n", + msm_pm_stats[off].name, + msm_pm_stats[off].count, + s, ns); + + bucket_time = msm_pm_stats[off].first_bucket_time; + for (i = 0; i < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; i++) { + s = bucket_time; + ns = do_div(s, NSEC_PER_SEC); + SNPRINTF(p, count, + " <%6lld.%09u: %7d (%lld-%lld)\n", + s, ns, msm_pm_stats[off].bucket[i], + msm_pm_stats[off].min_time[i], + msm_pm_stats[off].max_time[i]); + + bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT; + } + + SNPRINTF(p, count, " >=%6lld.%09u: %7d (%lld-%lld)\n", + s, ns, msm_pm_stats[off].bucket[i], + msm_pm_stats[off].min_time[i], + msm_pm_stats[off].max_time[i]); + + *start = (char *) 1; + *eof = (off + 1 >= ARRAY_SIZE(msm_pm_stats)); + } + + return p - page; +} +#undef SNPRINTF + +#define MSM_PM_STATS_RESET "reset" + +/* + * Reset the power management statistics values. + */ +static int msm_pm_write_proc(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char buf[sizeof(MSM_PM_STATS_RESET)]; + int ret; + unsigned long flags; + int i; + + if (count < strlen(MSM_PM_STATS_RESET)) { + ret = -EINVAL; + goto write_proc_failed; + } + + if (copy_from_user(buf, buffer, strlen(MSM_PM_STATS_RESET))) { + ret = -EFAULT; + goto write_proc_failed; + } + + if (memcmp(buf, MSM_PM_STATS_RESET, strlen(MSM_PM_STATS_RESET))) { + ret = -EINVAL; + goto write_proc_failed; + } + + local_irq_save(flags); + for (i = 0; i < ARRAY_SIZE(msm_pm_stats); i++) { + memset(msm_pm_stats[i].bucket, + 0, sizeof(msm_pm_stats[i].bucket)); + memset(msm_pm_stats[i].min_time, + 0, sizeof(msm_pm_stats[i].min_time)); + memset(msm_pm_stats[i].max_time, + 0, sizeof(msm_pm_stats[i].max_time)); + msm_pm_stats[i].count = 0; + msm_pm_stats[i].total_time = 0; + } + + msm_pm_sleep_limit = SLEEP_LIMIT_NONE; + bitmap_zero(msm_pm_clocks_no_tcxo_shutdown, NR_CLKS); + local_irq_restore(flags); + + return count; + +write_proc_failed: + return ret; +} +#undef MSM_PM_STATS_RESET +#endif /* CONFIG_MSM_IDLE_STATS */ + +static int __init msm_pm_init(void) +{ +#ifdef CONFIG_MSM_IDLE_STATS + struct proc_dir_entry *d_entry; +#endif + + pm_power_off = msm_pm_power_off; + arm_pm_restart = msm_pm_restart; + msm_pm_max_sleep_time = 0; + + register_reboot_notifier(&msm_reboot_notifier); + + msm_pm_sma.sleep_delay = smem_alloc(SMEM_SMSM_SLEEP_DELAY, + sizeof(*msm_pm_sma.sleep_delay)); + if (msm_pm_sma.sleep_delay == NULL) { + printk(KERN_ERR "msm_pm_init: failed get SLEEP_DELAY\n"); + return -ENODEV; + } + + msm_pm_sma.limit_sleep = smem_alloc(SMEM_SMSM_LIMIT_SLEEP, + sizeof(*msm_pm_sma.limit_sleep)); + if (msm_pm_sma.limit_sleep == NULL) { + printk(KERN_ERR "msm_pm_init: failed get LIMIT_SLEEP\n"); + return -ENODEV; + } + + msm_pm_sma.int_info_ext = smem_alloc(SMEM_SMSM_INT_INFO, + sizeof(*msm_pm_sma.int_info_ext)); + + if (msm_pm_sma.int_info_ext) + msm_pm_sma.int_info = (struct smsm_interrupt_info *) + msm_pm_sma.int_info_ext; + else + msm_pm_sma.int_info = smem_alloc(SMEM_SMSM_INT_INFO, + sizeof(*msm_pm_sma.int_info)); + + if (msm_pm_sma.int_info == NULL) { + printk(KERN_ERR "msm_pm_init: failed get INT_INFO\n"); + return -ENODEV; + } + +#if defined(CONFIG_ARCH_MSM_SCORPION) + /* The bootloader is responsible for initializing many of Scorpion's + * coprocessor registers for things like cache timing. The state of + * these coprocessor registers is lost on reset, so part of the + * bootloader must be re-executed. Do not overwrite the reset vector + * or bootloader area. + */ + msm_pm_reset_vector = PAGE_OFFSET; +#else + msm_pm_reset_vector = ioremap(0, PAGE_SIZE); + if (msm_pm_reset_vector == NULL) { + printk(KERN_ERR "msm_pm_init: failed to map reset vector\n"); + return -ENODEV; + } +#endif /* CONFIG_ARCH_MSM_SCORPION */ + + BUG_ON(msm_pm_modes == NULL); + + atomic_set(&msm_pm_init_done, 1); + suspend_set_ops(&msm_pm_ops); + +#ifdef CONFIG_MSM_IDLE_STATS + d_entry = create_proc_entry("msm_pm_stats", + S_IRUGO | S_IWUSR | S_IWGRP, NULL); + if (d_entry) { + d_entry->read_proc = msm_pm_read_proc; + d_entry->write_proc = msm_pm_write_proc; + d_entry->data = NULL; + } +#endif + + return 0; +} + +void __init msm_pm_set_platform_data(struct msm_pm_platform_data *data) +{ + msm_pm_modes = data; +} + +late_initcall(msm_pm_init); |