Files
efinix-linux/drivers/mmc/host/efx_emmc_dma.c
2026-08-09 22:42:42 -07:00

210 lines
4.9 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Efinix eMMC Host Controller DMA Support
*
* Copyright (C) 2025 Efinix, Inc.
* Author: Teoh Choon Zone <czteoh@efinixinc.com>
*/
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include "efx_emmc.h"
void efx_emmc_set_adma_addr(struct efx_emmc_host *host, dma_addr_t addr)
{
efx_emmc_writel(host, (u32)addr, EFX_EMMC_ADMA_SYS_ADDR_LOW);
efx_emmc_writel(host, 0, EFX_EMMC_ADMA_SYS_ADDR_HIGH);
}
static void efx_emmc_adma_mark_end(struct efx_adma_desc *desc)
{
desc->attr |= EFX_ADMA_DESC_END;
}
static void efx_emmc_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_emmc_adma_table_pre(struct efx_emmc_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;
bool use_bounce = false;
for_each_sg(data->sg, sg, data->sg_len, i) {
addr = sg_dma_address(sg);
len = sg_dma_len(sg);
if ((addr & 0x7) || (len & 0x7)) {
use_bounce = true;
break;
}
}
if (use_bounce) {
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;
}
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) {
memcpy(bounce_pos, sg_virt(sg), sg->length);
bounce_pos += sg->length;
}
}
desc = host->adma_desc;
efx_emmc_adma_set_desc(desc, host->bounce_dma,
data->blksz * data->blocks,
EFX_ADMA_DESC_VALID | EFX_ADMA_DESC_TRAN);
efx_emmc_adma_mark_end(desc);
desc_count = 1;
host->bounce_active = true;
host->bounce_used = data->blksz * data->blocks;
} else {
desc = host->adma_desc;
host->bounce_active = false;
host->bounce_used = 0;
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_emmc_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_emmc_adma_mark_end(&desc[desc_count - 1]);
}
}
if (desc_count == 0) {
dev_err(&host->pdev->dev, "No ADMA descriptors created\n");
return -EINVAL;
}
dma_sync_single_for_device(&host->pdev->dev, host->adma_desc_dma,
host->adma_desc_sz, DMA_TO_DEVICE);
return 0;
}
void efx_emmc_adma_table_post(struct efx_emmc_host *host, struct mmc_data *data)
{
dma_sync_single_for_cpu(&host->pdev->dev, host->adma_desc_dma,
host->adma_desc_sz, DMA_FROM_DEVICE);
if (host->bounce_buffer && (data->flags & MMC_DATA_READ)) {
struct scatterlist *sg;
char *bounce_pos;
int i;
unsigned int remaining;
if (!host->bounce_active)
goto out;
remaining = host->bounce_used;
bounce_pos = host->bounce_buffer;
for_each_sg(data->sg, sg, data->sg_len, i) {
unsigned int len = min(sg->length, remaining);
if (!len)
break;
memcpy(sg_virt(sg), bounce_pos, len);
bounce_pos += len;
remaining -= len;
}
}
out:
host->bounce_active = false;
host->bounce_used = 0;
}
void efx_emmc_prepare_dma(struct efx_emmc_host *host, struct mmc_data *data)
{
int ret;
if (!host->use_dma || !data) {
return;
}
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;
ret = efx_emmc_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;
}
efx_emmc_set_adma_addr(host, host->adma_desc_dma);
}
void efx_emmc_cleanup_dma(struct efx_emmc_host *host, struct mmc_data *data)
{
if (!host->use_dma || !data) {
return;
}
efx_emmc_adma_table_post(host, data);
dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
(data->flags & MMC_DATA_READ) ?
DMA_FROM_DEVICE : DMA_TO_DEVICE);
}