diff options
Diffstat (limited to 'arch/arm/mach-msm/pm2.c')
-rw-r--r-- | arch/arm/mach-msm/pm2.c | 1666 |
1 files changed, 1666 insertions, 0 deletions
diff --git a/arch/arm/mach-msm/pm2.c b/arch/arm/mach-msm/pm2.c new file mode 100644 index 000000000000..dd5d690b76a5 --- /dev/null +++ b/arch/arm/mach-msm/pm2.c @@ -0,0 +1,1666 @@ +/* arch/arm/mach-msm/pm2.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 <linux/io.h> +#include <mach/msm_iomap.h> +#include <mach/system.h> +#ifdef CONFIG_CACHE_L2X0 +#include <asm/hardware/cache-l2x0.h> +#endif +#ifdef CONFIG_VFP +#include <asm/vfp.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" + +/****************************************************************************** + * Debug Definitions + *****************************************************************************/ + +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 +); + +#define MSM_PM_DPRINTK(mask, level, message, ...) \ + do { \ + if ((mask) & msm_pm_debug_mask) \ + printk(level message, ## __VA_ARGS__); \ + } while (0) + +#define MSM_PM_DEBUG_PRINT_STATE(tag) \ + do { \ + MSM_PM_DPRINTK(MSM_PM_DEBUG_STATE, \ + KERN_INFO, "%s: " \ + "APPS_CLK_SLEEP_EN %x, APPS_PWRDOWN %x, " \ + "SMSM_POWER_MASTER_DEM %x, SMSM_MODEM_STATE %x, " \ + "SMSM_APPS_DEM %x\n", \ + tag, \ + readl(APPS_CLK_SLEEP_EN), readl(APPS_PWRDOWN), \ + smsm_get_state(SMSM_POWER_MASTER_DEM), \ + smsm_get_state(SMSM_MODEM_STATE), \ + smsm_get_state(SMSM_APPS_DEM)); \ + } while (0) + +#define MSM_PM_DEBUG_PRINT_SLEEP_INFO() \ + do { \ + if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE) \ + smsm_print_sleep_info(msm_pm_smem_data->sleep_time, \ + msm_pm_smem_data->resources_used, \ + msm_pm_smem_data->irq_mask, \ + msm_pm_smem_data->wakeup_reason, \ + msm_pm_smem_data->pending_irqs); \ + } while (0) + + +/****************************************************************************** + * Sleep Modes and Parameters + *****************************************************************************/ + +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 +); + +#define MSM_PM_MODE_ATTR_SUSPEND_ENABLED "suspend_enabled" +#define MSM_PM_MODE_ATTR_IDLE_ENABLED "idle_enabled" +#define MSM_PM_MODE_ATTR_LATENCY "latency" +#define MSM_PM_MODE_ATTR_RESIDENCY "residency" +#define MSM_PM_MODE_ATTR_NR (4) + +static char *msm_pm_sleep_mode_labels[MSM_PM_SLEEP_MODE_NR] = { + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND] = " ", + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = "power_collapse", + [MSM_PM_SLEEP_MODE_APPS_SLEEP] = "apps_sleep", + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT] = + "ramp_down_and_wfi", + [MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT] = "wfi", + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = + "power_collapse_no_xo_shutdown", +}; + +static struct msm_pm_platform_data *msm_pm_modes; + +static struct kobject *msm_pm_mode_kobjs[MSM_PM_SLEEP_MODE_NR]; +static struct attribute_group *msm_pm_mode_attr_group[MSM_PM_SLEEP_MODE_NR]; +static struct attribute **msm_pm_mode_attrs[MSM_PM_SLEEP_MODE_NR]; +static struct kobj_attribute *msm_pm_mode_kobj_attrs[MSM_PM_SLEEP_MODE_NR]; + +/* + * Write out the attribute. + */ +static ssize_t msm_pm_mode_attr_show( + struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int ret = -EINVAL; + int i; + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + struct kernel_param kp; + + if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i])) + continue; + + if (!strcmp(attr->attr.name, + MSM_PM_MODE_ATTR_SUSPEND_ENABLED)) { + u32 arg = msm_pm_modes[i].suspend_enabled; + kp.arg = &arg; + ret = param_get_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + MSM_PM_MODE_ATTR_IDLE_ENABLED)) { + u32 arg = msm_pm_modes[i].idle_enabled; + kp.arg = &arg; + ret = param_get_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + MSM_PM_MODE_ATTR_LATENCY)) { + kp.arg = &msm_pm_modes[i].latency; + ret = param_get_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + MSM_PM_MODE_ATTR_RESIDENCY)) { + kp.arg = &msm_pm_modes[i].residency; + ret = param_get_ulong(buf, &kp); + } + + break; + } + + if (ret > 0) { + strcat(buf, "\n"); + ret++; + } + + return ret; +} + +/* + * Read in the new attribute value. + */ +static ssize_t msm_pm_mode_attr_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret = -EINVAL; + int i; + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + struct kernel_param kp; + + if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i])) + continue; + + if (!strcmp(attr->attr.name, + MSM_PM_MODE_ATTR_SUSPEND_ENABLED)) { + kp.arg = &msm_pm_modes[i].suspend_enabled; + ret = param_set_byte(buf, &kp); + } else if (!strcmp(attr->attr.name, + MSM_PM_MODE_ATTR_IDLE_ENABLED)) { + kp.arg = &msm_pm_modes[i].idle_enabled; + ret = param_set_byte(buf, &kp); + } else if (!strcmp(attr->attr.name, + MSM_PM_MODE_ATTR_LATENCY)) { + kp.arg = &msm_pm_modes[i].latency; + ret = param_set_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + MSM_PM_MODE_ATTR_RESIDENCY)) { + kp.arg = &msm_pm_modes[i].residency; + ret = param_set_ulong(buf, &kp); + } + + break; + } + + return ret ? ret : count; +} + +/* + * Add sysfs entries for the sleep modes. + */ +static int __init msm_pm_mode_sysfs_add(void) +{ + struct kobject *module_kobj = NULL; + struct kobject *modes_kobj = NULL; + + struct kobject *kobj; + struct attribute_group *attr_group; + struct attribute **attrs; + struct kobj_attribute *kobj_attrs; + + int i, k; + int ret; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + printk(KERN_ERR "%s: cannot find kobject for module %s\n", + __func__, KBUILD_MODNAME); + ret = -ENOENT; + goto mode_sysfs_add_cleanup; + } + + modes_kobj = kobject_create_and_add("modes", module_kobj); + if (!modes_kobj) { + printk(KERN_ERR "%s: cannot create modes kobject\n", __func__); + ret = -ENOMEM; + goto mode_sysfs_add_cleanup; + } + + for (i = 0; i < ARRAY_SIZE(msm_pm_mode_kobjs); i++) { + if (!msm_pm_modes[i].supported) + continue; + + kobj = kobject_create_and_add( + msm_pm_sleep_mode_labels[i], modes_kobj); + attr_group = kzalloc(sizeof(*attr_group), GFP_KERNEL); + attrs = kzalloc(sizeof(*attrs) * (MSM_PM_MODE_ATTR_NR + 1), + GFP_KERNEL); + kobj_attrs = kzalloc(sizeof(*kobj_attrs) * MSM_PM_MODE_ATTR_NR, + GFP_KERNEL); + + if (!kobj || !attr_group || !attrs || !kobj_attrs) { + printk(KERN_ERR + "%s: cannot create kobject or attributes\n", + __func__); + ret = -ENOMEM; + goto mode_sysfs_add_abort; + } + + kobj_attrs[0].attr.name = MSM_PM_MODE_ATTR_SUSPEND_ENABLED; + kobj_attrs[1].attr.name = MSM_PM_MODE_ATTR_IDLE_ENABLED; + kobj_attrs[2].attr.name = MSM_PM_MODE_ATTR_LATENCY; + kobj_attrs[3].attr.name = MSM_PM_MODE_ATTR_RESIDENCY; + + for (k = 0; k < MSM_PM_MODE_ATTR_NR; k++) { + kobj_attrs[k].attr.mode = 0644; + kobj_attrs[k].show = msm_pm_mode_attr_show; + kobj_attrs[k].store = msm_pm_mode_attr_store; + + attrs[k] = &kobj_attrs[k].attr; + } + attrs[MSM_PM_MODE_ATTR_NR] = NULL; + + attr_group->attrs = attrs; + ret = sysfs_create_group(kobj, attr_group); + if (ret) { + printk(KERN_ERR + "%s: cannot create kobject attribute group\n", + __func__); + goto mode_sysfs_add_abort; + } + + msm_pm_mode_kobjs[i] = kobj; + msm_pm_mode_attr_group[i] = attr_group; + msm_pm_mode_attrs[i] = attrs; + msm_pm_mode_kobj_attrs[i] = kobj_attrs; + } + + return 0; + +mode_sysfs_add_abort: + kfree(kobj_attrs); + kfree(attrs); + kfree(attr_group); + kobject_put(kobj); + +mode_sysfs_add_cleanup: + for (i = ARRAY_SIZE(msm_pm_mode_kobjs) - 1; i >= 0; i--) { + if (!msm_pm_mode_kobjs[i]) + continue; + + sysfs_remove_group( + msm_pm_mode_kobjs[i], msm_pm_mode_attr_group[i]); + + kfree(msm_pm_mode_kobj_attrs[i]); + kfree(msm_pm_mode_attrs[i]); + kfree(msm_pm_mode_attr_group[i]); + kobject_put(msm_pm_mode_kobjs[i]); + } + + return ret; +} + +void __init msm_pm_set_platform_data(struct msm_pm_platform_data *data) +{ + msm_pm_modes = data; +} + + +/****************************************************************************** + * Sleep Limitations + *****************************************************************************/ +enum { + SLEEP_LIMIT_NONE = 0, + SLEEP_LIMIT_NO_TCXO_SHUTDOWN = 2 +}; + + +/****************************************************************************** + * Configure Hardware for Power Down/Up + *****************************************************************************/ + +#define APPS_CLK_SLEEP_EN (MSM_CSR_BASE + 0x11c) +#define APPS_PWRDOWN (MSM_CSR_BASE + 0x440) +#define APPS_STANDBY_CTL (MSM_CSR_BASE + 0x108) + +/* + * Configure hardware registers in preparation for Apps power down. + */ +static void msm_pm_config_hw_before_power_down(void) +{ + writel(0x1f, APPS_CLK_SLEEP_EN); + writel(1, APPS_PWRDOWN); + writel(0, APPS_STANDBY_CTL); +} + +/* + * Clear hardware registers after Apps powers up. + */ +static void msm_pm_config_hw_after_power_up(void) +{ + writel(0, APPS_PWRDOWN); + writel(0, APPS_CLK_SLEEP_EN); +} + +/* + * Configure hardware registers in preparation for SWFI. + */ +static void msm_pm_config_hw_before_swfi(void) +{ +#ifdef CONFIG_ARCH_MSM_SCORPION + writel(0x1f, APPS_CLK_SLEEP_EN); +#else + writel(0x0f, APPS_CLK_SLEEP_EN); +#endif +} + +/* + * 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 (;;) + ; +} + + +/****************************************************************************** + * State Polling Definitions + *****************************************************************************/ + +struct msm_pm_polled_group { + uint32_t group_id; + + uint32_t bits_all_set; + uint32_t bits_all_clear; + uint32_t bits_any_set; + uint32_t bits_any_clear; + + uint32_t value_read; +}; + +/* + * Return true if all bits indicated by flag are set in source. + */ +static inline bool msm_pm_all_set(uint32_t source, uint32_t flag) +{ + return (source & flag) == flag; +} + +/* + * Return true if any bit indicated by flag are set in source. + */ +static inline bool msm_pm_any_set(uint32_t source, uint32_t flag) +{ + return !flag || (source & flag); +} + +/* + * Return true if all bits indicated by flag are cleared in source. + */ +static inline bool msm_pm_all_clear(uint32_t source, uint32_t flag) +{ + return (~source & flag) == flag; +} + +/* + * Return true if any bit indicated by flag are cleared in source. + */ +static inline bool msm_pm_any_clear(uint32_t source, uint32_t flag) +{ + return !flag || (~source & flag); +} + +/* + * Poll the shared memory states as indicated by the poll groups. + * + * nr_grps: number of groups in the array + * grps: array of groups + * + * The function returns when conditions specified by any of the poll + * groups become true. The conditions specified by a poll group are + * deemed true when 1) at least one bit from bits_any_set is set OR one + * bit from bits_any_clear is cleared; and 2) all bits in bits_all_set + * are set; and 3) all bits in bits_all_clear are cleared. + * + * Return value: + * >=0: index of the poll group whose conditions have become true + * -ETIMEDOUT: timed out + */ +static int msm_pm_poll_state(int nr_grps, struct msm_pm_polled_group *grps) +{ + int i, k; + + for (i = 0; i < 500000; i++) + for (k = 0; k < nr_grps; k++) { + bool all_set, all_clear; + bool any_set, any_clear; + + grps[k].value_read = smsm_get_state(grps[k].group_id); + + all_set = msm_pm_all_set(grps[k].value_read, + grps[k].bits_all_set); + all_clear = msm_pm_all_clear(grps[k].value_read, + grps[k].bits_all_clear); + any_set = msm_pm_any_set(grps[k].value_read, + grps[k].bits_any_set); + any_clear = msm_pm_any_clear(grps[k].value_read, + grps[k].bits_any_clear); + + if (all_set && all_clear && (any_set || any_clear)) + return k; + } + + printk(KERN_ERR "%s failed:\n", __func__); + for (k = 0; k < nr_grps; k++) + printk(KERN_ERR "(%x, %x, %x, %x) %x\n", + grps[k].bits_all_set, grps[k].bits_all_clear, + grps[k].bits_any_set, grps[k].bits_any_clear, + grps[k].value_read); + + return -ETIMEDOUT; +} + + +/****************************************************************************** + * Suspend Max Sleep Time + *****************************************************************************/ + +#define SCLK_HZ (32768) +#define MSM_PM_SLEEP_TICK_LIMIT (0x6DDD000) + +#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 uint32_t msm_pm_max_sleep_time; + +/* + * Convert time from nanoseconds to slow clock ticks, then cap it to the + * specified limit + */ +static int64_t msm_pm_convert_and_cap_time(int64_t time_ns, int64_t limit) +{ + do_div(time_ns, NSEC_PER_SEC / SCLK_HZ); + return (time_ns > limit) ? limit : time_ns; +} + +/* + * Set the sleep time for suspend. 0 means infinite sleep time. + */ +void msm_pm_set_max_sleep_time(int64_t max_sleep_time_ns) +{ + unsigned long flags; + + local_irq_save(flags); + if (max_sleep_time_ns == 0) { + msm_pm_max_sleep_time = 0; + } else { + msm_pm_max_sleep_time = (uint32_t)msm_pm_convert_and_cap_time( + max_sleep_time_ns, MSM_PM_SLEEP_TICK_LIMIT); + + if (msm_pm_max_sleep_time == 0) + msm_pm_max_sleep_time = 1; + } + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND, KERN_INFO, + "%s(): Requested %lld ns Giving %u sclk ticks\n", __func__, + max_sleep_time_ns, msm_pm_max_sleep_time); + local_irq_restore(flags); +} +EXPORT_SYMBOL(msm_pm_set_max_sleep_time); + + +/****************************************************************************** + * CONFIG_MSM_IDLE_STATS + *****************************************************************************/ + +#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 uint32_t msm_pm_sleep_limit = SLEEP_LIMIT_NONE; +static DECLARE_BITMAP(msm_pm_clocks_no_tcxo_shutdown, NR_CLKS); + +/* + * Add the given time data to the statistics collection. + */ +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; +} + +/* + * 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 */ + + +/****************************************************************************** + * Shared Memory Bits + *****************************************************************************/ + +#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_APPS_STATE)) +#define DEM_MASTER_SMSM_RSA \ + (0x02UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) +#define DEM_MASTER_SMSM_PWRC_EARLY_EXIT \ + (0x04UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) +#define DEM_MASTER_SMSM_SLEEP_EXIT \ + (0x08UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) +#define DEM_MASTER_SMSM_READY \ + (0x10UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) +#define DEM_MASTER_SMSM_SLEEP \ + (0x20UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) + +/* 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) + + +/****************************************************************************** + * Shared Memory Data + *****************************************************************************/ + +#define DEM_MAX_PORT_NAME_LEN (20) + +struct msm_pm_smem_t { + uint32_t sleep_time; + uint32_t irq_mask; + uint32_t resources_used; + uint32_t reserved1; + + uint32_t wakeup_reason; + uint32_t pending_irqs; + uint32_t rpc_prog; + uint32_t rpc_proc; + char smd_port_name[DEM_MAX_PORT_NAME_LEN]; + uint32_t reserved2; +}; + + +/****************************************************************************** + * + *****************************************************************************/ +static struct msm_pm_smem_t *msm_pm_smem_data; +static uint32_t *msm_pm_reset_vector; +static atomic_t msm_pm_init_done = ATOMIC_INIT(0); + +/* + * Power collapse the Apps processor. This function executes the handshake + * protocol with Modem. + * + * Return value: + * -EAGAIN: modem reset occurred or early exit from power collapse + * -EBUSY: modem not ready for our power collapse -- no power loss + * -ETIMEDOUT: timed out waiting for modem's handshake -- no power loss + * 0: success + */ +static int msm_pm_power_collapse + (bool from_idle, uint32_t sleep_delay, uint32_t sleep_limit) +{ + struct msm_pm_polled_group state_grps[2]; + unsigned long saved_acpuclk_rate; + uint32_t saved_vector[2]; + int collapsed = 0; + int ret; +#ifdef CONFIG_MSM_ADM_OFF_AT_POWER_COLLAPSE + unsigned id; +#endif + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, "%s(): idle %d, delay %u, limit %u\n", __func__, + (int)from_idle, sleep_delay, sleep_limit); + + if (!(smsm_get_state(SMSM_POWER_MASTER_DEM) & DEM_MASTER_SMSM_READY)) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND | MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, "%s(): master not ready\n", __func__); + ret = -EBUSY; + goto power_collapse_bail; + } + + memset(msm_pm_smem_data, 0, sizeof(*msm_pm_smem_data)); + + msm_irq_enter_sleep1(true, from_idle, &msm_pm_smem_data->irq_mask); + msm_gpio_enter_sleep(from_idle); + + msm_pm_smem_data->sleep_time = sleep_delay; + msm_pm_smem_data->resources_used = sleep_limit; + + /* Enter PWRC/PWRC_SUSPEND */ + + if (from_idle) + smsm_change_state(SMSM_APPS_DEM, DEM_SLAVE_SMSM_RUN, + DEM_SLAVE_SMSM_PWRC); + else + smsm_change_state(SMSM_APPS_DEM, DEM_SLAVE_SMSM_RUN, + DEM_SLAVE_SMSM_PWRC | DEM_SLAVE_SMSM_PWRC_SUSPEND); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): PWRC"); + MSM_PM_DEBUG_PRINT_SLEEP_INFO(); + + memset(state_grps, 0, sizeof(state_grps)); + state_grps[0].group_id = SMSM_POWER_MASTER_DEM; + state_grps[0].bits_all_set = DEM_MASTER_SMSM_RSA; + state_grps[1].group_id = SMSM_MODEM_STATE; + state_grps[1].bits_all_set = SMSM_RESET; + + ret = msm_pm_poll_state(ARRAY_SIZE(state_grps), state_grps); + + if (ret < 0) { + printk(KERN_EMERG "%s(): power collapse entry " + "timed out waiting for Modem's response\n", __func__); + msm_pm_timeout(); + } + + if (ret == 1) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_poll_state detected Modem reset\n", + __func__); + goto power_collapse_early_exit; + } + + /* DEM Master in RSA */ + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): PWRC RSA"); + + ret = msm_irq_enter_sleep2(true, from_idle); + if (ret < 0) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_irq_enter_sleep2 aborted, %d\n", __func__, + ret); + goto power_collapse_early_exit; + } + +#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. */ + id = 1; + msm_proc_comm(PCOM_CLKCTL_RPC_DISABLE, &id, NULL); +#endif + + msm_pm_config_hw_before_power_down(); + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): pre power down"); + + saved_acpuclk_rate = acpuclk_power_collapse(); + MSM_PM_DPRINTK(MSM_PM_DEBUG_CLOCK, KERN_INFO, + "%s(): change clock rate (old rate = %lu)\n", __func__, + saved_acpuclk_rate); + + if (saved_acpuclk_rate == 0) { + msm_pm_config_hw_after_power_up(); + goto power_collapse_early_exit; + } + + 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); + + MSM_PM_DPRINTK(MSM_PM_DEBUG_RESET_VECTOR, KERN_INFO, + "%s(): vector %x %x -> %x %x\n", __func__, + 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 + + collapsed = msm_pm_collapse(); + +#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(); + local_fiq_enable(); + } + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND | MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_collapse returned %d\n", __func__, collapsed); + + MSM_PM_DPRINTK(MSM_PM_DEBUG_CLOCK, KERN_INFO, + "%s(): restore clock rate to %lu\n", __func__, + saved_acpuclk_rate); + if (acpuclk_set_rate(saved_acpuclk_rate, SETRATE_PC) < 0) + printk(KERN_ERR "%s(): failed to restore clock rate(%lu)\n", + __func__, saved_acpuclk_rate); + +#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 + "%s(): failed to turn on ADM clock\n", __func__); +#endif + + msm_irq_exit_sleep1(msm_pm_smem_data->irq_mask, + msm_pm_smem_data->wakeup_reason, + msm_pm_smem_data->pending_irqs); + + msm_pm_config_hw_after_power_up(); + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): post power up"); + + memset(state_grps, 0, sizeof(state_grps)); + state_grps[0].group_id = SMSM_POWER_MASTER_DEM; + state_grps[0].bits_any_set = + DEM_MASTER_SMSM_RSA | DEM_MASTER_SMSM_PWRC_EARLY_EXIT; + state_grps[1].group_id = SMSM_MODEM_STATE; + state_grps[1].bits_all_set = SMSM_RESET; + + ret = msm_pm_poll_state(ARRAY_SIZE(state_grps), state_grps); + + if (ret < 0) { + printk(KERN_EMERG "%s(): power collapse exit " + "timed out waiting for Modem's response\n", __func__); + msm_pm_timeout(); + } + + if (ret == 1) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_poll_state detected Modem reset\n", + __func__); + goto power_collapse_early_exit; + } + + /* Sanity check */ + if (collapsed) { + BUG_ON(!(state_grps[0].value_read & DEM_MASTER_SMSM_RSA)); + } else { + BUG_ON(!(state_grps[0].value_read & + DEM_MASTER_SMSM_PWRC_EARLY_EXIT)); + goto power_collapse_early_exit; + } + + /* Enter WFPI */ + + smsm_change_state(SMSM_APPS_DEM, + DEM_SLAVE_SMSM_PWRC | DEM_SLAVE_SMSM_PWRC_SUSPEND, + DEM_SLAVE_SMSM_WFPI); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): WFPI"); + + memset(state_grps, 0, sizeof(state_grps)); + state_grps[0].group_id = SMSM_POWER_MASTER_DEM; + state_grps[0].bits_all_set = DEM_MASTER_SMSM_RUN; + state_grps[1].group_id = SMSM_MODEM_STATE; + state_grps[1].bits_all_set = SMSM_RESET; + + ret = msm_pm_poll_state(ARRAY_SIZE(state_grps), state_grps); + + if (ret < 0) { + printk(KERN_EMERG "%s(): power collapse WFPI " + "timed out waiting for Modem's response\n", __func__); + msm_pm_timeout(); + } + + if (ret == 1) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_poll_state detected Modem reset\n", + __func__); + ret = -EAGAIN; + goto power_collapse_restore_gpio_bail; + } + + /* DEM Master == RUN */ + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): WFPI RUN"); + MSM_PM_DEBUG_PRINT_SLEEP_INFO(); + + msm_irq_exit_sleep2(msm_pm_smem_data->irq_mask, + msm_pm_smem_data->wakeup_reason, + msm_pm_smem_data->pending_irqs); + msm_irq_exit_sleep3(msm_pm_smem_data->irq_mask, + msm_pm_smem_data->wakeup_reason, + msm_pm_smem_data->pending_irqs); + msm_gpio_exit_sleep(); + + smsm_change_state(SMSM_APPS_DEM, + DEM_SLAVE_SMSM_WFPI, DEM_SLAVE_SMSM_RUN); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): RUN"); + + smd_sleep_exit(); + return 0; + +power_collapse_early_exit: + /* Enter PWRC_EARLY_EXIT */ + + smsm_change_state(SMSM_APPS_DEM, + DEM_SLAVE_SMSM_PWRC | DEM_SLAVE_SMSM_PWRC_SUSPEND, + DEM_SLAVE_SMSM_PWRC_EARLY_EXIT); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): EARLY_EXIT"); + + memset(state_grps, 0, sizeof(state_grps)); + state_grps[0].group_id = SMSM_POWER_MASTER_DEM; + state_grps[0].bits_all_set = DEM_MASTER_SMSM_PWRC_EARLY_EXIT; + state_grps[1].group_id = SMSM_MODEM_STATE; + state_grps[1].bits_all_set = SMSM_RESET; + + ret = msm_pm_poll_state(ARRAY_SIZE(state_grps), state_grps); + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): EARLY_EXIT EE"); + + if (ret < 0) { + printk(KERN_EMERG "%s(): power collapse EARLY_EXIT " + "timed out waiting for Modem's response\n", __func__); + msm_pm_timeout(); + } + + if (ret == 1) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_poll_state detected Modem reset\n", + __func__); + } + + /* DEM Master == RESET or PWRC_EARLY_EXIT */ + + ret = -EAGAIN; + +power_collapse_restore_gpio_bail: + msm_gpio_exit_sleep(); + + /* Enter RUN */ + smsm_change_state(SMSM_APPS_DEM, + DEM_SLAVE_SMSM_PWRC | DEM_SLAVE_SMSM_PWRC_SUSPEND | + DEM_SLAVE_SMSM_PWRC_EARLY_EXIT, DEM_SLAVE_SMSM_RUN); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): RUN"); + + if (collapsed) + smd_sleep_exit(); + +power_collapse_bail: + return ret; +} + +/* + * Apps-sleep the Apps processor. This function execute the handshake + * protocol with Modem. + * + * Return value: + * -ENOSYS: function not implemented yet + */ +static int msm_pm_apps_sleep(uint32_t sleep_delay, uint32_t sleep_limit) +{ + return -ENOSYS; +} + +/* + * Bring the Apps processor to SWFI. + * + * Return value: + * -EIO: could not ramp Apps processor clock + * 0: success + */ +static int msm_pm_swfi(bool ramp_acpu) +{ + unsigned long saved_acpuclk_rate = 0; + + if (ramp_acpu) { + saved_acpuclk_rate = acpuclk_wait_for_irq(); + MSM_PM_DPRINTK(MSM_PM_DEBUG_CLOCK, KERN_INFO, + "%s(): change clock rate (old rate = %lu)\n", __func__, + saved_acpuclk_rate); + + if (!saved_acpuclk_rate) + return -EIO; + } + + msm_pm_config_hw_before_swfi(); + msm_arch_idle(); + + if (ramp_acpu) { + MSM_PM_DPRINTK(MSM_PM_DEBUG_CLOCK, KERN_INFO, + "%s(): restore clock rate to %lu\n", __func__, + saved_acpuclk_rate); + if (acpuclk_set_rate(saved_acpuclk_rate, SETRATE_SWFI) < 0) + printk(KERN_ERR + "%s(): failed to restore clock rate(%lu)\n", + __func__, saved_acpuclk_rate); + } + + return 0; +} + + +/****************************************************************************** + * External Idle/Suspend Functions + *****************************************************************************/ + +/* + * Put CPU in low power mode. + */ +void arch_idle(void) +{ + bool allow[MSM_PM_SLEEP_MODE_NR]; + uint32_t sleep_limit = SLEEP_LIMIT_NONE; + + int latency_qos; + int64_t timer_expiration; + + int low_power; + int ret; + int i; + +#ifdef CONFIG_MSM_IDLE_STATS + DECLARE_BITMAP(clk_ids, NR_CLKS); + int64_t t1; + static int64_t t2; + int exit_stat; +#endif /* CONFIG_MSM_IDLE_STATS */ + + if (!atomic_read(&msm_pm_init_done)) + return; + + latency_qos = pm_qos_requirement(PM_QOS_CPU_DMA_LATENCY); + timer_expiration = 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, timer_expiration); +#endif /* CONFIG_MSM_IDLE_STATS */ + + for (i = 0; i < ARRAY_SIZE(allow); i++) + allow[i] = true; + + switch (msm_pm_idle_sleep_mode) { + case MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT: + allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT] = + false; + /* fall through */ + case MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT: + allow[MSM_PM_SLEEP_MODE_APPS_SLEEP] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_APPS_SLEEP: + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = false; + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND: + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: + break; + default: + printk(KERN_ERR "idle sleep mode is invalid: %d\n", + msm_pm_idle_sleep_mode); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif /* CONFIG_MSM_IDLE_STATS */ + low_power = 0; + goto arch_idle_exit; + } + + if ((timer_expiration < msm_pm_idle_sleep_min_time) || + !msm_irq_idle_sleep_allowed()) { + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false; + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = false; + allow[MSM_PM_SLEEP_MODE_APPS_SLEEP] = false; + } + + for (i = 0; i < ARRAY_SIZE(allow); i++) { + struct msm_pm_platform_data *mode = &msm_pm_modes[i]; + if (!mode->supported || !mode->idle_enabled || + mode->latency >= latency_qos || + mode->residency * 1000ULL >= timer_expiration) + allow[i] = false; + } + +#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 /* CONFIG_MSM_IDLE_STATS */ + +#ifdef CONFIG_CLOCK_BASED_SLEEP_LIMIT + if (ret) + sleep_limit = SLEEP_LIMIT_NO_TCXO_SHUTDOWN; +#endif + + MSM_PM_DPRINTK(MSM_PM_DEBUG_IDLE, KERN_INFO, + "%s(): latency qos %d, next timer %lld, sleep limit %u\n", + __func__, latency_qos, timer_expiration, sleep_limit); + + for (i = 0; i < ARRAY_SIZE(allow); i++) + MSM_PM_DPRINTK(MSM_PM_DEBUG_IDLE, KERN_INFO, + "%s(): allow %s: %d\n", __func__, + msm_pm_sleep_mode_labels[i], (int)allow[i]); + + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] || + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]) { + uint32_t sleep_delay; + + sleep_delay = (uint32_t) msm_pm_convert_and_cap_time( + timer_expiration, MSM_PM_SLEEP_TICK_LIMIT); + if (sleep_delay == 0) /* 0 would mean infinite time */ + sleep_delay = 1; + + if (!allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]) + sleep_limit = SLEEP_LIMIT_NO_TCXO_SHUTDOWN; + + ret = msm_pm_power_collapse(true, sleep_delay, sleep_limit); + low_power = (ret != -EBUSY && ret != -ETIMEDOUT); + +#ifdef CONFIG_MSM_IDLE_STATS + 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); + } +#endif /* CONFIG_MSM_IDLE_STATS */ + } else if (allow[MSM_PM_SLEEP_MODE_APPS_SLEEP]) { + uint32_t sleep_delay; + + sleep_delay = (uint32_t) msm_pm_convert_and_cap_time( + timer_expiration, MSM_PM_SLEEP_TICK_LIMIT); + if (sleep_delay == 0) /* 0 would mean infinite time */ + sleep_delay = 1; + + ret = msm_pm_apps_sleep(sleep_delay, sleep_limit); + low_power = 0; + +#ifdef CONFIG_MSM_IDLE_STATS + if (ret) + exit_stat = MSM_PM_STAT_IDLE_FAILED_SLEEP; + else + exit_stat = MSM_PM_STAT_IDLE_SLEEP; +#endif /* CONFIG_MSM_IDLE_STATS */ + } else if (allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT]) { + ret = msm_pm_swfi(true); + if (ret) + while (!msm_irq_pending()) + udelay(1); + low_power = 0; +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = ret ? MSM_PM_STAT_IDLE_SPIN : MSM_PM_STAT_IDLE_WFI; +#endif /* CONFIG_MSM_IDLE_STATS */ + } else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) { + msm_pm_swfi(false); + low_power = 0; +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_WFI; +#endif /* CONFIG_MSM_IDLE_STATS */ + } else { + while (!msm_irq_pending()) + udelay(1); + low_power = 0; +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif /* CONFIG_MSM_IDLE_STATS */ + } + +arch_idle_exit: + 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 /* CONFIG_MSM_IDLE_STATS */ +} + +/* + * Suspend the Apps processor. + * + * Return value: + * -EAGAIN: modem reset occurred or early exit from suspend + * -EBUSY: modem not ready for our suspend + * -EINVAL: invalid sleep mode + * -EIO: could not ramp Apps processor clock + * -ETIMEDOUT: timed out waiting for modem's handshake + * 0: success + */ +static int msm_pm_enter(suspend_state_t state) +{ + bool allow[MSM_PM_SLEEP_MODE_NR]; + uint32_t sleep_limit = SLEEP_LIMIT_NONE; + int ret; + int i; + +#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 + if (ret) + sleep_limit = SLEEP_LIMIT_NO_TCXO_SHUTDOWN; +#endif + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND, KERN_INFO, + "%s(): sleep limit %u\n", __func__, sleep_limit); + + for (i = 0; i < ARRAY_SIZE(allow); i++) + allow[i] = true; + + switch (msm_pm_sleep_mode) { + case MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT: + allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT] = + false; + /* fall through */ + case MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT: + allow[MSM_PM_SLEEP_MODE_APPS_SLEEP] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_APPS_SLEEP: + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = false; + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND: + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: + break; + default: + printk(KERN_ERR "suspend sleep mode is invalid: %d\n", + msm_pm_sleep_mode); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(allow); i++) { + struct msm_pm_platform_data *mode = &msm_pm_modes[i]; + if (!mode->supported || !mode->suspend_enabled) + allow[i] = false; + } + + ret = 0; + + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] || + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]) { +#ifdef CONFIG_MSM_IDLE_STATS + enum msm_pm_time_stats_id id; + int64_t end_time; +#endif + +#ifdef CONFIG_MSM_SLEEP_TIME_OVERRIDE + if (msm_pm_sleep_time_override > 0) { + int64_t ns; + 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 + if (!allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]) + sleep_limit = SLEEP_LIMIT_NO_TCXO_SHUTDOWN; + + ret = msm_pm_power_collapse( + false, msm_pm_max_sleep_time, sleep_limit); + +#ifdef CONFIG_MSM_IDLE_STATS + 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 + } else if (allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT]) { + ret = msm_pm_swfi(true); + if (ret) + while (!msm_irq_pending()) + udelay(1); + } else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) { + msm_pm_swfi(false); + } + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND, KERN_INFO, + "%s(): return %d\n", __func__, ret); + + return ret; +} + +static struct platform_suspend_ops msm_pm_ops = { + .enter = msm_pm_enter, + .valid = suspend_valid_only_mem, +}; + + +/****************************************************************************** + * Restart Definitions + *****************************************************************************/ + +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, +}; + + +/****************************************************************************** + * + *****************************************************************************/ + +/* + * Initialize the power management subsystem. + * + * Return value: + * -ENODEV: initialization failed + * 0: success + */ +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; + register_reboot_notifier(&msm_reboot_notifier); + + msm_pm_smem_data = smem_alloc(SMEM_APPS_DEM_SLAVE_DATA, + sizeof(*msm_pm_smem_data)); + if (msm_pm_smem_data == NULL) { + printk(KERN_ERR "%s: failed to get smsm_data\n", __func__); + return -ENODEV; + } + +#ifdef 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 = (uint32_t *) PAGE_OFFSET; +#else + msm_pm_reset_vector = ioremap(0, PAGE_SIZE); + if (msm_pm_reset_vector == NULL) { + printk(KERN_ERR "%s: failed to map reset vector\n", __func__); + return -ENODEV; + } +#endif /* CONFIG_ARCH_MSM_SCORPION */ + + ret = msm_timer_init_time_sync(); + if (ret) + return ret; + + ret = smsm_change_intr_mask(SMSM_POWER_MASTER_DEM, 0xFFFFFFFF, 0); + if (ret) { + printk(KERN_ERR "%s: failed to clear interrupt mask, %d\n", + __func__, ret); + return ret; + } + + BUG_ON(msm_pm_modes == NULL); + + atomic_set(&msm_pm_init_done, 1); + suspend_set_ops(&msm_pm_ops); + + msm_pm_mode_sysfs_add(); +#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; +} + +late_initcall(msm_pm_init); |