summaryrefslogtreecommitdiffstats
path: root/drivers/clk/bcm
diff options
context:
space:
mode:
authorNicolas Saenz Julienne <nsaenzjulienne@suse.de>2019-06-12 14:24:54 -0400
committerStephen Boyd <sboyd@kernel.org>2019-06-25 19:04:23 -0400
commit4e85e535e6cc6e8a96350e8ee684d0f22eb8629e (patch)
treed731c93bff3e96dde7057f3dc44dafa4ba990e03 /drivers/clk/bcm
parent2256d89333bd17b8b56b42734a7e1046d52f7fc3 (diff)
clk: bcm283x: add driver interfacing with Raspberry Pi's firmware
Raspberry Pi's firmware offers an interface though which update it's clock's frequencies. This is specially useful in order to change the CPU clock (pllb_arm) which is 'owned' by the firmware and we're unable to scale using the register interface provided by clk-bcm2835. Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne@suse.de> Acked-by: Eric Anholt <eric@anholt.net> Signed-off-by: Stephen Boyd <sboyd@kernel.org>
Diffstat (limited to 'drivers/clk/bcm')
-rw-r--r--drivers/clk/bcm/Kconfig7
-rw-r--r--drivers/clk/bcm/Makefile1
-rw-r--r--drivers/clk/bcm/clk-raspberrypi.c300
3 files changed, 308 insertions, 0 deletions
diff --git a/drivers/clk/bcm/Kconfig b/drivers/clk/bcm/Kconfig
index 0eb281d597fc..e5497e995cfb 100644
--- a/drivers/clk/bcm/Kconfig
+++ b/drivers/clk/bcm/Kconfig
@@ -72,3 +72,10 @@ config CLK_BCM_SR
72 default ARCH_BCM_IPROC 72 default ARCH_BCM_IPROC
73 help 73 help
74 Enable common clock framework support for the Broadcom Stingray SoC 74 Enable common clock framework support for the Broadcom Stingray SoC
75
76config CLK_RASPBERRYPI
77 tristate "Raspberry Pi firmware based clock support"
78 depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
79 help
80 Enable common clock framework support for Raspberry Pi's firmware
81 dependent clocks
diff --git a/drivers/clk/bcm/Makefile b/drivers/clk/bcm/Makefile
index e924f25bc6c8..004e9526d6f6 100644
--- a/drivers/clk/bcm/Makefile
+++ b/drivers/clk/bcm/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_CLK_BCM_KONA) += clk-bcm21664.o
7obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-asiu.o 7obj-$(CONFIG_COMMON_CLK_IPROC) += clk-iproc-armpll.o clk-iproc-pll.o clk-iproc-asiu.o
8obj-$(CONFIG_CLK_BCM2835) += clk-bcm2835.o 8obj-$(CONFIG_CLK_BCM2835) += clk-bcm2835.o
9obj-$(CONFIG_CLK_BCM2835) += clk-bcm2835-aux.o 9obj-$(CONFIG_CLK_BCM2835) += clk-bcm2835-aux.o
10obj-$(CONFIG_CLK_RASPBERRYPI) += clk-raspberrypi.o
10obj-$(CONFIG_ARCH_BCM_53573) += clk-bcm53573-ilp.o 11obj-$(CONFIG_ARCH_BCM_53573) += clk-bcm53573-ilp.o
11obj-$(CONFIG_CLK_BCM_CYGNUS) += clk-cygnus.o 12obj-$(CONFIG_CLK_BCM_CYGNUS) += clk-cygnus.o
12obj-$(CONFIG_CLK_BCM_HR2) += clk-hr2.o 13obj-$(CONFIG_CLK_BCM_HR2) += clk-hr2.o
diff --git a/drivers/clk/bcm/clk-raspberrypi.c b/drivers/clk/bcm/clk-raspberrypi.c
new file mode 100644
index 000000000000..fef1f7caee4f
--- /dev/null
+++ b/drivers/clk/bcm/clk-raspberrypi.c
@@ -0,0 +1,300 @@
1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Raspberry Pi driver for firmware controlled clocks
4 *
5 * Even though clk-bcm2835 provides an interface to the hardware registers for
6 * the system clocks we've had to factor out 'pllb' as the firmware 'owns' it.
7 * We're not allowed to change it directly as we might race with the
8 * over-temperature and under-voltage protections provided by the firmware.
9 *
10 * Copyright (C) 2019 Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
11 */
12
13#include <linux/clkdev.h>
14#include <linux/clk-provider.h>
15#include <linux/io.h>
16#include <linux/module.h>
17#include <linux/platform_device.h>
18
19#include <soc/bcm2835/raspberrypi-firmware.h>
20
21#define RPI_FIRMWARE_ARM_CLK_ID 0x00000003
22
23#define RPI_FIRMWARE_STATE_ENABLE_BIT BIT(0)
24#define RPI_FIRMWARE_STATE_WAIT_BIT BIT(1)
25
26/*
27 * Even though the firmware interface alters 'pllb' the frequencies are
28 * provided as per 'pllb_arm'. We need to scale before passing them trough.
29 */
30#define RPI_FIRMWARE_PLLB_ARM_DIV_RATE 2
31
32#define A2W_PLL_FRAC_BITS 20
33
34struct raspberrypi_clk {
35 struct device *dev;
36 struct rpi_firmware *firmware;
37
38 unsigned long min_rate;
39 unsigned long max_rate;
40
41 struct clk_hw pllb;
42 struct clk_hw *pllb_arm;
43 struct clk_lookup *pllb_arm_lookup;
44};
45
46/*
47 * Structure of the message passed to Raspberry Pi's firmware in order to
48 * change clock rates. The 'disable_turbo' option is only available to the ARM
49 * clock (pllb) which we enable by default as turbo mode will alter multiple
50 * clocks at once.
51 *
52 * Even though we're able to access the clock registers directly we're bound to
53 * use the firmware interface as the firmware ultimately takes care of
54 * mitigating overheating/undervoltage situations and we would be changing
55 * frequencies behind his back.
56 *
57 * For more information on the firmware interface check:
58 * https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
59 */
60struct raspberrypi_firmware_prop {
61 __le32 id;
62 __le32 val;
63 __le32 disable_turbo;
64} __packed;
65
66static int raspberrypi_clock_property(struct rpi_firmware *firmware, u32 tag,
67 u32 clk, u32 *val)
68{
69 struct raspberrypi_firmware_prop msg = {
70 .id = cpu_to_le32(clk),
71 .val = cpu_to_le32(*val),
72 .disable_turbo = cpu_to_le32(1),
73 };
74 int ret;
75
76 ret = rpi_firmware_property(firmware, tag, &msg, sizeof(msg));
77 if (ret)
78 return ret;
79
80 *val = le32_to_cpu(msg.val);
81
82 return 0;
83}
84
85static int raspberrypi_fw_pll_is_on(struct clk_hw *hw)
86{
87 struct raspberrypi_clk *rpi = container_of(hw, struct raspberrypi_clk,
88 pllb);
89 u32 val = 0;
90 int ret;
91
92 ret = raspberrypi_clock_property(rpi->firmware,
93 RPI_FIRMWARE_GET_CLOCK_STATE,
94 RPI_FIRMWARE_ARM_CLK_ID, &val);
95 if (ret)
96 return 0;
97
98 return !!(val & RPI_FIRMWARE_STATE_ENABLE_BIT);
99}
100
101
102static unsigned long raspberrypi_fw_pll_get_rate(struct clk_hw *hw,
103 unsigned long parent_rate)
104{
105 struct raspberrypi_clk *rpi = container_of(hw, struct raspberrypi_clk,
106 pllb);
107 u32 val = 0;
108 int ret;
109
110 ret = raspberrypi_clock_property(rpi->firmware,
111 RPI_FIRMWARE_GET_CLOCK_RATE,
112 RPI_FIRMWARE_ARM_CLK_ID,
113 &val);
114 if (ret)
115 return ret;
116
117 return val * RPI_FIRMWARE_PLLB_ARM_DIV_RATE;
118}
119
120static int raspberrypi_fw_pll_set_rate(struct clk_hw *hw, unsigned long rate,
121 unsigned long parent_rate)
122{
123 struct raspberrypi_clk *rpi = container_of(hw, struct raspberrypi_clk,
124 pllb);
125 u32 new_rate = rate / RPI_FIRMWARE_PLLB_ARM_DIV_RATE;
126 int ret;
127
128 ret = raspberrypi_clock_property(rpi->firmware,
129 RPI_FIRMWARE_SET_CLOCK_RATE,
130 RPI_FIRMWARE_ARM_CLK_ID,
131 &new_rate);
132 if (ret)
133 dev_err_ratelimited(rpi->dev, "Failed to change %s frequency: %d",
134 clk_hw_get_name(hw), ret);
135
136 return ret;
137}
138
139/*
140 * Sadly there is no firmware rate rounding interface. We borrowed it from
141 * clk-bcm2835.
142 */
143static int raspberrypi_pll_determine_rate(struct clk_hw *hw,
144 struct clk_rate_request *req)
145{
146 struct raspberrypi_clk *rpi = container_of(hw, struct raspberrypi_clk,
147 pllb);
148 u64 div, final_rate;
149 u32 ndiv, fdiv;
150
151 /* We can't use req->rate directly as it would overflow */
152 final_rate = clamp(req->rate, rpi->min_rate, rpi->max_rate);
153
154 div = (u64)final_rate << A2W_PLL_FRAC_BITS;
155 do_div(div, req->best_parent_rate);
156
157 ndiv = div >> A2W_PLL_FRAC_BITS;
158 fdiv = div & ((1 << A2W_PLL_FRAC_BITS) - 1);
159
160 final_rate = ((u64)req->best_parent_rate *
161 ((ndiv << A2W_PLL_FRAC_BITS) + fdiv));
162
163 req->rate = final_rate >> A2W_PLL_FRAC_BITS;
164
165 return 0;
166}
167
168static const struct clk_ops raspberrypi_firmware_pll_clk_ops = {
169 .is_prepared = raspberrypi_fw_pll_is_on,
170 .recalc_rate = raspberrypi_fw_pll_get_rate,
171 .set_rate = raspberrypi_fw_pll_set_rate,
172 .determine_rate = raspberrypi_pll_determine_rate,
173};
174
175static int raspberrypi_register_pllb(struct raspberrypi_clk *rpi)
176{
177 u32 min_rate = 0, max_rate = 0;
178 struct clk_init_data init;
179 int ret;
180
181 memset(&init, 0, sizeof(init));
182
183 /* All of the PLLs derive from the external oscillator. */
184 init.parent_names = (const char *[]){ "osc" };
185 init.num_parents = 1;
186 init.name = "pllb";
187 init.ops = &raspberrypi_firmware_pll_clk_ops;
188 init.flags = CLK_GET_RATE_NOCACHE | CLK_IGNORE_UNUSED;
189
190 /* Get min & max rates set by the firmware */
191 ret = raspberrypi_clock_property(rpi->firmware,
192 RPI_FIRMWARE_GET_MIN_CLOCK_RATE,
193 RPI_FIRMWARE_ARM_CLK_ID,
194 &min_rate);
195 if (ret) {
196 dev_err(rpi->dev, "Failed to get %s min freq: %d\n",
197 init.name, ret);
198 return ret;
199 }
200
201 ret = raspberrypi_clock_property(rpi->firmware,
202 RPI_FIRMWARE_GET_MAX_CLOCK_RATE,
203 RPI_FIRMWARE_ARM_CLK_ID,
204 &max_rate);
205 if (ret) {
206 dev_err(rpi->dev, "Failed to get %s max freq: %d\n",
207 init.name, ret);
208 return ret;
209 }
210
211 if (!min_rate || !max_rate) {
212 dev_err(rpi->dev, "Unexpected frequency range: min %u, max %u\n",
213 min_rate, max_rate);
214 return -EINVAL;
215 }
216
217 dev_info(rpi->dev, "CPU frequency range: min %u, max %u\n",
218 min_rate, max_rate);
219
220 rpi->min_rate = min_rate * RPI_FIRMWARE_PLLB_ARM_DIV_RATE;
221 rpi->max_rate = max_rate * RPI_FIRMWARE_PLLB_ARM_DIV_RATE;
222
223 rpi->pllb.init = &init;
224
225 return devm_clk_hw_register(rpi->dev, &rpi->pllb);
226}
227
228static int raspberrypi_register_pllb_arm(struct raspberrypi_clk *rpi)
229{
230 rpi->pllb_arm = clk_hw_register_fixed_factor(rpi->dev,
231 "pllb_arm", "pllb",
232 CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE,
233 1, 2);
234 if (IS_ERR(rpi->pllb_arm)) {
235 dev_err(rpi->dev, "Failed to initialize pllb_arm\n");
236 return PTR_ERR(rpi->pllb_arm);
237 }
238
239 rpi->pllb_arm_lookup = clkdev_hw_create(rpi->pllb_arm, NULL, "cpu0");
240 if (!rpi->pllb_arm_lookup) {
241 dev_err(rpi->dev, "Failed to initialize pllb_arm_lookup\n");
242 clk_hw_unregister_fixed_factor(rpi->pllb_arm);
243 return -ENOMEM;
244 }
245
246 return 0;
247}
248
249static int raspberrypi_clk_probe(struct platform_device *pdev)
250{
251 struct device_node *firmware_node;
252 struct device *dev = &pdev->dev;
253 struct rpi_firmware *firmware;
254 struct raspberrypi_clk *rpi;
255 int ret;
256
257 firmware_node = of_find_compatible_node(NULL, NULL,
258 "raspberrypi,bcm2835-firmware");
259 if (!firmware_node) {
260 dev_err(dev, "Missing firmware node\n");
261 return -ENOENT;
262 }
263
264 firmware = rpi_firmware_get(firmware_node);
265 of_node_put(firmware_node);
266 if (!firmware)
267 return -EPROBE_DEFER;
268
269 rpi = devm_kzalloc(dev, sizeof(*rpi), GFP_KERNEL);
270 if (!rpi)
271 return -ENOMEM;
272
273 rpi->dev = dev;
274 rpi->firmware = firmware;
275
276 ret = raspberrypi_register_pllb(rpi);
277 if (ret) {
278 dev_err(dev, "Failed to initialize pllb, %d\n", ret);
279 return ret;
280 }
281
282 ret = raspberrypi_register_pllb_arm(rpi);
283 if (ret)
284 return ret;
285
286 return 0;
287}
288
289static struct platform_driver raspberrypi_clk_driver = {
290 .driver = {
291 .name = "raspberrypi-clk",
292 },
293 .probe = raspberrypi_clk_probe,
294};
295module_platform_driver(raspberrypi_clk_driver);
296
297MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>");
298MODULE_DESCRIPTION("Raspberry Pi firmware clock driver");
299MODULE_LICENSE("GPL");
300MODULE_ALIAS("platform:raspberrypi-clk");