/*
 * Copyright (c) 2017-2019, 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 <errno.h>

#include "log_utils.h"
#include "nvmedia_isc.h"
#include "isc_ar0144.h"
#include "isc_ar0144_setting.h"
#include "isc_ar0144_regmap.h"
#include "os_common.h"

#define REG_ADDRESS_BYTES     2
#define REG_DATA_BYTES        2
#define REG_WRITE_BUFFER      256

#define AR0144_CHIP_ID        0x1356u

#define MIN_AGAIN             1.684f  /* sensor vendor recommanded */
#define MAX_AGAIN             16.0f   /* sensor vendor recommanded */
#define MIN_DGAIN             1.0f
#define MAX_DGAIN             (15.0f + (127.0f / 128.0f))
#define MAX_DGAIN_VAL         0x7FF   /* = 15 + 127 / 128 */
#define ONE_DGAIN_VAL         0x80    /* = 1.0; format4.7 XXXX.YYYYYYY */
#define MIN_GAIN              (MIN_AGAIN * MIN_DGAIN)
#define MAX_GAIN              (MAX_AGAIN * MAX_DGAIN)
#define MIN_WBGAIN            (1.0f)
#define MAX_WBGAIN            (8.0f)
#define MIN_FIT_ZERO_CIT      18
#define MAX_FIT_ZERO_CIT      (AR0144_HTS - 550)
#define MAX_FIT_NONZERO_CIT   (AR0144_HTS - 740)
#define MAX_CIT               (AR0144_VTS - 1)
#define GET_BLOCK_LENGTH(x)   x[0]
#define GET_BLOCK_DATA(x)     (&x[1])
#define SET_NEXT_BLOCK(x)     (x += (x[0] + 1u))
#define CALIBRATION_TEMP      55.0f
#define SLOPE_TEMP            0.7f

#define OSC_MHZ 27

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

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

static const float_t aGainCoarseTbl[] = {
    1.0f,   /* 0 - 1x */
    2.0f,   /* 1 - 2x */
    4.0f,   /* 2 - 4x */
    8.0f,   /* 3 - 8x */
    16.0f,  /* 4 - 16x */
    32.0f,  /* 5 - 32x */
    64.0f,  /* 6 - 64x */
    128.0f  /* 7 - 128x */
};

#define NUM_COARSE_A_GAIN ((sizeof(aGainCoarseTbl)) / (sizeof(aGainCoarseTbl[0])))

static const float_t aGainFineTbl[] = {
    1.0000f,  /* 0 - 32/32x */
    1.0323f,  /* 1 - 32/31x */
    1.0667f,  /* 2 - 32/30x */
    1.1034f,  /* 3 - 32/29x */
    1.1429f,  /* 4 - 32/28x */
    1.1852f,  /* 5 - 32/27x */
    1.2308f,  /* 6 - 32/26x */
    1.2800f,  /* 7 - 32/25x */
    1.3333f,  /* 8 - 32/24x */
    1.3913f,  /* 9 - 32/23x */
    1.4545f,  /*10 - 32/22x */
    1.5238f,  /*11 - 32/21x */
    1.6000f,  /*12 - 32/20x */
    1.6842f,  /*13 - 32/19x */
    1.7778f,  /*14 - 32/18x */
    1.8824f   /*15 - 32/17x */
};

#define NUM_FINE_A_GAIN ((sizeof(aGainFineTbl)) / (sizeof(aGainFineTbl[0])))

typedef struct {
    RevisionAR0144 revId;
    uint32_t revMajorVal;
    uint32_t revMinorVal;
} Revision;

/* These values must include all of values in the RevisionAR0144 enum */
static Revision supportedRevisions[] = {
    { ISC_AR0144_REV_4, 0x4, 0x2 }
};

typedef struct {
    const uint8_t *default_setting;
    RevisionAR0144 revision;
    float_t rowTime;
    ConfigInfoAR0144 config_info;
    NvMediaISCModuleConfig moduleCfg;
    uint8_t enableOnChipAE;
    ISCRegSetAR0144 regsel_table[SEL_EMB_LENGTH_AR0144];
    uint8_t     numActiveExposures;
    uint8_t     numFrameReportBytes;

} _DriverHandle;

static NvMediaStatus GetDeviceDriverName(char *name);

