diff mbox

[v3,09/12] ASoC: dmaengine_pcm: add snd_dmaengine_generic_pcm_open()

Message ID 1362494247-28909-1-git-send-email-shawn.guo@linaro.org (mailing list archive)
State New, archived
Headers show

Commit Message

Shawn Guo March 5, 2013, 2:37 p.m. UTC
With generic DMA device tree binding and helper function
dma_request_slave_channel() in place, dmaengine_pcm should support
that in requesting DMA channel for users that support generic DMA
device tree binding.

Add a new API snd_dmaengine_generic_pcm_open() as the variant of
snd_dmaengine_pcm_open().  It takes DMA client struct device pointer
and slave channel name as arguments and calls generic DMA helper
dma_request_slave_channel() to request DMA channel from dmaengine.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: alsa-devel@alsa-project.org
---
Changes since v2:
 - Drop 'const' from argument 'name' to fix the warning below.

sound/soc/soc-dmaengine-pcm.c: In function 'snd_dmaengine_generic_pcm_open':
sound/soc/soc-dmaengine-pcm.c:333:2: warning: passing argument 2 of 'dma_request_slave_channel' discards 'const' qualifier from pointer target type [enabled by default]
include/linux/dmaengine.h:971:18: note: expected 'char *' but argument is of type 'const char *'

 include/sound/dmaengine_pcm.h |    2 ++
 sound/soc/soc-dmaengine-pcm.c |   39 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 41 insertions(+)

Comments

Marek Vasut March 5, 2013, 11:28 p.m. UTC | #1
Hi Shawn,

> With generic DMA device tree binding and helper function
> dma_request_slave_channel() in place, dmaengine_pcm should support
> that in requesting DMA channel for users that support generic DMA
> device tree binding.
> 
> Add a new API snd_dmaengine_generic_pcm_open() as the variant of
> snd_dmaengine_pcm_open().  It takes DMA client struct device pointer
> and slave channel name as arguments and calls generic DMA helper
> dma_request_slave_channel() to request DMA channel from dmaengine.
> 
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: alsa-devel@alsa-project.org
> ---
> Changes since v2:
>  - Drop 'const' from argument 'name' to fix the warning below.
> 
[...]

v3 changelog missing ;-) What did you adjust here please?

Best regards,
Marek Vasut
Shawn Guo March 6, 2013, 5:11 a.m. UTC | #2
On Wed, Mar 06, 2013 at 12:28:18AM +0100, Marek Vasut wrote:
> Hi Shawn,
> 
> > With generic DMA device tree binding and helper function
> > dma_request_slave_channel() in place, dmaengine_pcm should support
> > that in requesting DMA channel for users that support generic DMA
> > device tree binding.
> > 
> > Add a new API snd_dmaengine_generic_pcm_open() as the variant of
> > snd_dmaengine_pcm_open().  It takes DMA client struct device pointer
> > and slave channel name as arguments and calls generic DMA helper
> > dma_request_slave_channel() to request DMA channel from dmaengine.
> > 
> > Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> > Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> > Cc: alsa-devel@alsa-project.org
> > ---
> > Changes since v2:
> >  - Drop 'const' from argument 'name' to fix the warning below.
> > 
> [...]
> 
> v3 changelog missing ;-) What did you adjust here please?

The "Changes since v2" means v3 changelog.

Shawn
Marek Vasut March 6, 2013, 4:41 p.m. UTC | #3
Dear Shawn Guo,

