audio: enable fsa8008 for earjack detection
Change-Id: Ifc6f9aa6fa6520002dec150808bb9192e48ed0a8
diff --git a/arch/arm/configs/mako-perf_defconfig b/arch/arm/configs/mako-perf_defconfig
index 61d4f69..2acd191 100644
--- a/arch/arm/configs/mako-perf_defconfig
+++ b/arch/arm/configs/mako-perf_defconfig
@@ -402,6 +402,7 @@
CONFIG_LEDS_TRIGGERS=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_SWITCH=y
+CONFIG_SWITCH_FSA8008=y
CONFIG_RTC_CLASS=y
# CONFIG_RTC_DRV_MSM is not set
CONFIG_RTC_DRV_PM8XXX=y
diff --git a/arch/arm/mach-msm/lge/mako/board-mako-pmic.c b/arch/arm/mach-msm/lge/mako/board-mako-pmic.c
index adaf779..0c5b50c 100644
--- a/arch/arm/mach-msm/lge/mako/board-mako-pmic.c
+++ b/arch/arm/mach-msm/lge/mako/board-mako-pmic.c
@@ -117,6 +117,7 @@
static struct pm8xxx_gpio_init pm8921_gpios[] __initdata = {
PM8921_GPIO_OUTPUT(17, 0, HIGH), /* CAM_VCM_EN */
PM8921_GPIO_OUTPUT(19, 0, HIGH), /* AMP_EN_AMP */
+ PM8921_GPIO_OUTPUT(20, 0, HIGH), /* PMIC - FSA8008 EAR_MIC_BIAS_EN */
PM8921_GPIO_OUTPUT(31, 0, HIGH), /* PMIC - FSA8008_EAR_MIC_EN */
PM8921_GPIO_INPUT(32, PM_GPIO_PULL_UP_1P5), /* PMIC - FSA8008_EARPOL_DETECT */
PM8921_GPIO_OUTPUT(33, 0, HIGH), /* HAPTIC_EN */
diff --git a/arch/arm/mach-msm/lge/mako/board-mako-sound.c b/arch/arm/mach-msm/lge/mako/board-mako-sound.c
index 6f364ca..92cd8de 100644
--- a/arch/arm/mach-msm/lge/mako/board-mako-sound.c
+++ b/arch/arm/mach-msm/lge/mako/board-mako-sound.c
@@ -16,14 +16,11 @@
#include <linux/platform_device.h>
#include <linux/mfd/pm8xxx/pm8921.h>
#include <linux/regulator/consumer.h>
+#include <linux/platform_data/hds_fsa8008.h>
#include "devices.h"
#include "board-8064.h"
-#ifdef CONFIG_SWITCH_FSA8008
-#include "../../../../sound/soc/codecs/wcd9310.h"
-#endif
-
#ifdef CONFIG_SND_SOC_TPA2028D
#include <sound/tpa2028d.h>
#endif
@@ -38,6 +35,7 @@
#define AGC_FIXED_GAIN 12
+#define GPIO_EAR_MIC_BIAS_EN PM8921_GPIO_PM_TO_SYS(20)
#define GPIO_EAR_SENSE_N 82
#define GPIO_EAR_MIC_EN PM8921_GPIO_PM_TO_SYS(31)
#define GPIO_EARPOL_DETECT PM8921_GPIO_PM_TO_SYS(32)
@@ -58,25 +56,6 @@
int len;
};
-
-struct fsa8008_platform_data {
- const char *switch_name; /* switch device name */
- const char *keypad_name; /* keypad device name */
-
- unsigned int key_code; /* key code for hook */
-
- unsigned int gpio_detect; /* DET : to detect jack inserted or not */
- unsigned int gpio_mic_en; /* EN : to enable mic */
- unsigned int gpio_jpole; /* JPOLE : 3pole or 4pole */
- unsigned int gpio_key; /* S/E button */
-
- /* callback function which is initialized while probing */
- void (*set_headset_mic_bias)(int enable);
-
- /* latency for pole (3 or 4)detection (in ms) */
- unsigned int latency_for_detection;
-};
-
#ifdef CONFIG_SND_SOC_TPA2028D
int amp_enable(int on_state)
{
@@ -164,7 +143,11 @@
}
-#ifdef CONFIG_SWITCH_FSA8008
+void enable_external_mic_bias(int status)
+{
+ gpio_set_value_cansleep(GPIO_EAR_MIC_BIAS_EN, status);
+}
+
static struct fsa8008_platform_data lge_hs_pdata = {
.switch_name = "h2w",
.keypad_name = "hs_detect",
@@ -173,11 +156,12 @@
.gpio_detect = GPIO_EAR_SENSE_N,
.gpio_mic_en = GPIO_EAR_MIC_EN,
+ .gpio_mic_bias_en = GPIO_EAR_MIC_BIAS_EN,
.gpio_jpole = GPIO_EARPOL_DETECT,
.gpio_key = GPIO_EAR_KEY_INT,
.latency_for_detection = 75,
- .set_headset_mic_bias = tabla_codec_micbias2_ctl,
+ .set_headset_mic_bias = enable_external_mic_bias,
};
static struct platform_device lge_hsd_device = {
@@ -194,19 +178,8 @@
return platform_device_register(&lge_hsd_device);
}
-static void __exit lge_hsd_fsa8008_exit(void)
-{
- printk(KERN_INFO "lge_hsd_fsa8008_exit\n");
- platform_device_unregister(&lge_hsd_device);
-}
-#endif
-
void __init lge_add_sound_devices(void)
{
lge_add_i2c_tpa2028d_devices();
-
-#ifdef CONFIG_SWITCH_FSA8008
lge_hsd_fsa8008_init();
-#endif
-
}
diff --git a/drivers/switch/Kconfig b/drivers/switch/Kconfig
index 5238591..d07e4ab 100644
--- a/drivers/switch/Kconfig
+++ b/drivers/switch/Kconfig
@@ -12,4 +12,9 @@
help
Say Y here to enable GPIO based switch support.
+config SWITCH_FSA8008
+ bool "FSA8008 Headset detection driver"
+ help
+ Say Y here to enable FSA8008 driver
+
endif # SWITCH
diff --git a/drivers/switch/Makefile b/drivers/switch/Makefile
index f7606ed..ec71d76 100644
--- a/drivers/switch/Makefile
+++ b/drivers/switch/Makefile
@@ -1,4 +1,5 @@
# Switch Class Driver
obj-$(CONFIG_SWITCH) += switch_class.o
obj-$(CONFIG_SWITCH_GPIO) += switch_gpio.o
+obj-$(CONFIG_SWITCH_FSA8008) += hds_fsa8008.o
diff --git a/drivers/switch/hds_fsa8008.c b/drivers/switch/hds_fsa8008.c
new file mode 100644
index 0000000..cc1e5c6
--- /dev/null
+++ b/drivers/switch/hds_fsa8008.c
@@ -0,0 +1,675 @@
+/* drivers/switch/hds_fsa8008.c
+ *
+ * LGE 3.5 PI Headset detection driver using fsa8008.
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * Copyright (C) 2009-2012 LGE, Inc.
+ * Lee SungYoung <lsy@lge.com>
+ * Kim Eun Hye <ehgrace.kim@lge.com>
+ * Yoon Gi Souk <gisouk.yoon@lge.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.
+ */
+
+/* Interface is following:
+ * android:frameworks/base/services/java/com/android/server/HeadsetObserver.java
+ * HEADSET_UEVENT_MATCH = "DEVPATH=/sys/devices/virtual/switch/h2w"
+ * HEADSET_STATE_PATH = /sys/class/switch/h2w/state
+ * HEADSET_NAME_PATH = /sys/class/switch/h2w/name
+ */
+#ifdef CONFIG_SWITCH_FSA8008
+#define CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/switch.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/mutex.h>
+#include <linux/hrtimer.h>
+#include <linux/input.h>
+#include <linux/debugfs.h>
+#include <linux/wakelock.h>
+#include <linux/platform_data/hds_fsa8008.h>
+
+#undef HSD_DEBUG_PRINT
+#undef HSD_ERROR_PRINT
+
+/* TODO */
+/* 1. coding for additional excetion case in probe function */
+/* 2. additional sleep related/excetional case */
+
+#if defined(HSD_DEBUG_PRINT)
+#define HSD_DBG(fmt, args...) printk(KERN_INFO "HSD.fsa8008[%-18s:%5d]" fmt, __func__, __LINE__, ## args)
+#else
+#define HSD_DBG(fmt, args...) do {} while (0)
+#endif
+
+#if defined(HSD_ERROR_PRINT)
+#define HSD_ERR(fmt, args...) printk(KERN_ERR "HSD.fsa8008[%-18s:%5d]" fmt, __func__, __LINE__, ## args)
+#else
+#define HSD_ERR(fmt, args...) do { } while (0)
+#endif
+
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+static struct workqueue_struct *local_fsa8008_workqueue;
+#endif
+
+static struct wake_lock ear_hook_wake_lock;
+
+struct hsd_info {
+ /* function devices provided by this driver */
+ struct switch_dev sdev;
+ struct input_dev *input;
+ /* mutex */
+ struct mutex mutex_lock;
+ /* h/w configuration : initilized by platform data */
+ unsigned int gpio_detect; /* DET : to detect jack inserted or not */
+ unsigned int gpio_mic_en; /* EN : to enable mic */
+ unsigned int gpio_mic_bias_en; /* EN : to enable mic bias */
+ unsigned int gpio_jpole; /* JPOLE : 3pole or 4pole */
+ unsigned int gpio_key; /* S/E button */
+
+ /* callback function which is initialized while probing */
+ void (*set_headset_mic_bias)(int enable);
+
+ unsigned int latency_for_detection;
+ unsigned int latency_for_key;
+
+ unsigned int key_code;
+
+ /* irqs */
+ unsigned int irq_detect;
+ unsigned int irq_key;
+ /* internal states */
+ atomic_t irq_key_enabled;
+ atomic_t is_3_pole_or_not;
+ atomic_t btn_state;
+ /* work for detect_work */
+ struct delayed_work work;
+ struct delayed_work work_for_key_pressed;
+ struct delayed_work work_for_key_released;
+};
+
+enum {
+ NO_DEVICE = 0,
+ HEADSET_WITH_MIC = (1 << 0),
+ HEADSET_NO_MIC = (1 << 1),
+};
+enum {
+ HEADSET_INSERT = 0,
+ HEADSET_REMOVE = 1,
+};
+
+enum {
+ HEADSET_4POLE = 0,
+ HEADSET_3POLE = 1,
+};
+
+enum {
+ FALSE = 0,
+ TRUE = 1,
+};
+
+static ssize_t hsd_print_name(struct switch_dev *sdev, char *buf)
+{
+ switch (switch_get_state(sdev)) {
+ case NO_DEVICE:
+ return sprintf(buf, "No Device\n");
+ case HEADSET_WITH_MIC:
+ return sprintf(buf, "Headset\n");
+ case HEADSET_NO_MIC:
+ return sprintf(buf, "Headset_no_mic\n");
+
+ }
+ return -EINVAL;
+}
+
+static ssize_t hsd_print_state(struct switch_dev *sdev, char *buf)
+{
+ return sprintf(buf, "%d\n", switch_get_state(sdev));
+}
+
+static void button_pressed(struct work_struct *work)
+{
+ struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+ struct hsd_info *hi = container_of(dwork, struct hsd_info, work_for_key_pressed);
+
+
+ if (gpio_get_value_cansleep(hi->gpio_detect) &&
+ (switch_get_state(&hi->sdev)== HEADSET_WITH_MIC)) {
+ HSD_ERR("button_pressed but ear jack is plugged out already!"
+ "just ignore the event.\n");
+ return;
+ }
+
+ HSD_DBG("button_pressed \n");
+
+ atomic_set(&hi->btn_state, 1);
+ input_report_key(hi->input, hi->key_code, 1);
+ input_sync(hi->input);
+}
+
+static void button_released(struct work_struct *work)
+{
+ struct delayed_work *dwork = container_of(
+ work, struct delayed_work, work);
+ struct hsd_info *hi = container_of(
+ dwork, struct hsd_info, work_for_key_released);
+
+ if (gpio_get_value_cansleep(hi->gpio_detect) &&
+ (switch_get_state(&hi->sdev)== HEADSET_WITH_MIC)){
+ HSD_ERR("button_released but ear jack is plugged out already!"
+ "just ignore the event.\n");
+ return;
+ }
+
+ HSD_DBG("button_released \n");
+
+ atomic_set(&hi->btn_state, 0);
+ input_report_key(hi->input, hi->key_code, 0);
+ input_sync(hi->input);
+}
+
+static void insert_headset(struct hsd_info *hi)
+{
+ int earjack_type;
+
+ HSD_DBG("insert_headset");
+
+ if (hi->set_headset_mic_bias)
+ hi->set_headset_mic_bias(TRUE);
+
+ gpio_set_value_cansleep(hi->gpio_mic_en, 1);
+
+ msleep(hi->latency_for_detection);
+
+ earjack_type = gpio_get_value_cansleep(hi->gpio_jpole);
+
+ if (earjack_type == HEADSET_3POLE) {
+ HSD_DBG("3 polarity earjack");
+
+ atomic_set(&hi->is_3_pole_or_not, 1);
+
+ mutex_lock(&hi->mutex_lock);
+ switch_set_state(&hi->sdev, HEADSET_NO_MIC);
+ mutex_unlock(&hi->mutex_lock);
+
+ gpio_set_value_cansleep(hi->gpio_mic_en, 0);
+ if (hi->set_headset_mic_bias)
+ hi->set_headset_mic_bias(FALSE);
+
+ input_report_switch(hi->input, SW_HEADPHONE_INSERT, 1);
+ input_sync(hi->input);
+ } else {
+ HSD_DBG("4 polarity earjack");
+
+ atomic_set(&hi->is_3_pole_or_not, 0);
+
+ mutex_lock(&hi->mutex_lock);
+ switch_set_state(&hi->sdev, HEADSET_WITH_MIC);
+ mutex_unlock(&hi->mutex_lock);
+
+ if (!atomic_read(&hi->irq_key_enabled)) {
+ unsigned long irq_flags;
+ HSD_DBG("enable_irq - irq_key");
+ local_irq_save(irq_flags);
+ enable_irq(hi->irq_key);
+ local_irq_restore(irq_flags);
+
+ atomic_set(&hi->irq_key_enabled, TRUE);
+ }
+ input_report_switch(hi->input, SW_HEADPHONE_INSERT, 1);
+ input_report_switch(hi->input, SW_MICROPHONE_INSERT, 1);
+ input_sync(hi->input);
+ }
+}
+
+static void remove_headset(struct hsd_info *hi)
+{
+ int has_mic = switch_get_state(&hi->sdev);
+
+ HSD_DBG("remove_headset");
+
+ gpio_set_value_cansleep(hi->gpio_mic_en, 0);
+ if (hi->set_headset_mic_bias)
+ hi->set_headset_mic_bias(FALSE);
+
+ atomic_set(&hi->is_3_pole_or_not, 1);
+ mutex_lock(&hi->mutex_lock);
+ switch_set_state(&hi->sdev, NO_DEVICE);
+ mutex_unlock(&hi->mutex_lock);
+
+ if (atomic_read(&hi->irq_key_enabled)) {
+ unsigned long irq_flags;
+
+ local_irq_save(irq_flags);
+ disable_irq(hi->irq_key);
+ local_irq_restore(irq_flags);
+ atomic_set(&hi->irq_key_enabled, FALSE);
+ }
+
+ if (atomic_read(&hi->btn_state))
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+ queue_delayed_work(local_fsa8008_workqueue,
+ &(hi->work_for_key_released), hi->latency_for_key );
+#else
+ schedule_delayed_work(&(hi->work_for_key_released),
+ hi->latency_for_key );
+#endif
+ input_report_switch(hi->input, SW_HEADPHONE_INSERT, 0);
+ if (has_mic == HEADSET_WITH_MIC)
+ input_report_switch(hi->input, SW_MICROPHONE_INSERT, 0);
+ input_sync(hi->input);
+}
+
+static void detect_work(struct work_struct *work)
+{
+ int state;
+ struct delayed_work *dwork = container_of(
+ work, struct delayed_work, work);
+ struct hsd_info *hi = container_of(dwork, struct hsd_info, work);
+
+ HSD_DBG("detect_work");
+
+ state = gpio_get_value_cansleep(hi->gpio_detect);
+
+ if (state == HEADSET_REMOVE) {
+ if (switch_get_state(&hi->sdev) != NO_DEVICE) {
+ HSD_DBG("headset removing\n");
+ remove_headset(hi);
+ } else {
+ HSD_DBG("err_invalid_state state = %d\n", state);
+ }
+ } else {
+
+ if (switch_get_state(&hi->sdev) == NO_DEVICE) {
+ HSD_DBG("headset inserting\n");
+ insert_headset(hi);
+ } else {
+ HSD_DBG("err_invalid_state state = %d\n", state);
+ }
+ }
+
+}
+
+static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
+{
+ struct hsd_info *hi = (struct hsd_info *) dev_id;
+
+ wake_lock_timeout(&ear_hook_wake_lock, 2 * HZ);
+
+ HSD_DBG("gpio_irq_handler");
+
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+ queue_delayed_work(local_fsa8008_workqueue, &(hi->work), HZ/2 ); /* 500ms */
+#else
+ schedule_delayed_work(&(hi->work), HZ/2); /* 500ms */
+#endif
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t button_irq_handler(int irq, void *dev_id)
+{
+ struct hsd_info *hi = (struct hsd_info *) dev_id;
+ int value;
+
+ wake_lock_timeout(&ear_hook_wake_lock, 2 * HZ);
+
+ HSD_DBG("button_irq_handler");
+
+ value = gpio_get_value_cansleep(hi->gpio_key);
+
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+ if (value)
+ queue_delayed_work(local_fsa8008_workqueue,
+ &(hi->work_for_key_pressed),
+ hi->latency_for_key );
+ else
+ queue_delayed_work(local_fsa8008_workqueue,
+ &(hi->work_for_key_released),
+ hi->latency_for_key );
+#else
+ if (value)
+ schedule_delayed_work(&(hi->work_for_key_pressed),
+ hi->latency_for_key );
+ else
+ schedule_delayed_work(&(hi->work_for_key_released),
+ hi->latency_for_key );
+#endif
+
+ return IRQ_HANDLED;
+}
+
+static int hsd_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct fsa8008_platform_data *pdata = pdev->dev.platform_data;
+
+ struct hsd_info *hi;
+
+ HSD_DBG("hsd_probe");
+
+ hi = kzalloc(sizeof(struct hsd_info), GFP_KERNEL);
+ if (NULL == hi) {
+ HSD_ERR("Failed to allloate headset per device info\n");
+ return -ENOMEM;
+ }
+
+ hi->key_code = pdata->key_code;
+
+ platform_set_drvdata(pdev, hi);
+
+ atomic_set(&hi->btn_state, 0);
+ atomic_set(&hi->is_3_pole_or_not, 1);
+
+ hi->gpio_detect = pdata->gpio_detect;
+ hi->gpio_mic_en = pdata->gpio_mic_en;
+ hi->gpio_mic_bias_en = pdata->gpio_mic_bias_en;
+ hi->gpio_jpole = pdata->gpio_jpole;
+ hi->gpio_key = pdata->gpio_key;
+ hi->set_headset_mic_bias = pdata->set_headset_mic_bias;
+
+ hi->latency_for_detection = pdata->latency_for_detection;
+
+ /* 200 ms * HZ / 100 */
+ hi->latency_for_key = 200 * HZ / 1000; /* convert milli to jiffies */
+ mutex_init(&hi->mutex_lock);
+ INIT_DELAYED_WORK(&hi->work, detect_work);
+ INIT_DELAYED_WORK(&hi->work_for_key_pressed, button_pressed);
+ INIT_DELAYED_WORK(&hi->work_for_key_released, button_released);
+
+ /* initialize gpio_detect */
+ ret = gpio_request(hi->gpio_detect, "gpio_detect");
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_request gpio%d (gpio_detect)\n",
+ hi->gpio_detect);
+ goto error_01;
+ }
+
+ ret = gpio_direction_input(hi->gpio_detect);
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_direction_input gpio%d (gpio_detect)\n",
+ hi->gpio_detect);
+ goto error_02;
+ }
+
+ /* initialize gpio_jpole */
+ ret = gpio_request(hi->gpio_jpole, "gpio_jpole");
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_request gpio%d (gpio_jpole)\n",
+ hi->gpio_jpole);
+ goto error_02;
+ }
+
+ ret = gpio_direction_input(hi->gpio_jpole);
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_direction_input gpio%d (gpio_jpole)\n",
+ hi->gpio_jpole);
+ goto error_03;
+ }
+
+ /* initialize gpio_key */
+ ret = gpio_request(hi->gpio_key, "gpio_key");
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_request gpio%d (gpio_key)\n",
+ hi->gpio_key);
+ goto error_03;
+ }
+
+ ret = gpio_direction_input(hi->gpio_key);
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_direction_input gpio%d (gpio_key)\n",
+ hi->gpio_key);
+ goto error_04;
+ }
+
+ /* initialize gpio_mic_en */
+ ret = gpio_request(hi->gpio_mic_en, "gpio_mic_en");
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_request gpio%d (gpio_mic_en)\n",
+ hi->gpio_mic_en);
+ goto error_04;
+ }
+
+ ret = gpio_direction_output(hi->gpio_mic_en, 0);
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_direction_output gpio%d (gpio_mic_en)\n",
+ hi->gpio_mic_en);
+ goto error_05;
+ }
+
+ /* initialize gpio_mic_bias_en */
+ ret = gpio_request(hi->gpio_mic_bias_en, "gpio_mic_bias_en");
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_request gpio%d (gpio_mic_bias_en)\n",
+ hi->gpio_mic_en);
+ goto error_04;
+ }
+
+ ret = gpio_direction_output(hi->gpio_mic_bias_en, 0);
+ if (ret < 0) {
+ HSD_ERR("Failed to gpio_direction_output gpio%d (gpio_mic_bias_en)\n",
+ hi->gpio_mic_en);
+ goto error_05;
+ }
+
+ /* initialize irq of gpio_jpole */
+ hi->irq_detect = gpio_to_irq(hi->gpio_detect);
+ HSD_DBG("hi->irq_detect = %d\n", hi->irq_detect);
+ if (hi->irq_detect < 0) {
+ HSD_ERR("Failed to get interrupt number\n");
+ ret = hi->irq_detect;
+ goto error_05;
+ }
+
+ ret = request_threaded_irq(hi->irq_detect, NULL, gpio_irq_handler,
+ IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
+ pdev->name, hi);
+ if (ret) {
+ HSD_ERR("failed to request button irq");
+ goto error_05;
+ }
+
+ ret = irq_set_irq_wake(hi->irq_detect, 1);
+ if (ret < 0) {
+ HSD_ERR("Failed to set irq_detect interrupt wake\n");
+ goto error_06;
+ }
+
+ /* initialize irq of gpio_key */
+ hi->irq_key = gpio_to_irq(hi->gpio_key);
+ HSD_DBG("hi->irq_key = %d\n", hi->irq_key);
+ if (hi->irq_key < 0) {
+ HSD_ERR("Failed to get interrupt number\n");
+ ret = hi->irq_key;
+ goto error_06;
+ }
+
+ ret = request_threaded_irq(hi->irq_key, NULL, button_irq_handler,
+ IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
+ pdev->name, hi);
+ if (ret) {
+ HSD_ERR("failed to request button irq");
+ goto error_06;
+ }
+
+ disable_irq(hi->irq_key);
+
+ ret = irq_set_irq_wake(hi->irq_key, 1);
+ if (ret < 0) {
+ HSD_ERR("Failed to set irq_key interrupt wake\n");
+ goto error_07;
+ }
+
+ /* initialize switch device */
+ hi->sdev.name = pdata->switch_name;
+ hi->sdev.print_state = hsd_print_state;
+ hi->sdev.print_name = hsd_print_name;
+
+ ret = switch_dev_register(&hi->sdev);
+ if (ret < 0) {
+ HSD_ERR("Failed to register switch device\n");
+ goto error_07;
+ }
+
+ /* initialize input device */
+ hi->input = input_allocate_device();
+ if (!hi->input) {
+ HSD_ERR("Failed to allocate input device\n");
+ ret = -ENOMEM;
+ goto error_08;
+ }
+
+ hi->input->name = pdata->keypad_name;
+
+ hi->input->id.vendor = 0x0001;
+ hi->input->id.product = 1;
+ hi->input->id.version = 1;
+
+ set_bit(EV_SYN, hi->input->evbit);
+ set_bit(EV_KEY, hi->input->evbit);
+ set_bit(EV_SW, hi->input->evbit);
+ set_bit(hi->key_code, hi->input->keybit);
+ set_bit(SW_HEADPHONE_INSERT, hi->input->swbit);
+ set_bit(SW_MICROPHONE_INSERT, hi->input->swbit);
+
+ ret = input_register_device(hi->input);
+ if (ret) {
+ HSD_ERR("Failed to register input device\n");
+ goto error_09;
+ }
+
+ if (!gpio_get_value_cansleep(hi->gpio_detect)) {
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+ /* to detect in initialization with eacjack insertion */
+ queue_delayed_work(local_fsa8008_workqueue, &(hi->work), 0);
+#else
+ /* to detect in initialization with eacjack insertion */
+ schedule_delayed_work(&(hi->work), 0);
+#endif
+ }
+
+#ifdef AT_TEST_GPKD
+ ret = device_create_file(&pdev->dev, &dev_attr_hookkeylog);
+#endif
+
+ return ret;
+
+error_09:
+ input_free_device(hi->input);
+error_08:
+ switch_dev_unregister(&hi->sdev);
+error_07:
+ free_irq(hi->irq_key, 0);
+error_06:
+ free_irq(hi->irq_detect, 0);
+error_05:
+ gpio_free(hi->gpio_mic_en);
+error_04:
+ gpio_free(hi->gpio_key);
+error_03:
+ gpio_free(hi->gpio_jpole);
+error_02:
+ gpio_free(hi->gpio_detect);
+error_01:
+ mutex_destroy(&hi->mutex_lock);
+ kfree(hi);
+
+ return ret;
+}
+
+static int hsd_remove(struct platform_device *pdev)
+{
+ struct hsd_info *hi = (struct hsd_info *)platform_get_drvdata(pdev);
+
+ HSD_DBG("hsd_remove");
+
+ if (switch_get_state(&hi->sdev))
+ remove_headset(hi);
+
+ input_unregister_device(hi->input);
+ switch_dev_unregister(&hi->sdev);
+
+ free_irq(hi->irq_key, 0);
+ free_irq(hi->irq_detect, 0);
+
+ gpio_free(hi->gpio_mic_en);
+ gpio_free(hi->gpio_mic_bias_en);
+ gpio_free(hi->gpio_key);
+ gpio_free(hi->gpio_jpole);
+ gpio_free(hi->gpio_detect);
+
+ mutex_destroy(&hi->mutex_lock);
+
+ kfree(hi);
+
+ return 0;
+}
+
+static struct platform_driver hsd_driver = {
+ .probe = hsd_probe,
+ .remove = hsd_remove,
+ .driver = {
+ .name = "fsa8008",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init hsd_init(void)
+{
+ int ret;
+
+ HSD_DBG("hsd_init");
+
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+ local_fsa8008_workqueue = create_workqueue("fsa8008");
+ if(!local_fsa8008_workqueue)
+ return -ENOMEM;
+#endif
+
+ ret = platform_driver_register(&hsd_driver);
+ if (ret)
+ HSD_ERR("Fail to register platform driver\n");
+
+ wake_lock_init(&ear_hook_wake_lock, WAKE_LOCK_SUSPEND, "ear_hook");
+
+ return ret;
+}
+
+static void __exit hsd_exit(void)
+{
+#ifdef CONFIG_FSA8008_USE_LOCAL_WORK_QUEUE
+ if (local_fsa8008_workqueue)
+ destroy_workqueue(local_fsa8008_workqueue);
+ local_fsa8008_workqueue = NULL;
+#endif
+
+ platform_driver_unregister(&hsd_driver);
+ wake_lock_destroy(&ear_hook_wake_lock);
+ HSD_DBG("hsd_exit");
+}
+
+/* to make init after pmic8058-othc module */
+late_initcall_sync(hsd_init);
+module_exit(hsd_exit);
+
+MODULE_AUTHOR("Yoon Gi Souk <gisouk.yoon@lge.com>");
+MODULE_DESCRIPTION("FSA8008 Headset detection driver");
+MODULE_LICENSE("GPL");
+#endif
diff --git a/include/linux/platform_data/hds_fsa8008.h b/include/linux/platform_data/hds_fsa8008.h
new file mode 100644
index 0000000..f076c8b
--- /dev/null
+++ b/include/linux/platform_data/hds_fsa8008.h
@@ -0,0 +1,37 @@
+/* include/linux/platform_data/hds_fsa8008.h
+ *
+ * Copyright (C) 2012 LG Electronics 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 __LGE_ANDROID_USB_H__
+#define __LGE_ANDROID_USB_H__
+
+struct fsa8008_platform_data {
+ const char *switch_name; /* switch device name */
+ const char *keypad_name; /* keypad device name */
+
+ unsigned int key_code; /* key code for hook */
+
+ unsigned int gpio_detect; /* DET : to detect jack inserted or not */
+ unsigned int gpio_mic_en; /* EN : to enable mic */
+ unsigned int gpio_mic_bias_en; /* EN : to enable mic bias */
+ unsigned int gpio_jpole; /* JPOLE : 3pole or 4pole */
+ unsigned int gpio_key; /* S/E button */
+
+ /* callback function which is initialized while probing */
+ void (*set_headset_mic_bias)(int enable);
+
+ /* latency for pole (3 or 4)detection (in ms) */
+ unsigned int latency_for_detection;
+};
+
+#endif /* __LGE_ANDROID_USB_H__ */
diff --git a/sound/soc/msm/apq8064.c b/sound/soc/msm/apq8064.c
index 862a666..1d81083 100644
--- a/sound/soc/msm/apq8064.c
+++ b/sound/soc/msm/apq8064.c
@@ -31,9 +31,7 @@
#ifdef CONFIG_SND_SOC_TPA2028D
#include <sound/tpa2028d.h>
#endif
-
/* 8064 machine driver */
-
#define PM8921_GPIO_BASE NR_GPIO_IRQS
#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE)
@@ -99,7 +97,9 @@
static struct clk *codec_clk;
static int clk_users;
+#ifndef CONFIG_SWITCH_FSA8008
static int msm_headset_gpios_configured;
+#endif
static struct snd_soc_jack hs_jack;
static struct snd_soc_jack button_jack;
@@ -1134,7 +1134,9 @@
static int msm_audrx_init(struct snd_soc_pcm_runtime *rtd)
{
int err;
+#ifndef CONFIG_SWITCH_FSA8008
uint32_t revision;
+#endif
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
@@ -1188,6 +1190,7 @@
codec_clk = clk_get(cpu_dai->dev, "osr_clk");
+#ifndef CONFIG_SWITCH_FSA8008
/* APQ8064 Rev 1.1 CDP and Liquid have mechanical switch */
revision = socinfo_get_version();
if (apq8064_hs_detect_use_gpio != -1) {
@@ -1233,6 +1236,9 @@
err = tabla_hs_detect(codec, &mbhc_cfg);
return err;
+#else
+ return 0;
+#endif
}
static int msm_slim_0_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
@@ -1349,6 +1355,7 @@
int ret = 0;
pr_debug("%s\n", __func__);
+
#ifdef CONFIG_SND_SOC_QDSP6_HDMI_AUDIO
ret = gpio_request(GPIO_AUX_PCM_DOUT, "AUX PCM DOUT");
if (ret < 0) {
@@ -1377,7 +1384,6 @@
goto fail_clk;
}
#endif
-
return 0;
#ifdef CONFIG_SND_SOC_QDSP6_HDMI_AUDIO
@@ -1389,7 +1395,6 @@
gpio_free(GPIO_AUX_PCM_DOUT);
fail_dout:
#endif
-
return ret;
}
@@ -1470,14 +1475,12 @@
.hw_params = msm_slimbus_4_hw_params,
.shutdown = msm_shutdown,
};
-
static struct snd_soc_ops msm_slimbus_2_be_ops = {
.startup = msm_startup,
.hw_params = msm_slimbus_2_hw_params,
.shutdown = msm_shutdown,
};
-
/* Digital audio interface glue - connects codec <---> CPU */
static struct snd_soc_dai_link msm_dai[] = {
/* FrontEnd DAI Links */
@@ -1929,6 +1932,7 @@
static struct platform_device *msm_snd_device;
+#ifndef CONFIG_SWITCH_FSA8008
static int msm_configure_headset_mic_gpios(void)
{
int ret;
@@ -1969,15 +1973,17 @@
PM8921_GPIO_PM_TO_SYS(35));
else
gpio_direction_output(PM8921_GPIO_PM_TO_SYS(35), 0);
-
return 0;
}
+#endif
static void msm_free_headset_mic_gpios(void)
{
+#ifndef CONFIG_SWITCH_FSA8008
if (msm_headset_gpios_configured) {
gpio_free(PM8921_GPIO_PM_TO_SYS(23));
gpio_free(PM8921_GPIO_PM_TO_SYS(35));
}
+#endif
}
static int __init msm_audio_init(void)
@@ -2010,12 +2016,13 @@
return ret;
}
+#ifndef CONFIG_SWITCH_FSA8008
if (msm_configure_headset_mic_gpios()) {
pr_err("%s Fail to configure headset mic gpios\n", __func__);
msm_headset_gpios_configured = 0;
} else
msm_headset_gpios_configured = 1;
-
+#endif
return ret;
}