#include "lip_sw_ksz8567.h"
#include "lip/phy/lip_phy_cfg_defs.h"

#if LIP_SW_SUPPORT_KSZ8567
#include "lip/link/lip_eth.h"
#include "lip/link/mac/tx/lip_mac_tx.h"
#include "lip/app/ptp/lip_ptp_cfg_defs.h"
#include <string.h>
#include "ltimer.h"
#if LIP_PTP_ENABLE
#include "lip/app/ptp/lip_ptp_private.h"
#include "lip/app/ptp/lip_ptp_proto_defs.h"
#endif

#define LIP_SW_KSZ8567_PTP_TIME_GET_INTERVAL          500

static bool f_tailtag_en;
#if LIP_PTP_ENABLE
static t_lip_ptp_tstmp f_cur_tstmp;
static bool f_ptp_en;
static t_ltimer f_ptp_poll_tmr;
static uint16_t f_ptp_ctl_reg;
#endif

t_kszsw_reg_acc *f_reg_acc = NULL;

#if LIP_PTP_ENABLE
/* Расширение 32-битной отметки времени (2 младших бита секунд + 30 бит нс)
 * до полного 64-битной отметки времени.
 * Используется периодически обновляемое значение времени из свича
 * (чтобы не читать на каждый таймтамп),
 * наносекнды берутся из 32-битной метке, а для расчета секунд
 * сравниваются последние 2 бита секунд метки и текущего времени, чтобы понять,
 * не было ли перехода через секунду, и секунды берутся
 * из текущего времени с учетом перехода (из диапазона -1,0,1,2) */
static void f_get_full_tstmp(uint32_t t32, t_lip_ptp_tstmp *tstmp) {
    int8_t delta = ((t32 >> 30) - f_cur_tstmp.sec) & 0x3;
    if (delta == 3)
        delta = -1;
    tstmp->ns = t32 & 0x3FFFFFFF;
    tstmp->sec = f_cur_tstmp.sec + delta;
    tstmp->hsec = 0;
    tstmp->fract_ns = 0;
}
#endif

t_lip_errs lip_sw_ksz8567_init(t_kszsw_reg_acc *regs_acc) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    f_reg_acc = regs_acc;

    /* Errata Module 4. Правка работы EEE требует установить регистр свича 0x03C2 в 0x0080 */
    err = kszsw_global_reg_write16(f_reg_acc, 0x03C2, 0x0080);



    return err;
}

t_lip_errs lip_sw_ksz8567_pull() {
    t_lip_errs err = LIP_ERR_SUCCESS;
#if LIP_PTP_ENABLE
    /* если PTP резрешен, то периодически обновляем в фоне полное текущее время свича,
     * для расширения 32-битной метки времени на 64-битную с полными секундами.
     * частота обновления должна быть достаточно частой, чтобы разница между текущем
     * временем и меткой пакета была в пределах от -1 до 2 с */
    if (f_ptp_en) {
        if (ltimer_expired(&f_ptp_poll_tmr)) {
            err = lip_sw_ksz8567_tstmp_read(&f_cur_tstmp);
            ltimer_restart(&f_ptp_poll_tmr);
        }
    }
#endif
    return err;
}




t_lip_errs lip_sw_ksz8567_tailtag_set_en(bool en) {
    f_tailtag_en = en;

    return LIP_ERR_SUCCESS;
}

#if LIP_PTP_ENABLE
t_lip_errs lip_sw_ksz8567_ptp_set_en(bool ptp_en) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    f_ptp_en = ptp_en;
    if (ptp_en) {
        ltimer_set_ms(&f_ptp_poll_tmr, LIP_SW_KSZ8567_PTP_TIME_GET_INTERVAL);
        err = kszsw_global_reg_read16(f_reg_acc, KSZSW_REGADDR_PTP_CLK_CTL, &f_ptp_ctl_reg);
    }
    return err;
}

bool lip_sw_ksz8567_ptp_is_en(void) {
    return f_ptp_en;
}

