From 4d43a86b605139a884520f3d5f4bddee2ef6d686 Mon Sep 17 00:00:00 2001 From: TuxSH Date: Thu, 24 May 2018 01:17:13 +0200 Subject: [PATCH] Copy latest sdmmc driver to stage2 --- fusee/fusee-primary/src/car.h | 2 +- fusee/fusee-secondary/src/car.h | 12 +- fusee/fusee-secondary/src/sdmmc.c | 1094 ++++++++++++++++++++++--- fusee/fusee-secondary/src/sdmmc.h | 56 +- fusee/fusee-secondary/src/switch_fs.c | 4 +- 5 files changed, 1035 insertions(+), 133 deletions(-) diff --git a/fusee/fusee-primary/src/car.h b/fusee/fusee-primary/src/car.h index e6dd2854b..9f62ad2a6 100644 --- a/fusee/fusee-primary/src/car.h +++ b/fusee/fusee-primary/src/car.h @@ -1,5 +1,5 @@ -#ifndef __FUSEE_CLOCK_H___ +#ifndef __FUSEE_CLOCK_H__ #define __FUSEE_CLOCK_H__ #include "utils.h" diff --git a/fusee/fusee-secondary/src/car.h b/fusee/fusee-secondary/src/car.h index c47a422ea..9f62ad2a6 100644 --- a/fusee/fusee-secondary/src/car.h +++ b/fusee/fusee-secondary/src/car.h @@ -1,5 +1,5 @@ -#ifndef __FUSEE_CLOCK_H___ +#ifndef __FUSEE_CLOCK_H__ #define __FUSEE_CLOCK_H__ #include "utils.h" @@ -49,10 +49,10 @@ enum { */ enum { CLK_SOURCE_MASK = (0b111 << 29), - CLK_SOURCE_FIRST = 0, - CLK_DIVIDER_UNITY = 0, + CLK_SOURCE_FIRST = (0b000 << 29), - CLK_DIVIDER_32 = 32, + CLK_DIVIDER_MASK = (0xff << 0), + CLK_DIVIDER_UNITY = (0x00 << 0), }; @@ -67,8 +67,8 @@ enum { enum { - CLK_SOURCE_SDMMC1 = 19, - CLK_SOURCE_SDMMC4 = 21, /* 0x54 into the the main source block */ + CLK_SOURCE_SDMMC1 = 20, + CLK_SOURCE_SDMMC4 = 25, /* 0x54 into the the main source block */ CLK_SOURCE_SDMMC_LEGACY = 0, /* first in block Y */ }; diff --git a/fusee/fusee-secondary/src/sdmmc.c b/fusee/fusee-secondary/src/sdmmc.c index f77ab609b..64034cf45 100644 --- a/fusee/fusee-secondary/src/sdmmc.c +++ b/fusee/fusee-secondary/src/sdmmc.c @@ -126,11 +126,50 @@ enum sdmmc_response_type { /** * Lengths of SD command responses */ -enum sdmmc_response_sizes { +enum sdmmc_constants { /* Bytes in a LEN136 response */ MMC_RESPONSE_SIZE_LEN136 = 15, }; +/** + * 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 = 4, // 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, + +}; + /** * SDMMC response sanity checks * see the standard for when these should be used @@ -153,6 +192,8 @@ enum sdmmc_register_bits { MMC_BUFFER_WRITE_ENABLE = (1 << 10), MMC_BUFFER_READ_ENABLE = (1 << 11), MMC_DAT0_LINE_STATE = (1 << 20), + MMC_READ_ACTIVE = (1 << 9), + MMC_WRITE_ACTIVE = (1 << 8), /* Block size register */ MMC_DMA_BOUNDARY_MAXIMUM = (0x7 << 12), @@ -176,14 +217,16 @@ enum sdmmc_register_bits { MMC_TRANSFER_LIMIT_BLOCK_COUNT = (1 << 1), MMC_TRANSFER_MULTIPLE_BLOCKS = (1 << 5), MMC_TRANSFER_AUTO_CMD_MASK = (0x3 << 2), - MMC_TRANSFER_AUTO_CMD = (0x3 << 2), MMC_TRANSFER_AUTO_CMD12 = (0x1 << 2), + MMC_TRANSFER_AUTO_CMD23 = (0x2 << 2), + MMC_TRANSFER_AUTO_CMD = (0x3 << 2), MMC_TRANSFER_CARD_TO_HOST = (1 << 4), /* Interrupt status */ 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), @@ -192,7 +235,11 @@ enum sdmmc_register_bits { MMC_STATUS_ERROR_MASK = (0xF << 16), /* Clock control */ - MMC_CLOCK_CONTROL_CARD_CLOCK_ENABLE = (1 << 2), + MMC_CLOCK_CONTROL_CARD_CLOCK_ENABLE = (1 << 2), + MMC_CLOCK_CONTROL_FREQUENCY_MASK = (0x3FF << 6), + MMC_CLOCK_CONTROL_FREQUENCY_SHIFT = 8, + MMC_CLOCK_CONTROL_FREQUENCY_INIT = 0x18, // generates 400kHz from the TRM dividers + MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH = 0x00, // passes through the CAR clock unmodified /* Host control */ MMC_DMA_SELECT_MASK = (0x3 << 3), @@ -200,9 +247,23 @@ enum sdmmc_register_bits { MMC_HOST_BUS_WIDTH_MASK = (1 << 1) | (1 << 5), MMC_HOST_BUS_WIDTH_4BIT = (1 << 1), MMC_HOST_BUS_WIDTH_8BIT = (1 << 5), + MMC_HOST_ENABLE_HIGH_SPEED = (1 << 2), + + /* Host control 2 */ + MMC_HOST2_DRIVE_STRENGTH_MASK = (0x3 << 4), + MMC_HOST2_DRIVE_STRENGTH_B = (0x0 << 4), + MMC_HOST2_DRIVE_STRENGTH_A = (0x1 << 4), + 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), @@ -213,6 +274,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, @@ -229,9 +292,51 @@ enum sdmmc_register_bits { MMC_POWER_CONTROL_VOLTAGE_SHIFT = 1, MMC_POWER_CONTROL_POWER_ENABLE = (1 << 0), + /* 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 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), }; +/** + * Represents the possible tuning modes for the X1 SDMMC controller. + */ +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, + MMC_VENDOR_TUNING_TRIES_HS200 = 2, + MMC_VENDOR_TUNING_TRIES_HS400 = 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 */ @@ -258,6 +363,8 @@ enum sdmmc_command { CMD_SET_BLKLEN = 16, CMD_READ_SINGLE_BLOCK = 17, CMD_READ_MULTIPLE_BLOCK = 18, + CMD_SD_SEND_TUNING_BLOCK = 19, + CMD_MMC_SEND_TUNING_BLOCK = 21, CMD_WRITE_SINGLE_BLOCK = 24, CMD_WRITE_MULTIPLE_BLOCK = 25, @@ -268,6 +375,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. */ @@ -291,6 +410,13 @@ static const char *sdmmc_command_string[] = { "CMD_SET_BLKLEN", "CMD_READ_SINGLE_BLOCK", "CMD_READ_MULTIPLE_BLOCK", + "CMD_SD_SEND_TUNING_BLOCK", + "", + "CMD_MMC_SEND_TUNING_BLOCK", + "", + "", + "CMD_WRITE_SINGLE_BLOCK", + "CMD_WRITE_WRITE_BLOCK", }; @@ -323,10 +449,26 @@ enum sdmmc_command_magic { MMC_IF_VOLTAGE_3V3 = (1 << 8), MMC_IF_CHECK_PATTERN = 0xAA, + /* Switch mode constants */ + SDMMC_SWITCH_MODE_MODE_SHIFT = 31, + SDMMC_SWITCH_MODE_ALL_FUNCTIONS_UNUSED = 0xFFFFFF, + SDMMC_SWITCH_MODE_FUNCTION_MASK = 0xF, + SDMMC_SWITCH_MODE_GROUP_SIZE_BITS = 4, + + SDMMC_SWITCH_MODE_MODE_QUERY = 0, + SDMMC_SWITCH_MODE_MODE_APPLY = 1, + SDMMC_SWITCH_MODE_ACCESS_MODE = 0, + 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_SPEED_MMC_OFFSET = 10, + + MMC_TUNING_TIMEOUT = 150 * 1000, // 150mS + MMC_TUNING_BLOCK_ORDER_4BIT = 6, + MMC_TUNING_BLOCK_ORDER_8BIT = 7, }; @@ -381,9 +523,19 @@ 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, }; + /** * Bitfield struct representing an SD SCR. */ @@ -400,12 +552,69 @@ struct PACKED sdmmc_scr { uint8_t scr_version : 4; }; + +/** + * Bitfield struct represneting an SD card's function status. + */ +struct PACKED sdmmc_function_status { + + /* NOTE: all of the below are reversed from CPU endianness! */ + uint16_t current_consumption; + uint16_t group6_support; + uint16_t group5_support; + uint16_t group4_support; + uint16_t group3_support; + uint16_t group2_support; + + /* support for various speed modes */ + uint16_t group1_support_reserved1 : 8; + uint16_t sdr12_support : 1; + uint16_t sdr25_support : 1; + uint16_t sdr50_support : 1; + uint16_t sdr104_support : 1; + uint16_t ddr50_support : 1; + uint16_t group1_support_reserved2 : 3; + + + uint8_t group5_selection : 4; + uint8_t group6_selection : 4; + uint8_t group3_selection : 4; + uint8_t group4_selection : 4; + uint8_t active_access_mode : 4; + uint8_t group2_selection : 4; + + uint8_t structure_version; + uint16_t group6_busy_status; + uint16_t group5_busy_status; + uint16_t group4_busy_status; + uint16_t group3_busy_status; + uint16_t group2_busy_status; + uint16_t group1_busy_status; + uint8_t reserved[34]; +}; + /* Callback function typedefs */ 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; @@ -422,10 +631,14 @@ static const uint16_t sdmmc_bounce_dma_boundary = MMC_DMA_BOUNDARY_8K; * Sets the current SDMMC debugging loglevel. * * @param loglevel Current log level. A higher value prints more logs. + * @return The loglevel prior to when this was applied, for easy restoration. */ -void sdmmc_set_loglevel(int loglevel) +int sdmmc_set_loglevel(int loglevel) { + int original_loglevel = sdmmc_loglevel; sdmmc_loglevel = loglevel; + + return original_loglevel; } @@ -470,6 +683,33 @@ static void mmc_debug(struct mmc *mmc, char *fmt, ...) } +/** + * @return a statically allocated string that describes the given command + */ +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 ""; + + // 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 ""; +} + + /** * @return a statically allocated string that describes the given command */ @@ -486,10 +726,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"; // For commands with low numbers, read them string from the relevant array. default: @@ -539,15 +775,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; @@ -569,12 +805,11 @@ static int sdmmc4_set_up_clock_and_io(struct mmc *mmc) // Put SDMMC4 in reset car->rst_dev_l_set |= 0x8000; - // Set SDMMC4 clock source (PLLP_OUT0) and divisor (32). - // We use 32 beacuse Nintendo does, and they probably know what they're doing? - car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | CLK_DIVIDER_32; + // Configure the clock to place the device into the initial mode. + car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12; // Set the legacy divier used for detecting timeouts. - car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | CLK_DIVIDER_32; + car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12; // Set SDMMC4 clock enable car->clk_enb_l_set |= 0x8000; @@ -587,7 +822,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; @@ -608,6 +843,9 @@ static void sdmmc_set_working_voltage(struct mmc *mmc, enum sdmmc_bus_voltage vo mmc->regs->power_control &= ~MMC_POWER_CONTROL_VOLTAGE_MASK; mmc->regs->power_control |= voltage << MMC_POWER_CONTROL_VOLTAGE_SHIFT; + // Switch to 1V8 signaling. + mmc->regs->host_control2 |= MMC_HOST2_USE_1V8_SIGNALING; + // Mark the power as on. mmc->regs->power_control |= MMC_POWER_CONTROL_POWER_ENABLE; } @@ -653,9 +891,9 @@ static int sdmmc1_enable_supplies(struct mmc *mmc) pinmux->dmic3_clk = PINMUX_SELECT_FUNCTION0; gpio_configure_mode(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_MODE_GPIO); gpio_configure_direction(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_DIRECTION_OUTPUT); + + // Bring up the SD card fixed regulator. gpio_write(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_LEVEL_HIGH); - - return 0; } @@ -668,16 +906,13 @@ static int sdmmc1_enable_supplies(struct mmc *mmc) */ static int sdmmc_set_up_clocking_parameters(struct mmc *mmc, enum sdmmc_bus_voltage operating_voltage) { - // TODO: decide if these should be split into separate functions after seeing how much - // is common to the tunable modes - - // TODO: timing for HS400/HS667 modes - // TODO: timing for tuanble modes (SDR50/104/200) - // Clear the I/O conditioning constants. 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; @@ -824,11 +1059,379 @@ static int sdmmc1_switch_to_low_voltage(struct mmc *mmc) */ static int sdmmc_always_fail(struct mmc *mmc) { - // This card + // This card is always in low voltage and should never have to switch. return ENOSYS; } +/** + * Sets up the clock for the given SDMMC controller. + * Assumes the controller's clock has been stopped with sdmmc_clock_enable before use. + * + * @param mmc The controller to work with. + * @param source The clock source, as defined by the CAR. + * @param car_divisor. The divisor for that source in the CAR. Usually one of the MMC_CLOCK_DIVIDER macros; + * a divider of N results in a clock that's (N/2) + 1 slower. + * @param sdmmc_divisor An additional divisor applied in the SDMMC controller. + */ +static void sdmmc4_configure_clock(struct mmc *mmc, int source, int car_divisor, int sdmmc_divisor) +{ + volatile struct tegra_car *car = car_get_regs(); + + // Set up the CAR aspect of the clock, and wait 2uS per change per the TRM. + car->clk_enb_l_clr = CAR_CONTROL_SDMMC4; + car->clk_src[CLK_SOURCE_SDMMC4] = source | car_divisor; + udelay(2); + car->clk_enb_l_set = CAR_CONTROL_SDMMC4; + + // ... and the SDMMC section of it. + mmc->regs->clock_control &= ~MMC_CLOCK_CONTROL_FREQUENCY_MASK; + mmc->regs->clock_control |= sdmmc_divisor << MMC_CLOCK_CONTROL_FREQUENCY_SHIFT; +} + + +/** + * Sets up the clock for the given SDMMC controller. + * Assumes the controller's clock has been stopped with sdmmc_clock_enable before use. + * + * @param mmc The controller to work with. + * @param source The clock source, as defined by the CAR. + * @param car_divisor. The divisor for that source in the CAR. Usually one of the MMC_CLOCK_DIVIDER macros; + * a divider of N results in a clock that's (N/2) + 1 slower. + * @param sdmmc_divisor An additional divisor applied in the SDMMC controller. + */ +static void sdmmc1_configure_clock(struct mmc *mmc, int source, int car_divisor, int sdmmc_divisor) +{ + volatile struct tegra_car *car = car_get_regs(); + + // Set up the CAR aspect of the clock, and wait 2uS per change per the TRM. + car->clk_enb_l_clr = CAR_CONTROL_SDMMC1; + car->clk_src[CLK_SOURCE_SDMMC1] = source | car_divisor; + udelay(2); + car->clk_enb_l_set = CAR_CONTROL_SDMMC1; + + // ... and the SDMMC section of it. + mmc->regs->clock_control &= ~MMC_CLOCK_CONTROL_FREQUENCY_MASK; + 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, enum sdmmc_command tuning_command) +{ + 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, 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. + 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. + * @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, + enum sdmmc_command tuning_command) +{ + 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; + + // 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); + + // 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 = mmc->tuning_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, tuning_command); + + // 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!"); + mmc_print(mmc, "host_control2: %08x", mmc->regs->host_control2); + 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; +} + + +/** + * Applies the appropriate clock dividers to the CAR and SD controller to enable use of the + * provided speed. Does not handle any requisite communications with the card. + * + * @param mmc The controller to affect. + * @param speed The speed to apply. + * @param enable_after If set, the SDMMC clock will be enabled after the change. If not, it will be left disabled. + */ +static int sdmmc_apply_clock_speed(struct mmc *mmc, enum sdmmc_bus_speed speed, bool enable_after) +{ + 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); + + // Clear the registers of any existing values, so we can apply new ones. + mmc->regs->host_control &= ~MMC_HOST_ENABLE_HIGH_SPEED; + + // Apply the dividers according to the speed provided. + switch (speed) { + + // 400kHz initialization mode. + case SDMMC_SPEED_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 (SD) + case SDMMC_SPEED_SDR12: + 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; + + // 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, MMC_CLOCK_SOURCE_SDR25, MMC_CLOCK_DIVIDER_SDR25, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH); + sdmmc_set_uhs_mode(mmc, SDMMC_SPEED_SDR25); + break; + + // 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; + + // 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, 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. + execute_tuning = true; + tuning_attempts = MMC_VENDOR_TUNING_TRIES_HS200; + tuning_command = CMD_MMC_SEND_TUNING_BLOCK; + break; + + default: + mmc_print(mmc, "ERROR: switching to unsupported speed!\n"); + 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); + udelay(MMC_POST_CLOCK_DELAY); + } + + // Finally store the operating speed. + mmc->operating_speed = speed; + return 0; +} + + /** * Performs low-level initialization for SDMMC1, used for the SD card slot. */ @@ -858,18 +1461,17 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc) padctl->vgpio_gpio_mux_sel &= ~PADCTL_SDMMC1_CD_SOURCE; // Put SDMMC1 in reset - car->rst_dev_l_set |= CAR_CONTROL_SDMMC1; + car->rst_dev_l_set = CAR_CONTROL_SDMMC1; - // Set SDMMC1 clock source (PLLP_OUT0) and divisor (32). - // We use 32 beacuse Nintendo does, and they probably know what they're doing? - car->clk_src[CLK_SOURCE_SDMMC1] = CLK_SOURCE_FIRST | CLK_DIVIDER_32; + // Configure the clock to place the device into the initial mode. + car->clk_src[CLK_SOURCE_SDMMC1] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12; // Set the legacy divier used for detecting timeouts. - car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | CLK_DIVIDER_32; + car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12; // Set SDMMC1 clock enable - car->clk_enb_l_set |= CAR_CONTROL_SDMMC1; - car->clk_enb_y_set |= CAR_CONTROL_SDMMC_LEGACY; + car->clk_enb_l_set = CAR_CONTROL_SDMMC1; + car->clk_enb_y_set = CAR_CONTROL_SDMMC_LEGACY; // host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles udelay(5000); @@ -906,8 +1508,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; @@ -978,38 +1580,33 @@ static int sdmmc_hardware_init(struct mmc *mmc) // Clear SDHCI_PROG_CLOCK_MODE regs->clock_control &= ~(0x20); - // Clear SDHCI_CTRL_SDMA and SDHCI_CTRL_ADMA2 - regs->host_control &= 0xE7; + // Ensure we're using Single-operation DMA (SDMA) mode for DMA. + regs->host_control &= ~MMC_DMA_SELECT_MASK; // Set the timeout to be the maximum value regs->timeout_control &= ~(0x0F); regs->timeout_control |= 0x0E; - // Clear SDHCI_CTRL_4BITBUS and SDHCI_CTRL_8BITBUS - regs->host_control &= 0xFD; - regs->host_control &= 0xDF; + // Ensure we start off using a single-bit bus. + mmc->regs->host_control &= ~MMC_HOST_BUS_WIDTH_MASK; // TODO: move me into enable voltages, if applicable? // Clear TAP_VAL_UPDATED_BY_HW regs->vendor_tuning_cntrl0 &= ~(0x20000); - // Clear SDHCI_CTRL_HISPD - regs->host_control &= 0xFB; + // Start off in non-high-speed mode. + regs->host_control &= ~MMC_HOST_ENABLE_HIGH_SPEED; // Clear SDHCI_CTRL_VDD_180 regs->host_control2 &= ~(0x08); - // Set SDHCI_DIVIDER and SDHCI_DIVIDER_HI - // FIXME: divider SD if necessary - regs->clock_control &= ~(0xFFC0); - regs->clock_control |= (0x18 << 8); // 200kHz, initially - - // Start delivering the clock to the card. - sdmmc_clock_enable(mmc, true); - - // Ensure we're using Single-operation DMA (SDMA) mode for DMA. - regs->host_control &= ~MMC_DMA_SELECT_MASK; + // Set up the card's initialization. + rc = sdmmc_apply_clock_speed(mmc, SDMMC_SPEED_INIT, true); + if (rc) { + mmc_print(mmc, "ERROR: could not set SDMMC card speed!"); + return rc; + } return 0; } @@ -1115,11 +1712,14 @@ static int sdmmc_flush_bounce_buffer(struct mmc *mmc) } /** - * Blocks until the SD driver has completed issuing a command. + * Generic SDMMC waiting function. * * @param mmc The MMC controller on which to wait. - * @param target_irq A bitmask that specifies the bits that - * will make this function return success + * @param target_irq A bitmask that specifies the interrupt bits that + * will make this function return success. + * @param state_condition A bitmask that specifies a collection of bits + * that indicate business in present_state. If zero, all of the relevant + * conditions becoming false will cause a sucessful return. * @param fault_conditions A bitmask that specifies the bits that * will make this function trigger its fault handler. * @param fault_handler A function that's called to handle DMA faults. @@ -1129,8 +1729,9 @@ static int sdmmc_flush_bounce_buffer(struct mmc *mmc) * @return 0 on sucess, EFAULT if a fault condition occurs, * or an error code if a transfer failure occurs */ -static int sdmmc_wait_for_interrupt(struct mmc *mmc, - uint32_t target_irq, uint32_t fault_conditions, fault_handler_t fault_handler) +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) { uint32_t timebase = get_time(); int rc; @@ -1144,14 +1745,14 @@ static int sdmmc_wait_for_interrupt(struct mmc *mmc, // If we don't have a handler, fault. if (!fault_handler) { - mmc_print(mmc, "ERROR: unhandled DMA fault!\n"); + mmc_print(mmc, "ERROR: unhandled DMA fault!"); return EFAULT; } // Call the DMA fault handler. rc = fault_handler(mmc); if (rc) { - mmc_print(mmc, "ERROR: unhandled DMA fault!\n (%d)", rc); + mmc_print(mmc, "ERROR: unhandled DMA fault! (%d)", rc); return rc; } @@ -1166,6 +1767,9 @@ static int sdmmc_wait_for_interrupt(struct mmc *mmc, if (mmc->regs->int_status & target_irq) return 0; + if (state_conditions && !(mmc->regs->present_state & state_conditions)) + return 0; + // If an error occurs, return it. if (mmc->regs->int_status & MMC_STATUS_ERROR_MASK) return (mmc->regs->int_status & MMC_STATUS_ERROR_MASK); @@ -1179,11 +1783,10 @@ static int sdmmc_wait_for_interrupt(struct mmc *mmc, */ static int sdmmc_wait_for_command_completion(struct mmc *mmc) { - return sdmmc_wait_for_interrupt(mmc, MMC_STATUS_COMMAND_COMPLETE, 0, NULL); + return sdmmc_wait_for_event(mmc, MMC_STATUS_COMMAND_COMPLETE, 0, 0, NULL); } - /** * Blocks until the SD driver has completed issuing a command. * @@ -1191,8 +1794,11 @@ static int sdmmc_wait_for_command_completion(struct mmc *mmc) */ static int sdmmc_wait_for_transfer_completion(struct mmc *mmc) { - return sdmmc_wait_for_interrupt(mmc, MMC_STATUS_TRANSFER_COMPLETE, - MMC_STATUS_DMA_INTERRUPT, sdmmc_flush_bounce_buffer); + int rc = sdmmc_wait_for_event(mmc, MMC_STATUS_TRANSFER_COMPLETE, + MMC_WRITE_ACTIVE | MMC_READ_ACTIVE, MMC_STATUS_DMA_INTERRUPT, sdmmc_flush_bounce_buffer); + + + return rc; } @@ -1319,13 +1925,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. @@ -1341,7 +1947,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. @@ -1350,8 +1956,14 @@ static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks, // If this command should automatically terminate, set the host to // terminate it after the block span is complete. - if (auto_terminate) - to_write |= MMC_TRANSFER_AUTO_CMD12; + if (auto_terminate) { + // If we're in SDR104, use AUTO_CMD23 intead of AUTO_CMD12, + // per the host controller specification. + if (mmc->operating_speed == SDMMC_SPEED_SDR104) + to_write |= MMC_TRANSFER_AUTO_CMD23; + else + to_write |= MMC_TRANSFER_AUTO_CMD12; + } // If this is a read, set the READ mode. if (!is_write) @@ -1382,6 +1994,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; @@ -1525,7 +2139,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. @@ -1541,7 +2155,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); @@ -1785,17 +2399,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; } @@ -1803,11 +2413,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; } @@ -1881,6 +2487,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; } @@ -1894,7 +2503,7 @@ static int sdmmc_mmc_switch_bus_width(struct mmc *mmc, enum sdmmc_bus_width widt { // Ask the card to adjust to the wider bus width. int rc = mmc->switch_mode(mmc, MMC_SWITCH_EXTCSD_NORMAL, - MMC_BUS_WIDTH, width, mmc->timeout); + MMC_BUS_WIDTH, width, mmc->timeout, NULL); if (rc) { mmc_print(mmc, "could not switch mode on the card side!"); return rc; @@ -1965,8 +2574,225 @@ static int sdmmc_optimize_transfer_mode(struct mmc *mmc) return rc; } - // TODO: step up into high speed modes - + // Automatically optimize speed as much as is possible. How this works depends on + // the type of card. + rc = mmc->optimize_speed(mmc); + if (rc) { + mmc_print(mmc, "could not optimize the controller's speed!"); + return rc; + } + + return 0; +} + + +/** + * 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_switch_bus_speed(struct mmc *mmc, enum sdmmc_bus_speed speed) +{ + int rc; + + // 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 switch card to %s", sdmmc_get_speed_string(speed)); + return rc; + } + + // Switch the host controller so it talks the relevant speed. + rc = sdmmc_apply_clock_speed(mmc, speed, true); + if (rc) { + mmc_print(mmc, "WARNING: could not switch the host to %s", sdmmc_get_speed_string(speed)); + + // Fall back to the original speed before returning.. + sdmmc_apply_clock_speed(mmc, mmc->operating_speed, true); + return rc; + } + + 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; +} + + +/** + * Optimize our SDMMC transfer speed by maxing out the bus as much as is possible. + */ +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) { + mmc_print(mmc, "WARNING: could not query card mode status; speed will be limited"); + return rc; + } + + // Debug information for very vebose modes. + mmc_debug(mmc, "this card supports the following speed modes:"); + mmc_debug(mmc, "SDR12: %d SDR25: %d SDR50: %d SDR104: %d DDR50: %d\n", + function_status.sdr12_support, function_status.sdr25_support, + function_status.sdr50_support, function_status.sdr104_support, + function_status.ddr50_support); + + // If we're operating in a UHS-compatbile low-voltage mode, check for UHS-modes. + if (mmc->operating_voltage == MMC_VOLTAGE_1V8) { + + // Try each of the UHS-I modes, we support. + if (function_status.sdr104_support && !sdmmc_switch_bus_speed(mmc, SDMMC_SPEED_SDR104)) + return 0; + 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_switch_bus_speed(mmc, SDMMC_SPEED_SDR25); + + mmc_debug(mmc, "couldn't improve speed above the speed already set."); + return 0; +} + + +/** + * Optimize our MMC transfer speed by maxing out the bus as much as is possible. + */ +static int sdmmc_mmc_optimize_speed(struct mmc *mmc) +{ + int rc; + bool hs52_support, hs200_support, hs400_support; + + // XXX + sdmmc_set_loglevel(3); + + // 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; + } + + // 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; } @@ -2081,13 +2907,17 @@ static int sdmmc_mmc_wait_for_card_readiness(struct mmc *mmc) int rc; uint32_t response[4]; + while (true) { uint32_t response_masked; // Ask the SD card to identify its state. It will respond with readiness and a capacity magic. + int original_loglevel = sdmmc_set_loglevel(0); rc = sdmmc_send_command(mmc, CMD_SEND_OPERATING_CONDITIONS, MMC_RESPONSE_LEN48, MMC_CHECKS_NONE, 0x40000080, response, 0, false, false, NULL); + sdmmc_set_loglevel(original_loglevel); + if (rc) { mmc_print(mmc, "ERROR: could not read the card's operating conditions!"); return rc; @@ -2132,8 +2962,10 @@ static int sdmmc_sd_wait_for_card_readiness(struct mmc *mmc, uint32_t *response) while (true) { // Ask the SD card to identify its state. + int original_loglevel = sdmmc_set_loglevel(0); rc = sdmmc_send_simple_app_command(mmc, CMD_APP_SEND_OP_COND, MMC_RESPONSE_LEN48, MMC_CHECKS_NONE, argument, response); + sdmmc_set_loglevel(original_loglevel); if (rc) { mmc_print(mmc, "ERROR: could not read the card's operating conditions!"); return rc; @@ -2250,7 +3082,7 @@ static int sdmmc_sd_card_init(struct mmc *mmc) mmc->uses_block_addressing = !!(ocr & MMC_SD_OPERATING_COND_HIGH_CAPACITY); // If the card supports using 1V8, drop down using lower voltages. - if (ocr & MMC_SD_OPERATING_COND_ACCEPTS_1V8) { + if (mmc->allow_voltage_switching && ocr & MMC_SD_OPERATING_COND_ACCEPTS_1V8) { if (mmc->operating_voltage != MMC_VOLTAGE_1V8) { rc = mmc->switch_to_low_voltage(mmc); if (rc) @@ -2338,7 +3170,7 @@ static int sdmmc_wait_for_card_ready(struct mmc *mmc, uint32_t timeout) * * @return 0 on success, or an error code on failure */ -static int sdmmc_mmc_switch_mode(struct mmc *mmc, enum sdmmc_switch_access_mode mode, enum sdmmc_switch_field field, uint16_t value, uint32_t timeout) +static int sdmmc_mmc_switch_mode(struct mmc *mmc, int mode, int field, int value, uint32_t timeout, void *unused) { // Collapse our various parameters into a single argument. uint32_t argument = @@ -2366,25 +3198,6 @@ static int sdmmc_mmc_switch_mode(struct mmc *mmc, enum sdmmc_switch_access_mode } -/** - * Issues a SWITCH_MODE command, which can be used to write registers on the SD card's controller, - * and thus to e.g. switch partitions. - * - * @param mmc The MMC device to use for comms. - * @param mode The access mode with which to access the controller. - * @param field The field to access. - * @param value The argument to the access mode. - * @param timeout The timeout, which is often longer than the normal MMC timeout. - * - * @return 0 on success, or an error code on failure - */ -static int sdmmc_sd_switch_mode(struct mmc *mmc, enum sdmmc_switch_access_mode mode, enum sdmmc_switch_field field, uint16_t value, uint32_t timeout) -{ - mmc_print(mmc, "ERROR: SD card mode switching not yet implemented"); - return ENOSYS; -} - - /** * @return True iff the given MMC card supports hardare partitions. */ @@ -2410,6 +3223,54 @@ bool sdmmc_external_card_present(struct mmc *mmc) return !gpio_read(mmc->card_detect_gpio); } +/** + * Issues an SD card mode-switch command. + * + * @param mmc The controller to use. + * @param mode The mode flag -- one to set function data, zero to query. + * @param group The SD card function group-- see the SD card Physical Layer spec. Set this negative to not apply arguments. + * @param response 64-byte buffer to store the response. + */ +static int sdmmc_sd_switch_mode(struct mmc *mmc, int mode, int group, int value, uint32_t timeout, void *response) +{ + int rc; + + // Read the current block order, so we can restore it. + int original_block_order = sdmmc_get_block_order(mmc, false); + + // Always request a single 64-byte block. + const int block_order = 6; + const int num_blocks = 1; + + // Build the argument we're going to issue. + uint32_t argument = (mode << SDMMC_SWITCH_MODE_MODE_SHIFT) | SDMMC_SWITCH_MODE_ALL_FUNCTIONS_UNUSED; + + // If we have a group argument, apply the group and value. + if(group >= 0) { + const int group_shift = group * SDMMC_SWITCH_MODE_GROUP_SIZE_BITS; + argument &= ~(SDMMC_SWITCH_MODE_FUNCTION_MASK << group_shift); + argument |= value << group_shift; + } + + // 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 = 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!"); + mmc->read_block_order = original_block_order; + return rc; + } + + // Restore the original block order. + mmc->read_block_order = original_block_order; + + return 0; +} + + /** * Switches a given SDMMC Controller where */ @@ -2428,6 +3289,9 @@ 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; + mmc->card_switch_bus_speed = sdmmc_mmc_switch_bus_speed; + mmc->optimize_speed = sdmmc_mmc_optimize_speed; + break; // SD-protocol cards @@ -2436,6 +3300,8 @@ static void sdmmc_apply_card_type(struct mmc *mmc, enum sdmmc_card_type type) mmc->establish_relative_address = sdmmc_get_relative_address; 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 @@ -2458,6 +3324,7 @@ static int sdmmc_initialize_defaults(struct mmc *mmc) case SWITCH_EMMC: mmc->name = "eMMC"; mmc->max_bus_width = MMC_BUS_WIDTH_8BIT; + mmc->tuning_block_order = MMC_TUNING_BLOCK_ORDER_8BIT; mmc->operating_voltage = MMC_VOLTAGE_1V8; // Set up function pointers for each of our per-instance functions. @@ -2465,6 +3332,7 @@ static int sdmmc_initialize_defaults(struct mmc *mmc) mmc->enable_supplies = sdmmc4_enable_supplies; mmc->switch_to_low_voltage = sdmmc_always_fail; mmc->card_present = sdmmc_builtin_card_present; + mmc->configure_clock = sdmmc4_configure_clock; // The EMMC controller always uses an EMMC card. sdmmc_apply_card_type(mmc, MMC_CARD_EMMC); @@ -2477,15 +3345,19 @@ static int sdmmc_initialize_defaults(struct mmc *mmc) mmc->name = "uSD"; mmc->card_type = MMC_CARD_SD; mmc->max_bus_width = SD_BUS_WIDTH_4BIT; + mmc->tuning_block_order = MMC_TUNING_BLOCK_ORDER_4BIT; mmc->operating_voltage = MMC_VOLTAGE_3V3; mmc->card_detect_gpio = GPIO_MICROSD_CARD_DETECT; - // For the microSD card slot, assume we have an SD-type card. - // Negotiation has a chance to change this, later. + // Per-instance functions. mmc->set_up_clock_and_io = sdmmc1_set_up_clock_and_io; mmc->enable_supplies = sdmmc1_enable_supplies; mmc->switch_to_low_voltage = sdmmc1_switch_to_low_voltage; mmc->card_present = sdmmc_external_card_present; + mmc->configure_clock = sdmmc1_configure_clock; + + // For the microSD card slot, assume we have an SD-type card. + // Negotiation has a chance to change this, later. sdmmc_apply_card_type(mmc, MMC_CARD_SD); // Start off assuming byte addressing; we'll detect and correct this @@ -2507,15 +3379,19 @@ static int sdmmc_initialize_defaults(struct mmc *mmc) * * @param mmc The SDMMC structure to be initiailized with the device state. * @param controler The controller description to be used; usually SWITCH_EMMC - * or SWTICH_MICROSD. + * or SWITCH_MICROSD. + * @param allow_voltage_switching True if we should allow voltage switching, + * which may not make sense if we're about to chainload to another component, + * a la fusee stage1. */ -int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller) +int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller, bool allow_voltage_switching) { int rc; // Get a reference to the registers for the relevant SDMMC controller. mmc->controller = controller; mmc->regs = sdmmc_get_regs(controller); + mmc->allow_voltage_switching = false; // Set the defaults for the card, including the default function pointers // for the assumed card type, and the per-controller options. @@ -2589,7 +3465,7 @@ int sdmmc_select_partition(struct mmc *mmc, enum sdmmc_partition partition) // Set the PARTITION_CONFIG register to select the active partition. mmc_print(mmc, "switching to partition %d", partition); - rc = mmc->switch_mode(mmc, MMC_SWITCH_MODE_WRITE_BYTE, MMC_PARTITION_CONFIG, argument, 0); + rc = mmc->switch_mode(mmc, MMC_SWITCH_MODE_WRITE_BYTE, MMC_PARTITION_CONFIG, argument, 0, NULL); if (rc) { mmc_print(mmc, "failed to select partition %d (%02x, rc=%d)", partition, argument, rc); } diff --git a/fusee/fusee-secondary/src/sdmmc.h b/fusee/fusee-secondary/src/sdmmc.h index 6d4bf95ca..67952b401 100644 --- a/fusee/fusee-secondary/src/sdmmc.h +++ b/fusee/fusee-secondary/src/sdmmc.h @@ -34,7 +34,6 @@ enum sdmmc_bus_width { */ enum sdmmc_bus_voltage { MMC_VOLTAGE_3V3 = 0b111, - MMC_VOLTAGE_3V0 = 0b110, MMC_VOLTAGE_1V8 = 0b101, }; @@ -116,13 +115,28 @@ enum sdmmc_switch_argument_offsets { /** - * Fields that can be modified by CMD_SWITCH_MODE. + * Bus speeds possible for an SDMMC controller. */ -enum sdmmc_switch_field { - /* Fields */ - MMC_GROUP_ERASE_DEF = 175, - MMC_PARTITION_CONFIG = 179, - MMC_BUS_WIDTH = 183, +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, }; @@ -136,13 +150,14 @@ struct mmc { /* Controller properties */ const char *name; bool use_dma; + bool allow_voltage_switching; 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. */ int (*set_up_clock_and_io)(struct mmc *mmc); + void (*configure_clock)(struct mmc *mmc, int source, int car_divisor, int sdmmc_divisor); int (*enable_supplies)(struct mmc *mmc); int (*switch_to_low_voltage)(struct mmc *mmc); bool (*card_present)(struct mmc *mmc); @@ -150,17 +165,22 @@ struct mmc { /* Per-card-type operations */ int (*card_init)(struct mmc *mmc); int (*establish_relative_address)(struct mmc *mmc); - int (*switch_mode)(struct mmc *mmc, enum sdmmc_switch_access_mode mode, - enum sdmmc_switch_field field, uint16_t value, uint32_t timeout); + 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; enum sdmmc_spec_version spec_version; enum sdmmc_bus_width max_bus_width; enum sdmmc_bus_voltage operating_voltage; + enum sdmmc_bus_speed operating_speed; uint8_t partition_support; uint8_t partition_config; @@ -169,6 +189,7 @@ struct mmc { uint8_t read_block_order; uint8_t write_block_order; + uint8_t tuning_block_order; bool uses_block_addressing; /* Current operation status flags */ @@ -201,17 +222,22 @@ enum sdmmc_partition { * Sets the current SDMMC debugging loglevel. * * @param loglevel Current log level. A higher value prints more logs. + * @return The loglevel prior to when this was applied, for easy restoration. */ -void sdmmc_set_loglevel(int loglevel); +int sdmmc_set_loglevel(int loglevel); /** - * Initiailzes an SDMMC controller for use with an eMMC or SD card device. + * Set up a new SDMMC driver. * - * @param mmc An (uninitialized) structure for the MMC device. - * @param controller The controller number to be initialized. Either SWITCH_MICROSD or SWITCH_EMMC. + * @param mmc The SDMMC structure to be initiailized with the device state. + * @param controler The controller description to be used; usually SWITCH_EMMC + * or SWITCH_MICROSD. + * @param allow_voltage_switching True if we should allow voltage switching, + * which may not make sense if we're about to chainload to another component, + * a la fusee stage1. */ -int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller); +int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller, bool allow_voltage_switching); /** diff --git a/fusee/fusee-secondary/src/switch_fs.c b/fusee/fusee-secondary/src/switch_fs.c index 67a97fa26..aeeb24a96 100644 --- a/fusee/fusee-secondary/src/switch_fs.c +++ b/fusee/fusee-secondary/src/switch_fs.c @@ -43,7 +43,7 @@ static int mmc_partition_initialize(device_partition_t *devpart) { if (mmcpart->mmc == &g_sd_mmc) { if (!g_sd_mmc_initialized) { - int rc = sdmmc_init(mmcpart->mmc, mmcpart->controller); + int rc = sdmmc_init(mmcpart->mmc, mmcpart->controller, true); if (rc == 0) { sdmmc_set_write_enable(mmcpart->mmc, SDMMC_WRITE_ENABLED); g_sd_mmc_initialized = true; @@ -56,7 +56,7 @@ static int mmc_partition_initialize(device_partition_t *devpart) { return 0; } else if (mmcpart->mmc == &g_nand_mmc) { if (!g_nand_mmc_initialized) { - int rc = sdmmc_init(mmcpart->mmc, mmcpart->controller); + int rc = sdmmc_init(mmcpart->mmc, mmcpart->controller, true); if (rc == 0) { g_nand_mmc_initialized = true; } else {