/* * Copyright (c) 2020 Amarula Solutions. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT st_stm32_sdmmc #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(stm32_sdmmc, CONFIG_SDMMC_LOG_LEVEL); #define STM32_SDMMC_USE_DMA DT_NODE_HAS_PROP(DT_DRV_INST(0), dmas) #if STM32_SDMMC_USE_DMA #include #include #include #endif #ifndef MMC_TypeDef #define MMC_TypeDef SDMMC_TypeDef #endif #ifndef SDMMC_BUS_WIDE_1B #define SDMMC_BUS_WIDE_1B SDIO_BUS_WIDE_1B #endif #ifndef SDMMC_BUS_WIDE_4B #define SDMMC_BUS_WIDE_4B SDIO_BUS_WIDE_4B #endif #ifndef SDMMC_BUS_WIDE_8B #define SDMMC_BUS_WIDE_8B SDIO_BUS_WIDE_8B #endif typedef void (*irq_config_func_t)(const struct device *dev); #if STM32_SDMMC_USE_DMA static const uint32_t table_priority[] = { DMA_PRIORITY_LOW, DMA_PRIORITY_MEDIUM, DMA_PRIORITY_HIGH, DMA_PRIORITY_VERY_HIGH }; struct sdmmc_dma_stream { const struct device *dev; uint32_t channel; uint32_t channel_nb; DMA_TypeDef *reg; struct dma_config cfg; }; #endif #ifdef CONFIG_SDMMC_STM32_EMMC typedef MMC_HandleTypeDef HandleTypeDef; typedef HAL_MMC_CardInfoTypeDef CardInfoTypeDef; #else typedef SD_HandleTypeDef HandleTypeDef; typedef HAL_SD_CardInfoTypeDef CardInfoTypeDef; #endif struct stm32_sdmmc_priv { irq_config_func_t irq_config; struct k_sem thread_lock; struct k_sem sync; HandleTypeDef hsd; int status; struct k_work work; struct gpio_callback cd_cb; struct gpio_dt_spec cd; struct gpio_dt_spec pe; struct stm32_pclken *pclken; const struct pinctrl_dev_config *pcfg; const struct reset_dt_spec reset; #if STM32_SDMMC_USE_DMA struct sdmmc_dma_stream dma_rx; struct sdmmc_dma_stream dma_tx; #endif }; #ifdef CONFIG_SDMMC_STM32_HWFC static void stm32_sdmmc_fc_enable(struct stm32_sdmmc_priv *priv) { MMC_TypeDef *sdmmcx = priv->hsd.Instance; sdmmcx->CLKCR |= SDMMC_CLKCR_HWFC_EN; } #endif static void stm32_sdmmc_isr(const struct device *dev) { struct stm32_sdmmc_priv *priv = dev->data; #ifdef CONFIG_SDMMC_STM32_EMMC HAL_MMC_IRQHandler(&priv->hsd); #else HAL_SD_IRQHandler(&priv->hsd); #endif } #define DEFINE_HAL_CALLBACK(name) \ void name(HandleTypeDef *hsd) \ { \ struct stm32_sdmmc_priv *priv = CONTAINER_OF(hsd, struct stm32_sdmmc_priv, hsd); \ \ priv->status = hsd->ErrorCode; \ \ k_sem_give(&priv->sync); \ } #ifdef CONFIG_SDMMC_STM32_EMMC DEFINE_HAL_CALLBACK(HAL_MMC_TxCpltCallback); DEFINE_HAL_CALLBACK(HAL_MMC_RxCpltCallback); DEFINE_HAL_CALLBACK(HAL_MMC_ErrorCallback); #else DEFINE_HAL_CALLBACK(HAL_SD_TxCpltCallback); DEFINE_HAL_CALLBACK(HAL_SD_RxCpltCallback); DEFINE_HAL_CALLBACK(HAL_SD_ErrorCallback); #endif static int stm32_sdmmc_clock_enable(struct stm32_sdmmc_priv *priv) { const struct device *clock; /* HSI48 Clock is enabled through using the device tree */ clock = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); if (DT_INST_NUM_CLOCKS(0) > 1) { if (clock_control_configure(clock, (clock_control_subsys_t)&priv->pclken[1], NULL) != 0) { LOG_ERR("Failed to enable SDMMC domain clock"); return -EIO; } } if (IS_ENABLED(CONFIG_SDMMC_STM32_CLOCK_CHECK)) { uint32_t sdmmc_clock_rate; if (clock_control_get_rate(clock, (clock_control_subsys_t)&priv->pclken[1], &sdmmc_clock_rate) != 0) { LOG_ERR("Failed to get SDMMC domain clock rate"); return -EIO; } if (sdmmc_clock_rate != MHZ(48)) { LOG_ERR("SDMMC Clock is not 48MHz (%d)", sdmmc_clock_rate); return -ENOTSUP; } } /* Enable the APB clock for stm32_sdmmc */ return clock_control_on(clock, (clock_control_subsys_t)&priv->pclken[0]); } #if !defined(CONFIG_SDMMC_STM32_EMMC) static int stm32_sdmmc_clock_disable(struct stm32_sdmmc_priv *priv) { const struct device *clock; clock = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); return clock_control_off(clock, (clock_control_subsys_t)&priv->pclken); } #endif #if STM32_SDMMC_USE_DMA static void stm32_sdmmc_dma_cb(const struct device *dev, void *arg, uint32_t channel, int status) { DMA_HandleTypeDef *hdma = arg; if (status != 0) { LOG_ERR("DMA callback error with channel %d.", channel); } HAL_DMA_IRQHandler(hdma); } static int stm32_sdmmc_configure_dma(DMA_HandleTypeDef *handle, struct sdmmc_dma_stream *dma) { int ret; if (!device_is_ready(dma->dev)) { LOG_ERR("Failed to get dma dev"); return -ENODEV; } dma->cfg.user_data = handle; ret = dma_config(dma->dev, dma->channel, &dma->cfg); if (ret != 0) { LOG_ERR("Failed to conig"); return ret; } handle->Instance = __LL_DMA_GET_STREAM_INSTANCE(dma->reg, dma->channel_nb); handle->Init.Channel = dma->cfg.dma_slot * DMA_CHANNEL_1; handle->Init.PeriphInc = DMA_PINC_DISABLE; handle->Init.MemInc = DMA_MINC_ENABLE; handle->Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; handle->Init.MemDataAlignment = DMA_MDATAALIGN_WORD; handle->Init.Mode = DMA_PFCTRL; handle->Init.Priority = table_priority[dma->cfg.channel_priority], handle->Init.FIFOMode = DMA_FIFOMODE_ENABLE; handle->Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; handle->Init.MemBurst = DMA_MBURST_INC4; handle->Init.PeriphBurst = DMA_PBURST_INC4; return ret; } static int stm32_sdmmc_dma_init(struct stm32_sdmmc_priv *priv) { static DMA_HandleTypeDef dma_tx_handle; static DMA_HandleTypeDef dma_rx_handle; int err; LOG_DBG("using dma"); err = stm32_sdmmc_configure_dma(&dma_tx_handle, &priv->dma_tx); if (err) { LOG_ERR("failed to init tx dma"); return err; } __HAL_LINKDMA(&priv->hsd, hdmatx, dma_tx_handle); HAL_DMA_DeInit(&dma_tx_handle); HAL_DMA_Init(&dma_tx_handle); err = stm32_sdmmc_configure_dma(&dma_rx_handle, &priv->dma_rx); if (err) { LOG_ERR("failed to init rx dma"); return err; } __HAL_LINKDMA(&priv->hsd, hdmarx, dma_rx_handle); HAL_DMA_DeInit(&dma_rx_handle); HAL_DMA_Init(&dma_rx_handle); return err; } #endif static int stm32_sdmmc_access_init(struct disk_info *disk) { const struct device *dev = disk->dev; struct stm32_sdmmc_priv *priv = dev->data; int err; if (priv->status == DISK_STATUS_OK) { return 0; } if (priv->status == DISK_STATUS_NOMEDIA) { return -ENODEV; } #if STM32_SDMMC_USE_DMA err = stm32_sdmmc_dma_init(priv); if (err) { LOG_ERR("DMA init failed"); return err; } #endif err = stm32_sdmmc_clock_enable(priv); if (err) { LOG_ERR("failed to init clocks"); return err; } err = reset_line_toggle_dt(&priv->reset); if (err) { LOG_ERR("failed to reset peripheral"); return err; } #ifdef CONFIG_SDMMC_STM32_EMMC err = HAL_MMC_Init(&priv->hsd); #else err = HAL_SD_Init(&priv->hsd); #endif if (err != HAL_OK) { LOG_ERR("failed to init stm32_sdmmc (ErrorCode 0x%X)", priv->hsd.ErrorCode); return -EIO; } #ifdef CONFIG_SDMMC_STM32_HWFC stm32_sdmmc_fc_enable(priv); #endif priv->status = DISK_STATUS_OK; return 0; } #if !defined(CONFIG_SDMMC_STM32_EMMC) static void stm32_sdmmc_access_deinit(struct stm32_sdmmc_priv *priv) { HAL_SD_DeInit(&priv->hsd); stm32_sdmmc_clock_disable(priv); } #endif static int stm32_sdmmc_access_status(struct disk_info *disk) { const struct device *dev = disk->dev; struct stm32_sdmmc_priv *priv = dev->data; return priv->status; } static int stm32_sdmmc_is_card_in_transfer(HandleTypeDef *hsd) { #ifdef CONFIG_SDMMC_STM32_EMMC return HAL_MMC_GetCardState(hsd) == HAL_MMC_CARD_TRANSFER; #else return HAL_SD_GetCardState(hsd) == HAL_SD_CARD_TRANSFER; #endif } static int stm32_sdmmc_read_blocks(HandleTypeDef *hsd, uint8_t *data_buf, uint32_t start_sector, uint32_t num_sector) { #if STM32_SDMMC_USE_DMA || IS_ENABLED(DT_PROP(DT_DRV_INST(0), idma)) #ifdef CONFIG_SDMMC_STM32_EMMC return HAL_MMC_ReadBlocks_DMA(hsd, data_buf, start_sector, num_sector); #else return HAL_SD_ReadBlocks_DMA(hsd, data_buf, start_sector, num_sector); #endif #else #ifdef CONFIG_SDMMC_STM32_EMMC return HAL_MMC_ReadBlocks_IT(hsd, data_buf, start_sector, num_sector); #else return HAL_SD_ReadBlocks_IT(hsd, data_buf, start_sector, num_sector); #endif #endif } static int stm32_sdmmc_access_read(struct disk_info *disk, uint8_t *data_buf, uint32_t start_sector, uint32_t num_sector) { const struct device *dev = disk->dev; struct stm32_sdmmc_priv *priv = dev->data; int err; k_sem_take(&priv->thread_lock, K_FOREVER); err = stm32_sdmmc_read_blocks(&priv->hsd, data_buf, start_sector, num_sector); if (err != HAL_OK) { LOG_ERR("sd read block failed %d", err); err = -EIO; goto end; } k_sem_take(&priv->sync, K_FOREVER); if (priv->status != DISK_STATUS_OK) { LOG_ERR("sd read error %d", priv->status); err = -EIO; goto end; } while (!stm32_sdmmc_is_card_in_transfer(&priv->hsd)) { } end: k_sem_give(&priv->thread_lock); return err; } static int stm32_sdmmc_write_blocks(HandleTypeDef *hsd, uint8_t *data_buf, uint32_t start_sector, uint32_t num_sector) { #if STM32_SDMMC_USE_DMA || IS_ENABLED(DT_PROP(DT_DRV_INST(0), idma)) #ifdef CONFIG_SDMMC_STM32_EMMC return HAL_MMC_WriteBlocks_DMA(hsd, data_buf, start_sector, num_sector); #else return HAL_SD_WriteBlocks_DMA(hsd, data_buf, start_sector, num_sector); #endif #else #ifdef CONFIG_SDMMC_STM32_EMMC return HAL_MMC_WriteBlocks_IT(hsd, data_buf, start_sector, num_sector); #else return HAL_SD_WriteBlocks_IT(hsd, data_buf, start_sector, num_sector); #endif #endif } static int stm32_sdmmc_access_write(struct disk_info *disk, const uint8_t *data_buf, uint32_t start_sector, uint32_t num_sector) { const struct device *dev = disk->dev; struct stm32_sdmmc_priv *priv = dev->data; int err; k_sem_take(&priv->thread_lock, K_FOREVER); err = stm32_sdmmc_write_blocks(&priv->hsd, (uint8_t *)data_buf, start_sector, num_sector); if (err != HAL_OK) { LOG_ERR("sd write block failed %d", err); err = -EIO; goto end; } k_sem_take(&priv->sync, K_FOREVER); if (priv->status != DISK_STATUS_OK) { LOG_ERR("sd write error %d", priv->status); err = -EIO; goto end; } while (!stm32_sdmmc_is_card_in_transfer(&priv->hsd)) { } end: k_sem_give(&priv->thread_lock); return err; } static int stm32_sdmmc_get_card_info(HandleTypeDef *hsd, CardInfoTypeDef *info) { #ifdef CONFIG_SDMMC_STM32_EMMC return HAL_MMC_GetCardInfo(hsd, info); #else return HAL_SD_GetCardInfo(hsd, info); #endif } static int stm32_sdmmc_access_ioctl(struct disk_info *disk, uint8_t cmd, void *buff) { const struct device *dev = disk->dev; struct stm32_sdmmc_priv *priv = dev->data; CardInfoTypeDef info; int err; switch (cmd) { case DISK_IOCTL_GET_SECTOR_COUNT: err = stm32_sdmmc_get_card_info(&priv->hsd, &info); if (err != HAL_OK) { return -EIO; } *(uint32_t *)buff = info.LogBlockNbr; break; case DISK_IOCTL_GET_SECTOR_SIZE: err = stm32_sdmmc_get_card_info(&priv->hsd, &info); if (err != HAL_OK) { return -EIO; } *(uint32_t *)buff = info.LogBlockSize; break; case DISK_IOCTL_GET_ERASE_BLOCK_SZ: *(uint32_t *)buff = 1; break; case DISK_IOCTL_CTRL_SYNC: /* we use a blocking API, so nothing to do for sync */ break; default: return -EINVAL; } return 0; } static const struct disk_operations stm32_sdmmc_ops = { .init = stm32_sdmmc_access_init, .status = stm32_sdmmc_access_status, .read = stm32_sdmmc_access_read, .write = stm32_sdmmc_access_write, .ioctl = stm32_sdmmc_access_ioctl, }; static struct disk_info stm32_sdmmc_info = { .name = CONFIG_SDMMC_VOLUME_NAME, .ops = &stm32_sdmmc_ops, }; #ifdef CONFIG_SDMMC_STM32_EMMC static bool stm32_sdmmc_card_present(struct stm32_sdmmc_priv *priv) { return true; } #else /* CONFIG_SDMMC_STM32_EMMC */ /* * Check if the card is present or not. If no card detect gpio is set, assume * the card is present. If reading the gpio fails for some reason, assume the * card is there. */ static bool stm32_sdmmc_card_present(struct stm32_sdmmc_priv *priv) { int err; if (!priv->cd.port) { return true; } err = gpio_pin_get_dt(&priv->cd); if (err < 0) { LOG_WRN("reading card detect failed %d", err); return true; } return err; } static void stm32_sdmmc_cd_handler(struct k_work *item) { struct stm32_sdmmc_priv *priv = CONTAINER_OF(item, struct stm32_sdmmc_priv, work); if (stm32_sdmmc_card_present(priv)) { LOG_DBG("card inserted"); priv->status = DISK_STATUS_UNINIT; } else { LOG_DBG("card removed"); stm32_sdmmc_access_deinit(priv); priv->status = DISK_STATUS_NOMEDIA; } } static void stm32_sdmmc_cd_callback(const struct device *gpiodev, struct gpio_callback *cb, uint32_t pin) { struct stm32_sdmmc_priv *priv = CONTAINER_OF(cb, struct stm32_sdmmc_priv, cd_cb); k_work_submit(&priv->work); } static int stm32_sdmmc_card_detect_init(struct stm32_sdmmc_priv *priv) { int err; if (!priv->cd.port) { return 0; } if (!gpio_is_ready_dt(&priv->cd)) { return -ENODEV; } gpio_init_callback(&priv->cd_cb, stm32_sdmmc_cd_callback, 1 << priv->cd.pin); err = gpio_add_callback(priv->cd.port, &priv->cd_cb); if (err) { return err; } err = gpio_pin_configure_dt(&priv->cd, GPIO_INPUT); if (err) { goto remove_callback; } err = gpio_pin_interrupt_configure_dt(&priv->cd, GPIO_INT_EDGE_BOTH); if (err) { goto unconfigure_pin; } return 0; unconfigure_pin: gpio_pin_configure_dt(&priv->cd, GPIO_DISCONNECTED); remove_callback: gpio_remove_callback(priv->cd.port, &priv->cd_cb); return err; } static int stm32_sdmmc_card_detect_uninit(struct stm32_sdmmc_priv *priv) { if (!priv->cd.port) { return 0; } gpio_pin_interrupt_configure_dt(&priv->cd, GPIO_INT_MODE_DISABLED); gpio_pin_configure_dt(&priv->cd, GPIO_DISCONNECTED); gpio_remove_callback(priv->cd.port, &priv->cd_cb); return 0; } #endif /* !CONFIG_SDMMC_STM32_EMMC */ static int stm32_sdmmc_pwr_init(struct stm32_sdmmc_priv *priv) { int err; if (!priv->pe.port) { return 0; } if (!gpio_is_ready_dt(&priv->pe)) { return -ENODEV; } err = gpio_pin_configure_dt(&priv->pe, GPIO_OUTPUT_ACTIVE); if (err) { return err; } k_sleep(K_MSEC(50)); return 0; } static int stm32_sdmmc_pwr_uninit(struct stm32_sdmmc_priv *priv) { if (!priv->pe.port) { return 0; } gpio_pin_configure_dt(&priv->pe, GPIO_DISCONNECTED); return 0; } static int disk_stm32_sdmmc_init(const struct device *dev) { struct stm32_sdmmc_priv *priv = dev->data; const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); int err; if (!device_is_ready(clk)) { LOG_ERR("clock control device not ready"); return -ENODEV; } if (!device_is_ready(priv->reset.dev)) { LOG_ERR("reset control device not ready"); return -ENODEV; } /* Configure dt provided device signals when available */ err = pinctrl_apply_state(priv->pcfg, PINCTRL_STATE_DEFAULT); if (err < 0) { return err; } priv->irq_config(dev); /* Initialize semaphores */ k_sem_init(&priv->thread_lock, 1, 1); k_sem_init(&priv->sync, 0, 1); #if !defined(CONFIG_SDMMC_STM32_EMMC) k_work_init(&priv->work, stm32_sdmmc_cd_handler); err = stm32_sdmmc_card_detect_init(priv); if (err) { return err; } #endif err = stm32_sdmmc_pwr_init(priv); if (err) { goto err_card_detect; } if (stm32_sdmmc_card_present(priv)) { priv->status = DISK_STATUS_UNINIT; } else { priv->status = DISK_STATUS_NOMEDIA; } stm32_sdmmc_info.dev = dev; err = disk_access_register(&stm32_sdmmc_info); if (err) { goto err_pwr; } return 0; err_pwr: stm32_sdmmc_pwr_uninit(priv); err_card_detect: #if !defined(CONFIG_SDMMC_STM32_EMMC) stm32_sdmmc_card_detect_uninit(priv); #endif return err; } #if DT_NODE_HAS_STATUS(DT_DRV_INST(0), okay) #if STM32_SDMMC_USE_DMA #define SDMMC_DMA_CHANNEL_INIT(dir, dir_cap) \ .dev = DEVICE_DT_GET(STM32_DMA_CTLR(0, dir)), \ .channel = DT_INST_DMAS_CELL_BY_NAME(0, dir, channel), \ .channel_nb = DT_DMAS_CELL_BY_NAME( \ DT_DRV_INST(0), dir, channel), \ .reg = (DMA_TypeDef *)DT_REG_ADDR( \ DT_PHANDLE_BY_NAME(DT_DRV_INST(0), dmas, dir)), \ .cfg = { \ .dma_slot = STM32_DMA_SLOT(0, dir, slot), \ .channel_priority = STM32_DMA_CONFIG_PRIORITY( \ STM32_DMA_CHANNEL_CONFIG(0, dir)), \ .dma_callback = stm32_sdmmc_dma_cb, \ .linked_channel = STM32_DMA_HAL_OVERRIDE, \ }, \ #define SDMMC_DMA_CHANNEL(dir, DIR) \ .dma_##dir = { \ COND_CODE_1(DT_INST_DMAS_HAS_NAME(0, dir), \ (SDMMC_DMA_CHANNEL_INIT(dir, DIR)), \ (NULL)) \ }, #else #define SDMMC_DMA_CHANNEL(dir, DIR) #endif PINCTRL_DT_INST_DEFINE(0); static void stm32_sdmmc_irq_config_func(const struct device *dev) { IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), stm32_sdmmc_isr, DEVICE_DT_INST_GET(0), 0); irq_enable(DT_INST_IRQN(0)); } #if DT_INST_PROP(0, bus_width) == 1 #define SDMMC_BUS_WIDTH SDMMC_BUS_WIDE_1B #elif DT_INST_PROP(0, bus_width) == 4 #define SDMMC_BUS_WIDTH SDMMC_BUS_WIDE_4B #elif DT_INST_PROP(0, bus_width) == 8 #define SDMMC_BUS_WIDTH SDMMC_BUS_WIDE_8B #endif /* DT_INST_PROP(0, bus_width) */ static struct stm32_pclken pclken_sdmmc[] = STM32_DT_INST_CLOCKS(0); static struct stm32_sdmmc_priv stm32_sdmmc_priv_1 = { .irq_config = stm32_sdmmc_irq_config_func, .hsd = { .Instance = (MMC_TypeDef *)DT_INST_REG_ADDR(0), .Init.BusWide = SDMMC_BUS_WIDTH, #if DT_INST_NODE_HAS_PROP(0, clk_div) .Init.ClockDiv = DT_INST_PROP(0, clk_div), #endif }, #if DT_INST_NODE_HAS_PROP(0, cd_gpios) .cd = GPIO_DT_SPEC_INST_GET(0, cd_gpios), #endif #if DT_INST_NODE_HAS_PROP(0, pwr_gpios) .pe = GPIO_DT_SPEC_INST_GET(0, pwr_gpios), #endif .pclken = pclken_sdmmc, .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), .reset = RESET_DT_SPEC_INST_GET(0), SDMMC_DMA_CHANNEL(rx, RX) SDMMC_DMA_CHANNEL(tx, TX) }; DEVICE_DT_INST_DEFINE(0, disk_stm32_sdmmc_init, NULL, &stm32_sdmmc_priv_1, NULL, POST_KERNEL, CONFIG_SD_INIT_PRIORITY, NULL); #endif