程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - ASoC 声卡之Jack设备

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :2023.04
linux   :6.3
----------------------------------------------------------------------------------------------------------------------------

jack字面上就是插孔,什么是插孔,我理解就是我们音频设备上的麦克风、耳机等设备的插孔。

那jack设备是用来实现什么功能的呢?其主要目的是为了实现耳机、麦克风等设备插入和拔出的检测。

我们以耳机检测为例来说说,一般的耳机检测包括普通的耳机检测和带有麦克风的耳机检测两种,这两种耳机统称为Headset,而对于不带麦克风的耳机,一般统称为Headphone。

对于Headset装置的插入检测,一般是通过jack来完成,大致原理是使用带检测机械结构的耳机插座,将检测引脚连接到SoC的GPIO口,如下图所示,当耳机插入时。耳机插头的金属会碰到检测引脚,使得检测引脚的电平发生改变,从而触发中断。这样就可以在中断处理函数中读取GPIO口的值,进一步判断是耳机插入还是拔出。

ALSA CORE已经实现了jack中间层,在文件include/sound/jack.h中提供了访问jack中间层代码的API。

一、ALSA核心数据结构

1.1 struct snd_jack 

Jack使用struct snd_jack数据结构来描述;

struct snd_jack {
        struct list_head kctl_list;
        struct snd_card *card;
        const char *id;
#ifdef CONFIG_SND_JACK_INPUT_DEV
        struct input_dev *input_dev;
        struct mutex input_dev_lock;
        int registered;
        int type;
        char name[100];
        unsigned int key[6];   /* Keep in sync with definitions above */
#endif /* CONFIG_SND_JACK_INPUT_DEV */
        int hw_status_cache;
        void *private_data;
        void (*private_free)(struct snd_jack *);
};

其中:

  • kctl_list:保存jack kcontrol的链表,链表中的每一个元素都是struct snd_jack_kctl;
  • card:所属声卡设备;
  • id:jack的唯一标识符;
  • input_dev:输入设备结构体(struct input_dev)指针,用于处理与jack相关的输入事件;
  • input_dev_lock:输入设备锁,用于保护对输入设备的操作;
  • registered:jack是否已经注册的标志;
  • type:jack能够上报的类型,参考enum snd_jack_types;
  • name:jack的名称字符串,长度为100字节;
  • key:如果jack能够上报SND_JACK_BTN_0、SND_JACK_BTN_1等类型,数组元素key[x]存放SND_JACK_BTN_x对应的EV_KEY事件的事件编码;
  • hw_status_cache:硬件状态的缓存值;
  • private_data:私有数据指针,可以用于存储与jack相关的其他数据;
  • private_free:私有数据释放函数指针,用于在释放snd_jack时释放相关的私有数据;

1.2 enum snd_jack_types 

jack能够上报的类型如结构体定义描述;

/**
 * enum snd_jack_types - Jack types which can be reported
 * @SND_JACK_HEADPHONE: Headphone
 * @SND_JACK_MICROPHONE: Microphone
 * @SND_JACK_HEADSET: Headset
 * @SND_JACK_LINEOUT: Line out
 * @SND_JACK_MECHANICAL: Mechanical switch
 * @SND_JACK_VIDEOOUT: Video out
 * @SND_JACK_AVOUT: AV (Audio Video) out
 * @SND_JACK_LINEIN:  Line in
 * @SND_JACK_BTN_0: Button 0
 * @SND_JACK_BTN_1: Button 1
 * @SND_JACK_BTN_2: Button 2
 * @SND_JACK_BTN_3: Button 3
 * @SND_JACK_BTN_4: Button 4
 * @SND_JACK_BTN_5: Button 5
 *
 * These values are used as a bitmask.
 *
 * Note that this must be kept in sync with the lookup table in
 * sound/core/jack.c.
 */
enum snd_jack_types {
        SND_JACK_HEADPHONE      = 0x0001,  // 如果jack->type & 0x001=0x001,则设置input设备可以上报EV_SW事件,事件编码为0x02
        SND_JACK_MICROPHONE     = 0x0002,  // 同上,设置input设备可以上报EV_SW事件,事件编码为0x04
        SND_JACK_HEADSET        = SND_JACK_HEADPHONE | SND_JACK_MICROPHONE,
        SND_JACK_LINEOUT        = 0x0004,  // 同上,设置input设备可以上报EV_SW事件,事件编码为0x04
        SND_JACK_MECHANICAL     = 0x0008, /* If detected separately */ 同上,设置input设备可以上报EV_SW事件,事件编码为0x07
        SND_JACK_VIDEOOUT       = 0x0010,  // 同上,设置input设备可以上报EV_SW事件,事件编码为0x08
        SND_JACK_AVOUT          = SND_JACK_LINEOUT | SND_JACK_VIDEOOUT,
        SND_JACK_LINEIN         = 0x0020,  // 同上,则设置input设备可以上报EV_SW事件,事件编码为0x0d

        /* Kept separate from switches to facilitate implementation */
        SND_JACK_BTN_0          = 0x4000,   // 如果jack->type & 0x4000=0x4000,则设置input设备可以上报EV_KEY事件,事件编码为0x100
        SND_JACK_BTN_1          = 0x2000,   // 同上,设置input设备可以上报EV_KEY事件,事件编码为0x101
        SND_JACK_BTN_2          = 0x1000,   // 同上,设置input设备可以上报EV_KEY事件,事件编码为0x102
        SND_JACK_BTN_3          = 0x0800,   // 同上,设置input设备可以上报EV_KEY事件,事件编码为0x103
        SND_JACK_BTN_4          = 0x0400,   // 同上,设置input设备可以上报EV_KEY事件,事件编码为0x104
        SND_JACK_BTN_5          = 0x0200,   // 同上。设置input设备可以上报EV_KEY事件,事件编码为0x105
};

