162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright (C) 2012, Analog Devices Inc.
462306a36Sopenharmony_ci *	Author: Lars-Peter Clausen <lars@metafoo.de>
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *  Based on:
762306a36Sopenharmony_ci *	imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
862306a36Sopenharmony_ci *	mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc.
962306a36Sopenharmony_ci *	ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
1062306a36Sopenharmony_ci *		      Copyright (C) 2006 Applied Data Systems
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/dmaengine.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci#include <sound/pcm.h>
1762306a36Sopenharmony_ci#include <sound/pcm_params.h>
1862306a36Sopenharmony_ci#include <sound/soc.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <sound/dmaengine_pcm.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistruct dmaengine_pcm_runtime_data {
2362306a36Sopenharmony_ci	struct dma_chan *dma_chan;
2462306a36Sopenharmony_ci	dma_cookie_t cookie;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	unsigned int pos;
2762306a36Sopenharmony_ci};
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic inline struct dmaengine_pcm_runtime_data *substream_to_prtd(
3062306a36Sopenharmony_ci	const struct snd_pcm_substream *substream)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	return substream->runtime->private_data;
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistruct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	return prtd->dma_chan;
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_chan);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/**
4462306a36Sopenharmony_ci * snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config
4562306a36Sopenharmony_ci * @substream: PCM substream
4662306a36Sopenharmony_ci * @params: hw_params
4762306a36Sopenharmony_ci * @slave_config: DMA slave config
4862306a36Sopenharmony_ci *
4962306a36Sopenharmony_ci * This function can be used to initialize a dma_slave_config from a substream
5062306a36Sopenharmony_ci * and hw_params in a dmaengine based PCM driver implementation.
5162306a36Sopenharmony_ci *
5262306a36Sopenharmony_ci * Return: zero if successful, or a negative error code
5362306a36Sopenharmony_ci */
5462306a36Sopenharmony_ciint snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream,
5562306a36Sopenharmony_ci	const struct snd_pcm_hw_params *params,
5662306a36Sopenharmony_ci	struct dma_slave_config *slave_config)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	enum dma_slave_buswidth buswidth;
5962306a36Sopenharmony_ci	int bits;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	bits = params_physical_width(params);
6262306a36Sopenharmony_ci	if (bits < 8 || bits > 64)
6362306a36Sopenharmony_ci		return -EINVAL;
6462306a36Sopenharmony_ci	else if (bits == 8)
6562306a36Sopenharmony_ci		buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE;
6662306a36Sopenharmony_ci	else if (bits == 16)
6762306a36Sopenharmony_ci		buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
6862306a36Sopenharmony_ci	else if (bits == 24)
6962306a36Sopenharmony_ci		buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES;
7062306a36Sopenharmony_ci	else if (bits <= 32)
7162306a36Sopenharmony_ci		buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
7262306a36Sopenharmony_ci	else
7362306a36Sopenharmony_ci		buswidth = DMA_SLAVE_BUSWIDTH_8_BYTES;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
7662306a36Sopenharmony_ci		slave_config->direction = DMA_MEM_TO_DEV;
7762306a36Sopenharmony_ci		slave_config->dst_addr_width = buswidth;
7862306a36Sopenharmony_ci	} else {
7962306a36Sopenharmony_ci		slave_config->direction = DMA_DEV_TO_MEM;
8062306a36Sopenharmony_ci		slave_config->src_addr_width = buswidth;
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	slave_config->device_fc = false;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	return 0;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hwparams_to_dma_slave_config);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci/**
9062306a36Sopenharmony_ci * snd_dmaengine_pcm_set_config_from_dai_data() - Initializes a dma slave config
9162306a36Sopenharmony_ci *  using DAI DMA data.
9262306a36Sopenharmony_ci * @substream: PCM substream
9362306a36Sopenharmony_ci * @dma_data: DAI DMA data
9462306a36Sopenharmony_ci * @slave_config: DMA slave configuration
9562306a36Sopenharmony_ci *
9662306a36Sopenharmony_ci * Initializes the {dst,src}_addr, {dst,src}_maxburst, {dst,src}_addr_width
9762306a36Sopenharmony_ci * fields of the DMA slave config from the same fields of the DAI DMA
9862306a36Sopenharmony_ci * data struct. The src and dst fields will be initialized depending on the
9962306a36Sopenharmony_ci * direction of the substream. If the substream is a playback stream the dst
10062306a36Sopenharmony_ci * fields will be initialized, if it is a capture stream the src fields will be
10162306a36Sopenharmony_ci * initialized. The {dst,src}_addr_width field will only be initialized if the
10262306a36Sopenharmony_ci * SND_DMAENGINE_PCM_DAI_FLAG_PACK flag is set or if the addr_width field of
10362306a36Sopenharmony_ci * the DAI DMA data struct is not equal to DMA_SLAVE_BUSWIDTH_UNDEFINED. If
10462306a36Sopenharmony_ci * both conditions are met the latter takes priority.
10562306a36Sopenharmony_ci */
10662306a36Sopenharmony_civoid snd_dmaengine_pcm_set_config_from_dai_data(
10762306a36Sopenharmony_ci	const struct snd_pcm_substream *substream,
10862306a36Sopenharmony_ci	const struct snd_dmaengine_dai_dma_data *dma_data,
10962306a36Sopenharmony_ci	struct dma_slave_config *slave_config)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
11262306a36Sopenharmony_ci		slave_config->dst_addr = dma_data->addr;
11362306a36Sopenharmony_ci		slave_config->dst_maxburst = dma_data->maxburst;
11462306a36Sopenharmony_ci		if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)
11562306a36Sopenharmony_ci			slave_config->dst_addr_width =
11662306a36Sopenharmony_ci				DMA_SLAVE_BUSWIDTH_UNDEFINED;
11762306a36Sopenharmony_ci		if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
11862306a36Sopenharmony_ci			slave_config->dst_addr_width = dma_data->addr_width;
11962306a36Sopenharmony_ci	} else {
12062306a36Sopenharmony_ci		slave_config->src_addr = dma_data->addr;
12162306a36Sopenharmony_ci		slave_config->src_maxburst = dma_data->maxburst;
12262306a36Sopenharmony_ci		if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)
12362306a36Sopenharmony_ci			slave_config->src_addr_width =
12462306a36Sopenharmony_ci				DMA_SLAVE_BUSWIDTH_UNDEFINED;
12562306a36Sopenharmony_ci		if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED)
12662306a36Sopenharmony_ci			slave_config->src_addr_width = dma_data->addr_width;
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	slave_config->peripheral_config = dma_data->peripheral_config;
13062306a36Sopenharmony_ci	slave_config->peripheral_size = dma_data->peripheral_size;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_set_config_from_dai_data);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic void dmaengine_pcm_dma_complete(void *arg)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	unsigned int new_pos;
13762306a36Sopenharmony_ci	struct snd_pcm_substream *substream = arg;
13862306a36Sopenharmony_ci	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	new_pos = prtd->pos + snd_pcm_lib_period_bytes(substream);
14162306a36Sopenharmony_ci	if (new_pos >= snd_pcm_lib_buffer_bytes(substream))
14262306a36Sopenharmony_ci		new_pos = 0;
14362306a36Sopenharmony_ci	prtd->pos = new_pos;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	snd_pcm_period_elapsed(substream);
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
15162306a36Sopenharmony_ci	struct dma_chan *chan = prtd->dma_chan;
15262306a36Sopenharmony_ci	struct dma_async_tx_descriptor *desc;
15362306a36Sopenharmony_ci	enum dma_transfer_direction direction;
15462306a36Sopenharmony_ci	unsigned long flags = DMA_CTRL_ACK;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	direction = snd_pcm_substream_to_dma_direction(substream);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (!substream->runtime->no_period_wakeup)
15962306a36Sopenharmony_ci		flags |= DMA_PREP_INTERRUPT;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	prtd->pos = 0;
16262306a36Sopenharmony_ci	desc = dmaengine_prep_dma_cyclic(chan,
16362306a36Sopenharmony_ci		substream->runtime->dma_addr,
16462306a36Sopenharmony_ci		snd_pcm_lib_buffer_bytes(substream),
16562306a36Sopenharmony_ci		snd_pcm_lib_period_bytes(substream), direction, flags);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	if (!desc)
16862306a36Sopenharmony_ci		return -ENOMEM;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	desc->callback = dmaengine_pcm_dma_complete;
17162306a36Sopenharmony_ci	desc->callback_param = substream;
17262306a36Sopenharmony_ci	prtd->cookie = dmaengine_submit(desc);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	return 0;
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci/**
17862306a36Sopenharmony_ci * snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation
17962306a36Sopenharmony_ci * @substream: PCM substream
18062306a36Sopenharmony_ci * @cmd: Trigger command
18162306a36Sopenharmony_ci *
18262306a36Sopenharmony_ci * This function can be used as the PCM trigger callback for dmaengine based PCM
18362306a36Sopenharmony_ci * driver implementations.
18462306a36Sopenharmony_ci *
18562306a36Sopenharmony_ci * Return: 0 on success, a negative error code otherwise
18662306a36Sopenharmony_ci */
18762306a36Sopenharmony_ciint snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
19062306a36Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
19162306a36Sopenharmony_ci	int ret;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	switch (cmd) {
19462306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_START:
19562306a36Sopenharmony_ci		ret = dmaengine_pcm_prepare_and_submit(substream);
19662306a36Sopenharmony_ci		if (ret)
19762306a36Sopenharmony_ci			return ret;
19862306a36Sopenharmony_ci		dma_async_issue_pending(prtd->dma_chan);
19962306a36Sopenharmony_ci		break;
20062306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_RESUME:
20162306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
20262306a36Sopenharmony_ci		dmaengine_resume(prtd->dma_chan);
20362306a36Sopenharmony_ci		break;
20462306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_SUSPEND:
20562306a36Sopenharmony_ci		if (runtime->info & SNDRV_PCM_INFO_PAUSE)
20662306a36Sopenharmony_ci			dmaengine_pause(prtd->dma_chan);
20762306a36Sopenharmony_ci		else
20862306a36Sopenharmony_ci			dmaengine_terminate_async(prtd->dma_chan);
20962306a36Sopenharmony_ci		break;
21062306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
21162306a36Sopenharmony_ci		dmaengine_pause(prtd->dma_chan);
21262306a36Sopenharmony_ci		break;
21362306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_STOP:
21462306a36Sopenharmony_ci		dmaengine_terminate_async(prtd->dma_chan);
21562306a36Sopenharmony_ci		break;
21662306a36Sopenharmony_ci	default:
21762306a36Sopenharmony_ci		return -EINVAL;
21862306a36Sopenharmony_ci	}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return 0;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci/**
22562306a36Sopenharmony_ci * snd_dmaengine_pcm_pointer_no_residue - dmaengine based PCM pointer implementation
22662306a36Sopenharmony_ci * @substream: PCM substream
22762306a36Sopenharmony_ci *
22862306a36Sopenharmony_ci * This function is deprecated and should not be used by new drivers, as its
22962306a36Sopenharmony_ci * results may be unreliable.
23062306a36Sopenharmony_ci *
23162306a36Sopenharmony_ci * Return: PCM position in frames
23262306a36Sopenharmony_ci */
23362306a36Sopenharmony_cisnd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
23662306a36Sopenharmony_ci	return bytes_to_frames(substream->runtime, prtd->pos);
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer_no_residue);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci/**
24162306a36Sopenharmony_ci * snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation
24262306a36Sopenharmony_ci * @substream: PCM substream
24362306a36Sopenharmony_ci *
24462306a36Sopenharmony_ci * This function can be used as the PCM pointer callback for dmaengine based PCM
24562306a36Sopenharmony_ci * driver implementations.
24662306a36Sopenharmony_ci *
24762306a36Sopenharmony_ci * Return: PCM position in frames
24862306a36Sopenharmony_ci */
24962306a36Sopenharmony_cisnd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
25262306a36Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
25362306a36Sopenharmony_ci	struct dma_tx_state state;
25462306a36Sopenharmony_ci	enum dma_status status;
25562306a36Sopenharmony_ci	unsigned int buf_size;
25662306a36Sopenharmony_ci	unsigned int pos = 0;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state);
25962306a36Sopenharmony_ci	if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) {
26062306a36Sopenharmony_ci		buf_size = snd_pcm_lib_buffer_bytes(substream);
26162306a36Sopenharmony_ci		if (state.residue > 0 && state.residue <= buf_size)
26262306a36Sopenharmony_ci			pos = buf_size - state.residue;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci		runtime->delay = bytes_to_frames(runtime,
26562306a36Sopenharmony_ci						 state.in_flight_bytes);
26662306a36Sopenharmony_ci	}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	return bytes_to_frames(runtime, pos);
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci/**
27362306a36Sopenharmony_ci * snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM
27462306a36Sopenharmony_ci * @filter_fn: Filter function used to request the DMA channel
27562306a36Sopenharmony_ci * @filter_data: Data passed to the DMA filter function
27662306a36Sopenharmony_ci *
27762306a36Sopenharmony_ci * This function request a DMA channel for usage with dmaengine PCM.
27862306a36Sopenharmony_ci *
27962306a36Sopenharmony_ci * Return: NULL or the requested DMA channel
28062306a36Sopenharmony_ci */
28162306a36Sopenharmony_cistruct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn,
28262306a36Sopenharmony_ci	void *filter_data)
28362306a36Sopenharmony_ci{
28462306a36Sopenharmony_ci	dma_cap_mask_t mask;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	dma_cap_zero(mask);
28762306a36Sopenharmony_ci	dma_cap_set(DMA_SLAVE, mask);
28862306a36Sopenharmony_ci	dma_cap_set(DMA_CYCLIC, mask);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	return dma_request_channel(mask, filter_fn, filter_data);
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel);
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci/**
29562306a36Sopenharmony_ci * snd_dmaengine_pcm_open - Open a dmaengine based PCM substream
29662306a36Sopenharmony_ci * @substream: PCM substream
29762306a36Sopenharmony_ci * @chan: DMA channel to use for data transfers
29862306a36Sopenharmony_ci *
29962306a36Sopenharmony_ci * The function should usually be called from the pcm open callback. Note that
30062306a36Sopenharmony_ci * this function will use private_data field of the substream's runtime. So it
30162306a36Sopenharmony_ci * is not available to your pcm driver implementation.
30262306a36Sopenharmony_ci *
30362306a36Sopenharmony_ci * Return: 0 on success, a negative error code otherwise
30462306a36Sopenharmony_ci */
30562306a36Sopenharmony_ciint snd_dmaengine_pcm_open(struct snd_pcm_substream *substream,
30662306a36Sopenharmony_ci	struct dma_chan *chan)
30762306a36Sopenharmony_ci{
30862306a36Sopenharmony_ci	struct dmaengine_pcm_runtime_data *prtd;
30962306a36Sopenharmony_ci	int ret;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	if (!chan)
31262306a36Sopenharmony_ci		return -ENXIO;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	ret = snd_pcm_hw_constraint_integer(substream->runtime,
31562306a36Sopenharmony_ci					    SNDRV_PCM_HW_PARAM_PERIODS);
31662306a36Sopenharmony_ci	if (ret < 0)
31762306a36Sopenharmony_ci		return ret;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
32062306a36Sopenharmony_ci	if (!prtd)
32162306a36Sopenharmony_ci		return -ENOMEM;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	prtd->dma_chan = chan;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	substream->runtime->private_data = prtd;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	return 0;
32862306a36Sopenharmony_ci}
32962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open);
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci/**
33262306a36Sopenharmony_ci * snd_dmaengine_pcm_open_request_chan - Open a dmaengine based PCM substream and request channel
33362306a36Sopenharmony_ci * @substream: PCM substream
33462306a36Sopenharmony_ci * @filter_fn: Filter function used to request the DMA channel
33562306a36Sopenharmony_ci * @filter_data: Data passed to the DMA filter function
33662306a36Sopenharmony_ci *
33762306a36Sopenharmony_ci * This function will request a DMA channel using the passed filter function and
33862306a36Sopenharmony_ci * data. The function should usually be called from the pcm open callback. Note
33962306a36Sopenharmony_ci * that this function will use private_data field of the substream's runtime. So
34062306a36Sopenharmony_ci * it is not available to your pcm driver implementation.
34162306a36Sopenharmony_ci *
34262306a36Sopenharmony_ci * Return: 0 on success, a negative error code otherwise
34362306a36Sopenharmony_ci */
34462306a36Sopenharmony_ciint snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream,
34562306a36Sopenharmony_ci	dma_filter_fn filter_fn, void *filter_data)
34662306a36Sopenharmony_ci{
34762306a36Sopenharmony_ci	return snd_dmaengine_pcm_open(substream,
34862306a36Sopenharmony_ci		    snd_dmaengine_pcm_request_channel(filter_fn, filter_data));
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open_request_chan);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci/**
35362306a36Sopenharmony_ci * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream
35462306a36Sopenharmony_ci * @substream: PCM substream
35562306a36Sopenharmony_ci *
35662306a36Sopenharmony_ci * Return: 0 on success, a negative error code otherwise
35762306a36Sopenharmony_ci */
35862306a36Sopenharmony_ciint snd_dmaengine_pcm_close(struct snd_pcm_substream *substream)
35962306a36Sopenharmony_ci{
36062306a36Sopenharmony_ci	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	dmaengine_synchronize(prtd->dma_chan);
36362306a36Sopenharmony_ci	kfree(prtd);
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	return 0;
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close);
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci/**
37062306a36Sopenharmony_ci * snd_dmaengine_pcm_close_release_chan - Close a dmaengine based PCM
37162306a36Sopenharmony_ci *					  substream and release channel
37262306a36Sopenharmony_ci * @substream: PCM substream
37362306a36Sopenharmony_ci *
37462306a36Sopenharmony_ci * Releases the DMA channel associated with the PCM substream.
37562306a36Sopenharmony_ci *
37662306a36Sopenharmony_ci * Return: zero if successful, or a negative error code
37762306a36Sopenharmony_ci */
37862306a36Sopenharmony_ciint snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream)
37962306a36Sopenharmony_ci{
38062306a36Sopenharmony_ci	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	dmaengine_synchronize(prtd->dma_chan);
38362306a36Sopenharmony_ci	dma_release_channel(prtd->dma_chan);
38462306a36Sopenharmony_ci	kfree(prtd);
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	return 0;
38762306a36Sopenharmony_ci}
38862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan);
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci/**
39162306a36Sopenharmony_ci * snd_dmaengine_pcm_refine_runtime_hwparams - Refine runtime hw params
39262306a36Sopenharmony_ci * @substream: PCM substream
39362306a36Sopenharmony_ci * @dma_data: DAI DMA data
39462306a36Sopenharmony_ci * @hw: PCM hw params
39562306a36Sopenharmony_ci * @chan: DMA channel to use for data transfers
39662306a36Sopenharmony_ci *
39762306a36Sopenharmony_ci * This function will query DMA capability, then refine the pcm hardware
39862306a36Sopenharmony_ci * parameters.
39962306a36Sopenharmony_ci *
40062306a36Sopenharmony_ci * Return: 0 on success, a negative error code otherwise
40162306a36Sopenharmony_ci */
40262306a36Sopenharmony_ciint snd_dmaengine_pcm_refine_runtime_hwparams(
40362306a36Sopenharmony_ci	struct snd_pcm_substream *substream,
40462306a36Sopenharmony_ci	struct snd_dmaengine_dai_dma_data *dma_data,
40562306a36Sopenharmony_ci	struct snd_pcm_hardware *hw,
40662306a36Sopenharmony_ci	struct dma_chan *chan)
40762306a36Sopenharmony_ci{
40862306a36Sopenharmony_ci	struct dma_slave_caps dma_caps;
40962306a36Sopenharmony_ci	u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
41062306a36Sopenharmony_ci			  BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
41162306a36Sopenharmony_ci			  BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
41262306a36Sopenharmony_ci	snd_pcm_format_t i;
41362306a36Sopenharmony_ci	int ret = 0;
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	if (!hw || !chan || !dma_data)
41662306a36Sopenharmony_ci		return -EINVAL;
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	ret = dma_get_slave_caps(chan, &dma_caps);
41962306a36Sopenharmony_ci	if (ret == 0) {
42062306a36Sopenharmony_ci		if (dma_caps.cmd_pause && dma_caps.cmd_resume)
42162306a36Sopenharmony_ci			hw->info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME;
42262306a36Sopenharmony_ci		if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT)
42362306a36Sopenharmony_ci			hw->info |= SNDRV_PCM_INFO_BATCH;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
42662306a36Sopenharmony_ci			addr_widths = dma_caps.dst_addr_widths;
42762306a36Sopenharmony_ci		else
42862306a36Sopenharmony_ci			addr_widths = dma_caps.src_addr_widths;
42962306a36Sopenharmony_ci	}
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	/*
43262306a36Sopenharmony_ci	 * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep
43362306a36Sopenharmony_ci	 * hw.formats set to 0, meaning no restrictions are in place.
43462306a36Sopenharmony_ci	 * In this case it's the responsibility of the DAI driver to
43562306a36Sopenharmony_ci	 * provide the supported format information.
43662306a36Sopenharmony_ci	 */
43762306a36Sopenharmony_ci	if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK))
43862306a36Sopenharmony_ci		/*
43962306a36Sopenharmony_ci		 * Prepare formats mask for valid/allowed sample types. If the
44062306a36Sopenharmony_ci		 * dma does not have support for the given physical word size,
44162306a36Sopenharmony_ci		 * it needs to be masked out so user space can not use the
44262306a36Sopenharmony_ci		 * format which produces corrupted audio.
44362306a36Sopenharmony_ci		 * In case the dma driver does not implement the slave_caps the
44462306a36Sopenharmony_ci		 * default assumption is that it supports 1, 2 and 4 bytes
44562306a36Sopenharmony_ci		 * widths.
44662306a36Sopenharmony_ci		 */
44762306a36Sopenharmony_ci		pcm_for_each_format(i) {
44862306a36Sopenharmony_ci			int bits = snd_pcm_format_physical_width(i);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci			/*
45162306a36Sopenharmony_ci			 * Enable only samples with DMA supported physical
45262306a36Sopenharmony_ci			 * widths
45362306a36Sopenharmony_ci			 */
45462306a36Sopenharmony_ci			switch (bits) {
45562306a36Sopenharmony_ci			case 8:
45662306a36Sopenharmony_ci			case 16:
45762306a36Sopenharmony_ci			case 24:
45862306a36Sopenharmony_ci			case 32:
45962306a36Sopenharmony_ci			case 64:
46062306a36Sopenharmony_ci				if (addr_widths & (1 << (bits / 8)))
46162306a36Sopenharmony_ci					hw->formats |= pcm_format_to_bits(i);
46262306a36Sopenharmony_ci				break;
46362306a36Sopenharmony_ci			default:
46462306a36Sopenharmony_ci				/* Unsupported types */
46562306a36Sopenharmony_ci				break;
46662306a36Sopenharmony_ci			}
46762306a36Sopenharmony_ci		}
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	return ret;
47062306a36Sopenharmony_ci}
47162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_dmaengine_pcm_refine_runtime_hwparams);
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
474