//*****************************************************************************
//
//! @file HM01B0.c
//!
//
//*****************************************************************************

#include "am_mcu_apollo.h"
#include "am_bsp.h"
#include "am_util.h"
#include "HM01B0.h"
#include "HM01B0_Walking1s_01.h"
#include "platform.h"

#define read_vsync()    (AM_REGVAL(AM_REGADDR(GPIO, RDA)) & (1 << HM01B0_PIN_VSYNC))
#define read_hsync()    (AM_REGVAL(AM_REGADDR(GPIO, RDA)) & (1 << HM01B0_PIN_HSYNC))
#define read_pclk()     (AM_REGVAL(AM_REGADDR(GPIO, RDA)) & (1 << HM01B0_PIN_PCLK))
#define read_byte()     (APBDMA->BBINPUT)

const am_hal_gpio_pincfg_t g_HM01B0_pin_int =
{
    .uFuncSel = 3,
    .eGPOutcfg      = AM_HAL_GPIO_PIN_OUTCFG_DISABLE,
    .eIntDir        = AM_HAL_GPIO_PIN_INTDIR_LO2HI,
    .eGPInput       = AM_HAL_GPIO_PIN_INPUT_ENABLE,
    .eGPRdZero      = AM_HAL_GPIO_PIN_RDZERO_READPIN
};

//*****************************************************************************
//
//! @brief Write HM01B0 registers
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//! @param ui16Reg              - Register address.
//! @param pui8Value            - Pointer to the data to be written.
//! @param ui32NumBytes         - Length of the data in bytes to be written.
//!
//! This function writes value to HM01B0 registers.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_write_reg(hm01b0_cfg_t *psCfg, \
                            uint16_t ui16Reg, uint8_t *pui8Value, uint32_t ui32NumBytes)
{
    am_hal_iom_transfer_t Transaction;

    //
    // Create the transaction.
    //
    Transaction.ui32InstrLen                = sizeof(uint16_t);
    Transaction.ui32Instr                   = (ui16Reg & 0x0000FFFF);
    Transaction.eDirection                  = AM_HAL_IOM_TX;
    Transaction.ui32NumBytes                = ui32NumBytes;
    Transaction.pui32TxBuffer               = (uint32_t *) pui8Value;
    Transaction.uPeerInfo.ui32I2CDevAddr    = (uint32_t) psCfg->ui16SlvAddr;
    Transaction.bContinue                   = false;
    Transaction.ui8RepeatCount              = 0;
    Transaction.ui32PauseCondition          = 0;
    Transaction.ui32StatusSetClr            = 0;

    //
    // Execute the transction over IOM.
    //
    if (am_hal_iom_blocking_transfer(psCfg->pIOMHandle, &Transaction))
    {
        return HM01B0_ERR_I2C;
    }

    return HM01B0_ERR_OK;

}

//*****************************************************************************
//
//! @brief Read HM01B0 registers
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//! @param ui16Reg              - Register address.
//! @param pui8Value            - Pointer to the buffer for read data to be put into.
//! @param ui32NumBytes         - Length of the data to be read.
//!
//! This function reads value from HM01B0 registers.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_read_reg(hm01b0_cfg_t *psCfg, \
                            uint16_t ui16Reg, uint8_t *pui8Value, uint32_t ui32NumBytes)
{
    am_hal_iom_transfer_t  Transaction;

    //
    // Create the transaction.
    //
    Transaction.ui32InstrLen                = sizeof(uint16_t);
    Transaction.ui32Instr                   = (ui16Reg & 0x0000FFFF);
    Transaction.eDirection                  = AM_HAL_IOM_RX;
    Transaction.ui32NumBytes                = ui32NumBytes;
    Transaction.pui32RxBuffer               = (uint32_t *) pui8Value;;
    Transaction.uPeerInfo.ui32I2CDevAddr    = (uint32_t) psCfg->ui16SlvAddr;
    Transaction.bContinue                   = false;
    Transaction.ui8RepeatCount              = 0;
    Transaction.ui32PauseCondition          = 0;
    Transaction.ui32StatusSetClr            = 0;

    //
    // Execute the transction over IOM.
    //
    if (am_hal_iom_blocking_transfer(psCfg->pIOMHandle, &Transaction))
    {
        return HM01B0_ERR_I2C;
    }

    return HM01B0_ERR_OK;
}

