/**************************************************************************//**
  @file lip_ll_lpc43xx.c

  Файл содержит реализацию порта TCP/IP стека lip под микроконтроллеры LPC43xx.
  Порт использует файлы для lpc_bits.h и chip_pin.h для настройки пинов
  (можно взять из https://bitbucket.org/lcard/lpc_chip), а также определения
  регистров и функций для работы с клоками, совместимые с
  LPCOpen (сокращенная достаточная версия также входит в https://bitbucket.org/lcard/lpc_chip).

  @author Borisov Alexey <borisov@lcard.ru>
  ******************************************************************************/

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


#include "chip.h"
#include "lip_ll_lpc43xx_descr.h"
#include "ltimer.h"

#include <string.h>

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

#ifndef LIP_PORT_USE_LPCOPEN_CLKCTL
    #include "lpc_config.h"
    #ifndef LPC_SYSCLK
        extern uint32_t SystemCoreClock;
        #define LPC_SYSCLK SystemCoreClock
    #endif
#endif


/* значения делителя для клоков интерфейса доступа к регистрам Phy */
static uint8_t f_mii_clk_div = 4;



/* подбор оптимального делителя клоков интерфейса доступа к
   регистрам phy по частоте блока Ethernet */
static uint8_t f_get_mmi_clk_div(uint32_t eth_clock) {
    static const struct {
        uint16_t clk_min;
        uint16_t clk_max;
        uint8_t  val;
    } f_mmi_div_tbl[] = {
        { 20,  35, 2},
        { 35,  60, 3},
        { 60, 100, 0},
        {100, 150, 1},
        {150, 250, 4},
        {250, 300, 5}
    };
    uint8_t div_ind = 0;
    unsigned i;

    eth_clock/=1000000;

    for (i=0; i < sizeof(f_mmi_div_tbl)/sizeof(f_mmi_div_tbl[0]); i++) {
        if ((eth_clock >= f_mmi_div_tbl[i].clk_min) &&
                (eth_clock <= f_mmi_div_tbl[i].clk_max)) {
            div_ind = i;
            break;
        }
    }
    return f_mmi_div_tbl[div_ind].val;
}





t_lip_errs lip_ll_mdio_read_start(unsigned dev_addr, unsigned reg_addr) {
    LPC_ETHERNET->MAC_MII_ADDR = MAC_MIIA_PA(dev_addr) | MAC_MIIA_CR(f_mii_clk_div) |
                                 MAC_MIIA_GR(reg_addr);
    LPC_ETHERNET->MAC_MII_ADDR |= MAC_MIIA_GB;
    return LIP_ERR_SUCCESS;
}

t_lip_errs lip_ll_mdio_write_start(unsigned dev_addr, unsigned reg_addr, unsigned value) {
    LPC_ETHERNET->MAC_MII_ADDR = MAC_MIIA_PA(dev_addr) | MAC_MIIA_CR(f_mii_clk_div) |
                                 MAC_MIIA_GR(reg_addr) | MAC_MIIA_W;
    LPC_ETHERNET->MAC_MII_DATA = value & MAC_MIID_GDMSK;
    LPC_ETHERNET->MAC_MII_ADDR |= MAC_MIIA_GB;
    return LIP_ERR_SUCCESS;
}

int lip_ll_mdio_rdy(void) {
    return !(LPC_ETHERNET->MAC_MII_ADDR & MAC_MIIA_GB);
}

unsigned lip_ll_mdio_data(void) {
    return LPC_ETHERNET->MAC_MII_DATA & MAC_MIID_GDMSK;
}

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






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:
#ifdef LIP_PORT_PHY_CLK_TIMER
    #ifdef LIP_PORT_USE_LPCOPEN_CLKCTL
            Chip_Clock_Enable(CLK_MX_TIMER2);
    #elif defined LPC_CCU_CFG
            LPC_CCU_CFG(CLK_MX_TIMER2, 1);
    #endif
            /* Настройка частоты 25 MHz для клока Phy */
            LPC_TIMER2->PC = 0;
            LPC_TIMER2->MR[0] = (LPC_SYSCLK/50000000)-1;
            LPC_TIMER2->MCR = TIMER_RESET_ON_MATCH(0);
            LPC_TIMER2->EMR = (3 << 4);
            LPC_TIMER2->TCR = TIMER_ENABLE;
            CHIP_PIN_CONFIG(CHIP_PIN_P6_7_T2_MAT0, 0, CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOPULL);
