> -----Original Message-----
> From: dmaengine-ow...@vger.kernel.org [mailto:dmaengine-
> ow...@vger.kernel.org] On Behalf Of Peter Griffin
> Sent: Thursday, April 21, 2016 4:34 PM
> To: linux-arm-ker...@lists.infradead.org; linux-kernel@vger.kernel.org;
> srinivas.kandaga...@gmail.com; maxime.coque...@st.com;
> patrice.chot...@st.com; vinod.k...@intel.com
> Cc: peter.grif...@linaro.org; lee.jo...@linaro.org;
> dmaeng...@vger.kernel.org; devicet...@vger.kernel.org; a...@arndb.de;
> broo...@kernel.org; ludovic.ba...@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.ba...@st.com>
> Signed-off-by: Peter Griffin <peter.grif...@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.ba...@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.ba...@st.com>");
> --
> 1.9.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe dmaengine" in
> the body of a message to majord...@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to