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