418 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			418 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 *
 | 
						|
 * /arch/arm/mach-msm/htc_headset_microp.c
 | 
						|
 *
 | 
						|
 *  HTC Micro-P headset detection driver.
 | 
						|
 *
 | 
						|
 *  Copyright (C) 2010 HTC, 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.
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
#include <linux/gpio.h>
 | 
						|
#include <linux/platform_device.h>
 | 
						|
 | 
						|
#include <mach/atmega_microp.h>
 | 
						|
 | 
						|
#include <mach/htc_headset_mgr.h>
 | 
						|
#include <mach/htc_headset_microp.h>
 | 
						|
 | 
						|
#define DRIVER_NAME "HS_MICROP"
 | 
						|
 | 
						|
static struct htc_headset_microp_info *hi;
 | 
						|
 | 
						|
static struct workqueue_struct *detect_wq;
 | 
						|
static void detect_microp_work_func(struct work_struct *work);
 | 
						|
static DECLARE_DELAYED_WORK(detect_microp_work, detect_microp_work_func);
 | 
						|
 | 
						|
static struct workqueue_struct *button_wq;
 | 
						|
static void button_microp_work_func(struct work_struct *work);
 | 
						|
static DECLARE_WORK(button_microp_work, button_microp_work_func);
 | 
						|
 | 
						|
