mmc: msm_sdcc: Fix scatterlist processing in PIO mode
sg_miter_next() does not place byte alignment restrictions on
sgl lengths. However, SDCC FIFO must be accessed in multiples
of 4 bytes, requiring rounding which can cause data corruption.
For example, an sgl of length 481 is decribed below:
struct scatterlist sgl;
sgl.page_link = 0xXXXX_XXXX;
sgl.length = 481;
sgl.offset = 3821;
First call to sg_miter_next():
Sgl length returned = (PAGE_SIZE - 3821) = 275 bytes
Driver rounds this length to multiple of 4 = 276 bytes
Next call to sg_miter_next():
Sgl length returned = (481 - 275) = 206 bytes
Driver rounds this length to 208 bytes
On a write, the extra byte written to the FIFO during the first
276 byte chunk is assumed valid. The next call writes 208 bytes,
but the last 3 bytes (276 + 208 - 481) including 1 valid byte,
are ignored, as the controller uses the MCI_DATA_LENGTH register
to figure out that it only needs to write a total of 481 bytes.
To fix the data corruption in cases as above, a 4 byte bounce
buffer is used realign buffer access to the FIFO, such that 4 byte
multiples are written until the last buffer chunk.
Note that in a simple case where all 481 bytes lie within a page,
the driver rounds the length to 484, but the MCI_DATA_LENGTH
register enusres only 481 bytes are actually written.
Change-Id: I164bae2df4857017b35857e465d753b9dc9edf6a
Signed-off-by: Oluwafemi Adeyemi <aadeyemi@codeaurora.org>
diff --git a/drivers/mmc/host/msm_sdcc.h b/drivers/mmc/host/msm_sdcc.h
index 319d721..8a728f2 100644
--- a/drivers/mmc/host/msm_sdcc.h
+++ b/drivers/mmc/host/msm_sdcc.h
@@ -281,9 +281,10 @@
};
struct msmsdcc_pio_data {
- struct scatterlist *sg;
- unsigned int sg_len;
- unsigned int sg_off;
+ struct sg_mapping_iter sg_miter;
+ char bounce_buf[4];
+ /* valid bytes in bounce_buf */
+ int bounce_buf_len;
};
struct msmsdcc_curr_req {
@@ -361,7 +362,7 @@
struct msmsdcc_sps_data sps;
bool is_dma_mode;
bool is_sps_mode;
- struct sg_mapping_iter sg_miter;
+ struct msmsdcc_pio_data pio;
#ifdef CONFIG_HAS_EARLYSUSPEND
struct early_suspend early_suspend;