linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH RFC 0/2] Use cpufreq-map governor for MT8183 CCI
@ 2019-06-18  4:14 Hsin-Yi Wang
  2019-06-18  4:14 ` [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor Hsin-Yi Wang
  2019-06-18  4:14 ` [PATCH RFC 2/2] devfreq: mt8183-cci: using cpufreq-map governor in cci dvfs driver Hsin-Yi Wang
  0 siblings, 2 replies; 8+ messages in thread
From: Hsin-Yi Wang @ 2019-06-18  4:14 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Saravana Kannan, Andrew-sh . Cheng, linux-pm, linux-kernel,
	Chanwoo Choi, Kyungmin Park, MyungJoo Ham, linux-mediatek,
	Matthias Brugger

From: Hsin-Yi Wang <hsinyi@chromium.org>

This series uses cpufreq-map governor for mt8183-cci to improve performance.

Hsin-Yi Wang (1):
  devfreq: mt8183-cci: using cpufreq-map governor in cci dvfs driver

Saravana Kannan (1):
  PM / devfreq: Generic CPU frequency to device frequency mapping
    governor

 .../bindings/devfreq/devfreq-cpufreq-map.txt  |  53 ++
 drivers/devfreq/Kconfig                       |   8 +
 drivers/devfreq/Makefile                      |   1 +
 drivers/devfreq/governor_cpufreq_map.c        | 583 ++++++++++++++++++
 drivers/devfreq/mt8183-cci-devfreq.c          | 194 +-----
 5 files changed, 649 insertions(+), 190 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
 create mode 100644 drivers/devfreq/governor_cpufreq_map.c

-- 
2.20.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor
  2019-06-18  4:14 [PATCH RFC 0/2] Use cpufreq-map governor for MT8183 CCI Hsin-Yi Wang
