diff options
Diffstat (limited to 'drivers/dma/s3c24xx-dma.c')
| -rw-r--r-- | drivers/dma/s3c24xx-dma.c | 113 | 
1 files changed, 111 insertions, 2 deletions
| diff --git a/drivers/dma/s3c24xx-dma.c b/drivers/dma/s3c24xx-dma.c index b209a0f17344..012520c9fd79 100644 --- a/drivers/dma/s3c24xx-dma.c +++ b/drivers/dma/s3c24xx-dma.c @@ -164,6 +164,7 @@ struct s3c24xx_sg {   * @disrcc: value for source control register   * @didstc: value for destination control register   * @dcon: base value for dcon register + * @cyclic: indicate cyclic transfer   */  struct s3c24xx_txd {  	struct virt_dma_desc vd; @@ -173,6 +174,7 @@ struct s3c24xx_txd {  	u32 disrcc;  	u32 didstc;  	u32 dcon; +	bool cyclic;  };  struct s3c24xx_dma_chan; @@ -669,8 +671,10 @@ static irqreturn_t s3c24xx_dma_irq(int irq, void *data)  		/* when more sg's are in this txd, start the next one */  		if (!list_is_last(txd->at, &txd->dsg_list)) {  			txd->at = txd->at->next; +			if (txd->cyclic) +				vchan_cyclic_callback(&txd->vd);  			s3c24xx_dma_start_next_sg(s3cchan, txd); -		} else { +		} else if (!txd->cyclic) {  			s3cchan->at = NULL;  			vchan_cookie_complete(&txd->vd); @@ -682,6 +686,12 @@ static irqreturn_t s3c24xx_dma_irq(int irq, void *data)  				s3c24xx_dma_start_next_txd(s3cchan);  			else  				s3c24xx_dma_phy_free(s3cchan); +		} else { +			vchan_cyclic_callback(&txd->vd); + +			/* Cyclic: reset at beginning */ +			txd->at = txd->dsg_list.next; +			s3c24xx_dma_start_next_sg(s3cchan, txd);  		}  	}  	spin_unlock(&s3cchan->vc.lock); @@ -877,6 +887,104 @@ static struct dma_async_tx_descriptor *s3c24xx_dma_prep_memcpy(  	return vchan_tx_prep(&s3cchan->vc, &txd->vd, flags);  } +static struct dma_async_tx_descriptor *s3c24xx_dma_prep_dma_cyclic( +	struct dma_chan *chan, dma_addr_t addr, size_t size, size_t period, +	enum dma_transfer_direction direction, unsigned long flags, +	void *context) +{ +	struct s3c24xx_dma_chan *s3cchan = to_s3c24xx_dma_chan(chan); +	struct s3c24xx_dma_engine *s3cdma = s3cchan->host; +	const struct s3c24xx_dma_platdata *pdata = s3cdma->pdata; +	struct s3c24xx_dma_channel *cdata = &pdata->channels[s3cchan->id]; +	struct s3c24xx_txd *txd; +	struct s3c24xx_sg *dsg; +	unsigned sg_len; +	dma_addr_t slave_addr; +	u32 hwcfg = 0; +	int i; + +	dev_dbg(&s3cdma->pdev->dev, +		"prepare cyclic transaction of %zu bytes with period %zu from %s\n", +		size, period, s3cchan->name); + +	if (!is_slave_direction(direction)) { +		dev_err(&s3cdma->pdev->dev, +			"direction %d unsupported\n", direction); +		return NULL; +	} + +	txd = s3c24xx_dma_get_txd(); +	if (!txd) +		return NULL; + +	txd->cyclic = 1; + +	if (cdata->handshake) +		txd->dcon |= S3C24XX_DCON_HANDSHAKE; + +	switch (cdata->bus) { +	case S3C24XX_DMA_APB: +		txd->dcon |= S3C24XX_DCON_SYNC_PCLK; +		hwcfg |= S3C24XX_DISRCC_LOC_APB; +		break; +	case S3C24XX_DMA_AHB: +		txd->dcon |= S3C24XX_DCON_SYNC_HCLK; +		hwcfg |= S3C24XX_DISRCC_LOC_AHB; +		break; +	} + +	/* +	 * Always assume our peripheral desintation is a fixed +	 * address in memory. +	 */ +	hwcfg |= S3C24XX_DISRCC_INC_FIXED; + +	/* +	 * Individual dma operations are requested by the slave, +	 * so serve only single atomic operations (S3C24XX_DCON_SERV_SINGLE). +	 */ +	txd->dcon |= S3C24XX_DCON_SERV_SINGLE; + +	if (direction == DMA_MEM_TO_DEV) { +		txd->disrcc = S3C24XX_DISRCC_LOC_AHB | +			      S3C24XX_DISRCC_INC_INCREMENT; +		txd->didstc = hwcfg; +		slave_addr = s3cchan->cfg.dst_addr; +		txd->width = s3cchan->cfg.dst_addr_width; +	} else { +		txd->disrcc = hwcfg; +		txd->didstc = S3C24XX_DIDSTC_LOC_AHB | +			      S3C24XX_DIDSTC_INC_INCREMENT; +		slave_addr = s3cchan->cfg.src_addr; +		txd->width = s3cchan->cfg.src_addr_width; +	} + +	sg_len = size / period; + +	for (i = 0; i < sg_len; i++) { +		dsg = kzalloc(sizeof(*dsg), GFP_NOWAIT); +		if (!dsg) { +			s3c24xx_dma_free_txd(txd); +			return NULL; +		} +		list_add_tail(&dsg->node, &txd->dsg_list); + +		dsg->len = period; +		/* Check last period length */ +		if (i == sg_len - 1) +			dsg->len = size - period * i; +		if (direction == DMA_MEM_TO_DEV) { +			dsg->src_addr = addr + period * i; +			dsg->dst_addr = slave_addr; +		} else { /* DMA_DEV_TO_MEM */ +			dsg->src_addr = slave_addr; +			dsg->dst_addr = addr + period * i; +		} +	} + +	return vchan_tx_prep(&s3cchan->vc, &txd->vd, flags); +} +  static struct dma_async_tx_descriptor *s3c24xx_dma_prep_slave_sg(  		struct dma_chan *chan, struct scatterlist *sgl,  		unsigned int sg_len, enum dma_transfer_direction direction, @@ -961,7 +1069,6 @@ static struct dma_async_tx_descriptor *s3c24xx_dma_prep_slave_sg(  			dsg->src_addr = slave_addr;  			dsg->dst_addr = sg_dma_address(sg);  		} -		break;  	}  	return vchan_tx_prep(&s3cchan->vc, &txd->vd, flags); @@ -1198,6 +1305,7 @@ static int s3c24xx_dma_probe(struct platform_device *pdev)  	/* Initialize slave engine for SoC internal dedicated peripherals */  	dma_cap_set(DMA_SLAVE, s3cdma->slave.cap_mask); +	dma_cap_set(DMA_CYCLIC, s3cdma->slave.cap_mask);  	dma_cap_set(DMA_PRIVATE, s3cdma->slave.cap_mask);  	s3cdma->slave.dev = &pdev->dev;  	s3cdma->slave.device_alloc_chan_resources = @@ -1207,6 +1315,7 @@ static int s3c24xx_dma_probe(struct platform_device *pdev)  	s3cdma->slave.device_tx_status = s3c24xx_dma_tx_status;  	s3cdma->slave.device_issue_pending = s3c24xx_dma_issue_pending;  	s3cdma->slave.device_prep_slave_sg = s3c24xx_dma_prep_slave_sg; +	s3cdma->slave.device_prep_dma_cyclic = s3c24xx_dma_prep_dma_cyclic;  	s3cdma->slave.device_control = s3c24xx_dma_control;  	/* Register as many memcpy channels as there are physical channels */ | 
