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 .max_register = ANACTRL_HIFIPLL_STS, 303 }; 304 305 static struct meson_clk_hw_data a1_pll_clks = { 306 .hws = a1_pll_hw_clks, 307 .num = ARRAY_SIZE(a1_pll_hw_clks), 308 }; 309 310 static int meson_a1_pll_probe(struct platform_device *pdev) 311 { 312 struct device *dev = &pdev->dev; 313 void __iomem *base; 314 struct regmap *map; 315 int clkid, i, err; 316 317 base = devm_platform_ioremap_resource(pdev, 0); 318 if (IS_ERR(base)) 319 return dev_err_probe(dev, PTR_ERR(base), 320 "can't ioremap resource\n"); 321 322 map = devm_regmap_init_mmio(dev, base, &a1_pll_regmap_cfg); 323 if (IS_ERR(map)) 324 return dev_err_probe(dev, PTR_ERR(map), 325 "can't init regmap mmio region\n"); 326 327 /* Populate regmap for the regmap backed clocks */ 328 for (i = 0; i < ARRAY_SIZE(a1_pll_regmaps); i++) 329 a1_pll_regmaps[i]->map = map; 330 331 /* Register clocks */ 332 for (clkid = 0; clkid < a1_pll_clks.num; clkid++) { 333 err = devm_clk_hw_register(dev, a1_pll_clks.hws[clkid]); 334 if (err) 335 return dev_err_probe(dev, err, 336 "clock[%d] registration failed\n", 337 clkid); 338 } 339 340 return devm_of_clk_add_hw_provider(dev, meson_clk_hw_get, 341 &a1_pll_clks); 342 } 343 344 static const struct of_device_id a1_pll_clkc_match_table[] = { 345 { .compatible = "amlogic,a1-pll-clkc", }, 346 {} 347 }; 348 MODULE_DEVICE_TABLE(of, a1_pll_clkc_match_table); 349 350 static struct platform_driver a1_pll_clkc_driver = { 351 .probe = meson_a1_pll_probe, 352 .driver = { 353 .name = "a1-pll-clkc", 354 .of_match_table = a1_pll_clkc_match_table, 355 }, 356 }; 357 module_platform_driver(a1_pll_clkc_driver); 358 359 MODULE_DESCRIPTION("Amlogic S4 PLL Clock Controller driver"); 360 MODULE_AUTHOR("Jian Hu <jian.hu@amlogic.com>"); 361 MODULE_AUTHOR("Dmitry Rokosov <ddrokosov@sberdevices.ru>"); 362 MODULE_LICENSE("GPL"); 363