//#include "lip_ll.h"
//#include "lip_log.h"
//#include "link/lip_eth.h"
//#include "phy/lip_phy.h"

#include "lip/lip_log.h"
#include "lip/ll/lip_ll.h"
#include "lip/phy/lip_phy.h"
#include "lip/link/mac/lip_mac.h"
#include "lip/link/mac/tx/lip_mac_tx.h"
#include "lip/link/mac/rx/lip_mac_rx.h"



#include "chip.h"
#include "lip_ll_gd32f4xx_descr.h"
#include "ltimer.h"

#include <string.h>

/* таймаут на выполнение сброса MAC-контроллера */
#define MAC_RESET_TOUT  10

static int enet_txfifo_flush(void)
{
    uint32_t flush_state;
    uint32_t timeout = 0U;
    int enet_state = -1;

    /* set the FTF bit for flushing transmit FIFO */
    ENET_DMA_CTL |= ENET_DMA_CTL_FTF;
    /* wait until the flush operation completes */
    do {
        flush_state = ENET_DMA_CTL & ENET_DMA_CTL_FTF;
        timeout++;
    } while((RESET != flush_state) && (timeout < ENET_DELAY_TO));
    /* return ERROR due to timeout */
    if(RESET == flush_state) {
        enet_state = 0;
    }

    return  enet_state;
}

static void enet_tx_enable(void)
{
    ENET_MAC_CFG |= ENET_MAC_CFG_TEN;
    enet_txfifo_flush();
    ENET_DMA_CTL |= ENET_DMA_CTL_STE;
}

static void enet_tx_disable(void)
{
    enet_txfifo_flush();
    ENET_DMA_CTL &= ~ENET_DMA_CTL_STE;
    ENET_MAC_CFG &= ~ENET_MAC_CFG_TEN;
}

static void enet_rx_enable(void)
{
    ENET_MAC_CFG |= ENET_MAC_CFG_REN;
    ENET_DMA_CTL |= ENET_DMA_CTL_SRE;
}

static void enet_rx_disable(void)
{
    ENET_DMA_CTL &= ~ENET_DMA_CTL_SRE;
    ENET_MAC_CFG &= ~ENET_MAC_CFG_REN;
}

void enet_enable(void)
{
    enet_tx_enable();
    enet_rx_enable();
}

static void enet_disable(void)
{
    enet_tx_disable();
    enet_rx_disable();
}


/*********************************************************************
 *  установить mac-адрес для фильтра mac-контроллера
 *********************************************************************/
void lip_ll_set_mac_addr(const uint8_t* mac_addr) {
    ENET_MAC_ADDR0L =   ((uint32_t) mac_addr[3] << 24) |
                                    ((uint32_t) mac_addr[2] << 16) |
                                    ((uint32_t) mac_addr[1] << 8) |
                                    ((uint32_t) mac_addr[0]);
    ENET_MAC_ADDR0H = ((uint32_t) mac_addr[5] << 8) |
                                    ((uint32_t) mac_addr[4]);
}

void lip_ll_ip_addr_changed(void) {
}

void phy_reset(void);
void lip_ll_close(void) {
    enet_disable();
    // Phy reset
    //phy_reset();
    rcu_periph_reset_enable(RCU_ENETRST);
}

void lip_ll_stop(void) {
    //NVIC_DisableIRQ(ETH_IRQn);
    //ETH->DMAIER = 0;

    enet_disable();
}



#ifdef LIP_USE_IGMP
/************************************************************************
 * Вызывается при подключение к mcast группе с заданным mac-адресом =>
 *   после вызова должны принимать пакеты по заданному адресу
 ************************************************************************/
t_lip_errs lip_ll_join(const uint8_t* mac) {

    return LIP_ERR_SUCCESS;
}

/***************************************************************************
 * Вызывается при отключение от mcast гурппы. После вызова порт может не
 * принимать больше пакеты по заданному mac-адресу
 ***************************************************************************/
t_lip_errs lip_ll_leave(const uint8_t* mac) {
    return LIP_ERR_SUCCESS;
}
#endif

