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>
This commit is contained in:
Shengjiu Wang 2020-08-25 13:46:02 +08:00
parent d5986dc1d4
commit 3184f6eabe
5 changed files with 527 additions and 69 deletions

View File

@ -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

View File

@ -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;

View File

@ -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 */

View File

@ -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");

View File

@ -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 */