[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