1.3 struct snd_jack_kctl

ALSA中使用struct snd_jack_kctl数据结构来描述jack中的kcontrol,定义在sound/core/jack.c:

struct snd_jack_kctl {
        struct snd_kcontrol *kctl;
        struct list_head list;  /* list of controls belong to the same jack */
        unsigned int mask_bits; /* only masked status bits are reported via kctl */
        struct snd_jack *jack;  /* pointer to struct snd_jack */
        bool sw_inject_enable;  /* allow to inject plug event via debugfs */
#ifdef CONFIG_SND_JACK_INJECTION_DEBUG
        struct dentry *jack_debugfs_root; /* jack_kctl debugfs root */
#endif
};

其中:

  • kctl:指向struct snd_kcontrol的指针;
  • list:链表节点,用于将当前节点链接到snd_jack的kctl_list链表;
  • mask_bits:当前jack control能够上报的类型,参考enum snd_jack_types;比如我们设置了mask_bits值为SND_JACK_HEADPHONE,当耳机插入或者拔出的时候HP_DET引脚就会接收到中断,中断处理程序读取引脚电平,并通过input设备上报SND_JACK_HEADPHONE对应的EV_SK事件,事件编码为0x02,值为插入/拔出;
  • jack:指向struct snd_jack的指针,表示与snd_jack_kctl相关联的jack;
  • sw_inject_enable:允许通过debugfs注入插头事件;
  • jack_debugfs_root:jack_kctl debugfs根目录;

1.4 关系图

为了更加清晰的了解struct snd_jack 、struct snd_kcontrol、struct snd_jack_kctl 等数据结构的关系,我们绘制了如下关系图:

其中我们比较关注的点是:

  • jack是挂在snd_card成员devices链表下面的一个snd_device;
  • snd_jack中的字段:kctl_list,该链表保存所有的jack kcontrol,每个jack kcontrol都关联一个kcontrol;
  • snd_jack中的字段:input_dev是一个input设备,用于上报耳机插入/拔出、麦克风插入/拔出等事件。

二、ALSA核心API

2.1 创建Jack设备

jack设备的创建可以通过snd_jack_new函数来完成,函数接收6个参数:

  • card:ALSA声卡设备;
  • id:jack设备唯一标识;
  • type:由枚举类型snd_jack_type的位掩码组成,表示该jack能够上报的类型;这里我们可以理解为jack能够检测的类型,比如耳机插入/拔出;
  • jack:jack指针的指针,用于保存分配的snd_jack;
  • initial_kctl: 如果为真,则创建一个jack kcontrol并将其添加到jack的kctl_list链表中;
  • phantom_jack:对于虚拟jack,不创建输入设备;

函数定义在sound/core/jack.c;

/**
 * snd_jack_new - Create a new jack
 * @card:  the card instance
 * @id:    an identifying string for this jack
 * @type:  a bitmask of enum snd_jack_type values that can be detected by
 *         this jack
 * @jjack: Used to provide the allocated jack object to the caller.
 * @initial_kctl: if true, create a kcontrol and add it to the jack list.
 * @phantom_jack: Don't create a input device for phantom jacks.
 *
 * Creates a new jack object.
 *
 * Return: Zero if successful, or a negative error code on failure.
 * On success @jjack will be initialised.
 */
int snd_jack_new(struct snd_card *card, const char *id, int type,
                 struct snd_jack **jjack, bool initial_kctl, bool phantom_jack)
{
        struct snd_jack *jack;
        struct snd_jack_kctl *jack_kctl = NULL;
        int err;
        static const struct snd_device_ops ops = {     // jack设备操作集
                .dev_free = snd_jack_dev_free,
#ifdef CONFIG_SND_JACK_INPUT_DEV  // 这个宏,默认是配置的
                .dev_register = snd_jack_dev_register,
                .dev_disconnect = snd_jack_dev_disconnect,
#endif /* CONFIG_SND_JACK_INPUT_DEV */
        };

        if (initial_kctl) {  // 如果为真,动态创建一个jack kcontrol ,包含一个名称为id的kcontrol
                jack_kctl = snd_jack_kctl_new(card, id, type); // jack kcontrol的mask字段设置为type
                if (!jack_kctl)
                        return -ENOMEM;
        }

        jack = kzalloc(sizeof(struct snd_jack), GFP_KERNEL);  // 动态申请内存,申请一个snd_jack数据结构
        if (jack == NULL)
                return -ENOMEM;

        jack->id = kstrdup(id, GFP_KERNEL); // 复制id,并返回
        if (jack->id == NULL) {
                kfree(jack);
                return -ENOMEM;
        }
#ifdef CONFIG_SND_JACK_INPUT_DEV
        mutex_init(&jack->input_dev_lock); // 获取互斥锁

