From 3061ecca3d0fdfb87dabbf5f63c9e06c2a30f53a Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Thu, 23 Aug 2018 17:08:59 +0200 Subject: o Initial import. --- .../components/libraries/sdcard/app_sdcard.c | 1179 ++++++++++++++++++++ 1 file changed, 1179 insertions(+) create mode 100644 thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/sdcard/app_sdcard.c (limited to 'thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/sdcard/app_sdcard.c') diff --git a/thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/sdcard/app_sdcard.c b/thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/sdcard/app_sdcard.c new file mode 100644 index 0000000..763bf60 --- /dev/null +++ b/thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/sdcard/app_sdcard.c @@ -0,0 +1,1179 @@ +/** + * Copyright (c) 2016 - 2018, Nordic Semiconductor ASA + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#include "sdk_config.h" +#if APP_SDCARD_ENABLED + +#include "app_sdcard.h" +#include "nrf_gpio.h" +#include "nrf_drv_spi.h" +#include "app_error.h" +#include "nrf_assert.h" + +#include "nrf_pt.h" + +#define CMD_MASK 0x40 +#define ACMD_MASK 0x80 +#define CMD0 (CMD_MASK | 0) /**< SDC/MMC command 0: GO_IDLE_STATE. */ +#define CMD1 (CMD_MASK | 1) /**< SDC/MMC command 1: SEND_OP_COND (MMC). */ +#define CMD8 (CMD_MASK | 8) /**< SDC/MMC command 8: SEND_IF_COND. */ +#define CMD9 (CMD_MASK | 9) /**< SDC/MMC command 9: SEND_CSD. */ +#define CMD10 (CMD_MASK | 10) /**< SDC/MMC command 10: SEND_CID. */ +#define CMD12 (CMD_MASK | 12) /**< SDC/MMC command 12: STOP_TRANSMISSION. */ +#define CMD16 (CMD_MASK | 16) /**< SDC/MMC command 16: SET_BLOCKLEN. */ +#define CMD17 (CMD_MASK | 17) /**< SDC/MMC command 17: READ_SINGLE_BLOCK. */ +#define CMD18 (CMD_MASK | 18) /**< SDC/MMC command 18: READ_MULTIPLE_BLOCK. */ +#define CMD23 (CMD_MASK | 23) /**< SDC/MMC command 23: SET_BLOCK_COUNT (MMC). */ +#define CMD24 (CMD_MASK | 24) /**< SDC/MMC command 24: WRITE_BLOCK. */ +#define CMD25 (CMD_MASK | 25) /**< SDC/MMC command 25: WRITE_MULTIPLE_BLOCK. */ +#define CMD32 (CMD_MASK | 32) /**< SDC/MMC command 32: ERASE_ER_BLK_START. */ +#define CMD33 (CMD_MASK | 33) /**< SDC/MMC command 33: ERASE_ER_BLK_END. */ +#define CMD38 (CMD_MASK | 38) /**< SDC/MMC command 38: ERASE. */ +#define CMD55 (CMD_MASK | 55) /**< SDC/MMC command 55: APP_CMD. */ +#define CMD58 (CMD_MASK | 58) /**< SDC/MMC command 58: READ_OCR. */ +#define ACMD13 (ACMD_MASK | CMD_MASK | 13) /**< SDC application command 13: SD_STATUS. */ +#define ACMD23 (ACMD_MASK | CMD_MASK | 23) /**< SDC application command 23: SET_WR_BLK_ERASE_COUNT. */ +#define ACMD41 (ACMD_MASK | CMD_MASK | 41) /**< SDC application command 41: SEND_OP_COND. */ + +#define IS_ACMD(CMD) ((CMD) & ACMD_MASK) /**< Check if command is an application command (ACMD). */ + +#define SDC_COMMAND_LEN 6 /**< Length of a command structure sent to the card. */ +#define SDC_COMMAND_POS 0 /**< Position of a command field inside the command structure. */ +#define SDC_COMMAND_MASK 0x7F /**< Bit mask of a command field. */ +#define SDC_COMMAND_ARG_POS 1 /**< Position of a 32-bit argument inside the command structure. */ +#define SDC_COMMAND_CRC_POS 5 /**< Position of a CRC field inside the command structure. */ + +#define SDC_MAX_NCR 8 /**< Maximum number of "busy" bytes sent before command response. */ +#define SDC_R1_LEN 1 /**< Length of R1 format response. */ +#define SDC_R3_LEN 5 /**< Length of R3 format response. */ +#define SDC_R7_LEN 5 /**< Length of R7 format response. */ +#define SDC_R_MAX_LEN 5 /**< Maximum length of command response. */ + +#define SDC_FLAG_IN_IDLE_STATE 0x01 /**< R1 response flag bit mask: idle state. */ +#define SDC_FLAG_ERASE_RESET 0x02 /**< R1 response flag bit mask: erase reset. */ +#define SDC_FLAG_ILLEGAL_COMMAND 0x04 /**< R1 response flag bit mask: illegal command. */ +#define SDC_FLAG_COM_CRC_ERROR 0x08 /**< R1 response flag bit mask: CRC error. */ +#define SDC_FLAG_ERASE_SEQUENCE_ERROR 0x10 /**< R1 response flag bit mask: erase sequence error. */ +#define SDC_FLAG_ADDRESS_ERROR 0x20 /**< R1 response flag bit mask: address error. */ +#define SDC_FLAG_PARAMETER_ERROR 0x40 /**< R1 response flag bit mask: parameter error. */ + +#define SDC_HCS_FLAG_MASK (1uL << 30) /**< High capacity support bit mask. */ + +#define SDC_EMPTY_BYTE 0xFF /**< Idle state token. */ +#define SDC_BUSY_BYTE 0x00 /**< Busy byte token. */ +#define SDC_TOKEN_START_BLOCK 0xFE /**< Data block start token. */ +#define SDC_TOKEN_START_BLOCK_MULT 0xFC /**< Data block start token for multiple write operation. */ +#define SDC_TOKEN_DATA_RESP_MASK 0x1F /**< Data response token mask. */ +#define SDC_TOKEN_DATA_RESP_ACCEPTED 0x05 /**< Data response message: accepted. */ +#define SDC_TOKEN_DATA_RESP_CRC_ERR 0x0B /**< Data response message: CRC error. */ +#define SDC_TOKEN_DATA_RESP_DATA_ERR 0x0D /**< Data response message: data error. */ +#define SDC_TOKEN_STOP_TRAN 0xFD /**< Stop transmission token. */ + +#define SDC_MAX_RETRY_COUNT_INIT 2000 /**< Maximum number of retries while card is busy (identification stage). */ +#define SDC_MAX_RETRY_COUNT 20000 /**< Maximum number of retries while card is busy. */ +#define SDC_SPI_MTU 240 /**< Maximum number of bytes in one SPI transaction. */ +#define SDC_CRC_CMD0 0x95 /**< Fixed CRC for reset command. */ +#define SDC_CRC_CMD8 0x87 /**< Fixed CRC for CMD8. */ +#define SDC_CRC_DUMMY 0xFF /**< Dummy CRC value. */ + +#define SDC_CMD_BUF_LEN 16 /**< Size of a buffer for storing SDC commands. */ +#define SDC_WORK_BUF_LEN 16 /**< Size of a working buffer. */ +#define SDC_DATA_WAIT_TX_SIZE 16 /**< Number of bytes sent during data / busy wait. */ + +#define SDC_CS_ASSERT() do { nrf_gpio_pin_clear(m_cb.cs_pin); } while (0) /**< Set CS pin to active state. */ +#define SDC_CS_DEASSERT() do { nrf_gpio_pin_set(m_cb.cs_pin); } while (0) /**< Set CS pin to inactive state. */ + +#define SDC_PT &m_cb.state.pt /**< Macro for getting SDC task structure pointer (Protothread). */ +#define SDC_PT_SUB &m_cb.state.pt_sub /**< Macro for getting SDC sub-task structure pointer (Protothread). */ + +/** Break current task (Protothread). */ +#define SDC_BREAK(PT, EXIT_CODE) do { \ + *p_exit_code = (EXIT_CODE); \ + PT_EXIT(PT); \ + } while (0) + +/**< Check the value of R1 response and break current task on error. */ +#define SDC_RESP_CHECK(PT, R1) do { \ + if ((R1) & ~(SDC_FLAG_IN_IDLE_STATE | SDC_FLAG_ERASE_RESET)) \ + { \ + SDC_BREAK((PT), SDC_ERROR_COMMUNICATION); \ + } \ + } while (0) + +/**< Check the result of an SDC operation and break on failure. */ +#define SDC_RESULT_CHECK(PT, RESULT) do { \ + if ((RESULT) != SDC_SUCCESS) \ + { \ + SDC_BREAK((PT), RESULT); \ + } \ + } while (0); + + +static const nrf_drv_spi_t m_spi = NRF_DRV_SPI_INSTANCE(APP_SDCARD_SPI_INSTANCE); /**< SPI instance. */ + +/** + * @brief SDC response type. + */ +typedef enum { + SDC_RNONE = 0, + SDC_R1, + SDC_R3, + SDC_R7 +} sdc_response_t; + +/** + * @brief SDC operation state. + */ +typedef enum { + SDC_UNINITIALIZED = 0, ///< Card not initialized. + SDC_OP_RESET = 1, ///< Reset state. + SDC_OP_IDENTIFICATION = 2, ///< Identification procedure. + SDC_OP_IDLE = 3, ///< Idle state. + SDC_OP_READ = 4, ///< Data read procedure. + SDC_OP_WRITE = 5 ///< Data write procedure. +} sdc_op_t; + +/** + * @brief SDC data bus state. + */ +typedef enum { + SDC_BUS_IDLE = 0, ///< Idle state, no active transfer. + SDC_BUS_CMD = 1, ///< Command is being transfered. + SDC_BUS_ACMD = 2, ///< Application command header transfer. + SDC_BUS_DATA_WAIT = 3, ///< Data wait state. + SDC_BUS_DATA = 4 ///< Data block transfer in progress. +} sdc_bus_state_t; + +/** + * @brief Current read/write operation state structure. + */ +typedef struct { + uint8_t * buffer; ///< Local data buffer. + uint32_t address; ///< Data block address. + uint16_t block_count; ///< Total number of blocks in read/write operation. + uint16_t blocks_left; ///< Blocks left in current read/write operation. + uint16_t position; ///< Number of blocks left to read/write. +} sdc_rw_op_t; + +/** + * @brief SDC state structure. + */ +typedef struct { + sdc_rw_op_t rw_op; ///< Read/write operation state. + pt_t pt; ///< Current task (Protothread) state. + pt_t pt_sub; ///< Current sub-task (Protothread) state. + uint16_t retry_count; ///< Number of retries left. + volatile sdc_op_t op; ///< Current operation. + sdc_bus_state_t bus_state; ///< Current data bud state. + uint8_t rsp_len; ///< Expected response length. +} sdc_state_t; + +/** + * @beirf SDC control block. + */ +typedef struct { + sdc_event_handler_t handler; ///< Event handler. + app_sdc_info_t info; ///< Card information structure. + sdc_state_t state; ///< Card state structure + uint8_t cmd_buf[SDC_CMD_BUF_LEN]; ///< Command buffer. + uint8_t rsp_buf[SDC_CMD_BUF_LEN]; ///< Card response buffer. + uint8_t work_buf[SDC_WORK_BUF_LEN]; ///< Working buffer + uint8_t cs_pin; ///< Chip select pin number. +} sdc_cb_t; + +static sdc_cb_t m_cb; ///< SDC control block. + + +/** + * @brief Function for requesting the SPI transaction. + * + * The SPI bus must be initialized prior to use of this function. + * + * @param[in] p_txb Pointer to the TX buffer. + * @param[in] tx_len TX buffer length. + * @param[out] p_rxb Pointer to the RX buffer. + * @param[in] tx_len RX buffer length. + */ +__STATIC_INLINE void sdc_spi_transfer(uint8_t const * const p_txb, + uint8_t tx_len, + uint8_t * const p_rxb, + uint8_t rx_len) +{ + SDC_CS_ASSERT(); + ret_code_t err_code = nrf_drv_spi_transfer(&m_spi, p_txb, tx_len, p_rxb, rx_len); + APP_ERROR_CHECK(err_code); +} + + +/** + * @brief Function for switching the SPI clock to high speed mode. + */ +__STATIC_INLINE void sdc_spi_hispeed(void) +{ +#ifdef SPI_PRESENT + nrf_spi_frequency_set(m_spi.u.spi.p_reg, + (nrf_spi_frequency_t) APP_SDCARD_FREQ_DATA); +#else + nrf_spim_frequency_set(m_spi.u.spim.p_reg, + (nrf_spi_frequency_t) APP_SDCARD_FREQ_DATA); +#endif +} + + +/** + * @brief Function for extracting the number of data block from the CSD structure. + * + * @param[in] p_csd Pointer to the card CSD structure. + * + * @return Number of data blocks or 0 if unsupported / invalid structure was provided. + */ +static uint32_t sdc_calculate_size(uint8_t const * const p_csd) +{ + // Values are calculated as stated in SD Specifications, chapter 5.3. + uint8_t csd_version = p_csd[0] >> 6; + + switch (csd_version) + { + case 0: + case 2: + { + // SD Standard Capacity or MMC. + uint32_t c_size = ((uint32_t) p_csd[8] >> 6) + (((uint32_t) p_csd[7]) << 2) + + ((uint32_t)(p_csd[6] & 0x03) << 10); + uint32_t read_bl_len = p_csd[5] & 0x0F; + uint32_t c_size_mult = ((p_csd[10] & 0x80) >> 7) + ((p_csd[9] & 0x03) << 1); + + // Block size in this implementation is set to 512, so the resulting number of bytes + // is divided by 512 (2^9) + return (c_size + 1) << (read_bl_len + c_size_mult + 2 - 9); + } + case 1: + { + // SD High Capacity. + uint32_t c_size = (uint32_t) p_csd[9] + ((uint32_t) p_csd[8] << 8) + + (((uint32_t) p_csd[7] & 0x3F) << 16); + + // According to SD 2.0 Specifications, capacity = (C_SIZE + 1) * 512KByte. + // Block size is equal to 512, so the result is divided by 512. + return (c_size + 1) * 1024uL; + } + default: + break; + } + return 0; +} + + +/** + * @brief Non-blocking function for sending a command to the card. + * + * @param[in] cmd SDC command ID. + * @param[in] arg 32-bit command argument. + * @param[in] rsp_type Expected command response format. + * + * @retval NRF_SUCCESS If command transmission was started successfully. + * @retval NRF_ERROR_BUSY If the card is not in idle state. + */ +static ret_code_t sdc_cmd(uint8_t cmd, uint32_t arg, sdc_response_t rsp_type) +{ + if (m_cb.state.bus_state != SDC_BUS_IDLE) + { + return NRF_ERROR_BUSY; + } + + uint8_t offset = 0; + + m_cb.state.bus_state = SDC_BUS_CMD; + if (IS_ACMD(cmd)) + { + // ACMD is a combination of CMD55 and the requested command, + // which will be placed next in the command buffer. + offset = SDC_COMMAND_LEN; + m_cb.state.bus_state = SDC_BUS_ACMD; + m_cb.cmd_buf[SDC_COMMAND_POS] = CMD55; + m_cb.cmd_buf[SDC_COMMAND_ARG_POS] = 0; + m_cb.cmd_buf[SDC_COMMAND_ARG_POS + 1] = 0; + m_cb.cmd_buf[SDC_COMMAND_ARG_POS + 2] = 0; + m_cb.cmd_buf[SDC_COMMAND_ARG_POS + 3] = 0; + m_cb.cmd_buf[SDC_COMMAND_CRC_POS] = SDC_CRC_DUMMY; + } + + m_cb.cmd_buf[SDC_COMMAND_POS + offset] = cmd & SDC_COMMAND_MASK; + m_cb.cmd_buf[SDC_COMMAND_ARG_POS + offset] = (uint8_t)(arg >> 24); + m_cb.cmd_buf[SDC_COMMAND_ARG_POS + 1 + offset] = (uint8_t)(arg >> 16); + m_cb.cmd_buf[SDC_COMMAND_ARG_POS + 2 + offset] = (uint8_t)(arg >> 8); + m_cb.cmd_buf[SDC_COMMAND_ARG_POS + 3 + offset] = (uint8_t)(arg); + + // Use predefined CRC values and omit the crc calculation if not required. + uint8_t crc; + switch (cmd) + { + case CMD0: + crc = SDC_CRC_CMD0; + break; + case CMD8: + crc = SDC_CRC_CMD8; + break; + default: + crc = SDC_CRC_DUMMY; + break; + } + m_cb.cmd_buf[SDC_COMMAND_CRC_POS + offset] = crc; + + switch (rsp_type) + { + case SDC_R3: + m_cb.state.rsp_len = SDC_R3_LEN; + break; + case SDC_R7: + m_cb.state.rsp_len = SDC_R7_LEN; + break; + default: + m_cb.state.rsp_len = SDC_R1_LEN; + break; + } + + uint8_t response_len = (IS_ACMD(cmd)) ? SDC_R1_LEN : m_cb.state.rsp_len; + sdc_spi_transfer(m_cb.cmd_buf, + SDC_COMMAND_LEN, + m_cb.rsp_buf, + SDC_COMMAND_LEN + SDC_MAX_NCR + response_len); + + return NRF_SUCCESS; +} + + +/** + * @brief Data block read subroutine. + * + * @param[in] p_rx_data Pointer to the data received in last transation. + * @param[in] rx_length Received data length. + * @param[in] block_len Size of a data block to read. + * @param[out] p_exit_code Pointer to the subroutine exit code variable. Valid only if the thread has exited. + * + * @return Protothread exit code. Zero if protothread is running and non-zero if exited. + */ +static PT_THREAD(sdc_pt_sub_data_read(uint8_t * p_rx_data, + uint8_t rx_length, + uint16_t block_len, + sdc_result_t * p_exit_code)) +{ + PT_BEGIN(SDC_PT_SUB); + while (1) + { + ASSERT(block_len); + ASSERT(m_cb.state.rw_op.block_count); + + m_cb.state.rw_op.blocks_left = m_cb.state.rw_op.block_count; + + while (m_cb.state.rw_op.blocks_left) + { + m_cb.state.retry_count = 0; + m_cb.cmd_buf[0] = 0xFF; + m_cb.state.rw_op.position = 0; + m_cb.state.bus_state = SDC_BUS_DATA_WAIT; + + while (m_cb.state.bus_state == SDC_BUS_DATA_WAIT) + { + ++m_cb.state.retry_count; + if (m_cb.state.retry_count > SDC_MAX_RETRY_COUNT) + { + SDC_BREAK(SDC_PT_SUB, SDC_ERROR_TIMEOUT); + } + + // Search for the first token. + while (rx_length && p_rx_data[0] == SDC_EMPTY_BYTE) + { + ++p_rx_data; + --rx_length; + } + + if (rx_length) + { + // A token has been found. + if (p_rx_data[0] == SDC_TOKEN_START_BLOCK) + { + // Expected data start token found. + // Copy the data bytes left in rx buffer into user buffer. + ++p_rx_data; + --rx_length; + m_cb.state.bus_state = SDC_BUS_DATA; + uint16_t copy_len = (rx_length > block_len) ? block_len : rx_length; + for (uint32_t i = 0; i < copy_len; ++i) + { + m_cb.state.rw_op.buffer[i] = p_rx_data[i]; + } + m_cb.state.rw_op.position = copy_len; + m_cb.state.rw_op.buffer += copy_len; + } + else + { + // Data error. + SDC_BREAK(SDC_PT_SUB, SDC_ERROR_DATA); + } + } + else + { + // Continue transfer until token is received. + sdc_spi_transfer(m_cb.cmd_buf, 1, m_cb.rsp_buf, SDC_DATA_WAIT_TX_SIZE); + PT_YIELD(SDC_PT_SUB); + } + } + + while (m_cb.state.rw_op.position < block_len) + { + { + uint16_t chunk_size = block_len - m_cb.state.rw_op.position; + if (chunk_size > SDC_SPI_MTU) + { + chunk_size = SDC_SPI_MTU; + } + + sdc_spi_transfer(m_cb.cmd_buf, 1, + m_cb.state.rw_op.buffer, chunk_size); + m_cb.state.rw_op.buffer += chunk_size; + m_cb.state.rw_op.position += chunk_size; + } + PT_YIELD(SDC_PT_SUB); + } + + // Get the CRC. + --m_cb.state.rw_op.blocks_left; + sdc_spi_transfer(m_cb.cmd_buf, 1, + m_cb.rsp_buf, 2); + PT_YIELD(SDC_PT_SUB); + + + + // Set rx length to 0 to force "busy check" transmission before next data block. + rx_length = 0; + } + + // Send padding bytes. + m_cb.cmd_buf[0] = SDC_EMPTY_BYTE; + sdc_spi_transfer(m_cb.cmd_buf, 1, + m_cb.rsp_buf, 2); + PT_YIELD(SDC_PT_SUB); + + m_cb.state.bus_state = SDC_BUS_IDLE; + SDC_BREAK(SDC_PT_SUB, SDC_SUCCESS); + } + PT_END(SDC_PT_SUB) +} + + +/** + * @brief Card identification co-routine. + * + * @param[in] p_rx_data Pointer to the data received in last transation. + * @param[in] rx_length Received data length. + * @param[out] p_exit_code Pointer to the routine exit code variable. Valid only if the thread has exited. + * + * @return Protothread exit code. Zero if protothread is running and non-zero if exited. + */ +static PT_THREAD(sdc_pt_identification(uint8_t * p_rx_data, + uint8_t rx_length, + sdc_result_t * p_exit_code)) +{ + uint8_t r1 = p_rx_data[0]; + uint32_t rsp = ((uint32_t)p_rx_data[1] << 24) + | ((uint32_t)p_rx_data[2] << 16) + | ((uint32_t)p_rx_data[3] << 8) + | ((uint32_t)p_rx_data[4]); + uint32_t arg; + ret_code_t err_code; + sdc_result_t sub_exit_code; + + PT_BEGIN(SDC_PT); + while (1) + { + err_code = sdc_cmd(CMD0, 0, SDC_R1); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + err_code = sdc_cmd(CMD0, 0, SDC_R1); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + + SDC_RESP_CHECK(SDC_PT, r1); + // Send CMD8 with fixed argument - 0x01AA. + err_code = sdc_cmd(CMD8, 0x1AA, SDC_R7); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + + if (!(r1 & SDC_FLAG_ILLEGAL_COMMAND)) + { + // CMD8 was accepted - SD v2 card. + m_cb.info.type.version = SDC_TYPE_SDV2; + SDC_RESP_CHECK(SDC_PT, r1); + } + + m_cb.state.retry_count = 0; + arg = (m_cb.info.type.version == SDC_TYPE_SDV2) ? SDC_HCS_FLAG_MASK : 0; + err_code = sdc_cmd(ACMD41, arg, SDC_R3); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + + if (r1 & SDC_FLAG_ILLEGAL_COMMAND) + { + // ACMD41 was rejected - MMC card. + m_cb.info.type.version = SDC_TYPE_MMCV3; + r1 &= ~SDC_FLAG_ILLEGAL_COMMAND; + + do + { + ++m_cb.state.retry_count; + if (m_cb.state.retry_count > SDC_MAX_RETRY_COUNT_INIT) + { + SDC_BREAK(SDC_PT, SDC_ERROR_TIMEOUT); + } + + err_code = sdc_cmd(CMD1, 0, SDC_R3); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + SDC_RESP_CHECK(SDC_PT, r1); + } + while (r1 & SDC_FLAG_IN_IDLE_STATE); + } + else + { + // SDv1 or SDv2 card. Send CMD58 or retry ACMD41 if not ready. + SDC_RESP_CHECK(SDC_PT, r1); + + while (r1 & SDC_FLAG_IN_IDLE_STATE) + { + ++m_cb.state.retry_count; + if (m_cb.state.retry_count > SDC_MAX_RETRY_COUNT_INIT) + { + SDC_BREAK(SDC_PT, SDC_ERROR_TIMEOUT); + } + + arg = (m_cb.info.type.version == SDC_TYPE_SDV2) ? SDC_HCS_FLAG_MASK : 0; + err_code = sdc_cmd(ACMD41, arg, SDC_R3); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + SDC_RESP_CHECK(SDC_PT, r1); + } + + err_code = sdc_cmd(CMD58, 0, SDC_R3); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + SDC_RESP_CHECK(SDC_PT, r1); + + if (rsp & SDC_HCS_FLAG_MASK) + { + m_cb.info.type.sdhc = 1; + } + } + + if (m_cb.info.type.version != SDC_TYPE_SDV2) + { + // Set block length to 512 (SDv1 and MMC cards only.) + err_code = sdc_cmd(CMD16, SDC_SECTOR_SIZE, SDC_R1); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + SDC_RESP_CHECK(SDC_PT, r1); + } + + // Setup the read operation and get the contents of 128-bit CSD register. + m_cb.state.rw_op.buffer = m_cb.work_buf; + m_cb.state.rw_op.block_count = 1; + + err_code = sdc_cmd(CMD9, 0, SDC_R1); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + SDC_RESP_CHECK(SDC_PT, r1); + + p_rx_data += SDC_R1_LEN; + rx_length -= SDC_R1_LEN; + PT_SPAWN(SDC_PT, SDC_PT_SUB, sdc_pt_sub_data_read(p_rx_data, rx_length, \ + 16, &sub_exit_code)); + SDC_RESULT_CHECK(SDC_PT, sub_exit_code); + + m_cb.info.num_blocks = sdc_calculate_size(m_cb.work_buf); + m_cb.info.block_len = SDC_SECTOR_SIZE; + + SDC_BREAK(SDC_PT, SDC_SUCCESS); + } + PT_END(SDC_PT) +} + + +/** + * @brief Data read co-routine. + * + * @param[in] p_rx_data Pointer to the data received in last transaction. + * @param[in] rx_length Received data length. + * @param[out] p_exit_code Pointer to the routine exit code variable. Valid only if the thread has exited. + * + * @return Protothread exit code. Zero if protothread is running and non-zero if exited. + */ +static PT_THREAD(sdc_pt_read(uint8_t * p_rx_data, + uint8_t rx_length, + sdc_result_t * p_exit_code)) +{ + uint8_t r1; + ret_code_t err_code; + sdc_result_t sub_exit_code; + + PT_BEGIN(SDC_PT); + while (1) + { + r1 = p_rx_data[0]; + SDC_RESP_CHECK(SDC_PT, r1); + + p_rx_data += SDC_R1_LEN; + rx_length -= SDC_R1_LEN; + + // Run the block read subroutine. + PT_SPAWN(SDC_PT, SDC_PT_SUB, sdc_pt_sub_data_read(p_rx_data, rx_length, + SDC_SECTOR_SIZE, + &sub_exit_code)); + SDC_RESULT_CHECK(SDC_PT, sub_exit_code); + + if (m_cb.state.rw_op.block_count > 1) + { + // Send the STOP_TRANSMISSION command in multiple block read mode. + err_code = sdc_cmd(CMD12, 0, SDC_R1); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + } + + SDC_BREAK(SDC_PT, SDC_SUCCESS); + } + PT_END(SDC_PT) +} + + +/** + * @brief Data write co-routine. + * + * @param[in] p_rx_data Pointer to the data received in last transation. + * @param[in] rx_length Received data length. + * @param[out] p_exit_code Pointer to the routine exit code variable. Valid only if the thread has exited. + * + * @return Protothread exit code. Zero if protothread is running and non-zero if exited. + */ +static PT_THREAD(sdc_pt_write(uint8_t * rx_data, + uint8_t rx_length, + sdc_result_t * p_exit_code)) +{ + ret_code_t err_code; + PT_BEGIN(SDC_PT); + while (1) + { + uint8_t r1; + r1 = rx_data[0]; + SDC_RESP_CHECK(SDC_PT, r1); + if (m_cb.info.type.version != SDC_TYPE_MMCV3 && m_cb.state.rw_op.block_count > 1) + { + err_code = sdc_cmd(CMD25, m_cb.state.rw_op.address, SDC_R1); + APP_ERROR_CHECK(err_code); + PT_YIELD(SDC_PT); + r1 = rx_data[0]; + SDC_RESP_CHECK(SDC_PT, r1); + } + + m_cb.state.rw_op.blocks_left = m_cb.state.rw_op.block_count; + while (m_cb.state.rw_op.blocks_left) + { + m_cb.state.rw_op.position = 0; + m_cb.state.bus_state = SDC_BUS_DATA; + + // Send block start token. + m_cb.cmd_buf[0] = SDC_EMPTY_BYTE; + m_cb.cmd_buf[1] = (m_cb.state.rw_op.block_count > 1) ? SDC_TOKEN_START_BLOCK_MULT + : SDC_TOKEN_START_BLOCK; + sdc_spi_transfer(m_cb.cmd_buf, 2, m_cb.rsp_buf, 2); + PT_YIELD(SDC_PT); + + // Send the data block. + while (m_cb.state.rw_op.position < SDC_SECTOR_SIZE) + { + { + uint16_t chunk_size = SDC_SECTOR_SIZE - m_cb.state.rw_op.position; + if (chunk_size > SDC_SPI_MTU) + { + chunk_size = SDC_SPI_MTU; + } + sdc_spi_transfer(&m_cb.state.rw_op.buffer[m_cb.state.rw_op.position], + chunk_size, + m_cb.rsp_buf, + 1); + m_cb.state.rw_op.position += chunk_size; + } + PT_YIELD(SDC_PT); + } + m_cb.state.rw_op.buffer += SDC_SECTOR_SIZE; + + // Send the dummy CRC (2 bytes) and receive data response token (1 byte). + m_cb.state.bus_state = SDC_BUS_DATA_WAIT; + sdc_spi_transfer(m_cb.cmd_buf, 1, + m_cb.rsp_buf, 3); + PT_YIELD(SDC_PT); + + { + uint8_t token = m_cb.rsp_buf[2] & SDC_TOKEN_DATA_RESP_MASK; + if (token != SDC_TOKEN_DATA_RESP_ACCEPTED) + { + if (token == SDC_TOKEN_DATA_RESP_CRC_ERR + || token == SDC_TOKEN_DATA_RESP_DATA_ERR) + { + SDC_BREAK(SDC_PT, SDC_ERROR_DATA); + } + else + { + SDC_BREAK(SDC_PT, SDC_ERROR_COMMUNICATION); + } + } + } + + // Wait for the card to complete the write process. + m_cb.state.retry_count = 0; + while (m_cb.state.bus_state == SDC_BUS_DATA_WAIT) + { + ++m_cb.state.retry_count; + if (m_cb.state.retry_count > SDC_MAX_RETRY_COUNT) + { + SDC_BREAK(SDC_PT, SDC_ERROR_TIMEOUT); + } + + sdc_spi_transfer(m_cb.cmd_buf, 1, + m_cb.rsp_buf, SDC_DATA_WAIT_TX_SIZE); + PT_YIELD(SDC_PT); + + for (uint32_t i = 0; i < rx_length; ++i) + { + if (rx_data[i] != 0x00) + { + m_cb.state.bus_state = SDC_BUS_IDLE; + break; + } + } + } + + --m_cb.state.rw_op.blocks_left; + } + + if (m_cb.state.rw_op.block_count > 1) + { + // Send STOP_TRAN token + padding byte when writing multiple blocks. + m_cb.cmd_buf[0] = SDC_EMPTY_BYTE; + m_cb.cmd_buf[1] = SDC_TOKEN_STOP_TRAN; + sdc_spi_transfer(m_cb.cmd_buf, 2, + m_cb.rsp_buf, 3); + PT_YIELD(SDC_PT); + + m_cb.state.retry_count = 0; + m_cb.state.bus_state = SDC_BUS_DATA_WAIT; + + // Wait for the card to complete the write process. + while (m_cb.state.bus_state == SDC_BUS_DATA_WAIT) + { + ++m_cb.state.retry_count; + if (m_cb.state.retry_count > SDC_MAX_RETRY_COUNT) + { + SDC_BREAK(SDC_PT, SDC_ERROR_TIMEOUT); + } + + sdc_spi_transfer(m_cb.cmd_buf, 1, + m_cb.rsp_buf, SDC_DATA_WAIT_TX_SIZE); + PT_YIELD(SDC_PT); + + for (uint32_t i = 0; i < rx_length; ++i) + { + if (rx_data[i] != 0x00) + { + m_cb.state.bus_state = SDC_BUS_IDLE; + break; + } + } + } + } + + SDC_BREAK(SDC_PT, SDC_SUCCESS); + } + PT_END(SDC_PT) +} + + +/** + * @brief SPI event handler. + * + * @param[in] p_event Pointer to the SPI event structure. + */ +static void spi_handler(nrf_drv_spi_evt_t const * p_event, + void * p_context) +{ + uint8_t * rx_data = p_event->data.done.p_rx_buffer; + uint8_t rx_length = p_event->data.done.rx_length; + + if (!m_cb.state.rw_op.blocks_left) + { + // Deassert CS pin if not in active data transfer. + SDC_CS_DEASSERT(); + } + + if (m_cb.state.bus_state == SDC_BUS_ACMD || m_cb.state.bus_state == SDC_BUS_CMD) + { + // Find the beginning of a response. + ASSERT(rx_length > SDC_COMMAND_LEN); + rx_length -= SDC_COMMAND_LEN; + rx_data += SDC_COMMAND_LEN; + + if (p_event->data.done.p_tx_buffer[0] == CMD12) + { + // Ignore the first byte if CMD12 was sent. + if (rx_length) + { + --rx_length; + ++rx_data; + } + } + + while (rx_length && rx_data[0] == SDC_EMPTY_BYTE) + { + --rx_length; + ++rx_data; + } + if (rx_length == 0) + { + if (p_event->data.done.p_tx_buffer[0] == CMD12) + { + // Ignore invalid reply on CMD12. + ++rx_length; + --rx_data; + } + else + { + rx_data = NULL; + } + } + + if (!rx_data && m_cb.state.op != SDC_OP_RESET) + { + // Command response missing. + sdc_evt_t evt; + evt.result = SDC_ERROR_NOT_RESPONDING; + switch (m_cb.state.op) + { + case SDC_OP_RESET: + case SDC_OP_IDENTIFICATION: + evt.type = SDC_EVT_INIT; + m_cb.state.op = SDC_OP_IDLE; + APP_ERROR_CHECK(app_sdc_uninit()); + break; + case SDC_OP_READ: + evt.type = SDC_EVT_READ; + break; + case SDC_OP_WRITE: + evt.type = SDC_EVT_WRITE; + break; + default: + APP_ERROR_CHECK(NRF_ERROR_INTERNAL); + break; + } + + SDC_CS_DEASSERT(); + m_cb.state.op = SDC_OP_IDLE; + m_cb.handler(&evt); + return; + } + + if (m_cb.state.bus_state == SDC_BUS_ACMD) + { + // Check the status of CMD55 and send the scheduled command if no errors has been reported. + m_cb.state.bus_state = SDC_BUS_CMD; + uint8_t r1 = rx_data[0]; + if (!(r1 & (~SDC_FLAG_IN_IDLE_STATE))) + { + sdc_spi_transfer(m_cb.cmd_buf + SDC_COMMAND_LEN, SDC_COMMAND_LEN, + m_cb.rsp_buf, SDC_COMMAND_LEN + SDC_MAX_NCR + m_cb.state.rsp_len); + return; + } + } + m_cb.state.bus_state = SDC_BUS_IDLE; + } + + sdc_result_t exit_code = SDC_ERROR_INTERNAL; + sdc_evt_t evt; + switch (m_cb.state.op) + { + case SDC_OP_RESET: + m_cb.state.op = SDC_OP_IDENTIFICATION; + PT_INIT(SDC_PT); + //lint -e{616} + case SDC_OP_IDENTIFICATION: + if (!PT_SCHEDULE(sdc_pt_identification(rx_data, rx_length, &exit_code))) + { + evt.type = SDC_EVT_INIT; + evt.result = exit_code; + m_cb.state.op = SDC_OP_IDLE; + SDC_CS_DEASSERT(); + if (exit_code != SDC_SUCCESS) + { + // Initialization process failed. Roll back to uninitialized state. + APP_ERROR_CHECK(app_sdc_uninit()); + } + sdc_spi_hispeed(); + m_cb.handler(&evt); + } + break; + case SDC_OP_READ: + if (!PT_SCHEDULE(sdc_pt_read(rx_data, rx_length, &exit_code))) + { + evt.type = SDC_EVT_READ; + evt.result = exit_code; + m_cb.state.op = SDC_OP_IDLE; + m_cb.state.rw_op.block_count = 0; + m_cb.state.rw_op.blocks_left = 0; + m_cb.state.bus_state = SDC_BUS_IDLE; + SDC_CS_DEASSERT(); + m_cb.handler(&evt); + } + break; + case SDC_OP_WRITE: + if (!PT_SCHEDULE(sdc_pt_write(rx_data, rx_length, &exit_code))) + { + evt.type = SDC_EVT_WRITE; + evt.result = exit_code; + m_cb.state.op = SDC_OP_IDLE; + m_cb.state.bus_state = SDC_BUS_IDLE; + m_cb.state.rw_op.block_count = 0; + m_cb.state.rw_op.blocks_left = 0; + SDC_CS_DEASSERT(); + m_cb.handler(&evt); + } + break; + default: + APP_ERROR_CHECK(NRF_ERROR_INTERNAL); + break; + } + + return; +} + + +ret_code_t app_sdc_block_read(uint8_t * p_buf, uint32_t block_address, uint16_t block_count) +{ + ASSERT(p_buf); + + if (m_cb.state.op == SDC_UNINITIALIZED) + { + return NRF_ERROR_INVALID_STATE; + } + if (m_cb.state.op != SDC_OP_IDLE) + { + return NRF_ERROR_BUSY; + } + if (block_count == 0) + { + return NRF_ERROR_INVALID_PARAM; + } + + m_cb.state.op = SDC_OP_READ; + + if (!m_cb.info.type.sdhc) + { + m_cb.state.rw_op.address = block_address * SDC_SECTOR_SIZE; + } + else + { + m_cb.state.rw_op.address = block_address; + } + m_cb.state.rw_op.buffer = p_buf; + m_cb.state.rw_op.block_count = block_count; + m_cb.state.rw_op.blocks_left = block_count; + + PT_INIT(&m_cb.state.pt); + uint8_t command = (block_count > 1) ? CMD18 : CMD17; + ret_code_t err_code = sdc_cmd(command, m_cb.state.rw_op.address, SDC_R1); + APP_ERROR_CHECK(err_code); + + return NRF_SUCCESS; +} + + +ret_code_t app_sdc_block_write(uint8_t const * p_buf, uint32_t block_address, uint16_t block_count) +{ + ASSERT(p_buf); + + if (m_cb.state.op == SDC_UNINITIALIZED) + { + return NRF_ERROR_INVALID_STATE; + } + if (m_cb.state.op != SDC_OP_IDLE) + { + return NRF_ERROR_BUSY; + } + if (block_count == 0) + { + return NRF_ERROR_INVALID_PARAM; + } + m_cb.state.op = SDC_OP_WRITE; + + if (!m_cb.info.type.sdhc) + { + m_cb.state.rw_op.address = block_address * 512uL; + } + else + { + m_cb.state.rw_op.address = block_address; + } + m_cb.state.rw_op.buffer = (uint8_t *) p_buf; + m_cb.state.rw_op.block_count = block_count; + m_cb.state.rw_op.blocks_left = block_count; + + PT_INIT(&m_cb.state.pt); + + ret_code_t err_code; + if (block_count == 1) + { + err_code = sdc_cmd(CMD24, m_cb.state.rw_op.address, SDC_R1); + + APP_ERROR_CHECK(err_code); + return NRF_SUCCESS; + } + + if (m_cb.info.type.version == SDC_TYPE_MMCV3) + { + // Start multiple block write. + err_code = sdc_cmd(CMD25, m_cb.state.rw_op.address, SDC_R1); + } + else + { + // Set pre-erase for SD cards before sending CMD25. + err_code = sdc_cmd(ACMD23, block_count, SDC_R1); + } + + APP_ERROR_CHECK(err_code); + return NRF_SUCCESS; +} + + +ret_code_t app_sdc_init(app_sdc_config_t const * const p_config, sdc_event_handler_t event_handler) +{ + if (m_cb.state.op != SDC_UNINITIALIZED) + { + return NRF_ERROR_INVALID_STATE; + } + if ((!event_handler) + || (p_config->cs_pin == NRF_DRV_SPI_PIN_NOT_USED) + || (p_config->miso_pin == NRF_DRV_SPI_PIN_NOT_USED) + || (p_config->mosi_pin == NRF_DRV_SPI_PIN_NOT_USED) + || (p_config->sck_pin == NRF_DRV_SPI_PIN_NOT_USED)) + { + return NRF_ERROR_INVALID_PARAM; + } + + ret_code_t err_code; + ASSERT(p_config->cs_pin && p_config->miso_pin + && p_config->mosi_pin && p_config->sck_pin); + + // Configure chip select pin. + m_cb.cs_pin = p_config->cs_pin; + nrf_gpio_cfg_output(m_cb.cs_pin); + SDC_CS_DEASSERT(); + + const nrf_drv_spi_config_t spi_cfg = { + .sck_pin = p_config->sck_pin, + .mosi_pin = p_config->mosi_pin, + .miso_pin = p_config->miso_pin, + .ss_pin = NRF_DRV_SPI_PIN_NOT_USED, + .irq_priority = SPI_DEFAULT_CONFIG_IRQ_PRIORITY, + .orc = 0xFF, + .frequency = (nrf_drv_spi_frequency_t) APP_SDCARD_FREQ_INIT, + .mode = NRF_DRV_SPI_MODE_0, + .bit_order = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST, + }; + err_code = nrf_drv_spi_init(&m_spi, &spi_cfg, spi_handler, NULL); + APP_ERROR_CHECK(err_code); + + m_cb.handler = event_handler; + m_cb.state.op = SDC_OP_RESET; + m_cb.info.type.version = SDC_TYPE_UNKNOWN; + m_cb.info.type.sdhc = 0; + m_cb.state.bus_state = SDC_BUS_IDLE; + + // Send 80 clocks with CS inactive to switch into SPI mode. + m_cb.cmd_buf[0] = 0xFF; + err_code = nrf_drv_spi_transfer(&m_spi, m_cb.cmd_buf, 1, + m_cb.rsp_buf, 10); + APP_ERROR_CHECK(err_code); + + return NRF_SUCCESS; +} + + +ret_code_t app_sdc_uninit(void) +{ + if (m_cb.state.op == SDC_UNINITIALIZED) + { + return NRF_ERROR_INVALID_STATE; + } + if (m_cb.state.op != SDC_OP_IDLE) + { + return NRF_ERROR_BUSY; + } + + nrf_drv_spi_uninit(&m_spi); + nrf_gpio_cfg_input(m_cb.cs_pin, NRF_GPIO_PIN_NOPULL); + + m_cb.state.bus_state = SDC_BUS_IDLE; + m_cb.state.op = SDC_UNINITIALIZED; + + return NRF_SUCCESS; +} + + +bool app_sdc_busy_check(void) +{ + return ((m_cb.state.op != SDC_OP_IDLE) && (m_cb.state.op != SDC_UNINITIALIZED)); +} + + +app_sdc_info_t const * app_sdc_info_get(void) +{ + if (m_cb.state.op >= SDC_OP_IDLE) + { + return &m_cb.info; + } + return NULL; +} + +#endif //APP_SDCARD_ENABLED -- cgit v1.2.3