[PATCH v3 1/2] dma: at_xdmac: creation of the atmel eXtended DMA Controller driver

Vinod Koul vinod.koul at intel.com
Fri Jul 25 00:16:31 PDT 2014


On Tue, Jul 08, 2014 at 03:11:35PM +0200, Ludovic Desroches wrote:
> New atmel DMA controller known as XDMAC, introduced with SAMA5D4
> devices.
Didnt check, but how much is delta b/w X/H Dmacs?

> +/* Call with lock hold. */
> +static void at_xdmac_start_xfer(struct at_xdmac_chan *atchan,
> +				struct at_xdmac_desc *first)
> +{
> +	struct at_xdmac	*atxdmac = to_at_xdmac(atchan->chan.device);
> +	u32		reg;
> +
> +	dev_vdbg(chan2dev(&atchan->chan), "%s: desc 0x%p\n", __func__, first);
> +
> +	if (at_xdmac_chan_is_enabled(atchan)) {
> +		dev_err(chan2dev(&atchan->chan),
> +			"BUG: Attempted to start a non-idle channel\n");
This shouldnt be BUG though. Your tasklet is always supposed to start and if
nothing to silent just return!


> +
> +static dma_cookie_t at_xdmac_tx_submit(struct dma_async_tx_descriptor *tx)
> +{
> +	struct at_xdmac_desc	*desc = txd_to_at_desc(tx);
> +	struct at_xdmac_chan	*atchan = to_at_xdmac_chan(tx->chan);
> +	dma_cookie_t		cookie;
> +	unsigned long		flags;
> +
> +	spin_lock_irqsave(&atchan->lock, flags);
> +	cookie = dma_cookie_assign(tx);
> +
> +	dev_vdbg(chan2dev(tx->chan), "%s: atchan 0x%p, add desc 0x%p to xfers_list\n",
> +		 __func__, atchan, desc);
> +	list_add_tail(&desc->xfer_node, &atchan->xfers_list);
> +	if (list_is_singular(&atchan->xfers_list))
> +		at_xdmac_start_xfer(atchan, desc);
so you dont start if list has more than 1 descriptors, why?

> +
> +	spin_unlock_irqrestore(&atchan->lock, flags);
> +	return cookie;
> +}
> +
> +static struct at_xdmac_desc *at_xdmac_alloc_desc(struct dma_chan *chan,
> +						 gfp_t gfp_flags)
why do we need last argument?

> +{
> +	struct at_xdmac_desc	*desc;
> +	struct at_xdmac		*atxdmac = to_at_xdmac(chan->device);
> +	dma_addr_t		phys;
> +
> +	desc = dma_pool_alloc(atxdmac->at_xdmac_desc_pool, gfp_flags, &phys);
> +	if (desc) {
> +		memset(desc, 0, sizeof(*desc));
> +		INIT_LIST_HEAD(&desc->descs_list);
> +		dma_async_tx_descriptor_init(&desc->tx_dma_desc, chan);
> +		desc->tx_dma_desc.tx_submit = at_xdmac_tx_submit;
> +		desc->tx_dma_desc.phys = phys;
> +	}
> +
> +	return desc;
> +}
> +
> +/* Call must be protected by lock. */
> +static struct at_xdmac_desc *at_xdmac_get_desc(struct at_xdmac_chan *atchan)
> +{
> +	struct at_xdmac_desc *desc;
> +
> +	if (list_empty(&atchan->free_descs_list)) {
> +		desc = at_xdmac_alloc_desc(&atchan->chan, GFP_ATOMIC);
GFP_NOWAIT pls

> +static struct dma_async_tx_descriptor *
> +at_xdmac_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src,
> +			 size_t len, unsigned long flags)
> +{
> +	struct at_xdmac_chan 	*atchan = to_at_xdmac_chan(chan);
> +	struct at_xdmac_desc	*first = NULL, *prev = NULL;
> +	size_t			remaining_size = len, xfer_size = 0, ublen;
> +	dma_addr_t		src_addr = src, dst_addr = dest;
> +	u32			dwidth;
> +	/*
> +	 * WARNING: The channel configuration is set here since there is no
> +	 * dmaengine_slave_config call in this case. Moreover we don't know the
> +	 * direction, it involves we can't dynamically set the source and dest
> +	 * interface so we have to use the same one. Only interface 0 allows EBI
> +	 * access. Hopefully we can access DDR through both ports (at least on
> +	 * SAMA5D4x), so we can use the same interface for source and dest,
> +	 * that solves the fact we don't know the direction.
and why do you need slave config for memcpy?

> +static enum dma_status
> +at_xdmac_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
> +		struct dma_tx_state *txstate)
> +{
> +	struct at_xdmac_chan 	*atchan = to_at_xdmac_chan(chan);
> +	struct at_xdmac		*atxdmac = to_at_xdmac(atchan->chan.device);
> +	struct at_xdmac_desc	*desc, *_desc;
> +	unsigned long		flags;
> +	enum dma_status		ret;
> +	int			residue;
> +	u32			cur_nda;
> +
> +	ret = dma_cookie_status(chan, cookie, txstate);
> +	if (ret == DMA_COMPLETE)
> +		return ret;
> +
> +	spin_lock_irqsave(&atchan->lock, flags);
> +
> +	desc = list_first_entry(&atchan->xfers_list, struct at_xdmac_desc, xfer_node);
> +
> +	if (!desc->active_xfer)
> +		dev_err(chan2dev(chan),
> +			"something goes wrong, there is no active transfer\n");
We can query resiude even when tx_submit hasnt been invoked. You need to
return the full length in that case.
> +
> +	residue = desc->xfer_size;

Also do check if txsate is NULL, in case just bail out here.

> +
> +	/* Flush FIFO. */
> +	at_xdmac_write(atxdmac, AT_XDMAC_GSWF, atchan->mask);
> +	while (!(at_xdmac_chan_read(atchan, AT_XDMAC_CIS) & AT_XDMAC_CIS_FIS));
> +		cpu_relax();
> +
> +	cur_nda = at_xdmac_chan_read(atchan, AT_XDMAC_CNDA) & 0xfffffffc;
> +	/*
> +	 * Remove size of all microblocks already transferred and the current
> +	 * one. Then add the remaining size to transfer of the current
> +	 * microblock.
> +	 */
> +	list_for_each_entry_safe(desc, _desc, &desc->descs_list, desc_node) {
> +		residue -= (desc->lld.mbr_ubc & 0xffffff) << atchan->dwidth;
> +		if ((desc->lld.mbr_nda & 0xfffffffc) == cur_nda)
> +			break;
> +	}
> +	residue += at_xdmac_chan_read(atchan, AT_XDMAC_CUBC) << atchan->dwidth;
> +
> +	spin_unlock_irqrestore(&atchan->lock, flags);
> +
> +	dma_set_residue(txstate, residue);
> +
> +	dev_dbg(chan2dev(chan),
> +		 "%s: desc=0x%p, tx_dma_desc.phys=0x%08x, tx_status=%d, cookie=%d, residue=%d\n",
> +		 __func__, desc, desc->tx_dma_desc.phys, ret, cookie, residue);
> +
> +	return ret;
> +}
> +
> +static void at_xdmac_terminate_xfer(struct at_xdmac_chan *atchan,
> +				    struct at_xdmac_desc *desc)
> +{
> +	dev_dbg(chan2dev(&atchan->chan), "%s: desc 0x%p\n", __func__, desc);
> +
> +	/*
> +	 * It's necessary to remove the transfer before calling the callback
> +	 * because some devices can call dma_engine_terminate_all causing to do
> +	 * dma_cookie_complete two times on the same cookie.
why would terminate call dma_cookie_complete?

> +	 */
> +	list_del(&desc->xfer_node);
> +	list_splice_init(&desc->descs_list, &atchan->free_descs_list);
shouldnt you move all the desc in active list in one shot ?

> +static void at_xdmac_tasklet(unsigned long data)
> +{
> +	struct at_xdmac_chan	*atchan = (struct at_xdmac_chan *)data;
> +	struct at_xdmac_desc	*desc;
> +	u32			error_mask;
> +
> +	dev_dbg(chan2dev(&atchan->chan), "%s: status=0x%08lx\n",
> +		 __func__, atchan->status);
> +
> +	error_mask = AT_XDMAC_CIS_RBEIS
> +		     | AT_XDMAC_CIS_WBEIS
> +		     | AT_XDMAC_CIS_ROIS;
> +
> +	if (at_xdmac_chan_is_cyclic(atchan)) {
> +		at_xdmac_handle_cyclic(atchan);
> +	} else if ((atchan->status & AT_XDMAC_CIS_LIS)
> +		   || (atchan->status & error_mask)) {
> +		struct dma_async_tx_descriptor  *txd;
> +
> +		if (atchan->status & AT_XDMAC_CIS_RBEIS)
> +			dev_err(chan2dev(&atchan->chan), "read bus error!!!");
> +		else if (atchan->status & AT_XDMAC_CIS_WBEIS)
> +			dev_err(chan2dev(&atchan->chan), "write bus error!!!");
> +		else if (atchan->status & AT_XDMAC_CIS_ROIS)
> +			dev_err(chan2dev(&atchan->chan), "request overflow error!!!");
> +
> +		desc = list_first_entry(&atchan->xfers_list,
> +					struct at_xdmac_desc,
> +					xfer_node);
> +		dev_vdbg(chan2dev(&atchan->chan), "%s: desc 0x%p\n", __func__, desc);
> +		BUG_ON(!desc->active_xfer);
> +
> +		txd = &desc->tx_dma_desc;
> +
> +		at_xdmac_terminate_xfer(atchan, desc);
why terminate here? This looks wrong

> +static int at_xdmac_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
> +			    unsigned long arg)
> +{
> +	struct at_xdmac_desc	*desc, *_desc;
> +	struct at_xdmac_chan	*atchan = to_at_xdmac_chan(chan);
> +	struct at_xdmac		*atxdmac = to_at_xdmac(atchan->chan.device);
> +	unsigned long		flags;
> +	int			ret = 0;
> +
> +	dev_dbg(chan2dev(chan), "%s: cmd=%d\n", __func__, cmd);
> +
> +	spin_lock_irqsave(&atchan->lock, flags);
> +
> +	switch (cmd) {
> +	case DMA_PAUSE:
> +		at_xdmac_write(atxdmac, AT_XDMAC_GRWS, atchan->mask);
> +		set_bit(AT_XDMAC_CHAN_IS_PAUSED, &atchan->status);
> +		break;
empty line here pls

> +	case DMA_RESUME:
> +		if (!at_xdmac_chan_is_paused(atchan))
> +			break;
> +
> +		at_xdmac_write(atxdmac, AT_XDMAC_GRWR, atchan->mask);
> +		clear_bit(AT_XDMAC_CHAN_IS_PAUSED, &atchan->status);
> +		break;
> +	case DMA_TERMINATE_ALL:
> +		at_xdmac_write(atxdmac, AT_XDMAC_GIE, atchan->mask);
> +		at_xdmac_write(atxdmac, AT_XDMAC_GD, atchan->mask);
> +		while (at_xdmac_read(atxdmac, AT_XDMAC_GS) & atchan->mask)
> +			cpu_relax();
> +
> +		/* Cancel all pending transfers. */
> +		list_for_each_entry_safe(desc, _desc, &atchan->xfers_list, xfer_node)
> +			at_xdmac_terminate_xfer(atchan, desc);
> +
> +		clear_bit(AT_XDMAC_CHAN_IS_CYCLIC, &atchan->status);
> +		break;
> +	case DMA_SLAVE_CONFIG:
> +		ret = at_xdmac_set_slave_config(chan,
> +				(struct dma_slave_config *)arg);
> +		break;
> +	default:
> +		dev_err(chan2dev(chan),
> +			"unmanaged or unknown dma control cmd: %d\n", cmd);
> +		ret = -ENXIO;
> +	}
> +
> +	spin_unlock_irqrestore(&atchan->lock, flags);
> +
> +	return ret;
> +}

> +static int atmel_xdmac_resume_noirq(struct device *dev)
> +{
> +	struct platform_device	*pdev = to_platform_device(dev);
> +	struct at_xdmac		*atxdmac = platform_get_drvdata(pdev);
> +	struct at_xdmac_chan	*atchan;
> +	struct dma_chan		*chan, *_chan;
> +	int			i;
> +
> +	clk_prepare_enable(atxdmac->clk);
> +
> +	/* Clear pending interrupts. */
> +	for (i = 0; i < atxdmac->dma.chancnt; i++) {
> +		atchan = &atxdmac->chan[i];
> +		while (at_xdmac_chan_read(atchan, AT_XDMAC_CIS))
> +			cpu_relax();
> +	}
> +
> +	at_xdmac_write(atxdmac, AT_XDMAC_GIE, atxdmac->save_gim);
> +	at_xdmac_write(atxdmac, AT_XDMAC_GE, atxdmac->save_gs);
> +	list_for_each_entry_safe(chan, _chan, &atxdmac->dma.channels, device_node) {
> +		atchan = to_at_xdmac_chan(chan);
> +		at_xdmac_chan_write(atchan, AT_XDMAC_CC, atchan->cfg);
> +		if (at_xdmac_chan_is_cyclic(atchan)) {
> +			at_xdmac_chan_write(atchan, AT_XDMAC_CNDA, atchan->save_cnda);
> +			at_xdmac_chan_write(atchan, AT_XDMAC_CNDC, atchan->save_cndc);
> +			at_xdmac_chan_write(atchan, AT_XDMAC_CIE, atchan->save_cim);
> +			at_xdmac_write(atxdmac, AT_XDMAC_GE, atchan->mask);
> +		}
> +	}
> +	return 0;
> +}
why noirq variants?

> +
> +static int at_xdmac_probe(struct platform_device *pdev)
> +{
> +	struct resource	*res;
> +	struct at_xdmac	*atxdmac;
> +	int		irq, size, nr_channels, i, ret;
> +	void __iomem	*base;
> +	u32		reg;
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!res)
> +		return -EINVAL;
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0)
> +		return irq;
> +
> +	base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(base))
> +		return PTR_ERR(base);
> +
> +	/*
> +	 * Read number of xdmac channels, read helper function can't be used
> +	 * since atxdmac is not yet allocated and we need to know the number
> +	 * of channels to do the allocation.
> +	 */
> +	reg = readl_relaxed(base + AT_XDMAC_GTYPE);
> +	nr_channels = AT_XDMAC_NB_CH(reg);
> +	if (nr_channels > AT_XDMAC_MAX_CHAN) {
> +		dev_err(&pdev->dev, "invalid number of channels (%u)\n",
> +			nr_channels);
> +		return -EINVAL;
> +	}
> +
> +	size = sizeof(*atxdmac);
> +	size += nr_channels * sizeof(struct at_xdmac_chan);
> +	atxdmac = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
devm_kmalloc_array/devm_kcalloc pls

> +	if (!atxdmac) {
> +		dev_err(&pdev->dev, "can't allocate at_xdmac structure\n");
> +		return -ENOMEM;
> +	}
> +
> +	atxdmac->regs = base;
> +	atxdmac->irq = irq;
> +
> +	ret = devm_request_irq(&pdev->dev, atxdmac->irq, at_xdmac_interrupt, 0,
> +			       "at_xdmac", atxdmac);
and this can invoke your irq handler and you may not be ready. Second this
cause races with tasklet, so usualy recommednation is to use regular
request_irq()
> +	if (ret) {
> +		dev_err(&pdev->dev, "can't request irq\n");
> +		return ret;
> +	}
> +
> +	atxdmac->clk = devm_clk_get(&pdev->dev, "dma_clk");
> +	if (IS_ERR(atxdmac->clk)) {
> +		dev_err(&pdev->dev, "can't get dma_clk\n");
> +		return PTR_ERR(atxdmac->clk);
> +	}
> +
> +	ret = clk_prepare_enable(atxdmac->clk);
> +	if (ret) {
> +		dev_err(&pdev->dev, "can't prepare or enable clock\n");
> +		return ret;
> +	}
> +
> +	atxdmac->at_xdmac_desc_pool =
> +		dmam_pool_create(dev_name(&pdev->dev), &pdev->dev,
> +				sizeof(struct at_xdmac_desc), 4, 0);
> +	if (!atxdmac->at_xdmac_desc_pool) {
> +		dev_err(&pdev->dev, "no memory for descriptors dma pool\n");
> +		ret = -ENOMEM;
> +		goto err_clk_disable;
> +	}
> +
> +	dma_cap_set(DMA_CYCLIC, atxdmac->dma.cap_mask);
> +	dma_cap_set(DMA_MEMCPY, atxdmac->dma.cap_mask);
> +	dma_cap_set(DMA_SLAVE, atxdmac->dma.cap_mask);
> +	atxdmac->dma.dev				= &pdev->dev;
> +	atxdmac->dma.device_alloc_chan_resources	= at_xdmac_alloc_chan_resources;
> +	atxdmac->dma.device_free_chan_resources		= at_xdmac_free_chan_resources;
> +	atxdmac->dma.device_tx_status			= at_xdmac_tx_status;
> +	atxdmac->dma.device_issue_pending		= at_xdmac_issue_pending;
> +	atxdmac->dma.device_prep_dma_cyclic		= at_xdmac_prep_dma_cyclic;
> +	atxdmac->dma.device_prep_dma_memcpy		= at_xdmac_prep_dma_memcpy;
> +	atxdmac->dma.device_prep_slave_sg		= at_xdmac_prep_slave_sg;
> +	atxdmac->dma.device_control			= at_xdmac_control;
> +	atxdmac->dma.chancnt				= nr_channels;
no caps, pls do implement that as you have cyclic dma too
> +
> +	/* Disable all chans and interrupts. */
> +	at_xdmac_off(atxdmac);
> +
> +	/* Init channels. */
> +	INIT_LIST_HEAD(&atxdmac->dma.channels);
> +	for (i = 0; i < nr_channels; i++) {
> +		struct at_xdmac_chan *atchan = &atxdmac->chan[i];
> +
> +		atchan->chan.device = &atxdmac->dma;
> +		list_add_tail(&atchan->chan.device_node,
> +			      &atxdmac->dma.channels);
> +
> +		atchan->ch_regs = at_xdmac_chan_reg_base(atxdmac, i);
> +		atchan->mask = 1 << i;
> +
> +		spin_lock_init(&atchan->lock);
> +		INIT_LIST_HEAD(&atchan->xfers_list);
> +		INIT_LIST_HEAD(&atchan->free_descs_list);
> +		tasklet_init(&atchan->tasklet, at_xdmac_tasklet,
> +			     (unsigned long)atchan);
> +
> +		/* Clear pending interrupts. */
> +		while (at_xdmac_chan_read(atchan, AT_XDMAC_CIS))
> +			cpu_relax();
> +	}
> +	platform_set_drvdata(pdev, atxdmac);
> +
> +	ret = dma_async_device_register(&atxdmac->dma);
> +	if (ret) {
> +		dev_err(&pdev->dev, "fail to register DMA engine device\n");
> +		goto err_clk_disable;
> +	}
> +
> +	ret = of_dma_controller_register(pdev->dev.of_node,
> +					 at_xdmac_xlate, atxdmac);
> +	if (ret) {
> +		dev_err(&pdev->dev, "could not register of dma controller\n");
> +		goto err_dma_unregister;
> +	}
> +
> +	dev_info(&pdev->dev, "%d channels, mapped at 0x%p\n",
> +		 nr_channels, atxdmac->regs);
> +
> +	return 0;
> +
> +err_dma_unregister:
> +	dma_async_device_unregister(&atxdmac->dma);
> +err_clk_disable:
> +	clk_disable_unprepare(atxdmac->clk);
> +	return ret;
> +}
> +
> +static int at_xdmac_remove(struct platform_device *pdev)
> +{
> +	struct at_xdmac	*atxdmac = (struct at_xdmac*)platform_get_drvdata(pdev);
> +	int		i;
> +
> +	at_xdmac_off(atxdmac);
> +	of_dma_controller_free(pdev->dev.of_node);
> +	dma_async_device_unregister(&atxdmac->dma);
> +	clk_disable_unprepare(atxdmac->clk);
> +
> +	synchronize_irq(atxdmac->irq);
> +
> +	for (i = 0; i < atxdmac->dma.chancnt; i++) {
> +		struct at_xdmac_chan *atchan = &atxdmac->chan[i];
> +
> +		tasklet_kill(&atchan->tasklet);
> +		at_xdmac_free_chan_resources(&atchan->chan);
> +	}
and you can still get irq!
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops atmel_xdmac_dev_pm_ops = {
> +	.prepare	= atmel_xdmac_prepare,
> +	.suspend_noirq	= atmel_xdmac_suspend_noirq,
> +	.resume_noirq	= atmel_xdmac_resume_noirq,
> +};
no ifdef CONFIG_PM?
You might want to make them late_suspend and early_resume to allow
clients suspending first

Thanks
-- 
~Vinod



More information about the linux-arm-kernel mailing list