1 // SPDX-License-Identifier: GPL-2.0+
3 * Author: Yinbo Zhu <zhuyinbo@loongson.cn>
4 * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
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>
16 static const struct clk_parent_data pdata
[] = {
17 { .fw_name
= "ref_100m", },
20 enum loongson2_clk_type
{
29 struct loongson2_clk_provider
{
32 struct clk_hw_onecell_data clk_data
;
33 spinlock_t clk_lock
; /* protect access to DIV registers */
36 struct loongson2_clk_data
{
45 struct loongson2_clk_board_info
{
47 enum loongson2_clk_type type
;
49 const char *parent_name
;
50 unsigned long fixed_rate
;
59 #define CLK_DIV(_id, _name, _pname, _offset, _dshift, _dwidth) \
62 .type = CLK_TYPE_DIVIDER, \
64 .parent_name = _pname, \
65 .reg_offset = _offset, \
66 .div_shift = _dshift, \
67 .div_width = _dwidth, \
70 #define CLK_PLL(_id, _name, _offset, _mshift, _mwidth, \
74 .type = CLK_TYPE_PLL, \
76 .parent_name = NULL, \
77 .reg_offset = _offset, \
78 .mult_shift = _mshift, \
79 .mult_width = _mwidth, \
80 .div_shift = _dshift, \
81 .div_width = _dwidth, \
84 #define CLK_SCALE(_id, _name, _pname, _offset, \
88 .type = CLK_TYPE_SCALE, \
90 .parent_name = _pname, \
91 .reg_offset = _offset, \
92 .div_shift = _dshift, \
93 .div_width = _dwidth, \
96 #define CLK_GATE(_id, _name, _pname, _offset, _bidx) \
99 .type = CLK_TYPE_GATE, \
101 .parent_name = _pname, \
102 .reg_offset = _offset, \
106 #define CLK_FIXED(_id, _name, _pname, _rate) \
109 .type = CLK_TYPE_FIXED, \
111 .parent_name = _pname, \
112 .fixed_rate = _rate, \
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),
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),
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
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),
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),
201 static inline struct loongson2_clk_data
*to_loongson2_clk(struct clk_hw
*hw
)
203 return container_of(hw
, struct loongson2_clk_data
, hw
);
206 static inline unsigned long loongson2_rate_part(u64 val
, u8 shift
, u8 width
)
208 return (val
& GENMASK(shift
+ width
- 1, shift
)) >> shift
;
211 static unsigned long loongson2_pll_recalc_rate(struct clk_hw
*hw
,
212 unsigned long parent_rate
)
215 struct loongson2_clk_data
*clk
= to_loongson2_clk(hw
);
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
);
221 return div_u64((u64
)parent_rate
* mult
, div
);
224 static const struct clk_ops loongson2_pll_recalc_ops
= {
225 .recalc_rate
= loongson2_pll_recalc_rate
,
228 static unsigned long loongson2_freqscale_recalc_rate(struct clk_hw
*hw
,
229 unsigned long parent_rate
)
232 struct loongson2_clk_data
*clk
= to_loongson2_clk(hw
);
234 val
= readq(clk
->reg
);
235 mult
= loongson2_rate_part(val
, clk
->div_shift
, clk
->div_width
) + 1;
237 return div_u64((u64
)parent_rate
* mult
, 8);
240 static const struct clk_ops loongson2_freqscale_recalc_ops
= {
241 .recalc_rate
= loongson2_freqscale_recalc_rate
,
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
)
250 struct loongson2_clk_data
*clk
;
251 struct clk_init_data init
= { };
253 clk
= devm_kzalloc(clp
->dev
, sizeof(*clk
), GFP_KERNEL
);
255 return ERR_PTR(-ENOMEM
);
257 init
.name
= cld
->name
;
260 init
.num_parents
= 1;
262 if (!cld
->parent_name
)
263 init
.parent_data
= pdata
;
265 init
.parent_names
= &cld
->parent_name
;
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
;
275 ret
= devm_clk_hw_register(clp
->dev
, hw
);
282 static int loongson2_clk_probe(struct platform_device
*pdev
)
286 struct device
*dev
= &pdev
->dev
;
287 struct loongson2_clk_provider
*clp
;
288 const struct loongson2_clk_board_info
*p
, *data
;
290 data
= device_get_match_data(dev
);
294 for (p
= data
; p
->name
; p
++)
297 clp
= devm_kzalloc(dev
, struct_size(clp
, clk_data
.hws
, clks_num
),
302 clp
->base
= devm_platform_ioremap_resource(pdev
, 0);
303 if (IS_ERR(clp
->base
))
304 return PTR_ERR(clp
->base
);
306 spin_lock_init(&clp
->clk_lock
);
307 clp
->clk_data
.num
= clks_num
+ 1;
310 for (i
= 0; i
< clks_num
; i
++) {
314 hw
= loongson2_clk_register(clp
, p
,
315 &loongson2_pll_recalc_ops
);
318 hw
= loongson2_clk_register(clp
, p
,
319 &loongson2_freqscale_recalc_ops
);
321 case CLK_TYPE_DIVIDER
:
322 hw
= devm_clk_hw_register_divider(dev
, p
->name
,
324 clp
->base
+ p
->reg_offset
,
325 p
->div_shift
, p
->div_width
,
326 CLK_DIVIDER_ONE_BASED
,
330 hw
= devm_clk_hw_register_gate(dev
, p
->name
, p
->parent_name
, 0,
331 clp
->base
+ p
->reg_offset
,
336 hw
= clk_hw_register_fixed_rate_parent_data(dev
, p
->name
, pdata
,
340 return dev_err_probe(dev
, -EINVAL
, "Invalid clk type\n");
344 return dev_err_probe(dev
, PTR_ERR(hw
),
345 "Register clk: %s, type: %u failed!\n",
348 clp
->clk_data
.hws
[p
->id
] = hw
;
351 return devm_of_clk_add_hw_provider(dev
, of_clk_hw_onecell_get
, &clp
->clk_data
);
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
},
360 MODULE_DEVICE_TABLE(of
, loongson2_clk_match_table
);
362 static struct platform_driver loongson2_clk_driver
= {
363 .probe
= loongson2_clk_probe
,
365 .name
= "loongson2-clk",
366 .of_match_table
= loongson2_clk_match_table
,
369 module_platform_driver(loongson2_clk_driver
);
371 MODULE_DESCRIPTION("Loongson2 clock driver");
372 MODULE_AUTHOR("Loongson Technology Corporation Limited");
373 MODULE_LICENSE("GPL");