1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Author: Yinbo Zhu <zhuyinbo@loongson.cn>
4 * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
5 */
6
7 #include <linux/err.h>
8 #include <linux/init.h>
9 #include <linux/clk-provider.h>
10 #include <linux/slab.h>
11 #include <linux/module.h>
12 #include <linux/platform_device.h>
13 #include <linux/io-64-nonatomic-lo-hi.h>
14 #include <dt-bindings/clock/loongson,ls2k-clk.h>
15
16 static const struct clk_parent_data pdata[] = {
17 { .fw_name = "ref_100m", },
18 };
19
20 enum loongson2_clk_type {
21 CLK_TYPE_PLL,
22 CLK_TYPE_SCALE,
23 CLK_TYPE_DIVIDER,
24 CLK_TYPE_GATE,
25 CLK_TYPE_FIXED,
26 CLK_TYPE_NONE,
27 };
28
29 struct loongson2_clk_provider {
30 void __iomem *base;
31 struct device *dev;
32 struct clk_hw_onecell_data clk_data;
33 spinlock_t clk_lock; /* protect access to DIV registers */
34 };
35
36 struct loongson2_clk_data {
37 struct clk_hw hw;
38 void __iomem *reg;
39 u8 div_shift;
40 u8 div_width;
41 u8 mult_shift;
42 u8 mult_width;
43 };
44
45 struct loongson2_clk_board_info {
46 u8 id;
47 enum loongson2_clk_type type;
48 const char *name;
49 const char *parent_name;
50 unsigned long fixed_rate;
51 u8 reg_offset;
52 u8 div_shift;
53 u8 div_width;
54 u8 mult_shift;
55 u8 mult_width;
56 u8 bit_idx;
57 };
58
59 #define CLK_DIV(_id, _name, _pname, _offset, _dshift, _dwidth) \
60 { \
61 .id = _id, \
62 .type = CLK_TYPE_DIVIDER, \
63 .name = _name, \
64 .parent_name = _pname, \
65 .reg_offset = _offset, \
66 .div_shift = _dshift, \
67 .div_width = _dwidth, \
68 }
69
70 #define CLK_PLL(_id, _name, _offset, _mshift, _mwidth, \
71 _dshift, _dwidth) \
72 { \
73 .id = _id, \
74 .type = CLK_TYPE_PLL, \
75 .name = _name, \
76 .parent_name = NULL, \
77 .reg_offset = _offset, \
78 .mult_shift = _mshift, \
79 .mult_width = _mwidth, \
80 .div_shift = _dshift, \
81 .div_width = _dwidth, \
82 }
83
84 #define CLK_SCALE(_id, _name, _pname, _offset, \
85 _dshift, _dwidth) \
86 { \
87 .id = _id, \
88 .type = CLK_TYPE_SCALE, \
89 .name = _name, \
90 .parent_name = _pname, \
91 .reg_offset = _offset, \
92 .div_shift = _dshift, \
93 .div_width = _dwidth, \
94 }
95
96 #define CLK_GATE(_id, _name, _pname, _offset, _bidx) \
97 { \
98 .id = _id, \
99 .type = CLK_TYPE_GATE, \
100 .name = _name, \
101 .parent_name = _pname, \
102 .reg_offset = _offset, \
103 .bit_idx = _bidx, \
104 }
105
106 #define CLK_FIXED(_id, _name, _pname, _rate) \
107 { \
108 .id = _id, \
109 .type = CLK_TYPE_FIXED, \
110 .name = _name, \
111 .parent_name = _pname, \
112 .fixed_rate = _rate, \
113 }
114
115 static const struct loongson2_clk_board_info ls2k0500_clks[] = {
116 CLK_PLL(LOONGSON2_NODE_PLL, "pll_node", 0, 16, 8, 8, 6),
117 CLK_PLL(LOONGSON2_DDR_PLL, "pll_ddr", 0x8, 16, 8, 8, 6),
118 CLK_PLL(LOONGSON2_DC_PLL, "pll_soc", 0x10, 16, 8, 8, 6),
119 CLK_PLL(LOONGSON2_PIX0_PLL, "pll_pix0", 0x18, 16, 8, 8, 6),
120 CLK_PLL(LOONGSON2_PIX1_PLL, "pll_pix1", 0x20, 16, 8, 8, 6),
121 CLK_DIV(LOONGSON2_NODE_CLK, "clk_node", "pll_node", 0, 24, 6),
122 CLK_DIV(LOONGSON2_DDR_CLK, "clk_ddr", "pll_ddr", 0x8, 24, 6),
123 CLK_DIV(LOONGSON2_HDA_CLK, "clk_hda", "pll_ddr", 0xc, 8, 6),
124 CLK_DIV(LOONGSON2_GPU_CLK, "clk_gpu", "pll_soc", 0x10, 24, 6),
125 CLK_DIV(LOONGSON2_DC_CLK, "clk_sb", "pll_soc", 0x14, 0, 6),
126 CLK_DIV(LOONGSON2_GMAC_CLK, "clk_gmac", "pll_soc", 0x14, 8, 6),
127 CLK_DIV(LOONGSON2_PIX0_CLK, "clk_pix0", "pll_pix0", 0x18, 24, 6),
128 CLK_DIV(LOONGSON2_PIX1_CLK, "clk_pix1", "pll_pix1", 0x20, 24, 6),
129 CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", "clk_sb", 0x28, 8, 3),
130 CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_sb", 0x28, 12, 3),
131 CLK_SCALE(LOONGSON2_USB_CLK, "clk_usb", "clk_sb", 0x28, 16, 3),
132 CLK_SCALE(LOONGSON2_APB_CLK, "clk_apb", "clk_sb", 0x28, 20, 3),
133 { /* Sentinel */ },
134 };
135
136 static const struct loongson2_clk_board_info ls2k1000_clks[] = {
137 CLK_PLL(LOONGSON2_NODE_PLL, "pll_node", 0, 32, 10, 26, 6),
138 CLK_PLL(LOONGSON2_DDR_PLL, "pll_ddr", 0x10, 32, 10, 26, 6),
139 CLK_PLL(LOONGSON2_DC_PLL, "pll_dc", 0x20, 32, 10, 26, 6),
140 CLK_PLL(LOONGSON2_PIX0_PLL, "pll_pix0", 0x30, 32, 10, 26, 6),
141 CLK_PLL(LOONGSON2_PIX1_PLL, "pll_pix1", 0x40, 32, 10, 26, 6),
142 CLK_DIV(LOONGSON2_NODE_CLK, "clk_node", "pll_node", 0x8, 0, 6),
143 CLK_DIV(LOONGSON2_DDR_CLK, "clk_ddr", "pll_ddr", 0x18, 0, 6),
144 CLK_DIV(LOONGSON2_GPU_CLK, "clk_gpu", "pll_ddr", 0x18, 22, 6),
145 /*
146 * The hda clk divisor in the upper 32bits and the clk-prodiver
147 * layer code doesn't support 64bit io operation thus a conversion
148 * is required that subtract shift by 32 and add 4byte to the hda
149 * address
150 */
151 CLK_DIV(LOONGSON2_HDA_CLK, "clk_hda", "pll_ddr", 0x22, 12, 7),
152 CLK_DIV(LOONGSON2_DC_CLK, "clk_dc", "pll_dc", 0x28, 0, 6),
153 CLK_DIV(LOONGSON2_GMAC_CLK, "clk_gmac", "pll_dc", 0x28, 22, 6),
154 CLK_DIV(LOONGSON2_PIX0_CLK, "clk_pix0", "pll_pix0", 0x38, 0, 6),
155 CLK_DIV(LOONGSON2_PIX1_CLK, "clk_pix1", "pll_pix1", 0x38, 0, 6),
156 CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", NULL, 0x50, 8, 3),
157 CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_gmac", 0x50, 12, 3),
158 CLK_SCALE(LOONGSON2_USB_CLK, "clk_usb", "clk_gmac", 0x50, 16, 3),
159 CLK_SCALE(LOONGSON2_APB_CLK, "clk_apb", "clk_gmac", 0x50, 20, 3),
160 { /* Sentinel */ },
161 };
162
163 static const struct loongson2_clk_board_info ls2k2000_clks[] = {
164 CLK_PLL(LOONGSON2_DC_PLL, "pll_0", 0, 21, 9, 32, 6),
165 CLK_PLL(LOONGSON2_DDR_PLL, "pll_1", 0x10, 21, 9, 32, 6),
166 CLK_PLL(LOONGSON2_NODE_PLL, "pll_2", 0x20, 21, 9, 32, 6),
167 CLK_PLL(LOONGSON2_PIX0_PLL, "pll_pix0", 0x30, 21, 9, 32, 6),
168 CLK_PLL(LOONGSON2_PIX1_PLL, "pll_pix1", 0x40, 21, 9, 32, 6),
169 CLK_GATE(LOONGSON2_OUT0_GATE, "out0_gate", "pll_0", 0, 40),
170 CLK_GATE(LOONGSON2_GMAC_GATE, "gmac_gate", "pll_0", 0, 41),
171 CLK_GATE(LOONGSON2_RIO_GATE, "rio_gate", "pll_0", 0, 42),
172 CLK_GATE(LOONGSON2_DC_GATE, "dc_gate", "pll_1", 0x10, 40),
173 CLK_GATE(LOONGSON2_DDR_GATE, "ddr_gate", "pll_1", 0x10, 41),
174 CLK_GATE(LOONGSON2_GPU_GATE, "gpu_gate", "pll_1", 0x10, 42),
175 CLK_GATE(LOONGSON2_HDA_GATE, "hda_gate", "pll_2", 0x20, 40),
176 CLK_GATE(LOONGSON2_NODE_GATE, "node_gate", "pll_2", 0x20, 41),
177 CLK_GATE(LOONGSON2_EMMC_GATE, "emmc_gate", "pll_2", 0x20, 42),
178 CLK_GATE(LOONGSON2_PIX0_GATE, "pix0_gate", "pll_pix0", 0x30, 40),
179 CLK_GATE(LOONGSON2_PIX1_GATE, "pix1_gate", "pll_pix1", 0x40, 40),
180 CLK_DIV(LOONGSON2_OUT0_CLK, "clk_out0", "out0_gate", 0, 0, 6),
181 CLK_DIV(LOONGSON2_GMAC_CLK, "clk_gmac", "gmac_gate", 0, 7, 6),
182 CLK_DIV(LOONGSON2_RIO_CLK, "clk_rio", "rio_gate", 0, 14, 6),
183 CLK_DIV(LOONGSON2_DC_CLK, "clk_dc", "dc_gate", 0x10, 0, 6),
184 CLK_DIV(LOONGSON2_GPU_CLK, "clk_gpu", "gpu_gate", 0x10, 7, 6),
185 CLK_DIV(LOONGSON2_DDR_CLK, "clk_ddr", "ddr_gate", 0x10, 14, 6),
186 CLK_DIV(LOONGSON2_HDA_CLK, "clk_hda", "hda_gate", 0x20, 0, 6),
187 CLK_DIV(LOONGSON2_NODE_CLK, "clk_node", "node_gate", 0x20, 7, 6),
188 CLK_DIV(LOONGSON2_EMMC_CLK, "clk_emmc", "emmc_gate", 0x20, 14, 6),
189 CLK_DIV(LOONGSON2_PIX0_CLK, "clk_pix0", "pll_pix0", 0x30, 0, 6),
190 CLK_DIV(LOONGSON2_PIX1_CLK, "clk_pix1", "pll_pix1", 0x40, 0, 6),
191 CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_out0", 0x50, 12, 3),
192 CLK_SCALE(LOONGSON2_USB_CLK, "clk_usb", "clk_out0", 0x50, 16, 3),
193 CLK_SCALE(LOONGSON2_APB_CLK, "clk_apb", "clk_node", 0x50, 20, 3),
194 CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", NULL, 0x50, 23, 3),
195 CLK_SCALE(LOONGSON2_DES_CLK, "clk_des", "clk_node", 0x50, 40, 3),
196 CLK_SCALE(LOONGSON2_I2S_CLK, "clk_i2s", "clk_node", 0x50, 44, 3),
197 CLK_FIXED(LOONGSON2_MISC_CLK, "clk_misc", NULL, 50000000),
198 { /* Sentinel */ },
199 };
200
to_loongson2_clk(struct clk_hw * hw)201 static inline struct loongson2_clk_data *to_loongson2_clk(struct clk_hw *hw)
202 {
203 return container_of(hw, struct loongson2_clk_data, hw);
204 }
205
loongson2_rate_part(u64 val,u8 shift,u8 width)206 static inline unsigned long loongson2_rate_part(u64 val, u8 shift, u8 width)
207 {
208 return (val & GENMASK(shift + width - 1, shift)) >> shift;
209 }
210
loongson2_pll_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)211 static unsigned long loongson2_pll_recalc_rate(struct clk_hw *hw,
212 unsigned long parent_rate)
213 {
214 u64 val, mult, div;
215 struct loongson2_clk_data *clk = to_loongson2_clk(hw);
216
217 val = readq(clk->reg);
218 mult = loongson2_rate_part(val, clk->mult_shift, clk->mult_width);
219 div = loongson2_rate_part(val, clk->div_shift, clk->div_width);
220
221 return div_u64((u64)parent_rate * mult, div);
222 }
223
224 static const struct clk_ops loongson2_pll_recalc_ops = {
225 .recalc_rate = loongson2_pll_recalc_rate,
226 };
227
loongson2_freqscale_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)228 static unsigned long loongson2_freqscale_recalc_rate(struct clk_hw *hw,
229 unsigned long parent_rate)
230 {
231 u64 val, mult;
232 struct loongson2_clk_data *clk = to_loongson2_clk(hw);
233
234 val = readq(clk->reg);
235 mult = loongson2_rate_part(val, clk->div_shift, clk->div_width) + 1;
236
237 return div_u64((u64)parent_rate * mult, 8);
238 }
239
240 static const struct clk_ops loongson2_freqscale_recalc_ops = {
241 .recalc_rate = loongson2_freqscale_recalc_rate,
242 };
243
loongson2_clk_register(struct loongson2_clk_provider * clp,const struct loongson2_clk_board_info * cld,const struct clk_ops * ops)244 static struct clk_hw *loongson2_clk_register(struct loongson2_clk_provider *clp,
245 const struct loongson2_clk_board_info *cld,
246 const struct clk_ops *ops)
247 {
248 int ret;
249 struct clk_hw *hw;
250 struct loongson2_clk_data *clk;
251 struct clk_init_data init = { };
252
253 clk = devm_kzalloc(clp->dev, sizeof(*clk), GFP_KERNEL);
254 if (!clk)
255 return ERR_PTR(-ENOMEM);
256
257 init.name = cld->name;
258 init.ops = ops;
259 init.flags = 0;
260 init.num_parents = 1;
261
262 if (!cld->parent_name)
263 init.parent_data = pdata;
264 else
265 init.parent_names = &cld->parent_name;
266
267 clk->reg = clp->base + cld->reg_offset;
268 clk->div_shift = cld->div_shift;
269 clk->div_width = cld->div_width;
270 clk->mult_shift = cld->mult_shift;
271 clk->mult_width = cld->mult_width;
272 clk->hw.init = &init;
273
274 hw = &clk->hw;
275 ret = devm_clk_hw_register(clp->dev, hw);
276 if (ret)
277 clk = ERR_PTR(ret);
278
279 return hw;
280 }
281
loongson2_clk_probe(struct platform_device * pdev)282 static int loongson2_clk_probe(struct platform_device *pdev)
283 {
284 int i, clks_num = 0;
285 struct clk_hw *hw;
286 struct device *dev = &pdev->dev;
287 struct loongson2_clk_provider *clp;
288 const struct loongson2_clk_board_info *p, *data;
289
290 data = device_get_match_data(dev);
291 if (!data)
292 return -EINVAL;
293
294 for (p = data; p->name; p++)
295 clks_num++;
296
297 clp = devm_kzalloc(dev, struct_size(clp, clk_data.hws, clks_num),
298 GFP_KERNEL);
299 if (!clp)
300 return -ENOMEM;
301
302 clp->base = devm_platform_ioremap_resource(pdev, 0);
303 if (IS_ERR(clp->base))
304 return PTR_ERR(clp->base);
305
306 spin_lock_init(&clp->clk_lock);
307 clp->clk_data.num = clks_num + 1;
308 clp->dev = dev;
309
310 for (i = 0; i < clks_num; i++) {
311 p = &data[i];
312 switch (p->type) {
313 case CLK_TYPE_PLL:
314 hw = loongson2_clk_register(clp, p,
315 &loongson2_pll_recalc_ops);
316 break;
317 case CLK_TYPE_SCALE:
318 hw = loongson2_clk_register(clp, p,
319 &loongson2_freqscale_recalc_ops);
320 break;
321 case CLK_TYPE_DIVIDER:
322 hw = devm_clk_hw_register_divider(dev, p->name,
323 p->parent_name, 0,
324 clp->base + p->reg_offset,
325 p->div_shift, p->div_width,
326 CLK_DIVIDER_ONE_BASED,
327 &clp->clk_lock);
328 break;
329 case CLK_TYPE_GATE:
330 hw = devm_clk_hw_register_gate(dev, p->name, p->parent_name, 0,
331 clp->base + p->reg_offset,
332 p->bit_idx, 0,
333 &clp->clk_lock);
334 break;
335 case CLK_TYPE_FIXED:
336 hw = clk_hw_register_fixed_rate_parent_data(dev, p->name, pdata,
337 0, p->fixed_rate);
338 break;
339 default:
340 return dev_err_probe(dev, -EINVAL, "Invalid clk type\n");
341 }
342
343 if (IS_ERR(hw))
344 return dev_err_probe(dev, PTR_ERR(hw),
345 "Register clk: %s, type: %u failed!\n",
346 p->name, p->type);
347
348 clp->clk_data.hws[p->id] = hw;
349 }
350
351 return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, &clp->clk_data);
352 }
353
354 static const struct of_device_id loongson2_clk_match_table[] = {
355 { .compatible = "loongson,ls2k0500-clk", .data = &ls2k0500_clks },
356 { .compatible = "loongson,ls2k-clk", .data = &ls2k1000_clks },
357 { .compatible = "loongson,ls2k2000-clk", .data = &ls2k2000_clks },
358 { }
359 };
360 MODULE_DEVICE_TABLE(of, loongson2_clk_match_table);
361
362 static struct platform_driver loongson2_clk_driver = {
363 .probe = loongson2_clk_probe,
364 .driver = {
365 .name = "loongson2-clk",
366 .of_match_table = loongson2_clk_match_table,
367 },
368 };
369 module_platform_driver(loongson2_clk_driver);
370
371 MODULE_DESCRIPTION("Loongson2 clock driver");
372 MODULE_AUTHOR("Loongson Technology Corporation Limited");
373 MODULE_LICENSE("GPL");
374