        /* don't create input device for phantom jack */
        if (!phantom_jack) {  // false时,创建输入设备
                int i;

                jack->input_dev = input_allocate_device(); // 动态创建输入设备
                if (jack->input_dev == NULL) {
                        err = -ENOMEM;
                        goto fail_input;
                }

                jack->input_dev->phys = "ALSA";

                jack->type = type;  // 初始化jack类型

                for (i = 0; i < SND_JACK_SWITCH_TYPES; i++) // 6
                        if (type & (1 << i))
                                input_set_capability(jack->input_dev, EV_SW, // 设置输入设备可以上报哪些输入事件 EV_SW代表开关事件
                                                     jack_switch_types[i]);   // 事件编码

        }
#endif /* CONFIG_SND_JACK_INPUT_DEV */
    //  创建一个新的snd_device实例,并添加到声卡设备的devices链表中
        err = snd_device_new(card, SNDRV_DEV_JACK, jack, &ops);  // device_data设置为jack
        if (err < 0)
                goto fail_input;

        jack->card = card;  // 设置声卡设备
        INIT_LIST_HEAD(&jack->kctl_list); // 初始化链表节点

        if (initial_kctl)  // 如果为真,将jack kcontrol添加到jack的kctl_list链表中
                snd_jack_kctl_add(jack, jack_kctl);

        *jjack = jack;  // 写回

        return 0;

fail_input:
#ifdef CONFIG_SND_JACK_INPUT_DEV
        input_free_device(jack->input_dev);
#endif
        kfree(jack->id);
        kfree(jack);
        return err;
}

jack_switch_types是全局数组,存放的是EV_SW类型事件的编码,长度为6,定义在sound/core/jack.c:

static const int jack_switch_types[SND_JACK_SWITCH_TYPES] = {
        SW_HEADPHONE_INSERT,     // 0x02
        SW_MICROPHONE_INSERT,    // 0x04  
        SW_LINEOUT_INSERT,       // 0x06 
        SW_JACK_PHYSICAL_INSERT, // 0x07
        SW_VIDEOOUT_INSERT,      // 0x80
        SW_LINEIN_INSERT,        // 0x0d
};
2.1.1 snd_jack_kctl_new

snd_jack_kctl_new定义在sound/core/jack.c,用于动态创建一个jack kcontrol ,包含一个名称为name的kcontrol;函数参数包括:

  • card:指向snd_card结构的指针,即指向ALSA中的声卡设备;
  • name:jack kcontrol 中包含的kcontrol的名称;
  • mask:新创建的jack kcontrtol可以上报的类型,其值取自enum snd_jack_type;比如我们设置了mask值为SND_JACK_HEADPHONE,当耳机插入或者拔出的时候HP_DET引脚就会接收到中断,中断处理程序读取引脚电平,并通过input设备上报SND_JACK_HEADPHONE对应的EV_SK事件,事件编码为0x02,值为插入/拔出;

 函数定义如下:

static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const char *name, unsigned int mask)
{
        struct snd_kcontrol *kctl;
        struct snd_jack_kctl *jack_kctl;
        int err;

        kctl = snd_kctl_jack_new(name, card); // 动态创建一个kcontrol
        if (!kctl)
                return NULL;

        err = snd_ctl_add(card, kctl); // 将kcontrol添加到声卡card的controls链表
        if (err < 0)
                return NULL;

        jack_kctl = kzalloc(sizeof(*jack_kctl), GFP_KERNEL); // 动态创建snd_jack_kctl

        if (!jack_kctl)
                goto error;

        jack_kctl->kctl = kctl;       // 初始化成员,设置关联的kcontrol
        jack_kctl->mask_bits = mask;  // 设置需要上报的事件

        kctl->private_data = jack_kctl;  // 设置私有数据
        kctl->private_free = snd_jack_kctl_private_free;

        return jack_kctl;
error:
        snd_ctl_free_one(kctl);
        return NULL;
}

其中snd_kctl_jack_new定义在sound/core/ctljack.c,用于动态创建一个kcontrol;

struct snd_kcontrol *
snd_kctl_jack_new(const char *name, struct snd_card *card)
{
        struct snd_kcontrol *kctl;

        kctl = snd_ctl_new1(&jack_detect_kctl, NULL);  // 会分配一个kcontrol,并把kcontrol模板中相应的值复制到该kcontrol中
        if (!kctl)
                return NULL;

        jack_kctl_name_gen(kctl->id.name, name, sizeof(kctl->id.name));
        kctl->id.index = get_available_index(card, kctl->id.name);
        kctl->private_value = 0;   // 保存kcontrol的状态
        return kctl;
}

jack_detect_kctl是一个全局变量,为struct snd_kcontrol_new类型,即kcontrol模板:

static const struct snd_kcontrol_new jack_detect_kctl = {
        /* name is filled later */
        .iface = SNDRV_CTL_ELEM_IFACE_CARD,
        .access = SNDRV_CTL_ELEM_ACCESS_READ,
        .info = jack_detect_kctl_info,
        .get = jack_detect_kctl_get,
};
2.1.2 snd_jack_kctl_add

snd_jack_kctl_add定义在sound/core/jack.c,用于将jack kcontrol添加到jack的kctl_list链表中;

static void snd_jack_kctl_add(struct snd_jack *jack, struct snd_jack_kctl *jack_kctl)
{
        jack_kctl->jack = jack;  // 设置所属jack
        list_add_tail(&jack_kctl->list, &jack->kctl_list); // 添加到jack的kctl_list链表中
        snd_jack_debugfs_add_inject_node(jack, jack_kctl);
}

2.2 snd_jack_add_new_kctl

snd_jack_add_new_kctl函数定义在sound/soc/jack.c,这个函数实际上就是对 snd_jack_kctl_new和snd_jack_kctl_add函数做了一层包装,用于创建一个新的snd_jack_kctl,并将其添加到jack;

