/*
 * Copyright (c) 2018-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 <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#include "ref_max96712_96759_constellation.h"
#include "isc_max96759.h"
#include "isc_max96712.h"
#include "isc_constellation.h"
#include "error_max96712.h"
#include "common_max96712.h"
#include "log_utils.h"
#include "dev_property.h"
#include "dev_map.h"
#include "os_common.h"

#define FTDI_ADDRESS         0x22

static void
Deinit(
    ExtImgDevice *device)
{
    uint32_t i;
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if (NULL == device) {
        return;
    }

    if (device->iscDeserializer) {
        /* Disable replication mode in slave mode */
        LOG_DBG("%s: Replicate output\n", __func__);
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                         ISC_CONFIG_MAX96712_DISABLE_REPLICATION);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to replicate output, err = %d(%s)\n", __func__, errno,
                    (const char*)strerror(errno));
        }
    }

    for (i = 0; i < device->sensorsNum; i++) {
        if (device->iscSerializer[i]) {
            NvMediaISCDeviceDestroy(device->iscSerializer[i]);
        }
        if (device->iscSensor[i]){
            NvMediaISCDeviceDestroy(device->iscSensor[i]);
        }
    }

    if (device->iscBroadcastSerializer){
        NvMediaISCDeviceDestroy(device->iscBroadcastSerializer);
    }
    if (device->iscBroadcastSensor){
        NvMediaISCDeviceDestroy(device->iscBroadcastSensor);
    }
    if (device->iscDeserializer){
        NvMediaISCDeviceDestroy(device->iscDeserializer);
    }
    if (device->iscRoot) {
        if (device->property.enableExtSync && device->property.dutyRatio) {
            NvMediaISCRootDeviceEnableSync(device->iscRoot,
                                           NVMEDIA_FALSE);
        }
        NvMediaISCRootDeviceDestroy(device->iscRoot);
    }

    free(device);

    return;
}

