//
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: GPL-3.0-or-later
//

#include "rhodium_radio_ctrl_impl.hpp"
#include "rhodium_cpld_ctrl.hpp"
#include "rhodium_constants.hpp"
#include <uhd/utils/log.hpp>

using namespace uhd;
using namespace uhd::usrp;
using namespace uhd::rfnoc;

namespace {
    const char* rx_band_to_log(rhodium_radio_ctrl_impl::rx_band rx_band)
    {
        switch (rx_band)
        {
        case rhodium_radio_ctrl_impl::rx_band::RX_BAND_0:
            return "0";
        case rhodium_radio_ctrl_impl::rx_band::RX_BAND_1:
            return "1";
        case rhodium_radio_ctrl_impl::rx_band::RX_BAND_2:
            return "2";
        case rhodium_radio_ctrl_impl::rx_band::RX_BAND_3:
            return "3";
        case rhodium_radio_ctrl_impl::rx_band::RX_BAND_4:
            return "4";
        case rhodium_radio_ctrl_impl::rx_band::RX_BAND_5:
            return "5";
        case rhodium_radio_ctrl_impl::rx_band::RX_BAND_6:
            return "6";
        case rhodium_radio_ctrl_impl::rx_band::RX_BAND_7:
            return "7";
        case rhodium_radio_ctrl_impl::rx_band::RX_BAND_INVALID:
            return "INVALID";
        default:
            UHD_THROW_INVALID_CODE_PATH();
        }
    }

    const char* tx_band_to_log(rhodium_radio_ctrl_impl::tx_band tx_band)
    {
        switch (tx_band)
        {
        case rhodium_radio_ctrl_impl::tx_band::TX_BAND_0:
            return "0";
        case rhodium_radio_ctrl_impl::tx_band::TX_BAND_1:
            return "1";
        case rhodium_radio_ctrl_impl::tx_band::TX_BAND_2:
            return "2";
        case rhodium_radio_ctrl_impl::tx_band::TX_BAND_3:
            return "3";
        case rhodium_radio_ctrl_impl::tx_band::TX_BAND_4:
            return "4";
        case rhodium_radio_ctrl_impl::tx_band::TX_BAND_5:
            return "5";
        case rhodium_radio_ctrl_impl::tx_band::TX_BAND_6:
            return "6";
        case rhodium_radio_ctrl_impl::tx_band::TX_BAND_7:
            return "7";
        case rhodium_radio_ctrl_impl::tx_band::TX_BAND_INVALID:
            return "INVALID";
        default:
            UHD_THROW_INVALID_CODE_PATH();
        }
    }
}

void rhodium_radio_ctrl_impl::_update_rx_freq_switches(
    const double freq
) {
    UHD_LOG_TRACE(unique_id(),
        "Update all RX freq related switches. f=" << freq << " Hz, "
    );
    const auto band = _map_freq_to_rx_band(freq);
    UHD_LOG_TRACE(unique_id(),
        "Selected band " << rx_band_to_log(band));

    // select values for lowband/highband switches
    const bool is_lowband = (band == rx_band::RX_BAND_0);
    auto rx_sw2_sw7 = is_lowband ?
        rhodium_cpld_ctrl::RX_SW2_SW7_LOWBANDFILTERBANK :
        rhodium_cpld_ctrl::RX_SW2_SW7_HIGHBANDFILTERBANK;
    auto rx_hb_lb_sel = is_lowband ?
        rhodium_cpld_ctrl::RX_HB_LB_SEL_LOWBAND :
        rhodium_cpld_ctrl::RX_HB_LB_SEL_HIGHBAND;

    // select values for filter bank switches
    rhodium_cpld_ctrl::rx_sw3_t rx_sw3;
    rhodium_cpld_ctrl::rx_sw4_sw5_t rx_sw4_sw5;
    rhodium_cpld_ctrl::rx_sw6_t rx_sw6;
    switch (band)
    {
    case rx_band::RX_BAND_0:
        // Low band doesn't use the filter banks, use configuration for band 1
    case rx_band::RX_BAND_1:
        rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4;
        rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ;
        rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5;
        break;
    case rx_band::RX_BAND_2:
        rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4;
        rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0760X1100MHZ;
        rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5;
        break;
    case rx_band::RX_BAND_3:
        rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4;
        rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1100X1410MHZ;
        rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5;
        break;
    case rx_band::RX_BAND_4:
        rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOSWITCH4;
        rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER1410X2050MHZ;
        rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMSWITCH5;
        break;
    case rx_band::RX_BAND_5:
        rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER2050X3000MHZ;
        rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ;
        rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER2050X3000MHZ;
        break;
    case rx_band::RX_BAND_6:
        rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER3000X4500MHZ;
        rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ;
        rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER3000X4500MHZ;
        break;
    case rx_band::RX_BAND_7:
        rx_sw3 = rhodium_cpld_ctrl::RX_SW3_TOFILTER4500X6000MHZ;
        rx_sw4_sw5 = rhodium_cpld_ctrl::RX_SW4_SW5_FILTER0450X0760MHZ;
        rx_sw6 = rhodium_cpld_ctrl::RX_SW6_FROMFILTER4500X6000MHZ;
        break;
    case rx_band::RX_BAND_INVALID:
        throw uhd::runtime_error(str(boost::format(
            "Cannot map RX frequency to band: %f") % freq));
    default:
        UHD_THROW_INVALID_CODE_PATH();
    }

    // commit settings to cpld
    _cpld->set_rx_switches(
        rx_sw2_sw7,
        rx_sw3,
        rx_sw4_sw5,
        rx_sw6,
        rx_hb_lb_sel
    );
}

