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