/* arch/arm/mach-msm/pm.c
 *
 * MSM Power Management Routines
 *
 * Copyright (C) 2007 Google, Inc.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/console.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/pm.h>
#include <linux/proc_fs.h>
#include <linux/suspend.h>
#include <linux/reboot.h>
#include <linux/earlysuspend.h>
#include <mach/msm_iomap.h>
#include <mach/system.h>
#include <asm/io.h>
#ifdef CONFIG_VFP
#include <asm/vfp.h>
#endif

#ifdef CONFIG_CACHE_L2X0
#include <asm/hardware/cache-l2x0.h>
#endif

#include "smd_private.h"
#include "smd_rpcrouter.h"
#include "acpuclock.h"
#include "proc_comm.h"
#include "clock.h"
#include "spm.h"
#ifdef CONFIG_HAS_WAKELOCK
#include <linux/wakelock.h>
#endif

enum {
	MSM_PM_DEBUG_SUSPEND = 1U << 0,
	MSM_PM_DEBUG_POWER_COLLAPSE = 1U << 1,
	MSM_PM_DEBUG_STATE = 1U << 2,
	MSM_PM_DEBUG_CLOCK = 1U << 3,
	MSM_PM_DEBUG_RESET_VECTOR = 1U << 4,
	MSM_PM_DEBUG_SMSM_STATE = 1U << 5,
	MSM_PM_DEBUG_IDLE = 1U << 6,
	MSM_PM_DEBUG_SLEEP_LIMIT = 1U << 7,
	MSM_PM_DEBUG_WAKEUP_REASON = 1U << 8,
};
static int msm_pm_debug_mask = MSM_PM_DEBUG_SLEEP_LIMIT | MSM_PM_DEBUG_WAKEUP_REASON;
module_param_named(debug_mask, msm_pm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);

enum {
	MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND,
	MSM_PM_SLEEP_MODE_POWER_COLLAPSE,
	MSM_PM_SLEEP_MODE_APPS_SLEEP,
	MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT,
	MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT,
};
static int msm_pm_sleep_mode = CONFIG_MSM7X00A_SLEEP_MODE;
module_param_named(sleep_mode, msm_pm_sleep_mode, int, S_IRUGO | S_IWUSR | S_IWGRP);
static int msm_pm_idle_sleep_mode = CONFIG_MSM7X00A_IDLE_SLEEP_MODE;
module_param_named(idle_sleep_mode, msm_pm_idle_sleep_mode, int, S_IRUGO | S_IWUSR | S_IWGRP);
static int msm_pm_idle_sleep_min_time = CONFIG_MSM7X00A_IDLE_SLEEP_MIN_TIME;
module_param_named(idle_sleep_min_time, msm_pm_idle_sleep_min_time, int, S_IRUGO | S_IWUSR | S_IWGRP);
static int msm_pm_idle_spin_time = CONFIG_MSM7X00A_IDLE_SPIN_TIME;
module_param_named(idle_spin_time, msm_pm_idle_spin_time, int, S_IRUGO | S_IWUSR | S_IWGRP);

#if defined (CONFIG_ARCH_MSM7X30)
#define A11S_CLK_SLEEP_EN (MSM_GCC_BASE + 0x020)
#define A11S_PWRDOWN (MSM_ACC_BASE + 0x01C)
#define A11S_SECOP (MSM_TCSR_BASE + 0x38)
#else
#define A11S_CLK_SLEEP_EN (MSM_CSR_BASE + 0x11c)
#define A11S_PWRDOWN (MSM_CSR_BASE + 0x440)
#define A11S_STANDBY_CTL (MSM_CSR_BASE + 0x108)
#endif
#define A11RAMBACKBIAS (MSM_CSR_BASE + 0x508)

#if defined(CONFIG_MSM_N_WAY_SMD)
#define DEM_MASTER_BITS_PER_CPU             6

/* Power Master State Bits - Per CPU */
#define DEM_MASTER_SMSM_RUN \
        (0x01UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
#define DEM_MASTER_SMSM_RSA \
        (0x02UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
#define DEM_MASTER_SMSM_PWRC_EARLY_EXIT \
        (0x04UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
#define DEM_MASTER_SMSM_SLEEP_EXIT \
        (0x08UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
#define DEM_MASTER_SMSM_READY \
        (0x10UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))
#define DEM_MASTER_SMSM_SLEEP \
        (0x20UL << (DEM_MASTER_BITS_PER_CPU * SMSM_STATE_APPS))

/* Power Slave State Bits */
#define DEM_SLAVE_SMSM_RUN                  (0x0001)
#define DEM_SLAVE_SMSM_PWRC                 (0x0002)
#define DEM_SLAVE_SMSM_PWRC_DELAY           (0x0004)
#define DEM_SLAVE_SMSM_PWRC_EARLY_EXIT      (0x0008)
#define DEM_SLAVE_SMSM_WFPI                 (0x0010)
#define DEM_SLAVE_SMSM_SLEEP                (0x0020)
#define DEM_SLAVE_SMSM_SLEEP_EXIT           (0x0040)
#define DEM_SLAVE_SMSM_MSGS_REDUCED         (0x0080)
#define DEM_SLAVE_SMSM_RESET                (0x0100)
#define DEM_SLAVE_SMSM_PWRC_SUSPEND         (0x0200)

#define PM_SMSM_WRITE_STATE	SMSM_STATE_APPS_DEM
#define PM_SMSM_READ_STATE	SMSM_STATE_POWER_MASTER_DEM

#define PM_SMSM_WRITE_RUN	DEM_SLAVE_SMSM_RUN
#define PM_SMSM_READ_RUN	DEM_MASTER_SMSM_RUN
#else
#define PM_SMSM_WRITE_STATE     SMSM_STATE_APPS
#define PM_SMSM_READ_STATE      SMSM_STATE_MODEM

#define PM_SMSM_WRITE_RUN       SMSM_RUN
#define PM_SMSM_READ_RUN        SMSM_RUN
#endif

int msm_pm_collapse(void);
int msm_arch_idle(void);
void msm_pm_collapse_exit(void);

int64_t msm_timer_enter_idle(void);
void msm_timer_exit_idle(int low_power);
int msm_irq_idle_sleep_allowed(void);
int msm_irq_pending(void);
int clks_allow_tcxo_locked_debug(void);
extern int board_mfg_mode(void);
extern char * board_get_mfg_sleep_gpio_table(void);
extern void gpio_set_diag_gpio_table(unsigned long * dwMFG_gpio_table);
extern void wait_rmt_final_call_back(int timeout);

static int axi_rate;
static int sleep_axi_rate;
static struct clk *axi_clk;
static uint32_t *msm_pm_reset_vector;

static uint32_t msm_pm_max_sleep_time;

#ifdef CONFIG_MSM_IDLE_STATS
enum msm_pm_time_stats_id {
	MSM_PM_STAT_REQUESTED_IDLE,
	MSM_PM_STAT_IDLE_SPIN,
	MSM_PM_STAT_IDLE_WFI,
	MSM_PM_STAT_IDLE_SLEEP,
	MSM_PM_STAT_IDLE_FAILED_SLEEP,
	MSM_PM_STAT_NOT_IDLE,
	MSM_PM_STAT_COUNT
};

static struct msm_pm_time_stats {
	const char *name;
	int bucket[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
	int64_t min_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
	int64_t max_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
	int count;
	int64_t total_time;
} msm_pm_stats[MSM_PM_STAT_COUNT] = {
	[MSM_PM_STAT_REQUESTED_IDLE].name = "idle-request",
	[MSM_PM_STAT_IDLE_SPIN].name = "idle-spin",
	[MSM_PM_STAT_IDLE_WFI].name = "idle-wfi",
	[MSM_PM_STAT_IDLE_SLEEP].name = "idle-sleep",
	[MSM_PM_STAT_IDLE_FAILED_SLEEP].name = "idle-failed-sleep",
	[MSM_PM_STAT_NOT_IDLE].name = "not-idle",
};

static void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t)
{
	int i;
	int64_t bt;
	msm_pm_stats[id].total_time += t;
	msm_pm_stats[id].count++;
	bt = t;
	do_div(bt, CONFIG_MSM_IDLE_STATS_FIRST_BUCKET);
	if (bt < 1ULL << (CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT *
				(CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1)))
		i = DIV_ROUND_UP(fls((uint32_t)bt),
					CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT);
	else
		i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
	msm_pm_stats[id].bucket[i]++;
	if (t < msm_pm_stats[id].min_time[i] || !msm_pm_stats[id].max_time[i])
		msm_pm_stats[id].min_time[i] = t;
	if (t > msm_pm_stats[id].max_time[i])
		msm_pm_stats[id].max_time[i] = t;
}
#endif

static int
msm_pm_wait_state(uint32_t wait_all_set, uint32_t wait_all_clear,
                  uint32_t wait_any_set, uint32_t wait_any_clear)
{
	int i;
	uint32_t state;

	for (i = 0; i < 100000; i++) {
		state = smsm_get_state(PM_SMSM_READ_STATE);
		if (((wait_all_set || wait_all_clear) && 
		     !(~state & wait_all_set) && !(state & wait_all_clear)) ||
		    (state & wait_any_set) || (~state & wait_any_clear))
			return 0;
		udelay(1);
	}
	pr_err("msm_pm_wait_state(%x, %x, %x, %x) failed %x\n",	wait_all_set,
		wait_all_clear, wait_any_set, wait_any_clear, state);
	return -ETIMEDOUT;
}

/*
 * For speeding up boot time:
 * During booting up, disable entering arch_idle() by disable_hlt()
 * Enable it after booting up BOOT_LOCK_TIMEOUT sec.
 */
#define BOOT_LOCK_TIMEOUT      (60 * HZ)
static void do_expire_boot_lock(struct work_struct *work)
{
	enable_hlt();
	pr_info("Release 'boot-time' halt_lock\n");
}
static DECLARE_DELAYED_WORK(work_expire_boot_lock, do_expire_boot_lock);

#ifdef CONFIG_MSM_FIQ_SUPPORT
void msm_fiq_exit_sleep(void);
#else
static inline void msm_fiq_exit_sleep(void) { }
#endif

#ifdef CONFIG_HTC_POWER_COLLAPSE_MAGIC
/* Set magic number in SMEM for power collapse state */
#define HTC_POWER_COLLAPSE_ADD	(MSM_SHARED_RAM_BASE + 0x000F8000 + 0x000007F8)
#define HTC_POWER_COLLAPSE_MAGIC_NUM	(HTC_POWER_COLLAPSE_ADD - 0x04)
unsigned int magic_num;
#endif

static int msm_sleep(int sleep_mode, uint32_t sleep_delay, int from_idle)
{
	uint32_t saved_vector[2];
	int collapsed;
	void msm_irq_enter_sleep1(bool arm9_wake, int from_idle);
	int msm_irq_enter_sleep2(bool arm9_wake, int from_idle);
	void msm_irq_exit_sleep1(void);
	void msm_irq_exit_sleep2(void);
	void msm_irq_exit_sleep3(void);
	void msm_gpio_enter_sleep(int from_idle);
	void msm_gpio_exit_sleep(void);
	void smd_sleep_exit(void);
	uint32_t enter_state;
	uint32_t enter_wait_set = 0;
	uint32_t enter_wait_clear = 0;
	uint32_t exit_state;
	uint32_t exit_wait_clear = 0;
	uint32_t exit_wait_any_set = 0;
	unsigned long pm_saved_acpu_clk_rate = 0;
	int ret;
	int rv = -EINTR;
	bool invalid_inital_state = false;

	if (board_mfg_mode() == 4) /*power test mode*/
		gpio_set_diag_gpio_table(board_get_mfg_sleep_gpio_table());

	if (msm_pm_debug_mask & MSM_PM_DEBUG_SUSPEND)
		printk(KERN_INFO "msm_sleep(): mode %d delay %u idle %d\n",
		       sleep_mode, sleep_delay, from_idle);
#if defined(CONFIG_MSM_N_WAY_SMD)
	switch (sleep_mode) {
	case MSM_PM_SLEEP_MODE_POWER_COLLAPSE:
		enter_state = DEM_SLAVE_SMSM_PWRC;
		enter_wait_set = DEM_MASTER_SMSM_RSA;
		exit_state = DEM_SLAVE_SMSM_WFPI;
		exit_wait_any_set =
			DEM_MASTER_SMSM_RUN | DEM_MASTER_SMSM_PWRC_EARLY_EXIT;
		break;
	case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND:
		enter_state = DEM_SLAVE_SMSM_PWRC_SUSPEND;
		enter_wait_set = DEM_MASTER_SMSM_RSA;
		exit_state = DEM_SLAVE_SMSM_WFPI;
		exit_wait_any_set =
			DEM_MASTER_SMSM_RUN | DEM_MASTER_SMSM_PWRC_EARLY_EXIT;
		break;
	case MSM_PM_SLEEP_MODE_APPS_SLEEP:
		enter_state = DEM_SLAVE_SMSM_SLEEP;
		enter_wait_set = DEM_MASTER_SMSM_SLEEP;
		exit_state = DEM_SLAVE_SMSM_SLEEP_EXIT;
		exit_wait_any_set = DEM_MASTER_SMSM_SLEEP_EXIT;
		break;
	default:
		enter_state = 0;
		exit_state = 0;
	}
#else
	switch (sleep_mode) {
	case MSM_PM_SLEEP_MODE_POWER_COLLAPSE:
		enter_state = SMSM_PWRC;
		enter_wait_set = SMSM_RSA;
		exit_state = SMSM_WFPI;
		exit_wait_clear = SMSM_RSA;
		break;
	case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND:
		enter_state = SMSM_PWRC_SUSPEND;
		enter_wait_set = SMSM_RSA;
		exit_state = SMSM_WFPI;
		exit_wait_clear = SMSM_RSA;
		break;
	case MSM_PM_SLEEP_MODE_APPS_SLEEP:
		enter_state = SMSM_SLEEP;
		exit_state = SMSM_SLEEPEXIT;
		exit_wait_any_set = SMSM_SLEEPEXIT;
		break;
	default:
		enter_state = 0;
		exit_state = 0;
	}
#endif
	clk_enter_sleep(from_idle);
	msm_irq_enter_sleep1(!!enter_state, from_idle);
	msm_gpio_enter_sleep(from_idle);

	if (enter_state) {
		/* Make sure last sleep request did not end with a timeout */
		ret = msm_pm_wait_state(PM_SMSM_READ_RUN, 0, 0, 0);
		if (ret) {
			printk(KERN_ERR "msm_sleep(): invalid inital state\n");
			invalid_inital_state = true;
		}

		if (sleep_delay == 0 && sleep_mode >= MSM_PM_SLEEP_MODE_APPS_SLEEP)
			sleep_delay = 192000*5; /* APPS_SLEEP does not allow infinite timeout */
		ret = smsm_set_sleep_duration(sleep_delay);
		if (ret) {
			printk(KERN_ERR "msm_sleep(): smsm_set_sleep_duration %x failed\n", enter_state);
			enter_state = 0;
			exit_state = 0;
		}
		if ((!from_idle && msm_pm_debug_mask & MSM_PM_DEBUG_SLEEP_LIMIT) ||
			(from_idle && msm_pm_debug_mask & MSM_PM_DEBUG_IDLE))
			clks_allow_tcxo_locked_debug();

		ret = smsm_change_state(PM_SMSM_WRITE_STATE, PM_SMSM_WRITE_RUN, enter_state);
		if (ret) {
			printk(KERN_ERR "msm_sleep(): smsm_change_state %x failed\n", enter_state);
			enter_state = 0;
			exit_state = 0;
		}
		ret = msm_pm_wait_state(enter_wait_set, enter_wait_clear, 0, 0);
		if (ret || invalid_inital_state) {
			printk(KERN_INFO "msm_sleep(): msm_pm_wait_state failed, %x\n", smsm_get_state(PM_SMSM_READ_STATE));
			goto enter_failed;
		}
	}
	if (msm_irq_enter_sleep2(!!enter_state, from_idle))
		goto enter_failed;

	if (enter_state) {
#if defined (CONFIG_ARCH_MSM7X30)
		writel(1, A11S_PWRDOWN);
		writel(4, A11S_SECOP);
#else
		writel(0x1f, A11S_CLK_SLEEP_EN);
		writel(1, A11S_PWRDOWN);

		writel(0, A11S_STANDBY_CTL);
#endif
#if !defined(CONFIG_MSM_N_WAY_SMD)
		writel(0, A11RAMBACKBIAS);
#endif

		if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE)
			printk(KERN_INFO "msm_sleep(): enter "
			       "A11S_CLK_SLEEP_EN %x, A11S_PWRDOWN %x, "
			       "smsm_get_state %x\n", readl(A11S_CLK_SLEEP_EN),
			       readl(A11S_PWRDOWN), smsm_get_state(PM_SMSM_READ_STATE));
	}

	if (sleep_mode <= MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT) {
		pm_saved_acpu_clk_rate = acpuclk_power_collapse(from_idle);
		if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK)
			printk(KERN_INFO "msm_sleep(): %ld enter power collapse"
			       "\n", pm_saved_acpu_clk_rate);
		if (pm_saved_acpu_clk_rate == 0)
			goto ramp_down_failed;

#ifdef CONFIG_AXI_SCREEN_POLICY
		/* Drop AXI request when the screen is on */
		if (axi_rate)
			clk_set_rate(axi_clk, sleep_axi_rate);
#endif
	}
#ifdef CONFIG_HTC_POWER_COLLAPSE_MAGIC
	magic_num = 0xAAAA1111;
	writel(magic_num, HTC_POWER_COLLAPSE_MAGIC_NUM);
#endif
	if (sleep_mode < MSM_PM_SLEEP_MODE_APPS_SLEEP) {
		if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE)
			smsm_print_sleep_info(0);
		saved_vector[0] = msm_pm_reset_vector[0];
		saved_vector[1] = msm_pm_reset_vector[1];
		msm_pm_reset_vector[0] = 0xE51FF004; /* ldr pc, 4 */
		msm_pm_reset_vector[1] = virt_to_phys(msm_pm_collapse_exit);
		if (msm_pm_debug_mask & MSM_PM_DEBUG_RESET_VECTOR)
			printk(KERN_INFO "msm_sleep(): vector %x %x -> "
			       "%x %x\n", saved_vector[0], saved_vector[1],
			       msm_pm_reset_vector[0], msm_pm_reset_vector[1]);
#ifdef CONFIG_VFP
		if (from_idle)
			vfp_flush_context();
#endif

#ifdef CONFIG_CACHE_L2X0
		l2x0_suspend();
#endif
		if (!from_idle) printk(KERN_INFO "[R] suspend end\n");
		/* reset idle sleep mode when suspend. */
		if (!from_idle) msm_pm_idle_sleep_mode = CONFIG_MSM7X00A_IDLE_SLEEP_MODE;
		collapsed = msm_pm_collapse();
		if (!from_idle) printk(KERN_INFO "[R] resume start\n");
#ifdef CONFIG_CACHE_L2X0
		l2x0_resume(collapsed);
#endif
		msm_pm_reset_vector[0] = saved_vector[0];
		msm_pm_reset_vector[1] = saved_vector[1];
		if (collapsed) {
#ifdef CONFIG_VFP
			if (from_idle)
				vfp_reinit();
#endif
			cpu_init();
			__asm__("cpsie   a");
			msm_fiq_exit_sleep();
			local_fiq_enable();
			rv = 0;
		}
		if (msm_pm_debug_mask & MSM_PM_DEBUG_POWER_COLLAPSE)
			printk(KERN_INFO "msm_pm_collapse(): returned %d\n",
			       collapsed);
		if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE)
			smsm_print_sleep_info(0);
	} else {
		msm_arch_idle();
		rv = 0;
	}
