Android 耳機驅動知識


Android 耳機驅動知識

2015-03-06

工作以后接手的第一個驅動就是android平台下耳機的插拔檢測和按鍵檢測。這部分涉及的硬件知識比較簡單,但是軟件上對中斷的處理,軟件檢測的魯棒性,都有比較高的要求,涉及到驅動開發中經常使用的中斷申請,工作隊列,tasklet,竟態和同步,linux input子系統,android 鍵值映射等知識。

耳機接口知識介紹

1.耳機的通用接口為一個裸露的圓柱體,從頭端到線側的直徑依次增大,並通過橡膠環進行絕緣而設計,這樣方便無論從哪個角度都可以插入。在耳機座上,通過彈片和耳機頭的金屬環觸而形成電路導通。

2.市面上流通的耳機從接口大小上分3.5mm和2.5mm兩種,主要適配不同尺寸的插口,比較常見的是3.5mm。從接口電氣特性上分三段式和四段式,四段式在三段耳機的基礎上增加了mic端——耳機內部的一個聲電轉化裝置。

  • 三段式耳機接口從頭部到線一側的定義式左聲道,右聲道,GND。
  • 四段式耳機分美標(CTIA)和歐標(國內要求為歐標,OMTP-Open Mobile Terminal Platform開放移動終端平台),主要區別在於耳機線側最后兩端的定義,美標為左聲道(L),右聲道(R),GND(G),MIC(M),括號內為縮寫,下面為清晰主要采用縮寫,國標為L,R,M,G。

三段和四段耳機的簡易電路示例圖

3.從耳機識別的角度來講,耳機上的電聲轉化裝置(左聲道聽音器和右聲道聽音器)可以認為是一個16歐或者32歐的電阻,電阻值根據耳機廠商的設計而不同,一般的標准為16歐或者32歐,但有些比較好的耳機這個內阻值比較大;mic端可以認為是一個大電阻(通常為1k歐)和一個開關(多按鍵耳機可以認為好多個開關串上不同組值得電阻)。

4端3.5mm耳機的接口分類

耳機標准-美標 (CTIA,通常稱為美標)

從插入端到線分別是: 左聲道,右聲道,GND,MIC。耳機上德絕緣橡膠環一般是白色的 代表品牌:iphone,MOTO,小米,魅族,索尼

ctia headset

耳機接口標准 (OMTP,通常稱為歐標)

從插入端到線分別是: 左聲道,右聲道,MIC,GND。耳機上德絕緣橡膠環一般是黑色的 代表品牌:諾基亞,三星,HTC

omtp headset

耳機座接口介紹

相應的,耳機座也分為支持歐標設計的耳機座和支持美標設計的耳機座。另外,從耳機座左聲道的檢測方式來又可以分為 “Nomally-closed type”(常閉型) 和 “Normally-open type”(常開型) 兩種。其簡易設計如下圖

headset jack type

圖中所示的耳機座為美標的。

  • 在常閉型中,不接耳機時,耳機座左聲道和檢測端HS-DET接觸,插入耳機時,HS-DET與HPH-L不導通。
  • 在常開型中,不接耳機時,耳機座左聲道和檢測端HS-DET不接觸,插入耳機時,HS-DET與HPH-L導通。

耳機的硬件檢測原理

下圖是一個高通平台下耳機座設計的原理圖

headset detect priciple

可以看到,該耳機座為常開型,采用了左聲道檢測的機制——CDC_HS_DET為插入耳機觸發硬件中斷的的管腳。當沒有插入耳機時,由於CDC_HS_DET懸空,而該網絡對應的平台端的gpio(輸入狀態)口為低電平。當插入耳機后,由於耳機左聲道內部相當於1個16歐的電阻和GND相接,於是有如下的模擬圖:

headset circuit

正常情況下,CDC_HPH_L會有一點電壓存在,通過電阻的分壓,於是CDC_HS_DET接收到了高電平,引起了軟件中斷。

軟件上通過debounce后,檢測到持續的高電平,於是認為有耳機插入。

