Linux Kernel4.9新增的alsa kcontol宏SND_SOC_BYTES_EXT

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传递

 

你可能感兴趣的:(Linux,Alsa)