162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * ALSA SoC using the QUICC Multichannel Controller (QMC) 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2022 CS GROUP France 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Herve Codina <herve.codina@bootlin.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/of.h> 1362306a36Sopenharmony_ci#include <linux/of_platform.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci#include <soc/fsl/qe/qmc.h> 1762306a36Sopenharmony_ci#include <sound/pcm_params.h> 1862306a36Sopenharmony_ci#include <sound/soc.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistruct qmc_dai { 2162306a36Sopenharmony_ci char *name; 2262306a36Sopenharmony_ci int id; 2362306a36Sopenharmony_ci struct device *dev; 2462306a36Sopenharmony_ci struct qmc_chan *qmc_chan; 2562306a36Sopenharmony_ci unsigned int nb_tx_ts; 2662306a36Sopenharmony_ci unsigned int nb_rx_ts; 2762306a36Sopenharmony_ci}; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistruct qmc_audio { 3062306a36Sopenharmony_ci struct device *dev; 3162306a36Sopenharmony_ci unsigned int num_dais; 3262306a36Sopenharmony_ci struct qmc_dai *dais; 3362306a36Sopenharmony_ci struct snd_soc_dai_driver *dai_drivers; 3462306a36Sopenharmony_ci}; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistruct qmc_dai_prtd { 3762306a36Sopenharmony_ci struct qmc_dai *qmc_dai; 3862306a36Sopenharmony_ci dma_addr_t dma_buffer_start; 3962306a36Sopenharmony_ci dma_addr_t period_ptr_submitted; 4062306a36Sopenharmony_ci dma_addr_t period_ptr_ended; 4162306a36Sopenharmony_ci dma_addr_t dma_buffer_end; 4262306a36Sopenharmony_ci size_t period_size; 4362306a36Sopenharmony_ci struct snd_pcm_substream *substream; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic int qmc_audio_pcm_construct(struct snd_soc_component *component, 4762306a36Sopenharmony_ci struct snd_soc_pcm_runtime *rtd) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci struct snd_card *card = rtd->card->snd_card; 5062306a36Sopenharmony_ci int ret; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); 5362306a36Sopenharmony_ci if (ret) 5462306a36Sopenharmony_ci return ret; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, card->dev, 5762306a36Sopenharmony_ci 64*1024, 64*1024); 5862306a36Sopenharmony_ci return 0; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic int qmc_audio_pcm_hw_params(struct snd_soc_component *component, 6262306a36Sopenharmony_ci struct snd_pcm_substream *substream, 6362306a36Sopenharmony_ci struct snd_pcm_hw_params *params) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 6662306a36Sopenharmony_ci struct qmc_dai_prtd *prtd = substream->runtime->private_data; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci prtd->dma_buffer_start = runtime->dma_addr; 6962306a36Sopenharmony_ci prtd->dma_buffer_end = runtime->dma_addr + params_buffer_bytes(params); 7062306a36Sopenharmony_ci prtd->period_size = params_period_bytes(params); 7162306a36Sopenharmony_ci prtd->period_ptr_submitted = prtd->dma_buffer_start; 7262306a36Sopenharmony_ci prtd->period_ptr_ended = prtd->dma_buffer_start; 7362306a36Sopenharmony_ci prtd->substream = substream; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci return 0; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic void qmc_audio_pcm_write_complete(void *context) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci struct qmc_dai_prtd *prtd = context; 8162306a36Sopenharmony_ci int ret; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci prtd->period_ptr_ended += prtd->period_size; 8462306a36Sopenharmony_ci if (prtd->period_ptr_ended >= prtd->dma_buffer_end) 8562306a36Sopenharmony_ci prtd->period_ptr_ended = prtd->dma_buffer_start; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci prtd->period_ptr_submitted += prtd->period_size; 8862306a36Sopenharmony_ci if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) 8962306a36Sopenharmony_ci prtd->period_ptr_submitted = prtd->dma_buffer_start; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan, 9262306a36Sopenharmony_ci prtd->period_ptr_submitted, prtd->period_size, 9362306a36Sopenharmony_ci qmc_audio_pcm_write_complete, prtd); 9462306a36Sopenharmony_ci if (ret) { 9562306a36Sopenharmony_ci dev_err(prtd->qmc_dai->dev, "write_submit failed %d\n", 9662306a36Sopenharmony_ci ret); 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci snd_pcm_period_elapsed(prtd->substream); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic void qmc_audio_pcm_read_complete(void *context, size_t length) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct qmc_dai_prtd *prtd = context; 10562306a36Sopenharmony_ci int ret; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci if (length != prtd->period_size) { 10862306a36Sopenharmony_ci dev_err(prtd->qmc_dai->dev, "read complete length = %zu, exp %zu\n", 10962306a36Sopenharmony_ci length, prtd->period_size); 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci prtd->period_ptr_ended += prtd->period_size; 11362306a36Sopenharmony_ci if (prtd->period_ptr_ended >= prtd->dma_buffer_end) 11462306a36Sopenharmony_ci prtd->period_ptr_ended = prtd->dma_buffer_start; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci prtd->period_ptr_submitted += prtd->period_size; 11762306a36Sopenharmony_ci if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) 11862306a36Sopenharmony_ci prtd->period_ptr_submitted = prtd->dma_buffer_start; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan, 12162306a36Sopenharmony_ci prtd->period_ptr_submitted, prtd->period_size, 12262306a36Sopenharmony_ci qmc_audio_pcm_read_complete, prtd); 12362306a36Sopenharmony_ci if (ret) { 12462306a36Sopenharmony_ci dev_err(prtd->qmc_dai->dev, "read_submit failed %d\n", 12562306a36Sopenharmony_ci ret); 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci snd_pcm_period_elapsed(prtd->substream); 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic int qmc_audio_pcm_trigger(struct snd_soc_component *component, 13262306a36Sopenharmony_ci struct snd_pcm_substream *substream, int cmd) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci struct qmc_dai_prtd *prtd = substream->runtime->private_data; 13562306a36Sopenharmony_ci int ret; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (!prtd->qmc_dai) { 13862306a36Sopenharmony_ci dev_err(component->dev, "qmc_dai is not set\n"); 13962306a36Sopenharmony_ci return -EINVAL; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci switch (cmd) { 14362306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 14462306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 14562306a36Sopenharmony_ci /* Submit first chunk ... */ 14662306a36Sopenharmony_ci ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan, 14762306a36Sopenharmony_ci prtd->period_ptr_submitted, prtd->period_size, 14862306a36Sopenharmony_ci qmc_audio_pcm_write_complete, prtd); 14962306a36Sopenharmony_ci if (ret) { 15062306a36Sopenharmony_ci dev_err(component->dev, "write_submit failed %d\n", 15162306a36Sopenharmony_ci ret); 15262306a36Sopenharmony_ci return ret; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci /* ... prepare next one ... */ 15662306a36Sopenharmony_ci prtd->period_ptr_submitted += prtd->period_size; 15762306a36Sopenharmony_ci if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) 15862306a36Sopenharmony_ci prtd->period_ptr_submitted = prtd->dma_buffer_start; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci /* ... and send it */ 16162306a36Sopenharmony_ci ret = qmc_chan_write_submit(prtd->qmc_dai->qmc_chan, 16262306a36Sopenharmony_ci prtd->period_ptr_submitted, prtd->period_size, 16362306a36Sopenharmony_ci qmc_audio_pcm_write_complete, prtd); 16462306a36Sopenharmony_ci if (ret) { 16562306a36Sopenharmony_ci dev_err(component->dev, "write_submit failed %d\n", 16662306a36Sopenharmony_ci ret); 16762306a36Sopenharmony_ci return ret; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci } else { 17062306a36Sopenharmony_ci /* Submit first chunk ... */ 17162306a36Sopenharmony_ci ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan, 17262306a36Sopenharmony_ci prtd->period_ptr_submitted, prtd->period_size, 17362306a36Sopenharmony_ci qmc_audio_pcm_read_complete, prtd); 17462306a36Sopenharmony_ci if (ret) { 17562306a36Sopenharmony_ci dev_err(component->dev, "read_submit failed %d\n", 17662306a36Sopenharmony_ci ret); 17762306a36Sopenharmony_ci return ret; 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci /* ... prepare next one ... */ 18162306a36Sopenharmony_ci prtd->period_ptr_submitted += prtd->period_size; 18262306a36Sopenharmony_ci if (prtd->period_ptr_submitted >= prtd->dma_buffer_end) 18362306a36Sopenharmony_ci prtd->period_ptr_submitted = prtd->dma_buffer_start; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* ... and send it */ 18662306a36Sopenharmony_ci ret = qmc_chan_read_submit(prtd->qmc_dai->qmc_chan, 18762306a36Sopenharmony_ci prtd->period_ptr_submitted, prtd->period_size, 18862306a36Sopenharmony_ci qmc_audio_pcm_read_complete, prtd); 18962306a36Sopenharmony_ci if (ret) { 19062306a36Sopenharmony_ci dev_err(component->dev, "write_submit failed %d\n", 19162306a36Sopenharmony_ci ret); 19262306a36Sopenharmony_ci return ret; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci break; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_RESUME: 19862306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 19962306a36Sopenharmony_ci break; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 20262306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_SUSPEND: 20362306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 20462306a36Sopenharmony_ci break; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci default: 20762306a36Sopenharmony_ci return -EINVAL; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci return 0; 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic snd_pcm_uframes_t qmc_audio_pcm_pointer(struct snd_soc_component *component, 21462306a36Sopenharmony_ci struct snd_pcm_substream *substream) 21562306a36Sopenharmony_ci{ 21662306a36Sopenharmony_ci struct qmc_dai_prtd *prtd = substream->runtime->private_data; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci return bytes_to_frames(substream->runtime, 21962306a36Sopenharmony_ci prtd->period_ptr_ended - prtd->dma_buffer_start); 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic int qmc_audio_of_xlate_dai_name(struct snd_soc_component *component, 22362306a36Sopenharmony_ci const struct of_phandle_args *args, 22462306a36Sopenharmony_ci const char **dai_name) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci struct qmc_audio *qmc_audio = dev_get_drvdata(component->dev); 22762306a36Sopenharmony_ci struct snd_soc_dai_driver *dai_driver; 22862306a36Sopenharmony_ci int id = args->args[0]; 22962306a36Sopenharmony_ci int i; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci for (i = 0; i < qmc_audio->num_dais; i++) { 23262306a36Sopenharmony_ci dai_driver = qmc_audio->dai_drivers + i; 23362306a36Sopenharmony_ci if (dai_driver->id == id) { 23462306a36Sopenharmony_ci *dai_name = dai_driver->name; 23562306a36Sopenharmony_ci return 0; 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci return -EINVAL; 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic const struct snd_pcm_hardware qmc_audio_pcm_hardware = { 24362306a36Sopenharmony_ci .info = SNDRV_PCM_INFO_MMAP | 24462306a36Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 24562306a36Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 24662306a36Sopenharmony_ci SNDRV_PCM_INFO_PAUSE, 24762306a36Sopenharmony_ci .period_bytes_min = 32, 24862306a36Sopenharmony_ci .period_bytes_max = 64*1024, 24962306a36Sopenharmony_ci .periods_min = 2, 25062306a36Sopenharmony_ci .periods_max = 2*1024, 25162306a36Sopenharmony_ci .buffer_bytes_max = 64*1024, 25262306a36Sopenharmony_ci}; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic int qmc_audio_pcm_open(struct snd_soc_component *component, 25562306a36Sopenharmony_ci struct snd_pcm_substream *substream) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 25862306a36Sopenharmony_ci struct qmc_dai_prtd *prtd; 25962306a36Sopenharmony_ci int ret; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci snd_soc_set_runtime_hwparams(substream, &qmc_audio_pcm_hardware); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci /* ensure that buffer size is a multiple of period size */ 26462306a36Sopenharmony_ci ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); 26562306a36Sopenharmony_ci if (ret < 0) 26662306a36Sopenharmony_ci return ret; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); 26962306a36Sopenharmony_ci if (prtd == NULL) 27062306a36Sopenharmony_ci return -ENOMEM; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci runtime->private_data = prtd; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci return 0; 27562306a36Sopenharmony_ci} 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_cistatic int qmc_audio_pcm_close(struct snd_soc_component *component, 27862306a36Sopenharmony_ci struct snd_pcm_substream *substream) 27962306a36Sopenharmony_ci{ 28062306a36Sopenharmony_ci struct qmc_dai_prtd *prtd = substream->runtime->private_data; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci kfree(prtd); 28362306a36Sopenharmony_ci return 0; 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_cistatic const struct snd_soc_component_driver qmc_audio_soc_platform = { 28762306a36Sopenharmony_ci .open = qmc_audio_pcm_open, 28862306a36Sopenharmony_ci .close = qmc_audio_pcm_close, 28962306a36Sopenharmony_ci .hw_params = qmc_audio_pcm_hw_params, 29062306a36Sopenharmony_ci .trigger = qmc_audio_pcm_trigger, 29162306a36Sopenharmony_ci .pointer = qmc_audio_pcm_pointer, 29262306a36Sopenharmony_ci .pcm_construct = qmc_audio_pcm_construct, 29362306a36Sopenharmony_ci .of_xlate_dai_name = qmc_audio_of_xlate_dai_name, 29462306a36Sopenharmony_ci}; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic unsigned int qmc_dai_get_index(struct snd_soc_dai *dai) 29762306a36Sopenharmony_ci{ 29862306a36Sopenharmony_ci struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci return dai->driver - qmc_audio->dai_drivers; 30162306a36Sopenharmony_ci} 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_cistatic struct qmc_dai *qmc_dai_get_data(struct snd_soc_dai *dai) 30462306a36Sopenharmony_ci{ 30562306a36Sopenharmony_ci struct qmc_audio *qmc_audio = snd_soc_dai_get_drvdata(dai); 30662306a36Sopenharmony_ci unsigned int index; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci index = qmc_dai_get_index(dai); 30962306a36Sopenharmony_ci if (index > qmc_audio->num_dais) 31062306a36Sopenharmony_ci return NULL; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci return qmc_audio->dais + index; 31362306a36Sopenharmony_ci} 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci/* 31662306a36Sopenharmony_ci * The constraints for format/channel is to match with the number of 8bit 31762306a36Sopenharmony_ci * time-slots available. 31862306a36Sopenharmony_ci */ 31962306a36Sopenharmony_cistatic int qmc_dai_hw_rule_channels_by_format(struct qmc_dai *qmc_dai, 32062306a36Sopenharmony_ci struct snd_pcm_hw_params *params, 32162306a36Sopenharmony_ci unsigned int nb_ts) 32262306a36Sopenharmony_ci{ 32362306a36Sopenharmony_ci struct snd_interval *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); 32462306a36Sopenharmony_ci snd_pcm_format_t format = params_format(params); 32562306a36Sopenharmony_ci struct snd_interval ch = {0}; 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci switch (snd_pcm_format_physical_width(format)) { 32862306a36Sopenharmony_ci case 8: 32962306a36Sopenharmony_ci ch.max = nb_ts; 33062306a36Sopenharmony_ci break; 33162306a36Sopenharmony_ci case 16: 33262306a36Sopenharmony_ci ch.max = nb_ts/2; 33362306a36Sopenharmony_ci break; 33462306a36Sopenharmony_ci case 32: 33562306a36Sopenharmony_ci ch.max = nb_ts/4; 33662306a36Sopenharmony_ci break; 33762306a36Sopenharmony_ci case 64: 33862306a36Sopenharmony_ci ch.max = nb_ts/8; 33962306a36Sopenharmony_ci break; 34062306a36Sopenharmony_ci default: 34162306a36Sopenharmony_ci dev_err(qmc_dai->dev, "format physical width %u not supported\n", 34262306a36Sopenharmony_ci snd_pcm_format_physical_width(format)); 34362306a36Sopenharmony_ci return -EINVAL; 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci ch.min = ch.max ? 1 : 0; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci return snd_interval_refine(c, &ch); 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cistatic int qmc_dai_hw_rule_playback_channels_by_format(struct snd_pcm_hw_params *params, 35262306a36Sopenharmony_ci struct snd_pcm_hw_rule *rule) 35362306a36Sopenharmony_ci{ 35462306a36Sopenharmony_ci struct qmc_dai *qmc_dai = rule->private; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, qmc_dai->nb_tx_ts); 35762306a36Sopenharmony_ci} 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_cistatic int qmc_dai_hw_rule_capture_channels_by_format( 36062306a36Sopenharmony_ci struct snd_pcm_hw_params *params, 36162306a36Sopenharmony_ci struct snd_pcm_hw_rule *rule) 36262306a36Sopenharmony_ci{ 36362306a36Sopenharmony_ci struct qmc_dai *qmc_dai = rule->private; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci return qmc_dai_hw_rule_channels_by_format(qmc_dai, params, qmc_dai->nb_rx_ts); 36662306a36Sopenharmony_ci} 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_cistatic int qmc_dai_hw_rule_format_by_channels(struct qmc_dai *qmc_dai, 36962306a36Sopenharmony_ci struct snd_pcm_hw_params *params, 37062306a36Sopenharmony_ci unsigned int nb_ts) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci struct snd_mask *f_old = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); 37362306a36Sopenharmony_ci unsigned int channels = params_channels(params); 37462306a36Sopenharmony_ci unsigned int slot_width; 37562306a36Sopenharmony_ci snd_pcm_format_t format; 37662306a36Sopenharmony_ci struct snd_mask f_new; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci if (!channels || channels > nb_ts) { 37962306a36Sopenharmony_ci dev_err(qmc_dai->dev, "channels %u not supported\n", 38062306a36Sopenharmony_ci nb_ts); 38162306a36Sopenharmony_ci return -EINVAL; 38262306a36Sopenharmony_ci } 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci slot_width = (nb_ts / channels) * 8; 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci snd_mask_none(&f_new); 38762306a36Sopenharmony_ci pcm_for_each_format(format) { 38862306a36Sopenharmony_ci if (snd_mask_test_format(f_old, format)) { 38962306a36Sopenharmony_ci if (snd_pcm_format_physical_width(format) <= slot_width) 39062306a36Sopenharmony_ci snd_mask_set_format(&f_new, format); 39162306a36Sopenharmony_ci } 39262306a36Sopenharmony_ci } 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci return snd_mask_refine(f_old, &f_new); 39562306a36Sopenharmony_ci} 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_cistatic int qmc_dai_hw_rule_playback_format_by_channels( 39862306a36Sopenharmony_ci struct snd_pcm_hw_params *params, 39962306a36Sopenharmony_ci struct snd_pcm_hw_rule *rule) 40062306a36Sopenharmony_ci{ 40162306a36Sopenharmony_ci struct qmc_dai *qmc_dai = rule->private; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_tx_ts); 40462306a36Sopenharmony_ci} 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_cistatic int qmc_dai_hw_rule_capture_format_by_channels( 40762306a36Sopenharmony_ci struct snd_pcm_hw_params *params, 40862306a36Sopenharmony_ci struct snd_pcm_hw_rule *rule) 40962306a36Sopenharmony_ci{ 41062306a36Sopenharmony_ci struct qmc_dai *qmc_dai = rule->private; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci return qmc_dai_hw_rule_format_by_channels(qmc_dai, params, qmc_dai->nb_rx_ts); 41362306a36Sopenharmony_ci} 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_cistatic int qmc_dai_startup(struct snd_pcm_substream *substream, 41662306a36Sopenharmony_ci struct snd_soc_dai *dai) 41762306a36Sopenharmony_ci{ 41862306a36Sopenharmony_ci struct qmc_dai_prtd *prtd = substream->runtime->private_data; 41962306a36Sopenharmony_ci snd_pcm_hw_rule_func_t hw_rule_channels_by_format; 42062306a36Sopenharmony_ci snd_pcm_hw_rule_func_t hw_rule_format_by_channels; 42162306a36Sopenharmony_ci struct qmc_dai *qmc_dai; 42262306a36Sopenharmony_ci unsigned int frame_bits; 42362306a36Sopenharmony_ci int ret; 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci qmc_dai = qmc_dai_get_data(dai); 42662306a36Sopenharmony_ci if (!qmc_dai) { 42762306a36Sopenharmony_ci dev_err(dai->dev, "Invalid dai\n"); 42862306a36Sopenharmony_ci return -EINVAL; 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci prtd->qmc_dai = qmc_dai; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { 43462306a36Sopenharmony_ci hw_rule_channels_by_format = qmc_dai_hw_rule_capture_channels_by_format; 43562306a36Sopenharmony_ci hw_rule_format_by_channels = qmc_dai_hw_rule_capture_format_by_channels; 43662306a36Sopenharmony_ci frame_bits = qmc_dai->nb_rx_ts * 8; 43762306a36Sopenharmony_ci } else { 43862306a36Sopenharmony_ci hw_rule_channels_by_format = qmc_dai_hw_rule_playback_channels_by_format; 43962306a36Sopenharmony_ci hw_rule_format_by_channels = qmc_dai_hw_rule_playback_format_by_channels; 44062306a36Sopenharmony_ci frame_bits = qmc_dai->nb_tx_ts * 8; 44162306a36Sopenharmony_ci } 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 44462306a36Sopenharmony_ci hw_rule_channels_by_format, qmc_dai, 44562306a36Sopenharmony_ci SNDRV_PCM_HW_PARAM_FORMAT, -1); 44662306a36Sopenharmony_ci if (ret) { 44762306a36Sopenharmony_ci dev_err(dai->dev, "Failed to add channels rule (%d)\n", ret); 44862306a36Sopenharmony_ci return ret; 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci ret = snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, 45262306a36Sopenharmony_ci hw_rule_format_by_channels, qmc_dai, 45362306a36Sopenharmony_ci SNDRV_PCM_HW_PARAM_CHANNELS, -1); 45462306a36Sopenharmony_ci if (ret) { 45562306a36Sopenharmony_ci dev_err(dai->dev, "Failed to add format rule (%d)\n", ret); 45662306a36Sopenharmony_ci return ret; 45762306a36Sopenharmony_ci } 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci ret = snd_pcm_hw_constraint_single(substream->runtime, 46062306a36Sopenharmony_ci SNDRV_PCM_HW_PARAM_FRAME_BITS, 46162306a36Sopenharmony_ci frame_bits); 46262306a36Sopenharmony_ci if (ret < 0) { 46362306a36Sopenharmony_ci dev_err(dai->dev, "Failed to add frame_bits constraint (%d)\n", ret); 46462306a36Sopenharmony_ci return ret; 46562306a36Sopenharmony_ci } 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci return 0; 46862306a36Sopenharmony_ci} 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_cistatic int qmc_dai_hw_params(struct snd_pcm_substream *substream, 47162306a36Sopenharmony_ci struct snd_pcm_hw_params *params, 47262306a36Sopenharmony_ci struct snd_soc_dai *dai) 47362306a36Sopenharmony_ci{ 47462306a36Sopenharmony_ci struct qmc_chan_param chan_param = {0}; 47562306a36Sopenharmony_ci struct qmc_dai *qmc_dai; 47662306a36Sopenharmony_ci int ret; 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci qmc_dai = qmc_dai_get_data(dai); 47962306a36Sopenharmony_ci if (!qmc_dai) { 48062306a36Sopenharmony_ci dev_err(dai->dev, "Invalid dai\n"); 48162306a36Sopenharmony_ci return -EINVAL; 48262306a36Sopenharmony_ci } 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { 48562306a36Sopenharmony_ci chan_param.mode = QMC_TRANSPARENT; 48662306a36Sopenharmony_ci chan_param.transp.max_rx_buf_size = params_period_bytes(params); 48762306a36Sopenharmony_ci ret = qmc_chan_set_param(qmc_dai->qmc_chan, &chan_param); 48862306a36Sopenharmony_ci if (ret) { 48962306a36Sopenharmony_ci dev_err(dai->dev, "set param failed %d\n", 49062306a36Sopenharmony_ci ret); 49162306a36Sopenharmony_ci return ret; 49262306a36Sopenharmony_ci } 49362306a36Sopenharmony_ci } 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci return 0; 49662306a36Sopenharmony_ci} 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_cistatic int qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd, 49962306a36Sopenharmony_ci struct snd_soc_dai *dai) 50062306a36Sopenharmony_ci{ 50162306a36Sopenharmony_ci struct qmc_dai *qmc_dai; 50262306a36Sopenharmony_ci int direction; 50362306a36Sopenharmony_ci int ret; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci qmc_dai = qmc_dai_get_data(dai); 50662306a36Sopenharmony_ci if (!qmc_dai) { 50762306a36Sopenharmony_ci dev_err(dai->dev, "Invalid dai\n"); 50862306a36Sopenharmony_ci return -EINVAL; 50962306a36Sopenharmony_ci } 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci direction = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 51262306a36Sopenharmony_ci QMC_CHAN_WRITE : QMC_CHAN_READ; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci switch (cmd) { 51562306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 51662306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_RESUME: 51762306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 51862306a36Sopenharmony_ci ret = qmc_chan_start(qmc_dai->qmc_chan, direction); 51962306a36Sopenharmony_ci if (ret) 52062306a36Sopenharmony_ci return ret; 52162306a36Sopenharmony_ci break; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 52462306a36Sopenharmony_ci ret = qmc_chan_stop(qmc_dai->qmc_chan, direction); 52562306a36Sopenharmony_ci if (ret) 52662306a36Sopenharmony_ci return ret; 52762306a36Sopenharmony_ci ret = qmc_chan_reset(qmc_dai->qmc_chan, direction); 52862306a36Sopenharmony_ci if (ret) 52962306a36Sopenharmony_ci return ret; 53062306a36Sopenharmony_ci break; 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_SUSPEND: 53362306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 53462306a36Sopenharmony_ci ret = qmc_chan_stop(qmc_dai->qmc_chan, direction); 53562306a36Sopenharmony_ci if (ret) 53662306a36Sopenharmony_ci return ret; 53762306a36Sopenharmony_ci break; 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci default: 54062306a36Sopenharmony_ci return -EINVAL; 54162306a36Sopenharmony_ci } 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci return 0; 54462306a36Sopenharmony_ci} 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_cistatic const struct snd_soc_dai_ops qmc_dai_ops = { 54762306a36Sopenharmony_ci .startup = qmc_dai_startup, 54862306a36Sopenharmony_ci .trigger = qmc_dai_trigger, 54962306a36Sopenharmony_ci .hw_params = qmc_dai_hw_params, 55062306a36Sopenharmony_ci}; 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_cistatic u64 qmc_audio_formats(u8 nb_ts) 55362306a36Sopenharmony_ci{ 55462306a36Sopenharmony_ci unsigned int format_width; 55562306a36Sopenharmony_ci unsigned int chan_width; 55662306a36Sopenharmony_ci snd_pcm_format_t format; 55762306a36Sopenharmony_ci u64 formats_mask; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci if (!nb_ts) 56062306a36Sopenharmony_ci return 0; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci formats_mask = 0; 56362306a36Sopenharmony_ci chan_width = nb_ts * 8; 56462306a36Sopenharmony_ci pcm_for_each_format(format) { 56562306a36Sopenharmony_ci /* 56662306a36Sopenharmony_ci * Support format other than little-endian (ie big-endian or 56762306a36Sopenharmony_ci * without endianness such as 8bit formats) 56862306a36Sopenharmony_ci */ 56962306a36Sopenharmony_ci if (snd_pcm_format_little_endian(format) == 1) 57062306a36Sopenharmony_ci continue; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci /* Support physical width multiple of 8bit */ 57362306a36Sopenharmony_ci format_width = snd_pcm_format_physical_width(format); 57462306a36Sopenharmony_ci if (format_width == 0 || format_width % 8) 57562306a36Sopenharmony_ci continue; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci /* 57862306a36Sopenharmony_ci * And support physical width that can fit N times in the 57962306a36Sopenharmony_ci * channel 58062306a36Sopenharmony_ci */ 58162306a36Sopenharmony_ci if (format_width > chan_width || chan_width % format_width) 58262306a36Sopenharmony_ci continue; 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_ci formats_mask |= pcm_format_to_bits(format); 58562306a36Sopenharmony_ci } 58662306a36Sopenharmony_ci return formats_mask; 58762306a36Sopenharmony_ci} 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_cistatic int qmc_audio_dai_parse(struct qmc_audio *qmc_audio, struct device_node *np, 59062306a36Sopenharmony_ci struct qmc_dai *qmc_dai, struct snd_soc_dai_driver *qmc_soc_dai_driver) 59162306a36Sopenharmony_ci{ 59262306a36Sopenharmony_ci struct qmc_chan_info info; 59362306a36Sopenharmony_ci u32 val; 59462306a36Sopenharmony_ci int ret; 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci qmc_dai->dev = qmc_audio->dev; 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci ret = of_property_read_u32(np, "reg", &val); 59962306a36Sopenharmony_ci if (ret) { 60062306a36Sopenharmony_ci dev_err(qmc_audio->dev, "%pOF: failed to read reg\n", np); 60162306a36Sopenharmony_ci return ret; 60262306a36Sopenharmony_ci } 60362306a36Sopenharmony_ci qmc_dai->id = val; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci qmc_dai->name = devm_kasprintf(qmc_audio->dev, GFP_KERNEL, "%s.%d", 60662306a36Sopenharmony_ci np->parent->name, qmc_dai->id); 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci qmc_dai->qmc_chan = devm_qmc_chan_get_byphandle(qmc_audio->dev, np, 60962306a36Sopenharmony_ci "fsl,qmc-chan"); 61062306a36Sopenharmony_ci if (IS_ERR(qmc_dai->qmc_chan)) { 61162306a36Sopenharmony_ci ret = PTR_ERR(qmc_dai->qmc_chan); 61262306a36Sopenharmony_ci return dev_err_probe(qmc_audio->dev, ret, 61362306a36Sopenharmony_ci "dai %d get QMC channel failed\n", qmc_dai->id); 61462306a36Sopenharmony_ci } 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci qmc_soc_dai_driver->id = qmc_dai->id; 61762306a36Sopenharmony_ci qmc_soc_dai_driver->name = qmc_dai->name; 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci ret = qmc_chan_get_info(qmc_dai->qmc_chan, &info); 62062306a36Sopenharmony_ci if (ret) { 62162306a36Sopenharmony_ci dev_err(qmc_audio->dev, "dai %d get QMC channel info failed %d\n", 62262306a36Sopenharmony_ci qmc_dai->id, ret); 62362306a36Sopenharmony_ci return ret; 62462306a36Sopenharmony_ci } 62562306a36Sopenharmony_ci dev_info(qmc_audio->dev, "dai %d QMC channel mode %d, nb_tx_ts %u, nb_rx_ts %u\n", 62662306a36Sopenharmony_ci qmc_dai->id, info.mode, info.nb_tx_ts, info.nb_rx_ts); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci if (info.mode != QMC_TRANSPARENT) { 62962306a36Sopenharmony_ci dev_err(qmc_audio->dev, "dai %d QMC chan mode %d is not QMC_TRANSPARENT\n", 63062306a36Sopenharmony_ci qmc_dai->id, info.mode); 63162306a36Sopenharmony_ci return -EINVAL; 63262306a36Sopenharmony_ci } 63362306a36Sopenharmony_ci qmc_dai->nb_tx_ts = info.nb_tx_ts; 63462306a36Sopenharmony_ci qmc_dai->nb_rx_ts = info.nb_rx_ts; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci qmc_soc_dai_driver->playback.channels_min = 0; 63762306a36Sopenharmony_ci qmc_soc_dai_driver->playback.channels_max = 0; 63862306a36Sopenharmony_ci if (qmc_dai->nb_tx_ts) { 63962306a36Sopenharmony_ci qmc_soc_dai_driver->playback.channels_min = 1; 64062306a36Sopenharmony_ci qmc_soc_dai_driver->playback.channels_max = qmc_dai->nb_tx_ts; 64162306a36Sopenharmony_ci } 64262306a36Sopenharmony_ci qmc_soc_dai_driver->playback.formats = qmc_audio_formats(qmc_dai->nb_tx_ts); 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci qmc_soc_dai_driver->capture.channels_min = 0; 64562306a36Sopenharmony_ci qmc_soc_dai_driver->capture.channels_max = 0; 64662306a36Sopenharmony_ci if (qmc_dai->nb_rx_ts) { 64762306a36Sopenharmony_ci qmc_soc_dai_driver->capture.channels_min = 1; 64862306a36Sopenharmony_ci qmc_soc_dai_driver->capture.channels_max = qmc_dai->nb_rx_ts; 64962306a36Sopenharmony_ci } 65062306a36Sopenharmony_ci qmc_soc_dai_driver->capture.formats = qmc_audio_formats(qmc_dai->nb_rx_ts); 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci qmc_soc_dai_driver->playback.rates = snd_pcm_rate_to_rate_bit(info.tx_fs_rate); 65362306a36Sopenharmony_ci qmc_soc_dai_driver->playback.rate_min = info.tx_fs_rate; 65462306a36Sopenharmony_ci qmc_soc_dai_driver->playback.rate_max = info.tx_fs_rate; 65562306a36Sopenharmony_ci qmc_soc_dai_driver->capture.rates = snd_pcm_rate_to_rate_bit(info.rx_fs_rate); 65662306a36Sopenharmony_ci qmc_soc_dai_driver->capture.rate_min = info.rx_fs_rate; 65762306a36Sopenharmony_ci qmc_soc_dai_driver->capture.rate_max = info.rx_fs_rate; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci qmc_soc_dai_driver->ops = &qmc_dai_ops; 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci return 0; 66262306a36Sopenharmony_ci} 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_cistatic int qmc_audio_probe(struct platform_device *pdev) 66562306a36Sopenharmony_ci{ 66662306a36Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 66762306a36Sopenharmony_ci struct qmc_audio *qmc_audio; 66862306a36Sopenharmony_ci struct device_node *child; 66962306a36Sopenharmony_ci unsigned int i; 67062306a36Sopenharmony_ci int ret; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci qmc_audio = devm_kzalloc(&pdev->dev, sizeof(*qmc_audio), GFP_KERNEL); 67362306a36Sopenharmony_ci if (!qmc_audio) 67462306a36Sopenharmony_ci return -ENOMEM; 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci qmc_audio->dev = &pdev->dev; 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci qmc_audio->num_dais = of_get_available_child_count(np); 67962306a36Sopenharmony_ci if (qmc_audio->num_dais) { 68062306a36Sopenharmony_ci qmc_audio->dais = devm_kcalloc(&pdev->dev, qmc_audio->num_dais, 68162306a36Sopenharmony_ci sizeof(*qmc_audio->dais), 68262306a36Sopenharmony_ci GFP_KERNEL); 68362306a36Sopenharmony_ci if (!qmc_audio->dais) 68462306a36Sopenharmony_ci return -ENOMEM; 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci qmc_audio->dai_drivers = devm_kcalloc(&pdev->dev, qmc_audio->num_dais, 68762306a36Sopenharmony_ci sizeof(*qmc_audio->dai_drivers), 68862306a36Sopenharmony_ci GFP_KERNEL); 68962306a36Sopenharmony_ci if (!qmc_audio->dai_drivers) 69062306a36Sopenharmony_ci return -ENOMEM; 69162306a36Sopenharmony_ci } 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci i = 0; 69462306a36Sopenharmony_ci for_each_available_child_of_node(np, child) { 69562306a36Sopenharmony_ci ret = qmc_audio_dai_parse(qmc_audio, child, 69662306a36Sopenharmony_ci qmc_audio->dais + i, 69762306a36Sopenharmony_ci qmc_audio->dai_drivers + i); 69862306a36Sopenharmony_ci if (ret) { 69962306a36Sopenharmony_ci of_node_put(child); 70062306a36Sopenharmony_ci return ret; 70162306a36Sopenharmony_ci } 70262306a36Sopenharmony_ci i++; 70362306a36Sopenharmony_ci } 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci platform_set_drvdata(pdev, qmc_audio); 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci ret = devm_snd_soc_register_component(qmc_audio->dev, 70962306a36Sopenharmony_ci &qmc_audio_soc_platform, 71062306a36Sopenharmony_ci qmc_audio->dai_drivers, 71162306a36Sopenharmony_ci qmc_audio->num_dais); 71262306a36Sopenharmony_ci if (ret) 71362306a36Sopenharmony_ci return ret; 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci return 0; 71662306a36Sopenharmony_ci} 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_cistatic const struct of_device_id qmc_audio_id_table[] = { 71962306a36Sopenharmony_ci { .compatible = "fsl,qmc-audio" }, 72062306a36Sopenharmony_ci {} /* sentinel */ 72162306a36Sopenharmony_ci}; 72262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, qmc_audio_id_table); 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_cistatic struct platform_driver qmc_audio_driver = { 72562306a36Sopenharmony_ci .driver = { 72662306a36Sopenharmony_ci .name = "fsl-qmc-audio", 72762306a36Sopenharmony_ci .of_match_table = of_match_ptr(qmc_audio_id_table), 72862306a36Sopenharmony_ci }, 72962306a36Sopenharmony_ci .probe = qmc_audio_probe, 73062306a36Sopenharmony_ci}; 73162306a36Sopenharmony_cimodule_platform_driver(qmc_audio_driver); 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ciMODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>"); 73462306a36Sopenharmony_ciMODULE_DESCRIPTION("CPM/QE QMC audio driver"); 73562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 736