feat(hal):graudate the sdmmc/sdio_slave hal driver into a new component

This commit is contained in:
armando
2026-02-04 19:00:57 +08:00
parent 148e333495
commit fe5b8358f3
44 changed files with 164 additions and 172 deletions
+25
View File
@@ -0,0 +1,25 @@
idf_build_get_property(target IDF_TARGET)
if(${target} STREQUAL "linux")
return() # This component is not supported by the POSIX/Linux simulator
endif()
set(srcs)
set(public_include "include")
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${target}/include")
list(APPEND public_include "${target}/include")
endif()
if(CONFIG_SOC_SDMMC_HOST_SUPPORTED)
list(APPEND srcs "sdmmc_hal.c")
list(APPEND srcs "${target}/sdmmc_periph.c")
endif()
if(CONFIG_SOC_SDIO_SLAVE_SUPPORTED)
list(APPEND srcs "sdio_slave_hal.c")
list(APPEND srcs "${target}/sdio_slave_periph.c")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${public_include}
REQUIRES soc hal)
+13
View File
@@ -0,0 +1,13 @@
# `esp_hal_sd`
⚠️ This HAL component is still under heavy development at the moment, so we don't guarantee the stability and backward-compatibility among versions.
The `esp_hal_sd` component provides a **Hardware Abstraction Layer** for
- SDMMC Host Controller
- SDIO Slave Controller
for all targets supported by ESP-IDF.
In a broad sense, the HAL layer consists of two sub-layers: HAL (upper) and Low-Level(bottom). The HAL layer defines the steps and data that is required to operate a peripheral (e.g. initialization, parameter settings). The low-level is a translation layer above the register files under the `soc` component, it only covers general conceptions to register configurations.
The functions in this file mainly provide hardware abstraction for IDF peripheral drivers. For advanced developers, the HAL layer functions can also be directly used to assist in implementing their own drivers. However, it needs to be mentioned again that the interfaces here do not guarantee stability.
@@ -0,0 +1,568 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in hal/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 <sys/queue.h>
#include <stdbool.h>
#include "hal/sdio_slave_types.h"
#include "hal/misc.h"
#include "soc/sdio_slc_struct.h"
#include "soc/sdio_slc_reg.h"
#include "soc/sdio_slc_host_struct.h"
#include "soc/sdio_slc_host_reg.h"
#include "soc/sdio_hinf_struct.h"
#include "soc/dport_reg.h"
#ifdef __cplusplus
extern "C" {
#endif
/// 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)
/// Get the mask of the interrupt status.
#define sdio_slave_ll_intr_status_mask (0xff | SLC_SLC0_RX_DONE_INT_ST | SLC_SLC0_RX_EOF_INT_ST | SLC_SLC0_TX_DONE_INT_ST)
/*
* SLC2 DMA Desc struct, aka sdio_slave_ll_desc_t
*
* --------------------------------------------------------------
* | own | EoF | sub_sof | 5'b0 | length [11:0] | size [11:0] |
* --------------------------------------------------------------
* | buf_ptr [31:0] |
* --------------------------------------------------------------
* | next_desc_ptr [31:0] |
* --------------------------------------------------------------
*/
/* this bitfield is start from the LSB!!! */
typedef struct sdio_slave_ll_desc_s {
volatile uint32_t size : 12,
length: 12,
offset: 5, /* starting from bit24, h/w reserved 5bit, s/w use it as offset in buffer */
sosf : 1, /* start of sub-frame */
eof : 1, /* end of frame */
owner : 1; /* hw or sw */
volatile const uint8_t *buf; /* point to buffer data */
union {
volatile uint32_t empty;
STAILQ_ENTRY(sdio_slave_ll_desc_s) qe; /* pointing to the next desc */
};
} sdio_slave_ll_desc_t;
/// 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;
/**
* @brief Enable the bus clock for the SDIO slave module
*
* @param enable true to enable, false to disable
*/
static inline void _sdio_slave_ll_enable_bus_clock(bool enable)
{
uint32_t reg_val = DPORT_READ_PERI_REG(DPORT_WIFI_CLK_EN_REG);
reg_val &= ~DPORT_WIFI_CLK_SDIOSLAVE_EN;
reg_val |= enable << 4;
DPORT_WRITE_PERI_REG(DPORT_WIFI_CLK_EN_REG, reg_val);
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdio_slave_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_sdio_slave_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* @brief Reset the SDIO slave module
*/
static inline void _sdio_slave_ll_reset_register(void)
{
DPORT_WRITE_PERI_REG(DPORT_CORE_RST_EN_REG, DPORT_SDIO_RST);
DPORT_WRITE_PERI_REG(DPORT_CORE_RST_EN_REG, 0);
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdio_slave_ll_reset_register(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
_sdio_slave_ll_reset_register(__VA_ARGS__); \
} while(0)
/**
* 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 CCCR, SDIO and Physical Layer version
*/
static inline void sdio_slave_ll_init_version(hinf_dev_t *hinf)
{
hinf->cfg_data1.sdio_ver = 0x232;
}
/**
* 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.highspeed_enable = 1;
} else {
hinf->cfg_data1.highspeed_enable = 0;
}
}
/**
* 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 sdio_slave_ll_desc_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, sdio_slave_ll_desc_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 (sdio_slave_hostint_t)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
HAL_FORCE_MODIFY_U32_REG_FIELD(slc->intvec_tohost, slc0_intvec, *mask);
}
/**
* Enable some of the slave interrupts (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 = (sdio_slave_ll_slvint_t)(slc->slc0_int_st.val & 0xff);
*out_slv_int = slv_int;
slc->slc0_int_clr.val = slv_int;
}
/**
* Get the address of the interrupt status register.
*
* @param slc Address of the SLC registers
* @return Address of the interrupt status register
*/
static inline volatile void* sdio_slave_ll_get_intr_status_reg(slc_dev_t *slc)
{
return &slc->slc0_int_st.val;
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,738 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The ll is not public api, don't use in application code.
* See readme.md in hal/include/hal/readme.md
******************************************************************************/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "esp_bit_defs.h"
#include "hal/assert.h"
#include "hal/misc.h"
#include "hal/sd_types.h"
#include "soc/clk_tree_defs.h"
#include "soc/sdmmc_struct.h"
#include "soc/sdmmc_reg.h"
#include "soc/dport_reg.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SDMMC_LL_GET_HW(id) (((id) == 0) ? (&SDMMC) : NULL)
#define SDMMC_LL_EVENT_IO_SLOT1 (1<<17)
#define SDMMC_LL_EVENT_IO_SLOT0 (1<<16)
#define SDMMC_LL_EVENT_EBE (1<<15)
#define SDMMC_LL_EVENT_ACD (1<<14)
#define SDMMC_LL_EVENT_SBE (1<<13)
#define SDMMC_LL_EVENT_BCI (1<<13)
#define SDMMC_LL_EVENT_HLE (1<<12)
#define SDMMC_LL_EVENT_FRUN (1<<11)
#define SDMMC_LL_EVENT_HTO (1<<10)
#define SDMMC_LL_EVENT_DTO (1<<9)
#define SDMMC_LL_EVENT_RTO (1<<8)
#define SDMMC_LL_EVENT_DCRC (1<<7)
#define SDMMC_LL_EVENT_RCRC (1<<6)
#define SDMMC_LL_EVENT_RXDR (1<<5)
#define SDMMC_LL_EVENT_TXDR (1<<4)
#define SDMMC_LL_EVENT_DATA_OVER (1<<3)
#define SDMMC_LL_EVENT_CMD_DONE (1<<2)
#define SDMMC_LL_EVENT_RESP_ERR (1<<1)
#define SDMMC_LL_EVENT_CD (1<<0)
/* Default disabled interrupts (on init):
* SDMMC_LL_EVENT_RXDR,
* SDMMC_LL_EVENT_TXDR,
* SDMMC_LL_EVENT_BCI,
* SDMMC_LL_EVENT_ACD,
* SDMMC_LL_EVENT_IO_SLOT1,
* SDMMC_LL_EVENT_IO_SLOT0
*/
// Default enabled interrupts (sdio is enabled only when use):
#define SDMMC_LL_EVENT_DEFAULT \
(SDMMC_LL_EVENT_CD | SDMMC_LL_EVENT_RESP_ERR | SDMMC_LL_EVENT_CMD_DONE | SDMMC_LL_EVENT_DATA_OVER | \
SDMMC_LL_EVENT_RCRC | SDMMC_LL_EVENT_DCRC | SDMMC_LL_EVENT_RTO | SDMMC_LL_EVENT_DTO | SDMMC_LL_EVENT_HTO | \
SDMMC_LL_EVENT_HLE | \
SDMMC_LL_EVENT_SBE | \
SDMMC_LL_EVENT_EBE)
#define SDMMC_LL_SD_EVENT_MASK \
(SDMMC_LL_EVENT_CD | SDMMC_LL_EVENT_RESP_ERR | SDMMC_LL_EVENT_CMD_DONE | SDMMC_LL_EVENT_DATA_OVER | \
SDMMC_LL_EVENT_TXDR | SDMMC_LL_EVENT_RXDR |\
SDMMC_LL_EVENT_RCRC | SDMMC_LL_EVENT_DCRC | SDMMC_LL_EVENT_RTO | SDMMC_LL_EVENT_DTO | SDMMC_LL_EVENT_HTO | \
SDMMC_LL_EVENT_FRUN | SDMMC_LL_EVENT_HLE |\
SDMMC_LL_EVENT_SBE | SDMMC_LL_EVENT_ACD |\
SDMMC_LL_EVENT_EBE)
// DMA interrupts (idsts register)
#define SDMMC_LL_EVENT_DMA_TI SDMMC_IDMAC_INTMASK_TI
#define SDMMC_LL_EVENT_DMA_RI SDMMC_IDMAC_INTMASK_RI
#define SDMMC_LL_EVENT_DMA_NI SDMMC_IDMAC_INTMASK_NI
#define SDMMC_LL_EVENT_DMA_MASK 0x1f //NI and AI will be indicated by TI/RI and FBE/DU respectively
/**
* SDMMC capabilities
*/
#define SDMMC_LL_SLOT_SUPPORT_GPIO_MATRIX(SLOT_ID) 0
#define SDMMC_LL_IOMUX_FUNC 3
#define SDMMC_LL_HOST_CTLR_NUMS 1U
typedef enum {
SDMMC_LL_DELAY_PHASE_0,
SDMMC_LL_DELAY_PHASE_1,
SDMMC_LL_DELAY_PHASE_2,
SDMMC_LL_DELAY_PHASE_3,
} sdmmc_ll_delay_phase_t;
/*---------------------------------------------------------------
Clock & Reset
---------------------------------------------------------------*/
/**
* @brief Enable the bus clock for SDMMC module
*
* @param group_id Group ID
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_bus_clock(int group_id, bool en)
{
(void)group_id;
if (en) {
DPORT_SET_PERI_REG_MASK(DPORT_WIFI_CLK_EN_REG, DPORT_WIFI_CLK_SDIO_HOST_EN);
} else {
DPORT_CLEAR_PERI_REG_MASK(DPORT_WIFI_CLK_EN_REG, DPORT_WIFI_CLK_SDIO_HOST_EN);
}
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* @brief Reset the SDMMC module
*
* @param group_id Group ID
*/
static inline void sdmmc_ll_reset_register(int group_id)
{
(void)group_id;
DPORT_SET_PERI_REG_MASK(DPORT_CORE_RST_EN_REG, DPORT_SDIO_HOST_RST);
DPORT_CLEAR_PERI_REG_MASK(DPORT_CORE_RST_EN_REG, DPORT_SDIO_HOST_RST);
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_reset_register(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_reset_register(__VA_ARGS__); \
} while(0)
/**
* @brief Select SDMMC clock source
*
* @param hw hardware instance address
* @param clk_src clock source, see valid sources in type `soc_periph_sdmmc_clk_src_t`
*/
static inline void sdmmc_ll_select_clk_source(sdmmc_dev_t *hw, soc_periph_sdmmc_clk_src_t clk_src)
{
//leave for compatibility
}
/**
* @brief Set SDMMC clock div
*
* @param hw hardware instance address
* @param div divider value
*/
static inline void sdmmc_ll_set_clock_div(sdmmc_dev_t *hw, uint32_t div)
{
/**
* Set frequency to 160MHz / div
*
* n: counter resets at div_factor_n.
* l: negedge when counter equals div_factor_l.
* h: posedge when counter equals div_factor_h.
*
* We set the duty cycle to 1/2
*/
HAL_ASSERT(div > 1 && div <= 16);
int h = div - 1;
int l = div / 2 - 1;
hw->clock.div_factor_h = h;
hw->clock.div_factor_l = l;
hw->clock.div_factor_n = h;
}
/**
* @brief Deinit clock
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_deinit_clk(sdmmc_dev_t *hw)
{
hw->clock.val = 0;
}
/**
* @brief Get SDMMC clock div
*
* @param hw hardware instance address
*
* @return Divider value
*/
static inline uint32_t sdmmc_ll_get_clock_div(sdmmc_dev_t *hw)
{
return hw->clock.div_factor_h + 1;
}
/**
* @brief Initialise the din, dout, self delay phase
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_init_phase_delay(sdmmc_dev_t *hw)
{
// 180 degree phase on input and output clocks
hw->clock.phase_dout = 4;
hw->clock.phase_din = 4;
hw->clock.phase_core = 0;
}
/**
* @brief Enable card clock
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_card_clock(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
uint32_t reg_val = HAL_FORCE_READ_U32_REG_FIELD(hw->clkena, cclk_enable);
if (en) {
reg_val |= BIT(slot);
} else {
reg_val &= ~BIT(slot);
}
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkena, cclk_enable, reg_val);
}
/**
* @brief Set card clock div
*
* @param hw hardware instance address
* @param slot slot
* @param card_div divider value
*/
static inline void sdmmc_ll_set_card_clock_div(sdmmc_dev_t *hw, uint32_t slot, uint32_t card_div)
{
if (slot == 0) {
hw->clksrc.card0 = 0;
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkdiv, div0, card_div);
} else if (slot == 1) {
hw->clksrc.card1 = 1;
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkdiv, div1, card_div);
} else {
HAL_ASSERT(false);
}
}
/**
* @brief Get card clock div
*
* @param hw hardware instance address
* @param slot slot
*
* @return Divider value
*/
static inline uint32_t sdmmc_ll_get_card_clock_div(sdmmc_dev_t *hw, uint32_t slot)
{
uint32_t card_div = 0;
if (slot == 0) {
card_div = HAL_FORCE_READ_U32_REG_FIELD(hw->clkdiv, div0);
} else if (slot == 1) {
card_div = HAL_FORCE_READ_U32_REG_FIELD(hw->clkdiv, div1);
} else {
HAL_ASSERT(false);
}
return card_div;
}
/**
* @brief Disable clock when the card is in IDLE state
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_card_clock_low_power(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
uint32_t reg_val = HAL_FORCE_READ_U32_REG_FIELD(hw->clkena, cclk_low_power);
if (en) {
reg_val |= BIT(slot);
} else {
reg_val &= ~BIT(slot);
}
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkena, cclk_low_power, reg_val);
}
/**
* @brief Reset controller
*
* @note Self clear after two AHB clock cycles, needs wait done
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_reset_controller(sdmmc_dev_t *hw)
{
hw->ctrl.controller_reset = 1;
}
/**
* @brief Get if controller reset is done
*
* @param hw hardware instance address
*
* @return true: done; false: not done
*/
static inline bool sdmmc_ll_is_controller_reset_done(sdmmc_dev_t *hw)
{
return hw->ctrl.controller_reset == 0;
}
/**
* @brief Reset DMA
*
* @note Self clear after two AHB clock cycles, needs wait done
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_reset_dma(sdmmc_dev_t *hw)
{
hw->ctrl.dma_reset = 1;
}
/**
* @brief Get if dma reset is done
*
* @param hw hardware instance address
*
* @return true: done; false: not done
*/
static inline bool sdmmc_ll_is_dma_reset_done(sdmmc_dev_t *hw)
{
return hw->ctrl.dma_reset == 0;
}
/**
* @brief Reset fifo
*
* @note Self clear after reset done, needs wait done
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_reset_fifo(sdmmc_dev_t *hw)
{
hw->ctrl.fifo_reset = 1;
}
/**
* @brief Get if fifo reset is done
*
* @param hw hardware instance address
*
* @return true: done; false: not done
*/
static inline bool sdmmc_ll_is_fifo_reset_done(sdmmc_dev_t *hw)
{
return hw->ctrl.fifo_reset == 0;
}
/*---------------------------------------------------------------
MISC
---------------------------------------------------------------*/
/**
* @brief Set card data read timeout cycles
*
* @param hw hardware instance address
* @param timeout_cycles timeout cycles
*/
static inline void sdmmc_ll_set_data_timeout(sdmmc_dev_t *hw, uint32_t timeout_cycles)
{
if (timeout_cycles > 0xffffff) {
timeout_cycles = 0xffffff;
}
hw->tmout.data = timeout_cycles;
}
/**
* @brief Set response timeout cycles (in card output clocks)
*
* @param hw hardware instance address
* @param timeout_cycles timeout cycles
*/
static inline void sdmmc_ll_set_response_timeout(sdmmc_dev_t *hw, uint32_t timeout_cycles)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->tmout, response, timeout_cycles);
}
/**
* @brief Check if card is detected
*
* @param hw hardware instance address
* @param slot slot
*
* @return True for detected
*/
static inline bool sdmmc_ll_is_card_detected(sdmmc_dev_t *hw, uint32_t slot)
{
return ((hw->cdetect.cards & BIT(slot)) == 0);
}
/**
* @brief Check if card is write protected
*
* @param hw hardware instance address
* @param slot slot
*
* @return True for write protected
*/
static inline bool sdmmc_ll_is_card_write_protected(sdmmc_dev_t *hw, uint32_t slot)
{
bool is_protected = hw->wrtprt.cards & BIT(slot);
return is_protected;
}
/**
* @brief Switch between 3.3V and 1.8V mode
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable 1.8V (3.3V on disable)
*/
static inline void sdmmc_ll_enable_1v8_mode(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
//for compatibility
}
/**
* @brief Enable DDR mode
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_ddr_mode(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
uint32_t ddr_reg_val = HAL_FORCE_READ_U32_REG_FIELD(hw->uhs, ddr);
if (en) {
ddr_reg_val |= BIT(slot);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->uhs, ddr, ddr_reg_val);
hw->emmc_ddr_reg |= BIT(slot);
} else {
ddr_reg_val &= ~BIT(slot);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->uhs, ddr, ddr_reg_val);
hw->emmc_ddr_reg &= ~BIT(slot);
}
}
/**
* @brief Set data transfer length
*
* @param hw hardware instance address
* @param len length
*/
static inline void sdmmc_ll_set_data_transfer_len(sdmmc_dev_t *hw, uint32_t len)
{
hw->bytcnt = len;
}
/**
* @brief Set block size
*
* @param hw hardware instance address
* @param block_size block size
*/
static inline void sdmmc_ll_set_block_size(sdmmc_dev_t *hw, uint32_t block_size)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->blksiz, block_size, block_size);
}
/**
* @brief Set descriptor addr
*
* @param hw hardware instance address
* @param block_size block size
*/
static inline void sdmmc_ll_set_desc_addr(sdmmc_dev_t *hw, uint32_t desc_addr)
{
hw->dbaddr = (sdmmc_desc_t *)desc_addr;
}
/**
* @brief Poll demand
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_poll_demand(sdmmc_dev_t *hw)
{
hw->pldmnd = 1;
}
/**
* @brief Set command
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_set_command(sdmmc_dev_t *hw, sdmmc_hw_cmd_t cmd)
{
memcpy((void *)&hw->cmd, &cmd, sizeof(sdmmc_hw_cmd_t));
}
/**
* @brief Get if command is taken by CIU
*
* @param hw hardware instance address
*
* @return 1: is taken; 0: not taken, should not write to any command regs
*/
static inline bool sdmmc_ll_is_command_taken(sdmmc_dev_t *hw)
{
return hw->cmd.start_command == 0;
}
/**
* @brief Set command argument
*
* @param hw hardware instance address
* @param arg value indicates command argument to be passed to card
*/
static inline void sdmmc_ll_set_command_arg(sdmmc_dev_t *hw, uint32_t arg)
{
hw->cmdarg = arg;
}
/**
* @brief Get version ID
*
* @param hw hardware instance address
*
* @return version ID
*/
static inline uint32_t sdmmc_ll_get_version_id(sdmmc_dev_t *hw)
{
return hw->verid;
}
/**
* @brief Get hardware configuration info
*
* @param hw hardware instance address
*
* @return hardware configurations
*/
static inline uint32_t sdmmc_ll_get_hw_config_info(sdmmc_dev_t *hw)
{
return hw->hcon.val;
}
/**
* @brief Set card width
*
* @param hw hardware instance address
* @param slot slot ID
* @param width card width
*/
static inline void sdmmc_ll_set_card_width(sdmmc_dev_t *hw, uint32_t slot, sd_bus_width_t width)
{
uint16_t mask = 1 << slot;
uint32_t reg_val = HAL_FORCE_READ_U32_REG_FIELD(hw->ctype, card_width);
uint32_t reg_val_8 = HAL_FORCE_READ_U32_REG_FIELD(hw->ctype, card_width_8);
switch (width) {
case SD_BUS_WIDTH_1_BIT:
reg_val_8 &= ~mask;
reg_val &= ~mask;
break;
case SD_BUS_WIDTH_4_BIT:
reg_val_8 &= ~mask;
reg_val |= mask;
break;
case SD_BUS_WIDTH_8_BIT:
reg_val_8 |= mask;
break;
default:
HAL_ASSERT(false);
}
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->ctype, card_width, reg_val);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->ctype, card_width_8, reg_val_8);
}
/**
* @brief Is card data busy
*
* @param hw hardware instance address
*
* @return 1: busy; 0: idle
*/
static inline bool sdmmc_ll_is_card_data_busy(sdmmc_dev_t *hw)
{
return hw->status.data_busy == 1;
}
/*---------------------------------------------------------------
DMA
---------------------------------------------------------------*/
/**
* @brief Init DMA
* - enable dma
* - clear bus mode reg and reset all dmac internal regs
* - enable internal dmac interrupt
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_init_dma(sdmmc_dev_t *hw)
{
hw->ctrl.dma_enable = 1;
hw->bmod.val = 0;
hw->bmod.sw_reset = 1;
hw->idinten.ni = 1;
hw->idinten.ri = 1;
hw->idinten.ti = 1;
}
/**
* @brief Enable DMA
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_dma(sdmmc_dev_t *hw, bool en)
{
hw->ctrl.dma_enable = en;
hw->ctrl.use_internal_dma = en;
hw->bmod.enable = en;
hw->bmod.fb = en;
}
/**
* @brief Stop DMA
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_stop_dma(sdmmc_dev_t *hw)
{
hw->ctrl.use_internal_dma = 0;
hw->ctrl.dma_reset = 1; //here might be an issue as we don't wait the `dma_reset` to be self-cleared, check in next steps
hw->bmod.fb = 0;
hw->bmod.enable = 0;
}
/*---------------------------------------------------------------
INTR
---------------------------------------------------------------*/
/**
* @brief Get masked interrupt-status register value
*
* @param hw hardware instance address
*/
static inline uint32_t sdmmc_ll_get_intr_status(sdmmc_dev_t *hw)
{
return hw->mintsts.val;
}
/**
* @brief Enable interrupt
*
* @param hw hardware instance address
* @param mask interrupt mask
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_interrupt(sdmmc_dev_t *hw, uint32_t mask, bool en)
{
if (en) {
hw->intmask.val |= mask;
} else {
hw->intmask.val &= ~mask;
}
}
/**
* @brief Get RAW interrupt-status register value
*/
static inline uint32_t sdmmc_ll_get_interrupt_raw(sdmmc_dev_t *hw)
{
return hw->rintsts.val;
}
/**
* @brief Clear interrupt
*
* @param hw hardware instance address
* @param mask interrupt mask
*/
static inline void sdmmc_ll_clear_interrupt(sdmmc_dev_t *hw, uint32_t mask)
{
hw->rintsts.val = mask;
}
/**
* @brief Enable / disable interrupts globally
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_global_interrupt(sdmmc_dev_t *hw, bool en)
{
hw->ctrl.int_enable = (uint32_t)en;
}
/**
* @brief Enable / disable busy clear interrupt
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_busy_clear_interrupt(sdmmc_dev_t *hw, bool en)
{
hw->cardthrctl.busy_clr_int_en = en;
}
/**
* @brief Get internal dmac status register val
*/
static inline uint32_t sdmmc_ll_get_idsts_interrupt_raw(sdmmc_dev_t *hw)
{
return hw->idsts.val;
}
/**
* @brief Clear internal dmac status register events
*
* @param hw hardware instance address
* @param mask interrupt mask
*/
static inline void sdmmc_ll_clear_idsts_interrupt(sdmmc_dev_t *hw, uint32_t mask)
{
hw->idsts.val = mask;
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CLK 6
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CMD 11
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D0 7
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D1 8
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D2 9
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D3 10
#define SDIO_SLAVE_SLOT0_FUNC 0
#define SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CLK 14
#define SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CMD 15
#define SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D0 2
#define SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D1 4
#define SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D2 12
#define SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D3 13
#define SDIO_SLAVE_SLOT1_FUNC 4
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define SDMMC_SLOT0_IOMUX_PIN_NUM_CLK 6
#define SDMMC_SLOT0_IOMUX_PIN_NUM_CMD 11
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D0 7
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D1 8
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D2 9
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D3 10
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D4 16
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D5 17
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D6 5
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D7 18
#define SDMMC_SLOT0_FUNC 0
#define SDMMC_SLOT1_IOMUX_PIN_NUM_CLK 14
#define SDMMC_SLOT1_IOMUX_PIN_NUM_CMD 15
#define SDMMC_SLOT1_IOMUX_PIN_NUM_D0 2
#define SDMMC_SLOT1_IOMUX_PIN_NUM_D1 4
#define SDMMC_SLOT1_IOMUX_PIN_NUM_D2 12
#define SDMMC_SLOT1_IOMUX_PIN_NUM_D3 13
#define SDMMC_SLOT1_FUNC 4
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "hal/sdio_slave_periph.h"
#include "soc/sdio_slave_pins.h"
/**
* I/O slot of sdio slave:
* Slot 0: GPIO 6, 11, 7, 8, 9, 10.
* Slot 1: GPIO 14, 15, 2, 4, 12, 13 for CLK, CMD, D0, D1, D2, D3 respectively.
*
* @note 1: Only one peripheral for SDIO and only one slot can work at the same time.
* @note 2: Slot 0 is occupied by SPI for Flash, therefore we only use Slot 1
*/
const sdio_slave_slot_info_t sdio_slave_slot_info[1] = {
{
.clk_gpio = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CLK,
.cmd_gpio = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CMD,
.d0_gpio = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D0,
.d1_gpio = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D1,
.d2_gpio = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D2,
.d3_gpio = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D3,
.func = SDIO_SLAVE_SLOT1_FUNC,
},
};
@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "hal/sdmmc_periph.h"
#include "soc/sdmmc_pins.h"
const sdmmc_slot_info_t sdmmc_slot_info[SOC_SDMMC_NUM_SLOTS] = {
{
.width = 8,
.card_detect = HOST_CARD_DETECT_N_1_IDX,
.write_protect = HOST_CARD_WRITE_PRT_1_IDX,
.card_int = HOST_CARD_INT_N_1_IDX,
},
{
.width = 4,
.card_detect = HOST_CARD_DETECT_N_2_IDX,
.write_protect = HOST_CARD_WRITE_PRT_2_IDX,
.card_int = HOST_CARD_INT_N_2_IDX,
}
};
const sdmmc_slot_io_info_t sdmmc_slot_gpio_num[SOC_SDMMC_NUM_SLOTS] = {
{
.clk = SDMMC_SLOT0_IOMUX_PIN_NUM_CLK,
.cmd = SDMMC_SLOT0_IOMUX_PIN_NUM_CMD,
.d0 = SDMMC_SLOT0_IOMUX_PIN_NUM_D0,
.d1 = SDMMC_SLOT0_IOMUX_PIN_NUM_D1,
.d2 = SDMMC_SLOT0_IOMUX_PIN_NUM_D2,
.d3 = SDMMC_SLOT0_IOMUX_PIN_NUM_D3,
.d4 = SDMMC_SLOT0_IOMUX_PIN_NUM_D4,
.d5 = SDMMC_SLOT0_IOMUX_PIN_NUM_D5,
.d6 = SDMMC_SLOT0_IOMUX_PIN_NUM_D6,
.d7 = SDMMC_SLOT0_IOMUX_PIN_NUM_D7,
},
{
.clk = SDMMC_SLOT1_IOMUX_PIN_NUM_CLK,
.cmd = SDMMC_SLOT1_IOMUX_PIN_NUM_CMD,
.d0 = SDMMC_SLOT1_IOMUX_PIN_NUM_D0,
.d1 = SDMMC_SLOT1_IOMUX_PIN_NUM_D1,
.d2 = SDMMC_SLOT1_IOMUX_PIN_NUM_D2,
.d3 = SDMMC_SLOT1_IOMUX_PIN_NUM_D3,
.d4 = -1, //slot1 has no D4-7
.d5 = -1,
.d6 = -1,
.d7 = -1,
}
};
const sdmmc_slot_io_info_t sdmmc_slot_gpio_sig[SOC_SDMMC_NUM_SLOTS] = {
{
.clk = -1,
.cmd = -1,
.d0 = -1,
.d1 = -1,
.d2 = -1,
.d3 = -1,
.d4 = -1,
.d5 = -1,
.d6 = -1,
.d7 = -1,
},
{
.clk = -1,
.cmd = -1,
.d0 = -1,
.d1 = -1,
.d2 = -1,
.d3 = -1,
.d4 = -1,
.d5 = -1,
.d6 = -1,
.d7 = -1,
}
};
@@ -0,0 +1,551 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in hal/include/hal/readme.md
******************************************************************************/
// The LL layer for 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 <sys/queue.h>
#include <stdbool.h>
#include "hal/sdio_slave_types.h"
#include "hal/misc.h"
#include "soc/sdio_slc_struct.h"
#include "soc/sdio_slc_reg.h"
#include "soc/sdio_slc_host_struct.h"
#include "soc/sdio_slc_host_reg.h"
#include "soc/sdio_hinf_struct.h"
#include "soc/pcr_struct.h"
#ifdef __cplusplus
extern "C" {
#endif
/// Get address of the only SLC registers
#define sdio_slave_ll_get_slc(ID) (&SLC)
/// Get address of the only HOST registers
#define sdio_slave_ll_get_host(ID) (&HOST)
/// Get address of the only HINF registers
#define sdio_slave_ll_get_hinf(ID) (&HINF)
/// Get the mask of the interrupt status.
#define sdio_slave_ll_intr_status_mask (0xff | SDIO_SLC0_RX_DONE_INT_ST | SDIO_SLC0_RX_EOF_INT_ST | SDIO_SLC0_TX_DONE_INT_ST)
/*
* SLC2 DMA Desc struct, aka sdio_slave_ll_desc_t
*
* --------------------------------------------------------------
* | own | EoF | sub_sof | 1'b0 | length [13:0] | size [13:0] |
* --------------------------------------------------------------
* | buf_ptr [31:0] |
* --------------------------------------------------------------
* | next_desc_ptr [31:0] |
* --------------------------------------------------------------
*/
/* this bitfield is start from the LSB!!! */
typedef struct sdio_slave_ll_desc_s {
volatile uint32_t size : 14,
length: 14,
offset: 1, /* starting from bit28, h/w reserved 1bit, s/w use it as offset in buffer */
sosf : 1, /* start of sub-frame */
eof : 1, /* end of frame */
owner : 1; /* hw or sw */
volatile const uint8_t *buf; /* point to buffer data */
union {
volatile uint32_t empty;
STAILQ_ENTRY(sdio_slave_ll_desc_s) qe; /* pointing to the next desc */
};
} sdio_slave_ll_desc_t;
/// 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;
/**
* @brief Enable the bus clock for the SDIO slave module
*
* @param enable true to enable, false to disable
*/
static inline void sdio_slave_ll_enable_bus_clock(bool enable)
{
PCR.sdio_slave_conf.sdio_slave_clk_en = enable;
}
/**
* @brief Reset the SDIO slave module
*/
static inline void sdio_slave_ll_reset_register(void)
{
PCR.sdio_slave_conf.sdio_slave_rst_en = 1;
PCR.sdio_slave_conf.sdio_slave_rst_en = 0;
}
/**
* Initialize the hardware.
*
* @param slc Address of the SLC registers
*/
static inline void sdio_slave_ll_init(slc_dev_t *slc)
{
slc->slc_slc0int_ena.val = 0;
slc->slc_conf0.slc0_rx_auto_wrback = 1;
slc->slc_conf0.slc0_token_auto_clr = 0;
slc->slc_conf0.slc0_rx_loop_test = 0;
slc->slc_conf0.slc0_tx_loop_test = 0;
slc->slc_conf1.slc0_rx_stitch_en = 0;
slc->slc_conf1.slc0_tx_stitch_en = 0;
slc->slc_conf1.slc0_len_auto_clr = 0;
slc->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 CCCR, SDIO and Physical Layer version
*/
static inline void sdio_slave_ll_init_version(hinf_dev_t *hinf)
{
hinf->cfg_data1.sdio_ver = 0x232;
}
/**
* 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.highspeed_enable = 1;
} else {
hinf->cfg_data1.highspeed_enable = 0;
}
}
/**
* 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->slc_conf0.slc0_rx_rst = 1;
slc->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 sdio_slave_ll_desc_t *desc)
{
slc->slc_slc0rx_link_addr.slc0_rxlink_addr = (uint32_t)desc;
slc->slc_slc0rx_link.slc0_rxlink_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->slc_slc0_len_conf.val = FIELD_TO_VALUE2(SDIO_SLC0_LEN_WDATA, len) | FIELD_TO_VALUE2(SDIO_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.hostslchost_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->slc_slc0int_ena.slc0_rx_done_int_ena = (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->slc_slc0int_clr.slc0_rx_done_int_clr = 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->slc_slc0int_raw.slc0_rx_done_int_raw;
}
/**
* Stop the sending DMA.
*
* @param slc Address of the SLC registers
*/
static inline void sdio_slave_ll_send_stop(slc_dev_t *slc)
{
slc->slc_slc0rx_link.slc0_rxlink_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->slc_slc0int_ena.slc0_rx_eof_int_ena = (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->slc_slc0int_clr.slc0_rx_eof_int_clr = 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->slc_slc0int_st.slc0_rx_eof_int_st != 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->slc0host_int_clr.slc0_rx_new_packet_int_clr = 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->slc_slc0int_ena.slc0_tx_done_int_ena = (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, sdio_slave_ll_desc_t *desc)
{
slc->slc_slc0tx_link_addr.slc0_txlink_addr = (uint32_t)desc;
slc->slc_slc0tx_link.slc0_txlink_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->slc_slc0token1.val = FIELD_TO_VALUE2(SDIO_SLC0_TOKEN1_WDATA, 1) | FIELD_TO_VALUE2(SDIO_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->slc_slc0token1.val = FIELD_TO_VALUE2(SDIO_SLC0_TOKEN1_WDATA, 0) | FIELD_TO_VALUE2(SDIO_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->slc_slc0int_raw.slc0_tx_done_int_raw != 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->slc_slc0int_clr.slc0_tx_done_int_clr = 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->slc_slc0tx_link.slc0_txlink_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->slc_conf0.slc0_tx_rst = 1;
slc->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->slc_slc0tx_link.slc0_txlink_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 (sdio_slave_hostint_t)host->slc0host_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->slc0host_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->slc0host_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
HAL_FORCE_MODIFY_U32_REG_FIELD(slc->slc_slcintvec_tohost, slc0_tohost_intvec, *mask);
}
/**
* Enable some of the slave interrupts (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->slc_slc0int_ena.val = (slc->slc_slc0int_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 = (sdio_slave_ll_slvint_t)(slc->slc_slc0int_st.val & 0xff);
*out_slv_int = slv_int;
slc->slc_slc0int_clr.val = slv_int;
}
/**
* Get the address of the interrupt status register.
*
* @param slc Address of the SLC registers
* @return Address of the interrupt status register
*/
static inline volatile void* sdio_slave_ll_get_intr_status_reg(slc_dev_t *slc)
{
return &slc->slc_slc0int_st.val;
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CMD 10
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CLK 9
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D0 8
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D1 7
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D2 14
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D3 13
#define SDIO_SLAVE_SLOT0_FUNC 0
@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "hal/sdio_slave_periph.h"
#include "soc/sdio_slave_pins.h"
const sdio_slave_slot_info_t sdio_slave_slot_info[1] = {
{
.clk_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CLK,
.cmd_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CMD,
.d0_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D0,
.d1_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D1,
.d2_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D2,
.d3_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D3,
.func = SDIO_SLAVE_SLOT0_FUNC,
},
};
@@ -0,0 +1,551 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in hal/include/hal/readme.md
******************************************************************************/
// The LL layer for 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 <sys/queue.h>
#include <stdbool.h>
#include "hal/sdio_slave_types.h"
#include "hal/misc.h"
#include "soc/sdio_slc_struct.h"
#include "soc/sdio_slc_reg.h"
#include "soc/sdio_slc_host_struct.h"
#include "soc/sdio_slc_host_reg.h"
#include "soc/sdio_hinf_struct.h"
#include "soc/pcr_struct.h"
#ifdef __cplusplus
extern "C" {
#endif
/// Get address of the only SLC registers
#define sdio_slave_ll_get_slc(ID) (&SLC)
/// Get address of the only HOST registers
#define sdio_slave_ll_get_host(ID) (&HOST)
/// Get address of the only HINF registers
#define sdio_slave_ll_get_hinf(ID) (&HINF)
/// Get the mask of the interrupt status.
#define sdio_slave_ll_intr_status_mask (0xff | SDIO_SLC0_RX_DONE_INT_ST | SDIO_SLC0_RX_EOF_INT_ST | SDIO_SLC0_TX_DONE_INT_ST)
/*
* SLC2 DMA Desc struct, aka sdio_slave_ll_desc_t
*
* --------------------------------------------------------------
* | own | EoF | sub_sof | 1'b0 | length [13:0] | size [13:0] |
* --------------------------------------------------------------
* | buf_ptr [31:0] |
* --------------------------------------------------------------
* | next_desc_ptr [31:0] |
* --------------------------------------------------------------
*/
/* this bitfield is start from the LSB!!! */
typedef struct sdio_slave_ll_desc_s {
volatile uint32_t size : 14,
length: 14,
offset: 1, /* starting from bit28, h/w reserved 1bit, s/w use it as offset in buffer */
sosf : 1, /* start of sub-frame */
eof : 1, /* end of frame */
owner : 1; /* hw or sw */
volatile const uint8_t *buf; /* point to buffer data */
union {
volatile uint32_t empty;
STAILQ_ENTRY(sdio_slave_ll_desc_s) qe; /* pointing to the next desc */
};
} sdio_slave_ll_desc_t;
/// 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;
/**
* @brief Enable the bus clock for the SDIO slave module
*
* @param enable true to enable, false to disable
*/
static inline void sdio_slave_ll_enable_bus_clock(bool enable)
{
PCR.sdio_slave_conf.sdio_slave_clk_en = enable;
}
/**
* @brief Reset the SDIO slave module
*/
static inline void sdio_slave_ll_reset_register(void)
{
PCR.sdio_slave_conf.sdio_slave_rst_en = 1;
PCR.sdio_slave_conf.sdio_slave_rst_en = 0;
}
/**
* Initialize the hardware.
*
* @param slc Address of the SLC registers
*/
static inline void sdio_slave_ll_init(slc_dev_t *slc)
{
slc->slc0int_ena.val = 0;
slc->slcconf0.slc0_rx_auto_wrback = 1;
slc->slcconf0.slc0_token_auto_clr = 0;
slc->slcconf0.slc0_rx_loop_test = 0;
slc->slcconf0.slc0_tx_loop_test = 0;
slc->slcconf1.slc0_rx_stitch_en = 0;
slc->slcconf1.slc0_tx_stitch_en = 0;
slc->slcconf1.slc0_len_auto_clr = 0;
slc->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 CCCR, SDIO and Physical Layer version
*/
static inline void sdio_slave_ll_init_version(hinf_dev_t *hinf)
{
hinf->cfg_data1.sdio_ver = 0x232;
}
/**
* 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.highspeed_enable = 1;
} else {
hinf->cfg_data1.highspeed_enable = 0;
}
}
/**
* 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->slcconf0.slc0_rx_rst = 1;
slc->slcconf0.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 sdio_slave_ll_desc_t *desc)
{
slc->slc0rx_link_addr.slc0_rxlink_addr = (uint32_t)desc;
slc->slc0rx_link.slc0_rxlink_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(SDIO_SLC0_LEN_WDATA, len) | FIELD_TO_VALUE2(SDIO_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.hostslchost_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->slc0int_ena.slc0_rx_done_int_ena = (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->slc0int_clr.slc0_rx_done_int_clr = 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->slc0int_raw.slc0_rx_done_int_raw;
}
/**
* Stop the sending DMA.
*
* @param slc Address of the SLC registers
*/
static inline void sdio_slave_ll_send_stop(slc_dev_t *slc)
{
slc->slc0rx_link.slc0_rxlink_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->slc0int_ena.slc0_rx_eof_int_ena = (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->slc0int_clr.slc0_rx_eof_int_clr = 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->slc0int_st.slc0_rx_eof_int_st != 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->slc0host_int_clr.slc0_rx_new_packet_int_clr = 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->slc0int_ena.slc0_tx_done_int_ena = (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, sdio_slave_ll_desc_t *desc)
{
slc->slc0tx_link_addr.slc0_txlink_addr = (uint32_t)desc;
slc->slc0tx_link.slc0_txlink_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->slc0token1.val = FIELD_TO_VALUE2(SDIO_SLC0_TOKEN1_WDATA, 1) | FIELD_TO_VALUE2(SDIO_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->slc0token1.val = FIELD_TO_VALUE2(SDIO_SLC0_TOKEN1_WDATA, 0) | FIELD_TO_VALUE2(SDIO_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->slc0int_raw.slc0_tx_done_int_raw != 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->slc0int_clr.slc0_tx_done_int_clr = 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->slc0tx_link.slc0_txlink_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->slcconf0.slc0_tx_rst = 1;
slc->slcconf0.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->slc0tx_link.slc0_txlink_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 (sdio_slave_hostint_t)host->slc0host_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->slc0host_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->slc0host_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
HAL_FORCE_MODIFY_U32_REG_FIELD(slc->slcintvec_tohost, slc0_tohost_intvec, *mask);
}
/**
* Enable some of the slave interrupts (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->slc0int_ena.val = (slc->slc0int_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 = (sdio_slave_ll_slvint_t)(slc->slc0int_st.val & 0xff);
*out_slv_int = slv_int;
slc->slc0int_clr.val = slv_int;
}
/**
* Get the address of the interrupt status register.
*
* @param slc Address of the SLC registers
* @return Address of the interrupt status register
*/
static inline volatile void* sdio_slave_ll_get_intr_status_reg(slc_dev_t *slc)
{
return &slc->slc0int_st.val;
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CMD 18
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CLK 19
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D0 20
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D1 21
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D2 22
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D3 23
#define SDIO_SLAVE_SLOT0_FUNC 0
@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "hal/sdio_slave_periph.h"
#include "soc/sdio_slave_pins.h"
const sdio_slave_slot_info_t sdio_slave_slot_info[1] = {
{
.clk_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CLK,
.cmd_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CMD,
.d0_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D0,
.d1_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D1,
.d2_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D2,
.d3_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D3,
.func = SDIO_SLAVE_SLOT0_FUNC,
},
};
@@ -0,0 +1,551 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in hal/include/hal/readme.md
******************************************************************************/
// The LL layer for 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 <sys/queue.h>
#include <stdbool.h>
#include "hal/sdio_slave_types.h"
#include "hal/misc.h"
#include "soc/sdio_slc_struct.h"
#include "soc/sdio_slc_reg.h"
#include "soc/sdio_slc_host_struct.h"
#include "soc/sdio_slc_host_reg.h"
#include "soc/sdio_hinf_struct.h"
#include "soc/pcr_struct.h"
#ifdef __cplusplus
extern "C" {
#endif
/// Get address of the only SLC registers
#define sdio_slave_ll_get_slc(ID) (&SLC)
/// Get address of the only HOST registers
#define sdio_slave_ll_get_host(ID) (&HOST)
/// Get address of the only HINF registers
#define sdio_slave_ll_get_hinf(ID) (&HINF)
/// Get the mask of the interrupt status.
#define sdio_slave_ll_intr_status_mask (0xff | SDIO_SLC0_RX_DONE_INT_ST | SDIO_SLC0_RX_EOF_INT_ST | SDIO_SLC0_TX_DONE_INT_ST)
/*
* SLC2 DMA Desc struct, aka sdio_slave_ll_desc_t
*
* --------------------------------------------------------------
* | own | EoF | sub_sof | 1'b0 | length [13:0] | size [13:0] |
* --------------------------------------------------------------
* | buf_ptr [31:0] |
* --------------------------------------------------------------
* | next_desc_ptr [31:0] |
* --------------------------------------------------------------
*/
/* this bitfield is start from the LSB!!! */
typedef struct sdio_slave_ll_desc_s {
volatile uint32_t size : 14,
length: 14,
offset: 1, /* starting from bit28, h/w reserved 1bit, s/w use it as offset in buffer */
sosf : 1, /* start of sub-frame */
eof : 1, /* end of frame */
owner : 1; /* hw or sw */
volatile const uint8_t *buf; /* point to buffer data */
union {
volatile uint32_t empty;
STAILQ_ENTRY(sdio_slave_ll_desc_s) qe; /* pointing to the next desc */
};
} sdio_slave_ll_desc_t;
/// 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;
/**
* @brief Enable the bus clock for the SDIO slave module
*
* @param enable true to enable, false to disable
*/
static inline void sdio_slave_ll_enable_bus_clock(bool enable)
{
PCR.sdio_slave_conf.sdio_slave_clk_en = enable;
}
/**
* @brief Reset the SDIO slave module
*/
static inline void sdio_slave_ll_reset_register(void)
{
PCR.sdio_slave_conf.sdio_slave_rst_en = 1;
PCR.sdio_slave_conf.sdio_slave_rst_en = 0;
}
/**
* Initialize the hardware.
*
* @param slc Address of the SLC registers
*/
static inline void sdio_slave_ll_init(slc_dev_t *slc)
{
slc->slc_slc0int_ena.val = 0;
slc->slc_conf0.slc0_rx_auto_wrback = 1;
slc->slc_conf0.slc0_token_auto_clr = 0;
slc->slc_conf0.slc0_rx_loop_test = 0;
slc->slc_conf0.slc0_tx_loop_test = 0;
slc->slc_conf1.slc0_rx_stitch_en = 0;
slc->slc_conf1.slc0_tx_stitch_en = 0;
slc->slc_conf1.slc0_len_auto_clr = 0;
slc->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->slc_host_conf.slchost_frc_sdio20 = 0x1f;
host->slc_host_conf.slchost_frc_sdio11 = 0;
host->slc_host_conf.slchost_frc_pos_samp = 0x1f;
host->slc_host_conf.slchost_frc_neg_samp = 0;
break;
case SDIO_SLAVE_TIMING_PSEND_NSAMPLE:
host->slc_host_conf.slchost_frc_sdio20 = 0x1f;
host->slc_host_conf.slchost_frc_sdio11 = 0;
host->slc_host_conf.slchost_frc_pos_samp = 0;
host->slc_host_conf.slchost_frc_neg_samp = 0x1f;
break;
case SDIO_SLAVE_TIMING_NSEND_PSAMPLE:
host->slc_host_conf.slchost_frc_sdio20 = 0;
host->slc_host_conf.slchost_frc_sdio11 = 0x1f;
host->slc_host_conf.slchost_frc_pos_samp = 0x1f;
host->slc_host_conf.slchost_frc_neg_samp = 0;
break;
case SDIO_SLAVE_TIMING_NSEND_NSAMPLE:
host->slc_host_conf.slchost_frc_sdio20 = 0;
host->slc_host_conf.slchost_frc_sdio11 = 0x1f;
host->slc_host_conf.slchost_frc_pos_samp = 0;
host->slc_host_conf.slchost_frc_neg_samp = 0x1f;
break;
}
}
/**
* Set the CCCR, SDIO and Physical Layer version
*/
static inline void sdio_slave_ll_init_version(hinf_dev_t *hinf)
{
hinf->cfg_data1.sdio_ver = 0x232;
}
/**
* 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.highspeed_enable = 1;
} else {
hinf->cfg_data1.highspeed_enable = 0;
}
}
/**
* 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->slc_conf0.slc0_rx_rst = 1;
slc->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 sdio_slave_ll_desc_t *desc)
{
slc->slc_slc0rx_link_addr.slc0_rxlink_addr = (uint32_t)desc;
slc->slc_slc0rx_link.slc0_rxlink_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->slc_slc0_len_conf.val = FIELD_TO_VALUE2(SDIO_SLC0_LEN_WDATA, len) | FIELD_TO_VALUE2(SDIO_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->slc_host_pkt_len.slchost_hostslchost_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->slc_slc0int_ena.slc0_rx_done_int_ena = (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->slc_slc0int_clr.slc0_rx_done_int_clr = 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->slc_slc0int_raw.slc0_rx_done_int_raw;
}
/**
* Stop the sending DMA.
*
* @param slc Address of the SLC registers
*/
static inline void sdio_slave_ll_send_stop(slc_dev_t *slc)
{
slc->slc_slc0rx_link.slc0_rxlink_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->slc_slc0int_ena.slc0_rx_eof_int_ena = (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->slc_slc0int_clr.slc0_rx_eof_int_clr = 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->slc_slc0int_st.slc0_rx_eof_int_st != 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->slc_host_slc0host_int_clr.slchost_slc0_rx_new_packet_int_clr = 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->slc_slc0int_ena.slc0_tx_done_int_ena = (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, sdio_slave_ll_desc_t *desc)
{
slc->slc_slc0tx_link_addr.slc0_txlink_addr = (uint32_t)desc;
slc->slc_slc0tx_link.slc0_txlink_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->slc_slc0token1.val = FIELD_TO_VALUE2(SDIO_SLC0_TOKEN1_WDATA, 1) | FIELD_TO_VALUE2(SDIO_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->slc_slc0token1.val = FIELD_TO_VALUE2(SDIO_SLC0_TOKEN1_WDATA, 0) | FIELD_TO_VALUE2(SDIO_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->slc_slc0int_raw.slc0_tx_done_int_raw != 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->slc_slc0int_clr.slc0_tx_done_int_clr = 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->slc_slc0tx_link.slc0_txlink_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->slc_conf0.slc0_tx_rst = 1;
slc->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->slc_slc0tx_link.slc0_txlink_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->slc_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 (sdio_slave_hostint_t)host->slc_host_slc0host_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->slc_host_slc0host_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->slc_host_slc0host_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
HAL_FORCE_MODIFY_U32_REG_FIELD(slc->slc_slcintvec_tohost, slc0_tohost_intvec, *mask);
}
/**
* Enable some of the slave interrupts (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->slc_slc0int_ena.val = (slc->slc_slc0int_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 = (sdio_slave_ll_slvint_t)(slc->slc_slc0int_st.val & 0xff);
*out_slv_int = slv_int;
slc->slc_slc0int_clr.val = slv_int;
}
/**
* Get the address of the interrupt status register.
*
* @param slc Address of the SLC registers
* @return Address of the interrupt status register
*/
static inline volatile void* sdio_slave_ll_get_intr_status_reg(slc_dev_t *slc)
{
return &slc->slc_slc0int_st.val;
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CMD 25
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CLK 26
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D0 27
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D1 28
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D2 22
#define SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D3 23
#define SDIO_SLAVE_SLOT0_FUNC 0
@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "hal/sdio_slave_periph.h"
#include "soc/sdio_slave_pins.h"
const sdio_slave_slot_info_t sdio_slave_slot_info[1] = {
{
.clk_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CLK,
.cmd_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_CMD,
.d0_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D0,
.d1_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D1,
.d2_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D2,
.d3_gpio = SDIO_SLAVE_SLOT0_IOMUX_PIN_NUM_D3,
.func = SDIO_SLAVE_SLOT0_FUNC,
},
};
@@ -0,0 +1,924 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The ll is not public api, don't use in application code.
* See readme.md in hal/include/hal/readme.md
******************************************************************************/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "esp_bit_defs.h"
#include "hal/assert.h"
#include "hal/misc.h"
#include "hal/sd_types.h"
#include "soc/clk_tree_defs.h"
#include "soc/sdmmc_struct.h"
#include "soc/sdmmc_reg.h"
#include "soc/hp_sys_clkrst_struct.h"
#include "soc/lp_clkrst_struct.h"
#include "soc/pmu_reg.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SDMMC_LL_GET_HW(id) (((id) == 0) ? (&SDMMC) : NULL)
#define SDMMC_LL_EVENT_IO_SLOT1 (1<<17)
#define SDMMC_LL_EVENT_IO_SLOT0 (1<<16)
#define SDMMC_LL_EVENT_EBE (1<<15)
#define SDMMC_LL_EVENT_ACD (1<<14)
#define SDMMC_LL_EVENT_SBE (1<<13)
#define SDMMC_LL_EVENT_BCI (1<<13)
#define SDMMC_LL_EVENT_HLE (1<<12)
#define SDMMC_LL_EVENT_FRUN (1<<11)
#define SDMMC_LL_EVENT_HTO (1<<10)
#define SDMMC_LL_EVENT_DTO (1<<9)
#define SDMMC_LL_EVENT_RTO (1<<8)
#define SDMMC_LL_EVENT_DCRC (1<<7)
#define SDMMC_LL_EVENT_RCRC (1<<6)
#define SDMMC_LL_EVENT_RXDR (1<<5)
#define SDMMC_LL_EVENT_TXDR (1<<4)
#define SDMMC_LL_EVENT_DATA_OVER (1<<3)
#define SDMMC_LL_EVENT_CMD_DONE (1<<2)
#define SDMMC_LL_EVENT_RESP_ERR (1<<1)
#define SDMMC_LL_EVENT_CD (1<<0)
/* Default disabled interrupts (on init):
* SDMMC_LL_EVENT_RXDR,
* SDMMC_LL_EVENT_TXDR,
* SDMMC_LL_EVENT_BCI,
* SDMMC_LL_EVENT_ACD,
* SDMMC_LL_EVENT_IO_SLOT1,
* SDMMC_LL_EVENT_IO_SLOT0
*/
// Default enabled interrupts (sdio is enabled only when use):
#define SDMMC_LL_EVENT_DEFAULT \
(SDMMC_LL_EVENT_CD | SDMMC_LL_EVENT_RESP_ERR | SDMMC_LL_EVENT_CMD_DONE | SDMMC_LL_EVENT_DATA_OVER | \
SDMMC_LL_EVENT_RCRC | SDMMC_LL_EVENT_DCRC | SDMMC_LL_EVENT_RTO | SDMMC_LL_EVENT_DTO | SDMMC_LL_EVENT_HTO | \
SDMMC_LL_EVENT_HLE | \
SDMMC_LL_EVENT_SBE | \
SDMMC_LL_EVENT_EBE)
#define SDMMC_LL_SD_EVENT_MASK \
(SDMMC_LL_EVENT_CD | SDMMC_LL_EVENT_RESP_ERR | SDMMC_LL_EVENT_CMD_DONE | SDMMC_LL_EVENT_DATA_OVER | \
SDMMC_LL_EVENT_TXDR | SDMMC_LL_EVENT_RXDR |\
SDMMC_LL_EVENT_RCRC | SDMMC_LL_EVENT_DCRC | SDMMC_LL_EVENT_RTO | SDMMC_LL_EVENT_DTO | SDMMC_LL_EVENT_HTO | \
SDMMC_LL_EVENT_FRUN | SDMMC_LL_EVENT_HLE |\
SDMMC_LL_EVENT_SBE | SDMMC_LL_EVENT_ACD |\
SDMMC_LL_EVENT_EBE)
// DMA interrupts (idsts register)
#define SDMMC_LL_EVENT_DMA_TI SDMMC_IDMAC_INTMASK_TI
#define SDMMC_LL_EVENT_DMA_RI SDMMC_IDMAC_INTMASK_RI
#define SDMMC_LL_EVENT_DMA_NI SDMMC_IDMAC_INTMASK_NI
#define SDMMC_LL_EVENT_DMA_MASK 0x1f //NI and AI will be indicated by TI/RI and FBE/DU respectively
/**
* SDMMC capabilities
*/
#define SDMMC_LL_SLOT_SUPPORT_GPIO_MATRIX(SLOT_ID) ((SLOT_ID == 0) ? 0 : 1)
#define SDMMC_LL_IOMUX_FUNC 0
#define SDMMC_LL_HOST_CTLR_NUMS 1U
#define SDMMC_LL_DELAY_MAX_NUMS_LS 4
#define SDMMC_LL_DELAY_PHASE_SUPPORTED 1
#define SDMMC_LL_SDIO_PLL_SUPPORTED 1
/**
* SDMMC delay phase
*/
typedef enum {
SDMMC_LL_DELAY_PHASE_0,
SDMMC_LL_DELAY_PHASE_1,
SDMMC_LL_DELAY_PHASE_2,
SDMMC_LL_DELAY_PHASE_3,
SDMMC_LL_DELAY_PHASE_4,
SDMMC_LL_DELAY_PHASE_5,
SDMMC_LL_DELAY_PHASE_6,
SDMMC_LL_DELAY_PHASE_7,
} sdmmc_ll_delay_phase_t;
/**
* SDMMC delayline
*/
typedef enum {
SDMMC_LL_DELAY_LINE_0,
SDMMC_LL_DELAY_LINE_1,
SDMMC_LL_DELAY_LINE_2,
SDMMC_LL_DELAY_LINE_3,
SDMMC_LL_DELAY_LINE_4,
SDMMC_LL_DELAY_LINE_5,
SDMMC_LL_DELAY_LINE_6,
SDMMC_LL_DELAY_LINE_7,
} sdmmc_ll_delay_line_t;
/**
* SDMMC speed mode
*/
typedef enum {
SDMMC_LL_SPEED_MODE_LS,
SDMMC_LL_SPEED_MODE_HS,
} sdmmc_ll_speed_mode_t;
/*---------------------------------------------------------------
Clock & Reset
---------------------------------------------------------------*/
/**
* @brief Enable the bus clock for SDMMC module
*
* @param group_id Group ID
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_bus_clock(int group_id, bool en)
{
(void)group_id;
HP_SYS_CLKRST.soc_clk_ctrl1.reg_sdmmc_sys_clk_en = en;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* @brief Reset the SDMMC module
*
* @param group_id Group ID
*/
static inline void sdmmc_ll_reset_register(int group_id)
{
(void)group_id;
LP_AON_CLKRST.hp_sdmmc_emac_rst_ctrl.rst_en_sdmmc = 1;
LP_AON_CLKRST.hp_sdmmc_emac_rst_ctrl.rst_en_sdmmc = 0;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_reset_register(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_reset_register(__VA_ARGS__); \
} while(0)
/**
* @brief Enable the bus clock for SDIO PLL
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_sdio_pll(sdmmc_dev_t *hw, bool en)
{
if (en) {
REG_SET_BIT(PMU_RF_PWC_REG, PMU_SDIO_PLL_XPD);
REG_SET_BIT(PMU_IMM_HP_CK_POWER_REG, PMU_TIE_HIGH_XPD_SDIOPLL_I2C);
REG_SET_BIT(PMU_IMM_HP_CK_POWER_REG, PMU_TIE_HIGH_XPD_SDIOPLL);
REG_SET_BIT(PMU_IMM_HP_CK_POWER_REG, PMU_TIE_HIGH_GLOBAL_SDIOPLL_ICG);
LP_AON_CLKRST.hp_clk_ctrl.hp_sdio_pll0_clk_en = 1;
LP_AON_CLKRST.hp_clk_ctrl.hp_sdio_pll1_clk_en = 1;
LP_AON_CLKRST.hp_clk_ctrl.hp_sdio_pll2_clk_en = 1;
} else {
REG_CLR_BIT(PMU_RF_PWC_REG, PMU_SDIO_PLL_XPD);
REG_CLR_BIT(PMU_IMM_HP_CK_POWER_REG, PMU_TIE_HIGH_XPD_SDIOPLL_I2C);
REG_CLR_BIT(PMU_IMM_HP_CK_POWER_REG, PMU_TIE_HIGH_XPD_SDIOPLL);
REG_CLR_BIT(PMU_IMM_HP_CK_POWER_REG, PMU_TIE_HIGH_GLOBAL_SDIOPLL_ICG);
LP_AON_CLKRST.hp_clk_ctrl.hp_sdio_pll0_clk_en = 0;
LP_AON_CLKRST.hp_clk_ctrl.hp_sdio_pll0_clk_en = 0;
LP_AON_CLKRST.hp_clk_ctrl.hp_sdio_pll2_clk_en = 0;
}
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_enable_sdio_pll(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_enable_sdio_pll(__VA_ARGS__); \
} while(0)
/**
* @brief Select SDMMC clock source
*
* @param hw hardware instance address
* @param clk_src clock source, see valid sources in type `soc_periph_sdmmc_clk_src_t`
*/
static inline void sdmmc_ll_select_clk_source(sdmmc_dev_t *hw, soc_periph_sdmmc_clk_src_t clk_src)
{
uint32_t clk_val = 0;
switch (clk_src) {
case SDMMC_CLK_SRC_PLL160M:
clk_val = 0;
break;
case SDMMC_CLK_SRC_SDIO_200M:
clk_val = 1;
break;
default:
HAL_ASSERT(false);
break;
}
HP_SYS_CLKRST.peri_clk_ctrl01.reg_sdio_ls_clk_src_sel = clk_val;
HP_SYS_CLKRST.peri_clk_ctrl01.reg_sdio_ls_clk_en = true;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_select_clk_source(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_select_clk_source(__VA_ARGS__); \
} while(0)
/**
* @brief Set SDMMC clock div
*
* @param hw hardware instance address
* @param div divider value
*/
static inline void sdmmc_ll_set_clock_div(sdmmc_dev_t *hw, uint32_t div)
{
if (div > 1) {
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_h = div / 2 - 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_n = div - 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_l = div - 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_cfg_update = 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_cfg_update = 0;
} else {
HP_SYS_CLKRST.peri_clk_ctrl01.reg_sdio_hs_mode = 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_h = 0;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_n = 0;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_l = 0;
}
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_set_clock_div(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_set_clock_div(__VA_ARGS__); \
} while(0)
/**
* @brief Deinit clock
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_deinit_clk(sdmmc_dev_t *hw)
{
hw->clk_edge_sel.val = 0;
}
/**
* @brief Get SDMMC clock div
*
* @param hw hardware instance address
*
* @return Divider value
*/
static inline uint32_t sdmmc_ll_get_clock_div(sdmmc_dev_t *hw)
{
uint32_t div = 0;
if (HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_h == 0 &&
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_n == 0 &&
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_l == 0) {
div = 1;
} else {
div = HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_l + 1;
}
return div;
}
/**
* @brief Initialise the din, dout, self delay phase
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_init_phase_delay(sdmmc_dev_t *hw)
{
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_drv_clk_en = 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_sam_clk_en = 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_slf_clk_en = 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_drv_clk_edge_sel = 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_sam_clk_edge_sel = 0;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_slf_clk_edge_sel = 0;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_cfg_update = 1;
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_clk_edge_cfg_update = 0;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_init_phase_delay(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_init_phase_delay(__VA_ARGS__); \
} while(0)
/**
* @brief Set SDMMC din delay phase
*
* @param hw hardware instance address
* @param phase delay phase
* @param mode speed mode
*/
static inline void sdmmc_ll_set_din_delay_phase(sdmmc_dev_t *hw, sdmmc_ll_delay_phase_t phase, sdmmc_ll_speed_mode_t mode)
{
if (mode == SDMMC_LL_SPEED_MODE_LS) {
switch (phase) {
case SDMMC_LL_DELAY_PHASE_1:
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_sam_clk_edge_sel = 0x1;
break;
case SDMMC_LL_DELAY_PHASE_2:
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_sam_clk_edge_sel = 0x2;
break;
case SDMMC_LL_DELAY_PHASE_3:
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_sam_clk_edge_sel = 0x3;
break;
default:
HP_SYS_CLKRST.peri_clk_ctrl02.reg_sdio_ls_sam_clk_edge_sel = 0x0;
break;
}
} else {
SDMMC.dll_clk_conf.dll_cclk_in_sam_phase = (phase << 3);
}
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_set_din_delay_phase(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_set_din_delay_phase(__VA_ARGS__); \
} while(0)
/**
* @brief Set SDMMC dout delay phase
*
* @param hw hardware instance address
* @param phase delay phase
* @param mode speed mode
*/
static inline void sdmmc_ll_set_dout_delay_phase(sdmmc_dev_t *hw, sdmmc_ll_delay_phase_t phase, sdmmc_ll_speed_mode_t mode)
{
if (mode == SDMMC_LL_SPEED_MODE_HS) {
SDMMC.dll_clk_conf.dll_cclk_in_drv_phase = (phase << 3);
}
}
/**
* @brief Set SDMMC din delay line
*
* @param hw hardware instance address
* @param phase delay line
* @param mode speed mode
*/
static inline void sdmmc_ll_set_din_delay_line(sdmmc_dev_t *hw, sdmmc_ll_delay_line_t phase, sdmmc_ll_speed_mode_t mode)
{
SDMMC.dll_clk_conf.dll_cclk_in_sam_phase &= ~0x7;
if (mode == SDMMC_LL_SPEED_MODE_HS) {
SDMMC.dll_clk_conf.dll_cclk_in_sam_phase |= phase;
} else {
HAL_ASSERT(false);
}
}
/**
* @brief Set SDMMC dout delay line
*
* @param hw hardware instance address
* @param phase delay line
* @param mode speed mode
*/
static inline void sdmmc_ll_set_dout_delay_line(sdmmc_dev_t *hw, sdmmc_ll_delay_line_t phase, sdmmc_ll_speed_mode_t mode)
{
SDMMC.dll_clk_conf.dll_cclk_in_drv_phase &= ~0x7;
if (mode == SDMMC_LL_SPEED_MODE_HS) {
SDMMC.dll_clk_conf.dll_cclk_in_drv_phase |= phase;
} else {
HAL_ASSERT(false);
}
}
/**
* @brief Enable card clock
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_card_clock(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
if (en) {
hw->clkena.cclk_enable |= BIT(slot);
} else {
hw->clkena.cclk_enable &= ~BIT(slot);
}
}
/**
* @brief Set card clock div
*
* @param hw hardware instance address
* @param slot slot
* @param card_div divider value
*/
static inline void sdmmc_ll_set_card_clock_div(sdmmc_dev_t *hw, uint32_t slot, uint32_t card_div)
{
if (slot == 0) {
hw->clksrc.card0 = 0;
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkdiv, clk_divider0, card_div);
} else if (slot == 1) {
hw->clksrc.card1 = 1;
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkdiv, clk_divider1, card_div);
} else {
HAL_ASSERT(false);
}
}
/**
* @brief Get card clock div
*
* @param hw hardware instance address
* @param slot slot
*
* @return Divider value
*/
static inline uint32_t sdmmc_ll_get_card_clock_div(sdmmc_dev_t *hw, uint32_t slot)
{
uint32_t card_div = 0;
if (slot == 0) {
card_div = HAL_FORCE_READ_U32_REG_FIELD(hw->clkdiv, clk_divider0);
} else if (slot == 1) {
card_div = HAL_FORCE_READ_U32_REG_FIELD(hw->clkdiv, clk_divider1);
} else {
HAL_ASSERT(false);
}
return card_div;
}
/**
* @brief Disable clock when the card is in IDLE state
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_card_clock_low_power(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
if (en) {
hw->clkena.lp_enable |= BIT(slot);
} else {
hw->clkena.lp_enable &= ~BIT(slot);
}
}
/**
* @brief Reset controller
*
* @note Self clear after two AHB clock cycles, needs wait done
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_reset_controller(sdmmc_dev_t *hw)
{
hw->ctrl.controller_reset = 1;
}
/**
* @brief Get if controller reset is done
*
* @param hw hardware instance address
*
* @return true: done; false: not done
*/
static inline bool sdmmc_ll_is_controller_reset_done(sdmmc_dev_t *hw)
{
return hw->ctrl.controller_reset == 0;
}
/**
* @brief Reset DMA
*
* @note Self clear after two AHB clock cycles, needs wait done
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_reset_dma(sdmmc_dev_t *hw)
{
hw->ctrl.dma_reset = 1;
}
/**
* @brief Get if dma reset is done
*
* @param hw hardware instance address
*
* @return true: done; false: not done
*/
static inline bool sdmmc_ll_is_dma_reset_done(sdmmc_dev_t *hw)
{
return hw->ctrl.dma_reset == 0;
}
/**
* @brief Reset fifo
*
* @note Self clear after reset done, needs wait done
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_reset_fifo(sdmmc_dev_t *hw)
{
hw->ctrl.fifo_reset = 1;
}
/**
* @brief Get if fifo reset is done
*
* @param hw hardware instance address
*
* @return true: done; false: not done
*/
static inline bool sdmmc_ll_is_fifo_reset_done(sdmmc_dev_t *hw)
{
return hw->ctrl.fifo_reset == 0;
}
/*---------------------------------------------------------------
MISC
---------------------------------------------------------------*/
/**
* @brief Set card data read timeout cycles
*
* @param hw hardware instance address
* @param timeout_cycles timeout cycles
*/
static inline void sdmmc_ll_set_data_timeout(sdmmc_dev_t *hw, uint32_t timeout_cycles)
{
if (timeout_cycles > 0xffffff) {
timeout_cycles = 0xffffff;
}
hw->tmout.data_timeout = timeout_cycles;
}
/**
* @brief Set response timeout cycles (in card output clocks)
*
* @param hw hardware instance address
* @param timeout_cycles timeout cycles
*/
static inline void sdmmc_ll_set_response_timeout(sdmmc_dev_t *hw, uint32_t timeout_cycles)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->tmout, response_timeout, timeout_cycles);
}
/**
* @brief Check if card is detected
*
* @param hw hardware instance address
* @param slot slot
*
* @return True for detected
*/
static inline bool sdmmc_ll_is_card_detected(sdmmc_dev_t *hw, uint32_t slot)
{
return ((hw->cdetect.card_detect_n & BIT(slot)) == 0);
}
/**
* @brief Check if card is write protected
*
* @param hw hardware instance address
* @param slot slot
*
* @return True for write protected
*/
static inline bool sdmmc_ll_is_card_write_protected(sdmmc_dev_t *hw, uint32_t slot)
{
bool is_protected = hw->wrtprt.write_protect & BIT(slot);
return is_protected;
}
/**
* @brief Switch between 3.3V and 1.8V mode
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable 1.8V (3.3V on disable)
*/
static inline void sdmmc_ll_enable_1v8_mode(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
if (en) {
hw->uhs.volt |= BIT(slot);
} else {
hw->uhs.volt &= ~BIT(slot);
}
}
/**
* @brief Enable DDR mode
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_ddr_mode(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
if (en) {
hw->uhs.ddr |= BIT(slot);
hw->emmcddr.halfstartbit_reg |= BIT(slot);
} else {
hw->uhs.ddr &= ~BIT(slot);
hw->emmcddr.halfstartbit_reg &= ~BIT(slot);
}
}
/**
* @brief Set data transfer length
*
* @param hw hardware instance address
* @param len length
*/
static inline void sdmmc_ll_set_data_transfer_len(sdmmc_dev_t *hw, uint32_t len)
{
hw->bytcnt.byte_count = len;
}
/**
* @brief Set block size
*
* @param hw hardware instance address
* @param block_size block size
*/
static inline void sdmmc_ll_set_block_size(sdmmc_dev_t *hw, uint32_t block_size)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->blksiz, block_size, block_size);
}
/**
* @brief Set descriptor addr
*
* @param hw hardware instance address
* @param block_size block size
*/
static inline void sdmmc_ll_set_desc_addr(sdmmc_dev_t *hw, uint32_t desc_addr)
{
hw->dbaddr.dbaddr_reg = desc_addr;
}
/**
* @brief Poll demand
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_poll_demand(sdmmc_dev_t *hw)
{
hw->pldmnd.pldmnd_pd = 1;
}
/**
* @brief Set command
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_set_command(sdmmc_dev_t *hw, sdmmc_hw_cmd_t cmd)
{
memcpy((void *)&hw->cmd, &cmd, sizeof(sdmmc_hw_cmd_t));
}
/**
* @brief Get if command is taken by CIU
*
* @param hw hardware instance address
*
* @return 1: is taken; 0: not taken, should not write to any command regs
*/
static inline bool sdmmc_ll_is_command_taken(sdmmc_dev_t *hw)
{
return hw->cmd.start_command == 0;
}
/**
* @brief Set command argument
*
* @param hw hardware instance address
* @param arg value indicates command argument to be passed to card
*/
static inline void sdmmc_ll_set_command_arg(sdmmc_dev_t *hw, uint32_t arg)
{
hw->cmdarg = arg;
}
/**
* @brief Get version ID
*
* @param hw hardware instance address
*
* @return version ID
*/
static inline uint32_t sdmmc_ll_get_version_id(sdmmc_dev_t *hw)
{
return hw->verid;
}
/**
* @brief Get hardware configuration info
*
* @param hw hardware instance address
*
* @return hardware configurations
*/
static inline uint32_t sdmmc_ll_get_hw_config_info(sdmmc_dev_t *hw)
{
return hw->hcon.val;
}
/**
* @brief Set card width
*
* @param hw hardware instance address
* @param slot slot ID
* @param width card width
*/
static inline void sdmmc_ll_set_card_width(sdmmc_dev_t *hw, uint32_t slot, sd_bus_width_t width)
{
uint16_t mask = 1 << slot;
switch (width) {
case SD_BUS_WIDTH_1_BIT:
hw->ctype.card_width_8 &= ~mask;
hw->ctype.card_width &= ~mask;
break;
case SD_BUS_WIDTH_4_BIT:
hw->ctype.card_width_8 &= ~mask;
hw->ctype.card_width |= mask;
break;
case SD_BUS_WIDTH_8_BIT:
hw->ctype.card_width_8 |= mask;
break;
default:
HAL_ASSERT(false);
}
}
/**
* @brief Is card data busy
*
* @param hw hardware instance address
*
* @return 1: busy; 0: idle
*/
static inline bool sdmmc_ll_is_card_data_busy(sdmmc_dev_t *hw)
{
return hw->status.data_busy == 1;
}
/*---------------------------------------------------------------
DMA
---------------------------------------------------------------*/
/**
* @brief Init DMA
* - enable dma
* - clear bus mode reg and reset all dmac internal regs
* - enable internal dmac interrupt
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_init_dma(sdmmc_dev_t *hw)
{
hw->ctrl.dma_enable = 1;
hw->bmod.val = 0;
hw->bmod.sw_reset = 1;
hw->idinten.ni = 1;
hw->idinten.ri = 1;
hw->idinten.ti = 1;
}
/**
* @brief Enable DMA
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_dma(sdmmc_dev_t *hw, bool en)
{
hw->ctrl.dma_enable = en;
hw->ctrl.use_internal_dma = en;
hw->bmod.enable = en;
hw->bmod.fb = en;
}
/**
* @brief Stop DMA
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_stop_dma(sdmmc_dev_t *hw)
{
hw->ctrl.use_internal_dma = 0;
hw->ctrl.dma_reset = 1; //here might be an issue as we don't wait the `dma_reset` to be self-cleared, check in next steps
hw->bmod.fb = 0;
hw->bmod.enable = 0;
}
/*---------------------------------------------------------------
INTR
---------------------------------------------------------------*/
/**
* @brief Get masked interrupt-status register value
*
* @param hw hardware instance address
*/
static inline uint32_t sdmmc_ll_get_intr_status(sdmmc_dev_t *hw)
{
return hw->mintsts.val;
}
/**
* @brief Enable interrupt
*
* @param hw hardware instance address
* @param mask interrupt mask
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_interrupt(sdmmc_dev_t *hw, uint32_t mask, bool en)
{
if (en) {
hw->intmask.val |= mask;
} else {
hw->intmask.val &= ~mask;
}
}
/**
* @brief Get RAW interrupt-status register value
*/
static inline uint32_t sdmmc_ll_get_interrupt_raw(sdmmc_dev_t *hw)
{
return hw->rintsts.val;
}
/**
* @brief Clear interrupt
*
* @param hw hardware instance address
* @param mask interrupt mask
*/
static inline void sdmmc_ll_clear_interrupt(sdmmc_dev_t *hw, uint32_t mask)
{
hw->rintsts.val = mask;
}
/**
* @brief Enable / disable interrupts globally
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_global_interrupt(sdmmc_dev_t *hw, bool en)
{
hw->ctrl.int_enable = (uint32_t)en;
}
/**
* @brief Enable / disable busy clear interrupt
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_busy_clear_interrupt(sdmmc_dev_t *hw, bool en)
{
hw->cardthrctl.busy_clr_int_en = en;
}
/**
* @brief Get internal dmac status register val
*/
static inline uint32_t sdmmc_ll_get_idsts_interrupt_raw(sdmmc_dev_t *hw)
{
return hw->idsts.val;
}
/**
* @brief Clear internal dmac status register events
*
* @param hw hardware instance address
* @param mask interrupt mask
*/
static inline void sdmmc_ll_clear_idsts_interrupt(sdmmc_dev_t *hw, uint32_t mask)
{
hw->idsts.val = mask;
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define SDMMC_SLOT0_IOMUX_PIN_NUM_CLK 43
#define SDMMC_SLOT0_IOMUX_PIN_NUM_CMD 44
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D0 39
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D1 40
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D2 41
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D3 42
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D4 45
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D5 46
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D6 47
#define SDMMC_SLOT0_IOMUX_PIN_NUM_D7 48
#define SDMMC_SLOT0_FUNC 0
// SLOT1 doesn't go through IOMUX
@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "hal/sdmmc_periph.h"
#include "soc/sdmmc_pins.h"
const sdmmc_slot_info_t sdmmc_slot_info[SOC_SDMMC_NUM_SLOTS] = {
{
.width = 8,
.card_detect = SD_CARD_DETECT_N_1_PAD_IN_IDX,
.write_protect = SD_CARD_WRITE_PRT_1_PAD_IN_IDX,
.card_int = SD_CARD_INT_N_1_PAD_IN_IDX,
},
{
.width = 8,
.card_detect = SD_CARD_DETECT_N_2_PAD_IN_IDX,
.write_protect = SD_CARD_WRITE_PRT_2_PAD_IN_IDX,
.card_int = SD_CARD_INT_N_2_PAD_IN_IDX,
}
};
const sdmmc_slot_io_info_t sdmmc_slot_gpio_num[SOC_SDMMC_NUM_SLOTS] = {
{
.clk = SDMMC_SLOT0_IOMUX_PIN_NUM_CLK,
.cmd = SDMMC_SLOT0_IOMUX_PIN_NUM_CMD,
.d0 = SDMMC_SLOT0_IOMUX_PIN_NUM_D0,
.d1 = SDMMC_SLOT0_IOMUX_PIN_NUM_D1,
.d2 = SDMMC_SLOT0_IOMUX_PIN_NUM_D2,
.d3 = SDMMC_SLOT0_IOMUX_PIN_NUM_D3,
.d4 = SDMMC_SLOT0_IOMUX_PIN_NUM_D4,
.d5 = SDMMC_SLOT0_IOMUX_PIN_NUM_D5,
.d6 = SDMMC_SLOT0_IOMUX_PIN_NUM_D6,
.d7 = SDMMC_SLOT0_IOMUX_PIN_NUM_D7,
},
{
.clk = -1,
.cmd = -1,
.d0 = -1,
.d1 = -1,
.d2 = -1,
.d3 = -1,
.d4 = -1,
.d5 = -1,
.d6 = -1,
.d7 = -1,
}
};
const sdmmc_slot_io_info_t sdmmc_slot_gpio_sig[SOC_SDMMC_NUM_SLOTS] = {
{
.clk = -1,
.cmd = -1,
.d0 = -1,
.d1 = -1,
.d2 = -1,
.d3 = -1,
.d4 = -1,
.d5 = -1,
.d6 = -1,
.d7 = -1,
},
{
.clk = SD_CARD_CCLK_2_PAD_OUT_IDX,
.cmd = SD_CARD_CCMD_2_PAD_OUT_IDX,
.d0 = SD_CARD_CDATA0_2_PAD_OUT_IDX,
.d1 = SD_CARD_CDATA1_2_PAD_OUT_IDX,
.d2 = SD_CARD_CDATA2_2_PAD_OUT_IDX,
.d3 = SD_CARD_CDATA3_2_PAD_OUT_IDX,
.d4 = SD_CARD_CDATA4_2_PAD_OUT_IDX,
.d5 = SD_CARD_CDATA5_2_PAD_OUT_IDX,
.d6 = SD_CARD_CDATA6_2_PAD_OUT_IDX,
.d7 = SD_CARD_CDATA7_2_PAD_OUT_IDX,
},
};
@@ -0,0 +1,802 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The ll is not public api, don't use in application code.
* See readme.md in hal/include/hal/readme.md
******************************************************************************/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "esp_bit_defs.h"
#include "hal/assert.h"
#include "hal/misc.h"
#include "hal/sd_types.h"
#include "soc/clk_tree_defs.h"
#include "soc/sdmmc_struct.h"
#include "soc/sdmmc_reg.h"
#include "soc/system_struct.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SDMMC_LL_GET_HW(id) (((id) == 0) ? (&SDMMC) : NULL)
#define SDMMC_LL_EVENT_IO_SLOT1 (1<<17)
#define SDMMC_LL_EVENT_IO_SLOT0 (1<<16)
#define SDMMC_LL_EVENT_EBE (1<<15)
#define SDMMC_LL_EVENT_ACD (1<<14)
#define SDMMC_LL_EVENT_SBE (1<<13)
#define SDMMC_LL_EVENT_BCI (1<<13)
#define SDMMC_LL_EVENT_HLE (1<<12)
#define SDMMC_LL_EVENT_FRUN (1<<11)
#define SDMMC_LL_EVENT_HTO (1<<10)
#define SDMMC_LL_EVENT_DTO (1<<9)
#define SDMMC_LL_EVENT_RTO (1<<8)
#define SDMMC_LL_EVENT_DCRC (1<<7)
#define SDMMC_LL_EVENT_RCRC (1<<6)
#define SDMMC_LL_EVENT_RXDR (1<<5)
#define SDMMC_LL_EVENT_TXDR (1<<4)
#define SDMMC_LL_EVENT_DATA_OVER (1<<3)
#define SDMMC_LL_EVENT_CMD_DONE (1<<2)
#define SDMMC_LL_EVENT_RESP_ERR (1<<1)
#define SDMMC_LL_EVENT_CD (1<<0)
/* Default disabled interrupts (on init):
* SDMMC_LL_EVENT_RXDR,
* SDMMC_LL_EVENT_TXDR,
* SDMMC_LL_EVENT_BCI,
* SDMMC_LL_EVENT_ACD,
* SDMMC_LL_EVENT_IO_SLOT1,
* SDMMC_LL_EVENT_IO_SLOT0
*/
// Default enabled interrupts (sdio is enabled only when use):
#define SDMMC_LL_EVENT_DEFAULT \
(SDMMC_LL_EVENT_CD | SDMMC_LL_EVENT_RESP_ERR | SDMMC_LL_EVENT_CMD_DONE | SDMMC_LL_EVENT_DATA_OVER | \
SDMMC_LL_EVENT_RCRC | SDMMC_LL_EVENT_DCRC | SDMMC_LL_EVENT_RTO | SDMMC_LL_EVENT_DTO | SDMMC_LL_EVENT_HTO | \
SDMMC_LL_EVENT_HLE | \
SDMMC_LL_EVENT_SBE | \
SDMMC_LL_EVENT_EBE)
#define SDMMC_LL_SD_EVENT_MASK \
(SDMMC_LL_EVENT_CD | SDMMC_LL_EVENT_RESP_ERR | SDMMC_LL_EVENT_CMD_DONE | SDMMC_LL_EVENT_DATA_OVER | \
SDMMC_LL_EVENT_TXDR | SDMMC_LL_EVENT_RXDR |\
SDMMC_LL_EVENT_RCRC | SDMMC_LL_EVENT_DCRC | SDMMC_LL_EVENT_RTO | SDMMC_LL_EVENT_DTO | SDMMC_LL_EVENT_HTO | \
SDMMC_LL_EVENT_FRUN | SDMMC_LL_EVENT_HLE |\
SDMMC_LL_EVENT_SBE | SDMMC_LL_EVENT_ACD |\
SDMMC_LL_EVENT_EBE)
// DMA interrupts (idsts register)
#define SDMMC_LL_EVENT_DMA_TI SDMMC_IDMAC_INTMASK_TI
#define SDMMC_LL_EVENT_DMA_RI SDMMC_IDMAC_INTMASK_RI
#define SDMMC_LL_EVENT_DMA_NI SDMMC_IDMAC_INTMASK_NI
#define SDMMC_LL_EVENT_DMA_MASK 0x1f //NI and AI will be indicated by TI/RI and FBE/DU respectively
/**
* SDMMC capabilities
*/
#define SDMMC_LL_SLOT_SUPPORT_GPIO_MATRIX(SLOT_ID) 1
#define SDMMC_LL_IOMUX_FUNC -1
#define SDMMC_LL_HOST_CTLR_NUMS 1U
#define SDMMC_LL_DELAY_MAX_NUMS_LS 4
#define SDMMC_LL_DELAY_PHASE_SUPPORTED 1
/**
* SDMMC delay phase
*/
typedef enum {
SDMMC_LL_DELAY_PHASE_0,
SDMMC_LL_DELAY_PHASE_1,
SDMMC_LL_DELAY_PHASE_2,
SDMMC_LL_DELAY_PHASE_3,
} sdmmc_ll_delay_phase_t;
/**
* SDMMC speed mode
*/
typedef enum {
SDMMC_LL_SPEED_MODE_LS,
SDMMC_LL_SPEED_MODE_HS,
} sdmmc_ll_speed_mode_t;
/*---------------------------------------------------------------
Clock & Reset
---------------------------------------------------------------*/
/**
* @brief Enable the bus clock for SDMMC module
*
* @param group_id Group ID
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_bus_clock(int group_id, bool en)
{
(void)group_id;
SYSTEM.perip_clk_en1.sdio_host_clk_en = en;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_enable_bus_clock(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_enable_bus_clock(__VA_ARGS__); \
} while(0)
/**
* @brief Reset the SDMMC module
*
* @param group_id Group ID
*/
static inline void sdmmc_ll_reset_register(int group_id)
{
(void)group_id;
SYSTEM.perip_rst_en1.sdio_host_rst = 1;
SYSTEM.perip_rst_en1.sdio_host_rst = 0;
}
/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define sdmmc_ll_reset_register(...) do { \
(void)__DECLARE_RCC_ATOMIC_ENV; \
sdmmc_ll_reset_register(__VA_ARGS__); \
} while(0)
/**
* @brief Select SDMMC clock source
*
* @param hw hardware instance address
* @param clk_src clock source, see valid sources in type `soc_periph_sdmmc_clk_src_t`
*/
static inline void sdmmc_ll_select_clk_source(sdmmc_dev_t *hw, soc_periph_sdmmc_clk_src_t clk_src)
{
uint32_t clk_val = 0;
switch (clk_src) {
case SDMMC_CLK_SRC_PLL160M:
clk_val = 1;
break;
case SDMMC_CLK_SRC_XTAL:
clk_val = 0;
break;
default:
HAL_ASSERT(false);
break;
}
hw->clock.clk_sel = clk_val;
}
/**
* @brief Set SDMMC clock div
*
* @param hw hardware instance address
* @param div divider value
*/
static inline void sdmmc_ll_set_clock_div(sdmmc_dev_t *hw, uint32_t div)
{
/**
* Set frequency to 160MHz / div
*
* n: counter resets at div_factor_n.
* l: negedge when counter equals div_factor_l.
* h: posedge when counter equals div_factor_h.
*
* We set the duty cycle to 1/2
*/
HAL_ASSERT(div > 1 && div <= 16);
int l = div - 1;
int h = div / 2 - 1;
hw->clock.div_factor_h = h;
hw->clock.div_factor_l = l;
hw->clock.div_factor_n = l;
}
/**
* @brief Deinit clock
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_deinit_clk(sdmmc_dev_t *hw)
{
hw->clock.val = 0;
}
/**
* @brief Get SDMMC clock div
*
* @param hw hardware instance address
*
* @return Divider value
*/
static inline uint32_t sdmmc_ll_get_clock_div(sdmmc_dev_t *hw)
{
return hw->clock.div_factor_l + 1;
}
/**
* @brief Initialise the din, dout, self delay phase
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_init_phase_delay(sdmmc_dev_t *hw)
{
hw->clock.phase_core = 0;
/* 90 deg. delay for cclk_out to satisfy large hold time for SDR12 (up to 25MHz) and SDR25 (up to 50MHz) modes.
* Whether this delayed clock will be used depends on use_hold_reg bit in CMD structure,
* determined when sending out the command.
*/
hw->clock.phase_dout = 1;
hw->clock.phase_din = 0;
}
/**
* @brief Set SDMMC din delay phase
*
* @param hw hardware instance address
* @param phase delay phase
* @param mode speed mode
*/
static inline void sdmmc_ll_set_din_delay_phase(sdmmc_dev_t *hw, sdmmc_ll_delay_phase_t phase, sdmmc_ll_speed_mode_t mode)
{
(void)mode;
switch (phase) {
case SDMMC_LL_DELAY_PHASE_1:
hw->clock.phase_din = 0x1;
break;
case SDMMC_LL_DELAY_PHASE_2:
hw->clock.phase_din = 0x4;
break;
case SDMMC_LL_DELAY_PHASE_3:
hw->clock.phase_din = 0x6;
break;
default:
hw->clock.phase_din = 0x0;
break;
}
}
/**
* @brief Set SDMMC dout delay phase
*
* @param hw hardware instance address
* @param phase delay phase
* @param mode speed mode
*/
static inline void sdmmc_ll_set_dout_delay_phase(sdmmc_dev_t *hw, sdmmc_ll_delay_phase_t phase, sdmmc_ll_speed_mode_t mode)
{
//for compatibility
}
/**
* @brief Enable card clock
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_card_clock(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
uint32_t reg_val = HAL_FORCE_READ_U32_REG_FIELD(hw->clkena, cclk_enable);
if (en) {
reg_val |= BIT(slot);
} else {
reg_val &= ~BIT(slot);
}
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkena, cclk_enable, reg_val);
}
/**
* @brief Set card clock div
*
* @param hw hardware instance address
* @param slot slot
* @param card_div divider value
*/
static inline void sdmmc_ll_set_card_clock_div(sdmmc_dev_t *hw, uint32_t slot, uint32_t card_div)
{
if (slot == 0) {
hw->clksrc.card0 = 0;
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkdiv, div0, card_div);
} else if (slot == 1) {
hw->clksrc.card1 = 1;
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkdiv, div1, card_div);
} else {
HAL_ASSERT(false);
}
}
/**
* @brief Get card clock div
*
* @param hw hardware instance address
* @param slot slot
*
* @return Divider value
*/
static inline uint32_t sdmmc_ll_get_card_clock_div(sdmmc_dev_t *hw, uint32_t slot)
{
uint32_t card_div = 0;
if (slot == 0) {
card_div = HAL_FORCE_READ_U32_REG_FIELD(hw->clkdiv, div0);
} else if (slot == 1) {
card_div = HAL_FORCE_READ_U32_REG_FIELD(hw->clkdiv, div1);
} else {
HAL_ASSERT(false);
}
return card_div;
}
/**
* @brief Disable clock when the card is in IDLE state
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_card_clock_low_power(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
uint32_t reg_val = HAL_FORCE_READ_U32_REG_FIELD(hw->clkena, cclk_low_power);
if (en) {
reg_val |= BIT(slot);
} else {
reg_val &= ~BIT(slot);
}
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->clkena, cclk_low_power, reg_val);
}
/**
* @brief Reset controller
*
* @note Self clear after two AHB clock cycles, needs wait done
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_reset_controller(sdmmc_dev_t *hw)
{
hw->ctrl.controller_reset = 1;
}
/**
* @brief Get if controller reset is done
*
* @param hw hardware instance address
*
* @return true: done; false: not done
*/
static inline bool sdmmc_ll_is_controller_reset_done(sdmmc_dev_t *hw)
{
return hw->ctrl.controller_reset == 0;
}
/**
* @brief Reset DMA
*
* @note Self clear after two AHB clock cycles, needs wait done
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_reset_dma(sdmmc_dev_t *hw)
{
hw->ctrl.dma_reset = 1;
}
/**
* @brief Get if dma reset is done
*
* @param hw hardware instance address
*
* @return true: done; false: not done
*/
static inline bool sdmmc_ll_is_dma_reset_done(sdmmc_dev_t *hw)
{
return hw->ctrl.dma_reset == 0;
}
/**
* @brief Reset fifo
*
* @note Self clear after reset done, needs wait done
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_reset_fifo(sdmmc_dev_t *hw)
{
hw->ctrl.fifo_reset = 1;
}
/**
* @brief Get if fifo reset is done
*
* @param hw hardware instance address
*
* @return true: done; false: not done
*/
static inline bool sdmmc_ll_is_fifo_reset_done(sdmmc_dev_t *hw)
{
return hw->ctrl.fifo_reset == 0;
}
/*---------------------------------------------------------------
MISC
---------------------------------------------------------------*/
/**
* @brief Set card data read timeout cycles
*
* @param hw hardware instance address
* @param timeout_cycles timeout cycles
*/
static inline void sdmmc_ll_set_data_timeout(sdmmc_dev_t *hw, uint32_t timeout_cycles)
{
if (timeout_cycles > 0xffffff) {
timeout_cycles = 0xffffff;
}
hw->tmout.data = timeout_cycles;
}
/**
* @brief Set response timeout cycles (in card output clocks)
*
* @param hw hardware instance address
* @param timeout_cycles timeout cycles
*/
static inline void sdmmc_ll_set_response_timeout(sdmmc_dev_t *hw, uint32_t timeout_cycles)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->tmout, response, timeout_cycles);
}
/**
* @brief Check if card is detected
*
* @param hw hardware instance address
* @param slot slot
*
* @return True for detected
*/
static inline bool sdmmc_ll_is_card_detected(sdmmc_dev_t *hw, uint32_t slot)
{
return ((hw->cdetect.cards & BIT(slot)) == 0);
}
/**
* @brief Check if card is write protected
*
* @param hw hardware instance address
* @param slot slot
*
* @return True for write protected
*/
static inline bool sdmmc_ll_is_card_write_protected(sdmmc_dev_t *hw, uint32_t slot)
{
bool is_protected = hw->wrtprt.cards & BIT(slot);
return is_protected;
}
/**
* @brief Switch between 3.3V and 1.8V mode
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable 1.8V (3.3V on disable)
*/
static inline void sdmmc_ll_enable_1v8_mode(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
//for compatibility
}
/**
* @brief Enable DDR mode
*
* @param hw hardware instance address
* @param slot slot
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_ddr_mode(sdmmc_dev_t *hw, uint32_t slot, bool en)
{
uint32_t ddr_reg_val = HAL_FORCE_READ_U32_REG_FIELD(hw->uhs, ddr);
if (en) {
ddr_reg_val |= BIT(slot);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->uhs, ddr, ddr_reg_val);
hw->emmc_ddr_reg |= BIT(slot);
} else {
ddr_reg_val &= ~BIT(slot);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->uhs, ddr, ddr_reg_val);
hw->emmc_ddr_reg &= ~BIT(slot);
}
}
/**
* @brief Set data transfer length
*
* @param hw hardware instance address
* @param len length
*/
static inline void sdmmc_ll_set_data_transfer_len(sdmmc_dev_t *hw, uint32_t len)
{
hw->bytcnt = len;
}
/**
* @brief Set block size
*
* @param hw hardware instance address
* @param block_size block size
*/
static inline void sdmmc_ll_set_block_size(sdmmc_dev_t *hw, uint32_t block_size)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->blksiz, block_size, block_size);
}
/**
* @brief Set descriptor addr
*
* @param hw hardware instance address
* @param block_size block size
*/
static inline void sdmmc_ll_set_desc_addr(sdmmc_dev_t *hw, uint32_t desc_addr)
{
hw->dbaddr = (sdmmc_desc_t *)desc_addr;
}
/**
* @brief Poll demand
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_poll_demand(sdmmc_dev_t *hw)
{
hw->pldmnd = 1;
}
/**
* @brief Set command
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_set_command(sdmmc_dev_t *hw, sdmmc_hw_cmd_t cmd)
{
memcpy((void *)&hw->cmd, &cmd, sizeof(sdmmc_hw_cmd_t));
}
/**
* @brief Get if command is taken by CIU
*
* @param hw hardware instance address
*
* @return 1: is taken; 0: not taken, should not write to any command regs
*/
static inline bool sdmmc_ll_is_command_taken(sdmmc_dev_t *hw)
{
return hw->cmd.start_command == 0;
}
/**
* @brief Set command argument
*
* @param hw hardware instance address
* @param arg value indicates command argument to be passed to card
*/
static inline void sdmmc_ll_set_command_arg(sdmmc_dev_t *hw, uint32_t arg)
{
hw->cmdarg = arg;
}
/**
* @brief Get version ID
*
* @param hw hardware instance address
*
* @return version ID
*/
static inline uint32_t sdmmc_ll_get_version_id(sdmmc_dev_t *hw)
{
return hw->verid;
}
/**
* @brief Get hardware configuration info
*
* @param hw hardware instance address
*
* @return hardware configurations
*/
static inline uint32_t sdmmc_ll_get_hw_config_info(sdmmc_dev_t *hw)
{
return hw->hcon.val;
}
/**
* @brief Set card width
*
* @param hw hardware instance address
* @param slot slot ID
* @param width card width
*/
static inline void sdmmc_ll_set_card_width(sdmmc_dev_t *hw, uint32_t slot, sd_bus_width_t width)
{
uint16_t mask = 1 << slot;
uint32_t reg_val = HAL_FORCE_READ_U32_REG_FIELD(hw->ctype, card_width);
uint32_t reg_val_8 = HAL_FORCE_READ_U32_REG_FIELD(hw->ctype, card_width_8);
switch (width) {
case SD_BUS_WIDTH_1_BIT:
reg_val_8 &= ~mask;
reg_val &= ~mask;
break;
case SD_BUS_WIDTH_4_BIT:
reg_val_8 &= ~mask;
reg_val |= mask;
break;
case SD_BUS_WIDTH_8_BIT:
reg_val_8 |= mask;
break;
default:
HAL_ASSERT(false);
}
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->ctype, card_width, reg_val);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->ctype, card_width_8, reg_val_8);
}
/**
* @brief Is card data busy
*
* @param hw hardware instance address
*
* @return 1: busy; 0: idle
*/
static inline bool sdmmc_ll_is_card_data_busy(sdmmc_dev_t *hw)
{
return hw->status.data_busy == 1;
}
/*---------------------------------------------------------------
DMA
---------------------------------------------------------------*/
/**
* @brief Init DMA
* - enable dma
* - clear bus mode reg and reset all dmac internal regs
* - enable internal dmac interrupt
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_init_dma(sdmmc_dev_t *hw)
{
hw->ctrl.dma_enable = 1;
hw->bmod.val = 0;
hw->bmod.sw_reset = 1;
hw->idinten.ni = 1;
hw->idinten.ri = 1;
hw->idinten.ti = 1;
}
/**
* @brief Enable DMA
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_dma(sdmmc_dev_t *hw, bool en)
{
hw->ctrl.dma_enable = en;
hw->ctrl.use_internal_dma = en;
hw->bmod.enable = en;
hw->bmod.fb = en;
}
/**
* @brief Stop DMA
*
* @param hw hardware instance address
*/
static inline void sdmmc_ll_stop_dma(sdmmc_dev_t *hw)
{
hw->ctrl.use_internal_dma = 0;
hw->ctrl.dma_reset = 1; //here might be an issue as we don't wait the `dma_reset` to be self-cleared, check in next steps
hw->bmod.fb = 0;
hw->bmod.enable = 0;
}
/*---------------------------------------------------------------
INTR
---------------------------------------------------------------*/
/**
* @brief Get masked interrupt-status register value
*
* @param hw hardware instance address
*/
static inline uint32_t sdmmc_ll_get_intr_status(sdmmc_dev_t *hw)
{
return hw->mintsts.val;
}
/**
* @brief Enable interrupt
*
* @param hw hardware instance address
* @param mask interrupt mask
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_interrupt(sdmmc_dev_t *hw, uint32_t mask, bool en)
{
if (en) {
hw->intmask.val |= mask;
} else {
hw->intmask.val &= ~mask;
}
}
/**
* @brief Get RAW interrupt-status register value
*/
static inline uint32_t sdmmc_ll_get_interrupt_raw(sdmmc_dev_t *hw)
{
return hw->rintsts.val;
}
/**
* @brief Clear interrupt
*
* @param hw hardware instance address
* @param mask interrupt mask
*/
static inline void sdmmc_ll_clear_interrupt(sdmmc_dev_t *hw, uint32_t mask)
{
hw->rintsts.val = mask;
}
/**
* @brief Enable / disable interrupts globally
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_global_interrupt(sdmmc_dev_t *hw, bool en)
{
hw->ctrl.int_enable = (uint32_t)en;
}
/**
* @brief Enable / disable busy clear interrupt
*
* @param hw hardware instance address
* @param en enable / disable
*/
static inline void sdmmc_ll_enable_busy_clear_interrupt(sdmmc_dev_t *hw, bool en)
{
hw->cardthrctl.busy_clr_int_en = en;
}
/**
* @brief Get internal dmac status register val
*/
static inline uint32_t sdmmc_ll_get_idsts_interrupt_raw(sdmmc_dev_t *hw)
{
return hw->idsts.val;
}
/**
* @brief Clear internal dmac status register events
*
* @param hw hardware instance address
* @param mask interrupt mask
*/
static inline void sdmmc_ll_clear_idsts_interrupt(sdmmc_dev_t *hw, uint32_t mask)
{
hw->idsts.val = mask;
}
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/* SDMMC pins on ESP32-S3 are configurable through GPIO matrix.
* This file is kept for compatibility only.
*/
@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "hal/sdmmc_periph.h"
#include "soc/sdmmc_pins.h"
const sdmmc_slot_info_t sdmmc_slot_info[SOC_SDMMC_NUM_SLOTS] = {
{
.width = 8,
.card_detect = SDHOST_CARD_DETECT_N_1_IDX,
.write_protect = SDHOST_CARD_WRITE_PRT_1_IDX,
.card_int = SDHOST_CARD_INT_N_1_IDX,
},
{
.width = 8,
.card_detect = SDHOST_CARD_DETECT_N_2_IDX,
.write_protect = SDHOST_CARD_WRITE_PRT_2_IDX,
.card_int = SDHOST_CARD_INT_N_2_IDX,
}
};
const sdmmc_slot_io_info_t sdmmc_slot_gpio_num[SOC_SDMMC_NUM_SLOTS] = {
{
.clk = -1,
.cmd = -1,
.d0 = -1,
.d1 = -1,
.d2 = -1,
.d3 = -1,
.d4 = -1,
.d5 = -1,
.d6 = -1,
.d7 = -1,
},
{
.clk = -1,
.cmd = -1,
.d0 = -1,
.d1 = -1,
.d2 = -1,
.d3 = -1,
.d4 = -1,
.d5 = -1,
.d6 = -1,
.d7 = -1,
}
};
const sdmmc_slot_io_info_t sdmmc_slot_gpio_sig[SOC_SDMMC_NUM_SLOTS] = {
{
.clk = SDHOST_CCLK_OUT_1_IDX,
.cmd = SDHOST_CCMD_OUT_1_IDX,
.d0 = SDHOST_CDATA_OUT_10_IDX,
.d1 = SDHOST_CDATA_OUT_11_IDX,
.d2 = SDHOST_CDATA_OUT_12_IDX,
.d3 = SDHOST_CDATA_OUT_13_IDX,
.d4 = SDHOST_CDATA_OUT_14_IDX,
.d5 = SDHOST_CDATA_OUT_15_IDX,
.d6 = SDHOST_CDATA_OUT_16_IDX,
.d7 = SDHOST_CDATA_OUT_17_IDX,
},
{
.clk = SDHOST_CCLK_OUT_2_IDX,
.cmd = SDHOST_CCMD_OUT_2_IDX,
.d0 = SDHOST_CDATA_OUT_20_IDX,
.d1 = SDHOST_CDATA_OUT_21_IDX,
.d2 = SDHOST_CDATA_OUT_22_IDX,
.d3 = SDHOST_CDATA_OUT_23_IDX,
.d4 = SDHOST_CDATA_OUT_24_IDX,
.d5 = SDHOST_CDATA_OUT_25_IDX,
.d6 = SDHOST_CDATA_OUT_26_IDX,
.d7 = SDHOST_CDATA_OUT_27_IDX,
}
};
@@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "soc/soc_caps.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief SD bus width
*/
typedef enum {
SD_BUS_WIDTH_1_BIT = 1, ///< 1 bit
SD_BUS_WIDTH_4_BIT = 4, ///< 4 bit
SD_BUS_WIDTH_8_BIT = 8, ///< 8 bit
} sd_bus_width_t;
/**
* @brief SD mode
*/
typedef enum {
SD_MODE_NORMAL, ///< Normal SD mode
SD_MODE_UHS1, ///< UHS-I SD mode
} sd_mode_t;
/**
* @brief SD sampling mode
*/
typedef enum {
SD_SAMPLING_MODE_SDR = 1, ///< Single data rate mode
SD_SAMPLING_MODE_DDR, ///< Double data rate mode
} sd_sampling_mode_t;
/**
* @brief SD/MMC Host clock timing delay phases
*
* This will only take effect when the host works in
* - SDMMC_FREQ_HIGHSPEED
* - SDMMC_FREQ_52M
* - SDR50
* - DDR50
* - SDR104
* Driver will print out how long the delay is, in picosecond (ps).
*/
typedef enum {
SDMMC_DELAY_PHASE_0 = 1, /*!< Delay phase 0 */
SDMMC_DELAY_PHASE_1, /*!< Delay phase 1 */
SDMMC_DELAY_PHASE_2, /*!< Delay phase 2 */
SDMMC_DELAY_PHASE_3, /*!< Delay phase 3 */
SDMMC_DELAY_PHASE_4, /*!< Delay phase 4 */
SDMMC_DELAY_PHASE_5, /*!< Delay phase 5 */
SDMMC_DELAY_PHASE_6, /*!< Delay phase 6 */
SDMMC_DELAY_PHASE_7, /*!< Delay phase 7 */
SDMMC_DELAY_PHASE_AUTO, /*!< Auto detect phase, only valid for UHS-I modes */
} sdmmc_delay_phase_t;
/**
* @brief SD/MMC Host clock timing delay lines
*/
typedef enum {
SDMMC_DELAY_LINE_0 = 1, /*!< Delay line 0 */
SDMMC_DELAY_LINE_1, /*!< Delay line 1 */
SDMMC_DELAY_LINE_2, /*!< Delay line 2 */
SDMMC_DELAY_LINE_3, /*!< Delay line 3 */
SDMMC_DELAY_LINE_4, /*!< Delay line 4 */
SDMMC_DELAY_LINE_5, /*!< Delay line 5 */
SDMMC_DELAY_LINE_6, /*!< Delay line 6 */
SDMMC_DELAY_LINE_7, /*!< Delay line 7 */
SDMMC_DELAY_LINE_AUTO, /*!< Auto detect line */
} sdmmc_delay_line_t;
#if SOC_SDMMC_DATA_WIDTH_MAX
#define SDMMC_DATA_SIG_NUM SOC_SDMMC_DATA_WIDTH_MAX ///< Number of data signals
#else
#define SDMMC_DATA_SIG_NUM 0 ///< Number of data signals
#endif
#define SDMMC_DMA_ALIGNMENT 4
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,542 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in hal/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 <stdbool.h>
#include "soc/soc_caps.h"
#if SOC_SDIO_SLAVE_SUPPORTED
#include "hal/sdio_slave_types.h"
#include "hal/sdio_slave_ll.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_SDIO_SLAVE_SUPPORTED
/// Space used for each sending descriptor. Should initialize the sendbuf according 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 {
sdio_slave_ll_desc_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 sdio_slave_ll_desc_t sdio_slave_hal_recv_desc_t;
#define sdio_slave_hal_recv_desc_s sdio_slave_ll_desc_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.
*/
//some boolean flags
struct {
uint32_t no_highspeed: 1; /**< Disable the highspeed support */
};
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 function 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 indicating 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 with 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);
/**
* Get the address of the interrupt status register.
*
* @param hal Context of the HAL layer.
* @return Address of the interrupt status register
*/
volatile void* sdio_slave_hal_get_intr_status_reg(sdio_slave_context_t *hal);
#endif // SOC_SDIO_SLAVE_SUPPORTED
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
//include soc related (generated) definitions
#include "soc/soc_caps.h"
#if SOC_SDIO_SLAVE_SUPPORTED
#include "soc/sdio_slc_reg.h"
#include "soc/sdio_slc_struct.h"
#include "soc/sdio_slc_host_reg.h"
#include "soc/sdio_slc_host_struct.h"
#include "soc/sdio_hinf_reg.h"
#include "soc/sdio_hinf_struct.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/** pin and signal information of each slot */
typedef struct {
uint32_t clk_gpio;
uint32_t cmd_gpio;
uint32_t d0_gpio;
uint32_t d1_gpio;
uint32_t d2_gpio;
uint32_t d3_gpio;
int func;
} sdio_slave_slot_info_t;
extern const sdio_slave_slot_info_t sdio_slave_slot_info[];
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_bit_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
/// 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.
* If :c:macro:`SDIO_SLAVE_FLAG_HIGH_SPEED` is specified in
* :cpp:class:`sdio_slave_config_t`, this should be selected.
* 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. If :c:macro:`SDIO_SLAVE_FLAG_DEFAULT_SPEED` is specified in
* :cpp:class:`sdio_slave_config_t`, this should be selected.
*/
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;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The hal is not public api, don't use in application code.
* See readme.md in hal/include/hal/readme.md
******************************************************************************/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct sdmmc_dev_t *sdmmc_soc_handle_t;
/**
* @brief Context of the HAL
*/
typedef struct {
sdmmc_soc_handle_t dev; // SDMMC SOC layer handle (i.e. register base address)
} sdmmc_hal_context_t;
/**
* @brief Init the sdmmc hal context.
*
* @param hal Context of the HAL
*/
void sdmmc_hal_init(sdmmc_hal_context_t *hal);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
//include soc related (generated) definitions
#include "soc/soc_caps.h"
#if SOC_SDMMC_HOST_SUPPORTED
#include "soc/sdmmc_reg.h"
#include "soc/sdmmc_struct.h"
#include "soc/gpio_sig_map.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_SDMMC_HOST_SUPPORTED
/**
* Common SDMMC slot info, doesn't depend on SOC_SDMMC_USE_{IOMUX,GPIO_MATRIX}
*/
typedef struct {
uint8_t width; /*!< Maximum supported slot width (1, 4, 8) */
uint8_t card_detect; /*!< Card detect signal in GPIO Matrix */
uint8_t write_protect; /*!< Write protect signal in GPIO Matrix */
uint8_t card_int; /*!< Card interrupt signal in GPIO Matrix */
} sdmmc_slot_info_t;
/** Width and GPIO matrix signal numbers for auxiliary SD host signals, one structure per slot */
extern const sdmmc_slot_info_t sdmmc_slot_info[SOC_SDMMC_NUM_SLOTS];
/**
* This structure lists pin numbers (if SOC_SDMMC_USE_IOMUX is set)
* or GPIO Matrix signal numbers (if SOC_SDMMC_USE_GPIO_MATRIX is set)
* for the SD bus signals. Field names match SD bus signal names.
*/
typedef union {
struct {
int cd;
int wp;
int clk;
int cmd;
int d0;
int d1;
int d2;
int d3;
int d4;
int d5;
int d6;
int d7;
};
int val[12]; // for iteration, num of entries in struct
} sdmmc_slot_io_info_t;
/** GPIO pin numbers of SD bus signals, one structure per slot */
extern const sdmmc_slot_io_info_t sdmmc_slot_gpio_num[SOC_SDMMC_NUM_SLOTS];
/** GPIO matrix signal numbers of SD bus signals, one structure per slot */
extern const sdmmc_slot_io_info_t sdmmc_slot_gpio_sig[SOC_SDMMC_NUM_SLOTS];
#endif // SOC_SDMMC_HOST_SUPPORTED
#ifdef __cplusplus
}
#endif
+736
View File
@@ -0,0 +1,736 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// The HAL layer for SDIO slave (common part)
#include <string.h>
#include <inttypes.h>
#include "soc/sdio_slc_struct.h"
#include "soc/sdio_hinf_struct.h"
#include "hal/sdio_slave_types.h"
#include "soc/sdio_slc_host_struct.h"
#include "hal/sdio_slave_hal.h"
#include "hal/assert.h"
#include "hal/log.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)
HAL_LOG_ATTR_TAG(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, sdio_slave_hal_send_desc_t **start, sdio_slave_hal_send_desc_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)=(sdio_slave_ll_desc_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) {
err = (*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, sdio_slave_hal_send_desc_t **start, sdio_slave_hal_send_desc_t **end, ringbuf_get_all_t get_all)
{
HAL_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 = (sdio_slave_hal_send_desc_t *) get_start;
}
if (end != NULL) {
*end = (sdio_slave_hal_send_desc_t *) buf->read_ptr;
}
return ESP_OK;
}
static inline int sdio_ringbuf_return(sdio_ringbuf_t* buf, uint8_t *ptr)
{
HAL_ASSERT(sdio_ringbuf_offset_ptr(buf, RINGBUF_FREE_PTR, SDIO_SLAVE_SEND_DESC_SIZE) == ptr);
size_t size = (buf->read_ptr + buf->size - buf->free_ptr) % buf->size;
size_t count = size / SDIO_SLAVE_SEND_DESC_SIZE;
HAL_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, !hal->no_highspeed);
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 __attribute((unused));
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, &last, NULL, RINGBUF_GET_ONE);
HAL_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, &first, &last, RINGBUF_GET_ALL);
HAL_ASSERT(rcv_res == ESP_OK);
HAL_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 sdio_slave_ll_desc_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;
HAL_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, (sdio_slave_ll_desc_t*)start_desc);
// update pkt_len register to allow host reading.
sdio_slave_ll_send_write_len(hal->slc, end_desc->pkt_len);
HAL_EARLY_LOGV(TAG, "send_length_write: %"PRIu32", last_len: %08"PRIX32"", end_desc->pkt_len, sdio_slave_ll_send_read_len(hal->host));
send_set_state(hal, STATE_SENDING);
HAL_EARLY_LOGD(TAG, "restart new send: %p->%p, pkt_len: %"PRIu32"", 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), &start, &end, RINGBUF_GET_ONE);
} else { //stream mode
ret = sdio_ringbuf_recv(&(hal->send_desc_queue), &start, &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
HAL_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);
sdio_slave_ll_send_write_len(hal->slc, 0);
HAL_EARLY_LOGV(TAG, "last_len: %08X", 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) {
HAL_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
HAL_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 = NULL;
sdio_slave_hal_send_desc_t *tail = NULL;
ret = sdio_ringbuf_recv(&(hal->send_desc_queue), &head, &tail, RINGBUF_GET_ONE);
if (ret == ESP_OK) {
//currently each packet takes only one desc.
HAL_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) {
HAL_ASSERT(hal->in_flight_head != NULL);
} else {
HAL_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 sdio_slave_ll_desc_t* recv_get_first_empty_buf(sdio_slave_context_t* hal)
{
sdio_slave_hal_recv_stailq_t *const queue = &(hal->recv_link_list);
sdio_slave_ll_desc_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;
}
sdio_slave_ll_desc_t *sdio_slave_hal_recv_unload_desc(sdio_slave_context_t *hal)
{
sdio_slave_hal_recv_stailq_t *const queue = &hal->recv_link_list;
sdio_slave_ll_desc_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, sdio_slave_ll_desc_t *desc, uint8_t *start)
{
*desc = (sdio_slave_ll_desc_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);
sdio_slave_ll_desc_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);
sdio_slave_ll_desc_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;
sdio_slave_ll_desc_t *desc = STAILQ_FIRST(queue);
HAL_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, sdio_slave_ll_desc_t *desc)
{
sdio_slave_hal_recv_stailq_t *const queue = &(hal->recv_link_list);
desc->owner = 1;
sdio_slave_ll_desc_t *const tail = STAILQ_LAST(queue, sdio_slave_ll_desc_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(sdio_slave_ll_desc_t *item)
{
HAL_EARLY_LOGI(TAG, "=> %p: size: %"PRIu32"(%"PRIu32"), eof: %"PRIu32", owner: %"PRIu32"", item, item->size, item->length, item->eof, item->owner);
HAL_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;
sdio_slave_ll_desc_t *item = NULL;
HAL_EARLY_LOGI(TAG, ">>>>> first: %p, last: %p <<<<<", queue->stqh_first, queue->stqh_last);
STAILQ_FOREACH(item, queue, qe) {
cnt++;
show_queue_item(item);
}
HAL_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);
}
volatile void* sdio_slave_hal_get_intr_status_reg(sdio_slave_context_t *hal)
{
return sdio_slave_ll_get_intr_status_reg(hal->slc);
}
+15
View File
@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include "hal/sdmmc_hal.h"
#include "hal/sdmmc_ll.h"
#include "soc/soc_caps.h"
void sdmmc_hal_init(sdmmc_hal_context_t *hal)
{
hal->dev = SDMMC_LL_GET_HW(0);
}