335 lines
11 KiB
C
335 lines
11 KiB
C
/*
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2023 Ibrahim Abdelkader <iabdalkader@openmv.io>
|
|
*
|
|
* 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 <stdio.h>
|
|
#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
|