Chinaunix首页 | 论坛 | 博客
  • 博客访问: 812471
  • 博文数量: 172
  • 博客积分: 3836
  • 博客等级: 中校
  • 技术积分: 1988
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-10 14:59
文章分类

全部博文(172)

文章存档

2014年(2)

2013年(1)

2012年(28)

2011年(141)

分类: LINUX

2011-11-30 12:13:32

control控制接口

控制接口对于许多开关(switch)和调节器(slider)应用广泛,它能被用户空间存取,从而读写Codec相关寄存器。control的主要用于mixer。它用snd_kcontrol_new结构体描述。


  1. 1. struct snd_kcontrol_new {
  2. 2. snd_ctl_elem_iface_t iface; /* interface identifier */
  3. 3. unsigned int device; /* device/client number */
  4. 4. unsigned int subdevice; /* subdevice (substream) number */
  5. 5. unsigned char *name; /* ASCII name of item */
  6. 6. unsigned int index; /* index of item */
  7. 7. unsigned int access; /* access rights */
  8. 8. unsigned int count; /* count of same elements */
  9. 9. snd_kcontrol_info_t *info;
  10. 10. snd_kcontrol_get_t *get;
  11. 11. snd_kcontrol_put_t *put;
  12. 12. union {
  13. 13. snd_kcontrol_tlv_rw_t *c;
  14. 14. const unsigned int *p;
  15. 15. } tlv;
  16. 16. unsigned long private_value;
  17. 17. };


iface字段定义了control的类型形式为SNDRV_CTL_ELEM_IFACE_XXX对于mixerSNDRV_CTL_ELEM_IFACE_MIXER对于不属于mixer的全局控制使用CARD如果关联到某类设备则是PCMRAWMIDITIMERSEQUENCER。在这里,我们主要关注mixer。

 

name字段是名称标识,这个字段非常重要,因为control的作用由名称来区分,对于名称相同的control,则使用index区分。下面会详细介绍上层应用如何根据name名称标识来找到底层相应的control。

name定义的标准是SOURCE DIRECTION FUNCTION源 方向 功能,SOURCE定义了control的源,如MasterPCM等;DIRECTION 则为PlaybackCapture等,如果DIRECTION忽略,意味着Playback和capture双向;FUNCTION则可以是SwitchVolumeRoute等。

【修 正】:上层也可以根据numid来找到对应的control,snd_ctl_find_id()也是优先判断上层是否传递了numid,是则直接返回这 个numid对应的control。用户层设置numid和control的关联时,可用alsa-lib的 snd_mixer_selem_set_enum_item()函数。snd_kcontrol_new结构体并没有numid这个成员,是因为 numid是系统自动管理的,原则是该control的注册次序,保存到snd_ctl_elem_value结构体中。

 

access 字段是访问控制权限。SNDRV_CTL_ELEM_ACCESS_READ意味着只读,这时put()函数不必实 现;SNDRV_CTL_ELEM_ACCESS_WRITE意味着只写,这时get()函数不必实现。若control值频繁变化,则需定义 VOLATILE标志。当control处于非激活状态时,应设置INACTIVE标志。

 

private_value字段包含1个长整型值,可以通过它给info()、get()和put()函数传递参数。

 

kcontrol宏

在早期的ALSA创建一个新的control需要实现snd_kcontrol_new中的info、get和put这三个成员函数。现在较新版本的ALSA均定义了一些宏,如:

  1. 1. #define SOC_SINGLE(xname, reg, shift, max, invert) /
  2. 2. { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, /
  3. 3. .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,/
  4. 4. .put = snd_soc_put_volsw, /
  5. 5. .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }

这个宏的对象是MIXER,对寄存器reg的位偏移shift可以设置0-max的数值。

又如

  1. 1. #define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) /
  2. 2. { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),/
  3. 3. .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |/
  4. 4. SNDRV_CTL_ELEM_ACCESS_READWRITE,/
  5. 5. .tlv.p = (tlv_array), /
  6. 6. .info = snd_soc_info_volsw_2r, /
  7. 7. .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, /
  8. 8. .private_value = (unsigned long)&(struct soc_mixer_control) /
  9. 9. {.reg = reg_left, .rreg = reg_right, .shift = xshift, /
  10. 10. .max = xmax, .invert = xinvert} }

这个宏与刚才类似但是它是对两个寄存器reg_leftreg_right进行同一操作Codec芯片中左右声道的寄存器配置一般来说是差不多的这就是这个宏存在的意义。

例如我们一个Playback Volume的kcontrol接口这样定义:

SOC_DOUBLE_R_TLV("Playback Volume", REG_VOL_L, REG_VOL_R, 0, 192, 0, digital_tlv)

我 们仅仅需要将Volume寄存器地址及位偏移,最大值填进去即可,当然这些数据要从Codec的datasheet取得。这里Volume寄存器地址是 REG_VOL_L(左声道)和REG_VOL_R(右声道),位偏移为0,DAC Digital Gain范围是0-192(steps)。

 