//*****************************************************************************
//
//! @brief Load HM01B0 a given script
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//! @param psScrip              - Pointer to the script to be loaded.
//! @param ui32ScriptCmdNum     - Number of entries in a given script.
//!
//! This function loads HM01B0 a given script.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_load_script(hm01b0_cfg_t *psCfg, hm_script_t *psScript, uint32_t ui32ScriptCmdNum)
{
    uint32_t ui32Err = HM01B0_ERR_OK;
    for (uint32_t idx = 0; idx < ui32ScriptCmdNum; idx++)
    {
        ui32Err = hm01b0_write_reg(psCfg, \
                                    (psScript + idx)->ui16Reg, \
                                    &((psScript + idx)->ui8Val), \
                                    sizeof(uint8_t));
        if (ui32Err != HM01B0_ERR_OK)
        {
            break;
        }
    }

    return ui32Err;   
}

//*****************************************************************************
//
//! @brief Power up HM01B0
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//!
//! This function powers up HM01B0.
//!
//! @return none.
//
//*****************************************************************************
void hm01b0_power_up(hm01b0_cfg_t *psCfg)
{
    // place holder
}

//*****************************************************************************
//
//! @brief Power down HM01B0
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//!
//! This function powers up HM01B0.
//!
//! @return none.
//
//*****************************************************************************
void hm01b0_power_down(hm01b0_cfg_t *psCfg)
{
    // place holder
}

//*****************************************************************************
//
//! @brief Enable MCLK
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//!
//! This function utilizes CTimer to generate MCLK for HM01B0.
//!
//! @return none.
//
//*****************************************************************************
void hm01b0_mclk_enable(hm01b0_cfg_t *psCfg)
{
#define MCLK_UI64PATTERN        0x55555555
#define MCLK_UI64PATTERNLEN     31

    am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_SYSCLK_MAX, 0);

    //
    // Set up timer.
    //
    am_hal_ctimer_clear(psCfg->ui32CTimerModule, psCfg->ui32CTimerSegment);

    am_hal_ctimer_config_single(psCfg->ui32CTimerModule,
                                    psCfg->ui32CTimerSegment,
                                    (
                                        AM_HAL_CTIMER_FN_PTN_REPEAT |
                                        AM_HAL_CTIMER_HFRC_12MHZ
                                    )
                                );

    //
    // Set the pattern in the CMPR registers.
    //
    am_hal_ctimer_compare_set(psCfg->ui32CTimerModule, psCfg->ui32CTimerSegment, 0, 
                            (uint32_t)(MCLK_UI64PATTERN & 0xFFFF));
    am_hal_ctimer_compare_set(psCfg->ui32CTimerModule, psCfg->ui32CTimerSegment, 1, 
                            (uint32_t)((MCLK_UI64PATTERN >> 16) & 0xFFFF));

    //
    // Set the timer trigger and pattern length.
    //
    am_hal_ctimer_config_trigger(psCfg->ui32CTimerModule,
                                    psCfg->ui32CTimerSegment,
                                    (
                                        (MCLK_UI64PATTERNLEN << CTIMER_AUX0_TMRA0LMT_Pos) |
                                        (CTIMER_AUX0_TMRB0TRIG_DIS << CTIMER_AUX0_TMRA0TRIG_Pos)
                                    )
                                );

    //
    // Configure timer output pin.
    //
    am_hal_ctimer_output_config(psCfg->ui32CTimerModule,
                                psCfg->ui32CTimerSegment,
                                psCfg->ui32CTimerOutputPin, 
                                AM_HAL_CTIMER_OUTPUT_NORMAL, 
                                AM_HAL_GPIO_PIN_DRIVESTRENGTH_12MA);

    //
    // Start the timer.
    //
    am_hal_ctimer_start(psCfg->ui32CTimerModule, psCfg->ui32CTimerSegment);

}