> On Wed, Mar 06, 2013 at 12:28:18AM +0100, Marek Vasut wrote:
> > Hi Shawn,
> > 
> > > With generic DMA device tree binding and helper function
> > > dma_request_slave_channel() in place, dmaengine_pcm should support
> > > that in requesting DMA channel for users that support generic DMA
> > > device tree binding.
> > > 
> > > Add a new API snd_dmaengine_generic_pcm_open() as the variant of
> > > snd_dmaengine_pcm_open().  It takes DMA client struct device pointer
> > > and slave channel name as arguments and calls generic DMA helper
> > > dma_request_slave_channel() to request DMA channel from dmaengine.
> > > 
> > > Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> > > Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> > > Cc: alsa-devel@alsa-project.org
> > > ---
> > > 
> > > Changes since v2:
> > >  - Drop 'const' from argument 'name' to fix the warning below.
> > 
> > [...]
> > 
> > v3 changelog missing ;-) What did you adjust here please?
> 
> The "Changes since v2" means v3 changelog.

Bah, it was too late then, sorry.

Best regards,
Marek Vasut
Russell King - ARM Linux March 6, 2013, 5:13 p.m. UTC | #4
On Tue, Mar 05, 2013 at 10:37:27PM +0800, Shawn Guo wrote:
> With generic DMA device tree binding and helper function
> dma_request_slave_channel() in place, dmaengine_pcm should support
> that in requesting DMA channel for users that support generic DMA
> device tree binding.

This purpetuates the brain-dead behaviour of the existing ASoC DMA
engine layer, which makes it unsuitable for platforms with special DMA
memory requirements.

The problem is that the DMA mask to be used for allocating DMA-able
memory is the DMA engine struct device, not the struct device associated
with the ASoC device.

I got this right in my ASoC generic DMA engine layer.  Converting this
layer is far from trivial though, and as my test platform has now become
my entire network firewall, I'm not doing any testing on that anymore.
Mark Brown March 7, 2013, 2:33 a.m. UTC | #5
On Wed, Mar 06, 2013 at 05:13:33PM +0000, Russell King - ARM Linux wrote:

> This purpetuates the brain-dead behaviour of the existing ASoC DMA
> engine layer, which makes it unsuitable for platforms with special DMA
> memory requirements.

> The problem is that the DMA mask to be used for allocating DMA-able
> memory is the DMA engine struct device, not the struct device associated
> with the ASoC device.

> I got this right in my ASoC generic DMA engine layer.  Converting this
> layer is far from trivial though, and as my test platform has now become
> my entire network firewall, I'm not doing any testing on that anymore.

Could you go into more detail here please?  Looking at the code I'm not
seeing any allocations done by the library code at all, the allocations
are all done by the individual platform DMA drivers so I don't see
anything stopping them doing what they need.
Russell King - ARM Linux March 7, 2013, 9:18 a.m. UTC | #6
On Thu, Mar 07, 2013 at 10:33:19AM +0800, Mark Brown wrote:
> On Wed, Mar 06, 2013 at 05:13:33PM +0000, Russell King - ARM Linux wrote:
> 
> > This purpetuates the brain-dead behaviour of the existing ASoC DMA
> > engine layer, which makes it unsuitable for platforms with special DMA
> > memory requirements.
> 
> > The problem is that the DMA mask to be used for allocating DMA-able
> > memory is the DMA engine struct device, not the struct device associated
> > with the ASoC device.
> 
> > I got this right in my ASoC generic DMA engine layer.  Converting this
> > layer is far from trivial though, and as my test platform has now become
> > my entire network firewall, I'm not doing any testing on that anymore.
> 
> Could you go into more detail here please?  Looking at the code I'm not
> seeing any allocations done by the library code at all, the allocations
> are all done by the individual platform DMA drivers so I don't see
> anything stopping them doing what they need.

I don't know what else you require apart from the description above.  Isn't
it rather obvious that you can't preallocate the ALSA buffer against the
DMA engine device if you can only obtain the DMA engine device in the open
function?

Notice in the code below where the DMA engine is obtained in pcm_new and
the buffer is preallocated against the DMA engine struct device.  That's
exactly what I'm talking about.

/*
 * Generic ASoC DMA engine backend
 *
 * Copyright (C) 2012 Russell King
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * We expect the DMA engine to give accurate residue information,
 * returning the number of bytes left to complete to the requested
 * cookie.  We also expect the DMA engine to be able to resume
 * submitted descriptors after a suspend/resume cycle.
 */
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>

