587 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * AXI clkgen driver4 *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 0x4018#define AXI_CLKGEN_V2_REG_CLKSEL 0x4419#define AXI_CLKGEN_V2_REG_DRP_CNTRL 0x7020#define AXI_CLKGEN_V2_REG_DRP_STATUS 0x7421 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 0x0731#define MMCM_REG_CLKOUT0_1 0x0832#define MMCM_REG_CLKOUT0_2 0x0933#define MMCM_REG_CLKOUT6_2 0x1334#define MMCM_REG_CLK_FB1 0x1435#define MMCM_REG_CLK_FB2 0x1536#define MMCM_REG_CLK_DIV 0x1637#define MMCM_REG_LOCK1 0x1838#define MMCM_REG_LOCK2 0x1939#define MMCM_REG_LOCK3 0x1a40#define MMCM_REG_POWER 0x2841#define MMCM_REG_FILTER1 0x4e42#define MMCM_REG_FILTER2 0x4f43 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 int axi_clkgen_determine_rate(struct clk_hw *hw,388 struct clk_rate_request *req)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, req->best_parent_rate, req->rate,396 &d, &m, &dout);397 398 if (d == 0 || dout == 0 || m == 0)399 return -EINVAL;400 401 tmp = (unsigned long long)req->best_parent_rate * m;402 tmp = DIV_ROUND_CLOSEST_ULL(tmp, dout * d);403 404 req->rate = min_t(unsigned long long, tmp, LONG_MAX);405 return 0;406}407 408static unsigned int axi_clkgen_get_div(struct axi_clkgen *axi_clkgen,409 unsigned int reg1, unsigned int reg2)410{411 unsigned int val1, val2;412 unsigned int div;413 414 axi_clkgen_mmcm_read(axi_clkgen, reg2, &val2);415 if (val2 & MMCM_CLKOUT_NOCOUNT)416 return 8;417 418 axi_clkgen_mmcm_read(axi_clkgen, reg1, &val1);419 420 div = (val1 & 0x3f) + ((val1 >> 6) & 0x3f);421 div <<= 3;422 423 if (val2 & MMCM_CLK_DIV_DIVIDE) {424 if ((val2 & BIT(7)) && (val2 & 0x7000) != 0x1000)425 div += 8;426 else427 div += 16;428 429 div += (val2 >> 12) & 0x7;430 }431 432 return div;433}434 435static unsigned long axi_clkgen_recalc_rate(struct clk_hw *clk_hw,436 unsigned long parent_rate)437{438 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);439 unsigned int d, m, dout;440 unsigned long long tmp;441 unsigned int val;442 443 dout = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLKOUT0_1,444 MMCM_REG_CLKOUT0_2);445 m = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLK_FB1,446 MMCM_REG_CLK_FB2);447 448 axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLK_DIV, &val);449 if (val & MMCM_CLK_DIV_NOCOUNT)450 d = 1;451 else452 d = (val & 0x3f) + ((val >> 6) & 0x3f);453 454 if (d == 0 || dout == 0)455 return 0;456 457 tmp = (unsigned long long)parent_rate * m;458 tmp = DIV_ROUND_CLOSEST_ULL(tmp, dout * d);459 460 return min_t(unsigned long long, tmp, ULONG_MAX);461}462 463static int axi_clkgen_enable(struct clk_hw *clk_hw)464{465 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);466 467 axi_clkgen_mmcm_enable(axi_clkgen, true);468 469 return 0;470}471 472static void axi_clkgen_disable(struct clk_hw *clk_hw)473{474 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);475 476 axi_clkgen_mmcm_enable(axi_clkgen, false);477}478 479static int axi_clkgen_set_parent(struct clk_hw *clk_hw, u8 index)480{481 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);482 483 axi_clkgen_write(axi_clkgen, AXI_CLKGEN_V2_REG_CLKSEL, index);484 485 return 0;486}487 488static u8 axi_clkgen_get_parent(struct clk_hw *clk_hw)489{490 struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);491 unsigned int parent;492 493 axi_clkgen_read(axi_clkgen, AXI_CLKGEN_V2_REG_CLKSEL, &parent);494 495 return parent;496}497 498static const struct clk_ops axi_clkgen_ops = {499 .recalc_rate = axi_clkgen_recalc_rate,500 .determine_rate = axi_clkgen_determine_rate,501 .set_rate = axi_clkgen_set_rate,502 .enable = axi_clkgen_enable,503 .disable = axi_clkgen_disable,504 .set_parent = axi_clkgen_set_parent,505 .get_parent = axi_clkgen_get_parent,506};507 508static int axi_clkgen_probe(struct platform_device *pdev)509{510 const struct axi_clkgen_limits *dflt_limits;511 struct axi_clkgen *axi_clkgen;512 struct clk_init_data init;513 const char *parent_names[2];514 const char *clk_name;515 unsigned int i;516 int ret;517 518 dflt_limits = device_get_match_data(&pdev->dev);519 if (!dflt_limits)520 return -ENODEV;521 522 axi_clkgen = devm_kzalloc(&pdev->dev, sizeof(*axi_clkgen), GFP_KERNEL);523 if (!axi_clkgen)524 return -ENOMEM;525 526 axi_clkgen->base = devm_platform_ioremap_resource(pdev, 0);527 if (IS_ERR(axi_clkgen->base))528 return PTR_ERR(axi_clkgen->base);529 530 init.num_parents = of_clk_get_parent_count(pdev->dev.of_node);531 if (init.num_parents < 1 || init.num_parents > 2)532 return -EINVAL;533 534 for (i = 0; i < init.num_parents; i++) {535 parent_names[i] = of_clk_get_parent_name(pdev->dev.of_node, i);536 if (!parent_names[i])537 return -EINVAL;538 }539 540 memcpy(&axi_clkgen->limits, dflt_limits, sizeof(axi_clkgen->limits));541 542 clk_name = pdev->dev.of_node->name;543 of_property_read_string(pdev->dev.of_node, "clock-output-names",544 &clk_name);545 546 init.name = clk_name;547 init.ops = &axi_clkgen_ops;548 init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;549 init.parent_names = parent_names;550 551 axi_clkgen_mmcm_enable(axi_clkgen, false);552 553 axi_clkgen->clk_hw.init = &init;554 ret = devm_clk_hw_register(&pdev->dev, &axi_clkgen->clk_hw);555 if (ret)556 return ret;557 558 return devm_of_clk_add_hw_provider(&pdev->dev, of_clk_hw_simple_get,559 &axi_clkgen->clk_hw);560}561 562static const struct of_device_id axi_clkgen_ids[] = {563 {564 .compatible = "adi,zynqmp-axi-clkgen-2.00.a",565 .data = &axi_clkgen_zynqmp_default_limits,566 },567 {568 .compatible = "adi,axi-clkgen-2.00.a",569 .data = &axi_clkgen_zynq_default_limits,570 },571 { }572};573MODULE_DEVICE_TABLE(of, axi_clkgen_ids);574 575static struct platform_driver axi_clkgen_driver = {576 .driver = {577 .name = "adi-axi-clkgen",578 .of_match_table = axi_clkgen_ids,579 },580 .probe = axi_clkgen_probe,581};582module_platform_driver(axi_clkgen_driver);583 584MODULE_LICENSE("GPL v2");585MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");586MODULE_DESCRIPTION("Driver for the Analog Devices' AXI clkgen pcore clock generator");587