From: Juha Yrjola <juha.yrjola@solidboot.com>
Date: Mon, 12 Mar 2007 17:16:14 +0000 (+0200)
Subject: CBUS: Add driver for Retu/Vilma headset detection
X-Git-Tag: v2.6.21-omap1~83^2~1
X-Git-Url: http://pilppa.com/gitweb/?a=commitdiff_plain;h=89692557b57c8fb5a31befa7e33c214734507789;p=linux-2.6-omap-h63xx.git

CBUS: Add driver for Retu/Vilma headset detection

Used in Nokia N800.

Signed-off-by: Juha Yrjola <juha.yrjola@solidboot.com>
---

diff --git a/drivers/cbus/Kconfig b/drivers/cbus/Kconfig
index f1a5c63e8c9..25f80394a60 100644
--- a/drivers/cbus/Kconfig
+++ b/drivers/cbus/Kconfig
@@ -78,4 +78,12 @@ config CBUS_RETU_WDT
 	  Say Y here if you want support for the watchdog in Retu. This will
 	  expose a sysfs interface to grok it.
 
+config CBUS_RETU_HEADSET
+	depends on CBUS_RETU && SYSFS
+	tristate "Support for headset detection with Retu/Vilma"
+	---help---
+	  Say Y here if you want support detecting a headset that's connected
+	  to Retu/Vilma. Detection state and events are exposed through
+	  sysfs.
+
 endmenu
diff --git a/drivers/cbus/Makefile b/drivers/cbus/Makefile
index 6390139fb96..347c2a465af 100644
--- a/drivers/cbus/Makefile
+++ b/drivers/cbus/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_CBUS_RETU_RTC)	+= retu-rtc.o
 obj-$(CONFIG_CBUS_RETU_WDT)	+= retu-wdt.o
 obj-$(CONFIG_CBUS_TAHVO_USER)	+= tahvo-user.o
 obj-$(CONFIG_CBUS_RETU_USER)	+= retu-user.o
+obj-$(CONFIG_CBUS_RETU_HEADSET)	+= retu-headset.o
diff --git a/drivers/cbus/retu-headset.c b/drivers/cbus/retu-headset.c
new file mode 100644
index 00000000000..cd55bf2131f
--- /dev/null
+++ b/drivers/cbus/retu-headset.c
@@ -0,0 +1,335 @@
+/**
+ * Retu/Vilma headset detection
+ *
+ * Copyright (C) 2006 Nokia Corporation
+ *
+ * Written by Juha Yrjölä
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+
+#include "retu.h"
+
+#define RETU_ADC_CHANNEL_HOOKDET	0x05
+
+#define RETU_HEADSET_KEY		KEY_PHONE
+
+#define STATE_DISABLE_DET		1
+#define STATE_ENABLE_DET		2
+
+struct retu_headset {
+	struct platform_device *pdev;
+	struct input_dev *idev;
+	unsigned bias_enabled:1;
+
+	unsigned detection_enabled:1, pressed:1;
+	int detection_state;
+	struct timer_list enable_timer, detect_timer;
+	spinlock_t lock;
+};
+
+static void retu_headset_set_bias(int enable)
+{
+	if (enable) {
+		retu_set_clear_reg_bits(RETU_REG_AUDTXR,
+					(1 << 0) | (1 << 1), 0);
+		msleep(2);
+		retu_set_clear_reg_bits(RETU_REG_AUDTXR, 1 << 3, 0);
+	} else {
+		retu_set_clear_reg_bits(RETU_REG_AUDTXR, 0,
+				    (1 << 0) | (1 << 1) | (1 << 3));
+	}
+}
+
+static void retu_headset_enable(struct retu_headset *hs)
+{
+	if (hs->bias_enabled)
+		return;
+	hs->bias_enabled = 1;
+	retu_headset_set_bias(1);
+}
+
+static void retu_headset_disable(struct retu_headset *hs)
+{
+	if (!hs->bias_enabled)
+		return;
+	hs->bias_enabled = 0;
+	retu_headset_set_bias(0);
+}
+
+static void retu_headset_det_enable(struct retu_headset *hs)
+{
+	if (hs->detection_enabled)
+		return;
+	hs->detection_enabled = 1;
+	retu_set_clear_reg_bits(RETU_REG_CC1, (1 << 10) | (1 << 8), 0);
+	retu_enable_irq(RETU_INT_HOOK);
+}
+
+static void retu_headset_det_disable(struct retu_headset *hs)
+{
+	if (!hs->detection_enabled)
+		return;
+	hs->detection_enabled = 0;
+	retu_disable_irq(RETU_INT_HOOK);
+	del_timer_sync(&hs->enable_timer);
+	del_timer_sync(&hs->detect_timer);
+	if (hs->pressed)
+		input_report_key(hs->idev, RETU_HEADSET_KEY, 0);
+	retu_set_clear_reg_bits(RETU_REG_CC1, 0, (1 << 10) | (1 << 8));
+}
+
+static ssize_t retu_headset_hookdet_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	int val;
+
+	val = retu_read_adc(RETU_ADC_CHANNEL_HOOKDET);
+	return sprintf(buf, "%d\n", val);
+}
+
+static DEVICE_ATTR(hookdet, S_IRUGO, retu_headset_hookdet_show, NULL);
+
+static ssize_t retu_headset_enable_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct retu_headset *hs = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", hs->bias_enabled);
+}
+
+static ssize_t retu_headset_enable_store(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct retu_headset *hs = dev_get_drvdata(dev);
+	int enable;
+
+	if (sscanf(buf, "%u", &enable) != 1)
+		return -EINVAL;
+	if (enable)
+		retu_headset_enable(hs);
+	else
+	        retu_headset_disable(hs);
+	return count;
+}
+
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
+		   retu_headset_enable_show, retu_headset_enable_store);
+
+static ssize_t retu_headset_enable_det_show(struct device *dev,
+					    struct device_attribute *attr,
+					    char *buf)
+{
+	struct retu_headset *hs = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", hs->detection_enabled);
+}
+
+static ssize_t retu_headset_enable_det_store(struct device *dev,
+					     struct device_attribute *attr,
+					     const char *buf, size_t count)
+{
+	struct retu_headset *hs = dev_get_drvdata(dev);
+	int enable;
+
+	if (sscanf(buf, "%u", &enable) != 1)
+		return -EINVAL;
+	if (enable)
+		retu_headset_det_enable(hs);
+	else
+	        retu_headset_det_disable(hs);
+	return count;
+}
+
+static DEVICE_ATTR(enable_det, S_IRUGO | S_IWUSR | S_IWGRP,
+		   retu_headset_enable_det_show,
+		   retu_headset_enable_det_store);
+
+static void retu_headset_hook_interrupt(unsigned long arg)
+{
+	struct retu_headset *hs = (struct retu_headset *) arg;
+	unsigned long flags;
+	int was_pressed = 0;
+
+	retu_ack_irq(RETU_INT_HOOK);
+	spin_lock_irqsave(&hs->lock, flags);
+	if (!hs->pressed) {
+		/* Headset button was just pressed down. */
+		hs->pressed = 1;
+		input_report_key(hs->idev, RETU_HEADSET_KEY, 1);
+		was_pressed = 1;
+	}
+	spin_unlock_irqrestore(&hs->lock, flags);
+	if (was_pressed)
+		dev_info(&hs->pdev->dev, "button pressed\n");
+	retu_set_clear_reg_bits(RETU_REG_CC1, 0, (1 << 10) | (1 << 8));
+	mod_timer(&hs->enable_timer, jiffies + msecs_to_jiffies(50));
+}
+
+static void retu_headset_enable_timer(unsigned long arg)
+{
+	struct retu_headset *hs = (struct retu_headset *) arg;
+
+	retu_set_clear_reg_bits(RETU_REG_CC1, (1 << 10) | (1 << 8), 0);
+	mod_timer(&hs->detect_timer, jiffies + msecs_to_jiffies(350));
+}
+
+static void retu_headset_detect_timer(unsigned long arg)
+{
+	struct retu_headset *hs = (struct retu_headset *) arg;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hs->lock, flags);
+	BUG_ON(!hs->pressed);
+	input_report_key(hs->idev, RETU_HEADSET_KEY, 0);
+	hs->pressed = 0;
+	spin_unlock_irqrestore(&hs->lock, flags);
+	dev_info(&hs->pdev->dev, "button released\n");
+}
+
+static int __init retu_headset_probe(struct platform_device *pdev)
+{
+	struct retu_headset *hs;
+	int r;
+
+	hs = kzalloc(sizeof(*hs), GFP_KERNEL);
+	if (hs == NULL)
+		return -ENOMEM;
+
+	hs->pdev = pdev;
+
+	hs->idev = input_allocate_device();
+	if (hs->idev == NULL) {
+		r = -ENOMEM;
+		goto err1;
+	}
+	hs->idev->name = "retu-headset";
+	hs->idev->cdev.dev = &pdev->dev;
+	hs->idev->private = hs;
+	set_bit(EV_KEY, hs->idev->evbit);
+	set_bit(RETU_HEADSET_KEY, hs->idev->keybit);
+	r = input_register_device(hs->idev);
+	if (r < 0)
+		goto err2;
+
+	r = device_create_file(&pdev->dev, &dev_attr_hookdet);
+	if (r < 0)
+		goto err3;
+	r = device_create_file(&pdev->dev, &dev_attr_enable);
+	if (r < 0)
+		goto err4;
+	r = device_create_file(&pdev->dev, &dev_attr_enable_det);
+	if (r < 0)
+		goto err5;
+	platform_set_drvdata(pdev, hs);
+
+	spin_lock_init(&hs->lock);
+	setup_timer(&hs->enable_timer, retu_headset_enable_timer,
+		    (unsigned long) hs);
+	setup_timer(&hs->detect_timer, retu_headset_detect_timer,
+		    (unsigned long) hs);
+
+	r = retu_request_irq(RETU_INT_HOOK, retu_headset_hook_interrupt,
+			     (unsigned long) hs, "hookdet");
+	if (r != 0) {
+		dev_err(&pdev->dev, "hookdet IRQ not available\n");
+		goto err6;
+	}
+	retu_disable_irq(RETU_INT_HOOK);
+	return 0;
+err6:
+	device_remove_file(&pdev->dev, &dev_attr_enable_det);
+err5:
+	device_remove_file(&pdev->dev, &dev_attr_enable);
+err4:
+	device_remove_file(&pdev->dev, &dev_attr_hookdet);
+err3:
+	input_unregister_device(hs->idev);
+err2:
+	input_free_device(hs->idev);
+err1:
+	kfree(hs);
+	return r;
+}
+
+static int retu_headset_remove(struct platform_device *pdev)
+{
+	struct retu_headset *hs = platform_get_drvdata(pdev);
+
+	device_remove_file(&pdev->dev, &dev_attr_hookdet);
+	device_remove_file(&pdev->dev, &dev_attr_enable);
+	device_remove_file(&pdev->dev, &dev_attr_enable_det);
+	retu_headset_disable(hs);
+	retu_headset_det_disable(hs);
+	retu_free_irq(RETU_INT_HOOK);
+	input_unregister_device(hs->idev);
+	input_free_device(hs->idev);
+	return 0;
+}
+
+static int retu_headset_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+	return 0;
+}
+
+static int retu_headset_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+
+static struct platform_driver retu_headset_driver = {
+	.probe		= retu_headset_probe,
+	.remove		= retu_headset_remove,
+	.suspend	= retu_headset_suspend,
+	.resume		= retu_headset_resume,
+	.driver		= {
+		.name	= "retu-headset",
+	},
+};
+
+static int __init retu_headset_init(void)
+{
+	int r;
+
+	printk(KERN_INFO "Retu/Vilma headset driver initializing\n");
+
+	r = platform_driver_register(&retu_headset_driver);
+	if (r < 0)
+		return r;
+
+	return 0;
+}
+
+static void __exit retu_headset_exit(void)
+{
+	platform_driver_unregister(&retu_headset_driver);
+}
+
+module_init(retu_headset_init);
+module_exit(retu_headset_exit);
+
+MODULE_DESCRIPTION("Retu/Vilma headset detection");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Juha Yrjölä");
diff --git a/drivers/cbus/retu.h b/drivers/cbus/retu.h
index 02be6d4defc..49f23e664dc 100644
--- a/drivers/cbus/retu.h
+++ b/drivers/cbus/retu.h
@@ -40,6 +40,7 @@
 #define RETU_REG_CTRL_SET	0x10	/* Regulator set register */
 #define RETU_REG_STATUS		0x16	/* Status register */
 #define RETU_REG_WATCHDOG	0x17	/* Watchdog register */
+#define RETU_REG_AUDTXR		0x18	/* Audio Codec Tx register */
 #define RETU_REG_MAX		0x1f
 
 /* Interrupt sources */
@@ -49,6 +50,8 @@
 #define RETU_INT_RTCM		3
 #define RETU_INT_RTCD		4
 #define RETU_INT_RTCA		5
+#define RETU_INT_HOOK		6
+#define RETU_INT_HEAD		7
 #define RETU_INT_ADCS		8
 
 #define	MAX_RETU_IRQ_HANDLERS	16