/*
 * ESPRESSIF MIT License
 *
 * Copyright (c) 2019 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
 *
 * Permission is hereby granted for use on ESPRESSIF SYSTEMS ESP32 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 <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "esp_system.h"
#include "esp_log.h"

#include "driver/gpio.h"
//#include "at_spi_driver.h"
#include "esp_at.h"

#include <string.h>
#include "sdkconfig.h"
#include <hal/spi_ll.h>
#include <hal/spi_slave_hal.h>
#include <soc/lldesc.h>
#include "driver/spi_common_internal.h"
#include "driver/spi_slave.h"
#include "soc/spi_periph.h"
#include "esp_types.h"
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_pm.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/xtensa_api.h"
#include "freertos/task.h"
#include "soc/soc_memory_layout.h"
#include "driver/gpio.h"
#include "esp_heap_caps.h"

//#define SPI_SLAVE_HANDSHARK_GPIO     CONFIG_AT_HSPI_HANDSHAKE_PIN
//#define SPI_SLAVE_HANDSHARK_SEL      (1ULL<<SPI_SLAVE_HANDSHARK_GPIO)
#define AT_READ_STREAM_BUFFER_SIZE      1024*2
#define AT_WRITE_STREAM_BUFFER_SIZE      1024*4

//#define LOG_LOCAL_LEVEL 4     //debug mode, it will print debug log
#define GPIO_HANDSHAKE 4
#define GPIO_MOSI 13
#define GPIO_MISO 12
#define GPIO_SCLK 14
#define GPIO_CS 15

static xSemaphoreHandle spi_pxMutex;
static const char* TAG = "HSPI-AT";
static uint8_t *recv_data;
static uint32_t notify_len = 0;
//static const char* TAG = "at_spi_slave";
#define SPI_CHECK(a, str, ret_val) \
    if (!(a)) { \
        ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \
        return (ret_val); \
    }

#define VALID_HOST(x) ((x>SPI_HOST) && (x<=VSPI_HOST))
#define DMA_CHAN    2

// DMA max transmit length at one time is 4096 - 4
#define MAX_SPI_RECEIVE_SIZE 4092

//#define LOG_LOCAL_LEVEL 4     //debug mode, it will print debug log

typedef enum {
    SPI_STATE_IDLE = 0,
    SPI_READ_STATUS,
    SPI_READ_DATA,
    SPI_WRITE_STATUS,
    SPI_WRITE_DATA
} spi_state_t;

typedef struct {
    int id;
    spi_slave_interface_config_t cfg;
    intr_handle_t intr;
    spi_dev_t* hw;
    lldesc_t* dmadesc_tx;
    lldesc_t* dmadesc_rx;
    uint32_t flags;
    int max_transfer_sz;
    QueueHandle_t recv_queue;
    QueueHandle_t send_queue;
    spi_state_t cur_state;
    void* rx_buffer;
    void* tx_buffer;
    int dma_chan;
#ifdef CONFIG_PM_ENABLE
    esp_pm_lock_handle_t pm_lock;
#endif
} spi_slave_t;

static spi_slave_t* spihost[3];

static void IRAM_ATTR at_spi_intr(void* arg);

static inline bool bus_is_iomux(spi_slave_t* host)
{
    return host->flags & SPICOMMON_BUSFLAG_NATIVE_PINS;
}

esp_err_t at_spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t* bus_config, const spi_slave_interface_config_t* slave_config, int dma_chan)
{
    bool spi_chan_claimed, dma_chan_claimed;
    esp_err_t ret = ESP_OK;
    //We only support HSPI/VSPI, period.
    SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG);
    SPI_CHECK(dma_chan > 0 && dma_chan <= 2, "invalid dma channel", ESP_ERR_INVALID_ARG);
    SPI_CHECK((bus_config->intr_flags & (ESP_INTR_FLAG_HIGH | ESP_INTR_FLAG_EDGE | ESP_INTR_FLAG_INTRDISABLED)) == 0, "intr flag not allowed", ESP_ERR_INVALID_ARG);

    spi_chan_claimed = spicommon_periph_claim(host, "spi slave");
    SPI_CHECK(spi_chan_claimed, "host already in use", ESP_ERR_INVALID_STATE);

    if (dma_chan != 0) {
        dma_chan_claimed = spicommon_dma_chan_claim(dma_chan);

        if (!dma_chan_claimed) {
            spicommon_periph_free(host);
            SPI_CHECK(dma_chan_claimed, "dma channel already in use", ESP_ERR_INVALID_STATE);
        }
    }

    spihost[host] = malloc(sizeof(spi_slave_t));

    if (spihost[host] == NULL) {
        ret = ESP_ERR_NO_MEM;
        goto cleanup;
    }

    memset(spihost[host], 0, sizeof(spi_slave_t));
    memcpy(&spihost[host]->cfg, slave_config, sizeof(spi_slave_interface_config_t));
    spihost[host]->id = host;

    ret = spicommon_bus_initialize_io(host, bus_config, dma_chan, SPICOMMON_BUSFLAG_SLAVE | bus_config->flags, &spihost[host]->flags);

    if (ret != ESP_OK) {
        goto cleanup;
    }

    spicommon_cs_initialize(host, slave_config->spics_io_num, 0, !bus_is_iomux(spihost[host]));

    spihost[host]->dma_chan = dma_chan;

    if (dma_chan != 0) {
        //See how many dma descriptors we need and allocate them
        int dma_desc_ct = (bus_config->max_transfer_sz + SPI_MAX_DMA_LEN - 1) / SPI_MAX_DMA_LEN;

        if (dma_desc_ct == 0) {
            dma_desc_ct = 1;    //default to 4k when max is not given
        }

        spihost[host]->max_transfer_sz = dma_desc_ct * SPI_MAX_DMA_LEN;

        // malloc
        spihost[host]->dmadesc_tx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);
        spihost[host]->dmadesc_rx = heap_caps_malloc(sizeof(lldesc_t) * dma_desc_ct, MALLOC_CAP_DMA);

        if (!spihost[host]->dmadesc_tx || !spihost[host]->dmadesc_rx) {
            ret = ESP_ERR_NO_MEM;
            goto cleanup;
        }
    }

#ifdef CONFIG_PM_ENABLE
    ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "spi_slave",
                             &spihost[host]->pm_lock);
    if (ret != ESP_OK) {
        goto cleanup;
    }

    // Lock APB frequency while SPI slave driver is in use
    esp_pm_lock_acquire(spihost[host]->pm_lock);
#endif //CONFIG_PM_ENABLE

    spihost[host]->recv_queue = xSemaphoreCreateBinary();
    spihost[host]->send_queue = xSemaphoreCreateBinary();

    if (!spihost[host]->recv_queue || !spihost[host]->send_queue) {
        ret = ESP_ERR_NO_MEM;
        goto cleanup;
    }

    // memory will always be used
    spihost[host]->rx_buffer = (uint8_t*) heap_caps_malloc(MAX_SPI_RECEIVE_SIZE, MALLOC_CAP_DMA);

    if (!spihost[host]->rx_buffer) {
        ret = ESP_ERR_NO_MEM;
        goto cleanup;
    }

    int flags = bus_config->intr_flags | ESP_INTR_FLAG_INTRDISABLED;
    ret = esp_intr_alloc(spicommon_irqsource_for_host(host), flags, at_spi_intr, (void*)spihost[host], &spihost[host]->intr);

    if (ret != ESP_OK) {
        goto cleanup;
    }

    spihost[host]->hw = spi_periph_signal[host].hw;

    //Configure slave
    spihost[host]->hw->clock.val = 0;
    spihost[host]->hw->user.val = 0;
    spihost[host]->hw->ctrl.val = 0;

    spihost[host]->hw->user.usr_command = 1;
    spihost[host]->hw->user.usr_addr = 1;
    spihost[host]->hw->user.usr_dummy = 0;
    spihost[host]->hw->user.usr_miso = 1;
    spihost[host]->hw->user.usr_mosi = 1;
    spihost[host]->hw->user.doutdin = 0;
    //spihost[host]->hw->user.rd_byte_order = 1;
    //spihost[host]->hw->user.wr_byte_order = 1;

    spihost[host]->hw->user1.usr_addr_bitlen = 0;
    spihost[host]->hw->user2.usr_command_bitlen = 7;
    spihost[host]->hw->user1.usr_dummy_cyclelen = 0;


    spihost[host]->hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
    spihost[host]->hw->dma_out_link.start = 0;
    spihost[host]->hw->dma_in_link.start = 0;
    spihost[host]->hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);
    spihost[host]->hw->dma_conf.out_data_burst_en = 1;

    bool nodelay = true;
    spihost[host]->hw->ctrl.rd_bit_order = (slave_config->flags & SPI_SLAVE_RXBIT_LSBFIRST) ? 1 : 0;
    spihost[host]->hw->ctrl.wr_bit_order = (slave_config->flags & SPI_SLAVE_TXBIT_LSBFIRST) ? 1 : 0;

    if (slave_config->mode == 0) {
        spihost[host]->hw->pin.ck_idle_edge = 0;
        spihost[host]->hw->user.ck_i_edge = 1;
        spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 2;
    } else if (slave_config->mode == 1) {
        spihost[host]->hw->pin.ck_idle_edge = 0;
        spihost[host]->hw->user.ck_i_edge = 0;
        spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 1;
    } else if (slave_config->mode == 2) {
        spihost[host]->hw->pin.ck_idle_edge = 1;
        spihost[host]->hw->user.ck_i_edge = 0;
        spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 1;
    } else if (slave_config->mode == 3) {
        spihost[host]->hw->pin.ck_idle_edge = 1;
        spihost[host]->hw->user.ck_i_edge = 1;
        spihost[host]->hw->ctrl2.miso_delay_mode = nodelay ? 0 : 2;
    }

    //Reset DMA
    spihost[host]->hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
    spihost[host]->hw->dma_out_link.start = 0;
    spihost[host]->hw->dma_in_link.start = 0;
    spihost[host]->hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);

    //Enable intr

    spihost[host]->hw->slave.slave_mode = 1;
    spihost[host]->hw->slave.wr_rd_buf_en = 1;
    spihost[host]->hw->slave.wr_rd_sta_en = 1;
    //spihost[host]->hw->slave.wr_rd_sta_en = 0;


    spihost[host]->hw->slave.rd_buf_inten = 1;
    spihost[host]->hw->slave.wr_buf_inten = 1;
    spihost[host]->hw->slave.rd_sta_inten = 1;
    //spihost[host]->hw->slave.rd_sta_inten = 0;
    spihost[host]->hw->slave.wr_sta_inten = 1;

    spihost[host]->hw->slave.trans_inten = 0;

    spihost[host]->hw->slave1.status_bitlen = 31;
    spihost[host]->hw->slave1.rdbuf_dummy_en = 0;
    spihost[host]->hw->slave1.rdsta_dummy_en = 0;
    spihost[host]->hw->slave1.wrbuf_dummy_en = 0;
    spihost[host]->hw->slave1.wrsta_dummy_en = 0;
    spihost[host]->hw->slave1.wr_addr_bitlen = 7;
    spihost[host]->hw->slave1.rd_addr_bitlen = 7;

    // if 1 will read WR_STATUS
    spihost[host]->hw->slave1.status_readback = 0;

    spihost[host]->hw->cmd.usr = 0;
    spihost[host]->hw->slave.sync_reset = 1;
    spihost[host]->hw->slave.sync_reset = 0;

    spihost[host]->hw->cmd.usr = 1;

    spihost[host]->cur_state = SPI_STATE_IDLE;

    esp_intr_enable(spihost[host]->intr);

    return ESP_OK;

cleanup:

    if (spihost[host]) {
        free(spihost[host]->dmadesc_tx);
        free(spihost[host]->dmadesc_rx);
#ifdef CONFIG_PM_ENABLE
        if (spihost[host]->pm_lock) {
            esp_pm_lock_release(spihost[host]->pm_lock);
            esp_pm_lock_delete(spihost[host]->pm_lock);
        }
#endif
    }

    if (spihost[host]->rx_buffer) {
        free(spihost[host]->rx_buffer);
    }

    free(spihost[host]);
    spihost[host] = NULL;
    spicommon_periph_free(host);
    spicommon_dma_chan_free(dma_chan);
    return ret;
}

esp_err_t at_spi_slave_free(spi_host_device_t host)
{
    SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG);
    SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG);

    if (spihost[host]->dma_chan > 0) {
        spicommon_dma_chan_free(spihost[host]->dma_chan);
    }

    if (spihost[host]->rx_buffer) {
        free(spihost[host]->rx_buffer);
    }

    free(spihost[host]->dmadesc_tx);
    free(spihost[host]->dmadesc_rx);
    esp_intr_free(spihost[host]->intr);
#ifdef CONFIG_PM_ENABLE
    esp_pm_lock_release(spihost[host]->pm_lock);
    esp_pm_lock_delete(spihost[host]->pm_lock);
#endif //CONFIG_PM_ENABLE
    free(spihost[host]);
    spihost[host] = NULL;
    spicommon_periph_free(host);
    return ESP_OK;
}

static void IRAM_ATTR spi_load_trans_buffer(spi_slave_t* host, uint8_t* trans_data, uint32_t trans_len, bool isrx)
{
    spicommon_dmaworkaround_transfer_active(host->dma_chan);
    host->hw->slv_rd_bit.slv_rdata_bit = 0;
    host->hw->dma_conf.val |= SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST;
    host->hw->dma_out_link.start = 0;
    host->hw->dma_in_link.start = 0;
    host->hw->dma_conf.val &= ~(SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST);
    host->hw->dma_conf.out_data_burst_en = 0;
    host->hw->dma_conf.indscr_burst_en = 0;
    host->hw->dma_conf.outdscr_burst_en = 0;

    host->hw->slv_wrbuf_dlen.bit_len = trans_len * 8 - 1;
    host->hw->slv_rdbuf_dlen.bit_len = trans_len * 8 - 1;
    host->hw->mosi_dlen.usr_mosi_dbitlen = trans_len * 8 - 1;
    host->hw->miso_dlen.usr_miso_dbitlen = trans_len * 8 - 1;

    //Fill DMA descriptors
    if (isrx) {
        host->hw->user.usr_miso_highpart = 0;
        lldesc_setup_link(host->dmadesc_rx, host->rx_buffer, trans_len, true);
        host->hw->dma_in_link.addr = (int)(&host->dmadesc_rx[0]) & 0xFFFFF;

        host->hw->dma_in_link.start = 1;
    } else {
        lldesc_setup_link(host->dmadesc_tx, trans_data, trans_len, false);
        host->hw->user.usr_mosi_highpart = 0;
        host->hw->dma_out_link.addr = (int)(&host->dmadesc_tx[0]) & 0xFFFFF;
        host->hw->dma_out_link.start = 1;
    }

    host->hw->slave.sync_reset = 1;
    host->hw->slave.sync_reset = 0;
}

esp_err_t IRAM_ATTR at_spi_slave_receive(spi_host_device_t host, uint8_t** recv_data, uint32_t* recv_len, TickType_t ticks_to_wait)
{
    BaseType_t r;
    SPI_CHECK(VALID_HOST(host), "invalid host", ESP_ERR_INVALID_ARG);
    SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG);

    // wait MCU send length
    r = xSemaphoreTake(spihost[host]->recv_queue, ticks_to_wait);

    if (!r) {
        return ESP_ERR_TIMEOUT;
    }

    if (spihost[host]->cur_state != SPI_WRITE_DATA) {
        ESP_LOGE(TAG, "Line: %d, Status error, %d\r\n", __LINE__, spihost[host]->cur_state);
        return ESP_FAIL;
    }

    *recv_len = spihost[host]->hw->slv_wr_status;
    *recv_data = spihost[host]->rx_buffer;

    return ESP_OK;
}

esp_err_t IRAM_ATTR at_spi_slave_free_receive_buffer(spi_host_device_t host)
{
    SPI_CHECK(spihost[host]->cur_state == SPI_WRITE_DATA, "SPI status error", ESP_ERR_INVALID_STATE);
    spihost[host]->hw->slv_wr_status = 0;

    memset(spihost[host]->rx_buffer, 0x0, MAX_SPI_RECEIVE_SIZE);
    spihost[host]->cur_state = SPI_STATE_IDLE;

    // notify MASTER can send again
    if (spihost[host]->cfg.post_setup_cb) {
        spihost[host]->cfg.post_setup_cb(NULL);
    }

    return ESP_OK;
}

esp_err_t IRAM_ATTR at_spi_slave_send(spi_host_device_t host, void* send_data, uint32_t send_len, TickType_t ticks_to_wait)
{
    BaseType_t r;
    SPI_CHECK(send_data == NULL || esp_ptr_dma_capable(send_data),
              "txdata not in DMA-capable memory", ESP_ERR_INVALID_ARG);
    SPI_CHECK(send_len > 0 && send_len < 4096, "Send length error", ESP_ERR_INVALID_ARG);

    //SPI_CHECK(spihost[host]->cur_state == SPI_STATE_IDLE, "SPI status error", ESP_ERR_INVALID_STATE);
    if (spihost[host]->cur_state != SPI_STATE_IDLE) {
        printf("send status error %d\r\n", spihost[host]->cur_state);
    }

    if (spihost[host]->cfg.post_trans_cb) {
        spihost[host]->cfg.post_trans_cb(NULL);
    }

    spihost[host]->hw->rd_status.status = send_len;
    spihost[host]->tx_buffer = send_data;

    // notify MASTER can receive
    if (spihost[host]->cfg.post_setup_cb) {
        spihost[host]->cfg.post_setup_cb(NULL);
    }

    r = xSemaphoreTake(spihost[host]->send_queue, ticks_to_wait);

    if (!r) {
        return ESP_ERR_TIMEOUT;
    }

    spihost[host]->hw->rd_status.status = 0;
    spihost[host]->cur_state = SPI_STATE_IDLE;

    return ESP_OK;
}

//This is run in interrupt context and apart from initialization and destruction, this is the only code
//touching the host (=spihost[x]) variable. The rest of the data arrives in queues. That is why there are
//no muxes in this code.
static void IRAM_ATTR at_spi_intr(void* arg)
{
    BaseType_t do_yield = pdFALSE;
    spi_slave_t* host = (spi_slave_t*)arg;
    uint32_t trans_len = 0;
    uint32_t cnt = 0;
    ESP_EARLY_LOGV(TAG, "intr: %x", host->hw->slave);
    ESP_EARLY_LOGE(TAG, "cur_state:: %d", host->cur_state);
    ESP_EARLY_LOGE(TAG, "wr_sta_done:: %d", host->hw->slave.wr_sta_done);
    ESP_EARLY_LOGE(TAG, "rd_sta_done:: %d", host->hw->slave.rd_sta_done);
    ESP_EARLY_LOGE(TAG, "wr_buf_done:: %d", host->hw->slave.wr_buf_done);
    ESP_EARLY_LOGE(TAG, "rd_buf_done:: %d", host->hw->slave.rd_buf_done);
    if (host->hw->slave.wr_sta_done) {
        if (host->cfg.post_trans_cb) {
            host->cfg.post_trans_cb(NULL);
        }

        host->hw->slave.wr_sta_done = 0;

        if (host->cur_state != SPI_STATE_IDLE) {
            ESP_EARLY_LOGE(TAG, "WR_STA error status %d", host->cur_state);
            return;
        }

        ESP_EARLY_LOGD(TAG, "WR_DONE, len: %d", host->hw->slv_wr_status);
        trans_len = host->hw->slv_wr_status;

        if (trans_len > MAX_SPI_RECEIVE_SIZE || trans_len == 0) {
            ets_printf("recv length error: %d\r\n", trans_len);
            return;
        }
        ESP_EARLY_LOGE(TAG, "recv length:: %d", trans_len);
        spi_load_trans_buffer(host, host->rx_buffer, trans_len, true);
        host->cur_state = SPI_WRITE_STATUS;
    }

    if (host->hw->slave.rd_sta_done) {
        if (host->cfg.post_trans_cb) {
            host->cfg.post_trans_cb(NULL);
        }

        host->hw->slave.rd_sta_done = 0;

        if (host->cur_state != SPI_STATE_IDLE) {
            ESP_EARLY_LOGE(TAG, "RD_STA error status %d", host->cur_state);
            return;
        }

        if (host->hw->rd_status.status == 0) {
            ESP_EARLY_LOGE(TAG, "RD_STA len error");
            return;
        }

        spi_load_trans_buffer(host, host->tx_buffer, host->hw->rd_status.status, false);
        ESP_EARLY_LOGD(TAG, "RD_DONE, len: %d", host->hw->rd_status.status);
        host->cur_state = SPI_READ_STATUS;
    }

    if (host->hw->slave.wr_buf_done) {
        host->hw->slave.wr_buf_done = 0;

        if (host->cur_state != SPI_WRITE_STATUS) {
            ESP_EARLY_LOGE(TAG, "WR_BUF error status %d\r\n", host->cur_state);
            return;
        }

        host->cur_state = SPI_WRITE_DATA;
        ESP_EARLY_LOGD(TAG, "WR_BUF_DONE");

        // tell muc need wait
        if (host->cfg.post_trans_cb) {
            host->cfg.post_trans_cb(NULL);
        }
        
        //Okay, transaction is done.
        //Return transaction descriptor.
        xSemaphoreGiveFromISR(host->recv_queue, &do_yield);
    }

    if (host->hw->slave.rd_buf_done) {
        if (host->cur_state != SPI_READ_STATUS) {
            ESP_EARLY_LOGE(TAG, "RD_BUF error status %d", host->cur_state);
            return;
        }

        host->hw->slave.rd_buf_done = 0;
        host->cur_state = SPI_READ_DATA;
        ESP_EARLY_LOGD(TAG, "RD_BUF_DONE");

        xSemaphoreGiveFromISR(host->send_queue, &do_yield);
    }

    // After the software cleanup interrupt, the hardware may still be uncleaned, at which point you need to wait
    while (host->hw->slave.val & 0xF) {
        host->hw->slave.val &= ~0xF;
        cnt++;
        // 500 is enough for the hardware to cleanup interrupt
        if (cnt >= 500) {
            ESP_EARLY_LOGE(TAG, "intr err: %x", host->hw->slave);
            cnt = 0;
        }
    }
    
    ESP_EARLY_LOGE(TAG, "cur_state:: %d", host->cur_state);
    ESP_EARLY_LOGE(TAG, "wr_sta_done:: %d", host->hw->slave.wr_sta_done);
    ESP_EARLY_LOGE(TAG, "rd_sta_done:: %d", host->hw->slave.rd_sta_done);
    ESP_EARLY_LOGE(TAG, "wr_buf_done:: %d", host->hw->slave.wr_buf_done);
    ESP_EARLY_LOGE(TAG, "rd_buf_done:: %d", host->hw->slave.rd_buf_done);
    if (host->cur_state == SPI_READ_STATUS || host->cur_state == SPI_WRITE_STATUS) {
        // handshake pull high
        if (host->cfg.post_setup_cb) {
            host->cfg.post_setup_cb(NULL);
        }
    }

    host->hw->cmd.usr = 1;

    if (do_yield) {
        portYIELD_FROM_ISR();
    }
}

void spi_mutex_lock(void)
{
    xSemaphoreTake(spi_pxMutex, portMAX_DELAY);
}

void spi_mutex_unlock(void)
{
    xSemaphoreGive(spi_pxMutex);
}

//Called notify master can send data. We use this to set the handshake line high.
void pull_high_cb(spi_slave_transaction_t *trans) {
    //WRITE_PERI_REG(GPIO_OUT_W1TS_REG, SPI_SLAVE_HANDSHARK_SEL);
    ESP_EARLY_LOGE(TAG, "handshake pull high");
    WRITE_PERI_REG(GPIO_OUT_W1TS_REG, (1<<GPIO_HANDSHAKE));
}

//Called notify master need to wait. We use this to set the handshake line low.
void pull_low_cb(spi_slave_transaction_t *trans) {
    //WRITE_PERI_REG(GPIO_OUT_W1TC_REG, SPI_SLAVE_HANDSHARK_SEL);
    ESP_EARLY_LOGE(TAG, "handshake pull low");
    WRITE_PERI_REG(GPIO_OUT_W1TC_REG, (1<<GPIO_HANDSHAKE));
}

/* Called when spi receive a normal AT command, make sure you have added \r\n in your spi data */
static int32_t at_spi_read_data(uint8_t* data, int32_t len)
{

    ESP_EARLY_LOGE(TAG, "read len: %d", len);
    if (data == NULL || len < 0) {
        return -1;
    }

    if (len == 0) {
        ESP_LOGI(TAG, "Empty read data.");
        return 0;
    }

    memcpy(data, recv_data, len);
    ESP_LOGD(TAG, "read len: %d", len);
    at_spi_slave_free_receive_buffer(HSPI_HOST);
    spi_mutex_unlock();
    return len;
}

