[PATCH rtems-libbsd 3/3] STM32H7: Add SDMMC driver

Christian Mauderer christian.mauderer at embedded-brains.de
Thu Mar 25 08:35:41 UTC 2021


---
 libbsd.py                                     |   2 +
 rtemsbsd/include/bsp/nexus-devices.h          |   2 +
 rtemsbsd/include/bsp/st-sdmmc-config.h        |  71 ++
 .../include/machine/rtems-bsd-nexus-bus.h     |  22 +
 rtemsbsd/sys/dev/mmc/st-sdmmc-config.c        |  46 +
 rtemsbsd/sys/dev/mmc/st-sdmmc.c               | 847 ++++++++++++++++++
 6 files changed, 990 insertions(+)
 create mode 100644 rtemsbsd/include/bsp/st-sdmmc-config.h
 create mode 100644 rtemsbsd/sys/dev/mmc/st-sdmmc-config.c
 create mode 100644 rtemsbsd/sys/dev/mmc/st-sdmmc.c

diff --git a/libbsd.py b/libbsd.py
index f8a35e6c6..befa1a1a5 100644
--- a/libbsd.py
+++ b/libbsd.py
@@ -228,6 +228,8 @@ class rtems(builder.Module):
                 'sys/dev/ffec/if_ffec_mcf548x.c',
                 'sys/dev/ffec/if_ffec_mpc8xx.c',
                 'sys/dev/input/touchscreen/tsc_lpc32xx.c',
+                'sys/dev/mmc/st-sdmmc.c',
+                'sys/dev/mmc/st-sdmmc-config.c',
                 'sys/dev/smc/if_smc_nexus.c',
                 'sys/dev/stmac/if_stmac.c',
                 'sys/dev/tsec/if_tsec_nexus.c',
diff --git a/rtemsbsd/include/bsp/nexus-devices.h b/rtemsbsd/include/bsp/nexus-devices.h
index 53a22798b..efe4fcb4f 100644
--- a/rtemsbsd/include/bsp/nexus-devices.h
+++ b/rtemsbsd/include/bsp/nexus-devices.h
@@ -193,6 +193,8 @@ static const rtems_bsd_device_resource dwcotg_res[] = {
 };
 RTEMS_BSD_DEFINE_NEXUS_DEVICE(dwcotg, 0, RTEMS_ARRAY_SIZE(dwcotg_res),
     dwcotg_res);
+RTEMS_BSD_DRIVER_ST_SDMMC(0, SDMMC1_BASE, DLYB_SDMMC1_BASE, SDMMC1_IRQn);
+RTEMS_BSD_DRIVER_MMC;
 RTEMS_BSD_DRIVER_USB;
 RTEMS_BSD_DRIVER_USB_MASS;
 