static
NvMediaStatus
SetupConfigLink(
   ExtImgDevParam *configParam,
   ExtImgDevice *device)
{
    NvMediaStatus status;
    WriteParametersParamMAX96712 paramsMAX96712;
    ReadWriteParams96759 paramsMAX96759;
    ReadParametersParamMAX96712 readParamsMAX96712;
    RevisionMAX96712 rev = ISC_MAX96712_INVALID_REV;
    uint8_t i = 0u, link = 0u;

    if ((NULL == configParam) || (NULL == device)) {
        return NVMEDIA_STATUS_BAD_PARAMETER;
    }

    if (device->iscDeserializer && device->iscBroadcastSerializer) {
        /* Read revision number */
        memset(&readParamsMAX96712, 0, sizeof(ReadParametersParamMAX96712));
        LOG_DBG("%s: Read the deserializer revision number\n", __func__);
        status = MAX96712ReadParameters(device->iscDeserializer,
                                        ISC_READ_PARAM_CMD_MAX96712_REV_ID,
                                        sizeof(readParamsMAX96712.rev),
                                        &readParamsMAX96712);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to read the deserializer revision number\n", __func__);
            return status;
        }
        rev = readParamsMAX96712.rev;

        /* Check for MAX96712 revision A limitations */
        if (rev == ISC_MAX96712_REV_2) {
            /* Bug 2182451: Adjust TX amplitude and timing  */
            status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                             ISC_CONFIG_MAX96712_ADJUST_TX_AMP_AND_TIMING);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to adjust TX amplitude and timing\n", __func__);
                return status;
            }

            /* Trigger the link adaptation in GMSL2 mode
             * Make sure the link is up before starting the link adaptation
             */
            for (i = 0u; i < MAX96712_MAX_NUM_LINK; i++) {
                LOG_DBG("%s: Trigger the link adaptation\n", __func__);
                status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                                 ISC_CONFIG_MAX96712_GMSL2_LINK_ADAPTATION_LINK0 + i);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: Failed to trigger the link adaptation\n", __func__);
                    return status;
                }
            }

            status = MAX96712OneShotReset(device->iscDeserializer, 0xF);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to do one-shot reset\n", __func__);
                return status;
            }

            /* nvsleep is used instead of calling MAX96712CheckLink */
            nvsleep(200000);
        }

        /* Disable all deserializer links */
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                           ISC_CONFIG_MAX96712_DISABLE_LINKS_0123);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to disable deserializer links\n", __func__);
            return status;
        }
        nvsleep(50000);

        /* Set Tx SRC ID */
        LOG_DBG("%s: Set Tx SRC ID\n", __func__);
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                           ISC_CONFIG_MAX96712_SET_TX_SRC_ID);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set Tx SRC ID\n", __func__);
            return status;
        }

        /* I2C ports must be reconfigured after deserializer reset */
        memset(&paramsMAX96712.I2CPort, 0, sizeof(paramsMAX96712.I2CPort));
        for (i = 0; i < MAX96712_MAX_NUM_PHY; i++) {
            paramsMAX96712.I2CPort.link = i;
            LOG_DBG("%s: Select I2C port over the link\n", __func__);
            status = MAX96712WriteParameters(device->iscDeserializer,
                                               ISC_WRITE_PARAM_CMD_MAX96712_SET_I2C_PORT,
                                               sizeof(paramsMAX96712.I2CPort),
                                               &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to select I2C port over the link\n", __func__);
                return status;
            }
        }

        /* Make sure the serializers are present and are the correct revision */
        for (i = 0; i < device->sensorsNum; i++) {
            /* link order Link A(VC 0) -> Link C(VC 1) -> Link B(VC 2) -> Link D(VC 3)
             * This is to support the different two E3590 board(A00 and B00) which have
             * the different HW archtecture */
            link = (i == 0) ? 0 :
                   ((i == 1) ? 2 :
                   ((i == 2) ? 1 : 3));
            paramsMAX96712.SpecificLink = link;
            status = MAX96712WriteParameters(device->iscDeserializer,
                            ISC_WRITE_PARAM_CMD_MAX96712_ENABLE_SPECIFIC_LINK,
                            sizeof(paramsMAX96712.SpecificLink),
                            &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable specific link %d\n",
                        __func__, i);
                return status;
            }

            nvsleep(100000);

            /* Ensure the serializer is present and the correct revision */
            status = MAX96759CheckPresence(device->iscBroadcastSerializer);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to detect a serializer with a supported revision\n", __func__);
                return status;
            }

            /* Set unique address with broadcast address */
            memset(&paramsMAX96759.DeviceAddress, 0 , sizeof(paramsMAX96759.DeviceAddress));
            paramsMAX96759.DeviceAddress.address = configParam->serAddr[i];
            status = MAX96759WriteParameters(device->iscBroadcastSerializer,
                                               ISC_WRITE_PARAM_CMD_MAX96759_SET_DEVICE_ADDRESS,
                                               sizeof(paramsMAX96759.DeviceAddress),
                                               &paramsMAX96759);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to set serializer device %d address\n", __func__,
                        configParam->serAddr[i]);
                return status;
            }

            /* Set address translation for the serializer */
            memset(&paramsMAX96759.Translator, 0 , sizeof(paramsMAX96759.Translator));
            paramsMAX96759.Translator.source = configParam->brdcstSerAddr;
            paramsMAX96759.Translator.destination = configParam->serAddr[i];
            status = MAX96759WriteParameters(device->iscSerializer[i],
                                               ISC_WRITE_PARAM_CMD_MAX96759_SET_TRANSLATOR_A,
                                               sizeof(paramsMAX96759.Translator),
                                               &paramsMAX96759);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Address translation A setup failed\n", __func__);
                return status;
            }

            /* Set address translation for the FTDI device */
            memset(&paramsMAX96759.Translator, 0 , sizeof(paramsMAX96759.Translator));
            paramsMAX96759.Translator.source = FTDI_ADDRESS + i + 1;
            paramsMAX96759.Translator.destination = FTDI_ADDRESS;
            status = MAX96759WriteParameters(device->iscSerializer[i],
                                               ISC_WRITE_PARAM_CMD_MAX96759_SET_TRANSLATOR_B,
                                               sizeof(paramsMAX96759.Translator),
                                               &paramsMAX96759);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Address translation B setup failed\n", __func__);
                return status;
            }
        }

        switch(configParam->sensorsNum) {
            case 1:
                LOG_DBG("%s: Enabling link: 0\n", __func__);
                status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                        ISC_CONFIG_MAX96712_ENABLE_LINK_0);
                break;
            case 2:
                LOG_DBG("%s: Enabling 2 links: 0 and 2\n", __func__);
                status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                        ISC_CONFIG_MAX96712_ENABLE_LINKS_02);
                break;
            case 3:
                LOG_DBG("%s: Enabling 3 links: 0, 1 and 2\n", __func__);
                status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                        ISC_CONFIG_MAX96712_ENABLE_LINKS_012);
                break;
            case 4:
                LOG_DBG("%s: Enabling 4 links: 0, 1, 2 and 3\n", __func__);
                status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                        ISC_CONFIG_MAX96712_ENABLE_LINKS_0123);
                break;
            default:
                LOG_ERR("%s: Failed to set deserializer configuration\n", __func__);
                return NVMEDIA_STATUS_ERROR;
        }
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: NvMediaISCSetDeviceConfig failed on enabling links, err = %d(%s)\n",
                    __func__, errno, (const char*)strerror(errno));
            return status;
        }

        status = MAX96712OneShotReset(device->iscDeserializer, 0xF);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to one shot reset", __func__);
            return status;
        }

        /* Set mapping from serializer pipeline to deserializer pipeline */
        memset(&paramsMAX96712.VideoPipelineMapping,
               0, sizeof(paramsMAX96712.VideoPipelineMapping));
        for (i = 0; i < device->sensorsNum; i++) {
            paramsMAX96712.VideoPipelineMapping.pipelineMapping[i].link = (i == 0) ? 0 :
                                                                           ((i == 1) ? 2 :
                                                                           ((i == 2) ? 1 : 3));;
            paramsMAX96712.VideoPipelineMapping.pipelineMapping[i].serializerStream = 0;
        }
        paramsMAX96712.VideoPipelineMapping.numPipelines = device->sensorsNum;
        status = MAX96712WriteParameters(device->iscDeserializer,
                                           ISC_WRITE_PARAM_CMD_MAX96712_SET_VIDEO_PIPELINE_MAPPING,
                                           sizeof(paramsMAX96712.VideoPipelineMapping),
                                           &paramsMAX96712);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to Set video pipeline selection", __func__);
            return status;
        }

        /* Override datatype on deserializer */
        memset(&paramsMAX96712.dataType, 0, sizeof(paramsMAX96712.dataType));
        paramsMAX96712.dataType = ISC_MAX96712_DATA_TYPE_RAW12;

        status = MAX96712WriteParameters(device->iscDeserializer,
                                           ISC_WRITE_PARAM_CMD_MAX96712_SET_DATA_TYPE,
                                           sizeof(paramsMAX96712.dataType),
                                           &paramsMAX96712);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to override datatype on the deserializer\n", __func__);
            return status;
        }

        /* Configure Mipi output */
        LOG_DBG("%s: Set MIPI output mode\n", __func__);
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                           ISC_CONFIG_MAX96712_CONFIG_MIPI_OUT_MODE);

        /* Disable unused pipelines that were enabled by the MIPI output configuation */
        memset(&paramsMAX96712.numPipelines, 0, sizeof(paramsMAX96712.numPipelines));
        paramsMAX96712.numPipelines = device->sensorsNum;

        status = MAX96712WriteParameters(device->iscDeserializer,
                                           ISC_WRITE_PARAM_CMD_MAX96712_ENABLE_SPECIFIC_PIPELINES,
                                           sizeof(paramsMAX96712.numPipelines),
                                           &paramsMAX96712);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to enable video pipelines\n", __func__);
            return status;
        }

        /* Set deserializer pipeline to PHY mapping and vitual channel override */
        memset(&paramsMAX96712.VC, 0, sizeof(paramsMAX96712.VC));
        paramsMAX96712.VC.dataType = ISC_MAX96712_DATA_TYPE_RAW12;
        paramsMAX96712.VC.embDataType = device->property.embDataType;
        for (i = 0; i < device->sensorsNum; i++) {
            paramsMAX96712.VC.link = i;
            status = MAX96712WriteParameters(device->iscDeserializer,
                                               ISC_WRITE_PARAM_CMD_MAX96712_PIPELINE_MAPPING,
                                               sizeof(paramsMAX96712.VC),
                                               &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to Set Deserializer mapping", __func__);
                return status;
            }
        }

        /* Set serializer defaults */
        LOG_DBG("%s: Set serializer defaults\n", __func__);
        status = MAX96759SetDefaults(device->iscBroadcastSerializer);
        if (NVMEDIA_STATUS_OK != status) {
            LOG_ERR("%s: Failed to set MAX96759 defaults\n", __func__);
            return status;
        }

        /* Configure serializer for ExtFrameSync if enabled */
        if (configParam->enableExtSync) {
            LOG_DBG("%s: Enable external frame synchronization\n", __func__);
            status = MAX96759SetDeviceConfig(device->iscBroadcastSerializer,
                                               ISC_CONFIG_MAX96759_ENABLE_EXT_FRAME_SYNC);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable exteral frame sync on serializer\n", __func__);
                return status;
            }
        }

        /* Set BPP on serializer*/
        LOG_DBG("%s: Set serializer BPP\n", __func__);
        memset(&paramsMAX96759.BitsPerPixel, 0, sizeof(paramsMAX96759.BitsPerPixel));
        paramsMAX96759.BitsPerPixel = 12;
        status = MAX96759WriteParameters(device->iscBroadcastSerializer,
                                           ISC_WRITE_PARAM_CMD_MAX96759_SET_BPP,
                                           sizeof(paramsMAX96759.BitsPerPixel),
                                           &paramsMAX96759.BitsPerPixel);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set serializer bits per pixel\n", __func__);
            return status;
        }

        /* Enable the TPG */
        if (0 == strcmp(configParam->moduleName, "ref_max96712_96759_constellation_tpg")) {
            memset(&paramsMAX96759.TPG, 0, sizeof(paramsMAX96759.TPG));
            paramsMAX96759.TPG.width  = device->property.width;
            paramsMAX96759.TPG.height = device->property.height +
                                        device->property.embLinesTop +
                                        device->property.embLinesBottom;
            paramsMAX96759.TPG.frameRate = device->property.frameRate;
            status = MAX96759WriteParameters(device->iscBroadcastSerializer,
                                               ISC_WRITE_PARAM_CMD_MAX96759_SET_TPG,
                                               sizeof(paramsMAX96759.TPG),
                                               &paramsMAX96759.TPG);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("Failed to set TPG settings\n");
                return status;
            }
        }
    }
    return NVMEDIA_STATUS_OK;
}

