Commit fc800b84 authored by Mark Brown's avatar Mark Brown

Add support for non-interleaved mode in qmc_audio

Merge series from Herve Codina <herve.codina@bootlin.com>:

The qmc_audio driver supports only audio in interleaved mode.
Non-interleaved mode can be easily supported using several QMC channel
per DAI. In that case, data related to ch0 are sent to (received from)
the first QMC channel, data related to ch1 use the next QMC channel and
so on up to the last channel.

In terms of constraints and settings, the interleaved and
non-interleaved modes are slightly different.

In interleaved mode:
  - The sample size should fit in the number of time-slots available for
    the QMC channel.
  - The number of audio channels should fit in the number of time-slots
    (taking into account the sample size) available for the QMC channel.

In non-interleaved mode:
  - The number of audio channels is the number of available QMC
    channels.
  - Each QMC channel should have the same number of time-slots.
  - The sample size equals the number of time-slots of one QMC channel.

This series add support for the non-interleaved mode in the qmc_audio
driver and is composed of the following parts:
  - Patches 1 and 2: Fix some issues in the qmc_audio
  - Patches 3 to 6: Prepare qmc_audio for the non-interleaved mode
  - Patches 7 and 8: Extend the QMC driver API
  - Patches 9 and 10: The support for non-interleaved mode itself