t_lip_errs LipStagePhyInit(void) {
    t_lip_errs err = LIP_ERR_SUCCESS;

/* это конфигурирование делается в lboot */
#ifdef LIP_PORT_PHY_CLK_TIMER
    CHIP_PIN_CONFIG_AF(PIN_ETH_CKOUT);    

    /* enable SYSCFG clock */
    rcu_periph_clock_enable(RCU_SYSCFG);
    
    /* choose DIV2 to get 50MHz from 200MHz on CKOUT0 pin (PA8) to clock the PHY */
    rcu_ckout0_config(RCU_CKOUT0SRC_PLLP, RCU_CKOUT0_DIV4);
#endif
    syscfg_enet_phy_interface_config(SYSCFG_ENET_PHY_RMII);
    
    /* configure the GPIO ports for ethernet pins */
    CHIP_PIN_CONFIG_AF(PIN_ETH_RMII_REF_CLK);
    CHIP_PIN_CONFIG_AF(PIN_ETH_MDIO);
    CHIP_PIN_CONFIG_AF(PIN_ETH_RMII_CRS_DV);

    CHIP_PIN_CONFIG_AF(PIN_ETH_RMII_TX_EN);
    CHIP_PIN_CONFIG_AF(PIN_ETH_RMII_TXD0);
    CHIP_PIN_CONFIG_AF(PIN_ETH_RMII_TXD1);


    CHIP_PIN_CONFIG_AF(PIN_ETH_MDC);
    CHIP_PIN_CONFIG_AF(PIN_ETH_RMII_RXD0);
    CHIP_PIN_CONFIG_AF(PIN_ETH_RMII_RXD1);

    /* enable ethernet clock  */
    rcu_periph_clock_enable(RCU_ENET);
    rcu_periph_clock_enable(RCU_ENETTX);
    rcu_periph_clock_enable(RCU_ENETRX);

    /* reset ethernet on AHB bus */
    rcu_periph_reset_enable(RCU_ENETRST);
    rcu_periph_reset_disable(RCU_ENETRST);

#if 1
    /* reset all core internal registers located in CLK_TX and CLK_RX */
    ENET_DMA_BCTL |= ENET_DMA_BCTL_SWR;

    uint32_t timeout = 0U;
    /* wait for reset operation complete */
    while((ENET_DMA_BCTL & ENET_DMA_BCTL_SWR) && (ENET_DELAY_TO != timeout)) {
        timeout++;
    }

    /* reset operation complete */
    if(ENET_DMA_BCTL & ENET_DMA_BCTL_SWR) {
        err = LIP_ERR_INTERNAL_PROCESSING;
        return err;
    }
#endif
    
    uint32_t ahbclk;
    uint32_t reg;

    /* clear the previous MDC clock */
    reg = ENET_MAC_PHY_CTL;
    reg &= ~ENET_MAC_PHY_CTL_CLR;

    /* get the HCLK frequency */
    ahbclk = rcu_clock_freq_get(CK_AHB);

    /* configure MDC clock according to HCLK frequency range */
    if(ENET_RANGE(ahbclk, 20000000U, 35000000U)) {
        reg |= ENET_MDC_HCLK_DIV16;
    } else if(ENET_RANGE(ahbclk, 35000000U, 60000000U)) {
        reg |= ENET_MDC_HCLK_DIV26;
    } else if(ENET_RANGE(ahbclk, 60000000U, 100000000U)) {
        reg |= ENET_MDC_HCLK_DIV42;
    } else if(ENET_RANGE(ahbclk, 100000000U, 150000000U)) {
        reg |= ENET_MDC_HCLK_DIV62;
    } else if((ENET_RANGE(ahbclk, 150000000U, 240000000U)) || (240000000U == ahbclk)) {
        reg |= ENET_MDC_HCLK_DIV102;
    } else {
        return LIP_ERR_PARAMETERS;
    }
    ENET_MAC_PHY_CTL = reg;
    
    return LIP_ERR_SUCCESS;
}

