diff --git a/cvorg/ad9516/ad9516.c b/cvorg/ad9516/ad9516.c index c62eccbdc42bb3d37d4260a79de22bf20b078ee5..b2cd8719a01370b1933e9cf34106039315ed4e06 100644 --- a/cvorg/ad9516/ad9516.c +++ b/cvorg/ad9516/ad9516.c @@ -1,4 +1,5 @@ #include <math.h> +#include <stdio.h> #include <ad9516.h> @@ -13,6 +14,9 @@ static int ad9516_get_dividers(int freq, struct ad9516_pll *pll) double mindiff = 1e10; int dvco, d1, d2; int i, j, k; + double f_vco; + + f_vco = ((double)pll->input_freq/pll->r)*((double)pll->p * pll->b + pll->a); /* * Note: this loop is sub-optimal, since sometimes combinations @@ -22,7 +26,7 @@ static int ad9516_get_dividers(int freq, struct ad9516_pll *pll) for (i = 2; i <= 6; i++) { for (j = 1; j <= 32; j++) { for (k = j; k <= 32; k++) { - diff = (double)AD9516_VCO_FREQ / i / j / k - freq; + diff = f_vco / i / j / k - freq; if (fabs(diff) <= (double)AD9516_MAXDIFF) goto found; if (fabs(diff) < fabs(mindiff)) { @@ -50,15 +54,19 @@ found: * Here we consider the dividers fixed, and play with N, R to calculate * a satisfactory f_vco, given a certain required output frequency @freq */ -static void ad9516_calc_pll_params(int freq, struct ad9516_pll *pll) +static void __ad9516_calc_pll_params(int freq, struct ad9516_pll *pll, unsigned int ext_clk_freq, int ext_clk) { double n, nr; + double clk = AD9516_OSCILLATOR_FREQ; /* * What's the required N / R ? * N / R = Dividers * freq / fref, where Dividers = dvco * d1 * d1 * and fref = AD9516_OSCILLATOR_FREQ */ - nr = (double)pll->dvco * pll->d1 * pll->d2 * freq / AD9516_OSCILLATOR_FREQ; + if (ext_clk) + clk = ext_clk_freq; + + nr = (double)pll->dvco * pll->d1 * pll->d2 * freq / clk; n = nr * pll->r; /* @@ -66,36 +74,126 @@ static void ad9516_calc_pll_params(int freq, struct ad9516_pll *pll) */ pll->b = ((int)n) / pll->p; pll->a = ((int)n) % pll->p; + } -static int ad9516_check_pll(struct ad9516_pll *pll) + +static void ad9516_calc_pll_params(int freq, struct ad9516_pll *pll) +{ + __ad9516_calc_pll_params(freq, pll, 0, 0); +} + +static void ad9516_calc_pll_params_ext_clk(int freq, struct ad9516_pll *pll, unsigned int ext_clk_freq) +{ + pll->input_freq = ext_clk_freq; + __ad9516_calc_pll_params(freq, pll, ext_clk_freq, 1); +} + +static int ad9516_check_pll(struct ad9516_pll *pll, unsigned int ext_clk_freq, int ext_clk) { int err = 0; - double fvco = (double)AD9516_OSCILLATOR_FREQ * - (pll->p * pll->b + pll->a) / pll->r; + double clk = AD9516_OSCILLATOR_FREQ; + double fvco; - if (pll->a > pll->b) + if (ext_clk) + clk = ext_clk_freq; + + fvco = clk * (pll->p * pll->b + pll->a) / pll->r; + + if (pll->a > pll->b) { err = -1; + } - if (pll->a > 63) + if (pll->a > 63) { err = -1; + } - if (pll->p != 16 && pll->p != 32) + /* valid values are powers of two in 2..32 range) */ + if (pll->p < 2 || pll->p > 32 || (pll->p & (pll->p -1))) { err = -1; + } - if (pll->b <= 0 || pll->b > 8191) + if (pll->b <= 0 || pll->b > 8191) { err = -1; + } - if (pll->r <= 0 || pll->r > 16383) + if (pll->r <= 0 || pll->r > 16383) { err = -1; + } - if (fvco < 2.55e9 || fvco > 2.95e9) + if (fvco < 2.55e9 || fvco > 2.95e9) { err = -1; + } return err; } -int ad9516_fill_pll_conf(unsigned int freq, struct ad9516_pll *pll) +int ad9516_get_fvco(unsigned int freq, struct ad9516_pll * pll, unsigned int ext_clk_freq) +{ + long int f_vco = 0; + int a = 0; + int b = 1; + int p[5] = {32, 16, 8, 4, 2}; + int r; + int i; + double alpha, beta; + + /* + * Using r = 100 the maximum is 7.5e9 Hz that is beyond the maximum unsigned int value. + * Just in case, the comparation is maintained. + */ + if (ext_clk_freq < 1e6 || ext_clk_freq > 7.5e9) + return -1; + + alpha = AD9516_VCO_FREQ_MIN / ext_clk_freq; + beta = AD9516_VCO_FREQ_MAX / ext_clk_freq; + + // To be sure for getting valid values, the constant is 2.0 instead of 1.0 + //r = ceil(2.0/(beta - alpha)); + //if (r <= 0) + // return -1; + + /* + * Use of this constant value (r = 100) to allow the PLL to lock with the external clock. + * The frequency used by the client is 10 MHz. This constant allows to give + * proper values for the rest of parameters. + */ + r = 100; + + f_vco = ceil(r*alpha); + + if (f_vco < r*alpha || f_vco > r*beta) + return -1; + + // Search the values of p, a, b. + for (i = 0; i < 5; i++) { + b = f_vco / p[i]; + a = f_vco % p[i]; + + if (b <= 0 || b >= 8192) + continue; + if (a == 0 || (b > a && b > 3)) + goto found; + + } + + // Not found proper parameters, give error + return -1; + +found : + pll->a = a; + pll->b = b; + pll->p = p[i]; + pll->r = r; + pll->dvco = 1; + pll->d1 = 1; + pll->d2 = 1; + pll->external = 0; + + return 0; +} + +int __ad9516_fill_pll_conf(unsigned int freq, struct ad9516_pll *pll, unsigned int ext_clk_freq, int ext_clk) { if (!freq) { pll->a = 0; @@ -110,20 +208,31 @@ int ad9516_fill_pll_conf(unsigned int freq, struct ad9516_pll *pll) } /* initial sanity check */ - if (freq < AD9516_MINFREQ) + if (freq < AD9516_MINFREQ){ return -1; + } /* * Algorithm for adjusting the PLL - * 1. set f_vco = 2.8 GHz - * N = (p * b) + a -> N = 70000 - * f_vco = f_ref * N / R = 40e6 * 70000 = 2.8e9 Hz */ - pll->a = 0; - pll->b = 4375; - pll->p = 16; - pll->r = 1000; - pll->external = 0; + + if (!ext_clk) { + /* + * 1. set f_vco = 2.8 GHz + * N = (p * b) + a -> N = 70000 + * f_vco = f_ref * N / R = 40e6 * 70000 = 2.8e9 Hz + */ + pll->a = 0; + pll->b = 4375; + pll->p = 16; + pll->r = 1000; + pll->external = 0; + } else { + // Search for proper parameter values. + if (ad9516_get_fvco(freq, pll, ext_clk_freq)) { + return -1; + } + } /* * 2. check if playing with the dividers we can achieve the required @@ -135,10 +244,29 @@ int ad9516_fill_pll_conf(unsigned int freq, struct ad9516_pll *pll) * 2.1 Playing with the dividers is not enough: tune the VCO * by adjusting the relation N/R. */ - ad9516_calc_pll_params(freq, pll); + if (ext_clk) + ad9516_calc_pll_params_ext_clk(freq, pll, ext_clk_freq); + else + ad9516_calc_pll_params(freq, pll); } /* 3. Check the calculated PLL configuration is feasible */ - return ad9516_check_pll(pll); + return ad9516_check_pll(pll, ext_clk_freq, ext_clk); + } +int ad9516_fill_pll_conf(unsigned int freq, struct ad9516_pll *pll) +{ + pll->ext_clk_pll = 0; + pll->external = 0; + pll->input_freq = AD9516_OSCILLATOR_FREQ; + return __ad9516_fill_pll_conf(freq, pll, 0, 0); +} + +int ad9516_fill_pll_conf_ext_clk(unsigned int freq, struct ad9516_pll *pll, unsigned int ext_clk_freq) +{ + pll->ext_clk_pll = 1; + pll->external = 0; /* As we use the internal PLL this should be zero */ + pll->input_freq = ext_clk_freq; + return __ad9516_fill_pll_conf(freq, pll, ext_clk_freq, 1); +} diff --git a/cvorg/ad9516/libad9516.h b/cvorg/ad9516/libad9516.h index ff5a58655ad0709038b3078a7b4ea929ef78de8c..8d20d2d954ef1735dc2c380917e851d6c18e24a2 100644 --- a/cvorg/ad9516/libad9516.h +++ b/cvorg/ad9516/libad9516.h @@ -4,5 +4,5 @@ #include <ad9516.h> extern int ad9516_fill_pll_conf(unsigned int freq, struct ad9516_pll *pll); - +extern int ad9516_fill_pll_conf_ext_clk(unsigned int freq, struct ad9516_pll *pll, unsigned int ext_clk_freq); #endif /* _AD9516_LIB_H_ */ diff --git a/cvorg/driver/clkgen.c b/cvorg/driver/clkgen.c index 7ef5e01ff16ed5bb8c1ba756e0e4285413341bbf..257ccbf83af3caf6ed5455fa15381af3948d8dfd 100644 --- a/cvorg/driver/clkgen.c +++ b/cvorg/driver/clkgen.c @@ -48,8 +48,11 @@ static uint32_t clkgen_rval(struct cvorg *cvorg, unsigned int addr) { uint32_t val; int i; + int data; - cvorg_writew(cvorg, CVORG_CLKCTL, CVORG_AD9516_OP_READ | (addr << 8)); + data = cvorg_readw(cvorg, CVORG_CLKCTL) & 0xFF000000; + + cvorg_writew(cvorg, CVORG_CLKCTL, data | CVORG_AD9516_OP_READ | (addr << 8)); /* try a few times, although normally 5us should be enough */ for (i = 0; i < 3; i++) { @@ -203,7 +206,7 @@ static int calib_vco_sleep(struct cvorg *cvorg, int r, int freqref) tswait(NULL, SEM_SIGIGNORE, interval + 1); /* check for completion */ - return clkgen_rd(cvorg, AD9516_PLLREADBACK) & 0x40 ? 0 : -1; + return clkgen_rd(cvorg, AD9516_PLLREADBACK) & 0x40 ? 0 : -1; } static int clkgen_read_dvco(struct cvorg *cvorg) @@ -314,8 +317,6 @@ void get_pll_conf(struct cvorg *cvorg, struct ad9516_pll *pll) */ static void check_pll_dividers(struct ad9516_pll *pll) { - if (pll->r == 1) - pll->r = 0; if (pll->dvco == 1) pll->dvco = 0; @@ -366,7 +367,29 @@ static void clkgen_write_r(struct cvorg *cvorg, int r) static void clkgen_write_p(struct cvorg *cvorg, int p) { - int prescaler = p == 16 ? 0x5 : 0x6; /* see the module's docs */ + int prescaler; + + switch(p) { + case 2: + prescaler = 0x2; + break; + case 4: + prescaler = 0x3; + break; + + case 8: + prescaler = 0x4; + break; + case 16: + prescaler = 0x5; + break; + + case 32: + prescaler = 0x6; + break; + default: + prescaler = 0x6; + } /* reset P */ clkgen_and(cvorg, AD9516_PLL1, ~0x07); @@ -464,6 +487,7 @@ int clkgen_apply_pll_conf(struct cvorg *cvorg) { int ret = 0; struct ad9516_pll *pll = &cvorg->pll; + unsigned int data; check_pll_dividers(pll); @@ -489,7 +513,21 @@ int clkgen_apply_pll_conf(struct cvorg *cvorg) clkgen_wr(cvorg, AD9516_PLL3, 0x01); clkgen_wr(cvorg, AD9516_UPDATE_ALL, 0x01); - if (calib_vco_sleep(cvorg, pll->r, AD9516_OSCILLATOR_FREQ)) { + /* Use the external clock as input for the pll + * instead of the internal oscillator. + */ + + if (pll->ext_clk_pll) { + data = cvorg_readw(cvorg, CVORG_CLKCTL); + cvorg_writew(cvorg, CVORG_CLKCTL, data | (1 << 31)); + } else { + + data = cvorg_readw(cvorg, CVORG_CLKCTL); + cvorg_writew(cvorg, CVORG_CLKCTL, data & 0x7FFFFFFF); + } + + + if (calib_vco_sleep(cvorg, pll->r, pll->input_freq)) { SK_DEBUG("VCO calibration failed [PLL: A=%d B=%d P=%d " "R=%d d1=%d d2=%d dvco=%d]", pll->a, pll->b, pll->p, pll->r, pll->d1, pll->d2, pll->dvco); @@ -506,12 +544,16 @@ int clkgen_check_pll(struct ad9516_pll *pll) if (pll->a > pll->b) err = 1; + if (pll->a > 63) err = 1; - if (pll->p != 16 && pll->p != 32) + + if (pll->p != 4 && pll->p != 8 && pll->p != 16 && pll->p != 16 && pll->p != 32) err = 1; + if (pll->b <= 0 || pll->b > 8191) err = 1; + if (pll->r <= 0 || pll->r > 16383) err = 1; diff --git a/cvorg/driver/cvorg-skel.c b/cvorg/driver/cvorg-skel.c index 1830f2433f24eb9cdf6b4f340b613a44adb48ded..81dd6ca778e6376ae879bf9c6371275ff9a8f08e 100644 --- a/cvorg/driver/cvorg-skel.c +++ b/cvorg/driver/cvorg-skel.c @@ -351,6 +351,8 @@ static void cvorg_pll_cfg_init(struct ad9516_pll *pll) pll->d1 = 14; pll->d2 = 1; pll->external = 0; + pll->input_freq = AD9516_OSCILLATOR_FREQ; + pll->ext_clk_pll = 0; } #define CVORG_FREQ_DIV 10000 @@ -367,14 +369,18 @@ static int cvorg_poll_sampfreq(struct cvorg *cvorg) unsigned int n = pll->p * pll->b + pll->a; int32_t sampfreq; int32_t freq; + unsigned int input_freq = AD9516_OSCILLATOR_FREQ; int i; /* When external, we cannot possibly know the desired frequency */ if (pll->external) return 0; + if (pll->ext_clk_pll) + input_freq = pll->input_freq; + /* Note: some dividers may be bypassed (ie set to zero) */ - freq = AD9516_OSCILLATOR_FREQ / CVORG_FREQ_DIV * n; + freq = input_freq / CVORG_FREQ_DIV * n; if (pll->r) freq /= pll->r; if (pll->dvco) diff --git a/cvorg/include/ad9516.h b/cvorg/include/ad9516.h index 7205679e085ae06cff23a5050243135dcf8dcff5..cdfc27336ac568e1b0c5dc00971ef22e07a56ca8 100644 --- a/cvorg/include/ad9516.h +++ b/cvorg/include/ad9516.h @@ -1,9 +1,11 @@ #ifndef _AD9516_H_ #define _AD9516_H_ -#define AD9516_VCO_FREQ 2800000000UL +#define AD9516_VCO_FREQ 2.8e9L +#define AD9516_VCO_FREQ_MIN 2.55e9L +#define AD9516_VCO_FREQ_MAX 2.95e9L #define AD9516_MAXDIFF 0 -#define AD9516_OSCILLATOR_FREQ 40000000UL +#define AD9516_OSCILLATOR_FREQ 40e6L #define AD9516_MINFREQ 416000 @@ -17,6 +19,7 @@ * @d1 - [1,32] * @d2 - [1,32] * @external - set to 0 to use the internal PLL; 1 to use EXTCLK + * @ext_clk_pll - set to 0 to use to use the internal oscillator in the PLL; 1 to use EXTCLK in the PLL. */ struct ad9516_pll { int a; @@ -27,6 +30,8 @@ struct ad9516_pll { int d1; int d2; int external; + int ext_clk_pll; + int input_freq; }; #endif /* _AD9516_H_ */ diff --git a/cvorg/include/libcvorg.h b/cvorg/include/libcvorg.h index a1266021e2152901f17ae640994805ef4db13f42..a385fc4f23db96289283d7e539729d7ce7575a09 100644 --- a/cvorg/include/libcvorg.h +++ b/cvorg/include/libcvorg.h @@ -33,6 +33,7 @@ int cvorg_unlock(cvorg_t *device); int cvorg_set_sampfreq(cvorg_t *device, unsigned int freq); int cvorg_get_sampfreq(cvorg_t *device, unsigned int *freq); +int cvorg_set_sampfreq_ext_clk(cvorg_t *device, unsigned int freq, unsigned int ext_clk_freq); int cvorg_channel_set_outoff(cvorg_t *device, enum cvorg_outoff outoff); int cvorg_channel_get_outoff(cvorg_t *device, enum cvorg_outoff *outoff); diff --git a/cvorg/lib/cvorg.c b/cvorg/lib/cvorg.c index 9ba080471c7d292128200e654f2279135daae6bb..11f0d779b9ad19b9c590dbb5a41e4bde4d79029e 100644 --- a/cvorg/lib/cvorg.c +++ b/cvorg/lib/cvorg.c @@ -195,7 +195,7 @@ int cvorg_unlock(cvorg_t *device) * @return 0 on success, -1 on failure * * This frequency is generated by tuning an internal oscillator. An external - * clock can be used as well by setting freq to 0. + * clock (without using the PLL) can be used as well by setting freq to 0. * * This function puts the calling process to sleep until the configured * frequency is available at the output of the module. Note that this @@ -231,6 +231,53 @@ int cvorg_set_sampfreq(cvorg_t *device, unsigned int freq) return 0; } +/** + * @brief Set the sampling frequency of the device using external clock and the internal PLL. + * @param device - CVORG device handle + * @param freq - Desired frequency, in Hz + * @param ext_clk_freq - Frequency of the external clk, in Hz + * + * @return 0 on success, -1 on failure + * + * This frequency is generated by tuning an external clock using the PLL. + * + * The frequency of the external clock should be between 1 MHz and 7.5 GHz. + * + * This function puts the calling process to sleep until the configured + * frequency is available at the output of the module. Note that this + * sleeping wait may last for a few hundreds of milliseconds. + * + * Changing the sampling frequency of a device with any of its channels + * in 'busy' state might fail. + */ +int cvorg_set_sampfreq_ext_clk(cvorg_t *device, unsigned int freq, unsigned int ext_clk_freq) +{ + struct ad9516_pll pll; + int ret; + + LIBCVORG_DEBUG(4, "fd %d freq %d\n", device->fd, freq); + if (freq > CVORG_MAX_FREQ) { + LIBCVORG_DEBUG(2, "Invalid frequency (%d Hz). Max: %d Hz\n", + freq, CVORG_MAX_FREQ); + __cvorg_errno = LIBCVORG_EINVAL_FREQ; + return -1; + } + + if (ad9516_fill_pll_conf_ext_clk(freq, &pll, ext_clk_freq)) { + __cvorg_internal_error(LIBCVORG_EINVAL_FREQ); + return -1; + } + + ret = ioctl(device->fd, CVORG_IOCSPLL, &pll); + + if (ret < 0) { + __cvorg_libc_error(__func__); + return -1; + } + return 0; +} + + /** * @brief Get the current sampling frequency of the device * @param device - CVORG device handle @@ -628,7 +675,7 @@ int cvorg_dac_set_conf(cvorg_t *device, struct cvorg_dac conf) if (status & CVORG_CHANSTAT_BUSY || status & CVORG_CHANSTAT_SRAM_BUSY) { fprintf(stderr, "current channel is busy. Cannot set DAC configuration.\n"); - return -1; /* XXX SIG: this return value or another? */ + return -1; } /* Setup the configuration */ diff --git a/cvorg/test/cvorgtest.c b/cvorg/test/cvorgtest.c index 5d6baa72cd22f84bebe07eb5f43009c6e471c8ec..df98d845c719f179f49623afbb8249e07e7151f2 100644 --- a/cvorg/test/cvorgtest.c +++ b/cvorg/test/cvorgtest.c @@ -708,10 +708,11 @@ static void print_pll(struct ad9516_pll *pll) { unsigned long n = pll->p * pll->b + pll->a; double fvco; + double sampfreq; - fvco = (double)AD9516_OSCILLATOR_FREQ * n / pll->r; + fvco = (double)pll->input_freq * n / pll->r; - printf("debug: fref=%g, n=%lu, r=%d\n", (double)AD9516_OSCILLATOR_FREQ, + printf("debug: fref=%g, n=%lu, r=%d\n", (double)pll->input_freq, n, pll->r); printf("PLL params: [P=%d, B=%d, A=%d, R = %d]\n" @@ -722,19 +723,25 @@ static void print_pll(struct ad9516_pll *pll) "\tf_ref = %g Hz\n" "\tN = %lu\n" "\tf_vco = %g Hz\n", - (double)AD9516_OSCILLATOR_FREQ, n, fvco); + (double)pll->input_freq, n, fvco); - if (pll->external) { + if (pll->external) printf("f_out: external\n"); - } else { - double sampfreq = fvco / pll->dvco / pll->d1 / pll->d2; - printf("f_out: "); - if (sampfreq < 1000000) - printf("%g KHz", sampfreq / 1e3); - else - printf("%g MHz", sampfreq / 1e6); + + printf("f_out: "); + sampfreq = fvco / pll->dvco / pll->d1 / pll->d2; + + if (sampfreq < 1000000) + printf("%g KHz", sampfreq / 1e3); + else + printf("%g MHz", sampfreq / 1e6); printf("\n"); + + if (pll->ext_clk_pll) { + printf("f_input: external\n"); + } else { + printf("f_input: internal oscillator\n"); } } @@ -776,14 +783,19 @@ out: int h_sampfreq(struct cmd_desc *cmdd, struct atom *atoms) { struct ad9516_pll pll; + unsigned int ext_clk = 0; + unsigned int freq; if (atoms == (struct atom *)VERBOSE_HELP) { printf("%s - get/set the sampling frequency\n" "%s - print the current sampling frequency\n" - "%s n:\n" + "%s n [m]:\n" "\tif n == 0, the sampling frequency is EXTCLK\n" "\tif n != 0, set the sampling frequency to " - "(approx.) n Hz (using the internal PLL)\n", + "(approx.) n Hz (using the internal PLL)\n" + "\tif m == 0 or not present, the input of internal PLL is the internal oscillator (40 MHz)\n" + "\tif m != 0, set the frequency of the EXTCLK" + "(approx.) m Hz (using the internal PLL)\n", cmdd->name, cmdd->name, cmdd->name); goto out; } @@ -807,10 +819,33 @@ int h_sampfreq(struct cmd_desc *cmdd, struct atom *atoms) printf("Out of range sampfreq. Max: %d\n", CVORG_MAX_FREQ); return -TST_ERR_WRONG_ARG; } + + freq = atoms->val; - if (ad9516_fill_pll_conf(atoms->val, &pll)) { - printf("Invalid argument\n"); - return -TST_ERR_WRONG_ARG; + if ((++atoms)->type == Terminator) { + ext_clk = 0; + } else { + if (atoms->type != Numeric) { + printf("Invalid argument\n"); + return -TST_ERR_WRONG_ARG; + } + if(atoms->val == 0) + ext_clk = 0; + else + ext_clk = 1; + } + + if (ext_clk) { + if (ad9516_fill_pll_conf_ext_clk(freq, &pll, atoms->val)) { + printf("Invalid argument\n"); + return -TST_ERR_WRONG_ARG; + } + + } else { + if (ad9516_fill_pll_conf(freq, &pll)) { + printf("Invalid argument\n"); + return -TST_ERR_WRONG_ARG; + } } if (ioctl(_DNFD, CVORG_IOCSPLL, &pll) < 0) {