blob: 3d4ce127ed976cfa354dd4cca2af78c3f06174f3 [file] [log] [blame]
/*
* 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 "../codecs/wcd9310.h"
#include "../../../arch/arm/mach-msm/board-8960.h"
#include <asm/mach-types.h>
#include <mach/board_asustek.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 button_press_work(struct work_struct *work);
static void button_release_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 BTN_SLEEP (250)
#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;
unsigned int hook_det_irq;
struct hrtimer btn_timer;
ktime_t debouncing_time;
ktime_t btn_debouncing_time;
bool btn_ignore;
bool btn_pressed;
};
extern struct snd_soc_codec *wcd9310_codec;
static struct headset_data *hs_data;
static struct workqueue_struct *g_detection_work_queue;
static struct workqueue_struct *g_button_release_work_queue;
static struct workqueue_struct *g_button_press_work_queue;
static DECLARE_DELAYED_WORK(g_detection_work, detection_work);
static struct work_struct g_button_press_works[2];
static struct work_struct g_button_release_works[2];
struct work_struct headset_work;
struct work_struct lineout_work;
static void set_hs_micbias(int status)
{
if (wcd9310_codec == NULL) {
printk(KERN_INFO "%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, 0x6d);
if (tabla_check_bandgap_status(wcd9310_codec) == 0) {
/* Disable Bandgap Reference power */
printk(KERN_INFO "%s badgap status: OFF power down bandgap\n",
__func__);
snd_soc_write(wcd9310_codec,
TABLA_A_BIAS_CENTRAL_BG_CTL, 0x50);
}
}
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(KERN_INFO "%s: debug board\n", __func__);
} else if (gpio_get_value(HS_HOOK_DET)) {
printk(KERN_INFO "%s: headphone\n", __func__);
switch_set_state(&hs_data->sdev, HEADSET_WITHOUT_MIC);
tabla_set_h2w_status(wcd9310_codec, H2W_HEADPHONE);
} else {
printk(KERN_INFO "%s: headset\n", __func__);
switch_set_state(&hs_data->sdev, HEADSET_WITH_MIC);
tabla_set_h2w_status(wcd9310_codec, H2W_HEADSET);
hs_data->btn_pressed = false;
hs_data->btn_ignore = false;
irq_set_irq_type(hs_data->hook_det_irq, IRQF_TRIGGER_HIGH);
enable_irq(hs_data->hook_det_irq);
}
hs_data->debouncing_time = ktime_set(0, 100000000); /* 100 ms */
}
static void remove_headset(void)
{
if (switch_get_state(&hs_data->sdev) == HEADSET_WITH_MIC) {
hs_data->btn_pressed = false;
disable_irq_nosync(hs_data->hook_det_irq);
}
tabla_set_h2w_status(wcd9310_codec, H2W_NONE);
switch_set_state(&hs_data->sdev, NO_DEVICE);
hs_data->debouncing_time = ktime_set(0, 100000000); /* 100 ms */
set_hs_micbias(OFF);
}
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);
irq_set_irq_type(hs_data->hook_det_irq, IRQF_TRIGGER_NONE);
set_hs_micbias(ON);
if (switch_get_state(&hs_data->sdev) != NO_DEVICE) {
/* Delay for pin stable when being removed. */
msleep(110);
} else {
/* Delay for pin stable when plugged. */
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(KERN_INFO "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(KERN_INFO "HEADSET: Jack-in GPIO is low, but not a headset\n");
goto closed_micbias;
}
return;
closed_micbias:
set_hs_micbias(OFF);
return;
}
static irqreturn_t button_irq_handler(int irq, void *dev_id)
{
int value1, value2;
int retry_limit = 10;
do {
value1 = gpio_get_value(hs_data->hook_det_gpio);
irq_set_irq_type(hs_data->hook_det_irq,
value1 ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH);
value2 = gpio_get_value(hs_data->hook_det_gpio);
} while (value1 != value2 && retry_limit-- > 0);
hrtimer_start(&hs_data->btn_timer,
hs_data->btn_debouncing_time, HRTIMER_MODE_REL);
return IRQ_HANDLED;
}
static void button_release_work(struct work_struct *work)
{
msleep(BTN_SLEEP);
if (hs_data->btn_ignore)
return;
pr_debug("release event\n");
input_report_key(hs_data->input, KEY_MEDIA, 0);
input_sync(hs_data->input);
}
static void button_press_work(struct work_struct *work)
{
msleep(BTN_SLEEP);
if (hs_data->btn_ignore)
return;
pr_debug("press event\n");
input_report_key(hs_data->input, KEY_MEDIA, 1);
input_sync(hs_data->input);
}
static enum hrtimer_restart button_event_timer_func(struct hrtimer *data)
{
int res;
if (hs_data->btn_ignore)
return HRTIMER_NORESTART;
if (gpio_get_value(hs_data->hook_det_gpio)) {
res = queue_work(g_button_press_work_queue,
g_button_press_works);
if (res == 0)
queue_work(g_button_press_work_queue,
g_button_press_works + 1);
hs_data->btn_pressed = true;
} else if (hs_data->btn_pressed) {
res = queue_work(g_button_release_work_queue,
g_button_release_works);
if (res == 0)
queue_work(g_button_release_work_queue,
g_button_release_works + 1);
hs_data->btn_pressed = false;
} else {
pr_debug("%s: extra event.\n", __func__);
}
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;
pr_debug("HEADSET: Config Headset Button detection gpio\n");
ret = gpio_request(hs_data->hook_det_gpio, "HS_HOOK_INT");
if (ret) {
pr_err("%s: Failed to request gpio %d\n", __func__,
hs_data->hook_det_gpio);
}
ret = gpio_direction_input(hs_data->hook_det_gpio);
hs_data->hook_det_irq = MSM_GPIO_TO_INT(hs_data->hook_det_gpio);
ret = request_irq(hs_data->hook_det_irq, button_irq_handler,
IRQF_TRIGGER_NONE | IRQF_NO_SUSPEND, "h2w_button", NULL);
disable_irq(hs_data->hook_det_irq);
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 == 1)) {
hs_data->btn_ignore = true;
queue_delayed_work(g_detection_work_queue, &g_detection_work,
msecs_to_jiffies(100));
}
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->hook_det_gpio = HS_HOOK_DET;
hs_data->hp_det_gpio = JACK_GPIO;
hs_data->debouncing_time = ktime_set(0, 100000000); /* 100 ms */
hs_data->btn_debouncing_time = ktime_set(0, 10000000);
hs_data->sdev.name = "h2w";
hs_data->sdev.print_name = headset_name_show;
hs_data->sdev.print_state = headset_state_show;
hs_data->btn_ignore = false;
hs_data->btn_pressed = false;
ret = switch_dev_register(&hs_data->sdev);
if (ret < 0) {
printk(KERN_ERR "Headset: Failed to register driver\n");
goto err_switch_dev_register;
}
g_detection_work_queue = create_workqueue("detection");
g_button_press_work_queue = create_workqueue("button_press");
g_button_release_work_queue = create_workqueue("button_release");
INIT_WORK(&g_button_press_works[0], button_press_work);
INIT_WORK(&g_button_press_works[1], button_press_work);
INIT_WORK(&g_button_release_works[0], button_release_work);
INIT_WORK(&g_button_release_works[1], button_release_work);
hrtimer_init(&hs_data->btn_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hs_data->btn_timer.function = button_event_timer_func;
hs_data->input = input_allocate_device();
if (!hs_data->input) {
printk(KERN_ERR "Headset: Failed to request input device\n");
ret = -ENOMEM;
goto err_input_dev_request;
}
hs_data->input->name = "h2w button";
set_bit(EV_SYN, hs_data->input->evbit);
set_bit(EV_KEY, hs_data->input->evbit);
set_bit(KEY_MEDIA, hs_data->input->keybit);
ret = input_register_device(hs_data->input);
if (ret < 0) {
printk(KERN_ERR "Headset: Failed to register input device\n");
goto err_input_dev_register;
}
ret = btn_config_gpio();
if (ret < 0)
printk(KERN_ERR "Headst: Failed to config button gpio\n");
printk(KERN_INFO "HEADSET: Headset detection mode\n");
jack_config_gpio();/*Config jack detection GPIO*/
printk(KERN_INFO "%s- #####\n", __func__);
return 0;
err_input_dev_register:
input_free_device(hs_data->input);
err_input_dev_request:
switch_dev_unregister(&hs_data->sdev);
err_switch_dev_register:
kfree(hs_data);
return ret;
}
/**********************************************************
** Function: Headset driver exit function
** Parameter: none
** Return value: none
**
************************************************************/
static void __exit headset_exit(void)
{
printk(KERN_INFO "HEADSET: Headset exit\n");
if (switch_get_state(&hs_data->sdev))
remove_headset();
gpio_free(JACK_GPIO);
gpio_free(DB_DET_GPIO);
gpio_free(HS_HOOK_DET);
free_irq(hs_data->hp_det_irq, 0);
free_irq(hs_data->hook_det_irq, 0);
destroy_workqueue(g_detection_work_queue);
destroy_workqueue(g_button_press_work_queue);
destroy_workqueue(g_button_release_work_queue);
switch_dev_unregister(&hs_data->sdev);
input_unregister_device(hs_data->input);
kfree(hs_data);
}
module_init(headset_init);
module_exit(headset_exit);