/**
 * @file        ethernetif.c
 *
 * @brief       This file provides configuration support for LwIP
 *
 * @version     V1.0.0
 *
 * @date        2023-12-01
 *
 * @attention
 *
 *  Copyright (C) 2023 Geehy Semiconductor
 *
 *  You may not use this file except in compliance with the
 *  GEEHY COPYRIGHT NOTICE (GEEHY SOFTWARE PACKAGE LICENSE).
 *
 *  The program is only for reference, which is distributed in the hope
 *  that it will be useful and instructional for customers to develop
 *  their software. Unless required by applicable law or agreed to in
 *  writing, the program is distributed on an "AS IS" BASIS, WITHOUT
 *  ANY WARRANTY OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the GEEHY SOFTWARE PACKAGE LICENSE for the governing permissions
 *  and limitations under the License.
 */

/* Includes ***************************************************************/
#include "ethernetif.h"

/* Private includes *******************************************************/
#include "apm32f4xx_device_cfg.h"
#include "board_lan8720.h"

#include "lwip/timeouts.h"
#include "netif/ethernet.h"
#include "netif/etharp.h"
#include "lwip/stats.h"
#include "lwip/snmp.h"
#include "lwip/tcpip.h"

#include <string.h>

/* Private macro **********************************************************/
/**
 * @brief Define those to better describe your network interface
 */
#define IFNAME0 'a'
#define IFNAME1 'p'

#define ETH_DMA_XFER_TIMEOUT            20U
#define ETH_RX_BUFFER_CNT               12U

/* Private typedef ********************************************************/
/**
 * @brief ETH_RX_Alloc_Status Ethernet RX allocate status
 */
typedef enum
{
    ETH_RX_ALLOC_OK,
    ETH_RX_ALLOC_ERROR,
} ETH_RX_ALLOC_T;

/**
 * @brief ETH_RX_BUF Ethernet RX buffer structure
 */
typedef struct
{
    struct pbuf_custom pbuf_custom;
    uint8_t buff[(ETH_BUFFER_SIZE_RX + 31U) & ~31U] __ALIGNED(32);
} ETH_RX_BUF_T;

/* Private variables ******************************************************/

/* Memory pool */
LWIP_MEMPOOL_DECLARE(RX_POOL, ETH_RX_BUFFER_CNT, sizeof(ETH_RX_BUF_T), "ETH RX POOL");

static uint32_t rxAllocStatus;

/* Private function prototypes ********************************************/
void ETH_FreeCustomPbuf(struct pbuf *p);

/* External variables *****************************************************/
extern ETH_HandleTypeDef heth1;
extern ETH_TxPacketConfig Tx_ConfigStruct;

/* External functions *****************************************************/

/**
 * @brief   Check ETH link state
 *
 * @param   None
 *
 * @retval  None
 */
void ethernetif_checkEthLinkState(struct netif *netif)
{
    ETH_MACConfigTypeDef MAC_ConfigStruct = {0};
    uint32_t phyLinkState;

    uint32_t duplex = 0U;
    uint32_t speed = 0U;
    uint32_t linkFlag = 0U;

    /* Get link state */
    phyLinkState = LAN8720_GetLinkState();

    if (netif_is_link_up(netif) && \
       (phyLinkState <= LAN8720_STATUS_LINK_DOWN))
    {
        DAL_ETH_Stop(&heth1);
        netif_set_down(netif);
        netif_set_link_down(netif);
    }
    else if(!netif_is_link_up(netif) && \
           (phyLinkState > LAN8720_STATUS_LINK_DOWN))
    {
        switch(phyLinkState)
        {
            case LAN8720_STATUS_FULLDUPLEX_100MBITS:
                duplex      = ETH_FULLDUPLEX_MODE;
                speed       = ETH_SPEED_100M;
                linkFlag    = 1U;
                break;

            case LAN8720_STATUS_FULLDUPLEX_10MBITS:
                duplex      = ETH_FULLDUPLEX_MODE;
                speed       = ETH_SPEED_10M;
                linkFlag    = 1U;
                break;

            case LAN8720_STATUS_HALFDUPLEX_100MBITS:
                duplex      = ETH_HALFDUPLEX_MODE;
                speed       = ETH_SPEED_100M;
                linkFlag    = 1U;
                break;

            case LAN8720_STATUS_HALFDUPLEX_10MBITS:
                duplex      = ETH_HALFDUPLEX_MODE;
                speed       = ETH_SPEED_10M;
                linkFlag    = 1U;
                break;

            default:
                break;
        }
    }

    if (linkFlag)
    {
        /* Configure MAC */
        DAL_ETH_GetMACConfig(&heth1, &MAC_ConfigStruct);
        MAC_ConfigStruct.DuplexMode     = duplex;
        MAC_ConfigStruct.Speed          = speed;
        DAL_ETH_SetMACConfig(&heth1, &MAC_ConfigStruct);

        /* Restart ETH */
        DAL_ETH_Start(&heth1);

        netif_set_up(netif);
        netif_set_link_up(netif);
    }
}

