[PATCH v5 2/3] dmaengine: mediatek: Add MediaTek High-Speed DMA controller for MT7622 and MT7623 SoC

Vinod Koul vinod.koul at intel.com
Thu Mar 1 00:23:29 PST 2018


On Sun, Feb 18, 2018 at 03:08:30AM +0800, sean.wang at mediatek.com wrote:

> @@ -0,0 +1,1054 @@
> +// SPDX-License-Identifier: GPL-2.0
// Copyright ...

The copyright line needs to follow SPDX tag line

> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/iopoll.h>
> +#include <linux/jiffies.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_dma.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/refcount.h>
> +#include <linux/slab.h>

that's a lot of headers, do u need all those?

> +
> +#include "../virt-dma.h"
> +
> +#define MTK_DMA_DEV KBUILD_MODNAME

why do you need this?

> +
> +#define MTK_HSDMA_USEC_POLL		20
> +#define MTK_HSDMA_TIMEOUT_POLL		200000
> +#define MTK_HSDMA_DMA_BUSWIDTHS		BIT(DMA_SLAVE_BUSWIDTH_UNDEFINED)

Undefined buswidth??

> +/**
> + * struct mtk_hsdma_pdesc - This is the struct holding info describing physical
> + *			    descriptor (PD) and its placement must be kept at
> + *			    4-bytes alignment in little endian order.
> + * @desc[1-4]:		    The control pad used to indicate hardware how to

pls align to 80char or lesser

> +/**
> + * struct mtk_hsdma_ring - This struct holds info describing underlying ring
> + *			   space
> + * @txd:		   The descriptor TX ring which describes DMA source
> + *			   information
> + * @rxd:		   The descriptor RX ring which describes DMA
> + *			   destination information
> + * @cb:			   The extra information pointed at by RX ring
> + * @tphys:		   The physical addr of TX ring
> + * @rphys:		   The physical addr of RX ring
> + * @cur_tptr:		   Pointer to the next free descriptor used by the host
> + * @cur_rptr:		   Pointer to the last done descriptor by the device

here alignment and 80 char wrap will help too and few other places...


> +struct mtk_hsdma_vchan {
> +	struct virt_dma_chan vc;
> +	struct completion issue_completion;
> +	bool issue_synchronize;
> +	/* protected by vc.lock */

this should be at comments above...

