/* IBM_PROLOG_BEGIN_TAG                                                   */
/* This is an automatically generated prolog.                             */
/*                                                                        */
/* $Source: import/chips/p9/procedures/hwp/lib/p9_avsbus_lib.C $          */
/*                                                                        */
/* OpenPOWER HCODE Project                                                */
/*                                                                        */
/* COPYRIGHT 2016,2017                                                    */
/* [+] International Business Machines Corp.                              */
/*                                                                        */
/*                                                                        */
/* Licensed under the Apache License, Version 2.0 (the "License");        */
/* you may not use this file except in compliance with the License.       */
/* You may obtain a copy of the License at                                */
/*                                                                        */
/*     http://www.apache.org/licenses/LICENSE-2.0                         */
/*                                                                        */
/* Unless required by applicable law or agreed to in writing, software    */
/* distributed under the License is distributed on an "AS IS" BASIS,      */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        */
/* implied. See the License for the specific language governing           */
/* permissions and limitations under the License.                         */
/*                                                                        */
/* IBM_PROLOG_END_TAG                                                     */

///
/// @file  p9_avsbus_lib.C
/// @brief Library functions for AVSBus
///
/// *HWP HWP Owner        : Greg Still <stillgs@us.ibm.com>
/// *HWP HWP Backup Owner : Brian Vanderpool <vanderp@us.ibm.com>
/// *HWP FW Owner         : Prasad BG Ranganath <prasadbgr@in.ibm.com>
/// *Team                 : PM
/// *Consumed by          : SBE:SGPE
/// *Level                : 3
///

#include <p9_avsbus_lib.H>
#include <p9_avsbus_registers.H>
#include <p9_ocb_firmware_registers.h>

//##############################################################################
// Function which generates a 3 bit CRC value for 29 bit data
//##############################################################################
#define AVS_CRC_DATA_MASK 0xFFFFFFF8
uint32_t avsCRCcalc(const uint32_t i_avs_cmd)
{
    //Polynomial = x^3 + x^1 + x^0 = 1*x^3 + 0*x^2 + 1*x^1 + 1*x^0
    //           = divisor(1011)
    uint32_t o_crc_value = 0;
    uint32_t l_polynomial = 0xB0000000;
    uint32_t l_msb =        0x80000000;

    o_crc_value = i_avs_cmd & AVS_CRC_DATA_MASK;

    while (o_crc_value & AVS_CRC_DATA_MASK)
    {
        if (o_crc_value & l_msb)
        {
            // if l_msb is 1'b1, divide by l_polynomial and shift l_polynomial
            // to the right
            o_crc_value = o_crc_value ^ l_polynomial;
            l_polynomial = l_polynomial >> 1;
        }
        else
        {
            // if l_msb is zero, shift l_polynomial
            l_polynomial = l_polynomial >> 1;
        }

        l_msb = l_msb >> 1;
    }

    FAPI_INF("The computed CRC Value is %d", o_crc_value)
    return o_crc_value;
}

