Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) /* * Copyright (c) 2016 AmLogic, Inc. * Author: Michael Turquette <mturquette@baylibre.com> */ /* * MultiPhase Locked Loops are outputs from a PLL with additional frequency * scaling capabilities. MPLL rates are calculated as: * * f(N2_integer, SDM_IN ) = 2.0G/(N2_integer + SDM_IN/16384) */ #include <linux/clk-provider.h> #include <linux/module.h> #include <linux/spinlock.h> #include "clk-regmap.h" #include "clk-mpll.h" #define SDM_DEN 16384 #define N2_MIN 4 #define N2_MAX 511 static inline struct meson_clk_mpll_data * meson_clk_mpll_data(struct clk_regmap *clk) { return (struct meson_clk_mpll_data *)clk->data; } static long rate_from_params(unsigned long parent_rate, unsigned int sdm, unsigned int n2) { unsigned long divisor = (SDM_DEN * n2) + sdm; if (n2 < N2_MIN) return -EINVAL; return DIV_ROUND_UP_ULL((u64)parent_rate * SDM_DEN, divisor); } static void params_from_rate(unsigned long requested_rate, unsigned long parent_rate, unsigned int *sdm, unsigned int *n2, u8 flags) { uint64_t div = parent_rate; uint64_t frac = do_div(div, requested_rate); frac *= SDM_DEN; if (flags & CLK_MESON_MPLL_ROUND_CLOSEST) *sdm = DIV_ROUND_CLOSEST_ULL(frac, requested_rate); else *sdm = DIV_ROUND_UP_ULL(frac, requested_rate); if (*sdm == SDM_DEN) { *sdm = 0; div += 1; } if (div < N2_MIN) { *n2 = N2_MIN; *sdm = 0; } else if (div > N2_MAX) { *n2 = N2_MAX; *sdm = SDM_DEN - 1; } else { *n2 = div; } } static unsigned long mpll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk); unsigned int sdm, n2; long rate; sdm = meson_parm_read(clk->map, &mpll->sdm); n2 = meson_parm_read(clk->map, &mpll->n2); rate = rate_from_params(parent_rate, sdm, n2); return rate < 0 ? 0 : rate; } static long mpll_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk); unsigned int sdm, n2; params_from_rate(rate, *parent_rate, &sdm, &n2, mpll->flags); return rate_from_params(*parent_rate, sdm, n2); } static int mpll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk); unsigned int sdm, n2; unsigned long flags = 0; params_from_rate(rate, parent_rate, &sdm, &n2, mpll->flags); if (mpll->lock) spin_lock_irqsave(mpll->lock, flags); else __acquire(mpll->lock); /* Set the fractional part */ meson_parm_write(clk->map, &mpll->sdm, sdm); /* Set the integer divider part */ meson_parm_write(clk->map, &mpll->n2, n2); if (mpll->lock) spin_unlock_irqrestore(mpll->lock, flags); else __release(mpll->lock); return 0; } static int mpll_init(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_mpll_data *mpll = meson_clk_mpll_data(clk); if (mpll->init_count) regmap_multi_reg_write(clk->map, mpll->init_regs, mpll->init_count); /* Enable the fractional part */ meson_parm_write(clk->map, &mpll->sdm_en, 1); /* Set spread spectrum if possible */ if (MESON_PARM_APPLICABLE(&mpll->ssen)) { unsigned int ss = mpll->flags & CLK_MESON_MPLL_SPREAD_SPECTRUM ? 1 : 0; meson_parm_write(clk->map, &mpll->ssen, ss); } /* Set the magic misc bit if required */ if (MESON_PARM_APPLICABLE(&mpll->misc)) meson_parm_write(clk->map, &mpll->misc, 1); return 0; } const struct clk_ops meson_clk_mpll_ro_ops = { .recalc_rate = mpll_recalc_rate, .round_rate = mpll_round_rate, }; EXPORT_SYMBOL_GPL(meson_clk_mpll_ro_ops); const struct clk_ops meson_clk_mpll_ops = { .recalc_rate = mpll_recalc_rate, .round_rate = mpll_round_rate, .set_rate = mpll_set_rate, .init = mpll_init, }; EXPORT_SYMBOL_GPL(meson_clk_mpll_ops); MODULE_DESCRIPTION("Amlogic MPLL driver"); MODULE_AUTHOR("Michael Turquette <mturquette@baylibre.com>"); MODULE_LICENSE("GPL v2"); |