diff options
Diffstat (limited to 'drivers/cpuidle/cpuidle-exynos4210.c')
-rw-r--r-- | drivers/cpuidle/cpuidle-exynos4210.c | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/drivers/cpuidle/cpuidle-exynos4210.c b/drivers/cpuidle/cpuidle-exynos4210.c new file mode 100644 index 000000000000..699f87fc7de5 --- /dev/null +++ b/drivers/cpuidle/cpuidle-exynos4210.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Copyright (c) 2014 Linaro : Daniel Lezcano <daniel.lezcano@linaro.org> + * http://www.linaro.org + * + * Based on the work of Colin Cross <ccross@android.com> + * + * 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/io.h> +#include <linux/platform_device.h> + +#include <asm/proc-fns.h> +#include <asm/suspend.h> +#include <asm/cpuidle.h> + +#include <plat/pm.h> +#include <plat/cpu.h> +#include <plat/map-base.h> +#include <plat/map-s5p.h> + +static atomic_t exynos_idle_barrier; +static atomic_t cpu1_wakeup = ATOMIC_INIT(0); + +#define BOOT_VECTOR S5P_VA_SYSRAM +#define S5P_VA_PMU S3C_ADDR(0x02180000) +#define S5P_PMUREG(x) (S5P_VA_PMU + (x)) +#define S5P_ARM_CORE1_CONFIGURATION S5P_PMUREG(0x2080) +#define S5P_ARM_CORE1_STATUS S5P_PMUREG(0x2084) + +static void (*exynos_aftr)(void); +extern void exynos_cpu_resume(void); +static int cpu_suspend_finish(unsigned long flags) +{ + if (flags) + exynos_aftr(); + + cpu_do_idle(); + + return -1; +} + +static int exynos_cpu0_enter_aftr(void) +{ + int ret = -1; + + /* + * If the other cpu is powered on, we have to power it off, because + * the AFTR state won't work otherwise + */ + if (cpu_online(1)) { + + /* + * We reach a sync point with the coupled idle state, we know + * the other cpu will power down itself or will abort the + * sequence, let's wait for one of these to happen + */ + while (__raw_readl(S5P_ARM_CORE1_STATUS) & 3) { + + /* + * The other cpu may skip idle and boot back + * up again + */ + if (atomic_read(&cpu1_wakeup)) + goto abort; + + /* + * The other cpu may bounce through idle and + * boot back up again, getting stuck in the + * boot rom code + */ + if (__raw_readl(BOOT_VECTOR) == 0) + goto abort; + + cpu_relax(); + } + } + + cpu_pm_enter(); + + ret = cpu_suspend(1, cpu_suspend_finish); + + cpu_pm_exit(); + +abort: + if (cpu_online(1)) { + /* + * Set the boot vector to something non-zero + */ + __raw_writel(virt_to_phys(exynos_cpu_resume), + BOOT_VECTOR); + dsb(); + + /* + * Turn on cpu1 and wait for it to be on + */ + __raw_writel(0x3, S5P_ARM_CORE1_CONFIGURATION); + while ((__raw_readl(S5P_ARM_CORE1_STATUS) & 3) != 3) + cpu_relax(); + + /* + * Wait for cpu1 to get stuck in the boot rom + */ + while ((__raw_readl(BOOT_VECTOR) != 0) && + !atomic_read(&cpu1_wakeup)) + cpu_relax(); + + if (!atomic_read(&cpu1_wakeup)) { + /* + * Poke cpu1 out of the boot rom + */ + __raw_writel(virt_to_phys(exynos_cpu_resume), + BOOT_VECTOR); + dsb_sev(); + } + + /* + * Wait for cpu1 to finish booting + */ + while (!atomic_read(&cpu1_wakeup)) + cpu_relax(); + } + + return ret; +} + +static int exynos_powerdown_cpu1(void) +{ + int ret = -1; + + /* + * Idle sequence for cpu1 + */ + if (cpu_pm_enter()) + goto cpu1_aborted; + + /* + * Turn off cpu 1 + */ + __raw_writel(0, S5P_ARM_CORE1_CONFIGURATION); + + ret = cpu_suspend(0, cpu_suspend_finish); + + cpu_pm_exit(); + +cpu1_aborted: + dsb(); + /* + * Notify cpu 0 that cpu 1 is awake + */ + atomic_set(&cpu1_wakeup, 1); + + return ret; +} + +static int exynos_enter_aftr(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int ret; + + __raw_writel(virt_to_phys(exynos_cpu_resume), BOOT_VECTOR); + + /* + * Waiting all cpus to reach this point at the same moment + */ + cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); + + /* + * Both cpus will reach this point at the same time + */ + ret = dev->cpu ? exynos_powerdown_cpu1() : exynos_cpu0_enter_aftr(); + if (ret) + index = ret; + + /* + * Waiting all cpus to finish the power sequence before going further + */ + cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); + + atomic_set(&cpu1_wakeup, 0); + + return index; +} + +static struct cpuidle_driver exynos_idle_driver = { + .name = "exynos4210_idle", + .owner = THIS_MODULE, + .states = { + ARM_CPUIDLE_WFI_STATE, + [1] = { + .enter = exynos_enter_aftr, + .exit_latency = 5000, + .target_residency = 10000, + .flags = CPUIDLE_FLAG_TIME_VALID | + CPUIDLE_FLAG_COUPLED | CPUIDLE_FLAG_TIMER_STOP, + .name = "C1", + .desc = "ARM power down", + }, + }, + .state_count = 2, + .safe_state_index = 0, +}; + +static int exynos_cpuidle_probe(struct platform_device *pdev) +{ + exynos_aftr = (void *)(pdev->dev.platform_data); + + return cpuidle_register(&exynos_idle_driver, cpu_possible_mask); +} + +static struct platform_driver exynos_cpuidle_driver = { + .driver = { + .name = "exynos4210-cpuidle", + .owner = THIS_MODULE, + }, + .probe = exynos_cpuidle_probe, +}; + +module_platform_driver(exynos_cpuidle_driver); |