diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index 8cb46245ae..f74e957c31 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -216,6 +216,7 @@ SRC_C += \ pendsv.c \ pin.c \ sdcard.c \ + sdio.c \ systick.c \ ticks.c \ tusb_port.c \ diff --git a/ports/mimxrt/sdio.c b/ports/mimxrt/sdio.c new file mode 100644 index 0000000000..771a01e3d9 --- /dev/null +++ b/ports/mimxrt/sdio.c @@ -0,0 +1,334 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Ibrahim Abdelkader + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "py/mperrno.h" +#include "py/mphal.h" +#include "pin.h" +#include "pendsv.h" + +#include "fsl_usdhc.h" +#include "fsl_iomuxc.h" + +#if MICROPY_PY_NETWORK_CYW43 + +#if MICROPY_HW_SDIO_SDMMC == 1 +#define SDMMC USDHC1 +#define SDMMC_IRQn USDHC1_IRQn +#define SDMMC_CLOCK_DIV kCLOCK_Usdhc1Div +#define SDMMC_CLOCK_MUX kCLOCK_Usdhc1Mux +#ifndef MICROPY_HW_SDIO_CLK_ALT +#define MICROPY_HW_SDIO_CMD_ALT (0) +#define MICROPY_HW_SDIO_CLK_ALT (0) +#define MICROPY_HW_SDIO_D0_ALT (0) +#define MICROPY_HW_SDIO_D1_ALT (0) +#define MICROPY_HW_SDIO_D2_ALT (0) +#define MICROPY_HW_SDIO_D3_ALT (0) +#endif +#else +#define SDMMC USDHC2 +#define SDMMC_IRQn USDHC2_IRQn +#define SDMMC_CLOCK_DIV kCLOCK_Usdhc2Div +#define SDMMC_CLOCK_MUX kCLOCK_Usdhc2Mux +#ifndef MICROPY_HW_SDIO_CLK_ALT +#define MICROPY_HW_SDIO_CMD_ALT (6) +#define MICROPY_HW_SDIO_CLK_ALT (6) +#define MICROPY_HW_SDIO_D0_ALT (6) +#define MICROPY_HW_SDIO_D1_ALT (6) +#define MICROPY_HW_SDIO_D2_ALT (6) +#define MICROPY_HW_SDIO_D3_ALT (6) +#endif +#endif + +#define SDMMC_CLOCK_400KHZ (400000U) +#define SDMMC_CLOCK_25MHZ (25000000U) +#define SDMMC_CLOCK_50MHZ (50000000U) + +#if SDIO_DEBUG +#define debug_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__) +#else +#define debug_printf(...) +#endif + +#define DMA_DESCRIPTOR_BUFFER_SIZE (32U) +AT_NONCACHEABLE_SECTION_ALIGN( + static uint32_t sdio_adma_descriptor_table[DMA_DESCRIPTOR_BUFFER_SIZE], USDHC_ADMA2_ADDRESS_ALIGN); + +typedef struct _mimxrt_sdmmc_t { + USDHC_Type *inst; + usdhc_handle_t handle; + volatile uint32_t xfer_flags; + volatile uint32_t xfer_error; +} mimxrt_sdmmc_t; + +static mimxrt_sdmmc_t sdmmc = { + .inst = SDMMC, +}; + +typedef enum { + SDIO_TRANSFER_DATA_COMPLETE = (1 << 0), + SDIO_TRANSFER_CMD_COMPLETE = (1 << 1), + SDIO_TRANSFER_ERROR = (1 << 2), +} sdio_xfer_flags_t; + +static uint32_t sdio_base_clk(void) { + return CLOCK_GetSysPfdFreq(kCLOCK_Pfd0) / (CLOCK_GetDiv(kCLOCK_Usdhc1Div) + 1U); +} + +static uint32_t sdio_response_type(uint32_t cmd) { + switch (cmd) { + case 3: + return kCARD_ResponseTypeR6; + case 5: + return kCARD_ResponseTypeR4; + case 7: + return kCARD_ResponseTypeR1; + case 52: + return kCARD_ResponseTypeR5; + default: + return kCARD_ResponseTypeNone; + } +} + +static void sdio_transfer_callback(USDHC_Type *base, + usdhc_handle_t *handle, status_t status, void *userData) { + if (status == kStatus_USDHC_TransferDataComplete) { + sdmmc.xfer_flags |= SDIO_TRANSFER_DATA_COMPLETE; + } else if (status == kStatus_USDHC_SendCommandSuccess) { + sdmmc.xfer_flags |= SDIO_TRANSFER_CMD_COMPLETE; + } else if (status != kStatus_USDHC_BusyTransferring) { + sdmmc.xfer_error = status; + sdmmc.xfer_flags |= SDIO_TRANSFER_ERROR; + } +} + +static void sdio_interrupt_callback(USDHC_Type *base, void *userData) { + extern void (*cyw43_poll)(void); + + USDHC_DisableInterruptSignal(base, kUSDHC_CardInterruptFlag); + USDHC_ClearInterruptStatusFlags(base, kUSDHC_CardInterruptFlag); + + if (cyw43_poll) { + pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll); + } +} + +void sdio_init(uint32_t irq_pri) { + machine_pin_config(MICROPY_HW_SDIO_CMD, PIN_MODE_ALT, PIN_PULL_UP_100K, PIN_DRIVE_6, 0, MICROPY_HW_SDIO_CMD_ALT); + machine_pin_config(MICROPY_HW_SDIO_CLK, PIN_MODE_ALT, PIN_PULL_DISABLED, PIN_DRIVE_6, 0, MICROPY_HW_SDIO_CLK_ALT); + machine_pin_config(MICROPY_HW_SDIO_D0, PIN_MODE_ALT, PIN_PULL_UP_100K, PIN_DRIVE_6, 0, MICROPY_HW_SDIO_D0_ALT); + machine_pin_config(MICROPY_HW_SDIO_D1, PIN_MODE_ALT, PIN_PULL_UP_100K, PIN_DRIVE_6, 0, MICROPY_HW_SDIO_D1_ALT); + machine_pin_config(MICROPY_HW_SDIO_D2, PIN_MODE_ALT, PIN_PULL_UP_100K, PIN_DRIVE_6, 0, MICROPY_HW_SDIO_D2_ALT); + machine_pin_config(MICROPY_HW_SDIO_D3, PIN_MODE_ALT, PIN_PULL_UP_100K, PIN_DRIVE_6, 0, MICROPY_HW_SDIO_D3_ALT); + + // Configure PFD0 of PLL2 (system PLL) fractional divider to 24 resulting in: + // with PFD0_clk = PLL2_clk * 18 / N + // PFD0_clk = 528MHz * 18 / 24 = 396MHz + CLOCK_InitSysPfd(kCLOCK_Pfd0, 24U); + CLOCK_SetDiv(SDMMC_CLOCK_DIV, 1U); // USDHC_input_clk = PFD0_clk / 2 + CLOCK_SetMux(SDMMC_CLOCK_MUX, 1U); // Select PFD0 as clock input for USDHC + + // Initialize USDHC + const usdhc_config_t config = { + .endianMode = kUSDHC_EndianModeLittle, + .dataTimeout = 0xFU, + .readBurstLen = 0, + .writeBurstLen = 0, + .readWatermarkLevel = 128U, + .writeWatermarkLevel = 128U, + }; + + USDHC_Init(sdmmc.inst, &config); + USDHC_Reset(SDMMC, kUSDHC_ResetAll, 1000U); + USDHC_DisableInterruptSignal(SDMMC, kUSDHC_AllInterruptFlags); + USDHC_SetSdClock(sdmmc.inst, sdio_base_clk(), SDMMC_CLOCK_25MHZ); + USDHC_SetDataBusWidth(sdmmc.inst, kUSDHC_DataBusWidth1Bit); + + mp_hal_delay_ms(10); + + NVIC_SetPriority(SDMMC_IRQn, irq_pri); + EnableIRQ(SDMMC_IRQn); + + usdhc_transfer_callback_t callbacks = { + .SdioInterrupt = sdio_interrupt_callback, + .TransferComplete = sdio_transfer_callback, + }; + USDHC_TransferCreateHandle(sdmmc.inst, &sdmmc.handle, &callbacks, NULL); +} + +void sdio_deinit(void) { +} + +void sdio_reenable(void) { +} + +void sdio_enable_irq(bool enable) { + if (enable) { + USDHC_ClearInterruptStatusFlags(sdmmc.inst, kUSDHC_CardInterruptFlag); + USDHC_EnableInterruptStatus(sdmmc.inst, kUSDHC_CardInterruptFlag); + USDHC_EnableInterruptSignal(sdmmc.inst, kUSDHC_CardInterruptFlag); + } else { + USDHC_DisableInterruptStatus(sdmmc.inst, kUSDHC_CardInterruptFlag); + USDHC_ClearInterruptStatusFlags(sdmmc.inst, kUSDHC_CardInterruptFlag); + USDHC_DisableInterruptSignal(sdmmc.inst, kUSDHC_CardInterruptFlag); + } +} + +void sdio_enable_high_speed_4bit(void) { + USDHC_SetSdClock(sdmmc.inst, sdio_base_clk(), SDMMC_CLOCK_50MHZ); + USDHC_SetDataBusWidth(sdmmc.inst, kUSDHC_DataBusWidth4Bit); +} + +static status_t sdio_transfer_dma(USDHC_Type *base, + usdhc_handle_t *handle, usdhc_transfer_t *transfer, uint32_t timeout_ms) { + status_t status; + usdhc_adma_config_t dma_config = { + .dmaMode = kUSDHC_DmaModeAdma2, + #if !FSL_FEATURE_USDHC_HAS_NO_RW_BURST_LEN + .burstLen = kUSDHC_EnBurstLenForINCR, + #endif + .admaTable = sdio_adma_descriptor_table, + .admaTableWords = DMA_DESCRIPTOR_BUFFER_SIZE, + }; + + sdmmc.xfer_flags = 0; + sdmmc.xfer_error = 0; + + uint32_t xfer_flags = SDIO_TRANSFER_CMD_COMPLETE; + if (transfer->data != NULL) { + xfer_flags |= SDIO_TRANSFER_DATA_COMPLETE; + } + + status = USDHC_TransferNonBlocking(base, handle, &dma_config, transfer); + if (status != kStatus_Success) { + debug_printf("sdio_transfer_dma failed to start transfer error: %lu\n", status); + return status; + } + + uint32_t start = mp_hal_ticks_ms(); + while ((sdmmc.xfer_flags != xfer_flags) && + !(sdmmc.xfer_flags & SDIO_TRANSFER_ERROR) && + (mp_hal_ticks_ms() - start) < timeout_ms) { + MICROPY_EVENT_POLL_HOOK; + } + + if (sdmmc.xfer_flags == 0) { + debug_printf("sdio_transfer_dma transfer timeout.\n"); + return kStatus_Timeout; + } else if (sdmmc.xfer_flags != xfer_flags) { + debug_printf("sdio_transfer_dma transfer failed: %lu\n", sdmmc.xfer_error); + USDHC_Reset(base, kUSDHC_ResetCommand, 100); + if (xfer_flags & SDIO_TRANSFER_DATA_COMPLETE) { + USDHC_Reset(base, kUSDHC_ResetData, 100); + } + return sdmmc.xfer_error; + } + + return kStatus_Success; +} + +int sdio_transfer(uint32_t cmd, uint32_t arg, uint32_t *resp) { + status_t status; + usdhc_command_t command = { + .index = cmd, + .argument = arg, + .type = kCARD_CommandTypeNormal, + .responseType = sdio_response_type(cmd), + .responseErrorFlags = 0 + }; + + usdhc_transfer_t transfer = { + .data = NULL, + .command = &command, + }; + + status = sdio_transfer_dma(sdmmc.inst, &sdmmc.handle, &transfer, 5000); + + if (status != kStatus_Success) { + debug_printf("sdio_transfer failed!\n"); + return -MP_EIO; + } + + if (resp != NULL) { + *resp = command.response[0]; + } + + return 0; +} + +int sdio_transfer_cmd53(bool write, uint32_t block_size, uint32_t arg, size_t len, uint8_t *buf) { + usdhc_data_t data = { + .enableAutoCommand12 = false, + .enableAutoCommand23 = false, + .enableIgnoreError = false, + .dataType = kUSDHC_TransferDataNormal, + }; + + usdhc_command_t command = { + .index = 53, + .argument = arg, + .type = kCARD_CommandTypeNormal, + .responseType = kCARD_ResponseTypeR5, + .responseErrorFlags = 0 + }; + + usdhc_transfer_t transfer = { + .data = &data, + .command = &command, + }; + + if (write) { + data.rxData = NULL; + data.txData = (uint32_t *)buf; + } else { + data.txData = NULL; + data.rxData = (uint32_t *)buf; + } + + if (arg & (1 << 27)) { + // SDIO_BLOCK_MODE + data.blockSize = block_size; + data.blockCount = len / block_size; + } else { + // SDIO_BYTE_MODE + data.blockSize = block_size; + data.blockCount = 1; + } + + debug_printf("cmd53 rw: %d addr 0x%p blocksize %u blockcount %lu total %lu len %d\n", + write, buf, data.blockSize, data.blockCount, data.blockSize * data.blockCount, len); + + status_t status = sdio_transfer_dma(sdmmc.inst, &sdmmc.handle, &transfer, 5000); + + if (status != kStatus_Success) { + debug_printf("sdio_transfer_cmd53 failed!\n"); + return -1; + } + + return 0; +} + +#endif diff --git a/ports/mimxrt/sdio.h b/ports/mimxrt/sdio.h new file mode 100644 index 0000000000..f8d6f498b6 --- /dev/null +++ b/ports/mimxrt/sdio.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2023 Ibrahim Abdelkader + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_MIMXRT_SDIO_H +#define MICROPY_INCLUDED_MIMXRT_SDIO_H + +#include +#include + +void sdio_init(uint32_t irq_pri); +void sdio_deinit(void); +void sdio_reenable(void); +void sdio_enable_irq(bool enable); +void sdio_enable_high_speed_4bit(void); +int sdio_transfer(uint32_t cmd, uint32_t arg, uint32_t *resp); +int sdio_transfer_cmd53(bool write, uint32_t block_size, uint32_t arg, size_t len, uint8_t *buf); + +#endif // MICROPY_INCLUDED_MIMXRT_SDIO_H