1d3df18a9SNicolas Saenz Julienne // SPDX-License-Identifier: GPL-2.0
2d3df18a9SNicolas Saenz Julienne /*
3d3df18a9SNicolas Saenz Julienne  * Raspberry Pi cpufreq driver
4d3df18a9SNicolas Saenz Julienne  *
5d3df18a9SNicolas Saenz Julienne  * Copyright (C) 2019, Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
6d3df18a9SNicolas Saenz Julienne  */
7d3df18a9SNicolas Saenz Julienne 
8d3df18a9SNicolas Saenz Julienne #include <linux/clk.h>
9d3df18a9SNicolas Saenz Julienne #include <linux/cpu.h>
10d3df18a9SNicolas Saenz Julienne #include <linux/cpufreq.h>
11d3df18a9SNicolas Saenz Julienne #include <linux/module.h>
12d3df18a9SNicolas Saenz Julienne #include <linux/platform_device.h>
13d3df18a9SNicolas Saenz Julienne #include <linux/pm_opp.h>
14d3df18a9SNicolas Saenz Julienne 
15d3df18a9SNicolas Saenz Julienne #define RASPBERRYPI_FREQ_INTERVAL	100000000
16d3df18a9SNicolas Saenz Julienne 
17d3df18a9SNicolas Saenz Julienne static struct platform_device *cpufreq_dt;
18d3df18a9SNicolas Saenz Julienne 
raspberrypi_cpufreq_probe(struct platform_device * pdev)19d3df18a9SNicolas Saenz Julienne static int raspberrypi_cpufreq_probe(struct platform_device *pdev)
20d3df18a9SNicolas Saenz Julienne {
21d3df18a9SNicolas Saenz Julienne 	struct device *cpu_dev;
22d3df18a9SNicolas Saenz Julienne 	unsigned long min, max;
23d3df18a9SNicolas Saenz Julienne 	unsigned long rate;
24d3df18a9SNicolas Saenz Julienne 	struct clk *clk;
25d3df18a9SNicolas Saenz Julienne 	int ret;
26d3df18a9SNicolas Saenz Julienne 
27d3df18a9SNicolas Saenz Julienne 	cpu_dev = get_cpu_device(0);
28d3df18a9SNicolas Saenz Julienne 	if (!cpu_dev) {
29d3df18a9SNicolas Saenz Julienne 		pr_err("Cannot get CPU for cpufreq driver\n");
30d3df18a9SNicolas Saenz Julienne 		return -ENODEV;
31d3df18a9SNicolas Saenz Julienne 	}
32d3df18a9SNicolas Saenz Julienne 
33d3df18a9SNicolas Saenz Julienne 	clk = clk_get(cpu_dev, NULL);
34d3df18a9SNicolas Saenz Julienne 	if (IS_ERR(clk)) {
35d3df18a9SNicolas Saenz Julienne 		dev_err(cpu_dev, "Cannot get clock for CPU0\n");
36d3df18a9SNicolas Saenz Julienne 		return PTR_ERR(clk);
37d3df18a9SNicolas Saenz Julienne 	}
38d3df18a9SNicolas Saenz Julienne 
39d3df18a9SNicolas Saenz Julienne 	/*
40d3df18a9SNicolas Saenz Julienne 	 * The max and min frequencies are configurable in the Raspberry Pi
41d3df18a9SNicolas Saenz Julienne 	 * firmware, so we query them at runtime.
42d3df18a9SNicolas Saenz Julienne 	 */
43d3df18a9SNicolas Saenz Julienne 	min = roundup(clk_round_rate(clk, 0), RASPBERRYPI_FREQ_INTERVAL);
44d3df18a9SNicolas Saenz Julienne 	max = roundup(clk_round_rate(clk, ULONG_MAX), RASPBERRYPI_FREQ_INTERVAL);
45d3df18a9SNicolas Saenz Julienne 	clk_put(clk);
46d3df18a9SNicolas Saenz Julienne 
47d3df18a9SNicolas Saenz Julienne 	for (rate = min; rate <= max; rate += RASPBERRYPI_FREQ_INTERVAL) {
48d3df18a9SNicolas Saenz Julienne 		ret = dev_pm_opp_add(cpu_dev, rate, 0);
49d3df18a9SNicolas Saenz Julienne 		if (ret)
50d3df18a9SNicolas Saenz Julienne 			goto remove_opp;
51d3df18a9SNicolas Saenz Julienne 	}
52d3df18a9SNicolas Saenz Julienne 
53d3df18a9SNicolas Saenz Julienne 	cpufreq_dt = platform_device_register_simple("cpufreq-dt", -1, NULL, 0);
54d3df18a9SNicolas Saenz Julienne 	ret = PTR_ERR_OR_ZERO(cpufreq_dt);
55d3df18a9SNicolas Saenz Julienne 	if (ret) {
56d3df18a9SNicolas Saenz Julienne 		dev_err(cpu_dev, "Failed to create platform device, %d\n", ret);
57d3df18a9SNicolas Saenz Julienne 		goto remove_opp;
58d3df18a9SNicolas Saenz Julienne 	}
59d3df18a9SNicolas Saenz Julienne 
60d3df18a9SNicolas Saenz Julienne 	return 0;
61d3df18a9SNicolas Saenz Julienne 
62d3df18a9SNicolas Saenz Julienne remove_opp:
63d3df18a9SNicolas Saenz Julienne 	dev_pm_opp_remove_all_dynamic(cpu_dev);
64d3df18a9SNicolas Saenz Julienne 
65d3df18a9SNicolas Saenz Julienne 	return ret;
66d3df18a9SNicolas Saenz Julienne }
67d3df18a9SNicolas Saenz Julienne 
raspberrypi_cpufreq_remove(struct platform_device * pdev)68*46ebd4d8SYangtao Li static void raspberrypi_cpufreq_remove(struct platform_device *pdev)
69d3df18a9SNicolas Saenz Julienne {
70d3df18a9SNicolas Saenz Julienne 	struct device *cpu_dev;
71d3df18a9SNicolas Saenz Julienne 
72d3df18a9SNicolas Saenz Julienne 	cpu_dev = get_cpu_device(0);
73d3df18a9SNicolas Saenz Julienne 	if (cpu_dev)
74d3df18a9SNicolas Saenz Julienne 		dev_pm_opp_remove_all_dynamic(cpu_dev);
75d3df18a9SNicolas Saenz Julienne 
76d3df18a9SNicolas Saenz Julienne 	platform_device_unregister(cpufreq_dt);
77d3df18a9SNicolas Saenz Julienne }
78d3df18a9SNicolas Saenz Julienne 
79d3df18a9SNicolas Saenz Julienne /*
80d3df18a9SNicolas Saenz Julienne  * Since the driver depends on clk-raspberrypi, which may return EPROBE_DEFER,
81d3df18a9SNicolas Saenz Julienne  * all the activity is performed in the probe, which may be defered as well.
82d3df18a9SNicolas Saenz Julienne  */
83d3df18a9SNicolas Saenz Julienne static struct platform_driver raspberrypi_cpufreq_driver = {
84d3df18a9SNicolas Saenz Julienne 	.driver = {
85d3df18a9SNicolas Saenz Julienne 		.name = "raspberrypi-cpufreq",
86d3df18a9SNicolas Saenz Julienne 	},
87d3df18a9SNicolas Saenz Julienne 	.probe          = raspberrypi_cpufreq_probe,
88*46ebd4d8SYangtao Li 	.remove_new	= raspberrypi_cpufreq_remove,
89d3df18a9SNicolas Saenz Julienne };
90d3df18a9SNicolas Saenz Julienne module_platform_driver(raspberrypi_cpufreq_driver);
91d3df18a9SNicolas Saenz Julienne 
92d3df18a9SNicolas Saenz Julienne MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de");
93d3df18a9SNicolas Saenz Julienne MODULE_DESCRIPTION("Raspberry Pi cpufreq driver");
94d3df18a9SNicolas Saenz Julienne MODULE_LICENSE("GPL");
95d3df18a9SNicolas Saenz Julienne MODULE_ALIAS("platform:raspberrypi-cpufreq");
96