ALSA subsystem (16) ------ virtual headphone driver

Hello! Here is Kite's blog,

Welcome to communicate with me.


Before that, we need to know some basic knowledge about the types of earphones:
headset
there are many types of earphones. The earphone with a microphone on the left side of the picture is called a headset, and the term without a microphone on the right is called a headset.
headset = receiver + mic, headset = receiver.

For the insertion detection of the Headset device, it is generally completed through the Jack, that is, the headphone socket. The general principle is to use the headphone socket with a detection mechanical structure, and connect the detection pin to the GPIO interrupt. When the headphone is inserted, the metal of the headphone plug will touch. to the detection pin, so that the level of the detection pin changes, causing an interrupt. In this way, the value of GPIO can be read in the interrupt processing function to further determine whether the earphone is plugged in or unplugged.
For the detection of whether the Headset has a mic, it is necessary to use the additional micbias current function of the codec

In an android system, when the user needs to play sound, the cpu sends the data signal to the sound card, performs digital-to-analog conversion (DAC) inside the sound card, and then plays it through the op amp, where the line out outputs the original data, that is, it has not passed through op amp data. The operation and amplifier through the earphone is called headphone (if the earphone with mic is called headset), you can also listen to the audio for operation and amplifier, which is approximately equal to the line out data.
PS: Some headphones also come with DAC decoding, we will talk about this later~

So for general devices, we support the three devices of headphone, headset and line out. In the Linux driver, the Android headphone device is notified by reporting events.
There are usually two reporting methods:
1. Input subsystem: It can report input events and switch events.
2. Extcon (External Connector Class): Use the UEvent system to report events to the application through the network.

It used to be reported by Switch Class, but after Linux3.5, this method was discarded, and it was reported by Extcon instead. See the rationale: Introduce External Connector Class (extcon)

The driver code refers to the demo given by Mr. Wei:

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/err.h>
#if defined(CONFIG_SWITCH) || defined(CONFIG_ANDROID_SWITCH)
#include <linux/switch.h>
#elif defined(CONFIG_EXTCON)
#include <linux/extcon.h>
#endif
#include <linux/input.h>

static struct input_dev *g_virtual_input;

static ssize_t input_test_store(struct device *dev,
				     struct device_attribute *attr,
				     const char *buf, size_t count)
{
    
    
	long code;
	long val;
	char *endp;

	/* 如果字符串前面含有非数字, simple_strtol不能处理 */
	while ((*buf == ' ') || (*buf == '\t'))
		buf++;

	code = simple_strtol(buf, &endp, 0);

	/* 如果字符串前面含有非数字, simple_strtol不能处理 */
	while ((*endp == ' ') || (*endp == '\t'))
		endp++;
	val  = simple_strtol(endp, NULL, 0);

	printk("emulate to report EV_SW: 0x%lx 0x%lx\n", code, val);
	input_event(g_virtual_input, EV_SW, code, val);
	input_sync(g_virtual_input);

	return count;
}

static DEVICE_ATTR(test_input, S_IRUGO | S_IWUSR, NULL, input_test_store);

static int register_input_device_for_jack(void)
{
    
    
	int err;
	
	/* 分配input_dev */
	g_virtual_input = input_allocate_device();

	/* 设置 */
	/* 2.1 能产生哪类事件 */
	set_bit(EV_SYN, g_virtual_input->evbit);
	set_bit(EV_SW, g_virtual_input->evbit);


	/* 2.2 能产生这类事件中的哪些 */
	/* headset = 听筒 + MIC = SW_HEADPHONE_INSERT + SW_MICROPHONE_INSERT
	 *    同时上报 SW_HEADPHONE_INSERT 和 SW_MICROPHONE_INSERT, 就表示headset
	 *    为了简化, 对于android系统只上报SW_MICROPHONE_INSERT也表示headset
	 */
	set_bit(SW_HEADPHONE_INSERT, g_virtual_input->swbit);
	set_bit(SW_MICROPHONE_INSERT, g_virtual_input->swbit);
	set_bit(SW_LINEOUT_INSERT, g_virtual_input->swbit);

	/* 2.3 这些事件的范围 */

	g_virtual_input->name = "alsa_switch"; /* 不重要 */

	/* 注册 */
	err = input_register_device(g_virtual_input);
	if (err) {
    
    
		input_free_device(g_virtual_input);	
		printk("input_register_device for virtual jack err!\n");
		return err;
	}

	/* 创建/sys/class/input/inputX/test_input文件
	 *   可以执行类似下面的命令来模拟耳麦的动作:  
	 *       触发上报headset插入: echo 4 1 > /sys/class/input/inputX/test_input  
	 *       触发上报headset取下: echo 4 0 > /sys/class/input/inputX/test_input  
	 */
	err = device_create_file(&g_virtual_input->dev, &dev_attr_test_input);
	if (err) {
    
    
		printk("device_create_file for test_input err!\n");
		input_unregister_device(g_virtual_input);
		input_free_device(g_virtual_input); 
		return err;
	}

	return 0;
}

