aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/cpuidle
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/cpuidle')
-rw-r--r--drivers/cpuidle/cpuidle-tegra.c74
1 files changed, 69 insertions, 5 deletions
diff --git a/drivers/cpuidle/cpuidle-tegra.c b/drivers/cpuidle/cpuidle-tegra.c
index 5691bdcf11cb..cd969ec18651 100644
--- a/drivers/cpuidle/cpuidle-tegra.c
+++ b/drivers/cpuidle/cpuidle-tegra.c
@@ -37,6 +37,7 @@
enum tegra_state {
TEGRA_C1,
+ TEGRA_C7,
TEGRA_CC6,
TEGRA_STATE_COUNT,
};
@@ -122,6 +123,11 @@ static int tegra_cpuidle_cc6_enter(unsigned int cpu)
return ret;
}
+static int tegra_cpuidle_c7_enter(void)
+{
+ return cpu_suspend(0, tegra30_pm_secondary_cpu_suspend);
+}
+
static int tegra_cpuidle_coupled_barrier(struct cpuidle_device *dev)
{
if (tegra_pending_sgi()) {
@@ -169,6 +175,10 @@ static int tegra_cpuidle_state_enter(struct cpuidle_device *dev,
cpu_pm_enter();
switch (index) {
+ case TEGRA_C7:
+ ret = tegra_cpuidle_c7_enter();
+ break;
+
case TEGRA_CC6:
ret = tegra_cpuidle_cc6_enter(cpu);
break;
@@ -185,6 +195,24 @@ static int tegra_cpuidle_state_enter(struct cpuidle_device *dev,
return ret;
}
+static int tegra_cpuidle_adjust_state_index(int index, unsigned int cpu)
+{
+ /*
+ * On Tegra30 CPU0 can't be power-gated separately from secondary
+ * cores because it gates the whole CPU cluster.
+ */
+ if (cpu > 0 || index != TEGRA_C7 || tegra_get_chip_id() != TEGRA30)
+ return index;
+
+ /* put CPU0 into C1 if C7 is requested and secondaries are online */
+ if (!IS_ENABLED(CONFIG_PM_SLEEP) || num_online_cpus() > 1)
+ index = TEGRA_C1;
+ else
+ index = TEGRA_CC6;
+
+ return index;
+}
+
static int tegra_cpuidle_enter(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
@@ -192,10 +220,17 @@ static int tegra_cpuidle_enter(struct cpuidle_device *dev,
unsigned int cpu = cpu_logical_map(dev->cpu);
int err;
- err = tegra_cpuidle_state_enter(dev, index, cpu);
- if (err && err != -EINTR)
- pr_err_once("cpu%u failed to enter idle state %d err: %d\n",
- cpu, index, err);
+ index = tegra_cpuidle_adjust_state_index(index, cpu);
+ if (dev->states_usage[index].disable)
+ return -1;
+
+ if (index == TEGRA_C1)
+ err = arm_cpuidle_simple_enter(dev, drv, index);
+ else
+ err = tegra_cpuidle_state_enter(dev, index, cpu);
+
+ if (err && (err != -EINTR || index != TEGRA_CC6))
+ pr_err_once("failed to enter state %d err: %d\n", index, err);
return err ? -1 : index;
}
@@ -221,6 +256,15 @@ static struct cpuidle_driver tegra_idle_driver = {
.name = "tegra_idle",
.states = {
[TEGRA_C1] = ARM_CPUIDLE_WFI_STATE_PWR(600),
+ [TEGRA_C7] = {
+ .enter = tegra_cpuidle_enter,
+ .exit_latency = 2000,
+ .target_residency = 2200,
+ .power_usage = 100,
+ .flags = CPUIDLE_FLAG_TIMER_STOP,
+ .name = "C7",
+ .desc = "CPU core powered off",
+ },
[TEGRA_CC6] = {
.enter = tegra_cpuidle_enter,
.exit_latency = 5000,
@@ -265,8 +309,28 @@ static int tegra_cpuidle_probe(struct platform_device *pdev)
* Tegra-arch core and PMC driver, is unavailable if PM-sleep option
* is disabled.
*/
- if (!IS_ENABLED(CONFIG_PM_SLEEP))
+ if (!IS_ENABLED(CONFIG_PM_SLEEP)) {
+ tegra_cpuidle_disable_state(TEGRA_C7);
tegra_cpuidle_disable_state(TEGRA_CC6);
+ }
+
+ /*
+ * Generic WFI state (also known as C1 or LP3) and the coupled CPU
+ * cluster power-off (CC6 or LP2) states are common for all Tegra SoCs.
+ */
+ switch (tegra_get_chip_id()) {
+ case TEGRA20:
+ /* Tegra20 isn't capable to power-off individual CPU cores */
+ tegra_cpuidle_disable_state(TEGRA_C7);
+ break;
+
+ case TEGRA30:
+ tegra_cpuidle_disable_state(TEGRA_CC6);
+ break;
+
+ default:
+ return -EINVAL;
+ }
return cpuidle_register(&tegra_idle_driver, cpu_possible_mask);
}