static NvMediaStatus
DriverCreate(
    NvMediaISCDevice *handle,
    void *clientContext)
{
    _DriverHandle *driverHandle;
    const ContextAR0144 *ctx = NULL;
    uint32_t lenCfgName;
    NvMediaStatus status = NVMEDIA_STATUS_OK;

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

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

    driverHandle->revision = ISC_AR0144_INVALID_REV;
    driverHandle->default_setting = ar0144_default;
    driverHandle->rowTime = (float_t)AR0144_HTS / (float_t)AR0144_PCLK;
    driverHandle->enableOnChipAE = (uint8_t)ISC_AR0144_AE_ENABLED;
    driverHandle->config_info.frameRate = 60u;

    handle->deviceDriverHandle = (void *)driverHandle;

    if (clientContext != NULL) {  /* ok to be NULL, then use default values */
        ctx = clientContext;

        lenCfgName = strlen(ctx->moduleConfig.cameraModuleCfgName);
        if ((sizeof(driverHandle->moduleCfg.cameraModuleCfgName)) > lenCfgName) {
            (void)strncpy(driverHandle->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;
        }

        driverHandle->moduleCfg.cameraModuleConfigPass1 = ctx->moduleConfig.cameraModuleConfigPass1;
        driverHandle->moduleCfg.cameraModuleConfigPass2 = ctx->moduleConfig.cameraModuleConfigPass2;
    }

    /* copy configInfo to current handle */
    (void)memcpy(driverHandle->regsel_table, regsel_ar0144,
                                        sizeof(ISCRegSetAR0144)*SEL_EMB_LENGTH_AR0144);

    driverHandle->numActiveExposures = 1u;
    driverHandle->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
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') {
        switch (arrayData[0]) {
            case 'w':
                status = NvMediaISCDeviceWrite(handle,
                                               0,
                                               arrayData[1],
                                               (uint8_t*)&arrayData[2]);
                arrayData += (arrayData[1] + 2u);
                break;
            case 'd':
                if (nvsleep((arrayData[1] << 8) + arrayData[2]) != 0) {
                    LOG_ERR("%s: nvsleep() failed\n", __func__);
                }
                arrayData += 3u;
                break;
            default:
                break;
        }

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

done:
    return status;
}

static NvMediaStatus
GetRevId (
    NvMediaISCDevice *handle,
    RevisionAR0144 *rev)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t readBuff[1] = {0u};
    uint32_t majorRev = 0u, minorRev = 0u;
    uint32_t i = 0u, numRev = sizeof(supportedRevisions) / sizeof(supportedRevisions[0]);

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

    /* Read lower byte of register */
    status = AR0144ReadRegister(handle, 0,
                                REG_DEV_REV_ADDR + 1,
                                1,
                                readBuff);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] Revision Id register read failed\n", __func__, __LINE__);
        goto done;
    }

    /* Read lower byte of register */
    minorRev = readBuff[0] & 0x0F; /* bit[3:0] - OTP Revision*/
    majorRev = (readBuff[0] >> 4) & 0x0F; /* bit[7:4] Silicon Revision*/

    for (i = 0; i < numRev; i++) {
        if (majorRev == supportedRevisions[i].revMajorVal) {
            if (minorRev == supportedRevisions[i].revMinorVal) {
                *rev = supportedRevisions[i].revId;
                LOG_MSG("Sensor AR0144 Rev %u.%u detected!\n", majorRev, minorRev);
                status = NVMEDIA_STATUS_OK;
                goto done;
            }
        }
    }

    *rev = ISC_AR0144_INVALID_REV;
    LOG_ERR("Unsupported AR0144 revision %u.%u detected! Supported revisions are:\n",
            majorRev, minorRev);
    status = NVMEDIA_STATUS_NOT_SUPPORTED;
    for (i = 0; i < numRev; i++) {
        LOG_MSG("AR0144 Rev %u.%u\n", supportedRevisions[i].revMajorVal,
                                      supportedRevisions[i].revMinorVal);
    }

done:
    return status;
}

NvMediaStatus
AR0144SetDefaults(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    _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] SetDefaults failed\n", __func__, __LINE__);
        goto done;
    }

done:
    return status;
}

NvMediaStatus
AR0144SetDeviceConfig(
        NvMediaISCDevice *handle,
        uint32_t enumeratedDeviceConfig)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    _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_AR0144_DVP_RAW12_1280X800_60:
            break;
        case ISC_CONFIG_AR0144_ENABLE_STREAMING:
            status = WriteArrayWithCommand(handle,
                                           AR0144_enable_streaming);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("[%s:%d] Failed to enable streaming\n", __func__, __LINE__);
                goto done;
            }
            nvsleep(10000);  /* wait 10ms */
            break;
        case ISC_CONFIG_AR0144_ENABLE_PG:
            status = WriteArrayWithCommand(handle,
                                           AR0144_enable_pg);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("[%s:%d] Failed to enable PG\n", __func__, __LINE__);
                goto done;
            }
            break;
        case ISC_CONFIG_AR0144_DISABLE_EMB_DATA:
            status = WriteArrayWithCommand(handle,
                                           AR0144_disable_emb_data);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("[%s:%d] Failed to disable embedded data\n", __func__, __LINE__);
                goto done;
            }
            break;
        case ISC_CONFIG_AR0144_DVP_RAW12_1280X800_30:
            drvHandle->config_info.frameRate = 30u;
            status = WriteArrayWithCommand(handle,
                                           AR0144_config_30fps);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("[%s:%d] Failed to set the framerate\n", __func__, __LINE__);
                goto done;
            }
            break;
        default:
            LOG_ERR("[%s:%d] Unsupported Device Config\n", __func__, __LINE__);
            status = NVMEDIA_STATUS_NOT_SUPPORTED;
            break;
    }

