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

Commit 05b1c784 authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "cpufreq: record CPUFREQ stat for fast switch path"

parents 452bee0e 54aaec6d
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -338,3 +338,9 @@ config ARM_PXA2xx_CPUFREQ
	  This add the CPUFreq driver support for Intel PXA2xx SOCs.

	  If in doubt, say N.

config CPU_FREQ_MSM
	bool "MSM CPUFreq support"
	depends on CPU_FREQ
	help
	  This enables the CPUFreq driver for Qualcomm Technologies, Inc. CPUs.
+1 −0
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ obj-$(CONFIG_ARM_IMX_CPUFREQ_DT) += imx-cpufreq-dt.o
obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ)	+= kirkwood-cpufreq.o
obj-$(CONFIG_ARM_MEDIATEK_CPUFREQ)	+= mediatek-cpufreq.o
obj-$(CONFIG_MACH_MVEBU_V7)		+= mvebu-cpufreq.o
obj-$(CONFIG_CPU_FREQ_MSM)              += qcom-cpufreq.o
obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ)	+= omap-cpufreq.o
obj-$(CONFIG_ARM_PXA2xx_CPUFREQ)	+= pxa2xx-cpufreq.o
obj-$(CONFIG_PXA3xx)			+= pxa3xx-cpufreq.o
+9 −11
Original line number Diff line number Diff line
@@ -1130,7 +1130,8 @@ static int cpufreq_add_policy_cpu(struct cpufreq_policy *policy, unsigned int cp
	if (has_target()) {
		ret = cpufreq_start_governor(policy);
		if (ret)
			pr_err("%s: Failed to start governor\n", __func__);
			pr_err("%s: Failed to start governor for CPU%u, policy CPU%u\n",
			       __func__, cpu, policy->cpu);
	}
	up_write(&policy->rwsem);
	return ret;
@@ -2056,8 +2057,10 @@ unsigned int cpufreq_driver_fast_switch(struct cpufreq_policy *policy,
	target_freq = clamp_val(target_freq, policy->min, policy->max);

	ret = cpufreq_driver->fast_switch(policy, target_freq);
	if (ret)
	if (ret) {
		cpufreq_times_record_transition(policy, ret);
		cpufreq_stats_record_transition(policy, ret);
	}

	return ret;
}
@@ -2162,15 +2165,6 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy,
	pr_debug("target for CPU %u: %u kHz, relation %u, requested %u kHz\n",
		 policy->cpu, target_freq, relation, old_target_freq);

	/*
	 * This might look like a redundant call as we are checking it again
	 * after finding index. But it is left intentionally for cases where
	 * exactly same freq is called again and so we can save on few function
	 * calls.
	 */
	if (target_freq == policy->cur)
		return 0;

	/* Save last value to restore later on errors */
	policy->restore_freq = policy->cur;

@@ -2430,6 +2424,10 @@ int cpufreq_set_policy(struct cpufreq_policy *policy,
	if (ret)
		return ret;

	/* adjust if necessary - hardware incompatibility */
	blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
			CPUFREQ_INCOMPATIBLE, new_policy);

	policy->min = new_policy->min;
	policy->max = new_policy->max;
	trace_cpu_frequency_limits(policy);
+6 −10
Original line number Diff line number Diff line
@@ -55,13 +55,11 @@ static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf)
	struct cpufreq_stats *stats = policy->stats;
	ssize_t len = 0;
	int i;
	unsigned long flags;

	if (policy->fast_switch_enabled)
		return 0;

	spin_lock(&stats->lock);
	spin_lock_irqsave(&stats->lock, flags);
	cpufreq_stats_update(stats);
	spin_unlock(&stats->lock);
	spin_unlock_irqrestore(&stats->lock, flags);

	for (i = 0; i < stats->state_num; i++) {
		len += sprintf(buf + len, "%u %llu\n", stats->freq_table[i],
@@ -87,9 +85,6 @@ static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf)
	ssize_t len = 0;
	int i, j;

	if (policy->fast_switch_enabled)
		return 0;

	len += snprintf(buf + len, PAGE_SIZE - len, "   From  :    To\n");
	len += snprintf(buf + len, PAGE_SIZE - len, "         : ");
	for (i = 0; i < stats->state_num; i++) {
@@ -227,6 +222,7 @@ void cpufreq_stats_record_transition(struct cpufreq_policy *policy,
{
	struct cpufreq_stats *stats = policy->stats;
	int old_index, new_index;
	unsigned long flags;

	if (!stats) {
		pr_debug("%s: No stats found\n", __func__);
@@ -240,11 +236,11 @@ void cpufreq_stats_record_transition(struct cpufreq_policy *policy,
	if (old_index == -1 || new_index == -1 || old_index == new_index)
		return;

	spin_lock(&stats->lock);
	spin_lock_irqsave(&stats->lock, flags);
	cpufreq_stats_update(stats);

	stats->last_index = new_index;
	stats->trans_table[old_index * stats->max_state + new_index]++;
	stats->total_trans++;
	spin_unlock(&stats->lock);
	spin_unlock_irqrestore(&stats->lock, flags);
}
+548 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/* drivers/cpufreq/qcom-cpufreq.c
 *
 * MSM architecture cpufreq driver
 *
 * Copyright (C) 2007 Google, Inc.
 * Copyright (c) 2007-2019, The Linux Foundation. All rights reserved.
 * Author: Mike A. Chan <mikechan@google.com>
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cpufreq.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/suspend.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/cpu_cooling.h>
#include <trace/events/power.h>

static DEFINE_MUTEX(l2bw_lock);

static struct thermal_cooling_device *cdev[NR_CPUS];
static struct clk *cpu_clk[NR_CPUS];
static struct clk *l2_clk;
static DEFINE_PER_CPU(struct cpufreq_frequency_table *, freq_table);
static bool hotplug_ready;

struct cpufreq_suspend_t {
	struct mutex suspend_mutex;
	int device_suspended;
};

static DEFINE_PER_CPU(struct cpufreq_suspend_t, suspend_data);
static DEFINE_PER_CPU(int, cached_resolve_idx);
static DEFINE_PER_CPU(unsigned int, cached_resolve_freq);

static int set_cpu_freq(struct cpufreq_policy *policy, unsigned int new_freq,
			unsigned int index)
{
	int ret = 0;
	struct cpufreq_freqs freqs;
	unsigned long rate;

	freqs.old = policy->cur;
	freqs.new = new_freq;
	freqs.cpu = policy->cpu;

	trace_cpu_frequency_switch_start(freqs.old, freqs.new, policy->cpu);
	cpufreq_freq_transition_begin(policy, &freqs);

	rate = new_freq * 1000;
	rate = clk_round_rate(cpu_clk[policy->cpu], rate);
	ret = clk_set_rate(cpu_clk[policy->cpu], rate);
	cpufreq_freq_transition_end(policy, &freqs, ret);
	if (!ret) {
		arch_set_freq_scale(policy->related_cpus, new_freq,
				    policy->cpuinfo.max_freq);
		trace_cpu_frequency_switch_end(policy->cpu);
	}

	return ret;
}

static int msm_cpufreq_target(struct cpufreq_policy *policy,
				unsigned int target_freq,
				unsigned int relation)
{
	int ret = 0;
	int index;
	struct cpufreq_frequency_table *table;
	int first_cpu = cpumask_first(policy->related_cpus);

	mutex_lock(&per_cpu(suspend_data, policy->cpu).suspend_mutex);

	if (target_freq == policy->cur)
		goto done;

	if (per_cpu(suspend_data, policy->cpu).device_suspended) {
		pr_debug("cpufreq: cpu%d scheduling frequency change in suspend\n",
			 policy->cpu);
		ret = -EFAULT;
		goto done;
	}

	table = policy->freq_table;
	if (per_cpu(cached_resolve_freq, first_cpu) == target_freq)
		index = per_cpu(cached_resolve_idx, first_cpu);
	else
		index = cpufreq_frequency_table_target(policy, target_freq,
						       relation);

	pr_debug("CPU[%d] target %d relation %d (%d-%d) selected %d\n",
		policy->cpu, target_freq, relation,
		policy->min, policy->max, table[index].frequency);

	ret = set_cpu_freq(policy, table[index].frequency,
			   table[index].driver_data);
done:
	mutex_unlock(&per_cpu(suspend_data, policy->cpu).suspend_mutex);
	return ret;
}

static unsigned int msm_cpufreq_resolve_freq(struct cpufreq_policy *policy,
					     unsigned int target_freq)
{
	int index;
	int first_cpu = cpumask_first(policy->related_cpus);
	unsigned int freq;

	index = cpufreq_frequency_table_target(policy, target_freq,
					       CPUFREQ_RELATION_L);
	freq = policy->freq_table[index].frequency;

	per_cpu(cached_resolve_idx, first_cpu) = index;
	per_cpu(cached_resolve_freq, first_cpu) = freq;

	return freq;
}

static int msm_cpufreq_verify(struct cpufreq_policy *policy)
{
	cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq,
			policy->cpuinfo.max_freq);
	return 0;
}

static unsigned int msm_cpufreq_get_freq(unsigned int cpu)
{
	return clk_get_rate(cpu_clk[cpu]) / 1000;
}

static int msm_cpufreq_init(struct cpufreq_policy *policy)
{
	int cur_freq;
	int index;
	int ret = 0;
	struct cpufreq_frequency_table *table =
			per_cpu(freq_table, policy->cpu);
	int cpu;

	/*
	 * In some SoC, some cores are clocked by same source, and their
	 * frequencies can not be changed independently. Find all other
	 * CPUs that share same clock, and mark them as controlled by
	 * same policy.
	 */
	for_each_possible_cpu(cpu)
		if (cpu_clk[cpu] == cpu_clk[policy->cpu])
			cpumask_set_cpu(cpu, policy->cpus);

	policy->freq_table = table;
	ret = cpufreq_table_validate_and_sort(policy);
	if (ret) {
		pr_err("cpufreq: failed to get policy min/max\n");
		return ret;
	}

	cur_freq = clk_get_rate(cpu_clk[policy->cpu])/1000;

	index =  cpufreq_frequency_table_target(policy, cur_freq,
						CPUFREQ_RELATION_H);
	/*
	 * Call set_cpu_freq unconditionally so that when cpu is set to
	 * online, frequency limit will always be updated.
	 */
	ret = set_cpu_freq(policy, table[index].frequency,
			   table[index].driver_data);
	if (ret)
		return ret;
	pr_debug("cpufreq: cpu%d init at %d switching to %d\n",
			policy->cpu, cur_freq, table[index].frequency);
	policy->cur = table[index].frequency;
	policy->dvfs_possible_from_any_cpu = true;

	return 0;
}

static int qcom_cpufreq_dead_cpu(unsigned int cpu)
{
	/* Fail hotplug until this driver can get CPU clocks */
	if (!hotplug_ready)
		return -EINVAL;

	clk_unprepare(cpu_clk[cpu]);
	clk_unprepare(l2_clk);
	return 0;
}

static int qcom_cpufreq_up_cpu(unsigned int cpu)
{
	int rc;

	/* Fail hotplug until this driver can get CPU clocks */
	if (!hotplug_ready)
		return -EINVAL;

	rc = clk_prepare(l2_clk);
	if (rc < 0)
		return rc;
	rc = clk_prepare(cpu_clk[cpu]);
	if (rc < 0)
		clk_unprepare(l2_clk);
	return rc;
}

static int qcom_cpufreq_dying_cpu(unsigned int cpu)
{
	/* Fail hotplug until this driver can get CPU clocks */
	if (!hotplug_ready)
		return -EINVAL;

	clk_disable(cpu_clk[cpu]);
	clk_disable(l2_clk);
	return 0;
}

static int qcom_cpufreq_starting_cpu(unsigned int cpu)
{
	int rc;

	/* Fail hotplug until this driver can get CPU clocks */
	if (!hotplug_ready)
		return -EINVAL;

	rc = clk_enable(l2_clk);
	if (rc < 0)
		return rc;
	rc = clk_enable(cpu_clk[cpu]);
	if (rc < 0)
		clk_disable(l2_clk);
	return rc;
}

static int msm_cpufreq_suspend(void)
{
	int cpu;

	for_each_possible_cpu(cpu) {
		mutex_lock(&per_cpu(suspend_data, cpu).suspend_mutex);
		per_cpu(suspend_data, cpu).device_suspended = 1;
		mutex_unlock(&per_cpu(suspend_data, cpu).suspend_mutex);
	}

	return NOTIFY_DONE;
}

static int msm_cpufreq_resume(void)
{
	int cpu, ret;
	struct cpufreq_policy policy;

	for_each_possible_cpu(cpu) {
		per_cpu(suspend_data, cpu).device_suspended = 0;
	}

	/*
	 * Freq request might be rejected during suspend, resulting
	 * in policy->cur violating min/max constraint.
	 * Correct the frequency as soon as possible.
	 */
	get_online_cpus();
	for_each_online_cpu(cpu) {
		ret = cpufreq_get_policy(&policy, cpu);
		if (ret)
			continue;
		if (policy.cur <= policy.max && policy.cur >= policy.min)
			continue;
		cpufreq_update_policy(cpu);
	}
	put_online_cpus();

	return NOTIFY_DONE;
}

static int msm_cpufreq_pm_event(struct notifier_block *this,
				unsigned long event, void *ptr)
{
	switch (event) {
	case PM_POST_HIBERNATION:
	case PM_POST_SUSPEND:
		return msm_cpufreq_resume();
	case PM_HIBERNATION_PREPARE:
	case PM_SUSPEND_PREPARE:
		return msm_cpufreq_suspend();
	default:
		return NOTIFY_DONE;
	}
}

static struct notifier_block msm_cpufreq_pm_notifier = {
	.notifier_call = msm_cpufreq_pm_event,
};

static struct freq_attr *msm_freq_attr[] = {
	&cpufreq_freq_attr_scaling_available_freqs,
	NULL,
};

static void msm_cpufreq_ready(struct cpufreq_policy *policy)
{
	struct device_node *np;
	unsigned int cpu = policy->cpu;

	if (cdev[cpu])
		return;

	np = of_cpu_device_node_get(cpu);
	if (WARN_ON(!np))
		return;

	/*
	 * For now, just loading the cooling device;
	 * thermal DT code takes care of matching them.
	 */
	if (of_find_property(np, "#cooling-cells", NULL)) {
		cdev[cpu] = of_cpufreq_cooling_register(policy);
		if (IS_ERR(cdev[cpu])) {
			pr_err("running cpufreq for CPU%d without cooling dev: %ld\n",
			       cpu, PTR_ERR(cdev[cpu]));
			cdev[cpu] = NULL;
		}
	}

	of_node_put(np);
}

static struct cpufreq_driver msm_cpufreq_driver = {
	/* lps calculations are handled here. */
	.flags		= CPUFREQ_STICKY | CPUFREQ_CONST_LOOPS |
				CPUFREQ_NEED_INITIAL_FREQ_CHECK,
	.init		= msm_cpufreq_init,
	.verify		= msm_cpufreq_verify,
	.target		= msm_cpufreq_target,
	.resolve_freq	= msm_cpufreq_resolve_freq,
	.get		= msm_cpufreq_get_freq,
	.name		= "msm",
	.attr		= msm_freq_attr,
	.ready		= msm_cpufreq_ready,
};

static struct cpufreq_frequency_table *cpufreq_parse_dt(struct device *dev,
						char *tbl_name, int cpu)
{
	int ret, nf, i, j;
	u32 *data;
	struct cpufreq_frequency_table *ftbl;

	/* Parse list of usable CPU frequencies. */
	if (!of_find_property(dev->of_node, tbl_name, &nf))
		return ERR_PTR(-EINVAL);
	nf /= sizeof(*data);

	if (nf == 0)
		return ERR_PTR(-EINVAL);

	data = devm_kzalloc(dev, nf * sizeof(*data), GFP_KERNEL);
	if (!data)
		return ERR_PTR(-ENOMEM);

	ret = of_property_read_u32_array(dev->of_node, tbl_name, data, nf);
	if (ret)
		return ERR_PTR(ret);

	ftbl = devm_kzalloc(dev, (nf + 1) * sizeof(*ftbl), GFP_KERNEL);
	if (!ftbl)
		return ERR_PTR(-ENOMEM);

	j = 0;
	for (i = 0; i < nf; i++) {
		unsigned long f;

		f = clk_round_rate(cpu_clk[cpu], data[i] * 1000);
		if (IS_ERR_VALUE(f))
			break;
		f /= 1000;

		/*
		 * Don't repeat frequencies if they round up to the same clock
		 * frequency.
		 *
		 */
		if (j > 0 && f <= ftbl[j - 1].frequency)
			continue;

		ftbl[j].driver_data = j;
		ftbl[j].frequency = f;
		j++;
	}

	ftbl[j].driver_data = j;
	ftbl[j].frequency = CPUFREQ_TABLE_END;

	devm_kfree(dev, data);

	return ftbl;
}

static int msm_cpufreq_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	char clk_name[] = "cpu??_clk";
	char tbl_name[] = "qcom,cpufreq-table-??";
	struct clk *c;
	int cpu, ret;
	struct cpufreq_frequency_table *ftbl;

	l2_clk = devm_clk_get(dev, "l2_clk");
	if (IS_ERR(l2_clk))
		l2_clk = NULL;

	for_each_possible_cpu(cpu) {
		snprintf(clk_name, sizeof(clk_name), "cpu%d_clk", cpu);
		c = devm_clk_get(dev, clk_name);
		if (cpu == 0 && IS_ERR(c))
			return PTR_ERR(c);
		else if (IS_ERR(c))
			c = cpu_clk[cpu-1];
		cpu_clk[cpu] = c;
	}
	hotplug_ready = true;

	/* Use per-policy governor tunable for some targets */
	if (of_property_read_bool(dev->of_node, "qcom,governor-per-policy"))
		msm_cpufreq_driver.flags |= CPUFREQ_HAVE_GOVERNOR_PER_POLICY;

	/* Parse commong cpufreq table for all CPUs */
	ftbl = cpufreq_parse_dt(dev, "qcom,cpufreq-table", 0);
	if (!IS_ERR(ftbl)) {
		for_each_possible_cpu(cpu)
			per_cpu(freq_table, cpu) = ftbl;
		goto out_register;
	}

	/*
	 * No common table. Parse individual tables for each unique
	 * CPU clock.
	 */
	for_each_possible_cpu(cpu) {
		snprintf(tbl_name, sizeof(tbl_name),
			 "qcom,cpufreq-table-%d", cpu);
		ftbl = cpufreq_parse_dt(dev, tbl_name, cpu);

		/* CPU0 must contain freq table */
		if (cpu == 0 && IS_ERR(ftbl)) {
			dev_err(dev, "Failed to parse CPU0's freq table\n");
			return PTR_ERR(ftbl);
		}
		if (cpu == 0) {
			per_cpu(freq_table, cpu) = ftbl;
			continue;
		}

		if (cpu_clk[cpu] != cpu_clk[cpu - 1] && IS_ERR(ftbl)) {
			dev_err(dev, "Failed to parse CPU%d's freq table\n",
				cpu);
			return PTR_ERR(ftbl);
		}

		/* Use previous CPU's table if it shares same clock */
		if (cpu_clk[cpu] == cpu_clk[cpu - 1]) {
			if (!IS_ERR(ftbl)) {
				dev_warn(dev, "Conflicting tables for CPU%d\n",
					 cpu);
				devm_kfree(dev, ftbl);
			}
			ftbl = per_cpu(freq_table, cpu - 1);
		}
		per_cpu(freq_table, cpu) = ftbl;
	}

out_register:
	ret = register_pm_notifier(&msm_cpufreq_pm_notifier);
	if (ret)
		return ret;

	ret = cpufreq_register_driver(&msm_cpufreq_driver);
	if (ret)
		unregister_pm_notifier(&msm_cpufreq_pm_notifier);

	return ret;
}

static const struct of_device_id msm_cpufreq_match_table[] = {
	{ .compatible = "qcom,msm-cpufreq" },
	{}
};

static struct platform_driver msm_cpufreq_plat_driver = {
	.probe = msm_cpufreq_probe,
	.driver = {
		.name = "msm-cpufreq",
		.of_match_table = msm_cpufreq_match_table,
	},
};

static int __init msm_cpufreq_register(void)
{
	int cpu, rc;

	for_each_possible_cpu(cpu) {
		mutex_init(&(per_cpu(suspend_data, cpu).suspend_mutex));
		per_cpu(suspend_data, cpu).device_suspended = 0;
		per_cpu(cached_resolve_freq, cpu) = UINT_MAX;
	}

	rc = platform_driver_register(&msm_cpufreq_plat_driver);
	if (rc < 0) {
		/* Unblock hotplug if msm-cpufreq probe fails */
		cpuhp_remove_state_nocalls(CPUHP_QCOM_CPUFREQ_PREPARE);
		cpuhp_remove_state_nocalls(CPUHP_AP_QCOM_CPUFREQ_STARTING);
		for_each_possible_cpu(cpu)
			mutex_destroy(&(per_cpu(suspend_data, cpu).
					suspend_mutex));
		return rc;
	}

	return 0;
}

subsys_initcall(msm_cpufreq_register);

static int __init msm_cpufreq_early_register(void)
{
	int ret;

	ret = cpuhp_setup_state_nocalls(CPUHP_AP_QCOM_CPUFREQ_STARTING,
					"AP_QCOM_CPUFREQ_STARTING",
					qcom_cpufreq_starting_cpu,
					qcom_cpufreq_dying_cpu);
	if (ret)
		return ret;

	ret = cpuhp_setup_state_nocalls(CPUHP_QCOM_CPUFREQ_PREPARE,
					"QCOM_CPUFREQ_PREPARE",
					qcom_cpufreq_up_cpu,
					qcom_cpufreq_dead_cpu);
	if (!ret)
		return ret;
	cpuhp_remove_state_nocalls(CPUHP_AP_QCOM_CPUFREQ_STARTING);
	return ret;
}
core_initcall(msm_cpufreq_early_register);
Loading