/*
 * Copyright (c) 2016-2020, NVIDIA CORPORATION.  All rights reserved. All
 * information contained herein is proprietary and confidential to NVIDIA
 * Corporation.  Any use, reproduction, or disclosure without the written
 * permission of NVIDIA Corporation is prohibited.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>

#include "log_utils.h"
#include "nvmedia_isc.h"
#include "isc_ar0231_rccb.h"
#include "isc_ar0231_rccb_regmap.h"
#include "isc_ar0231_rccb_setting.h"
#include "os_common.h"

#define AR0231_V7             7u
#define AR0231_V7_COLOR_FMT   3u
#define REG_ADDRESS_BYTES     2u
#define REG_DATA_BYTES        2u
#define REG_WRITE_BUFFER      256u
#define AR0231_CHIP_ID        0x0354u
#define REG_CHIP_VER          0x31FE
#define ENABLE_TEMP_SENSOR    1u
#define TEMP_CALIB_1          125.0f
#define TEMP_CALIB_2          55.0f
#define REG_DATA_FORMAT       0x31AC
#define REG_EMB_CTL           0x3064
#define REG_SYNC_MODE         0x30CE
#define FPS_30                30u
#define HIGH_CGAIN            3.0f     /* cgain bit 0->1.0, 1->3.0 */
#define MIN_AGAIN_LCG         0.8f     /* sensor vendor recommanded @ lcg */
#define MIN_AGAIN_HCG         1.0f     /* sensor vendor recommanded @ hcg */
#define MIN_DGAIN             1.0f
#define MAX_DGAIN_VAL         0x7FFu   /* =3 + 511 / 512 */
#define ONE_DGAIN_VAL         0x200u   /* =1.0; format2.9 XX.YYYYYYYYY */
#define ONE_COLOR_DGAIN_VAL   0x80u    /* =1.0; format4.7 XXXX.YYYYYYY */
#define MIN_AGAIN_LCG_IDX     6u
#define MIN_AGAIN_HCG_IDX     7u
#define MIN_WBGAIN            (1.0f)
#define MAX_WBGAIN            (8.0f)
#define SIZE_FUSE_ID          16u

/* exposure time limitation from sensor vendor */
#define MAX_AE_EXP_LINES_T2   62u
#define MAX_AE_EXP_LINES_T3   5u
#define MAX_GAIN              (HIGH_CGAIN * 8.0 * (3.0 + 511.0 / 512.0))
#define EXPOSURE_BYPASS(exp)  ((uint8_t)0x03u + (uint32_t)(1u << exp))
/* Bypass T1: 0x04u, T2: 0x05u, T3: 0x06u */

#define EXPOSURE_PRINT(args...)
/* #define EXPOSURE_PRINT(args...) printf(args) */

#define PARSE_PRINT(args...)
/* #define PARSE_PRINT(args...) printf(args) */

#define NUM_A_GAIN            (sizeof(aGainTbl) / sizeof(aGainTbl[0]))
#define GET_BLOCK_LENGTH(x)   x[0]
#define GET_BLOCK_DATA(x)     &x[1]
#define SET_NEXT_BLOCK(x)     (x += (x[0] + 1u))

static const float_t aGainTbl[] = {
    0.12500f, /* 0 - 1/8x */
    0.25000f, /* 1 - 2/8x */
    0.28571f, /* 2 - 2/7x */
    0.42857f, /* 3 - 3/7x */
    0.50000f, /* 4 - 3/6x */
    0.66667f, /* 5 - 4/6x */
    0.80000f, /* 6 - 4/5x - min aGain in lcg */
    1.00000f, /* 7 - 5/5x - min aGain in hcg */
    1.25000f, /* 8 - 5/4x */
    1.50000f, /* 9 - 6/4x */
    2.00000f, /* 10 - 6/3x */
    2.33333f, /* 11 - 7/3x */
    3.50000f, /* 12 - 7/2x */
    4.00000f, /* 13 - 8/2x */
    8.00000f  /* 14 - 8/1x */
};

typedef enum {
    CONTEXT_0 = 0,
    CONTEXT_1,
    CONTEXT_2,
    CONTEXT_3,
    CONTEXT_A,
    CONTEXT_B,
} ContextIndex;

static uint32_t colorIndex[4] = {
    1u, /* Gr - index 1; index order: R, Gr, Gb, B */
    3u, /* B  - index 3 */
    0u, /* R  - index 0 */
    2u  /* Gb - index 2 */
};

typedef struct {
    const uint8_t *default_setting;
    const uint8_t *mipi_setting;
    const TimingSettingAR0231 *timingSettings;
    ConfigInfoAR0231 config_info;
    uint32_t oscMHz;
    float_t maxGain;
    NvMediaISCModuleConfig moduleCfg;
    uint8_t loadedContext;           /* 0~3:ram based context, 4:context A, 5:context B */
    uint32_t patternMode; /* Test Pattern Mode
                             1: Default Solid Color (NV-Green: RGB=0x76b900)
                             2: Vertical Color Bars
                             3: Fade-to-Gray Vertical Color Bars
                             256: Walking 1s
                             > 256: Customized Solid Color (RGB=patternMode[31:8]) */
    uint32_t configSetIdx;           /* 0 - internal sync, 1 - external sync */
    uint32_t embDataType;
    ISCRegSetAR0231 regsel_table[SEL_EMB_LENGTH_AR0231];
    NvMediaBool charModeEnabled;
    uint8_t charModeExpNo;
    uint8_t numActiveExposures;
    uint8_t numFrameReportBytes;
    uint8_t fuseId[SIZE_FUSE_ID];
} _DriverHandle;

static NvMediaStatus
GetDeviceDriverName(char *name);

static NvMediaStatus
DriverCreate(
    NvMediaISCDevice *handle,
    void *clientContext)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    ContextAR0231 *ctx = NULL;
    uint32_t lenCfgName;
    ConfigResolutionAR0231 resSel = ISC_CONFIG_AR0231_1920X1208_30FPS;
    _DriverHandle *drvHandle = NULL;

    if (handle == NULL) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    drvHandle = calloc(1, sizeof(_DriverHandle));
    if (drvHandle == NULL) {
        LOG_ERR("[%s:%d] Out of Memory\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_OUT_OF_MEMORY;
        goto done;
    }

    drvHandle->default_setting = ar0231_raw12_default_v7;
    drvHandle->mipi_setting = ar0231_mipi;
    drvHandle->config_info.maxGain = MAX_GAIN;
    drvHandle->loadedContext = CONTEXT_A;
    drvHandle->config_info.frameRate = FPS_30;
    drvHandle->config_info.sensorVersion = 0u;
    drvHandle->patternMode = 0u;

    handle->deviceDriverHandle = (void *)drvHandle;
    (void)memcpy(drvHandle->regsel_table, regsel_AR0231,
        sizeof(ISCRegSetAR0231)*SEL_EMB_LENGTH_AR0231);

    if (clientContext != NULL) {  /* ok to be NULL, then use default values */
        ctx = clientContext;
        drvHandle->patternMode = ctx->patternMode;
        drvHandle->config_info.maxGain = ctx->maxGain;
        drvHandle->configSetIdx = ctx->configSetIdx;
        drvHandle->oscMHz = ctx->oscMHz;
        drvHandle->mipi_setting = (ctx->oscMHz == 27u) ? ar0231_mipi : ar0231_mipi_25mhz;
        drvHandle->embDataType = ctx->embDataType;
        lenCfgName = strlen(ctx->moduleConfig.cameraModuleCfgName);
        if (sizeof(drvHandle->moduleCfg.cameraModuleCfgName) > lenCfgName) {
            strncpy(drvHandle->moduleCfg.cameraModuleCfgName,
                    ctx->moduleConfig.cameraModuleCfgName,
                    lenCfgName);
        } else {
            LOG_ERR("[%s:%d] Out of Memory\n", __func__, __LINE__);
            status = NVMEDIA_STATUS_OUT_OF_MEMORY;
            goto done;
        }

        drvHandle->moduleCfg.cameraModuleConfigPass1 = ctx->moduleConfig.cameraModuleConfigPass1;
        drvHandle->moduleCfg.cameraModuleConfigPass2 = ctx->moduleConfig.cameraModuleConfigPass2;
        if (ctx->frameRate != 0u) {
            drvHandle->config_info.frameRate = ctx->frameRate;
            resSel = ISC_CONFIG_AR0231_1920X1208_30FPS;
        }
    }
    // set real default pattern mode for current sensor
    if (drvHandle->patternMode == 0u) {
        drvHandle->patternMode = 2;
    }

    drvHandle->config_info.resolution = resSel;
    drvHandle->timingSettings = &ar0231_timing[ctx->configSetIdx][resSel];
    drvHandle->charModeEnabled = NVMEDIA_FALSE;
    drvHandle->charModeExpNo = NVMEDIA_ISC_MAX_EXPOSURES;
    drvHandle->numActiveExposures = AR0231_NUM_HDR_EXP;
    drvHandle->numFrameReportBytes = 1u;

done:
    return status;
}

