mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-28 09:30:58 +01:00
fusee: SD: implement tuning, allowing for all SD UHS-I speed
This commit is contained in:
parent
38350e769c
commit
5f3fc8156c
@ -196,6 +196,7 @@ enum sdmmc_register_bits {
|
||||
MMC_STATUS_COMMAND_COMPLETE = (1 << 0),
|
||||
MMC_STATUS_TRANSFER_COMPLETE = (1 << 1),
|
||||
MMC_STATUS_DMA_INTERRUPT = (1 << 3),
|
||||
MMC_STATUS_BUFFER_READ_READY = (1 << 5),
|
||||
MMC_STATUS_COMMAND_TIMEOUT = (1 << 16),
|
||||
MMC_STATUS_COMMAND_CRC_ERROR = (1 << 17),
|
||||
MMC_STATUS_COMMAND_END_BIT_ERROR = (1 << 18),
|
||||
@ -225,9 +226,14 @@ enum sdmmc_register_bits {
|
||||
MMC_HOST2_DRIVE_STRENGTH_C = (0x2 << 4),
|
||||
MMC_HOST2_DRIVE_STRENGTH_D = (0x3 << 4),
|
||||
MMC_HOST2_USE_1V8_SIGNALING = (1 << 3),
|
||||
MMC_HOST2_EXECUTE_TUNING = (1 << 6),
|
||||
MMC_HOST2_SAMPLING_CLOCK_ENABLED = (1 << 7),
|
||||
MMC_HOST2_UHS_MODE_MASK = (0x7 << 3),
|
||||
|
||||
/* Software reset */
|
||||
MMC_SOFT_RESET_FULL = (1 << 0),
|
||||
MMC_SOFT_RESET_CMD = (1 << 1),
|
||||
MMC_SOFT_RESET_DAT = (1 << 2),
|
||||
|
||||
/* Vendor clock control */
|
||||
MMC_CLOCK_TAP_MASK = (0xFF << 16),
|
||||
@ -257,9 +263,42 @@ enum sdmmc_register_bits {
|
||||
/* Capabilities register high */
|
||||
MMC_SDR50_REQUIRES_TUNING = (1 << 13),
|
||||
|
||||
/* Vendor tuning control 0*/
|
||||
MMC_VENDOR_TUNING_TRIES_MASK = (0x7 << 13),
|
||||
MMC_VENDOR_TUNING_TRIES_SHIFT = 13,
|
||||
|
||||
MMC_VENDOR_TUNING_MULTIPLIER_MASK = (0x7F << 6),
|
||||
MMC_VENDOR_TUNING_MULTIPLIER_UNITY = (1 << 6),
|
||||
|
||||
MMC_VENDOR_TUNING_DIVIDER_MASK = (0x7 << 3),
|
||||
|
||||
MMC_VENDOR_TUNING_SET_BY_HW = (1 << 17),
|
||||
|
||||
/* Vendor tuning control 0*/
|
||||
MMC_VENDOR_TUNING_STEP_SIZE_SDR50_DEFAULT = (0 << 0),
|
||||
MMC_VENDOR_TUNING_STEP_SIZE_SDR104_DEFAULT = (0 << 4),
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The number o
|
||||
*/
|
||||
enum sdmmc_tuning_attempts {
|
||||
MMC_VENDOR_TUNING_TRIES_40 = 0,
|
||||
MMC_VENDOR_TUNING_TRIES_64 = 1,
|
||||
MMC_VENDOR_TUNING_TRIES_128 = 2,
|
||||
MMC_VENDOR_TUNING_TRIES_192 = 3,
|
||||
MMC_VENDOR_TUNING_TRIES_256 = 4,
|
||||
|
||||
/* Helpful aliases; values are from the TRM */
|
||||
MMC_VENDOR_TUNING_TRIES_SDR50 = 4,
|
||||
MMC_VENDOR_TUNING_TRIES_SDR104 = 2,
|
||||
};
|
||||
|
||||
|
||||
/* Constant map that converts from a MMC_VENDOR_TUNING_TRIES_* value to the number of tries. */
|
||||
static const int sdmmc_tuning_iterations[] = {40, 64, 128, 192, 256};
|
||||
|
||||
/**
|
||||
* SDMMC commands
|
||||
*/
|
||||
@ -286,6 +325,7 @@ enum sdmmc_command {
|
||||
CMD_SET_BLKLEN = 16,
|
||||
CMD_READ_SINGLE_BLOCK = 17,
|
||||
CMD_READ_MULTIPLE_BLOCK = 18,
|
||||
CMD_SEND_TUNING_BLOCK = 19,
|
||||
CMD_WRITE_SINGLE_BLOCK = 24,
|
||||
CMD_WRITE_MULTIPLE_BLOCK = 25,
|
||||
|
||||
@ -367,6 +407,9 @@ enum sdmmc_command_magic {
|
||||
SDMMC_SWITCH_MODE_ACCESS_MODE = 0,
|
||||
SDMMC_SWITCH_MODE_NO_GROUP = -1,
|
||||
|
||||
/* Misc constants */
|
||||
MMC_TUNING_TIMEOUT = 150 * 1000, // 150mS
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -420,8 +463,6 @@ enum sdmmc_ext_csd_extents {
|
||||
|
||||
MMC_EXT_CSD_PARTITION_SWITCH_TIME = 199,
|
||||
MMC_EXT_CSD_PARTITION_SWITCH_SCALE_US = 10000,
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -488,6 +529,22 @@ typedef int (*fault_handler_t)(struct mmc *mmc);
|
||||
/* Forward declarations */
|
||||
static int sdmmc_send_simple_command(struct mmc *mmc, enum sdmmc_command command,
|
||||
enum sdmmc_response_type response_type, uint32_t argument, void *response_buffer);
|
||||
static int sdmmc_wait_for_event(struct mmc *mmc,
|
||||
uint32_t target_irq, uint32_t state_conditions,
|
||||
uint32_t fault_conditions, fault_handler_t fault_handler);
|
||||
static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
||||
enum sdmmc_response_type response_type, enum sdmmc_response_checks checks,
|
||||
uint32_t argument, void *response_buffer, uint16_t blocks_to_transfer,
|
||||
bool is_write, bool auto_terminate, void *data_buffer);
|
||||
static int sdmmc_use_block_size(struct mmc *mmc, int block_order);
|
||||
static uint8_t sdmmc_get_block_order(struct mmc *mmc, bool is_write);
|
||||
static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks,
|
||||
bool is_write, bool auto_terminate, bool use_dma, int argument);
|
||||
static void sdmmc_prepare_command_registers(struct mmc *mmc, int blocks_to_xfer,
|
||||
enum sdmmc_command command, enum sdmmc_response_type response_type,
|
||||
enum sdmmc_response_checks checks);
|
||||
static int sdmmc_wait_for_command_readiness(struct mmc *mmc);
|
||||
static int sdmmc_wait_for_data_readiness(struct mmc *mmc);
|
||||
|
||||
/* SDMMC debug enable */
|
||||
static int sdmmc_loglevel = 0;
|
||||
@ -590,6 +647,8 @@ static const char *sdmmc_get_command_string(enum sdmmc_command command)
|
||||
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:
|
||||
@ -639,15 +698,15 @@ static struct tegra_sdmmc *sdmmc_get_regs(enum sdmmc_controller controller)
|
||||
* @param mmc The MMC controller to be reset.
|
||||
* @return 0 if the device successfully came out of reset; or an error code otherwise
|
||||
*/
|
||||
static int sdmmc_hardware_reset(struct mmc *mmc)
|
||||
static int sdmmc_hardware_reset(struct mmc *mmc, uint32_t reset_flags)
|
||||
{
|
||||
uint32_t timebase = get_time();
|
||||
|
||||
// Reset the MMC controller...
|
||||
mmc->regs->software_reset |= MMC_SOFT_RESET_FULL;
|
||||
mmc->regs->software_reset |= reset_flags;
|
||||
|
||||
// Wait for the SDMMC controller to come back up...
|
||||
while (mmc->regs->software_reset & MMC_SOFT_RESET_FULL) {
|
||||
while (mmc->regs->software_reset & reset_flags) {
|
||||
if (get_time_since(timebase) > mmc->timeout) {
|
||||
mmc_print(mmc, "failed to bring up SDMMC controller");
|
||||
return ETIMEDOUT;
|
||||
@ -951,7 +1010,6 @@ static void sdmmc4_configure_clock(struct mmc *mmc, int source, int car_divisor,
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the clock for the given SDMMC controller.
|
||||
* Assumes the controller's clock has been stopped with sdmmc_clock_enable before use.
|
||||
@ -977,17 +1035,191 @@ static void sdmmc1_configure_clock(struct mmc *mmc, int source, int car_divisor,
|
||||
mmc->regs->clock_control |= sdmmc_divisor << MMC_CLOCK_CONTROL_FREQUENCY_SHIFT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
int rc;
|
||||
uint32_t saved_int_enable = mmc->regs->int_enable;
|
||||
|
||||
// Enable the BUFFER_READ_READY interrupt for this run, and make sure it's not set.
|
||||
mmc->regs->int_enable |= MMC_STATUS_BUFFER_READ_READY;
|
||||
mmc->regs->int_status = mmc->regs->int_status;
|
||||
|
||||
// Wait until we can issue commands to the device.
|
||||
rc = sdmmc_wait_for_command_readiness(mmc);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "card not willing to accept commands (%d / %08x)", rc, mmc->regs->present_state);
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
rc = sdmmc_wait_for_data_readiness(mmc);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "card not willing to accept data-commands (%d / %08x)", rc, mmc->regs->present_state);
|
||||
return EBUSY;
|
||||
}
|
||||
|
||||
// Disable the SD card clock. [TRM 32.7.6.2 Step 3]
|
||||
// NVIDIA notes that issuing the re-tune command triggers a re-selection of clock tap, but also
|
||||
// due to a hardware issue, causes a one-microsecond window in which a clock glitch can occur.
|
||||
// We'll disable the SD card clock temporarily so the card itself isn't affected by the glitch.
|
||||
sdmmc_clock_enable(mmc, false);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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.
|
||||
udelay(1);
|
||||
|
||||
// Issue a software reset for the data and command lines. [TRM 32.7.6.2 Step 6/7]
|
||||
// This completes the workaround by ensuring the glitch didn't leave the sampling hardware
|
||||
// for these lines in an uncertain state. This function blocks until the glitch window has
|
||||
// complete, so it handles both TRM steps 7 and 8.
|
||||
sdmmc_hardware_reset(mmc, MMC_SOFT_RESET_CMD | MMC_SOFT_RESET_DAT);
|
||||
|
||||
// Restore the SDMMC clock, now that the workaround is complete. [TRM 32.7.6.2 Step 8]
|
||||
// This enables the actual command to be issued.
|
||||
sdmmc_clock_enable(mmc, true);
|
||||
|
||||
// Wait for the command to be completed. [TRM 32.7.6.2 Step 9]
|
||||
rc = sdmmc_wait_for_event(mmc, MMC_STATUS_BUFFER_READ_READY, 0, 0, NULL);
|
||||
|
||||
// Always restore the prior interrupt settings.
|
||||
mmc->regs->int_enable = saved_int_enable;
|
||||
|
||||
// If we had an error waiting for the interrupt, something went wrong.
|
||||
// TODO: decide if this should be a retry condition?
|
||||
if (rc) {
|
||||
mmc_print(mmc, "buffer ready ready didn't go high in time?");
|
||||
mmc_print(mmc, "error message: %s", strerror(rc));
|
||||
mmc_print(mmc, "interrupt reg: %08x", mmc->regs->int_status);
|
||||
mmc_print(mmc, "interrupt en: %08x", mmc->regs->int_enable);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Check the status of the "execute tuning", which indicates the success of
|
||||
// this tuning step. [TRM 32.7.6.2 Step 10]
|
||||
if (mmc->regs->host_control2 & MMC_HOST2_EXECUTE_TUNING)
|
||||
return EAGAIN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs an SDMMC clock tuning -- should be issued when switching up to a UHS-I
|
||||
* mode, and periodically thereafter per the spec.
|
||||
*
|
||||
* @param mmc The controller to tune.
|
||||
* @param iterations The total number of iterations to perform.
|
||||
*/
|
||||
static int sdmmc_tune_clock(struct mmc *mmc)
|
||||
static int sdmmc_tune_clock(struct mmc *mmc, enum sdmmc_tuning_attempts iterations)
|
||||
{
|
||||
// FIXME: implement
|
||||
mmc_print(mmc, "ERROR: can't use a mode that requires tuning, currently!");
|
||||
return ENOSYS;
|
||||
int rc;
|
||||
|
||||
// We follow the NVIDIA-suggested tuning procedure (TRM section 32.7.6),
|
||||
// including sections where it deviates from the SDMMC specifications.
|
||||
//
|
||||
// This seems to produce the most reliable results, and includes workarounds
|
||||
// for bugs in the X1 hardware.
|
||||
|
||||
// Read the current block order, so we can restore it.
|
||||
int original_block_order = sdmmc_get_block_order(mmc, false);
|
||||
|
||||
// Stores the current timeout; we'll restore it in a bit.
|
||||
int original_timeout = mmc->timeout;
|
||||
|
||||
// Figure out the block order to be used for communciations.
|
||||
// XXX: where does this come from
|
||||
int target_block_order = 6;
|
||||
|
||||
// The SDMMC host spec suggests tuning should occur over 40 iterations, so we'll stick to that.
|
||||
// Vendors seem to deviate from this, so this is a possible area to look into if something doesn't
|
||||
// wind up working correctly.
|
||||
int attempts_remaining = sdmmc_tuning_iterations[iterations];
|
||||
mmc_debug(mmc, "executing tuning (%d iterations)", attempts_remaining);
|
||||
|
||||
// These values come from the vendor's recommended values in the TRM.
|
||||
|
||||
// Allow the tuning hardware to control e.g. our clock taps.
|
||||
mmc->regs->vendor_tuning_cntrl0 |= MMC_VENDOR_TUNING_SET_BY_HW;
|
||||
|
||||
// Apply our number of tries.
|
||||
mmc->regs->vendor_tuning_cntrl0 &= ~MMC_VENDOR_TUNING_TRIES_MASK;
|
||||
mmc->regs->vendor_tuning_cntrl0 |= (iterations << MMC_VENDOR_TUNING_TRIES_SHIFT);
|
||||
|
||||
// Don't use a multiplier or a divider.
|
||||
mmc->regs->vendor_tuning_cntrl0 &= ~(MMC_VENDOR_TUNING_MULTIPLIER_MASK | MMC_VENDOR_TUNING_DIVIDER_MASK);
|
||||
mmc->regs->vendor_tuning_cntrl0 |= MMC_VENDOR_TUNING_MULTIPLIER_UNITY;
|
||||
|
||||
// Use zero step sizes; per TRM 32.7.6.1.
|
||||
mmc->regs->vendor_tuning_cntrl1 = MMC_VENDOR_TUNING_STEP_SIZE_SDR50_DEFAULT | MMC_VENDOR_TUNING_STEP_SIZE_SDR104_DEFAULT;
|
||||
|
||||
// Start the tuning process. [TRM 32.7.6.2 Step 2]
|
||||
mmc->regs->host_control2 |= MMC_HOST2_EXECUTE_TUNING;
|
||||
|
||||
// Momentarily step down to a smaller block size, so we don't
|
||||
// have to allocate a huge buffer for this command.
|
||||
mmc->read_block_order = target_block_order;
|
||||
|
||||
// Step down to the timeout recommended in the specification.
|
||||
mmc->timeout = MMC_TUNING_TIMEOUT;
|
||||
|
||||
// Iterate an iteration of the tuning process.
|
||||
while (attempts_remaining--) {
|
||||
|
||||
// Run an iteration of our tuning process.
|
||||
rc = sdmmc_run_tuning_iteration(mmc);
|
||||
|
||||
// If we have an error other than "retry, break.
|
||||
if (rc != EAGAIN)
|
||||
break;
|
||||
}
|
||||
|
||||
// Restore the original parameters.
|
||||
mmc->read_block_order = original_block_order;
|
||||
mmc->timeout = original_timeout;
|
||||
|
||||
// If we exceeded our attempts, set this as a timeout.
|
||||
if (rc == EAGAIN) {
|
||||
mmc_print(mmc, "tuning attempts exceeded!");
|
||||
rc = ETIMEDOUT;
|
||||
}
|
||||
|
||||
// If the tuning failed, for any reason, print and return the error.
|
||||
if (rc) {
|
||||
mmc_print(mmc, "ERROR: failed to tune the SDMMC clock! (%d)", rc);
|
||||
mmc_print(mmc, "error message %s", strerror(rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
// If we've made it here, this iteration completed tuning.
|
||||
// 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!");
|
||||
return EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configures the host controller to work at a given UHS-I mode.
|
||||
*
|
||||
* @param mmc The controller to work with
|
||||
* @param speed The UHS or pre-UHS speed to work at.
|
||||
*/
|
||||
static void sdmmc_set_uhs_mode(struct mmc *mmc, enum sdmmc_bus_speed speed)
|
||||
{
|
||||
// Set the UHS mode register.
|
||||
mmc->regs->host_control2 &= MMC_HOST2_UHS_MODE_MASK;
|
||||
mmc->regs->host_control2 |= speed;
|
||||
}
|
||||
|
||||
|
||||
@ -1015,11 +1247,13 @@ 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);
|
||||
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR12);
|
||||
break;
|
||||
|
||||
// 25MHz default speed
|
||||
case SDMMC_SPEED_SDR12:
|
||||
mmc->configure_clock(mmc, CLK_SOURCE_FIRST, MMC_CLOCK_DIVIDER_SDR12, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
|
||||
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR12);
|
||||
break;
|
||||
|
||||
// 50MHz high speed
|
||||
@ -1027,15 +1261,17 @@ static int sdmmc_apply_clock_speed(struct mmc *mmc, enum sdmmc_bus_speed speed,
|
||||
// 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);
|
||||
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);
|
||||
rc = sdmmc_tune_clock(mmc, MMC_VENDOR_TUNING_TRIES_SDR50);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "ERROR: tuning failed! (%d)", rc);
|
||||
return rc;
|
||||
@ -1046,9 +1282,10 @@ static int sdmmc_apply_clock_speed(struct mmc *mmc, enum sdmmc_bus_speed speed,
|
||||
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);
|
||||
sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR104);
|
||||
|
||||
// Execute tuning.
|
||||
rc = sdmmc_tune_clock(mmc);
|
||||
rc = sdmmc_tune_clock(mmc, MMC_VENDOR_TUNING_TRIES_SDR104);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "ERROR: tuning failed! (%d)", rc);
|
||||
return rc;
|
||||
@ -1072,7 +1309,6 @@ static int sdmmc_apply_clock_speed(struct mmc *mmc, enum sdmmc_bus_speed speed,
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Performs low-level initialization for SDMMC1, used for the SD card slot.
|
||||
*/
|
||||
@ -1127,7 +1363,6 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the low-level SDMMC hardware.
|
||||
* Thanks to hexkyz for this init code.
|
||||
@ -1150,8 +1385,8 @@ static int sdmmc_hardware_init(struct mmc *mmc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Software reset the SDMMC device
|
||||
rc = sdmmc_hardware_reset(mmc);
|
||||
// Software reset the SDMMC device.
|
||||
rc = sdmmc_hardware_reset(mmc, MMC_SOFT_RESET_FULL);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "failed to reset!");
|
||||
return rc;
|
||||
@ -1429,7 +1664,6 @@ static int sdmmc_wait_for_command_completion(struct mmc *mmc)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Blocks until the SD driver has completed issuing a command.
|
||||
*
|
||||
@ -1568,13 +1802,13 @@ static int sdmmc_handle_cpu_transfer(struct mmc *mmc, uint16_t blocks, bool is_w
|
||||
* to reclaim the data lines after a transaction.
|
||||
*/
|
||||
static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks,
|
||||
bool is_write, bool auto_terminate, int argument)
|
||||
bool is_write, bool auto_terminate, bool use_dma, int argument)
|
||||
{
|
||||
if (blocks) {
|
||||
uint16_t block_size = sdmmc_get_block_size(mmc, is_write);
|
||||
|
||||
// If we're using DMA, target our bounce buffer.
|
||||
if (mmc->use_dma)
|
||||
if (use_dma)
|
||||
mmc->regs->dma_address = (uint32_t)sdmmc_bounce_buffer;
|
||||
|
||||
// Set up the DMA block size and count.
|
||||
@ -1590,7 +1824,7 @@ static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks,
|
||||
uint32_t to_write = MMC_TRANSFER_LIMIT_BLOCK_COUNT;
|
||||
|
||||
// If this controller should use DMA, set that up.
|
||||
if (mmc->use_dma)
|
||||
if (use_dma)
|
||||
to_write |= MMC_TRANSFER_DMA_ENABLE;
|
||||
|
||||
// If this is a multi-block datagram, indicate so.
|
||||
@ -1631,6 +1865,8 @@ static void sdmmc_prepare_command_registers(struct mmc *mmc, int blocks_to_xfer,
|
||||
to_write |= MMC_COMMAND_TYPE_ABORT;
|
||||
|
||||
// If this command has a data stage, include it.
|
||||
// Note that tuning commands are atypical, but are considered to have a data stage
|
||||
// consiting of the tuning pattern.
|
||||
if (blocks_to_xfer)
|
||||
to_write |= MMC_COMMAND_HAS_DATA;
|
||||
|
||||
@ -1774,7 +2010,7 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
||||
}
|
||||
|
||||
// If we have data to send, prepare it.
|
||||
sdmmc_prepare_command_data(mmc, blocks_to_transfer, is_write, auto_terminate, argument);
|
||||
sdmmc_prepare_command_data(mmc, blocks_to_transfer, is_write, auto_terminate, mmc->use_dma, argument);
|
||||
|
||||
// If this is a write and we have data, we'll need to populate the bounce buffer before
|
||||
// issuing the command.
|
||||
@ -1790,7 +2026,7 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
||||
// Wait for the command to be completed.
|
||||
rc = sdmmc_wait_for_command_completion(mmc);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "failed to issue %s (arg=%08x, rc=%d)", sdmmc_get_command_string(command), argument, rc);
|
||||
mmc_print(mmc, "failed to issue %s (arg=%x, rc=%d)", sdmmc_get_command_string(command), argument, rc);
|
||||
mmc_print_command_errors(mmc, rc);
|
||||
|
||||
sdmmc_enable_interrupts(mmc, false);
|
||||
@ -2034,17 +2270,13 @@ static int sdmmc_read_and_parse_scr(struct mmc *mmc)
|
||||
|
||||
// Momentarily step down to a smaller block size, so we don't
|
||||
// have to allocate a huge buffer for this command.
|
||||
rc = sdmmc_use_block_size(mmc, block_order);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "could not step down to a smaller block size! (%d)", rc);
|
||||
return rc;
|
||||
}
|
||||
mmc->read_block_order = block_order;
|
||||
|
||||
// Request the CSD from the device.
|
||||
rc = sdmmc_send_app_command(mmc, CMD_APP_SEND_SCR, MMC_RESPONSE_LEN48, MMC_CHECKS_ALL, 0, NULL, num_blocks, false, &scr);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "could not get the card's SCR!");
|
||||
sdmmc_use_block_size(mmc, original_block_order);
|
||||
mmc->read_block_order = original_block_order;
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -2052,11 +2284,7 @@ static int sdmmc_read_and_parse_scr(struct mmc *mmc)
|
||||
mmc->spec_version = scr.spec_version;
|
||||
|
||||
// Restore the original block order.
|
||||
rc = sdmmc_use_block_size(mmc, original_block_order);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "could not restore the original block size! (%d)", rc);
|
||||
return rc;
|
||||
}
|
||||
mmc->read_block_order = original_block_order;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -2238,7 +2466,6 @@ static int sdmmc_optimize_transfer_mode(struct mmc *mmc)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Switches the active SD card and host controller to the given sppeed mode.
|
||||
*
|
||||
@ -2348,7 +2575,6 @@ static int sdmmc_emmc_optimize_speed(struct mmc *mmc)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Requests that an MMC target use the card's current relative address.
|
||||
*
|
||||
@ -2800,26 +3026,18 @@ static int sdmmc_sd_switch_mode(struct mmc *mmc, int mode, int group, int value,
|
||||
|
||||
// Momentarily step down to a smaller block size, so we don't
|
||||
// have to allocate a huge buffer for this command.
|
||||
rc = sdmmc_use_block_size(mmc, block_order);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "could not step down to a smaller block size! (%d)", rc);
|
||||
return rc;
|
||||
}
|
||||
mmc->read_block_order = block_order;
|
||||
|
||||
// Issue the command itself.
|
||||
rc = sdmmc_send_command(mmc, CMD_SWITCH_MODE, MMC_RESPONSE_LEN48, MMC_CHECKS_ALL, argument, NULL, num_blocks, false, false, response);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "could not issue switch command!");
|
||||
sdmmc_use_block_size(mmc, original_block_order);
|
||||
mmc->read_block_order = original_block_order;
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Restore the original block order.
|
||||
rc = sdmmc_use_block_size(mmc, original_block_order);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "could not restore the original block size! (%d)", rc);
|
||||
return rc;
|
||||
}
|
||||
mmc->read_block_order = original_block_order;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user