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