Atmosphere/fusee/fusee-primary/src/sdmmc/sdmmc_core.c
hexkyz ddbbb0b758 fusee: SDMMC driver re-design:
- Based on hekate's, linux's and u-boot's source code;
- Full support for SD and MMC;
- Fixes multiple issues;
- Deployed first in fusee-primary.

fusee: Code cleanup and style fixes.
2018-07-04 21:55:27 +01:00

1971 lines
No EOL
63 KiB
C

#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>
#include <inttypes.h>
#include "sdmmc_core.h"
#include "../car.h"
#include "../pinmux.h"
#include "../timers.h"
#include "../apb_misc.h"
#include "../gpio.h"
#include "../pmc.h"
#include "../lib/driver_utils.h"
#include "../hwinit/max7762x.h"
#define SDMMC_BOUNCE_BUFFER_ADDRESS 0x90000000
static SdmmcLogLevel g_sdmmc_log_level = SDMMC_LOG_NONE;
void sdmmc_set_log_level(SdmmcLogLevel log_level)
{
g_sdmmc_log_level = log_level;
}
static void sdmmc_print(sdmmc_t *sdmmc, SdmmcLogLevel log_level, char *fmt, va_list list)
{
if (log_level > g_sdmmc_log_level)
return;
switch (log_level) {
case SDMMC_LOG_ERROR:
printk("%s [ERROR]: ", sdmmc->name);
break;
case SDMMC_LOG_WARN:
printk("%s [WARN]: ", sdmmc->name);
break;
case SDMMC_LOG_INFO:
printk("%s [INFO]: ", sdmmc->name);
break;
case SDMMC_LOG_DEBUG:
printk("%s [DEBUG]: ", sdmmc->name);
break;
default:
break;
}
vprintk(fmt, list);
printk("\n");
}
void sdmmc_error(sdmmc_t *sdmmc, char *fmt, ...)
{
va_list list;
va_start(list, fmt);
sdmmc_print(sdmmc, SDMMC_LOG_ERROR, fmt, list);
va_end(list);
}
void sdmmc_warn(sdmmc_t *sdmmc, char *fmt, ...)
{
va_list list;
va_start(list, fmt);
sdmmc_print(sdmmc, SDMMC_LOG_WARN, fmt, list);
va_end(list);
}
void sdmmc_info(sdmmc_t *sdmmc, char *fmt, ...)
{
va_list list;
va_start(list, fmt);
sdmmc_print(sdmmc, SDMMC_LOG_INFO, fmt, list);
va_end(list);
}
void sdmmc_debug(sdmmc_t *sdmmc, char *fmt, ...)
{
va_list list;
va_start(list, fmt);
sdmmc_print(sdmmc, SDMMC_LOG_DEBUG, fmt, list);
va_end(list);
}
void sdmmc_dump_regs(sdmmc_t *sdmmc)
{
sdmmc_debug(sdmmc, "dma_address: 0x%08" PRIX32, sdmmc->regs->dma_address);
sdmmc_debug(sdmmc, "block_size: 0x%04" PRIX16, sdmmc->regs->block_size);
sdmmc_debug(sdmmc, "block_count: 0x%04" PRIX16, sdmmc->regs->block_count);
sdmmc_debug(sdmmc, "argument: 0x%08" PRIX32, sdmmc->regs->argument);
sdmmc_debug(sdmmc, "transfer_mode: 0x%04" PRIX16, sdmmc->regs->transfer_mode);
sdmmc_debug(sdmmc, "command: 0x%04" PRIX16, sdmmc->regs->command);
sdmmc_debug(sdmmc, "response[0]: 0x%08" PRIX32, sdmmc->regs->response[0]);
sdmmc_debug(sdmmc, "response[1]: 0x%08" PRIX32, sdmmc->regs->response[1]);
sdmmc_debug(sdmmc, "response[2]: 0x%08" PRIX32, sdmmc->regs->response[2]);
sdmmc_debug(sdmmc, "response[3]: 0x%08" PRIX32, sdmmc->regs->response[3]);
sdmmc_debug(sdmmc, "buffer: 0x%08" PRIX32, sdmmc->regs->buffer);
sdmmc_debug(sdmmc, "present_state: 0x%08" PRIX32, sdmmc->regs->present_state);
sdmmc_debug(sdmmc, "host_control: 0x%02" PRIX8, sdmmc->regs->host_control);
sdmmc_debug(sdmmc, "power_control: 0x%02" PRIX8, sdmmc->regs->power_control);
sdmmc_debug(sdmmc, "block_gap_control: 0x%02" PRIX8, sdmmc->regs->block_gap_control);
sdmmc_debug(sdmmc, "wake_up_control: 0x%02" PRIX8, sdmmc->regs->wake_up_control);
sdmmc_debug(sdmmc, "clock_control: 0x%04" PRIX16, sdmmc->regs->clock_control);
sdmmc_debug(sdmmc, "timeout_control: 0x%02" PRIX8, sdmmc->regs->timeout_control);
sdmmc_debug(sdmmc, "software_reset: 0x%02" PRIX8, sdmmc->regs->software_reset);
sdmmc_debug(sdmmc, "int_status: 0x%08" PRIX32, sdmmc->regs->int_status);
sdmmc_debug(sdmmc, "int_enable: 0x%08" PRIX32, sdmmc->regs->int_enable);
sdmmc_debug(sdmmc, "signal_enable: 0x%08" PRIX32, sdmmc->regs->signal_enable);
sdmmc_debug(sdmmc, "acmd12_err: 0x%04" PRIX16, sdmmc->regs->acmd12_err);
sdmmc_debug(sdmmc, "host_control2: 0x%04" PRIX16, sdmmc->regs->host_control2);
sdmmc_debug(sdmmc, "capabilities: 0x%08" PRIX32, sdmmc->regs->capabilities);
sdmmc_debug(sdmmc, "capabilities_1: 0x%08" PRIX32, sdmmc->regs->capabilities_1);
sdmmc_debug(sdmmc, "max_current: 0x%08" PRIX32, sdmmc->regs->max_current);
sdmmc_debug(sdmmc, "set_acmd12_error: 0x%04" PRIX16, sdmmc->regs->set_acmd12_error);
sdmmc_debug(sdmmc, "set_int_error: 0x%04" PRIX16, sdmmc->regs->set_int_error);
sdmmc_debug(sdmmc, "adma_error: 0x%02" PRIX16, sdmmc->regs->adma_error);
sdmmc_debug(sdmmc, "adma_address: 0x%08" PRIX32, sdmmc->regs->adma_address);
sdmmc_debug(sdmmc, "upper_adma_address: 0x%08" PRIX32, sdmmc->regs->upper_adma_address);
sdmmc_debug(sdmmc, "preset_for_init: 0x%04" PRIX16, sdmmc->regs->preset_for_init);
sdmmc_debug(sdmmc, "preset_for_default: 0x%04" PRIX16, sdmmc->regs->preset_for_default);
sdmmc_debug(sdmmc, "preset_for_high: 0x%04" PRIX16, sdmmc->regs->preset_for_high);
sdmmc_debug(sdmmc, "preset_for_sdr12: 0x%04" PRIX16, sdmmc->regs->preset_for_sdr12);
sdmmc_debug(sdmmc, "preset_for_sdr25: 0x%04" PRIX16, sdmmc->regs->preset_for_sdr25);
sdmmc_debug(sdmmc, "preset_for_sdr50: 0x%04" PRIX16, sdmmc->regs->preset_for_sdr50);
sdmmc_debug(sdmmc, "preset_for_sdr104: 0x%04" PRIX16, sdmmc->regs->preset_for_sdr104);
sdmmc_debug(sdmmc, "preset_for_ddr50: 0x%04" PRIX16, sdmmc->regs->preset_for_ddr50);
sdmmc_debug(sdmmc, "slot_int_status: 0x%04" PRIX16, sdmmc->regs->slot_int_status);
sdmmc_debug(sdmmc, "host_version: 0x%04" PRIX16, sdmmc->regs->host_version);
sdmmc_debug(sdmmc, "vendor_clock_cntrl: 0x%08" PRIX32, sdmmc->regs->vendor_clock_cntrl);
sdmmc_debug(sdmmc, "vendor_sys_sw_cntrl: 0x%08" PRIX32, sdmmc->regs->vendor_sys_sw_cntrl);
sdmmc_debug(sdmmc, "vendor_err_intr_status: 0x%08" PRIX32, sdmmc->regs->vendor_err_intr_status);
sdmmc_debug(sdmmc, "vendor_cap_overrides: 0x%08" PRIX32, sdmmc->regs->vendor_cap_overrides);
sdmmc_debug(sdmmc, "vendor_boot_cntrl: 0x%08" PRIX32, sdmmc->regs->vendor_boot_cntrl);
sdmmc_debug(sdmmc, "vendor_boot_ack_timeout: 0x%08" PRIX32, sdmmc->regs->vendor_boot_ack_timeout);
sdmmc_debug(sdmmc, "vendor_boot_dat_timeout: 0x%08" PRIX32, sdmmc->regs->vendor_boot_dat_timeout);
sdmmc_debug(sdmmc, "vendor_debounce_count: 0x%08" PRIX32, sdmmc->regs->vendor_debounce_count);
sdmmc_debug(sdmmc, "vendor_misc_cntrl: 0x%08" PRIX32, sdmmc->regs->vendor_misc_cntrl);
sdmmc_debug(sdmmc, "max_current_override: 0x%08" PRIX32, sdmmc->regs->max_current_override);
sdmmc_debug(sdmmc, "max_current_override_hi: 0x%08" PRIX32, sdmmc->regs->max_current_override_hi);
sdmmc_debug(sdmmc, "vendor_io_trim_cntrl: 0x%08" PRIX32, sdmmc->regs->vendor_io_trim_cntrl);
sdmmc_debug(sdmmc, "vendor_dllcal_cfg: 0x%08" PRIX32, sdmmc->regs->vendor_dllcal_cfg);
sdmmc_debug(sdmmc, "vendor_dll_ctrl0: 0x%08" PRIX32, sdmmc->regs->vendor_dll_ctrl0);
sdmmc_debug(sdmmc, "vendor_dll_ctrl1: 0x%08" PRIX32, sdmmc->regs->vendor_dll_ctrl1);
sdmmc_debug(sdmmc, "vendor_dllcal_cfg_sta: 0x%08" PRIX32, sdmmc->regs->vendor_dllcal_cfg_sta);
sdmmc_debug(sdmmc, "vendor_tuning_cntrl0: 0x%08" PRIX32, sdmmc->regs->vendor_tuning_cntrl0);
sdmmc_debug(sdmmc, "vendor_tuning_cntrl1: 0x%08" PRIX32, sdmmc->regs->vendor_tuning_cntrl1);
sdmmc_debug(sdmmc, "vendor_tuning_status0: 0x%08" PRIX32, sdmmc->regs->vendor_tuning_status0);
sdmmc_debug(sdmmc, "vendor_tuning_status1: 0x%08" PRIX32, sdmmc->regs->vendor_tuning_status1);
sdmmc_debug(sdmmc, "vendor_clk_gate_hysteresis_count: 0x%08" PRIX32, sdmmc->regs->vendor_clk_gate_hysteresis_count);
sdmmc_debug(sdmmc, "vendor_preset_val0: 0x%08" PRIX32, sdmmc->regs->vendor_preset_val0);
sdmmc_debug(sdmmc, "vendor_preset_val1: 0x%08" PRIX32, sdmmc->regs->vendor_preset_val1);
sdmmc_debug(sdmmc, "vendor_preset_val2: 0x%08" PRIX32, sdmmc->regs->vendor_preset_val2);
sdmmc_debug(sdmmc, "sdmemcomppadctrl: 0x%08" PRIX32, sdmmc->regs->sdmemcomppadctrl);
sdmmc_debug(sdmmc, "auto_cal_config: 0x%08" PRIX32, sdmmc->regs->auto_cal_config);
sdmmc_debug(sdmmc, "auto_cal_interval: 0x%08" PRIX32, sdmmc->regs->auto_cal_interval);
sdmmc_debug(sdmmc, "auto_cal_status: 0x%08" PRIX32, sdmmc->regs->auto_cal_status);
sdmmc_debug(sdmmc, "io_spare: 0x%08" PRIX32, sdmmc->regs->io_spare);
sdmmc_debug(sdmmc, "sdmmca_mccif_fifoctrl: 0x%08" PRIX32, sdmmc->regs->sdmmca_mccif_fifoctrl);
sdmmc_debug(sdmmc, "timeout_wcoal_sdmmca: 0x%08" PRIX32, sdmmc->regs->timeout_wcoal_sdmmca);
}
typedef struct {
uint32_t clk_source_val;
uint32_t clk_div_val;
} sdmmc_clk_source_t;
static sdmmc_clk_source_t sdmmc_clk_sources[4] = {0};
/* Check if the SDMMC device clock is held in reset. */
static bool is_sdmmc_clk_rst(SdmmcControllerNum controller)
{
volatile tegra_car_t *car = car_get_regs();
switch (controller) {
case SDMMC_1:
return (car->rst_dev_l & CLK_L_SDMMC1);
case SDMMC_2:
return (car->rst_dev_l & CLK_L_SDMMC2);
case SDMMC_3:
return (car->rst_dev_u & CLK_U_SDMMC3);
case SDMMC_4:
return (car->rst_dev_l & CLK_L_SDMMC4);
}
return false;
}
/* Put the SDMMC device clock in reset. */
static void sdmmc_clk_set_rst(SdmmcControllerNum controller)
{
volatile tegra_car_t *car = car_get_regs();
switch (controller) {
case SDMMC_1:
car->rst_dev_l_set = CLK_L_SDMMC1;
break;
case SDMMC_2:
car->rst_dev_l_set = CLK_L_SDMMC2;
break;
case SDMMC_3:
car->rst_dev_u_set = CLK_U_SDMMC3;
break;
case SDMMC_4:
car->rst_dev_l_set = CLK_L_SDMMC4;
break;
}
}
/* Take the SDMMC device clock out of reset. */
static void sdmmc_clk_clear_rst(SdmmcControllerNum controller)
{
volatile tegra_car_t *car = car_get_regs();
switch (controller) {
case SDMMC_1:
car->rst_dev_l_clr = CLK_L_SDMMC1;
break;
case SDMMC_2:
car->rst_dev_l_clr = CLK_L_SDMMC2;
break;
case SDMMC_3:
car->rst_dev_u_clr = CLK_U_SDMMC3;
break;
case SDMMC_4:
car->rst_dev_l_clr = CLK_L_SDMMC4;
break;
}
}
/* Check if the SDMMC device clock is enabled. */
static bool is_sdmmc_clk_enb(SdmmcControllerNum controller)
{
volatile tegra_car_t *car = car_get_regs();
switch (controller) {
case SDMMC_1:
return (car->clk_out_enb_l & CLK_L_SDMMC1);
case SDMMC_2:
return (car->clk_out_enb_l & CLK_L_SDMMC2);
case SDMMC_3:
return (car->clk_out_enb_u & CLK_U_SDMMC3);
case SDMMC_4:
return (car->clk_out_enb_l & CLK_L_SDMMC4);
}
return false;
}
/* Enable the SDMMC device clock. */
static void sdmmc_clk_set_enb(SdmmcControllerNum controller)
{
volatile tegra_car_t *car = car_get_regs();
switch (controller) {
case SDMMC_1:
car->clk_enb_l_set = CLK_L_SDMMC1;
break;
case SDMMC_2:
car->clk_enb_l_set = CLK_L_SDMMC2;
break;
case SDMMC_3:
car->clk_enb_u_set = CLK_U_SDMMC3;
break;
case SDMMC_4:
car->clk_enb_l_set = CLK_L_SDMMC4;
break;
}
}
/* Disable the SDMMC device clock. */
static void sdmmc_clk_clear_enb(SdmmcControllerNum controller)
{
volatile tegra_car_t *car = car_get_regs();
switch (controller) {
case SDMMC_1:
car->clk_enb_l_clr = CLK_L_SDMMC1;
break;
case SDMMC_2:
car->clk_enb_l_clr = CLK_L_SDMMC2;
break;
case SDMMC_3:
car->clk_enb_u_clr = CLK_U_SDMMC3;
break;
case SDMMC_4:
car->clk_enb_l_clr = CLK_L_SDMMC4;
break;
}
}
/* Get the appropriate SDMMC maximum frequency. */
static int sdmmc_get_sdclk_freq(SdmmcBusSpeed bus_speed)
{
switch (bus_speed)
{
case SDMMC_SPEED_INIT_HS:
case SDMMC_SPEED_HS26:
return 26000;
case SDMMC_SPEED_HS52:
return 52000;
case SDMMC_SPEED_HS200:
case SDMMC_SPEED_HS400:
case SDMMC_SPEED_SDR104:
return 200000;
case SDMMC_SPEED_INIT_SDR:
case SDMMC_SPEED_UNK6:
case SDMMC_SPEED_SDR12:
return 25000;
case SDMMC_SPEED_SDR25:
return 50000;
case SDMMC_SPEED_SDR50:
return 100000;
case SDMMC_SPEED_DDR50:
return 40800;
case SDMMC_SPEED_UNK14:
return 200000;
default:
return 0;
}
}
/* Get the appropriate SDMMC divider for the SDCLK. */
static int sdmmc_get_sdclk_div(SdmmcBusSpeed bus_speed)
{
switch (bus_speed)
{
case SDMMC_SPEED_INIT_HS:
return 66;
case SDMMC_SPEED_INIT_SDR:
// TODO: TRM says return 64?
case SDMMC_SPEED_HS26:
case SDMMC_SPEED_HS52:
case SDMMC_SPEED_HS200:
case SDMMC_SPEED_HS400:
case SDMMC_SPEED_UNK6:
case SDMMC_SPEED_SDR25:
case SDMMC_SPEED_SDR12:
case SDMMC_SPEED_SDR50:
case SDMMC_SPEED_SDR104:
case SDMMC_SPEED_DDR50:
return 1;
case SDMMC_SPEED_UNK14:
return 2;
default:
return 0;
}
}
/* Set the device clock source and CAR divider. */
static int sdmmc_clk_set_source(SdmmcControllerNum controller, uint32_t clk_freq)
{
volatile tegra_car_t *car = car_get_regs();
uint32_t car_div = 0;
uint32_t out_freq = 0;
switch (clk_freq)
{
case 25000:
out_freq = 24728;
car_div = SDMMC_CAR_DIVIDER_SDR12;
break;
case 26000:
out_freq = 25500;
car_div = SDMMC_CAR_DIVIDER_HS26;
break;
case 40800:
out_freq = 40800;
car_div = SDMMC_CAR_DIVIDER_DDR50;
break;
case 50000:
out_freq = 48000;
car_div = SDMMC_CAR_DIVIDER_SDR25;
break;
case 52000:
out_freq = 51000;
car_div = SDMMC_CAR_DIVIDER_HS52;
break;
case 100000:
out_freq = 90667;
car_div = SDMMC_CAR_DIVIDER_SDR50;
break;
case 200000:
out_freq = 163200;
car_div = SDMMC_CAR_DIVIDER_HS200;
break;
case 208000:
out_freq = 204000;
car_div = SDMMC_CAR_DIVIDER_SDR104;
break;
default:
return 0;
}
sdmmc_clk_sources[controller].clk_source_val = clk_freq;
sdmmc_clk_sources[controller].clk_div_val = out_freq;
switch (controller)
{
case SDMMC_1:
car->clk_src[CLK_SOURCE_SDMMC1] = (CLK_SOURCE_FIRST | car_div);
break;
case SDMMC_2:
car->clk_src[CLK_SOURCE_SDMMC2] = (CLK_SOURCE_FIRST | car_div);
break;
case SDMMC_3:
car->clk_src[CLK_SOURCE_SDMMC3] = (CLK_SOURCE_FIRST | car_div);
break;
case SDMMC_4:
car->clk_src[CLK_SOURCE_SDMMC4] = (CLK_SOURCE_FIRST | car_div);
break;
}
return out_freq;
}
/* Adjust the device clock source value. */
static int sdmmc_clk_adjust_source(SdmmcControllerNum controller, uint32_t clk_source_val)
{
uint32_t out_val = 0;
if (sdmmc_clk_sources[controller].clk_source_val == clk_source_val)
out_val = sdmmc_clk_sources[controller].clk_div_val;
else
{
bool was_sdmmc_clk_enb = is_sdmmc_clk_enb(controller);
/* Clock was already enabled. Disable it. */
if (was_sdmmc_clk_enb)
sdmmc_clk_clear_enb(controller);
out_val = sdmmc_clk_set_source(controller, clk_source_val);
/* Clock was already enabled. Enable it back. */
if (was_sdmmc_clk_enb)
sdmmc_clk_set_enb(controller);
/* Dummy read for value refreshing. */
is_sdmmc_clk_rst(controller);
}
return out_val;
}
/* Enable the SD clock if possible. */
static void sdmmc_enable_sd_clock(sdmmc_t *sdmmc)
{
if ((sdmmc->has_sd) && !(sdmmc->regs->clock_control & TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE))
sdmmc->regs->clock_control |= TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE;
sdmmc->is_sd_clk_enabled = true;
}
/* Disable the SD clock. */
static void sdmmc_disable_sd_clock(sdmmc_t *sdmmc)
{
sdmmc->is_sd_clk_enabled = false;
sdmmc->regs->clock_control &= ~TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE;
}
/* Automatically enable or disable the SD clock. */
void sdmmc_adjust_sd_clock(sdmmc_t *sdmmc)
{
if (!(sdmmc->has_sd) && (sdmmc->regs->clock_control & TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE))
sdmmc_disable_sd_clock(sdmmc);
else if (sdmmc->is_sd_clk_enabled && !(sdmmc->regs->clock_control & TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE))
sdmmc_enable_sd_clock(sdmmc);
}
/* Return the clock control value. Used for dummy reads. */
static int sdmmc_get_sd_clock_control(sdmmc_t *sdmmc)
{
return sdmmc->regs->clock_control;
}
/* Start the SDMMC clock. */
static void sdmmc_clk_start(SdmmcControllerNum controller, uint32_t clk_source_val)
{
/* Clock was already enabled. Disable it. */
if (is_sdmmc_clk_enb(controller))
sdmmc_clk_clear_enb(controller);
/* Put the device clock in reset. */
sdmmc_clk_set_rst(controller);
/* Configure the device clock source. */
uint32_t clk_div = sdmmc_clk_set_source(controller, clk_source_val);
/* Enable the device clock. */
sdmmc_clk_set_enb(controller);
/* Dummy read for value refreshing. */
is_sdmmc_clk_rst(controller);
/* Synchronize. */
udelay((100000 + clk_div - 1) / clk_div);
/* Take the device clock out of reset. */
sdmmc_clk_clear_rst(controller);
/* Dummy read for value refreshing. */
is_sdmmc_clk_rst(controller);
}
/* Stop the SDMMC clock. */
static void sdmmc_clk_stop(SdmmcControllerNum controller)
{
/* Put the device clock in reset. */
sdmmc_clk_set_rst(controller);
/* Disable the device clock. */
sdmmc_clk_clear_enb(controller);
/* Dummy read for value refreshing. */
is_sdmmc_clk_rst(controller);
}
/* Configure clock trimming. */
static void sdmmc_vendor_clock_cntrl_config(sdmmc_t *sdmmc)
{
/* Clear the I/O conditioning constants. */
sdmmc->regs->vendor_clock_cntrl &= ~(SDMMC_CLOCK_TRIM_MASK | SDMMC_CLOCK_TAP_MASK);
/* Per the TRM, set the PADPIPE clock enable */
sdmmc->regs->vendor_clock_cntrl |= SDMMC_CLOCK_PADPIPE_CLKEN_OVERRIDE;
/* Set the appropriate trim value. */
switch (sdmmc->controller) {
case SDMMC_1:
sdmmc->regs->vendor_clock_cntrl |= SDMMC_CLOCK_TRIM_SDMMC1;
break;
case SDMMC_2:
sdmmc->regs->vendor_clock_cntrl |= SDMMC_CLOCK_TRIM_SDMMC2;
break;
case SDMMC_3:
sdmmc->regs->vendor_clock_cntrl |= SDMMC_CLOCK_TRIM_SDMMC3;
break;
case SDMMC_4:
sdmmc->regs->vendor_clock_cntrl |= SDMMC_CLOCK_TRIM_SDMMC4;
break;
}
}
/* Configure automatic calibration. */
static int sdmmc_autocal_config(sdmmc_t *sdmmc, SdmmcBusVoltage voltage)
{
switch (sdmmc->controller) {
case SDMMC_1:
case SDMMC_3:
switch (voltage) {
case SDMMC_VOLTAGE_1V8:
sdmmc->regs->auto_cal_config &= ~SDMMC_AUTOCAL_PDPU_CONFIG_MASK;
sdmmc->regs->auto_cal_config |= SDMMC_AUTOCAL_PDPU_SDMMC1_1V8;
break;
case SDMMC_VOLTAGE_3V3:
sdmmc->regs->auto_cal_config &= ~SDMMC_AUTOCAL_PDPU_CONFIG_MASK;
sdmmc->regs->auto_cal_config |= SDMMC_AUTOCAL_PDPU_SDMMC1_3V3;
break;
default:
sdmmc_error(sdmmc, "microsd does not support voltage %d", voltage);
return 0;
}
break;
case SDMMC_2:
case SDMMC_4:
if (voltage != SDMMC_VOLTAGE_1V8) {
sdmmc_error(sdmmc, "eMMC can only run at 1V8, but sdmmc struct claims voltage %d", voltage);
return 0;
}
sdmmc->regs->auto_cal_config &= ~SDMMC_AUTOCAL_PDPU_CONFIG_MASK;
sdmmc->regs->auto_cal_config |= SDMMC_AUTOCAL_PDPU_SDMMC4_1V8;
break;
}
return 1;
}
/* Run automatic calibration. */
static void sdmmc_autocal_run(sdmmc_t *sdmmc, SdmmcBusVoltage voltage)
{
bool restart_sd_clock = false;
volatile tegra_padctl_t *padctl = padctl_get_regs();
/* SD clock is enabled. Disable it and restart later. */
if (sdmmc->is_sd_clk_enabled)
{
restart_sd_clock = true;
sdmmc_disable_sd_clock(sdmmc);
}
/* Set PAD_E_INPUT_OR_E_PWRD */
if (!(sdmmc->regs->sdmemcomppadctrl & 0x80000000))
{
sdmmc->regs->sdmemcomppadctrl |= 0x80000000;
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Delay. */
udelay(1);
}
/* Start automatic calibration. */
sdmmc->regs->auto_cal_config |= (SDMMC_AUTOCAL_START | SDMMC_AUTOCAL_ENABLE);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Delay. */
udelay(1);
/* Get current time. */
uint32_t timebase = get_time();
/* Wait until the autocal is complete. */
while ((sdmmc->regs->auto_cal_status & SDMMC_AUTOCAL_ACTIVE)) {
/* Ensure we haven't timed out. */
if (get_time_since(timebase) > SDMMC_AUTOCAL_TIMEOUT) {
sdmmc_error(sdmmc, "autocal timed out!");
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Upon timeout, fall back to standard values. */
if (sdmmc->controller == SDMMC_1) {
uint32_t drvup = (voltage == SDMMC_VOLTAGE_3V3) ? 0x12 : 0x11;
uint32_t drvdn = (voltage == SDMMC_VOLTAGE_3V3) ? 0x12 : 0x15;
uint32_t value = padctl->sdmmc1_pad_cfgpadctrl;
value &= ~(SDMMC1_PAD_CAL_DRVUP_MASK | SDMMC1_PAD_CAL_DRVDN_MASK);
value |= (drvup << SDMMC1_PAD_CAL_DRVUP_SHIFT);
value |= (drvdn << SDMMC1_PAD_CAL_DRVDN_SHIFT);
padctl->sdmmc1_pad_cfgpadctrl = value;
} else if (sdmmc->controller == SDMMC_4) {
uint32_t value = padctl->emmc4_pad_cfgpadctrl;
value &= ~(CFG2TMC_EMMC4_PAD_DRVUP_COMP_MASK | CFG2TMC_EMMC4_PAD_DRVDN_COMP_MASK);
value |= (0x10 << CFG2TMC_EMMC4_PAD_DRVUP_COMP_SHIFT);
value |= (0x10 << CFG2TMC_EMMC4_PAD_DRVDN_COMP_SHIFT);
padctl->emmc4_pad_cfgpadctrl = value;
}
/* Manually clear the autocal enable bit. */
sdmmc->regs->auto_cal_config &= ~SDMMC_AUTOCAL_ENABLE;
break;
}
}
/* Clear PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only) */
sdmmc->regs->sdmemcomppadctrl &= ~(0x80000000);
/* If requested, enable the SD clock. */
if (restart_sd_clock)
sdmmc_enable_sd_clock(sdmmc);
}
static int sdmmc_int_clk_enable(sdmmc_t *sdmmc)
{
/* Enable the internal clock. */
sdmmc->regs->clock_control |= TEGRA_MMC_CLKCON_INTERNAL_CLOCK_ENABLE;
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Program a timeout of 2000ms. */
uint32_t timebase = get_time();
bool is_timeout = false;
/* Wait for the clock to stabilize. */
while (!(sdmmc->regs->clock_control & TEGRA_MMC_CLKCON_INTERNAL_CLOCK_STABLE) && !is_timeout) {
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 2000000);
}
/* Clock failed to stabilize. */
if (is_timeout) {
sdmmc_error(sdmmc, "clock never stabilized!");
return 0;
}
/* Configure clock control and host control 2. */
sdmmc->regs->host_control2 &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
sdmmc->regs->clock_control &= ~TEGRA_MMC_CLKCON_PROG_CLOCK_MODE;
sdmmc->regs->host_control2 |= SDHCI_HOST_VERSION_4_EN;
/* Ensure 64bit addressing is supported. */
if (!(sdmmc->regs->capabilities & SDHCI_CAN_64BIT)) {
sdmmc_error(sdmmc, "64bit addressing is unsupported!");
return 0;
}
/* Enable 64bit addressing. */
sdmmc->regs->host_control2 |= SDHCI_ADDRESSING_64BIT_EN;
/* Use SDMA by default. */
sdmmc->regs->host_control &= ~SDHCI_CTRL_DMA_MASK;
/* Change to ADMA if requested. */
if (sdmmc->use_adma && (sdmmc->regs->capabilities & SDHCI_CAN_DO_ADMA2)) {
if (sdmmc->regs->capabilities & SDHCI_CAN_64BIT)
sdmmc->regs->host_control |= SDHCI_CTRL_ADMA64;
else
sdmmc->regs->host_control |= SDHCI_CTRL_ADMA32;
}
/* Set the timeout to be the maximum value. */
sdmmc->regs->timeout_control &= 0xF0;
sdmmc->regs->timeout_control |= 0x0E;
return 1;
}
void sdmmc_select_bus_width(sdmmc_t *sdmmc, SdmmcBusWidth width)
{
if (width == SDMMC_BUS_WIDTH_1BIT)
{
sdmmc->regs->host_control &= ~(SDHCI_CTRL_4BITBUS | SDHCI_CTRL_8BITBUS);
sdmmc->bus_width = SDMMC_BUS_WIDTH_1BIT;
}
else if (width == SDMMC_BUS_WIDTH_4BIT)
{
sdmmc->regs->host_control |= SDHCI_CTRL_4BITBUS;
sdmmc->regs->host_control &= ~SDHCI_CTRL_8BITBUS;
sdmmc->bus_width = SDMMC_BUS_WIDTH_4BIT;
}
else if (width == SDMMC_BUS_WIDTH_8BIT)
{
sdmmc->regs->host_control |= SDHCI_CTRL_8BITBUS;
sdmmc->bus_width = SDMMC_BUS_WIDTH_8BIT;
}
else
sdmmc_error(sdmmc, "Invalid bus width specified!");
}
void sdmmc_select_voltage(sdmmc_t *sdmmc, SdmmcBusVoltage voltage)
{
if (voltage == SDMMC_VOLTAGE_NONE)
{
sdmmc->regs->power_control &= ~TEGRA_MMC_PWRCTL_SD_BUS_POWER;
sdmmc->bus_voltage = SDMMC_VOLTAGE_NONE;
}
else if (voltage == SDMMC_VOLTAGE_1V8)
{
sdmmc->regs->power_control |= TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V1_8;
sdmmc->regs->power_control |= TEGRA_MMC_PWRCTL_SD_BUS_POWER;
sdmmc->bus_voltage = SDMMC_VOLTAGE_1V8;
}
else if (voltage == SDMMC_VOLTAGE_3V3)
{
sdmmc->regs->power_control |= TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V3_3;
sdmmc->regs->power_control |= TEGRA_MMC_PWRCTL_SD_BUS_POWER;
sdmmc->bus_voltage = SDMMC_VOLTAGE_3V3;
}
else
sdmmc_error(sdmmc, "Invalid power state specified!");
}
static void sdmmc_tap_config(sdmmc_t *sdmmc, SdmmcBusSpeed bus_speed)
{
if (bus_speed == SDMMC_SPEED_HS400)
{
/* Clear and set DQS_TRIM_VAL (used in HS400) */
sdmmc->regs->vendor_cap_overrides &= ~(0x3F00);
sdmmc->regs->vendor_cap_overrides |= 0x2800;
}
/* Clear TAP_VAL_UPDATED_BY_HW */
sdmmc->regs->vendor_tuning_cntrl0 &= ~(0x20000);
if (bus_speed == SDMMC_SPEED_HS400)
{
/* We must have obtained the tap value from the tuning procedure here. */
if (sdmmc->is_tuning_tap_val_set)
{
/* Clear and set the tap value. */
sdmmc->regs->vendor_clock_cntrl &= ~(0xFF0000);
sdmmc->regs->vendor_clock_cntrl |= (sdmmc->tap_val << 16);
}
}
else
{
/* Use the recommended values. */
switch (sdmmc->controller)
{
case SDMMC_1:
sdmmc->tap_val = 4;
break;
case SDMMC_2:
case SDMMC_4:
sdmmc->tap_val = 0;
break;
case SDMMC_3:
sdmmc->tap_val = 3;
break;
}
/* Clear and set the tap value. */
sdmmc->regs->vendor_clock_cntrl &= ~(0xFF0000);
sdmmc->regs->vendor_clock_cntrl |= (sdmmc->tap_val << 16);
}
}
static int sdmmc_dllcal_run(sdmmc_t *sdmmc)
{
bool shutdown_sd_clock = false;
/* SD clock is disabled. Enable it. */
if (!sdmmc->is_sd_clk_enabled)
{
shutdown_sd_clock = true;
sdmmc_enable_sd_clock(sdmmc);
}
/* Set the CALIBRATE bit. */
sdmmc->regs->vendor_dllcal_cfg |= 0x80000000;
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Program a timeout of 5ms. */
uint32_t timebase = get_time();
bool is_timeout = false;
/* Wait for CALIBRATE to be cleared. */
while ((sdmmc->regs->vendor_dllcal_cfg & 0x80000000) && !is_timeout) {
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 5000);
}
/* Calibration failed. */
if (is_timeout) {
sdmmc_error(sdmmc, "DLLCAL failed!");
return 0;
}
/* Program a timeout of 10ms. */
timebase = get_time();
is_timeout = false;
/* Wait for DLL_CAL_ACTIVE to be cleared. */
while ((sdmmc->regs->vendor_dllcal_cfg_sta & 0x80000000) && !is_timeout) {
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 10000);
}
/* Clock failed to stabilize. */
if (is_timeout) {
sdmmc_error(sdmmc, "ERROR: DLLCAL failed!");
return 0;
}
/* If requested, disable the SD clock. */
if (shutdown_sd_clock)
sdmmc_disable_sd_clock(sdmmc);
return 1;
}
int sdmmc_select_speed(sdmmc_t *sdmmc, SdmmcBusSpeed bus_speed)
{
bool restart_sd_clock = false;
/* SD clock is enabled. Disable it and restart later. */
if (sdmmc->is_sd_clk_enabled)
{
restart_sd_clock = true;
sdmmc_disable_sd_clock(sdmmc);
}
/* Configure tap values as necessary. */
sdmmc_tap_config(sdmmc, bus_speed);
/* Set the appropriate host speed. */
switch (bus_speed) {
/* 400kHz initialization mode and a few others. */
case SDMMC_SPEED_INIT_HS:
case SDMMC_SPEED_HS26:
case SDMMC_SPEED_INIT_SDR:
case SDMMC_SPEED_UNK6:
sdmmc->regs->host_control &= ~(SDHCI_CTRL_HISPD);
sdmmc->regs->host_control2 &= ~(SDHCI_CTRL_VDD_180);
break;
/* 50MHz high speed (SD) and 52MHz high speed (MMC). */
case SDMMC_SPEED_SDR25:
case SDMMC_SPEED_HS52:
sdmmc->regs->host_control |= SDHCI_CTRL_HISPD;
sdmmc->regs->host_control2 &= ~(SDHCI_CTRL_VDD_180);
break;
/* 200MHz UHS-I (SD) and other modes due to errata. */
case SDMMC_SPEED_HS200:
case SDMMC_SPEED_SDR104:
case SDMMC_SPEED_DDR50:
case SDMMC_SPEED_SDR50:
case SDMMC_SPEED_UNK14:
sdmmc->regs->host_control2 &= SDHCI_CTRL_UHS_MASK;
sdmmc->regs->host_control2 |= (SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_VDD_180);
break;
/* 200MHz single-data rate (MMC). */
case SDMMC_SPEED_HS400:
sdmmc->regs->host_control2 &= SDHCI_CTRL_UHS_MASK;
sdmmc->regs->host_control2 |= (SDHCI_CTRL_HS400 | SDHCI_CTRL_VDD_180);
break;
/* 25MHz default speed (SD). */
case SDMMC_SPEED_SDR12:
sdmmc->regs->host_control2 &= SDHCI_CTRL_UHS_MASK;
sdmmc->regs->host_control2 |= (SDHCI_CTRL_UHS_SDR12 | SDHCI_CTRL_VDD_180);
break;
default:
sdmmc_error(sdmmc, "Switching to unsupported speed!");
return 0;
}
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Get the clock's frequency and divider. */
uint32_t freq_val = sdmmc_get_sdclk_freq(bus_speed);
uint32_t div_val = sdmmc_get_sdclk_div(bus_speed);
/* Adjust the CAR side of the clock. */
uint32_t out_freq_val = sdmmc_clk_adjust_source(sdmmc->controller, freq_val);
/* Save the internal divider value. */
sdmmc->internal_divider = ((out_freq_val + div_val - 1) / div_val);
uint16_t div_val_lo = div_val >> 1;
uint16_t div_val_hi = 0;
if (div_val_lo > 0xFF)
div_val_hi = (div_val_lo >> 8);
/* Set the clock control divider values. */
sdmmc->regs->clock_control &= ~((SDHCI_DIV_HI_MASK | SDHCI_DIV_MASK) << 6);
sdmmc->regs->clock_control |= ((div_val_hi << SDHCI_DIVIDER_HI_SHIFT) | (div_val_lo << SDHCI_DIVIDER_SHIFT));
/* If requested, enable the SD clock. */
if (restart_sd_clock)
sdmmc_enable_sd_clock(sdmmc);
/* Run DLLCAL for HS400 only */
if (bus_speed == SDMMC_SPEED_HS400)
return sdmmc_dllcal_run(sdmmc);
return 1;
}
static int sdmmc1_config()
{
volatile tegra_pinmux_t *pinmux = pinmux_get_regs();
volatile tegra_padctl_t *padctl = padctl_get_regs();
volatile tegra_pmc_t *pmc = pmc_get_regs();
/* Set up the card detect pin as a GPIO input */
pinmux->pz1 = PINMUX_SELECT_FUNCTION1 | PINMUX_PULL_UP | PINMUX_INPUT;
padctl->vgpio_gpio_mux_sel = 0;
gpio_configure_mode(GPIO_MICROSD_CARD_DETECT, GPIO_MODE_GPIO);
gpio_configure_direction(GPIO_MICROSD_CARD_DETECT, GPIO_DIRECTION_INPUT);
udelay(100);
/* Check the GPIO. */
if (gpio_read(GPIO_MICROSD_CARD_DETECT))
return 0;
padctl->sdmmc1_clk_lpbk_control = 1;
/* Set up the SDMMC1 pinmux. */
pinmux->sdmmc1_clk = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT;
pinmux->sdmmc1_cmd = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP;
pinmux->sdmmc1_dat3 = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP;
pinmux->sdmmc1_dat2 = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP;
pinmux->sdmmc1_dat1 = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP;
pinmux->sdmmc1_dat0 = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP;
/* Ensure the PMC is prepared for the SDMMC1 card to receive power. */
pmc->no_iopower &= ~PMC_CONTROL_SDMMC1;
pmc->pwr_det_val |= PMC_CONTROL_SDMMC1;
/* Configure the enable line for the SD card power. */
pinmux->dmic3_clk = PINMUX_SELECT_FUNCTION1 | PINMUX_PULL_DOWN | PINMUX_INPUT;
gpio_configure_mode(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_MODE_GPIO);
gpio_write(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_LEVEL_HIGH);
gpio_configure_direction(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_DIRECTION_OUTPUT);
udelay(1000);
/* Set up SD card voltages. */
max77620_regulator_set_voltage(REGULATOR_LDO2, 3300000);
max77620_regulator_enable(REGULATOR_LDO2, 1);
udelay(1000);
padctl->sdmmc1_pad_cfgpadctrl = 0x10000000;
udelay(1000);
return 1;
}
static int sdmmc2_config()
{
return 1;
}
static int sdmmc3_config()
{
return 1;
}
static int sdmmc4_config()
{
return 1;
}
static int sdmmc_init_controller(sdmmc_t *sdmmc, SdmmcControllerNum controller)
{
/* Sanitize input number for the controller. */
if ((controller < SDMMC_1) || (controller > SDMMC_4))
return 0;
/* Clear up memory for our struct. */
memset(sdmmc, 0, sizeof(sdmmc_t));
/* Bind the appropriate controller and it's register space to our struct. */
sdmmc->controller = controller;
sdmmc->regs = sdmmc_get_regs(controller);
/* Set up per-device pointers and properties. */
switch (sdmmc->controller) {
case SDMMC_1:
/* Controller properties. */
sdmmc->name = "uSD";
sdmmc->has_sd = true;
sdmmc->is_clk_running = false;
sdmmc->is_sd_clk_enabled = false;
sdmmc->is_tuning_tap_val_set = false;
sdmmc->use_adma = false;
sdmmc->dma_bounce_buf = (uint8_t*)SDMMC_BOUNCE_BUFFER_ADDRESS;
sdmmc->tap_val = 0;
sdmmc->internal_divider = 0;
sdmmc->bus_voltage = SDMMC_VOLTAGE_NONE;
/* Function pointers. */
sdmmc->sdmmc_config = sdmmc1_config;
break;
case SDMMC_2:
/* Controller properties. */
sdmmc->name = "GC";
sdmmc->has_sd = true;
sdmmc->is_clk_running = false;
sdmmc->is_sd_clk_enabled = false;
sdmmc->is_tuning_tap_val_set = false;
sdmmc->use_adma = false;
sdmmc->dma_bounce_buf = (uint8_t*)SDMMC_BOUNCE_BUFFER_ADDRESS;
sdmmc->tap_val = 0;
sdmmc->internal_divider = 0;
sdmmc->bus_voltage = SDMMC_VOLTAGE_NONE;
/* Function pointers. */
sdmmc->sdmmc_config = sdmmc2_config;
break;
case SDMMC_3:
/* Controller properties. */
sdmmc->name = "UNUSED";
sdmmc->has_sd = true;
sdmmc->is_clk_running = false;
sdmmc->is_sd_clk_enabled = false;
sdmmc->is_tuning_tap_val_set = false;
sdmmc->use_adma = false;
sdmmc->dma_bounce_buf = (uint8_t*)SDMMC_BOUNCE_BUFFER_ADDRESS;
sdmmc->tap_val = 0;
sdmmc->internal_divider = 0;
sdmmc->bus_voltage = SDMMC_VOLTAGE_NONE;
/* Function pointers. */
sdmmc->sdmmc_config = sdmmc3_config;
break;
case SDMMC_4:
/* Controller properties. */
sdmmc->name = "eMMC";
sdmmc->has_sd = true;
sdmmc->is_clk_running = false;
sdmmc->is_sd_clk_enabled = false;
sdmmc->is_tuning_tap_val_set = false;
sdmmc->use_adma = false;
sdmmc->dma_bounce_buf = (uint8_t*)SDMMC_BOUNCE_BUFFER_ADDRESS;
sdmmc->tap_val = 0;
sdmmc->internal_divider = 0;
sdmmc->bus_voltage = SDMMC_VOLTAGE_NONE;
/* Function pointers. */
sdmmc->sdmmc_config = sdmmc4_config;
break;
}
return 1;
}
int sdmmc_init(sdmmc_t *sdmmc, SdmmcControllerNum controller, SdmmcBusVoltage bus_voltage, SdmmcBusWidth bus_width, SdmmcBusSpeed bus_speed)
{
/* Initialize our controller structure. */
if (!sdmmc_init_controller(sdmmc, controller)) {
sdmmc_error(sdmmc, "Failed to initialize SDMMC%d", controller + 1);
return 0;
}
/* Perform initial configuration steps if necessary. */
if (!sdmmc->sdmmc_config()) {
sdmmc_error(sdmmc, "Failed to configure controller!");
return 0;
}
/* Initialize the clock status. */
sdmmc->is_clk_running = false;
/* Clock is enabled and out of reset. Shouldn't happen. */
if (!is_sdmmc_clk_rst(controller) && is_sdmmc_clk_enb(controller)) {
/* Disable the SD clock. */
sdmmc_disable_sd_clock(sdmmc);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
}
/* Sort out the clock's frequency. */
uint32_t clk_freq_val = sdmmc_get_sdclk_freq(bus_speed);
/* Start the SDMMC clock. */
sdmmc_clk_start(controller, clk_freq_val);
/* Update the clock status. */
sdmmc->is_clk_running = true;
// Set IO_SPARE[19] (one cycle delay)
sdmmc->regs->io_spare |= 0x80000;
// Clear SEL_VREG
sdmmc->regs->vendor_io_trim_cntrl &= ~(0x04);
/* Configure vendor clocking. */
sdmmc_vendor_clock_cntrl_config(sdmmc);
// Set SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL to 0x07
sdmmc->regs->sdmemcomppadctrl &= 0x0F;
sdmmc->regs->sdmemcomppadctrl |= 0x07;
/* Configure autocal offsets. */
if (!sdmmc_autocal_config(sdmmc, bus_voltage)) {
sdmmc_error(sdmmc, "Failed to configure automatic calibration!");
return 0;
}
/* Do autocal. */
sdmmc_autocal_run(sdmmc, bus_voltage);
/* Enable the internal clock. */
if (!sdmmc_int_clk_enable(sdmmc)) {
sdmmc_error(sdmmc, "Failed to enable the internal clock!");
return 0;
}
/* Select the desired bus width. */
sdmmc_select_bus_width(sdmmc, bus_width);
/* Select the desired voltage. */
sdmmc_select_voltage(sdmmc, bus_voltage);
/* Enable the internal clock. */
if (!sdmmc_select_speed(sdmmc, bus_speed)) {
sdmmc_error(sdmmc, "Failed to apply the correct bus speed!");
return 0;
}
/* Correct any inconsistent states. */
sdmmc_adjust_sd_clock(sdmmc);
/* Enable the SD clock. */
sdmmc_enable_sd_clock(sdmmc);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
return 1;
}
void sdmmc_finish(sdmmc_t *sdmmc)
{
/* Stop everything. */
if (sdmmc->is_clk_running)
{
/* Disable the SD clock. */
sdmmc_disable_sd_clock(sdmmc);
/* Disable SD power. */
sdmmc_select_voltage(sdmmc, SDMMC_VOLTAGE_NONE);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Stop the SD clock. */
sdmmc_clk_stop(sdmmc->controller);
/* Clock is no longer running by now. */
sdmmc->is_clk_running = false;
}
}
static void sdmmc_do_sw_reset(sdmmc_t *sdmmc)
{
/* Assert a software reset. */
sdmmc->regs->software_reset |= (TEGRA_MMC_SWRST_SW_RESET_FOR_CMD_LINE | TEGRA_MMC_SWRST_SW_RESET_FOR_DAT_LINE);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Program a timeout of 100ms. */
uint32_t timebase = get_time();
bool is_timeout = false;
/* Wait for the register to be cleared. */
while ((sdmmc->regs->software_reset & (TEGRA_MMC_SWRST_SW_RESET_FOR_CMD_LINE | TEGRA_MMC_SWRST_SW_RESET_FOR_DAT_LINE)) && !is_timeout) {
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 100000);
}
}
static int sdmmc_wait_for_inhibit(sdmmc_t *sdmmc, bool wait_for_dat)
{
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Program a timeout of 10ms. */
uint32_t timebase = get_time();
bool is_timeout = false;
/* Wait on CMD inhibit to be cleared. */
while ((sdmmc->regs->present_state & TEGRA_MMC_PRNSTS_CMD_INHIBIT_CMD) && !is_timeout) {
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 10000);
}
/* Bit was never released. Reset. */
if (is_timeout)
{
sdmmc_do_sw_reset(sdmmc);
return 0;
}
if (wait_for_dat)
{
/* Program a timeout of 10ms. */
timebase = get_time();
is_timeout = false;
/* Wait on DAT inhibit to be cleared. */
while ((sdmmc->regs->present_state & TEGRA_MMC_PRNSTS_CMD_INHIBIT_DAT) && !is_timeout) {
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 10000);
}
/* Bit was never released, reset. */
if (is_timeout)
{
sdmmc_do_sw_reset(sdmmc);
return 0;
}
}
return 1;
}
static int sdmmc_wait_busy(sdmmc_t *sdmmc)
{
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Program a timeout of 10ms. */
uint32_t timebase = get_time();
bool is_timeout = false;
/* Wait on DAT0 level mask to be set. */
while (!(sdmmc->regs->present_state & SDHCI_DATA_0_LVL_MASK) && !is_timeout) {
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 10000);
}
/* Bit was never released. Reset. */
if (is_timeout)
{
sdmmc_do_sw_reset(sdmmc);
return 0;
}
return 1;
}
static void sdmmc_intr_enable(sdmmc_t *sdmmc)
{
/* Set all error bits and enable the relevant interrupts. */
sdmmc->regs->int_enable |= (0x017F0000 | (TEGRA_MMC_NORINTSTSEN_CMD_COMPLETE | TEGRA_MMC_NORINTSTSEN_XFER_COMPLETE | TEGRA_MMC_NORINTSTSEN_DMA_INTERRUPT));
/* Refresh status. */
sdmmc->regs->int_status = sdmmc->regs->int_status;
}
static void sdmmc_intr_disable(sdmmc_t *sdmmc)
{
/* Clear the interrupt bits. */
sdmmc->regs->int_enable &= ~(0x017F0000 | (TEGRA_MMC_NORINTSTSEN_CMD_COMPLETE | TEGRA_MMC_NORINTSTSEN_XFER_COMPLETE | TEGRA_MMC_NORINTSTSEN_DMA_INTERRUPT));
}
static bool sdmmc_intr_check_status(sdmmc_t *sdmmc, u16 status_mask)
{
bool is_masked = (sdmmc->regs->int_status & status_mask);
/* Mask status. */
if (is_masked)
sdmmc->regs->int_status &= status_mask;
return is_masked;
}
static bool sdmmc_intr_check_error(sdmmc_t *sdmmc)
{
bool is_error = (sdmmc->regs->int_status & TEGRA_MMC_NORINTSTS_ERR_INTERRUPT);
/* Refresh status. */
if (is_error)
sdmmc->regs->int_status = sdmmc->regs->int_status;
return is_error;
}
static int sdmmc_dma_init(sdmmc_t *sdmmc, sdmmc_request_t *req)
{
/* Invalid block count or size. */
if (!req->blksz || !req->num_blocks)
return 0;
uint32_t blkcnt = req->num_blocks;
/* Truncate block count. Length can't be over 65536 bytes. */
if (blkcnt >= 0xFFFF)
blkcnt = 0xFFFF;
/* Point to our bounce buffer. */
uint32_t dma_base_addr = (uint32_t)sdmmc->dma_bounce_buf;
/* DMA buffer address must be aligned to 4 bytes. */
if ((4 - (dma_base_addr & 0x03)) & 0x03)
return 0;
/* Write our address to the registers. */
if (sdmmc->use_adma)
{
/* Set ADMA registers. */
sdmmc->regs->adma_address = dma_base_addr;
sdmmc->regs->upper_adma_address = 0;
}
else
{
/* Set SDMA register. */
sdmmc->regs->dma_address = dma_base_addr;
}
/* Store the next DMA block address for updating. */
sdmmc->next_dma_addr = ((dma_base_addr + 0x80000) & 0xFFF80000);
/* Set the block size ORed with the DMA boundary mask. */
sdmmc->regs->block_size = req->blksz | 0x7000;
/* Set the block count. */
sdmmc->regs->block_count = blkcnt;
/* Select basic DMA transfer mode. */
uint32_t transfer_mode = TEGRA_MMC_TRNMOD_DMA_ENABLE;
/* Select multi block. */
if (req->is_multi_block)
transfer_mode |= (TEGRA_MMC_TRNMOD_MULTI_BLOCK_SELECT | TEGRA_MMC_TRNMOD_BLOCK_COUNT_ENABLE);
/* Select read mode. */
if (req->is_read)
transfer_mode |= TEGRA_MMC_TRNMOD_DATA_XFER_DIR_SEL_READ;
/* Select AUTO_CMD12. */
if (req->is_auto_cmd12)
{
transfer_mode &= ~(TEGRA_MMC_TRNMOD_AUTO_CMD12 & TEGRA_MMC_TRNMOD_AUTO_CMD23);
transfer_mode |= TEGRA_MMC_TRNMOD_AUTO_CMD12;
}
/* Set the transfer mode in the register. */
sdmmc->regs->transfer_mode = transfer_mode;
return blkcnt;
}
static int sdmmc_dma_update(sdmmc_t *sdmmc)
{
u16 blkcnt = 0;
/* Loop until all blocks have been consumed. */
do
{
/* Update block count. */
blkcnt = sdmmc->regs->block_count;
/* Program a large timeout. */
uint32_t timebase = get_time();
bool is_timeout = false;
/* Watch over the DMA transfer. */
while (!is_timeout)
{
/* An error has been raised. Reset. */
if (sdmmc_intr_check_error(sdmmc))
{
sdmmc_do_sw_reset(sdmmc);
return 0;
}
/* We have a DMA interrupt. Restart the transfer where it was interrupted. */
if (sdmmc_intr_check_status(sdmmc, TEGRA_MMC_NORINTSTS_DMA_INTERRUPT))
{
if (sdmmc->use_adma)
{
/* Update ADMA registers. */
sdmmc->regs->adma_address = sdmmc->next_dma_addr;
sdmmc->regs->upper_adma_address = 0;
}
else
{
/* Update SDMA register. */
sdmmc->regs->dma_address = sdmmc->next_dma_addr;
}
sdmmc->next_dma_addr += 0x80000;
}
/* Transfer is over. */
if (sdmmc_intr_check_status(sdmmc, TEGRA_MMC_NORINTSTS_XFER_COMPLETE))
return 1;
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 2000000);
}
} while (sdmmc->regs->block_count < blkcnt);
/* Should never get here. Reset. */
sdmmc_do_sw_reset(sdmmc);
return 0;
}
static void sdmmc_set_cmd_flags(sdmmc_t *sdmmc, sdmmc_command_t *cmd, bool is_dma)
{
u16 cmd_reg_flags = 0;
/* Select length flags based on response type. */
if (!(cmd->flags & SDMMC_RSP_PRESENT))
cmd_reg_flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_NO_RESPONSE;
else if (cmd->flags & SDMMC_RSP_136)
cmd_reg_flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_136;
else if (cmd->flags & SDMMC_RSP_BUSY)
cmd_reg_flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_48_BUSY;
else
cmd_reg_flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_48;
/* Select CRC flag based on response type. */
if (cmd->flags & SDMMC_RSP_CRC)
cmd_reg_flags |= TEGRA_MMC_TRNMOD_CMD_CRC_CHECK;
/* Select opcode flag based on response type. */
if (cmd->flags & SDMMC_RSP_OPCODE)
cmd_reg_flags |= TEGRA_MMC_TRNMOD_CMD_INDEX_CHECK;
/* Select data present flag. */
if (is_dma)
cmd_reg_flags |= TEGRA_MMC_TRNMOD_DATA_PRESENT_SELECT_DATA_TRANSFER;
/* Set the CMD's argument, opcode and flags. */
sdmmc->regs->argument = cmd->arg;
sdmmc->regs->command = ((cmd->opcode << 8) | cmd_reg_flags);
}
static int sdmmc_wait_for_cmd(sdmmc_t *sdmmc)
{
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Program a large timeout. */
uint32_t timebase = get_time();
bool is_timeout = false;
/* Set this for error checking. */
bool is_err = false;
/* Wait for CMD to finish. */
while (!is_err && !is_timeout) {
/* Command is done. */
if (sdmmc_intr_check_status(sdmmc, TEGRA_MMC_NORINTSTS_CMD_COMPLETE))
return 1;
/* Check for any raised errors. */
is_err = sdmmc_intr_check_error(sdmmc);
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 2000000);
}
/* Should never get here. Reset. */
sdmmc_do_sw_reset(sdmmc);
return 0;
}
static int sdmmc_save_response(sdmmc_t *sdmmc, uint32_t flags)
{
/* We have a valid response. */
if (flags & SDMMC_RSP_PRESENT)
{
if (flags & SDMMC_RSP_136)
{
/* CRC is stripped so we need to do some shifting. */
for (int i = 0; i < 4; i++) {
sdmmc->resp[i] = (sdmmc->regs->response[3 - i] << 0x08);
if (i != 0)
sdmmc->resp[i - 1] |= ((sdmmc->regs->response[3 - i] >> 24) & 0xFF);
}
}
else
{
/* Card is still busy. */
if (flags & SDMMC_RSP_BUSY)
{
/* Wait for DAT0 level mask. */
if (!sdmmc_wait_busy(sdmmc))
return 0;
}
/* Save our response. */
sdmmc->resp[0] = sdmmc->regs->response[0];
}
return 1;
}
/* Invalid response. */
return 0;
}
int sdmmc_load_response(sdmmc_t *sdmmc, uint32_t flags, uint32_t *resp)
{
/* Make sure our output buffer is valid. */
if (!resp)
return 0;
/* We have a valid response. */
if (flags & SDMMC_RSP_PRESENT)
{
if (flags & SDMMC_RSP_136)
{
resp[0] = sdmmc->resp[0];
resp[1] = sdmmc->resp[1];
resp[2] = sdmmc->resp[2];
resp[3] = sdmmc->resp[3];
}
else
resp[0] = sdmmc->resp[0];
return 1;
}
/* Invalid response. */
return 0;
}
int sdmmc_send_cmd(sdmmc_t *sdmmc, sdmmc_command_t *cmd, sdmmc_request_t *req, uint32_t *num_blocks_out)
{
uint32_t cmd_result = 0;
bool shutdown_sd_clock = false;
/* Run automatic calibration on each command submission for SDMMC1. */
if ((sdmmc->controller == SDMMC_1) && !(sdmmc->has_sd))
sdmmc_autocal_run(sdmmc, sdmmc->bus_voltage);
/* SD clock is disabled. Enable it. */
if (!sdmmc->is_sd_clk_enabled)
{
shutdown_sd_clock = true;
sdmmc_enable_sd_clock(sdmmc);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Provide 8 clock cycles after enabling the clock. */
udelay((8000 + sdmmc->internal_divider - 1) / sdmmc->internal_divider);
}
/* Determine if we should wait for data inhibit. */
bool wait_for_dat = (req || (cmd->flags & SDMMC_RSP_BUSY));
/* Wait for CMD and DAT inhibit. */
if (!sdmmc_wait_for_inhibit(sdmmc, wait_for_dat))
return 0;
uint32_t dma_blkcnt = 0;
bool is_dma = false;
/* This is a data transfer. */
if (req)
{
is_dma = true;
dma_blkcnt = sdmmc_dma_init(sdmmc, req);
if (!dma_blkcnt)
{
sdmmc_error(sdmmc, "Failed to initialize the DMA transfer!");
return 0;
}
/* If this is a write operation, copy the data into our bounce buffer. */
if (!req->is_read)
memcpy((void *)sdmmc->dma_bounce_buf, (void *)req->data, req->blksz * req->num_blocks);
}
/* Enable interrupts. */
sdmmc_intr_enable(sdmmc);
/* Parse and set the CMD's flags. */
sdmmc_set_cmd_flags(sdmmc, cmd, is_dma);
/* Wait for the CMD to finish. */
cmd_result = sdmmc_wait_for_cmd(sdmmc);
sdmmc_debug(sdmmc, "CMD(%d): %08X, %08X, %08X, %08X", cmd_result, sdmmc->regs->response[0], sdmmc->regs->response[1], sdmmc->regs->response[2], sdmmc->regs->response[3]);
if (cmd_result)
{
/* Save response, if necessary. */
sdmmc_save_response(sdmmc, cmd->flags);
/* Process the DMA request. */
if (req)
{
if (!sdmmc_dma_update(sdmmc))
{
sdmmc_error(sdmmc, "Failed to process the DMA transfer!");
return 0;
}
/* If this is a read operation, copy the data from our bounce buffer. */
if (req->is_read)
{
uint32_t dma_data_size = (sdmmc->regs->dma_address - (uint32_t)sdmmc->dma_bounce_buf);
memcpy((void *)req->data, (void *)sdmmc->dma_bounce_buf, dma_data_size);
}
}
}
/* Disable interrupts. */
sdmmc_intr_disable(sdmmc);
if (cmd_result)
{
if (req)
{
/* Save back the number of DMA blocks. */
if (num_blocks_out)
*num_blocks_out = dma_blkcnt;
/* Save the response for AUTO_CMD12. */
if (req->is_auto_cmd12)
sdmmc->resp_auto_cmd12 = sdmmc->regs->response[3];
}
/* Wait for DAT0 to be 0. */
if (req || (cmd->flags & SDMMC_RSP_BUSY))
cmd_result = sdmmc_wait_busy(sdmmc);
}
/* Provide 8 clock cycles before disabling the clock. */
udelay((8000 + sdmmc->internal_divider - 1) / sdmmc->internal_divider);
if (shutdown_sd_clock)
sdmmc_disable_sd_clock(sdmmc);
return cmd_result;
}
int sdmmc_switch_voltage(sdmmc_t *sdmmc)
{
volatile tegra_pmc_t *pmc = pmc_get_regs();
/* Reconfigure the internal clock. */
if (!sdmmc_select_speed(sdmmc, SDMMC_SPEED_SDR12))
{
sdmmc_error(sdmmc, "Failed to apply the correct bus speed for low voltage support!");
return 0;
}
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Reconfigure the regulator. */
max77620_regulator_set_voltage(REGULATOR_LDO2, 1800000);
pmc->pwr_det_val &= ~(PMC_CONTROL_SDMMC1);
/* Reconfigure autocal offsets. */
if (!sdmmc_autocal_config(sdmmc, SDMMC_VOLTAGE_1V8))
{
sdmmc_error(sdmmc, "Failed to configure automatic calibration for low voltage support!");
return 0;
}
/* Do autocal again. */
sdmmc_autocal_run(sdmmc, SDMMC_VOLTAGE_1V8);
/* Change the desired voltage. */
sdmmc_select_voltage(sdmmc, SDMMC_VOLTAGE_1V8);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Wait a while. */
udelay(5000);
/* Host control 2 flag should be set by now. */
if (sdmmc->regs->host_control2 & SDHCI_CTRL_VDD_180)
{
/* Enable the SD clock. */
sdmmc_enable_sd_clock(sdmmc);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Wait a while. */
udelay(1000);
/* Data level is up. Voltage switching is done.*/
if (sdmmc->regs->present_state & SDHCI_DATA_LVL_MASK)
return 1;
}
return 0;
}
static int sdmmc_send_tuning(sdmmc_t *sdmmc, uint32_t opcode)
{
/* Nothing to do. */
if (!sdmmc->has_sd)
return 0;
/* Wait for CMD and DAT inhibit. */
if (!sdmmc_wait_for_inhibit(sdmmc, true))
return 0;
/* Select the right size for sending the tuning block. */
if (sdmmc->bus_width == SDMMC_BUS_WIDTH_4BIT)
sdmmc->regs->block_size = 0x40;
else if (sdmmc->bus_width == SDMMC_BUS_WIDTH_8BIT)
sdmmc->regs->block_size = 0x80;
else
return 0;
/* Select the block count and transfer mode. */
sdmmc->regs->block_count = 1;
sdmmc->regs->transfer_mode = TEGRA_MMC_TRNMOD_DATA_XFER_DIR_SEL_READ;
/* Manually enable the Buffer Read Ready interrupt. */
sdmmc->regs->int_enable |= TEGRA_MMC_NORINTSTSEN_BUFFER_READ_READY;
/* Refresh status. */
sdmmc->regs->int_status = sdmmc->regs->int_status;
/* Disable the SD clock. */
sdmmc_disable_sd_clock(sdmmc);
/* Prepare the tuning command. */
sdmmc_command_t cmd = {};
cmd.opcode = opcode;
cmd.arg = 0;
cmd.flags = SDMMC_RSP_R1;
/* Parse and set the CMD's flags. */
sdmmc_set_cmd_flags(sdmmc, &cmd, true);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Wait a while. */
udelay(1);
/* Reset. */
sdmmc_do_sw_reset(sdmmc);
/* Enable back the SD clock. */
sdmmc_enable_sd_clock(sdmmc);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Program a 50ms timeout. */
uint32_t timebase = get_time();
bool is_timeout = false;
/* Wait for Buffer Read Ready interrupt. */
while (!is_timeout)
{
/* Buffer Read Ready was asserted. */
if (sdmmc_intr_check_status(sdmmc, TEGRA_MMC_NORINTSTSEN_BUFFER_READ_READY))
{
/* Manually disable the Buffer Read Ready interrupt. */
sdmmc->regs->int_enable &= ~(TEGRA_MMC_NORINTSTSEN_BUFFER_READ_READY);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Provide 8 clock cycles. */
udelay((8000 + sdmmc->internal_divider - 1) / sdmmc->internal_divider);
return 1;
}
/* Keep checking if timeout expired. */
is_timeout = (get_time_since(timebase) > 5000);
}
/* Reset. */
sdmmc_do_sw_reset(sdmmc);
/* Manually disable the Buffer Read Ready interrupt. */
sdmmc->regs->int_enable &= ~(TEGRA_MMC_NORINTSTSEN_BUFFER_READ_READY);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Provide 8 clock cycles. */
udelay((8000 + sdmmc->internal_divider - 1) / sdmmc->internal_divider);
return 0;
}
void sdmmc_set_tuning_tap_val(sdmmc_t *sdmmc)
{
sdmmc->tap_val = ((sdmmc->regs->vendor_clock_cntrl & 0xFF0000) >> 16);
sdmmc->is_tuning_tap_val_set = true;
}
int sdmmc_execute_tuning(sdmmc_t *sdmmc, SdmmcBusSpeed bus_speed, uint32_t opcode)
{
uint32_t max_tuning_loop = 0;
uint32_t tuning_cntrl_flag = 0;
sdmmc->regs->vendor_tuning_cntrl1 = 0;
switch (bus_speed)
{
case SDMMC_SPEED_HS200:
case SDMMC_SPEED_HS400:
case SDMMC_SPEED_SDR104:
max_tuning_loop = 0x80;
tuning_cntrl_flag = 0x4000;
break;
case SDMMC_SPEED_SDR50:
case SDMMC_SPEED_DDR50:
case SDMMC_SPEED_UNK14:
max_tuning_loop = 0x100;
tuning_cntrl_flag = 0x8000;
break;
default:
return 0;
}
sdmmc->regs->vendor_tuning_cntrl0 &= ~(0xE000);
sdmmc->regs->vendor_tuning_cntrl0 |= tuning_cntrl_flag;
sdmmc->regs->vendor_tuning_cntrl0 &= ~(0x1FC0);
sdmmc->regs->vendor_tuning_cntrl0 |= 0x40;
sdmmc->regs->vendor_tuning_cntrl0 |= 0x20000;
/* Start tuning. */
sdmmc->regs->host_control2 |= SDHCI_CTRL_EXEC_TUNING;
/* Repeat until Execute Tuning is set to 0 or the number of loops reaches the maximum value. */
for (uint32_t i = 0; i < max_tuning_loop; i++)
{
sdmmc_send_tuning(sdmmc, opcode);
/* Tuning is done. */
if (!(sdmmc->regs->host_control2 & SDHCI_CTRL_EXEC_TUNING))
break;
}
/* Success! */
if (sdmmc->regs->host_control2 & SDHCI_CTRL_TUNED_CLK)
return 1;
return 0;
}
int sdmmc_abort(sdmmc_t *sdmmc, uint32_t opcode)
{
uint32_t result = 0;
uint32_t cmd_result = 0;
bool shutdown_sd_clock = false;
/* SD clock is disabled. Enable it. */
if (!sdmmc->is_sd_clk_enabled)
{
shutdown_sd_clock = true;
sdmmc_enable_sd_clock(sdmmc);
/* Force a register read to refresh the clock control value. */
sdmmc_get_sd_clock_control(sdmmc);
/* Provide 8 clock cycles after enabling the clock. */
udelay((8000 + sdmmc->internal_divider - 1) / sdmmc->internal_divider);
}
/* Wait for CMD and DAT inhibit. */
if (sdmmc_wait_for_inhibit(sdmmc, false))
{
/* Enable interrupts. */
sdmmc_intr_enable(sdmmc);
/* Prepare the command. */
sdmmc_command_t cmd = {};
cmd.opcode = opcode;
cmd.arg = 0;
cmd.flags = SDMMC_RSP_R1B;
/* Parse and set the CMD's flags. */
sdmmc_set_cmd_flags(sdmmc, &cmd, false);
/* Wait for the CMD to finish. */
cmd_result = sdmmc_wait_for_cmd(sdmmc);
/* Disable interrupts. */
sdmmc_intr_disable(sdmmc);
if (cmd_result)
{
/* Save response, if necessary. */
sdmmc_save_response(sdmmc, cmd.flags);
/* Wait for DAT0 to be 0. */
result = sdmmc_wait_busy(sdmmc);
}
}
/* Provide 8 clock cycles before disabling the clock. */
udelay((8000 + sdmmc->internal_divider - 1) / sdmmc->internal_divider);
/* Disable the SD clock if requested. */
if (shutdown_sd_clock)
sdmmc_disable_sd_clock(sdmmc);
return result;
}