162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * CPUFreq support for Armada 370/XP platforms.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2012-2016 Marvell
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Yehuda Yitschak <yehuday@marvell.com>
862306a36Sopenharmony_ci * Gregory Clement <gregory.clement@free-electrons.com>
962306a36Sopenharmony_ci * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#define pr_fmt(fmt) "mvebu-pmsu: " fmt
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/clk.h>
1562306a36Sopenharmony_ci#include <linux/cpu.h>
1662306a36Sopenharmony_ci#include <linux/init.h>
1762306a36Sopenharmony_ci#include <linux/kernel.h>
1862306a36Sopenharmony_ci#include <linux/of_address.h>
1962306a36Sopenharmony_ci#include <linux/platform_device.h>
2062306a36Sopenharmony_ci#include <linux/pm_opp.h>
2162306a36Sopenharmony_ci#include <linux/resource.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic int __init armada_xp_pmsu_cpufreq_init(void)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	struct device_node *np;
2662306a36Sopenharmony_ci	struct resource res;
2762306a36Sopenharmony_ci	int ret, cpu;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	if (!of_machine_is_compatible("marvell,armadaxp"))
3062306a36Sopenharmony_ci		return 0;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	/*
3362306a36Sopenharmony_ci	 * In order to have proper cpufreq handling, we need to ensure
3462306a36Sopenharmony_ci	 * that the Device Tree description of the CPU clock includes
3562306a36Sopenharmony_ci	 * the definition of the PMU DFS registers. If not, we do not
3662306a36Sopenharmony_ci	 * register the clock notifier and the cpufreq driver. This
3762306a36Sopenharmony_ci	 * piece of code is only for compatibility with old Device
3862306a36Sopenharmony_ci	 * Trees.
3962306a36Sopenharmony_ci	 */
4062306a36Sopenharmony_ci	np = of_find_compatible_node(NULL, NULL, "marvell,armada-xp-cpu-clock");
4162306a36Sopenharmony_ci	if (!np)
4262306a36Sopenharmony_ci		return 0;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	ret = of_address_to_resource(np, 1, &res);
4562306a36Sopenharmony_ci	if (ret) {
4662306a36Sopenharmony_ci		pr_warn(FW_WARN "not enabling cpufreq, deprecated armada-xp-cpu-clock binding\n");
4762306a36Sopenharmony_ci		of_node_put(np);
4862306a36Sopenharmony_ci		return 0;
4962306a36Sopenharmony_ci	}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	of_node_put(np);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	/*
5462306a36Sopenharmony_ci	 * For each CPU, this loop registers the operating points
5562306a36Sopenharmony_ci	 * supported (which are the nominal CPU frequency and half of
5662306a36Sopenharmony_ci	 * it), and registers the clock notifier that will take care
5762306a36Sopenharmony_ci	 * of doing the PMSU part of a frequency transition.
5862306a36Sopenharmony_ci	 */
5962306a36Sopenharmony_ci	for_each_possible_cpu(cpu) {
6062306a36Sopenharmony_ci		struct device *cpu_dev;
6162306a36Sopenharmony_ci		struct clk *clk;
6262306a36Sopenharmony_ci		int ret;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci		cpu_dev = get_cpu_device(cpu);
6562306a36Sopenharmony_ci		if (!cpu_dev) {
6662306a36Sopenharmony_ci			pr_err("Cannot get CPU %d\n", cpu);
6762306a36Sopenharmony_ci			continue;
6862306a36Sopenharmony_ci		}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci		clk = clk_get(cpu_dev, NULL);
7162306a36Sopenharmony_ci		if (IS_ERR(clk)) {
7262306a36Sopenharmony_ci			pr_err("Cannot get clock for CPU %d\n", cpu);
7362306a36Sopenharmony_ci			return PTR_ERR(clk);
7462306a36Sopenharmony_ci		}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci		ret = dev_pm_opp_add(cpu_dev, clk_get_rate(clk), 0);
7762306a36Sopenharmony_ci		if (ret) {
7862306a36Sopenharmony_ci			clk_put(clk);
7962306a36Sopenharmony_ci			return ret;
8062306a36Sopenharmony_ci		}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		ret = dev_pm_opp_add(cpu_dev, clk_get_rate(clk) / 2, 0);
8362306a36Sopenharmony_ci		if (ret) {
8462306a36Sopenharmony_ci			dev_pm_opp_remove(cpu_dev, clk_get_rate(clk));
8562306a36Sopenharmony_ci			clk_put(clk);
8662306a36Sopenharmony_ci			dev_err(cpu_dev, "Failed to register OPPs\n");
8762306a36Sopenharmony_ci			return ret;
8862306a36Sopenharmony_ci		}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci		ret = dev_pm_opp_set_sharing_cpus(cpu_dev,
9162306a36Sopenharmony_ci						  cpumask_of(cpu_dev->id));
9262306a36Sopenharmony_ci		if (ret)
9362306a36Sopenharmony_ci			dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
9462306a36Sopenharmony_ci				__func__, ret);
9562306a36Sopenharmony_ci		clk_put(clk);
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	platform_device_register_simple("cpufreq-dt", -1, NULL, 0);
9962306a36Sopenharmony_ci	return 0;
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_cidevice_initcall(armada_xp_pmsu_cpufreq_init);
102