Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9001b8f1 authored by Puja Gupta's avatar Puja Gupta Committed by Todd Kjos
Browse files

ANDROID: sched: Add support for frequency/power energy model



This patch provides support to pass frequency/power values from energy
model instead of capacity/power. It is required to support multiple
speed bin devices i.e. devices which have different max frequency based
on hardware.

User can provide all supported frequencies across all versions of the
device in device tree energy model. Max frequency of the cpu is read
at runtime from OPP driver and the capacity values are populated
using that. Energy model proc file will display cap_states as a tuple of
(capacity, frequency, power), in which frequency will be 0 when not using
frequency energy model.

Change-Id: I25aec1f91aa8f09bcf67f0575792693808847618
Signed-off-by: default avatarPuja Gupta <pujag@codeaurora.org>
parent 1f648790
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -237,6 +237,11 @@ Property added to the cpu node:

	Any other configuration is invalid.

- freq-energy-model
	Description: Optional. Must be declared if the energy model
	represents frequency/power values. If absent, energy model is
	by default considered as capacity/power.

===========================================================
5 - Example dts
===========================================================
+2 −0
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ extern int sched_domain_level_max;

struct capacity_state {
	unsigned long cap;	/* compute capacity */
	unsigned long frequency;/* frequency */
	unsigned long power;	/* power consumption at this compute capacity */
};

@@ -194,6 +195,7 @@ typedef const struct cpumask *(*sched_domain_mask_f)(int cpu);
typedef int (*sched_domain_flags_f)(void);
typedef
const struct sched_group_energy * const(*sched_domain_energy_f)(int cpu);
extern bool energy_aware(void);

#define SDTL_OVERLAP	0x01

+170 −6
Original line number Diff line number Diff line
@@ -18,8 +18,6 @@
 */
#define pr_fmt(fmt) "sched-energy: " fmt

#define DEBUG

#include <linux/gfp.h>
#include <linux/of.h>
#include <linux/printk.h>
@@ -28,6 +26,11 @@
#include <linux/sched/energy.h>
#include <linux/stddef.h>
#include <linux/arch_topology.h>
#include <linux/cpu.h>
#include <linux/pm_opp.h>
#include <linux/platform_device.h>

#include "sched.h"

struct sched_group_energy *sge_array[NR_CPUS][NR_SD_LEVELS];

@@ -47,6 +50,8 @@ static void free_resources(void)
		}
	}
}
static bool sge_ready;
static bool freq_energy_model;

void check_max_cap_vs_cpu_scale(int cpu, struct sched_group_energy *sge)
{
@@ -83,6 +88,9 @@ void init_sched_energy_costs(void)
			pr_warn("CPU device node has no sched-energy-costs\n");
			return;
		}
		/* Check if the energy model contains frequency/power values */
		if (of_find_property(cn, "freq-energy-model", NULL))
			freq_energy_model = true;

		for_each_possible_sd_level(sd_level) {
			cp = of_parse_phandle(cn, "sched-energy-costs", sd_level);
@@ -104,7 +112,18 @@ void init_sched_energy_costs(void)
					     GFP_NOWAIT);

			for (i = 0, val = prop->value; i < nstates; i++) {
				if (freq_energy_model) {
					/*
					 * Capacity values will be calculated later using
					 * frequency reported by OPP driver and cpu_uarch_scale
					 * values.
					 */
					cap_states[i].frequency = be32_to_cpup(val++);
					cap_states[i].cap = 0;
				} else {
					cap_states[i].frequency = 0;
					cap_states[i].cap = be32_to_cpup(val++);
				}
				cap_states[i].power = be32_to_cpup(val++);
			}

@@ -130,13 +149,158 @@ void init_sched_energy_costs(void)

			sge_array[cpu][sd_level] = sge;
		}

		if (!freq_energy_model)
			check_max_cap_vs_cpu_scale(cpu, sge_array[cpu][SD_LEVEL0]);
	}

	sge_ready = true;
	pr_info("Sched-energy-costs installed from DT\n");
	return;

out:
	free_resources();
}

