/*
 * 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 "CNvMTransportLink_Max96712_9295.hpp"
#include "platform/CNvMPlatform.hpp"

#include <string>

// Include necessary CDI driver headers
extern "C" {
#include "cdi_max96712.h"
#include "cdi_max9295.h"
}

namespace nvsipl {

static GPIOTypeMAX9295
getMAX9295GPIO(
    uint8_t gpioInd)
{
    GPIOTypeMAX9295 gpio = CDI_MAX9295_GPIO_TYPE_INVALID;

    switch (gpioInd) {
        case 0:
            gpio = CDI_MAX9295_GPIO_TYPE_MFP0;
            break;
        case 1:
            gpio = CDI_MAX9295_GPIO_TYPE_MFP1;
            break;
        case 2:
            gpio = CDI_MAX9295_GPIO_TYPE_MFP2;
            break;
        case 3:
            gpio = CDI_MAX9295_GPIO_TYPE_MFP3;
            break;
        case 4:
            gpio = CDI_MAX9295_GPIO_TYPE_MFP4;
            break;
        case 5:
            gpio = CDI_MAX9295_GPIO_TYPE_MFP5;
            break;
        case 6:
            gpio = CDI_MAX9295_GPIO_TYPE_MFP6;
            break;
        case 7:
            gpio = CDI_MAX9295_GPIO_TYPE_MFP7;
            break;
        case 8:
            gpio = CDI_MAX9295_GPIO_TYPE_MFP8;
            break;
        default:
            LOG_ERR("Invalid Max9295 GPIO pin %u\n", gpioInd);
    }

    return gpio;
}

void CNvMTransportLink_Max96712_9295::DumpLinkParams()
{
    LOG_INFO("Link parameters\n");
    LOG_INFO("Link Index: %u \n", m_oLinkParams.ulinkIndex);
    LOG_INFO("Broadcast serializer addr: 0x%x \n", m_oLinkParams.uBrdcstSerAddr);
    LOG_INFO("Serializer addr: 0x%x \n", m_oLinkParams.uSerAddr);

    auto &sensor = m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty;
    LOG_INFO("\nBroadcast sensor addr: 0x%x \n", sensor.uBrdcstSensorAddrs);
    LOG_INFO("Sensor addr: 0x%x \n", sensor.uSensorAddrs);
    LOG_INFO("VCID: %u \n", sensor.uVCID);
    LOG_INFO("Embedded data type: %s \n", sensor.bEmbeddedDataType ? "true" : "false");
    LOG_INFO("Trigger mode sync: %s \n", sensor.bEnableTriggerModeSync ? "true" : "false");
    LOG_INFO("Frame rate: %.2f fps \n", sensor.fFrameRate);

    auto broadEEPROMAddr = m_oLinkParams.moduleConnectionProperty.brdcstEepromAddr;
    LOG_INFO("\nBroadcast EEPROM addr: 0x%x \n", broadEEPROMAddr);

    auto EEPROMAddr = m_oLinkParams.moduleConnectionProperty.eepromAddr;
    LOG_INFO("EEPROM addr: 0x%x \n", EEPROMAddr);

    LOG_INFO("Simulator mode: %u \n", m_oLinkParams.bEnableSimulator);
    LOG_INFO("Slave mode: %u \n", m_oLinkParams.bSlave);
}

SIPLStatus CNvMTransportLink_Max96712_9295::SetupAddressTranslations(DevBlkCDIDevice* brdcstSerCDI)
{
    NvMediaStatus nvmStatus = NVMEDIA_STATUS_OK;
    ReadWriteParamsMAX9295 paramsMAX9295 = {};

    // Check serializer is present
    LOG_INFO("Check broadcast serializer is present\n");
    nvmStatus = MAX9295CheckPresence(brdcstSerCDI);
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "MAX9295CheckPresence(brdcstSerCDI)");

    // Set address translation for the sensor
    paramsMAX9295.Translator.source =
        m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.uSensorAddrs;
    paramsMAX9295.Translator.destination =
        m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.uBrdcstSensorAddrs;
    LOG_INFO("Translate image sensor device addr %x to %x\n",
              paramsMAX9295.Translator.source, paramsMAX9295.Translator.destination);
    nvmStatus = MAX9295WriteParameters(brdcstSerCDI,
                                       CDI_WRITE_PARAM_CMD_MAX9295_SET_TRANSLATOR_A,
                                       sizeof(paramsMAX9295.Translator),
                                       &paramsMAX9295);
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX96705_SET_TRANSLATOR_A");

    // Set address translation for the eeprom
    if (m_oLinkParams.moduleConnectionProperty.eepromAddr != UINT8_MAX) {
        paramsMAX9295.Translator.source = m_oLinkParams.moduleConnectionProperty.eepromAddr;
        paramsMAX9295.Translator.destination = m_oLinkParams.moduleConnectionProperty.brdcstEepromAddr;
        LOG_INFO("Translate eeprom device addr %x to %x\n",
                            paramsMAX9295.Translator.source, paramsMAX9295.Translator.destination);
        nvmStatus = MAX9295WriteParameters(brdcstSerCDI,
                                           CDI_WRITE_PARAM_CMD_MAX9295_SET_TRANSLATOR_B,
                                           sizeof(paramsMAX9295.Translator),
                                           &paramsMAX9295);
        CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX96705_SET_TRANSLATOR_B");
    }

    // Set unique address with broadcast address
    LOG_INFO("Set unique address\n");
    paramsMAX9295.DeviceAddress.address = m_oLinkParams.uSerAddr;
    nvmStatus = MAX9295WriteParameters(brdcstSerCDI,
                                       CDI_WRITE_PARAM_CMD_MAX9295_SET_DEVICE_ADDRESS,
                                       sizeof(paramsMAX9295.DeviceAddress.address),
                                       &paramsMAX9295);
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX96705_SET_DEVICE_ADDRESS");

    return NVSIPL_STATUS_OK;
}

// This will do the init for 1 maxim deser and up to MAX_LINKS_PER_DESER maxim serializers.
SIPLStatus CNvMTransportLink_Max96712_9295::Init(DevBlkCDIDevice* brdcstSerCDI, uint8_t linkMask, bool groupInitProg)
{
    NvMediaStatus nvmStatus = NVMEDIA_STATUS_OK;
    SIPLStatus status = NVSIPL_STATUS_OK;
    WriteParametersParamMAX96712 paramsMAX96712 = {};
    LinkMAX96712 link = GetMAX96712Link(m_oLinkParams.ulinkIndex);
    ReadWriteParamsMAX9295 paramsMAX9295 = {};
    DataTypeMAX96712 dataTypeMAX96712 = CDI_MAX96712_DATA_TYPE_INVALID;
    DataTypeMAX9295 dataTypeMAX9295 = CDI_MAX9295_DATA_TYPE_INVALID;
    LinkPipelineMapMAX96712 *pipeLineMap = NULL;
    DevBlkCDIDevice *deserializerCDI = m_oLinkParams.pDeserCDIDevice;
    DevBlkCDIDevice *serCDI = m_oLinkParams.pSerCDIDevice;

    LOG_INFO("Initializing link %u\n", m_oLinkParams.ulinkIndex);

    switch (m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.inputFormat.inputFormatType)
    {
        case NVMEDIA_IMAGE_CAPTURE_INPUT_FORMAT_TYPE_RAW10:
            dataTypeMAX96712 = CDI_MAX96712_DATA_TYPE_RAW10;
            dataTypeMAX9295 = CDI_MAX9295_DATA_TYPE_RAW10;
            break;

        case NVMEDIA_IMAGE_CAPTURE_INPUT_FORMAT_TYPE_RAW12:
            dataTypeMAX96712 = CDI_MAX96712_DATA_TYPE_RAW12;
            dataTypeMAX9295 = CDI_MAX9295_DATA_TYPE_RAW12;
            break;

        case NVMEDIA_IMAGE_CAPTURE_INPUT_FORMAT_TYPE_RAW16:
            dataTypeMAX96712 = CDI_MAX96712_DATA_TYPE_RAW16;
            dataTypeMAX9295 = CDI_MAX9295_DATA_TYPE_RAW16;
            break;

        default:
            LOG_ERR("Input format not supported\n");
            return NVSIPL_STATUS_NOT_SUPPORTED;
    }

    DumpLinkParams();

    if (m_oLinkParams.bSlave or m_oLinkParams.bEnableSimulator) {
        return NVSIPL_STATUS_OK;
    }

    LOG_INFO("Check config link lock \n");
    nvmStatus = MAX96712CheckLink(deserializerCDI,
                                  GetMAX96712Link(m_oLinkParams.ulinkIndex),
                                  CDI_MAX96712_LINK_LOCK_GMSL2,
                                  true);
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "DevBlkCDICheckLink(CDI_MAX96712_GMSL2_LINK_LOCK)");

    // Check presence of serializer and setup address translations
    LOG_INFO("Setup address translations\n");
    status = SetupAddressTranslations(brdcstSerCDI);
    CHK_NVSIPLEERROR_AND_RETURN(status, "SetupAddressTranslations")

    CNvMCameraModule::ConnectionProperty::SensorConnectionProperty::SensorReset sensorReset =
            m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.sensorReset;
    if (sensorReset.isNeeded) {
        LOG_INFO("Release sensor reset\n");
        paramsMAX9295.GPIOOutp.gpioInd = getMAX9295GPIO(sensorReset.pinNum);
        paramsMAX9295.GPIOOutp.level = sensorReset.releaseResetLevel;
        nvmStatus = MAX9295WriteParameters(serCDI,
                                           CDI_WRITE_PARAM_CMD_MAX9295_SET_GPIO_OUTPUT,
                                           sizeof(paramsMAX9295.GPIOOutp),
                                           &paramsMAX9295);
        CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX9295_SET_GPIO_OUTPUT");
    }

    CNvMCameraModule::ConnectionProperty::SensorConnectionProperty::RefClock refClock =
            m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.refClock;
    if (refClock.isNeeded) {
        LOG_INFO("Enable ref clock\n");
        paramsMAX9295.RefClkGPIO.gpioInd = getMAX9295GPIO(refClock.pinNum); /* set source GPIO */
        paramsMAX9295.RefClkGPIO.enableRClk = true;
        nvmStatus = MAX9295WriteParameters(serCDI,
                                           CDI_WRITE_PARAM_CMD_MAX9295_ENABLE_REF_CLOCK,
                                           sizeof(paramsMAX9295.RefClkGPIO),
                                           &paramsMAX9295);
        CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX9295_ENABLE_REF_CLOCK");
    }

    LOG_INFO("CDI_WRITE_PARAM_CMD_MAX9295_SET_FSYNC_GPIO\n");
    paramsMAX9295.FSyncGPIO.gpioInd = getMAX9295GPIO(m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.frameSync.pinNum);
    paramsMAX9295.FSyncGPIO.rxID = CDI_MAX96712_GPIO_2;
    nvmStatus = MAX9295WriteParameters(serCDI,
                                       CDI_WRITE_PARAM_CMD_MAX9295_SET_FSYNC_GPIO,
                                       sizeof(paramsMAX9295.FSyncGPIO),
                                       &paramsMAX9295);
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX9295_SET_FSYNC_GPIO");

    paramsMAX9295.ConfigPhy.mapping.enableMapping = false;
    paramsMAX9295.ConfigPhy.numDataLanes = 4;
    if (m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.phyLanes.isLaneSwapNeeded) {
        paramsMAX9295.ConfigPhy.mapping.phy1_d0 = 3;
        paramsMAX9295.ConfigPhy.mapping.phy1_d1 = 2;
        paramsMAX9295.ConfigPhy.mapping.phy2_d0 = 1;
        paramsMAX9295.ConfigPhy.mapping.phy2_d1 = 0;
        paramsMAX9295.ConfigPhy.mapping.enableMapping = true;
    } else if (m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.phyLanes.isTwoLane) {
        paramsMAX9295.ConfigPhy.numDataLanes = 2;
    }

    LOG_INFO("CDI_WRITE_PARAM_CMD_MAX9295_CONFIG_PHY\n");
    nvmStatus = MAX9295WriteParameters(serCDI,
                                       CDI_WRITE_PARAM_CMD_MAX9295_CONFIG_PHY,
                                       sizeof(paramsMAX9295.ConfigPhy),
                                       &paramsMAX9295);
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX9295_CONFIG_PHY");

    paramsMAX96712.DoublePixelMode.link = link;
    paramsMAX96712.DoublePixelMode.dataType = dataTypeMAX96712;
    paramsMAX96712.DoublePixelMode.embDataType = m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.bEmbeddedDataType;
    nvmStatus = MAX96712WriteParameters(deserializerCDI,
                                        CDI_WRITE_PARAM_CMD_MAX96712_ENABLE_DOUBLE_PIXEL_MODE,
                                        sizeof(paramsMAX96712.DoublePixelMode),
                                        &paramsMAX96712);
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX96712_ENABLE_DOUBLE_PIXEL_MODE");

    LOG_INFO("CDI_WRITE_PARAM_CMD_MAX9295_CONFIG_VIDEO_PIPELINES\n");
    paramsMAX9295.ConfigVideoPipeline.dataType = dataTypeMAX9295;
    paramsMAX9295.ConfigVideoPipeline.embDataType = m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.bEmbeddedDataType;
    nvmStatus = MAX9295WriteParameters(serCDI,
                                       CDI_WRITE_PARAM_CMD_MAX9295_CONFIG_VIDEO_PIPELINES,
                                       sizeof(paramsMAX9295.ConfigVideoPipeline),
                                       &paramsMAX9295);
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX9295_CONFIG_VIDEO_PIPELINES");

    pipeLineMap = &paramsMAX96712.PipelineMapping.linkPipelineMap[m_oLinkParams.ulinkIndex];
    pipeLineMap->isEmbDataType = m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.bEmbeddedDataType;
    pipeLineMap->vcID = m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.uVCID;
    pipeLineMap->dataType = dataTypeMAX96712;
    pipeLineMap->isDTOverride = !m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.bEmbeddedDataType;
    paramsMAX96712.PipelineMapping.link = link;
    nvmStatus = MAX96712WriteParameters(deserializerCDI,
                                        CDI_WRITE_PARAM_CMD_MAX96712_PIPELINE_MAPPING,
                                        sizeof(paramsMAX96712.PipelineMapping),
                                        &paramsMAX96712);
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX96712_PIPELINE_MAPPING");

    paramsMAX96712 = {};
    LOG_INFO("Initializing link %u\n", m_oLinkParams.ulinkIndex);

    if (m_oLinkParams.bSlave or m_oLinkParams.bEnableSimulator) {
        return NVSIPL_STATUS_OK;
    }

    /* Enable FRSYNC */
    if (m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.bEnableTriggerModeSync &&
        !m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.bPostSensorInitFsync) {
        LOG_INFO("Set Fsync\n");
        paramsMAX96712.FSyncSettings.FSyncMode = (m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.fFrameRate ==
                                                  CNvMPlatform::GetInstance()->GetConfig().fps) ?
                                                  CDI_MAX96712_FSYNC_EXTERNAL : CDI_MAX96712_FSYNC_OSC_MANUAL;
        paramsMAX96712.FSyncSettings.fps = m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.fFrameRate;
        paramsMAX96712.FSyncSettings.link = link;
        LOG_INFO("Set FSYNC mode\n");
        nvmStatus = MAX96712WriteParameters(deserializerCDI,
                                            CDI_WRITE_PARAM_CMD_MAX96712_SET_FSYNC,
                                            sizeof(paramsMAX96712.FSyncSettings),
                                            &paramsMAX96712);
        CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX96712_FSYNC_MODE");
    }

    return NVSIPL_STATUS_OK;
}

