[alsa-devel] Implementing sync start
I'm implementing sync start as Takashi described... --------------- If the hardware supports the simultaneous triggering of multiple streams, add the sync support to the driver. Namely, - Add SNDRV_PCM_INFO_SYNC_START flag to PCM info field - Call snd_pcm_set_sync(substream) at each open callback - In the trigger callback, check all bundled streams via snd_pcm_group_for_each_entry(). Mark all the streams but the current one via snd_pcm_trigger_done(). Then start all streams at once. Ditto for stop and pause. -------------
Is there a simple way to test this with something like a mono sine wav file? A aplay mono.wav and then use and ALSA conf to copy the sine to all channels and then sync start them.
-----------
brutefit has this routine for sync start, does this look correct?
int bfio_synch_start(void) { snd_pcm_status_t *status; int err, n;
if (base_handle == NULL) { return 0; }
/* FIXME: the SND_PCM_STATE_RUNNING code would not be needed if the bfio_write autostart hack was not there */ snd_pcm_status_alloca(&status);
if (link_handles) { if ((err = snd_pcm_status(base_handle, status)) < 0) { fprintf(stderr, "ALSA I/O: Could not get status: %s.\n", snd_strerror(err)); return -1; } if (snd_pcm_status_get_state(status) == SND_PCM_STATE_RUNNING) { return 0; } if ((err = snd_pcm_start(base_handle)) < 0) { fprintf(stderr, "ALSA I/O: Could not start audio: %s.\n", snd_strerror(err)); return -1; } return 0; }
FOR_IN_AND_OUT { for (n = 0; n < n_handles[IO]; n++) { if ((err = snd_pcm_status(handles[IO][n], status)) < 0) { fprintf(stderr, "ALSA I/O: Could not get status: %s.\n", snd_strerror(err)); return -1; } if (snd_pcm_status_get_state(status) == SND_PCM_STATE_RUNNING) { continue; }
if ((err = snd_pcm_start(handles[IO][n])) < 0) { fprintf(stderr, "ALSA I/O: Could not start audio: %s.\n", snd_strerror(err)); return -1; } } } return 0; }
On Sat, Aug 8, 2009 at 2:16 PM, Jon Smirljonsmirl@gmail.com wrote:
Is there a simple way to test this with something like a mono sine wav file? A aplay mono.wav and then use and ALSA conf to copy the sine to all channels and then sync start them.
I've tried this, but I'm just getting static.
pcm.quad { type multi
slaves.a.pcm "plughw:0,0" slaves.a.channels 2 slaves.b.pcm "plughw:0,1" slaves.b.channels 2
bindings.0.slave a bindings.0.channel 0 bindings.1.slave a bindings.1.channel 1 bindings.2.slave b bindings.2.channel 0 bindings.3.slave b bindings.3.channel 1 }
pcm.quad2 { type route slave.pcm "quad" slave.channels 4 ttable.0.0 1 ttable.1.1 1 ttable.0.2 1 ttable.1.3 1 }
Trace looks ok, haven't figured out what is failing yet.
oot@phyCORE:~ aplay -D quad2 startup.wav mpc5200-psc-i2s f0002000.i2s: psc_dma_open(substream=c3870c00) dspeak01_fabric_startup asoc: pcm1690 <-> i2s-0 info: asoc: rate mask 0x1ee0 asoc: min ch 2 max ch 2 asoc: min rate 32000 max rate 192000 mpc5200-psc-i2s f0002200.i2s: psc_dma_open(substream=c3870d00) dspeak01_fabric_startup asoc: pcm1690 <-> i2s-1 info: asoc: rate mask 0x1ee0 asoc: min ch 2 max ch 2 asoc: min rate 32000 max rate 192000 Playing WAVE 'startup.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo dspeak01_fabric_hw_params mpc5200-psc-i2s f0002000.i2s: psc_i2s_set_sysclk(cpu_dai=c390d540, freq=22579200, dir=1) JDS - clock enable 1 23 pcm1690 0-004c: pcm1690_hw_params(substream=c3870c00, params=c395fc00) pcm1690 0-004c: rate=44100 format=11 mpc5200-psc-i2s f0002000.i2s: psc_i2s_hw_params(substream=c3870c00) p_size=5513 p_bytes=44104 periods=4 buffer_size=22052 buffer_bytes=176416 mpc5200-psc-i2s f0002000.i2s: psc_i2s_hw_params(substream=c3870c00) rate=44100 sysclk=22579200 framesync=64 bitclk=8 reg=3F070000 mpc5200-psc-i2s f0002000.i2s: psc_i2s_hw_params exit dspeak01_fabric_prepare soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_hw_params mpc5200-psc-i2s f0002200.i2s: psc_i2s_set_sysclk(cpu_dai=c390dc00, freq=22579200, dir=1) JDS - clock enable 2 23 pcm1690 0-004c: pcm1690_hw_params(substream=c3870d00, params=c395fc00) pcm1690 0-004c: rate=44100 format=11 mpc5200-psc-i2s f0002200.i2s: psc_i2s_hw_params(substream=c3870d00) p_size=5513 p_bytes=44104 periods=4 buffer_size=22052 buffer_bytes=176416 mpc5200-psc-i2s f0002200.i2s: psc_i2s_hw_params(substream=c3870d00) rate=44100 sysclk=22579200 framesync=64 bitclk=8 reg=3F070000 mpc5200-psc-i2s f0002200.i2s: psc_i2s_hw_params exit dspeak01_fabric_prepare soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_prepare soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_prepare soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_prepare soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_prepare soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) mpc5200-psc-i2s f0002000.i2s: psc_dma_trigger(substream=c3870c00, cmd=1) stream_id=0 mpc5200-psc-i2s f0002200.i2s: psc_dma_trigger(substream=c3870d00, cmd=1) stream_id=0 mpc5200-psc-i2s f0002000.i2s: psc_dma_trigger(substream=c3870c00, cmd=0) stream_id=0 mpc5200-psc-i2s f0002200.i2s: psc_dma_trigger(substream=c3870d00, cmd=0) stream_id=0 dspeak01_fabric_hw_free dspeak01_fabric_hw_free dspeak01_fabric_hw_free pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=1) dspeak01_fabric_shutdown mpc5200-psc-i2s f0002000.i2s: psc_dma_close(substream=c3870c00) dspeak01_fabric_hw_free pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=1) dspeak01_fabric_shutdown mpc5200-psc-i2s f0002200.i2s: psc_dma_close(substream=c3870d00) root@phyCORE:~ pop wq checking: Playback status: inactive waiting: yes soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event
I found the problem causing static. The MCLK was not programmed properly when both channels are active.
How do you do sync stop? or do you do sync stop? I'm getting this error when stopping the streams. The streams have been sync started.
ALSA sound/core/pcm_native.c:1499: playback drain error (DMA or IRQ trouble?)
root@phyCORE:~ aplay -D quad2 startup.wav mpc5200-psc-i2s f0002000.i2s: psc_dma_open(substream=c3862c00) dspeak01_fabric_startup asoc: pcm1690 <-> i2s-0 info: asoc: rate mask 0x1ee0 asoc: min ch 2 max ch 2 asoc: min rate 32000 max rate 192000 mpc5200-psc-i2s f0002200.i2s: psc_dma_open(substream=c3862d00) dspeak01_fabric_startup asoc: pcm1690 <-> i2s-1 info: asoc: rate mask 0x1ee0 asoc: min ch 2 max ch 2 asoc: min rate 32000 max rate 192000 Playing WAVE 'startup.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo dspeak01_fabric_hw_params mpc5200-psc-i2s f0002000.i2s: psc_i2s_set_sysclk(cpu_dai=c390d540, freq=22579200, dir=1, mclk=0) JDS - clock enable 1 23 pcm1690 0-004c: pcm1690_hw_params(substream=c3862c00, params=c396c400) pcm1690 0-004c: rate=44100 format=11 mpc5200-psc-i2s f0002000.i2s: psc_i2s_hw_params(substream=c3862c00) p_size=5513 p_bytes=44104 periods=4 buffer_size=22052 buffer_bytes=176416 mpc5200-psc-i2s f0002000.i2s: psc_i2s_hw_params(substream=c3862c00) rate=44100 sysclk=22579200 framesync=64 bitclk=8 reg=3F070000 mpc5200-psc-i2s f0002000.i2s: psc_i2s_hw_params exit dspeak01_fabric_prepare subdevice #0 soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_hw_params mpc5200-psc-i2s f0002200.i2s: psc_i2s_set_sysclk(cpu_dai=c390dc00, freq=22579200, dir=1, mclk=1) pcm1690 0-004c: pcm1690_hw_params(substream=c3862d00, params=c396c400) pcm1690 0-004c: rate=44100 format=11 mpc5200-psc-i2s f0002200.i2s: psc_i2s_hw_params(substream=c3862d00) p_size=5513 p_bytes=44104 periods=4 buffer_size=22052 buffer_bytes=176416 mpc5200-psc-i2s f0002200.i2s: psc_i2s_hw_params(substream=c3862d00) rate=44100 sysclk=22579200 framesync=64 bitclk=8 reg=3F070000 mpc5200-psc-i2s f0002200.i2s: psc_i2s_hw_params exit dspeak01_fabric_prepare subdevice #0 soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_prepare subdevice #0 soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_prepare subdevice #0 soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_prepare subdevice #0 soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) dspeak01_fabric_prepare subdevice #0 soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=0) mpc5200-psc-i2s f0002000.i2s: psc_dma_trigger(substream=c3862c00, cmd=1) stream_id=0 mpc5200-psc-i2s f0002200.i2s: psc_dma_trigger(substream=c3862c00, cmd=1) stream_id=0 mpc5200-psc-i2s f0002000.i2s: psc_dma_trigger(substream=c3862c00, cmd=0) stream_id=0 mpc5200-psc-i2s f0002200.i2s: psc_dma_trigger(substream=c3862c00, cmd=0) stream_id=0 ALSA sound/core/pcm_native.c:1499: playback drain error (DMA or IRQ trouble?) mpc5200-psc-i2s f0002000.i2s: psc_dma_trigger(substream=c3862d00, cmd=0) stream_id=0 mpc5200-psc-i2s f0002200.i2s: psc_dma_trigger(substream=c3862d00, cmd=0) stream_id=0 dspeak01_fabric_hw_free dspeak01_fabric_hw_free dspeak01_fabric_hw_free pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=1) dspeak01_fabric_shutdown mpc5200-psc-i2s f0002000.i2s: psc_dma_close(substream=c3862c00) dspeak01_fabric_hw_free pcm1690 0-004c: pcm1690_mute(dai=c0403230, mute=1) dspeak01_fabric_shutdown mpc5200-psc-i2s f0002200.i2s: psc_dma_close(substream=c3862d00) root@phyCORE:~ pop wq checking: Playback status: inactive waiting: yes soc-audio soc-audio.1: Setting bias prepare DAPM pcm1690 snd_soc_dapm_stream_event
Here's how I implemented sync start/stop.......
/** * psc_dma_trigger: start and stop the DMA transfer. * * This function is called by ALSA to start, stop, pause, and resume the DMA * transfer of data. */ static int psc_dma_trigger(struct snd_pcm_substream *sub, int cmd) { struct snd_soc_pcm_runtime *rtd; struct psc_dma *psc_dma; struct snd_pcm_runtime *runtime; struct psc_dma_stream *s; struct snd_pcm_substream *substream; struct mpc52xx_psc __iomem *regs; u16 imr; unsigned long flags; int i;
snd_pcm_group_for_each_entry(substream, sub) {
rtd = substream->private_data; psc_dma = rtd->dai->cpu_dai->private_data; runtime = substream->runtime; regs = psc_dma->psc_regs;
dev_dbg(psc_dma->dev, "psc_dma_trigger(substream=%p, cmd=%i)" " stream_id=%i\n", sub, cmd, substream->pstr->stream);
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) s = &psc_dma->capture; else s = &psc_dma->playback;
switch (cmd) { case SNDRV_PCM_TRIGGER_START: s->period_bytes = frames_to_bytes(runtime, runtime->period_size); s->period_start = virt_to_phys(runtime->dma_area); s->period_end = s->period_start + (s->period_bytes * runtime->periods); s->period_next_pt = s->period_start; s->period_current_pt = s->period_start; s->period_size = runtime->period_size; s->active = 1;
/* track appl_ptr so that we have a better chance of detecting * end of stream and not over running it. */ s->runtime = runtime; s->appl_ptr = s->runtime->control->appl_ptr - (runtime->period_size * runtime->periods);
/* Fill up the bestcomm bd queue and enable DMA. * This will begin filling the PSC's fifo. */ spin_lock_irqsave(&psc_dma->lock, flags);
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { bcom_gen_bd_rx_reset(s->bcom_task); for (i = 0; i < runtime->periods; i++) if (!bcom_queue_full(s->bcom_task)) psc_dma_bcom_enqueue_next_buffer(s); } else { bcom_gen_bd_tx_reset(s->bcom_task); psc_dma_bcom_enqueue_tx(s); }
bcom_enable(s->bcom_task); spin_unlock_irqrestore(&psc_dma->lock, flags);
out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT);
break;
case SNDRV_PCM_TRIGGER_STOP: s->active = 0;
spin_lock_irqsave(&psc_dma->lock, flags); bcom_disable(s->bcom_task); if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) bcom_gen_bd_rx_reset(s->bcom_task); else bcom_gen_bd_tx_reset(s->bcom_task); spin_unlock_irqrestore(&psc_dma->lock, flags);
break;
default: dev_dbg(psc_dma->dev, "invalid command\n"); return -EINVAL; } if (sub != substream) snd_pcm_trigger_done(substream, sub);
/* Update interrupt enable settings */ imr = 0; if (psc_dma->playback.active) imr |= MPC52xx_PSC_IMR_TXEMP; if (psc_dma->capture.active) imr |= MPC52xx_PSC_IMR_ORERR; out_be16(®s->isr_imr.imr, psc_dma->imr | imr); } return 0; }
Jon Smirl wrote:
Here's how I implemented sync start/stop.......
Have a look at oxygen_trigger() in sound/pci/oxygen/oxygen_pcm.c for a working sync start/stop implementation.
snd_pcm_group_for_each_entry(substream, sub) {
rtd = substream->private_data;
There is no guarantee the all linked substreams belong to the same card or the same driver; you have to check that first and ignore streams you don't know about.
if (sub != substream) snd_pcm_trigger_done(substream, sub);
You can just call snd_pcm_trigger_done() for every substream you handle.
...
} return 0;
There is no common code for the two substreams. Is your hardware actually capable of starting them at exactly the same time?
Best regards, Clemens
On Mon, Aug 10, 2009 at 3:40 AM, Clemens Ladischclemens@ladisch.de wrote:
Jon Smirl wrote:
Here's how I implemented sync start/stop.......
Have a look at oxygen_trigger() in sound/pci/oxygen/oxygen_pcm.c for a working sync start/stop implementation.
snd_pcm_group_for_each_entry(substream, sub) {
rtd = substream->private_data;
There is no guarantee the all linked substreams belong to the same card or the same driver; you have to check that first and ignore streams you don't know about.
ok
if (sub != substream) snd_pcm_trigger_done(substream, sub);
You can just call snd_pcm_trigger_done() for every substream you handle.
ok
... } return 0;
There is no common code for the two substreams. Is your hardware actually capable of starting them at exactly the same time?
Sync start is working. This is in the loop and starts the streams: bcom_enable(s->bcom_task); s is set to two different values in the loop.
I'm having problems with the drain error on stop.
Best regards, Clemens
Jon Smirl wrote:
On Mon, Aug 10, 2009 at 3:40 AM, Clemens Ladischclemens@ladisch.de wrote:
There is no common code for the two substreams. Is your hardware actually capable of starting them at exactly the same time?
Sync start is working. This is in the loop and starts the streams: bcom_enable(s->bcom_task); s is set to two different values in the loop.
But you are calling bcom_enable() twice, once for each stream. The same would happen if you didn't implement sync start and let ALSA handle the linked streams.
Sync start/stop is intended for hardware that can actually start two streams at _exactly_ the same time, by, e.g., writing a value with two bits set to a register.
I'm having problems with the drain error on stop. ALSA sound/core/pcm_native.c:1499: playback drain error (DMA or IRQ trouble?)
ALSA did not get the snd_pcm_period_elapsed() call (or the correct pointer value) for the last samples.
HTH Clemens
On Mon, Aug 10, 2009 at 7:57 AM, Clemens Ladischclemens@ladisch.de wrote:
Jon Smirl wrote:
On Mon, Aug 10, 2009 at 3:40 AM, Clemens Ladischclemens@ladisch.de wrote:
There is no common code for the two substreams. Is your hardware actually capable of starting them at exactly the same time?
Sync start is working. This is in the loop and starts the streams: bcom_enable(s->bcom_task); s is set to two different values in the loop.
But you are calling bcom_enable() twice, once for each stream. The same would happen if you didn't implement sync start and let ALSA handle the linked streams.
Sync start/stop is intended for hardware that can actually start two streams at _exactly_ the same time, by, e.g., writing a value with two bits set to a register.
The streams are on the same MCLK. I just need to make sure they get enabled in the same clock cycle. To ensure this I need to add some more code to look for a clock edge before enabling them.
If I didn't use sync start wouldn't I be vulnerable to user space getting delayed between the start calls? If I'm unlucky the second start could come in milliseconds after the first one.
I'm having problems with the drain error on stop. ALSA sound/core/pcm_native.c:1499: playback drain error (DMA or IRQ trouble?)
ALSA did not get the snd_pcm_period_elapsed() call (or the correct pointer value) for the last samples.
HTH Clemens
On Mon, Aug 10, 2009 at 8:29 AM, Clemens Ladischclemens@ladisch.de wrote:
Jon Smirl wrote:
If I didn't use sync start wouldn't I be vulnerable to user space getting delayed between the start calls?
Linked substreams are handled by ALSA's kernel part with a loop inside a spinlock.
Linking via snd_soc_dai_link or is there another way to link?
static struct snd_soc_dai_link dspeak01_fabric_dai[] = { { .name = "I2S-0", .stream_name = "I2S-0", .codec_dai = &pcm1690_dai, .ops = &dspeak01_fabric_ops, }, { .name = "I2S-1", .stream_name = "I2S-1", .codec_dai = &pcm1690_dai, .ops = &dspeak01_fabric_ops, }, };
Best regards, Clemens
On Mon, Aug 10, 2009 at 08:41:20AM -0400, Jon Smirl wrote:
On Mon, Aug 10, 2009 at 8:29 AM, Clemens Ladischclemens@ladisch.de wrote:
Linked substreams are handled by ALSA's kernel part with a loop inside a spinlock.
Linking via snd_soc_dai_link or is there another way to link?
That's completely unrelated - each link there just defines a CODEC<->CPU connection.
On Mon, Aug 10, 2009 at 8:43 AM, Mark Brownbroonie@opensource.wolfsonmicro.com wrote:
On Mon, Aug 10, 2009 at 08:41:20AM -0400, Jon Smirl wrote:
On Mon, Aug 10, 2009 at 8:29 AM, Clemens Ladischclemens@ladisch.de wrote:
Linked substreams are handled by ALSA's kernel part with a loop inside a spinlock.
Linking via snd_soc_dai_link or is there another way to link?
That's completely unrelated - each link there just defines a CODEC<->CPU connection.
Does this link them? snd_pcm_set_sync(substream); in open()
and then remove this loop in trigger? snd_pcm_group_for_each_entry(substream, sub) { snd_pcm_trigger_done(substream, sub);
Jon Smirl wrote:
Does this link them? snd_pcm_set_sync(substream); in open()
No; this sets the substream's sync ID which tells userspace applications that the driver actually supports sync start together with other substreams with the same ID.
Linking is done by an application with snd_pcm_link(). The driver does not get a notification of this; it notices this only in the trigger callback, where the loop goes over more than one substream.
and then remove this loop in trigger? snd_pcm_group_for_each_entry(substream, sub) { snd_pcm_trigger_done(substream, sub);
Why would you want to remove this loop?
The snd_pcm_group_for_each_entry() iterates over all substreams that are linked. The ALSA code that calls the trigger callback has a similar loop and calls .trigger for any streams for which snd_pcm_trigger_done() has not yet been called.
HTH Clemens
participants (3)
-
Clemens Ladisch
-
Jon Smirl
-
Mark Brown