//*****************************************************************************
//
//! @brief Disable MCLK
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//!
//! This function disable CTimer to stop MCLK for HM01B0.
//!
//! @return none.
//
//*****************************************************************************
void hm01b0_mclk_disable(hm01b0_cfg_t *psCfg)
{
    //
    // Stop the timer.
    //
    am_hal_ctimer_stop(psCfg->ui32CTimerModule, psCfg->ui32CTimerSegment);
    am_hal_gpio_pinconfig(psCfg->ui32CTimerOutputPin, g_AM_HAL_GPIO_DISABLE);

}

//*****************************************************************************
//
//! @brief Initialize interfaces
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//!
//! This function initializes interfaces.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_init_if(hm01b0_cfg_t *psCfg)
{
    void *pIOMHandle = NULL;

    if ( psCfg->ui32IOMModule > AM_REG_IOM_NUM_MODULES )
    {
        return HM01B0_ERR_I2C;
    }

    //
    // Enable fault detection.
    //
#if AM_APOLLO3_MCUCTRL
    am_hal_mcuctrl_control(AM_HAL_MCUCTRL_CONTROL_FAULT_CAPTURE_ENABLE, 0);
#else // AM_APOLLO3_MCUCTRL
    am_hal_mcuctrl_fault_capture_enable();
#endif // AM_APOLLO3_MCUCTRL

    //
    // Initialize the IOM instance.
    // Enable power to the IOM instance.
    // Configure the IOM for Serial operation during initialization.
    // Enable the IOM.
    //
    if (am_hal_iom_initialize(psCfg->ui32IOMModule, &pIOMHandle) ||
        am_hal_iom_power_ctrl(pIOMHandle, AM_HAL_SYSCTRL_WAKE, false) ||
        am_hal_iom_configure(pIOMHandle, &(psCfg->sIOMCfg)) ||
        am_hal_iom_enable(pIOMHandle))
    {
        return HM01B0_ERR_I2C;
    }
    else
    {
        //
        // Configure the IOM pins.
        //
        am_bsp_iom_pins_enable(psCfg->ui32IOMModule, psCfg->eIOMMode);

        psCfg->pIOMHandle = pIOMHandle;
    }

    // initialize pins for camera parallel interface.
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD0);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD1);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD2);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD3);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD4);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD5);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD6);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD7);

    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD0);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD1);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD2);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD3);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD4);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD5);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD6);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD7);

    am_hal_gpio_fast_pinconfig((uint64_t)0x1 << psCfg->ui8PinD0 |
                                 (uint64_t)0x1 << psCfg->ui8PinD1 |
                                 (uint64_t)0x1 << psCfg->ui8PinD2 |
                                 (uint64_t)0x1 << psCfg->ui8PinD3 |
                                 (uint64_t)0x1 << psCfg->ui8PinD4 |
                                 (uint64_t)0x1 << psCfg->ui8PinD5 |
                                 (uint64_t)0x1 << psCfg->ui8PinD6 |
                                 (uint64_t)0x1 << psCfg->ui8PinD7,
                                 g_AM_HAL_GPIO_INPUT, 0);

    am_hal_gpio_pinconfig(psCfg->ui8PinVSYNC,   g_AM_HAL_GPIO_INPUT);
    am_hal_gpio_pinconfig(psCfg->ui8PinHSYNC,   g_AM_HAL_GPIO_INPUT);
    am_hal_gpio_pinconfig(psCfg->ui8PinPCLK,    g_AM_HAL_GPIO_INPUT);

    am_hal_gpio_pinconfig(psCfg->ui8PinTrig,    g_AM_HAL_GPIO_OUTPUT);

    am_hal_gpio_pinconfig(psCfg->ui8PinInt,     g_AM_HAL_GPIO_DISABLE);
    // am_hal_gpio_pinconfig(psCfg->ui8PinInt,     g_HM01B0_pin_int);
    // am_hal_gpio_interrupt_clear(AM_HAL_GPIO_BIT(psCfg->ui8PinInt));
    // am_hal_gpio_interrupt_enable(AM_HAL_GPIO_BIT(psCfg->ui8PinInt));
    // NVIC_EnableIRQ(GPIO_IRQn);

    return HM01B0_ERR_OK;
}

//*****************************************************************************
//
//! @brief Deinitialize interfaces
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//!
//! This function deinitializes interfaces.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_deinit_if(hm01b0_cfg_t *psCfg)
{
    am_hal_iom_disable(psCfg->pIOMHandle);
    am_hal_iom_uninitialize(psCfg->pIOMHandle);

    am_hal_gpio_pinconfig(psCfg->ui8PinSCL,   g_AM_HAL_GPIO_DISABLE);
    am_hal_gpio_pinconfig(psCfg->ui8PinSDA,   g_AM_HAL_GPIO_DISABLE);

    // initialize pins for camera parallel interface.
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD0);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD1);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD2);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD3);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD4);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD5);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD6);
    am_hal_gpio_fastgpio_disable(psCfg->ui8PinD7);

    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD0);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD1);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD2);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD3);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD4);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD5);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD6);
    am_hal_gpio_fastgpio_clr(psCfg->ui8PinD7);

    am_hal_gpio_pinconfig(psCfg->ui8PinVSYNC,   g_AM_HAL_GPIO_DISABLE);
    am_hal_gpio_pinconfig(psCfg->ui8PinHSYNC,   g_AM_HAL_GPIO_DISABLE);
    am_hal_gpio_pinconfig(psCfg->ui8PinPCLK,    g_AM_HAL_GPIO_DISABLE);
    am_hal_gpio_pinconfig(psCfg->ui8PinTrig,    g_AM_HAL_GPIO_DISABLE);
    am_hal_gpio_pinconfig(psCfg->ui8PinInt,     g_AM_HAL_GPIO_DISABLE);


    return HM01B0_ERR_OK;
}

//*****************************************************************************
//
//! @brief Get HM01B0 Model ID
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//! @param pui16MID             - Pointer to buffer for the read back model ID.
//!
//! This function reads back HM01B0 model ID.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_get_modelid(hm01b0_cfg_t *psCfg, uint16_t *pui16MID)
{
    uint8_t ui8Data[1];
    uint32_t ui32Err;

    *pui16MID = 0x0000;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_MODEL_ID_H, ui8Data, sizeof(ui8Data));
    if (ui32Err == HM01B0_ERR_OK)
    {
        *pui16MID |= (ui8Data[0] << 8);
    }

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_MODEL_ID_L, ui8Data, sizeof(ui8Data));
    if (ui32Err == HM01B0_ERR_OK)
    {
        *pui16MID |= ui8Data[0];
    }

    return ui32Err;
}

//*****************************************************************************
//
//! @brief Initialize HM01B0
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//! @param psScript             - Pointer to HM01B0 initialization script.
//! @param ui32ScriptCmdNum     - No. of commands in HM01B0 initialization script.
//!
//! This function initilizes HM01B0 with a given script.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_init_system(hm01b0_cfg_t *psCfg, hm_script_t *psScript, uint32_t ui32ScriptCmdNum)
{
    return hm01b0_load_script(psCfg, psScript, ui32ScriptCmdNum);
}

//*****************************************************************************
//
//! @brief Set HM01B0 in the walking 1s test mode
//!
//! @param psCfg                - Pointer to HM01B0 configuration structure.
//!
//! This function sets HM01B0 in the walking 1s test mode.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_test_walking1s(hm01b0_cfg_t *psCfg)
{
    uint32_t ui32ScriptCmdNum = sizeof(sHM01b0TestModeScript_Walking1s) / sizeof(hm_script_t);
    hm_script_t *psScript = (hm_script_t *)sHM01b0TestModeScript_Walking1s;

    return hm01b0_load_script(psCfg, psScript, ui32ScriptCmdNum);
}

