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

#include "nvmedia_isc.h"
#include "isc_max9271.h"
#include "os_common.h"

#define REGISTER_ADDRESS_BYTES  1
#define REG_WRITE_BUFFER        32

#if !defined(__INTEGRITY)
#define MIN(a,b)            (((a) < (b)) ? (a) : (b))
#endif
#define GET_SIZE(x)         sizeof(x)
#define GET_BLOCK_LENGTH(x) x[0]
#define GET_BLOCK_DATA(x)   &x[1]
#define SET_NEXT_BLOCK(x)   x += (x[0] + 1)

unsigned char max9271_enable_reverse_channel[] = {
    2, 0x04, 0x43,  // Enable config link
};

unsigned char max9271_defaults[] = {
    // Enable high threshold for reverse channel input buffer.
    // This increase immunity to power supply noise when we use power over the coax.
    2, 0x08, 0x01  // Bug 1660114 : bit[3] should be cleared for the reverse channel stability issue.
};

unsigned char max9271_invert_vs[] = {
    // Enable high threshold for reverse channel input buffer.
    // This increase immunity to power supply noise when we use power over the coax.
    // Bug 1660114 : bit[3] should be cleared for the reverse channel stability issue.
    2, 0x08, 0x81  // invert vs
};

unsigned char max9271_config_input_mode[] = {
    2, 0x07, 0x94  // PCLKIN setting DBL=1, DRS=0, BWS=0, ES=1, HVEN=1, EDC=0
};

unsigned char max9271_enableSerialLink[] = {
    2, 0x04, 0x83,   // Enable serial link, disable config link
};

unsigned char max9271_set_translator_a[] = {
    2, 0x09, 0x00,
    2, 0x0a, 0x00,
};

unsigned char max9271_set_translator_b[] = {
    2, 0x0b, 0x00,
    2, 0x0c, 0x00,
};

unsigned char max9271_i2c_slave_remote_timeout[] = {
    2, 0x0d, 0xb7,
};

static NvMediaStatus
DriverCreate(
    NvMediaISCDevice *handle,
    void *clientContext)
{
    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;

    handle->deviceDriverHandle = NULL;

    return NVMEDIA_STATUS_OK;
}

static NvMediaStatus
DriverDestroy(
    NvMediaISCDevice *handle)
{
    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
MAX9271CheckPresence(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    unsigned char reg = 0x1e;
    unsigned char readBuff = 0;

    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    status = NvMediaISCDeviceRead(handle, 0,             // transaction
                REGISTER_ADDRESS_BYTES,     // regLength
                &reg,                       // regData
                1,                          // dataLength
                &readBuff);                 // dataBuff

    if(status != NVMEDIA_STATUS_OK)
        return status;

    return (readBuff == MAX9271_DEVICE_ID) ? NVMEDIA_STATUS_OK :
                                             NVMEDIA_STATUS_ERROR;
}

static NvMediaStatus
SetPreEmp(
    NvMediaISCDevice *handle,
    unsigned char preemp)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    unsigned char reg[2] = {0x06, 0xA0};

    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    reg[1] |= (preemp & 0xF);

    status = NvMediaISCDeviceWrite(handle, 0,
            sizeof(reg),
            reg);

    if(status != NVMEDIA_STATUS_OK)
        return status;

    nvsleep(5000); /* Delay to wait since I2C unavailable while GMSL locks from programming guide */

    return NVMEDIA_STATUS_OK;
}

