mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	ARM: imx: support arm power off in cpuidle for i.mx6sx
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>
This commit is contained in:
		
							parent
							
								
									df096fde08
								
							
						
					
					
						commit
						05136f0897
					
				
					 7 changed files with 144 additions and 8 deletions
				
			
		| 
						 | 
					@ -32,8 +32,7 @@ ifeq ($(CONFIG_CPU_IDLE),y)
 | 
				
			||||||
obj-$(CONFIG_SOC_IMX5) += cpuidle-imx5.o
 | 
					obj-$(CONFIG_SOC_IMX5) += cpuidle-imx5.o
 | 
				
			||||||
obj-$(CONFIG_SOC_IMX6Q) += cpuidle-imx6q.o
 | 
					obj-$(CONFIG_SOC_IMX6Q) += cpuidle-imx6q.o
 | 
				
			||||||
obj-$(CONFIG_SOC_IMX6SL) += cpuidle-imx6sl.o
 | 
					obj-$(CONFIG_SOC_IMX6SL) += cpuidle-imx6sl.o
 | 
				
			||||||
# i.MX6SX reuses i.MX6Q cpuidle driver
 | 
					obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6sx.o
 | 
				
			||||||
obj-$(CONFIG_SOC_IMX6SX) += cpuidle-imx6q.o
 | 
					 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ifdef CONFIG_SND_IMX_SOC
 | 
					ifdef CONFIG_SND_IMX_SOC
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,6 +70,10 @@ void imx_set_soc_revision(unsigned int rev);
 | 
				
			||||||
unsigned int imx_get_soc_revision(void);
 | 
					unsigned int imx_get_soc_revision(void);
 | 
				
			||||||
void imx_init_revision_from_anatop(void);
 | 
					void imx_init_revision_from_anatop(void);
 | 
				
			||||||
struct device *imx_soc_device_init(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 {
 | 
					enum mxc_cpu_pwr_mode {
 | 
				
			||||||
	WAIT_CLOCKED,		/* wfi only */
 | 
						WAIT_CLOCKED,		/* wfi only */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										107
									
								
								arch/arm/mach-imx/cpuidle-imx6sx.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								arch/arm/mach-imx/cpuidle-imx6sx.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@
 | 
				
			||||||
extern int imx5_cpuidle_init(void);
 | 
					extern int imx5_cpuidle_init(void);
 | 
				
			||||||
extern int imx6q_cpuidle_init(void);
 | 
					extern int imx6q_cpuidle_init(void);
 | 
				
			||||||
extern int imx6sl_cpuidle_init(void);
 | 
					extern int imx6sl_cpuidle_init(void);
 | 
				
			||||||
 | 
					extern int imx6sx_cpuidle_init(void);
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
static inline int imx5_cpuidle_init(void)
 | 
					static inline int imx5_cpuidle_init(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -27,4 +28,8 @@ static inline int imx6sl_cpuidle_init(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					static inline int imx6sx_cpuidle_init(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define GPC_IMR1		0x008
 | 
					#define GPC_IMR1		0x008
 | 
				
			||||||
#define GPC_PGC_CPU_PDN		0x2a0
 | 
					#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
 | 
					#define IMR_NUM			4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +31,23 @@ static void __iomem *gpc_base;
 | 
				
			||||||
static u32 gpc_wake_irqs[IMR_NUM];
 | 
					static u32 gpc_wake_irqs[IMR_NUM];
 | 
				
			||||||
static u32 gpc_saved_imrs[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 imx_gpc_pre_suspend(bool arm_power_off)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
 | 
						void __iomem *reg_imr1 = gpc_base + GPC_IMR1;
 | 
				
			||||||
| 
						 | 
					@ -34,7 +55,7 @@ void imx_gpc_pre_suspend(bool arm_power_off)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Tell GPC to power off ARM core when suspend */
 | 
						/* Tell GPC to power off ARM core when suspend */
 | 
				
			||||||
	if (arm_power_off)
 | 
						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++) {
 | 
						for (i = 0; i < IMR_NUM; i++) {
 | 
				
			||||||
		gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);
 | 
							gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4);
 | 
				
			||||||
| 
						 | 
					@ -48,7 +69,7 @@ void imx_gpc_post_resume(void)
 | 
				
			||||||
	int i;
 | 
						int i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Keep ARM core powered on for other low-power modes */
 | 
						/* 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++)
 | 
						for (i = 0; i < IMR_NUM; i++)
 | 
				
			||||||
		writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4);
 | 
							writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -90,7 +90,7 @@ static void __init imx6sx_init_irq(void)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void __init imx6sx_init_late(void)
 | 
					static void __init imx6sx_init_late(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	imx6q_cpuidle_init();
 | 
						imx6sx_cpuidle_init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ))
 | 
						if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ))
 | 
				
			||||||
		platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0);
 | 
							platform_device_register_simple("imx6q-cpufreq", -1, NULL, 0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -205,7 +205,7 @@ void imx6q_set_int_mem_clk_lpm(bool enable)
 | 
				
			||||||
	writel_relaxed(val, ccm_base + CGPR);
 | 
						writel_relaxed(val, ccm_base + CGPR);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void imx6q_enable_rbc(bool enable)
 | 
					void imx6_enable_rbc(bool enable)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	u32 val;
 | 
						u32 val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -359,7 +359,7 @@ static int imx6q_pm_enter(suspend_state_t state)
 | 
				
			||||||
		 * RBC setting, so we do NOT need to do that here.
 | 
							 * RBC setting, so we do NOT need to do that here.
 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
		if (!imx6_suspend_in_ocram_fn)
 | 
							if (!imx6_suspend_in_ocram_fn)
 | 
				
			||||||
			imx6q_enable_rbc(true);
 | 
								imx6_enable_rbc(true);
 | 
				
			||||||
		imx_gpc_pre_suspend(true);
 | 
							imx_gpc_pre_suspend(true);
 | 
				
			||||||
		imx_anatop_pre_suspend();
 | 
							imx_anatop_pre_suspend();
 | 
				
			||||||
		/* Zzz ... */
 | 
							/* Zzz ... */
 | 
				
			||||||
| 
						 | 
					@ -368,7 +368,7 @@ static int imx6q_pm_enter(suspend_state_t state)
 | 
				
			||||||
			imx_smp_prepare();
 | 
								imx_smp_prepare();
 | 
				
			||||||
		imx_anatop_post_resume();
 | 
							imx_anatop_post_resume();
 | 
				
			||||||
		imx_gpc_post_resume();
 | 
							imx_gpc_post_resume();
 | 
				
			||||||
		imx6q_enable_rbc(false);
 | 
							imx6_enable_rbc(false);
 | 
				
			||||||
		imx6q_enable_wb(false);
 | 
							imx6q_enable_wb(false);
 | 
				
			||||||
		imx6q_set_int_mem_clk_lpm(true);
 | 
							imx6q_set_int_mem_clk_lpm(true);
 | 
				
			||||||
		imx6q_set_lpm(WAIT_CLOCKED);
 | 
							imx6q_set_lpm(WAIT_CLOCKED);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue