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

#include "ref_max96712_96705_ar0144.h"
#include "isc_max96705.h"
#include "isc_max96712.h"
#include "isc_ar0144.h"
#include "error_max96712.h"
#include "common_max96712.h"
#include "log_utils.h"
#include "dev_property.h"
#include "os_common.h"

#define OSC_MHZ 27

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

    if (!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) {
        NvMediaISCRootDeviceDestroy(device->iscRoot);
    }

    free(device);

    return;
}

static
NvMediaStatus
SetupConfigLink(
    ExtImgDevParam *configParam,
    ExtImgDevice *device)
{
    WriteParametersParamMAX96712 paramsMAX96712;
    WriteReadParametersParamMAX96705 paramsMAX96705;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t i = 0;

    if ((!configParam) || (!device))
        return NVMEDIA_STATUS_ERROR;

    if ((device->iscDeserializer) && (device->iscBroadcastSerializer)) {
        /* 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, err = %d(%s)\n", __func__,
                    errno, (const char*)strerror(errno));
            return status;
        }

        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__);
            return status;
        }

        /* Select which I2C port to forward over the link */
        for (i = 0u; i < configParam->sensorsNum; 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 %d, err = %d(%s)\n", __func__,
                        i, errno, (const char*)strerror(errno));
                return status;
            }
        }

        /* Set GMSL mode and link speed */
        LOG_DBG("%s: Set GMSL mode and link speed\n", __func__);
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                         ISC_CONFIG_MAX96712_SET_GMSL1_MODE);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set GMSL mode and link speed, err = %d(%s)\n", __func__,
                    errno, (const char*)strerror(errno));
            return status;
        }
    }


    for (i = 0u; i < configParam->sensorsNum; i++) {
        if (device->iscDeserializer) {
            /* Enable specific link */
            paramsMAX96712.SpecificLink = i;
            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, err = %d(%s)\n",
                        __func__, i, errno, (const char*)strerror(errno));
                return status;
            }
            nvsleep(5000);

            paramsMAX96712.stepHIM.link = i;
            paramsMAX96712.stepHIM.step = 0;

            LOG_DBG("%s: Enable GMSL1 HIM mode - step 0\n", __func__);
            status = MAX96712WriteParameters(device->iscDeserializer,
                                             ISC_WRITE_PARAM_CMD_MAX96712_GMSL1_HIM_ENABLED,
                                             sizeof(paramsMAX96712.stepHIM),
                                             &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable GMSL1 HIM mode - step 1, err = %d(%s)\n",
                                                           __func__,
                                                           errno,
                                                           (const char*)strerror(errno));
                return status;
            }
        }

        if (device->iscBroadcastSerializer) {
            LOG_DBG("%s: Turn off HIM in MAX96705\n", __func__);
            status = MAX96705SetDeviceConfig(device->iscBroadcastSerializer,
                                             ISC_CONFIG_MAX96705_DISABLE_HIM_MODE);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to turn off HIM mode in MAX96705, err = %d(%s)\n", __func__,
                        errno, (const char*)strerror(errno));
                return status;
            }
        }

        if (device->iscDeserializer) {
            paramsMAX96712.stepHIM.link = i;
            paramsMAX96712.stepHIM.step = 1;

            LOG_DBG("%s: Enable GMSL1 HIM mode - step 1\n", __func__);
            status = MAX96712WriteParameters(device->iscDeserializer,
                                             ISC_WRITE_PARAM_CMD_MAX96712_GMSL1_HIM_ENABLED,
                                             sizeof(paramsMAX96712.stepHIM),
                                             &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable GMSL1 HIM mode - step 2, err = %d(%s)\n",
                                                           __func__,
                                                           errno,
                                                           (const char*)strerror(errno));
                return status;
            }
        }

        if (device->iscBroadcastSerializer) {
            LOG_DBG("%s: Enable config link in MAX96705\n", __func__);
            status = MAX96705SetDeviceConfig(device->iscBroadcastSerializer,
                                             ISC_CONFIG_MAX96705_ENABLE_REVERSE_CHANNEL);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable config link in MAX96705, err = %d(%s)\n", __func__,
                        errno, (const char*)strerror(errno));
                return status;
            }
        }

        if (device->iscDeserializer) {
            paramsMAX96712.SpecificLink = i;
            LOG_DBG("%s: Disable forward channel\n", __func__);
            status = MAX96712WriteParameters(device->iscDeserializer,
                                             ISC_WRITE_PARAM_CMD_MAX96712_DISABLE_FORWARD_CHANNEL,
                                             sizeof(paramsMAX96712.SpecificLink),
                                             &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to disable forward channel, err = %d(%s)\n",
                                                           __func__,
                                                           errno,
                                                           (const char*)strerror(errno));
                return status;
            }
        }

        if (device->iscBroadcastSerializer) {
            LOG_DBG("%s: Turn on HIM\n", __func__);
            status = MAX96705SetDeviceConfig(device->iscBroadcastSerializer,
                                             ISC_CONFIG_MAX96705_ENABLE_HIM_MODE);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to turn on HIM mode, err = %d(%s)\n", __func__,
                                                           errno,
                                                           (const char*)strerror(errno));
                return status;
            }
        }

        if (device->iscDeserializer) {
            paramsMAX96712.stepHIM.link = i;
            paramsMAX96712.stepHIM.step = 2;

            LOG_DBG("%s: Enable GMSL1 HIM mode - step 2\n", __func__);
            status = MAX96712WriteParameters(device->iscDeserializer,
                                             ISC_WRITE_PARAM_CMD_MAX96712_GMSL1_HIM_ENABLED,
                                             sizeof(paramsMAX96712.stepHIM),
                                             &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable GMSL1 HIM mode - step 3, err = %d(%s)\n",
                                                           __func__,
                                                           errno,
                                                           (const char*)strerror(errno));
                return status;
            }

            paramsMAX96712.SpecificLink = i;
            LOG_DBG("%s: Enable forward channel\n", __func__);
            status = MAX96712WriteParameters(device->iscDeserializer,
                                             ISC_WRITE_PARAM_CMD_MAX96712_ENABLE_FORWARD_CHANNEL,
                                             sizeof(paramsMAX96712.SpecificLink),
                                             &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable forward channel, err = %d(%s)\n",
                                                           __func__,
                                                           errno,
                                                           (const char*)strerror(errno));
                return status;
            }

            paramsMAX96712.SpecificLink = i;
            LOG_DBG("%s: Enable packet based control channel\n", __func__);
            status = MAX96712WriteParameters(device->iscDeserializer,
                                             ISC_WRITE_PARAM_CMD_MAX96712_ENABLE_PACKET_BASED_CONTROL_CHANNEL,
                                             sizeof(paramsMAX96712.SpecificLink),
                                             &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable packet based control channel, err = %d(%s)\n",
                                                           __func__,
                                                           errno,
                                                           (const char*)strerror(errno));
                return status;
            }

            paramsMAX96712.SpecificLink = i;
            LOG_DBG("%s: Enable DE\n", __func__);
            status = MAX96712WriteParameters(device->iscDeserializer,
                                             ISC_WRITE_PARAM_CMD_MAX96712_DISABLE_DE,
                                             sizeof(paramsMAX96712.SpecificLink),
                                             &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable DE, err = %d(%s)\n",
                                                           __func__,
                                                           errno,
                                                           (const char*)strerror(errno));
                return status;
            }
        }

        if (device->iscDeserializer) {
            status = MAX96712CheckLink(device->iscDeserializer,
                                         i, ISC_MAX96712_GMSL1_CONFIG_LINK_LOCK, NVMEDIA_TRUE);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s %d: Failed to check config link lock %d, err = %d(%s)\n", __func__, __LINE__, i,
                        errno, (const char*)strerror(errno));
                return status;
            }

            paramsMAX96712.SpecificLink = i;
            LOG_DBG("%s: Enable periodic AEQ\n", __func__);
            status = MAX96712WriteParameters(device->iscDeserializer,
                                             ISC_WRITE_PARAM_CMD_MAX96712_ENABLE_PERIODIC_AEQ,
                                             sizeof(paramsMAX96712.SpecificLink),
                                             &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable periodic AEQ, err = %d(%s)\n",
                                                           __func__,
                                                           errno,
                                                           (const char*)strerror(errno));
                return status;
            }
        }

        if (device->iscBroadcastSerializer) {
            /* Set PreEmphasis */
            paramsMAX96705.preemp = ISC_SET_PREEMP_MAX96705_PLU_3_3DB;

            LOG_DBG("%s: Set all serializer Preemphasis setting\n", __func__);
            status = MAX96705WriteParameters(device->iscBroadcastSerializer,
                                             ISC_WRITE_PARAM_CMD_MAX96705_SET_PREEMP,
                                             sizeof(paramsMAX96705.preemp),
                                             &paramsMAX96705);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to set Preemphasis setting\n", __func__);
                return status;
            }
        }

        if (device->iscBroadcastSensor) {
            /* Set address translation for the serializer to control individual serializer */
            if (configParam->serAddr[i] &&
                device->iscBroadcastSerializer &&
                device->iscSerializer[i]) {

                /* Check serializer is present */
                LOG_DBG("%s: Check serializer is present\n", __func__);
                status = MAX96705CheckPresence(device->iscBroadcastSerializer);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: Serializer(%d) device is not present, err = %d(%s)\n",
                            __func__, i, errno, (const char*)strerror(errno));
                    return status;
                }

                /* Set unique address with broadcast address */
                paramsMAX96705.DeviceAddress.address = configParam->serAddr[i];
                status = MAX96705WriteParameters(device->iscBroadcastSerializer,
                            ISC_WRITE_PARAM_CMD_MAX96705_SET_DEVICE_ADDRESS,
                            sizeof(paramsMAX96705.DeviceAddress),
                            &paramsMAX96705);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: Failed to set serializer device %d address, err = %d(%s)\n",
                            __func__, configParam->serAddr[i], errno,
                            (const char*)strerror(errno));
                    return status;
                }

                /* Set address translation for the serializer */
                paramsMAX96705.Translator.source = configParam->brdcstSerAddr;
                paramsMAX96705.Translator.destination = configParam->serAddr[i];
                status = MAX96705WriteParameters(device->iscSerializer[i],
                                ISC_WRITE_PARAM_CMD_MAX96705_SET_TRANSLATOR_A,
                                sizeof(paramsMAX96705.Translator),
                                &paramsMAX96705);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: Address translation setup failed, err = %d(%s)\n",
                            __func__, errno, (const char*)strerror(errno));
                    return status;
                }
            }

            if (device->iscSensor[i]) {
                /* Set address translation for the sensor to control individual sensor */
                if (configParam->sensorAddr[i] && device->iscBroadcastSerializer) {
                    /* Check sensor is present */
                    LOG_DBG("%s: Check sensor is present\n", __func__);
                    status = AR0144CheckPresence(device->iscBroadcastSensor);
                    if (status != NVMEDIA_STATUS_OK) {
                        LOG_ERR("%s: Image sensor(%d) device is not present, err = %d(%s)\n",
                                __func__, i, errno, (const char*)strerror(errno));
                        return status;
                    }

                    /* Set address translation for the sensor */
                    paramsMAX96705.Translator.source = configParam->sensorAddr[i];
                    paramsMAX96705.Translator.destination = configParam->brdcstSensorAddr;
                    LOG_INFO("%s: Translate image sensor device addr %x to %x\n", __func__,
                        paramsMAX96705.Translator.source, paramsMAX96705.Translator.destination);
                    status = MAX96705WriteParameters(device->iscSerializer[i],
                                    ISC_WRITE_PARAM_CMD_MAX96705_SET_TRANSLATOR_B,
                                    sizeof(paramsMAX96705.Translator),
                                    &paramsMAX96705);
                    if (status != NVMEDIA_STATUS_OK) {
                        LOG_ERR("%s: Address translation setup failed, err = %d(%s)\n",
                                __func__, errno, (const char*)strerror(errno));
                        return status;
                    }
                }
            }
        }
    }

    return status;
}

