From 4d75833447deecd29d73eaca82ecb7838d49f558 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 19 Mar 2012 20:15:50 -0500 Subject: ARM: EXYNOS: Add support AFTR mode on EXYNOS4210 This patch adds support AFTR(ARM OFF TOP RUNNING) mode in cpuidle driver. L2 cache keeps their data in this mode. This patch ports the code to the latest interfaces to save/restore CPU state inclusive of CPU PM notifiers, l2 resume and cpu_suspend/resume. Signed-off-by: Jaecheol Lee Signed-off-by: Lorenzo Pieralisi Signed-off-by: Amit Daniel Kachhap [kgene.kim@samsung.com: fixed for non-smp as per Tushar's pointing out] Signed-off-by: Kukjin Kim --- arch/arm/mach-exynos/cpuidle.c | 151 +++++++++++++++++++++++++++++++- arch/arm/mach-exynos/include/mach/pmu.h | 2 + 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/arch/arm/mach-exynos/cpuidle.c b/arch/arm/mach-exynos/cpuidle.c index 4ebb382c597..33ab4e7558a 100644 --- a/arch/arm/mach-exynos/cpuidle.c +++ b/arch/arm/mach-exynos/cpuidle.c @@ -11,25 +11,53 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include +#include +#include + +#include + +#define REG_DIRECTGO_ADDR (samsung_rev() == EXYNOS4210_REV_1_1 ? \ + S5P_INFORM7 : (samsung_rev() == EXYNOS4210_REV_1_0 ? \ + (S5P_VA_SYSRAM + 0x24) : S5P_INFORM0)) +#define REG_DIRECTGO_FLAG (samsung_rev() == EXYNOS4210_REV_1_1 ? \ + S5P_INFORM6 : (samsung_rev() == EXYNOS4210_REV_1_0 ? \ + (S5P_VA_SYSRAM + 0x20) : S5P_INFORM1)) + +#define S5P_CHECK_AFTR 0xFCBA0D10 static int exynos4_enter_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index); +static int exynos4_enter_lowpower(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index); -static struct cpuidle_state exynos4_cpuidle_set[] = { +static struct cpuidle_state exynos4_cpuidle_set[] __initdata = { [0] = { .enter = exynos4_enter_idle, .exit_latency = 1, .target_residency = 100000, .flags = CPUIDLE_FLAG_TIME_VALID, - .name = "IDLE", + .name = "C0", .desc = "ARM clock gating(WFI)", }, + [1] = { + .enter = exynos4_enter_lowpower, + .exit_latency = 300, + .target_residency = 100000, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "C1", + .desc = "ARM power down", + }, }; static DEFINE_PER_CPU(struct cpuidle_device, exynos4_cpuidle_device); @@ -39,9 +67,102 @@ static struct cpuidle_driver exynos4_idle_driver = { .owner = THIS_MODULE, }; +/* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */ +static void exynos4_set_wakeupmask(void) +{ + __raw_writel(0x0000ff3e, S5P_WAKEUP_MASK); +} + +static unsigned int g_pwr_ctrl, g_diag_reg; + +static void save_cpu_arch_register(void) +{ + /*read power control register*/ + asm("mrc p15, 0, %0, c15, c0, 0" : "=r"(g_pwr_ctrl) : : "cc"); + /*read diagnostic register*/ + asm("mrc p15, 0, %0, c15, c0, 1" : "=r"(g_diag_reg) : : "cc"); + return; +} + +static void restore_cpu_arch_register(void) +{ + /*write power control register*/ + asm("mcr p15, 0, %0, c15, c0, 0" : : "r"(g_pwr_ctrl) : "cc"); + /*write diagnostic register*/ + asm("mcr p15, 0, %0, c15, c0, 1" : : "r"(g_diag_reg) : "cc"); + return; +} + +static int idle_finisher(unsigned long flags) +{ + cpu_do_idle(); + return 1; +} + +static int exynos4_enter_core0_aftr(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + struct timeval before, after; + int idle_time; + unsigned long tmp; + + local_irq_disable(); + do_gettimeofday(&before); + + exynos4_set_wakeupmask(); + + /* Set value of power down register for aftr mode */ + exynos4_sys_powerdown_conf(SYS_AFTR); + + __raw_writel(virt_to_phys(s3c_cpu_resume), REG_DIRECTGO_ADDR); + __raw_writel(S5P_CHECK_AFTR, REG_DIRECTGO_FLAG); + + save_cpu_arch_register(); + + /* 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_pm_enter(); + cpu_suspend(0, idle_finisher); + +#ifdef CONFIG_SMP + scu_enable(S5P_VA_SCU); +#endif + cpu_pm_exit(); + + restore_cpu_arch_register(); + + /* + * 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); + + dev->last_residency = idle_time; + return index; +} + static int exynos4_enter_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, - int index) + int index) { struct timeval before, after; int idle_time; @@ -60,6 +181,22 @@ static int exynos4_enter_idle(struct cpuidle_device *dev, return index; } +static int exynos4_enter_lowpower(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + int new_index = index; + + /* This mode only can be entered when other core's are offline */ + if (num_online_cpus() > 1) + new_index = drv->safe_state_index; + + if (new_index == 0) + return exynos4_enter_idle(dev, drv, new_index); + else + return exynos4_enter_core0_aftr(dev, drv, new_index); +} + static int __init exynos4_init_cpuidle(void) { int i, max_cpuidle_state, cpu_id; @@ -74,19 +211,25 @@ static int __init exynos4_init_cpuidle(void) memcpy(&drv->states[i], &exynos4_cpuidle_set[i], sizeof(struct cpuidle_state)); } + drv->safe_state_index = 0; cpuidle_register_driver(&exynos4_idle_driver); for_each_cpu(cpu_id, cpu_online_mask) { device = &per_cpu(exynos4_cpuidle_device, cpu_id); device->cpu = cpu_id; - device->state_count = drv->state_count; + if (cpu_id == 0) + device->state_count = (sizeof(exynos4_cpuidle_set) / + sizeof(struct cpuidle_state)); + else + device->state_count = 1; /* Support IDLE only */ if (cpuidle_register_device(device)) { printk(KERN_ERR "CPUidle register device failed\n,"); return -EIO; } } + return 0; } device_initcall(exynos4_init_cpuidle); diff --git a/arch/arm/mach-exynos/include/mach/pmu.h b/arch/arm/mach-exynos/include/mach/pmu.h index 632dd563013..e76b7faba66 100644 --- a/arch/arm/mach-exynos/include/mach/pmu.h +++ b/arch/arm/mach-exynos/include/mach/pmu.h @@ -22,11 +22,13 @@ enum sys_powerdown { NUM_SYS_POWERDOWN, }; +extern unsigned long l2x0_regs_phys; struct exynos4_pmu_conf { void __iomem *reg; unsigned int val[NUM_SYS_POWERDOWN]; }; extern void exynos4_sys_powerdown_conf(enum sys_powerdown mode); +extern void s3c_cpu_resume(void); #endif /* __ASM_ARCH_PMU_H */ -- cgit v1.2.3 From ad393c1fd8e28dcbba6d29f0eea2b4a261c4696c Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 19 Mar 2012 20:15:50 -0500 Subject: ARM: S5P: add L2 early resume code This patch adds code to resume L2 before MMU is enabled in suspend and cpuidle resume paths. s3c_cpu_resume is moved to the data section with appropriate comments. Signed-off-by: Lorenzo Pieralisi Signed-off-by: Amit Daniel Kachhap Signed-off-by: Kukjin Kim --- arch/arm/plat-s5p/sleep.S | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/arch/arm/plat-s5p/sleep.S b/arch/arm/plat-s5p/sleep.S index 0fd591bfc9f..006bd01eda0 100644 --- a/arch/arm/plat-s5p/sleep.S +++ b/arch/arm/plat-s5p/sleep.S @@ -23,9 +23,18 @@ */ #include -#include +#include +#include - .text +/* + * The following code is located into the .data section. This is to + * allow l2x0_regs_phys to be accessed with a relative load while we + * can't rely on any MMU translation. We could have put l2x0_regs_phys + * in the .text section as well, but some setups might insist on it to + * be truly read-only. (Reference from: arch/arm/kernel/sleep.S) + */ + .data + .align /* * sleep magic, to allow the bootloader to check for an valid @@ -39,11 +48,34 @@ * s3c_cpu_resume * * resume code entry for bootloader 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) */ ENTRY(s3c_cpu_resume) +#ifdef CONFIG_CACHE_L2X0 + adr r0, l2x0_regs_phys + ldr r0, [r0] + ldr r1, [r0, #L2X0_R_PHY_BASE] + ldr r2, [r1, #L2X0_CTRL] + tst r2, #0x1 + bne resume_l2on + ldr r2, [r0, #L2X0_R_AUX_CTRL] + str r2, [r1, #L2X0_AUX_CTRL] + ldr r2, [r0, #L2X0_R_TAG_LATENCY] + str r2, [r1, #L2X0_TAG_LATENCY_CTRL] + ldr r2, [r0, #L2X0_R_DATA_LATENCY] + str r2, [r1, #L2X0_DATA_LATENCY_CTRL] + ldr r2, [r0, #L2X0_R_PREFETCH_CTRL] + str r2, [r1, #L2X0_PREFETCH_CTRL] + ldr r2, [r0, #L2X0_R_PWR_CTRL] + str r2, [r1, #L2X0_POWER_CTRL] + mov r2, #1 + str r2, [r1, #L2X0_CTRL] +resume_l2on: +#endif b cpu_resume +ENDPROC(s3c_cpu_resume) +#ifdef CONFIG_CACHE_L2X0 + .globl l2x0_regs_phys +l2x0_regs_phys: + .long 0 +#endif -- cgit v1.2.3 From 0f34107d88b8c07ee70331ef4f20d36c6a720dc2 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 19 Mar 2012 20:15:50 -0500 Subject: ARM: EXYNOS: save L2 settings during bootup This patch adds code to save L2 register configuration at boot, and later used to resume L2 before MMU is enabled in suspend and cpuidle resume paths. Signed-off-by: Lorenzo Pieralisi Signed-off-by: Amit Daniel Kachhap Signed-off-by: Kukjin Kim --- arch/arm/mach-exynos/common.c | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index c59e1887100..9ff38aa08fd 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -26,10 +26,12 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -441,20 +443,38 @@ core_initcall(exynos4_core_init); #ifdef CONFIG_CACHE_L2X0 static int __init exynos4_l2x0_cache_init(void) { - /* TAG, Data Latency Control: 2cycle */ - __raw_writel(0x110, S5P_VA_L2CC + L2X0_TAG_LATENCY_CTRL); + if (!(__raw_readl(S5P_VA_L2CC + L2X0_CTRL) & 0x1)) { + l2x0_saved_regs.phy_base = EXYNOS4_PA_L2CC; + /* TAG, Data Latency Control: 2 cycles */ + l2x0_saved_regs.tag_latency = 0x110; - if (soc_is_exynos4210()) - __raw_writel(0x110, S5P_VA_L2CC + L2X0_DATA_LATENCY_CTRL); - else if (soc_is_exynos4212() || soc_is_exynos4412()) - __raw_writel(0x120, S5P_VA_L2CC + L2X0_DATA_LATENCY_CTRL); + if (soc_is_exynos4212() || soc_is_exynos4412()) + l2x0_saved_regs.data_latency = 0x120; + else + l2x0_saved_regs.data_latency = 0x110; + + l2x0_saved_regs.prefetch_ctrl = 0x30000007; + l2x0_saved_regs.pwr_ctrl = + (L2X0_DYNAMIC_CLK_GATING_EN | L2X0_STNDBY_MODE_EN); + + l2x0_regs_phys = virt_to_phys(&l2x0_saved_regs); - /* L2X0 Prefetch Control */ - __raw_writel(0x30000007, S5P_VA_L2CC + L2X0_PREFETCH_CTRL); + __raw_writel(l2x0_saved_regs.tag_latency, + S5P_VA_L2CC + L2X0_TAG_LATENCY_CTRL); + __raw_writel(l2x0_saved_regs.data_latency, + S5P_VA_L2CC + L2X0_DATA_LATENCY_CTRL); - /* L2X0 Power Control */ - __raw_writel(L2X0_DYNAMIC_CLK_GATING_EN | L2X0_STNDBY_MODE_EN, - S5P_VA_L2CC + L2X0_POWER_CTRL); + /* L2X0 Prefetch Control */ + __raw_writel(l2x0_saved_regs.prefetch_ctrl, + S5P_VA_L2CC + L2X0_PREFETCH_CTRL); + + /* L2X0 Power Control */ + __raw_writel(l2x0_saved_regs.pwr_ctrl, + S5P_VA_L2CC + L2X0_POWER_CTRL); + + clean_dcache_area(&l2x0_regs_phys, sizeof(unsigned long)); + clean_dcache_area(&l2x0_saved_regs, sizeof(struct l2x0_regs)); + } l2x0_init(S5P_VA_L2CC, 0x7C470001, 0xC200ffff); -- cgit v1.2.3 From 8005472caf15d54415f6715223167e1474381afa Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 19 Mar 2012 20:15:51 -0500 Subject: ARM: EXYNOS: remove useless code to save/restore L2 Following the merge of CPU PM notifiers and L2 resume code, this patch removes useless code to save and restore L2 registers. This is now automatically covered by suspend calls which integrated CPU PM notifiers and new sleep code that allows to resume L2 before MMU is turned on. Signed-off-by: Lorenzo Pieralisi Signed-off-by: Amit Daniel Kachhap Signed-off-by: Kukjin Kim --- arch/arm/mach-exynos/pm.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/arch/arm/mach-exynos/pm.c b/arch/arm/mach-exynos/pm.c index e1901305177..481682745e7 100644 --- a/arch/arm/mach-exynos/pm.c +++ b/arch/arm/mach-exynos/pm.c @@ -155,13 +155,6 @@ static struct sleep_save exynos4_core_save[] = { SAVE_ITEM(S5P_SROM_BC3), }; -static struct sleep_save exynos4_l2cc_save[] = { - SAVE_ITEM(S5P_VA_L2CC + L2X0_TAG_LATENCY_CTRL), - SAVE_ITEM(S5P_VA_L2CC + L2X0_DATA_LATENCY_CTRL), - SAVE_ITEM(S5P_VA_L2CC + L2X0_PREFETCH_CTRL), - SAVE_ITEM(S5P_VA_L2CC + L2X0_POWER_CTRL), - SAVE_ITEM(S5P_VA_L2CC + L2X0_AUX_CTRL), -}; /* For Cortex-A9 Diagnostic and Power control register */ static unsigned int save_arm_register[2]; @@ -182,7 +175,6 @@ static void exynos4_pm_prepare(void) u32 tmp; s3c_pm_do_save(exynos4_core_save, ARRAY_SIZE(exynos4_core_save)); - s3c_pm_do_save(exynos4_l2cc_save, ARRAY_SIZE(exynos4_l2cc_save)); s3c_pm_do_save(exynos4_epll_save, ARRAY_SIZE(exynos4_epll_save)); s3c_pm_do_save(exynos4_vpll_save, ARRAY_SIZE(exynos4_vpll_save)); @@ -388,13 +380,6 @@ static void exynos4_pm_resume(void) scu_enable(S5P_VA_SCU); #endif -#ifdef CONFIG_CACHE_L2X0 - s3c_pm_do_restore_core(exynos4_l2cc_save, ARRAY_SIZE(exynos4_l2cc_save)); - outer_inv_all(); - /* enable L2X0*/ - writel_relaxed(1, S5P_VA_L2CC + L2X0_CTRL); -#endif - early_wakeup: return; } -- cgit v1.2.3 From 10491ab20450c7548b6e590a7aba2260c40661c7 Mon Sep 17 00:00:00 2001 From: Amit Daniel Kachhap Date: Mon, 19 Mar 2012 20:15:51 -0500 Subject: ARM: EXYNOS: Enable l2 configuration through device tree This patch enables calling generic l2 setup functions if device tree is used. Signed-off-by: Amit Daniel Kachhap Signed-off-by: Kukjin Kim --- arch/arm/mach-exynos/common.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index 9ff38aa08fd..cb5ad8c1fea 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -47,6 +47,8 @@ #include #include "common.h" +#define L2_AUX_VAL 0x7C470001 +#define L2_AUX_MASK 0xC200ffff static const char name_exynos4210[] = "EXYNOS4210"; static const char name_exynos4212[] = "EXYNOS4212"; @@ -443,6 +445,14 @@ core_initcall(exynos4_core_init); #ifdef CONFIG_CACHE_L2X0 static int __init exynos4_l2x0_cache_init(void) { + int ret; + ret = l2x0_of_init(L2_AUX_VAL, L2_AUX_MASK); + if (!ret) { + l2x0_regs_phys = virt_to_phys(&l2x0_saved_regs); + clean_dcache_area(&l2x0_regs_phys, sizeof(unsigned long)); + return 0; + } + if (!(__raw_readl(S5P_VA_L2CC + L2X0_CTRL) & 0x1)) { l2x0_saved_regs.phy_base = EXYNOS4_PA_L2CC; /* TAG, Data Latency Control: 2 cycles */ @@ -476,8 +486,7 @@ static int __init exynos4_l2x0_cache_init(void) clean_dcache_area(&l2x0_saved_regs, sizeof(struct l2x0_regs)); } - l2x0_init(S5P_VA_L2CC, 0x7C470001, 0xC200ffff); - + l2x0_init(S5P_VA_L2CC, L2_AUX_VAL, L2_AUX_MASK); return 0; } -- cgit v1.2.3 From b53662cb0a3b150f301a7e5e233cc8de1f892645 Mon Sep 17 00:00:00 2001 From: Robert Lee Date: Mon, 19 Mar 2012 20:15:51 -0500 Subject: cpuidle: Add common time keeping and irq enabling Make necessary changes to implement time keeping and irq enabling in the core cpuidle code. This will allow the removal of these functionalities from various platform cpuidle implementations whose timekeeping and irq enabling follows the form in this common code. Signed-off-by: Robert Lee Tested-by: Jean Pihet Tested-by: Amit Daniel Tested-by: Robert Lee Reviewed-by: Kevin Hilman Reviewed-by: Daniel Lezcano Reviewed-by: Deepthi Dharwar Acked-by: Jean Pihet --- arch/arm/include/asm/cpuidle.h | 30 +++++++++++++++++++ arch/arm/kernel/Makefile | 2 +- arch/arm/kernel/cpuidle.c | 21 ++++++++++++++ drivers/cpuidle/cpuidle.c | 66 +++++++++++++++++++++++++++++++++++++----- include/linux/cpuidle.h | 13 ++++++++- 5 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 arch/arm/include/asm/cpuidle.h create mode 100644 arch/arm/kernel/cpuidle.c diff --git a/arch/arm/include/asm/cpuidle.h b/arch/arm/include/asm/cpuidle.h new file mode 100644 index 00000000000..9322f444bcf --- /dev/null +++ b/arch/arm/include/asm/cpuidle.h @@ -0,0 +1,30 @@ +#ifndef __ASM_ARM_CPUIDLE_H +#define __ASM_ARM_CPUIDLE_H + +#ifdef CONFIG_CPU_IDLE +extern int arm_cpuidle_simple_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index); +#else +static inline int arm_cpuidle_simple_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) { return -ENODEV; } +#endif + +/* Common ARM WFI state */ +#define ARM_CPUIDLE_WFI_STATE_PWR(p) {\ + .enter = arm_cpuidle_simple_enter,\ + .exit_latency = 1,\ + .target_residency = 1,\ + .power_usage = p,\ + .flags = CPUIDLE_FLAG_TIME_VALID,\ + .name = "WFI",\ + .desc = "ARM WFI",\ +} + +/* + * in case power_specified == 1, give a default WFI power value needed + * by some governors + */ +#define ARM_CPUIDLE_WFI_STATE ARM_CPUIDLE_WFI_STATE_PWR(UINT_MAX) + +#endif + diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 43b740d0e37..940c27fde49 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -21,7 +21,7 @@ obj-$(CONFIG_DEPRECATED_PARAM_STRUCT) += compat.o obj-$(CONFIG_LEDS) += leds.o obj-$(CONFIG_OC_ETM) += etm.o - +obj-$(CONFIG_CPU_IDLE) += cpuidle.o obj-$(CONFIG_ISA_DMA_API) += dma.o obj-$(CONFIG_ARCH_ACORN) += ecard.o obj-$(CONFIG_FIQ) += fiq.o fiqasm.o diff --git a/arch/arm/kernel/cpuidle.c b/arch/arm/kernel/cpuidle.c new file mode 100644 index 00000000000..89545f6c840 --- /dev/null +++ b/arch/arm/kernel/cpuidle.c @@ -0,0 +1,21 @@ +/* + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include + +int arm_cpuidle_simple_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + cpu_do_idle(); + + return index; +} diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index 59f4261c753..4869b550023 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -53,6 +53,24 @@ static void cpuidle_kick_cpus(void) {} static int __cpuidle_register_device(struct cpuidle_device *dev); +static inline int cpuidle_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + struct cpuidle_state *target_state = &drv->states[index]; + return target_state->enter(dev, drv, index); +} + +static inline int cpuidle_enter_tk(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + return cpuidle_wrap_enter(dev, drv, index, cpuidle_enter); +} + +typedef int (*cpuidle_enter_t)(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index); + +static cpuidle_enter_t cpuidle_enter_ops; + /** * cpuidle_idle_call - the main idle loop * @@ -63,7 +81,6 @@ int cpuidle_idle_call(void) { struct cpuidle_device *dev = __this_cpu_read(cpuidle_devices); struct cpuidle_driver *drv = cpuidle_get_driver(); - struct cpuidle_state *target_state; int next_state, entered_state; if (off) @@ -92,12 +109,10 @@ int cpuidle_idle_call(void) return 0; } - target_state = &drv->states[next_state]; - trace_power_start(POWER_CSTATE, next_state, dev->cpu); trace_cpu_idle(next_state, dev->cpu); - entered_state = target_state->enter(dev, drv, next_state); + entered_state = cpuidle_enter_ops(dev, drv, next_state); trace_power_end(dev->cpu); trace_cpu_idle(PWR_EVENT_EXIT, dev->cpu); @@ -110,6 +125,8 @@ int cpuidle_idle_call(void) dev->states_usage[entered_state].time += (unsigned long long)dev->last_residency; dev->states_usage[entered_state].usage++; + } else { + dev->last_residency = 0; } /* give the governor an opportunity to reflect on the outcome */ @@ -164,6 +181,37 @@ void cpuidle_resume_and_unlock(void) EXPORT_SYMBOL_GPL(cpuidle_resume_and_unlock); +/** + * cpuidle_wrap_enter - performs timekeeping and irqen around enter function + * @dev: pointer to a valid cpuidle_device object + * @drv: pointer to a valid cpuidle_driver object + * @index: index of the target cpuidle state. + */ +int cpuidle_wrap_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index, + int (*enter)(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index)) +{ + ktime_t time_start, time_end; + s64 diff; + + time_start = ktime_get(); + + index = enter(dev, drv, index); + + time_end = ktime_get(); + + local_irq_enable(); + + diff = ktime_to_us(ktime_sub(time_end, time_start)); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; + + return index; +} + #ifdef CONFIG_ARCH_HAS_CPU_RELAX static int poll_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) @@ -212,10 +260,11 @@ static void poll_idle_init(struct cpuidle_driver *drv) {} int cpuidle_enable_device(struct cpuidle_device *dev) { int ret, i; + struct cpuidle_driver *drv = cpuidle_get_driver(); if (dev->enabled) return 0; - if (!cpuidle_get_driver() || !cpuidle_curr_governor) + if (!drv || !cpuidle_curr_governor) return -EIO; if (!dev->state_count) return -EINVAL; @@ -226,13 +275,16 @@ int cpuidle_enable_device(struct cpuidle_device *dev) return ret; } - poll_idle_init(cpuidle_get_driver()); + cpuidle_enter_ops = drv->en_core_tk_irqen ? + cpuidle_enter_tk : cpuidle_enter; + + poll_idle_init(drv); if ((ret = cpuidle_add_state_sysfs(dev))) return ret; if (cpuidle_curr_governor->enable && - (ret = cpuidle_curr_governor->enable(cpuidle_get_driver(), dev))) + (ret = cpuidle_curr_governor->enable(drv, dev))) goto fail_sysfs; for (i = 0; i < dev->state_count; i++) { diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h index 712abcc205a..927db28a2a4 100644 --- a/include/linux/cpuidle.h +++ b/include/linux/cpuidle.h @@ -15,6 +15,7 @@ #include #include #include +#include #define CPUIDLE_STATE_MAX 8 #define CPUIDLE_NAME_LEN 16 @@ -122,6 +123,8 @@ struct cpuidle_driver { struct module *owner; unsigned int power_specified:1; + /* set to 1 to use the core cpuidle time keeping (for all states). */ + unsigned int en_core_tk_irqen:1; struct cpuidle_state states[CPUIDLE_STATE_MAX]; int state_count; int safe_state_index; @@ -140,7 +143,10 @@ extern void cpuidle_pause_and_lock(void); extern void cpuidle_resume_and_unlock(void); extern int cpuidle_enable_device(struct cpuidle_device *dev); extern void cpuidle_disable_device(struct cpuidle_device *dev); - +extern int cpuidle_wrap_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index, + int (*enter)(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index)); #else static inline void disable_cpuidle(void) { } static inline int cpuidle_idle_call(void) { return -ENODEV; } @@ -157,6 +163,11 @@ static inline void cpuidle_resume_and_unlock(void) { } static inline int cpuidle_enable_device(struct cpuidle_device *dev) {return -ENODEV; } static inline void cpuidle_disable_device(struct cpuidle_device *dev) { } +static inline int cpuidle_wrap_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index, + int (*enter)(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index)) +{ return -ENODEV; } #endif -- cgit v1.2.3 From d030a8b5e4c9d696bf5410300504f278773e01b0 Mon Sep 17 00:00:00 2001 From: Robert Lee Date: Mon, 19 Mar 2012 20:15:51 -0500 Subject: ARM: imx: Add common imx cpuidle init functionality. Add common cpuidle init functionality that can be used by various imx platforms. Signed-off-by: Robert Lee --- arch/arm/plat-mxc/Makefile | 2 + arch/arm/plat-mxc/cpuidle.c | 89 ++++++++++++++++++++++++++++++++ arch/arm/plat-mxc/include/mach/cpuidle.h | 26 ++++++++++ 3 files changed, 117 insertions(+) create mode 100644 arch/arm/plat-mxc/cpuidle.c create mode 100644 arch/arm/plat-mxc/include/mach/cpuidle.h diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile index 076db84f3e3..4c9c2e935c5 100644 --- a/arch/arm/plat-mxc/Makefile +++ b/arch/arm/plat-mxc/Makefile @@ -18,6 +18,8 @@ obj-$(CONFIG_ARCH_MXC_AUDMUX_V1) += audmux-v1.o obj-$(CONFIG_ARCH_MXC_AUDMUX_V2) += audmux-v2.o obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o obj-$(CONFIG_CPU_FREQ_IMX) += cpufreq.o +obj-$(CONFIG_CPU_IDLE) += cpuidle.o + ifdef CONFIG_SND_IMX_SOC obj-y += ssi-fiq.o obj-y += ssi-fiq-ksym.o diff --git a/arch/arm/plat-mxc/cpuidle.c b/arch/arm/plat-mxc/cpuidle.c new file mode 100644 index 00000000000..c651105ae4f --- /dev/null +++ b/arch/arm/plat-mxc/cpuidle.c @@ -0,0 +1,89 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include + +static struct cpuidle_device __percpu * imx_cpuidle_devices; +static struct cpuidle_driver *drv = NULL; + +void __init imx_cpuidle_set_driver(struct cpuidle_driver *p) +{ + drv = p; +} + +void imx_cpuidle_devices_uninit(void) +{ + int cpu_id; + struct cpuidle_device *dev; + + for_each_possible_cpu(cpu_id) { + dev = per_cpu_ptr(imx_cpuidle_devices, cpu_id); + cpuidle_unregister_device(dev); + } + + free_percpu(imx_cpuidle_devices); +} + +static int __init imx_cpuidle_init(void) +{ + struct cpuidle_device *dev; + int cpu_id, ret; + + if (!drv || drv->state_count > CPUIDLE_STATE_MAX) { + pr_err("%s: Invalid Input\n", __func__); + return -EINVAL; + } + + ret = cpuidle_register_driver(drv); + + if (ret) { + pr_err("%s: Failed to register cpuidle driver\n", __func__); + return ret; + } + + imx_cpuidle_devices = alloc_percpu(struct cpuidle_device); + + if (imx_cpuidle_devices == NULL) { + ret = -ENOMEM; + goto unregister_drv; + } + + /* initialize state data for each cpuidle_device */ + for_each_possible_cpu(cpu_id) { + dev = per_cpu_ptr(imx_cpuidle_devices, cpu_id); + dev->cpu = cpu_id; + dev->state_count = drv->state_count; + + ret = cpuidle_register_device(dev); + if (ret) { + pr_err("%s: Failed to register cpu %u\n", + __func__, cpu_id); + goto uninit; + } + } + + return 0; + +uninit: + imx_cpuidle_devices_uninit(); + +unregister_drv: + cpuidle_unregister_driver(drv); + + return ret; +} +late_initcall(imx_cpuidle_init); diff --git a/arch/arm/plat-mxc/include/mach/cpuidle.h b/arch/arm/plat-mxc/include/mach/cpuidle.h new file mode 100644 index 00000000000..6509f194598 --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/cpuidle.h @@ -0,0 +1,26 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include + +extern int imx5_cpuidle_init(void); +extern int imx6q_cpuidle_init(void); + +#ifdef CONFIG_CPU_IDLE +extern void imx_cpuidle_devices_uninit(void); +extern void imx_cpuidle_set_driver(struct cpuidle_driver *p); +extern int imx5_cpuidle_init(void); +extern int imx6q_cpuidle_init(void); +#else +static inline void imx_cpuidle_devices_uninit(void) {} +static inline void imx_cpuidle_set_driver(struct cpuidle_driver *p) {} +#endif -- cgit v1.2.3 From 1dfe2d069e719d7714389454cab4599aafe41dbc Mon Sep 17 00:00:00 2001 From: Robert Lee Date: Mon, 19 Mar 2012 20:15:51 -0500 Subject: ARM: imx: Add imx5 cpuidle driver Add imx5 cpuidle driver. Signed-off-by: Robert Lee --- arch/arm/mach-imx/Makefile | 2 +- arch/arm/mach-imx/cpuidle-imx5.c | 49 +++++++++++++++++++++++++++++++++ arch/arm/mach-imx/mm-imx5.c | 5 +++- arch/arm/plat-mxc/include/mach/common.h | 1 + 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 arch/arm/mach-imx/cpuidle-imx5.c diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 55db9c488f2..9d3947fbd2a 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -11,7 +11,7 @@ obj-$(CONFIG_SOC_IMX27) += clock-imx27.o mm-imx27.o ehci-imx27.o obj-$(CONFIG_SOC_IMX31) += mm-imx3.o cpu-imx31.o clock-imx31.o iomux-imx31.o ehci-imx31.o obj-$(CONFIG_SOC_IMX35) += mm-imx3.o cpu-imx35.o clock-imx35.o ehci-imx35.o -obj-$(CONFIG_SOC_IMX5) += cpu-imx5.o mm-imx5.o clock-mx51-mx53.o ehci-imx5.o pm-imx5.o cpu_op-mx51.o +obj-$(CONFIG_SOC_IMX5) += cpu-imx5.o mm-imx5.o clock-mx51-mx53.o ehci-imx5.o pm-imx5.o cpu_op-mx51.o cpuidle-imx5.o # Support for CMOS sensor interface obj-$(CONFIG_MX1_VIDEO) += mx1-camera-fiq.o mx1-camera-fiq-ksym.o diff --git a/arch/arm/mach-imx/cpuidle-imx5.c b/arch/arm/mach-imx/cpuidle-imx5.c new file mode 100644 index 00000000000..4553fdc94ea --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx5.c @@ -0,0 +1,49 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include + +static int imx5_cpuidle_enter(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int idx) +{ + imx5_idle(); + + return idx; +} + +static struct cpuidle_driver imx5_cpuidle_driver = { + .name = "imx5_cpuidle", + .owner = THIS_MODULE, + .en_core_tk_irqen = 1, + .states[0] = { + .enter = imx5_cpuidle_enter, + .exit_latency = 20, /* max latency at 160MHz */ + .target_residency = 1, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "IMX5 SRPG", + .desc = "CPU state retained,powered off", + }, + .state_count = 1, +}; + +int __init imx5_cpuidle_init(void) +{ + imx_cpuidle_set_driver(&imx5_cpuidle_driver); + + return 0; +} diff --git a/arch/arm/mach-imx/mm-imx5.c b/arch/arm/mach-imx/mm-imx5.c index bc17dfea381..32734ab3e9e 100644 --- a/arch/arm/mach-imx/mm-imx5.c +++ b/arch/arm/mach-imx/mm-imx5.c @@ -21,10 +21,11 @@ #include #include #include +#include static struct clk *gpc_dvfs_clk; -static void imx5_idle(void) +void imx5_idle(void) { if (!need_resched()) { /* gpc clock is needed for SRPG */ @@ -191,6 +192,8 @@ void __init imx51_soc_init(void) /* i.mx51 has the i.mx35 type sdma */ imx_add_imx_sdma("imx35-sdma", MX51_SDMA_BASE_ADDR, MX51_INT_SDMA, &imx51_sdma_pdata); + + imx5_cpuidle_init(); } void __init imx53_soc_init(void) diff --git a/arch/arm/plat-mxc/include/mach/common.h b/arch/arm/plat-mxc/include/mach/common.h index 1bf0df81bdc..a95692c097f 100644 --- a/arch/arm/plat-mxc/include/mach/common.h +++ b/arch/arm/plat-mxc/include/mach/common.h @@ -85,6 +85,7 @@ enum mxc_cpu_pwr_mode { }; extern void mx5_cpu_lp_set(enum mxc_cpu_pwr_mode mode); +extern void imx5_idle(void); extern void imx_print_silicon_rev(const char *cpu, int srev); void avic_handle_irq(struct pt_regs *); -- cgit v1.2.3 From dabc71e521bc8be46926a9eafcb7bd9453f44152 Mon Sep 17 00:00:00 2001 From: Robert Lee Date: Mon, 19 Mar 2012 20:15:52 -0500 Subject: ARM: imx: Add imx6q cpuidle driver Add basic imx6q cpuidle driver. For now, only basic WFI state is supported. Deeper idle will be added in the future. Signed-off-by: Robert Lee --- arch/arm/mach-imx/Makefile | 2 +- arch/arm/mach-imx/cpuidle-imx6q.c | 33 +++++++++++++++++++++++++++++++++ arch/arm/mach-imx/mach-imx6q.c | 2 ++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 arch/arm/mach-imx/cpuidle-imx6q.c diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 9d3947fbd2a..33ec07c832a 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -72,7 +72,7 @@ AFLAGS_head-v7.o :=-Wa,-march=armv7-a obj-$(CONFIG_SMP) += platsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o -obj-$(CONFIG_SOC_IMX6Q) += clock-imx6q.o mach-imx6q.o +obj-$(CONFIG_SOC_IMX6Q) += clock-imx6q.o mach-imx6q.o cpuidle-imx6q.o ifeq ($(CONFIG_PM),y) obj-$(CONFIG_SOC_IMX6Q) += pm-imx6q.o diff --git a/arch/arm/mach-imx/cpuidle-imx6q.c b/arch/arm/mach-imx/cpuidle-imx6q.c new file mode 100644 index 00000000000..b74557f7a80 --- /dev/null +++ b/arch/arm/mach-imx/cpuidle-imx6q.c @@ -0,0 +1,33 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include + +static struct cpuidle_driver imx6q_cpuidle_driver = { + .name = "imx6q_cpuidle", + .owner = THIS_MODULE, + .en_core_tk_irqen = 1, + .states[0] = ARM_CPUIDLE_WFI_STATE, + .state_count = 1, +}; + +int __init imx6q_cpuidle_init(void) +{ + imx_cpuidle_set_driver(&imx6q_cpuidle_driver); + + return 0; +} diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c index c2572810691..b4825ba4617 100644 --- a/arch/arm/mach-imx/mach-imx6q.c +++ b/arch/arm/mach-imx/mach-imx6q.c @@ -27,6 +27,7 @@ #include #include #include +#include void imx6q_restart(char mode, const char *cmd) { @@ -82,6 +83,7 @@ static void __init imx6q_init_machine(void) of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); imx6q_pm_init(); + imx6q_cpuidle_init(); } static void __init imx6q_map_io(void) -- cgit v1.2.3 From bd19e7dbf88fcec83c81f8042047a284e6f90566 Mon Sep 17 00:00:00 2001 From: Robert Lee Date: Mon, 19 Mar 2012 20:15:52 -0500 Subject: ARM: exynos: Consolidate time keeping and irq enable Enable core cpuidle timekeeping and irq enabling and remove that handling from this code. Signed-off-by: Robert Lee --- arch/arm/mach-exynos/cpuidle.c | 53 +++++------------------------------------- 1 file changed, 6 insertions(+), 47 deletions(-) diff --git a/arch/arm/mach-exynos/cpuidle.c b/arch/arm/mach-exynos/cpuidle.c index 33ab4e7558a..26dac2893b8 100644 --- a/arch/arm/mach-exynos/cpuidle.c +++ b/arch/arm/mach-exynos/cpuidle.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -34,22 +35,12 @@ #define S5P_CHECK_AFTR 0xFCBA0D10 -static int exynos4_enter_idle(struct cpuidle_device *dev, - struct cpuidle_driver *drv, - int index); static int exynos4_enter_lowpower(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index); static struct cpuidle_state exynos4_cpuidle_set[] __initdata = { - [0] = { - .enter = exynos4_enter_idle, - .exit_latency = 1, - .target_residency = 100000, - .flags = CPUIDLE_FLAG_TIME_VALID, - .name = "C0", - .desc = "ARM clock gating(WFI)", - }, + [0] = ARM_CPUIDLE_WFI_STATE, [1] = { .enter = exynos4_enter_lowpower, .exit_latency = 300, @@ -63,8 +54,9 @@ static struct cpuidle_state exynos4_cpuidle_set[] __initdata = { static DEFINE_PER_CPU(struct cpuidle_device, exynos4_cpuidle_device); static struct cpuidle_driver exynos4_idle_driver = { - .name = "exynos4_idle", - .owner = THIS_MODULE, + .name = "exynos4_idle", + .owner = THIS_MODULE, + .en_core_tk_irqen = 1, }; /* Ext-GIC nIRQ/nFIQ is the only wakeup source in AFTR */ @@ -103,13 +95,8 @@ static int exynos4_enter_core0_aftr(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) { - struct timeval before, after; - int idle_time; unsigned long tmp; - local_irq_disable(); - do_gettimeofday(&before); - exynos4_set_wakeupmask(); /* Set value of power down register for aftr mode */ @@ -150,34 +137,6 @@ static int exynos4_enter_core0_aftr(struct cpuidle_device *dev, /* 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); - - dev->last_residency = idle_time; - return index; -} - -static int exynos4_enter_idle(struct cpuidle_device *dev, - struct cpuidle_driver *drv, - int index) -{ - struct timeval before, after; - int idle_time; - - local_irq_disable(); - do_gettimeofday(&before); - - cpu_do_idle(); - - do_gettimeofday(&after); - local_irq_enable(); - idle_time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC + - (after.tv_usec - before.tv_usec); - - dev->last_residency = idle_time; return index; } @@ -192,7 +151,7 @@ static int exynos4_enter_lowpower(struct cpuidle_device *dev, new_index = drv->safe_state_index; if (new_index == 0) - return exynos4_enter_idle(dev, drv, new_index); + return arm_cpuidle_simple_enter(dev, drv, new_index); else return exynos4_enter_core0_aftr(dev, drv, new_index); } -- cgit v1.2.3 From f3197067dc7d046a6540bf7890d965958d0f2ae9 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 19 Mar 2012 20:15:52 -0500 Subject: mfd : ux500 - decouple/recouple gic from the PRCMU This patch allows to decouple and recouple the gic from the PRCMU. This is needed to put the A9 core in retention mode with the cpuidle driver. Signed-off-by: Daniel Lezcano --- drivers/mfd/db8500-prcmu.c | 32 ++++++++++++++++++++++++++++++++ include/linux/mfd/db8500-prcmu.h | 2 ++ include/linux/mfd/dbx500-prcmu.h | 16 ++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index af8e0efedbe..9baf4cd7f07 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -787,6 +787,38 @@ int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) return 0; } +#define PRCMU_A9_MASK_REQ 0x00000328 +#define PRCMU_A9_MASK_REQ_MASK 0x00000001 +#define PRCMU_GIC_DELAY 1 + +/* This function decouple the gic from the prcmu */ +int db8500_prcmu_gic_decouple(void) +{ + u32 val = readl(_PRCMU_BASE + PRCMU_A9_MASK_REQ); + + /* Set bit 0 register value to 1 */ + writel(val | PRCMU_A9_MASK_REQ_MASK, _PRCMU_BASE + PRCMU_A9_MASK_REQ); + + /* Make sure the register is updated */ + readl(_PRCMU_BASE + PRCMU_A9_MASK_REQ); + + /* Wait a few cycles for the gic mask completion */ + udelay(PRCMU_GIC_DELAY); + + return 0; +} + +/* This function recouple the gic with the prcmu */ +int db8500_prcmu_gic_recouple(void) +{ + u32 val = readl(_PRCMU_BASE + PRCMU_A9_MASK_REQ); + + /* Set bit 0 register value to 0 */ + writel(val & ~PRCMU_A9_MASK_REQ_MASK, _PRCMU_BASE + PRCMU_A9_MASK_REQ); + + return 0; +} + /* This function should only be called while mb0_transfer.lock is held. */ static void config_wakeups(void) { diff --git a/include/linux/mfd/db8500-prcmu.h b/include/linux/mfd/db8500-prcmu.h index 60d27f7bfc1..a3c7eba8e6f 100644 --- a/include/linux/mfd/db8500-prcmu.h +++ b/include/linux/mfd/db8500-prcmu.h @@ -536,6 +536,8 @@ int prcmu_load_a9wdog(u8 id, u32 val); void db8500_prcmu_system_reset(u16 reset_code); int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll); +int db8500_prcmu_gic_decouple(void); +int db8500_prcmu_gic_recouple(void); void db8500_prcmu_enable_wakeups(u32 wakeups); int db8500_prcmu_set_epod(u16 epod_id, u8 epod_state); int db8500_prcmu_request_clock(u8 clock, bool enable); diff --git a/include/linux/mfd/dbx500-prcmu.h b/include/linux/mfd/dbx500-prcmu.h index bac942f959c..d38cf7d9657 100644 --- a/include/linux/mfd/dbx500-prcmu.h +++ b/include/linux/mfd/dbx500-prcmu.h @@ -237,6 +237,22 @@ static inline int prcmu_set_power_state(u8 state, bool keep_ulp_clk, keep_ap_pll); } +static inline int prcmu_gic_decouple(void) +{ + if (machine_is_u5500()) + return -EINVAL; + else + return db8500_prcmu_gic_decouple(); +} + +static inline int prcmu_gic_recouple(void) +{ + if (machine_is_u5500()) + return -EINVAL; + else + return db8500_prcmu_gic_recouple(); +} + static inline int prcmu_set_epod(u16 epod_id, u8 epod_state) { if (machine_is_u5500()) -- cgit v1.2.3 From 324ae6098c81213ab7de035dcd0cbabf29029b1f Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 19 Mar 2012 20:15:52 -0500 Subject: mfd : ux500 - fix redefined includes Signed-off-by: Daniel Lezcano --- drivers/mfd/db8500-prcmu.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index 9baf4cd7f07..9d2399284b1 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -787,23 +787,20 @@ int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) return 0; } -#define PRCMU_A9_MASK_REQ 0x00000328 -#define PRCMU_A9_MASK_REQ_MASK 0x00000001 -#define PRCMU_GIC_DELAY 1 - /* This function decouple the gic from the prcmu */ int db8500_prcmu_gic_decouple(void) { - u32 val = readl(_PRCMU_BASE + PRCMU_A9_MASK_REQ); + u32 val = readl(PRCM_A9_MASK_REQ); /* Set bit 0 register value to 1 */ - writel(val | PRCMU_A9_MASK_REQ_MASK, _PRCMU_BASE + PRCMU_A9_MASK_REQ); + writel(val | PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ, + PRCM_A9_MASK_REQ); /* Make sure the register is updated */ - readl(_PRCMU_BASE + PRCMU_A9_MASK_REQ); + readl(PRCM_A9_MASK_REQ); /* Wait a few cycles for the gic mask completion */ - udelay(PRCMU_GIC_DELAY); + udelay(1); return 0; } @@ -811,10 +808,10 @@ int db8500_prcmu_gic_decouple(void) /* This function recouple the gic with the prcmu */ int db8500_prcmu_gic_recouple(void) { - u32 val = readl(_PRCMU_BASE + PRCMU_A9_MASK_REQ); + u32 val = readl(PRCM_A9_MASK_REQ); /* Set bit 0 register value to 0 */ - writel(val & ~PRCMU_A9_MASK_REQ_MASK, _PRCMU_BASE + PRCMU_A9_MASK_REQ); + writel(val & ~PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ, PRCM_A9_MASK_REQ); return 0; } -- cgit v1.2.3 From 01d9ee53ab2c4f6088ac8a2661f5ce5977e57d10 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 19 Mar 2012 20:15:52 -0500 Subject: mfd : ux500 : check pending irq on gic Signed-off-by: Daniel Lezcano --- drivers/mfd/db8500-prcmu.c | 33 +++++++++++++++++++++++++++++++++ include/linux/mfd/db8500-prcmu.h | 1 + include/linux/mfd/dbx500-prcmu.h | 8 ++++++++ 3 files changed, 42 insertions(+) diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index 9d2399284b1..4a89b223790 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -816,6 +817,38 @@ int db8500_prcmu_gic_recouple(void) return 0; } +#define PRCMU_GIC_NUMBER_REGS 5 + +/* + * This function checks if there are pending irq on the gic. It only + * makes sense if the gic has been decoupled before with the + * db8500_prcmu_gic_decouple function. Disabling an interrupt only + * disables the forwarding of the interrupt to any CPU interface. It + * does not prevent the interrupt from changing state, for example + * becoming pending, or active and pending if it is already + * active. Hence, we have to check the interrupt is pending *and* is + * active. + */ +bool db8500_prcmu_gic_pending_irq(void) +{ + u32 pr; /* Pending register */ + u32 er; /* Enable register */ + void __iomem *dist_base = __io_address(U8500_GIC_DIST_BASE); + int i; + + /* 5 registers. STI & PPI not skipped */ + for (i = 0; i < PRCMU_GIC_NUMBER_REGS; i++) { + + pr = readl_relaxed(dist_base + GIC_DIST_PENDING_SET + i * 4); + er = readl_relaxed(dist_base + GIC_DIST_ENABLE_SET + i * 4); + + if (pr & er) + return true; /* There is a pending interrupt */ + } + + return false; +} + /* This function should only be called while mb0_transfer.lock is held. */ static void config_wakeups(void) { diff --git a/include/linux/mfd/db8500-prcmu.h b/include/linux/mfd/db8500-prcmu.h index a3c7eba8e6f..8bf0dc3ba04 100644 --- a/include/linux/mfd/db8500-prcmu.h +++ b/include/linux/mfd/db8500-prcmu.h @@ -538,6 +538,7 @@ void db8500_prcmu_system_reset(u16 reset_code); int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll); int db8500_prcmu_gic_decouple(void); int db8500_prcmu_gic_recouple(void); +bool db8500_prcmu_gic_pending_irq(void); void db8500_prcmu_enable_wakeups(u32 wakeups); int db8500_prcmu_set_epod(u16 epod_id, u8 epod_state); int db8500_prcmu_request_clock(u8 clock, bool enable); diff --git a/include/linux/mfd/dbx500-prcmu.h b/include/linux/mfd/dbx500-prcmu.h index d38cf7d9657..82c6d516d0b 100644 --- a/include/linux/mfd/dbx500-prcmu.h +++ b/include/linux/mfd/dbx500-prcmu.h @@ -253,6 +253,14 @@ static inline int prcmu_gic_recouple(void) return db8500_prcmu_gic_recouple(); } +static inline bool prcmu_gic_pending_irq(void) +{ + if (machine_is_u5500()) + return -EINVAL; + else + return db8500_prcmu_gic_pending_irq(); +} + static inline int prcmu_set_epod(u16 epod_id, u8 epod_state) { if (machine_is_u5500()) -- cgit v1.2.3 From e2e23afdc93d569c9bb9947c6794098920dc02a0 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 19 Mar 2012 20:15:53 -0500 Subject: mfd : ux500 - copy gic settings Signed-off-by: Daniel Lezcano --- drivers/mfd/db8500-prcmu.c | 19 +++++++++++++++++++ include/linux/mfd/db8500-prcmu.h | 2 ++ include/linux/mfd/dbx500-prcmu.h | 16 ++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index 4a89b223790..d2957ff146d 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -849,6 +849,25 @@ bool db8500_prcmu_gic_pending_irq(void) return false; } +/* + * This function copies the gic SPI settings to the prcmu in order to + * monitor them and abort/finish the retention/off sequence or state. + */ +int db8500_prcmu_copy_gic_settings(void) +{ + u32 er; /* Enable register */ + void __iomem *dist_base = __io_address(U8500_GIC_DIST_BASE); + int i; + + /* We skip the STI and PPI */ + for (i = 0; i < PRCMU_GIC_NUMBER_REGS - 1; i++) { + er = readl_relaxed(dist_base + GIC_DIST_ENABLE_SET + (i + 1) * 4); + writel(er, PRCM_ARMITMSK31TO0 + i * 4); + } + + return 0; +} + /* This function should only be called while mb0_transfer.lock is held. */ static void config_wakeups(void) { diff --git a/include/linux/mfd/db8500-prcmu.h b/include/linux/mfd/db8500-prcmu.h index 8bf0dc3ba04..026d93e8cef 100644 --- a/include/linux/mfd/db8500-prcmu.h +++ b/include/linux/mfd/db8500-prcmu.h @@ -538,7 +538,9 @@ void db8500_prcmu_system_reset(u16 reset_code); int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll); int db8500_prcmu_gic_decouple(void); int db8500_prcmu_gic_recouple(void); +int db8500_prcmu_copy_gic_settings(void); bool db8500_prcmu_gic_pending_irq(void); +bool db8500_prcmu_pending_irq(void); void db8500_prcmu_enable_wakeups(u32 wakeups); int db8500_prcmu_set_epod(u16 epod_id, u8 epod_state); int db8500_prcmu_request_clock(u8 clock, bool enable); diff --git a/include/linux/mfd/dbx500-prcmu.h b/include/linux/mfd/dbx500-prcmu.h index 82c6d516d0b..69848f8e652 100644 --- a/include/linux/mfd/dbx500-prcmu.h +++ b/include/linux/mfd/dbx500-prcmu.h @@ -261,6 +261,22 @@ static inline bool prcmu_gic_pending_irq(void) return db8500_prcmu_gic_pending_irq(); } +static inline bool prcmu_pending_irq(void) +{ + if (machine_is_u5500()) + return -EINVAL; + else + return db8500_prcmu_pending_irq(); +} + +static inline int prcmu_copy_gic_settings(void) +{ + if (machine_is_u5500()) + return -EINVAL; + else + return db8500_prcmu_copy_gic_settings(); +} + static inline int prcmu_set_epod(u16 epod_id, u8 epod_state) { if (machine_is_u5500()) -- cgit v1.2.3 From c20703f92b21c9c77caa9beffc2506a31ba3db10 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 19 Mar 2012 20:15:53 -0500 Subject: mfd : ux500 - check pending irq on the prcmu Signed-off-by: Daniel Lezcano --- drivers/mfd/db8500-prcmu.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index d2957ff146d..dd3fb0568d2 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -849,6 +849,26 @@ bool db8500_prcmu_gic_pending_irq(void) return false; } +/* + * This function checks if there are pending interrupt on the + * prcmu which has been delegated to monitor the irqs with the + * db8500_prcmu_copy_gic_settings function. + */ +bool db8500_prcmu_pending_irq(void) +{ + u32 it, im; + int i; + + for (i = 0; i < PRCMU_GIC_NUMBER_REGS - 1; i++) { + it = readl(PRCM_ARMITVAL31TO0 + i * 4); + im = readl(PRCM_ARMITMSK31TO0 + i * 4); + if (it & im) + return true; /* There is a pending interrupt */ + } + + return false; +} + /* * This function copies the gic SPI settings to the prcmu in order to * monitor them and abort/finish the retention/off sequence or state. -- cgit v1.2.3 From 3e4e9a48c8057d76bc5d45a2337c0406c2a77b90 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Mon, 19 Mar 2012 20:15:53 -0500 Subject: mfd : ux500 - check if the other core is in WFI This patch allows to check if the other core is in WFI mode. It is the last check the idle routine has to do before entering into the retention state. Signed-off-by: Daniel Lezcano --- drivers/mfd/db8500-prcmu.c | 12 ++++++++++++ drivers/mfd/dbx500-prcmu-regs.h | 2 ++ include/linux/mfd/db8500-prcmu.h | 1 + include/linux/mfd/dbx500-prcmu.h | 8 ++++++++ 4 files changed, 23 insertions(+) diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index dd3fb0568d2..e1ad6c79ccc 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -869,6 +869,18 @@ bool db8500_prcmu_pending_irq(void) return false; } +/* + * This function checks if the specified cpu is in in WFI. It's usage + * makes sense only if the gic is decoupled with the db8500_prcmu_gic_decouple + * function. Of course passing smp_processor_id() to this function will + * always return false... + */ +bool db8500_prcmu_is_cpu_in_wfi(int cpu) +{ + return readl(PRCM_ARM_WFI_STANDBY) & cpu ? PRCM_ARM_WFI_STANDBY_WFI1 : + PRCM_ARM_WFI_STANDBY_WFI0; +} + /* * This function copies the gic SPI settings to the prcmu in order to * monitor them and abort/finish the retention/off sequence or state. diff --git a/drivers/mfd/dbx500-prcmu-regs.h b/drivers/mfd/dbx500-prcmu-regs.h index ec22e9f15d3..78bb628068f 100644 --- a/drivers/mfd/dbx500-prcmu-regs.h +++ b/drivers/mfd/dbx500-prcmu-regs.h @@ -79,6 +79,8 @@ /* ARM WFI Standby signal register */ #define PRCM_ARM_WFI_STANDBY (_PRCMU_BASE + 0x130) +#define PRCM_ARM_WFI_STANDBY_WFI0 0x08 +#define PRCM_ARM_WFI_STANDBY_WFI1 0x10 #define PRCM_IOCR (_PRCMU_BASE + 0x310) #define PRCM_IOCR_IOFORCE 0x1 diff --git a/include/linux/mfd/db8500-prcmu.h b/include/linux/mfd/db8500-prcmu.h index 026d93e8cef..488cd401d10 100644 --- a/include/linux/mfd/db8500-prcmu.h +++ b/include/linux/mfd/db8500-prcmu.h @@ -541,6 +541,7 @@ int db8500_prcmu_gic_recouple(void); int db8500_prcmu_copy_gic_settings(void); bool db8500_prcmu_gic_pending_irq(void); bool db8500_prcmu_pending_irq(void); +bool db8500_prcmu_is_cpu_in_wfi(int cpu); void db8500_prcmu_enable_wakeups(u32 wakeups); int db8500_prcmu_set_epod(u16 epod_id, u8 epod_state); int db8500_prcmu_request_clock(u8 clock, bool enable); diff --git a/include/linux/mfd/dbx500-prcmu.h b/include/linux/mfd/dbx500-prcmu.h index 69848f8e652..b74a474f97f 100644 --- a/include/linux/mfd/dbx500-prcmu.h +++ b/include/linux/mfd/dbx500-prcmu.h @@ -269,6 +269,14 @@ static inline bool prcmu_pending_irq(void) return db8500_prcmu_pending_irq(); } +static inline bool prcmu_is_cpu_in_wfi(int cpu) +{ + if (cpu_is_u5500()) + return -EINVAL; + else + return db8500_prcmu_is_cpu_in_wfi(cpu); +} + static inline int prcmu_copy_gic_settings(void) { if (machine_is_u5500()) -- cgit v1.2.3 From 5587dae019fae5ed313599954895a330a2f58457 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 20 Mar 2012 00:16:37 +0100 Subject: fix prcmu routine backport from mfd-2.6 The mfd-2.6 kernel tree use the cpu_is_u5500 function while this version use machine_is_u5500. An error was introduced by backporting prcmu_is_cpu_in_wfi function from the mfd-2.6 kernel tree. Signed-off-by: Daniel Lezcano --- include/linux/mfd/dbx500-prcmu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/mfd/dbx500-prcmu.h b/include/linux/mfd/dbx500-prcmu.h index b74a474f97f..27f35b8a107 100644 --- a/include/linux/mfd/dbx500-prcmu.h +++ b/include/linux/mfd/dbx500-prcmu.h @@ -271,7 +271,7 @@ static inline bool prcmu_pending_irq(void) static inline bool prcmu_is_cpu_in_wfi(int cpu) { - if (cpu_is_u5500()) + if (machine_is_u5500()) return -EINVAL; else return db8500_prcmu_is_cpu_in_wfi(cpu); -- cgit v1.2.3 From 569aa60b8a69618517202fb3490c08e716327395 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 20 Mar 2012 00:16:38 +0100 Subject: fix compilation error with CONFIG_DB550 and CONFIG_CPU_IDLE * db5500_prcmu_set_power_state * db5500_prcmu_enable_wakeups are called from prcmu_set_power_state and prcmu_enable_wakeups but they are not defined. When applying the cpuidle patch, the code invokes both functions and that leads to an undefined functions compilation error. So let's do like the other functions and return EINVAL. Signed-off-by: Daniel Lezcano --- include/linux/mfd/dbx500-prcmu.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/linux/mfd/dbx500-prcmu.h b/include/linux/mfd/dbx500-prcmu.h index 27f35b8a107..df7a835c447 100644 --- a/include/linux/mfd/dbx500-prcmu.h +++ b/include/linux/mfd/dbx500-prcmu.h @@ -230,8 +230,7 @@ static inline int prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) { if (machine_is_u5500()) - return db5500_prcmu_set_power_state(state, keep_ulp_clk, - keep_ap_pll); + return -EINVAL; else return db8500_prcmu_set_power_state(state, keep_ulp_clk, keep_ap_pll); @@ -296,7 +295,7 @@ static inline int prcmu_set_epod(u16 epod_id, u8 epod_state) static inline void prcmu_enable_wakeups(u32 wakeups) { if (machine_is_u5500()) - db5500_prcmu_enable_wakeups(wakeups); + return; else db8500_prcmu_enable_wakeups(wakeups); } -- cgit v1.2.3 From 6f67c47532b20be74f2201589a98ceec2209ea09 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 20 Mar 2012 00:16:39 +0100 Subject: ux500 : add cpuidle for WFI and ARM retention This patch adds the cpuidle driver for the ux500 SoC. The boards saves 12mA with these states. It is based on the latest cpuidle consolidation from Robert Lee. The cpu can go to retention only if the other core is in WFI. If the other cpu is in WFI and we decoupled the gic from the cores, then we have the guarantee, it won't be wake up. It is up to the prcmu firmware to recouple the gic automatically after the power state mode is selected. Signed-off-by: Daniel Lezcano --- arch/arm/mach-ux500/Makefile | 2 +- arch/arm/mach-ux500/cpuidle.c | 169 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 arch/arm/mach-ux500/cpuidle.c diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 6bd2f451c18..6a435e5155e 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -18,4 +18,4 @@ obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o obj-$(CONFIG_U5500_MODEM_IRQ) += modem-irq-db5500.o obj-$(CONFIG_U5500_MBOX) += mbox-db5500.o - +obj-$(CONFIG_CPU_IDLE) += cpuidle.o diff --git a/arch/arm/mach-ux500/cpuidle.c b/arch/arm/mach-ux500/cpuidle.c new file mode 100644 index 00000000000..9aabfefc28d --- /dev/null +++ b/arch/arm/mach-ux500/cpuidle.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2012 Linaro : Daniel Lezcano (IBM) + * + * Based on the work of Rickard Andersson + * and Jonas Aaberg . + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +static atomic_t master = ATOMIC_INIT(0); +static DEFINE_SPINLOCK(master_lock); +static DEFINE_PER_CPU(struct cpuidle_device, ux500_cpuidle_device); + +static inline int ux500_enter_idle(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int this_cpu = smp_processor_id(); + bool recouple = false; + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &this_cpu); + + if (atomic_inc_return(&master) == num_online_cpus()) { + + /* With this lock, we prevent the other cpu to exit and enter + * this function again and become the master */ + if (!spin_trylock(&master_lock)) + goto wfi; + + /* decouple the gic from the A9 cores */ + if (prcmu_gic_decouple()) + goto out; + + /* If an error occur, we will have to recouple the gic + * manually */ + recouple = true; + + /* At this state, as the gic is decoupled, if the other + * cpu is in WFI, we have the guarantee it won't be wake + * up, so we can safely go to retention */ + if (!prcmu_is_cpu_in_wfi(this_cpu ? 0 : 1)) + goto out; + + /* The prcmu will be in charge of watching the interrupts + * and wake up the cpus */ + if (prcmu_copy_gic_settings()) + goto out; + + /* Check in the meantime an interrupt did + * not occur on the gic ... */ + if (prcmu_gic_pending_irq()) + goto out; + + /* ... and the prcmu */ + if (prcmu_pending_irq()) + goto out; + + /* Go to the retention state, the prcmu will wait for the + * cpu to go WFI and this is what happens after exiting this + * 'master' critical section */ + if (prcmu_set_power_state(PRCMU_AP_IDLE, true, true)) + goto out; + + /* When we switch to retention, the prcmu is in charge + * of recoupling the gic automatically */ + recouple = false; + + spin_unlock(&master_lock); + } +wfi: + cpu_do_idle(); +out: + atomic_dec(&master); + + if (recouple) { + prcmu_gic_recouple(); + spin_unlock(&master_lock); + } + + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &this_cpu); + + return index; +} + +static struct cpuidle_driver ux500_idle_driver = { + .name = "ux500_idle", + .owner = THIS_MODULE, + .en_core_tk_irqen = 1, + .states = { + ARM_CPUIDLE_WFI_STATE, + { + .enter = ux500_enter_idle, + .exit_latency = 70, + .target_residency = 260, + .flags = CPUIDLE_FLAG_TIME_VALID, + .name = "ApIdle", + .desc = "ARM Retention", + }, + }, + .safe_state_index = 0, + .state_count = 2, +}; + +int __init ux500_idle_init(void) +{ + int ret, cpu; + struct cpuidle_device *device; + + /* Configure wake up reasons */ + prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) | + PRCMU_WAKEUP(ABB)); + + ret = cpuidle_register_driver(&ux500_idle_driver); + if (ret) { + printk(KERN_ERR "failed to register ux500 idle driver\n"); + return ret; + } + + for_each_online_cpu(cpu) { + device = &per_cpu(ux500_cpuidle_device, cpu); + device->cpu = cpu; + device->state_count = ux500_idle_driver.state_count; + + ret = cpuidle_register_device(device); + if (ret) { + printk(KERN_ERR "Failed to register cpuidle " + "device for cpu%d\n", cpu); + goto out_unregister; + } + } +out: + return ret; + +out_unregister: + for_each_online_cpu(cpu) { + device = &per_cpu(ux500_cpuidle_device, cpu); + cpuidle_unregister_device(device); + } + + cpuidle_unregister_driver(&ux500_idle_driver); + goto out; +} + +void __exit ux500_idle_exit(void) +{ + int cpu; + struct cpuidle_device *device; + + for_each_online_cpu(cpu) { + device = &per_cpu(ux500_cpuidle_device, cpu); + cpuidle_unregister_device(device); + } + + cpuidle_unregister_driver(&ux500_idle_driver); +} + +device_initcall(ux500_idle_init); -- cgit v1.2.3 From 85141dd3025c5c41d921220ddac6fdb8e2393040 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 20 Mar 2012 00:16:40 +0100 Subject: disable the cpuidle for the db5500 board As the different prcmu routines are not supporting the db5500 board and will return EINVAL, it is pointless to use the other states than WFI. Signed-off-by: Daniel Lezcano --- arch/arm/mach-ux500/cpuidle.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/mach-ux500/cpuidle.c b/arch/arm/mach-ux500/cpuidle.c index 9aabfefc28d..04be6ed51ed 100644 --- a/arch/arm/mach-ux500/cpuidle.c +++ b/arch/arm/mach-ux500/cpuidle.c @@ -118,6 +118,11 @@ int __init ux500_idle_init(void) int ret, cpu; struct cpuidle_device *device; + if (machine_is_u5500()) { + printk(KERN_WARNING "cpuidle: u5500 not supported\n"); + return -ENOSYS; + } + /* Configure wake up reasons */ prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) | PRCMU_WAKEUP(ABB)); -- cgit v1.2.3