//##############################################################################
// Function which initializes the OCB O2S registers
//##############################################################################
fapi2::ReturnCode
avsInitExtVoltageControl(
    const fapi2::Target<fapi2::TARGET_TYPE_PROC_CHIP>& i_target,
    const uint8_t i_avsBusNum,
    const uint8_t i_o2sBridgeNum)
{

    fapi2::buffer<uint64_t> l_data64;
    uint32_t l_avsbus_frequency, l_value, l_nest_frequency;
    uint16_t l_divider;

    // O2SCTRLF
    // [ 0: 5] o2s_frame_size = 32; -> 0x20
    // [ 6:11] o2s_out_count1 = 32; -> 0x20
    // [12:17] o2s_in_delay1  =  0;     No concurrent input
    // [18:23] o2s_in_l_count1  =  X;     No input on first frame

    //uint32_t O2SCTRLF_value = 0b10000010000011111100000000000000;
    ocb_o2sctrlf0a_t O2SCTRLF_value;
    O2SCTRLF_value.fields.o2s_frame_size_an = p9avslib::O2S_FRAME_SIZE;
    O2SCTRLF_value.fields.o2s_out_count1_an = p9avslib::O2S_FRAME_SIZE;
    O2SCTRLF_value.fields.o2s_in_delay1_an = p9avslib::O2S_IN_DELAY1;

    l_data64.insertFromRight<0, 6>(O2SCTRLF_value.fields.o2s_frame_size_an);
    l_data64.insertFromRight<6, 6>(O2SCTRLF_value.fields.o2s_out_count1_an);
    l_data64.insertFromRight<12, 6>(O2SCTRLF_value.fields.o2s_in_delay1_an);
    FAPI_TRY(putScom(i_target,
                     p9avslib::OCB_O2SCTRLF[i_avsBusNum][i_o2sBridgeNum],
                     l_data64));

    // Note:  the buffer is a 32bit buffer.  make sure it is left
    // aligned for the SCOM

    // O2SCTRLS
    // [ 0: 5] o2s_out_count2  = 0;
    // [ 6:11] o2s_in_delay2   = 0;
    // [12:17] o2s_in_l_count2   = 32; -> 0x20;

    // uint32_t O2SCTRLS_value = 0b00000000000010000000000000000000;
    ocb_o2sctrls0a_t O2SCTRLS_value;
    O2SCTRLS_value.fields.o2s_in_count2_an = p9avslib::O2S_FRAME_SIZE;

    l_data64.flush<0>();
    l_data64.insertFromRight<12, 6>(O2SCTRLS_value.fields.o2s_in_count2_an);
    FAPI_TRY(putScom(i_target,
                     p9avslib::OCB_O2SCTRLS[i_avsBusNum][i_o2sBridgeNum],
                     l_data64));

    // O2SCTRL2
    // [    0] o2s_bridge_enable
    // [    1] pmcocr1_reserved_1
    // [    2] o2s_cpol = 0;            Low idle clock
    // [    3] o2s_cpha = 0;            First edge
    // [ 4:13] o2s_clock_divider = 0xFA    Yield 1MHz with 2GHz nest
    // [14:16] pmcocr1_reserved_2
    // [   17] o2s_nr_of_frames = 1; Two frames
    // [18:20] o2s_port_enable (only port 0 (18) by default

    // Divider calculation (which can be overwritten)
    //  Nest Frequency:  2000MHz (0x7D0)
    //  AVSBus Frequency:    1MHz (0x1) (eg  1us per bit)
    //
    // Divider = Nest Frequency / (AVSBus Frequency * 8) - 1
    //
    // @note:  PPE can multiply by a recipricol.  A precomputed
    // 1 / (AVSBus frequency *8) held in an attribute allows a
    // fully l_data64 driven computation without a divide operation.

    ocb_o2sctrl10a_t O2SCTRL1_value;
    O2SCTRL1_value.fields.o2s_bridge_enable_an = 1;

    //Nest frequency attribute in MHz
    FAPI_TRY(FAPI_ATTR_GET(fapi2::ATTR_FREQ_PB_MHZ,
                           fapi2::Target<fapi2::TARGET_TYPE_SYSTEM>(),
                           l_nest_frequency));

    // AVSBus frequency attribute in KHz
    FAPI_TRY(FAPI_ATTR_GET(fapi2::ATTR_AVSBUS_FREQUENCY,
                           fapi2::Target<fapi2::TARGET_TYPE_SYSTEM>(),
                           l_value));

    if (l_value == 0)
    {
        l_avsbus_frequency = p9avslib::AVSBUS_FREQUENCY / 1000;
    }
    else
    {
        l_avsbus_frequency = l_value / 1000;
    }

    // Divider = Nest Frequency / (AVSBus Frequency * 8) - 1
    l_divider = (l_nest_frequency / (l_avsbus_frequency * 8)) - 1;

    FAPI_INF("Nest frequency value is %d", l_nest_frequency);
    FAPI_INF("AVSBus frequency value is %d", l_avsbus_frequency);
    FAPI_INF("Divider value is %d", l_divider);

    O2SCTRL1_value.fields.o2s_clock_divider_an = l_divider;
    O2SCTRL1_value.fields.o2s_nr_of_frames_an = 1;
    O2SCTRL1_value.fields.o2s_cpol_an = 0;
    O2SCTRL1_value.fields.o2s_cpha_an = 1;

    l_data64.flush<0>();
    l_data64.insertFromRight<0, 1>(O2SCTRL1_value.fields.o2s_bridge_enable_an);
    l_data64.insertFromRight<2, 1>(O2SCTRL1_value.fields.o2s_cpol_an);
    l_data64.insertFromRight<3, 1>(O2SCTRL1_value.fields.o2s_cpha_an);
    l_data64.insertFromRight<4, 10>(O2SCTRL1_value.fields.o2s_clock_divider_an);
    l_data64.insertFromRight<17, 1>(O2SCTRL1_value.fields.o2s_nr_of_frames_an);
    FAPI_TRY(putScom(i_target,
                     p9avslib::OCB_O2SCTRL1[i_avsBusNum][i_o2sBridgeNum],
                     l_data64));

    // O2SCTRL1
    // OCC O2S Control2
    // [ 0:16] o2s_inter_frame_delay = filled in with ATTR l_data64
    // Needs to be 10us or greater for SPIVID part operation
    // Set to ~16us for conservatism using a 100ns hang pulse
    // 16us = 16000ns -> 16000/100 = 160 = 0xA0;  aligned to 0:16 -> 0x005
    ocb_o2sctrl20a_t O2SCTRL2_value;
    O2SCTRL2_value.fields.o2s_inter_frame_delay_an = 0x0;

    l_data64.flush<0>();
    l_data64.insertFromRight<0, 17>
    (O2SCTRL2_value.fields.o2s_inter_frame_delay_an);
    FAPI_TRY(putScom(i_target,
                     p9avslib::OCB_O2SCTRL2[i_avsBusNum][i_o2sBridgeNum],
                     l_data64));

fapi_try_exit:
    return fapi2::current_err;
}

