The driver supports UHS-I bus speed modes: SDR25, DDR50, and SDR104. By default, the controller operates in SDR25 mode. Higher-speed modes can be enabled via the Device Tree by adding the following properties to the SDIO node: - sd-uhs-ddr50 - sd-uhs-sdr104 When these properties are present, the driver negotiates the highest supported UHS mode with the card and host. Signed-off-by: Swee Aun Khor <sakhor@efinixinc.com>
228 lines
6.2 KiB
C
228 lines
6.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Efinix SDIO Host Controller DMA Support
|
|
*
|
|
* Copyright (C) 2026 Efinix, Inc.
|
|
* Author: Khor Swee Aun <sakhor@efinixinc.com>
|
|
*/
|
|
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
|
|
#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);
|
|
}
|