static
NvMediaStatus
SetupVideoLink (
    ExtImgDevParam *configParam,
    ExtImgDevice *device)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint32_t i, pclk = 0;
    ConfigSetMax96712SyncMode intSyncMode = ISC_CONFIG_MAX96712_OSC_MANUAL_SYNC;
    ReadParametersParamMAX96712 readParamsMAX96712;
    WriteParametersParamMAX96712 paramsMAX96712;
    WriteReadParametersParamAR0144 paramsAR0144;
    RevisionMAX96712 rev = ISC_MAX96712_INVALID_REV;
    int32_t config;
    NvMediaBool enablePG = NVMEDIA_FALSE;

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

        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 set data type\n", __func__);
            return status;
        }

        /* Update mapping table */
        for (i = 0; i < configParam->sensorsNum; i++) {
            paramsMAX96712.VC.embDataType = device->property.embDataType;
            paramsMAX96712.VC.link = i;
            paramsMAX96712.VC.dataType = ISC_MAX96712_DATA_TYPE_RAW12;
            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;
            }
        }
    }

    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 1\n", __func__);
            status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                    ISC_CONFIG_MAX96712_ENABLE_LINKS_01);
            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\n", __func__);
        return status;
    }

    /* Check config link lock  */
    for (i = 0; i < device->sensorsNum; i++) {
        status = MAX96712CheckLink(device->iscDeserializer,
                                   i, ISC_MAX96712_GMSL1_CONFIG_LINK_LOCK, NVMEDIA_FALSE);
        if (status != NVMEDIA_STATUS_OK) {
            /* Oneshot reset */
            LOG_DBG("%s: Assert one-shot rest\n", __func__);
            status = MAX96712OneShotReset(device->iscDeserializer, (1 << i));
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to set DBL, err = %d(%s)\n",
                        __func__, errno, (const char*)strerror(errno));
                return status;
            }

            status = MAX96712CheckLink(device->iscDeserializer,
                                       i, ISC_MAX96712_GMSL1_CONFIG_LINK_LOCK, NVMEDIA_TRUE);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to check config link lock, err = %d(%s)\n",
                        __func__, errno, (const char*)strerror(errno));
                return status;
            }
        }
    }

    status = GetAR0144ConfigSet(configParam->resolution,
                                    configParam->inputFormat,
                                    device->property.frameRate,
                                    &config);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to get config set\n", __func__);
        return status;
    }

    if (device->iscBroadcastSensor) {
        /* Set sensor defaults after software reset */
        LOG_DBG("%s: Set AR0144 defaults\n", __func__);
        status = AR0144SetDefaults(device->iscBroadcastSensor);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set AR0144 defaults\n", __func__);
            return status;
        }

        /* TODO: Add a method to expose/control this flag in order to enable
         * pattern generator from an application */
        if (enablePG == NVMEDIA_TRUE) {
            status = AR0144SetDeviceConfig(device->iscBroadcastSensor,
                                               ISC_CONFIG_AR0144_ENABLE_PG);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable pattern generator\n", __func__);
                return status;
            }
        }

        /* Set sensor configuration */
        LOG_DBG("%s: Set sensor configuration (%u)\n", __func__, config);
        status = AR0144SetDeviceConfig(device->iscBroadcastSensor,
                                           config);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set sensor configuration\n", __func__);
            return status;
        }

        /* Read PCLK frequency */
        LOG_DBG("%s: Read PCLK frequency\n", __func__);
        status = AR0144ReadParameters(device->iscBroadcastSensor,
                                          ISC_READ_PARAM_CMD_AR0144_PCLK,
                                          sizeof(paramsAR0144.pclk),
                                          &paramsAR0144);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to read PCLK frequency\n", __func__);
            return status;
        }

        pclk = paramsAR0144.pclk;
        LOG_DBG("%s: PCLK frequency : %dHz\n", __func__, pclk);
    }

    if (device->iscDeserializer) {
        /* 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;

        if ((rev == ISC_MAX96712_REV_1) ||
            (rev == ISC_MAX96712_REV_2)) {
            intSyncMode = ISC_CONFIG_MAX96712_MANUAL_SYNC;
        } else {
            intSyncMode = ISC_CONFIG_MAX96712_OSC_MANUAL_SYNC;
        }

        /* Enable FRSYNC */
        paramsMAX96712.FRSYNCMode.syncMode = (configParam->enableExtSync) ?
                                                 ISC_CONFIG_MAX96712_EXTERNAL_SYNC :
                                                 intSyncMode;
        paramsMAX96712.FRSYNCMode.pclk = pclk;
        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;
}

