linux/arch/arm/mach-omap2/cpuidle34xx.c
<<
>>
Prefs
   1/*
   2 * linux/arch/arm/mach-omap2/cpuidle34xx.c
   3 *
   4 * OMAP3 CPU IDLE Routines
   5 *
   6 * Copyright (C) 2008 Texas Instruments, Inc.
   7 * Rajendra Nayak <rnayak@ti.com>
   8 *
   9 * Copyright (C) 2007 Texas Instruments, Inc.
  10 * Karthik Dasu <karthik-dp@ti.com>
  11 *
  12 * Copyright (C) 2006 Nokia Corporation
  13 * Tony Lindgren <tony@atomide.com>
  14 *
  15 * Copyright (C) 2005 Texas Instruments, Inc.
  16 * Richard Woodruff <r-woodruff2@ti.com>
  17 *
  18 * Based on pm.c for omap2
  19 *
  20 * This program is free software; you can redistribute it and/or modify
  21 * it under the terms of the GNU General Public License version 2 as
  22 * published by the Free Software Foundation.
  23 */
  24
  25#include <linux/sched.h>
  26#include <linux/cpuidle.h>
  27#include <linux/export.h>
  28#include <linux/cpu_pm.h>
  29#include <asm/cpuidle.h>
  30
  31#include "powerdomain.h"
  32#include "clockdomain.h"
  33
  34#include "pm.h"
  35#include "control.h"
  36#include "common.h"
  37
  38/* Mach specific information to be recorded in the C-state driver_data */
  39struct omap3_idle_statedata {
  40        u8 mpu_state;
  41        u8 core_state;
  42        u8 per_min_state;
  43        u8 flags;
  44};
  45
  46static struct powerdomain *mpu_pd, *core_pd, *per_pd, *cam_pd;
  47
  48/*
  49 * Possible flag bits for struct omap3_idle_statedata.flags:
  50 *
  51 * OMAP_CPUIDLE_CX_NO_CLKDM_IDLE: don't allow the MPU clockdomain to go
  52 *    inactive.  This in turn prevents the MPU DPLL from entering autoidle
  53 *    mode, so wakeup latency is greatly reduced, at the cost of additional
  54 *    energy consumption.  This also prevents the CORE clockdomain from
  55 *    entering idle.
  56 */
  57#define OMAP_CPUIDLE_CX_NO_CLKDM_IDLE           BIT(0)
  58
  59/*
  60 * Prevent PER OFF if CORE is not in RETention or OFF as this would
  61 * disable PER wakeups completely.
  62 */
  63static struct omap3_idle_statedata omap3_idle_data[] = {
  64        {
  65                .mpu_state = PWRDM_POWER_ON,
  66                .core_state = PWRDM_POWER_ON,
  67                /* In C1 do not allow PER state lower than CORE state */
  68                .per_min_state = PWRDM_POWER_ON,
  69                .flags = OMAP_CPUIDLE_CX_NO_CLKDM_IDLE,
  70        },
  71        {
  72                .mpu_state = PWRDM_POWER_ON,
  73                .core_state = PWRDM_POWER_ON,
  74                .per_min_state = PWRDM_POWER_RET,
  75        },
  76        {
  77                .mpu_state = PWRDM_POWER_RET,
  78                .core_state = PWRDM_POWER_ON,
  79                .per_min_state = PWRDM_POWER_RET,
  80        },
  81        {
  82                .mpu_state = PWRDM_POWER_OFF,
  83                .core_state = PWRDM_POWER_ON,
  84                .per_min_state = PWRDM_POWER_RET,
  85        },
  86        {
  87                .mpu_state = PWRDM_POWER_RET,
  88                .core_state = PWRDM_POWER_RET,
  89                .per_min_state = PWRDM_POWER_OFF,
  90        },
  91        {
  92                .mpu_state = PWRDM_POWER_OFF,
  93                .core_state = PWRDM_POWER_RET,
  94                .per_min_state = PWRDM_POWER_OFF,
  95        },
  96        {
  97                .mpu_state = PWRDM_POWER_OFF,
  98                .core_state = PWRDM_POWER_OFF,
  99                .per_min_state = PWRDM_POWER_OFF,
 100        },
 101};
 102
 103/**
 104 * omap3_enter_idle - Programs OMAP3 to enter the specified state
 105 * @dev: cpuidle device
 106 * @drv: cpuidle driver
 107 * @index: the index of state to be entered
 108 */
 109static int omap3_enter_idle(struct cpuidle_device *dev,
 110                            struct cpuidle_driver *drv,
 111                            int index)
 112{
 113        struct omap3_idle_statedata *cx = &omap3_idle_data[index];
 114
 115        if (omap_irq_pending() || need_resched())
 116                goto return_sleep_time;
 117
 118        /* Deny idle for C1 */
 119        if (cx->flags & OMAP_CPUIDLE_CX_NO_CLKDM_IDLE) {
 120                clkdm_deny_idle(mpu_pd->pwrdm_clkdms[0]);
 121        } else {
 122                pwrdm_set_next_pwrst(mpu_pd, cx->mpu_state);
 123                pwrdm_set_next_pwrst(core_pd, cx->core_state);
 124        }
 125
 126        /*
 127         * Call idle CPU PM enter notifier chain so that
 128         * VFP context is saved.
 129         */
 130        if (cx->mpu_state == PWRDM_POWER_OFF)
 131                cpu_pm_enter();
 132
 133        /* Execute ARM wfi */
 134        omap_sram_idle();
 135
 136        /*
 137         * Call idle CPU PM enter notifier chain to restore
 138         * VFP context.
 139         */
 140        if (cx->mpu_state == PWRDM_POWER_OFF &&
 141            pwrdm_read_prev_pwrst(mpu_pd) == PWRDM_POWER_OFF)
 142                cpu_pm_exit();
 143
 144        /* Re-allow idle for C1 */
 145        if (cx->flags & OMAP_CPUIDLE_CX_NO_CLKDM_IDLE)
 146                clkdm_allow_idle(mpu_pd->pwrdm_clkdms[0]);
 147
 148return_sleep_time:
 149
 150        return index;
 151}
 152
 153/**
 154 * next_valid_state - Find next valid C-state
 155 * @dev: cpuidle device
 156 * @drv: cpuidle driver
 157 * @index: Index of currently selected c-state
 158 *
 159 * If the state corresponding to index is valid, index is returned back
 160 * to the caller. Else, this function searches for a lower c-state which is
 161 * still valid (as defined in omap3_power_states[]) and returns its index.
 162 *
 163 * A state is valid if the 'valid' field is enabled and
 164 * if it satisfies the enable_off_mode condition.
 165 */
 166static int next_valid_state(struct cpuidle_device *dev,
 167                            struct cpuidle_driver *drv, int index)
 168{
 169        struct omap3_idle_statedata *cx = &omap3_idle_data[index];
 170        u32 mpu_deepest_state = PWRDM_POWER_RET;
 171        u32 core_deepest_state = PWRDM_POWER_RET;
 172        int idx;
 173        int next_index = 0; /* C1 is the default value */
 174
 175        if (enable_off_mode) {
 176                mpu_deepest_state = PWRDM_POWER_OFF;
 177                /*
 178                 * Erratum i583: valable for ES rev < Es1.2 on 3630.
 179                 * CORE OFF mode is not supported in a stable form, restrict
 180                 * instead the CORE state to RET.
 181                 */
 182                if (!IS_PM34XX_ERRATUM(PM_SDRC_WAKEUP_ERRATUM_i583))
 183                        core_deepest_state = PWRDM_POWER_OFF;
 184        }
 185
 186        /* Check if current state is valid */
 187        if ((cx->mpu_state >= mpu_deepest_state) &&
 188            (cx->core_state >= core_deepest_state))
 189                return index;
 190
 191        /*
 192         * Drop to next valid state.
 193         * Start search from the next (lower) state.
 194         */
 195        for (idx = index - 1; idx >= 0; idx--) {
 196                cx = &omap3_idle_data[idx];
 197                if ((cx->mpu_state >= mpu_deepest_state) &&
 198                    (cx->core_state >= core_deepest_state)) {
 199                        next_index = idx;
 200                        break;
 201                }
 202        }
 203
 204        return next_index;
 205}
 206
 207/**
 208 * omap3_enter_idle_bm - Checks for any bus activity
 209 * @dev: cpuidle device
 210 * @drv: cpuidle driver
 211 * @index: array index of target state to be programmed
 212 *
 213 * This function checks for any pending activity and then programs
 214 * the device to the specified or a safer state.
 215 */
 216static int omap3_enter_idle_bm(struct cpuidle_device *dev,
 217                               struct cpuidle_driver *drv,
 218                               int index)
 219{
 220        int new_state_idx, ret;
 221        u8 per_next_state, per_saved_state;
 222        struct omap3_idle_statedata *cx;
 223
 224        /*
 225         * Use only C1 if CAM is active.
 226         * CAM does not have wakeup capability in OMAP3.
 227         */
 228        if (pwrdm_read_pwrst(cam_pd) == PWRDM_POWER_ON)
 229                new_state_idx = drv->safe_state_index;
 230        else
 231                new_state_idx = next_valid_state(dev, drv, index);
 232
 233        /*
 234         * FIXME: we currently manage device-specific idle states
 235         *        for PER and CORE in combination with CPU-specific
 236         *        idle states.  This is wrong, and device-specific
 237         *        idle management needs to be separated out into
 238         *        its own code.
 239         */
 240
 241        /* Program PER state */
 242        cx = &omap3_idle_data[new_state_idx];
 243
 244        per_next_state = pwrdm_read_next_pwrst(per_pd);
 245        per_saved_state = per_next_state;
 246        if (per_next_state < cx->per_min_state) {
 247                per_next_state = cx->per_min_state;
 248                pwrdm_set_next_pwrst(per_pd, per_next_state);
 249        }
 250
 251        ret = omap3_enter_idle(dev, drv, new_state_idx);
 252
 253        /* Restore original PER state if it was modified */
 254        if (per_next_state != per_saved_state)
 255                pwrdm_set_next_pwrst(per_pd, per_saved_state);
 256
 257        return ret;
 258}
 259
 260static struct cpuidle_driver omap3_idle_driver = {
 261        .name             = "omap3_idle",
 262        .owner            = THIS_MODULE,
 263        .states = {
 264                {
 265                        .enter            = omap3_enter_idle_bm,
 266                        .exit_latency     = 2 + 2,
 267                        .target_residency = 5,
 268                        .flags            = CPUIDLE_FLAG_TIME_VALID,
 269                        .name             = "C1",
 270                        .desc             = "MPU ON + CORE ON",
 271                },
 272                {
 273                        .enter            = omap3_enter_idle_bm,
 274                        .exit_latency     = 10 + 10,
 275                        .target_residency = 30,
 276                        .flags            = CPUIDLE_FLAG_TIME_VALID,
 277                        .name             = "C2",
 278                        .desc             = "MPU ON + CORE ON",
 279                },
 280                {
 281                        .enter            = omap3_enter_idle_bm,
 282                        .exit_latency     = 50 + 50,
 283                        .target_residency = 300,
 284                        .flags            = CPUIDLE_FLAG_TIME_VALID,
 285                        .name             = "C3",
 286                        .desc             = "MPU RET + CORE ON",
 287                },
 288                {
 289                        .enter            = omap3_enter_idle_bm,
 290                        .exit_latency     = 1500 + 1800,
 291                        .target_residency = 4000,
 292                        .flags            = CPUIDLE_FLAG_TIME_VALID,
 293                        .name             = "C4",
 294                        .desc             = "MPU OFF + CORE ON",
 295                },
 296                {
 297                        .enter            = omap3_enter_idle_bm,
 298                        .exit_latency     = 2500 + 7500,
 299                        .target_residency = 12000,
 300                        .flags            = CPUIDLE_FLAG_TIME_VALID,
 301                        .name             = "C5",
 302                        .desc             = "MPU RET + CORE RET",
 303                },
 304                {
 305                        .enter            = omap3_enter_idle_bm,
 306                        .exit_latency     = 3000 + 8500,
 307                        .target_residency = 15000,
 308                        .flags            = CPUIDLE_FLAG_TIME_VALID,
 309                        .name             = "C6",
 310                        .desc             = "MPU OFF + CORE RET",
 311                },
 312                {
 313                        .enter            = omap3_enter_idle_bm,
 314                        .exit_latency     = 10000 + 30000,
 315                        .target_residency = 30000,
 316                        .flags            = CPUIDLE_FLAG_TIME_VALID,
 317                        .name             = "C7",
 318                        .desc             = "MPU OFF + CORE OFF",
 319                },
 320        },
 321        .state_count = ARRAY_SIZE(omap3_idle_data),
 322        .safe_state_index = 0,
 323};
 324
 325/* Public functions */
 326
 327/**
 328 * omap3_idle_init - Init routine for OMAP3 idle
 329 *
 330 * Registers the OMAP3 specific cpuidle driver to the cpuidle
 331 * framework with the valid set of states.
 332 */
 333int __init omap3_idle_init(void)
 334{
 335        mpu_pd = pwrdm_lookup("mpu_pwrdm");
 336        core_pd = pwrdm_lookup("core_pwrdm");
 337        per_pd = pwrdm_lookup("per_pwrdm");
 338        cam_pd = pwrdm_lookup("cam_pwrdm");
 339
 340        if (!mpu_pd || !core_pd || !per_pd || !cam_pd)
 341                return -ENODEV;
 342
 343        return cpuidle_register(&omap3_idle_driver, NULL);
 344}
 345