done:
    return status;
}

NvMediaStatus
AR0144ReadRegister(
    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,
                                  registerData,
                                  dataLength,
                                  dataBuff);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: sensor read failed: 0x%x, length %d\n", __func__, registerNum, dataLength);
    }

done:
    return status;
}

NvMediaStatus
AR0144WriteRegister(
    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) || (dataLength == 0)) {
        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;
    memcpy(&data[2], dataBuff, dataLength);

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

done:
    return status;
}

NvMediaStatus
AR0144CheckPresence(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t readBuff[2] = {0u};
    RevisionAR0144 rev = ISC_AR0144_INVALID_REV;
    _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 = AR0144ReadRegister(handle, 0,
                                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] != ((AR0144_CHIP_ID >> 8) & 0xffu)) ||
                                                    (readBuff[1] != (AR0144_CHIP_ID & 0xffu))) {
        LOG_ERR("[%s:%d] Chip ID Mismatch\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    status = GetRevId(handle,
                      &rev);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] Failed to get revision ID\n", __func__, __LINE__);
        goto done;
    }

    if (rev == ISC_AR0144_INVALID_REV) {
        LOG_ERR("[%s:%d] Revision not supported\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_NOT_SUPPORTED;
        goto done;
    }

    drvHandle->revision = rev;

done:
    return status;
}

static NvMediaStatus
GetPCLKFreq(
    NvMediaISCDevice *handle,
    uint32_t *pclk)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    unsigned char data[8] = {0};

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

    /* read AR0144 PLL registers to get pclk */
    status = AR0144ReadRegister(handle, 0, REG_PLL_VT_PIXDIV, 8, data);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to get sensor PCLK\n", __func__);
        goto done;
    }
    /* pll_muliplier:data[7], pre_pll_clk_div:data[5],
     * vt_sys_clk_div:data[3], vt_pix_clk_div:data[1]
     */
    *pclk = (((OSC_MHZ * data[7]) * 1000) * 1000) / ((data[5] * data[3]) * data[1]);
    LOG_DBG("%s: Get sensor pclk: %d Hz\n", __func__, *pclk);

done:
    return status;
}

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

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

    param = (WriteReadParametersParamAR0144 *)parameter;
    if (param == NULL) {
        LOG_ERR("[%s:%d] Invalid pointer to WriteReadParametersParamAR0144\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    switch (parameterType) {
        case ISC_READ_PARAM_CMD_AR0144_PCLK:
            if (parameterSize != (sizeof(param->pclk))) {
                LOG_ERR("[%s:%d] PCLK value mismatch\n", __func__, __LINE__);
                status = NVMEDIA_STATUS_BAD_PARAMETER;
                goto done;
            }
            status = GetPCLKFreq(handle,
                                 &param->pclk);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to get PCLK frequency\n", __func__, __LINE__);
                goto done;
            }
            break;
        default:
            LOG_ERR("[%s:%d] Parameter type not supported\n", __func__, __LINE__);
            status = NVMEDIA_STATUS_NOT_SUPPORTED;
            break;
    }

done:
    return status;
}

static NvMediaStatus
ParseWBGain(
    _DriverHandle *drvHandle,
    NvMediaISCWhiteBalance *parsedWbInfo)
{
    uint32_t i;
    NvMediaStatus status = NVMEDIA_STATUS_OK;

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

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

    parsedWbInfo->wbValid = NVMEDIA_TRUE;
    for (i = 0; i < NVM_ISC_MAX_COLOR_COMPONENT; i++) {  /* Gr, B, R, Gb */
        parsedWbInfo->wbGain[0].value[i] = 1.0f;
    }

done:
    return status;
}

