/** \file
 *  \brief This file implements messaging using inter-processor communication (IPC).
 *
*   \see https://community.cypress.com/thread/36182.
 */

/**
 *  \ingroup CONSOLE
 */

/* Include Files */
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <project.h>

#include "COMM_IPC_Messages.h"

/* Private Function Prototypes */

#if (__CORTEX_M == 0)
static void Message_Received_for_CM0(uint32_t * msg);
static void Message_Received_by_CM4(void);
#endif // (__CORTEX_M == 0)

#if (__CORTEX_M == 4)
static void Message_Received_for_CM4(uint32_t * msg);
static void Message_Received_by_CM0(void);
#endif // (__CORTEX_M == 4)

static void IPC_UserPipeInit(void);
static void IPC_UserPipeISR(void);


/* Local Definitions and Constants */

//! Number of clients supported on the user pipe.
#define CY_IPC_USRPIPE_CLIENT_CNT       (uint32_t)(8u)

#define CY_IPC_CHAN_USRPIPE_CM0         (uint32_t)(8u)
#define CY_IPC_CHAN_USRPIPE_CM4         (uint32_t)(9u)

#define CY_IPC_INTR_USRPIPE_CM0         (uint32_t)(8u)
#define CY_IPC_INTR_USRPIPE_CM4         (uint32_t)(9u)

#define CY_IPC_EP_USRPIPE_ADDR_CM0_EP   (uint32_t)(2u)
#define CY_IPC_EP_USRPIPE_ADDR_CM4_EP   (uint32_t)(3u)
    
#if (CY_CPU_CORTEX_M0P)
    #define IPC_EP_USRPIPE_ADDR         CY_IPC_EP_USRPIPE_ADDR_CM0_EP
#else
    #define IPC_EP_USRPIPE_ADDR         CY_IPC_EP_USRPIPE_ADDR_CM4_EP
#endif  /* (CY_CPU_CORTEX_M0P) */

/* User Pipe Configuration */

#define IPC_USRPIPE_CHAN_MASK_CM0   (uint32_t)(0x0001ul << CY_IPC_CHAN_USRPIPE_CM0)
#define IPC_USRPIPE_CHAN_MASK_CM4   (uint32_t)(0x0001ul << CY_IPC_CHAN_USRPIPE_CM4)
#define IPC_USRPIPE_INTR_MASK       (uint32_t)( IPC_USRPIPE_CHAN_MASK_CM0 | IPC_USRPIPE_CHAN_MASK_CM4 )
#define IPC_INTR_USRPIPE_PRIOR_CM0  (uint32_t)(1u)   /* Notifier Priority */
#define IPC_INTR_USRPIPE_PRIOR_CM4  (uint32_t)(1u)   /* Notifier Priority */
#define IPC_INTR_USRPIPE_MUX_CM0    (uint32_t)(7u)   /* IPC CYPRESS PIPE */
#define IPC_USRPIPE_CONFIG_CM0      (uint32_t)(IPC_USRPIPE_INTR_MASK << CY_IPC_PIPE_CFG_IMASK_Pos)\
                                            |(CY_IPC_INTR_USRPIPE_CM0 << CY_IPC_PIPE_CFG_INTR_Pos )\
                                            |(CY_IPC_CHAN_USRPIPE_CM0)
#define IPC_USRPIPE_CONFIG_CM4      (uint32_t)(IPC_USRPIPE_INTR_MASK << CY_IPC_PIPE_CFG_IMASK_Pos)\
                                            |(CY_IPC_INTR_USRPIPE_CM4 << CY_IPC_PIPE_CFG_INTR_Pos )\
                                            |(CY_IPC_CHAN_USRPIPE_CM4) 

