Audio System: Lesson 003 _Linux Audio Driver: Section 004 _kcontrol of Sound Card Control

There are many registers in a chip, and some bits in a register are used to represent a function. As follows, does kcontrol we want to analyze represent a function or a register? Of course, using kcontrol to represent a function is better, as follows:

When a function is turned on, we can manipulate a kcontrol. Obviously we have found the core of kcontrol. Some bits of registers represent a function. They may be one bit or more bits. Obviously, the read and write functions of these bits are different. Kcontrol has its own read-write function. As shown above, we do some simple summaries:
1. A sound card has multiple kcontrol s
2. A kcontrol corresponds to a function, such as volume, switch, recording.
3.kcontrol has function setting function
Next we start to analyze the code, open the rt5651.c file, and find:

static const struct snd_kcontrol_new rt5651_snd_controls[] = {
	//Follow-up pasting
	......
	......
};

It can be found that the structure rt5651_snd_controls is directed by the control members of the snd_soc_codec_driver structure:

static struct snd_soc_codec_driver soc_codec_dev_rt5651 = {
	.controls = rt5651_snd_controls,
	.num_controls = ARRAY_SIZE(rt5651_snd_controls),
}
snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5651,rt5651_dai, ARRAY_SIZE(rt5651_dai));
	ret = snd_soc_register_card(card);
		/*Within this function, a lot of work will be done, such as binding dai, and so on.*/
		ret = snd_soc_instantiate_card(card);
			snd_soc_add_card_controls(card, card->controls, card->num_controls);
				snd_soc_add_controls(card, soc_card->dev, controls, num_controls,NULL, soc_card);
					const struct snd_kcontrol_new *control = &controls[i];
					snd_ctl_add(card, snd_soc_cnew(control, data,control->name, prefix));
						list_add_tail(&kcontrol->list, &card->controls);
						

It is known that soc_codec_dev_rt5651 will be registered by the snd_soc_register_codec function. Finally, it was added to the & kcontrol - > list list, which contains a bunch of snd_kcontrol *kcontrol, as follows:

Let's look at what exists in the snd_kcontrol structure:

struct snd_ctl_elem_id {
	unsigned int numid;		/* numeric identifier, zero = invalid */
	snd_ctl_elem_iface_t iface;	/* interface identifier */
	unsigned int device;		/* device/client number */
	unsigned int subdevice;		/* subdevice (substream) number */
	unsigned char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];		/* ASCII name of item */
	unsigned int index;		/* index of item */
};
struct snd_kcontrol {
	struct snd_ctl_elem_id id;
	unsigned int count;		/* count of same elements: Number of identical elements */
	snd_kcontrol_info_t *info;//Get information about kcontrol
	snd_kcontrol_get_t *get//Get the value of kcontrol (some values of registers)
	snd_kcontrol_put_t *put;//Set the value of kcontrol (register value)
	unsigned long private_value;//This value is used for the above three functions
	void *private_data;//This value is used for the above three functions (such as those not specified in registers, etc.)
}

The above values, such as struct snd_ctl_elem_id, registers and so on, are provided by the driver of our chip:

There is a series of snd_kcontrol_new in the chip driver. They are used to construct snd_kcontrol and then added to the sound card. Each snd_kcontrol_new has snd_kcontrol_info_t*info, snd_kcontrol_get_t*get, snd_kcontrol_put_t*put, etc.
It is convenient for us to write. It provides many macros like SOC_DOUBLE_TLV, SOC_DOUBLE, SOC_DOUBLE and so on.