diff --git a/rtemsbsd/include/bsp/st-sdmmc-config.h b/rtemsbsd/include/bsp/st-sdmmc-config.h
new file mode 100644
index 000000000..b44159918
--- /dev/null
+++ b/rtemsbsd/include/bsp/st-sdmmc-config.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/*
+ * Copyright (C) 2021 embedded brains GmbH (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BSP_ST_SDMMC_CONFIG_H
+#define BSP_ST_SDMMC_CONFIG_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stm32h7/hal.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+struct st_sdmmc_config {
+	/**
+	 * Number of data lines. Can be 1, 4 or 8
+	 */
+	uint8_t data_lines;
+	/**
+	 * Polarity of the DIR pins. See "SDMMC_POWER" register in the
+	 * STM32H7xx data sheet for that. If you don't have the lines, you can
+	 * use any value.
+	 */
+	bool dirpol;
+	/**
+	 * Possible OCR voltages. Should be something like
+	 * MMC_OCR_290_300 | MMC_OCR_300_310 depending on card supply.
+	 */
+	uint32_t ocr_voltage;
+};
+
+/**
+ * Get hardware specific configuration for SDMMC.
+ *
+ * This function can be overwritten in the application to adapt to another board
+ * configuration. The sdmmc_base points to the base address of the SDMMC
+ * instance. The cfg structure is set to zero before calling this function so
+ * that only the necessary fields have to be initialized.
+ */
+void st_sdmmc_get_config(uintptr_t sdmmc_base, struct st_sdmmc_config *cfg);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* BSP_ST_SDMMC_CONFIG_H */
diff --git a/rtemsbsd/include/machine/rtems-bsd-nexus-bus.h b/rtemsbsd/include/machine/rtems-bsd-nexus-bus.h
index f1ca66d70..5902c58c6 100644
--- a/rtemsbsd/include/machine/rtems-bsd-nexus-bus.h
+++ b/rtemsbsd/include/machine/rtems-bsd-nexus-bus.h
@@ -243,6 +243,28 @@ extern "C" {
     SYSINIT_DRIVER_REFERENCE(mmcsd, mmc)
 #endif /* RTEMS_BSD_DRIVER_MMC */
 
+#if !defined(RTEMS_BSD_DRIVER_ST_SDMMC)
+  #define RTEMS_BSD_DRIVER_ST_SDMMC(_num, _base, _dlyb, _irq)                 \
+    static const rtems_bsd_device_resource st_sdmmc ## _num ## _res[] = {     \
+      {                                                                       \
+        .type = RTEMS_BSD_RES_MEMORY,                                         \
+        .start_request = 0,                                                   \
+        .start_actual = (_base)                                               \
+      }, {                                                                    \
+        .type = RTEMS_BSD_RES_MEMORY,                                         \
+        .start_request = 1,                                                   \
+        .start_actual = (_dlyb)                                               \
+      }, {                                                                    \
+        .type = RTEMS_BSD_RES_IRQ,                                            \
+        .start_request = 0,                                                   \
+        .start_actual = (_irq)                                                \
+      }                                                                       \
+    };                                                                        \
+    RTEMS_BSD_DEFINE_NEXUS_DEVICE(st_sdmmc, 0,                                \
+                                  RTEMS_ARRAY_SIZE(st_sdmmc ## _num ## _res), \
+                                  &st_sdmmc ## _num ## _res[0])
+#endif /* RTEMS_BSD_DRIVER_ST_SDMMC */
+
 /*
  * USB Drivers.
  */
diff --git a/rtemsbsd/sys/dev/mmc/st-sdmmc-config.c b/rtemsbsd/sys/dev/mmc/st-sdmmc-config.c
new file mode 100644
index 000000000..723a169b9
--- /dev/null
+++ b/rtemsbsd/sys/dev/mmc/st-sdmmc-config.c
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/*
+ * Copyright (C) 2021 embedded brains GmbH (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <bsp/st-sdmmc-config.h>
+#include <dev/mmc/mmcreg.h>
+
+void
+st_sdmmc_get_config(uintptr_t sdmmc_base, struct st_sdmmc_config *cfg)
+{
+	switch (sdmmc_base) {
+	case SDMMC1_BASE:
+		cfg->data_lines = 4;
+		cfg->dirpol = true;
+		/*
+		 * FIXME: Also the evaluation board could switch to 1.8V, the
+		 * control for the level converter isn't implemented in the
+		 * driver yet. So only signal 2.9V.
+		 */
+		cfg->ocr_voltage = MMC_OCR_280_290 | MMC_OCR_290_300;
+		break;
+	}
+}
diff --git a/rtemsbsd/sys/dev/mmc/st-sdmmc.c b/rtemsbsd/sys/dev/mmc/st-sdmmc.c
new file mode 100644
index 000000000..bf8831a47
--- /dev/null
+++ b/rtemsbsd/sys/dev/mmc/st-sdmmc.c
@@ -0,0 +1,847 @@
+#include <machine/rtems-bsd-kernel-space.h>
+
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/*
+ * Copyright (C) 2021 embedded brains GmbH (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * STM32H7xx SDMMC controller. Documentation: ST RM0433 (Rev 6), Chapter 54
+ *
+ * According to Linux DTS, the SDMMC is compatible with an ARM Primecell PL18X
+ * with peripheral ID 0x10153180.
+ */
+/*
+ * Note: This driver is inspired by the NetBSD pl181 driver written by Jared D.
+ * McNeill.
+ */
+/*
+ * Note regarding DMA on STM32H7:
+ *
+ * The STM32H7 SDMMC doesn't have an interrupt for few received data (less than
+ * half the FIFO size). So especially for short responses, it is not possible to
+ * use the SDMMC without DMA.
+ *
+ * On the STM32H7 SDMMC there are two DMAs: One IDMA integrated into the SDMMC
+ * and one MDMA that is a general purpose DMA. MDMA can only be used on first
+ * instance of the SDMMC. For the second instance, the trigger signals are not
+ * connected.
+ *
+ * The IDMA of SDMMC1 can only access AXI SRAM, QSPI and FMC (where SDRAM is
+ * located). The IDMA of SDMMC2 could access memory in other domains too.
+ *
+ * MDMA is designed to be a companion for IDMA. It seems that ST thought of a
+ * very specific software structure for that. It can be either used to change
+ * the IDMA buffer addresses (which would allow some kind of scatter gather
+ * functionality with fixed buffer sizes) or to refill IDMA buffers from some
+ * RAM that can't be accessed directly by the IDMA. Take a look at ST AN5200 Rev
+ * 1 "Getting started with STM32H7 Series SDMMC host controller" for more
+ * details.
+ */
+
+#include <rtems/malloc.h>
+#include <rtems/irq-extension.h>
+
+#include <stm32h7/hal.h>
+#include <stm32h7/memory.h>
+
+#include <bsp/st-sdmmc-config.h>
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/resource.h>
+#include <sys/rman.h>
+#include <sys/condvar.h>
+
+#include <pthread.h>
+
+#include <machine/resource.h>
+
+#include <dev/mmc/bridge.h>
+#include <dev/mmc/mmcbrvar.h>
+
+#define	ST_SDMMC_LOCK(_sc)		mtx_lock(&(_sc)->mtx)
+#define	ST_SDMMC_UNLOCK(_sc)		mtx_unlock(&(_sc)->mtx)
+#define	ST_SDMMC_LOCK_INIT(_sc) \
+	mtx_init(&_sc->mtx, device_get_nameunit(_sc->dev), \
+	    "st_sdmmc", MTX_DEF)
+
+#define	SDMMC_INT_ERROR_MASK ( \
+		SDMMC_MASK_CTIMEOUTIE | \
+		SDMMC_MASK_CCRCFAILIE | \
+		SDMMC_MASK_DTIMEOUTIE | \
+		SDMMC_MASK_DCRCFAILIE | \
+		SDMMC_MASK_ACKFAILIE | \
+		SDMMC_MASK_RXOVERRIE | \
+		SDMMC_MASK_TXUNDERRIE | \
+		SDMMC_MASK_DABORTIE | \
+		SDMMC_MASK_ACKTIMEOUTIE )
+#define	SDMMC_INT_DATA_DONE_MASK ( SDMMC_MASK_IDMABTCIE | SDMMC_MASK_DATAENDIE )
+#define	SDMMC_INT_CMD_DONE_MASK ( SDMMC_MASK_CMDSENTIE )
+#define	SDMMC_INT_CMD_RESPONSE_DONE_MASK ( SDMMC_MASK_CMDRENDIE )
+
+#define RES_MEM_SDMMC 0
+#define RES_MEM_DLYB 1
+#define RES_IRQ_SDMMC 2
+#define RES_NR 3
+
+#define DMA_BUF_SIZE CPU_CACHE_LINE_BYTES
+
+#if 0
+#define debug_print(sc, lvl, ...) \
+    if (lvl <= 1) device_printf(sc->dev, __VA_ARGS__)
+#else
+#define debug_print(...)
+#endif
+
+struct st_sdmmc_softc;
+
+typedef void (*st_sdmmc_dma_setup_transfer)(struct st_sdmmc_softc *, void *);
+
+struct st_sdmmc_softc {
+	device_t dev;
+	struct mmc_host host;
+
+	struct mtx mtx;
+	rtems_binary_semaphore wait_done;
+	int bus_busy;
+
+	struct resource *res[RES_NR];
+	SDMMC_TypeDef *sdmmc;
+	DLYB_TypeDef *dlyb;
+	rtems_vector_number irq;
+
+	uint32_t sdmmc_ker_ck;
+	struct st_sdmmc_config cfg;
+
+	uint32_t intr_status;
+	uint8_t *dmabuf;
+};
+
+void st_sdmmc_idma_txrx(struct st_sdmmc_softc *sc, void *buf)
+{
+	BSD_ASSERT(
+	    (buf >= (void*) stm32h7_memory_sdram_1_begin &&
+	     buf  < (void*) stm32h7_memory_sdram_1_end) ||
+	    (buf >= (void*) stm32h7_memory_sram_axi_begin &&
+	     buf  < (void*) stm32h7_memory_sram_axi_end) ||
+	    (buf >= (void*) stm32h7_memory_sdram_2_begin &&
+	     buf  < (void*) stm32h7_memory_sdram_2_end) ||
+	    (buf >= (void*) stm32h7_memory_quadspi_begin &&
+	     buf  < (void*) stm32h7_memory_quadspi_end));
+	sc->sdmmc->IDMABASE0 = (uintptr_t) buf;
+	sc->sdmmc->IDMACTRL = SDMMC_IDMA_IDMAEN;
+}
+
+void st_sdmmc_idma_stop(struct st_sdmmc_softc *sc)
+{
+	sc->sdmmc->IDMACTRL = 0;
+}
+
+static void
+st_sdmmc_intr(void *arg)
+{
+	struct st_sdmmc_softc *sc;
+	uint32_t status;
+
+	sc = arg;
+
+	status = sc->sdmmc->STA;
+	sc->sdmmc->ICR = status;
+	sc->intr_status |= status;
+
+	/*
+	 * There seems to be some odd combination where the status is zero but
+	 * an interrupt occurred. In that case, the task shouldn't wake up.
+	 * Therefore check for status != 0.
+	 */
+	if (status != 0 &&
+	    ((status & SDMMC_STA_BUSYD0) == 0 ||
+	    (sc->sdmmc->MASK & SDMMC_STA_BUSYD0END) == 0)) {
+		rtems_binary_semaphore_post(&sc->wait_done);
+	}
+}
+
+static int
+st_sdmmc_probe(device_t dev)
+{
+	device_set_desc(dev, "STM32H7xx SDMMC Host");
+	return (0);
+}
+
+static int
+st_sdmmc_set_clock_and_bus(
+	struct st_sdmmc_softc *sc,
+	uint32_t freq,
+	enum mmc_bus_width width
+)
+{
+	uint32_t clk_div;
+	uint32_t clkcr;
+
+	clkcr = SDMMC_CLKCR_NEGEDGE | SDMMC_CLKCR_PWRSAV | SDMMC_CLKCR_HWFC_EN;
+	clk_div = howmany(sc->sdmmc_ker_ck, freq) / 2;
+	if (clk_div > SDMMC_CLKCR_CLKDIV >> SDMMC_CLKCR_CLKDIV_Pos) {
+		clk_div = SDMMC_CLKCR_CLKDIV >> SDMMC_CLKCR_CLKDIV_Pos;
+	}
+	clkcr |= clk_div << SDMMC_CLKCR_CLKDIV_Pos;
+
+	switch (width) {
+	default:
+		BSD_ASSERT(width == bus_width_1);
+		clkcr |= 0 << SDMMC_CLKCR_WIDBUS_Pos;
+		break;
+	case bus_width_4:
+		clkcr |= 1 << SDMMC_CLKCR_WIDBUS_Pos;
+		break;
+	case bus_width_8:
+		clkcr |= 2 << SDMMC_CLKCR_WIDBUS_Pos;
+		break;
+	}
+
+	sc->sdmmc->CLKCR = clkcr;
+
+	return 0;
+}
+
+static void
+st_sdmmc_host_reset(struct st_sdmmc_softc *sc)
+{
+	sc->sdmmc->MASK = 0;
+	sc->sdmmc->ICR = 0xFFFFFFFF;
+}
+
+static void
+st_sdmmc_hw_init(struct st_sdmmc_softc *sc)
+{
+	st_sdmmc_set_clock_and_bus(sc, 400000, bus_width_1);
+	sc->sdmmc->POWER = 0;
+	if (sc->cfg.dirpol) {
+		sc->sdmmc->POWER |= SDMMC_POWER_DIRPOL;
+	}
+	/* ST example code just set it on. So do the same. */
+	sc->sdmmc->POWER |= SDMMC_POWER_PWRCTRL_0 | SDMMC_POWER_PWRCTRL_1;
+	/*
+	 * Wait at least 74 cycles; lowest freq is 400kHz
+	 * -> 1/400kHz * 47 = 117us
+	 */
+	usleep(120000);
+
+	st_sdmmc_host_reset(sc);
+}
+
+static void
+st_sdmmc_board_init(void)
+{
+	HAL_SD_MspInit(NULL);
+}
+
+static int
+st_sdmmc_attach(device_t dev)
+{
+	struct st_sdmmc_softc *sc;
+	int error = 0;
+	static pthread_once_t once = PTHREAD_ONCE_INIT;
+	bool interrupt_installed = false;
+
+	sc = device_get_softc(dev);
+
+	memset(sc, 0, sizeof(*sc));
+
+	if (error == 0) {
+		sc->dev = dev;
+	}
+
+	if (error == 0) {
+		ST_SDMMC_LOCK_INIT(sc);
+		rtems_binary_semaphore_init(&sc->wait_done, "sdmmc-sem");
+	}
+
+	if (error == 0) {
+		int rid = 0;
+		sc->res[RES_MEM_SDMMC] = bus_alloc_resource(dev, SYS_RES_MEMORY,
+		    &rid, 0, ~0, 1, RF_ACTIVE);
+		if (sc->res[RES_MEM_SDMMC] == NULL) {
+			device_printf(dev,
+			    "could not allocate sdmmc resource\n");
+			error = ENXIO;
+		} else {
+			sc->sdmmc = (SDMMC_TypeDef *)
+			    sc->res[RES_MEM_SDMMC]->r_bushandle;
+		}
+	}
+	if (error == 0) {
+		int rid = 0;
+		sc->res[RES_MEM_DLYB] = bus_alloc_resource(dev, SYS_RES_MEMORY,
+		    &rid, 1, ~0, 1, RF_ACTIVE);
+		if (sc->res[RES_MEM_DLYB] == NULL) {
+			device_printf(dev,
+			    "could not allocate dlyb resource\n");
+			error = ENXIO;
+		} else {
+			sc->dlyb = (DLYB_TypeDef *)
+			    sc->res[RES_MEM_DLYB]->r_bushandle;
+		}
+	}
+	if (error == 0) {
+		int rid = 0;
+		sc->res[RES_IRQ_SDMMC] = bus_alloc_resource(dev, SYS_RES_IRQ,
+		    &rid, 0, ~0, 1, RF_ACTIVE);
+		if (sc->res[RES_IRQ_SDMMC] == NULL) {
+			device_printf(dev,
+			    "could not allocate interrupt resource\n");
+			error = ENXIO;
+		} else {
+			sc->irq = sc->res[RES_IRQ_SDMMC]->r_bushandle;
+		}
+	}
+
+	if (error == 0) {
+		/*
+		 * FIXME: This memory should be in AXI SRAM, QSPI or FMC. In the
+		 * configurations for our BSP, the heap is either in AXI SRAM or
+		 * in the SDRAM. So that is OK for now. Only assert that the
+		 * assumption is true. A better solution (like fixed AXI SRAM)
+		 * might would be a good idea.
+		 */
+		sc->dmabuf = rtems_heap_allocate_aligned_with_boundary(
+		    DMA_BUF_SIZE, CPU_CACHE_LINE_BYTES, 0);
+		if (sc->dmabuf == NULL) {
+			device_printf(dev, "could not allocate dma buffer\n");
+			error = ENOMEM;
+		}
+		BSD_ASSERT(
+		    ((void*) sc->dmabuf >= (void*) stm32h7_memory_sram_axi_begin &&
+		     (void*) sc->dmabuf < (void*) stm32h7_memory_sram_axi_end) ||
+		    ((void*) sc->dmabuf >= (void*) stm32h7_memory_sdram_1_begin &&
+		     (void*) sc->dmabuf < (void*) stm32h7_memory_sdram_1_end) ||
+		    ((void*) sc->dmabuf >= (void*) stm32h7_memory_sdram_2_begin &&
+		     (void*) sc->dmabuf < (void*) stm32h7_memory_sdram_2_end) ||
+		    ((void*) sc->dmabuf >= (void*) stm32h7_memory_quadspi_begin &&
+		     (void*) sc->dmabuf < (void*) stm32h7_memory_quadspi_end));
+	}
+
+	if (error == 0) {
+		pthread_once(&once, st_sdmmc_board_init);
+	}
+
+	if (error == 0) {
+		sc->sdmmc_ker_ck = HAL_RCCEx_GetPeriphCLKFreq(
+		    RCC_PERIPHCLK_SDMMC);
+
+		sc->host.f_min = 400000;
+		sc->host.f_max = (int) sc->sdmmc_ker_ck;
+		if (sc->host.f_max > 50000000)
+			sc->host.f_max = 50000000;
+	}
+
+	if (error == 0) {
+		st_sdmmc_get_config((uintptr_t)sc->sdmmc, &sc->cfg);
+		if (sc->cfg.data_lines == 0) {
+			device_printf(dev, "config not found!\n");
+			error = EINVAL;
+		}
+	}
+
+	if (error == 0) {
+		st_sdmmc_hw_init(sc);
+	}
+
+	if (error == 0) {
+		error = rtems_interrupt_handler_install(sc->irq, "SDMMC",
+		    RTEMS_INTERRUPT_UNIQUE, st_sdmmc_intr, sc);
+		if (error != 0) {
+			device_printf(dev,
+			    "could not setup interrupt handler.\n");
+		} else {
+			interrupt_installed = true;
+		}
+	}
+
+	if (error == 0) {
+		sc->host.host_ocr = sc->cfg.ocr_voltage &
+		    ((1 << (MMC_OCR_MAX_VOLTAGE_SHIFT + 1)) - 1);
+
+		sc->host.caps = MMC_CAP_HSPEED;
+		if (sc->cfg.data_lines >= 4) {
+			sc->host.caps |= MMC_CAP_4_BIT_DATA;
+		}
+		if (sc->cfg.data_lines >= 8) {
+			sc->host.caps |= MMC_CAP_8_BIT_DATA;
+		}
+	}
+
+	if (error == 0) {
+		device_add_child(dev, "mmc", -1);
+		error = bus_generic_attach(dev);
+	}
+
+	if (error != 0) {
+		/* Undo relevant parts */
+		if (interrupt_installed) {
+			rtems_interrupt_handler_remove(sc->irq,
+			    st_sdmmc_intr, sc);
+		}
+
+		free(sc->dmabuf);
+
+		/*
+		 * FIXME: Should free resources but RTEMS doesn't implement
+		 * bus_free_resource().
+		 */
+	}
+
+	return error;
+}
+
+static int
+st_sdmmc_detach(device_t dev)
+{
+	struct st_sdmmc_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	/* Always attached. So this is not necessary. */
+	BSD_ASSERT(0);
+
+	(void)sc;
+
+	return (EBUSY);
+}
+
+static int
+st_sdmmc_update_ios(device_t brdev, device_t reqdev)
+{
+	struct st_sdmmc_softc *sc;
+	struct mmc_ios *ios;
+	int err;
+
+	sc = device_get_softc(brdev);
+
+	ST_SDMMC_LOCK(sc);
+
+	ios = &sc->host.ios;
+	err = st_sdmmc_set_clock_and_bus(sc, ios->clock, ios->bus_width);
+	if (err != 0) {
+		return (err);
+	}
+
+	if (ios->power_mode == power_off) {
+		/*
+		 * FIXME: Maybe a reset of the module is necessary instead. But
+		 * the ST samples use a power off too so it should work. But
+		 * power saving hasn't been tested during development.
+		 */
+		sc->sdmmc->POWER &= ~(SDMMC_POWER_PWRCTRL);
+	} else {
+		sc->sdmmc->POWER |= SDMMC_POWER_PWRCTRL;
+	}
+
+	ST_SDMMC_UNLOCK(sc);
+
+	return (EIO);
+}
+
+static int
+st_sdmmc_wait_irq(struct st_sdmmc_softc *sc)
+{
+	int error = 0;
+
+	error = rtems_binary_semaphore_wait_timed_ticks(&sc->wait_done,
+	    RTEMS_MILLISECONDS_TO_TICKS(5000));
+
+	if (error != 0) {
+		error = MMC_ERR_TIMEOUT;
+	} else if ((sc->intr_status &
+	    (SDMMC_STA_DTIMEOUT | SDMMC_STA_CTIMEOUT)) != 0) {
+		error = MMC_ERR_TIMEOUT;
+	} else if ((sc->intr_status & SDMMC_INT_ERROR_MASK) != 0) {
+		error = MMC_ERR_FAILED;
+	}
+
+	return error;
+}
+
+static void
+st_sdmmc_cmd_do(struct st_sdmmc_softc *sc, struct mmc_command *cmd)
+{
+	uint32_t cmdval;
+	uint32_t xferlen;
+	uint32_t int_mask;
+	uint32_t arg;
+	void *data = NULL;
+	bool short_xfer = false;
+
+	debug_print(sc, 1, "cmd: %d, arg: %08x, flags: 0x%x\n",
+	    cmd->opcode, cmd->arg, cmd->flags);
+
+	xferlen = 0;
+	sc->intr_status = 0;
+
+	sc->sdmmc->CMD = 0;
+	/*
+	 * There should be a delay of "at least seven sdmmc_hclk clock periods"
+	 * before CMD is written again. The sdmmc_hclk is the clock that is used
+	 * to access the Registers. Some more registers are accessed before the
+	 * next CMD write. So that should be no problem.
+	 */
+	sc->sdmmc->MASK = 0;
+	sc->sdmmc->ICR = 0xFFFFFFFF;
+	/*
+	 * Make sure the semaphore is cleared at this point. There can be an
+	 * error case where a previous command run into a timeout and still
+	 * produced an interrupt before it has been disabled.
+	 */
+	if (rtems_binary_semaphore_try_wait(&sc->wait_done) == 0) {
+		device_printf(sc->dev, "Semaphore set from last command\n");
+	}
+
+	int_mask = SDMMC_INT_ERROR_MASK;
+
+	arg = cmd->arg;
+	cmdval = (cmd->opcode & SDMMC_CMD_CMDINDEX_Msk) | SDMMC_CMD_CPSMEN;
+
+	if ((cmd->flags & MMC_RSP_PRESENT) != 0) {
+		if ((cmd->flags & MMC_RSP_136) != 0) {
+			cmdval |= SDMMC_CMD_WAITRESP_0 | SDMMC_CMD_WAITRESP_1;
+		} else if ((cmd->flags & MMC_RSP_CRC) != 0) {
+			cmdval |= SDMMC_CMD_WAITRESP_0;
+		} else {
+			cmdval |= SDMMC_CMD_WAITRESP_1;
+		}
+		int_mask |= SDMMC_INT_CMD_RESPONSE_DONE_MASK;
+	} else {
+		int_mask |= SDMMC_INT_CMD_DONE_MASK;
+	}
+
+	if (cmd->opcode == MMC_STOP_TRANSMISSION) {
+		cmdval |= SDMMC_CMD_CMDSTOP;
+	}
+
+	if ((cmd->flags & MMC_CMD_MASK) == MMC_CMD_ADTC) {
+		cmdval |= SDMMC_CMD_CMDTRANS;
+	}
+
+	if ((cmd->flags & MMC_RSP_BUSY) != 0) {
+		int_mask |= SDMMC_MASK_BUSYD0ENDIE;
+	}
+
+	if (cmd->data != NULL) {
+		uint32_t blksize;
+		uint32_t dctrl = 0;
+		xferlen = cmd->data->len;
+
+		if (xferlen > MMC_SECTOR_SIZE) {
+			blksize = ffs(MMC_SECTOR_SIZE) - 1;
+		} else {
+			blksize = ffs(xferlen) - 1;
+		}
+
+		debug_print(sc, 1,
+		    "data: len: %d, xferlen: %d, blksize: %d, dataflags: 0x%x\n",
+		    cmd->data->len, xferlen, blksize, cmd->data->flags);
+
+		BSD_ASSERT(xferlen % (1 << blksize) == 0);
+
+		if (xferlen < CPU_CACHE_LINE_BYTES) {
+			if ((cmd->data->flags & MMC_DATA_READ) == 0) {
+				memcpy(sc->dmabuf, cmd->data->data, xferlen);
+			}
+			data = sc->dmabuf;
+			short_xfer = true;
+		} else {
+			data = cmd->data->data;
+		}
+
+		dctrl |= blksize << SDMMC_DCTRL_DBLOCKSIZE_Pos;
+		if ((cmd->data->flags & MMC_DATA_READ) != 0) {
+			dctrl |= SDMMC_DCTRL_DTDIR;
+			rtems_cache_invalidate_multiple_data_lines(data,
+			    roundup2(xferlen, CPU_CACHE_LINE_BYTES));
+		} else {
+			rtems_cache_flush_multiple_data_lines(data,
+			    roundup2(xferlen, CPU_CACHE_LINE_BYTES));
+		}
+		st_sdmmc_idma_txrx(sc, data);
+
+		sc->sdmmc->DTIMER = 0xFFFFFFFF;
+		sc->sdmmc->DLEN = xferlen;
+		sc->sdmmc->DCTRL = dctrl;
+
+		int_mask &= ~(SDMMC_INT_CMD_DONE_MASK |
+		    SDMMC_INT_CMD_RESPONSE_DONE_MASK);
+		int_mask |= SDMMC_INT_DATA_DONE_MASK;
+	}
+
+	sc->sdmmc->MASK = int_mask;
+	sc->sdmmc->ARG = arg;
+	sc->sdmmc->CMD = cmdval | cmd->opcode;
+
+	cmd->error = st_sdmmc_wait_irq(sc);
+	if (cmd->error) {
+		sleep(10);
+		device_printf(sc->dev,
+		    "error (%d) waiting for xfer: status %08x, cmd: %d, flags: %08x\n",
+		    cmd->error, sc->intr_status, cmd->opcode, cmd->flags);
+	} else {
+		if ((cmd->flags & MMC_RSP_PRESENT) != 0) {
+			if ((cmd->flags & MMC_RSP_136) != 0) {
+				cmd->resp[0] = sc->sdmmc->RESP1;
+				cmd->resp[1] = sc->sdmmc->RESP2;
+				cmd->resp[2] = sc->sdmmc->RESP3;
+				cmd->resp[3] = sc->sdmmc->RESP4;
+				debug_print(sc, 2, "rsp: %08x %08x %08x %08x\n",
+				    cmd->resp[0],
+				    cmd->resp[1],
+				    cmd->resp[2],
+				    cmd->resp[3]);
+			} else {
+				cmd->resp[0] = sc->sdmmc->RESP1;
+				debug_print(sc, 2, "rsp: %08x\n", cmd->resp[0]);
+			}
+		}
+
+		if (short_xfer && cmd->data != NULL &&
+		    (cmd->data->flags & MMC_DATA_READ) != 0) {
+			memcpy(cmd->data->data, sc->dmabuf, xferlen);
+		}
+	}
+
+	st_sdmmc_idma_stop(sc);
+	sc->sdmmc->CMD = 0;
+	sc->sdmmc->MASK = 0;
+	sc->sdmmc->ICR = 0xFFFFFFFF;
+}
+
+static int
+st_sdmmc_request(device_t brdev, device_t reqdev, struct mmc_request *req)
+{
+	struct st_sdmmc_softc *sc;
+
+	sc = device_get_softc(brdev);
+
+	ST_SDMMC_LOCK(sc);
+	st_sdmmc_cmd_do(sc, req->cmd);
+	if (req->stop != NULL) {
+		st_sdmmc_cmd_do(sc, req->stop);
+	}
+	ST_SDMMC_UNLOCK(sc);
+
+	(*req->done)(req);
+
+	return (0);
+}
+
+static int
+st_sdmmc_get_ro(device_t brdev, device_t reqdev)
+{
+
+	/*
+	 * FIXME: Currently just ignore write protection. Micro-SD doesn't have
+	 * it anyway and most boards are now using Micro-SD slots.
+	 */
+	return (0);
+}
+
+static int
+st_sdmmc_acquire_host(device_t brdev, device_t reqdev)
+{
+	struct st_sdmmc_softc *sc;
+
+	sc = device_get_softc(brdev);
+
+	ST_SDMMC_LOCK(sc);
+	while (sc->bus_busy)
+		msleep(sc, &sc->mtx, PZERO, "stsdmmcah", hz / 5);
+	sc->bus_busy++;
+	ST_SDMMC_UNLOCK(sc);
+	return (0);
+}
+
+static int
+st_sdmmc_release_host(device_t brdev, device_t reqdev)
+{
+	struct st_sdmmc_softc *sc;
+
+	sc = device_get_softc(brdev);
+
+	ST_SDMMC_LOCK(sc);
+	sc->bus_busy--;
+	wakeup(sc);
+	ST_SDMMC_UNLOCK(sc);
+	return (0);
+}
+
+static int
+st_sdmmc_read_ivar(device_t bus, device_t child, int which, uintptr_t *result)
+{
+	struct st_sdmmc_softc *sc;
+
+	sc = device_get_softc(bus);
+
+	switch (which) {
+	default:
+		return (EINVAL);
+	case MMCBR_IVAR_BUS_MODE:
+		*(int *)result = sc->host.ios.bus_mode;
+		break;
+	case MMCBR_IVAR_BUS_WIDTH:
+		*(int *)result = sc->host.ios.bus_width;
+		break;
+	case MMCBR_IVAR_CHIP_SELECT:
+		*(int *)result = sc->host.ios.chip_select;
+		break;
+	case MMCBR_IVAR_CLOCK:
+		*(int *)result = sc->host.ios.clock;
+		break;
+	case MMCBR_IVAR_F_MIN:
+		*(int *)result = sc->host.f_min;
+		break;
+	case MMCBR_IVAR_F_MAX:
+		*(int *)result = sc->host.f_max;
+		break;
+	case MMCBR_IVAR_HOST_OCR:
+		*(int *)result = sc->host.host_ocr;
+		break;
+	case MMCBR_IVAR_MODE:
+		*(int *)result = sc->host.mode;
+		break;
+	case MMCBR_IVAR_OCR:
+		*(int *)result = sc->host.ocr;
+		break;
+	case MMCBR_IVAR_POWER_MODE:
+		*(int *)result = sc->host.ios.power_mode;
+		break;
+	case MMCBR_IVAR_VDD:
+		*(int *)result = sc->host.ios.vdd;
+		break;
+	case MMCBR_IVAR_VCCQ:
+		*(int *)result = sc->host.ios.vccq;
+		break;
+	case MMCBR_IVAR_CAPS:
+		*(int *)result = sc->host.caps;
+		break;
+	case MMCBR_IVAR_MAX_DATA:
+		/*
+		 * Note: At the moment of writing this, RTEMS ignores this
+		 * value. So it's quite irrelevant what is returned here.
+		 */
+		*(int *)result = (SDMMC_DLEN_DATALENGTH_Msk) / MMC_SECTOR_SIZE;
+		break;
+	case MMCBR_IVAR_TIMING:
+		*(int *)result = sc->host.ios.timing;
+		break;
+	}
+	return (0);
+}
+
+static int
+st_sdmmc_write_ivar(device_t bus, device_t child, int which, uintptr_t value)
+{
+	struct st_sdmmc_softc *sc;
+
+	sc = device_get_softc(bus);
+
+	switch (which) {
+	default:
+		return (EINVAL);
+	case MMCBR_IVAR_BUS_MODE:
+		sc->host.ios.bus_mode = value;
+		break;
+	case MMCBR_IVAR_BUS_WIDTH:
+		sc->host.ios.bus_width = value;
+		break;
+	case MMCBR_IVAR_CHIP_SELECT:
+		sc->host.ios.chip_select = value;
+		break;
+	case MMCBR_IVAR_CLOCK:
+		sc->host.ios.clock = value;
+		break;
+	case MMCBR_IVAR_MODE:
+		sc->host.mode = value;
+		break;
+	case MMCBR_IVAR_OCR:
+		sc->host.ocr = value;
+		break;
+	case MMCBR_IVAR_POWER_MODE:
+		sc->host.ios.power_mode = value;
+		break;
+	case MMCBR_IVAR_VDD:
+		sc->host.ios.vdd = value;
+		break;
+	case MMCBR_IVAR_TIMING:
+		sc->host.ios.timing = value;
+		break;
+	case MMCBR_IVAR_VCCQ:
+		sc->host.ios.vccq = value;
+		break;
+	/* These are read-only */
+	case MMCBR_IVAR_CAPS:
+	case MMCBR_IVAR_HOST_OCR:
+	case MMCBR_IVAR_F_MIN:
+	case MMCBR_IVAR_F_MAX:
+	case MMCBR_IVAR_MAX_DATA:
+		return (EINVAL);
+	}
+	return (0);
+}
+
+static device_method_t st_sdmmc_methods[] = {
+	/* device_if */
+	DEVMETHOD(device_probe, st_sdmmc_probe),
+	DEVMETHOD(device_attach, st_sdmmc_attach),
+	DEVMETHOD(device_detach, st_sdmmc_detach),
+
+	/* Bus interface */
+	DEVMETHOD(bus_read_ivar, st_sdmmc_read_ivar),
+	DEVMETHOD(bus_write_ivar, st_sdmmc_write_ivar),
+
+	/* mmcbr_if */
+	DEVMETHOD(mmcbr_update_ios, st_sdmmc_update_ios),
+	DEVMETHOD(mmcbr_request, st_sdmmc_request),
+	DEVMETHOD(mmcbr_get_ro, st_sdmmc_get_ro),
+	DEVMETHOD(mmcbr_acquire_host, st_sdmmc_acquire_host),
+	DEVMETHOD(mmcbr_release_host, st_sdmmc_release_host),
+
+	DEVMETHOD_END
+};
+
+static driver_t st_sdmmc_driver = {
+	"st_sdmmc",
+	st_sdmmc_methods,
+	sizeof(struct st_sdmmc_softc)
+};
+
+static devclass_t st_sdmmc_devclass;
+
+DRIVER_MODULE(st_sdmmc, nexus, st_sdmmc_driver, st_sdmmc_devclass, NULL, NULL);
+DRIVER_MODULE(mmc, st_sdmmc, mmc_driver, mmc_devclass, NULL, NULL);
+MODULE_DEPEND(st_sdmmc, mmc, 1, 1, 1);
-- 
2.26.2



More information about the devel mailing list