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