static int sched_energy_probe(struct platform_device *pdev)
{
	int cpu;
	unsigned long *max_frequencies = NULL;
	int ret;

	if (!sge_ready)
		return -EPROBE_DEFER;

	if (!energy_aware() || !freq_energy_model)
		return 0;

	max_frequencies = kmalloc_array(nr_cpu_ids, sizeof(unsigned long),
					GFP_KERNEL);
	if (!max_frequencies) {
		ret = -ENOMEM;
		goto exit;
	}

	/*
	 * Find system max possible frequency and max frequencies for each
	 * CPUs.
	 */
	for_each_possible_cpu(cpu) {
		struct device *cpu_dev;
		struct dev_pm_opp *opp;

		cpu_dev = get_cpu_device(cpu);
		if (IS_ERR_OR_NULL(cpu_dev)) {
			if (!cpu_dev)
				ret = -EINVAL;
			else
				ret = PTR_ERR(cpu_dev);
			goto exit;
		}

		max_frequencies[cpu] = ULONG_MAX;

		opp = dev_pm_opp_find_freq_floor(cpu_dev,
						 &max_frequencies[cpu]);
		if (IS_ERR_OR_NULL(opp)) {
			if (!opp || PTR_ERR(opp) == -ENODEV)
				ret = -EPROBE_DEFER;
			else
				ret = PTR_ERR(opp);
			goto exit;
		}

		/* Convert HZ to KHZ */
		max_frequencies[cpu] /= 1000;
	}

	/* update capacity in energy model */
	for_each_possible_cpu(cpu) {
		unsigned long cpu_max_cap;
		struct sched_group_energy *sge_l0, *sge;
		cpu_max_cap = topology_get_cpu_scale(NULL, cpu);

		/*
		 * All the cap_states have same frequency table so use
		 * SD_LEVEL0's.
		 */
		sge_l0 = sge_array[cpu][SD_LEVEL0];
		if (sge_l0 && sge_l0->nr_cap_states > 0) {
			int i;
			int ncapstates = sge_l0->nr_cap_states;

			for (i = 0; i < ncapstates; i++) {
				int sd_level;
				unsigned long freq, cap;

				/*
				 * Energy model can contain more frequency
				 * steps than actual for multiple speedbin
				 * support. Ceil the max capacity with actual
				 * one.
				 */
				freq = min(sge_l0->cap_states[i].frequency,
					   max_frequencies[cpu]);
				cap = DIV_ROUND_UP(cpu_max_cap * freq,
						   max_frequencies[cpu]);

				for_each_possible_sd_level(sd_level) {
					sge = sge_array[cpu][sd_level];
					if (!sge)
						break;
					sge->cap_states[i].cap = cap;
				}
				dev_dbg(&pdev->dev,
					"cpu=%d freq=%ld cap=%ld power_d0=%ld\n",
					cpu, freq, sge_l0->cap_states[i].cap,
					sge_l0->cap_states[i].power);
			}

			dev_info(&pdev->dev,
				"cpu=%d [freq=%ld cap=%ld power_d0=%ld] -> [freq=%ld cap=%ld power_d0=%ld]\n",
				cpu,
				sge_l0->cap_states[0].frequency,
				sge_l0->cap_states[0].cap,
				sge_l0->cap_states[0].power,
				sge_l0->cap_states[ncapstates - 1].frequency,
				sge_l0->cap_states[ncapstates - 1].cap,
				sge_l0->cap_states[ncapstates - 1].power
				);
		}
	}

	kfree(max_frequencies);
	dev_info(&pdev->dev, "Sched-energy-costs capacity updated\n");
	return 0;

exit:
	if (ret != -EPROBE_DEFER)
		dev_err(&pdev->dev, "error=%d\n", ret);
	kfree(max_frequencies);
	return ret;
}

static struct platform_driver energy_driver = {
	.driver = {
		.name = "sched-energy",
	},
	.probe = sched_energy_probe,
};

static struct platform_device energy_device = {
	.name = "sched-energy",
};

static int __init sched_energy_init(void)
{
	int ret;

	ret = platform_device_register(&energy_device);
	if (ret)
		pr_err("%s device_register failed:%d\n", __func__, ret);
	ret = platform_driver_register(&energy_driver);
	if (ret) {
		pr_err("%s driver_register failed:%d\n", __func__, ret);
		platform_device_unregister(&energy_device);
	}
	return ret;
}
subsys_initcall(sched_energy_init);
+1 −1
Original line number Diff line number Diff line
@@ -5686,7 +5686,7 @@ unsigned long capacity_curr_of(int cpu)
	return cap_scale(max_cap, scale_freq);
}

static inline bool energy_aware(void)
inline bool energy_aware(void)
{
	return sched_feat(ENERGY_AWARE);
}