/**
 * snd_jack_add_new_kctl - Create a new snd_jack_kctl and add it to jack
 * @jack:  the jack instance which the kctl will attaching to
 * @name:  the name for the snd_kcontrol object
 * @mask:  a bitmask of enum snd_jack_type values that can be detected
 *         by this snd_jack_kctl object.
 *
 * Creates a new snd_kcontrol object and adds it to the jack kctl_list.
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_jack_add_new_kctl(struct snd_jack *jack, const char * name, int mask)
{
        struct snd_jack_kctl *jack_kctl;

        jack_kctl = snd_jack_kctl_new(jack->card, name, mask);  // 动态创建一个jack kcontrol ,包含一个名称为name的kcontrol
        if (!jack_kctl)
                return -ENOMEM;

        snd_jack_kctl_add(jack, jack_kctl);  // 用于将jack kcontrol添加到jack的kctl_list链表中;
        return 0;
}
View Code

2.3 snd_jack_dev_register

在注册声卡设备card时会遍历声卡设备的逻辑设备链表devices,并调用声卡逻辑设备操作集中的dev_register函数,对于jack设备也就是snd_jack_dev_register函数。

snd_jack_dev_register函数定义在sound/core/jack.c:

static int snd_jack_dev_register(struct snd_device *device)
{
        struct snd_jack *jack = device->device_data;  // 获取jack
        struct snd_card *card = device->card;    // 获取声卡
        int err, i;

        snprintf(jack->name, sizeof(jack->name), "%s %s",  // 设置jack名称 
                 card->shortname, jack->id);

        mutex_lock(&jack->input_dev_lock);  // 获取互斥锁
        if (!jack->input_dev) {
                mutex_unlock(&jack->input_dev_lock);
                return 0;
        }

        jack->input_dev->name = jack->name;  // 设置intput设备的名称

        /* Default to the sound card device. */
        if (!jack->input_dev->dev.parent)
                jack->input_dev->dev.parent = snd_card_get_device_link(card);  // 设置输入设备的父设备

        /* Add capabilities for any keys that are enabled */
        for (i = 0; i < ARRAY_SIZE(jack->key); i++) { // 设置jack可以上报的EV_KEY类型的事件,并将事件编码保存在jack->key[i]中
                int testbit = SND_JACK_BTN_0 >> i;  // 0x4000 >> i  

                if (!(jack->type & testbit)) // 如果已经设置了上报BTN_x类型,则不会进入
                        continue;

                if (!jack->key[i])
                        jack->key[i] = BTN_0 + i;  //  转换为EV_KEY类型事件的编码

                input_set_capability(jack->input_dev, EV_KEY, jack->key[i]); // 设置输入设备可以上报哪些输入事件 EV_KEY代表按键事件
        }

        err = input_register_device(jack->input_dev);   // 注册输入设备,会在/sys/class/input创建input%d文件
        if (err == 0)
                jack->registered = 1;  // 注册标志位

        mutex_unlock(&jack->input_dev_lock);   // 释放互斥锁
        return err;
}

2.4 snd_jack_report

snd_jack_report定义在sound/core/jack.c,用于向输入子系统上报jack的插拔状态;函数内部根据jack的上报类型调用input_report_key/input_report_switch来向上层汇报EV_KEY、EV_SE事件;

/**
 * snd_jack_report - Report the current status of a jack
 * Note: This function uses mutexes and should be called from a
 * context which can sleep (such as a workqueue).
 *
 * @jack:   The jack to report status for
 * @status: The current status of the jack
 */
void snd_jack_report(struct snd_jack *jack, int status)
{
        struct snd_jack_kctl *jack_kctl;
        unsigned int mask_bits = 0;
#ifdef CONFIG_SND_JACK_INPUT_DEV
        int i;
#endif

        if (!jack)
                return;

        jack->hw_status_cache = status;   // 缓存状态

        list_for_each_entry(jack_kctl, &jack->kctl_list, list) // 遍历jack kctl_list链表中的jack kcontrol,赋值给jack_kctl
                if (jack_kctl->sw_inject_enable)   // 这个应该没有设置
                        mask_bits |= jack_kctl->mask_bits;
                else
                        snd_kctl_jack_report(jack->card, jack_kctl->kctl,
                                             status & jack_kctl->mask_bits); // 需要设置的kcontrol的状态

#ifdef CONFIG_SND_JACK_INPUT_DEV
        mutex_lock(&jack->input_dev_lock);    // 获取互斥锁
        if (!jack->input_dev) {
                mutex_unlock(&jack->input_dev_lock);
                return;
        }

        for (i = 0; i < ARRAY_SIZE(jack->key); i++) {  // 遍历EV_KEY事件编码
                int testbit = ((SND_JACK_BTN_0 >> i) & ~mask_bits);   // 0x4000 >> i  ....

                if (jack->type & testbit)   // 如果已经设置了上报BTN_x类型
                        input_report_key(jack->input_dev, jack->key[i],  //上报EV_KEY类型,事件编码为jack->key[i],值为status & testbit
                                         status & testbit);
        }

        for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) {  // 遍历EV_SW事件编码
                int testbit = ((1 << i) & ~mask_bits);  

                if (jack->type & testbit)    // 如果已经设置了上报SND_JACK_HEADPHONE等类型
                        input_report_switch(jack->input_dev,    //上报EV_SW类型,事件编码为jack_switch_types[i],值为status & testbit
                                            jack_switch_types[i],
                                            status & testbit);
        }

        input_sync(jack->input_dev);
        mutex_unlock(&jack->input_dev_lock);  // 释放互斥锁