#define USRPIPE_CONFIG \
{\
    /* .ep0ConfigData */ {\
        /* .ipcNotifierNumber    */  CY_IPC_INTR_USRPIPE_CM0,\
        /* .ipcNotifierPriority  */  IPC_INTR_USRPIPE_PRIOR_CM0,\
        /* .ipcNotifierMuxNumber */  IPC_INTR_USRPIPE_MUX_CM0,\
        /* .epAddress            */  CY_IPC_EP_USRPIPE_ADDR_CM0_EP,\
        /* .epConfig             */  IPC_USRPIPE_CONFIG_CM0\
    },\
    /* .ep1ConfigData */ {\
        /* .ipcNotifierNumber    */  CY_IPC_INTR_USRPIPE_CM4,\
        /* .ipcNotifierPriority  */  IPC_INTR_USRPIPE_PRIOR_CM4,\
        /* .ipcNotifierMuxNumber */  0u,\
        /* .epAddress            */  CY_IPC_EP_USRPIPE_ADDR_CM4_EP,\
        /* .epConfig             */  IPC_USRPIPE_CONFIG_CM4\
    },\
    /* .endpointClientsCount     */  CY_IPC_USRPIPE_CLIENT_CNT,\
    /* .endpointsCallbacksArray  */  ipc_pipe_CbArray,\
    /* .userPipeIsrHandler       */  &IPC_UserPipeISR\
}

//! Client ID for messages from the CM0 to the CM4
#define COMM_IPC_CM0_TO_CM4_CLIENT_ID        0
//! Client ID for messages from the CM4 to the CM0
#define COMM_IPC_CM4_TO_CM0_CLIENT_ID        1


/* Public Variables */

/* Private Variables */

#if (__CORTEX_M == 0)
static COMM_IPCMessage_T MessageBuffer = 
{
    .ClientID = _VAL2FLD(CY_IPC_PIPE_MSG_CLIENT, COMM_IPC_CM0_TO_CM4_CLIENT_ID) | _VAL2FLD(CY_IPC_PIPE_MSG_USR, 0) | _VAL2FLD(CY_IPC_PIPE_MSG_RELEASE, IPC_USRPIPE_INTR_MASK),
    .MessageID = COMM_SMM_DefaultNoMessage,
    .Data = NULL
};

static volatile bool OK_to_send_from_CM0_to_CM4 = true;
#endif // (__CORTEX_M == 0)

#if (__CORTEX_M == 4)
static COMM_IPCMessage_T MessageBuffer = 
{
    .ClientID = _VAL2FLD(CY_IPC_PIPE_MSG_CLIENT, COMM_IPC_CM4_TO_CM0_CLIENT_ID) | _VAL2FLD(CY_IPC_PIPE_MSG_USR, 0) | _VAL2FLD(CY_IPC_PIPE_MSG_RELEASE, IPC_USRPIPE_INTR_MASK),
    .MessageID = COMM_SMM_DefaultNoMessage,
    .Data = NULL
};

static volatile bool OK_to_send_from_CM4_to_CM0 = true; 
#endif // (__CORTEX_M == 4)


/* Public Functions */

#if (__CORTEX_M == 0)   
//! Initializes the inter-processor communications on the Cortex-M0 core.
/*!
 *  This should be called *before* calling Cy_SysEnableCM4().
 */
void COMM_InitIPCMessages(void)
{    
    IPC_UserPipeInit();
    
    // Register a callback to handle messages from CM4.
    Cy_IPC_Pipe_RegisterCallback(IPC_EP_USRPIPE_ADDR,
                                 Message_Received_for_CM0,
                                 CY_IPC_EP_CYPIPE_CM4_ADDR);
}
#endif // (__CORTEX_M == 0)

#if (__CORTEX_M == 4)
//! Initializes the inter-processor communications on the Cortex-M4 core.
void COMM_InitIPCMessages(void)
{    
    IPC_UserPipeInit();
    
    // Register a callback to handle messages from CM0.
    Cy_IPC_Pipe_RegisterCallback(IPC_EP_USRPIPE_ADDR,
                                 Message_Received_for_CM4,
                                 CY_IPC_EP_CYPIPE_CM0_ADDR);
}
#endif // (__CORTEX_M == 4)