#ifdef CONFIG_HTC_POWER_COLLAPSE_MAGIC
	magic_num = 0xBBBB9999;
	writel(magic_num, HTC_POWER_COLLAPSE_MAGIC_NUM);
#endif
	if (sleep_mode <= MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT) {
		if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK)
			printk(KERN_INFO "msm_sleep(): exit power collapse %ld"
			       "\n", pm_saved_acpu_clk_rate);
#if defined(CONFIG_ARCH_QSD8X50)
		if (acpuclk_set_rate(pm_saved_acpu_clk_rate, 1) < 0)
#else
		if (acpuclk_set_rate(pm_saved_acpu_clk_rate,
			from_idle ? SETRATE_PC_IDLE : SETRATE_PC) < 0)
#endif
			printk(KERN_ERR "msm_sleep(): clk_set_rate %ld "
			       "failed\n", pm_saved_acpu_clk_rate);

#ifdef CONFIG_AXI_SCREEN_POLICY
		/* Restore axi rate if needed */
		if (axi_rate)
			clk_set_rate(axi_clk, axi_rate);
#endif
	}
	if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE)
		printk(KERN_INFO "msm_sleep(): exit A11S_CLK_SLEEP_EN %x, "
		       "A11S_PWRDOWN %x, smsm_get_state %x\n",
		       readl(A11S_CLK_SLEEP_EN), readl(A11S_PWRDOWN),
		       smsm_get_state(PM_SMSM_READ_STATE));
