/* 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
 */
#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>

#define FSA8008_USE_WORK_QUEUE
#define FSA8008_KEY_LATENCY_TIME	200 /* in ms */
#define FSA8008_DEBOUNCE_TIME		500 /* in ms */
#define FSA8008_WAKELOCK_TIMEOUT	(2*HZ)

#define HSD_DEBUG_PRINT

#ifdef HSD_DEBUG_PRINT
#define HSD_DBG(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __func__, ##args)
#else
#define HSD_DBG(fmt, args...) do {} while (0)
#endif

#ifdef FSA8008_USE_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_detect_can_wakeup;
	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);
	void (*set_uart_console)(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;
	int saved_detect;
	/* 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,
};

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)) {
		pr_warn("%s: ear jack was plugged out already!"
			"just ignore the event.\n", __func__);
		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)){
		pr_warn("%s: ear jack was plugged out already!"
			"just ignore the event.\n", __func__);
		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(1);

	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(0);
		if (hi->set_uart_console)
			hi->set_uart_console(0);

		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)) {
			HSD_DBG("enable_irq - irq_key");
			enable_irq(hi->irq_key);

			atomic_set(&hi->irq_key_enabled, 1);
		}
		if (hi->set_uart_console)
			hi->set_uart_console(0);
		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(0);

	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)) {
		disable_irq(hi->irq_key);
		atomic_set(&hi->irq_key_enabled, 0);
	}

	if (atomic_read(&hi->btn_state))
#ifdef	FSA8008_USE_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);

	state = gpio_get_value_cansleep(hi->gpio_detect);

	if (state == HEADSET_REMOVE) {
		if (switch_get_state(&hi->sdev) != NO_DEVICE) {
			remove_headset(hi);
		} else {
			HSD_DBG("err_invalid_state state = %d\n", state);
		}
	} else {

		if (switch_get_state(&hi->sdev) == NO_DEVICE) {
			insert_headset(hi);
		} else {
			HSD_DBG("err_invalid_state state = %d\n", state);
		}
	}

}

static void schedule_detect_work(struct hsd_info *hi)
{
	wake_lock_timeout(&ear_hook_wake_lock, FSA8008_WAKELOCK_TIMEOUT);

#ifdef FSA8008_USE_WORK_QUEUE
	queue_delayed_work(local_fsa8008_workqueue, &(hi->work),
			msecs_to_jiffies(FSA8008_DEBOUNCE_TIME));
#else
	schedule_delayed_work(&(hi->work),
			msecs_to_jiffies(FSA8008_DEBOUNCE_TIME));
#endif
}

static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
	struct hsd_info *hi = (struct hsd_info *) dev_id;

	HSD_DBG("gpio_irq_handler");

	schedule_detect_work(hi);

	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;

	HSD_DBG("button_irq_handler");

	wake_lock_timeout(&ear_hook_wake_lock, FSA8008_WAKELOCK_TIMEOUT);

	value = gpio_get_value_cansleep(hi->gpio_key);

#ifdef	FSA8008_USE_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_gpio_init(struct hsd_info *hi)
{
	int ret;

	/* initialize gpio_detect */
	ret = gpio_request_one(hi->gpio_detect, GPIOF_IN, "gpio_detect");
	if (ret < 0) {
		pr_err("%s: Failed to gpio_request gpio%d (gpio_detect)\n",
				__func__, hi->gpio_detect);
		goto error_01;
	}

	/* initialize gpio_jpole */
	ret = gpio_request_one(hi->gpio_jpole, GPIOF_IN, "gpio_jpole");
	if (ret < 0) {
		pr_err("%s: Failed to gpio_request gpio%d (gpio_jpole)\n",
				__func__, hi->gpio_jpole);
		goto error_02;
	}

	/* initialize gpio_key */
	ret = gpio_request_one(hi->gpio_key, GPIOF_IN, "gpio_key");
	if (ret < 0) {
		pr_err("%s: Failed to gpio_request gpio%d (gpio_key)\n",
				__func__, hi->gpio_key);
		goto error_03;
	}

	/* initialize gpio_mic_en */
	ret = gpio_request_one(hi->gpio_mic_en, GPIOF_OUT_INIT_LOW,
			"gpio_mic_en");
	if (ret < 0) {
		pr_err("%s: Failed to gpio_request gpio%d (gpio_mic_en)\n",
				__func__, hi->gpio_mic_en);
		goto error_04;
	}

	/* initialize gpio_mic_bias_en */
	if (gpio_is_valid(hi->gpio_mic_bias_en)) {
		ret = gpio_request_one(hi->gpio_mic_bias_en,
				GPIOF_OUT_INIT_LOW, "gpio_mic_bias_en");
		if (ret < 0) {
			pr_err("%s: Failed to gpio_request gpio%d "
			       "(gpio_mic_bias_en)\n",
					__func__, hi->gpio_mic_en);
			goto error_05;
		}
	}

	return 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:
	return ret;
}

static void hsd_gpio_free(struct hsd_info *hi)
{
	if (gpio_is_valid(hi->gpio_mic_bias_en))
		gpio_free(hi->gpio_mic_bias_en);
	gpio_free(hi->gpio_mic_en);
	gpio_free(hi->gpio_key);
	gpio_free(hi->gpio_jpole);
	gpio_free(hi->gpio_detect);
}