#include <sound/core.h>
#include <sound/soc-dmaengine.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>

#define BUFFER_SIZE_MAX		65536
#define PERIOD_SIZE_MIN		32
#define PERIOD_SIZE_MAX		16384
#define PERIODS_MIN		2
#define PERIODS_MAX		256

struct buf_info {
	struct scatterlist sg;
	dma_cookie_t cookie;
};

struct soc_dma_chan {
	const struct soc_dma_config *conf;
	spinlock_t lock;
	struct dma_chan *chan;
	struct dma_slave_config cfg;
	enum dma_transfer_direction dir;
	unsigned nr_periods;
	unsigned sg_index;
	unsigned stopped;
	unsigned cyclic;
	dma_cookie_t cyclic_cookie;
	struct buf_info buf[PERIODS_MAX];
};

struct soc_dma_info {
	struct soc_dma_chan *chan[2];
};

static const struct snd_pcm_hardware soc_dmaengine_hardware = {
	.info			= SNDRV_PCM_INFO_MMAP |
				  SNDRV_PCM_INFO_MMAP_VALID |
				  SNDRV_PCM_INFO_INTERLEAVED |
				  SNDRV_PCM_INFO_PAUSE |
				  SNDRV_PCM_INFO_RESUME,
	.period_bytes_min	= PERIOD_SIZE_MIN,
	.period_bytes_max	= PERIOD_SIZE_MAX,
	.periods_min		= PERIODS_MIN,
	.periods_max		= PERIODS_MAX,
	.buffer_bytes_max	= BUFFER_SIZE_MAX,
};

static int soc_dmaengine_submit(struct snd_pcm_substream *substream,
	struct soc_dma_chan *dma);

static void soc_dmaengine_callback(void *data)
{
	struct snd_pcm_substream *substream = data;
	struct soc_dma_chan *dma = substream->runtime->private_data;
	int ret;

	snd_pcm_period_elapsed(substream);

	if (!dma->stopped && !dma->cyclic) {
		spin_lock(&dma->lock);
		ret = soc_dmaengine_submit(substream, dma);
		spin_unlock(&dma->lock);

		if (ret == 0)
			dma_async_issue_pending(dma->chan);
		else
			pr_err("%s: failed to submit next DMA buffer\n", __func__);
	}
}

static int soc_dmaengine_submit(struct snd_pcm_substream *substream,
	struct soc_dma_chan *dma)
{
	struct dma_async_tx_descriptor *tx;
	struct dma_chan *ch = dma->chan;
	struct buf_info *buf;
	unsigned sg_index;

	sg_index = dma->sg_index;

	buf = &dma->buf[sg_index];

