Audio: add headset detection function.

Change-Id: I9af88313ecdb072b0aa71c3991359becac9cdcf9
Reviewed-on: http://mcrd1-5.corpnet.asus/code-review/master/68260
Reviewed-by: Jive Hwang <jive_hwang@asus.com>
Tested-by: Jive Hwang <jive_hwang@asus.com>
This commit is contained in:
sam_chen
2013-02-01 10:18:40 +08:00
committed by Iliyan Malchev
parent 616543bf0c
commit 435713f729
6 changed files with 436 additions and 0 deletions

View File

@@ -388,6 +388,7 @@ CONFIG_SND_DYNAMIC_MINORS=y
CONFIG_SND_USB_AUDIO=y
CONFIG_SND_SOC=y
CONFIG_SND_SOC_MSM8960=y
CONFIG_ASUSTEK_HEADSET=y
CONFIG_HID_APPLE=y
CONFIG_HID_MAGICMOUSE=y
CONFIG_HID_MICROSOFT=y

View File

@@ -764,6 +764,48 @@ static struct msm_gpiomux_config apq8064_audio_codec_configs[] __initdata = {
},
};
/* Config Headphone detect and Hook detect pin */
static struct gpiomux_setting hs_detect = {
.func = GPIOMUX_FUNC_GPIO,
.drv = GPIOMUX_DRV_2MA,
.pull = GPIOMUX_PULL_NONE,
};
static struct gpiomux_setting hs_hook_detect = {
.func = GPIOMUX_FUNC_GPIO,
.drv = GPIOMUX_DRV_2MA,
.pull = GPIOMUX_PULL_NONE,
};
static struct gpiomux_setting hs_db_detect = {
.func = GPIOMUX_FUNC_GPIO,
.drv = GPIOMUX_DRV_2MA,
.pull = GPIOMUX_PULL_NONE,
};
static struct msm_gpiomux_config apq8064_headphone_configs[] = {
{
.gpio = 85,
.settings = {
[GPIOMUX_SUSPENDED] = &hs_db_detect,
},
},
{
.gpio = 45,
.settings = {
[GPIOMUX_SUSPENDED] = &hs_detect,
},
},
{
.gpio = 62,
.settings = {
[GPIOMUX_SUSPENDED] = &hs_hook_detect,
},
},
};
/* External 3.3 V regulator enable */
/*static struct msm_gpiomux_config apq8064_ext_regulator_configs[] __initdata = {
{
@@ -1128,6 +1170,7 @@ static struct msm_gpiomux_config apq8064_sdc2_configs[] __initdata = {
},
},
#if 0
{
.gpio = 62,
.settings = {
@@ -1135,6 +1178,7 @@ static struct msm_gpiomux_config apq8064_sdc2_configs[] __initdata = {
[GPIOMUX_SUSPENDED] = &sdc2_suspended_cfg,
},
},
#endif
{
.gpio = 61,
.settings = {
@@ -1600,4 +1644,7 @@ void __init apq8064_init_gpiomux(void)
msm_gpiomux_install(msm8064_sp_gpio_config,
ARRAY_SIZE(msm8064_sp_gpio_config));
#endif
msm_gpiomux_install(apq8064_headphone_configs,
ARRAY_SIZE(apq8064_headphone_configs));
}

View File

@@ -134,6 +134,8 @@ static int tabla_codec_enable_slimrx(struct snd_soc_dapm_widget *w,
static int tabla_codec_enable_slimtx(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event);
struct snd_soc_codec *wcd9310_codec = NULL;
EXPORT_SYMBOL(wcd9310_codec);
enum tabla_bandgap_type {
TABLA_BANDGAP_OFF = 0,
@@ -8408,6 +8410,7 @@ static int tabla_codec_probe(struct snd_soc_codec *codec)
tabla->hs_polling_irq_prepared = false;
mutex_init(&tabla->codec_resource_lock);
tabla->codec = codec;
wcd9310_codec = codec;
tabla->mbhc_state = MBHC_STATE_NONE;
tabla->mbhc_last_resume = 0;
for (i = 0; i < COMPANDER_MAX; i++) {

View File

@@ -209,4 +209,10 @@ config SND_SOC_DUAL_AMIC
help
To add support for dual analog mic.
config ASUSTEK_HEADSET
tristate "ASUSTek Headset/headphone detection driver"
default n
help
Provides support for detecting ASUSTek headset/headphone devices.
endmenu

View File

@@ -84,3 +84,4 @@ obj-$(CONFIG_SND_SOC_MSM8974) += snd-soc-msm8974.o
snd-soc-qdsp6v2-objs := msm-dai-fe.o msm-dai-stub.o
obj-$(CONFIG_SND_SOC_QDSP6V2) += snd-soc-qdsp6v2.o
obj-$(CONFIG_ASUSTEK_HEADSET) += asustek_headset.o

View File

@@ -0,0 +1,378 @@
/*
* Headset device detection driver.
*
* Copyright (C) 2011 ASUSTek Corporation.
*
* Authors:
* Jason Cheng <jason4_cheng@asus.com>
*
* 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/module.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/mutex.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/hrtimer.h>
#include <linux/timer.h>
#include <linux/switch.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <sound/soc.h>
#include <asm/gpio.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <sound/soc.h>
#include <linux/mfd/wcd9xxx/wcd9310_registers.h>
#include "../../../arch/arm/mach-msm/board-8960.h"
#include <asm/mach-types.h>
MODULE_DESCRIPTION("Headset detection driver");
MODULE_LICENSE("GPL");
/*----------------------------------------------------------------------------
** FUNCTION DECLARATION
**----------------------------------------------------------------------------*/
static int __init headset_init(void);
static void __exit headset_exit(void);
static irqreturn_t detect_irq_handler(int irq, void *dev_id);
static void detection_work(struct work_struct *work);
static int jack_config_gpio(void);
static int btn_config_gpio(void);
static void detection_work(struct work_struct *work);
static void set_hs_micbias(int status);
/*----------------------------------------------------------------------------
** GLOBAL VARIABLES
**----------------------------------------------------------------------------*/
#define DB_DET_GPIO (85)
#define JACK_GPIO (45)
#define HS_HOOK_DET (62)
#define ON (1)
#define OFF (0)
enum{
NO_DEVICE = 0,
HEADSET_WITH_MIC = 1,
HEADSET_WITHOUT_MIC = 2,
};
struct headset_data {
struct switch_dev sdev;
struct input_dev *input;
unsigned hp_det_gpio;
unsigned hook_det_gpio;
unsigned int hp_det_irq;
struct hrtimer timer;
ktime_t debouncing_time;
};
extern struct snd_soc_codec *wcd9310_codec;
static struct headset_data *hs_data;
static struct workqueue_struct *g_detection_work_queue;
static DECLARE_WORK(g_detection_work, detection_work);
struct work_struct headset_work;
struct work_struct lineout_work;
static void set_hs_micbias(int status)
{
if (wcd9310_codec == NULL) {
printk("%s: wcd9310_codec = NULL\n", __func__);
return;
}
if (status) {
snd_soc_update_bits(wcd9310_codec, TABLA_A_MICB_CFILT_2_VAL,
0xFC, (0x28 << 2));
snd_soc_update_bits(wcd9310_codec, TABLA_A_MICB_CFILT_2_CTL,
0xC0, 0xC0);
snd_soc_update_bits(wcd9310_codec, TABLA_A_MICB_2_INT_RBIAS,
0xFF, 0x00);
snd_soc_update_bits(wcd9310_codec, TABLA_A_MICB_2_CTL,
0x80, 0x80);
/* Enable Bandgap Reference */
snd_soc_update_bits(wcd9310_codec, TABLA_A_BIAS_CENTRAL_BG_CTL,
0x0F, 0x05);
snd_soc_update_bits(wcd9310_codec, TABLA_A_LDO_H_MODE_1,
0xff, 0xdf);
} else {
snd_soc_update_bits(wcd9310_codec, TABLA_A_MICB_2_CTL,
0xC0, 0x00);
snd_soc_update_bits(wcd9310_codec, TABLA_A_LDO_H_MODE_1,
0xff, 0x65);
/* Disable Bandgap Reference */
snd_soc_update_bits(wcd9310_codec, TABLA_A_BIAS_CENTRAL_BG_CTL,
0x0F, 0x00);
}
return;
}
static ssize_t headset_name_show(struct switch_dev *sdev, char *buf)
{
switch (switch_get_state(&hs_data->sdev)){
case NO_DEVICE:{
return sprintf(buf, "%s\n", "No Device");
}
case HEADSET_WITH_MIC:{
return sprintf(buf, "%s\n", "HEADSET");
}
case HEADSET_WITHOUT_MIC:{
return sprintf(buf, "%s\n", "HEADPHONE");
}
}
return -EINVAL;
}
static ssize_t headset_state_show(struct switch_dev *sdev, char *buf)
{
switch (switch_get_state(&hs_data->sdev)){
case NO_DEVICE:
return sprintf(buf, "%d\n", 0);
case HEADSET_WITH_MIC:
return sprintf(buf, "%d\n", 1);
case HEADSET_WITHOUT_MIC:
return sprintf(buf, "%d\n", 2);
}
return -EINVAL;
}
static void insert_headset(void)
{
// if (gpio_get_value(DB_DET_GPIO) == 0) {
// printk("%s: debug board in\n", __func__);
if(gpio_get_value(HS_HOOK_DET)){
printk("%s: headphone\n", __func__);
switch_set_state(&hs_data->sdev, HEADSET_WITHOUT_MIC);
}else{
printk("%s: headset\n", __func__);
switch_set_state(&hs_data->sdev, HEADSET_WITH_MIC);
}
hs_data->debouncing_time = ktime_set(0, 100000000); /* 100 ms */
}
static void remove_headset(void)
{
printk("%s +++++++++++++++++\n", __func__);
switch_set_state(&hs_data->sdev, NO_DEVICE);
hs_data->debouncing_time = ktime_set(0, 100000000); /* 100 ms */
}
static void detection_work(struct work_struct *work)
{
unsigned long irq_flags;
int cable_in1;
int mic_in = 0;
/* Disable headset interrupt while detecting.*/
local_irq_save(irq_flags);
disable_irq(hs_data->hp_det_irq);
local_irq_restore(irq_flags);
set_hs_micbias(ON);
/* Delay 1000ms for pin stable. */
msleep(1000);
/* Restore IRQs */
local_irq_save(irq_flags);
enable_irq(hs_data->hp_det_irq);
local_irq_restore(irq_flags);
if (gpio_get_value(JACK_GPIO) != 0) {
/* Headset not plugged in */
remove_headset();
goto closed_micbias;
}
cable_in1 = gpio_get_value(JACK_GPIO);
mic_in = gpio_get_value(HS_HOOK_DET);
if (cable_in1 == 0) {
printk("HOOK_GPIO value: %d\n", mic_in);
if(switch_get_state(&hs_data->sdev) == NO_DEVICE)
insert_headset();
else if ( mic_in == 1)
goto closed_micbias;
} else{
printk("HEADSET: Jack-in GPIO is low, but not a headset \n");
goto closed_micbias;
}
return;
closed_micbias:
set_hs_micbias(OFF);
return;
}
static enum hrtimer_restart detect_event_timer_func(struct hrtimer *data)
{
queue_work(g_detection_work_queue, &g_detection_work);
return HRTIMER_NORESTART;
}
/**********************************************************
** Function: Headset Hook Key Detection interrupt handler
** Parameter: irq
** Return value: IRQ_HANDLED
** High: Hook button pressed
************************************************************/
static int btn_config_gpio()
{
int ret;
printk("HEADSET: Config Headset Button detection gpio\n");
ret = gpio_request(HS_HOOK_DET, "HS_HOOK_INT");
if (ret) {
pr_err("%s: Failed to request gpio %d\n", __func__,
HS_HOOK_DET);
}
ret = gpio_direction_input(HS_HOOK_DET);
return ret;
}
/**********************************************************
** Function: Jack detection-in gpio configuration function
** Parameter: none
** Return value: if sucess, then returns 0
**
************************************************************/
static int jack_config_gpio()
{
int ret;
ret = gpio_request(DB_DET_GPIO, "DB_DET");
if (ret) {
pr_err("%s: Error requesting GPIO %d\n", __func__, DB_DET_GPIO);
gpio_free(DB_DET_GPIO);
} else
gpio_direction_input(DB_DET_GPIO);
ret = gpio_request(JACK_GPIO, "JACK_IN_DET");
if (ret) {
pr_err("%s: Error requesting GPIO %d\n", __func__, JACK_GPIO);
gpio_free(JACK_GPIO);
} else
gpio_direction_input(JACK_GPIO);
hs_data->hp_det_irq = MSM_GPIO_TO_INT(JACK_GPIO);
ret = request_irq(hs_data->hp_det_irq, detect_irq_handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "h2w_detect", NULL);
ret = irq_set_irq_wake(hs_data->hp_det_irq, 1);
set_hs_micbias(ON);
msleep(50);
if (gpio_get_value(JACK_GPIO) == 0) {
insert_headset();
} else {
switch_set_state(&hs_data->sdev, NO_DEVICE);
remove_headset();
}
return 0;
}
static irqreturn_t detect_irq_handler(int irq, void *dev_id)
{
int value1, value2;
int retry_limit = 10;
do {
value1 = gpio_get_value(JACK_GPIO);
irq_set_irq_type(hs_data->hp_det_irq, value1 ?
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING);
value2 = gpio_get_value(JACK_GPIO);
}while (value1 != value2 && retry_limit-- > 0);
if ((switch_get_state(&hs_data->sdev) == NO_DEVICE) ^ value2){
hrtimer_start(&hs_data->timer, hs_data->debouncing_time, HRTIMER_MODE_REL);
}
return IRQ_HANDLED;
}
/**********************************************************
** Function: Headset driver init function
** Parameter: none
** Return value: none
**
************************************************************/
static int __init headset_init(void)
{
int ret = 0;
printk(KERN_INFO "%s+ #####\n", __func__);
hs_data = kzalloc(sizeof(struct headset_data), GFP_KERNEL);
if (!hs_data)
return -ENOMEM;
hs_data->hp_det_gpio = JACK_GPIO;
hs_data->debouncing_time = ktime_set(0, 100000000); /* 100 ms */
hs_data->sdev.name = "h2w";
hs_data->sdev.print_name = headset_name_show;
hs_data->sdev.print_state = headset_state_show;
ret = switch_dev_register(&hs_data->sdev);
if (ret < 0)
goto err_switch_dev_register;
g_detection_work_queue = create_workqueue("detection");
hrtimer_init(&hs_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hs_data->timer.function = detect_event_timer_func;
btn_config_gpio();
printk("HEADSET: Headset detection mode\n");
jack_config_gpio();/*Config jack detection GPIO*/
printk(KERN_INFO "%s- #####\n", __func__);
return 0;
err_switch_dev_register:
printk(KERN_ERR "Headset: Failed to register driver\n");
return ret;
}
/**********************************************************
** Function: Headset driver exit function
** Parameter: none
** Return value: none
**
************************************************************/
static void __exit headset_exit(void)
{
printk("HEADSET: Headset exit\n");
if (switch_get_state(&hs_data->sdev))
remove_headset();
gpio_free(JACK_GPIO);
free_irq(hs_data->hp_det_irq, 0);
destroy_workqueue(g_detection_work_queue);
switch_dev_unregister(&hs_data->sdev);
}
module_init(headset_init);
module_exit(headset_exit);