@@ -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
@@ -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);
new file mode 100644
@@ -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
@@ -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 */