[alsa-devel] [PATCH] ASoC: Add new TI TLV320AIC3204 CODEC driver
Mark Brown
broonie at opensource.wolfsonmicro.com
Sat Jun 19 12:57:07 CEST 2010
On Sat, Jun 19, 2010 at 07:49:08PM +1000, Stuart Longland wrote:
> On Sat, Jun 19, 2010 at 02:12:21AM +0100, Mark Brown wrote:
> > On Sat, Jun 19, 2010 at 08:24:36AM +1000, Stuart Longland wrote:
> > > + /* LDO Enable */
> > > + u8 ldo_en;
> > Similarly here.
> Well, I didn't see the point in allocating 32-bits when I'd only be
> using 1 of them. However, I can change it to an int if need be. I'm
> not certain that C has a true "bool" type, but I could be wrong.
C99 introduced a bool type, and there's always been bitfields. Besides,
you're microoptimising something that doesn't need it. When you write
u8 that means you really need this field to be unsigned and 8 bits which
is a bit confusing for something that's actually just a flag.
> > > + /* Oversampling rate (both ADC and DAC) */
> > > + u8 osr;
> > Why is this platform data rather than a runtime configurable option?
> How would the application elect a particular oversampling rate? I'll
> admit I'm very new to the ALSA framework in general, but I didn't
> realise there was an oversampling rate setting. I'll have a look at how
> the other drivers do it.
The oversampling rate selection is a performance/power tradeoff. The
user may choose to do things like use high performance when listening to
music but step back a bit when playing system sounds, for example.
There's no specific ALSA control for it, just make it a Switch.
> > > +/*
> > > + * Pretty-print the value of a register
> > > + */
> > > +static int aic3204_show_reg(struct snd_soc_codec *codec, char *buf,
> > > + int buf_sz, unsigned int reg)
> > > +{
> > > + return snprintf(buf, buf_sz, "%02x",
> > > + aic3204_read_reg_cache(codec, reg));
> > > +}
> > This looks suspiciously close to the standard show_reg() - it seems
> > wrong that you should need it.
> Well, the main difference; standard show_reg uses %4x as its format; and
> therefore wastes two characters printing nothing useful. Given space is
> already tight for displaying register dumps via debugfs, any little bit
> helps. :-)
> Certainly there's no explicit reason why it is necessary and it can go.
If you want to do this it'd be better to add support to the core, for
example by keying off a register width supplied by the CODEC.
> > > + /*
> > > + * These registers are not manipulated by us, so we must read them from
> > > + * the CODEC directly.
> > > + */
> > Hrm? Also, it strikes me that this code is also used in the WCLK
> > function and might benefit from being factored out - it's moderately
> > verbose.
> The ADC flags register (and the DAC flags registers) are read-only and
> manipulated by the CODEC when the ADCs and DACs are actually powered up.
> I could use the flags set elsewhere, but this reflects what *is* the
> present state, not what ought to be the present state.
This strikes me as something that's going to hit a race condition at
some point - if the two differ I'd hope that the state the driver has
set is going to be the actual CODEC state very soon.
> There is common code between the WCLK and BCLK functions; in fact I did
> consider making it the one callback, but since they are separate in the
> registers, I kept them separate in the callbacks.
I was thinking more a utility function that both callbacks use to work
out which of the DACs and ADCs are powered.
> Now that the mixer is working okay, I could cut this down... but also I
> hope that others who follow the same path as me, might find this helpful
> to determine what's going on.
If we're going to do that we'd need to be doing it for every single TLV
control in every single driver...
> Well, when the problem goes, so can the comment. :-) One question
> though, the shift value derives the mask for further operations on these
> registers... if we were to change the shift value so it reflected how
> other widget types work, how does one define the mask?
I'd intuitively expect it to be relative to the start of the controlled
data rather than the start of the register.
> > > + SOC_SINGLE("Differential Output Switch", AIC3204_DACS2,
> > > + AIC3204_DACS2_RMOD_INV_SHIFT, 1, 0),
> > This feels like platform data - the use of differential outputs is
> > normally a property of the physical wiring of the board, it's very rare
> > to be able to vary this usefully at runtime.
> Arguably, so is the DAC audio path routing. I wasn't sure how to set
> this up at the time, initially it was CODEC setup data, then I moved it
> into the mixer.
The DAC routing can be usefully varied at runtime depending on the use
case - for example, sometimes people choose to route a mono signal into
both DACs to give stereo output. It would be very unusual hardware that
provided a way of switching between single ended and differential in a
similar fashion.
> The other consideration here; is the use case where a device with a mono
> speaker, but a stereo line-out jack... I wasn't sure how to handle these
> features.
Presumably you can make individual outputs differential and single ended
then?
> > > + SOC_ENUM("MICBIAS Level", aic3204_enum_micbias_voltage),
> > > + SOC_ENUM("MICBIAS Source", aic3204_enum_micbias_src),
> > These I would expect to be managed in kernel - they're normally either a
> > property of the board or need to be managed in conjunction with jack
> > detection code which tends to live in kernel.
> I'll look into this... I was aware of the MICBIAS widget, but will have
> to do some further research here. As to the others... I wasn't sure how
> they should be handled.
Platform data initially then provide a runtime API for machine drivers
if requireed.
> > > +static const struct snd_soc_dapm_widget aic3204_dapm_widgets[] = {
> > > + /* Driver/Amplifier Selection */
> > > + SND_SOC_DAPM_SWITCH("HPL Playback Switch", AIC3204_OUTDRV,
> > > + AIC3204_OUTDRV_HPL_UP_SHIFT, 0,
> > > + &aic3204_hpl_switch),
> > > + SND_SOC_DAPM_SWITCH("HPR Playback Switch", AIC3204_OUTDRV,
> > > + AIC3204_OUTDRV_HPR_UP_SHIFT, 0,
> > > + &aic3204_hpr_switch),
> > A lot of these SWITCH controls feel like they ought to be PGAs and the
> > switch controls mutes on those. When muting an output you normally
> > don't want to power up and down the path since that is slow and tends to
> > trigger audible issues, you'd normally control the power for path
> > endpoints and elements that affect routing only.
> They are PGAs connecting the driver to its mixer... I wasn't sure how
> the PGAs worked in DAPM. I knew this did work however, the only catch
These should definitely be PGA widgets as described above.
> is that DAPM won't power up the DACs unless you switch them through via
> these mixers to one of the output drivers. I'll have a closer look at
> the PGAs and see what they're doing.
Not powering up the DACs unless there is a connected output path is the
expected behaviour.
> > It surprises me that the ordering matters too much here; worst case you
> > leave the interface declocked a little longer when you need to switch
> > sources but that doesn't seem like the end of the world?
> Well, what I found is that if I didn't do it there... it would call the
> WCLK and BCLK functions before powering up the DACs/ADCs. Since they
> have no idea whether recording or playback is in progress (I wasn't sure
> how to extract this information), the functions rely on the flags
> registers to determine this.
If you track this in the audio path setup/teardown path instead of
looking at the DAC power status you should find everything works fine.
> > > +static int aic3204_mute(struct snd_soc_dai *dai, int mute)
> > > +{
> > ...
> > > + aic3204_write(codec, AIC3204_DACS2, dacs2); /* Unmute DAC */
> > ...or possibly mute it :)
> Yes, obsolete comment I guess. :-) This is also a mixer control...
> which is better... leave it to ASoC core, or the user to control PCM
> mute? The user can mute using the line driver controls... so I guess I
> should ditch the "PCM Playback Switch" control.
Remove the mixer control. The automatic management ensures that no
noise that is generated when starting up the audio stream propagates
into the output path and that any soft unmute support in the CODEC gets
used to stop you having a hard power up with an active signal.
> > > + for (i = 0; i < AIC3204_CACHEREGNUM; i++) {
> > > + data[0] = i;
> > > + data[1] = cache[i];
> > > + codec->hw_write(codec->control_data, data, 2);
> > Since you've got a register defaults table you could skip rewriting
> > registers which have their default value.
> Indeed, suspend/resume isn't tested at all, so no idea if it works or
> not. I should probably do a forced reset too just to be on the safe
> side. This is a reminant from the aic3x driver.
The reset should not be required, either the device will be as you left
it or the power will have gone so it will be in the reset state anyway.
> > > + /* LDO enable, analogue blocks enable */
> > > + snd_soc_update_bits(codec, AIC3204_LDO,
> > > + AIC3204_LDO_ANALOG | AIC3204_LDO_AVDD_UP,
> > > + AIC3204_LDO_ANALOG_ENABLED |
> > > + (aic3204->pdata.ldo_en
> > > + ? AIC3204_LDO_AVDD_UP : 0));
> > This looks a bit fishy since the mask covers more bits than can ever be
> > enabled - it reads like the other two bits should be unconditionally
> > cleared.
> Would I be better breaking that statement up into two statements? I did
Yes, it would be much more legible.
> I wasn't sure about the link between AVDD and DVDD, so I left that
> configurable. I'm guessing most will probably want it disabled. This
> was configured using arbitrary register initialisation scripts, passed
> in via the CODEC setup data... so at least things are headded in the
> right direction. :-)
What does this register actually do? Does it describe the hardware
configuration to the device?
More information about the Alsa-devel
mailing list