fix: RTL-SDR bug fixes and accuracy improvements
This commit is contained in:
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 8
|
#define MONTAUK_BUILD_NUMBER 9
|
||||||
|
|||||||
@@ -155,7 +155,12 @@ namespace Drivers::Radio::Sdr {
|
|||||||
uint32_t space = RING_BYTES - r.count;
|
uint32_t space = RING_BYTES - r.count;
|
||||||
uint32_t n = len;
|
uint32_t n = len;
|
||||||
uint32_t dropped = 0;
|
uint32_t dropped = 0;
|
||||||
if (n > space) { dropped = n - space; n = space; }
|
if (n > space) {
|
||||||
|
// Truncate to a whole number of I/Q byte pairs: dropping an odd
|
||||||
|
// count would swap I and Q for the rest of the stream.
|
||||||
|
n = space & ~1u;
|
||||||
|
dropped = len - n;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t first = RING_BYTES - r.head;
|
uint32_t first = RING_BYTES - r.head;
|
||||||
if (first > n) first = n;
|
if (first > n) first = n;
|
||||||
@@ -241,6 +246,9 @@ namespace Drivers::Radio::Sdr {
|
|||||||
int Start(int handle) {
|
int Start(int handle) {
|
||||||
Receiver* r = Lookup(handle, true);
|
Receiver* r = Lookup(handle, true);
|
||||||
if (!r) return -1;
|
if (!r) return -1;
|
||||||
|
// Already streaming: a second Start must not re-arm the driver's
|
||||||
|
// transfer pool (it would double-queue every buffer).
|
||||||
|
if (r->streaming) return 0;
|
||||||
|
|
||||||
r->lock.Acquire();
|
r->lock.Acquire();
|
||||||
r->head = r->count = 0; // discard stale samples before (re)starting
|
r->head = r->count = 0; // discard stale samples before (re)starting
|
||||||
|
|||||||
@@ -120,12 +120,16 @@ namespace Drivers::USB::Radio {
|
|||||||
return WriteReg(d, reg, merged);
|
return WriteReg(d, reg, merged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read `len` bytes; the R820T always returns starting at register 0. The
|
// Read `len` bytes starting at register 0. The read pointer must be set
|
||||||
// status registers are bit-reversed on the wire, so the PLL/VCO read paths
|
// to 0 with an address-only write first: a preceding register write leaves
|
||||||
// un-reverse each byte to recover the logical value.
|
// it pointing past the last register written, which would return the wrong
|
||||||
|
// registers here. The status registers are bit-reversed on the wire, so
|
||||||
|
// the PLL/VCO read paths un-reverse each byte to recover the logical value.
|
||||||
static bool Read(R820tDev& d, uint8_t* out, uint8_t len) {
|
static bool Read(R820tDev& d, uint8_t* out, uint8_t len) {
|
||||||
uint8_t raw[16];
|
uint8_t raw[16];
|
||||||
if (len > sizeof(raw)) len = sizeof(raw);
|
if (len > sizeof(raw)) len = sizeof(raw);
|
||||||
|
uint8_t ptr = 0x00;
|
||||||
|
if (!RtlI2cWrite(d.slotId, R820T_I2C_ADDR, &ptr, 1)) return false;
|
||||||
if (!RtlI2cRead(d.slotId, R820T_I2C_ADDR, raw, len)) return false;
|
if (!RtlI2cRead(d.slotId, R820T_I2C_ADDR, raw, len)) return false;
|
||||||
for (uint8_t i = 0; i < len; i++) out[i] = BitRev(raw[i]);
|
for (uint8_t i = 0; i < len; i++) out[i] = BitRev(raw[i]);
|
||||||
return true;
|
return true;
|
||||||
@@ -135,6 +139,79 @@ namespace Drivers::USB::Radio {
|
|||||||
// Detection / init
|
// Detection / init
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
|
static bool SetPll(R820tDev& d, uint64_t freqHz); // defined below
|
||||||
|
|
||||||
|
// IF filter setup for the SDR receive path (the "BW < 6 MHz" digital-TV
|
||||||
|
// profile): calibrate the filter at a 56 MHz LO, then program the filter
|
||||||
|
// code, bandwidth / HP corner, image-rejection side and filter gain.
|
||||||
|
static bool ApplyIfFilterConfig(R820tDev& d) {
|
||||||
|
const uint8_t filtGain = 0x10; // +3 dB, 6 MHz on
|
||||||
|
const uint8_t imgR = 0x00; // image negative
|
||||||
|
const uint8_t filtQ = 0x10; // low Q
|
||||||
|
const uint8_t hpCor = 0x6b; // 1.7 MHz disable, +2 cap, 1.0 MHz corner
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
ok &= WriteRegMask(d, 0x0c, 0x00, 0x0f); // init flag & xtal check result
|
||||||
|
ok &= WriteRegMask(d, 0x13, 49, 0x3f); // version number
|
||||||
|
ok &= WriteRegMask(d, 0x1d, 0x00, 0x38); // LT gain test
|
||||||
|
|
||||||
|
// Filter calibration: park the PLL at 56 MHz, pulse the calibration
|
||||||
|
// trigger, read the resulting filter code back (status reg 4, low
|
||||||
|
// nibble). One retry; 0x0f means the calibration failed (use 0).
|
||||||
|
uint8_t calCode = 0;
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
ok &= WriteRegMask(d, 0x0b, hpCor, 0x60); // filt_cap
|
||||||
|
ok &= WriteRegMask(d, 0x0f, 0x04, 0x04); // calibration clock on
|
||||||
|
ok &= WriteRegMask(d, 0x10, 0x00, 0x03); // xtal cap 0 pF for PLL
|
||||||
|
SetPll(d, 56000000ull); // lock not required here
|
||||||
|
ok &= WriteRegMask(d, 0x0b, 0x10, 0x10); // start trigger
|
||||||
|
ok &= WriteRegMask(d, 0x0b, 0x00, 0x10); // stop trigger
|
||||||
|
ok &= WriteRegMask(d, 0x0f, 0x00, 0x04); // calibration clock off
|
||||||
|
|
||||||
|
uint8_t data[5] = {0};
|
||||||
|
if (!Read(d, data, sizeof(data))) return false;
|
||||||
|
calCode = (uint8_t)(data[4] & 0x0f);
|
||||||
|
if (calCode && calCode != 0x0f) break;
|
||||||
|
}
|
||||||
|
if (calCode == 0x0f) calCode = 0;
|
||||||
|
|
||||||
|
ok &= WriteRegMask(d, 0x0a, (uint8_t)(filtQ | calCode), 0x1f);
|
||||||
|
ok &= WriteRegMask(d, 0x0b, hpCor, 0xef); // bandwidth, filter gain, HP corner
|
||||||
|
ok &= WriteRegMask(d, 0x07, imgR, 0x80); // image rejection side
|
||||||
|
ok &= WriteRegMask(d, 0x06, filtGain, 0x30);// filt_3dB
|
||||||
|
ok &= WriteRegMask(d, 0x1e, 0x60, 0x60); // channel filter extension @ LNA max-1
|
||||||
|
ok &= WriteRegMask(d, 0x05, 0x01, 0x80); // loop-through
|
||||||
|
ok &= WriteRegMask(d, 0x1f, 0x00, 0x80); // loop-through attenuation enable
|
||||||
|
ok &= WriteRegMask(d, 0x0f, 0x00, 0x80); // filter extension widest: off
|
||||||
|
ok &= WriteRegMask(d, 0x19, 0x60, 0x60); // RF poly filter current: min
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive-path operating point for the digital/SDR profile: LNA and mixer
|
||||||
|
// detector top points and thresholds, input select, charge-pump and
|
||||||
|
// divider-buffer currents, AGC clock rate.
|
||||||
|
static bool ApplySysFreqConfig(R820tDev& d) {
|
||||||
|
bool ok = true;
|
||||||
|
ok &= WriteRegMask(d, 0x1d, 0xe5, 0xc7); // LNA top (detect bw 3, top 4)
|
||||||
|
ok &= WriteRegMask(d, 0x1c, 0x24, 0xf8); // mixer top 13, top-1, low-discharge
|
||||||
|
ok &= WriteReg(d, 0x0d, 0x53); // LNA vth 0.84 / vtl 0.64
|
||||||
|
ok &= WriteReg(d, 0x0e, 0x75); // mixer vth 1.04 / vtl 0.84
|
||||||
|
ok &= WriteRegMask(d, 0x05, 0x00, 0x60); // air-in input select
|
||||||
|
ok &= WriteRegMask(d, 0x06, 0x00, 0x08); // cable-2 input off
|
||||||
|
ok &= WriteRegMask(d, 0x11, 0x38, 0x38); // charge-pump current: auto
|
||||||
|
ok &= WriteRegMask(d, 0x17, 0x30, 0x30); // divider buffer current 150u
|
||||||
|
ok &= WriteRegMask(d, 0x0a, 0x40, 0x60); // filter current: low
|
||||||
|
ok &= WriteRegMask(d, 0x1d, 0x00, 0x38); // LNA top: lowest
|
||||||
|
ok &= WriteRegMask(d, 0x1c, 0x00, 0x04); // normal mode
|
||||||
|
ok &= WriteRegMask(d, 0x06, 0x00, 0x40); // pre-detect off
|
||||||
|
ok &= WriteRegMask(d, 0x1a, 0x30, 0x30); // AGC clock 250 Hz
|
||||||
|
ok &= WriteRegMask(d, 0x1d, 0x18, 0x38); // LNA top = 3
|
||||||
|
ok &= WriteRegMask(d, 0x1c, 0x24, 0x04); // mixer top bit
|
||||||
|
ok &= WriteRegMask(d, 0x1e, 0x0e, 0x1f); // LNA discharge current 14
|
||||||
|
ok &= WriteRegMask(d, 0x1a, 0x20, 0x30); // AGC clock 60 Hz
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
bool R820tDetect(uint8_t slotId) {
|
bool R820tDetect(uint8_t slotId) {
|
||||||
// Match the reference driver's chip-id probe: set the read pointer to
|
// Match the reference driver's chip-id probe: set the read pointer to
|
||||||
// register 0, then read one byte *without* bit-reversal and compare it
|
// register 0, then read one byte *without* bit-reversal and compare it
|
||||||
@@ -161,13 +238,21 @@ namespace Drivers::USB::Radio {
|
|||||||
d.hasLock = false;
|
d.hasLock = false;
|
||||||
d.inited = false;
|
d.inited = false;
|
||||||
|
|
||||||
// Load the init register block. The IF low-pass filter is left at the
|
// Load the init register block, then bring the IF filter and the
|
||||||
// init-array default (widest practical for the SDR receive path); a
|
// receive-path operating point to the SDR profile (incl. the 56 MHz
|
||||||
// dedicated bandwidth calibration is not performed.
|
// filter calibration).
|
||||||
if (!Write(d, 0x05, kInitArray, sizeof(kInitArray))) {
|
if (!Write(d, 0x05, kInitArray, sizeof(kInitArray))) {
|
||||||
KernelLogStream(ERROR, "R820T") << "init register write failed";
|
KernelLogStream(ERROR, "R820T") << "init register write failed";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!ApplyIfFilterConfig(d)) {
|
||||||
|
KernelLogStream(ERROR, "R820T") << "IF filter configuration failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ApplySysFreqConfig(d)) {
|
||||||
|
KernelLogStream(ERROR, "R820T") << "receive path configuration failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
d.inited = true;
|
d.inited = true;
|
||||||
KernelLogStream(OK, "R820T") << "Tuner initialised (xtal="
|
KernelLogStream(OK, "R820T") << "Tuner initialised (xtal="
|
||||||
@@ -209,7 +294,6 @@ namespace Drivers::USB::Radio {
|
|||||||
const uint32_t vcoMinKhz = 1770000; // 1.77 GHz
|
const uint32_t vcoMinKhz = 1770000; // 1.77 GHz
|
||||||
const uint32_t vcoMaxKhz = vcoMinKhz * 2; // 3.54 GHz
|
const uint32_t vcoMaxKhz = vcoMinKhz * 2; // 3.54 GHz
|
||||||
uint32_t pllRef = d.xtal;
|
uint32_t pllRef = d.xtal;
|
||||||
uint32_t pllRefKhz = (d.xtal + 500) / 1000;
|
|
||||||
uint32_t freqKhz = (uint32_t)((freqHz + 500) / 1000);
|
uint32_t freqKhz = (uint32_t)((freqHz + 500) / 1000);
|
||||||
|
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
@@ -240,28 +324,21 @@ namespace Drivers::USB::Radio {
|
|||||||
ok &= WriteRegMask(d, 0x10, (uint8_t)(divNum << 5), 0xe0);
|
ok &= WriteRegMask(d, 0x10, (uint8_t)(divNum << 5), 0xe0);
|
||||||
|
|
||||||
uint64_t vcoFreq = freqHz * (uint64_t)mixDiv;
|
uint64_t vcoFreq = freqHz * (uint64_t)mixDiv;
|
||||||
uint32_t nint = (uint32_t)(vcoFreq / (2u * pllRef));
|
|
||||||
uint32_t vcoFra = (uint32_t)((vcoFreq - 2ull * pllRef * nint) / 1000);
|
// Exact fractional-N split: vcoDiv = round(65536 * vcoFreq / (2*ref)).
|
||||||
|
// The top bits are the integer divider, the low 16 bits feed the
|
||||||
|
// sigma-delta modulator directly (no iterative approximation).
|
||||||
|
uint64_t vcoDiv = (pllRef + 65536ull * vcoFreq) / (2ull * pllRef);
|
||||||
|
uint32_t nint = (uint32_t)(vcoDiv >> 16);
|
||||||
|
uint16_t sdm = (uint16_t)(vcoDiv & 0xffff);
|
||||||
|
|
||||||
if (nint < 13) nint = 13; // keep ni/si arithmetic well-defined
|
if (nint < 13) nint = 13; // keep ni/si arithmetic well-defined
|
||||||
uint8_t ni = (uint8_t)((nint - 13) / 4);
|
uint8_t ni = (uint8_t)((nint - 13) / 4);
|
||||||
uint8_t si = (uint8_t)(nint - 4 * ni - 13);
|
uint8_t si = (uint8_t)(nint - 4 * ni - 13);
|
||||||
ok &= WriteReg(d, 0x14, (uint8_t)(ni + (si << 6)));
|
ok &= WriteReg(d, 0x14, (uint8_t)(ni + (si << 6)));
|
||||||
|
|
||||||
// Sigma-delta fractional path.
|
// Sigma-delta fractional path (powered down when the fraction is 0).
|
||||||
ok &= WriteRegMask(d, 0x12, vcoFra ? 0x00 : 0x08, 0x08);
|
ok &= WriteRegMask(d, 0x12, sdm ? 0x00 : 0x08, 0x08);
|
||||||
|
|
||||||
uint16_t nSdm = 2;
|
|
||||||
uint16_t sdm = 0;
|
|
||||||
while (vcoFra > 1) {
|
|
||||||
uint32_t step = 2 * pllRefKhz / nSdm;
|
|
||||||
if (vcoFra > step) {
|
|
||||||
sdm = (uint16_t)(sdm + 32768 / (nSdm / 2));
|
|
||||||
vcoFra -= step;
|
|
||||||
if (nSdm >= 0x8000) break;
|
|
||||||
}
|
|
||||||
nSdm <<= 1;
|
|
||||||
}
|
|
||||||
ok &= WriteReg(d, 0x16, (uint8_t)(sdm >> 8));
|
ok &= WriteReg(d, 0x16, (uint8_t)(sdm >> 8));
|
||||||
ok &= WriteReg(d, 0x15, (uint8_t)(sdm & 0xff));
|
ok &= WriteReg(d, 0x15, (uint8_t)(sdm & 0xff));
|
||||||
|
|
||||||
@@ -309,9 +386,10 @@ namespace Drivers::USB::Radio {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Air-in vs Cable-1 input select crosses over at 345 MHz.
|
// NOTE: the Air-in vs Cable-1 input switch at 345 MHz applies to the
|
||||||
uint8_t airCable1In = (rfHz > 345000000ull) ? 0x00 : 0x60;
|
// R828D only. The R820T/T2 uses the air input exclusively (selected
|
||||||
WriteRegMask(d, 0x05, airCable1In, 0x60);
|
// during init); writing the Cable-1 select on an R820T disconnects the
|
||||||
|
// antenna input below 345 MHz.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ namespace Drivers::USB::Radio {
|
|||||||
static int g_ppm = 0;
|
static int g_ppm = 0;
|
||||||
static int g_manual = 0; // tuner gain mode (0=auto)
|
static int g_manual = 0; // tuner gain mode (0=auto)
|
||||||
static int g_gain = 0; // tuner gain, tenths of dB
|
static int g_gain = 0; // tuner gain, tenths of dB
|
||||||
|
static int g_directSamp = 0; // 0=tuner path, 1=I ADC, 2=Q ADC
|
||||||
|
static uint64_t g_lastFreq = 0; // last successfully tuned freq (Hz)
|
||||||
|
|
||||||
// Bulk-IN streaming geometry. We keep BULK_POOL_BUFS transfers of
|
// Bulk-IN streaming geometry. We keep BULK_POOL_BUFS transfers of
|
||||||
// BULK_XFER_LEN bytes outstanding at once (multi-URB), so the RTL2832U FIFO
|
// BULK_XFER_LEN bytes outstanding at once (multi-URB), so the RTL2832U FIFO
|
||||||
@@ -92,14 +94,26 @@ namespace Drivers::USB::Radio {
|
|||||||
g_ctlBuf, false) == Xhci::CC_SUCCESS;
|
g_ctlBuf, false) == Xhci::CC_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t DemodRead(uint8_t page, uint16_t addr) {
|
||||||
|
if (!g_ctlBuf) return 0;
|
||||||
|
uint16_t raddr = (uint16_t)((addr << 8) | 0x20);
|
||||||
|
g_ctlBuf[0] = 0;
|
||||||
|
Xhci::ControlTransfer(g_slotId, CTRL_IN, 0, raddr, page, 1, g_ctlBuf, true);
|
||||||
|
return g_ctlBuf[0];
|
||||||
|
}
|
||||||
|
|
||||||
static bool DemodWrite(uint8_t page, uint16_t addr, uint16_t val, uint8_t len) {
|
static bool DemodWrite(uint8_t page, uint16_t addr, uint16_t val, uint8_t len) {
|
||||||
if (!g_ctlBuf) return false;
|
if (!g_ctlBuf) return false;
|
||||||
uint16_t waddr = (uint16_t)((addr << 8) | 0x20);
|
uint16_t waddr = (uint16_t)((addr << 8) | 0x20);
|
||||||
uint16_t index = (uint16_t)(0x10 | page);
|
uint16_t index = (uint16_t)(0x10 | page);
|
||||||
g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8);
|
g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8);
|
||||||
g_ctlBuf[1] = (uint8_t)(val & 0xff);
|
g_ctlBuf[1] = (uint8_t)(val & 0xff);
|
||||||
return Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, waddr, index, len,
|
bool ok = Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, waddr, index, len,
|
||||||
g_ctlBuf, false) == Xhci::CC_SUCCESS;
|
g_ctlBuf, false) == Xhci::CC_SUCCESS;
|
||||||
|
// Dummy status read after every demod write (reference behaviour);
|
||||||
|
// acts as a write barrier so the register latches before the next op.
|
||||||
|
DemodRead(0x0a, 0x01);
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SetI2cRepeater(bool on) {
|
static void SetI2cRepeater(bool on) {
|
||||||
@@ -246,9 +260,16 @@ namespace Drivers::USB::Radio {
|
|||||||
|
|
||||||
static int DoSetFreq(uint64_t hz) {
|
static int DoSetFreq(uint64_t hz) {
|
||||||
if (!EnsureInit()) return -1;
|
if (!EnsureInit()) return -1;
|
||||||
|
if (g_directSamp) {
|
||||||
|
// Tuner is bypassed: tuning is the demod's digital downconverter.
|
||||||
|
SetIfFreq((uint32_t)hz);
|
||||||
|
g_lastFreq = hz;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
SetI2cRepeater(true);
|
SetI2cRepeater(true);
|
||||||
bool ok = R820tSetFreq(g_tuner, hz);
|
bool ok = R820tSetFreq(g_tuner, hz);
|
||||||
SetI2cRepeater(false);
|
SetI2cRepeater(false);
|
||||||
|
if (ok) g_lastFreq = hz;
|
||||||
return ok ? 0 : -1;
|
return ok ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,9 +277,12 @@ namespace Drivers::USB::Radio {
|
|||||||
if (!EnsureInit()) return -1;
|
if (!EnsureInit()) return -1;
|
||||||
// The RTL2832 resampler does not cover 300k..900k.
|
// The RTL2832 resampler does not cover 300k..900k.
|
||||||
if (rate <= 225000 || rate > 3200000 ||
|
if (rate <= 225000 || rate > 3200000 ||
|
||||||
(rate > 300000 && rate < 900000)) return -1;
|
(rate > 300000 && rate <= 900000)) return -1;
|
||||||
|
|
||||||
uint32_t ratio = (uint32_t)(((uint64_t)g_rtlXtal * TWO_POW22) / rate);
|
// The ratio uses the NOMINAL crystal frequency: ppm correction is
|
||||||
|
// applied by the demod's sample-frequency-offset registers below, so
|
||||||
|
// baking it into the ratio too would correct the rate twice.
|
||||||
|
uint32_t ratio = (uint32_t)(((uint64_t)RTL_XTAL * TWO_POW22) / rate);
|
||||||
ratio &= 0x0ffffffc;
|
ratio &= 0x0ffffffc;
|
||||||
DemodWrite(1, 0x9f, (uint16_t)((ratio >> 16) & 0xffff), 2);
|
DemodWrite(1, 0x9f, (uint16_t)((ratio >> 16) & 0xffff), 2);
|
||||||
DemodWrite(1, 0xa1, (uint16_t)(ratio & 0xffff), 2);
|
DemodWrite(1, 0xa1, (uint16_t)(ratio & 0xffff), 2);
|
||||||
@@ -266,7 +290,7 @@ namespace Drivers::USB::Radio {
|
|||||||
ApplySampleFreqCorrection();
|
ApplySampleFreqCorrection();
|
||||||
DemodWrite(1, 0x01, 0x14, 1); // soft reset
|
DemodWrite(1, 0x01, 0x14, 1); // soft reset
|
||||||
DemodWrite(1, 0x01, 0x10, 1);
|
DemodWrite(1, 0x01, 0x10, 1);
|
||||||
SetIfFreq(R82XX_IF);
|
SetIfFreq(g_directSamp ? (uint32_t)g_lastFreq : R82XX_IF);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +319,9 @@ namespace Drivers::USB::Radio {
|
|||||||
g_rtlXtal = (uint32_t)((int64_t)RTL_XTAL + (int64_t)RTL_XTAL * ppm / 1000000);
|
g_rtlXtal = (uint32_t)((int64_t)RTL_XTAL + (int64_t)RTL_XTAL * ppm / 1000000);
|
||||||
g_tuner.xtal = g_rtlXtal;
|
g_tuner.xtal = g_rtlXtal;
|
||||||
ApplySampleFreqCorrection();
|
ApplySampleFreqCorrection();
|
||||||
|
// The tuner PLL (and, in direct mode, the DDC) derive from the xtal;
|
||||||
|
// retune so the new correction actually takes effect.
|
||||||
|
if (g_lastFreq) return DoSetFreq(g_lastFreq);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,18 +337,25 @@ namespace Drivers::USB::Radio {
|
|||||||
SetI2cRepeater(true);
|
SetI2cRepeater(true);
|
||||||
R820tStandby(g_tuner);
|
R820tStandby(g_tuner);
|
||||||
SetI2cRepeater(false);
|
SetI2cRepeater(false);
|
||||||
DemodWrite(1, 0xb1, 0x1b, 1); // zero-IF path
|
DemodWrite(1, 0xb1, 0x1a, 1); // disable zero-IF
|
||||||
DemodWrite(0, 0x08, 0x4d, 1);
|
DemodWrite(1, 0x15, 0x00, 1); // no spectrum inversion
|
||||||
|
DemodWrite(0, 0x08, 0x4d, 1); // In-phase ADC input
|
||||||
DemodWrite(0, 0x06, (mode == 2) ? 0x90 : 0x80, 1); // Q vs I ADC
|
DemodWrite(0, 0x06, (mode == 2) ? 0x90 : 0x80, 1); // Q vs I ADC
|
||||||
SetIfFreq(0);
|
g_directSamp = mode;
|
||||||
DemodWrite(1, 0x15, 0x00, 1);
|
// Tuning now happens in the DDC; carry the current frequency over.
|
||||||
|
SetIfFreq((uint32_t)g_lastFreq);
|
||||||
} else {
|
} else {
|
||||||
// Restore the R820T2 low-IF receive path.
|
// Restore the R820T2 low-IF receive path. Standby powered the
|
||||||
DemodWrite(1, 0xb1, 0x1a, 1);
|
// tuner down, so it needs a full re-initialisation.
|
||||||
DemodWrite(0, 0x08, 0x4d, 1);
|
SetI2cRepeater(true);
|
||||||
DemodWrite(0, 0x06, 0x80, 1);
|
bool ok = R820tInit(g_tuner, g_slotId, g_rtlXtal, R82XX_IF);
|
||||||
|
SetI2cRepeater(false);
|
||||||
|
if (!ok) return -1;
|
||||||
SetIfFreq(R82XX_IF);
|
SetIfFreq(R82XX_IF);
|
||||||
DemodWrite(1, 0x15, 0x01, 1);
|
DemodWrite(1, 0x15, 0x01, 1); // enable spectrum inversion
|
||||||
|
DemodWrite(0, 0x06, 0x80, 1); // default ADC I/Q datapath
|
||||||
|
g_directSamp = 0;
|
||||||
|
if (g_lastFreq) return DoSetFreq(g_lastFreq);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -447,11 +481,10 @@ namespace Drivers::USB::Radio {
|
|||||||
bool IsRtlSdr(uint16_t vid, uint16_t pid) {
|
bool IsRtlSdr(uint16_t vid, uint16_t pid) {
|
||||||
if (vid != 0x0bda) return false; // Realtek Semiconductor
|
if (vid != 0x0bda) return false; // Realtek Semiconductor
|
||||||
switch (pid) {
|
switch (pid) {
|
||||||
|
// Only the two known RTL2832U ids. In particular 0x2831 is the
|
||||||
|
// RTL2831U, a DIFFERENT demod this driver cannot program.
|
||||||
case 0x2832: // RTL2832U (generic)
|
case 0x2832: // RTL2832U (generic)
|
||||||
case 0x2838: // RTL2838 (most RTL-SDR.com dongles)
|
case 0x2838: // RTL2838 (most RTL-SDR.com dongles)
|
||||||
case 0x2831: // RTL2831U
|
|
||||||
case 0x2837: // RTL2832U variant
|
|
||||||
case 0x2834: // RTL2832U variant
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -473,6 +506,8 @@ namespace Drivers::USB::Radio {
|
|||||||
g_rtlXtal = RTL_XTAL;
|
g_rtlXtal = RTL_XTAL;
|
||||||
g_manual = 0;
|
g_manual = 0;
|
||||||
g_gain = 0;
|
g_gain = 0;
|
||||||
|
g_directSamp = 0;
|
||||||
|
g_lastFreq = 0;
|
||||||
g_tuner = R820tDev{};
|
g_tuner = R820tDev{};
|
||||||
|
|
||||||
g_ctlBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
|
g_ctlBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
|
||||||
|
|||||||
@@ -1024,6 +1024,10 @@ namespace Drivers::USB::Xhci {
|
|||||||
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
|
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
|
||||||
UsbDeviceInfo& dev = g_devices[slotId];
|
UsbDeviceInfo& dev = g_devices[slotId];
|
||||||
if (!dev.BulkInRing || dev.BulkInEpNum == 0) return;
|
if (!dev.BulkInRing || dev.BulkInEpNum == 0) return;
|
||||||
|
// Already armed: priming again would put a second transfer on the ring
|
||||||
|
// for every pool buffer and desync the completion rotation (callbacks
|
||||||
|
// would be handed the wrong buffer). Callers must Stop first.
|
||||||
|
if (g_bulkInPoolCount[slotId] != 0) return;
|
||||||
|
|
||||||
if (numBuffers < 1) numBuffers = 1;
|
if (numBuffers < 1) numBuffers = 1;
|
||||||
if (numBuffers > BULK_IN_POOL_MAX) numBuffers = BULK_IN_POOL_MAX;
|
if (numBuffers > BULK_IN_POOL_MAX) numBuffers = BULK_IN_POOL_MAX;
|
||||||
@@ -1060,7 +1064,36 @@ namespace Drivers::USB::Xhci {
|
|||||||
if (slotId == 0 || slotId > MAX_SLOTS) return;
|
if (slotId == 0 || slotId > MAX_SLOTS) return;
|
||||||
// Disarm the rotation; any late completion now takes the (no-op for SDR)
|
// Disarm the rotation; any late completion now takes the (no-op for SDR)
|
||||||
// legacy path and is not re-armed. Buffers are retained for reuse.
|
// legacy path and is not re-armed. Buffers are retained for reuse.
|
||||||
|
bool wasArmed = g_bulkInPoolCount[slotId] != 0;
|
||||||
g_bulkInPoolCount[slotId] = 0;
|
g_bulkInPoolCount[slotId] = 0;
|
||||||
|
if (!wasArmed) return;
|
||||||
|
|
||||||
|
// Flush the up-to-PoolCount TRBs still pending on the ring: Stop
|
||||||
|
// Endpoint, then Set TR Dequeue to the enqueue pointer. Without this a
|
||||||
|
// later Start would stack a fresh pool behind the stale TRBs and
|
||||||
|
// overflow the 32-entry transfer ring (16 stale + 16 new > 31 usable),
|
||||||
|
// wrapping the enqueue pointer onto still-pending TRBs.
|
||||||
|
UsbDeviceInfo& dev = g_devices[slotId];
|
||||||
|
if (!dev.Active || dev.BulkInEpNum == 0 || !dev.BulkInRing) return;
|
||||||
|
uint8_t dci = dev.BulkInEpNum * 2 + 1;
|
||||||
|
|
||||||
|
TRB stopTrb = {};
|
||||||
|
stopTrb.Control = (TRB_STOP_ENDPOINT << TRB_TYPE_SHIFT)
|
||||||
|
| ((uint32_t)slotId << 24)
|
||||||
|
| ((uint32_t)dci << 16);
|
||||||
|
SendCommand(stopTrb);
|
||||||
|
|
||||||
|
uint64_t newDeq = dev.BulkInRingPhys
|
||||||
|
+ (uint64_t)dev.BulkInRingEnqueue * sizeof(TRB);
|
||||||
|
if (dev.BulkInRingCCS) newDeq |= 1; // DCS bit
|
||||||
|
|
||||||
|
TRB deqTrb = {};
|
||||||
|
deqTrb.Parameter0 = (uint32_t)(newDeq & 0xFFFFFFFF);
|
||||||
|
deqTrb.Parameter1 = (uint32_t)(newDeq >> 32);
|
||||||
|
deqTrb.Control = (TRB_SET_TR_DEQUEUE << TRB_TYPE_SHIFT)
|
||||||
|
| ((uint32_t)slotId << 24)
|
||||||
|
| ((uint32_t)dci << 16);
|
||||||
|
SendCommand(deqTrb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user