	tx = dmaengine_prep_slave_sg(ch, &buf->sg, 1, dma->dir,
		DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
	if (tx) {
		tx->callback = soc_dmaengine_callback;
		tx->callback_param = substream;

		buf->cookie = dmaengine_submit(tx);

		sg_index++;
		if (sg_index >= dma->nr_periods)
			sg_index = 0;
		dma->sg_index = sg_index;
	}

	return tx ? 0 : -ENOMEM;
}

static int soc_dmaengine_start(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct soc_dma_chan *dma = runtime->private_data;
	unsigned long flags;
	unsigned i;
	int ret = 0;

	if (!dma->cyclic) {
		spin_lock_irqsave(&dma->lock, flags);
		for (i = 0; i < dma->nr_periods; i++) {
			ret = soc_dmaengine_submit(substream, dma);
			if (ret)
				break;
		}
		spin_unlock_irqrestore(&dma->lock, flags);
		if (ret == 0) {
			dma->stopped = 0;
			dma_async_issue_pending(dma->chan);
		} else {
			dma->stopped = 1;
			dmaengine_terminate_all(dma->chan);
		}
	} else {
		struct dma_async_tx_descriptor *tx;

		tx = dmaengine_prep_dma_cyclic(dma->chan, runtime->dma_addr,
			snd_pcm_lib_buffer_bytes(substream),
			snd_pcm_lib_period_bytes(substream), dma->dir,
			DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
		if (tx) {
			tx->callback = soc_dmaengine_callback;
			tx->callback_param = substream;

			dma->cyclic_cookie = dmaengine_submit(tx);
			dma_async_issue_pending(dma->chan);
		}
	}

	return ret;
}

static int soc_dmaengine_stop(struct snd_pcm_substream *substream)
{
	struct soc_dma_chan *dma = substream->runtime->private_data;

	dma->stopped = 1;
	dmaengine_terminate_all(dma->chan);

	return 0;
}

static int soc_dmaengine_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct soc_dma_info *info = snd_soc_platform_get_drvdata(rtd->platform);
	struct soc_dma_chan *dma = info->chan[substream->stream];
	int ret = 0;

	if (!dma)
		return -EINVAL;

	runtime->hw = soc_dmaengine_hardware;
	runtime->hw.fifo_size = dma->conf->fifo_size;

	if (dma->conf->align) {
		/*
		 * FIXME: Ideally, there should be some way to query
		 * this from the DMA engine itself.
		 *
		 * It would also be helpful to know the maximum size
		 * a scatterlist entry can be to set the period size.
		 */
		ret = snd_pcm_hw_constraint_step(runtime, 0,
			SNDRV_PCM_HW_PARAM_PERIOD_BYTES, dma->conf->align);
		if (ret)
			goto err;

		ret = snd_pcm_hw_constraint_step(runtime, 0,
			SNDRV_PCM_HW_PARAM_BUFFER_BYTES, dma->conf->align);
		if (ret)
			goto err;
	}

	runtime->private_data = dma;

 err:
	return ret;
}

static int soc_dmaengine_close(struct snd_pcm_substream *substream)
{
	return 0;
}

static int soc_dmaengine_hw_params(struct snd_pcm_substream *substream,
	struct snd_pcm_hw_params *params)
{
	int ret = snd_pcm_lib_malloc_pages(substream,
			params_buffer_bytes(params));

	return ret < 0 ? ret : 0;
}

static int soc_dmaengine_prepare(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct soc_dma_chan *dma = runtime->private_data;
	size_t buf_size = snd_pcm_lib_buffer_bytes(substream);
	size_t period = snd_pcm_lib_period_bytes(substream);
	dma_addr_t addr = runtime->dma_addr;
	unsigned i;

	/* Create an array of sg entries, one for each period */
	for (i = 0; i < PERIODS_MAX && buf_size; i++) {
		BUG_ON(buf_size < period);

		sg_dma_address(&dma->buf[i].sg) = addr;
		sg_dma_len(&dma->buf[i].sg) = period;

		addr += period;
		buf_size -= period;
	}

	if (buf_size) {
		pr_err("DMA buffer size not a multiple of the period size: residue=%zu\n", buf_size);
		return -EINVAL;
	}

	dma->nr_periods = i;
	dma->sg_index = 0;

	return 0;
}

static int soc_dmaengine_trigger(struct snd_pcm_substream *substream, int cmd)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct soc_dma_chan *dma = runtime->private_data;
	int ret;

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		ret = soc_dmaengine_start(substream);
		break;

	case SNDRV_PCM_TRIGGER_STOP:
		ret = soc_dmaengine_stop(substream);
		break;

	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
	case SNDRV_PCM_TRIGGER_SUSPEND:
		ret = dmaengine_pause(dma->chan);
		break;

	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
	case SNDRV_PCM_TRIGGER_RESUME:
		ret = dmaengine_resume(dma->chan);
		break;

	default:
		ret = -EINVAL;
	}

	return ret;
}

