blob: 6b026ace8974ca41b713085df9d56c59e90f25fd [file] [log] [blame]
/* arch/arm/mach-msm/pm2.c
*
* MSM Power Management Routines
*
* Copyright (C) 2007 Google, Inc.
* Copyright (c) 2008-2012 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 <linux/memory.h>
#ifdef CONFIG_HAS_WAKELOCK
#include <linux/wakelock.h>
#endif
#include <mach/msm_iomap.h>
#include <mach/system.h>
#ifdef CONFIG_CPU_V7
#include <asm/pgtable.h>
#include <asm/pgalloc.h>
#endif
#ifdef CONFIG_CACHE_L2X0
#include <asm/hardware/cache-l2x0.h>
#endif
#ifdef CONFIG_VFP
#include <asm/vfp.h>
#endif
#ifdef CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_DEEP_POWER_DOWN
#include <mach/msm_migrate_pages.h>
#endif
#include <mach/socinfo.h>
#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"
#include "spm.h"
#include "sirc.h"
#include "pm-boot.h"
/******************************************************************************
* Debug Definitions
*****************************************************************************/
enum {
MSM_PM_DEBUG_SUSPEND = BIT(0),
MSM_PM_DEBUG_POWER_COLLAPSE = BIT(1),
MSM_PM_DEBUG_STATE = BIT(2),
MSM_PM_DEBUG_CLOCK = BIT(3),
MSM_PM_DEBUG_RESET_VECTOR = BIT(4),
MSM_PM_DEBUG_SMSM_STATE = BIT(5),
MSM_PM_DEBUG_IDLE = BIT(6),
MSM_PM_DEBUG_HOTPLUG = BIT(7),
};
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, \
__raw_readl(APPS_CLK_SLEEP_EN), \
__raw_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_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
);
enum {
MSM_PM_MODE_ATTR_SUSPEND,
MSM_PM_MODE_ATTR_IDLE,
MSM_PM_MODE_ATTR_LATENCY,
MSM_PM_MODE_ATTR_RESIDENCY,
MSM_PM_MODE_ATTR_NR,
};
static char *msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_NR] = {
[MSM_PM_MODE_ATTR_SUSPEND] = "suspend_enabled",
[MSM_PM_MODE_ATTR_IDLE] = "idle_enabled",
[MSM_PM_MODE_ATTR_LATENCY] = "latency",
[MSM_PM_MODE_ATTR_RESIDENCY] = "residency",
};
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_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",
[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] =
"standalone_power_collapse",
};
static struct msm_pm_platform_data *msm_pm_modes;
struct msm_pm_kobj_attribute {
unsigned int cpu;
struct kobj_attribute ka;
};
#define GET_CPU_OF_ATTR(attr) \
(container_of(attr, struct msm_pm_kobj_attribute, ka)->cpu)
struct msm_pm_sysfs_sleep_mode {
struct kobject *kobj;
struct attribute_group attr_group;
struct attribute *attrs[MSM_PM_MODE_ATTR_NR + 1];
struct msm_pm_kobj_attribute kas[MSM_PM_MODE_ATTR_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;
unsigned int cpu;
struct msm_pm_platform_data *mode;
if (msm_pm_sleep_mode_labels[i] == NULL)
continue;
if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i]))
continue;
cpu = GET_CPU_OF_ATTR(attr);
mode = &msm_pm_modes[MSM_PM_MODE(cpu, i)];
if (!strcmp(attr->attr.name,
msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_SUSPEND])) {
u32 arg = mode->suspend_enabled;
kp.arg = &arg;
ret = param_get_ulong(buf, &kp);
} else if (!strcmp(attr->attr.name,
msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_IDLE])) {
u32 arg = mode->idle_enabled;
kp.arg = &arg;
ret = param_get_ulong(buf, &kp);
} else if (!strcmp(attr->attr.name,
msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_LATENCY])) {
u32 arg = mode->latency;
kp.arg = &arg;
ret = param_get_ulong(buf, &kp);
} else if (!strcmp(attr->attr.name,
msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_RESIDENCY])) {
u32 arg = mode->residency;
kp.arg = &arg;
ret = param_get_ulong(buf, &kp);
}
break;
}
if (ret > 0) {
strlcat(buf, "\n", PAGE_SIZE);
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;
unsigned int cpu;
struct msm_pm_platform_data *mode;
if (msm_pm_sleep_mode_labels[i] == NULL)
continue;
if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i]))
continue;
cpu = GET_CPU_OF_ATTR(attr);
mode = &msm_pm_modes[MSM_PM_MODE(cpu, i)];
if (!strcmp(attr->attr.name,
msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_SUSPEND])) {
kp.arg = &mode->suspend_enabled;
ret = param_set_byte(buf, &kp);
} else if (!strcmp(attr->attr.name,
msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_IDLE])) {
kp.arg = &mode->idle_enabled;
ret = param_set_byte(buf, &kp);
} else if (!strcmp(attr->attr.name,
msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_LATENCY])) {
kp.arg = &mode->latency;
ret = param_set_ulong(buf, &kp);
} else if (!strcmp(attr->attr.name,
msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_RESIDENCY])) {
kp.arg = &mode->residency;
ret = param_set_ulong(buf, &kp);
}
break;
}
return ret ? ret : count;
}
/* Add sysfs entries for one cpu. */
static int __init msm_pm_mode_sysfs_add_cpu(
unsigned int cpu, struct kobject *modes_kobj)
{
char cpu_name[8];
struct kobject *cpu_kobj;
struct msm_pm_sysfs_sleep_mode *mode = NULL;
int i, j, k;
int ret;
snprintf(cpu_name, sizeof(cpu_name), "cpu%u", cpu);
cpu_kobj = kobject_create_and_add(cpu_name, modes_kobj);
if (!cpu_kobj) {
pr_err("%s: cannot create %s kobject\n", __func__, cpu_name);
ret = -ENOMEM;
goto mode_sysfs_add_cpu_exit;
}
for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) {
int idx = MSM_PM_MODE(cpu, i);
if ((!msm_pm_modes[idx].suspend_supported) &&
(!msm_pm_modes[idx].idle_supported))
continue;
mode = kzalloc(sizeof(*mode), GFP_KERNEL);
if (!mode) {
pr_err("%s: cannot allocate memory for attributes\n",
__func__);
ret = -ENOMEM;
goto mode_sysfs_add_cpu_exit;
}
mode->kobj = kobject_create_and_add(
msm_pm_sleep_mode_labels[i], cpu_kobj);
if (!mode->kobj) {
pr_err("%s: cannot create kobject\n", __func__);
ret = -ENOMEM;
goto mode_sysfs_add_cpu_exit;
}
for (k = 0, j = 0; k < MSM_PM_MODE_ATTR_NR; k++) {
if ((k == MSM_PM_MODE_ATTR_IDLE) &&
!msm_pm_modes[idx].idle_supported)
continue;
if ((k == MSM_PM_MODE_ATTR_SUSPEND) &&
!msm_pm_modes[idx].suspend_supported)
continue;
mode->kas[j].cpu = cpu;
mode->kas[j].ka.attr.mode = 0644;
mode->kas[j].ka.show = msm_pm_mode_attr_show;
mode->kas[j].ka.store = msm_pm_mode_attr_store;
mode->kas[j].ka.attr.name = msm_pm_mode_attr_labels[k];
mode->attrs[j] = &mode->kas[j].ka.attr;
j++;
}
mode->attrs[j] = NULL;
mode->attr_group.attrs = mode->attrs;
ret = sysfs_create_group(mode->kobj, &mode->attr_group);
if (ret) {
printk(KERN_ERR
"%s: cannot create kobject attribute group\n",
__func__);
goto mode_sysfs_add_cpu_exit;
}
}
ret = 0;
mode_sysfs_add_cpu_exit:
if (ret) {
if (mode && mode->kobj)
kobject_del(mode->kobj);
kfree(mode);
}
return ret;
}
/*
* 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;
unsigned int cpu;
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_exit;
}
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_exit;
}
for_each_possible_cpu(cpu) {
ret = msm_pm_mode_sysfs_add_cpu(cpu, modes_kobj);
if (ret)
goto mode_sysfs_add_exit;
}
ret = 0;
mode_sysfs_add_exit:
return ret;
}
void __init msm_pm_set_platform_data(
struct msm_pm_platform_data *data, int count)
{
BUG_ON(MSM_PM_SLEEP_MODE_NR * num_possible_cpus() > count);
msm_pm_modes = data;
}
/******************************************************************************
* Sleep Limitations
*****************************************************************************/
enum {
SLEEP_LIMIT_NONE = 0,
SLEEP_LIMIT_NO_TCXO_SHUTDOWN = 2,
SLEEP_LIMIT_MASK = 0x03,
};
#ifdef CONFIG_MSM_MEMORY_LOW_POWER_MODE
enum {
SLEEP_RESOURCE_MEMORY_BIT0 = 0x0200,
SLEEP_RESOURCE_MEMORY_BIT1 = 0x0010,
};
#endif
/******************************************************************************
* Configure Hardware for Power Down/Up
*****************************************************************************/
#if defined(CONFIG_ARCH_MSM7X30)
#define APPS_CLK_SLEEP_EN (MSM_APCS_GCC_BASE + 0x020)
#define APPS_PWRDOWN (MSM_ACC0_BASE + 0x01c)
#define APPS_SECOP (MSM_TCSR_BASE + 0x038)
#else /* defined(CONFIG_ARCH_MSM7X30) */
#define APPS_CLK_SLEEP_EN (MSM_CSR_BASE + 0x11c)
#define APPS_PWRDOWN (MSM_CSR_BASE + 0x440)
#define APPS_STANDBY_CTL (MSM_CSR_BASE + 0x108)
#endif /* defined(CONFIG_ARCH_MSM7X30) */
/*
* Configure hardware registers in preparation for Apps power down.
*/
static void msm_pm_config_hw_before_power_down(void)
{
#if defined(CONFIG_ARCH_MSM7X30)
__raw_writel(1, APPS_PWRDOWN);
mb();
__raw_writel(4, APPS_SECOP);
mb();
#elif defined(CONFIG_ARCH_MSM7X27)
__raw_writel(0x1f, APPS_CLK_SLEEP_EN);
mb();
__raw_writel(1, APPS_PWRDOWN);
mb();
#elif defined(CONFIG_ARCH_MSM7x27A)
__raw_writel(0x7, APPS_CLK_SLEEP_EN);
mb();
__raw_writel(1, APPS_PWRDOWN);
mb();
#else
__raw_writel(0x1f, APPS_CLK_SLEEP_EN);
mb();
__raw_writel(1, APPS_PWRDOWN);
mb();
__raw_writel(0, APPS_STANDBY_CTL);
mb();
#endif
}
/*
* Clear hardware registers after Apps powers up.
*/
static void msm_pm_config_hw_after_power_up(void)
{
#if defined(CONFIG_ARCH_MSM7X30)
__raw_writel(0, APPS_SECOP);
mb();
__raw_writel(0, APPS_PWRDOWN);
mb();
msm_spm_reinit();
#elif defined(CONFIG_ARCH_MSM7x27A)
__raw_writel(0, APPS_PWRDOWN);
mb();
__raw_writel(0, APPS_CLK_SLEEP_EN);
mb();
#else
__raw_writel(0, APPS_PWRDOWN);
mb();
__raw_writel(0, APPS_CLK_SLEEP_EN);
mb();
#endif
}
/*
* Configure hardware registers in preparation for SWFI.
*/
static void msm_pm_config_hw_before_swfi(void)
{
#if defined(CONFIG_ARCH_QSD8X50)
__raw_writel(0x1f, APPS_CLK_SLEEP_EN);
mb();
#elif defined(CONFIG_ARCH_MSM7X27)
__raw_writel(0x0f, APPS_CLK_SLEEP_EN);
mb();
#elif defined(CONFIG_ARCH_MSM7X27A)
__raw_writel(0x7, APPS_CLK_SLEEP_EN);
mb();
#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 < 50000; 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;
}
udelay(50);
}
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_STANDALONE_POWER_COLLAPSE,
MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE,
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
};
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;
};
struct msm_pm_cpu_time_stats {
struct msm_pm_time_stats stats[MSM_PM_STAT_COUNT];
};
static DEFINE_PER_CPU_SHARED_ALIGNED(
struct msm_pm_cpu_time_stats, msm_pm_stats);
static uint32_t msm_pm_sleep_limit = SLEEP_LIMIT_NONE;
static DEFINE_SPINLOCK(msm_pm_stats_lock);
/*
* 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)
{
unsigned long flags;
struct msm_pm_time_stats *stats;
int i;
int64_t bt;
spin_lock_irqsave(&msm_pm_stats_lock, flags);
stats = __get_cpu_var(msm_pm_stats).stats;
stats[id].total_time += t;
stats[id].count++;
bt = t;
do_div(bt, 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;
if (i >= CONFIG_MSM_IDLE_STATS_BUCKET_COUNT)
i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
stats[id].bucket[i]++;
if (t < stats[id].min_time[i] || !stats[id].max_time[i])
stats[id].min_time[i] = t;
if (t > stats[id].max_time[i])
stats[id].max_time[i] = t;
spin_unlock_irqrestore(&msm_pm_stats_lock, flags);
}
/*
* 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)
{
unsigned int cpu = off / MSM_PM_STAT_COUNT;
int id = off % MSM_PM_STAT_COUNT;
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_MASK) ==
SLEEP_LIMIT_NONE)
SNPRINTF(p, count, "for TCXO shutdown\n\n");
else
SNPRINTF(p, count, "against TCXO shutdown\n\n");
}
if (cpu < num_possible_cpus()) {
unsigned long flags;
struct msm_pm_time_stats *stats;
int i;
int64_t bucket_time;
int64_t s;
uint32_t ns;
spin_lock_irqsave(&msm_pm_stats_lock, flags);
stats = per_cpu(msm_pm_stats, cpu).stats;
s = stats[id].total_time;
ns = do_div(s, NSEC_PER_SEC);
SNPRINTF(p, count,
"[cpu %u] %s:\n"
" count: %7d\n"
" total_time: %lld.%09u\n",
cpu, stats[id].name,
stats[id].count,
s, ns);
bucket_time = stats[id].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, stats[id].bucket[i],
stats[id].min_time[i],
stats[id].max_time[i]);
bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT;
}
SNPRINTF(p, count, " >=%6lld.%09u: %7d (%lld-%lld)\n",
s, ns, stats[id].bucket[i],
stats[id].min_time[i],
stats[id].max_time[i]);
*start = (char *) 1;
*eof = (off + 1 >= MSM_PM_STAT_COUNT * num_possible_cpus());
spin_unlock_irqrestore(&msm_pm_stats_lock, flags);
}
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;
unsigned int cpu;
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;
}
spin_lock_irqsave(&msm_pm_stats_lock, flags);
for_each_possible_cpu(cpu) {
struct msm_pm_time_stats *stats;
int i;
stats = per_cpu(msm_pm_stats, cpu).stats;
for (i = 0; i < MSM_PM_STAT_COUNT; i++) {
memset(stats[i].bucket,
0, sizeof(stats[i].bucket));
memset(stats[i].min_time,
0, sizeof(stats[i].min_time));
memset(stats[i].max_time,
0, sizeof(stats[i].max_time));
stats[i].count = 0;
stats[i].total_time = 0;
}
}
msm_pm_sleep_limit = SLEEP_LIMIT_NONE;
spin_unlock_irqrestore(&msm_pm_stats_lock, 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 atomic_t msm_pm_init_done = ATOMIC_INIT(0);
static int msm_pm_modem_busy(void)
{
if (!(smsm_get_state(SMSM_POWER_MASTER_DEM) & DEM_MASTER_SMSM_READY)) {
MSM_PM_DPRINTK(MSM_PM_DEBUG_POWER_COLLAPSE,
KERN_INFO, "%s(): master not ready\n", __func__);
return -EBUSY;
}
return 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;
int collapsed = 0;
int ret;
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));
if (cpu_is_msm8625()) {
/* Program the SPM */
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_POWER_COLLAPSE,
false);
WARN_ON(ret);
}
msm_irq_enter_sleep1(true, from_idle, &msm_pm_smem_data->irq_mask);
msm_sirc_enter_sleep();
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;
}
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;
}
msm_pm_boot_config_before_pc(smp_processor_id(),
virt_to_phys(msm_pm_collapse_exit));
#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_boot_config_after_pc(smp_processor_id());
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(smp_processor_id(), saved_acpuclk_rate,
SETRATE_PC) < 0)
printk(KERN_ERR "%s(): failed to restore clock rate(%lu)\n",
__func__, saved_acpuclk_rate);
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();
msm_sirc_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();
if (cpu_is_msm8625()) {
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING,
false);
WARN_ON(ret);
}
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();
msm_sirc_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:
if (cpu_is_msm8625()) {
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING,
false);
WARN_ON(ret);
}
return ret;
}
/*
* Power collapse the Apps processor without involving Modem.
*
* Return value:
* 0: success
*/
static int msm_pm_power_collapse_standalone(bool from_idle)
{
int collapsed = 0;
int ret;
void *entry;
MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE,
KERN_INFO, "%s()\n", __func__);
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_POWER_COLLAPSE, false);
WARN_ON(ret);
entry = (!smp_processor_id() || from_idle) ?
msm_pm_collapse_exit : msm_secondary_startup;
msm_pm_boot_config_before_pc(smp_processor_id(),
virt_to_phys(entry));
#ifdef CONFIG_VFP
vfp_flush_context();
#endif
#ifdef CONFIG_CACHE_L2X0
if (!cpu_is_msm8625())
l2x0_suspend();
#endif
collapsed = msm_pm_collapse();
#ifdef CONFIG_CACHE_L2X0
if (!cpu_is_msm8625())
l2x0_resume(collapsed);
#endif
msm_pm_boot_config_after_pc(smp_processor_id());
if (collapsed) {
#ifdef CONFIG_VFP
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);
ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false);
WARN_ON(ret);
return 0;
}
/*
* 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;
}
if (!cpu_is_msm8625())
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(smp_processor_id(), 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;
unsigned int cpu;
#ifdef CONFIG_MSM_IDLE_STATS
int64_t t1;
static int64_t t2;
int exit_stat;
#endif
if (!atomic_read(&msm_pm_init_done))
return;
cpu = smp_processor_id();
latency_qos = pm_qos_request(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);
exit_stat = MSM_PM_STAT_IDLE_SPIN;
low_power = 0;
#endif
for (i = 0; i < ARRAY_SIZE(allow); i++)
allow[i] = true;
if (num_online_cpus() > 1 ||
(timer_expiration < msm_pm_idle_sleep_min_time) ||
#ifdef CONFIG_HAS_WAKELOCK
has_wake_lock(WAKE_LOCK_IDLE) ||
#endif
!msm_irq_idle_sleep_allowed()) {
allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false;
allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = false;
}
for (i = 0; i < ARRAY_SIZE(allow); i++) {
struct msm_pm_platform_data *mode =
&msm_pm_modes[MSM_PM_MODE(cpu, i)];
if (!mode->idle_supported || !mode->idle_enabled ||
mode->latency >= latency_qos ||
mode->residency * 1000ULL >= timer_expiration)
allow[i] = false;
}
if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] ||
allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]) {
uint32_t wait_us = CONFIG_MSM_IDLE_WAIT_ON_MODEM;
while (msm_pm_modem_busy() && wait_us) {
if (wait_us > 100) {
udelay(100);
wait_us -= 100;
} else {
udelay(wait_us);
wait_us = 0;
}
}
if (msm_pm_modem_busy()) {
allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false;
allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]
= false;
}
}
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;
#if defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_IDLE_ACTIVE)
sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT1;
#elif defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_IDLE_RETENTION)
sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT0;
#endif
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;
}
#endif
} else if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) {
ret = msm_pm_power_collapse_standalone(true);
low_power = 0;
#ifdef CONFIG_MSM_IDLE_STATS
exit_stat = ret ?
MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE :
MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE;
#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);
low_power = 0;
#ifdef CONFIG_MSM_IDLE_STATS
exit_stat = ret ? MSM_PM_STAT_IDLE_SPIN : MSM_PM_STAT_IDLE_WFI;
#endif
} 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
} else {
while (!msm_irq_pending())
udelay(1);
low_power = 0;
#ifdef CONFIG_MSM_IDLE_STATS
exit_stat = MSM_PM_STAT_IDLE_SPIN;
#endif
}
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
}
/*
* Suspend the Apps processor.
*
* Return value:
* -EPERM: Suspend happened by a not permitted core
* -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 = -EPERM;
int i;
#ifdef CONFIG_MSM_IDLE_STATS
int64_t period = 0;
int64_t time = 0;
#endif
/* Must executed by CORE0 */
if (smp_processor_id()) {
__WARN();
goto suspend_exit;
}
#ifdef CONFIG_MSM_IDLE_STATS
time = msm_timer_get_sclk_time(&period);
#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;
for (i = 0; i < ARRAY_SIZE(allow); i++) {
struct msm_pm_platform_data *mode;
mode = &msm_pm_modes[MSM_PM_MODE(0, i)];
if (!mode->suspend_supported || !mode->suspend_enabled)
allow[i] = false;
}
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
clock_debug_print_enabled();
#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;
#if defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_ACTIVE)
sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT1;
#elif defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_RETENTION)
sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT0;
#elif defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_DEEP_POWER_DOWN)
if (get_msm_migrate_pages_status() != MEM_OFFLINE)
sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT0;
#endif
for (i = 0; i < 30 && msm_pm_modem_busy(); i++)
udelay(500);
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;
}
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_POWER_COLLAPSE_STANDALONE]) {
ret = msm_pm_power_collapse_standalone(false);
} 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);
}
suspend_exit:
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,
};
/* Hotplug the "non boot" CPU's and put
* the cores into low power mode
*/
void msm_pm_cpu_enter_lowpower(unsigned int cpu)
{
bool allow[MSM_PM_SLEEP_MODE_NR];
int i;
for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) {
struct msm_pm_platform_data *mode;
mode = &msm_pm_modes[MSM_PM_MODE(cpu, i)];
allow[i] = mode->suspend_supported && mode->suspend_enabled;
}
MSM_PM_DPRINTK(MSM_PM_DEBUG_HOTPLUG, KERN_INFO,
"CPU%u: %s: shutting down cpu\n", cpu, __func__);
if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) {
msm_pm_power_collapse_standalone(false);
} else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) {
msm_pm_swfi(false);
} else {
MSM_PM_DPRINTK(MSM_PM_DEBUG_HOTPLUG, KERN_INFO,
"CPU%u: %s: shutting down failed!!!\n", cpu, __func__);
}
}
/******************************************************************************
* Restart Definitions
*****************************************************************************/
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,
};
/*
* 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;
unsigned int cpu;
#endif
int ret;
#ifdef CONFIG_CPU_V7
pgd_t *pc_pgd;
pmd_t *pmd;
unsigned long pmdval;
if (cpu_is_msm8625())
return 0;
/* Page table for cores to come back up safely. */
pc_pgd = pgd_alloc(&init_mm);
if (!pc_pgd)
return -ENOMEM;
pmd = pmd_offset(pc_pgd +
pgd_index(virt_to_phys(msm_pm_collapse_exit)),
virt_to_phys(msm_pm_collapse_exit));
pmdval = (virt_to_phys(msm_pm_collapse_exit) & PGDIR_MASK) |
PMD_TYPE_SECT | PMD_SECT_AP_WRITE;
pmd[0] = __pmd(pmdval);
pmd[1] = __pmd(pmdval + (1 << (PGDIR_SHIFT - 1)));
/* It is remotely possible that the code in msm_pm_collapse_exit()
* which turns on the MMU with this mapping is in the
* next even-numbered megabyte beyond the
* start of msm_pm_collapse_exit().
* Map this megabyte in as well.
*/
pmd[2] = __pmd(pmdval + (2 << (PGDIR_SHIFT - 1)));
flush_pmd_entry(pmd);
msm_pm_pc_pgd = virt_to_phys(pc_pgd);
#endif
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;
}
ret = msm_timer_init_time_sync(msm_pm_timeout);
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;
}
#ifdef CONFIG_MSM_MEMORY_LOW_POWER_MODE
/* The wakeup_reason field is overloaded during initialization time
to signal Modem that Apps will control the low power modes of
the memory.
*/
msm_pm_smem_data->wakeup_reason = 1;
smsm_change_state(SMSM_APPS_DEM, 0, DEM_SLAVE_SMSM_RUN);
#endif
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
for_each_possible_cpu(cpu) {
struct msm_pm_time_stats *stats =
per_cpu(msm_pm_stats, cpu).stats;
stats[MSM_PM_STAT_REQUESTED_IDLE].name = "idle-request";
stats[MSM_PM_STAT_REQUESTED_IDLE].first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
stats[MSM_PM_STAT_IDLE_SPIN].name = "idle-spin";
stats[MSM_PM_STAT_IDLE_SPIN].first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
stats[MSM_PM_STAT_IDLE_WFI].name = "idle-wfi";
stats[MSM_PM_STAT_IDLE_WFI].first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
stats[MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE].name =
"idle-standalone-power-collapse";
stats[MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE].
first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
stats[MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE].name =
"idle-failed-standalone-power-collapse";
stats[MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE].
first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
stats[MSM_PM_STAT_IDLE_POWER_COLLAPSE].name =
"idle-power-collapse";
stats[MSM_PM_STAT_IDLE_POWER_COLLAPSE].first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
stats[MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].name =
"idle-failed-power-collapse";
stats[MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].
first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
stats[MSM_PM_STAT_SUSPEND].name = "suspend";
stats[MSM_PM_STAT_SUSPEND].first_bucket_time =
CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET;
stats[MSM_PM_STAT_FAILED_SUSPEND].name = "failed-suspend";
stats[MSM_PM_STAT_FAILED_SUSPEND].first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
stats[MSM_PM_STAT_NOT_IDLE].name = "not-idle";
stats[MSM_PM_STAT_NOT_IDLE].first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
}
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_sync(msm_pm_init);