Linux音频驱动之二:Control接口的调用

本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记

一. control接口说明

Control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频codec芯片中的多路开关,滑动控件等。

二.control接口的open

由“Linux音频驱动之一:音频驱动注册流程”这篇文章可知,control device调用的是snd_ctl_dev_register函数注册的。

static int snd_ctl_dev_register(struct snd_device *device)
{
	struct snd_card *card = device->device_data;
	int err, cardnum;
	char name[16];

	if (snd_BUG_ON(!card))
		return -ENXIO;
	cardnum = card->number;
	if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
		return -ENXIO;
	sprintf(name, "controlC%i", cardnum);
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
				       &snd_ctl_f_ops, card, name)) < 0)
		return err;
	return 0;
}

传入的f_ops是snd_ctl_f_ops,如下面所示。

static const struct file_operations snd_ctl_f_ops =
{
	.owner =	THIS_MODULE,
	.read =		snd_ctl_read,
	.open =		snd_ctl_open,
	.release =	snd_ctl_release,
	.poll =		snd_ctl_poll,
	.unlocked_ioctl =	snd_ctl_ioctl,
	.compat_ioctl =	snd_ctl_ioctl_compat,
	.fasync =	snd_ctl_fasync,
};

当打开control device设备时,就会调用上面的snd_ctl_open函数。
进入snd_ctl_open函数分析。

  • 获取snd_card数据。
card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL);
    mreg = snd_minors[minor];
    private_data = mreg->private_data;

iminor(inode) = MINOR(inode->i_rdev)
inode->i_rdev是文件对应设备的设备号,MINOR(inode->i_rdev)是次设备号。
我们在control device这个设备注册的时候,在snd_minors数组里面寻找一个没有使用的元素,把fops,snd_card,type等信息保存。把该元素的下标minor作为次设备号。
主设备号是116

#define CONFIG_SND_MAJOR	116

dev_t = 116 << 20 | minor
最后把dev_t注册进了内核。
现在open时根据次设备号找到snd_minors[minor],得到snd_card数据。

  • 调用snd_card_file_add函数
    这个函数将打开的文件保存,放入了snd_card的files_list链表。作用是用于跟踪连接状态,避免热插拔释放繁忙资源。
int snd_card_file_add(struct snd_card *card, struct file *file)
{
    struct snd_monitor_file *mfile;
    mfile = kmalloc(sizeof(*mfile), GFP_KERNEL);
    if (mfile == NULL)
        return -ENOMEM;
    mfile->file = file;
    mfile->disconnected_f_op = NULL;
    spin_lock(&card->files_lock);
    if (card->shutdown) 
    {
        spin_unlock(&card->files_lock);
        kfree(mfile);
        return -ENODEV;
    }
    list_add(&mfile->list, &card->files_list);
    spin_unlock(&card->files_lock);
    return 0;
}
  • 申请一个snd_ctl_file结构体,保存snd_card等信息,并把它赋值给了file->private_data,后面通过file->private_data可以访问到snd_card等信息。
ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
ctl->card = card;
ctl->prefer_pcm_subdevice = -1;
ctl->prefer_rawmidi_subdevice = -1;
ctl->pid = current->pid;
file->private_data = ctl;
  • 把snd_ctl_file添加到snd_card的ctl_files链表。
list_add_tail(&ctl->list, &card->ctl_files);
三. control接口的write操作

control接口的write操作通过要snd_ctl_ioctl。进入snd_ctl_ioctl函数。
通过file->private_data得到snd_ctl_file,通过snd_ctl_file得到snd_card。

ctl = file->private_data;
card = ctl->card;

write操作调用的接口是snd_ctl_elem_write_user。
进入snd_ctl_elem_write_user函数看一下。

  • 从用户空间拷贝数据到内核。数据类型是snd_ctl_elem_value
control = memdup_user(_control, sizeof(*control));
  • 写snd_ctl_elem_value数据。
result = snd_ctl_elem_write(card, file, control);

进入snd_ctl_elem_write函数。

  • 根据用户传进来的snd_ctl_elem_value的ID找到对应的snd_kcontrol。
    在声卡初始化的时候底层有注册20个snd_kcontrol到snd_card的controls链表。
kctl = snd_ctl_find_id(card, &control->id);
  • 调用kctl->put函数。
result = kctl->put(kctl, control);

kctl->put函数在snd_kcontrol注册前赋值,函数是snd_soc_put_volsw

四. snd_soc_put_volsw函数

以音量设置的control为例。

uda1341_snd_controls[0] = 
{
    .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
    .name = "Master Playback Volume",
    .info = snd_soc_info_volsw,
    .get = snd_soc_get_volsw,
    .put = snd_soc_put_volsw,
    .private_value =
    {
        soc_mixer_control.reg = UDA134X_DATA000,
        soc_mixer_control.shift = 0,
        soc_mixer_control.rshift = 0,
        soc_mixer_control.max = 0x3F,
        soc_mixer_control.invert = 1,
    },
}
  • 根据snd_kcontrol得到soc_mixer_control和snd_soc_codec
struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
  • 计算部分
    unsigned int reg = mc->reg;
    unsigned int shift = mc->shift;
    unsigned int rshift = mc->rshift;
    int max = mc->max;
    unsigned int mask = (1 << fls(max)) - 1;
    unsigned int invert = mc->invert;
    unsigned int val, val2, val_mask;

    val = (ucontrol->value.integer.value[0] & mask);
    if (invert)
        val = max - val;
    val_mask = mask << shift;
    val = val << shift;
    if (shift != rshift) 
    {
        val2 = (ucontrol->value.integer.value[1] & mask);
        if (invert)
            val2 = max - val2;
        val_mask |= mask << rshift;
        val |= val2 << rshift;
    }

reg = UDA134X_DATA000;
shift = 0;
rshift = 0;
max = 0x3f;
fls(max),max最高位的位置,fls(max) = 6, mask = 0x3f;
invert是否反转,invert = 1,需要反转;
val,用户空间给的值,反转的话,需要用最大值减,值越大,音量越大,需要设置到寄存器的值越小;
val_mask = 0x3f << 0;
val = val << 0;
shift != rshift的情况,猜测是左右声道需要分别设置,我们这里不需要设置。
在这里插入图片描述

  • 调用snd_soc_update_bits函数
snd_soc_update_bits(codec, reg, val_mask, val);
    snd_soc_write(codec, reg, new);
        codec->write(codec, reg, val);

codec->write = uda134x_write
uda134x_write函数在“Linux音频驱动之五:UDA1341芯片操作接口”中分析了,这里不在分析,就是写寄存器的值。

参考博客:
https://blog.youkuaiyun.com/sunweizhong1024/article/details/7697363
https://blog.youkuaiyun.com/droidphone/article/details/6409983

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值