1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
19760: cpu/sam0_common/periph: add low-level SDMMC peripheral driver for SDHC r=benpicco a=gschorcht

### Contribution description

This PR implements the low-level SDIO/SDMMC peripheral driver for SAM0 SDHC according to the definition in #19539.

### Testing procedure

```
BOARD=same54-xpro make -C tests/drivers/sdmmc
```
```
BOARD=same54-xpro make -C tests/sys/vfs_default
```

### Issues/PRs references

~Depends on PR #19539~
Depends on PR #19899

19946: posix_sockets.c: Fix 2 byte int compilation errors r=benpicco a=mrdeep1



19956: cpu/esp32: fix heap definition for ESP32-S2 and ESP32-S3 r=benpicco a=gschorcht

### Contribution description

For ESP32-S2 and ESP32-S3 the symbol `_heap_end` must not be used as `_eheap` for the newlibc `malloc` and function `sbrk`.

`_heap_end` is used by the ESP-IDF heap implementation `esp-idf-heap` and points to the highest possible address (0x40000000) that could be used for the heap in ESP-IDF. It doesn't point to the top address of the unused SRAM area that can be used in newlibc `malloc` and function `sbrk`. Instead, the origin and the length of `dram0_0_seg` must be used to calculate the end of the heap `_eheap`.

The problem only occurs for the newlibc `malloc` when the `sbrk` function is used but not for the ESP-IDF heap implementation `esp_idf_heap`.

### Testing procedure

Use any ESP32-S2 or ESP32-S3 board and flash `tests/sys/malloc`, e.g.
```
CFLAGS='-DCHUNK_SIZE=16384' USEMODULE='stdio_uart' BOARD=esp32s3-pros3 make -j8 -C tests/sys/malloc flash
```
Without the PR the `nm` command will give the wrong address 
```
nm -s tests/sys/malloc/bin/esp32s3-pros3/tests_malloc.elf | grep _eheap
40000000 A _eheap
```
The test will stuck, i.e. the allocation of memory stops when the top of unused SRAM is reached and the board restarts when the watchdog timer expires. With the PR it should work as expected
```
Help: Press s to start test, r to print it is ready
START
main(): This is RIOT! (Version: 2023.10-devel-309-g4669e)
calloc(zu, zu) = 0x10000000
CHUNK_SIZE: 16384
NUMBER_OF_TESTS: 3
Allocated 16384 Bytes at 0x3fc8c4b0, total 16384
...
Allocated 16384 Bytes at 0x3fcec6f0, total 409792
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x7 (TG0WDT_SYS_RST),boot:0x8 (SPI_FAST_FLASH_BOOT)
Saved PC:0x403763e3
```

With this PR the `nm` command should give a address in unused SRAM address space
```
nm -s tests/sys/malloc/bin/esp32s3-pros3/tests_malloc.elf | grep _eheap
3fcca000 A _eheap
```
and the test should pass.

### Issues/PRs references


19957: cpu/esp32: fix Octal SPI RAM for ESP32-S3 r=benpicco a=gschorcht

### Contribution description

This PR fixes Octal SPI RAM handling for ESP32-S3.

Functions that are used during the initialization of the Octal SPI RAM must reside in IRAM instead of Flash. Otherwise, the system stucks during boot once the Octal SPI RAM is enabled. The reason is that the Flash is not available during the initialization of the Octal SPI RAM and the functions that are called during that initialization can't be accessed in Flash. As a result the call of such a function leads to code that is messed up and the system crashes.

The PR also includes the documentation fixe for the `esp32s3-box`. It also includes a small documentation fix regarding the SPI RAM for the `esp32s3-pros3` board.

### Testing procedure

Use a board that has Octal SPI RAM and flash `tests/sys/malloc`, e.g.:
```
CFLAGS='-DCHUNK_SIZE=16384' USEMODULE='stdio_uart esp_spi_ram esp_log_startup' \
BOARD=esp32s3-box make -C tests/sys/malloc
```
Without the PR, the system stuck during boot once the information for the Octal SPI RAM is print
```
ESP-ROM:esp32s3-20210327
...
I (133) boot: Loaded app from partition at offset 0x10000
I (134) boot: Disabling RNG early entropy source...
vendor id : 0x0d (AP)
dev id    : 0x02 (generation 3)
density   : 0x03 (64 Mbit)
good-die  : 0x01 (Pass)
Latency   : 0x01 (Fixed)
VCC       : 0x01 (3V)
SRF       : 0x01 (Fast Refresh)
BurstType : 0x01 (Hybrid Wrap)
BurstLen  : 0x01 (32 Byte)
Readlatency  : 0x02 (10 cycles@Fixed)
DriveStrength: 0x00 (1/1)
```
and the board restarts when the watchdog timer expires.