t_lip_errs lip_sw_ksz8567_ptp_proc_irq(uint8_t port_num) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    uint16_t ptp_irqst;
    kszsw_port_reg_read16(f_reg_acc, port_num, KSZSW_REGADDR_PORT_PTP_INT_STAT, &ptp_irqst);
    kszsw_port_reg_write16(f_reg_acc, port_num, KSZSW_REGADDR_PORT_PTP_INT_STAT, ptp_irqst);

    if (ptp_irqst & KSZSW_REGFLD_PORT_PTP_INT_SYNC_TSTMP) {
        t_lip_ptp_tstmp tstmp;
        uint16_t h, l;
        err = kszsw_port_reg_read16(f_reg_acc, port_num, KSZSW_REGADDR_PORT_PTP_EGR_TSTMP_SYNC_H, &h);
        if (err == LIP_ERR_SUCCESS) {
            err = kszsw_port_reg_read16(f_reg_acc, port_num, KSZSW_REGADDR_PORT_PTP_EGR_TSTMP_SYNC_L, &l);
        }
        if (err == LIP_ERR_SUCCESS) {
            f_get_full_tstmp(((uint32_t)h << 16) | l, &tstmp);
            lip_ptp_proc_egress_tstmp(port_num, LIP_PTP_MSGTYPE_SYNC,  &tstmp);
        }
    }

    if (ptp_irqst & KSZSW_REGFLD_PORT_PTP_INT_DELREQ_TSTMP) {
        t_lip_ptp_tstmp tstmp;
        uint16_t h, l;
        err = kszsw_port_reg_read16(f_reg_acc, port_num, KSZSW_REGADDR_PORT_PTP_EGR_TSTMP_DLY_REQ_H, &h);
        if (err == LIP_ERR_SUCCESS) {
            err = kszsw_port_reg_read16(f_reg_acc, port_num, KSZSW_REGADDR_PORT_PTP_EGR_TSTMP_DLY_REQ_L, &l);
        }
        if (err == LIP_ERR_SUCCESS) {
            f_get_full_tstmp(((uint32_t)h << 16) | l, &tstmp);
            lip_ptp_proc_egress_tstmp(port_num, LIP_PTP_MSGTYPE_PDELAY_REQ,  &tstmp);
        }
    }

    if (ptp_irqst & KSZSW_REGFLD_PORT_PTP_INT_DELRESP_TSTMP) {
        t_lip_ptp_tstmp tstmp;
        uint16_t h, l;
        err = kszsw_port_reg_read16(f_reg_acc, port_num, KSZSW_REGADDR_PORT_PTP_EGR_TSTMP_DLY_RESP_H, &h);
        if (err == LIP_ERR_SUCCESS) {
            err = kszsw_port_reg_read16(f_reg_acc, port_num, KSZSW_REGADDR_PORT_PTP_EGR_TSTMP_DLY_RESP_L, &l);
        }
        if (err == LIP_ERR_SUCCESS) {
            f_get_full_tstmp(((uint32_t)h << 16) | l, &tstmp);
            lip_ptp_proc_egress_tstmp(port_num, LIP_PTP_MSGTYPE_PDELAY_RESP,  &tstmp);
        }
    }
    return err;
}
#endif


