/*
 * ESPRESSIF MIT License
 *
 * Copyright (c) 2016 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
 *
 * Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP8266 only, in which case,
 * it is free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished
 * to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "ets_sys.h"
#include "osapi.h"
#include "os_type.h"
//#include "gpio.h"
#include "user_interface.h"
#include "mem.h"
#include "driver/slc_register.h"
#include "driver/sdio_slv.h"

#define SDIO_TOKEN_SIZE     0 // 4
#define RX_BUFFER_SIZE      512
#define RX_BUFFER_NUM       4

#define TX_BUFFER_SIZE        512
#define SLC_INTEREST_EVENT (SLC_TX_EOF_INT_ENA | SLC_RX_EOF_INT_ENA | SLC_RX_UDF_INT_ENA | SLC_TX_DSCR_ERR_INT_ENA)
#define TRIG_TOHOST_INT()    SET_PERI_REG_MASK(SLC_INTVEC_TOHOST , BIT0);\
    //CLEAR_PERI_REG_MASK(SLC_INTVEC_TOHOST , BIT0)
struct sdio_queue {
    uint32_t    blocksize: 12;
    uint32_t    datalen: 12;
    uint32_t    unused: 5;
    uint32_t    sub_sof: 1;
    uint32_t    eof: 1;
    uint32_t    owner: 1;

    uint32_t    buf_ptr;
    uint32_t    next_link_ptr;
};

struct sdio_slave_status_element {
    uint32_t wr_busy: 1;
    uint32_t rd_empty : 1;
    uint32_t comm_cnt : 3;
    uint32_t intr_no : 3;
    uint32_t rx_length: 16;
    uint32_t res: 8;
};

union sdio_slave_status {
    struct sdio_slave_status_element elm_value;
    uint32_t word_value;
};

//uint8_t rx_buffer[RX_BUFFER_NUM][RX_BUFFER_SIZE],tx_buffer[1024];
uint8_t tx_buffer[TX_BUFFER_SIZE];

uint32_t data_len = 0;

struct sdio_list {
    uint8_t buffer[RX_BUFFER_SIZE + SDIO_TOKEN_SIZE];
    uint8_t *tail;
    struct sdio_list *next;
};

static sdio_recv_data_callback_t sdio_recv_data_callback_ptr = NULL;
struct sdio_list *pHead_ToSend;
struct sdio_list *pTail_ToSend;
struct sdio_list *pHead_Sended;
struct sdio_list *pTail_Sended;



os_event_t *sdioQueue;
struct sdio_queue rx_que, tx_que;

static bool has_read = 0;

static void sdio_slave_isr(void *para);
static void tx_buff_handle_done(void);
static void rx_buff_read_done(void);
static void tx_buff_write_done(void);

static void sdio_try_to_load(void);
static void sdio_read_done_process(void);

void sdio_slave_init(void)
{
    uint32_t regval = 0;
    union sdio_slave_status sdio_sta;
    ETS_SDIO_INTR_DISABLE();
    ////reset orginal link
    SET_PERI_REG_MASK(SLC_CONF0, SLC_RXLINK_RST | SLC_TXLINK_RST);
    CLEAR_PERI_REG_MASK(SLC_CONF0, SLC_RXLINK_RST | SLC_TXLINK_RST);

    os_printf("RX&TX link reset!\n");

    //set sdio mode
    SET_PERI_REG_MASK(SLC_RX_DSCR_CONF, SLC_RX_EOF_MODE | SLC_RX_FILL_MODE);
    //clear to host interrupt io signal for preventing from random initial signal.
    WRITE_PERI_REG(SLC_HOST_INTR_CLR, 0xffffffff);
    //enable 2 events to trigger the to host intr io
    SET_PERI_REG_MASK(SLC_HOST_INTR_ENA, SLC_HOST_TOHOST_BIT0_INT_ENA);
    ////initialize rx queue information

    has_read = TRUE;
    pHead_ToSend = NULL;

    int32_t loop = RX_BUFFER_NUM;
    struct sdio_list *p = NULL;

    while (loop--) {
        if (pHead_Sended == NULL) {
            pHead_Sended = (struct sdio_list *)os_malloc(sizeof(struct sdio_list));
            p = pHead_Sended;
        } else {
            p->next = (struct sdio_list *)os_malloc(sizeof(struct sdio_list));
            p = p->next;
        }

        //os_printf("p:0x%08x\r\n",p);
        p->tail = p->buffer + SDIO_TOKEN_SIZE;
        p->next = NULL;
    }

    pTail_Sended = p;

    rx_que.blocksize = RX_BUFFER_SIZE;
    rx_que.datalen = 0;
    rx_que.eof = 1;
    rx_que.owner = 1;
    rx_que.sub_sof = 0;
    rx_que.unused = 0;
    rx_que.buf_ptr = (uint32_t)pHead_Sended->buffer;
    rx_que.next_link_ptr = 0;


    ////initialize tx queue information
    tx_que.blocksize = TX_BUFFER_SIZE;
    tx_que.datalen = 0;
    tx_que.eof = 0;
    tx_que.owner = 1;
    tx_que.sub_sof = 0;
    tx_que.unused = 0;
    tx_que.buf_ptr = (uint32_t)tx_buffer;
    tx_que.next_link_ptr = 0;

    ///////link tx&rx queue information address to sdio hardware
    CLEAR_PERI_REG_MASK(SLC_RX_LINK, SLC_RXLINK_DESCADDR_MASK);
    regval = ((uint32_t)&rx_que);
    SET_PERI_REG_MASK(SLC_RX_LINK, regval & SLC_RXLINK_DESCADDR_MASK);
    CLEAR_PERI_REG_MASK(SLC_TX_LINK, SLC_TXLINK_DESCADDR_MASK);
    regval = ((uint32_t)&tx_que);
    SET_PERI_REG_MASK(SLC_TX_LINK, regval & SLC_TXLINK_DESCADDR_MASK);

#if (SDIO_TOKEN_SIZE == 0)
    SET_PERI_REG_MASK(SLC_RX_DSCR_CONF, SLC_TOKEN_NO_REPLACE);
#endif

    /////config sdio_status reg
    sdio_sta.elm_value.comm_cnt = 7;
    sdio_sta.elm_value.intr_no = INIT_STAGE;
    sdio_sta.elm_value.wr_busy = 0;
    sdio_sta.elm_value.rd_empty = 1;
    sdio_sta.elm_value.rx_length = 0;
    sdio_sta.elm_value.res = 0;
    SET_PERI_REG_MASK(SLC_TX_LINK, SLC_TXLINK_START);
    WRITE_PERI_REG(SLC_HOST_CONF_W2, sdio_sta.word_value);


    /////attach isr func to sdio interrupt
    ETS_SDIO_INTR_ATTACH(sdio_slave_isr, NULL);
    /////enable sdio operation intr
    WRITE_PERI_REG(SLC_INT_ENA,  SLC_INTEREST_EVENT);
    /////clear sdio initial random active intr signal
    WRITE_PERI_REG(SLC_INT_CLR, 0xffffffff);
    /////enable sdio intr in cpu
    ETS_SDIO_INTR_ENABLE();
}

static void sdio_slave_isr(void *para)
{
    uint32_t slc_intr_status, postval;
    static uint8_t state = 0;
    uint16_t rx_len, i;
    uint32_t *pword;
    union sdio_slave_status sdio_sta;

    slc_intr_status = READ_PERI_REG(SLC_INT_STATUS);

    if (slc_intr_status == 0) {
        /* No interested interrupts pending */
        return;
    }

    //clear all intrs
    WRITE_PERI_REG(SLC_INT_CLR, slc_intr_status);
    //os_printf("slc_intr_status:0x%08x\r\n",slc_intr_status);
    //process every intr

    //TO HOST DONE
    if (slc_intr_status & SLC_RX_EOF_INT_ENA) {
        //following code must be called after a data pack has been read
        rx_buff_read_done();
        //TRIG_TOHOST_INT();
        //system_os_post(2, 1, 0);
        sdio_read_done_process();
    }

    //FROM HOST DONE
    if (slc_intr_status & SLC_TX_EOF_INT_ENA) {
        //call the following function after host cpu data transmission finished
        tx_buff_write_done();

        //system_os_post(USER_TASK_PRIO_1,SDIO_DATA_ERROR,0);
        //os_printf("%d,%s\r\n",tx_que.datalen,tx_que.buf_ptr);
        //at_fake_uart_rx((uint8_t*)tx_que.buf_ptr,tx_que.datalen);
        if (sdio_recv_data_callback_ptr) {
            sdio_recv_data_callback_ptr((uint8_t *)tx_que.buf_ptr, tx_que.datalen);
        }

        tx_buff_handle_done();
        TRIG_TOHOST_INT();
        //system_os_post(2, 3, 0);
    }

    //TO HOST underflow
    if (slc_intr_status & SLC_RX_UDF_INT_ENA) {
    }

    //FROM HOST overflow
    if (slc_intr_status & SLC_TX_DSCR_ERR_INT_ENA) {
    }

    slc_intr_status = READ_PERI_REG(SLC_INT_STATUS);

    if (slc_intr_status) {
        WRITE_PERI_REG(SLC_INT_CLR, slc_intr_status);
        os_printf("slc_intr_status:0x%08x\r\n", slc_intr_status);
    }

}

