18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2014 Imagination Technologies 48c2ecf20Sopenharmony_ci * Author: Paul Burton <paul.burton@mips.com> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/cpu_pm.h> 88c2ecf20Sopenharmony_ci#include <linux/cpuidle.h> 98c2ecf20Sopenharmony_ci#include <linux/init.h> 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <asm/idle.h> 128c2ecf20Sopenharmony_ci#include <asm/pm-cps.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci/* Enumeration of the various idle states this driver may enter */ 158c2ecf20Sopenharmony_cienum cps_idle_state { 168c2ecf20Sopenharmony_ci STATE_WAIT = 0, /* MIPS wait instruction, coherent */ 178c2ecf20Sopenharmony_ci STATE_NC_WAIT, /* MIPS wait instruction, non-coherent */ 188c2ecf20Sopenharmony_ci STATE_CLOCK_GATED, /* Core clock gated */ 198c2ecf20Sopenharmony_ci STATE_POWER_GATED, /* Core power gated */ 208c2ecf20Sopenharmony_ci STATE_COUNT 218c2ecf20Sopenharmony_ci}; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic int cps_nc_enter(struct cpuidle_device *dev, 248c2ecf20Sopenharmony_ci struct cpuidle_driver *drv, int index) 258c2ecf20Sopenharmony_ci{ 268c2ecf20Sopenharmony_ci enum cps_pm_state pm_state; 278c2ecf20Sopenharmony_ci int err; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci /* 308c2ecf20Sopenharmony_ci * At least one core must remain powered up & clocked in order for the 318c2ecf20Sopenharmony_ci * system to have any hope of functioning. 328c2ecf20Sopenharmony_ci * 338c2ecf20Sopenharmony_ci * TODO: don't treat core 0 specially, just prevent the final core 348c2ecf20Sopenharmony_ci * TODO: remap interrupt affinity temporarily 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_ci if (cpus_are_siblings(0, dev->cpu) && (index > STATE_NC_WAIT)) 378c2ecf20Sopenharmony_ci index = STATE_NC_WAIT; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci /* Select the appropriate cps_pm_state */ 408c2ecf20Sopenharmony_ci switch (index) { 418c2ecf20Sopenharmony_ci case STATE_NC_WAIT: 428c2ecf20Sopenharmony_ci pm_state = CPS_PM_NC_WAIT; 438c2ecf20Sopenharmony_ci break; 448c2ecf20Sopenharmony_ci case STATE_CLOCK_GATED: 458c2ecf20Sopenharmony_ci pm_state = CPS_PM_CLOCK_GATED; 468c2ecf20Sopenharmony_ci break; 478c2ecf20Sopenharmony_ci case STATE_POWER_GATED: 488c2ecf20Sopenharmony_ci pm_state = CPS_PM_POWER_GATED; 498c2ecf20Sopenharmony_ci break; 508c2ecf20Sopenharmony_ci default: 518c2ecf20Sopenharmony_ci BUG(); 528c2ecf20Sopenharmony_ci return -EINVAL; 538c2ecf20Sopenharmony_ci } 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci /* Notify listeners the CPU is about to power down */ 568c2ecf20Sopenharmony_ci if ((pm_state == CPS_PM_POWER_GATED) && cpu_pm_enter()) 578c2ecf20Sopenharmony_ci return -EINTR; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci /* Enter that state */ 608c2ecf20Sopenharmony_ci err = cps_pm_enter_state(pm_state); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* Notify listeners the CPU is back up */ 638c2ecf20Sopenharmony_ci if (pm_state == CPS_PM_POWER_GATED) 648c2ecf20Sopenharmony_ci cpu_pm_exit(); 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci return err ?: index; 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic struct cpuidle_driver cps_driver = { 708c2ecf20Sopenharmony_ci .name = "cpc_cpuidle", 718c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 728c2ecf20Sopenharmony_ci .states = { 738c2ecf20Sopenharmony_ci [STATE_WAIT] = MIPS_CPUIDLE_WAIT_STATE, 748c2ecf20Sopenharmony_ci [STATE_NC_WAIT] = { 758c2ecf20Sopenharmony_ci .enter = cps_nc_enter, 768c2ecf20Sopenharmony_ci .exit_latency = 200, 778c2ecf20Sopenharmony_ci .target_residency = 450, 788c2ecf20Sopenharmony_ci .name = "nc-wait", 798c2ecf20Sopenharmony_ci .desc = "non-coherent MIPS wait", 808c2ecf20Sopenharmony_ci }, 818c2ecf20Sopenharmony_ci [STATE_CLOCK_GATED] = { 828c2ecf20Sopenharmony_ci .enter = cps_nc_enter, 838c2ecf20Sopenharmony_ci .exit_latency = 300, 848c2ecf20Sopenharmony_ci .target_residency = 700, 858c2ecf20Sopenharmony_ci .flags = CPUIDLE_FLAG_TIMER_STOP, 868c2ecf20Sopenharmony_ci .name = "clock-gated", 878c2ecf20Sopenharmony_ci .desc = "core clock gated", 888c2ecf20Sopenharmony_ci }, 898c2ecf20Sopenharmony_ci [STATE_POWER_GATED] = { 908c2ecf20Sopenharmony_ci .enter = cps_nc_enter, 918c2ecf20Sopenharmony_ci .exit_latency = 600, 928c2ecf20Sopenharmony_ci .target_residency = 1000, 938c2ecf20Sopenharmony_ci .flags = CPUIDLE_FLAG_TIMER_STOP, 948c2ecf20Sopenharmony_ci .name = "power-gated", 958c2ecf20Sopenharmony_ci .desc = "core power gated", 968c2ecf20Sopenharmony_ci }, 978c2ecf20Sopenharmony_ci }, 988c2ecf20Sopenharmony_ci .state_count = STATE_COUNT, 998c2ecf20Sopenharmony_ci .safe_state_index = 0, 1008c2ecf20Sopenharmony_ci}; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic void __init cps_cpuidle_unregister(void) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci int cpu; 1058c2ecf20Sopenharmony_ci struct cpuidle_device *device; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci for_each_possible_cpu(cpu) { 1088c2ecf20Sopenharmony_ci device = &per_cpu(cpuidle_dev, cpu); 1098c2ecf20Sopenharmony_ci cpuidle_unregister_device(device); 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci cpuidle_unregister_driver(&cps_driver); 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic int __init cps_cpuidle_init(void) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci int err, cpu, i; 1188c2ecf20Sopenharmony_ci struct cpuidle_device *device; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* Detect supported states */ 1218c2ecf20Sopenharmony_ci if (!cps_pm_support_state(CPS_PM_POWER_GATED)) 1228c2ecf20Sopenharmony_ci cps_driver.state_count = STATE_CLOCK_GATED + 1; 1238c2ecf20Sopenharmony_ci if (!cps_pm_support_state(CPS_PM_CLOCK_GATED)) 1248c2ecf20Sopenharmony_ci cps_driver.state_count = STATE_NC_WAIT + 1; 1258c2ecf20Sopenharmony_ci if (!cps_pm_support_state(CPS_PM_NC_WAIT)) 1268c2ecf20Sopenharmony_ci cps_driver.state_count = STATE_WAIT + 1; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci /* Inform the user if some states are unavailable */ 1298c2ecf20Sopenharmony_ci if (cps_driver.state_count < STATE_COUNT) { 1308c2ecf20Sopenharmony_ci pr_info("cpuidle-cps: limited to "); 1318c2ecf20Sopenharmony_ci switch (cps_driver.state_count - 1) { 1328c2ecf20Sopenharmony_ci case STATE_WAIT: 1338c2ecf20Sopenharmony_ci pr_cont("coherent wait\n"); 1348c2ecf20Sopenharmony_ci break; 1358c2ecf20Sopenharmony_ci case STATE_NC_WAIT: 1368c2ecf20Sopenharmony_ci pr_cont("non-coherent wait\n"); 1378c2ecf20Sopenharmony_ci break; 1388c2ecf20Sopenharmony_ci case STATE_CLOCK_GATED: 1398c2ecf20Sopenharmony_ci pr_cont("clock gating\n"); 1408c2ecf20Sopenharmony_ci break; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci /* 1458c2ecf20Sopenharmony_ci * Set the coupled flag on the appropriate states if this system 1468c2ecf20Sopenharmony_ci * requires it. 1478c2ecf20Sopenharmony_ci */ 1488c2ecf20Sopenharmony_ci if (coupled_coherence) 1498c2ecf20Sopenharmony_ci for (i = STATE_NC_WAIT; i < cps_driver.state_count; i++) 1508c2ecf20Sopenharmony_ci cps_driver.states[i].flags |= CPUIDLE_FLAG_COUPLED; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci err = cpuidle_register_driver(&cps_driver); 1538c2ecf20Sopenharmony_ci if (err) { 1548c2ecf20Sopenharmony_ci pr_err("Failed to register CPS cpuidle driver\n"); 1558c2ecf20Sopenharmony_ci return err; 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci for_each_possible_cpu(cpu) { 1598c2ecf20Sopenharmony_ci device = &per_cpu(cpuidle_dev, cpu); 1608c2ecf20Sopenharmony_ci device->cpu = cpu; 1618c2ecf20Sopenharmony_ci#ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED 1628c2ecf20Sopenharmony_ci cpumask_copy(&device->coupled_cpus, &cpu_sibling_map[cpu]); 1638c2ecf20Sopenharmony_ci#endif 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci err = cpuidle_register_device(device); 1668c2ecf20Sopenharmony_ci if (err) { 1678c2ecf20Sopenharmony_ci pr_err("Failed to register CPU%d cpuidle device\n", 1688c2ecf20Sopenharmony_ci cpu); 1698c2ecf20Sopenharmony_ci goto err_out; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci return 0; 1748c2ecf20Sopenharmony_cierr_out: 1758c2ecf20Sopenharmony_ci cps_cpuidle_unregister(); 1768c2ecf20Sopenharmony_ci return err; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_cidevice_initcall(cps_cpuidle_init); 179