t_lip_errs lip_sw_ksz8567_preproc_frame(t_lip_rx_ll_frame *ll_frame) {
    if (f_tailtag_en) {
        /* обрабатываем один байт tail-tag, чтобы определить, является ли пакет
         * PTP, что может влять не приоритет очереди.
         * Т.к. данные пакета могут быть разбиты на два буфера, то нужно проверить,
         * последний байт какого буфера нужно использовать для обработки */
        uint8_t tt_wrd = 0;
        if (ll_frame->pl_tail.size > 0) {
            tt_wrd = ll_frame->pl_tail.data[ll_frame->pl_tail.size-1];
            --ll_frame->pl_tail.size;
        } else if (ll_frame->pl_main.size > 0) {
            tt_wrd = ll_frame->pl_main.data[ll_frame->pl_main.size-1];
            --ll_frame->pl_main.size;
        }
        if (tt_wrd & KSZSW_TT_RXWRD_FLD_PTP) {
            /* для KSZ метка времени устанавливается для всех типао PTP пакетов,
             * поэтому признак наличия метки является и признаком PTP пакета.
             * Признак PTP определяем при предобработке, т.к. он может влиять на
             * приоритет очерди, саму метку уже разбираем в основной обработке,
             * так как заранее не требуется и там можно не беспокоиться
             * о гонках при изменении текущего времени свича */
            ll_frame->info.ll_flags |= LIP_LL_RXFLAGS_PTP_PACKET;
        }
        /* дополнительно сохраняем номер порта, чтобы этот байт можно было уже
         * не обрабатывать в дальнейшем */
        ll_frame->info.rx_port = LBITFIELD_GET(tt_wrd, KSZSW_TT_RXWRD_FLD_PORT);
        ll_frame->info.flags |= LIP_FRAME_RXINFO_FLAG_PORT_VALID;
    }
    return LIP_ERR_SUCCESS;
}

t_lip_errs lip_sw_ksz8567_process_packet(t_lip_rx_frame *pkt) {
    /* Основная обработка. Последний байт tail-tag уже обработан и откинут,
     * но если текущий пакет - PTP, то еще остается 4 байта метки времени,
     * которые нужно обработать */
#if LIP_PTP_ENABLE
    if (f_tailtag_en && (pkt->info.ll_flags & LIP_LL_RXFLAGS_PTP_PACKET) && (pkt->pl.size > 4)) {
        t_lip_size pkt_size = pkt->pl.size;

        /* получаем 32-битный формат из tail-tag */
        uint32_t tstmp =  (pkt->pl.data[pkt_size - 1] << 0)
                         | ((uint32_t)(pkt->pl.data[pkt_size - 2] << 8))
                         | ((uint32_t)(pkt->pl.data[pkt_size - 3] << 16))
                         | ((uint32_t)(pkt->pl.data[pkt_size - 4] << 24));
        /* получаем полный timestamp */
        f_get_full_tstmp(tstmp, &pkt->info.tstmp);
        pkt_size -= 4;
        pkt->info.flags |= LIP_FRAME_RXINFO_FLAG_TSTAMP_VALID;
        pkt->pl.size = pkt_size;
    }
#endif
    return LIP_ERR_SUCCESS;
}

t_lip_tx_frame *lip_sw_ksz8567_tx_prepare(const t_lip_tx_frame_info *info) {
     t_lip_tx_frame *frame = lip_mac_tx_frame_prepare(info);
     if (f_tailtag_en && frame) {
#if LIP_PTP_ENABLE
        frame->buf.end_ptr -= f_ptp_en ? 6 : 2;
#else
        frame->buf.end_ptr -= 2;
#endif

     }
     return frame;
}

t_lip_errs lip_sw_ksz8567_tx_flush(t_lip_tx_frame *frame) {
    t_lip_errs err;
    if (f_tailtag_en) {
        /* т.к. необходимо добавлять байты в конец пакета, то не можем использовать
         * внешний буфер для данных - копируем его в основной */
        if (frame->exdata_ptr != NULL) {
            memcpy(frame->buf.cur_ptr, frame->exdata_ptr, frame->exdata_size);
            frame->buf.cur_ptr += frame->exdata_size;
            frame->exdata_ptr = NULL;
            frame->exdata_size = 0;
        }
        /* т.к. tail-tag нужно добавлять в конец после padding-а, то не можем
         * рассчитывать на аппаратный padding (т.к. добавится после tail-tag),
         * а должны довести размер пакета до минимально допустимого вручную */
        /** @todo при аппаратном добавлении VLAN также LIP_ETH_MIN_TXDATA_SIZE нужно уменьшить на 4 */
        while (frame->buf.cur_ptr < (frame->buf.start_ptr + LIP_ETH_MIN_TXDATA_SIZE)) {
            *frame->buf.cur_ptr++ = 0x00;
        }
#if LIP_PTP_ENABLE
        if (f_ptp_en) {
            *frame->buf.cur_ptr++ = 0;
            *frame->buf.cur_ptr++ = 0;
            *frame->buf.cur_ptr++ = 0;
            *frame->buf.cur_ptr++ = 0;
        }
#endif

        uint16_t tt_tx_wrd = LBITFIELD_SET(KSZSW_TT_TXWRD_FLD_PRIO, frame->info.priority >> 6) |
                      LBITFIELD_SET(KSZSW_TT_TXWRD_FLD_PORTBL, (frame->info.flags & LIP_FRAME_TXINFO_FLAG_PORTS_BLK_OVD) != 0);
        if (frame->info.flags & LIP_FRAME_TXINFO_FLAG_PORTS_FWD) {
            tt_tx_wrd |= LBITFIELD_SET(KSZSW_TT_TXWRD_FLD_FWD_PORTS, frame->info.port_mask);
        } else {
            tt_tx_wrd |= KSZSW_TT_TXWRD_FLD_LOOKUP;
        }

        *frame->buf.cur_ptr++ = (tt_tx_wrd >> 8) & 0xFF;
        *frame->buf.cur_ptr++ = (tt_tx_wrd >> 0) & 0xFF;
    }

    err = lip_mac_tx_frame_flush(frame);
    return err;
}


