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 -- To unsubscribe from this list: send the line "unsubscribe dmaengine" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html