mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-12-22 18:32:05 +00:00
skeleton SDMMC
This commit is contained in:
parent
c19750a0d5
commit
06bf132022
6 changed files with 779 additions and 133 deletions
|
@ -16,9 +16,15 @@
|
|||
void printk(char *fmt, ...)
|
||||
{
|
||||
va_list list;
|
||||
char buf[512];
|
||||
va_start(list, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, list);
|
||||
video_puts(buf);
|
||||
vprintk(fmt, list);
|
||||
va_end(list);
|
||||
}
|
||||
|
||||
|
||||
void vprintk(char *fmt, va_list args)
|
||||
{
|
||||
char buf[512];
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
video_puts(buf);
|
||||
}
|
||||
|
|
|
@ -1,2 +1,9 @@
|
|||
#ifndef __PRINTK_H__
|
||||
#define __PRINTK_H__
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
void printk(char *fmt, ...);
|
||||
void vprintk(char *fmt, va_list args);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -19,6 +19,9 @@ struct va_format {
|
|||
|
||||
unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base);
|
||||
|
||||
int sprintf(char *buf, const char *fmt, ...);
|
||||
int scnprintf(char *buf, size_t size, const char *fmt, ...);
|
||||
int snprintf(char *buf, size_t size, const char *fmt, ...);
|
||||
int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
|
||||
int sscanf(const char *buf, const char *fmt, ...);
|
||||
|
||||
|
|
|
@ -1,27 +1,721 @@
|
|||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "sdmmc.h"
|
||||
#include "car.h"
|
||||
#include "timers.h"
|
||||
#include "apb_misc.h"
|
||||
#include "lib/printk.h"
|
||||
|
||||
/* Initialize the SDMMC1 (SD card) controller */
|
||||
void sdmmc1_init(void)
|
||||
#define TEGRA_SDMMC_BASE (0x700B0000)
|
||||
#define TEGRA_SDMMC_SIZE (0x200)
|
||||
|
||||
/**
|
||||
* Map of tegra SDMMC registers
|
||||
*/
|
||||
struct PACKED tegra_sdmmc {
|
||||
|
||||
/* SDHCI standard registers */
|
||||
uint32_t dma_address;
|
||||
uint16_t block_size;
|
||||
uint16_t block_count;
|
||||
uint32_t argument;
|
||||
uint16_t transfer_mode;
|
||||
uint16_t command;
|
||||
uint16_t response[0x8];
|
||||
uint32_t buffer;
|
||||
uint32_t present_state;
|
||||
uint8_t host_control;
|
||||
uint8_t power_control;
|
||||
uint8_t block_gap_control;
|
||||
uint8_t wake_up_control;
|
||||
uint16_t clock_control;
|
||||
uint8_t timeout_control;
|
||||
uint8_t software_reset;
|
||||
uint32_t int_status;
|
||||
uint32_t int_enable;
|
||||
uint32_t signal_enable;
|
||||
uint16_t acmd12_err;
|
||||
uint16_t host_control2;
|
||||
uint32_t capabilities;
|
||||
uint32_t capabilities_1;
|
||||
uint32_t max_current;
|
||||
uint32_t _0x4c;
|
||||
uint16_t set_acmd12_error;
|
||||
uint16_t set_int_error;
|
||||
uint16_t adma_error;
|
||||
uint8_t _0x55[0x3];
|
||||
uint32_t adma_address;
|
||||
uint32_t upper_adma_address;
|
||||
uint16_t preset_for_init;
|
||||
uint16_t preset_for_default;
|
||||
uint16_t preset_for_high;
|
||||
uint16_t preset_for_sdr12;
|
||||
uint16_t preset_for_sdr25;
|
||||
uint16_t preset_for_sdr50;
|
||||
uint16_t preset_for_sdr104;
|
||||
uint16_t preset_for_ddr50;
|
||||
uint8_t _0x70[0x3];
|
||||
uint32_t _0x74[0x22];
|
||||
uint16_t slot_int_status;
|
||||
uint16_t host_version;
|
||||
|
||||
/* vendor specific registers */
|
||||
uint32_t vendor_clock_cntrl;
|
||||
uint32_t vendor_sys_sw_cntrl;
|
||||
uint32_t vendor_err_intr_status;
|
||||
uint32_t vendor_cap_overrides;
|
||||
uint32_t vendor_boot_cntrl;
|
||||
uint32_t vendor_boot_ack_timeout;
|
||||
uint32_t vendor_boot_dat_timeout;
|
||||
uint32_t vendor_debounce_count;
|
||||
uint32_t vendor_misc_cntrl;
|
||||
uint32_t max_current_override;
|
||||
uint32_t max_current_override_hi;
|
||||
uint32_t _0x12c[0x21];
|
||||
uint32_t vendor_io_trim_cntrl;
|
||||
|
||||
/* start of sdmmc2/sdmmc4 only */
|
||||
uint32_t vendor_dllcal_cfg;
|
||||
uint32_t vendor_dll_ctrl0;
|
||||
uint32_t vendor_dll_ctrl1;
|
||||
uint32_t vendor_dllcal_cfg_sta;
|
||||
/* end of sdmmc2/sdmmc4 only */
|
||||
|
||||
uint32_t vendor_tuning_cntrl0;
|
||||
uint32_t vendor_tuning_cntrl1;
|
||||
uint32_t vendor_tuning_status0;
|
||||
uint32_t vendor_tuning_status1;
|
||||
uint32_t vendor_clk_gate_hysteresis_count;
|
||||
uint32_t vendor_preset_val0;
|
||||
uint32_t vendor_preset_val1;
|
||||
uint32_t vendor_preset_val2;
|
||||
uint32_t sdmemcomppadctrl;
|
||||
uint32_t auto_cal_config;
|
||||
uint32_t auto_cal_interval;
|
||||
uint32_t auto_cal_status;
|
||||
uint32_t io_spare;
|
||||
uint32_t sdmmca_mccif_fifoctrl;
|
||||
uint32_t timeout_wcoal_sdmmca;
|
||||
uint32_t _0x1fc;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* SDMMC response lengths
|
||||
*/
|
||||
enum sdmmc_response_type {
|
||||
MMC_RESPONSE_NONE = 0,
|
||||
MMC_RESPONSE_LEN136 = 1,
|
||||
MMC_RESPONSE_LEN48 = 2,
|
||||
MMC_RESPONSE_LEN48_CHK_BUSY = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* General masks for SDMMC registers.
|
||||
*/
|
||||
enum sdmmc_register_bits {
|
||||
|
||||
/* Present state register */
|
||||
MMC_COMMAND_INHIBIT = 1 << 0,
|
||||
|
||||
/* Command register */
|
||||
MMC_COMMAND_NUMBER_SHIFT = 8,
|
||||
MMC_COMMAND_RESPONSE_TYPE_SHIFT = 0,
|
||||
MMC_COMMAND_HAS_DATA = 1 << 5,
|
||||
MMC_COMMAND_TYPE_ABORT = 3 << 6,
|
||||
MMC_COMMAND_CHECK_NUMBER = 1 << 4,
|
||||
|
||||
/* Transfer mode arguments */
|
||||
MMC_TRANSFER_DMA_ENABLE = (1 << 0),
|
||||
MMC_TRANSFER_LIMIT_BLOCK_COUNT = (1 << 1),
|
||||
MMC_TRANSFER_MULTIPLE_BLOCKS = (1 << 5),
|
||||
MMC_TRANSFER_HOST_TO_CARD = (1 << 4),
|
||||
|
||||
/* Interrupt status */
|
||||
MMC_STATUS_COMMAND_COMPLETE = (1 << 0),
|
||||
MMC_STATUS_TRANSFER_COMPLETE = (1 << 1),
|
||||
MMC_STATUS_ERROR_MASK = (0xF << 16),
|
||||
|
||||
/* Host control */
|
||||
MMC_DMA_SELECT_MASK = (0x3 << 3),
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* SDMMC commands
|
||||
*/
|
||||
enum sdmmc_command {
|
||||
CMD_GO_IDLE_OR_INIT = 0,
|
||||
CMD_SEND_OPERATING_CONDITIONS = 1,
|
||||
CMD_ALL_SEND_CID = 2,
|
||||
CMD_SET_RELATIVE_ADDR = 3,
|
||||
CMD_SET_DSR = 4,
|
||||
CMD_TOGGLE_SLEEP_AWAKE = 5,
|
||||
CMD_SWITCH_MODE = 6,
|
||||
CMD_TOGGLE_CARD_SELECT = 7,
|
||||
CMD_SEND_EXT_CSD = 8,
|
||||
CMD_SEND_CSD = 9,
|
||||
CMD_SEND_CID = 10,
|
||||
CMD_STOP_TRANSMISSION = 12,
|
||||
CMD_READ_STATUS = 13,
|
||||
CMD_BUS_TEST = 14,
|
||||
CMD_GO_INACTIVE = 15,
|
||||
CMD_SET_BLKLEN = 16,
|
||||
CMD_READ_SINGLE_BLOCK = 17,
|
||||
CMD_READ_MULTIPLE_BLOCK = 18,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Page-aligned bounce buffer to target with SDMMC DMA.
|
||||
*/
|
||||
static uint8_t ALIGN(4096) sdmmc_bounce_buffer[4096 * 4];
|
||||
|
||||
/**
|
||||
* Debug print for SDMMC information.
|
||||
*/
|
||||
void mmc_print(struct mmc *mmc, char *fmt, ...)
|
||||
{
|
||||
/* TODO */
|
||||
va_list list;
|
||||
|
||||
// TODO: check SDMMC log level before printing
|
||||
|
||||
va_start(list, fmt);
|
||||
printk("%s: ", mmc->name);
|
||||
vprintk(fmt, list);
|
||||
printk("\n");
|
||||
va_end(list);
|
||||
}
|
||||
|
||||
/* Initialize the SDMMC2 (GC asic) controller */
|
||||
void sdmmc2_init(void)
|
||||
|
||||
/**
|
||||
* Retreives the SDMMC register range for the given controller.
|
||||
*/
|
||||
static struct tegra_sdmmc *sdmmc_get_regs(enum sdmmc_controller controller)
|
||||
{
|
||||
/* TODO */
|
||||
// Start with the base addresss of the SDMMC_BLOCK
|
||||
uintptr_t addr = TEGRA_SDMMC_BASE;
|
||||
|
||||
// Offset our address by the controller number.
|
||||
addr += (controller * TEGRA_SDMMC_SIZE);
|
||||
|
||||
// Return the controller.
|
||||
return (struct tegra_sdmmc *)addr;
|
||||
}
|
||||
|
||||
/* Initialize the SDMMC3 (unused) controller */
|
||||
void sdmmc3_init(void)
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int sdmmc_hardware_init(struct mmc *mmc)
|
||||
{
|
||||
/* TODO */
|
||||
volatile struct tegra_car *car = car_get_regs();
|
||||
volatile struct tegra_sdmmc *regs = mmc->regs;
|
||||
|
||||
uint32_t timebase;
|
||||
bool is_timeout;
|
||||
|
||||
/* XXX fixme XXX */
|
||||
bool is_hs400_hs667 = false;
|
||||
|
||||
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
|
||||
|
||||
// Put SDMMC4 in reset
|
||||
car->rst_dev_l_set |= 0x8000;
|
||||
|
||||
// Set SDMMC4 clock source (PLLP_OUT0) and divisor (1)
|
||||
car->clk_src[CLK_SOURCE_SDMMC4] = CLK_SOURCE_FIRST | CLK_DIVIDER_UNITY;
|
||||
|
||||
// Set SDMMC4 clock enable
|
||||
car->clk_dev_l_set |= 0x8000;
|
||||
|
||||
// host_clk_delay(0x64, clk_freq) -> Delay 100 host clock cycles
|
||||
udelay(5000);
|
||||
|
||||
// Take SDMMC4 out of reset
|
||||
car->rst_dev_l_clr |= 0x8000;
|
||||
|
||||
// Set IO_SPARE[19] (one cycle delay)
|
||||
regs->io_spare |= 0x80000;
|
||||
|
||||
// Clear SEL_VREG
|
||||
regs->vendor_io_trim_cntrl &= ~(0x04);
|
||||
|
||||
// Set trimmer value to 0x08 (SDMMC4)
|
||||
regs->vendor_clock_cntrl &= ~(0x1F000000);
|
||||
regs->vendor_clock_cntrl |= 0x08000000;
|
||||
|
||||
// Set SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL to 0x07
|
||||
regs->sdmemcomppadctrl &= ~(0x0F);
|
||||
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);
|
||||
|
||||
// Set PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only)
|
||||
regs->sdmemcomppadctrl |= 0x80000000;
|
||||
|
||||
// Wait one milisecond
|
||||
udelay(1000);
|
||||
|
||||
// Set AUTO_CAL_START and AUTO_CAL_ENABLE
|
||||
regs->auto_cal_config |= 0xA0000000;
|
||||
|
||||
// Wait one second
|
||||
udelay(1);
|
||||
|
||||
// Program a timeout of 10ms
|
||||
is_timeout = false;
|
||||
timebase = get_time();
|
||||
|
||||
// Wait for AUTO_CAL_ACTIVE to be cleared
|
||||
mmc_print(mmc, "initialing autocal...");
|
||||
while((regs->auto_cal_status & 0x80000000) && !is_timeout) {
|
||||
// Keep checking if timeout expired
|
||||
is_timeout = get_time_since(timebase) > 10000;
|
||||
}
|
||||
|
||||
// AUTO_CAL_ACTIVE was not cleared in time
|
||||
if (is_timeout)
|
||||
{
|
||||
mmc_print(mmc, "autocal timed out!");
|
||||
|
||||
// Set CFG2TMC_EMMC4_PAD_DRVUP_COMP and CFG2TMC_EMMC4_PAD_DRVDN_COMP
|
||||
APB_MISC_GP_EMMC4_PAD_CFGPADCTRL_0 = ((APB_MISC_GP_EMMC4_PAD_CFGPADCTRL_0 & ~(0x3F00)) | 0x1000);
|
||||
APB_MISC_GP_EMMC4_PAD_CFGPADCTRL_0 = ((APB_MISC_GP_EMMC4_PAD_CFGPADCTRL_0 & ~(0xFC)) | 0x40);
|
||||
|
||||
// Clear AUTO_CAL_ENABLE
|
||||
regs->auto_cal_config &= ~(0x20000000);
|
||||
}
|
||||
|
||||
mmc_print(mmc, "autocal complete.");
|
||||
|
||||
// Clear PAD_E_INPUT_OR_E_PWRD (relevant for eMMC only)
|
||||
regs->sdmemcomppadctrl &= ~(0x80000000);
|
||||
|
||||
// Set SDHCI_CLOCK_INT_EN
|
||||
regs->clock_control |= 0x01;
|
||||
|
||||
// Program a timeout of 2000ms
|
||||
timebase = get_time();
|
||||
is_timeout = false;
|
||||
|
||||
// Wait for SDHCI_CLOCK_INT_STABLE to be set
|
||||
mmc_print(mmc, "waiting for internal clock to stabalize...");
|
||||
while(!(regs->clock_control & 0x02) && !is_timeout) {
|
||||
// Keep checking if timeout expired
|
||||
is_timeout = get_time_since(timebase) > 2000000;
|
||||
}
|
||||
|
||||
// Clock failed to stabilize
|
||||
if (is_timeout) {
|
||||
mmc_print(mmc, "clock never stabalized!");
|
||||
return -1;
|
||||
} else {
|
||||
mmc_print(mmc, "clock stabalized.");
|
||||
}
|
||||
|
||||
// Clear upper 17 bits
|
||||
regs->host_control2 &= ~(0xFFFE0000);
|
||||
|
||||
// Clear SDHCI_PROG_CLOCK_MODE
|
||||
regs->clock_control &= ~(0x20);
|
||||
|
||||
// Set SDHCI_CTRL2_HOST_V4_ENABLE
|
||||
regs->host_control2 |= 0x1000;
|
||||
|
||||
// SDHCI_CAN_64BIT must be set
|
||||
if (!(regs->capabilities & 0x10000000)) {
|
||||
mmc_print(mmc, "missing CAN_64bit capability");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set SDHCI_CTRL2_64BIT_ENABLE
|
||||
regs->host_control2 |= 0x2000;
|
||||
|
||||
// Clear SDHCI_CTRL_SDMA and SDHCI_CTRL_ADMA2
|
||||
regs->host_control &= 0xE7;
|
||||
|
||||
// Set the timeout to be the maximum value
|
||||
regs->timeout_control &= ~(0x0F);
|
||||
regs->timeout_control |= 0x0E;
|
||||
|
||||
// Clear SDHCI_CTRL_4BITBUS and SDHCI_CTRL_8BITBUS
|
||||
regs->host_control &= 0xFD;
|
||||
regs->host_control &= 0xDF;
|
||||
|
||||
// Set SDHCI_POWER_180
|
||||
regs->power_control &= 0xF1;
|
||||
regs->power_control |= 0x0A;
|
||||
|
||||
regs->power_control |= 0x01;
|
||||
|
||||
if (is_hs400_hs667)
|
||||
{
|
||||
// Set DQS_TRIM_VAL
|
||||
regs->vendor_cap_overrides &= ~(0x3F00);
|
||||
regs->vendor_cap_overrides |= 0x2800;
|
||||
}
|
||||
|
||||
// Clear TAP_VAL_UPDATED_BY_HW
|
||||
regs->vendor_tuning_cntrl0 &= ~(0x20000);
|
||||
|
||||
// Software tap value should be 0 for SDMMC4, but HS400/HS667 modes
|
||||
// must take this value from the tuning procedure
|
||||
uint32_t tap_value = is_hs400_hs667 ? 1 : 0;
|
||||
|
||||
// Set TAP_VAL
|
||||
regs->vendor_clock_cntrl &= ~(0xFF0000);
|
||||
regs->vendor_clock_cntrl |= (tap_value << 16);
|
||||
|
||||
// Clear SDHCI_CTRL_HISPD
|
||||
regs->host_control &= 0xFB;
|
||||
|
||||
// Clear SDHCI_CTRL_VDD_180
|
||||
regs->host_control2 &= ~(0x08);
|
||||
|
||||
// Set SDHCI_DIVIDER and SDHCI_DIVIDER_HI
|
||||
// FIXME: divider SD if necessary
|
||||
regs->clock_control &= ~(0xFFC0);
|
||||
regs->clock_control |= (0x80 << 8); // XXX wtf is this
|
||||
//regs->clock_control |= ((sd_divider_lo << 0x08) | (sd_divider_hi << 0x06));
|
||||
|
||||
// HS400/HS667 modes require additional DLL calibration
|
||||
if (is_hs400_hs667)
|
||||
{
|
||||
// Set CALIBRATE
|
||||
regs->vendor_dllcal_cfg |= 0x80000000;
|
||||
|
||||
// Program a timeout of 5ms
|
||||
timebase = get_time();
|
||||
is_timeout = false;
|
||||
|
||||
// Wait for CALIBRATE to be cleared
|
||||
mmc_print(mmc, "starting calibration...");
|
||||
while(regs->vendor_dllcal_cfg & 0x80000000 && !is_timeout) {
|
||||
// Keep checking if timeout expired
|
||||
is_timeout = get_time_since(timebase) > 5000;
|
||||
}
|
||||
|
||||
// Failed to calibrate in time
|
||||
if (is_timeout) {
|
||||
mmc_print(mmc, "calibration failed!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
mmc_print(mmc, "calibration okay.");
|
||||
|
||||
// Program a timeout of 10ms
|
||||
timebase = get_time();
|
||||
is_timeout = false;
|
||||
|
||||
// Wait for DLL_CAL_ACTIVE to be cleared
|
||||
mmc_print(mmc, "waiting for calibration to finalize.... ");
|
||||
while((regs->vendor_dllcal_cfg_sta & 0x80000000) && !is_timeout) {
|
||||
// Keep checking if timeout expired
|
||||
is_timeout = get_time_since(timebase) > 10000;
|
||||
}
|
||||
|
||||
// Failed to calibrate in time
|
||||
if (is_timeout) {
|
||||
mmc_print(mmc, "calibration failed to finalize!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
mmc_print(mmc, "calibration complete!");
|
||||
}
|
||||
|
||||
// Set SDHCI_CLOCK_CARD_EN
|
||||
regs->clock_control |= 0x04;
|
||||
|
||||
mmc_print(mmc, "initialized.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Initialize the SDMMC4 (eMMC) controller */
|
||||
void sdmmc4_init(void)
|
||||
/**
|
||||
* Blocks until the SD driver is ready for a command,
|
||||
* or the MMC controller's timeout interval is met.
|
||||
*
|
||||
* @param mmc The MMC controller
|
||||
*/
|
||||
static int sdmmc_wait_for_command_readiness(struct mmc *mmc)
|
||||
{
|
||||
/* TODO */
|
||||
}
|
||||
uint32_t timebase = get_time();
|
||||
|
||||
// Wait until we either wind up ready, or until we've timed out.
|
||||
while(true) {
|
||||
if (get_time_since(timebase) > mmc->timeout) {
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
// Wait until we're not inhibited from sending commands...
|
||||
if (!(mmc->regs->present_state & MMC_COMMAND_INHIBIT))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Blocks until the SD driver has completed issuing a command.
|
||||
*
|
||||
* @param mmc The MMC controller
|
||||
*/
|
||||
static int sdmmc_wait_for_command_completion(struct mmc *mmc)
|
||||
{
|
||||
uint32_t timebase = get_time();
|
||||
|
||||
// Wait until we either wind up ready, or until we've timed out.
|
||||
while(true) {
|
||||
if (get_time_since(timebase) > mmc->timeout)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
// If the command completes, return that.
|
||||
if (mmc->regs->int_status & MMC_STATUS_COMMAND_COMPLETE)
|
||||
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 (mmc->regs->int_status & MMC_STATUS_ERROR_MASK)
|
||||
return (mmc->regs->int_status & MMC_STATUS_ERROR_MASK) >> 16;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Blocks until the SD driver has completed issuing a command.
|
||||
*
|
||||
* @param mmc The MMC controller
|
||||
*/
|
||||
static int sdmmc_wait_for_transfer_completion(struct mmc *mmc)
|
||||
{
|
||||
uint32_t timebase = get_time();
|
||||
|
||||
// Wait until we either wind up ready, or until we've timed out.
|
||||
while(true) {
|
||||
|
||||
if (get_time_since(timebase) > mmc->timeout)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
// If the command completes, return that.
|
||||
if (mmc->regs->int_status & MMC_STATUS_TRANSFER_COMPLETE)
|
||||
return 0;
|
||||
|
||||
// If an error occurs, return it.
|
||||
if (mmc->regs->int_status & MMC_STATUS_ERROR_MASK)
|
||||
return (mmc->regs->int_status & MMC_STATUS_ERROR_MASK) >> 16;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the data-related registers for command transmission.
|
||||
*
|
||||
* @param mmc The device to be used to transmit.
|
||||
* @param blocks The total number of blocks to be transferred.
|
||||
* @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)
|
||||
{
|
||||
// Ensure we're targeting our bounce buffer.
|
||||
mmc->regs->dma_address = (uint32_t)sdmmc_bounce_buffer;
|
||||
|
||||
// Ensure we're using System DMA mode for DMA.
|
||||
mmc->regs->host_control &= ~MMC_DMA_SELECT_MASK;
|
||||
|
||||
// Set up the DMA block size and count.
|
||||
// FIXME: implement!
|
||||
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_count = 0;
|
||||
|
||||
// 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 this is a multi-block datagram, indicate so.
|
||||
if (blocks > 1)
|
||||
mmc->regs->transfer_mode |= MMC_TRANSFER_MULTIPLE_BLOCKS;
|
||||
|
||||
// If this is a write, set the WRITE mode.
|
||||
if (is_write)
|
||||
mmc->regs->transfer_mode |= MMC_TRANSFER_HOST_TO_CARD;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the command-related registers for command transmission.
|
||||
*
|
||||
* @param mmc The device to be used to transmit.
|
||||
* @param blocks_to_xfer The total number of blocks to be transferred.
|
||||
* @param command The command number to issue.
|
||||
* @param response_type The type of response we'll expect.
|
||||
*/
|
||||
static void sdmmc_prepare_command_registers(struct mmc *mmc, int blocks_to_xfer,
|
||||
enum sdmmc_command command, enum sdmmc_response_type response_type)
|
||||
{
|
||||
// Populate the command number
|
||||
uint16_t to_write = (command << MMC_COMMAND_NUMBER_SHIFT) | (response_type << MMC_COMMAND_RESPONSE_TYPE_SHIFT) | MMC_COMMAND_CHECK_NUMBER;
|
||||
|
||||
// If this is a "stop transmitting" command, set the abort flag.
|
||||
if (command == CMD_STOP_TRANSMISSION)
|
||||
to_write |= MMC_COMMAND_TYPE_ABORT;
|
||||
|
||||
// TODO: do we want to support CRC or index checks?
|
||||
if (blocks_to_xfer)
|
||||
to_write |= MMC_COMMAND_HAS_DATA;
|
||||
|
||||
// Write our command to the given register.
|
||||
// This must be all done at once, as writes to this register have semantic meaning.
|
||||
mmc->regs->command = to_write;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sends a command to the SD card, and awaits a response.
|
||||
*/
|
||||
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)
|
||||
{
|
||||
int rc;
|
||||
|
||||
// Wait until we can issue commands to the device.
|
||||
mmc_print(mmc, "waiting for command readiness...");
|
||||
rc = sdmmc_wait_for_command_readiness(mmc);
|
||||
if(rc) {
|
||||
mmc_print(mmc, "card not willing to accept commands (%d / %08x)", rc, mmc->regs->present_state);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
// Populate the command argument.
|
||||
mmc_print(mmc, "populating argument...");
|
||||
mmc->regs->argument = argument;
|
||||
|
||||
// If we have data to send, prepare it.
|
||||
if (blocks_to_transfer) {
|
||||
mmc_print(mmc, "preparing data...");
|
||||
sdmmc_prepare_command_data(mmc, blocks_to_transfer, is_write);
|
||||
}
|
||||
|
||||
// Configure the controller to send the command.
|
||||
mmc_print(mmc, "preparing command...");
|
||||
sdmmc_prepare_command_registers(mmc, blocks_to_transfer, command, response_type);
|
||||
|
||||
// Wait for the command to be completed.
|
||||
mmc_print(mmc, "waiting for command completion...");
|
||||
rc = sdmmc_wait_for_command_completion(mmc);
|
||||
if(rc) {
|
||||
mmc_print(mmc, "failed to issue CMD%d (%d / %08x)", command, rc, mmc->regs->int_status);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// TODO: copy response to an out argument, if it we have one?
|
||||
|
||||
// If we had a data stage, handle it.
|
||||
if(blocks_to_transfer) {
|
||||
|
||||
// Wait for the transfer to be complete...
|
||||
mmc_print(mmc, "waiting for transfer completion...");
|
||||
rc = sdmmc_wait_for_transfer_completion(mmc);
|
||||
if(rc) {
|
||||
mmc_print(mmc, "failed to complete CMD%d data stage (%d)", command, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// TODO: copy data from the bounce buffer to the output buffer, if this was a read
|
||||
}
|
||||
|
||||
mmc_print(mmc, "command success!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
rc = sdmmc_send_command(mmc, CMD_GO_IDLE_OR_INIT, 0, 0, 0, 0);
|
||||
rc = sdmmc_send_command(mmc, CMD_SEND_OPERATING_CONDITIONS, MMC_RESPONSE_LEN48, 0, 0, 0);
|
||||
|
||||
mmc_print(mmc, "response: %08x %08x %08x %08x %08x %08x %08x %08x",
|
||||
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]);
|
||||
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set up a new SDMMC driver.
|
||||
* FIXME: clean up!
|
||||
*
|
||||
* @param mmc The SDMMC structure to be initiailized with the device state.
|
||||
* @param controler The controller description to be used; usually SWITCH_EMMC
|
||||
* or SWTICH_MICROSD.
|
||||
*/
|
||||
int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller)
|
||||
{
|
||||
int rc;
|
||||
|
||||
// Get a reference to the registers for the relevant SDMMC controller.
|
||||
mmc->regs = sdmmc_get_regs(controller);
|
||||
mmc->name = "eMMC";
|
||||
|
||||
// Default to a timeout of 1S.
|
||||
// FIXME: lower
|
||||
// FIXME: abstract
|
||||
mmc->timeout = 1000000;
|
||||
|
||||
// Initialize the raw SDMMC controller.
|
||||
mmc_print(mmc, "setting up hardware");
|
||||
rc = sdmmc_hardware_init(mmc);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "failed to set up controller! (%d)", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Initialize the SDMMC card.
|
||||
mmc_print(mmc, "setting up card");
|
||||
rc = sdmmc_card_init(mmc);
|
||||
if (rc) {
|
||||
mmc_print(mmc, "failed to set up card! (%d)", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads a sector or sectors from a given SD card.
|
||||
*
|
||||
* @param mmc The MMC device to work with.
|
||||
* @param buffer The output buffer to target.
|
||||
* @param sector The sector number to read.
|
||||
* @param count The number of sectors to read.
|
||||
*/
|
||||
int sdmmc_read(struct mmc *mmc, void *buffer, uint32_t sector, unsigned int count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -1,134 +1,54 @@
|
|||
#ifndef FUSEE_SDMMC_H
|
||||
#define FUSEE_SDMMC_H
|
||||
#ifndef __FUSEE_SDMMC_H__
|
||||
#define __FUSEE_SDMMC_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "utils.h"
|
||||
|
||||
typedef struct {
|
||||
uint32_t SDHCI_DMA_ADDRESS;
|
||||
uint16_t SDHCI_BLOCK_SIZE;
|
||||
uint16_t SDHCI_BLOCK_COUNT;
|
||||
uint32_t SDHCI_ARGUMENT;
|
||||
uint16_t SDHCI_TRANSFER_MODE;
|
||||
uint16_t SDHCI_COMMAND;
|
||||
uint16_t SDHCI_RESPONSE[0x8];
|
||||
uint32_t SDHCI_BUFFER;
|
||||
uint32_t SDHCI_PRESENT_STATE;
|
||||
uint8_t SDHCI_HOST_CONTROL;
|
||||
uint8_t SDHCI_POWER_CONTROL;
|
||||
uint8_t SDHCI_BLOCK_GAP_CONTROL;
|
||||
uint8_t SDHCI_WAKE_UP_CONTROL;
|
||||
uint16_t SDHCI_CLOCK_CONTROL;
|
||||
uint8_t SDHCI_TIMEOUT_CONTROL;
|
||||
uint8_t SDHCI_SOFTWARE_RESET;
|
||||
uint32_t SDHCI_INT_STATUS;
|
||||
uint32_t SDHCI_INT_ENABLE;
|
||||
uint32_t SDHCI_SIGNAL_ENABLE;
|
||||
uint16_t SDHCI_ACMD12_ERR;
|
||||
uint16_t SDHCI_HOST_CONTROL2;
|
||||
uint32_t SDHCI_CAPABILITIES;
|
||||
uint32_t SDHCI_CAPABILITIES_1;
|
||||
uint32_t SDHCI_MAX_CURRENT;
|
||||
uint32_t _0x4C;
|
||||
uint16_t SDHCI_SET_ACMD12_ERROR;
|
||||
uint16_t SDHCI_SET_INT_ERROR;
|
||||
uint16_t SDHCI_ADMA_ERROR;
|
||||
uint8_t _0x55[0x3];
|
||||
uint32_t SDHCI_ADMA_ADDRESS;
|
||||
uint32_t SDHCI_UPPER_ADMA_ADDRESS;
|
||||
uint16_t SDHCI_PRESET_FOR_INIT;
|
||||
uint16_t SDHCI_PRESET_FOR_DEFAULT;
|
||||
uint16_t SDHCI_PRESET_FOR_HIGH;
|
||||
uint16_t SDHCI_PRESET_FOR_SDR12;
|
||||
uint16_t SDHCI_PRESET_FOR_SDR25;
|
||||
uint16_t SDHCI_PRESET_FOR_SDR50;
|
||||
uint16_t SDHCI_PRESET_FOR_SDR104;
|
||||
uint16_t SDHCI_PRESET_FOR_DDR50;
|
||||
uint8_t _0x70[0x3];
|
||||
uint32_t _0x74[0x22];
|
||||
uint16_t SDHCI_SLOT_INT_STATUS;
|
||||
uint16_t SDHCI_HOST_VERSION;
|
||||
} sdhci_registers_t;
|
||||
|
||||
typedef struct {
|
||||
sdhci_registers_t standard_regs;
|
||||
uint32_t SDMMC_VENDOR_CLOCK_CNTRL;
|
||||
uint32_t SDMMC_VENDOR_SYS_SW_CNTRL;
|
||||
uint32_t SDMMC_VENDOR_ERR_INTR_STATUS;
|
||||
uint32_t SDMMC_VENDOR_CAP_OVERRIDES;
|
||||
uint32_t SDMMC_VENDOR_BOOT_CNTRL;
|
||||
uint32_t SDMMC_VENDOR_BOOT_ACK_TIMEOUT;
|
||||
uint32_t SDMMC_VENDOR_BOOT_DAT_TIMEOUT;
|
||||
uint32_t SDMMC_VENDOR_DEBOUNCE_COUNT;
|
||||
uint32_t SDMMC_VENDOR_MISC_CNTRL;
|
||||
uint32_t SDMMC_MAX_CURRENT_OVERRIDE;
|
||||
uint32_t SDMMC_MAX_CURRENT_OVERRIDE_HI;
|
||||
uint32_t _0x12C[0x21];
|
||||
uint32_t SDMMC_VENDOR_IO_TRIM_CNTRL;
|
||||
/* Start of SDMMC2/SDMMC4 only */
|
||||
uint32_t SDMMC_VENDOR_DLLCAL_CFG;
|
||||
uint32_t SDMMC_VENDOR_DLL_CTRL0;
|
||||
uint32_t SDMMC_VENDOR_DLL_CTRL1;
|
||||
uint32_t SDMMC_VENDOR_DLLCAL_CFG_STA;
|
||||
/* End of SDMMC2/SDMMC4 only */
|
||||
uint32_t SDMMC_VENDOR_TUNING_CNTRL0;
|
||||
uint32_t SDMMC_VENDOR_TUNING_CNTRL1;
|
||||
uint32_t SDMMC_VENDOR_TUNING_STATUS0;
|
||||
uint32_t SDMMC_VENDOR_TUNING_STATUS1;
|
||||
uint32_t SDMMC_VENDOR_CLK_GATE_HYSTERESIS_COUNT;
|
||||
uint32_t SDMMC_VENDOR_PRESET_VAL0;
|
||||
uint32_t SDMMC_VENDOR_PRESET_VAL1;
|
||||
uint32_t SDMMC_VENDOR_PRESET_VAL2;
|
||||
uint32_t SDMMC_SDMEMCOMPPADCTRL;
|
||||
uint32_t SDMMC_AUTO_CAL_CONFIG;
|
||||
uint32_t SDMMC_AUTO_CAL_INTERVAL;
|
||||
uint32_t SDMMC_AUTO_CAL_STATUS;
|
||||
uint32_t SDMMC_IO_SPARE;
|
||||
uint32_t SDMMC_SDMMCA_MCCIF_FIFOCTRL;
|
||||
uint32_t SDMMC_TIMEOUT_WCOAL_SDMMCA;
|
||||
uint32_t _0x1FC;
|
||||
} sdmmc_registers_t;
|
||||
/* Opaque pointer to the Tegra SDMMC registers */
|
||||
struct tegra_sdmmc;
|
||||
|
||||
static inline volatile sdmmc_registers_t *get_sdmmc1_regs(void) {
|
||||
return (volatile sdmmc_registers_t *)(0x700B0000);
|
||||
}
|
||||
/**
|
||||
* Primary data structure describing a Fusée MMC driver.
|
||||
*/
|
||||
struct mmc {
|
||||
char *name;
|
||||
|
||||
static inline volatile sdmmc_registers_t *get_sdmmc1b_regs(void) {
|
||||
return (volatile sdmmc_registers_t *)(0x700B0000 + 0x1000);
|
||||
}
|
||||
volatile struct tegra_sdmmc *regs;
|
||||
|
||||
static inline volatile sdmmc_registers_t *get_sdmmc2_regs(void) {
|
||||
return (volatile sdmmc_registers_t *)(0x700B0200);
|
||||
}
|
||||
unsigned int timeout;
|
||||
};
|
||||
|
||||
static inline volatile sdmmc_registers_t *get_sdmmc2b_regs(void) {
|
||||
return (volatile sdmmc_registers_t *)(0x700B0200 + 0x2000);
|
||||
}
|
||||
|
||||
static inline volatile sdmmc_registers_t *get_sdmmc3_regs(void) {
|
||||
return (volatile sdmmc_registers_t *)(0x700B0400);
|
||||
}
|
||||
/**
|
||||
* SDMMC controllers
|
||||
*/
|
||||
enum sdmmc_controller {
|
||||
SWITCH_MICROSD = 0,
|
||||
SWITCH_EMMC = 3
|
||||
};
|
||||
|
||||
static inline volatile sdmmc_registers_t *get_sdmmc3b_regs(void) {
|
||||
return (volatile sdmmc_registers_t *)(0x700B0400 + 0x3000);
|
||||
}
|
||||
|
||||
static inline volatile sdmmc_registers_t *get_sdmmc4_regs(void) {
|
||||
return (volatile sdmmc_registers_t *)(0x700B0600);
|
||||
}
|
||||
|
||||
static inline volatile sdmmc_registers_t *get_sdmmc4b_regs(void) {
|
||||
return (volatile sdmmc_registers_t *)(0x700B0600 + 0x4000);
|
||||
}
|
||||
/**
|
||||
* Initiailzes an SDMMC controller for use with an eMMC or SD card device.
|
||||
*
|
||||
* @param mmc An (uninitialized) structure for the MMC device.
|
||||
* @param controller The controller number to be initialized. Either SWITCH_MICROSD or SWITCH_EMMC.
|
||||
*/
|
||||
int sdmmc_init(struct mmc *mmc, enum sdmmc_controller controller);
|
||||
|
||||
#define SDMMC1_REGS (get_sdmmc1_regs())
|
||||
#define SDMMC2_REGS (get_sdmmc2_regs())
|
||||
#define SDMMC3_REGS (get_sdmmc3_regs())
|
||||
#define SDMMC4_REGS (get_sdmmc4_regs())
|
||||
|
||||
void sdmmc1_init(void);
|
||||
void sdmmc2_init(void);
|
||||
void sdmmc3_init(void);
|
||||
void sdmmc4_init(void);
|
||||
/**
|
||||
* Reads a sector or sectors from a given SD card.
|
||||
*
|
||||
* @param mmc The MMC device to work with.
|
||||
* @param buffer The output buffer to target.
|
||||
* @param sector The sector number to read.
|
||||
* @param count The number of sectors to read.
|
||||
*/
|
||||
int sdmmc_read(struct mmc *mmc, void *buffer, uint32_t sector, unsigned int count);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -26,6 +26,22 @@ static inline uint32_t get_time(void) {
|
|||
return TIMERUS_CNTR_1US_0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of microseconds that have passed since a given get_time().
|
||||
*/
|
||||
static inline uint32_t get_time_since(uint32_t base) {
|
||||
return get_time() - base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delays for a given number of microseconds.
|
||||
*/
|
||||
static inline void udelay(unsigned usecs)
|
||||
{
|
||||
uint32_t start = get_time();
|
||||
while (get_time() - start < usecs) ;
|
||||
}
|
||||
|
||||
__attribute__ ((noreturn)) void watchdog_reboot(void);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue