/*
    * R820t.cpp
    * Rafael Micro R820T / R820T2 silicon tuner driver.
    * Copyright (c) 2026 Daniel Hammer
*/

#include "R820t.hpp"
#include "RtlSdr.hpp"          // I2C facade (RtlI2cWrite / RtlI2cRead)
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>

using namespace Kt;

namespace Drivers::USB::Radio {

    // =========================================================================
    // Tuner constant tables
    // =========================================================================

    // 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.slotId, 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.slotId, R820T_I2C_ADDR, &ptr, 1)) 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]);
        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(uint8_t slotId) {
        // 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(slotId, R820T_I2C_ADDR, &ptr, 1);

        uint8_t raw[1] = {0};
        if (!RtlI2cRead(slotId, 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, uint8_t slotId, uint32_t xtal, uint32_t intFreq) {
        d.slotId  = slotId;
        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;
    }

}
