mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-27 06:12:15 +00:00
1514 lines
No EOL
46 KiB
C
1514 lines
No EOL
46 KiB
C
/*
|
|
* Copyright (c) 2018 naehrwert
|
|
* Copyright (c) 2018 CTCaer
|
|
* Copyright (c) 2018-2020 Atmosphère-NX
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
|
|
#include "sdmmc.h"
|
|
#include "mmc.h"
|
|
#include "sd.h"
|
|
|
|
#if defined(FUSEE_STAGE1_SRC)
|
|
#include "../../../fusee/fusee-primary/fusee-primary-main/src/timers.h"
|
|
#elif defined(FUSEE_STAGE2_SRC)
|
|
#include "../../../fusee/fusee-secondary/src/timers.h"
|
|
#elif defined(SEPT_STAGE2_SRC)
|
|
#include "../../../sept/sept-secondary/src/timers.h"
|
|
#endif
|
|
|
|
#define UNSTUFF_BITS(resp,start,size) \
|
|
({ \
|
|
const int __size = size; \
|
|
const uint32_t __mask = (__size < 32 ? 1 << __size : 0) - 1; \
|
|
const int __off = 3 - ((start) / 32); \
|
|
const int __shft = (start) & 31; \
|
|
uint32_t __res; \
|
|
\
|
|
__res = resp[__off] >> __shft; \
|
|
if (__size + __shft > 32) \
|
|
__res |= resp[__off-1] << ((32 - __shft) % 32); \
|
|
__res & __mask; \
|
|
})
|
|
|
|
static const unsigned int tran_exp[] = {
|
|
10000, 100000, 1000000, 10000000,
|
|
0, 0, 0, 0
|
|
};
|
|
|
|
static const unsigned char tran_mant[] = {
|
|
0, 10, 12, 13, 15, 20, 25, 30,
|
|
35, 40, 45, 50, 55, 60, 70, 80,
|
|
};
|
|
|
|
static const unsigned int taac_exp[] = {
|
|
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
|
|
};
|
|
|
|
static const unsigned int taac_mant[] = {
|
|
0, 10, 12, 13, 15, 20, 25, 30,
|
|
35, 40, 45, 50, 55, 60, 70, 80,
|
|
};
|
|
|
|
/*
|
|
Common SDMMC device functions.
|
|
*/
|
|
|
|
static bool is_sdmmc_device_r1_error(uint32_t status) {
|
|
return (status & (R1_OUT_OF_RANGE | R1_ADDRESS_ERROR | R1_BLOCK_LEN_ERROR
|
|
| R1_ERASE_SEQ_ERROR | R1_ERASE_PARAM | R1_WP_VIOLATION
|
|
| R1_LOCK_UNLOCK_FAILED | R1_COM_CRC_ERROR | R1_ILLEGAL_COMMAND
|
|
| R1_CARD_ECC_FAILED | R1_CC_ERROR | R1_ERROR | R1_CID_CSD_OVERWRITE
|
|
| R1_WP_ERASE_SKIP | R1_ERASE_RESET | R1_SWITCH_ERROR));
|
|
}
|
|
|
|
static int sdmmc_device_send_r1_cmd(sdmmc_device_t *device, uint32_t opcode, uint32_t arg, bool is_busy, uint32_t resp_mask, uint32_t resp_state) {
|
|
sdmmc_command_t cmd = {};
|
|
|
|
cmd.opcode = opcode;
|
|
cmd.arg = arg;
|
|
cmd.flags = (is_busy ? SDMMC_RSP_R1B : SDMMC_RSP_R1);
|
|
|
|
/* Try to send the command. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, &cmd, 0, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t resp = 0;
|
|
|
|
/* Try to load back the response. */
|
|
if (!sdmmc_load_response(device->sdmmc, SDMMC_RSP_R1, &resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Mask the response, if necessary. */
|
|
if (resp_mask) {
|
|
resp &= ~(resp_mask);
|
|
}
|
|
|
|
/* We got an error state. */
|
|
if (is_sdmmc_device_r1_error(resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* We need to check for the desired state. */
|
|
if (resp_state != 0xFFFFFFFF) {
|
|
/* We didn't get the expected state. */
|
|
if (R1_CURRENT_STATE(resp) != resp_state) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sdmmc_device_go_idle(sdmmc_device_t *device) {
|
|
sdmmc_command_t cmd = {};
|
|
|
|
cmd.opcode = MMC_GO_IDLE_STATE;
|
|
cmd.arg = 0;
|
|
cmd.flags = SDMMC_RSP_NONE;
|
|
|
|
return sdmmc_send_cmd(device->sdmmc, &cmd, 0, 0);
|
|
}
|
|
|
|
static int sdmmc_device_send_cid(sdmmc_device_t *device, uint32_t *cid) {
|
|
sdmmc_command_t cmd = {};
|
|
|
|
cmd.opcode = MMC_ALL_SEND_CID;
|
|
cmd.arg = 0;
|
|
cmd.flags = SDMMC_RSP_R2;
|
|
|
|
/* Try to send the command. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, &cmd, 0, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Try to load back the response. */
|
|
return sdmmc_load_response(device->sdmmc, SDMMC_RSP_R2, cid);
|
|
}
|
|
|
|
static int sdmmc_device_send_csd(sdmmc_device_t *device, uint32_t *csd) {
|
|
sdmmc_command_t cmd = {};
|
|
|
|
cmd.opcode = MMC_SEND_CSD;
|
|
cmd.arg = (device->rca << 16);
|
|
cmd.flags = SDMMC_RSP_R2;
|
|
|
|
/* Try to send the command. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, &cmd, 0, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Try to load back the response. */
|
|
return sdmmc_load_response(device->sdmmc, SDMMC_RSP_R2, csd);
|
|
}
|
|
|
|
static int sdmmc_device_select_card(sdmmc_device_t *device) {
|
|
/* Try to send the command. */
|
|
return sdmmc_device_send_r1_cmd(device, MMC_SELECT_CARD, (device->rca << 16), true, 0, 0xFFFFFFFF);
|
|
}
|
|
|
|
static int sdmmc_device_set_blocklen(sdmmc_device_t *device, uint32_t blocklen) {
|
|
/* Try to send the command. */
|
|
return sdmmc_device_send_r1_cmd(device, MMC_SET_BLOCKLEN, blocklen, false, 0, R1_STATE_TRAN);
|
|
}
|
|
|
|
static int sdmmc_device_send_status(sdmmc_device_t *device) {
|
|
/* Try to send the command. */
|
|
return sdmmc_device_send_r1_cmd(device, MMC_SEND_STATUS, (device->rca << 16), false, 0, R1_STATE_TRAN);
|
|
}
|
|
|
|
static int sdmmc_device_rw(sdmmc_device_t *device, uint32_t sector, uint32_t num_sectors, void *data, bool is_read) {
|
|
uint8_t *buf = (uint8_t *)data;
|
|
|
|
sdmmc_command_t cmd = {};
|
|
sdmmc_request_t req = {};
|
|
|
|
while (num_sectors) {
|
|
uint32_t num_blocks_out = 0;
|
|
uint32_t num_retries = 10;
|
|
|
|
for (; num_retries > 0; num_retries--) {
|
|
cmd.opcode = is_read ? MMC_READ_MULTIPLE_BLOCK : MMC_WRITE_MULTIPLE_BLOCK;
|
|
cmd.arg = sector;
|
|
cmd.flags = SDMMC_RSP_R1;
|
|
|
|
req.data = buf;
|
|
req.blksz = 512;
|
|
req.num_blocks = num_sectors;
|
|
req.is_read = is_read;
|
|
req.is_multi_block = true;
|
|
req.is_auto_cmd12 = true;
|
|
|
|
/* Try to send the command. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, &cmd, &req, &num_blocks_out)) {
|
|
/* Abort the transmission. */
|
|
sdmmc_abort(device->sdmmc, MMC_STOP_TRANSMISSION);
|
|
|
|
/* Peek the SD card's status. */
|
|
sdmmc_device_send_status(device);
|
|
|
|
/* Wait for a while. */
|
|
mdelay(100);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Failed to read/write on all attempts. */
|
|
if (!num_retries) {
|
|
return 0;
|
|
}
|
|
|
|
/* Advance to next sector. */
|
|
sector += num_blocks_out;
|
|
num_sectors -= num_blocks_out;
|
|
buf += (512 * num_blocks_out);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int sdmmc_device_read(sdmmc_device_t *device, uint32_t sector, uint32_t num_sectors, void *data) {
|
|
return sdmmc_device_rw(device, sector, num_sectors, data, true);
|
|
}
|
|
|
|
int sdmmc_device_write(sdmmc_device_t *device, uint32_t sector, uint32_t num_sectors, void *data) {
|
|
return sdmmc_device_rw(device, sector, num_sectors, data, false);
|
|
}
|
|
|
|
int sdmmc_device_finish(sdmmc_device_t *device) {
|
|
/* Place the device in idle state. */
|
|
if (!sdmmc_device_go_idle(device)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Terminate the device. */
|
|
sdmmc_finish(device->sdmmc);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
SD device functions.
|
|
*/
|
|
|
|
static void sdmmc_sd_decode_cid(sdmmc_device_t *device, uint32_t *cid) {
|
|
device->cid.manfid = UNSTUFF_BITS(cid, 120, 8);
|
|
device->cid.oemid = UNSTUFF_BITS(cid, 104, 16);
|
|
device->cid.prod_name[0] = UNSTUFF_BITS(cid, 96, 8);
|
|
device->cid.prod_name[1] = UNSTUFF_BITS(cid, 88, 8);
|
|
device->cid.prod_name[2] = UNSTUFF_BITS(cid, 80, 8);
|
|
device->cid.prod_name[3] = UNSTUFF_BITS(cid, 72, 8);
|
|
device->cid.prod_name[4] = UNSTUFF_BITS(cid, 64, 8);
|
|
device->cid.hwrev = UNSTUFF_BITS(cid, 60, 4);
|
|
device->cid.fwrev = UNSTUFF_BITS(cid, 56, 4);
|
|
device->cid.serial = UNSTUFF_BITS(cid, 24, 32);
|
|
device->cid.year = UNSTUFF_BITS(cid, 12, 8) + 2000; /* SD cards year offset */
|
|
device->cid.month = UNSTUFF_BITS(cid, 8, 4);
|
|
}
|
|
|
|
static int sdmmc_sd_decode_csd(sdmmc_device_t *device, uint32_t *csd) {
|
|
unsigned int e, m;
|
|
device->csd.structure = UNSTUFF_BITS(csd, 126, 2);
|
|
|
|
switch (device->csd.structure) {
|
|
case 0:
|
|
m = UNSTUFF_BITS(csd, 115, 4);
|
|
e = UNSTUFF_BITS(csd, 112, 3);
|
|
device->csd.taac_ns = (taac_exp[e] * taac_mant[m] + 9) / 10;
|
|
device->csd.taac_clks = UNSTUFF_BITS(csd, 104, 8) * 100;
|
|
|
|
m = UNSTUFF_BITS(csd, 99, 4);
|
|
e = UNSTUFF_BITS(csd, 96, 3);
|
|
device->csd.max_dtr = tran_exp[e] * tran_mant[m];
|
|
device->csd.cmdclass = UNSTUFF_BITS(csd, 84, 12);
|
|
|
|
e = UNSTUFF_BITS(csd, 47, 3);
|
|
m = UNSTUFF_BITS(csd, 62, 12);
|
|
device->csd.capacity = ((1 + m) << (e + 2));
|
|
|
|
device->csd.read_blkbits = UNSTUFF_BITS(csd, 80, 4);
|
|
device->csd.read_partial = UNSTUFF_BITS(csd, 79, 1);
|
|
device->csd.write_misalign = UNSTUFF_BITS(csd, 78, 1);
|
|
device->csd.read_misalign = UNSTUFF_BITS(csd, 77, 1);
|
|
device->csd.dsr_imp = UNSTUFF_BITS(csd, 76, 1);
|
|
device->csd.r2w_factor = UNSTUFF_BITS(csd, 26, 3);
|
|
device->csd.write_blkbits = UNSTUFF_BITS(csd, 22, 4);
|
|
device->csd.write_partial = UNSTUFF_BITS(csd, 21, 1);
|
|
|
|
if (UNSTUFF_BITS(csd, 46, 1)) {
|
|
device->csd.erase_size = 1;
|
|
} else if (device->csd.write_blkbits >= 9) {
|
|
device->csd.erase_size = UNSTUFF_BITS(csd, 39, 7) + 1;
|
|
device->csd.erase_size <<= (device->csd.write_blkbits - 9);
|
|
}
|
|
break;
|
|
case 1:
|
|
device->csd.taac_ns = 0; /* Unused */
|
|
device->csd.taac_clks = 0; /* Unused */
|
|
|
|
m = UNSTUFF_BITS(csd, 99, 4);
|
|
e = UNSTUFF_BITS(csd, 96, 3);
|
|
device->csd.max_dtr = tran_exp[e] * tran_mant[m];
|
|
device->csd.cmdclass = UNSTUFF_BITS(csd, 84, 12);
|
|
device->csd.c_size = UNSTUFF_BITS(csd, 48, 22);
|
|
|
|
m = UNSTUFF_BITS(csd, 48, 22);
|
|
device->csd.capacity = ((1 + m) << 10);
|
|
|
|
device->csd.read_blkbits = 9;
|
|
device->csd.read_partial = 0;
|
|
device->csd.write_misalign = 0;
|
|
device->csd.read_misalign = 0;
|
|
device->csd.r2w_factor = 4; /* Unused */
|
|
device->csd.write_blkbits = 9;
|
|
device->csd.write_partial = 0;
|
|
device->csd.erase_size = 1;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sdmmc_sd_decode_scr(sdmmc_device_t *device, uint8_t *scr) {
|
|
uint8_t tmp[8];
|
|
uint32_t resp[4];
|
|
|
|
/* This must be swapped. */
|
|
for (int i = 0; i < 8; i += 4) {
|
|
tmp[i + 3] = scr[i];
|
|
tmp[i + 2] = scr[i + 1];
|
|
tmp[i + 1] = scr[i + 2];
|
|
tmp[i] = scr[i + 3];
|
|
}
|
|
|
|
resp[3] = *(uint32_t *)&tmp[4];
|
|
resp[2] = *(uint32_t *)&tmp[0];
|
|
|
|
device->scr.sda_vsn = UNSTUFF_BITS(resp, 56, 4);
|
|
device->scr.bus_widths = UNSTUFF_BITS(resp, 48, 4);
|
|
|
|
/* Check if Physical Layer Spec v3.0 is supported. */
|
|
if (device->scr.sda_vsn == SD_SCR_SPEC_VER_2) {
|
|
device->scr.sda_spec3 = UNSTUFF_BITS(resp, 47, 1);
|
|
}
|
|
if (device->scr.sda_spec3) {
|
|
device->scr.cmds = UNSTUFF_BITS(resp, 32, 2);
|
|
}
|
|
|
|
/* Unknown SCR structure version. */
|
|
if (UNSTUFF_BITS(resp, 60, 4)) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static void sdmmc_sd_decode_ssr(sdmmc_device_t *device, uint8_t *ssr) {
|
|
uint8_t tmp[64];
|
|
uint32_t resp1[4];
|
|
uint32_t resp2[4];
|
|
|
|
/* This must be swapped. */
|
|
for (int i = 0; i < 64; i += 4) {
|
|
tmp[i + 3] = ssr[i];
|
|
tmp[i + 2] = ssr[i + 1];
|
|
tmp[i + 1] = ssr[i + 2];
|
|
tmp[i] = ssr[i + 3];
|
|
}
|
|
|
|
resp1[3] = *(uint32_t *)&tmp[12];
|
|
resp1[2] = *(uint32_t *)&tmp[8];
|
|
resp1[1] = *(uint32_t *)&tmp[4];
|
|
resp1[0] = *(uint32_t *)&tmp[0];
|
|
resp2[3] = *(uint32_t *)&tmp[28];
|
|
resp2[2] = *(uint32_t *)&tmp[24];
|
|
resp2[1] = *(uint32_t *)&tmp[20];
|
|
resp2[0] = *(uint32_t *)&tmp[16];
|
|
|
|
device->ssr.dat_bus_width = ((UNSTUFF_BITS(resp1, 126, 2) & SD_BUS_WIDTH_4) ? 4 : 1);
|
|
device->ssr.speed_class = UNSTUFF_BITS(resp1, 56, 8);
|
|
|
|
if (device->ssr.speed_class < 4) {
|
|
device->ssr.speed_class <<= 1;
|
|
} else if (device->ssr.speed_class == 4) {
|
|
device->ssr.speed_class = 10;
|
|
}
|
|
|
|
device->ssr.uhs_speed_grade = UNSTUFF_BITS(resp1, 12, 4);
|
|
device->ssr.video_speed_class = UNSTUFF_BITS(resp1, 0, 8);
|
|
device->ssr.app_perf_class = UNSTUFF_BITS(resp2, 80, 4);
|
|
}
|
|
|
|
static int sdmmc_sd_send_app_cmd(sdmmc_device_t *device, sdmmc_command_t *cmd, sdmmc_request_t *req, uint32_t resp_mask, uint32_t resp_state) {
|
|
/* Try to send the APP command. */
|
|
if (!sdmmc_device_send_r1_cmd(device, MMC_APP_CMD, (device->rca << 16), false, resp_mask, resp_state)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Send the actual command. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, cmd, req, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sdmmc_sd_send_if_cond(sdmmc_device_t *device, bool *is_sd_ver2) {
|
|
sdmmc_command_t cmd = {};
|
|
|
|
cmd.opcode = SD_SEND_IF_COND;
|
|
/* We set the bit if the host supports voltages between 2.7 and 3.6 V */
|
|
cmd.arg = 0x1AA;
|
|
cmd.flags = SDMMC_RSP_R7;
|
|
|
|
/* Command failed, this means SD Card is not version 2. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, &cmd, 0, 0)) {
|
|
*is_sd_ver2 = false;
|
|
return 1;
|
|
}
|
|
|
|
uint32_t resp = 0;
|
|
|
|
/* Try to load back the response. */
|
|
if (!sdmmc_load_response(device->sdmmc, SDMMC_RSP_R7, &resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Check if we got a valid response. */
|
|
if ((resp & 0xFF) == 0xAA) {
|
|
*is_sd_ver2 = true;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdmmc_sd_send_op_cond(sdmmc_device_t *device, bool is_sd_ver2, bool is_uhs_en) {
|
|
sdmmc_command_t cmd = {};
|
|
|
|
/* Program a large timeout. */
|
|
uint32_t timebase = get_time();
|
|
bool is_timeout = false;
|
|
|
|
while (!is_timeout) {
|
|
/* Set this since most cards do not answer if some reserved bits in the OCR are set. */
|
|
uint32_t arg = SD_OCR_VDD_32_33;
|
|
|
|
/* Request support for SDXC power control and SDHC block mode cards. */
|
|
if (is_sd_ver2) {
|
|
arg |= SD_OCR_XPC;
|
|
arg |= SD_OCR_CCS;
|
|
}
|
|
|
|
/* Request support 1.8V switching. */
|
|
if (is_uhs_en) {
|
|
arg |= SD_OCR_S18R;
|
|
}
|
|
|
|
cmd.opcode = SD_APP_OP_COND;
|
|
cmd.arg = arg;
|
|
cmd.flags = SDMMC_RSP_R3;
|
|
|
|
/* Try to send the command. */
|
|
if (!sdmmc_sd_send_app_cmd(device, &cmd, 0, is_sd_ver2 ? 0 : 0x400000, 0xFFFFFFFF)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t resp = 0;
|
|
|
|
/* Try to load back the response. */
|
|
if (!sdmmc_load_response(device->sdmmc, SDMMC_RSP_R3, &resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Card Power up bit is set. */
|
|
if (resp & MMC_CARD_BUSY) {
|
|
/* We have a SDHC block mode card. */
|
|
if (resp & SD_OCR_CCS) {
|
|
device->is_block_sdhc = true;
|
|
}
|
|
/* We asked for low voltage support and the card accepted. */
|
|
if (is_uhs_en && (resp & SD_ROCR_S18A)) {
|
|
/* Voltage switching is only valid for SDMMC1. */
|
|
if (device->sdmmc->controller == SDMMC_1) {
|
|
/* Failed to issue voltage switching command. */
|
|
if (!sdmmc_device_send_r1_cmd(device, SD_SWITCH_VOLTAGE, 0, false, 0, R1_STATE_READY)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Delay a bit before asking for the voltage switch. */
|
|
mdelay(100);
|
|
|
|
/* Tell the driver to switch the voltage. */
|
|
if (!sdmmc_switch_voltage(device->sdmmc)) {
|
|
return 0;
|
|
}
|
|
|
|
/* We are now running at 1.8V. */
|
|
device->is_180v = true;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Keep checking if timeout expired. */
|
|
is_timeout = (get_time_since(timebase) > 2000000);
|
|
|
|
/* Delay for a minimum of 10 milliseconds. */
|
|
mdelay(10);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdmmc_sd_send_relative_addr(sdmmc_device_t *device) {
|
|
sdmmc_command_t cmd = {};
|
|
|
|
cmd.opcode = SD_SEND_RELATIVE_ADDR;
|
|
cmd.arg = 0;
|
|
cmd.flags = SDMMC_RSP_R6;
|
|
|
|
/* Program a large timeout. */
|
|
uint32_t timebase = get_time();
|
|
bool is_timeout = false;
|
|
|
|
while (!is_timeout) {
|
|
/* Try to send the command. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, &cmd, 0, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t resp = 0;
|
|
|
|
/* Try to load back the response. */
|
|
if (!sdmmc_load_response(device->sdmmc, SDMMC_RSP_R6, &resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Save the RCA. */
|
|
if (resp >> 16) {
|
|
device->rca = (resp >> 16);
|
|
return 1;
|
|
}
|
|
|
|
/* Keep checking if timeout expired. */
|
|
is_timeout = (get_time_since(timebase) > 2000000);
|
|
|
|
/* Delay for an appropriate period. */
|
|
udelay(1000);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdmmc_sd_send_scr(sdmmc_device_t *device, uint8_t *scr) {
|
|
sdmmc_command_t cmd = {};
|
|
sdmmc_request_t req = {};
|
|
|
|
cmd.opcode = SD_APP_SEND_SCR;
|
|
cmd.arg = 0;
|
|
cmd.flags = SDMMC_RSP_R1;
|
|
|
|
req.data = scr;
|
|
req.blksz = 8;
|
|
req.num_blocks = 1;
|
|
req.is_read = true;
|
|
req.is_multi_block = false;
|
|
req.is_auto_cmd12 = false;
|
|
|
|
/* Try to send the APP command. */
|
|
if (!sdmmc_sd_send_app_cmd(device, &cmd, &req, 0, R1_STATE_TRAN)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t resp = 0;
|
|
|
|
/* Try to load back the response. */
|
|
if (!sdmmc_load_response(device->sdmmc, SDMMC_RSP_R1, &resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Evaluate the response. */
|
|
if (is_sdmmc_device_r1_error(resp)) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int sdmmc_sd_set_clr_card_detect(sdmmc_device_t *device) {
|
|
/* Try to send the APP command. */
|
|
if (!sdmmc_device_send_r1_cmd(device, MMC_APP_CMD, (device->rca << 16), false, 0, R1_STATE_TRAN)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Try to send the command. */
|
|
return sdmmc_device_send_r1_cmd(device, SD_APP_SET_CLR_CARD_DETECT, 0, false, 0, R1_STATE_TRAN);
|
|
}
|
|
|
|
static int sdmmc_sd_set_bus_width(sdmmc_device_t *device) {
|
|
/* Try to send the APP command. */
|
|
if (!sdmmc_device_send_r1_cmd(device, MMC_APP_CMD, (device->rca << 16), false, 0, R1_STATE_TRAN)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Try to send the command. */
|
|
return sdmmc_device_send_r1_cmd(device, SD_APP_SET_BUS_WIDTH, SD_BUS_WIDTH_4, false, 0, R1_STATE_TRAN);
|
|
}
|
|
|
|
static int sdmmc_sd_switch(sdmmc_device_t *device, uint32_t mode, uint32_t group, uint8_t value, uint8_t *data) {
|
|
sdmmc_command_t cmd = {};
|
|
sdmmc_request_t req = {};
|
|
|
|
cmd.opcode = SD_SWITCH;
|
|
cmd.arg = ((mode << 31) | 0x00FFFFFF);
|
|
cmd.arg &= ~(0xF << (group * 4));
|
|
cmd.arg |= (value << (group * 4));
|
|
cmd.flags = SDMMC_RSP_R1;
|
|
|
|
req.data = data;
|
|
req.blksz = 64;
|
|
req.num_blocks = 1;
|
|
req.is_read = true;
|
|
req.is_multi_block = false;
|
|
req.is_auto_cmd12 = false;
|
|
|
|
/* Try to send the command. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, &cmd, &req, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t resp = 0;
|
|
|
|
/* Try to load back the response. */
|
|
if (!sdmmc_load_response(device->sdmmc, SDMMC_RSP_R1, &resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Evaluate the response. */
|
|
if (is_sdmmc_device_r1_error(resp)) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int sdmmc_sd_set_current_limit(sdmmc_device_t *device, uint8_t *status) {
|
|
/* Start with the highest possible limit. */
|
|
int32_t current_limit = SD_SET_CURRENT_LIMIT_800;
|
|
|
|
/* Try each limit. */
|
|
while (current_limit > SD_SET_CURRENT_NO_CHANGE) {
|
|
/* Switch the current limit. */
|
|
if (!sdmmc_sd_switch(device, SD_SWITCH_SET, 3, current_limit, status)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Current limit was set successfully. */
|
|
if (((status[15] >> 4) & 0x0F) == current_limit) {
|
|
break;
|
|
}
|
|
|
|
current_limit--;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sdmmc_sd_switch_hs(sdmmc_device_t *device, uint32_t type, uint8_t *status) {
|
|
/* Test if the card supports high-speed mode. */
|
|
if (!sdmmc_sd_switch(device, 0, 0, type, status)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t res_type = (status[16] & 0xF);
|
|
|
|
/* This high-speed mode type is not supported. */
|
|
if (res_type != type) {
|
|
return 0;
|
|
}
|
|
|
|
if ((((uint16_t)status[0] << 8) | status[1]) < 0x320) {
|
|
/* Try to switch to high-speed mode. */
|
|
if (!sdmmc_sd_switch(device, 1, 0, type, status)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Something failed when switching to high-speed mode. */
|
|
if ((status[16] & 0xF) != res_type) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sdmmc_sd_switch_hs_low(sdmmc_device_t *device, uint8_t *status) {
|
|
/* Adjust the current limit. */
|
|
if (!sdmmc_sd_set_current_limit(device, status)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Invalid bus width. */
|
|
if (device->sdmmc->bus_width != SDMMC_BUS_WIDTH_4BIT) {
|
|
return 0;
|
|
}
|
|
|
|
/* Get the supported high-speed type. */
|
|
if (!sdmmc_sd_switch(device, 0, 0, 0xF, status)) {
|
|
return 0;
|
|
}
|
|
|
|
/* High-speed SDR104 is supported. */
|
|
if (status[13] & SD_MODE_UHS_SDR104) {
|
|
/* Switch to high-speed. */
|
|
if (!sdmmc_sd_switch_hs(device, UHS_SDR104_BUS_SPEED, status)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Reconfigure the internal clock. */
|
|
if (!sdmmc_select_speed(device->sdmmc, SDMMC_SPEED_SD_SDR104)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Run tuning. */
|
|
if (!sdmmc_execute_tuning(device->sdmmc, SDMMC_SPEED_SD_SDR104, MMC_SEND_TUNING_BLOCK)) {
|
|
return 0;
|
|
}
|
|
} else if (status[13] & SD_MODE_UHS_SDR50) { /* High-speed SDR50 is supported. */
|
|
/* Switch to high-speed. */
|
|
if (!sdmmc_sd_switch_hs(device, UHS_SDR50_BUS_SPEED, status)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Reconfigure the internal clock. */
|
|
if (!sdmmc_select_speed(device->sdmmc, SDMMC_SPEED_SD_SDR50)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Run tuning. */
|
|
if (!sdmmc_execute_tuning(device->sdmmc, SDMMC_SPEED_SD_SDR50, MMC_SEND_TUNING_BLOCK)) {
|
|
return 0;
|
|
}
|
|
} else if (status[13] & SD_MODE_UHS_SDR12) { /* High-speed SDR12 is supported. */
|
|
/* Switch to high-speed. */
|
|
if (!sdmmc_sd_switch_hs(device, UHS_SDR12_BUS_SPEED, status)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Reconfigure the internal clock. */
|
|
if (!sdmmc_select_speed(device->sdmmc, SDMMC_SPEED_SD_SDR12)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Run tuning. */
|
|
if (!sdmmc_execute_tuning(device->sdmmc, SDMMC_SPEED_SD_SDR12, MMC_SEND_TUNING_BLOCK)) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
/* Peek the SD card's status. */
|
|
return sdmmc_device_send_status(device);
|
|
}
|
|
|
|
static int sdmmc_sd_switch_hs_high(sdmmc_device_t *device, uint8_t *status) {
|
|
/* Get the supported high-speed type. */
|
|
if (!sdmmc_sd_switch(device, 0, 0, 0xF, status)) {
|
|
return 0;
|
|
}
|
|
|
|
/* High-speed is supported. */
|
|
if (status[13] & 2) {
|
|
/* Switch to high-speed. */
|
|
if (!sdmmc_sd_switch_hs(device, SDHCI_CTRL_UHS_SDR25, status)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Reconfigure the internal clock. */
|
|
if (!sdmmc_select_speed(device->sdmmc, SDMMC_SPEED_SD_SDR25)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Peek the SD card's status. */
|
|
return sdmmc_device_send_status(device);
|
|
}
|
|
|
|
/* Nothing to do. */
|
|
return 1;
|
|
}
|
|
|
|
static int sdmmc_sd_status(sdmmc_device_t *device, uint8_t *ssr) {
|
|
sdmmc_command_t cmd = {};
|
|
sdmmc_request_t req = {};
|
|
|
|
cmd.opcode = SD_APP_SD_STATUS;
|
|
cmd.arg = 0;
|
|
cmd.flags = SDMMC_RSP_R1;
|
|
|
|
req.data = ssr;
|
|
req.blksz = 64;
|
|
req.num_blocks = 1;
|
|
req.is_read = true;
|
|
req.is_multi_block = false;
|
|
req.is_auto_cmd12 = false;
|
|
|
|
/* Try to send the APP command. */
|
|
if (!sdmmc_sd_send_app_cmd(device, &cmd, &req, 0, R1_STATE_TRAN)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t resp = 0;
|
|
|
|
/* Try to load back the response. */
|
|
if (!sdmmc_load_response(device->sdmmc, SDMMC_RSP_R1, &resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Evaluate the response. */
|
|
if (is_sdmmc_device_r1_error(resp)) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int sdmmc_device_sd_init(sdmmc_device_t *device, sdmmc_t *sdmmc, SdmmcBusWidth bus_width, SdmmcBusSpeed bus_speed) {
|
|
bool is_sd_ver2 = false;
|
|
uint32_t cid[4] = {0};
|
|
uint32_t csd[4] = {0};
|
|
uint8_t scr[8] = {0};
|
|
uint8_t ssr[64] = {0};
|
|
uint8_t switch_status[512] = {0};
|
|
|
|
/* Initialize our device's struct. */
|
|
memset(device, 0, sizeof(sdmmc_device_t));
|
|
|
|
/* Try to initialize the driver. */
|
|
if (!sdmmc_init(sdmmc, SDMMC_1, SDMMC_VOLTAGE_3V3, SDMMC_BUS_WIDTH_1BIT, SDMMC_SPEED_SD_IDENT)) {
|
|
sdmmc_error(sdmmc, "Failed to initialize the SDMMC driver!");
|
|
return 0;
|
|
}
|
|
|
|
/* Bind the underlying driver. */
|
|
device->sdmmc = sdmmc;
|
|
|
|
sdmmc_info(sdmmc, "SDMMC driver was successfully initialized for SD!");
|
|
|
|
/* Apply at least 74 clock cycles. The card should be ready afterwards. */
|
|
udelay((74000 + sdmmc->internal_divider - 1) / sdmmc->internal_divider);
|
|
|
|
/* Instruct the SD card to go idle. */
|
|
if (!sdmmc_device_go_idle(device)) {
|
|
sdmmc_error(sdmmc, "Failed to go idle!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "SD card went idle!");
|
|
|
|
/* Get the SD card's interface operating condition. */
|
|
if (!sdmmc_sd_send_if_cond(device, &is_sd_ver2)) {
|
|
sdmmc_error(sdmmc, "Failed to send if cond!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Sent if cond to SD card!");
|
|
|
|
/* Get the SD card's operating conditions. */
|
|
if (!sdmmc_sd_send_op_cond(device, is_sd_ver2, (bus_width == SDMMC_BUS_WIDTH_4BIT) && (bus_speed == SDMMC_SPEED_SD_SDR104))) {
|
|
sdmmc_error(sdmmc, "Failed to send op cond!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Sent op cond to SD card!");
|
|
|
|
/* Get the SD card's CID. */
|
|
if (!sdmmc_device_send_cid(device, cid)) {
|
|
sdmmc_error(sdmmc, "Failed to get CID!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Got CID from SD card!");
|
|
|
|
/* Decode and save the CID. */
|
|
sdmmc_sd_decode_cid(device, cid);
|
|
|
|
/* Get the SD card's RCA. */
|
|
if (!sdmmc_sd_send_relative_addr(device)) {
|
|
sdmmc_error(sdmmc, "Failed to get RCA!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Got RCA (0x%08x) from SD card!", device->rca);
|
|
|
|
/* Get the SD card's CSD. */
|
|
if (!sdmmc_device_send_csd(device, csd)) {
|
|
sdmmc_error(sdmmc, "Failed to get CSD!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Got CSD from SD card!");
|
|
|
|
/* Decode and save the CSD. */
|
|
if (!sdmmc_sd_decode_csd(device, csd)) {
|
|
sdmmc_warn(sdmmc, "Got unknown CSD structure (0x%08x)!", device->csd.structure);
|
|
}
|
|
|
|
/* If we never switched to 1.8V, change the bus speed mode. */
|
|
if (!device->is_180v) {
|
|
/* Reconfigure the internal clock. */
|
|
if (!sdmmc_select_speed(device->sdmmc, SDMMC_SPEED_SD_DS)) {
|
|
sdmmc_error(sdmmc, "Failed to apply the correct bus speed!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Speed mode has been adjusted!");
|
|
}
|
|
|
|
/* Select the SD card. */
|
|
if (!sdmmc_device_select_card(device)) {
|
|
sdmmc_error(sdmmc, "Failed to select SD card!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "SD card is now selected!");
|
|
|
|
/* Change the SD card's block length. */
|
|
if (!sdmmc_device_set_blocklen(device, 512)) {
|
|
sdmmc_error(sdmmc, "Failed to set SD card's block length!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "SD card's block length is now 512!");
|
|
|
|
/* It's a good practice to disconnect the pull-up resistor with ACMD42. */
|
|
if (!sdmmc_sd_set_clr_card_detect(device)) {
|
|
sdmmc_error(sdmmc, "Failed to disconnect the pull-up resistor!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Pull-up resistor is now disconnected!");
|
|
|
|
/* Get the SD card's SCR. */
|
|
if (!sdmmc_sd_send_scr(device, scr)) {
|
|
sdmmc_error(sdmmc, "Failed to get SCR!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Got SCR from SD card!");
|
|
|
|
/* Decode and save the SCR. */
|
|
if (!sdmmc_sd_decode_scr(device, scr)) {
|
|
sdmmc_error(sdmmc, "Got unknown SCR structure!");
|
|
return 0;
|
|
}
|
|
|
|
/* Switch to wider bus (if supported). */
|
|
if ((bus_width == SDMMC_BUS_WIDTH_4BIT)
|
|
&& (device->scr.bus_widths & SD_SCR_BUS_WIDTH_4)
|
|
&& (device->scr.sda_vsn & (SD_SCR_SPEC_VER_1 | SD_SCR_SPEC_VER_2))) {
|
|
if (!sdmmc_sd_set_bus_width(device)) {
|
|
sdmmc_error(sdmmc, "Failed to switch to wider bus!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_select_bus_width(device->sdmmc, SDMMC_BUS_WIDTH_4BIT);
|
|
sdmmc_info(sdmmc, "Switched to wider bus!");
|
|
}
|
|
|
|
if (device->is_180v) {
|
|
/* Switch to high-speed from low voltage (if possible). */
|
|
if (!sdmmc_sd_switch_hs_low(device, switch_status)) {
|
|
sdmmc_error(sdmmc, "Failed to switch to high-speed from low voltage!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Switched to high-speed from low voltage!");
|
|
} else if ((device->scr.sda_vsn & (SD_SCR_SPEC_VER_1 | SD_SCR_SPEC_VER_2)) && ((bus_speed != SDMMC_SPEED_SD_DS))) {
|
|
/* Switch to high-speed from high voltage (if possible). */
|
|
if (!sdmmc_sd_switch_hs_high(device, switch_status)) {
|
|
sdmmc_error(sdmmc, "Failed to switch to high-speed from high voltage!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Switched to high-speed from high voltage!");
|
|
}
|
|
|
|
/* Correct any inconsistent states. */
|
|
sdmmc_adjust_sd_clock(sdmmc);
|
|
|
|
/* Get the SD card's SSR. */
|
|
if (!sdmmc_sd_status(device, ssr)) {
|
|
sdmmc_error(sdmmc, "Failed to get SSR!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Got SSR from SD card!");
|
|
|
|
/* Decode and save the SSR. */
|
|
sdmmc_sd_decode_ssr(device, scr);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
MMC device functions.
|
|
*/
|
|
|
|
static void sdmmc_mmc_decode_cid(sdmmc_device_t *device, uint32_t *cid) {
|
|
switch (device->csd.mmca_vsn) {
|
|
case 0: /* MMC v1.0 - v1.2 */
|
|
case 1: /* MMC v1.4 */
|
|
device->cid.prod_name[6] = UNSTUFF_BITS(cid, 48, 8);
|
|
device->cid.manfid = UNSTUFF_BITS(cid, 104, 24);
|
|
device->cid.hwrev = UNSTUFF_BITS(cid, 44, 4);
|
|
device->cid.fwrev = UNSTUFF_BITS(cid, 40, 4);
|
|
device->cid.serial = UNSTUFF_BITS(cid, 16, 24);
|
|
break;
|
|
case 2: /* MMC v2.0 - v2.2 */
|
|
case 3: /* MMC v3.1 - v3.3 */
|
|
case 4: /* MMC v4 */
|
|
device->cid.manfid = UNSTUFF_BITS(cid, 120, 8);
|
|
device->cid.oemid = UNSTUFF_BITS(cid, 104, 8);
|
|
device->cid.prv = UNSTUFF_BITS(cid, 48, 8);
|
|
device->cid.serial = UNSTUFF_BITS(cid, 16, 32);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
device->cid.prod_name[0] = UNSTUFF_BITS(cid, 96, 8);
|
|
device->cid.prod_name[1] = UNSTUFF_BITS(cid, 88, 8);
|
|
device->cid.prod_name[2] = UNSTUFF_BITS(cid, 80, 8);
|
|
device->cid.prod_name[3] = UNSTUFF_BITS(cid, 72, 8);
|
|
device->cid.prod_name[4] = UNSTUFF_BITS(cid, 64, 8);
|
|
device->cid.prod_name[5] = UNSTUFF_BITS(cid, 56, 8);
|
|
|
|
device->cid.month = UNSTUFF_BITS(cid, 12, 4);
|
|
device->cid.year = (UNSTUFF_BITS(cid, 8, 4) + 1997);
|
|
|
|
if ((device->ext_csd.rev >= 5) && (device->cid.year < 2010)) {
|
|
device->cid.year += 16;
|
|
}
|
|
}
|
|
|
|
static int sdmmc_mmc_decode_csd(sdmmc_device_t *device, uint32_t *csd) {
|
|
unsigned int e, m, a, b;
|
|
|
|
device->csd.structure = UNSTUFF_BITS(csd, 126, 2);
|
|
|
|
if (!device->csd.structure) {
|
|
return 0;
|
|
}
|
|
|
|
device->csd.mmca_vsn = UNSTUFF_BITS(csd, 122, 4);
|
|
|
|
m = UNSTUFF_BITS(csd, 115, 4);
|
|
e = UNSTUFF_BITS(csd, 112, 3);
|
|
device->csd.taac_ns = ((taac_exp[e] * taac_mant[m] + 9) / 10);
|
|
device->csd.taac_clks = (UNSTUFF_BITS(csd, 104, 8) * 100);
|
|
|
|
m = UNSTUFF_BITS(csd, 99, 4);
|
|
e = UNSTUFF_BITS(csd, 96, 3);
|
|
device->csd.max_dtr = (tran_exp[e] * tran_mant[m]);
|
|
device->csd.cmdclass = UNSTUFF_BITS(csd, 84, 12);
|
|
|
|
e = UNSTUFF_BITS(csd, 47, 3);
|
|
m = UNSTUFF_BITS(csd, 62, 12);
|
|
device->csd.capacity = ((1 + m) << (e + 2));
|
|
|
|
device->csd.read_blkbits = UNSTUFF_BITS(csd, 80, 4);
|
|
device->csd.read_partial = UNSTUFF_BITS(csd, 79, 1);
|
|
device->csd.write_misalign = UNSTUFF_BITS(csd, 78, 1);
|
|
device->csd.read_misalign = UNSTUFF_BITS(csd, 77, 1);
|
|
device->csd.dsr_imp = UNSTUFF_BITS(csd, 76, 1);
|
|
device->csd.r2w_factor = UNSTUFF_BITS(csd, 26, 3);
|
|
device->csd.write_blkbits = UNSTUFF_BITS(csd, 22, 4);
|
|
device->csd.write_partial = UNSTUFF_BITS(csd, 21, 1);
|
|
|
|
if (device->csd.write_blkbits >= 9) {
|
|
a = UNSTUFF_BITS(csd, 42, 5);
|
|
b = UNSTUFF_BITS(csd, 37, 5);
|
|
device->csd.erase_size = ((a + 1) * (b + 1));
|
|
device->csd.erase_size <<= (device->csd.write_blkbits - 9);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void sdmmc_mmc_decode_ext_csd(sdmmc_device_t *device, uint8_t *ext_csd) {
|
|
device->ext_csd.rev = ext_csd[EXT_CSD_REV];
|
|
device->ext_csd.raw_ext_csd_structure = ext_csd[EXT_CSD_STRUCTURE];
|
|
device->ext_csd.raw_card_type = ext_csd[EXT_CSD_CARD_TYPE];
|
|
device->ext_csd.raw_rpmb_size_mult = ext_csd[EXT_CSD_RPMB_MULT];
|
|
device->ext_csd.raw_sectors[0] = ext_csd[EXT_CSD_SEC_CNT + 0];
|
|
device->ext_csd.raw_sectors[1] = ext_csd[EXT_CSD_SEC_CNT + 1];
|
|
device->ext_csd.raw_sectors[2] = ext_csd[EXT_CSD_SEC_CNT + 2];
|
|
device->ext_csd.raw_sectors[3] = ext_csd[EXT_CSD_SEC_CNT + 3];
|
|
device->ext_csd.bkops = ext_csd[EXT_CSD_BKOPS_SUPPORT];
|
|
device->ext_csd.man_bkops_en = ext_csd[EXT_CSD_BKOPS_EN];
|
|
device->ext_csd.raw_bkops_status = ext_csd[EXT_CSD_BKOPS_STATUS];
|
|
}
|
|
|
|
static int sdmmc_mmc_send_op_cond(sdmmc_device_t *device, SdmmcBusVoltage bus_voltage) {
|
|
sdmmc_command_t cmd = {};
|
|
|
|
/* Program a large timeout. */
|
|
uint32_t timebase = get_time();
|
|
bool is_timeout = false;
|
|
|
|
while (!is_timeout) {
|
|
/* Set high capacity bit. */
|
|
uint32_t arg = SD_OCR_CCS;
|
|
|
|
/* Set voltage bits. */
|
|
if (bus_voltage == SDMMC_VOLTAGE_1V8) {
|
|
arg |= MMC_VDD_165_195;
|
|
} else if (bus_voltage == SDMMC_VOLTAGE_3V3) {
|
|
arg |= (MMC_VDD_33_34 | MMC_VDD_32_33 | MMC_VDD_31_32 | MMC_VDD_30_31 | MMC_VDD_29_30 | MMC_VDD_28_29 | MMC_VDD_27_28);
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
cmd.opcode = MMC_SEND_OP_COND;
|
|
cmd.arg = arg;
|
|
cmd.flags = SDMMC_RSP_R3;
|
|
|
|
/* Try to send the command. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, &cmd, 0, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t resp = 0;
|
|
|
|
/* Try to load back the response. */
|
|
if (!sdmmc_load_response(device->sdmmc, SDMMC_RSP_R3, &resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Card Power up bit is set. */
|
|
if (resp & MMC_CARD_BUSY) {
|
|
/* We have a SDHC block mode card. */
|
|
if (resp & SD_OCR_CCS) {
|
|
device->is_block_sdhc = true;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Keep checking if timeout expired. */
|
|
is_timeout = (get_time_since(timebase) > 2000000);
|
|
|
|
/* Delay for a minimum of 10 milliseconds. */
|
|
mdelay(10);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdmmc_mmc_send_ext_csd(sdmmc_device_t *device, uint8_t *ext_csd) {
|
|
sdmmc_command_t cmd = {};
|
|
sdmmc_request_t req = {};
|
|
|
|
cmd.opcode = MMC_SEND_EXT_CSD;
|
|
cmd.arg = 0;
|
|
cmd.flags = SDMMC_RSP_R1;
|
|
|
|
req.data = ext_csd;
|
|
req.blksz = 512;
|
|
req.num_blocks = 1;
|
|
req.is_read = true;
|
|
req.is_multi_block = false;
|
|
req.is_auto_cmd12 = false;
|
|
|
|
/* Try to send the command. */
|
|
if (!sdmmc_send_cmd(device->sdmmc, &cmd, &req, 0)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t resp = 0;
|
|
|
|
/* Try to load back the response. */
|
|
if (!sdmmc_load_response(device->sdmmc, SDMMC_RSP_R1, &resp)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Evaluate the response. */
|
|
if (is_sdmmc_device_r1_error(resp)) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int sdmmc_mmc_set_relative_addr(sdmmc_device_t *device) {
|
|
/* Try to send the command. */
|
|
return sdmmc_device_send_r1_cmd(device, MMC_SET_RELATIVE_ADDR, (device->rca << 16), false, 0, 0xFFFFFFFF);
|
|
}
|
|
|
|
static int sdmmc_mmc_switch(sdmmc_device_t *device, uint32_t arg) {
|
|
/* Try to send the command. */
|
|
return sdmmc_device_send_r1_cmd(device, MMC_SWITCH, arg, true, 0, 0xFFFFFFFF);
|
|
}
|
|
|
|
static int sdmmc_mmc_select_bus_width(sdmmc_device_t *device, SdmmcBusWidth bus_width) {
|
|
uint32_t arg = 0;
|
|
|
|
/* Choose the argument for the switch command. */
|
|
switch (bus_width) {
|
|
case SDMMC_BUS_WIDTH_1BIT:
|
|
return 1;
|
|
case SDMMC_BUS_WIDTH_4BIT:
|
|
arg = (((MMC_SWITCH_MODE_WRITE_BYTE) << 24) | ((EXT_CSD_BUS_WIDTH) << 16) | ((EXT_CSD_BUS_WIDTH_4) << 8));
|
|
break;
|
|
case SDMMC_BUS_WIDTH_8BIT:
|
|
arg = (((MMC_SWITCH_MODE_WRITE_BYTE) << 24) | ((EXT_CSD_BUS_WIDTH) << 16) | ((EXT_CSD_BUS_WIDTH_8) << 8));
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
/* Try to switch the bus width. */
|
|
if (sdmmc_mmc_switch(device, arg) && sdmmc_device_send_status(device)) {
|
|
sdmmc_select_bus_width(device->sdmmc, bus_width);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdmmc_mmc_select_hs(sdmmc_device_t *device, bool ignore_status) {
|
|
uint32_t arg = (((MMC_SWITCH_MODE_WRITE_BYTE) << 24) | ((EXT_CSD_HS_TIMING) << 16) | ((EXT_CSD_TIMING_HS) << 8));
|
|
|
|
/* Try to switch to HS. */
|
|
if (!sdmmc_mmc_switch(device, arg)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Check the status if necessary. */
|
|
if (!ignore_status && !sdmmc_device_send_status(device)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Reconfigure the internal clock. */
|
|
if (!sdmmc_select_speed(device->sdmmc, SDMMC_SPEED_MMC_HS)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Check the status if necessary. */
|
|
if (!ignore_status && !sdmmc_device_send_status(device)) {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sdmmc_mmc_select_hs200(sdmmc_device_t *device) {
|
|
uint32_t arg = (((MMC_SWITCH_MODE_WRITE_BYTE) << 24) | ((EXT_CSD_HS_TIMING) << 16) | ((EXT_CSD_TIMING_HS200) << 8));
|
|
|
|
/* Try to switch to HS200. */
|
|
if (!sdmmc_mmc_switch(device, arg)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Reconfigure the internal clock. */
|
|
if (!sdmmc_select_speed(device->sdmmc, SDMMC_SPEED_MMC_HS200)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Execute tuning procedure. */
|
|
if (!sdmmc_execute_tuning(device->sdmmc, SDMMC_SPEED_MMC_HS200, MMC_SEND_TUNING_BLOCK_HS200)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Peek the current status. */
|
|
return sdmmc_device_send_status(device);
|
|
}
|
|
|
|
static int sdmmc_mmc_select_hs400(sdmmc_device_t *device) {
|
|
uint32_t arg = 0;
|
|
|
|
/* Switch to HS200 first. */
|
|
if (!sdmmc_mmc_select_hs200(device)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Fetch and set the tuning tap value. */
|
|
sdmmc_set_tuning_tap_val(device->sdmmc);
|
|
|
|
/* Switch to HS. */
|
|
if (!sdmmc_mmc_select_hs(device, true)) {
|
|
return 0;
|
|
}
|
|
|
|
arg = (((MMC_SWITCH_MODE_WRITE_BYTE) << 24) | ((EXT_CSD_BUS_WIDTH) << 16) | ((EXT_CSD_DDR_BUS_WIDTH_8) << 8));
|
|
|
|
/* Try to switch to 8bit bus. */
|
|
if (!sdmmc_mmc_switch(device, arg)) {
|
|
return 0;
|
|
}
|
|
|
|
arg = (((MMC_SWITCH_MODE_WRITE_BYTE) << 24) | ((EXT_CSD_HS_TIMING) << 16) | ((EXT_CSD_TIMING_HS400) << 8));
|
|
|
|
/* Try to switch to HS400. */
|
|
if (!sdmmc_mmc_switch(device, arg)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Reconfigure the internal clock. */
|
|
if (!sdmmc_select_speed(device->sdmmc, SDMMC_SPEED_MMC_HS400)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Peek the current status. */
|
|
return sdmmc_device_send_status(device);
|
|
}
|
|
|
|
static int sdmmc_mmc_select_timing(sdmmc_device_t *device, SdmmcBusSpeed bus_speed) {
|
|
if ((bus_speed == SDMMC_SPEED_MMC_HS400) &&
|
|
(device->sdmmc->bus_width == SDMMC_BUS_WIDTH_8BIT) &&
|
|
(device->ext_csd.raw_card_type & EXT_CSD_CARD_TYPE_HS400_1_8V)) {
|
|
/* Switch to HS400. */
|
|
return sdmmc_mmc_select_hs400(device);
|
|
} else if (((bus_speed == SDMMC_SPEED_MMC_HS400) || (bus_speed == SDMMC_SPEED_MMC_HS200)) &&
|
|
((device->sdmmc->bus_width == SDMMC_BUS_WIDTH_8BIT) || (device->sdmmc->bus_width == SDMMC_BUS_WIDTH_4BIT)) &&
|
|
(device->ext_csd.raw_card_type & EXT_CSD_CARD_TYPE_HS200_1_8V)) {
|
|
/* Switch to HS200. */
|
|
return sdmmc_mmc_select_hs200(device);
|
|
} else if (device->ext_csd.raw_card_type & EXT_CSD_CARD_TYPE_HS_52) {
|
|
/* Switch to HS. */
|
|
return sdmmc_mmc_select_hs(device, false);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sdmmc_mmc_select_bkops(sdmmc_device_t *device) {
|
|
uint32_t arg = (((MMC_SWITCH_MODE_SET_BITS) << 24) | ((EXT_CSD_BKOPS_EN) << 16) | ((EXT_CSD_BKOPS_LEVEL_2) << 8));
|
|
|
|
/* Try to enable bkops. */
|
|
if (!sdmmc_mmc_switch(device, arg)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Peek the current status. */
|
|
return sdmmc_device_send_status(device);
|
|
}
|
|
|
|
int sdmmc_mmc_select_partition(sdmmc_device_t *device, SdmmcPartitionNum partition) {
|
|
uint32_t arg = (((MMC_SWITCH_MODE_WRITE_BYTE) << 24) | ((EXT_CSD_PART_CONFIG) << 16) | ((partition) << 8));
|
|
|
|
/* Try to change the active partition. */
|
|
if (!sdmmc_mmc_switch(device, arg)) {
|
|
return 0;
|
|
}
|
|
|
|
/* Peek the current status. */
|
|
return sdmmc_device_send_status(device);
|
|
}
|
|
|
|
int sdmmc_device_mmc_init(sdmmc_device_t *device, sdmmc_t *sdmmc, SdmmcBusWidth bus_width, SdmmcBusSpeed bus_speed) {
|
|
uint32_t cid[4] = {0};
|
|
uint32_t csd[4] = {0};
|
|
uint8_t ext_csd[512] = {0};
|
|
|
|
/* Initialize our device's struct. */
|
|
memset(device, 0, sizeof(sdmmc_device_t));
|
|
|
|
/* Try to initialize the driver. */
|
|
if (!sdmmc_init(sdmmc, SDMMC_4, SDMMC_VOLTAGE_1V8, SDMMC_BUS_WIDTH_1BIT, SDMMC_SPEED_MMC_IDENT)) {
|
|
sdmmc_error(sdmmc, "Failed to initialize the SDMMC driver!");
|
|
return 0;
|
|
}
|
|
|
|
/* Bind the underlying driver. */
|
|
device->sdmmc = sdmmc;
|
|
|
|
/* Set RCA. */
|
|
device->rca = 0x01;
|
|
|
|
sdmmc_info(sdmmc, "SDMMC driver was successfully initialized for eMMC!");
|
|
|
|
/* Apply at least 74 clock cycles. eMMC should be ready afterwards. */
|
|
udelay((74000 + sdmmc->internal_divider - 1) / sdmmc->internal_divider);
|
|
|
|
/* Instruct the eMMC to go idle. */
|
|
if (!sdmmc_device_go_idle(device)) {
|
|
sdmmc_error(sdmmc, "Failed to go idle!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "eMMC went idle!");
|
|
|
|
/* Get the eMMC's operating conditions. */
|
|
if (!sdmmc_mmc_send_op_cond(device, SDMMC_VOLTAGE_1V8)) {
|
|
sdmmc_error(sdmmc, "Failed to send op cond!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Sent op cond to eMMC!");
|
|
|
|
/* Get the eMMC's CID. */
|
|
if (!sdmmc_device_send_cid(device, cid)) {
|
|
sdmmc_error(sdmmc, "Failed to get CID!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Got CID from eMMC!");
|
|
|
|
/* Set the eMMC's RCA. */
|
|
if (!sdmmc_mmc_set_relative_addr(device)) {
|
|
sdmmc_error(sdmmc, "Failed to set RCA!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "RCA is now set in eMMC!");
|
|
|
|
/* Get the eMMC card's CSD. */
|
|
if (!sdmmc_device_send_csd(device, csd)) {
|
|
sdmmc_error(sdmmc, "Failed to get CSD!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Got CSD from eMMC!");
|
|
|
|
/* Decode and save the CSD. */
|
|
if (!sdmmc_mmc_decode_csd(device, csd)) {
|
|
sdmmc_warn(sdmmc, "Got unknown CSD structure (0x%08x)!", device->csd.structure);
|
|
}
|
|
|
|
/* Reconfigure the internal clock. */
|
|
if (!sdmmc_select_speed(device->sdmmc, SDMMC_SPEED_MMC_LEGACY)) {
|
|
sdmmc_error(sdmmc, "Failed to apply the correct bus speed!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Speed mode has been adjusted!");
|
|
|
|
/* Select the eMMC card. */
|
|
if (!sdmmc_device_select_card(device)) {
|
|
sdmmc_error(sdmmc, "Failed to select eMMC card!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "eMMC card is now selected!");
|
|
|
|
/* Change the eMMC's block length. */
|
|
if (!sdmmc_device_set_blocklen(device, 512)) {
|
|
sdmmc_error(sdmmc, "Failed to set eMMC's block length!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "eMMC's block length is now 512!");
|
|
|
|
/* Only specification version 4 and later support the next features. */
|
|
if (device->csd.mmca_vsn < CSD_SPEC_VER_4) {
|
|
return 1;
|
|
}
|
|
|
|
/* Change the eMMC's bus width. */
|
|
if (!sdmmc_mmc_select_bus_width(device, bus_width)) {
|
|
sdmmc_error(sdmmc, "Failed to set eMMC's bus width!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "eMMC's bus width has been adjusted!");
|
|
|
|
/* Get the eMMC's extended CSD. */
|
|
if (!sdmmc_mmc_send_ext_csd(device, ext_csd)) {
|
|
sdmmc_error(sdmmc, "Failed to get EXT_CSD!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Got EXT_CSD from eMMC!");
|
|
|
|
/* Decode and save the extended CSD. */
|
|
sdmmc_mmc_decode_ext_csd(device, ext_csd);
|
|
|
|
/* Decode and save the CID. */
|
|
sdmmc_mmc_decode_cid(device, cid);
|
|
|
|
/* TODO: Handle automatic BKOPS properly. Leave it disabled for now. */
|
|
if (false && device->ext_csd.bkops && !(device->ext_csd.auto_bkops_en & EXT_CSD_AUTO_BKOPS_MASK)) {
|
|
sdmmc_mmc_select_bkops(device);
|
|
sdmmc_info(sdmmc, "BKOPS is enabled!");
|
|
} else {
|
|
sdmmc_info(sdmmc, "BKOPS is disabled!");
|
|
}
|
|
|
|
/* Switch to high speed mode. */
|
|
if (!sdmmc_mmc_select_timing(device, bus_speed)) {
|
|
sdmmc_error(sdmmc, "Failed to switch to high speed mode!");
|
|
return 0;
|
|
}
|
|
|
|
sdmmc_info(sdmmc, "Switched to high speed mode!");
|
|
|
|
/* Correct any inconsistent states. */
|
|
sdmmc_adjust_sd_clock(sdmmc);
|
|
|
|
return 1;
|
|
} |