//*****************************************************************************
//
//! @brief Check the data read from HM01B0 in the walking 1s test mode
//!
//! @param pui8Buffer       - Pointer to data buffer.
//! @param ui32BufferLen    - Buffer length
//! @param ui32PrintCnt     - Number of mismatched data to be printed out
//!
//! This function sets HM01B0 in the walking 1s test mode.
//!
//! @return Error code.
//
//*****************************************************************************
void hm01b0_test_walking1s_check_data_sanity(uint8_t *pui8Buffer, uint32_t ui32BufferLen, uint32_t ui32PrintCnt)
{
    uint8_t ui8ByteData = *pui8Buffer;
    uint32_t ui32MismatchCnt = 0x00;

    for (uint32_t ui32Idx = 0; ui32Idx < ui32BufferLen; ui32Idx++)
    {
        if (*(pui8Buffer + ui32Idx) != ui8ByteData)
        {
            if (ui32PrintCnt)
            {
                am_util_stdio_printf("[0x%08X] actual 0x%02X expected 0x%02X\n", ui32Idx, *(pui8Buffer + ui32Idx), ui8ByteData);
                am_util_delay_ms(1);
                ui32PrintCnt--;
            }
            ui32MismatchCnt++;
        }

        if (ui8ByteData)
            ui8ByteData = ui8ByteData << 1;
        else
            ui8ByteData = 0x01;
    }

    am_util_stdio_printf("Mismatch Rate %d/%d\n", ui32MismatchCnt, ui32BufferLen);

}

//*****************************************************************************
//
//! @brief Software reset HM01B0
//!
//! @param psCfg        - Pointer to HM01B0 configuration structure.
//!
//! This function resets HM01B0 by issuing a reset command.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_reset_sw(hm01b0_cfg_t *psCfg)
{
    uint8_t ui8Data[1] = {0x00};
    return hm01b0_write_reg(psCfg, HM01B0_REG_SW_RESET, ui8Data, sizeof(ui8Data));
}

//*****************************************************************************
//
//! @brief Get current HM01B0 operation mode.
//!
//! @param psCfg        - Pointer to HM01B0 configuration structure.
//! @param pui8Mode     - Pointer to buffer
//!                     - for the read back operation mode to be put into
//!
//! This function get HM01B0 operation mode.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_get_mode(hm01b0_cfg_t *psCfg, uint8_t *pui8Mode)
{
    uint8_t ui8Data[1] = {0x01};
    uint32_t ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_MODE_SELECT, ui8Data, sizeof(ui8Data));

    *pui8Mode = ui8Data[0];

    return ui32Err;
}

//*****************************************************************************
//
//! @brief Set HM01B0 operation mode.
//!
//! @param psCfg        - Pointer to HM01B0 configuration structure.
//! @param ui8Mode      - Operation mode. One of:
//!     HM01B0_REG_MODE_SELECT_STANDBY
//!     HM01B0_REG_MODE_SELECT_STREAMING
//!     HM01B0_REG_MODE_SELECT_STREAMING_NFRAMES
//!     HM01B0_REG_MODE_SELECT_STREAMING_HW_TRIGGER
//! @param ui8FrameCnt  - Frame count for HM01B0_REG_MODE_SELECT_STREAMING_NFRAMES.
//!                     - Discarded if other modes.
//!
//! This function set HM01B0 operation mode.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_set_mode(hm01b0_cfg_t *psCfg, uint8_t ui8Mode, uint8_t ui8FrameCnt)
{
    uint32_t ui32Err = HM01B0_ERR_OK;

    if (ui8Mode == HM01B0_REG_MODE_SELECT_STREAMING_NFRAMES)
    {
        ui32Err = hm01b0_write_reg(psCfg, HM01B0_REG_PMU_PROGRAMMABLE_FRAMECNT, &ui8FrameCnt, sizeof(ui8FrameCnt));
    }

    if(ui32Err == HM01B0_ERR_OK)
    {
        ui32Err = hm01b0_write_reg(psCfg, HM01B0_REG_MODE_SELECT, &ui8Mode, sizeof(ui8Mode));
    }

    return ui32Err;
}

//*****************************************************************************
//
//! @brief Activate the updated settings to HM01B0.
//!
//! @param psCfg            - Pointer to HM01B0 configuration structure.
//!
//! Some settings updated to HM01B0 will only be affected after calling this function
//! 1. AE settings
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_cmd_update(hm01b0_cfg_t *psCfg)
{
    uint8_t     ui8Data = HM01B0_REG_GRP_PARAM_HOLD_HOLD;

    return hm01b0_write_reg(psCfg, HM01B0_REG_GRP_PARAM_HOLD, &ui8Data, sizeof(ui8Data));
}

//*****************************************************************************
//
//! @brief Get HM01B0 AE convergance
//!
//! @param psCfg            - Pointer to HM01B0 configuration structure.
//! @param psAECfg          - Pointer to the structure hm01b0_ae_cfg_t.
//!
//! This function checks if AE is converged or not and returns ui32Err accordingly.
//! If caller needs detailed AE settings, psAECfg has to be non NULL.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_get_ae(hm01b0_cfg_t *psCfg, hm01b0_ae_cfg_t *psAECfg)
{
    uint32_t    ui32Err = HM01B0_ERR_OK;
    uint8_t     ui8AETargetMean;
    uint8_t     ui8AEMinMean;
    uint8_t     ui8AEMean;
    uint8_t     ui8ConvergeInTh;
    uint8_t     ui8ConvergeOutTh;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_AE_TARGET_MEAN, &ui8AETargetMean, sizeof(ui8AETargetMean));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_AE_MIN_MEAN, &ui8AEMinMean, sizeof(ui8AEMinMean));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_CONVERGE_IN_TH, &ui8ConvergeInTh, sizeof(ui8ConvergeInTh));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_CONVERGE_OUT_TH, &ui8ConvergeOutTh, sizeof(ui8ConvergeOutTh));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, 0x2020, &ui8AEMean, sizeof(ui8AEMean));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    if ((ui8AEMean < (ui8AETargetMean - ui8ConvergeInTh)) || (ui8AEMean > (ui8AETargetMean + ui8ConvergeInTh)))
        ui32Err = HM01B0_ERR_AE_NOT_CONVERGED;

    if (psAECfg)
    {
        psAECfg->ui8AETargetMean    = ui8AETargetMean;
        psAECfg->ui8AEMinMean       = ui8AEMinMean;
        psAECfg->ui8ConvergeInTh    = ui8ConvergeInTh;
        psAECfg->ui8ConvergeOutTh   = ui8ConvergeOutTh;
        psAECfg->ui8AEMean          = ui8AEMean;
    }

    return ui32Err;
}

//*****************************************************************************
//
//! @brief AE calibration.
//!
//! @param psCfg            - Pointer to HM01B0 configuration structure.
//! @param ui8CalFrames     - Frame counts for calibratoin.
//! @param pui8Buffer       - Pointer to the frame buffer.
//! @param ui32BufferLen    - Framebuffer size.
//!
//! This function lets HM01B0 AE settled as much as possible within a given frame counts.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_cal_ae(hm01b0_cfg_t *psCfg, uint8_t ui8CalFrames, uint8_t *pui8Buffer, uint32_t ui32BufferLen)
{
    uint32_t        ui32Err     = HM01B0_ERR_OK;
    hm01b0_ae_cfg_t sAECfg;

    am_util_stdio_printf("[%s] +\n", __func__);

    hm01b0_set_mode(psCfg, HM01B0_REG_MODE_SELECT_STREAMING_NFRAMES, ui8CalFrames);

    for (uint8_t i = 0; i < ui8CalFrames; i++)
    {

        hm01b0_blocking_read_oneframe(psCfg, pui8Buffer, ui32BufferLen);

        ui32Err = hm01b0_get_ae(psCfg, &sAECfg);

        am_util_stdio_printf("AE Calibration(0x%02X) TargetMean 0x%02X, ConvergeInTh 0x%02X, AEMean 0x%02X\n", \
                                        ui32Err, sAECfg.ui8AETargetMean, sAECfg.ui8ConvergeInTh, sAECfg.ui8AEMean);

        // if AE calibration is done in ui8CalFrames, just exit to save some time.
        if (ui32Err == HM01B0_ERR_OK)
            break;
    }

    hm01b0_set_mode(psCfg, HM01B0_REG_MODE_SELECT_STANDBY, 0);

    am_util_stdio_printf("[%s] -\n", __func__);

    return ui32Err;
}