/* Result of AT command, auto call when read_data get data */
static int32_t at_spi_write_data(uint8_t* buf, int32_t len)
{
    esp_err_t ret;
    uint8_t wait_time = 10;
    uint8_t* sendbuf = NULL;
    ESP_EARLY_LOGE(TAG, "Write data len: %d", len);
    if (len < 0 || buf == NULL) {
        ESP_LOGE(TAG, "Cannot get write data.");
        return -1;
    }

    if (len == 0) {
        ESP_LOGE(TAG, "Empty write data.");
        return 0;
    }
    spi_mutex_lock();
    ESP_LOGD(TAG, "Write data len: %d", len);
    sendbuf = heap_caps_malloc(len, MALLOC_CAP_DMA);
    if (sendbuf == NULL) {
        ESP_LOGE(TAG , "Malloc send buffer fail!");
        spi_mutex_unlock();
        return 0;
    }
    memcpy(sendbuf, buf, len);

    while (wait_time--) {
        ret = at_spi_slave_send(HSPI_HOST, sendbuf, len, 1000 / portTICK_PERIOD_MS);
        if (ret == ESP_OK) {
            break;
        }
    }
    if (wait_time == 0) {
        ESP_LOGE(TAG, "send error");
        len = -1;
    }
    spi_mutex_unlock();

    free(sendbuf);
    return len;
}