@ 2019-06-18  4:14 ` Hsin-Yi Wang
  2019-06-20  6:58   ` Chanwoo Choi
  2019-06-18  4:14 ` [PATCH RFC 2/2] devfreq: mt8183-cci: using cpufreq-map governor in cci dvfs driver Hsin-Yi Wang
  1 sibling, 1 reply; 8+ messages in thread
From: Hsin-Yi Wang @ 2019-06-18  4:14 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Saravana Kannan, Andrew-sh . Cheng, linux-pm, linux-kernel,
	Chanwoo Choi, Kyungmin Park, MyungJoo Ham, linux-mediatek,
	Matthias Brugger

From: Saravana Kannan <skannan@codeaurora.org>

From: Saravana Kannan <skannan@codeaurora.org>

Many CPU architectures have caches that can scale independent of the CPUs.
Frequency scaling of the caches is necessary to make sure the cache is not
a performance bottleneck that leads to poor performance and power. The same
idea applies for RAM/DDR.

To achieve this, this patch adds a generic devfreq governor that takes the
current frequency of each CPU frequency domain and then adjusts the
frequency of the cache (or any devfreq device) based on the frequency of
the CPUs. It listens to CPU frequency transition notifiers to keep itself
up to date on the current CPU frequency.

To decide the frequency of the device, the governor does one of the
following:

* Uses a CPU frequency to device frequency mapping table
  - Either one mapping table used for all CPU freq policies (typically used
    for system with homogeneous cores/clusters that have the same OPPs).
  - One mapping table per CPU freq policy (typically used for ASMP systems
    with heterogeneous CPUs with different OPPs)

OR

* Scales the device frequency in proportion to the CPU frequency. So, if
  the CPUs are running at their max frequency, the device runs at its max
  frequency.  If the CPUs are running at their min frequency, the device
  runs at its min frequency. And interpolated for frequencies in between.

Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
---
 .../bindings/devfreq/devfreq-cpufreq-map.txt  |  53 ++
 drivers/devfreq/Kconfig                       |   8 +
 drivers/devfreq/Makefile                      |   1 +
 drivers/devfreq/governor_cpufreq_map.c        | 583 ++++++++++++++++++
 4 files changed, 645 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
 create mode 100644 drivers/devfreq/governor_cpufreq_map.c

diff --git a/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
new file mode 100644
index 000000000000..982a30bcfc86
--- /dev/null
+++ b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
@@ -0,0 +1,53 @@
+Devfreq CPUfreq governor
+
+devfreq-cpufreq-map is a parent device that contains one or more child devices.
+Each child device provides CPU frequency to device frequency mapping for a
+specific device. Examples of devices that could use this are: DDR, cache and
+CCI.
+
+Parent device name shall be "devfreq-cpufreq-map".
+
+Required child device properties:
+- cpu-to-dev-map, or cpu-to-dev-map-<X>:
+			A list of tuples where each tuple consists of a
+			CPU frequency (KHz) and the corresponding device
+			frequency. CPU frequencies not listed in the table
+			will use the device frequency that corresponds to the
+			next rounded up CPU frequency.
+			Use "cpu-to-dev-map" if all CPUs in the system should
+			share same mapping.
+			Use cpu-to-dev-map-<cpuid> to describe different
+			mappings for different CPUs. The property should be
+			listed only for the first CPU if multiple CPUs are
+			synchronous.
+- target-dev:		Phandle to device that this mapping applies to.
+
+Example:
+	devfreq-cpufreq-map {
+		cpubw-cpufreq {
+			target-dev = <&cpubw>;
+			cpu-to-dev-map =
+				<  300000  1144000 >,
+				<  422400  2288000 >,
+				<  652800  3051000 >,
+				<  883200  5996000 >,
+				< 1190400  8056000 >,
+				< 1497600 10101000 >,
+				< 1728000 12145000 >,
+				< 2649600 16250000 >;
+		};
+
+		cache-cpufreq {
+			target-dev = <&cache>;
+			cpu-to-dev-map =
+				<  300000  300000 >,
+				<  422400  422400 >,
+				<  652800  499200 >,
+				<  883200  576000 >,
+				<  960000  960000 >,
+				< 1497600 1036800 >,
+				< 1574400 1574400 >,
+				< 1728000 1651200 >,
+				< 2649600 1728000 >;
+		};
+	};
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
index 0c8204d6b78a..0303f5a400b6 100644
--- a/drivers/devfreq/Kconfig
+++ b/drivers/devfreq/Kconfig
@@ -74,6 +74,14 @@ config DEVFREQ_GOV_PASSIVE
 	  through sysfs entries. The passive governor recommends that
 	  devfreq device uses the OPP table to get the frequency/voltage.
 
+config DEVFREQ_GOV_CPUFREQ_MAP
+	tristate "CPUfreq Map"
+	depends on CPU_FREQ
+	help
+	  Chooses frequency based on the online CPUs' current frequency and a
+	  CPU frequency to device frequency mapping table(s). This governor
+	  can be useful for controlling devices such as DDR, cache, CCI, etc.
+
 comment "DEVFREQ Drivers"
 
 config ARM_EXYNOS_BUS_DEVFREQ
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index 817dde779f16..81141e2c784f 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE)	+= governor_performance.o
 obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
 obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
 obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
+obj-$(CONFIG_DEVFREQ_GOV_CPUFREQ_MAP)	+= governor_cpufreq_map.o
 
 # DEVFREQ Drivers
 obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
diff --git a/drivers/devfreq/governor_cpufreq_map.c b/drivers/devfreq/governor_cpufreq_map.c
new file mode 100644
index 000000000000..084a3ffb8f54
--- /dev/null
+++ b/drivers/devfreq/governor_cpufreq_map.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014-2015, 2018, The Linux Foundation. All rights reserved.
+ */
+
+#define pr_fmt(fmt) "dev-cpufreq-map: " fmt
+
+#include <linux/devfreq.h>
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include "governor.h"
+
+struct cpu_state {
+	unsigned int freq;
+	unsigned int min_freq;
+	unsigned int max_freq;
+	unsigned int first_cpu;
+};
+static struct cpu_state *state[NR_CPUS];
+static int cpufreq_cnt;
+
+struct freq_map {
+	unsigned int cpu_khz;
+	unsigned int target_freq;
+};
+
+struct devfreq_node {
+	struct devfreq *df;
+	void *orig_data;
+	struct device *dev;
+	struct device_node *of_node;
+	struct list_head list;
+	struct freq_map **map;
+	struct freq_map *common_map;
+};
+static LIST_HEAD(devfreq_list);
+static DEFINE_MUTEX(state_lock);
+static DEFINE_MUTEX(cpufreq_reg_lock);
+
+static void update_all_devfreqs(void)
+{
+	struct devfreq_node *node;
+
+	list_for_each_entry(node, &devfreq_list, list) {
+		struct devfreq *df = node->df;
+
+		if (!node->df)
+			continue;
+		mutex_lock(&df->lock);
+		update_devfreq(df);
+		mutex_unlock(&df->lock);
+
+	}
+}
+
+static struct devfreq_node *find_devfreq_node(struct device *dev)
+{
+	struct devfreq_node *node;
+
+	list_for_each_entry(node, &devfreq_list, list)
+		if (node->dev == dev || node->of_node == dev->of_node)
+			return node;
+
+	return NULL;
+}
+
+/* ==================== cpufreq part ==================== */
+static struct cpu_state *add_policy(struct cpufreq_policy *policy)
+{
+	struct cpu_state *new_state;
+	unsigned int cpu, first_cpu;
+
+	new_state = kzalloc(sizeof(struct cpu_state), GFP_KERNEL);
+	if (!new_state)
+		return NULL;
+
+	first_cpu = cpumask_first(policy->related_cpus);
+	new_state->first_cpu = first_cpu;
+	new_state->freq = policy->cur;
+	new_state->min_freq = policy->cpuinfo.min_freq;
+	new_state->max_freq = policy->cpuinfo.max_freq;
+
+	for_each_cpu(cpu, policy->related_cpus)
+		state[cpu] = new_state;
+
+	return new_state;
+}
+
+static int cpufreq_trans_notifier(struct notifier_block *nb,
+		unsigned long event, void *data)
+{
+	struct cpufreq_freqs *freq = data;
+	struct cpu_state *s;
+	struct cpufreq_policy *policy = NULL;
+
+	if (event != CPUFREQ_POSTCHANGE)
+		return 0;
+
+	mutex_lock(&state_lock);
+
+	s = state[freq->cpu];
+	if (!s) {
+		policy = cpufreq_cpu_get(freq->cpu);
+		if (policy) {
+			s = add_policy(policy);
+			cpufreq_cpu_put(policy);
+		}
+	}
+	if (!s)
+		goto out;
+
+	if (s->freq != freq->new || policy) {
+		s->freq = freq->new;
+		update_all_devfreqs();
+	}
+
+out:
+	mutex_unlock(&state_lock);
+	return 0;
+}
+
+static struct notifier_block cpufreq_trans_nb = {
+	.notifier_call = cpufreq_trans_notifier
+};
+
+static int register_cpufreq(void)
+{
+	int ret = 0;
+	unsigned int cpu;
+	struct cpufreq_policy *policy;
+
+	mutex_lock(&cpufreq_reg_lock);
+
+	if (cpufreq_cnt)
+		goto cnt_not_zero;
+
+	get_online_cpus();
+	ret = cpufreq_register_notifier(&cpufreq_trans_nb,
+				CPUFREQ_TRANSITION_NOTIFIER);
+	if (ret)
+		goto out;
+
+	for_each_online_cpu(cpu) {
+		policy = cpufreq_cpu_get(cpu);
+		if (policy) {
+			add_policy(policy);
+			cpufreq_cpu_put(policy);
+		}
+	}
+out:
+	put_online_cpus();
+cnt_not_zero:
+	if (!ret)
+		cpufreq_cnt++;
+	mutex_unlock(&cpufreq_reg_lock);
+	return ret;
+}
+
+static int unregister_cpufreq(void)
+{
+	int ret = 0;
+	int cpu;
+
+	mutex_lock(&cpufreq_reg_lock);
+
+	if (cpufreq_cnt > 1)
+		goto out;
+
+	cpufreq_unregister_notifier(&cpufreq_trans_nb,
+				CPUFREQ_TRANSITION_NOTIFIER);
+
+	for (cpu = ARRAY_SIZE(state) - 1; cpu >= 0; cpu--) {
+		if (!state[cpu])
+			continue;
+		if (state[cpu]->first_cpu == cpu)
+			kfree(state[cpu]);
+		state[cpu] = NULL;
+	}
+
+out:
+	cpufreq_cnt--;
+	mutex_unlock(&cpufreq_reg_lock);
+	return ret;
+}
+
+/* ==================== devfreq part ==================== */
+
+static unsigned int interpolate_freq(struct devfreq *df, unsigned int cpu)
+{
+	unsigned long *freq_table = df->profile->freq_table;
+	unsigned int cpu_min = state[cpu]->min_freq;
+	unsigned int cpu_max = state[cpu]->max_freq;
+	unsigned int cpu_freq = state[cpu]->freq;
+	unsigned int dev_min, dev_max, cpu_percent;
+
+	if (freq_table) {
+		dev_min = freq_table[0];
+		dev_max = freq_table[df->profile->max_state - 1];
+	} else {
+		if (df->max_freq <= df->min_freq)
+			return 0;
+		dev_min = df->min_freq;
+		dev_max = df->max_freq;
+	}
+
+	cpu_percent = ((cpu_freq - cpu_min) * 100) / (cpu_max - cpu_min);
+	return dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100);
+}
+
+static unsigned int cpu_to_dev_freq(struct devfreq *df, unsigned int cpu)
+{
+	struct freq_map *map = NULL;
+	unsigned int cpu_khz = 0, freq;
+	struct devfreq_node *n = df->data;
+
+	if (!state[cpu] || state[cpu]->first_cpu != cpu) {
+		freq = 0;
+		goto out;
+	}
+
+	if (n->common_map)
+		map = n->common_map;
+	else if (n->map)
+		map = n->map[cpu];
+
+	cpu_khz = state[cpu]->freq;
+
+	if (!map) {
+		freq = interpolate_freq(df, cpu);
+		goto out;
+	}
+
+	while (map->cpu_khz && map->cpu_khz < cpu_khz)
+		map++;
+	if (!map->cpu_khz)
+		map--;
+	freq = map->target_freq;
+
+out:
+	dev_dbg(df->dev.parent, "CPU%u: %d -> dev: %u\n", cpu, cpu_khz, freq);
+	return freq;
+}
+
+static int devfreq_cpufreq_get_freq(struct devfreq *df,
+					unsigned long *freq)
+{
+	unsigned int cpu, tgt_freq = 0;
+	struct devfreq_node *node;
+
+	node = df->data;
+	if (!node) {
+		pr_err("Unable to find devfreq node!\n");
+		return -ENODEV;
+	}
+
+	for_each_possible_cpu(cpu)
+		tgt_freq = max(tgt_freq, cpu_to_dev_freq(df, cpu));
+
+	*freq = tgt_freq;
+	return 0;
+}
+
+static unsigned int show_table(char *buf, unsigned int len,
+				struct freq_map *map)
+{
+	unsigned int cnt = 0;
+
+	cnt += snprintf(buf + cnt, len - cnt, "CPU freq\tDevice freq\n");
+
+	while (map->cpu_khz && cnt < len) {
+		cnt += snprintf(buf + cnt, len - cnt, "%8u\t%11u\n",
+				map->cpu_khz, map->target_freq);
+		map++;
+	}
+	if (cnt < len)
+		cnt += snprintf(buf + cnt, len - cnt, "\n");
+
+	return cnt;
+}
+
+static ssize_t freq_map_show(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct devfreq *df = to_devfreq(dev);
+	struct devfreq_node *n = df->data;
+	struct freq_map *map;
+	unsigned int cnt = 0, cpu;
+
+	mutex_lock(&state_lock);
+	if (n->common_map) {
+		map = n->common_map;
+		cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
+				"Common table for all CPUs:\n");
+		cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
+	} else if (n->map) {
+		for_each_possible_cpu(cpu) {
+			map = n->map[cpu];
+			if (!map)
+				continue;
+			cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
+					"CPU %u:\n", cpu);
+			if (cnt >= PAGE_SIZE)
+				break;
+			cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
+			if (cnt >= PAGE_SIZE)
+				break;
+		}
+	} else {
+		cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
+				"Device freq interpolated based on CPU freq\n");
+	}
+	mutex_unlock(&state_lock);
+
+	return cnt;
+}
+
+static DEVICE_ATTR_RO(freq_map);
+static struct attribute *dev_attr[] = {
+	&dev_attr_freq_map.attr,
+	NULL,
+};
+
+static struct attribute_group dev_attr_group = {
+	.name = "cpufreq-map",
+	.attrs = dev_attr,
+};
+
+static int devfreq_cpufreq_gov_start(struct devfreq *devfreq)
+{
+	int ret = 0;
+	struct devfreq_node *node;
+	bool alloc = false;
+
+	ret = register_cpufreq();
+	if (ret)
+		return ret;
+
+	ret = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group);
+	if (ret) {
+		unregister_cpufreq();
+		return ret;
+	}
+
+	mutex_lock(&state_lock);
+
+	node = find_devfreq_node(devfreq->dev.parent);
+	if (node == NULL) {
+		node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
+		if (!node) {
+			ret = -ENOMEM;
+			goto alloc_fail;
+		}
+		alloc = true;
+		node->dev = devfreq->dev.parent;
+		list_add_tail(&node->list, &devfreq_list);
+	}
+	node->df = devfreq;
+	node->orig_data = devfreq->data;
+	devfreq->data = node;
+
+	mutex_lock(&devfreq->lock);
+	ret = update_devfreq(devfreq);
+	mutex_unlock(&devfreq->lock);
+	if (ret) {
+		pr_err("Freq update failed!\n");
+		goto update_fail;
+	}
+
+	mutex_unlock(&state_lock);
+	return 0;
+
+update_fail:
+	devfreq->data = node->orig_data;
+	if (alloc) {
+		list_del(&node->list);
+		kfree(node);
+	}
+alloc_fail:
+	mutex_unlock(&state_lock);
+	sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
+	unregister_cpufreq();
+	return ret;
+}
+
+static void devfreq_cpufreq_gov_stop(struct devfreq *devfreq)
+{
+	struct devfreq_node *node = devfreq->data;
+
+	mutex_lock(&state_lock);
+	devfreq->data = node->orig_data;
+	if (node->map || node->common_map) {
+		node->df = NULL;
+	} else {
+		list_del(&node->list);
+		kfree(node);
+	}
+	mutex_unlock(&state_lock);
+
+	sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
+	unregister_cpufreq();
+}
+
+static int devfreq_cpufreq_ev_handler(struct devfreq *devfreq,
+					unsigned int event, void *data)
+{
+	int ret;
+
+	switch (event) {
+	case DEVFREQ_GOV_START:
+
+		ret = devfreq_cpufreq_gov_start(devfreq);
+		if (ret) {
+			pr_err("Governor start failed!\n");
+			return ret;
+		}
+		pr_debug("Enabled CPUfreq-map governor\n");
+		break;
+
+	case DEVFREQ_GOV_STOP:
+
+		devfreq_cpufreq_gov_stop(devfreq);
+		pr_debug("Disabled dev CPUfreq-map governor\n");
+		break;
+	}
+
+	return 0;
+}
+
+static struct devfreq_governor devfreq_cpufreq = {
+	.name = "cpufreq-map",
+	.get_target_freq = devfreq_cpufreq_get_freq,
+	.event_handler = devfreq_cpufreq_ev_handler,
+};
+
+#define NUM_COLS	2
+static struct freq_map *read_tbl(struct device_node *of_node, char *prop_name)
+{
+	int len, nf, i, j;
+	u32 data;
+	struct freq_map *tbl;
+
+	if (!of_find_property(of_node, prop_name, &len))
+		return NULL;
+	len /= sizeof(data);
+
+	if (len % NUM_COLS || len == 0)
+		return NULL;
+	nf = len / NUM_COLS;
+
+	tbl = kzalloc((nf + 1) * sizeof(*tbl), GFP_KERNEL);
+	if (!tbl)
+		return NULL;
+
+	for (i = 0, j = 0; i < nf; i++, j += 2) {
+		of_property_read_u32_index(of_node, prop_name, j, &data);
+		tbl[i].cpu_khz = data;
+
+		of_property_read_u32_index(of_node, prop_name, j + 1, &data);
+		tbl[i].target_freq = data;
+	}
+	tbl[i].cpu_khz = 0;
+
+	return tbl;
+}
+
+#define PROP_TARGET "target-dev"
+#define PROP_TABLE "cpu-to-dev-map"
+static int add_table_from_of(struct device_node *of_node)
+{
+	struct device_node *target_of_node;
+	struct devfreq_node *node;
+	struct freq_map *common_tbl;
+	struct freq_map **tbl_list = NULL;
+	static char prop_name[] = PROP_TABLE "-999999";
+	int cpu, ret, cnt = 0, prop_sz = ARRAY_SIZE(prop_name);
+
+	target_of_node = of_parse_phandle(of_node, PROP_TARGET, 0);
+	if (!target_of_node)
+		return -EINVAL;
+
+	node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
+	if (!node)
+		return -ENOMEM;
+
+	common_tbl = read_tbl(of_node, PROP_TABLE);
+	if (!common_tbl) {
+		tbl_list = kzalloc(sizeof(*tbl_list) * NR_CPUS, GFP_KERNEL);
+		if (!tbl_list) {
+			ret = -ENOMEM;
+			goto err_list;
+		}
+
+		for_each_possible_cpu(cpu) {
+			ret = snprintf(prop_name, prop_sz, "%s-%d",
+					PROP_TABLE, cpu);
+			if (ret >= prop_sz) {
+				pr_warn("More CPUs than I can handle!\n");
+				pr_warn("Skipping rest of the tables!\n");
+				break;
+			}
+			tbl_list[cpu] = read_tbl(of_node, prop_name);
+			if (tbl_list[cpu])
+				cnt++;
+		}
+	}
+	if (!common_tbl && !cnt) {
+		ret = -EINVAL;
+		goto err_tbl;
+	}
+
+	mutex_lock(&state_lock);
+	node->of_node = target_of_node;
+	node->map = tbl_list;
+	node->common_map = common_tbl;
+	list_add_tail(&node->list, &devfreq_list);
+	mutex_unlock(&state_lock);
+
+	return 0;
+err_tbl:
+	kfree(tbl_list);
+err_list:
+	kfree(node);
+	return ret;
+}
+
+static int __init devfreq_cpufreq_init(void)
+{
+	int ret;
+	struct device_node *of_par, *of_child;
+
+	of_par = of_find_node_by_name(NULL, "devfreq-cpufreq-map");
+	if (of_par) {
+		for_each_child_of_node(of_par, of_child) {
+			ret = add_table_from_of(of_child);
+			if (ret)
+				pr_err("Parsing %s failed!\n", of_child->name);
+			else
+				pr_debug("Parsed %s.\n", of_child->name);
+		}
+		of_node_put(of_par);
+	} else {
+		pr_info("No tables parsed from DT.\n");
+	}
+
+	ret = devfreq_add_governor(&devfreq_cpufreq);
+	if (ret) {
+		pr_err("cpufreq-map governor add failed!\n");
+		return ret;
+	}
+
+	return 0;
+}
+subsys_initcall(devfreq_cpufreq_init);
+
+static void __exit devfreq_cpufreq_exit(void)
+{
+	int ret, cpu;
+	struct devfreq_node *node, *tmp;
+
+	ret = devfreq_remove_governor(&devfreq_cpufreq);
+	if (ret)
+		pr_err("cpufreq-map governor remove failed!\n");
+
+	mutex_lock(&state_lock);
+	list_for_each_entry_safe(node, tmp, &devfreq_list, list) {
+		kfree(node->common_map);
+		for_each_possible_cpu(cpu)
+			kfree(node->map[cpu]);
+		kfree(node->map);
+		list_del(&node->list);
+		kfree(node);
+	}
+	mutex_unlock(&state_lock);
+}
+module_exit(devfreq_cpufreq_exit);
+
+MODULE_DESCRIPTION("devfreq gov that sets dev freq based on current CPU freq");
+MODULE_LICENSE("GPL v2");
-- 
2.20.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH RFC 2/2] devfreq: mt8183-cci: using cpufreq-map governor in cci dvfs driver
  2019-06-18  4:14 [PATCH RFC 0/2] Use cpufreq-map governor for MT8183 CCI Hsin-Yi Wang
  2019-06-18  4:14 ` [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor Hsin-Yi Wang
@ 2019-06-18  4:14 ` Hsin-Yi Wang
  1 sibling, 0 replies; 8+ messages in thread
From: Hsin-Yi Wang @ 2019-06-18  4:14 UTC (permalink / raw)
  To: linux-arm-kernel
  Cc: Saravana Kannan, Andrew-sh . Cheng, linux-pm, linux-kernel,
	Chanwoo Choi, Kyungmin Park, MyungJoo Ham, linux-mediatek,
	Matthias Brugger

From: Hsin-Yi Wang <hsinyi@chromium.org>

This is based on mediatek's devfreq patches[1].

In MT8183 SoC, CCI and little core cluster share same regulator. In original
implementation, CCI frequency depends on regulator voltage, which results in
bad memory access performance if tasks are loaded on other cpus other than
little cluster (cpus 0-3).

Using cpufreq-map mt8183-cci's devfreq governor can improve this situation,
since in most cases, higher cpufreq implies higher loadings on the CCI, and CCI
should consider all cpu loadings instead of only the little cluster.

[1] https://patchwork.kernel.org/patch/10946063/

Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
---
 drivers/devfreq/mt8183-cci-devfreq.c | 194 +--------------------------
 1 file changed, 4 insertions(+), 190 deletions(-)

diff --git a/drivers/devfreq/mt8183-cci-devfreq.c b/drivers/devfreq/mt8183-cci-devfreq.c
index 250c963789f3..a153adee033e 100644
--- a/drivers/devfreq/mt8183-cci-devfreq.c
+++ b/drivers/devfreq/mt8183-cci-devfreq.c
@@ -17,164 +17,7 @@
 struct cci_devfreq {
 	struct devfreq *devfreq;
 	struct regulator *proc_reg;
-	unsigned long proc_reg_uV;
 	struct clk *cci_clk;
-	unsigned long freq;
-	struct notifier_block nb;
-	struct notifier_block opp_nb;
-	int cci_min_freq;
-};
-
-static int cci_devfreq_regulator_notifier(struct notifier_block *nb,
-					  unsigned long val, void *data)
-{
-	int ret;
-	struct cci_devfreq *cci_df =
-		container_of(nb, struct cci_devfreq, nb);
-
-	/* deal with reduce frequency */
-	if (val & REGULATOR_EVENT_PRE_VOLTAGE_CHANGE) {
-		struct pre_voltage_change_data *pvc_data = data;
-
-		if (pvc_data->min_uV < pvc_data->old_uV) {
-			cci_df->proc_reg_uV =
-				(unsigned long)(pvc_data->min_uV);
-			mutex_lock(&cci_df->devfreq->lock);
-			ret = update_devfreq(cci_df->devfreq);
-			if (ret)
-				pr_err("Fail to reduce cci frequency: %d\n",
-				       ret);
-			mutex_unlock(&cci_df->devfreq->lock);
-		}
-	} else if ((val & REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE) &&
-	    ((unsigned long)data > cci_df->proc_reg_uV)) {
-		cci_df->proc_reg_uV = (unsigned long)data;
-		mutex_lock(&cci_df->devfreq->lock);
-		ret = update_devfreq(cci_df->devfreq);
-		if (ret)
-			pr_err("Fail to raise cci frequency back: %d\n", ret);
-		mutex_unlock(&cci_df->devfreq->lock);
-	} else if ((val & REGULATOR_EVENT_VOLTAGE_CHANGE) &&
-	    (cci_df->proc_reg_uV < (unsigned long)data)) {
-		/* deal with increase frequency */
-		cci_df->proc_reg_uV = (unsigned long)data;
-		mutex_lock(&cci_df->devfreq->lock);
-		ret = update_devfreq(cci_df->devfreq);
-		if (ret)
-			pr_err("Fail to raise cci frequency: %d\n", ret);
-		mutex_unlock(&cci_df->devfreq->lock);
-	}
-
-	return 0;
-}
-
-static int ccidevfreq_opp_notifier(struct notifier_block *nb,
-unsigned long event, void *data)
-{
-	int ret;
-	struct dev_pm_opp *opp = data;
-	struct cci_devfreq *cci_df = container_of(nb, struct cci_devfreq,
-						  opp_nb);
-	unsigned long	freq, volt, cur_volt;
-
-	if (event == OPP_EVENT_ADJUST_VOLTAGE) {
-		freq = dev_pm_opp_get_freq(opp);
-		/* current opp item is changed */
-		if (freq == cci_df->freq) {
-			volt = dev_pm_opp_get_voltage(opp);
-			cur_volt = regulator_get_voltage(cci_df->proc_reg);
-
-			if (volt > cur_volt) {
-				/* need reduce freq */
-				mutex_lock(&cci_df->devfreq->lock);
-				ret = update_devfreq(cci_df->devfreq);
-				if (ret)
-					pr_err("Fail to reduce cci frequency by opp notification: %d\n",
-					       ret);
-				mutex_unlock(&cci_df->devfreq->lock);
-			}
-		}
-
-		if (freq == cci_df->cci_min_freq) {
-			volt = dev_pm_opp_get_voltage(opp);
-			regulator_set_voltage(cci_df->proc_reg, volt, INT_MAX);
-		}
-	} else if (event == OPP_EVENT_DISABLE) {
-	}
-
-	return 0;
-}
-
-
-static int mtk_cci_governor_get_target(struct devfreq *devfreq,
-				       unsigned long *freq)
-{
-	struct cci_devfreq *cci_df;
-	struct dev_pm_opp *opp;
-	int ret;
-
-	cci_df = dev_get_drvdata(devfreq->dev.parent);
-
-	/* find available frequency */
-	opp = dev_pm_opp_find_freq_ceil_by_volt(devfreq->dev.parent,
-						cci_df->proc_reg_uV);
-	ret = PTR_ERR_OR_ZERO(opp);
-	if (ret) {
-		pr_err("%s[%d], cannot find opp with voltage=%d: %d\n",
-		       __func__, __LINE__, cci_df->proc_reg_uV, ret);
-		return ret;
-	}
-	*freq = dev_pm_opp_get_freq(opp);
-
-	return 0;
-}
-
-static int mtk_cci_governor_event_handler(struct devfreq *devfreq,
-					  unsigned int event, void *data)
-{
-	int ret;
-	struct cci_devfreq *cci_df;
-	struct notifier_block *nb;
-	struct notifier_block *opp_nb;
-
-	cci_df = dev_get_drvdata(devfreq->dev.parent);
-	nb = &cci_df->nb;
-	opp_nb = &cci_df->opp_nb;
-
-	switch (event) {
-	case DEVFREQ_GOV_START:
-	case DEVFREQ_GOV_RESUME:
-		nb->notifier_call = cci_devfreq_regulator_notifier;
-		ret = regulator_register_notifier(cci_df->proc_reg,
-						  nb);
-		if (ret)
-			pr_err("%s: failed to add governor: %d\n", __func__,
-			       ret);
-		opp_nb->notifier_call = ccidevfreq_opp_notifier;
-		dev_pm_opp_register_notifier(devfreq->dev.parent, opp_nb);
-		break;
-
-	case DEVFREQ_GOV_STOP:
-	case DEVFREQ_GOV_SUSPEND:
-		ret = regulator_unregister_notifier(cci_df->proc_reg,
-						    nb);
-		if (ret)
-			pr_err("%s: failed to add governor: %d\n", __func__,
-			       ret);
-		break;
-
-	default:
-		break;
-	}
-
-	return 0;
-}
-
-static struct devfreq_governor mtk_cci_devfreq_governor = {
-	.name = "mtk_cci_vmon",
-	.get_target_freq = mtk_cci_governor_get_target,
-	.event_handler = mtk_cci_governor_event_handler,
-	.immutable = true
 };
 
 static int mtk_cci_devfreq_target(struct device *dev, unsigned long *freq,
@@ -193,8 +36,6 @@ static int mtk_cci_devfreq_target(struct device *dev, unsigned long *freq,
 		return ret;
 	}
 
-	cci_df->freq = *freq;
-
 	return 0;
 }
 
@@ -206,8 +47,6 @@ static int mtk_cci_devfreq_probe(struct platform_device *pdev)
 {
 	struct device *cci_dev = &pdev->dev;
 	struct cci_devfreq *cci_df;
-	unsigned long freq, volt;
-	struct dev_pm_opp *opp;
 	int ret;
 
 	cci_df = devm_kzalloc(cci_dev, sizeof(*cci_df), GFP_KERNEL);
@@ -237,19 +76,12 @@ static int mtk_cci_devfreq_probe(struct platform_device *pdev)
 		return ret;
 	}
 
-	/* set voltage lower bound */
-	freq = 1;
-	opp = dev_pm_opp_find_freq_ceil(cci_dev, &freq);
-	cci_df->cci_min_freq = dev_pm_opp_get_freq(opp);
-	volt = dev_pm_opp_get_voltage(opp);
-	dev_pm_opp_put(opp);
-
 	platform_set_drvdata(pdev, cci_df);
 
 	cci_df->devfreq = devm_devfreq_add_device(cci_dev,
-						  &cci_devfreq_profile,
-						  "mtk_cci_vmon",
-						  NULL);
+						&cci_devfreq_profile,
+						"cpufreq-map",
+						NULL);
 	if (IS_ERR(cci_df->devfreq)) {
 		ret = PTR_ERR(cci_df->devfreq);
 		dev_err(cci_dev, "cannot create cci devfreq device:%d\n", ret);
@@ -277,30 +109,12 @@ static struct platform_driver cci_devfreq_driver = {
 
 static int __init mtk_cci_devfreq_init(void)
 {
-	int ret;
-
-	ret = devfreq_add_governor(&mtk_cci_devfreq_governor);
-	if (ret) {
-		pr_err("%s: failed to add governor: %d\n", __func__, ret);
-		return ret;
-	}
-
-	ret = platform_driver_register(&cci_devfreq_driver);
-	if (ret)
-		devfreq_remove_governor(&mtk_cci_devfreq_governor);
-
-	return ret;
+	return platform_driver_register(&cci_devfreq_driver);
 }
 module_init(mtk_cci_devfreq_init)
 
 static void __exit mtk_cci_devfreq_exit(void)
 {
-	int ret;
-
-	ret = devfreq_remove_governor(&mtk_cci_devfreq_governor);
-	if (ret)
-		pr_err("%s: failed to remove governor: %d\n", __func__, ret);
-
 	platform_driver_unregister(&cci_devfreq_driver);
 }
 module_exit(mtk_cci_devfreq_exit)
-- 
2.20.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor
  2019-06-18  4:14 ` [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor Hsin-Yi Wang
@ 2019-06-20  6:58   ` Chanwoo Choi
  2019-06-20  9:32     ` Hsin-Yi Wang
  0 siblings, 1 reply; 8+ messages in thread
From: Chanwoo Choi @ 2019-06-20  6:58 UTC (permalink / raw)
  To: Hsin-Yi Wang, linux-arm-kernel, Saravana Kannan, Sibi Sankar
  Cc: Andrew-sh . Cheng, linux-pm, linux-kernel, Kyungmin Park,
	MyungJoo Ham, linux-mediatek, Matthias Brugger

+ Sibi Sankar

Hi, Hsin-Yi Wang, Saravana Kannan and Sibi Sankar

I summarized the history of the related patch about this title.

Firstly,
As I knew, Saravana sent the patch[1] which contains
'governor_cpufreq_map.c' last year. According to the Myungoo's comment,

Secondly,
Sibi Sankar modified the 'governor_passive.c'[2] in order to support
the mapping between cpu frequency and device frequency.
Unfortunately, Sibi Sankar stopped the development about this
because he had found the other method to get his purpose as I knew.

Thirdly,
Hsin-Yi Wang send the original patch of Saravana without modification.


Sincerely, I think that the mapping between cpu frequency and device
frequency is necessary. And I prefer the Sibi's approach which implements
stuff to the existing 'passive' governor.

We need to discuss about how to implement them by whom.


[1] [v3,1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor
- https://patchwork.kernel.org/patch/10553171/

[2]
[PATCH RFC 0/9] Add CPU based scaling support to Passive governor
- https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/
[PATCH RFC 3/9] PM / devfreq: Add cpu based scaling support to passive_governor
- https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/#m1cafb7baf687d2a680d39c85d3ec7d1b590b68fc


Best Regards,
Chanwoo Choi

On 19. 6. 18. 오후 1:14, Hsin-Yi Wang wrote:
> From: Saravana Kannan <skannan@codeaurora.org>
> 
> From: Saravana Kannan <skannan@codeaurora.org>
> 
> Many CPU architectures have caches that can scale independent of the CPUs.
> Frequency scaling of the caches is necessary to make sure the cache is not
> a performance bottleneck that leads to poor performance and power. The same
> idea applies for RAM/DDR.
> 
> To achieve this, this patch adds a generic devfreq governor that takes the
> current frequency of each CPU frequency domain and then adjusts the
> frequency of the cache (or any devfreq device) based on the frequency of
> the CPUs. It listens to CPU frequency transition notifiers to keep itself
> up to date on the current CPU frequency.
> 
> To decide the frequency of the device, the governor does one of the
> following:
> 
> * Uses a CPU frequency to device frequency mapping table
>   - Either one mapping table used for all CPU freq policies (typically used
>     for system with homogeneous cores/clusters that have the same OPPs).
>   - One mapping table per CPU freq policy (typically used for ASMP systems
>     with heterogeneous CPUs with different OPPs)
> 
> OR
> 
> * Scales the device frequency in proportion to the CPU frequency. So, if
>   the CPUs are running at their max frequency, the device runs at its max
>   frequency.  If the CPUs are running at their min frequency, the device
>   runs at its min frequency. And interpolated for frequencies in between.
> 
> Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
> Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
> ---
>  .../bindings/devfreq/devfreq-cpufreq-map.txt  |  53 ++
>  drivers/devfreq/Kconfig                       |   8 +
>  drivers/devfreq/Makefile                      |   1 +
>  drivers/devfreq/governor_cpufreq_map.c        | 583 ++++++++++++++++++
>  4 files changed, 645 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
>  create mode 100644 drivers/devfreq/governor_cpufreq_map.c
> 
> diff --git a/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
> new file mode 100644
> index 000000000000..982a30bcfc86
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
> @@ -0,0 +1,53 @@
> +Devfreq CPUfreq governor
> +
> +devfreq-cpufreq-map is a parent device that contains one or more child devices.
> +Each child device provides CPU frequency to device frequency mapping for a
> +specific device. Examples of devices that could use this are: DDR, cache and
> +CCI.
> +
> +Parent device name shall be "devfreq-cpufreq-map".
> +
> +Required child device properties:
> +- cpu-to-dev-map, or cpu-to-dev-map-<X>:
> +			A list of tuples where each tuple consists of a
> +			CPU frequency (KHz) and the corresponding device
> +			frequency. CPU frequencies not listed in the table
> +			will use the device frequency that corresponds to the
> +			next rounded up CPU frequency.
> +			Use "cpu-to-dev-map" if all CPUs in the system should
> +			share same mapping.
> +			Use cpu-to-dev-map-<cpuid> to describe different
> +			mappings for different CPUs. The property should be
> +			listed only for the first CPU if multiple CPUs are
> +			synchronous.
> +- target-dev:		Phandle to device that this mapping applies to.
> +
> +Example:
> +	devfreq-cpufreq-map {
> +		cpubw-cpufreq {
> +			target-dev = <&cpubw>;
> +			cpu-to-dev-map =
> +				<  300000  1144000 >,
> +				<  422400  2288000 >,
> +				<  652800  3051000 >,
> +				<  883200  5996000 >,
> +				< 1190400  8056000 >,
> +				< 1497600 10101000 >,
> +				< 1728000 12145000 >,
> +				< 2649600 16250000 >;
> +		};
> +
> +		cache-cpufreq {
> +			target-dev = <&cache>;
> +			cpu-to-dev-map =
> +				<  300000  300000 >,
> +				<  422400  422400 >,
> +				<  652800  499200 >,
> +				<  883200  576000 >,
> +				<  960000  960000 >,
> +				< 1497600 1036800 >,
> +				< 1574400 1574400 >,
> +				< 1728000 1651200 >,
> +				< 2649600 1728000 >;
> +		};
> +	};
> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
> index 0c8204d6b78a..0303f5a400b6 100644
> --- a/drivers/devfreq/Kconfig
> +++ b/drivers/devfreq/Kconfig
> @@ -74,6 +74,14 @@ config DEVFREQ_GOV_PASSIVE
>  	  through sysfs entries. The passive governor recommends that
>  	  devfreq device uses the OPP table to get the frequency/voltage.
>  
> +config DEVFREQ_GOV_CPUFREQ_MAP
> +	tristate "CPUfreq Map"
> +	depends on CPU_FREQ
> +	help
> +	  Chooses frequency based on the online CPUs' current frequency and a
> +	  CPU frequency to device frequency mapping table(s). This governor
> +	  can be useful for controlling devices such as DDR, cache, CCI, etc.
> +
>  comment "DEVFREQ Drivers"
>  
>  config ARM_EXYNOS_BUS_DEVFREQ
> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
> index 817dde779f16..81141e2c784f 100644
> --- a/drivers/devfreq/Makefile
> +++ b/drivers/devfreq/Makefile
> @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE)	+= governor_performance.o
>  obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)	+= governor_powersave.o
>  obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)	+= governor_userspace.o
>  obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)	+= governor_passive.o
> +obj-$(CONFIG_DEVFREQ_GOV_CPUFREQ_MAP)	+= governor_cpufreq_map.o
>  
>  # DEVFREQ Drivers
>  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ)	+= exynos-bus.o
> diff --git a/drivers/devfreq/governor_cpufreq_map.c b/drivers/devfreq/governor_cpufreq_map.c
> new file mode 100644
> index 000000000000..084a3ffb8f54
> --- /dev/null
> +++ b/drivers/devfreq/governor_cpufreq_map.c
> @@ -0,0 +1,583 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2014-2015, 2018, The Linux Foundation. All rights reserved.
> + */
> +
> +#define pr_fmt(fmt) "dev-cpufreq-map: " fmt
> +
> +#include <linux/devfreq.h>
> +#include <linux/cpu.h>
> +#include <linux/cpufreq.h>
> +#include <linux/cpumask.h>
> +#include <linux/slab.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/module.h>
> +#include "governor.h"
> +
> +struct cpu_state {
> +	unsigned int freq;
> +	unsigned int min_freq;
> +	unsigned int max_freq;
> +	unsigned int first_cpu;
> +};
> +static struct cpu_state *state[NR_CPUS];
> +static int cpufreq_cnt;
> +
> +struct freq_map {
> +	unsigned int cpu_khz;
> +	unsigned int target_freq;
> +};
> +
> +struct devfreq_node {
> +	struct devfreq *df;
> +	void *orig_data;
> +	struct device *dev;
> +	struct device_node *of_node;
> +	struct list_head list;
> +	struct freq_map **map;
> +	struct freq_map *common_map;
> +};
> +static LIST_HEAD(devfreq_list);
> +static DEFINE_MUTEX(state_lock);
> +static DEFINE_MUTEX(cpufreq_reg_lock);
> +
> +static void update_all_devfreqs(void)
> +{
> +	struct devfreq_node *node;
> +
> +	list_for_each_entry(node, &devfreq_list, list) {
> +		struct devfreq *df = node->df;
> +
> +		if (!node->df)
> +			continue;
> +		mutex_lock(&df->lock);
> +		update_devfreq(df);
> +		mutex_unlock(&df->lock);
> +
> +	}
> +}
> +
> +static struct devfreq_node *find_devfreq_node(struct device *dev)
> +{
> +	struct devfreq_node *node;
> +
> +	list_for_each_entry(node, &devfreq_list, list)
> +		if (node->dev == dev || node->of_node == dev->of_node)
> +			return node;
> +
> +	return NULL;
> +}
> +
> +/* ==================== cpufreq part ==================== */
> +static struct cpu_state *add_policy(struct cpufreq_policy *policy)
> +{
> +	struct cpu_state *new_state;
> +	unsigned int cpu, first_cpu;
> +
> +	new_state = kzalloc(sizeof(struct cpu_state), GFP_KERNEL);
> +	if (!new_state)
> +		return NULL;
> +
> +	first_cpu = cpumask_first(policy->related_cpus);
> +	new_state->first_cpu = first_cpu;
> +	new_state->freq = policy->cur;
> +	new_state->min_freq = policy->cpuinfo.min_freq;
> +	new_state->max_freq = policy->cpuinfo.max_freq;
> +
> +	for_each_cpu(cpu, policy->related_cpus)
> +		state[cpu] = new_state;
> +
> +	return new_state;
> +}
> +
> +static int cpufreq_trans_notifier(struct notifier_block *nb,
> +		unsigned long event, void *data)
> +{
> +	struct cpufreq_freqs *freq = data;
> +	struct cpu_state *s;
> +	struct cpufreq_policy *policy = NULL;
> +
> +	if (event != CPUFREQ_POSTCHANGE)
> +		return 0;
> +
> +	mutex_lock(&state_lock);
> +
> +	s = state[freq->cpu];
> +	if (!s) {
> +		policy = cpufreq_cpu_get(freq->cpu);
> +		if (policy) {
> +			s = add_policy(policy);
> +			cpufreq_cpu_put(policy);
> +		}
> +	}
> +	if (!s)
> +		goto out;
> +
> +	if (s->freq != freq->new || policy) {
> +		s->freq = freq->new;
> +		update_all_devfreqs();
> +	}
> +
> +out:
> +	mutex_unlock(&state_lock);
> +	return 0;
> +}
> +
> +static struct notifier_block cpufreq_trans_nb = {
> +	.notifier_call = cpufreq_trans_notifier
> +};
> +
> +static int register_cpufreq(void)
> +{
> +	int ret = 0;
> +	unsigned int cpu;
> +	struct cpufreq_policy *policy;
> +
> +	mutex_lock(&cpufreq_reg_lock);
> +
> +	if (cpufreq_cnt)
> +		goto cnt_not_zero;
> +
> +	get_online_cpus();
> +	ret = cpufreq_register_notifier(&cpufreq_trans_nb,
> +				CPUFREQ_TRANSITION_NOTIFIER);
> +	if (ret)
> +		goto out;
> +
> +	for_each_online_cpu(cpu) {
> +		policy = cpufreq_cpu_get(cpu);
> +		if (policy) {
> +			add_policy(policy);
> +			cpufreq_cpu_put(policy);
> +		}
> +	}
> +out:
> +	put_online_cpus();
> +cnt_not_zero:
> +	if (!ret)
> +		cpufreq_cnt++;
> +	mutex_unlock(&cpufreq_reg_lock);
> +	return ret;
> +}
> +
> +static int unregister_cpufreq(void)
> +{
> +	int ret = 0;
> +	int cpu;
> +
> +	mutex_lock(&cpufreq_reg_lock);
> +
> +	if (cpufreq_cnt > 1)
> +		goto out;
> +
> +	cpufreq_unregister_notifier(&cpufreq_trans_nb,
> +				CPUFREQ_TRANSITION_NOTIFIER);
> +
> +	for (cpu = ARRAY_SIZE(state) - 1; cpu >= 0; cpu--) {
> +		if (!state[cpu])
> +			continue;
> +		if (state[cpu]->first_cpu == cpu)
> +			kfree(state[cpu]);
> +		state[cpu] = NULL;
> +	}
> +
> +out:
> +	cpufreq_cnt--;
> +	mutex_unlock(&cpufreq_reg_lock);
> +	return ret;
> +}
> +
> +/* ==================== devfreq part ==================== */
> +
> +static unsigned int interpolate_freq(struct devfreq *df, unsigned int cpu)
> +{
> +	unsigned long *freq_table = df->profile->freq_table;
> +	unsigned int cpu_min = state[cpu]->min_freq;
> +	unsigned int cpu_max = state[cpu]->max_freq;
> +	unsigned int cpu_freq = state[cpu]->freq;
> +	unsigned int dev_min, dev_max, cpu_percent;
> +
> +	if (freq_table) {
> +		dev_min = freq_table[0];
> +		dev_max = freq_table[df->profile->max_state - 1];
> +	} else {
> +		if (df->max_freq <= df->min_freq)
> +			return 0;
> +		dev_min = df->min_freq;
> +		dev_max = df->max_freq;
> +	}
> +
> +	cpu_percent = ((cpu_freq - cpu_min) * 100) / (cpu_max - cpu_min);
> +	return dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100);
> +}
> +
> +static unsigned int cpu_to_dev_freq(struct devfreq *df, unsigned int cpu)
> +{
> +	struct freq_map *map = NULL;
> +	unsigned int cpu_khz = 0, freq;
> +	struct devfreq_node *n = df->data;
> +
> +	if (!state[cpu] || state[cpu]->first_cpu != cpu) {
> +		freq = 0;
> +		goto out;
> +	}
> +
> +	if (n->common_map)
> +		map = n->common_map;
> +	else if (n->map)
> +		map = n->map[cpu];
> +
> +	cpu_khz = state[cpu]->freq;
> +
> +	if (!map) {
> +		freq = interpolate_freq(df, cpu);
> +		goto out;
> +	}
> +
> +	while (map->cpu_khz && map->cpu_khz < cpu_khz)
> +		map++;
> +	if (!map->cpu_khz)
> +		map--;
> +	freq = map->target_freq;
> +
> +out:
> +	dev_dbg(df->dev.parent, "CPU%u: %d -> dev: %u\n", cpu, cpu_khz, freq);
> +	return freq;
> +}
> +
> +static int devfreq_cpufreq_get_freq(struct devfreq *df,
> +					unsigned long *freq)
> +{
> +	unsigned int cpu, tgt_freq = 0;
> +	struct devfreq_node *node;
> +
> +	node = df->data;
> +	if (!node) {
> +		pr_err("Unable to find devfreq node!\n");
> +		return -ENODEV;
> +	}
> +
> +	for_each_possible_cpu(cpu)
> +		tgt_freq = max(tgt_freq, cpu_to_dev_freq(df, cpu));
> +
> +	*freq = tgt_freq;
> +	return 0;
> +}
> +
> +static unsigned int show_table(char *buf, unsigned int len,
> +				struct freq_map *map)
> +{
> +	unsigned int cnt = 0;
> +
> +	cnt += snprintf(buf + cnt, len - cnt, "CPU freq\tDevice freq\n");
> +
> +	while (map->cpu_khz && cnt < len) {
> +		cnt += snprintf(buf + cnt, len - cnt, "%8u\t%11u\n",
> +				map->cpu_khz, map->target_freq);
> +		map++;
> +	}
> +	if (cnt < len)
> +		cnt += snprintf(buf + cnt, len - cnt, "\n");
> +
> +	return cnt;
> +}
> +
> +static ssize_t freq_map_show(struct device *dev, struct device_attribute *attr,
> +			char *buf)
> +{
> +	struct devfreq *df = to_devfreq(dev);
> +	struct devfreq_node *n = df->data;
> +	struct freq_map *map;
> +	unsigned int cnt = 0, cpu;
> +
> +	mutex_lock(&state_lock);
> +	if (n->common_map) {
> +		map = n->common_map;
> +		cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> +				"Common table for all CPUs:\n");
> +		cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
> +	} else if (n->map) {
> +		for_each_possible_cpu(cpu) {
> +			map = n->map[cpu];
> +			if (!map)
> +				continue;
> +			cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> +					"CPU %u:\n", cpu);
> +			if (cnt >= PAGE_SIZE)
> +				break;
> +			cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
> +			if (cnt >= PAGE_SIZE)
> +				break;
> +		}
> +	} else {
> +		cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> +				"Device freq interpolated based on CPU freq\n");
> +	}
> +	mutex_unlock(&state_lock);
> +
> +	return cnt;
> +}
> +
> +static DEVICE_ATTR_RO(freq_map);
> +static struct attribute *dev_attr[] = {
> +	&dev_attr_freq_map.attr,
> +	NULL,
> +};
> +
> +static struct attribute_group dev_attr_group = {
> +	.name = "cpufreq-map",
> +	.attrs = dev_attr,
> +};
> +
> +static int devfreq_cpufreq_gov_start(struct devfreq *devfreq)
> +{
> +	int ret = 0;
> +	struct devfreq_node *node;
> +	bool alloc = false;
> +
> +	ret = register_cpufreq();
> +	if (ret)
> +		return ret;
> +
> +	ret = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group);
> +	if (ret) {
> +		unregister_cpufreq();
> +		return ret;
> +	}
> +
> +	mutex_lock(&state_lock);
> +
> +	node = find_devfreq_node(devfreq->dev.parent);
> +	if (node == NULL) {
> +		node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
> +		if (!node) {
> +			ret = -ENOMEM;
> +			goto alloc_fail;
> +		}
> +		alloc = true;
> +		node->dev = devfreq->dev.parent;
> +		list_add_tail(&node->list, &devfreq_list);
> +	}
> +	node->df = devfreq;
> +	node->orig_data = devfreq->data;
> +	devfreq->data = node;
> +
> +	mutex_lock(&devfreq->lock);
> +	ret = update_devfreq(devfreq);
> +	mutex_unlock(&devfreq->lock);
> +	if (ret) {
> +		pr_err("Freq update failed!\n");
> +		goto update_fail;
> +	}
> +
> +	mutex_unlock(&state_lock);
> +	return 0;
> +
> +update_fail:
> +	devfreq->data = node->orig_data;
> +	if (alloc) {
> +		list_del(&node->list);
> +		kfree(node);
> +	}
> +alloc_fail:
> +	mutex_unlock(&state_lock);
> +	sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
> +	unregister_cpufreq();
> +	return ret;
> +}
> +
> +static void devfreq_cpufreq_gov_stop(struct devfreq *devfreq)
> +{
> +	struct devfreq_node *node = devfreq->data;
> +
> +	mutex_lock(&state_lock);
> +	devfreq->data = node->orig_data;
> +	if (node->map || node->common_map) {
> +		node->df = NULL;
> +	} else {
> +		list_del(&node->list);
> +		kfree(node);
> +	}
> +	mutex_unlock(&state_lock);
> +
> +	sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
> +	unregister_cpufreq();
> +}
> +
> +static int devfreq_cpufreq_ev_handler(struct devfreq *devfreq,
> +					unsigned int event, void *data)
> +{
> +	int ret;
> +
> +	switch (event) {
> +	case DEVFREQ_GOV_START:
> +
> +		ret = devfreq_cpufreq_gov_start(devfreq);
> +		if (ret) {
> +			pr_err("Governor start failed!\n");
> +			return ret;
> +		}
> +		pr_debug("Enabled CPUfreq-map governor\n");
> +		break;
> +
> +	case DEVFREQ_GOV_STOP:
> +
> +		devfreq_cpufreq_gov_stop(devfreq);
> +		pr_debug("Disabled dev CPUfreq-map governor\n");
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct devfreq_governor devfreq_cpufreq = {
> +	.name = "cpufreq-map",
> +	.get_target_freq = devfreq_cpufreq_get_freq,
> +	.event_handler = devfreq_cpufreq_ev_handler,
> +};
> +
> +#define NUM_COLS	2
> +static struct freq_map *read_tbl(struct device_node *of_node, char *prop_name)
> +{
> +	int len, nf, i, j;
> +	u32 data;
> +	struct freq_map *tbl;
> +
> +	if (!of_find_property(of_node, prop_name, &len))
> +		return NULL;
> +	len /= sizeof(data);
> +
> +	if (len % NUM_COLS || len == 0)
> +		return NULL;
> +	nf = len / NUM_COLS;
> +
> +	tbl = kzalloc((nf + 1) * sizeof(*tbl), GFP_KERNEL);
> +	if (!tbl)
> +		return NULL;
> +
> +	for (i = 0, j = 0; i < nf; i++, j += 2) {
> +		of_property_read_u32_index(of_node, prop_name, j, &data);
> +		tbl[i].cpu_khz = data;
> +
> +		of_property_read_u32_index(of_node, prop_name, j + 1, &data);
> +		tbl[i].target_freq = data;
> +	}
> +	tbl[i].cpu_khz = 0;
> +
> +	return tbl;
> +}
> +
> +#define PROP_TARGET "target-dev"
> +#define PROP_TABLE "cpu-to-dev-map"
> +static int add_table_from_of(struct device_node *of_node)
> +{
> +	struct device_node *target_of_node;
> +	struct devfreq_node *node;
> +	struct freq_map *common_tbl;
> +	struct freq_map **tbl_list = NULL;
> +	static char prop_name[] = PROP_TABLE "-999999";
> +	int cpu, ret, cnt = 0, prop_sz = ARRAY_SIZE(prop_name);
> +
> +	target_of_node = of_parse_phandle(of_node, PROP_TARGET, 0);
> +	if (!target_of_node)
> +		return -EINVAL;
> +
> +	node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
> +	if (!node)
> +		return -ENOMEM;
> +
> +	common_tbl = read_tbl(of_node, PROP_TABLE);
> +	if (!common_tbl) {
> +		tbl_list = kzalloc(sizeof(*tbl_list) * NR_CPUS, GFP_KERNEL);
> +		if (!tbl_list) {
> +			ret = -ENOMEM;
> +			goto err_list;
> +		}
> +
> +		for_each_possible_cpu(cpu) {
> +			ret = snprintf(prop_name, prop_sz, "%s-%d",
> +					PROP_TABLE, cpu);
> +			if (ret >= prop_sz) {
> +				pr_warn("More CPUs than I can handle!\n");
> +				pr_warn("Skipping rest of the tables!\n");
> +				break;
> +			}
> +			tbl_list[cpu] = read_tbl(of_node, prop_name);
> +			if (tbl_list[cpu])
> +				cnt++;
> +		}
> +	}
> +	if (!common_tbl && !cnt) {
> +		ret = -EINVAL;
> +		goto err_tbl;
> +	}
> +
> +	mutex_lock(&state_lock);
> +	node->of_node = target_of_node;
> +	node->map = tbl_list;
> +	node->common_map = common_tbl;
> +	list_add_tail(&node->list, &devfreq_list);
> +	mutex_unlock(&state_lock);
> +
> +	return 0;
> +err_tbl:
> +	kfree(tbl_list);
> +err_list:
> +	kfree(node);
> +	return ret;
> +}
> +
> +static int __init devfreq_cpufreq_init(void)
> +{
> +	int ret;
> +	struct device_node *of_par, *of_child;
> +
> +	of_par = of_find_node_by_name(NULL, "devfreq-cpufreq-map");
> +	if (of_par) {
> +		for_each_child_of_node(of_par, of_child) {
> +			ret = add_table_from_of(of_child);
> +			if (ret)
> +				pr_err("Parsing %s failed!\n", of_child->name);
> +			else
> +				pr_debug("Parsed %s.\n", of_child->name);
> +		}
> +		of_node_put(of_par);
> +	} else {
> +		pr_info("No tables parsed from DT.\n");
> +	}
> +
> +	ret = devfreq_add_governor(&devfreq_cpufreq);
> +	if (ret) {
> +		pr_err("cpufreq-map governor add failed!\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +subsys_initcall(devfreq_cpufreq_init);
> +
> +static void __exit devfreq_cpufreq_exit(void)
> +{
> +	int ret, cpu;
> +	struct devfreq_node *node, *tmp;
> +
> +	ret = devfreq_remove_governor(&devfreq_cpufreq);
> +	if (ret)
> +		pr_err("cpufreq-map governor remove failed!\n");
> +
> +	mutex_lock(&state_lock);
> +	list_for_each_entry_safe(node, tmp, &devfreq_list, list) {
> +		kfree(node->common_map);
> +		for_each_possible_cpu(cpu)
> +			kfree(node->map[cpu]);
> +		kfree(node->map);
> +		list_del(&node->list);
> +		kfree(node);
> +	}
> +	mutex_unlock(&state_lock);
> +}
> +module_exit(devfreq_cpufreq_exit);
> +
> +MODULE_DESCRIPTION("devfreq gov that sets dev freq based on current CPU freq");
> +MODULE_LICENSE("GPL v2");
> 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor
  2019-06-20  6:58   ` Chanwoo Choi
@ 2019-06-20  9:32     ` Hsin-Yi Wang
  2019-06-20  9:41       ` Sibi Sankar
  0 siblings, 1 reply; 8+ messages in thread
From: Hsin-Yi Wang @ 2019-06-20  9:32 UTC (permalink / raw)
  To: Chanwoo Choi
  Cc: Saravana Kannan, Andrew-sh . Cheng, linux-pm, lkml,
	Kyungmin Park, MyungJoo Ham, linux-mediatek, Sibi Sankar,
	Matthias Brugger,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE

Hi Chanwoo Choi, Saravana Kannan and Sibi Sankar,

I've also tested Sibi Sankar's patch[1] locally with mt8183-cci, and
it works fine too!
It'd be great if Sibi Sankar or anyone who is familiar with the
original design can finish this implementation. But if no one has time
to do that, I think I can also help on address the comments. Thanks!


[1]
[RFC,2/9] OPP: Export a number of helpers to prevent code duplication
- https://patchwork.kernel.org/patch/10875199/
[RFC,3/9] PM / devfreq: Add cpu based scaling support to passive_governor
- https://patchwork.kernel.org/patch/10875195/

Hsin-Yi

On Thu, Jun 20, 2019 at 2:56 PM Chanwoo Choi <cw00.choi@samsung.com> wrote:
>
> + Sibi Sankar
>
> Hi, Hsin-Yi Wang, Saravana Kannan and Sibi Sankar
>
> I summarized the history of the related patch about this title.
>
> Firstly,
> As I knew, Saravana sent the patch[1] which contains
> 'governor_cpufreq_map.c' last year. According to the Myungoo's comment,
>
> Secondly,
> Sibi Sankar modified the 'governor_passive.c'[2] in order to support
> the mapping between cpu frequency and device frequency.
> Unfortunately, Sibi Sankar stopped the development about this
> because he had found the other method to get his purpose as I knew.
>
> Thirdly,
> Hsin-Yi Wang send the original patch of Saravana without modification.
>
>
> Sincerely, I think that the mapping between cpu frequency and device
> frequency is necessary. And I prefer the Sibi's approach which implements
> stuff to the existing 'passive' governor.
>
> We need to discuss about how to implement them by whom.
>
>
> [1] [v3,1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor
> - https://patchwork.kernel.org/patch/10553171/
>
> [2]
> [PATCH RFC 0/9] Add CPU based scaling support to Passive governor
> - https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/
> [PATCH RFC 3/9] PM / devfreq: Add cpu based scaling support to passive_governor
> - https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/#m1cafb7baf687d2a680d39c85d3ec7d1b590b68fc
>
>
> Best Regards,
> Chanwoo Choi
>
> On 19. 6. 18. 오후 1:14, Hsin-Yi Wang wrote:
> > From: Saravana Kannan <skannan@codeaurora.org>
> >
> > From: Saravana Kannan <skannan@codeaurora.org>
> >
> > Many CPU architectures have caches that can scale independent of the CPUs.
> > Frequency scaling of the caches is necessary to make sure the cache is not
> > a performance bottleneck that leads to poor performance and power. The same
> > idea applies for RAM/DDR.
> >
> > To achieve this, this patch adds a generic devfreq governor that takes the
> > current frequency of each CPU frequency domain and then adjusts the
> > frequency of the cache (or any devfreq device) based on the frequency of
> > the CPUs. It listens to CPU frequency transition notifiers to keep itself
> > up to date on the current CPU frequency.
> >
> > To decide the frequency of the device, the governor does one of the
> > following:
> >
> > * Uses a CPU frequency to device frequency mapping table
> >   - Either one mapping table used for all CPU freq policies (typically used
> >     for system with homogeneous cores/clusters that have the same OPPs).
> >   - One mapping table per CPU freq policy (typically used for ASMP systems
> >     with heterogeneous CPUs with different OPPs)
> >
> > OR
> >
> > * Scales the device frequency in proportion to the CPU frequency. So, if
> >   the CPUs are running at their max frequency, the device runs at its max
> >   frequency.  If the CPUs are running at their min frequency, the device
> >   runs at its min frequency. And interpolated for frequencies in between.
> >
> > Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
> > Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
> > ---
> >  .../bindings/devfreq/devfreq-cpufreq-map.txt  |  53 ++
> >  drivers/devfreq/Kconfig                       |   8 +
> >  drivers/devfreq/Makefile                      |   1 +
> >  drivers/devfreq/governor_cpufreq_map.c        | 583 ++++++++++++++++++
> >  4 files changed, 645 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
> >  create mode 100644 drivers/devfreq/governor_cpufreq_map.c
> >
> > diff --git a/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
> > new file mode 100644
> > index 000000000000..982a30bcfc86
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
> > @@ -0,0 +1,53 @@
> > +Devfreq CPUfreq governor
> > +
> > +devfreq-cpufreq-map is a parent device that contains one or more child devices.
> > +Each child device provides CPU frequency to device frequency mapping for a
> > +specific device. Examples of devices that could use this are: DDR, cache and
> > +CCI.
> > +
> > +Parent device name shall be "devfreq-cpufreq-map".
> > +
> > +Required child device properties:
> > +- cpu-to-dev-map, or cpu-to-dev-map-<X>:
> > +                     A list of tuples where each tuple consists of a
> > +                     CPU frequency (KHz) and the corresponding device
> > +                     frequency. CPU frequencies not listed in the table
> > +                     will use the device frequency that corresponds to the
> > +                     next rounded up CPU frequency.
> > +                     Use "cpu-to-dev-map" if all CPUs in the system should
> > +                     share same mapping.
> > +                     Use cpu-to-dev-map-<cpuid> to describe different
> > +                     mappings for different CPUs. The property should be
> > +                     listed only for the first CPU if multiple CPUs are
> > +                     synchronous.
> > +- target-dev:                Phandle to device that this mapping applies to.
> > +
> > +Example:
> > +     devfreq-cpufreq-map {
> > +             cpubw-cpufreq {
> > +                     target-dev = <&cpubw>;
> > +                     cpu-to-dev-map =
> > +                             <  300000  1144000 >,
> > +                             <  422400  2288000 >,
> > +                             <  652800  3051000 >,
> > +                             <  883200  5996000 >,
> > +                             < 1190400  8056000 >,
> > +                             < 1497600 10101000 >,
> > +                             < 1728000 12145000 >,
> > +                             < 2649600 16250000 >;
> > +             };
> > +
> > +             cache-cpufreq {
> > +                     target-dev = <&cache>;
> > +                     cpu-to-dev-map =
> > +                             <  300000  300000 >,
> > +                             <  422400  422400 >,
> > +                             <  652800  499200 >,
> > +                             <  883200  576000 >,
> > +                             <  960000  960000 >,
> > +                             < 1497600 1036800 >,
> > +                             < 1574400 1574400 >,
> > +                             < 1728000 1651200 >,
> > +                             < 2649600 1728000 >;
> > +             };
> > +     };
> > diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
> > index 0c8204d6b78a..0303f5a400b6 100644
> > --- a/drivers/devfreq/Kconfig
> > +++ b/drivers/devfreq/Kconfig
> > @@ -74,6 +74,14 @@ config DEVFREQ_GOV_PASSIVE
> >         through sysfs entries. The passive governor recommends that
> >         devfreq device uses the OPP table to get the frequency/voltage.
> >
> > +config DEVFREQ_GOV_CPUFREQ_MAP
> > +     tristate "CPUfreq Map"
> > +     depends on CPU_FREQ
> > +     help
> > +       Chooses frequency based on the online CPUs' current frequency and a
> > +       CPU frequency to device frequency mapping table(s). This governor
> > +       can be useful for controlling devices such as DDR, cache, CCI, etc.
> > +
> >  comment "DEVFREQ Drivers"
> >
> >  config ARM_EXYNOS_BUS_DEVFREQ
> > diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
> > index 817dde779f16..81141e2c784f 100644
> > --- a/drivers/devfreq/Makefile
> > +++ b/drivers/devfreq/Makefile
> > @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o
> >  obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)  += governor_powersave.o
> >  obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)  += governor_userspace.o
> >  obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)    += governor_passive.o
> > +obj-$(CONFIG_DEVFREQ_GOV_CPUFREQ_MAP)        += governor_cpufreq_map.o
> >
> >  # DEVFREQ Drivers
> >  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
> > diff --git a/drivers/devfreq/governor_cpufreq_map.c b/drivers/devfreq/governor_cpufreq_map.c
> > new file mode 100644
> > index 000000000000..084a3ffb8f54
> > --- /dev/null
> > +++ b/drivers/devfreq/governor_cpufreq_map.c
> > @@ -0,0 +1,583 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2014-2015, 2018, The Linux Foundation. All rights reserved.
> > + */
> > +
> > +#define pr_fmt(fmt) "dev-cpufreq-map: " fmt
> > +
> > +#include <linux/devfreq.h>
> > +#include <linux/cpu.h>
> > +#include <linux/cpufreq.h>
> > +#include <linux/cpumask.h>
> > +#include <linux/slab.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/of.h>
> > +#include <linux/module.h>
> > +#include "governor.h"
> > +
> > +struct cpu_state {
> > +     unsigned int freq;
> > +     unsigned int min_freq;
> > +     unsigned int max_freq;
> > +     unsigned int first_cpu;
> > +};
> > +static struct cpu_state *state[NR_CPUS];
> > +static int cpufreq_cnt;
> > +
> > +struct freq_map {
> > +     unsigned int cpu_khz;
> > +     unsigned int target_freq;
> > +};
> > +
> > +struct devfreq_node {
> > +     struct devfreq *df;
> > +     void *orig_data;
> > +     struct device *dev;
> > +     struct device_node *of_node;
> > +     struct list_head list;
> > +     struct freq_map **map;
> > +     struct freq_map *common_map;
> > +};
> > +static LIST_HEAD(devfreq_list);
> > +static DEFINE_MUTEX(state_lock);
> > +static DEFINE_MUTEX(cpufreq_reg_lock);
> > +
> > +static void update_all_devfreqs(void)
> > +{
> > +     struct devfreq_node *node;
> > +
> > +     list_for_each_entry(node, &devfreq_list, list) {
> > +             struct devfreq *df = node->df;
> > +
> > +             if (!node->df)
> > +                     continue;
> > +             mutex_lock(&df->lock);
> > +             update_devfreq(df);
> > +             mutex_unlock(&df->lock);
> > +
> > +     }
> > +}
> > +
> > +static struct devfreq_node *find_devfreq_node(struct device *dev)
> > +{
> > +     struct devfreq_node *node;
> > +
> > +     list_for_each_entry(node, &devfreq_list, list)
> > +             if (node->dev == dev || node->of_node == dev->of_node)
> > +                     return node;
> > +
> > +     return NULL;
> > +}
> > +
> > +/* ==================== cpufreq part ==================== */
> > +static struct cpu_state *add_policy(struct cpufreq_policy *policy)
> > +{
> > +     struct cpu_state *new_state;
> > +     unsigned int cpu, first_cpu;
> > +
> > +     new_state = kzalloc(sizeof(struct cpu_state), GFP_KERNEL);
> > +     if (!new_state)
> > +             return NULL;
> > +
> > +     first_cpu = cpumask_first(policy->related_cpus);
> > +     new_state->first_cpu = first_cpu;
> > +     new_state->freq = policy->cur;
> > +     new_state->min_freq = policy->cpuinfo.min_freq;
> > +     new_state->max_freq = policy->cpuinfo.max_freq;
> > +
> > +     for_each_cpu(cpu, policy->related_cpus)
> > +             state[cpu] = new_state;
> > +
> > +     return new_state;
> > +}
> > +
> > +static int cpufreq_trans_notifier(struct notifier_block *nb,
> > +             unsigned long event, void *data)
> > +{
> > +     struct cpufreq_freqs *freq = data;
> > +     struct cpu_state *s;
> > +     struct cpufreq_policy *policy = NULL;
> > +
> > +     if (event != CPUFREQ_POSTCHANGE)
> > +             return 0;
> > +
> > +     mutex_lock(&state_lock);
> > +
> > +     s = state[freq->cpu];
> > +     if (!s) {
> > +             policy = cpufreq_cpu_get(freq->cpu);
> > +             if (policy) {
> > +                     s = add_policy(policy);
> > +                     cpufreq_cpu_put(policy);
> > +             }
> > +     }
> > +     if (!s)
> > +             goto out;
> > +
> > +     if (s->freq != freq->new || policy) {
> > +             s->freq = freq->new;
> > +             update_all_devfreqs();
> > +     }
> > +
> > +out:
> > +     mutex_unlock(&state_lock);
> > +     return 0;
> > +}
> > +
> > +static struct notifier_block cpufreq_trans_nb = {
> > +     .notifier_call = cpufreq_trans_notifier
> > +};
> > +
> > +static int register_cpufreq(void)
> > +{
> > +     int ret = 0;
> > +     unsigned int cpu;
> > +     struct cpufreq_policy *policy;
> > +
> > +     mutex_lock(&cpufreq_reg_lock);
> > +
> > +     if (cpufreq_cnt)
> > +             goto cnt_not_zero;
> > +
> > +     get_online_cpus();
> > +     ret = cpufreq_register_notifier(&cpufreq_trans_nb,
> > +                             CPUFREQ_TRANSITION_NOTIFIER);
> > +     if (ret)
> > +             goto out;
> > +
> > +     for_each_online_cpu(cpu) {
> > +             policy = cpufreq_cpu_get(cpu);
> > +             if (policy) {
> > +                     add_policy(policy);
> > +                     cpufreq_cpu_put(policy);
> > +             }
> > +     }
> > +out:
> > +     put_online_cpus();
> > +cnt_not_zero:
> > +     if (!ret)
> > +             cpufreq_cnt++;
> > +     mutex_unlock(&cpufreq_reg_lock);
> > +     return ret;
> > +}
> > +
> > +static int unregister_cpufreq(void)
> > +{
> > +     int ret = 0;
> > +     int cpu;
> > +
> > +     mutex_lock(&cpufreq_reg_lock);
> > +
> > +     if (cpufreq_cnt > 1)
> > +             goto out;
> > +
> > +     cpufreq_unregister_notifier(&cpufreq_trans_nb,
> > +                             CPUFREQ_TRANSITION_NOTIFIER);
> > +
> > +     for (cpu = ARRAY_SIZE(state) - 1; cpu >= 0; cpu--) {
> > +             if (!state[cpu])
> > +                     continue;
> > +             if (state[cpu]->first_cpu == cpu)
> > +                     kfree(state[cpu]);
> > +             state[cpu] = NULL;
> > +     }
> > +
> > +out:
> > +     cpufreq_cnt--;
> > +     mutex_unlock(&cpufreq_reg_lock);
> > +     return ret;
> > +}
> > +
> > +/* ==================== devfreq part ==================== */
> > +
> > +static unsigned int interpolate_freq(struct devfreq *df, unsigned int cpu)
> > +{
> > +     unsigned long *freq_table = df->profile->freq_table;
> > +     unsigned int cpu_min = state[cpu]->min_freq;
> > +     unsigned int cpu_max = state[cpu]->max_freq;
> > +     unsigned int cpu_freq = state[cpu]->freq;
> > +     unsigned int dev_min, dev_max, cpu_percent;
> > +
> > +     if (freq_table) {
> > +             dev_min = freq_table[0];
> > +             dev_max = freq_table[df->profile->max_state - 1];
> > +     } else {
> > +             if (df->max_freq <= df->min_freq)
> > +                     return 0;
> > +             dev_min = df->min_freq;
> > +             dev_max = df->max_freq;
> > +     }
> > +
> > +     cpu_percent = ((cpu_freq - cpu_min) * 100) / (cpu_max - cpu_min);
> > +     return dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100);
> > +}
> > +
> > +static unsigned int cpu_to_dev_freq(struct devfreq *df, unsigned int cpu)
> > +{
> > +     struct freq_map *map = NULL;
> > +     unsigned int cpu_khz = 0, freq;
> > +     struct devfreq_node *n = df->data;
> > +
> > +     if (!state[cpu] || state[cpu]->first_cpu != cpu) {
> > +             freq = 0;
> > +             goto out;
> > +     }
> > +
> > +     if (n->common_map)
> > +             map = n->common_map;
> > +     else if (n->map)
> > +             map = n->map[cpu];
> > +
> > +     cpu_khz = state[cpu]->freq;
> > +
> > +     if (!map) {
> > +             freq = interpolate_freq(df, cpu);
> > +             goto out;
> > +     }
> > +
> > +     while (map->cpu_khz && map->cpu_khz < cpu_khz)
> > +             map++;
> > +     if (!map->cpu_khz)
> > +             map--;
> > +     freq = map->target_freq;
> > +
> > +out:
> > +     dev_dbg(df->dev.parent, "CPU%u: %d -> dev: %u\n", cpu, cpu_khz, freq);
> > +     return freq;
> > +}
> > +
> > +static int devfreq_cpufreq_get_freq(struct devfreq *df,
> > +                                     unsigned long *freq)
> > +{
> > +     unsigned int cpu, tgt_freq = 0;
> > +     struct devfreq_node *node;
> > +
> > +     node = df->data;
> > +     if (!node) {
> > +             pr_err("Unable to find devfreq node!\n");
> > +             return -ENODEV;
> > +     }
> > +
> > +     for_each_possible_cpu(cpu)
> > +             tgt_freq = max(tgt_freq, cpu_to_dev_freq(df, cpu));
> > +
> > +     *freq = tgt_freq;
> > +     return 0;
> > +}
> > +
> > +static unsigned int show_table(char *buf, unsigned int len,
> > +                             struct freq_map *map)
> > +{
> > +     unsigned int cnt = 0;
> > +
> > +     cnt += snprintf(buf + cnt, len - cnt, "CPU freq\tDevice freq\n");
> > +
> > +     while (map->cpu_khz && cnt < len) {
> > +             cnt += snprintf(buf + cnt, len - cnt, "%8u\t%11u\n",
> > +                             map->cpu_khz, map->target_freq);
> > +             map++;
> > +     }
> > +     if (cnt < len)
> > +             cnt += snprintf(buf + cnt, len - cnt, "\n");
> > +
> > +     return cnt;
> > +}
> > +
> > +static ssize_t freq_map_show(struct device *dev, struct device_attribute *attr,
> > +                     char *buf)
> > +{
> > +     struct devfreq *df = to_devfreq(dev);
> > +     struct devfreq_node *n = df->data;
> > +     struct freq_map *map;
> > +     unsigned int cnt = 0, cpu;
> > +
> > +     mutex_lock(&state_lock);
> > +     if (n->common_map) {
> > +             map = n->common_map;
> > +             cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> > +                             "Common table for all CPUs:\n");
> > +             cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
> > +     } else if (n->map) {
> > +             for_each_possible_cpu(cpu) {
> > +                     map = n->map[cpu];
> > +                     if (!map)
> > +                             continue;
> > +                     cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> > +                                     "CPU %u:\n", cpu);
> > +                     if (cnt >= PAGE_SIZE)
> > +                             break;
> > +                     cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
> > +                     if (cnt >= PAGE_SIZE)
> > +                             break;
> > +             }
> > +     } else {
> > +             cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> > +                             "Device freq interpolated based on CPU freq\n");
> > +     }
> > +     mutex_unlock(&state_lock);
> > +
> > +     return cnt;
> > +}
> > +
> > +static DEVICE_ATTR_RO(freq_map);
> > +static struct attribute *dev_attr[] = {
> > +     &dev_attr_freq_map.attr,
> > +     NULL,
> > +};
> > +
> > +static struct attribute_group dev_attr_group = {
> > +     .name = "cpufreq-map",
> > +     .attrs = dev_attr,
> > +};
> > +
> > +static int devfreq_cpufreq_gov_start(struct devfreq *devfreq)
> > +{
> > +     int ret = 0;
> > +     struct devfreq_node *node;
> > +     bool alloc = false;
> > +
> > +     ret = register_cpufreq();
> > +     if (ret)
> > +             return ret;
> > +
> > +     ret = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group);
> > +     if (ret) {
> > +             unregister_cpufreq();
> > +             return ret;
> > +     }
> > +
> > +     mutex_lock(&state_lock);
> > +
> > +     node = find_devfreq_node(devfreq->dev.parent);
> > +     if (node == NULL) {
> > +             node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
> > +             if (!node) {
> > +                     ret = -ENOMEM;
> > +                     goto alloc_fail;
> > +             }
> > +             alloc = true;
> > +             node->dev = devfreq->dev.parent;
> > +             list_add_tail(&node->list, &devfreq_list);
> > +     }
> > +     node->df = devfreq;
> > +     node->orig_data = devfreq->data;
> > +     devfreq->data = node;
> > +
> > +     mutex_lock(&devfreq->lock);
> > +     ret = update_devfreq(devfreq);
> > +     mutex_unlock(&devfreq->lock);
> > +     if (ret) {
> > +             pr_err("Freq update failed!\n");
> > +             goto update_fail;
> > +     }
> > +
> > +     mutex_unlock(&state_lock);
> > +     return 0;
> > +
> > +update_fail:
> > +     devfreq->data = node->orig_data;
> > +     if (alloc) {
> > +             list_del(&node->list);
> > +             kfree(node);
> > +     }
> > +alloc_fail:
> > +     mutex_unlock(&state_lock);
> > +     sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
> > +     unregister_cpufreq();
> > +     return ret;
> > +}
> > +
> > +static void devfreq_cpufreq_gov_stop(struct devfreq *devfreq)
> > +{
> > +     struct devfreq_node *node = devfreq->data;
> > +
> > +     mutex_lock(&state_lock);
> > +     devfreq->data = node->orig_data;
> > +     if (node->map || node->common_map) {
> > +             node->df = NULL;
> > +     } else {
> > +             list_del(&node->list);
> > +             kfree(node);
> > +     }
> > +     mutex_unlock(&state_lock);
> > +
> > +     sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
> > +     unregister_cpufreq();
> > +}
> > +
> > +static int devfreq_cpufreq_ev_handler(struct devfreq *devfreq,
> > +                                     unsigned int event, void *data)
> > +{
> > +     int ret;
> > +
> > +     switch (event) {
> > +     case DEVFREQ_GOV_START:
> > +
> > +             ret = devfreq_cpufreq_gov_start(devfreq);
> > +             if (ret) {
> > +                     pr_err("Governor start failed!\n");
> > +                     return ret;
> > +             }
> > +             pr_debug("Enabled CPUfreq-map governor\n");
> > +             break;
> > +
> > +     case DEVFREQ_GOV_STOP:
> > +
> > +             devfreq_cpufreq_gov_stop(devfreq);
> > +             pr_debug("Disabled dev CPUfreq-map governor\n");
> > +             break;
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static struct devfreq_governor devfreq_cpufreq = {
> > +     .name = "cpufreq-map",
> > +     .get_target_freq = devfreq_cpufreq_get_freq,
> > +     .event_handler = devfreq_cpufreq_ev_handler,
> > +};
> > +
> > +#define NUM_COLS     2
> > +static struct freq_map *read_tbl(struct device_node *of_node, char *prop_name)
> > +{
> > +     int len, nf, i, j;
> > +     u32 data;
> > +     struct freq_map *tbl;
> > +
> > +     if (!of_find_property(of_node, prop_name, &len))
> > +             return NULL;
> > +     len /= sizeof(data);
> > +
> > +     if (len % NUM_COLS || len == 0)
> > +             return NULL;
> > +     nf = len / NUM_COLS;
> > +
> > +     tbl = kzalloc((nf + 1) * sizeof(*tbl), GFP_KERNEL);
> > +     if (!tbl)
> > +             return NULL;
> > +
> > +     for (i = 0, j = 0; i < nf; i++, j += 2) {
> > +             of_property_read_u32_index(of_node, prop_name, j, &data);
> > +             tbl[i].cpu_khz = data;
> > +
> > +             of_property_read_u32_index(of_node, prop_name, j + 1, &data);
> > +             tbl[i].target_freq = data;
> > +     }
> > +     tbl[i].cpu_khz = 0;
> > +
> > +     return tbl;
> > +}
> > +
> > +#define PROP_TARGET "target-dev"
> > +#define PROP_TABLE "cpu-to-dev-map"
> > +static int add_table_from_of(struct device_node *of_node)
> > +{
> > +     struct device_node *target_of_node;
> > +     struct devfreq_node *node;
> > +     struct freq_map *common_tbl;
> > +     struct freq_map **tbl_list = NULL;
> > +     static char prop_name[] = PROP_TABLE "-999999";
> > +     int cpu, ret, cnt = 0, prop_sz = ARRAY_SIZE(prop_name);
> > +
> > +     target_of_node = of_parse_phandle(of_node, PROP_TARGET, 0);
> > +     if (!target_of_node)
> > +             return -EINVAL;
> > +
> > +     node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
> > +     if (!node)
> > +             return -ENOMEM;
> > +
> > +     common_tbl = read_tbl(of_node, PROP_TABLE);
> > +     if (!common_tbl) {
> > +             tbl_list = kzalloc(sizeof(*tbl_list) * NR_CPUS, GFP_KERNEL);
> > +             if (!tbl_list) {
> > +                     ret = -ENOMEM;
> > +                     goto err_list;
> > +             }
> > +
> > +             for_each_possible_cpu(cpu) {
> > +                     ret = snprintf(prop_name, prop_sz, "%s-%d",
> > +                                     PROP_TABLE, cpu);
> > +                     if (ret >= prop_sz) {
> > +                             pr_warn("More CPUs than I can handle!\n");
> > +                             pr_warn("Skipping rest of the tables!\n");
> > +                             break;
> > +                     }
> > +                     tbl_list[cpu] = read_tbl(of_node, prop_name);
> > +                     if (tbl_list[cpu])
> > +                             cnt++;
> > +             }
> > +     }
> > +     if (!common_tbl && !cnt) {
> > +             ret = -EINVAL;
> > +             goto err_tbl;
> > +     }
> > +
> > +     mutex_lock(&state_lock);
> > +     node->of_node = target_of_node;
> > +     node->map = tbl_list;
> > +     node->common_map = common_tbl;
> > +     list_add_tail(&node->list, &devfreq_list);
> > +     mutex_unlock(&state_lock);
> > +
> > +     return 0;
> > +err_tbl:
> > +     kfree(tbl_list);
> > +err_list:
> > +     kfree(node);
> > +     return ret;
> > +}
> > +
> > +static int __init devfreq_cpufreq_init(void)
> > +{
> > +     int ret;
> > +     struct device_node *of_par, *of_child;
> > +
> > +     of_par = of_find_node_by_name(NULL, "devfreq-cpufreq-map");
> > +     if (of_par) {
> > +             for_each_child_of_node(of_par, of_child) {
> > +                     ret = add_table_from_of(of_child);
> > +                     if (ret)
> > +                             pr_err("Parsing %s failed!\n", of_child->name);
> > +                     else
> > +                             pr_debug("Parsed %s.\n", of_child->name);
> > +             }
> > +             of_node_put(of_par);
> > +     } else {
> > +             pr_info("No tables parsed from DT.\n");
> > +     }
> > +
> > +     ret = devfreq_add_governor(&devfreq_cpufreq);
> > +     if (ret) {
> > +             pr_err("cpufreq-map governor add failed!\n");
> > +             return ret;
> > +     }
> > +
> > +     return 0;
> > +}
> > +subsys_initcall(devfreq_cpufreq_init);
> > +
> > +static void __exit devfreq_cpufreq_exit(void)
> > +{
> > +     int ret, cpu;
> > +     struct devfreq_node *node, *tmp;
> > +
> > +     ret = devfreq_remove_governor(&devfreq_cpufreq);
> > +     if (ret)
> > +             pr_err("cpufreq-map governor remove failed!\n");
> > +
> > +     mutex_lock(&state_lock);
> > +     list_for_each_entry_safe(node, tmp, &devfreq_list, list) {
> > +             kfree(node->common_map);
> > +             for_each_possible_cpu(cpu)
> > +                     kfree(node->map[cpu]);
> > +             kfree(node->map);
> > +             list_del(&node->list);
> > +             kfree(node);
> > +     }
> > +     mutex_unlock(&state_lock);
> > +}
> > +module_exit(devfreq_cpufreq_exit);
> > +
> > +MODULE_DESCRIPTION("devfreq gov that sets dev freq based on current CPU freq");
> > +MODULE_LICENSE("GPL v2");
> >

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor
  2019-06-20  9:32     ` Hsin-Yi Wang
@ 2019-06-20  9:41       ` Sibi Sankar
  2019-06-20  9:43         ` Hsin-Yi Wang
  2019-06-26  8:12         ` Chanwoo Choi
  0 siblings, 2 replies; 8+ messages in thread
From: Sibi Sankar @ 2019-06-20  9:41 UTC (permalink / raw)
  To: Hsin-Yi Wang
  Cc: linux-kernel-owner, Andrew-sh . Cheng, linux-pm, lkml,
	Chanwoo Choi, Kyungmin Park, Saravana Kannan, linux-mediatek,
	MyungJoo Ham, Matthias Brugger,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE

Hey Hsin-Yi, Chanwoo

On 2019-06-20 15:02, Hsin-Yi Wang wrote:
> Hi Chanwoo Choi, Saravana Kannan and Sibi Sankar,
> 
> I've also tested Sibi Sankar's patch[1] locally with mt8183-cci, and
> it works fine too!
> It'd be great if Sibi Sankar or anyone who is familiar with the
> original design can finish this implementation. But if no one has time
> to do that, I think I can also help on address the comments. Thanks!

Now that we have a user :) I am happy
to repost the patch with the comments
addressed.

https://lkml.org/lkml/2019/6/14/4
Also with ^^ patch and few more in the
series the dt parsing of required-opps
should get further simplified.

> 
> 
> [1]
> [RFC,2/9] OPP: Export a number of helpers to prevent code duplication
> - https://patchwork.kernel.org/patch/10875199/
> [RFC,3/9] PM / devfreq: Add cpu based scaling support to 
> passive_governor
> - https://patchwork.kernel.org/patch/10875195/
> 
> Hsin-Yi
> 
> On Thu, Jun 20, 2019 at 2:56 PM Chanwoo Choi <cw00.choi@samsung.com> 
> wrote:
>> 
>> + Sibi Sankar
>> 
>> Hi, Hsin-Yi Wang, Saravana Kannan and Sibi Sankar
>> 
>> I summarized the history of the related patch about this title.
>> 
>> Firstly,
>> As I knew, Saravana sent the patch[1] which contains
>> 'governor_cpufreq_map.c' last year. According to the Myungoo's 
>> comment,
>> 
>> Secondly,
>> Sibi Sankar modified the 'governor_passive.c'[2] in order to support
>> the mapping between cpu frequency and device frequency.
>> Unfortunately, Sibi Sankar stopped the development about this
>> because he had found the other method to get his purpose as I knew.
>> 
>> Thirdly,
>> Hsin-Yi Wang send the original patch of Saravana without modification.
>> 
>> 
>> Sincerely, I think that the mapping between cpu frequency and device
>> frequency is necessary. And I prefer the Sibi's approach which 
>> implements
>> stuff to the existing 'passive' governor.
>> 
>> We need to discuss about how to implement them by whom.
>> 
>> 
>> [1] [v3,1/2] PM / devfreq: Generic CPU frequency to device frequency 
>> mapping governor
>> - https://patchwork.kernel.org/patch/10553171/
>> 
>> [2]
>> [PATCH RFC 0/9] Add CPU based scaling support to Passive governor
>> - 
>> https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/
>> [PATCH RFC 3/9] PM / devfreq: Add cpu based scaling support to 
>> passive_governor
>> - 
>> https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/#m1cafb7baf687d2a680d39c85d3ec7d1b590b68fc
>> 
>> 
>> Best Regards,
>> Chanwoo Choi
>> 
>> On 19. 6. 18. 오후 1:14, Hsin-Yi Wang wrote:
>> > From: Saravana Kannan <skannan@codeaurora.org>
>> >
>> > From: Saravana Kannan <skannan@codeaurora.org>
>> >
>> > Many CPU architectures have caches that can scale independent of the CPUs.
>> > Frequency scaling of the caches is necessary to make sure the cache is not
>> > a performance bottleneck that leads to poor performance and power. The same
>> > idea applies for RAM/DDR.
>> >
>> > To achieve this, this patch adds a generic devfreq governor that takes the
>> > current frequency of each CPU frequency domain and then adjusts the
>> > frequency of the cache (or any devfreq device) based on the frequency of
>> > the CPUs. It listens to CPU frequency transition notifiers to keep itself
>> > up to date on the current CPU frequency.
>> >
>> > To decide the frequency of the device, the governor does one of the
>> > following:
>> >
>> > * Uses a CPU frequency to device frequency mapping table
>> >   - Either one mapping table used for all CPU freq policies (typically used
>> >     for system with homogeneous cores/clusters that have the same OPPs).
>> >   - One mapping table per CPU freq policy (typically used for ASMP systems
>> >     with heterogeneous CPUs with different OPPs)
>> >
>> > OR
>> >
>> > * Scales the device frequency in proportion to the CPU frequency. So, if
>> >   the CPUs are running at their max frequency, the device runs at its max
>> >   frequency.  If the CPUs are running at their min frequency, the device
>> >   runs at its min frequency. And interpolated for frequencies in between.
>> >
>> > Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
>> > Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
>> > ---
>> >  .../bindings/devfreq/devfreq-cpufreq-map.txt  |  53 ++
>> >  drivers/devfreq/Kconfig                       |   8 +
>> >  drivers/devfreq/Makefile                      |   1 +
>> >  drivers/devfreq/governor_cpufreq_map.c        | 583 ++++++++++++++++++
>> >  4 files changed, 645 insertions(+)
>> >  create mode 100644 Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
>> >  create mode 100644 drivers/devfreq/governor_cpufreq_map.c
>> >
>> > diff --git a/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
>> > new file mode 100644
>> > index 000000000000..982a30bcfc86
>> > --- /dev/null
>> > +++ b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
>> > @@ -0,0 +1,53 @@
>> > +Devfreq CPUfreq governor
>> > +
>> > +devfreq-cpufreq-map is a parent device that contains one or more child devices.
>> > +Each child device provides CPU frequency to device frequency mapping for a
>> > +specific device. Examples of devices that could use this are: DDR, cache and
>> > +CCI.
>> > +
>> > +Parent device name shall be "devfreq-cpufreq-map".
>> > +
>> > +Required child device properties:
>> > +- cpu-to-dev-map, or cpu-to-dev-map-<X>:
>> > +                     A list of tuples where each tuple consists of a
>> > +                     CPU frequency (KHz) and the corresponding device
>> > +                     frequency. CPU frequencies not listed in the table
>> > +                     will use the device frequency that corresponds to the
>> > +                     next rounded up CPU frequency.
>> > +                     Use "cpu-to-dev-map" if all CPUs in the system should
>> > +                     share same mapping.
>> > +                     Use cpu-to-dev-map-<cpuid> to describe different
>> > +                     mappings for different CPUs. The property should be
>> > +                     listed only for the first CPU if multiple CPUs are
>> > +                     synchronous.
>> > +- target-dev:                Phandle to device that this mapping applies to.
>> > +
>> > +Example:
>> > +     devfreq-cpufreq-map {
>> > +             cpubw-cpufreq {
>> > +                     target-dev = <&cpubw>;
>> > +                     cpu-to-dev-map =
>> > +                             <  300000  1144000 >,
>> > +                             <  422400  2288000 >,
>> > +                             <  652800  3051000 >,
>> > +                             <  883200  5996000 >,
>> > +                             < 1190400  8056000 >,
>> > +                             < 1497600 10101000 >,
>> > +                             < 1728000 12145000 >,
>> > +                             < 2649600 16250000 >;
>> > +             };
>> > +
>> > +             cache-cpufreq {
>> > +                     target-dev = <&cache>;
>> > +                     cpu-to-dev-map =
>> > +                             <  300000  300000 >,
>> > +                             <  422400  422400 >,
>> > +                             <  652800  499200 >,
>> > +                             <  883200  576000 >,
>> > +                             <  960000  960000 >,
>> > +                             < 1497600 1036800 >,
>> > +                             < 1574400 1574400 >,
>> > +                             < 1728000 1651200 >,
>> > +                             < 2649600 1728000 >;
>> > +             };
>> > +     };
>> > diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>> > index 0c8204d6b78a..0303f5a400b6 100644
>> > --- a/drivers/devfreq/Kconfig
>> > +++ b/drivers/devfreq/Kconfig
>> > @@ -74,6 +74,14 @@ config DEVFREQ_GOV_PASSIVE
>> >         through sysfs entries. The passive governor recommends that
>> >         devfreq device uses the OPP table to get the frequency/voltage.
>> >
>> > +config DEVFREQ_GOV_CPUFREQ_MAP
>> > +     tristate "CPUfreq Map"
>> > +     depends on CPU_FREQ
>> > +     help
>> > +       Chooses frequency based on the online CPUs' current frequency and a
>> > +       CPU frequency to device frequency mapping table(s). This governor
>> > +       can be useful for controlling devices such as DDR, cache, CCI, etc.
>> > +
>> >  comment "DEVFREQ Drivers"
>> >
>> >  config ARM_EXYNOS_BUS_DEVFREQ
>> > diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>> > index 817dde779f16..81141e2c784f 100644
>> > --- a/drivers/devfreq/Makefile
>> > +++ b/drivers/devfreq/Makefile
>> > @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o
>> >  obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)  += governor_powersave.o
>> >  obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)  += governor_userspace.o
>> >  obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)    += governor_passive.o
>> > +obj-$(CONFIG_DEVFREQ_GOV_CPUFREQ_MAP)        += governor_cpufreq_map.o
>> >
>> >  # DEVFREQ Drivers
>> >  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
>> > diff --git a/drivers/devfreq/governor_cpufreq_map.c b/drivers/devfreq/governor_cpufreq_map.c
>> > new file mode 100644
>> > index 000000000000..084a3ffb8f54
>> > --- /dev/null
>> > +++ b/drivers/devfreq/governor_cpufreq_map.c
>> > @@ -0,0 +1,583 @@
>> > +// SPDX-License-Identifier: GPL-2.0
>> > +/*
>> > + * Copyright (c) 2014-2015, 2018, The Linux Foundation. All rights reserved.
>> > + */
>> > +
>> > +#define pr_fmt(fmt) "dev-cpufreq-map: " fmt
>> > +
>> > +#include <linux/devfreq.h>
>> > +#include <linux/cpu.h>
>> > +#include <linux/cpufreq.h>
>> > +#include <linux/cpumask.h>
>> > +#include <linux/slab.h>
>> > +#include <linux/platform_device.h>
>> > +#include <linux/of.h>
>> > +#include <linux/module.h>
>> > +#include "governor.h"
>> > +
>> > +struct cpu_state {
>> > +     unsigned int freq;
>> > +     unsigned int min_freq;
>> > +     unsigned int max_freq;
>> > +     unsigned int first_cpu;
>> > +};
>> > +static struct cpu_state *state[NR_CPUS];
>> > +static int cpufreq_cnt;
>> > +
>> > +struct freq_map {
>> > +     unsigned int cpu_khz;
>> > +     unsigned int target_freq;
>> > +};
>> > +
>> > +struct devfreq_node {
>> > +     struct devfreq *df;
>> > +     void *orig_data;
>> > +     struct device *dev;
>> > +     struct device_node *of_node;
>> > +     struct list_head list;
>> > +     struct freq_map **map;
>> > +     struct freq_map *common_map;
>> > +};
>> > +static LIST_HEAD(devfreq_list);
>> > +static DEFINE_MUTEX(state_lock);
>> > +static DEFINE_MUTEX(cpufreq_reg_lock);
>> > +
>> > +static void update_all_devfreqs(void)
>> > +{
>> > +     struct devfreq_node *node;
>> > +
>> > +     list_for_each_entry(node, &devfreq_list, list) {
>> > +             struct devfreq *df = node->df;
>> > +
>> > +             if (!node->df)
>> > +                     continue;
>> > +             mutex_lock(&df->lock);
>> > +             update_devfreq(df);
>> > +             mutex_unlock(&df->lock);
>> > +
>> > +     }
>> > +}
>> > +
>> > +static struct devfreq_node *find_devfreq_node(struct device *dev)
>> > +{
>> > +     struct devfreq_node *node;
>> > +
>> > +     list_for_each_entry(node, &devfreq_list, list)
>> > +             if (node->dev == dev || node->of_node == dev->of_node)
>> > +                     return node;
>> > +
>> > +     return NULL;
>> > +}
>> > +
>> > +/* ==================== cpufreq part ==================== */
>> > +static struct cpu_state *add_policy(struct cpufreq_policy *policy)
>> > +{
>> > +     struct cpu_state *new_state;
>> > +     unsigned int cpu, first_cpu;
>> > +
>> > +     new_state = kzalloc(sizeof(struct cpu_state), GFP_KERNEL);
>> > +     if (!new_state)
>> > +             return NULL;
>> > +
>> > +     first_cpu = cpumask_first(policy->related_cpus);
>> > +     new_state->first_cpu = first_cpu;
>> > +     new_state->freq = policy->cur;
>> > +     new_state->min_freq = policy->cpuinfo.min_freq;
>> > +     new_state->max_freq = policy->cpuinfo.max_freq;
>> > +
>> > +     for_each_cpu(cpu, policy->related_cpus)
>> > +             state[cpu] = new_state;
>> > +
>> > +     return new_state;
>> > +}
>> > +
>> > +static int cpufreq_trans_notifier(struct notifier_block *nb,
>> > +             unsigned long event, void *data)
>> > +{
>> > +     struct cpufreq_freqs *freq = data;
>> > +     struct cpu_state *s;
>> > +     struct cpufreq_policy *policy = NULL;
>> > +
>> > +     if (event != CPUFREQ_POSTCHANGE)
>> > +             return 0;
>> > +
>> > +     mutex_lock(&state_lock);
>> > +
>> > +     s = state[freq->cpu];
>> > +     if (!s) {
>> > +             policy = cpufreq_cpu_get(freq->cpu);
>> > +             if (policy) {
>> > +                     s = add_policy(policy);
>> > +                     cpufreq_cpu_put(policy);
>> > +             }
>> > +     }
>> > +     if (!s)
>> > +             goto out;
>> > +
>> > +     if (s->freq != freq->new || policy) {
>> > +             s->freq = freq->new;
>> > +             update_all_devfreqs();
>> > +     }
>> > +
>> > +out:
>> > +     mutex_unlock(&state_lock);
>> > +     return 0;
>> > +}
>> > +
>> > +static struct notifier_block cpufreq_trans_nb = {
>> > +     .notifier_call = cpufreq_trans_notifier
>> > +};
>> > +
>> > +static int register_cpufreq(void)
>> > +{
>> > +     int ret = 0;
>> > +     unsigned int cpu;
>> > +     struct cpufreq_policy *policy;
>> > +
>> > +     mutex_lock(&cpufreq_reg_lock);
>> > +
>> > +     if (cpufreq_cnt)
>> > +             goto cnt_not_zero;
>> > +
>> > +     get_online_cpus();
>> > +     ret = cpufreq_register_notifier(&cpufreq_trans_nb,
>> > +                             CPUFREQ_TRANSITION_NOTIFIER);
>> > +     if (ret)
>> > +             goto out;
>> > +
>> > +     for_each_online_cpu(cpu) {
>> > +             policy = cpufreq_cpu_get(cpu);
>> > +             if (policy) {
>> > +                     add_policy(policy);
>> > +                     cpufreq_cpu_put(policy);
>> > +             }
>> > +     }
>> > +out:
>> > +     put_online_cpus();
>> > +cnt_not_zero:
>> > +     if (!ret)
>> > +             cpufreq_cnt++;
>> > +     mutex_unlock(&cpufreq_reg_lock);
>> > +     return ret;
>> > +}
>> > +
>> > +static int unregister_cpufreq(void)
>> > +{
>> > +     int ret = 0;
>> > +     int cpu;
>> > +
>> > +     mutex_lock(&cpufreq_reg_lock);
>> > +
>> > +     if (cpufreq_cnt > 1)
>> > +             goto out;
>> > +
>> > +     cpufreq_unregister_notifier(&cpufreq_trans_nb,
>> > +                             CPUFREQ_TRANSITION_NOTIFIER);
>> > +
>> > +     for (cpu = ARRAY_SIZE(state) - 1; cpu >= 0; cpu--) {
>> > +             if (!state[cpu])
>> > +                     continue;
>> > +             if (state[cpu]->first_cpu == cpu)
>> > +                     kfree(state[cpu]);
>> > +             state[cpu] = NULL;
>> > +     }
>> > +
>> > +out:
>> > +     cpufreq_cnt--;
>> > +     mutex_unlock(&cpufreq_reg_lock);
>> > +     return ret;
>> > +}
>> > +
>> > +/* ==================== devfreq part ==================== */
>> > +
>> > +static unsigned int interpolate_freq(struct devfreq *df, unsigned int cpu)
>> > +{
>> > +     unsigned long *freq_table = df->profile->freq_table;
>> > +     unsigned int cpu_min = state[cpu]->min_freq;
>> > +     unsigned int cpu_max = state[cpu]->max_freq;
>> > +     unsigned int cpu_freq = state[cpu]->freq;
>> > +     unsigned int dev_min, dev_max, cpu_percent;
>> > +
>> > +     if (freq_table) {
>> > +             dev_min = freq_table[0];
>> > +             dev_max = freq_table[df->profile->max_state - 1];
>> > +     } else {
>> > +             if (df->max_freq <= df->min_freq)
>> > +                     return 0;
>> > +             dev_min = df->min_freq;
>> > +             dev_max = df->max_freq;
>> > +     }
>> > +
>> > +     cpu_percent = ((cpu_freq - cpu_min) * 100) / (cpu_max - cpu_min);
>> > +     return dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100);
>> > +}
>> > +
>> > +static unsigned int cpu_to_dev_freq(struct devfreq *df, unsigned int cpu)
>> > +{
>> > +     struct freq_map *map = NULL;
>> > +     unsigned int cpu_khz = 0, freq;
>> > +     struct devfreq_node *n = df->data;
>> > +
>> > +     if (!state[cpu] || state[cpu]->first_cpu != cpu) {
>> > +             freq = 0;
>> > +             goto out;
>> > +     }
>> > +
>> > +     if (n->common_map)
>> > +             map = n->common_map;
>> > +     else if (n->map)
>> > +             map = n->map[cpu];
>> > +
>> > +     cpu_khz = state[cpu]->freq;
>> > +
>> > +     if (!map) {
>> > +             freq = interpolate_freq(df, cpu);
>> > +             goto out;
>> > +     }
>> > +
>> > +     while (map->cpu_khz && map->cpu_khz < cpu_khz)
>> > +             map++;
>> > +     if (!map->cpu_khz)
>> > +             map--;
>> > +     freq = map->target_freq;
>> > +
>> > +out:
>> > +     dev_dbg(df->dev.parent, "CPU%u: %d -> dev: %u\n", cpu, cpu_khz, freq);
>> > +     return freq;
>> > +}
>> > +
>> > +static int devfreq_cpufreq_get_freq(struct devfreq *df,
>> > +                                     unsigned long *freq)
>> > +{
>> > +     unsigned int cpu, tgt_freq = 0;
>> > +     struct devfreq_node *node;
>> > +
>> > +     node = df->data;
>> > +     if (!node) {
>> > +             pr_err("Unable to find devfreq node!\n");
>> > +             return -ENODEV;
>> > +     }
>> > +
>> > +     for_each_possible_cpu(cpu)
>> > +             tgt_freq = max(tgt_freq, cpu_to_dev_freq(df, cpu));
>> > +
>> > +     *freq = tgt_freq;
>> > +     return 0;
>> > +}
>> > +
>> > +static unsigned int show_table(char *buf, unsigned int len,
>> > +                             struct freq_map *map)
>> > +{
>> > +     unsigned int cnt = 0;
>> > +
>> > +     cnt += snprintf(buf + cnt, len - cnt, "CPU freq\tDevice freq\n");
>> > +
>> > +     while (map->cpu_khz && cnt < len) {
>> > +             cnt += snprintf(buf + cnt, len - cnt, "%8u\t%11u\n",
>> > +                             map->cpu_khz, map->target_freq);
>> > +             map++;
>> > +     }
>> > +     if (cnt < len)
>> > +             cnt += snprintf(buf + cnt, len - cnt, "\n");
>> > +
>> > +     return cnt;
>> > +}
>> > +
>> > +static ssize_t freq_map_show(struct device *dev, struct device_attribute *attr,
>> > +                     char *buf)
>> > +{
>> > +     struct devfreq *df = to_devfreq(dev);
>> > +     struct devfreq_node *n = df->data;
>> > +     struct freq_map *map;
>> > +     unsigned int cnt = 0, cpu;
>> > +
>> > +     mutex_lock(&state_lock);
>> > +     if (n->common_map) {
>> > +             map = n->common_map;
>> > +             cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
>> > +                             "Common table for all CPUs:\n");
>> > +             cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
>> > +     } else if (n->map) {
>> > +             for_each_possible_cpu(cpu) {
>> > +                     map = n->map[cpu];
>> > +                     if (!map)
>> > +                             continue;
>> > +                     cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
>> > +                                     "CPU %u:\n", cpu);
>> > +                     if (cnt >= PAGE_SIZE)
>> > +                             break;
>> > +                     cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
>> > +                     if (cnt >= PAGE_SIZE)
>> > +                             break;
>> > +             }
>> > +     } else {
>> > +             cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
>> > +                             "Device freq interpolated based on CPU freq\n");
>> > +     }
>> > +     mutex_unlock(&state_lock);
>> > +
>> > +     return cnt;
>> > +}
>> > +
>> > +static DEVICE_ATTR_RO(freq_map);
>> > +static struct attribute *dev_attr[] = {
>> > +     &dev_attr_freq_map.attr,
>> > +     NULL,
>> > +};
>> > +
>> > +static struct attribute_group dev_attr_group = {
>> > +     .name = "cpufreq-map",
>> > +     .attrs = dev_attr,
>> > +};
>> > +
>> > +static int devfreq_cpufreq_gov_start(struct devfreq *devfreq)
>> > +{
>> > +     int ret = 0;
>> > +     struct devfreq_node *node;
>> > +     bool alloc = false;
>> > +
>> > +     ret = register_cpufreq();
>> > +     if (ret)
>> > +             return ret;
>> > +
>> > +     ret = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group);
>> > +     if (ret) {
>> > +             unregister_cpufreq();
>> > +             return ret;
>> > +     }
>> > +
>> > +     mutex_lock(&state_lock);
>> > +
>> > +     node = find_devfreq_node(devfreq->dev.parent);
>> > +     if (node == NULL) {
>> > +             node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
>> > +             if (!node) {
>> > +                     ret = -ENOMEM;
>> > +                     goto alloc_fail;
>> > +             }
>> > +             alloc = true;
>> > +             node->dev = devfreq->dev.parent;
>> > +             list_add_tail(&node->list, &devfreq_list);
>> > +     }
>> > +     node->df = devfreq;
>> > +     node->orig_data = devfreq->data;
>> > +     devfreq->data = node;
>> > +
>> > +     mutex_lock(&devfreq->lock);
>> > +     ret = update_devfreq(devfreq);
>> > +     mutex_unlock(&devfreq->lock);
>> > +     if (ret) {
>> > +             pr_err("Freq update failed!\n");
>> > +             goto update_fail;
>> > +     }
>> > +
>> > +     mutex_unlock(&state_lock);
>> > +     return 0;
>> > +
>> > +update_fail:
>> > +     devfreq->data = node->orig_data;
>> > +     if (alloc) {
>> > +             list_del(&node->list);
>> > +             kfree(node);
>> > +     }
>> > +alloc_fail:
>> > +     mutex_unlock(&state_lock);
>> > +     sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
>> > +     unregister_cpufreq();
>> > +     return ret;
>> > +}
>> > +
>> > +static void devfreq_cpufreq_gov_stop(struct devfreq *devfreq)
>> > +{
>> > +     struct devfreq_node *node = devfreq->data;
>> > +
>> > +     mutex_lock(&state_lock);
>> > +     devfreq->data = node->orig_data;
>> > +     if (node->map || node->common_map) {
>> > +             node->df = NULL;
>> > +     } else {
>> > +             list_del(&node->list);
>> > +             kfree(node);
>> > +     }
>> > +     mutex_unlock(&state_lock);
>> > +
>> > +     sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
>> > +     unregister_cpufreq();
>> > +}
>> > +
>> > +static int devfreq_cpufreq_ev_handler(struct devfreq *devfreq,
>> > +                                     unsigned int event, void *data)
>> > +{
>> > +     int ret;
>> > +
>> > +     switch (event) {
>> > +     case DEVFREQ_GOV_START:
>> > +
>> > +             ret = devfreq_cpufreq_gov_start(devfreq);
>> > +             if (ret) {
>> > +                     pr_err("Governor start failed!\n");
>> > +                     return ret;
>> > +             }
>> > +             pr_debug("Enabled CPUfreq-map governor\n");
>> > +             break;
>> > +
>> > +     case DEVFREQ_GOV_STOP:
>> > +
>> > +             devfreq_cpufreq_gov_stop(devfreq);
>> > +             pr_debug("Disabled dev CPUfreq-map governor\n");
>> > +             break;
>> > +     }
>> > +
>> > +     return 0;
>> > +}
>> > +
>> > +static struct devfreq_governor devfreq_cpufreq = {
>> > +     .name = "cpufreq-map",
>> > +     .get_target_freq = devfreq_cpufreq_get_freq,
>> > +     .event_handler = devfreq_cpufreq_ev_handler,
>> > +};
>> > +
>> > +#define NUM_COLS     2
>> > +static struct freq_map *read_tbl(struct device_node *of_node, char *prop_name)
>> > +{
>> > +     int len, nf, i, j;
>> > +     u32 data;
>> > +     struct freq_map *tbl;
>> > +
>> > +     if (!of_find_property(of_node, prop_name, &len))
>> > +             return NULL;
>> > +     len /= sizeof(data);
>> > +
>> > +     if (len % NUM_COLS || len == 0)
>> > +             return NULL;
>> > +     nf = len / NUM_COLS;
>> > +
>> > +     tbl = kzalloc((nf + 1) * sizeof(*tbl), GFP_KERNEL);
>> > +     if (!tbl)
>> > +             return NULL;
>> > +
>> > +     for (i = 0, j = 0; i < nf; i++, j += 2) {
>> > +             of_property_read_u32_index(of_node, prop_name, j, &data);
>> > +             tbl[i].cpu_khz = data;
>> > +
>> > +             of_property_read_u32_index(of_node, prop_name, j + 1, &data);
>> > +             tbl[i].target_freq = data;
>> > +     }
>> > +     tbl[i].cpu_khz = 0;
>> > +
>> > +     return tbl;
>> > +}
>> > +
>> > +#define PROP_TARGET "target-dev"
>> > +#define PROP_TABLE "cpu-to-dev-map"
>> > +static int add_table_from_of(struct device_node *of_node)
>> > +{
>> > +     struct device_node *target_of_node;
>> > +     struct devfreq_node *node;
>> > +     struct freq_map *common_tbl;
>> > +     struct freq_map **tbl_list = NULL;
>> > +     static char prop_name[] = PROP_TABLE "-999999";
>> > +     int cpu, ret, cnt = 0, prop_sz = ARRAY_SIZE(prop_name);
>> > +
>> > +     target_of_node = of_parse_phandle(of_node, PROP_TARGET, 0);
>> > +     if (!target_of_node)
>> > +             return -EINVAL;
>> > +
>> > +     node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
>> > +     if (!node)
>> > +             return -ENOMEM;
>> > +
>> > +     common_tbl = read_tbl(of_node, PROP_TABLE);
>> > +     if (!common_tbl) {
>> > +             tbl_list = kzalloc(sizeof(*tbl_list) * NR_CPUS, GFP_KERNEL);
>> > +             if (!tbl_list) {
>> > +                     ret = -ENOMEM;
>> > +                     goto err_list;
>> > +             }
>> > +
>> > +             for_each_possible_cpu(cpu) {
>> > +                     ret = snprintf(prop_name, prop_sz, "%s-%d",
>> > +                                     PROP_TABLE, cpu);
>> > +                     if (ret >= prop_sz) {
>> > +                             pr_warn("More CPUs than I can handle!\n");
>> > +                             pr_warn("Skipping rest of the tables!\n");
>> > +                             break;
>> > +                     }
>> > +                     tbl_list[cpu] = read_tbl(of_node, prop_name);
>> > +                     if (tbl_list[cpu])
>> > +                             cnt++;
>> > +             }
>> > +     }
>> > +     if (!common_tbl && !cnt) {
>> > +             ret = -EINVAL;
>> > +             goto err_tbl;
>> > +     }
>> > +
>> > +     mutex_lock(&state_lock);
>> > +     node->of_node = target_of_node;
>> > +     node->map = tbl_list;
>> > +     node->common_map = common_tbl;
>> > +     list_add_tail(&node->list, &devfreq_list);
>> > +     mutex_unlock(&state_lock);
>> > +
>> > +     return 0;
>> > +err_tbl:
>> > +     kfree(tbl_list);
>> > +err_list:
>> > +     kfree(node);
>> > +     return ret;
>> > +}
>> > +
>> > +static int __init devfreq_cpufreq_init(void)
>> > +{
>> > +     int ret;
>> > +     struct device_node *of_par, *of_child;
>> > +
>> > +     of_par = of_find_node_by_name(NULL, "devfreq-cpufreq-map");
>> > +     if (of_par) {
>> > +             for_each_child_of_node(of_par, of_child) {
>> > +                     ret = add_table_from_of(of_child);
>> > +                     if (ret)
>> > +                             pr_err("Parsing %s failed!\n", of_child->name);
>> > +                     else
>> > +                             pr_debug("Parsed %s.\n", of_child->name);
>> > +             }
>> > +             of_node_put(of_par);
>> > +     } else {
>> > +             pr_info("No tables parsed from DT.\n");
>> > +     }
>> > +
>> > +     ret = devfreq_add_governor(&devfreq_cpufreq);
>> > +     if (ret) {
>> > +             pr_err("cpufreq-map governor add failed!\n");
>> > +             return ret;
>> > +     }
>> > +
>> > +     return 0;
>> > +}
>> > +subsys_initcall(devfreq_cpufreq_init);
>> > +
>> > +static void __exit devfreq_cpufreq_exit(void)
>> > +{
>> > +     int ret, cpu;
>> > +     struct devfreq_node *node, *tmp;
>> > +
>> > +     ret = devfreq_remove_governor(&devfreq_cpufreq);
>> > +     if (ret)
>> > +             pr_err("cpufreq-map governor remove failed!\n");
>> > +
>> > +     mutex_lock(&state_lock);
>> > +     list_for_each_entry_safe(node, tmp, &devfreq_list, list) {
>> > +             kfree(node->common_map);
>> > +             for_each_possible_cpu(cpu)
>> > +                     kfree(node->map[cpu]);
>> > +             kfree(node->map);
>> > +             list_del(&node->list);
>> > +             kfree(node);
>> > +     }
>> > +     mutex_unlock(&state_lock);
>> > +}
>> > +module_exit(devfreq_cpufreq_exit);
>> > +
>> > +MODULE_DESCRIPTION("devfreq gov that sets dev freq based on current CPU freq");
>> > +MODULE_LICENSE("GPL v2");
>> >

-- 
-- Sibi Sankar --
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor
  2019-06-20  9:41       ` Sibi Sankar
@ 2019-06-20  9:43         ` Hsin-Yi Wang
  2019-06-26  8:12         ` Chanwoo Choi
  1 sibling, 0 replies; 8+ messages in thread
From: Hsin-Yi Wang @ 2019-06-20  9:43 UTC (permalink / raw)
  To: Sibi Sankar
  Cc: linux-kernel-owner, Andrew-sh . Cheng, linux-pm, lkml,
	Chanwoo Choi, Kyungmin Park, Saravana Kannan, linux-mediatek,
	MyungJoo Ham, Matthias Brugger,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE

Thanks!

On Thu, Jun 20, 2019 at 5:41 PM Sibi Sankar <sibis@codeaurora.org> wrote:
>
> Hey Hsin-Yi, Chanwoo
>
> On 2019-06-20 15:02, Hsin-Yi Wang wrote:
> > Hi Chanwoo Choi, Saravana Kannan and Sibi Sankar,
> >
> > I've also tested Sibi Sankar's patch[1] locally with mt8183-cci, and
> > it works fine too!
> > It'd be great if Sibi Sankar or anyone who is familiar with the
> > original design can finish this implementation. But if no one has time
> > to do that, I think I can also help on address the comments. Thanks!
>
> Now that we have a user :) I am happy
> to repost the patch with the comments
> addressed.
>
> https://lkml.org/lkml/2019/6/14/4
> Also with ^^ patch and few more in the
> series the dt parsing of required-opps
> should get further simplified.
>
> >
> >
> > [1]
> > [RFC,2/9] OPP: Export a number of helpers to prevent code duplication
> > - https://patchwork.kernel.org/patch/10875199/
> > [RFC,3/9] PM / devfreq: Add cpu based scaling support to
> > passive_governor
> > - https://patchwork.kernel.org/patch/10875195/
> >
> > Hsin-Yi
> >
> > On Thu, Jun 20, 2019 at 2:56 PM Chanwoo Choi <cw00.choi@samsung.com>
> > wrote:
> >>
> >> + Sibi Sankar
> >>
> >> Hi, Hsin-Yi Wang, Saravana Kannan and Sibi Sankar
> >>
> >> I summarized the history of the related patch about this title.
> >>
> >> Firstly,
> >> As I knew, Saravana sent the patch[1] which contains
> >> 'governor_cpufreq_map.c' last year. According to the Myungoo's
> >> comment,
> >>
> >> Secondly,
> >> Sibi Sankar modified the 'governor_passive.c'[2] in order to support
> >> the mapping between cpu frequency and device frequency.
> >> Unfortunately, Sibi Sankar stopped the development about this
> >> because he had found the other method to get his purpose as I knew.
> >>
> >> Thirdly,
> >> Hsin-Yi Wang send the original patch of Saravana without modification.
> >>
> >>
> >> Sincerely, I think that the mapping between cpu frequency and device
> >> frequency is necessary. And I prefer the Sibi's approach which
> >> implements
> >> stuff to the existing 'passive' governor.
> >>
> >> We need to discuss about how to implement them by whom.
> >>
> >>
> >> [1] [v3,1/2] PM / devfreq: Generic CPU frequency to device frequency
> >> mapping governor
> >> - https://patchwork.kernel.org/patch/10553171/
> >>
> >> [2]
> >> [PATCH RFC 0/9] Add CPU based scaling support to Passive governor
> >> -
> >> https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/
> >> [PATCH RFC 3/9] PM / devfreq: Add cpu based scaling support to
> >> passive_governor
> >> -
> >> https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/#m1cafb7baf687d2a680d39c85d3ec7d1b590b68fc
> >>
> >>
> >> Best Regards,
> >> Chanwoo Choi
> >>
> >> On 19. 6. 18. 오후 1:14, Hsin-Yi Wang wrote:
> >> > From: Saravana Kannan <skannan@codeaurora.org>
> >> >
> >> > From: Saravana Kannan <skannan@codeaurora.org>
> >> >
> >> > Many CPU architectures have caches that can scale independent of the CPUs.
> >> > Frequency scaling of the caches is necessary to make sure the cache is not
> >> > a performance bottleneck that leads to poor performance and power. The same
> >> > idea applies for RAM/DDR.
> >> >
> >> > To achieve this, this patch adds a generic devfreq governor that takes the
> >> > current frequency of each CPU frequency domain and then adjusts the
> >> > frequency of the cache (or any devfreq device) based on the frequency of
> >> > the CPUs. It listens to CPU frequency transition notifiers to keep itself
> >> > up to date on the current CPU frequency.
> >> >
> >> > To decide the frequency of the device, the governor does one of the
> >> > following:
> >> >
> >> > * Uses a CPU frequency to device frequency mapping table
> >> >   - Either one mapping table used for all CPU freq policies (typically used
> >> >     for system with homogeneous cores/clusters that have the same OPPs).
> >> >   - One mapping table per CPU freq policy (typically used for ASMP systems
> >> >     with heterogeneous CPUs with different OPPs)
> >> >
> >> > OR
> >> >
> >> > * Scales the device frequency in proportion to the CPU frequency. So, if
> >> >   the CPUs are running at their max frequency, the device runs at its max
> >> >   frequency.  If the CPUs are running at their min frequency, the device
> >> >   runs at its min frequency. And interpolated for frequencies in between.
> >> >
> >> > Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
> >> > Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
> >> > ---
> >> >  .../bindings/devfreq/devfreq-cpufreq-map.txt  |  53 ++
> >> >  drivers/devfreq/Kconfig                       |   8 +
> >> >  drivers/devfreq/Makefile                      |   1 +
> >> >  drivers/devfreq/governor_cpufreq_map.c        | 583 ++++++++++++++++++
> >> >  4 files changed, 645 insertions(+)
> >> >  create mode 100644 Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
> >> >  create mode 100644 drivers/devfreq/governor_cpufreq_map.c
> >> >
> >> > diff --git a/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
> >> > new file mode 100644
> >> > index 000000000000..982a30bcfc86
> >> > --- /dev/null
> >> > +++ b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
> >> > @@ -0,0 +1,53 @@
> >> > +Devfreq CPUfreq governor
> >> > +
> >> > +devfreq-cpufreq-map is a parent device that contains one or more child devices.
> >> > +Each child device provides CPU frequency to device frequency mapping for a
> >> > +specific device. Examples of devices that could use this are: DDR, cache and
> >> > +CCI.
> >> > +
> >> > +Parent device name shall be "devfreq-cpufreq-map".
> >> > +
> >> > +Required child device properties:
> >> > +- cpu-to-dev-map, or cpu-to-dev-map-<X>:
> >> > +                     A list of tuples where each tuple consists of a
> >> > +                     CPU frequency (KHz) and the corresponding device
> >> > +                     frequency. CPU frequencies not listed in the table
> >> > +                     will use the device frequency that corresponds to the
> >> > +                     next rounded up CPU frequency.
> >> > +                     Use "cpu-to-dev-map" if all CPUs in the system should
> >> > +                     share same mapping.
> >> > +                     Use cpu-to-dev-map-<cpuid> to describe different
> >> > +                     mappings for different CPUs. The property should be
> >> > +                     listed only for the first CPU if multiple CPUs are
> >> > +                     synchronous.
> >> > +- target-dev:                Phandle to device that this mapping applies to.
> >> > +
> >> > +Example:
> >> > +     devfreq-cpufreq-map {
> >> > +             cpubw-cpufreq {
> >> > +                     target-dev = <&cpubw>;
> >> > +                     cpu-to-dev-map =
> >> > +                             <  300000  1144000 >,
> >> > +                             <  422400  2288000 >,
> >> > +                             <  652800  3051000 >,
> >> > +                             <  883200  5996000 >,
> >> > +                             < 1190400  8056000 >,
> >> > +                             < 1497600 10101000 >,
> >> > +                             < 1728000 12145000 >,
> >> > +                             < 2649600 16250000 >;
> >> > +             };
> >> > +
> >> > +             cache-cpufreq {
> >> > +                     target-dev = <&cache>;
> >> > +                     cpu-to-dev-map =
> >> > +                             <  300000  300000 >,
> >> > +                             <  422400  422400 >,
> >> > +                             <  652800  499200 >,
> >> > +                             <  883200  576000 >,
> >> > +                             <  960000  960000 >,
> >> > +                             < 1497600 1036800 >,
> >> > +                             < 1574400 1574400 >,
> >> > +                             < 1728000 1651200 >,
> >> > +                             < 2649600 1728000 >;
> >> > +             };
> >> > +     };
> >> > diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
> >> > index 0c8204d6b78a..0303f5a400b6 100644
> >> > --- a/drivers/devfreq/Kconfig
> >> > +++ b/drivers/devfreq/Kconfig
> >> > @@ -74,6 +74,14 @@ config DEVFREQ_GOV_PASSIVE
> >> >         through sysfs entries. The passive governor recommends that
> >> >         devfreq device uses the OPP table to get the frequency/voltage.
> >> >
> >> > +config DEVFREQ_GOV_CPUFREQ_MAP
> >> > +     tristate "CPUfreq Map"
> >> > +     depends on CPU_FREQ
> >> > +     help
> >> > +       Chooses frequency based on the online CPUs' current frequency and a
> >> > +       CPU frequency to device frequency mapping table(s). This governor
> >> > +       can be useful for controlling devices such as DDR, cache, CCI, etc.
> >> > +
> >> >  comment "DEVFREQ Drivers"
> >> >
> >> >  config ARM_EXYNOS_BUS_DEVFREQ
> >> > diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
> >> > index 817dde779f16..81141e2c784f 100644
> >> > --- a/drivers/devfreq/Makefile
> >> > +++ b/drivers/devfreq/Makefile
> >> > @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o
> >> >  obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)  += governor_powersave.o
> >> >  obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)  += governor_userspace.o
> >> >  obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)    += governor_passive.o
> >> > +obj-$(CONFIG_DEVFREQ_GOV_CPUFREQ_MAP)        += governor_cpufreq_map.o
> >> >
> >> >  # DEVFREQ Drivers
> >> >  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
> >> > diff --git a/drivers/devfreq/governor_cpufreq_map.c b/drivers/devfreq/governor_cpufreq_map.c
> >> > new file mode 100644
> >> > index 000000000000..084a3ffb8f54
> >> > --- /dev/null
> >> > +++ b/drivers/devfreq/governor_cpufreq_map.c
> >> > @@ -0,0 +1,583 @@
> >> > +// SPDX-License-Identifier: GPL-2.0
> >> > +/*
> >> > + * Copyright (c) 2014-2015, 2018, The Linux Foundation. All rights reserved.
> >> > + */
> >> > +
> >> > +#define pr_fmt(fmt) "dev-cpufreq-map: " fmt
> >> > +
> >> > +#include <linux/devfreq.h>
> >> > +#include <linux/cpu.h>
> >> > +#include <linux/cpufreq.h>
> >> > +#include <linux/cpumask.h>
> >> > +#include <linux/slab.h>
> >> > +#include <linux/platform_device.h>
> >> > +#include <linux/of.h>
> >> > +#include <linux/module.h>
> >> > +#include "governor.h"
> >> > +
> >> > +struct cpu_state {
> >> > +     unsigned int freq;
> >> > +     unsigned int min_freq;
> >> > +     unsigned int max_freq;
> >> > +     unsigned int first_cpu;
> >> > +};
> >> > +static struct cpu_state *state[NR_CPUS];
> >> > +static int cpufreq_cnt;
> >> > +
> >> > +struct freq_map {
> >> > +     unsigned int cpu_khz;
> >> > +     unsigned int target_freq;
> >> > +};
> >> > +
> >> > +struct devfreq_node {
> >> > +     struct devfreq *df;
> >> > +     void *orig_data;
> >> > +     struct device *dev;
> >> > +     struct device_node *of_node;
> >> > +     struct list_head list;
> >> > +     struct freq_map **map;
> >> > +     struct freq_map *common_map;
> >> > +};
> >> > +static LIST_HEAD(devfreq_list);
> >> > +static DEFINE_MUTEX(state_lock);
> >> > +static DEFINE_MUTEX(cpufreq_reg_lock);
> >> > +
> >> > +static void update_all_devfreqs(void)
> >> > +{
> >> > +     struct devfreq_node *node;
> >> > +
> >> > +     list_for_each_entry(node, &devfreq_list, list) {
> >> > +             struct devfreq *df = node->df;
> >> > +
> >> > +             if (!node->df)
> >> > +                     continue;
> >> > +             mutex_lock(&df->lock);
> >> > +             update_devfreq(df);
> >> > +             mutex_unlock(&df->lock);
> >> > +
> >> > +     }
> >> > +}
> >> > +
> >> > +static struct devfreq_node *find_devfreq_node(struct device *dev)
> >> > +{
> >> > +     struct devfreq_node *node;
> >> > +
> >> > +     list_for_each_entry(node, &devfreq_list, list)
> >> > +             if (node->dev == dev || node->of_node == dev->of_node)
> >> > +                     return node;
> >> > +
> >> > +     return NULL;
> >> > +}
> >> > +
> >> > +/* ==================== cpufreq part ==================== */
> >> > +static struct cpu_state *add_policy(struct cpufreq_policy *policy)
> >> > +{
> >> > +     struct cpu_state *new_state;
> >> > +     unsigned int cpu, first_cpu;
> >> > +
> >> > +     new_state = kzalloc(sizeof(struct cpu_state), GFP_KERNEL);
> >> > +     if (!new_state)
> >> > +             return NULL;
> >> > +
> >> > +     first_cpu = cpumask_first(policy->related_cpus);
> >> > +     new_state->first_cpu = first_cpu;
> >> > +     new_state->freq = policy->cur;
> >> > +     new_state->min_freq = policy->cpuinfo.min_freq;
> >> > +     new_state->max_freq = policy->cpuinfo.max_freq;
> >> > +
> >> > +     for_each_cpu(cpu, policy->related_cpus)
> >> > +             state[cpu] = new_state;
> >> > +
> >> > +     return new_state;
> >> > +}
> >> > +
> >> > +static int cpufreq_trans_notifier(struct notifier_block *nb,
> >> > +             unsigned long event, void *data)
> >> > +{
> >> > +     struct cpufreq_freqs *freq = data;
> >> > +     struct cpu_state *s;
> >> > +     struct cpufreq_policy *policy = NULL;
> >> > +
> >> > +     if (event != CPUFREQ_POSTCHANGE)
> >> > +             return 0;
> >> > +
> >> > +     mutex_lock(&state_lock);
> >> > +
> >> > +     s = state[freq->cpu];
> >> > +     if (!s) {
> >> > +             policy = cpufreq_cpu_get(freq->cpu);
> >> > +             if (policy) {
> >> > +                     s = add_policy(policy);
> >> > +                     cpufreq_cpu_put(policy);
> >> > +             }
> >> > +     }
> >> > +     if (!s)
> >> > +             goto out;
> >> > +
> >> > +     if (s->freq != freq->new || policy) {
> >> > +             s->freq = freq->new;
> >> > +             update_all_devfreqs();
> >> > +     }
> >> > +
> >> > +out:
> >> > +     mutex_unlock(&state_lock);
> >> > +     return 0;
> >> > +}
> >> > +
> >> > +static struct notifier_block cpufreq_trans_nb = {
> >> > +     .notifier_call = cpufreq_trans_notifier
> >> > +};
> >> > +
> >> > +static int register_cpufreq(void)
> >> > +{
> >> > +     int ret = 0;
> >> > +     unsigned int cpu;
> >> > +     struct cpufreq_policy *policy;
> >> > +
> >> > +     mutex_lock(&cpufreq_reg_lock);
> >> > +
> >> > +     if (cpufreq_cnt)
> >> > +             goto cnt_not_zero;
> >> > +
> >> > +     get_online_cpus();
> >> > +     ret = cpufreq_register_notifier(&cpufreq_trans_nb,
> >> > +                             CPUFREQ_TRANSITION_NOTIFIER);
> >> > +     if (ret)
> >> > +             goto out;
> >> > +
> >> > +     for_each_online_cpu(cpu) {
> >> > +             policy = cpufreq_cpu_get(cpu);
> >> > +             if (policy) {
> >> > +                     add_policy(policy);
> >> > +                     cpufreq_cpu_put(policy);
> >> > +             }
> >> > +     }
> >> > +out:
> >> > +     put_online_cpus();
> >> > +cnt_not_zero:
> >> > +     if (!ret)
> >> > +             cpufreq_cnt++;
> >> > +     mutex_unlock(&cpufreq_reg_lock);
> >> > +     return ret;
> >> > +}
> >> > +
> >> > +static int unregister_cpufreq(void)
> >> > +{
> >> > +     int ret = 0;
> >> > +     int cpu;
> >> > +
> >> > +     mutex_lock(&cpufreq_reg_lock);
> >> > +
> >> > +     if (cpufreq_cnt > 1)
> >> > +             goto out;
> >> > +
> >> > +     cpufreq_unregister_notifier(&cpufreq_trans_nb,
> >> > +                             CPUFREQ_TRANSITION_NOTIFIER);
> >> > +
> >> > +     for (cpu = ARRAY_SIZE(state) - 1; cpu >= 0; cpu--) {
> >> > +             if (!state[cpu])
> >> > +                     continue;
> >> > +             if (state[cpu]->first_cpu == cpu)
> >> > +                     kfree(state[cpu]);
> >> > +             state[cpu] = NULL;
> >> > +     }
> >> > +
> >> > +out:
> >> > +     cpufreq_cnt--;
> >> > +     mutex_unlock(&cpufreq_reg_lock);
> >> > +     return ret;
> >> > +}
> >> > +
> >> > +/* ==================== devfreq part ==================== */
> >> > +
> >> > +static unsigned int interpolate_freq(struct devfreq *df, unsigned int cpu)
> >> > +{
> >> > +     unsigned long *freq_table = df->profile->freq_table;
> >> > +     unsigned int cpu_min = state[cpu]->min_freq;
> >> > +     unsigned int cpu_max = state[cpu]->max_freq;
> >> > +     unsigned int cpu_freq = state[cpu]->freq;
> >> > +     unsigned int dev_min, dev_max, cpu_percent;
> >> > +
> >> > +     if (freq_table) {
> >> > +             dev_min = freq_table[0];
> >> > +             dev_max = freq_table[df->profile->max_state - 1];
> >> > +     } else {
> >> > +             if (df->max_freq <= df->min_freq)
> >> > +                     return 0;
> >> > +             dev_min = df->min_freq;
> >> > +             dev_max = df->max_freq;
> >> > +     }
> >> > +
> >> > +     cpu_percent = ((cpu_freq - cpu_min) * 100) / (cpu_max - cpu_min);
> >> > +     return dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100);
> >> > +}
> >> > +
> >> > +static unsigned int cpu_to_dev_freq(struct devfreq *df, unsigned int cpu)
> >> > +{
> >> > +     struct freq_map *map = NULL;
> >> > +     unsigned int cpu_khz = 0, freq;
> >> > +     struct devfreq_node *n = df->data;
> >> > +
> >> > +     if (!state[cpu] || state[cpu]->first_cpu != cpu) {
> >> > +             freq = 0;
> >> > +             goto out;
> >> > +     }
> >> > +
> >> > +     if (n->common_map)
> >> > +             map = n->common_map;
> >> > +     else if (n->map)
> >> > +             map = n->map[cpu];
> >> > +
> >> > +     cpu_khz = state[cpu]->freq;
> >> > +
> >> > +     if (!map) {
> >> > +             freq = interpolate_freq(df, cpu);
> >> > +             goto out;
> >> > +     }
> >> > +
> >> > +     while (map->cpu_khz && map->cpu_khz < cpu_khz)
> >> > +             map++;
> >> > +     if (!map->cpu_khz)
> >> > +             map--;
> >> > +     freq = map->target_freq;
> >> > +
> >> > +out:
> >> > +     dev_dbg(df->dev.parent, "CPU%u: %d -> dev: %u\n", cpu, cpu_khz, freq);
> >> > +     return freq;
> >> > +}
> >> > +
> >> > +static int devfreq_cpufreq_get_freq(struct devfreq *df,
> >> > +                                     unsigned long *freq)
> >> > +{
> >> > +     unsigned int cpu, tgt_freq = 0;
> >> > +     struct devfreq_node *node;
> >> > +
> >> > +     node = df->data;
> >> > +     if (!node) {
> >> > +             pr_err("Unable to find devfreq node!\n");
> >> > +             return -ENODEV;
> >> > +     }
> >> > +
> >> > +     for_each_possible_cpu(cpu)
> >> > +             tgt_freq = max(tgt_freq, cpu_to_dev_freq(df, cpu));
> >> > +
> >> > +     *freq = tgt_freq;
> >> > +     return 0;
> >> > +}
> >> > +
> >> > +static unsigned int show_table(char *buf, unsigned int len,
> >> > +                             struct freq_map *map)
> >> > +{
> >> > +     unsigned int cnt = 0;
> >> > +
> >> > +     cnt += snprintf(buf + cnt, len - cnt, "CPU freq\tDevice freq\n");
> >> > +
> >> > +     while (map->cpu_khz && cnt < len) {
> >> > +             cnt += snprintf(buf + cnt, len - cnt, "%8u\t%11u\n",
> >> > +                             map->cpu_khz, map->target_freq);
> >> > +             map++;
> >> > +     }
> >> > +     if (cnt < len)
> >> > +             cnt += snprintf(buf + cnt, len - cnt, "\n");
> >> > +
> >> > +     return cnt;
> >> > +}
> >> > +
> >> > +static ssize_t freq_map_show(struct device *dev, struct device_attribute *attr,
> >> > +                     char *buf)
> >> > +{
> >> > +     struct devfreq *df = to_devfreq(dev);
> >> > +     struct devfreq_node *n = df->data;
> >> > +     struct freq_map *map;
> >> > +     unsigned int cnt = 0, cpu;
> >> > +
> >> > +     mutex_lock(&state_lock);
> >> > +     if (n->common_map) {
> >> > +             map = n->common_map;
> >> > +             cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> >> > +                             "Common table for all CPUs:\n");
> >> > +             cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
> >> > +     } else if (n->map) {
> >> > +             for_each_possible_cpu(cpu) {
> >> > +                     map = n->map[cpu];
> >> > +                     if (!map)
> >> > +                             continue;
> >> > +                     cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> >> > +                                     "CPU %u:\n", cpu);
> >> > +                     if (cnt >= PAGE_SIZE)
> >> > +                             break;
> >> > +                     cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
> >> > +                     if (cnt >= PAGE_SIZE)
> >> > +                             break;
> >> > +             }
> >> > +     } else {
> >> > +             cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> >> > +                             "Device freq interpolated based on CPU freq\n");
> >> > +     }
> >> > +     mutex_unlock(&state_lock);
> >> > +
> >> > +     return cnt;
> >> > +}
> >> > +
> >> > +static DEVICE_ATTR_RO(freq_map);
> >> > +static struct attribute *dev_attr[] = {
> >> > +     &dev_attr_freq_map.attr,
> >> > +     NULL,
> >> > +};
> >> > +
> >> > +static struct attribute_group dev_attr_group = {
> >> > +     .name = "cpufreq-map",
> >> > +     .attrs = dev_attr,
> >> > +};
> >> > +
> >> > +static int devfreq_cpufreq_gov_start(struct devfreq *devfreq)
> >> > +{
> >> > +     int ret = 0;
> >> > +     struct devfreq_node *node;
> >> > +     bool alloc = false;
> >> > +
> >> > +     ret = register_cpufreq();
> >> > +     if (ret)
> >> > +             return ret;
> >> > +
> >> > +     ret = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group);
> >> > +     if (ret) {
> >> > +             unregister_cpufreq();
> >> > +             return ret;
> >> > +     }
> >> > +
> >> > +     mutex_lock(&state_lock);
> >> > +
> >> > +     node = find_devfreq_node(devfreq->dev.parent);
> >> > +     if (node == NULL) {
> >> > +             node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
> >> > +             if (!node) {
> >> > +                     ret = -ENOMEM;
> >> > +                     goto alloc_fail;
> >> > +             }
> >> > +             alloc = true;
> >> > +             node->dev = devfreq->dev.parent;
> >> > +             list_add_tail(&node->list, &devfreq_list);
> >> > +     }
> >> > +     node->df = devfreq;
> >> > +     node->orig_data = devfreq->data;
> >> > +     devfreq->data = node;
> >> > +
> >> > +     mutex_lock(&devfreq->lock);
> >> > +     ret = update_devfreq(devfreq);
> >> > +     mutex_unlock(&devfreq->lock);
> >> > +     if (ret) {
> >> > +             pr_err("Freq update failed!\n");
> >> > +             goto update_fail;
> >> > +     }
> >> > +
> >> > +     mutex_unlock(&state_lock);
> >> > +     return 0;
> >> > +
> >> > +update_fail:
> >> > +     devfreq->data = node->orig_data;
> >> > +     if (alloc) {
> >> > +             list_del(&node->list);
> >> > +             kfree(node);
> >> > +     }
> >> > +alloc_fail:
> >> > +     mutex_unlock(&state_lock);
> >> > +     sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
> >> > +     unregister_cpufreq();
> >> > +     return ret;
> >> > +}
> >> > +
> >> > +static void devfreq_cpufreq_gov_stop(struct devfreq *devfreq)
> >> > +{
> >> > +     struct devfreq_node *node = devfreq->data;
> >> > +
> >> > +     mutex_lock(&state_lock);
> >> > +     devfreq->data = node->orig_data;
> >> > +     if (node->map || node->common_map) {
> >> > +             node->df = NULL;
> >> > +     } else {
> >> > +             list_del(&node->list);
> >> > +             kfree(node);
> >> > +     }
> >> > +     mutex_unlock(&state_lock);
> >> > +
> >> > +     sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
> >> > +     unregister_cpufreq();
> >> > +}
> >> > +
> >> > +static int devfreq_cpufreq_ev_handler(struct devfreq *devfreq,
> >> > +                                     unsigned int event, void *data)
> >> > +{
> >> > +     int ret;
> >> > +
> >> > +     switch (event) {
> >> > +     case DEVFREQ_GOV_START:
> >> > +
> >> > +             ret = devfreq_cpufreq_gov_start(devfreq);
> >> > +             if (ret) {
> >> > +                     pr_err("Governor start failed!\n");
> >> > +                     return ret;
> >> > +             }
> >> > +             pr_debug("Enabled CPUfreq-map governor\n");
> >> > +             break;
> >> > +
> >> > +     case DEVFREQ_GOV_STOP:
> >> > +
> >> > +             devfreq_cpufreq_gov_stop(devfreq);
> >> > +             pr_debug("Disabled dev CPUfreq-map governor\n");
> >> > +             break;
> >> > +     }
> >> > +
> >> > +     return 0;
> >> > +}
> >> > +
> >> > +static struct devfreq_governor devfreq_cpufreq = {
> >> > +     .name = "cpufreq-map",
> >> > +     .get_target_freq = devfreq_cpufreq_get_freq,
> >> > +     .event_handler = devfreq_cpufreq_ev_handler,
> >> > +};
> >> > +
> >> > +#define NUM_COLS     2
> >> > +static struct freq_map *read_tbl(struct device_node *of_node, char *prop_name)
> >> > +{
> >> > +     int len, nf, i, j;
> >> > +     u32 data;
> >> > +     struct freq_map *tbl;
> >> > +
> >> > +     if (!of_find_property(of_node, prop_name, &len))
> >> > +             return NULL;
> >> > +     len /= sizeof(data);
> >> > +
> >> > +     if (len % NUM_COLS || len == 0)
> >> > +             return NULL;
> >> > +     nf = len / NUM_COLS;
> >> > +
> >> > +     tbl = kzalloc((nf + 1) * sizeof(*tbl), GFP_KERNEL);
> >> > +     if (!tbl)
> >> > +             return NULL;
> >> > +
> >> > +     for (i = 0, j = 0; i < nf; i++, j += 2) {
> >> > +             of_property_read_u32_index(of_node, prop_name, j, &data);
> >> > +             tbl[i].cpu_khz = data;
> >> > +
> >> > +             of_property_read_u32_index(of_node, prop_name, j + 1, &data);
> >> > +             tbl[i].target_freq = data;
> >> > +     }
> >> > +     tbl[i].cpu_khz = 0;
> >> > +
> >> > +     return tbl;
> >> > +}
> >> > +
> >> > +#define PROP_TARGET "target-dev"
> >> > +#define PROP_TABLE "cpu-to-dev-map"
> >> > +static int add_table_from_of(struct device_node *of_node)
> >> > +{
> >> > +     struct device_node *target_of_node;
> >> > +     struct devfreq_node *node;
> >> > +     struct freq_map *common_tbl;
> >> > +     struct freq_map **tbl_list = NULL;
> >> > +     static char prop_name[] = PROP_TABLE "-999999";
> >> > +     int cpu, ret, cnt = 0, prop_sz = ARRAY_SIZE(prop_name);
> >> > +
> >> > +     target_of_node = of_parse_phandle(of_node, PROP_TARGET, 0);
> >> > +     if (!target_of_node)
> >> > +             return -EINVAL;
> >> > +
> >> > +     node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
> >> > +     if (!node)
> >> > +             return -ENOMEM;
> >> > +
> >> > +     common_tbl = read_tbl(of_node, PROP_TABLE);
> >> > +     if (!common_tbl) {
> >> > +             tbl_list = kzalloc(sizeof(*tbl_list) * NR_CPUS, GFP_KERNEL);
> >> > +             if (!tbl_list) {
> >> > +                     ret = -ENOMEM;
> >> > +                     goto err_list;
> >> > +             }
> >> > +
> >> > +             for_each_possible_cpu(cpu) {
> >> > +                     ret = snprintf(prop_name, prop_sz, "%s-%d",
> >> > +                                     PROP_TABLE, cpu);
> >> > +                     if (ret >= prop_sz) {
> >> > +                             pr_warn("More CPUs than I can handle!\n");
> >> > +                             pr_warn("Skipping rest of the tables!\n");
> >> > +                             break;
> >> > +                     }
> >> > +                     tbl_list[cpu] = read_tbl(of_node, prop_name);
> >> > +                     if (tbl_list[cpu])
> >> > +                             cnt++;
> >> > +             }
> >> > +     }
> >> > +     if (!common_tbl && !cnt) {
> >> > +             ret = -EINVAL;
> >> > +             goto err_tbl;
> >> > +     }
> >> > +
> >> > +     mutex_lock(&state_lock);
> >> > +     node->of_node = target_of_node;
> >> > +     node->map = tbl_list;
> >> > +     node->common_map = common_tbl;
> >> > +     list_add_tail(&node->list, &devfreq_list);
> >> > +     mutex_unlock(&state_lock);
> >> > +
> >> > +     return 0;
> >> > +err_tbl:
> >> > +     kfree(tbl_list);
> >> > +err_list:
> >> > +     kfree(node);
> >> > +     return ret;
> >> > +}
> >> > +
> >> > +static int __init devfreq_cpufreq_init(void)
> >> > +{
> >> > +     int ret;
> >> > +     struct device_node *of_par, *of_child;
> >> > +
> >> > +     of_par = of_find_node_by_name(NULL, "devfreq-cpufreq-map");
> >> > +     if (of_par) {
> >> > +             for_each_child_of_node(of_par, of_child) {
> >> > +                     ret = add_table_from_of(of_child);
> >> > +                     if (ret)
> >> > +                             pr_err("Parsing %s failed!\n", of_child->name);
> >> > +                     else
> >> > +                             pr_debug("Parsed %s.\n", of_child->name);
> >> > +             }
> >> > +             of_node_put(of_par);
> >> > +     } else {
> >> > +             pr_info("No tables parsed from DT.\n");
> >> > +     }
> >> > +
> >> > +     ret = devfreq_add_governor(&devfreq_cpufreq);
> >> > +     if (ret) {
> >> > +             pr_err("cpufreq-map governor add failed!\n");
> >> > +             return ret;
> >> > +     }
> >> > +
> >> > +     return 0;
> >> > +}
> >> > +subsys_initcall(devfreq_cpufreq_init);
> >> > +
> >> > +static void __exit devfreq_cpufreq_exit(void)
> >> > +{
> >> > +     int ret, cpu;
> >> > +     struct devfreq_node *node, *tmp;
> >> > +
> >> > +     ret = devfreq_remove_governor(&devfreq_cpufreq);
> >> > +     if (ret)
> >> > +             pr_err("cpufreq-map governor remove failed!\n");
> >> > +
> >> > +     mutex_lock(&state_lock);
> >> > +     list_for_each_entry_safe(node, tmp, &devfreq_list, list) {
> >> > +             kfree(node->common_map);
> >> > +             for_each_possible_cpu(cpu)
> >> > +                     kfree(node->map[cpu]);
> >> > +             kfree(node->map);
> >> > +             list_del(&node->list);
> >> > +             kfree(node);
> >> > +     }
> >> > +     mutex_unlock(&state_lock);
> >> > +}
> >> > +module_exit(devfreq_cpufreq_exit);
> >> > +
> >> > +MODULE_DESCRIPTION("devfreq gov that sets dev freq based on current CPU freq");
> >> > +MODULE_LICENSE("GPL v2");
> >> >
>
> --
> -- Sibi Sankar --
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
> a Linux Foundation Collaborative Project.

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor
  2019-06-20  9:41       ` Sibi Sankar
  2019-06-20  9:43         ` Hsin-Yi Wang
