bdk: sdmmc: add UHS DDR200 support

The bdk flag BDK_SDMMC_UHS_DDR200_SUPPORT can be used to enable it.

SD Card DDR200 (DDR208) support

Proper procedure:
1. Check that Vendor Specific Command System is supported.
   Used as Enable DDR200 Bus.
2. Enable DDR200 bus mode via setting 14 to Group 2 via CMD6.
   Access Mode group is left to default 0 (SDR12).
3. Setup clock to 200 or 208 MHz.
4. Set host to DDR bus mode that supports such high clocks.
   Some hosts have special mode, others use DDR50 and others HS400.
5. Execute Tuning.

The true validation that this value in Group 2 activates it, is that DDR50 bus
and clocks/timings work fully after that point.

On Tegra X1, that can be done with DDR50 host mode.
Tuning though can't be done automatically on any DDR mode.
So it needs to be done manually and selected tap will be applied from the
biggest sampling window.

Finally, all that simply works, because the marketing materials for DDR200 are
basically overstatements to sell the feature. DDR200 is simply SDR104 in DDR mode,
so sampling on rising and falling edge and with variable output data window.
It can be supported by any host that is fast enough to support DDR at 200/208MHz
and can do hw/sw tuning for finding the proper sampling window in that mode.

Using a SDMMC controller on DDR200 mode at 400MHz, has latency allowance implications. The MC/EMC must be clocked enough to be able to serve the requests in time (512B in 1.28 ns).
This commit is contained in:
CTCaer 2023-03-31 08:54:13 +03:00
parent 7f32c6d211
commit d258c82d52
7 changed files with 302 additions and 41 deletions

View File

@ -764,6 +764,14 @@ static int _clock_sdmmc_config_clock_host(u32 *pclock, u32 id, u32 val)
*pclock = 199680;
divisor = 0; // 1 div.
break;
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
case 400000:
source = SDMMC_CLOCK_SRC_PLLC4_OUT0;
*pclock = 399360;
divisor = 3; // 2.5 div
break;
#endif
}
_clock_sdmmc_table[id].clock = val;
@ -878,6 +886,13 @@ void clock_sdmmc_get_card_clock_div(u32 *pclock, u16 *pdivisor, u32 type)
*pclock = 200000;
*pdivisor = 2;
break;
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
case SDHCI_TIMING_UHS_DDR200: // Actual card clock: 199.68 KHz.
*pclock = 400000;
*pdivisor = 2;
break;
#endif
}
}

View File

@ -22,6 +22,12 @@
#include <libs/fatfs/ff.h>
#include <mem/heap.h>
#ifndef BDK_SDMMC_UHS_DDR200_SUPPORT
#define SD_DEFAULT_SPEED SD_UHS_SDR104
#else
#define SD_DEFAULT_SPEED SD_UHS_DDR208
#endif
static bool sd_mounted = false;
static bool sd_init_done = false;
static bool insertion_event = false;
@ -80,7 +86,11 @@ u32 sd_get_mode()
int sd_init_retry(bool power_cycle)
{
u32 bus_width = SDMMC_BUS_WIDTH_4;
#ifndef BDK_SDMMC_UHS_DDR200_SUPPORT
u32 type = SDHCI_TIMING_UHS_SDR104;
#else
u32 type = SDHCI_TIMING_UHS_DDR200;
#endif
// Power cycle SD card.
if (power_cycle)
@ -111,6 +121,13 @@ int sd_init_retry(bool power_cycle)
case SD_UHS_SDR104:
type = SDHCI_TIMING_UHS_SDR104;
break;
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
case SD_UHS_DDR208:
type = SDHCI_TIMING_UHS_DDR200;
break;
#endif
default:
sd_mode = SD_DEFAULT_SPEED;
break;

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2018 naehrwert
* Copyright (c) 2018-2021 CTCaer
* Copyright (c) 2018-2023 CTCaer
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@ -30,7 +30,11 @@ enum
SD_1BIT_HS25 = 1,
SD_4BIT_HS25 = 2,
SD_UHS_SDR82 = 3,
SD_UHS_SDR104 = 4
SD_UHS_SDR104 = 4,
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
SD_UHS_DDR208 = 5
#endif
};
enum