static void unregister_input_device_for_jack(void)
{
    
    
	device_remove_file(&g_virtual_input->dev, &dev_attr_test_input);

	input_unregister_device(g_virtual_input);
	input_free_device(g_virtual_input);	
}

/**************************************************************************************************************/

#if defined(CONFIG_SWITCH) || defined(CONFIG_ANDROID_SWITCH)
static struct switch_dev g_virtual_switch;

static ssize_t state_test_store(struct device *dev,
				     struct device_attribute *attr,
				     const char *buf, size_t count)
{
    
    
	long val;

	val = simple_strtol(buf, NULL, 0);

	printk("emulate to report swtich state: 0x%lx\n", val);
	switch_set_state(&g_virtual_switch, val);

	return count;
}

static DEVICE_ATTR(test_state, S_IRUGO | S_IWUSR, NULL, state_test_store);

static int register_switch_device_for_jack(void)
{
    
    
	int err;
	
	g_virtual_switch.name = "h2w";
	err = switch_dev_register(&g_virtual_switch);
	if (err) {
    
    
		printk("switch_dev_register h2w err!\n");
		return err;
	}

	/* 创建/sys/class/switch/h2w/test_state文件
	 *   可以执行类似下面的命令来模拟耳麦的动作:  
	 *       触发上报headset插入: echo 1 > /sys/class/switch/h2w/test_state
	 *       触发上报headset取下: echo 0 > /sys/class/switch/h2w/test_state
	 */
	err = device_create_file(g_virtual_switch.dev, &dev_attr_test_state);
	if (err) {
    
    
		printk("device_create_file test err!\n");
		switch_dev_unregister(&g_virtual_switch);
		return err;
	}

	return 0;
}

static void unregister_switch_device_for_jack(void)
{
    
    
	device_remove_file(g_virtual_switch.dev, &dev_attr_test_state);
	switch_dev_unregister(&g_virtual_switch);
}
#elif defined(CONFIG_EXTCON)

struct extcon_dev *extcon_virtual_jack;
static const unsigned int jack_cable[] = {
    
    
	EXTCON_JACK_MICROPHONE,
	EXTCON_JACK_HEADPHONE,
	EXTCON_JACK_LINE_IN,
	EXTCON_NONE,
};

static ssize_t state_test_store(struct device *dev,
				     struct device_attribute *attr,
				     const char *buf, size_t count)
{
    
    
	long val;

	val = simple_strtol(buf, NULL, 0);

	printk("emulate to report extcon state: 0x%lx\n", val);
	extcon_set_state_sync(extcon_virtual_jack, EXTCON_JACK_MICROPHONE, val);

	return count;
}

static DEVICE_ATTR(test_state, S_IRUGO | S_IWUSR, NULL, state_test_store);

static int register_extcon_device_for_jack(void)
{
    
    
	int err;

	extcon_virtual_jack = extcon_dev_allocate(jack_cable);
	if (IS_ERR_OR_NULL(extcon_virtual_jack)) {
    
    
		printk("extcon_dev_allocate fail\n");
		return -1;
	}
	extcon_virtual_jack->name = "h2w";
	err = extcon_dev_register(extcon_virtual_jack);
	if (err) {
    
    
		printk("extcon_dev_register h2w err!\n");
		return err;
	}
	/* 创建/sys/class/switch/h2w/test_state文件
	 *   可以执行类似下面的命令来模拟耳麦的动作:  
	 *       触发上报headset插入: echo 1 > /sys/class/extcon/h2w/test_state
	 *       触发上报headset取下: echo 0 > /sys/class/extcon/h2w/test_state
	 */
	err = device_create_file(&extcon_virtual_jack->dev, &dev_attr_test_state);
	if (err) {
    
    
		printk("device_create_file test err!\n");
		extcon_dev_unregister(extcon_virtual_jack);
		return err;
	}

	return 0;
}

