fusee: fix issue with SDMMC psuedo-interrupts

This commit is contained in:
Kate J. Temkin 2018-04-28 00:34:32 -06:00
parent fbe159e4d3
commit 2e3af0c474
4 changed files with 192 additions and 52 deletions

View file

@ -0,0 +1,14 @@
#ifndef __APB_MISC_H__
#define __APB_MISC_H__
/* FIXME: clean up */
#define MISC_BASE (0x70000000UL)
#define PINMUX_BASE (MISC_BASE + 0x3000)
#define PINMUX_AUX_GPIO_PZ1_0 (*(volatile uint32_t *)(PINMUX_BASE + 0x280))
#define APB_MISC_GP_VGPIO_GPIO_MUX_SEL_0 MAKE_REG32(MISC_BASE + 0xb74)
#define APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL_0 MAKE_REG32(MISC_BASE + 0xa98)
#define APB_MISC_GP_EMMC4_PAD_CFGPADCTRL_0 MAKE_REG32(MISC_BASE + 0xab4)
#endif

View file

@ -5,6 +5,10 @@
#define PMC_BASE 0x7000E400 #define PMC_BASE 0x7000E400
#define APBDEV_PMC_CONTROL MAKE_REG32(PMC_BASE + 0x00)
#define APBDEV_PMC_DPD_ENABLE_0 MAKE_REG32(PMC_BASE + 0x24) #define APBDEV_PMC_DPD_ENABLE_0 MAKE_REG32(PMC_BASE + 0x24)
#define APBDEV_PMC_PWRGATE_TOGGLE_0 MAKE_REG32(PMC_BASE + 0x30) #define APBDEV_PMC_PWRGATE_TOGGLE_0 MAKE_REG32(PMC_BASE + 0x30)

View file