這時候需要判斷,插入的是三段還是四段。平台上打開mic_bias,當插入的是三段耳機時,MIC_IN2_P端口被拉低(忽略原理圖R3501處的NC,應該是個筆誤),於是判斷為三段耳機。若為四段耳機,MIC_IN2_P的電平接近於MIC_BIAS,軟件判斷該處的直流電壓之后設置識別了四段耳機。當按鍵按下時,MIC_IN2_P的電壓發生變化,觸發了系統中斷,之后軟件通過采樣該處的電壓值判斷按鍵阻值而確定按下了哪一個按鍵。

一般的,一鍵耳機按下后電阻值在10歐以下,三鍵帶音量加減的耳機上鍵的電阻范圍在60歐到100歐之間,中鍵在10歐以下,下鍵在120歐~200歐之間。

軟件檢測實現

我接觸過四個平台的耳機驅動,mtk、高通、Nividia和spreadtrum。除了高通將檢測耳機插拔的事件也申請為input設備外,,其他平台都注冊為switch/h2w設備。mtk平台的耳機驅動稱為ACCDET+EINT的模式,高通的機制叫做MBHC,都是一套看起來特別麻煩的機制。而展訊的code將耳機驅動作為misc下得一個設備驅動來用,很體現linux “write code do one thing and do it well”的哲理。下面來看看展訊的耳機驅動。

展訊平台的耳機驅動

headset.h

    /*
     * Copyright (C) 2012 Spreadtrum Communications Inc.
     *
     * This software is licensed under the terms of the GNU General Public
     * License version 2, as published by the Free Software Foundation, and
     * may be copied, distributed, and modified under those terms.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     */
    #ifndef __HEADSET_H__
    #define __HEADSET_H__
    #include <linux/switch.h>
    #include <linux/input.h>
    #include <linux/platform_device.h>
    enum {
    	BIT_HEADSET_OUT = 0,
    	BIT_HEADSET_MIC = (1 << 0),
    	BIT_HEADSET_NO_MIC = (1 << 1),
    };
    enum {
    	HEADSET_BUTTON_DOWN_INVALID = -1,
    	HEADSET_BUTTON_DOWN_SHORT,
    	HEADSET_BUTTON_DOWN_LONG,
    };
    struct _headset_gpio {
    	int active_low;
    	int gpio;
    	int irq;
    	unsigned int irq_type_active;
    	unsigned int irq_type_inactive;
    	int debounce;
    	int debounce_sw;
    	int holded;
    	int active;
    	int irq_enabled;
    	const char *desc;
    	struct _headset *parent;
    	unsigned int timeout_ms;
    	struct hrtimer timer;
    	enum hrtimer_restart (*callback)(int active, struct _headset_gpio *hgp);
    };
    struct _headset_keycap {
    	unsigned int type;
    	unsigned int key;
    };
    struct _headset_button {
    	struct _headset_keycap cap[15];
    	unsigned int (*headset_get_button_code_board_method)(int v);
    	unsigned int (*headset_map_code2push_code_board_method)(unsigned int code, int push_type);
    };
    struct _headset {
    	struct switch_dev sdev;
    	struct input_dev *input;
    	struct _headset_gpio detect;
    	struct _headset_gpio button;
    	int headphone;
    	int type;
    	struct work_struct switch_work;
    	struct workqueue_struct * switch_workqueue;
    };
    #ifndef ARRY_SIZE
    #define ARRY_SIZE(A) (sizeof(A)/sizeof(A[0]))
    #endif
    #endif

headset.c

/*
     * Copyright (C) 2012 Spreadtrum Communications Inc.
     *
     * This software is licensed under the terms of the GNU General Public
     * License version 2, as published by the Free Software Foundation, and
     * may be copied, distributed, and modified under those terms.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     */
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <mach/gpio.h>
#include <linux/headset.h>
#include <mach/board.h>
#ifndef HEADSET_DETECT_GPIO
#define HEADSET_DETECT_GPIO 165
#endif
#ifndef HEADSET_BUTTON_GPIO
#define HEADSET_BUTTON_GPIO 164
#endif
#ifndef HEADSET_DETECT_GPIO_ACTIVE_LOW
#define HEADSET_DETECT_GPIO_ACTIVE_LOW 1
#endif
#ifndef HEADSET_BUTTON_GPIO_ACTIVE_LOW
#define HEADSET_BUTTON_GPIO_ACTIVE_LOW 0
#endif
#ifndef HEADSET_DETECT_GPIO_DEBOUNCE_SW
#define HEADSET_DETECT_GPIO_DEBOUNCE_SW 1000
#endif
#ifndef HEADSET_BUTTON_GPIO_DEBOUNCE_SW
#define HEADSET_BUTTON_GPIO_DEBOUNCE_SW 100
#endif
static enum hrtimer_restart report_headset_button_status(int active, struct _headset_gpio *hgp);
static enum hrtimer_restart report_headset_detect_status(int active, struct _headset_gpio *hgp);
static struct _headset headset = {
    .sdev = {
        .name = "h2w",
    },
    .detect = {
        .desc = "headset detect",
        .active_low = HEADSET_DETECT_GPIO_ACTIVE_LOW,
        .gpio = HEADSET_DETECT_GPIO,
        .debounce = 0,
        .debounce_sw = HEADSET_DETECT_GPIO_DEBOUNCE_SW,
        .irq_enabled = 1,
        .callback = report_headset_detect_status,
    },
    .button = {
        .desc = "headset button",
        .active_low = HEADSET_BUTTON_GPIO_ACTIVE_LOW,
        .gpio = HEADSET_BUTTON_GPIO,
        .debounce = 0,
        .debounce_sw = HEADSET_BUTTON_GPIO_DEBOUNCE_SW,
        .irq_enabled = 1,
        .callback = report_headset_button_status,
        .timeout_ms = 800, /* 800ms for long button down */
    },
};
#ifndef headset_gpio_init
#define headset_gpio_init(gpio, desc) \
    	do { \
    		gpio_request(gpio, desc); \
    		gpio_direction_input(gpio); \
    	} while (0)
#endif
#ifndef headset_gpio_free
#define headset_gpio_free(gpio) \
    	gpio_free(gpio)
#endif
#ifndef headset_gpio2irq_free
#define headset_gpio2irq_free(irq, args) { }
#endif
#ifndef headset_gpio2irq
#define headset_gpio2irq(gpio) \
    	gpio_to_irq(gpio)
#endif
#ifndef headset_gpio_set_irq_type
#define headset_gpio_set_irq_type(irq, type) \
    	irq_set_irq_type(irq, type)
#endif
#ifndef headset_gpio_get_value
#define headset_gpio_get_value(gpio) \
    	gpio_get_value(gpio)
#endif
#ifndef headset_gpio_debounce
#define headset_gpio_debounce(gpio, ms) \
    	gpio_set_debounce(gpio, ms)
#endif
#ifndef headset_hook_detect
#define headset_hook_detect(status) { }
#endif
#define HEADSET_DEBOUNCE_ROUND_UP(dw) \
    	dw = (((dw ? dw : 1) + HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD - 1) / \
    		HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD) * HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD;
static struct _headset_keycap headset_key_capability[20] = {
    { EV_KEY, KEY_MEDIA },
    { EV_KEY, KEY_END },
    { EV_KEY, KEY_RESERVED },
};
static unsigned int (*headset_get_button_code_board_method)(int v);
static unsigned int (*headset_map_code2push_code_board_method)(unsigned int code, int push_type);
static __devinit int headset_button_probe(struct platform_device *pdev)
{
    struct _headset_button *headset_button = platform_get_drvdata(pdev);
    headset_get_button_code_board_method = headset_button->headset_get_button_code_board_method;
    headset_map_code2push_code_board_method = headset_button->headset_map_code2push_code_board_method;
    memcpy(headset_key_capability, headset_button->cap, sizeof headset_button->cap);
    return 0;
}
static struct platform_driver headset_button_driver = {
    .driver = {
        .name = "headset-button",
        .owner = THIS_MODULE,
    },
    .probe = headset_button_probe,
};
static unsigned int headset_get_button_code(int v)
{
    unsigned int code;
    if (headset_get_button_code_board_method)
        code = headset_get_button_code_board_method(v);
    else
        code = KEY_MEDIA;
    return code;
}
static unsigned int headset_map_code2key_type(unsigned int code)
{
    unsigned int key_type = EV_KEY;
    int i;
    for(i = 0; headset_key_capability[i].key != KEY_RESERVED &&
        headset_key_capability[i].key != code && i < ARRY_SIZE(headset_key_capability); i++);
    if (i < ARRY_SIZE(headset_key_capability) &&
        headset_key_capability[i].key == code)
        key_type = headset_key_capability[i].type;
    else
        pr_err("headset not find code [0x%x]'s maping type\n", code);
    return key_type;
}
static unsigned int headset_map_code2push_code(unsigned int code, int push_type)
{
    if (headset_map_code2push_code_board_method)
        return headset_map_code2push_code_board_method(code, push_type);
    switch (push_type) {
        case HEADSET_BUTTON_DOWN_SHORT:
            code = KEY_MEDIA;
            break;
        case HEADSET_BUTTON_DOWN_LONG:
            code = KEY_END;
            break;
    }
    return code;
}
/*tangyao modified on 2013-01-25*/
static void headset_gpio_irq_enable(int enable, struct _headset_gpio *hgp);
#define HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD	50 /* 10 */
static enum hrtimer_restart report_headset_button_status(int active, struct _headset_gpio *hgp)
{
    enum hrtimer_restart restart;
    static int step = 0;
    if (active < 0) {
        step = 0;
        return HRTIMER_NORESTART;
    }
    if (active) {
        restart = HRTIMER_RESTART;
        if (++step > 3)
            step = 0;
        switch (step) {
            case 1:
                /*short press report*/
                input_event(hgp->parent->input,EV_KEY,KEY_MEDIA, 1);
                input_sync(hgp->parent->input);
                break;
            case 2:
                /*long press report,first report short press release,then long press start*/
                input_event(hgp->parent->input,EV_KEY,KEY_MEDIA, 0);
                input_sync(hgp->parent->input);
                input_event(hgp->parent->input,EV_KEY,KEY_END, 1);
                input_sync(hgp->parent->input);
                break;
            default:
                pr_info("Are you press too long? step = %d\n",step);
        }
    } else {
        restart = HRTIMER_NORESTART;
        if (step == 1){
            /*short press release report*/
            input_event(hgp->parent->input,EV_KEY,KEY_MEDIA, 0);
            input_sync(hgp->parent->input);
        }else{
            /*long press release report*/
            input_event(hgp->parent->input,EV_KEY,KEY_END, 0);
            input_sync(hgp->parent->input);
        }
        step = 0;
    }

