blob: 651eb628dc4e7a46b33cfb97d569ebf581339d37 [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 <asm/gpio.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <sound/soc.h>
#include "../gpio-names.h"
#include "../codecs/rt5640.h"
#include <mach/board-grouper-misc.h>
#include <mach/pinmux.h>
#include "../board.h"
#include "../board-grouper.h"
MODULE_DESCRIPTION("Headset detection driver");
MODULE_LICENSE("GPL");
#define DEFAULT_PINMUX(_pingroup, _mux, _pupd, _tri, _io) \
{ \
.pingroup = TEGRA_PINGROUP_##_pingroup, \
.func = TEGRA_MUX_##_mux, \
.pupd = TEGRA_PUPD_##_pupd, \
.tristate = TEGRA_TRI_##_tri, \
.io = TEGRA_PIN_##_io, \
.lock = TEGRA_PIN_LOCK_DEFAULT, \
.od = TEGRA_PIN_OD_DEFAULT, \
.ioreset = TEGRA_PIN_IO_RESET_DEFAULT, \
}
/*----------------------------------------------------------------------------
** 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 irqreturn_t lineout_irq_handler(int irq, void *dev_id);
static void lineout_work_queue(struct work_struct *work);
static void dock_work_queue(struct work_struct *work);
static int lineout_config_gpio(u32 project_info);
static void detection_work(struct work_struct *work);
static int btn_config_gpio(void);
static int switch_config_gpio(void);
int hs_micbias_power(int on);
static irqreturn_t dockin_irq_handler(int irq, void *dev_id);
static void audio_dock_switch(void);
/*----------------------------------------------------------------------------
** GLOBAL VARIABLES
**----------------------------------------------------------------------------*/
#define JACK_GPIO (TEGRA_GPIO_PW2)
#define LINEOUT_GPIO_NAKASI (TEGRA_GPIO_PW3)
#define LINEOUT_GPIO_BACH (TEGRA_GPIO_PX6)
#define HOOK_GPIO (TEGRA_GPIO_PX2)
#define UART_HEADPHONE_SWITCH (TEGRA_GPIO_PS2)
#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 int irq;
struct hrtimer timer;
ktime_t debouncing_time;
};
static struct headset_data *hs_data;
bool headset_alive = false;
EXPORT_SYMBOL(headset_alive);
bool lineout_alive;
EXPORT_SYMBOL(lineout_alive);
static struct workqueue_struct *g_detection_work_queue;
static DECLARE_WORK(g_detection_work, detection_work);
extern struct snd_soc_codec *rt5640_audio_codec;
struct work_struct headset_work;
struct work_struct lineout_work;
struct work_struct dock_work;
static bool UART_enable = false;
static unsigned int revision;
static u32 lineout_gpio;
static int gpio_dock_in = 0;
static ssize_t dock_status_show(struct device *dev,struct device_attribute *attr, char *buf)
{
int dock_status = 0;
if(gpio_get_value(gpio_dock_in))
dock_status = 0;
else
dock_status = 1;
return sprintf(buf, "%d\n", dock_status);
}
DEVICE_ATTR(dock_status, S_IRUGO, dock_status_show, NULL);
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 tristate_uart(void)
{
enum tegra_pingroup pingroup = TEGRA_PINGROUP_ULPI_DATA0;
enum tegra_pullupdown pupd = TEGRA_PUPD_PULL_DOWN;
enum tegra_pin_io io = TEGRA_PIN_INPUT;
enum tegra_tristate tristate = TEGRA_TRI_TRISTATE;
tegra_pinmux_set_pullupdown(pingroup, pupd);
tegra_pinmux_set_tristate(pingroup, tristate);
}
static void pulldown_uart(void)
{
enum tegra_pingroup pingroupTx = TEGRA_PINGROUP_ULPI_DATA0;
enum tegra_pingroup pingroupRx = TEGRA_PINGROUP_ULPI_DATA1;
enum tegra_pullupdown pupd = TEGRA_PUPD_PULL_DOWN;
enum tegra_pin_io io = TEGRA_PIN_INPUT;
tegra_pinmux_set_pullupdown(pingroupTx, pupd);
tegra_pinmux_set_io(pingroupTx, io);
// tegra_pinmux_set_pullupdown(pingroupRx, pupd);
// tegra_pinmux_set_io(pingroupRx, io);
}
static void normal_uart(void)
{
struct tegra_pingroup_config debug_uart [] = {
DEFAULT_PINMUX(ULPI_DATA0, UARTA, NORMAL, NORMAL, OUTPUT),
};
tegra_pinmux_config_table(debug_uart, ARRAY_SIZE(debug_uart));
// tegra_pinmux_set_pullupdown(pingroupRx, pupd);
// tegra_pinmux_set_io(pingroupRx, ioIn);
}
static void enable_uart(void)
{
struct tegra_pingroup_config debug_uart [] = {
DEFAULT_PINMUX(ULPI_DATA0, ULPI, NORMAL, NORMAL, OUTPUT),
};
tegra_pinmux_config_table(debug_uart, ARRAY_SIZE(debug_uart));
}
static void disable_uart(void)
{
struct tegra_pingroup_config debug_uart [] = {
DEFAULT_PINMUX(ULPI_DATA0, ULPI, PULL_UP, TRISTATE, OUTPUT),
};
tegra_pinmux_config_table(debug_uart, ARRAY_SIZE(debug_uart));
}
static void insert_headset(void)
{
struct snd_soc_dapm_context *dapm;
dapm = &rt5640_audio_codec->dapm;
if(gpio_get_value(lineout_gpio) == 0 && UART_enable){
printk("%s: debug board\n", __func__);
switch_set_state(&hs_data->sdev, NO_DEVICE);
hs_micbias_power(OFF);
headset_alive = false;
gpio_direction_output(UART_HEADPHONE_SWITCH, 0);
normal_uart();
}else if(gpio_get_value(HOOK_GPIO)){
printk("%s: headphone\n", __func__);
switch_set_state(&hs_data->sdev, HEADSET_WITHOUT_MIC);
hs_micbias_power(OFF);
pulldown_uart();
gpio_direction_output(UART_HEADPHONE_SWITCH, 1);
headset_alive = false;
}else{
printk("%s: headset\n", __func__);
switch_set_state(&hs_data->sdev, HEADSET_WITHOUT_MIC);
hs_micbias_power(ON);
pulldown_uart();
gpio_direction_output(UART_HEADPHONE_SWITCH, 1);
headset_alive = true;
}
hs_data->debouncing_time = ktime_set(0, 100000000); /* 100 ms */
}
static void remove_headset(void)
{
switch_set_state(&hs_data->sdev, NO_DEVICE);
hs_data->debouncing_time = ktime_set(0, 100000000); /* 100 ms */
headset_alive = false;
tristate_uart();
gpio_direction_output(UART_HEADPHONE_SWITCH, 0);
}
static void detection_work(struct work_struct *work)
{
unsigned long irq_flags;
int cable_in1;
int mic_in = 0;
hs_micbias_power(ON);
/* Disable headset interrupt while detecting.*/
local_irq_save(irq_flags);
disable_irq(hs_data->irq);
local_irq_restore(irq_flags);
/* Delay 1000ms for pin stable. */
msleep(1000);
/* Restore IRQs */
local_irq_save(irq_flags);
enable_irq(hs_data->irq);
local_irq_restore(irq_flags);
if (gpio_get_value(JACK_GPIO) != 0) {
/* Headset not plugged in */
// if (switch_get_state(&hs_data->sdev) == HEADSET_WITH_MIC ||
// switch_get_state(&hs_data->sdev) == HEADSET_WITHOUT_MIC)
remove_headset();
goto closed_micbias;
}
cable_in1 = gpio_get_value(JACK_GPIO);
mic_in = gpio_get_value(HOOK_GPIO);
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:
hs_micbias_power(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: Jack detection-in gpio configuration function
** Parameter: none
** Return value: if sucess, then returns 0
**
************************************************************/
static int jack_config_gpio()
{
int ret;
printk("HEADSET: Config Jack-in detection gpio\n");
hs_micbias_power(ON);
tegra_gpio_enable(JACK_GPIO);
ret = gpio_request(JACK_GPIO, "h2w_detect");
ret = gpio_direction_input(JACK_GPIO);
hs_data->irq = gpio_to_irq(JACK_GPIO);
ret = request_irq(hs_data->irq, detect_irq_handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "h2w_detect", NULL);
ret = irq_set_irq_wake(hs_data->irq, 1);
msleep(1);
if (gpio_get_value(JACK_GPIO) == 0){
insert_headset();
}else {
hs_micbias_power(OFF);
headset_alive = false;
switch_set_state(&hs_data->sdev, NO_DEVICE);
remove_headset();
}
return 0;
}
/**********************************************************
** 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");
tegra_gpio_enable(HOOK_GPIO);
ret = gpio_request(HOOK_GPIO, "btn_INT");
ret = gpio_direction_input(HOOK_GPIO);
return 0;
}
static void lineout_work_queue(struct work_struct *work)
{
msleep(300);
if (gpio_get_value(lineout_gpio) == 0){
printk("LINEOUT: LineOut inserted\n");
lineout_alive = true;
}else if(gpio_get_value(lineout_gpio)){
printk("LINEOUT: LineOut removed\n");
lineout_alive = false;
}
}
static void dock_work_queue(struct work_struct *work)
{
audio_dock_switch();
}
/**********************************************************
** Function: LineOut Detection configuration function
** Parameter: none
** Return value: IRQ_HANDLED
**
************************************************************/
static int lineout_config_gpio(u32 project_info)
{
int ret;
printk("HEADSET: Config LineOut detection gpio\n");
if(project_info == GROUPER_PROJECT_BACH)
lineout_gpio = LINEOUT_GPIO_BACH;
else if(project_info == GROUPER_PROJECT_NAKASI)
lineout_gpio = LINEOUT_GPIO_NAKASI;
tegra_gpio_enable(lineout_gpio);
ret = gpio_request(lineout_gpio, "lineout_int");
ret = gpio_direction_input(lineout_gpio);
#if 0
ret = request_irq(gpio_to_irq(LINEOUT_GPIO), &lineout_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "lineout_int", 0);
#endif
if (gpio_get_value(lineout_gpio) == 0)
lineout_alive = true;
else
lineout_alive = false;
return 0;
}
static int switch_config_gpio()
{
int ret;
printk("HEADSET: Config uart<->headphone gpio\n");
tegra_gpio_enable(UART_HEADPHONE_SWITCH);
ret = gpio_request(UART_HEADPHONE_SWITCH, "uart_headphone_switch");
return 0;
}
static int dockin_config_gpio()
{
int ret = 0;
int irq_num = 0;
irq_num = gpio_to_irq(gpio_dock_in);
ret = request_irq(irq_num, dockin_irq_handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING | IRQF_SHARED, "dock_detect", hs_data);
if(ret < 0)
printk("%s: request irq fail errno = %d\n", __func__, ret);
return ret;
}
static void audio_dock_switch(void)
{
struct snd_soc_dapm_context *dapm;
dapm = &rt5640_audio_codec->dapm;
if(!gpio_get_value(gpio_dock_in)){
/* Change route to dock if current output device is spk */
if(snd_soc_dapm_get_pin_status(dapm, "Int Spk")){
snd_soc_dapm_disable_pin(dapm, "Int Spk");
snd_soc_dapm_enable_pin(dapm, "AUX");
snd_soc_update_bits(rt5640_audio_codec,
RT5640_DIG_INF_DATA, RT5640_IF1_DAC_SEL_MASK,
0x0000);
snd_soc_dapm_sync(dapm);
}else{
snd_soc_dapm_disable_pin(dapm, "AUX");
snd_soc_dapm_sync(dapm);
}
}else{
if(!snd_soc_dapm_get_pin_status(dapm, "Headphone Jack")){
snd_soc_dapm_enable_pin(dapm, "Int Spk");
snd_soc_update_bits(rt5640_audio_codec,
RT5640_DIG_INF_DATA, RT5640_IF1_DAC_SEL_MASK,
0x4000);
}
snd_soc_dapm_disable_pin(dapm, "AUX");
snd_soc_dapm_sync(dapm);
}
}
static irqreturn_t dockin_irq_handler(int irq, void *dev_id)
{
printk("%s\n", __func__);
schedule_work(&dock_work);
return IRQ_HANDLED;
}
/**********************************************************
** Function: LineOut detection interrupt handler
** Parameter: dedicated irq
** Return value: if sucess, then returns IRQ_HANDLED
**
************************************************************/
static irqreturn_t lineout_irq_handler(int irq, void *dev_id)
{
schedule_work(&lineout_work);
return IRQ_HANDLED;
}
/**********************************************************
** Function: Headset jack-in detection interrupt handler
** Parameter: dedicated irq
** Return value: if sucess, then returns IRQ_HANDLED
**
************************************************************/
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->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;
}
static int codec_micbias_power(int on)
{
if(on){
//for ALC5642
if(rt5640_audio_codec == NULL){
printk("%s: No rt5640_audio_codec - set micbias on fail\n", __func__);
return 0;
}
#if 0
snd_soc_update_bits(rt5640_audio_codec, RT5640_PWR_ANLG1, RT5640_PWR_LDO2, RT5640_PWR_LDO2); /* Enable LDO2 */
snd_soc_update_bits(rt5640_audio_codec, RT5640_PWR_ANLG2, RT5640_PWR_MB1, RT5640_PWR_MB1); /*Enable MicBias1 */
//for ALC5642
#endif
}else{
//for ALC5642
if(rt5640_audio_codec == NULL){
printk("%s: No rt5640_audio_codec - set micbias off fail\n", __func__);
return 0;
}
snd_soc_update_bits(rt5640_audio_codec, RT5640_PWR_ANLG2, RT5640_PWR_MB1, 0); /* Disable MicBias1 */
snd_soc_update_bits(rt5640_audio_codec, RT5640_PWR_ANLG1, RT5640_PWR_LDO2, 0); /* Disable LDO2 */
//for ALC5642
}
return 0;
}
int hs_micbias_power(int on)
{
static int nLastVregStatus = -1;
if(on && nLastVregStatus!=ON){
printk("HEADSET: Turn on micbias power\n");
nLastVregStatus = ON;
codec_micbias_power(ON);
}
else if(!on && nLastVregStatus!=OFF){
printk("HEADSET: Turn off micbias power\n");
nLastVregStatus = OFF;
codec_micbias_power(OFF);
}
return 0;
}
EXPORT_SYMBOL(hs_micbias_power);
/**********************************************************
** Function: Headset driver init function
** Parameter: none
** Return value: none
**
************************************************************/
static int __init headset_init(void)
{
u32 project_info = grouper_get_project_id();
u32 pmic_id = grouper_query_pmic_id();
printk(KERN_INFO "%s+ #####\n", __func__);
int ret;
printk("HEADSET: Headset detection init\n");
if (project_info == GROUPER_PROJECT_BACH)
gpio_dock_in = TEGRA_GPIO_PO5;
else
gpio_dock_in = TEGRA_GPIO_PU4;
if(project_info == GROUPER_PROJECT_BACH ||
(project_info == GROUPER_PROJECT_NAKASI && pmic_id ==GROUPER_PMIC_TI))
UART_enable = true;
revision = grouper_query_pcba_revision();
hs_data = kzalloc(sizeof(struct headset_data), GFP_KERNEL);
if (!hs_data)
return -ENOMEM;
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;
ret = device_create_file(rt5640_audio_codec->dev, &dev_attr_dock_status);
if (ret != 0) {
dev_err(rt5640_audio_codec->dev,
"Failed to create dock status: %d\n", ret);
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;
printk("HEADSET: Headset detection mode\n");
lineout_config_gpio(project_info);
btn_config_gpio();/*Config hook detection GPIO*/
switch_config_gpio(); /*Config uart and headphone switch*/
jack_config_gpio();/*Config jack detection GPIO*/
INIT_WORK(&lineout_work, lineout_work_queue);
INIT_WORK(&dock_work, dock_work_queue);
dockin_config_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);
gpio_free(HOOK_GPIO);
gpio_free(lineout_gpio);
free_irq(hs_data->irq, 0);
destroy_workqueue(g_detection_work_queue);
switch_dev_unregister(&hs_data->sdev);
}
module_init(headset_init);
module_exit(headset_exit);