Skip to content

Commit

Permalink
ARM: imx: support arm power off in cpuidle for i.mx6sx
Browse files Browse the repository at this point in the history
This patch introduces an independent cpuidle driver for
i.MX6SX, and supports arm power off in idle, totally
3 levels of cpuidle are supported as below:

1. ARM WFI;
2. SOC in WAIT mode;
3. SOC in WAIT mode + ARM power off.

ARM power off can save at least 5mW power.

This patch also replaces imx6q_enable_rbc with imx6_enable_rbc.

Signed-off-by: Anson Huang <b20788@freescale.com>
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
  • Loading branch information
Anson Huang authored and Shawn Guo committed Jan 5, 2015
1 parent df096fd commit 05136f0
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 8 deletions.
3 changes: 1 addition & 2 deletions arch/arm/mach-imx/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ ifeq ($(CONFIG_CPU_IDLE),y)
obj-$(CONFIG_SOC_IMX5) += cpuidle-imx5.o
obj-$(CONFIG_SOC_IMX6Q) += cpuidle-imx6q.o
obj-$(CONFIG_SOC_IMX6SL) += cpuidle-imx6sl.o
# i.MX6SX reuses i.MX6Q cpuidle driver
obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6q.o
obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o
endif

ifdef CONFIG_SND_IMX_SOC
Expand Down
4 changes: 4 additions & 0 deletions arch/arm/mach-imx/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ void imx_set_soc_revision(unsigned int rev);
unsigned int imx_get_soc_revision(void);
void imx_init_revision_from_anatop(void);
struct device *imx_soc_device_init(void);
void imx6_enable_rbc(bool enable);
void imx_gpc_set_arm_power_in_lpm(bool power_off);
void imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw);
void imx_gpc_set_arm_power_down_timing(u32 sw2iso, u32 sw);

enum mxc_cpu_pwr_mode {
WAIT_CLOCKED, /* wfi only */
Expand Down
107 changes: 107 additions & 0 deletions arch/arm/mach-imx/cpuidle-imx6sx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (C) 2014 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/

#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/module.h>
#include <asm/cpuidle.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>

#include "common.h"
#include "cpuidle.h"

static int imx6sx_idle_finish(unsigned long val)
{
cpu_do_idle();

return 0;
}

static int imx6sx_enter_wait(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
imx6q_set_lpm(WAIT_UNCLOCKED);

switch (index) {
case 1:
cpu_do_idle();
break;
case 2:
imx6_enable_rbc(true);
imx_gpc_set_arm_power_in_lpm(true);
imx_set_cpu_jump(0, v7_cpu_resume);
/* Need to notify there is a cpu pm operation. */
cpu_pm_enter();
cpu_cluster_pm_enter();

cpu_suspend(0, imx6sx_idle_finish);

cpu_cluster_pm_exit();
cpu_pm_exit();
imx_gpc_set_arm_power_in_lpm(false);
imx6_enable_rbc(false);
break;
default:
break;
}

imx6q_set_lpm(WAIT_CLOCKED);

return index;
}

static struct cpuidle_driver imx6sx_cpuidle_driver = {
.name = "imx6sx_cpuidle",
.owner = THIS_MODULE,
.states = {
/* WFI */
ARM_CPUIDLE_WFI_STATE,
/* WAIT */
{
.exit_latency = 50,
.target_residency = 75,
.flags = CPUIDLE_FLAG_TIME_VALID |
CPUIDLE_FLAG_TIMER_STOP,
.enter = imx6sx_enter_wait,
.name = "WAIT",
.desc = "Clock off",
},
/* WAIT + ARM power off */
{
/*
* ARM gating 31us * 5 + RBC clear 65us
* and some margin for SW execution, here set it
* to 300us.
*/
.exit_latency = 300,
.target_residency = 500,
.flags = CPUIDLE_FLAG_TIME_VALID,
.enter = imx6sx_enter_wait,
.name = "LOW-POWER-IDLE",
.desc = "ARM power off",
},
},
.state_count = 3,
.safe_state_index = 0,
};

int __init imx6sx_cpuidle_init(void)
{
imx6_enable_rbc(false);
/*
* set ARM power up/down timing to the fastest,
* sw2iso and sw can be set to one 32K cycle = 31us
* except for power up sw2iso which need to be
* larger than LDO ramp up time.
*/
imx_gpc_set_arm_power_up_timing(2, 1);
imx_gpc_set_arm_power_down_timing(1, 1);

return cpuidle_register(&imx6sx_cpuidle_driver, NULL);
}
5 changes: 5 additions & 0 deletions arch/arm/mach-imx/cpuidle.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
extern int imx5_cpuidle_init(void);
extern int imx6q_cpuidle_init(void);
extern int imx6sl_cpuidle_init(void);
extern int imx6sx_cpuidle_init(void);
#else
static inline int imx5_cpuidle_init(void)
{
Expand All @@ -27,4 +28,8 @@ static inline int imx6sl_cpuidle_init(void)
{
return 0;
}
static inline int imx6sx_cpuidle_init(void)
{
return 0;
}
#endif
25 changes: 23 additions & 2 deletions arch/arm/mach-imx/gpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,42 @@

#define GPC_IMR1 0x008
#define GPC_PGC_CPU_PDN 0x2a0
#define GPC_PGC_CPU_PUPSCR 0x2a4
#define GPC_PGC_CPU_PDNSCR 0x2a8
#define GPC_PGC_SW2ISO_SHIFT 0x8
#define GPC_PGC_SW_SHIFT 0x0

#define IMR_NUM 4

static void __iomem *gpc_base;
static u32 gpc_wake_irqs[IMR_NUM];
static u32 gpc_saved_imrs[IMR_NUM];

void imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw)
{
writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) |
(sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PUPSCR);
}

void imx_gpc_set_arm_power_down_timing(u32 sw2iso, u32 sw)
{
writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) |
(sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PDNSCR);
}

void imx_gpc_set_arm_power_in_lpm(bool power_off)
{
writel_relaxed(power_off, gpc_base + GPC_PGC_CPU_PDN);
}

void imx_gpc_pre_suspend(bool arm_power_off)
{
void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
int i;

/* Tell GPC to power off ARM core when suspend */
if (arm_power_off)
writel_relaxed(0x1, gpc_base + GPC_PGC_CPU_PDN);
imx_gpc_set_arm_power_in_lpm(arm_power_off);

for (i = 0; i < IMR_NUM; i++) {
gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);
Expand All @@ -48,7 +69,7 @@ void imx_gpc_post_resume(void)
int i;

/* Keep ARM core powered on for other low-power modes */
writel_relaxed(0x0, gpc_base + GPC_PGC_CPU_PDN);
imx_gpc_set_arm_power_in_lpm(false);

for (i = 0; i < IMR_NUM; i++)
writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4);
Expand Down
2 changes: 1 addition & 1 deletion arch/arm/mach-imx/mach-imx6sx.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ static void __init imx6sx_init_irq(void)

static void __init imx6sx_init_late(void)
{
imx6q_cpuidle_init();
imx6sx_cpuidle_init();

if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ))
platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0);
Expand Down
6 changes: 3 additions & 3 deletions arch/arm/mach-imx/pm-imx6.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ void imx6q_set_int_mem_clk_lpm(bool enable)
writel_relaxed(val, ccm_base + CGPR);
}

static void imx6q_enable_rbc(bool enable)
void imx6_enable_rbc(bool enable)
{
u32 val;

Expand Down Expand Up @@ -359,7 +359,7 @@ static int imx6q_pm_enter(suspend_state_t state)
* RBC setting, so we do NOT need to do that here.
*/
if (!imx6_suspend_in_ocram_fn)
imx6q_enable_rbc(true);
imx6_enable_rbc(true);
imx_gpc_pre_suspend(true);
imx_anatop_pre_suspend();
/* Zzz ... */
Expand All @@ -368,7 +368,7 @@ static int imx6q_pm_enter(suspend_state_t state)
imx_smp_prepare();
imx_anatop_post_resume();
imx_gpc_post_resume();
imx6q_enable_rbc(false);
imx6_enable_rbc(false);
imx6q_enable_wb(false);
imx6q_set_int_mem_clk_lpm(true);
imx6q_set_lpm(WAIT_CLOCKED);
Expand Down

0 comments on commit 05136f0

Please sign in to comment.