int32_t at_spi_get_data_length(void)
{
    ESP_EARLY_LOGE(TAG, "at_spi_get_data_length");
    return notify_len;
}

static void at_spi_slave_task(void* pvParameters)
{
    esp_err_t ret;
    uint32_t recv_len = 0;

    //Configuration for the SPI bus
    spi_bus_config_t buscfg={
        .mosi_io_num=GPIO_MOSI,
        .miso_io_num=GPIO_MISO,
        .sclk_io_num=GPIO_SCLK,
        //.quadwp_io_num = -1,
        //.quadhd_io_num = -1,
        .intr_flags = ESP_INTR_FLAG_IRAM
    };

    //Configuration for the SPI slave interface
    spi_slave_interface_config_t slvcfg={
        .mode=0,
        .spics_io_num=GPIO_CS,
        .queue_size=3,
        .flags=0,
        .post_setup_cb=pull_high_cb,
        .post_trans_cb=pull_low_cb
    };

    //Configuration for the handshake line
    gpio_config_t io_conf={
        .intr_type=GPIO_INTR_DISABLE,
        .mode=GPIO_MODE_OUTPUT,
        //.pin_bit_mask=SPI_SLAVE_HANDSHARK_SEL
        .pin_bit_mask=(1<<GPIO_HANDSHAKE)
    };

    //Configure handshake line as output
    gpio_config(&io_conf);
    //Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
    gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY);
    gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY);
    gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY);

    spi_pxMutex = xSemaphoreCreateMutex();
    if (!spi_pxMutex) {
        ESP_LOGE(TAG, "create mutex error\r\n");
    }
    
    //Initialize SPI slave interface
    ret= at_spi_slave_initialize(HSPI_HOST, &buscfg, &slvcfg, DMA_CHAN);
    assert(ret==ESP_OK);

    //ret = at_spi_write_data((uint8_t *)"ready\r\n", strlen("ready\r\n"));
    ESP_EARLY_LOGE(TAG, "ready %d", ret);
    char str[10] = {0};
    while (1) {
        ret = at_spi_slave_receive(HSPI_HOST, &recv_data, &recv_len, portMAX_DELAY);
        if(ret != ESP_OK) {
            ESP_LOGE(TAG, "Recv error: %d\r\n", ret);
            break;
        }
        memcpy(str,recv_data,4);
        ESP_EARLY_LOGE(TAG, "Recv %d ,len %d,data %s", ret,recv_len,str);
        
        spi_mutex_lock();
        ESP_EARLY_LOGE(TAG, "spi_mutex_lock");
        // notify length to AT core
        esp_at_port_recv_data_notify(recv_len, portMAX_DELAY);
        ESP_EARLY_LOGE(TAG, "notify_len");
        notify_len = recv_len;
    }
    
}

void at_interface_init(void)
{
    esp_at_device_ops_struct esp_at_device_ops = {
        .read_data = at_spi_read_data,
        .write_data = at_spi_write_data,
        .get_data_length = at_spi_get_data_length,
        .wait_write_complete = NULL

    };
    
    esp_at_device_ops_regist(&esp_at_device_ops);
}

void at_custom_init(void)
{
    xTaskCreate(at_spi_slave_task , "at_spi_task" , 4096 , NULL , 10 , NULL);
}

void app_main()
{
    at_interface_init();
    at_custom_init();
}