static snd_pcm_uframes_t soc_dmaengine_pointer(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct soc_dma_chan *dma = runtime->private_data;
	struct dma_chan *ch = dma->chan;
	struct dma_tx_state state;
	unsigned bytes;

	if (dma->cyclic) {
		ch->device->device_tx_status(ch, dma->cyclic_cookie, &state);

		bytes = snd_pcm_lib_buffer_bytes(substream) - state.residue;
	} else {
		unsigned index, pos;
		size_t period = snd_pcm_lib_period_bytes(substream);
		unsigned long flags;
		enum dma_status status;
		dma_cookie_t cookie;

		/*
		 * Get the next-to-be-submitted index, and the last submitted
		 * cookie value.  We use this to obtain the DMA engine state.
		 */
		spin_lock_irqsave(&dma->lock, flags);
		index = dma->sg_index;
		do {
			cookie = dma->buf[index].cookie;
			status = ch->device->device_tx_status(ch, cookie, &state);
			if (status == DMA_SUCCESS) {
				index++;
				if (index > dma->nr_periods)
					index = 0;
			}
		} while (status == DMA_SUCCESS);
		spin_unlock_irqrestore(&dma->lock, flags);

		/* The end of the current DMA buffer */
		pos = (index + 1) * period;
		/* Now take off the residue */
		bytes = pos - state.residue;
		/* And correct for wrap-around */
		if (bytes >= period * dma->nr_periods)
			bytes -= period * dma->nr_periods;
	}

	return bytes_to_frames(runtime, bytes);
}

static int soc_dmaengine_mmap(struct snd_pcm_substream *substream,
	struct vm_area_struct *vma)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_dma_buffer *buf = runtime->dma_buffer_p;

	return dma_mmap_writecombine(buf->dev.dev, vma,
		runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
}

static struct snd_pcm_ops soc_dmaengine_ops = {
	.open = soc_dmaengine_open,
	.close = soc_dmaengine_close,
	.ioctl = snd_pcm_lib_ioctl,
	.hw_params = soc_dmaengine_hw_params,
	.hw_free = snd_pcm_lib_free_pages,
	.prepare = soc_dmaengine_prepare,
	.trigger = soc_dmaengine_trigger,
	.pointer = soc_dmaengine_pointer,
	.mmap = soc_dmaengine_mmap,
};

static struct soc_dma_chan *soc_dmaengine_alloc(void)
{
	struct soc_dma_chan *dma = kzalloc(sizeof(*dma), GFP_KERNEL);
	unsigned i;

	if (dma) {
		spin_lock_init(&dma->lock);
		for (i = 0; i < PERIODS_MAX; i++)
			sg_init_table(&dma->buf[i].sg, 1);
	}
	return dma;
}

static struct dma_chan *soc_dmaengine_get(enum dma_transaction_type type,
	struct soc_dma_config *cfg)
{
	dma_cap_mask_t m;

	dma_cap_zero(m);
	dma_cap_set(type, m);
	return dma_request_channel(m, cfg->filter, cfg->data);
}

static int soc_dmaengine_request(struct soc_dma_chan *dma,
	struct soc_dma_config *cfg, unsigned stream)
{
	int ret;

	dma->conf = cfg;
	dma->chan = soc_dmaengine_get(DMA_CYCLIC, cfg);
	if (!dma->chan)
		dma->chan = soc_dmaengine_get(DMA_SLAVE, cfg);
	else
		dma->cyclic = 1;
	if (!dma->chan) {
		ret = -ENXIO;
		goto err_dma_req;
	}

	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
		dma->dir = DMA_MEM_TO_DEV;
		dma->cfg.direction = DMA_MEM_TO_DEV;
		dma->cfg.dst_addr = cfg->reg;
		dma->cfg.dst_addr_width = cfg->width;
		dma->cfg.dst_maxburst = cfg->maxburst;
	} else {
		dma->dir = DMA_DEV_TO_MEM;
		dma->cfg.direction = DMA_DEV_TO_MEM;
		dma->cfg.src_addr = cfg->reg;
		dma->cfg.src_addr_width = cfg->width;
		dma->cfg.src_maxburst = cfg->maxburst;
	}

	ret = dmaengine_slave_config(dma->chan, &dma->cfg);
	if (ret)
		goto err_dma_cfg;

	return 0;

 err_dma_cfg:
	dma_release_channel(dma->chan);
	dma->chan = NULL;
 err_dma_req:
	return ret;
}