static NvMediaStatus
DriverDestroy(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if (handle == NULL) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    if (handle->deviceDriverHandle != NULL) {
        free(handle->deviceDriverHandle);
        handle->deviceDriverHandle = NULL;
    }

done:
    return status;
}

static NvMediaStatus
GetResolution(
    uint32_t enumeratedDeviceConfig,
    uint32_t frameRate,
    ConfigResolutionAR0231 *resolution)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    switch (enumeratedDeviceConfig) {
        case ISC_CONFIG_AR0231_DVP_RAW12_COMP_1920X1208:
            if (frameRate == FPS_30)
                *resolution = ISC_CONFIG_AR0231_1920X1208_30FPS;
            break;
        default:
            LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
            status = NVMEDIA_STATUS_BAD_PARAMETER;
            break;
    }

    return status;
}

static NvMediaStatus
CalculateExposureLineRate(
    float_t *lineRate,
    uint32_t hts)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if (lineRate == NULL) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    *lineRate = (float_t)(AR0231_PCLK) / (float_t)hts;
    LOG_DBG("%s: lineRate %f\n", __func__, *lineRate);

done:
    return status;
}

static NvMediaStatus
WriteArrayWithCommand(
    NvMediaISCDevice *handle,
    const uint8_t *arrayData)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if ((handle == NULL) || (arrayData == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }


    while (arrayData[0] != (uint8_t)'e') { /* 'e' - end of line, 'd' - delay, 'w' - write  */
        switch (arrayData[0]) {
            case 'w':
                status = NvMediaISCDeviceWrite(handle, 0u,
                                               arrayData[1],
                                               (uint8_t*)&arrayData[2]);
                if (status == NVMEDIA_STATUS_OK) {
                    arrayData += (arrayData[1] + 2u);
                }
                break;
            case 'd':
                if (nvsleep((arrayData[1] << 8) + arrayData[2]) != 0) {
                    LOG_DBG("%s: nvsleep() failed\n", __func__);
                    status = NVMEDIA_STATUS_ERROR;
                    break;
                }
                arrayData += 3u;
                break;
            default:
                break;
        }

        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Array write failed\n", __func__, __LINE__);
            status = NVMEDIA_STATUS_ERROR;
            goto done;
        }
    }

done:
    return status;
}

NvMediaStatus
AR0231RCCBSetDefaults(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t ctlData = 0u;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    status = WriteArrayWithCommand(handle,
                                   drvHandle->default_setting);

    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] Acessing default setting failed\n", __func__, __LINE__);
        goto done;
    }

    if (ENABLE_TEMP_SENSOR == 1u) {
        ctlData = 0x07; /* bit[2:0]: enable temp sensor */
        status = AR0231RCCBWriteRegister(handle, 0u,
                                         REG_TEMPSENS0_CTL + 1u, 1u, &ctlData);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Temperature0 Control register write failed\n", __func__, __LINE__);
            goto done;
        }

        status = AR0231RCCBWriteRegister(handle, 0u,
                               REG_TEMPSENS1_CTL + 1u, 1u, &ctlData);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Temperature1 Control register write failed\n", __func__, __LINE__);
            goto done;
        }
    }

done:
    return status;
}

static NvMediaStatus
WriteArray(
    NvMediaISCDevice *handle,
    uint32_t arrayByteLength,
    const uint8_t *arrayData)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if ((handle == NULL) || (arrayData == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    while (arrayByteLength != 0u) {
        status = NvMediaISCDeviceWrite(handle, 0u,
                                       GET_BLOCK_LENGTH(arrayData),            /* dataLength */
                                       (uint8_t*) GET_BLOCK_DATA(arrayData));  /* data */
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Write failed\n", __func__, __LINE__);
            goto done;
        }

        arrayByteLength -= GET_BLOCK_LENGTH(arrayData) + 1u;
        SET_NEXT_BLOCK(arrayData);
    }

done:
    return status;
}

NvMediaStatus
AR0231RCCBSetDeviceConfig(
        NvMediaISCDevice *handle,
        uint32_t enumeratedDeviceConfig)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint32_t patternMode = 0u;
    uint8_t red, green, blue;
    uint8_t ctlData = 0u;
    uint8_t frameData[2] = {0u};
    ConfigResolutionAR0231 resolution = ISC_CONFIG_AR0231_1920X1208_30FPS;
    unsigned char ar0231_enable_pg[] = {
        4, 0x30, 0x64, 0x18, 0x82, // SMIA_TEST, disable TOP/BOTTOM EMB
        4, 0x31, 0xAC, 0x0C, 0x0C, // DATA_FORMAT_BITS
        4, 0x31, 0x10, 0x07, 0x11, // OUTPUT T4 only
        4, 0x30, 0x72, 0x76, 0x76, // TEST_DATA_RED
        4, 0x30, 0x74, 0xb9, 0xb9, // TEST_DATA_GREENR
        4, 0x30, 0x76, 0x00, 0x00, // TEST_DATA_BLUE
        4, 0x30, 0x78, 0xb9, 0xb9, // TEST_DATA_GREENB
        4, 0x30, 0x70, 0x00, 0x02, // TEST_PATTERN_MODE
    };
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    switch (enumeratedDeviceConfig) {
        case ISC_CONFIG_AR0231_DVP_RAW12_COMP_1920X1208:
            status = GetResolution(enumeratedDeviceConfig,
                                   drvHandle->config_info.frameRate,
                                   &resolution);

            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("[%s:%d] Accessing resolution failed\n", __func__, __LINE__);
                break;
            }

            drvHandle->config_info.enumeratedDeviceConfig =
                     enumeratedDeviceConfig;
            drvHandle->config_info.resolution = resolution;

            status = CalculateExposureLineRate(
                        &drvHandle->config_info.exposureLineRate,
                        drvHandle->timingSettings->hts);

            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("[%s:%d] Accessing timing settings failed\n", __func__, __LINE__);
                break;
            }

            status = WriteArrayWithCommand(handle,
                                           drvHandle->timingSettings->settings);

            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("[%s:%d] Writing timing settings failed\n", __func__, __LINE__);
                break;
            }

            break;
        case ISC_CONFIG_AR0231_RESET_FRAME_ID:
            status = AR0231RCCBWriteRegister(handle, 0u,
                                             REG_FRAME_REPORT,
                                             2u,
                                             frameData);
             break;
        case ISC_CONFIG_AR0231_ENABLE_BOTTOM_EMB:
            ctlData = 0x82;                          /* bit 7: enable bottom emb stats */
            status = AR0231RCCBWriteRegister(handle, 0u,
                                             REG_EMB_CTL + 1u,                     /* low byte */
                                             1u,
                                             &ctlData);
            break;
        case ISC_CONFIG_AR0231_ENABLE_STREAMING:
            status = WriteArray(handle,
                                sizeof(ar0231_enable_streaming[0]),
                                ar0231_enable_streaming[0]);
            nvsleep(10000);                          /* wait 10ms */
            break;
        case ISC_CONFIG_AR0231_ENABLE_MIPI_STREAMING:
            status = WriteArray(handle,
                                sizeof(ar0231_enable_streaming[1]),
                                ar0231_enable_streaming[1]);
            break;
        case ISC_CONFIG_AR0231_ENABLE_MIPI_IF:
            status = WriteArrayWithCommand(handle,
                                           drvHandle->mipi_setting);
            break;
        case ISC_CONFIG_AR0231_ENABLE_PG:
            patternMode = drvHandle->patternMode;
            if (patternMode > 256) {
                red = patternMode >> 24;
                green = (patternMode >> 16) & 0xff;
                blue = (patternMode >> 8) & 0xff;
                patternMode = 1;
                ar0231_enable_pg[18] = red;
                ar0231_enable_pg[19] = red;
                ar0231_enable_pg[23] = green;
                ar0231_enable_pg[24] = green;
                ar0231_enable_pg[28] = blue;
                ar0231_enable_pg[29] = blue;
                ar0231_enable_pg[33] = green;
                ar0231_enable_pg[34] = green;
            }
            ar0231_enable_pg[39] = patternMode;
            status = WriteArray(handle,
                                sizeof(ar0231_enable_pg),
                                ar0231_enable_pg);
            break;
        default:
            LOG_ERR("[%s:%d] Unsupported status\n", __func__, __LINE__);
            status = NVMEDIA_STATUS_NOT_SUPPORTED;
            break;
    }

done:
    return status;
}

static NvMediaStatus
SetConfigInfo(
        NvMediaISCDevice *handle,
        ConfigInfoAR0231 *configInfo)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)
        || (configInfo == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }
    /* copy configInfo to current handle */
    (void)memcpy(&drvHandle->config_info, configInfo, sizeof(ConfigInfoAR0231));

done:
    return status;
}

static NvMediaStatus
GetConfigInfo(
        NvMediaISCDevice *handle,
        ConfigInfoAR0231 *configInfo)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)
        || (configInfo == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }
    /* copy config info of current handle to input param */
    (void)memcpy(configInfo, &drvHandle->config_info, sizeof(ConfigInfoAR0231));

done:
    return status;
}

NvMediaStatus
AR0231RCCBWriteParameters(
        NvMediaISCDevice *handle,
        uint32_t parameterType,
        uint32_t parameterSize,
        void *parameter)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    WriteReadParametersParamAR0231 *param;

    if ((parameter == NULL) || (handle == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    param = (WriteReadParametersParamAR0231 *)parameter;

    switch (parameterType) {
        case ISC_WRITE_PARAM_CMD_AR0231_CONFIG_INFO:
            if (parameterSize != sizeof(param->configInfo)) {
                LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
                status = NVMEDIA_STATUS_BAD_PARAMETER;
                break;
            }
            status = SetConfigInfo(handle,
                                   param->configInfo);
            break;
        default:
            LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
            status = NVMEDIA_STATUS_BAD_PARAMETER;
            break;
    }

done:
    return status;
}

NvMediaStatus
AR0231RCCBReadParameters(
        NvMediaISCDevice *handle,
        uint32_t parameterType,
        uint32_t parameterSize,
        void *parameter)
{
    WriteReadParametersParamAR0231 *param;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    _DriverHandle *drvHandle = NULL;

    if ((parameter == NULL) || (handle == NULL) ||
        ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    param = (WriteReadParametersParamAR0231 *)parameter;
    if (parameterSize != sizeof(param->configInfo)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    switch (parameterType) {
        case ISC_READ_PARAM_CMD_AR0231_CONFIG_INFO:
            status = GetConfigInfo(handle,
                                   param->configInfo);
            break;
        case ISC_READ_PARAM_CMD_AR0231_EXP_LINE_RATE:
            status = CalculateExposureLineRate(
                        &param->configInfo->exposureLineRate,
                        drvHandle->timingSettings->hts);
            break;
        case ISC_READ_PARAM_CMD_AR0231_FUSE_ID:
            status = AR0231RCCBReadRegister(handle,
                                            0u,
                                            REG_FUSE_ID,
                                            SIZE_FUSE_ID,
                                            drvHandle->fuseId);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("[%s:%d] Get Fuse ID failed\n", __func__, __LINE__);
            }
            break;
        default:
            LOG_ERR("[%s:%d] Bad parameter type\n", __func__, __LINE__);
            status = NVMEDIA_STATUS_NOT_SUPPORTED;
            break;
    }

done:
    return status;
}

NvMediaStatus
AR0231RCCBReadRegister(
    NvMediaISCDevice *handle,
    uint32_t deviceIndex,
    uint32_t registerNum,
    uint32_t dataLength,
    uint8_t *dataBuff)
{
    uint8_t registerData[REG_ADDRESS_BYTES];
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if ((handle == NULL) || (dataBuff == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }


    registerData[0] = registerNum >> 8;
    registerData[1] = (registerNum & 0xFFu);
    status = NvMediaISCDeviceRead(handle,
                         deviceIndex,
                         REG_ADDRESS_BYTES, /* regLength */
                         registerData,      /* regData */
                         dataLength,        /* dataLength */
                         dataBuff);         /* data */

    if (status != NVMEDIA_STATUS_OK) {
        LOG_DBG("%s: sensor read failed: 0x%x, length %d\n", __func__, registerNum, dataLength);
    }

done:
    return status;
}

NvMediaStatus
AR0231RCCBWriteRegister(
    NvMediaISCDevice *handle,
    uint32_t deviceIndex,
    uint32_t registerNum,
    uint32_t dataLength,
    uint8_t *dataBuff)
{
    uint8_t data[REG_ADDRESS_BYTES + REG_WRITE_BUFFER];
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if ((handle == NULL) || (dataBuff == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    data[0] = registerNum >> 8;
    data[1] = registerNum & 0xFFu;
    (void)memcpy(&data[2], dataBuff, dataLength);

    status = NvMediaISCDeviceWrite(handle,
                                   deviceIndex,
                                   dataLength + (uint32_t)REG_ADDRESS_BYTES,    /* dataLength */
                                   data);                                       /* data */
    if (status != NVMEDIA_STATUS_OK) {
        LOG_DBG("%s: sensor write failed: 0x%x, length %d\n", __func__, registerNum, dataLength);
    }

done:
    return status;
}

NvMediaStatus
AR0231RCCBCheckPresence(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t readBuff[2] = {0u};
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    status = AR0231RCCBReadRegister(handle, 0u,
                                    REG_CHIP_ID,
                                    REG_DATA_BYTES,
                                    readBuff);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] Chip Id register read failed\n", __func__, __LINE__);
        goto done;
    }

    if ((readBuff[0] != ((AR0231_CHIP_ID >> 8) & 0xffu)) ||
        (readBuff[1] != (AR0231_CHIP_ID & 0xffu))) {
        LOG_ERR("[%s:%d] Chip Id mismatch!\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    status = AR0231RCCBReadRegister(handle, 0u,
                                    REG_CHIP_VER + 1u,  /* low byte */
                                    1u,
                                    readBuff);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] GetRevId failed\n", __func__, __LINE__);
        goto done;
    }

    if ((readBuff[0] & 0x0Fu) == AR0231_V7) { /* sensor version, [3:0] */
        drvHandle->config_info.sensorVersion = AR0231_V7;
        if (((readBuff[0] >> 4) & 0x0Fu) != AR0231_V7_COLOR_FMT) {
            /* 1 - RGGB; 2 - MONO; 3 - RCCB; 6 - RCCC */
            LOG_WARN("This is AR0231 Rev7, but not RCCB sensor!\n");
        } else {
            LOG_MSG("Sensor AR0231 RCCB Rev7 detected!\n"); /* To be fixed, LOG */
        }
    } else {
        LOG_ERR("RCCB mode for this sensor version is not supported\n");
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

done:
    return status;
}

NvMediaStatus
AR0231RCCBDumpRegisters(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t data[REG_DATA_BYTES] = {0u, 0u};
    uint8_t addr[REG_ADDRESS_BYTES] = {0u, 0u};
    uint8_t *arrayData;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    arrayData = (uint8_t*)drvHandle->default_setting;

    while (arrayData[0] != (uint8_t)'e') {
        if (arrayData[0] == (uint8_t)'w') {
            addr[0] = arrayData[2];
            addr[1] = arrayData[3];
            status = NvMediaISCDeviceRead(handle, 0u,
                                          REG_ADDRESS_BYTES,     /* regLength */
                                          addr,                  /* regData */
                                          REG_DATA_BYTES,        /* dataLength */
                                          data);                 /* dataBuff */
            LOG_DBG("20 %.2X%.2X %.2x %.2x\n", (uint32_t)arrayData[2],
                (uint32_t)arrayData[3], data[0], data[1]);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("[%s:%d] Function Read failed\n", __func__, __LINE__);
                goto done;
            }
            arrayData += (arrayData[1] + 2u);
        } else {
            arrayData += 3u;  /* for 'd' */
        }
    }

done:
    return status;
}

static NvMediaStatus
GetGainValues(
    NvMediaISCDevice *handle,
    const NvMediaISCExposure *exposureControl,
    uint32_t *cGainRegVal,
    uint32_t *aGainRegVal,
    uint32_t *dGainRegVal)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint32_t i, minAgVal = 0u;
    float_t tmp, cGainT1, aGainT1, dGainT1;
    *cGainRegVal = 0u;
    *aGainRegVal = 0u;
    *dGainRegVal = 0u;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        return NVMEDIA_STATUS_BAD_PARAMETER;
    }

    /* Calculate Cgain for T0 and set register value */
    if (exposureControl->sensorGain[0] >= (HIGH_CGAIN * MIN_AGAIN_HCG * MIN_DGAIN)) {
        cGainT1 = HIGH_CGAIN;
        minAgVal = MIN_AGAIN_HCG_IDX;            /* Table Index for Analog Gains */
        *cGainRegVal |= 1u << 0;
    } else {
        cGainT1 = 1.0f;
        minAgVal = MIN_AGAIN_LCG_IDX;            /* Table Index for Analog Gains */
    }

    /* Calculate Again for T0 and set register values */
    tmp = exposureControl->sensorGain[0]/cGainT1;
    *aGainRegVal = minAgVal;
    for (i = NUM_A_GAIN - 1u; i >= minAgVal; i--) {
        if (tmp >= aGainTbl[i]) {
            *aGainRegVal = i;
            break;
        }
    }
    aGainT1 = aGainTbl[*aGainRegVal];

    /* aGain is the same for all exp channels */
    *aGainRegVal |= (*aGainRegVal << 4) | (*aGainRegVal << 8) | (*aGainRegVal << 12);

    /* Calculate Dgain for T0 and set register values */
    tmp = exposureControl->sensorGain[0]/(cGainT1*aGainT1);

    /* dGain is the same for all exp channels */
    *dGainRegVal = (uint32_t)((tmp * (float_t)ONE_DGAIN_VAL) + 0.5f);
    if (*dGainRegVal > (uint32_t)MAX_DGAIN_VAL) {
        *dGainRegVal = MAX_DGAIN_VAL;
    }

    if (*dGainRegVal < (uint32_t)ONE_DGAIN_VAL) {
        *dGainRegVal = ONE_DGAIN_VAL;
    }
    dGainT1 = ((float_t) (*dGainRegVal)) / ((float_t) ONE_DGAIN_VAL);

    /* Calculate Cgain for T1 and T2 and set register value */
    for (i = 1u; i < drvHandle->numActiveExposures; i++) {
        tmp = exposureControl->sensorGain[i] / (dGainT1*aGainT1);
        if (tmp >= (HIGH_CGAIN * 0.5f + 0.5f)) {
            *cGainRegVal |= 1u << i;
        }
    }

    EXPOSURE_PRINT("Float Vals T1:: cgain=%f, again=%f, dgain=%f\n", cGainT1, aGainT1, dGainT1);

    return status;
}

