[PATCH 03/18] dmaengine: st_fdma: Add STMicroelectronics FDMA engine driver support

Appana Durga Kedareswara Rao appana.durga.rao at xilinx.com
Thu Apr 21 04:26:16 PDT 2016



> -----Original Message-----
> From: dmaengine-owner at vger.kernel.org [mailto:dmaengine-
> owner at vger.kernel.org] On Behalf Of Peter Griffin
> Sent: Thursday, April 21, 2016 4:34 PM
> To: linux-arm-kernel at lists.infradead.org; linux-kernel at vger.kernel.org;
> srinivas.kandagatla at gmail.com; maxime.coquelin at st.com;
> patrice.chotard at st.com; vinod.koul at intel.com
> Cc: peter.griffin at linaro.org; lee.jones at linaro.org;
> dmaengine at vger.kernel.org; devicetree at vger.kernel.org; arnd at arndb.de;
> broonie at kernel.org; ludovic.barre at st.com
> Subject: [PATCH 03/18] dmaengine: st_fdma: Add STMicroelectronics FDMA
> engine driver support
> 
> This patch adds support for the Flexible Direct Memory Access (FDMA) core
> driver. The FDMA is a slim core CPU with a dedicated firmware.
> It is a general purpose DMA controller capable of supporting 16
> independent DMA channels. Data moves maybe from memory to memory
> or between memory and paced latency critical real time targets and it
> is found on al STi based chipsets.
> 
> Signed-off-by: Ludovic Barre <ludovic.barre at st.com>
> Signed-off-by: Peter Griffin <peter.griffin at linaro.org>
> ---
>  drivers/dma/Kconfig   |  12 +
>  drivers/dma/Makefile  |   1 +
>  drivers/dma/st_fdma.c | 967
> ++++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 980 insertions(+)
>  create mode 100644 drivers/dma/st_fdma.c
> 
> diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> index d96d87c..5910c4f 100644
> --- a/drivers/dma/Kconfig
> +++ b/drivers/dma/Kconfig
> @@ -527,6 +527,18 @@ config ZX_DMA
>  	help
>  	  Support the DMA engine for ZTE ZX296702 platform devices.
> 
> +config ST_FDMA
> +	tristate "ST FDMA dmaengine support"
> +	depends on ARCH_STI
> +	select DMA_ENGINE
> +	select FW_LOADER
> +	select DMA_VIRTUAL_CHANNELS
> +	help
> +	  Enable support for ST FDMA controller.
> +	  It supports 16 independent DMA channels, accepts up to 32 DMA
> requests
> +
> +	  Say Y here if you have such a chipset.
> +	  If unsure, say N.
> 
>  # driver files
>  source "drivers/dma/bestcomm/Kconfig"
> diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
> index 6084127..b81ca99 100644
> --- a/drivers/dma/Makefile
> +++ b/drivers/dma/Makefile
> @@ -65,6 +65,7 @@ obj-$(CONFIG_TI_DMA_CROSSBAR) += ti-dma-crossbar.o
>  obj-$(CONFIG_TI_EDMA) += edma.o
>  obj-$(CONFIG_XGENE_DMA) += xgene-dma.o
>  obj-$(CONFIG_ZX_DMA) += zx296702_dma.o
> +obj-$(CONFIG_ST_FDMA) += st_fdma.o
> 
>  obj-y += qcom/
>  obj-y += xilinx/
> diff --git a/drivers/dma/st_fdma.c b/drivers/dma/st_fdma.c
> new file mode 100644
> index 0000000..9bf0100
> --- /dev/null
> +++ b/drivers/dma/st_fdma.c
> @@ -0,0 +1,967 @@
> +/*
> + * st_fdma.c
> + *
> + * Copyright (C) 2014 STMicroelectronics
> + * Author: Ludovic Barre <Ludovic.barre at st.com>
> + * License terms:  GNU General Public License (GPL), version 2
> + */
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_dma.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/clk.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dmapool.h>
> +#include <linux/firmware.h>
> +#include <linux/elf.h>
> +#include <linux/atomic.h>
> +
> +#include "st_fdma.h"
> +#include "dmaengine.h"
> +#include "virt-dma.h"
> +
> +static char *fdma_clk_name[CLK_MAX_NUM] = {
> +	[CLK_SLIM]	= "fdma_slim",
> +	[CLK_HI]	= "fdma_hi",
> +	[CLK_LOW]	= "fdma_low",
> +	[CLK_IC]	= "fdma_ic",
> +};
> +
> +static int st_fdma_clk_get(struct st_fdma_dev *fdev)
> +{
> +	int i;
> +
> +	for (i = 0; i < CLK_MAX_NUM; i++) {
> +		fdev->clks[i] = devm_clk_get(fdev->dev, fdma_clk_name[i]);
> +		if (IS_ERR(fdev->clks[i])) {
> +			dev_err(fdev->dev,
> +				"failed to get clock: %s\n", fdma_clk_name[i]);
> +			return PTR_ERR(fdev->clks[i]);
> +		}
> +	}
> +
> +	if (i != CLK_MAX_NUM) {
> +		dev_err(fdev->dev, "all clocks are not defined\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int st_fdma_clk_enable(struct st_fdma_dev *fdev)
> +{
> +	int i, ret;
> +
> +	for (i = 0; i < CLK_MAX_NUM; i++) {
> +		ret = clk_prepare_enable(fdev->clks[i]);
> +		if (ret < 0)

You should disable and unprepared the other clocks...

Kedar...

> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void st_fdma_clk_disable(struct st_fdma_dev *fdev)
> +{
> +	int i;
> +
> +	for (i = 0; i < CLK_MAX_NUM; i++)
> +		clk_disable_unprepare(fdev->clks[i]);
> +}
> +
> +static inline struct st_fdma_chan *to_st_fdma_chan(struct dma_chan *c)
> +{
> +	return container_of(c, struct st_fdma_chan, vchan.chan);
> +}
> +
> +static struct st_fdma_desc *to_st_fdma_desc(struct virt_dma_desc *vd)
> +{
> +	return container_of(vd, struct st_fdma_desc, vdesc);
> +}
> +
> +static void st_fdma_enable(struct st_fdma_dev *fdev)
> +{
> +	unsigned long hw_id, hw_ver, fw_rev;
> +	u32 val;
> +
> +	/* disable CPU pipeline clock & reset cpu pipeline */
> +	val = FDMA_CLK_GATE_DIS | FDMA_CLK_GATE_RESET;
> +	fdma_write(fdev, val, CLK_GATE);
> +	/* disable SLIM core STBus sync */
> +	fdma_write(fdev, FDMA_STBUS_SYNC_DIS, STBUS_SYNC);
> +	/* enable cpu pipeline clock */
> +	fdma_write(fdev, !FDMA_CLK_GATE_DIS, CLK_GATE);
> +	/* clear int & cmd mailbox */
> +	fdma_write(fdev, ~0UL, INT_CLR);
> +	fdma_write(fdev, ~0UL, CMD_CLR);
> +	/* enable all channels cmd & int */
> +	fdma_write(fdev, ~0UL, INT_MASK);
> +	fdma_write(fdev, ~0UL, CMD_MASK);
> +	/* enable cpu */
> +	writel(FDMA_EN_RUN, fdev->io_base + FDMA_EN_OFST);
> +
> +	hw_id = fdma_read(fdev, ID);
> +	hw_ver = fdma_read(fdev, VER);
> +	fw_rev = fdma_read(fdev, REV_ID);
> +
> +	dev_info(fdev->dev, "fw rev:%ld.%ld on SLIM %ld.%ld\n",
> +		 FDMA_REV_ID_MAJ(fw_rev), FDMA_REV_ID_MIN(fw_rev),
> +		 hw_id, hw_ver);
> +}
> +
> +static int st_fdma_disable(struct st_fdma_dev *fdev)
> +{
> +	/* mask all (cmd & int) channels */
> +	fdma_write(fdev, 0UL, INT_MASK);
> +	fdma_write(fdev, 0UL, CMD_MASK);
> +	/* disable cpu pipeline clock */
> +	fdma_write(fdev, FDMA_CLK_GATE_DIS, CLK_GATE);
> +	writel(!FDMA_EN_RUN, fdev->io_base + FDMA_EN_OFST);
> +
> +	return readl(fdev->io_base + FDMA_EN_OFST);
> +}
> +
> +static int st_fdma_dreq_get(struct st_fdma_chan *fchan)
> +{
> +	struct st_fdma_dev *fdev = fchan->fdev;
> +	u32 req_line_cfg = fchan->cfg.req_line;
> +	u32 dreq_line;
> +	int try = 0;
> +
> +	/*
> +	 * dreq_mask is shared for n channels of fdma, so all accesses must be
> +	 * atomic. if the dreq_mask it change between ffz ant set_bit,
> +	 * we retry
> +	 */
> +	do {
> +		if (fdev->dreq_mask == ~0L) {
> +			dev_err(fdev->dev, "No req lines available\n");
> +			return -EINVAL;
> +		}
> +
> +		if (try || req_line_cfg >= ST_FDMA_NR_DREQS) {
> +			dev_err(fdev->dev, "Invalid or used req line\n");
> +			return -EINVAL;
> +		} else {
> +			dreq_line = req_line_cfg;
> +		}
> +
> +		try++;
> +	} while (test_and_set_bit(dreq_line, &fdev->dreq_mask));
> +
> +	dev_dbg(fdev->dev, "get dreq_line:%d mask:%#lx\n",
> +		dreq_line, fdev->dreq_mask);
> +
> +	return dreq_line;
> +}
> +
> +static void st_fdma_dreq_put(struct st_fdma_chan *fchan)
> +{
> +	struct st_fdma_dev *fdev = fchan->fdev;
> +
> +	dev_dbg(fdev->dev, "put dreq_line:%#x\n", fchan->dreq_line);
> +	clear_bit(fchan->dreq_line, &fdev->dreq_mask);
> +}
> +
> +static void st_fdma_xfer_desc(struct st_fdma_chan *fchan)
> +{
> +	struct virt_dma_desc *vdesc;
> +	unsigned long nbytes, ch_cmd, cmd;
> +
> +	vdesc = vchan_next_desc(&fchan->vchan);
> +	if (!vdesc)
> +		return;
> +
> +	fchan->fdesc = to_st_fdma_desc(vdesc);
> +	nbytes = fchan->fdesc->node[0].desc->nbytes;
> +	cmd = FDMA_CMD_START(fchan->vchan.chan.chan_id);
> +	ch_cmd = fchan->fdesc->node[0].pdesc | FDMA_CH_CMD_STA_START;
> +
> +	/* start the channel for the descriptor */
> +	fnode_write(fchan, nbytes, CNTN);
> +	fchan_write(fchan, ch_cmd, CH_CMD);
> +	writel(cmd, fchan->fdev->io_base + FDMA_CMD_SET_OFST);
> +
> +	dev_dbg(fchan->fdev->dev, "start chan:%d\n", fchan-
> >vchan.chan.chan_id);
> +}
> +
> +static void st_fdma_ch_sta_update(struct st_fdma_chan *fchan,
> +				  unsigned long int_sta)
> +{
> +	unsigned long ch_sta, ch_err;
> +	int ch_id = fchan->vchan.chan.chan_id;
> +	struct st_fdma_dev *fdev = fchan->fdev;
> +
> +	ch_sta = fchan_read(fchan, CH_CMD);
> +	ch_err = ch_sta & FDMA_CH_CMD_ERR_MASK;
> +	ch_sta &= FDMA_CH_CMD_STA_MASK;
> +
> +	if (int_sta & FDMA_INT_STA_ERR) {
> +		dev_warn(fdev->dev, "chan:%d, error:%ld\n", ch_id, ch_err);
> +		fchan->status = DMA_ERROR;
> +		return;
> +	}
> +
> +	switch (ch_sta) {
> +	case FDMA_CH_CMD_STA_PAUSED:
> +		fchan->status = DMA_PAUSED;
> +		break;
> +	case FDMA_CH_CMD_STA_RUNNING:
> +		fchan->status = DMA_IN_PROGRESS;
> +		break;
> +	}
> +}
> +
> +static irqreturn_t st_fdma_irq_handler(int irq, void *dev_id)
> +{
> +	struct st_fdma_dev *fdev = dev_id;
> +	irqreturn_t ret = IRQ_NONE;
> +	struct st_fdma_chan *fchan = &fdev->chans[0];
> +	unsigned long int_sta, clr;
> +
> +	int_sta = fdma_read(fdev, INT_STA);
> +	clr = int_sta;
> +
> +	for (; int_sta != 0 ; int_sta >>= 2, fchan++) {
> +		if (!(int_sta & (FDMA_INT_STA_CH | FDMA_INT_STA_ERR)))
> +			continue;
> +
> +		spin_lock(&fchan->vchan.lock);
> +		st_fdma_ch_sta_update(fchan, int_sta);
> +
> +		if (fchan->fdesc) {
> +			if (!fchan->fdesc->iscyclic) {
> +				list_del(&fchan->fdesc->vdesc.node);
> +				vchan_cookie_complete(&fchan->fdesc-
> >vdesc);
> +				fchan->fdesc = NULL;
> +				fchan->status = DMA_COMPLETE;
> +			} else {
> +				vchan_cyclic_callback(&fchan->fdesc->vdesc);
> +			}
> +
> +			/* Start the next descriptor (if available) */
> +			if (!fchan->fdesc)
> +				st_fdma_xfer_desc(fchan);
> +		}
> +
> +		spin_unlock(&fchan->vchan.lock);
> +		ret = IRQ_HANDLED;
> +	}
> +
> +	fdma_write(fdev, clr, INT_CLR);
> +
> +	return ret;
> +}
> +
> +static struct dma_chan *st_fdma_of_xlate(struct of_phandle_args *dma_spec,
> +					 struct of_dma *ofdma)
> +{
> +	struct st_fdma_dev *fdev = ofdma->of_dma_data;
> +	struct st_fdma_cfg cfg;
> +
> +	if (dma_spec->args_count < 1)
> +		return NULL;
> +
> +	cfg.of_node = dma_spec->np;
> +	cfg.req_line = dma_spec->args[0];
> +	cfg.req_ctrl = 0;
> +	cfg.type = ST_FDMA_TYPE_FREE_RUN;
> +
> +	if (dma_spec->args_count > 1)
> +		cfg.req_ctrl = dma_spec->args[1] & REQ_CTRL_CFG_MASK;
> +
> +	if (dma_spec->args_count > 2)
> +		cfg.type = dma_spec->args[2];
> +
> +	dev_dbg(fdev->dev, "xlate req_line:%d type:%d req_ctrl:%#lx\n",
> +		cfg.req_line, cfg.type, cfg.req_ctrl);
> +
> +	return dma_request_channel(fdev->dma_device.cap_mask,
> +			st_fdma_filter_fn, &cfg);
> +}
> +
> +static void st_fdma_free_desc(struct virt_dma_desc *vdesc)
> +{
> +	struct st_fdma_desc *fdesc;
> +	int i;
> +
> +	fdesc = to_st_fdma_desc(vdesc);
> +	for (i = 0; i < fdesc->n_nodes; i++)
> +			dma_pool_free(fdesc->fchan->node_pool,
> +				      fdesc->node[i].desc,
> +				      fdesc->node[i].pdesc);
> +	kfree(fdesc);
> +}
> +
> +static struct st_fdma_desc *st_fdma_alloc_desc(struct st_fdma_chan *fchan,
> +					       int sg_len)
> +{
> +	struct st_fdma_desc *fdesc;
> +	int i;
> +
> +	fdesc = kzalloc(sizeof(*fdesc) +
> +			sizeof(struct st_fdma_sw_node) * sg_len,
> GFP_NOWAIT);
> +	if (!fdesc)
> +		return NULL;
> +
> +	fdesc->fchan = fchan;
> +	fdesc->n_nodes = sg_len;
> +	for (i = 0; i < sg_len; i++) {
> +		fdesc->node[i].desc = dma_pool_alloc(fchan->node_pool,
> +				GFP_NOWAIT, &fdesc->node[i].pdesc);
> +		if (!fdesc->node[i].desc)
> +			goto err;
> +	}
> +	return fdesc;
> +
> +err:
> +	while (--i >= 0)
> +		dma_pool_free(fchan->node_pool, fdesc->node[i].desc,
> +			      fdesc->node[i].pdesc);
> +	kfree(fdesc);
> +	return NULL;
> +}
> +
> +static int st_fdma_alloc_chan_res(struct dma_chan *chan)
> +{
> +	struct st_fdma_chan *fchan = to_st_fdma_chan(chan);
> +
> +	if (fchan->cfg.type == ST_FDMA_TYPE_FREE_RUN) {
> +		fchan->dreq_line = 0;
> +	} else {
> +		fchan->dreq_line = st_fdma_dreq_get(fchan);
> +		if (IS_ERR_VALUE(fchan->dreq_line))
> +			return -EINVAL;
> +	}
> +
> +	/* Create the dma pool for descriptor allocation */
> +	fchan->node_pool = dmam_pool_create(dev_name(&chan->dev-
> >device),
> +					    fchan->fdev->dev,
> +					    sizeof(struct st_fdma_hw_node),
> +					    __alignof__(struct
> st_fdma_hw_node),
> +					    0);
> +
> +	if (!fchan->node_pool) {
> +		dev_err(fchan->fdev->dev, "unable to allocate desc pool\n");
> +		return -ENOMEM;
> +	}
> +
> +	dev_dbg(fchan->fdev->dev, "alloc ch_id:%d type:%d\n",
> +		fchan->vchan.chan.chan_id, fchan->cfg.type);
> +
> +	return 0;
> +}
> +
> +static void st_fdma_free_chan_res(struct dma_chan *chan)
> +{
> +	struct st_fdma_chan *fchan = to_st_fdma_chan(chan);
> +	unsigned long flags;
> +	LIST_HEAD(head);
> +
> +	dev_dbg(fchan->fdev->dev, "freeing chan:%d\n",
> +		fchan->vchan.chan.chan_id);
> +
> +	if (fchan->cfg.type != ST_FDMA_TYPE_FREE_RUN)
> +		st_fdma_dreq_put(fchan);
> +
> +	spin_lock_irqsave(&fchan->vchan.lock, flags);
> +	fchan->fdesc = NULL;
> +	vchan_get_all_descriptors(&fchan->vchan, &head);
> +	spin_unlock_irqrestore(&fchan->vchan.lock, flags);
> +
> +	dma_pool_destroy(fchan->node_pool);
> +	fchan->node_pool = NULL;
> +	memset(&fchan->cfg, 0, sizeof(struct st_fdma_cfg));
> +}
> +
> +static struct dma_async_tx_descriptor *st_fdma_prep_dma_memcpy(
> +	struct dma_chan *chan,	dma_addr_t dst, dma_addr_t src,
> +	size_t len, unsigned long flags)
> +{
> +	struct st_fdma_chan *fchan;
> +	struct st_fdma_desc *fdesc;
> +	struct st_fdma_hw_node *hw_node;
> +
> +	if (!len)
> +		return NULL;
> +
> +	fchan = to_st_fdma_chan(chan);
> +
> +	if (!atomic_read(&fchan->fdev->fw_loaded)) {
> +		dev_err(fchan->fdev->dev, "%s: fdma fw not loaded\n",
> __func__);
> +		return NULL;
> +	}
> +
> +	/* We only require a single descriptor */
> +	fdesc = st_fdma_alloc_desc(fchan, 1);
> +	if (!fdesc) {
> +		dev_err(fchan->fdev->dev, "no memory for desc\n");
> +		return NULL;
> +	}
> +
> +	hw_node = fdesc->node[0].desc;
> +	hw_node->next = 0;
> +	hw_node->control = NODE_CTRL_REQ_MAP_FREE_RUN;
> +	hw_node->control |= NODE_CTRL_SRC_INCR;
> +	hw_node->control |= NODE_CTRL_DST_INCR;
> +	hw_node->control |= NODE_CTRL_INT_EON;
> +	hw_node->nbytes = len;
> +	hw_node->saddr = src;
> +	hw_node->daddr = dst;
> +	hw_node->generic.length = len;
> +	hw_node->generic.sstride = 0;
> +	hw_node->generic.dstride = 0;
> +
> +	return vchan_tx_prep(&fchan->vchan, &fdesc->vdesc, flags);
> +}
> +
> +static int config_reqctrl(struct st_fdma_chan *fchan,
> +			  enum dma_transfer_direction direction)
> +{
> +	u32 maxburst = 0, addr = 0;
> +	enum dma_slave_buswidth width;
> +	int ch_id = fchan->vchan.chan.chan_id;
> +	struct st_fdma_dev *fdev = fchan->fdev;
> +
> +	if (direction == DMA_DEV_TO_MEM) {
> +		fchan->cfg.req_ctrl &= ~REQ_CTRL_WNR;
> +		maxburst = fchan->scfg.src_maxburst;
> +		width = fchan->scfg.src_addr_width;
> +		addr = fchan->scfg.src_addr;
> +	} else if (direction == DMA_MEM_TO_DEV) {
> +		fchan->cfg.req_ctrl |= REQ_CTRL_WNR;
> +		maxburst = fchan->scfg.dst_maxburst;
> +		width = fchan->scfg.dst_addr_width;
> +		addr = fchan->scfg.dst_addr;
> +	} else {
> +		return -EINVAL;
> +	}
> +
> +	fchan->cfg.req_ctrl &= ~REQ_CTRL_OPCODE_MASK;
> +	if (width == DMA_SLAVE_BUSWIDTH_1_BYTE)
> +		fchan->cfg.req_ctrl |= REQ_CTRL_OPCODE_LD_ST1;
> +	else if (width == DMA_SLAVE_BUSWIDTH_2_BYTES)
> +		fchan->cfg.req_ctrl |= REQ_CTRL_OPCODE_LD_ST2;
> +	else if (width == DMA_SLAVE_BUSWIDTH_4_BYTES)
> +		fchan->cfg.req_ctrl |= REQ_CTRL_OPCODE_LD_ST4;
> +	else if (width == DMA_SLAVE_BUSWIDTH_8_BYTES)
> +		fchan->cfg.req_ctrl |= REQ_CTRL_OPCODE_LD_ST8;
> +	else
> +		return -EINVAL;
> +
> +	fchan->cfg.req_ctrl &= ~REQ_CTRL_NUM_OPS_MASK;
> +	fchan->cfg.req_ctrl |= REQ_CTRL_NUM_OPS(maxburst-1);
> +	dreq_write(fchan, fchan->cfg.req_ctrl, REQ_CTRL);
> +
> +	fchan->cfg.dev_addr = addr;
> +	fchan->cfg.dir = direction;
> +
> +	dev_dbg(fdev->dev, "chan:%d config_reqctrl:%#x req_ctrl:%#lx\n",
> +		ch_id, addr, fchan->cfg.req_ctrl);
> +
> +	return 0;
> +}
> +
> +static void fill_hw_node(struct st_fdma_hw_node *hw_node,
> +			struct st_fdma_chan *fchan,
> +			enum dma_transfer_direction direction)
> +{
> +
> +	if (direction == DMA_MEM_TO_DEV) {
> +		hw_node->control |= NODE_CTRL_SRC_INCR;
> +		hw_node->control |= NODE_CTRL_DST_STATIC;
> +		hw_node->daddr = fchan->cfg.dev_addr;
> +	} else {
> +		hw_node->control |= NODE_CTRL_SRC_STATIC;
> +		hw_node->control |= NODE_CTRL_DST_INCR;
> +		hw_node->saddr = fchan->cfg.dev_addr;
> +	}
> +	hw_node->generic.sstride = 0;
> +	hw_node->generic.dstride = 0;
> +}
> +
> +static struct dma_async_tx_descriptor *st_fdma_prep_dma_cyclic(
> +		struct dma_chan *chan, dma_addr_t buf_addr, size_t len,
> +		size_t period_len, enum dma_transfer_direction direction,
> +		unsigned long flags)
> +{
> +	struct st_fdma_chan *fchan;
> +	struct st_fdma_desc *fdesc;
> +	int sg_len, i;
> +
> +	if (!chan || !len || !period_len)
> +		return NULL;
> +
> +	fchan = to_st_fdma_chan(chan);
> +
> +	if (!atomic_read(&fchan->fdev->fw_loaded)) {
> +		dev_err(fchan->fdev->dev, "%s: fdma fw not loaded\n",
> __func__);
> +		return NULL;
> +	}
> +
> +	if (!is_slave_direction(direction)) {
> +		dev_err(fchan->fdev->dev, "bad direction?\n");
> +		return NULL;
> +	}
> +
> +	if (config_reqctrl(fchan, direction)) {
> +		dev_err(fchan->fdev->dev, "bad width or direction\n");
> +		return NULL;
> +	}
> +
> +	/* the buffer length must be a multiple of period_len */
> +	if (len % period_len != 0) {
> +		dev_err(fchan->fdev->dev, "len is not multiple of period\n");
> +		return NULL;
> +	}
> +
> +	sg_len = len / period_len;
> +	fdesc = st_fdma_alloc_desc(fchan, sg_len);
> +	if (!fdesc) {
> +		dev_err(fchan->fdev->dev, "no memory for desc\n");
> +		return NULL;
> +	}
> +
> +	fdesc->iscyclic = true;
> +
> +	for (i = 0; i < sg_len; i++) {
> +		struct st_fdma_hw_node *hw_node = fdesc->node[i].desc;
> +
> +		hw_node->next = fdesc->node[(i + 1) % sg_len].pdesc;
> +
> +		hw_node->control = NODE_CTRL_REQ_MAP_DREQ(fchan-
> >dreq_line);
> +		hw_node->control |= NODE_CTRL_INT_EON;
> +
> +
> +		fill_hw_node(hw_node, fchan, direction);
> +
> +		if (direction == DMA_MEM_TO_DEV)
> +			hw_node->saddr = buf_addr + (i * period_len);
> +		else
> +			hw_node->daddr = buf_addr + (i * period_len);
> +
> +		hw_node->nbytes = period_len;
> +		hw_node->generic.length = period_len;
> +	}
> +
> +	return vchan_tx_prep(&fchan->vchan, &fdesc->vdesc, flags);
> +}
> +
> +static struct dma_async_tx_descriptor *st_fdma_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 st_fdma_chan *fchan;
> +	struct st_fdma_desc *fdesc;
> +	struct st_fdma_hw_node *hw_node;
> +	struct scatterlist *sg;
> +	int i;
> +
> +	if (!chan || !sgl || !sg_len)
> +		return NULL;
> +
> +	fchan = to_st_fdma_chan(chan);
> +
> +	if (!atomic_read(&fchan->fdev->fw_loaded)) {
> +		dev_err(fchan->fdev->dev, "%s: fdma fw not loaded\n",
> __func__);
> +		return NULL;
> +	}
> +
> +	if (!is_slave_direction(direction)) {
> +		dev_err(fchan->fdev->dev, "bad direction?\n");
> +		return NULL;
> +	}
> +
> +	fdesc = st_fdma_alloc_desc(fchan, sg_len);
> +	if (!fdesc) {
> +		dev_err(fchan->fdev->dev, "no memory for desc\n");
> +		return NULL;
> +	}
> +
> +	fdesc->iscyclic = false;
> +
> +	for_each_sg(sgl, sg, sg_len, i) {
> +		hw_node = fdesc->node[i].desc;
> +
> +		hw_node->next = fdesc->node[(i + 1) % sg_len].pdesc;
> +		hw_node->control = NODE_CTRL_REQ_MAP_DREQ(fchan-
> >dreq_line);
> +
> +		fill_hw_node(hw_node, fchan, direction);
> +
> +		if (direction == DMA_MEM_TO_DEV)
> +			hw_node->saddr = sg_dma_address(sg);
> +		else
> +			hw_node->daddr = sg_dma_address(sg);
> +
> +		hw_node->nbytes = sg_dma_len(sg);
> +		hw_node->generic.length = sg_dma_len(sg);
> +	}
> +
> +	/* interrupt at end of last node */
> +	hw_node->control |= NODE_CTRL_INT_EON;
> +
> +	return vchan_tx_prep(&fchan->vchan, &fdesc->vdesc, flags);
> +}
> +
> +static size_t st_fdma_desc_residue(struct st_fdma_chan *fchan,
> +				   struct virt_dma_desc *vdesc,
> +				   bool in_progress)
> +{
> +	struct st_fdma_desc *fdesc = fchan->fdesc;
> +	size_t residue = 0;
> +	dma_addr_t cur_addr = 0;
> +	int i;
> +
> +	if (in_progress) {
> +		cur_addr = fchan_read(fchan, CH_CMD);
> +		cur_addr &= FDMA_CH_CMD_DATA_MASK;
> +	}
> +
> +	for (i = fchan->fdesc->n_nodes - 1 ; i >= 0; i--) {
> +		if (cur_addr == fdesc->node[i].pdesc) {
> +			residue += fnode_read(fchan, CNTN);
> +			break;
> +		}
> +		residue += fdesc->node[i].desc->nbytes;
> +	}
> +
> +	return residue;
> +}
> +
> +static enum dma_status st_fdma_tx_status(struct dma_chan *chan,
> +					 dma_cookie_t cookie,
> +					 struct dma_tx_state *txstate)
> +{
> +	struct st_fdma_chan *fchan = to_st_fdma_chan(chan);
> +	struct virt_dma_desc *vd;
> +	enum dma_status ret;
> +	unsigned long flags;
> +
> +	ret = dma_cookie_status(chan, cookie, txstate);
> +	if (ret == DMA_COMPLETE)
> +		return ret;
> +
> +	if (!txstate)
> +		return fchan->status;
> +
> +	spin_lock_irqsave(&fchan->vchan.lock, flags);
> +	vd = vchan_find_desc(&fchan->vchan, cookie);
> +	if (fchan->fdesc && cookie == fchan->fdesc->vdesc.tx.cookie)
> +		txstate->residue = st_fdma_desc_residue(fchan, vd, true);
> +	else if (vd)
> +		txstate->residue = st_fdma_desc_residue(fchan, vd, false);
> +	else
> +		txstate->residue = 0;
> +
> +	spin_unlock_irqrestore(&fchan->vchan.lock, flags);
> +
> +	return fchan->status;
> +}
> +
> +static void st_fdma_issue_pending(struct dma_chan *chan)
> +{
> +	struct st_fdma_chan *fchan = to_st_fdma_chan(chan);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&fchan->vchan.lock, flags);
> +
> +	if (vchan_issue_pending(&fchan->vchan) && !fchan->fdesc)
> +		st_fdma_xfer_desc(fchan);
> +
> +	spin_unlock_irqrestore(&fchan->vchan.lock, flags);
> +}
> +
> +static int st_fdma_pause(struct dma_chan *chan)
> +{
> +	unsigned long flags;
> +	LIST_HEAD(head);
> +	struct st_fdma_chan *fchan = to_st_fdma_chan(chan);
> +	int ch_id = fchan->vchan.chan.chan_id;
> +	unsigned long cmd = FDMA_CMD_PAUSE(ch_id);
> +
> +	dev_dbg(fchan->fdev->dev, "pause chan:%d\n", ch_id);
> +
> +	spin_lock_irqsave(&fchan->vchan.lock, flags);
> +	if (fchan->fdesc)
> +		fdma_write(fchan->fdev, cmd, CMD_SET);
> +	spin_unlock_irqrestore(&fchan->vchan.lock, flags);
> +
> +	return 0;
> +}
> +
> +static int st_fdma_resume(struct dma_chan *chan)
> +{
> +	unsigned long flags;
> +	unsigned long val;
> +	struct st_fdma_chan *fchan = to_st_fdma_chan(chan);
> +	int ch_id = fchan->vchan.chan.chan_id;
> +
> +	dev_dbg(fchan->fdev->dev, "resume chan:%d\n", ch_id);
> +
> +	spin_lock_irqsave(&fchan->vchan.lock, flags);
> +	if (fchan->fdesc) {
> +		val = fchan_read(fchan, CH_CMD);
> +		val &= FDMA_CH_CMD_DATA_MASK;
> +		fchan_write(fchan, val, CH_CMD);
> +	}
> +	spin_unlock_irqrestore(&fchan->vchan.lock, flags);
> +
> +	return 0;
> +}
> +
> +static int st_fdma_terminate_all(struct dma_chan *chan)
> +{
> +	unsigned long flags;
> +	LIST_HEAD(head);
> +	struct st_fdma_chan *fchan = to_st_fdma_chan(chan);
> +	int ch_id = fchan->vchan.chan.chan_id;
> +	unsigned long cmd = FDMA_CMD_PAUSE(ch_id);
> +
> +	dev_dbg(fchan->fdev->dev, "terminate chan:%d\n", ch_id);
> +
> +	spin_lock_irqsave(&fchan->vchan.lock, flags);
> +	fdma_write(fchan->fdev, cmd, CMD_SET);
> +	fchan->fdesc = NULL;
> +	vchan_get_all_descriptors(&fchan->vchan, &head);
> +	spin_unlock_irqrestore(&fchan->vchan.lock, flags);
> +	vchan_dma_desc_free_list(&fchan->vchan, &head);
> +
> +	return 0;
> +}
> +
> +static int st_fdma_slave_config(struct dma_chan *chan,
> +				struct dma_slave_config *slave_cfg)
> +{
> +	struct st_fdma_chan *fchan = to_st_fdma_chan(chan);
> +	memcpy(&fchan->scfg, slave_cfg, sizeof(fchan->scfg));
> +	return 0;
> +}
> +
> +static const struct st_fdma_ram fdma_mpe31_mem[] = {
> +	{ .name = "dmem", .offset = 0x10000, .size = 0x3000 },
> +	{ .name = "imem", .offset = 0x18000, .size = 0x8000 },
> +};
> +
> +static const struct st_fdma_driverdata fdma_mpe31_stih407_11 = {
> +	.fdma_mem = fdma_mpe31_mem,
> +	.num_mem = ARRAY_SIZE(fdma_mpe31_mem),
> +	.name = "STiH407",
> +	.id = 0,
> +};
> +
> +static const struct st_fdma_driverdata fdma_mpe31_stih407_12 = {
> +	.fdma_mem = fdma_mpe31_mem,
> +	.num_mem = ARRAY_SIZE(fdma_mpe31_mem),
> +	.name = "STiH407",
> +	.id = 1,
> +};
> +
> +static const struct st_fdma_driverdata fdma_mpe31_stih407_13 = {
> +	.fdma_mem = fdma_mpe31_mem,
> +	.num_mem = ARRAY_SIZE(fdma_mpe31_mem),
> +	.name = "STiH407",
> +	.id = 2,
> +};
> +
> +static const struct of_device_id st_fdma_match[] = {
> +	{ .compatible = "st,stih407-fdma-mpe31-11"
> +	  , .data = &fdma_mpe31_stih407_11 },
> +	{ .compatible = "st,stih407-fdma-mpe31-12"
> +	  , .data = &fdma_mpe31_stih407_12 },
> +	{ .compatible = "st,stih407-fdma-mpe31-13"
> +	  , .data = &fdma_mpe31_stih407_13 },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, st_fdma_match);
> +
> +static int st_fdma_parse_dt(struct platform_device *pdev,
> +			const struct st_fdma_driverdata *drvdata,
> +			struct st_fdma_dev *fdev)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	int ret;
> +
> +	if (!np)
> +		goto err;
> +
> +	ret = of_property_read_u32(np, "dma-channels", &fdev->nr_channels);
> +	if (ret)
> +		goto err;
> +
> +	snprintf(fdev->fw_name, FW_NAME_SIZE, "fdma_%s_%d.elf",
> +		drvdata->name, drvdata->id);
> +
> +err:
> +	return ret;
> +}
> +#define FDMA_DMA_BUSWIDTHS	(BIT(DMA_SLAVE_BUSWIDTH_1_BYTE)
> | \
> +				 BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
> +				 BIT(DMA_SLAVE_BUSWIDTH_3_BYTES) | \
> +				 BIT(DMA_SLAVE_BUSWIDTH_4_BYTES))
> +
> +static int st_fdma_probe(struct platform_device *pdev)
> +{
> +	struct st_fdma_dev *fdev;
> +	const struct of_device_id *match;
> +	struct device_node *np = pdev->dev.of_node;
> +	const struct st_fdma_driverdata *drvdata;
> +	int irq, ret, i;
> +
> +	match = of_match_device((st_fdma_match), &pdev->dev);
> +	if (!match || !match->data) {
> +		dev_err(&pdev->dev, "No device match found\n");
> +		return -ENODEV;
> +	}
> +
> +	drvdata = match->data;
> +
> +	fdev = devm_kzalloc(&pdev->dev, sizeof(*fdev), GFP_KERNEL);
> +	if (!fdev)
> +		return -ENOMEM;
> +
> +	ret = st_fdma_parse_dt(pdev, drvdata, fdev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "unable to find platform data\n");
> +		goto err;
> +	}
> +
> +	fdev->chans = devm_kzalloc(&pdev->dev,
> +				   fdev->nr_channels
> +				   * sizeof(struct st_fdma_chan), GFP_KERNEL);
> +	if (!fdev->chans)
> +		return -ENOMEM;
> +
> +	fdev->dev = &pdev->dev;
> +	fdev->drvdata = drvdata;
> +	platform_set_drvdata(pdev, fdev);
> +
> +	fdev->io_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	fdev->io_base = devm_ioremap_resource(&pdev->dev, fdev->io_res);
> +	if (IS_ERR(fdev->io_base))
> +		return PTR_ERR(fdev->io_base);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(&pdev->dev, "Failed to get irq resource\n");
> +		return -EINVAL;
> +	}
> +
> +	ret = devm_request_irq(&pdev->dev, irq, st_fdma_irq_handler, 0,
> +			       dev_name(&pdev->dev), fdev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to request irq\n");
> +		goto err;
> +	}
> +
> +	ret = st_fdma_clk_get(fdev);
> +	if (ret)
> +		goto err;
> +
> +	ret = st_fdma_clk_enable(fdev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to enable clocks\n");
> +		goto err_clk;
> +	}
> +
> +	/* Initialise list of FDMA channels */
> +	INIT_LIST_HEAD(&fdev->dma_device.channels);
> +	for (i = 0; i < fdev->nr_channels; i++) {
> +		struct st_fdma_chan *fchan = &fdev->chans[i];
> +
> +		fchan->fdev = fdev;
> +		fchan->vchan.desc_free = st_fdma_free_desc;
> +		vchan_init(&fchan->vchan, &fdev->dma_device);
> +	}
> +
> +	/* Initialise the FDMA dreq (reserve 0 & 31 for FDMA use) */
> +	fdev->dreq_mask = BIT(0) | BIT(31);
> +
> +	dma_cap_set(DMA_SLAVE, fdev->dma_device.cap_mask);
> +	dma_cap_set(DMA_CYCLIC, fdev->dma_device.cap_mask);
> +	dma_cap_set(DMA_MEMCPY, fdev->dma_device.cap_mask);
> +
> +	fdev->dma_device.dev = &pdev->dev;
> +	fdev->dma_device.device_alloc_chan_resources =
> st_fdma_alloc_chan_res;
> +	fdev->dma_device.device_free_chan_resources =
> st_fdma_free_chan_res;
> +	fdev->dma_device.device_prep_dma_cyclic	=
> st_fdma_prep_dma_cyclic;
> +	fdev->dma_device.device_prep_slave_sg = st_fdma_prep_slave_sg;
> +	fdev->dma_device.device_prep_dma_memcpy =
> st_fdma_prep_dma_memcpy;
> +	fdev->dma_device.device_tx_status = st_fdma_tx_status;
> +	fdev->dma_device.device_issue_pending = st_fdma_issue_pending;
> +	fdev->dma_device.device_terminate_all = st_fdma_terminate_all;
> +	fdev->dma_device.device_config = st_fdma_slave_config;
> +	fdev->dma_device.device_pause = st_fdma_pause;
> +	fdev->dma_device.device_resume = st_fdma_resume;
> +
> +	fdev->dma_device.src_addr_widths = FDMA_DMA_BUSWIDTHS;
> +	fdev->dma_device.dst_addr_widths = FDMA_DMA_BUSWIDTHS;
> +	fdev->dma_device.directions = BIT(DMA_DEV_TO_MEM) |
> BIT(DMA_MEM_TO_DEV);
> +	fdev->dma_device.residue_granularity =
> DMA_RESIDUE_GRANULARITY_BURST;
> +
> +	ret = dma_async_device_register(&fdev->dma_device);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register DMA device\n");
> +		goto err_clk;
> +	}
> +
> +	ret = of_dma_controller_register(np, st_fdma_of_xlate, fdev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register controller\n");
> +		goto err_dma_dev;
> +	}
> +
> +	dev_info(&pdev->dev, "ST FDMA engine driver, irq:%d\n", irq);
> +
> +	return 0;
> +
> +err_dma_dev:
> +	dma_async_device_unregister(&fdev->dma_device);
> +err_clk:
> +	st_fdma_clk_disable(fdev);
> +err:
> +	return ret;
> +}
> +
> +static int st_fdma_remove(struct platform_device *pdev)
> +{
> +	struct st_fdma_dev *fdev = platform_get_drvdata(pdev);
> +
> +	st_fdma_clk_disable(fdev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver st_fdma_platform_driver = {
> +	.driver = {
> +		.name = "st-fdma",
> +		.of_match_table = st_fdma_match,
> +	},
> +	.probe = st_fdma_probe,
> +	.remove = st_fdma_remove,
> +};
> +module_platform_driver(st_fdma_platform_driver);
> +
> +bool st_fdma_filter_fn(struct dma_chan *chan, void *param)
> +{
> +	struct st_fdma_cfg *config = param;
> +	struct st_fdma_chan *fchan = to_st_fdma_chan(chan);
> +
> +	if (!param)
> +		return false;
> +
> +	if (fchan->fdev->dma_device.dev->of_node != config->of_node)
> +		return false;
> +
> +	fchan->cfg = *config;
> +
> +	return true;
> +}
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("STMicroelectronics FDMA engine driver");
> +MODULE_AUTHOR("Ludovic.barre <Ludovic.barre at st.com>");
> --
> 1.9.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe dmaengine" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html



More information about the linux-arm-kernel mailing list