ramp_down_failed:
	msm_irq_exit_sleep1();
enter_failed:
	if (enter_state) {
#if defined (CONFIG_ARCH_MSM7X30)
		writel(0, A11S_SECOP);
		writel(0, A11S_PWRDOWN);
		msm_spm_reinit();
#else
		writel(0x00, A11S_CLK_SLEEP_EN);
		writel(0, A11S_PWRDOWN);
#endif
		smsm_change_state(PM_SMSM_WRITE_STATE, enter_state, exit_state);
		msm_pm_wait_state(0, exit_wait_clear, exit_wait_any_set, 0);
		if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE)
			printk(KERN_INFO "msm_sleep(): sleep exit "
			       "A11S_CLK_SLEEP_EN %x, A11S_PWRDOWN %x, "
			       "smsm_get_state %x\n", readl(A11S_CLK_SLEEP_EN),
			       readl(A11S_PWRDOWN), smsm_get_state(PM_SMSM_READ_STATE));
		if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE)
			smsm_print_sleep_info(0);
		if (msm_pm_debug_mask & MSM_PM_DEBUG_WAKEUP_REASON && !from_idle)
			smsm_print_sleep_info(1);
	}
	msm_irq_exit_sleep2();
	if (enter_state) {
		smsm_change_state(PM_SMSM_WRITE_STATE, exit_state, PM_SMSM_WRITE_RUN);
		msm_pm_wait_state(PM_SMSM_READ_RUN, 0, 0, 0);
		if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE)
			printk(KERN_INFO "msm_sleep(): sleep exit "
			       "A11S_CLK_SLEEP_EN %x, A11S_PWRDOWN %x, "
			       "smsm_get_state %x\n", readl(A11S_CLK_SLEEP_EN),
			       readl(A11S_PWRDOWN), smsm_get_state(PM_SMSM_READ_STATE));
	}
	msm_irq_exit_sleep3();
	msm_gpio_exit_sleep();
	smd_sleep_exit();
	clk_exit_sleep();
	return rv;
}

