[RFC PATCH 3/4] ARM: EXYNOS4: Add support AFTR mode cpuidle state on EXYNOS4210

Amit Daniel Kachhap amit.kachhap at linaro.org
Fri Aug 19 09:09:59 EDT 2011


This patch adds support AFTR(ARM OFF TOP RUNNING) mode in
cpuidle driver. L2 cache keeps their data in this mode.

Signed-off-by: Jaecheol Lee <jc.lee at samsung.com>
Signed-off-by: Amit Daniel Kachhap <amit.kachhap at linaro.org>
---
 arch/arm/mach-exynos4/Makefile           |    2 +-
 arch/arm/mach-exynos4/cpuidle.c          |  131 +++++++++++++++++++++++-
 arch/arm/mach-exynos4/idle.S             |  165 ++++++++++++++++++++++++++++++
 arch/arm/mach-exynos4/include/mach/pmu.h |    5 +-
 4 files changed, 300 insertions(+), 3 deletions(-)
 create mode 100644 arch/arm/mach-exynos4/idle.S

diff --git a/arch/arm/mach-exynos4/Makefile b/arch/arm/mach-exynos4/Makefile
index 2e3a407..12568b0 100644
--- a/arch/arm/mach-exynos4/Makefile
+++ b/arch/arm/mach-exynos4/Makefile
@@ -16,7 +16,7 @@ obj-$(CONFIG_CPU_EXYNOS4210)	+= cpu.o init.o clock.o irq-combiner.o
 obj-$(CONFIG_CPU_EXYNOS4210)	+= setup-i2c0.o irq-eint.o dma.o pmu.o
 obj-$(CONFIG_PM)		+= pm.o sleep.o
 obj-$(CONFIG_CPU_FREQ)		+= cpufreq.o
-obj-$(CONFIG_CPU_IDLE)		+= cpuidle.o
+obj-$(CONFIG_CPU_IDLE)		+= cpuidle.o idle.o
 
 obj-$(CONFIG_SMP)		+= platsmp.o headsmp.o
 
diff --git a/arch/arm/mach-exynos4/cpuidle.c b/arch/arm/mach-exynos4/cpuidle.c
index bf7e96f..1164945 100644
--- a/arch/arm/mach-exynos4/cpuidle.c
+++ b/arch/arm/mach-exynos4/cpuidle.c
@@ -12,12 +12,24 @@
 #include <linux/init.h>
 #include <linux/cpuidle.h>
 #include <linux/io.h>
+#include <linux/suspend.h>
 
 #include <asm/proc-fns.h>
+#include <asm/hardware/cache-l2x0.h>
+#include <asm/cacheflush.h>
+
+#include <mach/regs-pmu.h>
+#include <mach/pmu.h>
+
+#define REG_DIRECTGO_ADDR	(S5P_VA_SYSRAM + 0x24)
+#define REG_DIRECTGO_FLAG	(S5P_VA_SYSRAM + 0x20)
 
 static int exynos4_enter_idle(struct cpuidle_device *dev,
 			      struct cpuidle_state *state);
 