//##############################################################################
// Function polls OCB status register O2SST for o2s_ongoing=0
//##############################################################################
fapi2::ReturnCode
avsPollVoltageTransDone(
    const fapi2::Target<fapi2::TARGET_TYPE_PROC_CHIP>& i_target,
    const uint8_t i_avsBusNum,
    const uint8_t i_o2sBridgeNum)
{
    fapi2::ReturnCode l_rc = fapi2::FAPI2_RC_SUCCESS;
    fapi2::buffer<uint64_t> l_data64;

    uint8_t l_count = 0;

    while (l_count < p9avslib::MAX_POLL_COUNT_AVS)
    {
        FAPI_TRY(getScom(i_target, p9avslib::OCB_O2SST[i_avsBusNum]
                         [i_o2sBridgeNum], l_data64),
                 "Error from getscom 0x%.16llX",
                 p9avslib::OCB_O2SST[i_avsBusNum]);

        if (!l_data64.getBit<0>())
        {
            break;  // Leave the polling loop as "ongoing" has deasserted
        }

        l_count++;
    }

    // Check for timeout condition
    if (l_count >= p9avslib::MAX_POLL_COUNT_AVS)
    {
        // This will set current_err to a non success value that can be
        // checked by the caller.
        FAPI_ASSERT(false,
                    fapi2::PROCPM_AVSBUS_POLL_TIMEOUT()
                    .set_CHIP_TARGET(i_target)
                    .set_AVSBUS_NUM(i_avsBusNum)
                    .set_AVSBUS_BRIDGE_NUM(i_o2sBridgeNum)
                    .set_AVSBUS_MAX_POLL_CNT(p9avslib::MAX_POLL_COUNT_AVS),
                    "avsPollVoltageTransDone poll timeout");

    }

fapi_try_exit:
    return fapi2::current_err;
}

//##############################################################################
// Function which outputs a downstream command
//##############################################################################
fapi2::ReturnCode
avsDriveCommand(const fapi2::Target<fapi2::TARGET_TYPE_PROC_CHIP>& i_target,
                const uint8_t  i_avsBusNum,
                const uint8_t  i_o2sBridgeNum,
                const uint32_t i_RailSelect,
                const uint32_t i_CmdType,
                const uint32_t i_CmdGroup,
                const uint32_t i_CmdDataType,
                const uint32_t i_CmdData,
                enum avsBusOpType i_opType)
{

    fapi2::buffer<uint64_t> l_data64;
    fapi2::buffer<uint32_t> l_data64WithoutCRC;
    fapi2::ReturnCode l_rc;

    uint32_t l_StartCode = 0b01;
    uint32_t l_Reserved = 0b000;
    uint32_t l_crc;

