| // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) | |
| // | |
| // Copyright 2026 Intel Corporation. All rights reserved. | |
| // | |
| #include <sound/soc.h> | |
| #include <sound/sof.h> | |
| #include <sound/compress_driver.h> | |
| #include <sound/pcm_params.h> | |
| #include "sof-audio.h" | |
| #include "sof-priv.h" | |
| #include "sof-utils.h" | |
| #include "ops.h" | |
| #include "ipc4-priv.h" | |
| #include "ipc4-topology.h" | |
| #include "ipc4-fw-reg.h" | |
| /* Maximum processing size of the decoder/encoder is 2048 bytes */ | |
| #define SOF_IPC4_COMPR_MAX_PROCESSING_SIZE (SZ_2K) | |
| #define SOF_IPC4_COMPR_MIN_FRAGMENTS 3 | |
| #define SOF_IPC4_COMPR_MAX_FRAGMENT_SIZE (SZ_128K) | |
| #define SOF_IPC4_COMPR_MAX_FRAGMENTS 64 | |
| #define SOF_IPC4_COMPR_MIN_BUFFER_SIZE(min_size) ((min_size) * \ | |
| SOF_IPC4_COMPR_MIN_FRAGMENTS) | |
| struct sof_ipc4_compr_init_data { | |
| struct snd_codec codec; | |
| u32 dir; | |
| } __packed __aligned(4); | |
| static struct sof_ipc4_process * | |
| sof_ipc4_compr_get_module(struct snd_sof_pcm *spcm, int dir) | |
| { | |
| int id = dir ? snd_soc_dapm_encoder : snd_soc_dapm_decoder; | |
| struct snd_sof_pcm_stream *sps = &spcm->stream[dir]; | |
| struct snd_soc_dapm_widget *widget; | |
| int i; | |
| /* Find the (first) compr module in path */ | |
| for_each_dapm_widgets(sps->list, i, widget) { | |
| struct snd_sof_widget *swidget = widget->dobj.private; | |
| if (!swidget) | |
| continue; | |
| if (swidget->widget->id == id) | |
| return swidget->private; | |
| } | |
| return NULL; | |
| } | |
| static u32 sof_ipc4_compr_calc_min_fragment_size(struct snd_sof_pcm_stream *sps) | |
| { | |
| u32 host_buffer_estimate; | |
| /* Estimated host DMA buffer size based on stereo S32_LE, 48KHz */ | |
| host_buffer_estimate = snd_pcm_format_size(SNDRV_PCM_FORMAT_S32_LE, 2 * 48); | |
| host_buffer_estimate *= sps->dsp_max_burst_size_in_ms; | |
| /* | |
| * The minimum fragment size must not be smaller than the processing size | |
| * or in case of deep buffer on host side, the host DMA buffer size. | |
| */ | |
| return max(SOF_IPC4_COMPR_MAX_PROCESSING_SIZE, host_buffer_estimate); | |
| } | |
| /* The buffer must be described by the page table allocated for the stream */ | |
| static u64 sof_ipc4_compr_calc_max_buffer_size(struct snd_sof_pcm_stream *sps) | |
| { | |
| return (u64)SOF_PAGE_TABLE_MAX_PFNS(sps->page_table.bytes) * PAGE_SIZE; | |
| } | |
| static int sof_ipc4_compr_open(struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream) | |
| { | |
| struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); | |
| struct snd_soc_pcm_runtime *rtd = cstream->private_data; | |
| struct snd_sof_pcm *spcm; | |
| int dir, ret; | |
| spcm = snd_sof_find_spcm_dai(component, rtd); | |
| if (!spcm) | |
| return -EINVAL; | |
| dir = cstream->direction; | |
| if (spcm->stream[dir].cstream) | |
| return -EBUSY; | |
| spcm_dbg(spcm, dir, "Entry: open\n"); | |
| ret = snd_sof_compr_platform_open(sdev, cstream); | |
| if (ret < 0) { | |
| spcm_err(spcm, dir, "platform compress open failed %d\n", ret); | |
| return ret; | |
| } | |
| spcm->stream[dir].cstream = cstream; | |
| spcm->stream[dir].posn.host_posn = 0; | |
| spcm->stream[dir].posn.dai_posn = 0; | |
| spcm->prepared[dir] = false; | |
| spcm->pending_stop[dir] = false; | |
| spcm->compr_started[dir] = false; | |
| return 0; | |
| } | |
| static int sof_ipc4_compr_stream_free(struct snd_sof_dev *sdev, | |
| struct snd_sof_pcm *spcm, | |
| struct snd_compr_stream *cstream) | |
| { | |
| const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); | |
| int dir = cstream->direction; | |
| int ret = 0; | |
| int err = 0; | |
| if (spcm->prepared[dir]) { | |
| if (spcm->pending_stop[dir]) | |
| pcm_ops->trigger(sdev->component, NULL, spcm, | |
| SNDRV_PCM_TRIGGER_STOP, dir); | |
| snd_sof_compr_platform_trigger(sdev, cstream, | |
| SNDRV_PCM_TRIGGER_STOP); | |
| err = pcm_ops->hw_free(sdev->component, NULL, spcm, dir); | |
| if (err < 0) | |
| spcm_err(spcm, dir, "pcm_ops->hw_free failed %d\n", err); | |
| } | |
| spcm->prepared[dir] = false; | |
| spcm->pending_stop[dir] = false; | |
| spcm->compr_started[dir] = false; | |
| spcm->stream[dir].cstream = NULL; | |
| /* reset the DMA */ | |
| ret = snd_sof_compr_platform_hw_free(sdev, cstream); | |
| if (ret < 0) { | |
| spcm_err(spcm, dir, "platform hw free failed %d\n", ret); | |
| if (!err) | |
| err = ret; | |
| } | |
| /* free widget list */ | |
| ret = sof_widget_list_free(sdev, spcm, dir); | |
| if (ret < 0 && err == 0) { | |
| spcm_err(spcm, dir, "sof_widget_list_free failed %d\n", ret); | |
| err = ret; | |
| } | |
| return err; | |
| } | |
| static int sof_ipc4_compr_free(struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream) | |
| { | |
| struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); | |
| struct snd_soc_pcm_runtime *rtd = cstream->private_data; | |
| struct snd_sof_pcm *spcm; | |
| int ret, err; | |
| spcm = snd_sof_find_spcm_dai(component, rtd); | |
| if (!spcm) | |
| return -EINVAL; | |
| spcm_dbg(spcm, cstream->direction, "Entry: free\n"); | |
| ret = sof_ipc4_compr_stream_free(sdev, spcm, cstream); | |
| /* unprepare and free the list of DAPM widgets */ | |
| sof_widget_list_unprepare(sdev, spcm, cstream->direction); | |
| cancel_work_sync(&spcm->stream[cstream->direction].period_elapsed_work); | |
| snd_compr_free_pages(cstream); | |
| err = snd_sof_compr_platform_close(sdev, cstream); | |
| if (err < 0) { | |
| spcm_err(spcm, cstream->direction, | |
| "platform compress close failed %d\n", err); | |
| if (!ret) | |
| ret = err; | |
| } | |
| return ret; | |
| } | |
| #define SOF_IPC4_CODEC_INFO_GET_ID(value) ((value) & 0xff) | |
| #define SOF_IPC4_CODEC_INFO_GET_DIR(value) (((value) >> 8) & 0xf) | |
| struct sof_ipc4_codec_info_data { | |
| u32 count; | |
| u32 items[]; | |
| } __packed __aligned(4); | |
| static int sof_ipc4_compr_get_caps(struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream, | |
| struct snd_compr_caps *caps) | |
| { | |
| struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); | |
| struct snd_soc_pcm_runtime *rtd = cstream->private_data; | |
| struct sof_ipc4_fw_data *ipc4_data = sdev->private; | |
| struct sof_ipc4_codec_info_data *codec_info = ipc4_data->codec_info; | |
| int dir = cstream->direction; | |
| u64 max_buffer_size; | |
| u64 max_fragments; | |
| struct snd_sof_pcm *spcm; | |
| int i; | |
| spcm = snd_sof_find_spcm_dai(component, rtd); | |
| if (!spcm) | |
| return -EINVAL; | |
| /* No compress support available in booted firmware */ | |
| if (!codec_info || !codec_info->count) { | |
| spcm_err(spcm, dir, | |
| "Compress is not supported (no codecs available)\n"); | |
| return -EINVAL; | |
| } | |
| for (i = 0; i < codec_info->count; i++) { | |
| int _dir = SOF_IPC4_CODEC_INFO_GET_DIR(codec_info->items[i]); | |
| if (_dir == dir) { | |
| int id = SOF_IPC4_CODEC_INFO_GET_ID(codec_info->items[i]); | |
| if (caps->num_codecs < ARRAY_SIZE(caps->codecs)) { | |
| spcm_dbg(spcm, dir, "codec#%d: %d\n", | |
| caps->num_codecs, id); | |
| caps->codecs[caps->num_codecs++] = id; | |
| } else { | |
| spcm_dbg(spcm, dir, "codec#%d: %d ignored\n", | |
| caps->num_codecs, id); | |
| } | |
| } | |
| } | |
| caps->direction = dir; | |
| caps->min_fragment_size = | |
| sof_ipc4_compr_calc_min_fragment_size(&spcm->stream[dir]); | |
| caps->max_fragment_size = SOF_IPC4_COMPR_MAX_FRAGMENT_SIZE; | |
| if (caps->max_fragment_size < caps->min_fragment_size) | |
| caps->max_fragment_size = caps->min_fragment_size; | |
| caps->min_fragments = SOF_IPC4_COMPR_MIN_FRAGMENTS; | |
| caps->max_fragments = SOF_IPC4_COMPR_MAX_FRAGMENTS; | |
| /* | |
| * A buffer of max_fragments number of max_fragment_size fragments must | |
| * be describable by the page table allocated for the stream | |
| */ | |
| max_buffer_size = sof_ipc4_compr_calc_max_buffer_size(&spcm->stream[dir]); | |
| max_fragments = div_u64(max_buffer_size, caps->max_fragment_size); | |
| if (caps->max_fragments > max_fragments) | |
| caps->max_fragments = max_fragments; | |
| spcm_dbg(spcm, dir, | |
| "num_codecs: %u, fragment_size: %u-%u, fragments: %u-%u\n", | |
| caps->num_codecs, | |
| caps->min_fragment_size, caps->max_fragment_size, | |
| caps->min_fragments, caps->max_fragments); | |
| return 0; | |
| } | |
| static int sof_ipc4_compr_alloc_pages(struct device *dev, | |
| struct snd_sof_pcm_stream *sps, | |
| struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream) | |
| { | |
| u32 min_fragment_size = sof_ipc4_compr_calc_min_fragment_size(sps); | |
| u64 max_buffer_size = sof_ipc4_compr_calc_max_buffer_size(sps); | |
| struct snd_compr_runtime *crtd = cstream->runtime; | |
| u64 fragments = crtd->buffer_size; | |
| int ret; | |
| if (crtd->buffer_size < SOF_IPC4_COMPR_MIN_BUFFER_SIZE(min_fragment_size)) { | |
| dev_err(dev, "%s: Buffer size is too small: %llu (minimum is %u)\n", | |
| __func__, crtd->buffer_size, | |
| SOF_IPC4_COMPR_MIN_BUFFER_SIZE(min_fragment_size)); | |
| return -EINVAL; | |
| } | |
| if (crtd->buffer_size > max_buffer_size) { | |
| dev_err(dev, "%s: Buffer size is too big: %llu (maximum is %llu)\n", | |
| __func__, crtd->buffer_size, max_buffer_size); | |
| return -EINVAL; | |
| } | |
| if (crtd->fragment_size < min_fragment_size) { | |
| dev_err(dev, "%s: Fragment size is too small: %u (minimum is %u)\n", | |
| __func__, crtd->fragment_size, min_fragment_size); | |
| return -EINVAL; | |
| } | |
| do_div(fragments, crtd->fragment_size); | |
| if (fragments < SOF_IPC4_COMPR_MIN_FRAGMENTS || | |
| fragments > SOF_IPC4_COMPR_MAX_FRAGMENTS) { | |
| dev_err(dev, | |
| "%s: Incorrect number of fragments: %llu (valid: %d-%d)\n", | |
| __func__, fragments, SOF_IPC4_COMPR_MIN_FRAGMENTS, | |
| SOF_IPC4_COMPR_MAX_FRAGMENTS); | |
| return -EINVAL; | |
| } | |
| cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; | |
| cstream->dma_buffer.dev.dev = dev; | |
| ret = snd_compr_malloc_pages(cstream, crtd->buffer_size); | |
| if (ret < 0) | |
| return ret; | |
| ret = snd_sof_compr_create_page_table(component, cstream, crtd->dma_area, | |
| crtd->dma_bytes); | |
| if (ret < 0) | |
| snd_compr_free_pages(cstream); | |
| return ret; | |
| } | |
| static bool | |
| sof_ipc4_compr_codec_supported(struct snd_sof_dev *sdev, int codec_id, int dir) | |
| { | |
| struct sof_ipc4_fw_data *ipc4_data = sdev->private; | |
| struct sof_ipc4_codec_info_data *codec_info = ipc4_data->codec_info; | |
| int i; | |
| /* No compress support available in booted firmware */ | |
| if (!codec_info || !codec_info->count) | |
| return false; | |
| for (i = 0; i < codec_info->count; i++) { | |
| int _dir = SOF_IPC4_CODEC_INFO_GET_DIR(codec_info->items[i]); | |
| int _id = SOF_IPC4_CODEC_INFO_GET_ID(codec_info->items[i]); | |
| if (_dir == dir && codec_id == _id) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static int sof_ipc4_compr_set_params(struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream, | |
| struct snd_compr_params *params) | |
| { | |
| struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); | |
| const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); | |
| struct sof_ipc4_compr_init_data *compr_data __free(kfree) = NULL; | |
| struct snd_soc_pcm_runtime *rtd = cstream->private_data; | |
| struct snd_sof_platform_stream_params *platform_params; | |
| struct sof_ipc4_timestamp_info *time_info; | |
| struct snd_compr_params *compr_params; | |
| struct snd_sof_widget *host_swidget; | |
| struct sof_ipc4_process *process; | |
| struct snd_pcm_hw_params p = {0}; | |
| struct snd_interval *interval; | |
| struct snd_sof_pcm *spcm; | |
| struct snd_mask *fmt; | |
| int dir = cstream->direction; | |
| int ret; | |
| spcm = snd_sof_find_spcm_dai(component, rtd); | |
| if (!spcm) | |
| return -EINVAL; | |
| host_swidget = snd_sof_find_swidget_by_comp_id(sdev, spcm->stream[dir].comp_id); | |
| if (!host_swidget) { | |
| spcm_err(spcm, dir, "failed to find host widget with comp_id %d\n", | |
| spcm->stream[dir].comp_id); | |
| return -ENODEV; | |
| } | |
| if (!sof_ipc4_compr_codec_supported(sdev, params->codec.id, dir)) { | |
| spcm_err(spcm, dir, "Unsupported codec id: %u\n", params->codec.id); | |
| return -EINVAL; | |
| } | |
| spcm_dbg(spcm, dir, | |
| "codec_id: %u, rate: %u, ch in/out: %u/%u, format: %u/%u\n", | |
| params->codec.id, params->codec.sample_rate, params->codec.ch_in, | |
| params->codec.ch_out, params->codec.format, params->codec.pcm_format); | |
| if (spcm->prepared[dir]) { | |
| /* | |
| * This can only happen if user space re-configures the device | |
| * without closing it, for example after DRAIN completion | |
| */ | |
| ret = sof_ipc4_compr_stream_free(sdev, spcm, cstream); | |
| if (ret) | |
| return ret; | |
| } | |
| /* save the compress params */ | |
| compr_params = &spcm->cparams[dir]; | |
| memcpy(compr_params, params, sizeof(*params)); | |
| /* | |
| * Force format, rate and channels and use PCM hw_params structure to | |
| * set up the pipelines. | |
| */ | |
| fmt = hw_param_mask(&p, SNDRV_PCM_HW_PARAM_FORMAT); | |
| snd_mask_none(fmt); | |
| /* Use correct format based on the used codec */ | |
| switch (params->codec.id) { | |
| case SND_AUDIOCODEC_PCM: | |
| /* The format is provided by user space, it must be validated */ | |
| if (snd_pcm_format_width((snd_pcm_format_t)params->codec.format) <= 0) { | |
| spcm_err(spcm, dir, "Invalid PCM format: %u\n", | |
| params->codec.format); | |
| return -EINVAL; | |
| } | |
| snd_mask_set_format(fmt, (snd_pcm_format_t)params->codec.format); | |
| break; | |
| case SND_AUDIOCODEC_VORBIS: | |
| snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); | |
| break; | |
| case SND_AUDIOCODEC_FLAC: | |
| { | |
| struct snd_dec_flac *dec_flac = ¶ms->codec.options.flac_d; | |
| if (dec_flac->sample_size == 16) | |
| snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); | |
| else | |
| snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); | |
| break; | |
| } | |
| default: | |
| snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); | |
| } | |
| interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_CHANNELS); | |
| interval->min = compr_params->codec.ch_out; | |
| interval->max = compr_params->codec.ch_out; | |
| interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_RATE); | |
| interval->min = compr_params->codec.sample_rate; | |
| interval->max = compr_params->codec.sample_rate; | |
| ret = sof_ipc4_compr_alloc_pages(sdev->dev, &spcm->stream[dir], | |
| component, cstream); | |
| if (ret < 0) | |
| return ret; | |
| spcm_dbg(spcm, dir, | |
| "buffer_size: %llu, fragment_size: %u (fragments: %u)\n", | |
| cstream->runtime->buffer_size, cstream->runtime->fragment_size, | |
| (u32)cstream->runtime->buffer_size / cstream->runtime->fragment_size); | |
| interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_PERIOD_BYTES); | |
| interval->min = cstream->runtime->fragment_size; | |
| interval->max = cstream->runtime->fragment_size; | |
| interval = hw_param_interval(&p, SNDRV_PCM_HW_PARAM_BUFFER_BYTES); | |
| interval->min = cstream->runtime->buffer_size; | |
| interval->max = cstream->runtime->buffer_size; | |
| platform_params = &spcm->platform_params[dir]; | |
| ret = snd_sof_compr_platform_hw_params(sdev, cstream, compr_params, | |
| platform_params); | |
| if (ret < 0) { | |
| spcm_err(spcm, dir, "platform compress hw params failed\n"); | |
| goto free_pages; | |
| } | |
| /* set up the list of DAPM widgets if not already done */ | |
| if (!spcm->stream[dir].list) { | |
| ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, &p, | |
| platform_params, dir); | |
| if (ret < 0) | |
| goto free_pages; | |
| } | |
| process = sof_ipc4_compr_get_module(spcm, dir); | |
| if (!process) { | |
| ret = -EINVAL; | |
| goto free_list; | |
| } | |
| compr_data = kzalloc(sizeof(*compr_data), GFP_KERNEL); | |
| if (!compr_data) { | |
| ret = -ENOMEM; | |
| goto free_list; | |
| } | |
| memcpy(&compr_data->codec, &compr_params->codec, sizeof(compr_data->codec)); | |
| compr_data->dir = dir; | |
| process->init_ext_module_data = compr_data; | |
| process->init_ext_module_size = sizeof(*compr_data); | |
| /* | |
| * Make sure that the DSP is booted up, which might not be the | |
| * case if the on-demand DSP boot is used | |
| */ | |
| ret = snd_sof_boot_dsp_firmware(sdev); | |
| if (ret) | |
| goto clear_init_ext; | |
| /* set the host DMA ID */ | |
| if (tplg_ops && tplg_ops->host_config) | |
| tplg_ops->host_config(sdev, host_swidget, platform_params); | |
| /* set up the widgets and pipelines in the DSP */ | |
| ret = sof_widget_list_setup(sdev, spcm, &p, platform_params, dir); | |
| if (ret < 0) { | |
| spcm_err(spcm, dir, "widget list set up failed\n"); | |
| goto clear_init_ext; | |
| } | |
| memcpy(&spcm->params[dir], &p, sizeof(p)); | |
| spcm->prepared[dir] = true; | |
| time_info = sof_ipc4_sps_to_time_info(&spcm->stream[dir]); | |
| if (time_info) { | |
| /* delay calculation supported */ | |
| time_info->stream_start_offset = SOF_IPC4_INVALID_STREAM_POSITION; | |
| time_info->llp_offset = 0; | |
| sof_ipc4_build_time_info(sdev, &spcm->stream[dir]); | |
| } | |
| process->init_ext_module_data = NULL; | |
| process->init_ext_module_size = 0; | |
| return 0; | |
| clear_init_ext: | |
| process->init_ext_module_data = NULL; | |
| process->init_ext_module_size = 0; | |
| free_list: | |
| sof_widget_list_unprepare(sdev, spcm, dir); | |
| free_pages: | |
| snd_compr_free_pages(cstream); | |
| return ret; | |
| } | |
| static int sof_ipc4_compr_get_params(struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream, | |
| struct snd_codec *params) | |
| { | |
| struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); | |
| struct snd_soc_pcm_runtime *rtd = cstream->private_data; | |
| struct snd_sof_pcm *spcm; | |
| /* TODO: we don't query the supported codecs for now, if the | |
| * application asks for an unsupported codec the set_params() will fail. | |
| */ | |
| spcm = snd_sof_find_spcm_dai(component, rtd); | |
| if (!spcm) { | |
| dev_err(sdev->dev, "%s: can't find spcm\n", __func__); | |
| return -EINVAL; | |
| } | |
| spcm_dbg(spcm, cstream->direction, "Entry: get_params\n"); | |
| memcpy(params, &spcm->cparams[cstream->direction].codec, | |
| sizeof(*params)); | |
| return 0; | |
| } | |
| static int sof_ipc4_compr_trigger(struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream, int cmd) | |
| { | |
| struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); | |
| struct snd_soc_pcm_runtime *rtd = cstream->private_data; | |
| const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); | |
| struct snd_sof_pcm *spcm; | |
| int dir = cstream->direction; | |
| bool trigger_platform = false; | |
| int ret = 0; | |
| spcm = snd_sof_find_spcm_dai(component, rtd); | |
| if (!spcm) { | |
| dev_err(sdev->dev, "%s: can't find spcm\n", __func__); | |
| return -EINVAL; | |
| } | |
| switch (cmd) { | |
| case SNDRV_PCM_TRIGGER_START: | |
| /* | |
| * The host DMA is left running on stop and drain to match the | |
| * firmware, which only pauses the pipeline, and it is only | |
| * stopped during hw_free. A stream restarted after stop or | |
| * drain - which the compress core allows for capture - would | |
| * continue from a position the two sides no longer agree on. | |
| */ | |
| if (spcm->compr_started[dir]) { | |
| spcm_err(spcm, dir, "Stream restart is not supported\n"); | |
| return -EINVAL; | |
| } | |
| fallthrough; | |
| case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
| spcm->pending_stop[dir] = false; | |
| trigger_platform = true; | |
| break; | |
| case SNDRV_PCM_TRIGGER_STOP: | |
| case SNDRV_PCM_TRIGGER_SUSPEND: | |
| case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
| spcm->pending_stop[dir] = false; | |
| break; | |
| case SND_COMPR_TRIGGER_DRAIN: | |
| spcm->pending_stop[dir] = true; | |
| break; | |
| case SND_COMPR_TRIGGER_PARTIAL_DRAIN: | |
| case SND_COMPR_TRIGGER_NEXT_TRACK: | |
| /* Gapless playback is not supported */ | |
| spcm_dbg(spcm, dir, "Unsupported trigger cmd: %d\n", cmd); | |
| return -EOPNOTSUPP; | |
| default: | |
| spcm_dbg(spcm, dir, "Unhandled trigger cmd: %d\n", cmd); | |
| return 0; | |
| } | |
| spcm_dbg(spcm, dir, "Entry: trigger (cmd: %d)\n", cmd); | |
| ret = pcm_ops->trigger(component, NULL, spcm, cmd, dir); | |
| if (ret < 0) { | |
| spcm_err(spcm, dir, "pcm_ops->trigger failed for cmd %d\n", cmd); | |
| return ret; | |
| } | |
| if (!ret && trigger_platform) { | |
| ret = snd_sof_compr_platform_trigger(sdev, cstream, cmd); | |
| if (ret < 0) | |
| spcm_err(spcm, dir, | |
| "platform compress trigger start failed %d\n", | |
| ret); | |
| } | |
| if (!ret && cmd == SNDRV_PCM_TRIGGER_START) | |
| spcm->compr_started[dir] = true; | |
| return ret; | |
| } | |
| static int sof_ipc4_compr_copy_playback(struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream, | |
| char __user *buf, size_t count) | |
| { | |
| struct snd_compr_runtime *crtd = cstream->runtime; | |
| u64 offset, n; | |
| void *ptr; | |
| int ret; | |
| div64_u64_rem(crtd->total_bytes_available, crtd->buffer_size, &offset); | |
| ptr = crtd->dma_area + offset; | |
| n = crtd->buffer_size - offset; | |
| if (count < n) { | |
| ret = copy_from_user(ptr, buf, count); | |
| } else { | |
| ret = copy_from_user(ptr, buf, n); | |
| ret += copy_from_user(crtd->dma_area, buf + n, count - n); | |
| } | |
| return count - ret; | |
| } | |
| static int sof_ipc4_compr_copy_capture(struct snd_compr_runtime *crtd, | |
| char __user *buf, size_t count) | |
| { | |
| u64 offset, n; | |
| void *ptr; | |
| int ret; | |
| div64_u64_rem(crtd->total_bytes_transferred, crtd->buffer_size, &offset); | |
| ptr = crtd->dma_area + offset; | |
| n = crtd->buffer_size - offset; | |
| if (count < n) { | |
| ret = copy_to_user(buf, ptr, count); | |
| } else { | |
| ret = copy_to_user(buf, ptr, n); | |
| ret += copy_to_user(buf + n, crtd->dma_area, count - n); | |
| } | |
| return count - ret; | |
| } | |
| static int sof_ipc4_compr_copy(struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream, | |
| char __user *buf, size_t count) | |
| { | |
| struct snd_compr_runtime *crtd = cstream->runtime; | |
| if (count > crtd->buffer_size) | |
| count = crtd->buffer_size; | |
| if (cstream->direction == SND_COMPRESS_PLAYBACK) | |
| return sof_ipc4_compr_copy_playback(component, cstream, buf, count); | |
| return sof_ipc4_compr_copy_capture(crtd, buf, count); | |
| } | |
| static int sof_ipc4_compr_pointer(struct snd_soc_component *component, | |
| struct snd_compr_stream *cstream, | |
| struct snd_compr_tstamp64 *tstamp) | |
| { | |
| struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); | |
| struct snd_soc_pcm_runtime *rtd = cstream->private_data; | |
| struct sof_ipc4_timestamp_info *time_info; | |
| struct snd_pcm_hw_params *params; | |
| struct snd_sof_pcm_stream *sps; | |
| struct snd_sof_pcm *spcm; | |
| u64 dai_cnt = 0; | |
| int ret; | |
| spcm = snd_sof_find_spcm_dai(component, rtd); | |
| if (!spcm) | |
| return -EINVAL; | |
| params = &spcm->params[cstream->direction]; | |
| ret = snd_sof_compr_platform_pointer(sdev, cstream, tstamp); | |
| if (ret < 0) { | |
| spcm_err(spcm, cstream->direction, | |
| "platform compress pointer failed %d\n", ret); | |
| return ret; | |
| } | |
| sps = &spcm->stream[cstream->direction]; | |
| time_info = sof_ipc4_sps_to_time_info(sps); | |
| if (!time_info) | |
| goto host_only; | |
| /* | |
| * stream_start_offset is updated to memory window by FW based on | |
| * pipeline statistics and it may be invalid if host query happens before | |
| * the statistics is complete. And it will not change after the first | |
| * initialization. | |
| */ | |
| if (time_info->stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION) { | |
| ret = sof_ipc4_get_stream_start_offset(sdev, NULL, sps, time_info); | |
| if (ret < 0) | |
| goto host_only; | |
| } | |
| if (!time_info->llp_offset) { | |
| dai_cnt = snd_sof_compr_get_dai_frame_counter(sdev, cstream); | |
| } else { | |
| struct sof_ipc4_llp_reading_slot llp; | |
| sof_mailbox_read(sdev, time_info->llp_offset, &llp, sizeof(llp)); | |
| dai_cnt = ((u64)llp.reading.llp_u << 32) | llp.reading.llp_l; | |
| } | |
| if (dai_cnt) { | |
| dai_cnt = sof_ipc4_frames_dai_to_host(time_info, dai_cnt); | |
| dai_cnt += time_info->stream_end_offset; | |
| if (dai_cnt < time_info->stream_start_offset) | |
| dai_cnt = 0; | |
| else | |
| dai_cnt -= time_info->stream_start_offset; | |
| } | |
| host_only: | |
| tstamp->sampling_rate = params_rate(params); | |
| tstamp->pcm_io_frames = dai_cnt; | |
| return 0; | |
| } | |
| void sof_ipc4_compr_drain_done(struct snd_sof_dev *sdev, void *ipc_message) | |
| { | |
| struct sof_ipc4_msg *ipc4_msg = ipc_message; | |
| struct sof_ipc4_notify_module_data *ndata = ipc4_msg->data_ptr; | |
| struct snd_sof_widget *swidget, *host_swidget; | |
| bool widget_found = false; | |
| struct snd_sof_pcm *spcm; | |
| int dir; | |
| /* Find the swidget based on ndata->module_id and ndata->instance_id */ | |
| swidget = sof_ipc4_find_swidget_by_ids(sdev, ndata->module_id, | |
| ndata->instance_id); | |
| if (!swidget) { | |
| dev_err(sdev->dev, "%s: Failed to find widget for module %u.%u\n", | |
| __func__, ndata->module_id, ndata->instance_id); | |
| return; | |
| } | |
| if (!swidget->spipe || !swidget->spipe->pipe_widget) | |
| return; | |
| /* Find the swidget of the host copier on the same pipeline */ | |
| list_for_each_entry(host_swidget, &sdev->widget_list, list) { | |
| if (WIDGET_IS_AIF(host_swidget->id) && | |
| host_swidget->pipeline_id == swidget->pipeline_id) { | |
| widget_found = true; | |
| break; | |
| } | |
| } | |
| if (!widget_found) { | |
| dev_err(sdev->dev, "%s: Host widget not found for pipeline: %s\n", | |
| __func__, swidget->spipe->pipe_widget->widget->name); | |
| return; | |
| } | |
| /* Look up the spcm of the host copier */ | |
| spcm = snd_sof_find_spcm_comp(sdev->component, host_swidget->comp_id, &dir); | |
| if (!spcm) { | |
| dev_err(sdev->dev, "%s: Stream cannot be found for %s\n", __func__, | |
| host_swidget->widget->name); | |
| return; | |
| } | |
| spcm_dbg(spcm, dir, "Entry: EOS done\n"); | |
| if (spcm->stream[dir].cstream) | |
| snd_compr_drain_notify(spcm->stream[dir].cstream); | |
| } | |
| const struct snd_compress_ops sof_ipc4_compressed_ops = { | |
| .open = sof_ipc4_compr_open, | |
| .free = sof_ipc4_compr_free, | |
| .get_caps = sof_ipc4_compr_get_caps, | |
| .set_params = sof_ipc4_compr_set_params, | |
| .get_params = sof_ipc4_compr_get_params, | |
| .trigger = sof_ipc4_compr_trigger, | |
| .pointer = sof_ipc4_compr_pointer, | |
| .copy = sof_ipc4_compr_copy, | |
| }; |