--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ALSA SoC using the QUICC Multichannel Controller (QMC)
+ *
+ * Copyright 2022 CS GROUP France
+ *
+ * Author: Herve Codina <herve.codina@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.codina@bootlin.com>");
+MODULE_DESCRIPTION("CPM/QE QMC audio driver");
+MODULE_LICENSE("GPL");