fusee: begin adding support for MMC HS modes

This commit is contained in:
Kate J. Temkin 2018-05-20 07:04:18 -06:00
parent 5f3fc8156c
commit d5411b5b1f
2 changed files with 315 additions and 101 deletions

View File

@ -135,10 +135,39 @@ enum sdmmc_constants {
* SDMMC clock divider constants
*/
enum sdmmc_clock_dividers {
/* Clock dividers: SD */
MMC_CLOCK_DIVIDER_SDR12 = 31, // 16.5, from the TRM table
MMC_CLOCK_DIVIDER_SDR25 = 15, // 8.5, from the table
MMC_CLOCK_DIVIDER_SDR50 = 7, // 4.5, from the table
MMC_CLOCK_DIVIDER_SDR104 = 3, // 2, from the datasheet
/* Clock dividers: MMC */
MMC_CLOCK_DIVIDER_HS26 = 30, // 16, from the TRM table
MMC_CLOCK_DIVIDER_HS52 = 14, // 8, from the table
MMC_CLOCK_DIVIDER_HS200 = 2, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
MMC_CLOCK_DIVIDER_HS400 = 2, // 1 -- NOTE THIS IS WITH RESPECT TO PLLC4_OUT2_LJ
};
/**
* SDMMC clock divider constants
*/
enum sdmmc_clock_sources {
/* Clock dividers: SD */
MMC_CLOCK_SOURCE_SDR12 = 0, // PLLP
MMC_CLOCK_SOURCE_SDR25 = 0,
MMC_CLOCK_SOURCE_SDR50 = 0,
MMC_CLOCK_SOURCE_SDR104 = 0,
/* Clock dividers: MMC */
MMC_CLOCK_SOURCE_HS26 = 0, // PLLP
MMC_CLOCK_SOURCE_HS52 = 0,
MMC_CLOCK_SOURCE_HS200 = 1, // PLLC4_OUT2_LJ
MMC_CLOCK_SOURCE_HS400 = 1,
};
/**
@ -244,6 +273,8 @@ enum sdmmc_register_bits {
MMC_CLOCK_TRIM_SDMMC1 = (0x02 << 24),
MMC_CLOCK_TRIM_SDMMC4 = (0x08 << 24),
MMC_CLOCK_PADPIPE_CLKEN_OVERRIDE = (1 << 3),
/* Autocal configuration */
MMC_AUTOCAL_PDPU_CONFIG_MASK = 0x7f7f,
MMC_AUTOCAL_PDPU_SDMMC1_1V8 = 0x7b7b,
@ -274,14 +305,18 @@ enum sdmmc_register_bits {
MMC_VENDOR_TUNING_SET_BY_HW = (1 << 17),
/* Vendor tuning control 0*/
/* Vendor tuning control 1*/
MMC_VENDOR_TUNING_STEP_SIZE_SDR50_DEFAULT = (0 << 0),
MMC_VENDOR_TUNING_STEP_SIZE_SDR104_DEFAULT = (0 << 4),
/* Vendor capability overrides */
MMC_VENDOR_CAPABILITY_DQS_TRIM_MASK = (0x3f << 8),
MMC_VENDOR_CAPABILITY_DQS_TRIM_HS400 = (0x11 << 8),
};
/**
* The number o
* Represents the possible tuning modes for the X1 SDMMC controller.
*/
enum sdmmc_tuning_attempts {
MMC_VENDOR_TUNING_TRIES_40 = 0,
@ -293,6 +328,8 @@ enum sdmmc_tuning_attempts {
/* Helpful aliases; values are from the TRM */
MMC_VENDOR_TUNING_TRIES_SDR50 = 4,
MMC_VENDOR_TUNING_TRIES_SDR104 = 2,
MMC_VENDOR_TUNING_TRIES_HS200 = 2,
MMC_VENDOR_TUNING_TRIES_HS400 = 2,
};
@ -325,7 +362,8 @@ enum sdmmc_command {
CMD_SET_BLKLEN = 16,
CMD_READ_SINGLE_BLOCK = 17,
CMD_READ_MULTIPLE_BLOCK = 18,
CMD_SEND_TUNING_BLOCK = 19,
CMD_SD_SEND_TUNING_BLOCK = 19,
CMD_MMC_SEND_TUNING_BLOCK = 21,
CMD_WRITE_SINGLE_BLOCK = 24,
CMD_WRITE_MULTIPLE_BLOCK = 25,
@ -336,6 +374,18 @@ enum sdmmc_command {
};
/**
* Fields that can be modified by CMD_SWITCH_MODE.
*/
enum sdmmc_switch_field {
/* Fields */
MMC_PARTITION_CONFIG = 179,
MMC_BUS_WIDTH = 183,
MMC_HS_TIMING = 185,
};
/**
* String descriptions of each command.
*/
@ -359,6 +409,13 @@ static const char *sdmmc_command_string[] = {
"CMD_SET_BLKLEN",
"CMD_READ_SINGLE_BLOCK",
"CMD_READ_MULTIPLE_BLOCK",
"CMD_SD_SEND_TUNING_BLOCK",
"<invalid>",
"CMD_MMC_SEND_TUNING_BLOCK",
"<invalid>",
"<invalid>",
"CMD_WRITE_SINGLE_BLOCK",
"CMD_WRITE_WRITE_BLOCK",
};
@ -391,11 +448,6 @@ enum sdmmc_command_magic {
MMC_IF_VOLTAGE_3V3 = (1 << 8),
MMC_IF_CHECK_PATTERN = 0xAA,
/* Misc constants */
MMC_DEFAULT_BLOCK_ORDER = 9,
MMC_VOLTAGE_SWITCH_TIME = 5000, // 5mS
MMC_POST_CLOCK_DELAY = 1000, // 1mS
/* Switch mode constants */
SDMMC_SWITCH_MODE_MODE_SHIFT = 31,
SDMMC_SWITCH_MODE_ALL_FUNCTIONS_UNUSED = 0xFFFFFF,
@ -408,8 +460,11 @@ enum sdmmc_command_magic {
SDMMC_SWITCH_MODE_NO_GROUP = -1,
/* Misc constants */
MMC_DEFAULT_BLOCK_ORDER = 9,
MMC_VOLTAGE_SWITCH_TIME = 5000, // 5mS
MMC_POST_CLOCK_DELAY = 1000, // 1mS
MMC_TUNING_TIMEOUT = 150 * 1000, // 150mS
MMC_SPEED_MMC_OFFSET = 10,
};
@ -463,6 +518,17 @@ enum sdmmc_ext_csd_extents {
MMC_EXT_CSD_PARTITION_SWITCH_TIME = 199,
MMC_EXT_CSD_PARTITION_SWITCH_SCALE_US = 10000,
/* Card type register; we skip entries for
* non-1V8 modes, as we're fixed to 1V8 */
MMC_EXT_CSD_CARD_TYPE = 196,
MMC_EXT_CSD_CARD_TYPE_HS26 = (1 << 0),
MMC_EXT_CSD_CARD_TYPE_HS52 = (1 << 1),
MMC_EXT_CSD_CARD_TYPE_HS200_1V8 = (1 << 4),
MMC_EXT_CSD_CARD_TYPE_HS400_1V8 = (1 << 6),
/* Current HS mode register */
MMC_EXT_CSD_HS_TIMING = 185,
};
@ -616,11 +682,20 @@ static const char *sdmmc_get_speed_string(enum sdmmc_bus_speed speed)
{
switch (speed) {
case SDMMC_SPEED_INIT: return "400kHz (init)";
// SD card speeds
case SDMMC_SPEED_SDR12: return "12.5MB/s";
case SDMMC_SPEED_SDR25: return "25MB/s";
case SDMMC_SPEED_SDR50: return "50MB/s";
case SDMMC_SPEED_SDR104: return "104MB/s";
case SDMMC_SPEED_DDR50: return "104MB/s (DDR)";
case SDMMC_SPEED_OTHER: return "<invalid speed>";
// eMMC card speeds
case SDMMC_SPEED_HS26: return "26 MHz";
case SDMMC_SPEED_HS52: return "52 MHz";
case SDMMC_SPEED_HS200: return "200MHz";
case SDMMC_SPEED_HS400: return "200MHz (DDR)";
}
return "";
@ -643,12 +718,6 @@ static const char *sdmmc_get_command_string(enum sdmmc_command command)
return "CMD_APP_SET_CARD_DETECT";
case CMD_APP_SEND_SCR:
return "CMD_APP_SEND_SCR";
case CMD_WRITE_SINGLE_BLOCK:
return "CMD_WRITE_SINGLE_BLOCK";
case CMD_WRITE_MULTIPLE_BLOCK:
return "CMD_WRITE_MULTIPLE_BLOCK";
case CMD_SEND_TUNING_BLOCK:
return "CMD_SEND_TUNING_BLOCK";
// For commands with low numbers, read them string from the relevant array.
default:
@ -745,7 +814,7 @@ static int sdmmc4_set_up_clock_and_io(struct mmc *mmc)
car->rst_dev_l_clr |= 0x8000;
// Enable input paths for all pins.
padctl->sdmmc2_control |=
padctl->sdmmc4_control |=
PADCTL_SDMMC4_ENABLE_DATA_IN | PADCTL_SDMMC4_ENABLE_CLK_IN | PADCTL_SDMMC4_DEEP_LOOPBACK;
return 0;
@ -833,6 +902,9 @@ static int sdmmc_set_up_clocking_parameters(struct mmc *mmc, enum sdmmc_bus_volt
mmc->regs->vendor_clock_cntrl &= ~(MMC_CLOCK_TRIM_MASK | MMC_CLOCK_TAP_MASK);
mmc->regs->auto_cal_config &= ~MMC_AUTOCAL_PDPU_CONFIG_MASK;
// Per the TRM, set the PADPIPE clock enable.
mmc->regs->vendor_clock_cntrl |= MMC_CLOCK_PADPIPE_CLKEN_OVERRIDE;
switch (operating_voltage) {
case MMC_VOLTAGE_1V8:
mmc->regs->auto_cal_config |= MMC_AUTOCAL_PDPU_SDMMC4_1V8;
@ -1040,7 +1112,7 @@ static void sdmmc1_configure_clock(struct mmc *mmc, int source, int car_divisor,
* Runs a single iteration of an active SDMMC clock tune.
* You probably want sdmmc_tune_clock instead.
*/
static int sdmmc_run_tuning_iteration(struct mmc *mmc)
static int sdmmc_run_tuning_iteration(struct mmc *mmc, enum sdmmc_command tuning_command)
{
int rc;
uint32_t saved_int_enable = mmc->regs->int_enable;
@ -1070,7 +1142,7 @@ static int sdmmc_run_tuning_iteration(struct mmc *mmc)
// Issue our tuning command. [TRM 32.7.6.2 Step 4]
sdmmc_prepare_command_data(mmc, 1, false, false, false, 0);
sdmmc_prepare_command_registers(mmc, 1, CMD_SEND_TUNING_BLOCK, MMC_RESPONSE_LEN48, MMC_CHECKS_ALL);
sdmmc_prepare_command_registers(mmc, 1, tuning_command, MMC_RESPONSE_LEN48, MMC_CHECKS_ALL);
// Wait for 1us [TRM 32.7.6.2 Step 5]
// As part of the workaround above, we'll wait one microsecond for the glitch window to pass.
@ -1117,8 +1189,10 @@ static int sdmmc_run_tuning_iteration(struct mmc *mmc)
*
* @param mmc The controller to tune.
* @param iterations The total number of iterations to perform.
* @param tuning_command The command to be used for tuning; usually CMD19/21.
*/
static int sdmmc_tune_clock(struct mmc *mmc, enum sdmmc_tuning_attempts iterations)
static int sdmmc_tune_clock(struct mmc *mmc, enum sdmmc_tuning_attempts iterations,
enum sdmmc_command tuning_command)
{
int rc;
@ -1174,7 +1248,7 @@ static int sdmmc_tune_clock(struct mmc *mmc, enum sdmmc_tuning_attempts iteratio
while (attempts_remaining--) {
// Run an iteration of our tuning process.
rc = sdmmc_run_tuning_iteration(mmc);
rc = sdmmc_run_tuning_iteration(mmc, tuning_command);
// If we have an error other than "retry, break.
if (rc != EAGAIN)
@ -1202,6 +1276,7 @@ static int sdmmc_tune_clock(struct mmc *mmc, enum sdmmc_tuning_attempts iteratio
// Check for a tuning failure (SAMPLE CLOCK = 0). [TRM 32.7.6.2 Step 11]
if (!(mmc->regs->host_control2 & MMC_HOST2_SAMPLING_CLOCK_ENABLED)) {
mmc_print(mmc, "ERROR: tuning failed after complete iteration!");
mmc_print(mmc, "host_control2: %08x", mmc->regs->host_control2);
return EIO;
}
@ -1235,6 +1310,11 @@ static int sdmmc_apply_clock_speed(struct mmc *mmc, enum sdmmc_bus_speed speed,
{
int rc;
// By default, don't execute tuning after applying the clock.
bool execute_tuning = false;
enum sdmmc_tuning_attempts tuning_attempts = MMC_VENDOR_TUNING_TRIES_40;
enum sdmmc_command tuning_command = CMD_SD_SEND_TUNING_BLOCK;
// Ensure the clocks are not currently running to avoid glitches.
sdmmc_clock_enable(mmc, false);
@ -1246,50 +1326,65 @@ static int sdmmc_apply_clock_speed(struct mmc *mmc, enum sdmmc_bus_speed speed,
// 400kHz initialization mode.
case SDMMC_SPEED_INIT:
mmc->configure_clock(mmc, CLK_SOURCE_FIRST, MMC_CLOCK_DIVIDER_SDR12, MMC_CLOCK_CONTROL_FREQUENCY_INIT);
mmc->configure_clock(mmc, MMC_CLOCK_SOURCE_SDR12, MMC_CLOCK_DIVIDER_SDR12, MMC_CLOCK_CONTROL_FREQUENCY_INIT);
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR12);
break;
// 25MHz default speed
// 25MHz default speed (SD)
case SDMMC_SPEED_SDR12:
mmc->configure_clock(mmc, CLK_SOURCE_FIRST, MMC_CLOCK_DIVIDER_SDR12, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
mmc->configure_clock(mmc, MMC_CLOCK_SOURCE_SDR12, MMC_CLOCK_DIVIDER_SDR12, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR12);
break;
// 50MHz high speed
// 26MHz default speed (MMC)
case SDMMC_SPEED_HS26:
mmc->configure_clock(mmc, MMC_CLOCK_SOURCE_HS26, MMC_CLOCK_DIVIDER_HS26, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
break;
// 50MHz high speed (SD)
case SDMMC_SPEED_SDR25:
// Configure the host to use high-speed timing.
mmc->regs->host_control |= MMC_HOST_ENABLE_HIGH_SPEED;
mmc->configure_clock(mmc, CLK_SOURCE_FIRST, MMC_CLOCK_DIVIDER_SDR25, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
mmc->configure_clock(mmc, MMC_CLOCK_SOURCE_SDR25, MMC_CLOCK_DIVIDER_SDR25, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR25);
break;
// 100MHz UHS-I
case SDMMC_SPEED_SDR50:
mmc->regs->host_control |= MMC_HOST_ENABLE_HIGH_SPEED;
mmc->configure_clock(mmc, CLK_SOURCE_FIRST, MMC_CLOCK_DIVIDER_SDR50, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR50);
// Execute tuning.
rc = sdmmc_tune_clock(mmc, MMC_VENDOR_TUNING_TRIES_SDR50);
if (rc) {
mmc_print(mmc, "ERROR: tuning failed! (%d)", rc);
return rc;
}
// 52MHz high speed (MMC)
case SDMMC_SPEED_HS52:
mmc->configure_clock(mmc, MMC_CLOCK_SOURCE_HS52, MMC_CLOCK_DIVIDER_HS52, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
break;
// 200MHz UHS-I
// 100MHz UHS-I (SD)
case SDMMC_SPEED_SDR50:
mmc->regs->host_control |= MMC_HOST_ENABLE_HIGH_SPEED;
mmc->configure_clock(mmc, MMC_CLOCK_SOURCE_SDR50, MMC_CLOCK_DIVIDER_SDR50, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR50);
execute_tuning = true;
tuning_attempts = MMC_VENDOR_TUNING_TRIES_SDR50;
break;
// 200MHz UHS-I (SD)
case SDMMC_SPEED_SDR104:
mmc->regs->host_control |= MMC_HOST_ENABLE_HIGH_SPEED;
mmc->configure_clock(mmc, CLK_SOURCE_FIRST, MMC_CLOCK_DIVIDER_SDR104, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
mmc->configure_clock(mmc, MMC_CLOCK_SOURCE_SDR104, MMC_CLOCK_DIVIDER_SDR104, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR104);
execute_tuning = true;
tuning_attempts = MMC_VENDOR_TUNING_TRIES_SDR104;
break;
// 200MHz single-data rate (MMC)
case SDMMC_SPEED_HS200:
case SDMMC_SPEED_HS400:
mmc->regs->host_control |= MMC_HOST_ENABLE_HIGH_SPEED;
mmc->configure_clock(mmc, MMC_CLOCK_SOURCE_HS200, MMC_CLOCK_DIVIDER_HS200, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR104); // Per datasheet; we set the controller in SDR104 mode.
// Execute tuning.
rc = sdmmc_tune_clock(mmc, MMC_VENDOR_TUNING_TRIES_SDR104);
if (rc) {
mmc_print(mmc, "ERROR: tuning failed! (%d)", rc);
return rc;
}
execute_tuning = true;
tuning_attempts = MMC_VENDOR_TUNING_TRIES_HS200;
tuning_command = CMD_MMC_SEND_TUNING_BLOCK;
break;
default:
@ -1297,6 +1392,31 @@ static int sdmmc_apply_clock_speed(struct mmc *mmc, enum sdmmc_bus_speed speed,
return ENOSYS;
}
// If we need to execute tuning for this clock mode, do so.
if (execute_tuning) {
rc = sdmmc_tune_clock(mmc, tuning_attempts, tuning_command);
if (rc) {
mmc_print(mmc, "WARNING: clock tuning failed! speed mode can't be used. (%d)", rc);
sdmmc_clock_enable(mmc, true);
return rc;
}
}
// Special case: HS400 mode should be applied _after_ HS200 is applied, so we apply that
// first above, and then switch up and re-tune.
if (speed == SDMMC_SPEED_HS400) {
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_OTHER); // Special value, per datasheet
// Per the TRM, we should also use this opportunity to set up the DQS path trimmer.
// TODO: should this be used only in HS400?
mmc->regs->vendor_cap_overrides &= ~MMC_VENDOR_CAPABILITY_DQS_TRIM_MASK;
mmc->regs->vendor_cap_overrides |= MMC_VENDOR_CAPABILITY_DQS_TRIM_HS400;
// TODO: run the DLLCAL here!
mmc_print(mmc, "TODO: double the data rate here!");
}
// Re-enable the clock, if necessary.
if (enable_after) {
sdmmc_clock_enable(mmc, true);
@ -2358,6 +2478,9 @@ static int sdmmc_read_and_parse_ext_csd(struct mmc *mmc)
mmc->partitioned = ext_csd[MMC_EXT_CSD_PARTITION_SETTING_COMPLETE] & MMC_EXT_CSD_PARTITION_SETTING_COMPLETED;
mmc->partition_attribute = ext_csd[MMC_EXT_CSD_PARTITION_ATTRIBUTE];
// Speed support.
mmc->mmc_card_type = ext_csd[MMC_EXT_CSD_CARD_TYPE];
return 0;
}
@ -2442,18 +2565,6 @@ static int sdmmc_optimize_transfer_mode(struct mmc *mmc)
return rc;
}
// We started off with the controller opearting with a divided clock,
// which is meant to keep us within the pre-init operating frequency of 400kHz.
// We're now set up, so we can drop the divider. From this point forward, the
// clock is now driven by the CAR frequency.
rc = sdmmc_apply_clock_speed(mmc, SDMMC_SPEED_SDR12, true);
if (rc) {
mmc_print(mmc, "WARNING: could not step up to normal operating speeds!");
mmc_print(mmc, " ... expect delays.");
return rc;
}
mmc_debug(mmc, "now operating at 12.5MB/s!");
// Automatically optimize speed as much as is possible. How this works depends on
// the type of card.
rc = mmc->optimize_speed(mmc);
@ -2467,31 +2578,23 @@ static int sdmmc_optimize_transfer_mode(struct mmc *mmc)
/**
* Switches the active SD card and host controller to the given sppeed mode.
* Switches the active card and host controller to the given speed mode.
*
* @param mmc The MMC controller to affect.
* @param speed The speed to switch to.
* @param status_out The status result of the relevant switch operation.
*/
static int sdmmc_sd_switch_bus_speed(struct mmc *mmc, enum sdmmc_bus_speed speed, struct sdmmc_function_status *status_out)
static int sdmmc_switch_bus_speed(struct mmc *mmc, enum sdmmc_bus_speed speed)
{
int rc;
// Read the supported functions from the card.
rc = mmc->switch_mode(mmc, SDMMC_SWITCH_MODE_MODE_APPLY, SDMMC_SWITCH_MODE_ACCESS_MODE, speed, 0, status_out);
// Ask the card to switch to the new speed.
rc = mmc->card_switch_bus_speed(mmc, speed);
if (rc) {
mmc_print(mmc, "WARNING: could not issue the mode switch");
mmc_print(mmc, "WARNING: could not switch card to %s", sdmmc_get_speed_string(speed));
return rc;
}
// Check that the active operating mode has switched to the new mode.
if (status_out->active_access_mode != speed) {
mmc_print(mmc, "WARNING: device did not accept mode %s!", sdmmc_get_speed_string(speed));
mmc_print(mmc, " reported mode is %s / $d", sdmmc_get_speed_string(status_out->active_access_mode), status_out->active_access_mode);
mmc_print(mmc, " adjacent mode is %s / $d", sdmmc_get_speed_string(status_out->group2_selection), status_out->group2_selection);
return EINVAL;
}
// Switch the host controller so it talks the relevant speed.
rc = sdmmc_apply_clock_speed(mmc, speed, true);
if (rc) {
@ -2504,7 +2607,78 @@ static int sdmmc_sd_switch_bus_speed(struct mmc *mmc, enum sdmmc_bus_speed speed
mmc_debug(mmc, "now operating at %s!", sdmmc_get_speed_string(speed));
return 0;
}
/**
* Switches the active SD card and host controller to the given speed mode.
*
* @param mmc The MMC controller to affect.
* @param speed The speed to switch to.
* @param status_out The status result of the relevant switch operation.
*/
static int sdmmc_sd_switch_bus_speed(struct mmc *mmc, enum sdmmc_bus_speed speed)
{
struct sdmmc_function_status status_out;
int rc;
// Read the supported functions from the card.
rc = mmc->switch_mode(mmc, SDMMC_SWITCH_MODE_MODE_APPLY, SDMMC_SWITCH_MODE_ACCESS_MODE, speed, 0, &status_out);
if (rc) {
mmc_print(mmc, "WARNING: could not issue the bus speed switch");
return rc;
}
// Check that the active operating mode has switched to the new mode.
if (status_out.active_access_mode != speed) {
mmc_print(mmc, "WARNING: device did not accept mode %s!", sdmmc_get_speed_string(speed));
mmc_print(mmc, " reported mode is %s / %d", sdmmc_get_speed_string(status_out.active_access_mode), status_out.active_access_mode);
return EINVAL;
}
return 0;
}
/**
* Switches the active MMC card and host controller to the given speed mode.
*
* @param mmc The MMC controller to affect.
* @param speed The speed to switch to.
* @param status_out The status result of the relevant switch operation.
*/
static int sdmmc_mmc_switch_bus_speed(struct mmc *mmc, enum sdmmc_bus_speed speed)
{
int rc;
uint8_t ext_csd[MMC_EXT_CSD_SIZE];
// To disambiguate constants, we add ten to every MMC speed constant.
// we have to undo this before sending the constants out to the card.
uint32_t argument = speed - MMC_SPEED_MMC_OFFSET;
// Read the supported functions from the card.
rc = mmc->switch_mode(mmc, MMC_SWITCH_MODE_WRITE_BYTE, MMC_HS_TIMING, argument, 0, NULL);
if (rc) {
mmc_print(mmc, "WARNING: could not issue the mode switch");
return rc;
}
// Read the single EXT-CSD block, which contains the current speed.
rc = sdmmc_send_command(mmc, CMD_SEND_EXT_CSD, MMC_RESPONSE_LEN48,
MMC_CHECKS_ALL, 0, NULL, 1, false, false, ext_csd);
if (rc) {
mmc_print(mmc, "ERROR: failed to read the extended CSD after mode-switch!");
return rc;
}
// Check the ext-csd to make sure the change took.
if (ext_csd[MMC_EXT_CSD_HS_TIMING] != argument) {
mmc_print(mmc, "WARNING: device did not accept mode %s!", sdmmc_get_speed_string(speed));
mmc_print(mmc, " reported mode is %s / %d", sdmmc_get_speed_string(ext_csd[MMC_EXT_CSD_HS_TIMING]), ext_csd[MMC_EXT_CSD_HS_TIMING]);
return EINVAL;
}
return 0;
}
@ -2516,12 +2690,23 @@ static int sdmmc_sd_optimize_speed(struct mmc *mmc)
int rc;
struct sdmmc_function_status function_status;
// We started off with the controller opearting with a divided clock,
// which is meant to keep us within the pre-init operating frequency of 400kHz.
// We're now set up, so we can drop the divider. From this point forward, the
// clock is now driven by the CAR frequency.
rc = sdmmc_apply_clock_speed(mmc, SDMMC_SPEED_SDR12, true);
if (rc) {
mmc_print(mmc, "WARNING: could not step up to normal operating speeds!");
mmc_print(mmc, " ... expect delays.");
return rc;
}
mmc_debug(mmc, "now operating at 12.5MB/s!");
// If we're not at a full bus width, we can't switch to a higher speed.
// We're already optimal, vacuously succeed.
if (mmc->max_bus_width != SD_BUS_WIDTH_4BIT)
return 0;
// Read the supported functions from the card.
rc = mmc->switch_mode(mmc, SDMMC_SWITCH_MODE_MODE_QUERY, SDMMC_SWITCH_MODE_NO_GROUP, 0, 0, &function_status);
if (rc) {
@ -2540,15 +2725,15 @@ static int sdmmc_sd_optimize_speed(struct mmc *mmc)
if (mmc->operating_voltage == MMC_VOLTAGE_1V8) {
// Try each of the UHS-I modes, we support.
if (function_status.sdr104_support && !sdmmc_sd_switch_bus_speed(mmc, SDMMC_SPEED_SDR104, &function_status))
if (function_status.sdr104_support && !sdmmc_switch_bus_speed(mmc, SDMMC_SPEED_SDR104))
return 0;
if (function_status.sdr50_support && !sdmmc_sd_switch_bus_speed(mmc, SDMMC_SPEED_SDR50, &function_status))
if (function_status.sdr50_support && !sdmmc_switch_bus_speed(mmc, SDMMC_SPEED_SDR50))
return 0;
}
// If we support High Speed but not a UHS-I mode, use it.
if (function_status.sdr25_support)
return sdmmc_sd_switch_bus_speed(mmc, SDMMC_SPEED_SDR25, &function_status);
return sdmmc_switch_bus_speed(mmc, SDMMC_SPEED_SDR25);
mmc_debug(mmc, "couldn't improve speed above the speed already set.");
return 0;
@ -2560,17 +2745,46 @@ static int sdmmc_sd_optimize_speed(struct mmc *mmc)
*/
static int sdmmc_mmc_optimize_speed(struct mmc *mmc)
{
// TODO
return 0;
}
int rc;
bool hs52_support, hs200_support, hs400_support;
// XXX
sdmmc_set_loglevel(3);
/**
* Optimize our MMC transfer speed by maxing out the bus as much as is possible.
*/
static int sdmmc_emmc_optimize_speed(struct mmc *mmc)
{
// TODO
// We started off with the controller opearting with a divided clock,
// which is meant to keep us within the pre-init operating frequency of 400kHz.
// We're now set up, so we can drop the divider. From this point forward, the
// clock is now driven by the CAR frequency.
rc = sdmmc_apply_clock_speed(mmc, SDMMC_SPEED_SDR12, true);
if (rc) {
mmc_print(mmc, "WARNING: could not step up to normal operating speeds!");
mmc_print(mmc, " ... expect delays.");
return rc;
}
mmc_debug(mmc, "now operating at 25MB/s!");
// Determine which high-speed modes are supported, for easy reference below.
hs200_support = mmc->mmc_card_type & MMC_EXT_CSD_CARD_TYPE_HS400_1V8;
hs400_support = mmc->mmc_card_type & MMC_EXT_CSD_CARD_TYPE_HS400_1V8;
hs52_support = mmc->mmc_card_type & MMC_EXT_CSD_CARD_TYPE_HS52;
// First, try modes that are only supported at 1V8 and below,
// if we can.
if (mmc->operating_voltage == MMC_VOLTAGE_1V8) {
//if (hs400_support && !sdmmc_switch_bus_speed(mmc, SDMMC_SPEED_HS400))
// return 0;
//if (hs200_support && !sdmmc_switch_bus_speed(mmc, SDMMC_SPEED_HS200))
// return 0;
(void)hs400_support;
(void)hs200_support;
}
// Next, try the legacy "high speed" mode.
if (hs52_support)
return sdmmc_switch_bus_speed(mmc, SDMMC_SPEED_HS52);
mmc_debug(mmc, "couldn't improve speed above the default");
return 0;
}
@ -3061,11 +3275,7 @@ static void sdmmc_apply_card_type(struct mmc *mmc, enum sdmmc_card_type type)
mmc->establish_relative_address = sdmmc_set_relative_address;
mmc->switch_mode = sdmmc_mmc_switch_mode;
mmc->switch_bus_width = sdmmc_mmc_switch_bus_width;
// Handle eMMC and MMC speed optimizations differently.
if (type == MMC_CARD_EMMC)
mmc->optimize_speed = sdmmc_emmc_optimize_speed;
else
mmc->card_switch_bus_speed = sdmmc_mmc_switch_bus_speed;
mmc->optimize_speed = sdmmc_mmc_optimize_speed;
break;
@ -3077,6 +3287,7 @@ static void sdmmc_apply_card_type(struct mmc *mmc, enum sdmmc_card_type type)
mmc->switch_mode = sdmmc_sd_switch_mode;
mmc->switch_bus_width = sdmmc_sd_switch_bus_width;
mmc->optimize_speed = sdmmc_sd_optimize_speed;
mmc->card_switch_bus_speed = sdmmc_sd_switch_bus_speed;
break;
// Switch-cart protocol cards

View File

@ -34,7 +34,6 @@ enum sdmmc_bus_width {
*/
enum sdmmc_bus_voltage {
MMC_VOLTAGE_3V3 = 0b111,
MMC_VOLTAGE_3V0 = 0b110,
MMC_VOLTAGE_1V8 = 0b101,
};
@ -115,27 +114,28 @@ enum sdmmc_switch_argument_offsets {
};
/**
* Fields that can be modified by CMD_SWITCH_MODE.
*/
enum sdmmc_switch_field {
/* Fields */
MMC_GROUP_ERASE_DEF = 175,
MMC_PARTITION_CONFIG = 179,
MMC_BUS_WIDTH = 183,
};
/**
* Bus speeds possible for an SDMMC controller.
*/
enum sdmmc_bus_speed {
/* SD card speeds */
SDMMC_SPEED_SDR12 = 0,
SDMMC_SPEED_SDR25 = 1,
SDMMC_SPEED_SDR50 = 2,
SDMMC_SPEED_SDR104 = 3,
SDMMC_SPEED_DDR50 = 4,
/* Other speed: non-spec-compliant value used for e.g. HS400 */
SDMMC_SPEED_OTHER = 5,
/* eMMC card speeds */
/* note: to avoid an enum clash, we add ten to these */
SDMMC_SPEED_HS26 = 10 ,
SDMMC_SPEED_HS52 = 11 ,
SDMMC_SPEED_HS200 = 12 ,
SDMMC_SPEED_HS400 = 13 ,
/* special speeds */
SDMMC_SPEED_INIT = -1,
};
@ -152,7 +152,6 @@ struct mmc {
bool use_dma;
unsigned int timeout;
enum tegra_named_gpio card_detect_gpio;
enum sdmmc_card_type card_type;
enum sdmmc_write_permission write_enable;
/* Per-controller operations. */
@ -168,8 +167,12 @@ struct mmc {
int (*switch_mode)(struct mmc *mmc, int a, int b, int c, uint32_t timeout, void *response);
int (*switch_bus_width)(struct mmc *mmc, enum sdmmc_bus_width width);
int (*optimize_speed)(struct mmc *mmc);
int (*card_switch_bus_speed)(struct mmc *mmc, enum sdmmc_bus_speed speed);
/* Card properties */
enum sdmmc_card_type card_type;
uint32_t mmc_card_type;
uint8_t cid[15];
uint32_t relative_address;
uint8_t partitioned;