File Index
Files and directories
Sign in
FILE

pcm_dmaengine.c

File preview with syntax highlighting.

linux-6.19.14/sound/core/pcm_dmaengine.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 *  Copyright (C) 2012, Analog Devices Inc.
  4 *	Author: Lars-Peter Clausen <lars@metafoo.de>
  5 *
  6 *  Based on:
  7 *	imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
  8 *	mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc.
  9 *	ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
 10 *		      Copyright (C) 2006 Applied Data Systems
 11 */
 12#include <linux/module.h>
 13#include <linux/init.h>
 14#include <linux/dmaengine.h>
 15#include <linux/slab.h>
 16#include <sound/pcm.h>
 17#include <sound/pcm_params.h>
 18#include <sound/soc.h>
 19
 20#include <sound/dmaengine_pcm.h>
 21
 22struct dmaengine_pcm_runtime_data {
 23	struct dma_chan *dma_chan;
 24	dma_cookie_t cookie;
 25
 26	unsigned int pos;
 27};
 28
 29static inline struct dmaengine_pcm_runtime_data *substream_to_prtd(
 30	const struct snd_pcm_substream *substream)
 31{
 32	return substream->runtime->private_data;
 33}
 34
 35struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream)
 36{
 37	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
 38
 39	return prtd->dma_chan;
 40}
 41EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_chan);
 42
 43/**
 44 * snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config
 45 * @substream: PCM substream
 46 * @params: hw_params
 47 * @slave_config: DMA slave config
 48 *
 49 * This function can be used to initialize a dma_slave_config from a substream
 50 * and hw_params in a dmaengine based PCM driver implementation.
 51 *
 52 * Return: zero if successful, or a negative error code
 53 */
 54int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream,
 55	const struct snd_pcm_hw_params *params,
 56	struct dma_slave_config *slave_config)
 57{
 58	enum dma_slave_buswidth buswidth;
 59	int bits;
 60
 61	bits = params_physical_width(params);
 62	if (bits < 8 || bits > 64)
 63		return -EINVAL;
 64	else if (bits == 8)
 65		buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
 66	else if (bits == 16)
 67		buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
 68	else if (bits == 24)
 69		buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES;
 70	else if (bits <= 32)
 71		buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
 72	else
 73		buswidth = DMA_SLAVE_BUSWIDTH_8_BYTES;
 74
 75	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 76		slave_config->direction = DMA_MEM_TO_DEV;
 77		slave_config->dst_addr_width = buswidth;
 78	} else {
 79		slave_config->direction = DMA_DEV_TO_MEM;
 80		slave_config->src_addr_width = buswidth;
 81	}
 82
 83	slave_config->device_fc = false;
 84
 85	return 0;
 86}
 87EXPORT_SYMBOL_GPL(snd_hwparams_to_dma_slave_config);
 88
 89/**
 90 * snd_dmaengine_pcm_set_config_from_dai_data() - Initializes a dma slave config
 91 *  using DAI DMA data.
 92 * @substream: PCM substream
 93 * @dma_data: DAI DMA data
 94 * @slave_config: DMA slave configuration
 95 *
 96 * Initializes the {dst,src}_addr, {dst,src}_maxburst, {dst,src}_addr_width
 97 * fields of the DMA slave config from the same fields of the DAI DMA
 98 * data struct. The src and dst fields will be initialized depending on the
 99 * direction of the substream. If the substream is a playback stream the dst
100 * fields will be initialized, if it is a capture stream the src fields will be
101 * initialized. The {dst,src}_addr_width field will only be initialized if the
102 * SND_DMAENGINE_PCM_DAI_FLAG_PACK flag is set or if the addr_width field of
103 * the DAI DMA data struct is not equal to DMA_SLAVE_BUSWIDTH_UNDEFINED. If
104 * both conditions are met the latter takes priority.
105 */
106void snd_dmaengine_pcm_set_config_from_dai_data(
107	const struct snd_pcm_substream *substream,
108	const struct snd_dmaengine_dai_dma_data *dma_data,
109	struct dma_slave_config *slave_config)
110{
111	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
112		slave_config->dst_addr = dma_data->addr;
113		slave_config->dst_maxburst = dma_data->maxburst;
114		slave_config->dst_port_window_size = dma_data->port_window_size;
115		if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)
116			slave_config->dst_addr_width =
117				DMA_SLAVE_BUSWIDTH_UNDEFINED;
118		if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
119			slave_config->dst_addr_width = dma_data->addr_width;
120	} else {
121		slave_config->src_addr = dma_data->addr;
122		slave_config->src_maxburst = dma_data->maxburst;
123		slave_config->src_port_window_size = dma_data->port_window_size;
124		if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)
125			slave_config->src_addr_width =
126				DMA_SLAVE_BUSWIDTH_UNDEFINED;
127		if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
128			slave_config->src_addr_width = dma_data->addr_width;
129	}
130
131	slave_config->peripheral_config = dma_data->peripheral_config;
132	slave_config->peripheral_size = dma_data->peripheral_size;
133}
134EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_set_config_from_dai_data);
135
136static void dmaengine_pcm_dma_complete(void *arg)
137{
138	unsigned int new_pos;
139	struct snd_pcm_substream *substream = arg;
140	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
141
142	new_pos = prtd->pos + snd_pcm_lib_period_bytes(substream);
143	if (new_pos >= snd_pcm_lib_buffer_bytes(substream))
144		new_pos = 0;
145	prtd->pos = new_pos;
146
147	snd_pcm_period_elapsed(substream);
148}
149
150static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
151{
152	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
153	struct dma_chan *chan = prtd->dma_chan;
154	struct dma_async_tx_descriptor *desc;
155	enum dma_transfer_direction direction;
156	unsigned long flags = DMA_CTRL_ACK;
157
158	direction = snd_pcm_substream_to_dma_direction(substream);
159
160	if (!substream->runtime->no_period_wakeup)
161		flags |= DMA_PREP_INTERRUPT;
162
163	prtd->pos = 0;
164	desc = dmaengine_prep_dma_cyclic(chan,
165		substream->runtime->dma_addr,
166		snd_pcm_lib_buffer_bytes(substream),
167		snd_pcm_lib_period_bytes(substream), direction, flags);
168
169	if (!desc)
170		return -ENOMEM;
171
172	desc->callback = dmaengine_pcm_dma_complete;
173	desc->callback_param = substream;
174	prtd->cookie = dmaengine_submit(desc);
175
176	return 0;
177}
178
179/**
180 * snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation
181 * @substream: PCM substream
182 * @cmd: Trigger command
183 *
184 * This function can be used as the PCM trigger callback for dmaengine based PCM
185 * driver implementations.
186 *
187 * Return: 0 on success, a negative error code otherwise
188 */
189int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
190{
191	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
192	struct snd_pcm_runtime *runtime = substream->runtime;
193	int ret;
194
195	switch (cmd) {
196	case SNDRV_PCM_TRIGGER_START:
197		ret = dmaengine_pcm_prepare_and_submit(substream);
198		if (ret)
199			return ret;
200		dma_async_issue_pending(prtd->dma_chan);
201		break;
202	case SNDRV_PCM_TRIGGER_RESUME:
203	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
204		dmaengine_resume(prtd->dma_chan);
205		break;
206	case SNDRV_PCM_TRIGGER_SUSPEND:
207		if (runtime->info & SNDRV_PCM_INFO_PAUSE)
208			dmaengine_pause(prtd->dma_chan);
209		else
210			dmaengine_terminate_async(prtd->dma_chan);
211		break;
212	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
213		dmaengine_pause(prtd->dma_chan);
214		break;
215	case SNDRV_PCM_TRIGGER_STOP:
216		dmaengine_terminate_async(prtd->dma_chan);
217		break;
218	default:
219		return -EINVAL;
220	}
221
222	return 0;
223}
224EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger);
225
226/**
227 * snd_dmaengine_pcm_pointer_no_residue - dmaengine based PCM pointer implementation
228 * @substream: PCM substream
229 *
230 * This function is deprecated and should not be used by new drivers, as its
231 * results may be unreliable.
232 *
233 * Return: PCM position in frames
234 */
235snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream)
236{
237	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
238	return bytes_to_frames(substream->runtime, prtd->pos);
239}
240EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer_no_residue);
241
242/**
243 * snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation
244 * @substream: PCM substream
245 *
246 * This function can be used as the PCM pointer callback for dmaengine based PCM
247 * driver implementations.
248 *
249 * Return: PCM position in frames
250 */
251snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream)
252{
253	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
254	struct snd_pcm_runtime *runtime = substream->runtime;
255	struct dma_tx_state state;
256	enum dma_status status;
257	unsigned int buf_size;
258	unsigned int pos = 0;
259
260	status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state);
261	if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) {
262		buf_size = snd_pcm_lib_buffer_bytes(substream);
263		if (state.residue > 0 && state.residue <= buf_size)
264			pos = buf_size - state.residue;
265
266		runtime->delay = bytes_to_frames(runtime,
267						 state.in_flight_bytes);
268	}
269
270	return bytes_to_frames(runtime, pos);
271}
272EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer);
273
274/**
275 * snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM
276 * @filter_fn: Filter function used to request the DMA channel
277 * @filter_data: Data passed to the DMA filter function
278 *
279 * This function request a DMA channel for usage with dmaengine PCM.
280 *
281 * Return: NULL or the requested DMA channel
282 */
283struct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn,
284	void *filter_data)
285{
286	dma_cap_mask_t mask;
287
288	dma_cap_zero(mask);
289	dma_cap_set(DMA_SLAVE, mask);
290	dma_cap_set(DMA_CYCLIC, mask);
291
292	return dma_request_channel(mask, filter_fn, filter_data);
293}
294EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel);
295
296/**
297 * snd_dmaengine_pcm_open - Open a dmaengine based PCM substream
298 * @substream: PCM substream
299 * @chan: DMA channel to use for data transfers
300 *
301 * The function should usually be called from the pcm open callback. Note that
302 * this function will use private_data field of the substream's runtime. So it
303 * is not available to your pcm driver implementation.
304 *
305 * Return: 0 on success, a negative error code otherwise
306 */
307int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream,
308	struct dma_chan *chan)
309{
310	struct dmaengine_pcm_runtime_data *prtd;
311	int ret;
312
313	if (!chan)
314		return -ENXIO;
315
316	ret = snd_pcm_hw_constraint_integer(substream->runtime,
317					    SNDRV_PCM_HW_PARAM_PERIODS);
318	if (ret < 0)
319		return ret;
320
321	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
322	if (!prtd)
323		return -ENOMEM;
324
325	prtd->dma_chan = chan;
326
327	substream->runtime->private_data = prtd;
328
329	return 0;
330}
331EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open);
332
333int snd_dmaengine_pcm_sync_stop(struct snd_pcm_substream *substream)
334{
335	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
336	struct dma_tx_state state;
337	enum dma_status status;
338
339	status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state);
340	if (status != DMA_PAUSED)
341		dmaengine_synchronize(prtd->dma_chan);
342
343	return 0;
344}
345EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_sync_stop);
346
347static void __snd_dmaengine_pcm_close(struct snd_pcm_substream *substream,
348				      bool release_channel)
349{
350	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
351	struct dma_tx_state state;
352	enum dma_status status;
353
354	status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state);
355	if (status == DMA_PAUSED)
356		dmaengine_terminate_async(prtd->dma_chan);
357
358	dmaengine_synchronize(prtd->dma_chan);
359	if (release_channel)
360		dma_release_channel(prtd->dma_chan);
361	kfree(prtd);
362}
363
364/**
365 * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream
366 * @substream: PCM substream
367 *
368 * Return: 0 on success, a negative error code otherwise
369 */
370int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream)
371{
372	__snd_dmaengine_pcm_close(substream, false);
373	return 0;
374}
375EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close);
376
377/**
378 * snd_dmaengine_pcm_close_release_chan - Close a dmaengine based PCM
379 *					  substream and release channel
380 * @substream: PCM substream
381 *
382 * Releases the DMA channel associated with the PCM substream.
383 *
384 * Return: zero if successful, or a negative error code
385 */
386int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream)
387{
388	__snd_dmaengine_pcm_close(substream, true);
389	return 0;
390}
391EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan);
392
393/**
394 * snd_dmaengine_pcm_refine_runtime_hwparams - Refine runtime hw params
395 * @substream: PCM substream
396 * @dma_data: DAI DMA data
397 * @hw: PCM hw params
398 * @chan: DMA channel to use for data transfers
399 *
400 * This function will query DMA capability, then refine the pcm hardware
401 * parameters.
402 *
403 * Return: 0 on success, a negative error code otherwise
404 */
405int snd_dmaengine_pcm_refine_runtime_hwparams(
406	struct snd_pcm_substream *substream,
407	struct snd_dmaengine_dai_dma_data *dma_data,
408	struct snd_pcm_hardware *hw,
409	struct dma_chan *chan)
410{
411	struct dma_slave_caps dma_caps;
412	u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
413			  BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
414			  BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
415	snd_pcm_format_t i;
416	int ret = 0;
417
418	if (!hw || !chan || !dma_data)
419		return -EINVAL;
420
421	ret = dma_get_slave_caps(chan, &dma_caps);
422	if (ret == 0) {
423		if (dma_caps.cmd_pause && dma_caps.cmd_resume)
424			hw->info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME;
425		if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT)
426			hw->info |= SNDRV_PCM_INFO_BATCH;
427
428		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
429			addr_widths = dma_caps.dst_addr_widths;
430		else
431			addr_widths = dma_caps.src_addr_widths;
432	}
433
434	/*
435	 * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep
436	 * hw.formats set to 0, meaning no restrictions are in place.
437	 * In this case it's the responsibility of the DAI driver to
438	 * provide the supported format information.
439	 */
440	if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK))
441		/*
442		 * Prepare formats mask for valid/allowed sample types. If the
443		 * dma does not have support for the given physical word size,
444		 * it needs to be masked out so user space can not use the
445		 * format which produces corrupted audio.
446		 * In case the dma driver does not implement the slave_caps the
447		 * default assumption is that it supports 1, 2 and 4 bytes
448		 * widths.
449		 */
450		pcm_for_each_format(i) {
451			int bits = snd_pcm_format_physical_width(i);
452
453			/*
454			 * Enable only samples with DMA supported physical
455			 * widths
456			 */
457			switch (bits) {
458			case 8:
459			case 16:
460			case 24:
461			case 32:
462			case 64:
463				if (addr_widths & (1 << (bits / 8)))
464					hw->formats |= pcm_format_to_bits(i);
465				break;
466			default:
467				/* Unsupported types */
468				break;
469			}
470		}
471
472	return ret;
473}
474EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_refine_runtime_hwparams);
475
476MODULE_DESCRIPTION("PCM dmaengine helper APIs");
477MODULE_LICENSE("GPL");
~/ Read-only file preview.