在 Android 中添加了一个 switch 驱动,用于监测一些开关的变化,例如:HDMI、耳机的插拔检测之类的。
驱动源码存在内核源码的 drivers/switch 中,目录下有两个主要文件:switch_class.c 和switch_gpio.c,在 switch_class.c 中创建了 switch 设备类
static int create_switch_class(void)
实现了设备注册
int switch_dev_register(struct switch_dev *sdev)
在这个函数中,会创建两个文件,一个是保存状态的文件,一个是保存名称的文件,其中状态就开关量的表现,在这个文件中还有一个改变状态的方法
ret = device_create_file(sdev->dev, &dev_attr_state);
if (ret < 0)
goto err_create_file_1;
ret = device_create_file(sdev->dev, &dev_attr_name);
if (ret < 0)
goto err_create_file_2;
这个 switch_gpio.c 文件中,就是实现了一个由 GPIO 变化来触发的 switch 动作,当设备匹配上时,会注册一个 GPIO 中断,在中断处理函数中,可以看到它会先获取gpio的值,然后设置switch的状态
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
struct gpio_switch_data *switch_data =
(struct gpio_switch_data *)dev_id;
schedule_work(&switch_data->work);
return IRQ_HANDLED;
}
static void gpio_switch_work(struct work_struct *work)
{
int state;
struct gpio_switch_data *data =
container_of(work, struct gpio_switch_data, work);
state = gpio_get_value(data->gpio);
switch_set_state(&data->sdev, state);
gpio_set_value(AUDIO_SWITCH_GPIO_NUM, (state ? AUDIO_SWITCH_OFF : AUDIO_SWITCH_ON));
}
这里要注意的是,这个GPIO中断注册的时候默认选择的是低电平触发,如果是检测耳机接入,恰好耳机接入后会将 gpio 电平直接拉低,这时中断就会一直触发,可能会导致系统死机,如果遇到这种场景可将触发条件改成边沿触发就可以了。
@@ -112,7 +112,7 @@ static int gpio_switch_probe(struct platform_device *pdev)
}
ret = request_irq(switch_data->irq, gpio_irq_handler,
- IRQF_TRIGGER_LOW, pdev->name, switch_data);
+ (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), pdev->name, switch_data);
if (ret < 0)
goto err_request_irq;
下面代码实现耳机检测的功能,注册了一个 h2w 的 switch 驱动
#include <linux/module.h>
#include <linux/switch.h>
#include <linux/platform_device.h>
#define H2W_SWITCH_GPIO_NUM 22
static struct gpio_switch_platform_data pdata = {
.name = "h2w",
.gpio = H2W_SWITCH_GPIO_NUM,
.name_on = "1",
.name_off = "0",
.state_on = "1",
.state_off = "0",
};
static struct platform_device h2w_switch_dev = {
.name = "switch-gpio",
.dev = {
.platform_data = &pdata,
},
};
static int __init h2w_switch_init(void) {
return platform_device_register(&h2w_switch_dev);
}
static void __exit h2w_switch_exit(void) {
platform_device_unregister(&h2w_switch_dev);
}
module_init(h2w_switch_init);
module_exit(h2w_switch_exit);
为什么命名为 h2w ?
原因是 Android 已经给铺好路了,Android 有一个 有线访问管理 机制
frameworks/base/services/core/java/com/android/server/WiredAccessoryManager.java,这里默认就定义了三种类型设备访问并进行管理,其中包括h2w、usb、hdmi,h2w 就是指的有线耳机。
private List<UEventInfo> makeObservedUEventList() {
List<UEventInfo> retVal = new ArrayList<UEventInfo>();
UEventInfo uei;
// Monitor h2w
if (!mUseDevInputEventForAudioJack) {
uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC, BIT_LINEOUT);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
Slog.w(TAG, "This kernel does not have wired headset support");
}
}
// Monitor USB
uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
Slog.w(TAG, "This kernel does not have usb audio support");
}
// Monitor HDMI
//
// If the kernel has support for the "hdmi_audio" switch, use that. It will be
// signalled only when the HDMI driver has a video mode configured, and the downstream
// sink indicates support for audio in its EDID.
//
// If the kernel does not have an "hdmi_audio" switch, just fall back on the older
// "hdmi" switch instead.
uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
Slog.w(TAG, "This kernel does not have HDMI audio support");
}
}
return retVal;
}
关于这个 有线访问管理 是如何得知这个设备的,在我们设置设置这个 switch 状态的时候可以看到,它同时发送了一个 uevent 消息,然后被上层接收后进行处理。
kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp);
有线访问管理机制 就会告知 AudioManager 设备状态的改变
mAudioManager.setWiredDeviceConnectionState(outDevice, state, "", headsetName);