You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
824 lines
19 KiB
824 lines
19 KiB
/* |
|
* Copyright (c) 2020 Amarula Solutions. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT st_stm32_sdmmc |
|
|
|
#include <zephyr/devicetree.h> |
|
#include <zephyr/drivers/disk.h> |
|
#include <zephyr/drivers/clock_control.h> |
|
#include <zephyr/drivers/clock_control/stm32_clock_control.h> |
|
#include <zephyr/drivers/pinctrl.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/reset.h> |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/irq.h> |
|
#include <soc.h> |
|
#include <stm32_ll_rcc.h> |
|
|
|
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 <zephyr/drivers/dma.h> |
|
#include <zephyr/drivers/dma/dma_stm32.h> |
|
#include <stm32_ll_dma.h> |
|
#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; |
|
DMA_HandleTypeDef dma_tx_handle; |
|
DMA_HandleTypeDef dma_rx_handle; |
|
#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) |
|
{ |
|
int err; |
|
|
|
LOG_DBG("using dma"); |
|
|
|
err = stm32_sdmmc_configure_dma(&priv->dma_tx_handle, &priv->dma_tx); |
|
if (err) { |
|
LOG_ERR("failed to init tx dma"); |
|
return err; |
|
} |
|
__HAL_LINKDMA(&priv->hsd, hdmatx, priv->dma_tx_handle); |
|
HAL_DMA_Init(&priv->dma_tx_handle); |
|
|
|
err = stm32_sdmmc_configure_dma(&priv->dma_rx_handle, &priv->dma_rx); |
|
if (err) { |
|
LOG_ERR("failed to init rx dma"); |
|
return err; |
|
} |
|
__HAL_LINKDMA(&priv->hsd, hdmarx, priv->dma_rx_handle); |
|
HAL_DMA_Init(&priv->dma_rx_handle); |
|
|
|
return err; |
|
} |
|
|
|
static int stm32_sdmmc_dma_deinit(struct stm32_sdmmc_priv *priv) |
|
{ |
|
int ret; |
|
struct sdmmc_dma_stream *dma_tx = &priv->dma_tx; |
|
struct sdmmc_dma_stream *dma_rx = &priv->dma_rx; |
|
|
|
ret = dma_stop(dma_tx->dev, dma_tx->channel); |
|
HAL_DMA_DeInit(&priv->dma_tx_handle); |
|
if (ret != 0) { |
|
LOG_ERR("Failed to stop tx DMA transmission"); |
|
return ret; |
|
} |
|
ret = dma_stop(dma_rx->dev, dma_rx->channel); |
|
HAL_DMA_DeInit(&priv->dma_rx_handle); |
|
if (ret != 0) { |
|
LOG_ERR("Failed to stop rx DMA transmission"); |
|
return ret; |
|
} |
|
return ret; |
|
} |
|
|
|
#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_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; |
|
} |
|
|
|
static int stm32_sdmmc_access_deinit(struct stm32_sdmmc_priv *priv) |
|
{ |
|
int err = 0; |
|
|
|
#if STM32_SDMMC_USE_DMA |
|
err = stm32_sdmmc_dma_deinit(priv); |
|
if (err) { |
|
LOG_ERR("DMA deinit failed"); |
|
return err; |
|
} |
|
#endif |
|
|
|
#if defined(CONFIG_SDMMC_STM32_EMMC) |
|
err = HAL_MMC_DeInit(&priv->hsd); |
|
#else |
|
err = HAL_SD_DeInit(&priv->hsd); |
|
stm32_sdmmc_clock_disable(priv); |
|
#endif |
|
if (err != HAL_OK) { |
|
LOG_ERR("failed to deinit stm32_sdmmc (ErrorCode 0x%X)", priv->hsd.ErrorCode); |
|
return err; |
|
} |
|
|
|
priv->status = DISK_STATUS_UNINIT; |
|
return 0; |
|
} |
|
|
|
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; |
|
case DISK_IOCTL_CTRL_INIT: |
|
return stm32_sdmmc_access_init(disk); |
|
case DISK_IOCTL_CTRL_DEINIT: |
|
return stm32_sdmmc_access_deinit(priv); |
|
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_OKAY(DT_DRV_INST(0)) |
|
|
|
#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
|
|
|