mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-11-10 07:06:34 +00:00
fusee: get non-data SDMMC commands fully working on eMMC
This commit is contained in:
parent
dbb65428e8
commit
eb48e06331
3 changed files with 207 additions and 39 deletions
|
@ -50,7 +50,9 @@ enum {
|
||||||
enum {
|
enum {
|
||||||
CLK_SOURCE_MASK = (0b111 << 29),
|
CLK_SOURCE_MASK = (0b111 << 29),
|
||||||
CLK_SOURCE_FIRST = 0,
|
CLK_SOURCE_FIRST = 0,
|
||||||
CLK_DIVIDER_UNITY = 0
|
CLK_DIVIDER_UNITY = 0,
|
||||||
|
|
||||||
|
CLK_DIVIDER_32 = 32,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
|
|
@ -162,7 +162,6 @@ enum sdmmc_register_bits {
|
||||||
MMC_STATUS_COMMAND_INDEX_ERROR = (1 << 19),
|
MMC_STATUS_COMMAND_INDEX_ERROR = (1 << 19),
|
||||||
|
|
||||||
MMC_STATUS_ERROR_MASK = (0xF << 16),
|
MMC_STATUS_ERROR_MASK = (0xF << 16),
|
||||||
//MMC_STATUS_ERROR_MASK = (0xE << 16),
|
|
||||||
|
|
||||||
/* Host control */
|
/* Host control */
|
||||||
MMC_DMA_SELECT_MASK = (0x3 << 3),
|
MMC_DMA_SELECT_MASK = (0x3 << 3),
|
||||||
|
@ -195,13 +194,19 @@ enum sdmmc_command {
|
||||||
CMD_SET_BLKLEN = 16,
|
CMD_SET_BLKLEN = 16,
|
||||||
CMD_READ_SINGLE_BLOCK = 17,
|
CMD_READ_SINGLE_BLOCK = 17,
|
||||||
CMD_READ_MULTIPLE_BLOCK = 18,
|
CMD_READ_MULTIPLE_BLOCK = 18,
|
||||||
|
|
||||||
|
CMD_APP_CMD = 55,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SDMMC command argument numbers
|
* SDMMC command argument numbers
|
||||||
*/
|
*/
|
||||||
enum sdmmc_command_magic {
|
enum sdmmc_command_magic {
|
||||||
MMC_SEND_IF_COND_MAGIC = 0xaa,
|
MMC_EMMC_OPERATING_COND_CAPACITY_MAGIC = 0x00ff8080,
|
||||||
|
MMC_EMMC_OPERATING_COND_CAPACITY_MASK = 0x0fffffff,
|
||||||
|
MMC_EMMC_OPERATING_COND_BUSY = (0x04 << 28),
|
||||||
|
MMC_EMMC_OPERATING_COND_READY = (0x0c << 28),
|
||||||
|
MMC_EMMC_OPERATING_READINESS_MASK = (0x0f << 28),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -307,11 +312,12 @@ static int sdmmc_hardware_init(struct mmc *mmc)
|
||||||
// Put SDMMC4 in reset
|
// Put SDMMC4 in reset
|
||||||
car->rst_dev_l_set |= 0x8000;
|
car->rst_dev_l_set |= 0x8000;
|
||||||
|
|
||||||
// Set SDMMC4 clock source (PLLP_OUT0) and divisor (1)
|
// Set SDMMC4 clock source (PLLP_OUT0) and divisor (32).
|
||||||
car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | CLK_DIVIDER_UNITY;
|
// We use 32 beacuse Nintendo does, and they probably know what they're doing?
|
||||||
|
car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | CLK_DIVIDER_32;
|
||||||
|
|
||||||
// Set the legacy divier used for
|
// Set the legacy divier used for
|
||||||
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | CLK_DIVIDER_UNITY;
|
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | CLK_DIVIDER_32;
|
||||||
|
|
||||||
// Set SDMMC4 clock enable
|
// Set SDMMC4 clock enable
|
||||||
car->clk_dev_l_set |= 0x8000;
|
car->clk_dev_l_set |= 0x8000;
|
||||||
|
@ -340,6 +346,10 @@ static int sdmmc_hardware_init(struct mmc *mmc)
|
||||||
regs->vendor_clock_cntrl &= ~(0x1F000000);
|
regs->vendor_clock_cntrl &= ~(0x1F000000);
|
||||||
regs->vendor_clock_cntrl |= 0x08000000;
|
regs->vendor_clock_cntrl |= 0x08000000;
|
||||||
|
|
||||||
|
// The boootrom 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;
|
||||||
|
@ -473,7 +483,7 @@ 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 |= (0x80 << 8); // XXX wtf is this
|
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 |= ((sd_divider_lo << 0x08) | (sd_divider_hi << 0x06));
|
||||||
|
|
||||||
// HS400/HS667 modes require additional DLL calibration
|
// HS400/HS667 modes require additional DLL calibration
|
||||||
|
@ -838,11 +848,22 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
||||||
sdmmc_handle_command_data(mmc, blocks_to_transfer, is_write, data_buffer);
|
sdmmc_handle_command_data(mmc, blocks_to_transfer, is_write, data_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
mmc_print(mmc, "command success!");
|
mmc_print(mmc, "CMD%d success!", command);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function that sends a simple SDMMC command
|
||||||
|
* and awaits response. Wrapper around sdmmc_send_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 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_command(struct mmc *mmc, enum sdmmc_command command,
|
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)
|
enum sdmmc_response_type response_type, uint32_t argument, void *response_buffer)
|
||||||
{
|
{
|
||||||
|
@ -854,15 +875,15 @@ static int sdmmc_send_simple_command(struct mmc *mmc, enum sdmmc_command command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about the card, and populates the MMC structure accordingly.
|
* Handles eMMC-specific card initialization.
|
||||||
* Used as part of the SDMMC initialization process.
|
|
||||||
*/
|
*/
|
||||||
static int sdmmc_card_init(struct mmc *mmc)
|
static int emmc_card_init(struct mmc *mmc)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
uint8_t response[17];
|
uint32_t response[4];
|
||||||
|
|
||||||
|
mmc_print(mmc, "setting up card as eMMC");
|
||||||
|
|
||||||
// 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);
|
||||||
|
@ -871,17 +892,90 @@ static int sdmmc_card_init(struct mmc *mmc)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if this is a Version 2.0 card.
|
|
||||||
rc = sdmmc_send_simple_command(mmc, CMD_SEND_IF_COND, MMC_RESPONSE_LEN48, MMC_SEND_IF_COND_MAGIC, response);
|
// Wait for the card to finish being busy.
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
uint32_t response_masked;
|
||||||
|
|
||||||
|
// Ask the SD card to identify its state. It will respond with readiness and a capacity magic.
|
||||||
|
rc = sdmmc_send_command(mmc, CMD_SEND_OPERATING_CONDITIONS, MMC_RESPONSE_LEN48, MMC_CHECKS_NONE, 0x40000080, response, 0, 0, NULL);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
// If we had an error, this was a v1 card.
|
mmc_print(mmc, "ERROR: could not read the card's operating conditions!");
|
||||||
mmc_print(mmc, "handling as a v1.0 card.");
|
return rc;
|
||||||
} else {
|
|
||||||
// If this responded with the appropriate magic, it's a v2 card.
|
|
||||||
if (response[0] == MMC_SEND_IF_COND_MAGIC)
|
|
||||||
mmc_print(mmc, "handling as a v2.0 card.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate that this is a valid Switch eMMC.
|
||||||
|
// Per the spec, any card greater than 2GiB should respond with this magic number.
|
||||||
|
response_masked = response[0] & MMC_EMMC_OPERATING_COND_CAPACITY_MASK;
|
||||||
|
if (response_masked != MMC_EMMC_OPERATING_COND_CAPACITY_MAGIC) {
|
||||||
|
mmc_print(mmc, "ERROR: this doesn't appear to be a valid Switch eMMC!");
|
||||||
|
return ENOTTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the device has just become ready, we're done!
|
||||||
|
response_masked = response[0] & MMC_EMMC_OPERATING_READINESS_MASK;
|
||||||
|
if (response_masked == MMC_EMMC_OPERATING_COND_READY) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the active MMC card's Card Specific Data, and updates the MMC object's properties.
|
||||||
|
*
|
||||||
|
* @param mmc The MMC to be queired and updated.
|
||||||
|
* @returns 0 on success, or an errno on failure
|
||||||
|
*/
|
||||||
|
static int sdmmc_read_and_parse_csd(struct mmc *mmc)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
uint32_t csd[4];
|
||||||
|
|
||||||
|
rc = sdmmc_send_simple_command(mmc, CMD_SEND_CSD, MMC_RESPONSE_LEN136, mmc->relative_address << 16, csd);
|
||||||
|
if (rc) {
|
||||||
|
mmc_print(mmc, "could not get the card's CSD!");
|
||||||
|
return ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: parse CSD
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the active MMC card's Card Specific Data, and updates the MMC object's properties.
|
||||||
|
*
|
||||||
|
* @param mmc The MMC to be queired and updated.
|
||||||
|
* @returns 0 on success, or an errno on failure
|
||||||
|
*/
|
||||||
|
static int sdmmc_read_and_parse_ext_csd(struct mmc *mmc)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
uint32_t csd[4];
|
||||||
|
|
||||||
|
// FIXME: reading the extended CSD requires us to read data; so we're not ready to do it yet
|
||||||
|
// FIXME: parse CSD
|
||||||
|
|
||||||
|
(void)csd;
|
||||||
|
(void)rc;
|
||||||
|
|
||||||
|
mmc_print(mmc, "ERROR: ext-CSD reading not yet implemented");
|
||||||
|
return ENOSYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves information about the card, and populates the MMC structure accordingly.
|
||||||
|
* Used as part of the SDMMC initialization process.
|
||||||
|
*/
|
||||||
|
static int sdmmc_card_init(struct mmc *mmc)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
uint32_t response[4];
|
||||||
|
|
||||||
// Retreive the card ID.
|
// Retreive the card ID.
|
||||||
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) {
|
||||||
|
@ -892,17 +986,67 @@ static int sdmmc_card_init(struct mmc *mmc)
|
||||||
// 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));
|
||||||
|
|
||||||
// TODO: Read and handle the CSD.
|
// Set up the card's relative address.
|
||||||
// TODO: Toggle the card select, so we it knows we're talking to it.
|
rc = sdmmc_send_simple_command(mmc, CMD_SET_RELATIVE_ADDR, MMC_RESPONSE_LEN48, mmc->relative_address << 16, response);
|
||||||
// TODO: Read and handle the extended CSD.
|
if (rc) {
|
||||||
|
mmc_print(mmc, "could not set the card's relative address! (%d)", rc);
|
||||||
|
return EPIPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and handle card's Card Specific Data (CSD).
|
||||||
|
rc = sdmmc_read_and_parse_csd(mmc);
|
||||||
|
if (rc) {
|
||||||
|
mmc_print(mmc, "could not populate CSD attributes! (%d)", rc);
|
||||||
|
return EPIPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
if (rc) {
|
||||||
|
mmc_print(mmc, "could not select the active card for use! (%d)", rc);
|
||||||
|
return EPIPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and handle card's Extended Card Specific Data (ext-CSD).
|
||||||
|
rc = sdmmc_read_and_parse_ext_csd(mmc);
|
||||||
|
if (rc) {
|
||||||
|
mmc_print(mmc, "could not populate extended-CSD attributes! (%d)", rc);
|
||||||
|
return EPIPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle any speciailized initialization required by the given device type.
|
||||||
|
*
|
||||||
|
* @param mmc The device to initialize.
|
||||||
|
*/
|
||||||
|
static int sdmmc_handle_card_type_init(struct mmc *mmc)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
switch(mmc->card_type) {
|
||||||
|
|
||||||
|
// Handle initialization of eMMC cards.
|
||||||
|
case MMC_CARD_EMMC:
|
||||||
|
// FIXME: also handle MMC and SD cards that aren't eMMC
|
||||||
|
rc = emmc_card_init(mmc);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
mmc_print(mmc,"initialization of this device not yet supported!");
|
||||||
|
rc = ENOTTY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
/**
|
|
||||||
* Set up a new SDMMC driver.
|
* Set up a new SDMMC driver.
|
||||||
* FIXME: clean up!
|
* FIXME: clean up!
|
||||||
*
|
*
|
||||||
|
@ -917,29 +1061,39 @@ int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller)
|
||||||
// Get a reference to the registers for the relevant SDMMC controller.
|
// Get a reference to the registers for the relevant SDMMC controller.
|
||||||
mmc->regs = sdmmc_get_regs(controller);
|
mmc->regs = sdmmc_get_regs(controller);
|
||||||
mmc->name = "eMMC";
|
mmc->name = "eMMC";
|
||||||
|
mmc->card_type = MMC_CARD_EMMC;
|
||||||
|
|
||||||
// Default to a timeout of 1S.
|
// Default to a timeout of 1S.
|
||||||
// FIXME: lower
|
// FIXME: lower
|
||||||
// FIXME: abstract
|
// FIXME: abstract
|
||||||
mmc->timeout = 1000000;
|
mmc->timeout = 1000000;
|
||||||
|
|
||||||
|
// Default to relative address of zero.
|
||||||
|
mmc->relative_address = 0;
|
||||||
|
|
||||||
// Initialize the raw SDMMC controller.
|
// Initialize the raw SDMMC controller.
|
||||||
mmc_print(mmc, "setting up hardware");
|
|
||||||
rc = sdmmc_hardware_init(mmc);
|
rc = sdmmc_hardware_init(mmc);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
mmc_print(mmc, "failed to set up controller! (%d)", rc);
|
mmc_print(mmc, "failed to set up controller! (%d)", rc);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the SDMMC card.
|
// Handle the initialization that's specific to the card type.
|
||||||
mmc_print(mmc, "setting up card");
|
rc = sdmmc_handle_card_type_init(mmc);
|
||||||
rc = sdmmc_card_init(mmc);
|
|
||||||
if (rc) {
|
if (rc) {
|
||||||
mmc_print(mmc, "failed to set up card! (%d)", rc);
|
mmc_print(mmc, "failed to set run card-specific initialization (%d)!", rc);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle the initialization that's common to all card types.
|
||||||
|
rc = sdmmc_card_init(mmc);
|
||||||
|
if (rc) {
|
||||||
|
mmc_print(mmc, "failed to set up card (%d)!", rc);
|
||||||
return rc;
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,17 @@
|
||||||
/* Opaque pointer to the Tegra SDMMC registers */
|
/* Opaque pointer to the Tegra SDMMC registers */
|
||||||
struct tegra_sdmmc;
|
struct tegra_sdmmc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the different types of devices an MMC object
|
||||||
|
* can represent.
|
||||||
|
*/
|
||||||
|
enum mmc_card_type {
|
||||||
|
MMC_CARD_EMMC,
|
||||||
|
MMC_CARD_MMC,
|
||||||
|
MMC_CARD_SD,
|
||||||
|
MMC_CARD_CART,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary data structure describing a Fusée MMC driver.
|
* Primary data structure describing a Fusée MMC driver.
|
||||||
*/
|
*/
|
||||||
|
@ -16,10 +27,11 @@ struct mmc {
|
||||||
/* Controller properties */
|
/* Controller properties */
|
||||||
char *name;
|
char *name;
|
||||||
unsigned int timeout;
|
unsigned int timeout;
|
||||||
|
enum mmc_card_type card_type;
|
||||||
|
|
||||||
/* Card properties */
|
/* Card properties */
|
||||||
uint8_t cid[15];
|
uint8_t cid[15];
|
||||||
|
uint32_t relative_address;
|
||||||
|
|
||||||
/* Pointers to hardware structures */
|
/* Pointers to hardware structures */
|
||||||
volatile struct tegra_sdmmc *regs;
|
volatile struct tegra_sdmmc *regs;
|
||||||
|
|
Loading…
Reference in a new issue