为了追根究底,我们看看SOC_DOUBLE_R_TLV的put函数实现:

  1. 1. /**
  2. 2. * snd_soc_put_volsw_2r - double mixer set callback
  3. 3. * @kcontrol: mixer control
  4. 4. * @ucontrol: control element information
  5. 5. *
  6. 6. * Callback to set the value of a double mixer control that spans 2 registers.
  7. 7. *
  8. 8. * Returns 0 for success.
  9. 9. */
  10. 10. int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol,
  11. 11. struct snd_ctl_elem_value *ucontrol)
  12. 12. {
  13. 13. struct soc_mixer_control *mc =
  14. 14. (struct soc_mixer_control *)kcontrol->private_value;
  15. 15. struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
  16. 16. unsigned int reg = mc->reg;
  17. 17. unsigned int reg2 = mc->rreg;
  18. 18. unsigned int shift = mc->shift;
  19. 19. int max = mc->max;
  20. 20. unsigned int mask = (1 << fls(max)) - 1;
  21. 21. unsigned int invert = mc->invert;
  22. 22. int err;
  23. 23. unsigned int val, val2, val_mask;
  24. 24.
  25. 25. val_mask = mask << shift;
  26. 26. val = (ucontrol->value.integer.value[0] & mask);
  27. 27. val2 = (ucontrol->value.integer.value[1] & mask);
  28. 28.
  29. 29. if (invert) {
  30. 30. val = max - val;
  31. 31. val2 = max - val2;
  32. 32. }
  33. 33.
  34. 34. val = val << shift;
  35. 35. val2 = val2 << shift;
  36. 36.
  37. 37. err = snd_soc_update_bits(codec, reg, val_mask, val);
  38. 38. if (err < 0)
  39. 39. return err;
  40. 40.
  41. 41. err = snd_soc_update_bits(codec, reg2, val_mask, val2);
  42. 42. return err;
  43. 43. }

struct snd_ctl_elem_value *ucontrol从用户层传递下来的这个也可以从命名看出来kcontrol-kernel controlucontrol-user control);

shift是位偏移而位掩码mask是通过宏SOC_DOUBLE_R_TLV中的xmax运算得到unsigned int mask = (1 << fls(max)) - 1;

调用snd_soc_update_bits()->snd_soc_write()ucontrolvalue送到Codec的寄存器上。

 

snd_soc_put_volsw_2r()作为一个callback函数用户层要设置某些功能时如改变Playback Volume

#amixer cset numid=3,iface=MIXER,name='Playback Volume' 100

到内核层时会遍历一个节点类型为struct snd_kcontrol *的链表找到kcontrol.id.numid与3相匹配的kctl这个过程见snd_ctl_find_id()函数),然后调用kctl.put()函数将100写到Playback Volume寄存器中。

当然如果上层没有提供numid,则可根据name找到kcontrol.id.name相匹配的kctl。

amixer相关用法见http://hi.baidu.com/serial_story/blog/item/c4e826d82a562f3f32fa1c31.html

 

从上往下的大致流程:

  1. 1. amixer-用户层
  2. 2.   |->snd_ctl_ioctl-系统调用
  3. 3.       |->snd_ctl_elem_write_user-内核钩子函数
  4. 4.           |->snd_ctl_elem_wirte-
  5. 5.               |->snd_ctl_find_id-遍历kcontrol链表找到name字段匹配的kctl
  6. 6.                     |->kctl->put()-调用kctl的成员函数put()
  7. 7.                         |->snd_soc_put_volsw_2r

PS:上层如何设置kctl的numid,可参考http://blog.csdn.net/cpuwolf/archive/2009/10/17/4686830.aspx <2011/2/24> 

 

 

snd_ctl_find_id函数:

  1. 1. /**
  2. 2. * snd_ctl_find_id - find the control instance with the given id
  3. 3. * @card: the card instance
  4. 4. * @id: the id to search
  5. 5. *
  6. 6. * Finds the control instance with the given id from the card.
  7. 7. *
  8. 8. * Returns the pointer of the instance if found, or NULL if not.
  9. 9. *
  10. 10. * The caller must down card->controls_rwsem before calling this function
  11. 11. * (if the race condition can happen).
  12. 12. */
  13. 13. struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card,
  14. 14. struct snd_ctl_elem_id *id)
  15. 15. {
  16. 16. struct snd_kcontrol *kctl;
  17. 17.
  18. 18. if (snd_BUG_ON(!card || !id))
  19. 19. return NULL;
  20. 20. if (id->numid != 0)
  21. 21. return snd_ctl_find_numid(card, id->numid);
  22. 22. list_for_each_entry(kctl, &card->controls, list) {
  23. 23. if (kctl->id.iface != id->iface)
  24. 24. continue;
  25. 25. if (kctl->id.device != id->device)
  26. 26. continue;
  27. 27. if (kctl->id.subdevice != id->subdevice)
  28. 28. continue;
  29. 29. if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)))
  30. 30. continue;
  31. 31. if (kctl->id.index > id->index)
  32. 32. continue;
  33. 33. if (kctl->id.index + kctl->count <= id->index)
  34. 34. continue;
  35. 35. return kctl;
  36. 36. }
  37. 37. return NULL;
  38. 38. }




阅读(3960) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~