linux/arch/arm/mach-tegra/cpuidle-tegra20.c
<<
>>
Prefs
   1/*
   2 * CPU idle driver for Tegra CPUs
   3 *
   4 * Copyright (c) 2010-2012, NVIDIA Corporation.
   5 * Copyright (c) 2011 Google, Inc.
   6 * Author: Colin Cross <ccross@android.com>
   7 *         Gary King <gking@nvidia.com>
   8 *
   9 * Rework for 3.3 by Peter De Schrijver <pdeschrijver@nvidia.com>
  10 *
  11 * This program is free software; you can redistribute it and/or modify
  12 * it under the terms of the GNU General Public License as published by
  13 * the Free Software Foundation; either version 2 of the License, or
  14 * (at your option) any later version.
  15 *
  16 * This program is distributed in the hope that it will be useful, but WITHOUT
  17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  18 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  19 * more details.
  20 */
  21
  22#include <linux/kernel.h>
  23#include <linux/module.h>
  24#include <linux/cpuidle.h>
  25#include <linux/cpu_pm.h>
  26#include <linux/clockchips.h>
  27#include <linux/clk/tegra.h>
  28
  29#include <asm/cpuidle.h>
  30#include <asm/proc-fns.h>
  31#include <asm/suspend.h>
  32#include <asm/smp_plat.h>
  33
  34#include "pm.h"
  35#include "sleep.h"
  36#include "iomap.h"
  37#include "irq.h"
  38#include "flowctrl.h"
  39
  40#ifdef CONFIG_PM_SLEEP
  41static bool abort_flag;
  42static atomic_t abort_barrier;
  43static int tegra20_idle_lp2_coupled(struct cpuidle_device *dev,
  44                                    struct cpuidle_driver *drv,
  45                                    int index);
  46#define TEGRA20_MAX_STATES 2
  47#else
  48#define TEGRA20_MAX_STATES 1
  49#endif
  50
  51static struct cpuidle_driver tegra_idle_driver = {
  52        .name = "tegra_idle",
  53        .owner = THIS_MODULE,
  54        .states = {
  55                ARM_CPUIDLE_WFI_STATE_PWR(600),
  56#ifdef CONFIG_PM_SLEEP
  57                {
  58                        .enter            = tegra20_idle_lp2_coupled,
  59                        .exit_latency     = 5000,
  60                        .target_residency = 10000,
  61                        .power_usage      = 0,
  62                        .flags            = CPUIDLE_FLAG_TIME_VALID |
  63                        CPUIDLE_FLAG_COUPLED,
  64                        .name             = "powered-down",
  65                        .desc             = "CPU power gated",
  66                },
  67#endif
  68        },
  69        .state_count = TEGRA20_MAX_STATES,
  70        .safe_state_index = 0,
  71};
  72
  73#ifdef CONFIG_PM_SLEEP
  74#ifdef CONFIG_SMP
  75static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
  76
  77static int tegra20_reset_sleeping_cpu_1(void)
  78{
  79        int ret = 0;
  80
  81        tegra_pen_lock();
  82
  83        if (readl(pmc + PMC_SCRATCH41) == CPU_RESETTABLE)
  84                tegra20_cpu_shutdown(1);
  85        else
  86                ret = -EINVAL;
  87
  88        tegra_pen_unlock();
  89
  90        return ret;
  91}
  92
  93static void tegra20_wake_cpu1_from_reset(void)
  94{
  95        tegra_pen_lock();
  96
  97        tegra20_cpu_clear_resettable();
  98
  99        /* enable cpu clock on cpu */
 100        tegra_enable_cpu_clock(1);
 101
 102        /* take the CPU out of reset */
 103        tegra_cpu_out_of_reset(1);
 104
 105        /* unhalt the cpu */
 106        flowctrl_write_cpu_halt(1, 0);
 107
 108        tegra_pen_unlock();
 109}
 110
 111static int tegra20_reset_cpu_1(void)
 112{
 113        if (!cpu_online(1) || !tegra20_reset_sleeping_cpu_1())
 114                return 0;
 115
 116        tegra20_wake_cpu1_from_reset();
 117        return -EBUSY;
 118}
 119#else
 120static inline void tegra20_wake_cpu1_from_reset(void)
 121{
 122}
 123
 124static inline int tegra20_reset_cpu_1(void)
 125{
 126        return 0;
 127}
 128#endif
 129
 130static bool tegra20_cpu_cluster_power_down(struct cpuidle_device *dev,
 131                                           struct cpuidle_driver *drv,
 132                                           int index)
 133{
 134        while (tegra20_cpu_is_resettable_soon())
 135                cpu_relax();
 136
 137        if (tegra20_reset_cpu_1() || !tegra_cpu_rail_off_ready())
 138                return false;
 139
 140        clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
 141
 142        tegra_idle_lp2_last();
 143
 144        clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
 145
 146        if (cpu_online(1))
 147                tegra20_wake_cpu1_from_reset();
 148
 149        return true;
 150}
 151
 152#ifdef CONFIG_SMP
 153static bool tegra20_idle_enter_lp2_cpu_1(struct cpuidle_device *dev,
 154                                         struct cpuidle_driver *drv,
 155                                         int index)
 156{
 157        clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
 158
 159        cpu_suspend(0, tegra20_sleep_cpu_secondary_finish);
 160
 161        tegra20_cpu_clear_resettable();
 162
 163        clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
 164
 165        return true;
 166}
 167#else
 168static inline bool tegra20_idle_enter_lp2_cpu_1(struct cpuidle_device *dev,
 169                                                struct cpuidle_driver *drv,
 170                                                int index)
 171{
 172        return true;
 173}
 174#endif
 175
 176static int tegra20_idle_lp2_coupled(struct cpuidle_device *dev,
 177                                    struct cpuidle_driver *drv,
 178                                    int index)
 179{
 180        u32 cpu = is_smp() ? cpu_logical_map(dev->cpu) : dev->cpu;
 181        bool entered_lp2 = false;
 182
 183        if (tegra_pending_sgi())
 184                ACCESS_ONCE(abort_flag) = true;
 185
 186        cpuidle_coupled_parallel_barrier(dev, &abort_barrier);
 187
 188        if (abort_flag) {
 189                cpuidle_coupled_parallel_barrier(dev, &abort_barrier);
 190                abort_flag = false;     /* clean flag for next coming */
 191                return -EINTR;
 192        }
 193
 194        local_fiq_disable();
 195
 196        tegra_set_cpu_in_lp2(cpu);
 197        cpu_pm_enter();
 198
 199        if (cpu == 0)
 200                entered_lp2 = tegra20_cpu_cluster_power_down(dev, drv, index);
 201        else
 202                entered_lp2 = tegra20_idle_enter_lp2_cpu_1(dev, drv, index);
 203
 204        cpu_pm_exit();
 205        tegra_clear_cpu_in_lp2(cpu);
 206
 207        local_fiq_enable();
 208
 209        smp_rmb();
 210
 211        return entered_lp2 ? index : 0;
 212}
 213#endif
 214
 215int __init tegra20_cpuidle_init(void)
 216{
 217#ifdef CONFIG_PM_SLEEP
 218        tegra_tear_down_cpu = tegra20_tear_down_cpu;
 219#endif
 220        return cpuidle_register(&tegra_idle_driver, cpu_possible_mask);
 221}
 222