1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
4 * Author: Jian Hu <jian.hu@amlogic.com>
5 *
6 * Copyright (c) 2023, SberDevices. All Rights Reserved.
7 * Author: Dmitry Rokosov <ddrokosov@sberdevices.ru>
8 */
9
10 #include <linux/clk-provider.h>
11 #include <linux/mod_devicetable.h>
12 #include <linux/platform_device.h>
13 #include "a1-pll.h"
14 #include "clk-regmap.h"
15 #include "meson-clkc-utils.h"
16
17 #include <dt-bindings/clock/amlogic,a1-pll-clkc.h>
18
19 static struct clk_regmap fixed_pll_dco = {
20 .data = &(struct meson_clk_pll_data){
21 .en = {
22 .reg_off = ANACTRL_FIXPLL_CTRL0,
23 .shift = 28,
24 .width = 1,
25 },
26 .m = {
27 .reg_off = ANACTRL_FIXPLL_CTRL0,
28 .shift = 0,
29 .width = 8,
30 },
31 .n = {
32 .reg_off = ANACTRL_FIXPLL_CTRL0,
33 .shift = 10,
34 .width = 5,
35 },
36 .frac = {
37 .reg_off = ANACTRL_FIXPLL_CTRL1,
38 .shift = 0,
39 .width = 19,
40 },
41 .l = {
42 .reg_off = ANACTRL_FIXPLL_STS,
43 .shift = 31,
44 .width = 1,
45 },
46 .rst = {
47 .reg_off = ANACTRL_FIXPLL_CTRL0,
48 .shift = 29,
49 .width = 1,
50 },
51 },
52 .hw.init = &(struct clk_init_data){
53 .name = "fixed_pll_dco",
54 .ops = &meson_clk_pll_ro_ops,
55 .parent_data = &(const struct clk_parent_data) {
56 .fw_name = "fixpll_in",
57 },
58 .num_parents = 1,
59 },
60 };
61
62 static struct clk_regmap fixed_pll = {
63 .data = &(struct clk_regmap_gate_data){
64 .offset = ANACTRL_FIXPLL_CTRL0,
65 .bit_idx = 20,
66 },
67 .hw.init = &(struct clk_init_data) {
68 .name = "fixed_pll",
69 .ops = &clk_regmap_gate_ops,
70 .parent_hws = (const struct clk_hw *[]) {
71 &fixed_pll_dco.hw
72 },
73 .num_parents = 1,
74 },
75 };
76
77 static const struct pll_mult_range hifi_pll_mult_range = {
78 .min = 32,
79 .max = 64,
80 };
81
82 static const struct reg_sequence hifi_init_regs[] = {
83 { .reg = ANACTRL_HIFIPLL_CTRL1, .def = 0x01800000 },
84 { .reg = ANACTRL_HIFIPLL_CTRL2, .def = 0x00001100 },
85 { .reg = ANACTRL_HIFIPLL_CTRL3, .def = 0x100a1100 },
86 { .reg = ANACTRL_HIFIPLL_CTRL4, .def = 0x00302000 },
87 { .reg = ANACTRL_HIFIPLL_CTRL0, .def = 0x01f18000 },
88 };
89
90 static struct clk_regmap hifi_pll = {
91 .data = &(struct meson_clk_pll_data){
92 .en = {
93 .reg_off = ANACTRL_HIFIPLL_CTRL0,
94 .shift = 28,
95 .width = 1,
96 },
97 .m = {
98 .reg_off = ANACTRL_HIFIPLL_CTRL0,
99 .shift = 0,
100 .width = 8,
101 },
102 .n = {
103 .reg_off = ANACTRL_HIFIPLL_CTRL0,
104 .shift = 10,
105 .width = 5,
106 },
107 .frac = {
108 .reg_off = ANACTRL_HIFIPLL_CTRL1,
109 .shift = 0,
110 .width = 19,
111 },
112 .l = {
113 .reg_off = ANACTRL_HIFIPLL_STS,
114 .shift = 31,
115 .width = 1,
116 },
117 .current_en = {
118 .reg_off = ANACTRL_HIFIPLL_CTRL0,
119 .shift = 26,
120 .width = 1,
121 },
122 .l_detect = {
123 .reg_off = ANACTRL_HIFIPLL_CTRL2,
124 .shift = 6,
125 .width = 1,
126 },
127 .range = &hifi_pll_mult_range,
128 .init_regs = hifi_init_regs,
129 .init_count = ARRAY_SIZE(hifi_init_regs),
130 },
131 .hw.init = &(struct clk_init_data){
132 .name = "hifi_pll",
133 .ops = &meson_clk_pll_ops,
134 .parent_data = &(const struct clk_parent_data) {
135 .fw_name = "hifipll_in",
136 },
137 .num_parents = 1,
138 },
139 };
140
141 static struct clk_fixed_factor fclk_div2_div = {
142 .mult = 1,
143 .div = 2,
144 .hw.init = &(struct clk_init_data){
145 .name = "fclk_div2_div",
146 .ops = &clk_fixed_factor_ops,
147 .parent_hws = (const struct clk_hw *[]) {
148 &fixed_pll.hw
149 },
150 .num_parents = 1,
151 },
152 };
153
154 static struct clk_regmap fclk_div2 = {
155 .data = &(struct clk_regmap_gate_data){
156 .offset = ANACTRL_FIXPLL_CTRL0,
157 .bit_idx = 21,
158 },
159 .hw.init = &(struct clk_init_data){
160 .name = "fclk_div2",
161 .ops = &clk_regmap_gate_ops,
162 .parent_hws = (const struct clk_hw *[]) {
163 &fclk_div2_div.hw
164 },
165 .num_parents = 1,
166 /*
167 * This clock is used by DDR clock in BL2 firmware
168 * and is required by the platform to operate correctly.
169 * Until the following condition are met, we need this clock to
170 * be marked as critical:
171 * a) Mark the clock used by a firmware resource, if possible
172 * b) CCF has a clock hand-off mechanism to make the sure the
173 * clock stays on until the proper driver comes along
174 */
175 .flags = CLK_IS_CRITICAL,
176 },
177 };
178
179 static struct clk_fixed_factor fclk_div3_div = {
180 .mult = 1,
181 .div = 3,
182 .hw.init = &(struct clk_init_data){
183 .name = "fclk_div3_div",
184 .ops = &clk_fixed_factor_ops,
185 .parent_hws = (const struct clk_hw *[]) {
186 &fixed_pll.hw
187 },
188 .num_parents = 1,
189 },
190 };
191
192 static struct clk_regmap fclk_div3 = {
193 .data = &(struct clk_regmap_gate_data){
194 .offset = ANACTRL_FIXPLL_CTRL0,
195 .bit_idx = 22,
196 },
197 .hw.init = &(struct clk_init_data){
198 .name = "fclk_div3",
199 .ops = &clk_regmap_gate_ops,
200 .parent_hws = (const struct clk_hw *[]) {
201 &fclk_div3_div.hw
202 },
203 .num_parents = 1,
204 /*
205 * This clock is used by APB bus which is set in boot ROM code
206 * and is required by the platform to operate correctly.
207 */
208 .flags = CLK_IS_CRITICAL,
209 },
210 };
211
212 static struct clk_fixed_factor fclk_div5_div = {
213 .mult = 1,
214 .div = 5,
215 .hw.init = &(struct clk_init_data){
216 .name = "fclk_div5_div",
217 .ops = &clk_fixed_factor_ops,
218 .parent_hws = (const struct clk_hw *[]) {
219 &fixed_pll.hw
220 },
221 .num_parents = 1,
222 },
223 };
224
225 static struct clk_regmap fclk_div5 = {
226 .data = &(struct clk_regmap_gate_data){
227 .offset = ANACTRL_FIXPLL_CTRL0,
228 .bit_idx = 23,
229 },
230 .hw.init = &(struct clk_init_data){
231 .name = "fclk_div5",
232 .ops = &clk_regmap_gate_ops,
233 .parent_hws = (const struct clk_hw *[]) {
234 &fclk_div5_div.hw
235 },
236 .num_parents = 1,
237 /*
238 * This clock is used by AXI bus which setted in Romcode
239 * and is required by the platform to operate correctly.
240 */
241 .flags = CLK_IS_CRITICAL,
242 },
243 };
244
245 static struct clk_fixed_factor fclk_div7_div = {
246 .mult = 1,
247 .div = 7,
248 .hw.init = &(struct clk_init_data){
249 .name = "fclk_div7_div",
250 .ops = &clk_fixed_factor_ops,
251 .parent_hws = (const struct clk_hw *[]) {
252 &fixed_pll.hw
253 },
254 .num_parents = 1,
255 },
256 };
257
258 static struct clk_regmap fclk_div7 = {
259 .data = &(struct clk_regmap_gate_data){
260 .offset = ANACTRL_FIXPLL_CTRL0,
261 .bit_idx = 24,
262 },
263 .hw.init = &(struct clk_init_data){
264 .name = "fclk_div7",
265 .ops = &clk_regmap_gate_ops,
266 .parent_hws = (const struct clk_hw *[]) {
267 &fclk_div7_div.hw
268 },
269 .num_parents = 1,
270 },
271 };
272
273 /* Array of all clocks registered by this provider */
274 static struct clk_hw *a1_pll_hw_clks[] = {
275 [CLKID_FIXED_PLL_DCO] = &fixed_pll_dco.hw,
276 [CLKID_FIXED_PLL] = &fixed_pll.hw,
277 [CLKID_FCLK_DIV2_DIV] = &fclk_div2_div.hw,
278 [CLKID_FCLK_DIV3_DIV] = &fclk_div3_div.hw,
279 [CLKID_FCLK_DIV5_DIV] = &fclk_div5_div.hw,
280 [CLKID_FCLK_DIV7_DIV] = &fclk_div7_div.hw,
281 [CLKID_FCLK_DIV2] = &fclk_div2.hw,
282 [CLKID_FCLK_DIV3] = &fclk_div3.hw,
283 [CLKID_FCLK_DIV5] = &fclk_div5.hw,
284 [CLKID_FCLK_DIV7] = &fclk_div7.hw,
285 [CLKID_HIFI_PLL] = &hifi_pll.hw,
286 };
287
288 static struct clk_regmap *const a1_pll_regmaps[] = {
289 &fixed_pll_dco,
290 &fixed_pll,
291 &fclk_div2,
292 &fclk_div3,
293 &fclk_div5,
294 &fclk_div7,
295 &hifi_pll,
296 };
297
298 static struct regmap_config a1_pll_regmap_cfg = {
299 .reg_bits = 32,
300 .val_bits = 32,
301 .reg_stride = 4,
302 };
303
304 static struct meson_clk_hw_data a1_pll_clks = {
305 .hws = a1_pll_hw_clks,
306 .num = ARRAY_SIZE(a1_pll_hw_clks),
307 };
308
meson_a1_pll_probe(struct platform_device * pdev)309 static int meson_a1_pll_probe(struct platform_device *pdev)
310 {
311 struct device *dev = &pdev->dev;
312 void __iomem *base;
313 struct regmap *map;
314 int clkid, i, err;
315
316 base = devm_platform_ioremap_resource(pdev, 0);
317 if (IS_ERR(base))
318 return dev_err_probe(dev, PTR_ERR(base),
319 "can't ioremap resource\n");
320
321 map = devm_regmap_init_mmio(dev, base, &a1_pll_regmap_cfg);
322 if (IS_ERR(map))
323 return dev_err_probe(dev, PTR_ERR(map),
324 "can't init regmap mmio region\n");
325
326 /* Populate regmap for the regmap backed clocks */
327 for (i = 0; i < ARRAY_SIZE(a1_pll_regmaps); i++)
328 a1_pll_regmaps[i]->map = map;
329
330 /* Register clocks */
331 for (clkid = 0; clkid < a1_pll_clks.num; clkid++) {
332 err = devm_clk_hw_register(dev, a1_pll_clks.hws[clkid]);
333 if (err)
334 return dev_err_probe(dev, err,
335 "clock[%d] registration failed\n",
336 clkid);
337 }
338
339 return devm_of_clk_add_hw_provider(dev, meson_clk_hw_get,
340 &a1_pll_clks);
341 }
342
343 static const struct of_device_id a1_pll_clkc_match_table[] = {
344 { .compatible = "amlogic,a1-pll-clkc", },
345 {}
346 };
347 MODULE_DEVICE_TABLE(of, a1_pll_clkc_match_table);
348
349 static struct platform_driver a1_pll_clkc_driver = {
350 .probe = meson_a1_pll_probe,
351 .driver = {
352 .name = "a1-pll-clkc",
353 .of_match_table = a1_pll_clkc_match_table,
354 },
355 };
356
357 module_platform_driver(a1_pll_clkc_driver);
358 MODULE_AUTHOR("Jian Hu <jian.hu@amlogic.com>");
359 MODULE_AUTHOR("Dmitry Rokosov <ddrokosov@sberdevices.ru>");
360 MODULE_LICENSE("GPL");
361