/*
 * Copyright (c) 2017-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 "ref_max96712_tpg.h"
#include "isc_max96712.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"

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

    if (device == NULL) {
        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));
        }
    }

    if (device->iscDeserializer) {
        NvMediaISCDeviceDestroy(device->iscDeserializer);
    }

    if (device->iscRoot) {
        NvMediaISCRootDeviceDestroy(device->iscRoot);
    }

    free(device);

    return;
}

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

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

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

        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                    ISC_CONFIG_MAX96712_SET_PG_1920x1236);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set PG\n", __func__);
            return status;
        }

        /* 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.pgClkFreq = ISC_MAX96712_PG_CLK_150_MHZ;

        LOG_DBG("%s: Set pattern generator clock frequency\n", __func__);
        status = MAX96712WriteParameters(device->iscDeserializer,
                    ISC_WRITE_PARAM_CMD_MAX96712_SET_PG_CLK_FREQ,
                    sizeof(paramsMAX96712.pgClkFreq),
                    &paramsMAX96712);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set pattern generator clock frequency\n",
                    __func__);
            return status;
        }

        for (i = 0; i < MAX96712_NUM_VIDEO_PIPELINES; i++) {
            paramsMAX96712.pgPipelineClkSrc[i] = ISC_MAX96712_PG_CLK_150_MHZ;
        }

        LOG_DBG("%s: Set pattern generator clock source for each pipeline\n",
                __func__);
        status = MAX96712WriteParameters(device->iscDeserializer,
                    ISC_WRITE_PARAM_CMD_MAX96712_SET_PG_PIPELINE_CLK_SRC,
                    sizeof(paramsMAX96712.pgPipelineClkSrc),
                    &paramsMAX96712);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to set pattern generator clock source\n",
                    __func__);
            return status;
        }

        /* Set data type */
        if (device->property.inputFormatType == NVMEDIA_IMAGE_CAPTURE_INPUT_FORMAT_TYPE_RAW12) {
            paramsMAX96712.dataType = ISC_MAX96712_DATA_TYPE_RAW12;
        } else if (device->property.inputFormatType == NVMEDIA_IMAGE_CAPTURE_INPUT_FORMAT_TYPE_RGB888) {
            paramsMAX96712.dataType = ISC_MAX96712_DATA_TYPE_RGB;
        } else {
            LOG_ERR("%s: Invalid input format type provided\n", __func__);
            return NVMEDIA_STATUS_NOT_SUPPORTED;
        }
        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 =
            (device->property.inputFormatType == NVMEDIA_IMAGE_CAPTURE_INPUT_FORMAT_TYPE_RAW12) ?
                        ISC_MAX96712_DATA_TYPE_RAW12 : ISC_MAX96712_DATA_TYPE_RGB;
            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;
            }
        }

        /* When enabling TPG on Max96712, 1st TPG output is going to pipeline 0 ~ 3,
         * 2nd TPG output is going to pipeline 4 ~ 7.
         * And pipeline 0/4 is going to controller 0, pipeline 1/5 is going to controller 1
         * pipeline 2/6 is going to controller 2, pipeline 3/7 is going to controller 3 by default.
         * Since there is no way to disable TPG and TPG is behind the pipeline,
         * undesired pipeline output has to be mapped to unused controller.
         */
        for (i = configParam->sensorsNum; i < MAX96712_MAX_NUM_LINK; i++) {
            paramsMAX96712.VC.embDataType = device->property.embDataType;
            paramsMAX96712.VC.link = i;
            paramsMAX96712.VC.dataType =
            (device->property.inputFormatType == NVMEDIA_IMAGE_CAPTURE_INPUT_FORMAT_TYPE_RAW12) ?
                        ISC_MAX96712_DATA_TYPE_RAW12 : ISC_MAX96712_DATA_TYPE_RGB;
            status = MAX96712WriteParameters(device->iscDeserializer,
                                            ISC_WRITE_PARAM_CMD_MAX96712_UNUSED_PIPELINE_MAPPING,
                                            sizeof(paramsMAX96712.VC),
                                            &paramsMAX96712);
            if (status != NVMEDIA_STATUS_OK) {
                LOG_ERR("%s: Failed to set Deserializer mapping", __func__);
                return status;
            }
        }

        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                    ISC_CONFIG_MAX96712_ENABLE_PG);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to enable PG\n", __func__);
            return status;
        }
    }

    return status;
}

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

    SetLogLevel(LEVEL_INFO);

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

    if (configParam->enableVirtualChannels == NVMEDIA_FALSE && configParam->sensorsNum > 1) {
        LOG_ERR("%s: VC option has to be enabled\n", __func__);
        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 == NULL) {
        LOG_ERR("%s: Out of memory\n", __func__);
        return NULL;
    }

    LOG_INFO("%s: Set image device property\n", __func__);
    status = ImgDevSetProperty(
                        GetDriver_ref_max96712_tpg(),
                        configParam,
                        device);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Given property not supported. 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 == NULL) {
        LOG_ERR("%s: Failed to create NvMedia ISC root device\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,
                                                    GetMAX96712Driver(),     // deviceDriver
                                                    &advConfig);            // advancedConfig
        if (device->iscDeserializer == NULL) {
            LOG_ERR("%s: Failed to create deserializer device\n", __func__);
            goto failed;
        }
    }

    if (configParam->slave) {
        return device;
    }

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

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

    device->simulator = configParam->enableSimulator;

    // Delay to skip some frames at the begining
    nvsleep(100000);

    return device;

failed:
    Deinit(device);

    return NULL;
}

static NvMediaStatus
GetError(
   ExtImgDevice *device,
   uint32_t *link,
   ExtImgDevFailureType *errorType)
{

    return _GetError_max96712(device->iscDeserializer, link, errorType);
}

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

static ImgProperty properties[] = {
                   /* resolution, oscMHz, fps, duty, dphy, cphy, embTop, embBottom, inputFormat, pixelOrder, embDataType */
    IMG_PROPERTY_ENTRY_MIPI(1920x1236, 0,      30,  0.0, 0, 0, 0,      0,         raw12,       grbg,       0),
    IMG_PROPERTY_ENTRY_MIPI(1920x1236, 0,      30,  0.0, 0, 0, 0,      0,         rgb,         grbg,       0),
};

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

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