18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * ladder.c - the residency ladder algorithm
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
58c2ecf20Sopenharmony_ci *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
68c2ecf20Sopenharmony_ci *  Copyright (C) 2004, 2005 Dominik Brodowski <linux@brodo.de>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * (C) 2006-2007 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>
98c2ecf20Sopenharmony_ci *               Shaohua Li <shaohua.li@intel.com>
108c2ecf20Sopenharmony_ci *               Adam Belay <abelay@novell.com>
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * This code is licenced under the GPL.
138c2ecf20Sopenharmony_ci */
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/kernel.h>
168c2ecf20Sopenharmony_ci#include <linux/cpuidle.h>
178c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
188c2ecf20Sopenharmony_ci#include <linux/tick.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include <asm/io.h>
218c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define PROMOTION_COUNT 4
248c2ecf20Sopenharmony_ci#define DEMOTION_COUNT 1
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistruct ladder_device_state {
278c2ecf20Sopenharmony_ci	struct {
288c2ecf20Sopenharmony_ci		u32 promotion_count;
298c2ecf20Sopenharmony_ci		u32 demotion_count;
308c2ecf20Sopenharmony_ci		u64 promotion_time_ns;
318c2ecf20Sopenharmony_ci		u64 demotion_time_ns;
328c2ecf20Sopenharmony_ci	} threshold;
338c2ecf20Sopenharmony_ci	struct {
348c2ecf20Sopenharmony_ci		int promotion_count;
358c2ecf20Sopenharmony_ci		int demotion_count;
368c2ecf20Sopenharmony_ci	} stats;
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistruct ladder_device {
408c2ecf20Sopenharmony_ci	struct ladder_device_state states[CPUIDLE_STATE_MAX];
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic DEFINE_PER_CPU(struct ladder_device, ladder_devices);
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/**
468c2ecf20Sopenharmony_ci * ladder_do_selection - prepares private data for a state change
478c2ecf20Sopenharmony_ci * @ldev: the ladder device
488c2ecf20Sopenharmony_ci * @old_idx: the current state index
498c2ecf20Sopenharmony_ci * @new_idx: the new target state index
508c2ecf20Sopenharmony_ci */
518c2ecf20Sopenharmony_cistatic inline void ladder_do_selection(struct cpuidle_device *dev,
528c2ecf20Sopenharmony_ci				       struct ladder_device *ldev,
538c2ecf20Sopenharmony_ci				       int old_idx, int new_idx)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	ldev->states[old_idx].stats.promotion_count = 0;
568c2ecf20Sopenharmony_ci	ldev->states[old_idx].stats.demotion_count = 0;
578c2ecf20Sopenharmony_ci	dev->last_state_idx = new_idx;
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci/**
618c2ecf20Sopenharmony_ci * ladder_select_state - selects the next state to enter
628c2ecf20Sopenharmony_ci * @drv: cpuidle driver
638c2ecf20Sopenharmony_ci * @dev: the CPU
648c2ecf20Sopenharmony_ci * @dummy: not used
658c2ecf20Sopenharmony_ci */
668c2ecf20Sopenharmony_cistatic int ladder_select_state(struct cpuidle_driver *drv,
678c2ecf20Sopenharmony_ci			       struct cpuidle_device *dev, bool *dummy)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	struct ladder_device *ldev = this_cpu_ptr(&ladder_devices);
708c2ecf20Sopenharmony_ci	struct ladder_device_state *last_state;
718c2ecf20Sopenharmony_ci	int last_idx = dev->last_state_idx;
728c2ecf20Sopenharmony_ci	int first_idx = drv->states[0].flags & CPUIDLE_FLAG_POLLING ? 1 : 0;
738c2ecf20Sopenharmony_ci	s64 latency_req = cpuidle_governor_latency_req(dev->cpu);
748c2ecf20Sopenharmony_ci	s64 last_residency;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	/* Special case when user has set very strict latency requirement */
778c2ecf20Sopenharmony_ci	if (unlikely(latency_req == 0)) {
788c2ecf20Sopenharmony_ci		ladder_do_selection(dev, ldev, last_idx, 0);
798c2ecf20Sopenharmony_ci		return 0;
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	last_state = &ldev->states[last_idx];
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	last_residency = dev->last_residency_ns - drv->states[last_idx].exit_latency_ns;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	/* consider promotion */
878c2ecf20Sopenharmony_ci	if (last_idx < drv->state_count - 1 &&
888c2ecf20Sopenharmony_ci	    !dev->states_usage[last_idx + 1].disable &&
898c2ecf20Sopenharmony_ci	    last_residency > last_state->threshold.promotion_time_ns &&
908c2ecf20Sopenharmony_ci	    drv->states[last_idx + 1].exit_latency_ns <= latency_req) {
918c2ecf20Sopenharmony_ci		last_state->stats.promotion_count++;
928c2ecf20Sopenharmony_ci		last_state->stats.demotion_count = 0;
938c2ecf20Sopenharmony_ci		if (last_state->stats.promotion_count >= last_state->threshold.promotion_count) {
948c2ecf20Sopenharmony_ci			ladder_do_selection(dev, ldev, last_idx, last_idx + 1);
958c2ecf20Sopenharmony_ci			return last_idx + 1;
968c2ecf20Sopenharmony_ci		}
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	/* consider demotion */
1008c2ecf20Sopenharmony_ci	if (last_idx > first_idx &&
1018c2ecf20Sopenharmony_ci	    (dev->states_usage[last_idx].disable ||
1028c2ecf20Sopenharmony_ci	    drv->states[last_idx].exit_latency_ns > latency_req)) {
1038c2ecf20Sopenharmony_ci		int i;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci		for (i = last_idx - 1; i > first_idx; i--) {
1068c2ecf20Sopenharmony_ci			if (drv->states[i].exit_latency_ns <= latency_req)
1078c2ecf20Sopenharmony_ci				break;
1088c2ecf20Sopenharmony_ci		}
1098c2ecf20Sopenharmony_ci		ladder_do_selection(dev, ldev, last_idx, i);
1108c2ecf20Sopenharmony_ci		return i;
1118c2ecf20Sopenharmony_ci	}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	if (last_idx > first_idx &&
1148c2ecf20Sopenharmony_ci	    last_residency < last_state->threshold.demotion_time_ns) {
1158c2ecf20Sopenharmony_ci		last_state->stats.demotion_count++;
1168c2ecf20Sopenharmony_ci		last_state->stats.promotion_count = 0;
1178c2ecf20Sopenharmony_ci		if (last_state->stats.demotion_count >= last_state->threshold.demotion_count) {
1188c2ecf20Sopenharmony_ci			ladder_do_selection(dev, ldev, last_idx, last_idx - 1);
1198c2ecf20Sopenharmony_ci			return last_idx - 1;
1208c2ecf20Sopenharmony_ci		}
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	/* otherwise remain at the current state */
1248c2ecf20Sopenharmony_ci	return last_idx;
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci/**
1288c2ecf20Sopenharmony_ci * ladder_enable_device - setup for the governor
1298c2ecf20Sopenharmony_ci * @drv: cpuidle driver
1308c2ecf20Sopenharmony_ci * @dev: the CPU
1318c2ecf20Sopenharmony_ci */
1328c2ecf20Sopenharmony_cistatic int ladder_enable_device(struct cpuidle_driver *drv,
1338c2ecf20Sopenharmony_ci				struct cpuidle_device *dev)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	int i;
1368c2ecf20Sopenharmony_ci	int first_idx = drv->states[0].flags & CPUIDLE_FLAG_POLLING ? 1 : 0;
1378c2ecf20Sopenharmony_ci	struct ladder_device *ldev = &per_cpu(ladder_devices, dev->cpu);
1388c2ecf20Sopenharmony_ci	struct ladder_device_state *lstate;
1398c2ecf20Sopenharmony_ci	struct cpuidle_state *state;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	dev->last_state_idx = first_idx;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	for (i = first_idx; i < drv->state_count; i++) {
1448c2ecf20Sopenharmony_ci		state = &drv->states[i];
1458c2ecf20Sopenharmony_ci		lstate = &ldev->states[i];
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci		lstate->stats.promotion_count = 0;
1488c2ecf20Sopenharmony_ci		lstate->stats.demotion_count = 0;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci		lstate->threshold.promotion_count = PROMOTION_COUNT;
1518c2ecf20Sopenharmony_ci		lstate->threshold.demotion_count = DEMOTION_COUNT;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci		if (i < drv->state_count - 1)
1548c2ecf20Sopenharmony_ci			lstate->threshold.promotion_time_ns = state->exit_latency_ns;
1558c2ecf20Sopenharmony_ci		if (i > first_idx)
1568c2ecf20Sopenharmony_ci			lstate->threshold.demotion_time_ns = state->exit_latency_ns;
1578c2ecf20Sopenharmony_ci	}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	return 0;
1608c2ecf20Sopenharmony_ci}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci/**
1638c2ecf20Sopenharmony_ci * ladder_reflect - update the correct last_state_idx
1648c2ecf20Sopenharmony_ci * @dev: the CPU
1658c2ecf20Sopenharmony_ci * @index: the index of actual state entered
1668c2ecf20Sopenharmony_ci */
1678c2ecf20Sopenharmony_cistatic void ladder_reflect(struct cpuidle_device *dev, int index)
1688c2ecf20Sopenharmony_ci{
1698c2ecf20Sopenharmony_ci	if (index > 0)
1708c2ecf20Sopenharmony_ci		dev->last_state_idx = index;
1718c2ecf20Sopenharmony_ci}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_cistatic struct cpuidle_governor ladder_governor = {
1748c2ecf20Sopenharmony_ci	.name =		"ladder",
1758c2ecf20Sopenharmony_ci	.rating =	10,
1768c2ecf20Sopenharmony_ci	.enable =	ladder_enable_device,
1778c2ecf20Sopenharmony_ci	.select =	ladder_select_state,
1788c2ecf20Sopenharmony_ci	.reflect =	ladder_reflect,
1798c2ecf20Sopenharmony_ci};
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci/**
1828c2ecf20Sopenharmony_ci * init_ladder - initializes the governor
1838c2ecf20Sopenharmony_ci */
1848c2ecf20Sopenharmony_cistatic int __init init_ladder(void)
1858c2ecf20Sopenharmony_ci{
1868c2ecf20Sopenharmony_ci	/*
1878c2ecf20Sopenharmony_ci	 * When NO_HZ is disabled, or when booting with nohz=off, the ladder
1888c2ecf20Sopenharmony_ci	 * governor is better so give it a higher rating than the menu
1898c2ecf20Sopenharmony_ci	 * governor.
1908c2ecf20Sopenharmony_ci	 */
1918c2ecf20Sopenharmony_ci	if (!tick_nohz_enabled)
1928c2ecf20Sopenharmony_ci		ladder_governor.rating = 25;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	return cpuidle_register_governor(&ladder_governor);
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cipostcore_initcall(init_ladder);
198