> +static struct mtk_hsdma_device *to_hsdma_dev(struct dma_chan *chan)
> +{
> +	return container_of(chan->device, struct mtk_hsdma_device,
> +			    ddev);

and this can fit in a line

> +static int mtk_hsdma_alloc_pchan(struct mtk_hsdma_device *hsdma,
> +				 struct mtk_hsdma_pchan *pc)
> +{
> +	struct mtk_hsdma_ring *ring = &pc->ring;
> +	int err;
> +
> +	memset(pc, 0, sizeof(*pc));
> +
> +	/*
> +	 * Allocate ring space where [0 ... MTK_DMA_SIZE - 1] is for TX ring
> +	 * and [MTK_DMA_SIZE ... 2 * MTK_DMA_SIZE - 1] is for RX ring.
> +	 */
> +	pc->sz_ring = 2 * MTK_DMA_SIZE * sizeof(*ring->txd);
> +	ring->txd = dma_zalloc_coherent(hsdma2dev(hsdma), pc->sz_ring,
> +					&ring->tphys, GFP_ATOMIC);

GFP_NOWAIT please

> +	if (!ring->txd)
> +		return -ENOMEM;
> +
> +	ring->rxd = &ring->txd[MTK_DMA_SIZE];
> +	ring->rphys = ring->tphys + MTK_DMA_SIZE * sizeof(*ring->txd);
> +	ring->cur_tptr = 0;
> +	ring->cur_rptr = MTK_DMA_SIZE - 1;
> +
> +	ring->cb = kcalloc(MTK_DMA_SIZE, sizeof(*ring->cb), GFP_KERNEL);

this is inconsistent with your own pattern! make it GFP_NOWAIT pls

> +static int mtk_hsdma_issue_pending_vdesc(struct mtk_hsdma_device *hsdma,
> +					 struct mtk_hsdma_pchan *pc,
> +					 struct mtk_hsdma_vdesc *hvd)
> +{
> +	struct mtk_hsdma_ring *ring = &pc->ring;
> +	struct mtk_hsdma_pdesc *txd, *rxd;
> +	u16 reserved, prev, tlen, num_sgs;
> +	unsigned long flags;
> +
> +	/* Protect against PC is accessed by multiple VCs simultaneously */
> +	spin_lock_irqsave(&hsdma->lock, flags);
> +
> +	/*
> +	 * Reserve rooms, where pc->nr_free is used to track how many free
> +	 * rooms in the ring being updated in user and IRQ context.
> +	 */
> +	num_sgs = DIV_ROUND_UP(hvd->len, MTK_HSDMA_MAX_LEN);
> +	reserved = min_t(u16, num_sgs, atomic_read(&pc->nr_free));
> +
> +	if (!reserved) {
> +		spin_unlock_irqrestore(&hsdma->lock, flags);
> +		return -ENOSPC;
> +	}
> +
> +	atomic_sub(reserved, &pc->nr_free);
> +
> +	while (reserved--) {
> +		/* Limit size by PD capability for valid data moving */
> +		tlen = (hvd->len > MTK_HSDMA_MAX_LEN) ?
> +		       MTK_HSDMA_MAX_LEN : hvd->len;
> +
> +		/*
> +		 * Setup PDs using the remaining VD info mapped on those
> +		 * reserved rooms. And since RXD is shared memory between the
> +		 * host and the device allocated by dma_alloc_coherent call,
> +		 * the helper macro WRITE_ONCE can ensure the data written to
> +		 * RAM would really happens.
> +		 */
> +		txd = &ring->txd[ring->cur_tptr];
> +		WRITE_ONCE(txd->desc1, hvd->src);
> +		WRITE_ONCE(txd->desc2,
> +			   hsdma->soc->ls0 | MTK_HSDMA_DESC_PLEN(tlen));
> +
> +		rxd = &ring->rxd[ring->cur_tptr];
> +		WRITE_ONCE(rxd->desc1, hvd->dest);
> +		WRITE_ONCE(rxd->desc2, MTK_HSDMA_DESC_PLEN(tlen));
> +
> +		/* Associate VD, the PD belonged to */
> +		ring->cb[ring->cur_tptr].vd = &hvd->vd;
> +
> +		/* Move forward the pointer of TX ring */
> +		ring->cur_tptr = MTK_HSDMA_NEXT_DESP_IDX(ring->cur_tptr,
> +							 MTK_DMA_SIZE);
> +
> +		/* Update VD with remaining data */
> +		hvd->src  += tlen;
> +		hvd->dest += tlen;
> +		hvd->len  -= tlen;
> +	}
> +
> +	/*
> +	 * Tagging flag for the last PD for VD will be responsible for
> +	 * completing VD.
> +	 */
> +	if (!hvd->len) {
> +		prev = MTK_HSDMA_LAST_DESP_IDX(ring->cur_tptr, MTK_DMA_SIZE);
> +		ring->cb[prev].flag = MTK_HSDMA_VDESC_FINISHED;
> +	}
> +
> +	/* Ensure all changes indeed done before we're going on */
> +	wmb();
> +
> +	/*
> +	 * Updating into hardware the pointer of TX ring lets HSDMA to take
> +	 * action for those pending PDs.
> +	 */
> +	mtk_dma_write(hsdma, MTK_HSDMA_TX_CPU, ring->cur_tptr);
> +
> +	spin_unlock_irqrestore(&hsdma->lock, flags);
> +
> +	return !hvd->len ? 0 : -ENOSPC;

you already wrote and started txn, so why this?

> +static void mtk_hsdma_free_rooms_in_ring(struct mtk_hsdma_device *hsdma)
> +{
> +	struct mtk_hsdma_vchan *hvc;
> +	struct mtk_hsdma_pdesc *rxd;
> +	struct mtk_hsdma_vdesc *hvd;
> +	struct mtk_hsdma_pchan *pc;
> +	struct mtk_hsdma_cb *cb;
> +	__le32 desc2;
> +	u32 status;
> +	u16 next;
> +	int i;
> +
> +	pc = hsdma->pc;
> +
> +	/* Read IRQ status */
> +	status = mtk_dma_read(hsdma, MTK_HSDMA_INT_STATUS);
> +
> +	/*
> +	 * Ack the pending IRQ all to let hardware know software is handling
> +	 * those finished physical descriptors. Otherwise, the hardware would
> +	 * keep the used IRQ line in certain trigger state.
> +	 */
> +	mtk_dma_write(hsdma, MTK_HSDMA_INT_STATUS, status);
> +
> +	while (1) {
> +		next = MTK_HSDMA_NEXT_DESP_IDX(pc->ring.cur_rptr,
> +					       MTK_DMA_SIZE);

shouldn't we check if next is in range, we can crash if we get bad value
from hardware..

> +		rxd = &pc->ring.rxd[next];
> +
> +		/*
> +		 * If MTK_HSDMA_DESC_DDONE is no specified, that means data
> +		 * moving for the PD is still under going.
> +		 */
> +		desc2 = READ_ONCE(rxd->desc2);
> +		if (!(desc2 & hsdma->soc->ddone))
> +			break;

okay this is one break

> +
> +		cb = &pc->ring.cb[next];
> +		if (unlikely(!cb->vd)) {
> +			dev_err(hsdma2dev(hsdma), "cb->vd cannot be null\n");
> +			break;

and other for null, i feel we need to have checks for while(1) to break,
this can run infinitely if something bad happens, a fail safe would be good,
that too this being invoked from isr.

> +static enum dma_status mtk_hsdma_tx_status(struct dma_chan *c,
> +					   dma_cookie_t cookie,
> +					   struct dma_tx_state *txstate)
> +{
> +	struct mtk_hsdma_vchan *hvc = to_hsdma_vchan(c);
> +	struct mtk_hsdma_vdesc *hvd;
> +	struct virt_dma_desc *vd;
> +	enum dma_status ret;
> +	unsigned long flags;
> +	size_t bytes = 0;
> +
> +	ret = dma_cookie_status(c, cookie, txstate);
> +	if (ret == DMA_COMPLETE || !txstate)
> +		return ret;
> +
> +	spin_lock_irqsave(&hvc->vc.lock, flags);
> +	vd = mtk_hsdma_find_active_desc(c, cookie);
> +	spin_unlock_irqrestore(&hvc->vc.lock, flags);
> +
> +	if (vd) {
> +		hvd = to_hsdma_vdesc(vd);
> +		bytes = hvd->residue;

for active descriptor, shouldn't you read counters from hardware?

> +static struct dma_async_tx_descriptor *
> +mtk_hsdma_prep_dma_memcpy(struct dma_chan *c, dma_addr_t dest,
> +			  dma_addr_t src, size_t len, unsigned long flags)
> +{
> +	struct mtk_hsdma_vdesc *hvd;
> +
> +	hvd = kzalloc(sizeof(*hvd), GFP_NOWAIT);

GFP_NOWAIT here too

> +static int mtk_hsdma_terminate_all(struct dma_chan *c)
> +{
> +	mtk_hsdma_free_inactive_desc(c, false);

only inactive, active ones need to be freed and channel cleaned

-- 
~Vinod



More information about the linux-arm-kernel mailing list