static int msm_pm_idle_spin(void)
{
	int spin;
	spin = msm_pm_idle_spin_time >> 10;
	while (spin-- > 0) {
		if (msm_irq_pending()) {
			return -1;
		}
		udelay(1);
	}
	return 0;
}

void arch_idle(void)
{
	int ret;
	int64_t sleep_time;
	int low_power = 0;
#ifdef CONFIG_MSM_IDLE_STATS
	int64_t t1;
	static int64_t t2;
	int exit_stat;
#endif
	int allow_sleep =
		msm_pm_idle_sleep_mode < MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT &&
#ifdef CONFIG_HAS_WAKELOCK
		!has_wake_lock(WAKE_LOCK_IDLE) &&
#endif
		msm_irq_idle_sleep_allowed();
	if (msm_pm_reset_vector == NULL)
		return;

	sleep_time = msm_timer_enter_idle();
#ifdef CONFIG_MSM_IDLE_STATS
	t1 = ktime_to_ns(ktime_get());
	msm_pm_add_stat(MSM_PM_STAT_NOT_IDLE, t1 - t2);
	msm_pm_add_stat(MSM_PM_STAT_REQUESTED_IDLE, sleep_time);
#endif
	if (msm_pm_debug_mask & MSM_PM_DEBUG_IDLE)
		printk(KERN_INFO "arch_idle: sleep time %llu, allow_sleep %d\n",
		       sleep_time, allow_sleep);
	if (sleep_time < msm_pm_idle_sleep_min_time || !allow_sleep) {
		unsigned long saved_rate;
		/* only spin while trying wfi ramp down */
		if (acpuclk_get_wfi_rate() && msm_pm_idle_spin() < 0) {
#ifdef CONFIG_MSM_IDLE_STATS
			exit_stat = MSM_PM_STAT_IDLE_SPIN;
#endif
			goto abort_idle;
		}
		saved_rate = acpuclk_wait_for_irq();


		if (saved_rate && msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK)
			printk(KERN_DEBUG "arch_idle: clk %ld -> swfi\n",
				saved_rate);

		/*
		 * If there is a wfi speed specified and we failed to ramp, do not
		 * go into wfi.
		 */
		if (acpuclk_get_wfi_rate() && !saved_rate)
			while (!msm_irq_pending())
				udelay(1);
		else
			msm_arch_idle();

		if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK)
			printk(KERN_DEBUG "msm_sleep: clk swfi -> %ld\n",
				saved_rate);
#if defined(CONFIG_ARCH_QSD8X50)
		if (saved_rate && acpuclk_set_rate(saved_rate, 1) < 0)
#else
		if (saved_rate
		    && acpuclk_set_rate(saved_rate, SETRATE_SWFI) < 0)
#endif
			printk(KERN_ERR "msm_sleep(): clk_set_rate %ld "
			       "failed\n", saved_rate);
#ifdef CONFIG_MSM_IDLE_STATS
		exit_stat = MSM_PM_STAT_IDLE_WFI;
#endif
  	} else {
		if (msm_pm_idle_spin() < 0) {
#ifdef CONFIG_MSM_IDLE_STATS
			exit_stat = MSM_PM_STAT_IDLE_SPIN;
#endif
			goto abort_idle;
		}

		low_power = 1;
		do_div(sleep_time, NSEC_PER_SEC / 32768);
		if (sleep_time > 0x6DDD000) {
			printk("sleep_time too big %lld\n", sleep_time);
			sleep_time = 0x6DDD000;
		}
		ret = msm_sleep(msm_pm_idle_sleep_mode, sleep_time, 1);
#ifdef CONFIG_MSM_IDLE_STATS
		if (ret)
			exit_stat = MSM_PM_STAT_IDLE_FAILED_SLEEP;
		else
			exit_stat = MSM_PM_STAT_IDLE_SLEEP;
#endif
	}
