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:
commit
149cee491e
@ -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:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
* @{
|
||||
|
@ -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
|
||||
|
@ -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
2487
drivers/include/sdmmc/vendor/sdhc.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,9 @@
|
||||
MODULE = sdmmc
|
||||
|
||||
SRC = sdmmc.c
|
||||
|
||||
ifneq (,$(filter sdmmc_sdhc,$(USEMODULE)))
|
||||
SRC += sdmmc_sdhc.c
|
||||
endif
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
||||
|
@ -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
|
||||
|
@ -1 +1,2 @@
|
||||
PSEUDOMODULES += sdmmc_mmc
|
||||
PSEUDOMODULES += sdmmc_sdhc
|
||||
|
900
drivers/sdmmc/sdmmc_sdhc.c
Normal file
900
drivers/sdmmc/sdmmc_sdhc.c
Normal 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,
|
||||
};
|
@ -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
|
||||
|
@ -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)) {
|
||||
|
Loading…
Reference in New Issue
Block a user