/**
 * In this function, the hardware should be initialized.
 * Called from ethernetif_init().
 *
 * @param netif the already initialized lwip network interface structure
 *        for this ethernetif
 */
static void low_level_init(struct netif *netif)
{
    /* Initialize the RX POOL */
    LWIP_MEMPOOL_INIT(RX_POOL);

    /* Set MAC hardware address length */
    netif->hwaddr_len = ETHARP_HWADDR_LEN;

    /* Set MAC hardware address */
    netif->hwaddr[0] = ETH_MAC_ADDR_0;
    netif->hwaddr[1] = ETH_MAC_ADDR_1;
    netif->hwaddr[2] = ETH_MAC_ADDR_2;
    netif->hwaddr[3] = ETH_MAC_ADDR_3;
    netif->hwaddr[4] = ETH_MAC_ADDR_4;
    netif->hwaddr[5] = ETH_MAC_ADDR_5;

    /* Maximum transfer unit */
    netif->mtu = ETH_MAX_PAYLOAD;

    /* Device capabilities */
    /* Don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;

    if (DAL_ETH_Config() == DAL_OK)
    {
        /* Init LAN8720 */
        LAN8720_Init();

        /* Check link state */
        ethernetif_checkEthLinkState(netif);
    }
    else
    {
        Error_Handler();
    }
}

/**
 * This function should do the actual transmission of the packet. The packet is
 * contained in the pbuf that is passed to the function. This pbuf
 * might be chained.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
 * @return ERR_OK if the packet could be sent
 *         an err_t value if the packet couldn't be sent
 *
 * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
 *       strange results. You might consider waiting for space in the DMA queue
 *       to become availale since the stack doesn't retry to send a packet
 *       dropped because of memory failure (except for the TCP timers).
 */
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
    uint32_t i = 0U;
    struct pbuf *q = NULL;
    err_t errval = ERR_OK;
    ETH_BufferTypeDef txbuffer[ETH_TX_DESC_CNT] = {0};

    memset(txbuffer, 0U , ETH_TX_DESC_CNT * sizeof(ETH_BufferTypeDef));

    for (q = p; q != NULL; q = q->next)
    {
        if(i >= ETH_TX_DESC_CNT)
        {
            return ERR_IF;
        }

        txbuffer[i].buffer  = q->payload;
        txbuffer[i].len     = q->len;

        if (i > 0U)
        {
            txbuffer[i - 1U].next = &txbuffer[i];
        }

        if(q->next == NULL)
        {
            txbuffer[i].next = NULL;
        }

        i++;
    }

    Tx_ConfigStruct.Length      = p->tot_len;
    Tx_ConfigStruct.TxBuffer    = txbuffer;
    Tx_ConfigStruct.pData       = p;

    DAL_ETH_Transmit(&heth1, &Tx_ConfigStruct, ETH_DMA_XFER_TIMEOUT);

    return errval;
}

