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; }
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); }
参考文章
[3] Linux音频子系统
[4] http://www.alsa-project.org/。
[6] Linux内核中的链表——struct list_head