From 1bb4a56e4d7804bed0e44cd2204a1fce22a4011c Mon Sep 17 00:00:00 2001 From: Christian Mauderer Date: Wed, 10 Feb 2021 17:09:00 +0100 Subject: STM32H7: Add SDMMC driver Update #4372 --- libbsd.py | 2 + rtemsbsd/include/bsp/nexus-devices.h | 2 + rtemsbsd/include/bsp/st-sdmmc-config.h | 71 +++ rtemsbsd/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 33c852ea..add91e5a 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 53a22798..efe4fcb4 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 00000000..b4415991 --- /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 +#include +#include + +#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 f1ca66d7..5902c58c 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 00000000..723a169b --- /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 +#include + +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 00000000..bf8831a4 --- /dev/null +++ b/rtemsbsd/sys/dev/mmc/st-sdmmc.c @@ -0,0 +1,847 @@ +#include + +/* 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 +#include + +#include +#include + +#include + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#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); -- cgit v1.2.3