1
0
Fork 0
mirror of https://github.com/Atmosphere-NX/Atmosphere.git synced 2024-12-23 02:42:09 +00:00

fusee: support low-voltage SDMMC mode

This commit is contained in:
Kate J. Temkin 2018-05-10 06:05:00 -07:00
parent b20a04ede5
commit 437344fd25
4 changed files with 415 additions and 146 deletions

View file

@ -187,6 +187,9 @@ enum sdmmc_register_bits {
MMC_STATUS_ERROR_MASK = (0xF << 16), MMC_STATUS_ERROR_MASK = (0xF << 16),
/* Clock control */
MMC_CLOCK_CONTROL_CARD_CLOCK_ENABLE = (1 << 2),
/* Host control */ /* Host control */
MMC_DMA_SELECT_MASK = (0x3 << 3), MMC_DMA_SELECT_MASK = (0x3 << 3),
MMC_DMA_SELECT_SDMA = (0x0 << 3), MMC_DMA_SELECT_SDMA = (0x0 << 3),
@ -211,7 +214,16 @@ enum sdmmc_register_bits {
MMC_AUTOCAL_PDPU_SDMMC1_1V8 = 0x7b7b, MMC_AUTOCAL_PDPU_SDMMC1_1V8 = 0x7b7b,
MMC_AUTOCAL_PDPU_SDMMC1_3V3 = 0x7d00, MMC_AUTOCAL_PDPU_SDMMC1_3V3 = 0x7d00,
MMC_AUTOCAL_PDPU_SDMMC4_1V8 = 0x0505, MMC_AUTOCAL_PDPU_SDMMC4_1V8 = 0x0505,
MMC_AUTOCAL_START = (1 << 31),
MMC_AUTOCAL_ENABLE = (1 << 29),
/* Autocal status */
MMC_AUTOCAL_ACTIVE = (1 << 31),
/* Power control */
MMC_POWER_CONTROL_VOLTAGE_MASK = (0x3 << 1),
MMC_POWER_CONTROL_VOLTAGE_SHIFT = 1,
MMC_POWER_CONTROL_POWER_ENABLE = (1 << 0),
}; };
@ -234,6 +246,7 @@ enum sdmmc_command {
CMD_SEND_IF_COND = 8, CMD_SEND_IF_COND = 8,
CMD_SEND_CSD = 9, CMD_SEND_CSD = 9,
CMD_SEND_CID = 10, CMD_SEND_CID = 10,
CMD_SWITCH_TO_LOW_VOLTAGE = 11,
CMD_STOP_TRANSMISSION = 12, CMD_STOP_TRANSMISSION = 12,
CMD_READ_STATUS = 13, CMD_READ_STATUS = 13,
CMD_BUS_TEST = 14, CMD_BUS_TEST = 14,
@ -246,6 +259,7 @@ enum sdmmc_command {
CMD_APP_SEND_OP_COND = 41, CMD_APP_SEND_OP_COND = 41,
CMD_APP_SET_CARD_DETECT = 42, CMD_APP_SET_CARD_DETECT = 42,
CMD_APP_SEND_SCR = 51,
CMD_APP_COMMAND = 55, CMD_APP_COMMAND = 55,
}; };
@ -265,7 +279,7 @@ static const char *sdmmc_command_string[] = {
"CMD_SEND_EXT_CSD/CMD_SEND_IF_COND", "CMD_SEND_EXT_CSD/CMD_SEND_IF_COND",
"CMD_SEND_CSD", "CMD_SEND_CSD",
"CMD_SEND_CID ", "CMD_SEND_CID ",
"<unsupported>", "CMD_SWITCH_TO_LOW_VOLTAGE",
"CMD_STOP_TRANSMISSION", "CMD_STOP_TRANSMISSION",
"CMD_READ_STATUS", "CMD_READ_STATUS",
"CMD_BUS_TEST", "CMD_BUS_TEST",
@ -292,6 +306,7 @@ enum sdmmc_command_magic {
MMC_SD_OPERATING_COND_READY = (1 << 31), MMC_SD_OPERATING_COND_READY = (1 << 31),
MMC_SD_OPERATING_COND_HIGH_CAPACITY = (1 << 30), MMC_SD_OPERATING_COND_HIGH_CAPACITY = (1 << 30),
MMC_SD_OPERATING_COND_ACCEPTS_1V8 = (1 << 24),
MMC_SD_OPERATING_COND_ACCEPTS_3V3 = (1 << 20), MMC_SD_OPERATING_COND_ACCEPTS_3V3 = (1 << 20),
/* READ_STATUS responses */ /* READ_STATUS responses */
@ -303,6 +318,9 @@ enum sdmmc_command_magic {
/* IF_COND components */ /* IF_COND components */
MMC_IF_VOLTAGE_3V3 = (1 << 8), MMC_IF_VOLTAGE_3V3 = (1 << 8),
MMC_IF_CHECK_PATTERN = 0xAA, MMC_IF_CHECK_PATTERN = 0xAA,
/* Misc constants */
MMC_DEFAULT_BLOCK_ORDER = 9,
}; };
@ -315,7 +333,6 @@ enum sdmmc_csd_versions {
}; };
/** /**
* Positions of different fields in various CSDs. * Positions of different fields in various CSDs.
* May eventually be replaced with a bitfield struct, if we use enough of the CSDs. * May eventually be replaced with a bitfield struct, if we use enough of the CSDs.
@ -332,6 +349,7 @@ enum sdmmc_csd_extents {
}; };
/** /**
* Positions of the different fields in the Extended CSD. * Positions of the different fields in the Extended CSD.
*/ */
@ -360,6 +378,25 @@ enum sdmmc_ext_csd_extents {
}; };
/**
* Bitfield struct representing an SD SCR.
*/
struct PACKED sdmmc_scr {
uint32_t reserved1;
uint16_t reserved0;
uint8_t supports_width_1bit : 1;
uint8_t supports_width_reserved0 : 1;
uint8_t supports_width_4bit : 1;
uint8_t supports_width_reserved1 : 1;
uint8_t security_support : 3;
uint8_t data_after_erase : 1;
uint8_t spec_version : 4;
uint8_t scr_version : 4;
};
/* 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);
/* SDMMC debug enable */ /* SDMMC debug enable */
static int sdmmc_loglevel = 0; static int sdmmc_loglevel = 0;
@ -438,6 +475,8 @@ static const char *sdmmc_get_command_string(enum sdmmc_command command)
return "CMD_APP_SEND_OP_COND"; return "CMD_APP_SEND_OP_COND";
case CMD_APP_SET_CARD_DETECT: case CMD_APP_SET_CARD_DETECT:
return "CMD_APP_SET_CARD_DETECT"; return "CMD_APP_SET_CARD_DETECT";
case CMD_APP_SEND_SCR:
return "CMD_APP_SEND_SCR";
case CMD_WRITE_SINGLE_BLOCK: case CMD_WRITE_SINGLE_BLOCK:
return "CMD_WRITE_SINGLE_BLOCK"; return "CMD_WRITE_SINGLE_BLOCK";
case CMD_WRITE_MULTIPLE_BLOCK: case CMD_WRITE_MULTIPLE_BLOCK:
@ -545,6 +584,25 @@ static int sdmmc4_set_up_clock_and_io(struct mmc *mmc)
return 0; return 0;
} }
/**
* Sets the voltage that the given SDMMC is currently working with.
*
* @param mmc The controller to affect.
* @param voltage The voltage to apply.
*/
static void sdmmc_set_working_voltage(struct mmc *mmc, enum sdmmc_bus_voltage voltage)
{
// Apply the voltage...
mmc->operating_voltage = voltage;
// Set up the SD card's voltage.
mmc->regs->power_control &= ~MMC_POWER_CONTROL_VOLTAGE_MASK;
mmc->regs->power_control |= voltage << MMC_POWER_CONTROL_VOLTAGE_SHIFT;
// Mark the power as on.
mmc->regs->power_control |= MMC_POWER_CONTROL_POWER_ENABLE;
}
/** /**
* Enables power supplies for SDMMC4, used for eMMC. * Enables power supplies for SDMMC4, used for eMMC.
@ -552,6 +610,9 @@ static int sdmmc4_set_up_clock_and_io(struct mmc *mmc)
static int sdmmc4_enable_supplies(struct mmc *mmc) static int sdmmc4_enable_supplies(struct mmc *mmc)
{ {
// As a booot device, SDMMC4's power supply is always on. // As a booot device, SDMMC4's power supply is always on.
// Modify the controller to know the voltage being applied to it,
// and return success.
sdmmc_set_working_voltage(mmc, MMC_VOLTAGE_1V8);
return 0; return 0;
} }
@ -571,6 +632,14 @@ static int sdmmc1_enable_supplies(struct mmc *mmc)
pmc->no_iopower &= ~PMC_CONTROL_SDMMC1; pmc->no_iopower &= ~PMC_CONTROL_SDMMC1;
pmc->pwr_det_val |= PMC_CONTROL_SDMMC1; pmc->pwr_det_val |= PMC_CONTROL_SDMMC1;
// Set up SD card voltages.
udelay(1000);
supply_enable(SUPPLY_MICROSD, false);
udelay(1000);
// Modify the controller to know the voltage being applied to it.
sdmmc_set_working_voltage(mmc, MMC_VOLTAGE_3V3);
// Configure the enable line for the SD card power. // Configure the enable line for the SD card power.
pinmux->dmic3_clk = PINMUX_SELECT_FUNCTION0; pinmux->dmic3_clk = PINMUX_SELECT_FUNCTION0;
gpio_configure_mode(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_MODE_GPIO); gpio_configure_mode(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_MODE_GPIO);
@ -582,6 +651,170 @@ static int sdmmc1_enable_supplies(struct mmc *mmc)
} }
/**
* Configures clocking parameters for a given controller.
*
* @param mmc The MMC controller to set up.
* @param operating_voltage The operating voltage for the bus, currently.
*/
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;
switch (operating_voltage) {
case MMC_VOLTAGE_1V8:
mmc->regs->auto_cal_config |= MMC_AUTOCAL_PDPU_SDMMC4_1V8;
break;
case MMC_VOLTAGE_3V3:
mmc->regs->auto_cal_config |= MMC_AUTOCAL_PDPU_SDMMC1_3V3;
break;
default:
printk("ERROR: currently no controllers support voltage %d", mmc->operating_voltage);
return EINVAL;
}
// Set up the I/O conditioning constants used to ensure we have a reliable clock.
// Constants above and procedure below from the TRM.
switch (mmc->controller) {
case SWITCH_EMMC:
mmc->regs->vendor_clock_cntrl |= (MMC_CLOCK_TRIM_SDMMC4 | MMC_CLOCK_TAP_SDMMC4);
break;
case SWITCH_MICROSD:
mmc->regs->vendor_clock_cntrl |= (MMC_CLOCK_TRIM_SDMMC1 | MMC_CLOCK_TAP_SDMMC1);
break;
default:
printk("ERROR: initialization not yet writen for SDMMC%d", mmc->controller);
return ENODEV;
}
return 0;
}
/**
* Enables or disables delivering a clock to the downstream SD/MMC card.
*
* @param mmc The controller to be affected.
* @param enabled True if the clock should be enabled; false to disable.
*/
void sdmmc_clock_enable(struct mmc *mmc, bool enabled)
{
// Set or clear the card clock enable bit according to the
// controller paramter.
if (enabled)
mmc->regs->clock_control |= MMC_CLOCK_CONTROL_CARD_CLOCK_ENABLE;
else
mmc->regs->clock_control &= ~MMC_CLOCK_CONTROL_CARD_CLOCK_ENABLE;
}
/**
* Runs SDMMC automatic calibration-- this tunes the parameters used for SDMMC
* signal intergrity.
*
* @param mmc The controller whose card is to be tuned.
* @param restart_sd_clock True iff the SD card should be started after calibration.
*
* @return 0 on success, or an error code on failure
*/
static int sdmmc_run_autocal(struct mmc *mmc, bool restart_sd_clock)
{
uint32_t timebase;
// Stop the SD card's clock, so our autocal sequence doesn't
// confuse the target card.
sdmmc_clock_enable(mmc, false);
// Start automatic calibration...
mmc->regs->auto_cal_config |= (MMC_AUTOCAL_START | MMC_AUTOCAL_ENABLE);
udelay(1000);
// ... and wait until the autocal is complete
timebase = get_time();
while ((mmc->regs->auto_cal_status & MMC_AUTOCAL_ACTIVE)) {
// Ensure we haven't timed out...
if (get_time_since(timebase) > mmc->timeout) {
mmc_print(mmc, "ERROR: autocal timed out!");
return ETIMEDOUT;
}
}
// If requested, enable the SD clock.
if (restart_sd_clock)
sdmmc_clock_enable(mmc, true);
return 0;
}
/**
* Switches the Switch's microSD card into low-voltage mode.
*
* @param mmc The MMC controller via which to communicate.
* @return 0 on success, or an error code on failure.
*/
static int sdmmc1_switch_to_low_voltage(struct mmc *mmc)
{
volatile struct tegra_pmc *pmc = pmc_get_regs();
int rc;
// Let the SD card know we're about to switch into low-voltage mode.
// Set up the card's relative address.
rc = sdmmc_send_simple_command(mmc, CMD_SWITCH_TO_LOW_VOLTAGE, MMC_RESPONSE_LEN48, 0, NULL);
if (rc) {
mmc_print(mmc, "card was not willling to switch to low voltage! (%d)", rc);
return rc;
}
// Switch the MicroSD card supply into its low-voltage mode.
supply_enable(SUPPLY_MICROSD, true);
pmc->pwr_det_val &= ~PMC_CONTROL_SDMMC1;
// Apply our clocking parameters for low-voltage mode.
rc = sdmmc_set_up_clocking_parameters(mmc, MMC_VOLTAGE_1V8);
if (rc) {
mmc_print(mmc, "WARNING: could not optimize card clocking parameters. (%d)", rc);
}
// Rerun the main clock calibration...
rc = sdmmc_run_autocal(mmc, true);
if (rc)
mmc_print(mmc, "WARNING: failed to re-calibrate after voltage switch!");
// ... and ensure the host is set up to apply the relevant change.
sdmmc_set_working_voltage(mmc, MMC_VOLTAGE_1V8);
mmc_debug(mmc, "now running from 1V8");
return 0;
}
/**
* Low-voltage switching method for controllers that don't
* support a low-voltage switch. Always fails.
*
* @param mmc The MMC controller via which to communicate.
* @return ENOSYS, indicating failure, always
*/
static int sdmmc_always_fail(struct mmc *mmc)
{
// This card
return ENOSYS;
}
/** /**
* Performs low-level initialization for SDMMC1, used for the SD card slot. * Performs low-level initialization for SDMMC1, used for the SD card slot.
*/ */
@ -624,11 +857,6 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc)
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;
// Set up SD card voltages.
udelay(1000);
supply_enable(SUPPLY_MICROSD);
udelay(1000);
// 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);
@ -642,45 +870,6 @@ static int sdmmc1_set_up_clock_and_io(struct mmc *mmc)
} }
/**
* Configures clocking parameters for a given controller.
*
* @param mmc The MMC controller to set up.
*/
static int sdmmc_set_up_clocking_parameters(struct mmc *mmc)
{
// 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;
// Set up the I/O conditioning constants used to ensure we have a reliable clock.
// Constants above and procedure below from the TRM.
switch (mmc->controller) {
case SWITCH_EMMC:
mmc->regs->vendor_clock_cntrl |= (MMC_CLOCK_TRIM_SDMMC4 | MMC_CLOCK_TAP_SDMMC4);
mmc->regs->auto_cal_config |= MMC_AUTOCAL_PDPU_SDMMC4_1V8;
break;
case SWITCH_MICROSD:
mmc->regs->vendor_clock_cntrl |= (MMC_CLOCK_TRIM_SDMMC1 | MMC_CLOCK_TAP_SDMMC1);
mmc->regs->auto_cal_config |= MMC_AUTOCAL_PDPU_SDMMC1_3V3;
break;
default:
printk("ERROR: initialization not yet writen for SDMMC%d", mmc->controller);
return ENODEV;
}
return 0;
}
/** /**
* Initialize the low-level SDMMC hardware. * Initialize the low-level SDMMC hardware.
* Thanks to hexkyz for this init code. * Thanks to hexkyz for this init code.
@ -728,41 +917,23 @@ static int sdmmc_hardware_init(struct mmc *mmc)
regs->sdmemcomppadctrl &= ~(0x0F); regs->sdmemcomppadctrl &= ~(0x0F);
regs->sdmemcomppadctrl |= 0x07; regs->sdmemcomppadctrl |= 0x07;
// Set auto-calibration PD/PU offsets
/*
*/
// Set ourselves up to have a stable. // Set ourselves up to have a stable.
rc = sdmmc_set_up_clocking_parameters(mmc); rc = sdmmc_set_up_clocking_parameters(mmc, mmc->operating_voltage);
if (rc) { if (rc) {
mmc_print(mmc, "WARNING: could not optimize card clocking parameters. (%d)", rc); mmc_print(mmc, "WARNING: could not optimize card clocking parameters. (%d)", rc);
} }
// Set PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only) // Set PAD_E_INPUT_OR_E_PWRD
regs->sdmemcomppadctrl |= 0x80000000; regs->sdmemcomppadctrl |= 0x80000000;
// Wait one milisecond // Wait one milisecond
udelay(1000); udelay(1000);
// Set AUTO_CAL_START and AUTO_CAL_ENABLE // Run automatic calibration.
regs->auto_cal_config |= 0xA0000000; rc = sdmmc_run_autocal(mmc, false);
if (rc) {
udelay(1000); mmc_print(mmc, "autocal failed! (%d)", rc);
return rc;
// Program a timeout of 10ms
is_timeout = false;
timebase = get_time();
// Wait for AUTO_CAL_ACTIVE to be cleared
while ((regs->auto_cal_status & 0x80000000) && !is_timeout) {
// Keep checking if timeout expired
is_timeout = get_time_since(timebase) > 10000;
}
// AUTO_CAL_ACTIVE was not cleared in time
if (is_timeout) {
mmc_print(mmc, "autocal failed!");
return ETIMEDOUT;
} }
// Clear PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only) // Clear PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only)
@ -804,12 +975,7 @@ static int sdmmc_hardware_init(struct mmc *mmc)
regs->host_control &= 0xFD; regs->host_control &= 0xFD;
regs->host_control &= 0xDF; regs->host_control &= 0xDF;
// Set up the SD card's voltage. // TODO: move me into enable voltages, if applicable?
regs->power_control &= 0xF1;
regs->power_control |= mmc->operating_voltage << 1;
// Mark the power as on.
regs->power_control |= 0x01;
// Clear TAP_VAL_UPDATED_BY_HW // Clear TAP_VAL_UPDATED_BY_HW
regs->vendor_tuning_cntrl0 &= ~(0x20000); regs->vendor_tuning_cntrl0 &= ~(0x20000);
@ -823,10 +989,10 @@ static int sdmmc_hardware_init(struct mmc *mmc)
// Set SDHCI_DIVIDER and SDHCI_DIVIDER_HI // Set SDHCI_DIVIDER and SDHCI_DIVIDER_HI
// FIXME: divider SD if necessary // FIXME: divider SD if necessary
regs->clock_control &= ~(0xFFC0); regs->clock_control &= ~(0xFFC0);
regs->clock_control |= (0x18 << 8); // 400kHz, initially regs->clock_control |= (0x18 << 8); // 200kHz, initially
// Set SDHCI_CLOCK_CARD_EN // Start delivering the clock to the card.
regs->clock_control |= 0x04; sdmmc_clock_enable(mmc, true);
// Ensure we're using Single-operation DMA (SDMA) mode for DMA. // Ensure we're using Single-operation DMA (SDMA) mode for DMA.
regs->host_control &= ~MMC_DMA_SELECT_MASK; regs->host_control &= ~MMC_DMA_SELECT_MASK;
@ -873,7 +1039,6 @@ static int sdmmc_wait_for_physical_state(struct mmc *mmc, uint32_t present_state
} }
/** /**
* Blocks until the SD driver is ready for a command, * Blocks until the SD driver is ready for a command,
* or the MMC controller's timeout interval is met. * or the MMC controller's timeout interval is met.
@ -886,7 +1051,6 @@ static int sdmmc_wait_for_command_readiness(struct mmc *mmc)
} }
/** /**
* Blocks until the SD driver is ready to transmit data, * Blocks until the SD driver is ready to transmit data,
* or the MMC controller's timeout interval is met. * or the MMC controller's timeout interval is met.
@ -911,7 +1075,6 @@ static int sdmmc_wait_until_no_longer_busy(struct mmc *mmc)
} }
/** /**
* Blocks until the SD driver has completed issuing a command. * Blocks until the SD driver has completed issuing a command.
* *
@ -946,8 +1109,6 @@ static int sdmmc_wait_for_interrupt(struct mmc *mmc,
} }
} }
/** /**
* Blocks until the SD driver has completed issuing a command. * Blocks until the SD driver has completed issuing a command.
* *
@ -970,6 +1131,21 @@ static int sdmmc_wait_for_transfer_completion(struct mmc *mmc)
} }
/**
* Returns the block order for a given operation on the MMC controller.
*
* @param mmc The MMC controller for which we're quierying block size.
* @param is_write True iff the given operation is a write.
*/
static uint8_t sdmmc_get_block_order(struct mmc *mmc, bool is_write)
{
if (is_write)
return mmc->write_block_order;
else
return mmc->read_block_order;
}
/** /**
* Returns the block size for a given operation on the MMC controller. * Returns the block size for a given operation on the MMC controller.
* *
@ -978,14 +1154,10 @@ static int sdmmc_wait_for_transfer_completion(struct mmc *mmc)
*/ */
static uint32_t sdmmc_get_block_size(struct mmc *mmc, bool is_write) static uint32_t sdmmc_get_block_size(struct mmc *mmc, bool is_write)
{ {
// FIXME: support write blocks? return (1 << sdmmc_get_block_order(mmc, is_write));
(void)is_write;
return (1 << mmc->read_block_order);
} }
/** /**
* Handles execution of a DATA stage using the CPU, rather than by using DMA. * Handles execution of a DATA stage using the CPU, rather than by using DMA.
* *
@ -1236,7 +1408,6 @@ static int sdmmc_handle_command_response(struct mmc *mmc,
} }
/** /**
* Sends a command to the SD card, and awaits a response. * Sends a command to the SD card, and awaits a response.
* *
@ -1363,7 +1534,6 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
} }
/** /**
* Convenience function that sends a simple SDMMC command * Convenience function that sends a simple SDMMC command
* and awaits response. Wrapper around sdmmc_send_command. * and awaits response. Wrapper around sdmmc_send_command.
@ -1399,9 +1569,10 @@ static int sdmmc_send_simple_command(struct mmc *mmc, enum sdmmc_command command
* *
* @returns 0 on success, an error number on failure * @returns 0 on success, an error number on failure
*/ */
static int sdmmc_send_simple_app_command(struct mmc *mmc, enum sdmmc_command command, static int sdmmc_send_app_command(struct mmc *mmc, enum sdmmc_command command,
enum sdmmc_response_type response_type, enum sdmmc_response_checks checks, enum sdmmc_response_type response_type, enum sdmmc_response_checks checks,
uint32_t argument, void *response_buffer) uint32_t argument, void *response_buffer, uint16_t blocks_to_transfer,
bool auto_terminate, void *data_buffer)
{ {
int rc; int rc;
@ -1413,11 +1584,30 @@ static int sdmmc_send_simple_app_command(struct mmc *mmc, enum sdmmc_command com
} }
// And issue the body of the command. // And issue the body of the command.
return sdmmc_send_command(mmc, command, response_type, checks, argument, response_buffer, 0, false, false, NULL); return sdmmc_send_command(mmc, command, response_type, checks, argument, response_buffer,
blocks_to_transfer, false, auto_terminate, data_buffer);
} }
/**
* Sends an SDMMC application command.
*
* @param mmc The SDMMC device to be used to transmit the command.
* @param response_type The type of response to expect-- mostly specifies the length.
* @param checks Determines which sanity checks the host controller should run.
* @param argument The argument to the SDMMC command.
* @param response_buffer A buffer to store the response. Should be at uint32_t for a LEN48 command,
* or 16 bytes for a LEN136 command.
*
* @returns 0 on success, an error number on failure
*/
static int sdmmc_send_simple_app_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)
{
// Deletegate to the full app command function.
return sdmmc_send_app_command(mmc, command, response_type, checks, argument, response_buffer, 0, false, NULL);
}
/** /**
@ -1490,6 +1680,79 @@ static int sdmmc_parse_csd_version1(struct mmc *mmc, uint32_t *csd)
} }
/**
* Decides on a block transfer sized based on the information observed,
* and applies it to the card.
*
* @param mmc The controller to use to set the order
* @param block_order The order (log-base-2) of the block size to be used.
*/
static int sdmmc_use_block_size(struct mmc *mmc, int block_order)
{
int rc;
// Inform the card of the block size we'll want to use.
rc = sdmmc_send_simple_command(mmc, CMD_SET_BLKLEN, MMC_RESPONSE_LEN48, 1 << block_order, NULL);
if (rc) {
mmc_print(mmc, "could not fetch the CID");
return ENODEV;
}
// On success, store the relevant block size.
mmc->read_block_order = block_order;
mmc->write_block_order = block_order;
return 0;
}
/**
* Reads the active SD card's SD Configuration Register, and updates the object's properties.
*
* @param mmc The controller with which to query and to update.
* @returns 0 on success, or an errno on failure
*/
static int sdmmc_read_and_parse_scr(struct mmc *mmc)
{
int rc;
struct sdmmc_scr scr;
// Read the current block order, so we can restore it.
int original_block_order = sdmmc_get_block_order(mmc, false);
// Always request a single 8-byte block.
const int block_order = 3;
const int num_blocks = 1;
// 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;
}
// 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);
return rc;
}
// Store the SCR data.
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;
}
return 0;
}
/** /**
* Reads the active MMC card's Card Specific Data, and updates the MMC object's properties. * Reads the active MMC card's Card Specific Data, and updates the MMC object's properties.
* *
@ -1562,30 +1825,6 @@ static int sdmmc_read_and_parse_ext_csd(struct mmc *mmc)
} }
/**
* Decides on a block transfer sized based on the information observed,
* and applies it to the card.
*/
static int sdmmc_set_up_block_transfer_size(struct mmc *mmc)
{
int rc;
// For now, we'll only ever set up 512B blocks, because
// 1) every card supports this, and 2) we use SDMA, which only supports up to 512B
mmc->read_block_order = 9;
// Inform the card of the block size we'll want to use.
rc = sdmmc_send_simple_command(mmc, CMD_SET_BLKLEN, MMC_RESPONSE_LEN48, 1 << mmc->read_block_order, NULL);
if (rc) {
mmc_print(mmc, "could not fetch the CID");
return ENODEV;
}
return 0;
}
/** /**
* Switches the SDMMC card and controller to the fullest bus width possible. * Switches the SDMMC card and controller to the fullest bus width possible.
* *
@ -1763,22 +2002,14 @@ static int sdmmc_card_init(struct mmc *mmc)
return rc; return rc;
} }
// Determine the block size we want to work with, and then set up the size accordingly. // Set up a block transfer size of 512B blocks.
rc = sdmmc_set_up_block_transfer_size(mmc); // 1) every card supports this, and 2) we use SDMA, which only supports up to 512B
rc = sdmmc_use_block_size(mmc, MMC_DEFAULT_BLOCK_ORDER);
if (rc) { if (rc) {
mmc_print(mmc, "could not set up block transfer sizes! (%d)", rc); mmc_print(mmc, "could not set up block transfer sizes! (%d)", rc);
return rc; return rc;
} }
// Switch to a transfer mode that can more efficiently utilize the bus.
rc = sdmmc_optimize_transfer_mode(mmc);
if (rc) {
mmc_print(mmc, "could not optimize bus utlization! (%d)", rc);
// TODO: possibly skip this?
return rc;
}
return 0; return 0;
} }
@ -1834,13 +2065,16 @@ static int sdmmc_sd_wait_for_card_readiness(struct mmc *mmc, uint32_t *response)
int rc; int rc;
uint32_t argument = MMC_SD_OPERATING_COND_ACCEPTS_3V3; uint32_t argument = MMC_SD_OPERATING_COND_ACCEPTS_3V3;
// If this is a SDv2 or higher card, check for an SDHC card. // If this is a SDv2 or higher card, check for an SDHC card,
if (mmc->spec_version >= SD_VERSION_2) { // and for low-voltage support.
if (mmc->spec_version >= SD_VERSION_2_0) {
argument |= MMC_SD_OPERATING_COND_HIGH_CAPACITY; argument |= MMC_SD_OPERATING_COND_HIGH_CAPACITY;
argument |= MMC_SD_OPERATING_COND_ACCEPTS_1V8;
} }
while (true) { while (true) {
// Ask the SD card to identify its state. It will respond with readiness and a capacity magic.
// Ask the SD card to identify its state.
rc = sdmmc_send_simple_app_command(mmc, CMD_APP_SEND_OP_COND, rc = sdmmc_send_simple_app_command(mmc, CMD_APP_SEND_OP_COND,
MMC_RESPONSE_LEN48, MMC_CHECKS_NONE, argument, response); MMC_RESPONSE_LEN48, MMC_CHECKS_NONE, argument, response);
if (rc) { if (rc) {
@ -1913,7 +2147,6 @@ static bool sdmmc_check_pattern_present(uint32_t response)
} }
/** /**
* Handles SD-specific card initialization. * Handles SD-specific card initialization.
*/ */
@ -1931,8 +2164,6 @@ static int sdmmc_sd_card_init(struct mmc *mmc)
return rc; return rc;
} }
udelay(1000);
// Validate that the card can handle working with the voltages we can provide. // Validate that the card can handle working with the voltages we can provide.
rc = sdmmc_send_simple_command(mmc, CMD_SEND_IF_COND, MMC_RESPONSE_LEN48, MMC_IF_VOLTAGE_3V3 | MMC_IF_CHECK_PATTERN, &response); rc = sdmmc_send_simple_command(mmc, CMD_SEND_IF_COND, MMC_RESPONSE_LEN48, MMC_IF_VOLTAGE_3V3 | MMC_IF_CHECK_PATTERN, &response);
if (rc || !sdmmc_check_pattern_present(response)) { if (rc || !sdmmc_check_pattern_present(response)) {
@ -1947,7 +2178,7 @@ static int sdmmc_sd_card_init(struct mmc *mmc)
// If this responded, indicate that this is a v2 card. // If this responded, indicate that this is a v2 card.
else { else {
// store that this is a v2 card // store that this is a v2 card
mmc->spec_version = SD_VERSION_2; mmc->spec_version = SD_VERSION_2_0;
} }
// Wait for the card to finish being busy. // Wait for the card to finish being busy.
@ -1961,6 +2192,15 @@ static int sdmmc_sd_card_init(struct mmc *mmc)
// always use block addressing. // always use block addressing.
mmc->uses_block_addressing = !!(ocr & MMC_SD_OPERATING_COND_HIGH_CAPACITY); 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->operating_voltage != MMC_VOLTAGE_1V8) {
rc = mmc->switch_to_low_voltage(mmc);
if (rc)
mmc_print(mmc, "WARNING: could not switch to low-voltage mode! (%d)", rc);
}
}
// Run the common core card initialization. // Run the common core card initialization.
rc = sdmmc_card_init(mmc); rc = sdmmc_card_init(mmc);
if (rc) { if (rc) {
@ -1968,6 +2208,13 @@ static int sdmmc_sd_card_init(struct mmc *mmc)
return rc; return rc;
} }
// Read the card's SCR.
rc = sdmmc_read_and_parse_scr(mmc);
if (rc) {
mmc_print(mmc, "failed to read SCR! (%d)!", rc);
return rc;
}
return 0; return 0;
} }
@ -2159,6 +2406,7 @@ static int sdmmc_initialize_defaults(struct mmc *mmc)
// Set up function pointers for each of our per-instance functions. // Set up function pointers for each of our per-instance functions.
mmc->set_up_clock_and_io = sdmmc4_set_up_clock_and_io; mmc->set_up_clock_and_io = sdmmc4_set_up_clock_and_io;
mmc->enable_supplies = sdmmc4_enable_supplies; mmc->enable_supplies = sdmmc4_enable_supplies;
mmc->switch_to_low_voltage = sdmmc_always_fail;
mmc->card_present = sdmmc_builtin_card_present; mmc->card_present = sdmmc_builtin_card_present;
// The EMMC controller always uses an EMMC card. // The EMMC controller always uses an EMMC card.
@ -2179,6 +2427,7 @@ static int sdmmc_initialize_defaults(struct mmc *mmc)
// Negotiation has a chance to change this, later. // 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->card_present = sdmmc_external_card_present; mmc->card_present = sdmmc_external_card_present;
sdmmc_apply_card_type(mmc, MMC_CARD_SD); sdmmc_apply_card_type(mmc, MMC_CARD_SD);
@ -2198,7 +2447,6 @@ static int sdmmc_initialize_defaults(struct mmc *mmc)
/** /**
* Set up a new SDMMC driver. * Set up a new SDMMC driver.
* FIXME: clean up!
* *
* @param mmc The SDMMC structure to be initiailized with the device state. * @param mmc The SDMMC structure to be initiailized with the device state.
* @param controler The controller description to be used; usually SWITCH_EMMC * @param controler The controller description to be used; usually SWITCH_EMMC
@ -2253,6 +2501,12 @@ int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller)
return rc; return rc;
} }
// Switch to a transfer mode that can more efficiently utilize the bus.
rc = sdmmc_optimize_transfer_mode(mmc);
if (rc) {
mmc_print(mmc, "WARNING: could not optimize bus utlization! (%d)", rc);
}
return 0; return 0;
} }
@ -2366,7 +2620,7 @@ int sdmmc_write(struct mmc *mmc, const void *buffer, uint32_t block, unsigned in
// If this card uses byte addressing rather than sector addressing, // If this card uses byte addressing rather than sector addressing,
// multiply by the block size. // multiply by the block size.
if (!mmc->uses_block_addressing) { if (!mmc->uses_block_addressing) {
extent *= sdmmc_get_block_size(mmc, false); extent *= sdmmc_get_block_size(mmc, true);
} }
// Execute the relevant read. // Execute the relevant read.

View file

@ -61,8 +61,9 @@ enum sdmmc_spec_version {
MMC_VERSION_4 = 0, MMC_VERSION_4 = 0,
/* SD card versions */ /* SD card versions */
SD_VERSION_1 = 1, SD_VERSION_1_0 = 0,
SD_VERSION_2 = 2, SD_VERSION_1_1 = 1,
SD_VERSION_2_0 = 2,
}; };
@ -143,6 +144,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);
int (*enable_supplies)(struct mmc *mmc); int (*enable_supplies)(struct mmc *mmc);
int (*switch_to_low_voltage)(struct mmc *mmc);
bool (*card_present)(struct mmc *mmc); bool (*card_present)(struct mmc *mmc);
/* Per-card-type operations */ /* Per-card-type operations */
@ -166,6 +168,7 @@ struct mmc {
uint32_t partition_switch_time; uint32_t partition_switch_time;
uint8_t read_block_order; uint8_t read_block_order;
uint8_t write_block_order;
bool uses_block_addressing; bool uses_block_addressing;
/* Pointers to hardware structures */ /* Pointers to hardware structures */

View file

@ -13,12 +13,19 @@
* Enables a given power supply. * Enables a given power supply.
* *
* @param supply The power domain on the Switch that is to be enabled. * @param supply The power domain on the Switch that is to be enabled.
* @param use_low_voltage If the supply supports multiple voltages, use the lower one.
* Some devices start in a high power mode, but an can be switched to a lower one.
* Set this to false unless you know what you're doing.
*/ */
void supply_enable(enum switch_power_supply supply) void supply_enable(enum switch_power_supply supply, bool use_low_voltage)
{ {
uint32_t voltage = 0;
switch(supply) { switch(supply) {
case SUPPLY_MICROSD: case SUPPLY_MICROSD:
max77620_regulator_set_voltage(SUPPLY_MICROSD_REGULATOR, SUPPLY_MICROSD_VOLTAGE); voltage = use_low_voltage ? SUPPLY_MICROSD_LOW_VOLTAGE : SUPPLY_MICROSD_VOLTAGE;
max77620_regulator_set_voltage(SUPPLY_MICROSD_REGULATOR, voltage);
max77620_regulator_enable(SUPPLY_MICROSD_REGULATOR, true); max77620_regulator_enable(SUPPLY_MICROSD_REGULATOR, true);
return; return;

View file

@ -18,14 +18,19 @@ enum switch_power_constants {
/* MicroSD card */ /* MicroSD card */
SUPPLY_MICROSD_REGULATOR = 6, SUPPLY_MICROSD_REGULATOR = 6,
SUPPLY_MICROSD_VOLTAGE = 3300000, SUPPLY_MICROSD_VOLTAGE = 3300000,
SUPPLY_MICROSD_LOW_VOLTAGE = 1800000,
}; };
/** /**
* Enables a given power supply. * Enables a given power supply.
* *
* @param supply The power domain on the Switch that is to be enabled. * @param supply The power domain on the Switch that is to be enabled.
* @param use_low_voltage If the supply supports multiple voltages, use the lower one.
* Some devices start in a high power mode, but an can be switched to a lower one.
* Set this to false unless you know what you're doing.
*/ */
void supply_enable(enum switch_power_supply supply); void supply_enable(enum switch_power_supply supply, bool use_low_voltage);
#endif #endif