/*  -*-  mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8  -*-  */
/************************************************************************************
 **                                                                                 **
 **                                        UHSDR                                    **
 **               a powerful firmware for STM32 based SDR transceivers              **
 **                                                                                 **
 **---------------------------------------------------------------------------------**
 **                                                                                 **
 **  Licence:       GNU GPLv3                                                      **
 ************************************************************************************/
#include "uhsdr_board.h"
#include "uhsdr_hmc1023.h"

HMC1023_t hmc1023;

#ifdef USE_HMC1023

#define HMC1023_CS_PIN GPIO_PIN_9
#define HMC1023_CS_PORT GPIOG
#define hspiHmc1023 (hspi6)

#include <spi.h>

#define HMC1023_REG3_FINE_LIMIT  (11)

#define HMC1023_REG2_OPAMP_LIMIT (3)
#define HMC1023_REG2_OPAMP_SHIFT (0)
#define HMC1023_REG2_OPAMP_MASK  (0x03)

#define HMC1023_REG2_DRVR_LIMIT (3)
#define HMC1023_REG2_DRVR_SHIFT (2)
#define HMC1023_REG2_DRVR_MASK (0x03)

#define HMC1023_REG2_GAIN_LIMIT (1)
#define HMC1023_REG2_GAIN_SHIFT (4)
#define HMC1023_REG2_GAIN_MASK (0x01)

#define HMC1023_REG2_BYPASS_LIMIT (1)
#define HMC1023_REG2_BYPASS_SHIFT (5)
#define HMC1023_REG2_BYPASS_MASK (0x01)

#define HMC1023_REG2_COARSE_LIMIT (8)
#define HMC1023_REG2_COARSE_SHIFT (6)
#define HMC1023_REG2_COARSE_MASK (0x0f)

#define HMC1023_REG1_USE_SPI_SETTINGS (1 << 1)
#define HMC1023_REG1_FORCE_CAL_CODE (1 << 4)

/**
 * Switch hardware SPI settings from tx to rx mode and vice versa. Also provides necessary delay between
 * tx and rx.
 * HMC1023LP5E needs phase == 1EDGE for transmit by STM and 2EDGE for RX sampling by STM. Who designs such an SPI protocol?
 * @param is_tx true -> configure for  tx mode, false -> for rx mode
 */
static void hmc1023_ll_spi_tx(bool is_tx)
{

  hspiHmc1023.Init.CLKPhase = is_tx == true? SPI_PHASE_1EDGE : SPI_PHASE_2EDGE;
  if (HAL_SPI_Init(&hspi6) != HAL_OK)
  {
    Error_Handler();
  }
  __HAL_SPI_ENABLE(&hspi6);
}

/**
 * Control selection of HMC1023LP5E. Please note, output of SDO does not go tristate if deselected!
 * @param on true -> chip is selected (HMC1023LP5E input EN == LOW), false -> not selected
 */
static void hmc1023_ll_cs(bool on)
{
    if (on)
    {
        GPIO_ResetBits(HMC1023_CS_PORT,HMC1023_CS_PIN);
    }
    else
    {
        GPIO_SetBits(HMC1023_CS_PORT,HMC1023_CS_PIN);
    }
}

/**
 * Write  register value into the HMC1023LP5E. It is not checked if the value was received correctly!
 * @param regaddr which register to write to, not range checked!
 * @param value 24 bit value
 * @return HMC1023_ERROR if failed, HMC1023_OK otherwise.
 */
static uint32_t hmc1023_ll_write(uint32_t regaddr, uint32_t value)
{
    uint8_t tx_data[4];
    uint32_t retval = HMC1023_ERROR;

    tx_data[0] = value >> 16;
    tx_data[1] = value >> 8;
    tx_data[2] = value;
    tx_data[3] = 0x5 | (regaddr << 3); // chip id & register to write;

    hmc1023_ll_cs(true);
    if (HAL_SPI_Transmit(&hspi6,tx_data,4,100) == HAL_OK)
    {
        retval = HMC1023_OK;
    }
    hmc1023_ll_cs(false);
    return retval;
}
/**
 *
 * @param regaddr which register to read, not range checked!
 * @return 24 bit value in case of success, HMC1023_ERROR otherwise
 */
static uint32_t hmc1023_ll_read(uint32_t regaddr)
{

    uint32_t retval = HMC1023_ERROR;
    // write register address to read to register 0
    if (hmc1023_ll_write(0,regaddr) == HAL_OK)
    {
        uint8_t tx_data[4] = { 0, 0, 0, 0};
        uint8_t rx_data[4];

        hmc1023_ll_spi_tx(false);
        // reconfig through HAL should cause enough delay
        hmc1023_ll_cs(true);

        // now all tx_data is zero and we try to read using
        // the rx spi config
        if (HAL_SPI_TransmitReceive(&hspi6,tx_data,rx_data, 4,100) == HAL_OK)
        {
            if (( rx_data[3] & 0x07) == 5)  // we found the correct "chip id"
            {
                retval = rx_data[0] << 16 | rx_data[1] << 8 | rx_data[2];
            }
        }

        hmc1023_ll_cs(false);
        hmc1023_ll_spi_tx(true);

    }

    return retval;
}

/**
 *
 * @return true if HMC1023LP5E was detected by reading the correct id
 */
static bool hmc1023_ll_presence(void)
{
    uint32_t value = hmc1023_ll_read(0);
    return (value == 0x114080);
}

/**
 * Set bits in register 2
 * @param bits actual bits to set (zero based, bits will be later shifted)
 * @param limit maximum permitted value for bits (0...limit) is permitted range
 * @param mask bit mask for bits (zero based, bits will be later shifted)
 * @param shift position of mask (how many bits to shift left for final position)
 */