With this PR, the system starts as expected.
```
ESP-ROM:esp32s3-20210327
...
I (132) boot: Loaded app from partition at offset 0x10000
I (133) boot: Disabling RNG early entropy source...
vendor id : 0x0d (AP)
dev id    : 0x02 (generation 3)
density   : 0x03 (64 Mbit)
good-die  : 0x01 (Pass)
Latency   : 0x01 (Fixed)
VCC       : 0x01 (3V)
SRF       : 0x01 (Fast Refresh)
BurstType : 0x01 (Hybrid Wrap)
BurstLen  : 0x01 (32 Byte)
Readlatency  : 0x02 (10 cycles@Fixed)
DriveStrength: 0x00 (1/1)
Found 64MBit SPI RAM device
SPI RAM mode: sram 40m
PSRAM initialized, cache is in normal (1-core) mode.
Pro cpu up.
Single core mode
SPI SRAM memory test OK
Initializing. RAM available for dynamic allocation:
At 3FC8C150 len 00053EB0 (335 KiB): D/IRAM
At 3FCE0000 len 0000EE34 (59 KiB): STACK/DRAM
At 3FCF0000 len 00008000 (32 KiB): DRAM

Starting ESP32x with ID: f412fafd0f8c
ESP-IDF SDK Version v4.4.1

Current clocks in Hz: CPU=80000000 APB=80000000 XTAL=40000000 SLOW=150000
PRO cpu is up (single core mode, only PRO cpu is used)
PRO cpu starts user code
Adding pool of 8192K of external SPI memory to heap allocator
Used clocks in Hz: CPU=80000000 APB=80000000 XTAL=40000000 FAST=8000000 SLOW=150000
XTAL calibration value: 3643448
Heap free: 8754851 bytes

Board configuration:
	UART_DEV(0)	txd=43 rxd=44
	LED		pins=[ ]
	BUTTONS		pins=[ 0 ]

Starting RIOT kernel on PRO cpu
Help: Press s to start test, r to print it is ready
```

### Issues/PRs references


Co-authored-by: Gunar Schorcht <gunar@schorcht.net>
Co-authored-by: Jon Shallow <supjps-libcoap@jpshallow.com>
This commit is contained in:
bors[bot] 2023-09-29 08:36:50 +00:00 committed by GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 3530 additions and 11 deletions

View File

@ -37,7 +37,7 @@ The ESP32-S3-Box has following main features:
|:--------------------------------------------|:-------:|
| ESP32-S3 SoC | yes |
| 16 MB Flash | yes |
| 8 MB QSPI RAM | yes |
| 8 MB Octal SPI RAM | yes |
| 2.4\" LCD Display 320 x 240 with ILI9342C | yes |
| Capacitive Touch Panel | no |
| Dual Microphone ES7210 | no |
@ -104,7 +104,7 @@ UART_DEV(0) RxD | GPIO44 | PMOD2 | \ref esp32_uart_interfaces "UART interfaces"
The following figures show the pinouts as configured by default board
definition.
@image html https://raw.githubusercontent.com/espressif/esp-box/master/docs/_static/_get_started_static/hardware_pmod.png "ESP32-S3-BoxC-1 Pinout" width=900px
@image html https://raw.githubusercontent.com/espressif/esp-box/master/docs/_static/previous_get_started_fig/hardware_pmod.png "ESP32-S3-BoxC-1 Pinout" width=900px
The corresponding schematics can be found:

View File

@ -33,7 +33,7 @@ The main features of the board are:
- ESP32-S3 SoC with 2.4 GHz WiFi 802.11b/g/n and Bluetooth5, BLE
- 16 MByte Flash
- 4 MByte SPI RAM
- 8 MByte QSPI RAM
- RGB LED WS2812B
- Native USB and USB Serial JTAG
- LiPo Battery Charging and PicoBlade connector

View File

@ -18,6 +18,7 @@ config BOARD_SAME54_XPRO
select HAS_PERIPH_RTC
select HAS_PERIPH_RTT
select HAS_PERIPH_PWM
select HAS_PERIPH_SDMMC
select HAS_PERIPH_SPI
select HAS_PERIPH_TIMER
select HAS_PERIPH_UART
@ -31,6 +32,7 @@ config BOARD_SAME54_XPRO
select HAVE_SAM0_ETH
select HAVE_SAM0_SDHC
select HAVE_MTD_AT24CXXX
select HAVE_MTD_SDMMC_DEFAULT
# This specific board requires SPI_ON_QSPI for the MTD_SPI_NOR
select MODULE_PERIPH_SPI_ON_QSPI if MODULE_MTD_SPI_NOR

