blob: e64347710859c35269a04b94385df7f28ad20e11 [file] [log] [blame]
/****************************************************************************
* arch/xtensa/src/esp32s3/esp32s3_sdmmc.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <nuttx/arch.h>
#include <nuttx/wdog.h>
#include <nuttx/clock.h>
#include <nuttx/sdio.h>
#include <nuttx/wqueue.h>
#include <nuttx/semaphore.h>
#include <nuttx/mmcsd.h>
#if defined(CONFIG_ESP32S3_SDMMC_DMA) && defined(CONFIG_ESP32S3_SPIRAM)
#include <nuttx/kmalloc.h>
#endif
#include "xtensa.h"
#include "esp32s3_gpio.h"
#include "esp32s3_irq.h"
#include "hardware/esp32s3_sdmmc.h"
#include "hardware/esp32s3_system.h"
#include "hardware/esp32s3_gpio_sigmap.h"
#include "hardware/esp32s3_soc.h"
#ifdef CONFIG_ESP32S3_SDMMC
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define MCI_DMADES0_OWN (1UL << 31)
#define MCI_DMADES0_CH (1 << 4)
#define MCI_DMADES0_FS (1 << 3)
#define MCI_DMADES0_LD (1 << 2)
#define MCI_DMADES0_DIC (1 << 1)
#define MCI_DMADES1_MAXTR 4096
#define MCI_DMADES1_BS1(x) (x)
#define GPIO_MATRIX_CONST_ONE_INPUT (0x38)
#define GPIO_MATRIX_CONST_ZERO_INPUT (0x3C)
/* Configuration ************************************************************/
/* Required system configuration options:
*
* CONFIG_ARCH_DMA - Enable architecture-specific DMA subsystem
* initialization. Required if CONFIG_ESP32S3_SDMMC_DMA is enabled.
* CONFIG_ESP32S3_DMA2 - Enable ESP32S3 DMA2 support. Required if
* CONFIG_ESP32S3_SDMMC_DMA is enabled
* CONFIG_SCHED_WORKQUEUE -- Callback support requires work queue support.
*
* Driver-specific configuration options:
*
* CONFIG_SDIO_MUXBUS - Setting this configuration enables some locking
* APIs to manage concurrent accesses on the SDIO bus. This is not
* needed for the simple case of a single SD card, for example.
* CONFIG_ESP32S3_SDMMC_DMA - Enable SDIO. This is a marginally optional.
* For most usages, SDIO will cause data overruns if used without DMA.
* NOTE the above system DMA configuration options.
* CONFIG_SDIO_WIDTH_D1_ONLY - This may be selected to force the
* driver operate with only a single data line (the default is to use
* all 4 SD data lines).
* CONFIG_SDIO_XFRDEBUG - Enables some very low-level debug output
* This also requires CONFIG_DEBUG_FS and CONFIG_DEBUG_INFO
*/
/* Timing : 100mS short timeout, 2 seconds for long one */
#define SDCARD_CMDTIMEOUT MSEC2TICK(100)
#define SDCARD_LONGTIMEOUT MSEC2TICK(2000)
/* FIFO size in bytes */
#define ESP32S3_TXFIFO_SIZE (ESP32S3_TXFIFO_DEPTH | ESP32S3_TXFIFO_WIDTH)
#define ESP32S3_RXFIFO_SIZE (ESP32S3_RXFIFO_DEPTH | ESP32S3_RXFIFO_WIDTH)
/* Number of DMA Descriptors */
#define ESP32S3_MULTIBLOCK_LIMIT 128
#define NUM_DMA_DESCRIPTORS (1 + (ESP32S3_MULTIBLOCK_LIMIT * 512 / MCI_DMADES1_MAXTR))
#if (CONFIG_MMCSD_MULTIBLOCK_LIMIT == 0 || \
CONFIG_MMCSD_MULTIBLOCK_LIMIT > ESP32S3_MULTIBLOCK_LIMIT)
#error "CONFIG_MMCSD_MULTIBLOCK_LIMIT is too big"
#endif
/* Data transfer interrupt mask bits */
#define SDCARD_RECV_MASK (SDMMC_INT_DTO | SDMMC_INT_DCRC | SDMMC_INT_DRTO | \
SDMMC_INT_EBE | SDMMC_INT_RXDR | SDMMC_INT_SBE)
#define SDCARD_SEND_MASK (SDMMC_INT_DTO | SDMMC_INT_DCRC | SDMMC_INT_DRTO | \
SDMMC_INT_EBE | SDMMC_INT_TXDR | SDMMC_INT_SBE)
#define SDCARD_DMARECV_MASK (SDMMC_INT_DTO | SDMMC_INT_DCRC | SDMMC_INT_DRTO | \
SDMMC_INT_SBE | SDMMC_INT_EBE)
#define SDCARD_DMASEND_MASK (SDMMC_INT_DTO | SDMMC_INT_DCRC | SDMMC_INT_DRTO | \
SDMMC_INT_EBE)
#define SDCARD_DMAERROR_MASK (SDMMC_IDINTEN_FBE | SDMMC_IDINTEN_DU | \
SDMMC_IDINTEN_AIS)
#define SDCARD_TRANSFER_ALL (SDMMC_INT_DTO | SDMMC_INT_DCRC | SDMMC_INT_DRTO | \
SDMMC_INT_EBE | SDMMC_INT_TXDR | SDMMC_INT_RXDR | \
SDMMC_INT_SBE)
/* Event waiting interrupt mask bits */
#define SDCARD_INT_RESPERR (SDMMC_INT_RE | SDMMC_INT_RCRC | SDMMC_INT_RTO)
#ifdef CONFIG_MMCSD_HAVE_CARDDETECT
# define SDCARD_INT_CDET SDMMC_INT_CDET
#else
# define SDCARD_INT_CDET 0
#endif
#define SDCARD_CMDDONE_STA (SDMMC_INT_CDONE)
#define SDCARD_RESPDONE_STA (0)
#define SDCARD_CMDDONE_MASK (SDMMC_INT_CDONE)
#define SDCARD_RESPDONE_MASK (SDMMC_INT_CDONE | SDCARD_INT_RESPERR)
#define SDCARD_XFRDONE_MASK (0) /* Handled by transfer masks */
#define SDCARD_CMDDONE_CLEAR (SDMMC_INT_CDONE)
#define SDCARD_RESPDONE_CLEAR (SDMMC_INT_CDONE | SDCARD_INT_RESPERR)
#define SDCARD_XFRDONE_CLEAR (SDCARD_TRANSFER_ALL)
#define SDCARD_WAITALL_CLEAR (SDCARD_CMDDONE_CLEAR | SDCARD_RESPDONE_CLEAR | \
SDCARD_XFRDONE_CLEAR)
/* Let's wait until we have both SD card transfer complete and DMA
* complete.
*/
#define SDCARD_XFRDONE_FLAG (1)
#define SDCARD_DMADONE_FLAG (2)
#define SDCARD_ALLDONE (3)
#define BOARD_SDMMC_FREQUENCY (160 * 1000 * 1000)
/****************************************************************************
* Private Types
****************************************************************************/
struct sdmmc_dma_s
{
volatile uint32_t des0; /* Control and status */
volatile uint32_t des1; /* Buffer size(s) */
volatile uint32_t des2; /* Buffer address pointer 1 */
volatile uint32_t des3; /* Buffer address pointer 2 */
};
/**
* This structure lists pin numbers (if SOC_SDMMC_USE_IOMUX is set)
* or GPIO Matrix signal numbers (if SOC_SDMMC_USE_GPIO_MATRIX is set)
* for the SD bus signals. Field names match SD bus signal names.
*/
typedef struct
{
uint8_t clk;
uint8_t cmd;
uint8_t d0;
uint8_t d1;
uint8_t d2;
uint8_t d3;
uint8_t d4;
uint8_t d5;
uint8_t d6;
uint8_t d7;
} sdmmc_slot_io_info_t;
/**
* Common SDMMC slot info,
* doesn't depend on SOC_SDMMC_USE_{IOMUX,GPIO_MATRIX}
*/
typedef struct
{
uint8_t card_detect; /* !< Card detect signal in GPIO Matrix */
uint8_t write_protect; /* !< Write protect signal in GPIO Matrix */
uint8_t card_int; /* !< Card interrupt signal in GPIO Matrix */
} sdmmc_slot_info_t;
/* This structure defines the state of the ESP32S3 SDIO interface */
struct esp32s3_dev_s
{
struct sdio_dev_s dev; /* Standard, base SDIO interface */
/* ESP32S3-specific extensions */
/* Event support */
sem_t waitsem; /* Implements event waiting */
sdio_eventset_t waitevents; /* Set of events to be waited for */
uint32_t waitmask; /* Interrupt enables for event waiting */
volatile sdio_eventset_t wkupevent; /* The event that caused the wakeup */
struct wdog_s waitwdog; /* Watchdog that handles event timeouts */
/* Callback support */
sdio_statset_t cdstatus; /* Card status */
sdio_eventset_t cbevents; /* Set of events to be cause callbacks */
worker_t callback; /* Registered callback function */
void *cbarg; /* Registered callback argument */
struct work_s cbwork; /* Callback work queue structure */
/* Interrupt mode data transfer support */
uint32_t *buffer; /* Address of current R/W buffer */
size_t remaining; /* Number of bytes remaining in the transfer */
uint32_t xfrmask; /* Interrupt enables for data transfer */
#ifdef CONFIG_ESP32S3_SDMMC_DMA
uint32_t dmamask; /* Interrupt enables for DMA transfer */
volatile struct sdmmc_dma_s dma_desc[NUM_DMA_DESCRIPTORS];
#ifdef CONFIG_ESP32S3_SPIRAM
uint8_t *dma_buf;
size_t dma_buf_size;
#endif
#endif
bool wrdir; /* True: Writing False: Reading */
/* DMA data transfer support */
int slot;
const sdmmc_slot_io_info_t *sdio_pins;
const sdmmc_slot_info_t *slot_info;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* #define CONFIG_ESP32S3_SDMMC_REGDEBUG */
#ifdef CONFIG_ESP32S3_SDMMC_REGDEBUG
static uint32_t __esp32s3_getreg(const char *func, uint32_t addr);
static void __esp32s3_putreg(const char *func, uint32_t val, uint32_t addr);
# define esp32s3_getreg(addr) __esp32s3_getreg(__func__, addr)
# define esp32s3_putreg(val,addr) __esp32s3_putreg(__func__, val,addr)
#else
# define esp32s3_getreg(addr) getreg32(addr)
# define esp32s3_putreg(val,addr) putreg32(val,addr)
#endif
/* Low-level helpers ********************************************************/
/* DMA Helpers **************************************************************/
/* Data Transfer Helpers ****************************************************/
static void esp32s3_eventtimeout(wdparm_t arg);
static void esp32s3_endwait(struct esp32s3_dev_s *priv,
sdio_eventset_t wkupevent);
static void esp32s3_endtransfer(struct esp32s3_dev_s *priv,
sdio_eventset_t wkupevent);
/* Interrupt Handling *******************************************************/
static int esp32s3_interrupt(int irq, void *context, void *arg);
/* SDIO interface methods ***************************************************/
/* Mutual exclusion */
#ifdef CONFIG_SDIO_MUXBUS
static int esp32s3_lock(struct sdio_dev_s *dev, bool lock);
#endif
/* Initialization/setup */
static void esp32s3_reset(struct sdio_dev_s *dev);
static sdio_capset_t esp32s3_capabilities(struct sdio_dev_s *dev);
static sdio_statset_t esp32s3_status(struct sdio_dev_s *dev);
static void esp32s3_widebus(struct sdio_dev_s *dev, bool enable);
static void esp32s3_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate);
static int esp32s3_attach(struct sdio_dev_s *dev);
/* Command/Status/Data Transfer */
static int esp32s3_sendcmd(struct sdio_dev_s *dev, uint32_t cmd,
uint32_t arg);
#ifdef CONFIG_SDIO_BLOCKSETUP
static void esp32s3_blocksetup(struct sdio_dev_s *dev, unsigned int blocklen,
unsigned int nblocks);
#endif
static int esp32s3_recvsetup(struct sdio_dev_s *dev, uint8_t *buffer,
size_t nbytes);
static int esp32s3_sendsetup(struct sdio_dev_s *dev, const uint8_t *buffer,
size_t nbytes);
static int esp32s3_cancel(struct sdio_dev_s *dev);
static int esp32s3_waitresponse(struct sdio_dev_s *dev, uint32_t cmd);
static int esp32s3_recvshortcrc(struct sdio_dev_s *dev, uint32_t cmd,
uint32_t *rshort);
static int esp32s3_recvlong(struct sdio_dev_s *dev, uint32_t cmd,
uint32_t rlong[4]);
static int esp32s3_recvshort(struct sdio_dev_s *dev, uint32_t cmd,
uint32_t *rshort);
/* EVENT handler */
static void esp32s3_waitenable(struct sdio_dev_s *dev,
sdio_eventset_t eventset, uint32_t timeout);
static sdio_eventset_t esp32s3_eventwait(struct sdio_dev_s *dev);
static void esp32s3_callbackenable(struct sdio_dev_s *dev,
sdio_eventset_t eventset);
static int esp32s3_registercallback(struct sdio_dev_s *dev,
worker_t callback, void *arg);
/* DMA */
#ifdef CONFIG_ESP32S3_SDMMC_DMA
static int esp32s3_dmarecvsetup(struct sdio_dev_s *dev, uint8_t *buffer,
size_t buflen);
static int esp32s3_dmasendsetup(struct sdio_dev_s *dev,
const uint8_t *buffer, size_t buflen);
#endif
/* Initialization/uninitialization/reset ************************************/
static void esp32s3_callback(void *arg);
/****************************************************************************
* Private Data
****************************************************************************/
struct esp32s3_dev_s g_sdiodev =
{
.dev =
{
#ifdef CONFIG_SDIO_MUXBUS
.lock = esp32s3_lock,
#endif
.reset = esp32s3_reset,
.capabilities = esp32s3_capabilities,
.status = esp32s3_status,
.widebus = esp32s3_widebus,
.clock = esp32s3_clock,
.attach = esp32s3_attach,
.sendcmd = esp32s3_sendcmd,
#ifdef CONFIG_SDIO_BLOCKSETUP
.blocksetup = esp32s3_blocksetup,
#endif
.recvsetup = esp32s3_recvsetup,
.sendsetup = esp32s3_sendsetup,
.cancel = esp32s3_cancel,
.waitresponse = esp32s3_waitresponse,
.recv_r1 = esp32s3_recvshortcrc,
.recv_r2 = esp32s3_recvlong,
.recv_r3 = esp32s3_recvshort,
.recv_r4 = esp32s3_recvshort,
.recv_r5 = esp32s3_recvshortcrc,
.recv_r6 = esp32s3_recvshortcrc,
.recv_r7 = esp32s3_recvshort,
.waitenable = esp32s3_waitenable,
.eventwait = esp32s3_eventwait,
.callbackenable = esp32s3_callbackenable,
.registercallback = esp32s3_registercallback,
#ifdef CONFIG_ESP32S3_SDMMC_DMA
.dmarecvsetup = esp32s3_dmarecvsetup,
.dmasendsetup = esp32s3_dmasendsetup,
#endif
},
.waitsem = SEM_INITIALIZER(0),
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: esp32s3_getreg
*
* Description:
* This function may to used to intercept an monitor all register accesses.
* Clearly this is nothing you would want to do unless you are debugging
* this driver.
*
* Input Parameters:
* addr - The register address to read
*
* Returned Value:
* The value read from the register
*
****************************************************************************/
#ifdef CONFIG_ESP32S3_SDMMC_REGDEBUG
static uint32_t __esp32s3_getreg(const char *func, uint32_t addr)
{
static uint32_t prevaddr = 0;
static uint32_t preval = 0;
static uint32_t count = 0;
/* Read the value from the register */
uint32_t val = getreg32(addr);
/* Is this the same value that we read from the same register last time?
* Are we polling the register? If so, suppress some of the output.
*/
if (addr == prevaddr && val == preval)
{
if (count == 0xffffffff || ++count > 3)
{
if (count == 4)
{
mcerr("%s: ...\n", func);
}
return val;
}
}
/* No this is a new address or value */
else
{
/* Did we print "..." for the previous value? */
if (count > 3)
{
/* Yes.. then show how many times the value repeated */
mcerr("%s: [repeats %d more times]\n", func, count - 3);
}
/* Save the new address, value, and count */
prevaddr = addr;
preval = val;
count = 1;
}
/* Show the register value read */
mcerr("%s: %08x->%08x\n", func, addr, val);
return val;
}
#endif
/****************************************************************************
* Name: esp32s3_putreg
*
* Description:
* This function may to used to intercept an monitor all register accesses.
* Clearly this is nothing you would want to do unless you are debugging
* this driver.
*
* Input Parameters:
* val - The value to write to the register
* addr - The register address to read
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_ESP32S3_SDMMC_REGDEBUG
static void __esp32s3_putreg(const char *func, uint32_t val, uint32_t addr)
{
/* Show the register value being written */
mcerr("%s: %08x<-%08x\n", func, addr, val);
/* Write the value */
putreg32(val, addr);
}
#endif
/****************************************************************************
* Name: esp32s3_ciu_sendcmd
*
* Description:
* Function to send command to Card interface unit (CIU)
*
* Input Parameters:
* cmd - The command to be executed
* arg - The argument to use with the command.
*
* Returned Value:
* Returns zero on success. One will be returned on a timeout.
*
****************************************************************************/
static int esp32s3_ciu_sendcmd(uint32_t cmd, uint32_t arg)
{
clock_t watchtime;
DEBUGASSERT((esp32s3_getreg(ESP32S3_SDMMC_CMD) & SDMMC_CMD_STARTCMD) == 0);
watchtime = clock_systime_ticks();
while ((esp32s3_getreg(ESP32S3_SDMMC_CMD) & SDMMC_CMD_STARTCMD) != 0)
{
if (watchtime - clock_systime_ticks() > SDCARD_CMDTIMEOUT)
{
mcerr("TMO Timed out (%08X)\n",
esp32s3_getreg(ESP32S3_SDMMC_CMD));
return 1;
}
}
/* Set command arg reg */
cmd |= SDMMC_CMD_STARTCMD | SDMMC_CMD_USE_HOLE;
esp32s3_putreg(arg, ESP32S3_SDMMC_CMDARG);
esp32s3_putreg(cmd, ESP32S3_SDMMC_CMD);
mcinfo("cmd=0x%x arg=0x%x\n", cmd, arg);
/* Poll until command is accepted by the CIU, or we timeout */
return 0;
}
static void configure_pin(uint8_t gpio_pin, uint8_t sdio_pin,
gpio_pinattr_t attr)
{
esp32s3_configgpio(gpio_pin, attr);
if (attr & INPUT)
{
esp32s3_gpio_matrix_in(gpio_pin, sdio_pin, false);
}
if (attr & OUTPUT)
{
esp32s3_gpio_matrix_out(gpio_pin, sdio_pin, false, false);
}
}
/****************************************************************************
* Name: esp32s3_enable_ints
*
* Description:
* Enable/disable SD card interrupts per functional settings.
*
* Input Parameters:
* priv - A reference to the SD card device state structure
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32s3_enable_ints(struct esp32s3_dev_s *priv)
{
uint32_t regval;
#ifdef CONFIG_ESP32S3_SDMMC_DMA
mcinfo("waitmask=%04lx xfrmask=%04lx dmamask=%04lx RINTSTS=%08lx\n",
(unsigned long)priv->waitmask, (unsigned long)priv->xfrmask,
(unsigned long)priv->dmamask,
(unsigned long)esp32s3_getreg(ESP32S3_SDMMC_RINTSTS));
#else
mcinfo("waitmask=%04lx xfrmask=%04lx RINTSTS=%08lx\n",
(unsigned long)priv->waitmask, (unsigned long)priv->xfrmask,
(unsigned long)esp32s3_getreg(ESP32S3_SDMMC_RINTSTS));
#endif
/* Enable SDMMC interrupts */
regval = priv->xfrmask | priv->waitmask | SDCARD_INT_CDET;
esp32s3_putreg(regval, ESP32S3_SDMMC_INTMASK);
}
/****************************************************************************
* Name: esp32s3_disable_allints
*
* Description:
* Disable all SD card interrupts.
*
* Input Parameters:
* priv - A reference to the SD card device state structure
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32s3_disable_allints(struct esp32s3_dev_s *priv)
{
#ifdef CONFIG_ESP32S3_SDMMC_DMA
/* Disable DMA-related interrupts */
priv->dmamask = 0;
#endif
/* Disable all SDMMC interrupts (except card detect) */
esp32s3_putreg(SDCARD_INT_CDET, ESP32S3_SDMMC_INTMASK);
priv->waitmask = 0;
priv->xfrmask = 0;
}
/****************************************************************************
* Name: esp32s3_config_waitints
*
* Description:
* Enable/disable SD card interrupts needed to support the wait function
*
* Input Parameters:
* priv - A reference to the SD card device state structure
* waitmask - The set of bits in the SD card INTMASK register to set
* waitevents - Waited for events
* wkupevent - Wake-up events
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32s3_config_waitints(struct esp32s3_dev_s *priv,
uint32_t waitmask,
sdio_eventset_t waitevents,
sdio_eventset_t wkupevent)
{
irqstate_t flags;
mcinfo("waitevents=%04x wkupevent=%04x\n",
(unsigned)waitevents, (unsigned)wkupevent);
/* Save all of the data and set the new interrupt mask in one, atomic
* operation.
*/
flags = enter_critical_section();
priv->waitevents = waitevents;
priv->wkupevent = wkupevent;
priv->waitmask = waitmask;
esp32s3_enable_ints(priv);
leave_critical_section(flags);
}
/****************************************************************************
* Name: esp32s3_config_xfrints
*
* Description:
* Enable SD card interrupts needed to support the data transfer event
*
* Input Parameters:
* priv - A reference to the SD card device state structure
* xfrmask - The set of bits in the SD card MASK register to set
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32s3_config_xfrints(struct esp32s3_dev_s *priv,
uint32_t xfrmask)
{
irqstate_t flags;
flags = enter_critical_section();
priv->xfrmask = xfrmask;
esp32s3_enable_ints(priv);
leave_critical_section(flags);
}
/****************************************************************************
* Name: esp32s3_config_dmaints
*
* Description:
* Enable DMA transfer interrupts
*
* Input Parameters:
* priv - A reference to the SD card device state structure
* dmamask - The set of bits in the SD card MASK register to set
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_ESP32S3_SDMMC_DMA
static void esp32s3_config_dmaints(struct esp32s3_dev_s *priv,
uint32_t xfrmask, uint32_t dmamask)
{
irqstate_t flags;
flags = enter_critical_section();
priv->xfrmask = xfrmask;
priv->dmamask = dmamask;
esp32s3_enable_ints(priv);
leave_critical_section(flags);
}
#endif
/****************************************************************************
* Name: esp32s3_eventtimeout
*
* Description:
* The watchdog timeout setup when the event wait start has expired without
* any other waited-for event occurring.
*
* Input Parameters:
* arg - The argument
*
* Returned Value:
* None
*
* Assumptions:
* Always called from the interrupt level with interrupts disabled.
*
****************************************************************************/
static void esp32s3_eventtimeout(wdparm_t arg)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)arg;
/* There is always race conditions with timer expirations. */
DEBUGASSERT((priv->waitevents & SDIOWAIT_TIMEOUT) != 0 ||
priv->wkupevent != 0);
/* Is a data transfer complete event expected? */
if ((priv->waitevents & SDIOWAIT_TIMEOUT) != 0)
{
/* Yes.. wake up any waiting threads */
esp32s3_endwait(priv, SDIOWAIT_TIMEOUT);
mcerr("Timeout: remaining: %d\n", priv->remaining);
}
}
/****************************************************************************
* Name: esp32s3_endwait
*
* Description:
* Wake up a waiting thread if the waited-for event has occurred.
*
* Input Parameters:
* priv - An instance of the SDIO device interface
* wkupevent - The event that caused the wait to end
*
* Returned Value:
* None
*
* Assumptions:
* Always called from the interrupt level with interrupts disabled.
*
****************************************************************************/
static void esp32s3_endwait(struct esp32s3_dev_s *priv,
sdio_eventset_t wkupevent)
{
mcinfo("wkupevent=%04x\n", (unsigned)wkupevent);
/* Cancel the watchdog timeout */
wd_cancel(&priv->waitwdog);
/* Disable event-related interrupts */
esp32s3_config_waitints(priv, 0, 0, wkupevent);
/* Wake up the waiting thread */
nxsem_post(&priv->waitsem);
}
/****************************************************************************
* Name: esp32s3_endtransfer
*
* Description:
* Terminate a transfer with the provided status. This function is called
* only from the SDIO interrupt handler when end-of-transfer conditions
* are detected.
*
* Input Parameters:
* priv - An instance of the SDIO device interface
* wkupevent - The event that caused the transfer to end
*
* Returned Value:
* None
*
* Assumptions:
* Always called from the interrupt level with interrupts disabled.
*
****************************************************************************/
static void esp32s3_endtransfer(struct esp32s3_dev_s *priv,
sdio_eventset_t wkupevent)
{
mcinfo("wkupevent=%04x\n", (unsigned)wkupevent);
/* Disable all transfer related interrupts */
esp32s3_config_xfrints(priv, 0);
/* Clearing pending interrupt status on all transfer related interrupts */
esp32s3_putreg(priv->waitmask, ESP32S3_SDMMC_RINTSTS);
/* Mark the transfer finished */
priv->remaining = 0;
/* Is a thread wait for these data transfer complete events? */
if ((priv->waitevents & wkupevent) != 0)
{
/* Yes.. wake up any waiting threads */
esp32s3_endwait(priv, wkupevent);
}
}
/****************************************************************************
* Name: esp32s3_interrupt
*
* Description:
* SDIO interrupt handler
*
* Input Parameters:
* dev - An instance of the SDIO device interface
*
* Returned Value:
* None
*
****************************************************************************/
static int esp32s3_interrupt(int irq, void *context, void *arg)
{
struct esp32s3_dev_s *priv = arg;
uint32_t enabled;
uint32_t pending;
/* Loop while there are pending interrupts. Check the SD card status
* register. Mask out all bits that don't correspond to enabled
* interrupts. (This depends on the fact that bits are ordered
* the same in both the STA and MASK register). If there are non-zero
* bits remaining, then we have work to do here.
*/
while ((enabled = esp32s3_getreg(ESP32S3_SDMMC_MINTSTS)) != 0)
{
/* Clear pending status */
esp32s3_putreg(enabled, ESP32S3_SDMMC_RINTSTS);
#ifdef CONFIG_MMCSD_HAVE_CARDDETECT
/* Handle in card detection events ************************************/
if ((enabled & SDMMC_INT_CDET) != 0)
{
sdio_statset_t cdstatus;
/* Update card status */
cdstatus = priv->cdstatus;
if ((esp32s3_getreg(ESP32S3_SDMMC_CDETECT) &
SDMMC_CDETECT_NOTPRESENT(priv->slot)) == 0)
{
priv->cdstatus |= SDIO_STATUS_PRESENT;
#ifdef CONFIG_MMCSD_HAVE_WRITEPROTECT
if ((esp32s3_getreg(ESP32S3_SDMMC_WRTPRT) &
SDMMC_WRTPRT_PROTECTED(priv->slot)) != 0)
{
priv->cdstatus |= SDIO_STATUS_WRPROTECTED;
}
else
#endif
{
priv->cdstatus &= ~SDIO_STATUS_WRPROTECTED;
}
}
else
{
priv->cdstatus &=
~(SDIO_STATUS_PRESENT | SDIO_STATUS_WRPROTECTED);
}
mcinfo("cdstatus OLD: %02x NEW: %02x\n", cdstatus, priv->cdstatus);
/* Perform any requested callback if the status has changed */
if (cdstatus != priv->cdstatus)
{
esp32s3_callback(priv);
}
}
#endif
/* Handle data transfer events ****************************************/
pending = enabled & priv->xfrmask;
if (pending != 0)
{
/* Handle data request events */
if ((pending & SDMMC_INT_TXDR) != 0)
{
uint32_t status;
/* Transfer data to the TX FIFO */
DEBUGASSERT(priv->wrdir);
for (status = esp32s3_getreg(ESP32S3_SDMMC_STATUS);
(status & SDMMC_STATUS_FIFOFULL) == 0 &&
priv->remaining > 0;
status = esp32s3_getreg(ESP32S3_SDMMC_STATUS))
{
esp32s3_putreg(*priv->buffer, ESP32S3_SDMMC_DATA);
priv->buffer++;
priv->remaining -= 4;
}
}
else if ((pending & SDMMC_INT_RXDR) != 0)
{
uint32_t status;
/* Transfer data from the RX FIFO */
DEBUGASSERT(!priv->wrdir);
for (status = esp32s3_getreg(ESP32S3_SDMMC_STATUS);
(status & SDMMC_STATUS_FIFOEMPTY) == 0 &&
priv->remaining > 0;
status = esp32s3_getreg(ESP32S3_SDMMC_STATUS))
{
*priv->buffer = esp32s3_getreg(ESP32S3_SDMMC_DATA);
priv->buffer++;
priv->remaining -= 4;
}
}
/* Check for transfer errors */
/* Handle data block send/receive CRC failure */
if ((pending & SDMMC_INT_DCRC) != 0)
{
/* Terminate the transfer with an error */
mcerr("ERROR: Data CRC failure, pending=%08x remaining: %d\n",
pending, priv->remaining);
esp32s3_endtransfer(priv,
SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR);
}
/* Handle data timeout error */
else if ((pending & SDMMC_INT_DRTO) != 0)
{
/* Terminate the transfer with an error */
mcerr("ERROR: Data timeout, pending=%08x remaining: %d\n",
pending, priv->remaining);
esp32s3_endtransfer(priv,
SDIOWAIT_TRANSFERDONE | SDIOWAIT_TIMEOUT);
}
/* Handle RX FIFO overrun error */
else if ((pending & SDMMC_INT_FRUN) != 0)
{
/* Terminate the transfer with an error */
mcerr("ERROR: RX FIFO overrun, pending=%08x remaining: %d\n",
pending, priv->remaining);
esp32s3_endtransfer(priv,
SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR);
}
/* Handle TX FIFO underrun error */
else if ((pending & SDMMC_INT_FRUN) != 0)
{
/* Terminate the transfer with an error */
mcerr("ERROR: TX FIFO underrun, pending=%08x remaining: %d\n",
pending, priv->remaining);
esp32s3_endtransfer(priv,
SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR);
}
/* Handle start bit error */
else if ((pending & SDMMC_INT_SBE) != 0)
{
/* Terminate the transfer with an error */
mcerr("ERROR: Start bit, pending=%08x remaining: %d\n",
pending, priv->remaining);
esp32s3_endtransfer(priv,
SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR);
}
/* Handle data end events. Note that RXDR may accompany DTO, DTO
* will be set on received while there is still data in the FIFO.
* So for the case of receiving, we don't actually even enable the
* DTO interrupt.
*/
else if ((pending & SDMMC_INT_DTO) != 0)
{
/* Finish the transfer */
esp32s3_endtransfer(priv, SDIOWAIT_TRANSFERDONE);
}
}
/* Handle wait events *************************************************/
pending = enabled & priv->waitmask;
if (pending != 0)
{
/* Is this a response error event? */
if ((pending & SDCARD_INT_RESPERR) != 0)
{
/* If response errors are enabled, then we must certainly be
* waiting for a response.
*/
DEBUGASSERT((priv->waitevents & SDIOWAIT_RESPONSEDONE) != 0);
/* Wake the thread up */
mcerr("ERROR: Response error, pending=%08x\n", pending);
esp32s3_endwait(priv, SDIOWAIT_RESPONSEDONE | SDIOWAIT_ERROR);
}
/* Is this a command (plus response) completion event? */
else if ((pending & SDMMC_INT_CDONE) != 0)
{
/* Yes.. Is their a thread waiting for response done? */
if ((priv->waitevents & SDIOWAIT_RESPONSEDONE) != 0)
{
/* Yes.. wake the thread up */
esp32s3_endwait(priv, SDIOWAIT_RESPONSEDONE);
}
/* NO.. Is their a thread waiting for command done? */
else if ((priv->waitevents & SDIOWAIT_CMDDONE) != 0)
{
/* Yes.. wake the thread up */
esp32s3_endwait(priv, SDIOWAIT_CMDDONE);
}
}
}
}
#ifdef CONFIG_ESP32S3_SDMMC_DMA
/* DMA error events *******************************************************/
pending = esp32s3_getreg(ESP32S3_SDMMC_IDSTS);
if ((pending & priv->dmamask) != 0)
{
mcerr("ERROR: IDTS=%08lx\n", (unsigned long)pending);
/* Clear the pending interrupts */
esp32s3_putreg(pending, ESP32S3_SDMMC_IDSTS);
/* Abort the transfer */
esp32s3_endtransfer(priv, SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR);
}
#endif
return OK;
}
/****************************************************************************
* Name: esp32s3_lock
*
* Description:
* Locks the bus. Function calls low-level multiplexed bus routines to
* resolve bus requests and acknowledgment issues.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* lock - TRUE to lock, FALSE to unlock.
*
* Returned Value:
* OK on success; a negated errno on failure
*
****************************************************************************/
#ifdef CONFIG_SDIO_MUXBUS
static int esp32s3_lock(struct sdio_dev_s *dev, bool lock)
{
/* Single SDIO instance so there is only one possibility. The multiplex
* bus is part of board support package.
*/
/* FIXME: Implement the below function to support bus share:
*
* esp32s3_muxbus_sdio_lock(lock);
*/
return OK;
}
#endif
/****************************************************************************
* Name: esp32s3_reset
*
* Description:
* Reset the SDIO controller. Undo all setup and initialization.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32s3_reset(struct sdio_dev_s *dev)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
irqstate_t flags;
uint32_t regval;
mcinfo("Resetting...\n");
flags = enter_critical_section();
/* Reset all blocks */
esp32s3_putreg(SDMMC_CTRL_CNTLRRESET | SDMMC_CTRL_FIFORESET |
SDMMC_CTRL_DMARESET, ESP32S3_SDMMC_CTRL);
while ((esp32s3_getreg(ESP32S3_SDMMC_CTRL) &
(SDMMC_CTRL_CNTLRRESET | SDMMC_CTRL_FIFORESET |
SDMMC_CTRL_DMARESET)) != 0)
{
}
/* Select clock source and init phases */
regval = esp32s3_getreg(ESP32S3_SDMMC_CLOCK);
regval &= ~(SDMMC_CLOCK_CLK_SEL_MASK | SDMMC_CLOCK_PHASE_MASK);
regval |= SDMMC_CLOCK_CLK_SEL_PLL160M;
regval |= 1 << SDMMC_CLOCK_PHASE_DOUT_SHIFT;
esp32s3_putreg(regval, ESP32S3_SDMMC_CLOCK);
/* Select clock divider
* Slot N selects clock divider N.
*/
regval = esp32s3_getreg(ESP32S3_SDMMC_CLKSRC);
regval &= ~SDMMC_CLKSRC_MASK(priv->slot);
regval |= SDMMC_CLKSRC_CLKDIV(priv->slot, priv->slot);
esp32s3_putreg(regval, ESP32S3_SDMMC_CLKSRC);
#ifdef CONFIG_ESP32S3_SDMMC_DMA
esp32s3_putreg((uint32_t)&priv->dma_desc[0], ESP32S3_SDMMC_DBADDR);
#endif
/* Reset data */
priv->waitevents = 0; /* Set of events to be waited for */
priv->waitmask = 0; /* Interrupt enables for event waiting */
priv->wkupevent = 0; /* The event that caused the wakeup */
wd_cancel(&priv->waitwdog); /* Cancel any timeouts */
/* Interrupt mode data transfer support */
priv->buffer = 0; /* Address of current R/W buffer */
priv->remaining = 0; /* Number of bytes remaining in the transfer */
priv->xfrmask = 0; /* Interrupt enables for data transfer */
#ifdef CONFIG_ESP32S3_SDMMC_DMA
priv->dmamask = 0; /* Interrupt enables for DMA transfer */
#endif
/* DMA data transfer support */
priv->cdstatus = 0; /* Card status is unknown */
leave_critical_section(flags);
}
/****************************************************************************
* Name: esp32s3_capabilities
*
* Description:
* Get capabilities (and limitations) of the SDIO driver (optional)
*
* Input Parameters:
* dev - Device-specific state data
*
* Returned Value:
* Returns a bitset of status values (see SDIO_CAPS_* defines)
*
****************************************************************************/
static sdio_capset_t esp32s3_capabilities(struct sdio_dev_s *dev)
{
sdio_capset_t caps = 0;
caps |= SDIO_CAPS_DMABEFOREWRITE;
caps |= SDIO_CAPS_MMC_HS_MODE;
#ifdef CONFIG_SDIO_WIDTH_D1_ONLY
caps |= SDIO_CAPS_1BIT_ONLY;
#endif
#ifdef CONFIG_ESP32S3_SDMMC_DMA
caps |= SDIO_CAPS_DMASUPPORTED;
#endif
return caps;
}
/****************************************************************************
* Name: esp32s3_status
*
* Description:
* Get SDIO status.
*
* Input Parameters:
* dev - Device-specific state data
*
* Returned Value:
* Returns a bitset of status values (see esp32s3_status_* defines)
*
****************************************************************************/
static sdio_statset_t esp32s3_status(struct sdio_dev_s *dev)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
#ifdef CONFIG_MMCSD_HAVE_CARDDETECT
if ((esp32s3_getreg(ESP32S3_SDMMC_CDETECT) &
SDMMC_CDETECT_NOTPRESENT(priv->slot)) == 0)
{
priv->cdstatus |= SDIO_STATUS_PRESENT;
}
else
{
priv->cdstatus &= ~SDIO_STATUS_PRESENT;
}
#endif
mcinfo("cdstatus=%02x\n", priv->cdstatus);
return priv->cdstatus;
}
/****************************************************************************
* Name: esp32s3_widebus
*
* Description:
* Called after change in Bus width has been selected (via ACMD6). Most
* controllers will need to perform some special operations to work
* correctly in the new bus mode.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* wide - true: wide bus (4-bit) bus mode enabled
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32s3_widebus(struct sdio_dev_s *dev, bool wide)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
uint32_t regval;
regval = esp32s3_getreg(ESP32S3_SDMMC_CTYPE);
regval &= ~(SDMMC_CTYPE_WIDTH4_MASK(priv->slot));
regval &= ~(SDMMC_CTYPE_WIDTH8_MASK(priv->slot));
#ifndef CONFIG_SDIO_WIDTH_D1_ONLY
if (wide)
{
regval |= SDMMC_CTYPE_WIDTH4_MASK(priv->slot);
configure_pin(CONFIG_ESP32S3_SDMMC_D1, priv->sdio_pins->d1,
INPUT | OUTPUT | PULLUP);
configure_pin(CONFIG_ESP32S3_SDMMC_D2, priv->sdio_pins->d2,
INPUT | OUTPUT | PULLUP);
configure_pin(CONFIG_ESP32S3_SDMMC_D3, priv->sdio_pins->d3,
INPUT | OUTPUT | PULLUP);
}
#endif
esp32s3_putreg(regval, ESP32S3_SDMMC_CTYPE);
}
static int sdmmc_host_clock_update_command(struct esp32s3_dev_s *priv)
{
/* Clock update command
* not a real command; just updates CIU registers
*/
uint32_t cmd = SDMMC_CMD_UPDCLOCK | SDMMC_CMD_WAITPREV |
SDMMC_CMD_CARD_NUMBER(priv->slot);
uint32_t regval;
bool repeat = true;
int timeout_ms = 100;
int ret;
while (repeat)
{
ret = esp32s3_ciu_sendcmd(cmd, 0);
if (ret)
{
return ret;
}
while (timeout_ms)
{
regval = esp32s3_getreg(ESP32S3_SDMMC_RINTSTS);
if (regval & SDMMC_INT_HLE)
{
esp32s3_putreg(SDMMC_INT_HLE, ESP32S3_SDMMC_RINTSTS);
break;
}
if ((esp32s3_getreg(ESP32S3_SDMMC_CMD) & SDMMC_CMD_STARTCMD)
== 0)
{
repeat = false;
break;
}
timeout_ms--;
up_mdelay(1);
}
}
return timeout_ms > 0 ? OK : -ETIMEDOUT;
}
static void sdmmc_host_get_clk_dividers(uint32_t freq_khz, int *host_div,
int *card_div)
{
uint32_t clk_src_freq_hz = BOARD_SDMMC_FREQUENCY;
/* Calculate new dividers */
if (freq_khz >= 40 * 1000)
{
*host_div = 4; /* 160 MHz / 4 = 40 MHz */
*card_div = 0;
}
else if (freq_khz == 20 * 1000)
{
*host_div = 8; /* 160 MHz / 8 = 20 MHz */
*card_div = 0;
}
else if (freq_khz == 400)
{
*host_div = 10; /* 160 MHz / 10 / (20 * 2) = 400 kHz */
*card_div = 20;
}
else
{
/* for custom frequencies use maximum range of host divider (1-16),
* find the closest <= div. combination
* if exceeded, combine with the card divider to keep reasonable
* precision (applies mainly to low frequencies)
* effective frequency range: 400 kHz - 32 MHz (32.1 - 39.9 MHz cannot
* be covered with given divider scheme)
*/
*host_div = (clk_src_freq_hz) / (freq_khz * 1000);
if (*host_div > 15)
{
*host_div = 2;
*card_div = (clk_src_freq_hz / 2) / (2 * freq_khz * 1000);
if (((clk_src_freq_hz / 2) % (2 * freq_khz * 1000)) > 0)
{
(*card_div)++;
}
}
else if ((clk_src_freq_hz % (freq_khz * 1000)) > 0)
{
(*host_div)++;
}
}
}
static void sdmmc_host_set_clk_div(uint32_t slot, uint32_t host_div,
uint32_t card_div)
{
irqstate_t flags = enter_critical_section();
/* Set frequency to 160MHz / div
*
* n: counter resets at div_factor_n.
* l: negedge when counter equals div_factor_l.
* h: posedge when counter equals div_factor_h.
*
* We set the duty cycle to 1/2
*/
ASSERT(host_div > 1 && host_div <= 16);
int l = host_div - 1;
int h = host_div / 2 - 1;
uint32_t regval;
uint32_t divider;
/* Get the divider which is selected in esp32s3_reset() */
regval = esp32s3_getreg(ESP32S3_SDMMC_CLKSRC);
divider = (regval & SDMMC_CLKSRC_MASK(slot)) >> SDMMC_CLKSRC_SHIFT(slot);
/* Set card divider */
regval = esp32s3_getreg(ESP32S3_SDMMC_CLKDIV);
regval &= ~SDMMC_CLKDIV_MASK(divider);
regval |= SDMMC_CLKDIV(divider, card_div);
esp32s3_putreg(regval, ESP32S3_SDMMC_CLKDIV);
/* Set host_div divider */
regval = esp32s3_getreg(ESP32S3_SDMMC_CLOCK);
regval &= ~SDMMC_CLOCK_DIV_FACTOR_MASK;
regval |= (h << SDMMC_CLOCK_DIV_FACTOR_H_SHIFT)
& SDMMC_CLOCK_DIV_FACTOR_H_MASK;
regval |= (l << SDMMC_CLOCK_DIV_FACTOR_L_SHIFT)
& SDMMC_CLOCK_DIV_FACTOR_L_MASK;
regval |= (l << SDMMC_CLOCK_DIV_FACTOR_N_SHIFT)
& SDMMC_CLOCK_DIV_FACTOR_N_MASK;
esp32s3_putreg(regval, ESP32S3_SDMMC_CLOCK);
leave_critical_section(flags);
/* Wait for the clock to propagate */
up_udelay(10);
}
/****************************************************************************
* Name: esp32s3_clock
*
* Description:
* Enable/disable SDIO clocking
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* rate - Specifies the clocking to use (see enum sdio_clock_e)
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32s3_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate)
{
uint32_t freq_khz;
uint32_t regval;
bool clk_en = true;
int host_div = 0; /* clock divider of the host (SDMMC.clock) */
int card_div = 0; /* 1/2 of card clock divider (SDMMC.clkdiv) */
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
switch (rate)
{
/* Disable clocking (with default ID mode divisor) */
default:
case CLOCK_SDIO_DISABLED:
freq_khz = 400;
clk_en = false;
break;
/* Enable in initial ID mode clocking (<400KHz) */
case CLOCK_IDMODE:
freq_khz = 400;
break;
/* Enable in MMC normal operation clocking */
case CLOCK_MMC_TRANSFER:
if (esp32s3_capabilities(dev) & SDIO_CAPS_MMC_HS_MODE)
{
freq_khz = 40 * 1000;
}
else
{
freq_khz = 20 * 1000;
}
break;
/* SD normal operation clocking (wide 4-bit mode) */
case CLOCK_SD_TRANSFER_4BIT:
#ifndef CONFIG_ESP32S3_SDMMC_WIDTH_D1_ONLY
/* TODO: Use higher frequency */
freq_khz = 20 * 1000;
esp32s3_widebus(dev, true);
break;
#endif
/* SD normal operation clocking (narrow 1-bit mode) */
case CLOCK_SD_TRANSFER_1BIT:
/* TODO: Use higher frequency */
freq_khz = 20 * 1000;
esp32s3_widebus(dev, false);
break;
}
/* Disable clock first */
regval = esp32s3_getreg(ESP32S3_SDMMC_CLKENA);
regval &= ~(SDMMC_CLKENA_ENABLE(priv->slot)
| SDMMC_CLKENA_LOWPOWER(priv->slot));
esp32s3_putreg(regval, ESP32S3_SDMMC_CLKENA);
if (sdmmc_host_clock_update_command(priv) != OK)
{
mcerr("disabling clk failed\n");
return;
}
/* Program card clock settings, send them to the CIU */
sdmmc_host_get_clk_dividers(freq_khz, &host_div, &card_div);
sdmmc_host_set_clk_div(priv->slot, host_div, card_div);
if (sdmmc_host_clock_update_command(priv) != OK)
{
mcerr("setting clk div failed\n");
return;
}
/* Re-enable clocks */
if (clk_en)
{
regval = esp32s3_getreg(ESP32S3_SDMMC_CLKENA);
regval |= SDMMC_CLKENA_ENABLE(priv->slot)
| SDMMC_CLKENA_LOWPOWER(priv->slot);
esp32s3_putreg(regval, ESP32S3_SDMMC_CLKENA);
if (sdmmc_host_clock_update_command(priv) != OK)
{
mcerr("re-enabling clk failed\n");
return;
}
}
/* set data timeout to 100ms */
if (freq_khz * 100 > (SDMMC_TMOUT_DATA_MASK >> SDMMC_TMOUT_DATA_SHIFT))
{
regval = SDMMC_TMOUT_DATA_MASK;
}
else
{
regval = freq_khz * 100 << SDMMC_TMOUT_DATA_SHIFT;
}
/* always set response timeout to highest value, it's small enough anyway */
regval |= SDMMC_TMOUT_RESPONSE_MASK;
esp32s3_putreg(regval, ESP32S3_SDMMC_TMOUT);
}
/****************************************************************************
* Name: esp32s3_attach
*
* Description:
* Attach and prepare interrupts
*
* Input Parameters:
* dev - An instance of the SDIO device interface
*
* Returned Value:
* OK on success; A negated errno on failure.
*
****************************************************************************/
static int esp32s3_attach(struct sdio_dev_s *dev)
{
int ret;
uint32_t regval;
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
ret = esp32s3_setup_irq(up_cpu_index(), ESP32S3_PERIPH_SDIO_HOST,
1, ESP32S3_CPUINT_LEVEL);
DEBUGASSERT(ret >= 0);
/* Attach the SDIO interrupt handler */
ret = irq_attach(ESP32S3_IRQ_SDIO_HOST, esp32s3_interrupt, dev);
if (ret == OK)
{
/* Disable all interrupts at the SD card controller and clear static
* interrupt flags
*/
esp32s3_putreg(0, ESP32S3_SDMMC_INTMASK);
esp32s3_putreg(SDMMC_INT_ALL(priv->slot), ESP32S3_SDMMC_RINTSTS);
/* Enable Interrupts to happen when the INTMASK is activated */
regval = esp32s3_getreg(ESP32S3_SDMMC_CTRL);
regval |= SDMMC_CTRL_INTENABLE;
esp32s3_putreg(regval, ESP32S3_SDMMC_CTRL);
/* Enable card detection interrupts */
esp32s3_putreg(SDCARD_INT_CDET, ESP32S3_SDMMC_INTMASK);
/* Enable SD card interrupts at the NVIC. They can now be enabled at
* the SD card controller as needed.
*/
up_enable_irq(ESP32S3_IRQ_SDIO_HOST);
}
return ret;
}
/****************************************************************************
* Name: esp32s3_sendcmd
*
* Description:
* Send the SDIO command
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* cmd - The command to send (32-bits, encoded)
* arg - 32-bit argument required with some commands
*
* Returned Value:
* None
*
****************************************************************************/
static int esp32s3_sendcmd(struct sdio_dev_s *dev, uint32_t cmd,
uint32_t arg)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
uint32_t regval = 0;
mcinfo("cmd=%04x arg=%04x\n", cmd, arg);
if (cmd == MMCSD_CMD12)
{
regval |= SDMMC_CMD_STOPABORT;
}
else if (cmd == MMCSD_CMD0)
{
/* The CMD0 needs the SENDINIT CMD */
regval |= SDMMC_CMD_SENDINIT;
}
else
{
regval |= SDMMC_CMD_WAITPREV;
}
/* Is this a Read/Write Transfer Command ? */
if ((cmd & MMCSD_WRDATAXFR) == MMCSD_WRDATAXFR)
{
regval |= SDMMC_CMD_DATAXFREXPTD | SDMMC_CMD_WRITE;
}
else if ((cmd & MMCSD_RDDATAXFR) == MMCSD_RDDATAXFR)
{
regval |= SDMMC_CMD_DATAXFREXPTD;
}
/* Set WAITRESP bits */
switch (cmd & MMCSD_RESPONSE_MASK)
{
case MMCSD_NO_RESPONSE:
regval |= SDMMC_CMD_NORESPONSE;
break;
case MMCSD_R1B_RESPONSE:
regval |= SDMMC_CMD_RESPCRC;
regval |= SDMMC_CMD_WAITPREV;
regval |= SDMMC_CMD_SHORTRESPONSE;
break;
case MMCSD_R3_RESPONSE:
case MMCSD_R4_RESPONSE:
regval |= SDMMC_CMD_SHORTRESPONSE;
break;
case MMCSD_R1_RESPONSE:
case MMCSD_R5_RESPONSE:
case MMCSD_R6_RESPONSE:
case MMCSD_R7_RESPONSE:
regval |= SDMMC_CMD_RESPCRC;
regval |= SDMMC_CMD_SHORTRESPONSE;
break;
case MMCSD_R2_RESPONSE:
regval |= SDMMC_CMD_LONGRESPONSE;
regval |= SDMMC_CMD_RESPCRC;
break;
}
/* Set the command index */
regval |= (cmd & MMCSD_CMDIDX_MASK) >> MMCSD_CMDIDX_SHIFT;
regval |= SDMMC_CMD_CARD_NUMBER(priv->slot);
/* Write the SD card CMD */
esp32s3_ciu_sendcmd(regval, arg);
return OK;
}
/****************************************************************************
* Name: esp32s3_blocksetup
*
* Description:
* Configure block size and the number of blocks for next transfer
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* blocklen - The selected block size.
* nblocklen - The number of blocks to transfer
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_SDIO_BLOCKSETUP
static void esp32s3_blocksetup(struct sdio_dev_s *dev, unsigned int blocklen,
unsigned int nblocks)
{
/* Configure block size for next transfer */
esp32s3_putreg(blocklen, ESP32S3_SDMMC_BLKSIZ);
esp32s3_putreg(blocklen * nblocks, ESP32S3_SDMMC_BYTCNT);
}
#endif
/****************************************************************************
* Name: esp32s3_recvsetup
*
* Description:
* Setup hardware in preparation for data transfer from the card in non-DMA
* (interrupt driven mode). This method will do whatever controller setup
* is necessary. This would be called for SD memory just BEFORE sending
* CMD13 (SEND_STATUS), CMD17 (READ_SINGLE_BLOCK), CMD18
* (READ_MULTIPLE_BLOCKS), ACMD51 (SEND_SCR), etc. Normally,
* SDIO_WAITEVENT will be called to receive the indication that the
* transfer is complete.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* buffer - Address of the buffer in which to receive the data
* nbytes - The number of bytes in the transfer
*
* Returned Value:
* Number of bytes sent on success; a negated errno on failure
*
****************************************************************************/
static int esp32s3_recvsetup(struct sdio_dev_s *dev, uint8_t *buffer,
size_t nbytes)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
#ifdef CONFIG_ESP32S3_SDMMC_DMA
uint32_t regval;
#endif
mcinfo("nbytes=%ld\n", (long) nbytes);
DEBUGASSERT(priv != NULL && buffer != NULL && nbytes > 0);
DEBUGASSERT(((uint32_t)buffer & 3) == 0);
/* Save the destination buffer information for use by the interrupt
* handler.
*/
priv->buffer = (uint32_t *)buffer;
priv->remaining = nbytes;
priv->wrdir = false;
/* Configure the FIFO so that we will receive the RXDR interrupt whenever
* there are more than 1 words (at least 8 bytes) in the RX FIFO.
*/
esp32s3_putreg(SDMMC_FIFOTH_RXWMARK(1), ESP32S3_SDMMC_FIFOTH);
#ifdef CONFIG_ESP32S3_SDMMC_DMA
/* Make sure that internal DMA is disabled */
esp32s3_putreg(0, ESP32S3_SDMMC_BMOD);
regval = esp32s3_getreg(ESP32S3_SDMMC_CTRL);
regval &= ~SDMMC_CTRL_INTDMA;
esp32s3_putreg(regval, ESP32S3_SDMMC_CTRL);
#endif
/* Flush ints before we start */
esp32s3_putreg(SDCARD_TRANSFER_ALL, ESP32S3_SDMMC_RINTSTS);
/* Configure the transfer interrupts */
esp32s3_config_xfrints(priv, SDCARD_RECV_MASK);
return OK;
}
/****************************************************************************
* Name: esp32s3_sendsetup
*
* Description:
* Setup hardware in preparation for data transfer from the card. This
* method will do whatever controller setup is necessary. This would be
* called for SD memory just AFTER sending CMD24 (WRITE_BLOCK), CMD25
* (WRITE_MULTIPLE_BLOCK), ... and before SDIO_SENDDATA is called.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* buffer - Address of the buffer containing the data to send
* nbytes - The number of bytes in the transfer
*
* Returned Value:
* Number of bytes sent on success; a negated errno on failure
*
****************************************************************************/
static int esp32s3_sendsetup(struct sdio_dev_s *dev, const uint8_t *buffer,
size_t nbytes)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
#ifdef CONFIG_ESP32S3_SDMMC_DMA
uint32_t regval;
#endif
mcinfo("nbytes=%ld\n", (long)nbytes);
DEBUGASSERT(priv != NULL && buffer != NULL && nbytes > 0);
DEBUGASSERT(((uint32_t)buffer & 3) == 0);
/* Save the source buffer information for use by the interrupt handler */
priv->buffer = (uint32_t *)buffer;
priv->remaining = nbytes;
priv->wrdir = true;
/* Configure the FIFO so that we will receive the TXDR interrupt whenever
* there the TX FIFO is at least half empty.
*/
esp32s3_putreg(SDMMC_FIFOTH_TXWMARK(ESP32S3_TXFIFO_DEPTH / 2),
ESP32S3_SDMMC_FIFOTH);
#ifdef CONFIG_ESP32S3_SDMMC_DMA
/* Make sure that internal DMA is disabled */
esp32s3_putreg(0, ESP32S3_SDMMC_BMOD);
regval = esp32s3_getreg(ESP32S3_SDMMC_CTRL);
regval &= ~SDMMC_CTRL_INTDMA;
esp32s3_putreg(regval, ESP32S3_SDMMC_CTRL);
#endif
/* Flush ints before we start */
esp32s3_putreg(SDCARD_TRANSFER_ALL, ESP32S3_SDMMC_RINTSTS);
/* Configure the transfer interrupts */
esp32s3_config_xfrints(priv, SDCARD_SEND_MASK);
return OK;
}
/****************************************************************************
* Name: esp32s3_cancel
*
* Description:
* Cancel the data transfer setup of SDIO_RECVSETUP, SDIO_SENDSETUP,
* SDIO_DMARECVSETUP or SDIO_DMASENDSETUP. This must be called to cancel
* the data transfer setup if, for some reason, you cannot perform the
* transfer.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
*
* Returned Value:
* OK is success; a negated errno on failure
*
****************************************************************************/
static int esp32s3_cancel(struct sdio_dev_s *dev)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
mcinfo("Cancelling..\n");
/* Disable all transfer- and event- related interrupts */
esp32s3_disable_allints(priv);
/* Clearing pending interrupt status on all transfer- and event- related
* interrupts
*/
esp32s3_putreg(SDCARD_WAITALL_CLEAR, ESP32S3_SDMMC_RINTSTS);
/* Cancel any watchdog timeout */
wd_cancel(&priv->waitwdog);
#if defined(CONFIG_ESP32S3_SDMMC_DMA) && defined(CONFIG_ESP32S3_SPIRAM)
if (!esp32s3_ptr_dma_capable(priv->buffer) && priv->dma_buf)
{
kmm_free(priv->dma_buf);
priv->dma_buf = NULL;
}
#endif
/* Mark no transfer in progress */
priv->remaining = 0;
return OK;
}
/****************************************************************************
* Name: esp32s3_waitresponse
*
* Description:
* Poll-wait for the response to the last command to be ready.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* cmd - The command that was sent. See 32-bit command definitions above.
*
* Returned Value:
* OK is success; a negated errno on failure
*
****************************************************************************/
static int esp32s3_waitresponse(struct sdio_dev_s *dev, uint32_t cmd)
{
volatile int32_t timeout;
clock_t watchtime;
uint32_t events;
mcinfo("cmd=%04x\n", cmd);
switch (cmd & MMCSD_RESPONSE_MASK)
{
case MMCSD_NO_RESPONSE:
events = SDCARD_CMDDONE_STA;
timeout = SDCARD_CMDTIMEOUT;
break;
case MMCSD_R1_RESPONSE:
case MMCSD_R1B_RESPONSE:
case MMCSD_R2_RESPONSE:
case MMCSD_R4_RESPONSE:
case MMCSD_R5_RESPONSE:
case MMCSD_R6_RESPONSE:
events = (SDCARD_CMDDONE_STA | SDCARD_RESPDONE_STA);
timeout = SDCARD_LONGTIMEOUT;
break;
case MMCSD_R3_RESPONSE:
case MMCSD_R7_RESPONSE:
events = (SDCARD_CMDDONE_STA | SDCARD_RESPDONE_STA);
timeout = SDCARD_CMDTIMEOUT;
break;
default:
return -EINVAL;
}
/* Then wait for the response (or timeout or error) */
watchtime = clock_systime_ticks();
while ((esp32s3_getreg(ESP32S3_SDMMC_RINTSTS) & events) != events)
{
if (clock_systime_ticks() - watchtime > timeout)
{
mcerr("ERROR: Timeout cmd: %04x events: %04x STA: %08x "
"RINTSTS: %08x\n",
cmd, events, esp32s3_getreg(ESP32S3_SDMMC_STATUS),
esp32s3_getreg(ESP32S3_SDMMC_RINTSTS));
return -ETIMEDOUT;
}
else if ((esp32s3_getreg(ESP32S3_SDMMC_RINTSTS) & SDCARD_INT_RESPERR)
!= 0)
{
mcerr("ERROR: SDMMC failure cmd: %04x events: %04x STA: %08x "
"RINTSTS: %08x\n",
cmd, events, esp32s3_getreg(ESP32S3_SDMMC_STATUS),
esp32s3_getreg(ESP32S3_SDMMC_RINTSTS));
return -EIO;
}
}
esp32s3_putreg(SDCARD_CMDDONE_CLEAR, ESP32S3_SDMMC_RINTSTS);
return OK;
}
/****************************************************************************
* Name: esp32s3_recvshortcrc
*
* Description:
* Receive response to SDIO command. Only the critical payload is
* returned -- 32 bits for 48 bit status. The driver implementation
* verifies the correctness of the remaining, non-returned bits (CRCs, CMD
* index, etc.).
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* Rx - Buffer in which to receive the response
*
* Returned Value:
* Number of bytes sent on success; a negated errno on failure. Here a
* failure means only a faiure to obtain the requested response (due to
* transport problem -- timeout, CRC, etc.). The implementation only
* assures that the response is returned intacta and does not check errors
* within the response itself.
*
****************************************************************************/
static int esp32s3_recvshortcrc(struct sdio_dev_s *dev, uint32_t cmd,
uint32_t *rshort)
{
uint32_t regval;
int ret = OK;
mcinfo("cmd=%04x\n", cmd);
/* R1 Command response (48-bit)
* 47 0 Start bit
* 46 0 Transmission bit (0=from card)
* 45:40 bit5 - bit0 Command index (0-63)
* 39:8 bit31 - bit0 32-bit card status
* 7:1 bit6 - bit0 CRC7
* 0 1 End bit
*
* R1b Identical to R1 with the additional busy signaling via the data
* line.
*
* R6 Published RCA Response (48-bit, SD card only)
* 47 0 Start bit
* 46 0 Transmission bit (0=from card)
* 45:40 bit5 - bit0 Command index (0-63)
* 39:8 bit31 - bit0 32-bit Argument Field, consisting of:
* [31:16] New published RCA of card
* [15:0] Card status bits {23,22,19,12:0}
* 7:1 bit6 - bit0 CRC7
* 0 1 End bit
*/
#ifdef CONFIG_DEBUG_FEATURES
if (!rshort)
{
mcerr("ERROR: rshort=NULL\n");
ret = -EINVAL;
}
/* Check that this is the correct response to this command */
else if ((cmd & MMCSD_RESPONSE_MASK) != MMCSD_R1_RESPONSE &&
(cmd & MMCSD_RESPONSE_MASK) != MMCSD_R1B_RESPONSE &&
(cmd & MMCSD_RESPONSE_MASK) != MMCSD_R6_RESPONSE)
{
mcerr("ERROR: Wrong response CMD=%04x\n", cmd);
ret = -EINVAL;
}
else
#endif
{
/* Check if a timeout or CRC error occurred */
regval = esp32s3_getreg(ESP32S3_SDMMC_RINTSTS);
if ((regval & SDMMC_INT_RTO) != 0)
{
mcerr("ERROR: Command timeout: %08x\n", regval);
ret = -ETIMEDOUT;
}
else if ((regval & SDMMC_INT_RCRC) != 0)
{
mcerr("ERROR: CRC failure: %08x\n", regval);
ret = -EIO;
}
}
/* Clear all pending message completion events and return the R1/R6
* response.
*/
esp32s3_putreg(SDCARD_RESPDONE_CLEAR | SDCARD_CMDDONE_CLEAR,
ESP32S3_SDMMC_RINTSTS);
*rshort = esp32s3_getreg(ESP32S3_SDMMC_RESP0);
mcinfo("CRC=%04x\n", *rshort);
return ret;
}
/****************************************************************************
* Name: esp32s3_recvlong
*
* Description:
* Receive response to SDIO command. Only the critical payload is
* returned -- 128 bits for 136 bit status. The driver implementation
* verifies the correctness of the remaining, non-returned bits (CRCs, CMD
* index, etc.).
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* Rx - Buffer in which to receive the response
*
* Returned Value:
* Number of bytes sent on success; a negated errno on failure. Here a
* failure means only a faiure to obtain the requested response (due to
* transport problem -- timeout, CRC, etc.). The implementation only
* assures that the response is returned intacta and does not check errors
* within the response itself.
*
****************************************************************************/
static int esp32s3_recvlong(struct sdio_dev_s *dev, uint32_t cmd,
uint32_t rlong[4])
{
uint32_t regval;
int ret = OK;
mcinfo("cmd=%04x\n", cmd);
/* R2 CID, CSD register (136-bit)
* 135 0 Start bit
* 134 0 Transmission bit (0=from card)
* 133:128 bit5 - bit0 Reserved
* 127:1 bit127 - bit1 127-bit CID or CSD register
* (including internal CRC)
* 0 1 End bit
*/
#ifdef CONFIG_DEBUG_FEATURES
/* Check that R1 is the correct response to this command */
if ((cmd & MMCSD_RESPONSE_MASK) != MMCSD_R2_RESPONSE)
{
mcerr("ERROR: Wrong response CMD=%04x\n", cmd);
ret = -EINVAL;
}
else
#endif
{
/* Check if a timeout or CRC error occurred */
regval = esp32s3_getreg(ESP32S3_SDMMC_RINTSTS);
if (regval & SDMMC_INT_RTO)
{
mcerr("ERROR: Timeout STA: %08x\n", regval);
ret = -ETIMEDOUT;
}
else if (regval & SDMMC_INT_RCRC)
{
mcerr("ERROR: CRC fail STA: %08x\n", regval);
ret = -EIO;
}
}
/* Return the long response */
esp32s3_putreg(SDCARD_RESPDONE_CLEAR | SDCARD_CMDDONE_CLEAR,
ESP32S3_SDMMC_RINTSTS);
if (rlong)
{
rlong[0] = esp32s3_getreg(ESP32S3_SDMMC_RESP3);
rlong[1] = esp32s3_getreg(ESP32S3_SDMMC_RESP2);
rlong[2] = esp32s3_getreg(ESP32S3_SDMMC_RESP1);
rlong[3] = esp32s3_getreg(ESP32S3_SDMMC_RESP0);
}
return ret;
}
/****************************************************************************
* Name: esp32s3_recvshort
*
* Description:
* Receive response to SDIO command. Only the critical payload is
* returned -- 32 bits for 48 bit status. The driver implementation
* verifies the correctness of the remaining, non-returned bits (CMD
* index, etc., not including CRC).
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* Rx - Buffer in which to receive the response
*
* Returned Value:
* Number of bytes sent on success; a negated errno on failure. Here a
* failure means only a faiure to obtain the requested response (due to
* transport problem -- timeout, CRC, etc.). The implementation only
* assures that the response is returned intacta and does not check errors
* within the response itself.
*
****************************************************************************/
static int esp32s3_recvshort(struct sdio_dev_s *dev, uint32_t cmd,
uint32_t *rshort)
{
uint32_t regval;
int ret = OK;
mcinfo("cmd=%04x\n", cmd);
/* R3 OCR (48-bit)
* 47 0 Start bit
* 46 0 Transmission bit (0=from card)
* 45:40 bit5 - bit0 Reserved
* 39:8 bit31 - bit0 32-bit OCR register
* 7:1 bit6 - bit0 Reserved
* 0 1 End bit
*/
/* Check that this is the correct response to this command */
#ifdef CONFIG_DEBUG_FEATURES
if ((cmd & MMCSD_RESPONSE_MASK) != MMCSD_R3_RESPONSE &&
(cmd & MMCSD_RESPONSE_MASK) != MMCSD_R7_RESPONSE)
{
mcerr("ERROR: Wrong response CMD=%04x\n", cmd);
ret = -EINVAL;
}
else
#endif
{
/* Check if a timeout occurred (Apparently a CRC error can terminate
* a good response)
*/
regval = esp32s3_getreg(ESP32S3_SDMMC_RINTSTS);
if (regval & SDMMC_INT_RTO)
{
mcerr("ERROR: Timeout STA: %08x\n", regval);
ret = -ETIMEDOUT;
}
}
esp32s3_putreg(SDCARD_RESPDONE_CLEAR | SDCARD_CMDDONE_CLEAR,
ESP32S3_SDMMC_RINTSTS);
if (rshort)
{
*rshort = esp32s3_getreg(ESP32S3_SDMMC_RESP0);
}
return ret;
}
/****************************************************************************
* Name: esp32s3_waitenable
*
* Description:
* Enable/disable of a set of SDIO wait events. This is part of the
* the SDIO_WAITEVENT sequence. The set of to-be-waited-for events is
* configured before calling esp32s3_eventwait. This is done in this way
* to help the driver to eliminate race conditions between the command
* setup and the subsequent events.
*
* The enabled events persist until either (1) SDIO_WAITENABLE is called
* again specifying a different set of wait events, or (2) SDIO_EVENTWAIT
* returns.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* eventset - A bitset of events to enable or disable (see SDIOWAIT_*
* definitions). 0=disable; 1=enable.
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32s3_waitenable(struct sdio_dev_s *dev,
sdio_eventset_t eventset, uint32_t timeout)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
uint32_t waitmask;
mcinfo("eventset=%04x\n", (unsigned int)eventset);
DEBUGASSERT(priv != NULL);
/* Disable event-related interrupts */
esp32s3_config_waitints(priv, 0, 0, 0);
/* Select the interrupt mask that will give us the appropriate wakeup
* interrupts.
*/
waitmask = 0;
if ((eventset & SDIOWAIT_CMDDONE) != 0)
{
waitmask |= SDCARD_CMDDONE_MASK;
}
if ((eventset & SDIOWAIT_RESPONSEDONE) != 0)
{
waitmask |= SDCARD_RESPDONE_MASK;
}
if ((eventset & SDIOWAIT_TRANSFERDONE) != 0)
{
waitmask |= SDCARD_XFRDONE_MASK;
}
/* Enable event-related interrupts */
esp32s3_config_waitints(priv, waitmask, eventset, 0);
/* Check if the timeout event is specified in the event set */
if ((priv->waitevents & SDIOWAIT_TIMEOUT) != 0)
{
int delay;
int ret;
/* Yes.. Handle a cornercase: The user request a timeout event but
* with timeout == 0?
*/
if (!timeout)
{
priv->wkupevent = SDIOWAIT_TIMEOUT;
return;
}
/* Start the watchdog timer */
delay = MSEC2TICK(timeout);
ret = wd_start(&priv->waitwdog, delay,
esp32s3_eventtimeout, (wdparm_t)priv);
if (ret < 0)
{
mcerr("ERROR: wd_start failed: %d\n", ret);
}
}
}
/****************************************************************************
* Name: esp32s3_eventwait
*
* Description:
* Wait for one of the enabled events to occur (or a timeout). Note that
* all events enabled by SDIO_WAITEVENTS are disabled when
* esp32s3_eventwait returns. SDIO_WAITEVENTS must be called again before
* esp32s3_eventwait can be used again.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* timeout - Maximum time in milliseconds to wait. Zero means immediate
* timeout with no wait. The timeout value is ignored if
* SDIOWAIT_TIMEOUT is not included in the waited-for eventset.
*
* Returned Value:
* Event set containing the event(s) that ended the wait. Should always
* be non-zero. All events are disabled after the wait concludes.
*
****************************************************************************/
static sdio_eventset_t esp32s3_eventwait(struct sdio_dev_s *dev)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
sdio_eventset_t wkupevent = 0;
irqstate_t flags;
int ret;
/* There is a race condition here... the event may have completed before
* we get here. In this case waitevents will be zero, but wkupevents will
* be non-zero (and, hopefully, the semaphore count will also be non-zero.
*/
flags = enter_critical_section();
DEBUGASSERT(priv->waitevents != 0 || priv->wkupevent != 0);
/* Loop until the event (or the timeout occurs). Race conditions are
* avoided by calling esp32s3_waitenable prior to triggering the logic that
* will cause the wait to terminate. Under certain race conditions, the
* waited-for may have already occurred before this function was called!
*/
for (; ; )
{
/* Wait for an event in event set to occur. If this the event has
* already occurred, then the semaphore will already have been
* incremented and there will be no wait.
*/
ret = nxsem_wait_uninterruptible(&priv->waitsem);
if (ret < 0)
{
/* Task canceled. Cancel the wdog -- assuming it was started and
* return an SDIO error.
*/
wd_cancel(&priv->waitwdog);
wkupevent = SDIOWAIT_ERROR;
goto out;
}
wkupevent = priv->wkupevent;
/* Check if the event has occurred. When the event has occurred, then
* evenset will be set to 0 and wkupevent will be set to a nonzero
* value.
*/
if (wkupevent != 0)
{
/* Yes... break out of the loop with wkupevent non-zero */
break;
}
}
/* Disable all transfer- and event- related interrupts */
esp32s3_disable_allints(priv);
out:
leave_critical_section(flags);
#if defined(CONFIG_ESP32S3_SDMMC_DMA) && defined(CONFIG_ESP32S3_SPIRAM)
if (!esp32s3_ptr_dma_capable(priv->buffer) && priv->dma_buf)
{
if (!priv->wrdir && wkupevent == SDIOWAIT_TRANSFERDONE)
{
memcpy(priv->buffer, priv->dma_buf, priv->dma_buf_size);
}
kmm_free(priv->dma_buf);
priv->dma_buf = NULL;
}
#endif
mcinfo("wkupevent=%04x\n", wkupevent);
return wkupevent;
}
/****************************************************************************
* Name: esp32s3_callbackenable
*
* Description:
* Enable/disable of a set of SDIO callback events. This is part of the
* the SDIO callback sequence. The set of events is configured to enabled
* callbacks to the function provided in esp32s3_registercallback.
*
* Events are automatically disabled once the callback is performed and no
* further callback events will occur until they are again enabled by
* calling this method.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* eventset - A bitset of events to enable or disable (see SDIOMEDIA_*
* definitions). 0=disable; 1=enable.
*
* Returned Value:
* None
*
****************************************************************************/
static void esp32s3_callbackenable(struct sdio_dev_s *dev,
sdio_eventset_t eventset)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
mcinfo("eventset: %02x\n", eventset);
DEBUGASSERT(priv != NULL);
priv->cbevents = eventset;
esp32s3_callback(priv);
}
/****************************************************************************
* Name: esp32s3_registercallback
*
* Description:
* Register a callback that that will be invoked on any media status
* change. Callbacks should not be made from interrupt handlers, rather
* interrupt level events should be handled by calling back on the work
* thread.
*
* When this method is called, all callbacks should be disabled until they
* are enabled via a call to SDIO_CALLBACKENABLE
*
* Input Parameters:
* dev - Device-specific state data
* callback - The function to call on the media change
* arg - A caller provided value to return with the callback
*
* Returned Value:
* 0 on success; negated errno on failure.
*
****************************************************************************/
static int esp32s3_registercallback(struct sdio_dev_s *dev,
worker_t callback, void *arg)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
/* Disable callbacks and register this callback and is argument */
mcinfo("Register %p(%p)\n", callback, arg);
DEBUGASSERT(priv != NULL);
priv->cbevents = 0;
priv->cbarg = arg;
priv->callback = callback;
return OK;
}
#ifdef CONFIG_ESP32S3_SDMMC_DMA
static int esp32s3_fill_dma_desc(struct esp32s3_dev_s *priv)
{
uint32_t ctrl;
uint32_t maxs;
int i = 0;
size_t buflen = priv->remaining;
uint32_t buffer = (uint32_t)priv->buffer;
#ifdef CONFIG_ESP32S3_SPIRAM
if (!esp32s3_ptr_dma_capable(priv->buffer))
{
priv->dma_buf = kmm_memalign(16, buflen);
if (!priv->dma_buf)
{
return -ENOMEM;
}
priv->dma_buf_size = buflen;
buffer = (uint32_t)priv->dma_buf;
if (priv->wrdir)
{
memcpy(priv->dma_buf, priv->buffer, buflen);
}
}
#endif
/* Setup DMA list */
while (buflen > 0)
{
/* Limit size of the transfer to maximum buffer size */
maxs = buflen;
if (maxs > MCI_DMADES1_MAXTR)
{
maxs = MCI_DMADES1_MAXTR;
}
buflen -= maxs;
/* Set buffer size */
priv->dma_desc[i].des1 = MCI_DMADES1_BS1(maxs);
/* Setup buffer address (chained) */
priv->dma_desc[i].des2 = buffer + (i * MCI_DMADES1_MAXTR);
/* Setup basic control */
ctrl = MCI_DMADES0_OWN | MCI_DMADES0_CH;
if (i == 0)
{
ctrl |= MCI_DMADES0_FS; /* First DMA buffer */
}
/* No more data? Then this is the last descriptor */
if (buflen == 0)
{
ctrl |= MCI_DMADES0_LD;
priv->dma_desc[i].des3 = 0;
}
else
{
ctrl |= MCI_DMADES0_DIC;
priv->dma_desc[i].des3 = (uint32_t)&priv->dma_desc[i + 1];
}
priv->dma_desc[i].des0 = ctrl;
i++;
}
DEBUGASSERT(i < NUM_DMA_DESCRIPTORS);
return 0;
}
#endif
/****************************************************************************
* Name: esp32s3_dmarecvsetup
*
* Description:
* Setup to perform a read DMA. If the processor supports a data cache,
* then this method will also make sure that the contents of the DMA memory
* and the data cache are coherent. For read transfers this may mean
* invalidating the data cache.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* buffer - The memory to DMA from
* buflen - The size of the DMA transfer in bytes
*
* Returned Value:
* OK on success; a negated errno on failure
*
****************************************************************************/
#ifdef CONFIG_ESP32S3_SDMMC_DMA
static int esp32s3_dmarecvsetup(struct sdio_dev_s *dev, uint8_t *buffer,
size_t buflen)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
uint32_t regval;
/* Don't bother with DMA if the entire transfer will fit in the RX FIFO or
* if we do not have a 4-bit wide bus.
*/
DEBUGASSERT(priv != NULL);
DEBUGASSERT(buffer != NULL && buflen > 0 && ((uint32_t)buffer & 3) == 0);
/* Save the destination buffer information for use by the interrupt
* handler.
*/
priv->buffer = (uint32_t *)buffer;
priv->remaining = buflen;
priv->wrdir = false;
/* Setup DMA list */
if (esp32s3_fill_dma_desc(priv))
{
return -ENOMEM;
}
/* Flush ints before we start */
esp32s3_putreg(SDCARD_TRANSFER_ALL, ESP32S3_SDMMC_RINTSTS);
/* Enable internal DMA, burst size of 4, fixed burst */
regval = esp32s3_getreg(ESP32S3_SDMMC_CTRL);
regval |= SDMMC_CTRL_INTDMA;
esp32s3_putreg(regval, ESP32S3_SDMMC_CTRL);
regval = SDMMC_BMOD_DE | SDMMC_BMOD_FB;
esp32s3_putreg(regval, ESP32S3_SDMMC_BMOD);
esp32s3_putreg(1, ESP32S3_SDMMC_PLDMND);
/* Setup DMA error interrupts */
esp32s3_config_dmaints(priv, SDCARD_DMARECV_MASK, SDCARD_DMAERROR_MASK);
return OK;
}
#endif
/****************************************************************************
* Name: esp32s3_dmasendsetup
*
* Description:
* Setup to perform a write DMA. If the processor supports a data cache,
* then this method will also make sure that the contents of the DMA memory
* and the data cache are coherent. For write transfers, this may mean
* flushing the data cache.
*
* Input Parameters:
* dev - An instance of the SDIO device interface
* buffer - The memory to DMA into
* buflen - The size of the DMA transfer in bytes
*
* Returned Value:
* OK on success; a negated errno on failure
*
****************************************************************************/
#ifdef CONFIG_ESP32S3_SDMMC_DMA
static int esp32s3_dmasendsetup(struct sdio_dev_s *dev,
const uint8_t *buffer, size_t buflen)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)dev;
uint32_t regval;
/* Don't bother with DMA if the entire transfer will fit in the TX FIFO or
* if we do not have a 4-bit wide bus.
*/
DEBUGASSERT(priv != NULL);
mcinfo("buflen=%lu\n", (unsigned long)buflen);
DEBUGASSERT(buffer != NULL && buflen > 0 && ((uint32_t)buffer & 3) == 0);
/* Save the destination buffer information for use by the interrupt
* handler.
*/
priv->buffer = (uint32_t *)buffer;
priv->remaining = buflen;
priv->wrdir = true;
/* Setup DMA descriptor list */
if (esp32s3_fill_dma_desc(priv))
{
return -ENOMEM;
}
/* Flush ints before we start */
esp32s3_putreg(SDCARD_TRANSFER_ALL, ESP32S3_SDMMC_RINTSTS);
/* Enable internal DMA, fixed burst */
regval = esp32s3_getreg(ESP32S3_SDMMC_CTRL);
regval |= SDMMC_CTRL_INTDMA;
esp32s3_putreg(regval, ESP32S3_SDMMC_CTRL);
regval = SDMMC_BMOD_DE | SDMMC_BMOD_FB;
esp32s3_putreg(regval, ESP32S3_SDMMC_BMOD);
esp32s3_putreg(1, ESP32S3_SDMMC_PLDMND);
/* Setup DMA error interrupts */
esp32s3_config_dmaints(priv, SDCARD_DMASEND_MASK, SDCARD_DMAERROR_MASK);
return OK;
}
#endif
/****************************************************************************
* Name: esp32s3_callback
*
* Description:
* Perform callback.
*
* Assumptions:
* This function does not execute in the context of an interrupt handler.
* It may be invoked on any user thread or scheduled on the work thread
* from an interrupt handler.
*
****************************************************************************/
static void esp32s3_callback(void *arg)
{
struct esp32s3_dev_s *priv = (struct esp32s3_dev_s *)arg;
/* Is a callback registered? */
DEBUGASSERT(priv != NULL);
mcinfo("Callback %p(%p) cbevents: %02x cdstatus: %02x\n",
priv->callback, priv->cbarg, priv->cbevents, priv->cdstatus);
if (priv->callback)
{
/* Yes.. Check for enabled callback events */
if ((priv->cdstatus & SDIO_STATUS_PRESENT) != 0)
{
/* Media is present. Is the media inserted event enabled? */
if ((priv->cbevents & SDIOMEDIA_INSERTED) == 0)
{
/* No... return without performing the callback */
return;
}
}
else
{
/* Media is not present. Is the media eject event enabled? */
if ((priv->cbevents & SDIOMEDIA_EJECTED) == 0)
{
/* No... return without performing the callback */
return;
}
}
/* Perform the callback, disabling further callbacks. Of course, the
* the callback can (and probably should) re-enable callbacks.
*/
priv->cbevents = 0;
/* Callbacks cannot be performed in the context of an interrupt
* handler. If we are in an interrupt handler, then queue the
* callback to be performed later on the work thread.
*/
if (up_interrupt_context())
{
/* Yes.. queue it */
mcinfo("Queuing callback to %p(%p)\n",
priv->callback, priv->cbarg);
work_queue(HPWORK, &priv->cbwork, priv->callback,
priv->cbarg, 0);
}
else
{
/* No.. then just call the callback here */
mcinfo("Callback to %p(%p)\n", priv->callback, priv->cbarg);
priv->callback(priv->cbarg);
}
}
}
const static sdmmc_slot_info_t sdmmc_slot_info[] =
{
{
.card_detect = SDHOST_CARD_DETECT_N_1_IDX,
.write_protect = SDHOST_CARD_WRITE_PRT_1_IDX,
.card_int = SDHOST_CARD_INT_N_1_IDX,
},
{
.card_detect = SDHOST_CARD_DETECT_N_2_IDX,
.write_protect = SDHOST_CARD_WRITE_PRT_2_IDX,
.card_int = SDHOST_CARD_INT_N_2_IDX,
}
};
const static sdmmc_slot_io_info_t sdmmc_slot_gpio_sig[] =
{
{
.clk = SDHOST_CCLK_OUT_1_IDX,
.cmd = SDHOST_CCMD_OUT_1_IDX,
.d0 = SDHOST_CDATA_OUT_10_IDX,
.d1 = SDHOST_CDATA_OUT_11_IDX,
.d2 = SDHOST_CDATA_OUT_12_IDX,
.d3 = SDHOST_CDATA_OUT_13_IDX,
.d4 = SDHOST_CDATA_OUT_14_IDX,
.d5 = SDHOST_CDATA_OUT_15_IDX,
.d6 = SDHOST_CDATA_OUT_16_IDX,
.d7 = SDHOST_CDATA_OUT_17_IDX,
},
{
.clk = SDHOST_CCLK_OUT_2_IDX,
.cmd = SDHOST_CCMD_OUT_2_IDX,
.d0 = SDHOST_CDATA_OUT_20_IDX,
.d1 = SDHOST_CDATA_OUT_21_IDX,
.d2 = SDHOST_CDATA_OUT_22_IDX,
.d3 = SDHOST_CDATA_OUT_23_IDX,
.d4 = SDHOST_CDATA_OUT_24_IDX,
.d5 = SDHOST_CDATA_OUT_25_IDX,
.d6 = SDHOST_CDATA_OUT_26_IDX,
.d7 = SDHOST_CDATA_OUT_27_IDX,
}
};
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sdio_initialize
*
* Description:
* Initialize SDIO for operation.
*
* Input Parameters:
* slotno - Not used.
*
* Returned Value:
* A reference to an SDIO interface structure. NULL is returned on
* failures.
*
****************************************************************************/
struct sdio_dev_s *sdio_initialize(int slotno)
{
uint32_t regval;
struct esp32s3_dev_s *priv = &g_sdiodev;
priv->slot = slotno;
priv->slot_info = &sdmmc_slot_info[slotno];
priv->sdio_pins = &sdmmc_slot_gpio_sig[slotno];
/* enable bus clock */
regval = esp32s3_getreg(SYSTEM_PERIP_CLK_EN1_REG);
regval |= SYSTEM_SDIO_HOST_CLK_EN;
esp32s3_putreg(regval, SYSTEM_PERIP_CLK_EN1_REG);
/* reset registers */
regval = esp32s3_getreg(SYSTEM_PERIP_RST_EN1_REG);
regval |= SYSTEM_SDIO_HOST_RST;
esp32s3_putreg(regval, SYSTEM_PERIP_RST_EN1_REG);
regval &= ~SYSTEM_SDIO_HOST_RST;
esp32s3_putreg(regval, SYSTEM_PERIP_RST_EN1_REG);
/* Reset */
priv->dev.reset(&priv->dev);
/* Pin configuration */
configure_pin(CONFIG_ESP32S3_SDMMC_CLK, priv->sdio_pins->clk, OUTPUT);
configure_pin(CONFIG_ESP32S3_SDMMC_CMD, priv->sdio_pins->cmd,
INPUT | OUTPUT | PULLUP);
configure_pin(CONFIG_ESP32S3_SDMMC_D0, priv->sdio_pins->d0,
INPUT | OUTPUT | PULLUP);
esp32s3_gpio_matrix_in(GPIO_MATRIX_CONST_ONE_INPUT,
priv->slot_info->card_int, false);
esp32s3_gpio_matrix_in(GPIO_MATRIX_CONST_ZERO_INPUT,
priv->slot_info->card_detect, false);
esp32s3_gpio_matrix_in(GPIO_MATRIX_CONST_ONE_INPUT,
priv->slot_info->write_protect, true);
return &priv->dev;
}
#endif /* CONFIG_ESP32S3_SDMMC */