linux/arch/arm/mach-omap2/cpuidle44xx.c
<<
>>
Prefs
   1/*
   2 * OMAP4+ CPU idle Routines
   3 *
   4 * Copyright (C) 2011-2013 Texas Instruments, Inc.
   5 * Santosh Shilimkar <santosh.shilimkar@ti.com>
   6 * Rajendra Nayak <rnayak@ti.com>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2 as
  10 * published by the Free Software Foundation.
  11 */
  12
  13#include <linux/sched.h>
  14#include <linux/cpuidle.h>
  15#include <linux/cpu_pm.h>
  16#include <linux/export.h>
  17#include <linux/tick.h>
  18
  19#include <asm/cpuidle.h>
  20
  21#include "common.h"
  22#include "pm.h"
  23#include "prm.h"
  24#include "soc.h"
  25#include "clockdomain.h"
  26
  27#define MAX_CPUS        2
  28
  29/* Machine specific information */
  30struct idle_statedata {
  31        u32 cpu_state;
  32        u32 mpu_logic_state;
  33        u32 mpu_state;
  34        u32 mpu_state_vote;
  35};
  36
  37static struct idle_statedata omap4_idle_data[] = {
  38        {
  39                .cpu_state = PWRDM_POWER_ON,
  40                .mpu_state = PWRDM_POWER_ON,
  41                .mpu_logic_state = PWRDM_POWER_RET,
  42        },
  43        {
  44                .cpu_state = PWRDM_POWER_OFF,
  45                .mpu_state = PWRDM_POWER_RET,
  46                .mpu_logic_state = PWRDM_POWER_RET,
  47        },
  48        {
  49                .cpu_state = PWRDM_POWER_OFF,
  50                .mpu_state = PWRDM_POWER_RET,
  51                .mpu_logic_state = PWRDM_POWER_OFF,
  52        },
  53};
  54
  55static struct idle_statedata omap5_idle_data[] = {
  56        {
  57                .cpu_state = PWRDM_POWER_ON,
  58                .mpu_state = PWRDM_POWER_ON,
  59                .mpu_logic_state = PWRDM_POWER_ON,
  60        },
  61        {
  62                .cpu_state = PWRDM_POWER_RET,
  63                .mpu_state = PWRDM_POWER_RET,
  64                .mpu_logic_state = PWRDM_POWER_RET,
  65        },
  66};
  67
  68static struct powerdomain *mpu_pd, *cpu_pd[MAX_CPUS];
  69static struct clockdomain *cpu_clkdm[MAX_CPUS];
  70
  71static atomic_t abort_barrier;
  72static bool cpu_done[MAX_CPUS];
  73static struct idle_statedata *state_ptr = &omap4_idle_data[0];
  74static DEFINE_RAW_SPINLOCK(mpu_lock);
  75
  76/* Private functions */
  77
  78/**
  79 * omap_enter_idle_[simple/coupled] - OMAP4PLUS cpuidle entry functions
  80 * @dev: cpuidle device
  81 * @drv: cpuidle driver
  82 * @index: the index of state to be entered
  83 *
  84 * Called from the CPUidle framework to program the device to the
  85 * specified low power state selected by the governor.
  86 * Returns the amount of time spent in the low power state.
  87 */
  88static int omap_enter_idle_simple(struct cpuidle_device *dev,
  89                        struct cpuidle_driver *drv,
  90                        int index)
  91{
  92        omap_do_wfi();
  93        return index;
  94}
  95
  96static int omap_enter_idle_smp(struct cpuidle_device *dev,
  97                               struct cpuidle_driver *drv,
  98                               int index)
  99{
 100        struct idle_statedata *cx = state_ptr + index;
 101        unsigned long flag;
 102
 103        raw_spin_lock_irqsave(&mpu_lock, flag);
 104        cx->mpu_state_vote++;
 105        if (cx->mpu_state_vote == num_online_cpus()) {
 106                pwrdm_set_logic_retst(mpu_pd, cx->mpu_logic_state);
 107                omap_set_pwrdm_state(mpu_pd, cx->mpu_state);
 108        }
 109        raw_spin_unlock_irqrestore(&mpu_lock, flag);
 110
 111        omap4_enter_lowpower(dev->cpu, cx->cpu_state);
 112
 113        raw_spin_lock_irqsave(&mpu_lock, flag);
 114        if (cx->mpu_state_vote == num_online_cpus())
 115                omap_set_pwrdm_state(mpu_pd, PWRDM_POWER_ON);
 116        cx->mpu_state_vote--;
 117        raw_spin_unlock_irqrestore(&mpu_lock, flag);
 118
 119        return index;
 120}
 121
 122static int omap_enter_idle_coupled(struct cpuidle_device *dev,
 123                        struct cpuidle_driver *drv,
 124                        int index)
 125{
 126        struct idle_statedata *cx = state_ptr + index;
 127        u32 mpuss_can_lose_context = 0;
 128
 129        /*
 130         * CPU0 has to wait and stay ON until CPU1 is OFF state.
 131         * This is necessary to honour hardware recommondation
 132         * of triggeing all the possible low power modes once CPU1 is
 133         * out of coherency and in OFF mode.
 134         */
 135        if (dev->cpu == 0 && cpumask_test_cpu(1, cpu_online_mask)) {
 136                while (pwrdm_read_pwrst(cpu_pd[1]) != PWRDM_POWER_OFF) {
 137                        cpu_relax();
 138
 139                        /*
 140                         * CPU1 could have already entered & exited idle
 141                         * without hitting off because of a wakeup
 142                         * or a failed attempt to hit off mode.  Check for
 143                         * that here, otherwise we could spin forever
 144                         * waiting for CPU1 off.
 145                         */
 146                        if (cpu_done[1])
 147                            goto fail;
 148
 149                }
 150        }
 151
 152        mpuss_can_lose_context = (cx->mpu_state == PWRDM_POWER_RET) &&
 153                                 (cx->mpu_logic_state == PWRDM_POWER_OFF);
 154
 155        tick_broadcast_enter();
 156
 157        /*
 158         * Call idle CPU PM enter notifier chain so that
 159         * VFP and per CPU interrupt context is saved.
 160         */
 161        cpu_pm_enter();
 162
 163        if (dev->cpu == 0) {
 164                pwrdm_set_logic_retst(mpu_pd, cx->mpu_logic_state);
 165                omap_set_pwrdm_state(mpu_pd, cx->mpu_state);
 166
 167                /*
 168                 * Call idle CPU cluster PM enter notifier chain
 169                 * to save GIC and wakeupgen context.
 170                 */
 171                if (mpuss_can_lose_context)
 172                        cpu_cluster_pm_enter();
 173        }
 174
 175        omap4_enter_lowpower(dev->cpu, cx->cpu_state);
 176        cpu_done[dev->cpu] = true;
 177
 178        /* Wakeup CPU1 only if it is not offlined */
 179        if (dev->cpu == 0 && cpumask_test_cpu(1, cpu_online_mask)) {
 180
 181                if (IS_PM44XX_ERRATUM(PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD) &&
 182                    mpuss_can_lose_context)
 183                        gic_dist_disable();
 184
 185                clkdm_deny_idle(cpu_clkdm[1]);
 186                omap_set_pwrdm_state(cpu_pd[1], PWRDM_POWER_ON);
 187                clkdm_allow_idle(cpu_clkdm[1]);
 188
 189                if (IS_PM44XX_ERRATUM(PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD) &&
 190                    mpuss_can_lose_context) {
 191                        while (gic_dist_disabled()) {
 192                                udelay(1);
 193                                cpu_relax();
 194                        }
 195                        gic_timer_retrigger();
 196                }
 197        }
 198
 199        /*
 200         * Call idle CPU PM exit notifier chain to restore
 201         * VFP and per CPU IRQ context.
 202         */
 203        cpu_pm_exit();
 204
 205        /*
 206         * Call idle CPU cluster PM exit notifier chain
 207         * to restore GIC and wakeupgen context.
 208         */
 209        if (dev->cpu == 0 && mpuss_can_lose_context)
 210                cpu_cluster_pm_exit();
 211
 212        tick_broadcast_exit();
 213
 214fail:
 215        cpuidle_coupled_parallel_barrier(dev, &abort_barrier);
 216        cpu_done[dev->cpu] = false;
 217
 218        return index;
 219}
 220
 221/*
 222 * For each cpu, setup the broadcast timer because local timers
 223 * stops for the states above C1.
 224 */
 225static void omap_setup_broadcast_timer(void *arg)
 226{
 227        tick_broadcast_enable();
 228}
 229
 230static struct cpuidle_driver omap4_idle_driver = {
 231        .name                           = "omap4_idle",
 232        .owner                          = THIS_MODULE,
 233        .states = {
 234                {
 235                        /* C1 - CPU0 ON + CPU1 ON + MPU ON */
 236                        .exit_latency = 2 + 2,
 237                        .target_residency = 5,
 238                        .enter = omap_enter_idle_simple,
 239                        .name = "C1",
 240                        .desc = "CPUx ON, MPUSS ON"
 241                },
 242                {
 243                        /* C2 - CPU0 OFF + CPU1 OFF + MPU CSWR */
 244                        .exit_latency = 328 + 440,
 245                        .target_residency = 960,
 246                        .flags = CPUIDLE_FLAG_COUPLED,
 247                        .enter = omap_enter_idle_coupled,
 248                        .name = "C2",
 249                        .desc = "CPUx OFF, MPUSS CSWR",
 250                },
 251                {
 252                        /* C3 - CPU0 OFF + CPU1 OFF + MPU OSWR */
 253                        .exit_latency = 460 + 518,
 254                        .target_residency = 1100,
 255                        .flags = CPUIDLE_FLAG_COUPLED,
 256                        .enter = omap_enter_idle_coupled,
 257                        .name = "C3",
 258                        .desc = "CPUx OFF, MPUSS OSWR",
 259                },
 260        },
 261        .state_count = ARRAY_SIZE(omap4_idle_data),
 262        .safe_state_index = 0,
 263};
 264
 265static struct cpuidle_driver omap5_idle_driver = {
 266        .name                           = "omap5_idle",
 267        .owner                          = THIS_MODULE,
 268        .states = {
 269                {
 270                        /* C1 - CPU0 ON + CPU1 ON + MPU ON */
 271                        .exit_latency = 2 + 2,
 272                        .target_residency = 5,
 273                        .enter = omap_enter_idle_simple,
 274                        .name = "C1",
 275                        .desc = "CPUx WFI, MPUSS ON"
 276                },
 277                {
 278                        /* C2 - CPU0 RET + CPU1 RET + MPU CSWR */
 279                        .exit_latency = 48 + 60,
 280                        .target_residency = 100,
 281                        .flags = CPUIDLE_FLAG_TIMER_STOP,
 282                        .enter = omap_enter_idle_smp,
 283                        .name = "C2",
 284                        .desc = "CPUx CSWR, MPUSS CSWR",
 285                },
 286        },
 287        .state_count = ARRAY_SIZE(omap5_idle_data),
 288        .safe_state_index = 0,
 289};
 290
 291/* Public functions */
 292
 293/**
 294 * omap4_idle_init - Init routine for OMAP4+ idle
 295 *
 296 * Registers the OMAP4+ specific cpuidle driver to the cpuidle
 297 * framework with the valid set of states.
 298 */
 299int __init omap4_idle_init(void)
 300{
 301        struct cpuidle_driver *idle_driver;
 302
 303        if (soc_is_omap54xx()) {
 304                state_ptr = &omap5_idle_data[0];
 305                idle_driver = &omap5_idle_driver;
 306        } else {
 307                state_ptr = &omap4_idle_data[0];
 308                idle_driver = &omap4_idle_driver;
 309        }
 310
 311        mpu_pd = pwrdm_lookup("mpu_pwrdm");
 312        cpu_pd[0] = pwrdm_lookup("cpu0_pwrdm");
 313        cpu_pd[1] = pwrdm_lookup("cpu1_pwrdm");
 314        if ((!mpu_pd) || (!cpu_pd[0]) || (!cpu_pd[1]))
 315                return -ENODEV;
 316
 317        cpu_clkdm[0] = clkdm_lookup("mpu0_clkdm");
 318        cpu_clkdm[1] = clkdm_lookup("mpu1_clkdm");
 319        if (!cpu_clkdm[0] || !cpu_clkdm[1])
 320                return -ENODEV;
 321
 322        /* Configure the broadcast timer on each cpu */
 323        on_each_cpu(omap_setup_broadcast_timer, NULL, 1);
 324
 325        return cpuidle_register(idle_driver, cpu_online_mask);
 326}
 327