    // clear sticky bits in o2s_status_reg
    l_data64.setBit<1, 1>();
    FAPI_TRY(putScom(i_target,
                     p9avslib::OCB_O2SCMD[i_avsBusNum][i_o2sBridgeNum],
                     l_data64));

    // MSB sent out first always, which should be start code 0b01
    // compose and send frame
    //      CRC(31:29),
    //      l_Reserved(28:13) (read), CmdData(28:13) (write)
    //      RailSelect(12:9),
    //      l_CmdDataType(8:5),
    //      l_CmdGroup(4),
    //      l_CmdType(3:2),
    //      l_StartCode(1:0)
    l_data64.flush<0>();
    l_data64.insertFromRight<0, 2>(l_StartCode);
    l_data64.insertFromRight<2, 2>(i_CmdType);
    l_data64.insertFromRight<4, 1>(i_CmdGroup);
    l_data64.insertFromRight<5, 4>(i_CmdDataType);
    l_data64.insertFromRight<9, 4>(i_RailSelect);
    l_data64.insertFromRight<13, 16>(i_CmdData);
    l_data64.insertFromRight<29, 3>(l_Reserved);

    // Generate CRC
    l_data64.extract(l_data64WithoutCRC, 0, 32);
    l_crc = avsCRCcalc(l_data64WithoutCRC);
    l_data64.insertFromRight<29, 3>(l_crc);
    FAPI_TRY(putScom(i_target,
                     p9avslib::OCB_O2SWD[i_avsBusNum][i_o2sBridgeNum], l_data64));

    // Wait on o2s_ongoing = 0
    FAPI_TRY(avsPollVoltageTransDone(i_target, i_avsBusNum, i_o2sBridgeNum));
    // Note:  caller will check for the specific timeout return code.

fapi_try_exit:

    if (fapi2::current_err)
    {
        FAPI_ASSERT(false,
                    fapi2::PROCPM_AVSBUS_VOLTAGE_TIMEOUT()
                    .set_CHIP_TARGET(i_target)
                    .set_AVSBUS_NUM(i_avsBusNum)
                    .set_AVSBUS_BRIDGE_NUM(i_o2sBridgeNum)
                    .set_AVSBUS_CMD_TYPE(i_CmdType)
                    .set_AVSBUS_CMD_GROUP(i_CmdGroup)
                    .set_AVSBUS_CMD_DATATYPE(i_CmdDataType)
                    .set_AVSBUS_RAILSELECT(i_RailSelect)
                    .set_AVSBUS_CMD_DATA(i_CmdData)
                    .set_CRC(l_crc)
                    .set_AVSBUS_OP_TYPE(i_opType),
                    "AVS bus driver command funciton fail");
    }

    return fapi2::current_err;
}

//##############################################################################
// Function which writes to OCB registers to initiate a AVS read transaction
//##############################################################################
fapi2::ReturnCode
avsVoltageRead(const fapi2::Target<fapi2::TARGET_TYPE_PROC_CHIP>& i_target,
               const uint8_t i_avsBusNum,
               const uint8_t i_o2sBridgeNum,
               const uint32_t i_RailSelect,
               uint32_t& o_Voltage)
{

    fapi2::buffer<uint64_t> l_data64;

    // Values as per VRM spec
    // Cmd 0b11, cmd group 0, cmd data type - 0b0000 for voltage read,
    // outbound data = 0xFFFf
    uint32_t l_CmdType     = 3; // read
    uint32_t l_CmdGroup    = 0;
    uint32_t l_CmdDataType = 0;
    uint32_t l_outboundCmdData = 0xFFFF;

    // Drive a Read Command
    FAPI_TRY(avsDriveCommand(i_target,
                             i_avsBusNum,
                             i_o2sBridgeNum,
                             i_RailSelect,
                             l_CmdType,
                             l_CmdGroup,
                             l_CmdDataType,
                             l_outboundCmdData));

    // Read returned voltage value from Read frame
    FAPI_TRY(getScom(i_target,
                     p9avslib::OCB_O2SRD[i_avsBusNum][i_o2sBridgeNum], l_data64));
    // Extracting bits 8:23 , which contains voltage read data
    o_Voltage = (l_data64 & 0x00FFFF0000000000) >> 40;

    FAPI_INF("Voltage value read is %d mV", o_Voltage);

fapi_try_exit:
    return fapi2::current_err;
}

