clk-axi-clkgen.c (14942B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * AXI clkgen driver 4 * 5 * Copyright 2012-2013 Analog Devices Inc. 6 * Author: Lars-Peter Clausen <lars@metafoo.de> 7 */ 8 9#include <linux/platform_device.h> 10#include <linux/clk-provider.h> 11#include <linux/slab.h> 12#include <linux/io.h> 13#include <linux/of.h> 14#include <linux/module.h> 15#include <linux/err.h> 16 17#define AXI_CLKGEN_V2_REG_RESET 0x40 18#define AXI_CLKGEN_V2_REG_CLKSEL 0x44 19#define AXI_CLKGEN_V2_REG_DRP_CNTRL 0x70 20#define AXI_CLKGEN_V2_REG_DRP_STATUS 0x74 21 22#define AXI_CLKGEN_V2_RESET_MMCM_ENABLE BIT(1) 23#define AXI_CLKGEN_V2_RESET_ENABLE BIT(0) 24 25#define AXI_CLKGEN_V2_DRP_CNTRL_SEL BIT(29) 26#define AXI_CLKGEN_V2_DRP_CNTRL_READ BIT(28) 27 28#define AXI_CLKGEN_V2_DRP_STATUS_BUSY BIT(16) 29 30#define MMCM_REG_CLKOUT5_2 0x07 31#define MMCM_REG_CLKOUT0_1 0x08 32#define MMCM_REG_CLKOUT0_2 0x09 33#define MMCM_REG_CLKOUT6_2 0x13 34#define MMCM_REG_CLK_FB1 0x14 35#define MMCM_REG_CLK_FB2 0x15 36#define MMCM_REG_CLK_DIV 0x16 37#define MMCM_REG_LOCK1 0x18 38#define MMCM_REG_LOCK2 0x19 39#define MMCM_REG_LOCK3 0x1a 40#define MMCM_REG_POWER 0x28 41#define MMCM_REG_FILTER1 0x4e 42#define MMCM_REG_FILTER2 0x4f 43 44#define MMCM_CLKOUT_NOCOUNT BIT(6) 45 46#define MMCM_CLK_DIV_DIVIDE BIT(11) 47#define MMCM_CLK_DIV_NOCOUNT BIT(12) 48 49struct axi_clkgen_limits { 50 unsigned int fpfd_min; 51 unsigned int fpfd_max; 52 unsigned int fvco_min; 53 unsigned int fvco_max; 54}; 55 56struct axi_clkgen { 57 void __iomem *base; 58 struct clk_hw clk_hw; 59 struct axi_clkgen_limits limits; 60}; 61 62static uint32_t axi_clkgen_lookup_filter(unsigned int m) 63{ 64 switch (m) { 65 case 0: 66 return 0x01001990; 67 case 1: 68 return 0x01001190; 69 case 2: 70 return 0x01009890; 71 case 3: 72 return 0x01001890; 73 case 4: 74 return 0x01008890; 75 case 5 ... 8: 76 return 0x01009090; 77 case 9 ... 11: 78 return 0x01000890; 79 case 12: 80 return 0x08009090; 81 case 13 ... 22: 82 return 0x01001090; 83 case 23 ... 36: 84 return 0x01008090; 85 case 37 ... 46: 86 return 0x08001090; 87 default: 88 return 0x08008090; 89 } 90} 91 92static const uint32_t axi_clkgen_lock_table[] = { 93 0x060603e8, 0x060603e8, 0x080803e8, 0x0b0b03e8, 94 0x0e0e03e8, 0x111103e8, 0x131303e8, 0x161603e8, 95 0x191903e8, 0x1c1c03e8, 0x1f1f0384, 0x1f1f0339, 96 0x1f1f02ee, 0x1f1f02bc, 0x1f1f028a, 0x1f1f0271, 97 0x1f1f023f, 0x1f1f0226, 0x1f1f020d, 0x1f1f01f4, 98 0x1f1f01db, 0x1f1f01c2, 0x1f1f01a9, 0x1f1f0190, 99 0x1f1f0190, 0x1f1f0177, 0x1f1f015e, 0x1f1f015e, 100 0x1f1f0145, 0x1f1f0145, 0x1f1f012c, 0x1f1f012c, 101 0x1f1f012c, 0x1f1f0113, 0x1f1f0113, 0x1f1f0113, 102}; 103 104static uint32_t axi_clkgen_lookup_lock(unsigned int m) 105{ 106 if (m < ARRAY_SIZE(axi_clkgen_lock_table)) 107 return axi_clkgen_lock_table[m]; 108 return 0x1f1f00fa; 109} 110 111static const struct axi_clkgen_limits axi_clkgen_zynqmp_default_limits = { 112 .fpfd_min = 10000, 113 .fpfd_max = 450000, 114 .fvco_min = 800000, 115 .fvco_max = 1600000, 116}; 117 118static const struct axi_clkgen_limits axi_clkgen_zynq_default_limits = { 119 .fpfd_min = 10000, 120 .fpfd_max = 300000, 121 .fvco_min = 600000, 122 .fvco_max = 1200000, 123}; 124 125static void axi_clkgen_calc_params(const struct axi_clkgen_limits *limits, 126 unsigned long fin, unsigned long fout, 127 unsigned int *best_d, unsigned int *best_m, unsigned int *best_dout) 128{ 129 unsigned long d, d_min, d_max, _d_min, _d_max; 130 unsigned long m, m_min, m_max; 131 unsigned long f, dout, best_f, fvco; 132 unsigned long fract_shift = 0; 133 unsigned long fvco_min_fract, fvco_max_fract; 134 135 fin /= 1000; 136 fout /= 1000; 137 138 best_f = ULONG_MAX; 139 *best_d = 0; 140 *best_m = 0; 141 *best_dout = 0; 142 143 d_min = max_t(unsigned long, DIV_ROUND_UP(fin, limits->fpfd_max), 1); 144 d_max = min_t(unsigned long, fin / limits->fpfd_min, 80); 145 146again: 147 fvco_min_fract = limits->fvco_min << fract_shift; 148 fvco_max_fract = limits->fvco_max << fract_shift; 149 150 m_min = max_t(unsigned long, DIV_ROUND_UP(fvco_min_fract, fin) * d_min, 1); 151 m_max = min_t(unsigned long, fvco_max_fract * d_max / fin, 64 << fract_shift); 152 153 for (m = m_min; m <= m_max; m++) { 154 _d_min = max(d_min, DIV_ROUND_UP(fin * m, fvco_max_fract)); 155 _d_max = min(d_max, fin * m / fvco_min_fract); 156 157 for (d = _d_min; d <= _d_max; d++) { 158 fvco = fin * m / d; 159 160 dout = DIV_ROUND_CLOSEST(fvco, fout); 161 dout = clamp_t(unsigned long, dout, 1, 128 << fract_shift); 162 f = fvco / dout; 163 if (abs(f - fout) < abs(best_f - fout)) { 164 best_f = f; 165 *best_d = d; 166 *best_m = m << (3 - fract_shift); 167 *best_dout = dout << (3 - fract_shift); 168 if (best_f == fout) 169 return; 170 } 171 } 172 } 173 174 /* Lets see if we find a better setting in fractional mode */ 175 if (fract_shift == 0) { 176 fract_shift = 3; 177 goto again; 178 } 179} 180 181struct axi_clkgen_div_params { 182 unsigned int low; 183 unsigned int high; 184 unsigned int edge; 185 unsigned int nocount; 186 unsigned int frac_en; 187 unsigned int frac; 188 unsigned int frac_wf_f; 189 unsigned int frac_wf_r; 190 unsigned int frac_phase; 191}; 192 193static void axi_clkgen_calc_clk_params(unsigned int divider, 194 unsigned int frac_divider, struct axi_clkgen_div_params *params) 195{ 196 197 memset(params, 0x0, sizeof(*params)); 198 199 if (divider == 1) { 200 params->nocount = 1; 201 return; 202 } 203 204 if (frac_divider == 0) { 205 params->high = divider / 2; 206 params->edge = divider % 2; 207 params->low = divider - params->high; 208 } else { 209 params->frac_en = 1; 210 params->frac = frac_divider; 211 212 params->high = divider / 2; 213 params->edge = divider % 2; 214 params->low = params->high; 215 216 if (params->edge == 0) { 217 params->high--; 218 params->frac_wf_r = 1; 219 } 220 221 if (params->edge == 0 || frac_divider == 1) 222 params->low--; 223 if (((params->edge == 0) ^ (frac_divider == 1)) || 224 (divider == 2 && frac_divider == 1)) 225 params->frac_wf_f = 1; 226 227 params->frac_phase = params->edge * 4 + frac_divider / 2; 228 } 229} 230 231static void axi_clkgen_write(struct axi_clkgen *axi_clkgen, 232 unsigned int reg, unsigned int val) 233{ 234 writel(val, axi_clkgen->base + reg); 235} 236 237static void axi_clkgen_read(struct axi_clkgen *axi_clkgen, 238 unsigned int reg, unsigned int *val) 239{ 240 *val = readl(axi_clkgen->base + reg); 241} 242 243static int axi_clkgen_wait_non_busy(struct axi_clkgen *axi_clkgen) 244{ 245 unsigned int timeout = 10000; 246 unsigned int val; 247 248 do { 249 axi_clkgen_read(axi_clkgen, AXI_CLKGEN_V2_REG_DRP_STATUS, &val); 250 } while ((val & AXI_CLKGEN_V2_DRP_STATUS_BUSY) && --timeout); 251 252 if (val & AXI_CLKGEN_V2_DRP_STATUS_BUSY) 253 return -EIO; 254 255 return val & 0xffff; 256} 257 258static int axi_clkgen_mmcm_read(struct axi_clkgen *axi_clkgen, 259 unsigned int reg, unsigned int *val) 260{ 261 unsigned int reg_val; 262 int ret; 263 264 ret = axi_clkgen_wait_non_busy(axi_clkgen); 265 if (ret < 0) 266 return ret; 267 268 reg_val = AXI_CLKGEN_V2_DRP_CNTRL_SEL | AXI_CLKGEN_V2_DRP_CNTRL_READ; 269 reg_val |= (reg << 16); 270 271 axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_DRP_CNTRL, reg_val); 272 273 ret = axi_clkgen_wait_non_busy(axi_clkgen); 274 if (ret < 0) 275 return ret; 276 277 *val = ret; 278 279 return 0; 280} 281 282static int axi_clkgen_mmcm_write(struct axi_clkgen *axi_clkgen, 283 unsigned int reg, unsigned int val, unsigned int mask) 284{ 285 unsigned int reg_val = 0; 286 int ret; 287 288 ret = axi_clkgen_wait_non_busy(axi_clkgen); 289 if (ret < 0) 290 return ret; 291 292 if (mask != 0xffff) { 293 axi_clkgen_mmcm_read(axi_clkgen, reg, ®_val); 294 reg_val &= ~mask; 295 } 296 297 reg_val |= AXI_CLKGEN_V2_DRP_CNTRL_SEL | (reg << 16) | (val & mask); 298 299 axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_DRP_CNTRL, reg_val); 300 301 return 0; 302} 303 304static void axi_clkgen_mmcm_enable(struct axi_clkgen *axi_clkgen, 305 bool enable) 306{ 307 unsigned int val = AXI_CLKGEN_V2_RESET_ENABLE; 308 309 if (enable) 310 val |= AXI_CLKGEN_V2_RESET_MMCM_ENABLE; 311 312 axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_RESET, val); 313} 314 315static struct axi_clkgen *clk_hw_to_axi_clkgen(struct clk_hw *clk_hw) 316{ 317 return container_of(clk_hw, struct axi_clkgen, clk_hw); 318} 319 320static void axi_clkgen_set_div(struct axi_clkgen *axi_clkgen, 321 unsigned int reg1, unsigned int reg2, unsigned int reg3, 322 struct axi_clkgen_div_params *params) 323{ 324 axi_clkgen_mmcm_write(axi_clkgen, reg1, 325 (params->high << 6) | params->low, 0xefff); 326 axi_clkgen_mmcm_write(axi_clkgen, reg2, 327 (params->frac << 12) | (params->frac_en << 11) | 328 (params->frac_wf_r << 10) | (params->edge << 7) | 329 (params->nocount << 6), 0x7fff); 330 if (reg3 != 0) { 331 axi_clkgen_mmcm_write(axi_clkgen, reg3, 332 (params->frac_phase << 11) | (params->frac_wf_f << 10), 0x3c00); 333 } 334} 335 336static int axi_clkgen_set_rate(struct clk_hw *clk_hw, 337 unsigned long rate, unsigned long parent_rate) 338{ 339 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw); 340 const struct axi_clkgen_limits *limits = &axi_clkgen->limits; 341 unsigned int d, m, dout; 342 struct axi_clkgen_div_params params; 343 uint32_t power = 0; 344 uint32_t filter; 345 uint32_t lock; 346 347 if (parent_rate == 0 || rate == 0) 348 return -EINVAL; 349 350 axi_clkgen_calc_params(limits, parent_rate, rate, &d, &m, &dout); 351 352 if (d == 0 || dout == 0 || m == 0) 353 return -EINVAL; 354 355 if ((dout & 0x7) != 0 || (m & 0x7) != 0) 356 power |= 0x9800; 357 358 axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_POWER, power, 0x9800); 359 360 filter = axi_clkgen_lookup_filter(m - 1); 361 lock = axi_clkgen_lookup_lock(m - 1); 362 363 axi_clkgen_calc_clk_params(dout >> 3, dout & 0x7, ¶ms); 364 axi_clkgen_set_div(axi_clkgen, MMCM_REG_CLKOUT0_1, MMCM_REG_CLKOUT0_2, 365 MMCM_REG_CLKOUT5_2, ¶ms); 366 367 axi_clkgen_calc_clk_params(d, 0, ¶ms); 368 axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_CLK_DIV, 369 (params.edge << 13) | (params.nocount << 12) | 370 (params.high << 6) | params.low, 0x3fff); 371 372 axi_clkgen_calc_clk_params(m >> 3, m & 0x7, ¶ms); 373 axi_clkgen_set_div(axi_clkgen, MMCM_REG_CLK_FB1, MMCM_REG_CLK_FB2, 374 MMCM_REG_CLKOUT6_2, ¶ms); 375 376 axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK1, lock & 0x3ff, 0x3ff); 377 axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK2, 378 (((lock >> 16) & 0x1f) << 10) | 0x1, 0x7fff); 379 axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK3, 380 (((lock >> 24) & 0x1f) << 10) | 0x3e9, 0x7fff); 381 axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_FILTER1, filter >> 16, 0x9900); 382 axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_FILTER2, filter, 0x9900); 383 384 return 0; 385} 386 387static long axi_clkgen_round_rate(struct clk_hw *hw, unsigned long rate, 388 unsigned long *parent_rate) 389{ 390 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(hw); 391 const struct axi_clkgen_limits *limits = &axi_clkgen->limits; 392 unsigned int d, m, dout; 393 unsigned long long tmp; 394 395 axi_clkgen_calc_params(limits, *parent_rate, rate, &d, &m, &dout); 396 397 if (d == 0 || dout == 0 || m == 0) 398 return -EINVAL; 399 400 tmp = (unsigned long long)*parent_rate * m; 401 tmp = DIV_ROUND_CLOSEST_ULL(tmp, dout * d); 402 403 return min_t(unsigned long long, tmp, LONG_MAX); 404} 405 406static unsigned int axi_clkgen_get_div(struct axi_clkgen *axi_clkgen, 407 unsigned int reg1, unsigned int reg2) 408{ 409 unsigned int val1, val2; 410 unsigned int div; 411 412 axi_clkgen_mmcm_read(axi_clkgen, reg2, &val2); 413 if (val2 & MMCM_CLKOUT_NOCOUNT) 414 return 8; 415 416 axi_clkgen_mmcm_read(axi_clkgen, reg1, &val1); 417 418 div = (val1 & 0x3f) + ((val1 >> 6) & 0x3f); 419 div <<= 3; 420 421 if (val2 & MMCM_CLK_DIV_DIVIDE) { 422 if ((val2 & BIT(7)) && (val2 & 0x7000) != 0x1000) 423 div += 8; 424 else 425 div += 16; 426 427 div += (val2 >> 12) & 0x7; 428 } 429 430 return div; 431} 432 433static unsigned long axi_clkgen_recalc_rate(struct clk_hw *clk_hw, 434 unsigned long parent_rate) 435{ 436 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw); 437 unsigned int d, m, dout; 438 unsigned long long tmp; 439 unsigned int val; 440 441 dout = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLKOUT0_1, 442 MMCM_REG_CLKOUT0_2); 443 m = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLK_FB1, 444 MMCM_REG_CLK_FB2); 445 446 axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLK_DIV, &val); 447 if (val & MMCM_CLK_DIV_NOCOUNT) 448 d = 1; 449 else 450 d = (val & 0x3f) + ((val >> 6) & 0x3f); 451 452 if (d == 0 || dout == 0) 453 return 0; 454 455 tmp = (unsigned long long)parent_rate * m; 456 tmp = DIV_ROUND_CLOSEST_ULL(tmp, dout * d); 457 458 return min_t(unsigned long long, tmp, ULONG_MAX); 459} 460 461static int axi_clkgen_enable(struct clk_hw *clk_hw) 462{ 463 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw); 464 465 axi_clkgen_mmcm_enable(axi_clkgen, true); 466 467 return 0; 468} 469 470static void axi_clkgen_disable(struct clk_hw *clk_hw) 471{ 472 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw); 473 474 axi_clkgen_mmcm_enable(axi_clkgen, false); 475} 476 477static int axi_clkgen_set_parent(struct clk_hw *clk_hw, u8 index) 478{ 479 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw); 480 481 axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_CLKSEL, index); 482 483 return 0; 484} 485 486static u8 axi_clkgen_get_parent(struct clk_hw *clk_hw) 487{ 488 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw); 489 unsigned int parent; 490 491 axi_clkgen_read(axi_clkgen, AXI_CLKGEN_V2_REG_CLKSEL, &parent); 492 493 return parent; 494} 495 496static const struct clk_ops axi_clkgen_ops = { 497 .recalc_rate = axi_clkgen_recalc_rate, 498 .round_rate = axi_clkgen_round_rate, 499 .set_rate = axi_clkgen_set_rate, 500 .enable = axi_clkgen_enable, 501 .disable = axi_clkgen_disable, 502 .set_parent = axi_clkgen_set_parent, 503 .get_parent = axi_clkgen_get_parent, 504}; 505 506static int axi_clkgen_probe(struct platform_device *pdev) 507{ 508 const struct axi_clkgen_limits *dflt_limits; 509 struct axi_clkgen *axi_clkgen; 510 struct clk_init_data init; 511 const char *parent_names[2]; 512 const char *clk_name; 513 unsigned int i; 514 int ret; 515 516 dflt_limits = device_get_match_data(&pdev->dev); 517 if (!dflt_limits) 518 return -ENODEV; 519 520 axi_clkgen = devm_kzalloc(&pdev->dev, sizeof(*axi_clkgen), GFP_KERNEL); 521 if (!axi_clkgen) 522 return -ENOMEM; 523 524 axi_clkgen->base = devm_platform_ioremap_resource(pdev, 0); 525 if (IS_ERR(axi_clkgen->base)) 526 return PTR_ERR(axi_clkgen->base); 527 528 init.num_parents = of_clk_get_parent_count(pdev->dev.of_node); 529 if (init.num_parents < 1 || init.num_parents > 2) 530 return -EINVAL; 531 532 for (i = 0; i < init.num_parents; i++) { 533 parent_names[i] = of_clk_get_parent_name(pdev->dev.of_node, i); 534 if (!parent_names[i]) 535 return -EINVAL; 536 } 537 538 memcpy(&axi_clkgen->limits, dflt_limits, sizeof(axi_clkgen->limits)); 539 540 clk_name = pdev->dev.of_node->name; 541 of_property_read_string(pdev->dev.of_node, "clock-output-names", 542 &clk_name); 543 544 init.name = clk_name; 545 init.ops = &axi_clkgen_ops; 546 init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; 547 init.parent_names = parent_names; 548 549 axi_clkgen_mmcm_enable(axi_clkgen, false); 550 551 axi_clkgen->clk_hw.init = &init; 552 ret = devm_clk_hw_register(&pdev->dev, &axi_clkgen->clk_hw); 553 if (ret) 554 return ret; 555 556 return of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_simple_get, 557 &axi_clkgen->clk_hw); 558} 559 560static int axi_clkgen_remove(struct platform_device *pdev) 561{ 562 of_clk_del_provider(pdev->dev.of_node); 563 564 return 0; 565} 566 567static const struct of_device_id axi_clkgen_ids[] = { 568 { 569 .compatible = "adi,zynqmp-axi-clkgen-2.00.a", 570 .data = &axi_clkgen_zynqmp_default_limits, 571 }, 572 { 573 .compatible = "adi,axi-clkgen-2.00.a", 574 .data = &axi_clkgen_zynq_default_limits, 575 }, 576 { } 577}; 578MODULE_DEVICE_TABLE(of, axi_clkgen_ids); 579 580static struct platform_driver axi_clkgen_driver = { 581 .driver = { 582 .name = "adi-axi-clkgen", 583 .of_match_table = axi_clkgen_ids, 584 }, 585 .probe = axi_clkgen_probe, 586 .remove = axi_clkgen_remove, 587}; 588module_platform_driver(axi_clkgen_driver); 589 590MODULE_LICENSE("GPL v2"); 591MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); 592MODULE_DESCRIPTION("Driver for the Analog Devices' AXI clkgen pcore clock generator");