This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TLV320AIC3110: Limiting the maximum volume level

Part Number: TLV320AIC3110


Hello,

I'm trying to limit the maximum volume level allowed for the tlv320aic3110. In our initial tests, when the volume is at its maximum default volume, it ends up damaging the speaker. For now, we are lowering the default volume using ALSA controls, but we would like to have a limit that couldn't be surpassed even with ALSA controls. My idea is to use some values read from the device tree that would be written to the appropriate registers.

I have successfully modified the driver to read these values and I'm writing to a series of registers, but that doesn't seem to be having any effect. When I load the OS in our terminal and modify the volume level to the maximum value, I seem to be getting the same volume I was getting without my modifications.

These are the registers I'm writing to:

if (strncmp(name, "SP Analog Playback Volume", 25) == 0) {
	aic31xx->SP_Analog_Playback_Volume = 127 - limit;
}

if (strncmp(name, "SP Driver Playback Volume", 25) == 0) {
	aic31xx->SP_Driver_Playback_Volume = limit;
}

ret = snd_soc_component_write(component, AIC31XX_DACMIXERROUTE, 0x44);	/* Page 1 / Register 35 (0x23): DAC_L and DAC_R Output Mixer Routing */
ret = snd_soc_component_write(component, AIC31XX_LANALOGHPL, 0xC0);	/* Page 1 / Register 36 (0x24): Left Analog Volume to HPL */
ret = snd_soc_component_write(component, AIC31XX_RANALOGHPR, 0xC0);	/* Page 1 / Register 37 (0x25): Right Analog Volume to HPR */
ret = snd_soc_component_write(component, AIC31XX_LANALOGSPL, (aic31xx->SP_Analog_Playback_Volume & 0x7F) | 0x80);	/* Page 1 / Register 38 (0x26): Left Analog Volume to SPL */
ret = snd_soc_component_write(component, AIC31XX_RANALOGSPR, (aic31xx->SP_Analog_Playback_Volume & 0x7F) | 0x80);	/* Page 1 / Register 39 (0x27): Right Analog Volume to SPR */
ret = snd_soc_component_write(component, AIC31XX_HPDRIVER, 0xc4);	/* Page 1 / Register 31 (0x1F): Headphone Drivers */
ret = snd_soc_component_write(component, AIC31XX_HPLGAIN, 0x4d);	/* Page 1 / Register 40 (0x28): HPL Driver */
ret = snd_soc_component_write(component, AIC31XX_HPRGAIN, 0x4d);	/* Page 1 / Register 41 (0x29): HPR Driver */
ret = snd_soc_component_write(component, AIC31XX_SPLGAIN, ((aic31xx->SP_Driver_Playback_Volume & 0x03) << 3) | 0x5);	/* Page 1 / Register 42 (0x2A): SPL Driver */
ret = snd_soc_component_write(component, AIC31XX_SPRGAIN, ((aic31xx->SP_Driver_Playback_Volume & 0x03) << 3) | 0x5);	/* Page 1 / Register 43 (0x2B): SPR Driver */
ret = snd_soc_component_write(component, AIC31XX_SPKAMP, 0xc6);	/* Page 1 / Register 32 (0x20): Class-D Speaker Amplifier */
ret = snd_soc_component_write(component, AIC31XX_LANALOGSPL, 0x74);	/* Page 1 / Register 38 (0x26): Left Analog Volume to SPL */
ret = snd_soc_component_write(component, AIC31XX_RANALOGSPR, 0x74);	/* Page 1 / Register 39 (0x27): Right Analog Volume to SPR */

I'm writing to them in the probe function, at the end, right after reading the values from the device tree I mentioned earlier. According to the value returned by the write function, it is being successful, and the read function returns the same value I'm trying to write.

Should I write to these registers in a different place or with a certain frequency (every minute, for example)? Could it be a completely different thing?

