mirror of https://gitee.com/openkylin/linux.git
clk: axi-clkgen: Add support for fractional dividers
The axi-clkgen has (optional) fractional dividers on the output clock divider and feedback clock divider path. Utilizing the fractional dividers allows for a better resolution of the output clock, being able to synthesize more frequencies. Rework the driver support to support the fractional register fields, both for setting a new rate as well as reading back the current rate from the hardware. For setting the rate if no perfect divider settings were found in non-fractional mode try again in fractional mode and see if better settings can be found. This appears to be the recommended mode of operation. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com> Link: https://lore.kernel.org/r/20201001085948.21412-1-alexandru.ardelean@analog.com Signed-off-by: Stephen Boyd <sboyd@kernel.org>
This commit is contained in:
parent
9123e3a74e
commit
86378cf646
|
@ -27,8 +27,10 @@
|
|||
|
||||
#define AXI_CLKGEN_V2_DRP_STATUS_BUSY BIT(16)
|
||||
|
||||
#define MMCM_REG_CLKOUT5_2 0x07
|
||||
#define MMCM_REG_CLKOUT0_1 0x08
|
||||
#define MMCM_REG_CLKOUT0_2 0x09
|
||||
#define MMCM_REG_CLKOUT6_2 0x13
|
||||
#define MMCM_REG_CLK_FB1 0x14
|
||||
#define MMCM_REG_CLK_FB2 0x15
|
||||
#define MMCM_REG_CLK_DIV 0x16
|
||||
|
@ -40,6 +42,7 @@
|
|||
|
||||
#define MMCM_CLKOUT_NOCOUNT BIT(6)
|
||||
|
||||
#define MMCM_CLK_DIV_DIVIDE BIT(11)
|
||||
#define MMCM_CLK_DIV_NOCOUNT BIT(12)
|
||||
|
||||
struct axi_clkgen {
|
||||
|
@ -107,6 +110,8 @@ static void axi_clkgen_calc_params(unsigned long fin, unsigned long fout,
|
|||
unsigned long d, d_min, d_max, _d_min, _d_max;
|
||||
unsigned long m, m_min, m_max;
|
||||
unsigned long f, dout, best_f, fvco;
|
||||
unsigned long fract_shift = 0;
|
||||
unsigned long fvco_min_fract, fvco_max_fract;
|
||||
|
||||
fin /= 1000;
|
||||
fout /= 1000;
|
||||
|
@ -119,42 +124,89 @@ static void axi_clkgen_calc_params(unsigned long fin, unsigned long fout,
|
|||
d_min = max_t(unsigned long, DIV_ROUND_UP(fin, fpfd_max), 1);
|
||||
d_max = min_t(unsigned long, fin / fpfd_min, 80);
|
||||
|
||||
m_min = max_t(unsigned long, DIV_ROUND_UP(fvco_min, fin) * d_min, 1);
|
||||
m_max = min_t(unsigned long, fvco_max * d_max / fin, 64);
|
||||
again:
|
||||
fvco_min_fract = fvco_min << fract_shift;
|
||||
fvco_max_fract = fvco_max << fract_shift;
|
||||
|
||||
m_min = max_t(unsigned long, DIV_ROUND_UP(fvco_min_fract, fin) * d_min, 1);
|
||||
m_max = min_t(unsigned long, fvco_max_fract * d_max / fin, 64 << fract_shift);
|
||||
|
||||
for (m = m_min; m <= m_max; m++) {
|
||||
_d_min = max(d_min, DIV_ROUND_UP(fin * m, fvco_max));
|
||||
_d_max = min(d_max, fin * m / fvco_min);
|
||||
_d_min = max(d_min, DIV_ROUND_UP(fin * m, fvco_max_fract));
|
||||
_d_max = min(d_max, fin * m / fvco_min_fract);
|
||||
|
||||
for (d = _d_min; d <= _d_max; d++) {
|
||||
fvco = fin * m / d;
|
||||
|
||||
dout = DIV_ROUND_CLOSEST(fvco, fout);
|
||||
dout = clamp_t(unsigned long, dout, 1, 128);
|
||||
dout = clamp_t(unsigned long, dout, 1, 128 << fract_shift);
|
||||
f = fvco / dout;
|
||||
if (abs(f - fout) < abs(best_f - fout)) {
|
||||
best_f = f;
|
||||
*best_d = d;
|
||||
*best_m = m;
|
||||
*best_dout = dout;
|
||||
*best_m = m << (3 - fract_shift);
|
||||
*best_dout = dout << (3 - fract_shift);
|
||||
if (best_f == fout)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Lets see if we find a better setting in fractional mode */
|
||||
if (fract_shift == 0) {
|
||||
fract_shift = 3;
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
|
||||
static void axi_clkgen_calc_clk_params(unsigned int divider, unsigned int *low,
|
||||
unsigned int *high, unsigned int *edge, unsigned int *nocount)
|
||||
{
|
||||
if (divider == 1)
|
||||
*nocount = 1;
|
||||
else
|
||||
*nocount = 0;
|
||||
struct axi_clkgen_div_params {
|
||||
unsigned int low;
|
||||
unsigned int high;
|
||||
unsigned int edge;
|
||||
unsigned int nocount;
|
||||
unsigned int frac_en;
|
||||
unsigned int frac;
|
||||
unsigned int frac_wf_f;
|
||||
unsigned int frac_wf_r;
|
||||
unsigned int frac_phase;
|
||||
};
|
||||
|
||||
*high = divider / 2;
|
||||
*edge = divider % 2;
|
||||
*low = divider - *high;
|
||||
static void axi_clkgen_calc_clk_params(unsigned int divider,
|
||||
unsigned int frac_divider, struct axi_clkgen_div_params *params)
|
||||
{
|
||||
|
||||
memset(params, 0x0, sizeof(*params));
|
||||
|
||||
if (divider == 1) {
|
||||
params->nocount = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (frac_divider == 0) {
|
||||
params->high = divider / 2;
|
||||
params->edge = divider % 2;
|
||||
params->low = divider - params->high;
|
||||
} else {
|
||||
params->frac_en = 1;
|
||||
params->frac = frac_divider;
|
||||
|
||||
params->high = divider / 2;
|
||||
params->edge = divider % 2;
|
||||
params->low = params->high;
|
||||
|
||||
if (params->edge == 0) {
|
||||
params->high--;
|
||||
params->frac_wf_r = 1;
|
||||
}
|
||||
|
||||
if (params->edge == 0 || frac_divider == 1)
|
||||
params->low--;
|
||||
if (((params->edge == 0) ^ (frac_divider == 1)) ||
|
||||
(divider == 2 && frac_divider == 1))
|
||||
params->frac_wf_f = 1;
|
||||
|
||||
params->frac_phase = params->edge * 4 + frac_divider / 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void axi_clkgen_write(struct axi_clkgen *axi_clkgen,
|
||||
|
@ -246,15 +298,28 @@ static struct axi_clkgen *clk_hw_to_axi_clkgen(struct clk_hw *clk_hw)
|
|||
return container_of(clk_hw, struct axi_clkgen, clk_hw);
|
||||
}
|
||||
|
||||
static void axi_clkgen_set_div(struct axi_clkgen *axi_clkgen,
|
||||
unsigned int reg1, unsigned int reg2, unsigned int reg3,
|
||||
struct axi_clkgen_div_params *params)
|
||||
{
|
||||
axi_clkgen_mmcm_write(axi_clkgen, reg1,
|
||||
(params->high << 6) | params->low, 0xefff);
|
||||
axi_clkgen_mmcm_write(axi_clkgen, reg2,
|
||||
(params->frac << 12) | (params->frac_en << 11) |
|
||||
(params->frac_wf_r << 10) | (params->edge << 7) |
|
||||
(params->nocount << 6), 0x7fff);
|
||||
if (reg3 != 0) {
|
||||
axi_clkgen_mmcm_write(axi_clkgen, reg3,
|
||||
(params->frac_phase << 11) | (params->frac_wf_f << 10), 0x3c00);
|
||||
}
|
||||
}
|
||||
|
||||
static int axi_clkgen_set_rate(struct clk_hw *clk_hw,
|
||||
unsigned long rate, unsigned long parent_rate)
|
||||
{
|
||||
struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);
|
||||
unsigned int d, m, dout;
|
||||
unsigned int nocount;
|
||||
unsigned int high;
|
||||
unsigned int edge;
|
||||
unsigned int low;
|
||||
struct axi_clkgen_div_params params;
|
||||
uint32_t filter;
|
||||
uint32_t lock;
|
||||
|
||||
|
@ -269,21 +334,18 @@ static int axi_clkgen_set_rate(struct clk_hw *clk_hw,
|
|||
filter = axi_clkgen_lookup_filter(m - 1);
|
||||
lock = axi_clkgen_lookup_lock(m - 1);
|
||||
|
||||
axi_clkgen_calc_clk_params(dout, &low, &high, &edge, &nocount);
|
||||
axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_CLKOUT0_1,
|
||||
(high << 6) | low, 0xefff);
|
||||
axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_CLKOUT0_2,
|
||||
(edge << 7) | (nocount << 6), 0x03ff);
|
||||
axi_clkgen_calc_clk_params(dout >> 3, dout & 0x7, ¶ms);
|
||||
axi_clkgen_set_div(axi_clkgen, MMCM_REG_CLKOUT0_1, MMCM_REG_CLKOUT0_2,
|
||||
MMCM_REG_CLKOUT5_2, ¶ms);
|
||||
|
||||
axi_clkgen_calc_clk_params(d, &low, &high, &edge, &nocount);
|
||||
axi_clkgen_calc_clk_params(d, 0, ¶ms);
|
||||
axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_CLK_DIV,
|
||||
(edge << 13) | (nocount << 12) | (high << 6) | low, 0x3fff);
|
||||
(params.edge << 13) | (params.nocount << 12) |
|
||||
(params.high << 6) | params.low, 0x3fff);
|
||||
|
||||
axi_clkgen_calc_clk_params(m, &low, &high, &edge, &nocount);
|
||||
axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_CLK_FB1,
|
||||
(high << 6) | low, 0xefff);
|
||||
axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_CLK_FB2,
|
||||
(edge << 7) | (nocount << 6), 0x03ff);
|
||||
axi_clkgen_calc_clk_params(m >> 3, m & 0x7, ¶ms);
|
||||
axi_clkgen_set_div(axi_clkgen, MMCM_REG_CLK_FB1, MMCM_REG_CLK_FB2,
|
||||
MMCM_REG_CLKOUT6_2, ¶ms);
|
||||
|
||||
axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK1, lock & 0x3ff, 0x3ff);
|
||||
axi_clkgen_mmcm_write(axi_clkgen, MMCM_REG_LOCK2,
|
||||
|
@ -313,35 +375,51 @@ static long axi_clkgen_round_rate(struct clk_hw *hw, unsigned long rate,
|
|||
return min_t(unsigned long long, tmp, LONG_MAX);
|
||||
}
|
||||
|
||||
static unsigned int axi_clkgen_get_div(struct axi_clkgen *axi_clkgen,
|
||||
unsigned int reg1, unsigned int reg2)
|
||||
{
|
||||
unsigned int val1, val2;
|
||||
unsigned int div;
|
||||
|
||||
axi_clkgen_mmcm_read(axi_clkgen, reg2, &val2);
|
||||
if (val2 & MMCM_CLKOUT_NOCOUNT)
|
||||
return 8;
|
||||
|
||||
axi_clkgen_mmcm_read(axi_clkgen, reg1, &val1);
|
||||
|
||||
div = (val1 & 0x3f) + ((val1 >> 6) & 0x3f);
|
||||
div <<= 3;
|
||||
|
||||
if (val2 & MMCM_CLK_DIV_DIVIDE) {
|
||||
if ((val2 & BIT(7)) && (val2 & 0x7000) != 0x1000)
|
||||
div += 8;
|
||||
else
|
||||
div += 16;
|
||||
|
||||
div += (val2 >> 12) & 0x7;
|
||||
}
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
static unsigned long axi_clkgen_recalc_rate(struct clk_hw *clk_hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct axi_clkgen *axi_clkgen = clk_hw_to_axi_clkgen(clk_hw);
|
||||
unsigned int d, m, dout;
|
||||
unsigned int reg;
|
||||
unsigned long long tmp;
|
||||
unsigned int val;
|
||||
|
||||
axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLKOUT0_2, ®);
|
||||
if (reg & MMCM_CLKOUT_NOCOUNT) {
|
||||
dout = 1;
|
||||
} else {
|
||||
axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLKOUT0_1, ®);
|
||||
dout = (reg & 0x3f) + ((reg >> 6) & 0x3f);
|
||||
}
|
||||
dout = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLKOUT0_1,
|
||||
MMCM_REG_CLKOUT0_2);
|
||||
m = axi_clkgen_get_div(axi_clkgen, MMCM_REG_CLK_FB1,
|
||||
MMCM_REG_CLK_FB2);
|
||||
|
||||
axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLK_DIV, ®);
|
||||
if (reg & MMCM_CLK_DIV_NOCOUNT)
|
||||
axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLK_DIV, &val);
|
||||
if (val & MMCM_CLK_DIV_NOCOUNT)
|
||||
d = 1;
|
||||
else
|
||||
d = (reg & 0x3f) + ((reg >> 6) & 0x3f);
|
||||
|
||||
axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLK_FB2, ®);
|
||||
if (reg & MMCM_CLKOUT_NOCOUNT) {
|
||||
m = 1;
|
||||
} else {
|
||||
axi_clkgen_mmcm_read(axi_clkgen, MMCM_REG_CLK_FB1, ®);
|
||||
m = (reg & 0x3f) + ((reg >> 6) & 0x3f);
|
||||
}
|
||||
d = (val & 0x3f) + ((val >> 6) & 0x3f);
|
||||
|
||||
if (d == 0 || dout == 0)
|
||||
return 0;
|
||||
|
|
Loading…
Reference in New Issue