diff options
Diffstat (limited to 'drivers/clk/rockchip/clk.c')
-rw-r--r-- | drivers/clk/rockchip/clk.c | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c new file mode 100644 index 000000000000..278cf9dd1e23 --- /dev/null +++ b/drivers/clk/rockchip/clk.c | |||
@@ -0,0 +1,244 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2014 MundoReader S.L. | ||
3 | * Author: Heiko Stuebner <heiko@sntech.de> | ||
4 | * | ||
5 | * based on | ||
6 | * | ||
7 | * samsung/clk.c | ||
8 | * Copyright (c) 2013 Samsung Electronics Co., Ltd. | ||
9 | * Copyright (c) 2013 Linaro Ltd. | ||
10 | * Author: Thomas Abraham <thomas.ab@samsung.com> | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License as published by | ||
14 | * the Free Software Foundation; either version 2 of the License, or | ||
15 | * (at your option) any later version. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU General Public License for more details. | ||
21 | */ | ||
22 | |||
23 | #include <linux/slab.h> | ||
24 | #include <linux/clk.h> | ||
25 | #include <linux/clk-provider.h> | ||
26 | #include <linux/mfd/syscon.h> | ||
27 | #include <linux/regmap.h> | ||
28 | #include "clk.h" | ||
29 | |||
30 | /** | ||
31 | * Register a clock branch. | ||
32 | * Most clock branches have a form like | ||
33 | * | ||
34 | * src1 --|--\ | ||
35 | * |M |--[GATE]-[DIV]- | ||
36 | * src2 --|--/ | ||
37 | * | ||
38 | * sometimes without one of those components. | ||
39 | */ | ||
40 | struct clk *rockchip_clk_register_branch(const char *name, | ||
41 | const char **parent_names, u8 num_parents, void __iomem *base, | ||
42 | int muxdiv_offset, u8 mux_shift, u8 mux_width, u8 mux_flags, | ||
43 | u8 div_shift, u8 div_width, u8 div_flags, | ||
44 | struct clk_div_table *div_table, int gate_offset, | ||
45 | u8 gate_shift, u8 gate_flags, unsigned long flags, | ||
46 | spinlock_t *lock) | ||
47 | { | ||
48 | struct clk *clk; | ||
49 | struct clk_mux *mux = NULL; | ||
50 | struct clk_gate *gate = NULL; | ||
51 | struct clk_divider *div = NULL; | ||
52 | const struct clk_ops *mux_ops = NULL, *div_ops = NULL, | ||
53 | *gate_ops = NULL; | ||
54 | |||
55 | if (num_parents > 1) { | ||
56 | mux = kzalloc(sizeof(*mux), GFP_KERNEL); | ||
57 | if (!mux) | ||
58 | return ERR_PTR(-ENOMEM); | ||
59 | |||
60 | mux->reg = base + muxdiv_offset; | ||
61 | mux->shift = mux_shift; | ||
62 | mux->mask = BIT(mux_width) - 1; | ||
63 | mux->flags = mux_flags; | ||
64 | mux->lock = lock; | ||
65 | mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops | ||
66 | : &clk_mux_ops; | ||
67 | } | ||
68 | |||
69 | if (gate_offset >= 0) { | ||
70 | gate = kzalloc(sizeof(*gate), GFP_KERNEL); | ||
71 | if (!gate) | ||
72 | return ERR_PTR(-ENOMEM); | ||
73 | |||
74 | gate->flags = gate_flags; | ||
75 | gate->reg = base + gate_offset; | ||
76 | gate->bit_idx = gate_shift; | ||
77 | gate->lock = lock; | ||
78 | gate_ops = &clk_gate_ops; | ||
79 | } | ||
80 | |||
81 | if (div_width > 0) { | ||
82 | div = kzalloc(sizeof(*div), GFP_KERNEL); | ||
83 | if (!div) | ||
84 | return ERR_PTR(-ENOMEM); | ||
85 | |||
86 | div->flags = div_flags; | ||
87 | div->reg = base + muxdiv_offset; | ||
88 | div->shift = div_shift; | ||
89 | div->width = div_width; | ||
90 | div->lock = lock; | ||
91 | div->table = div_table; | ||
92 | div_ops = (div_flags & CLK_DIVIDER_READ_ONLY) | ||
93 | ? &clk_divider_ro_ops | ||
94 | : &clk_divider_ops; | ||
95 | } | ||
96 | |||
97 | clk = clk_register_composite(NULL, name, parent_names, num_parents, | ||
98 | mux ? &mux->hw : NULL, mux_ops, | ||
99 | div ? &div->hw : NULL, div_ops, | ||
100 | gate ? &gate->hw : NULL, gate_ops, | ||
101 | flags); | ||
102 | |||
103 | return clk; | ||
104 | } | ||
105 | |||
106 | static DEFINE_SPINLOCK(clk_lock); | ||
107 | static struct clk **clk_table; | ||
108 | static void __iomem *reg_base; | ||
109 | static struct clk_onecell_data clk_data; | ||
110 | static struct device_node *cru_node; | ||
111 | static struct regmap *grf; | ||
112 | |||
113 | void __init rockchip_clk_init(struct device_node *np, void __iomem *base, | ||
114 | unsigned long nr_clks) | ||
115 | { | ||
116 | reg_base = base; | ||
117 | cru_node = np; | ||
118 | grf = ERR_PTR(-EPROBE_DEFER); | ||
119 | |||
120 | clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL); | ||
121 | if (!clk_table) | ||
122 | pr_err("%s: could not allocate clock lookup table\n", __func__); | ||
123 | |||
124 | clk_data.clks = clk_table; | ||
125 | clk_data.clk_num = nr_clks; | ||
126 | of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); | ||
127 | } | ||
128 | |||
129 | struct regmap *rockchip_clk_get_grf(void) | ||
130 | { | ||
131 | if (IS_ERR(grf)) | ||
132 | grf = syscon_regmap_lookup_by_phandle(cru_node, "rockchip,grf"); | ||
133 | return grf; | ||
134 | } | ||
135 | |||
136 | void rockchip_clk_add_lookup(struct clk *clk, unsigned int id) | ||
137 | { | ||
138 | if (clk_table && id) | ||
139 | clk_table[id] = clk; | ||
140 | } | ||
141 | |||
142 | void __init rockchip_clk_register_plls(struct rockchip_pll_clock *list, | ||
143 | unsigned int nr_pll, int grf_lock_offset) | ||
144 | { | ||
145 | struct clk *clk; | ||
146 | int idx; | ||
147 | |||
148 | for (idx = 0; idx < nr_pll; idx++, list++) { | ||
149 | clk = rockchip_clk_register_pll(list->type, list->name, | ||
150 | list->parent_names, list->num_parents, | ||
151 | reg_base, list->con_offset, grf_lock_offset, | ||
152 | list->lock_shift, list->mode_offset, | ||
153 | list->mode_shift, list->rate_table, &clk_lock); | ||
154 | if (IS_ERR(clk)) { | ||
155 | pr_err("%s: failed to register clock %s\n", __func__, | ||
156 | list->name); | ||
157 | continue; | ||
158 | } | ||
159 | |||
160 | rockchip_clk_add_lookup(clk, list->id); | ||
161 | } | ||
162 | } | ||
163 | |||
164 | void __init rockchip_clk_register_branches( | ||
165 | struct rockchip_clk_branch *list, | ||
166 | unsigned int nr_clk) | ||
167 | { | ||
168 | struct clk *clk = NULL; | ||
169 | unsigned int idx; | ||
170 | unsigned long flags; | ||
171 | |||
172 | for (idx = 0; idx < nr_clk; idx++, list++) { | ||
173 | flags = list->flags; | ||
174 | |||
175 | /* catch simple muxes */ | ||
176 | switch (list->branch_type) { | ||
177 | case branch_mux: | ||
178 | clk = clk_register_mux(NULL, list->name, | ||
179 | list->parent_names, list->num_parents, | ||
180 | flags, reg_base + list->muxdiv_offset, | ||
181 | list->mux_shift, list->mux_width, | ||
182 | list->mux_flags, &clk_lock); | ||
183 | break; | ||
184 | case branch_divider: | ||
185 | if (list->div_table) | ||
186 | clk = clk_register_divider_table(NULL, | ||
187 | list->name, list->parent_names[0], | ||
188 | flags, reg_base + list->muxdiv_offset, | ||
189 | list->div_shift, list->div_width, | ||
190 | list->div_flags, list->div_table, | ||
191 | &clk_lock); | ||
192 | else | ||
193 | clk = clk_register_divider(NULL, list->name, | ||
194 | list->parent_names[0], flags, | ||
195 | reg_base + list->muxdiv_offset, | ||
196 | list->div_shift, list->div_width, | ||
197 | list->div_flags, &clk_lock); | ||
198 | break; | ||
199 | case branch_fraction_divider: | ||
200 | /* unimplemented */ | ||
201 | continue; | ||
202 | break; | ||
203 | case branch_gate: | ||
204 | flags |= CLK_SET_RATE_PARENT; | ||
205 | |||
206 | /* keep all gates untouched for now */ | ||
207 | flags |= CLK_IGNORE_UNUSED; | ||
208 | |||
209 | clk = clk_register_gate(NULL, list->name, | ||
210 | list->parent_names[0], flags, | ||
211 | reg_base + list->gate_offset, | ||
212 | list->gate_shift, list->gate_flags, &clk_lock); | ||
213 | break; | ||
214 | case branch_composite: | ||
215 | /* keep all gates untouched for now */ | ||
216 | flags |= CLK_IGNORE_UNUSED; | ||
217 | |||
218 | clk = rockchip_clk_register_branch(list->name, | ||
219 | list->parent_names, list->num_parents, | ||
220 | reg_base, list->muxdiv_offset, list->mux_shift, | ||
221 | list->mux_width, list->mux_flags, | ||
222 | list->div_shift, list->div_width, | ||
223 | list->div_flags, list->div_table, | ||
224 | list->gate_offset, list->gate_shift, | ||
225 | list->gate_flags, flags, &clk_lock); | ||
226 | break; | ||
227 | } | ||
228 | |||
229 | /* none of the cases above matched */ | ||
230 | if (!clk) { | ||
231 | pr_err("%s: unknown clock type %d\n", | ||
232 | __func__, list->branch_type); | ||
233 | continue; | ||
234 | } | ||
235 | |||
236 | if (IS_ERR(clk)) { | ||
237 | pr_err("%s: failed to register clock %s: %ld\n", | ||
238 | __func__, list->name, PTR_ERR(clk)); | ||
239 | continue; | ||
240 | } | ||
241 | |||
242 | rockchip_clk_add_lookup(clk, list->id); | ||
243 | } | ||
244 | } | ||