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

fusee: get uSD working, fix bad no_iopower, and abstract card differences

This commit is contained in:
Kate J. Temkin 2018-05-04 03:23:16 -06:00
parent 99f749ef82
commit 0120b9ce52
3 changed files with 257 additions and 98 deletions

View file

@ -202,7 +202,10 @@ enum tegra_pinmux_constants {
PINMUX_SELECT_FUNCTION3 = 3, PINMUX_SELECT_FUNCTION3 = 3,
/* Drive */ /* Drive */
PINMUX_DRIVE_1X = (0x0 << 13),
PINMUX_DRIVE_2X = (0x1 << 13), PINMUX_DRIVE_2X = (0x1 << 13),
PINMUX_DRIVE_3X = (0x2 << 13),
PINMUX_DRIVE_4X = (0x3 << 13),
}; };

View file

@ -195,6 +195,23 @@ enum sdmmc_register_bits {
/* Software reset */ /* Software reset */
MMC_SOFT_RESET_FULL = (1 << 0), MMC_SOFT_RESET_FULL = (1 << 0),
/* Vendor clock control */
MMC_CLOCK_TAP_MASK = (0xFF << 16),
MMC_CLOCK_TAP_SDMMC1 = (0x04 << 16),
MMC_CLOCK_TAP_SDMMC4 = (0x00 << 16),
MMC_CLOCK_TRIM_MASK = (0xFF << 24),
MMC_CLOCK_TRIM_SDMMC1 = (0x02 << 24),
MMC_CLOCK_TRIM_SDMMC4 = (0x08 << 24),
/* Auto cal configuration */
MMC_AUTOCAL_PDPU_CONFIG_MASK = 0x7f7f,
MMC_AUTOCAL_PDPU_SDMMC1_1V8 = 0x7b7b,
MMC_AUTOCAL_PDPU_SDMMC1_3V3 = 0x7d00,
MMC_AUTOCAL_PDPU_SDMMC4_1V8 = 0x0505,
}; };
@ -206,6 +223,7 @@ enum sdmmc_command {
CMD_SEND_OPERATING_CONDITIONS = 1, CMD_SEND_OPERATING_CONDITIONS = 1,
CMD_ALL_SEND_CID = 2, CMD_ALL_SEND_CID = 2,
CMD_SET_RELATIVE_ADDR = 3, CMD_SET_RELATIVE_ADDR = 3,
CMD_GET_RELATIVE_ADDR = 3,
CMD_SET_DSR = 4, CMD_SET_DSR = 4,
CMD_TOGGLE_SLEEP_AWAKE = 5, CMD_TOGGLE_SLEEP_AWAKE = 5,
CMD_SWITCH_MODE = 6, CMD_SWITCH_MODE = 6,
@ -305,6 +323,8 @@ enum sdmmc_command_magic {
MMC_EMMC_OPERATING_READINESS_MASK = (0x0f << 28), MMC_EMMC_OPERATING_READINESS_MASK = (0x0f << 28),
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_ACCEPTS_3V3 = (1 << 20),
/* READ_STATUS responses */ /* READ_STATUS responses */
MMC_STATUS_MASK = (0xf << 9), MMC_STATUS_MASK = (0xf << 9),
@ -470,7 +490,7 @@ static int sdmmc_hardware_reset(struct mmc *mmc)
mmc->regs->software_reset |= MMC_SOFT_RESET_FULL; mmc->regs->software_reset |= MMC_SOFT_RESET_FULL;
// Wait for the SDMMC controller to come back up... // Wait for the SDMMC controller to come back up...
while(mmc->regs->software_reset & MMC_SOFT_RESET_FULL) { while (mmc->regs->software_reset & MMC_SOFT_RESET_FULL) {
if (get_time_since(timebase) > mmc->timeout) { if (get_time_since(timebase) > mmc->timeout) {
mmc_print(mmc, "failed to bring up SDMMC controller"); mmc_print(mmc, "failed to bring up SDMMC controller");
return ETIMEDOUT; return ETIMEDOUT;
@ -527,8 +547,11 @@ static int sdmmc1_enable_supplies(struct mmc *mmc)
volatile struct tegra_pmc *pmc = pmc_get_regs(); volatile struct tegra_pmc *pmc = pmc_get_regs();
volatile struct tegra_pinmux *pinmux = pinmux_get_regs(); volatile struct tegra_pinmux *pinmux = pinmux_get_regs();
// Set PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only)
mmc->regs->sdmemcomppadctrl |= 0x80000000;
// Ensure the PMC is prepared for the SDMMC card to recieve power. // Ensure the PMC is prepared for the SDMMC card to recieve power.
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;
// Configure the enable line for the SD card power. // Configure the enable line for the SD card power.
@ -537,10 +560,6 @@ static int sdmmc1_enable_supplies(struct mmc *mmc)
gpio_configure_direction(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_DIRECTION_OUTPUT); gpio_configure_direction(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_DIRECTION_OUTPUT);
gpio_write(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_LEVEL_HIGH); gpio_write(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_LEVEL_HIGH);
// Set up SD card voltages.
udelay(1000);
supply_enable(SUPPLY_MICROSD);
udelay(1000);
return 0; return 0;
} }
@ -565,10 +584,6 @@ static int sdmmc1_hardware_init(struct mmc *mmc)
pinmux->sdmmc1_dat1 = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP; pinmux->sdmmc1_dat1 = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP;
pinmux->sdmmc1_dat0 = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP; pinmux->sdmmc1_dat0 = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP;
// Set up the SDMMC write protect.
// TODO: should this be an output, that we control?
pinmux->pz4 = PINMUX_SELECT_FUNCTION0 | PINMUX_PULL_UP;
// Set up the card detect pin as a GPIO input. // Set up the card detect pin as a GPIO input.
pinmux->pz1 = PINMUX_TRISTATE | PINMUX_SELECT_FUNCTION1 | PINMUX_PULL_UP | PINMUX_INPUT; pinmux->pz1 = PINMUX_TRISTATE | PINMUX_SELECT_FUNCTION1 | PINMUX_PULL_UP | PINMUX_INPUT;
gpio_configure_mode(GPIO_MICROSD_CARD_DETECT, GPIO_MODE_GPIO); gpio_configure_mode(GPIO_MICROSD_CARD_DETECT, GPIO_MODE_GPIO);
@ -592,6 +607,11 @@ static int sdmmc1_hardware_init(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);
@ -611,7 +631,7 @@ static int sdmmc1_hardware_init(struct mmc *mmc)
static int sdmmc_setup_controller_clock_and_io(struct mmc *mmc) static int sdmmc_setup_controller_clock_and_io(struct mmc *mmc)
{ {
// Always use the per-controller initialization functions. // Always use the per-controller initialization functions.
switch(mmc->controller) { switch (mmc->controller) {
case SWITCH_MICROSD: case SWITCH_MICROSD:
return sdmmc1_hardware_init(mmc); return sdmmc1_hardware_init(mmc);
case SWITCH_EMMC: case SWITCH_EMMC:
@ -632,7 +652,7 @@ static int sdmmc_setup_controller_clock_and_io(struct mmc *mmc)
static int sdmmc_enable_supplies(struct mmc *mmc) static int sdmmc_enable_supplies(struct mmc *mmc)
{ {
// Always use the per-controller initialization functions. // Always use the per-controller initialization functions.
switch(mmc->controller) { switch (mmc->controller) {
case SWITCH_MICROSD: case SWITCH_MICROSD:
return sdmmc1_enable_supplies(mmc); return sdmmc1_enable_supplies(mmc);
@ -648,6 +668,45 @@ static int sdmmc_enable_supplies(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: 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.
@ -677,27 +736,33 @@ static int sdmmc_hardware_init(struct mmc *mmc)
return rc; return rc;
} }
// Turn on the card's power supplies...
rc = sdmmc_enable_supplies(mmc);
if (rc) {
mmc_print(mmc, "ERROR: could power on the card!");
return rc;
}
// Set IO_SPARE[19] (one cycle delay) // Set IO_SPARE[19] (one cycle delay)
regs->io_spare |= 0x80000; regs->io_spare |= 0x80000;
// Clear SEL_VREG // Clear SEL_VREG
regs->vendor_io_trim_cntrl &= ~(0x04); regs->vendor_io_trim_cntrl &= ~(0x04);
// Set trimmer value to 0x08 (SDMMC4)
regs->vendor_clock_cntrl &= ~(0x1F000000);
regs->vendor_clock_cntrl |= 0x08000000;
// The bootrom sets TAP_VAL to be 4.
// We'll do that too. FIXME: should we?
regs->vendor_clock_cntrl |= 0x40000;
// Set SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL to 0x07 // Set SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL to 0x07
regs->sdmemcomppadctrl &= ~(0x0F); regs->sdmemcomppadctrl &= ~(0x0F);
regs->sdmemcomppadctrl |= 0x07; regs->sdmemcomppadctrl |= 0x07;
// Set auto-calibration PD/PU offsets // Set auto-calibration PD/PU offsets
regs->auto_cal_config = ((regs->auto_cal_config & ~(0x7f)) | 0x05); /*
regs->auto_cal_config = ((regs->auto_cal_config & ~(0x7f00)) | 0x05); */
// Set ourselves up to have a stable.
rc = sdmmc_set_up_clocking_parameters(mmc);
if (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 (relevant for eMMC only)
regs->sdmemcomppadctrl |= 0x80000000; regs->sdmemcomppadctrl |= 0x80000000;
@ -715,8 +780,7 @@ static int sdmmc_hardware_init(struct mmc *mmc)
timebase = get_time(); timebase = get_time();
// Wait for AUTO_CAL_ACTIVE to be cleared // Wait for AUTO_CAL_ACTIVE to be cleared
mmc_print(mmc, "initialing autocal..."); while ((regs->auto_cal_status & 0x80000000) && !is_timeout) {
while((regs->auto_cal_status & 0x80000000) && !is_timeout) {
// Keep checking if timeout expired // Keep checking if timeout expired
is_timeout = get_time_since(timebase) > 10000; is_timeout = get_time_since(timebase) > 10000;
} }
@ -727,21 +791,6 @@ static int sdmmc_hardware_init(struct mmc *mmc)
return ETIMEDOUT; return ETIMEDOUT;
} }
//
//if (is_timeout)
//{
// mmc_print(mmc, "autocal timed out!");
// // Set CFG2TMC_EMMC4_PAD_DRVUP_COMP and CFG2TMC_EMMC4_PAD_DRVDN_COMP
// APB_MISC_GP_EMMC4_PAD_CFGPADCTRL_0 = ((APB_MISC_GP_EMMC4_PAD_CFGPADCTRL_0 & ~(0x3F00)) | 0x1000);
// APB_MISC_GP_EMMC4_PAD_CFGPADCTRL_0 = ((APB_MISC_GP_EMMC4_PAD_CFGPADCTRL_0 & ~(0xFC)) | 0x40);
//
// // Clear AUTO_CAL_ENABLE
// regs->auto_cal_config &= ~(0x20000000);
//}
mmc_print(mmc, "autocal complete.");
// Clear PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only) // Clear PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only)
regs->sdmemcomppadctrl &= ~(0x80000000); regs->sdmemcomppadctrl &= ~(0x80000000);
@ -753,8 +802,7 @@ static int sdmmc_hardware_init(struct mmc *mmc)
is_timeout = false; is_timeout = false;
// Wait for SDHCI_CLOCK_INT_STABLE to be set // Wait for SDHCI_CLOCK_INT_STABLE to be set
mmc_print(mmc, "waiting for internal clock to stabalize..."); while (!(regs->clock_control & 0x02) && !is_timeout) {
while(!(regs->clock_control & 0x02) && !is_timeout) {
// Keep checking if timeout expired // Keep checking if timeout expired
is_timeout = get_time_since(timebase) > 2000000; is_timeout = get_time_since(timebase) > 2000000;
} }
@ -763,12 +811,10 @@ static int sdmmc_hardware_init(struct mmc *mmc)
if (is_timeout) { if (is_timeout) {
mmc_print(mmc, "clock never stabalized!"); mmc_print(mmc, "clock never stabalized!");
return -1; return -1;
} else {
mmc_print(mmc, "clock stabalized.");
} }
// Clear upper 17 bits // FIXME: replace this to support better clocks
regs->host_control2 &= ~(0xFFFF8000); regs->host_control2 = 0;
// Clear SDHCI_PROG_CLOCK_MODE // Clear SDHCI_PROG_CLOCK_MODE
regs->clock_control &= ~(0x20); regs->clock_control &= ~(0x20);
@ -784,18 +830,16 @@ 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 SDHCI_POWER_180 // Set up the SD card's voltage.
regs->power_control &= 0xF1; regs->power_control &= 0xF1;
regs->power_control |= 0x0A; regs->power_control |= mmc->operating_voltage << 1;
// Mark the power as on.
regs->power_control |= 0x01; 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);
// Set TAP_VAL
regs->vendor_clock_cntrl &= ~(0xFF0000);
// Clear SDHCI_CTRL_HISPD // Clear SDHCI_CTRL_HISPD
regs->host_control &= 0xFB; regs->host_control &= 0xFB;
@ -805,28 +849,20 @@ 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, for now regs->clock_control |= (0x30 << 8); // 200kHz, initially
// Set SDHCI_CLOCK_CARD_EN // Set SDHCI_CLOCK_CARD_EN
regs->clock_control |= 0x04; regs->clock_control |= 0x04;
// Ensure we're using System 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;
// Turn on the card's power supplies...
rc = sdmmc_enable_supplies(mmc);
if (rc) {
mmc_print(mmc, "ERROR: could power on the card!");
return rc;
}
// ... and verify that the card is there. // ... and verify that the card is there.
if (!sdmmc_card_present(mmc)) { if (!sdmmc_card_present(mmc)) {
mmc_print(mmc, "ERROR: no card detected!"); mmc_print(mmc, "ERROR: no card detected!");
return ENODEV; return ENODEV;
} }
mmc_print(mmc, "initialized."); mmc_print(mmc, "initialized.");
return 0; return 0;
} }
@ -849,7 +885,7 @@ static int sdmmc_wait_for_physical_state(struct mmc *mmc, uint32_t present_state
uint32_t condition; uint32_t condition;
// Retry until the event or an error happens // Retry until the event or an error happens
while(true) { while (true) {
// Handle timeout. // Handle timeout.
if (get_time_since(timebase) > mmc->timeout) { if (get_time_since(timebase) > mmc->timeout) {
@ -927,7 +963,7 @@ static int sdmmc_wait_for_interrupt(struct mmc *mmc,
uint32_t timebase = get_time(); uint32_t timebase = get_time();
// Wait until we either wind up ready, or until we've timed out. // Wait until we either wind up ready, or until we've timed out.
while(true) { while (true) {
if (get_time_since(timebase) > mmc->timeout) if (get_time_since(timebase) > mmc->timeout)
return ETIMEDOUT; return ETIMEDOUT;
@ -1170,7 +1206,7 @@ static int sdmmc_handle_command_response(struct mmc *mmc,
return 0; return 0;
switch(type) { switch (type) {
// Easy case: we don't have a response. We don't need to do anything. // Easy case: we don't have a response. We don't need to do anything.
case MMC_RESPONSE_NONE: case MMC_RESPONSE_NONE:
@ -1487,7 +1523,7 @@ static int sdmmc_read_and_parse_csd(struct mmc *mmc)
csd_version = sdmmc_extract_csd_bits(csd, MMC_CSD_STRUCTURE_START, MMC_CSD_STRUCTURE_WIDTH); csd_version = sdmmc_extract_csd_bits(csd, MMC_CSD_STRUCTURE_START, MMC_CSD_STRUCTURE_WIDTH);
// Handle each CSD version. // Handle each CSD version.
switch(csd_version) { switch (csd_version) {
// Handle version 1 CSDs. // Handle version 1 CSDs.
// (The Switch eMMC appears to always use ver1 CSDs.) // (The Switch eMMC appears to always use ver1 CSDs.)
@ -1576,7 +1612,7 @@ static int sdmmc_switch_bus_width(struct mmc *mmc, enum sdmmc_bus_width width)
// And switch the bus width on our side. // And switch the bus width on our side.
mmc->regs->host_control &= ~MMC_HOST_BUS_WIDTH_MASK; mmc->regs->host_control &= ~MMC_HOST_BUS_WIDTH_MASK;
switch(width) { switch (width) {
case MMC_BUS_WIDTH_4BIT: case MMC_BUS_WIDTH_4BIT:
mmc->regs->host_control |= MMC_HOST_BUS_WIDTH_4BIT; mmc->regs->host_control |= MMC_HOST_BUS_WIDTH_4BIT;
break; break;
@ -1636,7 +1672,77 @@ static int sdmmc_set_up_partitions(struct mmc *mmc)
} }
/**
* Requests that an MMC target use the card's current relative address.
*
* @param mmc The SDMMC controller to work with.
* @return 0 on success, or an error code on failure.
*/
static int sdmmc_set_relative_address(struct mmc *mmc)
{
int rc;
// Set up the card's relative address.
rc = sdmmc_send_simple_command(mmc, CMD_SET_RELATIVE_ADDR, MMC_RESPONSE_LEN48, mmc->relative_address << 16, NULL);
if (rc) {
mmc_print(mmc, "could not set the card's relative address! (%d)", rc);
return rc;
}
return 0;
}
/**
* Requests that an SD target report a relative address for us to use
* to communicate with it.
*
* @param mmc The SDMMC controller to work with.
* @return 0 on success, or an error code on failure.
*/
static int sdmmc_get_relative_address(struct mmc *mmc)
{
int rc;
uint32_t response;
//uint32_t timebase = get_time();
// TODO: do we need to repeatedly retry this? other codebases do
// Set up the card's relative address.
rc = sdmmc_send_simple_command(mmc, CMD_GET_RELATIVE_ADDR, MMC_RESPONSE_LEN48, 0, &response);
if (rc) {
mmc_print(mmc, "could not get the card's relative address! (%d)", rc);
return rc;
}
// Apply the fetched relative address.
mmc->relative_address = response >> 16;
return 0;
}
/**
* Establishes a relative address that can be used to communicate with a
* given card-- either by using or replacing mmc->relative_address.
*
* @param mmc The MMC controller to work with.
* @return 0 on success, or an error code on failure.
*/
static int sdmmc_get_or_set_relative_address(struct mmc *mmc)
{
// The SD and MMC specifications handle relative address assignemnt
// differently-- delegate accordingly.
switch (mmc->card_type) {
case MMC_CARD_EMMC:
case MMC_CARD_MMC:
return sdmmc_set_relative_address(mmc);
case MMC_CARD_SD:
return sdmmc_get_relative_address(mmc);
default:
mmc_print(mmc, "cannot figure out how to set up an relative address for TYPE%d devices", mmc->card_type);
return ENODEV;
}
}
/** /**
@ -1652,38 +1758,38 @@ static int sdmmc_card_init(struct mmc *mmc)
rc = sdmmc_send_simple_command(mmc, CMD_ALL_SEND_CID, MMC_RESPONSE_LEN136, 0, response); rc = sdmmc_send_simple_command(mmc, CMD_ALL_SEND_CID, MMC_RESPONSE_LEN136, 0, response);
if (rc) { if (rc) {
mmc_print(mmc, "could not fetch the CID"); mmc_print(mmc, "could not fetch the CID");
return ENODEV; return rc;
} }
// Store the card ID for later. // Store the card ID for later.
memcpy(mmc->cid, response, sizeof(mmc->cid)); memcpy(mmc->cid, response, sizeof(mmc->cid));
// Set up the card's relative address. // Establish a relative address to communicate with
rc = sdmmc_send_simple_command(mmc, CMD_SET_RELATIVE_ADDR, MMC_RESPONSE_LEN48, mmc->relative_address << 16, response); rc = sdmmc_get_or_set_relative_address(mmc);
if (rc) { if (rc) {
mmc_print(mmc, "could not set the card's relative address! (%d)", rc); mmc_print(mmc, "could not establish a relative address! (%d)", rc);
return EPIPE; return rc;
} }
// Read and handle card's Card Specific Data (CSD). // Read and handle card's Card Specific Data (CSD).
rc = sdmmc_read_and_parse_csd(mmc); rc = sdmmc_read_and_parse_csd(mmc);
if (rc) { if (rc) {
mmc_print(mmc, "could not populate CSD attributes! (%d)", rc); mmc_print(mmc, "could not populate CSD attributes! (%d)", rc);
return EPIPE; return rc;
} }
// Select our eMMC card, so it knows we're talking to it. // Select our eMMC card, so it knows we're talking to it.
rc = sdmmc_send_simple_command(mmc, CMD_TOGGLE_CARD_SELECT, MMC_RESPONSE_LEN48, mmc->relative_address << 16, response); rc = sdmmc_send_simple_command(mmc, CMD_TOGGLE_CARD_SELECT, MMC_RESPONSE_LEN48, mmc->relative_address << 16, response);
if (rc) { if (rc) {
mmc_print(mmc, "could not select the active card for use! (%d)", rc); mmc_print(mmc, "could not select the active card for use! (%d)", rc);
return EPIPE; return rc;
} }
// Determine the block size we want to work with, and then set up the size accordingly. // Determine the block size we want to work with, and then set up the size accordingly.
rc = sdmmc_set_up_block_transfer_size(mmc); rc = sdmmc_set_up_block_transfer_size(mmc);
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 EPIPE; return rc;
} }
return 0; return 0;
@ -1739,14 +1845,17 @@ static int sdmmc_mmc_wait_for_card_readiness(struct mmc *mmc)
static int sdmmc_sd_wait_for_card_readiness(struct mmc *mmc, uint32_t *response) 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;
// TODO: populate this correctly per version // If this is a SDv2 or higher card, check for an SDHC card.
uint32_t argument = 0; if (mmc->spec_version >= SD_VERSION_2) {
argument |= MMC_SD_OPERATING_COND_HIGH_CAPACITY;
}
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. It will respond with readiness and a capacity magic.
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_LEN136, MMC_CHECKS_NONE, argument, response); MMC_RESPONSE_LEN48, MMC_CHECKS_NONE, argument, response);
if (rc) { if (rc) {
mmc_print(mmc, "ERROR: could not read the card's operating conditions!"); mmc_print(mmc, "ERROR: could not read the card's operating conditions!");
return rc; return rc;
@ -1755,6 +1864,9 @@ static int sdmmc_sd_wait_for_card_readiness(struct mmc *mmc, uint32_t *response)
// If the device has just become ready, we're done! // If the device has just become ready, we're done!
if (response[0] & MMC_SD_OPERATING_COND_READY) if (response[0] & MMC_SD_OPERATING_COND_READY)
return 0; return 0;
// Wait a delay so we're not spamming the card incessantly.
udelay(1000);
} }
} }
@ -1769,11 +1881,6 @@ static int sdmmc_mmc_card_init(struct mmc *mmc)
mmc_print(mmc, "setting up card as MMC"); mmc_print(mmc, "setting up card as MMC");
// We only support Switch eMMC addressing, which is alawys block-based.
mmc->uses_block_addressing = true;
udelay(10000000);
// Bring the bus out of its idle state. // Bring the bus out of its idle state.
rc = sdmmc_send_simple_command(mmc, CMD_GO_IDLE_OR_INIT, MMC_RESPONSE_NONE, 0, NULL); rc = sdmmc_send_simple_command(mmc, CMD_GO_IDLE_OR_INIT, MMC_RESPONSE_NONE, 0, NULL);
if (rc) { if (rc) {
@ -1842,7 +1949,7 @@ static bool sdmmc_check_pattern_present(uint32_t response)
static int sdmmc_sd_card_init(struct mmc *mmc) static int sdmmc_sd_card_init(struct mmc *mmc)
{ {
int rc; int rc;
uint32_t response[4]; uint32_t ocr, response;
mmc_print(mmc, "setting up card as SD"); mmc_print(mmc, "setting up card as SD");
@ -1853,23 +1960,35 @@ 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[0]); 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[0])) { if (rc || !sdmmc_check_pattern_present(response)) {
// TODO: Maybe don't assume we _need_ 3V3 interfacing?
mmc_print(mmc, "v1 or MMC card detected"); // TODO: This is either a broken, SDv1 or MMC card.
} else { // Handle the latter two cases as best we can.
mmc_print(mmc, "this card is a v2 or greater card!");
mmc_print(mmc, "ERROR: this card isn't an SDHC card!");
mmc_print(mmc, " we don't yet support low-capacity cards. :(");
return rc;
}
// If this responded, indicate that this is a v2 card.
else {
// store that this is a v2 card
mmc->spec_version = SD_VERSION_2;
} }
// Wait for the card to finish being busy. // Wait for the card to finish being busy.
rc = sdmmc_sd_wait_for_card_readiness(mmc, response); rc = sdmmc_sd_wait_for_card_readiness(mmc, &ocr);
if (rc) { if (rc) {
mmc_print(mmc, "card failed to come up! (%d)", rc); mmc_print(mmc, "card failed to come up! (%d)", rc);
return rc; return rc;
} }
// FIXME: parse the response // If the response indicated this was a high capacity card,
// always use block addressing.
mmc->uses_block_addressing = !!(ocr & MMC_SD_OPERATING_COND_HIGH_CAPACITY);
// Run the common core card initialization. // Run the common core card initialization.
rc = sdmmc_card_init(mmc); rc = sdmmc_card_init(mmc);
@ -1895,7 +2014,7 @@ static int sdmmc_handle_card_type_init(struct mmc *mmc)
{ {
int rc; int rc;
switch(mmc->card_type) { switch (mmc->card_type) {
// Handle initialization of eMMC cards. // Handle initialization of eMMC cards.
case MMC_CARD_EMMC: case MMC_CARD_EMMC:
@ -1949,7 +2068,7 @@ static int sdmmc_wait_for_card_ready(struct mmc *mmc, uint32_t timeout)
uint32_t timebase = get_time(); uint32_t timebase = get_time();
while(true) { while (true) {
// Read the card's status. // Read the card's status.
rc = sdmmc_send_simple_command(mmc, CMD_READ_STATUS, MMC_RESPONSE_LEN48, mmc->relative_address << 16, &status); rc = sdmmc_send_simple_command(mmc, CMD_READ_STATUS, MMC_RESPONSE_LEN48, mmc->relative_address << 16, &status);
@ -2000,7 +2119,7 @@ static int sdmmc_switch_mode(struct mmc *mmc, enum sdmmc_switch_access_mode mode
} }
// Wait until we have a sense of the card status to return. // Wait until we have a sense of the card status to return.
if(timeout != 0) { if (timeout != 0) {
rc = sdmmc_wait_for_card_ready(mmc, timeout); rc = sdmmc_wait_for_card_ready(mmc, timeout);
if (rc){ if (rc){
mmc_print(mmc, "failed to talk to the card after SWITCH_MODE (%d)", rc); mmc_print(mmc, "failed to talk to the card after SWITCH_MODE (%d)", rc);
@ -2029,17 +2148,26 @@ static bool sdmmc_supports_hardware_partitions(struct mmc *mmc)
static void sdmmc_initialize_defaults(struct mmc *mmc) static void sdmmc_initialize_defaults(struct mmc *mmc)
{ {
// Set up based on the controller // Set up based on the controller
switch(mmc->controller) { switch (mmc->controller) {
case SWITCH_EMMC: case SWITCH_EMMC:
mmc->name = "eMMC"; mmc->name = "eMMC";
mmc->card_type = MMC_CARD_EMMC; mmc->card_type = MMC_CARD_EMMC;
mmc->max_bus_width = MMC_BUS_WIDTH_8BIT; mmc->max_bus_width = MMC_BUS_WIDTH_8BIT;
mmc->operating_voltage = MMC_VOLTAGE_1V8;
// The Switch's eMMC always uses block addressin.g
mmc->uses_block_addressing = true;
break; break;
case SWITCH_MICROSD: case SWITCH_MICROSD:
mmc->name = "uSD"; mmc->name = "uSD";
mmc->card_type = MMC_CARD_SD; mmc->card_type = MMC_CARD_SD;
mmc->max_bus_width = MMC_BUS_WIDTH_4BIT; mmc->max_bus_width = MMC_BUS_WIDTH_4BIT;
mmc->operating_voltage = MMC_VOLTAGE_3V3;
// Start off assuming byte addressing; we'll detect and correct this
// later, if necessary.
mmc->uses_block_addressing = false;
break; break;
default: default:

View file

@ -24,11 +24,22 @@ enum sdmmc_bus_width {
}; };
/**
* Describes the voltages that the host will use to drive the SD card.
* CAUTION: getting these wrong can damage (especially embedded) cards!
*/
enum sdmmc_bus_voltage {
MMC_VOLTAGE_3V3 = 0b111,
MMC_VOLTAGE_3V0 = 0b110,
MMC_VOLTAGE_1V8 = 0b101,
};
/** /**
* Represents the different types of devices an MMC object * Represents the different types of devices an MMC object
* can represent. * can represent.
*/ */
enum mmc_card_type { enum sdmmc_card_type {
MMC_CARD_EMMC, MMC_CARD_EMMC,
MMC_CARD_MMC, MMC_CARD_MMC,
MMC_CARD_SD, MMC_CARD_SD,
@ -36,6 +47,22 @@ enum mmc_card_type {
}; };
/**
* Specification versions for SD/MMC cards.
*/
enum sdmmc_spec_version {
/* MMC card versions */
MMC_VERSION_4 = 0,
/* SD card versions */
SD_VERSION_1 = 1,
SD_VERSION_2 = 2,
};
/** /**
* SDMMC controllers * SDMMC controllers
*/ */
@ -54,15 +81,16 @@ struct mmc {
/* Controller properties */ /* Controller properties */
char *name; char *name;
unsigned int timeout; unsigned int timeout;
enum mmc_card_type card_type; enum sdmmc_card_type card_type;
bool use_dma; bool use_dma;
/* Card properties */ /* Card properties */
uint8_t cid[15]; uint8_t cid[15];
uint32_t relative_address; uint32_t relative_address;
uint8_t partitioned; uint8_t partitioned;
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;
uint8_t partition_support; uint8_t partition_support;
uint8_t partition_config; uint8_t partition_config;