View File

@ -45,15 +45,14 @@
#define SD_APP_CHANGE_SECURE_AREA 49 /* adtc R1b */
/* OCR bit definitions */
#define SD_OCR_VDD_18 (1 << 7) /* VDD voltage 1.8 */
#define SD_VHD_27_36 (1 << 8) /* VDD voltage 2.7 ~ 3.6 */
#define SD_OCR_VDD_27_34 (0x7F << 15) /* VDD voltage 2.7 ~ 3.4 */
#define SD_OCR_VDD_32_33 (1 << 20) /* VDD voltage 3.2 ~ 3.3 */
#define SD_OCR_S18R (1 << 24) /* 1.8V switching request */
#define SD_ROCR_S18A SD_OCR_S18R /* 1.8V switching accepted by card */
#define SD_OCR_XPC (1 << 28) /* SDXC power control */
#define SD_OCR_CCS (1 << 30) /* Card Capacity Status */
#define SD_OCR_BUSY (1 << 31) /* Card Power up Status */
#define SD_OCR_VDD_18 (1U << 7) /* VDD voltage 1.8 */
#define SD_VHD_27_36 (1U << 8) /* VDD voltage 2.7 ~ 3.6 */
#define SD_OCR_VDD_32_33 (1U << 20) /* VDD voltage 3.2 ~ 3.3 */
#define SD_OCR_S18R (1U << 24) /* 1.8V switching request */
#define SD_ROCR_S18A SD_OCR_S18R /* 1.8V switching accepted by card */
#define SD_OCR_XPC (1U << 28) /* SDXC power control */
#define SD_OCR_CCS (1U << 30) /* Card Capacity Status */
#define SD_OCR_BUSY (1U << 31) /* Card Power up Status */
/*
* SD_SWITCH argument format:
@ -90,8 +89,8 @@
#define SCR_SPEC_VER_0 0 /* Implements system specification 1.0 - 1.01 */
#define SCR_SPEC_VER_1 1 /* Implements system specification 1.10 */
#define SCR_SPEC_VER_2 2 /* Implements system specification 2.00-3.0X */
#define SD_SCR_BUS_WIDTH_1 (1<<0)
#define SD_SCR_BUS_WIDTH_4 (1<<2)
#define SD_SCR_BUS_WIDTH_1 (1U << 0)
#define SD_SCR_BUS_WIDTH_4 (1U << 2)
/*
* SD bus widths
@ -102,33 +101,55 @@
/*
* SD bus speeds
*/
#define UHS_SDR12_BUS_SPEED 0
#define UHS_SDR12_BUS_SPEED 0
#define HIGH_SPEED_BUS_SPEED 1
#define UHS_SDR25_BUS_SPEED 1
#define UHS_SDR50_BUS_SPEED 2
#define UHS_SDR25_BUS_SPEED 1
#define UHS_SDR50_BUS_SPEED 2
#define UHS_SDR104_BUS_SPEED 3
#define UHS_DDR50_BUS_SPEED 4
#define HS400_BUS_SPEED 5
#define UHS_DDR50_BUS_SPEED 4
#define HS400_BUS_SPEED 5
#define SD_MODE_HIGH_SPEED (1 << HIGH_SPEED_BUS_SPEED)
#define SD_MODE_UHS_SDR12 (1 << UHS_SDR12_BUS_SPEED)
#define SD_MODE_UHS_SDR25 (1 << UHS_SDR25_BUS_SPEED)
#define SD_MODE_UHS_SDR50 (1 << UHS_SDR50_BUS_SPEED)
#define SD_MODE_UHS_SDR104 (1 << UHS_SDR104_BUS_SPEED)
#define SD_MODE_UHS_DDR50 (1 << UHS_DDR50_BUS_SPEED)
#define SD_MODE_HIGH_SPEED (1U << HIGH_SPEED_BUS_SPEED)
#define SD_MODE_UHS_SDR12 (1U << UHS_SDR12_BUS_SPEED)
#define SD_MODE_UHS_SDR25 (1U << UHS_SDR25_BUS_SPEED)
#define SD_MODE_UHS_SDR50 (1U << UHS_SDR50_BUS_SPEED)
#define SD_MODE_UHS_SDR104 (1U << UHS_SDR104_BUS_SPEED)
#define SD_MODE_UHS_DDR50 (1U << UHS_DDR50_BUS_SPEED)
#define SD_DRIVER_TYPE_B 0x01
#define SD_DRIVER_TYPE_A 0x02
#define SD_SET_DRIVER_TYPE_B 0
#define SD_SET_DRIVER_TYPE_A 1
#define SD_SET_DRIVER_TYPE_C 2
#define SD_SET_DRIVER_TYPE_D 3
#define SD_DRIVER_TYPE_B (1U << SD_SET_DRIVER_TYPE_B)
#define SD_DRIVER_TYPE_A (1U << SD_SET_DRIVER_TYPE_A)
#define SD_DRIVER_TYPE_C (1U << SD_SET_DRIVER_TYPE_C)
#define SD_DRIVER_TYPE_D (1U << SD_SET_DRIVER_TYPE_D)
#define SD_SET_POWER_LIMIT_0_72 0
#define SD_SET_POWER_LIMIT_1_44 1
#define SD_SET_POWER_LIMIT_2_16 2
#define SD_SET_POWER_LIMIT_2_88 3
#define SD_MAX_POWER_0_72 (1 << SD_SET_POWER_LIMIT_0_72)
#define SD_MAX_POWER_1_44 (1 << SD_SET_POWER_LIMIT_1_44)
#define SD_MAX_POWER_2_16 (1 << SD_SET_POWER_LIMIT_2_16)
#define SD_MAX_POWER_2_88 (1 << SD_SET_POWER_LIMIT_2_88)
#define SD_MAX_POWER_0_72 (1U << SD_SET_POWER_LIMIT_0_72)
#define SD_MAX_POWER_1_44 (1U << SD_SET_POWER_LIMIT_1_44)
#define SD_MAX_POWER_2_16 (1U << SD_SET_POWER_LIMIT_2_16)
#define SD_MAX_POWER_2_88 (1U << SD_SET_POWER_LIMIT_2_88)
#define SD_SET_CMD_SYSTEM_DEF 0
#define SD_SET_CMD_SYSTEM_MEC 1
#define SD_SET_CMD_SYSTEM_OTP 3
#define SD_SET_CMD_SYSTEM_OSD 3
#define SD_SET_CMD_SYSTEM_VND 14
#define UHS_DDR200_BUS_SPEED SD_SET_CMD_SYSTEM_VND
#define SD_CMD_SYSTEM_DEF (1U << SD_SET_CMD_SYSTEM_DEF)
#define SD_CMD_SYSTEM_MEC (1U << SD_SET_CMD_SYSTEM_MEC)
#define SD_CMD_SYSTEM_OTP (1U << SD_SET_CMD_SYSTEM_OTP)
#define SD_CMD_SYSTEM_OSD (1U << SD_SET_CMD_SYSTEM_OSD)
#define SD_CMD_SYSTEM_VND (1U << SD_SET_CMD_SYSTEM_VND)
#define SD_MODE_UHS_DDR200 SD_CMD_SYSTEM_VND
/*
* SD_SWITCH mode
@ -140,14 +161,14 @@
* SD_SWITCH function groups
*/
#define SD_SWITCH_GRP_ACCESS 0
#define SD_SWITCH_GRP_CMDSYS 1
#define SD_SWITCH_GRP_DRVSTR 2
#define SD_SWITCH_GRP_PWRLIM 3
#define SD_SWITCH_GRP_CMDSYS 1
#define SD_SWITCH_GRP_DRVSTR 2
#define SD_SWITCH_GRP_PWRLIM 3
/*
* SD_SWITCH access modes
*/
#define SD_SWITCH_ACCESS_DEF 0
#define SD_SWITCH_ACCESS_HS 1
#define SD_SWITCH_ACCESS_HS 1
#endif /* SD_DEF_H */

