mirror of
https://github.com/brain-hackers/linux-brain.git
synced 2024-06-09 23:36:23 +09:00
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:
parent
d5986dc1d4
commit
3184f6eabe
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
353
sound/soc/fsl/fsl_esai_mix.c
Normal file
353
sound/soc/fsl/fsl_esai_mix.c
Normal 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");
|
53
sound/soc/fsl/fsl_esai_mix.h
Normal file
53
sound/soc/fsl/fsl_esai_mix.h
Normal 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 */
|
Loading…
Reference in New Issue
Block a user