Quellcode durchsuchen

sdio_slave: support HAL layer

Michael (XIAO Xufeng) vor 6 Jahren
Ursprung
Commit
0ec08ca21b

+ 4 - 33
components/driver/include/driver/sdio_slave.h

@@ -20,6 +20,7 @@
 #include "esp_err.h"
 #include "sys/queue.h"
 
+#include "hal/sdio_slave_types.h"
 #include "soc/sdio_slave_periph.h"
 
 #ifdef __cplusplus
@@ -30,36 +31,6 @@ extern "C" {
 
 typedef void(*sdio_event_cb_t)(uint8_t event);
 
-/// Mask of interrupts sending to the host.
-typedef enum {
-    SDIO_SLAVE_HOSTINT_SEND_NEW_PACKET =  HOST_SLC0_RX_NEW_PACKET_INT_ENA,  ///< New packet available
-    SDIO_SLAVE_HOSTINT_RECV_OVF        =  HOST_SLC0_TX_OVF_INT_ENA,         ///< Slave receive buffer overflow
-    SDIO_SLAVE_HOSTINT_SEND_UDF        =  HOST_SLC0_RX_UDF_INT_ENA,         ///< Slave sending buffer underflow (this case only happen when the host do not request for packet according to the packet len).
-    SDIO_SLAVE_HOSTINT_BIT7            =  HOST_SLC0_TOHOST_BIT7_INT_ENA,    ///< General purpose interrupt bits that can be used by the user.
-    SDIO_SLAVE_HOSTINT_BIT6            =  HOST_SLC0_TOHOST_BIT6_INT_ENA,
-    SDIO_SLAVE_HOSTINT_BIT5            =  HOST_SLC0_TOHOST_BIT5_INT_ENA,
-    SDIO_SLAVE_HOSTINT_BIT4            =  HOST_SLC0_TOHOST_BIT4_INT_ENA,
-    SDIO_SLAVE_HOSTINT_BIT3            =  HOST_SLC0_TOHOST_BIT3_INT_ENA,
-    SDIO_SLAVE_HOSTINT_BIT2            =  HOST_SLC0_TOHOST_BIT2_INT_ENA,
-    SDIO_SLAVE_HOSTINT_BIT1            =  HOST_SLC0_TOHOST_BIT1_INT_ENA,
-    SDIO_SLAVE_HOSTINT_BIT0            =  HOST_SLC0_TOHOST_BIT0_INT_ENA,
-} sdio_slave_hostint_t;
-
-/// Timing of SDIO slave
-typedef enum {
-    SDIO_SLAVE_TIMING_PSEND_PSAMPLE = 0,/**< Send at posedge, and sample at posedge. Default value for HS mode.
-                                         *   Normally there's no problem using this to work in DS mode.
-                                         */
-    SDIO_SLAVE_TIMING_NSEND_PSAMPLE    ,///< Send at negedge, and sample at posedge. Default value for DS mode and below.
-    SDIO_SLAVE_TIMING_PSEND_NSAMPLE,    ///< Send at posedge, and sample at negedge
-    SDIO_SLAVE_TIMING_NSEND_NSAMPLE,    ///< Send at negedge, and sample at negedge
-} sdio_slave_timing_t;
-
-/// Configuration of SDIO slave mode
-typedef enum {
-    SDIO_SLAVE_SEND_STREAM = 0, ///< Stream mode, all packets to send will be combined as one if possible
-    SDIO_SLAVE_SEND_PACKET = 1, ///< Packet mode, one packets will be sent one after another (only increase packet_len if last packet sent).
-} sdio_slave_sending_mode_t;
 
 /// Configuration of SDIO slave
 typedef struct {
@@ -267,9 +238,9 @@ sdio_slave_hostint_t sdio_slave_get_host_intena(void);
 
 /** Set the interrupt enable for host.
  *
- * @param ena Enable mask for host interrupt.
+ * @param mask Enable mask for host interrupt.
  */
-void sdio_slave_set_host_intena(sdio_slave_hostint_t ena);
+void sdio_slave_set_host_intena(sdio_slave_hostint_t mask);
 
 /** Interrupt the host by general purpose interrupt.
  *
@@ -285,7 +256,7 @@ esp_err_t sdio_slave_send_host_int(uint8_t pos);
  *
  * @param mask Interrupt bits to clear, by bit mask.
  */
-void sdio_slave_clear_host_int(uint8_t mask);
+void sdio_slave_clear_host_int(sdio_slave_hostint_t mask);
 
 /** Wait for general purpose interrupt from host.
  *

Datei-Diff unterdrückt, da er zu groß ist
+ 175 - 640
components/driver/sdio_slave.c


+ 3 - 1
components/soc/CMakeLists.txt

@@ -38,7 +38,9 @@ list(APPEND srcs
     )
 
 if(IDF_TARGET STREQUAL "esp32")
-    list(APPEND srcs "src/hal/mcpwm_hal.c")
+    list(APPEND srcs "src/hal/mcpwm_hal.c"
+                     "src/hal/sdio_slave_hal.c"
+    )
 endif()
 
 if(IDF_TARGET STREQUAL "esp32s2beta")

+ 529 - 0
components/soc/include/hal/sdio_slave_hal.h

@@ -0,0 +1,529 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/*******************************************************************************
+ * NOTICE
+ * The hal is not public api, don't use in application code.
+ * See readme.md in soc/include/hal/readme.md
+ ******************************************************************************/
+
+// The HAL layer for SDIO slave (common part)
+
+// SDIO slave HAL usages:
+
+/*
+Architecture:
+
+The whole SDIO slave peripheral consists of three parts: the registers (including the interrupt
+control and shared registers), a send FIFO, and a receive FIFO. The document
+``esp_slave_protocol.rst`` describes the functionality of the peripheral in detail. An SDIO host
+will only ever access one of the three parts at any one time, thus the hardware functionality of
+the SDIO slave peripheral are completely independent. Likewise, this HAL is organized in such a
+fashion as to correspond to the three independent parts.
+
+The shared registers are quite simple: the slave can directly access them from the internal data
+bus, while the host can access them by CMD52/53 with the correct address. As for the interrupts:
+when an SDIO host interrupts the SDIO slave peripheral (by writing a command), the corresponding
+bit in the interrupt register will be set; when the SDIO slave peripheral needs to interrupt the
+host, it write some register to cause the host interrupt bit being set, and the slave hardware
+will output the interrupt signal on the DAT1 line.
+
+For the FIFOs, the peripheral provides counters as registers so that the host can always know whether the slave
+is ready to send/receive data. The HAL resets the counters during initialization, and the host should somehow
+inform the slave to reset the counters again if it should reboot (or lose the counter value for some reasons).
+Then the host can read/write the FIFOs by CMD53 commands according to the counters.
+
+In order to avoid copying data to/from the FIFOs or memory buffers each time, the HAL layer
+contains a descriptor queue (implemented as linked-list) that allows descriptors of memory
+buffers to be queued for transmission/reception. Once a buffer is queued, the HAL takes ownership
+of the buffer until some "finish" functions successfully return, indicating the
+transmission/reception of that buffer is complete. The ISR is invoked multiple times to iterate
+through the queued descriptors, and also to signal to the upper layer if a buffer has been
+freed.
+
+The HAL is used as below:
+
+- Receiving part:
+
+    1.  Call `sdio_slave_hal_recv_start` to start the receiving DMA.
+
+        If there are already buffers loaded, the receiving will start from those buffers first.
+
+    2.  Call `sdio_slave_hal_recv_init_desc` with a `sdio_slave_hal_recv_desc_t` and the buffer address to
+        associate the descriptor with the buffer.
+
+        The HAL initialize this descriptors with the determined length and maybe some extra data.
+
+    3.  Call `sdio_slave_hal_load_buf` with the initialized descriptor of the buffer to load a
+        receiving buffer to the HAL.
+
+        When the DMA is started, the descriptors is loaded onto the DMA linked-list, and the
+        counter of receiving buffers is increased so that the host will know this by the
+        receiving interrupt. The hardware will automatically go through the linked list and write
+        data into the buffers loaded on the list.
+
+    4.  (Optional, mandatory only when interrupt enabled) Call `sdio_slave_hal_recv_done` to check
+        and clear the receiving interrupt bits.
+
+    5.  Call `sdio_slave_hal_recv_has_next_item` to check whether there are finished buffers.
+
+    6.  Call `sdio_slave_hal_recv_unload_desc` for the same times as
+        `sdio_slave_hal_recv_has_next_item` successfully returns.
+
+    7.  (Optional) Call `sdio_slave_hal_recv_reset_counter` to reset the counter to current loaded
+        but not used buffers if you want to reset the counter only. This is available only when
+        the DMA is stopped.
+
+    8.  (Optional) Call `sdio_slave_hal_recv_flush_one_buffer` (recursively) if you want to
+        discard data of one (or more) buffers and load them again. This is available only when
+        the DMA is stopped.
+
+    9.  (Optional when deinitialization) Call `sdio_slave_hal_recv_unload_desc` recursively to get
+        all the buffers loaded to the HAL, no matter they are used or not. Don't do this when the
+        DMA is not stopped.
+
+- Sending part:
+
+    The sending driver is slightly different, since we are not using the re-start feature.
+    (TODO: re-write this part if the stitch mode is released)
+
+    1.  Call `sdio_slave_hal_send_start` to start the sending DMA.
+
+        If there is already any data queued, it will ne ready to be sent to host now.
+
+    2.  Call `sdio_slave_hal_send_queue` to queue the data to send.
+
+        If the interrupt is enabled, the ISR will be invoked.
+
+    3.  (Required if interrupt enabled) Call `` to clear the interrupt bits used by the SW
+        invoking logic.
+
+    4.  Call `sdio_slave_hal_send_new_packet_if_exist` to check and send new packet (if there is
+        data queued).
+
+    5.  Call `sdio_slave_hal_send_eof_happened` to check whether the previous packet is done.
+
+        It will also clear the interrupt status bit for this event.
+
+    6.  Call `sdio_slave_hal_send_get_next_finished_arg` recursively to get the arguments for the
+        finished buffers.
+
+    7.  (Optional when deinitialization) Call `sdio_slave_hal_send_flush_next_buffer` recursively
+        to get all buffers queued, regardless sent or not. Don't do this when the DMA is not stopped.
+
+    8.  (Optional) Call `sdio_slave_hal_send_reset_counter` to reset the counter to current loaded
+        but not sent buffers if you want to reset the counter only. Don't do this when the DMA is not
+        stopped.
+
+    Note a counter should be used when performing step 2 and 6, to make sure that the queue size
+    is enough.
+
+- Host part:
+
+    1.  Call `sdio_slave_hal_hostint_set_ena` and `sdio_slave_hal_hostint_get_ena` to
+        enable/disable the interrupt sent to master. Note that the host can also modify the same
+        registers at the same time. Try to avoid using them outside the initialization process.
+
+    2.  Call `sdio_slave_hal_hostint_send` and `sdio_slave_hal_hostint_clear` to trigger general
+        purpose interrupts or cancel all kinds of interrupts send to the host. These interrupts are
+        set/cleared in a concurrent-safe way, so the slave can call these functions safely.
+
+    3.  Call `sdio_slave_hal_slvint_fetch_clear` to fetch the general purpose interrupts sent by
+        the host to the slave. These interrupts will also be cleared after the calls.
+
+    4.  Call `sdio_slave_hal_host_get_reg` and `sdio_slave_hal_host_set_reg` to read/write the
+        general purpose shared between the host and slave. Note that these registers are also not
+        concurrent-safe. Try not to write to the same register from two directions at the same time.
+*/
+
+#pragma once
+#include <esp_err.h>
+#include "soc/lldesc.h"
+#include "hal/sdio_slave_types.h"
+#include "hal/sdio_slave_ll.h"
+
+/// Space used for each sending descriptor. Should initialize the sendbuf accoring to this size.
+#define SDIO_SLAVE_SEND_DESC_SIZE   sizeof(sdio_slave_hal_send_desc_t)
+
+
+/// Status of the sending part
+typedef enum {
+    STATE_IDLE = 1,
+    STATE_WAIT_FOR_START = 2,
+    STATE_SENDING = 3,
+    STATE_GETTING_RESULT = 4,
+    STATE_GETTING_UNSENT_DESC = 5,
+} send_state_t;
+
+typedef struct {
+    uint8_t* data;      ///< Address of the buffer
+    size_t  size;       ///< Size of the buffer, but can only queue (size/SDIO_SLAVE_SEND_DESC_SIZE)-1 descriptors
+    uint8_t* write_ptr;
+    uint8_t* read_ptr;
+    uint8_t* free_ptr;
+} sdio_ringbuf_t;
+
+// Append two extra words to be used by the HAL.
+// Should Initialize the member `data` of `send_desc_queue` of the HAL context
+// with size of this desc * N.
+
+/// DMA descriptor with extra fields
+typedef struct sdio_slave_hal_send_desc_s {
+    lldesc_t dma_desc;    ///< Used by Hardware, has pointer linking to next desc
+    uint32_t pkt_len;     ///< Accumulated length till this descriptor
+    void*   arg;          ///< Holding arguments indicating this buffer */
+} sdio_slave_hal_send_desc_t;
+
+/// Descriptor used by the receiving part, call `sdio_slave_hal_recv_init_desc`
+/// to initialize it before use.
+typedef lldesc_t sdio_slave_hal_recv_desc_t;
+#define sdio_slave_hal_recv_desc_s lldesc_s
+typedef STAILQ_HEAD(recv_stailq_head_s, sdio_slave_hal_recv_desc_s) sdio_slave_hal_recv_stailq_t;
+
+
+/** HAL context structure. Call `sdio_slave_hal_init` to initialize it and
+ * configure required members before actually use the HAL.
+ */
+typedef struct {
+    /// Hardware registers for this SDIO slave peripheral, configured by
+    /// `sdio_slave_hal_init`
+    struct {
+        slc_dev_t*      slc;
+        host_dev_t*     host;
+        hinf_dev_t*     hinf;
+    };
+    sdio_slave_sending_mode_t sending_mode; /**< Sending mode, should be manually configured before using the HAL.
+                                             * see `sdio_slave_sending_mode_t`.
+                                             */
+    sdio_slave_timing_t timing;             /**< Timing mode (launch edge and latch edge settings). Should be manually
+                                             * configured before using the HAL. `SDIO_SLAVE_TIMING_PSEND_PSAMPLE` is
+                                             * recommended by default.
+                                             */
+    int                 send_queue_size;    /**< Max buffers that can be queued before sending. Should be manually
+                                             * configured before using the HAL.
+                                             */
+    size_t              recv_buffer_size;   /**< The size of each buffer. The host and slave should share a
+                                             * pre-negotiated value. Should be manually configured before using
+                                             * the HAL.
+                                             */
+    sdio_ringbuf_t      send_desc_queue;            /**< The ring buffer used to hold queued descriptors. Should be manually
+                                             * initialized before using the HAL.
+                                             */
+    //Internal status, no need to touch.
+    send_state_t        send_state;         // Current state of sending part.
+    uint32_t            tail_pkt_len;       // The accumulated send length of the tail packet.
+    sdio_slave_hal_send_desc_t*         in_flight_head; // The head of linked list in-flight.
+    sdio_slave_hal_send_desc_t*         in_flight_end;  // The end of linked list in-flight.
+    sdio_slave_hal_send_desc_t*         in_flight_next; // The header of linked list to be sent next time.
+    sdio_slave_hal_send_desc_t*         returned_desc;  // The last returned descriptor
+
+    sdio_slave_hal_recv_stailq_t            recv_link_list; // Linked list of buffers ready to hold data and the buffers already hold data.
+    volatile sdio_slave_hal_recv_desc_t*    recv_cur_ret;   // Next desc to return, NULL if all loaded descriptors are returned.
+} sdio_slave_context_t ;
+
+/**
+ * Initialize the HAL, should provide buffers to the context and configure the
+ * members before this funciton is called.
+ *
+ * @param hal Context of the HAL layer.
+ */
+void sdio_slave_hal_init(sdio_slave_context_t *hal);
+
+/**
+ * Initialize the SDIO slave peripheral hardware.
+ *
+ * @param hal Context of the HAL layer.
+ */
+void sdio_slave_hal_hw_init(sdio_slave_context_t *hal);
+
+/**
+ * Set the IO ready for host to read.
+ *
+ * @param hal Context of the HAL layer.
+ * @param ready true to tell the host the slave is ready, otherwise false.
+ */
+void sdio_slave_hal_set_ioready(sdio_slave_context_t *hal, bool ready);
+
+/*---------------------------------------------------------------------------
+ *                  Send
+ *--------------------------------------------------------------------------*/
+
+/**
+ * The hardware sending DMA starts. If there is existing data, send them.
+ *
+ * @param hal Context of the HAL layer.
+ */
+esp_err_t sdio_slave_hal_send_start(sdio_slave_context_t *hal);
+
+/**
+ * Stops hardware sending DMA.
+ *
+ * @note The data in the queue, as well as the counter are not touched.
+ * @param hal Context of the HAL layer.
+ */
+void sdio_slave_hal_send_stop(sdio_slave_context_t *hal);
+
+/**
+ * Put some data into the sending queue.
+ *
+ * @note The caller should keeps the buffer, until the `arg` is returned by
+ *       `sdio_slave_hal_send_get_next_finished_arg`.
+ * @note The caller should count to ensure there is enough space in the queue.
+ *       The initial queue size is sizeof(sendbuf.data)/sizeof(sdio_slave_hal_send_desc_t)-1,
+ *       Will decrease by one when this function successfully returns.
+ *       Released only by `sdio_slave_hal_send_get_next_finished_arg` or
+ *       `sdio_slave_hal_send_flush_next_buffer`.
+ *
+ * @note The HAL is not thread-safe. The caller should use a spinlock to ensure
+ *       the `sdio_slave_hal_send_queue` and ... are not called at the same time.
+ *
+ * @param hal Context of the HAL layer.
+ * @param addr Address of data in the memory to send.
+ * @param len Length of data to send.
+ * @param arg Argument indicating this sending.
+ * @return Always ESP_OK.
+ */
+esp_err_t sdio_slave_hal_send_queue(sdio_slave_context_t *hal, uint8_t *addr, size_t len, void *arg);
+
+/**
+ * The ISR should call this, to handle the SW invoking event.
+ * @param hal Context of the HAL layer.
+ */
+void sdio_slave_hal_send_handle_isr_invoke(sdio_slave_context_t *hal);
+
+/**
+ * Check whether there is no in-flight transactions, and send new packet if there
+ * is new packets queued.
+ *
+ * @param hal Context of the HAL layer.
+ * @return
+ *  - ESP_OK: The DMA starts to send a new packet.
+ *  - ESP_ERR_NOT_FOUND: No packet waiting to be sent.
+ *  - ESP_ERR_INVALID_STATE: There is packet in-flight.
+ */
+esp_err_t sdio_slave_hal_send_new_packet_if_exist(sdio_slave_context_t *hal);
+
+/**
+ * Check whether the sending EOF has happened and clear the interrupt.
+ *
+ * Call `sdio_slave_hal_send_get_next_finished_arg` recursively to retrieve arguments of finished
+ * buffers.
+ *
+ * @param hal Context of the HAL layer.
+ * @return true if happened, otherwise false.
+ */
+bool sdio_slave_hal_send_eof_happened(sdio_slave_context_t *hal);
+
+/**
+ * Get the arguments of finished packets. Call recursively until all finished
+ * arguments are all retrieved.
+ *
+ * @param hal Context of the HAL layer.
+ * @param out_arg Output argument of the finished buffer.
+ * @param out_returned_cnt Released queue size to be queued again.
+ * @return
+ *  - ESP_OK: if one argument retrieved.
+ *  - ESP_ERR_NOT_FOUND: All the arguments of the finished buffers are retrieved.
+ */
+esp_err_t sdio_slave_hal_send_get_next_finished_arg(sdio_slave_context_t *hal, void **out_arg, uint32_t* out_returned_cnt);
+
+/**
+ * Flush one buffer in the queue, no matter sent, canceled or not sent yet.
+ *
+ * Call recursively to clear the whole queue before deinitialization.
+ *
+ * @note Only call when the DMA is stopped!
+ * @param hal Context of the HAL layer.
+ * @param out_arg Argument indiciating the buffer to send
+ * @param out_return_cnt Space in the queue released after this descriptor is flushed.
+ * @return
+ *  - ESP_ERR_INVALID_STATE: This function call be called only when the DMA is stopped.
+ *  - ESP_ERR_NOT_FOUND: if no buffer in the queue
+ *  - ESP_OK: if a buffer is successfully flushed and returned.
+ */
+esp_err_t sdio_slave_hal_send_flush_next_buffer(sdio_slave_context_t *hal, void **out_arg, uint32_t *out_return_cnt);
+
+/**
+ * Walk through all the unsent buffers and reset the counter to the accumulated length of them. The data will be kept.
+ *
+ * @note Only call when the DMA is stopped!
+ * @param hal Context of the HAL layer.
+ * @return
+ *  - ESP_ERR_INVALID_STATE: this function call be called only when the DMA is stopped
+ *  - ESP_OK: if success
+ */
+esp_err_t sdio_slave_hal_send_reset_counter(sdio_slave_context_t *hal);
+
+
+/*---------------------------------------------------------------------------
+ *                  Receive
+ *--------------------------------------------------------------------------*/
+/**
+ * Start the receiving DMA.
+ *
+ * @note If there are already some buffers loaded, will receive from them first.
+ * @param hal Context of the HAL layer.
+ */
+void sdio_slave_hal_recv_start(sdio_slave_context_t *hal);
+
+/**
+ * Stop the receiving DMA.
+ *
+ * @note Data and the counter will not be touched. You can still call
+ *       `sdio_slave_hal_recv_has_next_item` to get the received buffer.
+ *       And unused buffers loaded to the HAL will still be in the `loaded`
+ *       state in the HAL, until returned by `sdio_slave_hal_recv_unload_desc`.
+ * @param hal Context of the HAL layer.
+ */
+void sdio_slave_hal_recv_stop(sdio_slave_context_t* hal);
+
+/**
+ * Associate the buffer to the descriptor given. The descriptor may also be initialized with some
+ * other data.
+ *
+ * @param hal Context of the HAL layer.
+ * @param desc Descriptor to associate with the buffer
+ * @param start Start address of the buffer
+ */
+void sdio_slave_hal_recv_init_desc(sdio_slave_context_t *hal, sdio_slave_hal_recv_desc_t *desc, uint8_t *start);
+
+/**
+ * Load the buffer to the HAL to be used to receive data.
+ *
+ * @note Loaded buffers will be returned to the upper layer only when:
+ *       1. Returned by `sdio_slave_hal_recv_has_next_item` when receiving to that buffer successfully
+ *       done.
+ *       2. Returned by `sdio_slave_hal_recv_unload_desc` unconditionally.
+ * @param hal Context of the HAL layer.
+ * @param desc Descriptor to load to the HAL to receive.
+ */
+void sdio_slave_hal_load_buf(sdio_slave_context_t *hal, sdio_slave_hal_recv_desc_t *desc);
+
+/**
+ * Check and clear the interrupt indicating a buffer has finished receiving.
+ *
+ * @param hal Context of the HAL layer.
+ * @return true if interrupt triggered, otherwise false.
+ */
+bool sdio_slave_hal_recv_done(sdio_slave_context_t* hal);
+
+/**
+ * Call this function recursively to check whether there is any buffer that has
+ * finished receiving.
+ *
+ * Will walk through the linked list to find a newer finished buffer. For each successful return,
+ * it means there is one finished buffer. You can one by `sdio_slave_hal_recv_unload_desc`. You can
+ * also call `sdio_slave_hal_recv_has_next_item` several times continuously before you call the
+ * `sdio_slave_hal_recv_unload_desc` for the same times.
+ *
+ * @param hal Context of the HAL layer.
+ * @return true if there is
+ */
+bool sdio_slave_hal_recv_has_next_item(sdio_slave_context_t* hal);
+
+/**
+ * Unconditionally remove and return the first descriptor loaded to the HAL.
+ *
+ * Unless during de-initialization, `sdio_slave_hal_recv_has_next_item` should have succeed for the
+ * same times as this function is called, to ensure the returned descriptor has finished its
+ * receiving job.
+ *
+ * @param hal Context of the HAL layer.
+ * @return The removed descriptor, NULL means the linked-list is empty.
+ */
+sdio_slave_hal_recv_desc_t *sdio_slave_hal_recv_unload_desc(sdio_slave_context_t *hal);
+
+/**
+ * Walk through all the unused buffers and reset the counter to the number of
+ * them.
+ *
+ * @note Only call when the DMA is stopped!
+ * @param hal Context of the HAL layer.
+ */
+void sdio_slave_hal_recv_reset_counter(sdio_slave_context_t *hal);
+
+/**
+ * Walk through all the used buffers, clear the finished flag and appended them
+ * back to the end of the unused list, waiting to receive then.
+ *
+ * @note You will lose all the received data in the buffer.
+ * @note Only call when the DMA is stopped!
+ * @param hal Context of the HAL layer.
+ */
+void sdio_slave_hal_recv_flush_one_buffer(sdio_slave_context_t *hal);
+
+
+/*---------------------------------------------------------------------------
+ *                  Host
+ *--------------------------------------------------------------------------*/
+
+/**
+ * Enable some of the interrupts for the host.
+ *
+ * @note May have concurrency issue wit the host or other tasks, suggest only use it during
+ *       initialization.
+ * @param hal Context of the HAL layer.
+ * @param mask Bitwise mask for the interrupts to enable.
+ */
+void sdio_slave_hal_hostint_set_ena(sdio_slave_context_t *hal, const sdio_slave_hostint_t *mask);
+
+/**
+ * Get the enabled interrupts.
+ *
+ * @param hal Context of the HAL layer.
+ * @param out_int_mask Output of the enabled interrupts
+ */
+void sdio_slave_hal_hostint_get_ena(sdio_slave_context_t *hal, sdio_slave_hostint_t *out_int_mask);
+
+/**
+ * Send general purpose interrupt (slave send to host).
+ * @param hal Context of the HAL layer.
+ * @param mask Interrupts to send, only `SDIO_SLAVE_HOSTINT_BIT*` are allowed.
+ */
+void sdio_slave_hal_hostint_send(sdio_slave_context_t *hal, const sdio_slave_hostint_t *mask);
+
+/**
+ * Cleared the specified interrupts for the host.
+ *
+ * @param hal Context of the HAL layer.
+ * @param mask Interrupts to clear.
+ */
+void sdio_slave_hal_hostint_clear(sdio_slave_context_t *hal, const sdio_slave_hostint_t *mask);
+
+
+/**
+ * Fetch the interrupt (host send to slave) status bits and clear all of them.
+ * @param hal Context of the HAL layer.
+ * @param out_int_mask Output interrupt status
+ */
+void sdio_slave_hal_slvint_fetch_clear(sdio_slave_context_t *hal, sdio_slave_ll_slvint_t *out_int_mask);
+
+/**
+ * Get the value of a shared general purpose register.
+ *
+ * @param hal Context of the HAL layer.
+ * @param pos Position of the register, 4 bytes share a word. 0-63 except 24-27.
+ * @return The register value.
+ */
+uint8_t sdio_slave_hal_host_get_reg(sdio_slave_context_t *hal, int pos);
+
+/**
+ * Set the value of shared general purpose register.
+ *
+ * @param hal Context of the HAL layer.
+ * @param pos Position of the register, 4 bytes share a word. 0-63 except 24-27.
+ * @param reg Value to set.
+ */
+void sdio_slave_hal_host_set_reg(sdio_slave_context_t *hal, int pos, uint8_t reg);
+

+ 482 - 0
components/soc/include/hal/sdio_slave_ll.h

@@ -0,0 +1,482 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/*******************************************************************************
+ * NOTICE
+ * The hal is not public api, don't use in application code.
+ * See readme.md in soc/include/hal/readme.md
+ ******************************************************************************/
+
+// The LL layer for ESP32 SDIO slave register operations
+// It's strange but `tx_*` regs for host->slave transfers while `rx_*` regs for slave->host transfers
+// To reduce ambiguity, we call (host->slave, tx) transfers receiving and (slave->host, rx) transfers receiving
+
+#pragma once
+
+#include "hal/sdio_slave_hal.h"
+#include "soc/slc_struct.h"
+#include "soc/slc_reg.h"
+#include "soc/host_struct.h"
+#include "soc/host_reg.h"
+#include "soc/hinf_struct.h"
+#include "soc/lldesc.h"
+
+/// Get address of the only SLC registers for ESP32
+#define sdio_slave_ll_get_slc(ID)   (&SLC)
+/// Get address of the only HOST registers for ESP32
+#define sdio_slave_ll_get_host(ID)  (&HOST)
+/// Get address of the only HINF registers for ESP32
+#define sdio_slave_ll_get_hinf(ID)  (&HINF)
+
+
+/// Mask of general purpose interrupts sending from the host.
+typedef enum {
+    SDIO_SLAVE_LL_SLVINT_0 = BIT(0),    ///< General purpose interrupt bit 0.
+    SDIO_SLAVE_LL_SLVINT_1 = BIT(1),
+    SDIO_SLAVE_LL_SLVINT_2 = BIT(2),
+    SDIO_SLAVE_LL_SLVINT_3 = BIT(3),
+    SDIO_SLAVE_LL_SLVINT_4 = BIT(4),
+    SDIO_SLAVE_LL_SLVINT_5 = BIT(5),
+    SDIO_SLAVE_LL_SLVINT_6 = BIT(6),
+    SDIO_SLAVE_LL_SLVINT_7 = BIT(7),
+} sdio_slave_ll_slvint_t;
+
+/**
+ * Initialize the hardware.
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_init(slc_dev_t *slc)
+{
+    slc->slc0_int_ena.val = 0;
+
+    slc->conf0.slc0_rx_auto_wrback = 1;
+    slc->conf0.slc0_token_auto_clr = 0;
+    slc->conf0.slc0_rx_loop_test = 0;
+    slc->conf0.slc0_tx_loop_test = 0;
+
+    slc->conf1.slc0_rx_stitch_en = 0;
+    slc->conf1.slc0_tx_stitch_en = 0;
+    slc->conf1.slc0_len_auto_clr = 0;
+
+    slc->rx_dscr_conf.slc0_token_no_replace = 1;
+}
+
+/**
+ * Set the timing for the communication
+ *
+ * @param host Address of the host registers
+ * @param timing Timing configuration to set
+ */
+static inline void sdio_slave_ll_set_timing(host_dev_t *host, sdio_slave_timing_t timing)
+{
+    switch(timing) {
+        case SDIO_SLAVE_TIMING_PSEND_PSAMPLE:
+            host->conf.frc_sdio20 = 0x1f;
+            host->conf.frc_sdio11 = 0;
+            host->conf.frc_pos_samp = 0x1f;
+            host->conf.frc_neg_samp = 0;
+            break;
+        case SDIO_SLAVE_TIMING_PSEND_NSAMPLE:
+            host->conf.frc_sdio20 = 0x1f;
+            host->conf.frc_sdio11 = 0;
+            host->conf.frc_pos_samp = 0;
+            host->conf.frc_neg_samp = 0x1f;
+            break;
+        case SDIO_SLAVE_TIMING_NSEND_PSAMPLE:
+            host->conf.frc_sdio20 = 0;
+            host->conf.frc_sdio11 = 0x1f;
+            host->conf.frc_pos_samp = 0x1f;
+            host->conf.frc_neg_samp = 0;
+            break;
+        case SDIO_SLAVE_TIMING_NSEND_NSAMPLE:
+            host->conf.frc_sdio20 = 0;
+            host->conf.frc_sdio11 = 0x1f;
+            host->conf.frc_pos_samp = 0;
+            host->conf.frc_neg_samp = 0x1f;
+            break;
+    }
+}
+
+/**
+ * Set the HS supported bit to be read by the host.
+ *
+ * @param hinf Address of the hinf registers
+ * @param hs true if supported, otherwise false.
+ */
+static inline void sdio_slave_ll_enable_hs(hinf_dev_t *hinf, bool hs)
+{
+    if (hs) {
+        hinf->cfg_data1.sdio_ver = 0x232;
+        hinf->cfg_data1.highspeed_enable = 1;
+    }
+}
+
+/**
+ * Set the IO Ready bit to be read by the host.
+ *
+ * @param hinf Address of the hinf registers
+ * @param ready true if ready, otherwise false.
+ */
+static inline void sdio_slave_ll_set_ioready(hinf_dev_t *hinf, bool ready)
+{
+    hinf->cfg_data1.sdio_ioready1 = (ready ? 1 : 0);   //set IO ready to 1 to stop host from using
+}
+
+/*---------------------------------------------------------------------------
+ *                  Send
+ *--------------------------------------------------------------------------*/
+/**
+ * Reset the sending DMA.
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_send_reset(slc_dev_t *slc)
+{
+    //reset to flush previous packets
+    slc->conf0.slc0_rx_rst = 1;
+    slc->conf0.slc0_rx_rst = 0;
+}
+
+/**
+ * Start the sending DMA with the given descriptor.
+ *
+ * @param slc Address of the SLC registers
+ * @param desc Descriptor to send
+ */
+static inline void sdio_slave_ll_send_start(slc_dev_t *slc, const lldesc_t *desc)
+{
+    slc->slc0_rx_link.addr = (uint32_t)desc;
+    slc->slc0_rx_link.start = 1;
+}
+
+/**
+ * Write the PKT_LEN register to be written by the host to a certain value.
+ *
+ * @param slc Address of the SLC registers
+ * @param len Length to write
+ */
+static inline void sdio_slave_ll_send_write_len(slc_dev_t *slc, uint32_t len)
+{
+    slc->slc0_len_conf.val = FIELD_TO_VALUE2(SLC_SLC0_LEN_WDATA, len) | FIELD_TO_VALUE2(SLC_SLC0_LEN_WR, 1);
+}
+
+/**
+ * Read the value of PKT_LEN register. The register may keep the same until read
+ * by the host.
+ *
+ * @param host Address of the host registers
+ * @return The value of PKT_LEN register.
+ */
+static inline uint32_t sdio_slave_ll_send_read_len(host_dev_t *host)
+{
+    return host->pkt_len.reg_slc0_len;
+}
+
+/**
+ * Enable the rx_done interrupt. (sending)
+ *
+ * @param slc Address of the SLC registers
+ * @param ena true if enable, otherwise false.
+ */
+static inline void sdio_slave_ll_send_part_done_intr_ena(slc_dev_t *slc, bool ena)
+{
+    slc->slc0_int_ena.rx_done = (ena ? 1 : 0);
+}
+
+/**
+ * Clear the rx_done interrupt. (sending)
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_send_part_done_clear(slc_dev_t *slc)
+{
+    slc->slc0_int_clr.rx_done = 1;
+}
+
+/**
+ * Check whether the hardware is ready for the SW to use rx_done to invoke
+ * the ISR.
+ *
+ * @param slc Address of the SLC registers
+ * @return true if ready, otherwise false.
+ */
+static inline bool sdio_slave_ll_send_invoker_ready(slc_dev_t *slc)
+{
+    return slc->slc0_int_raw.rx_done;
+}
+
+/**
+ * Stop the sending DMA.
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_send_stop(slc_dev_t *slc)
+{
+    slc->slc0_rx_link.stop = 1;
+}
+
+/**
+ * Enable the sending interrupt (rx_eof).
+ *
+ * @param slc Address of the SLC registers
+ * @param ena true to enable, false to disable
+ */
+static inline void sdio_slave_ll_send_intr_ena(slc_dev_t *slc, bool ena)
+{
+    slc->slc0_int_ena.rx_eof = (ena? 1: 0);
+}
+
+/**
+ * Clear the sending interrupt (rx_eof).
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_send_intr_clr(slc_dev_t *slc)
+{
+    slc->slc0_int_clr.rx_eof = 1;
+}
+
+/**
+ * Check whether the sending is done.
+ *
+ * @param slc Address of the SLC registers
+ * @return true if done, otherwise false
+ */
+static inline bool sdio_slave_ll_send_done(slc_dev_t *slc)
+{
+    return slc->slc0_int_st.rx_eof != 0;
+}
+
+/**
+ * Clear the host interrupt indicating the slave having packet to be read.
+ *
+ * @param host Address of the host registers
+ */
+static inline void sdio_slave_ll_send_hostint_clr(host_dev_t *host)
+{
+    host->slc0_int_clr.rx_new_packet = 1;
+}
+
+/*---------------------------------------------------------------------------
+ *                  Receive
+ *--------------------------------------------------------------------------*/
+/**
+ * Enable the receiving interrupt.
+ *
+ * @param slc Address of the SLC registers
+ * @param ena
+ */
+static inline void sdio_slave_ll_recv_intr_ena(slc_dev_t *slc, bool ena)
+{
+    slc->slc0_int_ena.tx_done = (ena ? 1 : 0);
+}
+
+/**
+ * Start receiving DMA with the given descriptor.
+ *
+ * @param slc Address of the SLC registers
+ * @param desc Descriptor of the receiving buffer.
+ */
+static inline void sdio_slave_ll_recv_start(slc_dev_t *slc, lldesc_t *desc)
+{
+    slc->slc0_tx_link.addr = (uint32_t)desc;
+    slc->slc0_tx_link.start = 1;
+}
+
+/**
+ * Increase the receiving buffer counter by 1.
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_recv_size_inc(slc_dev_t *slc)
+{
+    // fields wdata and inc_more should be written by the same instruction.
+    slc->slc0_token1.val = FIELD_TO_VALUE2(SLC_SLC0_TOKEN1_WDATA, 1) | FIELD_TO_VALUE2(SLC_SLC0_TOKEN1_INC_MORE, 1);
+}
+
+/**
+ * Reset the receiving buffer.
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_recv_size_reset(slc_dev_t *slc)
+{
+    slc->slc0_token1.val = FIELD_TO_VALUE2(SLC_SLC0_TOKEN1_WDATA, 0) | FIELD_TO_VALUE2(SLC_SLC0_TOKEN1_WR, 1);
+}
+
+/**
+ * Check whether there is a receiving finished event.
+ *
+ * @param slc Address of the SLC registers
+ * @return
+ */
+static inline bool sdio_slave_ll_recv_done(slc_dev_t *slc)
+{
+    return slc->slc0_int_raw.tx_done != 0;
+}
+
+/**
+ * Clear the receiving finished interrupt.
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_recv_done_clear(slc_dev_t *slc)
+{
+    slc->slc0_int_clr.tx_done = 1;
+}
+
+/**
+ * Restart the DMA. Call after you modified the next pointer of the tail descriptor to the appended
+ * descriptor.
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_recv_restart(slc_dev_t *slc)
+{
+    slc->slc0_tx_link.restart = 1;
+}
+
+/**
+ * Reset the receiving DMA.
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_recv_reset(slc_dev_t *slc)
+{
+    slc->conf0.slc0_tx_rst = 1;
+    slc->conf0.slc0_tx_rst = 0;
+}
+
+/**
+ * Stop the receiving DMA.
+ *
+ * @param slc Address of the SLC registers
+ */
+static inline void sdio_slave_ll_recv_stop(slc_dev_t *slc)
+{
+    slc->slc0_tx_link.stop = 1;
+}
+
+/*---------------------------------------------------------------------------
+ *                  Host
+ *--------------------------------------------------------------------------*/
+/**
+ * Get the address of the shared general purpose register. Internal.
+ *
+ * @param host Address of the host registers
+ * @param pos Position of the register, 0-63 except 24-27.
+ * @return address of the register.
+ */
+static inline intptr_t sdio_slave_ll_host_get_w_reg(host_dev_t* host, int pos)
+{
+    return (intptr_t )&(host->conf_w0) + pos + (pos>23?4:0) + (pos>31?12:0);
+}
+
+/**
+ * Get the value of the shared general purpose register.
+ *
+ * @param host Address of the host registers
+ * @param pos Position of the register, 0-63, except 24-27.
+ * @return value of the register.
+ */
+static inline uint8_t sdio_slave_ll_host_get_reg(host_dev_t *host, int pos)
+{
+    return *(uint8_t*)sdio_slave_ll_host_get_w_reg(host, pos);
+}
+
+/**
+ * Set the value of the shared general purpose register.
+ *
+ * @param host Address of the host registers
+ * @param pos Position of the register, 0-63, except 24-27.
+ * @param reg Value to set.
+ */
+static inline void sdio_slave_ll_host_set_reg(host_dev_t* host, int pos, uint8_t reg)
+{
+    uint32_t* addr = (uint32_t*)(sdio_slave_ll_host_get_w_reg(host, pos) & (~3));
+    uint32_t shift = (pos % 4) * 8;
+    *addr &= ~(0xff << shift);
+    *addr |= ((uint32_t)reg << shift);
+}
+
+/**
+ * Get the interrupt enable bits for the host.
+ *
+ * @param host Address of the host registers
+ * @return Enabled interrupts
+ */
+static inline sdio_slave_hostint_t sdio_slave_ll_host_get_intena(host_dev_t* host)
+{
+    return host->slc0_func1_int_ena.val;
+}
+
+/**
+ * Set the interrupt enable bits for the host.
+ *
+ * @param host Address of the host registers
+ * @param mask Mask of interrupts to enable
+ */
+static inline void sdio_slave_ll_host_set_intena(host_dev_t *host, const sdio_slave_hostint_t *mask)
+{
+    host->slc0_func1_int_ena.val = (*mask);
+}
+
+/**
+ * Clear the interrupt bits for the host.
+ * @param host Address of the host registers
+ * @param mask Mask of interrupts to clear.
+ */
+static inline void sdio_slave_ll_host_intr_clear(host_dev_t* host, const sdio_slave_hostint_t *mask)
+{
+    host->slc0_int_clr.val = (*mask);
+}
+
+/**
+ * Send general purpose interrupts to the host.
+ * @param slc Address of the SLC registers
+ * @param mask Mask of interrupts to seend to host
+ */
+static inline void sdio_slave_ll_host_send_int(slc_dev_t *slc, const sdio_slave_hostint_t *mask)
+{
+    //use registers in SLC to trigger, rather than write HOST registers directly
+    //other interrupts than tohost interrupts are not supported yet
+    slc->intvec_tohost.slc0_intvec = (*mask);
+}
+
+/**
+ * Enable some of the slave interrups (send from host)
+ *
+ * @param slc Address of the SLC registers
+ * @param mask Mask of interrupts to enable, all those set to 0 will be disabled.
+ */
+static inline void sdio_slave_ll_slvint_set_ena(slc_dev_t *slc, const sdio_slave_ll_slvint_t *mask)
+{
+    //other interrupts are not enabled
+    slc->slc0_int_ena.val = (slc->slc0_int_ena.val & (~0xff)) | ((*mask) & 0xff);
+}
+
+/**
+ * Fetch the slave interrupts (send from host) and clear them.
+ *
+ * @param slc Address of the SLC registers
+ * @param out_slv_int Output of the slave interrupts fetched and cleared.
+ */
+static inline void sdio_slave_ll_slvint_fetch_clear(slc_dev_t *slc, sdio_slave_ll_slvint_t *out_slv_int)
+{
+    sdio_slave_ll_slvint_t slv_int = slc->slc0_int_st.val & 0xff;
+    *out_slv_int = slv_int;
+    slc->slc0_int_clr.val = slv_int;
+}
+

+ 47 - 0
components/soc/include/hal/sdio_slave_types.h

@@ -0,0 +1,47 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "soc/soc.h"
+
+/// Mask of interrupts sending to the host.
+typedef enum {
+    SDIO_SLAVE_HOSTINT_BIT0 = BIT(0),   ///< General purpose interrupt bit 0.
+    SDIO_SLAVE_HOSTINT_BIT1 = BIT(1),
+    SDIO_SLAVE_HOSTINT_BIT2 = BIT(2),
+    SDIO_SLAVE_HOSTINT_BIT3 = BIT(3),
+    SDIO_SLAVE_HOSTINT_BIT4 = BIT(4),
+    SDIO_SLAVE_HOSTINT_BIT5 = BIT(5),
+    SDIO_SLAVE_HOSTINT_BIT6 = BIT(6),
+    SDIO_SLAVE_HOSTINT_BIT7 = BIT(7),
+    SDIO_SLAVE_HOSTINT_SEND_NEW_PACKET = BIT(23), ///< New packet available
+} sdio_slave_hostint_t;
+
+
+/// Timing of SDIO slave
+typedef enum {
+    SDIO_SLAVE_TIMING_PSEND_PSAMPLE = 0,/**< Send at posedge, and sample at posedge. Default value for HS mode.
+                                         *   Normally there's no problem using this to work in DS mode.
+                                         */
+    SDIO_SLAVE_TIMING_NSEND_PSAMPLE    ,///< Send at negedge, and sample at posedge. Default value for DS mode and below.
+    SDIO_SLAVE_TIMING_PSEND_NSAMPLE,    ///< Send at posedge, and sample at negedge
+    SDIO_SLAVE_TIMING_NSEND_NSAMPLE,    ///< Send at negedge, and sample at negedge
+} sdio_slave_timing_t;
+
+/// Configuration of SDIO slave mode
+typedef enum {
+    SDIO_SLAVE_SEND_STREAM = 0, ///< Stream mode, all packets to send will be combined as one if possible
+    SDIO_SLAVE_SEND_PACKET = 1, ///< Packet mode, one packets will be sent one after another (only increase packet_len if last packet sent).
+} sdio_slave_sending_mode_t;

+ 729 - 0
components/soc/src/hal/sdio_slave_hal.c

@@ -0,0 +1,729 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The HAL layer for SDIO slave (common part)
+
+#include <soc/slc_struct.h>
+#include <soc/hinf_struct.h>
+#include <hal/sdio_slave_types.h>
+#include <soc/host_struct.h>
+#include <string.h>
+#include "hal/sdio_slave_hal.h"
+#include "hal/hal_defs.h"
+#include "esp_attr.h"
+
+
+#define SDIO_SLAVE_CHECK(res, str, ret_val) do { if(!(res)){\
+    HAL_LOGE(TAG, "%s", str);\
+    return ret_val;\
+} }while (0)
+
+const char TAG[] = "SDIO_HAL";
+
+static esp_err_t init_send_queue(sdio_slave_context_t *hal);
+
+/**************** Ring buffer for SDIO sending use *****************/
+typedef enum {
+    RINGBUF_GET_ONE = 0,
+    RINGBUF_GET_ALL = 1,
+} ringbuf_get_all_t;
+
+typedef enum {
+    RINGBUF_WRITE_PTR,
+    RINGBUF_READ_PTR,
+    RINGBUF_FREE_PTR,
+} sdio_ringbuf_pointer_t;
+
+static esp_err_t sdio_ringbuf_send(sdio_ringbuf_t *buf, esp_err_t (*copy_callback)(uint8_t *, void *), void *arg);
+static inline esp_err_t sdio_ringbuf_recv(sdio_ringbuf_t *buf, uint8_t **start, uint8_t **end, ringbuf_get_all_t get_all);
+static inline int sdio_ringbuf_return(sdio_ringbuf_t* buf, uint8_t *ptr);
+
+#define _SEND_DESC_NEXT(x)    STAILQ_NEXT(&((sdio_slave_hal_send_desc_t*)x)->dma_desc, qe)
+#define SEND_DESC_NEXT(x)    (sdio_slave_hal_send_desc_t*)_SEND_DESC_NEXT(x)
+#define SEND_DESC_NEXT_SET(x, target)    do { \
+        _SEND_DESC_NEXT(x)=(lldesc_t*)target; \
+    }while(0)
+
+static esp_err_t link_desc_to_last(uint8_t* desc, void* arg)
+{
+    SEND_DESC_NEXT_SET(arg, desc);
+    return ESP_OK;
+}
+
+//calculate a pointer with offset to a original pointer of the specific ringbuffer
+static inline uint8_t* sdio_ringbuf_offset_ptr(sdio_ringbuf_t *buf, sdio_ringbuf_pointer_t ptr, uint32_t offset)
+{
+    uint8_t *buf_ptr;
+    switch (ptr) {
+        case RINGBUF_WRITE_PTR:
+            buf_ptr = buf->write_ptr;
+            break;
+        case RINGBUF_READ_PTR:
+            buf_ptr = buf->read_ptr;
+            break;
+        case RINGBUF_FREE_PTR:
+            buf_ptr = buf->free_ptr;
+            break;
+        default:
+            abort();
+    }
+
+    uint8_t *offset_ptr=buf_ptr+offset;
+    if (offset_ptr >= buf->data + buf->size) {
+        offset_ptr -= buf->size;
+    }
+    return offset_ptr;
+}
+
+static esp_err_t sdio_ringbuf_send(sdio_ringbuf_t *buf, esp_err_t (*copy_callback)(uint8_t *, void *), void *arg)
+{
+    uint8_t* get_ptr = sdio_ringbuf_offset_ptr(buf, RINGBUF_WRITE_PTR, SDIO_SLAVE_SEND_DESC_SIZE);
+    esp_err_t err = ESP_OK;
+    if (copy_callback) {
+        (*copy_callback)(get_ptr, arg);
+    }
+    if (err != ESP_OK) return err;
+
+    buf->write_ptr = get_ptr;
+    return ESP_OK;
+}
+
+// this ringbuf is a return-before-recv-again strategy
+// since this is designed to be called in the ISR, no parallel logic
+static inline esp_err_t sdio_ringbuf_recv(sdio_ringbuf_t *buf, uint8_t **start, uint8_t **end, ringbuf_get_all_t get_all)
+{
+    assert(buf->free_ptr == buf->read_ptr);   //must return before recv again
+    if (start == NULL && end == NULL) return ESP_ERR_INVALID_ARG; // must have a output
+    if (buf->read_ptr == buf->write_ptr) return ESP_ERR_NOT_FOUND; // no data
+
+    uint8_t *get_start = sdio_ringbuf_offset_ptr(buf, RINGBUF_READ_PTR, SDIO_SLAVE_SEND_DESC_SIZE);
+
+    if (get_all != RINGBUF_GET_ONE) {
+        buf->read_ptr = buf->write_ptr;
+    } else {
+        buf->read_ptr = get_start;
+    }
+
+    if (start != NULL) {
+        *start = get_start;
+    }
+    if (end != NULL) {
+        *end = buf->read_ptr;
+    }
+    return ESP_OK;
+}
+
+static inline int sdio_ringbuf_return(sdio_ringbuf_t* buf, uint8_t *ptr)
+{
+    assert(sdio_ringbuf_offset_ptr(buf, RINGBUF_FREE_PTR, SDIO_SLAVE_SEND_DESC_SIZE) == ptr);
+    int size = (buf->read_ptr + buf->size - buf->free_ptr) % buf->size;
+    int count = size / SDIO_SLAVE_SEND_DESC_SIZE;
+    assert(count * SDIO_SLAVE_SEND_DESC_SIZE==size);
+    buf->free_ptr = buf->read_ptr;
+    return count;
+}
+
+static inline uint8_t* sdio_ringbuf_peek_front(sdio_ringbuf_t* buf)
+{
+    if (buf->read_ptr != buf->write_ptr) {
+        return sdio_ringbuf_offset_ptr(buf, RINGBUF_READ_PTR, SDIO_SLAVE_SEND_DESC_SIZE);
+    } else {
+        return NULL;
+    }
+}
+
+static inline uint8_t* sdio_ringbuf_peek_rear(sdio_ringbuf_t *buf)
+{
+    return buf->write_ptr;
+}
+
+static inline bool sdio_ringbuf_empty(sdio_ringbuf_t* buf)
+{
+    return (buf->read_ptr == buf->write_ptr);
+}
+
+/**************** End of Ring buffer *****************/
+
+void sdio_slave_hal_init(sdio_slave_context_t *hal)
+{
+    hal->host = sdio_slave_ll_get_host(0);
+    hal->slc = sdio_slave_ll_get_slc(0);
+    hal->hinf = sdio_slave_ll_get_hinf(0);
+    hal->send_state = STATE_IDLE;
+    hal->recv_link_list = (sdio_slave_hal_recv_stailq_t)STAILQ_HEAD_INITIALIZER(hal->recv_link_list);
+
+    init_send_queue(hal);
+}
+
+void sdio_slave_hal_hw_init(sdio_slave_context_t *hal)
+{
+    sdio_slave_ll_init(hal->slc);
+    sdio_slave_ll_enable_hs(hal->hinf, true);
+    sdio_slave_ll_set_timing(hal->host, hal->timing);
+    sdio_slave_ll_slvint_t intr_ena = 0xff;
+    sdio_slave_ll_slvint_set_ena(hal->slc, &intr_ena);
+}
+
+static esp_err_t init_send_queue(sdio_slave_context_t *hal)
+{
+    esp_err_t ret;
+    esp_err_t rcv_res;
+    sdio_ringbuf_t *buf = &(hal->send_desc_queue);
+
+    //initialize pointers
+    buf->write_ptr = buf->data;
+    buf->read_ptr = buf->data;
+    buf->free_ptr = buf->data;
+
+    sdio_slave_hal_send_desc_t *first = NULL, *last = NULL;
+    //no copy for the first descriptor
+
+    ret = sdio_ringbuf_send(buf, NULL, NULL);
+    if (ret != ESP_OK) return ret;
+
+    //loop in the ringbuf to link all the desc one after another as a ring
+    for (int i = 0; i < hal->send_queue_size + 1; i++) {
+        rcv_res = sdio_ringbuf_recv(buf, (uint8_t **) &last, NULL, RINGBUF_GET_ONE);
+        assert (rcv_res == ESP_OK);
+
+        ret = sdio_ringbuf_send(buf, link_desc_to_last, last);
+        if (ret != ESP_OK) return ret;
+
+        sdio_ringbuf_return(buf, (uint8_t *) last);
+    }
+
+    first = NULL;
+    last = NULL;
+    //clear the queue
+    rcv_res = sdio_ringbuf_recv(buf, (uint8_t **) &first, (uint8_t **) &last, RINGBUF_GET_ALL);
+    assert (rcv_res == ESP_OK);
+    assert(first == last); //there should be only one desc remain
+    sdio_ringbuf_return(buf, (uint8_t *) first);
+    return ESP_OK;
+}
+
+void sdio_slave_hal_set_ioready(sdio_slave_context_t *hal, bool ready)
+{
+    sdio_slave_ll_set_ioready(hal->hinf, ready);   //set IO ready to 1 to allow host to use
+}
+
+
+/*---------------------------------------------------------------------------
+ *                  Send
+ *
+ *  The hardware has a cache, so that once a descriptor is loaded onto the linked-list, it cannot be modified
+ *  until returned (used) by the hardware. This forbids us from loading descriptors onto the linked list during
+ *  the transfer (or the time waiting for host to start a transfer). However, we use a "ringbuffer" (different from
+ *  the one in ``freertos/`` folder) holding descriptors to solve this:
+
+ *  1.  The driver allocates continuous memory for several buffer descriptors (the maximum buffer number) during
+ *      initialization. Then the driver points the STAILQ_NEXT pointer of all the descriptors except the last one
+ *      to the next descriptor of each of them. Then the pointer of the last descriptor points back to the first one:
+ *      now the descriptor is in a ring.
+
+ *  2.  The "ringbuffer" has a write pointer points to where app can write new descriptor. The app writes the new descriptor
+ *      indicated by the write pointer without touching the STAILQ_NEXT pointer so that the descriptors are always in a
+ *      ring-like linked-list. The app never touches the part of linked-list being used by the hardware.
+
+ *  3.  When the hardware needs some data to send, it automatically pick a part of linked descriptors. According to the mode:
+ *          - Buffer mode: only pick the next one to the last one sent;
+ *          - Stream mode: pick the whole unsent linked list, starting from the one above, to the latest linked one.
+
+ *      The driver removes the STAILQ_NEXT pointer of the last descriptor and put the head of the part to the DMA controller so
+ *      that it looks like just a linear linked-list rather than a ring to the hardware.
+
+ *  4.  The counter of sending FIFO can increase when app load new buffers (in STREAM_MODE) or when new transfer should
+ *      start (in PACKET_MODE).
+
+ *  5.  When the sending transfer is finished, the driver goes through the descriptors just send in the ISR and push all
+ *      the ``arg`` member of descriptors to the queue back to the app, so that the app can handle finished buffers. The
+ *      driver also fix the STAILQ_NEXT pointer of the last descriptor so that the descriptors are now in a ring again.
+----------------------------------------------------------------------------*/
+static inline void send_set_state(sdio_slave_context_t *hal, send_state_t state)
+{
+    hal->send_state = state;
+}
+
+static inline send_state_t send_get_state(sdio_slave_context_t* hal)
+{
+    return hal->send_state;
+}
+
+DMA_ATTR static const lldesc_t start_desc = {
+    .owner = 1,
+    .buf = (void*)0x3ffbbbbb, //assign a dma-capable pointer other than NULL, which will not be used
+    .size = 1,
+    .length = 1,
+    .eof = 1,
+};
+
+//force trigger rx_done interrupt. the interrupt is abused to invoke ISR from the app by the enable bit and never cleared.
+static void send_isr_invoker_enable(const sdio_slave_context_t *hal)
+{
+    sdio_slave_ll_send_reset(hal->slc);
+    sdio_slave_ll_send_start(hal->slc, &start_desc);
+    //wait for rx_done
+    while(!sdio_slave_ll_send_invoker_ready(hal->slc));
+    sdio_slave_ll_send_stop(hal->slc);
+    sdio_slave_ll_send_hostint_clr(hal->host);
+}
+
+static void send_isr_invoker_disable(sdio_slave_context_t *hal)
+{
+    sdio_slave_ll_send_part_done_clear(hal->slc);
+}
+
+void sdio_slave_hal_send_handle_isr_invoke(sdio_slave_context_t *hal)
+{
+    sdio_slave_ll_send_part_done_intr_ena(hal->slc, false);
+}
+
+//start hw operation with existing data (if exist)
+esp_err_t sdio_slave_hal_send_start(sdio_slave_context_t *hal)
+{
+    SDIO_SLAVE_CHECK(send_get_state(hal) == STATE_IDLE,
+                     "already started", ESP_ERR_INVALID_STATE);
+    send_set_state(hal, STATE_WAIT_FOR_START);
+    send_isr_invoker_enable(hal);
+    sdio_slave_ll_send_intr_clr(hal->slc);
+    sdio_slave_ll_send_intr_ena(hal->slc, true);
+    return ESP_OK;
+}
+
+//only stop hw operations, no touch to data as well as counter
+void sdio_slave_hal_send_stop(sdio_slave_context_t *hal)
+{
+    sdio_slave_ll_send_stop(hal->slc);
+    send_isr_invoker_disable(hal);
+    sdio_slave_ll_send_intr_ena(hal->slc, false);
+    send_set_state(hal, STATE_IDLE);
+}
+
+static void send_new_packet(sdio_slave_context_t *hal)
+{
+    // since eof is changed, we have to stop and reset the link list,
+    // and restart new link list operation
+    sdio_slave_hal_send_desc_t *const start_desc = hal->in_flight_head;
+    sdio_slave_hal_send_desc_t *const end_desc = hal->in_flight_end;
+    assert(start_desc != NULL && end_desc != NULL);
+
+    sdio_slave_ll_send_stop(hal->slc);
+    sdio_slave_ll_send_reset(hal->slc);
+    sdio_slave_ll_send_start(hal->slc, (lldesc_t*)start_desc);
+
+    // update pkt_len register to allow host reading.
+    sdio_slave_ll_send_write_len(hal->slc, end_desc->pkt_len);
+    ESP_EARLY_LOGV(TAG, "send_length_write: %d, last_len: %08X", end_desc->pkt_len, sdio_slave_ll_send_read_len(hal->host));
+
+    send_set_state(hal, STATE_SENDING);
+
+    ESP_EARLY_LOGD(TAG, "restart new send: %p->%p, pkt_len: %d", start_desc, end_desc, end_desc->pkt_len);
+}
+
+static esp_err_t send_check_new_packet(sdio_slave_context_t *hal)
+{
+    esp_err_t ret;
+    sdio_slave_hal_send_desc_t *start = NULL;
+    sdio_slave_hal_send_desc_t *end = NULL;
+    if (hal->sending_mode == SDIO_SLAVE_SEND_PACKET) {
+        ret = sdio_ringbuf_recv(&(hal->send_desc_queue), (uint8_t **) &start, (uint8_t **) &end, RINGBUF_GET_ONE);
+    } else { //stream mode
+        ret = sdio_ringbuf_recv(&(hal->send_desc_queue), (uint8_t **) &start, (uint8_t **) &end, RINGBUF_GET_ALL);
+    }
+    if (ret == ESP_OK) {
+        hal->in_flight_head = start;
+        hal->in_flight_end = end;
+        end->dma_desc.eof = 1;
+        //temporarily break the link ring here, the ring will be re-connected in ``send_isr_eof()``.
+        hal->in_flight_next = SEND_DESC_NEXT(end);
+        SEND_DESC_NEXT_SET(end, NULL);
+    }
+    return ESP_OK;
+}
+
+bool sdio_slave_hal_send_eof_happened(sdio_slave_context_t* hal)
+{
+    // Goto idle state (cur_start=NULL) if transmission done,
+    // also update sequence and recycle descs.
+    if (sdio_slave_ll_send_done(hal->slc)) {
+        //check current state
+        assert(send_get_state(hal) == STATE_SENDING);
+        sdio_slave_ll_send_intr_clr(hal->slc);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+//clear counter but keep data
+esp_err_t sdio_slave_hal_send_reset_counter(sdio_slave_context_t* hal)
+{
+    SDIO_SLAVE_CHECK(send_get_state(hal) == STATE_IDLE,
+                     "reset counter when transmission started", ESP_ERR_INVALID_STATE);
+
+    uint32_t len;
+    sdio_slave_ll_send_write_len(hal->slc, 0);
+    ESP_EARLY_LOGV(TAG, "send_length_write: %d, last_len: %08X", len, sdio_slave_ll_send_read_len(hal->host));
+
+    hal->tail_pkt_len = 0;
+    sdio_slave_hal_send_desc_t *desc = hal->in_flight_head;
+    while(desc != NULL) {
+        hal->tail_pkt_len += desc->dma_desc.length;
+        desc->pkt_len = hal->tail_pkt_len;
+        desc = SEND_DESC_NEXT(desc);
+    }
+    // in theory the desc should be the one right next to the last of in_flight_head,
+    // but the link of last is NULL, so get the desc from the ringbuf directly.
+    desc = (sdio_slave_hal_send_desc_t*)sdio_ringbuf_peek_front(&(hal->send_desc_queue));
+    while(desc != NULL) {
+        hal->tail_pkt_len += desc->dma_desc.length;
+        desc->pkt_len = hal->tail_pkt_len;
+        desc = SEND_DESC_NEXT(desc);
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t send_get_inflight_desc(sdio_slave_context_t *hal, void **out_arg, uint32_t *out_returned_cnt,
+                                        bool init)
+{
+    esp_err_t ret;
+    if (init) {
+        assert(hal->returned_desc == NULL);
+        hal->returned_desc = hal->in_flight_head;
+        send_set_state(hal, STATE_GETTING_RESULT);
+    }
+
+    if (hal->returned_desc != NULL) {
+        *out_arg = hal->returned_desc->arg;
+        hal->returned_desc = SEND_DESC_NEXT(hal->returned_desc);
+        ret = ESP_OK;
+    } else {
+        if (hal->in_flight_head != NULL) {
+            // fix the link broken of last desc when being sent
+            assert(hal->in_flight_end != NULL);
+            SEND_DESC_NEXT_SET(hal->in_flight_end, hal->in_flight_next);
+
+            *out_returned_cnt = sdio_ringbuf_return(&(hal->send_desc_queue), (uint8_t*)hal->in_flight_head);
+        }
+
+        hal->in_flight_head = NULL;
+        hal->in_flight_end = NULL;
+
+        ret = ESP_ERR_NOT_FOUND;
+    }
+    return ret;
+}
+
+static esp_err_t send_get_unsent_desc(sdio_slave_context_t *hal, void **out_arg, uint32_t *out_return_cnt)
+{
+    esp_err_t ret;
+    sdio_slave_hal_send_desc_t *head, *tail;
+    ret = sdio_ringbuf_recv(&(hal->send_desc_queue), (uint8_t **) &head, (uint8_t **) &tail, RINGBUF_GET_ONE);
+
+    if (ret == ESP_OK) {
+        //currently each packet takes only one desc.
+        assert(head == tail);
+        (*out_arg) = head->arg;
+        (*out_return_cnt) = sdio_ringbuf_return(&(hal->send_desc_queue), (uint8_t*) head);
+    } else if (ret == ESP_ERR_NOT_FOUND) {
+        // if in wait to send state, set the sequence number of tail to the value last sent, just as if the packet wait to
+        // send never queued.
+        // Go to idle state (cur_end!=NULL and cur_start=NULL)
+        send_set_state(hal, STATE_IDLE);
+        hal->tail_pkt_len = sdio_slave_ll_send_read_len(hal->host);
+    }
+    return ret;
+}
+
+esp_err_t sdio_slave_hal_send_get_next_finished_arg(sdio_slave_context_t *hal, void **out_arg, uint32_t* out_returned_cnt)
+{
+    bool init = (send_get_state(hal) == STATE_SENDING);
+    if (init) {
+        assert(hal->in_flight_head != NULL);
+    } else {
+        assert(send_get_state(hal) == STATE_GETTING_RESULT);
+    }
+    *out_returned_cnt = 0;
+
+    esp_err_t ret = send_get_inflight_desc(hal, out_arg, out_returned_cnt, init);
+
+    if (ret == ESP_ERR_NOT_FOUND) {
+        // Go to wait for packet state
+        send_set_state(hal, STATE_WAIT_FOR_START);
+    }
+    return ret;
+}
+
+
+esp_err_t sdio_slave_hal_send_flush_next_buffer(sdio_slave_context_t *hal, void **out_arg, uint32_t *out_return_cnt)
+{
+    esp_err_t ret = ESP_OK;
+    *out_return_cnt = 0;
+    bool init = (send_get_state(hal) == STATE_IDLE);
+    if (!init) {
+        if (send_get_state(hal) != STATE_GETTING_RESULT && send_get_state(hal) != STATE_GETTING_UNSENT_DESC) {
+            return ESP_ERR_INVALID_STATE;
+        }
+    }
+
+    if (init || send_get_state(hal) == STATE_GETTING_RESULT) {
+        ret = send_get_inflight_desc(hal, out_arg, out_return_cnt, init);
+        if (ret == ESP_ERR_NOT_FOUND) {
+            send_set_state(hal, STATE_GETTING_UNSENT_DESC);
+        }
+    }
+    if (send_get_state(hal) == STATE_GETTING_UNSENT_DESC) {
+        ret = send_get_unsent_desc(hal, out_arg, out_return_cnt);
+        if (ret == ESP_ERR_NOT_FOUND) {
+            send_set_state(hal, STATE_IDLE);
+        }
+    }
+    return ret;
+}
+
+esp_err_t sdio_slave_hal_send_new_packet_if_exist(sdio_slave_context_t *hal)
+{
+    esp_err_t ret;
+    // Go to wait sending state (cur_start!=NULL && cur_end==NULL) if not sending and new packet ready.
+    // Note we may also enter this state by stopping sending in the app.
+    if (send_get_state(hal) == STATE_WAIT_FOR_START) {
+        if (hal->in_flight_head == NULL) {
+            send_check_new_packet(hal);
+        }
+        // Go to sending state (cur_start and cur_end != NULL) if has packet to send.
+        if (hal->in_flight_head) {
+            send_new_packet(hal);
+            ret = ESP_OK;
+        } else {
+            ret = ESP_ERR_NOT_FOUND;
+        }
+    } else {
+        ret = ESP_ERR_INVALID_STATE;
+    }
+    return ret;
+}
+
+static esp_err_t send_write_desc(uint8_t* desc, void* arg)
+{
+    sdio_slave_hal_send_desc_t* next_desc = SEND_DESC_NEXT(desc);
+    memcpy(desc, arg, sizeof(sdio_slave_hal_send_desc_t));
+    SEND_DESC_NEXT_SET(desc, next_desc);
+    return ESP_OK;
+}
+
+static void send_isr_invoke(sdio_slave_context_t *hal)
+{
+    sdio_slave_ll_send_part_done_intr_ena(hal->slc, true);
+}
+
+esp_err_t sdio_slave_hal_send_queue(sdio_slave_context_t* hal, uint8_t *addr, size_t len, void *arg)
+{
+    hal->tail_pkt_len += len;
+    sdio_slave_hal_send_desc_t new_desc = {
+        .dma_desc = {
+            .size   =   len,
+            .length =   len,
+            .buf    =   addr,
+            .owner  =   1,
+            // in stream mode, the eof is only appended (in ISR) when new packet is ready to be sent
+            .eof    =   (hal->sending_mode == SDIO_SLAVE_SEND_PACKET),
+        },
+        .arg    =   arg,
+        .pkt_len = hal->tail_pkt_len,
+    };
+
+    esp_err_t ret = sdio_ringbuf_send(&(hal->send_desc_queue), send_write_desc, &new_desc);
+    send_isr_invoke(hal);
+    return ret;
+}
+
+/*---------------------------------------------------------------------------
+ *                  Receive
+ *--------------------------------------------------------------------------*/
+
+static lldesc_t* recv_get_first_empty_buf(sdio_slave_context_t* hal)
+{
+    sdio_slave_hal_recv_stailq_t *const queue = &(hal->recv_link_list);
+    lldesc_t *desc = STAILQ_FIRST(queue);
+    while(desc && desc->owner == 0) {
+        desc = STAILQ_NEXT(desc, qe);
+    }
+    return desc;
+}
+
+void sdio_slave_hal_recv_stop(sdio_slave_context_t* hal)
+{
+    sdio_slave_ll_set_ioready(hal->hinf, false); //set IO ready to 0 to stop host from using
+    sdio_slave_ll_send_stop(hal->slc);
+    sdio_slave_ll_recv_stop(hal->slc);
+    sdio_slave_ll_recv_intr_ena(hal->slc, false);
+}
+
+//touching linked list, should be protected by spinlock
+bool sdio_slave_hal_recv_has_next_item(sdio_slave_context_t* hal)
+{
+
+    if (hal->recv_cur_ret == NULL || hal->recv_cur_ret->owner != 0) return false;
+
+    // This may cause the ``cur_ret`` pointer to be NULL, indicating the list is empty,
+    // in this case the ``tx_done`` should happen no longer until new desc is appended.
+    // The app is responsible to place the pointer to the right place again when appending new desc.
+
+    hal->recv_cur_ret = STAILQ_NEXT(hal->recv_cur_ret, qe);
+    return true;
+}
+
+bool sdio_slave_hal_recv_done(sdio_slave_context_t *hal)
+{
+    bool ret = sdio_slave_ll_recv_done(hal->slc);
+    if (ret) {
+        sdio_slave_ll_recv_done_clear(hal->slc);
+    }
+    return ret;
+}
+
+lldesc_t *sdio_slave_hal_recv_unload_desc(sdio_slave_context_t *hal)
+{
+    sdio_slave_hal_recv_stailq_t *const queue = &hal->recv_link_list;
+    lldesc_t *desc = STAILQ_FIRST(queue);
+    if (desc) {
+        STAILQ_REMOVE_HEAD(queue, qe);
+    }
+    return desc;
+}
+
+void sdio_slave_hal_recv_init_desc(sdio_slave_context_t* hal, lldesc_t *desc, uint8_t *start)
+{
+    *desc = (lldesc_t) {
+        .size = hal->recv_buffer_size,
+        .buf = start,
+    };
+}
+
+void sdio_slave_hal_recv_start(sdio_slave_context_t *hal)
+{
+    sdio_slave_ll_recv_reset(hal->slc);
+    lldesc_t *desc = recv_get_first_empty_buf(hal);
+    if (!desc) {
+        HAL_LOGD(TAG, "recv: restart without desc");
+    } else {
+        //the counter is handled when add/flush/reset
+        sdio_slave_ll_recv_start(hal->slc, desc);
+        sdio_slave_ll_recv_intr_ena(hal->slc, true);
+    }
+}
+
+void sdio_slave_hal_recv_reset_counter(sdio_slave_context_t *hal)
+{
+    sdio_slave_ll_recv_size_reset(hal->slc);
+    lldesc_t *desc = recv_get_first_empty_buf(hal);
+    while (desc != NULL) {
+        sdio_slave_ll_recv_size_inc(hal->slc);
+        desc = STAILQ_NEXT(desc, qe);
+    }
+}
+
+void sdio_slave_hal_recv_flush_one_buffer(sdio_slave_context_t *hal)
+{
+    sdio_slave_hal_recv_stailq_t *const queue = &hal->recv_link_list;
+    lldesc_t *desc = STAILQ_FIRST(queue);
+    assert (desc != NULL && desc->owner == 0);
+    STAILQ_REMOVE_HEAD(queue, qe);
+    desc->owner = 1;
+    STAILQ_INSERT_TAIL(queue, desc, qe);
+    sdio_slave_ll_recv_size_inc(hal->slc);
+    //we only add it to the tail here, without start the DMA nor increase buffer num.
+}
+
+void sdio_slave_hal_load_buf(sdio_slave_context_t *hal, lldesc_t *desc)
+{
+    sdio_slave_hal_recv_stailq_t *const queue = &(hal->recv_link_list);
+    desc->owner = 1;
+
+    lldesc_t *const tail = STAILQ_LAST(queue, lldesc_s, qe);
+
+    STAILQ_INSERT_TAIL(queue, desc, qe);
+    if (hal->recv_cur_ret == NULL) {
+        hal->recv_cur_ret = desc;
+    }
+
+    if (tail == NULL) {
+        //no one in the ll, start new ll operation.
+        sdio_slave_ll_recv_start(hal->slc, desc);
+        sdio_slave_ll_recv_intr_ena(hal->slc, true);
+        HAL_LOGV(TAG, "recv_load_buf: start new");
+    } else {
+        //restart former ll operation
+        sdio_slave_ll_recv_restart(hal->slc);
+        HAL_LOGV(TAG, "recv_load_buf: restart");
+    }
+    sdio_slave_ll_recv_size_inc(hal->slc);
+}
+
+static inline void show_queue_item(lldesc_t *item)
+{
+    ESP_EARLY_LOGI(TAG, "=> %p: size: %d(%d), eof: %d, owner: %d", item, item->size, item->length, item->eof, item->owner);
+    ESP_EARLY_LOGI(TAG, "   buf: %p, stqe_next: %p", item->buf, item->qe.stqe_next);
+}
+
+static void __attribute((unused)) dump_queue(sdio_slave_hal_recv_stailq_t *queue)
+{
+    int cnt = 0;
+    lldesc_t *item = NULL;
+    ESP_EARLY_LOGI(TAG, ">>>>> first: %p, last: %p <<<<<", queue->stqh_first, queue->stqh_last);
+    STAILQ_FOREACH(item, queue, qe) {
+        cnt++;
+        show_queue_item(item);
+    }
+    ESP_EARLY_LOGI(TAG, "total: %d", cnt);
+}
+
+/*---------------------------------------------------------------------------
+ *                  Host
+ *--------------------------------------------------------------------------*/
+void sdio_slave_hal_hostint_get_ena(sdio_slave_context_t *hal, sdio_slave_hostint_t *out_int_mask)
+{
+    *out_int_mask = sdio_slave_ll_host_get_intena(hal->host);
+}
+
+void sdio_slave_hal_hostint_clear(sdio_slave_context_t *hal, const sdio_slave_hostint_t *mask)
+{
+    sdio_slave_ll_host_intr_clear(hal->host, mask);//clear all interrupts
+}
+
+void sdio_slave_hal_hostint_set_ena(sdio_slave_context_t *hal, const sdio_slave_hostint_t *mask)
+{
+    sdio_slave_ll_host_set_intena(hal->host, mask);
+}
+
+void sdio_slave_hal_hostint_send(sdio_slave_context_t *hal, const sdio_slave_hostint_t *mask)
+{
+    sdio_slave_ll_host_send_int(hal->slc, mask);
+}
+
+uint8_t sdio_slave_hal_host_get_reg(sdio_slave_context_t *hal, int pos)
+{
+    return sdio_slave_ll_host_get_reg(hal->host, pos);
+}
+void sdio_slave_hal_host_set_reg(sdio_slave_context_t *hal, int pos, uint8_t reg)
+{
+    sdio_slave_ll_host_set_reg(hal->host, pos, reg);
+}
+
+void sdio_slave_hal_slvint_fetch_clear(sdio_slave_context_t *hal, sdio_slave_ll_slvint_t *out_int_mask)
+{
+    sdio_slave_ll_slvint_fetch_clear(hal->slc, out_int_mask);
+}
+

+ 1 - 0
docs/Doxyfile

@@ -184,6 +184,7 @@ INPUT = \
     ../../components/driver/include/driver/sdspi_host.h \
     ## SDIO slave
     ../../components/driver/include/driver/sdio_slave.h \
+    ../../components/soc/include/hal/sdio_slave_types.h \
     ## Non-Volatile Storage
     ../../components/nvs_flash/include/nvs.h \
     ../../components/nvs_flash/include/nvs_flash.h \

+ 1 - 0
docs/en/api-reference/peripherals/sdio_slave.rst

@@ -277,5 +277,6 @@ Slave/master communication: :example:`peripherals/sdio`.
 API Reference
 -------------
 
+.. include:: /_build/inc/sdio_slave_types.inc
 .. include:: /_build/inc/sdio_slave.inc
 

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.