diff options
Diffstat (limited to 'drivers/clk/si5324drv.c')
-rw-r--r-- | drivers/clk/si5324drv.c | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/drivers/clk/si5324drv.c b/drivers/clk/si5324drv.c new file mode 100644 index 000000000000..5c064a329e73 --- /dev/null +++ b/drivers/clk/si5324drv.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Si5324 clock driver + * + * Copyright (C) 2017-2018 Xilinx, Inc. + * + * Author: Venkateshwar Rao G <vgannava.xilinx.com> + * Leon Woestenberg <leon@sidebranch.com> + */ + +#include <linux/module.h> +#include <linux/types.h> +#include "si5324drv.h" + +/** + * si5324_rate_approx - Find closest rational approximation N2_LS/N3 fraction. + * + * @f: Holds the N2_LS/N3 fraction in 36.28 fixed point notation. + * @md: Holds the maximum denominator (N3) value allowed. + * @num: Store the numinator (N2_LS) found. + * @denom: Store the denominator (N3) found. + * + * This function finds the closest rational approximation. + * It allows only n/1 solution and as a part of the calculation + * multiply fraction until no digits after the decimal point and + * continued fraction and check denominator at each step. + */ +void si5324_rate_approx(u64 f, u64 md, u32 *num, u32 *denom) +{ + u64 a, h[3] = { 0, 1, 0 }, k[3] = { 1, 0, 0 }; + u64 x, d, m, n = 1; + int i = 0; + + if (md <= 1) { + *denom = 1; + *num = (u32)(f >> 28); + return; + } + + n <<= 28; + for (i = 0; i < 28; i++) { + if ((f & 0x1) == 0) { + n >>= 1; + f >>= 1; + } else { + break; + } + } + d = f; + + for (i = 0; i < 64; i++) { + a = n ? (div64_u64(d, n)) : 0; + if (i && !a) + break; + x = d; + d = n; + div64_u64_rem(x, n, &m); + n = m; + x = a; + if (k[1] * a + k[0] >= md) { + x = div64_u64((md - k[0]), k[1]); + if (x * 2 >= a || k[1] >= md) + i = 65; + else + break; + } + h[2] = x * h[1] + h[0]; + h[0] = h[1]; + h[1] = h[2]; + k[2] = x * k[1] + k[0]; + k[0] = k[1]; + k[1] = k[2]; + } + + *denom = (u32)k[1]; + *num = (u32)h[1]; +} + +/** + * si5324_find_n2ls - Search through the possible settings for the N2_LS. + * + * @settings: Holds the settings up till now. + * + * This function finds the best setting for N2_LS and N3n with the values + * for N1_HS, NCn_LS, and N2_HS. + * + * Return: 1 when the best possible result has been found, 0 on failure. + */ +static int si5324_find_n2ls(struct si5324_settingst *settings) +{ + u32 result = 0; + u64 f3_actual; + u64 fosc_actual; + u64 fout_actual; + u64 delta_fout; + u64 n2_ls_div_n3, mult_res; + u32 mult; + + n2_ls_div_n3 = div64_u64(div64_u64(div64_u64(settings->fosc, + (settings->fin >> SI5324_FIN_FOUT_SHIFT)), + (u64)settings->n2_hs), (u64)2); + + si5324_rate_approx(n2_ls_div_n3, settings->n31_max, &settings->n2_ls, + &settings->n31); + settings->n2_ls *= 2; + + if (settings->n2_ls < settings->n2_ls_min) { + mult = div64_u64(settings->n2_ls_min, settings->n2_ls); + div64_u64_rem(settings->n2_ls_min, settings->n2_ls, &mult_res); + mult = mult_res ? mult + 1 : mult; + settings->n2_ls *= mult; + settings->n31 *= mult; + } + + if (settings->n31 < settings->n31_min) { + mult = div64_u64(settings->n31_min, settings->n31); + div64_u64_rem(settings->n31_min, settings->n31, &mult_res); + mult = mult_res ? mult + 1 : mult; + settings->n2_ls *= mult; + settings->n31 *= mult; + } + pr_debug("Trying N2_LS = %d N3 = %d.\n", settings->n2_ls, + settings->n31); + + if (settings->n2_ls < settings->n2_ls_min || + settings->n2_ls > settings->n2_ls_max) { + pr_info("N2_LS out of range.\n"); + } else if ((settings->n31 < settings->n31_min) || + (settings->n31 > settings->n31_max)) { + pr_info("N3 out of range.\n"); + } else { + f3_actual = div64_u64(settings->fin, settings->n31); + fosc_actual = f3_actual * settings->n2_hs * settings->n2_ls; + fout_actual = div64_u64(fosc_actual, + (settings->n1_hs * settings->nc1_ls)); + delta_fout = fout_actual - settings->fout; + + if ((f3_actual < ((u64)SI5324_F3_MIN) << + SI5324_FIN_FOUT_SHIFT) || + (f3_actual > ((u64)SI5324_F3_MAX) << + SI5324_FIN_FOUT_SHIFT)) { + pr_debug("F3 frequency out of range.\n"); + } else if ((fosc_actual < ((u64)SI5324_FOSC_MIN) << + SI5324_FIN_FOUT_SHIFT) || + (fosc_actual > ((u64)SI5324_FOSC_MAX) << + SI5324_FIN_FOUT_SHIFT)) { + pr_debug("Fosc frequency out of range.\n"); + } else if ((fout_actual < ((u64)SI5324_FOUT_MIN) << + SI5324_FIN_FOUT_SHIFT) || + (fout_actual > ((u64)SI5324_FOUT_MAX) << + SI5324_FIN_FOUT_SHIFT)) { + pr_debug("Fout frequency out of range.\n"); + } else { + pr_debug("Found solution: fout = %dHz delta = %dHz.\n", + (u32)(fout_actual >> SI5324_FIN_FOUT_SHIFT), + (u32)(delta_fout >> SI5324_FIN_FOUT_SHIFT)); + pr_debug("fosc = %dkHz f3 = %dHz.\n", + (u32)((fosc_actual >> SI5324_FIN_FOUT_SHIFT) / + 1000), + (u32)(f3_actual >> SI5324_FIN_FOUT_SHIFT)); + + if (((u64)abs(delta_fout)) < + settings->best_delta_fout) { + settings->best_n1_hs = settings->n1_hs; + settings->best_nc1_ls = settings->nc1_ls; + settings->best_n2_hs = settings->n2_hs; + settings->best_n2_ls = settings->n2_ls; + settings->best_n3 = settings->n31; + settings->best_fout = fout_actual; + settings->best_delta_fout = abs(delta_fout); + if (delta_fout == 0) + result = 1; + } + } + } + return result; +} + +/** + * si5324_find_n2 - Find a valid setting for N2_HS and N2_LS. + * + * @settings: Holds the settings up till now. + * + * This function finds a valid settings for N2_HS and N2_LS. Iterates over + * all possibilities of N2_HS and then performs a binary search over the + * N2_LS values. + * + * Return: 1 when the best possible result has been found. + */ +static int si5324_find_n2(struct si5324_settingst *settings) +{ + u32 result = 0; + + for (settings->n2_hs = SI5324_N2_HS_MAX; settings->n2_hs >= + SI5324_N2_HS_MIN; settings->n2_hs--) { + pr_debug("Trying N2_HS = %d.\n", settings->n2_hs); + settings->n2_ls_min = (u32)(div64_u64(settings->fosc, + ((u64)(SI5324_F3_MAX * settings->n2_hs) + << SI5324_FIN_FOUT_SHIFT))); + + if (settings->n2_ls_min < SI5324_N2_LS_MIN) + settings->n2_ls_min = SI5324_N2_LS_MIN; + + settings->n2_ls_max = (u32)(div64_u64(settings->fosc, + ((u64)(SI5324_F3_MIN * + settings->n2_hs) << + SI5324_FIN_FOUT_SHIFT))); + if (settings->n2_ls_max > SI5324_N2_LS_MAX) + settings->n2_ls_max = SI5324_N2_LS_MAX; + + result = si5324_find_n2ls(settings); + if (result) + break; + } + return result; +} + +/** + * si5324_calc_ncls_limits - Calculates the valid range for NCn_LS. + * + * @settings: Holds the input and output frequencies and the setting + * for N1_HS. + * + * This function calculates the valid range for NCn_LS with the value + * for the output frequency and N1_HS already set in settings. + * + * Return: -1 when there are no valid settings, 0 otherwise. + */ +int si5324_calc_ncls_limits(struct si5324_settingst *settings) +{ + settings->nc1_ls_min = div64_u64(settings->n1_hs_min, + settings->n1_hs); + + if (settings->nc1_ls_min < SI5324_NC_LS_MIN) + settings->nc1_ls_min = SI5324_NC_LS_MIN; + if (settings->nc1_ls_min > 1 && (settings->nc1_ls_min & 0x1) == 1) + settings->nc1_ls_min++; + settings->nc1_ls_max = div64_u64(settings->n1_hs_max, settings->n1_hs); + + if (settings->nc1_ls_max > SI5324_NC_LS_MAX) + settings->nc1_ls_max = SI5324_NC_LS_MAX; + + if ((settings->nc1_ls_max & 0x1) == 1) + settings->nc1_ls_max--; + if ((settings->nc1_ls_max * settings->n1_hs < settings->n1_hs_min) || + (settings->nc1_ls_min * settings->n1_hs > settings->n1_hs_max)) + return -1; + + return 0; +} + +/** + * si5324_find_ncls - Find a valid setting for NCn_LS + * + * @settings: Holds the input and output frequencies, the setting for + * N1_HS, and the limits for NCn_LS. + * + * This function find a valid setting for NCn_LS that can deliver the correct + * output frequency. Assumes that the valid range is relatively small + * so a full search can be done (should be true for video clock frequencies). + * + * Return: 1 when the best possible result has been found. + */ +static int si5324_find_ncls(struct si5324_settingst *settings) +{ + u64 fosc_1; + u32 result; + + fosc_1 = settings->fout * settings->n1_hs; + for (settings->nc1_ls = settings->nc1_ls_min; + settings->nc1_ls <= settings->nc1_ls_max;) { + settings->fosc = fosc_1 * settings->nc1_ls; + pr_debug("Trying NCn_LS = %d: fosc = %dkHz.\n", + settings->nc1_ls, + (u32)(div64_u64((settings->fosc >> + SI5324_FIN_FOUT_SHIFT), 1000))); + + result = si5324_find_n2(settings); + if (result) + break; + if (settings->nc1_ls == 1) + settings->nc1_ls++; + else + settings->nc1_ls += 2; + } + return result; +} + +/** + * si5324_calcfreqsettings - Calculate the frequency settings + * + * @clkinfreq: Frequency of the input clock. + * @clkoutfreq: Desired output clock frequency. + * @clkactual: Actual clock frequency. + * @n1_hs: Set to the value for the N1_HS register. + * @ncn_ls: Set to the value for the NCn_LS register. + * @n2_hs: Set to the value for the N2_HS register. + * @n2_ls: Set to the value for the N2_LS register. + * @n3n: Set to the value for the N3n register. + * @bwsel: Set to the value for the BW_SEL register. + * + * This funciton calculates the frequency settings for the desired output + * frequency. + * + * Return: SI5324_SUCCESS for success, SI5324_ERR_FREQ when the + * requested frequency cannot be generated. + */ +int si5324_calcfreqsettings(u32 clkinfreq, u32 clkoutfreq, u32 *clkactual, + u8 *n1_hs, u32 *ncn_ls, u8 *n2_hs, u32 *n2_ls, + u32 *n3n, u8 *bwsel) +{ + struct si5324_settingst settings; + int result; + + settings.fin = (u64)clkinfreq << SI5324_FIN_FOUT_SHIFT; + settings.fout = (u64)clkoutfreq << SI5324_FIN_FOUT_SHIFT; + settings.best_delta_fout = settings.fout; + + settings.n1_hs_min = (int)(div64_u64(SI5324_FOSC_MIN, clkoutfreq)); + if (settings.n1_hs_min < SI5324_N1_HS_MIN * SI5324_NC_LS_MIN) + settings.n1_hs_min = SI5324_N1_HS_MIN * SI5324_NC_LS_MIN; + + settings.n1_hs_max = (int)(div64_u64(SI5324_FOSC_MAX, clkoutfreq)); + if (settings.n1_hs_max > SI5324_N1_HS_MAX * SI5324_NC_LS_MAX) + settings.n1_hs_max = SI5324_N1_HS_MAX * SI5324_NC_LS_MAX; + + settings.n31_min = div64_u64(clkinfreq, SI5324_F3_MAX); + if (settings.n31_min < SI5324_N3_MIN) + settings.n31_min = SI5324_N3_MIN; + + settings.n31_max = div64_u64(clkinfreq, SI5324_F3_MIN); + if (settings.n31_max > SI5324_N3_MAX) + settings.n31_max = SI5324_N3_MAX; + + /* Find a valid oscillator frequency with the highest setting of N1_HS + * possible (reduces power) + */ + for (settings.n1_hs = SI5324_N1_HS_MAX; + settings.n1_hs >= SI5324_N1_HS_MIN; settings.n1_hs--) { + pr_debug("Trying N1_HS = %d.\n", settings.n1_hs); + + result = si5324_calc_ncls_limits(&settings); + if (result) { + pr_debug("No valid settings\n"); + continue; + } + result = si5324_find_ncls(&settings); + if (result) + break; + } + + pr_debug("Si5324: settings.best_delta_fout = %llu\n", + (unsigned long long)settings.best_delta_fout); + pr_debug("Si5324: settings.fout = %llu\n", + (unsigned long long)settings.fout); + + if (settings.best_delta_fout == settings.fout) { + pr_debug("Si5324: No valid settings found."); + return SI5324_ERR_FREQ; + } + pr_debug("Si5324: Found solution: fout = %dHz.\n", + (u32)(settings.best_fout >> 28)); + + /* Post processing: convert temporary values to actual registers */ + *n1_hs = (u8)settings.best_n1_hs - 4; + *ncn_ls = settings.best_nc1_ls - 1; + *n2_hs = (u8)settings.best_n2_hs - 4; + *n2_ls = settings.best_n2_ls - 1; + *n3n = settings.best_n3 - 1; + /* + * How must the bandwidth selection be determined? + * Not all settings will be valid. + * refclk 2, 0xA2, BWSEL_REG=1010 (?) + * free running 2, 0x42, BWSEL_REG=0100 (?) + */ + *bwsel = 6; + + if (clkactual) + *clkactual = (settings.best_fout >> SI5324_FIN_FOUT_SHIFT); + + return SI5324_SUCCESS; +} |