[alsa-devel] ASOC, master clock direction
The MPC5200 has three clock configurations
1) Clock out, normal master mode 2) Clock in, normal slave mode 3) Something Freescale calls cellphone slave but it is more like clock distribution.
In cellslave mode one i2s port is in normal slave mode. The clock from this port is used to drive up to three other i2s ports in master mode.
I have two choices for set_sysclk: #define SND_SOC_CLOCK_IN 0 #define SND_SOC_CLOCK_OUT 1
When the clock frequency changes I need to notify the i2s drivers for the dependent channels. Do we need a third choice, SND_SOC_CLOCK_SLAVE? Alternatively I can use another clock id value.
On 7/18/08, Jon Smirl jonsmirl@gmail.com wrote:
The MPC5200 has three clock configurations
- Clock out, normal master mode
- Clock in, normal slave mode
- Something Freescale calls cellphone slave but it is more like clock
distribution.
In cellslave mode one i2s port is in normal slave mode. The clock from this port is used to drive up to three other i2s ports in master mode.
I have two choices for set_sysclk: #define SND_SOC_CLOCK_IN 0 #define SND_SOC_CLOCK_OUT 1
When the clock frequency changes I need to notify the i2s drivers for the dependent channels. Do we need a third choice, SND_SOC_CLOCK_SLAVE? Alternatively I can use another clock id value.
I can implement it like this, in cellslave mode I just record the frequency, CLOCK_OUT not in cellslave mode needs to set up the internal MPC clock generation (which can't make accurate audio clocks).
static int psc_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { struct psc_i2s *psc_i2s = cpu_dai->private_data; dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, freq=%ui, dir=%i)\n", cpu_dai, freq, dir); switch (dir) { case SND_SOC_CLOCK_IN: return 0; case SND_SOC_CLOCK_OUT: default: if (!(psc_i2s->sicr & MPC52xx_PSC_SICR_CELLSLAVE)) { /* implement me */ /* compute and set the needed dividers for the internal clock */ return -EINVAL; } psc_i2s->sysclk = freq; return -EINVAL; } }
static int psc_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; u32 sicr, rate, bits, bitblk, err, wordclk;
dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i" " periods=%i buffer_size=%i buffer_bytes=%i\n", __FUNCTION__, substream, params_period_size(params), params_period_bytes(params), params_periods(params), params_buffer_size(params), params_buffer_bytes(params));
sicr = psc_i2s->sicr; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: sicr |= MPC52xx_PSC_SICR_SIM_CODEC_8; bits = 8; break; case SNDRV_PCM_FORMAT_S16_BE: sicr |= MPC52xx_PSC_SICR_SIM_CODEC_16; bits = 16; break; case SNDRV_PCM_FORMAT_S24_BE: sicr |= MPC52xx_PSC_SICR_SIM_CODEC_24; bits = 24; break; case SNDRV_PCM_FORMAT_S32_BE: sicr |= MPC52xx_PSC_SICR_SIM_CODEC_32; bits = 32; break; default: dev_dbg(psc_i2s->dev, "invalid format\n"); return -EINVAL; } out_be32(&psc_i2s->psc_regs->sicr, sicr); if (psc_i2s->sysclk) { switch (params_rate(params)) { case SNDRV_PCM_RATE_5512: rate = 5512; break; case SNDRV_PCM_RATE_8000: rate = 8000; break; case SNDRV_PCM_RATE_11025: rate = 11025; break; case SNDRV_PCM_RATE_16000: rate = 16000; break; case SNDRV_PCM_RATE_22050: rate = 22050; break; case SNDRV_PCM_RATE_32000: rate = 32000; break; case SNDRV_PCM_RATE_44100: rate = 44100; break; case SNDRV_PCM_RATE_48000: rate = 48000; break; case SNDRV_PCM_RATE_64000: rate = 64000; break; case SNDRV_PCM_RATE_88200: rate = 88200; break; case SNDRV_PCM_RATE_96000: rate = 96000; break; case SNDRV_PCM_RATE_176400: rate = 176400; break; case SNDRV_PCM_RATE_192000: rate = 192000; break; default: dev_dbg(psc_i2s->dev, "invalid format\n"); return -EINVAL; } bitclk = psc_i2s->sysclk / rate; wordclk = bitclk / bits; out_be32(&psc_i2s->psc_regs->ccr, (bitclk - 1) << 24 | (wordclk << 16); }
//rc = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); //if (rc) { // dev_err(psc_i2s->dev, "could not allocate dma buffer\n"); // return rc; //}
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
return 0; }
-- Jon Smirl jonsmirl@gmail.com
On Fri, Jul 18, 2008 at 03:44:37PM -0400, Jon Smirl wrote:
On 7/18/08, Jon Smirl jonsmirl@gmail.com wrote:
- Something Freescale calls cellphone slave but it is more like clock
distribution.
In cellslave mode one i2s port is in normal slave mode. The clock from this port is used to drive up to three other i2s ports in master mode.
...
When the clock frequency changes I need to notify the i2s drivers for the dependent channels. Do we need a third choice, SND_SOC_CLOCK_SLAVE? Alternatively I can use another clock id value.
Without having looked at how this works in hardware I'd probably implement this by using a different clock id for the slave ports if I wanted to use set_sysclk() to configure cell slave mode. It sounds like you've got to choose between two sources within the SoC, one being that clocked in from the external source and the other regular clocks. From what you're saying as far as the individual I2S port is concerned it is the master in both cases.
switch (dir) { case SND_SOC_CLOCK_IN: return 0; case SND_SOC_CLOCK_OUT: default: if (!(psc_i2s->sicr & MPC52xx_PSC_SICR_CELLSLAVE)) { /* implement me */ /* compute and set the needed dividers for the internal clock */ return -EINVAL; } psc_i2s->sysclk = freq; return -EINVAL; } }
If you did use a separate clock id then you could set or clear MPC52xx_PSC_SICR_CELLSLAVE as appropriate here which might be a bit easier to write. It depends on what's clearer and what work is needed to configure the slave mode - I guess there may be other configuration required?
Having said that the important thing here is that what you do is sensible and idiomatic for people working on the platform so it may be that what you've got already is the most expressive thing. The clock configuration is done by machine drivers which will obviously be platform specific.
On 7/18/08, Mark Brown broonie@opensource.wolfsonmicro.com wrote:
On Fri, Jul 18, 2008 at 03:44:37PM -0400, Jon Smirl wrote:
On 7/18/08, Jon Smirl jonsmirl@gmail.com wrote:
- Something Freescale calls cellphone slave but it is more like clock
distribution.
In cellslave mode one i2s port is in normal slave mode. The clock from this port is used to drive up to three other i2s ports in master mode.
...
When the clock frequency changes I need to notify the i2s drivers for the dependent channels. Do we need a third choice, SND_SOC_CLOCK_SLAVE? Alternatively I can use another clock id value.
Without having looked at how this works in hardware I'd probably implement this by using a different clock id for the slave ports if I wanted to use set_sysclk() to configure cell slave mode. It sounds like you've got to choose between two sources within the SoC, one being that clocked in from the external source and the other regular clocks. From what you're saying as far as the individual I2S port is concerned it is the master in both cases.
That might work better. Currently I was setting cellslave mode at boot time from the device tree. But by using different clock ids it can be set dynamically.
switch (dir) { case SND_SOC_CLOCK_IN: return 0; case SND_SOC_CLOCK_OUT: default: if (!(psc_i2s->sicr & MPC52xx_PSC_SICR_CELLSLAVE)) { /* implement me */ /* compute and set the needed dividers for the internal clock */ return -EINVAL; } psc_i2s->sysclk = freq; return -EINVAL; }
}
If you did use a separate clock id then you could set or clear MPC52xx_PSC_SICR_CELLSLAVE as appropriate here which might be a bit easier to write. It depends on what's clearer and what work is needed to configure the slave mode - I guess there may be other configuration required?
Having said that the important thing here is that what you do is sensible and idiomatic for people working on the platform so it may be that what you've got already is the most expressive thing. The clock configuration is done by machine drivers which will obviously be platform specific.
participants (2)
-
Jon Smirl
-
Mark Brown