/*
 * 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 <float.h>

#include "common_max96712.h"
#include "log_utils.h"

#define DPHY_BITS_PER_SAMPLE    1.0
#define CPHY_BITS_PER_SAMPLE    2.28f

#define BW_BUFFER        1.3

static NvMediaStatus
GetNumCsiLanes(
    uint8_t *numLanes,
    NvMediaICPInterfaceType interface)
{
    if (numLanes == NULL) {
        return NVMEDIA_STATUS_BAD_PARAMETER;
    }

    switch (interface) {
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_A:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_B:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_C:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_D:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_E:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_F:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_G:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_H:
            *numLanes = 2u;
            break;
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_AB:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_CD:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_EF:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_GH:
            *numLanes = 4u;
            break;
        default:
            return NVMEDIA_STATUS_BAD_PARAMETER;
    }

    return NVMEDIA_STATUS_OK;
}

static inline uint8_t
GetBPP(
    NvMediaBitsPerPixel bpp)
{
    switch (bpp) {
        case NVMEDIA_BITS_PER_PIXEL_8:
            return 8u;
        case NVMEDIA_BITS_PER_PIXEL_10:
            return 10u;
        case NVMEDIA_BITS_PER_PIXEL_12:
            return 12u;
        case NVMEDIA_BITS_PER_PIXEL_14:
            return 14u;
        case NVMEDIA_BITS_PER_PIXEL_16:
            return 16u;
        case NVMEDIA_BITS_PER_PIXEL_20:
            return 20u;
        default:
            return 0u;
    }
}

static NvMediaStatus
CheckCSIBandwidth(
    ExtImgDevice *device,
    ExtImgDevParam *configParam,
    uint8_t numLanes)
{
    ExtImgDevProperty *property = NULL;
    double_t availBw = 0.0, reqBw = 0.0;
    uint8_t phyMode = 0u, bpp = 0u;

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

    property = &device->property;

    phyMode = (property->phyMode == NVMEDIA_ICP_CSI_CPHY_MODE) ?
               ISC_MAX96712_CPHY : ISC_MAX96712_DPHY;
    bpp = GetBPP(property->bitsPerPixel);

    /* Calculate requested bandwidth in GBps
     * BW = (HTS * VTS * BPP * FPS * numSensors * additional buffer) /
     *      (1000 * 1000 * 1000) */
    reqBw = ((uint64_t)property->width * property->height * bpp * property->frameRate *
            configParam->sensorsNum * BW_BUFFER) / (1000u * 1000u * 1000u);

    /* Calulate available bandwidth in GBps
     * BW = (MIPI speed onDes in Msamples * numLanes * bits per sample) /
     *      (1000) */
    availBw = (property->mipiSpeed[phyMode] * numLanes *
               (phyMode == ISC_MAX96712_CPHY ? CPHY_BITS_PER_SAMPLE : DPHY_BITS_PER_SAMPLE))
               / (1000 * 1000);

    /* Check bandwidth */
    if (availBw < reqBw) {
        LOG_ERR("%s: The selected configuration exceeds the supported bandwidth."
                " Please use a different configuration.\n"
                "Bandwidth requested: %f GBps, Bandwidth supported: %f GBps\n",
                __func__, reqBw, availBw);
        return NVMEDIA_STATUS_ERROR;
    }

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
SetMAX96712Ctx(
    ExtImgDevice *device,
    ExtImgDevParam *configParam,
    ContextMAX96712 *ctxMAX96712)
{
    NvMediaICPInterfaceType interface = device->property.interface;
    uint8_t phyMode = ISC_MAX96712_DPHY;
    uint8_t numLanes = 0u;
    NvMediaStatus status = NVMEDIA_STATUS_OK;

    if ((device == NULL) || (ctxMAX96712 == NULL) ||
        (configParam == NULL) || (configParam->board == NULL)) {
        return NVMEDIA_STATUS_BAD_PARAMETER;
    }

    phyMode = (device->property.phyMode == NVMEDIA_ICP_CSI_CPHY_MODE) ?
                                           ISC_MAX96712_CPHY : ISC_MAX96712_DPHY;

    /* Get number of CSI lanes */
    status = GetNumCsiLanes(&numLanes, interface);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to get number of CSI lanes\n", __func__);
        return status;
    }

    /* Set CSI output frequency on deserializer */
    if (phyMode == ISC_MAX96712_DPHY) {
        ctxMAX96712->mipiSpeed = ISC_MAX96712_MIPI_SPEED_2500_MHZ;
    } else if (phyMode == ISC_MAX96712_CPHY) {
        if (numLanes == 4) {
            ctxMAX96712->mipiSpeed = ISC_MAX96712_MIPI_SPEED_1700_MHZ;
        } else {
            if ((strncasecmp(configParam->board, "ddpx-a", strlen("ddpx-a")) == 0) ||
                (strncasecmp(configParam->board, "ddpx-b", strlen("ddpx-b")) == 0) ||
                (strncasecmp(configParam->board, "ddpx-c", strlen("ddpx-c")) == 0)) {
                ctxMAX96712->mipiSpeed = ISC_MAX96712_MIPI_SPEED_2000_MHZ;
            } else {
                ctxMAX96712->mipiSpeed = ISC_MAX96712_MIPI_SPEED_2500_MHZ;
            }
        }
    }

    ctxMAX96712->slaveEnabled = configParam->slave;
    ctxMAX96712->phyMode = phyMode;
    device->property.mipiSpeed[phyMode] = ctxMAX96712->mipiSpeed * 100000u;

    /* Select I2C port */
    if ((strncasecmp(configParam->board, "ddpx-a", strlen("ddpx-a")) == 0) ||
        (strncasecmp(configParam->board, "p3479", strlen("p3479")) == 0)) {
        ctxMAX96712->i2cPort = ISC_MAX96712_I2CPORT_0;
    } else if ((strncasecmp(configParam->board, "ddpx-b", strlen("ddpx-b")) == 0) ||
               (strncasecmp(configParam->board, "ddpx-c", strlen("ddpx-c")) == 0)) {
        ctxMAX96712->i2cPort = ISC_MAX96712_I2CPORT_1;
    } else {
        return NVMEDIA_STATUS_NOT_SUPPORTED;
    }

    switch (interface) {
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_A:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_C:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_E:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_G:
            ctxMAX96712->txPort = ISC_MAX96712_TXPORT_PHY_C | (ctxMAX96712->i2cPort << 1);
            ctxMAX96712->mipiCfg = MAX96712_MIPI_OUT_4x2;
            break;
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_B:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_D:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_F:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_H:
            ctxMAX96712->txPort = ISC_MAX96712_TXPORT_PHY_D | (ctxMAX96712->i2cPort << 1);
            ctxMAX96712->mipiCfg = MAX96712_MIPI_OUT_4x2;
            break;
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_AB:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_CD:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_EF:
        case NVMEDIA_IMAGE_CAPTURE_CSI_INTERFACE_TYPE_CSI_GH:
            if (phyMode == ISC_MAX96712_DPHY) {
                if (ctxMAX96712->i2cPort == ISC_MAX96712_I2CPORT_0) {
                    ctxMAX96712->txPort = ISC_MAX96712_TXPORT_PHY_D;
                    ctxMAX96712->mipiCfg = MAX96712_MIPI_OUT_2x4;
                } else {
                    ctxMAX96712->txPort = ISC_MAX96712_TXPORT_PHY_E;
                    ctxMAX96712->mipiCfg = MAX96712_MIPI_OUT_2x4;
                }
            } else if (phyMode == ISC_MAX96712_CPHY) {
                if (ctxMAX96712->i2cPort == ISC_MAX96712_I2CPORT_0) { /* 4 trios for Port A */
                    ctxMAX96712->txPort = ISC_MAX96712_TXPORT_PHY_D;
                    ctxMAX96712->mipiCfg = MAX96712_MIPI_OUT_2x4;
                } else { /* 4 trios for Port B */
                    ctxMAX96712->txPort = ISC_MAX96712_TXPORT_PHY_E;
                    ctxMAX96712->mipiCfg = MAX96712_MIPI_OUT_2x4;
                }
            }
            break;
        default:
            return NVMEDIA_STATUS_BAD_PARAMETER;
    }

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
ChkMAX96712Limitaions(
    ExtImgDevice *device,
    ExtImgDevParam *configParam)
{
    NvMediaICPInterfaceType interface = device->property.interface;
    uint8_t phyMode = ISC_MAX96712_DPHY;
    uint8_t numLanes = 0u;
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    ReadParametersParamMAX96712 readParamsMAX96712;
    RevisionMAX96712 rev = ISC_MAX96712_INVALID_REV;

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

    phyMode = (device->property.phyMode == NVMEDIA_ICP_CSI_CPHY_MODE) ?
                                           ISC_MAX96712_CPHY : ISC_MAX96712_DPHY;

    /* Get number of CSI lanes */
    status = GetNumCsiLanes(&numLanes, interface);
    if (status != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to get number of CSI lanes\n", __func__);
        return status;
    }

    /* 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_1) {
        if (phyMode == ISC_MAX96712_CPHY) {
            LOG_ERR("CPHY mode is not supported on MAX96712 Rev 1\n");
            return NVMEDIA_STATUS_NOT_SUPPORTED;
        }

        if ((phyMode == ISC_MAX96712_DPHY) && (numLanes == 4)) {
            LOG_ERR("4 lane DPHY capture is not supported on MAX96712 Rev 1\n");
            return NVMEDIA_STATUS_NOT_SUPPORTED;
        }
    /* Check for MAX96712 revision C limitations */
    } else if (rev == ISC_MAX96712_REV_3) {
        /* Bug 2446492: Disable 2-bit ECC error reporting as spurious ECC errors are
         * intermittently observed on Rev C */
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                         ISC_CONFIG_MAX96712_DISABLE_MEMORY_ECC_ERRB);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to disable ECC error reporting\n", __func__);
            return status;
        }
    } else if (rev == ISC_MAX96712_REV_4) {
        status = MAX96712SetDeviceConfig(device->iscDeserializer,
                                         ISC_CONFIG_MAX96712_ENABLE_MEMORY_ECC_ERRB);
        if (status != NVMEDIA_STATUS_OK) {
            LOG_ERR("%s: Failed to disable ECC error reporting\n", __func__);
            return status;
        }
    } else if (rev == ISC_MAX96712_INVALID_REV) {
        LOG_ERR("Invalid MAX96712 Rev detected.\n");
        return NVMEDIA_STATUS_NOT_SUPPORTED;
    }

    /* Check CSI bandwidth limitations */
    return CheckCSIBandwidth(device, configParam, numLanes);
}