t_lip_errs lip_sw_ksz8567_set_port_state_flags(uint8_t port_num, unsigned flags) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (f_reg_acc) {
        err = kszsw_port_reg_write8(f_reg_acc, port_num, KSZSW_REGADDR_PORT_MSTP_STATE,
                                LBITFIELD_SET(KSZSW_REGFLD_PORT_MSTP_STATE_TX_EN, (flags & LIP_PORT_STATE_FLAG_TX_EN) != 0)
                              | LBITFIELD_SET(KSZSW_REGFLD_PORT_MSTP_STATE_RX_EN, (flags & LIP_PORT_STATE_FLAG_RX_EN) != 0)
                              | LBITFIELD_SET(KSZSW_REGFLD_PORT_MSTP_STATE_LEARN_DIS, (flags & LIP_PORT_STATE_FLAG_LEARN_EN) != 0));
    }
    return err;
}


#if LIP_PTP_ENABLE
t_lip_errs lip_sw_ksz8567_tstmp_read_req(void) {
    return kszsw_global_reg_write16(f_reg_acc, KSZSW_REGADDR_PTP_CLK_CTL, f_ptp_ctl_reg | KSZSW_REGFLD_PTP_CLK_CTL_READ);
}

t_lip_errs lip_sw_ksz8567_tstmp_read_finish(t_lip_ptp_tstmp *tstmp) {
    uint16_t ns_h, ns_l, s_h, s_l;
    t_lip_errs err = kszsw_global_reg_read16(f_reg_acc, KSZSW_REGADDR_PTP_NS_H, &ns_h);
    if (err == LIP_ERR_SUCCESS) {
        err = kszsw_global_reg_read16(f_reg_acc, KSZSW_REGADDR_PTP_NS_L, &ns_l);
    }
    if (err == LIP_ERR_SUCCESS) {
        err = kszsw_global_reg_read16(f_reg_acc, KSZSW_REGADDR_PTP_SEC_H, &s_h);
    }
    if (err == LIP_ERR_SUCCESS) {
        err = kszsw_global_reg_read16(f_reg_acc, KSZSW_REGADDR_PTP_SEC_L, &s_l);
    }

    if (err == LIP_ERR_SUCCESS) {
        tstmp->ns = (ns_h << 16) | ns_l;
        tstmp->sec = (s_h << 16) | s_l;
        tstmp->fract_ns = 0;
        tstmp->hsec = 0; /** @todo подумать определение hsec (возможно из глабально настраиваемой переменной) */
    }
    return err;
}

t_lip_errs lip_sw_ksz8567_tstmp_read(t_lip_ptp_tstmp *tstmp) {
    t_lip_errs err = lip_sw_ksz8567_tstmp_read_req();
    if (err == LIP_ERR_SUCCESS) {
        err = lip_sw_ksz8567_tstmp_read_finish(tstmp);
    }
    return err;
}
#endif


#endif










