| /* |
| * 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; |
| } |