//##############################################################################
// Function which writes to OCB registers to initiate a AVS write transaction
//##############################################################################
fapi2::ReturnCode
avsVoltageWrite(const fapi2::Target<fapi2::TARGET_TYPE_PROC_CHIP>& i_target,
                const uint8_t i_avsBusNum,
                const uint8_t i_o2sBridgeNum,
                const uint32_t i_RailSelect,
                const uint32_t i_Voltage)
{

    uint32_t l_CmdType     = 0; // write and commit
    uint32_t l_CmdGroup    = 0;
    uint32_t l_CmdDataType = 0;

    // Drive a Write Command
    FAPI_TRY(avsDriveCommand(i_target,
                             i_avsBusNum,
                             i_o2sBridgeNum,
                             i_RailSelect,
                             l_CmdType,
                             l_CmdGroup,
                             l_CmdDataType,
                             i_Voltage));

fapi_try_exit:
    return fapi2::current_err;
}

//##############################################################################
// Function which writes to OCB registers to initialize the AVS Slave with an
// idle frame
//##############################################################################
fapi2::ReturnCode
avsIdleFrame(const fapi2::Target<fapi2::TARGET_TYPE_PROC_CHIP>& i_target,
             const uint8_t i_avsBusNum,
             const uint8_t i_o2sBridgeNum)
{
    fapi2::buffer<uint64_t> l_idleframe = 0xFFFFFFFFFFFFFFFFull;
    fapi2::buffer<uint64_t> l_scomdata;

    // clear sticky bits in o2s_status_reg
    l_scomdata.setBit<1, 1>();
    FAPI_TRY(putScom(i_target,
                     p9avslib::OCB_O2SCMD[i_avsBusNum][i_o2sBridgeNum],
                     l_scomdata),
             "Error clearing sticky bits in o2s_status_reg");

    FAPI_INF("Sending idle frame of all 1s");
    // Send the idle frame
    l_scomdata = l_idleframe;
    FAPI_TRY(putScom(i_target,
                     p9avslib::OCB_O2SWD[i_avsBusNum][i_o2sBridgeNum],
                     l_scomdata));

    // Wait on o2s_ongoing = 0
    FAPI_TRY(avsPollVoltageTransDone(i_target, i_avsBusNum, i_o2sBridgeNum));

fapi_try_exit:

    if (fapi2::current_err)
    {
        FAPI_ASSERT(false,
                    fapi2::PROCPM_AVSBUS_IDLEFRAME_TIMEOUT()
                    .set_CHIP_TARGET(i_target)
                    .set_AVSBUS_NUM(i_avsBusNum)
                    .set_AVSBUS_BRIDGE_NUM(i_o2sBridgeNum),
                    "AVS Idle frame function fails");
    }

    return fapi2::current_err;
}

