/* * In interleaved mode, the driver uses one QMC channel for all audio * channels whereas in non-interleaved mode, it uses one QMC channel per * audio channel.
*/
prtd->channels = qmc_audio_access_is_interleaved(params_access(params)) ?
1 : params_channels(params);
/* Mark the current channel as completed */
bitmap_clear(prtd->chans_pending, chan - prtd->qmc_dai->chans, 1);
/* * All QMC channels involved must have completed their transfer before * submitting a new one.
*/ if (!bitmap_empty(prtd->chans_pending, 64)) return;
prtd->buffer_ended += prtd->period_size; if (prtd->buffer_ended >= prtd->buffer_size)
prtd->buffer_ended = 0;
prtd->ch_dma_addr_current += prtd->ch_dma_size; if (prtd->ch_dma_addr_current >= prtd->ch_dma_addr_end)
prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;
/* * All QMC channels involved must have completed their transfer before * submitting a new one.
*/ if (!bitmap_empty(prtd->chans_pending, 64)) return;
prtd->buffer_ended += prtd->period_size; if (prtd->buffer_ended >= prtd->buffer_size)
prtd->buffer_ended = 0;
prtd->ch_dma_addr_current += prtd->ch_dma_size; if (prtd->ch_dma_addr_current >= prtd->ch_dma_addr_end)
prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;
qmc_audio_pcm_read_submit(prtd);
snd_pcm_period_elapsed(prtd->substream);
}
staticint 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; unsignedint i; int ret;
if (!prtd->qmc_dai) {
dev_err(component->dev, "qmc_dai is not set\n"); return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { for (i = 0; i < prtd->channels; i++)
prtd->qmc_dai->chans[i].prtd_tx = prtd;
/* Submit first chunk ... */
ret = qmc_audio_pcm_write_submit(prtd); if (ret) return ret;
/* ... prepare next one ... */
prtd->ch_dma_addr_current += prtd->ch_dma_size; if (prtd->ch_dma_addr_current >= prtd->ch_dma_addr_end)
prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;
/* ... and send it */
ret = qmc_audio_pcm_write_submit(prtd); if (ret) return ret;
} else { for (i = 0; i < prtd->channels; i++)
prtd->qmc_dai->chans[i].prtd_rx = prtd;
/* Submit first chunk ... */
ret = qmc_audio_pcm_read_submit(prtd); if (ret) return ret;
/* ... prepare next one ... */
prtd->ch_dma_addr_current += prtd->ch_dma_size; if (prtd->ch_dma_addr_current >= prtd->ch_dma_addr_end)
prtd->ch_dma_addr_current = prtd->ch_dma_addr_start;
/* ... and send it */
ret = qmc_audio_pcm_read_submit(prtd); if (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;
/* 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) return -ENOMEM;
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.
*/ staticint qmc_dai_hw_rule_channels_by_format(struct qmc_dai *qmc_dai, struct snd_pcm_hw_params *params, unsignedint 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;
}
/* * In interleaved mode, the driver uses one QMC channel for all audio * channels whereas in non-interleaved mode, it uses one QMC channel per * audio channel.
*/
nb_chans_used = qmc_audio_access_is_interleaved(params_access(params)) ?
1 : params_channels(params);
if (nb_chans_used > qmc_dai->nb_chans_avail) {
dev_err(dai->dev, "Not enough qmc_chans. Need %u, avail %u\n",
nb_chans_used, qmc_dai->nb_chans_avail); return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
chan_param.mode = QMC_TRANSPARENT;
chan_param.transp.max_rx_buf_size = params_period_bytes(params) / nb_chans_used; for (i = 0; i < nb_chans_used; i++) {
ret = qmc_chan_set_param(qmc_dai->chans[i].qmc_chan, &chan_param); if (ret) {
dev_err(dai->dev, "chans[%u], set param failed %d\n",
i, ret); return ret;
}
}
qmc_dai->nb_chans_used_rx = nb_chans_used;
} else {
qmc_dai->nb_chans_used_tx = nb_chans_used;
}
return 0;
}
staticint qmc_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
{ unsignedint nb_chans_used; struct qmc_dai *qmc_dai; unsignedint i; int direction; int ret = 0; int ret_tmp;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
direction = QMC_CHAN_WRITE;
nb_chans_used = qmc_dai->nb_chans_used_tx;
} else {
direction = QMC_CHAN_READ;
nb_chans_used = qmc_dai->nb_chans_used_rx;
}
switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: for (i = 0; i < nb_chans_used; i++) {
ret = qmc_chan_start(qmc_dai->chans[i].qmc_chan, direction); if (ret) goto err_stop;
} break;
case SNDRV_PCM_TRIGGER_STOP: /* Stop and reset all QMC channels and return the first error encountered */ for (i = 0; i < nb_chans_used; i++) {
ret_tmp = qmc_chan_stop(qmc_dai->chans[i].qmc_chan, direction); if (!ret)
ret = ret_tmp; if (ret_tmp) continue;
ret_tmp = qmc_chan_reset(qmc_dai->chans[i].qmc_chan, direction); if (!ret)
ret = ret_tmp;
} if (ret) return ret; break;
case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* Stop all QMC channels and return the first error encountered */ for (i = 0; i < nb_chans_used; i++) {
ret_tmp = qmc_chan_stop(qmc_dai->chans[i].qmc_chan, direction); if (!ret)
ret = ret_tmp;
} if (ret) return ret; break;
formats_mask = 0;
chan_width = nb_ts * 8;
pcm_for_each_format(format) { /* * Support format other than little-endian (ie big-endian or * without endianness such as 8bit formats)
*/ if (snd_pcm_format_little_endian(format) == 1) continue;
/* Support physical width multiple of 8bit */
format_width = snd_pcm_format_physical_width(format); 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;
/* * In non interleaved mode, we can only support formats that * can fit only 1 time in the channel
*/ if (is_noninterleaved && format_width != chan_width) continue;
count = qmc_chan_count_phandles(np, "fsl,qmc-chan"); if (count < 0) return dev_err_probe(qmc_audio->dev, count, "dai %d get number of QMC channel failed\n", qmc_dai->id); if (!count) return dev_err_probe(qmc_audio->dev, -EINVAL, "dai %d no QMC channel defined\n", qmc_dai->id);
qmc_dai->chans = devm_kcalloc(qmc_audio->dev, count, sizeof(*qmc_dai->chans), GFP_KERNEL); if (!qmc_dai->chans) return -ENOMEM;
for (i = 0; i < count; i++) {
qmc_dai->chans[i].qmc_chan = devm_qmc_chan_get_byphandles_index(qmc_audio->dev, np, "fsl,qmc-chan", i); if (IS_ERR(qmc_dai->chans[i].qmc_chan)) { return dev_err_probe(qmc_audio->dev, PTR_ERR(qmc_dai->chans[i].qmc_chan), "dai %d get QMC channel %d failed\n", qmc_dai->id, i);
}
ret = qmc_chan_get_info(qmc_dai->chans[i].qmc_chan, &info); if (ret) {
dev_err(qmc_audio->dev, "dai %d get QMC %d channel info failed %d\n",
qmc_dai->id, i, ret); return ret;
}
if (info.mode != QMC_TRANSPARENT) {
dev_err(qmc_audio->dev, "dai %d QMC chan %d mode %d is not QMC_TRANSPARENT\n",
qmc_dai->id, i, info.mode); return -EINVAL;
}
/* * All channels must have the same number of Tx slots and the * same numbers of Rx slots.
*/ if (i == 0) {
nb_tx_ts = info.nb_tx_ts;
nb_rx_ts = info.nb_rx_ts;
tx_fs_rate = info.tx_fs_rate;
rx_fs_rate = info.rx_fs_rate;
} else { if (nb_tx_ts != info.nb_tx_ts) {
dev_err(qmc_audio->dev, "dai %d QMC chan %d inconsistent number of Tx timeslots (%u instead of %u)\n",
qmc_dai->id, i, info.nb_tx_ts, nb_tx_ts); return -EINVAL;
} if (nb_rx_ts != info.nb_rx_ts) {
dev_err(qmc_audio->dev, "dai %d QMC chan %d inconsistent number of Rx timeslots (%u instead of %u)\n",
qmc_dai->id, i, info.nb_rx_ts, nb_rx_ts); return -EINVAL;
} if (tx_fs_rate != info.tx_fs_rate) {
dev_err(qmc_audio->dev, "dai %d QMC chan %d inconsistent Tx frame sample rate (%lu instead of %lu)\n",
qmc_dai->id, i, info.tx_fs_rate, tx_fs_rate); return -EINVAL;
} if (rx_fs_rate != info.rx_fs_rate) {
dev_err(qmc_audio->dev, "dai %d QMC chan %d inconsistent Rx frame sample rate (%lu instead of %lu)\n",
qmc_dai->id, i, info.rx_fs_rate, rx_fs_rate); return -EINVAL;
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.