blob: 5a7f372dd201ea414de7cabd26fe3388ab13cea0 [file] [log] [blame]
/*
* Copyright 2022 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_imx_usdhc
#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/sdhc.h>
#include <zephyr/sd/sd_spec.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
#include <soc.h>
#include <zephyr/drivers/pinctrl.h>
#define PINCTRL_STATE_SLOW PINCTRL_STATE_PRIV_START
#define PINCTRL_STATE_MED (PINCTRL_STATE_PRIV_START + 1U)
#define PINCTRL_STATE_FAST (PINCTRL_STATE_PRIV_START + 2U)
#define PINCTRL_STATE_NOPULL (PINCTRL_STATE_PRIV_START + 3U)
LOG_MODULE_REGISTER(usdhc, CONFIG_SDHC_LOG_LEVEL);
#include <fsl_usdhc.h>
#include <fsl_cache.h>
#include <zephyr/irq.h>
enum transfer_callback_status {
TRANSFER_CMD_COMPLETE = BIT(0),
TRANSFER_CMD_FAILED = BIT(1),
TRANSFER_DATA_COMPLETE = BIT(2),
TRANSFER_DATA_FAILED = BIT(3),
};
#define TRANSFER_CMD_FLAGS (TRANSFER_CMD_COMPLETE | TRANSFER_CMD_FAILED)
#define TRANSFER_DATA_FLAGS (TRANSFER_DATA_COMPLETE | TRANSFER_DATA_FAILED)
/* USDHC tuning constants */
#define IMX_USDHC_STANDARD_TUNING_START (10U)
#define IMX_USDHC_TUNING_STEP (2U)
#define IMX_USDHC_STANDARD_TUNING_COUNTER (60U)
/* Default transfer timeout in ms for tuning */
#define IMX_USDHC_DEFAULT_TIMEOUT (5000U)
struct usdhc_host_transfer {
usdhc_transfer_t *transfer;
k_timeout_t command_timeout;
k_timeout_t data_timeout;
};
struct usdhc_config {
USDHC_Type *base;
const struct device *clock_dev;
clock_control_subsys_t clock_subsys;
uint8_t nusdhc;
const struct gpio_dt_spec pwr_gpio;
const struct gpio_dt_spec detect_gpio;
bool detect_dat3;
bool detect_cd;
bool no_180_vol;
uint32_t data_timeout;
uint32_t read_watermark;
uint32_t write_watermark;
uint32_t max_current_330;
uint32_t max_current_300;
uint32_t max_current_180;
uint32_t power_delay_ms;
uint32_t min_bus_freq;
uint32_t max_bus_freq;
bool mmc_hs200_1_8v;
bool mmc_hs400_1_8v;
const struct pinctrl_dev_config *pincfg;
void (*irq_config_func)(const struct device *dev);
};
struct usdhc_data {
const struct device *dev;
struct sdhc_host_props props;
bool card_present;
struct k_sem transfer_sem;
volatile uint32_t transfer_status;
usdhc_handle_t transfer_handle;
struct sdhc_io host_io;
struct k_mutex access_mutex;
sdhc_interrupt_cb_t sdhc_cb;
struct gpio_callback cd_callback;
void *sdhc_cb_user_data;
uint8_t usdhc_rx_dummy[128] __aligned(32);
#ifdef CONFIG_IMX_USDHC_DMA_SUPPORT
uint32_t *usdhc_dma_descriptor; /* ADMA descriptor table (noncachable) */
uint32_t dma_descriptor_len; /* DMA descriptor table length in words */
#endif
};
static void transfer_complete_cb(USDHC_Type *usdhc, usdhc_handle_t *handle,
status_t status, void *user_data)
{
const struct device *dev = (const struct device *)user_data;
struct usdhc_data *data = dev->data;
if (status == kStatus_USDHC_TransferDataFailed) {
data->transfer_status |= TRANSFER_DATA_FAILED;
} else if (status == kStatus_USDHC_TransferDataComplete) {
data->transfer_status |= TRANSFER_DATA_COMPLETE;
} else if (status == kStatus_USDHC_SendCommandFailed) {
data->transfer_status |= TRANSFER_CMD_FAILED;
} else if (status == kStatus_USDHC_SendCommandSuccess) {
data->transfer_status |= TRANSFER_CMD_COMPLETE;
}
k_sem_give(&data->transfer_sem);
}
static void sdio_interrupt_cb(USDHC_Type *usdhc, void *user_data)
{
const struct device *dev = user_data;
struct usdhc_data *data = dev->data;
if (data->sdhc_cb) {
data->sdhc_cb(dev, SDHC_INT_SDIO, data->sdhc_cb_user_data);
}
}
static void card_inserted_cb(USDHC_Type *usdhc, void *user_data)
{
const struct device *dev = user_data;
struct usdhc_data *data = dev->data;
if (data->sdhc_cb) {
data->sdhc_cb(dev, SDHC_INT_INSERTED, data->sdhc_cb_user_data);
}
}
static void card_removed_cb(USDHC_Type *usdhc, void *user_data)
{
const struct device *dev = user_data;
struct usdhc_data *data = dev->data;
if (data->sdhc_cb) {
data->sdhc_cb(dev, SDHC_INT_REMOVED, data->sdhc_cb_user_data);
}
}
static void card_detect_gpio_cb(const struct device *port,
struct gpio_callback *cb,
gpio_port_pins_t pins)
{
struct usdhc_data *data = CONTAINER_OF(cb, struct usdhc_data, cd_callback);
const struct device *dev = data->dev;
const struct usdhc_config *cfg = dev->config;
if (data->sdhc_cb) {
if (gpio_pin_get_dt(&cfg->detect_gpio)) {
data->sdhc_cb(dev, SDHC_INT_INSERTED, data->sdhc_cb_user_data);
} else {
data->sdhc_cb(dev, SDHC_INT_REMOVED, data->sdhc_cb_user_data);
}
}
}
static void imx_usdhc_select_1_8v(USDHC_Type *base, bool enable_1_8v)
{
#if !(defined(FSL_FEATURE_USDHC_HAS_NO_VOLTAGE_SELECT) && \
(FSL_FEATURE_USDHC_HAS_NO_VOLTAGE_SELECT))
UDSHC_SelectVoltage(base, enable_1_8v);
#endif
}
static int imx_usdhc_dat3_pull(const struct usdhc_config *cfg, bool pullup)
{
int ret = 0U;
ret = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_NOPULL);
if (ret) {
LOG_ERR("No DAT3 floating state defined, but dat3 detect selected");
return ret;
}
#ifdef CONFIG_IMX_USDHC_DAT3_PWR_TOGGLE
if (!pullup) {
/* Power off the card to clear DAT3 legacy status */
if (cfg->pwr_gpio.port) {
ret = gpio_pin_set_dt(&cfg->pwr_gpio, 0);
if (ret) {
return ret;
}
/* Delay for card power off to complete */
k_busy_wait(1000);
ret = gpio_pin_set_dt(&cfg->pwr_gpio, 1);
/* Delay for power on */
k_busy_wait(1000);
if (ret) {
return ret;
}
}
}
#endif
return ret;
}
/*
* Reset SDHC after command error
*/
static void imx_usdhc_error_recovery(const struct device *dev)
{
const struct usdhc_config *cfg = dev->config;
uint32_t status = USDHC_GetPresentStatusFlags(cfg->base);
if (status & kUSDHC_CommandInhibitFlag) {
/* Reset command line */
USDHC_Reset(cfg->base, kUSDHC_ResetCommand, 100U);
}
if (((status & (uint32_t)kUSDHC_DataInhibitFlag) != 0U) ||
(USDHC_GetAdmaErrorStatusFlags(cfg->base) != 0U)) {
/* Reset data line */
USDHC_Reset(cfg->base, kUSDHC_DataInhibitFlag, 100U);
}
}
/*
* Initialize SDHC host properties for use in get_host_props api call
*/
static void imx_usdhc_init_host_props(const struct device *dev)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *data = dev->data;
usdhc_capability_t caps;
struct sdhc_host_props *props = &data->props;
memset(props, 0, sizeof(struct sdhc_host_props));
props->f_max = cfg->max_bus_freq;
props->f_min = cfg->min_bus_freq;
props->max_current_330 = cfg->max_current_330;
props->max_current_180 = cfg->max_current_180;
props->power_delay = cfg->power_delay_ms;
/* Read host capabilities */
USDHC_GetCapability(cfg->base, &caps);
if (cfg->no_180_vol) {
props->host_caps.vol_180_support = false;
} else {
props->host_caps.vol_180_support = (bool)(caps.flags & kUSDHC_SupportV180Flag);
}
props->host_caps.vol_300_support = (bool)(caps.flags & kUSDHC_SupportV300Flag);
props->host_caps.vol_330_support = (bool)(caps.flags & kUSDHC_SupportV330Flag);
props->host_caps.suspend_res_support = (bool)(caps.flags & kUSDHC_SupportSuspendResumeFlag);
props->host_caps.sdma_support = (bool)(caps.flags & kUSDHC_SupportDmaFlag);
props->host_caps.high_spd_support = (bool)(caps.flags & kUSDHC_SupportHighSpeedFlag);
props->host_caps.adma_2_support = (bool)(caps.flags & kUSDHC_SupportAdmaFlag);
props->host_caps.max_blk_len = (bool)(caps.maxBlockLength);
props->host_caps.ddr50_support = (bool)(caps.flags & kUSDHC_SupportDDR50Flag);
props->host_caps.sdr104_support = (bool)(caps.flags & kUSDHC_SupportSDR104Flag);
props->host_caps.sdr50_support = (bool)(caps.flags & kUSDHC_SupportSDR50Flag);
props->host_caps.bus_8_bit_support = (bool)(caps.flags & kUSDHC_Support8BitFlag);
props->host_caps.bus_4_bit_support = (bool)(caps.flags & kUSDHC_Support4BitFlag);
props->host_caps.hs200_support = (bool)(cfg->mmc_hs200_1_8v);
props->host_caps.hs400_support = (bool)(cfg->mmc_hs400_1_8v);
}
/*
* Reset USDHC controller
*/
static int imx_usdhc_reset(const struct device *dev)
{
const struct usdhc_config *cfg = dev->config;
/* Switch to default I/O voltage of 3.3V */
imx_usdhc_select_1_8v(cfg->base, false);
USDHC_EnableDDRMode(cfg->base, false, 0U);
#if defined(FSL_FEATURE_USDHC_HAS_SDR50_MODE) && (FSL_FEATURE_USDHC_HAS_SDR50_MODE)
USDHC_EnableStandardTuning(cfg->base, 0, 0, false);
USDHC_EnableAutoTuning(cfg->base, false);
#endif
#if FSL_FEATURE_USDHC_HAS_HS400_MODE
/* Disable HS400 mode */
USDHC_EnableHS400Mode(cfg->base, false);
/* Disable DLL */
USDHC_EnableStrobeDLL(cfg->base, false);
#endif
/* Reset data/command/tuning circuit */
return USDHC_Reset(cfg->base, kUSDHC_ResetAll, 1000U) == true ? 0 : -ETIMEDOUT;
}
/*
* Set SDHC io properties
*/
static int imx_usdhc_set_io(const struct device *dev, struct sdhc_io *ios)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *data = dev->data;
uint32_t src_clk_hz, bus_clk;
struct sdhc_io *host_io = &data->host_io;
LOG_DBG("SDHC I/O: bus width %d, clock %dHz, card power %s, voltage %s",
ios->bus_width,
ios->clock,
ios->power_mode == SDHC_POWER_ON ? "ON" : "OFF",
ios->signal_voltage == SD_VOL_1_8_V ? "1.8V" : "3.3V"
);
if (clock_control_get_rate(cfg->clock_dev,
cfg->clock_subsys,
&src_clk_hz)) {
return -EINVAL;
}
if (ios->clock && (ios->clock > data->props.f_max || ios->clock < data->props.f_min)) {
return -EINVAL;
}
/* Set host clock */
if (host_io->clock != ios->clock) {
if (ios->clock != 0) {
/* Enable the clock output */
bus_clk = USDHC_SetSdClock(cfg->base, src_clk_hz, ios->clock);
LOG_DBG("BUS CLOCK: %d", bus_clk);
if (bus_clk == 0) {
return -ENOTSUP;
}
}
host_io->clock = ios->clock;
}
/* Set bus width */
if (host_io->bus_width != ios->bus_width) {
switch (ios->bus_width) {
case SDHC_BUS_WIDTH1BIT:
USDHC_SetDataBusWidth(cfg->base, kUSDHC_DataBusWidth1Bit);
break;
case SDHC_BUS_WIDTH4BIT:
USDHC_SetDataBusWidth(cfg->base, kUSDHC_DataBusWidth4Bit);
break;
case SDHC_BUS_WIDTH8BIT:
USDHC_SetDataBusWidth(cfg->base, kUSDHC_DataBusWidth8Bit);
break;
default:
return -ENOTSUP;
}
host_io->bus_width = ios->bus_width;
}
/* Set host signal voltage */
if (ios->signal_voltage != host_io->signal_voltage) {
switch (ios->signal_voltage) {
case SD_VOL_3_3_V:
case SD_VOL_3_0_V:
imx_usdhc_select_1_8v(cfg->base, false);
break;
case SD_VOL_1_8_V:
/**
* USDHC peripheral deviates from SD spec here.
* The host controller specification claims
* the "SD clock enable" bit can be used to gate the SD
* clock by clearing it. The USDHC controller does not
* provide this bit, only a way to force the SD clock
* on. We will instead delay 10 ms to allow the clock
* to be gated for enough time, then force it on for
* 10 ms, then allow it to be gated again.
*/
/* Switch to 1.8V */
imx_usdhc_select_1_8v(cfg->base, true);
/* Wait 10 ms- clock will be gated during this period */
k_msleep(10);
/* Force the clock on */
USDHC_ForceClockOn(cfg->base, true);
/* Keep the clock on for a moment, so SD will recognize it */
k_msleep(10);
/* Stop forcing clock on */
USDHC_ForceClockOn(cfg->base, false);
break;
default:
return -ENOTSUP;
}
/* Save new host voltage */
host_io->signal_voltage = ios->signal_voltage;
}
/* Set card power */
if ((host_io->power_mode != ios->power_mode) && (cfg->pwr_gpio.port)) {
if (ios->power_mode == SDHC_POWER_OFF) {
gpio_pin_set_dt(&cfg->pwr_gpio, 0);
} else if (ios->power_mode == SDHC_POWER_ON) {
gpio_pin_set_dt(&cfg->pwr_gpio, 1);
}
host_io->power_mode = ios->power_mode;
}
/* Set I/O timing */
if (host_io->timing != ios->timing) {
switch (ios->timing) {
case SDHC_TIMING_LEGACY:
case SDHC_TIMING_HS:
break;
case SDHC_TIMING_DDR50:
case SDHC_TIMING_DDR52:
/* Enable DDR mode */
USDHC_EnableDDRMode(cfg->base, true, 0);
__fallthrough;
case SDHC_TIMING_SDR12:
case SDHC_TIMING_SDR25:
pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_SLOW);
break;
case SDHC_TIMING_SDR50:
pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_MED);
break;
case SDHC_TIMING_HS400:
#if FSL_FEATURE_USDHC_HAS_HS400_MODE
USDHC_EnableHS400Mode(cfg->base, true);
USDHC_EnableDDRMode(cfg->base, true, 0U);
USDHC_ConfigStrobeDLL(cfg->base, 7U, 4U);
USDHC_EnableStrobeDLL(cfg->base, true);
#else
LOG_ERR("HS400 not supported for this device");
return -ENOTSUP;
#endif
case SDHC_TIMING_SDR104:
case SDHC_TIMING_HS200:
pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_FAST);
break;
default:
return -ENOTSUP;
}
host_io->timing = ios->timing;
}
return 0;
}
/*
* Internal transfer function, used by tuning and request apis
*/
static int imx_usdhc_transfer(const struct device *dev,
struct usdhc_host_transfer *request)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *dev_data = dev->data;
status_t error;
#ifdef CONFIG_IMX_USDHC_DMA_SUPPORT
usdhc_adma_config_t dma_config = {0};
/* Configure DMA */
dma_config.admaTable = dev_data->usdhc_dma_descriptor;
dma_config.admaTableWords = dev_data->dma_descriptor_len;
#if !(defined(FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN) && FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN)
dma_config.burstLen = kUSDHC_EnBurstLenForINCR;
#endif
dma_config.dmaMode = kUSDHC_DmaModeAdma2;
#endif /* CONFIG_IMX_USDHC_DMA_SUPPORT */
/* Reset transfer status */
dev_data->transfer_status = 0U;
/* Reset semaphore */
k_sem_reset(&dev_data->transfer_sem);
#ifdef CONFIG_IMX_USDHC_DMA_SUPPORT
error = USDHC_TransferNonBlocking(cfg->base, &dev_data->transfer_handle,
&dma_config, request->transfer);
#else
error = USDHC_TransferNonBlocking(cfg->base, &dev_data->transfer_handle,
NULL, request->transfer);
#endif
if (error == kStatus_USDHC_ReTuningRequest) {
return -EAGAIN;
} else if (error != kStatus_Success) {
return -EIO;
}
/* Wait for event to occur */
while ((dev_data->transfer_status & (TRANSFER_CMD_FLAGS | TRANSFER_DATA_FLAGS)) == 0) {
if (k_sem_take(&dev_data->transfer_sem, request->command_timeout)) {
return -ETIMEDOUT;
}
}
if (dev_data->transfer_status & TRANSFER_CMD_FAILED) {
return -EIO;
}
/* If data was sent, wait for that to complete */
if (request->transfer->data) {
while ((dev_data->transfer_status & TRANSFER_DATA_FLAGS) == 0) {
if (k_sem_take(&dev_data->transfer_sem, request->data_timeout)) {
return -ETIMEDOUT;
}
}
if (dev_data->transfer_status & TRANSFER_DATA_FAILED) {
return -EIO;
}
}
return 0;
}
/* Stops transmission after failed command with CMD12 */
static void imx_usdhc_stop_transmission(const struct device *dev)
{
usdhc_command_t stop_cmd = {0};
struct usdhc_host_transfer request;
usdhc_transfer_t transfer;
/* Send CMD12 to stop transmission */
stop_cmd.index = SD_STOP_TRANSMISSION;
stop_cmd.argument = 0U;
stop_cmd.type = kCARD_CommandTypeAbort;
stop_cmd.responseType = SD_RSP_TYPE_R1b;
transfer.command = &stop_cmd;
transfer.data = NULL;
request.transfer = &transfer;
request.command_timeout = K_MSEC(IMX_USDHC_DEFAULT_TIMEOUT);
request.data_timeout = K_MSEC(IMX_USDHC_DEFAULT_TIMEOUT);
imx_usdhc_transfer(dev, &request);
}
/*
* Return 0 if card is not busy, 1 if it is
*/
static int imx_usdhc_card_busy(const struct device *dev)
{
const struct usdhc_config *cfg = dev->config;
return (USDHC_GetPresentStatusFlags(cfg->base)
& (kUSDHC_Data0LineLevelFlag |
kUSDHC_Data1LineLevelFlag |
kUSDHC_Data2LineLevelFlag |
kUSDHC_Data3LineLevelFlag))
? 0 : 1;
}
/*
* Execute card tuning
*/
static int imx_usdhc_execute_tuning(const struct device *dev)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *dev_data = dev->data;
usdhc_command_t cmd = {0};
usdhc_data_t data = {0};
struct usdhc_host_transfer request;
usdhc_transfer_t transfer;
int ret;
bool retry_tuning = true;
if ((dev_data->host_io.timing == SDHC_TIMING_HS200) ||
(dev_data->host_io.timing == SDHC_TIMING_HS400)) {
/*Currently only reaches here when MMC */
cmd.index = MMC_SEND_TUNING_BLOCK;
} else {
cmd.index = SD_SEND_TUNING_BLOCK;
}
cmd.argument = 0;
cmd.responseType = SD_RSP_TYPE_R1;
if (dev_data->host_io.bus_width == SDHC_BUS_WIDTH8BIT) {
data.blockSize = sizeof(dev_data->usdhc_rx_dummy);
} else {
data.blockSize = sizeof(dev_data->usdhc_rx_dummy) / 2;
}
data.blockCount = 1;
data.rxData = (uint32_t *)dev_data->usdhc_rx_dummy;
data.dataType = kUSDHC_TransferDataTuning;
transfer.command = &cmd;
transfer.data = &data;
/* Reset tuning circuit */
USDHC_Reset(cfg->base, kUSDHC_ResetTuning, 100U);
/* Disable standard tuning */
USDHC_EnableStandardTuning(cfg->base, IMX_USDHC_STANDARD_TUNING_START,
IMX_USDHC_TUNING_STEP, false);
USDHC_ForceClockOn(cfg->base, true);
/*
* Tuning fail found on some SOCs is caused by the different of delay
* cell, so we need to increase the tuning counter to cover the
* adjustable tuning window
*/
USDHC_SetStandardTuningCounter(cfg->base, IMX_USDHC_STANDARD_TUNING_COUNTER);
/* Reenable standard tuning */
USDHC_EnableStandardTuning(cfg->base, IMX_USDHC_STANDARD_TUNING_START,
IMX_USDHC_TUNING_STEP, true);
request.command_timeout = K_MSEC(IMX_USDHC_DEFAULT_TIMEOUT);
request.data_timeout = K_MSEC(IMX_USDHC_DEFAULT_TIMEOUT);
request.transfer = &transfer;
while (true) {
ret = imx_usdhc_transfer(dev, &request);
if (ret) {
return ret;
}
/* Delay 1ms */
k_busy_wait(1000);
/* Wait for execute tuning bit to clear */
if (USDHC_GetExecuteStdTuningStatus(cfg->base) != 0) {
continue;
}
/* If tuning had error, retry tuning */
if ((USDHC_CheckTuningError(cfg->base) != 0U) && retry_tuning) {
retry_tuning = false;
/* Enable standard tuning */
USDHC_EnableStandardTuning(cfg->base,
IMX_USDHC_STANDARD_TUNING_START,
IMX_USDHC_TUNING_STEP, true);
USDHC_SetTuningDelay(cfg->base,
IMX_USDHC_STANDARD_TUNING_START, 0U, 0U);
} else {
break;
}
}
/* Check tuning result */
if (USDHC_CheckStdTuningResult(cfg->base) == 0) {
return -EIO;
}
USDHC_ForceClockOn(cfg->base, false);
/* Enable auto tuning */
USDHC_EnableAutoTuning(cfg->base, true);
return 0;
}
/*
* Send CMD or CMD/DATA via SDHC
*/
static int imx_usdhc_request(const struct device *dev, struct sdhc_command *cmd,
struct sdhc_data *data)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *dev_data = dev->data;
usdhc_command_t host_cmd = {0};
usdhc_data_t host_data = {0};
struct usdhc_host_transfer request;
usdhc_transfer_t transfer;
int busy_timeout = IMX_USDHC_DEFAULT_TIMEOUT;
int ret = 0;
int retries = (int)cmd->retries;
if (cmd->opcode == SD_GO_IDLE_STATE) {
USDHC_SetCardActive(cfg->base, 0xFFFF);
}
host_cmd.index = cmd->opcode;
host_cmd.argument = cmd->arg;
/* Mask out part of response type field used for SPI commands */
host_cmd.responseType = (cmd->response_type & SDHC_NATIVE_RESPONSE_MASK);
transfer.command = &host_cmd;
if (cmd->timeout_ms == SDHC_TIMEOUT_FOREVER) {
request.command_timeout = K_FOREVER;
} else {
request.command_timeout = K_MSEC(cmd->timeout_ms);
}
if (data) {
host_data.blockSize = data->block_size;
host_data.blockCount = data->blocks;
/*
* Determine type of command. Note that driver is expected to
* handle CMD12 and CMD23 for reading and writing blocks
*/
switch (cmd->opcode) {
case SD_WRITE_SINGLE_BLOCK:
host_data.enableAutoCommand12 = true;
host_data.txData = data->data;
break;
case SD_WRITE_MULTIPLE_BLOCK:
if (dev_data->host_io.timing == SDHC_TIMING_SDR104) {
/* Card uses UHS104, so it must support CMD23 */
host_data.enableAutoCommand23 = true;
} else {
/* No CMD23 support */
host_data.enableAutoCommand12 = true;
}
host_data.txData = data->data;
break;
case SD_READ_SINGLE_BLOCK:
host_data.enableAutoCommand12 = true;
host_data.rxData = data->data;
break;
case SD_READ_MULTIPLE_BLOCK:
if (dev_data->host_io.timing == SDHC_TIMING_SDR104) {
/* Card uses UHS104, so it must support CMD23 */
host_data.enableAutoCommand23 = true;
} else {
/* No CMD23 support */
host_data.enableAutoCommand12 = true;
}
host_data.rxData = data->data;
break;
case MMC_CHECK_BUS_TEST:
case MMC_SEND_EXT_CSD:
case SD_APP_SEND_SCR:
case SD_SWITCH:
case SD_APP_SEND_NUM_WRITTEN_BLK:
host_data.rxData = data->data;
break;
case SDIO_RW_EXTENDED:
/* Use R/W bit to determine data direction */
if (host_cmd.argument & BIT(SDIO_CMD_ARG_RW_SHIFT)) {
host_data.txData = data->data;
} else {
host_data.rxData = data->data;
}
break;
default:
return -ENOTSUP;
}
transfer.data = &host_data;
if (data->timeout_ms == SDHC_TIMEOUT_FOREVER) {
request.data_timeout = K_FOREVER;
} else {
request.data_timeout = K_MSEC(data->timeout_ms);
}
} else {
transfer.data = NULL;
request.data_timeout = K_NO_WAIT;
}
request.transfer = &transfer;
/* Ensure we have exclusive access to SD card before sending request */
if (k_mutex_lock(&dev_data->access_mutex, request.command_timeout) != 0) {
return -EBUSY;
}
while (retries >= 0) {
ret = imx_usdhc_transfer(dev, &request);
if (ret && data) {
/*
* Disable and clear interrupts. If the data transmission
* completes later, we will encounter issues because
* the USDHC driver expects data to be present in the
* current transmission, but CMD12 does not contain data
*/
USDHC_DisableInterruptSignal(cfg->base, kUSDHC_CommandFlag |
kUSDHC_DataFlag | kUSDHC_DataDMAFlag);
USDHC_ClearInterruptStatusFlags(cfg->base, kUSDHC_CommandFlag |
kUSDHC_DataFlag | kUSDHC_DataDMAFlag);
/* Stop transmission with CMD12 in case of data error */
imx_usdhc_stop_transmission(dev);
/* Wait for card to go idle */
while (busy_timeout > 0) {
if (!imx_usdhc_card_busy(dev)) {
break;
}
/* Wait 125us before polling again */
k_busy_wait(125);
busy_timeout -= 125;
}
if (busy_timeout <= 0) {
LOG_DBG("Card did not idle after CMD12");
k_mutex_unlock(&dev_data->access_mutex);
return -ETIMEDOUT;
}
}
if (ret == -EAGAIN) {
/* Retry, card made a tuning request */
if (dev_data->host_io.timing == SDHC_TIMING_SDR50 ||
dev_data->host_io.timing == SDHC_TIMING_SDR104 ||
dev_data->host_io.timing == SDHC_TIMING_HS200 ||
dev_data->host_io.timing == SDHC_TIMING_HS400) {
/* Retune card */
LOG_DBG("Card made tuning request, retune");
ret = imx_usdhc_execute_tuning(dev);
if (ret) {
LOG_DBG("Card failed to tune");
k_mutex_unlock(&dev_data->access_mutex);
return ret;
}
}
}
if (ret) {
imx_usdhc_error_recovery(dev);
retries--;
} else {
break;
}
}
/* Release access on card */
k_mutex_unlock(&dev_data->access_mutex);
/* Record command response */
memcpy(cmd->response, host_cmd.response, sizeof(cmd->response));
if (data) {
/* Record number of bytes xfered */
data->bytes_xfered = dev_data->transfer_handle.transferredWords;
}
return ret;
}
/*
* Get card presence
*/
static int imx_usdhc_get_card_present(const struct device *dev)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *data = dev->data;
if (cfg->detect_dat3) {
/*
* If card is already present, do not retry detection.
* Power line toggling would reset SD card
*/
if (!data->card_present) {
/* Detect card presence with DAT3 line pull */
imx_usdhc_dat3_pull(cfg, false);
USDHC_CardDetectByData3(cfg->base, true);
/* Delay to ensure host has time to detect card */
k_busy_wait(1000);
data->card_present = USDHC_DetectCardInsert(cfg->base);
/* Clear card detection and pull */
imx_usdhc_dat3_pull(cfg, true);
USDHC_CardDetectByData3(cfg->base, false);
}
} else if (cfg->detect_cd) {
/*
* Detect the card via the USDHC_CD signal internal to
* the peripheral
*/
data->card_present = USDHC_DetectCardInsert(cfg->base);
} else if (cfg->detect_gpio.port) {
data->card_present = gpio_pin_get_dt(&cfg->detect_gpio) > 0;
} else {
LOG_WRN("No card detection method configured, assuming card "
"is present");
data->card_present = true;
}
return ((int)data->card_present);
}
/*
* Get host properties
*/
static int imx_usdhc_get_host_props(const struct device *dev,
struct sdhc_host_props *props)
{
struct usdhc_data *data = dev->data;
memcpy(props, &data->props, sizeof(struct sdhc_host_props));
return 0;
}
/*
* Enable SDHC card interrupt
*/
static int imx_usdhc_enable_interrupt(const struct device *dev,
sdhc_interrupt_cb_t callback,
int sources, void *user_data)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *data = dev->data;
int ret;
/* Record SDIO callback parameters */
data->sdhc_cb = callback;
data->sdhc_cb_user_data = user_data;
/* Disable interrupts, then enable what the user requested */
USDHC_DisableInterruptStatus(cfg->base, kUSDHC_CardInterruptFlag);
USDHC_DisableInterruptSignal(cfg->base, kUSDHC_CardInterruptFlag);
if (cfg->detect_gpio.port) {
ret = gpio_pin_interrupt_configure_dt(&cfg->detect_gpio,
GPIO_INT_DISABLE);
if (ret) {
return ret;
}
} else {
USDHC_DisableInterruptSignal(cfg->base, kUSDHC_CardInsertionFlag);
USDHC_DisableInterruptStatus(cfg->base, kUSDHC_CardInsertionFlag);
USDHC_DisableInterruptSignal(cfg->base, kUSDHC_CardRemovalFlag);
USDHC_DisableInterruptStatus(cfg->base, kUSDHC_CardRemovalFlag);
}
if (sources & SDHC_INT_SDIO) {
/* Enable SDIO card interrupt */
USDHC_EnableInterruptStatus(cfg->base, kUSDHC_CardInterruptFlag);
USDHC_EnableInterruptSignal(cfg->base, kUSDHC_CardInterruptFlag);
}
if (sources & SDHC_INT_INSERTED) {
if (cfg->detect_gpio.port) {
/* Use GPIO interrupt */
ret = gpio_pin_interrupt_configure_dt(&cfg->detect_gpio,
GPIO_INT_EDGE_TO_ACTIVE);
if (ret) {
return ret;
}
} else {
/* Enable card insertion interrupt */
USDHC_EnableInterruptStatus(cfg->base,
kUSDHC_CardInsertionFlag);
USDHC_EnableInterruptSignal(cfg->base,
kUSDHC_CardInsertionFlag);
}
}
if (sources & SDHC_INT_REMOVED) {
if (cfg->detect_gpio.port) {
/* Use GPIO interrupt */
ret = gpio_pin_interrupt_configure_dt(&cfg->detect_gpio,
GPIO_INT_EDGE_TO_INACTIVE);
if (ret) {
return ret;
}
} else {
/* Enable card removal interrupt */
USDHC_EnableInterruptStatus(cfg->base,
kUSDHC_CardRemovalFlag);
USDHC_EnableInterruptSignal(cfg->base,
kUSDHC_CardRemovalFlag);
}
}
return 0;
}
static int imx_usdhc_disable_interrupt(const struct device *dev, int sources)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *data = dev->data;
int ret;
if (sources & SDHC_INT_SDIO) {
/* Disable SDIO card interrupt */
USDHC_DisableInterruptStatus(cfg->base, kUSDHC_CardInterruptFlag);
USDHC_DisableInterruptSignal(cfg->base, kUSDHC_CardInterruptFlag);
}
if (sources & SDHC_INT_INSERTED) {
if (cfg->detect_gpio.port) {
ret = gpio_pin_interrupt_configure_dt(&cfg->detect_gpio,
GPIO_INT_DISABLE);
if (ret) {
return ret;
}
} else {
/* Disable card insertion interrupt */
USDHC_DisableInterruptStatus(cfg->base,
kUSDHC_CardInsertionFlag);
USDHC_DisableInterruptSignal(cfg->base,
kUSDHC_CardInsertionFlag);
}
}
if (sources & SDHC_INT_REMOVED) {
if (cfg->detect_gpio.port) {
ret = gpio_pin_interrupt_configure_dt(&cfg->detect_gpio,
GPIO_INT_DISABLE);
if (ret) {
return ret;
}
} else {
/* Disable card removal interrupt */
USDHC_DisableInterruptStatus(cfg->base,
kUSDHC_CardRemovalFlag);
USDHC_DisableInterruptSignal(cfg->base,
kUSDHC_CardRemovalFlag);
}
}
/* If all interrupt flags are disabled, remove callback */
if ((USDHC_GetEnabledInterruptStatusFlags(cfg->base) &
(kUSDHC_CardInterruptFlag | kUSDHC_CardInsertionFlag |
kUSDHC_CardRemovalFlag)) == 0) {
data->sdhc_cb = NULL;
data->sdhc_cb_user_data = NULL;
}
return 0;
}
static int imx_usdhc_isr(const struct device *dev)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *data = dev->data;
USDHC_TransferHandleIRQ(cfg->base, &data->transfer_handle);
return 0;
}
/*
* Perform early system init for SDHC
*/
static int imx_usdhc_init(const struct device *dev)
{
const struct usdhc_config *cfg = dev->config;
struct usdhc_data *data = dev->data;
usdhc_config_t host_config = {0};
int ret;
const usdhc_transfer_callback_t callbacks = {
.TransferComplete = transfer_complete_cb,
.SdioInterrupt = sdio_interrupt_cb,
.CardInserted = card_inserted_cb,
.CardRemoved = card_removed_cb,
};
if (!device_is_ready(cfg->clock_dev)) {
LOG_ERR("clock control device not ready");
return -ENODEV;
}
ret = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT);
if (ret) {
return ret;
}
USDHC_TransferCreateHandle(cfg->base, &data->transfer_handle,
&callbacks, (void *)dev);
cfg->irq_config_func(dev);
host_config.dataTimeout = cfg->data_timeout;
host_config.endianMode = kUSDHC_EndianModeLittle;
host_config.readWatermarkLevel = cfg->read_watermark;
host_config.writeWatermarkLevel = cfg->write_watermark;
USDHC_Init(cfg->base, &host_config);
/* Read host controller properties */
imx_usdhc_init_host_props(dev);
/* Set power GPIO low, so card starts powered off */
if (cfg->pwr_gpio.port) {
ret = gpio_pin_configure_dt(&cfg->pwr_gpio, GPIO_OUTPUT_INACTIVE);
if (ret) {
return ret;
}
} else {
LOG_WRN("No power control GPIO defined. Without power control,\n"
"the SD card may fail to communicate with the host");
}
if (cfg->detect_gpio.port) {
ret = gpio_pin_configure_dt(&cfg->detect_gpio, GPIO_INPUT);
if (ret) {
return ret;
}
gpio_init_callback(&data->cd_callback, card_detect_gpio_cb,
BIT(cfg->detect_gpio.pin));
ret = gpio_add_callback_dt(&cfg->detect_gpio, &data->cd_callback);
if (ret) {
return ret;
}
}
data->dev = dev;
k_mutex_init(&data->access_mutex);
/* Setup initial host IO values */
data->host_io.clock = 0;
data->host_io.bus_mode = SDHC_BUSMODE_PUSHPULL;
data->host_io.power_mode = SDHC_POWER_OFF;
data->host_io.bus_width = SDHC_BUS_WIDTH1BIT;
data->host_io.timing = SDHC_TIMING_LEGACY;
data->host_io.driver_type = SD_DRIVER_TYPE_B;
data->host_io.signal_voltage = SD_VOL_3_3_V;
return k_sem_init(&data->transfer_sem, 0, 1);
}
static const struct sdhc_driver_api usdhc_api = {
.reset = imx_usdhc_reset,
.request = imx_usdhc_request,
.set_io = imx_usdhc_set_io,
.get_card_present = imx_usdhc_get_card_present,
.execute_tuning = imx_usdhc_execute_tuning,
.card_busy = imx_usdhc_card_busy,
.get_host_props = imx_usdhc_get_host_props,
.enable_interrupt = imx_usdhc_enable_interrupt,
.disable_interrupt = imx_usdhc_disable_interrupt,
};
#ifdef CONFIG_NOCACHE_MEMORY
#define IMX_USDHC_NOCACHE_TAG __attribute__((__section__(".nocache")));
#else
#define IMX_USDHC_NOCACHE_TAG
#endif
#ifdef CONFIG_IMX_USDHC_DMA_SUPPORT
#define IMX_USDHC_DMA_BUFFER_DEFINE(n) \
static uint32_t __aligned(32) \
usdhc_##n##_dma_descriptor[CONFIG_IMX_USDHC_DMA_BUFFER_SIZE / 4]\
IMX_USDHC_NOCACHE_TAG;
#define IMX_USDHC_DMA_BUFFER_INIT(n) \
.usdhc_dma_descriptor = usdhc_##n##_dma_descriptor, \
.dma_descriptor_len = CONFIG_IMX_USDHC_DMA_BUFFER_SIZE / 4,
#else
#define IMX_USDHC_DMA_BUFFER_DEFINE(n)
#define IMX_USDHC_DMA_BUFFER_INIT(n)
#endif /* CONFIG_IMX_USDHC_DMA_SUPPORT */
#define IMX_USDHC_INIT(n) \
static void usdhc_##n##_irq_config_func(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
imx_usdhc_isr, DEVICE_DT_INST_GET(n), 0); \
irq_enable(DT_INST_IRQN(n)); \
} \
\
PINCTRL_DT_INST_DEFINE(n); \
\
static const struct usdhc_config usdhc_##n##_config = { \
.base = (USDHC_Type *) DT_INST_REG_ADDR(n), \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
.clock_subsys = \
(clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \
.nusdhc = n, \
.pwr_gpio = GPIO_DT_SPEC_INST_GET_OR(n, pwr_gpios, {0}), \
.detect_gpio = GPIO_DT_SPEC_INST_GET_OR(n, cd_gpios, {0}), \
.data_timeout = DT_INST_PROP(n, data_timeout), \
.detect_dat3 = DT_INST_PROP(n, detect_dat3), \
.detect_cd = DT_INST_PROP(n, detect_cd), \
.no_180_vol = DT_INST_PROP(n, no_1_8_v), \
.read_watermark = DT_INST_PROP(n, read_watermark), \
.write_watermark = DT_INST_PROP(n, write_watermark), \
.max_current_330 = DT_INST_PROP(n, max_current_330), \
.max_current_180 = DT_INST_PROP(n, max_current_180), \
.min_bus_freq = DT_INST_PROP(n, min_bus_freq), \
.max_bus_freq = DT_INST_PROP(n, max_bus_freq), \
.power_delay_ms = DT_INST_PROP(n, power_delay_ms), \
.mmc_hs200_1_8v = DT_INST_PROP(n, mmc_hs200_1_8v), \
.mmc_hs400_1_8v = DT_INST_PROP(n, mmc_hs400_1_8v), \
.irq_config_func = usdhc_##n##_irq_config_func, \
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
}; \
\
\
IMX_USDHC_DMA_BUFFER_DEFINE(n) \
\
static struct usdhc_data usdhc_##n##_data = { \
.card_present = false, \
IMX_USDHC_DMA_BUFFER_INIT(n) \
}; \
\
DEVICE_DT_INST_DEFINE(n, \
&imx_usdhc_init, \
NULL, \
&usdhc_##n##_data, \
&usdhc_##n##_config, \
POST_KERNEL, \
CONFIG_SDHC_INIT_PRIORITY, \
&usdhc_api);
DT_INST_FOREACH_STATUS_OKAY(IMX_USDHC_INIT)