static void rx_buff_read_done(void)
{
    union sdio_slave_status sdio_sta;
    /////modify sdio status reg
    sdio_sta.word_value = READ_PERI_REG(SLC_HOST_CONF_W2);
    sdio_sta.elm_value.comm_cnt++;
    sdio_sta.elm_value.rd_empty = 1;
    sdio_sta.elm_value.rx_length = 0;
    sdio_sta.elm_value.intr_no &= (~RX_AVAILIBLE);
    WRITE_PERI_REG(SLC_HOST_CONF_W2, sdio_sta.word_value);  //update sdio status register
    //os_printf("rx_buff_read_done\r\n");
}

static void tx_buff_write_done(void)
{
    union sdio_slave_status sdio_sta;
    /////modify sdio status reg
    sdio_sta.word_value = READ_PERI_REG(SLC_HOST_CONF_W2);
    sdio_sta.elm_value.comm_cnt++;
    sdio_sta.elm_value.wr_busy = 1;
    sdio_sta.elm_value.intr_no &= (~TX_AVAILIBLE);
    WRITE_PERI_REG(SLC_HOST_CONF_W2, sdio_sta.word_value);  //update sdio status register
}

static void tx_buff_handle_done(void)
{
    union sdio_slave_status sdio_sta;

    /////config tx queue information
    tx_que.blocksize = TX_BUFFER_SIZE;
    tx_que.datalen = 0;
    tx_que.eof = 0;
    tx_que.owner = 1;

    /////modify sdio status reg
    sdio_sta.word_value = READ_PERI_REG(SLC_HOST_CONF_W2);
    sdio_sta.elm_value.wr_busy = 0;
    sdio_sta.elm_value.intr_no |= TX_AVAILIBLE;

    SET_PERI_REG_MASK(SLC_TX_LINK, SLC_TXLINK_START);       //tx buffer is ready for being written
    WRITE_PERI_REG(SLC_HOST_CONF_W2, sdio_sta.word_value);  //update sdio status register
    //*******************************************************************//

}
static int32_t rx_buff_load_done(uint16_t rx_len)
{
    union sdio_slave_status sdio_sta;

    if (rx_len == 0) {
        return 0;
    }

    if (rx_len > rx_que.blocksize) {
        rx_len = rx_que.blocksize;
    }

    //os_memcpy(rx_que.buf_ptr,data,rx_len);
    /////config rx queue information
    rx_que.blocksize = RX_BUFFER_SIZE;
    rx_que.datalen = rx_len + SDIO_TOKEN_SIZE;
    rx_que.eof = 1;
    rx_que.owner = 1;

    //ETS_SDIO_INTR_DISABLE();
    //available_buffer_amount--;

    /////modify sdio status reg
    sdio_sta.word_value = READ_PERI_REG(SLC_HOST_CONF_W2);
    sdio_sta.elm_value.rd_empty = 0;
    sdio_sta.elm_value.intr_no |= RX_AVAILIBLE;
    sdio_sta.elm_value.rx_length = rx_len;

    SET_PERI_REG_MASK(SLC_RX_LINK, SLC_RXLINK_START);       //rx buffer is ready for being read
    WRITE_PERI_REG(SLC_HOST_CONF_W2, sdio_sta.word_value);  //update sdio status register
    //ETS_SDIO_INTR_ENABLE();
    //os_printf("rx_buff_load_done(%d,0x%08x):%s\r\n",rx_len,rx_que.buf_ptr,rx_que.buf_ptr);
    //os_printf("rx_buff_load_done:%d\r\n",rx_len);
    return rx_len;
}