//! Sends an inter-processor communication message to the other core.
bool COMM_SendMessageToOtherCore(COMM_IPCMessageID_T message_ID, void * message_data)
{
    bool message_sent = false;
    
    MessageBuffer.MessageID = message_ID;
    MessageBuffer.Data = message_data;
    
#if (__CORTEX_M == 0)
    if (OK_to_send_from_CM0_to_CM4 == true)
    {
        OK_to_send_from_CM0_to_CM4 = false;
        uint32_t timeout_in_us = 2000;
        cy_en_ipc_pipe_status_t ipcStatus;
        
        do
        {
            ipcStatus = Cy_IPC_Pipe_SendMessage(CY_IPC_EP_USRPIPE_ADDR_CM4_EP,
                                                CY_IPC_EP_USRPIPE_ADDR_CM0_EP,
                                                (uint32_t *) &MessageBuffer,
                                                Message_Received_by_CM4);
            Cy_SysLib_DelayUs(1u);
            timeout_in_us--;
        } while((ipcStatus != CY_IPC_PIPE_SUCCESS) && (timeout_in_us != 0));
        
        message_sent = true;
    }
#endif // (__CORTEX_M == 0)

#if (__CORTEX_M == 4)
    if (OK_to_send_from_CM4_to_CM0 == true)
    {
        OK_to_send_from_CM4_to_CM0 = false;
        uint32_t timeout_in_us = 2000;
        cy_en_ipc_pipe_status_t ipcStatus;
        
        do
        {
            ipcStatus = Cy_IPC_Pipe_SendMessage(CY_IPC_EP_USRPIPE_ADDR_CM0_EP,
                                                CY_IPC_EP_USRPIPE_ADDR_CM4_EP,
                                                (uint32_t *) &MessageBuffer,
                                                Message_Received_by_CM0);
            Cy_SysLib_DelayUs(1u);
            timeout_in_us--;
        } while((ipcStatus != CY_IPC_PIPE_SUCCESS) && (timeout_in_us != 0));
        
        message_sent = true;
    }
#endif // (__CORTEX_M == 4)

    return message_sent;
}


/* Private Functions */

#if (__CORTEX_M == 0) 
//! Callback for messages received by the CM0 core from the CM4 core.
/*!
 *  \note This code is executed inside an interrupt handler.
 */
static void Message_Received_for_CM0(uint32_t * msg)
{
    switch (((COMM_IPCMessage_T *)msg)->MessageID)
    {
        default:
        case COMM_SMM_DefaultNoMessage:
        case COMM_SMM_NoMessage:
            break;
        
        case COMM_SMM_RebootImmediately:
            // Perform a software reset of both cores.
            NVIC_SystemReset();
            break;
    }
}

static void Message_Received_by_CM4(void)
{
    OK_to_send_from_CM0_to_CM4 = true;
}
#endif // (__CORTEX_M == 0)

#if (__CORTEX_M == 4)
//! Callback for messages received by the CM4 core from the CM0 core.
/*!
 *  \note This code is executed inside an interrupt handler.
 */
static void Message_Received_for_CM4(uint32_t * msg)
{
    switch (((COMM_IPCMessage_T *)msg)->MessageID)
    {
        default:
        case COMM_SMM_DefaultNoMessage:
        case COMM_SMM_NoMessage:
            break;
        
        case COMM_SMM_RebootImmediately:
            // This message does nothing on CM4
            break;
    }
}

static void Message_Received_by_CM0(void)
{
    OK_to_send_from_CM4_to_CM0 = true;
}
#endif // (__CORTEX_M == 4)

//! Initializes the IPC user pipe.
static void IPC_UserPipeInit(void)
{
    static cy_ipc_pipe_callback_ptr_t ipc_pipe_CbArray[CY_IPC_USRPIPE_CLIENT_CNT];
    static const cy_stc_ipc_pipe_config_t userPipeConfig = USRPIPE_CONFIG;

    uint32_t savedIntrStatus = Cy_SysLib_EnterCriticalSection();
    
    Cy_IPC_Pipe_Init(&userPipeConfig);
    
    Cy_SysLib_ExitCriticalSection(savedIntrStatus);
}

//! Interrupt service routine for the user pipe.
void IPC_UserPipeISR(void)
{
    Cy_IPC_Pipe_ExecuteCallback(IPC_EP_USRPIPE_ADDR);
}

