/***************************************************************************//**
* \file cy_scb_ezi2c.c
* \version 3.20
*
* Provides EZI2C API implementation of the SCB driver.
*
********************************************************************************
* \copyright
* Copyright 2016-2021 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_device.h"
#if (defined (CY_IP_MXSCB) || defined (CY_IP_MXS22SCB))
#include "cy_scb_ezi2c.h"
#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);
#if ((defined (CY_IP_MXSCB_VERSION) && CY_IP_MXSCB_VERSION==1))
SCB_CTRL(base) |= SCB_CTRL_BYTE_MODE_Msk;
#endif /* CY_IP_MXSCB_VERSION */
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->buf2Size = 0UL;
context->buf2rwBondary = 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:
* * Wakeup-capable: 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.
* * Not wakeup-capable: 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 rev-08 of the CY8CKIT-062-BLE.
* 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:
/* Unknown state */
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:
/* Unknown state */
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.
*
* \note
* Please observe following limitation while using both primary and secondary
* slave addresses. EZI2C IP has single address match mask. This mask is setup as
* (address1 ^ address2), This can generate false address match. For example, if
* address1 is 0x24 and address2 is 0x10, then slave address 0x20 will also
* generate a false match. Hence, choose address1 and 2 such that all other
* slave address on the bus satisfy
* ((slave address) & (address1 ^ address2)) != 0
*
*******************************************************************************/
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:
/* Unknown state */
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.
*
* \note
* Please observe following limitation while using both primary and secondary
* slave addresses. EZI2C IP has single address match mask. This mask is setup as
* (address1 ^ address2), This can generate false address match. For example, if
* address1 is 0x24 and address2 is 0x10, then slave address 0x20 will also
* generate a false match. Hence, choose address1 and 2 such that all other
* slave address on the bus satisfy
* ((slave address) & (address1 ^ address2)) != 0
*
*******************************************************************************/
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 /* (defined (CY_IP_MXSCB) || defined (CY_IP_MXS22SCB)) */
/* [] END OF FILE */