static NvMediaStatus
ParseExposure(
    _DriverHandle *drvHandle,
    NvMediaISCExposure *sensorExpInfo)
{
    float_t expT;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint16_t a_coarse_gain, a_fine_gain, dgain, cit, fit;
    uint16_t ae_ctrl_reg, ae_curr_gains;
    float_t analogGain, digitalGain;

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

    (void)memset(sensorExpInfo, 0, sizeof(NvMediaISCExposure));

    ae_ctrl_reg = drvHandle->regsel_table[REG_AECTRLREG_AR0144].data;

    if ((ae_ctrl_reg & (uint16_t)0x0001) == 1u) {
        cit = drvHandle->regsel_table[REG_AE_COARSE_INTEGRATION_TIME_AR0144].data;
        fit = 0u;
        ae_curr_gains = drvHandle->regsel_table[REG_AE_CURRENT_GAINS_AR0144].data;
        /* analog coarse gain is bit[13:11] */
        a_coarse_gain = (uint16_t)((ae_curr_gains & (uint16_t)0x3800) >> 11);
        a_fine_gain = 0u;
        dgain = (uint16_t)(ae_curr_gains & (uint16_t)0x07FF);                   /* bit[10:0] */
    } else {
        cit = drvHandle->regsel_table[REG_COARSE_INTEGRATION_TIME_AR0144].data;
        fit = drvHandle->regsel_table[REG_FINE_INTEGRATION_TIME_AR0144].data;
        ae_curr_gains = drvHandle->regsel_table[REG_ANALOG_GAIN_AR0144].data;
        a_coarse_gain = (uint16_t)((ae_curr_gains & (uint16_t)0x0070) >> 4);    /* bit[6:4] */
        a_fine_gain = (uint16_t)(ae_curr_gains & (uint16_t)0x000F);             /* bit[3:0] */
        dgain = drvHandle->regsel_table[REG_GLOBAL_GAIN_AR0144].data;
    }

    digitalGain = (float_t)(dgain) / (float_t)ONE_DGAIN_VAL;
    analogGain = aGainCoarseTbl[a_coarse_gain] * aGainFineTbl[a_fine_gain];
    expT = ((float_t)cit * drvHandle->rowTime) + ((float_t)fit / (float_t)AR0144_PCLK);

    sensorExpInfo->gainValid = NVMEDIA_TRUE;
    sensorExpInfo->sensorGain[0] = digitalGain * analogGain;
    sensorExpInfo->expTimeValid = NVMEDIA_TRUE;
    sensorExpInfo->exposureTime[0]= expT;

    PARSE_PRINT("ae_enabled=%d:: cit=%d, fit=%d, expT=%f\n",
                (ae_ctrl_reg & (uint16_t)0x0001), cit, fit, expT);
    PARSE_PRINT("ag_reg=%x:: ag_coarse=%d, ag_fine=%d, aGain=%f\n",
                ae_curr_gains, a_coarse_gain, a_fine_gain, analogGain);
    PARSE_PRINT("dgain=%d, digitalGain=%f\n",
                dgain, digitalGain);
    EXPOSURE_PRINT("%s: global dGain = %f\n", __func__, dgain);

done:
    return status;
}

