392 lines · c
1// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)2//3// Copyright 2021 NXP4//5// Author: Daniel Baluta <daniel.baluta@nxp.com>6 7#include <sound/soc.h>8#include <sound/sof.h>9#include <sound/compress_driver.h>10#include "sof-audio.h"11#include "sof-priv.h"12#include "sof-utils.h"13#include "ops.h"14 15static void sof_set_transferred_bytes(struct sof_compr_stream *sstream,16 u64 host_pos, u64 buffer_size)17{18 u64 prev_pos;19 unsigned int copied;20 21 div64_u64_rem(sstream->copied_total, buffer_size, &prev_pos);22 23 if (host_pos < prev_pos)24 copied = (buffer_size - prev_pos) + host_pos;25 else26 copied = host_pos - prev_pos;27 28 sstream->copied_total += copied;29}30 31static void snd_sof_compr_fragment_elapsed_work(struct work_struct *work)32{33 struct snd_sof_pcm_stream *sps =34 container_of(work, struct snd_sof_pcm_stream,35 period_elapsed_work);36 37 snd_compr_fragment_elapsed(sps->cstream);38}39 40void snd_sof_compr_init_elapsed_work(struct work_struct *work)41{42 INIT_WORK(work, snd_sof_compr_fragment_elapsed_work);43}44 45/*46 * sof compr fragment elapse, this could be called in irq thread context47 */48void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream)49{50 struct snd_soc_pcm_runtime *rtd;51 struct snd_compr_runtime *crtd;52 struct snd_soc_component *component;53 struct sof_compr_stream *sstream;54 struct snd_sof_pcm *spcm;55 56 if (!cstream)57 return;58 59 rtd = cstream->private_data;60 crtd = cstream->runtime;61 sstream = crtd->private_data;62 component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);63 64 spcm = snd_sof_find_spcm_dai(component, rtd);65 if (!spcm) {66 dev_err(component->dev,67 "fragment elapsed called for unknown stream!\n");68 return;69 }70 71 sof_set_transferred_bytes(sstream, spcm->stream[cstream->direction].posn.host_posn,72 crtd->buffer_size);73 74 /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */75 schedule_work(&spcm->stream[cstream->direction].period_elapsed_work);76}77 78static int create_page_table(struct snd_soc_component *component,79 struct snd_compr_stream *cstream,80 unsigned char *dma_area, size_t size)81{82 struct snd_dma_buffer *dmab = cstream->runtime->dma_buffer_p;83 struct snd_soc_pcm_runtime *rtd = cstream->private_data;84 int dir = cstream->direction;85 struct snd_sof_pcm *spcm;86 87 spcm = snd_sof_find_spcm_dai(component, rtd);88 if (!spcm)89 return -EINVAL;90 91 return snd_sof_create_page_table(component->dev, dmab,92 spcm->stream[dir].page_table.area, size);93}94 95static int sof_compr_open(struct snd_soc_component *component,96 struct snd_compr_stream *cstream)97{98 struct snd_soc_pcm_runtime *rtd = cstream->private_data;99 struct snd_compr_runtime *crtd = cstream->runtime;100 struct sof_compr_stream *sstream;101 struct snd_sof_pcm *spcm;102 int dir;103 104 sstream = kzalloc(sizeof(*sstream), GFP_KERNEL);105 if (!sstream)106 return -ENOMEM;107 108 spcm = snd_sof_find_spcm_dai(component, rtd);109 if (!spcm) {110 kfree(sstream);111 return -EINVAL;112 }113 114 dir = cstream->direction;115 116 if (spcm->stream[dir].cstream) {117 kfree(sstream);118 return -EBUSY;119 }120 121 spcm->stream[dir].cstream = cstream;122 spcm->stream[dir].posn.host_posn = 0;123 spcm->stream[dir].posn.dai_posn = 0;124 spcm->prepared[dir] = false;125 126 crtd->private_data = sstream;127 128 return 0;129}130 131static int sof_compr_free(struct snd_soc_component *component,132 struct snd_compr_stream *cstream)133{134 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);135 struct sof_compr_stream *sstream = cstream->runtime->private_data;136 struct snd_soc_pcm_runtime *rtd = cstream->private_data;137 struct sof_ipc_stream stream;138 struct snd_sof_pcm *spcm;139 int ret = 0;140 141 spcm = snd_sof_find_spcm_dai(component, rtd);142 if (!spcm)143 return -EINVAL;144 145 stream.hdr.size = sizeof(stream);146 stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;147 stream.comp_id = spcm->stream[cstream->direction].comp_id;148 149 if (spcm->prepared[cstream->direction]) {150 ret = sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));151 if (!ret)152 spcm->prepared[cstream->direction] = false;153 }154 155 cancel_work_sync(&spcm->stream[cstream->direction].period_elapsed_work);156 spcm->stream[cstream->direction].cstream = NULL;157 kfree(sstream);158 159 return ret;160}161 162static int sof_compr_set_params(struct snd_soc_component *component,163 struct snd_compr_stream *cstream, struct snd_compr_params *params)164{165 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);166 struct snd_soc_pcm_runtime *rtd = cstream->private_data;167 struct snd_compr_runtime *crtd = cstream->runtime;168 struct sof_ipc_pcm_params_reply ipc_params_reply;169 struct sof_ipc_fw_ready *ready = &sdev->fw_ready;170 struct sof_ipc_fw_version *v = &ready->version;171 struct sof_compr_stream *sstream;172 struct sof_ipc_pcm_params *pcm;173 struct snd_sof_pcm *spcm;174 size_t ext_data_size;175 int ret;176 177 if (v->abi_version < SOF_ABI_VER(3, 22, 0)) {178 dev_err(component->dev,179 "Compress params not supported with FW ABI version %d:%d:%d\n",180 SOF_ABI_VERSION_MAJOR(v->abi_version),181 SOF_ABI_VERSION_MINOR(v->abi_version),182 SOF_ABI_VERSION_PATCH(v->abi_version));183 return -EINVAL;184 }185 186 sstream = crtd->private_data;187 188 spcm = snd_sof_find_spcm_dai(component, rtd);189 190 if (!spcm)191 return -EINVAL;192 193 ext_data_size = sizeof(params->codec);194 195 if (sizeof(*pcm) + ext_data_size > sdev->ipc->max_payload_size)196 return -EINVAL;197 198 pcm = kzalloc(sizeof(*pcm) + ext_data_size, GFP_KERNEL);199 if (!pcm)200 return -ENOMEM;201 202 cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG;203 cstream->dma_buffer.dev.dev = sdev->dev;204 ret = snd_compr_malloc_pages(cstream, crtd->buffer_size);205 if (ret < 0)206 goto out;207 208 ret = create_page_table(component, cstream, crtd->dma_area, crtd->dma_bytes);209 if (ret < 0)210 goto out;211 212 pcm->params.buffer.pages = PFN_UP(crtd->dma_bytes);213 pcm->hdr.size = sizeof(*pcm) + ext_data_size;214 pcm->hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;215 216 pcm->comp_id = spcm->stream[cstream->direction].comp_id;217 pcm->params.hdr.size = sizeof(pcm->params) + ext_data_size;218 pcm->params.buffer.phy_addr = spcm->stream[cstream->direction].page_table.addr;219 pcm->params.buffer.size = crtd->dma_bytes;220 pcm->params.direction = cstream->direction;221 pcm->params.channels = params->codec.ch_out;222 pcm->params.rate = params->codec.sample_rate;223 pcm->params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;224 pcm->params.frame_fmt = SOF_IPC_FRAME_S32_LE;225 pcm->params.sample_container_bytes =226 snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32) >> 3;227 pcm->params.host_period_bytes = params->buffer.fragment_size;228 pcm->params.ext_data_length = ext_data_size;229 230 memcpy((u8 *)pcm->params.ext_data, ¶ms->codec, ext_data_size);231 232 ret = sof_ipc_tx_message(sdev->ipc, pcm, sizeof(*pcm) + ext_data_size,233 &ipc_params_reply, sizeof(ipc_params_reply));234 if (ret < 0) {235 dev_err(component->dev, "error ipc failed\n");236 goto out;237 }238 239 ret = snd_sof_set_stream_data_offset(sdev, &spcm->stream[cstream->direction],240 ipc_params_reply.posn_offset);241 if (ret < 0) {242 dev_err(component->dev, "Invalid stream data offset for Compr %d\n",243 spcm->pcm.pcm_id);244 goto out;245 }246 247 sstream->sampling_rate = params->codec.sample_rate;248 sstream->channels = params->codec.ch_out;249 sstream->sample_container_bytes = pcm->params.sample_container_bytes;250 251 spcm->prepared[cstream->direction] = true;252 253out:254 kfree(pcm);255 256 return ret;257}258 259static int sof_compr_get_params(struct snd_soc_component *component,260 struct snd_compr_stream *cstream, struct snd_codec *params)261{262 /* TODO: we don't query the supported codecs for now, if the263 * application asks for an unsupported codec the set_params() will fail.264 */265 return 0;266}267 268static int sof_compr_trigger(struct snd_soc_component *component,269 struct snd_compr_stream *cstream, int cmd)270{271 struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);272 struct snd_soc_pcm_runtime *rtd = cstream->private_data;273 struct sof_ipc_stream stream;274 struct snd_sof_pcm *spcm;275 276 spcm = snd_sof_find_spcm_dai(component, rtd);277 if (!spcm)278 return -EINVAL;279 280 stream.hdr.size = sizeof(stream);281 stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG;282 stream.comp_id = spcm->stream[cstream->direction].comp_id;283 284 switch (cmd) {285 case SNDRV_PCM_TRIGGER_START:286 stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;287 break;288 case SNDRV_PCM_TRIGGER_STOP:289 stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;290 break;291 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:292 stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;293 break;294 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:295 stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;296 break;297 default:298 dev_err(component->dev, "error: unhandled trigger cmd %d\n", cmd);299 break;300 }301 302 return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));303}304 305static int sof_compr_copy_playback(struct snd_compr_runtime *rtd,306 char __user *buf, size_t count)307{308 void *ptr;309 unsigned int offset, n;310 int ret;311 312 div_u64_rem(rtd->total_bytes_available, rtd->buffer_size, &offset);313 ptr = rtd->dma_area + offset;314 n = rtd->buffer_size - offset;315 316 if (count < n) {317 ret = copy_from_user(ptr, buf, count);318 } else {319 ret = copy_from_user(ptr, buf, n);320 ret += copy_from_user(rtd->dma_area, buf + n, count - n);321 }322 323 return count - ret;324}325 326static int sof_compr_copy_capture(struct snd_compr_runtime *rtd,327 char __user *buf, size_t count)328{329 void *ptr;330 unsigned int offset, n;331 int ret;332 333 div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset);334 ptr = rtd->dma_area + offset;335 n = rtd->buffer_size - offset;336 337 if (count < n) {338 ret = copy_to_user(buf, ptr, count);339 } else {340 ret = copy_to_user(buf, ptr, n);341 ret += copy_to_user(buf + n, rtd->dma_area, count - n);342 }343 344 return count - ret;345}346 347static int sof_compr_copy(struct snd_soc_component *component,348 struct snd_compr_stream *cstream,349 char __user *buf, size_t count)350{351 struct snd_compr_runtime *rtd = cstream->runtime;352 353 if (count > rtd->buffer_size)354 count = rtd->buffer_size;355 356 if (cstream->direction == SND_COMPRESS_PLAYBACK)357 return sof_compr_copy_playback(rtd, buf, count);358 else359 return sof_compr_copy_capture(rtd, buf, count);360}361 362static int sof_compr_pointer(struct snd_soc_component *component,363 struct snd_compr_stream *cstream,364 struct snd_compr_tstamp *tstamp)365{366 struct snd_sof_pcm *spcm;367 struct snd_soc_pcm_runtime *rtd = cstream->private_data;368 struct sof_compr_stream *sstream = cstream->runtime->private_data;369 370 spcm = snd_sof_find_spcm_dai(component, rtd);371 if (!spcm)372 return -EINVAL;373 374 tstamp->sampling_rate = sstream->sampling_rate;375 tstamp->copied_total = sstream->copied_total;376 tstamp->pcm_io_frames = div_u64(spcm->stream[cstream->direction].posn.dai_posn,377 sstream->channels * sstream->sample_container_bytes);378 379 return 0;380}381 382struct snd_compress_ops sof_compressed_ops = {383 .open = sof_compr_open,384 .free = sof_compr_free,385 .set_params = sof_compr_set_params,386 .get_params = sof_compr_get_params,387 .trigger = sof_compr_trigger,388 .pointer = sof_compr_pointer,389 .copy = sof_compr_copy,390};391EXPORT_SYMBOL(sof_compressed_ops);392