本文是基于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