int32_t ICACHE_FLASH_ATTR sdio_load_data(const uint8_t *data, uint32_t len)
{
    int32_t data_len = 0;

    if (pHead_Sended == NULL) {
        os_printf("no buf\r\n");
        return 0;
    }

    int32_t left_len = 0;

    while (len) {
        left_len = RX_BUFFER_SIZE + SDIO_TOKEN_SIZE - (uint32_t)(pHead_Sended->tail - pHead_Sended->buffer);

        if (len < left_len) {
            os_memcpy(pHead_Sended->tail, data, len);
            pHead_Sended->tail += len;
            len = 0;
            data_len += len;
            //os_printf(">555:0x%08x,0x%08x\r\n",pHead_Sended->buffer,pHead_Sended->tail);
        } else {
            os_memcpy(pHead_Sended->tail, data, left_len);
            pHead_Sended->tail += left_len;
            len -= left_len;
            data += left_len;
            data_len += left_len;

            if (pHead_ToSend == NULL) {
                pTail_ToSend = pHead_Sended;
                pHead_ToSend = pTail_ToSend;
            } else {
                pTail_ToSend->next = pHead_Sended;
                pTail_ToSend = pTail_ToSend->next;
            }

            pHead_Sended = pHead_Sended->next;

            pTail_ToSend->next = NULL;

            if (pHead_Sended == NULL) {
                os_printf("buf full\r\n");
                break;
            }

            //os_printf(">666\r\n");
        }
    }

    //os_printf(">>pHead_ToSend:0x%08x\r\n",pHead_ToSend);

    if (pHead_ToSend == NULL) {
        pTail_ToSend = pHead_Sended;
        pHead_ToSend = pTail_ToSend;

        pHead_Sended = pHead_Sended->next;
        pTail_ToSend->next = NULL;
        //system_os_post(2, 2, 0);
        sdio_try_to_load();
    }

    return data_len;
}

