mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-05 19:51:45 +00:00
fusee: further flesh out sdmmc implementation
This commit is contained in:
parent
2e3af0c474
commit
dbb65428e8
4 changed files with 161 additions and 29 deletions
|
@ -54,7 +54,10 @@ enum {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
CLK_SOURCE_SDMMC1 = 19,
|
||||||
CLK_SOURCE_SDMMC4 = 21, /* 0x54 into the the main source block */
|
CLK_SOURCE_SDMMC4 = 21, /* 0x54 into the the main source block */
|
||||||
|
|
||||||
|
CLK_SOURCE_SDMMC_LEGACY = 0, /* first in block Y */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ struct PACKED tegra_sdmmc {
|
||||||
uint32_t argument;
|
uint32_t argument;
|
||||||
uint16_t transfer_mode;
|
uint16_t transfer_mode;
|
||||||
uint16_t command;
|
uint16_t command;
|
||||||
uint16_t response[0x8];
|
uint32_t response[0x4];
|
||||||
uint32_t buffer;
|
uint32_t buffer;
|
||||||
uint32_t present_state;
|
uint32_t present_state;
|
||||||
uint8_t host_control;
|
uint8_t host_control;
|
||||||
|
@ -110,6 +110,15 @@ enum sdmmc_response_type {
|
||||||
MMC_RESPONSE_LEN136 = 1,
|
MMC_RESPONSE_LEN136 = 1,
|
||||||
MMC_RESPONSE_LEN48 = 2,
|
MMC_RESPONSE_LEN48 = 2,
|
||||||
MMC_RESPONSE_LEN48_CHK_BUSY = 3,
|
MMC_RESPONSE_LEN48_CHK_BUSY = 3,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lengths of SD command responses
|
||||||
|
*/
|
||||||
|
enum sdmmc_response_sizes {
|
||||||
|
/* Bytes in a LEN136 response */
|
||||||
|
MMC_RESPONSE_SIZE_LEN136 = 15,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -153,6 +162,7 @@ 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),
|
||||||
|
@ -175,6 +185,7 @@ enum sdmmc_command {
|
||||||
CMD_SWITCH_MODE = 6,
|
CMD_SWITCH_MODE = 6,
|
||||||
CMD_TOGGLE_CARD_SELECT = 7,
|
CMD_TOGGLE_CARD_SELECT = 7,
|
||||||
CMD_SEND_EXT_CSD = 8,
|
CMD_SEND_EXT_CSD = 8,
|
||||||
|
CMD_SEND_IF_COND = 8,
|
||||||
CMD_SEND_CSD = 9,
|
CMD_SEND_CSD = 9,
|
||||||
CMD_SEND_CID = 10,
|
CMD_SEND_CID = 10,
|
||||||
CMD_STOP_TRANSMISSION = 12,
|
CMD_STOP_TRANSMISSION = 12,
|
||||||
|
@ -186,9 +197,17 @@ enum sdmmc_command {
|
||||||
CMD_READ_MULTIPLE_BLOCK = 18,
|
CMD_READ_MULTIPLE_BLOCK = 18,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SDMMC command argument numbers
|
||||||
|
*/
|
||||||
|
enum sdmmc_command_magic {
|
||||||
|
MMC_SEND_IF_COND_MAGIC = 0xaa,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page-aligned bounce buffer to target with SDMMC DMA.
|
* Page-aligned bounce buffer to target with SDMMC DMA.
|
||||||
|
* FIXME: size this thing
|
||||||
*/
|
*/
|
||||||
static uint8_t ALIGN(4096) sdmmc_bounce_buffer[4096 * 4];
|
static uint8_t ALIGN(4096) sdmmc_bounce_buffer[4096 * 4];
|
||||||
|
|
||||||
|
@ -283,7 +302,6 @@ static int sdmmc_hardware_init(struct mmc *mmc)
|
||||||
|
|
||||||
mmc_print(mmc, "initializing in %s-speed mode...", is_hs400_hs667 ? "high" : "low");
|
mmc_print(mmc, "initializing in %s-speed mode...", is_hs400_hs667 ? "high" : "low");
|
||||||
|
|
||||||
|
|
||||||
// FIXME: set up clock and reset to fetch the relevant clock register offsets
|
// FIXME: set up clock and reset to fetch the relevant clock register offsets
|
||||||
|
|
||||||
// Put SDMMC4 in reset
|
// Put SDMMC4 in reset
|
||||||
|
@ -292,6 +310,9 @@ static int sdmmc_hardware_init(struct mmc *mmc)
|
||||||
// Set SDMMC4 clock source (PLLP_OUT0) and divisor (1)
|
// Set SDMMC4 clock source (PLLP_OUT0) and divisor (1)
|
||||||
car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | CLK_DIVIDER_UNITY;
|
car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | CLK_DIVIDER_UNITY;
|
||||||
|
|
||||||
|
// Set the legacy divier used for
|
||||||
|
car->clk_src_y[CLK_SOURCE_SDMMC_LEGACY] = CLK_SOURCE_FIRST | CLK_DIVIDER_UNITY;
|
||||||
|
|
||||||
// Set SDMMC4 clock enable
|
// Set SDMMC4 clock enable
|
||||||
car->clk_dev_l_set |= 0x8000;
|
car->clk_dev_l_set |= 0x8000;
|
||||||
|
|
||||||
|
@ -679,21 +700,99 @@ static void sdmmc_enable_interrupts(struct mmc *mmc, bool enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the response to an SDMMC command, copying the data
|
||||||
|
* from the SDMMC response holding area to the user-provided response buffer.
|
||||||
|
*/
|
||||||
|
static void sdmmc_handle_command_response(struct mmc *mmc,
|
||||||
|
enum sdmmc_response_type type, void *response_buffer)
|
||||||
|
{
|
||||||
|
uint32_t *buffer = (uint32_t *)response_buffer;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
// Easy case: we don't have a response. We don't need to do anything.
|
||||||
|
case MMC_RESPONSE_NONE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// If we have a 48-bit response, then we have 32 bits of response and 16 bits of CRC.
|
||||||
|
// The naming is a little odd, but that's thanks to the SDMMC standard.
|
||||||
|
case MMC_RESPONSE_LEN48:
|
||||||
|
case MMC_RESPONSE_LEN48_CHK_BUSY:
|
||||||
|
*buffer = mmc->regs->response[0];
|
||||||
|
|
||||||
|
mmc_print(mmc, "response: %08x", *buffer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// If we have a 136-bit response, we have 120 response and 16 bits of CRC.
|
||||||
|
// TODO: validate that this is the right format/endianness/everything
|
||||||
|
case MMC_RESPONSE_LEN136:
|
||||||
|
|
||||||
|
// Clear the final byte of the buffer, as it won't have a full copy.
|
||||||
|
// (We don't copy in the CRC.):
|
||||||
|
buffer[3] = 0;
|
||||||
|
|
||||||
|
// Copy the response to the buffer manually.
|
||||||
|
// We avoid memcpy here, because this is volatile.
|
||||||
|
for(int i = 0; i < 4; ++i)
|
||||||
|
buffer[i] = mmc->regs->response[i];
|
||||||
|
|
||||||
|
mmc_print(mmc, "response: %08x%08x%08x%08x", buffer[0], buffer[1], buffer[2], buffer[3]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
mmc_print(mmc, "invalid response type; not handling response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles copying data from the SDMMC bounce buffer to the final target buffer.
|
||||||
|
*
|
||||||
|
* @param blocks_to_transfer The number of SDMMC blocks to be transferred with the given command,
|
||||||
|
* or 0 to indicate that this command should not expect response data.
|
||||||
|
* @param is_write True iff the given command issues data _to_ the card, instead of vice versa.
|
||||||
|
* @param data_buffer A byte buffer that either contains the data to be sent, or which should
|
||||||
|
* receive data, depending on the is_write argument.
|
||||||
|
*/
|
||||||
|
static void sdmmc_handle_command_data(struct mmc *mmc, int blocks_to_transfer,
|
||||||
|
bool is_write, void *data_buffer)
|
||||||
|
{
|
||||||
|
(void)blocks_to_transfer;
|
||||||
|
(void)is_write;
|
||||||
|
(void)data_buffer;
|
||||||
|
|
||||||
|
mmc_print(mmc, "WARNING: not handling command data yet -- not implemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a command to the SD card, and awaits a response.
|
* Sends a command to the SD card, and awaits a response.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* @param blocks_to_transfer The number of SDMMC blocks to be transferred with the given command,
|
||||||
|
* or 0 to indicate that this command should not expect response data.
|
||||||
|
* @param is_write True iff the given command issues data _to_ the card, instead of vice versa.
|
||||||
|
* @param data_buffer A byte buffer that either contains the data to be sent, or which should
|
||||||
|
* receive data, depending on the is_write argument.
|
||||||
|
*
|
||||||
|
* @returns 0 on success, an error number on failure
|
||||||
*/
|
*/
|
||||||
static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
||||||
enum sdmmc_response_type response_type, enum sdmmc_response_checks checks,
|
enum sdmmc_response_type response_type, enum sdmmc_response_checks checks,
|
||||||
uint32_t argument, int blocks_to_transfer, bool is_write)
|
uint32_t argument, void *response_buffer, int blocks_to_transfer,
|
||||||
|
bool is_write, void *data_buffer)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
// XXX: get rid of
|
||||||
mmc_print(mmc, "issuing CMD%d", command);
|
mmc_print(mmc, "issuing CMD%d", command);
|
||||||
|
|
||||||
// Wait until we can issue commands to the device.
|
// Wait until we can issue commands to the device.
|
||||||
mmc_print(mmc, "waiting for command readiness...");
|
|
||||||
rc = sdmmc_wait_for_command_readiness(mmc);
|
rc = sdmmc_wait_for_command_readiness(mmc);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
mmc_print(mmc, "card not willing to accept commands (%d / %08x)", rc, mmc->regs->present_state);
|
mmc_print(mmc, "card not willing to accept commands (%d / %08x)", rc, mmc->regs->present_state);
|
||||||
|
@ -701,18 +800,15 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have data to send, prepare it.
|
// If we have data to send, prepare it.
|
||||||
mmc_print(mmc, "preparing data...");
|
|
||||||
sdmmc_prepare_command_data(mmc, blocks_to_transfer, is_write, argument);
|
sdmmc_prepare_command_data(mmc, blocks_to_transfer, is_write, argument);
|
||||||
|
|
||||||
// Configure the controller to send the command.
|
// Configure the controller to send the command.
|
||||||
mmc_print(mmc, "preparing command...");
|
|
||||||
sdmmc_prepare_command_registers(mmc, blocks_to_transfer, command, response_type, checks);
|
sdmmc_prepare_command_registers(mmc, blocks_to_transfer, command, response_type, checks);
|
||||||
|
|
||||||
// Ensure we get the status response we want.
|
// Ensure we get the status response we want.
|
||||||
sdmmc_enable_interrupts(mmc, true);
|
sdmmc_enable_interrupts(mmc, true);
|
||||||
|
|
||||||
// Wait for the command to be completed.
|
// Wait for the command to be completed.
|
||||||
mmc_print(mmc, "waiting for command completion...");
|
|
||||||
rc = sdmmc_wait_for_command_completion(mmc);
|
rc = sdmmc_wait_for_command_completion(mmc);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
mmc_print(mmc, "failed to issue CMD%d (%d / %08x)", command, rc, mmc->regs->int_status);
|
mmc_print(mmc, "failed to issue CMD%d (%d / %08x)", command, rc, mmc->regs->int_status);
|
||||||
|
@ -722,19 +818,10 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
||||||
|
|
||||||
// Disable resporting psuedo-interrupts.
|
// Disable resporting psuedo-interrupts.
|
||||||
// (This is mostly for when the GIC is brought up)
|
// (This is mostly for when the GIC is brought up)
|
||||||
sdmmc_enable_interrupts(mmc, true);
|
sdmmc_enable_interrupts(mmc, false);
|
||||||
|
|
||||||
|
// Copy the response received to the output buffer, if applicable.
|
||||||
// TODO: copy response to an out argument, if it we have one?
|
sdmmc_handle_command_response(mmc, response_type, response_buffer);
|
||||||
|
|
||||||
// FIXME: remove, this is excessive
|
|
||||||
if (response_type != MMC_RESPONSE_NONE) {
|
|
||||||
mmc_print(mmc, "response: %04x %04x %04x %04x %04x %04x %04x %04x",
|
|
||||||
mmc->regs->response[0], mmc->regs->response[1],
|
|
||||||
mmc->regs->response[2], mmc->regs->response[2],
|
|
||||||
mmc->regs->response[4], mmc->regs->response[5],
|
|
||||||
mmc->regs->response[6], mmc->regs->response[7]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we had a data stage, handle it.
|
// If we had a data stage, handle it.
|
||||||
if (blocks_to_transfer) {
|
if (blocks_to_transfer) {
|
||||||
|
@ -747,7 +834,8 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: copy data from the bounce buffer to the output buffer, if this was a read
|
// Copy the SDMMC result from the bounce buffer to the target buffer.
|
||||||
|
sdmmc_handle_command_data(mmc, blocks_to_transfer, is_write, data_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
mmc_print(mmc, "command success!");
|
mmc_print(mmc, "command success!");
|
||||||
|
@ -755,6 +843,18 @@ static int sdmmc_send_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)
|
||||||
|
{
|
||||||
|
// If we don't expect a response, don't check; otherwise check everything.
|
||||||
|
enum sdmmc_response_checks checks = (response_type == MMC_RESPONSE_NONE) ? MMC_CHECKS_NONE : MMC_CHECKS_ALL;
|
||||||
|
|
||||||
|
// Deletegate the full checks function.
|
||||||
|
return sdmmc_send_command(mmc, command, response_type, checks, argument, response_buffer, 0, 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about the card, and populates the MMC structure accordingly.
|
* Retrieves information about the card, and populates the MMC structure accordingly.
|
||||||
* Used as part of the SDMMC initialization process.
|
* Used as part of the SDMMC initialization process.
|
||||||
|
@ -762,14 +862,40 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
|
||||||
static int sdmmc_card_init(struct mmc *mmc)
|
static int sdmmc_card_init(struct mmc *mmc)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
|
uint8_t response[17];
|
||||||
|
|
||||||
// Bring the bus out of its idle state.
|
// Bring the bus out of its idle state.
|
||||||
rc = sdmmc_send_command(mmc, CMD_GO_IDLE_OR_INIT, MMC_RESPONSE_NONE, MMC_CHECKS_NONE, 0, 0, 0);
|
rc = sdmmc_send_simple_command(mmc, CMD_GO_IDLE_OR_INIT, MMC_RESPONSE_NONE, 0, NULL);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
mmc_print(mmc, "could not bring bus to idle!");
|
mmc_print(mmc, "could not bring bus to idle!");
|
||||||
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);
|
||||||
|
if (rc) {
|
||||||
|
// If we had an error, this was a v1 card.
|
||||||
|
mmc_print(mmc, "handling as a v1.0 card.");
|
||||||
|
} 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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retreive the card ID.
|
||||||
|
rc = sdmmc_send_simple_command(mmc, CMD_ALL_SEND_CID, MMC_RESPONSE_LEN136, 0, response);
|
||||||
|
if (rc) {
|
||||||
|
mmc_print(mmc, "could not fetch the CID");
|
||||||
|
return ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the card ID for later.
|
||||||
|
memcpy(mmc->cid, response, sizeof(mmc->cid));
|
||||||
|
|
||||||
|
// TODO: Read and handle the CSD.
|
||||||
|
// TODO: Toggle the card select, so we it knows we're talking to it.
|
||||||
|
// TODO: Read and handle the extended CSD.
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -813,8 +939,6 @@ int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,11 +13,16 @@ struct tegra_sdmmc;
|
||||||
* Primary data structure describing a Fusée MMC driver.
|
* Primary data structure describing a Fusée MMC driver.
|
||||||
*/
|
*/
|
||||||
struct mmc {
|
struct mmc {
|
||||||
|
/* Controller properties */
|
||||||
char *name;
|
char *name;
|
||||||
|
|
||||||
volatile struct tegra_sdmmc *regs;
|
|
||||||
|
|
||||||
unsigned int timeout;
|
unsigned int timeout;
|
||||||
|
|
||||||
|
/* Card properties */
|
||||||
|
uint8_t cid[15];
|
||||||
|
|
||||||
|
|
||||||
|
/* Pointers to hardware structures */
|
||||||
|
volatile struct tegra_sdmmc *regs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ __attribute__ ((noreturn)) void panic(uint32_t code) {
|
||||||
APBDEV_PMC_SCRATCH0_0 = (1 << 1);
|
APBDEV_PMC_SCRATCH0_0 = (1 << 1);
|
||||||
|
|
||||||
/* Reset the processor. */
|
/* Reset the processor. */
|
||||||
APBDEV_PMC_CONTROL = (1 < 4);
|
APBDEV_PMC_CONTROL = (1 << 4);
|
||||||
while(1);
|
while(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue