fusee: sdmmc: support CPU reads in addition to (broken?) DMA

This commit is contained in:
Kate J. Temkin 2018-04-29 04:45:28 -06:00
parent 21c177804e
commit ef1923ebab
2 changed files with 159 additions and 58 deletions

View file

@ -138,8 +138,10 @@ enum sdmmc_response_checks {
enum sdmmc_register_bits { enum sdmmc_register_bits {
/* Present state register */ /* Present state register */
MMC_COMMAND_INHIBIT = 1 << 0, MMC_COMMAND_INHIBIT = (1 << 0),
MMC_DATA_INHIBIT = 1 << 1, MMC_DATA_INHIBIT = (1 << 1),
MMC_BUFFER_WRITE_ENABLE = (1 << 10),
MMC_BUFFER_READ_ENABLE = (1 << 11),
/* Block size register */ /* Block size register */
MMC_DMA_BOUNDARY_MAXIMUM = (0x3 << 12), MMC_DMA_BOUNDARY_MAXIMUM = (0x3 << 12),
@ -662,6 +664,7 @@ static int sdmmc_wait_for_transfer_completion(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) {
@ -672,20 +675,10 @@ static int sdmmc_wait_for_transfer_completion(struct mmc *mmc)
if (mmc->regs->int_status & MMC_STATUS_TRANSFER_COMPLETE) if (mmc->regs->int_status & MMC_STATUS_TRANSFER_COMPLETE)
return 0; return 0;
// Automatically traverse DMA page boundaries. // If we've hit a DMA page boundary, fault.
if (mmc->regs->int_status & MMC_STATUS_DMA_INTERRUPT) { if (mmc->regs->int_status & MMC_STATUS_DMA_INTERRUPT) {
mmc_print(mmc, "transaction would overrun the DMA buffer!");
// The SDMMC SDMA architecture is designed to pause at page return -EFAULT;
// boundaries to allow for CPU-assisted scatter gather. We don't
// use the scatter-gather feature, but we do need to "unpause"
// by telling it the next DMA address.
//
// Since we're always continuing as is, we'll read the current
// DMA address, clear the DMA interrupt, and then write the DMA
// address back. This is our "unpause".
uint32_t address = mmc->regs->dma_address;
mmc->regs->int_status |= MMC_STATUS_DMA_INTERRUPT;
mmc->regs->dma_address = address;
} }
// If an error occurs, return it. // If an error occurs, return it.
@ -695,6 +688,90 @@ static int sdmmc_wait_for_transfer_completion(struct mmc *mmc)
} }
/**
* Returns the block size for a given operation on the MMC controller.
*
* @param mmc The MMC controller for which we're quierying block size.
* @param is_write True iff the given operation is a write.
*/
static uint32_t sdmmc_get_block_size(struct mmc *mmc, bool is_write)
{
// FIXME: support write blocks?
(void)is_write;
return (1 << mmc->read_block_order);
}
/**
* Handles execution of a DATA stage using the CPU, rather than by using DMA.
*
* @param mmc The MMc controller to work with.
* @param blocks The number of blocks to work with.
* @param is_write True iff the data is being set _to_ the CARD.
* @param data_buffer The data buffer to be transmitted or populated.
*
* @return 0 on success, or an error code on failure.
*/
static int sdmmc_handle_cpu_transfer(struct mmc *mmc, uint16_t blocks, bool is_write, void *data_buffer)
{
uint16_t blocks_remaining = blocks;
uint16_t bytes_remaining_in_block;
uint32_t timebase = get_time();
// Get a window that lets us work with the data buffer in 32-bit chunks.
uint32_t *buffer = data_buffer;
// Figure out the mask to check based on whether this is a read or a write.
uint32_t mask = is_write ? MMC_BUFFER_WRITE_ENABLE : MMC_BUFFER_READ_ENABLE;
// While we have blocks left to read...
while (blocks_remaining) {
// Get the number of bytes per block read.
bytes_remaining_in_block = sdmmc_get_block_size(mmc, false);
// Wait for a block read to complete.
while (!(mmc->regs->present_state & mask)) {
// If an error occurs, return it.
if (mmc->regs->int_status & MMC_STATUS_ERROR_MASK) {
return (mmc->regs->int_status & MMC_STATUS_ERROR_MASK);
}
// Check for timeout.
if (get_time_since(timebase) > mmc->timeout)
return ETIMEDOUT;
}
// While we've still bytes left to read.
while (bytes_remaining_in_block) {
// Check for timeout.
if (get_time_since(timebase) > mmc->timeout)
return ETIMEDOUT;
// Transfer the data to the relevant
if (is_write) {
mmc->regs->buffer = *buffer;
} else {
*buffer = mmc->regs->buffer;
}
// Advance by a register size...
bytes_remaining_in_block -= sizeof(mmc->regs->buffer);
++buffer;
}
// Advice by a block...
--blocks_remaining;
}
return 0;
}
/** /**
* Prepare the data-related registers for command transmission. * Prepare the data-related registers for command transmission.
* *
@ -704,9 +781,10 @@ static int sdmmc_wait_for_transfer_completion(struct mmc *mmc)
*/ */
static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks, bool is_write, int argument) static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks, bool is_write, int argument)
{ {
// Ensure we're targeting our bounce buffer.
if (blocks) { if (blocks) {
mmc->regs->dma_address = (uint32_t)sdmmc_bounce_buffer; // If we're using DMA, target our bounce buffer.
if (mmc->use_dma)
mmc->regs->dma_address = (uint32_t)sdmmc_bounce_buffer;
// Set up the DMA block size and count. // Set up the DMA block size and count.
// This is synchronized with the size of our bounce buffer. // This is synchronized with the size of our bounce buffer.
@ -719,13 +797,16 @@ static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks, bool is
// Always use DMA mode for data, as that's what Nintendo does. :) // Always use DMA mode for data, as that's what Nintendo does. :)
if (blocks) { if (blocks) {
uint32_t to_write = MMC_TRANSFER_DMA_ENABLE | MMC_TRANSFER_LIMIT_BLOCK_COUNT; uint32_t to_write = MMC_TRANSFER_LIMIT_BLOCK_COUNT;
// If this controller should use DMA, set that up.
if (mmc->use_dma)
to_write |= MMC_TRANSFER_DMA_ENABLE;
// If this is a multi-block datagram, indicate so. // If this is a multi-block datagram, indicate so.
// Also, configure the host to automatically stop the card when transfers are complete. // Also, configure the host to automatically stop the card when transfers are complete.
if (blocks > 1) { if (blocks > 1)
to_write |= (MMC_TRANSFER_MULTIPLE_BLOCKS | MMC_TRANSFER_AUTO_CMD12); to_write |= (MMC_TRANSFER_MULTIPLE_BLOCKS | MMC_TRANSFER_AUTO_CMD12);
}
// If this is a read, set the READ mode. // If this is a read, set the READ mode.
if (!is_write) if (!is_write)
@ -837,20 +918,6 @@ static void sdmmc_handle_command_response(struct mmc *mmc,
} }
/**
* Returns the block size for a given operation on the MMC controller.
*
* @param mmc The MMC controller for which we're quierying block size.
* @param is_write True iff the given operation is a write.
*/
static uint32_t sdmmc_get_block_size(struct mmc *mmc, bool is_write)
{
// FIXME: support write blocks?
(void)is_write;
return (1 << mmc->read_block_order);
}
/** /**
* Sends a command to the SD card, and awaits a response. * Sends a command to the SD card, and awaits a response.
@ -913,7 +980,7 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
// If this is a write and we have data, we'll need to populate the bounce buffer before // If this is a write and we have data, we'll need to populate the bounce buffer before
// issuing the command. // issuing the command.
if (blocks_to_transfer && is_write) { if (blocks_to_transfer && is_write && mmc->use_dma) {
memcpy(sdmmc_bounce_buffer, data_buffer, total_data_to_xfer); memcpy(sdmmc_bounce_buffer, data_buffer, total_data_to_xfer);
} }
@ -939,38 +1006,52 @@ static int sdmmc_send_command(struct mmc *mmc, enum sdmmc_command command,
// If we had a data stage, handle it. // If we had a data stage, handle it.
if (blocks_to_transfer) { if (blocks_to_transfer) {
// Wait for the transfer to be complete... // If this is a DMA transfer, wait for its completion.
mmc_print(mmc, "waiting for transfer completion..."); if (mmc->use_dma) {
rc = sdmmc_wait_for_transfer_completion(mmc);
if (rc) {
mmc_print(mmc, "failed to complete CMD%d data stage (%d)", command, rc);
sdmmc_enable_interrupts(mmc, false); // Wait for the transfer to be complete...
return rc; mmc_print(mmc, "waiting for transfer completion...");
} rc = sdmmc_wait_for_transfer_completion(mmc);
if (rc) {
// If this is a read, and we've just finished a transfer, copy the data from mmc_print(mmc, "failed to complete CMD%d data stage via DMA (%d)", command, rc);
// our bounce buffer to the target data buffer. sdmmc_enable_interrupts(mmc, false);
if (!is_write) { return rc;
printk("read result (%d):\n", total_data_to_xfer); }
for (int i = 0; i < 64; ++i) {
// If this is a read, and we've just finished a transfer, copy the data from
if(i % 8 == 0) { // our bounce buffer to the target data buffer.
printk("\n"); if (!is_write) {
} memcpy(data_buffer, sdmmc_bounce_buffer, total_data_to_xfer);
printk("%02x ", sdmmc_bounce_buffer[i]);
} }
printk("\n");
memcpy(data_buffer, sdmmc_bounce_buffer, total_data_to_xfer);
} }
// Otherwise, perform the transfer using the CPU.
else {
mmc_print(mmc, "transferring data...");
rc = sdmmc_handle_cpu_transfer(mmc, blocks_to_transfer, is_write, data_buffer);
if (rc) {
mmc_print(mmc, "failed to complete CMD%d data stage via CPU (%d)", command, rc);
sdmmc_enable_interrupts(mmc, false);
return rc;
}
}
// XXX: get rid of this
printk("read result (%d):\n", total_data_to_xfer);
for (int i = 0; i < 64; ++i) {
if(i % 8 == 0) {
printk("\n");
}
printk("%02x ", ((uint8_t *)data_buffer)[i]);
}
printk("\n");
} }
// 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, false); sdmmc_enable_interrupts(mmc, false);
mmc_print(mmc, "CMD%d success!", command); mmc_print(mmc, "CMD%d success!", command);
return 0; return 0;
} }
@ -1217,6 +1298,15 @@ static int sdmmc_set_up_block_transfer_size(struct mmc *mmc)
return 0; return 0;
} }
/**
* Optimize our SDMMC transfer mode to fully utilize the bus.
*/
static int sdmmc_optimize_transfer_mode(struct mmc *mmc)
{
// TODO: FIXME
return 0;
}
/** /**
@ -1266,6 +1356,12 @@ static int sdmmc_card_init(struct mmc *mmc)
return EPIPE; return EPIPE;
} }
// Switch to a transfer mode that can more efficiently utilize the bus.
rc = sdmmc_optimize_transfer_mode(mmc);
if (rc) {
mmc_print(mmc, "could not optimize bus utlization! (%d)", rc);
}
// Read and handle card's Extended Card Specific Data (ext-CSD). // Read and handle card's Extended Card Specific Data (ext-CSD).
rc = sdmmc_read_and_parse_ext_csd(mmc); rc = sdmmc_read_and_parse_ext_csd(mmc);
if (rc) { if (rc) {
@ -1327,6 +1423,9 @@ int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller)
// FIXME: abstract // FIXME: abstract
mmc->timeout = 1000000; mmc->timeout = 1000000;
// FIXME: make this configurable?
mmc->use_dma = false;
// Default to relative address of zero. // Default to relative address of zero.
mmc->relative_address = 0; mmc->relative_address = 0;

View file

@ -27,7 +27,9 @@ struct mmc {
/* Controller properties */ /* Controller properties */
char *name; char *name;
unsigned int timeout; unsigned int timeout;
enum mmc_card_type card_type; enum mmc_card_type card_type;
bool use_dma;
/* Card properties */ /* Card properties */
uint8_t cid[15]; uint8_t cid[15];