/* arch/arm/mach-msm/pm.c * * MSM Power Management Routines * * Copyright (C) 2007 Google, Inc. * Copyright (c) 2008-2011, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_HAS_WAKELOCK #include #endif #include "smd_private.h" #include "smd_rpcrouter.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; #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) { __raw_writel(0x1f, A11S_CLK_SLEEP_EN); __raw_writel(1, A11S_PWRDOWN); __raw_writel(0, A11S_STANDBY_CTL); __raw_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", __raw_readl(A11S_CLK_SLEEP_EN), __raw_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) { 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); } 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(smp_processor_id(), 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", __raw_readl(A11S_CLK_SLEEP_EN), __raw_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) { __raw_writel(0x00, A11S_CLK_SLEEP_EN); __raw_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", __raw_readl(A11S_CLK_SLEEP_EN), __raw_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", __raw_readl(A11S_CLK_SLEEP_EN), __raw_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 int64_t t1; static int64_t t2; int exit_stat; #endif int latency_qos = pm_qos_request(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(smp_processor_id(), saved_rate, SETRATE_SWFI) < 0) printk(KERN_ERR "msm_sleep(): clk_set_rate %ld " "failed\n", saved_rate); } else { 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; } 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 = SLEEP_LIMIT_NONE; int ret; #ifdef CONFIG_MSM_IDLE_STATS int64_t period = 0; int64_t time = 0; time = msm_timer_get_sclk_time(&period); #endif clock_debug_print_enabled(); #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; } 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_rpcrouter_close(); msm_proc_comm(PCOM_POWER_DOWN, 0, 0); for (;;) ; } static void msm_pm_restart(char str, const char *cmd) { msm_rpcrouter_close(); 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; if (count < 1024) { *start = (char *) 0; *eof = 0; return 0; } if (!off) { 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; 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 int ret; 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) && !defined(CONFIG_MSM_SMP) /* 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 = (uint32_t *) 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 */ ret = msm_timer_init_time_sync(msm_pm_timeout); if (ret) return ret; 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, int count) { BUG_ON(MSM_PM_SLEEP_MODE_NR != count); msm_pm_modes = data; } late_initcall(msm_pm_init);