fusee: SD driver: implement everything necessary for UHS except tuning

This commit is contained in:
Kate J. Temkin 2018-05-18 21:51:12 -06:00
parent 633c5e95ed
commit 38350e769c
2 changed files with 401 additions and 104 deletions

View file

@ -135,9 +135,9 @@ enum sdmmc_constants {
* SDMMC clock divider constants * SDMMC clock divider constants
*/ */
enum sdmmc_clock_dividers { enum sdmmc_clock_dividers {
MMC_CLOCK_DIVIDER_SDR12 = 32, // 16.5, from the TRM table MMC_CLOCK_DIVIDER_SDR12 = 31, // 16.5, from the TRM table
MMC_CLOCK_DIVIDER_SDR25 = 16, // 8.5, from the table MMC_CLOCK_DIVIDER_SDR25 = 15, // 8.5, from the table
MMC_CLOCK_DIVIDER_SDR50 = 8, // 4.5, from the table MMC_CLOCK_DIVIDER_SDR50 = 7, // 4.5, from the table
MMC_CLOCK_DIVIDER_SDR104 = 3, // 2, from the datasheet MMC_CLOCK_DIVIDER_SDR104 = 3, // 2, from the datasheet
}; };
@ -206,7 +206,9 @@ enum sdmmc_register_bits {
/* Clock control */ /* 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_MASK = (0x3FF << 6),
MMC_CLOCK_CONTROL_FREQUENCY_INIT = (0x18 << 8), 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 */ /* Host control */
MMC_DMA_SELECT_MASK = (0x3 << 3), MMC_DMA_SELECT_MASK = (0x3 << 3),
@ -252,6 +254,9 @@ enum sdmmc_register_bits {
MMC_POWER_CONTROL_VOLTAGE_SHIFT = 1, MMC_POWER_CONTROL_VOLTAGE_SHIFT = 1,
MMC_POWER_CONTROL_POWER_ENABLE = (1 << 0), MMC_POWER_CONTROL_POWER_ENABLE = (1 << 0),
/* Capabilities register high */
MMC_SDR50_REQUIRES_TUNING = (1 << 13),
}; };
@ -356,6 +361,12 @@ enum sdmmc_command_magic {
SDMMC_SWITCH_MODE_ALL_FUNCTIONS_UNUSED = 0xFFFFFF, SDMMC_SWITCH_MODE_ALL_FUNCTIONS_UNUSED = 0xFFFFFF,
SDMMC_SWITCH_MODE_FUNCTION_MASK = 0xF, SDMMC_SWITCH_MODE_FUNCTION_MASK = 0xF,
SDMMC_SWITCH_MODE_GROUP_SIZE_BITS = 4, 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,
}; };
@ -413,19 +424,6 @@ enum sdmmc_ext_csd_extents {
}; };
/**
* Bus speeds possible for an SDMMC controller.
*/
enum sdmmc_bus_speed {
SDMMC_SPEED_SDR12 = 0,
SDMMC_SPEED_SDR25 = 1,
SDMMC_SPEED_SDR50 = 2,
SDMMC_SPEED_SDR104 = 3,
SDMMC_SPEED_DDR50 = 4,
SDMMC_SPEED_INIT = -1,
};
/** /**
* Bitfield struct representing an SD SCR. * Bitfield struct representing an SD SCR.
@ -443,6 +441,47 @@ struct PACKED sdmmc_scr {
uint8_t scr_version : 4; 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 ddr50_support : 1;
uint16_t sdr104_support : 1;
uint16_t sdr50_support : 1;
uint16_t sdr25_support : 1;
uint16_t sdr12_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 */ /* Callback function typedefs */
typedef int (*fault_handler_t)(struct mmc *mmc); typedef int (*fault_handler_t)(struct mmc *mmc);
@ -513,6 +552,24 @@ 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)";
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)";
}
return "";
}
/** /**
* @return a statically allocated string that describes the given command * @return a statically allocated string that describes the given command
*/ */
@ -612,9 +669,7 @@ static int sdmmc4_set_up_clock_and_io(struct mmc *mmc)
// Put SDMMC4 in reset // Put SDMMC4 in reset
car->rst_dev_l_set |= 0x8000; car->rst_dev_l_set |= 0x8000;
// Set SDMMC4 clock source (PLLP_OUT0) and divisor (17). // Configure the clock to place the device into the initial mode.
// Divider value is the suggested base from the dataseet.
// TODO: abstract me!
car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12; car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
// Set the legacy divier used for detecting timeouts. // Set the legacy divier used for detecting timeouts.
@ -715,12 +770,6 @@ static int sdmmc1_enable_supplies(struct mmc *mmc)
*/ */
static int sdmmc_set_up_clocking_parameters(struct mmc *mmc, enum sdmmc_bus_voltage operating_voltage) 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. // Clear the I/O conditioning constants.
mmc->regs->vendor_clock_cntrl &= ~(MMC_CLOCK_TRIM_MASK | MMC_CLOCK_TAP_MASK); mmc->regs->vendor_clock_cntrl &= ~(MMC_CLOCK_TRIM_MASK | MMC_CLOCK_TAP_MASK);
mmc->regs->auto_cal_config &= ~MMC_AUTOCAL_PDPU_CONFIG_MASK; mmc->regs->auto_cal_config &= ~MMC_AUTOCAL_PDPU_CONFIG_MASK;
@ -871,11 +920,159 @@ static int sdmmc1_switch_to_low_voltage(struct mmc *mmc)
*/ */
static int sdmmc_always_fail(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; 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;
}
/**
* 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.
*/
static int sdmmc_tune_clock(struct mmc *mmc)
{
// FIXME: implement
mmc_print(mmc, "ERROR: can't use a mode that requires tuning, currently!");
return ENOSYS;
}
/**
* 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;
// 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, CLK_SOURCE_FIRST, MMC_CLOCK_DIVIDER_SDR12, MMC_CLOCK_CONTROL_FREQUENCY_INIT);
break;
// 25MHz default speed
case SDMMC_SPEED_SDR12:
mmc->configure_clock(mmc, CLK_SOURCE_FIRST, MMC_CLOCK_DIVIDER_SDR12, MMC_CLOCK_CONTROL_FREQUENCY_PASSTHROUGH);
break;
// 50MHz high speed
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);
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);
// Execute tuning.
rc = sdmmc_tune_clock(mmc);
if (rc) {
mmc_print(mmc, "ERROR: tuning failed! (%d)", rc);
return rc;
}
break;
// 200MHz UHS-I
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);
// Execute tuning.
rc = sdmmc_tune_clock(mmc);
if (rc) {
mmc_print(mmc, "ERROR: tuning failed! (%d)", rc);
return rc;
}
break;
default:
mmc_print(mmc, "ERROR: switching to unsupported speed!\n");
return ENOSYS;
}
// 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. * Performs low-level initialization for SDMMC1, used for the SD card slot.
*/ */
@ -905,19 +1102,17 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc)
padctl->vgpio_gpio_mux_sel &= ~PADCTL_SDMMC1_CD_SOURCE; padctl->vgpio_gpio_mux_sel &= ~PADCTL_SDMMC1_CD_SOURCE;
// Put SDMMC1 in reset // 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 (div by 17). // Configure the clock to place the device into the initial mode.
// This is the datasheet recommended value for SDR12.
// TODO: abstract this into a clock source specification function!
car->clk_src[CLK_SOURCE_SDMMC1] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12; car->clk_src[CLK_SOURCE_SDMMC1] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
// Set the legacy divier used for detecting timeouts. // Set the legacy divier used for detecting timeouts.
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12; car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | MMC_CLOCK_DIVIDER_SDR12;
// Set SDMMC1 clock enable // Set SDMMC1 clock enable
car->clk_enb_l_set |= CAR_CONTROL_SDMMC1; car->clk_enb_l_set = CAR_CONTROL_SDMMC1;
car->clk_enb_y_set |= CAR_CONTROL_SDMMC_LEGACY; car->clk_enb_y_set = CAR_CONTROL_SDMMC_LEGACY;
// host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles // host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles
udelay(5000); udelay(5000);
@ -932,52 +1127,6 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc)
} }
/**
* 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)
{
// TODO: also set the clock for the main divider
// 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->clock_control &= ~MMC_CLOCK_CONTROL_FREQUENCY_MASK;
mmc->regs->host_control &= ~MMC_HOST_ENABLE_HIGH_SPEED;
mmc->regs->host_control2 &= ~MMC_HOST2_DRIVE_STRENGTH_MASK;
// Apply the dividers according to the speed provided.
switch (speed) {
// 400kHz initialization mode.
case SDMMC_SPEED_INIT:
mmc->regs->clock_control |= MMC_CLOCK_CONTROL_FREQUENCY_INIT;
break;
// 25MHz default speed
case SDMMC_SPEED_SDR12:
break;
default:
mmc_print(mmc, "ERROR: switching to unsupported speed!\n");
return ENOSYS;
}
// Re-enable the clock, if necessary.
if (enable_after) {
sdmmc_clock_enable(mmc, true);
udelay(MMC_POST_CLOCK_DELAY);
}
return 0;
}
/** /**
* Initialize the low-level SDMMC hardware. * Initialize the low-level SDMMC hardware.
@ -1095,8 +1244,11 @@ static int sdmmc_hardware_init(struct mmc *mmc)
regs->host_control2 &= ~(0x08); regs->host_control2 &= ~(0x08);
// Set up the card's initialization. // Set up the card's initialization.
sdmmc_apply_clock_speed(mmc, SDMMC_SPEED_INIT, true); 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; return 0;
} }
@ -2066,14 +2218,137 @@ static int sdmmc_optimize_transfer_mode(struct mmc *mmc)
// which is meant to keep us within the pre-init operating frequency of 400kHz. // 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 // We're now set up, so we can drop the divider. From this point forward, the
// clock is now driven by the CAR frequency. // clock is now driven by the CAR frequency.
sdmmc_apply_clock_speed(mmc, SDMMC_SPEED_SDR12, true); 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!");
// 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; return 0;
} }
/**
* Switches the active SD card and host controller to the given sppeed 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 mode 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);
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) {
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;
}
/**
* 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;
// 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_sd_switch_bus_speed(mmc, SDMMC_SPEED_SDR104, &function_status))
return 0;
if (function_status.sdr50_support && !sdmmc_sd_switch_bus_speed(mmc, SDMMC_SPEED_SDR50, &function_status))
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);
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)
{
// TODO
return 0;
}
/**
* 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
return 0;
}
/** /**
* Requests that an MMC target use the card's current relative address. * Requests that an MMC target use the card's current relative address.
* *
@ -2499,7 +2774,8 @@ bool sdmmc_external_card_present(struct mmc *mmc)
* *
* @param mmc The controller to use. * @param mmc The controller to use.
* @param mode The mode flag -- one to set function data, zero to query. * @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. * @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) static int sdmmc_sd_switch_mode(struct mmc *mmc, int mode, int group, int value, uint32_t timeout, void *response)
{ {
@ -2513,10 +2789,14 @@ static int sdmmc_sd_switch_mode(struct mmc *mmc, int mode, int group, int value,
const int num_blocks = 1; const int num_blocks = 1;
// Build the argument we're going to issue. // Build the argument we're going to issue.
const int group_shift = group * SDMMC_SWITCH_MODE_GROUP_SIZE_BITS;
uint32_t argument = (mode << SDMMC_SWITCH_MODE_MODE_SHIFT) | SDMMC_SWITCH_MODE_ALL_FUNCTIONS_UNUSED; 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 &= ~(SDMMC_SWITCH_MODE_FUNCTION_MASK << group_shift);
argument |= value << group_shift; argument |= value << group_shift;
}
// Momentarily step down to a smaller block size, so we don't // Momentarily step down to a smaller block size, so we don't
// have to allocate a huge buffer for this command. // have to allocate a huge buffer for this command.
@ -2545,18 +2825,6 @@ static int sdmmc_sd_switch_mode(struct mmc *mmc, int mode, int group, int value,
} }
/**
* Switches the host controller and SD card to high speed mode.
*/
/*
static int sdmmc_sd_switch_to_high_speed(struct mmc *mmc)
{
return ENOSYS;
}
*/
/** /**
* Switches a given SDMMC Controller where * Switches a given SDMMC Controller where
*/ */
@ -2575,6 +2843,13 @@ static void sdmmc_apply_card_type(struct mmc *mmc, enum sdmmc_card_type type)
mmc->establish_relative_address = sdmmc_set_relative_address; mmc->establish_relative_address = sdmmc_set_relative_address;
mmc->switch_mode = sdmmc_mmc_switch_mode; mmc->switch_mode = sdmmc_mmc_switch_mode;
mmc->switch_bus_width = sdmmc_mmc_switch_bus_width; 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->optimize_speed = sdmmc_mmc_optimize_speed;
break; break;
// SD-protocol cards // SD-protocol cards
@ -2583,6 +2858,7 @@ static void sdmmc_apply_card_type(struct mmc *mmc, enum sdmmc_card_type type)
mmc->establish_relative_address = sdmmc_get_relative_address; mmc->establish_relative_address = sdmmc_get_relative_address;
mmc->switch_mode = sdmmc_sd_switch_mode; mmc->switch_mode = sdmmc_sd_switch_mode;
mmc->switch_bus_width = sdmmc_sd_switch_bus_width; mmc->switch_bus_width = sdmmc_sd_switch_bus_width;
mmc->optimize_speed = sdmmc_sd_optimize_speed;
break; break;
// Switch-cart protocol cards // Switch-cart protocol cards
@ -2612,6 +2888,7 @@ static int sdmmc_initialize_defaults(struct mmc *mmc)
mmc->enable_supplies = sdmmc4_enable_supplies; mmc->enable_supplies = sdmmc4_enable_supplies;
mmc->switch_to_low_voltage = sdmmc_always_fail; mmc->switch_to_low_voltage = sdmmc_always_fail;
mmc->card_present = sdmmc_builtin_card_present; mmc->card_present = sdmmc_builtin_card_present;
mmc->configure_clock = sdmmc4_configure_clock;
// The EMMC controller always uses an EMMC card. // The EMMC controller always uses an EMMC card.
sdmmc_apply_card_type(mmc, MMC_CARD_EMMC); sdmmc_apply_card_type(mmc, MMC_CARD_EMMC);
@ -2627,12 +2904,15 @@ static int sdmmc_initialize_defaults(struct mmc *mmc)
mmc->operating_voltage = MMC_VOLTAGE_3V3; mmc->operating_voltage = MMC_VOLTAGE_3V3;
mmc->card_detect_gpio = GPIO_MICROSD_CARD_DETECT; mmc->card_detect_gpio = GPIO_MICROSD_CARD_DETECT;
// For the microSD card slot, assume we have an SD-type card. // Per-instance functions.
// Negotiation has a chance to change this, later.
mmc->set_up_clock_and_io = sdmmc1_set_up_clock_and_io; mmc->set_up_clock_and_io = sdmmc1_set_up_clock_and_io;
mmc->enable_supplies = sdmmc1_enable_supplies; mmc->enable_supplies = sdmmc1_enable_supplies;
mmc->switch_to_low_voltage = sdmmc1_switch_to_low_voltage; mmc->switch_to_low_voltage = sdmmc1_switch_to_low_voltage;
mmc->card_present = sdmmc_external_card_present; 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); sdmmc_apply_card_type(mmc, MMC_CARD_SD);
// Start off assuming byte addressing; we'll detect and correct this // Start off assuming byte addressing; we'll detect and correct this

