mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 12:42:02 +00:00
spi: lpspi: add dma mode support
Add dma mode support for LPSPI. Any frame longer than half txfifosize will be sent by dma mode. For now, there are some limits: 1. The maximum transfer speed in master mode depends on the slave device, at least 40MHz(tested by spi-nor on 8qm-lpddr4-arm2 base board); 2. The maximum transfer speed in slave mode is 15MHz(imx7ulp), 22MHz(8qm/qxp). In order to reach the maximum speed which is mentioned in datasheet, the load of connect wires between master and slave should be less than 15pF. Signed-off-by: Clark Wang <xiaoning.wang@nxp.com> Acked-by: Fugang Duan <Fugang.duan@nxp.com> Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
parent
c7a4025995
commit
09c04466ce
@ -8,6 +8,8 @@
|
||||
#include <linux/clk.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
@ -20,6 +22,7 @@
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/platform_data/dma-imx.h>
|
||||
#include <linux/platform_data/spi-imx.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
@ -31,6 +34,9 @@
|
||||
|
||||
#define FSL_LPSPI_RPM_TIMEOUT 50 /* 50ms */
|
||||
|
||||
/* The maximum bytes that edma can transfer once.*/
|
||||
#define FSL_LPSPI_MAX_EDMA_BYTES ((1 << 15) - 1)
|
||||
|
||||
/* i.MX7ULP LPSPI registers */
|
||||
#define IMX7ULP_VERID 0x0
|
||||
#define IMX7ULP_PARAM 0x4
|
||||
@ -64,6 +70,8 @@
|
||||
#define IER_FCIE BIT(9)
|
||||
#define IER_RDIE BIT(1)
|
||||
#define IER_TDIE BIT(0)
|
||||
#define DER_RDDE BIT(1)
|
||||
#define DER_TDDE BIT(0)
|
||||
#define CFGR1_PCSCFG BIT(27)
|
||||
#define CFGR1_PINCFG (BIT(24)|BIT(25))
|
||||
#define CFGR1_PCSPOL BIT(8)
|
||||
@ -91,6 +99,7 @@ struct lpspi_config {
|
||||
struct fsl_lpspi_data {
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
unsigned long base_phys;
|
||||
struct clk *clk_ipg;
|
||||
struct clk *clk_per;
|
||||
bool is_slave;
|
||||
@ -111,6 +120,11 @@ struct fsl_lpspi_data {
|
||||
|
||||
bool slave_aborted;
|
||||
|
||||
/* DMA */
|
||||
bool usedma;
|
||||
struct completion dma_rx_completion;
|
||||
struct completion dma_tx_completion;
|
||||
|
||||
int chipselect[0];
|
||||
};
|
||||
|
||||
@ -158,6 +172,35 @@ static void fsl_lpspi_intctrl(struct fsl_lpspi_data *fsl_lpspi,
|
||||
writel(enable, fsl_lpspi->base + IMX7ULP_IER);
|
||||
}
|
||||
|
||||
static int fsl_lpspi_bytes_per_word(const int bpw)
|
||||
{
|
||||
return DIV_ROUND_UP(bpw, BITS_PER_BYTE);
|
||||
}
|
||||
|
||||
static bool fsl_lpspi_can_dma(struct spi_controller *controller,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *transfer)
|
||||
{
|
||||
unsigned int bytes_per_word;
|
||||
|
||||
if (!controller->dma_rx)
|
||||
return false;
|
||||
|
||||
bytes_per_word = fsl_lpspi_bytes_per_word(transfer->bits_per_word);
|
||||
|
||||
switch (bytes_per_word)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int lpspi_prepare_xfer_hardware(struct spi_controller *controller)
|
||||
{
|
||||
struct fsl_lpspi_data *fsl_lpspi =
|
||||
@ -245,11 +288,13 @@ static void fsl_lpspi_set_cmd(struct fsl_lpspi_data *fsl_lpspi)
|
||||
* For the first transfer, clear TCR_CONTC to assert SS.
|
||||
* For subsequent transfer, set TCR_CONTC to keep SS asserted.
|
||||
*/
|
||||
temp |= TCR_CONT;
|
||||
if (fsl_lpspi->is_first_byte)
|
||||
temp &= ~TCR_CONTC;
|
||||
else
|
||||
temp |= TCR_CONTC;
|
||||
if (!fsl_lpspi->usedma) {
|
||||
temp |= TCR_CONT;
|
||||
if (fsl_lpspi->is_first_byte)
|
||||
temp &= ~TCR_CONTC;
|
||||
else
|
||||
temp |= TCR_CONTC;
|
||||
}
|
||||
}
|
||||
writel(temp, fsl_lpspi->base + IMX7ULP_TCR);
|
||||
|
||||
@ -260,7 +305,11 @@ static void fsl_lpspi_set_watermark(struct fsl_lpspi_data *fsl_lpspi)
|
||||
{
|
||||
u32 temp;
|
||||
|
||||
temp = fsl_lpspi->watermark >> 1 | (fsl_lpspi->watermark >> 1) << 16;
|
||||
if (!fsl_lpspi->usedma)
|
||||
temp = fsl_lpspi->watermark >> 1 |
|
||||
(fsl_lpspi->watermark >> 1) << 16;
|
||||
else
|
||||
temp = fsl_lpspi->watermark >> 1;
|
||||
|
||||
writel(temp, fsl_lpspi->base + IMX7ULP_FCR);
|
||||
|
||||
@ -302,6 +351,53 @@ static int fsl_lpspi_set_bitrate(struct fsl_lpspi_data *fsl_lpspi)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_lpspi_dma_configure(struct spi_controller *controller)
|
||||
{
|
||||
int ret;
|
||||
enum dma_slave_buswidth buswidth;
|
||||
struct dma_slave_config rx = {}, tx = {};
|
||||
struct fsl_lpspi_data *fsl_lpspi =
|
||||
spi_controller_get_devdata(controller);
|
||||
|
||||
switch (fsl_lpspi_bytes_per_word(fsl_lpspi->config.bpw)) {
|
||||
case 4:
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
break;
|
||||
case 2:
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
||||
break;
|
||||
case 1:
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tx.direction = DMA_MEM_TO_DEV;
|
||||
tx.dst_addr = fsl_lpspi->base_phys + IMX7ULP_TDR;
|
||||
tx.dst_addr_width = buswidth;
|
||||
tx.dst_maxburst = 1;
|
||||
ret = dmaengine_slave_config(controller->dma_tx, &tx);
|
||||
if (ret) {
|
||||
dev_err(fsl_lpspi->dev, "TX dma configuration failed with %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rx.direction = DMA_DEV_TO_MEM;
|
||||
rx.src_addr = fsl_lpspi->base_phys + IMX7ULP_RDR;
|
||||
rx.src_addr_width = buswidth;
|
||||
rx.src_maxburst = 1;
|
||||
ret = dmaengine_slave_config(controller->dma_rx, &rx);
|
||||
if (ret) {
|
||||
dev_err(fsl_lpspi->dev, "RX dma configuration failed with %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_lpspi_config(struct fsl_lpspi_data *fsl_lpspi)
|
||||
{
|
||||
u32 temp;
|
||||
@ -327,10 +423,16 @@ static int fsl_lpspi_config(struct fsl_lpspi_data *fsl_lpspi)
|
||||
temp |= CR_RRF | CR_RTF | CR_MEN;
|
||||
writel(temp, fsl_lpspi->base + IMX7ULP_CR);
|
||||
|
||||
temp = 0;
|
||||
if (fsl_lpspi->usedma)
|
||||
temp = DER_TDDE | DER_RDDE;
|
||||
writel(temp, fsl_lpspi->base + IMX7ULP_DER);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_lpspi_setup_transfer(struct spi_device *spi,
|
||||
static int fsl_lpspi_setup_transfer(struct spi_controller *controller,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *t)
|
||||
{
|
||||
struct fsl_lpspi_data *fsl_lpspi =
|
||||
@ -363,6 +465,11 @@ static int fsl_lpspi_setup_transfer(struct spi_device *spi,
|
||||
else
|
||||
fsl_lpspi->watermark = fsl_lpspi->txfifosize;
|
||||
|
||||
if (fsl_lpspi_can_dma(controller, spi, t))
|
||||
fsl_lpspi->usedma = 1;
|
||||
else
|
||||
fsl_lpspi->usedma = 0;
|
||||
|
||||
return fsl_lpspi_config(fsl_lpspi);
|
||||
}
|
||||
|
||||
@ -401,8 +508,10 @@ static int fsl_lpspi_reset(struct fsl_lpspi_data *fsl_lpspi)
|
||||
{
|
||||
u32 temp;
|
||||
|
||||
/* Disable all interrupt */
|
||||
fsl_lpspi_intctrl(fsl_lpspi, 0);
|
||||
if (!fsl_lpspi->usedma) {
|
||||
/* Disable all interrupt */
|
||||
fsl_lpspi_intctrl(fsl_lpspi, 0);
|
||||
}
|
||||
|
||||
/* W1C for all flags in SR */
|
||||
temp = 0x3F << 8;
|
||||
@ -415,6 +524,176 @@ static int fsl_lpspi_reset(struct fsl_lpspi_data *fsl_lpspi)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fsl_lpspi_dma_rx_callback(void *cookie)
|
||||
{
|
||||
struct fsl_lpspi_data *fsl_lpspi = (struct fsl_lpspi_data *)cookie;
|
||||
|
||||
complete(&fsl_lpspi->dma_rx_completion);
|
||||
}
|
||||
|
||||
static void fsl_lpspi_dma_tx_callback(void *cookie)
|
||||
{
|
||||
struct fsl_lpspi_data *fsl_lpspi = (struct fsl_lpspi_data *)cookie;
|
||||
|
||||
complete(&fsl_lpspi->dma_tx_completion);
|
||||
}
|
||||
|
||||
static int fsl_lpspi_calculate_timeout(struct fsl_lpspi_data *fsl_lpspi,
|
||||
int size)
|
||||
{
|
||||
unsigned long timeout = 0;
|
||||
|
||||
/* Time with actual data transfer and CS change delay related to HW */
|
||||
timeout = (8 + 4) * size / fsl_lpspi->config.speed_hz;
|
||||
|
||||
/* Add extra second for scheduler related activities */
|
||||
timeout += 1;
|
||||
|
||||
/* Double calculated timeout */
|
||||
return msecs_to_jiffies(2 * timeout * MSEC_PER_SEC);
|
||||
}
|
||||
|
||||
static int fsl_lpspi_dma_transfer(struct spi_controller *controller,
|
||||
struct fsl_lpspi_data *fsl_lpspi,
|
||||
struct spi_transfer *transfer)
|
||||
{
|
||||
struct dma_async_tx_descriptor *desc_tx, *desc_rx;
|
||||
unsigned long transfer_timeout;
|
||||
unsigned long timeout;
|
||||
struct sg_table *tx = &transfer->tx_sg, *rx = &transfer->rx_sg;
|
||||
int ret;
|
||||
|
||||
ret = fsl_lpspi_dma_configure(controller);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
desc_rx = dmaengine_prep_slave_sg(controller->dma_rx,
|
||||
rx->sgl, rx->nents, DMA_DEV_TO_MEM,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc_rx)
|
||||
return -EINVAL;
|
||||
|
||||
desc_rx->callback = fsl_lpspi_dma_rx_callback;
|
||||
desc_rx->callback_param = (void *)fsl_lpspi;
|
||||
dmaengine_submit(desc_rx);
|
||||
reinit_completion(&fsl_lpspi->dma_rx_completion);
|
||||
dma_async_issue_pending(controller->dma_rx);
|
||||
|
||||
desc_tx = dmaengine_prep_slave_sg(controller->dma_tx,
|
||||
tx->sgl, tx->nents, DMA_MEM_TO_DEV,
|
||||
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
||||
if (!desc_tx) {
|
||||
dmaengine_terminate_all(controller->dma_tx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
desc_tx->callback = fsl_lpspi_dma_tx_callback;
|
||||
desc_tx->callback_param = (void *)fsl_lpspi;
|
||||
dmaengine_submit(desc_tx);
|
||||
reinit_completion(&fsl_lpspi->dma_tx_completion);
|
||||
dma_async_issue_pending(controller->dma_tx);
|
||||
|
||||
fsl_lpspi->slave_aborted = false;
|
||||
|
||||
if (!fsl_lpspi->is_slave) {
|
||||
transfer_timeout = fsl_lpspi_calculate_timeout(fsl_lpspi,
|
||||
transfer->len);
|
||||
|
||||
/* Wait eDMA to finish the data transfer.*/
|
||||
timeout = wait_for_completion_timeout(&fsl_lpspi->dma_tx_completion,
|
||||
transfer_timeout);
|
||||
if (!timeout) {
|
||||
dev_err(fsl_lpspi->dev, "I/O Error in DMA TX\n");
|
||||
dmaengine_terminate_all(controller->dma_tx);
|
||||
dmaengine_terminate_all(controller->dma_rx);
|
||||
fsl_lpspi_reset(fsl_lpspi);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
timeout = wait_for_completion_timeout(&fsl_lpspi->dma_rx_completion,
|
||||
transfer_timeout);
|
||||
if (!timeout) {
|
||||
dev_err(fsl_lpspi->dev, "I/O Error in DMA RX\n");
|
||||
dmaengine_terminate_all(controller->dma_tx);
|
||||
dmaengine_terminate_all(controller->dma_rx);
|
||||
fsl_lpspi_reset(fsl_lpspi);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
} else {
|
||||
if (wait_for_completion_interruptible(&fsl_lpspi->dma_tx_completion) ||
|
||||
fsl_lpspi->slave_aborted) {
|
||||
dev_dbg(fsl_lpspi->dev,
|
||||
"I/O Error in DMA TX interrupted\n");
|
||||
dmaengine_terminate_all(controller->dma_tx);
|
||||
dmaengine_terminate_all(controller->dma_rx);
|
||||
fsl_lpspi_reset(fsl_lpspi);
|
||||
return -EINTR;
|
||||
}
|
||||
|
||||
if (wait_for_completion_interruptible(&fsl_lpspi->dma_rx_completion) ||
|
||||
fsl_lpspi->slave_aborted) {
|
||||
dev_dbg(fsl_lpspi->dev,
|
||||
"I/O Error in DMA RX interrupted\n");
|
||||
dmaengine_terminate_all(controller->dma_tx);
|
||||
dmaengine_terminate_all(controller->dma_rx);
|
||||
fsl_lpspi_reset(fsl_lpspi);
|
||||
return -EINTR;
|
||||
}
|
||||
}
|
||||
|
||||
fsl_lpspi_reset(fsl_lpspi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fsl_lpspi_dma_exit(struct spi_controller *controller)
|
||||
{
|
||||
if (controller->dma_rx) {
|
||||
dma_release_channel(controller->dma_rx);
|
||||
controller->dma_rx = NULL;
|
||||
}
|
||||
|
||||
if (controller->dma_tx) {
|
||||
dma_release_channel(controller->dma_tx);
|
||||
controller->dma_tx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int fsl_lpspi_dma_init(struct device *dev,
|
||||
struct fsl_lpspi_data *fsl_lpspi,
|
||||
struct spi_controller *controller)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Prepare for TX DMA: */
|
||||
controller->dma_tx = dma_request_slave_channel_reason(dev, "tx");
|
||||
if (IS_ERR(controller->dma_tx)) {
|
||||
ret = PTR_ERR(controller->dma_tx);
|
||||
dev_dbg(dev, "can't get the TX DMA channel, error %d!\n", ret);
|
||||
controller->dma_tx = NULL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Prepare for RX DMA: */
|
||||
controller->dma_rx = dma_request_slave_channel_reason(dev, "rx");
|
||||
if (IS_ERR(controller->dma_rx)) {
|
||||
ret = PTR_ERR(controller->dma_rx);
|
||||
dev_dbg(dev, "can't get the RX DMA channel, error %d\n", ret);
|
||||
controller->dma_rx = NULL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
init_completion(&fsl_lpspi->dma_rx_completion);
|
||||
init_completion(&fsl_lpspi->dma_tx_completion);
|
||||
controller->can_dma = fsl_lpspi_can_dma;
|
||||
controller->max_dma_len = FSL_LPSPI_MAX_EDMA_BYTES;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
fsl_lpspi_dma_exit(controller);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fsl_lpspi_pio_transfer(struct spi_controller *controller,
|
||||
struct spi_transfer *t)
|
||||
{
|
||||
@ -449,14 +728,17 @@ static int fsl_lpspi_transfer_one(struct spi_controller *controller,
|
||||
int ret;
|
||||
|
||||
fsl_lpspi->is_first_byte = true;
|
||||
ret = fsl_lpspi_setup_transfer(spi, t);
|
||||
ret = fsl_lpspi_setup_transfer(controller, spi, t);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fsl_lpspi_set_cmd(fsl_lpspi);
|
||||
fsl_lpspi->is_first_byte = false;
|
||||
|
||||
ret = fsl_lpspi_pio_transfer(controller, t);
|
||||
if (fsl_lpspi->usedma)
|
||||
ret = fsl_lpspi_dma_transfer(controller, fsl_lpspi, t);
|
||||
else
|
||||
ret = fsl_lpspi_pio_transfer(controller, t);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@ -606,6 +888,7 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
|
||||
ret = PTR_ERR(fsl_lpspi->base);
|
||||
goto out_controller_put;
|
||||
}
|
||||
fsl_lpspi->base_phys = res->start;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
@ -647,6 +930,13 @@ static int fsl_lpspi_probe(struct platform_device *pdev)
|
||||
fsl_lpspi->txfifosize = 1 << (temp & 0x0f);
|
||||
fsl_lpspi->rxfifosize = 1 << ((temp >> 8) & 0x0f);
|
||||
|
||||
ret = fsl_lpspi_dma_init(&pdev->dev, fsl_lpspi, controller);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
goto out_controller_put;
|
||||
|
||||
if (ret < 0)
|
||||
dev_err(&pdev->dev, "dma setup error %d, use pio\n", ret);
|
||||
|
||||
ret = devm_spi_register_controller(&pdev->dev, controller);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "spi_register_controller error.\n");
|
||||
|
Loading…
Reference in New Issue
Block a user