rk29: add pm support
author黄涛 <huangtao@rock-chips.com>
Tue, 25 Jan 2011 12:34:43 +0000 (20:34 +0800)
committer黄涛 <huangtao@rock-chips.com>
Tue, 25 Jan 2011 12:34:43 +0000 (20:34 +0800)
arch/arm/mach-rk29/Makefile
arch/arm/mach-rk29/clock.c
arch/arm/mach-rk29/cpufreq.c
arch/arm/mach-rk29/include/mach/cru.h
arch/arm/mach-rk29/pm.c [new file with mode: 0644]

index dde268b1161cc01b3cd9054e27ae730de0f394ac..352e1c6fc1ccf9c35654f4df447bb14b29e34135 100755 (executable)
@@ -1,4 +1,5 @@
 obj-y += timer.o io.o devices.o iomux.o clock.o rk29-pl330.o dma.o gpio.o ddr.o sram.o
+obj-$(CONFIG_PM) += pm.o
 obj-$(CONFIG_CPU_FREQ) += cpufreq.o
 obj-$(CONFIG_RK29_VPU) += vpu.o vpu_mem.o
 obj-$(CONFIG_MACH_RK29SDK) += board-rk29sdk.o board-rk29sdk-key.o board-rk29sdk-rfkill.o
index 34123cb6f383846447037eecdd49ea5337a28da5..cd75bfd39982a7c3dd499ed60335c2ab897cf7b3 100755 (executable)
 #include <mach/pmu.h>
 
 
-/* CRU PLL CON */
-#define PLL_HIGH_BAND  (0x01 << 16)
-#define PLL_LOW_BAND   (0x00 << 16)
-#define PLL_PD         (0x01 << 15)
-
-#define PLL_CLKR(i)    ((((i) - 1) & 0x1f) << 10)
-#define PLL_NR(v)      ((((v) >> 10) & 0x1f) + 1)
-
-#define PLL_CLKF(i)    ((((i) - 1) & 0x7f) << 3)
-#define PLL_NF(v)      ((((v) >> 3) & 0x7f) + 1)
-#define PLL_NF2(v)     (((((v) >> 3) & 0x7f) + 1) << 1)
-
-#define PLL_CLKOD(i)   (((i) & 0x03) << 1)
-#define PLL_NO_1       PLL_CLKOD(0)
-#define PLL_NO_2       PLL_CLKOD(1)
-#define PLL_NO_4       PLL_CLKOD(2)
-#define PLL_NO_8       PLL_CLKOD(3)
-#define PLL_NO_SHIFT(v)        (((v) >> 1) & 0x03)
-
-#define PLL_BYPASS     (0x01)
-
-/* CRU MODE CON */
-#define CRU_CPU_MODE_MASK      (0x03u << 0)
-#define CRU_CPU_MODE_SLOW      (0x00u << 0)
-#define CRU_CPU_MODE_NORMAL    (0x01u << 0)
-#define CRU_CPU_MODE_SLOW27    (0x02u << 0)
-
-#define CRU_GENERAL_MODE_MASK  (0x03u << 2)
-#define CRU_GENERAL_MODE_SLOW  (0x00u << 2)
-#define CRU_GENERAL_MODE_NORMAL        (0x01u << 2)
-#define CRU_GENERAL_MODE_SLOW27        (0x02u << 2)
-
-#define CRU_CODEC_MODE_MASK    (0x03u << 4)
-#define CRU_CODEC_MODE_SLOW    (0x00u << 4)
-#define CRU_CODEC_MODE_NORMAL  (0x01u << 4)
-#define CRU_CODEC_MODE_SLOW27  (0x02u << 4)
-
-#define CRU_DDR_MODE_MASK      (0x03u << 6)
-#define CRU_DDR_MODE_SLOW      (0x00u << 6)
-#define CRU_DDR_MODE_NORMAL    (0x01u << 6)
-#define CRU_DDR_MODE_SLOW27    (0x02u << 6)
-
 /* Clock flags */
 /* bit 0 is free */
 #define RATE_FIXED             (1 << 1)        /* Fixed clock rate */
@@ -480,14 +438,16 @@ static struct clk ddr_pll_clk = {
 
 static int codec_pll_clk_mode(struct clk *clk, int on)
 {
+       u32 cpll = cru_readl(CRU_CPLL_CON);
        if (on) {
-               cru_writel(cru_readl(CRU_CPLL_CON) & ~PLL_PD, CRU_CPLL_CON);
+               cru_writel(cpll & ~(PLL_PD | PLL_BYPASS), CRU_CPLL_CON);
                delay_300us();
                pll_wait_lock(CODEC_PLL_IDX);
                cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_CODEC_MODE_MASK) | CRU_CODEC_MODE_NORMAL, CRU_MODE_CON);
        } else {
                cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_CODEC_MODE_MASK) | CRU_CODEC_MODE_SLOW, CRU_MODE_CON);
-               cru_writel(cru_readl(CRU_CPLL_CON) | PLL_PD, CRU_CPLL_CON);
+               cru_writel(cpll | PLL_BYPASS, CRU_CPLL_CON);
+               cru_writel(cpll | PLL_PD | PLL_BYPASS, CRU_CPLL_CON);
                delay_500ns();
        }
        return 0;
@@ -2241,10 +2201,10 @@ static void __init rk29_clock_common_init(void)
        /* codec pll */
        clk_set_rate_nolock(&codec_pll_clk, 552 * MHZ);
        clk_set_parent_nolock(&clk_gpu, &codec_pll_clk);
-       clk_set_parent_nolock(&clk_mac_ref_div, &codec_pll_clk);
 
        /* arm pll */
        clk_set_rate_nolock(&arm_pll_clk, armclk);
+       clk_set_parent_nolock(&clk_mac_ref_div, &arm_pll_clk);
 }
 
 static void __init clk_enable_init_clocks(void)
index 907eaa15409b3aa2ff6ce8f837c6283b394f879e..a1a737d92b9d914daf54a67c996dca03124d66ff 100755 (executable)
  *
  */
 
+#ifdef CONFIG_CPU_FREQ_DEBUG
+#define DEBUG
+#endif
+#define pr_fmt(fmt) "cpufreq: %s: " fmt, __func__
+
 #include <linux/clk.h>
 #include <linux/cpufreq.h>
 #include <linux/err.h>
 #include <linux/init.h>
 #include <linux/regulator/consumer.h>
+#include <linux/suspend.h>
+
+#define SLEEP_FREQ     (800 * 1000) /* Use 800MHz when entering sleep */
+
+/* additional symantics for "relation" in cpufreq with pm */
+#define DISABLE_FURTHER_CPUFREQ         0x10
+#define ENABLE_FURTHER_CPUFREQ          0x20
+#define MASK_FURTHER_CPUFREQ            0x30
+/* With 0x00(NOCHANGE), it depends on the previous "further" status */
+static int no_cpufreq_access;
 
 static struct cpufreq_frequency_table freq_table[] = {
 //     { .index =  950000, .frequency =  204000 },
@@ -46,8 +61,28 @@ static int rk29_cpufreq_target(struct cpufreq_policy *policy, unsigned int targe
 
        if (policy->cpu != 0)
                return -EINVAL;
+
+       if ((relation & ENABLE_FURTHER_CPUFREQ) &&
+           (relation & DISABLE_FURTHER_CPUFREQ)) {
+               /* Invalidate both if both marked */
+               relation &= ~ENABLE_FURTHER_CPUFREQ;
+               relation &= ~DISABLE_FURTHER_CPUFREQ;
+               pr_err("denied marking FURTHER_CPUFREQ as both marked.\n");
+       }
+       if (relation & ENABLE_FURTHER_CPUFREQ)
+               no_cpufreq_access--;
+       if (no_cpufreq_access) {
+#ifdef CONFIG_PM_VERBOSE
+               pr_err("denied access to %s as it is disabled temporarily\n", __func__);
+#endif
+               return -EINVAL;
+       }
+       if (relation & DISABLE_FURTHER_CPUFREQ)
+               no_cpufreq_access++;
+       relation &= ~MASK_FURTHER_CPUFREQ;
+
        if (cpufreq_frequency_table_target(policy, freq_table, target_freq, relation, &index)) {
-               pr_err("cpufreq: invalid target_freq: %d\n", target_freq);
+               pr_err("invalid target_freq: %d\n", target_freq);
                return -EINVAL;
        }
        freq = &freq_table[index];
@@ -58,15 +93,13 @@ static int rk29_cpufreq_target(struct cpufreq_policy *policy, unsigned int targe
        freqs.old = policy->cur;
        freqs.new = freq->frequency;
        freqs.cpu = 0;
-#ifdef CONFIG_CPU_FREQ_DEBUG
-       printk(KERN_DEBUG "%s %d r %d (%d-%d) selected %d (%duV)\n", __func__, target_freq, relation, policy->min, policy->max, freq->frequency, freq->index);
-#endif
+       pr_debug("%d r %d (%d-%d) selected %d (%duV)\n", target_freq, relation, policy->min, policy->max, freq->frequency, freq->index);
 
 #ifdef CONFIG_REGULATOR
        if (vcore && freqs.new > freqs.old) {
                err = regulator_set_voltage(vcore, freq->index, freq->index);
                if (err) {
-                       pr_err("cpufreq: fail to set vcore (%duV) for %dkHz: %d\n",
+                       pr_err("fail to set vcore (%duV) for %dkHz: %d\n",
                                freq->index, freqs.new, err);
                        goto err_vol;
                }
@@ -82,7 +115,7 @@ static int rk29_cpufreq_target(struct cpufreq_policy *policy, unsigned int targe
        if (vcore && freqs.new < freqs.old) {
                err = regulator_set_voltage(vcore, freq->index, freq->index);
                if (err) {
-                       pr_err("cpufreq: fail to set vcore (%duV) for %dkHz: %d\n",
+                       pr_err("fail to set vcore (%duV) for %dkHz: %d\n",
                                freq->index, freqs.new, err);
                }
        }
@@ -106,7 +139,7 @@ static int __init rk29_cpufreq_init(struct cpufreq_policy *policy)
 #ifdef CONFIG_REGULATOR
        vcore = regulator_get(NULL, "vcore");
        if (IS_ERR(vcore)) {
-               pr_err("cpufreq: fail to get regulator vcore: %ld\n", PTR_ERR(vcore));
+               pr_err("fail to get regulator vcore: %ld\n", PTR_ERR(vcore));
                vcore = NULL;
        }
 #endif
@@ -143,8 +176,35 @@ static struct cpufreq_driver rk29_cpufreq_driver = {
        .attr           = rk29_cpufreq_attr,
 };
 
+static int rk29_cpufreq_pm_notifier_event(struct notifier_block *this,
+               unsigned long event, void *ptr)
+{
+       int ret;
+
+       switch (event) {
+       case PM_SUSPEND_PREPARE:
+               ret = cpufreq_driver_target(cpufreq_cpu_get(0), SLEEP_FREQ,
+                               DISABLE_FURTHER_CPUFREQ);
+               if (ret < 0)
+                       return NOTIFY_BAD;
+               return NOTIFY_OK;
+       case PM_POST_RESTORE:
+       case PM_POST_SUSPEND:
+               cpufreq_driver_target(cpufreq_cpu_get(0), SLEEP_FREQ,
+                               ENABLE_FURTHER_CPUFREQ);
+               return NOTIFY_OK;
+       }
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block rk29_cpufreq_pm_notifier = {
+       .notifier_call = rk29_cpufreq_pm_notifier_event,
+};
+
 static int __init rk29_cpufreq_register(void)
 {
+       register_pm_notifier(&rk29_cpufreq_pm_notifier);
+
        return cpufreq_register_driver(&rk29_cpufreq_driver);
 }
 
index 2d973a7754911ee6d723c2d8590d5bc15f92eb0d..cc21243244184eb7c63128a62f083571533c1495 100644 (file)
@@ -137,6 +137,133 @@ enum cru_clk_gate
        CLK_GATE_MAX,
 };
 
+enum cru_soft_reset {
+       SOFT_RST_ARM_CORE = 0,
+       SOFT_RST_CPU_SYSTEM_INTERCONNECT_1_AXI,
+       SOFT_RST_CPU_SYSTEM_INTERCONNECT_1_AHB,
+       SOFT_RST_CPU_SYSTEM_INTERCONNECT_1_APB,
+       SOFT_RST_CPU_SYSTEM_INTERCONNECT_1_ATB,
+       SOFT_RST_CPU_SYSTEM_INTERCONNECT_2,
+       SOFT_RST_DMA0 = 7,
+       SOFT_RST_GIC,
+       SOFT_RST_INTERNAL_MEMORY,
+       SOFT_RST_TZPC = 11,
+       SOFT_RST_ROM,
+       SOFT_RST_I2S0,
+       SOFT_RST_I2S1,
+       SOFT_RST_SPDIF,
+       SOFT_RST_UART0,
+       SOFT_RST_RTC,
+       SOFT_RST_DDR_PHY,
+       SOFT_RST_DDR_DLL_BYTE0,
+       SOFT_RST_DDR_DLL_BYTE1,
+       SOFT_RST_DDR_DLL_BYTE2,
+       SOFT_RST_DDR_DLL_BYTE3,
+       SOFT_RST_DDR_DLL_CMD,
+       SOFT_RST_DDR_CONTROLLER,
+       SOFT_RST_ARM_CORE_DEBUG,
+       SOFT_RST_DAP_DBG,
+       SOFT_RST_CPU_VODEC_A2A_AHB,
+       SOFT_RST_CPU_DISPLAY_A2A_AHB,
+       SOFT_RST_DAP_SYS,
+
+       SOFT_RST_PERI_SYSTEM_INTERCONNECT_1_AXI = 32,
+       SOFT_RST_PERI_SYSTEM_INTERCONNECT_1_AHB,
+       SOFT_RST_PERI_SYSTEM_INTERCONNECT_1_APB,
+       SOFT_RST_PERIPH_EMEM = 32 + 4,
+       SOFT_RST_PERIPH_USB,
+       SOFT_RST_DMA1,
+       SOFT_RST_MAC,
+       SOFT_RST_HIF,
+       SOFT_RST_NANDC,
+       SOFT_RST_SMC,
+       SOFT_RST_HSADC,
+       SOFT_RST_SDMMC,
+       SOFT_RST_SDIO,
+       SOFT_RST_EMMC,
+       SOFT_RST_USB_OTG_2_0_AHB_BUS,
+       SOFT_RST_USB_OTG_2_0_PHY,
+       SOFT_RST_USB_OTG_2_0_CONTROLLER,
+       SOFT_RST_USB_HOST_2_0_AHB_BUS,
+       SOFT_RST_USB_HOST_2_0_PHY,
+       SOFT_RST_USB_HOST_2_0_CONTROLLER,
+       SOFT_RST_UHOST,
+       SOFT_RST_VIP,
+       SOFT_RST_VIP_MATRIX_AHB,
+       SOFT_RST_SPI0,
+       SOFT_RST_SPI1,
+       SOFT_RST_SARADC,
+       SOFT_RST_UART1,
+       SOFT_RST_UART2,
+       SOFT_RST_UART3,
+       SOFT_RST_PWM,
+
+       SOFT_RST_DISPLAY_INTERCONNECTOR_AXI = 64,
+       SOFT_RST_DISPLAY_INTERCONNECTOR_AHB,
+       SOFT_RST_LCDC,
+       SOFT_RST_IPP,
+       SOFT_RST_EBC,
+       SOFT_RST_GPU = 64 + 7,
+       SOFT_RST_DDR_REG_PORT,
+       SOFT_RST_DDR_CPU_PORT,
+       SOFT_RST_PERIPH_USED_CPU_AXI,
+       SOFT_RST_DDR_PERIPH_PORT,
+       SOFT_RST_DDR_LCDC_PORT,
+       SOFT_RST_DDR_VCODEC_PORT = 64 + 15,
+       SOFT_RST_DDR_GPU_PORT,
+       SOFT_RST_PID_FILTER_AHB,
+       SOFT_RST_VCODEC_AXI_BUS,
+       SOFT_RST_VCODEC_AHB_BUS,
+       SOFT_RST_TIMER0,
+       SOFT_RST_TIMER1,
+       SOFT_RST_TIMER2,
+       SOFT_RST_TIMER3,
+
+       SOFT_RST_MAX,
+};
+
+/* CRU MODE CON */
+#define CRU_CPU_MODE_MASK      (0x03u << 0)
+#define CRU_CPU_MODE_SLOW      (0x00u << 0)
+#define CRU_CPU_MODE_NORMAL    (0x01u << 0)
+#define CRU_CPU_MODE_SLOW27    (0x02u << 0)
+
+#define CRU_GENERAL_MODE_MASK  (0x03u << 2)
+#define CRU_GENERAL_MODE_SLOW  (0x00u << 2)
+#define CRU_GENERAL_MODE_NORMAL        (0x01u << 2)
+#define CRU_GENERAL_MODE_SLOW27        (0x02u << 2)
+
+#define CRU_CODEC_MODE_MASK    (0x03u << 4)
+#define CRU_CODEC_MODE_SLOW    (0x00u << 4)
+#define CRU_CODEC_MODE_NORMAL  (0x01u << 4)
+#define CRU_CODEC_MODE_SLOW27  (0x02u << 4)
+
+#define CRU_DDR_MODE_MASK      (0x03u << 6)
+#define CRU_DDR_MODE_SLOW      (0x00u << 6)
+#define CRU_DDR_MODE_NORMAL    (0x01u << 6)
+#define CRU_DDR_MODE_SLOW27    (0x02u << 6)
+
+/* CRU PLL CON */
+#define PLL_HIGH_BAND  (0x01 << 16)
+#define PLL_LOW_BAND   (0x00 << 16)
+#define PLL_PD         (0x01 << 15)
+
+#define PLL_CLKR(i)    ((((i) - 1) & 0x1f) << 10)
+#define PLL_NR(v)      ((((v) >> 10) & 0x1f) + 1)
+
+#define PLL_CLKF(i)    ((((i) - 1) & 0x7f) << 3)
+#define PLL_NF(v)      ((((v) >> 3) & 0x7f) + 1)
+#define PLL_NF2(v)     (((((v) >> 3) & 0x7f) + 1) << 1)
+
+#define PLL_CLKOD(i)   (((i) & 0x03) << 1)
+#define PLL_NO_1       PLL_CLKOD(0)
+#define PLL_NO_2       PLL_CLKOD(1)
+#define PLL_NO_4       PLL_CLKOD(2)
+#define PLL_NO_8       PLL_CLKOD(3)
+#define PLL_NO_SHIFT(v)        (((v) >> 1) & 0x03)
+
+#define PLL_BYPASS     (0x01)
+
 /* Register definitions */
 #define CRU_APLL_CON           0x00
 #define CRU_DPLL_CON           0x04
@@ -169,4 +296,24 @@ enum cru_clk_gate
 #define CRU_SOFTRST1_CON       0x70
 #define CRU_SOFTRST2_CON       0x74
 
+static inline void cru_set_soft_reset(enum cru_soft_reset idx, bool on)
+{
+       unsigned long flags;
+       u32 addr = RK29_CRU_BASE + CRU_SOFTRST0_CON + ((idx >> 5) << 2);
+       u32 mask = 1 << (idx & 31);
+       u32 v;
+
+       if (idx >= SOFT_RST_MAX)
+               return;
+
+       local_irq_save(flags);
+       v = readl(addr);
+       if (on)
+               v |= mask;
+       else
+               v &= ~mask;
+       writel(v, addr);
+       local_irq_restore(flags);
+}
+
 #endif
diff --git a/arch/arm/mach-rk29/pm.c b/arch/arm/mach-rk29/pm.c
new file mode 100644 (file)
index 0000000..f0ebc58
--- /dev/null
@@ -0,0 +1,196 @@
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pm.h>
+#include <linux/suspend.h>
+#include <linux/regulator/consumer.h>
+#include <linux/io.h>
+#include <linux/wakelock.h>
+
+#include <mach/rk29_iomap.h>
+#include <mach/cru.h>
+#include <mach/pmu.h>
+#include <mach/board.h>
+#include <mach/system.h>
+#include <mach/sram.h>
+
+#define cru_readl(offset)      readl(RK29_CRU_BASE + offset)
+#define cru_writel(v, offset)  do { writel(v, RK29_CRU_BASE + offset); readl(RK29_CRU_BASE + offset); } while (0)
+#define pmu_readl(offset)      readl(RK29_PMU_BASE + offset)
+#define pmu_writel(v, offset)  do { writel(v, RK29_PMU_BASE + offset); readl(RK29_PMU_BASE + offset); } while (0)
+static unsigned long save_sp;
+
+static inline void delay_500ns(void)
+{
+       int delay = 12;
+       while (delay--)
+               barrier();
+}
+
+static inline void delay_300us(void)
+{
+       int i;
+       for (i = 0; i < 600; i++)
+               delay_500ns();
+}
+
+static u32 apll;
+static u32 lpj;
+static struct regulator *vcore;
+static int vcore_uV;
+
+extern void ddr_suspend(void);
+extern void ddr_resume(void);
+
+static void __sramfunc rk29_pm_enter_ddr(void)
+{
+       u32 clksel0, dpll, mode;
+
+       asm("dsb");
+       ddr_suspend();
+
+       /* suspend ddr pll */
+       mode = cru_readl(CRU_MODE_CON);
+       dpll = cru_readl(CRU_DPLL_CON);
+       cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_DDR_MODE_MASK) | CRU_DDR_MODE_SLOW, CRU_MODE_CON);
+       cru_writel(dpll | PLL_BYPASS, CRU_DPLL_CON);
+       cru_writel(dpll | PLL_PD | PLL_BYPASS, CRU_DPLL_CON);
+       delay_500ns();
+
+       /* set arm clk 24MHz/32 = 750KHz */
+       clksel0 = cru_readl(CRU_CLKSEL0_CON);
+       cru_writel(clksel0 | 0x1F, CRU_CLKSEL0_CON);
+
+       asm("wfi");
+
+       /* resume arm clk */
+       cru_writel(clksel0, CRU_CLKSEL0_CON);
+
+       /* resume ddr pll */
+       cru_writel(dpll, CRU_DPLL_CON);
+       delay_300us();
+       cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_DDR_MODE_MASK) | (mode & CRU_DDR_MODE_MASK), CRU_MODE_CON);
+
+       ddr_resume();
+}
+
+static int rk29_pm_enter(suspend_state_t state)
+{
+       u32 cpll, gpll, mode;
+
+       mode = cru_readl(CRU_MODE_CON);
+
+       /* suspend codec pll */
+       cpll = cru_readl(CRU_CPLL_CON);
+       cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_CODEC_MODE_MASK) | CRU_CODEC_MODE_SLOW, CRU_MODE_CON);
+       cru_writel(cpll | PLL_BYPASS, CRU_CPLL_CON);
+       cru_writel(cpll | PLL_PD | PLL_BYPASS, CRU_CPLL_CON);
+       delay_500ns();
+
+       /* suspend general pll */
+       gpll = cru_readl(CRU_GPLL_CON);
+       cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_GENERAL_MODE_MASK) | CRU_GENERAL_MODE_SLOW, CRU_MODE_CON);
+       cru_writel(gpll | PLL_BYPASS, CRU_GPLL_CON);
+       cru_writel(gpll | PLL_PD | PLL_BYPASS, CRU_GPLL_CON);
+       delay_500ns();
+
+       DDR_SAVE_SP(save_sp);
+       rk29_pm_enter_ddr();
+       DDR_RESTORE_SP(save_sp);
+
+       /* resume general pll */
+       cru_writel(gpll, CRU_GPLL_CON);
+       delay_300us();
+       cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_GENERAL_MODE_MASK) | (mode & CRU_GENERAL_MODE_MASK), CRU_MODE_CON);
+
+       /* resume codec pll */
+       cru_writel(cpll, CRU_CPLL_CON);
+       delay_300us();
+       cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_CODEC_MODE_MASK) | (mode & CRU_CODEC_MODE_MASK), CRU_MODE_CON);
+
+       return 0;
+}
+
+static int rk29_pm_prepare(void)
+{
+       printk("+%s\n", __func__);
+       disable_hlt(); // disable entering rk29_idle() by disable_hlt()
+
+       /* suspend arm pll */
+       apll = cru_readl(CRU_APLL_CON);
+       cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_CPU_MODE_MASK) | CRU_CPU_MODE_SLOW, CRU_MODE_CON);
+       cru_writel(apll | PLL_BYPASS, CRU_APLL_CON);
+       cru_writel(apll | PLL_PD | PLL_BYPASS, CRU_APLL_CON);
+       delay_500ns();
+       lpj = loops_per_jiffy;
+       loops_per_jiffy = 120000; // make udelay/mdelay correct
+
+       if (vcore) {
+               vcore_uV = regulator_get_voltage(vcore);
+               regulator_set_voltage(vcore, 1000000, 1000000);
+       }
+       printk("-%s\n", __func__);
+       return 0;
+}
+
+static void rk29_pm_finish(void)
+{
+       printk("+%s\n", __func__);
+       if (vcore) {
+               regulator_set_voltage(vcore, vcore_uV, vcore_uV);
+       }
+
+       /* resume arm pll */
+       loops_per_jiffy = lpj;
+       cru_writel(apll, CRU_APLL_CON);
+       delay_300us();
+       cru_writel((cru_readl(CRU_MODE_CON) & ~CRU_CPU_MODE_MASK) | CRU_CPU_MODE_NORMAL, CRU_MODE_CON);
+
+       enable_hlt();
+       printk("-%s\n", __func__);
+}
+
+static struct platform_suspend_ops rk29_pm_ops = {
+       .enter          = rk29_pm_enter,
+       .valid          = suspend_valid_only_mem,
+       .prepare        = rk29_pm_prepare,
+       .finish         = rk29_pm_finish,
+};
+
+static void rk29_idle(void)
+{
+       if (!need_resched()) {
+               int allow_sleep = 1;
+#ifdef CONFIG_HAS_WAKELOCK
+               allow_sleep = !has_wake_lock(WAKE_LOCK_IDLE);
+#endif
+               if (allow_sleep) {
+                       u32 mode_con = cru_readl(CRU_MODE_CON);
+                       cru_writel((mode_con & ~CRU_CPU_MODE_MASK) | CRU_CPU_MODE_SLOW, CRU_MODE_CON);
+                       arch_idle();
+                       cru_writel((mode_con & ~CRU_CPU_MODE_MASK) | CRU_CPU_MODE_NORMAL, CRU_MODE_CON);
+               } else {
+                       arch_idle();
+               }
+       }
+       local_irq_enable();
+}
+
+static int __init rk29_pm_init(void)
+{
+       vcore = regulator_get(NULL, "vcore");
+       if (IS_ERR(vcore)) {
+               pr_err("pm: fail to get regulator vcore: %ld\n", PTR_ERR(vcore));
+               vcore = NULL;
+       }
+
+       suspend_set_ops(&rk29_pm_ops);
+
+       /* set idle function */
+       pm_idle = rk29_idle;
+
+       return 0;
+}
+__initcall(rk29_pm_init);