kcontol为何?写过alsa codec驱动的人都很熟悉了。
droidphone前辈的这篇文章也非常详细:
https://blog.csdn.net/droidphone/article/details/12793293
通俗一点讲,kcontol控件提供了很方便的在线调试手段,比如linux下使用amixer,android下使用timymix,也方便我们封装接口供上层使用,比较静音,设置codec音量等等。通常这些控制都比较简单,基本上就是操作codec寄存器的某一个bit而已,例如TI 5707芯片设置音量和静音的kcontol控制为:
static const struct snd_kcontrol_new tas5707_snd_controls[] = {
SOC_SINGLE_TLV("Master Volume", DDX_MASTER_VOLUME, 0,
0xff, 1, mvol_tlv),
SOC_SINGLE_TLV("Ch1 Volume", DDX_CHANNEL1_VOL, 0,
0xff, 1, chvol_tlv),
SOC_SINGLE_TLV("Ch2 Volume", DDX_CHANNEL2_VOL, 0,
0xff, 1, chvol_tlv),
SOC_SINGLE("Ch1 Switch", DDX_SOFT_MUTE, 0, 1, 1),
SOC_SINGLE("Ch2 Switch", DDX_SOFT_MUTE, 1, 1, 1),
SOC_SINGLE_RANGE("Fine Master Volume", DDX_CHANNEL3_VOL, 0,
0x80, 0x83, 0),
};
alsa定义的宏很方便我们对这些寄存器进行操作。在老的kernel版本中,kcontol并没有好的方式处理批量寄存器的设置,比如codec的EQ\DRC设置,用户层在切换音效时需要设置多组EQ相关的寄存器,这个时候就需要自己想办法实现了,比如增加一个节点等。
最近在看android P的code时发现,最新的linux kernel,增加了SND_SOC_BYTES_EXT这样一个宏,可以设置多组寄存器,先来看看alsa关于这个宏的定义:
#define SND_SOC_BYTES_EXT(xname, xcount, xhandler_get, xhandler_put) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.info = snd_soc_bytes_info_ext, \
.get = xhandler_get, .put = xhandler_put, \
.private_value = (unsigned long)&(struct soc_bytes_ext) \
{.max = xcount} }
xname为控件的名字
xcount为设置寄存器的最大个数
xhandler_get和xhandler_put分别为获取和设置参数的接口,具体的实现由我们来编写
private_value可以作为我们传下来的寄存器值数组
还是拿ti 5707设置EQ参数举例,看看set和get方法的实现:
SND_SOC_BYTES_EXT("EQ table", TAS5707_EQ_LENGTH,
tas5707_get_EQ_param, tas5707_set_EQ_param)
上面是kcontol宏的定义
static int tas5707_set_EQ_param(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
struct tas5707_priv *tas5707 = snd_soc_codec_get_drvdata(codec);
struct soc_bytes_ext *params = (void *)kcontrol->private_value;
void *data;
u8 *val, *p = &tas5707_EQ_table[0];
unsigned int i = 0, addr;
data = kmemdup(ucontrol->value.bytes.data,
params->max, GFP_KERNEL | GFP_DMA);
if (!data)
return -ENOMEM;
val = (u8 *)data;
memcpy(p, val, params->max / sizeof(u8));
for (i = 0; i < 14; i++) {
addr = DDX_CH1_BQ_0 + i;
regmap_raw_write(tas5707->regmap,
addr, p, TAS5707_EQ_PARAM_LENGTH);
p += TAS5707_EQ_PARAM_LENGTH;
}
kfree(data);
return 0;
}
static int tas5707_get_EQ_param(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
/*struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
*struct snd_soc_codec *codec = snd_soc_component_to_codec(component);
*struct tas5707_priv *tas5707 = snd_soc_codec_get_drvdata(codec);
*/
unsigned int i, addr;
u8 *val = (u8 *)ucontrol->value.bytes.data;
u8 *p = &tas5707_EQ_table[0];
for (i = 0; i < 14; i++) {
addr = DDX_CH1_BQ_0 + i;
/*regmap_raw_read(tas5707->regmap,
* addr, p, TAS5707_EQ_PARAM_LENGTH);
*/
memcpy(val, p, TAS5707_EQ_PARAM_LENGTH);
p += TAS5707_EQ_PARAM_LENGTH;
val += TAS5707_EQ_PARAM_LENGTH;
}
return 0;
}
写完了kernel的kcontol接口,即通过tinyalsa封装接口往kernel传递