diff --git a/CODEOWNERS b/CODEOWNERS index f2f7486d40..c64282b532 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -399,6 +399,7 @@ /drivers/spi/*b91* @yurvyn /drivers/spi/spi_rv32m1_lpspi* @karstenkoenig /drivers/spi/*esp32* @glaubermaroto +/drivers/sdhc/ @danieldegrasse /drivers/timer/*apic* @dcpleung @nashif /drivers/timer/apic_tsc.c @andyross /drivers/timer/*arm_arch* @carlocaione diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 2a94c8898e..1ec3020236 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -31,6 +31,7 @@ add_subdirectory_ifdef(CONFIG_PINMUX pinmux) add_subdirectory_ifdef(CONFIG_PWM pwm) add_subdirectory_ifdef(CONFIG_SENSOR sensor) add_subdirectory_ifdef(CONFIG_SPI spi) +add_subdirectory_ifdef(CONFIG_SDHC sdhc) add_subdirectory_ifdef(CONFIG_WATCHDOG watchdog) add_subdirectory_ifdef(CONFIG_WIFI wifi) add_subdirectory_ifdef(CONFIG_CAN can) diff --git a/drivers/Kconfig b/drivers/Kconfig index 0ced9f0343..097cc1699f 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -39,6 +39,8 @@ source "drivers/interrupt_controller/Kconfig.shared_irq" source "drivers/spi/Kconfig" +source "drivers/sdhc/Kconfig" + source "drivers/i2c/Kconfig" source "drivers/i2s/Kconfig" diff --git a/drivers/clock_control/clock_control_mcux_ccm.c b/drivers/clock_control/clock_control_mcux_ccm.c index dec680e166..a05338a3a0 100644 --- a/drivers/clock_control/clock_control_mcux_ccm.c +++ b/drivers/clock_control/clock_control_mcux_ccm.c @@ -96,14 +96,14 @@ static int mcux_ccm_get_subsys_rate(const struct device *dev, break; #endif -#if DT_NODE_HAS_STATUS(DT_NODELABEL(usdhc1), okay) && CONFIG_DISK_DRIVER_SDMMC +#if DT_NODE_HAS_STATUS(DT_NODELABEL(usdhc1), okay) && CONFIG_IMX_USDHC case IMX_CCM_USDHC1_CLK: *rate = CLOCK_GetSysPfdFreq(kCLOCK_Pfd0) / (CLOCK_GetDiv(kCLOCK_Usdhc1Div) + 1U); break; #endif -#if DT_NODE_HAS_STATUS(DT_NODELABEL(usdhc2), okay) && CONFIG_DISK_DRIVER_SDMMC +#if DT_NODE_HAS_STATUS(DT_NODELABEL(usdhc2), okay) && CONFIG_IMX_USDHC case IMX_CCM_USDHC2_CLK: *rate = CLOCK_GetSysPfdFreq(kCLOCK_Pfd0) / (CLOCK_GetDiv(kCLOCK_Usdhc2Div) + 1U); diff --git a/drivers/sdhc/CMakeLists.txt b/drivers/sdhc/CMakeLists.txt new file mode 100644 index 0000000000..9e699ed43d --- /dev/null +++ b/drivers/sdhc/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (c) 2022 NXP +# SPDX-License-Identifier: Apache-2.0 +if (CONFIG_SDHC) + +zephyr_library() +zephyr_library_sources_ifdef(CONFIG_IMX_USDHC imx_usdhc.c) +endif() diff --git a/drivers/sdhc/Kconfig b/drivers/sdhc/Kconfig new file mode 100644 index 0000000000..bc688bdb97 --- /dev/null +++ b/drivers/sdhc/Kconfig @@ -0,0 +1,51 @@ +# Copyright 2022 NXP +# SPDX-License-Identifier: Apache-2.0 + +menuconfig SDHC + bool "SDHC drivers" + help + Include drivers for SD host controller + +if SDHC + +source "drivers/sdhc/Kconfig.imx" + +config SDHC_INIT_PRIORITY + int "SDHC driver init priority" + default 85 + help + SDHC driver system init priority + +config SDHC_BUFFER_ALIGNMENT + int + default 1 + help + Some SD host controllers require alignment of their data buffers + in order to DMA to work correctly. Devices should change default of + this value if they require alignment. This represents the alignment + of buffers required in bytes + +config SDHC_SUPPORTS_UHS + bool + help + Selected by host controller driver if UHS support is present. required + to enable UHS portion of protocol stack. + +config SDHC_SUPPORTS_SPI_MODE + bool + help + Selected by host controller driver if SPI mode support is required. + Enables SPI protocol in SD protocol stack + +config SDHC_SUPPORTS_NATIVE_MODE + bool + help + Selected by host controller driver if native SD mode support is + required. Enables native protocol in SD protocol stack. + +module = SDHC +module-str = sdhc +source "subsys/logging/Kconfig.template.log_config" + + +endif # SDHC diff --git a/drivers/sdhc/Kconfig.imx b/drivers/sdhc/Kconfig.imx new file mode 100644 index 0000000000..e0883e1549 --- /dev/null +++ b/drivers/sdhc/Kconfig.imx @@ -0,0 +1,42 @@ +# Copyright (c) 2022, NXP +# SPDX -License-Identifier: Apache-2.0 + +config IMX_USDHC + bool "NXP IMX USDHC Driver" + depends on (HAS_MCUX_USDHC1 || HAS_MCUX_USDHC2) + select SDHC_SUPPORTS_UHS + select SDHC_SUPPORTS_NATIVE_MODE + help + Enable the NXP IMX SD Host controller driver + +if IMX_USDHC + +config IMX_USDHC_DAT3_PWR_TOGGLE + bool "Toggle power when pulling DAT3 line low" + default y + help + Toggle power to SD card to clear DAT3 pull when pulling line low + +config IMX_USDHC_DMA_SUPPORT + bool + depends on ARCH_HAS_NOCACHE_MEMORY_SUPPORT + default y + select NOCACHE_MEMORY + +if IMX_USDHC_DMA_SUPPORT + +# USDHC DMA needs 32 bit aligned buffers +config SDHC_BUFFER_ALIGNMENT + default 4 + +config IMX_USDHC_DMA_BUFFER_SIZE + int "Size of DMA descriptor buffer in bytes" + default 128 + help + Size of USDHC ADMA descriptor buffer in bytes + +endif # IMX_USDHC_DMA_SUPPORT + + + +endif diff --git a/drivers/sdhc/imx_usdhc.c b/drivers/sdhc/imx_usdhc.c new file mode 100644 index 0000000000..434031d8a2 --- /dev/null +++ b/drivers/sdhc/imx_usdhc.c @@ -0,0 +1,908 @@ +/* + * Copyright (c) 2022 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#define DT_DRV_COMPAT nxp_imx_sdhc + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_PINCTRL +#include +#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) +#endif + +LOG_MODULE_REGISTER(usdhc, CONFIG_SDHC_LOG_LEVEL); + +#include +#include + +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 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; +#ifdef CONFIG_PINCTRL + const struct pinctrl_dev_config *pincfg; +#endif + void (*irq_config_func)(const struct device *dev); +}; + +struct usdhc_data { + 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; + uint8_t usdhc_rx_dummy[64] __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 int imx_usdhc_dat3_pull(const struct usdhc_config *cfg, bool pullup) +{ + int ret = 0U; + +#ifdef CONFIG_PINCTRL + ret = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_NOPULL); + if (ret) { + LOG_ERR("No DAT3 floating state defined, but dat3 detect selected"); + return ret; + } +#else + /* Call board specific function to pull down DAT3 */ + imxrt_usdhc_dat3_pull(pullup); +#endif +#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); +} + +/* + * 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 */ + UDSHC_SelectVoltage(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, 100U) == true ? 0 : -ETIMEDOUT; +} + +/* Wait for USDHC to gate clock when it is disabled */ +static inline void imx_usdhc_wait_clock_gate(USDHC_Type *base) +{ + uint32_t timeout = 1000; + + while (timeout--) { + if (base->PRES_STATE & USDHC_PRES_STATE_SDOFF_MASK) { + break; + } + } + if (timeout == 0) { + LOG_WRN("SD clock did not gate in time"); + } +} + +/* + * 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); + 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: + UDSHC_SelectVoltage(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 */ + UDSHC_SelectVoltage(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 (host_io->power_mode == SDHC_POWER_ON) { + /* Send 74 clock cycles if SD card is just powering on */ + USDHC_SetCardActive(cfg->base, 0xFFFF); + } + if (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_SDR12: + case SDHC_TIMING_SDR25: +#ifdef CONFIG_PINCTRL + pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_SLOW); +#else + imxrt_usdhc_pinmux(cfg->nusdhc, false, 0, 7); +#endif + break; + case SDHC_TIMING_SDR50: +#ifdef CONFIG_PINCTRL + pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_MED); +#else + imxrt_usdhc_pinmux(cfg->nusdhc, false, 2, 7); +#endif + break; + case SDHC_TIMING_SDR104: + case SDHC_TIMING_DDR50: + case SDHC_TIMING_DDR52: + case SDHC_TIMING_HS200: + case SDHC_TIMING_HS400: +#ifdef CONFIG_PINCTRL + pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_FAST); +#else + imxrt_usdhc_pinmux(cfg->nusdhc, false, 3, 7); +#endif + 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; + + cmd.index = SD_SEND_TUNING_BLOCK; + cmd.argument = 0; + cmd.responseType = SD_RSP_TYPE_R1; + + data.blockSize = sizeof(dev_data->usdhc_rx_dummy); + 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); + /* + * 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; + } + + /* 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; + + 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 & 0xF); + 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 SD_APP_SEND_SCR: + case SD_SWITCH: + case SD_APP_SEND_NUM_WRITTEN_BLK: + 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) { + /* 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)); + /* 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_gpio.port) { + data->card_present = gpio_pin_get_dt(&cfg->detect_gpio) > 0; + } else { + LOG_WRN("No card presence 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; +} + +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, + }; + + +#ifdef CONFIG_PINCTRL + ret = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT); + if (ret) { + return ret; + } +#endif + 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; + } + } + k_mutex_init(&data->access_mutex); + memset(&data->host_io, 0, sizeof(data->host_io)); + 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, +}; + +#ifdef CONFIG_PINCTRL +#define IMX_USDHC_PINCTRL_DEFINE(n) PINCTRL_DT_INST_DEFINE(n); +#define IMX_USDHC_PINCTRL_INIT(n) .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), +#else +#define IMX_USDHC_PINCTRL_DEFINE(n) +#define IMX_USDHC_PINCTRL_INIT(n) +#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]\ + __attribute__((__section__(".nocache"))); +#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)); \ + } \ + \ + IMX_USDHC_PINCTRL_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), \ + .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), \ + .irq_config_func = usdhc_##n##_irq_config_func, \ + IMX_USDHC_PINCTRL_INIT(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)