mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
2b6f65a08a
This changes the API of xfa from XFA(array_name, prio) type element_name = INITIALIZER; to XFA(type, array_name, prio) element_name = INITIALIZER; this allows forcing natural alignment of the type, fixing failing tests on `native64`.
901 lines
27 KiB
C
901 lines
27 KiB
C
/*
|
|
* Copyright (C) 2023 Gunar Schorcht
|
|
* 2023 Benjamin Valentin
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* @brief Low-level SDIO/SD/MMC driver for SD Host Controller peripherals
|
|
*
|
|
* The module implements a the low-level SDIO/SD/MMC driver for peripherals
|
|
* that are compliant with the SD Host Controller Simplified Specification,
|
|
* Version 3.00 [[sdcard.org](https://www.sdcard.org)]. It is intended
|
|
* exclusively for use as a low-level driver for the SDIO/SD/MMC API
|
|
* (module `sdmmc`).
|
|
*
|
|
* @note The driver uses the definition of the SD Host Controller interface
|
|
* from the Atmel SAME54 Series Device Support Package (1.1.134)
|
|
* [http://packs.download.atmel.com/].
|
|
*
|
|
* @note Some parts of the driver were inspired by the implementation in
|
|
* https://github.com/alkgrove/initmaker/blob/master/samd5x/src/sd.c.
|
|
*
|
|
* @author Gunar Schorcht <gunar@schorcht.net>
|
|
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "assert.h"
|
|
#include "macros/math.h"
|
|
#include "periph_cpu.h"
|
|
#include "periph/gpio.h"
|
|
#include "periph/pm.h"
|
|
#include "time_units.h"
|
|
#include "ztimer.h"
|
|
|
|
#include "sdmmc/sdmmc.h"
|
|
#include "sdmmc/vendor/sdhc.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#if defined(CPU_SAMD5X) || defined(CPU_SAME5X)
|
|
|
|
#ifndef SDHC_CLOCK
|
|
#define SDHC_CLOCK SAM0_GCLK_MAIN
|
|
#endif
|
|
|
|
#ifndef SDHC_CLOCK_SLOW
|
|
#define SDHC_CLOCK_SLOW SAM0_GCLK_TIMER
|
|
#endif
|
|
|
|
#else
|
|
#error "CPU not supported"
|
|
#endif
|
|
|
|
/* 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
|
|
#define CONFIG_SDMMC_CLK_MAX MHZ(20)
|
|
#elif CONFIG_SDMMC_CLK_MAX_25MHZ
|
|
#define CONFIG_SDMMC_CLK_MAX MHZ(25)
|
|
#else
|
|
#define CONFIG_SDMMC_CLK_MAX MHZ(50)
|
|
#endif
|
|
|
|
/* millisecond timer definitions dependent on active ztimer backend */
|
|
#if IS_USED(MODULE_ZTIMER_MSEC)
|
|
#define _ZTIMER_ACQUIRE() ztimer_acquire(ZTIMER_MSEC)
|
|
#define _ZTIMER_RELEASE() ztimer_release(ZTIMER_MSEC)
|
|
#define _ZTIMER_NOW() ztimer_now(ZTIMER_MSEC)
|
|
#define _ZTIMER_SLEEP_MS(n) ztimer_sleep(ZTIMER_MSEC, n)
|
|
#elif IS_USED(MODULE_ZTIMER_USEC)
|
|
#define _ZTIMER_ACQUIRE() ztimer_acquire(ZTIMER_USEC)
|
|
#define _ZTIMER_RELEASE() ztimer_release(ZTIMER_USEC)
|
|
#define _ZTIMER_NOW() ztimer_now(ZTIMER_USEC) / US_PER_MS
|
|
#define _ZTIMER_SLEEP_MS(n) ztimer_sleep(ZTIMER_USEC, n * US_PER_MS)
|
|
#else
|
|
#error "Either module ztimer_msec or ztimer_usec is needed"
|
|
#endif
|
|
|
|
/* Monitor card insertion and removal */
|
|
#define SDHC_NISTR_CARD_DETECT (SDHC_NISTR_CREM | SDHC_NISTR_CINS)
|
|
#define SDHC_NISTER_CARD_DETECT (SDHC_NISTER_CREM | SDHC_NISTER_CINS)
|
|
#define SDHC_NISIER_CARD_DETECT (SDHC_NISIER_CREM | SDHC_NISIER_CINS)
|
|
|
|
#include "board.h"
|
|
|
|
/* forward declaration of _driver */
|
|
static const sdmmc_driver_t _driver;
|
|
|
|
/* SDHC device context */
|
|
typedef struct {
|
|
sdmmc_dev_t sdmmc_dev; /**< Inherited sdmmc_dev_t struct */
|
|
const sdhc_conf_t *conf; /**< SDHC peripheral config reference */
|
|
mutex_t irq_wait; /**< ISR mutex */
|
|
uint16_t error; /**< last SDHC error status (EISTR) */
|
|
bool data_transfer; /**< Transfer active */
|
|
} sdhc_dev_t;
|
|
|
|
/* SDHC device context array */
|
|
static sdhc_dev_t _sdhc_devs[] = {
|
|
{
|
|
.sdmmc_dev = {
|
|
.driver = &_driver,
|
|
},
|
|
.conf = &sdhc_config[0],
|
|
.irq_wait = MUTEX_INIT_LOCKED,
|
|
},
|
|
#if SDHC_CONFIG_NUMOF > 1
|
|
{
|
|
.sdmmc_dev = {
|
|
.driver = &_driver,
|
|
},
|
|
.conf = &sdhc_config[1],
|
|
.irq_wait = MUTEX_INIT_LOCKED,
|
|
}
|
|
#endif
|
|
};
|
|
|
|
/* sanity check of configuration */
|
|
static_assert(SDHC_CONFIG_NUMOF == ARRAY_SIZE(sdhc_config),
|
|
"SDHC_CONFIG_NUMOF and the number of elements in sdhc_config differ");
|
|
static_assert(SDHC_CONFIG_NUMOF == ARRAY_SIZE(_sdhc_devs),
|
|
"SDHC_CONFIG_NUMOF and the number of elements in sdhc_devs differ");
|
|
|
|
/* check that the number of devices does not exhaust the number of available devices */
|
|
#ifdef SDHC1
|
|
static_assert(SDHC_CONFIG_NUMOF < 3, "MCU supports only 2 SDHC peripherals");
|
|
#else
|
|
static_assert(SDHC_CONFIG_NUMOF < 2, "MCU supports only 1 SDHC peripheral");
|
|
#endif
|
|
|
|
XFA_CONST(sdmmc_dev_t * const, sdmmc_devs, 0) _sdmmc_0 = (sdmmc_dev_t * const)&_sdhc_devs[0];
|
|
#if SDHC_CONFIG_NUMOF > 1
|
|
XFA_CONST(sdmmc_dev_t * const, sdmmc_devs, 0) _sdmmc_1 = (sdmmc_dev_t * const)&_sdhc_devs[1];
|
|
#endif
|
|
|
|
static int _set_clock_rate(sdmmc_dev_t *dev, sdmmc_clock_rate_t rate);
|
|
|
|
/* forward declaration of internal functions */
|
|
static void _core_init(sdhc_dev_t *sdhc_dev);
|
|
static void _init_pins(sdhc_dev_t *sdhc_dev);
|
|
|
|
static void _reset_sdhc(sdhc_dev_t *sdhc_dev, uint8_t type);
|
|
static int _wait_sdhc_busy(sdhc_t *sdhc);
|
|
static bool _wait_for_event(sdhc_dev_t *sdhc_dev,
|
|
uint16_t event, uint16_t error_mask,
|
|
uint8_t reset);
|
|
static int _enable_sd_clk(sdhc_dev_t *sdhc_dev);
|
|
static int _disable_sd_clk(sdhc_dev_t *sdhc_dev);
|
|
static int _sdhc_to_sdmmc_err_code(uint16_t code);
|
|
static void _isr(sdhc_dev_t *dev);
|
|
|
|
static sdhc_dev_t *isr_ctx_0;
|
|
#ifdef SDHC1
|
|
static sdhc_dev_t *isr_ctx_1;
|
|
#endif
|
|
|
|
static void _init(sdmmc_dev_t *dev)
|
|
{
|
|
sdhc_dev_t *sdhc_dev = container_of(dev, sdhc_dev_t, sdmmc_dev);
|
|
|
|
assert(sdhc_dev);
|
|
assert(sdhc_dev->conf);
|
|
assert(sdhc_dev->conf->sdhc);
|
|
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
#ifdef SDHC1
|
|
assert((sdhc == (void *)SDHC0) || (sdhc == (void *)SDHC1));
|
|
#else
|
|
assert(sdhc == (void *)SDHC0);
|
|
#endif
|
|
|
|
_core_init(sdhc_dev);
|
|
|
|
/* pins have to initialized after enabling the clock for the SDHC core */
|
|
_init_pins(sdhc_dev);
|
|
|
|
/* Enable all the status bits in NISTR and EISTR */
|
|
sdhc->NISTER.reg = SDHC_NISTER_MASK;
|
|
sdhc->EISTER.reg = SDHC_EISTER_MASK;
|
|
|
|
sdhc->PCR.bit.SDBVSEL = SDHC_PCR_SDBVSEL_3V3_Val;
|
|
sdhc->PCR.bit.SDBPWR = 1;
|
|
|
|
if (sdhc == (void *)SDHC0) {
|
|
isr_ctx_0 = sdhc_dev;
|
|
NVIC_ClearPendingIRQ(SDHC0_IRQn);
|
|
NVIC_SetPriority(SDHC0_IRQn, 1);
|
|
NVIC_EnableIRQ(SDHC0_IRQn);
|
|
}
|
|
#ifdef SDHC1
|
|
else if (sdhc == (void *)SDHC1) {
|
|
isr_ctx_1 = sdhc_dev;
|
|
NVIC_EnableIRQ(SDHC1_IRQn);
|
|
NVIC_SetPriority(SDHC1_IRQn, 1);
|
|
NVIC_EnableIRQ(SDHC1_IRQn);
|
|
}
|
|
#endif
|
|
|
|
sdhc->TCR.reg = 14; /* max timeout is 14 or about 1sec */
|
|
sdhc->PCR.reg = SDHC_PCR_SDBVSEL_3V3;
|
|
|
|
sdhc->NISTER.reg = SDHC_NISTER_MASK; /* enable all normal interrupt status flags */
|
|
sdhc->EISTER.reg = SDHC_EISTER_MASK; /* enable all error interrupt status flags */
|
|
|
|
sdhc->NISIER.reg = SDHC_NISIER_CARD_DETECT; /* enable card detection interrupt signals */
|
|
|
|
/* set the clock rate to enable the internal clock of the SDHC which is
|
|
* needed for card detection interrupts. */
|
|
_set_clock_rate(dev, SDMMC_CLK_400K);
|
|
|
|
dev->present = true;
|
|
}
|
|
|
|
static int _send_cmd(sdmmc_dev_t *dev, uint8_t cmd_idx, uint32_t arg,
|
|
uint8_t resp_type, uint32_t *resp)
|
|
{
|
|
/* ensure that `sdmmc_send_acmd` is used for application specific commands */
|
|
assert((cmd_idx & SDMMC_ACMD_PREFIX) == 0);
|
|
|
|
sdhc_dev_t *sdhc_dev = container_of(dev, sdhc_dev_t, sdmmc_dev);
|
|
|
|
assert(sdhc_dev);
|
|
assert(sdhc_dev->conf);
|
|
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
DEBUG("[sdmmc] send CMD%d with R%d%s\n", cmd_idx,
|
|
resp_type & SDMMC_RESP_IDX, (resp_type & SDMMC_RESP_BUSY) ? "B" : "");
|
|
|
|
uint32_t cmd;
|
|
|
|
/* since the SD bus power is automatically turned off when the card is
|
|
* removed, it has to be turned on when a command is sent if needed */
|
|
if (!(sdhc->PCR.reg & SDHC_PCR_SDBPWR)) {
|
|
sdhc->PCR.reg |= SDHC_PCR_SDBPWR;
|
|
}
|
|
|
|
/* enable the clock to the card if needed */
|
|
if (IS_USED(MODULE_PERIPH_SDMMC_AUTO_CLK) && _enable_sd_clk(sdhc_dev)) {
|
|
return -EIO;
|
|
}
|
|
|
|
/* wait if card is still busy */
|
|
if (_wait_sdhc_busy(sdhc)) {
|
|
/* if timeout occurs, there is a serious situation, in this case
|
|
* reset the entire peripheral */
|
|
_reset_sdhc(sdhc_dev, SDHC_SRR_SWRSTALL);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/* clear command related normal interrupt status flags */
|
|
sdhc->NISTR.reg = SDHC_NISTR_TRFC | SDHC_NISTR_CMDC;
|
|
|
|
cmd = SDHC_CR_CMDIDX(cmd_idx) | SDHC_CR_CMDTYP_NORMAL;
|
|
|
|
switch (resp_type) {
|
|
case SDMMC_NO_R:
|
|
break;
|
|
case SDMMC_R2:
|
|
cmd |= SDHC_CR_RESPTYP_136_BIT;
|
|
break;
|
|
case SDMMC_R1B:
|
|
cmd |= SDHC_CR_RESPTYP_48_BIT_BUSY;
|
|
break;
|
|
default:
|
|
cmd |= SDHC_CR_RESPTYP_48_BIT;
|
|
break;
|
|
}
|
|
|
|
if (sdhc_dev->data_transfer) {
|
|
/* command is part of a data transfer, TMR and BCR are already
|
|
* prepared in _xfer_prepare and must not be overwritten */
|
|
cmd |= SDHC_CR_DPSEL_DATA;
|
|
}
|
|
else {
|
|
/* reset TMR and BCR otherwise */
|
|
sdhc->TMR.reg = 0;
|
|
sdhc->BCR.reg = 0;
|
|
}
|
|
|
|
#if defined(CPU_SAMD5X) || defined(CPU_SAME5X)
|
|
Sdhc *sam0_sdhc = (Sdhc *)sdhc;
|
|
/* CMD0, CMD1, CMD2, CMD3 and CMD8 are broadcast commands */
|
|
if ((cmd_idx <= SDMMC_CMD3) || (cmd_idx == SDMMC_CMD8)) {
|
|
sam0_sdhc->MC1R.reg |= SDHC_MC1R_OPD;
|
|
}
|
|
else {
|
|
sam0_sdhc->MC1R.reg &= ~SDHC_MC1R_OPD;
|
|
}
|
|
#endif
|
|
|
|
sdhc_dev->error = 0;
|
|
|
|
/* used error interrupts */
|
|
uint16_t eis = SDHC_EISTR_CMDTEO | SDHC_EISTR_CMDEND | SDHC_EISTR_CMDIDX;
|
|
|
|
eis |= (resp_type & SDMMC_RESP_CRC) ? SDHC_EISTR_CMDCRC : 0;
|
|
eis |= (resp_type & SDMMC_RESP_BUSY) ? SDHC_EISTR_DATTEO : 0;
|
|
|
|
if (sdhc_dev->data_transfer) {
|
|
/* if command is starts a data transfer, also DAT related
|
|
* error interrupts are used */
|
|
eis |= SDHC_EISTR_DATTEO | SDHC_EISTR_DATEND | SDHC_EISTR_DATCRC;
|
|
}
|
|
|
|
/* use TRFC (Transfer Complete) interrupt in case of R1b response with busy
|
|
* and CMDC (Command Complete) otherwise */
|
|
uint16_t nis = (resp_type == SDMMC_R1B) ? SDHC_NISTR_TRFC
|
|
: SDHC_NISTR_CMDC;
|
|
|
|
sdhc->ARG1R.reg = arg; /* setup the argument register */
|
|
sdhc->CR.reg = cmd; /* send command */
|
|
|
|
/* wait until the command is completed or an error occurred */
|
|
if (!_wait_for_event(sdhc_dev, nis, eis, SDHC_SRR_SWRSTCMD)) {
|
|
return _sdhc_to_sdmmc_err_code(sdhc_dev->error);
|
|
}
|
|
|
|
if ((resp_type == SDMMC_R1) || (resp_type == SDMMC_R1B)) {
|
|
dev->status = sdhc->RR[0].reg;
|
|
}
|
|
|
|
if (resp) {
|
|
if (resp_type == SDMMC_R2) {
|
|
resp[0] = (sdhc->RR[3].reg << 8) | (sdhc->RR[2].reg >> 24);
|
|
resp[1] = (sdhc->RR[2].reg << 8) | (sdhc->RR[1].reg >> 24);
|
|
resp[2] = (sdhc->RR[1].reg << 8) | (sdhc->RR[0].reg >> 24);
|
|
resp[3] = (sdhc->RR[0].reg << 8);
|
|
}
|
|
else if (resp_type != SDMMC_NO_R) {
|
|
resp[0] = sdhc->RR[0].reg;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _set_bus_width(sdmmc_dev_t *dev, sdmmc_bus_width_t width)
|
|
{
|
|
sdhc_dev_t *sdhc_dev = container_of(dev, sdhc_dev_t, sdmmc_dev);
|
|
|
|
assert(sdhc_dev);
|
|
assert(sdhc_dev->conf);
|
|
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
#ifdef SDHC_HC1R_EXTDW
|
|
switch (width) {
|
|
case SDMMC_BUS_WIDTH_1BIT:
|
|
sdhc->HC1R.bit.EXTDW = 0;
|
|
sdhc->HC1R.bit.DW = 0;
|
|
break;
|
|
case SDMMC_BUS_WIDTH_4BIT:
|
|
sdhc->HC1R.bit.EXTDW = 0;
|
|
sdhc->HC1R.bit.DW = 1;
|
|
break;
|
|
case SDMMC_BUS_WIDTH_8BIT:
|
|
sdhc->HC1R.bit.EXTDW = 1;
|
|
sdhc->HC1R.bit.DW = 0;
|
|
break;
|
|
}
|
|
#else
|
|
switch (width) {
|
|
case SDMMC_BUS_WIDTH_1BIT:
|
|
sdhc->HC1R.bit.DW = 0;
|
|
break;
|
|
case SDMMC_BUS_WIDTH_4BIT:
|
|
sdhc->HC1R.bit.DW = 1;
|
|
break;
|
|
case SDMMC_BUS_WIDTH_8BIT:
|
|
DEBUG("[sdmmc] Bus width of 8 bit not supported\n");
|
|
return -ENOTSUP;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _set_clock_rate(sdmmc_dev_t *dev, sdmmc_clock_rate_t rate)
|
|
{
|
|
sdhc_dev_t *sdhc_dev = container_of(dev, sdhc_dev_t, sdmmc_dev);
|
|
|
|
assert(sdhc_dev);
|
|
assert(sdhc_dev->conf);
|
|
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
uint32_t fsdhc = (rate > CONFIG_SDMMC_CLK_MAX) ? CONFIG_SDMMC_CLK_MAX
|
|
: rate;
|
|
if (IS_USED(MODULE_SDMMC_MMC) &&
|
|
(dev->type == SDMMC_CARD_TYPE_MMC) && (fsdhc > MHZ(25))) {
|
|
/* maximum frequency supported for MMCs */
|
|
fsdhc = MHZ(25);
|
|
}
|
|
else if (fsdhc > MHZ(50)) {
|
|
/* maximum frequency supported for SD/SDIO High Speed */
|
|
fsdhc = MHZ(50);
|
|
}
|
|
|
|
/* disable the clock to the card if already active */
|
|
if (_disable_sd_clk(sdhc_dev)) {
|
|
return -EIO;
|
|
}
|
|
|
|
uint32_t base_clk = sdhc->CA0R.bit.BASECLKF;
|
|
uint32_t clk_mult = sdhc->CA1R.bit.CLKMULT;
|
|
|
|
#if defined(CPU_SAMD5X) || defined(CPU_SAME5X)
|
|
/* if CA1R.CLKMULT is 0, programmable clock is not supported */
|
|
assert(clk_mult);
|
|
base_clk = (base_clk == 0) ? sam0_gclk_freq(SDHC_CLOCK) / 2 : MHZ(base_clk);
|
|
#endif
|
|
|
|
uint32_t div;
|
|
|
|
/* divider for programmable clock: f_sdclk = f_baseclk / (div + 1) */
|
|
div = DIV_ROUND_UP(base_clk * (clk_mult + 1), fsdhc);
|
|
div = (div) ? div - 1 : div;
|
|
|
|
/* enable programmable clock if multiplier is defined */
|
|
if (clk_mult) {
|
|
sdhc->CCR.reg |= SDHC_CCR_CLKGSEL;
|
|
}
|
|
|
|
/* write the 10 bit clock divider */
|
|
sdhc->CCR.reg &= ~(SDHC_CCR_USDCLKFSEL_Msk | SDHC_CCR_SDCLKFSEL_Msk);
|
|
sdhc->CCR.reg |= SDHC_CCR_SDCLKFSEL(div) | SDHC_CCR_USDCLKFSEL(div >> 8);
|
|
sdhc->CCR.reg |= SDHC_CCR_INTCLKEN; /* enable internal clock */
|
|
while (!sdhc->CCR.bit.INTCLKS) {} /* wait for clock to be stable */
|
|
|
|
#if 0
|
|
/* for testing purposes if it is necessary to enable the SD clock when
|
|
* the clock rate is changed */
|
|
if (!IS_USED(MODULE_PERIPH_SDMMC_AUTO_CLK)) {
|
|
/* if periph_sdmmc_auto_clk is not used, enable the clock to the card here */
|
|
if (_enable_sd_clk(sdhc_dev)) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if !IS_USED(MODULE_PERIPH_SDMMC_AUTO_CLK)
|
|
int _enable_clock(sdmmc_dev_t *dev, bool enable)
|
|
{
|
|
DEBUG("[sdmmc] %s clock\n", enable ? "enable" : "disable");
|
|
|
|
sdhc_dev_t *sdhc_dev = container_of(dev, sdhc_dev_t, sdmmc_dev);
|
|
|
|
return (enable) ? _enable_sd_clk(sdhc_dev)
|
|
: _disable_sd_clk(sdhc_dev);
|
|
}
|
|
#endif
|
|
|
|
static int _xfer_prepare(sdmmc_dev_t *dev, sdmmc_xfer_desc_t *xfer)
|
|
{
|
|
sdhc_dev_t *sdhc_dev = container_of(dev, sdhc_dev_t, sdmmc_dev);
|
|
|
|
assert(sdhc_dev);
|
|
assert(sdhc_dev->conf);
|
|
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
/* SDHC uses 32-bit words */
|
|
/* TODO: at the moment only 32-bit words supported */
|
|
assert((xfer->block_size % sizeof(uint32_t)) == 0);
|
|
|
|
/* indicate that a data transfer is prepared */
|
|
sdhc_dev->data_transfer = true;
|
|
|
|
uint32_t tmr;
|
|
|
|
tmr = xfer->write ? SDHC_TMR_DTDSEL_WRITE : SDHC_TMR_DTDSEL_READ;
|
|
|
|
if (xfer->block_num > 1) {
|
|
tmr |= SDHC_TMR_MSBSEL_MULTIPLE | SDHC_TMR_BCEN;
|
|
tmr |= IS_USED(MODULE_PERIPH_SDMMC_AUTO_CMD12) ? SDHC_TMR_ACMDEN_CMD12 : 0;
|
|
}
|
|
else {
|
|
tmr |= SDHC_TMR_MSBSEL_SINGLE;
|
|
}
|
|
|
|
sdhc->TMR.reg = tmr;
|
|
sdhc->BSR.reg = SDHC_BSR_BLOCKSIZE(xfer->block_size) | SDHC_BSR_BOUNDARY_4K;
|
|
sdhc->BCR.reg = (xfer->block_num == 1) ? 0 : SDHC_BCR_BCNT(xfer->block_num);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _xfer_execute(sdmmc_dev_t *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));
|
|
|
|
sdhc_dev_t *sdhc_dev = container_of(dev, sdhc_dev_t, sdmmc_dev);
|
|
|
|
assert(sdhc_dev);
|
|
assert(sdhc_dev->conf);
|
|
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
uint32_t ret = 0;
|
|
|
|
if (dev->status & SDMMC_CARD_STATUS_ERRORS) {
|
|
ret = -EIO;
|
|
goto out;
|
|
}
|
|
|
|
int num_words = (xfer->block_num * xfer->block_size) >> 2;
|
|
|
|
if (xfer->write) {
|
|
const uint32_t *data_to_write = data_wr;
|
|
do {
|
|
/* wait until there is space in the write buffer */
|
|
if (!_wait_for_event(sdhc_dev, SDHC_NISIER_BWRRDY,
|
|
SDHC_EISTR_DATTEO | SDHC_EISTR_DATEND | SDHC_EISTR_DATCRC,
|
|
SDHC_SRR_SWRSTDAT)) {
|
|
ret = _sdhc_to_sdmmc_err_code(sdhc_dev->error);
|
|
goto out;
|
|
}
|
|
|
|
/* write data to the buffer as long there is space */
|
|
while (sdhc->PSR.bit.BUFWREN && num_words && !sdhc->EISTR.reg) {
|
|
sdhc->BDPR.reg = *data_to_write++;
|
|
num_words--;
|
|
}
|
|
} while (num_words);
|
|
}
|
|
else {
|
|
uint32_t *data_to_read = data_rd;
|
|
do {
|
|
/* wait until there is data in the read buffer */
|
|
if (!_wait_for_event(sdhc_dev, SDHC_NISIER_BRDRDY,
|
|
SDHC_EISTR_DATTEO | SDHC_EISTR_DATEND | SDHC_EISTR_DATCRC,
|
|
SDHC_SRR_SWRSTDAT)) {
|
|
ret = _sdhc_to_sdmmc_err_code(sdhc_dev->error);
|
|
goto out;
|
|
}
|
|
|
|
/* read all data that is available */
|
|
while (sdhc->PSR.bit.BUFRDEN && num_words /* && !sdhc->EISTR.reg */) {
|
|
*data_to_read++ = sdhc->BDPR.reg;
|
|
num_words--;
|
|
}
|
|
} while (num_words);
|
|
}
|
|
|
|
/* wait for transfer complete */
|
|
if (!_wait_for_event(sdhc_dev, SDHC_NISTR_TRFC,
|
|
SDHC_EISTR_DATTEO | SDHC_EISTR_DATEND | SDHC_EISTR_DATCRC,
|
|
SDHC_SRR_SWRSTALL)) {
|
|
ret = _sdhc_to_sdmmc_err_code(sdhc_dev->error);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
sdhc_dev->data_transfer = false;
|
|
sdhc_dev->error = 0;
|
|
|
|
if (done) {
|
|
*done = xfer->block_num - sdhc->BCR.reg;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int _xfer_finish(sdmmc_dev_t *dev, sdmmc_xfer_desc_t *xfer)
|
|
{
|
|
(void)xfer;
|
|
|
|
sdhc_dev_t *sdhc_dev = container_of(dev, sdhc_dev_t, sdmmc_dev);
|
|
|
|
assert(sdhc_dev);
|
|
assert(sdhc_dev->conf);
|
|
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
sdhc->NISTR.reg = (SDHC_NISTR_BWRRDY | SDHC_NISTR_BRDRDY);
|
|
|
|
if (IS_USED(MODULE_PERIPH_SDMMC_AUTO_CLK)) {
|
|
/* disable the clock to the card */
|
|
return _disable_sd_clk(sdhc_dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Internal functions */
|
|
#if defined(CPU_SAMD5X) || defined(CPU_SAME5X)
|
|
|
|
void _core_init(sdhc_dev_t *sdhc_dev)
|
|
{
|
|
sam0_gclk_enable(SDHC_CLOCK_SLOW);
|
|
sam0_gclk_enable(SDHC_CLOCK);
|
|
|
|
if (sdhc_dev->conf->sdhc == SDHC0) {
|
|
GCLK->PCHCTRL[SDHC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN
|
|
| GCLK_PCHCTRL_GEN(SDHC_CLOCK);
|
|
GCLK->PCHCTRL[SDHC0_GCLK_ID_SLOW].reg = GCLK_PCHCTRL_CHEN
|
|
| GCLK_PCHCTRL_GEN(SDHC_CLOCK_SLOW);
|
|
MCLK->AHBMASK.bit.SDHC0_ = 1;
|
|
}
|
|
#ifdef SDHC1
|
|
else if (sdhc_dev->conf->sdhc == SDHC1) {
|
|
GCLK->PCHCTRL[SDHC1_GCLK_ID].reg = GCLK_PCHCTRL_CHEN
|
|
| GCLK_PCHCTRL_GEN(SDHC_CLOCK);
|
|
GCLK->PCHCTRL[SDHC1_GCLK_ID_SLOW].reg = GCLK_PCHCTRL_CHEN
|
|
| GCLK_PCHCTRL_GEN(SDHC_CLOCK_SLOW);
|
|
MCLK->AHBMASK.bit.SDHC1_ = 1;
|
|
}
|
|
#endif /* SDHC1 */
|
|
}
|
|
|
|
static void _init_pins(sdhc_dev_t *sdhc_dev)
|
|
{
|
|
const sdhc_conf_t *conf = sdhc_dev->conf;
|
|
|
|
if (gpio_is_valid(conf->cd)) {
|
|
gpio_init_mux(conf->cd, SAM0_SDHC_MUX);
|
|
}
|
|
else {
|
|
sdhc_dev->sdmmc_dev.present = true;
|
|
}
|
|
|
|
if (gpio_is_valid(conf->wp)) {
|
|
gpio_init_mux(conf->wp, SAM0_SDHC_MUX);
|
|
}
|
|
|
|
if (conf->sdhc == SDHC0) {
|
|
/* data pins are fixed */
|
|
gpio_init_mux(SAM0_SDHC0_PIN_SDDAT0, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC0_PIN_SDDAT1, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC0_PIN_SDDAT2, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC0_PIN_SDDAT3, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC0_PIN_SDCMD, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC0_PIN_SDCK, SAM0_SDHC_MUX);
|
|
}
|
|
|
|
#ifdef SDHC1
|
|
if (conf->sdhc == SDHC1) {
|
|
/* data pins are fixed */
|
|
gpio_init_mux(SAM0_SDHC1_PIN_SDDAT0, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC1_PIN_SDDAT1, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC1_PIN_SDDAT2, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC1_PIN_SDDAT3, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC1_PIN_SDCMD, SAM0_SDHC_MUX);
|
|
gpio_init_mux(SAM0_SDHC1_PIN_SDCK, SAM0_SDHC_MUX);
|
|
}
|
|
#endif /* SDHC1 */
|
|
|
|
sdhc_dev->sdmmc_dev.bus_width = SDMMC_BUS_WIDTH_4BIT;
|
|
}
|
|
|
|
#endif /* defined(CPU_SAMD5X) || defined(CPU_SAME5X) */
|
|
|
|
/**
|
|
* @brief Reset the entire SDHC peripheral or a part of it
|
|
* @param type SDHC_SRR_SWRSTALL | SDHC_SRR_SWRSTCMD | SDHC_SRR_SWRSTDAT
|
|
*/
|
|
static void _reset_sdhc(sdhc_dev_t *sdhc_dev, uint8_t type)
|
|
{
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
sdhc->SRR.reg = type;
|
|
while (sdhc->SRR.reg & type) {}
|
|
|
|
if (type == SDHC_SRR_SWRSTALL) {
|
|
sdmmc_dev_t *sdmmc_dev = &sdhc_dev->sdmmc_dev;
|
|
|
|
/* peripheral needs a complete re-initialization */
|
|
sdmmc_dev->driver->init(sdmmc_dev);
|
|
/* trigger card_init */
|
|
sdmmc_dev->init_done = false;
|
|
}
|
|
|
|
sdhc_dev->data_transfer = 0;
|
|
|
|
sdhc->NISIER.reg = SDHC_NISIER_CARD_DETECT; /* enable card detection interrupt signals */
|
|
sdhc->EISIER.reg = 0;
|
|
}
|
|
|
|
#define SDHC_BUSY_TIMEOUT 500 /* limit SDHC busy time to 500 ms */
|
|
|
|
static int _wait_sdhc_busy(sdhc_t *sdhc)
|
|
{
|
|
uint32_t start = _ZTIMER_NOW(); /* waiting start time in msec */
|
|
uint32_t now = start;
|
|
|
|
_ZTIMER_ACQUIRE();
|
|
while ((sdhc->PSR.reg & (SDHC_PSR_CMDINHC | SDHC_PSR_CMDINHD)) &&
|
|
((now - start) < SDHC_BUSY_TIMEOUT)) {
|
|
now = _ZTIMER_NOW();
|
|
}
|
|
_ZTIMER_RELEASE();
|
|
return ((now - start) >= SDHC_BUSY_TIMEOUT) ? -ETIMEDOUT : 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Wait for a given event while checking for errors
|
|
*
|
|
* @param state SDHC device context
|
|
* @param event Event to wait for [SDHC_NISTR_*]
|
|
* @param error_mask Mask of errors to be checked [SDHC_EISTR_*]
|
|
* @param reset Reset type in case of errors
|
|
* [SDHC_SRR_SWRSTALL | SDHC_SRR_SWRSTCMD | SDHC_SRR_SWRSTDAT]
|
|
*
|
|
* @return true if the given event has occurred, or false if an error has occurred.
|
|
*/
|
|
static bool _wait_for_event(sdhc_dev_t *sdhc_dev,
|
|
uint16_t event, uint16_t error_mask,
|
|
uint8_t reset)
|
|
{
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
sdhc_dev->error = 0;
|
|
|
|
/* TODO: timeout */
|
|
sdhc->NISIER.reg |= event;
|
|
sdhc->EISIER.reg |= error_mask;
|
|
|
|
/* block IDLE so that the CPU clock does not stop */
|
|
|
|
#if defined(CPU_SAMD5X) || defined(CPU_SAME5X)
|
|
pm_block(SAM0_PM_IDLE);
|
|
#endif
|
|
mutex_lock(&sdhc_dev->irq_wait);
|
|
#if defined(CPU_SAMD5X) || defined(CPU_SAME5X)
|
|
pm_unblock(SAM0_PM_IDLE);
|
|
#endif
|
|
|
|
sdhc->NISIER.reg &= ~event;
|
|
sdhc->EISIER.reg &= ~error_mask;
|
|
|
|
if (sdhc_dev->error & error_mask) {
|
|
if (IS_USED(ENABLE_DEBUG)) {
|
|
DEBUG("[sdmmc] SDHC error: EISTR=%04x, ", sdhc_dev->error);
|
|
switch (reset) {
|
|
case SDHC_SRR_SWRSTCMD:
|
|
DEBUG("reset CMD\n");
|
|
break;
|
|
case SDHC_SRR_SWRSTDAT:
|
|
DEBUG("reset DAT\n");
|
|
break;
|
|
case SDHC_SRR_SWRSTALL:
|
|
DEBUG("reset ALL\n");
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
_reset_sdhc(sdhc_dev, reset);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int _enable_sd_clk(sdhc_dev_t *sdhc_dev)
|
|
{
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
if (!(sdhc->CCR.reg & SDHC_CCR_SDCLKEN)) {
|
|
DEBUG("[sdmmc] enable SDCLK\n");
|
|
/* TODO timeout handling */
|
|
sdhc->CCR.reg |= SDHC_CCR_SDCLKEN; /* enable clock to card */
|
|
|
|
/* a very small delay is required after clock changing */
|
|
volatile unsigned count = (CLOCK_CORECLOCK / US_PER_SEC) * 10;
|
|
while (--count) {}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _disable_sd_clk(sdhc_dev_t *sdhc_dev)
|
|
{
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
/* TODO timeout handling */
|
|
if (sdhc->CCR.bit.SDCLKEN) {
|
|
DEBUG("[sdmmc] disable SDCLK\n");
|
|
/* wait for command/data to go inactive */
|
|
while (sdhc->PSR.reg & (SDHC_PSR_CMDINHC | SDHC_PSR_CMDINHD)) {}
|
|
/* disable the clock to card */
|
|
sdhc->CCR.reg &= ~SDHC_CCR_SDCLKEN;
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int _sdhc_to_sdmmc_err_code(uint16_t code)
|
|
{
|
|
if (code & SDHC_EISTR_CMDIDX) {
|
|
DEBUG("[sdmmc] CMD index error\n");
|
|
return -ENOTSUP;
|
|
}
|
|
else if (code & (SDHC_EISTR_CMDTEO | SDHC_EISTR_DATTEO)) {
|
|
DEBUG("[sdmmc] Timeout error\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
else if (code & (SDHC_EISTR_CMDCRC | SDHC_EISTR_DATCRC)) {
|
|
DEBUG("[sdmmc] CRC error\n");
|
|
return -EBADMSG;
|
|
}
|
|
else {
|
|
DEBUG("[sdmmc] Other error\n");
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
static void _isr(sdhc_dev_t *sdhc_dev)
|
|
{
|
|
sdhc_t *sdhc = sdhc_dev->conf->sdhc;
|
|
|
|
if (sdhc->NISTR.reg & SDHC_NISTR_CARD_DETECT) {
|
|
DEBUG_PUTS("[sdmmc] card presence changed");
|
|
|
|
sdhc->NISTR.reg = SDHC_NISTR_CARD_DETECT;
|
|
|
|
sdmmc_dev_t *sdmmc_dev = &sdhc_dev->sdmmc_dev;
|
|
|
|
sdmmc_dev->init_done = false;
|
|
sdmmc_dev->present = sdhc->PSR.reg & SDHC_PSR_CARDINS;
|
|
|
|
if (sdmmc_dev->event_cb) {
|
|
sdmmc_dev->event_cb(sdmmc_dev,
|
|
sdmmc_dev->present ? SDMMC_EVENT_CARD_INSERTED
|
|
: SDMMC_EVENT_CARD_REMOVED);
|
|
}
|
|
}
|
|
|
|
if (sdhc->NISTR.reg & SDHC_NISTR_ERRINT) {
|
|
sdhc->NISTR.reg = SDHC_NISTR_ERRINT;
|
|
sdhc_dev->error = sdhc->EISTR.reg;
|
|
sdhc->EISTR.reg = SDHC_EISTR_MASK;
|
|
DEBUG_PUTS("[sdmmc] error");
|
|
mutex_unlock(&sdhc_dev->irq_wait);
|
|
}
|
|
|
|
if (sdhc->NISTR.reg & (sdhc->NISIER.reg & ~SDHC_NISTR_CARD_DETECT)) {
|
|
sdhc->NISTR.reg = (sdhc->NISIER.reg & ~SDHC_NISTR_CARD_DETECT);
|
|
DEBUG_PUTS("[sdmmc] unlock");
|
|
mutex_unlock(&sdhc_dev->irq_wait);
|
|
}
|
|
|
|
#ifdef MODULE_CORTEXM_COMMON
|
|
cortexm_isr_end();
|
|
#endif
|
|
}
|
|
|
|
void isr_sdhc0(void)
|
|
{
|
|
_isr(isr_ctx_0);
|
|
}
|
|
|
|
#ifdef SDHC1
|
|
void isr_sdhc1(void)
|
|
{
|
|
_isr(isr_ctx_1);
|
|
}
|
|
#endif
|
|
|
|
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,
|
|
#if !IS_USED(MODULE_PERIPH_SDMMC_AUTO_CLK)
|
|
.enable_clock = _enable_clock,
|
|
#endif
|
|
.xfer_prepare = _xfer_prepare,
|
|
.xfer_execute = _xfer_execute,
|
|
.xfer_finish = _xfer_finish,
|
|
};
|