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 = 0xa1;
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_AGC_LNA_FORCE: u8 = 0x0d;
const REG_VCO_CALIB: u8 = 0x0e;
const REG_LNA_GAIN: u8 = 0x13;
const NUM_INIT_REGS: usize = 21;
const INIT_REGS: [u8; NUM_INIT_REGS] = [
0x05, 0x10, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xff, 0x6e, 0xb8, 0x82, 0xfc, 0x02, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x04, ];
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;
struct FreqDivider {
max_freq: u32,
multi: u8,
reg5: u8,
reg6: u8,
}
const FREQ_DIVIDERS: [FreqDivider; 10] = [
FreqDivider {
max_freq: 37_084_000,
multi: 96,
reg5: 0x82,
reg6: 0x00,
},
FreqDivider {
max_freq: 55_625_000,
multi: 64,
reg5: 0x82,
reg6: 0x02,
},
FreqDivider {
max_freq: 74_167_000,
multi: 48,
reg5: 0x42,
reg6: 0x00,
},
FreqDivider {
max_freq: 111_250_000,
multi: 32,
reg5: 0x42,
reg6: 0x02,
},
FreqDivider {
max_freq: 148_334_000,
multi: 24,
reg5: 0x22,
reg6: 0x00,
},
FreqDivider {
max_freq: 222_500_000,
multi: 16,
reg5: 0x22,
reg6: 0x02,
},
FreqDivider {
max_freq: 296_667_000,
multi: 12,
reg5: 0x12,
reg6: 0x00,
},
FreqDivider {
max_freq: 445_000_000,
multi: 8,
reg5: 0x12,
reg6: 0x02,
},
FreqDivider {
max_freq: 593_334_000,
multi: 6,
reg5: 0x0a,
reg6: 0x00,
},
FreqDivider {
max_freq: u32::MAX,
multi: 4,
reg5: 0x0a,
reg6: 0x02,
},
];
pub const FC0012_GAINS: [i32; 5] = [-99, -40, 71, 179, 192];
const GAIN_NEG_9_9_DB: u8 = 0x02;
const GAIN_NEG_4_0_DB: u8 = 0x00;
const GAIN_7_1_DB: u8 = 0x08;
const GAIN_17_9_DB: u8 = 0x17;
const GAIN_19_2_DB: u8 = 0x10;
const GAIN_THRESH_NEG_9_9: i32 = -40;
const GAIN_THRESH_NEG_4_0: i32 = 71;
const GAIN_THRESH_7_1: i32 = 179;
const GAIN_THRESH_17_9: i32 = 192;
const PLL_REG1_MAX: u8 = 15;
const PLL_REG2_MIN: u8 = 0x0b;
pub struct Fc0012Tuner {
xtal: u32,
bandwidth: u32,
freq: u32,
}
impl Fc0012Tuner {
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)
}
#[allow(clippy::cast_possible_truncation)]
fn set_params(
&self,
handle: &rusb::DeviceHandle<rusb::GlobalContext>,
freq: u32,
bandwidth: u32,
) -> Result<(), RtlSdrError> {
let xtal_freq_div_2 = self.xtal / 2;
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: "FC0012",
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: "FC0012",
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;
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)?;
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(())
}
}
impl Tuner for Fc0012Tuner {
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 gain_bits = if gain < GAIN_THRESH_NEG_9_9 {
GAIN_NEG_9_9_DB
} else if gain < GAIN_THRESH_NEG_4_0 {
GAIN_NEG_4_0_DB
} else if gain < GAIN_THRESH_7_1 {
GAIN_7_1_DB
} else if gain < GAIN_THRESH_17_9 {
GAIN_17_9_DB
} else {
GAIN_19_2_DB
};
tmp |= gain_bits;
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> {
if manual {
self.write_reg(handle, REG_AGC_LNA_FORCE, 0x02)?;
} else {
self.write_reg(handle, REG_AGC_LNA_FORCE, 0x00)?;
}
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 FC0012_GAINS.windows(2) {
assert!(w[0] < w[1], "gains must be in ascending order");
}
}
#[test]
fn test_gains_count() {
assert_eq!(FC0012_GAINS.len(), 5);
}
#[test]
fn test_gains_values() {
assert_eq!(FC0012_GAINS[0], -99); assert_eq!(FC0012_GAINS[1], -40); assert_eq!(FC0012_GAINS[2], 71); assert_eq!(FC0012_GAINS[3], 179); assert_eq!(FC0012_GAINS[4], 192); }
#[test]
fn test_i2c_addr() {
assert_eq!(I2C_ADDR, 0xc6);
}
#[test]
fn test_check_val() {
assert_eq!(CHECK_VAL, 0xa1);
}
#[test]
fn test_new_defaults() {
let tuner = Fc0012Tuner::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);
}
#[test]
fn test_gain_threshold_values() {
assert_eq!(GAIN_THRESH_NEG_9_9, -40);
assert_eq!(GAIN_THRESH_NEG_4_0, 71);
assert_eq!(GAIN_THRESH_7_1, 179);
assert_eq!(GAIN_THRESH_17_9, 192);
}
}