To modify the volume once my change is installed, I'm using alsamixer, and I'm using speaker-test to produce sound.

  • Hi Jesús,

    I don't think you should need to reapply settings again after your initial write to the registers. 

    First, could you try setting the driver gain to a constant 6dB, by setting register 42 and 43 bits 7-1 to 0b0000010? Then we can try varying the analog output control to see if there's a difference.

    Also, is there a reason Reg 38/39 are set on lines 12/13 in the code above and again on lines 20/21? I think setting them to 0x74 like on lines 20/21 should cause the analog volume control to be muted since bit 7 will be set to 0. You could try setting these to F4 here instead and seeing if you hear a difference. The fact that the output isn't muted with the current code does make me question if the settings are applying persistently.

    Let me know if this helps!

    Regards,
    Lukas

  • Hello,

    I have tried setting 42 and 43 to 0b0000010 and then, 38 and 39 to 0xF4, but nothing seems to change. The volume seems to be the same when I set it to the max using alsamixer. Is there maybe something I need to do to have the changes take effect?

    About setting 38 and 39, I genuinely hadn't noticed. I had written the first lines and, when I saw it wasn't having any effect, during my investigation, I saw those two registers and added them at the end, I guess I forgot about using them before.

    Thank you.

  • Hello Jesús,

    Thanks for trying those suggestions. I noticed in your code that you aren't writing to the Page Register (0x00). If you're not already doing so somewhere else, could you try writing 0x01 to Reg 0x00 prior to your other writes, in order to select Page 1?

    Regards,
    Lukas

  • Hello,

    I have added that and it seems like it's not being correctly set to 0x01. I have added this at the start of all the writes:

    ret = snd_soc_component_write(component, AIC31XX_PAGECTL, 0x01); /* Page Control Register */
    
    if (ret < 0) {
    	dev_info(aic31xx->dev, ~~~~~Error in write - %d\n", ret);
    }
    else {
    	dev_info(aic31xx->dev, "~~~~~Register value - "BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(snd_soc_component_read(component, AIC31XX_PAGECTL)));
    	dev_info(aic31xx->dev, "~~~~~Wanted to write: "BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(0x01));
    }
    

    And this is what dmesg shows me:

    [    7.251271] tlv320aic31xx-codec 0-0018: ASoC: error at soc_component_read_no_lock on tlv320aic31xx-codec.0-0018: -16
    [    7.312438] tlv320aic31xx-codec 0-0018: ASoC: error at soc_component_read_no_lock on tlv320aic31xx-codec.0-0018: -16
    [    7.405444] tlv320aic31xx-codec 0-0018: ASoC: error at soc_component_read_no_lock on tlv320aic31xx-codec.0-0018: -16
    [    7.449900] tlv320aic31xx-codec 0-0018: ASoC: error at soc_component_read_no_lock on tlv320aic31xx-codec.0-0018: -16
    [    7.485123] tlv320aic31xx-codec 0-0018: ASoC: error at soc_component_read_no_lock on tlv320aic31xx-codec.0-0018: -16
    [    7.522520] tlv320aic31xx-codec 0-0018: ASoC: error at soc_component_read_no_lock on tlv320aic31xx-codec.0-0018: -16
    [    7.554308] tlv320aic31xx-codec 0-0018: ASoC: error at soc_component_read_no_lock on tlv320aic31xx-codec.0-0018: -16
    [    7.586582] tlv320aic31xx-codec 0-0018: ASoC: error at soc_component_read_no_lock on tlv320aic31xx-codec.0-0018: -16
    [    7.619376] tlv320aic31xx-codec 0-0018: ~~~~~Register value - 11110000
    [    7.619415] tlv320aic31xx-codec 0-0018: ~~~~~Wanted to write: 00000001

    I have also tried writing to "Page 0 / Register 1 (0x01): Software Reset" and I have the same problem. It fails at reading it a few times after "writing" on it and then it shows the wrong value, that -16 in binary.

  • Hi Jesús,

    If you want to limit the maximum volume level, and adjust volume by alsamixer. you don't need to rewrite registers, because each time you adjust kcontrol, the modification will be covered. My suggestion is to rewrite the max value of kcontrol.

    See below lines of code.

    static const struct snd_kcontrol_new common31xx_snd_controls[] = {
    	SOC_DOUBLE_R_S_TLV("DAC Playback Volume", AIC31XX_LDACVOL,
    			   AIC31XX_RDACVOL, 0, -127, 48, 7, 0, dac_vol_tlv),
    
    	SOC_DOUBLE_R("HP Driver Playback Switch", AIC31XX_HPLGAIN,
    		     AIC31XX_HPRGAIN, 2, 1, 0),
    	SOC_DOUBLE_R_TLV("HP Driver Playback Volume", AIC31XX_HPLGAIN,
    			 AIC31XX_HPRGAIN, 3, 0x09, 0, hp_drv_tlv),
    
    	SOC_DOUBLE_R_TLV("HP Analog Playback Volume", AIC31XX_LANALOGHPL,
    			 AIC31XX_RANALOGHPR, 0, 0x7F, 1, hp_vol_tlv),
    
    	/* HP de-pop control: apply power not immediately but via ramp
    	 * function with these psarameters. Note that power up sequence
    	 * has to wait for this to complete; this is implemented by
    	 * polling HP driver status in aic31xx_dapm_power_event()
    	 */
    	SOC_ENUM("HP Output Driver Power-On time", hp_poweron_time_enum),
    	SOC_ENUM("HP Output Driver Ramp-up step", hp_rampup_step_enum),
    
    	SOC_ENUM("Volume Soft Stepping", vol_soft_step_mode_enum),
    };
    
    static const struct snd_kcontrol_new aic31xx_snd_controls[] = {
    	SOC_SINGLE_TLV("ADC Fine Capture Volume", AIC31XX_ADCFGA, 4, 4, 1,
    		       adc_fgain_tlv),
    
    	SOC_SINGLE("ADC Capture Switch", AIC31XX_ADCFGA, 7, 1, 1),
    	SOC_DOUBLE_R_S_TLV("ADC Capture Volume", AIC31XX_ADCVOL, AIC31XX_ADCVOL,
    			   0, -24, 40, 6, 0, adc_cgain_tlv),
    
    	SOC_SINGLE_TLV("Mic PGA Capture Volume", AIC31XX_MICPGA, 0,
    		       119, 0, mic_pga_tlv),
    };
    
    static const struct snd_kcontrol_new aic311x_snd_controls[] = {
    	SOC_DOUBLE_R("Speaker Driver Playback Switch", AIC31XX_SPLGAIN,
    		     AIC31XX_SPRGAIN, 2, 1, 0),
    	SOC_DOUBLE_R_TLV("Speaker Driver Playback Volume", AIC31XX_SPLGAIN,
    			 AIC31XX_SPRGAIN, 3, 3, 0, class_D_drv_tlv),
    
    	SOC_DOUBLE_R_TLV("Speaker Analog Playback Volume", AIC31XX_LANALOGSPL,
    			 AIC31XX_RANALOGSPR, 0, 0x7F, 1, sp_vol_tlv),
    };

    The struct of snd_kcontrol_new will create the kcontrol you used in alsamixer, see the definition of each macro. for example, check the definition of the SOC_DOUBLE_R_TLV and SOC_DOUBLE_R_S_TLV, xmax corresponds to the maximum value set by the register, so limit the xmax, you should limit maximum volume. For controlling the output of speaker, you should modify the "Speaker Driver Playback Volume" and "DAC Playback Volume".

    #define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) \
    {	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
    	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
    		 SNDRV_CTL_ELEM_ACCESS_READWRITE,\
    	.tlv.p = (tlv_array), \
    	.info = snd_soc_info_volsw, \
    	.get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \
    	.private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \
    					    xmax, xinvert) }
    
    #define SOC_DOUBLE_R_S_TLV(xname, reg_left, reg_right, xshift, xmin, xmax, xsign_bit, xinvert, tlv_array) \
    {	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
    	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
    		 SNDRV_CTL_ELEM_ACCESS_READWRITE,\
    	.tlv.p = (tlv_array), \
    	.info = snd_soc_info_volsw, \
    	.get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \
    	.private_value = SOC_DOUBLE_R_S_VALUE(reg_left, reg_right, xshift, \
    					    xmin, xmax, xsign_bit, xinvert) }

    I hope this information can help, kindly let me know if this can work.

    Thanks

    Kevin Lu

  • Hello,

    I have modified the values in that struct and the maximum volume has now changed successfully. Thank you very much.

    I now have to modify it according to the value from the device tree, but that's a different problem.

    Thank you very much for your help.