static void unregister_extcon_device_for_jack(void)
{
    
    
	device_remove_file(&extcon_virtual_jack->dev, &dev_attr_test_state);
	extcon_dev_unregister(extcon_virtual_jack);
}
#endif

/**************************************************************************************************************/

static int __init virtual_jack_init(void)
{
    
    
	int err;
	
	err = register_input_device_for_jack();
#if defined(CONFIG_SWITCH) || defined(CONFIG_ANDROID_SWITCH)
	err = register_switch_device_for_jack();
#elif defined(CONFIG_EXTCON)
	err = register_extcon_device_for_jack();
#endif
	
	return 0;
}

static void __exit virtual_jack_exit(void)
{
    
    
	unregister_input_device_for_jack();
#if defined(CONFIG_SWITCH) || defined(CONFIG_ANDROID_SWITCH)
	unregister_switch_device_for_jack();
#elif defined(CONFIG_EXTCON)
	unregister_extcon_device_for_jack();
#endif
}

module_init(virtual_jack_init);
module_exit(virtual_jack_exit);

MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("Virutal jack driver for sound card");
MODULE_LICENSE("GPL");

Reporting SW_HEADPHONE_INSERT and SW_MICROPHONE_INSERT at the same time means headset.
For simplicity, for the android system, only SW_MICROPHONE_INSERT is reported and also indicates the headset

  • INPUT:

    • Trigger reporting for headset insertion: echo 4 1 > /sys/class/input/inputX/test_input
    • Trigger report to remove the headset: echo 4 0 > /sys/class/input/inputX/test_input
    • Trigger report headphone insertion: echo 2 1 > /sys/class/input/inputX/test_input
    • Trigger report headphone removal: echo 2 0 > /sys/class/input/inputX/test_input
  • SWITCH:

    • Trigger report headset insertion: echo 1 > /sys/class/switch/h2w/test_state
    • Trigger report to remove the headset: echo 0 > /sys/class/switch/h2w/test_state
  • EXTCON:

    • Trigger report headset insertion: echo 1 > /sys/class/extcon/h2w/test_state
    • Trigger report to remove the headset: echo 0 > /sys/class/extcon/h2w/test_state

After Linux reports the headphone event, how Android handles it is another article~
Android Audio Subsystem (4)------Headphone Unplugging Process


extcon also supports the notify mechanism:

//dts设备树中填写:extcon = <&你的extcon设备节点>;
struct sound_card_priv {
    
    
	struct snd_soc_card *card;
	struct snd_soc_codec *codec;
	struct extcon_dev *extdev;
	struct notifier_block hp_nb;
	u32 detect_state;
}

static int hp_plugin_notifier(struct notifier_block *nb, unsigned long event,
				void *ptr)
{
    
    
	struct sound_card_priv *priv = container_of(nb, struct sound_card_priv, hp_nb);

	pr_debug("[%s] line:%d event=%ld\n", __func__, __LINE__, event);
	if (event)
		priv->detect_state = PLUG_IN;
	else
		priv->detect_state = PLUG_OUT;

	return NOTIFY_DONE;
}
static int sound_card_dev_probe(struct platform_device *pdev)
{
    
    
		//......
		priv->extdev = extcon_get_edev_by_phandle(&pdev->dev, 0);
		if (IS_ERR(priv->extdev)) {
    
    
			ret = -ENODEV;
			goto err_free_mic_gnd_sw_gpio;
		}
		priv->hp_nb.notifier_call = hp_plugin_notifier;
		ret = extcon_register_notifier(priv->extdev,
				EXTCON_JACK_HEADPHONE,
				&priv->hp_nb);
		if (ret < 0) {
    
    
			dev_err(&pdev->dev, "register hp notifier failed\n");
			goto err_free_mic_gnd_sw_gpio;
		}
		//......
}

Guess you like

Origin blog.csdn.net/Guet_Kite/article/details/117520599