static ExtImgDevice *
Init(
    ExtImgDevParam *configParam)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint32_t i = 0;
    ExtImgDevice *device = NULL;
    NvMediaISCAdvancedConfig advConfig;
    WriteParametersParamMAX96712 paramsMAX96712;
    ReadParametersParamMAX96712 readParamsMAX96712;
    ContextMAX96712 ctxMAX96712 = {};
    uint32_t remapIdx[MAX_AGGREGATE_IMAGES] = {0};
    uint32_t portCfg = 0;
    uint32_t addressList[1] = {0};
    ContextAR0144 ctxAR0144;

    if (!configParam)
        return NULL;

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

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

    if (configParam->enableExtSync) {
        configParam->reqFrameRate = 30;
        LOG_INFO("%s:  External sync is enabled 30fps set\n", __func__);
    }

    LOG_INFO("%s: Set image device property\n", __func__);
    status = ImgDevSetProperty(GetDriver_ref_max96712_96705_ar0144(),
                                        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;
    }

    memset(&ctxAR0144, 0, sizeof(ContextAR0144));
    status = ImgDevGetModuleConfig(&ctxAR0144.moduleConfig, configParam->moduleName);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to get camera module config file name\n", __func__);
        goto failed;
    }

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

        /* Configure MAX96712 settings */
        ctxMAX96712.gmslMode = ISC_MAX96712_GMSL1_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,                      /* size of address list */
                            GetMAX96712Driver(),    /* deviceDriver */
                            &advConfig);            /* advancedConfig */
        if (!device->iscDeserializer) {
            LOG_ERR("%s: Failed to create deserializer device\n", __func__);
            goto failed;
        }
    }

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

        /* Create broadcast serializer device */
        LOG_INFO("%s: Create broadcast serializer device on address 0x%x\n", __func__,
                          configParam->brdcstSerAddr);
        device->iscBroadcastSerializer = NvMediaISCDeviceCreate(
                          device->iscRoot,
                          addressList,
                          1,
                          GetMAX96705Driver(),
                          NULL);
        if (!device->iscBroadcastSerializer) {
            LOG_ERR("%s: Failed to create broadcast serializer device\n", __func__);
            goto failed;
        }
    }

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

        ADV_CONFIG_INIT(advConfig, &ctxAR0144);

        /* Create the image sensor device */
        LOG_INFO("%s: Create broadcast sensor device on address 0x%x\n", __func__,
                         configParam->brdcstSensorAddr);
        device->iscBroadcastSensor = NvMediaISCDeviceCreate(
                                        device->iscRoot,
                                        addressList,
                                        1,
                                        GetAR0144Driver(),
                                        &advConfig);
        if (!device->iscBroadcastSensor) {
            LOG_ERR("%s: Failed to create broadcast sensor 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__, i,
                        configParam->serAddr[i]);
            device->iscSerializer[i] = NvMediaISCDeviceCreate(
                        device->iscRoot,
                        addressList,
                        1,
                        GetMAX96705Driver(),
                        NULL);
            if (!device->iscSerializer[i]) {
                LOG_ERR("%s: Failed to create serializer device\n", __func__);
                goto failed;
            }
        }

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

            ADV_CONFIG_INIT(advConfig, &ctxAR0144);

            /* Create the image sensor device */
            LOG_INFO("%s: Create image sensor device %u on address 0x%x\n", __func__, i,
                        configParam->sensorAddr[i]);
            device->iscSensor[i] = NvMediaISCDeviceCreate(
                                        device->iscRoot,
                                        addressList,
                                        1,
                                        GetAR0144Driver(),
                                        &advConfig);
            if (!device->iscSensor[i]) {
                LOG_ERR("%s: Failed to create image sensor device\n", __func__);
                goto failed;
            }
        }
    }

    if ((configParam->initialized == NVMEDIA_TRUE) ||
        (configParam->enableSimulator == NVMEDIA_TRUE) ||
        (configParam->slave == NVMEDIA_TRUE)) {
        return device;
    }

    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);
    if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to setup video link\n", __func__);
            goto failed;
    }


    if (device->iscBroadcastSensor) {
        LOG_DBG("%s: Enable sensor streaming\n", __func__);
        status = AR0144SetDeviceConfig(device->iscBroadcastSensor,
                                        ISC_CONFIG_AR0144_ENABLE_STREAMING);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to enable sensor streaming\n", __func__);
            goto failed;
        }
        nvsleep(10000);
    }

    for (i = 0; i < configParam->sensorsNum; i++) {
        if (device->iscBroadcastSerializer) {
            /* Enable PCLK In */
            LOG_DBG("%s: Enable PCLK In\n", __func__);
            status = MAX96705SetDeviceConfig(device->iscSerializer[i],
                                               ISC_CONFIG_MAX96705_PCLKIN);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable PCLK In, err = %d(%s)\n", __func__,
                        errno, (const char*)strerror(errno));
                goto failed;
            }
        }

        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));
                goto failed;
            }
        }

        if (device->iscBroadcastSerializer) {
            /* Enable each serial link */
            LOG_DBG("%s: Enable serial link\n", __func__);
            status = MAX96705SetDeviceConfig(device->iscSerializer[i],
                                             ISC_CONFIG_MAX96705_ENABLE_SERIAL_LINK);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to enable serial link, err = %d(%s)\n", __func__,
                        errno, (const char*)strerror(errno));
                goto failed;
            }

            nvsleep(40000);
        }
    }

    /* TODO : Incompleted EMB data received before getting the first frame.
     *        This will cause EMBED_SHORT_FRAME errors and nvmimg_cap can't
     *        recover it. This is a delay temporally and will be removed later.
     */
    nvsleep(100000);  /* wait 100ms */

    if (device->iscDeserializer) {
        /* Check video link lock  */
        for (i = 0; i < device->sensorsNum; i++) {
            status = MAX96712CheckLink(device->iscDeserializer,
                                       i, ISC_MAX96712_VIDEO_LINK_LOCK, NVMEDIA_FALSE);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_DBG("%s: Failed to check video link lock %d, err = %d(%s)\n", __func__,
                                                                                  i,
                                                                                  errno,
                                                                                  (const char*)strerror(errno));
                /* The link was up and the sensor was configured properly
                   Assert Onehot reset to make sure the data path cleared
                   and the setting is properly applied */
                /* Oneshot reset */
                LOG_DBG("%s: Assert one-shot rest\n", __func__);
                status = MAX96712OneShotReset(device->iscDeserializer, GET_MAX96712_GMSL_LINK(i));
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: Failed to assert one shot reset, err = %d(%s)\n", __func__,
                                                                     errno,
                                                                     (const char*)strerror(errno));
                    goto failed;
                }

                /* Check video link lock again */
                LOG_DBG("%s: Check video link lock\n", __func__);
                status = MAX96712CheckLink(device->iscDeserializer,
                                           i, ISC_MAX96712_VIDEO_LINK_LOCK, NVMEDIA_TRUE);
                if (status != NVMEDIA_STATUS_OK) {
                    LOG_ERR("%s: Failed to check video lock, err = %d(%s)\n", __func__,
                                                                                errno,
                                                                                (const char*)strerror(errno));
                    goto failed;
                }
            }

            /* Clear the packet based control channel CRC error. GMSL1 only */
            readParamsMAX96712.ErrorStatus.link = i;
            status = MAX96712ReadParameters(device->iscDeserializer,
                                              ISC_READ_PARAM_CMD_MAX96712_CONTROL_CHANNEL_CRC_ERROR,
                                              sizeof(readParamsMAX96712.ErrorStatus),
                                              &readParamsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to clear control channel crc error(%d)\n", __func__, i);
                goto failed;
            }
        }

        if (!((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));
                goto failed;
            }
        }
    }

    if (device->iscBroadcastSerializer) {
        /* Set the remote-i2c-master timeout to never in MAX96705 to prevent timeout in
         * remote-i2c-master while transferring i2c data from the actual i2c master (Bug 1802338, 200419005) */
        status = MAX96705SetDeviceConfig(device->iscBroadcastSerializer,
                                         ISC_CONFIG_MAX96705_SET_MAX_REMOTE_I2C_MASTER_TIMEOUT);
        if(status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set the max remote-i2c-master timeout, err = %d(%s)\n", __func__,
                                                               errno, (const char*)strerror(errno));
            goto failed;
        }
    }

    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 (!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 status;
}

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(1280x800,  OSC_MHZ, 60, 0.0, 0, 0,      2,       2,       raw12,       grbg,       0), /* default resolution and frame rate */
    IMG_PROPERTY_ENTRY_MIPI(1280x800,  OSC_MHZ, 30, 0.0, 0, 0,      2,       2,       raw12,       grbg,       0), /* default resolution and 30 fps */
};

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

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