diff options
author | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2024-11-03 15:56:55 +0000 |
---|---|---|
committer | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2024-11-03 15:56:55 +0000 |
commit | cf4444e7390365df43ecbd3d130015c1e06ef88f (patch) | |
tree | 8a6eb114135a04d5efd5af213577b4fac47532ae /Radio/HW/BladeRF/src/driver/si5338.c | |
parent | ca50c0f64f1b2fce46b4cb83ed111854bac13852 (diff) | |
download | PrySDR-cf4444e7390365df43ecbd3d130015c1e06ef88f.tar.gz PrySDR-cf4444e7390365df43ecbd3d130015c1e06ef88f.zip |
BladeRF library compiles
Diffstat (limited to 'Radio/HW/BladeRF/src/driver/si5338.c')
-rw-r--r-- | Radio/HW/BladeRF/src/driver/si5338.c | 669 |
1 files changed, 669 insertions, 0 deletions
diff --git a/Radio/HW/BladeRF/src/driver/si5338.c b/Radio/HW/BladeRF/src/driver/si5338.c new file mode 100644 index 0000000..80cda11 --- /dev/null +++ b/Radio/HW/BladeRF/src/driver/si5338.c @@ -0,0 +1,669 @@ +/* + * This file is part of the bladeRF project: + * http://www.github.com/nuand/bladeRF + * + * Copyright (C) 2013 Nuand LLC + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <string.h> +#include <inttypes.h> +#include <limits.h> + +#include <libbladeRF.h> + +#include "rel_assert.h" +#include "host_config.h" +#include "log.h" + +#include "si5338.h" + +#define SI5338_EN_A 0x01 +#define SI5338_EN_B 0x02 + +#define SI5338_F_VCO (38400000UL * 66UL) + +/** + * This is used set or recreate the si5338 frequency + * Each si5338 multisynth module can be set independently + */ +struct si5338_multisynth { + /* Multisynth to program (0-3) */ + uint8_t index; + + /* Base address of the multisynth */ + uint16_t base; + + /* Requested and actual sample rates */ + struct bladerf_rational_rate requested; + struct bladerf_rational_rate actual; + + /* Enables for A and/or B outputs */ + uint8_t enable; + + /* f_out = fvco / (a + b/c) / r */ + uint32_t a, b, c, r; + + /* (a, b, c) in multisynth (p1, p2, p3) form */ + uint32_t p1, p2, p3; + + /* (p1, p2, p3) in register form */ + uint8_t regs[10]; +}; + +static void si5338_log_read_error(int error, const char *s) +{ + log_debug("Could not read from si5338 (%d): %s\n", error, s); + return; +} + +static void si5338_log_write_error(int error, const char *s) +{ + log_debug("Could not write to si5338 (%d): %s\n", error, s); + return; +} + +static uint64_t si5338_gcd(uint64_t a, uint64_t b) +{ + uint64_t t; + while (b != 0) { + t = b; + b = a % t; + a = t; + } + return a; +} + +static void si5338_rational_reduce(struct bladerf_rational_rate *r) +{ + int64_t val; + + if ((r->den > 0) && (r->num >= r->den)) { + /* Get whole number */ + uint64_t whole = r->num / r->den; + r->integer += whole; + r->num = r->num - whole*r->den; + } + + /* Reduce fraction */ + val = si5338_gcd(r->num, r->den); + if (val > 0) { + r->num /= val; + r->den /= val; + } + + return ; +} + +static void si5338_rational_double(struct bladerf_rational_rate *r) +{ + r->integer *= 2; + r->num *= 2; + si5338_rational_reduce(r); + return; +} + +/** + * Update the base address of the selected multisynth + */ +static void si5338_update_base(struct si5338_multisynth *ms) +{ + ms->base = 53 + ms->index*11 ; + return; +} + +/** + * Unpack the recently read registers into (p1, p2, p3) and (a, b, c) + * + * Precondition: + * regs[10] and r have been read + * + * Post-condition: + * (p1, p2, p3), (a, b, c) and actual are populated + */ +static void si5338_unpack_regs(struct si5338_multisynth *ms) +{ + uint64_t temp; + + /* Zeroize */ + ms->p1 = ms->p2 = ms->p3 = 0; + + /* Populate */ + ms->p1 = ((ms->regs[2]&3)<<16) | (ms->regs[1]<<8) | (ms->regs[0]); + ms->p2 = (ms->regs[5]<<22) | (ms->regs[4]<<14) | (ms->regs[3]<<6) | ((ms->regs[2]>>2)&0x3f); + ms->p3 = ((ms->regs[9]&0x3f)<<24) | (ms->regs[8]<<16) | (ms->regs[7]<<8) | (ms->regs[6]); + + log_verbose("Unpacked P1: 0x%8.8x (%u) P2: 0x%8.8x (%u) P3: 0x%8.8x (%u)\n", + ms->p1, ms->p1, ms->p2, ms->p2, ms->p3, ms->p3); + + /* c = p3 */ + ms->c = ms->p3; + + /* a = (p1+512)/128 + * + * NOTE: The +64 is for rounding purposes. + */ + ms->a = (ms->p1+512)/128; + + /* b = (((p1+512)-128*a)*c + (b % c) + 64)/128 */ + temp = (ms->p1+512)-128*(uint64_t)ms->a; + temp = (temp * ms->c) + ms->p2; + temp = (temp + 64) / 128; + assert(temp <= UINT32_MAX); + ms->b = (uint32_t)temp; + + log_verbose("Unpacked a + b/c: %d + %d/%d\n", ms->a, ms->b, ms->c); + log_verbose("Unpacked r: %d\n", ms->r); +} + +/* + * Pack (a, b, c, r) into (p1, p2, p3) and regs[] + */ +static void si5338_pack_regs(struct si5338_multisynth *ms) +{ + /* Precondition: + * (a, b, c) and r have been populated + * + * Post-condition: + * (p1, p2, p3) and regs[10] are populated + */ + + /* p1 = (a * c + b) * 128 / c - 512 */ + uint64_t temp; + temp = (uint64_t)ms->a * ms->c + ms->b; + temp = temp * 128 ; + temp = temp / ms->c - 512; + assert(temp <= UINT32_MAX); + ms->p1 = (uint32_t)temp; + //ms->p1 = ms->a * ms->c + ms->b; + //ms->p1 = ms->p1 * 128; + //ms->p1 = ms->p1 / ms->c - 512; + + /* p2 = (b * 128) % c */ + temp = (uint64_t)ms->b * 128; + temp = temp % ms->c; + assert(temp <= UINT32_MAX); + ms->p2 = (uint32_t)temp; + + /* p3 = c */ + ms->p3 = ms->c; + + log_verbose("MSx P1: 0x%8.8x (%u) P2: 0x%8.8x (%u) P3: 0x%8.8x (%u)\n", + ms->p1, ms->p1, ms->p2, ms->p2, ms->p3, ms->p3); + + /* Regs */ + ms->regs[0] = ms->p1 & 0xff; + ms->regs[1] = (ms->p1 >> 8) & 0xff; + ms->regs[2] = ((ms->p2 & 0x3f) << 2) | ((ms->p1 >> 16) & 0x3); + ms->regs[3] = (ms->p2 >> 6) & 0xff; + ms->regs[4] = (ms->p2 >> 14) & 0xff; + ms->regs[5] = (ms->p2 >> 22) & 0xff; + ms->regs[6] = ms->p3 & 0xff; + ms->regs[7] = (ms->p3 >> 8) & 0xff; + ms->regs[8] = (ms->p3 >> 16) & 0xff; + ms->regs[9] = (ms->p3 >> 24) & 0xff; + + return ; +} + +static int si5338_write_multisynth(struct bladerf *dev, + struct si5338_multisynth *ms) +{ + int i, status; + uint8_t r_power, r_count, val; + + log_verbose("Writing MS%d\n", ms->index); + + /* Write out the enables */ + status = dev->backend->si5338_read(dev, 36 + ms->index, &val); + if (status < 0) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + val |= ms->enable; + log_verbose("Wrote enable register: 0x%2.2x\n", val); + status = dev->backend->si5338_write(dev, 36 + ms->index, val); + if (status < 0) { + si5338_log_write_error(status, bladerf_strerror(status)); + return status; + } + + /* Write out the registers */ + for (i = 0 ; i < 10 ; i++) { + status = dev->backend->si5338_write(dev, ms->base + i, *(ms->regs+i)); + if (status < 0) { + si5338_log_write_error(status, bladerf_strerror(status)); + return status; + } + log_verbose("Wrote regs[%d]: 0x%2.2x\n", i, *(ms->regs+i)); + } + + /* Calculate r_power from c_count */ + r_power = 0; + r_count = ms->r >> 1 ; + while (r_count > 0) { + r_count >>= 1; + r_power++; + } + + /* Set the r value to the log2(r_count) to match Figure 18 */ + val = 0xc0; + val |= (r_power<<2); + + log_verbose("Wrote r register: 0x%2.2x\n", val); + + status = dev->backend->si5338_write(dev, 31 + ms->index, val); + if (status < 0) { + si5338_log_write_error(status, bladerf_strerror(status)); + } + + return status ; +} + +static int si5338_read_multisynth(struct bladerf *dev, + struct si5338_multisynth *ms) +{ + int i, status; + uint8_t val; + + log_verbose("Reading MS%d\n", ms->index); + + /* Read the enable bits */ + status = dev->backend->si5338_read(dev, 36 + ms->index, &val); + if (status < 0) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status ; + } + ms->enable = val&7; + log_verbose("Read enable register: 0x%2.2x\n", val); + + /* Read all of the multisynth registers */ + for (i = 0; i < 10; i++) { + status = dev->backend->si5338_read(dev, ms->base + i, ms->regs+i); + if (status < 0) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + log_verbose("Read regs[%d]: 0x%2.2x\n", i, *(ms->regs+i)); + } + + /* Populate the RxDIV value from the register */ + status = dev->backend->si5338_read(dev, 31 + ms->index, &val); + if (status < 0) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + /* RxDIV is stored as a power of 2, so restore it on readback */ + log_verbose("Read r register: 0x%2.2x\n", val); + val = (val>>2)&7; + ms->r = (1<<val); + + /* Unpack the regs into appropriate values */ + si5338_unpack_regs(ms) ; + + return 0; +} + +static void si5338_calculate_ms_freq(struct si5338_multisynth *ms, + struct bladerf_rational_rate *rate) +{ + struct bladerf_rational_rate abc; + abc.integer = ms->a; + abc.num = ms->b; + abc.den = ms->c; + + rate->integer = 0; + rate->num = SI5338_F_VCO * abc.den; + rate->den = (uint64_t)ms->r*(abc.integer * abc.den + abc.num); + + /* Compensate for doubling of frequency for LMS sampling clocks */ + if(ms->index == 1 || ms->index == 2) { + rate->den *= 2; + } + + si5338_rational_reduce(rate); + + log_verbose("Calculated multisynth frequency: %" + PRIu64" + %"PRIu64"/%"PRIu64"\n", + rate->integer, rate->num, rate->den); + + return; +} + +static int si5338_calculate_multisynth(struct si5338_multisynth *ms, + struct bladerf_rational_rate *rate) +{ + + struct bladerf_rational_rate req; + struct bladerf_rational_rate abc; + uint8_t r_value; + + /* Don't muss with the users data */ + req = *rate; + + /* Double requested frequency for sample clocks since LMS requires + * 2:1 clock:sample rate + */ + if(ms->index == 1 || ms->index == 2) { + si5338_rational_double(&req); + } + + /* Find a suitable R value */ + r_value = 1; + while (req.integer < 5000000 && r_value < 32) { + si5338_rational_double(&req); + r_value <<= 1; + } + + if (r_value == 32 && req.integer < 5000000) { + log_debug("Sample rate requires r > 32\n"); + return BLADERF_ERR_INVAL; + } else { + log_verbose("Found r value of: %d\n", r_value); + } + + /* Find suitable MS (a, b, c) values */ + abc.integer = 0; + abc.num = SI5338_F_VCO * req.den; + abc.den = req.integer * req.den + req.num; + si5338_rational_reduce(&abc); + + log_verbose("MSx a + b/c: %"PRIu64" + %"PRIu64"/%"PRIu64"\n", + abc.integer, abc.num, abc.den); + + /* Check values to make sure they are OK */ + if (abc.integer < 8) { + switch (abc.integer) { + case 0: + case 1: + case 2: + case 3: + case 5: + case 7: + log_debug("Integer portion too small: %"PRIu64"\n", abc.integer); + return BLADERF_ERR_INVAL; + } + } else if (abc.integer > 567) { + log_debug("Integer portion too large: %"PRIu64"\n", abc.integer); + return BLADERF_ERR_INVAL; + } + + /* Loss of precision if num or den are greater than 2^30-1 */ + while (abc.num > (1<<30) || abc.den > (1<<30) ) { + log_debug("Loss of precision in reducing fraction from " + "%"PRIu64"/%"PRIu64" to %"PRIu64"/%"PRIu64"\n", + abc.num, abc.den, abc.num>>1, abc.den>>1); + abc.num >>= 1; + abc.den >>= 1; + } + + log_verbose("MSx a + b/c: %"PRIu64" + %"PRIu64"/%"PRIu64"\n", + abc.integer, abc.num, abc.den); + + /* Set it in the multisynth */ + assert(abc.integer <= UINT32_MAX); + assert(abc.num <= UINT32_MAX); + assert(abc.den <= UINT32_MAX); + ms->a = (uint32_t)abc.integer; + ms->b = (uint32_t)abc.num; + ms->c = (uint32_t)abc.den; + ms->r = r_value; + + /* Pack the registers */ + si5338_pack_regs(ms); + + return 0; +} + +/** + * Configure a multisynth for either the RX/TX sample clocks (index=1 or 2) + * or for the SMB output (index=3). + */ +static int si5338_set_rational_multisynth(struct bladerf *dev, + uint8_t index, uint8_t channel, + struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual_ret) +{ + struct si5338_multisynth ms; + struct bladerf_rational_rate req; + struct bladerf_rational_rate actual; + int status; + + si5338_rational_reduce(rate); + + /* Save off the value */ + req = *rate; + + /* Setup the multisynth enables and index */ + ms.index = index; + ms.enable = channel; + + /* Update the base address register */ + si5338_update_base(&ms); + + /* Calculate multisynth values */ + status = si5338_calculate_multisynth(&ms, &req); + if(status != 0) { + return status; + } + + /* Get the actual rate */ + si5338_calculate_ms_freq(&ms, &actual); + if (actual_ret) { + memcpy(actual_ret, &actual, sizeof(*actual_ret)); + } + + /* Program it to the part */ + status = si5338_write_multisynth(dev, &ms); + + /* Done */ + return status ; +} + + +int si5338_set_rational_sample_rate(struct bladerf *dev, bladerf_channel ch, + const struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual) +{ + struct bladerf_rational_rate rate_reduced = *rate; + uint8_t index = (ch == BLADERF_CHANNEL_RX(0)) ? 1 : 2; + uint8_t channel = SI5338_EN_A; + + /* Enforce minimum sample rate */ + si5338_rational_reduce(&rate_reduced); + if (rate_reduced.integer < BLADERF_SAMPLERATE_MIN) { + log_debug("%s: provided sample rate violates minimum\n", __FUNCTION__); + return BLADERF_ERR_INVAL; + } + + if (ch == BLADERF_CHANNEL_TX(0)) { + channel |= SI5338_EN_B; + } + + return si5338_set_rational_multisynth(dev, index, channel, &rate_reduced, actual); +} + +int si5338_set_rational_smb_freq(struct bladerf *dev, + const struct bladerf_rational_rate *rate, + struct bladerf_rational_rate *actual) +{ + struct bladerf_rational_rate rate_reduced = *rate; + + /* Enforce minimum and maximum frequencies */ + si5338_rational_reduce(&rate_reduced); + + if (rate_reduced.integer < BLADERF_SMB_FREQUENCY_MIN) { + log_debug("%s: provided SMB freq violates minimum\n", __FUNCTION__); + return BLADERF_ERR_INVAL; + } else if (rate_reduced.integer > BLADERF_SMB_FREQUENCY_MAX) { + log_debug("%s: provided SMB freq violates maximum\n", __FUNCTION__); + return BLADERF_ERR_INVAL; + } + + return si5338_set_rational_multisynth(dev, 3, SI5338_EN_A, &rate_reduced, actual); +} + +int si5338_set_sample_rate(struct bladerf *dev, bladerf_channel ch, + uint32_t rate, uint32_t *actual) +{ + struct bladerf_rational_rate req, act; + int status; + + memset(&act, 0, sizeof(act)); + log_verbose("Setting integer sample rate: %d\n", rate); + req.integer = rate; + req.num = 0; + req.den = 1; + + status = si5338_set_rational_sample_rate(dev, ch, &req, &act); + + if (status == 0 && act.num != 0) { + log_info("Non-integer sample rate set from integer sample rate, " + "truncating output.\n"); + } + + assert(act.integer <= UINT32_MAX); + + if (actual) { + *actual = (uint32_t)act.integer; + } + log_verbose("Set actual integer sample rate: %d\n", act.integer); + + return status ; +} + +int si5338_set_smb_freq(struct bladerf *dev, uint32_t rate, uint32_t *actual) +{ + struct bladerf_rational_rate req, act; + int status; + + memset(&act, 0, sizeof(act)); + log_verbose("Setting integer SMB frequency: %d\n", rate); + req.integer = rate; + req.num = 0; + req.den = 1; + + status = si5338_set_rational_smb_freq(dev, &req, &act); + + if (status == 0 && act.num != 0) { + log_info("Non-integer SMB frequency set from integer frequency, " + "truncating output.\n"); + } + + assert(act.integer <= UINT32_MAX); + + if (actual) { + *actual = (uint32_t)act.integer; + } + log_verbose("Set actual integer SMB frequency: %d\n", act.integer); + + return status; +} + +int si5338_get_rational_sample_rate(struct bladerf *dev, bladerf_channel ch, + struct bladerf_rational_rate *rate) +{ + + struct si5338_multisynth ms; + int status; + + /* Select the multisynth we want to read */ + ms.index = (ch == BLADERF_CHANNEL_RX(0)) ? 1 : 2; + + /* Update the base address */ + si5338_update_base(&ms); + + /* Readback */ + status = si5338_read_multisynth(dev, &ms); + + if (status) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + + si5338_calculate_ms_freq(&ms, rate); + + return 0; +} + +int si5338_get_rational_smb_freq(struct bladerf *dev, + struct bladerf_rational_rate *rate) +{ + struct si5338_multisynth ms; + int status; + + /* Select MS3 for the SMB output */ + ms.index = 3; + si5338_update_base(&ms); + + status = si5338_read_multisynth(dev, &ms); + + if (status) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + + si5338_calculate_ms_freq(&ms, rate); + + return 0; +} + +int si5338_get_sample_rate(struct bladerf *dev, bladerf_channel ch, + unsigned int *rate) +{ + struct bladerf_rational_rate actual; + int status; + + status = si5338_get_rational_sample_rate(dev, ch, &actual); + + if (status) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + + if (actual.num != 0) { + log_debug("Fractional sample rate truncated during integer sample rate" + "retrieval\n"); + } + + assert(actual.integer <= UINT_MAX); + *rate = (unsigned int)actual.integer; + + return 0; +} + +int si5338_get_smb_freq(struct bladerf *dev, unsigned int *rate) +{ + struct bladerf_rational_rate actual; + int status; + + status = si5338_get_rational_smb_freq(dev, &actual); + + if (status) { + si5338_log_read_error(status, bladerf_strerror(status)); + return status; + } + + if (actual.num != 0) { + log_debug("Fractional SMB frequency truncated during integer SMB" + " frequency retrieval\n"); + } + + assert(actual.integer <= UINT_MAX); + *rate = (unsigned int)actual.integer; + + return 0; +} |