Hello all.
I working with Allwinner A13 microprocessor. I use spi-nor flash on device and I want storing some information
belong to about of device. So I want use spi-nor flash as usb mass storage. And for this I ran tiny linux
(kernel version is 5.12-rc3)
on spi-nor flash and I can use spi-nor flash as mass storage(~5Mb).
But I got some errors like below:
[ 9557.574285] spi_master spi0: spi0.0: timeout transferring 64 bytes@1000000Hz for 110(100)ms
[ 9557.592685] spi-nor spi0.0: SPI transfer failed: -110
[ 9557.601985] spi_master spi0: failed to transfer one message from queue
[ 9557.613673] jffs2: Write of 68 bytes at 0x0045d264 failed. returned -110, retlen 0
[ 9557.631970] jffs2: Not marking the space at 0x0045d264 as dirty because the flash driver returned retlen zero
So I some researched and I found repo of hramrach for spi working with
dma(https://github.com/hramrach/linux-sunxi
branch is: sunxi-spi-pio-dma ).
I merged dma works of hramrach to my kernel 5.12-rc3 and unfortunately I got same error as below.
[ 198.747427] Buffer I/O error on dev loop0, logical block 131, lost async page write
[ 198.864358] spi_master spi0: spi0.0: timeout transferring 64 bytes@1000000Hz for 101250(100)ms
[ 198.877517] spi-nor spi0.0: SPI transfer failed: -110
[ 198.886939] spi_master spi0: failed to transfer one message from queue
[ 198.897785] jffs2: Write of 98 bytes at 0x0043a7c0 failed. returned -110, retlen 0
[ 198.909638] jffs2: Not marking the space at 0x0043a7c0 as dirty because the flash driver returned retlen zero
[ 199.034358] spi_master spi0: spi0.0: timeout transferring 64 bytes@1000000Hz for 101080(100)ms
[ 199.047536] spi-nor spi0.0: SPI transfer failed: -110
[ 199.056950] spi_master spi0: failed to transfer one message from queue
[ 199.067793] jffs2: Write of 98 bytes at 0x0043a7c0 failed. returned -110, retlen 0
[ 199.079704] jffs2: Not marking the space at 0x0043a7c0 as dirty because the flash driver returned retlen zero
[ 199.098175] loop: Write error at byte offset 68096, length 512.
The difference seems between before and after patch is about time. But unfortunately error still occurs.
Generally when ran `mkdosfs` command and when I tried transfer file big size(~2MB) I take error.
My diff patch is:
diff --color -Nuar linux/dmaengine.h linux-new/dmaengine.h
--- linux/dmaengine.h 2021-06-17 13:35:46.033174969 +0300
+++ linux-new/dmaengine.h 2021-06-17 13:35:33.088291303 +0300
@@ -1484,7 +1484,8 @@
struct dma_chan *__dma_request_channel(const dma_cap_mask_t *mask,
dma_filter_fn fn, void *fn_param,
struct device_node *np);
-
+struct dma_chan *dma_request_slave_channel_reason(struct device *dev,
+ const char *name);
struct dma_chan *dma_request_chan(struct device *dev, const char *name);
struct dma_chan *dma_request_chan_by_mask(const dma_cap_mask_t *mask);
@@ -1513,6 +1514,11 @@
{
return NULL;
}
+struct dma_chan *dma_request_slave_channel_reason(struct device *dev,
+ const char *name);
+{
+ return ERR_PTR(-ENODEV);
+}
static inline struct dma_chan *dma_request_chan(struct device *dev,
const char *name)
{
diff --color -Nuar spi/spi-sun4i.c spi-new/spi-sun4i.c
--- spi/spi-sun4i.c 2021-06-17 13:17:52.253811064 +0300
+++ spi-new/spi-sun4i.c 2021-06-17 13:16:28.868703981 +0300
@@ -15,6 +15,8 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
#include <linux/spi/spi.h>
@@ -30,6 +32,7 @@
#define SUN4I_CTL_CPHA BIT(2)
#define SUN4I_CTL_CPOL BIT(3)
#define SUN4I_CTL_CS_ACTIVE_LOW BIT(4)
+#define SUN4I_CTL_DMAMC_DEDICATED BIT(5)
#define SUN4I_CTL_LMTF BIT(6)
#define SUN4I_CTL_TF_RST BIT(8)
#define SUN4I_CTL_RF_RST BIT(9)
@@ -49,6 +52,8 @@
#define SUN4I_INT_STA_REG 0x10
#define SUN4I_DMA_CTL_REG 0x14
+#define SUN4I_DMA_CTL_RF_READY BIT(0)
+#define SUN4I_DMA_CTL_TF_NOT_FULL BIT(10)
#define SUN4I_WAIT_REG 0x18
@@ -85,6 +90,7 @@
const u8 *tx_buf;
u8 *rx_buf;
int len;
+ bool has_dma;
};
static inline u32 sun4i_spi_read(struct sun4i_spi *sspi, u32 reg)
@@ -159,6 +165,14 @@
}
}
+static bool sun4i_spi_can_dma(struct spi_master *master,
+ struct spi_device *spi,
+ struct spi_transfer *tfr)
+{
+ struct sun4i_spi *sspi = spi_master_get_devdata(spi->master);
+ return sspi->has_dma && tfr->len >= SUN4I_FIFO_DEPTH;
+}
+
static void sun4i_spi_set_cs(struct spi_device *spi, bool enable)
{
struct sun4i_spi *sspi = spi_master_get_devdata(spi->master);
@@ -206,18 +220,22 @@
struct spi_transfer *tfr)
{
struct sun4i_spi *sspi = spi_master_get_devdata(master);
- unsigned int mclk_rate, div, timeout;
- unsigned int start, end, tx_time;
+ struct dma_async_tx_descriptor *desc_tx = NULL, *desc_rx = NULL;
+ // unsigned int mclk_rate, div, timeout;
+ unsigned int speed, mclk_rate, div, timeout;
+ // unsigned int start, end, tx_time;
unsigned int tx_len = 0;
+ u32 reg, trigger = 0;
int ret = 0;
- u32 reg;
+ unsigned int start, end, tx_time;
+ //u32 reg;
/* We don't support transfer larger than the FIFO */
if (tfr->len > SUN4I_MAX_XFER_SIZE)
- return -EMSGSIZE;
+ return -EINVAL; //-EMSGSIZE;
- if (tfr->tx_buf && tfr->len >= SUN4I_MAX_XFER_SIZE)
- return -EMSGSIZE;
+ /*if (tfr->tx_buf && tfr->len >= SUN4I_MAX_XFER_SIZE)
+ return -EMSGSIZE; */
reinit_completion(&sspi->done);
sspi->tx_buf = tfr->tx_buf;
@@ -227,7 +245,6 @@
/* Clear pending interrupts */
sun4i_spi_write(sspi, SUN4I_INT_STA_REG, ~0);
-
reg = sun4i_spi_read(sspi, SUN4I_CTL_REG);
/* Reset FIFOs */
@@ -265,10 +282,18 @@
sun4i_spi_write(sspi, SUN4I_CTL_REG, reg);
+ speed = spi->max_speed_hz;
+ if(!speed) {
+ speed = 100000;
+ dev_warn(&spi->dev, "SPI speed not set. Using %uHz.", speed);
+ }
+
/* Ensure that we have a parent clock fast enough */
mclk_rate = clk_get_rate(sspi->mclk);
- if (mclk_rate < (2 * tfr->speed_hz)) {
- clk_set_rate(sspi->mclk, 2 * tfr->speed_hz);
+ //if (mclk_rate < (2 * tfr->speed_hz)) {
+ // clk_set_rate(sspi->mclk, 2 * tfr->speed_hz);
+ if (mclk_rate < (2 * speed)) {
+ clk_set_rate(sspi->mclk, 2 * speed);
mclk_rate = clk_get_rate(sspi->mclk);
}
@@ -286,14 +311,16 @@
* First try CDR2, and if we can't reach the expected
* frequency, fall back to CDR1.
*/
- div = mclk_rate / (2 * tfr->speed_hz);
+ // div = mclk_rate / (2 * tfr->speed_hz);
+ div = mclk_rate / (2 * speed);
if (div <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) {
if (div > 0)
div--;
reg = SUN4I_CLK_CTL_CDR2(div) | SUN4I_CLK_CTL_DRS;
} else {
- div = ilog2(mclk_rate) - ilog2(tfr->speed_hz);
+ // div = ilog2(mclk_rate) - ilog2(tfr->speed_hz);
+ div = ilog2(mclk_rate) - ilog2(speed);
reg = SUN4I_CLK_CTL_CDR1(div);
}
@@ -312,33 +339,104 @@
* Filling the FIFO fully causes timeout for some reason
* at least on spi2 on A10s
*/
- sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH - 1);
+ // sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH - 1);
/* Enable the interrupts */
- sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TC |
- SUN4I_INT_CTL_RF_F34);
- /* Only enable Tx FIFO interrupt if we really need it */
- if (tx_len > SUN4I_FIFO_DEPTH)
- sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TF_E34);
+ // sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TC | SUN4I_INT_CTL_RF_F34);
+ // /* Only enable Tx FIFO interrupt if we really need it */
+ // if (tx_len > SUN4I_FIFO_DEPTH)
+ // sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TF_E34);
+
+ sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TC);
+
+ if (sun4i_spi_can_dma(master, spi, tfr)) {
+ dev_dbg(&sspi->master->dev, "Using DMA mode for transfer\n");
+
+ if (sspi->tx_buf) {
+ desc_tx = dmaengine_prep_slave_sg(master->dma_tx,
+ tfr->tx_sg.sgl, tfr->tx_sg.nents,
+ DMA_TO_DEVICE,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc_tx) {
+ dev_err(&sspi->master->dev,
+ "Couldn't prepare dma slave\n");
+ return -EIO;
+ }
+
+ trigger |= SUN4I_DMA_CTL_TF_NOT_FULL;
+
+ dmaengine_submit(desc_tx);
+ dma_async_issue_pending(master->dma_tx);
+
+ }
+
+ if (sspi->rx_buf) {
+ desc_rx = dmaengine_prep_slave_sg(master->dma_rx,
+ tfr->rx_sg.sgl, tfr->rx_sg.nents,
+ DMA_FROM_DEVICE,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc_rx) {
+ dev_err(&sspi->master->dev,
+ "Couldn't prepare dma slave\n");
+ return -EIO;
+ }
+
+ trigger |= SUN4I_DMA_CTL_RF_READY;
+
+ dmaengine_submit(desc_rx);
+ dma_async_issue_pending(master->dma_rx);
+ }
+
+ /* Enable Dedicated DMA requests */
+ reg = sun4i_spi_read(sspi, SUN4I_CTL_REG);
+ reg |= SUN4I_CTL_DMAMC_DEDICATED;
+ sun4i_spi_write(sspi, SUN4I_CTL_REG, reg);
+ sun4i_spi_write(sspi, SUN4I_DMA_CTL_REG, trigger);
+ } else {
+ dev_dbg(&sspi->master->dev, "Using PIO mode for transfer\n");
+
+ /* Enable the FIFO interrupts */
+ sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_RF_F34);
+ /* Only enable Tx FIFO interrupt if we really need it */
+ if (tx_len > SUN4I_FIFO_DEPTH)
+ sun4i_spi_enable_interrupt(sspi, SUN4I_INT_CTL_TF_E34);
+
+ /* Disable DMA requests */
+ reg = sun4i_spi_read(sspi, SUN4I_CTL_REG);
+ sun4i_spi_write(sspi, SUN4I_CTL_REG,
+ reg & ~SUN4I_CTL_DMAMC_DEDICATED);
+ sun4i_spi_write(sspi, SUN4I_DMA_CTL_REG, 0);
+
+ /* Fill the TX FIFO */
+ sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH - 1);
+ }
/* Start the transfer */
reg = sun4i_spi_read(sspi, SUN4I_CTL_REG);
sun4i_spi_write(sspi, SUN4I_CTL_REG, reg | SUN4I_CTL_XCH);
-
+
+ /*
tx_time = max(tfr->len * 8 * 2 / (tfr->speed_hz / 1000), 100U);
start = jiffies;
timeout = wait_for_completion_timeout(&sspi->done,
msecs_to_jiffies(tx_time));
- end = jiffies;
+ end = jiffies; */
+
+ tx_time = max_t(int, tfr->len * 8 * 2 / (speed / 1000), 100);
+ start = jiffies;
+ timeout = wait_for_completion_timeout(&sspi->done, msecs_to_jiffies(tx_time)); // Added manually
if (!timeout) {
dev_warn(&master->dev,
"%s: timeout transferring %u bytes@%iHz for %i(%i)ms",
- dev_name(&spi->dev), tfr->len, tfr->speed_hz,
+ dev_name(&spi->dev), tfr->len, speed,
jiffies_to_msecs(end - start), tx_time);
ret = -ETIMEDOUT;
goto out;
}
+ if (sun4i_spi_can_dma(master, spi, tfr) && desc_rx)
+ /* The receive transfer should be the last one to finish */
+ dma_wait_for_async_tx(desc_rx);
out:
sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0);
@@ -424,6 +522,70 @@
return 0;
}
+static void sun4i_spi_request_dma(struct platform_device *pdev,
+ struct resource *res,
+ struct spi_master *master)
+{
+ struct sun4i_spi *sspi = spi_master_get_devdata(master);
+ struct dma_slave_config dma_sconfig;
+ int ret;
+
+ master->dma_tx = dma_request_slave_channel_reason(&pdev->dev, "tx");
+ if (IS_ERR(master->dma_tx)) {
+ dev_warn(&pdev->dev, "Unable to acquire DMA channel TX (%li)\n",
+ PTR_ERR(master->dma_tx));
+ goto err_dma;
+ }
+
+ dma_sconfig.direction = DMA_MEM_TO_DEV;
+ dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ dma_sconfig.dst_addr = res->start + SUN4I_TXDATA_REG;
+ dma_sconfig.src_maxburst = 1;
+ dma_sconfig.dst_maxburst = 1;
+
+ ret = dmaengine_slave_config(master->dma_tx, &dma_sconfig);
+ if (ret) {
+ dev_warn(&pdev->dev, "Unable to configure TX DMA slave (%i)\n",
+ ret);
+ goto err_tx_dma_release;
+ }
+
+ master->dma_rx = dma_request_slave_channel_reason(&pdev->dev, "rx");
+ if (IS_ERR(master->dma_rx)) {
+ dev_warn(&pdev->dev, "Unable to acquire DMA channel RX (%li)\n",
+ PTR_ERR(master->dma_rx));
+ goto err_tx_dma_release;
+ }
+
+ dma_sconfig.direction = DMA_DEV_TO_MEM;
+ dma_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ dma_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ dma_sconfig.src_addr = res->start + SUN4I_RXDATA_REG;
+ dma_sconfig.src_maxburst = 1;
+ dma_sconfig.dst_maxburst = 1;
+
+ ret = dmaengine_slave_config(master->dma_rx, &dma_sconfig);
+ if (ret) {
+ dev_warn(&pdev->dev, "Unable to configure RX DMA slave (%i)\n",
+ ret);
+ goto err_rx_dma_release;
+ }
+
+ sspi->has_dma = true;
+ return;
+
+err_rx_dma_release:
+ dma_release_channel(master->dma_rx);
+err_tx_dma_release:
+ dma_release_channel(master->dma_tx);
+err_dma:
+ master->dma_rx = NULL;
+ master->dma_tx = NULL;
+ sspi->has_dma = false;
+ return;
+}
+
static int sun4i_spi_probe(struct platform_device *pdev)
{
struct spi_master *master;
@@ -458,7 +620,9 @@
goto err_free_master;
}
+ init_completion(&sspi->done);
sspi->master = master;
+ master->can_dma = sun4i_spi_can_dma;
master->max_speed_hz = 100 * 1000 * 1000;
master->min_speed_hz = 3 * 1000;
master->set_cs = sun4i_spi_set_cs;
@@ -493,7 +657,8 @@
ret = sun4i_spi_runtime_resume(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Couldn't resume the device\n");
- goto err_free_master;
+ goto err_rx_dma_release;
+ //goto err_free_master;
}
pm_runtime_set_active(&pdev->dev);
@@ -511,6 +676,11 @@
err_pm_disable:
pm_runtime_disable(&pdev->dev);
sun4i_spi_runtime_suspend(&pdev->dev);
+err_rx_dma_release:
+ if (master->dma_rx)
+ dma_release_channel(master->dma_rx);
+ if (master->dma_tx)
+ dma_release_channel(master->dma_tx);
err_free_master:
spi_master_put(master);
return ret;
@@ -518,8 +688,12 @@
static int sun4i_spi_remove(struct platform_device *pdev)
{
+ struct spi_master *master = platform_get_drvdata(pdev);
pm_runtime_force_suspend(&pdev->dev);
+ dma_release_channel(master->dma_rx);
+ dma_release_channel(master->dma_tx);
+
return 0;
}
Any clue would be very helpful.
Thanks.