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