1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (c) 2014 Marvell Technology Group Ltd.
5 * Alexandre Belloni <alexandre.belloni@free-electrons.com>
6 * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
8 #include <linux/bitops.h>
9 #include <linux/clk-provider.h>
12 #include <linux/of_address.h>
13 #include <linux/slab.h>
14 #include <linux/spinlock.h>
16 #include "berlin2-div.h"
19 * Clock dividers in Berlin2 SoCs comprise a complex cell to select
20 * input pll and divider. The virtual structure as it is used in Marvell
21 * BSP code can be seen as:
24 * pll0 --------------->| 0 | +---+
25 * +---+ |(B)|--+--------------->| 0 | +---+
26 * pll1.0 -->| 0 | +-->| 1 | | +--------+ |(E)|----->| 0 | +---+
27 * pll1.1 -->| 1 | | +---+ +-->|(C) 1:M |-->| 1 | |(F)|-->|(G)|->
28 * ... -->|(A)|--+ | +--------+ +---+ +-->| 1 | +---+
29 * ... -->| | +-->|(D) 1:3 |----------+ +---+
30 * pll1.N -->| N | +---------
33 * (A) input pll clock mux controlled by <PllSelect[1:n]>
34 * (B) input pll bypass mux controlled by <PllSwitch>
35 * (C) programmable clock divider controlled by <Select[1:n]>
36 * (D) constant div-by-3 clock divider
37 * (E) programmable clock divider bypass controlled by <Switch>
38 * (F) constant div-by-3 clock mux controlled by <D3Switch>
39 * (G) clock gate controlled by <Enable>
41 * For whatever reason, above control signals come in two flavors:
42 * - single register dividers with all bits in one register
43 * - shared register dividers with bits spread over multiple registers
44 * (including signals for the same cell spread over consecutive registers)
46 * Also, clock gate and pll mux is not available on every div cell, so
47 * we have to deal with those, too. We reuse common clock composite driver
51 #define PLL_SELECT_MASK 0x7
52 #define DIV_SELECT_MASK 0x7
57 struct berlin2_div_map map
;
61 #define to_berlin2_div(hw) container_of(hw, struct berlin2_div, hw)
63 static u8 clk_div
[] = { 1, 2, 4, 6, 8, 12, 1, 1 };
65 static int berlin2_div_is_enabled(struct clk_hw
*hw
)
67 struct berlin2_div
*div
= to_berlin2_div(hw
);
68 struct berlin2_div_map
*map
= &div
->map
;
74 reg
= readl_relaxed(div
->base
+ map
->gate_offs
);
75 reg
>>= map
->gate_shift
;
78 spin_unlock(div
->lock
);
83 static int berlin2_div_enable(struct clk_hw
*hw
)
85 struct berlin2_div
*div
= to_berlin2_div(hw
);
86 struct berlin2_div_map
*map
= &div
->map
;
92 reg
= readl_relaxed(div
->base
+ map
->gate_offs
);
93 reg
|= BIT(map
->gate_shift
);
94 writel_relaxed(reg
, div
->base
+ map
->gate_offs
);
97 spin_unlock(div
->lock
);
102 static void berlin2_div_disable(struct clk_hw
*hw
)
104 struct berlin2_div
*div
= to_berlin2_div(hw
);
105 struct berlin2_div_map
*map
= &div
->map
;
109 spin_lock(div
->lock
);
111 reg
= readl_relaxed(div
->base
+ map
->gate_offs
);
112 reg
&= ~BIT(map
->gate_shift
);
113 writel_relaxed(reg
, div
->base
+ map
->gate_offs
);
116 spin_unlock(div
->lock
);
119 static int berlin2_div_set_parent(struct clk_hw
*hw
, u8 index
)
121 struct berlin2_div
*div
= to_berlin2_div(hw
);
122 struct berlin2_div_map
*map
= &div
->map
;
126 spin_lock(div
->lock
);
128 /* index == 0 is PLL_SWITCH */
129 reg
= readl_relaxed(div
->base
+ map
->pll_switch_offs
);
131 reg
&= ~BIT(map
->pll_switch_shift
);
133 reg
|= BIT(map
->pll_switch_shift
);
134 writel_relaxed(reg
, div
->base
+ map
->pll_switch_offs
);
136 /* index > 0 is PLL_SELECT */
138 reg
= readl_relaxed(div
->base
+ map
->pll_select_offs
);
139 reg
&= ~(PLL_SELECT_MASK
<< map
->pll_select_shift
);
140 reg
|= (index
- 1) << map
->pll_select_shift
;
141 writel_relaxed(reg
, div
->base
+ map
->pll_select_offs
);
145 spin_unlock(div
->lock
);
150 static u8
berlin2_div_get_parent(struct clk_hw
*hw
)
152 struct berlin2_div
*div
= to_berlin2_div(hw
);
153 struct berlin2_div_map
*map
= &div
->map
;
158 spin_lock(div
->lock
);
160 /* PLL_SWITCH == 0 is index 0 */
161 reg
= readl_relaxed(div
->base
+ map
->pll_switch_offs
);
162 reg
&= BIT(map
->pll_switch_shift
);
164 reg
= readl_relaxed(div
->base
+ map
->pll_select_offs
);
165 reg
>>= map
->pll_select_shift
;
166 reg
&= PLL_SELECT_MASK
;
171 spin_unlock(div
->lock
);
176 static unsigned long berlin2_div_recalc_rate(struct clk_hw
*hw
,
177 unsigned long parent_rate
)
179 struct berlin2_div
*div
= to_berlin2_div(hw
);
180 struct berlin2_div_map
*map
= &div
->map
;
181 u32 divsw
, div3sw
, divider
= 1;
184 spin_lock(div
->lock
);
186 divsw
= readl_relaxed(div
->base
+ map
->div_switch_offs
) &
187 (1 << map
->div_switch_shift
);
188 div3sw
= readl_relaxed(div
->base
+ map
->div3_switch_offs
) &
189 (1 << map
->div3_switch_shift
);
191 /* constant divide-by-3 (dominant) */
194 /* divider can be bypassed with DIV_SWITCH == 0 */
195 } else if (divsw
== 0) {
197 /* clock divider determined by DIV_SELECT */
200 reg
= readl_relaxed(div
->base
+ map
->div_select_offs
);
201 reg
>>= map
->div_select_shift
;
202 reg
&= DIV_SELECT_MASK
;
203 divider
= clk_div
[reg
];
207 spin_unlock(div
->lock
);
209 return parent_rate
/ divider
;
212 static const struct clk_ops berlin2_div_rate_ops
= {
213 .recalc_rate
= berlin2_div_recalc_rate
,
216 static const struct clk_ops berlin2_div_gate_ops
= {
217 .is_enabled
= berlin2_div_is_enabled
,
218 .enable
= berlin2_div_enable
,
219 .disable
= berlin2_div_disable
,
222 static const struct clk_ops berlin2_div_mux_ops
= {
223 .set_parent
= berlin2_div_set_parent
,
224 .get_parent
= berlin2_div_get_parent
,
227 struct clk_hw
* __init
228 berlin2_div_register(const struct berlin2_div_map
*map
,
229 void __iomem
*base
, const char *name
, u8 div_flags
,
230 const char **parent_names
, int num_parents
,
231 unsigned long flags
, spinlock_t
*lock
)
233 const struct clk_ops
*mux_ops
= &berlin2_div_mux_ops
;
234 const struct clk_ops
*rate_ops
= &berlin2_div_rate_ops
;
235 const struct clk_ops
*gate_ops
= &berlin2_div_gate_ops
;
236 struct berlin2_div
*div
;
238 div
= kzalloc(sizeof(*div
), GFP_KERNEL
);
240 return ERR_PTR(-ENOMEM
);
242 /* copy div_map to allow __initconst */
243 memcpy(&div
->map
, map
, sizeof(*map
));
247 if ((div_flags
& BERLIN2_DIV_HAS_GATE
) == 0)
249 if ((div_flags
& BERLIN2_DIV_HAS_MUX
) == 0)
252 return clk_hw_register_composite(NULL
, name
, parent_names
, num_parents
,
253 &div
->hw
, mux_ops
, &div
->hw
, rate_ops
,
254 &div
->hw
, gate_ops
, flags
);