static void sdio_try_to_load(void)
{
    if ((has_read == TRUE) && (pHead_ToSend != NULL)) {
        rx_que.buf_ptr = (uint32_t)pHead_ToSend->buffer;
        rx_buff_load_done(pHead_ToSend->tail - pHead_ToSend->buffer - SDIO_TOKEN_SIZE);
        //pHead_ToSend = pHead_ToSend->next;
        has_read = FALSE;
        //os_printf("SLC_INT_STATUS:0x%08x\r\n",READ_PERI_REG(SLC_INT_STATUS));
        TRIG_TOHOST_INT();
    }
}

static void sdio_read_done_process(void)
{
    has_read = TRUE;

    pHead_ToSend->tail = pHead_ToSend->buffer + SDIO_TOKEN_SIZE;

    if (pHead_Sended) {
        pTail_Sended->next = pHead_ToSend;
        pTail_Sended = pTail_Sended->next;
    } else {
        pTail_Sended = pHead_ToSend;
        pHead_Sended = pTail_Sended;
    }

    pHead_ToSend = pHead_ToSend->next;
    pTail_Sended->next = NULL;

    //os_printf(">>pHead_ToSend:0x%08x,pHead_Sended:0x%08x,0x%08x,0x%08x\r\n",pHead_ToSend,pHead_Sended,pHead_Sended->buffer,pHead_Sended->tail);
    if (pHead_ToSend) {
        rx_que.buf_ptr = (uint32_t)pHead_ToSend->buffer;
        rx_buff_load_done(pHead_ToSend->tail - pHead_ToSend->buffer - SDIO_TOKEN_SIZE);
        has_read = FALSE;
        //os_printf("intr trig\r\n");
        //TRIG_TOHOST_INT();
    } else if ((pHead_Sended != NULL) && (pHead_Sended->buffer != (pHead_Sended->tail - SDIO_TOKEN_SIZE))) {
        pHead_ToSend = pHead_Sended;
        pTail_ToSend = pHead_ToSend;
        pHead_Sended = pHead_Sended->next;
        pTail_ToSend->next = NULL;

        rx_que.buf_ptr = (uint32_t)pHead_ToSend->buffer;
        rx_buff_load_done(pHead_ToSend->tail - pHead_ToSend->buffer - SDIO_TOKEN_SIZE);
        has_read = FALSE;
        //os_printf("intr trig\r\n");
        //TRIG_TOHOST_INT();
    }

    TRIG_TOHOST_INT();
}

bool sdio_register_recv_cb(sdio_recv_data_callback_t cb)
{
    sdio_recv_data_callback_ptr = cb;

    return TRUE;
}

