第十五部分 聲卡控制接口
前面第一篇中講到了每註冊一個聲卡都會創建註冊一個對應的聲卡控制設備,並給出了其操作函數集snd_ctl_f_ops,
在"amixer,aplay,arecord的使用"一文中提及了amixer如何設置獲取聲卡的控制選項
接着在第二篇中講到Asoc聲卡驅動編寫的步驟,步驟中沒牽扯到控制設備的控制選項,這裏要補充第5個步驟
第5個步驟就是在設備驅動中要調用snd_soc_add_controls函數添加相應的控制選項,也就是添加一系列的snd_kcontrol_new對象
1.snd_kcontrol_new結構體
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* 接口號 */
unsigned int device; /* 設備號 */
unsigned int subdevice; /* 子設備號 */
unsigned char *name; /* item名 */
unsigned int index; /* item索引號 */
unsigned int access; /* 通道權限(讀/寫/執行) */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
3.snd_soc_add_controls函數原型
int snd_soc_add_controls(struct snd_soc_codec *codec,const struct snd_kcontrol_new *controls, int num_controls)
{
struct snd_card *card = codec->card->snd_card; //獲取聲卡
int err, i;
for (i = 0; i < num_controls; i++) { //添加num_controls個控制元素
const struct snd_kcontrol_new *control = &controls[i]; //獲取snd_kcontrol_new對象
err = snd_ctl_add(card, snd_soc_cnew(control, codec, NULL)); //添加1個控制元素
if (err < 0) {
dev_err(codec->dev, "%s: Failed to add %s: %d\n",codec->name, control->name, err);
return err;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_add_controls);
調用snd_ctl_add函數之前會先調用snd_soc_cnew創建一個新的snd_kcontrol結構體對象
4.snd_kcontrol結構體
struct snd_kcontrol {
struct list_head list; /* list of controls */
struct snd_ctl_elem_id id; //控制元素id
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
void *private_data; //私有數據
void (*private_free)(struct snd_kcontrol *kcontrol);
struct snd_kcontrol_volatile vd[0]; /* volatile data */
};
5.snd_soc_cnew函數
struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,void *data, char *long_name)
{
struct snd_kcontrol_new template;
memcpy(&template, _template, sizeof(template)); //拷貝一份snd_kcontrol_new
if (long_name) //"NULL"
template.name = long_name;
template.index = 0;
return snd_ctl_new1(&template, data);
}
EXPORT_SYMBOL_GPL(snd_soc_cnew);
snd_soc_cnew函數基本只是做個拷貝工作,主要的任務還是交給snd_ctl_new1函數處理
5.1snd_ctl_new1函數
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,void *private_data)
{
struct snd_kcontrol kctl;
unsigned int access;
if (snd_BUG_ON(!ncontrol || !ncontrol->info))
return NULL;
memset(&kctl, 0, sizeof(kctl)); //初始化snd_kcontrol對象
//_____________________下面的工作基本上是將snd_kcontrol_new對象的對應值賦值給snd_kcontrol對象_____________________//
kctl.id.iface = ncontrol->iface; //接口號
kctl.id.device = ncontrol->device; //設備號
kctl.id.subdevice = ncontrol->subdevice; //子設備號
if (ncontrol->name) {
strlcpy(kctl.id.name, ncontrol->name, sizeof(kctl.id.name)); //拷貝item名到snd_kcontrol對象
if (strcmp(ncontrol->name, kctl.id.name) != 0)
snd_printk(KERN_WARNING"Control name '%s' truncated to '%s'\n",ncontrol->name, kctl.id.name);
}
kctl.id.index = ncontrol->index; //索引號
kctl.count = ncontrol->count ? ncontrol->count : 1;
access = ncontrol->access == 0 ? //通道權限
SNDRV_CTL_ELEM_ACCESS_READWRITE :(ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE|
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE|SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND|SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK));
kctl.info = ncontrol->info;
kctl.get = ncontrol->get;
kctl.put = ncontrol->put;
kctl.tlv.p = ncontrol->tlv.p;
kctl.private_value = ncontrol->private_value;
kctl.private_data = private_data; //指向codec設備
return snd_ctl_new(&kctl, access);
}
賦值完後又交由snd_ctl_new函數處理通道權限
5.2snd_ctl_new函數
static struct snd_kcontrol *snd_ctl_new(struct snd_kcontrol *control,unsigned int access)
{
struct snd_kcontrol *kctl;
unsigned int idx;
if (snd_BUG_ON(!control || !control->count))
return NULL;
if (control->count > MAX_CONTROL_COUNT)
return NULL;
kctl = kzalloc(sizeof(*kctl) + sizeof(struct snd_kcontrol_volatile) * control->count, GFP_KERNEL); //分配snd_kcontrol內存
if (kctl == NULL) {
snd_printk(KERN_ERR "Cannot allocate control instance\n");
return NULL;
}
*kctl = *control; //獲取snd_kcontrol對象
for (idx = 0; idx < kctl->count; idx++)
kctl->vd[idx].access = access; //設置通道權限
return kctl;
}
最後將初始化,賦值處理好的snd_kcontrol結構體對象返回作爲snd_ctl_add函數的第二個參數
6.添加1個控制元素
添加1個控制元素由snd_ctl_add函數實現,而1個控制元素用snd_ctl_elem_id結構體對象去表達
6.1 snd_ctl_elem_id結構體
struct snd_ctl_elem_id {
unsigned int numid; /* 控制編號 */
snd_ctl_elem_iface_t iface; /* 接口號 */
unsigned int device; /* 設備號 */
unsigned int subdevice; /* 子設備號 */
unsigned char name[44]; /* item名 */
unsigned int index; /* item索引號 */
};
6.2 snd_ctl_add函數
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
struct snd_ctl_elem_id id;
unsigned int idx;
int err = -EINVAL;
if (! kcontrol)
return err;
if (snd_BUG_ON(!card || !kcontrol->info))
goto error;
id = kcontrol->id;//獲取控制元素id
down_write(&card->controls_rwsem);
if (snd_ctl_find_id(card, &id)) { //根據id查找控制接口
up_write(&card->controls_rwsem);
snd_printd(KERN_ERR "control %i:%i:%i:%s:%i is already present\n",id.iface,id.device,id.subdevice,id.name,id.index);
err = -EBUSY;
goto error;
}
if (snd_ctl_find_hole(card, kcontrol->count) < 0) { //設置card的last_numid
up_write(&card->controls_rwsem);
err = -ENOMEM;
goto error;
}
list_add_tail(&kcontrol->list, &card->controls); //添加到所屬聲卡控制鏈表(以後可以在鏈表中搜查需要的項)
card->controls_count += kcontrol->count;
kcontrol->id.numid = card->last_numid + 1; //獲取控制編號
card->last_numid += kcontrol->count; //更新card的last_numid
up_write(&card->controls_rwsem);
for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
return 0;
error:
snd_ctl_free_one(kcontrol);
return err;
}
EXPORT_SYMBOL(snd_ctl_add);
設置snd_kcontrol對象的控制元素snd_ctl_elem_id對象,並將其添加到所屬聲卡的控制鏈表中
7.前面提到的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,
.llseek = no_llseek,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl,
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,
};
主要關注其unlocked_ioctl方法既snd_ctl_ioctl
8.snd_ctl_ioctl
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_ctl_file *ctl;
struct snd_card *card;
struct snd_kctl_ioctl *p;
void __user *argp = (void __user *)arg;
int __user *ip = argp;
int err;
ctl = file->private_data;
card = ctl->card;
if (snd_BUG_ON(!card))
return -ENXIO;
switch (cmd) {
case SNDRV_CTL_IOCTL_PVERSION:
return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
case SNDRV_CTL_IOCTL_CARD_INFO:
return snd_ctl_card_info(card, ctl, cmd, argp);
case SNDRV_CTL_IOCTL_ELEM_LIST: //對應amixer controls
return snd_ctl_elem_list(card, argp);
case SNDRV_CTL_IOCTL_ELEM_INFO: //對應amixer contents
return snd_ctl_elem_info_user(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_READ: //對應amixer cget cID P
return snd_ctl_elem_read_user(card, argp);
case SNDRV_CTL_IOCTL_ELEM_WRITE: //對應amixer cget cID
return snd_ctl_elem_write_user(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_LOCK:
return snd_ctl_elem_lock(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
return snd_ctl_elem_unlock(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_ADD:
return snd_ctl_elem_add_user(ctl, argp, 0);
case SNDRV_CTL_IOCTL_ELEM_REPLACE:
return snd_ctl_elem_add_user(ctl, argp, 1);
case SNDRV_CTL_IOCTL_ELEM_REMOVE:
return snd_ctl_elem_remove(ctl, argp);
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
return snd_ctl_subscribe_events(ctl, ip);
case SNDRV_CTL_IOCTL_TLV_READ:
return snd_ctl_tlv_ioctl(ctl, argp, 0);
case SNDRV_CTL_IOCTL_TLV_WRITE:
return snd_ctl_tlv_ioctl(ctl, argp, 1);
case SNDRV_CTL_IOCTL_TLV_COMMAND:
return snd_ctl_tlv_ioctl(ctl, argp, -1);
case SNDRV_CTL_IOCTL_POWER:
return -ENOPROTOOPT;
case SNDRV_CTL_IOCTL_POWER_STATE:
return put_user(card->power_state, ip) ? -EFAULT : 0;
}
down_read(&snd_ioctl_rwsem);
list_for_each_entry(p, &snd_control_ioctls, list) {
err = p->fioctl(card, ctl, cmd, arg);
if (err != -ENOIOCTLCMD) {
up_read(&snd_ioctl_rwsem);
return err;
}
}
up_read(&snd_ioctl_rwsem);
snd_printdd("unknown ioctl = 0x%x\n", cmd);
return -ENOTTY;
}