static
NvMediaStatus
SetupVideoLink (
    ExtImgDevParam *configParam,
    ExtImgDevice *device,
    uint32_t *remapIdx)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    ReadWriteParams96759 paramsMAX96759;
    WriteParametersParamMAX96712 paramsMAX96712;
    uint32_t i;

    if ((NULL == configParam) || (NULL == device) || (NULL == remapIdx)) {
        return NVMEDIA_STATUS_BAD_PARAMETER;
    }

    if (device->iscBroadcastSerializer) {
        /* Set EDID */
        LOG_DBG("%s: Set EDID\n", __func__);
        memset(&paramsMAX96759.EDID, 0, sizeof(paramsMAX96759.EDID));
        paramsMAX96759.EDID.width  = device->property.width;
        paramsMAX96759.EDID.height = device->property.height +
                                     device->property.embLinesTop +
                                     device->property.embLinesBottom;
        paramsMAX96759.EDID.frameRate = device->property.frameRate;
        for (i = 0; i < device->sensorsNum; i++) {
            paramsMAX96759.EDID.identifier = configParam->sensorIdentifier[i];
            status = MAX96759WriteParameters(device->iscSerializer[i],
                                               ISC_WRITE_PARAM_CMD_MAX96759_SET_EDID,
                                               sizeof(paramsMAX96759.EDID),
                                               &paramsMAX96759.EDID);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to set EDID for serializer (%u)\n", __func__, i);
                return status;
            }
        }
    }

    /* Enable FRSYNC */
    if (device->iscDeserializer && configParam->enableExtSync) {
        paramsMAX96712.FRSYNCMode.syncMode = ISC_CONFIG_MAX96712_EXTERNAL_SYNC;
        paramsMAX96712.FRSYNCMode.fps = device->property.frameRate;
        LOG_DBG("%s: Set FSYNC mode\n", __func__);
        status = MAX96712WriteParameters(device->iscDeserializer,
                                           ISC_WRITE_PARAM_CMD_MAX96712_FSYNC_MODE,
                                           sizeof(paramsMAX96712.FRSYNCMode),
                                           &paramsMAX96712);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set FSYNC mode", __func__);
            return status;
        }

        for (i = 0; i < configParam->sensorsNum; i++) {
            if (device->iscDeserializer) {
                /* Set DBL */
                paramsMAX96712.SpecificLink = i;
                LOG_DBG("%s: Set DBL\n", __func__);
                status = MAX96712WriteParameters(device->iscDeserializer,
                                                 ISC_WRITE_PARAM_CMD_MAX96712_SET_DBL,
                                                 sizeof(paramsMAX96712.SpecificLink),
                                                 &paramsMAX96712);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: Failed to set DBL, err = %d(%s)\n",
                            __func__, errno, (const char*)strerror(errno));
                    return status;
                }
            }
        }
    }
    return status;
}

