aboutsummaryrefslogtreecommitdiffstats
path: root/src/tuner_r82xx.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tuner_r82xx.c')
-rw-r--r--src/tuner_r82xx.c192
1 files changed, 142 insertions, 50 deletions
diff --git a/src/tuner_r82xx.c b/src/tuner_r82xx.c
index 997abd7..6de6b24 100644
--- a/src/tuner_r82xx.c
+++ b/src/tuner_r82xx.c
@@ -25,6 +25,7 @@
#include <stdio.h>
#include <stdint.h>
#include <string.h>
+#include <stdbool.h>
#include "rtlsdr_i2c.h"
#include "tuner_r82xx.h"
@@ -33,6 +34,10 @@
#define MHZ(x) ((x)*1000*1000)
#define KHZ(x) ((x)*1000)
+#define HF 1
+#define VHF 2
+#define UHF 3
+
/*
* Static constants
*/
@@ -239,6 +244,7 @@ static void shadow_store(struct r82xx_priv *priv, uint8_t reg, const uint8_t *va
if (r < 0) {
len += r;
+ val -= r;
r = 0;
}
if (len <= 0)
@@ -249,11 +255,29 @@ static void shadow_store(struct r82xx_priv *priv, uint8_t reg, const uint8_t *va
memcpy(&priv->regs[r], val, len);
}
+static bool shadow_equal(struct r82xx_priv *priv, uint8_t reg, const uint8_t *val,
+ int len)
+{
+ int r = reg - REG_SHADOW_START;
+
+ if (r < 0 || len < 0 || len > NUM_REGS - r)
+ return false;
+
+ if (memcmp(&priv->regs[r], val, len) == 0)
+ return true;
+
+ return false;
+}
+
static int r82xx_write(struct r82xx_priv *priv, uint8_t reg, const uint8_t *val,
unsigned int len)
{
int rc, size, pos = 0;
+ /* Avoid setting registers unnecessarily since it's slow */
+ if (shadow_equal(priv, reg, val, len))
+ return 0;
+
/* Store the shadow registers */
shadow_store(priv, reg, val, len);
@@ -420,17 +444,21 @@ static int r82xx_set_mux(struct r82xx_priv *priv, uint32_t freq)
return rc;
}
+static inline uint8_t mask_reg8(uint8_t reg, uint8_t val, uint8_t mask)
+{
+ return (reg & ~mask) | (val & mask);
+}
+
static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq)
{
int rc, i;
unsigned sleep_time = 10000;
uint64_t vco_freq;
- uint32_t vco_fra; /* VCO contribution by SDM (kHz) */
- uint32_t vco_min = 1770000;
- uint32_t vco_max = vco_min * 2;
- uint32_t freq_khz, pll_ref, pll_ref_khz;
- uint16_t n_sdm = 2;
- uint16_t sdm = 0;
+ uint64_t vco_div;
+ uint32_t vco_min = 1770000; /* kHz */
+ uint32_t vco_max = vco_min * 2; /* kHz */
+ uint32_t freq_khz, pll_ref;
+ uint32_t sdm = 0;
uint8_t mix_div = 2;
uint8_t div_buf = 0;
uint8_t div_num = 0;
@@ -438,25 +466,24 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq)
uint8_t refdiv2 = 0;
uint8_t ni, si, nint, vco_fine_tune, val;
uint8_t data[5];
+ uint8_t regs[7];
/* Frequency in kHz */
freq_khz = (freq + 500) / 1000;
pll_ref = priv->cfg->xtal;
- pll_ref_khz = (priv->cfg->xtal + 500) / 1000;
-
- rc = r82xx_write_reg_mask(priv, 0x10, refdiv2, 0x10);
- if (rc < 0)
- return rc;
/* set pll autotune = 128kHz */
rc = r82xx_write_reg_mask(priv, 0x1a, 0x00, 0x0c);
if (rc < 0)
return rc;
+ /* regs 0x10 to 0x16 */
+ memcpy(regs, &priv->regs[0x10 - REG_SHADOW_START], 7);
+
+ regs[0] = mask_reg8(regs[0], refdiv2, 0x10);
+
/* set VCO current = 100 */
- rc = r82xx_write_reg_mask(priv, 0x12, 0x80, 0xe0);
- if (rc < 0)
- return rc;
+ regs[2] = mask_reg8(regs[2], 0x80, 0xe0);
/* Calculate divider */
while (mix_div <= 64) {
@@ -486,13 +513,27 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq)
else if (vco_fine_tune < vco_power_ref)
div_num = div_num + 1;
- rc = r82xx_write_reg_mask(priv, 0x10, div_num << 5, 0xe0);
- if (rc < 0)
- return rc;
+ regs[0] = mask_reg8(regs[0], div_num << 5, 0xe0);
vco_freq = (uint64_t)freq * (uint64_t)mix_div;
- nint = vco_freq / (2 * pll_ref);
- vco_fra = (vco_freq - 2 * pll_ref * nint) / 1000;
+
+ /* We want to approximate:
+ * vco_freq / (2 * pll_ref)
+ *
+ * in the form:
+ * nint + sdm/65536
+ *
+ * where nint,sdm are integers and 0 < nint, 0 <= sdm < 65536
+ *
+ * Scaling to fixed point and rounding:
+ *
+ * vco_div = 65536*(nint + sdm/65536) = int( 0.5 + 65536 * vco_freq / (2 * pll_ref) )
+ * vco_div = 65536*nint + sdm = int( (pll_ref + 65536 * vco_freq) / (2 * pll_ref) )
+ */
+
+ vco_div = (pll_ref + 65536 * vco_freq) / (2 * pll_ref);
+ nint = (uint32_t) (vco_div / 65536);
+ sdm = (uint32_t) (vco_div % 65536);
if (nint > ((128 / vco_power_ref) - 1)) {
fprintf(stderr, "[R82XX] No valid PLL values for %u Hz!\n", freq);
@@ -502,35 +543,20 @@ static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq)
ni = (nint - 13) / 4;
si = nint - 4 * ni - 13;
- rc = r82xx_write_reg(priv, 0x14, ni + (si << 6));
- if (rc < 0)
- return rc;
+ regs[4] = ni + (si << 6);
/* pw_sdm */
- if (!vco_fra)
+ if (sdm == 0)
val = 0x08;
else
val = 0x00;
- rc = r82xx_write_reg_mask(priv, 0x12, val, 0x08);
- if (rc < 0)
- return rc;
+ regs[2] = mask_reg8(regs[2], val, 0x08);
- /* sdm calculator */
- while (vco_fra > 1) {
- if (vco_fra > (2 * pll_ref_khz / n_sdm)) {
- sdm = sdm + 32768 / (n_sdm / 2);
- vco_fra = vco_fra - 2 * pll_ref_khz / n_sdm;
- if (n_sdm >= 0x8000)
- break;
- }
- n_sdm <<= 1;
- }
+ regs[5] = sdm & 0xff;
+ regs[6] = sdm >> 8;
- rc = r82xx_write_reg(priv, 0x16, sdm >> 8);
- if (rc < 0)
- return rc;
- rc = r82xx_write_reg(priv, 0x15, sdm & 0xff);
+ rc = r82xx_write(priv, 0x10, regs, 7);
if (rc < 0)
return rc;
@@ -1098,8 +1124,23 @@ int r82xx_set_bandwidth(struct r82xx_priv *priv, int bw, uint32_t rate)
int r82xx_set_freq(struct r82xx_priv *priv, uint32_t freq)
{
int rc = -1;
- uint32_t lo_freq = freq + priv->int_freq;
+ int is_rtlsdr_blog_v4;
+ uint32_t upconvert_freq;
+ uint32_t lo_freq;
uint8_t air_cable1_in;
+ uint8_t open_d;
+ uint8_t band;
+ uint8_t cable_2_in;
+ uint8_t cable_1_in;
+ uint8_t air_in;
+
+ is_rtlsdr_blog_v4 = rtlsdr_check_dongle_model(priv->rtl_dev, "RTLSDRBlog", "Blog V4");
+
+ /* if it's an RTL-SDR Blog V4, automatically upconvert by 28.8 MHz if we tune to HF
+ * so that we don't need to manually set any upconvert offset in the SDR software */
+ upconvert_freq = is_rtlsdr_blog_v4 ? ((freq < MHZ(28.8)) ? (freq + MHZ(28.8)) : freq) : freq;
+
+ lo_freq = upconvert_freq + priv->int_freq;
rc = r82xx_set_mux(priv, lo_freq);
if (rc < 0)
@@ -1109,16 +1150,66 @@ int r82xx_set_freq(struct r82xx_priv *priv, uint32_t freq)
if (rc < 0 || !priv->has_lock)
goto err;
- /* switch between 'Cable1' and 'Air-In' inputs on sticks with
- * R828D tuner. We switch at 345 MHz, because that's where the
- * noise-floor has about the same level with identical LNA
- * settings. The original driver used 320 MHz. */
- air_cable1_in = (freq > MHZ(345)) ? 0x00 : 0x60;
+ if (is_rtlsdr_blog_v4) {
+ /* determine if notch filters should be on or off notches are turned OFF
+ * when tuned within the notch band and ON when tuned outside the notch band.
+ */
+ open_d = (freq <= MHZ(2.2) || (freq >= MHZ(85) && freq <= MHZ(112)) || (freq >= MHZ(172) && freq <= MHZ(242))) ? 0x00 : 0x08;
+ rc = r82xx_write_reg_mask(priv, 0x17, open_d, 0x08);
+
+ if (rc < 0)
+ return rc;
+
+ /* select tuner band based on frequency and only switch if there is a band change
+ *(to avoid excessive register writes when tuning rapidly)
+ */
+ band = (freq <= MHZ(28.8)) ? HF : ((freq > MHZ(28.8) && freq < MHZ(250)) ? VHF : UHF);
+
+ /* switch between tuner inputs on the RTL-SDR Blog V4 */
+ if (band != priv->input) {
+ priv->input = band;
- if ((priv->cfg->rafael_chip == CHIP_R828D) &&
- (air_cable1_in != priv->input)) {
- priv->input = air_cable1_in;
- rc = r82xx_write_reg_mask(priv, 0x05, air_cable1_in, 0x60);
+ /* activate cable 2 (HF input) */
+ cable_2_in = (band == HF) ? 0x08 : 0x00;
+ rc = r82xx_write_reg_mask(priv, 0x06, cable_2_in, 0x08);
+
+ if (rc < 0)
+ goto err;
+
+ /* Control upconverter GPIO switch on newer batches */
+ rc = rtlsdr_set_bias_tee_gpio(priv->rtl_dev, 5, !cable_2_in);
+
+ if (rc < 0)
+ goto err;
+
+ /* activate cable 1 (VHF input) */
+ cable_1_in = (band == VHF) ? 0x40 : 0x00;
+ rc = r82xx_write_reg_mask(priv, 0x05, cable_1_in, 0x40);
+
+ if (rc < 0)
+ goto err;
+
+ /* activate air_in (UHF input) */
+ air_in = (band == UHF) ? 0x00 : 0x20;
+ rc = r82xx_write_reg_mask(priv, 0x05, air_in, 0x20);
+
+ if (rc < 0)
+ goto err;
+ }
+ }
+ else /* Standard R828D dongle*/
+ {
+ /* switch between 'Cable1' and 'Air-In' inputs on sticks with
+ * R828D tuner. We switch at 345 MHz, because that's where the
+ * noise-floor has about the same level with identical LNA
+ * settings. The original driver used 320 MHz. */
+ air_cable1_in = (freq > MHZ(345)) ? 0x00 : 0x60;
+
+ if ((priv->cfg->rafael_chip == CHIP_R828D) &&
+ (air_cable1_in != priv->input)) {
+ priv->input = air_cable1_in;
+ rc = r82xx_write_reg_mask(priv, 0x05, air_cable1_in, 0x60);
+ }
}
err:
@@ -1248,6 +1339,7 @@ int r82xx_init(struct r82xx_priv *priv)
priv->xtal_cap_sel = XTAL_HIGH_CAP_0P;
/* Initialize registers */
+ memset(priv->regs, 0, NUM_REGS);
rc = r82xx_write(priv, 0x05,
r82xx_init_array, sizeof(r82xx_init_array));