17.4.4 控制接口
1.control
控制接口对于许多开关(switch)和调节器(slider)而言应用相当广泛,control能从用户空间被存取。control 的最主要用途是 mixer,所有的 mixer 元素基于 control 内核 API 实现,control 用 snd_kcontrol 结构体描述。
include/sound/control.h
struct snd_kcontrol {
struct list_head list; /* list of controls */
struct snd_ctl_elem_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 */
};
ALSA 有一个定义很好的 AC97 控制模块,对于仅支持 AC97 的芯片,不必实现本节的内容。
创建一个新的 control 至少需要实现 snd_kcontrol_new 中的 info()、get()和 put()这 3 个成员函数,snd_kcontrol_new 结构体的定义如代码清单 17.16 所示。
代码清单 17.16 snd_kcontrol_new 结构体
include/sound/control.h
//定义函数类型
typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo);
typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /*接口 ID,SNDRV_CTL_ELEM_IFACE_XXX */
unsigned int device; /* 设备号 */
unsigned int subdevice; /* 子流(子设备)号 */
unsigned char *name; /* 名称(ASCII 格式) */
unsigned int index; /* 索引 */
unsigned int access; /* 访问权限 */
unsigned int count; /* 享用元素的数量 */
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;
};
分析:
iface 定义 control 的类型,形式为 SNDRV_CTL_ELEM_IFACE_XXX,通常是 MIXER,对于不属于 mixer 的全局控制,使用 CARD。如果关联于某类设备,则使用 HWDEP、PCM、RAWMIDI、TIMER 或 SEQUENCER。
name 是名称标识字符串,control 的名称非常重要,因为 control 的作用由名称来区分。对于名称相同的 control,使用 index 区分。name 定义的标准是“SOURCE DIRECTION FUNCTION”即“源方向功能”,SOURCE 定义control 的源,如“Master”、“PCM”、“CD”和“Line”,方向则为“Playback”、“Capture”、“Bypass Playback”或“Bypass Capture”,如果方向省略,意味着 playback 和 capture 双向,第 3 个参数可以是“Switch”、“Volume”和“Route”等。
“SOURCE DIRECTION FUNCTION”格式的名称例子如“Master Capture Switch”、“PCM Playback
Volume”。
下面几种 control 的命名不采用“SOURCE DIRECTION FUNCTION”格式,属于例外。
(1)全局控制。
“Capture Source”、“Capture Switch”和“Capture Volume”用于全局录音源、输入开关和录音音量控制;“Playback Switch”、“Playback Volume”用于全局输出开关和音量控制。
(2)音调控制。
音调控制名称的形式为“Tone Control – XXX”,例如“Tone Control – Switch”、“Tone Control – Bas”和“Tone Control – Center”。
(3)3D 控制。
3D 控制名称的形式为“3D Control – XXX”,例如“3D Control – Switch”、 “3D Control – Center”
和“3D Control – Space”。
(4)麦克风增益(Mic boost)。
麦克风增益被设置为“Mic Boost”或“Mic Boost (6dB)”。
access 字段是访问控制权限,形式如 SNDRV_CTL_ELEM_ACCESS_XXX。SNDRV_CTL_ELEM_ACCESS_READ 意味着只读,这时 put()函数不必实现;SNDRV_CTL_ELEM_ACCESS_WRITE 意味着只写,这时 get()函数不必实现。若 control值频繁变化,则需定义 VOLATILE 标志。当 control 处于非激活状态时,应设置 INACTIVE
标志。
private_value 字段包含一个长整型值,可以通过该字段给 info()、get()和 put()函数传递参数。
2.info()函数
用于获得该 control 的详细信息,该函数必须填充传递给它的第二个参数 snd_ctl_elem_info 结构体,info()函数的形式如下:
static int snd _ xxxctl _ info(struct snd _ kcontrol *kcontrol, struct snd _ ctl _ elem _ info *uinfo);
snd_ctl_elem_info 结构体的定义如代码清单 17.17 所示。
代码清单 17.17 snd_ctl_elem_info 结构体
include/sound/asound.h
struct snd_ctl_elem_info {
struct snd_ctl_elem_id id; /* W: element ID */
snd_ctl_elem_type_t type; /* R: value type - SNDRV_CTL_ELEM_TYPE_* */
unsigned int access; /* R: value access (bitmask) - SNDRV_CTL_ELEM_ACCESS_* */
unsigned int count; /* count of values */
pid_t owner; /* owner's PID of this control */
union {
struct {
long min; /* R: minimum value */
long max; /* R: maximum value */
long step; /* R: step (0 variable) */
} integer;
struct {
long long min; /* R: minimum value */
long long max; /* R: maximum value */
long long step; /* R: step (0 variable) */
} integer64;
struct {
unsigned int items; /* R: number of items */
unsigned int item; /* W: item number */
char name[64]; /* R: value name */
} enumerated;
unsigned char reserved[128];
} value;
union {
unsigned short d[4]; /* dimensions */
unsigned short *d_ptr; /* indirect */
} dimen;
unsigned char reserved[64-4*sizeof(unsigned short)];
};
分析:
type 字段定义control 的类型,包括 BOOLEAN、INTEGER、ENUMERATED、BYTES、IEC958 和 INTEGER64。count 字段定义control 中包含的元素的数量,例如一个立体声音量 control 的 count = 2。value 是一个共用体,其所存储的值的具体类型依赖于 type。代码清单 17.18 所示为一个 info()函数填充 snd_ctl_elem_info 结构体的范例。
代码清单 17.18 snd_ctl_elem_info 结构体中的 info()函数范例
static int snd _ xxxctl _ info(struct snd _ kcontrol *kcontrol, struct snd _ ctl _ elem _ info *uinfo)
{
uinfo->type = SNDRV _ CTL _ ELEM _ TYPE _ BOOLEAN;/* 类型为 BOOLEAN */
uinfo->count = 1;/* 数量为 1 */
uinfo->value.integer.min = 0;/* 最小值为 0 */
uinfo->value.integer.max = 1;/* 最大值为 1 */
return 0;
}
枚举类型和其他类型略有不同,对枚举类型,应为目前项目索引设置名称字符串,如代码清单 17.19 所示。
代码清单 17.19 填充 snd_ctl_elem_info 结构体中的 枚举类型值
static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
/* 值名称字符串*/
static char *texts[4] = {"First", "Second", "Third", "Fourth"};
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;/* 枚举类型*/uinfo->count = 1;/* 数量为 1 */
uinfo->value.enumerated.items = 4;/* 项目数量为 1 */
/* 超过 3 的项目号改为 3 */
if (uinfo->value.enumerated.item > 3)
uinfo->value.enumerated.item = 3;
/* 为目前项目索引复制名称字符串*/
strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
return 0;
}
3.get()函数
用于得到 control 的目前值并返回用户空间,代码清单 17.20 所示为 get()函数的范例。
代码清单 17.20 snd_ctl_elem_info 结构体中的 get()函数范例
static int snd _ xxxctl _ get(struct snd _ kcontrol *kcontrol, struct snd _ ctl _ elem _ value *ucontrol)
{
/* 从 snd _ kcontrol 获得 xxxchip 指针*/
struct xxxchip *chip = snd _ kcontrol _ chip(kcontrol);
/* 从 xxxchip 获得值并写入 snd _ ctl _ elem _ value */
ucontrol->value.integer.value[0] = get _ some _ value(chip);
return 0;
}
get()函数的第二个参数的类型为 snd_ctl_elem_value,其定义如代码清单 17.21 所示。
snd_ctl_elem_ value 结构体的内部也包含一个由 integer、integer64、enumerated 等组成的值共用体,其具体类型依赖于 control 的类型和 info()函数。
代码清单 17.21 snd_ctl_elem_value 结构体
include/sound/asound.h
struct snd_ctl_elem_value {
struct snd_ctl_elem_id id; /* W: 元素 ID */
unsigned int indirect: 1; /* W: 使用间接指针(xxx_ptr 成员) */
/* 值共用体*/
union {
long value[128];
long *value_ptr;
} integer;
union {
long long value[64];
long long *value_ptr;
} integer64;
union {
unsigned int item[128];
unsigned int *item_ptr;
} enumerated;
union {
unsigned char data[512];
unsigned char *data_ptr;
} bytes;
struct snd_aes_iec958 iec958;
} value; /* 只读 */
struct timespec tstamp;
unsigned char reserved[ 128-sizeof(struct timespec)];
};
4.put()函数
用于从用户空间写入值,如果值被改变,该函数返回 1,否则返回 0;如果发生错误,该函数返回一个错误码。代码清单 17.22 所示为一个 put()函数的范例。
代码清单 17.22 snd_ctl_elem_info 结构体中的 put()函数范例
static int snd_xxxctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
/* 从 snd_kcontrol 获得 xxxchip 指针*/
struct xxxchip *chip = snd_kcontrol_chip(kcontrol); // 获取芯片特定的数据结构体指针
int changed = 0;/* 默认返回值为 0 */
/* 值被改变*/
if (chip->current_value != ucontrol->value.integer.value[0]) {
change_current_value(chip, ucontrol->value.integer.value[0]);
changed = 1;/* 返回值为 1 */
}
return changed;
}
对于 get()和 put()函数,如果 control 有多于一个元素,即 count >1,则每个元素都需要被返回或写入。
5.构造 control
当所有事情准备好后,需要创建一个 control,
调用 snd_ctl_add()和 snd_ctl_new1()这两个函数来完成,这两个函数的原型为:
include/sound/control.h
int snd_ctl_add(struct snd_card * card, struct snd_kcontrol * kcontrol);
sound/core/control.c
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;
snd_assert(card != NULL, goto error);
snd_assert(kcontrol->info != NULL, goto error);
id = kcontrol->id;
down_write(&card->controls_rwsem);
if (snd_ctl_find_id(card, &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) {
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;
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);
struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new * kcontrolnew, void * private_data);
snd_ctl_new1()函数用于创建一个 snd_kcontrol 并返回其指针,snd_ctl_add()函数用于将创建的snd_kcontrol 添加到对应的 card 中。
6.变更通知
如果驱动中需要在中断服务程序中改变或更新一个 control,调用 snd_ctl_notify()函数,此函数原型为:
include/sound/control.h
void snd_ctl_notify(struct snd_card * card, unsigned int mask, struct snd_ctl_elem_id * id);
sound/core/control.c
void snd_ctl_notify(struct snd_card *card, unsigned int mask, struct snd_ctl_elem_id *id)
{
unsigned long flags;
struct snd_ctl_file *ctl;
struct snd_kctl_event *ev;
snd_assert(card != NULL && id != NULL, return);
read_lock(&card->ctl_files_rwlock);
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
card->mixer_oss_change_count++;
#endif
list_for_each_entry(ctl, &card->ctl_files, list) {
if (!ctl->subscribed)
continue;
spin_lock_irqsave(&ctl->read_lock, flags);
list_for_each_entry(ev, &ctl->events, list) {
if (ev->id.numid == id->numid) {
ev->mask |= mask;
goto _found;
}
}
ev = kzalloc(sizeof(*ev), GFP_ATOMIC);
if (ev) {
ev->id = *id;
ev->mask = mask;
list_add_tail(&ev->list, &ctl->events);
} else {
snd_printk(KERN_ERR "No memory available to allocate event\n");
}
_found:
wake_up(&ctl->change_sleep);
spin_unlock_irqrestore(&ctl->read_lock, flags);
kill_fasync(&ctl->fasync, SIGIO, POLL_IN);
}
read_unlock(&card->ctl_files_rwlock);
}
EXPORT_SYMBOL(snd_ctl_notify);
分析:该函数的第二个参数为事件掩码(event-mask),第三个参数为该通知的 control 元素 id 指针。
例如,如下语句定义的事件掩码 SNDRV_CTL_EVENT_MASK_VALUE 意味着 control 值的改变被通知:
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id _ pointer);
17.4.5 AC97 API 接口
ALSA AC97 编解码层被很好地定义,利用它,驱动工程师只需编写少量底层的控制函数。
1.AC97 实例构造
为了创建一个 AC97 实例,首先需要调用 snd_ac97_bus()函数构建 AC97 总线及其操作,这个函数的原型为:
include/sound/ac97_codec.h
/* create new AC97 bus */
int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops *ops, void *private_data, struct snd_ac97_bus **rbus);
sound/pci/ac97/ac97_codec.c
/**
* snd_ac97_bus - create an AC97 bus component
* @card: the card instance
* @num: the bus number
* @ops: the bus callbacks table
* @private_data: private data pointer for the new instance
* @rbus: the pointer to store the new AC97 bus instance.
*
* Creates an AC97 bus component. An struct snd_ac97_bus instance is newly
* allocated and initialized.
*
* The ops table must include valid callbacks (at least read and
* write). The other callbacks, wait and reset, are not mandatory.
*
* The clock is set to 48000. If another clock is needed, set
* (*rbus)->clock manually.
*
* The AC97 bus instance is registered as a low-level device, so you don't
* have to release it manually.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_ac97_bus(struct snd_card *card, int num, struct snd_ac97_bus_ops *ops, void *private_data, struct snd_ac97_bus **rbus)
{
int err;
struct snd_ac97_bus *bus;
static struct snd_device_ops dev_ops = {
.dev_free = snd_ac97_bus_dev_free,
};
snd_assert(card != NULL, return -EINVAL);
snd_assert(rbus != NULL, return -EINVAL);
bus = kzalloc(sizeof(*bus), GFP_KERNEL);
if (bus == NULL)
return -ENOMEM;
bus->card = card;
bus->num = num;
bus->ops = ops;
bus->private_data = private_data;
bus->clock = 48000;
spin_lock_init(&bus->bus_lock);
snd_ac97_bus_proc_init(bus);
if ((err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops)) < 0) {
snd_ac97_bus_free(bus);
return err;
}
*rbus = bus;
return 0;
}
EXPORT_SYMBOL(snd_ac97_bus);
该函数的第 3 个参数 ops 是一个 snd_ac97_bus_ops 结构体,其定义如代码清单 17.23 所示。
代码清单 17.23 snd_ac97_bus_ops 结构体
include/sound/ac97_codec.h
struct snd_ac97_bus_ops {
void (*reset) (struct snd_ac97 *ac97);
void (*warm_reset)(struct snd_ac97 *ac97);
void (*write) (struct snd_ac97 *ac97, unsigned short reg, unsigned short val);
unsigned short (*read) (struct snd_ac97 *ac97, unsigned short reg);
void (*wait) (struct snd_ac97 *ac97);
void (*init) (struct snd_ac97 *ac97);
};
接下来,调用 snd_ac97_mixer()函数注册混音器,这个函数的原型为:
include/sound/ac97_codec.h
/* create mixer controls */
int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template,
struct snd_ac97 **rac97);
sound/pci/ac97/ac97_codec.c
/**
* snd_ac97_mixer - create an Codec97 component
* @bus: the AC97 bus which codec is attached to
* @template: the template of ac97, including index, callbacks and
* the private data.
* @rac97: the pointer to store the new ac97 instance.
*
* Creates an Codec97 component. An struct snd_ac97 instance is newly
* allocated and initialized from the template. The codec
* is then initialized by the standard procedure.
*
* The template must include the codec number (num) and address (addr),
* and the private data (private_data).
*
* The ac97 instance is registered as a low-level device, so you don't
* have to release it manually.
*
* Returns zero if successful, or a negative error code on failure.
*/
int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, struct snd_ac97 **rac97)
{
int err;
struct snd_ac97 *ac97;
struct snd_card *card;
char name[64];
unsigned long end_time;
unsigned int reg;
const struct ac97_codec_id *pid;
static struct snd_device_ops ops = {
.dev_free = snd_ac97_dev_free,
.dev_register = snd_ac97_dev_register,
.dev_disconnect = snd_ac97_dev_disconnect,
};
snd_assert(rac97 != NULL, return -EINVAL);
*rac97 = NULL;
snd_assert(bus != NULL && template != NULL, return -EINVAL);
snd_assert(template->num < 4 && bus->codec[template->num] == NULL, return -EINVAL);
card = bus->card;
ac97 = kzalloc(sizeof(*ac97), GFP_KERNEL);
if (ac97 == NULL)
return -ENOMEM;
ac97->private_data = template->private_data;
ac97->private_free = template->private_free;
ac97->bus = bus;
ac97->pci = template->pci;
ac97->num = template->num;
ac97->addr = template->addr;
ac97->scaps = template->scaps;
ac97->res_table = template->res_table;
bus->codec[ac97->num] = ac97;
mutex_init(&ac97->reg_mutex);
mutex_init(&ac97->page_mutex);
#ifdef CONFIG_SND_AC97_POWER_SAVE
INIT_DELAYED_WORK(&ac97->power_work, do_update_power);
#endif
#ifdef CONFIG_PCI
if (ac97->pci) {
pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_VENDOR_ID, &ac97->subsystem_vendor);
pci_read_config_word(ac97->pci, PCI_SUBSYSTEM_ID, &ac97->subsystem_device);
}
#endif
if (bus->ops->reset) {
bus->ops->reset(ac97);
goto __access_ok;
}
ac97->id = snd_ac97_read(ac97, AC97_VENDOR_ID1) << 16;
ac97->id |= snd_ac97_read(ac97, AC97_VENDOR_ID2);
if (ac97->id && ac97->id != (unsigned int)-1) {
pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id);
if (pid && (pid->flags & AC97_DEFAULT_POWER_OFF))
goto __access_ok;
}
/* reset to defaults */
if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO))
snd_ac97_write(ac97, AC97_RESET, 0);
if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM))
snd_ac97_write(ac97, AC97_EXTENDED_MID, 0);
if (bus->ops->wait)
bus->ops->wait(ac97);
else {
udelay(50);
if (ac97->scaps & AC97_SCAP_SKIP_AUDIO)
err = ac97_reset_wait(ac97, HZ/2, 1);
else {
err = ac97_reset_wait(ac97, HZ/2, 0);
if (err < 0)
err = ac97_reset_wait(ac97, HZ/2, 1);
}
if (err < 0) {
snd_printk(KERN_WARNING "AC'97 %d does not respond - RESET\n", ac97->num);
/* proceed anyway - it's often non-critical */
}
}
__access_ok:
ac97->id = snd_ac97_read(ac97, AC97_VENDOR_ID1) << 16;
ac97->id |= snd_ac97_read(ac97, AC97_VENDOR_ID2);
if (! (ac97->scaps & AC97_SCAP_DETECT_BY_VENDOR) &&
(ac97->id == 0x00000000 || ac97->id == 0xffffffff)) {
snd_printk(KERN_ERR "AC'97 %d access is not valid [0x%x], removing mixer.\n", ac97->num, ac97->id);
snd_ac97_free(ac97);
return -EIO;
}
pid = look_for_codec_id(snd_ac97_codec_ids, ac97->id);
if (pid)
ac97->flags |= pid->flags;
/* test for AC'97 */
if (!(ac97->scaps & AC97_SCAP_SKIP_AUDIO) && !(ac97->scaps & AC97_SCAP_AUDIO)) {
/* test if we can write to the record gain volume register */
snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x8a06);
if (((err = snd_ac97_read(ac97, AC97_REC_GAIN)) & 0x7fff) == 0x0a06)
ac97->scaps |= AC97_SCAP_AUDIO;
}
if (ac97->scaps & AC97_SCAP_AUDIO) {
ac97->caps = snd_ac97_read(ac97, AC97_RESET);
ac97->ext_id = snd_ac97_read(ac97, AC97_EXTENDED_ID);
if (ac97->ext_id == 0xffff) /* invalid combination */
ac97->ext_id = 0;
}
/* test for MC'97 */
if (!(ac97->scaps & AC97_SCAP_SKIP_MODEM) && !(ac97->scaps & AC97_SCAP_MODEM)) {
ac97->ext_mid = snd_ac97_read(ac97, AC97_EXTENDED_MID);
if (ac97->ext_mid == 0xffff) /* invalid combination */
ac97->ext_mid = 0;
if (ac97->ext_mid & 1)
ac97->scaps |= AC97_SCAP_MODEM;
}
if (!ac97_is_audio(ac97) && !ac97_is_modem(ac97)) {
if (!(ac97->scaps & (AC97_SCAP_SKIP_AUDIO|AC97_SCAP_SKIP_MODEM)))
snd_printk(KERN_ERR "AC'97 %d access error (not audio or modem codec)\n", ac97->num);
snd_ac97_free(ac97);
return -EACCES;
}
if (bus->ops->reset) // FIXME: always skipping?
goto __ready_ok;
/* FIXME: add powerdown control */
if (ac97_is_audio(ac97)) {
/* nothing should be in powerdown mode */
snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0);
if (! (ac97->flags & AC97_DEFAULT_POWER_OFF)) {
snd_ac97_write_cache(ac97, AC97_RESET, 0); /* reset to defaults */
udelay(100);
snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0);
}
/* nothing should be in powerdown mode */
snd_ac97_write_cache(ac97, AC97_GENERAL_PURPOSE, 0);
end_time = jiffies + (HZ / 10);
do {
if ((snd_ac97_read(ac97, AC97_POWERDOWN) & 0x0f) == 0x0f)
goto __ready_ok;
schedule_timeout_uninterruptible(1);
} while (time_after_eq(end_time, jiffies));
snd_printk(KERN_WARNING "AC'97 %d analog subsections not ready\n", ac97->num);
}
/* FIXME: add powerdown control */
if (ac97_is_modem(ac97)) {
unsigned char tmp;
/* nothing should be in powerdown mode */
/* note: it's important to set the rate at first */
tmp = AC97_MEA_GPIO;
if (ac97->ext_mid & AC97_MEI_LINE1) {
snd_ac97_write_cache(ac97, AC97_LINE1_RATE, 8000);
tmp |= AC97_MEA_ADC1 | AC97_MEA_DAC1;
}
if (ac97->ext_mid & AC97_MEI_LINE2) {
snd_ac97_write_cache(ac97, AC97_LINE2_RATE, 8000);
tmp |= AC97_MEA_ADC2 | AC97_MEA_DAC2;
}
if (ac97->ext_mid & AC97_MEI_HANDSET) {
snd_ac97_write_cache(ac97, AC97_HANDSET_RATE, 8000);
tmp |= AC97_MEA_HADC | AC97_MEA_HDAC;
}
snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0);
udelay(100);
/* nothing should be in powerdown mode */
snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0);
end_time = jiffies + (HZ / 10);
do {
if ((snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS) & tmp) == tmp)
goto __ready_ok;
schedule_timeout_uninterruptible(1);
} while (time_after_eq(end_time, jiffies));
snd_printk(KERN_WARNING "MC'97 %d converters and GPIO not ready (0x%x)\n", ac97->num, snd_ac97_read(ac97, AC97_EXTENDED_MSTATUS));
}
__ready_ok:
if (ac97_is_audio(ac97))
ac97->addr = (ac97->ext_id & AC97_EI_ADDR_MASK) >> AC97_EI_ADDR_SHIFT;
else
ac97->addr = (ac97->ext_mid & AC97_MEI_ADDR_MASK) >> AC97_MEI_ADDR_SHIFT;
if (ac97->ext_id & 0x01c9) { /* L/R, MIC, SDAC, LDAC VRA support */
reg = snd_ac97_read(ac97, AC97_EXTENDED_STATUS);
reg |= ac97->ext_id & 0x01c0; /* LDAC/SDAC/CDAC */
if (! bus->no_vra)
reg |= ac97->ext_id & 0x0009; /* VRA/VRM */
snd_ac97_write_cache(ac97, AC97_EXTENDED_STATUS, reg);
}
if ((ac97->ext_id & AC97_EI_DRA) && bus->dra) {
/* Intel controllers require double rate data to be put in
* slots 7+8, so let's hope the codec supports it. */
snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, AC97_GP_DRSS_MASK, AC97_GP_DRSS_78);
if ((snd_ac97_read(ac97, AC97_GENERAL_PURPOSE) & AC97_GP_DRSS_MASK) == AC97_GP_DRSS_78)
ac97->flags |= AC97_DOUBLE_RATE;
/* restore to slots 10/11 to avoid the confliction with surrounds */
snd_ac97_update_bits(ac97, AC97_GENERAL_PURPOSE, AC97_GP_DRSS_MASK, 0);
}
if (ac97->ext_id & AC97_EI_VRA) { /* VRA support */
snd_ac97_determine_rates(ac97, AC97_PCM_FRONT_DAC_RATE, 0, &ac97->rates[AC97_RATES_FRONT_DAC]);
snd_ac97_determine_rates(ac97, AC97_PCM_LR_ADC_RATE, 0, &ac97->rates[AC97_RATES_ADC]);
} else {
ac97->rates[AC97_RATES_FRONT_DAC] = SNDRV_PCM_RATE_48000;
if (ac97->flags & AC97_DOUBLE_RATE)
ac97->rates[AC97_RATES_FRONT_DAC] |= SNDRV_PCM_RATE_96000;
ac97->rates[AC97_RATES_ADC] = SNDRV_PCM_RATE_48000;
}
if (ac97->ext_id & AC97_EI_SPDIF) {
/* codec specific code (patch) should override these values */
ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_32000;
}
if (ac97->ext_id & AC97_EI_VRM) { /* MIC VRA support */
snd_ac97_determine_rates(ac97, AC97_PCM_MIC_ADC_RATE, 0, &ac97->rates[AC97_RATES_MIC_ADC]);
} else {
ac97->rates[AC97_RATES_MIC_ADC] = SNDRV_PCM_RATE_48000;
}
if (ac97->ext_id & AC97_EI_SDAC) { /* SDAC support */
snd_ac97_determine_rates(ac97, AC97_PCM_SURR_DAC_RATE, AC97_PCM_FRONT_DAC_RATE, &ac97->rates[AC97_RATES_SURR_DAC]);
ac97->scaps |= AC97_SCAP_SURROUND_DAC;
}
if (ac97->ext_id & AC97_EI_LDAC) { /* LDAC support */
snd_ac97_determine_rates(ac97, AC97_PCM_LFE_DAC_RATE, AC97_PCM_FRONT_DAC_RATE, &ac97->rates[AC97_RATES_LFE_DAC]);
ac97->scaps |= AC97_SCAP_CENTER_LFE_DAC;
}
/* additional initializations */
if (bus->ops->init)
bus->ops->init(ac97);
snd_ac97_get_name(ac97, ac97->id, name, !ac97_is_audio(ac97));
snd_ac97_get_name(NULL, ac97->id, name, !ac97_is_audio(ac97)); // ac97->id might be changed in the special setup code
if (! ac97->build_ops)
ac97->build_ops = &null_build_ops;
if (ac97_is_audio(ac97)) {
char comp[16];
if (card->mixername[0] == '\0') {
strcpy(card->mixername, name);
} else {
if (strlen(card->mixername) + 1 + strlen(name) + 1 <= sizeof(card->mixername)) {
strcat(card->mixername, ",");
strcat(card->mixername, name);
}
}
sprintf(comp, "AC97a:%08x", ac97->id);
if ((err = snd_component_add(card, comp)) < 0) {
snd_ac97_free(ac97);
return err;
}
if (snd_ac97_mixer_build(ac97) < 0) {
snd_ac97_free(ac97);
return -ENOMEM;
}
}
if (ac97_is_modem(ac97)) {
char comp[16];
if (card->mixername[0] == '\0') {
strcpy(card->mixername, name);
} else {
if (strlen(card->mixername) + 1 + strlen(name) + 1 <= sizeof(card->mixername)) {
strcat(card->mixername, ",");
strcat(card->mixername, name);
}
}
sprintf(comp, "AC97m:%08x", ac97->id);
if ((err = snd_component_add(card, comp)) < 0) {
snd_ac97_free(ac97);
return err;
}
if (snd_ac97_modem_build(card, ac97) < 0) {
snd_ac97_free(ac97);
return -ENOMEM;
}
}
if (ac97_is_audio(ac97))
update_power_regs(ac97);
snd_ac97_proc_init(ac97);
if ((err = snd_device_new(card, SNDRV_DEV_CODEC, ac97, &ops)) < 0) {
snd_ac97_free(ac97);
return err;
}
*rac97 = ac97;
return 0;
}
EXPORT_SYMBOL(snd_ac97_mixer);
代码清单 17.24 所示为 AC97 实例的创建过程。
代码清单 17.24 AC97 实例的创建过程范例
struct snd_ac97_bus *bus;
/* AC97 总线操作*/
static struct snd_ac97_bus_ops ops = {
.write = snd_mychip_ac97_write,
.read = snd_mychip_ac97_read,
};
/* AC97 总线与操作创建*/
snd_ac97_bus(card, 0, &ops, NULL, &bus);
/* AC97 模板*/
struct snd_ac97_template ac97;
int err;
memset(&ac97, 0, sizeof(ac97));
ac97.private_data = chip;/* 私有数据*/
/* 注册混音器 */
snd_ac97_mixer(bus, &ac97, &chip->ac97);
如果一个声卡上包含多个编解码器,这种情况下,需要多次调用snd_ac97_mixer()并对snd_ac97 的num 成员(编解码器序号)赋予相应的序号。驱动中可以为不同的编解码器编写不同的snd_ac97_bus_ops成员函数中,或者只是在相同的一套成员函数中通过ac97.num 获得序号后再区分进行具体的操作。
2.snd_ac97_bus_ops 成员函数
snd_ac97_bus_ops 结构体中的 read()和 write()成员函数完成底层的硬件访问,reset()函数用于复位编解码器,wait()函数用于编解码器标准初始化过程中的特定等待,如果芯片要求额外的等待时间,则应实现这个函数,init()用于完成编解码器附加的初始化。代码清单 17.25所示为 read()和 write()函数的范例。
代码清单 17.25 snd_ac97_bus_ops 结构体中的 read()和 write()函数范例
static unsigned short snd _ xxxchip _ ac97 _ read(struct snd _ ac97 *ac97, unsigned short reg)
{
struct xxxchip *chip = ac97->private _ data; // 获取芯片特定的数据结构体指针
...
return the_register_value; /* 返回寄存器值*/
}
static void snd _ xxxchip _ ac97 _ write(struct snd _ ac97 *ac97, unsigned short reg,
unsigned short val)
{
struct xxxchip *chip = ac97->private _ data;// 获取芯片特定的数据结构体指针
...
/* 将被给的寄存器值写入 codec */
}
3.修改寄存器
如果需要在驱动中访问编解码器,可使用如下函数:
include/sound/ac97_codec.h
void snd_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);
int snd_ac97_update(struct snd_ac97 *ac97, unsigned short reg, unsigned short value);
int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg, unsigned short mask, unsigned short value);
unsigned short snd_ac97_read(struct snd_ac97 *ac97, unsigned short reg);
snd_ac97_update()与 snd_ac97_write()的区别在于snd_ac97_update()在值已经设置的情况下不会再设置,而snd_ac97_write()则会再写一次。snd_ac97_update_bits()用于更新寄存器的某些位,由 mask 决定。
snd_ac97_set_rate()用于设置采样率,函数原型为:
int snd_ac97_set_rate(struct snd _ ac97 *ac97, int reg, unsigned int rate);
分析:
snd_ac97_set_rate()的第二个参数 reg 可以是 AC97_PCM_MIC_ADC_RATE、
AC97_PCM_FRONT_DAC_RATE、AC97_PCM_LR_ADC_RATE 和 AC97_SPDIF,对于 AC97 _SPDIF ,寄存器并非真地被改变了,只是相应的 IEC958 状态位将被更新。
4.时钟调整
在一些芯片上,编解码器的时钟频率不是 48000Hz,而是使用 PCI 时钟以节省一个晶振,在这种情况下,应该改变 bus->clock 为相应的值。
5.proc 文件
ALSA AC97接口会创建如/proc/asound/card0/codec97#0/ac97#0-0和ac97#0-0+regs这样的proc文件,通过这些文件可以查看编解码器目前的状态和寄存器。
如果一个芯片上有多个 codecs,可多次调用 snd_ac97_mixer()来注册混音器。
17.4.6 ALSA 用户空间编程
ALSA 驱动的声卡在用户空间不宜直接使用文件接口,而应使用 alsa-lib。