static void hs_microp_key_enable(int enable)
 | 
						|
{
 | 
						|
	uint8_t addr;
 | 
						|
	uint8_t data[3];
 | 
						|
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	if (hi->pdata.remote_enable_pin) {
 | 
						|
		addr = (enable) ? MICROP_I2C_WCMD_GPO_LED_STATUS_EN :
 | 
						|
				  MICROP_I2C_WCMD_GPO_LED_STATUS_DIS;
 | 
						|
		data[0] = (hi->pdata.remote_enable_pin >> 16) & 0xFF;
 | 
						|
		data[1] = (hi->pdata.remote_enable_pin >> 8) & 0xFF;
 | 
						|
		data[2] = (hi->pdata.remote_enable_pin >> 0) & 0xFF;
 | 
						|
		microp_i2c_write(addr, data, 3);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static int headset_microp_enable_interrupt(int interrupt, int enable)
 | 
						|
{
 | 
						|
	uint8_t addr = 0x00;
 | 
						|
	uint8_t data[2];
 | 
						|
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	addr = (enable) ? MICROP_I2C_WCMD_GPI_INT_CTL_EN :
 | 
						|
			  MICROP_I2C_WCMD_GPI_INT_CTL_DIS;
 | 
						|
 | 
						|
	memset(data, 0x00, sizeof(data));
 | 
						|
	data[0] = (uint8_t) (interrupt >> 8);
 | 
						|
	data[1] = (uint8_t) interrupt;
 | 
						|
 | 
						|
	return microp_i2c_write(addr, data, 2);
 | 
						|
}
 | 
						|
 | 
						|
static int headset_microp_enable_button_int(int enable)
 | 
						|
{
 | 
						|
	int ret = 0;
 | 
						|
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	if (hi->pdata.remote_int)
 | 
						|
		ret = headset_microp_enable_interrupt(hi->pdata.remote_int,
 | 
						|
						      enable);
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static int headset_microp_remote_adc(int *adc)
 | 
						|
{
 | 
						|
	int ret = 0;
 | 
						|
	uint8_t data[2];
 | 
						|
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	data[0] = 0x00;
 | 
						|
	data[1] = hi->pdata.adc_channel;
 | 
						|
	ret = microp_read_adc(data);
 | 
						|
	if (ret != 0) {
 | 
						|
		SYS_MSG("Failed to read Micro-P ADC");
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	*adc = data[0] << 8 | data[1];
 | 
						|
	SYS_MSG("Remote ADC %d (0x%X)", *adc, *adc);
 | 
						|
 | 
						|
	return 1;
 | 
						|
}
 | 
						|
 | 
						|
static int headset_microp_mic_status(void)
 | 
						|
{
 | 
						|
	int ret = HEADSET_NO_MIC;
 | 
						|
	int adc = 0;
 | 
						|
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	ret = headset_microp_remote_adc(&adc);
 | 
						|
	if (!ret) {
 | 
						|
		SYS_MSG("Failed to read Micro-P remote ADC");
 | 
						|
		return HEADSET_NO_MIC;
 | 
						|
	}
 | 
						|
 | 
						|
	if (hi->pdata.adc_metrico[0] && hi->pdata.adc_metrico[1] &&
 | 
						|
	    adc >= hi->pdata.adc_metrico[0] && adc <= hi->pdata.adc_metrico[1])
 | 
						|
		ret = HEADSET_METRICO; /* For Metrico lab test */
 | 
						|
	else if (adc >= HS_DEF_MIC_ADC_10_BIT)
 | 
						|
		ret = HEADSET_MIC;
 | 
						|
	else
 | 
						|
		ret = HEADSET_NO_MIC;
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static void detect_microp_work_func(struct work_struct *work)
 | 
						|
{
 | 
						|
	int insert = 0;
 | 
						|
	int gpio_status = 0;
 | 
						|
	uint8_t data[3];
 | 
						|
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	microp_read_gpio_status(data);
 | 
						|
	gpio_status = data[0] << 16 | data[1] << 8 | data[2];
 | 
						|
	insert = (gpio_status & hi->hpin_gpio_mask) ? 0 : 1;
 | 
						|
	htc_35mm_remote_notify_insert_ext_headset(insert);
 | 
						|
 | 
						|
	hi->hpin_debounce = (insert) ? HS_JIFFIES_REMOVE : HS_JIFFIES_INSERT;
 | 
						|
}
 | 
						|
 | 
						|
static void button_microp_work_func(struct work_struct *work)
 | 
						|
{
 | 
						|
	int ret = 0;
 | 
						|
	int keycode = -1;
 | 
						|
	uint8_t data[2];
 | 
						|
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	memset(data, 0x00, sizeof(data));
 | 
						|
	ret = microp_i2c_read(MICROP_I2C_RCMD_REMOTE_KEYCODE, data, 2);
 | 
						|
	if (ret != 0) {
 | 
						|
		SYS_MSG("Failed to read Micro-P remote key code");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!data[1])
 | 
						|
		keycode = -1; /* no key code */
 | 
						|
	else if (data[1] & 0x80)
 | 
						|
		keycode = 0; /* release key code */
 | 
						|
	else
 | 
						|
		keycode = (int) data[1];
 | 
						|
 | 
						|
	SYS_MSG("Key code %d", keycode);
 | 
						|
 | 
						|
	htc_35mm_remote_notify_button_status(keycode);
 | 
						|
}
 | 
						|
 | 
						|
static irqreturn_t htc_headset_microp_detect_irq(int irq, void *data)
 | 
						|
{
 | 
						|
	hs_notify_hpin_irq();
 | 
						|
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	queue_delayed_work(detect_wq, &detect_microp_work, hi->hpin_debounce);
 | 
						|
 | 
						|
	return IRQ_HANDLED;
 | 
						|
}
 | 
						|
 | 
						|
static irqreturn_t htc_headset_microp_button_irq(int irq, void *data)
 | 
						|
{
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	queue_work(button_wq, &button_microp_work);
 | 
						|
 | 
						|
	return IRQ_HANDLED;
 | 
						|
}
 | 
						|
 | 
						|
static void hs_microp_register(void)
 | 
						|
{
 | 
						|
	struct headset_notifier notifier;
 | 
						|
 | 
						|
	if (hi->pdata.adc_channel) {
 | 
						|
		notifier.id = HEADSET_REG_REMOTE_ADC;
 | 
						|
		notifier.func = headset_microp_remote_adc;
 | 
						|
		headset_notifier_register(¬ifier);
 | 
						|
 | 
						|
		notifier.id = HEADSET_REG_MIC_STATUS;
 | 
						|
		notifier.func = headset_microp_mic_status;
 | 
						|
		headset_notifier_register(¬ifier);
 | 
						|
	}
 | 
						|
 | 
						|
	if (hi->pdata.remote_int) {
 | 
						|
		notifier.id = HEADSET_REG_KEY_INT_ENABLE;
 | 
						|
		notifier.func = headset_microp_enable_button_int;
 | 
						|
		headset_notifier_register(¬ifier);
 | 
						|
	}
 | 
						|
 | 
						|
	if (hi->pdata.remote_enable_pin) {
 | 
						|
		notifier.id = HEADSET_REG_KEY_ENABLE;
 | 
						|
		notifier.func = hs_microp_key_enable;
 | 
						|
		headset_notifier_register(¬ifier);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static int htc_headset_microp_probe(struct platform_device *pdev)
 | 
						|
{
 | 
						|
	int i = 0;
 | 
						|
	int ret = 0;
 | 
						|
	uint8_t data[12];
 | 
						|
 | 
						|
	struct htc_headset_microp_platform_data *pdata = NULL;
 | 
						|
 | 
						|
	SYS_MSG("++++++++++++++++++++");
 | 
						|
 | 
						|
	pdata = pdev->dev.platform_data;
 | 
						|
 | 
						|
	hi = kzalloc(sizeof(struct htc_headset_microp_info), GFP_KERNEL);
 | 
						|
	if (!hi) {
 | 
						|
		SYS_MSG("Failed to allocate memory for headset info");
 | 
						|
		return -ENOMEM;
 | 
						|
	}
 | 
						|
 | 
						|
	hi->pdata.hpin_int = pdata->hpin_int;
 | 
						|
	hi->pdata.hpin_irq = pdata->hpin_irq;
 | 
						|
	if (pdata->hpin_mask[0] || pdata->hpin_mask[1] || pdata->hpin_mask[2])
 | 
						|
		memcpy(hi->pdata.hpin_mask, pdata->hpin_mask,
 | 
						|
		       sizeof(hi->pdata.hpin_mask));
 | 
						|
 | 
						|
	hi->pdata.remote_int = pdata->remote_int;
 | 
						|
	hi->pdata.remote_irq = pdata->remote_irq;
 | 
						|
	hi->pdata.remote_enable_pin = pdata->remote_enable_pin;
 | 
						|
	hi->pdata.adc_channel = pdata->adc_channel;
 | 
						|
 | 
						|
	if (pdata->adc_remote[5])
 | 
						|
		memcpy(hi->pdata.adc_remote, pdata->adc_remote,
 | 
						|
		       sizeof(hi->pdata.adc_remote));
 | 
						|
 | 
						|
	if (pdata->adc_metrico[0] && pdata->adc_metrico[1])
 | 
						|
		memcpy(hi->pdata.adc_metrico, pdata->adc_metrico,
 | 
						|
		       sizeof(hi->pdata.adc_metrico));
 | 
						|
 | 
						|
	hi->hpin_debounce = HS_JIFFIES_INSERT;
 | 
						|
 | 
						|
	if (hi->pdata.hpin_int) {
 | 
						|
		hi->hpin_gpio_mask = pdata->hpin_mask[0] << 16 |
 | 
						|
				     pdata->hpin_mask[1] << 8 |
 | 
						|
				     pdata->hpin_mask[2];
 | 
						|
	}
 | 
						|
 | 
						|
	detect_wq = create_workqueue("detect");
 | 
						|
	if (detect_wq == NULL) {
 | 
						|
		ret = -ENOMEM;
 | 
						|
		SYS_MSG("Failed to create detect workqueue");
 | 
						|
		goto err_create_detect_work_queue;
 | 
						|
	}
 | 
						|
 | 
						|
	button_wq = create_workqueue("button");
 | 
						|
	if (button_wq == NULL) {
 | 
						|
		ret = -ENOMEM;
 | 
						|
		SYS_MSG("Failed to create button workqueue");
 | 
						|
		goto err_create_button_work_queue;
 | 
						|
	}
 | 
						|
 | 
						|
	if (hi->pdata.hpin_int) {
 | 
						|
		ret = headset_microp_enable_interrupt(hi->pdata.hpin_int, 1);
 | 
						|
		if (ret != 0) {
 | 
						|
			SYS_MSG("Failed to enable Micro-P HPIN interrupt");
 | 
						|
			goto err_enable_microp_hpin_interrupt;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (hi->pdata.hpin_irq) {
 | 
						|
		ret = request_irq(hi->pdata.hpin_irq,
 | 
						|
				  htc_headset_microp_detect_irq,
 | 
						|
				  IRQF_TRIGGER_NONE,
 | 
						|
				  "HTC_HEADSET_MICROP_BUTTON", NULL);
 | 
						|
		if (ret < 0) {
 | 
						|
			ret = -EINVAL;
 | 
						|
			SYS_MSG("Failed to request Micro-P IRQ (ERROR %d)",
 | 
						|
				ret);
 | 
						|
			goto err_request_microp_detect_irq;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (hi->pdata.adc_remote[5]) {
 | 
						|
		memset(data, 0x00, sizeof(data));
 | 
						|
		for (i = 0; i < 6; i++)
 | 
						|
			data[i + 6] = (uint8_t) hi->pdata.adc_remote[i];
 | 
						|
		ret = microp_i2c_write(MICROP_I2C_WCMD_REMOTEKEY_TABLE,
 | 
						|
				       data, 12);
 | 
						|
 | 
						|
		if (ret != 0) {
 | 
						|
			ret = -EIO;
 | 
						|
			SYS_MSG("Failed to write Micro-P ADC table");
 | 
						|
			goto err_write_microp_adc_table;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (hi->pdata.remote_int) {
 | 
						|
		ret = headset_microp_enable_interrupt(hi->pdata.remote_int, 1);
 | 
						|
		if (ret != 0) {
 | 
						|
			SYS_MSG("Failed to enable Micro-P remote interrupt");
 | 
						|
			goto err_enable_microp_remote_interrupt;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (hi->pdata.remote_irq) {
 | 
						|
		ret = request_irq(hi->pdata.remote_irq,
 | 
						|
				  htc_headset_microp_button_irq,
 | 
						|
				  IRQF_TRIGGER_NONE,
 | 
						|
				  "HTC_HEADSET_MICROP_BUTTON", NULL);
 | 
						|
		if (ret < 0) {
 | 
						|
			ret = -EINVAL;
 | 
						|
			SYS_MSG("Failed to request Micro-P IRQ (ERROR %d)",
 | 
						|
				ret);
 | 
						|
			goto err_request_microp_button_irq;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	hs_microp_register();
 | 
						|
 | 
						|
	SYS_MSG("--------------------");
 | 
						|
 | 
						|
	return 0;
 | 
						|
 | 
						|
err_request_microp_button_irq:
 | 
						|
	if (hi->pdata.remote_int)
 | 
						|
		headset_microp_enable_interrupt(hi->pdata.remote_int, 0);
 | 
						|
 | 
						|
err_enable_microp_remote_interrupt:
 | 
						|
err_write_microp_adc_table:
 | 
						|
	if (hi->pdata.hpin_irq)
 | 
						|
		free_irq(hi->pdata.hpin_irq, 0);
 | 
						|
 | 
						|
err_request_microp_detect_irq:
 | 
						|
	if (hi->pdata.hpin_int)
 | 
						|
		headset_microp_enable_interrupt(hi->pdata.hpin_int, 0);
 | 
						|
 | 
						|
err_enable_microp_hpin_interrupt:
 | 
						|
	destroy_workqueue(button_wq);
 | 
						|
 | 
						|
err_create_button_work_queue:
 | 
						|
	destroy_workqueue(detect_wq);
 | 
						|
 | 
						|
err_create_detect_work_queue:
 | 
						|
	kfree(hi);
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static int htc_headset_microp_remove(struct platform_device *pdev)
 | 
						|
{
 | 
						|
	DBG_MSG();
 | 
						|
 | 
						|
	if (hi->pdata.remote_irq)
 | 
						|
		free_irq(hi->pdata.remote_irq, 0);
 | 
						|
 | 
						|
	if (hi->pdata.remote_int)
 | 
						|
		headset_microp_enable_interrupt(hi->pdata.remote_int, 0);
 | 
						|
 | 
						|
	if (hi->pdata.hpin_irq)
 | 
						|
		free_irq(hi->pdata.hpin_irq, 0);
 | 
						|
 | 
						|
	if (hi->pdata.hpin_int)
 | 
						|
		headset_microp_enable_interrupt(hi->pdata.hpin_int, 0);
 | 
						|
 | 
						|
	destroy_workqueue(button_wq);
 | 
						|
	destroy_workqueue(detect_wq);
 | 
						|
 | 
						|
	kfree(hi);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static struct platform_driver htc_headset_microp_driver = {
 | 
						|
	.probe	= htc_headset_microp_probe,
 | 
						|
	.remove	= htc_headset_microp_remove,
 | 
						|
	.driver	= {
 | 
						|
		.name	= "HTC_HEADSET_MICROP",
 | 
						|
		.owner	= THIS_MODULE,
 | 
						|
	},
 | 
						|
};
 | 
						|
 | 
						|
static int __init htc_headset_microp_init(void)
 | 
						|
{
 | 
						|
	DBG_MSG();
 | 
						|
	return platform_driver_register(&htc_headset_microp_driver);
 | 
						|
}
 | 
						|
 | 
						|
static void __exit htc_headset_microp_exit(void)
 | 
						|
{
 | 
						|
	DBG_MSG();
 | 
						|
	platform_driver_unregister(&htc_headset_microp_driver);
 | 
						|
}
 | 
						|
 | 
						|
module_init(htc_headset_microp_init);
 | 
						|
module_exit(htc_headset_microp_exit);
 | 
						|
 | 
						|
MODULE_DESCRIPTION("HTC Micro-P headset detection driver");
 | 
						|
MODULE_LICENSE("GPL");
 |