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 <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. -- You received this message because you are subscribed to the Google Groups "linux-sunxi" group. To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscr...@googlegroups.com. To view this discussion on the web, visit https://groups.google.com/d/msgid/linux-sunxi/CA%2BH9mKg-8OTf2a_7Tv2z6ga9f9tZeSSFuTn4RZznwymubQyzyQ%40mail.gmail.com.