void rhodium_radio_ctrl_impl::_update_tx_freq_switches(
    const double freq
){
    UHD_LOG_TRACE(unique_id(),
        "Update all TX freq related switches. f=" << freq << " Hz, "
    );

    const auto band = _map_freq_to_tx_band(freq);

    UHD_LOG_TRACE(unique_id(),
        "Selected band " << tx_band_to_log(band));

    // select values for lowband/highband switches
    const bool is_lowband = (band == tx_band::TX_BAND_0);
    auto tx_hb_lb_sel = is_lowband ?
        rhodium_cpld_ctrl::TX_HB_LB_SEL_LOWBAND :
        rhodium_cpld_ctrl::TX_HB_LB_SEL_HIGHBAND;

    // select values for filter bank switches
    rhodium_cpld_ctrl::tx_sw2_t tx_sw2;
    rhodium_cpld_ctrl::tx_sw3_sw4_t tx_sw3_sw4;
    rhodium_cpld_ctrl::tx_sw5_t tx_sw5;
    switch (band)
    {
    case tx_band::TX_BAND_0:
        // Low band doesn't use the filter banks, use configuration for band 1
    case tx_band::TX_BAND_1:
        tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3;
        tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ;
        tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4;
        break;
    case tx_band::TX_BAND_2:
        tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3;
        tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1000MHZ;
        tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4;
        break;
    case tx_band::TX_BAND_3:
        tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3;
        tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1350MHZ;
        tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4;
        break;
    case tx_band::TX_BAND_4:
        tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMSWITCH3;
        tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP1900MHZ;
        tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOSWITCH4;
        break;
    case tx_band::TX_BAND_5:
        tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP3000MHZ;
        tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ;
        tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP3000MHZ;
        break;
    case tx_band::TX_BAND_6:
        tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP4100MHZ;
        tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ;
        tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP4100MHZ;
        break;
    case tx_band::TX_BAND_7:
        tx_sw2 = rhodium_cpld_ctrl::TX_SW2_FROMTXFILTERLP6000MHZ;
        tx_sw3_sw4 = rhodium_cpld_ctrl::TX_SW3_SW4_FROMTXFILTERLP0650MHZ;
        tx_sw5 = rhodium_cpld_ctrl::TX_SW5_TOTXFILTERLP6000MHZ;
        break;
    case tx_band::TX_BAND_INVALID:
        throw uhd::runtime_error(str(boost::format(
            "Cannot map TX frequency to band: %f") % freq));
    default:
        UHD_THROW_INVALID_CODE_PATH();
    }

    // commit settings to cpld
    _cpld->set_tx_switches(
        tx_sw2,
        tx_sw3_sw4,
        tx_sw5,
        tx_hb_lb_sel
    );
}

void rhodium_radio_ctrl_impl::_update_rx_input_switches(
    const std::string &input
) {
    UHD_LOG_TRACE(unique_id(),
        "Update all RX input related switches. input=" << input
    );
    const rhodium_cpld_ctrl::cal_iso_sw_t cal_iso = (input == "CAL") ?
        rhodium_cpld_ctrl::CAL_ISO_CALLOOPBACK :
        rhodium_cpld_ctrl::CAL_ISO_ISOLATION;
    const rhodium_cpld_ctrl::rx_sw1_t sw1 = [input]{
        if (input == "TX/RX")
        {
            return rhodium_cpld_ctrl::RX_SW1_FROMTXRXINPUT;
        }
        else if (input == "RX2") {
            return rhodium_cpld_ctrl::RX_SW1_FROMRX2INPUT;
        }
        else if (input == "CAL") {
            return rhodium_cpld_ctrl::RX_SW1_FROMCALLOOPBACK;
        }
        else if (input == "TERM") {
            return rhodium_cpld_ctrl::RX_SW1_ISOLATION;
        }
        else {
            throw uhd::runtime_error("Invalid antenna in _update_rx_input_switches: " + input);
        }
    }();

    UHD_LOG_TRACE(unique_id(),
        "Selected switch values:"
        " sw1=" << sw1 <<
        " cal_iso=" << cal_iso
    );
    _cpld->set_rx_input_switches(sw1, cal_iso);
}

void rhodium_radio_ctrl_impl::_update_tx_output_switches(
    const std::string &output
) {
    UHD_LOG_TRACE(unique_id(),
        "Update all TX output related switches. output=" << output
    );
    rhodium_cpld_ctrl::tx_sw1_t sw1;

    if (output == "TX/RX")
    {
        //SW1 needs to select low/high band
        if (_is_tx_lowband(get_tx_frequency(0)))
        {
            sw1 = rhodium_cpld_ctrl::TX_SW1_TOLOWBAND;
        }
        else {
            sw1 = rhodium_cpld_ctrl::TX_SW1_TOSWITCH2;
        }
    }
    else if (output == "CAL") {
        sw1 = rhodium_cpld_ctrl::TX_SW1_TOCALLOOPBACK;
    }
    else if (output == "TERM") {
        sw1 = rhodium_cpld_ctrl::TX_SW1_ISOLATION;
    }
    else {
        throw uhd::runtime_error("Invalid antenna in _update_tx_output_switches: " + output);
    }

    UHD_LOG_TRACE(unique_id(),
        "Selected switch values: sw1=" << sw1
    );

    _cpld->set_tx_output_switches(sw1);
}