aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk/berlin
diff options
context:
space:
mode:
authorAlexandre Belloni <alexandre.belloni@free-electrons.com>2014-05-19 12:43:26 -0400
committerMike Turquette <mturquette@linaro.org>2014-05-29 12:30:18 -0400
commit6f9ba9b44724888c5419289e790077ab1621c62d (patch)
tree8225edf096b8f92f5e392656a3e73cc6f0eed6d2 /drivers/clk/berlin
parentcf8de5a7f870c7e3940d888e2c6ce6988aeb26c6 (diff)
clk: berlin: add driver for BG2x complex divider cells
This is a driver for the complex divider cells found on Marvell Berlin2 SoCs. The cells come in two flavors: single register cells and shared register cells. Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com> Signed-off-by: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> Signed-off-by: Mike Turquette <mturquette@linaro.org>
Diffstat (limited to 'drivers/clk/berlin')
-rw-r--r--drivers/clk/berlin/Makefile2
-rw-r--r--drivers/clk/berlin/berlin2-div.c265
-rw-r--r--drivers/clk/berlin/berlin2-div.h89
3 files changed, 355 insertions, 1 deletions
diff --git a/drivers/clk/berlin/Makefile b/drivers/clk/berlin/Makefile
index 9d3490e9782f..f0a7dc8b5e30 100644
--- a/drivers/clk/berlin/Makefile
+++ b/drivers/clk/berlin/Makefile
@@ -1 +1 @@
obj-y += berlin2-avpll.o berlin2-pll.o obj-y += berlin2-avpll.o berlin2-pll.o berlin2-div.o
diff --git a/drivers/clk/berlin/berlin2-div.c b/drivers/clk/berlin/berlin2-div.c
new file mode 100644
index 000000000000..81ff97f8aa0b
--- /dev/null
+++ b/drivers/clk/berlin/berlin2-div.c
@@ -0,0 +1,265 @@
1/*
2 * Copyright (c) 2014 Marvell Technology Group Ltd.
3 *
4 * Alexandre Belloni <alexandre.belloni@free-electrons.com>
5 * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms and conditions of the GNU General Public License,
9 * version 2, as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19#include <linux/bitops.h>
20#include <linux/clk-provider.h>
21#include <linux/of.h>
22#include <linux/of_address.h>
23#include <linux/slab.h>
24#include <linux/spinlock.h>
25
26#include "berlin2-div.h"
27
28/*
29 * Clock dividers in Berlin2 SoCs comprise a complex cell to select
30 * input pll and divider. The virtual structure as it is used in Marvell
31 * BSP code can be seen as:
32 *
33 * +---+
34 * pll0 --------------->| 0 | +---+
35 * +---+ |(B)|--+--------------->| 0 | +---+
36 * pll1.0 -->| 0 | +-->| 1 | | +--------+ |(E)|----->| 0 | +---+
37 * pll1.1 -->| 1 | | +---+ +-->|(C) 1:M |-->| 1 | |(F)|-->|(G)|->
38 * ... -->|(A)|--+ | +--------+ +---+ +-->| 1 | +---+
39 * ... -->| | +-->|(D) 1:3 |----------+ +---+
40 * pll1.N -->| N | +---------
41 * +---+
42 *
43 * (A) input pll clock mux controlled by <PllSelect[1:n]>
44 * (B) input pll bypass mux controlled by <PllSwitch>
45 * (C) programmable clock divider controlled by <Select[1:n]>
46 * (D) constant div-by-3 clock divider
47 * (E) programmable clock divider bypass controlled by <Switch>
48 * (F) constant div-by-3 clock mux controlled by <D3Switch>
49 * (G) clock gate controlled by <Enable>
50 *
51 * For whatever reason, above control signals come in two flavors:
52 * - single register dividers with all bits in one register
53 * - shared register dividers with bits spread over multiple registers
54 * (including signals for the same cell spread over consecutive registers)
55 *
56 * Also, clock gate and pll mux is not available on every div cell, so
57 * we have to deal with those, too. We reuse common clock composite driver
58 * for it.
59 */
60
61#define PLL_SELECT_MASK 0x7
62#define DIV_SELECT_MASK 0x7
63
64struct berlin2_div {
65 struct clk_hw hw;
66 void __iomem *base;
67 struct berlin2_div_map map;
68 spinlock_t *lock;
69};
70
71#define to_berlin2_div(hw) container_of(hw, struct berlin2_div, hw)
72
73static u8 clk_div[] = { 1, 2, 4, 6, 8, 12, 1, 1 };
74
75static int berlin2_div_is_enabled(struct clk_hw *hw)
76{
77 struct berlin2_div *div = to_berlin2_div(hw);
78 struct berlin2_div_map *map = &div->map;
79 u32 reg;
80
81 if (div->lock)
82 spin_lock(div->lock);
83
84 reg = readl_relaxed(div->base + map->gate_offs);
85 reg >>= map->gate_shift;
86
87 if (div->lock)
88 spin_unlock(div->lock);
89
90 return (reg & 0x1);
91}
92
93static int berlin2_div_enable(struct clk_hw *hw)
94{
95 struct berlin2_div *div = to_berlin2_div(hw);
96 struct berlin2_div_map *map = &div->map;
97 u32 reg;
98
99 if (div->lock)
100 spin_lock(div->lock);
101
102 reg = readl_relaxed(div->base + map->gate_offs);
103 reg |= BIT(map->gate_shift);
104 writel_relaxed(reg, div->base + map->gate_offs);
105
106 if (div->lock)
107 spin_unlock(div->lock);
108
109 return 0;
110}
111
112static void berlin2_div_disable(struct clk_hw *hw)
113{
114 struct berlin2_div *div = to_berlin2_div(hw);
115 struct berlin2_div_map *map = &div->map;
116 u32 reg;
117
118 if (div->lock)
119 spin_lock(div->lock);
120
121 reg = readl_relaxed(div->base + map->gate_offs);
122 reg &= ~BIT(map->gate_shift);
123 writel_relaxed(reg, div->base + map->gate_offs);
124
125 if (div->lock)
126 spin_unlock(div->lock);
127}
128
129static int berlin2_div_set_parent(struct clk_hw *hw, u8 index)
130{
131 struct berlin2_div *div = to_berlin2_div(hw);
132 struct berlin2_div_map *map = &div->map;
133 u32 reg;
134
135 if (div->lock)
136 spin_lock(div->lock);
137
138 /* index == 0 is PLL_SWITCH */
139 reg = readl_relaxed(div->base + map->pll_switch_offs);
140 if (index == 0)
141 reg &= ~BIT(map->pll_switch_shift);
142 else
143 reg |= BIT(map->pll_switch_shift);
144 writel_relaxed(reg, div->base + map->pll_switch_offs);
145
146 /* index > 0 is PLL_SELECT */
147 if (index > 0) {
148 reg = readl_relaxed(div->base + map->pll_select_offs);
149 reg &= ~(PLL_SELECT_MASK << map->pll_select_shift);
150 reg |= (index - 1) << map->pll_select_shift;
151 writel_relaxed(reg, div->base + map->pll_select_offs);
152 }
153
154 if (div->lock)
155 spin_unlock(div->lock);
156
157 return 0;
158}
159
160static u8 berlin2_div_get_parent(struct clk_hw *hw)
161{
162 struct berlin2_div *div = to_berlin2_div(hw);
163 struct berlin2_div_map *map = &div->map;
164 u32 reg;
165 u8 index = 0;
166
167 if (div->lock)
168 spin_lock(div->lock);
169
170 /* PLL_SWITCH == 0 is index 0 */
171 reg = readl_relaxed(div->base + map->pll_switch_offs);
172 reg &= BIT(map->pll_switch_shift);
173 if (reg) {
174 reg = readl_relaxed(div->base + map->pll_select_offs);
175 reg >>= map->pll_select_shift;
176 reg &= PLL_SELECT_MASK;
177 index = 1 + reg;
178 }
179
180 if (div->lock)
181 spin_unlock(div->lock);
182
183 return index;
184}
185
186static unsigned long berlin2_div_recalc_rate(struct clk_hw *hw,
187 unsigned long parent_rate)
188{
189 struct berlin2_div *div = to_berlin2_div(hw);
190 struct berlin2_div_map *map = &div->map;
191 u32 divsw, div3sw, divider = 1;
192
193 if (div->lock)
194 spin_lock(div->lock);
195
196 divsw = readl_relaxed(div->base + map->div_switch_offs) &
197 (1 << map->div_switch_shift);
198 div3sw = readl_relaxed(div->base + map->div3_switch_offs) &
199 (1 << map->div3_switch_shift);
200
201 /* constant divide-by-3 (dominant) */
202 if (div3sw != 0) {
203 divider = 3;
204 /* divider can be bypassed with DIV_SWITCH == 0 */
205 } else if (divsw == 0) {
206 divider = 1;
207 /* clock divider determined by DIV_SELECT */
208 } else {
209 u32 reg;
210 reg = readl_relaxed(div->base + map->div_select_offs);
211 reg >>= map->div_select_shift;
212 reg &= DIV_SELECT_MASK;
213 divider = clk_div[reg];
214 }
215
216 if (div->lock)
217 spin_unlock(div->lock);
218
219 return parent_rate / divider;
220}
221
222static const struct clk_ops berlin2_div_rate_ops = {
223 .recalc_rate = berlin2_div_recalc_rate,
224};
225
226static const struct clk_ops berlin2_div_gate_ops = {
227 .is_enabled = berlin2_div_is_enabled,
228 .enable = berlin2_div_enable,
229 .disable = berlin2_div_disable,
230};
231
232static const struct clk_ops berlin2_div_mux_ops = {
233 .set_parent = berlin2_div_set_parent,
234 .get_parent = berlin2_div_get_parent,
235};
236
237struct clk * __init
238berlin2_div_register(const struct berlin2_div_map *map,
239 void __iomem *base, const char *name, u8 div_flags,
240 const char **parent_names, int num_parents,
241 unsigned long flags, spinlock_t *lock)
242{
243 const struct clk_ops *mux_ops = &berlin2_div_mux_ops;
244 const struct clk_ops *rate_ops = &berlin2_div_rate_ops;
245 const struct clk_ops *gate_ops = &berlin2_div_gate_ops;
246 struct berlin2_div *div;
247
248 div = kzalloc(sizeof(*div), GFP_KERNEL);
249 if (!div)
250 return ERR_PTR(-ENOMEM);
251
252 /* copy div_map to allow __initconst */
253 memcpy(&div->map, map, sizeof(*map));
254 div->base = base;
255 div->lock = lock;
256
257 if ((div_flags & BERLIN2_DIV_HAS_GATE) == 0)
258 gate_ops = NULL;
259 if ((div_flags & BERLIN2_DIV_HAS_MUX) == 0)
260 mux_ops = NULL;
261
262 return clk_register_composite(NULL, name, parent_names, num_parents,
263 &div->hw, mux_ops, &div->hw, rate_ops,
264 &div->hw, gate_ops, flags);
265}
diff --git a/drivers/clk/berlin/berlin2-div.h b/drivers/clk/berlin/berlin2-div.h
new file mode 100644
index 000000000000..15e3384f3116
--- /dev/null
+++ b/drivers/clk/berlin/berlin2-div.h
@@ -0,0 +1,89 @@
1/*
2 * Copyright (c) 2014 Marvell Technology Group Ltd.
3 *
4 * Alexandre Belloni <alexandre.belloni@free-electrons.com>
5 * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms and conditions of the GNU General Public License,
9 * version 2, as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19#ifndef __BERLIN2_DIV_H
20#define __BERLIN2_DIV_H
21
22struct clk;
23
24#define BERLIN2_DIV_HAS_GATE BIT(0)
25#define BERLIN2_DIV_HAS_MUX BIT(1)
26
27#define BERLIN2_PLL_SELECT(_off, _sh) \
28 .pll_select_offs = _off, \
29 .pll_select_shift = _sh
30
31#define BERLIN2_PLL_SWITCH(_off, _sh) \
32 .pll_switch_offs = _off, \
33 .pll_switch_shift = _sh
34
35#define BERLIN2_DIV_SELECT(_off, _sh) \
36 .div_select_offs = _off, \
37 .div_select_shift = _sh
38
39#define BERLIN2_DIV_SWITCH(_off, _sh) \
40 .div_switch_offs = _off, \
41 .div_switch_shift = _sh
42
43#define BERLIN2_DIV_D3SWITCH(_off, _sh) \
44 .div3_switch_offs = _off, \
45 .div3_switch_shift = _sh
46
47#define BERLIN2_DIV_GATE(_off, _sh) \
48 .gate_offs = _off, \
49 .gate_shift = _sh
50
51#define BERLIN2_SINGLE_DIV(_off) \
52 BERLIN2_DIV_GATE(_off, 0), \
53 BERLIN2_PLL_SELECT(_off, 1), \
54 BERLIN2_PLL_SWITCH(_off, 4), \
55 BERLIN2_DIV_SWITCH(_off, 5), \
56 BERLIN2_DIV_D3SWITCH(_off, 6), \
57 BERLIN2_DIV_SELECT(_off, 7)
58
59struct berlin2_div_map {
60 u16 pll_select_offs;
61 u16 pll_switch_offs;
62 u16 div_select_offs;
63 u16 div_switch_offs;
64 u16 div3_switch_offs;
65 u16 gate_offs;
66 u8 pll_select_shift;
67 u8 pll_switch_shift;
68 u8 div_select_shift;
69 u8 div_switch_shift;
70 u8 div3_switch_shift;
71 u8 gate_shift;
72};
73
74struct berlin2_div_data {
75 const char *name;
76 const u8 *parent_ids;
77 int num_parents;
78 unsigned long flags;
79 struct berlin2_div_map map;
80 u8 div_flags;
81};
82
83struct clk * __init
84berlin2_div_register(const struct berlin2_div_map *map,
85 void __iomem *base, const char *name, u8 div_flags,
86 const char **parent_names, int num_parents,
87 unsigned long flags, spinlock_t *lock);
88
89#endif /* __BERLIN2_DIV_H */