nxp-mcuxpresso / mcux-sdk

MCUXpresso SDK
BSD 3-Clause "New" or "Revised" License
301 stars 136 forks source link

[BUG] DMA XFERCFG::XFERCOUNT overflow when submitting large I2S DMA transfer #186

Open maciejmatuszak opened 2 months ago

maciejmatuszak commented 2 months ago

Describe the bug Submitting large I2S DMA Rx transfer using I2S_RxTransferReceiveDMA(...) splits the transfer into multiple DMA transfers. This happens if I2S_GetTransferBytes() reduce the size. I2S_GetTransferBytes calculates max single DMA transfer which is limited by hardware registers XFERCFGn:XFERCOUNT and XFERCFGn:WIDTH however the function I2S_GetTransferBytes uses:

#define DMA_MAX_TRANSFER_BYTES (DMA_MAX_TRANSFER_COUNT * sizeof(uint32_t))

Which assumes frame width of 4 bytes (sizeof(uint32_t)). This leads to I2S_GetTransferBytes() limiting it to 4096 bytes. The HW register XFERCFGn is set by DMA_SetChannelXferConfig() which tries to set the XFERCFGn:XFERCOUNT field to 4096/2=2048 which is bigger than the 10 bits the XFERCFGn:XFERCOUNT allows. As a result only half of the 4096 bytes buffer to be filled.

To Reproduce

Expected behavior The s_Buffer should be filled completly

Screenshots and console output image

Additional context

maciejmatuszak commented 2 months ago

This is How I fixed it:

static uint16_t I2S_GetTransferBytes(i2s_dma_handle_t *handle, volatile i2s_transfer_t *transfer)
{
    assert(transfer != NULL);

    uint16_t transferBytes;

    if (transfer->dataSize >( DMA_MAX_TRANSFER_COUNT*handle->bytesPerFrame))
    {
        transferBytes = DMA_MAX_TRANSFER_COUNT*handle->bytesPerFrame;
        if ((transferBytes % 4U) != 0U)
        {
            transferBytes -= (transferBytes % 4U);
        }
    }
    else
    {
        transferBytes = (uint16_t)transfer->dataSize;
    }

    return transferBytes;
}

Also I put assert in DMA_SetChannelXferConfig

static inline uint32_t DMA_SetChannelXferConfig(
    bool reload, bool clrTrig, bool intA, bool intB, uint8_t width, uint8_t srcInc, uint8_t dstInc, uint32_t bytes)
{
    assert(((uint32_t)bytes / (uint32_t)width - 1UL) < DMA_MAX_TRANSFER_COUNT);
    return (DMA_CHANNEL_XFERCFG_CFGVALID_MASK | DMA_CHANNEL_XFERCFG_RELOAD(reload) |
            DMA_CHANNEL_XFERCFG_CLRTRIG(clrTrig) | DMA_CHANNEL_XFERCFG_SETINTA(intA) |
            DMA_CHANNEL_XFERCFG_SETINTB(intB) |
            DMA_CHANNEL_XFERCFG_WIDTH((uint32_t)width == 4UL ? 2UL : ((uint32_t)width - 1UL)) |
            DMA_CHANNEL_XFERCFG_SRCINC((uint32_t)srcInc == 4UL ? ((uint32_t)srcInc - 1UL) : (uint32_t)srcInc) |
            DMA_CHANNEL_XFERCFG_DSTINC((uint32_t)dstInc == 4UL ? ((uint32_t)dstInc - 1UL) : (uint32_t)dstInc) |
            DMA_CHANNEL_XFERCFG_XFERCOUNT((uint32_t)bytes / (uint32_t)width - 1UL));
}
mcuxsusan commented 2 months ago

Thanks for raising up the issue and suggest fix, I have forwarded the issue to internal developer, but the feedback maybe delayed, appreciate for your patience.