driver: SD protocol driver for SPI peripheral
This commit is contained in:
committed by
Ivan Grokhotkov
parent
5f8785eaec
commit
e5bb45f381
156
components/driver/include/driver/sdspi_host.h
Normal file
156
components/driver/include/driver/sdspi_host.h
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
#include "sdmmc_types.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Default sdmmc_host_t structure initializer for SD over SPI driver
|
||||
*
|
||||
* Uses SPI mode and max frequency set to 20MHz
|
||||
*
|
||||
* 'slot' can be set to one of HSPI_HOST, VSPI_HOST.
|
||||
*/
|
||||
#define SDSPI_HOST_DEFAULT() {\
|
||||
.flags = SDMMC_HOST_FLAG_SPI, \
|
||||
.slot = HSPI_HOST, \
|
||||
.max_freq_khz = SDMMC_FREQ_DEFAULT, \
|
||||
.io_voltage = 3.3f, \
|
||||
.init = &sdspi_host_init, \
|
||||
.set_bus_width = NULL, \
|
||||
.set_card_clk = &sdspi_host_set_card_clk, \
|
||||
.do_transaction = &sdspi_host_do_transaction, \
|
||||
.deinit = &sdspi_host_deinit, \
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra configuration for SPI host
|
||||
*/
|
||||
typedef struct {
|
||||
gpio_num_t gpio_miso; ///< GPIO number of MISO signal
|
||||
gpio_num_t gpio_mosi; ///< GPIO number of MOSI signal
|
||||
gpio_num_t gpio_sck; ///< GPIO number of SCK signal
|
||||
gpio_num_t gpio_cs; ///< GPIO number of CS signal
|
||||
gpio_num_t gpio_cd; ///< GPIO number of card detect signal
|
||||
gpio_num_t gpio_wp; ///< GPIO number of write protect signal
|
||||
int dma_channel; ///< DMA channel to be used by SPI driver (1 or 2)
|
||||
} sdspi_slot_config_t;
|
||||
|
||||
#define SDSPI_SLOT_NO_CD ((gpio_num_t) -1) ///< indicates that card detect line is not used
|
||||
#define SDSPI_SLOT_NO_WP ((gpio_num_t) -1) ///< indicates that write protect line is not used
|
||||
|
||||
/**
|
||||
* Macro defining default configuration of SPI host
|
||||
*/
|
||||
#define SDSPI_SLOT_CONFIG_DEFAULT() {\
|
||||
.gpio_miso = GPIO_NUM_2, \
|
||||
.gpio_mosi = GPIO_NUM_15, \
|
||||
.gpio_sck = GPIO_NUM_14, \
|
||||
.gpio_cs = GPIO_NUM_13, \
|
||||
.gpio_cd = SDMMC_SLOT_NO_CD, \
|
||||
.gpio_wp = SDMMC_SLOT_NO_WP, \
|
||||
.dma_channel = 1 \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize SD SPI driver
|
||||
*
|
||||
* @note This function is not thread safe
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - other error codes may be returned in future versions
|
||||
*/
|
||||
esp_err_t sdspi_host_init();
|
||||
|
||||
/**
|
||||
* @brief Initialize SD SPI driver for the specific SPI controller
|
||||
*
|
||||
* @note This function is not thread safe
|
||||
*
|
||||
* @param slot SPI controller to use (HSPI_HOST or VSPI_HOST)
|
||||
* @param slot_config pointer to slot configuration structure
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if sdspi_init_slot has invalid arguments
|
||||
* - ESP_ERR_NO_MEM if memory can not be allocated
|
||||
* - other errors from the underlying spi_master and gpio drivers
|
||||
*/
|
||||
esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config);
|
||||
|
||||
/**
|
||||
* @brief Send command to the card and get response
|
||||
*
|
||||
* This function returns when command is sent and response is received,
|
||||
* or data is transferred, or timeout occurs.
|
||||
*
|
||||
* @note This function is not thread safe w.r.t. init/deinit functions,
|
||||
* and bus width/clock speed configuration functions. Multiple tasks
|
||||
* can call sdspi_host_do_transaction as long as other sdspi_host_*
|
||||
* functions are not called.
|
||||
*
|
||||
* @param slot SPI controller (HSPI_HOST or VSPI_HOST)
|
||||
* @param cmdinfo pointer to structure describing command and data to transfer
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_TIMEOUT if response or data transfer has timed out
|
||||
* - ESP_ERR_INVALID_CRC if response or data transfer CRC check has failed
|
||||
* - ESP_ERR_INVALID_RESPONSE if the card has sent an invalid response
|
||||
*/
|
||||
esp_err_t sdspi_host_do_transaction(int slot, sdmmc_command_t *cmdinfo);
|
||||
|
||||
/**
|
||||
* @brief Set card clock frequency
|
||||
*
|
||||
* Currently only integer fractions of 40MHz clock can be used.
|
||||
* For High Speed cards, 40MHz can be used.
|
||||
* For Default Speed cards, 20MHz can be used.
|
||||
*
|
||||
* @note This function is not thread safe
|
||||
*
|
||||
* @param slot SPI controller (HSPI_HOST or VSPI_HOST)
|
||||
* @param freq_khz card clock frequency, in kHz
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - other error codes may be returned in the future
|
||||
*/
|
||||
esp_err_t sdspi_host_set_card_clk(int slot, uint32_t freq_khz);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Release resources allocated using sdspi_host_init
|
||||
*
|
||||
* @note This function is not thread safe
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_STATE if sdspi_host_init function has not been called
|
||||
*/
|
||||
esp_err_t sdspi_host_deinit();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
53
components/driver/sdspi_crc.c
Normal file
53
components/driver/sdspi_crc.c
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdint.h>
|
||||
#include "rom/crc.h"
|
||||
#include "sdspi_crc.h"
|
||||
|
||||
static const uint8_t crc7_table[256] =
|
||||
{
|
||||
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
|
||||
0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e,
|
||||
0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
|
||||
0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c,
|
||||
0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13,
|
||||
0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
|
||||
0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21,
|
||||
0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38,
|
||||
0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
|
||||
0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f,
|
||||
0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04,
|
||||
0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
|
||||
0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52,
|
||||
0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b,
|
||||
0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
|
||||
0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79,
|
||||
};
|
||||
|
||||
// returns the CRC-7 for a message of "length" bytes
|
||||
uint8_t sdspi_crc7(const uint8_t *data, size_t size)
|
||||
{
|
||||
uint8_t result = 0;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
result = crc7_table[(result << 1) ^ data[i]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Return CRC16 of data, in the on-the-wire format used by SD protocol
|
||||
uint16_t sdspi_crc16(const uint8_t* data, size_t size)
|
||||
{
|
||||
return __builtin_bswap16(crc16_be(UINT16_MAX, data, size) ^ UINT16_MAX);
|
||||
}
|
||||
43
components/driver/sdspi_crc.h
Normal file
43
components/driver/sdspi_crc.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* @brief Return CRC7 of data, in the format used by SD protocol
|
||||
* @param data array of data used to compute CRC
|
||||
* @param size size of data in bytes
|
||||
* @return CRC7 value
|
||||
*/
|
||||
uint8_t sdspi_crc7(const uint8_t *data, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Return CRC16 of data, in the format used by SD protocol
|
||||
* @param data array of data used to compute CRC
|
||||
* @param size size of data in bytes
|
||||
* @return CRC16 value
|
||||
*/
|
||||
uint16_t sdspi_crc16(const uint8_t* data, size_t size);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
815
components/driver/sdspi_host.c
Normal file
815
components/driver/sdspi_host.c
Normal file
@@ -0,0 +1,815 @@
|
||||
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/param.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "sdspi_private.h"
|
||||
#include "sdspi_crc.h"
|
||||
|
||||
/// Max number of transactions in flight (used in start_command_write_blocks)
|
||||
#define SDSPI_TRANSACTION_COUNT 4
|
||||
#define SDSPI_MOSI_IDLE_VAL 0xff //!< Data value which causes MOSI to stay high
|
||||
/// FIXME: this has to be replaced with a timeout expressed in ms, rather in retries
|
||||
#define SDSPI_RETRY_COUNT 1000
|
||||
#define GPIO_UNUSED 0xff //!< Flag indicating that CD/WP is unused
|
||||
/// Size of the buffer returned by get_block_buf
|
||||
#define SDSPI_BLOCK_BUF_SIZE (SDSPI_MAX_DATA_LEN + 4)
|
||||
|
||||
|
||||
/// Structure containing run time configuration for a single SD slot
|
||||
typedef struct {
|
||||
spi_device_handle_t handle; //!< SPI device handle, used for transactions
|
||||
uint8_t gpio_cs; //!< CS GPIO
|
||||
uint8_t gpio_cd; //!< Card detect GPIO, or GPIO_UNUSED
|
||||
uint8_t gpio_wp; //!< Write protect GPIO, or GPIO_UNUSED
|
||||
/// Set to 1 if the higher layer has asked the card to enable CRC checks
|
||||
uint8_t data_crc_enabled : 1;
|
||||
/// Number of transactions in 'transactions' array which are in use
|
||||
uint8_t used_transaction_count: 3;
|
||||
/// Intermediate buffer used when application buffer is not in DMA memory;
|
||||
/// allocated on demand, SDSPI_BLOCK_BUF_SIZE bytes long. May be zero.
|
||||
uint8_t* block_buf;
|
||||
/// array with SDSPI_TRANSACTION_COUNT transaction structures
|
||||
spi_transaction_t* transactions;
|
||||
} slot_info_t;
|
||||
|
||||
static slot_info_t s_slots[3];
|
||||
static const char *TAG = "sdspi_host";
|
||||
|
||||
/// Functions to send out different kinds of commands
|
||||
static esp_err_t start_command_read_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
uint8_t *data, uint32_t rx_length);
|
||||
|
||||
static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
const uint8_t *data, uint32_t tx_length);
|
||||
|
||||
static esp_err_t start_command_default(int slot, int flags, sdspi_hw_cmd_t *cmd);
|
||||
|
||||
/// A few helper functions
|
||||
|
||||
/// Set CS high for given slot
|
||||
static void cs_high(int slot)
|
||||
{
|
||||
gpio_set_level(s_slots[slot].gpio_cs, 1);
|
||||
}
|
||||
|
||||
/// Set CS low for given slot
|
||||
static void cs_low(int slot)
|
||||
{
|
||||
gpio_set_level(s_slots[slot].gpio_cs, 0);
|
||||
}
|
||||
|
||||
/// Return true if WP pin is configured and is low
|
||||
static bool card_write_protected(int slot)
|
||||
{
|
||||
if (s_slots[slot].gpio_wp == GPIO_UNUSED) {
|
||||
return false;
|
||||
}
|
||||
return gpio_get_level(s_slots[slot].gpio_wp) == 0;
|
||||
}
|
||||
|
||||
/// Return true if CD pin is configured and is high
|
||||
static bool card_missing(int slot)
|
||||
{
|
||||
if (s_slots[slot].gpio_cd == GPIO_UNUSED) {
|
||||
return false;
|
||||
}
|
||||
return gpio_get_level(s_slots[slot].gpio_cd) == 1;
|
||||
}
|
||||
|
||||
/// Check if slot number is within bounds
|
||||
static bool is_valid_slot(int slot)
|
||||
{
|
||||
return slot == VSPI_HOST || slot == HSPI_HOST;
|
||||
}
|
||||
|
||||
static spi_device_handle_t spi_handle(int slot)
|
||||
{
|
||||
return s_slots[slot].handle;
|
||||
}
|
||||
|
||||
static bool is_slot_initialized(int slot)
|
||||
{
|
||||
return spi_handle(slot) != NULL;
|
||||
}
|
||||
|
||||
static bool data_crc_enabled(int slot)
|
||||
{
|
||||
return s_slots[slot].data_crc_enabled;
|
||||
}
|
||||
|
||||
/// Get pointer to a block of DMA memory, allocate if necessary.
|
||||
/// This is used if the application provided buffer is not in DMA capable memory.
|
||||
static esp_err_t get_block_buf(int slot, uint8_t** out_buf)
|
||||
{
|
||||
if (s_slots[slot].block_buf == NULL) {
|
||||
s_slots[slot].block_buf = heap_caps_malloc(SDSPI_BLOCK_BUF_SIZE, MALLOC_CAP_DMA);
|
||||
if (s_slots[slot].block_buf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
*out_buf = s_slots[slot].block_buf;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static spi_transaction_t* get_transaction(int slot)
|
||||
{
|
||||
size_t used_transaction_count = s_slots[slot].used_transaction_count;
|
||||
assert(used_transaction_count < SDSPI_TRANSACTION_COUNT);
|
||||
spi_transaction_t* ret = &s_slots[slot].transactions[used_transaction_count];
|
||||
++s_slots[slot].used_transaction_count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void release_transaction(int slot)
|
||||
{
|
||||
--s_slots[slot].used_transaction_count;
|
||||
}
|
||||
|
||||
static void wait_for_transactions(int slot)
|
||||
{
|
||||
size_t used_transaction_count = s_slots[slot].used_transaction_count;
|
||||
for (size_t i = 0; i < used_transaction_count; ++i) {
|
||||
spi_transaction_t* t_out;
|
||||
spi_device_get_trans_result(spi_handle(slot), &t_out, portMAX_DELAY);
|
||||
release_transaction(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clock out one byte (CS has to be high) to make the card release MISO
|
||||
/// (clocking one bit would work as well, but that triggers a bug in SPI DMA)
|
||||
static void release_bus(int slot)
|
||||
{
|
||||
spi_transaction_t t = {
|
||||
.flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA,
|
||||
.length = 8,
|
||||
.tx_data = {0xff}
|
||||
};
|
||||
spi_device_transmit(spi_handle(slot), &t);
|
||||
// don't care if this failed
|
||||
}
|
||||
|
||||
/// Clock out 80 cycles (10 bytes) before GO_IDLE command
|
||||
static void go_idle_clockout(int slot)
|
||||
{
|
||||
uint8_t data[10];
|
||||
memset(data, 0xff, sizeof(data));
|
||||
spi_transaction_t t = {
|
||||
.length = sizeof(data) * 8,
|
||||
.tx_buffer = data,
|
||||
.rx_buffer = data,
|
||||
};
|
||||
spi_device_transmit(spi_handle(slot), &t);
|
||||
// don't care if this failed
|
||||
}
|
||||
|
||||
|
||||
/// Return true if the pointer can be used for DMA
|
||||
static bool ptr_dma_compatible(const void* ptr)
|
||||
{
|
||||
return (uintptr_t) ptr >= 0x3FFAE000 &&
|
||||
(uintptr_t) ptr < 0x40000000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize SPI device. Used to change clock speed.
|
||||
* @param slot SPI host number
|
||||
* @param clock_speed_hz clock speed, Hz
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
static esp_err_t init_spi_dev(int slot, int clock_speed_hz)
|
||||
{
|
||||
if (spi_handle(slot)) {
|
||||
// Reinitializing
|
||||
spi_bus_remove_device(spi_handle(slot));
|
||||
s_slots[slot].handle = NULL;
|
||||
}
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.clock_speed_hz = clock_speed_hz,
|
||||
.mode = 0,
|
||||
// For SD cards, CS must stay low during the whole read/write operation,
|
||||
// rather than a single SPI transaction.
|
||||
.spics_io_num = -1,
|
||||
.queue_size = SDSPI_TRANSACTION_COUNT,
|
||||
};
|
||||
return spi_bus_add_device((spi_host_device_t) slot, &devcfg, &s_slots[slot].handle);
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_init()
|
||||
{
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_deinit()
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(s_slots)/sizeof(s_slots[0]); ++i) {
|
||||
if (s_slots[i].handle) {
|
||||
spi_bus_remove_device(s_slots[i].handle);
|
||||
free(s_slots[i].block_buf);
|
||||
s_slots[i].block_buf = NULL;
|
||||
free(s_slots[i].transactions);
|
||||
s_slots[i].transactions = NULL;
|
||||
spi_bus_free((spi_host_device_t) i);
|
||||
s_slots[i].handle = NULL;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_set_card_clk(int slot, uint32_t freq_khz)
|
||||
{
|
||||
if (!is_valid_slot(slot)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!is_slot_initialized(slot)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
ESP_LOGD(TAG, "Setting card clock to %d kHz", freq_khz);
|
||||
return init_spi_dev(slot, freq_khz * 1000);
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config)
|
||||
{
|
||||
ESP_LOGD(TAG, "%s: SPI%d miso=%d mosi=%d sck=%d cs=%d cd=%d wp=%d, dma_ch=%d",
|
||||
__func__, slot + 1,
|
||||
slot_config->gpio_miso, slot_config->gpio_mosi,
|
||||
slot_config->gpio_sck, slot_config->gpio_cs,
|
||||
slot_config->gpio_cd, slot_config->gpio_wp,
|
||||
slot_config->dma_channel);
|
||||
|
||||
spi_host_device_t host = (spi_host_device_t) slot;
|
||||
if (!is_valid_slot(slot)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.miso_io_num = slot_config->gpio_miso,
|
||||
.mosi_io_num = slot_config->gpio_mosi,
|
||||
.sclk_io_num = slot_config->gpio_sck,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1
|
||||
};
|
||||
|
||||
// Initialize SPI bus
|
||||
esp_err_t ret = spi_bus_initialize((spi_host_device_t)slot, &buscfg,
|
||||
slot_config->dma_channel);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "spi_bus_initialize failed with rc=0x%x", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Attach the SD card to the SPI bus
|
||||
ret = init_spi_dev(slot, SDMMC_FREQ_PROBING * 1000);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "spi_bus_add_device failed with rc=0x%x", ret);
|
||||
spi_bus_free(host);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Configure CS pin
|
||||
s_slots[slot].gpio_cs = (uint8_t) slot_config->gpio_cs;
|
||||
gpio_config_t io_conf = {
|
||||
.intr_type = GPIO_PIN_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pin_bit_mask = 1LL << slot_config->gpio_cs,
|
||||
};
|
||||
|
||||
ret = gpio_config(&io_conf);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "gpio_config (CS) failed with rc=0x%x", ret);
|
||||
spi_bus_remove_device(spi_handle(slot));
|
||||
s_slots[slot].handle = NULL;
|
||||
spi_bus_free(host);
|
||||
return ret;
|
||||
}
|
||||
cs_high(slot);
|
||||
|
||||
// Configure CD and WP pins
|
||||
io_conf = (gpio_config_t) {
|
||||
.intr_type = GPIO_PIN_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pin_bit_mask = 0,
|
||||
.pull_up_en = true
|
||||
};
|
||||
if (slot_config->gpio_cd != SDSPI_SLOT_NO_CD) {
|
||||
io_conf.pin_bit_mask |= (1 << slot_config->gpio_cd);
|
||||
s_slots[slot].gpio_wp = slot_config->gpio_wp;
|
||||
} else {
|
||||
s_slots[slot].gpio_wp = GPIO_UNUSED;
|
||||
}
|
||||
|
||||
if (slot_config->gpio_wp != SDSPI_SLOT_NO_WP) {
|
||||
io_conf.pin_bit_mask |= (1 << slot_config->gpio_wp);
|
||||
s_slots[slot].gpio_cd = slot_config->gpio_cd;
|
||||
} else {
|
||||
s_slots[slot].gpio_cd = GPIO_UNUSED;
|
||||
}
|
||||
|
||||
if (io_conf.pin_bit_mask != 0) {
|
||||
ret = gpio_config(&io_conf);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "gpio_config (CD/WP) failed with rc=0x%x", ret);
|
||||
spi_bus_remove_device(spi_handle(slot));
|
||||
s_slots[slot].handle = NULL;
|
||||
spi_bus_free(host);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
s_slots[slot].transactions = calloc(SDSPI_TRANSACTION_COUNT, sizeof(spi_transaction_t));
|
||||
if (s_slots[slot].transactions == NULL) {
|
||||
spi_bus_remove_device(spi_handle(slot));
|
||||
s_slots[slot].handle = NULL;
|
||||
spi_bus_free(host);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t sdspi_host_start_command(int slot, sdspi_hw_cmd_t *cmd, void *data,
|
||||
uint32_t data_size, int flags)
|
||||
{
|
||||
if (!is_valid_slot(slot)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!is_slot_initialized(slot)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (card_missing(slot)) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
// save some parts of cmd, as its contents will be overwritten
|
||||
int cmd_index = cmd->cmd_index;
|
||||
uint32_t cmd_arg;
|
||||
memcpy(&cmd_arg, cmd->arguments, sizeof(cmd_arg));
|
||||
cmd_arg = __builtin_bswap32(cmd_arg);
|
||||
ESP_LOGV(TAG, "%s: slot=%i, CMD%d, arg=0x%08x flags=0x%x, data=%p, data_size=%i crc=0x%02x",
|
||||
__func__, slot, cmd_index, cmd_arg, flags, data, data_size, cmd->crc7);
|
||||
|
||||
|
||||
// For CMD0, clock out 80 cycles to help the card enter idle state,
|
||||
// *before* CS is asserted.
|
||||
if (cmd_index == MMC_GO_IDLE_STATE) {
|
||||
go_idle_clockout(slot);
|
||||
}
|
||||
// actual transaction
|
||||
esp_err_t ret = ESP_OK;
|
||||
cs_low(slot);
|
||||
if (flags & SDSPI_CMD_FLAG_DATA) {
|
||||
if (flags & SDSPI_CMD_FLAG_WRITE) {
|
||||
ret = start_command_write_blocks(slot, cmd, data, data_size);
|
||||
} else {
|
||||
ret = start_command_read_blocks(slot, cmd, data, data_size);
|
||||
}
|
||||
} else {
|
||||
ret = start_command_default(slot, flags, cmd);
|
||||
}
|
||||
cs_high(slot);
|
||||
|
||||
release_bus(slot);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s: cmd=%d error=0x%x", __func__, cmd_index, ret);
|
||||
} else {
|
||||
// Update internal state when some commands are sent successfully
|
||||
if (cmd_index == SD_CRC_ON_OFF) {
|
||||
s_slots[slot].data_crc_enabled = (uint8_t) cmd_arg;
|
||||
ESP_LOGD(TAG, "data CRC set=%d", s_slots[slot].data_crc_enabled);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t start_command_default(int slot, int flags, sdspi_hw_cmd_t *cmd)
|
||||
{
|
||||
size_t cmd_size = SDSPI_CMD_R1_SIZE;
|
||||
if (flags & SDSPI_CMD_FLAG_RSP_R1) {
|
||||
cmd_size = SDSPI_CMD_R1_SIZE;
|
||||
} else if (flags & SDSPI_CMD_FLAG_RSP_R2) {
|
||||
cmd_size = SDSPI_CMD_R2_SIZE;
|
||||
} else if (flags & SDSPI_CMD_FLAG_RSP_R3) {
|
||||
cmd_size = SDSPI_CMD_R3_SIZE;
|
||||
} else if (flags & SDSPI_CMD_FLAG_RSP_R7) {
|
||||
cmd_size = SDSPI_CMD_R7_SIZE;
|
||||
}
|
||||
spi_transaction_t t = {
|
||||
.flags = 0,
|
||||
.length = cmd_size * 8,
|
||||
.tx_buffer = cmd,
|
||||
.rx_buffer = cmd
|
||||
};
|
||||
esp_err_t ret = spi_device_transmit(spi_handle(slot), &t);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Wait until MISO goes high
|
||||
static esp_err_t poll_busy(int slot, spi_transaction_t* t)
|
||||
{
|
||||
uint8_t t_rx;
|
||||
uint8_t rd_data;
|
||||
*t = (spi_transaction_t) {
|
||||
.tx_buffer = &t_rx,
|
||||
.rx_buffer = &rd_data,
|
||||
.length = 8,
|
||||
};
|
||||
esp_err_t ret;
|
||||
|
||||
for (int i = 0; i < SDSPI_RETRY_COUNT; i++) {
|
||||
t_rx = SDSPI_MOSI_IDLE_VAL;
|
||||
rd_data = 0;
|
||||
ret = spi_device_transmit(spi_handle(slot), t);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
if (rd_data != 0) {
|
||||
if (i < SDSPI_RETRY_COUNT - 2) {
|
||||
i = SDSPI_RETRY_COUNT - 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Wait for response token
|
||||
static esp_err_t poll_response_token(int slot, spi_transaction_t* t)
|
||||
{
|
||||
uint8_t t_rx;
|
||||
uint8_t rd_data;
|
||||
*t = (spi_transaction_t) {
|
||||
.tx_buffer = &t_rx,
|
||||
.rx_buffer = &rd_data,
|
||||
.length = 8,
|
||||
};
|
||||
esp_err_t ret;
|
||||
|
||||
for (int retry = 0; retry < SDSPI_RETRY_COUNT; retry++) {
|
||||
t_rx = SDSPI_MOSI_IDLE_VAL;
|
||||
rd_data = 0;
|
||||
ret = spi_device_transmit(spi_handle(slot), t);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
if ((rd_data & TOKEN_RSP_MASK) == TOKEN_RSP_OK) {
|
||||
break;
|
||||
}
|
||||
if ((rd_data & TOKEN_RSP_MASK) == TOKEN_RSP_CRC_ERR) {
|
||||
return ESP_ERR_INVALID_CRC;
|
||||
}
|
||||
if ((rd_data & TOKEN_RSP_MASK) == TOKEN_RSP_WRITE_ERR) {
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
if (retry == SDSPI_RETRY_COUNT - 1) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Wait for data token, reading 8 bytes at a time.
|
||||
// If the token is found, write all subsequent bytes to extra_ptr,
|
||||
// and store the number of bytes written to extra_size.
|
||||
static esp_err_t poll_data_token(int slot, spi_transaction_t* t,
|
||||
uint8_t* extra_ptr, size_t* extra_size)
|
||||
{
|
||||
uint8_t t_rx[8];
|
||||
*t = (spi_transaction_t) {
|
||||
.tx_buffer = &t_rx,
|
||||
.rx_buffer = &t_rx,
|
||||
.length = sizeof(t_rx) * 8,
|
||||
};
|
||||
esp_err_t ret;
|
||||
for (int retry = 0; retry < SDSPI_RETRY_COUNT; retry++) {
|
||||
memset(t_rx, SDSPI_MOSI_IDLE_VAL, sizeof(t_rx));
|
||||
ret = spi_device_transmit(spi_handle(slot), t);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
bool found = false;
|
||||
for (int byte_idx = 0; byte_idx < sizeof(t_rx); byte_idx++) {
|
||||
uint8_t rd_data = t_rx[byte_idx];
|
||||
if (rd_data == TOKEN_BLOCK_START) {
|
||||
found = true;
|
||||
memcpy(extra_ptr, t_rx + byte_idx + 1, sizeof(t_rx) - byte_idx - 1);
|
||||
*extra_size = sizeof(t_rx) - byte_idx - 1;
|
||||
break;
|
||||
}
|
||||
if (rd_data != 0xff && rd_data != 0) {
|
||||
ESP_LOGD(TAG, "%s: received 0x%02x while waiting for data",
|
||||
__func__, rd_data);
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
if (retry == SDSPI_RETRY_COUNT - 1) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Receiving one or more blocks of data happens as follows:
|
||||
* 1. send command + receive r1 response (SDSPI_CMD_R1_SIZE bytes total)
|
||||
* 2. keep receiving bytes until TOKEN_BLOCK_START is encountered (this may
|
||||
* take a while, depending on card's read speed)
|
||||
* 3. receive up to SDSPI_MAX_DATA_LEN = 512 bytes of actual data
|
||||
* 4. receive 2 bytes of CRC
|
||||
* 5. for multi block transfers, go to step 2
|
||||
*
|
||||
* These steps can be done separately, but that leads to a less than optimal
|
||||
* performance on large transfers because of delays between each step.
|
||||
* For example, if steps 3 and 4 are separate SPI transactions queued one after
|
||||
* another, there will be ~16 microseconds of dead time between end of step 3
|
||||
* and the beginning of step 4. A delay between two blocking SPI transactions
|
||||
* in step 2 is even higher (~60 microseconds).
|
||||
*
|
||||
* To improve read performance the following sequence is adopted:
|
||||
* 1. Do the first transfer: command + r1 response + 8 extra bytes.
|
||||
* Set pre_scan_data_ptr to point to the 8 extra bytes, and set
|
||||
* pre_scan_data_size to 8.
|
||||
* 2. Search pre_scan_data_size bytes for TOKEN_BLOCK_START.
|
||||
* If found, the rest of the bytes contain part of the actual data.
|
||||
* Store pointer to and size of that extra data as extra_data_{ptr,size}.
|
||||
* If not found, fall back to polling for TOKEN_BLOCK_START, 8 bytes at a
|
||||
* time (in poll_data_token function). Deal with extra data in the same way,
|
||||
* by setting extra_data_{ptr,size}.
|
||||
* 3. Receive the remaining 512 - extra_data_size bytes, plus 4 extra bytes
|
||||
* (i.e. 516 - extra_data_size). Of the 4 extra bytes, first two will capture
|
||||
* the CRC value, and the other two will capture 0xff 0xfe sequence
|
||||
* indicating the start of the next block. Actual scanning is done by
|
||||
* setting pre_scan_data_ptr to point to these last 2 bytes, and setting
|
||||
* pre_scan_data_size = 2, then going to step 2 to receive the next block.
|
||||
*
|
||||
* With this approach the delay between blocks of a multi-block transfer is
|
||||
* ~95 microseconds, out of which 35 microseconds are spend doing the CRC check.
|
||||
* Further speedup is possible by pipelining transfers and CRC checks, at an
|
||||
* expense of one extra temporary buffer.
|
||||
*/
|
||||
static esp_err_t start_command_read_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
uint8_t *data, uint32_t rx_length)
|
||||
{
|
||||
bool need_stop_command = rx_length > SDSPI_MAX_DATA_LEN;
|
||||
spi_transaction_t* t_command = get_transaction(slot);
|
||||
*t_command = (spi_transaction_t) {
|
||||
.length = (SDSPI_CMD_R1_SIZE + 8) * 8,
|
||||
.tx_buffer = cmd,
|
||||
.rx_buffer = cmd,
|
||||
};
|
||||
esp_err_t ret = spi_device_transmit(spi_handle(slot), t_command);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
release_transaction(slot);
|
||||
|
||||
uint8_t* cmd_u8 = (uint8_t*) cmd;
|
||||
size_t pre_scan_data_size = 8;
|
||||
uint8_t* pre_scan_data_ptr = cmd_u8 + SDSPI_CMD_R1_SIZE;
|
||||
|
||||
|
||||
while (rx_length > 0) {
|
||||
size_t extra_data_size = 0;
|
||||
const uint8_t* extra_data_ptr = NULL;
|
||||
bool need_poll = true;
|
||||
|
||||
for (int i = 0; i < pre_scan_data_size; ++i) {
|
||||
if (pre_scan_data_ptr[i] == TOKEN_BLOCK_START) {
|
||||
extra_data_size = pre_scan_data_size - i - 1;
|
||||
extra_data_ptr = pre_scan_data_ptr + i + 1;
|
||||
need_poll = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_poll) {
|
||||
// Wait for data to be ready
|
||||
spi_transaction_t* t_poll = get_transaction(slot);
|
||||
poll_data_token(slot, t_poll, cmd_u8 + SDSPI_CMD_R1_SIZE, &extra_data_size);
|
||||
if (extra_data_size) {
|
||||
extra_data_ptr = cmd_u8 + SDSPI_CMD_R1_SIZE;
|
||||
}
|
||||
release_transaction(slot);
|
||||
}
|
||||
|
||||
// Arrange RX buffer
|
||||
size_t will_receive = MIN(rx_length, SDSPI_MAX_DATA_LEN) - extra_data_size;
|
||||
uint8_t* rx_data;
|
||||
ret = get_block_buf(slot, &rx_data);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// receive actual data
|
||||
const size_t receive_extra_bytes = 4;
|
||||
memset(rx_data, 0xff, will_receive + receive_extra_bytes);
|
||||
spi_transaction_t* t_data = get_transaction(slot);
|
||||
*t_data = (spi_transaction_t) {
|
||||
.length = (will_receive + receive_extra_bytes) * 8,
|
||||
.rx_buffer = rx_data,
|
||||
.tx_buffer = rx_data
|
||||
};
|
||||
|
||||
ret = spi_device_transmit(spi_handle(slot), t_data);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
release_transaction(slot);
|
||||
|
||||
// CRC bytes need to be received even if CRC is not enabled
|
||||
uint16_t crc = UINT16_MAX;
|
||||
memcpy(&crc, rx_data + will_receive, sizeof(crc));
|
||||
|
||||
// Bytes to scan for the start token
|
||||
pre_scan_data_size = receive_extra_bytes - sizeof(crc);
|
||||
pre_scan_data_ptr = rx_data + will_receive + sizeof(crc);
|
||||
|
||||
// Copy data to the destination buffer
|
||||
memcpy(data + extra_data_size, rx_data, will_receive);
|
||||
if (extra_data_size) {
|
||||
memcpy(data, extra_data_ptr, extra_data_size);
|
||||
}
|
||||
|
||||
// compute CRC of the received data
|
||||
uint16_t crc_of_data = 0;
|
||||
if (data_crc_enabled(slot)) {
|
||||
crc_of_data = sdspi_crc16(data, will_receive + extra_data_size);
|
||||
if (crc_of_data != crc) {
|
||||
ESP_LOGE(TAG, "data CRC failed, got=0x%04x expected=0x%04x", crc_of_data, crc);
|
||||
esp_log_buffer_hex(TAG, data, 16);
|
||||
return ESP_ERR_INVALID_CRC;
|
||||
}
|
||||
}
|
||||
|
||||
data += will_receive + extra_data_size;
|
||||
rx_length -= will_receive + extra_data_size;
|
||||
extra_data_size = 0;
|
||||
extra_data_ptr = NULL;
|
||||
}
|
||||
|
||||
if (need_stop_command) {
|
||||
// To end multi block transfer, send stop command and wait for the
|
||||
// card to process it
|
||||
sdspi_hw_cmd_t stop_cmd;
|
||||
make_hw_cmd(MMC_STOP_TRANSMISSION, 0, &stop_cmd);
|
||||
ret = start_command_default(slot, SDSPI_CMD_FLAG_RSP_R1, &stop_cmd);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
spi_transaction_t* t_poll = get_transaction(slot);
|
||||
ret = poll_busy(slot, t_poll);
|
||||
release_transaction(slot);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
const uint8_t *data, uint32_t tx_length)
|
||||
{
|
||||
if (card_write_protected(slot)) {
|
||||
ESP_LOGW(TAG, "%s: card write protected", __func__);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
spi_transaction_t* t_command = get_transaction(slot);
|
||||
*t_command = (spi_transaction_t) {
|
||||
.length = SDSPI_CMD_R1_SIZE * 8,
|
||||
.tx_buffer = cmd,
|
||||
.rx_buffer = cmd,
|
||||
};
|
||||
esp_err_t ret = spi_device_queue_trans(spi_handle(slot), t_command, 0);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
uint8_t start_token = tx_length <= SDSPI_MAX_DATA_LEN ?
|
||||
TOKEN_BLOCK_START : TOKEN_BLOCK_START_WRITE_MULTI;
|
||||
wait_for_transactions(slot);
|
||||
|
||||
while (tx_length > 0) {
|
||||
|
||||
// Write block start token
|
||||
spi_transaction_t* t_start_token = get_transaction(slot);
|
||||
*t_start_token = (spi_transaction_t) {
|
||||
.length = sizeof(start_token) * 8,
|
||||
.tx_buffer = &start_token
|
||||
};
|
||||
esp_err_t ret = spi_device_queue_trans(spi_handle(slot), t_start_token, 0);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Prepare data to be sent
|
||||
size_t will_send = MIN(tx_length, SDSPI_MAX_DATA_LEN);
|
||||
const uint8_t* tx_data = data;
|
||||
if (!ptr_dma_compatible(tx_data)) {
|
||||
// If the pointer can't be used with DMA, copy data into a new buffer
|
||||
uint8_t* tmp;
|
||||
ret = get_block_buf(slot, &tmp);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
memcpy(tmp, tx_data, will_send);
|
||||
tx_data = tmp;
|
||||
}
|
||||
|
||||
// Write data
|
||||
spi_transaction_t* t_data = get_transaction(slot);
|
||||
*t_data = (spi_transaction_t) {
|
||||
.length = will_send * 8,
|
||||
.tx_buffer = tx_data,
|
||||
};
|
||||
ret = spi_device_queue_trans(spi_handle(slot), t_data, 0);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Write CRC
|
||||
uint16_t crc = sdspi_crc16(data, will_send);
|
||||
spi_transaction_t* t_crc = get_transaction(slot);
|
||||
*t_crc = (spi_transaction_t) {
|
||||
.length = sizeof(crc) * 8,
|
||||
.tx_buffer = (uint8_t*) &crc,
|
||||
};
|
||||
ret = spi_device_queue_trans(spi_handle(slot), t_crc, 0);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Wait for data to be sent
|
||||
wait_for_transactions(slot);
|
||||
|
||||
// Check if R1 response for the command was correct
|
||||
if (cmd->r1 != 0) {
|
||||
ESP_LOGD(TAG, "%s: invalid R1 response: 0x%02x", __func__, cmd->r1);
|
||||
return ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
// Poll for response
|
||||
spi_transaction_t* t_poll = get_transaction(slot);
|
||||
ret = poll_response_token(slot, t_poll);
|
||||
release_transaction(slot);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Wait for the card to finish writing data
|
||||
t_poll = get_transaction(slot);
|
||||
ret = poll_busy(slot, t_poll);
|
||||
release_transaction(slot);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
tx_length -= will_send;
|
||||
data += will_send;
|
||||
}
|
||||
|
||||
if (start_token == TOKEN_BLOCK_START_WRITE_MULTI) {
|
||||
uint8_t stop_token[2] = {
|
||||
TOKEN_BLOCK_STOP_WRITE_MULTI,
|
||||
SDSPI_MOSI_IDLE_VAL
|
||||
};
|
||||
spi_transaction_t* t_stop_token = get_transaction(slot);
|
||||
*t_stop_token = (spi_transaction_t) {
|
||||
.length = sizeof(stop_token) * 8,
|
||||
.tx_buffer = &stop_token,
|
||||
};
|
||||
ret = spi_device_queue_trans(spi_handle(slot), t_stop_token, 0);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
wait_for_transactions(slot);
|
||||
|
||||
spi_transaction_t* t_poll = get_transaction(slot);
|
||||
ret = poll_busy(slot, t_poll);
|
||||
release_transaction(slot);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
96
components/driver/sdspi_private.h
Normal file
96
components/driver/sdspi_private.h
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
|
||||
/// Control tokens used to frame data transfers
|
||||
/// (see section 7.3.3 of SD simplified spec)
|
||||
|
||||
/// Token sent before single/multi block reads and single block writes
|
||||
#define TOKEN_BLOCK_START 0b11111110
|
||||
/// Token sent before multi block writes
|
||||
#define TOKEN_BLOCK_START_WRITE_MULTI 0b11111100
|
||||
/// Token used to stop multi block write (for reads, CMD12 is used instead)
|
||||
#define TOKEN_BLOCK_STOP_WRITE_MULTI 0b11111101
|
||||
|
||||
/// Data response tokens
|
||||
|
||||
/// Mask (high 3 bits are undefined for data response tokens)
|
||||
#define TOKEN_RSP_MASK 0b11111
|
||||
/// Data accepted
|
||||
#define TOKEN_RSP_OK 0b00101
|
||||
/// Data rejected due to CRC error
|
||||
#define TOKEN_RSP_CRC_ERR 0b01011
|
||||
/// Data rejected due to write error
|
||||
#define TOKEN_RSP_WRITE_ERR 0b01101
|
||||
|
||||
|
||||
/// Data error tokens have format 0b0000xyzw where xyzw are signle bit flags.
|
||||
/// MASK and VAL are used to check if a token is an error token
|
||||
#define TOKEN_ERR_MASK 0b11110000
|
||||
#define TOKEN_ERR_VAL 0b00000000
|
||||
|
||||
/// Argument is out of range
|
||||
#define TOKEN_ERR_RANGE BIT(3)
|
||||
/// Card internal ECC error
|
||||
#define TOKEN_ERR_CARD_ECC BIT(2)
|
||||
/// Card controller error
|
||||
#define TOKEN_ERR_INTERNAL BIT(1)
|
||||
/// Card is locked
|
||||
#define TOKEN_ERR_LOCKED BIT(0)
|
||||
|
||||
|
||||
/// Transfer format in SPI mode. See section 7.3.1.1 of SD simplified spec.
|
||||
typedef struct {
|
||||
// These fields form the command sent from host to the card (6 bytes)
|
||||
uint8_t cmd_index : 6;
|
||||
uint8_t transmission_bit : 1;
|
||||
uint8_t start_bit : 1;
|
||||
uint8_t arguments[4];
|
||||
uint8_t stop_bit : 1;
|
||||
uint8_t crc7 : 7;
|
||||
/// Ncr is the dead time between command and response; should be 0xff
|
||||
uint8_t ncr;
|
||||
/// Response data, should be set by host to 0xff for read operations
|
||||
uint8_t r1;
|
||||
/// Up to 16 bytes of response. Luckily, this is aligned on 4 byte boundary.
|
||||
uint32_t response[4];
|
||||
} sdspi_hw_cmd_t;
|
||||
|
||||
#define SDSPI_CMD_NORESP_SIZE 6 //!< Size of the command without any response
|
||||
#define SDSPI_CMD_R1_SIZE 8 //!< Size of the command with R1 response
|
||||
#define SDSPI_CMD_R2_SIZE 9 //!< Size of the command with R1b response
|
||||
#define SDSPI_CMD_R3_SIZE 12 //!< Size of the command with R3 response
|
||||
#define SDSPI_CMD_R7_SIZE 12 //!< Size of the command with R7 response
|
||||
|
||||
#define SDSPI_CMD_FLAG_DATA BIT(0) //!< Command has data transfer
|
||||
#define SDSPI_CMD_FLAG_WRITE BIT(1) //!< Data is written to the card
|
||||
#define SDSPI_CMD_FLAG_RSP_R1 BIT(2) //!< Response format R1 (1 byte)
|
||||
#define SDSPI_CMD_FLAG_RSP_R2 BIT(3) //!< Response format R2 (2 bytes)
|
||||
#define SDSPI_CMD_FLAG_RSP_R3 BIT(4) //!< Response format R3 (5 bytes)
|
||||
#define SDSPI_CMD_FLAG_RSP_R7 BIT(5) //!< Response format R7 (5 bytes)
|
||||
|
||||
#define SDSPI_MAX_DATA_LEN 512 //!< Max size of single block transfer
|
||||
|
||||
void make_hw_cmd(uint32_t opcode, uint32_t arg, sdspi_hw_cmd_t *hw_cmd);
|
||||
|
||||
esp_err_t sdspi_host_start_command(int slot, sdspi_hw_cmd_t *cmd,
|
||||
void *data, uint32_t data_size, int flags);
|
||||
122
components/driver/sdspi_transaction.c
Normal file
122
components/driver/sdspi_transaction.c
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "sys/lock.h"
|
||||
#include "soc/sdmmc_reg.h"
|
||||
#include "soc/sdmmc_struct.h"
|
||||
#include "driver/sdmmc_types.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "sdspi_private.h"
|
||||
#include "sdspi_crc.h"
|
||||
|
||||
static const char* TAG = "sdspi_transaction";
|
||||
|
||||
static _lock_t s_lock;
|
||||
static bool s_app_cmd;
|
||||
|
||||
static uint8_t sdspi_msg_crc7(sdspi_hw_cmd_t* hw_cmd)
|
||||
{
|
||||
const size_t bytes_to_crc = offsetof(sdspi_hw_cmd_t, arguments) +
|
||||
sizeof(hw_cmd->arguments); /* can't take address of bit fields */
|
||||
return sdspi_crc7((const uint8_t *)hw_cmd, bytes_to_crc);
|
||||
}
|
||||
|
||||
void make_hw_cmd(uint32_t opcode, uint32_t arg, sdspi_hw_cmd_t *hw_cmd)
|
||||
{
|
||||
hw_cmd->start_bit = 0;
|
||||
hw_cmd->transmission_bit = 1;
|
||||
hw_cmd->cmd_index = opcode;
|
||||
hw_cmd->stop_bit = 1;
|
||||
hw_cmd->r1 = 0xff;
|
||||
memset(hw_cmd->response, 0xff, sizeof(hw_cmd->response));
|
||||
hw_cmd->ncr = 0xff;
|
||||
uint32_t arg_s = __builtin_bswap32(arg);
|
||||
memcpy(hw_cmd->arguments, &arg_s, sizeof(arg_s));
|
||||
hw_cmd->crc7 = sdspi_msg_crc7(hw_cmd);
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_do_transaction(int slot, sdmmc_command_t *cmdinfo)
|
||||
{
|
||||
_lock_acquire(&s_lock);
|
||||
// Convert the command to wire format
|
||||
sdspi_hw_cmd_t hw_cmd;
|
||||
make_hw_cmd(cmdinfo->opcode, cmdinfo->arg, &hw_cmd);
|
||||
|
||||
// Flags indicate which of the transfer types should be used
|
||||
int flags = 0;
|
||||
if (SCF_CMD(cmdinfo->flags) == SCF_CMD_ADTC) {
|
||||
flags = SDSPI_CMD_FLAG_DATA | SDSPI_CMD_FLAG_WRITE;
|
||||
} else if (SCF_CMD(cmdinfo->flags) == (SCF_CMD_ADTC | SCF_CMD_READ)) {
|
||||
flags = SDSPI_CMD_FLAG_DATA;
|
||||
}
|
||||
|
||||
// In SD host, response format is encoded using SCF_RSP_* flags which come
|
||||
// as part of sdmmc_command_t from the upper layer (sdmmc_cmd.c).
|
||||
// SPI mode uses different command formats. In fact, most of the commands
|
||||
// use R1 response. Therefore, instead of adding another parallel set of
|
||||
// response flags for the SPI mode, response format is determined here:
|
||||
if (!s_app_cmd && cmdinfo->opcode == SD_SEND_IF_COND) {
|
||||
flags |= SDSPI_CMD_FLAG_RSP_R7;
|
||||
} else if (!s_app_cmd && cmdinfo->opcode == MMC_SEND_STATUS) {
|
||||
flags |= SDSPI_CMD_FLAG_RSP_R2;
|
||||
} else if (!s_app_cmd && cmdinfo->opcode == SD_READ_OCR) {
|
||||
flags |= SDSPI_CMD_FLAG_RSP_R3;
|
||||
} else if (s_app_cmd && cmdinfo->opcode == SD_APP_SD_STATUS) {
|
||||
flags |= SDSPI_CMD_FLAG_RSP_R2;
|
||||
} else {
|
||||
flags |= SDSPI_CMD_FLAG_RSP_R1;
|
||||
}
|
||||
|
||||
// Send the command and get the response.
|
||||
esp_err_t ret = sdspi_host_start_command(slot, &hw_cmd,
|
||||
cmdinfo->data, cmdinfo->datalen, flags);
|
||||
|
||||
// Extract response bytes and store them into cmdinfo structure
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGV(TAG, "r1 = 0x%02x hw_cmd.r[0]=0x%08x", hw_cmd.r1, hw_cmd.response[0]);
|
||||
// Some errors should be reported using return code
|
||||
if (flags & SDSPI_CMD_FLAG_RSP_R1) {
|
||||
cmdinfo->response[0] = hw_cmd.r1;
|
||||
if (hw_cmd.r1 == 0xff) {
|
||||
// No response received at all
|
||||
} else if (hw_cmd.r1 & SD_SPI_R1_CMD_CRC_ERR) {
|
||||
ret = ESP_ERR_INVALID_CRC;
|
||||
} else if (hw_cmd.r1 & SD_SPI_R1_IDLE_STATE) {
|
||||
// Idle state is handled at command layer
|
||||
} else if (hw_cmd.r1 != 0) {
|
||||
ESP_LOGD(TAG, "Unexpected R1 response: 0x%02x", hw_cmd.r1);
|
||||
}
|
||||
} else if (flags & SDSPI_CMD_FLAG_RSP_R2) {
|
||||
cmdinfo->response[0] = (((uint32_t)hw_cmd.r1) << 8) | (hw_cmd.response[0] >> 24);
|
||||
} else if (flags & (SDSPI_CMD_FLAG_RSP_R3 | SDSPI_CMD_FLAG_RSP_R7)) {
|
||||
// Drop r1 response, only copy the other 4 bytes of data
|
||||
// TODO: can we somehow preserve r1 response and keep upper layer
|
||||
// same as in SD mode?
|
||||
cmdinfo->response[0] = __builtin_bswap32(hw_cmd.response[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Save a flag whether the next command is expected to be an app command
|
||||
if (ret == ESP_OK) {
|
||||
s_app_cmd = (cmdinfo->opcode == MMC_APP_CMD);
|
||||
} else {
|
||||
s_app_cmd = false;
|
||||
}
|
||||
_lock_release(&s_lock);
|
||||
return ret;
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/sdmmc_types.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "ff.h"
|
||||
#include "wear_levelling.h"
|
||||
|
||||
@@ -98,9 +99,9 @@ typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t;
|
||||
* @brief Convenience function to get FAT filesystem on SD card registered in VFS
|
||||
*
|
||||
* This is an all-in-one function which does the following:
|
||||
* - initializes SD/MMC peripheral with configuration in host_config
|
||||
* - initializes SD/MMC card with configuration in slot_config
|
||||
* - mounts FAT partition on SD/MMC card using FATFS library, with configuration in mount_config
|
||||
* - initializes SDMMC driver or SPI driver with configuration in host_config
|
||||
* - initializes SD card with configuration in slot_config
|
||||
* - mounts FAT partition on SD card using FATFS library, with configuration in mount_config
|
||||
* - registers FATFS library with VFS, with prefix given by base_prefix variable
|
||||
*
|
||||
* This function is intended to make example code more compact.
|
||||
@@ -109,8 +110,16 @@ typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t;
|
||||
* with proper error checking and handling of exceptional conditions.
|
||||
*
|
||||
* @param base_path path where partition should be registered (e.g. "/sdcard")
|
||||
* @param host_config pointer to structure describing SDMMC host
|
||||
* @param slot_config pointer to structure with extra SDMMC slot configuration
|
||||
* @param host_config Pointer to structure describing SDMMC host. When using
|
||||
* SDMMC peripheral, this structure can be initialized using
|
||||
* SDMMC_HOST_DEFAULT() macro. When using SPI peripheral,
|
||||
* this structure can be initialized using SDSPI_HOST_DEFAULT()
|
||||
* macro.
|
||||
* @param slot_config Pointer to structure with slot configuration.
|
||||
* For SDMMC peripheral, pass a pointer to sdmmc_slot_config_t
|
||||
* structure initialized using SDMMC_SLOT_CONFIG_DEFAULT.
|
||||
* For SPI peripheral, pass a pointer to sdspi_slot_config_t
|
||||
* structure initialized using SDSPI_SLOT_CONFIG_DEFAULT.
|
||||
* @param mount_config pointer to structure with extra parameters for mounting FATFS
|
||||
* @param[out] out_card if not NULL, pointer to the card information structure will be returned via this argument
|
||||
* @return
|
||||
@@ -118,11 +127,11 @@ typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t;
|
||||
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called
|
||||
* - ESP_ERR_NO_MEM if memory can not be allocated
|
||||
* - ESP_FAIL if partition can not be mounted
|
||||
* - other error codes from SDMMC host, SDMMC protocol, or FATFS drivers
|
||||
* - other error codes from SDMMC or SPI drivers, SDMMC protocol, or FATFS drivers
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config,
|
||||
const sdmmc_slot_config_t* slot_config,
|
||||
const void* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "diskio.h"
|
||||
|
||||
@@ -28,8 +29,8 @@ static char * s_base_path = NULL;
|
||||
|
||||
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config,
|
||||
const sdmmc_slot_config_t* slot_config,
|
||||
const esp_vfs_fat_sdmmc_mount_config_t* mount_config,
|
||||
const void* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card)
|
||||
{
|
||||
const size_t workbuf_size = 4096;
|
||||
@@ -52,22 +53,32 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
ESP_LOGD(TAG, "could not copy base_path");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// enable SDMMC
|
||||
sdmmc_host_init();
|
||||
|
||||
// enable card slot
|
||||
esp_err_t err = sdmmc_host_init_slot(host_config->slot, slot_config);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t err = ESP_OK;
|
||||
s_card = malloc(sizeof(sdmmc_card_t));
|
||||
if (s_card == NULL) {
|
||||
err = ESP_ERR_NO_MEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
err = (*host_config->init)();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGD(TAG, "host init returned rc=0x%x", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// configure SD slot
|
||||
if (host_config->flags == SDMMC_HOST_FLAG_SPI) {
|
||||
err = sdspi_host_init_slot(host_config->slot,
|
||||
(const sdspi_slot_config_t*) slot_config);
|
||||
} else {
|
||||
err = sdmmc_host_init_slot(host_config->slot,
|
||||
(const sdmmc_slot_config_t*) slot_config);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGD(TAG, "slot_config returned rc=0x%x", err);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// probe and initialize card
|
||||
err = sdmmc_card_init(host_config, s_card);
|
||||
if (err != ESP_OK) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "unity.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "esp_log.h"
|
||||
@@ -30,16 +31,29 @@ TEST_CASE("can probe SD", "[sd][ignore]")
|
||||
{
|
||||
sdmmc_host_t config = SDMMC_HOST_DEFAULT();
|
||||
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
sdmmc_host_init();
|
||||
sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config);
|
||||
TEST_ESP_OK(sdmmc_host_init());
|
||||
TEST_ESP_OK(sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config));
|
||||
sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
|
||||
TEST_ASSERT_NOT_NULL(card);
|
||||
TEST_ESP_OK(sdmmc_card_init(&config, card));
|
||||
sdmmc_card_print_info(stdout, card);
|
||||
sdmmc_host_deinit();
|
||||
TEST_ESP_OK(sdmmc_host_deinit());
|
||||
free(card);
|
||||
}
|
||||
|
||||
TEST_CASE("can probe SD (using SPI)", "[sdspi][ignore]")
|
||||
{
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(sdspi_host_init());
|
||||
TEST_ESP_OK(sdspi_host_init_slot(config.slot, &slot_config));
|
||||
sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
|
||||
TEST_ASSERT_NOT_NULL(card);
|
||||
TEST_ESP_OK(sdmmc_card_init(&config, card));
|
||||
sdmmc_card_print_info(stdout, card);
|
||||
TEST_ESP_OK(sdspi_host_deinit());
|
||||
free(card);
|
||||
}
|
||||
|
||||
static void do_single_write_read_test(sdmmc_card_t* card,
|
||||
size_t start_block, size_t block_count)
|
||||
@@ -66,7 +80,7 @@ static void do_single_write_read_test(sdmmc_card_t* card,
|
||||
gettimeofday(&t_stop_rd, NULL);
|
||||
float time_rd = 1e3f * (t_stop_rd.tv_sec - t_start_rd.tv_sec) + 1e-3f * (t_stop_rd.tv_usec - t_start_rd.tv_usec);
|
||||
|
||||
printf(" | %6.2f | %.2f | %.2fs | %.2f\n",
|
||||
printf(" | %6.2f | %.2f | %.2f | %.2f\n",
|
||||
time_wr, total_size / (time_wr / 1000) / (1024 * 1024),
|
||||
time_rd, total_size / (time_rd / 1000) / (1024 * 1024));
|
||||
srand(start_block);
|
||||
@@ -76,16 +90,8 @@ static void do_single_write_read_test(sdmmc_card_t* card,
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
TEST_CASE("can write and read back blocks", "[sd][ignore]")
|
||||
static void read_write_test(sdmmc_card_t* card)
|
||||
{
|
||||
sdmmc_host_t config = SDMMC_HOST_DEFAULT();
|
||||
config.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
|
||||
sdmmc_host_init();
|
||||
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config);
|
||||
sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
|
||||
TEST_ASSERT_NOT_NULL(card);
|
||||
TEST_ESP_OK(sdmmc_card_init(&config, card));
|
||||
sdmmc_card_print_info(stdout, card);
|
||||
printf(" sector | count | size(kB) | wr_time(ms) | wr_speed(MB/s) | rd_time(ms) | rd_speed(MB/s)\n");
|
||||
do_single_write_read_test(card, 0, 1);
|
||||
@@ -104,6 +110,35 @@ TEST_CASE("can write and read back blocks", "[sd][ignore]")
|
||||
do_single_write_read_test(card, card->csd.capacity/2, 32);
|
||||
do_single_write_read_test(card, card->csd.capacity/2, 64);
|
||||
do_single_write_read_test(card, card->csd.capacity/2, 128);
|
||||
free(card);
|
||||
sdmmc_host_deinit();
|
||||
}
|
||||
|
||||
TEST_CASE("can write and read back blocks", "[sd][ignore]")
|
||||
{
|
||||
sdmmc_host_t config = SDMMC_HOST_DEFAULT();
|
||||
config.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
|
||||
TEST_ESP_OK(sdmmc_host_init());
|
||||
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config));
|
||||
sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
|
||||
TEST_ASSERT_NOT_NULL(card);
|
||||
TEST_ESP_OK(sdmmc_card_init(&config, card));
|
||||
read_write_test(card);
|
||||
free(card);
|
||||
TEST_ESP_OK(sdmmc_host_deinit());
|
||||
}
|
||||
|
||||
TEST_CASE("can write and read back blocks (using SPI)", "[sdspi][ignore]")
|
||||
{
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
config.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
|
||||
sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
|
||||
TEST_ESP_OK(sdspi_host_init());
|
||||
TEST_ESP_OK(sdspi_host_init_slot(config.slot, &slot_config));
|
||||
sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
|
||||
TEST_ASSERT_NOT_NULL(card);
|
||||
TEST_ESP_OK(sdmmc_card_init(&config, card));
|
||||
read_write_test(card);
|
||||
free(card);
|
||||
TEST_ESP_OK(sdspi_host_deinit());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user