|  | /* | 
|  | * 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 "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); | 
|  | } |