// SPDX-License-Identifier: GPL-2.0-or-later /* * Efinix SDIO Host Controller DMA Support * * Copyright (C) 2026 Efinix, Inc. * Author: Khor Swee Aun */ #include #include #include #include #include "efx_sdio.h" void efx_sdio_set_adma_addr(struct efx_sdio_host *host, dma_addr_t addr) { efx_sdio_writel(host, (u32)addr, EFX_SDIO_ADMA_SYS_ADDR_LOW); if (host->dma_64bit) { efx_sdio_writel(host, (u32)((u64)addr >> 32), EFX_SDIO_ADMA_SYS_ADDR_HIGH); } } static void efx_sdio_adma_mark_end(struct efx_adma_desc *desc) { desc->attr |= EFX_ADMA_DESC_END; } static void efx_sdio_adma_set_desc(struct efx_adma_desc *desc, u32 addr, u16 len, u16 attr) { desc->attr = attr; desc->len = len; desc->addr = addr; } int efx_sdio_adma_table_pre(struct efx_sdio_host *host, struct mmc_data *data) { struct efx_adma_desc *desc; struct scatterlist *sg; dma_addr_t addr, align_addr; u32 len, offset, align_len; int i, desc_count = 0; /* Init use_bounce flag */ host->use_bounce = false; /* Check if we need bounce buffer due to alignment requirements */ for_each_sg(data->sg, sg, data->sg_len, i) { addr = sg_dma_address(sg); len = sg_dma_len(sg); /* Check 4-byte alignment len requirement for DMA */ if (len & 0x3) { host->use_bounce = true; break; } } if (host->use_bounce) { /* Use bounce buffer for unaligned transfers */ if (!host->bounce_buffer) { dev_err(&host->pdev->dev, "Bounce buffer not available\n"); return -ENOMEM; } if (data->blksz * data->blocks > host->bounce_buffer_size) { dev_err(&host->pdev->dev, "Transfer too large for bounce buffer\n"); return -EINVAL; } /* Copy data to bounce buffer for write operations */ if (data->flags & MMC_DATA_WRITE) { struct scatterlist *sg; char *bounce_pos; int i; bounce_pos = host->bounce_buffer; for_each_sg(data->sg, sg, data->sg_len, i) { /* Copy data to bounce buffer */ memcpy(bounce_pos, sg_virt(sg), sg->length); bounce_pos += sg->length; } } /* Setup single descriptor for bounce buffer */ desc = host->adma_desc; efx_sdio_adma_set_desc(desc, host->bounce_dma, data->blksz * data->blocks, EFX_ADMA_DESC_VALID | EFX_ADMA_DESC_TRAN); efx_sdio_adma_mark_end(desc); desc_count = 1; } else { /*Not using bounce buffer */ /* Setup descriptors for scatter-gather list */ desc = host->adma_desc; for_each_sg(data->sg, sg, data->sg_len, i) { addr = sg_dma_address(sg); len = sg_dma_len(sg); offset = 0; while (len > 0) { align_addr = addr + offset; align_len = min(len, (u32)EFX_ADMA_MAX_LEN); if (desc_count >= (EFX_ADMA_TABLE_SZ / sizeof(struct efx_adma_desc))) { dev_err(&host->pdev->dev, "Too many ADMA descriptors\n"); return -EINVAL; } efx_sdio_adma_set_desc(&desc[desc_count], align_addr, align_len, EFX_ADMA_DESC_VALID | EFX_ADMA_DESC_TRAN); offset += align_len; len -= align_len; desc_count++; } } if (desc_count > 0) { efx_sdio_adma_mark_end(&desc[desc_count - 1]); } } if (desc_count == 0) { dev_err(&host->pdev->dev, "No ADMA descriptors created\n"); return -EINVAL; } return 0; } void efx_sdio_adma_table_post(struct efx_sdio_host *host, struct mmc_data *data) { /* If bounce buffer is used, copy data from bounce buffer for read operations */ if (data->flags & MMC_DATA_READ) { if (host->use_bounce) { struct scatterlist *sg; char *bounce_pos; int i; bounce_pos = host->bounce_buffer; dma_sync_single_for_cpu(&host->pdev->dev, host->bounce_dma, data->blksz * data->blocks, DMA_FROM_DEVICE); for_each_sg(data->sg, sg, data->sg_len, i) { /* Copy data from bounce buffer */ memcpy(sg_virt(sg), bounce_pos, sg->length); bounce_pos += sg->length; } } else { // Sync scatter-gather list for CPU from device dma_sync_sg_for_cpu(&host->pdev->dev, data->sg, data->sg_len, DMA_FROM_DEVICE); } } } void efx_sdio_prepare_dma(struct efx_sdio_host *host, struct mmc_data *data) { int ret; if (!host->use_dma || !data) { return; } // Can not do DMA on a block size is not 4-byte aligned if ( (data->blksz ) & 3) { dev_warn(&host->pdev->dev, "Data block size %u not 4-byte aligned, falling back to PIO\n", data->blksz); host->use_dma = false; return; } /* Map scatter-gather list for DMA */ ret = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); if (ret == 0) { dev_err(&host->pdev->dev, "Failed to map DMA scatter-gather list\n"); host->use_dma = false; return; } data->sg_len = ret; /* Setup ADMA descriptor table */ ret = efx_sdio_adma_table_pre(host, data); if (ret) { dev_err(&host->pdev->dev, "Failed to setup ADMA table: %d\n", ret); dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len, (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); host->use_dma = false; return; } /* Set ADMA system address */ efx_sdio_set_adma_addr(host, host->adma_desc_dma); } void efx_sdio_cleanup_dma(struct efx_sdio_host *host, struct mmc_data *data) { if (!host->use_dma || !data) { return; } /* Post-process ADMA table */ efx_sdio_adma_table_post(host, data); /* Unmap scatter-gather list */ dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len, (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); }