#endif /* CONFIG_SND_JACK_INPUT_DEV */
}

其中函数snd_kctl_jack_report定义在sound/core/ctljack.c,用于发送kcontrol状态变更通知;

void snd_kctl_jack_report(struct snd_card *card,
                          struct snd_kcontrol *kctl, bool status)
{
        if (kctl->private_value == status)  // 状态没有发生改变
                return;
        kctl->private_value = status;  // 更新kcontrol的状态
        snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
}

函数调用了snd_ctl_notify,其第2个参数为事件掩码(event-mask),第3个参数为该通知的kcontrol元素id指针;如下代码定义的事件掩码SNDRV_CTL_EVENT_MASK_VALUE意味着kcontrol值的改变被通知;

三、ASoC核心数据结构

3.1 struct snd_soc_jack 

在ASoC中使用struct snd_soc_jack来描述jack,并提供了对其状态、引脚等进行管理和通知的功能,定义在include/sound/soc-jack.h:

struct snd_soc_jack {
        struct mutex mutex;
        struct snd_jack *jack;
        struct snd_soc_card *card;
        struct list_head pins;
        int status;
        struct blocking_notifier_head notifier;
        struct list_head jack_zones;
};

其中:

  • mutex:一个互斥锁,用于保护对当前结构体的并发访问;
  • jack:指向snd_jack结构的指针,即指向ALSA中的jack;
  • card:指向snd_soc_card结构的指针,表示ASoC声卡设备;
  • pins:保存jack pin信息的链表,每一个元素都是struct snd_soc_jack_pin;
  • status:表示该jack的插拔状态;
  • notifier:一个阻塞通知器头,用于在jack状态发生变化时通知关联的观察者;
  • jack_zones:用于保存该jack的区域信息的链表头;

3.2 struct snd_soc_jack_gpio

在ASoC中使用struct snd_soc_jack_gpio来描述用于描述声音插孔检测时使用的GPIO引脚的属性和配置,包括GPIO编号、报告值、消抖时间等;

/**
 * struct snd_soc_jack_gpio - Describes a gpio pin for jack detection
 *
 * @gpio:         legacy gpio number
 * @idx:          gpio descriptor index within the function of the GPIO
 *                consumer device
 * @gpiod_dev:    GPIO consumer device
 * @name:         gpio name. Also as connection ID for the GPIO consumer
 *                device function name lookup
 * @report:       value to report when jack detected
 * @invert:       report presence in low state
 * @debounce_time: debounce time in ms
 * @wake:         enable as wake source
 * @jack_status_check: callback function which overrides the detection
 *                     to provide more complex checks (eg, reading an
 *                     ADC).
 */
struct snd_soc_jack_gpio {
        unsigned int gpio;
        unsigned int idx;
        struct device *gpiod_dev;
        const char *name;
        int report;
        int invert;
        int debounce_time;
        bool wake;

        /* private: */
        struct snd_soc_jack *jack;
        struct delayed_work work;
        struct notifier_block pm_notifier;
        struct gpio_desc *desc;

        void *data;
        /* public: */
        int (*jack_status_check)(void *data);
};

该结构体包含以下成员:

  • gpio:GPIO编号,全局唯一;
  • idx:表示GPIO描述符在GPIO消费者设备的功能中的索引;
  • gpiod_dev:指向GPIO消费者设备的指针;
  • name:GPIO的名称,也用作GPIO消费者设备函数名称查找的连接ID;
  • report:GPIO可以上报的类型,比如耳机插入/拔出对应的值为SND_JACK_HEADPHONE;
  • invert:反转的意思,正常情况下是GPIO高电平认为插孔有设备插入,如果设置了invert,则GPIO为低电平认为插孔有设备插入;
  • debounce_time:消抖时间,以毫秒为单位;
  • wake:是否将该GPIO引脚启用为唤醒源;
  • jack:指向snd_soc_jack结构的指针,表即ASoC的jack;
  • work:延迟工作,用于处理插孔检测的延迟处理;
  • pm_notifier:电源管理通知器块,用于处理系统电源事件;
  • desc:指向GPIO描述符的指针;
  • data:指向任意数据的指针,用于存储私有数据;
  • jack_status_check:回调函数,用于覆盖检测过程,提供更复杂的检测方法(例如,读取ADC);

3.3 struct snd_soc_jack_pin

struct snd_soc_jack_pin定义在include/sound/soc-jack.h,用于描述在插孔检测时要进行上下电操作的widget;

/**
 * struct snd_soc_jack_pin - Describes a pin to update based on jack detection
 *
 * @pin:    name of the pin to update
 * @mask:   bits to check for in reported jack status
 * @invert: if non-zero then pin is enabled when status is not reported
 * @list:   internal list entry
 */
struct snd_soc_jack_pin {
        struct list_head list;
        const char *pin;
        int mask;
        bool invert;
};

结构体成员包括:

  • pin:引脚的名称,实际上就是widget的名字;在设置引脚状态时,会到声卡widgets链表查找名称为pin的widget,然后对其进行上下电操作;
  • mask:当前jack pin可以上报的类型,其值取自enum snd_jack_type;
  • invert:在设置引脚状态时默认0:下电,1:上电;如果设置了invert非0,则表示1:下电,0:上电;
  • list:链表节点,用于将该节点添加到ASoC Jack的pins链表中;

四、ASoC核心API

4.1 snd_soc_card_jack_new_pins