static void hmc1023_set_reg2_bits(uint8_t bits,uint8_t limit, uint32_t mask, uint8_t shift)
{
    if (bits <= limit)
    {
        const uint32_t shifted_mask = (mask << shift);
        const uint32_t shifted_bits = (bits << shift);

        if ( (hmc1023.reg2 & shifted_mask)  != shifted_bits)
        {
            MODIFY_REG(hmc1023.reg2,shifted_mask,shifted_bits);
            hmc1023_ll_write(2,hmc1023.reg2);
        }
    }
}

/**
 * Sets the coarse bandwidth from about 5 / 7 / 10 / 14 / 20 / 28 / 40 / 50 / 72
 * Note +/-20% variation if not calibrated!
 * @param coarse 0 - 8
 */
void hmc1023_set_coarse(uint8_t value)
{
    hmc1023_set_reg2_bits(value,HMC1023_REG2_COARSE_LIMIT,HMC1023_REG2_COARSE_MASK, HMC1023_REG2_COARSE_SHIFT);
}

/**
 * Control HMC1023LP5E opamp bias
 * @param value: 0 - 3 (higher value, higher bias)
 */
void hmc1023_set_bias_opamp(uint8_t value)
{
    hmc1023_set_reg2_bits(value,HMC1023_REG2_OPAMP_LIMIT,HMC1023_REG2_OPAMP_MASK, HMC1023_REG2_OPAMP_SHIFT);
}

/**
 * Control HMC1023LP5E driver bias
 * @param value: 0 - 3 (higher value, higher bias)
 */
void hmc1023_set_bias_drvr(uint8_t value)
{
    hmc1023_set_reg2_bits(value,HMC1023_REG2_DRVR_LIMIT,HMC1023_REG2_DRVR_MASK, HMC1023_REG2_DRVR_SHIFT);
}

/**
 * Sets the 10db amplifier on or off
 * @param on true on / false off
 */
void hmc1023_set_gain(bool on)
{
    hmc1023_set_reg2_bits(on?1:0,HMC1023_REG2_GAIN_LIMIT,HMC1023_REG2_GAIN_MASK, HMC1023_REG2_GAIN_SHIFT);
}

/**
 * Sets the LPF bypass on or off
 * @param on true on / false off
 */
void hmc1023_set_bypass(bool on)
{
    hmc1023_set_reg2_bits(on?1:0,HMC1023_REG2_BYPASS_LIMIT,HMC1023_REG2_BYPASS_MASK, HMC1023_REG2_BYPASS_SHIFT);
}

/**
 * adjusts the lpf bandwidth from about 0.803 to about 1.183 of the coarse frequency
 * Note +/-20% variation if not calibrated!
 * @param fine 0 - 11
 */
void hmc1023_set_fine(uint8_t fine)
{
    if (fine < HMC1023_REG3_FINE_LIMIT)
    {
        if ( (hmc1023.reg3)  != fine )
        {
            hmc1023.reg3 = fine;
            hmc1023_ll_write(3,hmc1023.reg3);
        }
    }
}

/**
 * use HMC1023LP5E register settings for coarse and fine LPF bandwidth control
 * please note that this enables both coarse and fine bandwidth from SPI, this is incompatible with
 * automatic calibration as discussed in data sheet.
 * @param on true -> use bandwidth settings in reg2/reg3, false -> use internal defaults
 */
void hmc1023_use_spi_settings(bool on)
{
    const uint32_t target_bits = on == true ? (HMC1023_REG1_USE_SPI_SETTINGS | HMC1023_REG1_FORCE_CAL_CODE) : 0;
    const uint32_t target_mask = (HMC1023_REG1_USE_SPI_SETTINGS | HMC1023_REG1_FORCE_CAL_CODE);
    if ((hmc1023.reg1 & target_mask) != target_bits)
    {
        MODIFY_REG(hmc1023.reg1, target_mask, target_bits);
        hmc1023_ll_write(1,hmc1023.reg1);
    }
}

/**
 * Configures SPI bus 6 AND CS GPIO PG9 to detect and control an HMC1023LP5E LPF
 * presence of an LPF is return via hmc1023.present
 * Does not return hardware configuration to original state, even if LPF is not detected!
 */
void hmc1023_init()
{

    // TODO: Move to HAL Config
    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(HMC1023_CS_PORT, HMC1023_CS_PIN, GPIO_PIN_SET);

    GPIO_InitTypeDef GPIO_InitStruct;
    /*Configure GPIO pins : PFPin PFPin PFPin */
    GPIO_InitStruct.Pin = HMC1023_CS_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(HMC1023_CS_PORT, &GPIO_InitStruct);

    // setup SPI with correct data
    hspiHmc1023.Init.Mode = SPI_MODE_MASTER;
    hspiHmc1023.Init.Direction = SPI_DIRECTION_2LINES;
    hspiHmc1023.Init.DataSize = SPI_DATASIZE_8BIT;
    hspiHmc1023.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspiHmc1023.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspiHmc1023.Init.NSS = SPI_NSS_SOFT;
    hspiHmc1023.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
    hspiHmc1023.Init.FirstBit = SPI_FIRSTBIT_MSB;

    hmc1023_ll_spi_tx(true);
    // make sure we are in right config for TX,
    // which is assumed by the read/write code


    hmc1023.present = hmc1023_ll_presence();

    if (hmc1023.present)
    {
        hmc1023.reg1 = hmc1023_ll_read(1);
        hmc1023.reg2 = hmc1023_ll_read(2);
        hmc1023.reg2 = hmc1023_ll_read(3);
        hmc1023_use_spi_settings(true);
        hmc1023_set_bias_opamp(1);
        hmc1023_set_bias_opamp(0);
    }
}
#endif // UI_BRD_OVI40
