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