//##############################################################################
// Function which reads the data response from the AVSBus and validates it.
//##############################################################################
fapi2::ReturnCode
avsValidateResponse(const fapi2::Target<fapi2::TARGET_TYPE_PROC_CHIP>& i_target,
                    const uint8_t i_avsBusNum,
                    const uint8_t i_o2sBridgeNum,
                    const uint8_t i_throw_assert,
                    uint8_t& o_goodResponse
                   )
{
    fapi2::buffer<uint64_t> l_data64;
    fapi2::buffer<uint32_t> l_rsp_rcvd_crc;
    fapi2::buffer<uint8_t>  l_data_status_code;
    fapi2::buffer<uint32_t> l_rsp_data;

    uint32_t l_rsp_computed_crc;
    o_goodResponse = false;

    // Read the data response register
    FAPI_DBG("Reading the OS2SRD register to check status");
    FAPI_TRY(getScom(i_target, p9avslib::OCB_O2SRD[i_avsBusNum][i_o2sBridgeNum],
                     l_data64));

    // Status Return Code and Received CRC
    l_data64.extractToRight(l_data_status_code, 0, 2);
    l_data64.extractToRight(l_rsp_rcvd_crc, 29, 3);
    l_data64.extractToRight(l_rsp_data, 0, 32);

    // Compute CRC on Response frame
    l_rsp_computed_crc = avsCRCcalc(l_rsp_data);

    if ((l_data_status_code == 0) &&                           // no error code
        (l_rsp_rcvd_crc == l_rsp_computed_crc) &&              // good crc
        (l_rsp_data != 0) && (l_rsp_data != 0xFFFFFFFF))       // valid response
    {
        o_goodResponse = true;
    }
    else
    {

        FAPI_INF("Incorrect response received - Computed CRC %X Received %X - Full Response %08X",
                 l_rsp_computed_crc, l_rsp_rcvd_crc, l_rsp_data);

        if(l_rsp_data == 0x00000000)
        {
            FAPI_DBG("ERROR: AVS command failed. All 0 response data received possibly due to AVSBus IO RI/DIs disabled.");
            FAPI_ASSERT((i_throw_assert != true),
                        fapi2::PM_AVSBUS_ZERO_RESP_ERROR()
                        .set_TARGET(i_target)
                        .set_BUS(i_avsBusNum)
                        .set_BRIDGE(i_o2sBridgeNum),
                        "ERROR: AVS command failed. All 0 response data received possibly due to AVSBus IO RI/DIs disabled.");
        }
        else if(l_rsp_data == 0xFFFFFFFF)
        {
            FAPI_DBG("ERROR: AVS command failed failed. No response from VRM device, Check AVSBus interface connectivity to VRM in system.");
            FAPI_ASSERT((i_throw_assert != true),
                        fapi2::PM_AVSBUS_NO_RESP_ERROR()
                        .set_TARGET(i_target)
                        .set_BUS(i_avsBusNum)
                        .set_BRIDGE(i_o2sBridgeNum),
                        "ERROR: AVS command failed. No response from VRM device, Check AVSBus interface connectivity to VRM in system.");
        }
        else if(l_rsp_rcvd_crc != l_rsp_computed_crc)
        {
            FAPI_DBG("ERROR: AVS command failed failed. Bad CRC detected by P9 on AVSBus Slave Segement.");
            FAPI_ASSERT((i_throw_assert != true),
                        fapi2::PM_AVSBUS_MASTER_BAD_CRC_ERROR()
                        .set_TARGET(i_target)
                        .set_BUS(i_avsBusNum)
                        .set_BRIDGE(i_o2sBridgeNum),
                        "ERROR: AVS command failed. Bad CRC detected by P9 on AVSBus Slave Segement.");
        }
        else if(l_data_status_code == 0x02)
        {
            FAPI_DBG("ERROR: AVS command failed failed. Bad CRC indicated by Slave VRM on AVSBus Master Segement.");
            FAPI_ASSERT((i_throw_assert != true),
                        fapi2::PM_AVSBUS_SLAVE_BAD_CRC_ERROR()
                        .set_TARGET(i_target)
                        .set_BUS(i_avsBusNum)
                        .set_BRIDGE(i_o2sBridgeNum),
                        "ERROR: AVS command failed. Bad CRC indicated by Slave VRM on AVSBus Master Segement.");
        }
        else if(l_data_status_code == 0x01)
        {
            FAPI_DBG("ERROR: AVS command failed failed. Valid data sent but no action is taken due to unavailable resource.");
            FAPI_ASSERT((i_throw_assert != true),
                        fapi2::PM_AVSBUS_UNAVAILABLE_RESOURCE_ERROR()
                        .set_TARGET(i_target)
                        .set_BUS(i_avsBusNum)
                        .set_BRIDGE(i_o2sBridgeNum),
                        "ERROR: AVS command failed. Valid data sent but no action is taken due to unavailable resource.");
        }
        else if(l_data_status_code == 0x03)
        {
            FAPI_DBG("ERROR: AVS command failed failed. Unknown resource, invalid data, incorrect data or incorrect action.");
            FAPI_ASSERT((i_throw_assert != true),
                        fapi2::PM_AVSBUS_INVALID_DATA_ERROR()
                        .set_TARGET(i_target)
                        .set_BUS(i_avsBusNum)
                        .set_BRIDGE(i_o2sBridgeNum),
                        "ERROR: AVS command failed. Unknown resource, invalid data, incorrect data or incorrect action.");
        }
    }

fapi_try_exit:
    return fapi2::current_err;

}