View File

@ -909,8 +909,6 @@ static void _sd_storage_parse_scr(sdmmc_storage_t *storage)
memcpy(&resp[2], storage->raw_scr, 8);
_debug_scr(storage->raw_scr);
storage->scr.sda_vsn = unstuff_bits(resp, 56, 4);
storage->scr.bus_widths = unstuff_bits(resp, 48, 4);
@ -1032,6 +1030,74 @@ static void _sd_storage_set_power_limit(sdmmc_storage_t *storage, u16 power_limi
}
}
/*
* SD Card DDR200 (DDR208) support
*
* Proper procedure:
* 1. Check that Vendor Specific Command System is supported.
* Used as Enable DDR200 Bus.
* 2. Enable DDR200 bus mode via setting 14 to Group 2 via CMD6.
* Access Mode group is left to default 0 (SDR12).
* 3. Setup clock to 200 or 208 MHz.
* 4. Set host to DDR bus mode that supports such high clocks.
* Some hosts have special mode, others use DDR50 and others HS400.
* 5. Execute Tuning.
*
* The true validation that this value in Group 2 activates it, is that DDR50 bus
* and clocks/timings work fully after that point.
*
* On Tegra X1, that can be done with DDR50 host mode.
* Tuning though can't be done automatically on any DDR mode.
* So it needs to be done manually and selected tap will be applied from the biggest
* sampling window.
*
* Finally, all that simply works, because the marketing materials for DDR200 are
* basically overstatements to sell the feature. DDR200 is simply SDR104 in DDR mode,
* so sampling on rising and falling edge and with variable output data window.
* It can be supported by any host that is fast enough to support DDR at 200/208MHz
* and can do hw/sw tuning for finding the proper sampling window in that mode.
*/
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
static int _sd_storage_enable_DDR200(sdmmc_storage_t *storage, u8 *buf)
{
u32 cmd_system = UHS_DDR200_BUS_SPEED;
if (!_sd_storage_switch(storage, buf, SD_SWITCH_CHECK, SD_SWITCH_GRP_CMDSYS, cmd_system))
return 0;
u32 system_out = (buf[16] >> 4) & 0xF;
if (system_out != cmd_system)
return 0;
DPRINTF("[SD] supports DDR200 mode\n");
u16 total_pwr_consumption = ((u16)buf[0] << 8) | buf[1];
DPRINTF("[SD] max power: %d mW\n", total_pwr_consumption * 3600 / 1000);
storage->card_power_limit = total_pwr_consumption;
if (total_pwr_consumption <= 800)
{
if (!_sd_storage_switch(storage, buf, SD_SWITCH_SET, SD_SWITCH_GRP_CMDSYS, cmd_system))
return 0;
if (system_out != ((buf[16] >> 4) & 0xF))
return 0;
DPRINTF("[SD] card accepted DDR200\n");
if (!sdmmc_setup_clock(storage->sdmmc, SDHCI_TIMING_UHS_DDR200))
return 0;
DPRINTF("[SD] after setup clock DDR200\n");
if (!sdmmc_tuning_execute(storage->sdmmc, SDHCI_TIMING_UHS_DDR200, MMC_SEND_TUNING_BLOCK))
return 0;
DPRINTF("[SD] after tuning DDR200\n");
return _sdmmc_storage_check_status(storage);
}
DPRINTF("[SD] card max power over limit\n");
return 0;
}
#endif
static int _sd_storage_set_card_bus_speed(sdmmc_storage_t *storage, u32 hs_type, u8 *buf)
{
if (!_sd_storage_switch(storage, buf, SD_SWITCH_CHECK, SD_SWITCH_GRP_ACCESS, hs_type))
@ -1070,12 +1136,27 @@ static int _sd_storage_enable_uhs_low_volt(sdmmc_storage_t *storage, u32 type, u
return 0;
u8 access_mode = buf[13];
u16 power_limit = buf[7] | buf[6] << 8;
u16 power_limit = buf[7] | buf[6] << 8;
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
u16 cmd_system = buf[11] | buf[10] << 8;
#endif
DPRINTF("[SD] access: %02X, power: %02X\n", access_mode, power_limit);
u32 hs_type = 0;
switch (type)
{
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
case SDHCI_TIMING_UHS_DDR200:
// Fall through if DDR200 is not supported.
if (cmd_system & SD_MODE_UHS_DDR200)
{
DPRINTF("[SD] setting bus speed to DDR200\n");
storage->csd.busspeed = 200;
_sd_storage_set_power_limit(storage, power_limit, buf);
return _sd_storage_enable_DDR200(storage, buf);
}
#endif
case SDHCI_TIMING_UHS_SDR104:
case SDHCI_TIMING_UHS_SDR82:
// Fall through if not supported.
@ -1348,6 +1429,7 @@ static bool _sdmmc_storage_get_bus_uhs_support(u32 bus_width, u32 type)
case SDHCI_TIMING_UHS_SDR104:
case SDHCI_TIMING_UHS_SDR82:
case SDHCI_TIMING_UHS_DDR50:
case SDHCI_TIMING_UHS_DDR200:
if (bus_width == SDMMC_BUS_WIDTH_4)
return true;
default:

View File

@ -352,6 +352,9 @@ int sdmmc_setup_clock(sdmmc_t *sdmmc, u32 type)
break;
case SDHCI_TIMING_UHS_DDR50:
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
case SDHCI_TIMING_UHS_DDR200:
#endif
sdmmc->regs->hostctl2 = (sdmmc->regs->hostctl2 & (~SDHCI_CTRL_UHS_MASK)) | UHS_DDR50_BUS_SPEED;
sdmmc->regs->hostctl2 |= SDHCI_CTRL_VDD_180;
break;
@ -617,10 +620,8 @@ static void _sdmmc_send_tuning_cmd(sdmmc_t *sdmmc, u32 cmd)
_sdmmc_send_cmd(sdmmc, &cmdbuf, true);
}
static int _sdmmc_tuning_execute_once(sdmmc_t *sdmmc, u32 cmd)
static int _sdmmc_tuning_execute_once(sdmmc_t *sdmmc, u32 cmd, u32 tap)
{
if (sdmmc->powersave_enabled)
return 0;
if (!_sdmmc_wait_cmd_data_inhibit(sdmmc, true))
return 0;
@ -630,6 +631,16 @@ static int _sdmmc_tuning_execute_once(sdmmc_t *sdmmc, u32 cmd)
sdmmc->regs->norintsts = sdmmc->regs->norintsts;
sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
// Set tap if manual tuning.
if (tap != HW_TAP_TUNING)
{
sdmmc->regs->ventunctl0 &= ~SDHCI_TEGRA_TUNING_TAP_HW_UPDATED;
sdmmc->regs->venclkctl = (sdmmc->regs->venclkctl & 0xFF00FFFF) | (tap << 16);
sdmmc->regs->ventunctl0 |= SDHCI_TEGRA_TUNING_TAP_HW_UPDATED;
}
#endif
_sdmmc_send_tuning_cmd(sdmmc, cmd);
_sdmmc_commit_changes(sdmmc);
usleep(1);
@ -661,10 +672,112 @@ static int _sdmmc_tuning_execute_once(sdmmc_t *sdmmc, u32 cmd)
return 0;
}
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
typedef struct _sdmmc_manual_tuning_t
{
u32 result[8];
u32 num_iter;
u32 tap_start;
u32 tap_end;
} sdmmc_manual_tuning_t;
static int _sdmmc_manual_tuning_set_tap(sdmmc_t *sdmmc, sdmmc_manual_tuning_t *tuning)
{
u32 tap_start = INVALID_TAP;
u32 win_size = 0;
u32 best_tap = 0;
u32 best_size = 0;
for (u32 i = 0; i < tuning->num_iter; i++)
{
u32 iter_end = i == (tuning->num_iter - 1) ? 1 : 0;
u32 stable = tuning->result[i / 32] & BIT(i % 32);
if (stable && !iter_end)
{
if (tap_start == INVALID_TAP)
tap_start = i;
win_size++;
}
else
{
if (tap_start != INVALID_TAP)
{
u32 tap_end = !iter_end ? (i - 1) : i;
// Check if window is wider.
if (win_size > best_size)
{
best_tap = (tap_start + tap_end) / 2;
best_size = win_size + iter_end;
}
tap_start = INVALID_TAP;
win_size = 0;
}
}
}
// Check if failed.
if (!best_tap)
return 0;
sdmmc->regs->clkcon &= ~SDHCI_CLOCK_CARD_EN;
sdmmc->regs->ventunctl0 &= ~SDHCI_TEGRA_TUNING_TAP_HW_UPDATED;
// Set tap.
sdmmc->regs->venclkctl = (sdmmc->regs->venclkctl & 0xFF00FFFF) | (best_tap << 16);
sdmmc->regs->ventunctl0 |= SDHCI_TEGRA_TUNING_TAP_HW_UPDATED;
sdmmc->regs->clkcon |= SDHCI_CLOCK_CARD_EN;
return 1;
}
/*
* SD Card DDR200 (DDR208) support
*
* On Tegra X1, that can be done with DDR50 host mode.
* Tuning though can't be done automatically on any DDR mode.
* So it needs to be done manually and selected tap will be applied from the biggest
* sampling window.
*/
static int sdmmc_tuning_execute_ddr200(sdmmc_t *sdmmc)
{
sdmmc_manual_tuning_t manual_tuning = { 0 };
manual_tuning.num_iter = 128;
sdmmc->regs->ventunctl1 = 0; // step_size 1.
sdmmc->regs->ventunctl0 = (sdmmc->regs->ventunctl0 & 0xFFFF1FFF) | (2 << 13); // 128 Tries.
sdmmc->regs->ventunctl0 = (sdmmc->regs->ventunctl0 & 0xFFFFE03F) | (1 << 6); // 1x Multiplier.
sdmmc->regs->ventunctl0 |= SDHCI_TEGRA_TUNING_TAP_HW_UPDATED;
sdmmc->regs->hostctl2 |= SDHCI_CTRL_EXEC_TUNING;
for (u32 i = 0; i < manual_tuning.num_iter; i++)
{
_sdmmc_tuning_execute_once(sdmmc, MMC_SEND_TUNING_BLOCK, i);
// Save result for manual tuning.
int sampled = (sdmmc->regs->hostctl2 >> SDHCI_CTRL_TUNED_CLK_SHIFT) & 1;
manual_tuning.result[i / 32] |= sampled << (i % 32);
if (!(sdmmc->regs->hostctl2 & SDHCI_CTRL_EXEC_TUNING))
break;
}
return _sdmmc_manual_tuning_set_tap(sdmmc, &manual_tuning);
}
#endif
int sdmmc_tuning_execute(sdmmc_t *sdmmc, u32 type, u32 cmd)
{
u32 num_iter, flag;
if (sdmmc->powersave_enabled)
return 0;
switch (type)
{
case SDHCI_TIMING_MMC_HS200:
@ -686,6 +799,11 @@ int sdmmc_tuning_execute(sdmmc_t *sdmmc, u32 type, u32 cmd)
case SDHCI_TIMING_UHS_SDR25:
return 1;
#ifdef BDK_SDMMC_UHS_DDR200_SUPPORT
case SDHCI_TIMING_UHS_DDR200:
return sdmmc_tuning_execute_ddr200(sdmmc);
#endif
default:
return 0;
}
@ -699,7 +817,7 @@ int sdmmc_tuning_execute(sdmmc_t *sdmmc, u32 type, u32 cmd)
for (u32 i = 0; i < num_iter; i++)
{
_sdmmc_tuning_execute_once(sdmmc, cmd);
_sdmmc_tuning_execute_once(sdmmc, cmd, HW_TAP_TUNING);
if (!(sdmmc->regs->hostctl2 & SDHCI_CTRL_EXEC_TUNING))
break;

View File

@ -262,6 +262,7 @@
// SDR104 with a 163.2MHz -> 81.6MHz clock.
#define SDHCI_TIMING_UHS_SDR82 13 // GC FPGA. Obsolete and Repurposed. MMC_HS50 -> SDR82.
#define SDHCI_TIMING_MMC_HS100 14 // GC ASIC.
#define SDHCI_TIMING_UHS_DDR200 15
/*! SDMMC Low power features. */
#define SDMMC_POWER_SAVE_DISABLE 0
@ -270,6 +271,9 @@
/*! Helper for SWITCH command argument. */
#define SDMMC_SWITCH(mode, index, value) (((mode) << 24) | ((index) << 16) | ((value) << 8))
#define HW_TAP_TUNING 0x100
#define INVALID_TAP 0x100
/*! SDMMC controller context. */
typedef struct _sdmmc_t
{