1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
2 | // |
3 | // Copyright 2021 NXP |
4 | // |
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 | |
15 | static 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(dividend: sstream->copied_total, divisor: buffer_size, remainder: &prev_pos); |
22 | |
23 | if (host_pos < prev_pos) |
24 | copied = (buffer_size - prev_pos) + host_pos; |
25 | else |
26 | copied = host_pos - prev_pos; |
27 | |
28 | sstream->copied_total += copied; |
29 | } |
30 | |
31 | static 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(stream: sps->cstream); |
38 | } |
39 | |
40 | void 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 context |
47 | */ |
48 | void 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(scomp: 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, host_pos: spcm->stream[cstream->direction].posn.host_posn, |
72 | buffer_size: crtd->buffer_size); |
73 | |
74 | /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */ |
75 | schedule_work(work: &spcm->stream[cstream->direction].period_elapsed_work); |
76 | } |
77 | |
78 | static 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(scomp: component, rtd); |
88 | if (!spcm) |
89 | return -EINVAL; |
90 | |
91 | return snd_sof_create_page_table(dev: component->dev, dmab, |
92 | page_table: spcm->stream[dir].page_table.area, size); |
93 | } |
94 | |
95 | static 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(size: sizeof(*sstream), GFP_KERNEL); |
105 | if (!sstream) |
106 | return -ENOMEM; |
107 | |
108 | spcm = snd_sof_find_spcm_dai(scomp: component, rtd); |
109 | if (!spcm) { |
110 | kfree(objp: sstream); |
111 | return -EINVAL; |
112 | } |
113 | |
114 | dir = cstream->direction; |
115 | |
116 | if (spcm->stream[dir].cstream) { |
117 | kfree(objp: 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 | |
131 | static 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(c: 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(scomp: 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(ipc: sdev->ipc, msg_data: &stream, msg_bytes: sizeof(stream)); |
151 | if (!ret) |
152 | spcm->prepared[cstream->direction] = false; |
153 | } |
154 | |
155 | cancel_work_sync(work: &spcm->stream[cstream->direction].period_elapsed_work); |
156 | spcm->stream[cstream->direction].cstream = NULL; |
157 | kfree(objp: sstream); |
158 | |
159 | return ret; |
160 | } |
161 | |
162 | static 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(c: 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(scomp: 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(size: 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(stream: cstream, size: crtd->buffer_size); |
205 | if (ret < 0) |
206 | goto out; |
207 | |
208 | ret = create_page_table(component, cstream, dma_area: crtd->dma_area, size: 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(ipc: sdev->ipc, msg_data: pcm, msg_bytes: sizeof(*pcm) + ext_data_size, |
233 | reply_data: &ipc_params_reply, reply_bytes: 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, sps: &spcm->stream[cstream->direction], |
240 | posn_offset: 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 | |
253 | out: |
254 | kfree(objp: pcm); |
255 | |
256 | return ret; |
257 | } |
258 | |
259 | static 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 the |
263 | * application asks for an unsupported codec the set_params() will fail. |
264 | */ |
265 | return 0; |
266 | } |
267 | |
268 | static 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(c: 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(scomp: 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(ipc: sdev->ipc, msg_data: &stream, msg_bytes: sizeof(stream)); |
303 | } |
304 | |
305 | static 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(dividend: rtd->total_bytes_available, divisor: rtd->buffer_size, remainder: &offset); |
313 | ptr = rtd->dma_area + offset; |
314 | n = rtd->buffer_size - offset; |
315 | |
316 | if (count < n) { |
317 | ret = copy_from_user(to: ptr, from: buf, n: count); |
318 | } else { |
319 | ret = copy_from_user(to: ptr, from: buf, n); |
320 | ret += copy_from_user(to: rtd->dma_area, from: buf + n, n: count - n); |
321 | } |
322 | |
323 | return count - ret; |
324 | } |
325 | |
326 | static 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(dividend: rtd->total_bytes_transferred, divisor: rtd->buffer_size, remainder: &offset); |
334 | ptr = rtd->dma_area + offset; |
335 | n = rtd->buffer_size - offset; |
336 | |
337 | if (count < n) { |
338 | ret = copy_to_user(to: buf, from: ptr, n: count); |
339 | } else { |
340 | ret = copy_to_user(to: buf, from: ptr, n); |
341 | ret += copy_to_user(to: buf + n, from: rtd->dma_area, n: count - n); |
342 | } |
343 | |
344 | return count - ret; |
345 | } |
346 | |
347 | static 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 | else |
359 | return sof_compr_copy_capture(rtd, buf, count); |
360 | } |
361 | |
362 | static 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(scomp: 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(dividend: spcm->stream[cstream->direction].posn.dai_posn, |
377 | divisor: sstream->channels * sstream->sample_container_bytes); |
378 | |
379 | return 0; |
380 | } |
381 | |
382 | struct 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 | }; |
391 | EXPORT_SYMBOL(sof_compressed_ops); |
392 | |