summaryrefslogtreecommitdiff
path: root/sound/soc/fsl
diff options
context:
space:
mode:
authorShengjiu Wang <shengjiu.wang@nxp.com>2020-08-25 13:46:02 +0800
committerShengjiu Wang <shengjiu.wang@nxp.com>2020-08-31 18:08:17 +0800
commit3184f6eabe276bea8d3b7e5448bbce634efd3941 (patch)
treea474249e95467f4e21521c9178a8a51be6768915 /sound/soc/fsl
parentd5986dc1d41899a11bd636fe337ef7d503183683 (diff)
MLK-24612-2: ASoC: fsl_esai: Add esai mix driver
ESAI mixer is for mixing the data from clients. There is a ping-pong buffer in the mixer for storing the mixed data. The period size is same as the period size in client (unit is sample number). Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com> Reviewed-by: Peng Zhang <peng.zhang_8@nxp.com>
Diffstat (limited to 'sound/soc/fsl')
-rw-r--r--sound/soc/fsl/Makefile2
-rw-r--r--sound/soc/fsl/fsl_esai.c118
-rw-r--r--sound/soc/fsl/fsl_esai.h70
-rw-r--r--sound/soc/fsl/fsl_esai_mix.c353
-rw-r--r--sound/soc/fsl/fsl_esai_mix.h53
5 files changed, 527 insertions, 69 deletions
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index 5906d9f0c81f..e1dd77c90d2a 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -22,7 +22,7 @@ snd-soc-fsl-sai-objs := fsl_sai.o fsl_sai_sysfs.o
snd-soc-fsl-ssi-y := fsl_ssi.o
snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o
snd-soc-fsl-spdif-objs := fsl_spdif.o
-snd-soc-fsl-esai-objs := fsl_esai.o
+snd-soc-fsl-esai-objs := fsl_esai.o fsl_esai_mix.o
snd-soc-fsl-dai-objs := fsl_dai.o
snd-soc-fsl-micfil-objs := fsl_micfil.o
snd-soc-fsl-utils-objs := fsl_utils.o
diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c
index 3bd7ac30406c..47df69f57bb0 100644
--- a/sound/soc/fsl/fsl_esai.c
+++ b/sound/soc/fsl/fsl_esai.c
@@ -14,6 +14,7 @@
#include <sound/pcm_params.h>
#include "fsl_esai.h"
+#include "fsl_esai_mix.h"
#include "imx-pcm.h"
#define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
@@ -21,70 +22,6 @@
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE)
-/**
- * fsl_esai_soc_data: soc specific data
- *
- * @imx: for imx platform
- * @reset_at_xrun: flags for enable reset operaton
- * @use_edma: edma is used.
- */
-struct fsl_esai_soc_data {
- bool imx;
- bool reset_at_xrun;
- bool use_edma;
-};
-
-/**
- * fsl_esai: ESAI private data
- *
- * @dma_params_rx: DMA parameters for receive channel
- * @dma_params_tx: DMA parameters for transmit channel
- * @pdev: platform device pointer
- * @regmap: regmap handler
- * @coreclk: clock source to access register
- * @extalclk: esai clock source to derive HCK, SCK and FS
- * @fsysclk: system clock source to derive HCK, SCK and FS
- * @spbaclk: SPBA clock (optional, depending on SoC design)
- * @soc: soc specific data
- * @lock: spin lock between hw_reset() and trigger()
- * @fifo_depth: depth of tx/rx FIFO
- * @slot_width: width of each DAI slot
- * @slots: number of slots
- * @channels: channel num for tx or rx
- * @hck_rate: clock rate of desired HCKx clock
- * @sck_rate: clock rate of desired SCKx clock
- * @hck_dir: the direction of HCKx pads
- * @sck_div: if using PSR/PM dividers for SCKx clock
- * @slave_mode: if fully using DAI slave mode
- * @synchronous: if using tx/rx synchronous mode
- * @name: driver name
- */
-struct fsl_esai {
- struct snd_dmaengine_dai_dma_data dma_params_rx;
- struct snd_dmaengine_dai_dma_data dma_params_tx;
- struct platform_device *pdev;
- struct regmap *regmap;
- struct clk *coreclk;
- struct clk *extalclk;
- struct clk *fsysclk;
- struct clk *spbaclk;
- const struct fsl_esai_soc_data *soc;
- spinlock_t lock; /* Protect hw_reset and trigger */
- u32 fifo_depth;
- u32 slot_width;
- u32 slots;
- u32 tx_mask;
- u32 rx_mask;
- u32 channels[2];
- u32 hck_rate[2];
- u32 sck_rate[2];
- bool hck_dir[2];
- bool sck_div[2];
- bool slave_mode[2];
- bool synchronous;
- char name[32];
-};
-
static struct fsl_esai_soc_data fsl_esai_vf610 = {
.imx = false,
.reset_at_xrun = true,
@@ -567,11 +504,23 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
tx ? esai_priv->dma_params_tx.maxburst :
esai_priv->dma_params_rx.maxburst);
+ if (esai_priv->sw_mix)
+ fsl_esai_mix_open(substream, &esai_priv->mix[tx]);
return 0;
}
+static void fsl_esai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ if (esai_priv->sw_mix)
+ fsl_esai_mix_close(substream, &esai_priv->mix[tx]);
+}
+
static int fsl_esai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
@@ -627,6 +576,10 @@ static int fsl_esai_hw_params(struct snd_pcm_substream *substream,
ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO));
regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC,
ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO));
+
+ if (esai_priv->sw_mix)
+ fsl_esai_mix_hw_params(substream, params, &esai_priv->mix[tx]);
+
return 0;
}
@@ -799,13 +752,23 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
unsigned long lock_flags;
+ u32 state;
- esai_priv->channels[tx] = substream->runtime->channels;
+ if (esai_priv->sw_mix)
+ esai_priv->channels[tx] = esai_priv->mix[tx].channels;
+ else
+ esai_priv->channels[tx] = substream->runtime->channels;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (esai_priv->sw_mix) {
+ state = atomic_cmpxchg(&esai_priv->mix[tx].active, 0, 1);
+ if (!state)
+ fsl_esai_mix_trigger(substream, cmd, &esai_priv->mix[tx]);
+ }
+
spin_lock_irqsave(&esai_priv->lock, lock_flags);
fsl_esai_trigger_start(esai_priv, tx);
spin_unlock_irqrestore(&esai_priv->lock, lock_flags);
@@ -813,6 +776,12 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (esai_priv->sw_mix) {
+ state = atomic_cmpxchg(&esai_priv->mix[tx].active, 1, 0);
+ if (state)
+ fsl_esai_mix_trigger(substream, cmd, &esai_priv->mix[tx]);
+ }
+
spin_lock_irqsave(&esai_priv->lock, lock_flags);
fsl_esai_trigger_stop(esai_priv, tx);
spin_unlock_irqrestore(&esai_priv->lock, lock_flags);
@@ -826,6 +795,7 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
static const struct snd_soc_dai_ops fsl_esai_dai_ops = {
.startup = fsl_esai_startup,
+ .shutdown = fsl_esai_shutdown,
.trigger = fsl_esai_trigger,
.hw_params = fsl_esai_hw_params,
.set_sysclk = fsl_esai_set_dai_sysclk,
@@ -1137,15 +1107,27 @@ static int fsl_esai_probe(struct platform_device *pdev)
regcache_cache_only(esai_priv->regmap, true);
- ret = imx_pcm_platform_register(&pdev->dev);
- if (ret)
- dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret);
+ if (of_property_read_bool(pdev->dev.of_node, "client-dais")) {
+ esai_priv->sw_mix = true;
+ ret = fsl_esai_mix_probe(&pdev->dev, &esai_priv->mix[0], &esai_priv->mix[1]);
+ if (ret)
+ dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret);
+ } else {
+ ret = imx_pcm_platform_register(&pdev->dev);
+ if (ret)
+ dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret);
+ }
return ret;
}
static int fsl_esai_remove(struct platform_device *pdev)
{
+ struct fsl_esai *esai_priv = platform_get_drvdata(pdev);
+
+ if (esai_priv->sw_mix)
+ fsl_esai_mix_remove(&pdev->dev, &esai_priv->mix[0], &esai_priv->mix[1]);
+
pm_runtime_disable(&pdev->dev);
return 0;
diff --git a/sound/soc/fsl/fsl_esai.h b/sound/soc/fsl/fsl_esai.h
index f873588d9045..628d711ac214 100644
--- a/sound/soc/fsl/fsl_esai.h
+++ b/sound/soc/fsl/fsl_esai.h
@@ -10,6 +10,9 @@
#ifndef _FSL_ESAI_DAI_H
#define _FSL_ESAI_DAI_H
+#include <sound/dmaengine_pcm.h>
+#include "fsl_esai_mix.h"
+
/* ESAI Register Map */
#define REG_ESAI_ETDR 0x00
#define REG_ESAI_ERDR 0x04
@@ -348,4 +351,71 @@
#define ESAI_RX_DIV_PSR 3
#define ESAI_RX_DIV_PM 4
#define ESAI_RX_DIV_FP 5
+
+/**
+ * fsl_esai_soc_data: soc specific data
+ *
+ * @imx: for imx platform
+ * @reset_at_xrun: flags for enable reset operaton
+ * @use_edma: edma is used.
+ */
+struct fsl_esai_soc_data {
+ bool imx;
+ bool reset_at_xrun;
+ bool use_edma;
+};
+
+/**
+ * fsl_esai: ESAI private data
+ *
+ * @dma_params_rx: DMA parameters for receive channel
+ * @dma_params_tx: DMA parameters for transmit channel
+ * @pdev: platform device pointer
+ * @regmap: regmap handler
+ * @coreclk: clock source to access register
+ * @extalclk: esai clock source to derive HCK, SCK and FS
+ * @fsysclk: system clock source to derive HCK, SCK and FS
+ * @spbaclk: SPBA clock (optional, depending on SoC design)
+ * @soc: soc specific data
+ * @lock: spin lock between hw_reset() and trigger()
+ * @fifo_depth: depth of tx/rx FIFO
+ * @slot_width: width of each DAI slot
+ * @slots: number of slots
+ * @channels: channel num for tx or rx
+ * @hck_rate: clock rate of desired HCKx clock
+ * @sck_rate: clock rate of desired SCKx clock
+ * @hck_dir: the direction of HCKx pads
+ * @sck_div: if using PSR/PM dividers for SCKx clock
+ * @slave_mode: if fully using DAI slave mode
+ * @synchronous: if using tx/rx synchronous mode
+ * @sw_mix: enable sw mix in driver
+ * @name: driver name
+ */
+struct fsl_esai {
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct snd_dmaengine_dai_dma_data dma_params_tx;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct clk *coreclk;
+ struct clk *extalclk;
+ struct clk *fsysclk;
+ struct clk *spbaclk;
+ const struct fsl_esai_soc_data *soc;
+ struct fsl_esai_mix mix[2];
+ spinlock_t lock; /* Protect hw_reset and trigger */
+ u32 fifo_depth;
+ u32 slot_width;
+ u32 slots;
+ u32 tx_mask;
+ u32 rx_mask;
+ u32 channels[2];
+ u32 hck_rate[2];
+ u32 sck_rate[2];
+ bool hck_dir[2];
+ bool sck_div[2];
+ bool slave_mode[2];
+ bool synchronous;
+ bool sw_mix;
+ char name[32];
+};
#endif /* _FSL_ESAI_DAI_H */
diff --git a/sound/soc/fsl/fsl_esai_mix.c b/sound/soc/fsl/fsl_esai_mix.c
new file mode 100644
index 000000000000..58ddd6725f96
--- /dev/null
+++ b/sound/soc/fsl/fsl_esai_mix.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2019 NXP
+/*
+ * Support mix two streams for ESAI
+ *
+ */
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+#include "imx-pcm.h"
+#include "fsl_esai_client.h"
+#include "fsl_esai.h"
+#include "fsl_esai_mix.h"
+
+int fsl_esai_mix_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct fsl_esai_mix *mix)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ struct dma_slave_config config;
+ int err = 0;
+
+ mix->channels = params_channels(params);
+ mix->word_width = snd_pcm_format_physical_width(params_format(params)) / 8;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ if (!dma_data)
+ return 0;
+
+ /* fills in addr_width and direction */
+ err = snd_hwparams_to_dma_slave_config(substream, params, &config);
+ if (err)
+ return err;
+
+ snd_dmaengine_pcm_set_config_from_dai_data(substream,
+ dma_data,
+ &config);
+
+ return dmaengine_slave_config(mix->chan, &config);
+}
+
+int fsl_esai_mix_open(struct snd_pcm_substream *substream, struct fsl_esai_mix *mix)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ mix->chan = dma_request_slave_channel(rtd->cpu_dai->dev,
+ dma_data->chan_name);
+
+ return 0;
+}
+
+int fsl_esai_mix_close(struct snd_pcm_substream *substream,
+ struct fsl_esai_mix *mix)
+{
+ dmaengine_synchronize(mix->chan);
+ dma_release_channel(mix->chan);
+
+ return 0;
+}
+
+static void fsl_esai_mix_buffer_from_fe_tx(struct snd_pcm_substream *substream, int size, bool elapse)
+{
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ struct fsl_esai_mix *mix = &esai->mix[tx];
+ struct fsl_esai_client *client;
+ struct fsl_esai_client_dma *client_dma;
+ struct snd_soc_dpcm *dpcm;
+ unsigned long flags;
+ int sample_offset = 0;
+ int channel_cnt = 0;
+ int i = 0, j = 0;
+ int dst_idx;
+ u16 *src16;
+ u16 *dst16;
+
+ for (j = 0; j < MAX_CLIENT_NUM; j++) {
+ mix->fe_substream[j] = NULL;
+ mix->client[j] = NULL;
+ }
+
+ /* Get the active client */
+ spin_lock_irqsave(&rtd->card->dpcm_lock, flags);
+ for_each_dpcm_fe(rtd, substream->stream, dpcm) {
+ if (dpcm->be != rtd)
+ continue;
+
+ mix->fe_substream[i] = snd_soc_dpcm_get_substream(dpcm->fe, substream->stream);
+ mix->client[i] = snd_soc_dai_get_drvdata(dpcm->fe->cpu_dai);
+
+ i++;
+ if (i >= MAX_CLIENT_NUM)
+ break;
+ }
+ spin_unlock_irqrestore(&rtd->card->dpcm_lock, flags);
+
+ /* mix->word_width == client->word_width */
+ /* Mix the internal buffer */
+ while (sample_offset * mix->word_width < size) {
+ dst16 = (u16 *)(mix->dma_buffer.area + mix->buffer_offset);
+ for (channel_cnt = 0; channel_cnt < mix->channels; channel_cnt++)
+ *dst16++ = 0;
+
+ for (i = 0; i < mix->client_cnt; i++) {
+ if (!mix->client[i])
+ continue;
+
+ client = mix->client[i];
+ client_dma = &client->dma[tx];
+
+ /* check client is active ? */
+ if (client_dma->active) {
+ src16 = (u16 *)(client_dma->dma_buffer.area + client_dma->buffer_offset);
+ dst16 = (u16 *)(mix->dma_buffer.area + mix->buffer_offset);
+
+ /* mix the data and reorder it for correct pin */
+ for (j = 0; j < client_dma->channels; j++) {
+ dst_idx = client->id + j * mix->sdo_cnt;
+ dst16[dst_idx] = *src16++;
+ }
+
+ client_dma->buffer_offset += client_dma->channels * client_dma->word_width;
+ client_dma->buffer_offset = client_dma->buffer_offset % client_dma->buffer_bytes;
+ }
+ }
+
+ sample_offset += mix->channels;
+ mix->buffer_offset += mix->channels * mix->word_width;
+ mix->buffer_offset = mix->buffer_offset % mix->buffer_bytes;
+ }
+
+ /* update the pointer of client buffer */
+ for (i = 0; i < mix->client_cnt; i++) {
+ if (elapse && mix->fe_substream[i])
+ snd_pcm_period_elapsed(mix->fe_substream[i]);
+ }
+}
+
+static void fsl_esai_split_buffer_from_be_rx(struct snd_pcm_substream *substream, int size, bool elapse)
+{
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ struct fsl_esai_mix *mix = &esai->mix[tx];
+ struct fsl_esai_client *client;
+ struct fsl_esai_client_dma *client_dma;
+ struct snd_soc_dpcm *dpcm;
+ unsigned long flags;
+ int sample_offset = 0;
+ int i = 0, j = 0;
+ int src_idx;
+ u16 *src16;
+ u16 *dst16;
+
+ for (j = 0; j < MAX_CLIENT_NUM; j++) {
+ mix->fe_substream[j] = NULL;
+ mix->client[j] = NULL;
+ }
+ /* Get the active client */
+ spin_lock_irqsave(&rtd->card->dpcm_lock, flags);
+ for_each_dpcm_fe(rtd, substream->stream, dpcm) {
+ if (dpcm->be != rtd)
+ continue;
+
+ mix->fe_substream[i] = snd_soc_dpcm_get_substream(dpcm->fe, substream->stream);
+ mix->client[i] = snd_soc_dai_get_drvdata(dpcm->fe->cpu_dai);
+
+ i++;
+ if (i >= MAX_CLIENT_NUM)
+ break;
+ }
+ spin_unlock_irqrestore(&rtd->card->dpcm_lock, flags);
+
+ /* mix->word_width == client->word_width */
+ /* split the internal buffer */
+ while (sample_offset * mix->word_width < size) {
+ for (i = 0; i < mix->client_cnt; i++) {
+ if (!mix->client[i])
+ continue;
+
+ client = mix->client[i];
+ client_dma = &client->dma[tx];
+
+ if (client_dma->active) {
+ dst16 = (u16 *)(client_dma->dma_buffer.area + client_dma->buffer_offset);
+ src16 = (u16 *)(mix->dma_buffer.area + mix->buffer_offset);
+
+ /* split the data to corret client*/
+ for (j = 0; j < client_dma->channels; j++) {
+ src_idx = client->id + j * mix->sdi_cnt;
+ *dst16++ = src16[src_idx];
+ }
+
+ client_dma->buffer_offset += client_dma->channels * client_dma->word_width;
+ client_dma->buffer_offset = client_dma->buffer_offset % client_dma->buffer_bytes;
+ }
+ }
+
+ sample_offset += mix->channels;
+ mix->buffer_offset += mix->channels * mix->word_width;
+ mix->buffer_offset = mix->buffer_offset % mix->buffer_bytes;
+ }
+
+ /* update the pointer of client buffer */
+ for (i = 0; i < mix->client_cnt; i++) {
+ if (elapse && mix->fe_substream[i])
+ snd_pcm_period_elapsed(mix->fe_substream[i]);
+ }
+}
+
+/* call back of dma event */
+static void fsl_esai_mix_dma_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct fsl_esai_mix *mix = &esai->mix[tx];
+
+ if (tx)
+ fsl_esai_mix_buffer_from_fe_tx(substream, mix->period_bytes, true);
+ else
+ fsl_esai_split_buffer_from_be_rx(substream, mix->period_bytes, true);
+}
+
+static int fsl_esai_mix_prepare_and_submit(struct snd_pcm_substream *substream,
+ struct fsl_esai_mix *mix)
+{
+ struct dma_async_tx_descriptor *desc;
+ enum dma_transfer_direction direction;
+ unsigned long flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
+
+ direction = snd_pcm_substream_to_dma_direction(substream);
+
+ /* ping-pong buffer for mix */
+ desc = dmaengine_prep_dma_cyclic(mix->chan,
+ mix->dma_buffer.addr,
+ mix->buffer_bytes,
+ mix->period_bytes,
+ direction, flags);
+ if (!desc)
+ return -ENOMEM;
+
+ desc->callback = fsl_esai_mix_dma_complete;
+ desc->callback_param = substream;
+ dmaengine_submit(desc);
+
+ mix->buffer_offset = 0;
+
+ /* Mix the tx buffer */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ fsl_esai_mix_buffer_from_fe_tx(substream, mix->buffer_bytes, false);
+
+ return 0;
+}
+
+int fsl_esai_mix_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct fsl_esai_mix *mix)
+{
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = fsl_esai_mix_prepare_and_submit(substream, mix);
+ if (ret)
+ return ret;
+ dma_async_issue_pending(mix->chan);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_STOP:
+ dmaengine_terminate_async(mix->chan);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int fsl_esai_mix_probe(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx)
+{
+ int ret = 0;
+
+ ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ /**
+ * initialize info for mixing
+ * two clients, TX0 pin is for client 0, TX1 pin is for client 1
+ * total supported channel is 4.
+ */
+ mix_tx->client_cnt = 2;
+ mix_tx->sdo_cnt = 2;
+ mix_tx->sdi_cnt = 2;
+ mix_tx->channels = 4;
+ mix_tx->buffer_bytes = 2048 * mix_tx->client_cnt * 2;
+ mix_tx->period_bytes = 2048 * mix_tx->client_cnt;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+ dev,
+ IMX_SSI_DMABUF_SIZE * mix_tx->client_cnt,
+ &mix_tx->dma_buffer);
+ if (ret)
+ return ret;
+
+ /**
+ * initialize info for mixing
+ * two clients, TX0 pin is for client 0, TX1 pin is for client 1
+ * total supported channel is 4.
+ */
+ mix_rx->client_cnt = 2;
+ mix_rx->sdo_cnt = 2;
+ mix_rx->sdi_cnt = 2;
+ mix_rx->channels = 4;
+ mix_rx->buffer_bytes = 2048 * mix_rx->client_cnt * 2;
+ mix_rx->period_bytes = 2048 * mix_rx->client_cnt;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+ dev,
+ IMX_SSI_DMABUF_SIZE * mix_rx->client_cnt,
+ &mix_rx->dma_buffer);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+int fsl_esai_mix_remove(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx)
+{
+ snd_dma_free_pages(&mix_tx->dma_buffer);
+ snd_dma_free_pages(&mix_rx->dma_buffer);
+
+ return 0;
+}
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_esai_mix.h b/sound/soc/fsl/fsl_esai_mix.h
new file mode 100644
index 000000000000..7d117154b27a
--- /dev/null
+++ b/sound/soc/fsl/fsl_esai_mix.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _FSL_ESAI_MIX_H
+#define _FSL_ESAI_MIX_H
+
+/* maximum client number is 4; */
+#define MAX_CLIENT_NUM 4
+
+/**
+ * fsl_esai_mix: esai mix/split data
+ * @chan: dma channel
+ * @fe_substream: handler of front end substream
+ * @client: handler of client
+ * @dma_buffer: structure of dma buffer
+ * @buffer_offset: read offset of buffer
+ * @buffer_bytes: buffer size in bytes
+ * @period_bytes: period size in bytes
+ * @period_num: period number
+ * @word_width: word width in bytes
+ * @channels: channel number
+ * @client_cnt: client number, default 2.
+ * @sdo_cnt: output pin number of esai
+ * @sdi_cnt: input pin number of esai
+ * @active: mixer is enabled or not
+ */
+struct fsl_esai_mix {
+ struct dma_chan *chan;
+ struct snd_pcm_substream *fe_substream[MAX_CLIENT_NUM];
+ struct fsl_esai_client *client[MAX_CLIENT_NUM];
+ struct snd_dma_buffer dma_buffer;
+ u32 buffer_offset;
+ u32 buffer_bytes;
+ u32 period_bytes;
+ u32 period_num;
+ u32 word_width;
+ u32 channels;
+ u32 client_cnt;
+ u32 sdo_cnt;
+ u32 sdi_cnt;
+ atomic_t active;
+};
+
+int fsl_esai_mix_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct fsl_esai_mix *mix);
+int fsl_esai_mix_open(struct snd_pcm_substream *substream, struct fsl_esai_mix *mix);
+int fsl_esai_mix_close(struct snd_pcm_substream *substream, struct fsl_esai_mix *mix);
+int fsl_esai_mix_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct fsl_esai_mix *mix);
+int fsl_esai_mix_probe(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx);
+int fsl_esai_mix_remove(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx);
+
+#endif /* _FSL_ESAI_MIX_H */