static ExtImgDevice *
Init(
    ExtImgDevParam *configParam)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint32_t i = 0;
    ExtImgDevice *device = NULL;
    NvMediaISCAdvancedConfig advConfig;
    uint32_t remapIdx[MAX_AGGREGATE_IMAGES] = {0};
    ContextMAX96712 ctxMAX96712;
    ContextConstellation ctxConstellation;
    uint32_t portCfg = 0;
    uint32_t addressList[1] = {0};

    if (NULL == configParam) {
        return NULL;
    }

    if ((!configParam->enableVirtualChannels) && (configParam->sensorsNum > 1)) {
        LOG_ERR("%s: --vc_enable option has to be enabled\n", __func__);
        return NULL;
    }

    if (NULL != configParam->camMap) {
        LOG_WARN("%s: Cam map is not supported for this deserializer. Ignoring\n", __func__);
        configParam->camMap = NULL;
    }

    for (i = 0; i < MAX_AGGREGATE_IMAGES; i++) {
        remapIdx[i] = i;
    }

    memset(&ctxMAX96712, 0, sizeof(ContextMAX96712));
    device = calloc(1, sizeof(ExtImgDevice));
    if (!device) {
        LOG_ERR("%s: out of memory\n", __func__);
        return NULL;
    }

    LOG_INFO("%s: Set image device property\n", __func__);
    status = ImgDevSetProperty(GetDriver_ref_max96712_96759_constellation(),
                               configParam,
                               device);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: doesn't support the given property, check input param and image property\n",
                __func__);
        goto failed;
    }

    LOG_INFO("%s: Create root device\n", __func__);
    if (configParam->slave == NVMEDIA_FALSE) {
        portCfg = ISC_RDEV_CFG(device->property.interface,
                               configParam->enableSimulator ?
                               NVMEDIA_ISC_I2C_SIMULATOR :
                               configParam->i2cDevice);
    } else {
        portCfg = ISC_SLV_RDEV_CFG(device->property.interface,
                                   configParam->enableSimulator ?
                                   NVMEDIA_ISC_I2C_SIMULATOR :
                                   configParam->i2cDevice);
    }

    device->iscRoot = NvMediaISCRootDeviceCreate(portCfg);
    if (!device->iscRoot) {
        LOG_ERR("%s: Failed to create NvMedia ISC root device\n", __func__);
        goto failed;
    }

    if ((configParam->enableExtSync) && (device->property.dutyRatio)) {
        status = NvMediaISCRootDeviceSetSyncConfig(device->iscRoot,
                                                   device->property.frameRate,
                                                   device->property.dutyRatio);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to config Sync\n", __func__);
            goto failed;
        }

        status = NvMediaISCRootDeviceEnableSync(device->iscRoot,
                                                NVMEDIA_TRUE);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to enable Sync\n", __func__);
            goto failed;
        }
    }

    /* Delay for 50 ms in order to let deserializer power on */
    nvsleep(50000);

    if (configParam->desAddr) {
        addressList[0] = configParam->desAddr;

        /* Configure MAX96712 settings */
        ctxMAX96712.gmslMode = ISC_MAX96712_GMSL2_MODE;
        status = SetMAX96712Ctx(device,
                                configParam,
                                &ctxMAX96712);
        if (status != NVMEDIA_STATUS_OK) {
            goto failed;
        }

        ADV_CONFIG_INIT(advConfig, &ctxMAX96712);

        /* Create the deserializer device */
        LOG_INFO("%s: Create deserializer device on address 0x%x\n",
                 __func__, configParam->desAddr);
        device->iscDeserializer = NvMediaISCDeviceCreate(device->iscRoot,     /* rootDevice */
                                                         addressList,         /* addressList */
                                                         1,                   /* addrList size */
                                                         GetMAX96712Driver(), /* deviceDriver */
                                                         &advConfig);         /* advancedConfig */
        if (!device->iscDeserializer) {
            LOG_ERR("%s: Failed to create deserializer device\n", __func__);
            goto failed;
        }

        if (!(configParam->slave || configParam->enableSimulator)) {
            /* Check the deserializer is present */
            LOG_DBG("%s: Check the deserializer is present\n", __func__);
            status = MAX96712CheckPresence(device->iscDeserializer);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: The deserializer is not present\n", __func__);
                goto failed;
            }

            LOG_DBG("%s: Check deserializer limitations\n", __func__);
            status = ChkMAX96712Limitaions(device, configParam);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Check deserializer limitations failed\n", __func__);
                goto failed;
            }
        }
    }

    if (configParam->brdcstSerAddr) {
        addressList[0] = configParam->slave ? NVMEDIA_ISC_SIMULATOR_ADDRESS :
                                            configParam->brdcstSerAddr;

        /* Create the serializer device */
        LOG_INFO("%s: Create broadcast serializer device on address 0x%x\n", __func__,
                 configParam->brdcstSerAddr);

        device->iscBroadcastSerializer = NvMediaISCDeviceCreate(device->iscRoot,
                                                                addressList,
                                                                1,
                                                                GetMAX96759Driver(),
                                                                NULL);
        if (!device->iscBroadcastSerializer) {
            LOG_ERR("%s: Failed to create serializer device\n", __func__);
            goto failed;
        }
    }

    /* Assign unique address to each serializer based on the broadcast address */
    for (i = 0; i < configParam->sensorsNum; i++) {
        if (!configParam->serAddr[i]) {
            configParam->serAddr[i] = configParam->brdcstSerAddr + i + 1;
        }
    }

    /* Assign unique address to each sensor based on the broadcast address */
    for (i = 0; i < configParam->sensorsNum; i++) {
        if (!configParam->sensorAddr[i]) {
            configParam->sensorAddr[i] = configParam->brdcstSensorAddr + i + 1;
        }
    }

    for (i = 0; i < configParam->sensorsNum; i++) {
        if (configParam->serAddr[i]) {
            addressList[0] = configParam->slave ? NVMEDIA_ISC_SIMULATOR_ADDRESS :
                configParam->serAddr[i];

            /* Create the serializer device */
            LOG_INFO("%s: Create serializer device %u on address 0x%x\n", __func__, remapIdx[i],
                     configParam->serAddr[i]);
            device->iscSerializer[i] = NvMediaISCDeviceCreate(device->iscRoot,
                                                              addressList,
                                                              1,
                                                              GetMAX96759Driver(),
                                                              NULL);
            if (!device->iscSerializer[i]) {
                LOG_ERR("%s: Failed to create serializer device\n", __func__);
                goto failed;
            }
        }
    }

    /* Create simulated image sensors */
    memset(&ctxConstellation, 0, sizeof(ctxConstellation));
    ADV_CONFIG_INIT(advConfig, &ctxConstellation);

    status = GetConstellationConfigSet(configParam->resolution,
                                       device->property.inputFormatType,
                                       device->property.frameRate,
                                       &ctxConstellation.configSetIdx);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Could not find config set with given properties\n", __func__);
        goto failed;
    }

    /* Get the module ISP config */
    status = ImgDevGetModuleConfig(&ctxConstellation.moduleConfig, configParam->moduleName);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to get camera module config file name\n", __func__);
    }

    for (i = 0; i < configParam->sensorsNum; i++) {
        if (configParam->sensorAddr[i]) {
            addressList[0] = configParam->slave ? NVMEDIA_ISC_SIMULATOR_ADDRESS :
                             configParam->sensorAddr[i];

            device->iscSensor[i] = NvMediaISCDeviceCreate(device->iscRoot,
                                                          addressList,
                                                          1,
                                                          GetConstellationDriver(),
                                                          &advConfig);
        }
    }

    if (configParam->initialized || configParam->enableSimulator || configParam->slave) {
        goto init_done;
    }

    status = SetupConfigLink(configParam, device);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to setup config link\n", __func__);
        goto failed;
    }

    status = SetupVideoLink(configParam, device, remapIdx);
    if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to setup video link\n", __func__);
            goto failed;
    }

