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.
828 lines
24 KiB
828 lines
24 KiB
/* |
|
* Copyright (c) 2025 Ambiq Micro Inc. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT ambiq_sdio |
|
|
|
#include <zephyr/devicetree.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/cache.h> |
|
#include <zephyr/irq.h> |
|
#include <zephyr/drivers/sdhc.h> |
|
#include <zephyr/drivers/pinctrl.h> |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/pm/device.h> |
|
#include <zephyr/pm/policy.h> |
|
#include <zephyr/pm/device_runtime.h> |
|
|
|
#include <soc.h> |
|
|
|
LOG_MODULE_REGISTER(ambiq_sdio, CONFIG_SDHC_LOG_LEVEL); |
|
|
|
#define CACHABLE_START_ADDR SSRAM_BASEADDR |
|
|
|
struct ambiq_sdio_config { |
|
SDIO_Type *pSDHC; |
|
const struct pinctrl_dev_config *pincfg; |
|
void (*irq_config_func)(const struct device *dev); |
|
uint32_t inst; |
|
uint8_t tx_delay; |
|
uint8_t rx_delay; |
|
uint32_t max_bus_freq; |
|
uint32_t min_bus_freq; |
|
uint32_t power_delay_ms; |
|
}; |
|
|
|
struct ambiq_sdio_data { |
|
am_hal_card_t card; |
|
am_hal_card_host_t *host; |
|
sdhc_interrupt_cb_t sdio_cb; |
|
void *sdio_cb_user_data; |
|
struct k_mutex access_mutex; |
|
#ifdef CONFIG_AMBIQ_SDIO_ASYNC |
|
struct k_sem *async_sem; |
|
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */ |
|
}; |
|
|
|
#ifdef CONFIG_AMBIQ_SDIO_ASYNC |
|
/* Semaphore for SDIO0 Async Data Transfer */ |
|
static K_SEM_DEFINE(sdio_async_sem_0, 0, 1); |
|
|
|
/* |
|
* SDIO0 Async Data Transfer Callback Function |
|
*/ |
|
static void ambiq_sdio_event_cb_0(am_hal_host_evt_t *pEvt) |
|
{ |
|
am_hal_card_host_t *pHost = (am_hal_card_host_t *)pEvt->pCtx; |
|
|
|
if (AM_HAL_EVT_XFER_COMPLETE == pEvt->eType && |
|
pHost->AsyncCmdData.dir == AM_HAL_DATA_DIR_READ) { |
|
LOG_DBG("Last Read Xfered block %d\n", pEvt->ui32BlkCnt); |
|
k_sem_give(&sdio_async_sem_0); |
|
} else if (AM_HAL_EVT_XFER_COMPLETE == pEvt->eType && |
|
pHost->AsyncCmdData.dir == AM_HAL_DATA_DIR_WRITE) { |
|
LOG_DBG("Last Write Xfered block %d\n", pEvt->ui32BlkCnt); |
|
k_sem_give(&sdio_async_sem_0); |
|
} else if (AM_HAL_EVT_CARD_INT == pEvt->eType) { |
|
LOG_DBG("SDIO Card Interrupt\n"); |
|
} |
|
} |
|
|
|
/* Semaphore for SDIO1 Async Data Transfer */ |
|
static K_SEM_DEFINE(sdio_async_sem_1, 0, 1); |
|
|
|
/* |
|
* SDIO1 Async Data Transfer Callback Function |
|
*/ |
|
static void ambiq_sdio_event_cb_1(am_hal_host_evt_t *pEvt) |
|
{ |
|
am_hal_card_host_t *pHost = (am_hal_card_host_t *)pEvt->pCtx; |
|
|
|
if (AM_HAL_EVT_XFER_COMPLETE == pEvt->eType && |
|
pHost->AsyncCmdData.dir == AM_HAL_DATA_DIR_READ) { |
|
LOG_DBG("Last Read Xfered block %d\n", pEvt->ui32BlkCnt); |
|
k_sem_give(&sdio_async_sem_1); |
|
} else if (AM_HAL_EVT_XFER_COMPLETE == pEvt->eType && |
|
pHost->AsyncCmdData.dir == AM_HAL_DATA_DIR_WRITE) { |
|
LOG_DBG("Last Write Xfered block %d\n", pEvt->ui32BlkCnt); |
|
k_sem_give(&sdio_async_sem_1); |
|
} else if (AM_HAL_EVT_CARD_INT == pEvt->eType) { |
|
LOG_DBG("SDIO Card Interrupt\n"); |
|
} |
|
} |
|
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */ |
|
|
|
/* |
|
* Ambiq SDIO interrupt service routine |
|
*/ |
|
static void ambiq_sdio_isr(const struct device *dev) |
|
{ |
|
uint32_t ui32IntStatus; |
|
struct ambiq_sdio_data *data = dev->data; |
|
|
|
am_hal_sdhc_intr_status_get(data->host->pHandle, &ui32IntStatus, true); |
|
am_hal_sdhc_intr_status_clear(data->host->pHandle, ui32IntStatus); |
|
am_hal_sdhc_interrupt_service(data->host->pHandle, ui32IntStatus); |
|
if (data->sdio_cb != NULL && data->host->Evt.eType == AM_HAL_EVT_CARD_INT) { |
|
data->sdio_cb(dev, SDHC_INT_SDIO, data->sdio_cb_user_data); |
|
} |
|
} |
|
|
|
/* |
|
* Ambiq SDIO host controller software reset |
|
*/ |
|
static int ambiq_sdio_reset(const struct device *dev) |
|
{ |
|
const struct ambiq_sdio_config *config = dev->config; |
|
uint32_t ui32Status = 0; |
|
int ret = 0; |
|
|
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
ret = pm_device_runtime_get(dev); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_get failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
|
|
LOG_DBG("SDHC Software Reset"); |
|
ui32Status = am_hal_sdhc_software_reset(config->pSDHC, AM_HAL_SDHC_SW_RESET_ALL); |
|
if (ui32Status) { |
|
LOG_ERR("SDHC software reset failed, ui32Status = %u", ui32Status); |
|
ret = -EIO; |
|
} |
|
|
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
/* Use async put to avoid useless device suspension/resumption |
|
* when doing consecutive transmission. |
|
*/ |
|
ret = pm_device_runtime_put_async(dev, K_MSEC(2)); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_put failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* Get Ambiq SDIO Host controller properties |
|
*/ |
|
static int ambiq_sdio_get_host_props(const struct device *dev, struct sdhc_host_props *props) |
|
{ |
|
const struct ambiq_sdio_config *config = dev->config; |
|
|
|
LOG_DBG("SDHC get host props"); |
|
memset(props, 0, sizeof(*props)); |
|
props->f_max = config->max_bus_freq; |
|
props->f_min = config->min_bus_freq; |
|
props->power_delay = config->power_delay_ms; |
|
props->host_caps.suspend_res_support = true; |
|
props->host_caps.adma_2_support = true; |
|
props->host_caps.sdio_async_interrupt_support = true; |
|
props->host_caps.vol_180_support = true; |
|
props->host_caps.bus_4_bit_support = true; |
|
props->host_caps.bus_8_bit_support = true; |
|
props->host_caps.high_spd_support = true; |
|
props->host_caps.sdr50_support = true; |
|
props->host_caps.sdr104_support = true; |
|
props->host_caps.ddr50_support = true; |
|
props->host_caps.hs200_support = true; |
|
props->max_current_330 = 1020; |
|
props->max_current_300 = 1020; |
|
props->max_current_180 = 1020; |
|
props->is_spi = false; |
|
return 0; |
|
} |
|
|
|
/* |
|
* Set I/O properties of SDIO host controller |
|
*/ |
|
static int ambiq_sdio_set_io(const struct device *dev, struct sdhc_io *ios) |
|
{ |
|
const struct ambiq_sdio_config *config = dev->config; |
|
struct ambiq_sdio_data *data = dev->data; |
|
am_hal_host_bus_voltage_e eBusVoltage; |
|
am_hal_host_bus_width_e eBusWidth; |
|
am_hal_host_uhs_mode_e eUHSMode = AM_HAL_HOST_UHS_SDR50; |
|
uint32_t ui32Status = 0; |
|
int ret = 0; |
|
|
|
LOG_DBG("(SDIO clock_freq=%d, bus_width=%d, timing_mode=%d, bus_mode=%d)", ios->clock, |
|
ios->bus_width, ios->timing, ios->bus_mode); |
|
|
|
if (ios->clock != 0 && (ios->clock <= config->max_bus_freq) && |
|
(ios->clock >= config->min_bus_freq)) { |
|
data->card.cfg.ui32Clock = ios->clock; |
|
} else if (ios->clock != 0 && (ios->clock > config->max_bus_freq) && |
|
(ios->clock <= MMC_CLOCK_HS200)) { |
|
/*Limit HS200 frequency to config->max_bus_freq*/ |
|
data->card.cfg.ui32Clock = config->max_bus_freq; |
|
eUHSMode = AM_HAL_HOST_UHS_SDR104; |
|
} else if (ios->clock != 0) { |
|
return -ENOTSUP; |
|
} |
|
|
|
if (ios->bus_mode != SDHC_BUSMODE_PUSHPULL) { |
|
return -ENOTSUP; |
|
} |
|
|
|
switch (ios->bus_width) { |
|
case SDHC_BUS_WIDTH1BIT: |
|
eBusWidth = AM_HAL_HOST_BUS_WIDTH_1; |
|
break; |
|
case SDHC_BUS_WIDTH4BIT: |
|
eBusWidth = AM_HAL_HOST_BUS_WIDTH_4; |
|
break; |
|
case SDHC_BUS_WIDTH8BIT: |
|
eBusWidth = AM_HAL_HOST_BUS_WIDTH_8; |
|
break; |
|
default: |
|
return -ENOTSUP; |
|
} |
|
|
|
switch (ios->signal_voltage) { |
|
case SD_VOL_3_3_V: |
|
eBusVoltage = AM_HAL_HOST_BUS_VOLTAGE_3_3; |
|
break; |
|
case SD_VOL_3_0_V: |
|
eBusVoltage = AM_HAL_HOST_BUS_VOLTAGE_3_0; |
|
break; |
|
case SD_VOL_1_8_V: |
|
eBusVoltage = AM_HAL_HOST_BUS_VOLTAGE_1_8; |
|
break; |
|
default: |
|
return -ENOTSUP; |
|
} |
|
|
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
ret = pm_device_runtime_get(dev); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_get failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
|
|
/* Change SDIO Host Bus Voltage */ |
|
if (eBusVoltage != data->card.cfg.eIoVoltage) { |
|
data->card.cfg.eIoVoltage = eBusVoltage; |
|
ui32Status = data->host->ops->set_bus_voltage(data->host->pHandle, eBusVoltage); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
#ifdef CONFIG_PM_DEVICE_RUNTIME |
|
pm_device_runtime_put(dev); |
|
#endif |
|
return -ENOTSUP; |
|
} |
|
} |
|
|
|
/* Change SDIO Host Bus Width */ |
|
if (eBusWidth != data->card.cfg.eBusWidth) { |
|
data->card.cfg.eBusWidth = eBusWidth; |
|
ui32Status = data->host->ops->set_bus_width(data->host->pHandle, eBusWidth); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
#ifdef CONFIG_PM_DEVICE_RUNTIME |
|
pm_device_runtime_put(dev); |
|
#endif |
|
return -ENOTSUP; |
|
} |
|
} |
|
|
|
/* Change SDIO Host Clock Speed */ |
|
ui32Status = data->host->ops->set_bus_clock(data->host->pHandle, data->card.cfg.ui32Clock); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
#ifdef CONFIG_PM_DEVICE_RUNTIME |
|
pm_device_runtime_put(dev); |
|
#endif |
|
return -ENOTSUP; |
|
} |
|
|
|
if (ios->timing == SDHC_TIMING_DDR52) { |
|
LOG_DBG("MMC Card DDR50 Mode"); |
|
/* DDR50 mode must be 4bit or 8bit width according to EMMC Spec */ |
|
if (eBusWidth == AM_HAL_HOST_BUS_WIDTH_1) { |
|
#ifdef CONFIG_PM_DEVICE_RUNTIME |
|
pm_device_runtime_put(dev); |
|
#endif |
|
return -ENOTSUP; |
|
} |
|
eUHSMode = AM_HAL_HOST_UHS_DDR50; |
|
} |
|
|
|
/* Change SDIO Host UHS mode */ |
|
if (eUHSMode != data->card.cfg.eUHSMode) { |
|
data->card.cfg.eUHSMode = eUHSMode; |
|
ui32Status = data->host->ops->set_uhs_mode(data->host->pHandle, eUHSMode); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
#ifdef CONFIG_PM_DEVICE_RUNTIME |
|
pm_device_runtime_put(dev); |
|
#endif |
|
return -ENOTSUP; |
|
} |
|
} |
|
|
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
/* Use async put to avoid useless device suspension/resumption |
|
* when doing consecutive transmission. |
|
*/ |
|
ret = pm_device_runtime_put_async(dev, K_MSEC(2)); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_put failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* Ambiq SDIO Host Initialization Function |
|
*/ |
|
static int ambiq_sdio_init(const struct device *dev) |
|
{ |
|
const struct ambiq_sdio_config *config = dev->config; |
|
struct ambiq_sdio_data *data = dev->data; |
|
int ret; |
|
|
|
LOG_DBG("Ambiq SDIO Initialize Host #%d", config->inst); |
|
|
|
ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
/* Get the uderlying SDHC host instance */ |
|
data->host = am_hal_get_card_host(AM_HAL_SDHC_CARD_HOST + config->inst, true); |
|
|
|
if (data->host == NULL) { |
|
LOG_ERR("No such card host and stop"); |
|
return -ENODEV; |
|
} |
|
|
|
config->irq_config_func(dev); |
|
|
|
/* Fill the Card default setting with the host's default value */ |
|
data->card.pHost = data->host; |
|
data->card.cfg.eBusWidth = data->host->eBusWidth; |
|
data->card.cfg.eIoVoltage = data->host->eBusVoltage; |
|
data->card.cfg.ui32Clock = data->host->ui32MinClock; |
|
data->card.cfg.eUHSMode = data->host->eUHSMode; |
|
|
|
/* Set AM_HAL_CARD_PWR_CTRL_SDHC_OFF as default SDIO power control policy */ |
|
data->card.eCardPwrCtrlPolicy = AM_HAL_CARD_PWR_CTRL_SDHC_OFF; |
|
data->card.eState = AM_HAL_CARD_STATE_PWRON; |
|
data->card.pCardPwrCtrlFunc = NULL; |
|
|
|
#ifdef CONFIG_AMBIQ_SDIO_ASYNC |
|
/* Register callback function for Async data transfer */ |
|
if (config->inst == 0) { |
|
data->async_sem = &sdio_async_sem_0; |
|
am_hal_card_register_evt_callback(&data->card, ambiq_sdio_event_cb_0); |
|
} else if (config->inst == 1) { |
|
data->async_sem = &sdio_async_sem_1; |
|
am_hal_card_register_evt_callback(&data->card, ambiq_sdio_event_cb_1); |
|
} else { |
|
return -ENODEV; |
|
} |
|
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */ |
|
|
|
k_mutex_init(&data->access_mutex); |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Set TX & RX delay for Ambiq SDIO Timing Tuning |
|
*/ |
|
static int ambiq_sdio_execute_tuning(const struct device *dev) |
|
{ |
|
const struct ambiq_sdio_config *config = dev->config; |
|
struct ambiq_sdio_data *data = dev->data; |
|
uint8_t ui8TxRxDelays[2] = {0}; |
|
int ret = 0; |
|
|
|
/* SDIO TX delay setting range is 0 ~ 15 */ |
|
if (config->tx_delay < 16) { |
|
ui8TxRxDelays[0] = config->tx_delay; |
|
} else { |
|
return -EINVAL; |
|
} |
|
|
|
/* SDIO RX delay setting range is 0 ~ 31 */ |
|
if (config->rx_delay < 32) { |
|
ui8TxRxDelays[1] = config->rx_delay; |
|
} else { |
|
return -EINVAL; |
|
} |
|
|
|
/* Timing delay is disabled if both TX and RX delay are set into 0 */ |
|
if (ui8TxRxDelays[0] != 0 || ui8TxRxDelays[1] != 0) { |
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
ret = pm_device_runtime_get(dev); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_get failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
|
|
am_hal_card_host_set_txrx_delay(data->host, ui8TxRxDelays); |
|
|
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
/* Use async put to avoid useless device suspension/resumption |
|
* when doing consecutive transmission. |
|
*/ |
|
ret = pm_device_runtime_put_async(dev, K_MSEC(2)); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_put failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* Check for Card Presence |
|
*/ |
|
static int ambiq_sdio_get_card_present(const struct device *dev) |
|
{ |
|
struct ambiq_sdio_data *data = dev->data; |
|
int status = 0; |
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
int ret = 0; |
|
|
|
ret = pm_device_runtime_get(dev); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_get failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
LOG_DBG("Get card present status"); |
|
status = data->host->ops->get_cd(data->host->pHandle); |
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
/* Use async put to avoid useless device suspension/resumption |
|
* when doing consecutive transmission. |
|
*/ |
|
ret = pm_device_runtime_put_async(dev, K_MSEC(2)); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_put failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
return status; |
|
} |
|
|
|
/* |
|
* Check for Card Busy Status |
|
*/ |
|
static int ambiq_sdio_card_busy(const struct device *dev) |
|
{ |
|
struct ambiq_sdio_data *data = dev->data; |
|
uint32_t ui32Status; |
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
int ret = 0; |
|
|
|
ret = pm_device_runtime_get(dev); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_get failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
ui32Status = data->host->ops->card_busy(data->host->pHandle, DEFAULT_GET_STATUS_TIMEOUT_MS); |
|
LOG_DBG("Check card busy status"); |
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
/* Use async put to avoid useless device suspension/resumption |
|
* when doing consecutive transmission. |
|
*/ |
|
ret = pm_device_runtime_put_async(dev, K_MSEC(2)); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_put failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
|
|
return (ui32Status != AM_HAL_STATUS_SUCCESS) ? 1 : 0; |
|
} |
|
|
|
/* |
|
* Ambiq SDIO Command and Data Request Function |
|
*/ |
|
static int ambiq_sdio_request(const struct device *dev, struct sdhc_command *cmd, |
|
struct sdhc_data *data) |
|
{ |
|
struct ambiq_sdio_data *dev_data = dev->data; |
|
uint32_t ui32Status = 0; |
|
int ret = 0; |
|
|
|
am_hal_card_cmd_t sdio_cmd = {0}; |
|
am_hal_card_cmd_data_t cmd_data = {0}; |
|
|
|
if (cmd) { |
|
sdio_cmd.ui8Idx = cmd->opcode; |
|
sdio_cmd.ui32Arg = cmd->arg; |
|
sdio_cmd.ui32RespType = cmd->response_type; |
|
sdio_cmd.bASync = false; |
|
} else { |
|
LOG_ERR("Invalid CMD"); |
|
return -EINVAL; |
|
} |
|
|
|
if (data) { |
|
cmd_data.ui32BlkCnt = data->blocks; |
|
cmd_data.ui32BlkSize = data->block_size; |
|
cmd_data.pui8Buf = data->data; |
|
|
|
if (sdio_cmd.ui8Idx == MMC_CMD_READ_MULTIPLE_BLOCK || |
|
sdio_cmd.ui8Idx == MMC_CMD_WRITE_MULTIPLE_BLOCK) { |
|
sdio_cmd.bAutoCMD23 = true; |
|
} |
|
|
|
if (sdio_cmd.ui8Idx == MMC_CMD_WRITE_SINGLE_BLOCK || |
|
sdio_cmd.ui8Idx == MMC_CMD_WRITE_MULTIPLE_BLOCK) { |
|
cmd_data.dir = AM_HAL_DATA_DIR_WRITE; |
|
} else if ((sdio_cmd.ui8Idx == SDIO_CMD_IO_RW_EXTENDED) && |
|
(sdio_cmd.ui32Arg & BIT(SDIO_CMD_ARG_RW_SHIFT))) { |
|
cmd_data.dir = AM_HAL_DATA_DIR_WRITE; |
|
} else { |
|
cmd_data.dir = AM_HAL_DATA_DIR_READ; |
|
} |
|
} |
|
|
|
LOG_DBG("Send SDIO CMD%d", sdio_cmd.ui8Idx); |
|
LOG_DBG("CMD->Arg = 0x%x CMD->RespType = 0x%x", sdio_cmd.ui32Arg, sdio_cmd.ui32RespType); |
|
|
|
if (sdio_cmd.ui8Idx == 1) { |
|
LOG_DBG("Config CMD1 RespType"); |
|
sdio_cmd.ui32RespType = MMC_RSP_R3; |
|
} else if (sdio_cmd.ui8Idx == 3) { |
|
LOG_DBG("Config CMD3 RespType"); |
|
sdio_cmd.ui32RespType = MMC_RSP_R6; |
|
} else if ((sdio_cmd.ui8Idx == 52) || (sdio_cmd.ui8Idx == 53)) { |
|
LOG_DBG("Config CMD%d RespType", sdio_cmd.ui8Idx); |
|
sdio_cmd.ui32RespType = MMC_RSP_R5; |
|
} else if (sdio_cmd.ui8Idx == 6 || sdio_cmd.ui8Idx == 38) { |
|
LOG_DBG("Set CheckBusyCmd"); |
|
sdio_cmd.bCheckBusyCmd = true; |
|
sdio_cmd.ui32RespType = MMC_RSP_R1b; |
|
} else if (sdio_cmd.ui8Idx == 17 || sdio_cmd.ui8Idx == 18 || sdio_cmd.ui8Idx == 24 || |
|
sdio_cmd.ui8Idx == 25) { |
|
sdio_cmd.ui32RespType = MMC_RSP_R1; |
|
} |
|
|
|
#ifdef CONFIG_AMBIQ_SDIO_ASYNC |
|
if (data) { |
|
sdio_cmd.bASync = true; |
|
dev_data->host->AsyncCmd = sdio_cmd; |
|
dev_data->host->AsyncCmdData = cmd_data; |
|
} |
|
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */ |
|
|
|
ret = k_mutex_lock(&dev_data->access_mutex, K_MSEC(cmd->timeout_ms)); |
|
if (ret) { |
|
LOG_ERR("Could not access card"); |
|
return -EBUSY; |
|
} |
|
|
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
ret = pm_device_runtime_get(dev); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_get failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
|
|
if (data) { |
|
#if defined(CONFIG_CACHE_MANAGEMENT) && defined(CONFIG_DCACHE) |
|
/* Clean Dcache before DMA write */ |
|
if (cmd_data.dir == AM_HAL_DATA_DIR_WRITE && |
|
(uint32_t)(data->data) >= CACHABLE_START_ADDR) { |
|
sys_cache_data_flush_range(data->data, data->blocks * data->block_size); |
|
} |
|
#endif |
|
|
|
#ifdef CONFIG_AMBIQ_SDIO_ASYNC |
|
k_sem_reset(dev_data->async_sem); |
|
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */ |
|
ui32Status = dev_data->host->ops->execute_cmd(dev_data->host->pHandle, &sdio_cmd, |
|
&cmd_data); |
|
#ifdef CONFIG_AMBIQ_SDIO_ASYNC |
|
if ((ui32Status & 0xFFFF) == AM_HAL_STATUS_SUCCESS) { |
|
if (k_sem_take(dev_data->async_sem, K_MSEC(data->timeout_ms))) { |
|
return -ETIMEDOUT; |
|
} |
|
} |
|
#endif /* CONFIG_AMBIQ_SDIO_ASYNC */ |
|
|
|
#if defined(CONFIG_CACHE_MANAGEMENT) && defined(CONFIG_DCACHE) |
|
/* Invalidate Dcache after DMA read */ |
|
if (cmd_data.dir == AM_HAL_DATA_DIR_READ && |
|
(uint32_t)(data->data) >= CACHABLE_START_ADDR) { |
|
sys_cache_data_invd_range(data->data, data->blocks * data->block_size); |
|
} |
|
#endif |
|
|
|
} else { |
|
ui32Status = |
|
dev_data->host->ops->execute_cmd(dev_data->host->pHandle, &sdio_cmd, NULL); |
|
} |
|
if ((ui32Status & 0xFFFF) != AM_HAL_STATUS_SUCCESS) { |
|
if ((ui32Status & 0xFFFF) == AM_HAL_STATUS_TIMEOUT) { |
|
LOG_DBG("CMD%d Timeout!", sdio_cmd.ui8Idx); |
|
ret = -ETIMEDOUT; |
|
} else { |
|
LOG_DBG("Failed to send CMD%d, ui32Status = 0x%x", sdio_cmd.ui8Idx, |
|
ui32Status); |
|
ret = -EIO; |
|
} |
|
} |
|
|
|
k_mutex_unlock(&dev_data->access_mutex); |
|
|
|
memcpy(cmd->response, sdio_cmd.ui32Resp, sizeof(cmd->response)); |
|
|
|
LOG_DBG("Resp0 = 0x%x, Resp1 = 0x%x, Resp2 = 0x%x, Resp3 = 0x%x", cmd->response[0], |
|
cmd->response[1], cmd->response[2], cmd->response[3]); |
|
|
|
if (data) { |
|
data->bytes_xfered = (ui32Status >> 16) & 0xFFFF; |
|
} |
|
|
|
#if defined(CONFIG_PM_DEVICE_RUNTIME) |
|
/* Use async put to avoid useless device suspension/resumption |
|
* when doing consecutive transmission. |
|
*/ |
|
ret = pm_device_runtime_put_async(dev, K_MSEC(2)); |
|
|
|
if (ret < 0) { |
|
LOG_ERR("pm_device_runtime_put failed: %d", ret); |
|
} |
|
#endif /* CONFIG_PM_DEVICE_RUNTIME */ |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* Enable Card Interrupts |
|
*/ |
|
static int ambiq_sdio_card_interrupt_enable(const struct device *dev, sdhc_interrupt_cb_t callback, |
|
int sources, void *user_data) |
|
{ |
|
struct ambiq_sdio_data *data = dev->data; |
|
uint32_t ui32Status = 0; |
|
|
|
data->sdio_cb = callback; |
|
data->sdio_cb_user_data = user_data; |
|
|
|
if (sources & SDHC_INT_SDIO) { |
|
/* Enable SDIO Card Interrupt */ |
|
ui32Status = am_hal_sdhc_intr_signal_enable(data->host->pHandle, |
|
SDIO_INTSIG_CARDINTEN_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
ui32Status = am_hal_sdhc_intr_status_enable( |
|
data->host->pHandle, SDIO_INTENABLE_CARDINTERRUPTSTATUSENABLE_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
} |
|
|
|
if (sources & SDHC_INT_INSERTED) { |
|
/* Enable Card Insert Interrupt */ |
|
ui32Status = am_hal_sdhc_intr_signal_enable(data->host->pHandle, |
|
SDIO_INTSIG_CARDINSERTEN_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
ui32Status = am_hal_sdhc_intr_status_enable( |
|
data->host->pHandle, SDIO_INTENABLE_CARDINSERTIONSTATUSENABLE_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
} |
|
|
|
if (sources & SDHC_INT_REMOVED) { |
|
/* Enable Card Removal Interrupt */ |
|
ui32Status = am_hal_sdhc_intr_signal_enable(data->host->pHandle, |
|
SDIO_INTSIG_CARDREMOVALEN_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
ui32Status = am_hal_sdhc_intr_status_enable( |
|
data->host->pHandle, SDIO_INTENABLE_CARDREMOVALSTATUSENABLE_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Disable Card Interrupts |
|
*/ |
|
static int ambiq_sdio_card_interrupt_disable(const struct device *dev, int sources) |
|
{ |
|
struct ambiq_sdio_data *data = dev->data; |
|
uint32_t ui32Status = 0; |
|
|
|
data->sdio_cb = NULL; |
|
data->sdio_cb_user_data = NULL; |
|
|
|
if (sources & SDHC_INT_SDIO) { |
|
/* Disable SDIO Card Interrupt */ |
|
ui32Status = am_hal_sdhc_intr_signal_disable(data->host->pHandle, |
|
SDIO_INTSIG_CARDINTEN_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
ui32Status = am_hal_sdhc_intr_status_disable( |
|
data->host->pHandle, SDIO_INTENABLE_CARDINTERRUPTSTATUSENABLE_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
} |
|
|
|
if (sources & SDHC_INT_INSERTED) { |
|
/* Disable Card Insert Interrupt */ |
|
ui32Status = am_hal_sdhc_intr_signal_disable(data->host->pHandle, |
|
SDIO_INTSIG_CARDINSERTEN_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
ui32Status = am_hal_sdhc_intr_status_disable( |
|
data->host->pHandle, SDIO_INTENABLE_CARDINSERTIONSTATUSENABLE_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
} |
|
|
|
if (sources & SDHC_INT_REMOVED) { |
|
/* Disable Card Removal Interrupt */ |
|
ui32Status = am_hal_sdhc_intr_signal_disable(data->host->pHandle, |
|
SDIO_INTSIG_CARDREMOVALEN_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
ui32Status = am_hal_sdhc_intr_status_disable( |
|
data->host->pHandle, SDIO_INTENABLE_CARDREMOVALSTATUSENABLE_Msk); |
|
if (ui32Status != AM_HAL_STATUS_SUCCESS) { |
|
return -EIO; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static DEVICE_API(sdhc, ambiq_sdio_api) = { |
|
.reset = ambiq_sdio_reset, |
|
.request = ambiq_sdio_request, |
|
.set_io = ambiq_sdio_set_io, |
|
.get_card_present = ambiq_sdio_get_card_present, |
|
.execute_tuning = ambiq_sdio_execute_tuning, |
|
.card_busy = ambiq_sdio_card_busy, |
|
.get_host_props = ambiq_sdio_get_host_props, |
|
.enable_interrupt = ambiq_sdio_card_interrupt_enable, |
|
.disable_interrupt = ambiq_sdio_card_interrupt_disable, |
|
}; |
|
|
|
#ifdef CONFIG_PM_DEVICE |
|
/* |
|
* Ambiq SDIO peripheral power management function |
|
*/ |
|
static int ambiq_sdio_pm_action(const struct device *dev, enum pm_device_action action) |
|
{ |
|
struct ambiq_sdio_data *data = dev->data; |
|
int ret; |
|
|
|
switch (action) { |
|
case PM_DEVICE_ACTION_RESUME: |
|
ret = am_hal_card_pwrctrl_wakeup(&data->card); |
|
break; |
|
case PM_DEVICE_ACTION_SUSPEND: |
|
ret = am_hal_card_pwrctrl_sleep(&data->card); |
|
break; |
|
default: |
|
return -ENOTSUP; |
|
} |
|
|
|
if (ret != AM_HAL_STATUS_SUCCESS) { |
|
return -EPERM; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
#endif /* CONFIG_PM_DEVICE */ |
|
|
|
#define AMBIQ_SDIO_INIT(n) \ |
|
static void sdio_##n##_irq_config_func(const struct device *dev) \ |
|
{ \ |
|
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), ambiq_sdio_isr, \ |
|
DEVICE_DT_INST_GET(n), 0); \ |
|
irq_enable(DT_INST_IRQN(n)); \ |
|
} \ |
|
\ |
|
PINCTRL_DT_INST_DEFINE(n); \ |
|
\ |
|
static const struct ambiq_sdio_config ambiq_sdio_config_##n = { \ |
|
.pSDHC = (SDIO_Type *)DT_INST_REG_ADDR(n), \ |
|
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
|
.irq_config_func = sdio_##n##_irq_config_func, \ |
|
.inst = n, \ |
|
.tx_delay = DT_INST_PROP(n, txdelay), \ |
|
.rx_delay = DT_INST_PROP(n, rxdelay), \ |
|
.max_bus_freq = DT_INST_PROP(n, max_bus_freq), \ |
|
.min_bus_freq = DT_INST_PROP(n, min_bus_freq), \ |
|
.power_delay_ms = DT_INST_PROP(n, power_delay_ms), \ |
|
}; \ |
|
\ |
|
static struct ambiq_sdio_data ambiq_sdio_data_##n; \ |
|
PM_DEVICE_DT_INST_DEFINE(n, ambiq_sdio_pm_action); \ |
|
DEVICE_DT_INST_DEFINE(n, &ambiq_sdio_init, PM_DEVICE_DT_INST_GET(n), &ambiq_sdio_data_##n, \ |
|
&ambiq_sdio_config_##n, POST_KERNEL, CONFIG_SDHC_INIT_PRIORITY, \ |
|
&ambiq_sdio_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(AMBIQ_SDIO_INIT)
|
|
|