1. 程式人生 > >[RK3288][Android6.0] 有線耳機驅動小結

[RK3288][Android6.0] 有線耳機驅動小結

Platform: Rockchip
OS: Android 6.0
Kernel: 3.10.92

Overview

Rockchip針對有線耳機的驅動單獨建了目錄, 在kernel/drivers/headset_observe/

這裡寫圖片描述

  • rockchip_headset_core.c: 讀取dts中的配置,根據配置不同決定使用adc還是普通的headset探測。
  • rk_headset.c: 普通方式headset驅動
  • rk_headset_irq_hook_adc.c: adc方式headset驅動

註冊

耳機插拔事件通過switch子系統上報,目錄: kernel/drivers/switch/

通過其switch_dev_register()介面把headset註冊到switch子系統中

headset->sdev.name = “h2w”;
headset->sdev.print_name = h2w_print_name;
ret = switch_dev_register(&headset->sdev);

這樣就能在/sys下看到h2w的子目錄

這裡寫圖片描述

然後,使用者空間就能收到uevent事件得知耳機裝置狀態有變化,從state檔案讀取狀態得知插入還是拔出來做相應處理。

插拔狀態的改變是通過switch_set_state()來實現,本質上就是通知上層並改變state檔案值。

dts的配置如下

&adc {
    status = "okay";

    rockchip_headset {
        compatible = "rockchip_headset";
        headset_gpio = <&gpio7 GPIO_A7 GPIO_ACTIVE_LOW>;
        pinctrl-names = "default";
        pinctrl-0 = <&gpio7_a7>;
        io-channels = <&adc 2>; 
       /*
               hook_gpio = ;
               hook_down_type = ; //interrupt hook key down status 
        */
}; };
  • headset_gpio: gpio 中斷pin .
  • io-channels: 表示當前要使用adc以及對應的channel.
  • hook_gpio和hook_down_type並沒有定義。

插拔處理:

耳機插拔中斷檢測在headset_interrupt()@rk_headset_irq_hook_adc.c

static irqreturn_t headset_interrupt(int irq, void *dev_id)
{
    //休眠等待電平穩定
    msleep(150);
    if(headset_info->headset_status == HEADSET_IN)
    {
        if(pdata->chan != 0)
        {
            //detect Hook key
            //驅動把耳機狀態和hook key的檢測都放同一個流程中處理了。
            schedule_delayed_work(&headset_info->h_delayed_work[HOOK],msecs_to_jiffies(200));
        }
    }else if(headset_info->headset_status == HEADSET_OUT)
    {
        ......
    }
    //觸發方式從電平觸發改成邊沿觸發,這樣,hook key的中斷不會影響耳機的插拔電平
    //而且耳機插拔也可以被檢測到
    if(pdata->headset_insert_type == HEADSET_IN_HIGH)
        irq_set_irq_type(headset_info->irq[HEADSET],IRQF_TRIGGER_RISING);
    else
        irq_set_irq_type(headset_info->irq[HEADSET],IRQF_TRIGGER_FALLING);
    return IRQ_HANDLED;
}

下半部對應的work queue是hook_once_work()

static void hook_once_work(struct work_struct *work)
{
    //通知audio codec預先處理mic
    #if defined (CONFIG_SND_SOC_RT3261) || defined (CONFIG_SND_SOC_RT3224)
    rt3261_headset_mic_detect(true);
    #endif
    //adc讀取
    ret = iio_read_channel_raw(headset_info->chan, &val);
    if (ret < 0) {
            pr_err("read hook_once_work adc channel() error: %d\n", ret);
    }

    //沒有mic的情況
    if(val >= 0 && val < HOOK_LEVEL_LOW)
    {
        headset_info->isMic= 0;//No microphone
        #if defined (CONFIG_SND_SOC_RT3261) || defined (CONFIG_SND_SOC_RT3224)
        rt3261_headset_mic_detect(false);
        #endif                  
    }   
    //有mic的情況
    else if(val >= HOOK_LEVEL_HIGH)
    {
        headset_info->isMic = 1;//have mic
        //不同按鍵的電阻值不同,那麼讀到的電壓也不同,hook_work_callback()
        //處理識別(adc 讀取)。
        schedule_delayed_work(&headset_info->hook_work,msecs_to_jiffies(100));
    }
    //根據是否有mic來得知是headset還是headphone.
    headset_info->cur_headset_status = headset_info->isMic ? BIT_HEADSET:BIT_HEADSET_NO_MIC;
    //通知上層裝置狀態變化並改變state檔案值
    switch_set_state(&headset_info->sdev, headset_info->cur_headset_status);    
}

hook key 的上報通過input 子系統實現

註冊:

int rk_headset_adc_probe(struct platform_device *pdev,struct rk_headset_pdata *pdata)
{
    // Create and register the input driver. 
    headset->input_dev = input_allocate_device();
    if (!headset->input_dev) {
        dev_err(&pdev->dev, "failed to allocate input device\n");
        ret = -ENOMEM;
        goto failed_free;
    }   
    headset->input_dev->name = pdev->name;
    headset->input_dev->open = rk_hskey_open;
    headset->input_dev->close = rk_hskey_close;
    headset->input_dev->dev.parent = &pdev->dev;
    //input_dev->phys = KEY_PHYS_NAME;
    headset->input_dev->id.vendor = 0x0001;
    headset->input_dev->id.product = 0x0001;
    headset->input_dev->id.version = 0x0100;
    // Register the input device 
    ret = input_register_device(headset->input_dev);
    if (ret) {
        dev_err(&pdev->dev, "failed to register input device\n");
        goto failed_free_dev;
    }

    input_set_capability(headset->input_dev, EV_KEY, HOOK_KEY_CODE);
}

上報:

static void hook_work_callback(struct work_struct *work)
{
    input_report_key(headset->input_dev,HOOK_KEY_CODE,headset->hook_status);
    input_sync(headset->input_dev);
}