/***************************************************************************//**
* \file cy_scb_ezi2c.c
* \version 2.40
*
* Provides EZI2C API implementation of the SCB driver.
*
********************************************************************************
* \copyright
* Copyright 2016-2019 Cypress Semiconductor Corporation
* SPDX-License-Identifier: Apache-2.0
*
* 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.
*******************************************************************************/

#include "cy_scb_ezi2c.h"

#ifdef CY_IP_MXSCB

#if defined(__cplusplus)
extern "C" {
#endif


/***************************************
*        Function Prototypes
***************************************/

static void HandleErrors      (CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context);
static void HandleAddress     (CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context);
static void UpdateRxFifoLevel (CySCB_Type *base, uint32_t bufSize);
static void HandleDataReceive (CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context);
static void HandleDataTransmit(CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context);
static void HandleStop        (CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context);
static void UpdateAddressMask (CySCB_Type *base, cy_stc_scb_ezi2c_context_t const *context);


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_Init
****************************************************************************//**
*
* Initializes the SCB for the EZI2C operation.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param config
* The pointer to the configuration structure \ref cy_stc_scb_ezi2c_config_t.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t
* allocated by the user. The structure is used during the EZI2C operation for
* internal configuration and data retention. The user must not modify anything
* in this structure.
*
* \return
* \ref cy_en_scb_ezi2c_status_t
*
* \note
* Ensure that the SCB block is disabled before calling this function.
*
*******************************************************************************/
cy_en_scb_ezi2c_status_t Cy_SCB_EZI2C_Init(CySCB_Type *base, cy_stc_scb_ezi2c_config_t const *config,
                                           cy_stc_scb_ezi2c_context_t *context)
{
    /* Input parameters verification */
    if ((NULL == base) || (NULL == config) || (NULL == context))
    {
        return CY_SCB_EZI2C_BAD_PARAM;
    }

    CY_ASSERT_L2(CY_SCB_IS_I2C_ADDR_VALID(config->slaveAddress1));
    CY_ASSERT_L2(CY_SCB_IS_I2C_ADDR_VALID(config->slaveAddress2));
    CY_ASSERT_L2(config->slaveAddress1 != config->slaveAddress2);
    CY_ASSERT_L3(CY_SCB_EZI2C_IS_NUM_OF_ADDR_VALID  (config->numberOfAddresses));
    CY_ASSERT_L3(CY_SCB_EZI2C_IS_SUB_ADDR_SIZE_VALID(config->subAddressSize));

    /* Configure the EZI2C interface */
    SCB_CTRL(base) = _BOOL2FLD(SCB_CTRL_ADDR_ACCEPT, (config->numberOfAddresses == CY_SCB_EZI2C_TWO_ADDRESSES)) |
                 _BOOL2FLD(SCB_CTRL_EC_AM_MODE, config->enableWakeFromSleep) |
                 SCB_CTRL_BYTE_MODE_Msk;

    SCB_I2C_CTRL(base) = CY_SCB_EZI2C_I2C_CTRL;

    /* Configure the RX direction */
    SCB_RX_CTRL(base)      = CY_SCB_EZI2C_RX_CTRL;
    SCB_RX_FIFO_CTRL(base) = 0UL;

    /* Set the default address and mask */
    if (config->numberOfAddresses == CY_SCB_EZI2C_ONE_ADDRESS)
    {
        context->address2 = 0U;
        Cy_SCB_EZI2C_SetAddress1(base, config->slaveAddress1, context);
    }
    else
    {
        Cy_SCB_EZI2C_SetAddress1(base, config->slaveAddress1, context);
        Cy_SCB_EZI2C_SetAddress2(base, config->slaveAddress2, context);
    }

    /* Configure the TX direction */
    SCB_TX_CTRL(base)      = CY_SCB_EZI2C_TX_CTRL;
    SCB_TX_FIFO_CTRL(base) = CY_SCB_EZI2C_HALF_FIFO_SIZE;

    /* Configure the interrupt sources */
    SCB_INTR_SPI_EC_MASK(base) = 0UL;
    SCB_INTR_I2C_EC_MASK(base) = 0UL;
    SCB_INTR_RX_MASK(base)     = 0UL;
    SCB_INTR_TX_MASK(base)     = 0UL;
    SCB_INTR_M_MASK(base)      = 0UL;
    SCB_INTR_S_MASK(base)      = CY_SCB_EZI2C_SLAVE_INTR;

    /* Initialize the context */
    context->status = 0UL;
    context->state  = CY_SCB_EZI2C_STATE_IDLE;

    context->subAddrSize = config->subAddressSize;

    context->buf1Size      = 0UL;
    context->buf1rwBondary = 0UL;
    context->baseAddr1     = 0UL;

    context->buf1Size      = 0UL;
    context->buf1rwBondary = 0UL;
    context->baseAddr2     = 0UL;

    return CY_SCB_EZI2C_SUCCESS;
}


/*******************************************************************************
*  Function Name: Cy_SCB_EZI2C_DeInit
****************************************************************************//**
*
* De-initializes the SCB block, returns the register values to default.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \note
* Ensure that the SCB block is disabled before calling this function.
*
*******************************************************************************/
void Cy_SCB_EZI2C_DeInit(CySCB_Type *base)
{
    /* Return the block registers into the default state */
    SCB_CTRL(base)     = CY_SCB_CTRL_DEF_VAL;
    SCB_I2C_CTRL(base) = CY_SCB_I2C_CTRL_DEF_VAL;

    SCB_RX_CTRL(base)      = CY_SCB_RX_CTRL_DEF_VAL;
    SCB_RX_FIFO_CTRL(base) = 0UL;
    SCB_RX_MATCH(base)     = 0UL;

    SCB_TX_CTRL(base)      = CY_SCB_TX_CTRL_DEF_VAL;
    SCB_TX_FIFO_CTRL(base) = 0UL;

    SCB_INTR_SPI_EC_MASK(base) = 0UL;
    SCB_INTR_I2C_EC_MASK(base) = 0UL;
    SCB_INTR_RX_MASK(base)     = 0UL;
    SCB_INTR_TX_MASK(base)     = 0UL;
    SCB_INTR_M_MASK(base)      = 0UL;
    SCB_INTR_S_MASK(base)      = 0UL;
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_Disable
****************************************************************************//**
*
* Disables the SCB block and clears the context statuses.
* Note that after the block is disabled, the TX and RX FIFOs and hardware
* statuses are cleared. Also, the hardware stops driving the output and
* ignores the input.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t
* allocated by the user. The structure is used during the EZI2C operation for
* internal configuration and data retention. The user must not modify anything
* in this structure.
*
* \note
* Calling this function while EZI2C is busy (the slave has been addressed and is
* communicating with the master), may cause transaction corruption because
* the hardware stops driving the output and ignores the input. Ensure that
* the EZI2C slave is not busy before calling this function.
*
*******************************************************************************/
void Cy_SCB_EZI2C_Disable(CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context)
{
    SCB_CTRL(base) &= (uint32_t) ~SCB_CTRL_ENABLED_Msk;

    /* Set the state to default and clear the statuses */
    context->status = 0UL;
    context->state  = CY_SCB_EZI2C_STATE_IDLE;
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_DeepSleepCallback
****************************************************************************//**
*
* This function handles the transition of the EZI2C SCB into and out of
* Deep Sleep mode. It prevents the device from entering Deep Sleep mode if 
* the EZI2C slave is actively communicating.
* The following behavior of the EZI2C depends on whether the SCB block is
* wakeup-capable:
* * <b>Wakeup-capable</b>: on the incoming EZI2C slave address, the slave
*   receives the address and stretches the clock until the device is woken from
*   Deep Sleep mode. If the slave address occurs before the device enters
*   Deep Sleep mode, the device will not enter Deep Sleep mode.
* * <b>Not wakeup-capable</b>: the EZI2C is disabled. It is enabled 
*   when the device fails to enter Deep Sleep mode or it is woken from Deep Sleep
*   mode. While the EZI2C is disabled, it stops driving the outputs and
*   ignores the input lines. The slave NACKs all incoming addresses.
*
* This function must be called during execution of \ref Cy_SysPm_CpuEnterDeepSleep.
* To do this, register this function as a callback before calling
* \ref Cy_SysPm_CpuEnterDeepSleep : specify \ref CY_SYSPM_DEEPSLEEP as the callback
* type and call \ref Cy_SysPm_RegisterCallback.
*
* \param callbackParams
* The pointer to the callback parameters structure.
* \ref cy_stc_syspm_callback_params_t.
*
* \param mode
* Callback mode, see \ref cy_en_syspm_callback_mode_t
*
* \return
* \ref cy_en_syspm_status_t
*
* \note
* Only applicable for <b>rev-08 of the CY8CKIT-062-BLE</b>.
* For proper operation, when the EZI2C slave is configured to be a wakeup source
* from Deep Sleep mode, this function must be copied and modified by the user.
* The EZI2C clock disable code must be inserted in the
* \ref CY_SYSPM_BEFORE_TRANSITION and clock enable code in the 
* \ref CY_SYSPM_AFTER_TRANSITION mode processing.
*
*******************************************************************************/
cy_en_syspm_status_t Cy_SCB_EZI2C_DeepSleepCallback(cy_stc_syspm_callback_params_t *callbackParams, cy_en_syspm_callback_mode_t mode)
{
    CySCB_Type *locBase = (CySCB_Type *) callbackParams->base;
    cy_stc_scb_ezi2c_context_t *locContext = (cy_stc_scb_ezi2c_context_t *) callbackParams->context;

    cy_en_syspm_status_t retStatus = CY_SYSPM_FAIL;

    switch (mode)
    {
        case CY_SYSPM_CHECK_READY:
        {
            /* Disable the slave interrupt sources to protect the state */
            Cy_SCB_SetSlaveInterruptMask(locBase, CY_SCB_CLEAR_ALL_INTR_SRC);

            /* If the EZI2C is in the IDLE state, it is ready for Deep Sleep
            *  mode. Otherwise, it returns fail and restores the slave interrupt
            * sources.
            */
            if (CY_SCB_EZI2C_STATE_IDLE == locContext->state)
            {
                if (_FLD2BOOL(SCB_CTRL_EC_AM_MODE, SCB_CTRL(locBase)))
                {
                    /* The SCB is wakeup-capable: do not restore the address
                    * match interrupt source. The next transaction intended
                    * for the slave will be paused (the SCL is stretched) before
                    * the address is ACKed because the corresponding interrupt
                    * source is disabled.
                    */
                    Cy_SCB_SetSlaveInterruptMask(locBase, CY_SCB_EZI2C_SLAVE_INTR_NO_ADDR);
                }
                else
                {
                    /* The SCB is NOT wakeup-capable: disable the EZI2C.
                    * The slave stops responding to the master until the
                    * EZI2C is enabled. This happens when the device fails
                    * to enter Deep Sleep mode or it is woken from Deep Sleep
                    * mode.
                    */
                    Cy_SCB_EZI2C_Disable(locBase, locContext);
                    Cy_SCB_SetSlaveInterruptMask(locBase, CY_SCB_EZI2C_SLAVE_INTR);
                }

                retStatus = CY_SYSPM_SUCCESS;
            }
            else
            {
                /* Restore the slave interrupt sources */
                Cy_SCB_SetSlaveInterruptMask(locBase, CY_SCB_EZI2C_SLAVE_INTR);
            }
        }
        break;

        case CY_SYSPM_CHECK_FAIL:
        {
            /* The other driver is not ready for Deep Sleep mode. Restore
            * Active mode configuration.
            */

            if (_FLD2BOOL(SCB_CTRL_EC_AM_MODE, SCB_CTRL(locBase)))
            {
                /* The SCB is wakeup-capable: restore the slave interrupt
                * sources.
                */
                Cy_SCB_SetSlaveInterruptMask(locBase, CY_SCB_EZI2C_SLAVE_INTR);
            }
            else
            {
                /* The SCB is NOT wakeup-capable: enable the slave to operate. */
                Cy_SCB_EZI2C_Enable(locBase);
            }

            retStatus = CY_SYSPM_SUCCESS;
        }
        break;

        case CY_SYSPM_BEFORE_TRANSITION:
        {
            /* This code executes inside the critical section and enabling the
            * active interrupt source makes the interrupt pending in the NVIC.
            * However, the interrupt processing is delayed until the code exists
            * the critical section. The pending interrupt force WFI instruction
            * does nothing and the device remains in Active mode.
            */

            if (_FLD2BOOL(SCB_CTRL_EC_AM_MODE, SCB_CTRL(locBase)))
            {
                /* The SCB is wakeup-capable: enable the I2C wakeup interrupt
                * source. If any transaction was paused the the EZI2C interrupt
                * becomes pending and prevents entering Deep Sleep mode.
                * The transaction continues as soon as the global interrupts
                * are enabled.
                */
                Cy_SCB_SetI2CInterruptMask(locBase, CY_SCB_I2C_INTR_WAKEUP);

                /* Disable SCB clock */
                SCB_I2C_CFG(locBase) &= (uint32_t) ~CY_SCB_I2C_CFG_CLK_ENABLE_Msk;
                            
                /* IMPORTANT (replace line above for the CY8CKIT-062 rev-08): 
                * for proper entering Deep Sleep mode the I2C clock must be disabled. 
                * This code must be inserted by the user because the driver 
                * does not have access to the clock.
                */
            }

            retStatus = CY_SYSPM_SUCCESS;
        }
        break;

        case CY_SYSPM_AFTER_TRANSITION:
        {
            if (_FLD2BOOL(SCB_CTRL_EC_AM_MODE, SCB_CTRL(locBase)))
            {
                /* Enable SCB clock */
                SCB_I2C_CFG(locBase) |= CY_SCB_I2C_CFG_CLK_ENABLE_Msk;
                
                /* IMPORTANT (replace line above for the CY8CKIT-062 rev-08): 
                * for proper exiting Deep Sleep mode, the I2C clock must be enabled. 
                * This code must be inserted by the user because the driver 
                * does not have access to the clock.
                */

                /* The SCB is wakeup-capable: disable the I2C wakeup interrupt
                * source and restore slave interrupt sources.
                */
                Cy_SCB_SetI2CInterruptMask  (locBase, CY_SCB_CLEAR_ALL_INTR_SRC);
                Cy_SCB_SetSlaveInterruptMask(locBase, CY_SCB_EZI2C_SLAVE_INTR);
            }
            else
            {
                /* The SCB is NOT wakeup-capable: enable the slave to operate */
                Cy_SCB_EZI2C_Enable(locBase);
            }

            retStatus = CY_SYSPM_SUCCESS;
        }
        break;

        default:
            break;
    }

    return (retStatus);
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_HibernateCallback
****************************************************************************//**
*
* This function handles the transition of the EZI2C SCB block into Hibernate
* mode. It prevents the device from entering Hibernate mode if the EZI2C slave 
* is actively communicating.
* If the EZI2C is ready to enter Hibernate mode, it is disabled. If the device
* fails to enter Hibernate mode, the EZI2C is enabled. While the EZI2C
* is disabled, it stops driving the output and ignores the inputs.
* The slave NACKs all incoming addresses.
*
* This function must be called during execution of \ref Cy_SysPm_SystemEnterHibernate.
* To do this, register this function as a callback before calling
* \ref Cy_SysPm_SystemEnterHibernate : specify \ref CY_SYSPM_HIBERNATE as the callback
* type and call \ref Cy_SysPm_RegisterCallback.
*
* \param callbackParams
* The pointer to the callback parameters structure
* \ref cy_stc_syspm_callback_params_t.
*
* \param mode
* Callback mode, see \ref cy_en_syspm_callback_mode_t
*
* \return
* \ref cy_en_syspm_status_t
*
*******************************************************************************/
cy_en_syspm_status_t Cy_SCB_EZI2C_HibernateCallback(cy_stc_syspm_callback_params_t *callbackParams, cy_en_syspm_callback_mode_t mode)
{
    CySCB_Type *locBase = (CySCB_Type *) callbackParams->base;
    cy_stc_scb_ezi2c_context_t *locContext = (cy_stc_scb_ezi2c_context_t *) callbackParams->context;

    cy_en_syspm_status_t retStatus = CY_SYSPM_FAIL;

    switch (mode)
    {
        case CY_SYSPM_CHECK_READY:
        {
            /* Disable the slave interrupt sources to protect the state */
            Cy_SCB_SetSlaveInterruptMask(locBase, CY_SCB_CLEAR_ALL_INTR_SRC);

            /* If the EZI2C is in the IDLE state, it is ready for Hibernate mode.
            * Otherwise, returns fail and restores the slave interrupt sources.
            */
            if (CY_SCB_EZI2C_STATE_IDLE == locContext->state)
            {
                /* Disable the EZI2C. It stops responding to the master until
                * the EZI2C is enabled. This happens if the device fails to
                * enter Hibernate mode.
                */
                Cy_SCB_EZI2C_Disable(locBase, locContext);

                retStatus = CY_SYSPM_SUCCESS;
            }

            /* Restore the slave interrupt sources */
            Cy_SCB_SetSlaveInterruptMask(locBase, CY_SCB_EZI2C_SLAVE_INTR);
        }
        break;

        case CY_SYSPM_CHECK_FAIL:
        {
            /* The other driver is not ready for Hibernate mode. Restore the
            * Active mode configuration.
            */

            /* Enable the slave to operate */
            Cy_SCB_EZI2C_Enable(locBase);

            retStatus = CY_SYSPM_SUCCESS;
        }
        break;

        case CY_SYSPM_BEFORE_TRANSITION:
        case CY_SYSPM_AFTER_TRANSITION:
        {
            /* The SCB is not capable of waking up from Hibernate mode: do nothing */
            retStatus = CY_SYSPM_SUCCESS;
        }
        break;

        default:
        break;
    }

    return (retStatus);
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_GetActivity
****************************************************************************//**
*
* Returns a non-zero value if an I2C Read or Write cycle has occurred since the
* last time this function was called. All flags are reset to zero at the end of
* this function call, except the \ref CY_SCB_EZI2C_STATUS_BUSY.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t
* allocated by the user. The structure is used during the EZI2C operation for
* internal configuration and data retention. The user must not modify anything
* in this structure.
*
* \return
* \ref group_scb_ezi2c_macros_get_activity.
*
*******************************************************************************/
uint32_t Cy_SCB_EZI2C_GetActivity(CySCB_Type const *base, cy_stc_scb_ezi2c_context_t *context)
{
    uint32_t intrState;
    uint32_t retStatus;

    /* Suppress a compiler warning about unused variables */
    (void) base;

    intrState = Cy_SysLib_EnterCriticalSection();

    retStatus = context->status;
    context->status &= CY_SCB_EZI2C_STATUS_BUSY;

    Cy_SysLib_ExitCriticalSection(intrState);

    return (retStatus);
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_SetAddress1
****************************************************************************//**
*
* Sets the primary EZI2C slave address.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param addr
* The 7-bit right justified slave address.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t
* allocated by the user. The structure is used during the EZI2C operation for
* internal configuration and data retention. The user must not modify anything
* in this structure.
*
*******************************************************************************/
void Cy_SCB_EZI2C_SetAddress1(CySCB_Type *base, uint8_t addr, cy_stc_scb_ezi2c_context_t *context)
{
    CY_ASSERT_L2(CY_SCB_IS_I2C_ADDR_VALID(addr));
    CY_ASSERT_L2(addr != context->address2);

    context->address1 = addr;

    CY_REG32_CLR_SET(SCB_RX_MATCH(base), SCB_RX_MATCH_ADDR, ((uint32_t)((uint32_t) addr << 1UL)));

    UpdateAddressMask(base, context);
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_GetAddress1
****************************************************************************//**
*
* Returns the primary the EZI2C slave address.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* * \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t
* allocated by the user. The structure is used during the EZI2C operation for
* internal configuration and data retention. The user must not modify anything
* in this structure.
*
* \return
* The 7-bit right justified slave address.
*
*******************************************************************************/
uint32_t Cy_SCB_EZI2C_GetAddress1(CySCB_Type const *base, cy_stc_scb_ezi2c_context_t const *context)
{
    /* Suppress a compiler warning about unused variables */
    (void) base;

    return ((uint32_t) context->address1);
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_SetBuffer1
****************************************************************************//**
*
* Sets up the data buffer to be exposed to the I2C master on the primary slave
* address request.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param buffer
* The pointer to the data buffer.
*
* \param size
* The size of the buffer in bytes.
*
* \param rwBoundary
* The number of data bytes starting from the beginning of the buffer with Read and
* Write access. The data bytes located at rwBoundary or greater are read only.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t
* allocated by the user. The structure is used during the EZI2C operation for
* internal configuration and data retention. The user must not modify anything
* in this structure.
*
* \note
* * This function is not interrupt-protected and to prevent a race condition,
*   it must be protected from the EZI2C interruption in the place where it
*   is called.
* * Calling this function in the middle of a transaction intended for the
*   secondary slave address leads to unexpected behavior.
*
*******************************************************************************/
void Cy_SCB_EZI2C_SetBuffer1(CySCB_Type const *base, uint8_t *buffer, uint32_t size, uint32_t rwBoundary,
                             cy_stc_scb_ezi2c_context_t *context)
{
    CY_ASSERT_L1(CY_SCB_IS_I2C_BUFFER_VALID(buffer, size));
    CY_ASSERT_L2(rwBoundary <= size);

    /* Suppress a compiler warning about unused variables */
    (void) base;

    context->buf1          = buffer;
    context->buf1Size      = size;
    context->buf1rwBondary = rwBoundary;
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_SetAddress2
****************************************************************************//**
*
* Sets the secondary EZI2C slave address.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param addr
* The 7-bit right justified slave address.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t
* allocated by the user. The structure is used during the EZI2C operation for
* internal configuration and data retention. The user must not modify anything
* in this structure.
*
* \note
* Calling this function when the EZI2C slave is configured for one-address
* operation leads to unexpected behavior because it updates the address mask.
*
*******************************************************************************/
void Cy_SCB_EZI2C_SetAddress2(CySCB_Type *base, uint8_t addr, cy_stc_scb_ezi2c_context_t *context)
{
    CY_ASSERT_L2(CY_SCB_IS_I2C_ADDR_VALID(addr));
    CY_ASSERT_L2(addr != context->address1);

    context->address2 = addr;

    UpdateAddressMask(base, context);
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_GetAddress2
****************************************************************************//**
*
* Returns the secondary EZI2C slave address.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t
* allocated by the user. The structure is used during the EZI2C operation for
* internal configuration and data retention. The user must not modify anything
* in this structure.
*
* \return
* The 7-bit right justified slave address.
*
*******************************************************************************/
uint32_t Cy_SCB_EZI2C_GetAddress2(CySCB_Type const *base, cy_stc_scb_ezi2c_context_t const *context)
{
    /* Suppress a compiler warning about unused variables */
    (void) base;

    return ((uint32_t) context->address2);
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_SetBuffer2
****************************************************************************//**
*
* Sets up the data buffer to be exposed to the I2C master on the secondary
* slave address request.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param buffer
* The pointer to the data buffer.
*
* \param size
* The size of the buffer in bytes.
*
* \param rwBoundary
* The number of data bytes starting from the beginning of the buffer with Read and
* Write access. The data bytes located at rwBoundary or greater are read only.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t
* allocated by the user. The structure is used during the EZI2C operation for
* internal configuration and data retention. The user must not modify anything
* in this structure.
*
* \note
* * This function is not interrupt-protected. To prevent a race condition,
*   it must be protected from the EZI2C interruption in the place where it
*   is called.
* * Calling this function in the middle of a transaction intended for the
*   secondary slave address leads to unexpected behavior.
*
*******************************************************************************/
void Cy_SCB_EZI2C_SetBuffer2(CySCB_Type const *base, uint8_t *buffer, uint32_t size, uint32_t rwBoundary,
                             cy_stc_scb_ezi2c_context_t *context)
{
    CY_ASSERT_L1(CY_SCB_IS_I2C_BUFFER_VALID(buffer, size));
    CY_ASSERT_L2(rwBoundary <= size);

    /* Suppress a compiler warning about unused variables */
    (void) base;

    context->buf2          = buffer;
    context->buf2Size      = size;
    context->buf2rwBondary = rwBoundary;
}


/*******************************************************************************
* Function Name: Cy_SCB_EZI2C_Interrupt
****************************************************************************//**
*
* This is the interrupt function for the SCB configured in the EZI2C mode.
* This function must be called inside the user-defined interrupt service
* routine to make the EZI2C slave work.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t allocated
* by the user. The structure is used during the EZI2C operation for internal
* configuration and data retention. The user must not modify anything
* in this structure.
*
*******************************************************************************/
void Cy_SCB_EZI2C_Interrupt(CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context)
{
    uint32_t slaveIntrStatus;

    /* Handle an I2C wake-up event */
    if (0UL != (CY_SCB_I2C_INTR_WAKEUP & Cy_SCB_GetI2CInterruptStatusMasked(base)))
    {
        /* Move from IDLE state, the slave was addressed. Following address match 
        * interrupt continue transfer.
        */
        context->state = CY_SCB_EZI2C_STATE_ADDR;
        
        Cy_SCB_ClearI2CInterrupt(base, CY_SCB_I2C_INTR_WAKEUP);
    }

    /* Get the slave interrupt sources */
    slaveIntrStatus = Cy_SCB_GetSlaveInterruptStatusMasked(base);

    /* Handle the error conditions */
    if (0UL != (CY_SCB_EZI2C_SLAVE_INTR_ERROR & slaveIntrStatus))
    {
        HandleErrors(base, context);

        Cy_SCB_ClearSlaveInterrupt(base, CY_SCB_EZI2C_SLAVE_INTR_ERROR);

        /* Trigger the stop handling to complete the transaction */
        slaveIntrStatus |= CY_SCB_SLAVE_INTR_I2C_STOP;
    }
    else
    {
        if ((CY_SCB_EZI2C_STATE_RX_DATA1 == context->state) &&
            (0UL != (CY_SCB_SLAVE_INTR_I2C_STOP & slaveIntrStatus)))
        {
            /* Get data from the RX FIFO after Stop is generated */
            Cy_SCB_SetRxInterrupt    (base, CY_SCB_RX_INTR_LEVEL);
            Cy_SCB_SetRxInterruptMask(base, CY_SCB_RX_INTR_LEVEL);
        }
    }

    /* Handle the receive direction (master writes data) */
    if (0UL != (CY_SCB_RX_INTR_LEVEL & Cy_SCB_GetRxInterruptStatusMasked(base)))
    {
        HandleDataReceive(base, context);

        Cy_SCB_ClearRxInterrupt(base, CY_SCB_RX_INTR_LEVEL);
    }

    /* Handle the transaction completion */
    if (0UL != (CY_SCB_SLAVE_INTR_I2C_STOP & slaveIntrStatus))
    {
        HandleStop(base, context);

        Cy_SCB_ClearSlaveInterrupt(base, CY_SCB_SLAVE_INTR_I2C_STOP);

        /* Update the slave interrupt status */
        slaveIntrStatus = Cy_SCB_GetSlaveInterruptStatusMasked(base);
    }

    /* Handle the address byte */
    if (0UL != (CY_SCB_SLAVE_INTR_I2C_ADDR_MATCH & slaveIntrStatus))
    {
        HandleAddress(base, context);

        Cy_SCB_ClearI2CInterrupt  (base, CY_SCB_I2C_INTR_WAKEUP);
        Cy_SCB_ClearSlaveInterrupt(base, CY_SCB_SLAVE_INTR_I2C_ADDR_MATCH);
    }

    /* Handle the transmit direction (master reads data) */
    if (0UL != (CY_SCB_TX_INTR_LEVEL & Cy_SCB_GetTxInterruptStatusMasked(base)))
    {
        HandleDataTransmit(base, context);

        Cy_SCB_ClearTxInterrupt(base, CY_SCB_TX_INTR_LEVEL);
    }
}



/*******************************************************************************
* Function Name: HandleErrors
****************************************************************************//**
*
* Handles an error conditions.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t allocated
* by the user. The structure is used during the EZI2C operation for internal
* configuration and data retention. The user must not modify anything
* in this structure.
*
*******************************************************************************/
static void HandleErrors(CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context)
{
    context->status |= CY_SCB_EZI2C_STATUS_ERR;

    /* Drop any data available in the RX FIFO */
    Cy_SCB_ClearRxFifo(base);

    /* Stop the TX and RX processing */
    Cy_SCB_SetRxInterruptMask(base, CY_SCB_CLEAR_ALL_INTR_SRC);
    Cy_SCB_SetTxInterruptMask(base, CY_SCB_CLEAR_ALL_INTR_SRC);
}


/*******************************************************************************
* Function Name: HandleAddress
****************************************************************************//**
*
* Prepares the EZI2C slave for the following read or write transfer after the
* matched address was received.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t allocated
* by the user. The structure is used during the EZI2C operation for internal
* configuration and data retention. The user must not modify anything
* in this structure.
*
*******************************************************************************/
static void HandleAddress(CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context)
{
    /* Default actions: ACK address 1 */
    uint32_t cmd = SCB_I2C_S_CMD_S_ACK_Msk;
    context->addr1Active = true;

    if (0U != context->address2)
    {
        /* Get an address from the RX FIFO and make it a 7-bit address */
        uint32_t address = (Cy_SCB_ReadRxFifo(base) >> 1UL);
        Cy_SCB_ClearRxInterrupt(base, CY_SCB_RX_INTR_LEVEL);

        /* Decide whether the address matches */
        if ((address == context->address1) || (address == context->address2))
        {
            /* ACK the address */
            if (address == context->address2)
            {
                context->addr1Active = false;
            }

            /* Clear and enable the stop interrupt source */
            Cy_SCB_ClearSlaveInterrupt  (base, CY_SCB_SLAVE_INTR_I2C_STOP);
            Cy_SCB_SetSlaveInterruptMask(base, CY_SCB_EZI2C_SLAVE_INTR);
        }
        else
        {
            /* NACK the address */
            cmd = SCB_I2C_S_CMD_S_NACK_Msk;

            /* Disable the stop interrupt source */
            Cy_SCB_SetI2CInterruptMask(base, CY_SCB_EZI2C_SLAVE_INTR_NO_STOP);
        }
    }

    /* Clear the TX FIFO before continuing the transaction */
    Cy_SCB_ClearTxFifo(base);

    /* Set the command to an ACK or NACK address */
    SCB_I2C_S_CMD(base) = cmd;

    if (cmd == SCB_I2C_S_CMD_S_ACK_Msk)
    {
        context->status |= CY_SCB_EZI2C_STATUS_BUSY;

        /* Prepare for a transaction */
        if (_FLD2BOOL(SCB_I2C_STATUS_S_READ, SCB_I2C_STATUS(base)))
        {
            /* The master reads data from the slave */
            context->state = CY_SCB_EZI2C_STATE_TX_DATA;

            /* Prepare the buffer for transmit */
            if (context->addr1Active)
            {
                context->curBuf  = &context->buf1[context->baseAddr1];
                context->bufSize = context->buf1Size - context->baseAddr1;
            }
            else
            {
                context->curBuf  = &context->buf2[context->baseAddr2];
                context->bufSize = context->buf2Size - context->baseAddr2;
            }

            Cy_SCB_SetTxInterruptMask(base, CY_SCB_TX_INTR_LEVEL);
        }
        else
        {
            /* The master writes data into the slave */
            context->state = CY_SCB_EZI2C_STATE_RX_OFFSET_MSB;

            context->bufSize = ((context->addr1Active) ? context->buf1Size : context->buf2Size);

            Cy_SCB_SetRxFifoLevel    (base, 0UL);
            Cy_SCB_SetRxInterruptMask(base, CY_SCB_RX_INTR_LEVEL);
        }
    }
}


/*******************************************************************************
* Function Name: HandleDataReceive
****************************************************************************//**
*
* Updates the RX FIFO level to trigger the next read from it. It also manages
* the auto-data NACK feature.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param bufSize
* The size of the buffer in bytes.
*
*******************************************************************************/
static void UpdateRxFifoLevel(CySCB_Type *base, uint32_t bufSize)
{
    uint32_t level;
    uint32_t fifoSize = CY_SCB_EZI2C_FIFO_SIZE;

    if (bufSize > fifoSize)
    {
        /* Continue the transaction: there is space in the buffer */
        level = (bufSize - fifoSize);
        level = ((level > fifoSize) ? (fifoSize / 2UL) : level) - 1UL;
    }
    else
    {
        /* Prepare to end the transaction: after the FIFO becomes full, NACK the next byte.
        * The NACKed byte is dropped by the hardware.
        */
        SCB_I2C_CTRL(base) |= SCB_I2C_CTRL_S_NOT_READY_DATA_NACK_Msk;

        level = ((bufSize == 0UL) ? (0UL) : (bufSize - 1UL));
        Cy_SCB_SetRxInterruptMask(base, CY_SCB_CLEAR_ALL_INTR_SRC);
    }

    Cy_SCB_SetRxFifoLevel(base, level);
}


/*******************************************************************************
* Function Name: HandleDataReceive
****************************************************************************//**
*
* Handles the data read from the RX FIFO.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t allocated
* by the user. The structure is used during the EZI2C operation for internal
* configuration and data retention. The user must not modify anything
* in this structure.
*
*******************************************************************************/
static void HandleDataReceive(CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context)
{
    switch(context->state)
    {
        case CY_SCB_EZI2C_STATE_RX_OFFSET_MSB:
        case CY_SCB_EZI2C_STATE_RX_OFFSET_LSB:
        {
            /* Default actions: compare the base address and ACK it */
            bool checkBaseAddr = true;

            /* Get the base address from the RX FIFO */
            uint32_t baseAddr = Cy_SCB_ReadRxFifo(base);

            if (context->subAddrSize == CY_SCB_EZI2C_SUB_ADDR16_BITS)
            {
                if (context->state == CY_SCB_EZI2C_STATE_RX_OFFSET_MSB)
                {
                    /* ACK base address MSB */
                    SCB_I2C_S_CMD(base) = SCB_I2C_S_CMD_S_ACK_Msk;

                    /* Temporary store base address MSB */
                    context->idx = (uint32_t) (baseAddr << 8UL);

                    /* Do not compare until 16 bits are received */
                    checkBaseAddr = false;
                    context->state = CY_SCB_EZI2C_STATE_RX_OFFSET_LSB;
                }
                else
                {
                    /* Get the base address (MSB | LSB) */
                    baseAddr |= context->idx;
                }
            }

            /* Check whether the received base address is valid */
            if (checkBaseAddr)
            {
                uint32_t cmd = SCB_I2C_S_CMD_S_ACK_Msk;

                /* Decide whether the base address within the buffer range */
                if (baseAddr < context->bufSize)
                {
                    /* Accept the new base address */
                    if (context->addr1Active)
                    {
                        context->baseAddr1 = baseAddr;
                    }
                    else
                    {
                        context->baseAddr2 = baseAddr;
                    }

                    /* Store the base address to use it later */
                    context->idx = baseAddr;
                }
                else
                {
                    /* Restore the valid base address */
                    context->idx = ((context->addr1Active) ? context->baseAddr1 : context->baseAddr2);

                    /* The base address is out of range - NACK it */
                    cmd = SCB_I2C_S_CMD_S_NACK_Msk;
                }

                /* Set the command to an ACK or NACK address */
                SCB_I2C_S_CMD(base) = cmd;

                if (cmd == SCB_I2C_S_CMD_S_ACK_Msk)
                {
                    /* Prepare the buffer for a write */
                    if (context->addr1Active)
                    {
                        context->curBuf  = &context->buf1[context->baseAddr1];
                        context->bufSize = ((context->baseAddr1 < context->buf1rwBondary) ?
                                                    (context->buf1rwBondary - context->baseAddr1) : (0UL));
                    }
                    else
                    {
                        context->curBuf  = &context->buf2[context->baseAddr2];
                        context->bufSize = ((context->baseAddr2 < context->buf2rwBondary) ?
                                                    (context->buf2rwBondary - context->baseAddr2) : (0UL));
                    }

                    /* Choice receive scheme  */
                    if ((0U != context->address2) || (context->bufSize < CY_SCB_EZI2C_FIFO_SIZE))
                    {
                        /* Handle each byte separately */
                        context->state = CY_SCB_EZI2C_STATE_RX_DATA0;
                    }
                    else
                    {
                        /* Use the RX FIFO and the auto-ACK/NACK features */
                        SCB_I2C_CTRL(base) |= SCB_I2C_CTRL_S_READY_DATA_ACK_Msk;
                        UpdateRxFifoLevel(base, context->bufSize);

                        context->state = CY_SCB_EZI2C_STATE_RX_DATA1;
                    }
                }
            }
        }
        break;

        case CY_SCB_EZI2C_STATE_RX_DATA0:
        {
            uint32_t byte = Cy_SCB_ReadRxFifo(base);

            /* Check whether there is space to store the byte */
            if (context->bufSize > 0UL)
            {
                /* Continue the transfer: send an ACK */
                SCB_I2C_S_CMD(base) = SCB_I2C_S_CMD_S_ACK_Msk;

                /* Store the byte in the buffer */
                context->curBuf[0UL] = (uint8_t) byte;
                context->bufSize--;
                context->curBuf++;

                /* Update the base address to notice that the buffer is modified */
                context->idx++;
            }
            else
            {
                /* Finish the transfer: send a NACK. Drop the received byte */
                SCB_I2C_S_CMD(base) = SCB_I2C_S_CMD_S_NACK_Msk;
                Cy_SCB_SetRxInterruptMask(base, CY_SCB_CLEAR_ALL_INTR_SRC);
            }
        }
        break;

        case CY_SCB_EZI2C_STATE_RX_DATA1:
        {
            /* Get the number of bytes to read from the RX FIFO */
            uint32_t numToCopy = Cy_SCB_GetRxFifoLevel(base) + 1UL;

            /* Get data from the RX FIFO */
            numToCopy = Cy_SCB_ReadArray(base, context->curBuf, numToCopy);
            context->bufSize -= numToCopy;
            context->curBuf  += numToCopy;

            /* Configure the next RX FIFO read event */
            UpdateRxFifoLevel(base, context->bufSize);

            /* Update the base address to notice that the buffer is modified */
            context->idx++;
        }
        break;

        default:
        break;
    }
}


/*******************************************************************************
* Function Name: HandleDataTransmit
****************************************************************************//**
*
* Loads the TX FIFO with data from the buffer.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t allocated
* by the user. The structure is used during the EZI2C operation for internal
* configuration and data retention. The user must not modify anything
* in this structure.
*
*******************************************************************************/
static void HandleDataTransmit(CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context)
{
    if (context->bufSize > 0UL)
    {
        /* Write data into the TX FIFO from the buffer */
        uint32_t numToCopy = Cy_SCB_WriteArray(base, context->curBuf, context->bufSize);
        context->bufSize  -= numToCopy;
        context->curBuf   += numToCopy;
    }

    if (0UL == context->bufSize)
    {
        /* Write the default bytes into the TX FIFO */
        (void) Cy_SCB_WriteDefaultArray(base, CY_SCB_EZI2C_DEFAULT_TX, CY_SCB_EZI2C_FIFO_SIZE);
    }
}


/*******************************************************************************
* Function Name: HandleStop
****************************************************************************//**
*
* Handles the transfer completion.
* It is triggered by a Stop or Restart condition on the bus.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t allocated
* by the user. The structure is used during the EZI2C operation for internal
* configuration and data retention. The user must not modify anything
* in this structure.
*
*******************************************************************************/
static void HandleStop(CySCB_Type *base, cy_stc_scb_ezi2c_context_t *context)
{
    /* Check for errors */
    if (0UL != (CY_SCB_EZI2C_STATUS_ERR & context->status))
    {
        /* Re-enable the SCB to recover from errors */
        Cy_SCB_FwBlockReset(base);
    }

    /* Clean up the hardware to be ready for the next transaction */
    if (CY_SCB_EZI2C_STATE_TX_DATA == context->state)
    {
        Cy_SCB_SetTxInterruptMask(base, CY_SCB_CLEAR_ALL_INTR_SRC);
    }
    else
    {
        Cy_SCB_SetRxInterruptMask(base, CY_SCB_CLEAR_ALL_INTR_SRC);

        SCB_I2C_CTRL(base) &= (uint32_t) ~(SCB_I2C_CTRL_S_READY_DATA_ACK_Msk |
                                           SCB_I2C_CTRL_S_NOT_READY_DATA_NACK_Msk);
    }

    /* Update the statuses */
    context->status &= (uint32_t) ~CY_SCB_EZI2C_STATUS_BUSY;

    if (context->addr1Active)
    {
        context->status |= ((CY_SCB_EZI2C_STATE_TX_DATA == context->state) ? CY_SCB_EZI2C_STATUS_READ1 :
                             ((context->baseAddr1 != context->idx) ?  CY_SCB_EZI2C_STATUS_WRITE1 : 0UL));
    }
    else
    {
        context->status |= ((CY_SCB_EZI2C_STATE_TX_DATA == context->state) ? CY_SCB_EZI2C_STATUS_READ2 :
                             ((context->baseAddr2 != context->idx) ? CY_SCB_EZI2C_STATUS_WRITE2 : 0UL));
    }

    /* Back to the idle state */
    context->state = CY_SCB_EZI2C_STATE_IDLE;
}


/*******************************************************************************
* Function Name: UpdateAddressMask
****************************************************************************//**
*
* Updates the slave address mask to enable the SCB hardware to receive matching
* slave addresses.
*
* \param base
* The pointer to the EZI2C SCB instance.
*
* \param context
* The pointer to the context structure \ref cy_stc_scb_ezi2c_context_t allocated
* by the user. The structure is used during the EZI2C operation for internal
* configuration and data retention. The user must not modify anything
* in this structure.
*
*******************************************************************************/
static void UpdateAddressMask(CySCB_Type *base, cy_stc_scb_ezi2c_context_t const *context)
{
    uint32_t addrMask;

    /* Check how many addresses are used: */
    if (0U != context->address2)
    {
        /* If (addr1 and addr2) bits match - mask bit equals 1; otherwise 0 */
        addrMask  = (uint32_t) ~((uint32_t) context->address1 ^ (uint32_t) context->address2);
    }
    else
    {
        addrMask = CY_SCB_EZI2C_ONE_ADDRESS_MASK;
    }

    /* Update the hardware address match */
    CY_REG32_CLR_SET(SCB_RX_MATCH(base), SCB_RX_MATCH_MASK, ((uint32_t) addrMask << 1UL));
}


#if defined(__cplusplus)
}
#endif

#endif /* CY_IP_MXSCB */

/* [] END OF FILE */