static void soc_dmaengine_release(struct soc_dma_chan *dma)
{
	if (dma && dma->chan)
		dma_release_channel(dma->chan);
	kfree(dma);
}

static int soc_dmaengine_preallocate_buffer(struct snd_pcm *pcm,
	unsigned stream, struct soc_dma_chan *dma)
{
	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
	int ret = 0;

	if (substream) {
		struct snd_dma_buffer *buf = &substream->dma_buffer;

		buf->dev.type = SNDRV_DMA_TYPE_DEV;
		buf->dev.dev = dma->chan->device->dev;
		buf->private_data = NULL;
		buf->area = dma_alloc_writecombine(buf->dev.dev,
				BUFFER_SIZE_MAX, &buf->addr, GFP_KERNEL);
		if (!buf->area)
			return -ENOMEM;

		buf->bytes = BUFFER_SIZE_MAX;
	}
	return ret;
}

static int soc_dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_pcm *pcm = rtd->pcm;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct soc_dma_info *info;
	unsigned stream;
	int ret = 0;

	if (!cpu_dai)
		return -EINVAL;

	if (!cpu_dai->playback_dma_data &&
	    !cpu_dai->capture_dma_data) {
		pr_err("soc_dmaengine: %s has no cpu_dai DMA data - incorrect probe ordering?\n",
			rtd->card->name);
		return -EINVAL;
	}

	info = kzalloc(sizeof(*info), GFP_KERNEL);
	if (!info)
		return -ENOMEM;

	for (stream = 0; stream < ARRAY_SIZE(pcm->streams); stream++) {
		struct soc_dma_config *cfg = NULL;
		struct soc_dma_chan *dma;

		if (stream == SNDRV_PCM_STREAM_PLAYBACK)
			cfg = cpu_dai->playback_dma_data;
		else if (stream == SNDRV_PCM_STREAM_CAPTURE)
			cfg = cpu_dai->capture_dma_data;

		if (!cfg)
			continue;

		info->chan[stream] = dma = soc_dmaengine_alloc();
		if (!dma) {
			ret = -ENOMEM;
			break;
		}

		ret = soc_dmaengine_request(dma, cfg, stream);
		if (ret)
			return ret;

		ret = soc_dmaengine_preallocate_buffer(pcm, stream, dma);
		if (ret)
			break;
	}

	if (ret) {
		for (stream = 0; stream < ARRAY_SIZE(info->chan); stream++)
			soc_dmaengine_release(info->chan[stream]);
		kfree(info);
	} else {
		snd_soc_platform_set_drvdata(rtd->platform, info);
	}

	return ret;
}

/*
 * Use write-combining memory here: the standard ALSA
 * snd_free_dev_pages() doesn't support this.
 */
static void soc_dmaengine_pcm_free(struct snd_pcm *pcm)
{
	unsigned stream;

	for (stream = 0; stream < ARRAY_SIZE(pcm->streams); stream++) {
		struct snd_pcm_substream *substream = pcm->streams[stream].substream;
		struct snd_dma_buffer *buf;

		if (!substream)
			continue;
		buf = &substream->dma_buffer;
		if (!buf->area)
			continue;

		if (buf->dev.type == SNDRV_DMA_TYPE_DEV)
			dma_free_writecombine(buf->dev.dev, buf->bytes,
					      buf->area, buf->addr);
		else
			snd_dma_free_pages(buf);
		buf->area = NULL;
	}
}

/*
 * This is annoying - we can't have symetry with .pcm_new because .pcm_free
 * is called after the runtime information has been removed.  It would be
 * better if we could find somewhere else to store our soc_dma_info pointer.
 */