t_lip_errs lip_ll_init(t_lip_init_stage stage, int flags) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    switch (stage) {
        case LIP_INIT_STAGE_PHY_INTF_INIT:
            err = LipStagePhyInit();
            break;
        case LIP_INIT_STAGE_MAC_INIT:
            /*checksum offload, full duplex, 100Mbps*/
            ENET_MAC_CFG = ENET_MAC_CFG_IPFCO | ENET_MAC_CFG_DPM | ENET_MAC_CFG_SPD | ENET_INTERFRAMEGAP_64BIT;
            /* receive all frames */
            ENET_MAC_FRMF = ENET_MAC_FRMF_FAR;
            ENET_DMA_CTL = ENET_DMA_CTL_RSFD | ENET_DMA_CTL_TSFD | ENET_DMA_CTL_FERF | ENET_DMA_CTL_DTCERFD;
            break;
        case LIP_INIT_STAGE_MAC_START:
        {
            /* сброс loopback mode */
            ENET_MAC_CFG &= ~(ENET_MAC_CFG_LBM);
            const t_lip_phy_mode *mode = lip_mac_phy_mode();

            if ((mode->flags & LIP_PHY_MODE_FLAG_DUPLEX_FULL) == 0) {
                ENET_MAC_CFG &= ~ENET_MAC_CFG_DPM;
            }

            if (mode->speed != LIP_PHY_SPEED_100) {
                ENET_MAC_CFG &= ~ENET_MAC_CFG_SPD;
            }

            /* configure the ethernet MAC/DMA */
            ENET_DMA_BCTL = ENET_DMA_BCTL_DFM | DMA_BCTL_DPSL(0)|ENET_PGBL_1BEAT|ENET_RXDP_1BEAT;
            ENET_DMA_RDTADDR  = lip_ll_gd32descr_rx_init();
            ENET_DMA_TDTADDR = lip_ll_gd32descr_tx_init();

            enet_enable();
            break;
        }
    }
    return err;
}


t_lip_errs lip_ll_mdio_read_start(uint8_t phyaddr, uint8_t regaddr) {
    uint32_t reg;

    /* configure ENET_MAC_PHY_CTL with write/read operation */
    reg = ENET_MAC_PHY_CTL;
    reg &= ~(ENET_MAC_PHY_CTL_PB | ENET_MAC_PHY_CTL_PW | ENET_MAC_PHY_CTL_PR | ENET_MAC_PHY_CTL_PA);
    reg |= (ENET_PHY_READ | MAC_PHY_CTL_PR(regaddr) | MAC_PHY_CTL_PA(phyaddr) | ENET_MAC_PHY_CTL_PB);

    /* do PHY write/read operation, and wait the operation complete  */
    ENET_MAC_PHY_CTL = reg;
    return LIP_ERR_SUCCESS;
}

bool lip_ll_mdio_rdy(void) {
    return !(ENET_MAC_PHY_CTL & ENET_MAC_PHY_CTL_PB);
}

t_lip_errs lip_ll_mdio_data(uint16_t *data) {
    *data = (uint16_t)(ENET_MAC_PHY_DATA);
    return 0;
}

//t_lip_errs lip_phy_reg_write(uint8_t dev_addr, uint8_t reg_addr, uint16_t value) {
t_lip_errs lip_ll_mdio_write_start(uint8_t phyaddr, uint8_t regaddr, uint16_t value) {
    uint32_t reg, phy_flag;
    uint32_t timeout = 0U;

    /* configure ENET_MAC_PHY_CTL with write/read operation */
    reg = ENET_MAC_PHY_CTL;
    reg &= ~(ENET_MAC_PHY_CTL_PB | ENET_MAC_PHY_CTL_PW | ENET_MAC_PHY_CTL_PR | ENET_MAC_PHY_CTL_PA);
    reg |= (ENET_PHY_WRITE | MAC_PHY_CTL_PR(regaddr) | MAC_PHY_CTL_PA(phyaddr) | ENET_MAC_PHY_CTL_PB);

    ENET_MAC_PHY_DATA = value;

    /* do PHY write/read operation, and wait the operation complete  */
    ENET_MAC_PHY_CTL = reg;

    do {
        phy_flag = (ENET_MAC_PHY_CTL & ENET_MAC_PHY_CTL_PB);
        timeout++;
    } while((RESET != phy_flag) && (ENET_DELAY_TO != timeout));

    /* write/read operation complete */
    if(RESET == (ENET_MAC_PHY_CTL & ENET_MAC_PHY_CTL_PB)) {
        return LIP_ERR_SUCCESS;
    }
    return LIP_ERR_PHY_MDIO_OP_TOUT;
}