    return restart;
}
static enum hrtimer_restart report_headset_detect_status(int active, struct _headset_gpio *hgp)
{
    struct _headset * ht = hgp->parent;
    if (active) {
        headset_hook_detect(1);
        ht->headphone = 0;
        /*headphone support,tangyao modified on 2012-01-25*/
        ht->headphone = ht->button.active_low ^ headset_gpio_get_value(ht->button.gpio); 
        if (ht->headphone) {
            ht->type = BIT_HEADSET_NO_MIC;
            queue_work(ht->switch_workqueue, &ht->switch_work);
            pr_info("headphone plug in\n");
        } else {
            ht->type = BIT_HEADSET_MIC;
            queue_work(ht->switch_workqueue, &ht->switch_work);
            pr_info("headset plug in\n");
            headset_gpio_set_irq_type(ht->button.irq, ht->button.irq_type_active);
            headset_gpio_irq_enable(1, &ht->button);
        }
    } else {
        headset_gpio_irq_enable(0, &ht->button);
        ht->button.callback(-1, &ht->button);
        headset_hook_detect(0);
        if (ht->headphone)
            pr_info("headphone plug out\n");
        else
            pr_info("headset plug out\n");
        ht->type = BIT_HEADSET_OUT;
        queue_work(ht->switch_workqueue, &ht->switch_work);
    }
    /* use below code only when gpio irq misses state, because of the dithering */
    headset_gpio_set_irq_type(hgp->irq, active ? hgp->irq_type_inactive : hgp->irq_type_active);
    return HRTIMER_NORESTART;
}
static enum hrtimer_restart headset_gpio_timer_func(struct hrtimer *timer)
{
    enum hrtimer_restart restart = HRTIMER_RESTART;
    struct _headset_gpio *hgp =
        container_of(timer, struct _headset_gpio, timer);
    int active = hgp->active_low ^ headset_gpio_get_value(hgp->gpio); /* hgp->active */
    int green_ch = (!active && &hgp->parent->detect == hgp);
    if (active != hgp->active) {
        pr_info("The value %s mismatch [%d:%d] at %dms!\n",
                hgp->desc, active, hgp->active, hgp->holded);
        hgp->holded = 0;
    }
    pr_debug("%s : %s %s green_ch[%d], holed=%d, debounce_sw=%d\n", __func__,
             hgp->desc, active ? "active" : "inactive", green_ch, hgp->holded, hgp->debounce_sw);
    hgp->holded += HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD;
    if (hgp->holded >= hgp->debounce_sw || green_ch) {
        if (hgp->holded == hgp->debounce_sw || \
            hgp->holded == hgp->timeout_ms || \
            green_ch) {
            pr_debug("call headset gpio handler\n");
            restart = hgp->callback(active, hgp);
        } else
            pr_debug("gpio <%d> has kept active for %d ms\n", hgp->gpio, hgp->holded);
    }
    if (restart == HRTIMER_RESTART)
        hrtimer_forward_now(timer,
                            ktime_set(HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD / 1000,
                                      (HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD % 1000) * 1000000)); /* repeat timer */
    return restart;
}
static irqreturn_t headset_gpio_irq_handler(int irq, void *dev)
{
    struct _headset_gpio *hgp = dev;
    hrtimer_cancel(&hgp->timer);
    hgp->active = hgp->active_low ^ headset_gpio_get_value(hgp->gpio);
    headset_gpio_set_irq_type(hgp->irq, hgp->active ? hgp->irq_type_inactive : hgp->irq_type_active);
    pr_debug("%s : %s %s\n", __func__, hgp->desc, hgp->active ? "active" : "inactive");
    hgp->holded = 0;
    hrtimer_start(&hgp->timer,
                  ktime_set(HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD / 1000,
                            (HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD % 1000) * 1000000),
                  HRTIMER_MODE_REL);
    return IRQ_HANDLED;
}
static void headset_gpio_irq_enable(int enable, struct _headset_gpio *hgp)
{
    int action = 0;
    if (enable) {
        if (!hgp->irq_enabled) {
            hrtimer_cancel(&hgp->timer);
            hgp->irq_enabled = 1;
            action = 1;
            hgp->holded = 0;
            enable_irq(hgp->irq);
        }
    } else {
        if (hgp->irq_enabled) {
            disable_irq(hgp->irq);
            hrtimer_cancel(&hgp->timer);
            hgp->irq_enabled = 0;
            action = 1;
            hgp->holded = 0;
        }
    }
    pr_info("%s [ irq=%d ] --- %saction %s\n", __func__, hgp->irq_enabled, action ? "do " : "no ", hgp->desc);
}
static void headset_switch_state(struct work_struct *work)
{
    struct _headset *ht;
    int type;
    ht = container_of(work, struct _headset, switch_work);
    type = ht->type;
    switch_set_state(&headset.sdev, type);
    pr_info("set headset state to %d\n", type);
}
static int __init headset_init(void)
{
    int ret, i;
    struct _headset *ht = &headset;
    ret = switch_dev_register(&ht->sdev);
    if (ret < 0) {
        pr_err("switch_dev_register failed!\n");
        return ret;
    }
    platform_driver_register(&headset_button_driver);
    ht->input = input_allocate_device();
    if (ht->input == NULL) {
        pr_err("switch_dev_register failed!\n");
        goto _switch_dev_register;
    }
    ht->input->name = "headset-keyboard";
    ht->input->id.bustype = BUS_HOST;
    ht->input->id.vendor = 0x0001;
    ht->input->id.product = 0x0001;
    ht->input->id.version = 0x0100;
    for(i = 0; headset_key_capability[i].key != KEY_RESERVED; i++) {
        __set_bit(headset_key_capability[i].type, ht->input->evbit);
        input_set_capability(ht->input, headset_key_capability[i].type, headset_key_capability[i].key);
    }
    if (input_register_device(ht->input))
        goto _switch_dev_register;
    headset_gpio_init(ht->detect.gpio, ht->detect.desc);
    headset_gpio_init(ht->button.gpio, ht->button.desc);
    headset_gpio_debounce(ht->detect.gpio, ht->detect.debounce * 1000);
    headset_gpio_debounce(ht->button.gpio, ht->button.debounce * 1000);
    hrtimer_init(&ht->button.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    ht->button.timer.function = headset_gpio_timer_func;
    HEADSET_DEBOUNCE_ROUND_UP(ht->button.debounce_sw);
    HEADSET_DEBOUNCE_ROUND_UP(ht->button.timeout_ms);
    ht->button.parent = ht;
    ht->button.irq = headset_gpio2irq(ht->button.gpio);
    ht->button.irq_type_active = ht->button.active_low ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH;
    ht->button.irq_type_inactive = ht->button.active_low ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW;
    ret = request_irq(ht->button.irq, headset_gpio_irq_handler,
                      ht->button.irq_type_active, ht->button.desc, &ht->button);
    if (ret) {
        pr_err("request_irq gpio %d's irq failed!\n", ht->button.gpio);
        goto _gpio_request;
    }
    headset_gpio_irq_enable(0, &ht->button);
    hrtimer_init(&ht->detect.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    ht->detect.timer.function = headset_gpio_timer_func;
    HEADSET_DEBOUNCE_ROUND_UP(ht->detect.debounce_sw);
    ht->detect.parent = ht;
    ht->detect.irq = headset_gpio2irq(ht->detect.gpio);
    ht->detect.irq_type_active = ht->detect.active_low ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH;
    ht->detect.irq_type_inactive = ht->detect.active_low ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW;
    ret = request_irq(ht->detect.irq, headset_gpio_irq_handler,
                      ht->detect.irq_type_active, ht->detect.desc, &ht->detect);
    if (ret) {
        pr_err("request_irq gpio %d's irq failed!\n", ht->detect.gpio);
        goto _headset_button_gpio_irq_handler;
    }
    INIT_WORK(&ht->switch_work, headset_switch_state);
    ht->switch_workqueue = create_singlethread_workqueue("headset_switch");
    if (ht->switch_workqueue == NULL) {
        pr_err("can't create headset switch workqueue\n");
        ret = -ENOMEM;
        goto _headset_workqueue;
    }
    return 0;
    _headset_workqueue:
    destroy_workqueue(ht->switch_workqueue);
    _headset_button_gpio_irq_handler:
    free_irq(ht->button.irq, &ht->button);
    headset_gpio2irq_free(ht->button.irq, &ht->button);
    _gpio_request:
    headset_gpio_free(ht->detect.gpio);
    headset_gpio_free(ht->button.gpio);
    input_free_device(ht->input);
    _switch_dev_register:
    platform_driver_unregister(&headset_button_driver);
    switch_dev_unregister(&ht->sdev);
    return ret;
}
module_init(headset_init);
static void __exit headset_exit(void)
{
    struct _headset *ht = &headset;
    destroy_workqueue(ht->switch_workqueue);
    headset_gpio_irq_enable(0, &ht->button);
    headset_gpio_irq_enable(0, &ht->detect);
    free_irq(ht->detect.irq, &ht->detect);
    headset_gpio2irq_free(ht->detect.irq, &ht->detect);
    free_irq(ht->button.irq, &ht->button);
    headset_gpio2irq_free(ht->button.irq, &ht->button);
    headset_gpio_free(ht->detect.gpio);
    headset_gpio_free(ht->button.gpio);
    input_free_device(ht->input);
    platform_driver_unregister(&headset_button_driver);
    switch_dev_unregister(&ht->sdev);
}
module_exit(headset_exit);
MODULE_DESCRIPTION("headset & button detect driver");
MODULE_AUTHOR("Luther Ge <luther.ge@spreadtrum.com>");
MODULE_LICENSE("GPL");

分析:

  1. 360-373行,注冊input設備。可以看到,一個input設備的注冊方法,首先使用input_allocate_device為設備申請相關數據結構,然后初始化該結構的相關成員,如input->name,input->id.vendor, input->id.product, input->id.version(這四個字符串決定了鍵盤映射文件的名稱),然后調用__set_bit設置該input設備支持的事件類型,及調用input_set_capability設置支持的按鍵值,最后調用input_register_device將輸出化完成的數據結構注冊到input子系統中。一般的,我們不用去實現他的handle函數,evdev.c就可以完成該目的。
  2. 374-378行,初始化耳機和按鍵檢測時用到的gpio口
  3. 380-394行,申請耳機按鍵檢測的中斷處理函數,初始化中斷下半段的處理機制。可以看到這里使用了hr_timer這樣一個內核中的高精度定時器來實現
  4. 396-417行,申請耳機插拔檢測的中斷處理函數,初始化中斷下半段的處理機制。可以看到這里使用了work_queue這樣一個機制來實現。

可以看到,耳機在中斷下半段處理時采用了內核定時器timer來實現。另外,耳機插拔的檢測使用了h2w這個class,hook按鍵上報則采用了input子系統。

headset插拔識別的框架代碼分析和hook按鍵的處理

headset插拔識別的框架代碼分析

涉及的相關文件如下

hardware/libhardware_legacy/uevent.c
frameworks/base/core/jni/android_os_UEventObserver.cpp
frameworks/base/core/java/android/os/UEventObserver.java
frameworks/services/java/com/android/server/SystemServer.java
frameworks/base/services/java/com/android/server/WiredAccessoryManager.java

流程待分析

hook按鍵的處理

hook按鍵通過input子系統上報給Android,在Android手機/system/usr/keylayout/目錄下保存着鍵值映射配置文件。

一般的,耳機按鍵對應的按鍵映射: key 231 CALL key 122 ENDCALL WAKE key 166 MEDIA_STOP key 163 HEADSETHOOK key 164 MEDIA_PLAY_PAUSE key 165 MEDIA_PREVIOUS key 114 VOLUME_DOWN key 115 VOLUME_UP

這個按鍵配置文件第三列的字符串在/frameworks/base/include/androidfw/KeycodeLabels.h (android 4.0), frameworks/native/include/input/KeycodeLabels.h(android 4.4), 被定義成:

static const KeycodeLabel KEYCODES[] = {
    // ...
    { "CALL", 5 },
    { "ENDCALL", 6 },
    // ...
    { "MEDIA_PLAY_PAUSE", 85 },
    { "MEDIA_STOP", 86 },
    // ...
} 

最終/frameworks/base/core/java/android/view/KeyEvent.java會把這個數字定義成這樣的常量:

public class KeyEvent extends InputEvent implements Parcelable {
    // ...
    public static final int KEYCODE_CALL = 5;
    /** Key code constant: End Call key. */
    public static final int KEYCODE_ENDCALL = 6;
    // ...
    /** Key code constant: Play/Pause media key. */
    public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
    /** Key code constant: Stop media key. */
    public static final int KEYCODE_MEDIA_STOP = 86;
    // ...
}

總結

手機耳機是手機非常重要的功能之一,耳機的插拔檢測和按鍵檢測和相對比較麻煩,日常工作中也容易出現一些新的需求,如新的設備需要通過耳機接口被接入到手機中。因此,研究其驅動和應用層的實現還是很有必要的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM