summaryrefslogtreecommitdiff
path: root/arch/arm/mach-msm/board-sapphire-h2w.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-msm/board-sapphire-h2w.c')
-rw-r--r--arch/arm/mach-msm/board-sapphire-h2w.c545
1 files changed, 545 insertions, 0 deletions
diff --git a/arch/arm/mach-msm/board-sapphire-h2w.c b/arch/arm/mach-msm/board-sapphire-h2w.c
new file mode 100644
index 000000000000..aa83e216974d
--- /dev/null
+++ b/arch/arm/mach-msm/board-sapphire-h2w.c
@@ -0,0 +1,545 @@
+/*
+ * H2W device detection driver.
+ *
+ * Copyright (C) 2008 HTC Corporation.
+ * Copyright (C) 2008 Google, Inc.
+ *
+ * Authors:
+ * Laurence Chen <Laurence_Chen@htc.com>
+ * Nick Pelly <npelly@google.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.
+ *
+ */
+
+/* For detecting HTC 2 Wire devices, such as wired headset.
+
+ Logically, the H2W driver is always present, and H2W state (hi->state)
+ indicates what is currently plugged into the H2W interface.
+
+ When the headset is plugged in, CABLE_IN1 is pulled low. When the headset
+ button is pressed, CABLE_IN2 is pulled low. These two lines are shared with
+ the TX and RX (respectively) of UART3 - used for serial debugging.
+
+ This headset driver keeps the CPLD configured as UART3 for as long as
+ possible, so that we can do serial FIQ debugging even when the kernel is
+ locked and this driver no longer runs. So it only configures the CPLD to
+ GPIO while the headset is plugged in, and for 10ms during detection work.
+
+ Unfortunately we can't leave the CPLD as UART3 while a headset is plugged
+ in, UART3 is pullup on TX but the headset is pull-down, causing a 55 mA
+ drain on sapphire.
+
+ The headset detection work involves setting CPLD to GPIO, and then pulling
+ CABLE_IN1 high with a stronger pullup than usual. A H2W headset will still
+ pull this line low, whereas other attachments such as a serial console
+ would get pulled up by this stronger pullup.
+
+ Headset insertion/removal causes UEvent's to be sent, and
+ /sys/class/switch/h2w/state to be updated.
+
+ Button presses are interpreted as input event (KEY_MEDIA). Button presses
+ are ignored if the headset is plugged in, so the buttons on 11 pin -> 3.5mm
+ jack adapters do not work until a headset is plugged into the adapter. This
+ is to avoid serial RX traffic causing spurious button press events.
+
+ We tend to check the status of CABLE_IN1 a few more times than strictly
+ necessary during headset detection, to avoid spurious headset insertion
+ events caused by serial debugger TX traffic.
+*/
+
+
+#include <linux/module.h>
+#include <linux/sysdev.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/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/hrtimer.h>
+#include <linux/switch.h>
+#include <linux/input.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <asm/atomic.h>
+#include <mach/board.h>
+#include <mach/vreg.h>
+#include <asm/mach-types.h>
+#include "board-sapphire.h"
+
+#ifdef CONFIG_DEBUG_SAPPHIRE_H2W
+#define H2W_DBG(fmt, arg...) printk(KERN_INFO "[H2W] %s " fmt "\n", __FUNCTION__, ## arg)
+#else
+#define H2W_DBG(fmt, arg...) do {} while (0)
+#endif
+
+static struct workqueue_struct *g_detection_work_queue;
+static void detection_work(struct work_struct *work);
+static DECLARE_WORK(g_detection_work, detection_work);
+enum {
+ NO_DEVICE = 0,
+ HTC_HEADSET = 1,
+};
+
+enum {
+ UART3 = 0,
+ GPIO = 1,
+};
+
+struct h2w_info {
+ struct switch_dev sdev;
+ struct input_dev *input;
+
+ atomic_t btn_state;
+ int ignore_btn;
+
+ unsigned int irq;
+ unsigned int irq_btn;
+
+ struct hrtimer timer;
+ ktime_t debounce_time;
+
+ struct hrtimer btn_timer;
+ ktime_t btn_debounce_time;
+};
+static struct h2w_info *hi;
+
+static ssize_t sapphire_h2w_print_name(struct switch_dev *sdev, char *buf)
+{
+ switch (switch_get_state(&hi->sdev)) {
+ case NO_DEVICE:
+ return sprintf(buf, "No Device\n");
+ case HTC_HEADSET:
+ return sprintf(buf, "Headset\n");
+ }
+ return -EINVAL;
+}
+
+static void configure_cpld(int route)
+{
+ H2W_DBG(" route = %s", route == UART3 ? "UART3" : "GPIO");
+ switch (route) {
+ case UART3:
+ gpio_set_value(SAPPHIRE_GPIO_H2W_SEL0, 0);
+ gpio_set_value(SAPPHIRE_GPIO_H2W_SEL1, 1);
+ break;
+ case GPIO:
+ gpio_set_value(SAPPHIRE_GPIO_H2W_SEL0, 0);
+ gpio_set_value(SAPPHIRE_GPIO_H2W_SEL1, 0);
+ break;
+ }
+}
+
+static void button_pressed(void)
+{
+ H2W_DBG("");
+ atomic_set(&hi->btn_state, 1);
+ input_report_key(hi->input, KEY_MEDIA, 1);
+ input_sync(hi->input);
+}
+
+static void button_released(void)
+{
+ H2W_DBG("");
+ atomic_set(&hi->btn_state, 0);
+ input_report_key(hi->input, KEY_MEDIA, 0);
+ input_sync(hi->input);
+}
+
+#ifdef CONFIG_MSM_SERIAL_DEBUGGER
+extern void msm_serial_debug_enable(int);
+#endif
+
+static void insert_headset(void)
+{
+ unsigned long irq_flags;
+
+ H2W_DBG("");
+
+ switch_set_state(&hi->sdev, HTC_HEADSET);
+ configure_cpld(GPIO);
+
+#ifdef CONFIG_MSM_SERIAL_DEBUGGER
+ msm_serial_debug_enable(false);
+#endif
+
+
+ /* On some non-standard headset adapters (usually those without a
+ * button) the btn line is pulled down at the same time as the detect
+ * line. We can check here by sampling the button line, if it is
+ * low then it is probably a bad adapter so ignore the button.
+ * If the button is released then we stop ignoring the button, so that
+ * the user can recover from the situation where a headset is plugged
+ * in with button held down.
+ */
+ hi->ignore_btn = !gpio_get_value(SAPPHIRE_GPIO_CABLE_IN2);
+
+ /* Enable button irq */
+ local_irq_save(irq_flags);
+ enable_irq(hi->irq_btn);
+ local_irq_restore(irq_flags);
+
+ hi->debounce_time = ktime_set(0, 20000000); /* 20 ms */
+}
+
+static void remove_headset(void)
+{
+ unsigned long irq_flags;
+
+ H2W_DBG("");
+
+ switch_set_state(&hi->sdev, NO_DEVICE);
+ configure_cpld(UART3);
+
+ /* Disable button */
+ local_irq_save(irq_flags);
+ disable_irq(hi->irq_btn);
+ local_irq_restore(irq_flags);
+
+ if (atomic_read(&hi->btn_state))
+ button_released();
+
+ hi->debounce_time = ktime_set(0, 100000000); /* 100 ms */
+}
+
+static void detection_work(struct work_struct *work)
+{
+ unsigned long irq_flags;
+ int clk, cable_in1;
+
+ H2W_DBG("");
+
+ if (gpio_get_value(SAPPHIRE_GPIO_CABLE_IN1) != 0) {
+ /* Headset not plugged in */
+ if (switch_get_state(&hi->sdev) == HTC_HEADSET)
+ remove_headset();
+ return;
+ }
+
+ /* Something plugged in, lets make sure its a headset */
+
+ /* Switch CPLD to GPIO to do detection */
+ configure_cpld(GPIO);
+ /* Disable headset interrupt while detecting.*/
+ local_irq_save(irq_flags);
+ disable_irq(hi->irq);
+ local_irq_restore(irq_flags);
+
+ /* Set GPIO_CABLE_IN1 as output high */
+ gpio_direction_output(SAPPHIRE_GPIO_CABLE_IN1, 1);
+ /* Delay 10ms for pin stable. */
+ msleep(10);
+ /* Save H2W_CLK */
+ clk = gpio_get_value(SAPPHIRE_GPIO_H2W_CLK_GPI);
+ /* Set GPIO_CABLE_IN1 as input */
+ gpio_direction_input(SAPPHIRE_GPIO_CABLE_IN1);
+
+ /* Restore IRQs */
+ local_irq_save(irq_flags);
+ enable_irq(hi->irq);
+ local_irq_restore(irq_flags);
+
+ cable_in1 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN1);
+
+ if (cable_in1 == 0 && clk == 0) {
+ if (switch_get_state(&hi->sdev) == NO_DEVICE)
+ insert_headset();
+ } else {
+ configure_cpld(UART3);
+ H2W_DBG("CABLE_IN1 was low, but not a headset "
+ "(recent cable_in1 = %d, clk = %d)", cable_in1, clk);
+ }
+}
+
+static enum hrtimer_restart button_event_timer_func(struct hrtimer *data)
+{
+ H2W_DBG("");
+
+ if (switch_get_state(&hi->sdev) == HTC_HEADSET) {
+ if (gpio_get_value(SAPPHIRE_GPIO_CABLE_IN2)) {
+ if (hi->ignore_btn)
+ hi->ignore_btn = 0;
+ else if (atomic_read(&hi->btn_state))
+ button_released();
+ } else {
+ if (!hi->ignore_btn && !atomic_read(&hi->btn_state))
+ button_pressed();
+ }
+ }
+
+ return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart detect_event_timer_func(struct hrtimer *data)
+{
+ H2W_DBG("");
+
+ queue_work(g_detection_work_queue, &g_detection_work);
+ return HRTIMER_NORESTART;
+}
+
+static irqreturn_t detect_irq_handler(int irq, void *dev_id)
+{
+ int value1, value2;
+ int retry_limit = 10;
+
+ H2W_DBG("");
+ do {
+ value1 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN1);
+ set_irq_type(hi->irq, value1 ?
+ IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH);
+ value2 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN1);
+ } while (value1 != value2 && retry_limit-- > 0);
+
+ H2W_DBG("value2 = %d (%d retries)", value2, (10-retry_limit));
+
+ if ((switch_get_state(&hi->sdev) == NO_DEVICE) ^ value2) {
+ if (switch_get_state(&hi->sdev) == HTC_HEADSET)
+ hi->ignore_btn = 1;
+ /* Do the rest of the work in timer context */
+ hrtimer_start(&hi->timer, hi->debounce_time, HRTIMER_MODE_REL);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t button_irq_handler(int irq, void *dev_id)
+{
+ int value1, value2;
+ int retry_limit = 10;
+
+ H2W_DBG("");
+ do {
+ value1 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN2);
+ set_irq_type(hi->irq_btn, value1 ?
+ IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH);
+ value2 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN2);
+ } while (value1 != value2 && retry_limit-- > 0);
+
+ H2W_DBG("value2 = %d (%d retries)", value2, (10-retry_limit));
+
+ hrtimer_start(&hi->btn_timer, hi->btn_debounce_time, HRTIMER_MODE_REL);
+
+ return IRQ_HANDLED;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+static void h2w_debug_set(void *data, u64 val)
+{
+ switch_set_state(&hi->sdev, (int)val);
+}
+
+static u64 h2w_debug_get(void *data)
+{
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(h2w_debug_fops, h2w_debug_get, h2w_debug_set, "%llu\n");
+static int __init h2w_debug_init(void)
+{
+ struct dentry *dent;
+
+ dent = debugfs_create_dir("h2w", 0);
+ if (IS_ERR(dent))
+ return PTR_ERR(dent);
+
+ debugfs_create_file("state", 0644, dent, NULL, &h2w_debug_fops);
+
+ return 0;
+}
+
+device_initcall(h2w_debug_init);
+#endif
+
+static int sapphire_h2w_probe(struct platform_device *pdev)
+{
+ int ret;
+ unsigned long irq_flags;
+
+ printk(KERN_INFO "H2W: Registering H2W (headset) driver\n");
+ hi = kzalloc(sizeof(struct h2w_info), GFP_KERNEL);
+ if (!hi)
+ return -ENOMEM;
+
+ atomic_set(&hi->btn_state, 0);
+ hi->ignore_btn = 0;
+
+ hi->debounce_time = ktime_set(0, 100000000); /* 100 ms */
+ hi->btn_debounce_time = ktime_set(0, 10000000); /* 10 ms */
+ hi->sdev.name = "h2w";
+ hi->sdev.print_name = sapphire_h2w_print_name;
+
+ ret = switch_dev_register(&hi->sdev);
+ if (ret < 0)
+ goto err_switch_dev_register;
+
+ g_detection_work_queue = create_workqueue("detection");
+ if (g_detection_work_queue == NULL) {
+ ret = -ENOMEM;
+ goto err_create_work_queue;
+ }
+
+ ret = gpio_request(SAPPHIRE_GPIO_CABLE_IN1, "h2w_detect");
+ if (ret < 0)
+ goto err_request_detect_gpio;
+
+ ret = gpio_request(SAPPHIRE_GPIO_CABLE_IN2, "h2w_button");
+ if (ret < 0)
+ goto err_request_button_gpio;
+
+ ret = gpio_direction_input(SAPPHIRE_GPIO_CABLE_IN1);
+ if (ret < 0)
+ goto err_set_detect_gpio;
+
+ ret = gpio_direction_input(SAPPHIRE_GPIO_CABLE_IN2);
+ if (ret < 0)
+ goto err_set_button_gpio;
+
+ hi->irq = gpio_to_irq(SAPPHIRE_GPIO_CABLE_IN1);
+ if (hi->irq < 0) {
+ ret = hi->irq;
+ goto err_get_h2w_detect_irq_num_failed;
+ }
+
+ hi->irq_btn = gpio_to_irq(SAPPHIRE_GPIO_CABLE_IN2);
+ if (hi->irq_btn < 0) {
+ ret = hi->irq_btn;
+ goto err_get_button_irq_num_failed;
+ }
+
+ /* Set CPLD MUX to H2W <-> CPLD GPIO */
+ configure_cpld(UART3);
+ /* Set the CPLD connected H2W GPIO's to input */
+ gpio_set_value(SAPPHIRE_GPIO_H2W_CLK_DIR, 0);
+ gpio_set_value(SAPPHIRE_GPIO_H2W_DAT_DIR, 0);
+
+ hrtimer_init(&hi->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ hi->timer.function = detect_event_timer_func;
+ hrtimer_init(&hi->btn_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ hi->btn_timer.function = button_event_timer_func;
+
+ ret = request_irq(hi->irq, detect_irq_handler,
+ IRQF_TRIGGER_LOW, "h2w_detect", NULL);
+ if (ret < 0)
+ goto err_request_detect_irq;
+
+ /* Disable button until plugged in */
+ set_irq_flags(hi->irq_btn, IRQF_VALID | IRQF_NOAUTOEN);
+ ret = request_irq(hi->irq_btn, button_irq_handler,
+ IRQF_TRIGGER_LOW, "h2w_button", NULL);
+ if (ret < 0)
+ goto err_request_h2w_headset_button_irq;
+
+ ret = set_irq_wake(hi->irq, 1);
+ if (ret < 0)
+ goto err_request_input_dev;
+ ret = set_irq_wake(hi->irq_btn, 1);
+ if (ret < 0)
+ goto err_request_input_dev;
+
+ hi->input = input_allocate_device();
+ if (!hi->input) {
+ ret = -ENOMEM;
+ goto err_request_input_dev;
+ }
+
+ hi->input->name = "h2w headset";
+ hi->input->evbit[0] = BIT_MASK(EV_KEY);
+ hi->input->keybit[BIT_WORD(KEY_MEDIA)] = BIT_MASK(KEY_MEDIA);
+
+ ret = input_register_device(hi->input);
+ if (ret < 0)
+ goto err_register_input_dev;
+
+ return 0;
+
+err_register_input_dev:
+ input_free_device(hi->input);
+err_request_input_dev:
+ free_irq(hi->irq_btn, 0);
+err_request_h2w_headset_button_irq:
+ free_irq(hi->irq, 0);
+err_request_detect_irq:
+err_get_button_irq_num_failed:
+err_get_h2w_detect_irq_num_failed:
+err_set_button_gpio:
+err_set_detect_gpio:
+ gpio_free(SAPPHIRE_GPIO_CABLE_IN2);
+err_request_button_gpio:
+ gpio_free(SAPPHIRE_GPIO_CABLE_IN1);
+err_request_detect_gpio:
+ destroy_workqueue(g_detection_work_queue);
+err_create_work_queue:
+ switch_dev_unregister(&hi->sdev);
+err_switch_dev_register:
+ printk(KERN_ERR "H2W: Failed to register driver\n");
+
+ return ret;
+}
+
+static int sapphire_h2w_remove(struct platform_device *pdev)
+{
+ H2W_DBG("");
+ if (switch_get_state(&hi->sdev))
+ remove_headset();
+ input_unregister_device(hi->input);
+ gpio_free(SAPPHIRE_GPIO_CABLE_IN2);
+ gpio_free(SAPPHIRE_GPIO_CABLE_IN1);
+ free_irq(hi->irq_btn, 0);
+ free_irq(hi->irq, 0);
+ destroy_workqueue(g_detection_work_queue);
+ switch_dev_unregister(&hi->sdev);
+
+ return 0;
+}
+
+static struct platform_device sapphire_h2w_device = {
+ .name = "sapphire-h2w",
+};
+
+static struct platform_driver sapphire_h2w_driver = {
+ .probe = sapphire_h2w_probe,
+ .remove = sapphire_h2w_remove,
+ .driver = {
+ .name = "sapphire-h2w",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init sapphire_h2w_init(void)
+{
+ if (!machine_is_sapphire())
+ return 0;
+ int ret;
+ H2W_DBG("");
+ ret = platform_driver_register(&sapphire_h2w_driver);
+ if (ret)
+ return ret;
+ return platform_device_register(&sapphire_h2w_device);
+}
+
+static void __exit sapphire_h2w_exit(void)
+{
+ platform_device_unregister(&sapphire_h2w_device);
+ platform_driver_unregister(&sapphire_h2w_driver);
+}
+
+module_init(sapphire_h2w_init);
+module_exit(sapphire_h2w_exit);
+
+MODULE_AUTHOR("Laurence Chen <Laurence_Chen@htc.com>");
+MODULE_DESCRIPTION("HTC 2 Wire detection driver for sapphire");
+MODULE_LICENSE("GPL");