[PATCH v6 1/3] dma: at_xdmac: creation of the atmel eXtended DMA Controller driver
Ludovic Desroches
ludovic.desroches at atmel.com
Thu Oct 16 07:10:48 PDT 2014
On Wed, Oct 15, 2014 at 07:00:04PM +0530, Vinod Koul wrote:
> On Wed, Oct 01, 2014 at 04:59:23PM +0200, Ludovic Desroches wrote:
> > New atmel DMA controller known as XDMAC, introduced with SAMA5D4
> > devices.
> >
> > +
> > +static inline u32 at_xdmac_csize(u32 maxburst)
> > +{
> > + u32 csize;
> > +
> > + switch (ffs(maxburst) - 1) {
> This is pretty odd, I dont see a reason why we can have proper case values
> and converted ones. It would have made sense if we use this for conversion
> but in case values in quite puzzling
>
> > + case 1:
> > + csize = AT_XDMAC_CC_CSIZE_CHK_2;
> and looking at values this can be ffs(maxburst) - 1 for valid cases
Yes I can return ffs(maxburst) - 1 for valid cases.
> > + break;
> > + case 2:
> > + csize = AT_XDMAC_CC_CSIZE_CHK_4;
> > + break;
> > + case 3:
> > + csize = AT_XDMAC_CC_CSIZE_CHK_8;
> > + break;
> > + case 4:
> > + csize = AT_XDMAC_CC_CSIZE_CHK_16;
> > + break;
> > + default:
> > + csize = AT_XDMAC_CC_CSIZE_CHK_1;
> why?
Because some devices don't set maxburst, for example, our serial driver.
>
> > + }
> > +
> > + return csize;
> > +};
> > +
> > +static unsigned int init_nr_desc_per_channel = 64;
> > +module_param(init_nr_desc_per_channel, uint, 0644);
> > +MODULE_PARM_DESC(init_nr_desc_per_channel,
> > + "initial descriptors per channel (default: 64)");
> > +
> > +
> > +static bool at_xdmac_chan_is_enabled(struct at_xdmac_chan *atchan)
> > +{
> > + return at_xdmac_chan_read(atchan, AT_XDMAC_GS) & atchan->mask;
> > +}
> > +
> > +static void at_xdmac_off(struct at_xdmac *atxdmac)
> > +{
> > + at_xdmac_write(atxdmac, AT_XDMAC_GD, -1L);
> > +
> > + /* Wait that all chans are disabled. */
> > + while (at_xdmac_read(atxdmac, AT_XDMAC_GS))
> > + cpu_relax();
> > +
> > + at_xdmac_write(atxdmac, AT_XDMAC_GID, -1L);
> > +}
> > +
> > +/* 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))
> > + return;
> > +
> > + /* Set transfer as active to not try to start it again. */
> > + first->active_xfer = true;
> > +
> > + /* Tell xdmac where to get the first descriptor. */
> > + reg = AT_XDMAC_CNDA_NDA(first->tx_dma_desc.phys)
> > + | AT_XDMAC_CNDA_NDAIF(atchan->memif);
> > + at_xdmac_chan_write(atchan, AT_XDMAC_CNDA, reg);
> > +
> > + /*
> > + * When doing memory to memory transfer we need to use the next
> > + * descriptor view 2 since some fields of the configuration register
> > + * depend on transfer size and src/dest addresses.
> > + */
> > + if (atchan->cfg & AT_XDMAC_CC_TYPE_PER_TRAN) {
> > + reg = AT_XDMAC_CNDC_NDVIEW_NDV1;
> > + at_xdmac_chan_write(atchan, AT_XDMAC_CC, atchan->cfg);
> > + } else {
> > + reg = AT_XDMAC_CNDC_NDVIEW_NDV2;
> > + }
> > +
> > + reg |= AT_XDMAC_CNDC_NDDUP
> > + | AT_XDMAC_CNDC_NDSUP
> > + | AT_XDMAC_CNDC_NDE;
> > + at_xdmac_chan_write(atchan, AT_XDMAC_CNDC, reg);
> > +
> > + dev_vdbg(chan2dev(&atchan->chan),
> > + "%s: XDMAC_CC=0x%08x XDMAC_CNDA=0x%08x, XDMAC_CNDC=0x%08x, "
> > + "XDMAC_CSA=0x%08x, XDMAC_CDA=0x%08x, XDMAC_CUBC=0x%08x\n",
> multi line prints are not encouraged. You could perhpas do XDMAC CC, CNDC...
> and reduce your text size and ignore 80char limit.
Ok
>
> > + __func__, at_xdmac_chan_read(atchan, AT_XDMAC_CC),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CNDA),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CNDC),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CSA),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CDA),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CUBC));
> > +
> > + at_xdmac_chan_write(atchan, AT_XDMAC_CID, 0xffffffff);
> > + reg = AT_XDMAC_CIE_RBEIE | AT_XDMAC_CIE_WBEIE | AT_XDMAC_CIE_ROIE;
> > + /*
> > + * There is no end of list when doing cyclic dma, we need to get
> > + * an interrupt after each periods.
> > + */
> > + if (at_xdmac_chan_is_cyclic(atchan))
> > + at_xdmac_chan_write(atchan, AT_XDMAC_CIE,
> > + reg | AT_XDMAC_CIE_BIE);
> > + else
> > + at_xdmac_chan_write(atchan, AT_XDMAC_CIE,
> > + reg | AT_XDMAC_CIE_LIE);
> > + at_xdmac_write(atxdmac, AT_XDMAC_GIE, atchan->mask);
> > + dev_vdbg(chan2dev(&atchan->chan),
> > + "%s: enable channel (0x%08x)\n", __func__, atchan->mask);
> > + wmb();
> > + at_xdmac_write(atxdmac, AT_XDMAC_GE, atchan->mask);
> > +
> > + dev_vdbg(chan2dev(&atchan->chan),
> > + "%s: XDMAC_CC=0x%08x XDMAC_CNDA=0x%08x, XDMAC_CNDC=0x%08x, "
> > + "XDMAC_CSA=0x%08x, XDMAC_CDA=0x%08x, XDMAC_CUBC=0x%08x\n",
> > + __func__, at_xdmac_chan_read(atchan, AT_XDMAC_CC),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CNDA),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CNDC),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CSA),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CDA),
> > + at_xdmac_chan_read(atchan, AT_XDMAC_CUBC));
> > +
> > +}
>
> > +static int at_xdmac_set_slave_config(struct dma_chan *chan,
> > + struct dma_slave_config *sconfig)
> > +{
> > + struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
> > +
> > + atchan->cfg = AT91_XDMAC_DT_PERID(atchan->perid)
> > + | AT_XDMAC_CC_SWREQ_HWR_CONNECTED
> > + | AT_XDMAC_CC_MBSIZE_SIXTEEN
> > + | AT_XDMAC_CC_TYPE_PER_TRAN;
> > +
> > + if (sconfig->direction == DMA_DEV_TO_MEM) {
> > + atchan->cfg |= AT_XDMAC_CC_DAM_INCREMENTED_AM
> > + | AT_XDMAC_CC_SAM_FIXED_AM
> > + | AT_XDMAC_CC_DIF(atchan->memif)
> > + | AT_XDMAC_CC_SIF(atchan->perif)
> > + | AT_XDMAC_CC_DSYNC_PER2MEM;
> > + atchan->dwidth = ffs(sconfig->src_addr_width) - 1;
> > + atchan->cfg |= AT_XDMAC_CC_DWIDTH(atchan->dwidth);
> > + atchan->cfg |= at_xdmac_csize(sconfig->src_maxburst);
> > + } else if (sconfig->direction == DMA_MEM_TO_DEV) {
> > + atchan->cfg |= AT_XDMAC_CC_DAM_FIXED_AM
> > + | AT_XDMAC_CC_SAM_INCREMENTED_AM
> > + | AT_XDMAC_CC_DIF(atchan->perif)
> > + | AT_XDMAC_CC_SIF(atchan->memif)
> > + | AT_XDMAC_CC_DSYNC_MEM2PER;
> > + atchan->dwidth = ffs(sconfig->dst_addr_width) - 1;
> > + atchan->cfg |= AT_XDMAC_CC_DWIDTH(atchan->dwidth);
> > + atchan->cfg |= at_xdmac_csize(sconfig->dst_maxburst);
> please store both direction configs and use them based on direction in
> prep_xxx calls. We will remove the direction here.
Ok, I'll do this update.
>
> > + } else {
> > + return -EINVAL;
> > + }
> > +
> > + /*
> > + * Src address and dest addr are needed to configure the link list
> > + * descriptor so keep the slave configuration.
> > + */
> > + memcpy(&atchan->dma_sconfig, sconfig, sizeof(struct dma_slave_config));
> > +
> > + dev_dbg(chan2dev(chan), "%s: atchan->cfg=0x%08x\n", __func__, atchan->cfg);
> > +
> > + return 0;
> > +}
> > +
> > +static struct dma_async_tx_descriptor *
> > +at_xdmac_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
> > + unsigned int sg_len, enum dma_transfer_direction direction,
> > + unsigned long flags, void *context)
> > +{
> > + struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
> > + struct dma_slave_config *sconfig = &atchan->dma_sconfig;
> > + struct at_xdmac_desc *first = NULL, *prev = NULL;
> > + struct scatterlist *sg;
> > + int i;
> > +
> > + if (!sgl)
> > + return NULL;
> > +
> > + if (!is_slave_direction(direction)) {
> > + dev_err(chan2dev(chan), "invalid DMA direction\n");
> > + return NULL;
> > + }
> > +
> > + dev_dbg(chan2dev(chan), "%s: sg_len=%d, dir=%s, flags=0x%lx\n",
> > + __func__, sg_len,
> > + direction == DMA_MEM_TO_DEV ? "to device" : "from device",
> > + flags);
> > +
> > + /* Protect dma_sconfig field that can be modified by set_slave_conf. */
> > + spin_lock_bh(&atchan->lock);
> > +
> > + /* Prepare descriptors. */
> > + for_each_sg(sgl, sg, sg_len, i) {
> > + struct at_xdmac_desc *desc = NULL;
> > + u32 len, mem;
> > +
> > + len = sg_dma_len(sg);
> > + mem = sg_dma_address(sg);
> > + if (unlikely(!len)) {
> > + dev_err(chan2dev(chan), "sg data length is zero\n");
> > + spin_unlock_bh(&atchan->lock);
> > + return NULL;
> > + }
> > + dev_dbg(chan2dev(chan), "%s: * sg%d len=%u, mem=0x%08x\n",
> > + __func__, i, len, mem);
> > +
> > + desc = at_xdmac_get_desc(atchan);
> > + if (!desc) {
> > + dev_err(chan2dev(chan), "can't get descriptor\n");
> > + if (first)
> > + list_splice_init(&first->descs_list, &atchan->free_descs_list);
> > + spin_unlock_bh(&atchan->lock);
> > + return NULL;
> > + }
> > +
> > + /* Linked list descriptor setup. */
> > + if (direction == DMA_DEV_TO_MEM) {
> > + desc->lld.mbr_sa = sconfig->src_addr;
> > + desc->lld.mbr_da = mem;
> > + } else {
> > + desc->lld.mbr_sa = mem;
> > + desc->lld.mbr_da = sconfig->dst_addr;
> > + }
> > + desc->lld.mbr_ubc = AT_XDMAC_MBR_UBC_NDV1 /* next descriptor view */
> > + | AT_XDMAC_MBR_UBC_NDEN /* next descriptor dst parameter update */
> > + | AT_XDMAC_MBR_UBC_NSEN /* next descriptor src parameter update */
> > + | (i == sg_len - 1 ? 0 : AT_XDMAC_MBR_UBC_NDE) /* descriptor fetch */
> > + | len / (1 << atchan->dwidth); /* microblock length */
> > + dev_dbg(chan2dev(chan),
> > + "%s: lld: mbr_sa=0x%08x, mbr_da=0x%08x, mbr_ubc=0x%08x\n",
> > + __func__, desc->lld.mbr_sa, desc->lld.mbr_da, desc->lld.mbr_ubc);
> > +
> > + /* Chain lld. */
> > + if (prev) {
> > + prev->lld.mbr_nda = desc->tx_dma_desc.phys;
> > + dev_dbg(chan2dev(chan),
> > + "%s: chain lld: prev=0x%p, mbr_nda=0x%08x\n",
> > + __func__, prev, prev->lld.mbr_nda);
> > + }
> > +
> > + prev = desc;
> > + if (!first)
> > + first = desc;
> > +
> > + dev_dbg(chan2dev(chan), "%s: add desc 0x%p to descs_list 0x%p\n",
> > + __func__, desc, first);
> > + list_add_tail(&desc->desc_node, &first->descs_list);
> > + }
> > +
> > + spin_unlock_bh(&atchan->lock);
> > +
> > + first->tx_dma_desc.cookie = -EBUSY;
> why are you init cookie here
Inspired by other driver. What is the right place so?
> > + first->tx_dma_desc.flags = flags;
> > + first->xfer_size = sg_len;
> > +
> > + return &first->tx_dma_desc;
> > +}
> > +
>
> > +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.
> For memcpy why should we need slave_config. The system memory source and
> destination width could be assumed to relastic values and then burst sizes
> maxed for performance. These values make more sense for periphral where we
> have to match up with the periphral
I don't tell I need slave_config. We have already talked about this. I don't
see the problem. It is only a comment, a reminder. The only information
I may need, one day, is the direction because we have to set src and dst
interfaces. At the moment, all our products are done in a way nand flash
and DDR are on the same interface so we don't have to care about
direction.
Since we don't have the direction, two solutions:
- remember this limitation for next products, that's why there is this reminder,
- change our nand driver in order to see nand as a peripheral instead of
a memory.
>
> > + */
> > + u32 chan_cc = AT_XDMAC_CC_DAM_INCREMENTED_AM
> > + | AT_XDMAC_CC_SAM_INCREMENTED_AM
> > + | AT_XDMAC_CC_DIF(0)
> > + | AT_XDMAC_CC_SIF(0)
> > + | AT_XDMAC_CC_MBSIZE_SIXTEEN
> > + | AT_XDMAC_CC_TYPE_MEM_TRAN;
> > +
> > + dev_dbg(chan2dev(chan), "%s: src=0x%08x, dest=0x%08x, len=%d, flags=0x%lx\n",
> > + __func__, src, dest, len, flags);
> > +
> > + if (unlikely(!len))
> > + return NULL;
> > +
> > + /*
> > + * Check address alignment to select the greater data width we can use.
> > + * Some XDMAC implementations don't provide dword transfer, in this
> > + * case selecting dword has the same behavior as selecting word transfers.
> > + */
> > + if (!((src_addr | dst_addr) & 7)) {
> > + dwidth = AT_XDMAC_CC_DWIDTH_DWORD;
> > + dev_dbg(chan2dev(chan), "%s: dwidth: double word\n", __func__);
> > + } else if (!((src_addr | dst_addr) & 3)) {
> > + dwidth = AT_XDMAC_CC_DWIDTH_WORD;
> > + dev_dbg(chan2dev(chan), "%s: dwidth: word\n", __func__);
> > + } else if (!((src_addr | dst_addr) & 1)) {
> > + dwidth = AT_XDMAC_CC_DWIDTH_HALFWORD;
> > + dev_dbg(chan2dev(chan), "%s: dwidth: half word\n", __func__);
> > + } else {
> > + dwidth = AT_XDMAC_CC_DWIDTH_BYTE;
> > + dev_dbg(chan2dev(chan), "%s: dwidth: byte\n", __func__);
> > + }
> > +
> > + atchan->cfg = chan_cc | AT_XDMAC_CC_DWIDTH(dwidth);
> > +
> > + /* Prepare descriptors. */
> > + while (remaining_size) {
> > + struct at_xdmac_desc *desc = NULL;
> > +
> > + dev_dbg(chan2dev(chan), "%s: remaining_size=%u\n", __func__, remaining_size);
> > +
> > + spin_lock_bh(&atchan->lock);
> > + desc = at_xdmac_get_desc(atchan);
> > + spin_unlock_bh(&atchan->lock);
> > + if (!desc) {
> > + dev_err(chan2dev(chan), "can't get descriptor\n");
> > + if (first)
> > + list_splice_init(&first->descs_list, &atchan->free_descs_list);
> > + return NULL;
> > + }
> > +
> > + /* Update src and dest addresses. */
> > + src_addr += xfer_size;
> > + dst_addr += xfer_size;
> > +
> > + if (remaining_size >= AT_XDMAC_MBR_UBC_UBLEN_MAX << dwidth)
> > + xfer_size = AT_XDMAC_MBR_UBC_UBLEN_MAX << dwidth;
> > + else
> > + xfer_size = remaining_size;
> > +
> > + dev_dbg(chan2dev(chan), "%s: xfer_size=%u\n", __func__, xfer_size);
> > +
> > + /* Check remaining length and change data width if needed. */
> > + if (!((src_addr | dst_addr | xfer_size) & 7)) {
> > + dwidth = AT_XDMAC_CC_DWIDTH_DWORD;
> > + dev_dbg(chan2dev(chan), "%s: dwidth: double word\n", __func__);
> > + } else if (!((src_addr | dst_addr | xfer_size) & 3)) {
> > + dwidth = AT_XDMAC_CC_DWIDTH_WORD;
> > + dev_dbg(chan2dev(chan), "%s: dwidth: word\n", __func__);
> > + } else if (!((src_addr | dst_addr | xfer_size) & 1)) {
> > + dwidth = AT_XDMAC_CC_DWIDTH_HALFWORD;
> > + dev_dbg(chan2dev(chan), "%s: dwidth: half word\n", __func__);
> > + } else if ((src_addr | dst_addr | xfer_size) & 1) {
> > + dwidth = AT_XDMAC_CC_DWIDTH_BYTE;
> > + dev_dbg(chan2dev(chan), "%s: dwidth: byte\n", __func__);
> > + }
> > + chan_cc |= AT_XDMAC_CC_DWIDTH(dwidth);
> > +
> > + ublen = xfer_size >> dwidth;
> > + remaining_size -= xfer_size;
> > +
> > + desc->lld.mbr_sa = src_addr;
> > + desc->lld.mbr_da = dst_addr;
> > + desc->lld.mbr_ubc = AT_XDMAC_MBR_UBC_NDV2
> > + | AT_XDMAC_MBR_UBC_NDEN
> > + | AT_XDMAC_MBR_UBC_NSEN
> > + | (remaining_size ? AT_XDMAC_MBR_UBC_NDE : 0)
> > + | ublen;
> > + desc->lld.mbr_cfg = chan_cc;
> > +
> > + dev_dbg(chan2dev(chan),
> > + "%s: lld: mbr_sa=0x%08x, mbr_da=0x%08x, mbr_ubc=0x%08x, mbr_cfg=0x%08x\n",
> > + __func__, desc->lld.mbr_sa, desc->lld.mbr_da, desc->lld.mbr_ubc, desc->lld.mbr_cfg);
> > +
> > + /* Chain lld. */
> > + if (prev) {
> > + prev->lld.mbr_nda = desc->tx_dma_desc.phys;
> > + dev_dbg(chan2dev(chan),
> > + "%s: chain lld: prev=0x%p, mbr_nda=0x%08x\n",
> > + __func__, prev, prev->lld.mbr_nda);
> > + }
> > +
> > + prev = desc;
> > + if (!first)
> > + first = desc;
> > +
> > + dev_dbg(chan2dev(chan), "%s: add desc 0x%p to descs_list 0x%p\n",
> > + __func__, desc, first);
> > + list_add_tail(&desc->desc_node, &first->descs_list);
> > + }
> > +
> > + first->tx_dma_desc.cookie = -EBUSY;
> again..
>
> --
> ~Vinod
Ludovic
More information about the linux-arm-kernel
mailing list