[alsa-devel] SoC Machine Code Suggestions
First, thanks to all for helping me get the at91 soc stuff working with my board and codec. It now plays telecom quality audio using aplay and records it as well using arecord. For those who were following my trials and did not hear what the solution was, essentially, when the at91-ssc code goes into playback mode, it only activates the TX side of the SSC (PCM) bus. Similarly, when it is in capture mode, it only activates the RX side of the bus. This certainly makes sense and works in most situations I am sure. However, I needed both the RX and TX frames operating in both playback and capture. As such, I had to modify at91-ssc slightly to accomplish this.
Now that I have decent audio, I want to clean up around the edges of the audio and determine the best way to proceed with my application code.
First. when the pcm interface starts/stops, I get a pop that comes from the codec analog output transitioning some. Now, there may be some hardware fixes to this, but what I would like to do it mute the amp until the last possible second before audio is played. I can accomplish this with a simple gpio call, but am not sure where best to turn the mute on and off. Any suggestions with that would be appreciated.
Also, my next phase of development is to determine whether I need to write my own application to accomplish what I want to do, or if there is an application out there that will do the trick--and that I can cross-compile to an ARM. What I would like is an application I will stream audio (half-duplex) between two ARM boards connected via ethernet. I would need to be able to control the start/stop of the audio. Sometimes this control will depend on the state of the machine and other times it will be activate with a button press by the user. I hope that makes sense. Obviously it would have to seamlessly stream to the codec via ALSA. Also, there may be times when it streams to a pc. Now, the codec uses 8-bit ulaw, so this code if it were on a pc, would need to be able to play that on the pc in its native state or some type of conversion on the fly.
I guess you can think of the application as similar to voip, but half duplex instead of full duplex. Does anyone know if something like this exists that I might use. If not, how hard would it be to code this for ALSA?
Thanks again and I look forward to your responses.
Paul
On Wed, 2007-06-27 at 10:47 -0500, Paul Kavan wrote:
First. when the pcm interface starts/stops, I get a pop that comes from the codec analog output transitioning some.
Does your codec have a digital mute ?
Fwiw, you may want read the application notes in the codec datasheet for minimising any pops and clicks.
It's also sometimes useful to minimise any output gains at startup and then ramp them up to the desired volume before playback starts on some devices.
Now, there may be some hardware fixes to this, but what I would like to do it mute the amp until the last possible second before audio is played. I can accomplish this with a simple gpio call, but am not sure where best to turn the mute on and off. Any suggestions with that would be appreciated.
Turning the amp on and off will probably also cause a pop. It's just a matter of finding what pops the least. You could add your GPIO amp code to the machine driver say in startup/shutdown, but please bear in mind the amp may take a little time to get up and running.
Liam
On 6/27/07, Liam Girdwood lg@opensource.wolfsonmicro.com wrote:
On Wed, 2007-06-27 at 10:47 -0500, Paul Kavan wrote:
First. when the pcm interface starts/stops, I get a pop that comes from
the
codec analog output transitioning some.
Does your codec have a digital mute ?
It has a digital power down/up line. I suppose I could strap a gpio to it and then turn the codec on first...wait an sec....and then turn the amp on. Then on shutdown, do the reverse. However....the more I think....it also has auto shutdown once any of the 5 clocks is off. So knowing where the ssc starts and stops clocking would be helpful. I could then turn my amp on and off around those clock start/stops.
Fwiw, you may want read the application notes in the codec datasheet for
minimising any pops and clicks.
It's also sometimes useful to minimise any output gains at startup and then ramp them up to the desired volume before playback starts on some devices.
Now, there may be some hardware fixes to this, but what I would like to do it mute the amp until the
last
possible second before audio is played. I can accomplish this with a
simple
gpio call, but am not sure where best to turn the mute on and off. Any suggestions with that would be appreciated.
Turning the amp on and off will probably also cause a pop. It's just a matter of finding what pops the least. You could add your GPIO amp code to the machine driver say in startup/shutdown, but please bear in mind the amp may take a little time to get up and running.
There is a cap on the amp mute that is designed to bring it up and down slower. So the amp does not pop much when it is brought in and out of mute.
I am thinking it would be best to do the muting around the ssc clocking start/stops. I thought I understood where those are in code, but it is not seeming to work that way.
For startup, I was trying the following in at91-ssc.c:
static int at91_ssc_prepare(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; struct at91_pcm_dma_params *dma_params; int dir, i;
dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
dma_params = ssc_p->dma_params[dir];
at91_ssc_write(dma_params->ssc_base + AT91_SSC_CR, dma_params->mask->ssc_enable);
/* Check direction. If playback, unmute the amp */ if(dir==0) at91_set_gpio_value(AT91_PIN_PB8,1);
DBG("%s enabled SSC_SR=0x%08lx\n", dir ? "receive" : "transmit", at91_ssc_read(dma_params->ssc_base + AT91_SSC_SR)); return 0;
}
For shutdown, in at91-ssc.c I was trying:
/* * Shutdown. Clear DMA parameters and shutdown the SSC if there * are no other substreams open. */ static void at91_ssc_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; struct at91_pcm_dma_params *dma_params; int dir, dir_mask;
/*Ensure the amp is muted*/ at91_set_gpio_value(AT91_PIN_PB8,0);
dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; dma_params = ssc_p->dma_params[dir];
if (dma_params != NULL) { at91_ssc_write(dma_params->ssc_base + AT91_SSC_CR, dma_params->mask->ssc_disable); DBG("%s disabled SSC_SR=0x%08lx\n", (dir ? "receive" : "transmit"), at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR));
dma_params->ssc_base = NULL; dma_params->substream = NULL; ssc_p->dma_params[dir] = NULL; }
dir_mask = 1 << dir;
spin_lock_irq(&ssc_p->lock); ssc_p->dir_mask &= ~dir_mask; if (!ssc_p->dir_mask) { /* Shutdown the SSC clock. */ DBG("Stopping pid %d clock\n", ssc_p->ssc.pid); at91_sys_write(AT91_PMC_PCDR, 1<<ssc_p->ssc.pid);
if (ssc_p->initialized) { free_irq(ssc_p->ssc.pid, ssc_p); ssc_p->initialized = 0; }
/* Reset the SSC */ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR, AT91_SSC_SWRST);
/* Clear the SSC dividers */ ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0; } spin_unlock_irq(&ssc_p->lock); }
/* * Record the SSC system clock rate. */ static int at91_ssc_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { /* * The only clock supplied to the SSC is the AT91 master clock, * which is only used if the SSC is generating BCLK and/or * LRC clocks. */ switch (clk_id) { case AT91_SYSCLK_MCK: at91_ssc_sysclk = freq; break; default: return -EINVAL; }
return 0; }
/* * Record the DAI format for use in hw_params(). */ static int at91_ssc_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int fmt) { struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
ssc_p->daifmt = fmt; return 0; }
/* * Record SSC clock dividers for use in hw_params(). */ static int at91_ssc_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai, int div_id, int div) { struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
switch (div_id) { case AT91SSC_CMR_DIV: /* * The same master clock divider is used for both * transmit and receive, so if a value has already * been set, it must match this value. */ if (ssc_p->cmr_div == 0) ssc_p->cmr_div = div; else if (div != ssc_p->cmr_div) return -EBUSY; break;
case AT91SSC_TCMR_PERIOD: ssc_p->tcmr_period = div; break;
case AT91SSC_RCMR_PERIOD: ssc_p->rcmr_period = div; break;
default: return -EINVAL; }
return 0; }
This does not seem to work. Any ideas why?
Paul
participants (2)
-
Liam Girdwood
-
Paul Kavan