static int soc_dmaengine_plat_remove(struct snd_soc_platform *platform)
{
	struct soc_dma_info *info = snd_soc_platform_get_drvdata(platform);
	unsigned stream;

	for (stream = 0; stream < ARRAY_SIZE(info->chan); stream++)
		soc_dmaengine_release(info->chan[stream]);
	kfree(info);

	return 0;
}

static struct snd_soc_platform_driver soc_dmaengine_platform = {
	.remove = soc_dmaengine_plat_remove,
	.pcm_new = soc_dmaengine_pcm_new,
	.pcm_free = soc_dmaengine_pcm_free,
	.ops = &soc_dmaengine_ops,
	/* Wait until the cpu_dai has been probed */
	.probe_order = SND_SOC_COMP_ORDER_LATE,
};

static int soc_dmaengine_probe(struct platform_device *pdev)
{
	return snd_soc_register_platform(&pdev->dev, &soc_dmaengine_platform);
}

static int soc_dmaengine_remove(struct platform_device *pdev)
{
	snd_soc_unregister_platform(&pdev->dev);
	return 0;
}

static struct platform_driver soc_dmaengine_driver = {
	.driver = {
		.name = "soc-dmaengine",
		.owner = THIS_MODULE,
	},
	.probe = soc_dmaengine_probe,
	.remove = soc_dmaengine_remove,
};

module_platform_driver(soc_dmaengine_driver);

MODULE_AUTHOR("Russell King");
MODULE_DESCRIPTION("ALSA SoC DMA engine driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:soc-dmaengine");
Mark Brown March 7, 2013, 9:31 a.m. UTC | #7
On Thu, Mar 07, 2013 at 09:18:04AM +0000, Russell King - ARM Linux wrote:
> On Thu, Mar 07, 2013 at 10:33:19AM +0800, Mark Brown wrote:

> > Could you go into more detail here please?  Looking at the code I'm not
> > seeing any allocations done by the library code at all, the allocations
> > are all done by the individual platform DMA drivers so I don't see
> > anything stopping them doing what they need.

> I don't know what else you require apart from the description above.  Isn't
> it rather obvious that you can't preallocate the ALSA buffer against the
> DMA engine device if you can only obtain the DMA engine device in the open
> function?

The bit I'm missing is why this is particularly hard to change, it
doesn't seem like a massive refactoring and there's not many users.
Russell King - ARM Linux March 7, 2013, 11:20 a.m. UTC | #8
On Thu, Mar 07, 2013 at 05:31:23PM +0800, Mark Brown wrote:
> On Thu, Mar 07, 2013 at 09:18:04AM +0000, Russell King - ARM Linux wrote:
> > On Thu, Mar 07, 2013 at 10:33:19AM +0800, Mark Brown wrote:
> 
> > > Could you go into more detail here please?  Looking at the code I'm not
> > > seeing any allocations done by the library code at all, the allocations
> > > are all done by the individual platform DMA drivers so I don't see
> > > anything stopping them doing what they need.
> 
> > I don't know what else you require apart from the description above.  Isn't
> > it rather obvious that you can't preallocate the ALSA buffer against the
> > DMA engine device if you can only obtain the DMA engine device in the open
> > function?
> 
> The bit I'm missing is why this is particularly hard to change, it
> doesn't seem like a massive refactoring and there's not many users.

Well, it requires the thing to be reworked along with everyone who uses
it, specifically snd_dmaengine_pcm_open() and snd_dmaengine_pcm_close().

Now, I could use your excuse that you've given me in the past: "I don't
have much of that hardware so I can't test the changes, so I'm not going
to touch this code evar again!"  (That's basically what you said about
the AC'97 struct device stuff.)  You can't have it both ways and always
shovel what you don't like onto other people.

So... I'll just go back to quietly sitting on my SA11x0 ASoC support
and totally ignoring mainline with it because of obstructive
maintainers. :)
Mark Brown March 8, 2013, 7:43 a.m. UTC | #9
On Thu, Mar 07, 2013 at 11:20:06AM +0000, Russell King - ARM Linux wrote:
> On Thu, Mar 07, 2013 at 05:31:23PM +0800, Mark Brown wrote:

> > The bit I'm missing is why this is particularly hard to change, it
> > doesn't seem like a massive refactoring and there's not many users.

> Well, it requires the thing to be reworked along with everyone who uses
> it, specifically snd_dmaengine_pcm_open() and snd_dmaengine_pcm_close().

Oh, OK.  That doesn't seem like a big deal really - it's certainly not a
throw the thing out and start over job.  It sounded like you'd identifed
some new issue you'd not mentioned rather than just the same issue, it
wasn't clear to me that it was the same issue.

> Now, I could use your excuse that you've given me in the past: "I don't
> have much of that hardware so I can't test the changes, so I'm not going
> to touch this code evar again!"  (That's basically what you said about

I'm not particularly asking you to fix this yourself except in that it
seems like it's an important issue for you.  If anything something like
this patch ought to make things marginally easier to deal with by
factoring out a very small bit of the code.

> the AC'97 struct device stuff.)  You can't have it both ways and always
> shovel what you don't like onto other people.

I'm aware of the issue, as are the people who've worked on the code.
Speaking personally I just happen to disagree with you about the urgency
here - it's not like it's the only problem we've got and the practical
effects are limited to a subset of mostly older hardware which generally
doesn't use dmaeengine in the first place.  I imagine that a similar
thing is true for everyone else.

There's plenty of other hardware that doesn't work right now, another
pressing example I can think of is devices that subdivide an audio
interface into multiple unrelated streams of audio, you aren't alone in
having hardware that needs the frameworks improving in order to achieve
basic functionality.
diff mbox

Patch

diff --git a/include/sound/dmaengine_pcm.h b/include/sound/dmaengine_pcm.h
index b877334..394272b 100644
--- a/include/sound/dmaengine_pcm.h
+++ b/include/sound/dmaengine_pcm.h
@@ -43,6 +43,8 @@  snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream
 
 int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream,
 	dma_filter_fn filter_fn, void *filter_data);
+int snd_dmaengine_generic_pcm_open(struct snd_pcm_substream *substream,
+				   struct device *dev, char *name);
 int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream);
 
 struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream);
diff --git a/sound/soc/soc-dmaengine-pcm.c b/sound/soc/soc-dmaengine-pcm.c
index 111b7d92..e9d9897 100644
--- a/sound/soc/soc-dmaengine-pcm.c
+++ b/sound/soc/soc-dmaengine-pcm.c
@@ -304,6 +304,45 @@  int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream,
 EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open);
 
 /**
+ * snd_dmaengine_generic_pcm_open - Open a dmaengine based PCM substream
+ * @substream: PCM substream
+ * @dev: pointer to DMA client device structure
+ * @name: DMA slave channel name
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ *
+ * This function is a variant of snd_dmaengine_pcm_open(). It takes different
+ * parameters and calls generic DMA helper dma_request_slave_channel() to
+ * request a DMA channel from dmaengine.
+ */
+int snd_dmaengine_generic_pcm_open(struct snd_pcm_substream *substream,
+				   struct device *dev, char *name)
+{
+	struct dmaengine_pcm_runtime_data *prtd;
+	int ret;
+
+	ret = snd_pcm_hw_constraint_integer(substream->runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		return ret;
+
+	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+	if (!prtd)
+		return -ENOMEM;
+
+	prtd->dma_chan = dma_request_slave_channel(dev, name);
+	if (!prtd->dma_chan) {
+		kfree(prtd);
+		return -ENXIO;
+	}
+
+	substream->runtime->private_data = prtd;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_dmaengine_generic_pcm_open);
+
+/**
  * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream
  * @substream: PCM substream
  */