| /* |
| * This file is part of the MicroPython project, http://micropython.org/ |
| * |
| * The MIT License (MIT) |
| * |
| * Copyright (c) 2018-2019 Damien P. George |
| * |
| * 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 "genhdr/pins.h" |
| #include "pendsv.h" |
| #include "sdio.h" |
| |
| #if MICROPY_PY_NETWORK_CYW43 |
| |
| #define DEFAULT_MASK (0) |
| |
| enum { |
| SDMMC_IRQ_STATE_DONE, |
| SDMMC_IRQ_STATE_CMD_DONE, |
| SDMMC_IRQ_STATE_CMD_DATA_PENDING, |
| }; |
| |
| static volatile int sdmmc_irq_state; |
| static volatile uint32_t sdmmc_block_size_log2; |
| static volatile bool sdmmc_write; |
| static volatile bool sdmmc_dma; |
| static volatile uint32_t sdmmc_error; |
| static volatile uint8_t *sdmmc_buf_cur; |
| static volatile uint8_t *sdmmc_buf_top; |
| |
| void sdio_init(uint32_t irq_pri) { |
| // configure IO pins |
| mp_hal_pin_config(pin_C8, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, 12); |
| mp_hal_pin_config(pin_C9, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, 12); |
| mp_hal_pin_config(pin_C10, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, 12); |
| mp_hal_pin_config(pin_C11, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, 12); |
| mp_hal_pin_config(pin_C12, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, 12); // CLK doesn't need pull-up |
| mp_hal_pin_config(pin_D2, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, 12); |
| |
| __HAL_RCC_SDMMC1_CLK_ENABLE(); // enable SDIO peripheral |
| |
| SDMMC_TypeDef *SDIO = SDMMC1; |
| #if defined(STM32F7) |
| SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_PWRSAV | (120 - 2); // 1-bit, 400kHz |
| #else |
| SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_PWRSAV | (120 / 2); // 1-bit, 400kHz |
| #endif |
| mp_hal_delay_us(10); |
| SDIO->POWER = 3; // the card is clocked |
| mp_hal_delay_us(10); |
| SDIO->DCTRL = SDMMC_DCTRL_RWMOD; // RWMOD is SDIO_CK |
| #if defined(STM32F7) |
| SDIO->CLKCR |= SDMMC_CLKCR_CLKEN; |
| #endif |
| mp_hal_delay_us(10); |
| |
| __HAL_RCC_DMA2_CLK_ENABLE(); // enable DMA2 peripheral |
| |
| NVIC_SetPriority(SDMMC1_IRQn, irq_pri); |
| |
| SDIO->MASK = 0; |
| HAL_NVIC_EnableIRQ(SDMMC1_IRQn); |
| } |
| |
| void sdio_deinit(void) { |
| __HAL_RCC_SDMMC1_CLK_DISABLE(); |
| #if defined(STM32F7) |
| __HAL_RCC_DMA2_CLK_DISABLE(); |
| #endif |
| } |
| |
| void sdio_enable_high_speed_4bit(void) { |
| SDMMC_TypeDef *SDIO = SDMMC1; |
| SDIO->POWER = 0; // power off |
| mp_hal_delay_us(10); |
| #if defined(STM32F7) |
| SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_WIDBUS_0 | SDMMC_CLKCR_BYPASS /*| SDMMC_CLKCR_PWRSAV*/; // 4-bit, 48MHz |
| #else |
| SDIO->CLKCR = SDMMC_CLKCR_HWFC_EN | SDMMC_CLKCR_WIDBUS_0; // 4-bit, 48MHz |
| #endif |
| mp_hal_delay_us(10); |
| SDIO->POWER = 3; // the card is clocked |
| mp_hal_delay_us(10); |
| SDIO->DCTRL = SDMMC_DCTRL_SDIOEN | SDMMC_DCTRL_RWMOD; // SDIOEN, RWMOD is SDIO_CK |
| #if defined(STM32F7) |
| SDIO->CLKCR |= SDMMC_CLKCR_CLKEN; |
| #endif |
| SDIO->MASK = DEFAULT_MASK; |
| mp_hal_delay_us(10); |
| } |
| |
| void SDMMC1_IRQHandler(void) { |
| if (SDMMC1->STA & SDMMC_STA_CMDREND) { |
| SDMMC1->ICR = SDMMC_ICR_CMDRENDC; |
| uint32_t r1 = SDMMC1->RESP1; |
| if (SDMMC1->RESPCMD == 53 && r1 & 0x800) { |
| printf("bad RESP1: %lu %lx\n", SDMMC1->RESPCMD, r1); |
| sdmmc_error = 0xffffffff; |
| SDMMC1->MASK &= SDMMC_MASK_SDIOITIE; |
| sdmmc_irq_state = SDMMC_IRQ_STATE_DONE; |
| return; |
| } |
| #if defined(STM32H7) |
| if (!sdmmc_dma) { |
| while (sdmmc_buf_cur < sdmmc_buf_top && (SDMMC1->STA & SDMMC_STA_DPSMACT) && !(SDMMC1->STA & SDMMC_STA_RXFIFOE)) { |
| *(uint32_t *)sdmmc_buf_cur = SDMMC1->FIFO; |
| sdmmc_buf_cur += 4; |
| } |
| } |
| #endif |
| if (sdmmc_buf_cur >= sdmmc_buf_top) { |
| // data transfer finished, so we are done |
| SDMMC1->MASK &= SDMMC_MASK_SDIOITIE; |
| sdmmc_irq_state = SDMMC_IRQ_STATE_DONE; |
| return; |
| } |
| if (sdmmc_write) { |
| SDMMC1->DCTRL = |
| SDMMC_DCTRL_SDIOEN |
| | SDMMC_DCTRL_RWMOD |
| | sdmmc_block_size_log2 << SDMMC_DCTRL_DBLOCKSIZE_Pos |
| #if defined(STM32F7) |
| | (sdmmc_dma << SDMMC_DCTRL_DMAEN_Pos) |
| #endif |
| | (!sdmmc_write) << SDMMC_DCTRL_DTDIR_Pos |
| | SDMMC_DCTRL_DTEN |
| ; |
| if (!sdmmc_dma) { |
| SDMMC1->MASK |= SDMMC_MASK_TXFIFOHEIE; |
| } |
| } |
| sdmmc_irq_state = SDMMC_IRQ_STATE_CMD_DONE; |
| } else if (SDMMC1->STA & SDMMC_STA_DATAEND) { |
| // data transfer complete |
| // note: it's possible to get DATAEND before CMDREND |
| SDMMC1->ICR = SDMMC_ICR_DATAENDC; |
| #if defined(STM32F7) |
| // check if there is some remaining data in RXFIFO |
| if (!sdmmc_dma) { |
| while (SDMMC1->STA & SDMMC_STA_RXDAVL) { |
| *(uint32_t *)sdmmc_buf_cur = SDMMC1->FIFO; |
| sdmmc_buf_cur += 4; |
| } |
| } |
| #endif |
| if (sdmmc_irq_state == SDMMC_IRQ_STATE_CMD_DONE) { |
| // command and data finished, so we are done |
| SDMMC1->MASK &= SDMMC_MASK_SDIOITIE; |
| sdmmc_irq_state = SDMMC_IRQ_STATE_DONE; |
| } |
| } else if (SDMMC1->STA & SDMMC_STA_TXFIFOHE) { |
| if (!sdmmc_dma && sdmmc_write) { |
| // write up to 8 words to fifo |
| for (size_t i = 8; i && sdmmc_buf_cur < sdmmc_buf_top; --i) { |
| SDMMC1->FIFO = *(uint32_t *)sdmmc_buf_cur; |
| sdmmc_buf_cur += 4; |
| } |
| if (sdmmc_buf_cur >= sdmmc_buf_top) { |
| // finished, disable IRQ |
| SDMMC1->MASK &= ~SDMMC_MASK_TXFIFOHEIE; |
| } |
| } |
| } else if (SDMMC1->STA & SDMMC_STA_RXFIFOHF) { |
| if (!sdmmc_dma && !sdmmc_write) { |
| // read up to 8 words from fifo |
| for (size_t i = 8; i && sdmmc_buf_cur < sdmmc_buf_top; --i) { |
| *(uint32_t *)sdmmc_buf_cur = SDMMC1->FIFO; |
| sdmmc_buf_cur += 4; |
| } |
| } |
| } else if (SDMMC1->STA & SDMMC_STA_SDIOIT) { |
| SDMMC1->MASK &= ~SDMMC_MASK_SDIOITIE; |
| SDMMC1->ICR = SDMMC_ICR_SDIOITC; |
| |
| #if MICROPY_PY_NETWORK_CYW43 |
| extern void (*cyw43_poll)(void); |
| if (cyw43_poll) { |
| pendsv_schedule_dispatch(PENDSV_DISPATCH_CYW43, cyw43_poll); |
| } |
| #endif |
| } else if (SDMMC1->STA & 0x3f) { |
| // an error |
| sdmmc_error = SDMMC1->STA; |
| SDMMC1->ICR = SDMMC_STATIC_FLAGS; |
| SDMMC1->MASK &= SDMMC_MASK_SDIOITIE; |
| sdmmc_irq_state = SDMMC_IRQ_STATE_DONE; |
| } |
| } |
| |
| int sdio_transfer(uint32_t cmd, uint32_t arg, uint32_t *resp) { |
| #if defined(STM32F7) |
| // Wait for any outstanding TX to complete |
| while (SDMMC1->STA & SDMMC_STA_TXACT) { |
| } |
| #endif |
| |
| DMA2_Stream3->CR = 0; // ensure DMA is reset |
| SDMMC1->ICR = SDMMC_STATIC_FLAGS; // clear interrupts |
| SDMMC1->ARG = arg; |
| SDMMC1->CMD = cmd | SDMMC_CMD_WAITRESP_0 | SDMMC_CMD_CPSMEN; |
| |
| sdmmc_irq_state = SDMMC_IRQ_STATE_CMD_DATA_PENDING; |
| sdmmc_error = 0; |
| sdmmc_buf_cur = NULL; |
| sdmmc_buf_top = NULL; |
| SDMMC1->MASK = (SDMMC1->MASK & SDMMC_MASK_SDIOITIE) | SDMMC_MASK_CMDRENDIE | 0x3f; |
| |
| uint32_t start = mp_hal_ticks_ms(); |
| for (;;) { |
| __WFI(); |
| if (sdmmc_irq_state == SDMMC_IRQ_STATE_DONE) { |
| break; |
| } |
| if (mp_hal_ticks_ms() - start > 1000) { |
| SDMMC1->MASK = DEFAULT_MASK; |
| printf("sdio_transfer timeout STA=0x%08x\n", (uint)SDMMC1->STA); |
| return -MP_ETIMEDOUT; |
| } |
| } |
| |
| SDMMC1->MASK &= SDMMC_MASK_SDIOITIE; |
| |
| if (sdmmc_error == SDMMC_STA_CCRCFAIL && cmd == 5) { |
| // Errata: CMD CRC error is incorrectly generated for CMD 5 |
| return 0; |
| } else if (sdmmc_error) { |
| return -(0x1000000 | sdmmc_error); |
| } |
| |
| uint32_t rcmd = SDMMC1->RESPCMD; |
| if (rcmd != cmd) { |
| printf("sdio_transfer: cmd=%lu rcmd=%lu\n", cmd, rcmd); |
| return -MP_EIO; |
| } |
| if (resp != NULL) { |
| *resp = SDMMC1->RESP1; |
| } |
| return 0; |
| } |
| |
| int sdio_transfer_cmd53(bool write, uint32_t block_size, uint32_t arg, size_t len, uint8_t *buf) { |
| #if defined(STM32F7) |
| // Wait for any outstanding TX to complete |
| while (SDMMC1->STA & SDMMC_STA_TXACT) { |
| } |
| #endif |
| |
| // for SDIO_BYTE_MODE the SDIO chuck of data must be a single block of the length of buf |
| int block_size_log2 = 0; |
| if (block_size == 4) { |
| block_size_log2 = 2; |
| } else if (block_size == 8) { |
| block_size_log2 = 3; |
| } else if (block_size == 16) { |
| block_size_log2 = 4; |
| } else if (block_size == 32) { |
| block_size_log2 = 5; |
| } else if (block_size == 64) { |
| block_size_log2 = 6; |
| } else if (block_size == 128) { |
| block_size_log2 = 7; |
| } else if (block_size == 256) { |
| block_size_log2 = 8; |
| } else { |
| printf("sdio_transfer_cmd53: invalid block_size %lu", block_size); |
| return -MP_EINVAL; |
| } |
| |
| bool dma = (len > 16); |
| |
| SDMMC1->ICR = SDMMC_STATIC_FLAGS; // clear interrupts |
| SDMMC1->MASK &= SDMMC_MASK_SDIOITIE; |
| |
| SDMMC1->DTIMER = 0x2000000; // about 700ms running at 48MHz |
| SDMMC1->DLEN = (len + block_size - 1) & ~(block_size - 1); |
| |
| DMA2_Stream3->CR = 0; |
| |
| if (dma) { |
| // prepare DMA so it's ready when the DPSM starts its transfer |
| |
| #if defined(STM32F7) |
| // enable DMA2 peripheral in case it was turned off by someone else |
| RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; |
| #endif |
| |
| if (write) { |
| // make sure cache is flushed to RAM so the DMA can read the correct data |
| MP_HAL_CLEAN_DCACHE(buf, len); |
| } else { |
| // make sure cache is flushed and invalidated so when DMA updates the RAM |
| // from reading the peripheral the CPU then reads the new data |
| MP_HAL_CLEANINVALIDATE_DCACHE(buf, len); |
| } |
| |
| #if defined(STM32F7) |
| DMA2->LIFCR = 0x3f << 22; |
| DMA2_Stream3->FCR = 0x07; // ? |
| DMA2_Stream3->PAR = (uint32_t)&SDMMC1->FIFO; |
| if ((uint32_t)buf & 3) { |
| printf("sdio_transfer_cmd53: buf=%p is not aligned for DMA\n", buf); |
| return -MP_EINVAL; |
| } |
| DMA2_Stream3->M0AR = (uint32_t)buf; |
| DMA2_Stream3->NDTR = ((len + block_size - 1) & ~(block_size - 1)) / 4; |
| DMA2_Stream3->CR = 4 << 25 // channel 4 |
| | 1 << 23 // MBURST INCR4 |
| | 1 << 21 // PBURST INCR4 |
| | 3 << 16 // PL very high |
| | 2 << 13 // MSIZE word |
| | 2 << 11 // PSIZE word |
| | 1 << 10 // MINC enabled |
| | 0 << 9 // PINC disabled |
| | write << 6 // DIR mem-to-periph |
| | 1 << 5 // PFCTRL periph is flow controller |
| | 1 << 0 // EN |
| ; |
| #else |
| SDMMC1->IDMABASE0 = (uint32_t)buf; |
| SDMMC1->IDMACTRL = SDMMC_IDMA_IDMAEN; |
| #endif |
| } else { |
| #if defined(STM32H7) |
| SDMMC1->IDMACTRL = 0; |
| #endif |
| } |
| |
| // for reading, need to initialise the DPSM before starting the CPSM |
| // so that the DPSM is ready to receive when the device sends data |
| // (and in case we get a long-running unrelated IRQ here on the host just |
| // after writing to CMD to initiate the command) |
| if (!write) { |
| SDMMC1->DCTRL = |
| SDMMC_DCTRL_SDIOEN |
| | SDMMC_DCTRL_RWMOD |
| | block_size_log2 << SDMMC_DCTRL_DBLOCKSIZE_Pos |
| #if defined(STM32F7) |
| | (dma << SDMMC_DCTRL_DMAEN_Pos) |
| #endif |
| | (!write) << SDMMC_DCTRL_DTDIR_Pos |
| | SDMMC_DCTRL_DTEN |
| ; |
| } |
| |
| SDMMC1->ARG = arg; |
| SDMMC1->CMD = 53 | SDMMC_CMD_WAITRESP_0 | SDMMC_CMD_CPSMEN; |
| |
| sdmmc_irq_state = SDMMC_IRQ_STATE_CMD_DATA_PENDING; |
| sdmmc_block_size_log2 = block_size_log2; |
| sdmmc_write = write; |
| sdmmc_dma = dma; |
| sdmmc_error = 0; |
| sdmmc_buf_cur = (uint8_t *)buf; |
| sdmmc_buf_top = (uint8_t *)buf + len; |
| SDMMC1->MASK = (SDMMC1->MASK & SDMMC_MASK_SDIOITIE) | SDMMC_MASK_CMDRENDIE | SDMMC_MASK_DATAENDIE | SDMMC_MASK_RXFIFOHFIE | 0x3f; |
| |
| // wait to complete transfer |
| uint32_t start = mp_hal_ticks_ms(); |
| for (;;) { |
| __WFI(); |
| if (sdmmc_irq_state == SDMMC_IRQ_STATE_DONE) { |
| break; |
| } |
| if (mp_hal_ticks_ms() - start > 200) { |
| SDMMC1->MASK &= SDMMC_MASK_SDIOITIE; |
| #if defined(STM32F7) |
| printf("sdio_transfer_cmd53: timeout wr=%d len=%u dma=%u buf_idx=%u STA=%08x SDMMC=%08x:%08x DMA=%08x:%08x:%08x RCC=%08x\n", write, (uint)len, (uint)dma, sdmmc_buf_cur - buf, (uint)SDMMC1->STA, (uint)SDMMC1->DCOUNT, (uint)SDMMC1->FIFOCNT, (uint)DMA2->LISR, (uint)DMA2->HISR, (uint)DMA2_Stream3->NDTR, (uint)RCC->AHB1ENR); |
| #else |
| printf("sdio_transfer_cmd53: timeout wr=%d len=%u dma=%u buf_idx=%u STA=%08x SDMMC=%08x:%08x IDMA=%08x\n", write, (uint)len, (uint)dma, sdmmc_buf_cur - buf, (uint)SDMMC1->STA, (uint)SDMMC1->DCOUNT, (uint)SDMMC1->DCTRL, (uint)SDMMC1->IDMACTRL); |
| #endif |
| return -MP_ETIMEDOUT; |
| } |
| } |
| |
| SDMMC1->MASK &= SDMMC_MASK_SDIOITIE; |
| |
| if (sdmmc_error) { |
| #if defined(STM32F7) |
| printf("sdio_transfer_cmd53: error=%08lx wr=%d len=%u dma=%u buf_idx=%u STA=%08x SDMMC=%08x:%08x DMA=%08x:%08x:%08x RCC=%08x\n", sdmmc_error, write, (uint)len, (uint)dma, sdmmc_buf_cur - buf, (uint)SDMMC1->STA, (uint)SDMMC1->DCOUNT, (uint)SDMMC1->FIFOCNT, (uint)DMA2->LISR, (uint)DMA2->HISR, (uint)DMA2_Stream3->NDTR, (uint)RCC->AHB1ENR); |
| #else |
| printf("sdio_transfer_cmd53: error=%08lx wr=%d len=%u dma=%u buf_idx=%u STA=%08x SDMMC=%08x:%08x IDMA=%08x\n", sdmmc_error, write, (uint)len, (uint)dma, sdmmc_buf_cur - buf, (uint)SDMMC1->STA, (uint)SDMMC1->DCOUNT, (uint)SDMMC1->DCTRL, (uint)SDMMC1->IDMACTRL); |
| #endif |
| return -(0x1000000 | sdmmc_error); |
| } |
| |
| if (!sdmmc_dma) { |
| if (sdmmc_buf_cur != sdmmc_buf_top) { |
| printf("sdio_transfer_cmd53: didn't transfer correct length: cur=%p top=%p\n", sdmmc_buf_cur, sdmmc_buf_top); |
| return -MP_EIO; |
| } |
| } |
| |
| return 0; |
| } |
| |
| #endif |