snd_soc_card_jack_new_pins函数定义在sound/soc/soc-card.c,用于在ASoC声卡中创建一个带有pin的jack。该函数的参数包括:

  • card:指向ASoC声卡结构的指针;
  • id:用于标识此jack的字符串;
  • type:一个位掩码,表示可以由此插孔检测到的snd_jack_type枚举值;
  • jack:指向ASoC jack结构的指针;
  • pins:要添加到ASoC jack的引脚数组,或者为NULL;
  • num_pins:pins数组中元素的数量。

函数定义如下:

/**
 * snd_soc_card_jack_new_pins - Create a new jack with pins
 * @card:  ASoC card
 * @id:    an identifying string for this jack
 * @type:  a bitmask of enum snd_jack_type values that can be detected by
 *         this jack
 * @jack:  structure to use for the jack
 * @pins:  Array of jack pins to be added to the jack or NULL
 * @num_pins: Number of elements in the @pins array
 *
 * Creates a new jack object with pins. If not adding pins,
 * snd_soc_card_jack_new() should be used instead.
 *
 * Returns zero if successful, or a negative error code on failure.
 * On success jack will be initialised.
 */
int snd_soc_card_jack_new_pins(struct snd_soc_card *card, const char *id,
                               int type, struct snd_soc_jack *jack, // ASoC jack
                               struct snd_soc_jack_pin *pins,   // jack引脚
                               unsigned int num_pins)      // jack引脚数目
{
        int ret;

        ret = jack_new(card, id, type, jack, false);  // 创建一个新的snd_jack实例,赋值给jack->jack
        if (ret)
                goto end;

        if (num_pins)
                ret = snd_soc_jack_add_pins(jack, num_pins, pins);  // 为每个jack pin创建一个新的snd_jack_kctl,并将其添加到jack->jack的kctl_list中。
end:
        return soc_card_ret(card, ret);
}

该函数内部首先调用jack_new函数创建一个新的snd_jack实例赋值给jack->jack,并将jack其与ASoC声卡设备关联起来。

然后,如果提供了引脚,就调用snd_soc_jack_add_pins为每个jack pin创建一个新的snd_jack_kctl,并将其添加到jack->jack的kctl_list中。

4.1.1 jack_new

jack_new定义如下:

static int jack_new(struct snd_soc_card *card, const char *id, int type,
                    struct snd_soc_jack *jack, bool initial_kctl)
{
        mutex_init(&jack->mutex);         // 初始化互斥锁
        jack->card = card;                // 设置ASoC声卡设备
        INIT_LIST_HEAD(&jack->pins);      // 初始化链表节点
        INIT_LIST_HEAD(&jack->jack_zones);  // 初始化链表节点
        BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier);

        return snd_jack_new(card->snd_card, id, type, &jack->jack, initial_kctl, false);  // 创建一个snd_jack设备,赋值给jack->jack
}
4.1.2 snd_soc_jack_add_pins

snd_soc_jack_add_pins函数定义在sound/soc/soc-jack.c,用于为每个jack pin创建一个新的snd_jack_kctl,并将其添加到jack的kctl_list中,函数定义如下:

/**
 * snd_soc_jack_add_pins - Associate DAPM pins with an ASoC jack
 *
 * @jack:  ASoC jack created with snd_soc_card_jack_new_pins()
 * @count: Number of pins
 * @pins:  Array of pins
 *
 * After this function has been called the DAPM pins specified in the
 * pins array will have their status updated to reflect the current
 * state of the jack whenever the jack status is updated.
 */
int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count,
                          struct snd_soc_jack_pin *pins)
{
        int i;

        for (i = 0; i < count; i++) {  // 遍历jack pin引脚
                if (!pins[i].pin) {
                        dev_err(jack->card->dev, "ASoC: No name for pin %d\n",
                                i);
                        return -EINVAL;
                }
                if (!pins[i].mask) {
                        dev_err(jack->card->dev, "ASoC: No mask for pin %d"
                                " (%s)\n", i, pins[i].pin);
                        return -EINVAL;
                }

                INIT_LIST_HEAD(&pins[i].list);  // 初始化链表节点
                list_add(&(pins[i].list), &jack->pins); // 将当前jack pin节点并添加到jack->pins链表中
                snd_jack_add_new_kctl(jack->jack, pins[i].pin, pins[i].mask);  // 为每个jack pin创建一个新的snd_jack_kctl,并将其添加到jack的kctl_list中
        }

        /* Update to reflect the last reported status; canned jack
         * implementations are likely to set their state before the
         * card has an opportunity to associate pins.
         */
        snd_soc_jack_report(jack, 0, 0);

        return 0;
}

函数首先遍历pins数组,对每个引脚执行以下操作:

  • 检查引脚的名称是否为空,如果为空则返回错误;
  • 检查引脚的掩码是否为空,如果为空则返回错误;
  • 初始化链表节点pins[i].list,并将其添加到jack->pins链表中;
  • 调用snd_jack_add_new_kctl函数为该引脚创建创建一个新的snd_jack_kctl,并将其添加到jack->jack的kctl_list中;
  • 然后,函数调用snd_soc_jack_report函数,以反映最后一次报告的插孔状态;

4.2 snd_soc_jack_report

snd_soc_jack_report函数定义在sound/soc/soc-jack.c,用于上报jack的插拔状态,函数参数包括:

  • jack:指向ASoC jack结构的指针;
  • status:表示的就是当前检测到的jack的插拔状态;
  • mask:由枚举类型snd_jack_type的位掩码组成,表示当前需要上报的类型;

