1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/esp32/periph/sdmmc.c
2024-01-05 07:22:27 +01:00

482 lines
14 KiB
C

/*
* Copyright (C) 2023 Gunar Schorcht
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_esp32
* @{
*
* @file
* @brief Low-level SDIO/SD/MMC peripheral driver interface for ESP32
*
* @author Gunar Schorcht <gunar@schorcht.net>
*
* @}
*/
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "assert.h"
#include "bitarithm.h"
#include "container.h"
#include "log.h"
#include "periph/gpio.h"
#include "syscalls.h"
#include "ztimer.h"
#include "sdmmc/sdmmc.h"
#include "driver/sdmmc_types.h"
#include "driver/sdmmc_host.h"
#include "soc/sdmmc_reg.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/* CLK_EDGE_SEL - clock phase selection register */
#define SDMMC_CLOCK_REG_CCLKIN_EDGE_SAM_SEL_S (3)
#define SDMMC_CLOCK_REG_CCLKIN_EDGE_SAM_SEL_M (0x7 << SDMMC_CLOCK_REG_CCLKIN_EDGE_SAM_SEL_S)
/* we have to redefine it here since we can't include "gpio_types.h" due to
* naming conflicts */
#define GPIO_NUM_NC (GPIO_UNDEF)
/* debounce time for CD pin */
#define CONFIG_CD_PIN_DEBOUNCE_US 25000
/* limit the Default and High Speed clock rates for debugging */
#if CONFIG_SDMMC_CLK_MAX_400KHZ
#define CONFIG_SDMMC_CLK_MAX KHZ(400)
#elif CONFIG_SDMMC_CLK_MAX_1MHZ
#define CONFIG_SDMMC_CLK_MAX MHZ(1)
#elif CONFIG_SDMMC_CLK_MAX_4MHZ
#define CONFIG_SDMMC_CLK_MAX MHZ(4)
#elif CONFIG_SDMMC_CLK_MAX_10MHZ
#define CONFIG_SDMMC_CLK_MAX MHZ(10)
#elif CONFIG_SDMMC_CLK_MAX_20MHZ || !IS_USED(MODULE_PERIPH_SDMMC_HS)
#define CONFIG_SDMMC_CLK_MAX MHZ(20)
#else
#define CONFIG_SDMMC_CLK_MAX MHZ(40)
#endif
/* millisecond timer definitions dependent on active ztimer backend */
#if IS_USED(MODULE_ZTIMER_MSEC)
#define _ZTIMER_SLEEP_MS(n) ztimer_sleep(ZTIMER_MSEC, n)
#elif IS_USED(MODULE_ZTIMER_USEC)
#define _ZTIMER_SLEEP_MS(n) ztimer_sleep(ZTIMER_USEC, n * US_PER_MS)
#else
#error "Either ztimer_msec or ztimer_usec is needed"
#endif
/* forward declaration of _driver */
static const sdmmc_driver_t _driver;
/* driver related */
typedef struct {
sdmmc_dev_t sdmmc_dev; /**< Inherited sdmmc_dev_t struct */
const sdmmc_conf_t *config; /**< SDIO/SD/MMC peripheral config */
uint32_t last_cd_pin_irq; /**< Last CD Pin IRQ time for debouncing */
bool data_transfer; /**< Transfer active */
} esp32_sdmmc_dev_t;
static esp32_sdmmc_dev_t _sdmmc_devs[] = {
{
.sdmmc_dev = {
.driver = &_driver,
},
.config = &sdmmc_config[0],
},
#if SDMMC_CONFIG_NUMOF == 2
{
.sdmmc_dev = {
.driver = &_driver,
},
.config = &sdmmc_config[1],
}
#endif
};
/* sanity check of configuration */
static_assert(SDMMC_CONFIG_NUMOF == ARRAY_SIZE(sdmmc_config),
"SDMMC_CONFIG_NUMOF and the number of elements in sdmmc_config differ");
static_assert(SDMMC_CONFIG_NUMOF == ARRAY_SIZE(_sdmmc_devs),
"SDMMC_CONFIG_NUMOF and the number of elements in sdmmc_devs differ");
static_assert(SDMMC_CONFIG_NUMOF <= SOC_SDMMC_NUM_SLOTS,
"SDMMC_CONFIG_NUMOF is greater than the supported number of slots");
XFA_CONST(sdmmc_devs, 0) sdmmc_dev_t * const _sdmmc_0 = (sdmmc_dev_t * const)&_sdmmc_devs[0];
#if SDMMC_CONFIG_NUMOF > 1
XFA_CONST(sdmmc_devs, 0) sdmmc_dev_t * const _sdmmc_1 = (sdmmc_dev_t * const)&_sdmmc_devs[1];
#endif
/* forward declaration of internal functions */
static int _esp_err_to_sdmmc_err_code(esp_err_t code);
static void _isr_cd_pin(void *arg);
static void _init(sdmmc_dev_t *sdmmc_dev)
{
esp32_sdmmc_dev_t *dev = container_of(sdmmc_dev, esp32_sdmmc_dev_t, sdmmc_dev);
assert(dev);
const sdmmc_conf_t *conf = dev->config;
assert(conf);
/* additional sanity checks */
assert(conf->slot < SOC_SDMMC_NUM_SLOTS);
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
#if IS_USED(CPU_FAM_ESP32)
/* On ESP32 only Slot 1 can be used */
assert(conf->slot == SDMMC_SLOT_1);
/* Slot 1 has only 4 data lines */
assert((conf->bus_width == 1) || (conf->bus_width == 4));
sdmmc_dev->bus_width = conf->bus_width;
#elif IS_USED(CPU_FAM_ESP32S3)
assert(gpio_is_valid(conf->clk) && !gpio_is_equal(conf->clk, GPIO0));
assert(gpio_is_valid(conf->cmd) && !gpio_is_equal(conf->cmd, GPIO0));
assert(gpio_is_valid(conf->dat0) && !gpio_is_equal(conf->dat0, GPIO0));
/* TODO Check for collision with Flash GPIOs */
slot_config.clk = conf->clk;
slot_config.cmd = conf->cmd;
slot_config.d0 = conf->dat0;
sdmmc_dev->bus_width = SDMMC_BUS_WIDTH_1BIT;
slot_config.d1 = GPIO_UNDEF;
slot_config.d2 = GPIO_UNDEF;
slot_config.d3 = GPIO_UNDEF;
slot_config.d4 = GPIO_UNDEF;
slot_config.d5 = GPIO_UNDEF;
slot_config.d6 = GPIO_UNDEF;
slot_config.d7 = GPIO_UNDEF;
if (gpio_is_valid(conf->dat1) && !gpio_is_equal(conf->dat1, GPIO0) &&
gpio_is_valid(conf->dat2) && !gpio_is_equal(conf->dat2, GPIO0) &&
gpio_is_valid(conf->dat3) && !gpio_is_equal(conf->dat3, GPIO0)) {
slot_config.d1 = conf->dat1;
slot_config.d2 = conf->dat2;
slot_config.d3 = conf->dat3;
sdmmc_dev->bus_width = SDMMC_BUS_WIDTH_4BIT;
#if IS_USED(MODULE_PERIPH_SDMMC_8BIT)
if (gpio_is_valid(conf->dat4) && !gpio_is_equal(conf->dat4, GPIO0) &&
gpio_is_valid(conf->dat5) && !gpio_is_equal(conf->dat5, GPIO0) &&
gpio_is_valid(conf->dat6) && !gpio_is_equal(conf->dat6, GPIO0) &&
gpio_is_valid(conf->dat7) && !gpio_is_equal(conf->dat7, GPIO0)) {
slot_config.d4 = conf->dat4;
slot_config.d5 = conf->dat5;
slot_config.d6 = conf->dat6;
slot_config.d7 = conf->dat7;
sdmmc_dev->bus_width = SDMMC_BUS_WIDTH_8BIT;
}
#endif
}
#else
#error "ESP32x variant not supported"
#endif
#if IS_USED(CONFIG_SDMMC_INTERNAL_PULLUP)
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
#endif
slot_config.width = sdmmc_dev->bus_width;
dev->data_transfer = false;
esp_err_t res;
if ((res = sdmmc_host_init())) {
LOG_ERROR("[sdmmc] Could not initialize SDMMC host controller\n");
assert(false);
}
if ((res = sdmmc_host_init_slot(dev->config->slot, &slot_config))) {
LOG_ERROR("[sdmmc] Could not initialize SDMMC slot\n");
assert(false);
}
if (gpio_is_valid(conf->cd)) {
dev->last_cd_pin_irq = system_get_time();
gpio_init_int(conf->cd, GPIO_IN, GPIO_BOTH, _isr_cd_pin, sdmmc_dev);
sdmmc_dev->present = gpio_read(conf->cd) == 0;
}
else {
sdmmc_dev->present = true;
}
sdmmc_dev->bus_width = SDMMC_BUS_WIDTH_1BIT; // SDMMC_BUS_WIDTH_4BIT;
}
static int _send_cmd(sdmmc_dev_t *sdmmc_dev, sdmmc_cmd_t cmd_idx, uint32_t arg,
sdmmc_resp_t resp_type, uint32_t *resp)
{
/* to ensure that `sdmmc_send_acmd` is used for application specific commands */
assert((cmd_idx & SDMMC_ACMD_PREFIX) == 0);
esp32_sdmmc_dev_t *dev = container_of(sdmmc_dev, esp32_sdmmc_dev_t, sdmmc_dev);
assert(dev);
assert(dev->config);
if (dev->data_transfer) {
/* data transfer command is issued in _xfer_execute as one transaction
* together with data phase */
return 0;
}
sdmmc_command_t cmd = {
.opcode =cmd_idx,
.flags = 0,
.arg = arg,
.data = 0,
.datalen = 0,
.blklen = 0,
.timeout_ms = 100,
};
switch (resp_type) {
case SDMMC_R1:
cmd.flags |= SCF_RSP_R1;
break;
case SDMMC_R1B:
cmd.flags |= SCF_RSP_R1B;
break;
case SDMMC_R2:
cmd.flags |= SCF_RSP_R2;
break;
case SDMMC_R3:
cmd.flags |= SCF_RSP_R3;
break;
case SDMMC_R4:
cmd.flags |= SCF_RSP_R4;
break;
case SDMMC_R5:
cmd.flags |= SCF_RSP_R5;
break;
case SDMMC_R6:
cmd.flags |= SCF_RSP_R7;
break;
case SDMMC_R7:
cmd.flags |= SCF_RSP_R7;
break;
default:
break;
}
esp_err_t res = sdmmc_host_do_transaction(dev->config->slot, &cmd);
if (res) {
return _esp_err_to_sdmmc_err_code(res);
}
else if (cmd.error) {
return _esp_err_to_sdmmc_err_code(cmd.error);
}
if ((resp_type == SDMMC_R1) || (resp_type == SDMMC_R1B)) {
sdmmc_dev->status = cmd.response[0];
}
if (resp) {
if (resp_type == SDMMC_R2) {
resp[0] = cmd.response[3];
resp[1] = cmd.response[2];
resp[2] = cmd.response[1];
resp[3] = cmd.response[0];
}
else if (resp_type != SDMMC_NO_R) {
resp[0] = cmd.response[0];
}
}
return 0;
}
static int _set_bus_width(sdmmc_dev_t *sdmmc_dev, sdmmc_bus_width_t width)
{
DEBUG("[sdmmc] %s width=%d\n", __func__, width);
esp32_sdmmc_dev_t *dev = container_of(sdmmc_dev, esp32_sdmmc_dev_t, sdmmc_dev);
assert(dev);
esp_err_t res = sdmmc_host_set_bus_width(dev->config->slot, width);
if (res) {
return _esp_err_to_sdmmc_err_code(res);
}
return 0;
}
static int _set_clock_rate(sdmmc_dev_t *sdmmc_dev, sdmmc_clock_rate_t rate)
{
DEBUG("[sdmmc] %s rate=%"PRIu32" ", __func__, (uint32_t)rate);
esp32_sdmmc_dev_t *dev = container_of(sdmmc_dev, esp32_sdmmc_dev_t, sdmmc_dev);
assert(dev);
if (rate > CONFIG_SDMMC_CLK_MAX) {
rate = CONFIG_SDMMC_CLK_MAX;
}
DEBUG("actual_rate=%"PRIu32"\n", (uint32_t)rate);
esp_err_t res = sdmmc_host_set_card_clk(dev->config->slot, rate / KHZ(1));
if (res) {
return _esp_err_to_sdmmc_err_code(res);
}
#if SOC_SDMMC_USE_GPIO_MATRIX
/* phase has to be modified to get it working for MMCs if
* SOC_SDMMC_USE_GPIO_MATRIX is used */
uint32_t reg = *((uint32_t *)SDMMC_CLOCK_REG);
reg &= ~SDMMC_CLOCK_REG_CCLKIN_EDGE_SAM_SEL_M;
reg |= (6 << SDMMC_CLOCK_REG_CCLKIN_EDGE_SAM_SEL_S);
*((uint32_t *)SDMMC_CLOCK_REG) = reg;
#endif
return 0;
}
static int _xfer_prepare(sdmmc_dev_t *sdmmc_dev, sdmmc_xfer_desc_t *xfer)
{
esp32_sdmmc_dev_t *dev = container_of(sdmmc_dev, esp32_sdmmc_dev_t, sdmmc_dev);
assert(dev);
assert(dev->config);
/* SDIO/SD/MMC uses 32-bit words */
/* TODO: at the moment only 32-bit words supported */
assert((xfer->block_size % sizeof(uint32_t)) == 0);
dev->data_transfer = true;
return 0;
}
static int _xfer_execute(sdmmc_dev_t *sdmmc_dev, sdmmc_xfer_desc_t *xfer,
const void *data_wr, void *data_rd,
uint16_t *done)
{
assert(xfer);
assert((xfer->write && data_wr) || (!xfer->write && data_rd));
/* check the alignment required for the buffers */
assert(HAS_ALIGNMENT_OF(data_wr, SDMMC_CPU_DMA_ALIGNMENT));
assert(HAS_ALIGNMENT_OF(data_rd, SDMMC_CPU_DMA_ALIGNMENT));
esp32_sdmmc_dev_t *dev = container_of(sdmmc_dev, esp32_sdmmc_dev_t, sdmmc_dev);
assert(dev);
assert(dev->config);
sdmmc_command_t cmd = {
.opcode = xfer->cmd_idx & ~SDMMC_ACMD_PREFIX,
.flags = SCF_RSP_R1 | (xfer->write ? 0 : SCF_CMD_READ),
.arg = xfer->arg,
.data = xfer->write ? (void *)data_wr : data_rd,
.datalen = xfer->block_num * xfer->block_size,
.blklen = xfer->block_size,
.timeout_ms = xfer->write ? 2500 : 1000, // TODO
};
if (done) {
*done = 0;
}
esp_err_t res = sdmmc_host_do_transaction(dev->config->slot, &cmd);
if (res) {
return _esp_err_to_sdmmc_err_code(res);
}
else if (cmd.error) {
return _esp_err_to_sdmmc_err_code(cmd.error);
}
if (done) {
*done = xfer->block_num;
}
return 0;
}
static int _xfer_finish(sdmmc_dev_t *sdmmc_dev, sdmmc_xfer_desc_t *xfer)
{
(void)xfer;
esp32_sdmmc_dev_t *dev = container_of(sdmmc_dev, esp32_sdmmc_dev_t, sdmmc_dev);
dev->data_transfer = false;
return 0;
}
static int _esp_err_to_sdmmc_err_code(esp_err_t error)
{
switch (error) {
case ESP_ERR_TIMEOUT:
DEBUG("[sdmmc] Timeout error\n");
return -ETIMEDOUT;
case ESP_ERR_INVALID_CRC:
DEBUG("[sdmmc] CRC error\n");
return -EBADMSG;
case ESP_ERR_INVALID_RESPONSE:
DEBUG("[sdmmc] Invalid response\n");
return -EIO;
case ESP_ERR_INVALID_SIZE:
DEBUG("[sdmmc] Invalid size\n");
return -EIO;
case ESP_ERR_INVALID_ARG:
DEBUG("[sdmmc] Invalid argument\n");
return -EIO;
default:
DEBUG("[sdmmc] Other error\n");
return -EIO;
}
}
static void _isr_cd_pin(void *arg)
{
uint32_t state = irq_disable();
esp32_sdmmc_dev_t *dev = arg;
assert(dev);
/* for debouncing handle only the first CD Pin interrupts and ignore further
* interrupts that happen within the debouncing time interval */
if ((system_get_time() - dev->last_cd_pin_irq) > CONFIG_CD_PIN_DEBOUNCE_US) {
dev->last_cd_pin_irq = system_get_time();
sdmmc_dev_t *sdmmc_dev = &dev->sdmmc_dev;
sdmmc_dev->present = !sdmmc_dev->present;
sdmmc_dev->init_done = false;
if (sdmmc_dev->event_cb) {
sdmmc_dev->event_cb(sdmmc_dev,
sdmmc_dev->present ? SDMMC_EVENT_CARD_INSERTED
: SDMMC_EVENT_CARD_REMOVED);
}
}
irq_restore(state);
}
static const sdmmc_driver_t _driver = {
.init = _init,
.card_init = NULL, /* no own card init function */
.send_cmd = _send_cmd,
.set_bus_width = _set_bus_width,
.set_clock_rate = _set_clock_rate,
.xfer_prepare = _xfer_prepare,
.xfer_execute = _xfer_execute,
.xfer_finish = _xfer_finish,
};