+static int exynos4_enter_lowpower(struct cpuidle_device *dev,
+				  struct cpuidle_state *state);
+
 static struct cpuidle_state exynos4_cpuidle_set[] = {
 	[0] = {
 		.enter			= exynos4_enter_idle,
@@ -27,6 +39,14 @@ static struct cpuidle_state exynos4_cpuidle_set[] = {
 		.name			= "IDLE",
 		.desc			= "ARM clock gating(WFI)",
 	},
+	[1] = {
+		.enter			= exynos4_enter_lowpower,
+		.exit_latency		= 300,
+		.target_residency	= 100000,
+		.flags			= CPUIDLE_FLAG_TIME_VALID,
+		.name			= "LOW_POWER",
+		.desc			= "ARM power down",
+	},
 };
 
 static DEFINE_PER_CPU(struct cpuidle_device, exynos4_cpuidle_device);
@@ -36,6 +56,80 @@ static struct cpuidle_driver exynos4_idle_driver = {
 	.owner		= THIS_MODULE,
 };
 
+void exynos4_cpu_lp(void *stack_addr)
+{
+	/*
+	 * Refer to v7 cpu_suspend function.
+	 * From saveblk to stack_addr + (4 * 3) + (4 * 9)
+	 * 4byte * (v:p offset, virt sp, phy resume fn)
+	 * cpu_suspend_size = 4 * 9 (from proc-v7.S)
+	 * Min L2 cache clean size = 36 + 12 + 36 = 84
+	 */
+
+	outer_clean_range(virt_to_phys(stack_addr), 84);
+
+	/* To clean sleep_save_sp area */
+
+	outer_clean_range(virt_to_phys(cpu_resume), 64);
+
+	cpu_do_idle();
+}
+
+/* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */
+static void exynos4_set_wakeupmask(void)
+{
+	__raw_writel(0x0000ff3e, S5P_WAKEUP_MASK);
+}
+
+static int exynos4_enter_core0_aftr(struct cpuidle_device *dev,
+				    struct cpuidle_state *state)
+{
+	struct timeval before, after;
+	int idle_time;
+	unsigned long tmp;
+
+	local_irq_disable();
+	do_gettimeofday(&before);
+
+	exynos4_set_wakeupmask();
+
+	__raw_writel(virt_to_phys(exynos4_idle_resume), REG_DIRECTGO_ADDR);
+	__raw_writel(0xfcba0d10, REG_DIRECTGO_FLAG);
+
+	/* Set value of power down register for aftr mode */
+	exynos4_sys_powerdown_conf(SYS_AFTR);
+
+	/* Setting Central Sequence Register for power down mode */
+	tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
+	tmp &= ~S5P_CENTRAL_LOWPWR_CFG;
+	__raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
+
+	exynos4_enter_lp(0, PLAT_PHYS_OFFSET - PAGE_OFFSET);
+
+	/*
+	 * If PMU failed while entering sleep mode, WFI will be
+	 * ignored by PMU and then exiting cpu_do_idle().
+	 * S5P_CENTRAL_LOWPWR_CFG bit will not be set automatically
+	 * in this situation.
+	 */
+	tmp = __raw_readl(S5P_CENTRAL_SEQ_CONFIGURATION);
+	if (!(tmp & S5P_CENTRAL_LOWPWR_CFG)) {
+		tmp |= S5P_CENTRAL_LOWPWR_CFG;
+		__raw_writel(tmp, S5P_CENTRAL_SEQ_CONFIGURATION);
+	}
+	cpu_init();
+	/* Clear wakeup state register */
+	__raw_writel(0x0, S5P_WAKEUP_STAT);
+
+	do_gettimeofday(&after);
+
+	local_irq_enable();
+	idle_time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC +
+		    (after.tv_usec - before.tv_usec);
+
+	return idle_time;
+}
+
 static int exynos4_enter_idle(struct cpuidle_device *dev,
 			      struct cpuidle_state *state)
 {
@@ -55,6 +149,26 @@ static int exynos4_enter_idle(struct cpuidle_device *dev,
 	return idle_time;
 }
 
+static int exynos4_enter_lowpower(struct cpuidle_device *dev,
+				  struct cpuidle_state *state)
+{
+	struct cpuidle_state *new_state = state;
+
+	/* This mode only can be entered when Core1 is offline */
+	if (cpu_online(1)) {
+		BUG_ON(!dev->safe_state);
+		new_state = dev->safe_state;
+	}
+	dev->last_state = new_state;
+
+	if (new_state == &dev->states[0])
+		return exynos4_enter_idle(dev, new_state);
+	else
+		return exynos4_enter_core0_aftr(dev, new_state);
+
+	return exynos4_enter_idle(dev, new_state);
+}
+
 static int __init exynos4_init_cpuidle(void)
 {
 	int i, max_cpuidle_state, cpu_id;
@@ -66,8 +180,11 @@ static int __init exynos4_init_cpuidle(void)
 		device = &per_cpu(exynos4_cpuidle_device, cpu_id);
 		device->cpu = cpu_id;
 
-		device->state_count = (sizeof(exynos4_cpuidle_set) /
+		if (cpu_id == 0)
+			device->state_count = (sizeof(exynos4_cpuidle_set) /
 					       sizeof(struct cpuidle_state));
+		else
+			device->state_count = 1;	/* Support IDLE only */
 
 		max_cpuidle_state = device->state_count;
 
@@ -76,11 +193,23 @@ static int __init exynos4_init_cpuidle(void)
 					sizeof(struct cpuidle_state));
 		}
 
+		device->safe_state = &device->states[0];
+
 		if (cpuidle_register_device(device)) {
 			printk(KERN_ERR "CPUidle register device failed\n,");
 			return -EIO;
 		}
 	}
+
+	l2cc_save[0] = __raw_readl(S5P_VA_L2CC + L2X0_PREFETCH_CTRL);
+	l2cc_save[1] = __raw_readl(S5P_VA_L2CC + L2X0_POWER_CTRL);
+	l2cc_save[2] = __raw_readl(S5P_VA_L2CC + L2X0_TAG_LATENCY_CTRL);
+	l2cc_save[3] = __raw_readl(S5P_VA_L2CC + L2X0_DATA_LATENCY_CTRL);
+	l2cc_save[4] = __raw_readl(S5P_VA_L2CC + L2X0_AUX_CTRL);
+
+	clean_dcache_area(&l2cc_save[0], 5 * sizeof(unsigned long));
+	outer_clean_range(virt_to_phys(&l2cc_save[0]),
+			  virt_to_phys(&l2cc_save[4] + sizeof(unsigned long)));
 	return 0;
 }
 device_initcall(exynos4_init_cpuidle);