static NvMediaStatus
ReCalculate3ExpTime(
    NvMediaISCDevice *handle,
    const NvMediaISCExposure *exposureControl,
    uint32_t *exposureLines)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint32_t hts;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL) ||
        (exposureControl == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    hts = drvHandle->timingSettings->hts;
    /* re-calculate expTime for 3-exp
     * e3 = (cit3 - 1) * llpck + fit3
     * e2 = (cit2 + cit3 - 2) * llpck + fit2 + fit3
     * e1 = (cit1 + cit2 + cit3 - 3) * llpck + fit1 + fit2 + fit3
     */
    exposureLines[2] = (uint32_t)(exposureControl->exposureTime[2] *
                       drvHandle->config_info.exposureLineRate + 1.0f -
                       (float_t)AR0231_FINE_INTEG_TIME_T3 / (float_t)hts + 0.5f);
    if (exposureLines[2] < 1u) {
         exposureLines[2] = 1u;
    } else if (exposureLines[2] > MAX_AE_EXP_LINES_T3) {
         exposureLines[2] = MAX_AE_EXP_LINES_T3;
    }

    exposureLines[1] = (uint32_t)(exposureControl->exposureTime[1] *
                       drvHandle->config_info.exposureLineRate -
                       (float_t)exposureLines[2] + 2.0f - (float_t)(AR0231_FINE_INTEG_TIME_T2 +
                       AR0231_FINE_INTEG_TIME_T3) / (float_t)hts + 0.5f);
    if (exposureLines[1] < 1u) {
         exposureLines[1] = 1u;
    } else if (exposureLines[1] > MAX_AE_EXP_LINES_T2) {
         exposureLines[1] = MAX_AE_EXP_LINES_T2;
    }

    exposureLines[0] = (uint32_t)(exposureControl->exposureTime[0]*
                       drvHandle->config_info.exposureLineRate -
                       (float_t)exposureLines[1] - (float_t)exposureLines[2] + 3.0f
                       - (float_t)(AR0231_FINE_INTEG_TIME_T1 + AR0231_FINE_INTEG_TIME_T2
                       + AR0231_FINE_INTEG_TIME_T3) / (float_t)hts + 0.5f);
    if (exposureLines[0] < 1u) {
        exposureLines[0] = 1u;
    }

    EXPOSURE_PRINT("exposureLines[0]=%d, exposureLines[1]=%d, exposureLines[2]=%d\n",
                   exposureLines[0], exposureLines[1], exposureLines[2]);
    EXPOSURE_PRINT("%s: ExpT1: %f Lines: %d; ExpT2: %f Lines: %d; ExpT3: %f Lines: %d\n",
                   __func__, exposureControl->exposureTime[0],
                   (uint32_t)(exposureLines[0] + exposureLines[1] + exposureLines[2] - 3 +
                   (float_t)((AR0231_FINE_INTEG_TIME_T1 + AR0231_FINE_INTEG_TIME_T2 +
                    AR0231_FINE_INTEG_TIME_T3) / hts), exposureControl->exposureTime[1]),
                   (uint32_t)(exposureLines[1] + exposureLines[2] - 2 +
                   (float_t)(AR0231_FINE_INTEG_TIME_T2 + AR0231_FINE_INTEG_TIME_T3) / hts),
                   exposureControl->exposureTime[2],
                   (uint32_t)(AR0231_FINE_INTEG_TIME_T3 - 1 + (float_t)AR0231_FINE_INTEG_TIME_T3 /
                   hts));

done:
    return status;
}

static NvMediaStatus
WriteExpRegister(
    NvMediaISCDevice *handle,
    uint32_t exposureLines,
    uint32_t whichExp)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t regVal[2];
    uint32_t regAdd;
    uint32_t dataLength;

    if (whichExp == ISC_AR0231_T1_EXPOSURE) { /* T1 exp has 2 bytes */
        regAdd = REG_INTEG_TIME_T1;
        regVal[0] = (exposureLines >> 8) & 0xFFu;
        regVal[1] = exposureLines & 0xFFu;
        dataLength = 2u;
    } else {                                 /* T2, T3 exp has 2 byte */
        regAdd = ((uint32_t)REG_INTEG_TIME_T2 + (whichExp - 1u) * 4u);
        regVal[0] = (exposureLines >> 8) & 0xFFu;
        regVal[1] = exposureLines & 0xFFu;
        dataLength = 2u;
    }

    status = AR0231RCCBWriteRegister(handle, 0u, regAdd, dataLength, regVal);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] Write Register failed\n", __func__, __LINE__);
    }

    return status;
}

static NvMediaStatus
SetExposure(
    NvMediaISCDevice *handle,
    const NvMediaISCExposure *exposureControl)
{
    _DriverHandle *drvHandle = NULL;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t regVal[2];
    uint32_t aGainVal = 0u, dGainVal = 0u, cGainVal = 0u;
    uint32_t exposureLines[AR0231_NUM_HDR_EXP] = {0u, 0u, 0u};
    NvMediaISCExposure expCtrlChar;
    const NvMediaISCExposure *exposureCtrlSettings;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL) ||
        (exposureControl == NULL)) {
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    if (drvHandle->charModeEnabled == NVMEDIA_TRUE) {
        memset(&expCtrlChar, 0u, sizeof(NvMediaISCExposure));
        expCtrlChar.expTimeValid = exposureControl->expTimeValid;
        expCtrlChar.gainValid = exposureControl->gainValid;
        switch (drvHandle->charModeExpNo) {
            default:
            case ISC_AR0231_T1_EXPOSURE:
                expCtrlChar.exposureTime[0] = exposureControl->exposureTime[0];
                expCtrlChar.exposureTime[1] = exposureControl->exposureTime[0] / 16.0f;
                expCtrlChar.exposureTime[2] = exposureControl->exposureTime[0] / (16.0f * 16.0f);
                break;
            case ISC_AR0231_T2_EXPOSURE:
                expCtrlChar.exposureTime[1] = exposureControl->exposureTime[0];
                expCtrlChar.exposureTime[0] = exposureControl->exposureTime[0] * 16.0f;
                expCtrlChar.exposureTime[2] = exposureControl->exposureTime[0] / 16.0f;
                break;
            case ISC_AR0231_T3_EXPOSURE:
                expCtrlChar.exposureTime[2] = exposureControl->exposureTime[0];
                expCtrlChar.exposureTime[1] = exposureControl->exposureTime[0] * 16.0f;
                expCtrlChar.exposureTime[0] = exposureControl->exposureTime[0] * 16.0f * 16.0f;
                break;
        }
        expCtrlChar.sensorGain[0] = exposureControl->sensorGain[0];
        expCtrlChar.sensorGain[1] = exposureControl->sensorGain[0];
        expCtrlChar.sensorGain[2] = exposureControl->sensorGain[0];
        exposureCtrlSettings = &expCtrlChar;
    } else {
        exposureCtrlSettings = exposureControl;
    }

    if (exposureCtrlSettings->expTimeValid) {
        status = ReCalculate3ExpTime(handle, exposureCtrlSettings, exposureLines);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Exposure time calculation failed\n", __func__, __LINE__);
            goto done;
        }

        /* Write T1 Exposure Register */
        status = WriteExpRegister(handle, exposureLines[0], 0u);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] T1 Exposure register write failed\n", __func__, __LINE__);
            goto done;
        }

        /* Write T2 Exposure Register */
        status = WriteExpRegister(handle, exposureLines[1], 1u);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] T2 Exposure register write failed\n", __func__, __LINE__);
            goto done;
        }

        /* Write T3 Exposure Register */
        status = WriteExpRegister(handle, exposureLines[2], 2u);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] T3 Exposure register write failed\n", __func__, __LINE__);
            goto done;
        }
    }

    if (exposureCtrlSettings->gainValid) {
        status = GetGainValues(handle, exposureCtrlSettings, &cGainVal, &aGainVal, &dGainVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] GetGainValues failed\n", __func__, __LINE__);
            goto done;
        }
        EXPOSURE_PRINT("Requested Settings: gainT1 %f, gainT2 %f, gainT3 %f\n",  \
                       exposureCtrlSettings->sensorGain[0], exposureCtrlSettings->sensorGain[1],\
                       exposureCtrlSettings->sensorGain[2]);
        EXPOSURE_PRINT("Final Reg Settings: cG %x, aG %x, dG %x\n", cGainVal, aGainVal, dGainVal);
        regVal[0] = (uint8_t)(cGainVal & 0x000F);
        status = AR0231RCCBWriteRegister(handle, 0u, REG_CGAIN + 1u, 1u, regVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Conversion Gain register write failed\n", __func__, __LINE__);
            goto done;
        }

        regVal[0] = (uint8_t)((aGainVal >> 8) & 0x00FF);
        regVal[1] = (uint8_t)(aGainVal & 0x00FF);
        status = AR0231RCCBWriteRegister(handle, 0u, REG_AGAIN, 2u, regVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Analog Gain register write failed\n", __func__, __LINE__);
            goto done;
        }

        regVal[0] = (uint8_t)((dGainVal >> 8) & 0x00FF);
        regVal[1] = (uint8_t)(dGainVal & 0x00FF);
        status = AR0231RCCBWriteRegister(handle, 0u, REG_DGAIN, 2u, regVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Digital Gain register write failed\n", __func__, __LINE__);
        }
    }
done:
    return status;
}

