1
0
Fork 0
mirror of https://github.com/Atmosphere-NX/Atmosphere.git synced 2024-11-05 19:51:45 +00:00

fusee: fix SD pinmuxing / signal integrity issues

This commit is contained in:
Kate J. Temkin 2018-05-03 04:54:36 -06:00
parent 5b3be77f0e
commit 99f749ef82
3 changed files with 144 additions and 46 deletions

View file

@ -62,6 +62,7 @@ enum {
enum {
CAR_CONTROL_SDMMC1 = (1 << 14),
CAR_CONTROL_SDMMC4 = (1 << 15),
CAR_CONTROL_SDMMC_LEGACY = (1 << 1),
};
@ -149,12 +150,12 @@ struct PACKED tegra_car {
uint32_t reserved30[2]; /* _reserved_30, 0x318, 0x31c */
/* _CLK_ENB_L/H/U_CLR_0 0x320 ~ 0x334 */
uint32_t clk_dev_l_set;
uint32_t clk_dev_l_clr;
uint32_t clk_dev_h_set;
uint32_t clk_dev_h_clr;
uint32_t clk_dev_u_set;
uint32_t clk_dev_u_clr;
uint32_t clk_enb_l_set;
uint32_t clk_enb_l_clr;
uint32_t clk_enb_h_set;
uint32_t clk_enb_h_clr;
uint32_t clk_enb_u_set;
uint32_t clk_enb_u_clr;
uint32_t reserved31[2]; /* _reserved_31, 0x338, 0x33c */

View file

@ -182,15 +182,18 @@ struct PACKED tegra_pinmux {
enum tegra_pinmux_constants {
/* Tristate (output buffer) control */
PINMUX_TRISTATE_PASSTHROUGH = (1 << 4),
PINMUX_TRISTATE = (1 << 4),
/* Park control */
PINMUX_PARKED = (1 << 5),
/* Input control */
PINMUX_INPUT = (1 << 6),
/* Pull resistors */
PINMUX_PULL_NONE = 0,
PINMUX_PULL_DOWN = 1,
PINMUX_PULL_UP = 2,
PINMUX_PULL_NONE = (0 << 2),
PINMUX_PULL_DOWN = (1 << 2),
PINMUX_PULL_UP = (2 << 2),
/* Function select */
PINMUX_SELECT_FUNCTION0 = 0,

View file

@ -221,6 +221,9 @@ enum sdmmc_command {
CMD_SET_BLKLEN = 16,
CMD_READ_SINGLE_BLOCK = 17,
CMD_READ_MULTIPLE_BLOCK = 18,
CMD_APP_SEND_OP_COND = 41,
CMD_APP_COMMAND = 55,
};
/**
@ -301,6 +304,8 @@ enum sdmmc_command_magic {
MMC_EMMC_OPERATING_COND_READY = (0x0c << 28),
MMC_EMMC_OPERATING_READINESS_MASK = (0x0f << 28),
MMC_SD_OPERATING_COND_READY = (1 << 31),
/* READ_STATUS responses */
MMC_STATUS_MASK = (0xf << 9),
MMC_STATUS_PROGRAMMING = (0x7 << 9),
@ -397,6 +402,25 @@ void mmc_print(struct mmc *mmc, char *fmt, ...)
va_end(list);
}
/**
* @return a statically allocated string that describes the given command
*/
static const char *sdmmc_get_command_string(enum sdmmc_command command)
{
switch (command) {
// Commands that aren't in the lower block.
case CMD_APP_COMMAND:
return "CMD_APP_COMMAND";
case CMD_APP_SEND_OP_COND:
return "CMD_APP_SEND_OP_COND";
// For commands with low numbers, read them string from the relevant array.
default:
return sdmmc_command_string[command];
}
}
/**
* Debug: print out any errors that occurred during a command timeout
*/
@ -478,7 +502,8 @@ static int sdmmc4_hardware_init(struct mmc *mmc)
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | CLK_DIVIDER_32;
// Set SDMMC4 clock enable
car->clk_dev_l_set |= 0x8000;
car->clk_enb_l_set |= 0x8000;
car->clk_enb_y_set |= CAR_CONTROL_SDMMC_LEGACY;
// host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles
udelay(5000);
@ -506,11 +531,11 @@ static int sdmmc1_enable_supplies(struct mmc *mmc)
pmc->no_iopower |= PMC_CONTROL_SDMMC1;
pmc->pwr_det_val |= PMC_CONTROL_SDMMC1;
// Set up the card detect pin as a GPIO input.
pinmux->pz1= PINMUX_SELECT_FUNCTION1 | PINMUX_PULL_UP | PINMUX_INPUT;
gpio_configure_mode(GPIO_MICROSD_CARD_DETECT, GPIO_MODE_GPIO);
gpio_configure_direction(GPIO_MICROSD_CARD_DETECT, GPIO_DIRECTION_INPUT);
udelay(100);
// Configure the enable line for the SD card power.
pinmux->dmic3_clk = PINMUX_SELECT_FUNCTION0;
gpio_configure_mode(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_MODE_GPIO);
gpio_configure_direction(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_DIRECTION_OUTPUT);
gpio_write(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_LEVEL_HIGH);
// Set up SD card voltages.
udelay(1000);
@ -531,28 +556,27 @@ static int sdmmc1_hardware_init(struct mmc *mmc)
volatile struct tegra_padctl *padctl = padctl_get_regs();
(void)mmc;
// Configure the enable line for the SD card power.
pinmux->dmic3_clk = PINMUX_TRISTATE_PASSTHROUGH | PINMUX_SELECT_FUNCTION0;
gpio_configure_mode(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_MODE_GPIO);
gpio_configure_direction(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_DIRECTION_OUTPUT);
gpio_write(GPIO_MICROSD_SUPPLY_ENABLE, GPIO_LEVEL_HIGH);
// Set up each of the relevant pins to be connected to output drivers,
// and selected for SDMMC use.
pinmux->sdmmc1_clk = PINMUX_DRIVE_2X | PINMUX_TRISTATE_PASSTHROUGH | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT;
pinmux->sdmmc1_cmd = PINMUX_DRIVE_2X | PINMUX_TRISTATE_PASSTHROUGH | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT;
pinmux->sdmmc1_dat3 = PINMUX_DRIVE_2X | PINMUX_TRISTATE_PASSTHROUGH | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT;
pinmux->sdmmc1_dat2 = PINMUX_DRIVE_2X | PINMUX_TRISTATE_PASSTHROUGH | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT;
pinmux->sdmmc1_dat1 = PINMUX_DRIVE_2X | PINMUX_TRISTATE_PASSTHROUGH | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT;
pinmux->sdmmc1_dat0 = PINMUX_DRIVE_2X | PINMUX_TRISTATE_PASSTHROUGH | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT;
pinmux->sdmmc1_clk = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT;
pinmux->sdmmc1_cmd = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP;
pinmux->sdmmc1_dat3 = PINMUX_DRIVE_2X | PINMUX_PARKED | PINMUX_SELECT_FUNCTION0 | PINMUX_INPUT | PINMUX_PULL_UP;
pinmux->sdmmc1_dat2 = 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;
// Set up the SDMMC write protect.
// TODO: should this be an output, that we control?
pinmux->pz4 = PINMUX_TRISTATE_PASSTHROUGH | PINMUX_SELECT_FUNCTION0 | PINMUX_PULL_UP;
pinmux->pz4 = PINMUX_SELECT_FUNCTION0 | PINMUX_PULL_UP;
// Set up the card detect pin as a GPIO 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_direction(GPIO_MICROSD_CARD_DETECT, GPIO_DIRECTION_INPUT);
udelay(100);
// Ensure we're using GPIO and not GPIO for the SD card's card detect.
padctl->vgpio_gpio_mux_sel &= ~PADCTL_SDMMC1_CD_SOURCE;
mmc_print(mmc, "mux sel is at %p", &padctl->vgpio_gpio_mux_sel);
// Put SDMMC1 in reset
car->rst_dev_l_set |= CAR_CONTROL_SDMMC1;
@ -565,7 +589,8 @@ static int sdmmc1_hardware_init(struct mmc *mmc)
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | CLK_DIVIDER_32;
// Set SDMMC1 clock enable
car->clk_dev_l_set |= CAR_CONTROL_SDMMC1;
car->clk_enb_l_set |= CAR_CONTROL_SDMMC1;
car->clk_enb_y_set |= CAR_CONTROL_SDMMC_LEGACY;
// host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles
udelay(5000);
@ -662,7 +687,7 @@ static int sdmmc_hardware_init(struct mmc *mmc)
regs->vendor_clock_cntrl &= ~(0x1F000000);
regs->vendor_clock_cntrl |= 0x08000000;
// The boootrom sets TAP_VAL to be 4.
// The bootrom sets TAP_VAL to be 4.
// We'll do that too. FIXME: should we?
regs->vendor_clock_cntrl |= 0x40000;
@ -671,8 +696,8 @@ static int sdmmc_hardware_init(struct mmc *mmc)
regs->sdmemcomppadctrl |= 0x07;
// 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);
regs->auto_cal_config = ((regs->auto_cal_config & ~(0x7f)) | 0x05);
regs->auto_cal_config = ((regs->auto_cal_config & ~(0x7f00)) | 0x05);
// Set PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only)
regs->sdmemcomppadctrl |= 0x80000000;
@ -780,8 +805,7 @@ static int sdmmc_hardware_init(struct mmc *mmc)
// Set SDHCI_DIVIDER and SDHCI_DIVIDER_HI
// FIXME: divider SD if necessary
regs->clock_control &= ~(0xFFC0);
regs->clock_control |= (0x80 << 8); // use the slowest setting, for now
//regs->clock_control |= ((sd_divider_lo << 0x08) | (sd_divider_hi << 0x06));
regs->clock_control |= (0x18 << 8); // 400kHz, for now
// Set SDHCI_CLOCK_CARD_EN
regs->clock_control |= 0x04;
@ -1259,7 +1283,7 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
// Wait for the command to be completed.
rc = sdmmc_wait_for_command_completion(mmc);
if (rc) {
mmc_print(mmc, "failed to issue %s (arg=%08x, rc=%d)", sdmmc_command_string[command], argument, rc);
mmc_print(mmc, "failed to issue %s (arg=%08x, rc=%d)", sdmmc_get_command_string(command), argument, rc);
mmc_print_command_errors(mmc, rc);
sdmmc_enable_interrupts(mmc, false);
@ -1308,10 +1332,12 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
// (This is mostly for when the GIC is brought up)
sdmmc_enable_interrupts(mmc, false);
mmc_print(mmc, "completed %s.", sdmmc_command_string[command]);
mmc_print(mmc, "completed %s.", sdmmc_get_command_string(command));
return 0;
}
/**
* Convenience function that sends a simple SDMMC command
* and awaits response. Wrapper around sdmmc_send_command.
@ -1335,6 +1361,38 @@ static int sdmmc_send_simple_command(struct mmc *mmc, enum sdmmc_command command
}
/**
* 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)
{
int rc;
// First, send the application command.
rc = sdmmc_send_simple_command(mmc, CMD_APP_COMMAND, MMC_RESPONSE_LEN48, 0, NULL);
if (rc) {
mmc_print(mmc, "failed to prepare application command! (%d)", rc);
return rc;
}
// And issue the body of the command.
return sdmmc_send_command(mmc, command, response_type, checks, argument, response_buffer, 0, false, false, NULL);
}
/**
* Reads a collection of bits from the CSD register.
@ -1637,7 +1695,7 @@ static int sdmmc_card_init(struct mmc *mmc)
*
* @param mmc The MMC device that should do the waiting.
*/
static int sdmmc_wait_for_card_readiness(struct mmc *mmc)
static int sdmmc_mmc_wait_for_card_readiness(struct mmc *mmc)
{
int rc;
uint32_t response[4];
@ -1671,6 +1729,37 @@ static int sdmmc_wait_for_card_readiness(struct mmc *mmc)
}
/**
* Blocks until the SD card is fully initialized.
*
* @param mmc The MMC device that should do the waiting.
* @aparam response Out argument that recieves the final, ready command response.
* Should have roon for uint32_t.
*/
static int sdmmc_sd_wait_for_card_readiness(struct mmc *mmc, uint32_t *response)
{
int rc;
// TODO: populate this correctly per version
uint32_t argument = 0;
while (true) {
// 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,
MMC_RESPONSE_LEN136, MMC_CHECKS_NONE, argument, response);
if (rc) {
mmc_print(mmc, "ERROR: could not read the card's operating conditions!");
return rc;
}
// If the device has just become ready, we're done!
if (response[0] & MMC_SD_OPERATING_COND_READY)
return 0;
}
}
/**
* Handles MMC-specific card initialization.
*/
@ -1683,6 +1772,8 @@ static int sdmmc_mmc_card_init(struct mmc *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.
rc = sdmmc_send_simple_command(mmc, CMD_GO_IDLE_OR_INIT, MMC_RESPONSE_NONE, 0, NULL);
if (rc) {
@ -1691,7 +1782,7 @@ static int sdmmc_mmc_card_init(struct mmc *mmc)
}
// Wait for the card to finish being busy.
rc = sdmmc_wait_for_card_readiness(mmc);
rc = sdmmc_mmc_wait_for_card_readiness(mmc);
if (rc) {
mmc_print(mmc, "card failed to come up! (%d)", rc);
return rc;
@ -1751,7 +1842,7 @@ static bool sdmmc_check_pattern_present(uint32_t response)
static int sdmmc_sd_card_init(struct mmc *mmc)
{
int rc;
uint32_t response;
uint32_t response[4];
mmc_print(mmc, "setting up card as SD");
@ -1763,20 +1854,23 @@ static int sdmmc_sd_card_init(struct mmc *mmc)
}
// 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);
if (rc || !sdmmc_check_pattern_present(response)) {
rc = sdmmc_send_simple_command(mmc, CMD_SEND_IF_COND, MMC_RESPONSE_LEN48, MMC_IF_VOLTAGE_3V3 | MMC_IF_CHECK_PATTERN, &response[0]);
if (rc || !sdmmc_check_pattern_present(response[0])) {
// TODO: Maybe don't assume we _need_ 3V3 interfacing?
mmc_print(mmc, "card can't talk at our voltage (rc=%d, check=%02x)!", rc, response & 0xFF);
return rc;
mmc_print(mmc, "v1 or MMC card detected");
} else {
mmc_print(mmc, "this card is a v2 or greater card!");
}
// Wait for the card to finish being busy.
rc = sdmmc_wait_for_card_readiness(mmc);
rc = sdmmc_sd_wait_for_card_readiness(mmc, response);
if (rc) {
mmc_print(mmc, "card failed to come up! (%d)", rc);
return rc;
}
// FIXME: parse the response
// Run the common core card initialization.
rc = sdmmc_card_init(mmc);
if (rc) {