use crate::error::{RtlSdrError, TunerError};
use crate::tuner::Tuner;
use crate::usb;
pub const I2C_ADDR: u8 = 0xc6;
pub const CHECK_ADDR: u8 = 0x00;
pub const CHECK_VAL: u8 = 0xa3;
const REG_RF_A: u8 = 0x01;
const REG_RF_M: u8 = 0x02;
const REG_RF_K_HIGH: u8 = 0x03;
const REG_RF_K_LOW: u8 = 0x04;
const REG_RF_OUTDIV_A: u8 = 0x05;
const REG_VCO_BW: u8 = 0x06;
const REG_XTAL_SPEED: u8 = 0x07;
#[allow(dead_code)]
const REG_RC_CAL: u8 = 0x10;
const REG_MULTI_SELECT: u8 = 0x11;
const REG_IF_GAIN: u8 = 0x13;
const REG_AGC_LNA_FORCE: u8 = 0x0d;
const REG_VCO_CALIB: u8 = 0x0e;
const REG_LNA_GAIN: u8 = 0x14;
const REG_VHF_TRACK: u8 = 0x1d;
const NUM_INIT_REGS: usize = 21;
const INIT_REGS: [u8; NUM_INIT_REGS] = [
0x09, 0x16, 0x00, 0x00, 0x17, 0x02, 0x0a, 0xff, 0x6e, 0xb8, 0x82, 0xfc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x01, ];
const VCO_HIGH_THRESH: u64 = 3_060_000_000;
const VCO_VOLTAGE_LOW: u8 = 0x02;
const VCO_VOLTAGE_HIGH: u8 = 0x3c;
const VCO_VOLTAGE_MASK: u8 = 0x3f;
const VCO_SPEED_BIT: u8 = 0x08;
const CLOCK_OUT_BIT: u8 = 0x20;
const VCO_CALIB_TRIGGER: u8 = 0x80;
const VCO_CALIB_RESET: u8 = 0x00;
const XTAL_28_8_MHZ_BIT: u8 = 0x20;
const DUAL_MASTER_BIT: u8 = 0x02;
const XIN_THRESHOLD: u16 = 16384;
const XIN_OVERFLOW_ADD: u16 = 32768;
const BW_MASK: u8 = 0x3f;
const BW_6MHZ: u8 = 0x80;
const BW_7MHZ: u8 = 0x40;
const BW_6MHZ_HZ: u32 = 6_000_000;
const BW_7MHZ_HZ: u32 = 7_000_000;
const REALTEK_DEMOD_BITS: u8 = 0x07;
const LNA_GAIN_MASK: u8 = 0xe0;
const VHF_FILTER_ENABLE_BIT: u8 = 0x10;
const VHF_FILTER_DISABLE_MASK: u8 = 0xef;
const BAND_SELECT_MASK: u8 = 0x1f;
const UHF_ENABLE_BIT: u8 = 0x40;
const VHF_TRACK_MASK: u8 = 0xe3;
const MULTI_64_BIT: u8 = 0x04;
const MULTI_64_DISABLE_MASK: u8 = 0xfb;
const MANUAL_GAIN_BIT: u8 = 1 << 3;
const FIXED_IF_GAIN: u8 = 0x0a;
#[allow(dead_code)]
const RC_CAL_FORCE: u8 = 0x11;
#[allow(dead_code)]
const RC_CAL_RESET: u8 = 0x01;
#[allow(dead_code)]
const RC_CAL_MAX: u8 = 0x0f;
const VHF_TRACK_7: u8 = 0x1c;
const VHF_TRACK_6: u8 = 0x18;
const VHF_TRACK_5: u8 = 0x14;
const VHF_TRACK_4: u8 = 0x10;
const VHF_TRACK_3: u8 = 0x0c;
const VHF_TRACK_2: u8 = 0x08;
const VHF_TRACK_1: u8 = 0x04;
const VHF_TRACK_UHF_GPS: u8 = 0x1c;
const VHF_TRACK_THRESH_7: u32 = 177_500_000;
const VHF_TRACK_THRESH_6: u32 = 184_500_000;
const VHF_TRACK_THRESH_5: u32 = 191_500_000;
const VHF_TRACK_THRESH_4: u32 = 198_500_000;
const VHF_TRACK_THRESH_3: u32 = 205_500_000;
const VHF_TRACK_THRESH_2: u32 = 219_500_000;
const VHF_UHF_BOUNDARY: u32 = 300_000_000;
struct FreqDivider {
max_freq: u32,
multi: u8,
reg5: u8,
reg6: u8,
}
const FREQ_DIVIDERS: [FreqDivider; 11] = [
FreqDivider {
max_freq: 37_084_000,
multi: 96,
reg5: 0x82,
reg6: 0x00,
},
FreqDivider {
max_freq: 55_625_000,
multi: 64,
reg5: 0x02,
reg6: 0x02,
},
FreqDivider {
max_freq: 74_167_000,
multi: 48,
reg5: 0x42,
reg6: 0x00,
},
FreqDivider {
max_freq: 111_250_000,
multi: 32,
reg5: 0x82,
reg6: 0x02,
},
FreqDivider {
max_freq: 148_334_000,
multi: 24,
reg5: 0x22,
reg6: 0x00,
},
FreqDivider {
max_freq: 222_500_000,
multi: 16,
reg5: 0x42,
reg6: 0x02,
},
FreqDivider {
max_freq: 296_667_000,
multi: 12,
reg5: 0x12,
reg6: 0x00,
},
FreqDivider {
max_freq: 445_000_000,
multi: 8,
reg5: 0x22,
reg6: 0x02,
},
FreqDivider {
max_freq: 593_334_000,
multi: 6,
reg5: 0x0a,
reg6: 0x00,
},
FreqDivider {
max_freq: 950_000_000,
multi: 4,
reg5: 0x12,
reg6: 0x02,
},
FreqDivider {
max_freq: u32::MAX,
multi: 2,
reg5: 0x0a,
reg6: 0x02,
},
];
const LNA_GAINS: [(i32, u8); 24] = [
(-99, 0x02),
(-73, 0x03),
(-65, 0x05),
(-63, 0x04),
(-63, 0x00),
(-60, 0x07),
(-58, 0x01),
(-54, 0x06),
(58, 0x0f),
(61, 0x0e),
(63, 0x0d),
(65, 0x0c),
(67, 0x0b),
(68, 0x0a),
(70, 0x09),
(71, 0x08),
(179, 0x17),
(181, 0x16),
(182, 0x15),
(184, 0x14),
(186, 0x13),
(188, 0x12),
(191, 0x11),
(197, 0x10),
];
pub const FC0013_GAINS: [i32; 23] = [
-99, -73, -65, -63, -60, -58, -54, 58, 61, 63, 65, 67, 68, 70, 71, 179, 181, 182, 184, 186,
188, 191, 197,
];
const PLL_REG1_MAX: u8 = 15;
const PLL_REG2_MIN: u8 = 0x0b;
pub struct Fc0013Tuner {
xtal: u32,
bandwidth: u32,
freq: u32,
}
impl Fc0013Tuner {
pub fn new(xtal: u32) -> Self {
Self {
xtal,
bandwidth: BW_6MHZ_HZ,
freq: 0,
}
}
#[allow(clippy::unused_self)]
fn write_reg(
&self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
reg: u8,
val: u8,
) -> Result<(), RtlSdrError> {
usb::i2c_write_reg(handle, I2C_ADDR, reg, val)
}
#[allow(clippy::unused_self)]
fn read_reg(
&self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
reg: u8,
) -> Result<u8, RtlSdrError> {
usb::i2c_read_reg(handle, I2C_ADDR, reg)
}
fn set_vhf_track(
&self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
freq: u32,
) -> Result<(), RtlSdrError> {
let tmp = self.read_reg(handle, REG_VHF_TRACK)?;
let tmp = tmp & VHF_TRACK_MASK;
let track_bits = if freq <= VHF_TRACK_THRESH_7 {
VHF_TRACK_7
} else if freq <= VHF_TRACK_THRESH_6 {
VHF_TRACK_6
} else if freq <= VHF_TRACK_THRESH_5 {
VHF_TRACK_5
} else if freq <= VHF_TRACK_THRESH_4 {
VHF_TRACK_4
} else if freq <= VHF_TRACK_THRESH_3 {
VHF_TRACK_3
} else if freq <= VHF_TRACK_THRESH_2 {
VHF_TRACK_2
} else if freq < VHF_UHF_BOUNDARY {
VHF_TRACK_1
} else {
VHF_TRACK_UHF_GPS
};
self.write_reg(handle, REG_VHF_TRACK, tmp | track_bits)
}
#[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
fn set_params(
&self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
freq: u32,
bandwidth: u32,
) -> Result<(), RtlSdrError> {
let xtal_freq_div_2 = self.xtal / 2;
self.set_vhf_track(handle, freq)?;
if freq < VHF_UHF_BOUNDARY {
let tmp = self.read_reg(handle, REG_XTAL_SPEED)?;
self.write_reg(handle, REG_XTAL_SPEED, tmp | VHF_FILTER_ENABLE_BIT)?;
let tmp = self.read_reg(handle, REG_LNA_GAIN)?;
self.write_reg(handle, REG_LNA_GAIN, tmp & BAND_SELECT_MASK)?;
} else {
let tmp = self.read_reg(handle, REG_XTAL_SPEED)?;
self.write_reg(handle, REG_XTAL_SPEED, tmp & VHF_FILTER_DISABLE_MASK)?;
let tmp = self.read_reg(handle, REG_LNA_GAIN)?;
self.write_reg(
handle,
REG_LNA_GAIN,
(tmp & BAND_SELECT_MASK) | UHF_ENABLE_BIT,
)?;
}
let divider = FREQ_DIVIDERS
.iter()
.find(|d| freq < d.max_freq)
.unwrap_or(&FREQ_DIVIDERS[FREQ_DIVIDERS.len() - 1]);
let multi = divider.multi;
let mut reg5 = divider.reg5;
let mut reg6 = divider.reg6;
let f_vco = u64::from(freq) * u64::from(multi);
let mut vco_select = false;
if f_vco >= VCO_HIGH_THRESH {
reg6 |= VCO_SPEED_BIT;
vco_select = true;
}
let mut xdiv = (f_vco / u64::from(xtal_freq_div_2)) as u16;
let remainder = f_vco - u64::from(xdiv) * u64::from(xtal_freq_div_2);
if remainder >= u64::from(xtal_freq_div_2 / 2) {
xdiv += 1;
}
let mut pm = xdiv / 8;
let mut am = xdiv - (8 * pm);
if am < 2 {
if pm == 0 {
return Err(TunerError::PllProgrammingFailed {
backend: "FC0013",
freq_hz: freq,
reason: "PLL inputs out of range (xdiv too small)",
}
.into());
}
am += 8;
pm -= 1;
}
let (reg1, reg2) = if pm > 31 {
((am + 8 * (pm - 31)) as u8, 31u8)
} else {
(am as u8, pm as u8)
};
if reg1 > PLL_REG1_MAX || reg2 < PLL_REG2_MIN {
return Err(TunerError::PllProgrammingFailed {
backend: "FC0013",
freq_hz: freq,
reason: "no valid PLL combination",
}
.into());
}
reg6 |= CLOCK_OUT_BIT;
let xin_remainder =
(f_vco - (f_vco / u64::from(xtal_freq_div_2)) * u64::from(xtal_freq_div_2)) / 1000;
debug_assert!(
xin_remainder <= 0xFFFF,
"XIN remainder exceeds u16 range: {xin_remainder}"
);
let xin_wide = (u32::from(xin_remainder as u16) << 15) / (xtal_freq_div_2 / 1000);
let mut xin = xin_wide as u16;
if xin >= XIN_THRESHOLD {
xin += XIN_OVERFLOW_ADD;
}
let reg3 = (xin >> 8) as u8;
let reg4 = (xin & 0xff) as u8;
reg6 &= BW_MASK;
match bandwidth {
BW_6MHZ_HZ => reg6 |= BW_6MHZ,
BW_7MHZ_HZ => reg6 |= BW_7MHZ,
_ => {} }
reg5 |= REALTEK_DEMOD_BITS;
self.write_reg(handle, REG_RF_A, reg1)?;
self.write_reg(handle, REG_RF_M, reg2)?;
self.write_reg(handle, REG_RF_K_HIGH, reg3)?;
self.write_reg(handle, REG_RF_K_LOW, reg4)?;
self.write_reg(handle, REG_RF_OUTDIV_A, reg5)?;
self.write_reg(handle, REG_VCO_BW, reg6)?;
let tmp = self.read_reg(handle, REG_MULTI_SELECT)?;
if multi == 64 {
self.write_reg(handle, REG_MULTI_SELECT, tmp | MULTI_64_BIT)?;
} else {
self.write_reg(handle, REG_MULTI_SELECT, tmp & MULTI_64_DISABLE_MASK)?;
}
self.write_reg(handle, REG_VCO_CALIB, VCO_CALIB_TRIGGER)?;
self.write_reg(handle, REG_VCO_CALIB, VCO_CALIB_RESET)?;
self.write_reg(handle, REG_VCO_CALIB, VCO_CALIB_RESET)?;
let tmp = self.read_reg(handle, REG_VCO_CALIB)?;
let voltage = tmp & VCO_VOLTAGE_MASK;
if vco_select {
if voltage > VCO_VOLTAGE_HIGH {
reg6 &= !VCO_SPEED_BIT;
self.write_reg(handle, REG_VCO_BW, reg6)?;
self.write_reg(handle, REG_VCO_CALIB, VCO_CALIB_TRIGGER)?;
self.write_reg(handle, REG_VCO_CALIB, VCO_CALIB_RESET)?;
}
} else if voltage < VCO_VOLTAGE_LOW {
reg6 |= VCO_SPEED_BIT;
self.write_reg(handle, REG_VCO_BW, reg6)?;
self.write_reg(handle, REG_VCO_CALIB, VCO_CALIB_TRIGGER)?;
self.write_reg(handle, REG_VCO_CALIB, VCO_CALIB_RESET)?;
}
Ok(())
}
#[allow(dead_code)]
pub(crate) fn rc_cal_add(
&self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
rc_val: i32,
) -> Result<(), RtlSdrError> {
self.write_reg(handle, REG_RC_CAL, 0x00)?;
let rc_cal = self.read_reg(handle, REG_RC_CAL)?;
let rc_cal = i32::from(rc_cal & RC_CAL_MAX);
let val = rc_cal + rc_val;
self.write_reg(handle, REG_AGC_LNA_FORCE, RC_CAL_FORCE)?;
if val > i32::from(RC_CAL_MAX) {
self.write_reg(handle, REG_RC_CAL, RC_CAL_MAX)?;
} else if val < 0 {
self.write_reg(handle, REG_RC_CAL, 0x00)?;
} else {
self.write_reg(handle, REG_RC_CAL, val as u8)?;
}
Ok(())
}
#[allow(dead_code)]
pub(crate) fn rc_cal_reset(
&self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
) -> Result<(), RtlSdrError> {
self.write_reg(handle, REG_AGC_LNA_FORCE, RC_CAL_RESET)?;
self.write_reg(handle, REG_RC_CAL, 0x00)?;
Ok(())
}
}
impl Tuner for Fc0013Tuner {
fn init(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
) -> Result<(), RtlSdrError> {
let mut regs = INIT_REGS;
regs[6] |= XTAL_28_8_MHZ_BIT;
regs[11] |= DUAL_MASTER_BIT;
for (i, &val) in regs.iter().enumerate() {
let reg_addr = (i + 1) as u8;
self.write_reg(handle, reg_addr, val)?;
}
Ok(())
}
fn exit(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
) -> Result<(), RtlSdrError> {
let val = self.read_reg(handle, REG_VCO_BW)?;
self.write_reg(handle, REG_VCO_BW, val | 0x01)?;
Ok(())
}
fn set_freq(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
freq: u32,
) -> Result<(), RtlSdrError> {
self.freq = freq;
self.set_params(handle, freq, self.bandwidth)
}
fn set_bw(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
bw: u32,
_sample_rate: u32,
) -> Result<u32, RtlSdrError> {
self.bandwidth = bw;
if self.freq > 0 {
self.set_params(handle, self.freq, bw)?;
}
Ok(0)
}
fn set_gain(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
gain: i32,
) -> Result<(), RtlSdrError> {
let mut tmp = self.read_reg(handle, REG_LNA_GAIN)?;
tmp &= LNA_GAIN_MASK;
let &(_, reg_val) = LNA_GAINS
.iter()
.find(|&&(g, _)| g >= gain)
.or_else(|| LNA_GAINS.last())
.unwrap_or(&LNA_GAINS[0]);
tmp |= reg_val;
self.write_reg(handle, REG_LNA_GAIN, tmp)?;
Ok(())
}
fn set_xtal(&mut self, xtal: u32) {
self.xtal = xtal;
}
fn set_gain_mode(
&mut self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
manual: bool,
) -> Result<(), RtlSdrError> {
let mut tmp = self.read_reg(handle, REG_AGC_LNA_FORCE)?;
if manual {
tmp |= MANUAL_GAIN_BIT;
} else {
tmp &= !MANUAL_GAIN_BIT;
}
self.write_reg(handle, REG_AGC_LNA_FORCE, tmp)?;
self.write_reg(handle, REG_IF_GAIN, FIXED_IF_GAIN)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_init_regs_length() {
assert_eq!(INIT_REGS.len(), NUM_INIT_REGS);
assert_eq!(NUM_INIT_REGS, 21);
}
#[test]
fn test_freq_dividers_ordered() {
for w in FREQ_DIVIDERS.windows(2) {
assert!(
w[0].max_freq < w[1].max_freq,
"frequency dividers must be in ascending order"
);
}
}
#[test]
fn test_freq_dividers_cover_full_range() {
assert_eq!(FREQ_DIVIDERS[FREQ_DIVIDERS.len() - 1].max_freq, u32::MAX);
}
#[test]
fn test_gains_sorted() {
for w in FC0013_GAINS.windows(2) {
assert!(w[0] <= w[1], "gains must be in ascending order");
}
}
#[test]
fn test_gains_count() {
assert_eq!(FC0013_GAINS.len(), 23);
}
#[test]
fn test_lna_gains_count() {
assert_eq!(LNA_GAINS.len(), 24);
}
#[test]
fn test_lna_gains_match_c_source() {
assert_eq!(LNA_GAINS[0], (-99, 0x02));
assert_eq!(LNA_GAINS[1], (-73, 0x03));
assert_eq!(LNA_GAINS[3], (-63, 0x04));
assert_eq!(LNA_GAINS[4], (-63, 0x00));
assert_eq!(LNA_GAINS[8], (58, 0x0f));
assert_eq!(LNA_GAINS[15], (71, 0x08));
assert_eq!(LNA_GAINS[16], (179, 0x17));
assert_eq!(LNA_GAINS[23], (197, 0x10));
}
#[test]
fn test_i2c_addr() {
assert_eq!(I2C_ADDR, 0xc6);
}
#[test]
fn test_check_val() {
assert_eq!(CHECK_VAL, 0xa3);
}
#[test]
fn test_new_defaults() {
let tuner = Fc0013Tuner::new(28_800_000);
assert_eq!(tuner.xtal, 28_800_000);
assert_eq!(tuner.bandwidth, BW_6MHZ_HZ);
assert_eq!(tuner.freq, 0);
}
#[test]
fn test_init_regs_xtal_bit() {
let mut regs = INIT_REGS;
regs[6] |= XTAL_28_8_MHZ_BIT;
assert_eq!(regs[6] & XTAL_28_8_MHZ_BIT, XTAL_28_8_MHZ_BIT);
}
#[test]
fn test_init_regs_dual_master_bit() {
let mut regs = INIT_REGS;
regs[11] |= DUAL_MASTER_BIT;
assert_eq!(regs[11] & DUAL_MASTER_BIT, DUAL_MASTER_BIT);
}
#[test]
fn test_freq_divider_selection() {
let divider = FREQ_DIVIDERS
.iter()
.find(|d| 100_000_000 < d.max_freq)
.expect("should find divider for 100 MHz");
assert_eq!(divider.multi, 32);
let divider = FREQ_DIVIDERS
.iter()
.find(|d| 500_000_000 < d.max_freq)
.expect("should find divider for 500 MHz");
assert_eq!(divider.multi, 6);
let divider = FREQ_DIVIDERS
.iter()
.find(|d| 30_000_000 < d.max_freq)
.expect("should find divider for 30 MHz");
assert_eq!(divider.multi, 96);
let divider = FREQ_DIVIDERS
.iter()
.find(|d| 700_000_000 < d.max_freq)
.expect("should find divider for 700 MHz");
assert_eq!(divider.multi, 4);
let divider = FREQ_DIVIDERS
.iter()
.find(|d| 1_000_000_000 < d.max_freq)
.expect("should find divider for 1 GHz");
assert_eq!(divider.multi, 2);
}
#[test]
fn test_fc0013_differs_from_fc0012_dividers() {
assert_eq!(FREQ_DIVIDERS.len(), 11);
assert_eq!(FREQ_DIVIDERS[FREQ_DIVIDERS.len() - 1].multi, 2);
assert_eq!(FREQ_DIVIDERS[9].multi, 4);
assert_eq!(FREQ_DIVIDERS[9].max_freq, 950_000_000);
}
#[test]
fn test_vhf_track_thresholds() {
assert_eq!(VHF_TRACK_THRESH_7, 177_500_000);
assert_eq!(VHF_TRACK_THRESH_6, 184_500_000);
assert_eq!(VHF_TRACK_THRESH_5, 191_500_000);
assert_eq!(VHF_TRACK_THRESH_4, 198_500_000);
assert_eq!(VHF_TRACK_THRESH_3, 205_500_000);
assert_eq!(VHF_TRACK_THRESH_2, 219_500_000);
}
#[test]
fn test_init_reg_values_match_c_source() {
assert_eq!(INIT_REGS[0], 0x09); assert_eq!(INIT_REGS[1], 0x16); assert_eq!(INIT_REGS[5], 0x02); assert_eq!(INIT_REGS[6], 0x0a); assert_eq!(INIT_REGS[7], 0xff); assert_eq!(INIT_REGS[8], 0x6e); assert_eq!(INIT_REGS[11], 0xfc); assert_eq!(INIT_REGS[12], 0x01); assert_eq!(INIT_REGS[19], 0x50); assert_eq!(INIT_REGS[20], 0x01); }
}