#include <stdint.h>
#include <string.h>
#include <strings.h>

#include "pru_ethernet.h"
#include "pru_mdio.h"
#include <pru_cfg.h>
#include <pru_ctrl.h>
#include <pru_iep.h>
#include <pru_intc.h>
#include <pru_mii_g_rt.h>
#include <pru_mii_rt.h>
#include <pru_tasks_mgr.h>

// FIFO (Bits 0–7 and 8–15)
#define R31_FIFO0_MASK   0x000000FF // Bits 0–7
#define R31_FIFO1_MASK   0x0000FF00 // Bits 8–15
#define R31_FIFO_MASK    0x0000FFFF // Bits 0–15
#define RX_EOF_ERROR     (1 << 27)
#define R31_ERROR_CRC    (1 << 24)
#define R31_ERROR_NIBBLE (1 << 23)
#define R31_RX_SOF       (1 << 22)
#define R31_RX_SFD       (1 << 21)
#define R31_RX_EOF       (1 << 20)
#define R31_RX_ERROR     (1 << 19)
#define R31_WORD_RDY     (1 << 18)
#define R31_BYTE_RDY     (1 << 17)
#define R31_DATA_RDY     (1 << 16)

#define R31_TX_CRC_ERR      (1U << 31)
#define R31_TX_RESET        (1U << 30)
#define R31_TX_EOF          (1U << 29)
#define R31_TX_ERROR_NIBBLE (1U << 28)
#define R31_TX_CRC_HIGH     (1U << 27)
#define R31_TX_CRC_LOW      (1U << 26)
#define R31_TX_PUSH16       (1U << 25)
#define R31_TX_PUSH8        (1U << 24)
#define R31_RX_ERROR_CLR    (1U << 23)
#define R31_RX_EOF_CLR      (1U << 22)
#define R31_RX_SFD_CLR      (1U << 21)
#define R31_RX_SOF_CLR      (1U << 20)
#define R31_RX_RESET        (1U << 18)
#define R31_RX_POP16        (1U << 17)
#define R31_RX_POP8         (1U << 16)

#define CORE_FREQ 333333333ul

volatile register uint32_t __R30;
volatile register uint32_t __R31;

ethernet_message_t MessageReceived;

void send_message(ethernet_message_t *msg);

// TODO:
//  - Configure the PHY
//  - Wait for link up
//  - Wait for a frame
//  - Send a frame
int main(void) {
    // --- We need to use the Slice 1 of ICSSG1 ---
    // select mux MII mode    
    CT_CFG.gpcfg0_reg = 0x0A000003;
    CT_CFG.gpcfg1_reg = 0x08000003;
    CT_CFG.sa_mx_reg = 0x1;
    CT_CFG.pru0_sd_clk_div_reg = 0xA;

    CT_MII_G_RT.icss_g_cfg = 0x0001002D;
    CT_MII_G_RT.rgmii_cfg = 0x006600D0;
    CT_MII_G_RT.mac_pru1_0 = 0x1A49631C;
    CT_MII_G_RT.mac_pru1_1 = 0x0000F1D8;
    CT_MII_G_RT.ft1_0_da0_pru0 = 6;

    // mdio
    CT_MDIO.CONTROL_REG = 0x81000090;
    CT_MDIO.POLL_REG = 0x40000064;
    CT_MDIO.POLL_EN_REG = 0x80000001;
    CT_MDIO.CONTROL_REG_bit.ENABLE = 1;

    // Configure the PHY
    // The phy should be auto configured on startup
    // so for now we skip this step

    // -- da abililitare dopo aver caricato il buffer
    CT_MII_RT.txcfg1 = 0x00001903; // 0x00001103;
    CT_MII_RT.rxcfg1 = 0x20D;
    CT_MII_RT.rx_frms1 = 0;
    CT_MII_RT.rx_frms0 = 0;
    CT_MII_RT.rx_pcnt0 = 0xB;
    CT_MII_RT.rx_pcnt1 = 0xB;

    // disable all tasks
    CT_TM_PRU1.global_cfg = 0x0;

    // configure interrupts
    CT_INTC.ENABLE_SET_INDEX_REG_bit.ENABLE_SET_INDEX = 16;
    CT_INTC.ENABLE_SET_INDEX_REG_bit.ENABLE_SET_INDEX = 17;
    CT_INTC.CH_MAP_REG4_bit.CH_MAP_16 = 2;
    CT_INTC.CH_MAP_REG4_bit.CH_MAP_17 = 3;
    CT_INTC.HINT_MAP_REG0_bit.HINT_MAP_2 = 2;
    CT_INTC.HINT_MAP_REG0_bit.HINT_MAP_3 = 3;
    CT_INTC.GLOBAL_ENABLE_HINT_REG = 1;
    CT_INTC.ENABLE_HINT_REG0 = 0b1100;

    // initialize messages
    memset(&MessageReceived, 0, sizeof(MessageReceived));
    ResetRxL1Fifo();
    while (1) {
        // raise host interrupt 2 (that is catched inside R5F)
        CT_INTC.STATUS_SET_INDEX_REG = 16;
        CT_INTC.STATUS_CLR_INDEX_REG = 16;

        WaitForMessage(&MessageReceived);
        // swap mac addresses
        uint8_t tmp[6];
        memcpy(tmp, MessageReceived.buffer, sizeof(tmp));
        memcpy(MessageReceived.buffer, MessageReceived.buffer + 6, 6);
        memcpy(MessageReceived.buffer + 6, tmp, 6);
        MessageReceived.length -= 4; // remove the CRC

        send_message(&MessageReceived);

        // raise host interrupt 3 (that is catched inside R5F)
        CT_INTC.STATUS_SET_INDEX_REG = 17;
        CT_INTC.STATUS_CLR_INDEX_REG = 17;
    }
}

uint8_t dummy;

void send_message(ethernet_message_t *msg) {
    __R31 |= R31_TX_RESET;
    const register uint8_t *cursor = msg->buffer;
    uint32_t c = msg->length & 0x3;
    const register uint8_t *end1 = msg->buffer + msg->length - c;
    const register uint8_t *end2 = msg->buffer + msg->length;

    while (cursor < end1) {
        __R30 = *((uint32_t *)cursor);
        cursor += 4;
        asm("   nop");
    }

    while (cursor < end2) {
        __R30 = *cursor;
        cursor += 1;
    }
    // asm("   LDI R31.w2, 0x2c00");

    // set crc high
    asm("   set R31, R31, 27");
    // wait 6 cycles
    asm("   nop");
    asm("   nop");
    asm("   nop");
    asm("   nop");
    asm("   nop");
    asm("   nop");
    // set crc low and tx eof
    asm("   LDI R31.w2, 0x2400");
}