static NvMediaStatus
ParseExposure(
    _DriverHandle *drvHandle,
    NvMediaISCExposure *sensorExpInfo,
    float_t lineRate)
{
    uint32_t i;
    float_t expT[AR0231_NUM_HDR_EXP] = {0.0f,0.0f,0.0f};
    uint16_t fineIntegTime[3] = {0u,0u,0u};
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint32_t hts = 0u;
    uint16_t cgain, again, dgain;
    float_t et[AR0231_NUM_HDR_EXP], exposureTime[AR0231_NUM_HDR_EXP];
    float_t analogGain[AR0231_NUM_HDR_EXP], conversionGain[AR0231_NUM_HDR_EXP], digitalGain;

    if ((drvHandle == NULL) || (sensorExpInfo == NULL) || (lineRate == 0.0f)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    hts = drvHandle->timingSettings->hts;
    fineIntegTime[0] = drvHandle->regsel_table[REG_FINE_INTEGRATION_ACTUAL_T1_AR0231].data;
    fineIntegTime[1] = drvHandle->regsel_table[REG_FINE_INTEGRATION_ACTUAL_T2_AR0231].data;
    fineIntegTime[2] = drvHandle->regsel_table[REG_FINE_INTEGRATION_ACTUAL_T3_AR0231].data;
    again = drvHandle->regsel_table[REG_ANALOG_GAIN_AR0231].data;
    cgain = drvHandle->regsel_table[REG_DC_GAIN_AR0231].data;
    dgain = drvHandle->regsel_table[REG_DIGITAL_GAIN_AR0231].data;
    et[0] = drvHandle->regsel_table[REG_EXPOSURE_T1_ROW_AR0231].data;
    et[1] = drvHandle->regsel_table[REG_EXPOSURE_T2_ROW_AR0231].data;
    et[2] = drvHandle->regsel_table[REG_EXPOSURE_T3_ROW_AR0231].data;
    digitalGain = (float_t)(dgain) / (float_t)ONE_DGAIN_VAL;
    (void)memset(sensorExpInfo, 0u, sizeof(NvMediaISCExposure));

    for (i = 0u; i < AR0231_NUM_HDR_EXP; i++) {
        expT[i] = ((((float_t)(et[i])) - 1.0f +
                   ((float_t)fineIntegTime[i] / (float_t)hts)) / lineRate);
        analogGain[i] = aGainTbl[(again >> (4u * i)) & (uint32_t)0xF];
        conversionGain[i] = (((cgain >> i) & (uint32_t)0x01) == 0u) ? 1.0 : HIGH_CGAIN;
        LOG_DBG("CIT[%d] = %f\nFIT[%d] = %d\nanalogGain[%d] = %f\nconversionGain[%d] = %f\n",
                i, et[i], i, fineIntegTime[i], i, analogGain[i], i, conversionGain[i]);
    }

    exposureTime[0] = expT[0] + expT[1] + expT[2];
    exposureTime[1] = expT[1] + expT[2];
    exposureTime[2] = expT[2];

    for (i = 0u; i < AR0231_NUM_HDR_EXP; i++) {
        LOG_DBG("exposureTime[%d] = %f\n", i, exposureTime[i]);
    }

    sensorExpInfo->gainValid = NVMEDIA_TRUE;
    sensorExpInfo->expTimeValid = NVMEDIA_TRUE;
    if (drvHandle->charModeEnabled) {
        sensorExpInfo->sensorGain[0] = digitalGain * analogGain[drvHandle->charModeExpNo]
        * conversionGain[drvHandle->charModeExpNo];
        sensorExpInfo->exposureTime[0] = exposureTime[drvHandle->charModeExpNo];
        LOG_DBG("sensorGain[0] = %f\texposureTime[0] = %f\n", sensorExpInfo->sensorGain[0],
                                                              sensorExpInfo->exposureTime[0]);
    } else {
        for (i = 0u; i < drvHandle->numActiveExposures; i++) {
            sensorExpInfo->sensorGain[i] = digitalGain * analogGain[i] * conversionGain[i];
            sensorExpInfo->exposureTime[i] = exposureTime[i];
            LOG_DBG("sensorGain[%d] = %f\texposureTime[%d] = %f\n",
                    i, sensorExpInfo->sensorGain[i], i, sensorExpInfo->exposureTime[i]);
        }
    }

done:
    return status;
}

static NvMediaStatus
SetSensorWbGain(
    NvMediaISCDevice *handle,
    const NvMediaISCWhiteBalance *wbControl)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint16_t gain = 0u;
    uint8_t regVal[8] = {0u};
    uint8_t *dataAdd;
    uint32_t i, index, regAdd, dataLength;

    if ((handle == NULL) || (wbControl == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    /* global WB gains, using index 0 */
    if (wbControl->wbValid == NVMEDIA_TRUE) {
        for (i = 0u; i < NVM_ISC_MAX_COLOR_COMPONENT; i++) {  /* Gr, B, R, Gb */
            index = colorIndex[i];                           /* R, Gr, Gb, B */
            gain = (uint16_t) ((wbControl->wbGain[0].value[index] *
                    (float_t)ONE_COLOR_DGAIN_VAL) + 0.5f);
            if (gain != 0u) {
                regVal[i * 2u] = (gain >> 8) & (uint32_t)0xFF;
                regVal[(i * 2u) + 1u] = gain & (uint32_t)0xFF;
                LOG_DBG("%s: SetWBGain %d: %x\n", __func__, i, gain);
            }
        }

        regAdd = (uint32_t)REG_DGAIN_GR;
        dataLength = 8u;
        dataAdd = &regVal[0];
        status = AR0231RCCBWriteRegister(handle, 0u, regAdd, dataLength, dataAdd);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Failed to write group hold register\n", __func__, __LINE__);
            goto done;
        }
    } else { /* all wbGain[i].valid are FALSE */
        LOG_WARN("No valid flag set for WB gains.\n");
    }

done:
    return status;
}

static NvMediaStatus
ParseWBGain(
    _DriverHandle *drvHandle,
    NvMediaISCWhiteBalance *parsedWbInfo)
{
    uint32_t i, j, index;
    float_t value = 0u;
    uint16_t gain[NVM_ISC_MAX_COLOR_COMPONENT];
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if ((drvHandle == NULL) || (parsedWbInfo == NULL)){
        LOG_ERR("[%s:%d] Bad input parameter/s\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    gain[0] = drvHandle->regsel_table[REG_GREEN1_GAIN_AR0231].data;
    gain[1] = drvHandle->regsel_table[REG_BLUE_GAIN_AR0231].data;
    gain[2] = drvHandle->regsel_table[REG_RED_GAIN_AR0231].data;
    gain[3] = drvHandle->regsel_table[REG_GREEN2_GAIN_AR0231].data;

    (void)memset(parsedWbInfo, 0u, sizeof(NvMediaISCWhiteBalance));

    for (i = 0u; i < NVM_ISC_MAX_COLOR_COMPONENT; i++) {  /* Gr, B, R, Gb */
        index = colorIndex[i];
        value = (float_t)(gain[i]) / (float_t)ONE_COLOR_DGAIN_VAL;
        for (j = 0u; j < drvHandle->numActiveExposures; j++) {
            parsedWbInfo->wbGain[j].value[index] = value;
        }
    }

    parsedWbInfo->wbValid = NVMEDIA_TRUE;

done:
    return status;
}

/* Depack Function */
static NvMediaStatus
DepackEmbeddedLineParallelInterface (
    ISCRegSetAR0231* regset,
    uint32_t lineLength,
    const uint8_t *lineData,
    uint16_t selLength)
{
    uint16_t i, j;
    uint16_t addrl = 0u;
    uint16_t addrh = 0u;
    uint16_t datal = 0u;
    uint16_t datah = 0u;
    uint8_t dataReady = 1u;
    uint8_t dataFormatCode = 0u;
    uint8_t tag = 0u;
    uint8_t dat = 0u;
    uint16_t addr = 0u;
    uint16_t data;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint16_t flag_end_detected = 1u;
    const uint8_t* praw = lineData;

    if ((lineLength == 0u) || (lineData == NULL) || (selLength == 0u) || (regset == NULL)){
        LOG_ERR("[%s:%d] Bad input parameter/s\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    if (praw == NULL){
        LOG_ERR("[%s:%d] Pointer assignment fail\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    (praw++);/* skipping garbage */
    dataFormatCode = *(praw++);
    (praw++);/* skipping garbage */
    if (dataFormatCode != 0x0Au) {
         LOG_ERR("[%s:%d] Data format code mismatch\n", __func__, __LINE__);
         status = NVMEDIA_STATUS_ERROR;
         goto done;
    } else {
        flag_end_detected = 0u;
    }

    for (i = 1u; i < lineLength / 4u; i++) {
        if (flag_end_detected == 1u) {
            tag = *(praw++);
            if (tag == 0x0Au) {
                flag_end_detected = 0u;
                praw++;/* skipping garbage */
            }
        } else {
            tag = *(praw++);
            praw++; /* skipping garbage */
            dat = *(praw++);
            praw++;/* skipping garbage */
            if (tag == 0xAAu) {
                addrh = dat;
            } else if (tag == 0xA5u) {
                addrl = dat;
                addr = (addrh << 8) + addrl;
                dataReady = 0u;
            } else if (tag == 0x5Au) {
                if ((dataReady & 0x01) == 1u) {
                    datal = dat;
                    data = (datah << 8) + datal;
                    for (j = 0u; j < selLength; j++) {
                        if (regset[j].addr == addr) {
                            regset[j].data = data;
                            break;
                        }
                    }
                    addr = addr + 2;
                    dataReady = ~dataReady;
                } else {
                    datah = dat;
                    dataReady = ~dataReady;
                }
            } else if (tag == 0x07u) {
                flag_end_detected = 1u;
            }
        }
    }

done:
    return status;
}

static NvMediaStatus
DepackEmbeddedLineMipiInterface (
    ISCRegSetAR0231* regset,
    uint32_t lineLength,
    const uint8_t *lineData,
    uint16_t selLength)
{
    uint16_t i, j;
    uint16_t addrl = 0u;
    uint16_t addrh = 0u;
    uint16_t datal = 0u;
    uint16_t datah = 0u;
    uint8_t dataReady = 1u;
    uint8_t dataFormatCode = 0u;
    uint8_t tag = 0u;
    uint8_t dat = 0u;
    uint16_t addr = 0u;
    uint16_t data;
    uint16_t flag_end_detected = 1u;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    const uint8_t* praw = lineData;

    if ((lineLength == 0u) || (lineData == NULL) || (selLength == 0u) || (regset == NULL)){
        LOG_ERR("[%s:%d] Bad input parameter/s\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    if (praw == NULL){
        LOG_ERR("[%s:%d] Pointer assignment fail\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    dataFormatCode = *(praw++);
    if (dataFormatCode != 0x0Au) {
        LOG_ERR("[%s:%d] Data format code mismatch\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    } else {
        flag_end_detected = 0u;
    }

    for (i = 1u; i < lineLength / 3u; i++) {
        if (flag_end_detected == 1u) {
            tag = *(praw++);
            if (tag == 0x0Au) {
                flag_end_detected = 0u;
            }
        } else {
            tag = *(praw++);
            praw++; /* skipping garbage */
            dat = *(praw++);
            if (tag == 0xAAu) {
                addrh = dat;
            } else if (tag == 0xA5u) {
                addrl = dat;
                addr = (addrh << 8) + addrl;
                dataReady = 0u;
            } else if (tag == 0x5Au) {
                if ((dataReady & 0x01) == 1u) {
                    datal = dat;
                    data = (datah << 8) + datal;
                    for (j = 0u; j < selLength; j++) {
                        if (regset[j].addr == addr) {
                            regset[j].data = data;
                            break;
                        }
                    }
                    addr = addr + 2u;
                    dataReady = ~dataReady;
                } else {
                    datah = dat;
                    dataReady = ~dataReady;
                }
            } else if (tag == 0x07u) {
                flag_end_detected = 1u;
            }
        }
    }

done:
    return status;
}

static NvMediaStatus
GetModuleConfig(
    NvMediaISCDevice *handle,
    NvMediaISCModuleConfig *cameraModuleConfig)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL) ||
        (cameraModuleConfig == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    strncpy(cameraModuleConfig->cameraModuleCfgName,
           drvHandle->moduleCfg.cameraModuleCfgName,
           strlen(drvHandle->moduleCfg.cameraModuleCfgName));
    cameraModuleConfig->cameraModuleConfigPass1 =
        drvHandle->moduleCfg.cameraModuleConfigPass1;
    cameraModuleConfig->cameraModuleConfigPass2 =
        drvHandle->moduleCfg.cameraModuleConfigPass2;

done:
    return status;
}

static NvMediaStatus
SetSensorFrameReport(
    NvMediaISCDevice *handle,
    const NvMediaISCFrameReport *frameReport)
{
    uint8_t regVal[2] = {0u};
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL) ||
        (frameReport == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    if (frameReport->numBytes > drvHandle->numFrameReportBytes) {
        LOG_ERR("Unsupported numFrameReportBytes. Max value supported \
                 by driver: %d \n", drvHandle->numFrameReportBytes);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    regVal[0] = frameReport->sensorframeReport[0];
    status = AR0231RCCBWriteRegister(handle, 0u, REG_FRAME_REPORT, 1u, regVal);
    if (NVMEDIA_STATUS_OK != status) {
        LOG_ERR("Failed to write Frame Report Register\n");
    }

done:
    return status;
}

static NvMediaStatus
SetSensorControls(
    NvMediaISCDevice *handle,
    const struct NvMediaISCSensorControl *sensorControl,
    const size_t sensrCtrlStructSize)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t regVal[2];

    if ((handle == NULL) || (sensorControl == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    if (sensrCtrlStructSize != sizeof(NvMediaISCSensorControl)) {
        LOG_MSG("Version mismatch detected. Please update client application.\n");
        status = NVMEDIA_STATUS_NOT_SUPPORTED;
        goto done;
    }

    if (sensorControl->numSensorContexts == 1u) {
        /* group hold  */
        regVal[0] = 0x01;
        status = AR0231RCCBWriteRegister(handle, 0u, REG_GROUP_HOLD, 1u, regVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("Failed to write group hold register\n");
            goto done;
        }
        /* exposure control calculation */
        if (sensorControl->exposureControl[0].expTimeValid ||
           sensorControl->exposureControl[0].gainValid) {
            SetExposure(handle, &sensorControl->exposureControl[0]);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("Failed to SetExposure\n");
                goto done;
            }
        }

        /* wb control calculation */
        if (sensorControl->wbControl[0].wbValid) {
            SetSensorWbGain(handle, &sensorControl->wbControl[0]);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("Failed to SetSensorWbGain\n");
                goto done;
            }
        }

        /* frame report control calculation */
        if (sensorControl->frameReportControl.frameReportValid) {
            SetSensorFrameReport(handle, &sensorControl->frameReportControl);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("Failed to SetSensorFrameReport\n");
                goto done;
            }
        }

        /* group hold release */
        regVal[0] = 0x00;
        status = AR0231RCCBWriteRegister(handle, 0u, REG_GROUP_HOLD, 1u, regVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("Failed to write group hold release register\n");
            goto done;
        }

    } else {
        LOG_MSG("Maximum Number of Sensor Context supported by driver : 1 \n");
        status = NVMEDIA_STATUS_NOT_SUPPORTED;
        goto done;
    }

done:
    return status;

}

static NvMediaStatus
ParseEmbedDataInfo(
    NvMediaISCDevice *handle,
    const struct NvMediaISCEmbeddedDataChunk *embeddedTopDataChunk,
    const struct NvMediaISCEmbeddedDataChunk *embeddedBotDataChunk,
    const size_t dataChunkStructSize,
    struct NvMediaISCEmbeddedDataInfo *parsedInfo,
    const size_t parsedInfoStructSize)
{
    uint16_t i, k;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    float_t lineRate;
    float_t slope,T0;
    uint32_t frame_counter;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL) ||
        (embeddedTopDataChunk == NULL) || (embeddedBotDataChunk == NULL) || (parsedInfo == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    if ((dataChunkStructSize != sizeof(NvMediaISCEmbeddedDataChunk)) ||
        (parsedInfoStructSize != sizeof(NvMediaISCEmbeddedDataInfo))) {
        LOG_ERR("Version mismatch. Please re-compile the client application. \n");
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    lineRate = drvHandle->config_info.exposureLineRate;
    PARSE_PRINT("%s: line length %d\n", __func__, embeddedTopDataChunk->lineLength);
    PARSE_PRINT("%s: embedded data\n", __func__);

    for (k = 0u; k < embeddedTopDataChunk->lineLength / 3; k++) {
        PARSE_PRINT("0x%02x ", *(embeddedTopDataChunk->lineData +1 + k));
        if ((k % 15u) == 14u) {
            PARSE_PRINT("\n");
        }
    }
    PARSE_PRINT("\n");

    if (drvHandle->embDataType == 1u){ /* MIPI Interface */
        /* decoding embedded line & filling register set */
        status = DepackEmbeddedLineMipiInterface (drvHandle->regsel_table,
                                                  embeddedTopDataChunk->lineLength,
                                                  embeddedTopDataChunk->lineData,
                                                  SEL_EMB_LENGTH_AR0231);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: DepackEmbeddedLineMipiInterface failed \n", __func__);
            goto done;
        }
    } else { /* Parallel Interface */
        /* decoding embedded line & filling register set */
        status = DepackEmbeddedLineParallelInterface (drvHandle->regsel_table,
                                                      embeddedTopDataChunk->lineLength,
                                                      embeddedTopDataChunk->lineData,
                                                      SEL_EMB_LENGTH_AR0231);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: DepackEmbeddedLineParallelInterface failed \n", __func__);
            goto done;
        }
    }

    for (k = 0u; k < SEL_EMB_LENGTH_AR0231; k++) {
        PARSE_PRINT("%s [add] 0x%04x [data] 0x%04x\n", drvHandle->regsel_table[k].name,
                                                       drvHandle->regsel_table[k].addr,
                                                       drvHandle->regsel_table[k].data);
    }

    (void)memset(parsedInfo, 0u, sizeof(NvMediaISCEmbeddedDataInfo));

    /* frame number of exposures info */
    parsedInfo->numExposures = drvHandle->numActiveExposures;

    /* sensor exposure info */
    status = ParseExposure(drvHandle, &parsedInfo->sensorExpInfo, lineRate);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: ParseExposure failed \n", __func__);
        goto done;
    }

    /* sensor white balance info */
    status = ParseWBGain(drvHandle, &parsedInfo->sensorWBInfo);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: ParseWBGain failed \n", __func__);
        goto done;
    }

    /* sensor temperature info */
    parsedInfo->sensorTempInfo.tempValid = NVMEDIA_TRUE;
    parsedInfo->sensorTempInfo.numTemperatures = 2; /* top and bottom */

    /* Temperature Calculations
     * Temperature = slope x TempDataReg + T0
     * slope = (TempCalib1 - TempCalib2) / (TempCalib1Reg - TempCalib2Reg)
     * T0 = slope x TempCalib2Reg - TempCalib2
     */
    if (ENABLE_TEMP_SENSOR == 1u) {
        slope = (TEMP_CALIB_1 - TEMP_CALIB_2) /
                ((drvHandle->regsel_table[REG_TEMPSENS0_CAL1_REG_AR0231].data)
                -(drvHandle->regsel_table[REG_TEMPSENS0_CAL2_REG_AR0231].data));
        T0 = ((float)(drvHandle->regsel_table[REG_TEMPSENS0_CAL2_REG_AR0231].data) * slope)
             - TEMP_CALIB_2;
        parsedInfo->sensorTempInfo.sensorTempCelsius[0] = slope *
            drvHandle->regsel_table[REG_TEMPSENS0_DATA_REG_AR0231].data - T0;

        slope = (TEMP_CALIB_1 - TEMP_CALIB_2) /
                ((drvHandle->regsel_table[REG_TEMPSENS1_CAL1_REG_AR0231].data)
                - (drvHandle->regsel_table[REG_TEMPSENS1_CAL2_REG_AR0231].data));
        T0 = ((float)(drvHandle->regsel_table[REG_TEMPSENS1_CAL2_REG_AR0231].data) * slope)
             - TEMP_CALIB_2;
        parsedInfo->sensorTempInfo.sensorTempCelsius[1] = slope *
            drvHandle->regsel_table[REG_TEMPSENS1_DATA_REG_AR0231].data - T0;

        PARSE_PRINT("temperatureDataTop=%f, temperatureDataBot=%f\n",
                 parsedInfo->sensorTempInfo.sensorTempCelsius[0],
                 parsedInfo->sensorTempInfo.sensorTempCelsius[1]);
    }

    /* frame sequence number info */
    parsedInfo->frameSeqNumInfo.frameSeqNumValid = NVMEDIA_TRUE;
    frame_counter = (drvHandle->regsel_table[REG_FRAME_COUNT_MSB_AR0231].data << 16) |
                    drvHandle->regsel_table[REG_FRAME_COUNT_LSB_AR0231].data;
    parsedInfo->frameSeqNumInfo.frameSequenceNumber = frame_counter;
    PARSE_PRINT("frameSequenceNumber=%ld\n", parsedInfo->frameSeqNumInfo.frameSequenceNumber);

    /* sensor frame report */
    parsedInfo->sensorReportInfo.frameReportValid = NVMEDIA_TRUE;
    parsedInfo->sensorReportInfo.numBytes = 1u;
    parsedInfo->sensorReportInfo.sensorframeReport[0] = (uint32_t)
                (0x0F & (drvHandle->regsel_table[REG_FRAME_REPORT_AR0231].data >> 8));

    for (i = 0u; i < parsedInfo->numExposures; i++) {
       PARSE_PRINT("ET[%d].value=%f, valid=%d, Gain[%d].value=%f, valid=%d \n", i,
                   parsedInfo->sensorExpInfo.exposureTime[i],
                   parsedInfo->sensorExpInfo.expTimeValid,
                   i, parsedInfo->sensorExpInfo.sensorGain[i],
                   parsedInfo->sensorExpInfo.gainValid);
    }

    for (i = 0u; i < parsedInfo->numExposures; i++) {
        PARSE_PRINT("WB[%d].value={%f, %f, %f, %f}  valid=%d\n", i,
                    parsedInfo->sensorWBInfo.wbGain[i].value[0],
                    parsedInfo->sensorWBInfo.wbGain[i].value[1],
                    parsedInfo->sensorWBInfo.wbGain[i].value[2],
                    parsedInfo->sensorWBInfo.wbGain[i].value[3],
                    parsedInfo->sensorWBInfo.wbValid);
    }

done:
    return status;
}

static NvMediaStatus
GetSensorAttributes(
    NvMediaISCDevice *handle,
    struct NvMediaISCSensorAttributes *sensorAttr,
    const size_t sensorAttrStructSize)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    char name[128];
    float_t pixelRate = 0.0f;
    float_t minVal[3] = {0.0f};
    float_t maxVal[3] = {0.0f};
    uint32_t i = 0u;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL) ||
        (sensorAttr == NULL) || (sensorAttrStructSize == 0u)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    if (sensorAttrStructSize != sizeof(NvMediaISCSensorAttributes)) {
        LOG_ERR("Version mismatch!\n");
        status = NVMEDIA_STATUS_NOT_SUPPORTED;
        goto done;
    }

    pixelRate = 1e9 / (float_t)AR0231_PCLK;
    (void)memset(sensorAttr, 0u, sizeof(NvMediaISCSensorAttributes));
    GetDeviceDriverName(name);
    /* attr sensor name */
    (void)memcpy(sensorAttr->sensorName, name, NVMEDIA_ISC_MAX_SENSOR_NAME_LENGTH);

    for (i = 0u; i < SIZE_FUSE_ID; i++) {
        sensorAttr->sensorFuseId[i] = (char)drvHandle->fuseId[i];
    }

    sensorAttr->sensorCFA = NVM_SURF_ATTR_COMPONENT_ORDER_CRBC;

    /* attr number of active exposures */
    sensorAttr->numActiveExposures = drvHandle->numActiveExposures;
    /* exposure range */
    minVal[2] = (pixelRate * (float_t)AR0231_FINE_INTEG_TIME_T3) / 1e9;
    minVal[1] = ((pixelRate * (float_t)AR0231_FINE_INTEG_TIME_T2) / 1e9 + minVal[2]);
    minVal[0] = ((pixelRate * (float_t)AR0231_FINE_INTEG_TIME_T1) / 1e9 + minVal[1]);

    maxVal[2] = (pixelRate * ((float_t)(drvHandle->timingSettings->hts) *
                (float_t)(MAX_AE_EXP_LINES_T3 - 1u) + (float_t)AR0231_FINE_INTEG_TIME_T3)) / 1e9;
    maxVal[1] = ((pixelRate * ((float_t)(drvHandle->timingSettings->hts) *
                 (float_t)(MAX_AE_EXP_LINES_T2 - 1u) + (float_t)AR0231_FINE_INTEG_TIME_T2))
                 / 1e9 + maxVal[2]);
    maxVal[0] = ((pixelRate * ((float_t)(drvHandle->timingSettings->hts) *
                 (float_t)(drvHandle->timingSettings->vts - 9u) +
                 (float_t)AR0231_FINE_INTEG_TIME_T1 + (float_t)AR0231_FINE_INTEG_TIME_T2 +
                 (float_t)AR0231_FINE_INTEG_TIME_T3)) / 1e9);
    if (drvHandle->charModeEnabled) {
       sensorAttr->sensorExpRange[0].min = minVal[drvHandle->charModeExpNo];
       sensorAttr->sensorExpRange[0].max = maxVal[drvHandle->charModeExpNo];
       sensorAttr->sensorGainRange[0].min = MIN_AGAIN_LCG * MIN_DGAIN;
       sensorAttr->sensorGainRange[0].max = drvHandle->config_info.maxGain;
       sensorAttr->sensorWhiteBalanceRange[0].min = MIN_WBGAIN;
       sensorAttr->sensorWhiteBalanceRange[0].max = MAX_WBGAIN;

    } else {
        for (i = 0u; i < drvHandle->numActiveExposures; i++) {
            sensorAttr->sensorExpRange[i].min = minVal[i];
            sensorAttr->sensorExpRange[i].max = maxVal[i];
            sensorAttr->sensorGainRange[i].min = MIN_AGAIN_LCG * MIN_DGAIN;
            sensorAttr->sensorGainRange[i].max = drvHandle->config_info.maxGain;
            sensorAttr->sensorWhiteBalanceRange[i].min = MIN_WBGAIN;
            sensorAttr->sensorWhiteBalanceRange[i].max = MAX_WBGAIN;
        }
    }

    sensorAttr->numFrameReportBytes = drvHandle->numFrameReportBytes;

done:
    return status;
}

static NvMediaStatus
SetSensorCharMode (
    NvMediaISCDevice *handle,
    uint8_t expNo)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    NvMediaISCWhiteBalance sensorWbCtrl;
    uint8_t regVal;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    /* bypass only one channel */
    regVal = EXPOSURE_BYPASS(expNo);
    status = AR0231RCCBWriteRegister(handle, 0u, REG_HDR_CTL, 1u, &regVal);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] HDR control register write failed\n", __func__, __LINE__);
        goto done;
    }
    /* 12bit raw */
    regVal = 0x0C;
    status = AR0231RCCBWriteRegister(handle, 0u, REG_DATA_FORMAT, 1u, &regVal);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] Data format register write failed\n", __func__, __LINE__);
        goto done;
    }

    /* Apply [1.0,1.0,1.0,1.0] as wbGain */
    sensorWbCtrl.wbValid = NVMEDIA_TRUE;
    sensorWbCtrl.wbGain[0].value[0] = 1.0f;
    sensorWbCtrl.wbGain[0].value[1] = 1.0f;
    sensorWbCtrl.wbGain[0].value[2] = 1.0f;
    sensorWbCtrl.wbGain[0].value[3] = 1.0f;

    status = SetSensorWbGain(handle, &sensorWbCtrl);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] Setting White balance gain failed\n", __func__, __LINE__);
        goto done;
    }

    drvHandle->charModeEnabled = NVMEDIA_TRUE;
    drvHandle->charModeExpNo = expNo;
    drvHandle->numActiveExposures = 1u;

