1072 lines
29 KiB
C
1072 lines
29 KiB
C
/*
|
|
* Copyright 2022-2023 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/drivers/sdhc.h>
|
|
#include <zephyr/sd/sd.h>
|
|
#include <zephyr/sd/sdmmc.h>
|
|
#include <zephyr/sd/sd_spec.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
#include "sd_ops.h"
|
|
#include "sd_utils.h"
|
|
|
|
LOG_MODULE_DECLARE(sd, CONFIG_SD_LOG_LEVEL);
|
|
|
|
uint8_t cis_tuples[] = {
|
|
SDIO_TPL_CODE_MANIFID,
|
|
SDIO_TPL_CODE_FUNCID,
|
|
SDIO_TPL_CODE_FUNCE,
|
|
};
|
|
|
|
/*
|
|
* Send SDIO OCR using CMD5
|
|
*/
|
|
static int sdio_send_ocr(struct sd_card *card, uint32_t ocr)
|
|
{
|
|
struct sdhc_command cmd = {0};
|
|
int ret;
|
|
int retries;
|
|
|
|
cmd.opcode = SDIO_SEND_OP_COND;
|
|
cmd.arg = ocr;
|
|
cmd.response_type = (SD_RSP_TYPE_R4 | SD_SPI_RSP_TYPE_R4);
|
|
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
|
|
/* Send OCR5 to initialize card */
|
|
for (retries = 0; retries < CONFIG_SD_OCR_RETRY_COUNT; retries++) {
|
|
ret = sdhc_request(card->sdhc, &cmd, NULL);
|
|
if (ret) {
|
|
if (ocr == 0) {
|
|
/* Just probing card, likely not SDIO */
|
|
return SD_NOT_SDIO;
|
|
}
|
|
return ret;
|
|
}
|
|
if (ocr == 0) {
|
|
/* We are probing card, check number of IO functions */
|
|
card->num_io = (cmd.response[0] & SDIO_OCR_IO_NUMBER)
|
|
>> SDIO_OCR_IO_NUMBER_SHIFT;
|
|
if ((card->num_io == 0) ||
|
|
((cmd.response[0] & SDIO_IO_OCR_MASK) == 0)) {
|
|
if (cmd.response[0] & SDIO_OCR_MEM_PRESENT_FLAG) {
|
|
/* Card is not an SDIO card */
|
|
return SD_NOT_SDIO;
|
|
}
|
|
/* Card is not a supported SD device */
|
|
return -ENOTSUP;
|
|
}
|
|
/* Card has IO present, return zero to
|
|
* indicate SDIO card
|
|
*/
|
|
return 0;
|
|
}
|
|
/* Check to see if card is busy with power up */
|
|
if (cmd.response[0] & SD_OCR_PWR_BUSY_FLAG) {
|
|
break;
|
|
}
|
|
/* Delay before retrying command */
|
|
sd_delay(10);
|
|
}
|
|
if (retries >= CONFIG_SD_OCR_RETRY_COUNT) {
|
|
/* OCR timed out */
|
|
LOG_ERR("Card never left busy state");
|
|
return -ETIMEDOUT;
|
|
}
|
|
LOG_DBG("SDIO responded to CMD5 after %d attempts", retries);
|
|
if (!card->host_props.is_spi) {
|
|
/* Save OCR */
|
|
card->ocr = cmd.response[0U];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sdio_io_rw_direct(struct sd_card *card,
|
|
enum sdio_io_dir direction,
|
|
enum sdio_func_num func,
|
|
uint32_t reg_addr,
|
|
uint8_t data_in,
|
|
uint8_t *data_out)
|
|
{
|
|
int ret;
|
|
struct sdhc_command cmd = {0};
|
|
|
|
cmd.opcode = SDIO_RW_DIRECT;
|
|
cmd.arg = (func << SDIO_CMD_ARG_FUNC_NUM_SHIFT) |
|
|
((reg_addr & SDIO_CMD_ARG_REG_ADDR_MASK) << SDIO_CMD_ARG_REG_ADDR_SHIFT);
|
|
if (direction == SDIO_IO_WRITE) {
|
|
cmd.arg |= data_in & SDIO_DIRECT_CMD_DATA_MASK;
|
|
cmd.arg |= BIT(SDIO_CMD_ARG_RW_SHIFT);
|
|
if (data_out) {
|
|
cmd.arg |= BIT(SDIO_DIRECT_CMD_ARG_RAW_SHIFT);
|
|
}
|
|
}
|
|
cmd.response_type = (SD_RSP_TYPE_R5 | SD_SPI_RSP_TYPE_R5);
|
|
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
|
|
|
|
ret = sdhc_request(card->sdhc, &cmd, NULL);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if (data_out) {
|
|
if (card->host_props.is_spi) {
|
|
*data_out = (cmd.response[0U] >> 8) & SDIO_DIRECT_CMD_DATA_MASK;
|
|
} else {
|
|
*data_out = cmd.response[0U] & SDIO_DIRECT_CMD_DATA_MASK;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int sdio_io_rw_extended(struct sd_card *card,
|
|
enum sdio_io_dir direction,
|
|
enum sdio_func_num func,
|
|
uint32_t reg_addr,
|
|
bool increment,
|
|
uint8_t *buf,
|
|
uint32_t blocks,
|
|
uint32_t block_size)
|
|
{
|
|
struct sdhc_command cmd = {0};
|
|
struct sdhc_data data = {0};
|
|
|
|
cmd.opcode = SDIO_RW_EXTENDED;
|
|
cmd.arg = (func << SDIO_CMD_ARG_FUNC_NUM_SHIFT) |
|
|
((reg_addr & SDIO_CMD_ARG_REG_ADDR_MASK) << SDIO_CMD_ARG_REG_ADDR_SHIFT);
|
|
cmd.arg |= (direction == SDIO_IO_WRITE) ? BIT(SDIO_CMD_ARG_RW_SHIFT) : 0;
|
|
cmd.arg |= increment ? BIT(SDIO_EXTEND_CMD_ARG_OP_CODE_SHIFT) : 0;
|
|
cmd.response_type = (SD_RSP_TYPE_R5 | SD_SPI_RSP_TYPE_R5);
|
|
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
|
|
if (blocks == 0) {
|
|
/* Byte mode */
|
|
cmd.arg |= (block_size == 512) ? 0 : block_size;
|
|
} else {
|
|
/* Block mode */
|
|
cmd.arg |= BIT(SDIO_EXTEND_CMD_ARG_BLK_SHIFT) | blocks;
|
|
}
|
|
|
|
data.block_size = block_size;
|
|
/* Host expects blocks to be at least 1 */
|
|
data.blocks = blocks ? blocks : 1;
|
|
data.data = buf;
|
|
data.timeout_ms = CONFIG_SD_DATA_TIMEOUT;
|
|
return sdhc_request(card->sdhc, &cmd, &data);
|
|
}
|
|
|
|
/*
|
|
* Helper for extended r/w. Splits the transfer into the minimum possible
|
|
* number of block r/w, then uses byte transfers for remaining data
|
|
*/
|
|
static int sdio_io_rw_extended_helper(struct sdio_func *func,
|
|
enum sdio_io_dir direction,
|
|
uint32_t reg_addr,
|
|
bool increment,
|
|
uint8_t *buf,
|
|
uint32_t len)
|
|
{
|
|
int ret;
|
|
int remaining = len;
|
|
uint32_t blocks, size;
|
|
|
|
if (func->num > SDIO_MAX_IO_NUMS) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((func->card->cccr_flags & SDIO_SUPPORT_MULTIBLOCK) &&
|
|
((len > func->block_size))) {
|
|
/* Use block I/O for r/w where possible */
|
|
while (remaining >= func->block_size) {
|
|
blocks = remaining / func->block_size;
|
|
size = blocks * func->block_size;
|
|
ret = sdio_io_rw_extended(func->card, direction,
|
|
func->num, reg_addr, increment, buf, blocks,
|
|
func->block_size);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
/* Update remaining length and buffer pointer */
|
|
remaining -= size;
|
|
buf += size;
|
|
if (increment) {
|
|
reg_addr += size;
|
|
}
|
|
}
|
|
}
|
|
/* Remaining data must be written using byte I/O */
|
|
while (remaining > 0) {
|
|
size = MIN(remaining, func->cis.max_blk_size);
|
|
|
|
ret = sdio_io_rw_extended(func->card, direction, func->num,
|
|
reg_addr, increment, buf, 0, size);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
remaining -= size;
|
|
buf += size;
|
|
if (increment) {
|
|
reg_addr += size;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Read card capability register to determine features card supports.
|
|
*/
|
|
static int sdio_read_cccr(struct sd_card *card)
|
|
{
|
|
int ret;
|
|
uint8_t data;
|
|
uint32_t cccr_ver;
|
|
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_CCCR, 0, &data);
|
|
if (ret) {
|
|
LOG_DBG("CCCR read failed: %d", ret);
|
|
return ret;
|
|
}
|
|
cccr_ver = (data & SDIO_CCCR_CCCR_REV_MASK) >>
|
|
SDIO_CCCR_CCCR_REV_SHIFT;
|
|
LOG_DBG("SDIO cccr revision %u", cccr_ver);
|
|
/* Read SD spec version */
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_SD, 0, &data);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
card->sd_version = (data & SDIO_CCCR_SD_SPEC_MASK) >> SDIO_CCCR_SD_SPEC_SHIFT;
|
|
/* Read CCCR capability flags */
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_CAPS, 0, &data);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
card->cccr_flags = 0;
|
|
if (data & SDIO_CCCR_CAPS_BLS) {
|
|
card->cccr_flags |= SDIO_SUPPORT_4BIT_LS_BUS;
|
|
}
|
|
if (data & SDIO_CCCR_CAPS_SMB) {
|
|
card->cccr_flags |= SDIO_SUPPORT_MULTIBLOCK;
|
|
}
|
|
if (cccr_ver >= SDIO_CCCR_CCCR_REV_2_00) {
|
|
/* Read high speed properties */
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_SPEED, 0, &data);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if (data & SDIO_CCCR_SPEED_SHS) {
|
|
card->cccr_flags |= SDIO_SUPPORT_HS;
|
|
}
|
|
}
|
|
if (cccr_ver >= SDIO_CCCR_CCCR_REV_3_00 &&
|
|
(card->flags & SD_1800MV_FLAG)) {
|
|
/* Read UHS properties */
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_UHS, 0, &data);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if (sdmmc_host_uhs(&card->host_props)) {
|
|
if (data & SDIO_CCCR_UHS_SDR50) {
|
|
card->cccr_flags |= SDIO_SUPPORT_SDR50;
|
|
}
|
|
if (data & SDIO_CCCR_UHS_SDR104) {
|
|
card->cccr_flags |= SDIO_SUPPORT_SDR104;
|
|
}
|
|
if (data & SDIO_CCCR_UHS_DDR50) {
|
|
card->cccr_flags |= SDIO_SUPPORT_DDR50;
|
|
}
|
|
}
|
|
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_DRIVE_STRENGTH, 0, &data);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
card->switch_caps.sd_drv_type = 0;
|
|
if (data & SDIO_CCCR_DRIVE_STRENGTH_A) {
|
|
card->switch_caps.sd_drv_type |= SD_DRIVER_TYPE_A;
|
|
}
|
|
if (data & SDIO_CCCR_DRIVE_STRENGTH_C) {
|
|
card->switch_caps.sd_drv_type |= SD_DRIVER_TYPE_C;
|
|
}
|
|
if (data & SDIO_CCCR_DRIVE_STRENGTH_D) {
|
|
card->switch_caps.sd_drv_type |= SD_DRIVER_TYPE_D;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void sdio_decode_cis(struct sdio_cis *cis, enum sdio_func_num func,
|
|
uint8_t *data, uint8_t tpl_code, uint8_t tpl_link)
|
|
{
|
|
switch (tpl_code) {
|
|
case SDIO_TPL_CODE_MANIFID:
|
|
cis->manf_id = data[0] | ((uint16_t)data[1] << 8);
|
|
cis->manf_code = data[2] | ((uint16_t)data[3] << 8);
|
|
break;
|
|
case SDIO_TPL_CODE_FUNCID:
|
|
cis->func_id = data[0];
|
|
break;
|
|
case SDIO_TPL_CODE_FUNCE:
|
|
if (func == 0) {
|
|
cis->max_blk_size = data[1] | ((uint16_t)data[2] << 8);
|
|
cis->max_speed = data[3];
|
|
} else {
|
|
cis->max_blk_size = data[12] | ((uint16_t)data[13] << 8);
|
|
cis->rdy_timeout = data[28] | ((uint16_t)data[29] << 8);
|
|
}
|
|
break;
|
|
default:
|
|
LOG_WRN("Unknown CIS tuple %d", tpl_code);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read CIS for a given SDIO function.
|
|
* Tuples provides a list of tuples that should be decoded.
|
|
*/
|
|
static int sdio_read_cis(struct sdio_func *func,
|
|
uint8_t *tuples,
|
|
uint32_t tuple_count)
|
|
{
|
|
int ret;
|
|
char *data = func->card->card_buffer;
|
|
uint32_t cis_ptr = 0, num = 0;
|
|
uint8_t tpl_code, tpl_link;
|
|
bool match_tpl = false;
|
|
|
|
memset(&func->cis, 0, sizeof(struct sdio_cis));
|
|
/* First find the CIS pointer for this function */
|
|
for (int i = 0; i < 3; i++) {
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_FBR_BASE(func->num) + SDIO_FBR_CIS + i, 0, data);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
cis_ptr |= *data << (i * 8);
|
|
}
|
|
/* Read CIS tuples until we have read all requested CIS tuple codes */
|
|
do {
|
|
/* Read tuple code */
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
cis_ptr++, 0, &tpl_code);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if (tpl_code == SDIO_TPL_CODE_END) {
|
|
/* End of tuple chain */
|
|
break;
|
|
}
|
|
if (tpl_code == SDIO_TPL_CODE_NULL) {
|
|
/* Skip NULL tuple */
|
|
continue;
|
|
}
|
|
/* Read tuple link */
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
cis_ptr++, 0, &tpl_link);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if (tpl_link == SDIO_TPL_CODE_END) {
|
|
/* End of tuple chain */
|
|
break;
|
|
}
|
|
/* Check to see if read tuple matches any we should look for */
|
|
for (int i = 0; i < tuple_count; i++) {
|
|
if (tpl_code == tuples[i]) {
|
|
match_tpl = true;
|
|
break;
|
|
}
|
|
}
|
|
if (match_tpl) {
|
|
/* tuple chains may be maximum of 255 bytes long */
|
|
memset(data, 0, 255);
|
|
for (int i = 0; i < tpl_link; i++) {
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_READ,
|
|
SDIO_FUNC_NUM_0, cis_ptr++, 0, data + i);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
num++;
|
|
match_tpl = false;
|
|
/* Decode the CIS data we read */
|
|
sdio_decode_cis(&func->cis, func->num, data,
|
|
tpl_code, tpl_link);
|
|
} else {
|
|
/* Advance CIS pointer */
|
|
cis_ptr += tpl_link;
|
|
}
|
|
} while (num < tuple_count);
|
|
LOG_DBG("SDIO CIS max block size for func %d: %d", func->num,
|
|
func->cis.max_blk_size);
|
|
return ret;
|
|
}
|
|
|
|
static int sdio_set_bus_width(struct sd_card *card, enum sdhc_bus_width width)
|
|
{
|
|
uint8_t reg_bus_interface = 0U;
|
|
int ret;
|
|
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_BUS_IF, 0, ®_bus_interface);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
reg_bus_interface &= ~SDIO_CCCR_BUS_IF_WIDTH_MASK;
|
|
switch (width) {
|
|
case SDHC_BUS_WIDTH1BIT:
|
|
reg_bus_interface |= SDIO_CCCR_BUS_IF_WIDTH_1_BIT;
|
|
break;
|
|
case SDHC_BUS_WIDTH4BIT:
|
|
reg_bus_interface |= SDIO_CCCR_BUS_IF_WIDTH_4_BIT;
|
|
break;
|
|
case SDHC_BUS_WIDTH8BIT:
|
|
reg_bus_interface |= SDIO_CCCR_BUS_IF_WIDTH_8_BIT;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_WRITE, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_BUS_IF, reg_bus_interface, ®_bus_interface);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
/* Card now has changed bus width. Change host bus width */
|
|
card->bus_io.bus_width = width;
|
|
ret = sdhc_set_io(card->sdhc, &card->bus_io);
|
|
if (ret) {
|
|
LOG_DBG("Could not change host bus width");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static inline void sdio_select_bus_speed(struct sd_card *card)
|
|
{
|
|
if (card->host_props.host_caps.sdr104_support &&
|
|
(card->cccr_flags & SDIO_SUPPORT_SDR104)) {
|
|
card->card_speed = SD_TIMING_SDR104;
|
|
card->switch_caps.uhs_max_dtr = UHS_SDR104_MAX_DTR;
|
|
} else if (card->host_props.host_caps.ddr50_support &&
|
|
(card->cccr_flags & SDIO_SUPPORT_DDR50)) {
|
|
card->card_speed = SD_TIMING_DDR50;
|
|
card->switch_caps.uhs_max_dtr = UHS_DDR50_MAX_DTR;
|
|
} else if (card->host_props.host_caps.sdr50_support &&
|
|
(card->cccr_flags & SDIO_SUPPORT_SDR50)) {
|
|
card->card_speed = SD_TIMING_SDR50;
|
|
card->switch_caps.uhs_max_dtr = UHS_SDR50_MAX_DTR;
|
|
} else if (card->host_props.host_caps.high_spd_support &&
|
|
(card->cccr_flags & SDIO_SUPPORT_HS)) {
|
|
card->card_speed = SD_TIMING_HIGH_SPEED;
|
|
card->switch_caps.hs_max_dtr = HS_MAX_DTR;
|
|
} else {
|
|
card->card_speed = SD_TIMING_DEFAULT;
|
|
}
|
|
}
|
|
|
|
/* Applies selected card bus speed to card and host */
|
|
static int sdio_set_bus_speed(struct sd_card *card)
|
|
{
|
|
int ret, timing, retries = CONFIG_SD_RETRY_COUNT;
|
|
uint32_t bus_clock;
|
|
uint8_t speed_reg, target_speed;
|
|
|
|
switch (card->card_speed) {
|
|
/* Set bus clock speed */
|
|
case SD_TIMING_SDR104:
|
|
bus_clock = MIN(card->host_props.f_max, card->switch_caps.uhs_max_dtr);
|
|
target_speed = SDIO_CCCR_SPEED_SDR104;
|
|
timing = SDHC_TIMING_SDR104;
|
|
break;
|
|
case SD_TIMING_DDR50:
|
|
bus_clock = MIN(card->host_props.f_max, card->switch_caps.uhs_max_dtr);
|
|
target_speed = SDIO_CCCR_SPEED_DDR50;
|
|
timing = SDHC_TIMING_DDR50;
|
|
break;
|
|
case SD_TIMING_SDR50:
|
|
bus_clock = MIN(card->host_props.f_max, card->switch_caps.uhs_max_dtr);
|
|
target_speed = SDIO_CCCR_SPEED_SDR50;
|
|
timing = SDHC_TIMING_SDR50;
|
|
break;
|
|
case SD_TIMING_HIGH_SPEED:
|
|
bus_clock = MIN(card->host_props.f_max, card->switch_caps.hs_max_dtr);
|
|
target_speed = SDIO_CCCR_SPEED_SDR25;
|
|
timing = SDHC_TIMING_HS;
|
|
break;
|
|
case SD_TIMING_DEFAULT:
|
|
bus_clock = MIN(card->host_props.f_max, MHZ(25));
|
|
target_speed = SDIO_CCCR_SPEED_SDR12;
|
|
timing = SDHC_TIMING_LEGACY;
|
|
break;
|
|
default:
|
|
/* No need to change bus speed */
|
|
return 0;
|
|
}
|
|
/* Read the bus speed register */
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_SPEED, 0, &speed_reg);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
/* Attempt to set speed several times */
|
|
do {
|
|
/* Set new speed */
|
|
speed_reg &= ~SDIO_CCCR_SPEED_MASK;
|
|
speed_reg |= (target_speed << SDIO_CCCR_SPEED_SHIFT);
|
|
ret = sdio_io_rw_direct(card, SDIO_IO_WRITE, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_SPEED, speed_reg, &speed_reg);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
} while (((speed_reg & target_speed) != target_speed) && retries-- > 0);
|
|
if (retries == 0) {
|
|
/* Don't error out, as card can still work */
|
|
LOG_WRN("Could not set target SDIO speed");
|
|
} else {
|
|
/* Set card bus clock and timing */
|
|
card->bus_io.timing = timing;
|
|
card->bus_io.clock = bus_clock;
|
|
LOG_DBG("Setting bus clock to: %d", card->bus_io.clock);
|
|
ret = sdhc_set_io(card->sdhc, &card->bus_io);
|
|
if (ret) {
|
|
LOG_ERR("Failed to change host bus speed");
|
|
return ret;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Initialize an SDIO card for use with subsystem
|
|
*/
|
|
int sdio_card_init(struct sd_card *card)
|
|
{
|
|
int ret;
|
|
uint32_t ocr_arg = 0U;
|
|
|
|
/* Probe card with SDIO OCR CM5 */
|
|
ret = sdio_send_ocr(card, ocr_arg);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
/* Card responded to CMD5, type is SDIO */
|
|
card->type = CARD_SDIO;
|
|
/* Set voltage window */
|
|
if (card->host_props.host_caps.vol_300_support) {
|
|
ocr_arg |= SD_OCR_VDD29_30FLAG;
|
|
}
|
|
ocr_arg |= (SD_OCR_VDD32_33FLAG | SD_OCR_VDD33_34FLAG);
|
|
if (IS_ENABLED(CONFIG_SDHC_SUPPORTS_NATIVE_MODE) &&
|
|
card->host_props.host_caps.vol_180_support) {
|
|
/* See if the card also supports 1.8V */
|
|
ocr_arg |= SD_OCR_SWITCH_18_REQ_FLAG;
|
|
}
|
|
ret = sdio_send_ocr(card, ocr_arg);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if (card->ocr & SD_OCR_SWITCH_18_ACCEPT_FLAG) {
|
|
LOG_DBG("Card supports 1.8V signalling");
|
|
card->flags |= SD_1800MV_FLAG;
|
|
}
|
|
/* Check OCR voltage window */
|
|
if (card->ocr & SD_OCR_VDD29_30FLAG) {
|
|
card->flags |= SD_3000MV_FLAG;
|
|
}
|
|
/* Check mem present flag */
|
|
if (card->ocr & SDIO_OCR_MEM_PRESENT_FLAG) {
|
|
card->flags |= SD_MEM_PRESENT_FLAG;
|
|
}
|
|
/* Following steps are only required when using native SD mode */
|
|
if (IS_ENABLED(CONFIG_SDHC_SUPPORTS_NATIVE_MODE)) {
|
|
/*
|
|
* If card and host support 1.8V, perform voltage switch sequence now.
|
|
* note that we skip this switch if the UHS protocol is not enabled.
|
|
*/
|
|
if ((card->flags & SD_1800MV_FLAG) &&
|
|
(!card->host_props.is_spi) &&
|
|
(card->host_props.host_caps.vol_180_support) &&
|
|
IS_ENABLED(CONFIG_SD_UHS_PROTOCOL)) {
|
|
ret = sdmmc_switch_voltage(card);
|
|
if (ret) {
|
|
/* Disable host support for 1.8 V */
|
|
card->host_props.host_caps.vol_180_support = false;
|
|
/*
|
|
* The host or SD card may have already switched to
|
|
* 1.8V. Return SD_RESTART to indicate
|
|
* negotiation should be restarted.
|
|
*/
|
|
card->status = CARD_ERROR;
|
|
return SD_RESTART;
|
|
}
|
|
}
|
|
if ((card->flags & SD_MEM_PRESENT_FLAG) &&
|
|
((card->flags & SD_SDHC_FLAG) == 0)) {
|
|
/* We must send CMD2 to get card cid */
|
|
ret = card_read_cid(card);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
/* Send CMD3 to get card relative address */
|
|
ret = sdmmc_request_rca(card);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
/* Move the card to transfer state (with CMD7) to run
|
|
* remaining commands
|
|
*/
|
|
ret = sdmmc_select_card(card);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
/* Read SDIO card common control register */
|
|
ret = sdio_read_cccr(card);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
/* Initialize internal card function 0 structure */
|
|
card->func0.num = SDIO_FUNC_NUM_0;
|
|
card->func0.card = card;
|
|
ret = sdio_read_cis(&card->func0, cis_tuples,
|
|
ARRAY_SIZE(cis_tuples));
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* If card and host support 4 bit bus, enable it */
|
|
if (IS_ENABLED(CONFIG_SDHC_SUPPORTS_NATIVE_MODE) &&
|
|
((card->cccr_flags & SDIO_SUPPORT_HS) ||
|
|
(card->cccr_flags & SDIO_SUPPORT_4BIT_LS_BUS))) {
|
|
/* Raise bus width to 4 bits */
|
|
ret = sdio_set_bus_width(card, SDHC_BUS_WIDTH4BIT);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
LOG_DBG("Raised card bus width to 4 bits");
|
|
}
|
|
|
|
/* Select and set bus speed */
|
|
sdio_select_bus_speed(card);
|
|
ret = sdio_set_bus_speed(card);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if (card->card_speed == SD_TIMING_SDR50 ||
|
|
card->card_speed == SD_TIMING_SDR104) {
|
|
/* SDR104, SDR50, and DDR50 mode need tuning */
|
|
ret = sdhc_execute_tuning(card->sdhc);
|
|
if (ret) {
|
|
LOG_ERR("SD tuning failed: %d", ret);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize SDIO function.
|
|
*
|
|
* Initializes SDIO card function. The card function will not be enabled,
|
|
* but after this call returns the SDIO function structure can be used to read
|
|
* and write data from the card.
|
|
* @param func: function structure to initialize
|
|
* @param card: SD card to enable function on
|
|
* @param num: function number to initialize
|
|
* @retval 0 function was initialized successfully
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_init_func(struct sd_card *card, struct sdio_func *func,
|
|
enum sdio_func_num num)
|
|
{
|
|
/* Initialize function structure */
|
|
func->num = num;
|
|
func->card = card;
|
|
func->block_size = 0;
|
|
/* Read function properties from CCCR */
|
|
return sdio_read_cis(func, cis_tuples, ARRAY_SIZE(cis_tuples));
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Enable SDIO function
|
|
*
|
|
* Enables SDIO card function. @ref sdio_init_func must be called to
|
|
* initialized the function structure before enabling it in the card.
|
|
* @param func: function to enable
|
|
* @retval 0 function was enabled successfully
|
|
* @retval -ETIMEDOUT: card I/O timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_enable_func(struct sdio_func *func)
|
|
{
|
|
int ret;
|
|
uint8_t reg;
|
|
uint16_t retries = CONFIG_SD_RETRY_COUNT;
|
|
|
|
/* Enable the I/O function */
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_READ, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_IO_EN, 0, ®);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
reg |= BIT(func->num);
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_WRITE, SDIO_FUNC_NUM_0,
|
|
SDIO_CCCR_IO_EN, reg, ®);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
/* Wait for I/O ready to be set */
|
|
if (func->cis.rdy_timeout) {
|
|
retries = 1U;
|
|
}
|
|
do {
|
|
/* Timeout is in units of 10ms */
|
|
sd_delay(((uint32_t)func->cis.rdy_timeout) * 10U);
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_READ,
|
|
SDIO_FUNC_NUM_0, SDIO_CCCR_IO_RD, 0, ®);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
if (reg & BIT(func->num)) {
|
|
return 0;
|
|
}
|
|
} while (retries-- != 0);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/**
|
|
* @brief Set block size of SDIO function
|
|
*
|
|
* Set desired block size for SDIO function, used by block transfers
|
|
* to SDIO registers.
|
|
* @param func: function to set block size for
|
|
* @param bsize: block size
|
|
* @retval 0 block size was set
|
|
* @retval -EINVAL: unsupported/invalid block size
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_set_block_size(struct sdio_func *func, uint16_t bsize)
|
|
{
|
|
int ret;
|
|
uint8_t reg;
|
|
|
|
if (func->cis.max_blk_size < bsize) {
|
|
return -EINVAL;
|
|
}
|
|
for (int i = 0; i < 2; i++) {
|
|
reg = (bsize >> (i * 8));
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_WRITE, SDIO_FUNC_NUM_0,
|
|
SDIO_FBR_BASE(func->num) + SDIO_FBR_BLK_SIZE + i, reg, NULL);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
func->block_size = bsize;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Read byte from SDIO register
|
|
*
|
|
* Reads byte from SDIO register
|
|
* @param func: function to read from
|
|
* @param reg: register address to read from
|
|
* @param val: filled with byte value read from register
|
|
* @retval 0 read succeeded
|
|
* @retval -EBUSY: card is busy with another request
|
|
* @retval -ETIMEDOUT: card read timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_read_byte(struct sdio_func *func, uint32_t reg, uint8_t *val)
|
|
{
|
|
int ret;
|
|
|
|
if ((func->card->type != CARD_SDIO) && (func->card->type != CARD_COMBO)) {
|
|
LOG_WRN("Card does not support SDIO commands");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = k_mutex_lock(&func->card->lock, K_MSEC(CONFIG_SD_DATA_TIMEOUT));
|
|
if (ret) {
|
|
LOG_WRN("Could not get SD card mutex");
|
|
return -EBUSY;
|
|
}
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_READ, func->num, reg, 0, val);
|
|
k_mutex_unlock(&func->card->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Write byte to SDIO register
|
|
*
|
|
* Writes byte to SDIO register
|
|
* @param func: function to write to
|
|
* @param reg: register address to write to
|
|
* @param write_val: value to write to register
|
|
* @retval 0 write succeeded
|
|
* @retval -EBUSY: card is busy with another request
|
|
* @retval -ETIMEDOUT: card write timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_write_byte(struct sdio_func *func, uint32_t reg, uint8_t write_val)
|
|
{
|
|
int ret;
|
|
|
|
if ((func->card->type != CARD_SDIO) && (func->card->type != CARD_COMBO)) {
|
|
LOG_WRN("Card does not support SDIO commands");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = k_mutex_lock(&func->card->lock, K_MSEC(CONFIG_SD_DATA_TIMEOUT));
|
|
if (ret) {
|
|
LOG_WRN("Could not get SD card mutex");
|
|
return -EBUSY;
|
|
}
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_WRITE, func->num, reg,
|
|
write_val, NULL);
|
|
k_mutex_unlock(&func->card->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Write byte to SDIO register, and read result
|
|
*
|
|
* Writes byte to SDIO register, and reads the register after write
|
|
* @param func: function to write to
|
|
* @param reg: register address to write to
|
|
* @param write_val: value to write to register
|
|
* @param read_val: filled with value read from register
|
|
* @retval 0 write succeeded
|
|
* @retval -EBUSY: card is busy with another request
|
|
* @retval -ETIMEDOUT: card write timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_rw_byte(struct sdio_func *func, uint32_t reg, uint8_t write_val,
|
|
uint8_t *read_val)
|
|
{
|
|
int ret;
|
|
|
|
if ((func->card->type != CARD_SDIO) && (func->card->type != CARD_COMBO)) {
|
|
LOG_WRN("Card does not support SDIO commands");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = k_mutex_lock(&func->card->lock, K_MSEC(CONFIG_SD_DATA_TIMEOUT));
|
|
if (ret) {
|
|
LOG_WRN("Could not get SD card mutex");
|
|
return -EBUSY;
|
|
}
|
|
ret = sdio_io_rw_direct(func->card, SDIO_IO_WRITE, func->num, reg,
|
|
write_val, read_val);
|
|
k_mutex_unlock(&func->card->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Read bytes from SDIO fifo
|
|
*
|
|
* Reads bytes from SDIO register, treating it as a fifo. Reads will
|
|
* all be done from same address.
|
|
* @param func: function to read from
|
|
* @param reg: register address of fifo
|
|
* @param data: filled with data read from fifo
|
|
* @param len: length of data to read from card
|
|
* @retval 0 read succeeded
|
|
* @retval -EBUSY: card is busy with another request
|
|
* @retval -ETIMEDOUT: card read timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_read_fifo(struct sdio_func *func, uint32_t reg, uint8_t *data,
|
|
uint32_t len)
|
|
{
|
|
int ret;
|
|
|
|
if ((func->card->type != CARD_SDIO) && (func->card->type != CARD_COMBO)) {
|
|
LOG_WRN("Card does not support SDIO commands");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = k_mutex_lock(&func->card->lock, K_MSEC(CONFIG_SD_DATA_TIMEOUT));
|
|
if (ret) {
|
|
LOG_WRN("Could not get SD card mutex");
|
|
return -EBUSY;
|
|
}
|
|
ret = sdio_io_rw_extended_helper(func, SDIO_IO_READ, reg, false,
|
|
data, len);
|
|
k_mutex_unlock(&func->card->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Write bytes to SDIO fifo
|
|
*
|
|
* Writes bytes to SDIO register, treating it as a fifo. Writes will
|
|
* all be done to same address.
|
|
* @param func: function to write to
|
|
* @param reg: register address of fifo
|
|
* @param data: data to write to fifo
|
|
* @param len: length of data to write to card
|
|
* @retval 0 write succeeded
|
|
* @retval -EBUSY: card is busy with another request
|
|
* @retval -ETIMEDOUT: card write timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_write_fifo(struct sdio_func *func, uint32_t reg, uint8_t *data,
|
|
uint32_t len)
|
|
{
|
|
int ret;
|
|
|
|
if ((func->card->type != CARD_SDIO) && (func->card->type != CARD_COMBO)) {
|
|
LOG_WRN("Card does not support SDIO commands");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = k_mutex_lock(&func->card->lock, K_MSEC(CONFIG_SD_DATA_TIMEOUT));
|
|
if (ret) {
|
|
LOG_WRN("Could not get SD card mutex");
|
|
return -EBUSY;
|
|
}
|
|
ret = sdio_io_rw_extended_helper(func, SDIO_IO_WRITE, reg, false,
|
|
data, len);
|
|
k_mutex_unlock(&func->card->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Read blocks from SDIO fifo
|
|
*
|
|
* Reads blocks from SDIO register, treating it as a fifo. Reads will
|
|
* all be done from same address.
|
|
* @param func: function to read from
|
|
* @param reg: register address of fifo
|
|
* @param data: filled with data read from fifo
|
|
* @param blocks: number of blocks to read from fifo
|
|
* @retval 0 read succeeded
|
|
* @retval -EBUSY: card is busy with another request
|
|
* @retval -ETIMEDOUT: card read timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_read_blocks_fifo(struct sdio_func *func, uint32_t reg, uint8_t *data,
|
|
uint32_t blocks)
|
|
{
|
|
int ret;
|
|
|
|
if ((func->card->type != CARD_SDIO) && (func->card->type != CARD_COMBO)) {
|
|
LOG_WRN("Card does not support SDIO commands");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = k_mutex_lock(&func->card->lock, K_MSEC(CONFIG_SD_DATA_TIMEOUT));
|
|
if (ret) {
|
|
LOG_WRN("Could not get SD card mutex");
|
|
return -EBUSY;
|
|
}
|
|
ret = sdio_io_rw_extended(func->card, SDIO_IO_READ, func->num, reg,
|
|
false, data, blocks, func->block_size);
|
|
k_mutex_unlock(&func->card->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Write blocks to SDIO fifo
|
|
*
|
|
* Writes blocks from SDIO register, treating it as a fifo. Writes will
|
|
* all be done to same address.
|
|
* @param func: function to write to
|
|
* @param reg: register address of fifo
|
|
* @param data: data to write to fifo
|
|
* @param blocks: number of blocks to write to fifo
|
|
* @retval 0 write succeeded
|
|
* @retval -EBUSY: card is busy with another request
|
|
* @retval -ETIMEDOUT: card write timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_write_blocks_fifo(struct sdio_func *func, uint32_t reg, uint8_t *data,
|
|
uint32_t blocks)
|
|
{
|
|
int ret;
|
|
|
|
if ((func->card->type != CARD_SDIO) && (func->card->type != CARD_COMBO)) {
|
|
LOG_WRN("Card does not support SDIO commands");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = k_mutex_lock(&func->card->lock, K_MSEC(CONFIG_SD_DATA_TIMEOUT));
|
|
if (ret) {
|
|
LOG_WRN("Could not get SD card mutex");
|
|
return -EBUSY;
|
|
}
|
|
ret = sdio_io_rw_extended(func->card, SDIO_IO_WRITE, func->num, reg,
|
|
false, data, blocks, func->block_size);
|
|
k_mutex_unlock(&func->card->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Copy bytes from an SDIO card
|
|
*
|
|
* Copies bytes from an SDIO card, starting from provided address.
|
|
* @param func: function to read from
|
|
* @param reg: register address to start copy at
|
|
* @param data: buffer to copy data into
|
|
* @param len: length of data to read
|
|
* @retval 0 read succeeded
|
|
* @retval -EBUSY: card is busy with another request
|
|
* @retval -ETIMEDOUT: card read timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_read_addr(struct sdio_func *func, uint32_t reg, uint8_t *data,
|
|
uint32_t len)
|
|
{
|
|
int ret;
|
|
|
|
if ((func->card->type != CARD_SDIO) && (func->card->type != CARD_COMBO)) {
|
|
LOG_WRN("Card does not support SDIO commands");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = k_mutex_lock(&func->card->lock, K_MSEC(CONFIG_SD_DATA_TIMEOUT));
|
|
if (ret) {
|
|
LOG_WRN("Could not get SD card mutex");
|
|
return -EBUSY;
|
|
}
|
|
ret = sdio_io_rw_extended_helper(func, SDIO_IO_READ, reg, true,
|
|
data, len);
|
|
k_mutex_unlock(&func->card->lock);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Copy bytes to an SDIO card
|
|
*
|
|
* Copies bytes to an SDIO card, starting from provided address.
|
|
*
|
|
* @param func: function to write to
|
|
* @param reg: register address to start copy at
|
|
* @param data: buffer to copy data from
|
|
* @param len: length of data to write
|
|
* @retval 0 write succeeded
|
|
* @retval -EBUSY: card is busy with another request
|
|
* @retval -ETIMEDOUT: card write timed out
|
|
* @retval -EIO: I/O error
|
|
*/
|
|
int sdio_write_addr(struct sdio_func *func, uint32_t reg, uint8_t *data,
|
|
uint32_t len)
|
|
{
|
|
int ret;
|
|
|
|
if ((func->card->type != CARD_SDIO) && (func->card->type != CARD_COMBO)) {
|
|
LOG_WRN("Card does not support SDIO commands");
|
|
return -ENOTSUP;
|
|
}
|
|
ret = k_mutex_lock(&func->card->lock, K_MSEC(CONFIG_SD_DATA_TIMEOUT));
|
|
if (ret) {
|
|
LOG_WRN("Could not get SD card mutex");
|
|
return -EBUSY;
|
|
}
|
|
ret = sdio_io_rw_extended_helper(func, SDIO_IO_WRITE, reg, true,
|
|
data, len);
|
|
k_mutex_unlock(&func->card->lock);
|
|
return ret;
|
|
}
|