abort_idle:
	msm_timer_exit_idle(low_power);
#ifdef CONFIG_MSM_IDLE_STATS
	t2 = ktime_to_ns(ktime_get());
	msm_pm_add_stat(exit_stat, t2 - t1);
#endif
}

static int msm_pm_enter(suspend_state_t state)
{
	msm_sleep(msm_pm_sleep_mode, msm_pm_max_sleep_time, 0);
	return 0;
}

static struct platform_suspend_ops msm_pm_ops = {
	.enter		= msm_pm_enter,
	.valid		= suspend_valid_only_mem,
};

static uint32_t restart_reason = 0x776655AA;

static int msm_wakeup_after;	/* default, no wakeup by alarm */
static int msm_power_wakeup_after(const char *val, struct kernel_param *kp)
{
	int ret;
	struct uart_port *port;
	struct msm_port *msm_port;

	ret = param_set_int(val, kp);
	printk(KERN_INFO "+msm_power_wakeup_after, ret=%d\r\n", ret);

	if (!ret) {
		printk(KERN_INFO "msm_wakeup_after=%d\r\n", msm_wakeup_after);
		if (msm_wakeup_after < 0)
			msm_wakeup_after = 0; /* invalid, no wakeup */
	}
	printk(KERN_INFO "-msm_power_wakeup_after, msm_wakeup_after=%d\r\n", msm_wakeup_after);
	return ret;
}

module_param_call(wakeup_after, msm_power_wakeup_after, param_get_int,
		  &msm_wakeup_after, S_IWUSR | S_IRUGO);

static void msm_pm_power_off(void)
{
	printk(KERN_INFO "msm_pm_power_off:wakeup after %d\r\n", msm_wakeup_after);

	if (msm_wakeup_after)
		msm_proc_comm(PCOM_SET_RTC_ALARM, &msm_wakeup_after, 0);

	msm_proc_comm(PCOM_POWER_DOWN, 0, 0);

#if CONFIG_MSM_RMT_STORAGE_SERVER
		printk(KERN_INFO "from %s\r\n", __func__);
		wait_rmt_final_call_back(10);
		printk(KERN_INFO "back %s\r\n", __func__);
#endif
	for (;;) ;
}

static bool console_flushed;

void msm_pm_flush_console(void)
{
	if (console_flushed)
		return;
	console_flushed = true;

	printk("\n");
	printk(KERN_EMERG "Restarting %s\n", linux_banner);
	if (!try_acquire_console_sem()) {
		release_console_sem();
		return;
	}

	mdelay(50);

	local_irq_disable();
	if (try_acquire_console_sem())
		printk(KERN_EMERG "msm_restart: Console was locked! Busting\n");
	else
		printk(KERN_EMERG "msm_restart: Console was locked!\n");
	release_console_sem();
}

static void msm_pm_restart(char str)
{
	msm_pm_flush_console();

	/*  always reboot device through proc comm */
	if (restart_reason == 0x6f656d99)
		msm_proc_comm(PCOM_RESET_CHIP_IMM, &restart_reason, 0);
	else
		msm_proc_comm(PCOM_RESET_CHIP, &restart_reason, 0);

#if CONFIG_MSM_RMT_STORAGE_SERVER
	printk(KERN_INFO "from %s\r\n", __func__);
	wait_rmt_final_call_back(10);
	printk(KERN_INFO "back %s\r\n", __func__);
	/* wait 2 seconds to let radio reset device after the final EFS sync*/
	mdelay(2000);
#else
	/* In case Radio is dead, reset device after notify Radio 5 seconds */
	mdelay(5000);
#endif

	/* hard reboot if possible */
	if (msm_hw_reset_hook) {
		printk(KERN_INFO "%s : Do HW_RESET by APP not by RADIO\r\n", __func__);
		msm_hw_reset_hook();
	}

	for (;;) ;
}

static int msm_reboot_call(struct notifier_block *this, unsigned long code, void *_cmd)
{
	if((code == SYS_RESTART) && _cmd) {
		char *cmd = _cmd;
		if (!strcmp(cmd, "bootloader")) {
			restart_reason = 0x77665500;
		} else if (!strcmp(cmd, "recovery")) {
			restart_reason = 0x77665502;
		} else if (!strcmp(cmd, "eraseflash")) {
			restart_reason = 0x776655EF;
		} else if (!strncmp(cmd, "oem-", 4)) {
			unsigned code = simple_strtoul(cmd + 4, 0, 16) & 0xff;
			restart_reason = 0x6f656d00 | code;
		} else if (!strcmp(cmd, "force-hard")) {
			restart_reason = 0x776655AA;
		} else {
			restart_reason = 0x77665501;
		}
	}
	return NOTIFY_DONE;
}

static struct notifier_block msm_reboot_notifier =
{
	.notifier_call = msm_reboot_call,
};

