| /* |
| * Copyright (c) 2025 EXALT Technologies. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT st_stm32_sdio |
| |
| #include <zephyr/cache.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/stm32_clock_control.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/reset.h> |
| #include <zephyr/drivers/sdhc.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/pm/device_runtime.h> |
| #include <zephyr/pm/policy.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_REGISTER(sdhc_stm32, CONFIG_SDHC_LOG_LEVEL); |
| |
| typedef void (*irq_config_func_t)(void); |
| |
| BUILD_ASSERT((CONFIG_SDHC_BUFFER_ALIGNMENT % sizeof(uint32_t)) == 0U); |
| |
| #define SDIO_OCR_SDIO_S18R BIT(24) /* SDIO OCR bit indicating support for 1.8V switching */ |
| |
| struct sdhc_stm32_config { |
| bool hw_flow_control; /* flag for enabling hardware flow control */ |
| bool support_1_8_v; /* flag indicating support for 1.8V signaling */ |
| unsigned int max_freq; /* Max bus frequency in Hz */ |
| unsigned int min_freq; /* Min bus frequency in Hz */ |
| uint8_t bus_width; /* Width of the SDIO bus */ |
| uint16_t clk_div; /* Clock divider value to configure SDIO clock speed */ |
| uint32_t power_delay_ms; /* power delay prop for the host in milliseconds */ |
| uint32_t reg_addr; /* Base address of the SDIO peripheral register block */ |
| SDIO_HandleTypeDef *hsd; /* Pointer to SDIO HAL handle */ |
| const struct stm32_pclken *pclken; /* Pointer to peripheral clock configuration */ |
| const struct pinctrl_dev_config *pcfg; /* Pointer to pin control configuration */ |
| struct gpio_dt_spec sdhi_on_gpio; /* Power pin to control the regulators used by card.*/ |
| struct gpio_dt_spec cd_gpio; /* Card detect GPIO pin */ |
| irq_config_func_t irq_config_func; /* IRQ config function */ |
| }; |
| |
| struct sdhc_stm32_data { |
| struct k_mutex bus_mutex; /* Sync between commands */ |
| struct sdhc_io host_io; /* Input/Output host configuration */ |
| uint32_t cmd_index; /* current command opcode */ |
| struct sdhc_host_props props; /* current host properties */ |
| struct k_sem device_sync_sem; /* Sync between device communication messages */ |
| void *sdio_dma_buf; /* DMA buffer for SDIO data transfer */ |
| uint32_t total_transfer_bytes; /* number of bytes transferred */ |
| }; |
| |
| /* |
| * Power on the card. |
| * |
| * This function toggles a GPIO to control the internal regulator used |
| * by the card device. It handles GPIO configuration and timing delays. |
| * |
| * @param dev Device structure pointer. |
| * |
| * @return 0 on success, non-zero code on failure. |
| */ |
| static int sdhi_power_on(const struct device *dev) |
| { |
| int ret; |
| const struct sdhc_stm32_config *config = dev->config; |
| |
| if (!device_is_ready(config->sdhi_on_gpio.port)) { |
| LOG_ERR("Card is not ready"); |
| return -ENODEV; |
| } |
| |
| ret = gpio_pin_configure_dt(&config->sdhi_on_gpio, GPIO_OUTPUT_HIGH); |
| if (ret < 0) { |
| LOG_ERR("Card configuration failed, ret:%d", ret); |
| return ret; |
| } |
| |
| k_msleep(config->power_delay_ms); |
| return ret; |
| } |
| |
| /** |
| * Logs detailed SDIO error types using Zephyr's logging subsystem. |
| * |
| * This helper function queries the error status of an SDIO operation |
| * and reports specific error types using `LOG_ERR()`. |
| * In addition to logging, it also resets the `ErrorCode` field |
| * of the `SDIO_HandleTypeDef` to `HAL_SDIO_ERROR_NONE`. |
| * |
| * @param hsd Pointer to the SDIO handle. |
| */ |
| static void sdhc_stm32_log_err_type(SDIO_HandleTypeDef *hsd) |
| { |
| uint32_t error_code = HAL_SDIO_GetError(hsd); |
| |
| if ((error_code & HAL_SDIO_ERROR_TIMEOUT) != 0U) { |
| LOG_ERR("SDIO Timeout"); |
| } |
| |
| if ((error_code & HAL_SDIO_ERROR_DATA_TIMEOUT) != 0U) { |
| LOG_ERR("SDIO Data Timeout"); |
| } |
| |
| if ((error_code & HAL_SDIO_ERROR_DATA_CRC_FAIL) != 0U) { |
| LOG_ERR("SDIO Data CRC"); |
| } |
| |
| if ((error_code & HAL_SDIO_ERROR_TX_UNDERRUN) != 0U) { |
| LOG_ERR("SDIO FIFO Transmit Underrun"); |
| } |
| |
| if ((error_code & HAL_SDIO_ERROR_RX_OVERRUN) != 0U) { |
| LOG_ERR("SDIO FIFO Receive Overrun"); |
| } |
| |
| if ((error_code & HAL_SDIO_ERROR_INVALID_CALLBACK) != 0U) { |
| LOG_ERR("SDIO Invalid Callback"); |
| } |
| |
| if ((error_code & SDMMC_ERROR_ADDR_MISALIGNED) != 0U) { |
| LOG_ERR("SDIO Misaligned address"); |
| } |
| |
| if ((error_code & SDMMC_ERROR_WRITE_PROT_VIOLATION) != 0U) { |
| LOG_ERR("Attempt to program a write protected block"); |
| } |
| |
| if ((error_code & SDMMC_ERROR_ILLEGAL_CMD) != 0U) { |
| LOG_ERR("Command is not legal for the card state"); |
| } |
| |
| hsd->ErrorCode = HAL_SDIO_ERROR_NONE; |
| } |
| |
| /** |
| * @brief No-operation callback for SDIO card identification. |
| * @param hsd: Pointer to an SDIO_HandleTypeDef structure that contains |
| * the configuration information for SDIO module. |
| * @retval HAL status |
| */ |
| static HAL_StatusTypeDef noop_identify_card_callback(SDIO_HandleTypeDef *hsd) |
| { |
| ARG_UNUSED(hsd); |
| return HAL_OK; |
| } |
| |
| /** |
| * Initializes the SDIO peripheral with the configuration specified. |
| * |
| * This includes deinitializing any previous configuration, and applying |
| * parameters like clock edge, power saving, clock divider, hardware |
| * flow control and bus width. |
| * |
| * @param dev Pointer to the device structure for the SDIO peripheral. |
| * |
| * @return 0 on success, err code on failure. |
| */ |
| static int sdhc_stm32_sd_init(const struct device *dev) |
| { |
| struct sdhc_stm32_data *data = dev->data; |
| const struct sdhc_stm32_config *config = dev->config; |
| SDIO_HandleTypeDef *hsd = config->hsd; |
| |
| data->host_io.bus_width = config->bus_width; |
| hsd->Instance = (MMC_TypeDef *) config->reg_addr; |
| |
| if (HAL_SDIO_DeInit(hsd) != HAL_OK) { |
| LOG_ERR("Failed to de-initialize the SDIO device"); |
| return -EIO; |
| } |
| |
| hsd->Init.ClockEdge = SDMMC_CLOCK_EDGE_FALLING; |
| hsd->Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE; |
| hsd->Init.ClockDiv = config->clk_div; |
| |
| if (config->hw_flow_control) { |
| hsd->Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_ENABLE; |
| } else { |
| hsd->Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE; |
| } |
| |
| if (data->host_io.bus_width == SDHC_BUS_WIDTH4BIT) { |
| hsd->Init.BusWide = SDMMC_BUS_WIDE_4B; |
| } else if (data->host_io.bus_width == SDHC_BUS_WIDTH8BIT) { |
| hsd->Init.BusWide = SDMMC_BUS_WIDE_8B; |
| } else { |
| hsd->Init.BusWide = SDMMC_BUS_WIDE_1B; |
| } |
| |
| if (HAL_SDIO_RegisterIdentifyCardCallback(config->hsd, |
| noop_identify_card_callback) != HAL_OK) { |
| LOG_ERR("Register identify card callback failed"); |
| return -EIO; |
| } |
| |
| if (HAL_SDIO_Init(hsd) != HAL_OK) { |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static int sdhc_stm32_activate(const struct device *dev) |
| { |
| int ret; |
| const struct sdhc_stm32_config *config = (struct sdhc_stm32_config *)dev->config; |
| const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); |
| |
| if (!device_is_ready(clk)) { |
| return -ENODEV; |
| } |
| |
| ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (DT_INST_NUM_CLOCKS(0) > 1) { |
| if (clock_control_configure(clk, |
| (clock_control_subsys_t)(uintptr_t) &config->pclken[1], |
| NULL) != 0) { |
| LOG_ERR("Failed to enable SDHC domain clock"); |
| return -EIO; |
| } |
| } |
| |
| if (clock_control_on(clk, (clock_control_subsys_t)(uintptr_t)&config->pclken[0]) != 0) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static uint32_t sdhc_stm32_go_idle_state(const struct device *dev) |
| { |
| const struct sdhc_stm32_config *config = dev->config; |
| |
| return SDMMC_CmdGoIdleState(config->hsd->Instance); |
| } |
| |
| static int sdhc_stm32_rw_direct(const struct device *dev, struct sdhc_command *cmd) |
| { |
| HAL_StatusTypeDef res; |
| const struct sdhc_stm32_config *config = dev->config; |
| bool direction = cmd->arg >> SDIO_CMD_ARG_RW_SHIFT; |
| bool raw_flag = cmd->arg >> SDIO_DIRECT_CMD_ARG_RAW_SHIFT; |
| |
| uint8_t func = (cmd->arg >> SDIO_CMD_ARG_FUNC_NUM_SHIFT) & 0x7; |
| uint32_t reg_addr = (cmd->arg >> SDIO_CMD_ARG_REG_ADDR_SHIFT) & SDIO_CMD_ARG_REG_ADDR_MASK; |
| |
| HAL_SDIO_DirectCmd_TypeDef arg = { |
| .Reg_Addr = reg_addr, |
| .ReadAfterWrite = raw_flag, |
| .IOFunctionNbr = func |
| }; |
| |
| if (direction == SDIO_IO_WRITE) { |
| uint8_t data_in = cmd->arg & SDIO_DIRECT_CMD_DATA_MASK; |
| |
| res = HAL_SDIO_WriteDirect(config->hsd, &arg, data_in); |
| } else { |
| res = HAL_SDIO_ReadDirect(config->hsd, &arg, (uint8_t *)&cmd->response); |
| } |
| |
| return res == HAL_OK ? 0 : -EIO; |
| } |
| |
| static int sdhc_stm32_rw_extended(const struct device *dev, struct sdhc_command *cmd, |
| struct sdhc_data *data) |
| { |
| HAL_StatusTypeDef res; |
| struct sdhc_stm32_data *dev_data = dev->data; |
| const struct sdhc_stm32_config *config = dev->config; |
| bool direction = cmd->arg >> SDIO_CMD_ARG_RW_SHIFT; |
| bool increment = cmd->arg & BIT(SDIO_EXTEND_CMD_ARG_OP_CODE_SHIFT); |
| bool is_block_mode = cmd->arg & BIT(SDIO_EXTEND_CMD_ARG_BLK_SHIFT); |
| uint8_t func = (cmd->arg >> SDIO_CMD_ARG_FUNC_NUM_SHIFT) & 0x7; |
| uint32_t reg_addr = (cmd->arg >> SDIO_CMD_ARG_REG_ADDR_SHIFT) & SDIO_CMD_ARG_REG_ADDR_MASK; |
| |
| if (data->data == NULL) { |
| LOG_ERR("Invalid NULL data buffer passed to CMD53"); |
| return -EINVAL; |
| } |
| |
| HAL_SDIO_ExtendedCmd_TypeDef arg = { |
| .Reg_Addr = reg_addr, |
| .IOFunctionNbr = func, |
| .Block_Mode = is_block_mode ? SDMMC_SDIO_MODE_BLOCK : HAL_SDIO_MODE_BYTE, |
| .OpCode = increment |
| }; |
| |
| config->hsd->block_size = is_block_mode ? data->block_size : 0; |
| dev_data->total_transfer_bytes = data->blocks * data->block_size; |
| |
| if (!IS_ENABLED(CONFIG_SDHC_STM32_POLLING_SUPPORT)) { |
| dev_data->sdio_dma_buf = k_aligned_alloc(CONFIG_SDHC_BUFFER_ALIGNMENT, |
| data->blocks * data->block_size); |
| if (dev_data->sdio_dma_buf == NULL) { |
| LOG_ERR("DMA buffer allocation failed"); |
| return -ENOMEM; |
| } |
| } |
| |
| if (direction == SDIO_IO_WRITE) { |
| if (IS_ENABLED(CONFIG_SDHC_STM32_POLLING_SUPPORT)) { |
| res = HAL_SDIO_WriteExtended(config->hsd, &arg, data->data, |
| dev_data->total_transfer_bytes, |
| data->timeout_ms); |
| } else { |
| memcpy(dev_data->sdio_dma_buf, data->data, |
| dev_data->total_transfer_bytes); |
| sys_cache_data_flush_range(dev_data->sdio_dma_buf, |
| dev_data->total_transfer_bytes); |
| res = HAL_SDIO_WriteExtended_DMA(config->hsd, &arg, |
| dev_data->sdio_dma_buf, |
| dev_data->total_transfer_bytes); |
| } |
| } else { |
| if (IS_ENABLED(CONFIG_SDHC_STM32_POLLING_SUPPORT)) { |
| res = HAL_SDIO_ReadExtended(config->hsd, &arg, data->data, |
| dev_data->total_transfer_bytes, |
| data->timeout_ms); |
| } else { |
| sys_cache_data_flush_range(dev_data->sdio_dma_buf, |
| dev_data->total_transfer_bytes); |
| res = HAL_SDIO_ReadExtended_DMA(config->hsd, &arg, dev_data->sdio_dma_buf, |
| dev_data->total_transfer_bytes); |
| } |
| } |
| |
| if (!IS_ENABLED(CONFIG_SDHC_STM32_POLLING_SUPPORT)) { |
| /* Wait for whole transfer to complete */ |
| if (k_sem_take(&dev_data->device_sync_sem, K_MSEC(CONFIG_SD_CMD_TIMEOUT)) != 0) { |
| k_free(dev_data->sdio_dma_buf); |
| return -ETIMEDOUT; |
| } |
| |
| if (direction == SDIO_IO_READ) { |
| sys_cache_data_invd_range(dev_data->sdio_dma_buf, |
| dev_data->total_transfer_bytes); |
| memcpy(data->data, dev_data->sdio_dma_buf, data->block_size * data->blocks); |
| } |
| |
| k_free(dev_data->sdio_dma_buf); |
| } |
| |
| return res == HAL_OK ? 0 : -EIO; |
| } |
| |
| static int sdhc_stm32_switch_to_1_8v(const struct device *dev) |
| { |
| uint32_t res; |
| struct sdhc_stm32_data *data = dev->data; |
| const struct sdhc_stm32_config *config = dev->config; |
| |
| /* Check if host supports 1.8V signaling */ |
| if (!data->props.host_caps.vol_180_support) { |
| LOG_ERR("Host does not support 1.8V signaling"); |
| return -ENOTSUP; |
| } |
| |
| res = SDMMC_CmdVoltageSwitch(config->hsd->Instance); |
| if (res != 0) { |
| LOG_ERR("CMD11 failed: %#x", res); |
| return -EIO; |
| } |
| |
| LOG_DBG("Successfully switched to 1.8V signaling"); |
| return 0; |
| } |
| |
| static int sdhc_stm32_request(const struct device *dev, struct sdhc_command *cmd, |
| struct sdhc_data *data) |
| { |
| int res = 0; |
| uint32_t sdmmc_res = 0U; |
| struct sdhc_stm32_data *dev_data = dev->data; |
| const struct sdhc_stm32_config *config = dev->config; |
| |
| __ASSERT_NO_MSG(cmd != NULL); |
| |
| if (k_mutex_lock(&dev_data->bus_mutex, K_MSEC(cmd->timeout_ms))) { |
| return -EBUSY; |
| } |
| |
| if (HAL_SDIO_GetState(config->hsd) != HAL_SDIO_STATE_READY) { |
| LOG_ERR("SDIO Card is busy"); |
| k_mutex_unlock(&dev_data->bus_mutex); |
| return -ETIMEDOUT; |
| } |
| |
| (void)pm_device_runtime_get(dev); |
| |
| /* Prevent the clocks to be stopped during the request */ |
| pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| |
| dev_data->cmd_index = cmd->opcode; |
| switch (cmd->opcode) { |
| case SD_GO_IDLE_STATE: |
| sdmmc_res = sdhc_stm32_go_idle_state(dev); |
| if (sdmmc_res != 0U) { |
| res = -EIO; |
| } |
| break; |
| |
| case SD_SELECT_CARD: |
| sdmmc_res = SDMMC_CmdSelDesel(config->hsd->Instance, cmd->arg); |
| if (sdmmc_res != 0U) { |
| res = -EIO; |
| break; |
| } |
| /* Clear unused flags to avoid SDIO card identification issues */ |
| cmd->response[0U] &= |
| ~(SD_R1_ERASE_SKIP | SD_R1_CSD_OVERWRITE | SD_R1_ERASE_PARAM); |
| break; |
| |
| case SD_SEND_RELATIVE_ADDR: |
| sdmmc_res = SDMMC_CmdSetRelAdd(config->hsd->Instance, (uint16_t *)&cmd->response); |
| if (sdmmc_res != 0U) { |
| res = -EIO; |
| break; |
| } |
| /* |
| * Restore RCA by reversing the double 16-bit right shift from |
| * Zephyr subsys and SDMMC_CmdSetRelAdd |
| */ |
| cmd->response[0] = cmd->response[0] << 16; |
| break; |
| |
| case SDIO_SEND_OP_COND: |
| sdmmc_res = SDMMC_CmdSendOperationcondition(config->hsd->Instance, cmd->arg, |
| (uint32_t *)&cmd->response); |
| if (sdmmc_res != 0U) { |
| res = -EIO; |
| } |
| break; |
| |
| case SDIO_RW_DIRECT: |
| res = sdhc_stm32_rw_direct(dev, cmd); |
| break; |
| |
| case SDIO_RW_EXTENDED: |
| __ASSERT_NO_MSG(data != NULL); |
| res = sdhc_stm32_rw_extended(dev, cmd, data); |
| break; |
| |
| case SD_VOL_SWITCH: |
| res = sdhc_stm32_switch_to_1_8v(dev); |
| break; |
| |
| default: |
| LOG_DBG("Unsupported Command, opcode:%d", cmd->opcode); |
| res = -ENOTSUP; |
| } |
| |
| if (res != 0) { |
| LOG_DBG("Command Failed, opcode:%d", cmd->opcode); |
| sdhc_stm32_log_err_type(config->hsd); |
| } |
| |
| pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| (void)pm_device_runtime_put(dev); |
| k_mutex_unlock(&dev_data->bus_mutex); |
| |
| return res; |
| } |
| |
| static int sdhc_stm32_set_io(const struct device *dev, struct sdhc_io *ios) |
| { |
| int res = 0; |
| struct sdhc_stm32_data *data = dev->data; |
| const struct sdhc_stm32_config *config = dev->config; |
| struct sdhc_io *host_io = &data->host_io; |
| struct sdhc_host_props *props = &data->props; |
| |
| (void)pm_device_runtime_get(dev); |
| /* Prevent the clocks to be stopped during the request */ |
| pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| k_mutex_lock(&data->bus_mutex, K_FOREVER); |
| |
| if ((ios->clock != 0) && (host_io->clock != ios->clock)) { |
| if ((ios->clock > props->f_max) || (ios->clock < props->f_min)) { |
| LOG_ERR("Invalid clock frequency, domain (%u, %u)", |
| props->f_min, props->f_max); |
| res = -EINVAL; |
| goto out; |
| } |
| if (HAL_SDIO_ConfigFrequency(config->hsd, (uint32_t)ios->clock) != HAL_OK) { |
| LOG_ERR("Failed to set clock to %d", ios->clock); |
| res = -EIO; |
| goto out; |
| } |
| host_io->clock = ios->clock; |
| LOG_DBG("Clock set to %d", ios->clock); |
| } |
| |
| if (ios->power_mode == SDHC_POWER_OFF) { |
| (void)SDMMC_PowerState_OFF(config->hsd->Instance); |
| k_msleep(data->props.power_delay); |
| } else { |
| (void)SDMMC_PowerState_ON(config->hsd->Instance); |
| k_msleep(data->props.power_delay); |
| } |
| |
| if ((ios->bus_width != 0) && (host_io->bus_width != ios->bus_width)) { |
| uint32_t bus_width_reg_value; |
| |
| if (ios->bus_width == SDHC_BUS_WIDTH8BIT) { |
| bus_width_reg_value = SDMMC_BUS_WIDE_8B; |
| } else if (ios->bus_width == SDHC_BUS_WIDTH4BIT) { |
| bus_width_reg_value = SDMMC_BUS_WIDE_4B; |
| } else { |
| bus_width_reg_value = SDMMC_BUS_WIDE_1B; |
| } |
| |
| MODIFY_REG(config->hsd->Instance->CLKCR, SDMMC_CLKCR_WIDBUS, bus_width_reg_value); |
| host_io->bus_width = ios->bus_width; |
| } |
| |
| out: |
| k_mutex_unlock(&data->bus_mutex); |
| pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| (void)pm_device_runtime_put(dev); |
| |
| return res; |
| } |
| |
| static void sdhc_stm32_init_props(const struct device *dev) |
| { |
| const struct sdhc_stm32_config *sdhc_config = (const struct sdhc_stm32_config *)dev->config; |
| struct sdhc_stm32_data *data = dev->data; |
| struct sdhc_host_props *props = &data->props; |
| |
| memset(props, 0, sizeof(struct sdhc_host_props)); |
| |
| props->f_min = sdhc_config->min_freq; |
| props->f_max = sdhc_config->max_freq; |
| props->power_delay = sdhc_config->power_delay_ms; |
| props->host_caps.vol_330_support = true; |
| props->host_caps.vol_180_support = sdhc_config->support_1_8_v; |
| props->host_caps.bus_8_bit_support = (sdhc_config->bus_width == SDHC_BUS_WIDTH8BIT); |
| props->host_caps.bus_4_bit_support = (sdhc_config->bus_width == SDHC_BUS_WIDTH4BIT); |
| } |
| |
| static int sdhc_stm32_get_host_props(const struct device *dev, struct sdhc_host_props *props) |
| { |
| struct sdhc_stm32_data *data = dev->data; |
| |
| memcpy(props, &data->props, sizeof(struct sdhc_host_props)); |
| |
| return 0; |
| } |
| |
| static int sdhc_stm32_get_card_present(const struct device *dev) |
| { |
| int res = 0; |
| struct sdhc_stm32_data *dev_data = dev->data; |
| const struct sdhc_stm32_config *config = dev->config; |
| |
| /* If a CD pin is configured, use it for card detection */ |
| if (config->cd_gpio.port != NULL) { |
| res = gpio_pin_get_dt(&config->cd_gpio); |
| return res; |
| } |
| |
| (void)pm_device_runtime_get(dev); |
| /* Prevent the clocks to be stopped during the request */ |
| pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| k_mutex_lock(&dev_data->bus_mutex, K_FOREVER); |
| |
| /* Card is considered present if the read command did not time out */ |
| if (SDMMC_CmdSendOperationcondition(config->hsd->Instance, 0, NULL) != 0) { |
| res = -EIO; |
| sdhc_stm32_log_err_type(config->hsd); |
| } |
| k_mutex_unlock(&dev_data->bus_mutex); |
| pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| (void)pm_device_runtime_put(dev); |
| |
| return res == 0; |
| } |
| |
| static int sdhc_stm32_card_busy(const struct device *dev) |
| { |
| const struct sdhc_stm32_config *config = dev->config; |
| |
| return HAL_SDIO_GetState(config->hsd) == HAL_SDIO_STATE_BUSY; |
| } |
| |
| static int sdhc_stm32_reset(const struct device *dev) |
| { |
| HAL_StatusTypeDef res; |
| struct sdhc_stm32_data *data = dev->data; |
| const struct sdhc_stm32_config *config = dev->config; |
| |
| (void)pm_device_runtime_get(dev); |
| /* Prevent the clocks to be stopped during the request */ |
| pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| k_mutex_lock(&data->bus_mutex, K_FOREVER); |
| |
| /* Resetting Host controller */ |
| (void)SDMMC_PowerState_OFF(config->hsd->Instance); |
| k_msleep(data->props.power_delay); |
| (void)SDMMC_PowerState_ON(config->hsd->Instance); |
| k_msleep(data->props.power_delay); |
| |
| /* Resetting card */ |
| res = HAL_SDIO_CardReset(config->hsd); |
| if (res != HAL_OK) { |
| LOG_ERR("Card reset failed"); |
| } |
| |
| k_mutex_unlock(&data->bus_mutex); |
| pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| (void)pm_device_runtime_put(dev); |
| |
| return res == HAL_OK ? 0 : -EIO; |
| } |
| |
| static DEVICE_API(sdhc, sdhc_stm32_api) = { |
| .request = sdhc_stm32_request, |
| .set_io = sdhc_stm32_set_io, |
| .get_host_props = sdhc_stm32_get_host_props, |
| .get_card_present = sdhc_stm32_get_card_present, |
| .card_busy = sdhc_stm32_card_busy, |
| .reset = sdhc_stm32_reset, |
| }; |
| |
| void sdhc_stm32_event_isr(const struct device *dev) |
| { |
| uint32_t icr_clear_flag = 0; |
| struct sdhc_stm32_data *data = dev->data; |
| const struct sdhc_stm32_config *config = dev->config; |
| |
| if (__HAL_SDIO_GET_FLAG(config->hsd, |
| SDMMC_FLAG_DATAEND | SDMMC_FLAG_DCRCFAIL | SDMMC_FLAG_DTIMEOUT | |
| SDMMC_FLAG_RXOVERR | SDMMC_FLAG_TXUNDERR)) { |
| k_sem_give(&data->device_sync_sem); |
| } |
| |
| if ((config->hsd->Instance->STA & SDMMC_STA_DCRCFAIL) != 0U) { |
| icr_clear_flag |= SDMMC_ICR_DCRCFAILC; |
| } |
| if ((config->hsd->Instance->STA & SDMMC_STA_DTIMEOUT) != 0U) { |
| icr_clear_flag |= SDMMC_ICR_DTIMEOUTC; |
| } |
| if ((config->hsd->Instance->STA & SDMMC_STA_TXUNDERR) != 0U) { |
| icr_clear_flag |= SDMMC_ICR_TXUNDERRC; |
| } |
| if ((config->hsd->Instance->STA & SDMMC_STA_RXOVERR) != 0U) { |
| icr_clear_flag |= SDMMC_ICR_RXOVERRC; |
| } |
| if (icr_clear_flag) { |
| LOG_ERR("SDMMC interrupt err flag raised: 0x%08X", icr_clear_flag); |
| config->hsd->Instance->ICR = icr_clear_flag; |
| } |
| |
| HAL_SDIO_IRQHandler(config->hsd); |
| } |
| |
| static int sdhc_stm32_init(const struct device *dev) |
| { |
| int ret; |
| struct sdhc_stm32_data *data = dev->data; |
| const struct sdhc_stm32_config *config = dev->config; |
| |
| if (config->sdhi_on_gpio.port != NULL) { |
| if (sdhi_power_on(dev) != 0) { |
| LOG_ERR("Failed to power card on"); |
| return -ENODEV; |
| } |
| } |
| |
| if (config->cd_gpio.port != NULL) { |
| if (!device_is_ready(config->cd_gpio.port)) { |
| LOG_ERR("Card detect GPIO device not ready"); |
| return -ENODEV; |
| } |
| |
| ret = gpio_pin_configure_dt(&config->cd_gpio, GPIO_INPUT); |
| if (ret < 0) { |
| LOG_ERR("Couldn't configure card-detect pin; (%d)", ret); |
| return ret; |
| } |
| } |
| |
| ret = sdhc_stm32_activate(dev); |
| if (ret != 0) { |
| LOG_ERR("Clock and GPIO could not be initialized for the SDHC module, err=%d", ret); |
| return ret; |
| } |
| |
| ret = sdhc_stm32_sd_init(dev); |
| if (ret != 0) { |
| LOG_ERR("SDIO Init Failed"); |
| sdhc_stm32_log_err_type(config->hsd); |
| return ret; |
| } |
| |
| LOG_INF("SDIO Init Passed Successfully"); |
| |
| sdhc_stm32_init_props(dev); |
| |
| config->irq_config_func(); |
| k_sem_init(&data->device_sync_sem, 0, K_SEM_MAX_LIMIT); |
| k_mutex_init(&data->bus_mutex); |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_PM_DEVICE |
| static int sdhc_stm32_suspend(const struct device *dev) |
| { |
| int ret; |
| const struct sdhc_stm32_config *cfg = (struct sdhc_stm32_config *)dev->config; |
| const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); |
| |
| /* Disable device clock. */ |
| ret = clock_control_off(clk, (clock_control_subsys_t)(uintptr_t)&cfg->pclken[0]); |
| if (ret < 0) { |
| LOG_ERR("Failed to disable SDHC clock during PM suspend process"); |
| return ret; |
| } |
| |
| /* Move pins to sleep state */ |
| ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_SLEEP); |
| if (ret == -ENOENT) { |
| /* Warn but don't block suspend */ |
| LOG_WRN_ONCE("SDHC pinctrl sleep state not available"); |
| return 0; |
| } |
| |
| return ret; |
| } |
| |
| static int sdhc_stm32_pm_action(const struct device *dev, enum pm_device_action action) |
| { |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| return sdhc_stm32_activate(dev); |
| case PM_DEVICE_ACTION_SUSPEND: |
| return sdhc_stm32_suspend(dev); |
| default: |
| return -ENOTSUP; |
| } |
| } |
| #endif /* CONFIG_PM_DEVICE */ |
| |
| #define STM32_SDHC_IRQ_HANDLER(index) \ |
| static void sdhc_stm32_irq_config_func_##index(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQ_BY_NAME(index, event, irq), \ |
| DT_INST_IRQ_BY_NAME(index, event, priority), sdhc_stm32_event_isr, \ |
| DEVICE_DT_INST_GET(index), 0); \ |
| irq_enable(DT_INST_IRQ_BY_NAME(index, event, irq)); \ |
| } |
| |
| #define SDHC_STM32_INIT(index) \ |
| \ |
| STM32_SDHC_IRQ_HANDLER(index) \ |
| \ |
| static SDIO_HandleTypeDef hsd_##index; \ |
| \ |
| static const struct stm32_pclken pclken_##index[] = STM32_DT_INST_CLOCKS(index); \ |
| \ |
| PINCTRL_DT_INST_DEFINE(index); \ |
| \ |
| static const struct sdhc_stm32_config sdhc_stm32_cfg_##index = { \ |
| .hsd = &hsd_##index, \ |
| .reg_addr = DT_INST_REG_ADDR(index), \ |
| .irq_config_func = sdhc_stm32_irq_config_func_##index, \ |
| .pclken = pclken_##index, \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \ |
| .hw_flow_control = DT_INST_PROP(index, hw_flow_control), \ |
| .clk_div = DT_INST_PROP(index, clk_div), \ |
| .bus_width = DT_INST_PROP(index, bus_width), \ |
| .power_delay_ms = DT_INST_PROP(index, power_delay_ms), \ |
| .support_1_8_v = DT_INST_PROP(index, support_1_8_v), \ |
| .sdhi_on_gpio = GPIO_DT_SPEC_GET_OR(DT_DRV_INST(index), sdhi_on_gpios, {0}), \ |
| .cd_gpio = GPIO_DT_SPEC_GET_OR(DT_DRV_INST(index), cd_gpios, {0}), \ |
| .min_freq = DT_INST_PROP(index, min_bus_freq), \ |
| .max_freq = DT_INST_PROP(index, max_bus_freq), \ |
| }; \ |
| \ |
| static struct sdhc_stm32_data sdhc_stm32_data_##index; \ |
| \ |
| PM_DEVICE_DT_INST_DEFINE(index, sdhc_stm32_pm_action); \ |
| \ |
| DEVICE_DT_INST_DEFINE(index, &sdhc_stm32_init, NULL, &sdhc_stm32_data_##index, \ |
| &sdhc_stm32_cfg_##index, POST_KERNEL, CONFIG_SDHC_INIT_PRIORITY,\ |
| &sdhc_stm32_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SDHC_STM32_INIT) |