static NvMediaStatus
DepackEmbeddedLine (
    ISCRegSetAR0144* 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;
    const uint8_t* praw = lineData;
    uint16_t flag_end_detected = 1u;
    NvMediaStatus status = NVMEDIA_STATUS_OK;

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

    praw++;
    dataFormatCode = *(praw++);
    if (dataFormatCode != 0x0A) {
        LOG_ERR("Broken embedded data, can not parse embedded data!\n");
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    } else {
        flag_end_detected = 0u;
    }

    for (i = 1; (i < lineLength / 2) && ((praw - lineData) < lineLength); i++) {
        if (flag_end_detected == 1) {
            tag = *(praw++);
            if (tag == 0x0A) {
                flag_end_detected = 0u;
            }
        } else {
            praw++; /* garbage */
            tag = *(praw++);
            praw++; /* garbage */
            dat = *(praw++);

            if (tag == 0xAA) {
                addrh = dat;
            } else if (tag == 0xA5) {
                addrl = dat;
                addr = (addrh << 8) + addrl;
                dataReady = 0;
            } else if (tag == 0x5A) {
                if (dataReady & 0x01) {
                    datal = dat;
                    data = (datah << 8) + datal;
                    for (j = 0; 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 == 0x07) {
                flag_end_detected = 1u;
            }
        }
    }

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 k;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint16_t ae_ctrl;
    float_t regTempData, regCalibData, interceptT0;
    _DriverHandle *drvHandle = NULL;

    if ((handle == NULL) || (((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL)) ||
        (embeddedTopDataChunk == NULL) || (embeddedBotDataChunk == NULL) || (parsedInfo == NULL) ||
        dataChunkStructSize == 0 || (parsedInfoStructSize == 0)) {
        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;
    }

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

    PARSE_PRINT("%s: line length %d\n", __func__, embeddedTopDataChunk->lineLength);
    PARSE_PRINT("%s: embedded data\n", __func__);
    for (k = 0; k < embeddedTopDataChunk->lineLength / 4; k++) {
        PARSE_PRINT("0x%02x ", *(embeddedTopDataChunk->lineData + k));
        if ((k % 16) == 15) {
            PARSE_PRINT("\n");
        }
    }
    PARSE_PRINT("\n");

    /* decoding embedded line & filling register set */
    if (embeddedTopDataChunk->lineLength != 0 && embeddedTopDataChunk->lineData != NULL) {
        status = DepackEmbeddedLine (drvHandle->regsel_table,
                                     embeddedTopDataChunk->lineLength,
                                     embeddedTopDataChunk->lineData,
                                     SEL_EMB_LENGTH_AR0144);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: DepackEmbeddedLine failed \n", __func__);
            goto done;
        }
    }
    else{
        LOG_ERR("%s: Invalid Top Embedded data\n", __func__);
        status = NVMEDIA_STATUS_NOT_SUPPORTED;
        goto done;
    }

    for (k = 0; k < SEL_EMB_LENGTH_AR0144; 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);
    }

    ae_ctrl = (drvHandle->regsel_table[REG_AECTRLREG_AR0144].data) & (uint16_t)0x0001;
    if (((ae_ctrl == 1u) && (drvHandle->enableOnChipAE == ISC_AR0144_AE_DISABLED)) ||
        ((ae_ctrl == 0u) && (drvHandle->enableOnChipAE == ISC_AR0144_AE_ENABLED))) {
        LOG_ERR("%s: AE Driver Settings Not Matching Register Settings", __func__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

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

    /* sensor exposure info */
    status = ParseExposure(drvHandle, &parsedInfo->sensorExpInfo);
    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;
    }

    /* parse sensor temperature data */
    parsedInfo->sensorTempInfo.tempValid = NVMEDIA_TRUE;
    parsedInfo->sensorTempInfo.numTemperatures = 1;


    /* Using TEMPSENS_CALIB1 gives more realistic temperature value that using
    TEMPSENS_CALIB2 - 0x30C8 as mentioned in the Design document*/
    regTempData = drvHandle->regsel_table[REG_TEMPSENS_DATA_REG_AR0144].data;
    regCalibData = drvHandle->regsel_table[REG_TEMPSENS_CALIB1_AR0144].data;
    interceptT0 = CALIBRATION_TEMP - SLOPE_TEMP * regCalibData;
    parsedInfo->sensorTempInfo.sensorTempCelsius[0] =
                (SLOPE_TEMP*regTempData) + interceptT0;

    /* parse frame counter */
    parsedInfo->frameSeqNumInfo.frameSeqNumValid = NVMEDIA_TRUE;
    parsedInfo->frameSeqNumInfo.frameSequenceNumber =
                drvHandle->regsel_table[REG_FRAME_COUNT_AR0144].data;

    /*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_AR0144].data >> 8));

    PARSE_PRINT("--PARSED INFORMATION--\n");
    PARSE_PRINT("Temperature from Register: %f\n", regTempData);
    PARSE_PRINT("Calibration temperature: %f\n", regCalibData);
    PARSE_PRINT("Intercept T0: %f\n", interceptT0);
    PARSE_PRINT("Temperature in Celsius: %f\n",
                 parsedInfo->sensorTempInfo.sensorTempCelsius[0]);
    PARSE_PRINT("ET[0].value=%f, valid=%d, Gain[%d].value=%f, valid=%d \n",
                parsedInfo->sensorExpInfo.exposureTime[0],
                parsedInfo->sensorExpInfo.expTimeValid,
                i, parsedInfo->sensorExpInfo.sensorGain[0],
                parsedInfo->sensorExpInfo.gainValid);

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

    PARSE_PRINT("sensorTempCelsius=%f, frameSequenceNumber=%ld\n\n",
                parsedInfo->sensorTempInfo.sensorTempCelsius[0],
                parsedInfo->frameSeqNumInfo.frameSequenceNumber);

done:
    return status;
}

static NvMediaStatus
SetSensorWbGain(
    NvMediaISCDevice *handle,
    const NvMediaISCWhiteBalance *wbControl)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;

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

    return status;
}

static NvMediaStatus
GetGainValues(
    const NvMediaISCExposure *exposureControl,
    uint16_t *aGainRegVal,
    uint16_t *dGainRegVal)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint16_t i;
    uint8_t aGainFine, aGainCoarse;
    uint16_t dGain;
    float_t fineGain = 0;
    float_t setAGain, setDGain;

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

    /* Apply restrictions on Analog Gain */
    if (exposureControl->sensorGain[0] <= MIN_AGAIN) {
        setAGain = MIN_AGAIN;
    } else if (exposureControl->sensorGain[0] <= MAX_AGAIN) {
        setAGain = exposureControl->sensorGain[0];
    } else {
        setAGain = MAX_AGAIN;
    }

    /* coarse analog gain estimation */
    aGainCoarse = 0;
    for (i = NUM_COARSE_A_GAIN - 1u; i >= 0; i--) {
        if (setAGain >= aGainCoarseTbl[i]) {
            aGainCoarse = (uint8_t) i;
            break;
        }
    }

    /* fine analog gain estimation */
    aGainFine = 0;
    for (i = NUM_FINE_A_GAIN - 1u; i >= 0; i--) {
        fineGain = aGainCoarseTbl[aGainCoarse] * aGainFineTbl[i];
        if (fineGain <= setAGain) {
            aGainFine = (uint8_t) i;
            break;
        }
    }

    /* Calculate and apply restrictions on Digital Gain */
    setDGain = exposureControl->sensorGain[0] /
               (aGainCoarseTbl[aGainCoarse] * aGainFineTbl[aGainFine]);

    if (setDGain <= MIN_DGAIN) {
        setDGain = MIN_DGAIN;
    } else if (setDGain >= MAX_DGAIN) {
        setDGain = MAX_DGAIN;
    }

    dGain = (uint16_t)((setDGain * ONE_DGAIN_VAL) + 0.5f);

    *dGainRegVal = (dGain > (uint16_t)MAX_DGAIN_VAL) ? ((uint16_t) MAX_DGAIN_VAL) :
                   (dGain < (uint16_t)ONE_DGAIN_VAL) ? ((uint16_t) ONE_DGAIN_VAL) : (dGain);

    /* Calculate Register Settings for Analog Gain */
    aGainFine   = aGainFine & 0x0F;    /* register bit[3:0] */
    aGainCoarse = aGainCoarse & 0x07;  /* register bit[6:4] */
    *aGainRegVal = aGainFine | (aGainCoarse << 4);

    EXPOSURE_PRINT("%s: effectiv_gain = %f, again_coarse=%f, again_fine=%f, dgain=%f\n",
                   __func__, ((float_t)dGain / ONE_DGAIN_VAL) *
                   aGainCoarseTbl[aGainCoarse] * aGainFineTbl[aGainFine],
                   aGainCoarseTbl[aGainCoarse],
                   aGainFineTbl[aGainFine],
                   (float_t)dGain / ONE_DGAIN_VAL);

done:
    return status;
}

static NvMediaStatus
SetExposure(
    NvMediaISCDevice *handle,
    const NvMediaISCExposure *exposureControl)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t regVal[2];
    uint16_t aGainRegVal = 0;
    uint16_t dGainRegVal = 0;
    uint16_t cit, fit;
    _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;
    }

    if ((exposureControl->gainValid == NVMEDIA_FALSE) &&
        (exposureControl->expTimeValid == NVMEDIA_FALSE)) {
        LOG_ERR("%s: Gain Not Valid and Exposure Not Valid!! \n", __func__);
        status = NVMEDIA_STATUS_ERROR;
        goto done;
    }

    EXPOSURE_PRINT("%s: set sensor FrameId = %x\n", __func__, regVal[0]);

    if (exposureControl->gainValid == NVMEDIA_TRUE) {
        status = GetGainValues(exposureControl, &aGainRegVal, &dGainRegVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] GetGainValues failed\n", __func__, __LINE__);
            goto done;
        }
        EXPOSURE_PRINT("%s: Gain Requested: %f\n", __func__,
                       exposureControl->sensorGain[0]);
        EXPOSURE_PRINT("%s: Reponding Gain Settings: %f\n", __func__,
                       aGainCoarseTbl[aGainRegVal >> 4] *
                       aGainFineTbl[aGainRegVal & 0x000F] *
                       (float_t)dGainRegVal / ONE_DGAIN_VAL);
        PARSE_PRINT("%s: Final Reg Settings: aG   %x\n", __func__, aGainRegVal);
        PARSE_PRINT("%s: Final Reg Settings: dG   %x\n", __func__, dGainRegVal);

        /* analog gain */
        regVal[0] = aGainRegVal & (uint16_t)0xFF;
        status = AR0144WriteRegister(handle, 0, REG_AGAIN + 1u, 1, regVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Analog gain register write failed\n", __func__, __LINE__);
            goto done;
        }

        /* digital gain */
        regVal[0] = (dGainRegVal >> 8) & (uint16_t)0xFF;
        regVal[1] = dGainRegVal & (uint16_t)0xFF;
        status = AR0144WriteRegister(handle, 0, REG_DGAIN, 2, regVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Digital gain register write failed\n", __func__, __LINE__);
            goto done;
        }
    }

    if (exposureControl->expTimeValid == NVMEDIA_TRUE) {
        cit = (uint16_t)(exposureControl->exposureTime[0] / drvHandle->rowTime);
        fit = (uint16_t)((exposureControl->exposureTime[0]
                      - (((float_t)cit) * drvHandle->rowTime))
                      * ((float_t)AR0144_PCLK) + 0.5f);
        if (cit >= MAX_CIT) {
            cit = MAX_CIT;
            fit = 0;
        } else if (cit == 0) {
            if (fit < MIN_FIT_ZERO_CIT) {
                fit = MIN_FIT_ZERO_CIT;
            } else if (fit > MAX_FIT_ZERO_CIT) {
                fit = MAX_FIT_ZERO_CIT;
            }
        } else {
            if (fit > MAX_FIT_NONZERO_CIT) {
                fit = MAX_FIT_NONZERO_CIT;
            }
        }

        EXPOSURE_PRINT("%s: exposure time requested: %f\n", __func__,
                       exposureControl->exposureTime[0]);
        EXPOSURE_PRINT("%s: Reponding exposure Settings: %f\n", __func__,
                       (((float_t)cit) * drvHandle->rowTime) +
                       (((float_t)fit) / ((float_t)AR0144_PCLK)));
        PARSE_PRINT("%s: Final Reg Settings: cit   %x\n", __func__, cit);
        PARSE_PRINT("%s: Final Reg Settings: fit   %x\n", __func__, fit);

        /* cit */
        regVal[0] = (cit >> 8) & (uint16_t)0xFF;
        regVal[1] = cit & (uint16_t)0xFF;
        status = AR0144WriteRegister(handle, 0, REG_INTEG_TIME_T1, 2, regVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Coarse Integration time register write failed\n", __func__, __LINE__);
            goto done;
        }

        /* fit */
        regVal[0] = (fit >> 8) & (uint16_t)0xFF;
        regVal[1] = fit & (uint16_t)0xFF;
        status = AR0144WriteRegister(handle, 0, REG_FIT, 2, regVal);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("[%s:%d] Fine Integration time register write failed\n", __func__, __LINE__);
        }
    }

done:
    return status;
}

static NvMediaStatus
SetSensorFrameReport(
    NvMediaISCDevice *handle,
    const NvMediaISCFrameReport *frameReport)
{
    uint8_t regVal[2] = {0};
    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_NOT_SUPPORTED;
        goto done;
    }

    regVal[0] = frameReport->sensorframeReport[0];

    status = AR0144WriteRegister(handle, 0, REG_FRAME_REPORT, 1, regVal);
    if (status != NVMEDIA_STATUS_OK) {
        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) ||
        (sensrCtrlStructSize == 0)) {
        LOG_ERR("[%s:%d] Bad input parameter\n", __func__, __LINE__);
        status = NVMEDIA_STATUS_BAD_PARAMETER;
        goto done;
    }

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

    if (sensorControl->numSensorContexts == 1) {
        /* group hold */
        regVal[0] = 0x01;
        status = AR0144WriteRegister(handle, 0, REG_GROUP_HOLD, 1, 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 == NVMEDIA_TRUE) ||
            (sensorControl->exposureControl[0].gainValid == NVMEDIA_TRUE)) {
            status = SetExposure(handle, &sensorControl->exposureControl[0]);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("Failed to Set Exposure\n");
                goto done;
            }
        }

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

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

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

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

done:
    return status;
}

