summaryrefslogtreecommitdiff
path: root/sound/soc/fsl/fsl_easrc_m2m.c
diff options
context:
space:
mode:
authorShengjiu Wang <shengjiu.wang@nxp.com>2019-06-05 10:46:25 +0800
committerDong Aisheng <aisheng.dong@nxp.com>2019-11-25 15:48:43 +0800
commitd1d594749325328e72dd61bff1dbd43108d17d27 (patch)
treeca91dea6996cadfde1fbc3a92233cdd87fa83156 /sound/soc/fsl/fsl_easrc_m2m.c
parentafd67f633160e70924b22481d2c3a5d8f56c6e0b (diff)
MLK-21940-4: ASoC: fsl_easrc: Support the m2m function
EASRC M2M function is not able to put upstream due to its self-designed ioctl protocol. So make a single patch for it and make it merge into P2P driver as simply as possible. The patch can only be maintained internally unless some one designs a new protocol or implement the originally protocol by using some common approach provided by Linux Kernel. Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com> Reviewed-by: Daniel Baluta <daniel.baluta@nxp.com> Reviewed-by: Viorel Suman <viorel.suman@nxp.com>
Diffstat (limited to 'sound/soc/fsl/fsl_easrc_m2m.c')
-rw-r--r--sound/soc/fsl/fsl_easrc_m2m.c956
1 files changed, 956 insertions, 0 deletions
diff --git a/sound/soc/fsl/fsl_easrc_m2m.c b/sound/soc/fsl/fsl_easrc_m2m.c
new file mode 100644
index 000000000000..6acbb3b3e8d3
--- /dev/null
+++ b/sound/soc/fsl/fsl_easrc_m2m.c
@@ -0,0 +1,956 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2019 NXP
+
+struct fsl_easrc_m2m {
+ struct fsl_easrc *easrc;
+ struct fsl_easrc_context *ctx;
+ struct completion complete[2];
+ struct dma_block dma_block[2];
+ unsigned int ctx_hold;
+ unsigned int easrc_active;
+ unsigned int first_convert;
+ unsigned int sg_nodes[2];
+ struct scatterlist sg[2][9];
+ struct dma_async_tx_descriptor *desc[2];
+ spinlock_t lock; /* protect mem resource */
+};
+
+void fsl_easrc_get_status(struct fsl_easrc_context *ctx,
+ struct asrc_status_flags *flags)
+{
+ flags->overload_error = 0;
+}
+
+#define mxc_easrc_dma_umap_in(dev, m2m) \
+ dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \
+ DMA_MEM_TO_DEV) \
+
+#define mxc_easrc_dma_umap_out(dev, m2m) \
+ dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \
+ DMA_DEV_TO_MEM) \
+
+#define EASRC_xPUT_DMA_CALLBACK(dir) \
+ ((dir == IN) ? fsl_easrc_input_dma_callback \
+ : fsl_easrc_output_dma_callback)
+
+#define DIR_STR(dir) dir == IN ? "in" : "out"
+
+static void fsl_easrc_input_dma_callback(void *data)
+{
+ struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data;
+
+ complete(&m2m->complete[IN]);
+}
+
+static void fsl_easrc_output_dma_callback(void *data)
+{
+ struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data;
+
+ complete(&m2m->complete[OUT]);
+}
+
+static int fsl_allocate_dma_buf(struct fsl_easrc_m2m *m2m)
+{
+ struct dma_block *input = &m2m->dma_block[IN];
+ struct dma_block *output = &m2m->dma_block[OUT];
+
+ input->dma_vaddr = kzalloc(input->length, GFP_KERNEL);
+ if (!input->dma_vaddr)
+ return -ENOMEM;
+
+ output->dma_vaddr = kzalloc(output->length, GFP_KERNEL);
+ if (!output->dma_vaddr)
+ goto alloc_fail;
+
+ return 0;
+
+alloc_fail:
+ kfree(input->dma_vaddr);
+
+ return -ENOMEM;
+}
+
+static int fsl_easrc_dmaconfig(struct fsl_easrc_m2m *m2m,
+ struct dma_chan *chan,
+ u32 dma_addr, void *buf_addr, u32 buf_len,
+ bool dir, int bits)
+{
+ struct dma_async_tx_descriptor *desc = m2m->desc[dir];
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct device *dev = &easrc->pdev->dev;
+ unsigned int sg_nent = m2m->sg_nodes[dir];
+ struct scatterlist *sg = m2m->sg[dir];
+ struct dma_slave_config slave_config;
+ enum dma_slave_buswidth buswidth;
+ int ret, i;
+
+ switch (bits) {
+ case 16:
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case 24:
+ buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES;
+ break;
+ default:
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ }
+
+ if (dir == IN) {
+ slave_config.direction = DMA_MEM_TO_DEV;
+ slave_config.dst_addr = dma_addr;
+ slave_config.dst_addr_width = buswidth;
+ slave_config.dst_maxburst =
+ ctx->in_params.fifo_wtmk * ctx->channels;
+ } else {
+ slave_config.direction = DMA_DEV_TO_MEM;
+ slave_config.src_addr = dma_addr;
+ slave_config.src_addr_width = buswidth;
+ slave_config.src_maxburst =
+ ctx->out_params.fifo_wtmk * ctx->channels;
+ }
+
+ ret = dmaengine_slave_config(chan, &slave_config);
+ if (ret) {
+ dev_err(dev, "failed to config dmaengine for %sput task: %d\n",
+ DIR_STR(dir), ret);
+ return -EINVAL;
+ }
+
+ sg_init_table(sg, sg_nent);
+ switch (sg_nent) {
+ case 1:
+ sg_init_one(sg, buf_addr, buf_len);
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ for (i = 0; i < (sg_nent - 1); i++)
+ sg_set_buf(&sg[i],
+ buf_addr + i * m2m->dma_block[dir].max_buf_size,
+ m2m->dma_block[dir].max_buf_size);
+
+ sg_set_buf(&sg[i],
+ buf_addr + i * m2m->dma_block[dir].max_buf_size,
+ buf_len - i * m2m->dma_block[dir].max_buf_size);
+ break;
+ default:
+ dev_err(dev, "invalid input DMA nodes number: %d\n", sg_nent);
+ return -EINVAL;
+ }
+
+ ret = dma_map_sg(dev, sg, sg_nent, slave_config.direction);
+ if (ret != sg_nent) {
+ dev_err(dev, "failed to map DMA sg for %sput task\n",
+ DIR_STR(dir));
+ return -EINVAL;
+ }
+
+ desc = dmaengine_prep_slave_sg(chan, sg, sg_nent,
+ slave_config.direction,
+ DMA_PREP_INTERRUPT);
+ if (!desc) {
+ dev_err(dev, "failed to prepare dmaengine for %sput task\n",
+ DIR_STR(dir));
+ return -EINVAL;
+ }
+
+ m2m->desc[dir] = desc;
+ m2m->desc[dir]->callback = EASRC_xPUT_DMA_CALLBACK(dir);
+
+ desc->callback = EASRC_xPUT_DMA_CALLBACK(dir);
+ desc->callback_param = m2m;
+
+ return 0;
+}
+
+static long fsl_easrc_calc_outbuf_len(struct fsl_easrc_m2m *m2m,
+ struct asrc_convert_buffer *pbuf)
+{
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ unsigned int out_length;
+ unsigned int in_width, out_width;
+ unsigned int channels = ctx->channels;
+ unsigned int in_samples, out_samples;
+
+ in_width = snd_pcm_format_physical_width(ctx->in_params.sample_format) / 8;
+ out_width = snd_pcm_format_physical_width(ctx->out_params.sample_format) / 8;
+
+ in_samples = pbuf->input_buffer_length / (in_width * channels);
+ out_samples = ctx->out_params.sample_rate * in_samples /
+ ctx->in_params.sample_rate;
+ out_length = out_samples * out_width * channels;
+
+ if (out_samples <= ctx->out_missed_sample) {
+ out_length = 0;
+ ctx->out_missed_sample -= out_samples;
+ } else {
+ out_length -= ctx->out_missed_sample * out_width * channels;
+ ctx->out_missed_sample = 0;
+ }
+
+ return out_length;
+}
+
+static long fsl_easrc_prepare_io_buffer(struct fsl_easrc_m2m *m2m,
+ struct asrc_convert_buffer *buf,
+ bool dir)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct dma_chan *dma_chan = ctx->dma_chan[dir];
+ unsigned int *dma_len = &m2m->dma_block[dir].length;
+ unsigned int *sg_nodes = &m2m->sg_nodes[dir];
+ void *dma_vaddr = m2m->dma_block[dir].dma_vaddr;
+ enum asrc_pair_index index = m2m->ctx->index;
+ unsigned int buf_len, bits;
+ u32 fifo_addr;
+ void __user *buf_vaddr;
+ int ret;
+
+ if (dir == IN) {
+ buf_vaddr = (void __user *)buf->input_buffer_vaddr;
+ buf_len = buf->input_buffer_length;
+ bits = snd_pcm_format_physical_width(ctx->in_params.sample_format);
+ fifo_addr = easrc->paddr + REG_EASRC_WRFIFO(index);
+ } else {
+ buf_vaddr = (void __user *)buf->output_buffer_vaddr;
+ buf_len = buf->output_buffer_length;
+ bits = snd_pcm_format_physical_width(ctx->out_params.sample_format);
+ fifo_addr = easrc->paddr + REG_EASRC_RDFIFO(index);
+ }
+
+ if (dir == IN) {
+ ret = copy_from_user(dma_vaddr, buf_vaddr, buf_len);
+ if (ret) {
+ dev_err(dev, "failed to copy input buffer %d\n", ret);
+ return ret;
+ }
+ }
+
+ *dma_len = buf_len;
+
+ if (dir == OUT)
+ *dma_len = fsl_easrc_calc_outbuf_len(m2m, buf);
+
+ if (*dma_len <= 0)
+ return 0;
+
+ *sg_nodes = *dma_len / m2m->dma_block[dir].max_buf_size + 1;
+
+ return fsl_easrc_dmaconfig(m2m, dma_chan, fifo_addr, dma_vaddr,
+ *dma_len, dir, bits);
+}
+
+static long fsl_easrc_prepare_buffer(struct fsl_easrc_m2m *m2m,
+ struct asrc_convert_buffer *buf)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ int ret;
+
+ ret = fsl_easrc_prepare_io_buffer(m2m, buf, IN);
+ if (ret) {
+ dev_err(dev, "failed to prepare input buffer %d\n", ret);
+ return ret;
+ }
+
+ ret = fsl_easrc_prepare_io_buffer(m2m, buf, OUT);
+ if (ret) {
+ dev_err(dev, "failed to prepare output buffer %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int fsl_easrc_process_buffer_pre(struct fsl_easrc_m2m *m2m, bool dir)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+
+ if (!wait_for_completion_interruptible_timeout(&m2m->complete[dir],
+ 10 * HZ)) {
+ dev_err(dev, "%sput DMA task timeout\n", DIR_STR(dir));
+ return -ETIME;
+ } else if (signal_pending(current)) {
+ dev_err(dev, "%sput task forcibly aborted\n", DIR_STR(dir));
+ return -ERESTARTSYS;
+ }
+
+ return 0;
+}
+
+static unsigned int fsl_easrc_get_output_FIFO_size(struct fsl_easrc_m2m *m2m)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ enum asrc_pair_index index = m2m->ctx->index;
+ u32 val;
+
+ regmap_read(easrc->regmap, REG_EASRC_SFS(index), &val);
+
+ val &= EASRC_SFS_NSGO_MASK;
+
+ return val >> EASRC_SFS_NSGO_SHIFT;
+}
+
+static void fsl_easrc_read_last_FIFO(struct fsl_easrc_m2m *m2m)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct dma_block *output = &m2m->dma_block[OUT];
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ enum asrc_pair_index index = m2m->ctx->index;
+ u32 i, reg, size, t_size = 0, width;
+ u32 *reg32 = NULL;
+ u16 *reg16 = NULL;
+
+ width = snd_pcm_format_physical_width(ctx->out_params.sample_format);
+
+ if (width == 32)
+ reg32 = output->dma_vaddr + output->length;
+ else
+ reg16 = output->dma_vaddr + output->length;
+retry:
+ size = fsl_easrc_get_output_FIFO_size(m2m);
+ for (i = 0; i < size * ctx->channels; i++) {
+ regmap_read(easrc->regmap, REG_EASRC_RDFIFO(index), &reg);
+
+ if (reg32) {
+ *(reg32) = reg;
+ reg32++;
+ } else {
+ *(reg16) = (u16)reg;
+ reg16++;
+ }
+ }
+ t_size += size;
+
+ if (size)
+ goto retry;
+
+ if (reg32)
+ output->length += t_size * ctx->channels * 4;
+ else
+ output->length += t_size * ctx->channels * 2;
+}
+
+static long fsl_easrc_process_buffer(struct fsl_easrc_m2m *m2m,
+ struct asrc_convert_buffer *buf)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ unsigned long lock_flags;
+ int ret;
+
+ /* Check input task first */
+ ret = fsl_easrc_process_buffer_pre(m2m, IN);
+ if (ret) {
+ mxc_easrc_dma_umap_in(dev, m2m);
+ if (m2m->dma_block[OUT].length > 0)
+ mxc_easrc_dma_umap_out(dev, m2m);
+ return ret;
+ }
+
+ /* ...then output task*/
+ if (m2m->dma_block[OUT].length > 0) {
+ ret = fsl_easrc_process_buffer_pre(m2m, OUT);
+ if (ret) {
+ mxc_easrc_dma_umap_in(dev, m2m);
+ mxc_easrc_dma_umap_out(dev, m2m);
+ return ret;
+ }
+ }
+
+ mxc_easrc_dma_umap_in(dev, m2m);
+ if (m2m->dma_block[OUT].length > 0)
+ mxc_easrc_dma_umap_out(dev, m2m);
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ if (!m2m->ctx_hold) {
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+ return -EFAULT;
+ }
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ /* Fetch the remaining data */
+ fsl_easrc_read_last_FIFO(m2m);
+
+ /* Update final lengths after getting last FIFO */
+ buf->input_buffer_length = m2m->dma_block[IN].length;
+ buf->output_buffer_length = m2m->dma_block[OUT].length;
+
+ if (copy_to_user((void __user *)buf->output_buffer_vaddr,
+ m2m->dma_block[OUT].dma_vaddr,
+ m2m->dma_block[OUT].length))
+ return -EFAULT;
+
+ return 0;
+}
+
+void fsl_easrc_submit_dma(struct fsl_easrc_m2m *m2m)
+{
+ /* Submit DMA request */
+ dmaengine_submit(m2m->desc[IN]);
+ dma_async_issue_pending(m2m->desc[IN]->chan);
+
+ if (m2m->dma_block[OUT].length > 0) {
+ dmaengine_submit(m2m->desc[OUT]);
+ dma_async_issue_pending(m2m->desc[OUT]->chan);
+ }
+}
+
+static long fsl_easrc_ioctl_req_context(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct asrc_req req;
+ unsigned long lock_flags;
+ long ret;
+
+ ret = copy_from_user(&req, user, sizeof(req));
+ if (ret) {
+ dev_err(dev, "failed to get req from user space:%ld\n", ret);
+ return ret;
+ }
+
+ ret = fsl_easrc_request_context(m2m->ctx, req.chn_num);
+ if (ret < 0) {
+ dev_err(dev, "failed to request context:%ld\n", ret);
+ return ret;
+ }
+
+ /* request context returns the context id in case of success */
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ m2m->ctx_hold = 1;
+ req.index = m2m->ctx->index;
+ req.supported_in_format = FSL_EASRC_FORMATS;
+ req.supported_out_format = FSL_EASRC_FORMATS;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ ret = copy_to_user(user, &req, sizeof(req));
+ if (ret) {
+ dev_err(dev, "failed to send req to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_config_context(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ enum asrc_pair_index index = m2m->ctx->index;
+ struct device *dev = &easrc->pdev->dev;
+ struct asrc_config config;
+ int ret;
+ int in_word_size, out_word_size;
+
+ ret = copy_from_user(&config, user, sizeof(config));
+ if (ret) {
+ dev_err(dev, "failed to get config from user space: %d\n", ret);
+ return ret;
+ }
+
+ /* set context configuration parameters received from userspace */
+ ctx->in_params.sample_rate = config.input_sample_rate;
+ ctx->out_params.sample_rate = config.output_sample_rate;
+
+ ctx->in_params.fifo_wtmk = FSL_EASRC_INPUTFIFO_WML;
+ ctx->out_params.fifo_wtmk = FSL_EASRC_OUTPUTFIFO_WML;
+
+ ctx->in_params.sample_format = config.input_format;
+ ctx->out_params.sample_format = config.output_format;
+
+ ctx->channels = config.channel_num;
+ ctx->rs_init_mode = 0x2;
+ ctx->pf_init_mode = 0x2;
+
+ ret = fsl_easrc_config_context(easrc, index);
+ if (ret) {
+ dev_err(dev, "failed to config context %d\n", ret);
+ return ret;
+ }
+
+ fsl_easrc_process_format(easrc, &ctx->in_params.fmt,
+ config.input_format);
+ fsl_easrc_process_format(easrc, &ctx->out_params.fmt,
+ config.output_format);
+
+ /* FIXME - fix sample position?
+ * if the input sample is 16-bits wide and left-justified on a
+ * 32-bit boundary, then this register should be set to 16. If
+ * the input sample is 16-bits wide and right-
+ * justified on a 32-bit boundary, then this register should be
+ * set to 0
+ */
+ ret = fsl_easrc_set_ctx_format(ctx, NULL, NULL);
+ if (ret)
+ return ret;
+
+ ctx->in_params.iterations = 1;
+ ctx->in_params.group_len = ctx->channels;
+ ctx->in_params.access_len = ctx->channels;
+ ctx->out_params.iterations = 1;
+ ctx->out_params.group_len = ctx->channels;
+ ctx->out_params.access_len = ctx->channels;
+
+ /* You can also call fsl_easrc_set_ctx_organziation for
+ * sample interleaving support
+ */
+ ret = fsl_easrc_set_ctx_organziation(ctx);
+ if (ret) {
+ dev_err(dev, "failed to set fifo organization\n");
+ return ret;
+ }
+
+ in_word_size = snd_pcm_format_physical_width(config.input_format) / 8;
+ out_word_size = snd_pcm_format_physical_width(config.output_format) / 8;
+
+ /* allocate dma buffers */
+ m2m->dma_block[IN].length = EASRC_DMA_BUFFER_SIZE;
+ m2m->dma_block[IN].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE,
+ in_word_size * ctx->channels);
+ m2m->dma_block[OUT].length = EASRC_DMA_BUFFER_SIZE;
+ m2m->dma_block[OUT].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE,
+ out_word_size * ctx->channels);
+
+ ret = fsl_allocate_dma_buf(m2m);
+ if (ret) {
+ dev_err(dev, "failed to allocate DMA buffers: %d\n", ret);
+ return ret;
+ }
+
+ ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN);
+ if (!ctx->dma_chan[IN]) {
+ dev_err(dev, "[ctx%d] failed to get input DMA channel\n",
+ m2m->ctx->index);
+ return -EBUSY;
+ }
+ ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT);
+ if (!ctx->dma_chan[OUT]) {
+ dev_err(dev, "[ctx%d] failed to get output DMA channel\n",
+ m2m->ctx->index);
+ return -EBUSY;
+ }
+
+ ret = copy_to_user(user, &config, sizeof(config));
+ if (ret) {
+ dev_err(dev, "failed to send config to user: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_release_context(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct device *dev = &easrc->pdev->dev;
+ enum asrc_pair_index index;
+ unsigned long lock_flags;
+ int ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ dev_err(dev,
+ "[ctx%d] failed to get index from user space %d\n",
+ m2m->ctx->index, ret);
+ return ret;
+ }
+
+ if (index != m2m->ctx->index) {
+ dev_err(dev,
+ "[ctx%d] releasing wrong context - %d\n",
+ m2m->ctx->index, index);
+ return -EINVAL;
+ }
+
+ m2m->easrc_active = 0;
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ m2m->ctx_hold = 0;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ if (ctx->dma_chan[IN])
+ dma_release_channel(ctx->dma_chan[IN]);
+ if (ctx->dma_chan[OUT])
+ dma_release_channel(ctx->dma_chan[OUT]);
+
+ ctx->dma_chan[IN] = NULL;
+ ctx->dma_chan[OUT] = NULL;
+
+ /* free buffers allocated in config context*/
+ kfree(m2m->dma_block[IN].dma_vaddr);
+ kfree(m2m->dma_block[OUT].dma_vaddr);
+
+ fsl_easrc_release_context(ctx);
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_convert(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct asrc_convert_buffer buf;
+ int ret;
+
+ ret = copy_from_user(&buf, user, sizeof(buf));
+ if (ret) {
+ dev_err(dev, "failed to get buf from user space: %d\n", ret);
+ return ret;
+ }
+
+ /* fsl_easrc_calc_last_period_size(ctx, &buf); */
+ ret = fsl_easrc_prepare_buffer(m2m, &buf);
+ if (ret) {
+ dev_err(dev, "failed to prepare buffer\n");
+ return ret;
+ }
+
+ init_completion(&m2m->complete[IN]);
+ init_completion(&m2m->complete[OUT]);
+
+ fsl_easrc_submit_dma(m2m);
+
+ if (m2m->first_convert) {
+ fsl_easrc_start_context(ctx);
+ m2m->first_convert = 0;
+ }
+
+ ret = fsl_easrc_process_buffer(m2m, &buf);
+ if (ret) {
+ dev_err(dev, "failed to process buffer %d\n", ret);
+ return ret;
+ }
+
+ ret = copy_to_user(user, &buf, sizeof(buf));
+ if (ret) {
+ dev_err(dev, "failed to send buffer to user: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_start_conv(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ enum asrc_pair_index index;
+ int ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ dev_err(dev, "failed to get index from user space: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (index != m2m->ctx->index) {
+ dev_err(dev, "[ctx%d] attempting to start wrong context%d\n",
+ m2m->ctx->index, index);
+ return -EINVAL;
+ }
+
+ m2m->easrc_active = 1;
+ m2m->first_convert = 1;
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_stop_conv(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct device *dev = &easrc->pdev->dev;
+ enum asrc_pair_index index;
+ int ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ dev_err(dev, "failed to get index from user space: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (index != m2m->ctx->index) {
+ dev_err(dev, "[ctx%d] attempting to start wrong context%d\n",
+ m2m->ctx->index, index);
+ return -EINVAL;
+ }
+
+ dmaengine_terminate_all(ctx->dma_chan[IN]);
+ dmaengine_terminate_all(ctx->dma_chan[OUT]);
+
+ fsl_easrc_stop_context(ctx);
+ m2m->easrc_active = 0;
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl_status(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct asrc_status_flags flags;
+ int ret;
+
+ ret = copy_from_user(&flags, user, sizeof(flags));
+ if (ret) {
+ dev_err(dev,
+ "[ctx%d] failed to get flags from user space: %d\n",
+ m2m->ctx->index, ret);
+ return ret;
+ }
+
+ if (m2m->ctx->index != flags.index) {
+ dev_err(dev, "[ctx%d] getting status for other context: %d\n",
+ m2m->ctx->index, flags.index);
+ return -EINVAL;
+ }
+
+ fsl_easrc_get_status(ctx, &flags);
+
+ ret = copy_to_user(user, &flags, sizeof(flags));
+ if (ret)
+ dev_err(dev, "[ctx%d] failed to send flags to user space\n",
+ m2m->ctx->index);
+
+ return ret;
+}
+
+static long fsl_easrc_ioctl_flush(struct fsl_easrc_m2m *m2m,
+ void __user *user)
+{
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct device *dev = &easrc->pdev->dev;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+
+ /* Release DMA and request again */
+ dma_release_channel(ctx->dma_chan[IN]);
+ dma_release_channel(ctx->dma_chan[OUT]);
+
+ ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN);
+ if (!ctx->dma_chan[IN]) {
+ dev_err(dev, "failed to request input task DMA channel\n");
+ return -EBUSY;
+ }
+
+ ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT);
+ if (!ctx->dma_chan[OUT]) {
+ dev_err(dev, "failed to request output task DMA channel\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int fsl_easrc_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *easrc_miscdev = file->private_data;
+ struct fsl_easrc *easrc = dev_get_drvdata(easrc_miscdev->parent);
+ struct fsl_easrc_m2m *m2m;
+ struct fsl_easrc_context *ctx;
+ struct device *dev = &easrc->pdev->dev;
+ int ret;
+
+ ret = signal_pending(current);
+ if (ret) {
+ dev_err(dev, "current process has a signal pending\n");
+ return ret;
+ }
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ /* set the pointer to easrc private data */
+ m2m = kzalloc(sizeof(*m2m), GFP_KERNEL);
+ if (!m2m)
+ return -ENOMEM;
+
+ /* just save the pointer to easrc private data */
+ m2m->easrc = easrc;
+ m2m->ctx = ctx;
+ ctx->easrc = easrc;
+ ctx->private_data = m2m;
+
+ spin_lock_init(&m2m->lock);
+
+ /* context structs are already allocated in fsl_easrc->ctx[i] */
+ file->private_data = m2m;
+
+ pm_runtime_get_sync(dev);
+
+ return 0;
+}
+
+static int fsl_easrc_close(struct inode *inode, struct file *file)
+{
+ struct fsl_easrc_m2m *m2m = file->private_data;
+ struct fsl_easrc *easrc = m2m->easrc;
+ struct fsl_easrc_context *ctx = m2m->ctx;
+ struct device *dev = &easrc->pdev->dev;
+ unsigned long lock_flags;
+
+ if (m2m->easrc_active) {
+ m2m->easrc_active = 0;
+ dmaengine_terminate_all(ctx->dma_chan[IN]);
+ dmaengine_terminate_all(ctx->dma_chan[OUT]);
+
+ fsl_easrc_stop_context(ctx);
+ fsl_easrc_input_dma_callback((void *)m2m);
+ fsl_easrc_output_dma_callback((void *)m2m);
+ }
+
+ if (!ctx)
+ goto null_ctx;
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ if (m2m->ctx_hold) {
+ m2m->ctx_hold = 0;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ if (ctx->dma_chan[IN])
+ dma_release_channel(ctx->dma_chan[IN]);
+ if (ctx->dma_chan[OUT])
+ dma_release_channel(ctx->dma_chan[OUT]);
+
+ kfree(m2m->dma_block[IN].dma_vaddr);
+ kfree(m2m->dma_block[OUT].dma_vaddr);
+
+ fsl_easrc_release_context(ctx);
+ } else {
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+ }
+
+null_ctx:
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+ kfree(m2m);
+ kfree(ctx);
+ file->private_data = NULL;
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+
+ pm_runtime_put_sync(dev);
+
+ return 0;
+}
+
+static long fsl_easrc_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct fsl_easrc_m2m *m2m = file->private_data;
+ struct fsl_easrc *easrc = m2m->easrc;
+ void __user *user = (void __user *)arg;
+ long ret = 0;
+
+ switch (cmd) {
+ case ASRC_REQ_PAIR:
+ ret = fsl_easrc_ioctl_req_context(m2m, user);
+ break;
+ case ASRC_CONFIG_PAIR:
+ ret = fsl_easrc_ioctl_config_context(m2m, user);
+ break;
+ case ASRC_RELEASE_PAIR:
+ ret = fsl_easrc_ioctl_release_context(m2m, user);
+ break;
+ case ASRC_CONVERT:
+ ret = fsl_easrc_ioctl_convert(m2m, user);
+ break;
+ case ASRC_START_CONV:
+ ret = fsl_easrc_ioctl_start_conv(m2m, user);
+ break;
+ case ASRC_STOP_CONV:
+ ret = fsl_easrc_ioctl_stop_conv(m2m, user);
+ break;
+ case ASRC_STATUS:
+ ret = fsl_easrc_ioctl_status(m2m, user);
+ break;
+ case ASRC_FLUSH:
+ ret = fsl_easrc_ioctl_flush(m2m, user);
+ break;
+ default:
+ dev_err(&easrc->pdev->dev, "invalid ioctl command\n");
+ }
+
+ return ret;
+}
+
+static const struct file_operations easrc_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fsl_easrc_ioctl,
+ .open = fsl_easrc_open,
+ .release = fsl_easrc_close,
+};
+
+static int fsl_easrc_m2m_init(struct fsl_easrc *easrc)
+{
+ struct device *dev = &easrc->pdev->dev;
+ int ret;
+
+ easrc->easrc_miscdev.fops = &easrc_fops;
+ easrc->easrc_miscdev.parent = dev;
+ easrc->easrc_miscdev.name = easrc->name;
+ easrc->easrc_miscdev.minor = MISC_DYNAMIC_MINOR;
+ ret = misc_register(&easrc->easrc_miscdev);
+ if (ret)
+ dev_err(dev, "failed to register char device %d\n", ret);
+
+ return ret;
+}
+
+static void fsl_easrc_m2m_suspend(struct fsl_easrc *easrc)
+{
+ struct fsl_easrc_context *ctx;
+ struct fsl_easrc_m2m *m2m;
+ unsigned long lock_flags;
+ int i;
+
+ for (i = 0; i < EASRC_CTX_MAX_NUM; i++) {
+ spin_lock_irqsave(&easrc->lock, lock_flags);
+ ctx = easrc->ctx[i];
+ if (!ctx || !ctx->private_data) {
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+ continue;
+ }
+ m2m = ctx->private_data;
+
+ if (!completion_done(&m2m->complete[IN])) {
+ if (ctx->dma_chan[IN])
+ dmaengine_terminate_all(ctx->dma_chan[IN]);
+ fsl_easrc_input_dma_callback((void *)m2m);
+ }
+ if (!completion_done(&m2m->complete[OUT])) {
+ if (ctx->dma_chan[OUT])
+ dmaengine_terminate_all(ctx->dma_chan[OUT]);
+ fsl_easrc_output_dma_callback((void *)m2m);
+ }
+
+ m2m->first_convert = 1;
+ fsl_easrc_stop_context(ctx);
+ spin_unlock_irqrestore(&easrc->lock, lock_flags);
+ }
+}
+
+static void fsl_easrc_m2m_resume(struct fsl_easrc *easrc)
+{
+ /* null */
+}