18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ARM/ARM64 generic CPU idle driver. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2014 ARM Ltd. 68c2ecf20Sopenharmony_ci * Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "CPUidle arm: " fmt 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/cpu_cooling.h> 128c2ecf20Sopenharmony_ci#include <linux/cpuidle.h> 138c2ecf20Sopenharmony_ci#include <linux/cpumask.h> 148c2ecf20Sopenharmony_ci#include <linux/cpu_pm.h> 158c2ecf20Sopenharmony_ci#include <linux/kernel.h> 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/of.h> 188c2ecf20Sopenharmony_ci#include <linux/slab.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <asm/cpuidle.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#include "dt_idle_states.h" 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* 258c2ecf20Sopenharmony_ci * arm_enter_idle_state - Programs CPU to enter the specified state 268c2ecf20Sopenharmony_ci * 278c2ecf20Sopenharmony_ci * dev: cpuidle device 288c2ecf20Sopenharmony_ci * drv: cpuidle driver 298c2ecf20Sopenharmony_ci * idx: state index 308c2ecf20Sopenharmony_ci * 318c2ecf20Sopenharmony_ci * Called from the CPUidle framework to program the device to the 328c2ecf20Sopenharmony_ci * specified target state selected by the governor. 338c2ecf20Sopenharmony_ci */ 348c2ecf20Sopenharmony_cistatic int arm_enter_idle_state(struct cpuidle_device *dev, 358c2ecf20Sopenharmony_ci struct cpuidle_driver *drv, int idx) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci /* 388c2ecf20Sopenharmony_ci * Pass idle state index to arm_cpuidle_suspend which in turn 398c2ecf20Sopenharmony_ci * will call the CPU ops suspend protocol with idle index as a 408c2ecf20Sopenharmony_ci * parameter. 418c2ecf20Sopenharmony_ci */ 428c2ecf20Sopenharmony_ci return CPU_PM_CPU_IDLE_ENTER(arm_cpuidle_suspend, idx); 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic struct cpuidle_driver arm_idle_driver __initdata = { 468c2ecf20Sopenharmony_ci .name = "arm_idle", 478c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 488c2ecf20Sopenharmony_ci /* 498c2ecf20Sopenharmony_ci * State at index 0 is standby wfi and considered standard 508c2ecf20Sopenharmony_ci * on all ARM platforms. If in some platforms simple wfi 518c2ecf20Sopenharmony_ci * can't be used as "state 0", DT bindings must be implemented 528c2ecf20Sopenharmony_ci * to work around this issue and allow installing a special 538c2ecf20Sopenharmony_ci * handler for idle state index 0. 548c2ecf20Sopenharmony_ci */ 558c2ecf20Sopenharmony_ci .states[0] = { 568c2ecf20Sopenharmony_ci .enter = arm_enter_idle_state, 578c2ecf20Sopenharmony_ci .exit_latency = 1, 588c2ecf20Sopenharmony_ci .target_residency = 1, 598c2ecf20Sopenharmony_ci .power_usage = UINT_MAX, 608c2ecf20Sopenharmony_ci .name = "WFI", 618c2ecf20Sopenharmony_ci .desc = "ARM WFI", 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci}; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic const struct of_device_id arm_idle_state_match[] __initconst = { 668c2ecf20Sopenharmony_ci { .compatible = "arm,idle-state", 678c2ecf20Sopenharmony_ci .data = arm_enter_idle_state }, 688c2ecf20Sopenharmony_ci { }, 698c2ecf20Sopenharmony_ci}; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci/* 728c2ecf20Sopenharmony_ci * arm_idle_init_cpu 738c2ecf20Sopenharmony_ci * 748c2ecf20Sopenharmony_ci * Registers the arm specific cpuidle driver with the cpuidle 758c2ecf20Sopenharmony_ci * framework. It relies on core code to parse the idle states 768c2ecf20Sopenharmony_ci * and initialize them using driver data structures accordingly. 778c2ecf20Sopenharmony_ci */ 788c2ecf20Sopenharmony_cistatic int __init arm_idle_init_cpu(int cpu) 798c2ecf20Sopenharmony_ci{ 808c2ecf20Sopenharmony_ci int ret; 818c2ecf20Sopenharmony_ci struct cpuidle_driver *drv; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci drv = kmemdup(&arm_idle_driver, sizeof(*drv), GFP_KERNEL); 848c2ecf20Sopenharmony_ci if (!drv) 858c2ecf20Sopenharmony_ci return -ENOMEM; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci drv->cpumask = (struct cpumask *)cpumask_of(cpu); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci /* 908c2ecf20Sopenharmony_ci * Initialize idle states data, starting at index 1. This 918c2ecf20Sopenharmony_ci * driver is DT only, if no DT idle states are detected (ret 928c2ecf20Sopenharmony_ci * == 0) let the driver initialization fail accordingly since 938c2ecf20Sopenharmony_ci * there is no reason to initialize the idle driver if only 948c2ecf20Sopenharmony_ci * wfi is supported. 958c2ecf20Sopenharmony_ci */ 968c2ecf20Sopenharmony_ci ret = dt_init_idle_driver(drv, arm_idle_state_match, 1); 978c2ecf20Sopenharmony_ci if (ret <= 0) { 988c2ecf20Sopenharmony_ci ret = ret ? : -ENODEV; 998c2ecf20Sopenharmony_ci goto out_kfree_drv; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* 1038c2ecf20Sopenharmony_ci * Call arch CPU operations in order to initialize 1048c2ecf20Sopenharmony_ci * idle states suspend back-end specific data 1058c2ecf20Sopenharmony_ci */ 1068c2ecf20Sopenharmony_ci ret = arm_cpuidle_init(cpu); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci /* 1098c2ecf20Sopenharmony_ci * Allow the initialization to continue for other CPUs, if the 1108c2ecf20Sopenharmony_ci * reported failure is a HW misconfiguration/breakage (-ENXIO). 1118c2ecf20Sopenharmony_ci * 1128c2ecf20Sopenharmony_ci * Some platforms do not support idle operations 1138c2ecf20Sopenharmony_ci * (arm_cpuidle_init() returning -EOPNOTSUPP), we should 1148c2ecf20Sopenharmony_ci * not flag this case as an error, it is a valid 1158c2ecf20Sopenharmony_ci * configuration. 1168c2ecf20Sopenharmony_ci */ 1178c2ecf20Sopenharmony_ci if (ret) { 1188c2ecf20Sopenharmony_ci if (ret != -EOPNOTSUPP) 1198c2ecf20Sopenharmony_ci pr_err("CPU %d failed to init idle CPU ops\n", cpu); 1208c2ecf20Sopenharmony_ci ret = ret == -ENXIO ? 0 : ret; 1218c2ecf20Sopenharmony_ci goto out_kfree_drv; 1228c2ecf20Sopenharmony_ci } 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci ret = cpuidle_register(drv, NULL); 1258c2ecf20Sopenharmony_ci if (ret) 1268c2ecf20Sopenharmony_ci goto out_kfree_drv; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci cpuidle_cooling_register(drv); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci return 0; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ciout_kfree_drv: 1338c2ecf20Sopenharmony_ci kfree(drv); 1348c2ecf20Sopenharmony_ci return ret; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci/* 1388c2ecf20Sopenharmony_ci * arm_idle_init - Initializes arm cpuidle driver 1398c2ecf20Sopenharmony_ci * 1408c2ecf20Sopenharmony_ci * Initializes arm cpuidle driver for all CPUs, if any CPU fails 1418c2ecf20Sopenharmony_ci * to register cpuidle driver then rollback to cancel all CPUs 1428c2ecf20Sopenharmony_ci * registeration. 1438c2ecf20Sopenharmony_ci */ 1448c2ecf20Sopenharmony_cistatic int __init arm_idle_init(void) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci int cpu, ret; 1478c2ecf20Sopenharmony_ci struct cpuidle_driver *drv; 1488c2ecf20Sopenharmony_ci struct cpuidle_device *dev; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci for_each_possible_cpu(cpu) { 1518c2ecf20Sopenharmony_ci ret = arm_idle_init_cpu(cpu); 1528c2ecf20Sopenharmony_ci if (ret) 1538c2ecf20Sopenharmony_ci goto out_fail; 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci return 0; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ciout_fail: 1598c2ecf20Sopenharmony_ci while (--cpu >= 0) { 1608c2ecf20Sopenharmony_ci dev = per_cpu(cpuidle_devices, cpu); 1618c2ecf20Sopenharmony_ci drv = cpuidle_get_cpu_driver(dev); 1628c2ecf20Sopenharmony_ci cpuidle_unregister(drv); 1638c2ecf20Sopenharmony_ci kfree(drv); 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci return ret; 1678c2ecf20Sopenharmony_ci} 1688c2ecf20Sopenharmony_cidevice_initcall(arm_idle_init); 169