Compared to the previous iteration, this v2 series mainly improves
qmc_audio_access_is_interleaved().
parents ecaec47b 188d9cae
......@@ -12,7 +12,9 @@ maintainers:
description: |
The QMC audio is an ASoC component which uses QMC (QUICC Multichannel
Controller) channels to transfer the audio data.
It provides as many DAI as the number of QMC channel used.
It provides several DAIs. For each DAI, the DAI is working in interleaved mode
if only one QMC channel is used by the DAI or it is working in non-interleaved
mode if several QMC channels are used by the DAI.
allOf:
- $ref: dai-common.yaml#
......@@ -45,12 +47,19 @@ patternProperties:
fsl,qmc-chan:
$ref: /schemas/types.yaml#/definitions/phandle-array
items:
- items:
- description: phandle to QMC node
- description: Channel number
items:
- description: phandle to QMC node
- description: Channel number
minItems: 1
description:
Should be a phandle/number pair. The phandle to QMC node and the QMC
channel to use for this DAI.
Should be a phandle/number pair list. The list of phandle to QMC node
and the QMC channel pair to use for this DAI.
If only one phandle/number pair is provided, this DAI works in
interleaved mode, i.e. audio channels for this DAI are interleaved in
the QMC channel. If more than one pair is provided, this DAI works
in non-interleave mode. In that case the first audio channel uses the
the first QMC channel, the second audio channel uses the second QMC
channel, etc...
required:
- reg
......@@ -79,6 +88,11 @@ examples:
reg = <17>;
fsl,qmc-chan = <&qmc 17>;
};
dai@18 {
reg = <18>;
/* Non-interleaved mode */
fsl,qmc-chan = <&qmc 18>, <&qmc 19>;
};
};
sound {
......@@ -115,4 +129,19 @@ examples:
dai-tdm-slot-rx-mask = <0 0 1 0 1 0 1 0 1>;
};
};
simple-audio-card,dai-link@2 {
reg = <2>;
format = "dsp_b";
cpu {
sound-dai = <&audio_controller 18>;
};
codec {
sound-dai = <&codec3>;
dai-tdm-slot-num = <2>;
dai-tdm-slot-width = <8>;
/* TS 9, 10 */
dai-tdm-slot-tx-mask = <0 0 0 0 0 0 0 0 0 1 1>;
dai-tdm-slot-rx-mask = <0 0 0 0 0 0 0 0 0 1 1>;
};
};
};
......@@ -1777,13 +1777,28 @@ static struct qmc_chan *qmc_chan_get_from_qmc(struct device_node *qmc_np, unsign
return qmc_chan;
}
struct qmc_chan *qmc_chan_get_byphandle(struct device_node *np, const char *phandle_name)
int qmc_chan_count_phandles(struct device_node *np, const char *phandles_name)
{
int count;
/* phandles are fixed args phandles with one arg */
count = of_count_phandle_with_args(np, phandles_name, NULL);
if (count < 0)
return count;
return count / 2;
}
EXPORT_SYMBOL(qmc_chan_count_phandles);
struct qmc_chan *qmc_chan_get_byphandles_index(struct device_node *np,
const char *phandles_name,
int index)
{
struct of_phandle_args out_args;
struct qmc_chan *qmc_chan;
int ret;
ret = of_parse_phandle_with_fixed_args(np, phandle_name, 1, 0,
ret = of_parse_phandle_with_fixed_args(np, phandles_name, 1, index,
&out_args);
if (ret < 0)
return ERR_PTR(ret);
......@@ -1797,7 +1812,7 @@ struct qmc_chan *qmc_chan_get_byphandle(struct device_node *np, const char *phan
of_node_put(out_args.np);
return qmc_chan;
}
EXPORT_SYMBOL(qmc_chan_get_byphandle);
EXPORT_SYMBOL(qmc_chan_get_byphandles_index);
struct qmc_chan *qmc_chan_get_bychild(struct device_node *np)
{
......@@ -1827,9 +1842,10 @@ static void devm_qmc_chan_release(struct device *dev, void *res)
qmc_chan_put(*qmc_chan);
}
struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev,
struct device_node *np,
const char *phandle_name)
struct qmc_chan *devm_qmc_chan_get_byphandles_index(struct device *dev,
struct device_node *np,
const char *phandles_name,
int index)
{
struct qmc_chan *qmc_chan;
struct qmc_chan **dr;
......@@ -1838,7 +1854,7 @@ struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev,
if (!dr)
return ERR_PTR(-ENOMEM);
qmc_chan = qmc_chan_get_byphandle(np, phandle_name);
qmc_chan = qmc_chan_get_byphandles_index(np, phandles_name, index);
if (!IS_ERR(qmc_chan)) {
*dr = qmc_chan;
devres_add(dev, dr);
......@@ -1848,7 +1864,7 @@ struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev,
return qmc_chan;
}
EXPORT_SYMBOL(devm_qmc_chan_get_byphandle);
EXPORT_SYMBOL(devm_qmc_chan_get_byphandles_index);
struct qmc_chan *devm_qmc_chan_get_bychild(struct device *dev,
struct device_node *np)
......
......@@ -16,11 +16,32 @@ struct device_node;
struct device;
struct qmc_chan;
struct qmc_chan *qmc_chan_get_byphandle(struct device_node *np, const char *phandle_name);
int qmc_chan_count_phandles(struct device_node *np, const char *phandles_name);
struct qmc_chan *qmc_chan_get_byphandles_index(struct device_node *np,
const char *phandles_name,
int index);
struct qmc_chan *devm_qmc_chan_get_byphandles_index(struct device *dev,
struct device_node *np,
const char *phandles_name,
int index);
static inline struct qmc_chan *qmc_chan_get_byphandle(struct device_node *np,
const char *phandle_name)
{
return qmc_chan_get_byphandles_index(np, phandle_name, 0);
}
static inline struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev,
struct device_node *np,
const char *phandle_name)
{
return devm_qmc_chan_get_byphandles_index(dev, np, phandle_name, 0);
}
struct qmc_chan *qmc_chan_get_bychild(struct device_node *np);
void qmc_chan_put(struct qmc_chan *chan);
struct qmc_chan *devm_qmc_chan_get_byphandle(struct device *dev, struct device_node *np,
const char *phandle_name);
struct qmc_chan *devm_qmc_chan_get_bychild(struct device *dev, struct device_node *np);
enum qmc_mode {
......
......@@ -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,66 +95,143 @@ 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 void qmc_audio_pcm_write_complete(void *context);
static int qmc_audio_pcm_write_submit(struct qmc_dai_prtd *prtd)
{
struct qmc_dai_prtd *prtd = context;
unsigned int i;
int ret;
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->period_ptr_submitted += prtd->period_size;
if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
prtd->period_ptr_submitted = prtd->dma_buffer_start;
for (i = 0; i < prtd->channels; i++) {
bitmap_set(prtd->chans_pending, i, 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);
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_chan *chan = context;
struct qmc_dai_prtd *prtd;
prtd = chan->prtd_tx;
/* Mark the current channel as completed */
bitmap_clear(prtd->chans_pending, chan - prtd->qmc_dai->chans, 1);
/*
* 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) {
dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp %zu\n",
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;
}
}
prtd->period_ptr_ended += prtd->period_size;
if (prtd->period_ptr_ended >= prtd->dma_buffer_end)
prtd->period_ptr_ended = prtd->dma_buffer_start;
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;
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_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);
if (length != prtd->ch_dma_size) {
dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp %zu\n",
length, prtd->ch_dma_size);
}
/*
* 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_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,27 +535,86 @@ 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;
}
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_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(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_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_soc_dai_driver->id = qmc_dai->id;
qmc_soc_dai_driver->name = qmc_dai->name;
qmc_dai->chans = devm_kcalloc(qmc_audio->dev, count, sizeof(*qmc_dai->chans), GFP_KERNEL);
if (!qmc_dai->chans)
return -ENOMEM;
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);
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);
}
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;
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_tx_ts = info.nb_tx_ts;
qmc_dai->nb_rx_ts = info.nb_rx_ts;
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;
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,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment