From 5197329b67481a8e4d12deb36d2cb96f06996a48 Mon Sep 17 00:00:00 2001 From: "Austin.Chen" Date: Mon, 22 Jun 2026 13:11:58 +0800 Subject: [PATCH] arch/arm/stm32h5: add SDMMC1/SDMMC2 driver Add the STM32H5 SDMMC1/SDMMC2 lower-half SDIO driver (interrupt-mode and IDMA transfers, SD/SDIO card mode), following the same structure as the existing STM32H7 SDMMC driver. Three fixes were needed to get this actually building, selectable, and correct: - The driver checked CONFIG_STM32H5_SDMMC1/CONFIG_STM32H5_SDMMC_IDMA/ CONFIG_STM32H5_SDMMC_XFRDEBUG, but the real Kconfig symbols selected by this chip are the shared CONFIG_STM32_SDMMC1/CONFIG_STM32_SDMMC_IDMA/ CONFIG_STM32_SDMMC_XFRDEBUG (see arch/arm/src/common/stm32/Kconfig.sdio, Kconfig.periph). With the old names the driver silently compiled out. Renamed all guards in stm32_sdmmc.c to match. Also fixed a similar typo, STM32H5_SRAM3_SIZE -> STM32_SRAM3_SIZE, in the IDMA-reach check. - arch/arm/src/common/stm32/Kconfig.sdio's STM32_SDMMC_IDMA and the SDMMC1/2 SDIO-mode/pull-up options depended on ARCH_CHIP_STM32H7 / STM32_COMMON_F7_H7 only. Extended STM32_SDMMC_IDMA to also allow ARCH_CHIP_STM32H5, and switched the SDIO-mode/pull-up options to STM32_COMMON_F7_H7_H5, matching the pattern already used for other STM32H5 peripherals (Ethernet, ADC, SPI, timers). - stm32_sdmmc.c was only added to Make.defs, not to CMakeLists.txt, so the driver would silently be omitted from CMake builds. Added it to the same unconditional source list as stm32_exti_gpio.c. Also ports a fix from a related STM32H7 SDMMC commit (2cb7b7c03e1aecb85d9fbf568ee50cd10349fc49): stm32_recvdma()'s aligned IDMA receive path invalidated the destination buffer before the DMA but never again after it completed, so a speculative cache prefetch into that buffer between those two points could shadow the freshly-received data with a stale line. Added the missing post-DMA invalidate, matching the pattern already used elsewhere on this chip for other DMA-capable peripherals (e.g. stm32_ethernet.c's RX path). Needed for a custom STM32H5 board that uses SDMMC1 in SDIO mode with IDMA to talk to an onboard WiFi module. Co-authored-by: Liam Howatt Signed-off-by: Marwan Madkour --- arch/arm/src/common/stm32/Kconfig.sdio | 12 +- arch/arm/src/stm32h5/CMakeLists.txt | 3 +- arch/arm/src/stm32h5/Kconfig | 2 + arch/arm/src/stm32h5/Make.defs | 2 +- arch/arm/src/stm32h5/hardware/stm32_sdmmc.h | 33 + .../src/stm32h5/hardware/stm32h5xxx_sdmmc.h | 246 ++ arch/arm/src/stm32h5/stm32_sdmmc.c | 3764 +++++++++++++++++ arch/arm/src/stm32h5/stm32_sdmmc.h | 134 + 8 files changed, 4188 insertions(+), 8 deletions(-) create mode 100644 arch/arm/src/stm32h5/hardware/stm32_sdmmc.h create mode 100644 arch/arm/src/stm32h5/hardware/stm32h5xxx_sdmmc.h create mode 100644 arch/arm/src/stm32h5/stm32_sdmmc.c create mode 100644 arch/arm/src/stm32h5/stm32_sdmmc.h diff --git a/arch/arm/src/common/stm32/Kconfig.sdio b/arch/arm/src/common/stm32/Kconfig.sdio index 7e11939f9e5..627de0bc253 100644 --- a/arch/arm/src/common/stm32/Kconfig.sdio +++ b/arch/arm/src/common/stm32/Kconfig.sdio @@ -56,7 +56,7 @@ config STM32_SDIO_WIDTH_D1_ONLY config STM32_SDMMC_IDMA bool "Support IDMA data transfers" - depends on ARCH_CHIP_STM32H7 && STM32_SDMMC + depends on (ARCH_CHIP_STM32H7 || ARCH_CHIP_STM32H5) && STM32_SDMMC default y select SDIO_DMA ---help--- @@ -101,14 +101,14 @@ config SDMMC1_WIDTH_D1_ONLY config SDMMC1_SDIO_MODE bool "SDIO Card Support" - depends on STM32_COMMON_F7_H7 && STM32_SDMMC && STM32_SDMMC1 + depends on STM32_COMMON_F7_H7_H5 && STM32_SDMMC && STM32_SDMMC1 ---help--- Build in additional support needed only for SDIO cards (vs. SD memory cards) config SDMMC1_SDIO_PULLUP bool "Enable internal Pull-Ups" - depends on STM32_COMMON_F7_H7 && STM32_SDMMC && STM32_SDMMC1 + depends on STM32_COMMON_F7_H7_H5 && STM32_SDMMC && STM32_SDMMC1 ---help--- If you are using an external SDCard module that does not have the pull-up resistors for the SDIO interface (like the Gadgeteer SD Card @@ -117,20 +117,20 @@ config SDMMC1_SDIO_PULLUP config SDMMC2_WIDTH_D1_ONLY bool "Use D1 only on SDMMC2" - depends on STM32_COMMON_F7_H7 && STM32_SDMMC && STM32_SDMMC2 + depends on STM32_COMMON_F7_H7_H5 && STM32_SDMMC && STM32_SDMMC2 ---help--- Select 1-bit transfer mode. Default: 4-bit transfer mode. config SDMMC2_SDIO_MODE bool "SDIO Card Support" - depends on STM32_COMMON_F7_H7 && STM32_SDMMC && STM32_SDMMC2 + depends on STM32_COMMON_F7_H7_H5 && STM32_SDMMC && STM32_SDMMC2 ---help--- Build in additional support needed only for SDIO cards (vs. SD memory cards) config SDMMC2_SDIO_PULLUP bool "Enable internal Pull-Ups" - depends on STM32_COMMON_F7_H7 && STM32_SDMMC && STM32_SDMMC2 + depends on STM32_COMMON_F7_H7_H5 && STM32_SDMMC && STM32_SDMMC2 ---help--- If you are using an external SDCard module that does not have the pull-up resistors for the SDIO interface (like the Gadgeteer SD Card diff --git a/arch/arm/src/stm32h5/CMakeLists.txt b/arch/arm/src/stm32h5/CMakeLists.txt index 82956715775..244ca4ab298 100644 --- a/arch/arm/src/stm32h5/CMakeLists.txt +++ b/arch/arm/src/stm32h5/CMakeLists.txt @@ -42,7 +42,8 @@ list( stm32_timerisr.c stm32_lse.c stm32_lsi.c - stm32_exti_gpio.c) + stm32_exti_gpio.c + stm32_sdmmc.c) if(CONFIG_STM32_USART) list(APPEND SRCS stm32_serial.c) diff --git a/arch/arm/src/stm32h5/Kconfig b/arch/arm/src/stm32h5/Kconfig index 09a0a520970..672874f8387 100644 --- a/arch/arm/src/stm32h5/Kconfig +++ b/arch/arm/src/stm32h5/Kconfig @@ -167,6 +167,8 @@ config STM32_STM32H56XXX select STM32_HAVE_I2C4 select STM32_HAVE_USBFS select STM32_HAVE_HSI48 + select STM32_HAVE_SDMMC1 + select STM32_HAVE_SDMMC2 select STM32_HAVE_ICACHE_REMAP config STM32_STM32H563XX diff --git a/arch/arm/src/stm32h5/Make.defs b/arch/arm/src/stm32h5/Make.defs index 71151d5c466..7d3fd06b0b3 100644 --- a/arch/arm/src/stm32h5/Make.defs +++ b/arch/arm/src/stm32h5/Make.defs @@ -38,7 +38,7 @@ endif CHIP_CSRCS += stm32_gpio.c stm32_irq.c stm32_lowputc.c stm32_rcc.c CHIP_CSRCS += stm32_start.c stm32_pwr.c stm32_timerisr.c -CHIP_CSRCS += stm32_lse.c stm32_lsi.c stm32_exti_gpio.c +CHIP_CSRCS += stm32_lse.c stm32_lsi.c stm32_exti_gpio.c stm32_sdmmc.c ifneq ($(CONFIG_ARCH_IDLE_CUSTOM),y) CHIP_CSRCS += stm32_idle.c diff --git a/arch/arm/src/stm32h5/hardware/stm32_sdmmc.h b/arch/arm/src/stm32h5/hardware/stm32_sdmmc.h new file mode 100644 index 00000000000..d9cca6abe71 --- /dev/null +++ b/arch/arm/src/stm32h5/hardware/stm32_sdmmc.h @@ -0,0 +1,33 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/hardware/stm32_sdmmc.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_STM32H5_HARDWARE_STM32_SDMMC_H +#define __ARCH_ARM_SRC_STM32H5_HARDWARE_STM32_SDMMC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include "chip.h" + +#include "stm32h5xxx_sdmmc.h" + +#endif /* __ARCH_ARM_SRC_STM32H5_HARDWARE_STM32_SDMMC_H */ diff --git a/arch/arm/src/stm32h5/hardware/stm32h5xxx_sdmmc.h b/arch/arm/src/stm32h5/hardware/stm32h5xxx_sdmmc.h new file mode 100644 index 00000000000..b151f3c09bb --- /dev/null +++ b/arch/arm/src/stm32h5/hardware/stm32h5xxx_sdmmc.h @@ -0,0 +1,246 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/hardware/stm32h5xxx_sdmmc.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_STM32H5_HARDWARE_STM32H5XXX_SDMMC_H +#define __ARCH_ARM_SRC_STM32H5_HARDWARE_STM32H5XXX_SDMMC_H + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Register Offsets *********************************************************/ + +#define STM32_SDMMC_POWER_OFFSET 0x0000 /* SDMMC power control register */ +#define STM32_SDMMC_CLKCR_OFFSET 0x0004 /* SDMMC clock control register */ +#define STM32_SDMMC_ARG_OFFSET 0x0008 /* SDMMC argument register */ +#define STM32_SDMMC_CMD_OFFSET 0x000c /* SDMMC command register */ +#define STM32_SDMMC_RESPCMD_OFFSET 0x0010 /* SDMMC command response register */ +#define STM32_SDMMC_RESP_OFFSET(n) (0x0010+4*(n)) +#define STM32_SDMMC_RESP1_OFFSET 0x0014 /* SDMMC response 1 register */ +#define STM32_SDMMC_RESP2_OFFSET 0x0018 /* SDMMC response 2 register */ +#define STM32_SDMMC_RESP3_OFFSET 0x001c /* SDMMC response 3 register */ +#define STM32_SDMMC_RESP4_OFFSET 0x0020 /* SDMMC response 4 register */ +#define STM32_SDMMC_DTIMER_OFFSET 0x0024 /* SDMMC data timer register */ +#define STM32_SDMMC_DLEN_OFFSET 0x0028 /* SDMMC data length register */ +#define STM32_SDMMC_DCTRL_OFFSET 0x002c /* SDMMC data control register */ +#define STM32_SDMMC_DCOUNT_OFFSET 0x0030 /* SDMMC data counter register */ +#define STM32_SDMMC_STA_OFFSET 0x0034 /* SDMMC status register */ +#define STM32_SDMMC_ICR_OFFSET 0x0038 /* SDMMC interrupt clear register */ +#define STM32_SDMMC_MASK_OFFSET 0x003c /* SDMMC mask register */ +#define STM32_SDMMC_IDMACTRLR_OFFSET 0x0050 /* SDMMC DMA control register */ +#define STM32_SDMMC_IDMABSIZER_OFFSET 0x0054 /* SDMMC DMA size register */ +#define STM32_SDMMC_IDMABASE0R_OFFSET 0x0058 /* SDMMC IDMA buffer 0 base register */ +#define STM32_SDMMC_IDMABASE1R_OFFSET 0x005c /* SDMMC IDMA buffer 1 base register */ +#define STM32_SDMMC_FIFO_OFFSET 0x0080 /* SDMMC data FIFO register */ + +/* Register Bitfield Definitions ********************************************/ + +#define STM32_SDMMC_POWER_PWRCTRL_SHIFT (0) /* Bits 0-1: Power supply control bits */ +#define STM32_SDMMC_POWER_PWRCTRL_MASK (3 << STM32_SDMMC_POWER_PWRCTRL_SHIFT) +# define STM32_SDMMC_POWER_PWRCTRL_OFF (0 << STM32_SDMMC_POWER_PWRCTRL_SHIFT) /* 00: Power-off: card clock stopped */ +# define STM32_SDMMC_POWER_PWRCTRL_CYCLE (2 << STM32_SDMMC_POWER_PWRCTRL_SHIFT) /* 10: Reserved power-up */ +# define STM32_SDMMC_POWER_PWRCTRL_ON (3 << STM32_SDMMC_POWER_PWRCTRL_SHIFT) /* 11: Power-on: card is clocked */ + +#define STM32_SDMMC_POWER_RESET (0) /* Reset value */ + +#define STM32_SDMMC_CLKCR_CLKDIV_SHIFT (0) /* Bits 9-0: Clock divide factor */ +#define STM32_SDMMC_CLKCR_CLKDIV_MASK (0x3ff << STM32_SDMMC_CLKCR_CLKDIV_SHIFT) +#define STM32_SDMMC_CLKCR_PWRSAV (1 << 12) /* Bit 12: Power saving configuration bit */ +#define STM32_SDMMC_CLKCR_WIDBUS_SHIFT (14) /* Bits 15-14: Wide bus mode enable bits */ +#define STM32_SDMMC_CLKCR_WIDBUS_MASK (3 << STM32_SDMMC_CLKCR_WIDBUS_SHIFT) +# define STM32_SDMMC_CLKCR_WIDBUS_D1 (0 << STM32_SDMMC_CLKCR_WIDBUS_SHIFT) /* 00: Default (STM32_SDMMC_D0) */ +# define STM32_SDMMC_CLKCR_WIDBUS_D4 (1 << STM32_SDMMC_CLKCR_WIDBUS_SHIFT) /* 01: 4-wide (STM32_SDMMC_D[3:0]) */ +# define STM32_SDMMC_CLKCR_WIDBUS_D8 (2 << STM32_SDMMC_CLKCR_WIDBUS_SHIFT) /* 10: 8-wide (STM32_SDMMC_D[7:0]) */ + +#define STM32_SDMMC_CLKCR_NEGEDGE (1 << 16) /* Bit 16: STM32_SDMMC_CK dephasing selection bit */ +#define STM32_SDMMC_CLKCR_HWFC_EN (1 << 17) /* Bit 17: HW Flow Control enable */ +#define STM32_SDMMC_CLKCR_DDR (1 << 18) /* Bit 18: Data rate signaling selection */ +#define STM32_SDMMC_CLKCR_BUS_SPEED (1 << 19) /* Bit 19: Bus speed mode selection */ +#define STM32_SDMMC_CLKCR_SELCLKRX_SHIFT (20) /* Bits 21-20: Receive clock selection */ +#define STM32_SDMMC_CLKCR_SELCLKRX_MASK (3 << STM32_SDMMC_CLKCR_SELCLKRX_SHIFT) +# define STM32_SDMMC_CLKCR_SELCLKRX_IO_IN (0 << STM32_SDMMC_CLKCR_SELCLKRX_SHIFT) /* 00: sdmmc_io_in_ck selected as receive clock */ +# define STM32_SDMMC_CLKCR_SELCLKRX_CKIN (1 << STM32_SDMMC_CLKCR_SELCLKRX_SHIFT) /* 01: SDMMC_CKIN feedback clock selected as receive clock */ +# define STM32_SDMMC_CLKCR_SELCLKRX_FB (2 << STM32_SDMMC_CLKCR_SELCLKRX_SHIFT) /* 10: sdmmc_fb_ck tuned feedback clock selected as receive clock. */ + /* 11: Reserved (select sdmmc_io_in_ck) */ + +#define STM32_SDMMC_CLKCR_RESET (0) /* Reset value */ + +#define STM32_SDMMC_ARG_RESET (0) /* Reset value */ + +#define STM32_SDMMC_CMD_CMDINDEX_SHIFT (0) +#define STM32_SDMMC_CMD_CMDINDEX_MASK (0x3f << STM32_SDMMC_CMD_CMDINDEX_SHIFT) +#define STM32_SDMMC_CMD_CMDTRANS (1 << 6) /* Bit 6: The CPSM treats the command as a data transfer command */ +#define STM32_SDMMC_CMD_CMDSTOP (1 << 7) /* Bit 7: The CPSM treats the command as a Stop Transmission command */ +#define STM32_SDMMC_CMD_WAITRESP_SHIFT (8) /* Bits 8-9: Wait for response bits */ +#define STM32_SDMMC_CMD_WAITRESP_MASK (3 << STM32_SDMMC_CMD_WAITRESP_SHIFT) +# define STM32_SDMMC_CMD_NORESPONSE (0 << STM32_SDMMC_CMD_WAITRESP_SHIFT) /* 00/10: No response */ +# define STM32_SDMMC_CMD_SHORTRESPONSE (1 << STM32_SDMMC_CMD_WAITRESP_SHIFT) /* 01: Short response */ +# define STM32_SDMMC_CMD_SHORTRESPONSE_NOCRC (2 << STM32_SDMMC_CMD_WAITRESP_SHIFT) /* 01: Short response, no CRC */ +# define STM32_SDMMC_CMD_LONGRESPONSE (3 << STM32_SDMMC_CMD_WAITRESP_SHIFT) /* 11: Long response */ + +#define STM32_SDMMC_CMD_WAITINT (1 << 10) /* Bit 10: CPSM waits for interrupt request */ +#define STM32_SDMMC_CMD_WAITPEND (1 << 11) /* Bit 11: CPSM Waits for ends of data transfer */ +#define STM32_SDMMC_CMD_CPSMEN (1 << 12) /* Bit 12: Command path state machine enable */ +#define STM32_SDMMC_CMD_DTHOLD (1 << 13) /* Bit 13: Hold new data block transmission and reception in the DPSM */ +#define STM32_SDMMC_CMD_BOOTMODE (1 << 14) /* Bit 14: Select bootmode */ +#define STM32_SDMMC_CMD_BOOTEN (1 << 15) /* Bit 15: Enable boot mode */ +#define STM32_SDMMC_CMD_SUSPEND (1 << 16) /* Bit 16: SD I/O suspend command */ + +#define STM32_SDMMC_CMD_RESET (0) /* Reset value */ + +#define STM32_SDMMC_RESPCMD_SHIFT (0) +#define STM32_SDMMC_RESPCMD_MASK (0x3f << STM32_SDMMC_RESPCMD_SHIFT) + +#define STM32_SDMMC_DTIMER_RESET (0) /* Reset value */ + +#define STM32_SDMMC_DLEN_SHIFT (0) +#define STM32_SDMMC_DLEN_MASK (0x01ffffff << STM32_SDMMC_DLEN_SHIFT) + +#define STM32_SDMMC_DLEN_RESET (0) /* Reset value */ + +#define STM32_SDMMC_DCTRL_DTEN (1 << 0) /* Bit 0: Data transfer enabled bit */ +#define STM32_SDMMC_DCTRL_DTDIR (1 << 1) /* Bit 1: Data transfer direction */ +#define STM32_SDMMC_DCTRL_DTMODE_SHIFT (2) /* Bits 2-3: Data transfer mode */ +#define STM32_SDMMC_DCTRL_DTMODE_MASK (3 << STM32_SDMMC_DCTRL_DTMODE_SHIFT) +# define STM32_SDMMC_DCTRL_DTMODE_BLOCK (0 << STM32_SDMMC_DCTRL_DTMODE_SHIFT) +# define STM32_SDMMC_DCTRL_DTMODE_SDIO (1 << STM32_SDMMC_DCTRL_DTMODE_SHIFT) +# define STM32_SDMMC_DCTRL_DTMODE_EMMC (2 << STM32_SDMMC_DCTRL_DTMODE_SHIFT) +# define STM32_SDMMC_DCTRL_DTMODE_BLKSTOP (3 << STM32_SDMMC_DCTRL_DTMODE_SHIFT) + +#define STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT (4) /* Bits 7-4: Data block size */ +#define STM32_SDMMC_DCTRL_DBLOCKSIZE_MASK (0xf << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_1BYTE (0 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_2BYTES (1 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_4BYTES (2 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_8BYTES (3 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_16BYTES (4 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_32BYTES (5 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_64BYTES (6 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_128BYTES (7 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_256BYTES (8 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_512BYTES (9 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_1KBYTE (10 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_2KBYTES (11 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_4KBYTES (12 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_8KBYTES (13 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +# define STM32_SDMMC_DCTRL_16KBYTES (14 << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT) +#define STM32_SDMMC_DCTRL_RWSTART (1 << 8) /* Bit 8: Read wait start */ +#define STM32_SDMMC_DCTRL_RWSTOP (1 << 9) /* Bit 9: Read wait stop */ +#define STM32_SDMMC_DCTRL_RWMOD (1 << 10) /* Bit 10: Read wait mode */ +#define STM32_SDMMC_DCTRL_SDIOEN (1 << 11) /* Bit 11: SD I/O enable functions */ +#define STM32_SDMMC_DCTRL_BOOTACKEN (1 << 12) /* Bit 12: Enable reception of boot ack */ +#define STM32_SDMMC_DCTRL_FIFORST (1 << 13) /* Bit 13: FIFO reset */ + +#define STM32_SDMMC_DCTRL_RESET (0) /* Reset value */ + +#define STM32_SDMMC_DCOUNT_SHIFT (0) +#define STM32_SDMMC_DCOUNT_MASK (0x01ffffff << STM32_SDMMC_DCOUNT_SHIFT) + +#define STM32_SDMMC_STA_CCRCFAIL (1 << 0) /* Bit 0: Command response CRC fail */ +#define STM32_SDMMC_STA_DCRCFAIL (1 << 1) /* Bit 1: Data block CRC fail */ +#define STM32_SDMMC_STA_CTIMEOUT (1 << 2) /* Bit 2: Command response timeout */ +#define STM32_SDMMC_STA_DTIMEOUT (1 << 3) /* Bit 3: Data timeout */ +#define STM32_SDMMC_STA_TXUNDERR (1 << 4) /* Bit 4: Transmit FIFO underrun error */ +#define STM32_SDMMC_STA_RXOVERR (1 << 5) /* Bit 5: Received FIFO overrun error */ +#define STM32_SDMMC_STA_CMDREND (1 << 6) /* Bit 6: Command response received */ +#define STM32_SDMMC_STA_CMDSENT (1 << 7) /* Bit 7: Command sent */ +#define STM32_SDMMC_STA_DATAEND (1 << 8) /* Bit 8: Data end */ +#define STM32_SDMMC_STA_DHOLD (1 << 9) /* Bit 9: Data transfer hold */ +#define STM32_SDMMC_STA_DBCKEND (1 << 10) /* Bit 10: Data block sent/received */ +#define STM32_SDMMC_STA_DABORT (1 << 11) /* Bit 11: Data transfer aborted by CMD12 */ +#define STM32_SDMMC_STA_DPSMACT (1 << 12) /* Bit 12: Data path state machine active */ +#define STM32_SDMMC_STA_CPSMACT (1 << 13) /* Bit 13: Command path state machine active */ +#define STM32_SDMMC_STA_TXFIFOHE (1 << 14) /* Bit 14: Transmit FIFO half empty */ +#define STM32_SDMMC_STA_RXFIFOHF (1 << 15) /* Bit 15: Receive FIFO half full */ +#define STM32_SDMMC_STA_TXFIFOF (1 << 16) /* Bit 16: Transmit FIFO full */ +#define STM32_SDMMC_STA_RXFIFOF (1 << 17) /* Bit 17: Receive FIFO full */ +#define STM32_SDMMC_STA_TXFIFOE (1 << 18) /* Bit 18: Transmit FIFO empty */ +#define STM32_SDMMC_STA_RXFIFOE (1 << 19) /* Bit 19: Receive FIFO empty */ +#define STM32_SDMMC_STA_BUSYD0 (1 << 20) /* Bit 20: Card signals busy on SDMMC_D0 */ +#define STM32_SDMMC_STA_BUSYD0END (1 << 21) /* Bit 21: card SDMMC_D0 signal changed from busy to NOT busy */ +#define STM32_SDMMC_STA_SDIOIT (1 << 22) /* Bit 22: SDIO interrupt received */ +#define STM32_SDMMC_STA_ACKFAIL (1 << 23) /* Bit 23: Boot ack check fail */ +#define STM32_SDMMC_STA_ACKTIMEOUT (1 << 24) /* Bit 24: Boot ack timeout */ +#define STM32_SDMMC_STA_VSWEND (1 << 25) /* Bit 25: Voltage switch critical timing section completion */ +#define STM32_SDMMC_STA_CKSTOP (1 << 26) /* Bit 26: SDMMC_CK stopped in Voltage switch procedure */ +#define STM32_SDMMC_STA_IDMATE (1 << 27) /* Bit 27: IDMA transfer error */ +#define STM32_SDMMC_STA_IDMABTC (1 << 28) /* Bit 28: IDMA buffer transfer complete */ + +#define STM32_SDMMC_ICR_CCRCFAILC (1 << 0) /* Bit 0: CCRCFAIL flag clear bit */ +#define STM32_SDMMC_ICR_DCRCFAILC (1 << 1) /* Bit 1: DCRCFAIL flag clear bit */ +#define STM32_SDMMC_ICR_CTIMEOUTC (1 << 2) /* Bit 2: CTIMEOUT flag clear bit */ +#define STM32_SDMMC_ICR_DTIMEOUTC (1 << 3) /* Bit 3: DTIMEOUT flag clear bit */ +#define STM32_SDMMC_ICR_TXUNDERRC (1 << 4) /* Bit 4: TXUNDERR flag clear bit */ +#define STM32_SDMMC_ICR_RXOVERRC (1 << 5) /* Bit 5: RXOVERR flag clear bit */ +#define STM32_SDMMC_ICR_CMDRENDC (1 << 6) /* Bit 6: CMDREND flag clear bit */ +#define STM32_SDMMC_ICR_CMDSENTC (1 << 7) /* Bit 7: CMDSENT flag clear bit */ +#define STM32_SDMMC_ICR_DATAENDC (1 << 8) /* Bit 8: DATAEND flag clear bit */ +#define STM32_SDMMC_ICR_DHOLDC (1 << 9) /* Bit 9: DHOLD flag clear bit */ +#define STM32_SDMMC_ICR_DBCKENDC (1 << 10) /* Bit 10: DBCKEND flag clear bit */ +#define STM32_SDMMC_ICR_DABORTC (1 << 11) /* Bit 11: DABORT flag clear bit */ +#define STM32_SDMMC_ICR_BUSYD0ENDC (1 << 21) /* Bit 21: BUSYD0END flag clear bit */ +#define STM32_SDMMC_ICR_SDIOITC (1 << 22) /* Bit 22: SDIOIT flag clear bit */ +#define STM32_SDMMC_ICR_ACKFAILC (1 << 23) /* Bit 23: ACKFAIL flag clear bit */ +#define STM32_SDMMC_ICR_ACKTIMEOUTC (1 << 24) /* Bit 24: ACKTIMEOUT flag clear bit */ +#define STM32_SDMMC_ICR_VSWENDC (1 << 25) /* Bit 25: VSWEND flag clear bit */ +#define STM32_SDMMC_ICR_CKSTOPC (1 << 26) /* Bit 26: CKSTOP flag clear bit */ +#define STM32_SDMMC_ICR_IDMATEC (1 << 27) /* Bit 27: IDMA transfer error clear bit */ +#define STM32_SDMMC_ICR_IDMABTCC (1 << 28) /* Bit 28: IDMA buffer transfer complete clear bit */ + +#define STM32_SDMMC_ICR_RESET 0x1fe00fff +#define STM32_SDMMC_ICR_ALLFLAGS 0x1fe00fff + +#define STM32_SDMMC_MASK_CCRCFAILIE (1 << 0) /* Bit 0: Command CRC fail interrupt enable */ +#define STM32_SDMMC_MASK_DCRCFAILIE (1 << 1) /* Bit 1: Data CRC fail interrupt enable */ +#define STM32_SDMMC_MASK_CTIMEOUTIE (1 << 2) /* Bit 2: Command timeout interrupt enable */ +#define STM32_SDMMC_MASK_DTIMEOUTIE (1 << 3) /* Bit 3: Data timeout interrupt enable */ +#define STM32_SDMMC_MASK_TXUNDERRIE (1 << 4) /* Bit 4: Tx FIFO underrun error interrupt enable */ +#define STM32_SDMMC_MASK_RXOVERRIE (1 << 5) /* Bit 5: Rx FIFO overrun error interrupt enable */ +#define STM32_SDMMC_MASK_CMDRENDIE (1 << 6) /* Bit 6: Command response received interrupt enable */ +#define STM32_SDMMC_MASK_CMDSENTIE (1 << 7) /* Bit 7: Command sent interrupt enable */ +#define STM32_SDMMC_MASK_DATAENDIE (1 << 8) /* Bit 8: Data end interrupt enable */ +#define STM32_SDMMC_MASK_DHOLDIE (1 << 9) /* Bit 9: Data hold interrupt enable */ +#define STM32_SDMMC_MASK_DBCKENDIE (1 << 10) /* Bit 10: Data block end interrupt enable */ +#define STM32_SDMMC_MASK_DABOTRTIE (1 << 11) /* Bit 11: Data transfer aborted interrupt enable */ +#define STM32_SDMMC_MASK_TXFIFOHEIE (1 << 14) /* Bit 14: Tx FIFO half empty interrupt enable */ +#define STM32_SDMMC_MASK_RXFIFOHFIE (1 << 15) /* Bit 15: Rx FIFO half full interrupt enable */ +#define STM32_SDMMC_MASK_RXFIFOFIE (1 << 17) /* Bit 17: Rx FIFO full interrupt enable */ +#define STM32_SDMMC_MASK_TXFIFOFIE (1 << 18) /* Bit 18: Tx FIFO full interrupt enable */ +#define STM32_SDMMC_MASK_BUSYD0ENDIE (1 << 21) /* Bit 21: BUSYD0END interrupt enable */ +#define STM32_SDMMC_MASK_SDIOITIE (1 << 22) /* Bit 22: SDIO mode interrupt received interrupt enable */ +#define STM32_SDMMC_MASK_ACKFAILIE (1 << 23) /* Bit 23: Acknowledgment Fail interrupt enable */ +#define STM32_SDMMC_MASK_ACKTIMEOUTIE (1 << 24) /* Bit 24: Acknowledgment timeout interrupt enable */ +#define STM32_SDMMC_MASK_VSWENDIE (1 << 25) /* Bit 25: Voltage switch critical timing section completion interrupt enable */ +#define STM32_SDMMC_MASK_CKSTOPIE (1 << 26) /* Bit 26: Voltage Switch clock stopped interrupt enable */ +#define STM32_SDMMC_MASK_IDMABTCIE (1 << 28) /* Bit 28: IDMA buffer transfer complete interrupt enable */ + +#define STM32_SDMMC_MASK_RESET (0) + +#define STM32_SDMMC_IDMACTRLR_IDMAEN (1 << 0) /* Bit 0: IDMA enable */ +#define STM32_SDMMC_IDMACTRLR_IDMABMODE (1 << 1) /* Bit 1: Buffer mode selection */ +#define STM32_SDMMC_IDMACTRLR_IDMABACT (1 << 2) /* Bit 2: Double buffer mode active buffer indication */ + +#define STM32_SDMMC_IDMABSIZER_SHIFT (5) /* Bits 12-5: Number of bytes per buffer divided by 32 */ +#define STM32_SDMMC_IDMABSIZER_MASK (0xff << STM32_SDMMC_IDMABSIZER_SHIFT) + +#define STM32_SDMMC_FIFOCNT_SHIFT (0) +#define STM32_SDMMC_FIFOCNT_MASK (0x0ffffff << STM32_SDMMC_FIFOCNT_SHIFT) + +#endif /* __ARCH_ARM_SRC_STM32H5_HARDWARE_STM32H5XXX_SDMMC_H */ diff --git a/arch/arm/src/stm32h5/stm32_sdmmc.c b/arch/arm/src/stm32h5/stm32_sdmmc.c new file mode 100644 index 00000000000..e82be34f4b6 --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_sdmmc.c @@ -0,0 +1,3764 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_sdmmc.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "chip.h" +#include "arm_internal.h" +#include "stm32_dma.h" +#include "stm32_gpio.h" +#include "stm32_rcc.h" +#include "stm32_sdmmc.h" + +#if defined(CONFIG_STM32_SDMMC1) || defined(CONFIG_STM32_SDMMC2) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +/* HW Issues when not using IDMA + * + * The FIFO lacks a FIFO data available interrupt. It only has a + * FIFO Half Full and Full interrupt. Given that the FIFO size is 64 + * bytes (16 uint 32), this creates an issue for commands that read + * less than 32 bytes hereafter referred to as a Lame FIFO. + * + * The DATAEND interrupt is only issued on the FIFO being emptied and it + * appears the DTIMER does not start running again until the FIFO is read + * so that DTIMEOUT interrupt can not be used to end the read operation. + * + * The only workaround is to use the IDMA to service the FIFO and to monitor + * for a remainder in the FIFO that is less than FIFO_SIZE_IN_BYTES / 2. + * + * Therefore even when IDMA is not used, the IDMA still has to be used to + * service the FIFO to receive less than FIFO_SIZE_IN_BYTES / 2 to a local + * buffer. When the FIFO Half Full and Full interrupt do fire the FIFO will + * be monitored off the an HP work thread for a residual of less than + * FIFO_SIZE_IN_BYTES / 2. + * + * HW Issues when using IDMA + * + * The DMA buffer must be located in a zone accessible via IDMA. + * For SDMMC1, IDMA cannot access SRAM123. Refer to ST AN5200. + * Buffer validity is checked when CONFIG_ARCH_HAVE_SDIO_PREFLIGHT is set. + * + * MDMA is only available on for SDMMC1 and Not supported at this time. + * + * Required system configuration options: + * + * CONFIG_SCHED_WORKQUEUE -- Callback support requires work queue support. + * + * Driver-specific configuration options: + * + * CONFIG_SDIO_MUXBUS - Setting this configuration enables some locking + * APIs to manage concurrent accesses on the SDMMC bus. This is not + * needed for the simple case of a single SD card, for example. + * CONFIG_STM32_SDMMC_IDMA - Enable SDMMC IDMA. + * DMA support for SDMMC. If disabled, the SDMMC will work in + * interrupt mode and still use the IDMA to a local buffer for data + * lengths less the 32 bytes due to the FIFO limitations. + * CONFIG_SDMMC1/2_WIDTH_D1_ONLY - This may be selected to force the driver + * operate with only a single data line (the default is to use all + * 4 SD data lines). + * CONFIG_STM32_SDMMC_XFRDEBUG - Enables some very low-level debug + * output This also requires CONFIG_DEBUG_FS and CONFIG_DEBUG_INFO + * CONFIG_SDMMC1/2_SDIO_MODE + * Build ins additional support needed only for SDIO cards (vs. SD memory + * cards) + * CONFIG_SDMMC1/2_SDIO_PULLUP + * If you are using an external SDCard module that does not have the + * pull-up resistors for the SDIO interface (like the Gadgeteer SD Card + * Module) then enable this option to activate the internal pull-up + * resistors. + */ + +/* If there are 2 SDMMC enabled, then Slot 0 is SDMMC1, and Slot 1 is SDMMC2 + * If there is only 1 SDMMC, then Slot 0 is assigned to the defined SDMMC + * hence, if only SDMMC2 is defined it will be slot 0. + */ + +#if !defined(CONFIG_STM32_SDMMC1) +# define SDMMC2_SLOT 0 +#else +# define SDMMC2_SLOT 1 +#endif + +#if !defined(CONFIG_STM32_SDMMC_IDMA) +# warning "Large Non-DMA transfer may result in RX overrun failures" +#elif defined(CONFIG_STM32_SDMMC1) +# define STM32H5_SRAM_START STM32_SRAM1_BASE +# define STM32H5_SRAM_END (STM32_SRAM3_BASE + STM32_SRAM3_SIZE) +#endif + +#ifndef CONFIG_SCHED_WORKQUEUE +# error "Callback support requires CONFIG_SCHED_WORKQUEUE" +#endif + +#undef HAVE_SDMMC_SDIO_MODE +#if defined(CONFIG_SDMMC1_SDIO_MODE) || defined(CONFIG_SDMMC2_SDIO_MODE) +# define HAVE_SDMMC_SDIO_MODE +#endif + +#if !defined(CONFIG_DEBUG_FS) || !defined(CONFIG_DEBUG_FEATURES) +# undef CONFIG_STM32_SDMMC_XFRDEBUG +#endif + +#ifdef CONFIG_SDMMC1_SDIO_PULLUP +# define SDMMC1_SDIO_PULL(g) (((g) & ~GPIO_PUPD_MASK) | GPIO_PULLUP) +#else +# define SDMMC1_SDIO_PULL(g) (((g) & ~GPIO_PUPD_MASK) | GPIO_FLOAT) +#endif + +#ifdef CONFIG_SDMMC2_SDIO_PULLUP +# define SDMMC2_SDIO_PULL(g) (((g) & ~GPIO_PUPD_MASK) | GPIO_PULLUP) +#else +# define SDMMC2_SDIO_PULL(g) (((g) & ~GPIO_PUPD_MASK) | GPIO_FLOAT) +#endif + +/* Define the Hardware FIFO size */ + +#define FIFO_SIZE_IN_BYTES 64 + +/* Friendly Clock source & CLKCR bit re-definitions *************************/ + +/* If not set in board use default pll1_q_ck clock is selected as + * kernel peripheral clock (default after reset) + */ + +#if !defined(STM32_RCC_D1CCIPR_SDMMCSEL) +# define STM32_RCC_D1CCIPR_SDMMCSEL RCC_CCIPR4_SDMMC1SEL +#endif + +#if STM32_RCC_D1CCIPR_SDMMCSEL == RCC_CCIPR4_SDMMC1SEL +# define STM32_SDMMC_CLK STM32_PLL1Q_FREQUENCY +#else +# define STM32_SDMMC_CLK STM32_PLL2R_FREQUENCY +#endif + +#define STM32_CLKCR_RISINGEDGE (0) +#define STM32_CLKCR_FALLINGEDGE STM32_SDMMC_CLKCR_NEGEDGE + +/* Use the default of the rising edge but allow a configuration, + * that does not have the errata, to override the edge the SDIO + * command and data is changed on. + */ + +#if !defined(STM32_SDMMC_CLKCR_EDGE) +# define STM32_SDMMC_CLKCR_EDGE STM32_CLKCR_RISINGEDGE +#endif + +/* Mode dependent settings. These depend on clock divisor settings that must + * be defined in the board-specific board.h header file: + * STM32_SDMMC_INIT_CLKDIV, STM32_SDMMC_MMCXFR_CLKDIV, and + * STM32_SDMMC_SDXFR_CLKDIV. + */ + +#define STM32_CLCKCR_INIT (STM32_SDMMC_INIT_CLKDIV | \ + STM32_SDMMC_CLKCR_EDGE | \ + STM32_SDMMC_CLKCR_WIDBUS_D1) +#define STM32_SDMMC_CLKCR_MMCXFR (STM32_SDMMC_MMCXFR_CLKDIV | \ + STM32_SDMMC_CLKCR_EDGE | \ + STM32_SDMMC_CLKCR_PWRSAV | \ + STM32_SDMMC_CLKCR_WIDBUS_D1) +#define STM32_SDMMC_CLCKR_SDXFR (STM32_SDMMC_SDXFR_CLKDIV | \ + STM32_SDMMC_CLKCR_EDGE | \ + STM32_SDMMC_CLKCR_PWRSAV | \ + STM32_SDMMC_CLKCR_WIDBUS_D1) +#ifdef HAVE_SDMMC_SDIO_MODE +/* Do not enable power saving configuration bit (in SD 4-bit mode) because + * the SDIO clock is not enabled when the bus goes to the idle state. + * This condition breaks interrupts delivering mechanism over DAT[1]/IRQ + * SDIO line to the host. + */ +# define STM32_SDMMC_CLCKR_SDWIDEXFR (STM32_SDMMC_SDXFR_CLKDIV | \ + STM32_SDMMC_CLKCR_EDGE | \ + STM32_SDMMC_CLKCR_WIDBUS_D4) +#else +# define STM32_SDMMC_CLCKR_SDWIDEXFR (STM32_SDMMC_SDXFR_CLKDIV | \ + STM32_SDMMC_CLKCR_EDGE | \ + STM32_SDMMC_CLKCR_PWRSAV | \ + STM32_SDMMC_CLKCR_WIDBUS_D4) +#endif + +/* Timing */ + +#define SDMMC_CMDTIMEOUT (100000) +#define SDMMC_LONGTIMEOUT (0x7fffffff) + +/* DTIMER setting */ + +#define SDMMC_DTIMER_DATATIMEOUT_MS 250 + +/* Block size for multi-block transfers */ + +#define SDMMC_MAX_BLOCK_SIZE (512) + +/* Data transfer interrupt mask bits */ + +/* DMA interrupts */ + +#define STM32_SDMMC_DMARECV_MASK (STM32_SDMMC_MASK_DCRCFAILIE | \ + STM32_SDMMC_MASK_DTIMEOUTIE | \ + STM32_SDMMC_MASK_DATAENDIE | \ + STM32_SDMMC_MASK_RXOVERRIE) + +#define STM32_SDMMC_DMASEND_MASK (STM32_SDMMC_MASK_DCRCFAILIE | \ + STM32_SDMMC_MASK_DTIMEOUTIE | \ + STM32_SDMMC_MASK_DATAENDIE | \ + STM32_SDMMC_MASK_TXUNDERRIE) +#define STM32_SDMMC_XFRDONE_MASK (0) + +/* Interrupt mode */ + +#define STM32_SDMMC_RECV_MASK (STM32_SDMMC_MASK_DCRCFAILIE | \ + STM32_SDMMC_MASK_DTIMEOUTIE | \ + STM32_SDMMC_MASK_DATAENDIE | \ + STM32_SDMMC_MASK_RXOVERRIE | \ + STM32_SDMMC_MASK_RXFIFOHFIE) + +#define STM32_SDMMC_SEND_MASK (STM32_SDMMC_MASK_DCRCFAILIE | \ + STM32_SDMMC_MASK_DTIMEOUTIE | \ + STM32_SDMMC_MASK_DATAENDIE | \ + STM32_SDMMC_MASK_TXFIFOHEIE | \ + STM32_SDMMC_MASK_TXUNDERRIE) + +/* Event waiting interrupt mask bits */ + +#define STM32_SDMMC_CMDDONE_STA (STM32_SDMMC_STA_CMDSENT) + +#define STM32_SDMMC_RESPDONE_STA (STM32_SDMMC_STA_CTIMEOUT | \ + STM32_SDMMC_STA_CCRCFAIL | \ + STM32_SDMMC_STA_CMDREND) + +#define STM32_SDMMC_CMDDONE_MASK (STM32_SDMMC_MASK_CMDSENTIE) + +#define STM32_SDMMC_RESPDONE_MASK (STM32_SDMMC_MASK_CCRCFAILIE | \ + STM32_SDMMC_MASK_CTIMEOUTIE | \ + STM32_SDMMC_MASK_CMDRENDIE) + +#define STM32_SDMMC_XFRDONE_MASK (0) + +#define STM32_SDMMC_CMDDONE_ICR (STM32_SDMMC_ICR_CMDSENTC | \ + STM32_SDMMC_ICR_DBCKENDC) + +#define STM32_SDMMC_RESPDONE_ICR (STM32_SDMMC_ICR_CTIMEOUTC | \ + STM32_SDMMC_ICR_CCRCFAILC | \ + STM32_SDMMC_ICR_CMDRENDC | \ + STM32_SDMMC_ICR_DBCKENDC) + +#define STM32_SDMMC_XFRDONE_ICR (STM32_SDMMC_ICR_DATAENDC | \ + STM32_SDMMC_ICR_DCRCFAILC | \ + STM32_SDMMC_ICR_DTIMEOUTC | \ + STM32_SDMMC_ICR_RXOVERRC | \ + STM32_SDMMC_ICR_TXUNDERRC | \ + STM32_SDMMC_ICR_DBCKENDC) + +#define STM32_SDMMC_WAITALL_ICR (STM32_SDMMC_CMDDONE_ICR | \ + STM32_SDMMC_RESPDONE_ICR | \ + STM32_SDMMC_XFRDONE_ICR | \ + STM32_SDMMC_ICR_DBCKENDC) + +/* Let's wait until we have both SDIO transfer complete and DMA complete. */ + +#define SDMMC_XFRDONE_FLAG (1) +#define SDMMC_DMADONE_FLAG (2) +#define SDMMC_ALLDONE (3) + +/* Register logging support */ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +# define SAMPLENDX_BEFORE_SETUP 0 +# define SAMPLENDX_AFTER_SETUP 1 +# define SAMPLENDX_END_TRANSFER 2 +# define DEBUG_NSAMPLES 3 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure defines the state of the STM32 SDMMC interface */ + +struct stm32_dev_s +{ + struct sdio_dev_s dev; /* Standard, base SDIO interface */ + + /* STM32-specific extensions */ + + uint32_t base; + int nirq; +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) + uint32_t d0_gpio; +#endif + + /* Event support */ + + sem_t waitsem; /* Implements event waiting */ + sdio_eventset_t waitevents; /* Set of events to be waited for */ + uint32_t waitmask; /* Interrupt enables for event waiting */ + volatile sdio_eventset_t wkupevent; /* The event that caused the wakeup */ + struct wdog_s waitwdog; /* Watchdog that handles event timeouts */ + + /* Callback support */ + + sdio_statset_t cdstatus; /* Card status */ + sdio_eventset_t cbevents; /* Set of events to be cause callbacks */ + worker_t callback; /* Registered callback function */ + void *cbarg; /* Registered callback argument */ + struct work_s cbwork; /* Callback work queue structure */ + + /* Interrupt mode data transfer support */ + + uint32_t *buffer; /* Address of current R/W buffer */ + size_t remaining; /* Number of bytes remaining in the transfer */ + uint32_t xfrmask; /* Interrupt enables for data transfer */ + + /* DMA data transfer support */ + + bool widebus; /* Required for DMA support */ + bool onebit; /* true: Only 1-bit transfers are supported */ + +#if defined(HAVE_SDMMC_SDIO_MODE) + /* Interrupt at SDIO_D1 pin, only for SDIO cards */ + + uint32_t sdiointmask; /* STM32 SDIO register mask */ + int (*do_sdio_card)(void *); /* SDIO card ISR */ + void *do_sdio_arg; /* arg for SDIO card ISR */ + bool sdiomode; /* True: in SDIO mode */ +#endif + + /* Misc */ + + uint32_t blocksize; /* Current block size */ + uint32_t nblocks; /* Current block count (for DTMODE selection) */ + uint32_t receivecnt; /* Real count to receive */ +#if !defined(CONFIG_STM32_SDMMC_IDMA) + struct work_s cbfifo; /* Monitor for Lame FIFO */ +#endif + uint8_t rxfifo[FIFO_SIZE_IN_BYTES] /* To offload with IDMA and support un-aligned buffers */ + aligned_data(ARMV8M_DCACHE_LINESIZE); + bool unaligned_rx; /* read buffer is not cache-line or 32 bit aligned */ + + /* Input dma buffer for unaligned transfers */ +#if defined(CONFIG_STM32_SDMMC_IDMA) + + /* REVISIT: hack for now to avoid sorting out the DMA's reach */ + + uint8_t *sdmmc_rxbuffer; +#endif +}; + +/* Register logging support */ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +struct stm32_sdioregs_s +{ + uint8_t power; + uint16_t clkcr; + uint16_t dctrl; + uint32_t dtimer; + uint32_t dlen; + uint32_t dcount; + uint32_t sta; + uint32_t mask; +}; + +struct stm32_sampleregs_s +{ + struct stm32_sdioregs_s sdio; +}; +#endif + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Low-level helpers ********************************************************/ + +static inline void sdmmc_putreg32(struct stm32_dev_s *priv, uint32_t value, + int offset); +static inline uint32_t sdmmc_getreg32(struct stm32_dev_s *priv, int offset); +static inline void stm32_setclkcr(struct stm32_dev_s *priv, uint32_t clkcr); +static void stm32_configwaitints(struct stm32_dev_s *priv, uint32_t waitmask, + sdio_eventset_t waitevents, + sdio_eventset_t wkupevents); +static void stm32_configxfrints(struct stm32_dev_s *priv, uint32_t xfrmask); +static void stm32_setpwrctrl(struct stm32_dev_s *priv, uint32_t pwrctrl); + +/* Debug Helpers ************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +static void stm32_sampleinit(void); +static void stm32_sdiosample(struct stm32_dev_s *priv, + struct stm32_sdioregs_s *regs); +static void stm32_sample(struct stm32_dev_s *priv, int index); +static void stm32_sdiodump(struct stm32_sdioregs_s *regs, const char *msg); +static void stm32_dumpsample(struct stm32_dev_s *priv, + struct stm32_sampleregs_s *regs, + const char *msg); +static void stm32_dumpsamples(struct stm32_dev_s *priv); +#else +# define stm32_sampleinit() +# define stm32_sample(priv,index) +# define stm32_dumpsamples(priv) +#endif + +/* Data Transfer Helpers ****************************************************/ + +static uint8_t stm32_log2(uint16_t value); +static void stm32_dataconfig(struct stm32_dev_s *priv, uint32_t timeout, + uint32_t dlen, bool receive); +static void stm32_datadisable(struct stm32_dev_s *priv); +#ifndef CONFIG_STM32_SDMMC_IDMA +static void stm32_sendfifo(struct stm32_dev_s *priv); +static void stm32_recvfifo(struct stm32_dev_s *priv); +#else +static void stm32_recvdma(struct stm32_dev_s *priv); +#endif +static void stm32_eventtimeout(wdparm_t arg); +static void stm32_endwait(struct stm32_dev_s *priv, + sdio_eventset_t wkupevent); +static void stm32_endtransfer(struct stm32_dev_s *priv, + sdio_eventset_t wkupevent); + +/* Interrupt Handling *******************************************************/ + +static int stm32_sdmmc_interrupt(int irq, void *context, void *arg); +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) +static int stm32_sdmmc_rdyinterrupt(int irq, void *context, void *arg); +#endif + +/* SDIO interface methods ***************************************************/ + +/* Mutual exclusion */ + +#if defined(CONFIG_SDIO_MUXBUS) +static int stm32_lock(struct sdio_dev_s *dev, bool lock); +#endif + +/* Initialization/setup */ + +static void stm32_reset(struct sdio_dev_s *dev); +static sdio_capset_t stm32_capabilities(struct sdio_dev_s *dev); +static sdio_statset_t stm32_status(struct sdio_dev_s *dev); +static void stm32_widebus(struct sdio_dev_s *dev, bool enable); +static void stm32_clock(struct sdio_dev_s *dev, + enum sdio_clock_e rate); +static int stm32_attach(struct sdio_dev_s *dev); + +/* Command/Status/Data Transfer */ + +static int stm32_sendcmd(struct sdio_dev_s *dev, uint32_t cmd, + uint32_t arg); +static void stm32_blocksetup(struct sdio_dev_s *dev, + unsigned int blocksize, unsigned int nblocks); +#ifndef CONFIG_STM32_SDMMC_IDMA +static int stm32_recvsetup(struct sdio_dev_s *dev, uint8_t *buffer, + size_t nbytes); +static int stm32_sendsetup(struct sdio_dev_s *dev, + const uint8_t *buffer, size_t nbytes); +#endif +static int stm32_cancel(struct sdio_dev_s *dev); + +static int stm32_waitresponse(struct sdio_dev_s *dev, uint32_t cmd); +static int stm32_recvshortcrc(struct sdio_dev_s *dev, uint32_t cmd, + uint32_t *rshort); +static int stm32_recvlong(struct sdio_dev_s *dev, uint32_t cmd, + uint32_t rlong[4]); +static int stm32_recvshort(struct sdio_dev_s *dev, uint32_t cmd, + uint32_t *rshort); + +/* EVENT handler */ + +static void stm32_waitenable(struct sdio_dev_s *dev, + sdio_eventset_t eventset, uint32_t timeout); +static sdio_eventset_t stm32_eventwait(struct sdio_dev_s *dev); +static void stm32_callbackenable(struct sdio_dev_s *dev, + sdio_eventset_t eventset); +static int stm32_registercallback(struct sdio_dev_s *dev, + worker_t callback, void *arg); + +/* DMA */ + +#if defined(CONFIG_STM32_SDMMC_IDMA) +# if defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) +static int stm32_dmapreflight(struct sdio_dev_s *dev, + const uint8_t *buffer, size_t buflen); +# endif +static int stm32_dmarecvsetup(struct sdio_dev_s *dev, + uint8_t *buffer, size_t buflen); +static int stm32_dmasendsetup(struct sdio_dev_s *dev, + const uint8_t *buffer, size_t buflen); +#endif + +/* Initialization/uninitialization/reset ************************************/ + +static void stm32_callback(void *arg); +static void stm32_default(struct stm32_dev_s *priv); + +/**************************************************************************** + * Private Data + ****************************************************************************/ +#if defined(CONFIG_STM32_SDMMC1) +struct stm32_dev_s g_sdmmcdev1 = +{ + .dev = + { +#if defined(CONFIG_SDIO_MUXBUS) + .lock = stm32_lock, +#endif + .reset = stm32_reset, + .capabilities = stm32_capabilities, + .status = stm32_status, + .widebus = stm32_widebus, + .clock = stm32_clock, + .attach = stm32_attach, + .sendcmd = stm32_sendcmd, + .blocksetup = stm32_blocksetup, +#if defined(CONFIG_STM32_SDMMC_IDMA) + .recvsetup = stm32_dmarecvsetup, + .sendsetup = stm32_dmasendsetup, +#else + .recvsetup = stm32_recvsetup, + .sendsetup = stm32_sendsetup, +#endif + .cancel = stm32_cancel, + .waitresponse = stm32_waitresponse, + .recv_r1 = stm32_recvshortcrc, + .recv_r2 = stm32_recvlong, + .recv_r3 = stm32_recvshort, + .recv_r4 = stm32_recvshort, + .recv_r5 = stm32_recvshortcrc, + .recv_r6 = stm32_recvshortcrc, + .recv_r7 = stm32_recvshort, + .waitenable = stm32_waitenable, + .eventwait = stm32_eventwait, + .callbackenable = stm32_callbackenable, + .registercallback = stm32_registercallback, +#if defined(CONFIG_STM32_SDMMC_IDMA) +# if defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) + .dmapreflight = stm32_dmapreflight, +# endif + .dmarecvsetup = stm32_dmarecvsetup, + .dmasendsetup = stm32_dmasendsetup, +#endif + }, + .base = STM32_SDMMC1_BASE, + .nirq = STM32_IRQ_SDMMC1, +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) + .d0_gpio = SDMMC1_SDIO_PULL(GPIO_SDMMC1_D0), +#endif + .waitsem = SEM_INITIALIZER(0), +#if defined(HAVE_SDMMC_SDIO_MODE) && defined(CONFIG_SDMMC1_SDIO_MODE) + .sdiomode = true, +#endif +}; +#endif +#if defined(CONFIG_STM32_SDMMC2) +struct stm32_dev_s g_sdmmcdev2 = +{ + .dev = + { +#if defined(CONFIG_SDIO_MUXBUS) + .lock = stm32_lock, +#endif + .reset = stm32_reset, + .capabilities = stm32_capabilities, + .status = stm32_status, + .widebus = stm32_widebus, + .clock = stm32_clock, + .attach = stm32_attach, + .sendcmd = stm32_sendcmd, + .blocksetup = stm32_blocksetup, +#if defined(CONFIG_STM32_SDMMC_IDMA) + .recvsetup = stm32_dmarecvsetup, + .sendsetup = stm32_dmasendsetup, +#else + .recvsetup = stm32_recvsetup, + .sendsetup = stm32_sendsetup, +#endif + .cancel = stm32_cancel, + .waitresponse = stm32_waitresponse, + .recv_r1 = stm32_recvshortcrc, + .recv_r2 = stm32_recvlong, + .recv_r3 = stm32_recvshort, + .recv_r4 = stm32_recvshort, + .recv_r5 = stm32_recvshortcrc, + .recv_r6 = stm32_recvshortcrc, + .recv_r7 = stm32_recvshort, + .waitenable = stm32_waitenable, + .eventwait = stm32_eventwait, + .callbackenable = stm32_callbackenable, + .registercallback = stm32_registercallback, +#if defined(CONFIG_STM32_SDMMC_IDMA) +# if defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) + .dmapreflight = stm32_dmapreflight, +# endif + .dmarecvsetup = stm32_dmarecvsetup, + .dmasendsetup = stm32_dmasendsetup, +#endif + }, + .base = STM32_SDMMC2_BASE, + .nirq = STM32_IRQ_SDMMC2, +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) + .d0_gpio = SDMMC2_SDIO_PULL(GPIO_SDMMC2_D0), +#endif + .waitsem = SEM_INITIALIZER(0), +#if defined(HAVE_SDMMC_SDIO_MODE) && defined(CONFIG_SDMMC2_SDIO_MODE) + .sdiomode = true, +#endif +}; +#endif +/* Register logging support */ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +static struct stm32_sampleregs_s g_sampleregs[DEBUG_NSAMPLES]; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: sdmmc_putreg32 + ****************************************************************************/ + +static inline void sdmmc_putreg32(struct stm32_dev_s *priv, uint32_t value, + int offset) +{ + putreg32(value, priv->base + offset); +} + +/**************************************************************************** + * Name: sdmmc_gettreg32 + ****************************************************************************/ + +static inline uint32_t sdmmc_getreg32(struct stm32_dev_s *priv, int offset) +{ + return getreg32(priv->base + offset); +} + +/**************************************************************************** + * Name: sdmmc_modifyreg32 + ****************************************************************************/ + +static inline void sdmmc_modifyreg32(struct stm32_dev_s *priv, int offset, + uint32_t clearbits, uint32_t setbits) +{ + irqstate_t flags; + int32_t regval; + + flags = enter_critical_section(); + regval = getreg32(priv->base + offset); + regval &= ~clearbits; + regval |= setbits; + putreg32(regval, priv->base + offset); + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: stm32_setclkcr + * + * Description: + * Modify oft-changed bits in the CLKCR register. Only the following bit- + * fields are changed: + * + * CLKDIV, PWRSAV, BYPASS, WIDBUS, NEGEDGE, and HWFC_EN + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * clkcr - A new CLKCR setting for the above mentions bits (other bits + * are ignored. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static inline void stm32_setclkcr(struct stm32_dev_s *priv, uint32_t clkcr) +{ + uint32_t regval = sdmmc_getreg32(priv, STM32_SDMMC_CLKCR_OFFSET); + + /* Clear CLKDIV, PWRSAV, WIDBUS, NEGEDGE, HWFC_EN bits */ + + regval &= ~(STM32_SDMMC_CLKCR_CLKDIV_MASK | STM32_SDMMC_CLKCR_PWRSAV | + STM32_SDMMC_CLKCR_WIDBUS_MASK | STM32_SDMMC_CLKCR_NEGEDGE | + STM32_SDMMC_CLKCR_HWFC_EN | STM32_SDMMC_CLKCR_DDR | + STM32_SDMMC_CLKCR_BUS_SPEED | + STM32_SDMMC_CLKCR_SELCLKRX_MASK); + + /* Replace with user provided settings */ + + clkcr &= (STM32_SDMMC_CLKCR_CLKDIV_MASK | STM32_SDMMC_CLKCR_PWRSAV | + STM32_SDMMC_CLKCR_WIDBUS_MASK | STM32_SDMMC_CLKCR_NEGEDGE | + STM32_SDMMC_CLKCR_HWFC_EN | STM32_SDMMC_CLKCR_DDR | + STM32_SDMMC_CLKCR_BUS_SPEED | + STM32_SDMMC_CLKCR_SELCLKRX_MASK); + + regval |= clkcr | STM32_SDMMC_CLKCR_HWFC_EN; + + sdmmc_putreg32(priv, regval, STM32_SDMMC_CLKCR_OFFSET); + + mcinfo("CLKCR: %08" PRIx32 " PWR: %08" PRIx32 "\n", + sdmmc_getreg32(priv, STM32_SDMMC_CLKCR_OFFSET), + sdmmc_getreg32(priv, STM32_SDMMC_POWER_OFFSET)); +} + +/**************************************************************************** + * Name: stm32_configwaitints + * + * Description: + * Enable/disable SDIO interrupts needed to support the wait function + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * waitmask - The set of bits in the SDIO MASK register to set + * waitevents - Waited for events + * wkupevent - Wake-up events + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_configwaitints(struct stm32_dev_s *priv, uint32_t waitmask, + sdio_eventset_t waitevents, + sdio_eventset_t wkupevent) +{ + irqstate_t flags; +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) + int pinset; +#endif + + /* Save all of the data and set the new interrupt mask in one, atomic + * operation. + */ + + flags = enter_critical_section(); + +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) + if ((waitevents & SDIOWAIT_WRCOMPLETE) != 0) + { + pinset = priv->d0_gpio & (GPIO_PORT_MASK | GPIO_PIN_MASK | \ + GPIO_PUPD_MASK); + pinset |= (GPIO_INPUT | GPIO_EXTI); + + /* Arm the SDMMC_D0 Ready and install Isr */ + + stm32_gpiosetevent(pinset, true, false, false, + stm32_sdmmc_rdyinterrupt, priv); + } + + /* Disarm SDMMC_D0 ready and return it to SDMMC D0 */ + + if ((wkupevent & SDIOWAIT_WRCOMPLETE) != 0) + { + stm32_gpiosetevent(priv->d0_gpio, false, false, false, + NULL, NULL); + } +#endif + + priv->waitevents = waitevents; + priv->wkupevent = wkupevent; + priv->waitmask = waitmask; + +#if defined(HAVE_SDMMC_SDIO_MODE) + if (priv->sdiomode == true) + { + sdmmc_putreg32(priv, + priv->xfrmask | priv->waitmask | priv->sdiointmask, + STM32_SDMMC_MASK_OFFSET); + } + else +#endif + { + sdmmc_putreg32(priv, priv->xfrmask | priv->waitmask, + STM32_SDMMC_MASK_OFFSET); + } + + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: stm32_configxfrints + * + * Description: + * Enable SDIO interrupts needed to support the data transfer event + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * xfrmask - The set of bits in the SDIO MASK register to set + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_configxfrints(struct stm32_dev_s *priv, uint32_t xfrmask) +{ + irqstate_t flags; + + flags = enter_critical_section(); + priv->xfrmask = xfrmask; + +#if defined(HAVE_SDMMC_SDIO_MODE) + if (priv->sdiomode == true) + { + sdmmc_putreg32(priv, + priv->xfrmask | priv->waitmask | priv->sdiointmask, + STM32_SDMMC_MASK_OFFSET); + } + else +#endif + { + sdmmc_putreg32(priv, priv->xfrmask | priv->waitmask, + STM32_SDMMC_MASK_OFFSET); + } + + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: stm32_setpwrctrl + * + * Description: + * Change the PWRCTRL field of the SDIO POWER register to turn the SDIO + * ON or OFF + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * clkcr - A new PWRCTRL setting + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_setpwrctrl(struct stm32_dev_s *priv, uint32_t pwrctrl) +{ + uint32_t regval; + + regval = sdmmc_getreg32(priv, STM32_SDMMC_POWER_OFFSET); + regval &= ~STM32_SDMMC_POWER_PWRCTRL_MASK; + regval |= pwrctrl; + sdmmc_putreg32(priv, regval, STM32_SDMMC_POWER_OFFSET); +} + +/**************************************************************************** + * Name: stm32_sampleinit + * + * Description: + * Setup prior to collecting register samples + * + ****************************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +static void stm32_sampleinit(void) +{ + memset(g_sampleregs, 0xff, DEBUG_NSAMPLES * + sizeof(struct stm32_sampleregs_s)); +} +#endif + +/**************************************************************************** + * Name: stm32_sdiosample + * + * Description: + * Sample SDIO registers + * + ****************************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +static void stm32_sdiosample(struct stm32_dev_s *priv, + struct stm32_sdioregs_s *regs) +{ + regs->power = (uint8_t)sdmmc_getreg32(priv, STM32_SDMMC_POWER_OFFSET); + regs->clkcr = (uint16_t)sdmmc_getreg32(priv, STM32_SDMMC_CLKCR_OFFSET); + regs->dctrl = (uint16_t)sdmmc_getreg32(priv, STM32_SDMMC_DCTRL_OFFSET); + regs->dtimer = sdmmc_getreg32(priv, STM32_SDMMC_DTIMER_OFFSET); + regs->dlen = sdmmc_getreg32(priv, STM32_SDMMC_DLEN_OFFSET); + regs->dcount = sdmmc_getreg32(priv, STM32_SDMMC_DCOUNT_OFFSET); + regs->sta = sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET); + regs->mask = sdmmc_getreg32(priv, STM32_SDMMC_MASK_OFFSET); +} +#endif + +/**************************************************************************** + * Name: stm32_sample + * + * Description: + * Sample SDIO registers + * + ****************************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +static void stm32_sample(struct stm32_dev_s *priv, int index) +{ + struct stm32_sampleregs_s *regs = &g_sampleregs[index]; + + stm32_sdiosample(priv, ®s->sdio); +} +#endif + +/**************************************************************************** + * Name: stm32_sdiodump + * + * Description: + * Dump one register sample + * + ****************************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +static void stm32_sdiodump(struct stm32_sdioregs_s *regs, const char *msg) +{ + mcinfo("SDIO Registers: %s\n", msg); + mcinfo(" POWER[%08x]: %08" PRIx8 "\n", STM32_SDMMC_POWER_OFFSET, + regs->power); + mcinfo(" CLKCR[%08x]: %08" PRIx16 "\n", STM32_SDMMC_CLKCR_OFFSET, + regs->clkcr); + mcinfo(" DCTRL[%08x]: %08" PRIx16 "\n", STM32_SDMMC_DCTRL_OFFSET, + regs->dctrl); + mcinfo(" DTIMER[%08x]: %08" PRIx32 "\n", STM32_SDMMC_DTIMER_OFFSET, + regs->dtimer); + mcinfo(" DLEN[%08x]: %08" PRIx32 "\n", STM32_SDMMC_DLEN_OFFSET, + regs->dlen); + mcinfo(" DCOUNT[%08x]: %08" PRIx32 "\n", STM32_SDMMC_DCOUNT_OFFSET, + regs->dcount); + mcinfo(" STA[%08x]: %08" PRIx32 "\n", STM32_SDMMC_STA_OFFSET, + regs->sta); + mcinfo(" MASK[%08x]: %08" PRIx32 "\n", STM32_SDMMC_MASK_OFFSET, + regs->mask); +} +#endif + +/**************************************************************************** + * Name: stm32_dumpsample + * + * Description: + * Dump one register sample + * + ****************************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +static void stm32_dumpsample(struct stm32_dev_s *priv, + struct stm32_sampleregs_s *regs, + const char *msg) +{ + stm32_sdiodump(®s->sdio, msg); +} +#endif + +/**************************************************************************** + * Name: stm32_dumpsamples + * + * Description: + * Dump all sampled register data + * + ****************************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_XFRDEBUG) +static void stm32_dumpsamples(struct stm32_dev_s *priv) +{ + stm32_dumpsample(priv, &g_sampleregs[SAMPLENDX_BEFORE_SETUP], + "Before setup"); + stm32_dumpsample(priv, &g_sampleregs[SAMPLENDX_AFTER_SETUP], + "After setup"); + stm32_dumpsample(priv, &g_sampleregs[SAMPLENDX_END_TRANSFER], + "End of transfer"); +} +#endif + +/**************************************************************************** + * Name: stm32_log2 + * + * Description: + * Take (approximate) log base 2 of the provided number (Only works if the + * provided number is a power of 2). + * + ****************************************************************************/ + +static uint8_t stm32_log2(uint16_t value) +{ + uint8_t log2 = 0; + + /* 0000 0000 0000 0001 -> return 0, + * 0000 0000 0000 001x -> return 1, + * 0000 0000 0000 01xx -> return 2, + * 0000 0000 0000 1xxx -> return 3, + * ... + * 1xxx xxxx xxxx xxxx -> return 15, + */ + + DEBUGASSERT(value > 0); + while (value != 1) + { + value >>= 1; + log2++; + } + + return log2; +} + +/**************************************************************************** + * Name: stm32_dataconfig + * + * Description: + * Configure the SDIO data path for the next data transfer + * + ****************************************************************************/ + +static void stm32_dataconfig(struct stm32_dev_s *priv, uint32_t timeout, + uint32_t dlen, bool receive) +{ + uint32_t clkdiv; + uint32_t regval; + uint32_t dctrl; + uint32_t sdio_clk = STM32_SDMMC_CLK; + + DEBUGASSERT((sdmmc_getreg32(priv, STM32_SDMMC_IDMACTRLR_OFFSET) & + STM32_SDMMC_IDMACTRLR_IDMAEN) == 0); + DEBUGASSERT((sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) & + STM32_SDMMC_STA_DPSMACT) == 0); + + /* Configure DCTRL DTDIR, DTMODE, and DBLOCKSIZE fields. + * Note: the DTEN is not used, DPSM, and CPSM are used. + */ + + dctrl = sdmmc_getreg32(priv, STM32_SDMMC_DCTRL_OFFSET); + dctrl &= ~(STM32_SDMMC_DCTRL_DTDIR | STM32_SDMMC_DCTRL_DTMODE_MASK | + STM32_SDMMC_DCTRL_DBLOCKSIZE_MASK | STM32_SDMMC_DCTRL_DTEN | + STM32_SDMMC_DCTRL_BOOTACKEN); + + dctrl &= (STM32_SDMMC_DCTRL_DTDIR | STM32_SDMMC_DCTRL_DTMODE_MASK | + STM32_SDMMC_DCTRL_DBLOCKSIZE_MASK); + + /* Configure the data direction */ + + if (receive) + { + dctrl |= STM32_SDMMC_DCTRL_DTDIR; + } + + /* Set SDIO_MODE */ + +#if defined(HAVE_SDMMC_SDIO_MODE) + if (priv->sdiomode == true) + { + dctrl |= STM32_SDMMC_DCTRL_SDIOEN; + } +#endif + + /* Select DTMODE: + * - CMD53 byte mode: nblocks=1, blocksize = actual byte count < 128 + * -> use DTMODE_SDIO (SDIO multibyte), matching bare-metal + * TRANSFER_MODE_SDIO + * - CMD53 block mode: blocksize = registered block size (128 for CC33xx) + * -> use DTMODE_BLOCK for per-block CRC checking + * Threshold 128 = CC33xx registered Fn2 block size. Byte mode transfers + * always have blocksize < 128 because the CMD53 byte count is < 128. + */ + +#if defined(HAVE_SDMMC_SDIO_MODE) + if (priv->sdiomode && priv->nblocks == 1 && priv->blocksize < 128) + { + dctrl |= STM32_SDMMC_DCTRL_DTMODE_SDIO; /* CMD53 byte/multibyte mode */ + } + else +#endif + { + dctrl |= STM32_SDMMC_DCTRL_DTMODE_BLOCK; + } + + /* if dlen > priv->blocksize we assume that this is a multi-block transfer + * and that the len is multiple of priv->blocksize. + */ + + if (dlen > priv->blocksize) + { + DEBUGASSERT((dlen % priv->blocksize) == 0); + +#if defined(CONFIG_STM32_SDMMC_IDMA) + /* If this is an unaligned receive, then receive one block at a + * time to the internal buffer + */ + + if (priv->unaligned_rx) + { + dlen = priv->blocksize; + } +#endif + } + + dctrl |= stm32_log2(priv->blocksize) << STM32_SDMMC_DCTRL_DBLOCKSIZE_SHIFT; + + /* Enable data path */ + + /* Set DTIMER + * + * Enable data path using a timeout scaled to the SD_CLOCK (the card + * clock). + */ + + regval = sdmmc_getreg32(priv, STM32_SDMMC_CLKCR_OFFSET); + clkdiv = (regval & STM32_SDMMC_CLKCR_CLKDIV_MASK) >> + STM32_SDMMC_CLKCR_CLKDIV_SHIFT; + + /* CLKDIV_ of 0x000: is Bypass */ + + if (clkdiv != 0) + { + sdio_clk = sdio_clk / (2 * clkdiv); + } + + /* Convert Timeout in Ms to SD_CLK counts */ + + timeout = timeout * (sdio_clk / 1000); + + sdmmc_putreg32(priv, timeout, STM32_SDMMC_DTIMER_OFFSET); + + /* Set DLEN */ + + sdmmc_putreg32(priv, dlen, STM32_SDMMC_DLEN_OFFSET); + + /* Set DCTRL */ + + sdmmc_putreg32(priv, dctrl, STM32_SDMMC_DCTRL_OFFSET); +} + +/**************************************************************************** + * Name: stm32_datadisable + * + * Description: + * Disable the SDIO data path setup by stm32_dataconfig() and + * disable DMA. + * + ****************************************************************************/ + +static void stm32_datadisable(struct stm32_dev_s *priv) +{ + uint32_t regval; + + /* Clear all error/completion flags first; DPSM may reject new + * configuration if stale flags are set. + */ + + sdmmc_putreg32(priv, STM32_SDMMC_ICR_ALLFLAGS, + STM32_SDMMC_ICR_OFFSET); + + /* Disable the IDMA */ + + sdmmc_putreg32(priv, 0, STM32_SDMMC_IDMACTRLR_OFFSET); + + /* Disable the data path */ + + /* Reset DTIMER */ + + sdmmc_putreg32(priv, UINT32_MAX, STM32_SDMMC_DTIMER_OFFSET); + + /* Reset DLEN */ + + sdmmc_putreg32(priv, 0, STM32_SDMMC_DLEN_OFFSET); + + /* Reset DCTRL DTEN, DTDIR, DTMODE, and DBLOCKSIZE fields */ + + regval = sdmmc_getreg32(priv, STM32_SDMMC_DCTRL_OFFSET); + regval &= ~(STM32_SDMMC_DCTRL_DTEN | STM32_SDMMC_DCTRL_DBLOCKSIZE_MASK | + STM32_SDMMC_DCTRL_DTDIR | STM32_SDMMC_DCTRL_DTMODE_MASK); + sdmmc_putreg32(priv, regval, STM32_SDMMC_DCTRL_OFFSET); + + /* Wait for DPSMACT to clear (1 µs per iteration, up to 10 ms). + * DTIMER/DLEN/DCTRL can only be written when DPSMACT=0; without this + * wait, stm32_dataconfig silently ignores those writes and every + * subsequent transfer is misconfigured (TXFIFOF=1, DATAEND never fires). + */ + + int timeout = 10000; + + while ((sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) & + STM32_SDMMC_STA_DPSMACT) && --timeout > 0) + { + up_udelay(1); + } + + if (timeout == 0) + { + mcerr("datadisable: DPSMACT stuck after 10ms! STA=0x%08" PRIx32 + " DTIMER=0x%08" PRIx32 "\n", + sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET), + sdmmc_getreg32(priv, STM32_SDMMC_DTIMER_OFFSET)); + } +} + +/**************************************************************************** + * Name: stm32_sendfifo + * + * Description: + * Send SDIO data in interrupt mode + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#if !defined(CONFIG_STM32_SDMMC_IDMA) +static void stm32_sendfifo(struct stm32_dev_s *priv) +{ + union + { + uint32_t w; + uint8_t b[4]; + } data; + + /* Loop while there is more data to be sent and the RX FIFO is not full */ + + while (priv->remaining > 0 && + (sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) & + STM32_SDMMC_STA_TXFIFOF) == 0) + { + /* Is there a full word remaining in the user buffer? */ + + if (priv->remaining >= sizeof(uint32_t)) + { + /* Yes, transfer the word to the TX FIFO */ + + data.w = *priv->buffer++; + priv->remaining -= sizeof(uint32_t); + } + else + { + /* No.. transfer just the bytes remaining in the user buffer, + * padding with zero as necessary to extend to a full word. + */ + + uint8_t *ptr = (uint8_t *)priv->buffer; + int i; + + data.w = 0; + for (i = 0; i < (int)priv->remaining; i++) + { + data.b[i] = *ptr++; + } + + /* Now the transfer is finished */ + + priv->remaining = 0; + } + + /* Put the word in the FIFO */ + + sdmmc_putreg32(priv, data.w, STM32_SDMMC_FIFO_OFFSET); + } +} +#endif + +/**************************************************************************** + * Name: stm32_recvfifo + * + * Description: + * Receive SDIO data in interrupt mode + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#if !defined(CONFIG_STM32_SDMMC_IDMA) +static void stm32_recvfifo(struct stm32_dev_s *priv) +{ + union + { + uint32_t w; + uint8_t b[4]; + } data; + + /* Loop while there is space to store the data and there is more + * data available in the RX FIFO. + */ + + while (priv->remaining > 0 && + (sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) & + STM32_SDMMC_STA_RXFIFOE) == 0) + { + /* Read the next word from the RX FIFO */ + + data.w = sdmmc_getreg32(priv, STM32_SDMMC_FIFO_OFFSET); + if (priv->remaining >= sizeof(uint32_t)) + { + /* Transfer the whole word to the user buffer */ + + *priv->buffer++ = data.w; + priv->remaining -= sizeof(uint32_t); + } + else + { + /* Transfer any trailing fractional word */ + + uint8_t *ptr = (uint8_t *)priv->buffer; + int i; + + for (i = 0; i < (int)priv->remaining; i++) + { + *ptr++ = data.b[i]; + } + + /* Now the transfer is finished */ + + priv->remaining = 0; + } + } +} +#endif + +/**************************************************************************** + * Name: stm32_recvdma + * + * Description: + * Receive SDIO data in dma mode + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#if defined (CONFIG_STM32_SDMMC_IDMA) +static void stm32_recvdma(struct stm32_dev_s *priv) +{ + uint32_t dctrl; + + if (priv->unaligned_rx) + { + /* If we are receiving multiple blocks to an unaligned buffers, + * we receive them one-by-one + */ + + /* Copy the received data to client buffer */ + + memcpy(priv->buffer, priv->sdmmc_rxbuffer, priv->blocksize); + + /* Invalidate the cache before receiving next block */ + + up_invalidate_dcache((uintptr_t)priv->sdmmc_rxbuffer, + (uintptr_t)priv->sdmmc_rxbuffer + + priv->blocksize); + + /* Update how much there is left to receive */ + + priv->remaining -= priv->blocksize; + } + else + { + /* In an aligned case, we have always received all blocks. + * + * The destination buffer was invalidated before the DMA in + * stm32_dmarecvsetup(), but the cache can speculatively prefetch + * into this (cacheable) buffer between that point and DMA + * completion, leaving stale lines that shadow the data just + * written by the IDMA. Invalidate again now that the transfer is + * complete, before the buffer is consumed, so the CPU reads the + * freshly received data instead of a previously cached sector. + * The buffer and length are cache-line aligned here (that is why + * this aligned path was taken), so no adjacent memory is affected. + */ + + up_invalidate_dcache((uintptr_t)priv->buffer, + (uintptr_t)priv->buffer + priv->receivecnt); + + priv->remaining = 0; + } + + if (priv->remaining == 0) + { + /* no data remaining, end the transfer */ + + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE); + } + else + { + /* We end up here only in unaligned rx-buffers case, and are receiving + * the data one block at a time + */ + + /* Update where to receive the following block */ + + priv->buffer = (uint32_t *)((uintptr_t)priv->buffer + priv->blocksize); + + /* Clear pending interrupt status */ + + sdmmc_putreg32(priv, STM32_SDMMC_XFRDONE_ICR, STM32_SDMMC_ICR_OFFSET); + + /* Re-enable datapath and wait for next block */ + + dctrl = sdmmc_getreg32(priv, STM32_SDMMC_DCTRL_OFFSET); + dctrl |= STM32_SDMMC_DCTRL_DTEN; + sdmmc_putreg32(priv, dctrl, STM32_SDMMC_DCTRL_OFFSET); + } +} +#endif + +/**************************************************************************** + * Name: stm32_eventtimeout + * + * Description: + * The watchdog timeout setup when the event wait start has expired without + * any other waited-for event occurring. + * + * Input Parameters: + * arg - The argument + * + * Returned Value: + * None + * + * Assumptions: + * Always called from the interrupt level with interrupts disabled. + * + ****************************************************************************/ + +static void stm32_eventtimeout(wdparm_t arg) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)arg; + + /* There is always race conditions with timer expirations. */ + + DEBUGASSERT((priv->waitevents & SDIOWAIT_TIMEOUT) != 0 || + priv->wkupevent != 0); + + mcinfo("sta: %08" PRIx32 " enabled irq: %08" PRIx32 "\n", + sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET), + sdmmc_getreg32(priv, STM32_SDMMC_MASK_OFFSET)); + + /* Is a data transfer complete event expected? */ + + if ((priv->waitevents & SDIOWAIT_TIMEOUT) != 0) + { + /* Yes.. wake up any waiting threads */ + +#ifdef CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE + stm32_endwait(priv, SDIOWAIT_TIMEOUT | + (priv->waitevents & SDIOWAIT_WRCOMPLETE)); +#else + stm32_endwait(priv, SDIOWAIT_TIMEOUT); +#endif + mcerr("Timeout: remaining: %zu\n", priv->remaining); + } +} + +/**************************************************************************** + * Name: stm32_endwait + * + * Description: + * Wake up a waiting thread if the waited-for event has occurred. + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * wkupevent - The event that caused the wait to end + * + * Returned Value: + * None + * + * Assumptions: + * Always called from the interrupt level with interrupts disabled. + * + ****************************************************************************/ + +static void stm32_endwait(struct stm32_dev_s *priv, + sdio_eventset_t wkupevent) +{ + /* Cancel the watchdog timeout */ + + wd_cancel(&priv->waitwdog); + + /* Disable event-related interrupts */ + + stm32_configwaitints(priv, 0, 0, wkupevent); + + /* Wake up the waiting thread */ + + nxsem_post(&priv->waitsem); +} + +/**************************************************************************** + * Name: stm32_endtransfer + * + * Description: + * Terminate a transfer with the provided status. This function is called + * only from the SDIO interrupt handler when end-of-transfer conditions + * are detected. + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * wkupevent - The event that caused the transfer to end + * + * Returned Value: + * None + * + * Assumptions: + * Always called from the interrupt level with interrupts disabled. + * + ****************************************************************************/ + +static void stm32_endtransfer(struct stm32_dev_s *priv, + sdio_eventset_t wkupevent) +{ + /* Disable all transfer related interrupts */ + + stm32_configxfrints(priv, 0); + + /* If there were errors, send a stop command to DPSM */ + + if ((wkupevent & (~SDIOWAIT_TRANSFERDONE)) != 0) + { + sdmmc_putreg32(priv, STM32_SDMMC_CMD_CMDSTOP, + STM32_SDMMC_CMD_OFFSET); + } + else + { + sdmmc_modifyreg32(priv, STM32_SDMMC_CMD_OFFSET, + STM32_SDMMC_CMD_CPSMEN, 0); + } + + /* Clearing pending interrupt status on all transfer related interrupts */ + + sdmmc_putreg32(priv, STM32_SDMMC_XFRDONE_ICR, STM32_SDMMC_ICR_OFFSET); + + /* DMA debug instrumentation */ + + stm32_sample(priv, SAMPLENDX_END_TRANSFER); + + /* Mark the transfer finished */ + + priv->remaining = 0; + + /* Is a thread wait for these data transfer complete events? */ + + if ((priv->waitevents & wkupevent) != 0) + { + /* Yes.. wake up any waiting threads */ + + stm32_endwait(priv, wkupevent); + } +} + +/**************************************************************************** + * Name: stm32_sdmmc_fifo_monitor + * + * Description: + * SDMMC FIFO monitor is used to detect the FIFO is stalled with less + * than FIFO_SIZE_IN_BYTES /2 that will trigger an interrupt. + * + * Input Parameters: + * arg - is the stm32_dev_s + * + * Returned Value: + * None + * + ****************************************************************************/ + +#if !defined(CONFIG_STM32_SDMMC_IDMA) +static void stm32_sdmmc_fifo_monitor(void *arg) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)arg; + + if (priv->receivecnt && priv->remaining && + priv->remaining < FIFO_SIZE_IN_BYTES / 2) + { + /* Manually Receive data from the lame RX FIFO */ + + stm32_recvfifo(priv); + } + + /* Check for the lame FIFO condition */ + + if (sdmmc_getreg32(priv, STM32_SDMMC_DCOUNT_OFFSET) != 0 && + sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) == + STM32_SDMMC_STA_DPSMACT) + { + work_queue(HPWORK, &priv->cbfifo, + stm32_sdmmc_fifo_monitor, arg, 1); + } +} +#endif + +/**************************************************************************** + * Name: stm32_sdmmc[1|2]_rdyinterrupt + * + * Description: + * SDMMC ready interrupt handler + * + * Input Parameters: + * irq - not used + * context - not used + * + * Returned Value: + * None + * + ****************************************************************************/ + +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) +static int stm32_sdmmc_rdyinterrupt(int irq, void *context, void *arg) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)arg; + + /* Avoid noise, check the state */ + + if (stm32_gpioread(priv->d0_gpio)) + { + stm32_endwait(priv, SDIOWAIT_WRCOMPLETE); + } + + return OK; +} +#endif + +/**************************************************************************** + * Name: stm32_sdmmc_interrupt + * + * Description: + * SDMMC interrupt handler + * + * Input Parameters: + * priv - Instance of the SDMMC private state structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static int stm32_sdmmc_interrupt(int irq, void *context, void *arg) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)arg; + uint32_t enabled; + uint32_t pending; +#if defined(HAVE_SDMMC_SDIO_MODE) + uint32_t mask; +#endif + + DEBUGASSERT(priv != NULL); + + /* Loop while there are pending interrupts. Check the SDIO status + * register. Mask out all bits that don't correspond to enabled + * interrupts. (This depends on the fact that bits are ordered + * the same in both the STA and MASK register). If there are non-zero + * bits remaining, then we have work to do here. + */ + + while ((enabled = sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) & + sdmmc_getreg32(priv, STM32_SDMMC_MASK_OFFSET)) != 0) + { + /* Handle in progress, interrupt driven data transfers ****************/ + + pending = enabled & priv->xfrmask; + if (pending != 0) + { +#ifndef CONFIG_STM32_SDMMC_IDMA + /* Is the RX FIFO half full or more? Is so then we must be + * processing a receive transaction. + */ + + if ((pending & STM32_SDMMC_STA_RXFIFOHF) != 0) + { + /* Receive data from the RX FIFO */ + + stm32_recvfifo(priv); + work_queue(HPWORK, &priv->cbfifo, + stm32_sdmmc_fifo_monitor, arg, 1); + } + + /* Otherwise, Is the transmit FIFO half empty or less? If so + * we must be processing a send transaction. NOTE: We can't + * be processing both! + */ + + else if ((pending & STM32_SDMMC_STA_TXFIFOHE) != 0) + { + /* Send data via the TX FIFO */ + + stm32_sendfifo(priv); + + /* If we are done shutdown the TXFIFOHE interrupt */ + + if (priv->remaining == 0) + { + sdmmc_putreg32(priv, ~STM32_SDMMC_MASK_TXFIFOHEIE & + sdmmc_getreg32(priv, + STM32_SDMMC_MASK_OFFSET), + STM32_SDMMC_MASK_OFFSET); + } + } +#endif + + /* Handle data end events */ + + if ((pending & STM32_SDMMC_STA_DATAEND) != 0) + { + /* Handle any data remaining the RX FIFO. If the RX FIFO is + * less than half full at the end of the transfer, then no + * half-full interrupt will be received. + */ + +#ifndef CONFIG_STM32_SDMMC_IDMA + + /* If the transfer would not trigger fifo half full + * we used IDMA to manage the lame fifo + */ + + if (priv->remaining && priv->remaining < + FIFO_SIZE_IN_BYTES / 2) + { + memcpy(priv->buffer, priv->rxfifo, priv->remaining); + } +#else + if (priv->receivecnt) + { + /* Invalidate dcache, and copy the received data into + * client buffers in unaligned case + */ + + stm32_recvdma(priv); + } + else +#endif + { + /* Then terminate the transfer. + * Sets STM32_SDMMC_ICR_DATAENDC + */ + + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE); + } + } + + /* Handle data block send/receive CRC failure */ + + else if ((pending & STM32_SDMMC_STA_DCRCFAIL) != 0) + { + /* Terminate the transfer with an error. + * Sets STM32_SDMMC_ICR_DCRCFAILC + */ + + mcerr("ERROR: Data block CRC failure, remaining: %u\n", + priv->remaining); + + stm32_endtransfer(priv, + SDIOWAIT_TRANSFERDONE | SDIOWAIT_ERROR); + } + + /* Handle data timeout error */ + + else if ((pending & STM32_SDMMC_STA_DTIMEOUT) != 0) + { + /* Terminate the transfer with an error + * Sets STM32_SDMMC_ICR_DTIMEOUTC + */ + + mcerr("ERROR: Data timeout, remaining: %u\n", + priv->remaining); + + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE | + SDIOWAIT_TIMEOUT); + } + + /* Handle RX FIFO overrun error */ + + else if ((pending & STM32_SDMMC_STA_RXOVERR) != 0) + { + /* Terminate the transfer with an error + * Sets STM32_SDMMC_ICR_RXOVERRC + */ + + mcerr("ERROR: RX FIFO overrun, remaining: %u\n", + priv->remaining); + + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE | + SDIOWAIT_ERROR); + } + + /* Handle TX FIFO underrun error */ + + else if ((pending & STM32_SDMMC_STA_TXUNDERR) != 0) + { + /* Terminate the transfer with an error + * Sets STM32_SDMMC_ICR_TXUNDERRC + */ + + mcerr("ERROR: TX FIFO underrun, remaining: %u\n", + priv->remaining); + + stm32_endtransfer(priv, SDIOWAIT_TRANSFERDONE | + SDIOWAIT_ERROR); + } + } + + /* Handle wait events *************************************************/ + + pending = enabled & priv->waitmask; + if (pending != 0) + { + /* Is this a response completion event? */ + + if ((pending & STM32_SDMMC_RESPDONE_STA) != 0) + { + sdmmc_putreg32(priv, STM32_SDMMC_RESPDONE_STA, + STM32_SDMMC_ICR_OFFSET); + + /* Yes.. Is there a thread waiting for response done? */ + + if ((priv->waitevents & SDIOWAIT_RESPONSEDONE) != 0) + { + /* Yes.. wake the thread up */ + + stm32_endwait(priv, SDIOWAIT_RESPONSEDONE); + } + } + + /* Is this a command completion event? */ + + if ((pending & STM32_SDMMC_CMDDONE_STA) != 0) + { + sdmmc_putreg32(priv, STM32_SDMMC_CMDDONE_STA, + STM32_SDMMC_ICR_OFFSET); + + /* Yes.. Is there a thread waiting for command done? */ + + if ((priv->waitevents & SDIOWAIT_RESPONSEDONE) != 0) + { + /* Yes.. wake the thread up */ + + stm32_endwait(priv, SDIOWAIT_CMDDONE); + } + } + } + +#if defined(HAVE_SDMMC_SDIO_MODE) + if (priv->sdiomode == true) + { + pending = enabled & priv->sdiointmask; + if (pending != 0) + { + mask = sdmmc_getreg32(priv, STM32_SDMMC_MASK_OFFSET); + + /* Clear the mask so we don't get call'd again */ + + sdmmc_putreg32(priv, mask & ~STM32_SDMMC_MASK_SDIOITIE, + STM32_SDMMC_MASK_OFFSET); + + /* Now clear the interruption */ + + sdmmc_putreg32(priv, STM32_SDMMC_ICR_SDIOITC, + STM32_SDMMC_ICR_OFFSET); + + /* Call the ISR that has been registered */ + + if (priv->do_sdio_card) + { + priv->do_sdio_card(priv->do_sdio_arg); + } + } + } +#endif + } + + return OK; +} + +/**************************************************************************** + * Name: stm32_lock + * + * Description: + * Locks the bus. Function calls low-level multiplexed bus routines to + * resolve bus requests and acknowledgment issues. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * lock - TRUE to lock, FALSE to unlock. + * + * Returned Value: + * OK on success; a negated errno on failure + * + ****************************************************************************/ + +#if defined(CONFIG_SDIO_MUXBUS) +static int stm32_lock(struct sdio_dev_s *dev, bool lock) +{ + /* The multiplex bus is part of board support package. */ + + /* FIXME: Implement the below function to support bus share: + * + * stm32_muxbus_sdio_lock(dev, lock); + */ + + return OK; +} +#endif + +/**************************************************************************** + * Name: stm32_reset + * + * Description: + * Reset the SDIO controller. Undo all setup and initialization. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_reset(struct sdio_dev_s *dev) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + irqstate_t flags; + uint32_t regval; + uint32_t regaddress; + uint32_t restval; + + /* Disable clocking */ + + flags = enter_critical_section(); + +#if defined(CONFIG_STM32_SDMMC1) + if (priv->base == STM32_SDMMC1_BASE) + { + regaddress = STM32_RCC_AHB4RSTR; + restval = RCC_AHB4RSTR_SDMMC1RST; + } +#endif + +#if defined CONFIG_STM32_SDMMC2 + if (priv->base == STM32_SDMMC2_BASE) + { + regaddress = STM32_RCC_AHB4RSTR; + restval = RCC_AHB4RSTR_SDMMC2RST; + } +#endif + + regval = getreg32(regaddress); + putreg32(regval | restval, regaddress); + nxsig_usleep(2); + putreg32(regval, regaddress); + + stm32_setpwrctrl(priv, STM32_SDMMC_POWER_PWRCTRL_CYCLE); + nxsig_usleep(1000); + + /* Put SDIO registers in their default, reset state */ + + stm32_default(priv); + + /* Reset data */ + + priv->waitevents = 0; /* Set of events to be waited for */ + priv->waitmask = 0; /* Interrupt enables for event waiting */ + priv->wkupevent = 0; /* The event that caused the wakeup */ + + wd_cancel(&priv->waitwdog); /* Cancel any timeouts */ + + /* Interrupt mode data transfer support */ + + priv->buffer = 0; /* Address of current R/W buffer */ + priv->remaining = 0; /* Number of bytes remaining in the transfer */ + priv->xfrmask = 0; /* Interrupt enables for data transfer */ + +#ifdef HAVE_SDMMC_SDIO_MODE + priv->sdiointmask = 0; /* SDIO card in-band interrupt mask */ +#endif + + priv->widebus = false; + + /* Configure the SDIO peripheral */ + + stm32_setpwrctrl(priv, STM32_SDMMC_POWER_PWRCTRL_OFF); + nxsig_usleep(1000); + stm32_setpwrctrl(priv, STM32_SDMMC_POWER_PWRCTRL_ON); + + stm32_setclkcr(priv, STM32_CLCKCR_INIT); + + leave_critical_section(flags); + + mcinfo("CLCKR: %08" PRIx32 " POWER: %08" PRIx32 "\n", + sdmmc_getreg32(priv, STM32_SDMMC_CLKCR_OFFSET), + sdmmc_getreg32(priv, STM32_SDMMC_POWER_OFFSET)); +} + +/**************************************************************************** + * Name: stm32_capabilities + * + * Description: + * Get capabilities (and limitations) of the SDIO driver (optional) + * + * Input Parameters: + * dev - Device-specific state data + * + * Returned Value: + * Returns a bitset of status values (see SDIO_CAPS_* defines) + * + ****************************************************************************/ + +static sdio_capset_t stm32_capabilities(struct sdio_dev_s *dev) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + sdio_capset_t caps = 0; + + if (priv->onebit) + { + caps |= SDIO_CAPS_1BIT_ONLY; + } + + caps |= SDIO_CAPS_DMABEFOREWRITE; + +#if defined(CONFIG_STM32_SDMMC_IDMA) + caps |= SDIO_CAPS_DMASUPPORTED; +#endif + + return caps; +} + +/**************************************************************************** + * Name: stm32_status + * + * Description: + * Get SDIO status. + * + * Input Parameters: + * dev - Device-specific state data + * + * Returned Value: + * Returns a bitset of status values (see stm32_status_* defines) + * + ****************************************************************************/ + +static sdio_statset_t stm32_status(struct sdio_dev_s *dev) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + return priv->cdstatus; +} + +/**************************************************************************** + * Name: stm32_widebus + * + * Description: + * Called after change in Bus width has been selected (via ACMD6). Most + * controllers will need to perform some special operations to work + * correctly in the new bus mode. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * wide - true: wide bus (4-bit) bus mode enabled + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_widebus(struct sdio_dev_s *dev, bool wide) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + priv->widebus = wide; +} + +/**************************************************************************** + * Name: stm32_clock + * + * Description: + * Enable/disable SDIO clocking + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * rate - Specifies the clocking to use (see enum sdio_clock_e) + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_clock(struct sdio_dev_s *dev, enum sdio_clock_e rate) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + uint32_t clckr; + + switch (rate) + { + /* Disable clocking (with default ID mode divisor) */ + + default: + case CLOCK_SDIO_DISABLED: + clckr = STM32_CLCKCR_INIT; + break; + + /* Enable in initial ID mode clocking (<400KHz) */ + + case CLOCK_IDMODE: + clckr = STM32_CLCKCR_INIT; + break; + + /* Enable in MMC normal operation clocking */ + + case CLOCK_MMC_TRANSFER: + clckr = STM32_SDMMC_CLKCR_MMCXFR; + break; + + /* SD normal operation clocking (wide 4-bit mode) */ + + case CLOCK_SD_TRANSFER_4BIT: + if (!priv->onebit) + { + clckr = STM32_SDMMC_CLCKR_SDWIDEXFR; + break; + } + + /* SD normal operation clocking (narrow 1-bit mode) */ + + case CLOCK_SD_TRANSFER_1BIT: + clckr = STM32_SDMMC_CLCKR_SDXFR; + break; + } + + /* Set the new clock frequency along with the clock enable/disable bit */ + + stm32_setclkcr(priv, clckr); +} + +/**************************************************************************** + * Name: stm32_attach + * + * Description: + * Attach and prepare interrupts + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * + * Returned Value: + * OK on success; A negated errno on failure. + * + ****************************************************************************/ + +static int stm32_attach(struct sdio_dev_s *dev) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + int ret; + + /* Attach the SDIO interrupt handler */ + + ret = irq_attach(priv->nirq, stm32_sdmmc_interrupt, priv); + if (ret == OK) + { + /* Disable all interrupts at the SDIO controller and clear + * interrupt flags + */ + + sdmmc_putreg32(priv, STM32_SDMMC_MASK_RESET, STM32_SDMMC_MASK_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_ICR_ALLFLAGS, STM32_SDMMC_ICR_OFFSET); + + /* Enable SDIO interrupts at the NVIC. They can now be enabled at + * the SDIO controller as needed. + */ + + up_enable_irq(priv->nirq); + } + + return ret; +} + +/**************************************************************************** + * Name: stm32_sendcmd + * + * Description: + * Send the SDIO command + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * cmd - The command to send (32-bits, encoded) + * arg - 32-bit argument required with some commands + * + * Returned Value: + * None + * + ****************************************************************************/ + +static int stm32_sendcmd(struct sdio_dev_s *dev, uint32_t cmd, + uint32_t arg) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + uint32_t regval; + uint32_t cmdidx; + + /* Clear CMDINDEX, CMDTRANS, WAITRESP, WAITINT, WAITPEND, and CPSMEN bits */ + + regval = sdmmc_getreg32(priv, STM32_SDMMC_CMD_OFFSET); + + /* Make sure the register is clear */ + + if (regval & STM32_SDMMC_CMD_CPSMEN) + { + sdmmc_putreg32(priv, 0, STM32_SDMMC_CMD_OFFSET); + } + + /* Set the SDIO Argument value */ + + sdmmc_putreg32(priv, arg, STM32_SDMMC_ARG_OFFSET); + + /* Clear CMDINDEX, WAITRESP, WAITINT, WAITPEND, and CPSMEN bits */ + + regval &= ~(STM32_SDMMC_CMD_CMDINDEX_MASK | STM32_SDMMC_CMD_WAITRESP_MASK | + STM32_SDMMC_CMD_CMDTRANS | STM32_SDMMC_CMD_CMDSTOP | + STM32_SDMMC_CMD_WAITRESP_MASK | STM32_SDMMC_CMD_WAITINT | + STM32_SDMMC_CMD_WAITPEND | STM32_SDMMC_CMD_CPSMEN | + STM32_SDMMC_CMD_DTHOLD | STM32_SDMMC_CMD_BOOTMODE | + STM32_SDMMC_CMD_BOOTEN | STM32_SDMMC_CMD_SUSPEND); + + /* Set WAITRESP bits */ + + switch (cmd & MMCSD_RESPONSE_MASK) + { + case MMCSD_NO_RESPONSE: + regval |= STM32_SDMMC_CMD_NORESPONSE; + break; + + case MMCSD_R1_RESPONSE: + case MMCSD_R1B_RESPONSE: + case MMCSD_R3_RESPONSE: + case MMCSD_R4_RESPONSE: + case MMCSD_R5_RESPONSE: + case MMCSD_R6_RESPONSE: + case MMCSD_R7_RESPONSE: + regval |= STM32_SDMMC_CMD_SHORTRESPONSE; + break; + + case MMCSD_R2_RESPONSE: + regval |= STM32_SDMMC_CMD_LONGRESPONSE; + break; + } + + /* Set CPSMEN and the command index */ + + cmdidx = (cmd & MMCSD_CMDIDX_MASK) >> MMCSD_CMDIDX_SHIFT; + regval |= cmdidx | STM32_SDMMC_CMD_CPSMEN; + + switch (cmd & MMCSD_DATAXFR_MASK) + { + case MMCSD_RDDATAXFR: /* Read block transfer */ + case MMCSD_WRDATAXFR: /* Write block transfer */ + case MMCSD_RDSTREAM: /* MMC Read stream */ + case MMCSD_WRSTREAM: /* MMC Write stream */ + regval |= STM32_SDMMC_CMD_CMDTRANS; + break; + + case MMCSD_NODATAXFR: + default: + if ((cmd & MMCSD_STOPXFR) != 0) + { + regval |= STM32_SDMMC_CMD_CMDSTOP; + } + break; + } + + /* Clear interrupts */ + + sdmmc_putreg32(priv, STM32_SDMMC_CMDDONE_ICR | STM32_SDMMC_RESPDONE_ICR, + STM32_SDMMC_ICR_OFFSET); + + mcinfo("cmd: %08" PRIx32 " arg: %08" PRIx32 " regval: %08" PRIx32 + " enabled irq: %08" PRIx32 "\n", + cmd, arg, regval, sdmmc_getreg32(priv, STM32_SDMMC_MASK_OFFSET)); + + /* Write the SDIO CMD */ + + sdmmc_putreg32(priv, regval, STM32_SDMMC_CMD_OFFSET); + return OK; +} + +/**************************************************************************** + * Name: stm32_blocksetup + * + * Description: + * Configure block size and the number of blocks for next transfer. + * + * Input Parameters: + * dev - An instance of the SDIO device interface. + * blocksize - The selected block size. + * nblocks - The number of blocks to transfer. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_blocksetup(struct sdio_dev_s *dev, + unsigned int blocksize, unsigned int nblocks) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + priv->blocksize = blocksize; + priv->nblocks = nblocks; +} + +/**************************************************************************** + * Name: stm32_recvsetup + * + * Description: + * Setup hardware in preparation for data transfer from the card in non-DMA + * (interrupt driven mode). This method will do whatever controller setup + * is necessary. This would be called for SD memory just BEFORE sending + * CMD13 (SEND_STATUS), CMD17 (READ_SINGLE_BLOCK), CMD18 + * (READ_MULTIPLE_BLOCKS), ACMD51 (SEND_SCR), etc. Normally, + * SDMMC_WAITEVENT will be called to receive the indication that the + * transfer is complete. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * buffer - Address of the buffer in which to receive the data + * nbytes - The number of bytes in the transfer + * + * Returned Value: + * Number of bytes sent on success; a negated errno on failure + * + ****************************************************************************/ + +#ifndef CONFIG_STM32_SDMMC_IDMA +static int stm32_recvsetup(struct sdio_dev_s *dev, uint8_t *buffer, + size_t nbytes) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + DEBUGASSERT(priv != NULL && buffer != NULL && nbytes > 0); + DEBUGASSERT(((uint32_t)buffer & 3) == 0); + + /* Reset the DPSM configuration */ + + stm32_datadisable(priv); + + /* Initialize register sampling */ + + stm32_sampleinit(); + stm32_sample(priv, SAMPLENDX_BEFORE_SETUP); + + /* Save the destination buffer information for use by the interrupt + * handler + */ + + priv->buffer = (uint32_t *)buffer; + priv->remaining = nbytes; + priv->receivecnt = nbytes; + + /* Then set up the SDIO data path */ + + stm32_dataconfig(priv, SDMMC_DTIMER_DATATIMEOUT_MS, nbytes, true); + + /* Workaround the FIFO data available issue */ + + if (nbytes < FIFO_SIZE_IN_BYTES / 2) + { + /* Use the driver's buffer */ + + sdmmc_putreg32(priv, (uintptr_t)priv->rxfifo, + STM32_SDMMC_IDMABASE0R_OFFSET); + + sdmmc_putreg32(priv, STM32_SDMMC_IDMACTRLR_IDMAEN, + STM32_SDMMC_IDMACTRLR_OFFSET); + + /* Ensure CPU will read DMA-ed data */ + + up_invalidate_dcache((uintptr_t)priv->rxfifo, + (uintptr_t)priv->rxfifo + nbytes); + } + + /* And enable interrupts */ + + stm32_configxfrints(priv, STM32_SDMMC_RECV_MASK); + stm32_sample(priv, SAMPLENDX_AFTER_SETUP); + + return OK; +} +#endif + +/**************************************************************************** + * Name: stm32_sendsetup + * + * Description: + * Setup hardware in preparation for data transfer from the card. This + * method will do whatever controller setup is necessary. This would be + * called for SD memory just AFTER sending CMD24 (WRITE_BLOCK), CMD25 + * (WRITE_MULTIPLE_BLOCK), ... and before SDMMC_SENDDATA is called. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * buffer - Address of the buffer containing the data to send + * nbytes - The number of bytes in the transfer + * + * Returned Value: + * Number of bytes sent on success; a negated errno on failure + * + ****************************************************************************/ + +#ifndef CONFIG_STM32_SDMMC_IDMA +static int stm32_sendsetup(struct sdio_dev_s *dev, const + uint8_t *buffer, size_t nbytes) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + DEBUGASSERT(priv != NULL && buffer != NULL && nbytes > 0); + DEBUGASSERT(((uint32_t)buffer & 3) == 0); + + /* Reset the DPSM configuration */ + + stm32_datadisable(priv); + + /* Initialize register sampling */ + + stm32_sampleinit(); + stm32_sample(priv, SAMPLENDX_BEFORE_SETUP); + + /* Save the source buffer information for use by the interrupt handler */ + + priv->buffer = (uint32_t *)buffer; + priv->remaining = nbytes; + priv->receivecnt = 0; + + /* Then set up the SDIO data path */ + + stm32_dataconfig(priv, SDMMC_DTIMER_DATATIMEOUT_MS, nbytes, false); + + /* Enable TX interrupts */ + + stm32_configxfrints(priv, STM32_SDMMC_SEND_MASK); + stm32_sample(priv, SAMPLENDX_AFTER_SETUP); + return OK; +} +#endif + +/**************************************************************************** + * Name: stm32_cancel + * + * Description: + * Cancel the data transfer setup of SDMMC_RECVSETUP, SDMMC_SENDSETUP, + * SDMMC_DMARECVSETUP or SDMMC_DMASENDSETUP. This must be called to cancel + * the data transfer setup if, for some reason, you cannot perform the + * transfer. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * + * Returned Value: + * OK is success; a negated errno on failure + * + ****************************************************************************/ + +static int stm32_cancel(struct sdio_dev_s *dev) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + /* Disable all transfer- and event- related interrupts */ + + stm32_configxfrints(priv, 0); + stm32_configwaitints(priv, 0, 0, 0); + + /* Send a stop command to DPSM */ + + sdmmc_putreg32(priv, STM32_SDMMC_CMD_CMDSTOP, + STM32_SDMMC_CMD_OFFSET); + + /* If this was a IDMA transfer, make sure that IDMA is stopped */ + + sdmmc_putreg32(priv, 0, STM32_SDMMC_IDMACTRLR_OFFSET); + + /* Clearing pending interrupt status on all transfer- and event- related + * interrupts + */ + + sdmmc_putreg32(priv, STM32_SDMMC_WAITALL_ICR, STM32_SDMMC_ICR_OFFSET); + + /* Cancel any watchdog timeout */ + + wd_cancel(&priv->waitwdog); + + /* Mark no transfer in progress */ + + priv->remaining = 0; + return OK; +} + +/**************************************************************************** + * Name: stm32_waitresponse + * + * Description: + * Poll-wait for the response to the last command to be ready. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * cmd - The command that was sent. See 32-bit command definitions above. + * + * Returned Value: + * OK is success; a negated errno on failure + * + ****************************************************************************/ + +static int stm32_waitresponse(struct sdio_dev_s *dev, uint32_t cmd) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + int32_t timeout; + uint32_t events; + + switch (cmd & MMCSD_RESPONSE_MASK) + { + case MMCSD_NO_RESPONSE: + events = STM32_SDMMC_CMDDONE_STA; + timeout = SDMMC_CMDTIMEOUT; + break; + + case MMCSD_R1_RESPONSE: + case MMCSD_R1B_RESPONSE: + case MMCSD_R2_RESPONSE: + case MMCSD_R4_RESPONSE: + case MMCSD_R5_RESPONSE: + case MMCSD_R6_RESPONSE: + events = STM32_SDMMC_RESPDONE_STA; + timeout = SDMMC_LONGTIMEOUT; + break; + + case MMCSD_R3_RESPONSE: + case MMCSD_R7_RESPONSE: + events = STM32_SDMMC_RESPDONE_STA; + timeout = SDMMC_CMDTIMEOUT; + break; + + default: + return -EINVAL; + } + + /* Then wait for the response (or timeout) */ + + while ((sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) & events) == 0) + { + if (--timeout <= 0) + { + mcerr("ERROR: Timeout cmd: %08" PRIx32 " events: %08" PRIx32 + " STA: %08" PRIx32 "\n", + cmd, events, sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET)); + + return -ETIMEDOUT; + } + } + + sdmmc_putreg32(priv, events, STM32_SDMMC_ICR_OFFSET); + return OK; +} + +/**************************************************************************** + * Name: stm32_recv* + * + * Description: + * Receive response to SDIO command. Only the critical payload is + * returned -- that is 32 bits for 48 bit status and 128 bits for 136 bit + * status. The driver implementation should verify the correctness of + * the remaining, non-returned bits (CRCs, CMD index, etc.). + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * Rx - Buffer in which to receive the response + * + * Returned Value: + * Number of bytes sent on success; a negated errno on failure. Here a + * failure means only a failure to obtain the requested response (due to + * transport problem -- timeout, CRC, etc.). The implementation only + * assures that the response is returned intact and does not check errors + * within the response itself. + * + ****************************************************************************/ + +static int stm32_recvshortcrc(struct sdio_dev_s *dev, uint32_t cmd, + uint32_t *rshort) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; +#if defined(CONFIG_DEBUG_FEATURES) + uint32_t respcmd; +#endif + uint32_t regval; + int ret = OK; + + /* R1 Command response (48-bit) + * 47 0 Start bit + * 46 0 Transmission bit (0=from card) + * 45:40 bit5 - bit0 Command index (0-63) + * 39:8 bit31 - bit0 32-bit card status + * 7:1 bit6 - bit0 CRC7 + * 0 1 End bit + * + * R1b Identical to R1 with the additional busy signaling via the data + * line. + * + * R6 Published RCA Response (48-bit, SD card only) + * 47 0 Start bit + * 46 0 Transmission bit (0=from card) + * 45:40 bit5 - bit0 Command index (0-63) + * 39:8 bit31 - bit0 32-bit Argument Field, consisting of: + * [31:16] New published RCA of card + * [15:0] Card status bits {23,22,19,12:0} + * 7:1 bit6 - bit0 CRC7 + * 0 1 End bit + */ + +#if defined(CONFIG_DEBUG_FEATURES) + if (!rshort) + { + mcerr("ERROR: rshort=NULL\n"); + ret = -EINVAL; + } + + /* Check that this is the correct response to this command */ + + else if ((cmd & MMCSD_RESPONSE_MASK) != MMCSD_R1_RESPONSE && + (cmd & MMCSD_RESPONSE_MASK) != MMCSD_R1B_RESPONSE && + (cmd & MMCSD_RESPONSE_MASK) != MMCSD_R5_RESPONSE && + (cmd & MMCSD_RESPONSE_MASK) != MMCSD_R6_RESPONSE) + { + mcerr("ERROR: Wrong response CMD=%08" PRIx32 "\n", cmd); + ret = -EINVAL; + } + else +#endif + { + /* Check if a timeout or CRC error occurred */ + + regval = sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET); + if ((regval & STM32_SDMMC_STA_CTIMEOUT) != 0) + { + mcerr("ERROR: Command timeout: %08" PRIx32 "\n", regval); + ret = -ETIMEDOUT; + } + else if ((regval & STM32_SDMMC_STA_CCRCFAIL) != 0) + { + mcerr("ERROR: CRC failure: %08" PRIx32 "\n", regval); + ret = -EIO; + } +#if defined(CONFIG_DEBUG_FEATURES) + else + { + /* Check response received is of desired command */ + + respcmd = sdmmc_getreg32(priv, STM32_SDMMC_RESPCMD_OFFSET); + if ((uint8_t)(respcmd & STM32_SDMMC_RESPCMD_MASK) != + (cmd & MMCSD_CMDIDX_MASK)) + { + mcerr("ERROR: RESCMD=%02" PRIx32 " CMD=%08" PRIx32 + "\n", respcmd, cmd); + ret = -EINVAL; + } + } +#endif + } + + /* Clear all pending message completion events and return the R1/R6 + * response. + */ + + sdmmc_putreg32(priv, STM32_SDMMC_RESPDONE_ICR | STM32_SDMMC_CMDDONE_ICR, + STM32_SDMMC_ICR_OFFSET); + *rshort = sdmmc_getreg32(priv, STM32_SDMMC_RESP1_OFFSET); + return ret; +} + +static int stm32_recvlong(struct sdio_dev_s *dev, uint32_t cmd, + uint32_t rlong[4]) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + uint32_t regval; + int ret = OK; + + /* R2 CID, CSD register (136-bit) + * 135 0 Start bit + * 134 0 Transmission bit (0=from card) + * 133:128 bit5 - bit0 Reserved + * 127:1 bit127 - bit1 127-bit CID or CSD register + * (including internal CRC) + * 0 1 End bit + */ + +#if defined(CONFIG_DEBUG_FEATURES) + /* Check that R1 is the correct response to this command */ + + if ((cmd & MMCSD_RESPONSE_MASK) != MMCSD_R2_RESPONSE) + { + mcerr("ERROR: Wrong response CMD=%08" PRIx32 "\n", cmd); + ret = -EINVAL; + } + else +#endif + { + /* Check if a timeout or CRC error occurred */ + + regval = sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET); + if (regval & STM32_SDMMC_STA_CTIMEOUT) + { + mcerr("ERROR: Timeout STA: %08" PRIx32 "\n", regval); + ret = -ETIMEDOUT; + } + else if (regval & STM32_SDMMC_STA_CCRCFAIL) + { + mcerr("ERROR: CRC fail STA: %08" PRIx32 "\n", regval); + ret = -EIO; + } + } + + /* Return the long response */ + + sdmmc_putreg32(priv, STM32_SDMMC_RESPDONE_ICR | STM32_SDMMC_CMDDONE_ICR, + STM32_SDMMC_ICR_OFFSET); + if (rlong) + { + rlong[0] = sdmmc_getreg32(priv, STM32_SDMMC_RESP1_OFFSET); + rlong[1] = sdmmc_getreg32(priv, STM32_SDMMC_RESP2_OFFSET); + rlong[2] = sdmmc_getreg32(priv, STM32_SDMMC_RESP3_OFFSET); + rlong[3] = sdmmc_getreg32(priv, STM32_SDMMC_RESP4_OFFSET); + } + + return ret; +} + +static int stm32_recvshort(struct sdio_dev_s *dev, uint32_t cmd, + uint32_t *rshort) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + uint32_t regval; + int ret = OK; + + /* R3 OCR (48-bit) + * 47 0 Start bit + * 46 0 Transmission bit (0=from card) + * 45:40 bit5 - bit0 Reserved + * 39:8 bit31 - bit0 32-bit OCR register + * 7:1 bit6 - bit0 Reserved + * 0 1 End bit + */ + + /* Check that this is the correct response to this command */ + +#if defined(CONFIG_DEBUG_FEATURES) + if ((cmd & MMCSD_RESPONSE_MASK) != MMCSD_R3_RESPONSE && + (cmd & MMCSD_RESPONSE_MASK) != MMCSD_R4_RESPONSE && + (cmd & MMCSD_RESPONSE_MASK) != MMCSD_R7_RESPONSE) + { + mcerr("ERROR: Wrong response CMD=%08" PRIx32 "\n", cmd); + ret = -EINVAL; + } + else +#endif + { + /* Check if a timeout occurred (Apparently a CRC error can terminate + * a good response) + */ + + regval = sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET); + if (regval & STM32_SDMMC_STA_CTIMEOUT) + { + mcerr("ERROR: Timeout STA: %08" PRIx32 "\n", regval); + ret = -ETIMEDOUT; + } + } + + sdmmc_putreg32(priv, STM32_SDMMC_RESPDONE_ICR | STM32_SDMMC_CMDDONE_ICR, + STM32_SDMMC_ICR_OFFSET); + if (rshort) + { + *rshort = sdmmc_getreg32(priv, STM32_SDMMC_RESP1_OFFSET); + } + + return ret; +} + +/**************************************************************************** + * Name: stm32_waitenable + * + * Description: + * Enable/disable of a set of SDIO wait events. This is part of the + * the SDMMC_WAITEVENT sequence. The set of to-be-waited-for events is + * configured before calling stm32_eventwait. This is done in this way + * to help the driver to eliminate race conditions between the command + * setup and the subsequent events. + * + * The enabled events persist until either (1) SDMMC_WAITENABLE is called + * again specifying a different set of wait events, or (2) SDMMC_EVENTWAIT + * returns. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * eventset - A bitset of events to enable or disable (see SDIOWAIT_* + * definitions). 0=disable; 1=enable. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_waitenable(struct sdio_dev_s *dev, + sdio_eventset_t eventset, uint32_t timeout) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + uint32_t waitmask = 0; + + DEBUGASSERT(priv != NULL); + + /* Disable event-related interrupts */ + + stm32_configwaitints(priv, 0, 0, 0); + + /* Select the interrupt mask that will give us the appropriate wakeup + * interrupts. + */ + +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) + if ((eventset & SDIOWAIT_WRCOMPLETE) != 0) + { + /* Read pin to see if ready (true) skip timeout */ + + if (stm32_gpioread(priv->d0_gpio)) + { + eventset &= ~(SDIOWAIT_TIMEOUT | SDIOWAIT_WRCOMPLETE); + } + } + else +#endif + { + if ((eventset & SDIOWAIT_CMDDONE) != 0) + { + waitmask |= STM32_SDMMC_CMDDONE_MASK; + } + + if ((eventset & SDIOWAIT_RESPONSEDONE) != 0) + { + waitmask |= STM32_SDMMC_RESPDONE_MASK; + } + + if ((eventset & SDIOWAIT_TRANSFERDONE) != 0) + { + waitmask |= STM32_SDMMC_XFRDONE_MASK; + } + + /* Enable event-related interrupts */ + + sdmmc_putreg32(priv, STM32_SDMMC_WAITALL_ICR, STM32_SDMMC_ICR_OFFSET); + } + + stm32_configwaitints(priv, waitmask, eventset, 0); + + /* Check if the timeout event is specified in the event set */ + + if ((priv->waitevents & SDIOWAIT_TIMEOUT) != 0) + { + int delay; + int ret; + + /* Yes.. Handle a cornercase: The user request a timeout event but + * with timeout == 0? + */ + + if (!timeout) + { + priv->wkupevent = SDIOWAIT_TIMEOUT; + return; + } + + /* Start the watchdog timer */ + + delay = MSEC2TICK(timeout); + ret = wd_start(&priv->waitwdog, delay, + stm32_eventtimeout, (wdparm_t)priv); + if (ret < OK) + { + mcerr("ERROR: wd_start failed: %d\n", ret); + } + } +} + +/**************************************************************************** + * Name: stm32_eventwait + * + * Description: + * Wait for one of the enabled events to occur (or a timeout). Note that + * all events enabled by SDMMC_WAITEVENTS are disabled when stm32_eventwait + * returns. SDMMC_WAITEVENTS must be called again before stm32_eventwait + * can be used again. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * timeout - Maximum time in milliseconds to wait. Zero means immediate + * timeout with no wait. The timeout value is ignored if + * SDIOWAIT_TIMEOUT is not included in the waited-for eventset. + * + * Returned Value: + * Event set containing the event(s) that ended the wait. Should always + * be non-zero. All events are disabled after the wait concludes. + * + ****************************************************************************/ + +static sdio_eventset_t stm32_eventwait(struct sdio_dev_s *dev) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + sdio_eventset_t wkupevent = 0; + irqstate_t flags; + int ret; + + /* There is a race condition here... the event may have completed before + * we get here. In this case waitevents will be zero, but wkupevents will + * be non-zero (and, hopefully, the semaphore count will also be non-zero. + */ + + flags = enter_critical_section(); + +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) + /* A card ejected while in SDIOWAIT_WRCOMPLETE can lead to a + * condition where there is no waitevents set and no wkupevent + */ + + if (priv->waitevents == 0 && priv->wkupevent == 0) + { + wkupevent = SDIOWAIT_ERROR; + goto errout_with_waitints; + } + +#else + DEBUGASSERT(priv->waitevents != 0 || priv->wkupevent != 0); +#endif + +#if defined(CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE) + if ((priv->waitevents & SDIOWAIT_WRCOMPLETE) != 0) + { + /* Atomically read pin to see if ready (true) and determine if ISR + * fired. If Pin is ready and if ISR did NOT fire end the wait here. + */ + + if (stm32_gpioread(priv->d0_gpio) && + (priv->wkupevent & SDIOWAIT_WRCOMPLETE) == 0) + { + stm32_endwait(priv, SDIOWAIT_WRCOMPLETE); + } + } +#endif + + /* Loop until the event (or the timeout occurs). Race conditions are + * avoided by calling stm32_waitenable prior to triggering the logic that + * will cause the wait to terminate. Under certain race conditions, the + * waited-for may have already occurred before this function was called! + */ + + for (; ; ) + { + /* Wait for an event in event set to occur. If this the event has + * already occurred, then the semaphore will already have been + * incremented and there will be no wait. + */ + + ret = nxsem_wait_uninterruptible(&priv->waitsem); + if (ret < 0) + { + /* Task canceled. Cancel the wdog (assuming it was started) and + * return an SDIO error. + */ + + wd_cancel(&priv->waitwdog); + wkupevent = SDIOWAIT_ERROR; + goto errout_with_waitints; + } + + wkupevent = priv->wkupevent; + + /* Check if the event has occurred. When the event has occurred, then + * evenset will be set to 0 and wkupevent will be set to a nonzero + * value. + */ + + if (wkupevent != 0) + { + /* Yes... break out of the loop with wkupevent non-zero */ + + break; + } + } + + /* Disable event-related interrupts */ + +errout_with_waitints: + stm32_configwaitints(priv, 0, 0, 0); + + leave_critical_section(flags); + stm32_dumpsamples(priv); + return wkupevent; +} + +/**************************************************************************** + * Name: stm32_callbackenable + * + * Description: + * Enable/disable of a set of SDIO callback events. This is part of the + * the SDIO callback sequence. The set of events is configured to enabled + * callbacks to the function provided in stm32_registercallback. + * + * Events are automatically disabled once the callback is performed and no + * further callback events will occur until they are again enabled by + * calling this method. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * eventset - A bitset of events to enable or disable (see SDIOMEDIA_* + * definitions). 0=disable; 1=enable. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void stm32_callbackenable(struct sdio_dev_s *dev, + sdio_eventset_t eventset) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + mcinfo("eventset: %02" PRIx8 "\n", eventset); + DEBUGASSERT(priv != NULL); + + priv->cbevents = eventset; + stm32_callback(priv); +} + +/**************************************************************************** + * Name: stm32_registercallback + * + * Description: + * Register a callback that that will be invoked on any media status + * change. Callbacks should not be made from interrupt handlers, rather + * interrupt level events should be handled by calling back on the work + * thread. + * + * When this method is called, all callbacks should be disabled until they + * are enabled via a call to SDMMC_CALLBACKENABLE + * + * Input Parameters: + * dev - Device-specific state data + * callback - The function to call on the media change + * arg - A caller provided value to return with the callback + * + * Returned Value: + * 0 on success; negated errno on failure. + * + ****************************************************************************/ + +static int stm32_registercallback(struct sdio_dev_s *dev, + worker_t callback, void *arg) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + /* Disable callbacks and register this callback and is argument */ + + mcinfo("Register %p(%p)\n", callback, arg); + DEBUGASSERT(priv != NULL); + + priv->cbevents = 0; + priv->cbarg = arg; + priv->callback = callback; + return OK; +} + +/**************************************************************************** + * Name: stm32_dmapreflight + * + * Description: + * Preflight an SDIO DMA operation. If the buffer is not well-formed for + * SDIO DMA transfer (alignment, size, etc.) returns an error. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * buffer - The memory to DMA to/from + * buflen - The size of the DMA transfer in bytes + * + * Returned Value: + * OK on success; a negated errno on failure + ****************************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_IDMA) && defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) +static int stm32_dmapreflight(struct sdio_dev_s *dev, + const uint8_t *buffer, size_t buflen) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0); + + /* IDMA must be possible to the buffer */ + +#if defined(CONFIG_STM32_SDMMC1) + if (priv->base == STM32_SDMMC1_BASE) + { + /* STM32H5: SDMMC1 IDMA can access all SRAM (SRAM1/2/3). + * Just verify the buffer falls within the valid SRAM range. + */ + + if ((uintptr_t)buffer < STM32H5_SRAM_START || + (uintptr_t)buffer + buflen > STM32H5_SRAM_END) + { + mcerr("invalid IDMA address " + "buffer:0x%08" PRIxPTR " end:0x%08" PRIxPTR "\n", + (uintptr_t)buffer, (uintptr_t)(buffer + buflen - 1)); + return -EFAULT; + } + } +#endif + +#if defined(CONFIG_ARMV8M_DCACHE) && !defined(CONFIG_ARMV8M_DCACHE_WRITETHROUGH) + /* buffer alignment is required for DMA transfers with dcache in buffered + * mode (not write-through) because a) arch_invalidate_dcache could lose + * buffered writes and b) arch_flush_dcache could corrupt adjacent memory + * if the maddr and the mend+1, the next next address are not on + * ARMV8M_DCACHE_LINESIZE boundaries. + */ + + if (buffer != priv->rxfifo && + (((uintptr_t)buffer & (ARMV8M_DCACHE_LINESIZE - 1)) != 0 || + ((uintptr_t)(buffer + buflen) & (ARMV8M_DCACHE_LINESIZE - 1)) != 0)) + { + mcerr("dcache unaligned " + "buffer:%p end:%p\n", + buffer, buffer + buflen - 1); + return -EFAULT; + } +#endif + + return 0; +} +#endif + +/**************************************************************************** + * Name: stm32_dmarecvsetup + * + * Description: + * Setup to perform a read DMA. If the processor supports a data cache, + * then this method will also make sure that the contents of the DMA memory + * and the data cache are coherent. For read transfers this may mean + * invalidating the data cache. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * buffer - The memory to DMA from + * buflen - The size of the DMA transfer in bytes + * + * Returned Value: + * OK on success; a negated errno on failure + * + ****************************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_IDMA) +static int stm32_dmarecvsetup(struct sdio_dev_s *dev, + uint8_t *buffer, size_t buflen) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + /* If the previous transfer left the DPSM active, force hardware and + * software state cleanup before starting a new transfer. + */ + + uint32_t sta = sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET); + + if (sta & STM32_SDMMC_STA_DPSMACT) + { + mcwarn("WARNING: DPSM still active (STA=0x%08" PRIx32 + "), forcing cleanup\n", sta); + + /* Step 1: Disable IDMA (must precede DCTRL clear) */ + + sdmmc_putreg32(priv, 0, STM32_SDMMC_IDMACTRLR_OFFSET); + + /* Step 2: Stop DPSM by clearing DCTRL */ + + sdmmc_putreg32(priv, 0, STM32_SDMMC_DCTRL_OFFSET); + + /* Step 3: Clear DLEN */ + + sdmmc_putreg32(priv, 0, STM32_SDMMC_DLEN_OFFSET); + + /* Step 4: Clear all interrupt flags */ + + sdmmc_putreg32(priv, STM32_SDMMC_ICR_ALLFLAGS, + STM32_SDMMC_ICR_OFFSET); + + /* Step 5: Wait for DPSMACT to deassert */ + + int timeout = 1000; + + while ((sdmmc_getreg32(priv, STM32_SDMMC_STA_OFFSET) & + STM32_SDMMC_STA_DPSMACT) && --timeout > 0) + { + up_udelay(1); + } + + if (timeout == 0) + { + mcerr("ERROR: DPSMACT won't clear, FIFO reset\n"); + sdmmc_putreg32(priv, STM32_SDMMC_DCTRL_FIFORST, + STM32_SDMMC_DCTRL_OFFSET); + up_udelay(10); + sdmmc_putreg32(priv, 0, STM32_SDMMC_DCTRL_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_ICR_ALLFLAGS, + STM32_SDMMC_ICR_OFFSET); + } + + /* Step 6: Reset driver internal state */ + + stm32_configxfrints(priv, 0); + priv->remaining = 0; + priv->receivecnt = 0; + priv->waitevents = 0; + priv->wkupevent = 0; + priv->buffer = NULL; + + /* Step 7: Restore DTIMER */ + + sdmmc_putreg32(priv, UINT32_MAX, STM32_SDMMC_DTIMER_OFFSET); + } + + DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0); +#if defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) + DEBUGASSERT(stm32_dmapreflight(dev, buffer, buflen) == 0); +#endif + +#if defined(CONFIG_ARMV8M_DCACHE) + if (((uintptr_t)buffer & (ARMV8M_DCACHE_LINESIZE - 1)) != 0 || + (buflen & (ARMV8M_DCACHE_LINESIZE - 1)) != 0) + { + /* The read buffer is not cache-line aligned. Read to an internal + * buffer instead. + */ + + up_invalidate_dcache((uintptr_t)priv->sdmmc_rxbuffer, + (uintptr_t)priv->sdmmc_rxbuffer + + priv->blocksize); + + priv->unaligned_rx = true; + } + else + { + up_invalidate_dcache((uintptr_t)buffer, + (uintptr_t)buffer + buflen); + + priv->unaligned_rx = false; + } +#else + + /* IDMA access must be 32 bit aligned */ + + priv->unaligned_rx = ((uintptr_t)buffer & 0x3) != 0; + +#endif + + /* Reset the DPSM configuration */ + + stm32_datadisable(priv); + + /* Initialize register sampling */ + + stm32_sampleinit(); + stm32_sample(priv, SAMPLENDX_BEFORE_SETUP); + + /* Save the destination buffer information for use by the interrupt + * handler. + */ + + priv->buffer = (uint32_t *)buffer; + priv->remaining = buflen; + priv->receivecnt = buflen; + + /* Then set up the SDIO data path */ + + stm32_dataconfig(priv, SDMMC_DTIMER_DATATIMEOUT_MS, buflen, true); + + /* Configure the RX DMA */ + + if (priv->unaligned_rx) + { + sdmmc_putreg32(priv, (uintptr_t)priv->sdmmc_rxbuffer, + STM32_SDMMC_IDMABASE0R_OFFSET); + } + else + { + sdmmc_putreg32(priv, (uintptr_t)priv->buffer, + STM32_SDMMC_IDMABASE0R_OFFSET); + } + + sdmmc_putreg32(priv, STM32_SDMMC_IDMACTRLR_IDMAEN, + STM32_SDMMC_IDMACTRLR_OFFSET); + + /* And enable interrupts */ + + stm32_configxfrints(priv, STM32_SDMMC_DMARECV_MASK); + + stm32_sample(priv, SAMPLENDX_AFTER_SETUP); + return OK; +} +#endif + +/**************************************************************************** + * Name: stm32_dmasendsetup + * + * Description: + * Setup to perform a write DMA. If the processor supports a data cache, + * then this method will also make sure that the contents of the DMA memory + * and the data cache are coherent. For write transfers, this may mean + * flushing the data cache. + * + * Input Parameters: + * dev - An instance of the SDIO device interface + * buffer - The memory to DMA into + * buflen - The size of the DMA transfer in bytes + * + * Returned Value: + * OK on success; a negated errno on failure + * + ****************************************************************************/ + +#if defined(CONFIG_STM32_SDMMC_IDMA) +static int stm32_dmasendsetup(struct sdio_dev_s *dev, + const uint8_t *buffer, size_t buflen) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + DEBUGASSERT(priv != NULL && buffer != NULL && buflen > 0); +#if defined(CONFIG_ARCH_HAVE_SDIO_PREFLIGHT) + DEBUGASSERT(stm32_dmapreflight(dev, buffer, buflen) == 0); +#endif + + priv->unaligned_rx = false; + + /* Reset the DPSM configuration */ + + stm32_datadisable(priv); + + /* Initialize register sampling */ + + stm32_sampleinit(); + stm32_sample(priv, SAMPLENDX_BEFORE_SETUP); + + /* Flush cache to physical memory when not in DTCM memory */ + +#if defined(CONFIG_ARMV8M_DCACHE) && \ + !defined(CONFIG_ARMV8M_DCACHE_WRITETHROUGH) + up_clean_dcache((uintptr_t)buffer, (uintptr_t)buffer + buflen); +#endif + + /* Save the source buffer information for use by the interrupt handler */ + + priv->buffer = (uint32_t *)buffer; + priv->remaining = buflen; + priv->receivecnt = 0; + + /* Then set up the SDIO data path */ + + stm32_dataconfig(priv, SDMMC_DTIMER_DATATIMEOUT_MS, buflen, false); + + /* Configure the TX DMA */ + + sdmmc_putreg32(priv, (uintptr_t)priv->buffer, + STM32_SDMMC_IDMABASE0R_OFFSET); + + sdmmc_putreg32(priv, STM32_SDMMC_IDMACTRLR_IDMAEN, + STM32_SDMMC_IDMACTRLR_OFFSET); + + /* Enable TX interrupts */ + + stm32_configxfrints(priv, STM32_SDMMC_DMASEND_MASK); + stm32_sample(priv, SAMPLENDX_AFTER_SETUP); + return OK; +} +#endif + +/**************************************************************************** + * Name: stm32_callback + * + * Description: + * Perform callback. + * + * Assumptions: + * This function does not execute in the context of an interrupt handler. + * It may be invoked on any user thread or scheduled on the work thread + * from an interrupt handler. + * + ****************************************************************************/ + +static void stm32_callback(void *arg) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)arg; + + /* Is a callback registered? */ + + DEBUGASSERT(priv != NULL); + mcinfo("Callback %p(%p) cbevents: %02" PRIx8 " cdstatus: %02" PRIx8 "\n", + priv->callback, priv->cbarg, priv->cbevents, priv->cdstatus); + + if (priv->callback) + { + /* Yes.. Check for enabled callback events */ + + if ((priv->cdstatus & SDIO_STATUS_PRESENT) != 0) + { + /* Media is present. Is the media inserted event enabled? */ + + if ((priv->cbevents & SDIOMEDIA_INSERTED) == 0) + { + /* No... return without performing the callback */ + + return; + } + } + else + { + /* Media is not present. Is the media eject event enabled? */ + + if ((priv->cbevents & SDIOMEDIA_EJECTED) == 0) + { + /* No... return without performing the callback */ + + return; + } + } + + /* Perform the callback, disabling further callbacks. Of course, the + * the callback can (and probably should) re-enable callbacks. + */ + + priv->cbevents = 0; + + /* Callbacks cannot be performed in the context of an interrupt + * handler. If we are in an interrupt handler, then queue the + * callback to be performed later on the work thread. + */ + + if (up_interrupt_context()) + { + /* Yes.. queue it */ + + mcinfo("Queuing callback to %p(%p)\n", + priv->callback, priv->cbarg); + + work_queue(HPWORK, &priv->cbwork, priv->callback, + priv->cbarg, 0); + } + else + { + /* No.. then just call the callback here */ + + mcinfo("Callback to %p(%p)\n", priv->callback, priv->cbarg); + priv->callback(priv->cbarg); + } + } +} + +/**************************************************************************** + * Name: stm32_default + * + * Description: + * Restore SDIO registers to their default, reset values + * + ****************************************************************************/ + +static void stm32_default(struct stm32_dev_s *priv) +{ + sdmmc_putreg32(priv, STM32_SDMMC_POWER_RESET, STM32_SDMMC_POWER_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_CLKCR_RESET, STM32_SDMMC_CLKCR_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_ARG_RESET, STM32_SDMMC_ARG_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_CMD_RESET, STM32_SDMMC_CMD_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_DTIMER_RESET, STM32_SDMMC_DTIMER_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_DLEN_RESET, STM32_SDMMC_DLEN_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_DCTRL_RESET, STM32_SDMMC_DCTRL_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_ICR_RESET, STM32_SDMMC_ICR_OFFSET); + sdmmc_putreg32(priv, STM32_SDMMC_MASK_RESET, STM32_SDMMC_MASK_OFFSET); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: sdio_initialize + * + * Description: + * Initialize SDIO for operation. + * + * Input Parameters: + * slotno - Not used. + * + * Returned Values: + * A reference to an SDIO interface structure. NULL is returned on + * failures. + * + ****************************************************************************/ + +struct sdio_dev_s *sdio_initialize(int slotno) +{ + struct stm32_dev_s *priv = NULL; + +#if defined(CONFIG_STM32_SDMMC1) + if (slotno == 0) + { + /* Select SDMMC 1 */ + + priv = &g_sdmmcdev1; + + /* REVISIT: hack for now to avoid sorting out the DMA's reach */ + + priv->sdmmc_rxbuffer = (uint8_t *)kmm_memalign(ARMV8M_DCACHE_LINESIZE, + SDMMC_MAX_BLOCK_SIZE); + +#if defined(CONFIG_SDMMC1_WIDTH_D1_ONLY) + priv->onebit = true; +#else + priv->onebit = false; +#endif + + /* Configure GPIOs for 4-bit, wide-bus operation (the chip is capable + * of 8-bit wide bus operation but D4-D7 are not configured). + * + * If bus is multiplexed then there is a custom bus configuration + * utility in the scope of the board support package. + */ + +#ifndef CONFIG_SDIO_MUXBUS + stm32_configgpio(SDMMC1_SDIO_PULL(GPIO_SDMMC1_D0)); +# ifndef CONFIG_SDMMC1_WIDTH_D1_ONLY + stm32_configgpio(SDMMC1_SDIO_PULL(GPIO_SDMMC1_D1)); + stm32_configgpio(SDMMC1_SDIO_PULL(GPIO_SDMMC1_D2)); + stm32_configgpio(SDMMC1_SDIO_PULL(GPIO_SDMMC1_D3)); +# endif + stm32_configgpio(GPIO_SDMMC1_CK); + stm32_configgpio(SDMMC1_SDIO_PULL(GPIO_SDMMC1_CMD)); +#endif + } + else +#endif +#if defined(CONFIG_STM32_SDMMC2) + if (slotno == SDMMC2_SLOT) + { + /* Select SDMMC 2 */ + + priv = &g_sdmmcdev2; + + /* REVISIT: hack for now to avoid sorting out the DMA's reach */ + + priv->sdmmc_rxbuffer = (uint8_t *)kmm_memalign(ARMV8M_DCACHE_LINESIZE, + SDMMC_MAX_BLOCK_SIZE); + +# if defined(CONFIG_SDMMC2_WIDTH_D1_ONLY) + priv->onebit = true; +# else + priv->onebit = false; +# endif + + /* Configure GPIOs for 4-bit, wide-bus operation (the chip is capable + * of 8-bit wide bus operation but D4-D7 are not configured). + * + * If bus is multiplexed then there is a custom bus configuration + * utility in the scope of the board support package. + */ + +# ifndef CONFIG_SDIO_MUXBUS + stm32_configgpio(SDMMC2_SDIO_PULL(GPIO_SDMMC2_D0)); +# ifndef CONFIG_SDMMC2_WIDTH_D1_ONLY + stm32_configgpio(SDMMC2_SDIO_PULL(GPIO_SDMMC2_D1)); + stm32_configgpio(SDMMC2_SDIO_PULL(GPIO_SDMMC2_D2)); + stm32_configgpio(SDMMC2_SDIO_PULL(GPIO_SDMMC2_D3)); +# endif + stm32_configgpio(GPIO_SDMMC2_CK); + stm32_configgpio(SDMMC2_SDIO_PULL(GPIO_SDMMC2_CMD)); +# endif + } + else +#endif + { + mcerr("ERROR: Unsupported SDMMC slot: %d\n", slotno); + return NULL; + } + + /* Reset the card and assure that it is in the initial, unconfigured + * state. + */ + + stm32_reset(&priv->dev); + return &priv->dev; +} + +/**************************************************************************** + * Name: sdio_mediachange + * + * Description: + * Called by board-specific logic -- possible from an interrupt handler -- + * in order to signal to the driver that a card has been inserted or + * removed from the slot + * + * Input Parameters: + * dev - An instance of the SDIO driver device state structure. + * cardinslot - true is a card has been detected in the slot; false if a + * card has been removed from the slot. Only transitions + * (inserted->removed or removed->inserted should be reported) + * + * Returned Value: + * None + * + ****************************************************************************/ + +void sdio_mediachange(struct sdio_dev_s *dev, bool cardinslot) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + sdio_statset_t cdstatus; + irqstate_t flags; + + /* Update card status */ + + flags = enter_critical_section(); + cdstatus = priv->cdstatus; + if (cardinslot) + { + priv->cdstatus |= SDIO_STATUS_PRESENT; + } + else + { + priv->cdstatus &= ~SDIO_STATUS_PRESENT; + } + + leave_critical_section(flags); + + mcinfo("cdstatus OLD: %02" PRIx8 " NEW: %02" PRIx8 "\n", + cdstatus, priv->cdstatus); + + /* Perform any requested callback if the status has changed */ + + if (cdstatus != priv->cdstatus) + { + stm32_callback(priv); + } +} + +/**************************************************************************** + * Name: sdio_wrprotect + * + * Description: + * Called by board-specific logic to report if the card in the slot is + * mechanically write protected. + * + * Input Parameters: + * dev - An instance of the SDIO driver device state structure. + * wrprotect - true is a card is writeprotected. + * + * Returned Value: + * None + * + ****************************************************************************/ + +void sdio_wrprotect(struct sdio_dev_s *dev, bool wrprotect) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + irqstate_t flags; + + /* Update card status */ + + flags = enter_critical_section(); + if (wrprotect) + { + priv->cdstatus |= SDIO_STATUS_WRPROTECTED; + } + else + { + priv->cdstatus &= ~SDIO_STATUS_WRPROTECTED; + } + + mcinfo("cdstatus: %02" PRIx8 "\n", priv->cdstatus); + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: sdio_set_sdio_card_isr + * + * Description: + * SDIO card generates interrupt via SDIO_DATA_1 pin. + * Called by board-specific logic to register an ISR for SDIO card. + * + * Input Parameters: + * func - callback function. + * arg - arg to be passed to the function. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef HAVE_SDMMC_SDIO_MODE +void sdio_set_sdio_card_isr(struct sdio_dev_s *dev, + int (*func)(void *), void *arg) +{ + struct stm32_dev_s *priv = (struct stm32_dev_s *)dev; + + priv->do_sdio_card = func; + + if (func != NULL) + { + priv->sdiointmask = STM32_SDMMC_MASK_SDIOITIE; + priv->do_sdio_arg = arg; + } + else + { + priv->sdiointmask = 0; + } + + sdmmc_putreg32(priv, priv->xfrmask | priv->waitmask | priv->sdiointmask, + STM32_SDMMC_MASK_OFFSET); +} +#endif + +#endif /* CONFIG_STM32_SDMMC1 || CONFIG_STM32_SDMMC2 */ diff --git a/arch/arm/src/stm32h5/stm32_sdmmc.h b/arch/arm/src/stm32h5/stm32_sdmmc.h new file mode 100644 index 00000000000..39aedbbd962 --- /dev/null +++ b/arch/arm/src/stm32h5/stm32_sdmmc.h @@ -0,0 +1,134 @@ +/**************************************************************************** + * arch/arm/src/stm32h5/stm32_sdmmc.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_STM32H5_STM32_SDMMC_H +#define __ARCH_ARM_SRC_STM32H5_STM32_SDMMC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include "chip.h" +#include "hardware/stm32_sdmmc.h" + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: sdio_initialize + * + * Description: + * Initialize SDIO for operation. + * + * Input Parameters: + * slotno - Not used. + * + * Returned Values: + * A reference to an SDIO interface structure. NULL is returned on + * failures. + * + ****************************************************************************/ + +struct sdio_dev_s; /* See include/nuttx/sdio.h */ +struct sdio_dev_s *sdio_initialize(int slotno); + +/**************************************************************************** + * Name: sdio_mediachange + * + * Description: + * Called by board-specific logic -- possibly from an interrupt handler -- + * in order to signal to the driver that a card has been inserted or + * removed from the slot. + * + * Input Parameters: + * dev - An instance of the SDIO driver device state structure. + * cardinslot - true is a card has been detected in the slot; false if a + * card has been removed from the slot. Only transitions + * (inserted->removed or removed->inserted should be reported) + * + * Returned Values: + * None + * + ****************************************************************************/ + +void sdio_mediachange(struct sdio_dev_s *dev, bool cardinslot); + +/**************************************************************************** + * Name: sdio_wrprotect + * + * Description: + * Called by board-specific logic to report if the card in the slot is + * mechanically write protected. + * + * Input Parameters: + * dev - An instance of the SDIO driver device state structure. + * wrprotect - true is a card is writeprotected. + * + * Returned Values: + * None + * + ****************************************************************************/ + +void sdio_wrprotect(struct sdio_dev_s *dev, bool wrprotect); + +/**************************************************************************** + * Name: sdio_set_sdio_card_isr + * + * Description: + * SDIO card generates interrupt via SDIO_DATA_1 pin. + * Called by board-specific logic to register an ISR for SDIO card. + * + * Input Parameters: + * func - callback function. + * arg - arg to be passed to the function. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#if defined(CONFIG_SDMMC1_SDIO_MODE) || defined(CONFIG_SDMMC2_SDIO_MODE) +void sdio_set_sdio_card_isr(struct sdio_dev_s *dev, + int (*func)(void *), void *arg); +#endif + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __ARCH_ARM_SRC_STM32H5_STM32_SDMMC_H */