diff --git a/components/esp_driver_sdmmc/src/sdmmc_transaction.c b/components/esp_driver_sdmmc/src/sdmmc_transaction.c index c6a360b8d1..fb6facbaa1 100644 --- a/components/esp_driver_sdmmc/src/sdmmc_transaction.c +++ b/components/esp_driver_sdmmc/src/sdmmc_transaction.c @@ -5,6 +5,7 @@ */ #include +#include #include "esp_err.h" #include "esp_log.h" #include "esp_check.h" @@ -37,6 +38,8 @@ typedef enum { SDMMC_SENDING_CMD, SDMMC_SENDING_DATA, SDMMC_BUSY, + SDMMC_SENDING_VOLTAGE_SWITCH, + SDMMC_WAITING_VOLTAGE_SWITCH, } sdmmc_req_state_t; typedef struct { @@ -70,14 +73,17 @@ static esp_pm_lock_handle_t s_pm_lock; static esp_err_t handle_idle_state_events(void); static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd); -static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* state, +static esp_err_t handle_event(int slot, sdmmc_command_t* cmd, sdmmc_req_state_t* state, sdmmc_event_t* unhandled_events); -static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, +static esp_err_t process_events(int slot, sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_req_state_t* pstate, sdmmc_event_t* unhandled_events); static void process_command_response(uint32_t status, sdmmc_command_t* cmd); static void fill_dma_descriptors(size_t num_desc); static size_t get_free_descriptors_count(void); static bool wait_for_busy_cleared(uint32_t timeout_ms); +static void handle_voltage_switch_stage1(int slot, sdmmc_command_t* cmd); +static void handle_voltage_switch_stage2(int slot, sdmmc_command_t* cmd); +static void handle_voltage_switch_stage3(int slot, sdmmc_command_t* cmd); esp_err_t sdmmc_host_transaction_handler_init(void) { @@ -124,6 +130,12 @@ esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo) // dispose of any events which happened asynchronously handle_idle_state_events(); + + // special handling for voltage switch command + if (cmdinfo->opcode == SD_SWITCH_VOLTAGE) { + handle_voltage_switch_stage1(slot, cmdinfo); + } + // convert cmdinfo to hardware register value sdmmc_hw_cmd_t hw_cmd = make_hw_cmd(cmdinfo); if (cmdinfo->data) { @@ -174,9 +186,12 @@ esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo) // process events until transfer is complete cmdinfo->error = ESP_OK; sdmmc_req_state_t state = SDMMC_SENDING_CMD; + if (cmdinfo->opcode == SD_SWITCH_VOLTAGE) { + state = SDMMC_SENDING_VOLTAGE_SWITCH; + } sdmmc_event_t unhandled_events = { 0 }; while (state != SDMMC_IDLE) { - ret = handle_event(cmdinfo, &state, &unhandled_events); + ret = handle_event(slot, cmdinfo, &state, &unhandled_events); if (ret != ESP_OK) { break; } @@ -286,7 +301,7 @@ static esp_err_t handle_idle_state_events(void) return ESP_OK; } -static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* state, +static esp_err_t handle_event(int slot, sdmmc_command_t* cmd, sdmmc_req_state_t* state, sdmmc_event_t* unhandled_events) { sdmmc_event_t event; @@ -298,13 +313,13 @@ static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* state, } return err; } - ESP_LOGV(TAG, "sdmmc_handle_event: event %08"PRIx32" %08"PRIx32", unhandled %08"PRIx32" %08"PRIx32, - event.sdmmc_status, event.dma_status, + ESP_LOGV(TAG, "sdmmc_handle_event: slot %d event %08"PRIx32" %08"PRIx32", unhandled %08"PRIx32" %08"PRIx32, + slot, event.sdmmc_status, event.dma_status, unhandled_events->sdmmc_status, unhandled_events->dma_status); event.sdmmc_status |= unhandled_events->sdmmc_status; event.dma_status |= unhandled_events->dma_status; - process_events(event, cmd, state, unhandled_events); - ESP_LOGV(TAG, "sdmmc_handle_event: events unhandled: %08"PRIx32" %08"PRIx32, unhandled_events->sdmmc_status, unhandled_events->dma_status); + process_events(slot, event, cmd, state, unhandled_events); + ESP_LOGV(TAG, "sdmmc_handle_event: slot %d events unhandled: %08"PRIx32" %08"PRIx32, slot, unhandled_events->sdmmc_status, unhandled_events->dma_status); return ESP_OK; } @@ -327,12 +342,15 @@ static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd) res.stop_abort_cmd = 1; } else if (cmd->opcode == MMC_GO_IDLE_STATE) { res.send_init = 1; + } else if (cmd->opcode == SD_SWITCH_VOLTAGE) { + res.volt_switch = 1; } else { res.wait_complete = 1; } if (cmd->opcode == MMC_GO_IDLE_STATE) { res.send_init = 1; } + if (cmd->flags & SCF_RSP_PRESENT) { res.response_expect = 1; if (cmd->flags & SCF_RSP_136) { @@ -419,18 +437,20 @@ static inline bool mask_check_and_clear(uint32_t* state, uint32_t mask) return ret; } -static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, +static esp_err_t process_events(int slot, sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_req_state_t* pstate, sdmmc_event_t* unhandled_events) { const char* const s_state_names[] __attribute__((unused)) = { "IDLE", "SENDING_CMD", "SENDIND_DATA", - "BUSY" + "BUSY", + "SENDING_VOLTAGE_SWITCH", + "WAITING_VOLTAGE_SWITCH", }; sdmmc_event_t orig_evt = evt; - ESP_LOGV(TAG, "%s: state=%s evt=%"PRIx32" dma=%"PRIx32, __func__, s_state_names[*pstate], - evt.sdmmc_status, evt.dma_status); + ESP_LOGV(TAG, "%s: slot=%d state=%s evt=%"PRIx32" dma=%"PRIx32, __func__, slot, + s_state_names[*pstate], evt.sdmmc_status, evt.dma_status); sdmmc_req_state_t next_state = *pstate; sdmmc_req_state_t state = (sdmmc_req_state_t) -1; while (next_state != state) { @@ -461,6 +481,32 @@ static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, } break; + case SDMMC_SENDING_VOLTAGE_SWITCH: + if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_CMD_ERR_MASK)) { + process_command_response(orig_evt.sdmmc_status, cmd); + next_state = SDMMC_IDLE; + } + if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_VOLT_SW)) { + handle_voltage_switch_stage2(slot, cmd); + if (cmd->error != ESP_OK) { + next_state = SDMMC_IDLE; + } else { + next_state = SDMMC_WAITING_VOLTAGE_SWITCH; + } + } + break; + + case SDMMC_WAITING_VOLTAGE_SWITCH: + if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_CMD_ERR_MASK)) { + process_command_response(orig_evt.sdmmc_status, cmd); + next_state = SDMMC_IDLE; + } + if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_VOLT_SW)) { + handle_voltage_switch_stage3(slot, cmd); + next_state = SDMMC_IDLE; + } + break; + case SDMMC_SENDING_DATA: if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_DATA_ERR_MASK)) { process_data_status(orig_evt.sdmmc_status, cmd); @@ -518,3 +564,32 @@ static bool wait_for_busy_cleared(uint32_t timeout_ms) } return false; } + +static void handle_voltage_switch_stage1(int slot, sdmmc_command_t* cmd) +{ + ESP_LOGV(TAG, "%s: enabling clock", __func__); + sdmmc_host_set_cclk_always_on(slot, true); +} + +static void handle_voltage_switch_stage2(int slot, sdmmc_command_t* cmd) +{ + ESP_LOGV(TAG, "%s: disabling clock", __func__); + sdmmc_host_enable_clk_cmd11(slot, false); + usleep(100); + ESP_LOGV(TAG, "%s: switching voltage", __func__); + esp_err_t err = cmd->volt_switch_cb(cmd->volt_switch_cb_arg, 1800); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to switch voltage (0x%x)", err); + cmd->error = err; + } + ESP_LOGV(TAG, "%s: waiting 10ms", __func__); + usleep(10000); + ESP_LOGV(TAG, "%s: enabling clock", __func__); + sdmmc_host_enable_clk_cmd11(slot, true); +} + +static void handle_voltage_switch_stage3(int slot, sdmmc_command_t* cmd) +{ + ESP_LOGV(TAG, "%s: voltage switch complete, clock back to low-power mode", __func__); + sdmmc_host_set_cclk_always_on(slot, false); +} diff --git a/components/hal/esp32p4/include/hal/sdmmc_ll.h b/components/hal/esp32p4/include/hal/sdmmc_ll.h index 1141854f5c..2f7eb74bcd 100644 --- a/components/hal/esp32p4/include/hal/sdmmc_ll.h +++ b/components/hal/esp32p4/include/hal/sdmmc_ll.h @@ -451,6 +451,22 @@ static inline bool sdmmc_ll_is_card_write_protected(sdmmc_dev_t *hw, uint32_t sl 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_18v_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 * diff --git a/components/soc/esp32p4/register/soc/sdmmc_reg.h b/components/soc/esp32p4/register/soc/sdmmc_reg.h index 536a95e4ef..719dae8260 100644 --- a/components/soc/esp32p4/register/soc/sdmmc_reg.h +++ b/components/soc/esp32p4/register/soc/sdmmc_reg.h @@ -1,5 +1,5 @@ /** - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -1502,6 +1502,7 @@ extern "C" { #define SDMMC_INTMASK_HLE BIT(12) #define SDMMC_INTMASK_FRUN BIT(11) #define SDMMC_INTMASK_HTO BIT(10) +#define SDMMC_INTMASK_VOLT_SW SDMMC_INTMASK_HTO #define SDMMC_INTMASK_DTO BIT(9) #define SDMMC_INTMASK_RTO BIT(8) #define SDMMC_INTMASK_DCRC BIT(7) diff --git a/components/soc/esp32p4/register/soc/sdmmc_struct.h b/components/soc/esp32p4/register/soc/sdmmc_struct.h index 10318d6c9c..0cf50596bb 100644 --- a/components/soc/esp32p4/register/soc/sdmmc_struct.h +++ b/components/soc/esp32p4/register/soc/sdmmc_struct.h @@ -1,5 +1,5 @@ /** - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -458,7 +458,13 @@ typedef union { * not masked. */ uint32_t ccs_expected:1; - uint32_t reserved_24:5; + uint32_t reserved_24:4; + /** volt_switch : R/W; bitpos: [28]; default: 0; + * Voltage switch bit. + * 0: No voltage switching. + * 1: Voltage switching enabled; must be set for CMD11 only. + */ + uint32_t volt_switch:1; /** use_hole_reg : R/W; bitpos: [29]; default: 1; * Use Hold Register. * 0: CMD and DATA sent to card bypassing HOLD Register; @@ -907,11 +913,17 @@ typedef union { */ typedef union { struct { - uint32_t reserved_0:16; - /** ddr : R/W; bitpos: [17:16]; default: 0; - * DDR mode selection,1 bit for each card. - * 0-Non-DDR mode. - * 1-DDR mode. + /** volt: R/W; bitpos: [1:0]; default: 0; + * Voltage mode selection, 1 bit for each card. + * 0: 3.3V mode. + * 1: 1.8V mode. + */ + uint32_t volt:2; + uint32_t reserved_0:14; + /** ddr: R/W; bitpos: [17:16]; default: 0; + * DDR mode selection, 1 bit for each card. + * 0: Non-DDR mode. + * 1: DDR mode. */ uint32_t ddr:2; uint32_t reserved_18:14;