static int hsd_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct fsa8008_platform_data *pdata = pdev->dev.platform_data;
	struct hsd_info *hi;

	pr_info("fsa8008 probe\n");

	if (!pdata) {
		pr_err("%s: no pdata\n", __func__);
		return -ENODEV;
	}

	hi = kzalloc(sizeof(struct hsd_info), GFP_KERNEL);
	if (NULL == hi) {
		pr_err("%s: out of memory\n", __func__);
		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_detect_can_wakeup = pdata->gpio_detect_can_wakeup;
	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->set_uart_console = pdata->set_uart_console;
	hi->latency_for_detection = pdata->latency_for_detection;
	hi->latency_for_key = msecs_to_jiffies(FSA8008_KEY_LATENCY_TIME);

	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);

	if (hsd_gpio_init(hi) < 0)
		goto error_01;

	/* 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) {
		pr_err("%s: Failed to get interrupt number\n", __func__);
		ret = hi->irq_detect;
		goto error_02;
	}

	ret = request_threaded_irq(hi->irq_detect, NULL, gpio_irq_handler,
			IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
			pdev->name, hi);
	if (ret) {
		pr_err("%s: failed to request button irq\n", __func__);
		goto error_02;
	}

	if (hi->gpio_detect_can_wakeup) {
		ret = irq_set_irq_wake(hi->irq_detect, 1);
		if (ret < 0) {
			pr_err("%s: Failed to set irq_detect interrupt wake\n",
					__func__);
			goto error_03;
		}
	}

	/* 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) {
		pr_err("%s: Failed to get interrupt number\n", __func__);
		ret = hi->irq_key;
		goto error_03;
	}

	ret = request_threaded_irq(hi->irq_key, NULL, button_irq_handler,
			IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
			pdev->name, hi);
	if (ret) {
		pr_err("%s: failed to request button irq\n", __func__);
		goto error_03;
	}

	disable_irq(hi->irq_key);

	ret = irq_set_irq_wake(hi->irq_key, 1);
	if (ret < 0) {
		pr_err("%s: Failed to set irq_key interrupt wake\n", __func__);
		goto error_04;
	}

	/* 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) {
		pr_err("%s: Failed to register switch device\n", __func__);
		goto error_04;
	}

	/* initialize input device */
	hi->input = input_allocate_device();
	if (!hi->input) {
		pr_err("%s: Failed to allocate input device\n", __func__);
		ret = -ENOMEM;
		goto error_05;
	}

	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) {
		pr_err("%s: Failed to register input device\n", __func__);
		goto error_06;
	}

	if (!gpio_get_value_cansleep(hi->gpio_detect)) {
#ifdef FSA8008_USE_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
	}

	return ret;

error_06:
	input_free_device(hi->input);
error_05:
	switch_dev_unregister(&hi->sdev);
error_04:
	free_irq(hi->irq_key, 0);
error_03:
	free_irq(hi->irq_detect, 0);
error_02:
	hsd_gpio_free(hi);
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);

	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);

	hsd_gpio_free(hi);

	mutex_destroy(&hi->mutex_lock);
	kfree(hi);

	return 0;
}

static int hsd_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct hsd_info *hi = platform_get_drvdata(pdev);

	if (!hi->gpio_detect_can_wakeup) {
		disable_irq(hi->irq_detect);
		hi->saved_detect = gpio_get_value(hi->gpio_detect);
	}

	return 0;
}

static int hsd_resume(struct device *dev)
{

	struct platform_device *pdev = to_platform_device(dev);
	struct hsd_info *hi = platform_get_drvdata(pdev);
	int detect = 0;

	detect = gpio_get_value(hi->gpio_detect);
	if (HEADSET_INSERT == detect)
		if (hi->set_uart_console)
			hi->set_uart_console(0);

	if (!hi->gpio_detect_can_wakeup) {
		enable_irq(hi->irq_detect);
		if (hi->saved_detect != detect)
			schedule_detect_work(hi);
	}

	return 0;
}

static const struct dev_pm_ops hsd_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(hsd_suspend, hsd_resume)
};

static struct platform_driver hsd_driver = {
	.probe          = hsd_probe,
	.remove         = hsd_remove,
	.driver         = {
		.name   = "fsa8008",
		.owner  = THIS_MODULE,
		.pm = &hsd_pm_ops,
	},
};

static int __init hsd_init(void)
{
	int ret;

	pr_info("fsa8008 init\n");

	wake_lock_init(&ear_hook_wake_lock, WAKE_LOCK_SUSPEND, "ear_hook");

#ifdef FSA8008_USE_WORK_QUEUE
	local_fsa8008_workqueue = create_workqueue("fsa8008");
	if (!local_fsa8008_workqueue) {
		pr_err("%s: out of memory\n", __func__);
		ret = -ENOMEM;
		goto err_workqueue;
	}
#endif

	ret = platform_driver_register(&hsd_driver);
	if (ret < 0) {
		pr_err("%s: Fail to register platform driver\n", __func__);
		goto err_platform_driver_register;
	}

	return 0;

err_platform_driver_register:
#ifdef FSA8008_USE_WORK_QUEUE
	if (local_fsa8008_workqueue)
		destroy_workqueue(local_fsa8008_workqueue);
	local_fsa8008_workqueue = NULL;
#endif
err_workqueue:
	wake_lock_destroy(&ear_hook_wake_lock);

	return ret;
}

static void __exit hsd_exit(void)
{
#ifdef FSA8008_USE_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);
}

/* to make init after pmicxxxx 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");