/* * 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" #if MICROPY_PY_NETWORK_CYW43 #include "fsl_usdhc.h" #include "fsl_iomuxc.h" #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