View File

@ -10,7 +10,11 @@ ifneq (,$(filter mtd,$(USEMODULE)))
FEATURES_REQUIRED += periph_spi_on_qspi
USEMODULE += mtd_spi_nor
USEMODULE += mtd_at24cxxx at24mac
USEMODULE += sam0_sdhc
ifeq (,$(filter sam0_sdhc,$(USEMODULE)))
# during a transition period it is possible to use the `sam0_sdhc` MTD
# driver instead of the SD/MMC MTD driver
USEMODULE += mtd_sdmmc_default
endif
endif
# enables sam0_eth as default network device

View File

@ -8,6 +8,7 @@ FEATURES_PROVIDED += periph_i2c
FEATURES_PROVIDED += periph_rtc
FEATURES_PROVIDED += periph_rtt
FEATURES_PROVIDED += periph_pwm
FEATURES_PROVIDED += periph_sdmmc
FEATURES_PROVIDED += periph_spi
FEATURES_PROVIDED += periph_timer
FEATURES_PROVIDED += periph_uart

View File

@ -76,6 +76,8 @@ extern mtd_dev_t *mtd0, *mtd1, *mtd2;
#define MTD_1 mtd1
#define MTD_2 mtd2
#define MTD_NUMOF 3
#define CONFIG_SDMMC_GENERIC_MTD_OFFSET 2 /**< mtd2 is used for SD Card */
/** @} */
/**

View File

@ -369,6 +369,18 @@ static const adc_conf_chan_t adc_channels[] = {
*/
#define SDHC_DEV SDHC1 /**< The SDHC instance to use */
#define SDHC_DEV_ISR isr_sdhc1 /**< Interrupt service routing for SDHC1 */
/** SDHC devices */
static const sdhc_conf_t sdhc_config[] = {
{
.sdhc = SDHC1,
.cd = GPIO_PIN(PD, 20),
.wp = GPIO_UNDEF,
},
};
/** Number of configured SDHC devices */
#define SDHC_CONFIG_NUMOF 1
/** @} */
/**

View File

@ -590,9 +590,11 @@ SECTIONS
_sheap = ABSOLUTE(.);
} > dram0_0_seg
. = _heap_end;
. = ORIGIN(dram0_0_seg) + LENGTH(dram0_0_seg);
_eheap = ABSOLUTE(.);
. = _heap_end;
#ifdef MODULE_PERIPH_FLASHPAGE
.flash_writable (NOLOAD) : ALIGN(65536)
{

View File

@ -213,6 +213,8 @@ SECTIONS
*components/esp_hw_support/*/rtc_sleep.*(.literal .literal.* .text .text.*)
*components/esp_hw_support/*/rtc_time.*(.literal .literal.* .text .text.*)
*components/esp_hw_support/*/rtc_wdt.*(.literal .literal.* .text .text.*)
*components/esp_hw_support/*/spiram_psram.*(.literal .literal.* .text .text.*)
*components/esp_hw_support/*/opiram_psram.*(.literal .literal.* .text .text.*)
*components/esp_ringbuf/*(.literal .literal.* .text .text.*)
*components/esp_rom/esp_rom_spiflash.*(.literal .literal.* .text .text.*)
*components/esp_system/esp_err.*(.literal .literal.* .text .text.*)
@ -617,9 +619,11 @@ SECTIONS
_sheap = ABSOLUTE(.);
} > dram0_0_seg
. = _heap_end;
. = ORIGIN(dram0_0_seg) + LENGTH(dram0_0_seg);
_eheap = ABSOLUTE(.);
. = _heap_end;
#ifdef MODULE_PERIPH_FLASHPAGE
.flash_writable (NOLOAD) : ALIGN(65536)
{

View File

@ -17,6 +17,10 @@ config CPU_COMMON_SAM0
select HAS_PERIPH_I2C_RECONFIGURE
select HAS_PERIPH_RTT_SET_COUNTER
select HAS_PERIPH_RTT_OVERFLOW
select HAS_PERIPH_SDMMC_AUTO_CMD12
select HAS_PERIPH_SDMMC_HS
select HAS_PERIPH_SDMMC_MMC
select HAS_PERIPH_SDMMC_SDHC
select HAS_PERIPH_SPI_RECONFIGURE
select HAS_PERIPH_SPI_GPIO_MODE
select HAS_PERIPH_TIMER_PERIODIC

View File

@ -10,6 +10,10 @@ ifneq (,$(filter periph_spi,$(USEMODULE)))
USEMODULE += periph_spi_gpio_mode
endif
ifneq (,$(filter periph_sdmmc,$(USEMODULE)))
USEMODULE += sdmmc_sdhc
endif
# include sam0 common periph drivers
USEMODULE += sam0_common_periph

View File

@ -15,6 +15,10 @@ FEATURES_PROVIDED += periph_gpio periph_gpio_irq
FEATURES_PROVIDED += periph_i2c_reconfigure
FEATURES_PROVIDED += periph_rtt_set_counter
FEATURES_PROVIDED += periph_rtt_overflow
FEATURES_PROVIDED += periph_sdmmc_auto_cmd12
FEATURES_PROVIDED += periph_sdmmc_hs
FEATURES_PROVIDED += periph_sdmmc_mmc
FEATURES_PROVIDED += periph_sdmmc_sdhc
FEATURES_PROVIDED += periph_spi_reconfigure
FEATURES_PROVIDED += periph_spi_gpio_mode
FEATURES_PROVIDED += periph_timer_periodic # implements timer_set_periodic()

View File

@ -907,6 +907,25 @@ typedef struct {
} sam0_common_usb_config_t;
#endif /* USB_INST_NUM */
/**
* @brief SDIO/SDMMC buffer alignment for SDHC because of DMA/FIFO buffer restrictions
*/
#define SDMMC_CPU_DMA_ALIGNMENT 4
/**
* @brief SDIO/SDMMC buffer instantiation requirement for SDHC
*/
#define SDMMC_CPU_DMA_REQUIREMENTS __attribute__((aligned(SDMMC_CPU_DMA_ALIGNMENT)))
/**
* @brief SDHC peripheral configuration
*/
typedef struct {
void *sdhc; /**< SDHC peripheral */
gpio_t cd; /**< Card Detect pin (must be GPIO_UNDEF if not connected) */
gpio_t wp; /**< Write Protect pin (must be GPIO_UNDEF if not connected) */
} sdhc_conf_t;
/**
* @name WDT upper and lower bound times in ms
* @{

View File

@ -11,6 +11,9 @@
* @ingroup cpu_sam0_common
* @brief SD card interface functions for sam0 class devices
*
* @warning This driver is deprecated. Use the `sdmmc` driver module
* instead. You can refer to the `same54-xpro´ board as an example
* on how to use it.
* @{
*
* @file

View File

@ -54,6 +54,16 @@ extern "C" {
* @{
*/
#define PM_NUM_MODES (4) /**< Backup, Hibernate, Standby, Idle */
/**
* @brief Power modes
*/
enum {
SAM0_PM_BACKUP = 0,
SAM0_PM_HIBERNATE = 1,
SAM0_PM_STANDBY = 2,
SAM0_PM_IDLE = 3,
};
/** @} */
/**

2487
drivers/include/sdmmc/vendor/sdhc.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,7 @@ config HAVE_MTD_SDMMC_DEFAULT
depends on HAS_PERIPH_SDMMC
imply MODULE_MTD_SDMMC if MODULE_MTD
imply MODULE_MTD_SDMMC_DEFAULT if MODULE_MTD
imply MODULE_SDMMC if MODULE_MTD
help
Indicates that a SD/MMC MTD is present with generic configuration

View File

@ -59,4 +59,32 @@ config MODULE_PERIPH_INIT_SDMMC_AUTO_CLK
depends on MODULE_PERIPH_SDMMC_AUTO_CLK
default y if MODULE_PERIPH_INIT
config MODULE_PERIPH_SDMMC_AUTO_CMD12
bool
depends on HAS_PERIPH_SDMMC_AUTO_CMD12
default y
help
If the SDIO/SD/MMC peripheral supports the Auto-CMD12 feature is
enabled, i.e. CMD12 is sent automatically to stop the transmission in
multiple block operations.
config MODULE_PERIPH_INIT_SDMMC_AUTO_CMD12
bool
depends on MODULE_PERIPH_SDMMC_AUTO_CMD12
default y if MODULE_PERIPH_INIT
config MODULE_PERIPH_SDMMC_SDHC
bool
depends on HAS_PERIPH_SDMMC_SDHC
default y
help
If the SDIO/SD/MMC peripheral is compliant with the SD Host Controller
Specification, the low-level SD Host Controller (SDHC) peripheral
driver is used.
config MODULE_PERIPH_INIT_SDMMC_SDHC
bool
depends on MODULE_PERIPH_SDMMC_SDHC
default y if MODULE_PERIPH_INIT
endif # MODULE_PERIPH_SDMMC

View File

@ -9,7 +9,7 @@ config MODULE_SDMMC
bool "SDIO/SD/MMC interface"
depends on HAS_PERIPH_SDMMC
select MODULE_PERIPH_SDMMC
select MODULE_ZTIMER_MSEC if !ZTIMER_USEC
select MODULE_ZTIMER_MSEC
if MODULE_SDMMC
@ -19,4 +19,13 @@ config MODULE_SDMMC_MMC
help
Say y to support MMCs/eMMCs.
config MODULE_SDMMC_SDHC
bool
depends on HAS_PERIPH_SDMMC_SDHC
default y
help
SDHC driver is used as low-level SDIO/SD/MMC implementation for
peripherals that are compliant with SD Host Controller Simplified
Specification, Version 3.00.
endif # MODULE_SDMMC

View File

@ -1,3 +1,9 @@
MODULE = sdmmc
SRC = sdmmc.c
ifneq (,$(filter sdmmc_sdhc,$(USEMODULE)))
SRC += sdmmc_sdhc.c
endif
include $(RIOTBASE)/Makefile.base

View File

@ -7,7 +7,8 @@ ifneq (,$(filter sdmmc_mmc,$(USEMODULE)))
FEATURES_REQUIRED += periph_sdmmc_mmc
endif
ifeq (,$(filter ztimer_usec,$(USEMODULE)))
# enable ztimer_msec backend if ztimer_usec is not enabled
USEMODULE += ztimer_msec
ifneq (,$(filter sdmmc_sdhc,$(USEMODULE)))
FEATURES_REQUIRED += periph_sdmmc_sdhc
endif
USEMODULE += ztimer_msec

View File

@ -1 +1,2 @@
PSEUDOMODULES += sdmmc_mmc
PSEUDOMODULES += sdmmc_sdhc

900
drivers/sdmmc/sdmmc_sdhc.c Normal file
View File

@ -0,0 +1,900 @@
/*
* 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(MCU_SAMD5X) || defined(MCU_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 "MCU 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_devs, 0) sdmmc_dev_t * const _sdmmc_0 = (sdmmc_dev_t * const)&_sdhc_devs[0];
#if SDHC_CONFIG_NUMOF > 1
XFA_CONST(sdmmc_devs, 0) sdmmc_dev_t * const _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(MCU_SAMD5X) || defined(MCU_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(MCU_SAMD5X) || defined(MCU_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 perpared */
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(MCU_SAMD5X) || defined(MCU_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(MCU_SAMD5X) || defined(MCU_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(MCU_SAMD5X) || defined(MCU_SAME5X)
pm_block(SAM0_PM_IDLE);
#endif
mutex_lock(&sdhc_dev->irq_wait);
#if defined(MCU_SAMD5X) || defined(MCU_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,
};

View File

@ -514,6 +514,13 @@ config HAS_PERIPH_SDMMC_MMC
Indicates that the SDIO/SD/MMC peripheral supports MMC/eMMCs. This
feature shall be provided by the MCU.
config HAS_PERIPH_SDMMC_SDHC
bool
help
Indicates that the SDIO/SD/MMC peripheral is compliant with the
SD Host Controller Specification. This feature shall
be provided by the MCU.
config HAS_PERIPH_SPI
bool
help

View File

@ -1121,7 +1121,7 @@ int setsockopt(int socket, int level, int option_name, const void *option_value,
#ifdef POSIX_SETSOCKOPT
socket_t *s;
struct timeval *tv;
const uint32_t max_timeout_secs = UINT32_MAX / (1000 * 1000);
const uint32_t max_timeout_secs = UINT32_MAX / US_PER_SEC;
if (level != SOL_SOCKET
|| option_name != SO_RCVTIMEO) {
@ -1144,10 +1144,18 @@ int setsockopt(int socket, int level, int option_name, const void *option_value,
tv = (struct timeval *) option_value;
#if MODULE_AVR8_COMMON
/* tv_sec is uint32_t, so never negative */
if (tv->tv_usec < 0) {
errno = EINVAL;
return -1;
}
#else /* ! MODULE_AVR8_COMMON */
if (tv->tv_sec < 0 || tv->tv_usec < 0) {
errno = EINVAL;
return -1;
}
#endif /* ! MODULE_AVR8_COMMON */
if ((uint32_t)tv->tv_sec > max_timeout_secs
|| ((uint32_t)tv->tv_sec == max_timeout_secs && (uint32_t)tv->tv_usec > UINT32_MAX - max_timeout_secs * 1000 * 1000)) {