18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * CPUFreq support for Armada 8K
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2018 Marvell
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Omri Itach <omrii@marvell.com>
88c2ecf20Sopenharmony_ci * Gregory Clement <gregory.clement@bootlin.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/clk.h>
148c2ecf20Sopenharmony_ci#include <linux/cpu.h>
158c2ecf20Sopenharmony_ci#include <linux/err.h>
168c2ecf20Sopenharmony_ci#include <linux/init.h>
178c2ecf20Sopenharmony_ci#include <linux/kernel.h>
188c2ecf20Sopenharmony_ci#include <linux/module.h>
198c2ecf20Sopenharmony_ci#include <linux/of.h>
208c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
218c2ecf20Sopenharmony_ci#include <linux/pm_opp.h>
228c2ecf20Sopenharmony_ci#include <linux/slab.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/*
258c2ecf20Sopenharmony_ci * Setup the opps list with the divider for the max frequency, that
268c2ecf20Sopenharmony_ci * will be filled at runtime.
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_cistatic const int opps_div[] __initconst = {1, 2, 3, 4};
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistatic struct platform_device *armada_8k_pdev;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistruct freq_table {
338c2ecf20Sopenharmony_ci	struct device *cpu_dev;
348c2ecf20Sopenharmony_ci	unsigned int freq[ARRAY_SIZE(opps_div)];
358c2ecf20Sopenharmony_ci};
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/* If the CPUs share the same clock, then they are in the same cluster. */
388c2ecf20Sopenharmony_cistatic void __init armada_8k_get_sharing_cpus(struct clk *cur_clk,
398c2ecf20Sopenharmony_ci					      struct cpumask *cpumask)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	int cpu;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	for_each_possible_cpu(cpu) {
448c2ecf20Sopenharmony_ci		struct device *cpu_dev;
458c2ecf20Sopenharmony_ci		struct clk *clk;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci		cpu_dev = get_cpu_device(cpu);
488c2ecf20Sopenharmony_ci		if (!cpu_dev) {
498c2ecf20Sopenharmony_ci			pr_warn("Failed to get cpu%d device\n", cpu);
508c2ecf20Sopenharmony_ci			continue;
518c2ecf20Sopenharmony_ci		}
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci		clk = clk_get(cpu_dev, 0);
548c2ecf20Sopenharmony_ci		if (IS_ERR(clk)) {
558c2ecf20Sopenharmony_ci			pr_warn("Cannot get clock for CPU %d\n", cpu);
568c2ecf20Sopenharmony_ci		} else {
578c2ecf20Sopenharmony_ci			if (clk_is_match(clk, cur_clk))
588c2ecf20Sopenharmony_ci				cpumask_set_cpu(cpu, cpumask);
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci			clk_put(clk);
618c2ecf20Sopenharmony_ci		}
628c2ecf20Sopenharmony_ci	}
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic int __init armada_8k_add_opp(struct clk *clk, struct device *cpu_dev,
668c2ecf20Sopenharmony_ci				    struct freq_table *freq_tables,
678c2ecf20Sopenharmony_ci				    int opps_index)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	unsigned int cur_frequency;
708c2ecf20Sopenharmony_ci	unsigned int freq;
718c2ecf20Sopenharmony_ci	int i, ret;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	/* Get nominal (current) CPU frequency. */
748c2ecf20Sopenharmony_ci	cur_frequency = clk_get_rate(clk);
758c2ecf20Sopenharmony_ci	if (!cur_frequency) {
768c2ecf20Sopenharmony_ci		dev_err(cpu_dev, "Failed to get clock rate for this CPU\n");
778c2ecf20Sopenharmony_ci		return -EINVAL;
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	freq_tables[opps_index].cpu_dev = cpu_dev;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(opps_div); i++) {
838c2ecf20Sopenharmony_ci		freq = cur_frequency / opps_div[i];
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci		ret = dev_pm_opp_add(cpu_dev, freq, 0);
868c2ecf20Sopenharmony_ci		if (ret)
878c2ecf20Sopenharmony_ci			return ret;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci		freq_tables[opps_index].freq[i] = freq;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	return 0;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic void armada_8k_cpufreq_free_table(struct freq_table *freq_tables)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	int opps_index, nb_cpus = num_possible_cpus();
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	for (opps_index = 0 ; opps_index <= nb_cpus; opps_index++) {
1008c2ecf20Sopenharmony_ci		int i;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci		/* If cpu_dev is NULL then we reached the end of the array */
1038c2ecf20Sopenharmony_ci		if (!freq_tables[opps_index].cpu_dev)
1048c2ecf20Sopenharmony_ci			break;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(opps_div); i++) {
1078c2ecf20Sopenharmony_ci			/*
1088c2ecf20Sopenharmony_ci			 * A 0Hz frequency is not valid, this meant
1098c2ecf20Sopenharmony_ci			 * that it was not yet initialized so there is
1108c2ecf20Sopenharmony_ci			 * no more opp to free
1118c2ecf20Sopenharmony_ci			 */
1128c2ecf20Sopenharmony_ci			if (freq_tables[opps_index].freq[i] == 0)
1138c2ecf20Sopenharmony_ci				break;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci			dev_pm_opp_remove(freq_tables[opps_index].cpu_dev,
1168c2ecf20Sopenharmony_ci					  freq_tables[opps_index].freq[i]);
1178c2ecf20Sopenharmony_ci		}
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	kfree(freq_tables);
1218c2ecf20Sopenharmony_ci}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_cistatic int __init armada_8k_cpufreq_init(void)
1248c2ecf20Sopenharmony_ci{
1258c2ecf20Sopenharmony_ci	int ret = 0, opps_index = 0, cpu, nb_cpus;
1268c2ecf20Sopenharmony_ci	struct freq_table *freq_tables;
1278c2ecf20Sopenharmony_ci	struct device_node *node;
1288c2ecf20Sopenharmony_ci	struct cpumask cpus;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	node = of_find_compatible_node(NULL, NULL, "marvell,ap806-cpu-clock");
1318c2ecf20Sopenharmony_ci	if (!node || !of_device_is_available(node)) {
1328c2ecf20Sopenharmony_ci		of_node_put(node);
1338c2ecf20Sopenharmony_ci		return -ENODEV;
1348c2ecf20Sopenharmony_ci	}
1358c2ecf20Sopenharmony_ci	of_node_put(node);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	nb_cpus = num_possible_cpus();
1388c2ecf20Sopenharmony_ci	freq_tables = kcalloc(nb_cpus, sizeof(*freq_tables), GFP_KERNEL);
1398c2ecf20Sopenharmony_ci	if (!freq_tables)
1408c2ecf20Sopenharmony_ci		return -ENOMEM;
1418c2ecf20Sopenharmony_ci	cpumask_copy(&cpus, cpu_possible_mask);
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	/*
1448c2ecf20Sopenharmony_ci	 * For each CPU, this loop registers the operating points
1458c2ecf20Sopenharmony_ci	 * supported (which are the nominal CPU frequency and full integer
1468c2ecf20Sopenharmony_ci	 * divisions of it).
1478c2ecf20Sopenharmony_ci	 */
1488c2ecf20Sopenharmony_ci	for_each_cpu(cpu, &cpus) {
1498c2ecf20Sopenharmony_ci		struct cpumask shared_cpus;
1508c2ecf20Sopenharmony_ci		struct device *cpu_dev;
1518c2ecf20Sopenharmony_ci		struct clk *clk;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci		cpu_dev = get_cpu_device(cpu);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci		if (!cpu_dev) {
1568c2ecf20Sopenharmony_ci			pr_err("Cannot get CPU %d\n", cpu);
1578c2ecf20Sopenharmony_ci			continue;
1588c2ecf20Sopenharmony_ci		}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci		clk = clk_get(cpu_dev, 0);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci		if (IS_ERR(clk)) {
1638c2ecf20Sopenharmony_ci			pr_err("Cannot get clock for CPU %d\n", cpu);
1648c2ecf20Sopenharmony_ci			ret = PTR_ERR(clk);
1658c2ecf20Sopenharmony_ci			goto remove_opp;
1668c2ecf20Sopenharmony_ci		}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci		ret = armada_8k_add_opp(clk, cpu_dev, freq_tables, opps_index);
1698c2ecf20Sopenharmony_ci		if (ret) {
1708c2ecf20Sopenharmony_ci			clk_put(clk);
1718c2ecf20Sopenharmony_ci			goto remove_opp;
1728c2ecf20Sopenharmony_ci		}
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci		opps_index++;
1758c2ecf20Sopenharmony_ci		cpumask_clear(&shared_cpus);
1768c2ecf20Sopenharmony_ci		armada_8k_get_sharing_cpus(clk, &shared_cpus);
1778c2ecf20Sopenharmony_ci		dev_pm_opp_set_sharing_cpus(cpu_dev, &shared_cpus);
1788c2ecf20Sopenharmony_ci		cpumask_andnot(&cpus, &cpus, &shared_cpus);
1798c2ecf20Sopenharmony_ci		clk_put(clk);
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	armada_8k_pdev = platform_device_register_simple("cpufreq-dt", -1,
1838c2ecf20Sopenharmony_ci							 NULL, 0);
1848c2ecf20Sopenharmony_ci	ret = PTR_ERR_OR_ZERO(armada_8k_pdev);
1858c2ecf20Sopenharmony_ci	if (ret)
1868c2ecf20Sopenharmony_ci		goto remove_opp;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	platform_set_drvdata(armada_8k_pdev, freq_tables);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	return 0;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ciremove_opp:
1938c2ecf20Sopenharmony_ci	armada_8k_cpufreq_free_table(freq_tables);
1948c2ecf20Sopenharmony_ci	return ret;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_cimodule_init(armada_8k_cpufreq_init);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic void __exit armada_8k_cpufreq_exit(void)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	struct freq_table *freq_tables = platform_get_drvdata(armada_8k_pdev);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	platform_device_unregister(armada_8k_pdev);
2038c2ecf20Sopenharmony_ci	armada_8k_cpufreq_free_table(freq_tables);
2048c2ecf20Sopenharmony_ci}
2058c2ecf20Sopenharmony_cimodule_exit(armada_8k_cpufreq_exit);
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_cistatic const struct of_device_id __maybe_unused armada_8k_cpufreq_of_match[] = {
2088c2ecf20Sopenharmony_ci	{ .compatible = "marvell,ap806-cpu-clock" },
2098c2ecf20Sopenharmony_ci	{ },
2108c2ecf20Sopenharmony_ci};
2118c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, armada_8k_cpufreq_of_match);
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ciMODULE_AUTHOR("Gregory Clement <gregory.clement@bootlin.com>");
2148c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Armada 8K cpufreq driver");
2158c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
216