SIPLStatus CNvMTransportLink_Max96712_9295::PostSensorInit(DevBlkCDIDevice* brdcstSerCDI, uint8_t linkMask, bool groupInitProg) {
    WriteParametersParamMAX96712 paramsMAX96712 = {};
    LinkMAX96712 link = GetMAX96712Link(m_oLinkParams.ulinkIndex);
    DevBlkCDIDevice *deserializerCDI = m_oLinkParams.pDeserCDIDevice;
    NvMediaStatus nvmStatus = NVMEDIA_STATUS_OK;

    /* Enable FRSYNC */
    if (m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.bEnableTriggerModeSync &&
        m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.bPostSensorInitFsync) {
        LOG_INFO("Set Fsync\n");
        paramsMAX96712.FSyncSettings.FSyncMode = (m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.fFrameRate ==
                                                  CNvMPlatform::GetInstance()->GetConfig().fps) ?
                                                  CDI_MAX96712_FSYNC_EXTERNAL : CDI_MAX96712_FSYNC_OSC_MANUAL;
        paramsMAX96712.FSyncSettings.fps = m_oLinkParams.moduleConnectionProperty.sensorConnectionProperty.fFrameRate;
        paramsMAX96712.FSyncSettings.link = link;
        LOG_INFO("Set FSYNC mode\n");
        nvmStatus = MAX96712WriteParameters(deserializerCDI,
                                            CDI_WRITE_PARAM_CMD_MAX96712_SET_FSYNC,
                                            sizeof(paramsMAX96712.FSyncSettings),
                                            &paramsMAX96712);
        CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "CDI_WRITE_PARAM_CMD_MAX96712_FSYNC_MODE");
    }

    return NVSIPL_STATUS_OK;
}


SIPLStatus CNvMTransportLink_Max96712_9295::MiscInit()
{
    return NVSIPL_STATUS_OK;
}

} // end of namespace nvsipl