diff --git a/arch/arm/mach-exynos4/idle.S b/arch/arm/mach-exynos4/idle.S
new file mode 100644
index 0000000..5a3cd41
--- /dev/null
+++ b/arch/arm/mach-exynos4/idle.S
@@ -0,0 +1,165 @@
+/* linux/arch/arm/mach-exynos4/idle.S
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * EXYNOS4210 AFTR/LPA idle support
+ *
+ * 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/linkage.h>
+
+#include <asm/assembler.h>
+#include <asm/memory.h>
+#include <asm/hardware/cache-l2x0.h>
+
+#include <mach/map.h>
+
+	.text
+
+	/*
+	 * exynos4_enter_lp
+	 *
+	 * entry:
+	 *	r1 = v:p offset
+	 */
+
+ENTRY(exynos4_enter_lp)
+	stmfd	sp!, { r3 - r12, lr }
+
+	adr	r0, sleep_save_misc
+
+	mrc	p15, 0, r2, c15, c0, 0	@ read power control register
+	str	r2, [r0], #4
+
+	mrc	p15, 0, r2, c15, c0, 1	@ read diagnostic register
+	str	r2, [r0], #4
+
+	ldr	r3, =resume_with_mmu
+	bl	cpu_suspend
+
+	mov	r0, sp
+	bl	exynos4_cpu_lp
+
+	/* Restore original sp */
+	mov	r0, sp
+	add	r0, r0, #4
+	ldr	sp, [r0]
+
+	mov	r0, #0
+	b	early_wakeup
+
+resume_with_mmu:
+
+	adr	r0, sleep_save_misc
+
+	ldr	r1, [r0], #4
+	mcr	p15, 0, r1, c15, c0, 0	@ write power control register
+
+	ldr	r1, [r0], #4
+	mcr	p15, 0, r1, c15, c0, 1	@ write diagnostic register
+
+	mov	r0, #1
+early_wakeup:
+
+	ldmfd	sp!, { r3 - r12, pc }
+
+	.ltorg
+
+	/*
+	 * sleep magic, to allow the bootloader to check for an valid
+	 * image to resume to. Must be the first word before the
+	 * s3c_cpu_resume entry.
+	 */
+
+	.word	0x2bedf00d
+
+sleep_save_misc:
+	.long	0
+	.long	0
+
+	/*
+	 * exynos4_idle_resume
+	 *
+	 * resume code entry for IROM to call
+	 *
+	 * we must put this code here in the data segment as we have no
+	 * other way of restoring the stack pointer after sleep, and we
+	 * must not write to the code segment (code is read-only)
+	 */
+	.data
+	.align
+ENTRY(exynos4_idle_resume)
+	ldr	r0, scu_pa_addr		@ load physica address of SCU
+	ldr	r1, [r0]
+	orr	r1, r1, #1
+	orr	r1, r1, #(1 << 5)
+	str	r1, [r0]		@ enable SCU
+
+	ldr	r0, l2cc_pa_addr	@ load physical address of L2CC
+
+	ldr	r1, l2cc_tag_latency_ctrl	@ tag latency register offset
+	add	r1, r0, r1
+	ldr	r2, l2cc_tag_data	@ load saved tag latency register
+	str	r2, [r1]		@ store saved value to register
+
+	ldr	r1, l2cc_data_latency_ctrl	@ data latency register offset
+	add	r1, r0, r1
+	ldr	r2, l2cc_data_data	@ load saved data latency register
+	str	r2, [r1]		@ store saved value to register
+
+	ldr	r1, l2cc_prefetch_ctrl	@ prefetch control register offset
+	add	r1, r0, r1
+	ldr	r2, l2cc_prefetch_data	@ load saved prefetch control register
+	str	r2, [r1]		@ store saved value to register
+
+	ldr	r1, l2cc_pwr_ctrl	@ power control register offset
+	add	r1, r0, r1
+	ldr	r2, l2cc_pwr_data	@ load saved power control register
+	str	r2, [r1]		@ store saved value to register
+
+	ldr	r1, l2cc_aux_ctrl	@ aux control register offset
+	add	r1, r0, r1
+	ldr	r2, l2cc_aux_data	@ load saved aux control register
+	str	r2, [r1]		@ store saved value to register
+
+	ldr	r1, l2cc_ctrl		@ control register offset
+	add	r1, r0, r1
+	mov	r2, #1			@ enable L2CC
+	str	r2, [r1]
+
+	b	cpu_resume
+ENDPROC(exynos4_idle_resume)
+
+	.global	l2cc_save
+
+scu_pa_addr:
+	.word	EXYNOS4_PA_COREPERI
+l2cc_pa_addr:
+	.word	EXYNOS4_PA_L2CC
+l2cc_prefetch_ctrl:
+	.word	L2X0_PREFETCH_CTRL
+l2cc_pwr_ctrl:
+	.word	L2X0_POWER_CTRL
+l2cc_tag_latency_ctrl:
+	.word	L2X0_TAG_LATENCY_CTRL
+l2cc_data_latency_ctrl:
+	.word	L2X0_DATA_LATENCY_CTRL
+l2cc_aux_ctrl:
+	.word	L2X0_AUX_CTRL
+l2cc_ctrl:
+	.word	L2X0_CTRL
+l2cc_save:
+l2cc_prefetch_data:
+	.long	0
+l2cc_pwr_data:
+	.long	0
+l2cc_tag_data:
+	.long	0
+l2cc_data_data:
+	.long	0
+l2cc_aux_data:
+	.long	0
diff --git a/arch/arm/mach-exynos4/include/mach/pmu.h b/arch/arm/mach-exynos4/include/mach/pmu.h
index a952904..960456f 100644
--- a/arch/arm/mach-exynos4/include/mach/pmu.h
+++ b/arch/arm/mach-exynos4/include/mach/pmu.h
@@ -21,5 +21,8 @@ enum sys_powerdown {
 };
 
 extern void exynos4_sys_powerdown_conf(enum sys_powerdown mode);
-
+extern void exynos4_idle_resume(void);
+extern void exynos4_enter_lp(unsigned long arg, long offset);
+/* Keep following save sequence prefetch, power, tag, data, aux */
+extern unsigned long l2cc_save[5];
 #endif /* __ASM_ARCH_PMU_H */
-- 
1.7.1




More information about the linux-arm-kernel mailing list