18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * OMAP4+ CPU idle Routines 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2011-2013 Texas Instruments, Inc. 68c2ecf20Sopenharmony_ci * Santosh Shilimkar <santosh.shilimkar@ti.com> 78c2ecf20Sopenharmony_ci * Rajendra Nayak <rnayak@ti.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/sched.h> 118c2ecf20Sopenharmony_ci#include <linux/cpuidle.h> 128c2ecf20Sopenharmony_ci#include <linux/cpu_pm.h> 138c2ecf20Sopenharmony_ci#include <linux/export.h> 148c2ecf20Sopenharmony_ci#include <linux/tick.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <asm/cpuidle.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include "common.h" 198c2ecf20Sopenharmony_ci#include "pm.h" 208c2ecf20Sopenharmony_ci#include "prm.h" 218c2ecf20Sopenharmony_ci#include "soc.h" 228c2ecf20Sopenharmony_ci#include "clockdomain.h" 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define MAX_CPUS 2 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci/* Machine specific information */ 278c2ecf20Sopenharmony_cistruct idle_statedata { 288c2ecf20Sopenharmony_ci u32 cpu_state; 298c2ecf20Sopenharmony_ci u32 mpu_logic_state; 308c2ecf20Sopenharmony_ci u32 mpu_state; 318c2ecf20Sopenharmony_ci u32 mpu_state_vote; 328c2ecf20Sopenharmony_ci}; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic struct idle_statedata omap4_idle_data[] = { 358c2ecf20Sopenharmony_ci { 368c2ecf20Sopenharmony_ci .cpu_state = PWRDM_POWER_ON, 378c2ecf20Sopenharmony_ci .mpu_state = PWRDM_POWER_ON, 388c2ecf20Sopenharmony_ci .mpu_logic_state = PWRDM_POWER_RET, 398c2ecf20Sopenharmony_ci }, 408c2ecf20Sopenharmony_ci { 418c2ecf20Sopenharmony_ci .cpu_state = PWRDM_POWER_OFF, 428c2ecf20Sopenharmony_ci .mpu_state = PWRDM_POWER_RET, 438c2ecf20Sopenharmony_ci .mpu_logic_state = PWRDM_POWER_RET, 448c2ecf20Sopenharmony_ci }, 458c2ecf20Sopenharmony_ci { 468c2ecf20Sopenharmony_ci .cpu_state = PWRDM_POWER_OFF, 478c2ecf20Sopenharmony_ci .mpu_state = PWRDM_POWER_RET, 488c2ecf20Sopenharmony_ci .mpu_logic_state = PWRDM_POWER_OFF, 498c2ecf20Sopenharmony_ci }, 508c2ecf20Sopenharmony_ci}; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic struct idle_statedata omap5_idle_data[] = { 538c2ecf20Sopenharmony_ci { 548c2ecf20Sopenharmony_ci .cpu_state = PWRDM_POWER_ON, 558c2ecf20Sopenharmony_ci .mpu_state = PWRDM_POWER_ON, 568c2ecf20Sopenharmony_ci .mpu_logic_state = PWRDM_POWER_ON, 578c2ecf20Sopenharmony_ci }, 588c2ecf20Sopenharmony_ci { 598c2ecf20Sopenharmony_ci .cpu_state = PWRDM_POWER_RET, 608c2ecf20Sopenharmony_ci .mpu_state = PWRDM_POWER_RET, 618c2ecf20Sopenharmony_ci .mpu_logic_state = PWRDM_POWER_RET, 628c2ecf20Sopenharmony_ci }, 638c2ecf20Sopenharmony_ci}; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic struct powerdomain *mpu_pd, *cpu_pd[MAX_CPUS]; 668c2ecf20Sopenharmony_cistatic struct clockdomain *cpu_clkdm[MAX_CPUS]; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic atomic_t abort_barrier; 698c2ecf20Sopenharmony_cistatic bool cpu_done[MAX_CPUS]; 708c2ecf20Sopenharmony_cistatic struct idle_statedata *state_ptr = &omap4_idle_data[0]; 718c2ecf20Sopenharmony_cistatic DEFINE_RAW_SPINLOCK(mpu_lock); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci/* Private functions */ 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci/** 768c2ecf20Sopenharmony_ci * omap_enter_idle_[simple/coupled] - OMAP4PLUS cpuidle entry functions 778c2ecf20Sopenharmony_ci * @dev: cpuidle device 788c2ecf20Sopenharmony_ci * @drv: cpuidle driver 798c2ecf20Sopenharmony_ci * @index: the index of state to be entered 808c2ecf20Sopenharmony_ci * 818c2ecf20Sopenharmony_ci * Called from the CPUidle framework to program the device to the 828c2ecf20Sopenharmony_ci * specified low power state selected by the governor. 838c2ecf20Sopenharmony_ci * Returns the amount of time spent in the low power state. 848c2ecf20Sopenharmony_ci */ 858c2ecf20Sopenharmony_cistatic int omap_enter_idle_simple(struct cpuidle_device *dev, 868c2ecf20Sopenharmony_ci struct cpuidle_driver *drv, 878c2ecf20Sopenharmony_ci int index) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci omap_do_wfi(); 908c2ecf20Sopenharmony_ci return index; 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic int omap_enter_idle_smp(struct cpuidle_device *dev, 948c2ecf20Sopenharmony_ci struct cpuidle_driver *drv, 958c2ecf20Sopenharmony_ci int index) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci struct idle_statedata *cx = state_ptr + index; 988c2ecf20Sopenharmony_ci unsigned long flag; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&mpu_lock, flag); 1018c2ecf20Sopenharmony_ci cx->mpu_state_vote++; 1028c2ecf20Sopenharmony_ci if (cx->mpu_state_vote == num_online_cpus()) { 1038c2ecf20Sopenharmony_ci pwrdm_set_logic_retst(mpu_pd, cx->mpu_logic_state); 1048c2ecf20Sopenharmony_ci omap_set_pwrdm_state(mpu_pd, cx->mpu_state); 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&mpu_lock, flag); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci omap4_enter_lowpower(dev->cpu, cx->cpu_state); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&mpu_lock, flag); 1118c2ecf20Sopenharmony_ci if (cx->mpu_state_vote == num_online_cpus()) 1128c2ecf20Sopenharmony_ci omap_set_pwrdm_state(mpu_pd, PWRDM_POWER_ON); 1138c2ecf20Sopenharmony_ci cx->mpu_state_vote--; 1148c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&mpu_lock, flag); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci return index; 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic int omap_enter_idle_coupled(struct cpuidle_device *dev, 1208c2ecf20Sopenharmony_ci struct cpuidle_driver *drv, 1218c2ecf20Sopenharmony_ci int index) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct idle_statedata *cx = state_ptr + index; 1248c2ecf20Sopenharmony_ci u32 mpuss_can_lose_context = 0; 1258c2ecf20Sopenharmony_ci int error; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci /* 1288c2ecf20Sopenharmony_ci * CPU0 has to wait and stay ON until CPU1 is OFF state. 1298c2ecf20Sopenharmony_ci * This is necessary to honour hardware recommondation 1308c2ecf20Sopenharmony_ci * of triggeing all the possible low power modes once CPU1 is 1318c2ecf20Sopenharmony_ci * out of coherency and in OFF mode. 1328c2ecf20Sopenharmony_ci */ 1338c2ecf20Sopenharmony_ci if (dev->cpu == 0 && cpumask_test_cpu(1, cpu_online_mask)) { 1348c2ecf20Sopenharmony_ci while (pwrdm_read_pwrst(cpu_pd[1]) != PWRDM_POWER_OFF) { 1358c2ecf20Sopenharmony_ci cpu_relax(); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci /* 1388c2ecf20Sopenharmony_ci * CPU1 could have already entered & exited idle 1398c2ecf20Sopenharmony_ci * without hitting off because of a wakeup 1408c2ecf20Sopenharmony_ci * or a failed attempt to hit off mode. Check for 1418c2ecf20Sopenharmony_ci * that here, otherwise we could spin forever 1428c2ecf20Sopenharmony_ci * waiting for CPU1 off. 1438c2ecf20Sopenharmony_ci */ 1448c2ecf20Sopenharmony_ci if (cpu_done[1]) 1458c2ecf20Sopenharmony_ci goto fail; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci mpuss_can_lose_context = (cx->mpu_state == PWRDM_POWER_RET) && 1518c2ecf20Sopenharmony_ci (cx->mpu_logic_state == PWRDM_POWER_OFF); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci /* Enter broadcast mode for periodic timers */ 1548c2ecf20Sopenharmony_ci RCU_NONIDLE(tick_broadcast_enable()); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci /* Enter broadcast mode for one-shot timers */ 1578c2ecf20Sopenharmony_ci RCU_NONIDLE(tick_broadcast_enter()); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* 1608c2ecf20Sopenharmony_ci * Call idle CPU PM enter notifier chain so that 1618c2ecf20Sopenharmony_ci * VFP and per CPU interrupt context is saved. 1628c2ecf20Sopenharmony_ci */ 1638c2ecf20Sopenharmony_ci error = cpu_pm_enter(); 1648c2ecf20Sopenharmony_ci if (error) 1658c2ecf20Sopenharmony_ci goto cpu_pm_out; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci if (dev->cpu == 0) { 1688c2ecf20Sopenharmony_ci pwrdm_set_logic_retst(mpu_pd, cx->mpu_logic_state); 1698c2ecf20Sopenharmony_ci RCU_NONIDLE(omap_set_pwrdm_state(mpu_pd, cx->mpu_state)); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci /* 1728c2ecf20Sopenharmony_ci * Call idle CPU cluster PM enter notifier chain 1738c2ecf20Sopenharmony_ci * to save GIC and wakeupgen context. 1748c2ecf20Sopenharmony_ci */ 1758c2ecf20Sopenharmony_ci if (mpuss_can_lose_context) { 1768c2ecf20Sopenharmony_ci error = cpu_cluster_pm_enter(); 1778c2ecf20Sopenharmony_ci if (error) { 1788c2ecf20Sopenharmony_ci index = 0; 1798c2ecf20Sopenharmony_ci cx = state_ptr + index; 1808c2ecf20Sopenharmony_ci pwrdm_set_logic_retst(mpu_pd, cx->mpu_logic_state); 1818c2ecf20Sopenharmony_ci RCU_NONIDLE(omap_set_pwrdm_state(mpu_pd, cx->mpu_state)); 1828c2ecf20Sopenharmony_ci mpuss_can_lose_context = 0; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci omap4_enter_lowpower(dev->cpu, cx->cpu_state); 1888c2ecf20Sopenharmony_ci cpu_done[dev->cpu] = true; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci /* Wakeup CPU1 only if it is not offlined */ 1918c2ecf20Sopenharmony_ci if (dev->cpu == 0 && cpumask_test_cpu(1, cpu_online_mask)) { 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (IS_PM44XX_ERRATUM(PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD) && 1948c2ecf20Sopenharmony_ci mpuss_can_lose_context) 1958c2ecf20Sopenharmony_ci gic_dist_disable(); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci RCU_NONIDLE(clkdm_deny_idle(cpu_clkdm[1])); 1988c2ecf20Sopenharmony_ci RCU_NONIDLE(omap_set_pwrdm_state(cpu_pd[1], PWRDM_POWER_ON)); 1998c2ecf20Sopenharmony_ci RCU_NONIDLE(clkdm_allow_idle(cpu_clkdm[1])); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci if (IS_PM44XX_ERRATUM(PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD) && 2028c2ecf20Sopenharmony_ci mpuss_can_lose_context) { 2038c2ecf20Sopenharmony_ci while (gic_dist_disabled()) { 2048c2ecf20Sopenharmony_ci udelay(1); 2058c2ecf20Sopenharmony_ci cpu_relax(); 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci gic_timer_retrigger(); 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci /* 2128c2ecf20Sopenharmony_ci * Call idle CPU cluster PM exit notifier chain 2138c2ecf20Sopenharmony_ci * to restore GIC and wakeupgen context. 2148c2ecf20Sopenharmony_ci */ 2158c2ecf20Sopenharmony_ci if (dev->cpu == 0 && mpuss_can_lose_context) 2168c2ecf20Sopenharmony_ci cpu_cluster_pm_exit(); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci /* 2198c2ecf20Sopenharmony_ci * Call idle CPU PM exit notifier chain to restore 2208c2ecf20Sopenharmony_ci * VFP and per CPU IRQ context. 2218c2ecf20Sopenharmony_ci */ 2228c2ecf20Sopenharmony_ci cpu_pm_exit(); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_cicpu_pm_out: 2258c2ecf20Sopenharmony_ci RCU_NONIDLE(tick_broadcast_exit()); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cifail: 2288c2ecf20Sopenharmony_ci cpuidle_coupled_parallel_barrier(dev, &abort_barrier); 2298c2ecf20Sopenharmony_ci cpu_done[dev->cpu] = false; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci return index; 2328c2ecf20Sopenharmony_ci} 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cistatic struct cpuidle_driver omap4_idle_driver = { 2358c2ecf20Sopenharmony_ci .name = "omap4_idle", 2368c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2378c2ecf20Sopenharmony_ci .states = { 2388c2ecf20Sopenharmony_ci { 2398c2ecf20Sopenharmony_ci /* C1 - CPU0 ON + CPU1 ON + MPU ON */ 2408c2ecf20Sopenharmony_ci .exit_latency = 2 + 2, 2418c2ecf20Sopenharmony_ci .target_residency = 5, 2428c2ecf20Sopenharmony_ci .enter = omap_enter_idle_simple, 2438c2ecf20Sopenharmony_ci .name = "C1", 2448c2ecf20Sopenharmony_ci .desc = "CPUx ON, MPUSS ON" 2458c2ecf20Sopenharmony_ci }, 2468c2ecf20Sopenharmony_ci { 2478c2ecf20Sopenharmony_ci /* C2 - CPU0 OFF + CPU1 OFF + MPU CSWR */ 2488c2ecf20Sopenharmony_ci .exit_latency = 328 + 440, 2498c2ecf20Sopenharmony_ci .target_residency = 960, 2508c2ecf20Sopenharmony_ci .flags = CPUIDLE_FLAG_COUPLED, 2518c2ecf20Sopenharmony_ci .enter = omap_enter_idle_coupled, 2528c2ecf20Sopenharmony_ci .name = "C2", 2538c2ecf20Sopenharmony_ci .desc = "CPUx OFF, MPUSS CSWR", 2548c2ecf20Sopenharmony_ci }, 2558c2ecf20Sopenharmony_ci { 2568c2ecf20Sopenharmony_ci /* C3 - CPU0 OFF + CPU1 OFF + MPU OSWR */ 2578c2ecf20Sopenharmony_ci .exit_latency = 460 + 518, 2588c2ecf20Sopenharmony_ci .target_residency = 1100, 2598c2ecf20Sopenharmony_ci .flags = CPUIDLE_FLAG_COUPLED, 2608c2ecf20Sopenharmony_ci .enter = omap_enter_idle_coupled, 2618c2ecf20Sopenharmony_ci .name = "C3", 2628c2ecf20Sopenharmony_ci .desc = "CPUx OFF, MPUSS OSWR", 2638c2ecf20Sopenharmony_ci }, 2648c2ecf20Sopenharmony_ci }, 2658c2ecf20Sopenharmony_ci .state_count = ARRAY_SIZE(omap4_idle_data), 2668c2ecf20Sopenharmony_ci .safe_state_index = 0, 2678c2ecf20Sopenharmony_ci}; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic struct cpuidle_driver omap5_idle_driver = { 2708c2ecf20Sopenharmony_ci .name = "omap5_idle", 2718c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2728c2ecf20Sopenharmony_ci .states = { 2738c2ecf20Sopenharmony_ci { 2748c2ecf20Sopenharmony_ci /* C1 - CPU0 ON + CPU1 ON + MPU ON */ 2758c2ecf20Sopenharmony_ci .exit_latency = 2 + 2, 2768c2ecf20Sopenharmony_ci .target_residency = 5, 2778c2ecf20Sopenharmony_ci .enter = omap_enter_idle_simple, 2788c2ecf20Sopenharmony_ci .name = "C1", 2798c2ecf20Sopenharmony_ci .desc = "CPUx WFI, MPUSS ON" 2808c2ecf20Sopenharmony_ci }, 2818c2ecf20Sopenharmony_ci { 2828c2ecf20Sopenharmony_ci /* C2 - CPU0 RET + CPU1 RET + MPU CSWR */ 2838c2ecf20Sopenharmony_ci .exit_latency = 48 + 60, 2848c2ecf20Sopenharmony_ci .target_residency = 100, 2858c2ecf20Sopenharmony_ci .flags = CPUIDLE_FLAG_TIMER_STOP, 2868c2ecf20Sopenharmony_ci .enter = omap_enter_idle_smp, 2878c2ecf20Sopenharmony_ci .name = "C2", 2888c2ecf20Sopenharmony_ci .desc = "CPUx CSWR, MPUSS CSWR", 2898c2ecf20Sopenharmony_ci }, 2908c2ecf20Sopenharmony_ci }, 2918c2ecf20Sopenharmony_ci .state_count = ARRAY_SIZE(omap5_idle_data), 2928c2ecf20Sopenharmony_ci .safe_state_index = 0, 2938c2ecf20Sopenharmony_ci}; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci/* Public functions */ 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci/** 2988c2ecf20Sopenharmony_ci * omap4_idle_init - Init routine for OMAP4+ idle 2998c2ecf20Sopenharmony_ci * 3008c2ecf20Sopenharmony_ci * Registers the OMAP4+ specific cpuidle driver to the cpuidle 3018c2ecf20Sopenharmony_ci * framework with the valid set of states. 3028c2ecf20Sopenharmony_ci */ 3038c2ecf20Sopenharmony_ciint __init omap4_idle_init(void) 3048c2ecf20Sopenharmony_ci{ 3058c2ecf20Sopenharmony_ci struct cpuidle_driver *idle_driver; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci if (soc_is_omap54xx()) { 3088c2ecf20Sopenharmony_ci state_ptr = &omap5_idle_data[0]; 3098c2ecf20Sopenharmony_ci idle_driver = &omap5_idle_driver; 3108c2ecf20Sopenharmony_ci } else { 3118c2ecf20Sopenharmony_ci state_ptr = &omap4_idle_data[0]; 3128c2ecf20Sopenharmony_ci idle_driver = &omap4_idle_driver; 3138c2ecf20Sopenharmony_ci } 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci mpu_pd = pwrdm_lookup("mpu_pwrdm"); 3168c2ecf20Sopenharmony_ci cpu_pd[0] = pwrdm_lookup("cpu0_pwrdm"); 3178c2ecf20Sopenharmony_ci cpu_pd[1] = pwrdm_lookup("cpu1_pwrdm"); 3188c2ecf20Sopenharmony_ci if ((!mpu_pd) || (!cpu_pd[0]) || (!cpu_pd[1])) 3198c2ecf20Sopenharmony_ci return -ENODEV; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci cpu_clkdm[0] = clkdm_lookup("mpu0_clkdm"); 3228c2ecf20Sopenharmony_ci cpu_clkdm[1] = clkdm_lookup("mpu1_clkdm"); 3238c2ecf20Sopenharmony_ci if (!cpu_clkdm[0] || !cpu_clkdm[1]) 3248c2ecf20Sopenharmony_ci return -ENODEV; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci return cpuidle_register(idle_driver, cpu_online_mask); 3278c2ecf20Sopenharmony_ci} 328