Le 17/02/2023 à 15:56, Herve Codina a écrit :
> The QMC audio is an ASoC component which provides DAIs
> that use the QMC (QUICC Multichannel Controller) to transfer
> the audio data.
> 
> It provides as many DAIs as the number of QMC channels it
> references.
> 
> Signed-off-by: Herve Codina <herve.cod...@bootlin.com>

Reviewed-by: Christophe Leroy <christophe.le...@csgroup.eu>
Tested-by: Christophe Leroy <christophe.le...@csgroup.eu>

> ---
>   sound/soc/fsl/Kconfig         |   9 +
>   sound/soc/fsl/Makefile        |   2 +
>   sound/soc/fsl/fsl_qmc_audio.c | 735 ++++++++++++++++++++++++++++++++++
>   3 files changed, 746 insertions(+)
>   create mode 100644 sound/soc/fsl/fsl_qmc_audio.c
> 
> diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
> index 614eceda6b9e..17db29c25d96 100644
> --- a/sound/soc/fsl/Kconfig
> +++ b/sound/soc/fsl/Kconfig
> @@ -172,6 +172,15 @@ config SND_MPC52xx_DMA
>   config SND_SOC_POWERPC_DMA
>       tristate
>   
> +config SND_SOC_POWERPC_QMC_AUDIO
> +     tristate "QMC ALSA SoC support"
> +     depends on CPM_QMC
> +     help
> +       ALSA SoC Audio support using the Freescale QUICC Multichannel
> +       Controller (QMC).
> +       Say Y or M if you want to add support for SoC audio using Freescale
> +       QMC.
> +
>   comment "SoC Audio support for Freescale PPC boards:"
>   
>   config SND_SOC_MPC8610_HPCD
> diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
> index b54beb1a66fa..8db7e97d0bd5 100644
> --- a/sound/soc/fsl/Makefile
> +++ b/sound/soc/fsl/Makefile
> @@ -28,6 +28,7 @@ snd-soc-fsl-easrc-objs := fsl_easrc.o
>   snd-soc-fsl-xcvr-objs := fsl_xcvr.o
>   snd-soc-fsl-aud2htx-objs := fsl_aud2htx.o
>   snd-soc-fsl-rpmsg-objs := fsl_rpmsg.o
> +snd-soc-fsl-qmc-audio-objs := fsl_qmc_audio.o
>   
>   obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o
>   obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
> @@ -44,6 +45,7 @@ obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
>   obj-$(CONFIG_SND_SOC_FSL_XCVR) += snd-soc-fsl-xcvr.o
>   obj-$(CONFIG_SND_SOC_FSL_AUD2HTX) += snd-soc-fsl-aud2htx.o
>   obj-$(CONFIG_SND_SOC_FSL_RPMSG) += snd-soc-fsl-rpmsg.o
> +obj-$(CONFIG_SND_SOC_POWERPC_QMC_AUDIO) += snd-soc-fsl-qmc-audio.o
>   
>   # MPC5200 Platform Support
>   obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
> diff --git a/sound/soc/fsl/fsl_qmc_audio.c b/sound/soc/fsl/fsl_qmc_audio.c
> new file mode 100644
> index 000000000000..7cbb8e4758cc
> --- /dev/null
> +++ b/sound/soc/fsl/fsl_qmc_audio.c
> @@ -0,0 +1,735 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ALSA SoC using the QUICC Multichannel Controller (QMC)
> + *
> + * Copyright 2022 CS GROUP France
> + *
> + * Author: Herve Codina <herve.cod...@bootlin.com>
> + */
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <soc/fsl/qe/qmc.h>
> +#include <sound/pcm_params.h>
> +#include <sound/soc.h>
> +
> +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;
> +};
> +
> +struct qmc_audio {
> +     struct device *dev;
> +     unsigned int num_dais;
> +     struct qmc_dai *dais;
> +     struct snd_soc_dai_driver *dai_drivers;
> +};
> +
> +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;
> +     struct snd_pcm_substream *substream;
> +};
> +
> +static int qmc_audio_pcm_construct(struct snd_soc_component *component,
> +                                struct snd_soc_pcm_runtime *rtd)
> +{
> +     struct snd_card *card = rtd->card->snd_card;
> +     int ret;
> +
> +     ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
> +     if (ret)
> +             return ret;
> +
> +     snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, card->dev,
> +                                    64*1024, 64*1024);
> +     return 0;
> +}
> +
> +static int qmc_audio_pcm_hw_params(struct snd_soc_component *component,
> +                                struct snd_pcm_substream *substream,
> +                                struct snd_pcm_hw_params *params)
> +{
> +     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;
> +     prtd->substream = substream;
> +
> +     return 0;
> +}
> +
> +static void qmc_audio_pcm_write_complete(void *context)
> +{
> +     struct qmc_dai_prtd *prtd = context;
> +     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;
> +
> +     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);
> +     }
> +
> +     snd_pcm_period_elapsed(prtd->substream);
> +}
> +
> +static void qmc_audio_pcm_read_complete(void *context, size_t length)
> +{
> +     struct qmc_dai_prtd *prtd = context;
> +     int ret;
> +
> +     if (length != prtd->period_size) {
> +             dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp 
> %zu\n",
> +                     length, prtd->period_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;
> +
> +     prtd->period_ptr_submitted += prtd->period_size;
> +     if (prtd->period_ptr_submitted >= prtd->dma_buffer_end)
> +             prtd->period_ptr_submitted = prtd->dma_buffer_start;
> +
> +     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);
> +     }
> +
> +     snd_pcm_period_elapsed(prtd->substream);
> +}
> +
> +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;
> +     int ret;
> +
> +     if (!prtd->qmc_dai) {
> +             dev_err(component->dev, "qmc_dai is not set\n");
> +             return -EINVAL;
> +     }
> +
> +     switch (cmd) {
> +     case SNDRV_PCM_TRIGGER_START:
> +             if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> +                     /* 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);
> +                             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;
> +
> +                     /* ... 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);
> +                             return ret;
> +                     }
> +             } else {
> +                     /* 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);
> +                             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;
> +
> +                     /* ... 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);
> +                             return ret;
> +                     }
> +             }
> +             break;
> +
> +     case SNDRV_PCM_TRIGGER_RESUME:
> +     case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +             break;
> +
> +     case SNDRV_PCM_TRIGGER_STOP:
> +     case SNDRV_PCM_TRIGGER_SUSPEND:
> +     case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> +             break;
> +
> +     default:
> +             return -EINVAL;
> +     }
> +
> +     return 0;
> +}
> +
> +static snd_pcm_uframes_t qmc_audio_pcm_pointer(struct snd_soc_component 
> *component,
> +                                            struct snd_pcm_substream 
> *substream)
> +{
> +     struct qmc_dai_prtd *prtd = substream->runtime->private_data;
> +
> +     return bytes_to_frames(substream->runtime,
> +                            prtd->period_ptr_ended - prtd->dma_buffer_start);
> +}
> +
> +static int qmc_audio_of_xlate_dai_name(struct snd_soc_component *component,
> +                                     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;
> +     int id = args->args[0];
> +     int i;
> +
> +     for (i = 0; i  < qmc_audio->num_dais; i++) {
> +             dai_driver = qmc_audio->dai_drivers + i;
> +             if (dai_driver->id == id) {
> +                     *dai_name = dai_driver->name;
> +                     return 0;
> +             }
> +     }
> +
> +     return -EINVAL;
> +}
> +
> +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_PAUSE,
> +     .period_bytes_min       = 32,
> +     .period_bytes_max       = 64*1024,
> +     .periods_min            = 2,
> +     .periods_max            = 2*1024,
> +     .buffer_bytes_max       = 64*1024,
> +};
> +
> +static int qmc_audio_pcm_open(struct snd_soc_component *component,
> +                           struct snd_pcm_substream *substream)
> +{
> +     struct snd_pcm_runtime *runtime = substream->runtime;
> +     struct qmc_dai_prtd *prtd;
> +     int ret;
> +
> +     snd_soc_set_runtime_hwparams(substream, &qmc_audio_pcm_hardware);
> +
> +     /* ensure that buffer size is a multiple of period size */
> +     ret = snd_pcm_hw_constraint_integer(runtime, 
> SNDRV_PCM_HW_PARAM_PERIODS);
> +     if (ret < 0)
> +             return ret;
> +
> +     prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
> +     if (prtd == NULL)
> +             return -ENOMEM;
> +
> +     runtime->private_data = prtd;
> +
> +     return 0;
> +}
> +
> +static int qmc_audio_pcm_close(struct snd_soc_component *component,
> +                            struct snd_pcm_substream *substream)
> +{
> +     struct qmc_dai_prtd *prtd = substream->runtime->private_data;
> +
> +     kfree(prtd);
> +     return 0;
> +}
> +
> +static const struct snd_soc_component_driver qmc_audio_soc_platform = {
> +     .open                   = qmc_audio_pcm_open,
> +     .close                  = qmc_audio_pcm_close,
> +     .hw_params              = qmc_audio_pcm_hw_params,
> +     .trigger                = qmc_audio_pcm_trigger,
> +     .pointer                = qmc_audio_pcm_pointer,
> +     .pcm_construct          = qmc_audio_pcm_construct,
> +     .of_xlate_dai_name      = qmc_audio_of_xlate_dai_name,
> +};
> +
> +static unsigned int qmc_dai_get_index(struct snd_soc_dai *dai)
> +{
> +     struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai);
> +
> +     return dai->driver - qmc_audio->dai_drivers;
> +}
> +
> +static struct qmc_dai *qmc_dai_get_data(struct snd_soc_dai *dai)
> +{
> +     struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai);
> +     unsigned int index;
> +
> +     index = qmc_dai_get_index(dai);
> +     if (index > qmc_audio->num_dais)
> +             return NULL;
> +
> +     return qmc_audio->dais + index;
> +}
> +
> +/*
> + * The constraints for format/channel is to match with the number of 8bit
> + * time-slots available.
> + */
> +static int qmc_dai_hw_rule_channels_by_format(struct qmc_dai *qmc_dai,
> +                                           struct snd_pcm_hw_params *params,
> +                                           unsigned int nb_ts)
> +{
> +     struct snd_interval *c = hw_param_interval(params, 
> SNDRV_PCM_HW_PARAM_CHANNELS);
> +     snd_pcm_format_t format = params_format(params);
> +     struct snd_interval ch = {0};
> +
> +     switch (snd_pcm_format_physical_width(format)) {
> +     case 8:
> +             ch.max = nb_ts;
> +             break;
> +     case 16:
> +             ch.max = nb_ts/2;
> +             break;
> +     case 32:
> +             ch.max = nb_ts/4;
> +             break;
> +     case 64:
> +             ch.max = nb_ts/8;
> +             break;
> +     default:
> +             dev_err(qmc_dai->dev, "format physical width %u not 
> supported\n",
> +                     snd_pcm_format_physical_width(format));
> +             return -EINVAL;
> +     }
> +
> +     ch.min = ch.max ? 1 : 0;
> +
> +     return snd_interval_refine(c, &ch);
> +}
> +
> +static int qmc_dai_hw_rule_playback_channels_by_format(struct 
> snd_pcm_hw_params *params,
> +                                                    struct snd_pcm_hw_rule 
> *rule)
> +{
> +     struct qmc_dai *qmc_dai = rule->private;
> +
> +     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)
> +{
> +     struct qmc_dai *qmc_dai = rule->private;
> +
> +     return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, 
> qmc_dai->nb_rx_ts);
> +}
> +
> +static int qmc_dai_hw_rule_format_by_channels(struct qmc_dai *qmc_dai,
> +                                           struct snd_pcm_hw_params *params,
> +                                           unsigned int nb_ts)
> +{
> +     struct snd_mask *f_old = hw_param_mask(params, 
> SNDRV_PCM_HW_PARAM_FORMAT);
> +     unsigned int channels = params_channels(params);
> +     unsigned int slot_width;
> +     struct snd_mask f_new;
> +     unsigned int i;
> +
> +     if (!channels || channels > nb_ts) {
> +             dev_err(qmc_dai->dev, "channels %u not supported\n",
> +                     nb_ts);
> +             return -EINVAL;
> +     }
> +
> +     slot_width = (nb_ts / channels) * 8;
> +
> +     snd_mask_none(&f_new);
> +     for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) {
> +             if (snd_mask_test(f_old, i)) {
> +                     if (snd_pcm_format_physical_width(i) <= slot_width)
> +                             snd_mask_set(&f_new, i);
> +             }
> +     }
> +
> +     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)
> +{
> +     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)
> +{
> +     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)
> +{
> +     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;
> +     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;
> +             frame_bits = qmc_dai->nb_rx_ts * 8;
> +     } else {
> +             hw_rule_channels_by_format = 
> qmc_dai_hw_rule_playback_channels_by_format;
> +             hw_rule_format_by_channels = 
> qmc_dai_hw_rule_playback_format_by_channels;
> +             frame_bits = qmc_dai->nb_tx_ts * 8;
> +     }
> +
> +     ret = snd_pcm_hw_rule_add(substream->runtime, 0, 
> SNDRV_PCM_HW_PARAM_CHANNELS,
> +                               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);
> +             return ret;
> +     }
> +
> +     ret = snd_pcm_hw_rule_add(substream->runtime, 0,  
> SNDRV_PCM_HW_PARAM_FORMAT,
> +                               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);
> +             return ret;
> +     }
> +
> +     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);
> +             return ret;
> +     }
> +
> +     return 0;
> +}
> +
> +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};
> +     struct qmc_dai *qmc_dai;
> +     int ret;
> +
> +     qmc_dai = qmc_dai_get_data(dai);
> +     if (!qmc_dai) {
> +             dev_err(dai->dev, "Invalid dai\n");
> +             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;
> +             }
> +     }
> +
> +     return 0;
> +}
> +
> +static int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
> +                        struct snd_soc_dai *dai)
> +{
> +     struct qmc_dai *qmc_dai;
> +     int direction;
> +     int ret;
> +
> +     qmc_dai = qmc_dai_get_data(dai);
> +     if (!qmc_dai) {
> +             dev_err(dai->dev, "Invalid dai\n");
> +             return -EINVAL;
> +     }
> +
> +     direction = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
> +                 QMC_CHAN_WRITE : QMC_CHAN_READ;
> +
> +     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;
> +             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);
> +             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);
> +             if (ret)
> +                     return ret;
> +             break;
> +
> +     default:
> +             return -EINVAL;
> +     }
> +
> +     return 0;
> +}
> +
> +static const struct snd_soc_dai_ops qmc_dai_ops = {
> +     .startup        = qmc_dai_startup,
> +     .trigger        = qmc_dai_trigger,
> +     .hw_params      = qmc_dai_hw_params,
> +};
> +
> +static u64 qmc_audio_formats(u8 nb_ts)
> +{
> +     u64 formats;
> +     unsigned int chan_width;
> +     unsigned int format_width;
> +     int i;
> +
> +     if (!nb_ts)
> +             return 0;
> +
> +     formats = 0;
> +     chan_width = nb_ts * 8;
> +     for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) {
> +             /*
> +              * Support format other than little-endian (ie big-endian or
> +              * without endianness such as 8bit formats)
> +              */
> +             if (snd_pcm_format_little_endian(i) == 1)
> +                     continue;
> +
> +             /* Support physical width multiple of 8bit */
> +             format_width = snd_pcm_format_physical_width(i);
> +             if (format_width == 0 || format_width % 8)
> +                     continue;
> +
> +             /*
> +              * And support physical width that can fit N times in the
> +              * channel
> +              */
> +             if (format_width > chan_width || chan_width % format_width)
> +                     continue;
> +
> +             formats |= (1ULL << i);
> +     }
> +     return formats;
> +}
> +
> +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_chan_info info;
> +     u32 val;
> +     int ret;
> +
> +     qmc_dai->dev = qmc_audio->dev;
> +
> +     ret = of_property_read_u32(np, "reg", &val);
> +     if (ret) {
> +             dev_err(qmc_audio->dev, "%pOF: failed to read reg\n", np);
> +             return ret;
> +     }
> +     qmc_dai->id = val;
> +
> +     qmc_dai->name = devm_kasprintf(qmc_audio->dev, GFP_KERNEL, "%s.%d",
> +                                    np->parent->name, qmc_dai->id);
> +
> +     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);
> +     }
> +
> +     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) {
> +             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.formats = 
> qmc_audio_formats(qmc_dai->nb_tx_ts);
> +
> +     qmc_soc_dai_driver->capture.channels_min = 0;
> +     qmc_soc_dai_driver->capture.channels_max = 0;
> +     if (qmc_dai->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.formats = 
> qmc_audio_formats(qmc_dai->nb_rx_ts);
> +
> +     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->ops = &qmc_dai_ops;
> +
> +     return 0;
> +}
> +
> +static int qmc_audio_probe(struct platform_device *pdev)
> +{
> +     struct device_node *np = pdev->dev.of_node;
> +     struct qmc_audio *qmc_audio;
> +     struct device_node *child;
> +     unsigned int i;
> +     int ret;
> +
> +     qmc_audio = devm_kzalloc(&pdev->dev, sizeof(*qmc_audio), GFP_KERNEL);
> +     if (!qmc_audio)
> +             return -ENOMEM;
> +
> +     qmc_audio->dev = &pdev->dev;
> +
> +     qmc_audio->num_dais = of_get_available_child_count(np);
> +     if (qmc_audio->num_dais) {
> +             qmc_audio->dais = devm_kcalloc(&pdev->dev, qmc_audio->num_dais,
> +                                            sizeof(*qmc_audio->dais),
> +                                            GFP_KERNEL);
> +             if (!qmc_audio->dais)
> +                     return -ENOMEM;
> +
> +             qmc_audio->dai_drivers = devm_kcalloc(&pdev->dev, 
> qmc_audio->num_dais,
> +                                                   
> sizeof(*qmc_audio->dai_drivers),
> +                                                   GFP_KERNEL);
> +             if (!qmc_audio->dai_drivers)
> +                     return -ENOMEM;
> +     }
> +
> +     i = 0;
> +     for_each_available_child_of_node(np, child) {
> +             ret = qmc_audio_dai_parse(qmc_audio, child,
> +                                       qmc_audio->dais + i,
> +                                       qmc_audio->dai_drivers + i);
> +             if (ret) {
> +                     of_node_put(child);
> +                     return ret;
> +             }
> +             i++;
> +     }
> +
> +
> +     platform_set_drvdata(pdev, qmc_audio);
> +
> +     ret = devm_snd_soc_register_component(qmc_audio->dev,
> +                                           &qmc_audio_soc_platform,
> +                                           qmc_audio->dai_drivers,
> +                                           qmc_audio->num_dais);
> +     if (ret)
> +             return ret;
> +
> +     return 0;
> +}
> +
> +static const struct of_device_id qmc_audio_id_table[] = {
> +     { .compatible = "fsl,qmc-audio" },
> +     {} /* sentinel */
> +};
> +MODULE_DEVICE_TABLE(of, qmc_audio_id_table);
> +
> +static struct platform_driver qmc_audio_driver = {
> +     .driver = {
> +             .name = "fsl-qmc-audio",
> +             .of_match_table = of_match_ptr(qmc_audio_id_table),
> +     },
> +     .probe = qmc_audio_probe,
> +};
> +module_platform_driver(qmc_audio_driver);
> +
> +MODULE_AUTHOR("Herve Codina <herve.cod...@bootlin.com>");
> +MODULE_DESCRIPTION("CPM/QE QMC audio driver");
> +MODULE_LICENSE("GPL");

Reply via email to