@ -112,6 +112,17 @@ enum sdmmc_response_type {
MMC_RESPONSE_LEN48_CHK_BUSY = 3, MMC_RESPONSE_LEN48_CHK_BUSY = 3,
}; };
/**
* SDMMC response sanity checks
* see the standard for when these should be used
*/
enum sdmmc_response_checks {
MMC_CHECKS_NONE = 0,
MMC_CHECKS_CRC = (1 << 3),
MMC_CHECKS_INDEX = (1 << 4),
MMC_CHECKS_ALL = (1 << 4) | (1 << 3),
};
/** /**
* General masks for SDMMC registers. * General masks for SDMMC registers.
*/ */
@ -136,10 +147,18 @@ enum sdmmc_register_bits {
/* Interrupt status */ /* Interrupt status */
MMC_STATUS_COMMAND_COMPLETE = (1 << 0), MMC_STATUS_COMMAND_COMPLETE = (1 << 0),
MMC_STATUS_TRANSFER_COMPLETE = (1 << 1), MMC_STATUS_TRANSFER_COMPLETE = (1 << 1),
MMC_STATUS_COMMAND_TIMEOUT = (1 << 16),
MMC_STATUS_COMMAND_CRC_ERROR = (1 << 17),
MMC_STATUS_COMMAND_END_BIT_ERROR = (1 << 18),
MMC_STATUS_COMMAND_INDEX_ERROR = (1 << 19),
MMC_STATUS_ERROR_MASK = (0xF << 16), MMC_STATUS_ERROR_MASK = (0xF << 16),
/* Host control */ /* Host control */
MMC_DMA_SELECT_MASK = (0x3 << 3), MMC_DMA_SELECT_MASK = (0x3 << 3),
/* Software reset */
MMC_SOFT_RESET_FULL = (1 << 0),
}; };
@ -189,6 +208,25 @@ void mmc_print(struct mmc *mmc, char *fmt, ...)
va_end(list); va_end(list);
} }
/**
* Debug: print out any errors that occurred during a command timeout
*/
void mmc_print_command_errors(struct mmc *mmc, int command_errno)
{
if (command_errno & MMC_STATUS_COMMAND_TIMEOUT)
mmc_print(mmc, "ERROR: command timed out!");
if (command_errno & MMC_STATUS_COMMAND_CRC_ERROR)
mmc_print(mmc, "ERROR: command response had invalid CRC");
if (command_errno & MMC_STATUS_COMMAND_END_BIT_ERROR)
mmc_print(mmc, "error: command response had invalid end bit");
if (command_errno & MMC_STATUS_COMMAND_INDEX_ERROR)
mmc_print(mmc, "error: response appears not to be for the last issued command");
}
/** /**
* Retreives the SDMMC register range for the given controller. * Retreives the SDMMC register range for the given controller.
@ -206,6 +244,27 @@ static struct tegra_sdmmc *sdmmc_get_regs(enum sdmmc_controller controller)
} }
static int sdmmc_hardware_reset(struct mmc *mmc)
{
uint32_t timebase;
// Reset the MMC controller...
mmc->regs->software_reset |= MMC_SOFT_RESET_FULL;
timebase = get_time();
// Wait for the SDMMC controller to come back up...
while(mmc->regs->software_reset & MMC_SOFT_RESET_FULL) {
if (get_time_since(timebase) > mmc->timeout) {
mmc_print(mmc, "failed to bring up SDMMC controller");
return ETIMEDOUT;
}
}
return 0;
}
/** /**
* *
*/ */
@ -217,6 +276,8 @@ static int sdmmc_hardware_init(struct mmc *mmc)
uint32_t timebase; uint32_t timebase;
bool is_timeout; bool is_timeout;
int rc;
/* XXX fixme XXX */ /* XXX fixme XXX */
bool is_hs400_hs667 = false; bool is_hs400_hs667 = false;
@ -240,6 +301,14 @@ static int sdmmc_hardware_init(struct mmc *mmc)
// Take SDMMC4 out of reset // Take SDMMC4 out of reset
car->rst_dev_l_clr |= 0x8000; car->rst_dev_l_clr |= 0x8000;
// Software reset the SDMMC device
mmc_print(mmc, "resetting controller...");
rc = sdmmc_hardware_reset(mmc);
if (rc) {
mmc_print(mmc, "failed to reset!");
return rc;
}
// Set IO_SPARE[19] (one cycle delay) // Set IO_SPARE[19] (one cycle delay)
regs->io_spare |= 0x80000; regs->io_spare |= 0x80000;
@ -322,7 +391,7 @@ static int sdmmc_hardware_init(struct mmc *mmc)
} }
// Clear upper 17 bits // Clear upper 17 bits
regs->host_control2 &= ~(0xFFFE0000); regs->host_control2 &= ~(0xFFFF8000);
// Clear SDHCI_PROG_CLOCK_MODE // Clear SDHCI_PROG_CLOCK_MODE
regs->clock_control &= ~(0x20); regs->clock_control &= ~(0x20);
@ -451,7 +520,8 @@ static int sdmmc_wait_for_command_readiness(struct mmc *mmc)
// 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; mmc_print(mmc, "timed out waiting for command readiness!");
return ETIMEDOUT;
} }
// Wait until we're not inhibited from sending commands... // Wait until we're not inhibited from sending commands...
@ -472,21 +542,18 @@ static int sdmmc_wait_for_command_completion(struct mmc *mmc)
// 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; mmc_print(mmc, "timed out waiting for command completion!");
return ETIMEDOUT;
}
// If the command completes, return that. // If the command completes, return that.
if (mmc->regs->int_status & MMC_STATUS_COMMAND_COMPLETE) if (mmc->regs->int_status & MMC_STATUS_COMMAND_COMPLETE)
return 0; return 0;
// If the command's no longer active, and we don't have an error, use that.
// This is what the bootrom does?
if (!(mmc->regs->present_state & MMC_COMMAND_INHIBIT))
return 0;
// If an error occurs, return it. // If an error occurs, return it.
if (mmc->regs->int_status & MMC_STATUS_ERROR_MASK) if (mmc->regs->int_status & MMC_STATUS_ERROR_MASK)
return (mmc->regs->int_status & MMC_STATUS_ERROR_MASK) >> 16; return (mmc->regs->int_status & MMC_STATUS_ERROR_MASK);
} }
} }
@ -524,30 +591,37 @@ static int sdmmc_wait_for_transfer_completion(struct mmc *mmc)
* @param blocks The total number of blocks to be transferred. * @param blocks The total number of blocks to be transferred.
* @param is_write True iff we're sending data _to_ the card. * @param is_write True iff we're sending data _to_ the card.
*/ */
static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks, bool is_write) static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks, bool is_write, int argument)
{ {
// Ensure we're targeting our bounce buffer. // Ensure we're targeting our bounce buffer.
mmc->regs->dma_address = (uint32_t)sdmmc_bounce_buffer; if (blocks) {
mmc->regs->dma_address = (uint32_t)sdmmc_bounce_buffer;
// Ensure we're using System DMA mode for DMA. // Ensure we're using System DMA mode for DMA.
mmc->regs->host_control &= ~MMC_DMA_SELECT_MASK; mmc->regs->host_control &= ~MMC_DMA_SELECT_MASK;
// Set up the DMA block size and count. // Set up the DMA block size and count.
// FIXME: implement! // FIXME: implement!
mmc_print(mmc, "WARNING: block size and count register needs to be set up, but CSD code isnt done yet!"); mmc_print(mmc, "WARNING: block size and count register needs to be set up, but CSD code isnt done yet!");
mmc->regs->block_size = 0; mmc->regs->block_size = 0;
mmc->regs->block_count = 0; mmc->regs->block_count = 0;
}
// Populate the command argument.
mmc->regs->argument = argument;
// Always use DMA mode for data, as that's what Nintendo does. :) // Always use DMA mode for data, as that's what Nintendo does. :)
mmc->regs->transfer_mode = MMC_TRANSFER_DMA_ENABLE | MMC_TRANSFER_LIMIT_BLOCK_COUNT; if (blocks) {
mmc->regs->transfer_mode = MMC_TRANSFER_DMA_ENABLE | MMC_TRANSFER_LIMIT_BLOCK_COUNT ;
// If this is a multi-block datagram, indicate so. // If this is a multi-block datagram, indicate so.
if (blocks > 1) if (blocks > 1)
mmc->regs->transfer_mode |= MMC_TRANSFER_MULTIPLE_BLOCKS; mmc->regs->transfer_mode |= MMC_TRANSFER_MULTIPLE_BLOCKS;
// If this is a write, set the WRITE mode. // If this is a write, set the WRITE mode.
if (is_write) if (is_write)
mmc->regs->transfer_mode |= MMC_TRANSFER_HOST_TO_CARD; mmc->regs->transfer_mode |= MMC_TRANSFER_HOST_TO_CARD;
}
} }
@ -561,10 +635,10 @@ static void sdmmc_prepare_command_data(struct mmc *mmc, uint16_t blocks, bool is
* @param response_type The type of response we'll expect. * @param response_type The type of response we'll expect.
*/ */
static void sdmmc_prepare_command_registers(struct mmc *mmc, int blocks_to_xfer, static void sdmmc_prepare_command_registers(struct mmc *mmc, int blocks_to_xfer,
enum sdmmc_command command, enum sdmmc_response_type response_type) enum sdmmc_command command, enum sdmmc_response_type response_type, enum sdmmc_response_checks checks)
{ {
// Populate the command number // Populate the command number
uint16_t to_write = (command << MMC_COMMAND_NUMBER_SHIFT) | (response_type << MMC_COMMAND_RESPONSE_TYPE_SHIFT) | MMC_COMMAND_CHECK_NUMBER; uint16_t to_write = (command << MMC_COMMAND_NUMBER_SHIFT) | (response_type << MMC_COMMAND_RESPONSE_TYPE_SHIFT) | checks;
// If this is a "stop transmitting" command, set the abort flag. // If this is a "stop transmitting" command, set the abort flag.
if (command == CMD_STOP_TRANSMISSION) if (command == CMD_STOP_TRANSMISSION)
@ -580,49 +654,90 @@ static void sdmmc_prepare_command_registers(struct mmc *mmc, int blocks_to_xfer,
} }
/**
* Enables or disables the SDMMC interrupts.
* We leave these masked, but checkt their status in their status register.
*
* @param mmc The eMMC device to work with.
* @param enabled True if interrupts should enabled, or false to disable them.
*/
static void sdmmc_enable_interrupts(struct mmc *mmc, bool enabled)
{
// Get an mask that represents all interrupts.
uint32_t all_interrupts =
MMC_STATUS_COMMAND_COMPLETE | MMC_STATUS_TRANSFER_COMPLETE |
MMC_STATUS_ERROR_MASK;
// Clear any pending interrupts.
mmc->regs->int_status |= all_interrupts;
// And enable or disable the pseudo-interrupts.
if (enabled) {
mmc->regs->int_enable |= all_interrupts;
} else {
mmc->regs->int_enable &= ~all_interrupts;
}
}
/** /**
* Sends a command to the SD card, and awaits a response. * Sends a command to the SD card, and awaits a response.
*/ */
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, uint32_t argument, int blocks_to_transfer, bool is_write) enum sdmmc_response_type response_type, enum sdmmc_response_checks checks,
uint32_t argument, int blocks_to_transfer, bool is_write)
{ {
int rc; int rc;
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..."); 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);
return -EBUSY; return -EBUSY;
} }
// Populate the command argument.
mmc_print(mmc, "populating argument...");
mmc->regs->argument = argument;
// If we have data to send, prepare it. // If we have data to send, prepare it.
if (blocks_to_transfer) { mmc_print(mmc, "preparing data...");
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);
}
// Configure the controller to send the command. // Configure the controller to send the command.
mmc_print(mmc, "preparing command..."); mmc_print(mmc, "preparing command...");
sdmmc_prepare_command_registers(mmc, blocks_to_transfer, command, response_type); sdmmc_prepare_command_registers(mmc, blocks_to_transfer, command, response_type, checks);
// Ensure we get the status response we want.
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..."); 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);
mmc_print_command_errors(mmc, rc);
return rc; return rc;
} }
// Disable resporting psuedo-interrupts.
// (This is mostly for when the GIC is brought up)
sdmmc_enable_interrupts(mmc, true);
// TODO: copy response to an out argument, if it we have one? // TODO: copy response to an out argument, if it we have one?
// 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) {
// Wait for the transfer to be complete... // Wait for the transfer to be complete...
mmc_print(mmc, "waiting for transfer completion..."); mmc_print(mmc, "waiting for transfer completion...");
@ -648,15 +763,12 @@ static int sdmmc_card_init(struct mmc *mmc)
{ {
int rc; int rc;
rc = sdmmc_send_command(mmc, CMD_GO_IDLE_OR_INIT, 0, 0, 0, 0); // Bring the bus out of its idle state.
rc = sdmmc_send_command(mmc, CMD_SEND_OPERATING_CONDITIONS, MMC_RESPONSE_LEN48, 0, 0, 0); rc = sdmmc_send_command(mmc, CMD_GO_IDLE_OR_INIT, MMC_RESPONSE_NONE, MMC_CHECKS_NONE, 0, 0, 0);
if (rc) {
mmc_print(mmc, "response: %08x %08x %08x %08x %08x %08x %08x %08x", mmc_print(mmc, "could not bring bus to idle!");
mmc->regs->response[0], mmc->regs->response[1], return rc;
mmc->regs->response[2], mmc->regs->response[2], }
mmc->regs->response[4], mmc->regs->response[5],
mmc->regs->response[6], mmc->regs->response[7]);
return rc; return rc;
} }

View file

@ -6,6 +6,8 @@
#include "panic_color.h" #include "panic_color.h"
#include "timers.h" #include "timers.h"
#include "hwinit/btn.h"
__attribute__ ((noreturn)) void panic(uint32_t code) { __attribute__ ((noreturn)) void panic(uint32_t code) {
/* Set Panic Code for NX_BOOTLOADER. */ /* Set Panic Code for NX_BOOTLOADER. */
@ -17,8 +19,16 @@ __attribute__ ((noreturn)) void panic(uint32_t code) {
/* For now, just use NX BOOTLOADER's panic. */ /* For now, just use NX BOOTLOADER's panic. */
fuse_disable_programming(); fuse_disable_programming();
APBDEV_PMC_CRYPTO_OP_0 = 1; /* Disable all SE operations. */ APBDEV_PMC_CRYPTO_OP_0 = 1; /* Disable all SE operations. */
/* TODO: watchdog_reboot(); */
while (1) { } /* FIXME: clean up and use this instead of replacing things */
while(btn_read() != BTN_POWER);
/* Ensure we boot back into RCM, for development. */
APBDEV_PMC_SCRATCH0_0 = (1 << 1);
/* Reset the processor. */
APBDEV_PMC_CONTROL = (1 < 4);
while(1);
} }
__attribute__ ((noreturn)) void generic_panic(void) { __attribute__ ((noreturn)) void generic_panic(void) {