mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-11-10 07:06:34 +00:00
fusee: get uSD working, fix bad no_iopower, and abstract card differences
This commit is contained in:
parent
99f749ef82
commit
0120b9ce52
3 changed files with 257 additions and 98 deletions
|
@ -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),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -304,7 +322,9 @@ enum sdmmc_command_magic {
|
||||||
MMC_EMMC_OPERATING_COND_READY = (0x0c << 28),
|
MMC_EMMC_OPERATING_COND_READY = (0x0c << 28),
|
||||||
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:
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue