* [PATCH 1/4] mmc: sunxi: Report manual stop errors correctly
2026-11-03 18:30 [PATCH 0/4] mmc: sunxi: fixes and preparation for A523 UHS support Jernej Skrabec
@ 2026-11-03 18:30 ` Jernej Skrabec
2026-11-03 18:30 ` [PATCH 2/4] mmc: sunxi: Set the IDMA DMA mask Jernej Skrabec
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Jernej Skrabec @ 2026-11-03 18:30 UTC (permalink / raw)
To: Ulf Hansson
Cc: Chen-Yu Tsai, Samuel Holland, Andre Przywara, Anfernee Cao,
linux-mmc, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
When a data transfer fails, sunxi_mmc_finalize_request() sets the stop
command error to -ETIMEDOUT and the threaded interrupt handler sends a
manual stop command. This has two problems:
- If the manual stop command succeeds, its response is stored, but the
provisional -ETIMEDOUT error is left in place. The request thus
completes with a stop command error that didn't happen, and as the
block layer only checks the card status in the stop response when
the stop command didn't fail, errors reported there are ignored.
- If the manual stop command fails, -ETIMEDOUT is stored in the
response instead of in the error field. The block layer then decodes
it as an R1 card status with random error bits set.
Store the error of a failed stop command in the error field, and clear
the provisional error when the manual stop command succeeds.
Fixes: 3cbcb16095f9 ("mmc: sunxi: Add driver for SD/MMC hosts found on Allwinner sunxi SoCs")
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/mmc/host/sunxi-mmc.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c
index fe4c0f6d73f3..9744b6b860ac 100644
--- a/drivers/mmc/host/sunxi-mmc.c
+++ b/drivers/mmc/host/sunxi-mmc.c
@@ -470,10 +470,12 @@ static void sunxi_mmc_send_manual_stop(struct sunxi_mmc_host *host,
if (!(ri & SDXC_COMMAND_DONE) || (ri & SDXC_INTERRUPT_ERROR_BIT)) {
dev_err(mmc_dev(host->mmc), "send stop command failed\n");
if (req->stop)
- req->stop->resp[0] = -ETIMEDOUT;
+ req->stop->error = -ETIMEDOUT;
} else {
- if (req->stop)
+ if (req->stop) {
req->stop->resp[0] = mmc_readl(host, REG_RESP0);
+ req->stop->error = 0;
+ }
}
mmc_writel(host, REG_RINTR, 0xffff);
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 2/4] mmc: sunxi: Set the IDMA DMA mask
2026-11-03 18:30 [PATCH 0/4] mmc: sunxi: fixes and preparation for A523 UHS support Jernej Skrabec
2026-11-03 18:30 ` [PATCH 1/4] mmc: sunxi: Report manual stop errors correctly Jernej Skrabec
@ 2026-11-03 18:30 ` Jernej Skrabec
2026-11-03 18:30 ` [PATCH 3/4] mmc: sunxi: Build IDMA descriptors from mapped SG list Jernej Skrabec
2026-11-03 18:30 ` [PATCH 4/4] mmc: sunxi: Distinguish CRC and timeout errors Jernej Skrabec
3 siblings, 0 replies; 5+ messages in thread
From: Jernej Skrabec @ 2026-11-03 18:30 UTC (permalink / raw)
To: Ulf Hansson
Cc: Chen-Yu Tsai, Samuel Holland, Andre Przywara, Anfernee Cao,
linux-mmc, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
Controllers with a non-zero idma_des_shift (D1, A100 and H616) store
buffer and descriptor addresses shifted right by two, so they can
address 34 bits of physical memory. The driver never sets a DMA mask,
so the default 32-bit mask applies. On boards with DRAM above 4 GiB,
like H616 boards with 4 GiB of DRAM starting at 0x40000000, every
buffer above that limit is needlessly bounced through SWIOTLB.
Set the DMA mask to the range the controller can address. For the
other controllers, it stays at 32 bits.
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/mmc/host/sunxi-mmc.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c
index 9744b6b860ac..479420071c2d 100644
--- a/drivers/mmc/host/sunxi-mmc.c
+++ b/drivers/mmc/host/sunxi-mmc.c
@@ -1385,6 +1385,11 @@ static int sunxi_mmc_probe(struct platform_device *pdev)
if (ret)
return ret;
+ ret = dma_set_mask_and_coherent(&pdev->dev,
+ DMA_BIT_MASK(32 + host->cfg->idma_des_shift));
+ if (ret)
+ goto error_disable_mmc;
+
host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
&host->sg_dma, GFP_KERNEL);
if (!host->sg_cpu)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 3/4] mmc: sunxi: Build IDMA descriptors from mapped SG list
2026-11-03 18:30 [PATCH 0/4] mmc: sunxi: fixes and preparation for A523 UHS support Jernej Skrabec
2026-11-03 18:30 ` [PATCH 1/4] mmc: sunxi: Report manual stop errors correctly Jernej Skrabec
2026-11-03 18:30 ` [PATCH 2/4] mmc: sunxi: Set the IDMA DMA mask Jernej Skrabec
@ 2026-11-03 18:30 ` Jernej Skrabec
2026-11-03 18:30 ` [PATCH 4/4] mmc: sunxi: Distinguish CRC and timeout errors Jernej Skrabec
3 siblings, 0 replies; 5+ messages in thread
From: Jernej Skrabec @ 2026-11-03 18:30 UTC (permalink / raw)
To: Ulf Hansson
Cc: Chen-Yu Tsai, Samuel Holland, Andre Przywara, Anfernee Cao,
linux-mmc, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
dma_map_sg() may merge scatterlist entries and returns the number of
resulting DMA segments. The DMA API requires using only that many
segments, with sg_dma_address() and sg_dma_len() (see
Documentation/core-api/dma-api.rst).
The driver instead builds one descriptor per original entry and uses
its CPU side length. Build the descriptors from the mapped segments
instead, and split segments longer than one descriptor can handle.
This can't overflow the descriptor table, as each original entry fits
into a single descriptor.
Also check the alignment of the mapped segments, and unmap the
scatterlist when it is misaligned instead of leaking the mapping.
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/mmc/host/sunxi-mmc.c | 91 +++++++++++++++++++++---------------
1 file changed, 54 insertions(+), 37 deletions(-)
diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c
index 479420071c2d..b875ef4c2b79 100644
--- a/drivers/mmc/host/sunxi-mmc.c
+++ b/drivers/mmc/host/sunxi-mmc.c
@@ -356,36 +356,49 @@ static int sunxi_mmc_init_host(struct sunxi_mmc_host *host)
}
static void sunxi_mmc_init_idma_des(struct sunxi_mmc_host *host,
- struct mmc_data *data)
+ struct mmc_data *data, int nents)
{
struct sunxi_idma_des *pdes = (struct sunxi_idma_des *)host->sg_cpu;
dma_addr_t next_desc = host->sg_dma;
- int i, max_len = (1 << host->cfg->idma_des_size_bits);
-
- for (i = 0; i < data->sg_len; i++) {
- pdes[i].config = cpu_to_le32(SDXC_IDMAC_DES0_CH |
- SDXC_IDMAC_DES0_OWN |
- SDXC_IDMAC_DES0_DIC);
-
- if (data->sg[i].length == max_len)
- pdes[i].buf_size = 0; /* 0 == max_len */
- else
- pdes[i].buf_size = cpu_to_le32(data->sg[i].length);
-
- next_desc += sizeof(struct sunxi_idma_des);
- pdes[i].buf_addr_ptr1 =
- cpu_to_le32(sg_dma_address(&data->sg[i]) >>
- host->cfg->idma_des_shift);
- pdes[i].buf_addr_ptr2 =
- cpu_to_le32(next_desc >>
- host->cfg->idma_des_shift);
+ unsigned int max_len = 1 << host->cfg->idma_des_size_bits;
+ unsigned int i, d = 0;
+ struct scatterlist *sg;
+
+ /* dma_map_sg() may merge entries beyond the descriptor size. */
+ for_each_sg(data->sg, sg, nents, i) {
+ dma_addr_t addr = sg_dma_address(sg);
+ unsigned int len = sg_dma_len(sg);
+
+ while (len) {
+ unsigned int chunk = min(len, max_len);
+
+ pdes[d].config = cpu_to_le32(SDXC_IDMAC_DES0_CH |
+ SDXC_IDMAC_DES0_OWN |
+ SDXC_IDMAC_DES0_DIC);
+
+ if (chunk == max_len)
+ pdes[d].buf_size = 0; /* 0 == max_len */
+ else
+ pdes[d].buf_size = cpu_to_le32(chunk);
+
+ next_desc += sizeof(struct sunxi_idma_des);
+ pdes[d].buf_addr_ptr1 =
+ cpu_to_le32(addr >> host->cfg->idma_des_shift);
+ pdes[d].buf_addr_ptr2 =
+ cpu_to_le32(next_desc >>
+ host->cfg->idma_des_shift);
+
+ addr += chunk;
+ len -= chunk;
+ d++;
+ }
}
pdes[0].config |= cpu_to_le32(SDXC_IDMAC_DES0_FD);
- pdes[i - 1].config |= cpu_to_le32(SDXC_IDMAC_DES0_LD |
+ pdes[d - 1].config |= cpu_to_le32(SDXC_IDMAC_DES0_LD |
SDXC_IDMAC_DES0_ER);
- pdes[i - 1].config &= cpu_to_le32(~SDXC_IDMAC_DES0_DIC);
- pdes[i - 1].buf_addr_ptr2 = 0;
+ pdes[d - 1].config &= cpu_to_le32(~SDXC_IDMAC_DES0_DIC);
+ pdes[d - 1].buf_addr_ptr2 = 0;
/*
* Avoid the io-store starting the idmac hitting io-mem before the
@@ -397,7 +410,7 @@ static void sunxi_mmc_init_idma_des(struct sunxi_mmc_host *host,
static int sunxi_mmc_map_dma(struct sunxi_mmc_host *host,
struct mmc_data *data)
{
- u32 i, dma_len;
+ int i, dma_len;
struct scatterlist *sg;
dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
@@ -407,24 +420,28 @@ static int sunxi_mmc_map_dma(struct sunxi_mmc_host *host,
return -ENOMEM;
}
- for_each_sg(data->sg, sg, data->sg_len, i) {
- if (sg->offset & 3 || sg->length & 3) {
+ for_each_sg(data->sg, sg, dma_len, i) {
+ dma_addr_t addr = sg_dma_address(sg);
+
+ if (addr & 3 || sg_dma_len(sg) & 3) {
dev_err(mmc_dev(host->mmc),
- "unaligned scatterlist: os %x length %d\n",
- sg->offset, sg->length);
+ "unaligned DMA segment: address %pad length %u\n",
+ &addr, sg_dma_len(sg));
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, mmc_get_dma_dir(data));
return -EINVAL;
}
}
- return 0;
+ return dma_len;
}
static void sunxi_mmc_start_dma(struct sunxi_mmc_host *host,
- struct mmc_data *data)
+ struct mmc_data *data, int nents)
{
u32 rval;
- sunxi_mmc_init_idma_des(host, data);
+ sunxi_mmc_init_idma_des(host, data, nents);
rval = mmc_readl(host, REG_GCTRL);
rval |= SDXC_DMA_ENABLE_BIT;
@@ -1016,7 +1033,7 @@ static void sunxi_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
u32 imask = SDXC_INTERRUPT_ERROR_BIT;
u32 cmd_val = SDXC_START | (cmd->opcode & 0x3f);
bool wait_dma = host->wait_dma;
- int ret;
+ int nents = 0;
/* Check for set_ios errors (should never happen) */
if (host->ferror) {
@@ -1026,11 +1043,11 @@ static void sunxi_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
}
if (data) {
- ret = sunxi_mmc_map_dma(host, data);
- if (ret < 0) {
+ nents = sunxi_mmc_map_dma(host, data);
+ if (nents < 0) {
dev_err(mmc_dev(mmc), "map DMA failed\n");
- cmd->error = ret;
- data->error = ret;
+ cmd->error = nents;
+ data->error = nents;
mmc_request_done(mmc, mrq);
return;
}
@@ -1091,7 +1108,7 @@ static void sunxi_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
if (data) {
mmc_writel(host, REG_BLKSZ, data->blksz);
mmc_writel(host, REG_BCNTR, data->blksz * data->blocks);
- sunxi_mmc_start_dma(host, data);
+ sunxi_mmc_start_dma(host, data, nents);
}
host->mrq = mrq;
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 4/4] mmc: sunxi: Distinguish CRC and timeout errors
2026-11-03 18:30 [PATCH 0/4] mmc: sunxi: fixes and preparation for A523 UHS support Jernej Skrabec
` (2 preceding siblings ...)
2026-11-03 18:30 ` [PATCH 3/4] mmc: sunxi: Build IDMA descriptors from mapped SG list Jernej Skrabec
@ 2026-11-03 18:30 ` Jernej Skrabec
3 siblings, 0 replies; 5+ messages in thread
From: Jernej Skrabec @ 2026-11-03 18:30 UTC (permalink / raw)
To: Ulf Hansson
Cc: Chen-Yu Tsai, Samuel Holland, Andre Przywara, Anfernee Cao,
linux-mmc, linux-arm-kernel, linux-sunxi, linux-kernel,
Jernej Skrabec
The driver reports every command and data error as -ETIMEDOUT. The MMC
core however relies on -EILSEQ to recognize CRC errors, which mean
that the sampling point drifted, and flags the host for retuning when
it sees one. With every error reported as a timeout, retuning is never
triggered.
Report response and data CRC, start bit and end bit errors as -EILSEQ
and keep reporting timeouts as -ETIMEDOUT, attributed to the command or
data phase that actually failed. Report any other error as -EIO.
Currently supported SoCs don't implement tuning, so this doesn't change
much for them, but it is needed for the upcoming A523 delay-chain
tuning support.
Signed-off-by: Jernej Skrabec <jernej.skrabec@gmail.com>
---
drivers/mmc/host/sunxi-mmc.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c
index b875ef4c2b79..eb1b7883a715 100644
--- a/drivers/mmc/host/sunxi-mmc.c
+++ b/drivers/mmc/host/sunxi-mmc.c
@@ -537,13 +537,29 @@ static irqreturn_t sunxi_mmc_finalize_request(struct sunxi_mmc_host *host)
if (host->int_sum & SDXC_INTERRUPT_ERROR_BIT) {
sunxi_mmc_dump_errinfo(host);
- mrq->cmd->error = -ETIMEDOUT;
+
+ if (host->int_sum &
+ (SDXC_RESP_ERROR | SDXC_RESP_CRC_ERROR))
+ mrq->cmd->error = -EILSEQ;
+ else if (host->int_sum & SDXC_RESP_TIMEOUT)
+ mrq->cmd->error = -ETIMEDOUT;
if (data) {
- data->error = -ETIMEDOUT;
+ if (host->int_sum &
+ (SDXC_DATA_CRC_ERROR | SDXC_START_BIT_ERROR |
+ SDXC_END_BIT_ERROR))
+ data->error = -EILSEQ;
+ else if (host->int_sum &
+ (SDXC_DATA_TIMEOUT | SDXC_FIFO_RUN_ERROR |
+ SDXC_HARD_WARE_LOCKED))
+ data->error = -ETIMEDOUT;
+
host->manual_stop_mrq = mrq;
}
+ if (!mrq->cmd->error && (!data || !data->error))
+ mrq->cmd->error = -EIO;
+
if (mrq->stop)
mrq->stop->error = -ETIMEDOUT;
} else {
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread