第17章Linux音频设备驱动之Linux ALSA 音频设备驱动(二)

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 {
                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。



猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/80731581