init_done:

    if ((device->iscDeserializer) &&
        !((configParam->initialized == NVMEDIA_TRUE) ||
          (configParam->enableSimulator == NVMEDIA_TRUE) ||
          (configParam->slave == NVMEDIA_TRUE))) {
        /* Check CSIPLL lock and force CSI PLL Lock if required */
        LOG_DBG("%s: Force CSI PLL Lock\n", __func__);
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                         ISC_CONFIG_MAX96712_CHECK_CSIPLL_LOCK);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to check CSIPLL lock, err = %d(%s)\n", __func__,
                    errno, (const char*)strerror(errno));
        }
    }

    memcpy(device->remapIdx, remapIdx, sizeof(device->remapIdx));
    device->simulator = configParam->enableSimulator;
    return device;

failed:
    Deinit(device);

    return NULL;
}

static NvMediaStatus
Start(
    ExtImgDevice *device)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if (NULL == device) {
        return NVMEDIA_STATUS_BAD_PARAMETER;
    }

    if (device->iscDeserializer) {
        /* Initialize replication (for slave capture) */
        LOG_DBG("%s: Initialize the replicate mode\n", __func__);
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                         ISC_CONFIG_MAX96712_ENABLE_REPLICATION);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to replicate output, err = %d(%s)\n", __func__, errno,
                    (const char*)strerror(errno));
            return status;
        }
    }

    if ((device->iscDeserializer) &&
        (device->property.phyMode == NVMEDIA_ICP_CSI_DPHY_MODE) &&
        (device->property.mipiSpeed[NVMEDIA_ICP_CSI_DPHY_MODE] >= 1500000)) {
        /* Trigger the one time initial deskew */
        LOG_DBG("%s: Trigger the one time initial deskew\n", __func__);
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                         ISC_CONFIG_MAX96712_TRIGGER_DESKEW);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to trigger the one time initial deskew, err = %d(%s)\n", __func__,
                    errno, (const char*)strerror(errno));
            return status;
        }
    }

    return NVMEDIA_STATUS_OK;
}

