mmc: mmci: stm32: use a buffer for unaligned DMA requests
[ Upstream commit 970dc9c11a17994ab878016b536612ab00d1441d ] In SDIO mode, the sg list for requests can be unaligned with what the STM32 SDMMC internal DMA can support. In that case, instead of failing, use a temporary bounce buffer to copy from/to the sg list. This buffer is limited to 1MB. But for that we need to also limit max_req_size to 1MB. It has not shown any throughput penalties for SD-cards or eMMC. Signed-off-by: Yann Gautier <yann.gautier@foss.st.com> Link: https://lore.kernel.org/r/20220328145114.334577-1-yann.gautier@foss.st.com Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> Stable-dep-of: 6b1ba3f9040b ("mmc: mmci: stm32: fix DMA API overlapping mappings warning") Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
parent
1c57ab2a45
commit
8834b57bd1
1 changed files with 71 additions and 17 deletions
|
@ -43,6 +43,9 @@ struct sdmmc_lli_desc {
|
||||||
struct sdmmc_idma {
|
struct sdmmc_idma {
|
||||||
dma_addr_t sg_dma;
|
dma_addr_t sg_dma;
|
||||||
void *sg_cpu;
|
void *sg_cpu;
|
||||||
|
dma_addr_t bounce_dma_addr;
|
||||||
|
void *bounce_buf;
|
||||||
|
bool use_bounce_buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sdmmc_dlyb {
|
struct sdmmc_dlyb {
|
||||||
|
@ -54,6 +57,8 @@ struct sdmmc_dlyb {
|
||||||
static int sdmmc_idma_validate_data(struct mmci_host *host,
|
static int sdmmc_idma_validate_data(struct mmci_host *host,
|
||||||
struct mmc_data *data)
|
struct mmc_data *data)
|
||||||
{
|
{
|
||||||
|
struct sdmmc_idma *idma = host->dma_priv;
|
||||||
|
struct device *dev = mmc_dev(host->mmc);
|
||||||
struct scatterlist *sg;
|
struct scatterlist *sg;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
@ -61,41 +66,69 @@ static int sdmmc_idma_validate_data(struct mmci_host *host,
|
||||||
* idma has constraints on idmabase & idmasize for each element
|
* idma has constraints on idmabase & idmasize for each element
|
||||||
* excepted the last element which has no constraint on idmasize
|
* excepted the last element which has no constraint on idmasize
|
||||||
*/
|
*/
|
||||||
|
idma->use_bounce_buffer = false;
|
||||||
for_each_sg(data->sg, sg, data->sg_len - 1, i) {
|
for_each_sg(data->sg, sg, data->sg_len - 1, i) {
|
||||||
if (!IS_ALIGNED(sg->offset, sizeof(u32)) ||
|
if (!IS_ALIGNED(sg->offset, sizeof(u32)) ||
|
||||||
!IS_ALIGNED(sg->length, SDMMC_IDMA_BURST)) {
|
!IS_ALIGNED(sg->length, SDMMC_IDMA_BURST)) {
|
||||||
dev_err(mmc_dev(host->mmc),
|
dev_dbg(mmc_dev(host->mmc),
|
||||||
"unaligned scatterlist: ofst:%x length:%d\n",
|
"unaligned scatterlist: ofst:%x length:%d\n",
|
||||||
data->sg->offset, data->sg->length);
|
data->sg->offset, data->sg->length);
|
||||||
return -EINVAL;
|
goto use_bounce_buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IS_ALIGNED(sg->offset, sizeof(u32))) {
|
if (!IS_ALIGNED(sg->offset, sizeof(u32))) {
|
||||||
dev_err(mmc_dev(host->mmc),
|
dev_dbg(mmc_dev(host->mmc),
|
||||||
"unaligned last scatterlist: ofst:%x length:%d\n",
|
"unaligned last scatterlist: ofst:%x length:%d\n",
|
||||||
data->sg->offset, data->sg->length);
|
data->sg->offset, data->sg->length);
|
||||||
return -EINVAL;
|
goto use_bounce_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
use_bounce_buffer:
|
||||||
|
if (!idma->bounce_buf) {
|
||||||
|
idma->bounce_buf = dmam_alloc_coherent(dev,
|
||||||
|
host->mmc->max_req_size,
|
||||||
|
&idma->bounce_dma_addr,
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!idma->bounce_buf) {
|
||||||
|
dev_err(dev, "Unable to map allocate DMA bounce buffer.\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idma->use_bounce_buffer = true;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int _sdmmc_idma_prep_data(struct mmci_host *host,
|
static int _sdmmc_idma_prep_data(struct mmci_host *host,
|
||||||
struct mmc_data *data)
|
struct mmc_data *data)
|
||||||
{
|
{
|
||||||
int n_elem;
|
struct sdmmc_idma *idma = host->dma_priv;
|
||||||
|
|
||||||
n_elem = dma_map_sg(mmc_dev(host->mmc),
|
if (idma->use_bounce_buffer) {
|
||||||
data->sg,
|
if (data->flags & MMC_DATA_WRITE) {
|
||||||
data->sg_len,
|
unsigned int xfer_bytes = data->blksz * data->blocks;
|
||||||
mmc_get_dma_dir(data));
|
|
||||||
|
|
||||||
if (!n_elem) {
|
sg_copy_to_buffer(data->sg, data->sg_len,
|
||||||
dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n");
|
idma->bounce_buf, xfer_bytes);
|
||||||
return -EINVAL;
|
dma_wmb();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int n_elem;
|
||||||
|
|
||||||
|
n_elem = dma_map_sg(mmc_dev(host->mmc),
|
||||||
|
data->sg,
|
||||||
|
data->sg_len,
|
||||||
|
mmc_get_dma_dir(data));
|
||||||
|
|
||||||
|
if (!n_elem) {
|
||||||
|
dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,8 +145,19 @@ static int sdmmc_idma_prep_data(struct mmci_host *host,
|
||||||
static void sdmmc_idma_unprep_data(struct mmci_host *host,
|
static void sdmmc_idma_unprep_data(struct mmci_host *host,
|
||||||
struct mmc_data *data, int err)
|
struct mmc_data *data, int err)
|
||||||
{
|
{
|
||||||
dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
|
struct sdmmc_idma *idma = host->dma_priv;
|
||||||
mmc_get_dma_dir(data));
|
|
||||||
|
if (idma->use_bounce_buffer) {
|
||||||
|
if (data->flags & MMC_DATA_READ) {
|
||||||
|
unsigned int xfer_bytes = data->blksz * data->blocks;
|
||||||
|
|
||||||
|
sg_copy_from_buffer(data->sg, data->sg_len,
|
||||||
|
idma->bounce_buf, xfer_bytes);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
|
||||||
|
mmc_get_dma_dir(data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sdmmc_idma_setup(struct mmci_host *host)
|
static int sdmmc_idma_setup(struct mmci_host *host)
|
||||||
|
@ -137,6 +181,8 @@ static int sdmmc_idma_setup(struct mmci_host *host)
|
||||||
host->mmc->max_segs = SDMMC_LLI_BUF_LEN /
|
host->mmc->max_segs = SDMMC_LLI_BUF_LEN /
|
||||||
sizeof(struct sdmmc_lli_desc);
|
sizeof(struct sdmmc_lli_desc);
|
||||||
host->mmc->max_seg_size = host->variant->stm32_idmabsize_mask;
|
host->mmc->max_seg_size = host->variant->stm32_idmabsize_mask;
|
||||||
|
|
||||||
|
host->mmc->max_req_size = SZ_1M;
|
||||||
} else {
|
} else {
|
||||||
host->mmc->max_segs = 1;
|
host->mmc->max_segs = 1;
|
||||||
host->mmc->max_seg_size = host->mmc->max_req_size;
|
host->mmc->max_seg_size = host->mmc->max_req_size;
|
||||||
|
@ -154,8 +200,16 @@ static int sdmmc_idma_start(struct mmci_host *host, unsigned int *datactrl)
|
||||||
struct scatterlist *sg;
|
struct scatterlist *sg;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (!host->variant->dma_lli || data->sg_len == 1) {
|
if (!host->variant->dma_lli || data->sg_len == 1 ||
|
||||||
writel_relaxed(sg_dma_address(data->sg),
|
idma->use_bounce_buffer) {
|
||||||
|
u32 dma_addr;
|
||||||
|
|
||||||
|
if (idma->use_bounce_buffer)
|
||||||
|
dma_addr = idma->bounce_dma_addr;
|
||||||
|
else
|
||||||
|
dma_addr = sg_dma_address(data->sg);
|
||||||
|
|
||||||
|
writel_relaxed(dma_addr,
|
||||||
host->base + MMCI_STM32_IDMABASE0R);
|
host->base + MMCI_STM32_IDMABASE0R);
|
||||||
writel_relaxed(MMCI_STM32_IDMAEN,
|
writel_relaxed(MMCI_STM32_IDMAEN,
|
||||||
host->base + MMCI_STM32_IDMACTRLR);
|
host->base + MMCI_STM32_IDMACTRLR);
|
||||||
|
|
Loading…
Add table
Reference in a new issue