|
|
|
|
@@ -17,13 +17,23 @@
|
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
|
#include <sound/soc.h>
|
|
|
|
|
|
|
|
|
|
struct qmc_dai_chan {
|
|
|
|
|
struct qmc_dai_prtd *prtd_tx;
|
|
|
|
|
struct qmc_dai_prtd *prtd_rx;
|
|
|
|
|
struct qmc_chan *qmc_chan;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct qmc_dai {
|
|
|
|
|
char *name;
|
|
|
|
|
int id;
|
|
|
|
|
struct device *dev;
|
|
|
|
|
struct qmc_chan *qmc_chan;
|
|
|
|
|
unsigned int nb_tx_ts;
|
|
|
|
|
unsigned int nb_rx_ts;
|
|
|
|
|
|
|
|
|
|
unsigned int nb_chans_avail;
|
|
|
|
|
unsigned int nb_chans_used_tx;
|
|
|
|
|
unsigned int nb_chans_used_rx;
|
|
|
|
|
struct qmc_dai_chan *chans;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct qmc_audio {
|
|
|
|
|
@@ -35,11 +45,19 @@ struct qmc_audio {
|
|
|
|
|
|
|
|
|
|
struct qmc_dai_prtd {
|
|
|
|
|
struct qmc_dai *qmc_dai;
|
|
|
|
|
dma_addr_t dma_buffer_start;
|
|
|
|
|
dma_addr_t period_ptr_submitted;
|
|
|
|
|
dma_addr_t period_ptr_ended;
|
|
|
|
|
dma_addr_t dma_buffer_end;
|
|
|
|
|
size_t period_size;
|
|
|
|
|
|
|
|
|
|
snd_pcm_uframes_t buffer_ended;
|
|
|
|
|
snd_pcm_uframes_t buffer_size;
|
|
|
|
|
snd_pcm_uframes_t period_size;
|
|
|
|
|
|
|
|
|
|
dma_addr_t ch_dma_addr_start;
|
|
|
|
|
dma_addr_t ch_dma_addr_current;
|
|
|
|
|
dma_addr_t ch_dma_addr_end;
|
|
|
|
|
size_t ch_dma_size;
|
|
|
|
|
size_t ch_dma_offset;
|
|
|
|
|
|
|
|
|
|
unsigned int channels;
|
|
|
|
|
DECLARE_BITMAP(chans_pending, 64);
|
|
|
|
|
struct snd_pcm_substream *substream;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -54,10 +72,22 @@ static int qmc_audio_pcm_construct(struct snd_soc_component *component,
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, card->dev,
|
|
|
|
|
64*1024, 64*1024);
|
|
|
|
|
64 * 1024, 64 * 1024);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool qmc_audio_access_is_interleaved(snd_pcm_access_t access)
|
|
|
|
|
{
|
|
|
|
|
switch (access) {
|
|
|
|
|
case SNDRV_PCM_ACCESS_MMAP_INTERLEAVED:
|
|
|
|
|
case SNDRV_PCM_ACCESS_RW_INTERLEAVED:
|
|
|
|
|
return true;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_audio_pcm_hw_params(struct snd_soc_component *component,
|
|
|
|
|
struct snd_pcm_substream *substream,
|
|
|
|
|
struct snd_pcm_hw_params *params)
|
|
|
|
|
@@ -65,65 +95,142 @@ static int qmc_audio_pcm_hw_params(struct snd_soc_component *component,
|
|
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
|
|
|
struct qmc_dai_prtd *prtd = substream->runtime->private_data;
|
|
|
|
|
|
|
|
|
|
prtd->dma_buffer_start = runtime->dma_addr;
|
|
|
|
|
prtd->dma_buffer_end = runtime->dma_addr + params_buffer_bytes(params);
|
|
|
|
|
prtd->period_size = params_period_bytes(params);
|
|
|
|
|
prtd->period_ptr_submitted = prtd->dma_buffer_start;
|
|
|
|
|
prtd->period_ptr_ended = prtd->dma_buffer_start;
|
|
|
|
|
/*
|
|
|
|
|
* In interleaved mode, the driver uses one QMC channel for all audio
|
|
|
|
|
* channels whereas in non-interleaved mode, it uses one QMC channel per
|
|
|
|
|
* audio channel.
|
|
|
|
|
*/
|
|
|
|
|
prtd->channels = qmc_audio_access_is_interleaved(params_access(params)) ?
|
|
|
|
|
1 : params_channels(params);
|
|
|
|
|
|
|
|
|
|
prtd->substream = substream;
|
|
|
|
|
|
|
|
|
|
prtd->buffer_ended = 0;
|
|
|
|
|
prtd->buffer_size = params_buffer_size(params);
|
|
|
|
|
prtd->period_size = params_period_size(params);
|
|
|
|
|
|
|
|
|
|
prtd->ch_dma_addr_start = runtime->dma_addr;
|
|
|
|
|
prtd->ch_dma_offset = params_buffer_bytes(params) / prtd->channels;
|
|
|
|
|
prtd->ch_dma_addr_end = runtime->dma_addr + prtd->ch_dma_offset;
|
|
|
|
|
prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;
|
|
|
|
|
prtd->ch_dma_size = params_period_bytes(params) / prtd->channels;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void qmc_audio_pcm_write_complete(void *context);
|
|
|
|
|
|
|
|
|
|
static int qmc_audio_pcm_write_submit(struct qmc_dai_prtd *prtd)
|
|
|
|
|
{
|
|
|
|
|
unsigned int i;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < prtd->channels; i++) {
|
|
|
|
|
bitmap_set(prtd->chans_pending, i, 1);
|
|
|
|
|
|
|
|
|
|
ret = qmc_chan_write_submit(prtd->qmc_dai->chans[i].qmc_chan,
|
|
|
|
|
prtd->ch_dma_addr_current + i * prtd->ch_dma_offset,
|
|
|
|
|
prtd->ch_dma_size,
|
|
|
|
|
qmc_audio_pcm_write_complete,
|
|
|
|
|
&prtd->qmc_dai->chans[i]);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(prtd->qmc_dai->dev, "write_submit %u failed %d\n",
|
|
|
|
|
i, ret);
|
|
|
|
|
bitmap_clear(prtd->chans_pending, i, 1);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void qmc_audio_pcm_write_complete(void *context)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai_prtd *prtd = context;
|
|
|
|
|
int ret;
|
|
|
|
|
struct qmc_dai_chan *chan = context;
|
|
|
|
|
struct qmc_dai_prtd *prtd;
|
|
|
|
|
|
|
|
|
|
prtd->period_ptr_ended += prtd->period_size;
|
|
|
|
|
if (prtd->period_ptr_ended >= prtd->dma_buffer_end)
|
|
|
|
|
prtd->period_ptr_ended = prtd->dma_buffer_start;
|
|
|
|
|
prtd = chan->prtd_tx;
|
|
|
|
|
|
|
|
|
|
prtd->period_ptr_submitted += prtd->period_size;
|
|
|
|
|
if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
|
|
|
|
|
prtd->period_ptr_submitted = prtd->dma_buffer_start;
|
|
|
|
|
/* Mark the current channel as completed */
|
|
|
|
|
bitmap_clear(prtd->chans_pending, chan - prtd->qmc_dai->chans, 1);
|
|
|
|
|
|
|
|
|
|
ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan,
|
|
|
|
|
prtd->period_ptr_submitted, prtd->period_size,
|
|
|
|
|
qmc_audio_pcm_write_complete, prtd);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(prtd->qmc_dai->dev, "write_submit failed %d\n",
|
|
|
|
|
ret);
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* All QMC channels involved must have completed their transfer before
|
|
|
|
|
* submitting a new one.
|
|
|
|
|
*/
|
|
|
|
|
if (!bitmap_empty(prtd->chans_pending, 64))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
prtd->buffer_ended += prtd->period_size;
|
|
|
|
|
if (prtd->buffer_ended >= prtd->buffer_size)
|
|
|
|
|
prtd->buffer_ended = 0;
|
|
|
|
|
|
|
|
|
|
prtd->ch_dma_addr_current += prtd->ch_dma_size;
|
|
|
|
|
if (prtd->ch_dma_addr_current >= prtd->ch_dma_addr_end)
|
|
|
|
|
prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;
|
|
|
|
|
|
|
|
|
|
qmc_audio_pcm_write_submit(prtd);
|
|
|
|
|
|
|
|
|
|
snd_pcm_period_elapsed(prtd->substream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void qmc_audio_pcm_read_complete(void *context, size_t length, unsigned int flags)
|
|
|
|
|
static void qmc_audio_pcm_read_complete(void *context, size_t length, unsigned int flags);
|
|
|
|
|
|
|
|
|
|
static int qmc_audio_pcm_read_submit(struct qmc_dai_prtd *prtd)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai_prtd *prtd = context;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (length != prtd->period_size) {
|
|
|
|
|
for (i = 0; i < prtd->channels; i++) {
|
|
|
|
|
bitmap_set(prtd->chans_pending, i, 1);
|
|
|
|
|
|
|
|
|
|
ret = qmc_chan_read_submit(prtd->qmc_dai->chans[i].qmc_chan,
|
|
|
|
|
prtd->ch_dma_addr_current + i * prtd->ch_dma_offset,
|
|
|
|
|
prtd->ch_dma_size,
|
|
|
|
|
qmc_audio_pcm_read_complete,
|
|
|
|
|
&prtd->qmc_dai->chans[i]);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(prtd->qmc_dai->dev, "read_submit %u failed %d\n",
|
|
|
|
|
i, ret);
|
|
|
|
|
bitmap_clear(prtd->chans_pending, i, 1);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void qmc_audio_pcm_read_complete(void *context, size_t length, unsigned int flags)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai_chan *chan = context;
|
|
|
|
|
struct qmc_dai_prtd *prtd;
|
|
|
|
|
|
|
|
|
|
prtd = chan->prtd_rx;
|
|
|
|
|
|
|
|
|
|
/* Mark the current channel as completed */
|
|
|
|
|
bitmap_clear(prtd->chans_pending, chan - prtd->qmc_dai->chans, 1);
|
|
|
|
|
|
|
|
|
|
if (length != prtd->ch_dma_size) {
|
|
|
|
|
dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp %zu\n",
|
|
|
|
|
length, prtd->period_size);
|
|
|
|
|
length, prtd->ch_dma_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prtd->period_ptr_ended += prtd->period_size;
|
|
|
|
|
if (prtd->period_ptr_ended >= prtd->dma_buffer_end)
|
|
|
|
|
prtd->period_ptr_ended = prtd->dma_buffer_start;
|
|
|
|
|
/*
|
|
|
|
|
* All QMC channels involved must have completed their transfer before
|
|
|
|
|
* submitting a new one.
|
|
|
|
|
*/
|
|
|
|
|
if (!bitmap_empty(prtd->chans_pending, 64))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
prtd->period_ptr_submitted += prtd->period_size;
|
|
|
|
|
if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
|
|
|
|
|
prtd->period_ptr_submitted = prtd->dma_buffer_start;
|
|
|
|
|
prtd->buffer_ended += prtd->period_size;
|
|
|
|
|
if (prtd->buffer_ended >= prtd->buffer_size)
|
|
|
|
|
prtd->buffer_ended = 0;
|
|
|
|
|
|
|
|
|
|
ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan,
|
|
|
|
|
prtd->period_ptr_submitted, prtd->period_size,
|
|
|
|
|
qmc_audio_pcm_read_complete, prtd);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(prtd->qmc_dai->dev, "read_submit failed %d\n",
|
|
|
|
|
ret);
|
|
|
|
|
}
|
|
|
|
|
prtd->ch_dma_addr_current += prtd->ch_dma_size;
|
|
|
|
|
if (prtd->ch_dma_addr_current >= prtd->ch_dma_addr_end)
|
|
|
|
|
prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;
|
|
|
|
|
|
|
|
|
|
qmc_audio_pcm_read_submit(prtd);
|
|
|
|
|
|
|
|
|
|
snd_pcm_period_elapsed(prtd->substream);
|
|
|
|
|
}
|
|
|
|
|
@@ -132,6 +239,7 @@ static int qmc_audio_pcm_trigger(struct snd_soc_component *component,
|
|
|
|
|
struct snd_pcm_substream *substream, int cmd)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai_prtd *prtd = substream->runtime->private_data;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (!prtd->qmc_dai) {
|
|
|
|
|
@@ -141,56 +249,43 @@ static int qmc_audio_pcm_trigger(struct snd_soc_component *component,
|
|
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
|
|
|
bitmap_zero(prtd->chans_pending, 64);
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
|
for (i = 0; i < prtd->channels; i++)
|
|
|
|
|
prtd->qmc_dai->chans[i].prtd_tx = prtd;
|
|
|
|
|
|
|
|
|
|
/* Submit first chunk ... */
|
|
|
|
|
ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan,
|
|
|
|
|
prtd->period_ptr_submitted, prtd->period_size,
|
|
|
|
|
qmc_audio_pcm_write_complete, prtd);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(component->dev, "write_submit failed %d\n",
|
|
|
|
|
ret);
|
|
|
|
|
ret = qmc_audio_pcm_write_submit(prtd);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ... prepare next one ... */
|
|
|
|
|
prtd->period_ptr_submitted += prtd->period_size;
|
|
|
|
|
if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
|
|
|
|
|
prtd->period_ptr_submitted = prtd->dma_buffer_start;
|
|
|
|
|
prtd->ch_dma_addr_current += prtd->ch_dma_size;
|
|
|
|
|
if (prtd->ch_dma_addr_current >= prtd->ch_dma_addr_end)
|
|
|
|
|
prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;
|
|
|
|
|
|
|
|
|
|
/* ... and send it */
|
|
|
|
|
ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan,
|
|
|
|
|
prtd->period_ptr_submitted, prtd->period_size,
|
|
|
|
|
qmc_audio_pcm_write_complete, prtd);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(component->dev, "write_submit failed %d\n",
|
|
|
|
|
ret);
|
|
|
|
|
ret = qmc_audio_pcm_write_submit(prtd);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (i = 0; i < prtd->channels; i++)
|
|
|
|
|
prtd->qmc_dai->chans[i].prtd_rx = prtd;
|
|
|
|
|
|
|
|
|
|
/* Submit first chunk ... */
|
|
|
|
|
ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan,
|
|
|
|
|
prtd->period_ptr_submitted, prtd->period_size,
|
|
|
|
|
qmc_audio_pcm_read_complete, prtd);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(component->dev, "read_submit failed %d\n",
|
|
|
|
|
ret);
|
|
|
|
|
ret = qmc_audio_pcm_read_submit(prtd);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ... prepare next one ... */
|
|
|
|
|
prtd->period_ptr_submitted += prtd->period_size;
|
|
|
|
|
if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
|
|
|
|
|
prtd->period_ptr_submitted = prtd->dma_buffer_start;
|
|
|
|
|
prtd->ch_dma_addr_current += prtd->ch_dma_size;
|
|
|
|
|
if (prtd->ch_dma_addr_current >= prtd->ch_dma_addr_end)
|
|
|
|
|
prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;
|
|
|
|
|
|
|
|
|
|
/* ... and send it */
|
|
|
|
|
ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan,
|
|
|
|
|
prtd->period_ptr_submitted, prtd->period_size,
|
|
|
|
|
qmc_audio_pcm_read_complete, prtd);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(component->dev, "write_submit failed %d\n",
|
|
|
|
|
ret);
|
|
|
|
|
ret = qmc_audio_pcm_read_submit(prtd);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
@@ -215,13 +310,12 @@ static snd_pcm_uframes_t qmc_audio_pcm_pointer(struct snd_soc_component *compone
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai_prtd *prtd = substream->runtime->private_data;
|
|
|
|
|
|
|
|
|
|
return bytes_to_frames(substream->runtime,
|
|
|
|
|
prtd->period_ptr_ended - prtd->dma_buffer_start);
|
|
|
|
|
return prtd->buffer_ended;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_audio_of_xlate_dai_name(struct snd_soc_component *component,
|
|
|
|
|
const struct of_phandle_args *args,
|
|
|
|
|
const char **dai_name)
|
|
|
|
|
const struct of_phandle_args *args,
|
|
|
|
|
const char **dai_name)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_audio *qmc_audio = dev_get_drvdata(component->dev);
|
|
|
|
|
struct snd_soc_dai_driver *dai_driver;
|
|
|
|
|
@@ -243,12 +337,13 @@ static const struct snd_pcm_hardware qmc_audio_pcm_hardware = {
|
|
|
|
|
.info = SNDRV_PCM_INFO_MMAP |
|
|
|
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
|
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
|
|
|
SNDRV_PCM_INFO_NONINTERLEAVED |
|
|
|
|
|
SNDRV_PCM_INFO_PAUSE,
|
|
|
|
|
.period_bytes_min = 32,
|
|
|
|
|
.period_bytes_max = 64*1024,
|
|
|
|
|
.period_bytes_max = 64 * 1024,
|
|
|
|
|
.periods_min = 2,
|
|
|
|
|
.periods_max = 2*1024,
|
|
|
|
|
.buffer_bytes_max = 64*1024,
|
|
|
|
|
.periods_max = 2 * 1024,
|
|
|
|
|
.buffer_bytes_max = 64 * 1024,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int qmc_audio_pcm_open(struct snd_soc_component *component,
|
|
|
|
|
@@ -266,7 +361,7 @@ static int qmc_audio_pcm_open(struct snd_soc_component *component,
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
|
|
|
|
|
if (prtd == NULL)
|
|
|
|
|
if (!prtd)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
runtime->private_data = prtd;
|
|
|
|
|
@@ -329,13 +424,13 @@ static int qmc_dai_hw_rule_channels_by_format(struct qmc_dai *qmc_dai,
|
|
|
|
|
ch.max = nb_ts;
|
|
|
|
|
break;
|
|
|
|
|
case 16:
|
|
|
|
|
ch.max = nb_ts/2;
|
|
|
|
|
ch.max = nb_ts / 2;
|
|
|
|
|
break;
|
|
|
|
|
case 32:
|
|
|
|
|
ch.max = nb_ts/4;
|
|
|
|
|
ch.max = nb_ts / 4;
|
|
|
|
|
break;
|
|
|
|
|
case 64:
|
|
|
|
|
ch.max = nb_ts/8;
|
|
|
|
|
ch.max = nb_ts / 8;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
dev_err(qmc_dai->dev, "format physical width %u not supported\n",
|
|
|
|
|
@@ -356,9 +451,8 @@ static int qmc_dai_hw_rule_playback_channels_by_format(struct snd_pcm_hw_params
|
|
|
|
|
return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, qmc_dai->nb_tx_ts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_dai_hw_rule_capture_channels_by_format(
|
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
|
|
|
|
static int qmc_dai_hw_rule_capture_channels_by_format(struct snd_pcm_hw_params *params,
|
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai *qmc_dai = rule->private;
|
|
|
|
|
|
|
|
|
|
@@ -394,42 +488,31 @@ static int qmc_dai_hw_rule_format_by_channels(struct qmc_dai *qmc_dai,
|
|
|
|
|
return snd_mask_refine(f_old, &f_new);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_dai_hw_rule_playback_format_by_channels(
|
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
|
|
|
|
static int qmc_dai_hw_rule_playback_format_by_channels(struct snd_pcm_hw_params *params,
|
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai *qmc_dai = rule->private;
|
|
|
|
|
|
|
|
|
|
return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_tx_ts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_dai_hw_rule_capture_format_by_channels(
|
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
|
|
|
|
static int qmc_dai_hw_rule_capture_format_by_channels(struct snd_pcm_hw_params *params,
|
|
|
|
|
struct snd_pcm_hw_rule *rule)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai *qmc_dai = rule->private;
|
|
|
|
|
|
|
|
|
|
return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_rx_ts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_dai_startup(struct snd_pcm_substream *substream,
|
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
|
static int qmc_dai_constraints_interleaved(struct snd_pcm_substream *substream,
|
|
|
|
|
struct qmc_dai *qmc_dai)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai_prtd *prtd = substream->runtime->private_data;
|
|
|
|
|
snd_pcm_hw_rule_func_t hw_rule_channels_by_format;
|
|
|
|
|
snd_pcm_hw_rule_func_t hw_rule_format_by_channels;
|
|
|
|
|
struct qmc_dai *qmc_dai;
|
|
|
|
|
unsigned int frame_bits;
|
|
|
|
|
u64 access;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
qmc_dai = qmc_dai_get_data(dai);
|
|
|
|
|
if (!qmc_dai) {
|
|
|
|
|
dev_err(dai->dev, "Invalid dai\n");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prtd->qmc_dai = qmc_dai;
|
|
|
|
|
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
|
|
|
hw_rule_channels_by_format = qmc_dai_hw_rule_capture_channels_by_format;
|
|
|
|
|
hw_rule_format_by_channels = qmc_dai_hw_rule_capture_format_by_channels;
|
|
|
|
|
@@ -444,7 +527,7 @@ static int qmc_dai_startup(struct snd_pcm_substream *substream,
|
|
|
|
|
hw_rule_channels_by_format, qmc_dai,
|
|
|
|
|
SNDRV_PCM_HW_PARAM_FORMAT, -1);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(dai->dev, "Failed to add channels rule (%d)\n", ret);
|
|
|
|
|
dev_err(qmc_dai->dev, "Failed to add channels rule (%d)\n", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -452,7 +535,7 @@ static int qmc_dai_startup(struct snd_pcm_substream *substream,
|
|
|
|
|
hw_rule_format_by_channels, qmc_dai,
|
|
|
|
|
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(dai->dev, "Failed to add format rule (%d)\n", ret);
|
|
|
|
|
dev_err(qmc_dai->dev, "Failed to add format rule (%d)\n", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -460,19 +543,78 @@ static int qmc_dai_startup(struct snd_pcm_substream *substream,
|
|
|
|
|
SNDRV_PCM_HW_PARAM_FRAME_BITS,
|
|
|
|
|
frame_bits);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
dev_err(dai->dev, "Failed to add frame_bits constraint (%d)\n", ret);
|
|
|
|
|
dev_err(qmc_dai->dev, "Failed to add frame_bits constraint (%d)\n", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED |
|
|
|
|
|
1ULL << (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED;
|
|
|
|
|
ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS,
|
|
|
|
|
access);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(qmc_dai->dev, "Failed to add hw_param_access constraint (%d)\n", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_dai_constraints_noninterleaved(struct snd_pcm_substream *substream,
|
|
|
|
|
struct qmc_dai *qmc_dai)
|
|
|
|
|
{
|
|
|
|
|
unsigned int frame_bits;
|
|
|
|
|
u64 access;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
frame_bits = (substream->stream == SNDRV_PCM_STREAM_CAPTURE) ?
|
|
|
|
|
qmc_dai->nb_rx_ts * 8 : qmc_dai->nb_tx_ts * 8;
|
|
|
|
|
ret = snd_pcm_hw_constraint_single(substream->runtime,
|
|
|
|
|
SNDRV_PCM_HW_PARAM_FRAME_BITS,
|
|
|
|
|
frame_bits);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
dev_err(qmc_dai->dev, "Failed to add frame_bits constraint (%d)\n", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
access = 1ULL << (__force int)SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED |
|
|
|
|
|
1ULL << (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED;
|
|
|
|
|
ret = snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_ACCESS,
|
|
|
|
|
access);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(qmc_dai->dev, "Failed to add hw_param_access constraint (%d)\n", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_dai_startup(struct snd_pcm_substream *substream,
|
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_dai_prtd *prtd = substream->runtime->private_data;
|
|
|
|
|
struct qmc_dai *qmc_dai;
|
|
|
|
|
|
|
|
|
|
qmc_dai = qmc_dai_get_data(dai);
|
|
|
|
|
if (!qmc_dai) {
|
|
|
|
|
dev_err(dai->dev, "Invalid dai\n");
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prtd->qmc_dai = qmc_dai;
|
|
|
|
|
|
|
|
|
|
return qmc_dai->nb_chans_avail > 1 ?
|
|
|
|
|
qmc_dai_constraints_noninterleaved(substream, qmc_dai) :
|
|
|
|
|
qmc_dai_constraints_interleaved(substream, qmc_dai);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_dai_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_chan_param chan_param = {0};
|
|
|
|
|
unsigned int nb_chans_used;
|
|
|
|
|
struct qmc_dai *qmc_dai;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
qmc_dai = qmc_dai_get_data(dai);
|
|
|
|
|
@@ -481,15 +623,34 @@ static int qmc_dai_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* In interleaved mode, the driver uses one QMC channel for all audio
|
|
|
|
|
* channels whereas in non-interleaved mode, it uses one QMC channel per
|
|
|
|
|
* audio channel.
|
|
|
|
|
*/
|
|
|
|
|
nb_chans_used = qmc_audio_access_is_interleaved(params_access(params)) ?
|
|
|
|
|
1 : params_channels(params);
|
|
|
|
|
|
|
|
|
|
if (nb_chans_used > qmc_dai->nb_chans_avail) {
|
|
|
|
|
dev_err(dai->dev, "Not enough qmc_chans. Need %u, avail %u\n",
|
|
|
|
|
nb_chans_used, qmc_dai->nb_chans_avail);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
|
|
|
chan_param.mode = QMC_TRANSPARENT;
|
|
|
|
|
chan_param.transp.max_rx_buf_size = params_period_bytes(params);
|
|
|
|
|
ret = qmc_chan_set_param(qmc_dai->qmc_chan, &chan_param);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(dai->dev, "set param failed %d\n",
|
|
|
|
|
ret);
|
|
|
|
|
return ret;
|
|
|
|
|
chan_param.transp.max_rx_buf_size = params_period_bytes(params) / nb_chans_used;
|
|
|
|
|
for (i = 0; i < nb_chans_used; i++) {
|
|
|
|
|
ret = qmc_chan_set_param(qmc_dai->chans[i].qmc_chan, &chan_param);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(dai->dev, "chans[%u], set param failed %d\n",
|
|
|
|
|
i, ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
qmc_dai->nb_chans_used_rx = nb_chans_used;
|
|
|
|
|
} else {
|
|
|
|
|
qmc_dai->nb_chans_used_tx = nb_chans_used;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
@@ -498,9 +659,12 @@ static int qmc_dai_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
|
static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
|
{
|
|
|
|
|
unsigned int nb_chans_used;
|
|
|
|
|
struct qmc_dai *qmc_dai;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
int direction;
|
|
|
|
|
int ret;
|
|
|
|
|
int ret = 0;
|
|
|
|
|
int ret_tmp;
|
|
|
|
|
|
|
|
|
|
qmc_dai = qmc_dai_get_data(dai);
|
|
|
|
|
if (!qmc_dai) {
|
|
|
|
|
@@ -508,30 +672,50 @@ static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
direction = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
|
|
|
|
QMC_CHAN_WRITE : QMC_CHAN_READ;
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
|
direction = QMC_CHAN_WRITE;
|
|
|
|
|
nb_chans_used = qmc_dai->nb_chans_used_tx;
|
|
|
|
|
} else {
|
|
|
|
|
direction = QMC_CHAN_READ;
|
|
|
|
|
nb_chans_used = qmc_dai->nb_chans_used_rx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
|
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
|
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
|
|
|
ret = qmc_chan_start(qmc_dai->qmc_chan, direction);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
for (i = 0; i < nb_chans_used; i++) {
|
|
|
|
|
ret = qmc_chan_start(qmc_dai->chans[i].qmc_chan, direction);
|
|
|
|
|
if (ret)
|
|
|
|
|
goto err_stop;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
|
|
|
ret = qmc_chan_stop(qmc_dai->qmc_chan, direction);
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
ret = qmc_chan_reset(qmc_dai->qmc_chan, direction);
|
|
|
|
|
/* Stop and reset all QMC channels and return the first error encountered */
|
|
|
|
|
for (i = 0; i < nb_chans_used; i++) {
|
|
|
|
|
ret_tmp = qmc_chan_stop(qmc_dai->chans[i].qmc_chan, direction);
|
|
|
|
|
if (!ret)
|
|
|
|
|
ret = ret_tmp;
|
|
|
|
|
if (ret_tmp)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
ret_tmp = qmc_chan_reset(qmc_dai->chans[i].qmc_chan, direction);
|
|
|
|
|
if (!ret)
|
|
|
|
|
ret = ret_tmp;
|
|
|
|
|
}
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
|
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
|
|
|
ret = qmc_chan_stop(qmc_dai->qmc_chan, direction);
|
|
|
|
|
/* Stop all QMC channels and return the first error encountered */
|
|
|
|
|
for (i = 0; i < nb_chans_used; i++) {
|
|
|
|
|
ret_tmp = qmc_chan_stop(qmc_dai->chans[i].qmc_chan, direction);
|
|
|
|
|
if (!ret)
|
|
|
|
|
ret = ret_tmp;
|
|
|
|
|
}
|
|
|
|
|
if (ret)
|
|
|
|
|
return ret;
|
|
|
|
|
break;
|
|
|
|
|
@@ -541,6 +725,13 @@ static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err_stop:
|
|
|
|
|
while (i--) {
|
|
|
|
|
qmc_chan_stop(qmc_dai->chans[i].qmc_chan, direction);
|
|
|
|
|
qmc_chan_reset(qmc_dai->chans[i].qmc_chan, direction);
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct snd_soc_dai_ops qmc_dai_ops = {
|
|
|
|
|
@@ -549,7 +740,7 @@ static const struct snd_soc_dai_ops qmc_dai_ops = {
|
|
|
|
|
.hw_params = qmc_dai_hw_params,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static u64 qmc_audio_formats(u8 nb_ts)
|
|
|
|
|
static u64 qmc_audio_formats(u8 nb_ts, bool is_noninterleaved)
|
|
|
|
|
{
|
|
|
|
|
unsigned int format_width;
|
|
|
|
|
unsigned int chan_width;
|
|
|
|
|
@@ -581,15 +772,29 @@ static u64 qmc_audio_formats(u8 nb_ts)
|
|
|
|
|
if (format_width > chan_width || chan_width % format_width)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* In non interleaved mode, we can only support formats that
|
|
|
|
|
* can fit only 1 time in the channel
|
|
|
|
|
*/
|
|
|
|
|
if (is_noninterleaved && format_width != chan_width)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
formats_mask |= pcm_format_to_bits(format);
|
|
|
|
|
}
|
|
|
|
|
return formats_mask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *np,
|
|
|
|
|
struct qmc_dai *qmc_dai, struct snd_soc_dai_driver *qmc_soc_dai_driver)
|
|
|
|
|
struct qmc_dai *qmc_dai,
|
|
|
|
|
struct snd_soc_dai_driver *qmc_soc_dai_driver)
|
|
|
|
|
{
|
|
|
|
|
struct qmc_chan_info info;
|
|
|
|
|
unsigned long rx_fs_rate;
|
|
|
|
|
unsigned long tx_fs_rate;
|
|
|
|
|
unsigned int nb_tx_ts;
|
|
|
|
|
unsigned int nb_rx_ts;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
int count;
|
|
|
|
|
u32 val;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
@@ -604,57 +809,108 @@ static int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *
|
|
|
|
|
|
|
|
|
|
qmc_dai->name = devm_kasprintf(qmc_audio->dev, GFP_KERNEL, "%s.%d",
|
|
|
|
|
np->parent->name, qmc_dai->id);
|
|
|
|
|
if (!qmc_dai->name)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
qmc_dai->qmc_chan = devm_qmc_chan_get_byphandle(qmc_audio->dev, np,
|
|
|
|
|
"fsl,qmc-chan");
|
|
|
|
|
if (IS_ERR(qmc_dai->qmc_chan)) {
|
|
|
|
|
ret = PTR_ERR(qmc_dai->qmc_chan);
|
|
|
|
|
return dev_err_probe(qmc_audio->dev, ret,
|
|
|
|
|
"dai %d get QMC channel failed\n", qmc_dai->id);
|
|
|
|
|
count = qmc_chan_count_phandles(np, "fsl,qmc-chan");
|
|
|
|
|
if (count < 0)
|
|
|
|
|
return dev_err_probe(qmc_audio->dev, count,
|
|
|
|
|
"dai %d get number of QMC channel failed\n", qmc_dai->id);
|
|
|
|
|
if (!count)
|
|
|
|
|
return dev_err_probe(qmc_audio->dev, -EINVAL,
|
|
|
|
|
"dai %d no QMC channel defined\n", qmc_dai->id);
|
|
|
|
|
|
|
|
|
|
qmc_dai->chans = devm_kcalloc(qmc_audio->dev, count, sizeof(*qmc_dai->chans), GFP_KERNEL);
|
|
|
|
|
if (!qmc_dai->chans)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
|
qmc_dai->chans[i].qmc_chan = devm_qmc_chan_get_byphandles_index(qmc_audio->dev, np,
|
|
|
|
|
"fsl,qmc-chan", i);
|
|
|
|
|
if (IS_ERR(qmc_dai->chans[i].qmc_chan)) {
|
|
|
|
|
return dev_err_probe(qmc_audio->dev, PTR_ERR(qmc_dai->chans[i].qmc_chan),
|
|
|
|
|
"dai %d get QMC channel %d failed\n", qmc_dai->id, i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = qmc_chan_get_info(qmc_dai->chans[i].qmc_chan, &info);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(qmc_audio->dev, "dai %d get QMC %d channel info failed %d\n",
|
|
|
|
|
qmc_dai->id, i, ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
dev_info(qmc_audio->dev, "dai %d QMC channel %d mode %d, nb_tx_ts %u, nb_rx_ts %u\n",
|
|
|
|
|
qmc_dai->id, i, info.mode, info.nb_tx_ts, info.nb_rx_ts);
|
|
|
|
|
|
|
|
|
|
if (info.mode != QMC_TRANSPARENT) {
|
|
|
|
|
dev_err(qmc_audio->dev, "dai %d QMC chan %d mode %d is not QMC_TRANSPARENT\n",
|
|
|
|
|
qmc_dai->id, i, info.mode);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* All channels must have the same number of Tx slots and the
|
|
|
|
|
* same numbers of Rx slots.
|
|
|
|
|
*/
|
|
|
|
|
if (i == 0) {
|
|
|
|
|
nb_tx_ts = info.nb_tx_ts;
|
|
|
|
|
nb_rx_ts = info.nb_rx_ts;
|
|
|
|
|
tx_fs_rate = info.tx_fs_rate;
|
|
|
|
|
rx_fs_rate = info.rx_fs_rate;
|
|
|
|
|
} else {
|
|
|
|
|
if (nb_tx_ts != info.nb_tx_ts) {
|
|
|
|
|
dev_err(qmc_audio->dev, "dai %d QMC chan %d inconsistent number of Tx timeslots (%u instead of %u)\n",
|
|
|
|
|
qmc_dai->id, i, info.nb_tx_ts, nb_tx_ts);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
if (nb_rx_ts != info.nb_rx_ts) {
|
|
|
|
|
dev_err(qmc_audio->dev, "dai %d QMC chan %d inconsistent number of Rx timeslots (%u instead of %u)\n",
|
|
|
|
|
qmc_dai->id, i, info.nb_rx_ts, nb_rx_ts);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
if (tx_fs_rate != info.tx_fs_rate) {
|
|
|
|
|
dev_err(qmc_audio->dev, "dai %d QMC chan %d inconsistent Tx frame sample rate (%lu instead of %lu)\n",
|
|
|
|
|
qmc_dai->id, i, info.tx_fs_rate, tx_fs_rate);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
if (rx_fs_rate != info.rx_fs_rate) {
|
|
|
|
|
dev_err(qmc_audio->dev, "dai %d QMC chan %d inconsistent Rx frame sample rate (%lu instead of %lu)\n",
|
|
|
|
|
qmc_dai->id, i, info.rx_fs_rate, rx_fs_rate);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qmc_dai->nb_chans_avail = count;
|
|
|
|
|
qmc_dai->nb_tx_ts = nb_tx_ts * count;
|
|
|
|
|
qmc_dai->nb_rx_ts = nb_rx_ts * count;
|
|
|
|
|
|
|
|
|
|
qmc_soc_dai_driver->id = qmc_dai->id;
|
|
|
|
|
qmc_soc_dai_driver->name = qmc_dai->name;
|
|
|
|
|
|
|
|
|
|
ret = qmc_chan_get_info(qmc_dai->qmc_chan, &info);
|
|
|
|
|
if (ret) {
|
|
|
|
|
dev_err(qmc_audio->dev, "dai %d get QMC channel info failed %d\n",
|
|
|
|
|
qmc_dai->id, ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
dev_info(qmc_audio->dev, "dai %d QMC channel mode %d, nb_tx_ts %u, nb_rx_ts %u\n",
|
|
|
|
|
qmc_dai->id, info.mode, info.nb_tx_ts, info.nb_rx_ts);
|
|
|
|
|
|
|
|
|
|
if (info.mode != QMC_TRANSPARENT) {
|
|
|
|
|
dev_err(qmc_audio->dev, "dai %d QMC chan mode %d is not QMC_TRANSPARENT\n",
|
|
|
|
|
qmc_dai->id, info.mode);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
qmc_dai->nb_tx_ts = info.nb_tx_ts;
|
|
|
|
|
qmc_dai->nb_rx_ts = info.nb_rx_ts;
|
|
|
|
|
|
|
|
|
|
qmc_soc_dai_driver->playback.channels_min = 0;
|
|
|
|
|
qmc_soc_dai_driver->playback.channels_max = 0;
|
|
|
|
|
if (qmc_dai->nb_tx_ts) {
|
|
|
|
|
if (nb_tx_ts) {
|
|
|
|
|
qmc_soc_dai_driver->playback.channels_min = 1;
|
|
|
|
|
qmc_soc_dai_driver->playback.channels_max = qmc_dai->nb_tx_ts;
|
|
|
|
|
qmc_soc_dai_driver->playback.channels_max = count > 1 ? count : nb_tx_ts;
|
|
|
|
|
}
|
|
|
|
|
qmc_soc_dai_driver->playback.formats = qmc_audio_formats(qmc_dai->nb_tx_ts);
|
|
|
|
|
qmc_soc_dai_driver->playback.formats = qmc_audio_formats(nb_tx_ts,
|
|
|
|
|
count > 1 ? true : false);
|
|
|
|
|
|
|
|
|
|
qmc_soc_dai_driver->capture.channels_min = 0;
|
|
|
|
|
qmc_soc_dai_driver->capture.channels_max = 0;
|
|
|
|
|
if (qmc_dai->nb_rx_ts) {
|
|
|
|
|
if (nb_rx_ts) {
|
|
|
|
|
qmc_soc_dai_driver->capture.channels_min = 1;
|
|
|
|
|
qmc_soc_dai_driver->capture.channels_max = qmc_dai->nb_rx_ts;
|
|
|
|
|
qmc_soc_dai_driver->capture.channels_max = count > 1 ? count : nb_rx_ts;
|
|
|
|
|
}
|
|
|
|
|
qmc_soc_dai_driver->capture.formats = qmc_audio_formats(qmc_dai->nb_rx_ts);
|
|
|
|
|
qmc_soc_dai_driver->capture.formats = qmc_audio_formats(nb_rx_ts,
|
|
|
|
|
count > 1 ? true : false);
|
|
|
|
|
|
|
|
|
|
qmc_soc_dai_driver->playback.rates = snd_pcm_rate_to_rate_bit(info.tx_fs_rate);
|
|
|
|
|
qmc_soc_dai_driver->playback.rate_min = info.tx_fs_rate;
|
|
|
|
|
qmc_soc_dai_driver->playback.rate_max = info.tx_fs_rate;
|
|
|
|
|
qmc_soc_dai_driver->capture.rates = snd_pcm_rate_to_rate_bit(info.rx_fs_rate);
|
|
|
|
|
qmc_soc_dai_driver->capture.rate_min = info.rx_fs_rate;
|
|
|
|
|
qmc_soc_dai_driver->capture.rate_max = info.rx_fs_rate;
|
|
|
|
|
qmc_soc_dai_driver->playback.rates = snd_pcm_rate_to_rate_bit(tx_fs_rate);
|
|
|
|
|
qmc_soc_dai_driver->playback.rate_min = tx_fs_rate;
|
|
|
|
|
qmc_soc_dai_driver->playback.rate_max = tx_fs_rate;
|
|
|
|
|
qmc_soc_dai_driver->capture.rates = snd_pcm_rate_to_rate_bit(rx_fs_rate);
|
|
|
|
|
qmc_soc_dai_driver->capture.rate_min = rx_fs_rate;
|
|
|
|
|
qmc_soc_dai_driver->capture.rate_max = rx_fs_rate;
|
|
|
|
|
|
|
|
|
|
qmc_soc_dai_driver->ops = &qmc_dai_ops;
|
|
|
|
|
|
|
|
|
|
@@ -702,7 +958,6 @@ static int qmc_audio_probe(struct platform_device *pdev)
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
platform_set_drvdata(pdev, qmc_audio);
|
|
|
|
|
|
|
|
|
|
ret = devm_snd_soc_register_component(qmc_audio->dev,
|
|
|
|
|
|