#endif

            /* Configure ethernet pins */
            CHIP_PIN_CONFIG(CHIP_PIN_P0_0_ENET_RXD1,      1,  CHIP_PIN_MODE_NOPULL | CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOFILTER);
            CHIP_PIN_CONFIG(CHIP_PIN_P0_1_ENET_TX_EN,     0,  CHIP_PIN_MODE_NOPULL | CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOFILTER);
            CHIP_PIN_CONFIG(CHIP_PIN_P1_15_ENET_RXD0,     1,  CHIP_PIN_MODE_NOPULL | CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOFILTER);
            CHIP_PIN_CONFIG(CHIP_PIN_P1_16_ENET_RX_DV,    1,  CHIP_PIN_MODE_NOPULL | CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOFILTER);
            CHIP_PIN_CONFIG(CHIP_PIN_P1_17_ENET_MDIO,     1,  CHIP_PIN_MODE_NOPULL | CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOFILTER);
            CHIP_PIN_CONFIG(CHIP_PIN_P1_18_ENET_TXD0,     0,  CHIP_PIN_MODE_NOPULL | CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOFILTER);
            CHIP_PIN_CONFIG(CHIP_PIN_P1_19_ENET_TX_CLK,   1,  CHIP_PIN_MODE_NOPULL | CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOFILTER);
            CHIP_PIN_CONFIG(CHIP_PIN_P1_20_ENET_TXD1,     0,  CHIP_PIN_MODE_NOPULL | CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOFILTER);
            CHIP_PIN_CONFIG(LIP_PHY_PIN_MDC,              0,  CHIP_PIN_MODE_NOPULL | CHIP_PIN_MODE_FAST | CHIP_PIN_MODE_NOFILTER);
            break;

        case LIP_INIT_STAGE_MAC_INIT: {
                uint32_t clk;
                t_ltimer tmr;
                /* установка режима RMII */                
                LPC_CREG->CREG6 &= ~7;
                LPC_CREG->CREG6 |= 0x04;

#ifdef LIP_PORT_USE_LPCOPEN_CLKCTL
                Chip_Clock_Enable(CLK_MX_ETHERNET);
                clk = Chip_Clock_GetRate(CLK_MX_ETHERNET);
#else
    #ifdef LPC_CCU_CFG
                LPC_CCU_CFG(CLK_MX_ETHERNET, 1);
    #endif
                clk = LPC_SYSCLK;
#endif

                f_mii_clk_div = f_get_mmi_clk_div(clk);

                /* Software reset */
                LPC_ETHERNET->DMA_BUS_MODE |= DMA_BM_SWR;

                ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(MAC_RESET_TOUT));
                while ((LPC_ETHERNET->DMA_BUS_MODE & DMA_BM_SWR) && !ltimer_expired(&tmr)) {
                    continue;
                }

                /* если не удалось выполнить сброс, то это означает как правило,
                 * что нет действительного клока от PHY. дальнейшая работа
                 * невозможна */
                if (LPC_ETHERNET->DMA_BUS_MODE & DMA_BM_SWR) {
                    err = LIP_ERR_INIT_MAC_RESET;
                    lip_printf(LIP_LOG_MODULE_PORT, LIP_LOG_LVL_ERROR_FATAL, err,
                               "lip port: mac reset error. possibly phy clock not present!\n");
                } else {
                    /* Enhanced descriptors, burst length = 32 */
                    LPC_ETHERNET->DMA_BUS_MODE = DMA_BM_ATDS | DMA_BM_PBL(32) | DMA_BM_RPBL(32);

                    /* checksum offload, full duplex,
                        100Mbps, disable receive own in half duplex, inter-frame gap
                        of 64-bits */
                    LPC_ETHERNET->MAC_CONFIG = MAC_CFG_BL(0) | MAC_CFG_IPC | MAC_CFG_DM |
                                       MAC_CFG_DO | MAC_CFG_FES | MAC_CFG_PS | MAC_CFG_IFG(3);

                    if (flags & LIP_INIT_FLAGS_MAC_RX_FILTER_DIS) {
                        /* при запрете фильтра разрешаем все пакеты */
                        LPC_ETHERNET->MAC_FRAME_FILTER = MAC_FF_PR | MAC_FF_RA;
                    } else {
#ifdef LIP_USE_IGMP
                        /** @todo сейчас разрешаем все multicast. мб сделать в будущем
                                  по hesh-таблицам */
                        LPC_ETHERNET->MAC_FRAME_FILTER = MAC_FF_PM;
#else
                        LPC_ETHERNET->MAC_FRAME_FILTER = 0;
#endif
                    }

                    /* Flush transmit FIFO */
                    LPC_ETHERNET->DMA_OP_MODE = DMA_OM_FTF;

                    /* Setup DMA to flush receive FIFOs at 32 bytes, service TX FIFOs at
                       64 bytes */

                    /** @warning Включение OSF режима приводит к тому, что при
                        постоянном потоке на передачу, данные перестают передаваться
                        вообще (хотя DMA дескрипторы закрывает).
                        Так как большой разницы в скорости не наблюдается, то OSF
                        режим оставляем выключенным */
                    LPC_ETHERNET->DMA_OP_MODE |= DMA_OM_RTC(1) | DMA_OM_TTC(0);

                    /* Clear all MAC interrupts */
                    LPC_ETHERNET->DMA_STAT = DMA_ST_ALL;

                    /* disable MAC interrupts */
                    LPC_ETHERNET->DMA_INT_EN = 0;
                }
            }
            break;

        case LIP_INIT_STAGE_MAC_START:
            /* настраиваем режим в соответствии с полученынм режимом phy */
            if (lip_phy_mode()!=LIP_PHY_DUPLEX_MODE_FULL)
                LPC_ETHERNET->MAC_CONFIG &= ~MAC_CFG_DM;

            if (lip_phy_speed()!=LIP_PHY_SPEED_100)
                LPC_ETHERNET->MAC_CONFIG &= ~MAC_CFG_FES;

            /* инициализация дескрипторов */
            LPC_ETHERNET->DMA_REC_DES_ADDR  = lip_ll_lpc43descr_rx_init();
            LPC_ETHERNET->DMA_TRANS_DES_ADDR = lip_ll_lpc43descr_tx_init();

            /* разрешение приема, передачи и DMA в порядке из user manual */
            LPC_ETHERNET->MAC_CONFIG |= MAC_CFG_TE;
            LPC_ETHERNET->DMA_OP_MODE |= DMA_OM_ST | DMA_OM_SR;
            LPC_ETHERNET->MAC_CONFIG |= MAC_CFG_RE;
    }

    return err;
}