/**
 * Should allocate a pbuf and transfer the bytes of the incoming
 * packet from the interface into the pbuf.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return a pbuf filled with the received packet (including MAC header)
 *         NULL on memory error
 */
static struct pbuf *low_level_input(struct netif *netif)
{
    struct pbuf *p = NULL;

    if (rxAllocStatus == ETH_RX_ALLOC_OK)
    {
        DAL_ETH_ReadData(&heth1, (void **)&p);
    }

    return p;
}

/**
 * This function should be called when a packet is ready to be read
 * from the interface. It uses the function low_level_input() that
 * should handle the actual reception of bytes from the network
 * interface. Then the type of the received packet is determined and
 * the appropriate input function is called.
 *
 * @param netif the lwip network interface structure for this ethernetif
 */
void ethernetif_input(struct netif *netif)
{
    struct pbuf *p = NULL;

    do
    {
        p = low_level_input(netif);
        if (p != NULL)
        {
            if (netif->input(p, netif) != ERR_OK)
            {
                pbuf_free(p);
            }
        }
    } while(p != NULL);
}

/**
 * Should be called at the beginning of the program to set up the
 * network interface. It calls the function low_level_init() to do the
 * actual setup of the hardware.
 *
 * This function should be passed as a parameter to netif_add().
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return ERR_OK if the loopif is initialized
 *         ERR_MEM if private data couldn't be allocated
 *         any other err_t on error
 */
err_t ethernetif_init(struct netif *netif)
{
    LWIP_ASSERT("netif != NULL", (netif != NULL));

#if LWIP_NETIF_HOSTNAME
    /* Initialize interface hostname */
    netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */

    netif->name[0] = IFNAME0;
    netif->name[1] = IFNAME1;
    /* We directly use etharp_output() here to save a function call.
    * You can instead declare your own function an call etharp_output()
    * from it if you have to do some checks before sending (e.g. if link
    * is available...) */
    netif->output = etharp_output;
    netif->linkoutput = low_level_output;

    /* initialize the hardware */
    low_level_init(netif);

    return ERR_OK;
}

/**
 * @brief  Custom Rx pbuf free callback
 * @param  pbuf: pbuf to be freed
 * @retval None
 */
void ETH_FreeCustomPbuf(struct pbuf *p)
{
    struct pbuf_custom* custom_pbuf = (struct pbuf_custom*)p;
    LWIP_MEMPOOL_FREE(RX_POOL, custom_pbuf);

    /* If the Rx Buffer Pool was exhausted, signal the ethernetif_input task to
    * call DAL_ETH_GetRxDataBuffer to rebuild the Rx descriptors. */

    if (rxAllocStatus == ETH_RX_ALLOC_ERROR)
    {
        rxAllocStatus = ETH_RX_ALLOC_OK;
    }
}

/**
 * @brief   Returns the current time in milliseconds
 *          when LWIP_TIMERS == 1 and NO_SYS == 1
 *
 * @param   None
 *
 * @retval  Current Time value
 */
u32_t sys_now(void)
{
    return DAL_GetTick();
}

/**
 * @brief   Initializes PHY
 *
 * @param   None
 *
 * @retval  0 if OK, -1 if ERROR
 */
int32_t LAN8720_PHY_InitCallback(void)
{
#if defined(PHY_HARDWARE_RESET)
    /* Reset PHY */
    DAL_GPIO_WritePin(PHY_RESET_GPIO_PORT, PHY_RESET_GPIO_PIN, GPIO_PIN_RESET);
    DAL_Delay(100U);
    DAL_GPIO_WritePin(PHY_RESET_GPIO_PORT, PHY_RESET_GPIO_PIN, GPIO_PIN_SET);
    DAL_Delay(100U);
#endif /* PHY_HARDWARE_RESET */

    /* Configure the MDIO Clock */
    DAL_ETH_SetMDIOClockRange(&heth1);

    return 0U;
}

/**
 * @brief   De-initializes PHY
 *
 * @param   None
 *
 * @retval  0 if OK, -1 if ERROR
 */
