//
// Included Files
//
#include "device.h"
#include "can.h"
#include "interrupt.h"
#include "sysctl.h"
#include "ipc.h"

//
// Defines
//
#define IPC_CMD_READ_CAN  0x1002
#define IPC_CMD_RESP      0x2002

#define CPU1_TO_CPU2_FLAG IPC_FLAG0
#define CPU2_TO_CPU1_FLAG IPC_FLAG1

#define RX_MSG_OBJ_ID     1
#define MSG_DATA_LENGTH   8

#pragma DATA_SECTION(rxMsgData, "MSGRAM_CPU2_TO_CPU1");
volatile uint16_t rxMsgData[MSG_DATA_LENGTH] = {0};

//
// Function Prototypes
//
void initCANB(void);
__interrupt void canbISR(void);
__interrupt void IPC_ISR0(void);

//
// Main Function (CPU2)
//
void main(void)
{
    // Wait for CPU1 to assign CANB before using it
    while ((HWREG(IPC_CPU2_L_CPU1_R + IPC_O_FLG) & CPU1_TO_CPU2_FLAG) == 0);

    // Acknowledge CPU1 that CPU2 has received the flag
    IPC_ackFlagRtoL(IPC_CPU2_L_CPU1_R, CPU1_TO_CPU2_FLAG);

    // Initialize Device & GPIOs
    Device_init();
    Device_initGPIO();

    // Initialize CANB after CPU1 assigns ownership
    initCANB();
    CAN_startModule(CANB_BASE);

    // IPC Initialization
    IPC_clearFlagLtoR(IPC_CPU2_L_CPU1_R, IPC_FLAG_ALL);

    // Register IPC Interrupt
    Interrupt_register(INT_IPC_0, IPC_ISR0);
    Interrupt_enable(INT_IPC_0);

    // Enable Global Interrupts
    EINT;
    ERTM;

    while (1);
}

//
// Initialize CANB Module (After CPU1 assigns ownership)
//
void initCANB(void)
{
    CAN_initModule(CANB_BASE);
    CAN_setBitRate(CANB_BASE, DEVICE_SYSCLK_FREQ, 500000, 20);

    // Setup RX Message Object
    CAN_setupMessageObject(CANB_BASE, RX_MSG_OBJ_ID, 0x1,
                           CAN_MSG_FRAME_STD, CAN_MSG_OBJ_TYPE_RX, 0,
                           CAN_MSG_OBJ_RX_INT_ENABLE, MSG_DATA_LENGTH);

    // Enable CANB Interrupts
    CAN_enableInterrupt(CANB_BASE, CAN_INT_IE0 | CAN_INT_ERROR | CAN_INT_STATUS);

    // Register CANB ISR
    Interrupt_register(INT_CANB0, &canbISR);
    Interrupt_enable(INT_CANB0);
}

//
// CANB Interrupt Service Routine (ISR) - Receives CAN Data
//
__interrupt void canbISR(void)
{
    uint32_t status = CAN_getInterruptCause(CANB_BASE);

    if (status == RX_MSG_OBJ_ID)
    {
        //Read received CAN data from an external source
        CAN_readMessage(CANB_BASE, RX_MSG_OBJ_ID, (uint16_t*)rxMsgData);
    }

    CAN_clearGlobalInterruptStatus(CANB_BASE, CAN_GLOBAL_INT_CANINT0);
    Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP9);
}

//
// IPC Interrupt Service Routine (Handles CPU1 -> CPU2 Command)
//
__interrupt void IPC_ISR0(void)
{
    uint32_t command, i;

    // Read IPC command from CPU1
    IPC_readCommand(IPC_CPU2_L_CPU1_R, CPU1_TO_CPU2_FLAG, IPC_ADDR_CORRECTION_ENABLE, &command, NULL, NULL);

    if (command == IPC_CMD_READ_CAN)
    {
        //Send the latest received CAN data to CPU1 via IPC
        for (i = 0; i < MSG_DATA_LENGTH; i++)
        {
            IPC_sendCommand(IPC_CPU2_L_CPU1_R, CPU2_TO_CPU1_FLAG, IPC_ADDR_CORRECTION_ENABLE,
                            IPC_CMD_RESP, rxMsgData[i], i);
        }

        //Wait for CPU1 to acknowledge reception
        IPC_waitForAck(IPC_CPU2_L_CPU1_R, CPU2_TO_CPU1_FLAG);
    }

    //Acknowledge CPU1 command
    IPC_ackFlagRtoL(IPC_CPU2_L_CPU1_R, CPU1_TO_CPU2_FLAG);
    Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP1);
}
