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>
11 #include <linux/of_address.h>
12 #include <linux/slab.h>
13 #include <linux/spinlock.h>
15 #include "berlin2-div.h"
18 * Clock dividers in Berlin2 SoCs comprise a complex cell to select
19 * input pll and divider. The virtual structure as it is used in Marvell
20 * BSP code can be seen as:
23 * pll0 --------------->| 0 | +---+
24 * +---+ |(B)|--+--------------->| 0 | +---+
25 * pll1.0 -->| 0 | +-->| 1 | | +--------+ |(E)|----->| 0 | +---+
26 * pll1.1 -->| 1 | | +---+ +-->|(C) 1:M |-->| 1 | |(F)|-->|(G)|->
27 * ... -->|(A)|--+ | +--------+ +---+ +-->| 1 | +---+
28 * ... -->| | +-->|(D) 1:3 |----------+ +---+
29 * pll1.N -->| N | +---------
32 * (A) input pll clock mux controlled by <PllSelect[1:n]>
33 * (B) input pll bypass mux controlled by <PllSwitch>
34 * (C) programmable clock divider controlled by <Select[1:n]>
35 * (D) constant div-by-3 clock divider
36 * (E) programmable clock divider bypass controlled by <Switch>
37 * (F) constant div-by-3 clock mux controlled by <D3Switch>
38 * (G) clock gate controlled by <Enable>
40 * For whatever reason, above control signals come in two flavors:
41 * - single register dividers with all bits in one register
42 * - shared register dividers with bits spread over multiple registers
43 * (including signals for the same cell spread over consecutive registers)
45 * Also, clock gate and pll mux is not available on every div cell, so
46 * we have to deal with those, too. We reuse common clock composite driver
50 #define PLL_SELECT_MASK 0x7
51 #define DIV_SELECT_MASK 0x7
56 struct berlin2_div_map map
;
60 #define to_berlin2_div(hw) container_of(hw, struct berlin2_div, hw)
62 static u8 clk_div
[] = { 1, 2, 4, 6, 8, 12, 1, 1 };
64 static int berlin2_div_is_enabled(struct clk_hw
*hw
)
66 struct berlin2_div
*div
= to_berlin2_div(hw
);
67 struct berlin2_div_map
*map
= &div
->map
;
73 reg
= readl_relaxed(div
->base
+ map
->gate_offs
);
74 reg
>>= map
->gate_shift
;
77 spin_unlock(div
->lock
);
82 static int berlin2_div_enable(struct clk_hw
*hw
)
84 struct berlin2_div
*div
= to_berlin2_div(hw
);
85 struct berlin2_div_map
*map
= &div
->map
;
91 reg
= readl_relaxed(div
->base
+ map
->gate_offs
);
92 reg
|= BIT(map
->gate_shift
);
93 writel_relaxed(reg
, div
->base
+ map
->gate_offs
);
96 spin_unlock(div
->lock
);
101 static void berlin2_div_disable(struct clk_hw
*hw
)
103 struct berlin2_div
*div
= to_berlin2_div(hw
);
104 struct berlin2_div_map
*map
= &div
->map
;
108 spin_lock(div
->lock
);
110 reg
= readl_relaxed(div
->base
+ map
->gate_offs
);
111 reg
&= ~BIT(map
->gate_shift
);
112 writel_relaxed(reg
, div
->base
+ map
->gate_offs
);
115 spin_unlock(div
->lock
);
118 static int berlin2_div_set_parent(struct clk_hw
*hw
, u8 index
)
120 struct berlin2_div
*div
= to_berlin2_div(hw
);
121 struct berlin2_div_map
*map
= &div
->map
;
125 spin_lock(div
->lock
);
127 /* index == 0 is PLL_SWITCH */
128 reg
= readl_relaxed(div
->base
+ map
->pll_switch_offs
);
130 reg
&= ~BIT(map
->pll_switch_shift
);
132 reg
|= BIT(map
->pll_switch_shift
);
133 writel_relaxed(reg
, div
->base
+ map
->pll_switch_offs
);
135 /* index > 0 is PLL_SELECT */
137 reg
= readl_relaxed(div
->base
+ map
->pll_select_offs
);
138 reg
&= ~(PLL_SELECT_MASK
<< map
->pll_select_shift
);
139 reg
|= (index
- 1) << map
->pll_select_shift
;
140 writel_relaxed(reg
, div
->base
+ map
->pll_select_offs
);
144 spin_unlock(div
->lock
);
149 static u8
berlin2_div_get_parent(struct clk_hw
*hw
)
151 struct berlin2_div
*div
= to_berlin2_div(hw
);
152 struct berlin2_div_map
*map
= &div
->map
;
157 spin_lock(div
->lock
);
159 /* PLL_SWITCH == 0 is index 0 */
160 reg
= readl_relaxed(div
->base
+ map
->pll_switch_offs
);
161 reg
&= BIT(map
->pll_switch_shift
);
163 reg
= readl_relaxed(div
->base
+ map
->pll_select_offs
);
164 reg
>>= map
->pll_select_shift
;
165 reg
&= PLL_SELECT_MASK
;
170 spin_unlock(div
->lock
);
175 static unsigned long berlin2_div_recalc_rate(struct clk_hw
*hw
,
176 unsigned long parent_rate
)
178 struct berlin2_div
*div
= to_berlin2_div(hw
);
179 struct berlin2_div_map
*map
= &div
->map
;
180 u32 divsw
, div3sw
, divider
= 1;
183 spin_lock(div
->lock
);
185 divsw
= readl_relaxed(div
->base
+ map
->div_switch_offs
) &
186 (1 << map
->div_switch_shift
);
187 div3sw
= readl_relaxed(div
->base
+ map
->div3_switch_offs
) &
188 (1 << map
->div3_switch_shift
);
190 /* constant divide-by-3 (dominant) */
193 /* divider can be bypassed with DIV_SWITCH == 0 */
194 } else if (divsw
== 0) {
196 /* clock divider determined by DIV_SELECT */
199 reg
= readl_relaxed(div
->base
+ map
->div_select_offs
);
200 reg
>>= map
->div_select_shift
;
201 reg
&= DIV_SELECT_MASK
;
202 divider
= clk_div
[reg
];
206 spin_unlock(div
->lock
);
208 return parent_rate
/ divider
;
211 static const struct clk_ops berlin2_div_rate_ops
= {
212 .recalc_rate
= berlin2_div_recalc_rate
,
215 static const struct clk_ops berlin2_div_gate_ops
= {
216 .is_enabled
= berlin2_div_is_enabled
,
217 .enable
= berlin2_div_enable
,
218 .disable
= berlin2_div_disable
,
221 static const struct clk_ops berlin2_div_mux_ops
= {
222 .set_parent
= berlin2_div_set_parent
,
223 .get_parent
= berlin2_div_get_parent
,
226 struct clk_hw
* __init
227 berlin2_div_register(const struct berlin2_div_map
*map
,
228 void __iomem
*base
, const char *name
, u8 div_flags
,
229 const char **parent_names
, int num_parents
,
230 unsigned long flags
, spinlock_t
*lock
)
232 const struct clk_ops
*mux_ops
= &berlin2_div_mux_ops
;
233 const struct clk_ops
*rate_ops
= &berlin2_div_rate_ops
;
234 const struct clk_ops
*gate_ops
= &berlin2_div_gate_ops
;
235 struct berlin2_div
*div
;
237 div
= kzalloc(sizeof(*div
), GFP_KERNEL
);
239 return ERR_PTR(-ENOMEM
);
241 /* copy div_map to allow __initconst */
242 memcpy(&div
->map
, map
, sizeof(*map
));
246 if ((div_flags
& BERLIN2_DIV_HAS_GATE
) == 0)
248 if ((div_flags
& BERLIN2_DIV_HAS_MUX
) == 0)
251 return clk_hw_register_composite(NULL
, name
, parent_names
, num_parents
,
252 &div
->hw
, mux_ops
, &div
->hw
, rate_ops
,
253 &div
->hw
, gate_ops
, flags
);