比如status = 0010  0010b,mask= 0000 0011,也就说status中的第0位、第1位为有效位,其他位都是无效位;

函数定义如下:

/**
 * snd_soc_jack_report - Report the current status for a jack
 *
 * @jack:   the jack
 * @status: a bitmask of enum snd_jack_type values that are currently detected.
 * @mask:   a bitmask of enum snd_jack_type values that being reported.
 *
 * If configured using snd_soc_jack_add_pins() then the associated
 * DAPM pins will be enabled or disabled as appropriate and DAPM
 * synchronised.
 *
 * Note: This function uses mutexes and should be called from a
 * context which can sleep (such as a workqueue).
 */
void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask)
{
        struct snd_soc_dapm_context *dapm;
        struct snd_soc_jack_pin *pin;
        unsigned int sync = 0;

        if (!jack)
                return;
        trace_snd_soc_jack_report(jack, mask, status);

        dapm = &jack->card->dapm;  // 声卡设备的dapm域

        mutex_lock(&jack->mutex);   // 获取互斥锁

        jack->status &= ~mask;          // 清空旧状态(mask掩码指向的位)
        jack->status |= status & mask;  // 设置新的状态(mask掩码执行的位)
 
        trace_snd_soc_jack_notify(jack, status);

        list_for_each_entry(pin, &jack->pins, list) {     // 遍历每一个jack pin,对其进行上下电管理
                int enable = pin->mask & jack->status;    // 获取当前jack pin可以上报的类型的状态值,1:上电 0:下电

if (pin->invert) // 上电/下电值取反 enable = !enable; if (enable) snd_soc_dapm_enable_pin(dapm, pin->pin); // 设置名称为pin->pin的widget为连接状态 else snd_soc_dapm_disable_pin(dapm, pin->pin); // 设置名称为pin->pin的widget为断开状态 /* we need to sync for this case only */ sync = 1; } /* Report before the DAPM sync to help users updating micbias status */ blocking_notifier_call_chain(&jack->notifier, jack->status, jack); if (sync) snd_soc_dapm_sync(dapm); // 统一处理连接状态发生改变的widget,对齐进行上下电管理 snd_jack_report(jack->jack, jack->status); // 通过input_report_key/input_report_switch来向上层汇报EV_KEY、EV_SW事件 mutex_unlock(&jack->mutex); // 释放jack的互斥锁 }

函数主要完成以下两个工作:

(1) 该函数根据jack插拔状态更新前面通过snd_soc_jack_add_pins加入的jack pin的状态,具体如下:

  • 调用snd_soc_dapm_enable_pin到声卡widgets链表查找名称为pin->pin的widget,如果widget连接状态发生了变更,则将也widget添加到声卡card的dapm_dirty链表的尾部;并更新widget.connected字段值;snd_soc_dapm_enable_pin函数会在Rockchip RK3399 - DAPM Widget&Route&Path中介绍;
  • 最后调用snd_soc_dapm_sync统一处理连接状态发生改变的widget,对其进行上下电管理(实际上就是操作寄存器来控制它们的电源状态,当然如果没有电源控制相关的寄存器,那什么也不会做);

(2) 调用snd_jack_report,在其中通过input_report_key/input_report_switch来向上层汇报EV_KEY、EV_SW事件;

4.3 snd_soc_jack_add_gpios

基于上面的介绍,可以用以下做法来实现基于input event机制的耳机插拔检测:

  • 调用snd_soc_card_jack_new_pins创建带有pin的jack对象;
  • 通过request irq申请耳机插拔中断,在中断处理函数中通过检测上升沿/下降沿判断耳机是插入还是拔出;
  • 根据判断结果调用snd_soc_jack_report向上层汇报EV_KEY、EV_SW事件;

针对后面两个步骤,ASoC还提供了一个封装好的函数来实现:

int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,struct snd_soc_jack_gpio *gpios)

该函数通过标准GPIO驱动申请GPIO及GPIO对应中断,并提供了统一的中断处理函数来汇报事件,此函数适用于耳机中断接至GPIO且GPIO驱动为Linux标准驱动的情况下。文章Rockchip RK3399 - ASoC Machine驱动基础中介绍的Machine driver注册时中会调用该函数:

gpio.name        = "Headphone detection";
gpio.report      = SND_JACK_HEADPHONE;
gpio.desc        = desc;
gpio.debounce_time = 150;                           
snd_soc_jack_add_gpios(jack, 1, &gpio);

函数定义在sound/soc/soc-jack.c;

/**
 * snd_soc_jack_add_gpios - Associate GPIO pins with an ASoC jack
 *
 * @jack:  ASoC jack
 * @count: number of pins
 * @gpios: array of gpio pins
 *
 * This function will request gpio, set data direction and request irq
 * for each gpio in the array.
 */
int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,
                        struct snd_soc_jack_gpio *gpios)
{
        int i, ret;
        struct jack_gpio_tbl *tbl;

        tbl = devres_alloc(jack_devres_free_gpios, sizeof(*tbl), GFP_KERNEL); // 动态申请内存,数据结构类型为struct jack_gpio_tbl
        if (!tbl)
                return -ENOMEM;
        tbl->jack = jack;
        tbl->count = count;
        tbl->gpios = gpios;

