fix: move RTL-SDR to userspace
This commit is contained in:
1 parent
b1f1cfe32b
commit
5cd5c2e6be
35 files changed
+1922
-1592
No files matched your search
@@ -0,0 +1,32 @@
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
CC := $(TOOLCHAIN_PREFIX)gcc
|
||||
LD := $(TOOLCHAIN_PREFIX)ld
|
||||
STRIP := $(TOOLCHAIN_PREFIX)strip
|
||||
PROG_INC := ../../include
|
||||
OBJDIR := obj
|
||||
LIBOUTDIR := ../../bin/os
|
||||
LIBC_SRC := ../../lib/libc/libc.c
|
||||
GCC_INCLUDE := $(shell $(CC) -print-file-name=include)
|
||||
CXXFLAGS := -std=gnu++20 -g -O2 -pipe -Wall -Wextra -ffreestanding -fno-stack-protector -fno-stack-check -fPIC -fvisibility=hidden -fno-rtti -fno-exceptions -ffunction-sections -fdata-sections -mno-80387 -mno-mmx -mno-sse -mno-sse2 -MMD -MP -I $(PROG_INC) -isystem $(PROG_INC)/libc
|
||||
LIBC_CFLAGS := -std=gnu11 -O2 -ffreestanding -fno-stack-protector -fno-stack-check -fPIC -fvisibility=hidden -ffunction-sections -fdata-sections -isystem $(PROG_INC)/libc -isystem $(GCC_INCLUDE)
|
||||
LDFLAGS := -shared --build-id=none --gc-sections --hash-style=sysv -m elf_x86_64
|
||||
OBJS := $(OBJDIR)/rtlsdr.o $(OBJDIR)/r820t.o
|
||||
TARGET := $(LIBOUTDIR)/rtlsdr.lib
|
||||
.PHONY: all clean
|
||||
all: $(TARGET)
|
||||
$(TARGET): $(OBJS) $(OBJDIR)/libc_pic.o Makefile
|
||||
mkdir -p $(LIBOUTDIR)
|
||||
$(LD) $(LDFLAGS) $(OBJS) $(OBJDIR)/libc_pic.o -o $@
|
||||
$(STRIP) --strip-debug $@
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
$(OBJDIR)/libc_pic.o: $(LIBC_SRC) Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CC) $(LIBC_CFLAGS) -c $(LIBC_SRC) -o $@
|
||||
-include $(OBJS:.o=.d)
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
@@ -0,0 +1,445 @@
|
||||
#include "r820t.hpp"
|
||||
|
||||
namespace {
|
||||
struct LogSink { template<class T> LogSink& operator<<(const T&) { return *this; } };
|
||||
namespace base { static constexpr int hex = 0; static constexpr int dec = 0; }
|
||||
}
|
||||
#define KernelLogStream(level, tag) LogSink{}
|
||||
#define WARNING 0
|
||||
#define INFO 0
|
||||
#define ERROR 0
|
||||
#define OK 0
|
||||
|
||||
namespace rtlsdr_internal {
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Initial register values for registers 0x05..0x1f (27 registers). Written
|
||||
// verbatim at init; the shadow cache then tracks read-modify-writes.
|
||||
static const uint8_t kInitArray[R820T_NUM_REGS] = {
|
||||
0x83, 0x32, 0x75, // 0x05 - 0x07
|
||||
0xc0, 0x40, 0xd6, 0x6c, // 0x08 - 0x0b
|
||||
0xf5, 0x63, 0x75, 0x68, // 0x0c - 0x0f
|
||||
0x6c, 0x83, 0x80, 0x00, // 0x10 - 0x13
|
||||
0x0f, 0x00, 0xc0, 0x30, // 0x14 - 0x17
|
||||
0x48, 0xcc, 0x60, 0x00, // 0x18 - 0x1b
|
||||
0x54, 0xae, 0x4a, 0xc0 // 0x1c - 0x1f
|
||||
};
|
||||
|
||||
// RF tracking-filter / mux band selection, keyed by LO frequency in MHz.
|
||||
struct FreqRange {
|
||||
uint32_t freqMhz;
|
||||
uint8_t openD; // reg 0x17 bit 3 (open drain)
|
||||
uint8_t rfMuxPoly; // reg 0x1a bits (RF mux + poly)
|
||||
uint8_t tfC; // reg 0x1b (tracking filter band)
|
||||
};
|
||||
|
||||
static const FreqRange kFreqRanges[] = {
|
||||
{ 0, 0x08, 0x02, 0xdf },
|
||||
{ 50, 0x08, 0x02, 0xbe },
|
||||
{ 55, 0x08, 0x02, 0x8b },
|
||||
{ 60, 0x08, 0x02, 0x7b },
|
||||
{ 65, 0x08, 0x02, 0x69 },
|
||||
{ 70, 0x08, 0x02, 0x58 },
|
||||
{ 75, 0x00, 0x02, 0x44 },
|
||||
{ 80, 0x00, 0x02, 0x44 },
|
||||
{ 90, 0x00, 0x02, 0x34 },
|
||||
{ 100, 0x00, 0x02, 0x34 },
|
||||
{ 110, 0x00, 0x02, 0x24 },
|
||||
{ 120, 0x00, 0x02, 0x24 },
|
||||
{ 140, 0x00, 0x02, 0x14 },
|
||||
{ 180, 0x00, 0x02, 0x13 },
|
||||
{ 220, 0x00, 0x02, 0x13 },
|
||||
{ 250, 0x00, 0x02, 0x11 },
|
||||
{ 280, 0x00, 0x02, 0x00 },
|
||||
{ 310, 0x00, 0x41, 0x00 },
|
||||
{ 450, 0x00, 0x41, 0x00 },
|
||||
{ 588, 0x00, 0x40, 0x00 },
|
||||
{ 650, 0x00, 0x40, 0x00 },
|
||||
};
|
||||
|
||||
// Composite gain steps (tenths of dB) reachable by walking the LNA + mixer
|
||||
// gain stages together. Advertised to userspace as the discrete gain set.
|
||||
static const int kGains[] = {
|
||||
0, 9, 14, 27, 37, 77, 87, 125, 144, 157,
|
||||
166, 197, 207, 229, 254, 280, 297, 328,
|
||||
338, 364, 372, 386, 402, 421, 434, 439,
|
||||
445, 480, 496,
|
||||
};
|
||||
|
||||
// Per-index increments of the LNA and mixer gain stages (tenths of dB).
|
||||
static const int kLnaGainSteps[] = { 0, 9, 13, 40, 38, 13, 31, 22,
|
||||
26, 31, 26, 14, 19, 5, 35, 13 };
|
||||
static const int kMixerGainSteps[] = { 0, 5, 10, 10, 19, 9, 10, 25,
|
||||
17, 10, 8, 16, 13, 6, 3, -8 };
|
||||
|
||||
// =========================================================================
|
||||
// Low-level register access (through the demod's I2C repeater)
|
||||
// =========================================================================
|
||||
|
||||
static uint8_t BitRev(uint8_t b) {
|
||||
static const uint8_t lut[16] = {
|
||||
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
|
||||
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf,
|
||||
};
|
||||
return (uint8_t)((lut[b & 0xf] << 4) | lut[b >> 4]);
|
||||
}
|
||||
|
||||
// Write `len` register values starting at `reg`. The RTL2832 I2C path is
|
||||
// chunked to 7 values per transfer (8-byte message incl. the start reg).
|
||||
static bool Write(R820tDev& d, uint8_t reg, const uint8_t* val, uint8_t len) {
|
||||
uint8_t buf[8];
|
||||
uint8_t pos = 0;
|
||||
while (len > 0) {
|
||||
uint8_t size = len > 7 ? 7 : len;
|
||||
buf[0] = reg;
|
||||
for (uint8_t i = 0; i < size; i++) buf[1 + i] = val[pos + i];
|
||||
if (!RtlI2cWrite(d.owner, R820T_I2C_ADDR, buf, (uint8_t)(size + 1)))
|
||||
return false;
|
||||
for (uint8_t i = 0; i < size; i++)
|
||||
if ((reg + i) < 32) d.regs[reg + i] = val[pos + i];
|
||||
pos += size;
|
||||
reg = (uint8_t)(reg + size);
|
||||
len = (uint8_t)(len - size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool WriteReg(R820tDev& d, uint8_t reg, uint8_t val) {
|
||||
return Write(d, reg, &val, 1);
|
||||
}
|
||||
|
||||
// Read-modify-write using the register shadow as the source of truth for the
|
||||
// bits outside `mask`.
|
||||
static bool WriteRegMask(R820tDev& d, uint8_t reg, uint8_t val, uint8_t mask) {
|
||||
uint8_t cur = (reg < 32) ? d.regs[reg] : 0;
|
||||
uint8_t merged = (uint8_t)((cur & ~mask) | (val & mask));
|
||||
return WriteReg(d, reg, merged);
|
||||
}
|
||||
|
||||
// Read `len` bytes starting at register 0. The read pointer must be set
|
||||
// to 0 with an address-only write first: a preceding register write leaves
|
||||
// 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) {
|
||||
uint8_t raw[16];
|
||||
if (len > sizeof(raw)) len = sizeof(raw);
|
||||
uint8_t ptr = 0x00;
|
||||
if (!RtlI2cWrite(d.owner, R820T_I2C_ADDR, &ptr, 1)) return false;
|
||||
if (!RtlI2cRead(d.owner, R820T_I2C_ADDR, raw, len)) return false;
|
||||
for (uint8_t i = 0; i < len; i++) out[i] = BitRev(raw[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 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(rtlsdr_device* owner) {
|
||||
// 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
|
||||
// to the raw R820T id. (The bit-reversal only applies to the status
|
||||
// registers read during tuning, not to this id check.)
|
||||
uint8_t ptr = 0x00;
|
||||
RtlI2cWrite(owner, R820T_I2C_ADDR, &ptr, 1);
|
||||
|
||||
uint8_t raw[1] = {0};
|
||||
if (!RtlI2cRead(owner, R820T_I2C_ADDR, raw, 1)) {
|
||||
KernelLogStream(WARNING, "R820T") << "id read failed (I2C transfer error)";
|
||||
return false;
|
||||
}
|
||||
KernelLogStream(INFO, "R820T") << "chip id = 0x" << base::hex
|
||||
<< (uint64_t)raw[0] << base::dec
|
||||
<< (raw[0] == R820T_CHECK_VAL ? " (R820T/R820T2)" : " (unrecognised)");
|
||||
return raw[0] == R820T_CHECK_VAL;
|
||||
}
|
||||
|
||||
bool R820tInit(R820tDev& d, rtlsdr_device* owner, uint32_t xtal, uint32_t intFreq) {
|
||||
d.owner = owner;
|
||||
d.xtal = xtal;
|
||||
d.intFreq = intFreq;
|
||||
d.hasLock = false;
|
||||
d.inited = false;
|
||||
|
||||
// Load the init register block, then bring the IF filter and the
|
||||
// receive-path operating point to the SDR profile (incl. the 56 MHz
|
||||
// filter calibration).
|
||||
if (!Write(d, 0x05, kInitArray, sizeof(kInitArray))) {
|
||||
KernelLogStream(ERROR, "R820T") << "init register write failed";
|
||||
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;
|
||||
KernelLogStream(OK, "R820T") << "Tuner initialised (xtal="
|
||||
<< (uint64_t)xtal << " IF=" << (uint64_t)intFreq << ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// RF mux / tracking filter band
|
||||
// =========================================================================
|
||||
|
||||
static bool SetMux(R820tDev& d, uint64_t loHz) {
|
||||
uint32_t loMhz = (uint32_t)(loHz / 1000000);
|
||||
|
||||
unsigned idx = 0;
|
||||
const unsigned n = sizeof(kFreqRanges) / sizeof(kFreqRanges[0]);
|
||||
for (; idx < n - 1; idx++) {
|
||||
if (loMhz < kFreqRanges[idx + 1].freqMhz) break;
|
||||
}
|
||||
const FreqRange& r = kFreqRanges[idx];
|
||||
|
||||
bool ok = true;
|
||||
ok &= WriteRegMask(d, 0x17, r.openD, 0x08); // open drain
|
||||
ok &= WriteRegMask(d, 0x1a, r.rfMuxPoly, 0xc3); // RF mux + poly
|
||||
ok &= WriteReg(d, 0x1b, r.tfC); // tracking-filter band
|
||||
// Default XTAL cap (high-cap-0p selection) and the unused LNA/mixer
|
||||
// top registers.
|
||||
ok &= WriteRegMask(d, 0x10, 0x00, 0x0b);
|
||||
ok &= WriteRegMask(d, 0x08, 0x00, 0x3f);
|
||||
ok &= WriteRegMask(d, 0x09, 0x00, 0x3f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PLL / VCO frequency synthesis
|
||||
// =========================================================================
|
||||
|
||||
static bool SetPll(R820tDev& d, uint64_t freqHz) {
|
||||
const uint32_t vcoMinKhz = 1770000; // 1.77 GHz
|
||||
const uint32_t vcoMaxKhz = vcoMinKhz * 2; // 3.54 GHz
|
||||
uint32_t pllRef = d.xtal;
|
||||
uint32_t freqKhz = (uint32_t)((freqHz + 500) / 1000);
|
||||
|
||||
bool ok = true;
|
||||
ok &= WriteRegMask(d, 0x10, 0x00, 0x10); // refdiv = /1
|
||||
ok &= WriteRegMask(d, 0x1a, 0x00, 0x0c); // pll autotune 128 kHz
|
||||
ok &= WriteRegMask(d, 0x12, 0x80, 0xe0); // VCO current = 100
|
||||
|
||||
// Pick the smallest mixer divider that lands the VCO in range.
|
||||
uint8_t mixDiv = 2;
|
||||
uint8_t divNum = 0;
|
||||
while (mixDiv <= 64) {
|
||||
if ((uint64_t)freqKhz * mixDiv >= vcoMinKhz &&
|
||||
(uint64_t)freqKhz * mixDiv < vcoMaxKhz) {
|
||||
uint8_t divBuf = mixDiv;
|
||||
while (divBuf > 2) { divBuf >>= 1; divNum++; }
|
||||
break;
|
||||
}
|
||||
mixDiv <<= 1;
|
||||
}
|
||||
|
||||
// VCO fine-tune feedback adjusts the divider selection.
|
||||
uint8_t data[5] = {0};
|
||||
if (!Read(d, data, sizeof(data))) return false;
|
||||
uint8_t vcoPowerRef = 2;
|
||||
uint8_t vcoFineTune = (uint8_t)((data[4] & 0x30) >> 4);
|
||||
if (vcoFineTune > vcoPowerRef && divNum > 0) divNum--;
|
||||
else if (vcoFineTune < vcoPowerRef) divNum++;
|
||||
ok &= WriteRegMask(d, 0x10, (uint8_t)(divNum << 5), 0xe0);
|
||||
|
||||
uint64_t vcoFreq = freqHz * (uint64_t)mixDiv;
|
||||
|
||||
// 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
|
||||
uint8_t ni = (uint8_t)((nint - 13) / 4);
|
||||
uint8_t si = (uint8_t)(nint - 4 * ni - 13);
|
||||
ok &= WriteReg(d, 0x14, (uint8_t)(ni + (si << 6)));
|
||||
|
||||
// Sigma-delta fractional path (powered down when the fraction is 0).
|
||||
ok &= WriteRegMask(d, 0x12, sdm ? 0x00 : 0x08, 0x08);
|
||||
ok &= WriteReg(d, 0x16, (uint8_t)(sdm >> 8));
|
||||
ok &= WriteReg(d, 0x15, (uint8_t)(sdm & 0xff));
|
||||
|
||||
// Confirm lock; bump the VCO current once if the first read is not
|
||||
// locked.
|
||||
d.hasLock = false;
|
||||
uint8_t lk[3] = {0};
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (!Read(d, lk, sizeof(lk))) {
|
||||
KernelLogStream(WARNING, "R820T") << "PLL status read failed";
|
||||
return false;
|
||||
}
|
||||
if (lk[2] & 0x40) { d.hasLock = true; break; }
|
||||
if (i == 0) WriteRegMask(d, 0x12, 0x60, 0xe0); // raise VCO current
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "R820T") << "PLL nint=" << (uint64_t)nint
|
||||
<< " sdm=0x" << base::hex << (uint64_t)sdm
|
||||
<< " mixDiv=" << base::dec << (uint64_t)mixDiv
|
||||
<< " status=0x" << base::hex << (uint64_t)lk[2] << base::dec
|
||||
<< (d.hasLock ? " LOCKED" : " UNLOCKED");
|
||||
|
||||
ok &= WriteRegMask(d, 0x1a, 0x08, 0x08); // pll autotune 8 kHz
|
||||
return ok && d.hasLock;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Public tuner control
|
||||
// =========================================================================
|
||||
|
||||
bool R820tSetFreq(R820tDev& d, uint64_t rfHz) {
|
||||
if (!d.inited) {
|
||||
KernelLogStream(WARNING, "R820T") << "SetFreq: tuner not inited";
|
||||
return false;
|
||||
}
|
||||
uint64_t loHz = rfHz + d.intFreq;
|
||||
|
||||
if (!SetMux(d, loHz)) {
|
||||
KernelLogStream(WARNING, "R820T") << "SetFreq: mux write failed";
|
||||
return false;
|
||||
}
|
||||
if (!SetPll(d, loHz)) {
|
||||
KernelLogStream(WARNING, "R820T") << "PLL not locked at "
|
||||
<< (uint64_t)(rfHz / 1000) << " kHz";
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: the Air-in vs Cable-1 input switch at 345 MHz applies to the
|
||||
// R828D only. The R820T/T2 uses the air input exclusively (selected
|
||||
// during init); writing the Cable-1 select on an R820T disconnects the
|
||||
// antenna input below 345 MHz.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool R820tSetGain(R820tDev& d, int manual, int tenthsDb) {
|
||||
if (!d.inited) {
|
||||
KernelLogStream(WARNING, "R820T") << "SetGain: tuner not inited";
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
|
||||
if (manual) {
|
||||
// LNA + mixer to manual; VGA to a fixed mid value.
|
||||
ok &= WriteRegMask(d, 0x05, 0x10, 0x10); // LNA AGC off
|
||||
ok &= WriteRegMask(d, 0x07, 0x00, 0x10); // mixer AGC off
|
||||
ok &= WriteRegMask(d, 0x0c, 0x08, 0x9f); // VGA = 16.3 dB
|
||||
|
||||
int total = 0;
|
||||
uint8_t lnaIndex = 0, mixIndex = 0;
|
||||
for (int i = 0; i < 15; i++) {
|
||||
if (total >= tenthsDb) break;
|
||||
total += kLnaGainSteps[++lnaIndex];
|
||||
if (total >= tenthsDb) break;
|
||||
total += kMixerGainSteps[++mixIndex];
|
||||
}
|
||||
ok &= WriteRegMask(d, 0x05, lnaIndex, 0x0f);
|
||||
ok &= WriteRegMask(d, 0x07, mixIndex, 0x0f);
|
||||
} else {
|
||||
ok &= WriteRegMask(d, 0x05, 0x00, 0x10); // LNA AGC on
|
||||
ok &= WriteRegMask(d, 0x07, 0x10, 0x10); // mixer AGC on
|
||||
ok &= WriteRegMask(d, 0x0c, 0x0b, 0x9f); // VGA = 26.5 dB
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void R820tStandby(R820tDev& d) {
|
||||
if (!d.inited) return;
|
||||
// Documented R820T standby register values (mute + power-down).
|
||||
WriteReg(d, 0x06, 0xb1);
|
||||
WriteReg(d, 0x05, 0xa0);
|
||||
WriteReg(d, 0x07, 0x3a);
|
||||
WriteReg(d, 0x08, 0x40);
|
||||
WriteReg(d, 0x09, 0xc0);
|
||||
WriteReg(d, 0x0a, 0x36);
|
||||
WriteReg(d, 0x0c, 0x35);
|
||||
WriteReg(d, 0x0f, 0x68);
|
||||
WriteReg(d, 0x11, 0x03);
|
||||
WriteReg(d, 0x17, 0xf4);
|
||||
WriteReg(d, 0x19, 0x0c);
|
||||
}
|
||||
|
||||
const int* R820tGainTable(int* count) {
|
||||
if (count) *count = (int)(sizeof(kGains) / sizeof(kGains[0]));
|
||||
return kGains;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
struct rtlsdr_device;
|
||||
|
||||
namespace rtlsdr_internal {
|
||||
static constexpr uint8_t R820T_I2C_ADDR = 0x34;
|
||||
static constexpr uint8_t R820T_CHECK_VAL = 0x69;
|
||||
static constexpr uint8_t R820T_NUM_REGS = 27;
|
||||
|
||||
struct R820tDev {
|
||||
rtlsdr_device* owner;
|
||||
uint32_t xtal;
|
||||
uint32_t intFreq;
|
||||
uint8_t regs[32];
|
||||
bool hasLock;
|
||||
bool inited;
|
||||
};
|
||||
|
||||
bool RtlI2cWrite(rtlsdr_device* owner, uint8_t addr, const uint8_t* data, uint8_t len);
|
||||
bool RtlI2cRead(rtlsdr_device* owner, uint8_t addr, uint8_t* data, uint8_t len);
|
||||
bool R820tDetect(rtlsdr_device* owner);
|
||||
bool R820tInit(R820tDev& dev, rtlsdr_device* owner, uint32_t xtal, uint32_t intFreq);
|
||||
bool R820tSetFreq(R820tDev& dev, uint64_t hz);
|
||||
bool R820tSetGain(R820tDev& dev, int manual, int tenthsDb);
|
||||
void R820tStandby(R820tDev& dev);
|
||||
const int* R820tGainTable(int* count);
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
#include <rtlsdr/rtlsdr.h>
|
||||
#include <montauk/syscall.h>
|
||||
#include <libc/string.h>
|
||||
#include "r820t.hpp"
|
||||
|
||||
struct rtlsdr_device { int marker; };
|
||||
|
||||
namespace rtlsdr_internal {
|
||||
namespace {
|
||||
struct LogSink { template<class T> LogSink& operator<<(const T&) { return *this; } };
|
||||
namespace base { static constexpr int hex=0; static constexpr int dec=0; }
|
||||
}
|
||||
#define KernelLogStream(level, tag) LogSink{}
|
||||
#define WARNING 0
|
||||
#define INFO 0
|
||||
#define ERROR 0
|
||||
#define OK 0
|
||||
|
||||
static rtlsdr_device g_storage{};
|
||||
static rtlsdr_device* g_openDev = nullptr;
|
||||
static int g_usbHandle = -1;
|
||||
static bool g_hwInited = false;
|
||||
static bool g_streaming = false;
|
||||
static uint8_t g_ctlStorage[4096]{};
|
||||
static uint8_t* g_ctlBuf = g_ctlStorage;
|
||||
static R820tDev g_tuner{};
|
||||
static uint32_t g_rtlXtal = 28800000;
|
||||
static int g_ppm = 0;
|
||||
static int g_manual = 0;
|
||||
static int g_gain = 0;
|
||||
static int g_directSamp = 0;
|
||||
static uint64_t g_lastFreq = 0;
|
||||
static uint32_t g_sampleRate = 0;
|
||||
|
||||
namespace UsbCompat {
|
||||
static constexpr uint32_t CC_SUCCESS = 1;
|
||||
static uint32_t ControlTransfer(rtlsdr_device*, uint8_t requestType,
|
||||
uint8_t request, uint16_t value, uint16_t index, uint16_t length,
|
||||
uint8_t* data, bool) {
|
||||
montauk::abi::UsbControlRequest setup{requestType, request, value, index, length};
|
||||
return montauk::usb_control(g_usbHandle, &setup, data, length) == 0 ? CC_SUCCESS : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Constants
|
||||
// =========================================================================
|
||||
|
||||
// Vendor control-transfer request types (vendor, host<->device).
|
||||
static constexpr uint8_t CTRL_OUT = 0x40; // host-to-device, vendor
|
||||
static constexpr uint8_t CTRL_IN = 0xC0; // device-to-host, vendor
|
||||
|
||||
// RTL2832U register blocks (high byte of wIndex; OR 0x10 to write).
|
||||
static constexpr uint8_t BLOCK_USB = 1;
|
||||
static constexpr uint8_t BLOCK_SYS = 2;
|
||||
static constexpr uint8_t BLOCK_IIC = 6;
|
||||
|
||||
// USB / system register addresses.
|
||||
static constexpr uint16_t USB_EPA_CTL = 0x2148;
|
||||
static constexpr uint16_t USB_EPA_MAXPKT = 0x2158;
|
||||
static constexpr uint16_t USB_SYSCTL = 0x2000;
|
||||
static constexpr uint16_t SYS_DEMOD_CTL = 0x3000;
|
||||
static constexpr uint16_t SYS_DEMOD_CTL1 = 0x300b;
|
||||
static constexpr uint32_t RTL_XTAL = 28800000;
|
||||
static constexpr uint32_t R82XX_IF = 3570000;
|
||||
static constexpr uint32_t TWO_POW22 = 1u << 22;
|
||||
|
||||
static bool RegWrite(uint8_t block, uint16_t addr, uint16_t val, uint8_t len) {
|
||||
if (!g_ctlBuf) return false;
|
||||
g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8);
|
||||
g_ctlBuf[1] = (uint8_t)(val & 0xff);
|
||||
uint16_t index = (uint16_t)((block << 8) | 0x10);
|
||||
return UsbCompat::ControlTransfer(g_openDev, CTRL_OUT, 0, addr, index, len,
|
||||
g_ctlBuf, false) == UsbCompat::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;
|
||||
UsbCompat::ControlTransfer(g_openDev, 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) {
|
||||
if (!g_ctlBuf) return false;
|
||||
uint16_t waddr = (uint16_t)((addr << 8) | 0x20);
|
||||
uint16_t index = (uint16_t)(0x10 | page);
|
||||
g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8);
|
||||
g_ctlBuf[1] = (uint8_t)(val & 0xff);
|
||||
bool ok = UsbCompat::ControlTransfer(g_openDev, CTRL_OUT, 0, waddr, index, len,
|
||||
g_ctlBuf, false) == UsbCompat::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) {
|
||||
DemodWrite(1, 0x01, on ? 0x18 : 0x10, 1);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// I2C facade for the tuner module
|
||||
// =========================================================================
|
||||
|
||||
bool RtlI2cWrite(rtlsdr_device* owner, uint8_t i2cAddr, const uint8_t* buf, uint8_t len) {
|
||||
if (!g_ctlBuf || len == 0 || len > 64) return false;
|
||||
memcpy(g_ctlBuf, buf, len);
|
||||
uint16_t index = (uint16_t)((BLOCK_IIC << 8) | 0x10);
|
||||
uint32_t cc = UsbCompat::ControlTransfer(owner, CTRL_OUT, 0, i2cAddr, index, len,
|
||||
g_ctlBuf, false);
|
||||
if (cc != UsbCompat::CC_SUCCESS)
|
||||
KernelLogStream(WARNING, "RTL-SDR") << "I2C write cc=" << (uint64_t)cc
|
||||
<< " reg=0x" << base::hex << (uint64_t)buf[0]
|
||||
<< " len=" << base::dec << (uint64_t)len;
|
||||
return cc == UsbCompat::CC_SUCCESS;
|
||||
}
|
||||
|
||||
bool RtlI2cRead(rtlsdr_device* owner, uint8_t i2cAddr, uint8_t* buf, uint8_t len) {
|
||||
if (!g_ctlBuf || len == 0 || len > 64) return false;
|
||||
uint16_t index = (uint16_t)(BLOCK_IIC << 8);
|
||||
uint32_t cc = UsbCompat::ControlTransfer(owner, CTRL_IN, 0, i2cAddr, index, len,
|
||||
g_ctlBuf, true);
|
||||
if (cc != UsbCompat::CC_SUCCESS) {
|
||||
KernelLogStream(WARNING, "RTL-SDR") << "I2C read cc=" << (uint64_t)cc
|
||||
<< " len=" << (uint64_t)len;
|
||||
return false;
|
||||
}
|
||||
memcpy(buf, g_ctlBuf, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Demodulator bring-up
|
||||
// =========================================================================
|
||||
|
||||
// The 16-tap default FIR (8x int8 then 8x int12) used for the SDR/FM path.
|
||||
static void SetFir() {
|
||||
static const int fir[16] = {
|
||||
-54, -36, -41, -40, -32, -14, 14, 53,
|
||||
101, 156, 215, 273, 327, 372, 404, 421,
|
||||
};
|
||||
uint8_t buf[20];
|
||||
for (int i = 0; i < 8; i++) buf[i] = (uint8_t)(fir[i] & 0xff);
|
||||
for (int i = 0; i < 8; i += 2) {
|
||||
int v0 = fir[8 + i];
|
||||
int v1 = fir[8 + i + 1];
|
||||
buf[8 + i * 3 / 2] = (uint8_t)((v0 >> 4) & 0xff);
|
||||
buf[8 + i * 3 / 2 + 1] = (uint8_t)(((v0 << 4) | ((v1 >> 8) & 0x0f)) & 0xff);
|
||||
buf[8 + i * 3 / 2 + 2] = (uint8_t)(v1 & 0xff);
|
||||
}
|
||||
for (int i = 0; i < 20; i++) DemodWrite(1, (uint16_t)(0x1c + i), buf[i], 1);
|
||||
}
|
||||
|
||||
static bool BasebandInit() {
|
||||
// USB FIFO / endpoint A setup.
|
||||
RegWrite(BLOCK_USB, USB_SYSCTL, 0x09, 1);
|
||||
RegWrite(BLOCK_USB, USB_EPA_MAXPKT, 0x0002, 2);
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
|
||||
// Power on the demod.
|
||||
RegWrite(BLOCK_SYS, SYS_DEMOD_CTL1, 0x22, 1);
|
||||
RegWrite(BLOCK_SYS, SYS_DEMOD_CTL, 0xe8, 1);
|
||||
|
||||
// Soft-reset the demod state machine.
|
||||
DemodWrite(1, 0x01, 0x14, 1);
|
||||
DemodWrite(1, 0x01, 0x10, 1);
|
||||
|
||||
// Disable spectrum inversion + clear DDC shift / IF registers.
|
||||
DemodWrite(1, 0x15, 0x00, 1);
|
||||
DemodWrite(1, 0x16, 0x0000, 2);
|
||||
for (int i = 0; i < 6; i++) DemodWrite(1, (uint16_t)(0x16 + i), 0x00, 1);
|
||||
|
||||
SetFir();
|
||||
|
||||
DemodWrite(0, 0x19, 0x05, 1); // enable SDR mode, disable DAGC
|
||||
DemodWrite(1, 0x93, 0xf0, 1);
|
||||
DemodWrite(1, 0x94, 0x0f, 1);
|
||||
DemodWrite(1, 0x11, 0x00, 1); // disable AGC loop
|
||||
DemodWrite(1, 0x04, 0x00, 1);
|
||||
DemodWrite(0, 0x61, 0x60, 1); // disable PID filter
|
||||
DemodWrite(0, 0x06, 0x80, 1); // default ADC I/Q datapath
|
||||
DemodWrite(1, 0xb1, 0x1b, 1); // zero-IF + DC cancel + IQ comp/est
|
||||
DemodWrite(0, 0x0d, 0x83, 1); // disable clock output on TP_CK0
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the digital downconversion IF frequency the demod searches at.
|
||||
static void SetIfFreq(uint32_t freq) {
|
||||
int32_t ifv = (int32_t)(-(int64_t)((uint64_t)freq * TWO_POW22 / g_rtlXtal));
|
||||
DemodWrite(1, 0x19, (uint16_t)((ifv >> 16) & 0x3f), 1);
|
||||
DemodWrite(1, 0x1a, (uint16_t)((ifv >> 8) & 0xff), 1);
|
||||
DemodWrite(1, 0x1b, (uint16_t)(ifv & 0xff), 1);
|
||||
}
|
||||
|
||||
static void ApplySampleFreqCorrection() {
|
||||
int32_t offs = (int32_t)(-(int64_t)g_ppm * (1 << 24) / 1000000);
|
||||
DemodWrite(1, 0x3f, (uint16_t)(offs & 0xff), 1);
|
||||
DemodWrite(1, 0x3e, (uint16_t)((offs >> 8) & 0x3f), 1);
|
||||
}
|
||||
|
||||
static bool TunerInit() {
|
||||
SetI2cRepeater(true);
|
||||
// Retry detection a few times: an I2C read can transiently come back
|
||||
// wrong if it raced another core's USB activity around bring-up.
|
||||
bool detected = false;
|
||||
for (int attempt = 0; attempt < 4 && !detected; attempt++)
|
||||
detected = R820tDetect(g_openDev);
|
||||
bool ok = detected && R820tInit(g_tuner, g_openDev, g_rtlXtal, R82XX_IF);
|
||||
SetI2cRepeater(false);
|
||||
if (!detected) {
|
||||
KernelLogStream(WARNING, "RTL-SDR") << "no R820T2 tuner found on I2C";
|
||||
return false;
|
||||
}
|
||||
if (!ok) return false;
|
||||
|
||||
// Demod path for the R820T2 low-IF tuner.
|
||||
DemodWrite(1, 0xb1, 0x1a, 1); // disable zero-IF mode
|
||||
DemodWrite(0, 0x08, 0x4d, 1); // enable In-phase ADC input only
|
||||
SetIfFreq(R82XX_IF);
|
||||
DemodWrite(1, 0x15, 0x01, 1); // enable spectrum inversion
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EnsureInit() {
|
||||
if (g_hwInited) return true;
|
||||
if (!g_openDev || !g_ctlBuf) return false;
|
||||
if (!BasebandInit()) return false;
|
||||
if (!TunerInit()) return false;
|
||||
g_hwInited = true;
|
||||
KernelLogStream(OK, "RTL-SDR") << "Demod + tuner brought up on slot "
|
||||
<< (uint64_t)g_openDev;
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Tuning / configuration (each holds g_ctlLock via the op wrappers)
|
||||
// =========================================================================
|
||||
|
||||
static int DoSetFreq(uint64_t hz) {
|
||||
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);
|
||||
bool ok = R820tSetFreq(g_tuner, hz);
|
||||
SetI2cRepeater(false);
|
||||
if (ok) g_lastFreq = hz;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetSampleRate(uint32_t rate) {
|
||||
if (!EnsureInit()) return -1;
|
||||
// The RTL2832 resampler does not cover 300k..900k.
|
||||
if (rate <= 225000 || rate > 3200000 ||
|
||||
(rate > 300000 && rate <= 900000)) return -1;
|
||||
|
||||
// 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;
|
||||
DemodWrite(1, 0x9f, (uint16_t)((ratio >> 16) & 0xffff), 2);
|
||||
DemodWrite(1, 0xa1, (uint16_t)(ratio & 0xffff), 2);
|
||||
|
||||
ApplySampleFreqCorrection();
|
||||
DemodWrite(1, 0x01, 0x14, 1); // soft reset
|
||||
DemodWrite(1, 0x01, 0x10, 1);
|
||||
SetIfFreq(g_directSamp ? (uint32_t)g_lastFreq : R82XX_IF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DoSetGainMode(int manual) {
|
||||
if (!EnsureInit()) return -1;
|
||||
g_manual = manual ? 1 : 0;
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tSetGain(g_tuner, g_manual, g_gain);
|
||||
SetI2cRepeater(false);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetGain(int tenths) {
|
||||
if (!EnsureInit()) return -1;
|
||||
g_gain = tenths;
|
||||
g_manual = 1; // selecting an explicit gain implies manual mode
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tSetGain(g_tuner, 1, g_gain);
|
||||
SetI2cRepeater(false);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetFreqCorrection(int ppm) {
|
||||
if (!EnsureInit()) return -1;
|
||||
g_ppm = ppm;
|
||||
g_rtlXtal = (uint32_t)((int64_t)RTL_XTAL + (int64_t)RTL_XTAL * ppm / 1000000);
|
||||
g_tuner.xtal = g_rtlXtal;
|
||||
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;
|
||||
}
|
||||
|
||||
static int DoSetAgc(int on) {
|
||||
if (!EnsureInit()) return -1;
|
||||
return DemodWrite(0, 0x19, on ? 0x25 : 0x05, 1) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetDirectSampling(int mode) {
|
||||
if (!EnsureInit()) return -1;
|
||||
if (mode) {
|
||||
// Bypass the tuner and digitise the ADC input directly.
|
||||
SetI2cRepeater(true);
|
||||
R820tStandby(g_tuner);
|
||||
SetI2cRepeater(false);
|
||||
DemodWrite(1, 0xb1, 0x1a, 1); // disable zero-IF
|
||||
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
|
||||
g_directSamp = mode;
|
||||
// Tuning now happens in the DDC; carry the current frequency over.
|
||||
SetIfFreq((uint32_t)g_lastFreq);
|
||||
} else {
|
||||
// Restore the R820T2 low-IF receive path. Standby powered the
|
||||
// tuner down, so it needs a full re-initialisation.
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tInit(g_tuner, g_openDev, g_rtlXtal, R82XX_IF);
|
||||
SetI2cRepeater(false);
|
||||
if (!ok) return -1;
|
||||
SetIfFreq(R82XX_IF);
|
||||
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;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Streaming
|
||||
// =========================================================================
|
||||
|
||||
|
||||
static bool IsSupported(uint16_t vid, uint16_t pid) {
|
||||
return vid == 0x0bda && (pid == 0x2832 || pid == 0x2838);
|
||||
}
|
||||
|
||||
static int FindInterface(int wanted, montauk::abi::UsbInterfaceInfo* out) {
|
||||
montauk::abi::UsbInterfaceInfo interfaces[32]{};
|
||||
int count = montauk::usb_list(interfaces, 32);
|
||||
int matched = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (!IsSupported(interfaces[i].vendorId, interfaces[i].productId)) continue;
|
||||
if (matched++ == wanted) { if (out) *out = interfaces[i]; return 0; }
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void CopyText(char* dst, uint32_t cap, const char* src) {
|
||||
uint32_t i = 0;
|
||||
while (i + 1 < cap && src[i]) { dst[i] = src[i]; ++i; }
|
||||
dst[i] = 0;
|
||||
}
|
||||
|
||||
static void FillInfo(rtlsdr_device_info* out) {
|
||||
*out = {};
|
||||
CopyText(out->name, sizeof(out->name), "Realtek RTL2832U");
|
||||
CopyText(out->tuner, sizeof(out->tuner), "Rafael Micro R820T2");
|
||||
CopyText(out->serial, sizeof(out->serial), "USB RTL-SDR");
|
||||
out->freq_min = 24000000ull;
|
||||
out->freq_max = 1766000000ull;
|
||||
out->sample_rate_min = 225001;
|
||||
out->sample_rate_max = 3200000;
|
||||
int n = 0;
|
||||
const int* gains = R820tGainTable(&n);
|
||||
out->num_gains = n < 32 ? (uint32_t)n : 32;
|
||||
for (uint32_t i = 0; i < out->num_gains; ++i) out->gains[i] = gains[i];
|
||||
}
|
||||
|
||||
} // namespace rtlsdr_internal
|
||||
|
||||
#define RTLSDR_EXPORT extern "C" __attribute__((visibility("default")))
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_count(void) {
|
||||
montauk::abi::UsbInterfaceInfo info{};
|
||||
int n = 0;
|
||||
while (rtlsdr_internal::FindInterface(n, &info) == 0) ++n;
|
||||
return n;
|
||||
}
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_get_device_info(int index, rtlsdr_device_info* out) {
|
||||
if (!out || index < 0 || rtlsdr_internal::FindInterface(index, nullptr) != 0) return -1;
|
||||
rtlsdr_internal::FillInfo(out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_open(rtlsdr_device** out, int index) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (!out || index < 0 || g_openDev) return -1;
|
||||
montauk::abi::UsbInterfaceInfo info{};
|
||||
if (FindInterface(index, &info) != 0 || info.kernelDriverBound || info.claimed ||
|
||||
!info.bulkInEndpoint) return -1;
|
||||
int handle = montauk::usb_claim(info.slotId, info.interfaceNumber);
|
||||
if (handle < 0) return handle;
|
||||
g_usbHandle = handle;
|
||||
g_openDev = &g_storage;
|
||||
g_storage.marker = 0x52544c53;
|
||||
g_hwInited = false;
|
||||
g_streaming = false;
|
||||
g_rtlXtal = RTL_XTAL;
|
||||
g_ppm = g_manual = g_gain = g_directSamp = 0;
|
||||
g_lastFreq = 0;
|
||||
g_sampleRate = 0;
|
||||
g_tuner = {};
|
||||
*out = g_openDev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_close(rtlsdr_device* dev) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (!dev || dev != g_openDev) return -1;
|
||||
if (g_streaming) {
|
||||
g_streaming = false;
|
||||
montauk::usb_bulk_in_stop(g_usbHandle);
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
}
|
||||
int result = montauk::usb_close(g_usbHandle);
|
||||
g_usbHandle = -1;
|
||||
g_openDev = nullptr;
|
||||
g_hwInited = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_set_center_freq(rtlsdr_device* dev, uint64_t hz) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetFreq(hz) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT uint64_t rtlsdr_get_center_freq(const rtlsdr_device* dev) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::g_lastFreq : 0;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_sample_rate(rtlsdr_device* dev, uint32_t hz) {
|
||||
if (dev != rtlsdr_internal::g_openDev) return -1;
|
||||
int result = rtlsdr_internal::DoSetSampleRate(hz);
|
||||
if (result == 0) rtlsdr_internal::g_sampleRate = hz;
|
||||
return result;
|
||||
}
|
||||
RTLSDR_EXPORT uint32_t rtlsdr_get_sample_rate(const rtlsdr_device* dev) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::g_sampleRate : 0;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_tuner_gain_mode(rtlsdr_device* dev, int manual) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetGainMode(manual) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_tuner_gain(rtlsdr_device* dev, int gain) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetGain(gain) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_freq_correction(rtlsdr_device* dev, int ppm) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetFreqCorrection(ppm) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_agc_mode(rtlsdr_device* dev, int on) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetAgc(on) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_direct_sampling(rtlsdr_device* dev, int mode) {
|
||||
if (mode < 0 || mode > 2) return -1;
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetDirectSampling(mode) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_start(rtlsdr_device* dev) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (dev != g_openDev || !EnsureInit()) return -1;
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x0000, 2);
|
||||
int result = montauk::usb_bulk_in_start(g_usbHandle, 4096, 16);
|
||||
if (result == 0) g_streaming = true;
|
||||
return result;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_stop(rtlsdr_device* dev) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (dev != g_openDev) return -1;
|
||||
g_streaming = false;
|
||||
int result = montauk::usb_bulk_in_stop(g_usbHandle);
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
return result;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_read(rtlsdr_device* dev, void* data, uint32_t length) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (dev != g_openDev || !g_streaming) return -1;
|
||||
return montauk::usb_bulk_in_read(g_usbHandle, data, length);
|
||||
}
|
||||
Reference in new issue
Block a user