NvMediaStatus
AR0144DumpRegisters(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t data[REG_DATA_BYTES] = {0};
    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') {
            status = NvMediaISCDeviceRead(handle, 0,
                                          REG_ADDRESS_BYTES,
                                          &arrayData[2],
                                          REG_DATA_BYTES,
                                          data);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Register read failed \n", __func__);
                goto done;
            }

            LOG_DBG("20 %.2X%.2X %.2x %.2x\n", (uint32_t)arrayData[2],
                                               (uint32_t)arrayData[3], data[0], data[1]);
            arrayData += (arrayData[1] + 2u);
        } else {
            /* for 'd' */
            arrayData += 3;
        }
    }

done:
    return status;
}

static NvMediaStatus
GetSensorAttributes(
    NvMediaISCDevice *handle,
    struct NvMediaISCSensorAttributes *sensorAttr,
    const size_t sensorAttrStructSize)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    _DriverHandle *drvHandle = NULL;
    char name[NVMEDIA_ISC_MAX_SENSOR_NAME_LENGTH];
    float_t minVal = 0.0f;
    float_t maxVal = 0.0f;

    if ((handle == NULL) || ((drvHandle = (_DriverHandle *)handle->deviceDriverHandle) == NULL) ||
        (sensorAttr == NULL) || (sensorAttrStructSize == 0)) {
        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;
    }

    (void)memset(sensorAttr, 0, sizeof(NvMediaISCSensorAttributes));
    status = GetDeviceDriverName(name);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to get device driver name\n", __func__);
        goto done;
    }

    /* attr sensor name */
    memcpy(sensorAttr->sensorName, name, NVMEDIA_ISC_MAX_SENSOR_NAME_LENGTH);

    /* attr number of active exposures */
    sensorAttr->numActiveExposures = drvHandle->numActiveExposures;

    sensorAttr->sensorCFA = NVM_SURF_ATTR_COMPONENT_ORDER_CCCC;

    /* exposure range */
    minVal = ((float_t)MIN_FIT_ZERO_CIT) / ((float_t)AR0144_PCLK);
    maxVal = ((float_t)MAX_CIT) * drvHandle->rowTime;

    sensorAttr->sensorExpRange[0].min = minVal;
    sensorAttr->sensorExpRange[0].max = maxVal;
    sensorAttr->sensorGainRange[0].min = MIN_GAIN;
    sensorAttr->sensorGainRange[0].max = MAX_GAIN;
    sensorAttr->sensorWhiteBalanceRange[0].min = MIN_WBGAIN;
    sensorAttr->sensorWhiteBalanceRange[0].max = MAX_WBGAIN;
    sensorAttr->sensorGainFactor[0] = 1.0f;
    sensorAttr->numFrameReportBytes = drvHandle->numFrameReportBytes;