        for (i = 0; i < count; i++) {  // gpio个数
                if (!gpios[i].name) {
                        dev_err(jack->card->dev,
                                "ASoC: No name for gpio at index %d\n", i);
                        ret = -EINVAL;
                        goto undo;
                }

                if (gpios[i].desc) {   // 如果已经初始化desc,会走这里,比如我们后面介绍的Machine驱动实例,
// 调用snd_soc_jack_add_gpios函数时,传入的耳机检测引脚已经初始化了成员desc
/* Already have a GPIO descriptor. */ goto got_gpio; } else if (gpios[i].gpiod_dev) { /* Get a GPIO descriptor */ gpios[i].desc = gpiod_get_index(gpios[i].gpiod_dev, gpios[i].name, gpios[i].idx, GPIOD_IN); if (IS_ERR(gpios[i].desc)) { ret = PTR_ERR(gpios[i].desc); dev_err(gpios[i].gpiod_dev, "ASoC: Cannot get gpio at index %d: %d", i, ret); goto undo; } } else { /* legacy GPIO number */ if (!gpio_is_valid(gpios[i].gpio)) { dev_err(jack->card->dev, "ASoC: Invalid gpio %d\n", gpios[i].gpio); ret = -EINVAL; goto undo; } ret = gpio_request_one(gpios[i].gpio, GPIOF_IN, gpios[i].name); if (ret) goto undo; gpios[i].desc = gpio_to_desc(gpios[i].gpio); } got_gpio: INIT_DELAYED_WORK(&gpios[i].work, gpio_work); // 初始化延迟工作,工作函数设置为gpio_work gpios[i].jack = jack; ret = request_any_context_irq(gpiod_to_irq(gpios[i].desc), // 将GPIO描述符转换为IRQ编号,然后申请中断,上升沿/下降沿触发 gpio_handler, // 中断处理函数为gpio_handler IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpios[i].name, // "Headphone detection" &gpios[i]); if (ret < 0) goto err; if (gpios[i].wake) { // 如果该GPIO引脚启用为唤醒源 ret = irq_set_irq_wake(gpiod_to_irq(gpios[i].desc), 1); if (ret != 0) dev_err(jack->card->dev, "ASoC: Failed to mark GPIO at index %d as wake source: %d\n", i, ret); } /* * Register PM notifier so we do not miss state transitions * happening while system is asleep. */ gpios[i].pm_notifier.notifier_call = snd_soc_jack_pm_notifier; register_pm_notifier(&gpios[i].pm_notifier); /* Expose GPIO value over sysfs for diagnostic purposes */ gpiod_export(gpios[i].desc, false); /* Update initial jack status */ schedule_delayed_work(&gpios[i].work, msecs_to_jiffies(gpios[i].debounce_time)); // 在debounce_time时间之后提交工作,交由缺省的工作队列和工作线程执行 } devres_add(jack->card->dev, tbl); return 0; err: gpio_free(gpios[i].gpio); undo: jack_free_gpios(jack, i, gpios); devres_free(tbl); return ret; }
4.3.1 gpio_handler

我们看一下GPIO口中断处理函数gpio_handler,内容比较简单,主要就是将GPIO工作延时推到工作队列schedule_delayed_work中去执行;

/* irq handler for gpio pin */
static irqreturn_t gpio_handler(int irq, void *data)
{
        struct snd_soc_jack_gpio *gpio = data;
        struct device *dev = gpio->jack->card->dev;

        trace_snd_soc_jack_irq(gpio->name);

        if (device_may_wakeup(dev))
                pm_wakeup_event(dev, gpio->debounce_time + 50);

        queue_delayed_work(system_power_efficient_wq, &gpio->work, // 类似于schedule_delayed_work,区别在于queue_work把给定工作提交给创建的工作队列system_power_efficient_wq
// 而不是缺省队列 msecs_to_jiffies(gpio
->debounce_time)); return IRQ_HANDLED; }
4.3.2 gpio_work

GPIO工作函数被设置为了gpio_work,该函数调用了snd_soc_jack_report上报当前GPIO插拔的状态,比如1:表示有设备插入,0:表示没有设备插入;

/* gpio detect */
static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio)
{
        struct snd_soc_jack *jack = gpio->jack;
        int enable;
        int report;

        enable = gpiod_get_value_cansleep(gpio->desc);  // 获取GPIO的值
        if (gpio->invert)   // 是否需要取反 这里传入0
                enable = !enable;

        if (enable)        // 有设备插入
                report = gpio->report;  // SND_JACK_HEADPHONE
        else               // 有设备拔出
                report = 0;

        if (gpio->jack_status_check)
                report = gpio->jack_status_check(gpio->data);  // 检查GPIO口的状态

        snd_soc_jack_report(jack, report, gpio->report);      // 上报当前GPIO插拔的状态
}

/* gpio work */ static void gpio_work(struct work_struct *work) { struct snd_soc_jack_gpio *gpio; gpio = container_of(work, struct snd_soc_jack_gpio, work.work); snd_soc_jack_gpio_detect(gpio); }

参考文章

[1] RK3399 探索之旅 / Audio 驱动层速读

[2] Linux ALSA音频驱动之一:框架概述

[3] Linux音频子系统

[4] http://www.alsa-project.org/

[5] Linux ALSA驱动之二:声卡的创建流程

[6] Linux内核中的链表——struct list_head

[7] Linux ALSA 音频系统:物理链路篇

[8] Sound Subsystem Documentation

[9] Linux ALSA 之二:ALSA 声卡与设备

[10] Audio 耳机 (三) SoC-Jack

[11] Android 10平台向wm8524添加耳机插入检测功能和分析

posted @ 2023-07-08 11:48  大奥特曼打小怪兽  阅读(544)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步