done:
    return status;
}


static NvMediaISCDeviceDriver deviceDriver = {
    .deviceName = "AR0231 Image Sensor",
    .regLength = 2u,
    .dataLength = 2u,
    .DriverCreate = DriverCreate,
    .DriverDestroy = DriverDestroy,
    .GetModuleConfig = GetModuleConfig,
    .GetSensorAttributes = GetSensorAttributes,
    .SetSensorControls = SetSensorControls,
    .ParseEmbedDataInfo = ParseEmbedDataInfo,
    .SetSensorCharMode = SetSensorCharMode,
};

NvMediaISCDeviceDriver *
GetAR0231RccbDriver(void)
{
    return &deviceDriver;
}

NvMediaStatus
GetAR0231RccbConfigSet(
    char *resolution,
    char *inputFormat,
    int *configSet,
    uint32_t framerate)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    /* set input mode setting for ar0231 */
    // set input mode setting for ar0231
    if ((resolution == NULL) || (inputFormat == NULL) || (configSet == NULL)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    if(strcasecmp(inputFormat, "raw12") == 0) {
        if(strcasecmp(resolution, "1920x1208") == 0) {
            if(framerate == 30u) {
                *configSet = ISC_CONFIG_AR0231_DVP_RAW12_COMP_1920X1208;
            }
        } else if((resolution != NULL) && (strcasecmp(resolution, "1920x1008") == 0)) {
            *configSet = ISC_CONFIG_AR0231_DVP_RAW12_COMP_1920X1008;
        } else {
            LOG_DBG("%s: not support %s \n", __func__, inputFormat);
            status = NVMEDIA_STATUS_ERROR;
            goto done;
        }
    } else {
        LOG_DBG("%s: not support %s \n", __func__, inputFormat);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

done:
    return status;
}

static NvMediaStatus
GetDeviceDriverName(char *name)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if (name == NULL) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

    (void)memcpy(name, deviceDriver.deviceName, NVMEDIA_ISC_MAX_SENSOR_NAME_LENGTH);

done:
    return status;
}
