162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2014 Freescale Semiconductor, Inc. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/cpuidle.h> 762306a36Sopenharmony_ci#include <linux/cpu_pm.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <asm/cacheflush.h> 1062306a36Sopenharmony_ci#include <asm/cpuidle.h> 1162306a36Sopenharmony_ci#include <asm/suspend.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include "common.h" 1462306a36Sopenharmony_ci#include "cpuidle.h" 1562306a36Sopenharmony_ci#include "hardware.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic int imx6sx_idle_finish(unsigned long val) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci /* 2062306a36Sopenharmony_ci * for Cortex-A7 which has an internal L2 2162306a36Sopenharmony_ci * cache, need to flush it before powering 2262306a36Sopenharmony_ci * down ARM platform, since flushing L1 cache 2362306a36Sopenharmony_ci * here again has very small overhead, compared 2462306a36Sopenharmony_ci * to adding conditional code for L2 cache type, 2562306a36Sopenharmony_ci * just call flush_cache_all() is fine. 2662306a36Sopenharmony_ci */ 2762306a36Sopenharmony_ci flush_cache_all(); 2862306a36Sopenharmony_ci cpu_do_idle(); 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci return 0; 3162306a36Sopenharmony_ci} 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic __cpuidle int imx6sx_enter_wait(struct cpuidle_device *dev, 3462306a36Sopenharmony_ci struct cpuidle_driver *drv, int index) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci imx6_set_lpm(WAIT_UNCLOCKED); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci switch (index) { 3962306a36Sopenharmony_ci case 1: 4062306a36Sopenharmony_ci cpu_do_idle(); 4162306a36Sopenharmony_ci break; 4262306a36Sopenharmony_ci case 2: 4362306a36Sopenharmony_ci imx6_enable_rbc(true); 4462306a36Sopenharmony_ci imx_gpc_set_arm_power_in_lpm(true); 4562306a36Sopenharmony_ci imx_set_cpu_jump(0, v7_cpu_resume); 4662306a36Sopenharmony_ci /* Need to notify there is a cpu pm operation. */ 4762306a36Sopenharmony_ci cpu_pm_enter(); 4862306a36Sopenharmony_ci cpu_cluster_pm_enter(); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci ct_cpuidle_enter(); 5162306a36Sopenharmony_ci cpu_suspend(0, imx6sx_idle_finish); 5262306a36Sopenharmony_ci ct_cpuidle_exit(); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci cpu_cluster_pm_exit(); 5562306a36Sopenharmony_ci cpu_pm_exit(); 5662306a36Sopenharmony_ci imx_gpc_set_arm_power_in_lpm(false); 5762306a36Sopenharmony_ci imx6_enable_rbc(false); 5862306a36Sopenharmony_ci break; 5962306a36Sopenharmony_ci default: 6062306a36Sopenharmony_ci break; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci imx6_set_lpm(WAIT_CLOCKED); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci return index; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic struct cpuidle_driver imx6sx_cpuidle_driver = { 6962306a36Sopenharmony_ci .name = "imx6sx_cpuidle", 7062306a36Sopenharmony_ci .owner = THIS_MODULE, 7162306a36Sopenharmony_ci .states = { 7262306a36Sopenharmony_ci /* WFI */ 7362306a36Sopenharmony_ci ARM_CPUIDLE_WFI_STATE, 7462306a36Sopenharmony_ci /* WAIT */ 7562306a36Sopenharmony_ci { 7662306a36Sopenharmony_ci .exit_latency = 50, 7762306a36Sopenharmony_ci .target_residency = 75, 7862306a36Sopenharmony_ci .flags = CPUIDLE_FLAG_TIMER_STOP, 7962306a36Sopenharmony_ci .enter = imx6sx_enter_wait, 8062306a36Sopenharmony_ci .name = "WAIT", 8162306a36Sopenharmony_ci .desc = "Clock off", 8262306a36Sopenharmony_ci }, 8362306a36Sopenharmony_ci /* WAIT + ARM power off */ 8462306a36Sopenharmony_ci { 8562306a36Sopenharmony_ci /* 8662306a36Sopenharmony_ci * ARM gating 31us * 5 + RBC clear 65us 8762306a36Sopenharmony_ci * and some margin for SW execution, here set it 8862306a36Sopenharmony_ci * to 300us. 8962306a36Sopenharmony_ci */ 9062306a36Sopenharmony_ci .exit_latency = 300, 9162306a36Sopenharmony_ci .target_residency = 500, 9262306a36Sopenharmony_ci .flags = CPUIDLE_FLAG_TIMER_STOP | 9362306a36Sopenharmony_ci CPUIDLE_FLAG_RCU_IDLE, 9462306a36Sopenharmony_ci .enter = imx6sx_enter_wait, 9562306a36Sopenharmony_ci .name = "LOW-POWER-IDLE", 9662306a36Sopenharmony_ci .desc = "ARM power off", 9762306a36Sopenharmony_ci }, 9862306a36Sopenharmony_ci }, 9962306a36Sopenharmony_ci .state_count = 3, 10062306a36Sopenharmony_ci .safe_state_index = 0, 10162306a36Sopenharmony_ci}; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ciint __init imx6sx_cpuidle_init(void) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci imx6_set_int_mem_clk_lpm(true); 10662306a36Sopenharmony_ci imx6_enable_rbc(false); 10762306a36Sopenharmony_ci imx_gpc_set_l2_mem_power_in_lpm(false); 10862306a36Sopenharmony_ci /* 10962306a36Sopenharmony_ci * set ARM power up/down timing to the fastest, 11062306a36Sopenharmony_ci * sw2iso and sw can be set to one 32K cycle = 31us 11162306a36Sopenharmony_ci * except for power up sw2iso which need to be 11262306a36Sopenharmony_ci * larger than LDO ramp up time. 11362306a36Sopenharmony_ci */ 11462306a36Sopenharmony_ci imx_gpc_set_arm_power_up_timing(cpu_is_imx6sx() ? 0xf : 0x2, 1); 11562306a36Sopenharmony_ci imx_gpc_set_arm_power_down_timing(1, 1); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci return cpuidle_register(&imx6sx_cpuidle_driver, NULL); 11862306a36Sopenharmony_ci} 119