From 849369d6c66d3054688672f97d31fceb8e8230fb Mon Sep 17 00:00:00 2001 From: root Date: Fri, 25 Dec 2015 04:40:36 +0000 Subject: initial_commit --- arch/arm/mach-omap2/cpuidle34xx.c | 405 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 arch/arm/mach-omap2/cpuidle34xx.c (limited to 'arch/arm/mach-omap2/cpuidle34xx.c') diff --git a/arch/arm/mach-omap2/cpuidle34xx.c b/arch/arm/mach-omap2/cpuidle34xx.c new file mode 100644 index 00000000..4bf6e6e8 --- /dev/null +++ b/arch/arm/mach-omap2/cpuidle34xx.c @@ -0,0 +1,405 @@ +/* + * linux/arch/arm/mach-omap2/cpuidle34xx.c + * + * OMAP3 CPU IDLE Routines + * + * Copyright (C) 2008 Texas Instruments, Inc. + * Rajendra Nayak + * + * Copyright (C) 2007 Texas Instruments, Inc. + * Karthik Dasu + * + * Copyright (C) 2006 Nokia Corporation + * Tony Lindgren + * + * Copyright (C) 2005 Texas Instruments, Inc. + * Richard Woodruff + * + * Based on pm.c for omap2 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include + +#include +#include +#include "powerdomain.h" +#include "clockdomain.h" +#include + +#include "pm.h" +#include "control.h" + +#ifdef CONFIG_CPU_IDLE + +/* + * The latencies/thresholds for various C states have + * to be configured from the respective board files. + * These are some default values (which might not provide + * the best power savings) used on boards which do not + * pass these details from the board file. + */ +static struct cpuidle_params cpuidle_params_table[] = { + /* C1 */ + {2 + 2, 5, 1}, + /* C2 */ + {10 + 10, 30, 1}, + /* C3 */ + {50 + 50, 300, 1}, + /* C4 */ + {1500 + 1800, 4000, 1}, + /* C5 */ + {2500 + 7500, 12000, 1}, + /* C6 */ + {3000 + 8500, 15000, 1}, + /* C7 */ + {10000 + 30000, 300000, 1}, +}; +#define OMAP3_NUM_STATES ARRAY_SIZE(cpuidle_params_table) + +/* Mach specific information to be recorded in the C-state driver_data */ +struct omap3_idle_statedata { + u32 mpu_state; + u32 core_state; + u8 valid; +}; +struct omap3_idle_statedata omap3_idle_data[OMAP3_NUM_STATES]; + +struct powerdomain *mpu_pd, *core_pd, *per_pd, *cam_pd; + +static int _cpuidle_allow_idle(struct powerdomain *pwrdm, + struct clockdomain *clkdm) +{ + clkdm_allow_idle(clkdm); + return 0; +} + +static int _cpuidle_deny_idle(struct powerdomain *pwrdm, + struct clockdomain *clkdm) +{ + clkdm_deny_idle(clkdm); + return 0; +} + +/** + * omap3_enter_idle - Programs OMAP3 to enter the specified state + * @dev: cpuidle device + * @state: The target state to be programmed + * + * Called from the CPUidle framework to program the device to the + * specified target state selected by the governor. + */ +static int omap3_enter_idle(struct cpuidle_device *dev, + struct cpuidle_state *state) +{ + struct omap3_idle_statedata *cx = cpuidle_get_statedata(state); + struct timespec ts_preidle, ts_postidle, ts_idle; + u32 mpu_state = cx->mpu_state, core_state = cx->core_state; + + /* Used to keep track of the total time in idle */ + getnstimeofday(&ts_preidle); + + local_irq_disable(); + local_fiq_disable(); + + pwrdm_set_next_pwrst(mpu_pd, mpu_state); + pwrdm_set_next_pwrst(core_pd, core_state); + + if (omap_irq_pending() || need_resched()) + goto return_sleep_time; + + /* Deny idle for C1 */ + if (state == &dev->states[0]) { + pwrdm_for_each_clkdm(mpu_pd, _cpuidle_deny_idle); + pwrdm_for_each_clkdm(core_pd, _cpuidle_deny_idle); + } + + /* Execute ARM wfi */ + omap_sram_idle(); + + /* Re-allow idle for C1 */ + if (state == &dev->states[0]) { + pwrdm_for_each_clkdm(mpu_pd, _cpuidle_allow_idle); + pwrdm_for_each_clkdm(core_pd, _cpuidle_allow_idle); + } + +return_sleep_time: + getnstimeofday(&ts_postidle); + ts_idle = timespec_sub(ts_postidle, ts_preidle); + + local_irq_enable(); + local_fiq_enable(); + + return ts_idle.tv_nsec / NSEC_PER_USEC + ts_idle.tv_sec * USEC_PER_SEC; +} + +/** + * next_valid_state - Find next valid C-state + * @dev: cpuidle device + * @state: Currently selected C-state + * + * If the current state is valid, it is returned back to the caller. + * Else, this function searches for a lower c-state which is still + * valid. + * + * A state is valid if the 'valid' field is enabled and + * if it satisfies the enable_off_mode condition. + */ +static struct cpuidle_state *next_valid_state(struct cpuidle_device *dev, + struct cpuidle_state *curr) +{ + struct cpuidle_state *next = NULL; + struct omap3_idle_statedata *cx = cpuidle_get_statedata(curr); + u32 mpu_deepest_state = PWRDM_POWER_RET; + u32 core_deepest_state = PWRDM_POWER_RET; + + if (enable_off_mode) { + mpu_deepest_state = PWRDM_POWER_OFF; + /* + * Erratum i583: valable for ES rev < Es1.2 on 3630. + * CORE OFF mode is not supported in a stable form, restrict + * instead the CORE state to RET. + */ + if (!IS_PM34XX_ERRATUM(PM_SDRC_WAKEUP_ERRATUM_i583)) + core_deepest_state = PWRDM_POWER_OFF; + } + + /* Check if current state is valid */ + if ((cx->valid) && + (cx->mpu_state >= mpu_deepest_state) && + (cx->core_state >= core_deepest_state)) { + return curr; + } else { + int idx = OMAP3_NUM_STATES - 1; + + /* Reach the current state starting at highest C-state */ + for (; idx >= 0; idx--) { + if (&dev->states[idx] == curr) { + next = &dev->states[idx]; + break; + } + } + + /* Should never hit this condition */ + WARN_ON(next == NULL); + + /* + * Drop to next valid state. + * Start search from the next (lower) state. + */ + idx--; + for (; idx >= 0; idx--) { + cx = cpuidle_get_statedata(&dev->states[idx]); + if ((cx->valid) && + (cx->mpu_state >= mpu_deepest_state) && + (cx->core_state >= core_deepest_state)) { + next = &dev->states[idx]; + break; + } + } + /* + * C1 is always valid. + * So, no need to check for 'next==NULL' outside this loop. + */ + } + + return next; +} + +/** + * omap3_enter_idle_bm - Checks for any bus activity + * @dev: cpuidle device + * @state: The target state to be programmed + * + * This function checks for any pending activity and then programs + * the device to the specified or a safer state. + */ +static int omap3_enter_idle_bm(struct cpuidle_device *dev, + struct cpuidle_state *state) +{ + struct cpuidle_state *new_state; + u32 core_next_state, per_next_state = 0, per_saved_state = 0, cam_state; + struct omap3_idle_statedata *cx; + int ret; + + if (!omap3_can_sleep()) { + new_state = dev->safe_state; + goto select_state; + } + + /* + * Prevent idle completely if CAM is active. + * CAM does not have wakeup capability in OMAP3. + */ + cam_state = pwrdm_read_pwrst(cam_pd); + if (cam_state == PWRDM_POWER_ON) { + new_state = dev->safe_state; + goto select_state; + } + + /* + * FIXME: we currently manage device-specific idle states + * for PER and CORE in combination with CPU-specific + * idle states. This is wrong, and device-specific + * idle management needs to be separated out into + * its own code. + */ + + /* + * Prevent PER off if CORE is not in retention or off as this + * would disable PER wakeups completely. + */ + cx = cpuidle_get_statedata(state); + core_next_state = cx->core_state; + per_next_state = per_saved_state = pwrdm_read_next_pwrst(per_pd); + if ((per_next_state == PWRDM_POWER_OFF) && + (core_next_state > PWRDM_POWER_RET)) + per_next_state = PWRDM_POWER_RET; + + /* Are we changing PER target state? */ + if (per_next_state != per_saved_state) + pwrdm_set_next_pwrst(per_pd, per_next_state); + + new_state = next_valid_state(dev, state); + +select_state: + dev->last_state = new_state; + ret = omap3_enter_idle(dev, new_state); + + /* Restore original PER state if it was modified */ + if (per_next_state != per_saved_state) + pwrdm_set_next_pwrst(per_pd, per_saved_state); + + return ret; +} + +DEFINE_PER_CPU(struct cpuidle_device, omap3_idle_dev); + +void omap3_pm_init_cpuidle(struct cpuidle_params *cpuidle_board_params) +{ + int i; + + if (!cpuidle_board_params) + return; + + for (i = 0; i < OMAP3_NUM_STATES; i++) { + cpuidle_params_table[i].valid = cpuidle_board_params[i].valid; + cpuidle_params_table[i].exit_latency = + cpuidle_board_params[i].exit_latency; + cpuidle_params_table[i].target_residency = + cpuidle_board_params[i].target_residency; + } + return; +} + +struct cpuidle_driver omap3_idle_driver = { + .name = "omap3_idle", + .owner = THIS_MODULE, +}; + +/* Helper to fill the C-state common data and register the driver_data */ +static inline struct omap3_idle_statedata *_fill_cstate( + struct cpuidle_device *dev, + int idx, const char *descr) +{ + struct omap3_idle_statedata *cx = &omap3_idle_data[idx]; + struct cpuidle_state *state = &dev->states[idx]; + + state->exit_latency = cpuidle_params_table[idx].exit_latency; + state->target_residency = cpuidle_params_table[idx].target_residency; + state->flags = CPUIDLE_FLAG_TIME_VALID; + state->enter = omap3_enter_idle_bm; + cx->valid = cpuidle_params_table[idx].valid; + sprintf(state->name, "C%d", idx + 1); + strncpy(state->desc, descr, CPUIDLE_DESC_LEN); + cpuidle_set_statedata(state, cx); + + return cx; +} + +/** + * omap3_idle_init - Init routine for OMAP3 idle + * + * Registers the OMAP3 specific cpuidle driver to the cpuidle + * framework with the valid set of states. + */ +int __init omap3_idle_init(void) +{ + struct cpuidle_device *dev; + struct omap3_idle_statedata *cx; + + mpu_pd = pwrdm_lookup("mpu_pwrdm"); + core_pd = pwrdm_lookup("core_pwrdm"); + per_pd = pwrdm_lookup("per_pwrdm"); + cam_pd = pwrdm_lookup("cam_pwrdm"); + + cpuidle_register_driver(&omap3_idle_driver); + dev = &per_cpu(omap3_idle_dev, smp_processor_id()); + + /* C1 . MPU WFI + Core active */ + cx = _fill_cstate(dev, 0, "MPU ON + CORE ON"); + (&dev->states[0])->enter = omap3_enter_idle; + dev->safe_state = &dev->states[0]; + cx->valid = 1; /* C1 is always valid */ + cx->mpu_state = PWRDM_POWER_ON; + cx->core_state = PWRDM_POWER_ON; + + /* C2 . MPU WFI + Core inactive */ + cx = _fill_cstate(dev, 1, "MPU ON + CORE ON"); + cx->mpu_state = PWRDM_POWER_ON; + cx->core_state = PWRDM_POWER_ON; + + /* C3 . MPU CSWR + Core inactive */ + cx = _fill_cstate(dev, 2, "MPU RET + CORE ON"); + cx->mpu_state = PWRDM_POWER_RET; + cx->core_state = PWRDM_POWER_ON; + + /* C4 . MPU OFF + Core inactive */ + cx = _fill_cstate(dev, 3, "MPU OFF + CORE ON"); + cx->mpu_state = PWRDM_POWER_OFF; + cx->core_state = PWRDM_POWER_ON; + + /* C5 . MPU RET + Core RET */ + cx = _fill_cstate(dev, 4, "MPU RET + CORE RET"); + cx->mpu_state = PWRDM_POWER_RET; + cx->core_state = PWRDM_POWER_RET; + + /* C6 . MPU OFF + Core RET */ + cx = _fill_cstate(dev, 5, "MPU OFF + CORE RET"); + cx->mpu_state = PWRDM_POWER_OFF; + cx->core_state = PWRDM_POWER_RET; + + /* C7 . MPU OFF + Core OFF */ + cx = _fill_cstate(dev, 6, "MPU OFF + CORE OFF"); + /* + * Erratum i583: implementation for ES rev < Es1.2 on 3630. We cannot + * enable OFF mode in a stable form for previous revisions. + * We disable C7 state as a result. + */ + if (IS_PM34XX_ERRATUM(PM_SDRC_WAKEUP_ERRATUM_i583)) { + cx->valid = 0; + pr_warn("%s: core off state C7 disabled due to i583\n", + __func__); + } + cx->mpu_state = PWRDM_POWER_OFF; + cx->core_state = PWRDM_POWER_OFF; + + dev->state_count = OMAP3_NUM_STATES; + if (cpuidle_register_device(dev)) { + printk(KERN_ERR "%s: CPUidle register device failed\n", + __func__); + return -EIO; + } + + return 0; +} +#else +int __init omap3_idle_init(void) +{ + return 0; +} +#endif /* CONFIG_CPU_IDLE */ -- cgit v1.2.3