//*****************************************************************************
//
//! @brief Save HM01B0 exposure gain settings.
//!
//! @param psCfg            - Pointer to HM01B0 configuration structure.
//! @param psExpoGainCtrl   - Pointer to the structure hm01b0_snr_expo_gain_ctrl_t
//!
//! This function saves HM01B0 exposure gain settings.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_save_exposure_gains(hm01b0_cfg_t *psCfg, hm01b0_snr_expo_gain_ctrl_t *psExpoGainCtrl)
{
    uint32_t ui32Err = HM01B0_ERR_OK;
    uint8_t ui8IntegrationH;
    uint8_t ui8IntegrationL;
    uint8_t ui8AGain;
    uint8_t ui8DGain_H;
    uint8_t ui8DGain_L;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_INTEGRATION_H,     &ui8IntegrationH, sizeof(ui8IntegrationH));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_INTEGRATION_L,     &ui8IntegrationL, sizeof(ui8IntegrationL));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_ANALOG_GAIN,       &ui8AGain,        sizeof(ui8AGain));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_DIGITAL_GAIN_H,    &ui8DGain_H,      sizeof(ui8DGain_H));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_DIGITAL_GAIN_L,    &ui8DGain_L,      sizeof(ui8DGain_L));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    if (psExpoGainCtrl)
    {
        psExpoGainCtrl->ui8IntegrationH     = ui8IntegrationH;
        psExpoGainCtrl->ui8IntegrationL     = ui8IntegrationL;
        psExpoGainCtrl->ui8AGain            = ui8AGain;
        psExpoGainCtrl->ui8DGain_H          = ui8DGain_H;
        psExpoGainCtrl->ui8DGain_L          = ui8DGain_L;
    }

    return ui32Err;
}

//*****************************************************************************
//
//! @brief Restore HM01B0 exposure gain settings.
//!
//! @param psCfg            - Pointer to HM01B0 configuration structure.
//! @param psExpoGainCtrl   - Pointer to the structure hm01b0_snr_expo_gain_ctrl_t
//!
//! This function restores HM01B0 exposure gain settings. The call flow shall be
//! hm01b0_restore_exposure_gains() -> hm01b0_cmd_update() -> hm01b0_set_mode()
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_restore_exposure_gains(hm01b0_cfg_t *psCfg, hm01b0_snr_expo_gain_ctrl_t *psExpoGainCtrl)
{
    uint32_t    ui32Err = HM01B0_ERR_OK;
    uint8_t     ui8Tmp;

    ui32Err = hm01b0_write_reg(psCfg, HM01B0_REG_INTEGRATION_H,  &(psExpoGainCtrl->ui8IntegrationH), sizeof(psExpoGainCtrl->ui8IntegrationH));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_write_reg(psCfg, HM01B0_REG_INTEGRATION_L,  &(psExpoGainCtrl->ui8IntegrationL), sizeof(psExpoGainCtrl->ui8IntegrationL));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_ANALOG_GAIN,     &ui8Tmp,    sizeof(ui8Tmp));
    ui8Tmp = (ui8Tmp & ~(0x7 << 4)) | (psExpoGainCtrl->ui8AGain & (0x7 << 4));
    ui32Err = hm01b0_write_reg(psCfg, HM01B0_REG_ANALOG_GAIN,    &ui8Tmp,    sizeof(ui8Tmp));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_DIGITAL_GAIN_H,      &ui8Tmp,    sizeof(ui8Tmp));
    ui8Tmp = (ui8Tmp & ~(0x3 << 0)) | (psExpoGainCtrl->ui8DGain_H & (0x3 << 0));
    ui32Err = hm01b0_write_reg(psCfg, HM01B0_REG_DIGITAL_GAIN_H,     &ui8Tmp,    sizeof(ui8Tmp));
    if (ui32Err != HM01B0_ERR_OK) return ui32Err;

    ui32Err = hm01b0_read_reg(psCfg, HM01B0_REG_DIGITAL_GAIN_L,      &ui8Tmp,    sizeof(ui8Tmp));
    ui8Tmp = (ui8Tmp & ~(0x3F << 2)) | (psExpoGainCtrl->ui8DGain_L & (0x3F << 2));
    ui32Err = hm01b0_write_reg(psCfg, HM01B0_REG_DIGITAL_GAIN_L,     &ui8Tmp,    sizeof(ui8Tmp));

    return ui32Err;

}

