aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Walmsley <paul.walmsley@sifive.com>2019-04-30 16:51:00 -0400
committerStephen Boyd <sboyd@kernel.org>2019-05-03 12:20:56 -0400
commit30b8e27e3b581a173779e52096237ed19172eaf4 (patch)
tree663babb80e015cae0fcc4f90af5aaf796eb24d20
parent7b9487a9a5c41ce0ff4f6ca74652e99541bd51c3 (diff)
clk: sifive: add a driver for the SiFive FU540 PRCI IP block
Add driver code for the SiFive FU540 PRCI IP block. This IP block handles reset and clock control for the SiFive FU540 device and implements SoC-level clock tree controls and dividers. Based on code written by Wesley Terpstra <wesley@sifive.com>: https://github.com/riscv/riscv-linux/commit/999529edf517ed75b56659d456d221b2ee56bb60 Boot and PLL rate change were tested on a SiFive HiFive Unleashed board. This version includes several changes requested by Stephen Boyd <sboyd@kernel.org>. Signed-off-by: Paul Walmsley <paul.walmsley@sifive.com> Signed-off-by: Paul Walmsley <paul@pwsan.com> Cc: Michael Turquette <mturquette@baylibre.com> Cc: Stephen Boyd <sboyd@kernel.org> Cc: Albert Ou <aou@eecs.berkeley.edu> Cc: Wesley W. Terpstra <wesley@sifive.com> Cc: Palmer Dabbelt <palmer@sifive.com> Cc: Megan Wachs <megan@sifive.com> Cc: linux-riscv@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: linux-clk@vger.kernel.org [sboyd@kernel.org: Fix some const and ARRAY_SIZE() issues, make makefile only descend if CLK_SIFIVE=y] Signed-off-by: Stephen Boyd <sboyd@kernel.org>
-rw-r--r--drivers/clk/Kconfig1
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/sifive/Kconfig18
-rw-r--r--drivers/clk/sifive/Makefile1
-rw-r--r--drivers/clk/sifive/fu540-prci.c626
5 files changed, 647 insertions, 0 deletions
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 8a0e77f791ab..420e80508ddc 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -311,6 +311,7 @@ source "drivers/clk/mvebu/Kconfig"
311source "drivers/clk/qcom/Kconfig" 311source "drivers/clk/qcom/Kconfig"
312source "drivers/clk/renesas/Kconfig" 312source "drivers/clk/renesas/Kconfig"
313source "drivers/clk/samsung/Kconfig" 313source "drivers/clk/samsung/Kconfig"
314source "drivers/clk/sifive/Kconfig"
314source "drivers/clk/sprd/Kconfig" 315source "drivers/clk/sprd/Kconfig"
315source "drivers/clk/sunxi-ng/Kconfig" 316source "drivers/clk/sunxi-ng/Kconfig"
316source "drivers/clk/tegra/Kconfig" 317source "drivers/clk/tegra/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 091ee1d8af65..692f69ef6779 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -94,6 +94,7 @@ obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/
94obj-y += renesas/ 94obj-y += renesas/
95obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ 95obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
96obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/ 96obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/
97obj-$(CONFIG_CLK_SIFIVE) += sifive/
97obj-$(CONFIG_ARCH_SIRF) += sirf/ 98obj-$(CONFIG_ARCH_SIRF) += sirf/
98obj-$(CONFIG_ARCH_SOCFPGA) += socfpga/ 99obj-$(CONFIG_ARCH_SOCFPGA) += socfpga/
99obj-$(CONFIG_PLAT_SPEAR) += spear/ 100obj-$(CONFIG_PLAT_SPEAR) += spear/
diff --git a/drivers/clk/sifive/Kconfig b/drivers/clk/sifive/Kconfig
new file mode 100644
index 000000000000..8db4a3eb4782
--- /dev/null
+++ b/drivers/clk/sifive/Kconfig
@@ -0,0 +1,18 @@
1# SPDX-License-Identifier: GPL-2.0
2
3menuconfig CLK_SIFIVE
4 bool "SiFive SoC driver support"
5 help
6 SoC drivers for SiFive Linux-capable SoCs.
7
8if CLK_SIFIVE
9
10config CLK_SIFIVE_FU540_PRCI
11 bool "PRCI driver for SiFive FU540 SoCs"
12 select CLK_ANALOGBITS_WRPLL_CLN28HPC
13 help
14 Supports the Power Reset Clock interface (PRCI) IP block found in
15 FU540 SoCs. If this kernel is meant to run on a SiFive FU540 SoC,
16 enable this driver.
17
18endif
diff --git a/drivers/clk/sifive/Makefile b/drivers/clk/sifive/Makefile
new file mode 100644
index 000000000000..74d58a4c0756
--- /dev/null
+++ b/drivers/clk/sifive/Makefile
@@ -0,0 +1 @@
obj-$(CONFIG_CLK_SIFIVE_FU540_PRCI) += fu540-prci.o
diff --git a/drivers/clk/sifive/fu540-prci.c b/drivers/clk/sifive/fu540-prci.c
new file mode 100644
index 000000000000..0ec8bf7b4b28
--- /dev/null
+++ b/drivers/clk/sifive/fu540-prci.c
@@ -0,0 +1,626 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2018-2019 SiFive, Inc.
4 * Wesley Terpstra
5 * Paul Walmsley
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * The FU540 PRCI implements clock and reset control for the SiFive
17 * FU540-C000 chip. This driver assumes that it has sole control
18 * over all PRCI resources.
19 *
20 * This driver is based on the PRCI driver written by Wesley Terpstra:
21 * https://github.com/riscv/riscv-linux/commit/999529edf517ed75b56659d456d221b2ee56bb60
22 *
23 * References:
24 * - SiFive FU540-C000 manual v1p0, Chapter 7 "Clocking and Reset"
25 */
26
27#include <dt-bindings/clock/sifive-fu540-prci.h>
28#include <linux/clkdev.h>
29#include <linux/clk-provider.h>
30#include <linux/clk/analogbits-wrpll-cln28hpc.h>
31#include <linux/delay.h>
32#include <linux/err.h>
33#include <linux/module.h>
34#include <linux/of.h>
35#include <linux/of_clk.h>
36#include <linux/platform_device.h>
37#include <linux/slab.h>
38
39/*
40 * EXPECTED_CLK_PARENT_COUNT: how many parent clocks this driver expects:
41 * hfclk and rtcclk
42 */
43#define EXPECTED_CLK_PARENT_COUNT 2
44
45/*
46 * Register offsets and bitmasks
47 */
48
49/* COREPLLCFG0 */
50#define PRCI_COREPLLCFG0_OFFSET 0x4
51# define PRCI_COREPLLCFG0_DIVR_SHIFT 0
52# define PRCI_COREPLLCFG0_DIVR_MASK (0x3f << PRCI_COREPLLCFG0_DIVR_SHIFT)
53# define PRCI_COREPLLCFG0_DIVF_SHIFT 6
54# define PRCI_COREPLLCFG0_DIVF_MASK (0x1ff << PRCI_COREPLLCFG0_DIVF_SHIFT)
55# define PRCI_COREPLLCFG0_DIVQ_SHIFT 15
56# define PRCI_COREPLLCFG0_DIVQ_MASK (0x7 << PRCI_COREPLLCFG0_DIVQ_SHIFT)
57# define PRCI_COREPLLCFG0_RANGE_SHIFT 18
58# define PRCI_COREPLLCFG0_RANGE_MASK (0x7 << PRCI_COREPLLCFG0_RANGE_SHIFT)
59# define PRCI_COREPLLCFG0_BYPASS_SHIFT 24
60# define PRCI_COREPLLCFG0_BYPASS_MASK (0x1 << PRCI_COREPLLCFG0_BYPASS_SHIFT)
61# define PRCI_COREPLLCFG0_FSE_SHIFT 25
62# define PRCI_COREPLLCFG0_FSE_MASK (0x1 << PRCI_COREPLLCFG0_FSE_SHIFT)
63# define PRCI_COREPLLCFG0_LOCK_SHIFT 31
64# define PRCI_COREPLLCFG0_LOCK_MASK (0x1 << PRCI_COREPLLCFG0_LOCK_SHIFT)
65
66/* DDRPLLCFG0 */
67#define PRCI_DDRPLLCFG0_OFFSET 0xc
68# define PRCI_DDRPLLCFG0_DIVR_SHIFT 0
69# define PRCI_DDRPLLCFG0_DIVR_MASK (0x3f << PRCI_DDRPLLCFG0_DIVR_SHIFT)
70# define PRCI_DDRPLLCFG0_DIVF_SHIFT 6
71# define PRCI_DDRPLLCFG0_DIVF_MASK (0x1ff << PRCI_DDRPLLCFG0_DIVF_SHIFT)
72# define PRCI_DDRPLLCFG0_DIVQ_SHIFT 15
73# define PRCI_DDRPLLCFG0_DIVQ_MASK (0x7 << PRCI_DDRPLLCFG0_DIVQ_SHIFT)
74# define PRCI_DDRPLLCFG0_RANGE_SHIFT 18
75# define PRCI_DDRPLLCFG0_RANGE_MASK (0x7 << PRCI_DDRPLLCFG0_RANGE_SHIFT)
76# define PRCI_DDRPLLCFG0_BYPASS_SHIFT 24
77# define PRCI_DDRPLLCFG0_BYPASS_MASK (0x1 << PRCI_DDRPLLCFG0_BYPASS_SHIFT)
78# define PRCI_DDRPLLCFG0_FSE_SHIFT 25
79# define PRCI_DDRPLLCFG0_FSE_MASK (0x1 << PRCI_DDRPLLCFG0_FSE_SHIFT)
80# define PRCI_DDRPLLCFG0_LOCK_SHIFT 31
81# define PRCI_DDRPLLCFG0_LOCK_MASK (0x1 << PRCI_DDRPLLCFG0_LOCK_SHIFT)
82
83/* DDRPLLCFG1 */
84#define PRCI_DDRPLLCFG1_OFFSET 0x10
85# define PRCI_DDRPLLCFG1_CKE_SHIFT 24
86# define PRCI_DDRPLLCFG1_CKE_MASK (0x1 << PRCI_DDRPLLCFG1_CKE_SHIFT)
87
88/* GEMGXLPLLCFG0 */
89#define PRCI_GEMGXLPLLCFG0_OFFSET 0x1c
90# define PRCI_GEMGXLPLLCFG0_DIVR_SHIFT 0
91# define PRCI_GEMGXLPLLCFG0_DIVR_MASK (0x3f << PRCI_GEMGXLPLLCFG0_DIVR_SHIFT)
92# define PRCI_GEMGXLPLLCFG0_DIVF_SHIFT 6
93# define PRCI_GEMGXLPLLCFG0_DIVF_MASK (0x1ff << PRCI_GEMGXLPLLCFG0_DIVF_SHIFT)
94# define PRCI_GEMGXLPLLCFG0_DIVQ_SHIFT 15
95# define PRCI_GEMGXLPLLCFG0_DIVQ_MASK (0x7 << PRCI_GEMGXLPLLCFG0_DIVQ_SHIFT)
96# define PRCI_GEMGXLPLLCFG0_RANGE_SHIFT 18
97# define PRCI_GEMGXLPLLCFG0_RANGE_MASK (0x7 << PRCI_GEMGXLPLLCFG0_RANGE_SHIFT)
98# define PRCI_GEMGXLPLLCFG0_BYPASS_SHIFT 24
99# define PRCI_GEMGXLPLLCFG0_BYPASS_MASK (0x1 << PRCI_GEMGXLPLLCFG0_BYPASS_SHIFT)
100# define PRCI_GEMGXLPLLCFG0_FSE_SHIFT 25
101# define PRCI_GEMGXLPLLCFG0_FSE_MASK (0x1 << PRCI_GEMGXLPLLCFG0_FSE_SHIFT)
102# define PRCI_GEMGXLPLLCFG0_LOCK_SHIFT 31
103# define PRCI_GEMGXLPLLCFG0_LOCK_MASK (0x1 << PRCI_GEMGXLPLLCFG0_LOCK_SHIFT)
104
105/* GEMGXLPLLCFG1 */
106#define PRCI_GEMGXLPLLCFG1_OFFSET 0x20
107# define PRCI_GEMGXLPLLCFG1_CKE_SHIFT 24
108# define PRCI_GEMGXLPLLCFG1_CKE_MASK (0x1 << PRCI_GEMGXLPLLCFG1_CKE_SHIFT)
109
110/* CORECLKSEL */
111#define PRCI_CORECLKSEL_OFFSET 0x24
112# define PRCI_CORECLKSEL_CORECLKSEL_SHIFT 0
113# define PRCI_CORECLKSEL_CORECLKSEL_MASK (0x1 << PRCI_CORECLKSEL_CORECLKSEL_SHIFT)
114
115/* DEVICESRESETREG */
116#define PRCI_DEVICESRESETREG_OFFSET 0x28
117# define PRCI_DEVICESRESETREG_DDR_CTRL_RST_N_SHIFT 0
118# define PRCI_DEVICESRESETREG_DDR_CTRL_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_DDR_CTRL_RST_N_SHIFT)
119# define PRCI_DEVICESRESETREG_DDR_AXI_RST_N_SHIFT 1
120# define PRCI_DEVICESRESETREG_DDR_AXI_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_DDR_AXI_RST_N_SHIFT)
121# define PRCI_DEVICESRESETREG_DDR_AHB_RST_N_SHIFT 2
122# define PRCI_DEVICESRESETREG_DDR_AHB_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_DDR_AHB_RST_N_SHIFT)
123# define PRCI_DEVICESRESETREG_DDR_PHY_RST_N_SHIFT 3
124# define PRCI_DEVICESRESETREG_DDR_PHY_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_DDR_PHY_RST_N_SHIFT)
125# define PRCI_DEVICESRESETREG_GEMGXL_RST_N_SHIFT 5
126# define PRCI_DEVICESRESETREG_GEMGXL_RST_N_MASK (0x1 << PRCI_DEVICESRESETREG_GEMGXL_RST_N_SHIFT)
127
128/* CLKMUXSTATUSREG */
129#define PRCI_CLKMUXSTATUSREG_OFFSET 0x2c
130# define PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_SHIFT 1
131# define PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_MASK (0x1 << PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_SHIFT)
132
133/*
134 * Private structures
135 */
136
137/**
138 * struct __prci_data - per-device-instance data
139 * @va: base virtual address of the PRCI IP block
140 * @hw_clks: encapsulates struct clk_hw records
141 *
142 * PRCI per-device instance data
143 */
144struct __prci_data {
145 void __iomem *va;
146 struct clk_hw_onecell_data hw_clks;
147};
148
149/**
150 * struct __prci_wrpll_data - WRPLL configuration and integration data
151 * @c: WRPLL current configuration record
152 * @enable_bypass: fn ptr to code to bypass the WRPLL (if applicable; else NULL)
153 * @disable_bypass: fn ptr to code to not bypass the WRPLL (or NULL)
154 * @cfg0_offs: WRPLL CFG0 register offset (in bytes) from the PRCI base address
155 *
156 * @enable_bypass and @disable_bypass are used for WRPLL instances
157 * that contain a separate external glitchless clock mux downstream
158 * from the PLL. The WRPLL internal bypass mux is not glitchless.
159 */
160struct __prci_wrpll_data {
161 struct wrpll_cfg c;
162 void (*enable_bypass)(struct __prci_data *pd);
163 void (*disable_bypass)(struct __prci_data *pd);
164 u8 cfg0_offs;
165};
166
167/**
168 * struct __prci_clock - describes a clock device managed by PRCI
169 * @name: user-readable clock name string - should match the manual
170 * @parent_name: parent name for this clock
171 * @ops: struct clk_ops for the Linux clock framework to use for control
172 * @hw: Linux-private clock data
173 * @pwd: WRPLL-specific data, associated with this clock (if not NULL)
174 * @pd: PRCI-specific data associated with this clock (if not NULL)
175 *
176 * PRCI clock data. Used by the PRCI driver to register PRCI-provided
177 * clocks to the Linux clock infrastructure.
178 */
179struct __prci_clock {
180 const char *name;
181 const char *parent_name;
182 const struct clk_ops *ops;
183 struct clk_hw hw;
184 struct __prci_wrpll_data *pwd;
185 struct __prci_data *pd;
186};
187
188#define clk_hw_to_prci_clock(pwd) container_of(pwd, struct __prci_clock, hw)
189
190/*
191 * Private functions
192 */
193
194/**
195 * __prci_readl() - read from a PRCI register
196 * @pd: PRCI context
197 * @offs: register offset to read from (in bytes, from PRCI base address)
198 *
199 * Read the register located at offset @offs from the base virtual
200 * address of the PRCI register target described by @pd, and return
201 * the value to the caller.
202 *
203 * Context: Any context.
204 *
205 * Return: the contents of the register described by @pd and @offs.
206 */
207static u32 __prci_readl(struct __prci_data *pd, u32 offs)
208{
209 return readl_relaxed(pd->va + offs);
210}
211
212static void __prci_writel(u32 v, u32 offs, struct __prci_data *pd)
213{
214 writel_relaxed(v, pd->va + offs);
215}
216
217/* WRPLL-related private functions */
218
219/**
220 * __prci_wrpll_unpack() - unpack WRPLL configuration registers into parameters
221 * @c: ptr to a struct wrpll_cfg record to write config into
222 * @r: value read from the PRCI PLL configuration register
223 *
224 * Given a value @r read from an FU540 PRCI PLL configuration register,
225 * split it into fields and populate it into the WRPLL configuration record
226 * pointed to by @c.
227 *
228 * The COREPLLCFG0 macros are used below, but the other *PLLCFG0 macros
229 * have the same register layout.
230 *
231 * Context: Any context.
232 */
233static void __prci_wrpll_unpack(struct wrpll_cfg *c, u32 r)
234{
235 u32 v;
236
237 v = r & PRCI_COREPLLCFG0_DIVR_MASK;
238 v >>= PRCI_COREPLLCFG0_DIVR_SHIFT;
239 c->divr = v;
240
241 v = r & PRCI_COREPLLCFG0_DIVF_MASK;
242 v >>= PRCI_COREPLLCFG0_DIVF_SHIFT;
243 c->divf = v;
244
245 v = r & PRCI_COREPLLCFG0_DIVQ_MASK;
246 v >>= PRCI_COREPLLCFG0_DIVQ_SHIFT;
247 c->divq = v;
248
249 v = r & PRCI_COREPLLCFG0_RANGE_MASK;
250 v >>= PRCI_COREPLLCFG0_RANGE_SHIFT;
251 c->range = v;
252
253 c->flags &= (WRPLL_FLAGS_INT_FEEDBACK_MASK |
254 WRPLL_FLAGS_EXT_FEEDBACK_MASK);
255
256 /* external feedback mode not supported */
257 c->flags |= WRPLL_FLAGS_INT_FEEDBACK_MASK;
258}
259
260/**
261 * __prci_wrpll_pack() - pack PLL configuration parameters into a register value
262 * @c: pointer to a struct wrpll_cfg record containing the PLL's cfg
263 *
264 * Using a set of WRPLL configuration values pointed to by @c,
265 * assemble a PRCI PLL configuration register value, and return it to
266 * the caller.
267 *
268 * Context: Any context. Caller must ensure that the contents of the
269 * record pointed to by @c do not change during the execution
270 * of this function.
271 *
272 * Returns: a value suitable for writing into a PRCI PLL configuration
273 * register
274 */
275static u32 __prci_wrpll_pack(const struct wrpll_cfg *c)
276{
277 u32 r = 0;
278
279 r |= c->divr << PRCI_COREPLLCFG0_DIVR_SHIFT;
280 r |= c->divf << PRCI_COREPLLCFG0_DIVF_SHIFT;
281 r |= c->divq << PRCI_COREPLLCFG0_DIVQ_SHIFT;
282 r |= c->range << PRCI_COREPLLCFG0_RANGE_SHIFT;
283
284 /* external feedback mode not supported */
285 r |= PRCI_COREPLLCFG0_FSE_MASK;
286
287 return r;
288}
289
290/**
291 * __prci_wrpll_read_cfg() - read the WRPLL configuration from the PRCI
292 * @pd: PRCI context
293 * @pwd: PRCI WRPLL metadata
294 *
295 * Read the current configuration of the PLL identified by @pwd from
296 * the PRCI identified by @pd, and store it into the local configuration
297 * cache in @pwd.
298 *
299 * Context: Any context. Caller must prevent the records pointed to by
300 * @pd and @pwd from changing during execution.
301 */
302static void __prci_wrpll_read_cfg(struct __prci_data *pd,
303 struct __prci_wrpll_data *pwd)
304{
305 __prci_wrpll_unpack(&pwd->c, __prci_readl(pd, pwd->cfg0_offs));
306}
307
308/**
309 * __prci_wrpll_write_cfg() - write WRPLL configuration into the PRCI
310 * @pd: PRCI context
311 * @pwd: PRCI WRPLL metadata
312 * @c: WRPLL configuration record to write
313 *
314 * Write the WRPLL configuration described by @c into the WRPLL
315 * configuration register identified by @pwd in the PRCI instance
316 * described by @c. Make a cached copy of the WRPLL's current
317 * configuration so it can be used by other code.
318 *
319 * Context: Any context. Caller must prevent the records pointed to by
320 * @pd and @pwd from changing during execution.
321 */
322static void __prci_wrpll_write_cfg(struct __prci_data *pd,
323 struct __prci_wrpll_data *pwd,
324 struct wrpll_cfg *c)
325{
326 __prci_writel(__prci_wrpll_pack(c), pwd->cfg0_offs, pd);
327
328 memcpy(&pwd->c, c, sizeof(*c));
329}
330
331/* Core clock mux control */
332
333/**
334 * __prci_coreclksel_use_hfclk() - switch the CORECLK mux to output HFCLK
335 * @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg
336 *
337 * Switch the CORECLK mux to the HFCLK input source; return once complete.
338 *
339 * Context: Any context. Caller must prevent concurrent changes to the
340 * PRCI_CORECLKSEL_OFFSET register.
341 */
342static void __prci_coreclksel_use_hfclk(struct __prci_data *pd)
343{
344 u32 r;
345
346 r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);
347 r |= PRCI_CORECLKSEL_CORECLKSEL_MASK;
348 __prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd);
349
350 r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET); /* barrier */
351}
352
353/**
354 * __prci_coreclksel_use_corepll() - switch the CORECLK mux to output COREPLL
355 * @pd: struct __prci_data * for the PRCI containing the CORECLK mux reg
356 *
357 * Switch the CORECLK mux to the PLL output clock; return once complete.
358 *
359 * Context: Any context. Caller must prevent concurrent changes to the
360 * PRCI_CORECLKSEL_OFFSET register.
361 */
362static void __prci_coreclksel_use_corepll(struct __prci_data *pd)
363{
364 u32 r;
365
366 r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET);
367 r &= ~PRCI_CORECLKSEL_CORECLKSEL_MASK;
368 __prci_writel(r, PRCI_CORECLKSEL_OFFSET, pd);
369
370 r = __prci_readl(pd, PRCI_CORECLKSEL_OFFSET); /* barrier */
371}
372
373/*
374 * Linux clock framework integration
375 *
376 * See the Linux clock framework documentation for more information on
377 * these functions.
378 */
379
380static unsigned long sifive_fu540_prci_wrpll_recalc_rate(struct clk_hw *hw,
381 unsigned long parent_rate)
382{
383 struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
384 struct __prci_wrpll_data *pwd = pc->pwd;
385
386 return wrpll_calc_output_rate(&pwd->c, parent_rate);
387}
388
389static long sifive_fu540_prci_wrpll_round_rate(struct clk_hw *hw,
390 unsigned long rate,
391 unsigned long *parent_rate)
392{
393 struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
394 struct __prci_wrpll_data *pwd = pc->pwd;
395 struct wrpll_cfg c;
396
397 memcpy(&c, &pwd->c, sizeof(c));
398
399 wrpll_configure_for_rate(&c, rate, *parent_rate);
400
401 return wrpll_calc_output_rate(&c, *parent_rate);
402}
403
404static int sifive_fu540_prci_wrpll_set_rate(struct clk_hw *hw,
405 unsigned long rate,
406 unsigned long parent_rate)
407{
408 struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
409 struct __prci_wrpll_data *pwd = pc->pwd;
410 struct __prci_data *pd = pc->pd;
411 int r;
412
413 r = wrpll_configure_for_rate(&pwd->c, rate, parent_rate);
414 if (r)
415 return r;
416
417 if (pwd->enable_bypass)
418 pwd->enable_bypass(pd);
419
420 __prci_wrpll_write_cfg(pd, pwd, &pwd->c);
421
422 udelay(wrpll_calc_max_lock_us(&pwd->c));
423
424 if (pwd->disable_bypass)
425 pwd->disable_bypass(pd);
426
427 return 0;
428}
429
430static const struct clk_ops sifive_fu540_prci_wrpll_clk_ops = {
431 .set_rate = sifive_fu540_prci_wrpll_set_rate,
432 .round_rate = sifive_fu540_prci_wrpll_round_rate,
433 .recalc_rate = sifive_fu540_prci_wrpll_recalc_rate,
434};
435
436static const struct clk_ops sifive_fu540_prci_wrpll_ro_clk_ops = {
437 .recalc_rate = sifive_fu540_prci_wrpll_recalc_rate,
438};
439
440/* TLCLKSEL clock integration */
441
442static unsigned long sifive_fu540_prci_tlclksel_recalc_rate(struct clk_hw *hw,
443 unsigned long parent_rate)
444{
445 struct __prci_clock *pc = clk_hw_to_prci_clock(hw);
446 struct __prci_data *pd = pc->pd;
447 u32 v;
448 u8 div;
449
450 v = __prci_readl(pd, PRCI_CLKMUXSTATUSREG_OFFSET);
451 v &= PRCI_CLKMUXSTATUSREG_TLCLKSEL_STATUS_MASK;
452 div = v ? 1 : 2;
453
454 return div_u64(parent_rate, div);
455}
456
457static const struct clk_ops sifive_fu540_prci_tlclksel_clk_ops = {
458 .recalc_rate = sifive_fu540_prci_tlclksel_recalc_rate,
459};
460
461/*
462 * PRCI integration data for each WRPLL instance
463 */
464
465static struct __prci_wrpll_data __prci_corepll_data = {
466 .cfg0_offs = PRCI_COREPLLCFG0_OFFSET,
467 .enable_bypass = __prci_coreclksel_use_hfclk,
468 .disable_bypass = __prci_coreclksel_use_corepll,
469};
470
471static struct __prci_wrpll_data __prci_ddrpll_data = {
472 .cfg0_offs = PRCI_DDRPLLCFG0_OFFSET,
473};
474
475static struct __prci_wrpll_data __prci_gemgxlpll_data = {
476 .cfg0_offs = PRCI_GEMGXLPLLCFG0_OFFSET,
477};
478
479/*
480 * List of clock controls provided by the PRCI
481 */
482
483static struct __prci_clock __prci_init_clocks[] = {
484 [PRCI_CLK_COREPLL] = {
485 .name = "corepll",
486 .parent_name = "hfclk",
487 .ops = &sifive_fu540_prci_wrpll_clk_ops,
488 .pwd = &__prci_corepll_data,
489 },
490 [PRCI_CLK_DDRPLL] = {
491 .name = "ddrpll",
492 .parent_name = "hfclk",
493 .ops = &sifive_fu540_prci_wrpll_ro_clk_ops,
494 .pwd = &__prci_ddrpll_data,
495 },
496 [PRCI_CLK_GEMGXLPLL] = {
497 .name = "gemgxlpll",
498 .parent_name = "hfclk",
499 .ops = &sifive_fu540_prci_wrpll_clk_ops,
500 .pwd = &__prci_gemgxlpll_data,
501 },
502 [PRCI_CLK_TLCLK] = {
503 .name = "tlclk",
504 .parent_name = "corepll",
505 .ops = &sifive_fu540_prci_tlclksel_clk_ops,
506 },
507};
508
509/**
510 * __prci_register_clocks() - register clock controls in the PRCI with Linux
511 * @dev: Linux struct device *
512 *
513 * Register the list of clock controls described in __prci_init_plls[] with
514 * the Linux clock framework.
515 *
516 * Return: 0 upon success or a negative error code upon failure.
517 */
518static int __prci_register_clocks(struct device *dev, struct __prci_data *pd)
519{
520 struct clk_init_data init = { };
521 struct __prci_clock *pic;
522 int parent_count, i, r;
523
524 parent_count = of_clk_get_parent_count(dev->of_node);
525 if (parent_count != EXPECTED_CLK_PARENT_COUNT) {
526 dev_err(dev, "expected only two parent clocks, found %d\n",
527 parent_count);
528 return -EINVAL;
529 }
530
531 /* Register PLLs */
532 for (i = 0; i < ARRAY_SIZE(__prci_init_clocks); ++i) {
533 pic = &__prci_init_clocks[i];
534
535 init.name = pic->name;
536 init.parent_names = &pic->parent_name;
537 init.num_parents = 1;
538 init.ops = pic->ops;
539 pic->hw.init = &init;
540
541 pic->pd = pd;
542
543 if (pic->pwd)
544 __prci_wrpll_read_cfg(pd, pic->pwd);
545
546 r = devm_clk_hw_register(dev, &pic->hw);
547 if (r) {
548 dev_warn(dev, "Failed to register clock %s: %d\n",
549 init.name, r);
550 return r;
551 }
552
553 r = clk_hw_register_clkdev(&pic->hw, pic->name, dev_name(dev));
554 if (r) {
555 dev_warn(dev, "Failed to register clkdev for %s: %d\n",
556 init.name, r);
557 return r;
558 }
559
560 pd->hw_clks.hws[i] = &pic->hw;
561 }
562
563 pd->hw_clks.num = i;
564
565 r = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
566 &pd->hw_clks);
567 if (r) {
568 dev_err(dev, "could not add hw_provider: %d\n", r);
569 return r;
570 }
571
572 return 0;
573}
574
575/*
576 * Linux device model integration
577 *
578 * See the Linux device model documentation for more information about
579 * these functions.
580 */
581static int sifive_fu540_prci_probe(struct platform_device *pdev)
582{
583 struct device *dev = &pdev->dev;
584 struct resource *res;
585 struct __prci_data *pd;
586 int r;
587
588 pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
589 if (!pd)
590 return -ENOMEM;
591
592 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
593 pd->va = devm_ioremap_resource(dev, res);
594 if (IS_ERR(pd->va))
595 return PTR_ERR(pd->va);
596
597 r = __prci_register_clocks(dev, pd);
598 if (r) {
599 dev_err(dev, "could not register clocks: %d\n", r);
600 return r;
601 }
602
603 dev_dbg(dev, "SiFive FU540 PRCI probed\n");
604
605 return 0;
606}
607
608static const struct of_device_id sifive_fu540_prci_of_match[] = {
609 { .compatible = "sifive,fu540-c000-prci", },
610 {}
611};
612MODULE_DEVICE_TABLE(of, sifive_fu540_prci_of_match);
613
614static struct platform_driver sifive_fu540_prci_driver = {
615 .driver = {
616 .name = "sifive-fu540-prci",
617 .of_match_table = sifive_fu540_prci_of_match,
618 },
619 .probe = sifive_fu540_prci_probe,
620};
621
622static int __init sifive_fu540_prci_init(void)
623{
624 return platform_driver_register(&sifive_fu540_prci_driver);
625}
626core_initcall(sifive_fu540_prci_init);