int32_t LAN8720_PHY_UninitCallback(void)
{
    return 0U;
}

/**
 * @brief   Get base tick to handle PHY process
 *
 * @param   None
 *
 * @retval  Tick value
 */
int32_t LAN8720_PHY_GetTickCallback(void)
{
    return DAL_GetTick();
}

/**
 * @brief   Read a PHY register
 *
 * @param   devAddr: PHY port address
 *
 * @param   regAddr: PHY register address
 *
 * @param   regVal: Pointer to the register value
 *
 * @retval  0 if OK, -1 if ERROR
 */
int32_t LAN8720_PHY_ReadRegCallback(uint32_t devAddr, uint32_t regAddr, uint32_t *regVal)
{
    if (DAL_ETH_ReadPHYRegister(&heth1, devAddr, regAddr, regVal) != DAL_OK)
    {
        return -1;
    }

    return 0;
}

/**
 * @brief   Write a value to a PHY register
 *
 * @param   devAddr: PHY port address
 *
 * @param   regAddr: PHY register address
 *
 * @param   regVal: Value to be written
 *
 * @retval  0 if OK, -1 if ERROR
 */
int32_t LAN8720_PHY_WriteRegCallback(uint32_t devAddr, uint32_t regAddr, uint32_t regVal)
{
    if (DAL_ETH_WritePHYRegister(&heth1, devAddr, regAddr, regVal) != DAL_OK)
    {
        return -1;
    }

    return 0;
}

/**
 * @brief   Rx allocate callback
 *
 * @param   buff: pointer to allocated buffer
 *
 * @retval  None
 */
void DAL_ETH_RxAllocateCallback(uint8_t **buff)
{
    struct pbuf_custom *p = LWIP_MEMPOOL_ALLOC(RX_POOL);

    if (p)
    {
        /* Get the buff from the struct pbuf address */
        *buff = (uint8_t *)p + offsetof(ETH_RX_BUF_T, buff);
        p->custom_free_function = ETH_FreeCustomPbuf;

        /* Initialize the struct pbuf */
        pbuf_alloced_custom(PBUF_RAW, 0U, PBUF_REF, p, *buff, ETH_BUFFER_SIZE_RX);
    }
    else
    {
        rxAllocStatus = ETH_RX_ALLOC_ERROR;
        *buff = NULL;
    }
}

/**
 * @brief   Rx Link callback
 *
 * @param   pStart: pointer to packet start
 *
 * @param   pStart: pointer to packet end
 *
 * @param   buff: pointer to received data
 *
 * @param   Length: received data length
 *
 * @retval  None
 */
void DAL_ETH_RxLinkCallback(void **pStart, void **pEnd, uint8_t *buff, uint16_t Length)
{
    struct pbuf **ppStart = (struct pbuf **)pStart;
    struct pbuf **ppEnd = (struct pbuf **)pEnd;
    struct pbuf *p = NULL;

    /* Get the struct pbuf from the buff address. */
    p = (struct pbuf *)(buff - offsetof(ETH_RX_BUF_T, buff));
    p->next = NULL;
    p->tot_len = 0U;
    p->len = Length;

    /* Chain the buffer. */
    if (!*ppStart)
    {
        /* The first buffer of the packet. */
        *ppStart = p;
    }
    else
    {
        /* Chain the buffer to the end of the packet. */
        (*ppEnd)->next = p;
    }

    *ppEnd  = p;

    /* Update the total length of all the buffers of the chain. Each pbuf in the chain should have its tot_len
    * set to its own length, plus the length of all the following pbufs in the chain. */
    for (p = *ppStart; p != NULL; p = p->next)
    {
        p->tot_len += Length;
    }
}

/**
 * @brief   Tx Free callback
 *
 * @param   buff: pointer to buffer to free
 *
 * @retval  None
 */
void DAL_ETH_TxFreeCallback(uint32_t * buff)
{
    pbuf_free((struct pbuf *)buff);
}
