ALSA子系统(十六)------虚拟耳机驱动

你好!这里是风筝的博客,
欢迎和我一起交流。

在这之前,我们需要知道一些耳机类型的基本知识:
headset
耳机有很多类型,图片左边带麦克的耳机术语叫做headset,右边不带麦克的术语叫做headphone。
headset = 听筒 + mic, headphone = 听筒。

对于Headset装置的插入检测,一般通过Jack即耳机插座来完成,大致的原理是使用带检测机械结构的耳机插座,将检测脚连到可GPIO中断上,当耳机插入时,耳机插头的金属会碰到检测脚,使得检测脚的电平产生变化,从而引起中断。这样就可以在中断处理函数中读取GPIO的的值,进一步判断出耳机是插入还是拔出。
而对于Headset是否带mic的检测,需要通过codec附加的micbias电流的功能

一个android系统里面,当用户需要播放声音的时候,cpu把数据信号发送给声卡,在声卡内部进行数模转换(DAC),然后通过运放进行播放,其中line out输出原始数据,即还没有经过运放的数据。通过耳机运放称为headphone(如果有麦的耳机叫做headset),也可以听过音响进行运放,其约等于line out数据。
PS:有的耳机还会自带DAC解码,这个之后再谈~

所以对于一般的设备,我们支持headphone,headset以及line out这三种设备就差不多了,在Linux 驱动中,通过上报事件来通知Android 耳机设备的拔插。
通常有两种上报方式:
1.输入子系统:可上报输入事件,也可上报开关事件。
2.Extcon(External Connector Class):使用UEvent系统,通过网络向应用程序上报事件。

以前是用Switch Class上报的,不过在Linux3.5之后这种方式就废弃了,改为使用Extcon上报。理由见:Introduce External Connector Class (extcon)

驱动代码参考韦老师给的demo:

#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("fengzheng923@qq.com");
MODULE_DESCRIPTION("Virutal jack driver for sound card");
MODULE_LICENSE("GPL");

同时上报 SW_HEADPHONE_INSERT 和 SW_MICROPHONE_INSERT, 就表示headset。
为了简化, 对于android系统只上报SW_MICROPHONE_INSERT也表示headset

  • INPUT:

    • 触发上报headset插入: echo 4 1 > /sys/class/input/inputX/test_input
    • 触发上报headset取下: echo 4 0 > /sys/class/input/inputX/test_input
    • 触发上报headphone插入: echo 2 1 > /sys/class/input/inputX/test_input
    • 触发上报headphone取下: echo 2 0 > /sys/class/input/inputX/test_input
  • SWITCH:

    • 触发上报headset插入: echo 1 > /sys/class/switch/h2w/test_state
    • 触发上报headset取下: echo 0 > /sys/class/switch/h2w/test_state
  • EXTCON:

    • 触发上报headset插入: echo 1 > /sys/class/extcon/h2w/test_state
    • 触发上报headset取下: echo 0 > /sys/class/extcon/h2w/test_state

Linux上报耳机事件之后,Android如何处理,就是另一篇文章了~
Android音频子系统(四)------耳机拔插流程


extcon还支持notify机制:

//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;
		}
		//......
}
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值