374 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Author: Yinbo Zhu <zhuyinbo@loongson.cn>4 * Copyright (C) 2022-2023 Loongson Technology Corporation Limited5 */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 16static const struct clk_parent_data pdata[] = {17 { .fw_name = "ref_100m", },18};19 20enum 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 29struct 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 36struct 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 45struct 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 115static 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 136static 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-prodiver147 * layer code doesn't support 64bit io operation thus a conversion148 * is required that subtract shift by 32 and add 4byte to the hda149 * address150 */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 163static 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 201static 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 206static inline unsigned long loongson2_rate_part(u64 val, u8 shift, u8 width)207{208 return (val & GENMASK(shift + width - 1, shift)) >> shift;209}210 211static 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 224static const struct clk_ops loongson2_pll_recalc_ops = {225 .recalc_rate = loongson2_pll_recalc_rate,226};227 228static 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 240static const struct clk_ops loongson2_freqscale_recalc_ops = {241 .recalc_rate = loongson2_freqscale_recalc_rate,242};243 244static 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 else265 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 282static 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 354static 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};360MODULE_DEVICE_TABLE(of, loongson2_clk_match_table);361 362static 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};369module_platform_driver(loongson2_clk_driver);370 371MODULE_DESCRIPTION("Loongson2 clock driver");372MODULE_AUTHOR("Loongson Technology Corporation Limited");373MODULE_LICENSE("GPL");374