[alsa-devel] [PATCH] ASoC: Add support for Cirrus Logic CS42L52 LowpowerStereo Codec
Austin, Brian
Brian.Austin at cirrus.com
Fri Feb 26 02:59:29 CET 2010
On Feb 25, 2010, at 6:53 PM, "Mark Brown" <broonie at opensource.wolfsonmicro.com
> wrote:
> On Thu, Feb 25, 2010 at 06:07:35PM -0600, Brian wrote:
>
> This is a quick first pass review only. It'd be best if you could
> include a Signed-off-by line in submissions so that they can be merged
> if they're OK, even if you expect problems you may be surprised!
>
> Overall this looks fairly good - there's a lot of comments below but
> the
> vast majority of them are stylistic issues or needing to update to
> current APIs rather than major structural problems with the code. The
> main thing I don't really follow is the register access code, there's
> quite a bit of stuff going on in there.
>
>> + * published by the Free Software Foundation.
>> + * Revision history
>> + * Nov 2007 Initial version.
>> + * Oct 2008 Updated to 2.6.26
>> + * Feb 2010 Updated to latest asoc git tree
>
> Please remove the changelog, once things are merged git takes care of
> that.
>
>> + */
>> +#define DEBUG
>
> Not by default in mainline :)
>
>> +#ifdef DEBUG
>> +#define SOCDBG(fmt, arg...) printk(KERN_ERR "%s: %s() " fmt,
>> SOC_CS42L52_NAME, __FUNCTION__, ##arg)
>> +#else
>> +#define SOCDBG(fmt, arg...)
>> +#endif
>> +#define SOCINF(fmt, args...) printk(KERN_INFO "%s: " fmt,
>> SOC_CS42L52_NAME, ##args)
>> +#define SOCERR(fmt, args...) printk(KERN_ERR "%s: " fmt,
>> SOC_CS42L52_NAME, ##args)
>
> Please convert the driver to use the standard dev_ and pr_ macros for
> this (where possible the dev_ ones are better).
>
>> +static int soc_cs42l52_set_bias_level(struct snd_soc_codec *codec,
>> + enum snd_soc_bias_level level);
>> +static int soc_cs42l52_pcm_hw_params(struct snd_pcm_substream
>> *substream,
>> + struct snd_pcm_hw_params *params,
>> + struct snd_soc_dai *dai);
>> +static int soc_cs42l52_set_sysclk(struct snd_soc_dai *codec_dai,
>> + int clk_id, u_int freq, int dir);
>> +static int soc_cs42l52_digital_mute(struct snd_soc_dai *dai, int
>> mute);
>> +static int soc_cs42l52_set_fmt(struct snd_soc_dai *codec_dai,
>> + u_int fmt);
>> +
>> +static unsigned int soc_cs42l52_read(struct snd_soc_codec *codec,
>> + u_int reg);
>
> Please use 'unsigned int' rather than 'u_int' for consistency with the
> rest of ASoC. It's a bit surprising that you need the forward
> declarations at all but they do no harm.
>
>> +/**
>> + * snd_soc_get_volsw - single mixer get callback
>> + * @kcontrol: mixer control
>> + * @uinfo: control element information
>> + *
>> + * Callback to get the value of a single mixer control.
>> + *
>> + * Returns 0 for success.
>> + */
>> +int snd_soc_cs42l5x_get_volsw(struct snd_kcontrol *kcontrol,
>> + struct snd_ctl_elem_value *ucontrol)
>> +{
>> + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
>> + int reg = kcontrol->private_value & 0xff;
>
> The indentation looks wrong - should be tabs for indenting the code
> blocks. scripts/checkpatch.pl is a pretty good check for this sort of
> stuff, I'll not mention other issues.
>
>> + int shift = (kcontrol->private_value >> 8) & 0x0f;
>> + int rshift = (kcontrol->private_value >> 12) & 0x0f;
>> + int max = (kcontrol->private_value >> 16) & 0xff;
>> + int mask = (1 << fls(max)) - 1;
>> + int min = (kcontrol->private_value >> 24) & 0xff;
>> +
>> + ucontrol->value.integer.value[0] =
>> + ((snd_soc_read(codec, reg) >> shift) - min) & mask;
>> + if (shift != rshift)
>> + ucontrol->value.integer.value[1] =
>> + ((snd_soc_read(codec, reg) >> rshift) - min) & mask;
>
> This and most of the other custom control stuff looks an awful lot
> like
> it should be done by adding a SND_SOC_DOUBLE_SIGNED() or something
> along
> the lines of the existing _S8() but I didn't read entirely closely. I
> was speaking to someone on IRC only the other day who needed some of
> those types for a different device.
>
> These should also be updated to use struct soc_mixer_control for the
> private value rather than shifting everything into the private
> value, it
> allows wider ranges and is less error prone.
>
>> +static const u8 soc_cs42l52_reg_default[] = {
>> + 0x00, 0xE0, 0x01, 0x07, 0x05, /*4*/
>> + 0xa0, 0x00, 0x00, 0x81, /*8*/
>
> Very nitpicky but you're using a mix of upper and lower case for the
> letters in the hex digits.
>
>> +static inline int soc_cs42l52_read_reg_cache(struct snd_soc_codec
>> *codec,
>> + u_int reg)
>> +{
>> + u8 *cache = codec->reg_cache;
>> +
>> + return reg > SOC_CS42L52_REG_NUM ? -EINVAL : cache[reg];
>> +}
>
> You should be able to replace all your register I/O code with use of
> soc-cache.c with 8 bit data, 8 bit register. Except...
>
>> + if(info->flags & SOC_CS42L52_ALL_IN_ONE)
>> + {
>> + for(i = 0; i < codec->num_dai; i++)
>> + {
>> + if(info->machine_handler)
>> + info->machine_handler(i);
>
> ...I'm not sure what's going on here but it could really use an
> explanation? I'm not sure what ALL_IN_ONE is supposed to do, or what
> the machine_handler() function is for.
>
>> + if(info->flags & SOC_CS42L52_CHIP_SWICTH)
>> + {
>> + num = info->flags & SOC_CS42L52_CHIP_MASK;
>> + if(info->machine_handler)
>> + info->machine_handler(num);
>> + }
>
> This too. There's a lot of unusual code using this machine handler
> function. I suspect this can all use ASoC standard stuff.
>
>> +static inline int soc_cs42l52_get_revison(struct snd_soc_codec
>> *codec)
>> +{
>> + u8 data;
>> + u8 addr;
>> + int num, ret = 0;
>
>> + struct soc_codec_cs42l52 *info = (struct soc_codec_cs42l52*)
>> codec->private_data;
>
> No need to cast away from void (quite a few examples of this in the
> code).
>
>> + return ret < 0 ? ret : data;
>
> I'm not a big fan of the ternery operator...
>
>> +static unsigned int soc_cs42l52_read(struct snd_soc_codec *codec,
>> + u_int reg)
>> +{
>> + u8 data;
>> + u8 addr;
>> + int i, ret = 0;
>> + struct soc_codec_cs42l52 *info = (struct
>> soc_codec_cs42l52*)codec->private_data;
>> +#ifndef CONFIG_CS42L52_DEBUG
>> + if(reg == CODEC_CS42L52_SPK_STATUS)
>> + {
>> +#endif
>
> This really shouldn't be ifdefed. If you need to handle registers
> that
> report status from the device with the soc-cache stuff use the
> volatile_register() callback in the CODEC, any registers that need a
> CODEC read will call through to the hardware rather than using the
> cache.
>
>> +static const char *cs42l52_adc_mux[] = {"AIN1", "AIN2", "AIN3",
>> "AIN4", "PGA"};
>> +static const char *cs42l52_mic_mux[] = {"MIC1", "MIC2"};
>> +static const char *cs42l52_stereo_mux[] = {"Mono", "Stereo"};
>> +static const char *cs42l52_off[] = {"On", "Off"};
>> +static const char *cs42l52_hpmux[] = {"Off", "On"};
>> +
>> +static const struct soc_enum soc_cs42l52_enum[] = {
>> +SOC_ENUM_DOUBLE(CODEC_CS42L52_ANALOG_HPF_CTL, 4, 6, 2,
>> cs42l52_hpf_freeze), /*0*/
>> +SOC_ENUM_SINGLE(CODEC_CS42L52_ADC_HPF_FREQ, 0, 4,
>> cs42l52_hpf_corner_freq),
>
> Don't use an array of enums, define individual variables for them.
> Some
> drivers use the arrays for historical reasons - they're quite
> painful to
> work with if you ever want to change anything in the middle and we've
> found a few off by one errors in the referencing into the arrays
> before
> :/.
>
>> +static const struct snd_kcontrol_new soc_cs42l52_controls[] = {
>> +
>> +SOC_ENUM("Mic VA Capture Switch", soc_cs42l52_enum[18]), /*0*/
>
> enums shouldn't be called Switch - Switch has a specific meaning to
> ALSA
> (see Documentation/sound/alsa/ControlNames.txt, there's quite a few
> other controls could do with having the components of the name
> reordered
> and various other things following that). Normally just omit the '
> Switch'
> from the name.
>
>> +SOC_DOUBLE("Analog SR Capture Switch",
>> CODEC_CS42L52_ANALOG_HPF_CTL, 1, 3, 1, 1),
>> +SOC_DOUBLE("Analog ZC Capture Switch",
>> CODEC_CS42L52_ANALOG_HPF_CTL, 0, 2, 1, 1),
>
> This'd normally be 'Capture ZC Switch' if it's for zero cross.
>
>> +SOC_ENUM("HPF corner freq Capture Switch", soc_cs42l52_enum[1]), /
>> *5*/
>> +
>> +SOC_SINGLE("Ganged Ctl Capture Switch",
>> CODEC_CS42L52_ADC_MISC_CTL, 7, 1, 1), /* should be enabled init */
>
> What does the comment mean here?
>
>> +SOC_SINGLE("HP Analog Gain Playback Volume",
>> CODEC_CS42L52_PB_CTL1, 5, 7, 0),
>
> Just "Headphone Volume"?
>
>> +SOC_SINGLE("Playback B=A Volume Playback Switch",
>> CODEC_CS42L52_PB_CTL1, 4, 1, 0), /*10*/ /*should be enabled init*/
>
> If this is the mute for the headphone volume it should have the same
> name but with Switch instead of Volume so applications like alsamixer
> can display the two together.
>
>> +static int soc_cs42l52_add_controls(struct snd_soc_codec *codec)
>> +{
>> + int i,ret = 0;
>> +
>> + for(i = 0; i < ARRAY_SIZE(soc_cs42l52_controls); i++)
>> + {
>> + ret = snd_ctl_add(codec->card,
>> + snd_soc_cnew(&soc_cs42l52_controls[i], codec, NULL));
>> + if(ret < 0)
>> + {
>> + SOCDBG("add cs42l52 controls failed\n");
>> + break;
>
> Replace this with snd_soc_add_controls().
>
>> + /* Sum switches */
>> + SND_SOC_DAPM_PGA("AIN1A Switch", CODEC_CS42L52_ADC_PGA_A, 0,
>> 0, NULL, 0),
>
> Without having checked the datasheet I *suspect* most of these
> switches
> should be modelled as inputs to a mixer (possibly one with no power
> control of its own)? The "Switch" in the name certainly looks very
> out
> of place in the name of a DAPM control.
>
>> + /* Output path */
>> + SND_SOC_DAPM_MUX("Passthrough Left Playback Switch",
>> SND_SOC_NOPM, 0, 0, &cs42l52_hpa_mux),
>
> Normally "Bypass" instead of "Passthrough".
>
>> + /* Mic Bias */
>> + {"Mic Bias Capture Switch", "On", "PGA MICA"},
>> + {"Mic Bias Capture Switch", "On", "PGA MICB"},
>> + {"Mic-Bias", NULL, "Mic Bias Capture Switch"},
>> + {"Mic-Bias", NULL, "Mic Bias Capture Switch"},
>> + {"ADC Mux Left Capture Switch", "PGA", "Mic-Bias"},
>> + {"ADC Mux Right Capture Switch", "PGA", "Mic-Bias"},
>> + {"Passthrough Left Playback Switch", "On", "Mic-Bias"},
>> + {"Passthrough Right Playback Switch", "On", "Mic-Bias"},
>
> This looks odd - normally the microphone bias would be connected to
> the
> microphone input externally and would not be hooked up as an input
> wihtin the CODEC?
>
>> + /* terminator */
>> + {NULL, NULL, NULL},
>
> Not needed any more (I'm vaugely surprised this doesn't generate an
> error).
>
>> +#define SOC_CS42L52_RATES ( SNDRV_PCM_RATE_8000 |
>> SNDRV_PCM_RATE_11025 | \
>> + SNDRV_PCM_RATE_16000 |
>> SNDRV_PCM_RATE_22050 | \
>> + SNDRV_PCM_RATE_32000 |
>> SNDRV_PCM_RATE_44100 | \
>> + SNDRV_PCM_RATE_48000 |
>> SNDRV_PCM_RATE_88200 | \
>> + SNDRV_PCM_RATE_96000 ) /*refer to
>> cs42l52 datasheet page35*/
>
> This is equivalent to SNDRV_PCM_RATE_8000_96000
>
>> +/* #define CONFIG_MANUAL_CLK */
>
> This might use some comments (or should possibly be deleted)?
>
>> + soc_cs42l52_set_bias_level(soc_codec, SND_SOC_BIAS_OFF);
>> + soc_codec->bias_level = SND_SOC_BIAS_STANDBY;
>> + schedule_delayed_work(&soc_codec->delayed_work,
>> msecs_to_jiffies(1000));
>> + soc_cs42l52_required_setup(soc_codec);
>
> A comment about what the delayed work is up to wouldn't hurt?
>
>> + /*set hp default volume*/
>> + soc_cs42l52_write(soc_codec, CODEC_CS42L52_HPA_VOL,
>> DEFAULT_HP_VOL);
>> + soc_cs42l52_write(soc_codec, CODEC_CS42L52_HPB_VOL,
>> DEFAULT_HP_VOL);
>
> For pretty much all the configuration you're doing here just let
> userspace set things up, there's no telling what's an appropriate
> setting on any given system and presumably the chip designers made a
> reasonable effort to ensure that the default state of the chip is at
> least not harmful.
>
>> +#ifdef CONFIG_MANUAL_CLK
>> + soc_cs42l52_write(soc_codec, CODEC_CS42L52_CLK_CTL,
>> + (soc_cs42l52_read(soc_codec, CODEC_CS42L52_CLK_CTL)
>> + & ~CLK_CTL_AUTODECT_ENABLE));
>
> Pass this configuration in as platform data, this will allow multiple
> boards to build from the same kernel.
>
>> + ret = snd_soc_init_card(soc_dev);
>> + if(ret)
>> + {
>> + SOCERR("add snd card failed\n");
>> + goto card_err;
>> + }
>
> Please develop against
>
> git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
> for-2.6.35
>
> (or whatever the latest kernel version branch is at any given time.)
>
> You should also be calling snd_soc_register_codec() and
> snd_soc_register_dai() from this function.
>
>> +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
>
> No need for the ifdefs if you only support I2C, the ifdefs are used by
> drivers that have multiple control interfaces (typically I2C plus
> SPI).
>
>> + }
>> +
>> +
>> + return ret;
>> +}
>> +
>> +static int cs42l52_i2c_remove(struct i2c_client *client)
>> +{
>> + struct snd_soc_codec *soc_codec;
>> + if(client)
>> + {
>> + soc_codec = i2c_get_clientdata(client);
>> + if(soc_codec)
>> + kfree(soc_codec->reg_cache);
>> + }
>> + return 0;
>> +}
>
> This should be unregistering from ASoC as well, or refusing to
> remove if
> not implemented.
>
>> + case SND_SOC_DAIFMT_DSP_B:
>> + SOCINF("unsupported format\n");
>> + ret = -EINVAL;
>> + goto done;
>> + default:
>> + SOCINF("invaild format\n");
>> + ret = -EINVAL;
>> + goto done;
>
> Could just squash these together.
>
>> + case SND_SOC_DAIFMT_IB_IF:
>> + SOCDBG("codec dai fmt inversed sclk\n");
>> + iface |= IFACE_CTL1_INV_SCLK;
>> + break;
>> + case SND_SOC_DAIFMT_IB_NF:
>> + iface |= IFACE_CTL1_INV_SCLK;
>> + break;
>
> It looks like the hardware only supports the inversion of one of the
> clocks so only two of the clock polarity combinations should be
> supported?
>
>> + info->format = iface;
>
> I'd really expect to see interaction with the hardware here?
>
>> + /* 96k */
>> + {12288000, 96000, CLK_CTL_S_DS_MODE, CLK_CTL_NOT_32K,
>> CLK_CTL_NOT_27M, CLK_CTL_RATIO_128, 0},
>> + {18432000, 96000, CLK_CTL_S_DS_MODE, CLK_CTL_NOT_32K,
>> CLK_CTL_NOT_27M, CLK_CTL_RATIO_128, 0},/*29*/
>> + {12000000, 96000, CLK_CTL_S_DS_MODE, CLK_CTL_NOT_32K,
>> CLK_CTL_NOT_27M, CLK_CTL_RATIO_125, 0},
>> + {24000000, 96000, CLK_CTL_S_DS_MODE, CLK_CTL_NOT_32K,
>> CLK_CTL_NOT_27M, CLK_CTL_RATIO_125, 1},
>
> If you're feeling adventurous you should be able to use this data to
> set
> up constraints for the DAI in the startup() callback so only sample
> rates supported from the system clock can be used by userspace.
>
>> +static int soc_cs42l52_pcm_hw_params(struct snd_pcm_substream
>> *substream,
>> + struct snd_pcm_hw_params *params,
>> + struct snd_soc_dai *dai)
>> +{
>> + int ret = 0;
>> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
>> + struct snd_soc_device *soc_dev = rtd->socdev;
>> + struct snd_soc_codec *soc_codec = soc_dev->card->codec;
>> + struct soc_codec_cs42l52 *info = (struct soc_codec_cs42l52*)
>> soc_codec->private_data;
>> +
>> + u32 clk = 0;
>> + int index = soc_cs42l52_get_clk(info->sysclk, params_rate
>> (params));
>
>> +static int soc_cs42l52_suspend(struct platform_device *pdev,
>> pm_message_t state)
>> +{
>> + struct snd_soc_device *soc_dev = (struct snd_soc_device*)
>> platform_get_drvdata(pdev);
>> + struct snd_soc_codec *soc_codec = soc_dev->card->codec;
>> +
>> + soc_cs42l52_write(soc_codec, CODEC_CS42L52_PWCTL1,
>> PWCTL1_PDN_CODEC);
>> + soc_cs42l52_set_bias_level(soc_codec, SND_SOC_BIAS_OFF);
>
> I'd expect that set_bias_level() would do what the explict write does?
>
>> +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE)
>> + if(soc_codec_data->i2c_addr)
>> + {
>> + ret = i2c_add_driver(&cs42l52_i2c_drv);
>> +
>> + if(ret)
>> + {
>> + SOCERR("register i2c driver failed\n");
>> + goto out;
>> +
>> + }
>> + SOCINF("Cirrus Logic ALSA SoC codec cs42l52 driver verison 1.2 2/2010
>> \n");
>> + return ret;
>> + }
>
> This should be changed to use standard device model registration - see
>
> http://git.kernel.org/?p=linux/kernel/git/broonie/sound-2.6.git;a=commit;h=5998102b9095fdb7c67755812038612afea315c5
>
> for an example of doing the conversion.
>
>> +struct soc_codec_cs42l52_data{
>> + u_short i2c_addr;
>> +};
>
> With the device model stuff this won't be needed any more.
>
>> +/*
>> + *CS42L52 internal registers
>> + */
>> +#define CODEC_CS42L52_CHIP 0x01
>> +#define CHIP_ID 0xE0
>> +#define CHIP_ID_MASK 0xF8
>
> Many of the register names and other definitions for register fields
> and
> values need to be namespace to avoid collisions.
>
Thanks alot for the feedback, I'll start fixing it up tomorrow.
Should I send the smdk2443 driver, or wait till this is fixed up?
Thanks again
Brian
More information about the Alsa-devel
mailing list