//*****************************************************************************
//
//! @brief Hardware trigger HM01B0 to stream.
//!
//! @param psCfg        - Pointer to HM01B0 configuration structure.
//! @param bTrigger     - True to start streaming
//!                     - False to stop streaming
//!
//! This function triggers HM01B0 to stream by toggling the TRIG pin.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_hardware_trigger_streaming(hm01b0_cfg_t *psCfg, bool bTrigger)
{
    uint32_t ui32Err = HM01B0_ERR_OK;
    uint8_t ui8Mode;

    ui32Err = hm01b0_get_mode(psCfg, &ui8Mode);

    if (ui32Err != HM01B0_ERR_OK)
        goto end;

    if (ui8Mode != HM01B0_REG_MODE_SELECT_STREAMING_HW_TRIGGER)
    {
        ui32Err = HM01B0_ERR_MODE;
        goto end;
    }

    if (bTrigger)
    {
        am_hal_gpio_output_set(psCfg->ui8PinTrig);
    }
    else
    {
        am_hal_gpio_output_clear(psCfg->ui8PinTrig);
    }

end:
    return ui32Err;
}

//*****************************************************************************
//
//! @brief Set HM01B0 mirror mode.
//!
//! @param psCfg        - Pointer to HM01B0 configuration structure.
//! @param bHmirror     - Horizontal mirror
//! @param bVmirror     - Vertical mirror
//!
//! This function set HM01B0 mirror mode.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_set_mirror(hm01b0_cfg_t *psCfg, bool bHmirror, bool bVmirror)
{
    uint8_t ui8Data = 0x00;
    uint32_t ui32Err = HM01B0_ERR_OK;

    if (bHmirror)
    {
        ui8Data |= HM01B0_REG_IMAGE_ORIENTATION_HMIRROR;
    }

    if (bVmirror)
    {
        ui8Data |= HM01B0_REG_IMAGE_ORIENTATION_VMIRROR;
    }

    ui32Err = hm01b0_write_reg(psCfg, HM01B0_REG_IMAGE_ORIENTATION, &ui8Data, sizeof(ui8Data));

    if (ui32Err == HM01B0_ERR_OK)
    {
        ui8Data = HM01B0_REG_GRP_PARAM_HOLD_HOLD;
        ui32Err = hm01b0_write_reg(psCfg, HM01B0_REG_GRP_PARAM_HOLD, &ui8Data, sizeof(ui8Data));
    }

    return ui32Err;

}

//*****************************************************************************
//
//! @brief Read data of one frame from HM01B0.
//!
//! @param psCfg            - Pointer to HM01B0 configuration structure.
//! @param pui8Buffer       - Pointer to the frame buffer.
//! @param ui32BufferLen    - Framebuffer size.
//!
//! This function read data of one frame from HM01B0.
//!
//! @return Error code.
//
//*****************************************************************************
uint32_t hm01b0_blocking_read_oneframe(hm01b0_cfg_t *psCfg, uint8_t *pui8Buffer, uint32_t ui32BufferLen)
{
    uint32_t    ui32Err         = HM01B0_ERR_OK;
    uint32_t    ui32Idx         = 0x00;

    am_util_stdio_printf("[%s] +\n", __func__);

    uint32_t    ui32HsyncCnt    = 0x00;

    while((ui32HsyncCnt < HM01B0_PIXEL_Y_NUM))
    {
        while (0x00 == read_hsync());

        // read one row
        while(read_hsync())
        {
            while(0x00 == read_pclk());

            *(pui8Buffer + ui32Idx++) = read_byte();

            if (ui32Idx == ui32BufferLen) {
                goto end;
            }

            while(read_pclk());
        }

        ui32HsyncCnt++;
    }

end:
    am_util_stdio_printf("[%s] - Byte Counts %d\n", __func__, ui32Idx);

    return ui32Err;

}