@ 2019-06-26  8:12         ` Chanwoo Choi
  1 sibling, 0 replies; 8+ messages in thread
From: Chanwoo Choi @ 2019-06-26  8:12 UTC (permalink / raw)
  To: Sibi Sankar, Hsin-Yi Wang
  Cc: linux-kernel-owner, Andrew-sh . Cheng, linux-pm, lkml,
	Kyungmin Park, Saravana Kannan, linux-mediatek, MyungJoo Ham,
	Matthias Brugger,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE

Hello Sibi and Hsin-Yi,

On 19. 6. 20. 오후 6:41, Sibi Sankar wrote:
> Hey Hsin-Yi, Chanwoo
> 
> On 2019-06-20 15:02, Hsin-Yi Wang wrote:
>> Hi Chanwoo Choi, Saravana Kannan and Sibi Sankar,
>>
>> I've also tested Sibi Sankar's patch[1] locally with mt8183-cci, and
>> it works fine too!
>> It'd be great if Sibi Sankar or anyone who is familiar with the
>> original design can finish this implementation. But if no one has time
>> to do that, I think I can also help on address the comments. Thanks!
> 
> Now that we have a user :) I am happy
> to repost the patch with the comments
> addressed.
> 
> https://lkml.org/lkml/2019/6/14/4
> Also with ^^ patch and few more in the
> series the dt parsing of required-opps
> should get further simplified.

Even if patch[1] suggested by Saravana is merged,
the patch[2] is necessary. Because, until now,
the child devfreq device cannot catch the timing
of CPU frequency without CPUFREQ notification.

The existing passive governor only supports between
devfreq device and other devfreq device.

[1] https://lkml.org/lkml/2019/6/14/4

[2]
[PATCH RFC 0/9] Add CPU based scaling support to Passive governor
- https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/
[PATCH RFC 3/9] PM / devfreq: Add cpu based scaling support to passive_governor
- https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/#m1cafb7baf687d2a680d39c85d3ec7d1b590b68fc



> 
>>
>>
>> [1]
>> [RFC,2/9] OPP: Export a number of helpers to prevent code duplication
>> - https://patchwork.kernel.org/patch/10875199/
>> [RFC,3/9] PM / devfreq: Add cpu based scaling support to passive_governor
>> - https://patchwork.kernel.org/patch/10875195/
>>
>> Hsin-Yi
>>
>> On Thu, Jun 20, 2019 at 2:56 PM Chanwoo Choi <cw00.choi@samsung.com> wrote:
>>>
>>> + Sibi Sankar
>>>
>>> Hi, Hsin-Yi Wang, Saravana Kannan and Sibi Sankar
>>>
>>> I summarized the history of the related patch about this title.
>>>
>>> Firstly,
>>> As I knew, Saravana sent the patch[1] which contains
>>> 'governor_cpufreq_map.c' last year. According to the Myungoo's comment,
>>>
>>> Secondly,
>>> Sibi Sankar modified the 'governor_passive.c'[2] in order to support
>>> the mapping between cpu frequency and device frequency.
>>> Unfortunately, Sibi Sankar stopped the development about this
>>> because he had found the other method to get his purpose as I knew.
>>>
>>> Thirdly,
>>> Hsin-Yi Wang send the original patch of Saravana without modification.
>>>
>>>
>>> Sincerely, I think that the mapping between cpu frequency and device
>>> frequency is necessary. And I prefer the Sibi's approach which implements
>>> stuff to the existing 'passive' governor.
>>>
>>> We need to discuss about how to implement them by whom.
>>>
>>>
>>> [1] [v3,1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor
>>> - https://patchwork.kernel.org/patch/10553171/
>>>
>>> [2]
>>> [PATCH RFC 0/9] Add CPU based scaling support to Passive governor
>>> - https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/
>>> [PATCH RFC 3/9] PM / devfreq: Add cpu based scaling support to passive_governor
>>> - https://lore.kernel.org/lkml/08c3cff8c39e3d82e044db93e992da72@codeaurora.org/T/#m1cafb7baf687d2a680d39c85d3ec7d1b590b68fc
>>>
>>>
>>> Best Regards,
>>> Chanwoo Choi
>>>
>>> On 19. 6. 18. 오후 1:14, Hsin-Yi Wang wrote:
>>> > From: Saravana Kannan <skannan@codeaurora.org>
>>> >
>>> > From: Saravana Kannan <skannan@codeaurora.org>
>>> >
>>> > Many CPU architectures have caches that can scale independent of the CPUs.
>>> > Frequency scaling of the caches is necessary to make sure the cache is not
>>> > a performance bottleneck that leads to poor performance and power. The same
>>> > idea applies for RAM/DDR.
>>> >
>>> > To achieve this, this patch adds a generic devfreq governor that takes the
>>> > current frequency of each CPU frequency domain and then adjusts the
>>> > frequency of the cache (or any devfreq device) based on the frequency of
>>> > the CPUs. It listens to CPU frequency transition notifiers to keep itself
>>> > up to date on the current CPU frequency.
>>> >
>>> > To decide the frequency of the device, the governor does one of the
>>> > following:
>>> >
>>> > * Uses a CPU frequency to device frequency mapping table
>>> >   - Either one mapping table used for all CPU freq policies (typically used
>>> >     for system with homogeneous cores/clusters that have the same OPPs).
>>> >   - One mapping table per CPU freq policy (typically used for ASMP systems
>>> >     with heterogeneous CPUs with different OPPs)
>>> >
>>> > OR
>>> >
>>> > * Scales the device frequency in proportion to the CPU frequency. So, if
>>> >   the CPUs are running at their max frequency, the device runs at its max
>>> >   frequency.  If the CPUs are running at their min frequency, the device
>>> >   runs at its min frequency. And interpolated for frequencies in between.
>>> >
>>> > Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
>>> > Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
>>> > ---
>>> >  .../bindings/devfreq/devfreq-cpufreq-map.txt  |  53 ++
>>> >  drivers/devfreq/Kconfig                       |   8 +
>>> >  drivers/devfreq/Makefile                      |   1 +
>>> >  drivers/devfreq/governor_cpufreq_map.c        | 583 ++++++++++++++++++
>>> >  4 files changed, 645 insertions(+)
>>> >  create mode 100644 Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
>>> >  create mode 100644 drivers/devfreq/governor_cpufreq_map.c
>>> >
>>> > diff --git a/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
>>> > new file mode 100644
>>> > index 000000000000..982a30bcfc86
>>> > --- /dev/null
>>> > +++ b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq-map.txt
>>> > @@ -0,0 +1,53 @@
>>> > +Devfreq CPUfreq governor
>>> > +
>>> > +devfreq-cpufreq-map is a parent device that contains one or more child devices.
>>> > +Each child device provides CPU frequency to device frequency mapping for a
>>> > +specific device. Examples of devices that could use this are: DDR, cache and
>>> > +CCI.
>>> > +
>>> > +Parent device name shall be "devfreq-cpufreq-map".
>>> > +
>>> > +Required child device properties:
>>> > +- cpu-to-dev-map, or cpu-to-dev-map-<X>:
>>> > +                     A list of tuples where each tuple consists of a
>>> > +                     CPU frequency (KHz) and the corresponding device
>>> > +                     frequency. CPU frequencies not listed in the table
>>> > +                     will use the device frequency that corresponds to the
>>> > +                     next rounded up CPU frequency.
>>> > +                     Use "cpu-to-dev-map" if all CPUs in the system should
>>> > +                     share same mapping.
>>> > +                     Use cpu-to-dev-map-<cpuid> to describe different
>>> > +                     mappings for different CPUs. The property should be
>>> > +                     listed only for the first CPU if multiple CPUs are
>>> > +                     synchronous.
>>> > +- target-dev:                Phandle to device that this mapping applies to.
>>> > +
>>> > +Example:
>>> > +     devfreq-cpufreq-map {
>>> > +             cpubw-cpufreq {
>>> > +                     target-dev = <&cpubw>;
>>> > +                     cpu-to-dev-map =
>>> > +                             <  300000  1144000 >,
>>> > +                             <  422400  2288000 >,
>>> > +                             <  652800  3051000 >,
>>> > +                             <  883200  5996000 >,
>>> > +                             < 1190400  8056000 >,
>>> > +                             < 1497600 10101000 >,
>>> > +                             < 1728000 12145000 >,
>>> > +                             < 2649600 16250000 >;
>>> > +             };
>>> > +
>>> > +             cache-cpufreq {
>>> > +                     target-dev = <&cache>;
>>> > +                     cpu-to-dev-map =
>>> > +                             <  300000  300000 >,
>>> > +                             <  422400  422400 >,
>>> > +                             <  652800  499200 >,
>>> > +                             <  883200  576000 >,
>>> > +                             <  960000  960000 >,
>>> > +                             < 1497600 1036800 >,
>>> > +                             < 1574400 1574400 >,
>>> > +                             < 1728000 1651200 >,
>>> > +                             < 2649600 1728000 >;
>>> > +             };
>>> > +     };
>>> > diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
>>> > index 0c8204d6b78a..0303f5a400b6 100644
>>> > --- a/drivers/devfreq/Kconfig
>>> > +++ b/drivers/devfreq/Kconfig
>>> > @@ -74,6 +74,14 @@ config DEVFREQ_GOV_PASSIVE
>>> >         through sysfs entries. The passive governor recommends that
>>> >         devfreq device uses the OPP table to get the frequency/voltage.
>>> >
>>> > +config DEVFREQ_GOV_CPUFREQ_MAP
>>> > +     tristate "CPUfreq Map"
>>> > +     depends on CPU_FREQ
>>> > +     help
>>> > +       Chooses frequency based on the online CPUs' current frequency and a
>>> > +       CPU frequency to device frequency mapping table(s). This governor
>>> > +       can be useful for controlling devices such as DDR, cache, CCI, etc.
>>> > +
>>> >  comment "DEVFREQ Drivers"
>>> >
>>> >  config ARM_EXYNOS_BUS_DEVFREQ
>>> > diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
>>> > index 817dde779f16..81141e2c784f 100644
>>> > --- a/drivers/devfreq/Makefile
>>> > +++ b/drivers/devfreq/Makefile
>>> > @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o
>>> >  obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE)  += governor_powersave.o
>>> >  obj-$(CONFIG_DEVFREQ_GOV_USERSPACE)  += governor_userspace.o
>>> >  obj-$(CONFIG_DEVFREQ_GOV_PASSIVE)    += governor_passive.o
>>> > +obj-$(CONFIG_DEVFREQ_GOV_CPUFREQ_MAP)        += governor_cpufreq_map.o
>>> >
>>> >  # DEVFREQ Drivers
>>> >  obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
>>> > diff --git a/drivers/devfreq/governor_cpufreq_map.c b/drivers/devfreq/governor_cpufreq_map.c
>>> > new file mode 100644
>>> > index 000000000000..084a3ffb8f54
>>> > --- /dev/null
>>> > +++ b/drivers/devfreq/governor_cpufreq_map.c
>>> > @@ -0,0 +1,583 @@
>>> > +// SPDX-License-Identifier: GPL-2.0
>>> > +/*
>>> > + * Copyright (c) 2014-2015, 2018, The Linux Foundation. All rights reserved.
>>> > + */
>>> > +
>>> > +#define pr_fmt(fmt) "dev-cpufreq-map: " fmt
>>> > +
>>> > +#include <linux/devfreq.h>
>>> > +#include <linux/cpu.h>
>>> > +#include <linux/cpufreq.h>
>>> > +#include <linux/cpumask.h>
>>> > +#include <linux/slab.h>
>>> > +#include <linux/platform_device.h>
>>> > +#include <linux/of.h>
>>> > +#include <linux/module.h>
>>> > +#include "governor.h"
>>> > +
>>> > +struct cpu_state {
>>> > +     unsigned int freq;
>>> > +     unsigned int min_freq;
>>> > +     unsigned int max_freq;
>>> > +     unsigned int first_cpu;
>>> > +};
>>> > +static struct cpu_state *state[NR_CPUS];
>>> > +static int cpufreq_cnt;
>>> > +
>>> > +struct freq_map {
>>> > +     unsigned int cpu_khz;
>>> > +     unsigned int target_freq;
>>> > +};
>>> > +
>>> > +struct devfreq_node {
>>> > +     struct devfreq *df;
>>> > +     void *orig_data;
>>> > +     struct device *dev;
>>> > +     struct device_node *of_node;
>>> > +     struct list_head list;
>>> > +     struct freq_map **map;
>>> > +     struct freq_map *common_map;
>>> > +};
>>> > +static LIST_HEAD(devfreq_list);
>>> > +static DEFINE_MUTEX(state_lock);
>>> > +static DEFINE_MUTEX(cpufreq_reg_lock);
>>> > +
>>> > +static void update_all_devfreqs(void)
>>> > +{
>>> > +     struct devfreq_node *node;
>>> > +
>>> > +     list_for_each_entry(node, &devfreq_list, list) {
>>> > +             struct devfreq *df = node->df;
>>> > +
>>> > +             if (!node->df)
>>> > +                     continue;
>>> > +             mutex_lock(&df->lock);
>>> > +             update_devfreq(df);
>>> > +             mutex_unlock(&df->lock);
>>> > +
>>> > +     }
>>> > +}
>>> > +
>>> > +static struct devfreq_node *find_devfreq_node(struct device *dev)
>>> > +{
>>> > +     struct devfreq_node *node;
>>> > +
>>> > +     list_for_each_entry(node, &devfreq_list, list)
>>> > +             if (node->dev == dev || node->of_node == dev->of_node)
>>> > +                     return node;
>>> > +
>>> > +     return NULL;
>>> > +}
>>> > +
>>> > +/* ==================== cpufreq part ==================== */
>>> > +static struct cpu_state *add_policy(struct cpufreq_policy *policy)
>>> > +{
>>> > +     struct cpu_state *new_state;
>>> > +     unsigned int cpu, first_cpu;
>>> > +
>>> > +     new_state = kzalloc(sizeof(struct cpu_state), GFP_KERNEL);
>>> > +     if (!new_state)
>>> > +             return NULL;
>>> > +
>>> > +     first_cpu = cpumask_first(policy->related_cpus);
>>> > +     new_state->first_cpu = first_cpu;
>>> > +     new_state->freq = policy->cur;
>>> > +     new_state->min_freq = policy->cpuinfo.min_freq;
>>> > +     new_state->max_freq = policy->cpuinfo.max_freq;
>>> > +
>>> > +     for_each_cpu(cpu, policy->related_cpus)
>>> > +             state[cpu] = new_state;
>>> > +
>>> > +     return new_state;
>>> > +}
>>> > +
>>> > +static int cpufreq_trans_notifier(struct notifier_block *nb,
>>> > +             unsigned long event, void *data)
>>> > +{
>>> > +     struct cpufreq_freqs *freq = data;
>>> > +     struct cpu_state *s;
>>> > +     struct cpufreq_policy *policy = NULL;
>>> > +
>>> > +     if (event != CPUFREQ_POSTCHANGE)
>>> > +             return 0;
>>> > +
>>> > +     mutex_lock(&state_lock);
>>> > +
>>> > +     s = state[freq->cpu];
>>> > +     if (!s) {
>>> > +             policy = cpufreq_cpu_get(freq->cpu);
>>> > +             if (policy) {
>>> > +                     s = add_policy(policy);
>>> > +                     cpufreq_cpu_put(policy);
>>> > +             }
>>> > +     }
>>> > +     if (!s)
>>> > +             goto out;
>>> > +
>>> > +     if (s->freq != freq->new || policy) {
>>> > +             s->freq = freq->new;
>>> > +             update_all_devfreqs();
>>> > +     }
>>> > +
>>> > +out:
>>> > +     mutex_unlock(&state_lock);
>>> > +     return 0;
>>> > +}
>>> > +
>>> > +static struct notifier_block cpufreq_trans_nb = {
>>> > +     .notifier_call = cpufreq_trans_notifier
>>> > +};
>>> > +
>>> > +static int register_cpufreq(void)
>>> > +{
>>> > +     int ret = 0;
>>> > +     unsigned int cpu;
>>> > +     struct cpufreq_policy *policy;
>>> > +
>>> > +     mutex_lock(&cpufreq_reg_lock);
>>> > +
>>> > +     if (cpufreq_cnt)
>>> > +             goto cnt_not_zero;
>>> > +
>>> > +     get_online_cpus();
>>> > +     ret = cpufreq_register_notifier(&cpufreq_trans_nb,
>>> > +                             CPUFREQ_TRANSITION_NOTIFIER);
>>> > +     if (ret)
>>> > +             goto out;
>>> > +
>>> > +     for_each_online_cpu(cpu) {
>>> > +             policy = cpufreq_cpu_get(cpu);
>>> > +             if (policy) {
>>> > +                     add_policy(policy);
>>> > +                     cpufreq_cpu_put(policy);
>>> > +             }
>>> > +     }
>>> > +out:
>>> > +     put_online_cpus();
>>> > +cnt_not_zero:
>>> > +     if (!ret)
>>> > +             cpufreq_cnt++;
>>> > +     mutex_unlock(&cpufreq_reg_lock);
>>> > +     return ret;
>>> > +}
>>> > +
>>> > +static int unregister_cpufreq(void)
>>> > +{
>>> > +     int ret = 0;
>>> > +     int cpu;
>>> > +
>>> > +     mutex_lock(&cpufreq_reg_lock);
>>> > +
>>> > +     if (cpufreq_cnt > 1)
>>> > +             goto out;
>>> > +
>>> > +     cpufreq_unregister_notifier(&cpufreq_trans_nb,
>>> > +                             CPUFREQ_TRANSITION_NOTIFIER);
>>> > +
>>> > +     for (cpu = ARRAY_SIZE(state) - 1; cpu >= 0; cpu--) {
>>> > +             if (!state[cpu])
>>> > +                     continue;
>>> > +             if (state[cpu]->first_cpu == cpu)
>>> > +                     kfree(state[cpu]);
>>> > +             state[cpu] = NULL;
>>> > +     }
>>> > +
>>> > +out:
>>> > +     cpufreq_cnt--;
>>> > +     mutex_unlock(&cpufreq_reg_lock);
>>> > +     return ret;
>>> > +}
>>> > +
>>> > +/* ==================== devfreq part ==================== */
>>> > +
>>> > +static unsigned int interpolate_freq(struct devfreq *df, unsigned int cpu)
>>> > +{
>>> > +     unsigned long *freq_table = df->profile->freq_table;
>>> > +     unsigned int cpu_min = state[cpu]->min_freq;
>>> > +     unsigned int cpu_max = state[cpu]->max_freq;
>>> > +     unsigned int cpu_freq = state[cpu]->freq;
>>> > +     unsigned int dev_min, dev_max, cpu_percent;
>>> > +
>>> > +     if (freq_table) {
>>> > +             dev_min = freq_table[0];
>>> > +             dev_max = freq_table[df->profile->max_state - 1];
>>> > +     } else {
>>> > +             if (df->max_freq <= df->min_freq)
>>> > +                     return 0;
>>> > +             dev_min = df->min_freq;
>>> > +             dev_max = df->max_freq;
>>> > +     }
>>> > +
>>> > +     cpu_percent = ((cpu_freq - cpu_min) * 100) / (cpu_max - cpu_min);
>>> > +     return dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100);
>>> > +}
>>> > +
>>> > +static unsigned int cpu_to_dev_freq(struct devfreq *df, unsigned int cpu)
>>> > +{
>>> > +     struct freq_map *map = NULL;
>>> > +     unsigned int cpu_khz = 0, freq;
>>> > +     struct devfreq_node *n = df->data;
>>> > +
>>> > +     if (!state[cpu] || state[cpu]->first_cpu != cpu) {
>>> > +             freq = 0;
>>> > +             goto out;
>>> > +     }
>>> > +
>>> > +     if (n->common_map)
>>> > +             map = n->common_map;
>>> > +     else if (n->map)
>>> > +             map = n->map[cpu];
>>> > +
>>> > +     cpu_khz = state[cpu]->freq;
>>> > +
>>> > +     if (!map) {
>>> > +             freq = interpolate_freq(df, cpu);
>>> > +             goto out;
>>> > +     }
>>> > +
>>> > +     while (map->cpu_khz && map->cpu_khz < cpu_khz)
>>> > +             map++;
>>> > +     if (!map->cpu_khz)
>>> > +             map--;
>>> > +     freq = map->target_freq;
>>> > +
>>> > +out:
>>> > +     dev_dbg(df->dev.parent, "CPU%u: %d -> dev: %u\n", cpu, cpu_khz, freq);
>>> > +     return freq;
>>> > +}
>>> > +
>>> > +static int devfreq_cpufreq_get_freq(struct devfreq *df,
>>> > +                                     unsigned long *freq)
>>> > +{
>>> > +     unsigned int cpu, tgt_freq = 0;
>>> > +     struct devfreq_node *node;
>>> > +
>>> > +     node = df->data;
>>> > +     if (!node) {
>>> > +             pr_err("Unable to find devfreq node!\n");
>>> > +             return -ENODEV;
>>> > +     }
>>> > +
>>> > +     for_each_possible_cpu(cpu)
>>> > +             tgt_freq = max(tgt_freq, cpu_to_dev_freq(df, cpu));
>>> > +
>>> > +     *freq = tgt_freq;
>>> > +     return 0;
>>> > +}
>>> > +
>>> > +static unsigned int show_table(char *buf, unsigned int len,
>>> > +                             struct freq_map *map)
>>> > +{
>>> > +     unsigned int cnt = 0;
>>> > +
>>> > +     cnt += snprintf(buf + cnt, len - cnt, "CPU freq\tDevice freq\n");
>>> > +
>>> > +     while (map->cpu_khz && cnt < len) {
>>> > +             cnt += snprintf(buf + cnt, len - cnt, "%8u\t%11u\n",
>>> > +                             map->cpu_khz, map->target_freq);
>>> > +             map++;
>>> > +     }
>>> > +     if (cnt < len)
>>> > +             cnt += snprintf(buf + cnt, len - cnt, "\n");
>>> > +
>>> > +     return cnt;
>>> > +}
>>> > +
>>> > +static ssize_t freq_map_show(struct device *dev, struct device_attribute *attr,
>>> > +                     char *buf)
>>> > +{
>>> > +     struct devfreq *df = to_devfreq(dev);
>>> > +     struct devfreq_node *n = df->data;
>>> > +     struct freq_map *map;
>>> > +     unsigned int cnt = 0, cpu;
>>> > +
>>> > +     mutex_lock(&state_lock);
>>> > +     if (n->common_map) {
>>> > +             map = n->common_map;
>>> > +             cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
>>> > +                             "Common table for all CPUs:\n");
>>> > +             cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
>>> > +     } else if (n->map) {
>>> > +             for_each_possible_cpu(cpu) {
>>> > +                     map = n->map[cpu];
>>> > +                     if (!map)
>>> > +                             continue;
>>> > +                     cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
>>> > +                                     "CPU %u:\n", cpu);
>>> > +                     if (cnt >= PAGE_SIZE)
>>> > +                             break;
>>> > +                     cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
>>> > +                     if (cnt >= PAGE_SIZE)
>>> > +                             break;
>>> > +             }
>>> > +     } else {
>>> > +             cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
>>> > +                             "Device freq interpolated based on CPU freq\n");
>>> > +     }
>>> > +     mutex_unlock(&state_lock);
>>> > +
>>> > +     return cnt;
>>> > +}
>>> > +
>>> > +static DEVICE_ATTR_RO(freq_map);
>>> > +static struct attribute *dev_attr[] = {
>>> > +     &dev_attr_freq_map.attr,
>>> > +     NULL,
>>> > +};
>>> > +
>>> > +static struct attribute_group dev_attr_group = {
>>> > +     .name = "cpufreq-map",
>>> > +     .attrs = dev_attr,
>>> > +};
>>> > +
>>> > +static int devfreq_cpufreq_gov_start(struct devfreq *devfreq)
>>> > +{
>>> > +     int ret = 0;
>>> > +     struct devfreq_node *node;
>>> > +     bool alloc = false;
>>> > +
>>> > +     ret = register_cpufreq();
>>> > +     if (ret)
>>> > +             return ret;
>>> > +
>>> > +     ret = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group);
>>> > +     if (ret) {
>>> > +             unregister_cpufreq();
>>> > +             return ret;
>>> > +     }
>>> > +
>>> > +     mutex_lock(&state_lock);
>>> > +
>>> > +     node = find_devfreq_node(devfreq->dev.parent);
>>> > +     if (node == NULL) {
>>> > +             node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
>>> > +             if (!node) {
>>> > +                     ret = -ENOMEM;
>>> > +                     goto alloc_fail;
>>> > +             }
>>> > +             alloc = true;
>>> > +             node->dev = devfreq->dev.parent;
>>> > +             list_add_tail(&node->list, &devfreq_list);
>>> > +     }
>>> > +     node->df = devfreq;
>>> > +     node->orig_data = devfreq->data;
>>> > +     devfreq->data = node;
>>> > +
>>> > +     mutex_lock(&devfreq->lock);
>>> > +     ret = update_devfreq(devfreq);
>>> > +     mutex_unlock(&devfreq->lock);
>>> > +     if (ret) {
>>> > +             pr_err("Freq update failed!\n");
>>> > +             goto update_fail;
>>> > +     }
>>> > +
>>> > +     mutex_unlock(&state_lock);
>>> > +     return 0;
>>> > +
>>> > +update_fail:
>>> > +     devfreq->data = node->orig_data;
>>> > +     if (alloc) {
>>> > +             list_del(&node->list);
>>> > +             kfree(node);
>>> > +     }
>>> > +alloc_fail:
>>> > +     mutex_unlock(&state_lock);
>>> > +     sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
>>> > +     unregister_cpufreq();
>>> > +     return ret;
>>> > +}
>>> > +
>>> > +static void devfreq_cpufreq_gov_stop(struct devfreq *devfreq)
>>> > +{
>>> > +     struct devfreq_node *node = devfreq->data;
>>> > +
>>> > +     mutex_lock(&state_lock);
>>> > +     devfreq->data = node->orig_data;
>>> > +     if (node->map || node->common_map) {
>>> > +             node->df = NULL;
>>> > +     } else {
>>> > +             list_del(&node->list);
>>> > +             kfree(node);
>>> > +     }
>>> > +     mutex_unlock(&state_lock);
>>> > +
>>> > +     sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
>>> > +     unregister_cpufreq();
>>> > +}
>>> > +
>>> > +static int devfreq_cpufreq_ev_handler(struct devfreq *devfreq,
>>> > +                                     unsigned int event, void *data)
>>> > +{
>>> > +     int ret;
>>> > +
>>> > +     switch (event) {
>>> > +     case DEVFREQ_GOV_START:
>>> > +
>>> > +             ret = devfreq_cpufreq_gov_start(devfreq);
>>> > +             if (ret) {
>>> > +                     pr_err("Governor start failed!\n");
>>> > +                     return ret;
>>> > +             }
>>> > +             pr_debug("Enabled CPUfreq-map governor\n");
>>> > +             break;
>>> > +
>>> > +     case DEVFREQ_GOV_STOP:
>>> > +
>>> > +             devfreq_cpufreq_gov_stop(devfreq);
>>> > +             pr_debug("Disabled dev CPUfreq-map governor\n");
>>> > +             break;
>>> > +     }
>>> > +
>>> > +     return 0;
>>> > +}
>>> > +
>>> > +static struct devfreq_governor devfreq_cpufreq = {
>>> > +     .name = "cpufreq-map",
>>> > +     .get_target_freq = devfreq_cpufreq_get_freq,
>>> > +     .event_handler = devfreq_cpufreq_ev_handler,
>>> > +};
>>> > +
>>> > +#define NUM_COLS     2
>>> > +static struct freq_map *read_tbl(struct device_node *of_node, char *prop_name)
>>> > +{
>>> > +     int len, nf, i, j;
>>> > +     u32 data;
>>> > +     struct freq_map *tbl;
>>> > +
>>> > +     if (!of_find_property(of_node, prop_name, &len))
>>> > +             return NULL;
>>> > +     len /= sizeof(data);
>>> > +
>>> > +     if (len % NUM_COLS || len == 0)
>>> > +             return NULL;
>>> > +     nf = len / NUM_COLS;
>>> > +
>>> > +     tbl = kzalloc((nf + 1) * sizeof(*tbl), GFP_KERNEL);
>>> > +     if (!tbl)
>>> > +             return NULL;
>>> > +
>>> > +     for (i = 0, j = 0; i < nf; i++, j += 2) {
>>> > +             of_property_read_u32_index(of_node, prop_name, j, &data);
>>> > +             tbl[i].cpu_khz = data;
>>> > +
>>> > +             of_property_read_u32_index(of_node, prop_name, j + 1, &data);
>>> > +             tbl[i].target_freq = data;
>>> > +     }
>>> > +     tbl[i].cpu_khz = 0;
>>> > +
>>> > +     return tbl;
>>> > +}
>>> > +
>>> > +#define PROP_TARGET "target-dev"
>>> > +#define PROP_TABLE "cpu-to-dev-map"
>>> > +static int add_table_from_of(struct device_node *of_node)
>>> > +{
>>> > +     struct device_node *target_of_node;
>>> > +     struct devfreq_node *node;
>>> > +     struct freq_map *common_tbl;
>>> > +     struct freq_map **tbl_list = NULL;
>>> > +     static char prop_name[] = PROP_TABLE "-999999";
>>> > +     int cpu, ret, cnt = 0, prop_sz = ARRAY_SIZE(prop_name);
>>> > +
>>> > +     target_of_node = of_parse_phandle(of_node, PROP_TARGET, 0);
>>> > +     if (!target_of_node)
>>> > +             return -EINVAL;
>>> > +
>>> > +     node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
>>> > +     if (!node)
>>> > +             return -ENOMEM;
>>> > +
>>> > +     common_tbl = read_tbl(of_node, PROP_TABLE);
>>> > +     if (!common_tbl) {
>>> > +             tbl_list = kzalloc(sizeof(*tbl_list) * NR_CPUS, GFP_KERNEL);
>>> > +             if (!tbl_list) {
>>> > +                     ret = -ENOMEM;
>>> > +                     goto err_list;
>>> > +             }
>>> > +
>>> > +             for_each_possible_cpu(cpu) {
>>> > +                     ret = snprintf(prop_name, prop_sz, "%s-%d",
>>> > +                                     PROP_TABLE, cpu);
>>> > +                     if (ret >= prop_sz) {
>>> > +                             pr_warn("More CPUs than I can handle!\n");
>>> > +                             pr_warn("Skipping rest of the tables!\n");
>>> > +                             break;
>>> > +                     }
>>> > +                     tbl_list[cpu] = read_tbl(of_node, prop_name);
>>> > +                     if (tbl_list[cpu])
>>> > +                             cnt++;
>>> > +             }
>>> > +     }
>>> > +     if (!common_tbl && !cnt) {
>>> > +             ret = -EINVAL;
>>> > +             goto err_tbl;
>>> > +     }
>>> > +
>>> > +     mutex_lock(&state_lock);
>>> > +     node->of_node = target_of_node;
>>> > +     node->map = tbl_list;
>>> > +     node->common_map = common_tbl;
>>> > +     list_add_tail(&node->list, &devfreq_list);
>>> > +     mutex_unlock(&state_lock);
>>> > +
>>> > +     return 0;
>>> > +err_tbl:
>>> > +     kfree(tbl_list);
>>> > +err_list:
>>> > +     kfree(node);
>>> > +     return ret;
>>> > +}
>>> > +
>>> > +static int __init devfreq_cpufreq_init(void)
>>> > +{
>>> > +     int ret;
>>> > +     struct device_node *of_par, *of_child;
>>> > +
>>> > +     of_par = of_find_node_by_name(NULL, "devfreq-cpufreq-map");
>>> > +     if (of_par) {
>>> > +             for_each_child_of_node(of_par, of_child) {
>>> > +                     ret = add_table_from_of(of_child);
>>> > +                     if (ret)
>>> > +                             pr_err("Parsing %s failed!\n", of_child->name);
>>> > +                     else
>>> > +                             pr_debug("Parsed %s.\n", of_child->name);
>>> > +             }
>>> > +             of_node_put(of_par);
>>> > +     } else {
>>> > +             pr_info("No tables parsed from DT.\n");
>>> > +     }
>>> > +
>>> > +     ret = devfreq_add_governor(&devfreq_cpufreq);
>>> > +     if (ret) {
>>> > +             pr_err("cpufreq-map governor add failed!\n");
>>> > +             return ret;
>>> > +     }
>>> > +
>>> > +     return 0;
>>> > +}
>>> > +subsys_initcall(devfreq_cpufreq_init);
>>> > +
>>> > +static void __exit devfreq_cpufreq_exit(void)
>>> > +{
>>> > +     int ret, cpu;
>>> > +     struct devfreq_node *node, *tmp;
>>> > +
>>> > +     ret = devfreq_remove_governor(&devfreq_cpufreq);
>>> > +     if (ret)
>>> > +             pr_err("cpufreq-map governor remove failed!\n");
>>> > +
>>> > +     mutex_lock(&state_lock);
>>> > +     list_for_each_entry_safe(node, tmp, &devfreq_list, list) {
>>> > +             kfree(node->common_map);
>>> > +             for_each_possible_cpu(cpu)
>>> > +                     kfree(node->map[cpu]);
>>> > +             kfree(node->map);
>>> > +             list_del(&node->list);
>>> > +             kfree(node);
>>> > +     }
>>> > +     mutex_unlock(&state_lock);
>>> > +}
>>> > +module_exit(devfreq_cpufreq_exit);
>>> > +
>>> > +MODULE_DESCRIPTION("devfreq gov that sets dev freq based on current CPU freq");
>>> > +MODULE_LICENSE("GPL v2");
>>> >
> 


-- 
Best Regards,
Chanwoo Choi
Samsung Electronics

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2019-06-26  8:10 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-06-18  4:14 [PATCH RFC 0/2] Use cpufreq-map governor for MT8183 CCI Hsin-Yi Wang
2019-06-18  4:14 ` [PATCH RFC 1/2] PM / devfreq: Generic CPU frequency to device frequency mapping governor Hsin-Yi Wang
2019-06-20  6:58   ` Chanwoo Choi
2019-06-20  9:32     ` Hsin-Yi Wang
2019-06-20  9:41       ` Sibi Sankar
2019-06-20  9:43         ` Hsin-Yi Wang
2019-06-26  8:12         ` Chanwoo Choi
2019-06-18  4:14 ` [PATCH RFC 2/2] devfreq: mt8183-cci: using cpufreq-map governor in cci dvfs driver Hsin-Yi Wang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).