[PATCH] ARM: EXYNOS4: Add support AFTR mode on EXYNOS4210
Jaecheol Lee
jc.lee at samsung.com
Fri Jul 8 05:43:55 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>
---
arch/arm/mach-exynos4/Makefile | 2 +-
arch/arm/mach-exynos4/cpuidle.c | 139 ++++++++++++++++++++++++++++++++++++++-
arch/arm/mach-exynos4/idle.S | 112 +++++++++++++++++++++++++++++++
3 files changed, 251 insertions(+), 2 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 60fe5ec..7de3865 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
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..0987e81 100644
--- a/arch/arm/mach-exynos4/cpuidle.c
+++ b/arch/arm/mach-exynos4/cpuidle.c
@@ -14,10 +14,22 @@
#include <linux/io.h>
#include <asm/proc-fns.h>
+#include <asm/suspend.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,85 @@ static struct cpuidle_driver exynos4_idle_driver = {
.owner = THIS_MODULE,
};
+void exynos4_cpu_lp(unsigned long arg)
+{
+ unsigned int sp;
+
+ /* get current stack address */
+ asm("mov %0, sp" : "=r" (sp) : : "cc");
+
+ /*
+ * 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((void *)sp), 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);
+}
+
+extern void exynos4_idle_resume(void);
+
+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);
+
+ cpu_suspend(0, exynos4_cpu_lp);
+
+ /*
+ * 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);
+ }
+
+ /* 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 +154,29 @@ 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);
+}
+
+/* Keep following save sequence prefetch, power, tag, data, aux */
+extern unsigned long l2cc_save[5];
+
static int __init exynos4_init_cpuidle(void)
{
int i, max_cpuidle_state, cpu_id;
@@ -66,8 +188,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 +201,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..21e934c
--- /dev/null
+++ b/arch/arm/mach-exynos4/idle.S
@@ -0,0 +1,112 @@
+/* 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
+
+ /*
+ * 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
+
+ /*
+ * 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
--
1.7.1
More information about the linux-arm-kernel
mailing list