uint8_t* lip_ll_rx_get_buf(size_t* length) {
    return lip_ll_lpc43descr_rx_get_buf(length);
}

t_lip_errs lip_ll_rx_drop(void) {
    return lip_ll_lpc43descr_rx_drop();
}

uint8_t *lip_ll_tx_get_buf(int *max_length) {
    return lip_ll_lpc43descr_tx_get_buf(max_length);
}

t_lip_errs lip_ll_tx_flush_split(size_t size, const uint8_t* data, int data_size) {
    return lip_ll_lpc43descr_tx_flush_split(size, data, data_size);
}



void lip_ll_lpc43descr_tx_poll_req(void) {
    LPC_ETHERNET->DMA_TRANS_POLL_DEMAND = 1;
}

void lip_ll_lpc43descr_clear_stat(void) {
    LPC_ETHERNET->DMA_STAT = LPC_ETHERNET->DMA_STAT;
}

void lip_ll_lpc43descr_dmb(void) {
    __dmb();
}



#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



/***********************************************************************
 *  Отключение интерфейса. После данного вызова порт может
 *  отключить аппаратные средства под ethernet
 ***********************************************************************/
void lip_ll_close(void) {
    /* Flush transmit FIFO */
    LPC_ETHERNET->DMA_OP_MODE = DMA_OM_FTF;
    /* Disable Rx/Tx */
    LPC_ETHERNET->MAC_CONFIG = 0;

#ifdef LIP_PORT_USE_LPCOPEN_CLKCTL
    Chip_Clock_Disable(CLK_MX_ETHERNET);
#else
    #ifdef LPC_CCU_CFG
            LPC_CCU_CFG(CLK_MX_ETHERNET, 0);
    #endif
#endif

}