static NvMediaStatus
GetError(
   ExtImgDevice *device,
   uint32_t *link,
   ExtImgDevFailureType *errorType)
{
    return _GetError_max96712(device->iscDeserializer, link, errorType);
}

static ImgProperty properties[] = {
                   /*      resolution, oscMHz, fps,    duty, mipiDphy, mipiCphy, embTop, embBottom, inputFormat, pixelOrder, embDataType */
    IMG_PROPERTY_ENTRY_MIPI(1920x1208,      0,  30,    0.25,  0,  0,     24,         4,       raw12,       grbg,           0),
    IMG_PROPERTY_ENTRY_MIPI(1920x1236,      0,  30,    0.25,  0,  0,      0,         0,       raw12,       grbg,           0),
    IMG_PROPERTY_ENTRY_MIPI(3848x2168,      0,  30,    0.25,  0,  0,      6,         0,       raw12,       grbg,           0),
};

static ImgDevDriver device = {
    .name = "ref_max96712_96759",
    .Init = Init,
    .Deinit = Deinit,
    .Start = Start,
    .GetError = GetError,
    .properties = properties,
    .numProperties = sizeof(properties) / sizeof(properties[0]),
};

ImgDevDriver *
GetDriver_ref_max96712_96759_constellation(void)
{
    return &device;
}