#ifdef CONFIG_MSM_IDLE_STATS
static int msm_pm_read_proc(char *page, char **start, off_t off,
                               int count, int *eof, void *data)
{
	int len = 0;
	int i, j;
	char *p = page;

	for (i = 0; i < ARRAY_SIZE(msm_pm_stats); i++) {
		int64_t bucket_time;
		int64_t s;
		uint32_t ns;
		s = msm_pm_stats[i].total_time;
		ns = do_div(s, NSEC_PER_SEC);
		p += sprintf(p,
			"%s:\n"
			"  count: %7d\n"
			"  total_time: %lld.%09u\n",
			msm_pm_stats[i].name,
			msm_pm_stats[i].count,
			s, ns);
		bucket_time = CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
		for (j = 0; j < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; j++) {
			s = bucket_time;
			ns = do_div(s, NSEC_PER_SEC);
			p += sprintf(p, "   <%2lld.%09u: %7d (%lld-%lld)\n",
				s, ns, msm_pm_stats[i].bucket[j],
				msm_pm_stats[i].min_time[j],
				msm_pm_stats[i].max_time[j]);
			bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT;
		}
		p += sprintf(p, "  >=%2lld.%09u: %7d (%lld-%lld)\n",
			s, ns, msm_pm_stats[i].bucket[j],
			msm_pm_stats[i].min_time[j],
			msm_pm_stats[i].max_time[j]);
	}
	*start = page + off;

	len = p - page;
	if (len > off)
		len -= off;
	else
		len = 0;

	return len < count ? len  : count;
}
#endif

void msm_pm_set_max_sleep_time(int64_t max_sleep_time_ns)
{
	int64_t max_sleep_time_bs = max_sleep_time_ns;

	/* Convert from ns -> BS units */
	do_div(max_sleep_time_bs, NSEC_PER_SEC / 32768);

	if (max_sleep_time_bs > 0x6DDD000)
		msm_pm_max_sleep_time = (uint32_t) 0x6DDD000;
	else
		msm_pm_max_sleep_time = (uint32_t) max_sleep_time_bs;

	if (msm_pm_debug_mask & MSM_PM_DEBUG_SUSPEND)
		printk("%s: Requested %lldns (%lldbs), Giving %ubs\n",
		       __func__, max_sleep_time_ns, 
		       max_sleep_time_bs, 
		       msm_pm_max_sleep_time);
}
EXPORT_SYMBOL(msm_pm_set_max_sleep_time);

#ifdef CONFIG_EARLYSUSPEND
/* axi 128 screen on, 61mhz screen off */
static void axi_early_suspend(struct early_suspend *handler)
{
	axi_rate = 0;
	clk_set_rate(axi_clk, axi_rate);
}

static void axi_late_resume(struct early_suspend *handler)
{
	axi_rate = 128000000;
	sleep_axi_rate = 117000000;
	clk_set_rate(axi_clk, axi_rate);
}

static struct early_suspend axi_screen_suspend = {
	.suspend = axi_early_suspend,
	.resume = axi_late_resume,
};
#endif

static void __init msm_pm_axi_init(void)
{
#ifdef CONFIG_EARLYSUSPEND
	axi_clk = clk_get(NULL, "ebi1_clk");
	if (IS_ERR(axi_clk)) {
		int result = PTR_ERR(axi_clk);
		pr_err("clk_get(ebi1_clk) returned %d\n", result);
		return;
	}
	axi_rate = 128000000;
	sleep_axi_rate = 117000000;
	clk_set_rate(axi_clk, axi_rate);
	register_early_suspend(&axi_screen_suspend);
#else
	axi_rate = 0;
#endif
}

static int __init msm_pm_init(void)
{
	pm_power_off = msm_pm_power_off;
	arm_pm_restart = msm_pm_restart;
	msm_pm_max_sleep_time = 0;
#if defined(CONFIG_ARCH_MSM_SCORPION)
#ifdef CONFIG_AXI_SCREEN_POLICY
	msm_pm_axi_init();
#endif
#endif

	register_reboot_notifier(&msm_reboot_notifier);
	msm_pm_reset_vector = ioremap(0x0, PAGE_SIZE);

	if (msm_pm_reset_vector == NULL) {
		printk(KERN_ERR "msm_pm_init: failed to map reset vector\n");
		return -ENODEV;
	}

	suspend_set_ops(&msm_pm_ops);

#ifdef CONFIG_MSM_IDLE_STATS
	create_proc_read_entry("msm_pm_stats", S_IRUGO,
				NULL, msm_pm_read_proc, NULL);
#endif

	if ((board_mfg_mode() == 0) || (board_mfg_mode() == 1)) {
		disable_hlt();
		schedule_delayed_work(&work_expire_boot_lock, BOOT_LOCK_TIMEOUT);
		pr_info("Acquire 'boot-time' halt_lock\n");
	}
	return 0;
}

__initcall(msm_pm_init);