done:
    return status;
}

static NvMediaStatus
SetSensorCharMode (
    NvMediaISCDevice *handle,
    uint8_t expNo) {

    NvMediaStatus status = NVMEDIA_STATUS_OK;
    _DriverHandle *drvHandle = NULL;
    uint8_t regVal;

    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;
    }

    /* Disable AE */
    regVal = 0x00;
    status = AR0144WriteRegister(handle, 0, REG_AECTRLREG + 1u, 1, &regVal);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("[%s:%d] Disabling AE failed\n", __func__, __LINE__);
        goto done;
    }

    drvHandle->enableOnChipAE = ISC_AR0144_AE_DISABLED;

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;
    }

    (void)strncpy(cameraModuleConfig->cameraModuleCfgName,
                  drvHandle->moduleCfg.cameraModuleCfgName,
                  strlen(drvHandle->moduleCfg.cameraModuleCfgName));

    cameraModuleConfig->cameraModuleConfigPass1 =
        drvHandle->moduleCfg.cameraModuleConfigPass1;
    cameraModuleConfig->cameraModuleConfigPass2 =
        drvHandle->moduleCfg.cameraModuleConfigPass2;

done:
    return status;
}

static NvMediaISCDeviceDriver deviceDriver = {
    .deviceName = "AR0144 Image Sensor",
    .regLength = REG_ADDRESS_BYTES,
    .dataLength = REG_DATA_BYTES,
    .DriverCreate = DriverCreate,
    .DriverDestroy = DriverDestroy,
    .GetModuleConfig = GetModuleConfig,
    .SetSensorControls = SetSensorControls,
    .SetSensorCharMode = SetSensorCharMode,
    .GetSensorAttributes = GetSensorAttributes,
    .ParseEmbedDataInfo = ParseEmbedDataInfo,
};

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

NvMediaStatus
GetAR0144ConfigSet(
    char *resolution,
    char *inputFormat,
    uint32_t framerate,
    int *configSet)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    /* set input mode setting for ar0144 */
    if ((inputFormat != NULL) && (strcasecmp(inputFormat, "raw12") == 0)
        && (resolution != NULL) && (strcasecmp(resolution, "1280x800") == 0)) {
        if (framerate == 60)
            *configSet = ISC_CONFIG_AR0144_DVP_RAW12_1280X800_60;
        else if (framerate == 30)
            *configSet = ISC_CONFIG_AR0144_DVP_RAW12_1280X800_30;
        else {
            LOG_ERR("%s: Not supported framerate=%d\n", __func__, framerate);
            status = NVMEDIA_STATUS_ERROR;
            goto done;
        }
    } else {
        LOG_ERR("%s: Not supported %s \n", __func__, inputFormat);
        status = NVMEDIA_STATUS_ERROR;
    }

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;
    }

    memcpy(name, deviceDriver.deviceName, NVMEDIA_ISC_MAX_SENSOR_NAME_LENGTH);

done:
    return status;
}