View file

@ -126,6 +126,21 @@ enum sdmmc_switch_field {
}; };
/**
* Bus speeds possible for an SDMMC controller.
*/
enum sdmmc_bus_speed {
SDMMC_SPEED_SDR12 = 0,
SDMMC_SPEED_SDR25 = 1,
SDMMC_SPEED_SDR50 = 2,
SDMMC_SPEED_SDR104 = 3,
SDMMC_SPEED_DDR50 = 4,
SDMMC_SPEED_INIT = -1,
};
/** /**
* Primary data structure describing a Fusée MMC driver. * Primary data structure describing a Fusée MMC driver.
*/ */
@ -142,6 +157,7 @@ struct mmc {
/* Per-controller operations. */ /* Per-controller operations. */
int (*set_up_clock_and_io)(struct mmc *mmc); 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 (*enable_supplies)(struct mmc *mmc);
int (*switch_to_low_voltage)(struct mmc *mmc); int (*switch_to_low_voltage)(struct mmc *mmc);
bool (*card_present)(struct mmc *mmc); bool (*card_present)(struct mmc *mmc);
@ -151,7 +167,7 @@ struct mmc {
int (*establish_relative_address)(struct mmc *mmc); int (*establish_relative_address)(struct mmc *mmc);
int (*switch_mode)(struct mmc *mmc, int a, int b, int c, uint32_t timeout, void *response); 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 (*switch_bus_width)(struct mmc *mmc, enum sdmmc_bus_width width);
int (*switch_to_high_speed)(struct mmc *mmc); int (*optimize_speed)(struct mmc *mmc);
/* Card properties */ /* Card properties */
uint8_t cid[15]; uint8_t cid[15];
@ -160,6 +176,7 @@ struct mmc {
enum sdmmc_spec_version spec_version; enum sdmmc_spec_version spec_version;
enum sdmmc_bus_width max_bus_width; enum sdmmc_bus_width max_bus_width;
enum sdmmc_bus_voltage operating_voltage; enum sdmmc_bus_voltage operating_voltage;
enum sdmmc_bus_speed operating_speed;
uint8_t partition_support; uint8_t partition_support;
uint8_t partition_config; uint8_t partition_config;