static NvMediaStatus
WriteArray(
    NvMediaISCDevice *handle,
    uint32_t arrayByteLength,
    const uint8_t *arrayData)
{
    NvMediaStatus status;

    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    while(arrayByteLength) {
        if(arrayData[0] < 10) {
            status = NvMediaISCDeviceWrite(handle, 0,                 // transaction
                    GET_BLOCK_LENGTH(arrayData), // dataLength
                    GET_BLOCK_DATA(arrayData));

            if(status != NVMEDIA_STATUS_OK) {
                printf("MAX9271 : error: wri2c   0x80    0x%.2X    0x%.2X\n",
                    (unsigned int)arrayData[1],
                    (unsigned int)arrayData[2]);
                break;
            }
            /* This SER-DES pair needs 20SCLK clocks or more timing for next I2C command so we set 100 us with margin */
            nvsleep(100);

            arrayByteLength -= GET_BLOCK_LENGTH(arrayData) + 1;
            SET_NEXT_BLOCK(arrayData);
        } else {
            nvsleep(arrayData[0] * 10);
            arrayData++;
            arrayByteLength--;
        }
    }

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
MAX9271SetDefaults(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status;

    status = WriteArray(
                handle,
                GET_SIZE(max9271_defaults),
                max9271_defaults);

    return status;
}

static NvMediaStatus
EnableReverseChannel(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_ERROR;

    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    status = NvMediaISCDeviceWrite(handle, 0,
            max9271_enable_reverse_channel[0],
            &max9271_enable_reverse_channel[1]);

    nvsleep(3000);

    return status;
}

static NvMediaStatus
EnableSerialLink(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_ERROR;

    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    status = NvMediaISCDeviceWrite(handle, 0,
            max9271_enableSerialLink[0],
            &max9271_enableSerialLink[1]);

    nvsleep(350);

    return status;
}

NvMediaStatus
MAX9271SetDeviceConfig(
        NvMediaISCDevice *handle,
        unsigned int enumeratedDeviceConfig)
{
    switch(enumeratedDeviceConfig) {
        case ISC_CONFIG_MAX9271_DEFAULT:
            return WriteArray(
                handle,
                GET_SIZE(max9271_defaults),
                max9271_defaults);
        case ISC_CONFIG_MAX9271_ENABLE_SERIAL_LINK:
        return EnableSerialLink(handle);
        case ISC_CONFIG_MAX9271_PCLKIN:
            return WriteArray(
                handle,
                GET_SIZE(max9271_config_input_mode),
                max9271_config_input_mode);
        case ISC_CONFIG_MAX9271_ENABLE_REVERSE_CHANNEL:
            return EnableReverseChannel(handle);
        case ISC_CONFIG_MAX9271_INVERT_VS:
            return WriteArray(
                handle,
                GET_SIZE(max9271_invert_vs),
                max9271_invert_vs);
        case ISC_CONFIG_MAX9271_SET_MAX_REMOTE_I2C_SLAVE_TIMEOUT:
            return WriteArray(handle,
                              GET_SIZE(max9271_i2c_slave_remote_timeout),
                              max9271_i2c_slave_remote_timeout);
    }

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
MAX9271ReadRegister(
    NvMediaISCDevice *handle,
    uint32_t deviceIndex,
    unsigned int registerNum,
    unsigned int dataLength,
    unsigned char *dataBuff)
{
    unsigned char registerData[REGISTER_ADDRESS_BYTES];
    NvMediaStatus status;

    if(!handle || !dataBuff)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    registerData[0] = registerNum & 0xFF;
    status = NvMediaISCDeviceRead(handle,
        deviceIndex,
        REGISTER_ADDRESS_BYTES,
        registerData,
        dataLength,
        dataBuff);

    return status;
}

NvMediaStatus
MAX9271WriteRegister(
    NvMediaISCDevice *handle,
    uint32_t deviceIndex,
    unsigned int registerNum,
    unsigned int dataLength,
    unsigned char *dataBuff)
{
    unsigned char data[REGISTER_ADDRESS_BYTES + REG_WRITE_BUFFER];
    NvMediaStatus status;

    if(!handle || !dataBuff)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    data[0] = registerNum & 0xFF;
    memcpy(&data[1], dataBuff, MIN(REG_WRITE_BUFFER, dataLength));

    status = NvMediaISCDeviceWrite(handle,
        deviceIndex,
        dataLength + REGISTER_ADDRESS_BYTES,
        data);

    return status;
}

static NvMediaStatus
SetTranslator(
        NvMediaISCDevice *handle,
        unsigned int parameterType,
        WriteReadParametersParamMAX9271 *param)
{
    unsigned char *max9271_set_translator;

    if(parameterType == ISC_WRITE_PARAM_CMD_MAX9271_SET_TRANSLATOR_A) {
            max9271_set_translator = &max9271_set_translator_a[0];
    } else {
            max9271_set_translator = &max9271_set_translator_b[0];
    }
    max9271_set_translator[2] = param->Translator.source << 1;
    max9271_set_translator[5] = param->Translator.destination << 1;

    return WriteArray(
        handle,
        GET_SIZE(max9271_set_translator_a),
        max9271_set_translator);
}

static NvMediaStatus
SetDeviceAddress(
        NvMediaISCDevice *handle,
        unsigned char address)
{
    unsigned char data[2];

    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    data[0] = 0x00;

    if(address > 0x80)
        return NVMEDIA_STATUS_BAD_PARAMETER;

    data[1] = address << 1;

    return NvMediaISCDeviceWrite(handle, 0,
        2,
        data);
}

static NvMediaStatus
GetDeviceAddress(
        NvMediaISCDevice *handle,
        unsigned char *address)
{
    unsigned char data[2];
    NvMediaStatus status;

    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    data[0] = 0x00;
    data[1] = 0x00;

    status = NvMediaISCDeviceRead(handle, 0,
        1,
        &data[0],
        1,
        &data[1]);

    if (status == NVMEDIA_STATUS_OK)
        *address = data[1];

    return status;
}

static NvMediaStatus
SetInputMode(
        NvMediaISCDevice *handle,
        ConfigureInputModeMAX9271 *inputmode)
{
    NvMediaStatus status;

    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;


    max9271_config_input_mode[2] =  (unsigned char)inputmode->byte;

    status = NvMediaISCDeviceWrite(handle, 0,
        max9271_config_input_mode[0],
        &max9271_config_input_mode[1]);

    return status;
}

NvMediaStatus
MAX9271WriteParameters(
        NvMediaISCDevice *handle,
        unsigned int parameterType,
        unsigned int parameterSize,
        void *parameter)
{
    WriteReadParametersParamMAX9271 *param;

    if(!parameter)
        return NVMEDIA_STATUS_BAD_PARAMETER;
    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;

    param = (WriteReadParametersParamMAX9271 *)parameter;

    switch(parameterType) {
        case ISC_WRITE_PARAM_CMD_MAX9271_SET_TRANSLATOR_A:
        case ISC_WRITE_PARAM_CMD_MAX9271_SET_TRANSLATOR_B:
            if(parameterSize != sizeof(param->Translator))
                return NVMEDIA_STATUS_BAD_PARAMETER;
            return SetTranslator(
                handle,
                parameterType,
                param);
        case ISC_WRITE_PARAM_CMD_MAX9271_SET_DEVICE_ADDRESS:
            if(parameterSize != sizeof(param->DeviceAddress))
                return NVMEDIA_STATUS_BAD_PARAMETER;
            return SetDeviceAddress(
                handle,
                param->DeviceAddress.address);
        case ISC_WRITE_PARAM_CMD_MAX9271_CONFIG_INPUT_MODE:
            if(parameterSize != sizeof(param->inputmode))
                return NVMEDIA_STATUS_BAD_PARAMETER;
            return SetInputMode(
                handle,
                param->inputmode);
        case ISC_WRITE_PARAM_CMD_MAX9271_SET_PREEMP:
            if(parameterSize != sizeof(param->preemp))
                return NVMEDIA_STATUS_BAD_PARAMETER;
            return SetPreEmp(
                handle,
                param->preemp);
        default:
            break;
    }

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
MAX9271ReadParameters(
        NvMediaISCDevice *handle,
        unsigned int parameterType,
        unsigned int parameterSize,
        void *parameter)
{
    WriteReadParametersParamMAX9271 *param;

    if(!parameter)
        return NVMEDIA_STATUS_BAD_PARAMETER;
    if(!handle)
        return NVMEDIA_STATUS_BAD_PARAMETER;

    param = (WriteReadParametersParamMAX9271 *)parameter;

    switch(parameterType) {
        case ISC_READ_PARAM_CMD_MAX9271_GET_DEVICE_ADDRESS:
            if(parameterSize != sizeof(param->DeviceAddress))
                return NVMEDIA_STATUS_BAD_PARAMETER;
            return GetDeviceAddress(
                handle,
                (unsigned char*)param);
        default:
            break;
    }

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus
MAX9271DumpRegisters(
    NvMediaISCDevice *handle)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    uint8_t data[3] = {3, 0x00, 0};
    uint32_t i = 0u, addr = 0u;

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


    for (i = 0; i <= MAX9271_MAX_REG_ADDRESS; i++) {
        data[1] = i;

        status = NvMediaISCDeviceRead(handle,0,
                REGISTER_ADDRESS_BYTES,
                &data[1],
                1,
                &data[2]);
        if (status != NVMEDIA_STATUS_OK)
            return status;

        if (i == 0)
            addr = data[2];

        printf(" Max9271(0x%x) : 0x%02x - 0x%02x\n", addr, data[1], data[2]);
    }

    return status;
}

static NvMediaISCDeviceDriver deviceDriver = {
    .deviceName = "Maxim 9271 Serializer",
    .regLength = 1,
    .dataLength = 1,
    .DriverCreate = DriverCreate,
    .DriverDestroy = DriverDestroy,
};

NvMediaISCDeviceDriver *
GetMAX9271Driver(void)
{
    return &deviceDriver;
}