static const struct snd_kcontrol_new rt5651_snd_controls[] = {
	/* Headphone Output Volume */
	SOC_DOUBLE_TLV("HP Playback Volume", RT5651_HP_VOL,
		RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, 39, 1, out_vol_tlv),
	/* OUTPUT Control */
	SOC_DOUBLE_TLV("OUT Playback Volume", RT5651_LOUT_CTRL1,
		RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, 39, 1, out_vol_tlv),

	/* DAC Digital Volume */
	SOC_DOUBLE("DAC2 Playback Switch", RT5651_DAC2_CTRL,
		RT5651_M_DAC_L2_VOL_SFT, RT5651_M_DAC_R2_VOL_SFT, 1, 1),
	SOC_DOUBLE_TLV("DAC1 Playback Volume", RT5651_DAC1_DIG_VOL,
			RT5651_L_VOL_SFT, RT5651_R_VOL_SFT,
			175, 0, dac_vol_tlv),
	SOC_DOUBLE_TLV("Mono DAC Playback Volume", RT5651_DAC2_DIG_VOL,
			RT5651_L_VOL_SFT, RT5651_R_VOL_SFT,
			175, 0, dac_vol_tlv),
	/* IN1/IN2 Control */
	SOC_SINGLE_TLV("IN1 Boost", RT5651_IN1_IN2,
		RT5651_BST_SFT1, 8, 0, bst_tlv),
	SOC_SINGLE_TLV("IN2 Boost", RT5651_IN1_IN2,
		RT5651_BST_SFT2, 8, 0, bst_tlv),
	/* INL/INR Volume Control */
	SOC_DOUBLE_TLV("IN Capture Volume", RT5651_INL1_INR1_VOL,
			RT5651_INL_VOL_SFT, RT5651_INR_VOL_SFT,
			31, 1, in_vol_tlv),
	/* ADC Digital Volume Control */
	SOC_DOUBLE("ADC Capture Switch", RT5651_ADC_DIG_VOL,
		RT5651_L_MUTE_SFT, RT5651_R_MUTE_SFT, 1, 1),
	SOC_DOUBLE_TLV("ADC Capture Volume", RT5651_ADC_DIG_VOL,
			RT5651_L_VOL_SFT, RT5651_R_VOL_SFT,
			127, 0, adc_vol_tlv),
	SOC_DOUBLE_TLV("Mono ADC Capture Volume", RT5651_ADC_DATA,
			RT5651_L_VOL_SFT, RT5651_R_VOL_SFT,
			127, 0, adc_vol_tlv),
	/* ADC Boost Volume Control */
	SOC_DOUBLE_TLV("ADC Boost Gain", RT5651_ADC_BST_VOL,
			RT5651_ADC_L_BST_SFT, RT5651_ADC_R_BST_SFT,
			3, 0, adc_bst_tlv),

	/* RT5651 ASRC Switch */
	SOC_ENUM_EXT("RT5651 ASRC Switch", rt5651_asrc_enum,
		     rt5651_asrc_get, rt5651_asrc_put),
	/* ASRC */
	SOC_SINGLE("IF1 ASRC Switch", RT5651_PLL_MODE_1,
		RT5651_STO1_T_SFT, 1, 0),
	SOC_SINGLE("IF2 ASRC Switch", RT5651_PLL_MODE_1,
		RT5651_STO2_T_SFT, 1, 0),
	SOC_SINGLE("DMIC ASRC Switch", RT5651_PLL_MODE_1,
		RT5651_DMIC_1_M_SFT, 1, 0),

	SOC_ENUM("ADC IF2 Data Switch", rt5651_if2_adc_enum),
	SOC_ENUM("DAC IF2 Data Switch", rt5651_if2_dac_enum),
};

We enter SOC_DOUBLE_TLV macro:

#define SOC_DOUBLE_TLV(xname, reg, shift_left, shift_right, max, invert, 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_VALUE(reg, shift_left, shift_right, \
					  max, invert, 0) }

This macro will help us implement info, put, and private_value functions, as well as the construction of private data, whose private data is stored in SOC_DOUBLE_VALUE, which is a structure, as follows:

#define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert, xautodisable) \
	((unsigned long)&(struct soc_mixer_control) \
	{.reg = xreg, .rreg = xreg, .shift = shift_left, \
	.rshift = shift_right, .max = xmax, .platform_max = xmax, \
	.invert = xinvert, .autodisable = xautodisable})

It specifies:
reg: Register start, rreg: register end, shift_left: from which to start shift_right: from which end, xmax: in several bits
Wait a minute. This is a simple example.
Implementing tinymix on the development board lists all:

You can see the first "HP Playback Volume" with the driver

SOC_DOUBLE_TLV("HP Playback Volume", RT5651_HP_VOL,RT5651_L_VOL_SFT, RT5651_R_VOL_SFT, 39, 1, out_vol_tlv),

Correspondingly, the others are the same, in turn. We can use its sequence, or we can control it by its name. Such as:
tinymix "OUT Playback Volume"
Get:
OUT Playback Volume: 31 31 (range 0->39)
Implementation:
tinymix "OUT Playback Volume" 10
You can set it to 10 and then execute it.
tinymix "OUT Playback Volume"
Names can be changed into sequences, i.e. 1, 2, 3, 4, 5, etc.

The unsigned int count in snd_kcontrol mentioned earlier represents how many elements there are, that is to say, there can be multiple snd_ctl_elem_id. If the snd_ctl_elem_id of the first snd_kcontrol is 1, count is 3, and the snd_ctl_elem_id of the second snd_kcontrol is 3.

Here are some summaries:

Keywords: ascii

Added by danielhalawi on Tue, 14 May 2019 13:10:59 +0300