diff options
Diffstat (limited to 'arch/arm/mach-tegra/cpuidle-tegra30.c')
-rw-r--r-- | arch/arm/mach-tegra/cpuidle-tegra30.c | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/cpuidle-tegra30.c b/arch/arm/mach-tegra/cpuidle-tegra30.c new file mode 100644 index 000000000..c1417361e --- /dev/null +++ b/arch/arm/mach-tegra/cpuidle-tegra30.c @@ -0,0 +1,148 @@ +/* + * CPU idle driver for Tegra CPUs + * + * Copyright (c) 2010-2012, NVIDIA Corporation. + * Copyright (c) 2011 Google, Inc. + * Author: Colin Cross <ccross@android.com> + * Gary King <gking@nvidia.com> + * + * Rework for 3.3 by Peter De Schrijver <pdeschrijver@nvidia.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/clk/tegra.h> +#include <linux/tick.h> +#include <linux/cpuidle.h> +#include <linux/cpu_pm.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include <asm/cpuidle.h> +#include <asm/smp_plat.h> +#include <asm/suspend.h> + +#include "cpuidle.h" +#include "pm.h" +#include "sleep.h" + +#ifdef CONFIG_PM_SLEEP +static int tegra30_idle_lp2(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index); +#endif + +static struct cpuidle_driver tegra_idle_driver = { + .name = "tegra_idle", + .owner = THIS_MODULE, +#ifdef CONFIG_PM_SLEEP + .state_count = 2, +#else + .state_count = 1, +#endif + .states = { + [0] = ARM_CPUIDLE_WFI_STATE_PWR(600), +#ifdef CONFIG_PM_SLEEP + [1] = { + .enter = tegra30_idle_lp2, + .exit_latency = 2000, + .target_residency = 2200, + .power_usage = 0, + .name = "powered-down", + .desc = "CPU power gated", + }, +#endif + }, +}; + +#ifdef CONFIG_PM_SLEEP +static bool tegra30_cpu_cluster_power_down(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + /* All CPUs entering LP2 is not working. + * Don't let CPU0 enter LP2 when any secondary CPU is online. + */ + if (num_online_cpus() > 1 || !tegra_cpu_rail_off_ready()) { + cpu_do_idle(); + return false; + } + + tick_broadcast_enter(); + + tegra_idle_lp2_last(); + + tick_broadcast_exit(); + + return true; +} + +#ifdef CONFIG_SMP +static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + tick_broadcast_enter(); + + smp_wmb(); + + cpu_suspend(0, tegra30_sleep_cpu_secondary_finish); + + tick_broadcast_exit(); + + return true; +} +#else +static inline bool tegra30_cpu_core_power_down(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + return true; +} +#endif + +static int tegra30_idle_lp2(struct cpuidle_device *dev, + struct cpuidle_driver *drv, + int index) +{ + bool entered_lp2 = false; + bool last_cpu; + + local_fiq_disable(); + + last_cpu = tegra_set_cpu_in_lp2(); + cpu_pm_enter(); + + if (dev->cpu == 0) { + if (last_cpu) + entered_lp2 = tegra30_cpu_cluster_power_down(dev, drv, + index); + else + cpu_do_idle(); + } else { + entered_lp2 = tegra30_cpu_core_power_down(dev, drv, index); + } + + cpu_pm_exit(); + tegra_clear_cpu_in_lp2(); + + local_fiq_enable(); + + smp_rmb(); + + return (entered_lp2) ? index : 0; +} +#endif + +int __init tegra30_cpuidle_init(void) +{ + return cpuidle_register(&tegra_idle_driver, NULL); +} |