926 lines
22 KiB
C
926 lines
22 KiB
C
/*
|
|
*
|
|
* /arch/arm/mach-msm/htc_headset_mgr.c
|
|
*
|
|
* HTC headset manager 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/kernel.h>
|
|
#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/platform_device.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/err.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/gpio.h>
|
|
|
|
#include <asm/atomic.h>
|
|
#include <asm/mach-types.h>
|
|
|
|
#include <mach/board.h>
|
|
#include <mach/vreg.h>
|
|
#include <mach/atmega_microp.h>
|
|
|
|
#include <mach/htc_headset_mgr.h>
|
|
|
|
#define DRIVER_NAME "HS_MGR"
|
|
|
|
/* #define CONFIG_DEBUG_H2W */
|
|
|
|
/*Delay 200ms when 11pin device plug in*/
|
|
#define H2W_DETECT_DELAY msecs_to_jiffies(200)
|
|
#define BUTTON_H2W_DELAY msecs_to_jiffies(10)
|
|
|
|
#define H2WI(fmt, arg...) \
|
|
printk(KERN_INFO "[H2W] %s " fmt "\r\n", __func__, ## arg)
|
|
#define H2WE(fmt, arg...) \
|
|
printk(KERN_ERR "[H2W] %s " fmt "\r\n", __func__, ## arg)
|
|
|
|
#ifdef CONFIG_DEBUG_H2W
|
|
#define H2W_DBG(fmt, arg...) \
|
|
printk(KERN_INFO "[H2W] %s " fmt "\r\n", __func__, ## arg)
|
|
#else
|
|
#define H2W_DBG(fmt, arg...) do {} while (0)
|
|
#endif
|
|
|
|
static struct workqueue_struct *detect_wq;
|
|
|
|
static void insert_35mm_do_work(struct work_struct *work);
|
|
static DECLARE_WORK(insert_35mm_work, insert_35mm_do_work);
|
|
static void remove_35mm_do_work(struct work_struct *work);
|
|
static DECLARE_WORK(remove_35mm_work, remove_35mm_do_work);
|
|
|
|
static struct workqueue_struct *button_wq;
|
|
|
|
static void button_35mm_do_work(struct work_struct *w);
|
|
static DECLARE_DELAYED_WORK(button_35mm_work, button_35mm_do_work);
|
|
|
|
static int hs_mgr_rpc_call(struct msm_rpc_server *server,
|
|
struct rpc_request_hdr *req, unsigned len);
|
|
|
|
static struct msm_rpc_server hs_rpc_server = {
|
|
.prog = HS_RPC_SERVER_PROG,
|
|
.vers = HS_RPC_SERVER_VERS,
|
|
.rpc_call = hs_mgr_rpc_call,
|
|
};
|
|
|
|
struct button_work {
|
|
struct delayed_work key_work;
|
|
int key_code;
|
|
};
|
|
|
|
static struct h2w_info *hi;
|
|
static struct hs_notifier_func hs_mgr_notifier;
|
|
|
|
void hs_notify_hpin_irq(void)
|
|
{
|
|
hi->hpin_jiffies = jiffies;
|
|
SYS_MSG("HPIN IRQ");
|
|
}
|
|
|
|
int hs_hpin_stable(void)
|
|
{
|
|
unsigned long last_hpin_jiffies = 0;
|
|
unsigned long unstable_jiffies = 1.2 * HZ;
|
|
|
|
last_hpin_jiffies = hi->hpin_jiffies;
|
|
|
|
if (time_before_eq(jiffies, last_hpin_jiffies + unstable_jiffies))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int headset_notifier_register(struct headset_notifier *notifier)
|
|
{
|
|
if (!notifier->func) {
|
|
SYS_MSG("NULL register function");
|
|
return 0;
|
|
}
|
|
|
|
switch (notifier->id) {
|
|
case HEADSET_REG_REMOTE_ADC:
|
|
SYS_MSG("Register REMOTE_ADC notifier");
|
|
hs_mgr_notifier.remote_adc = notifier->func;
|
|
break;
|
|
case HEADSET_REG_RPC_KEY:
|
|
SYS_MSG("Register RPC_KEY notifier");
|
|
hs_mgr_notifier.rpc_key = notifier->func;
|
|
break;
|
|
case HEADSET_REG_MIC_STATUS:
|
|
SYS_MSG("Register MIC_STATUS notifier");
|
|
hs_mgr_notifier.mic_status = notifier->func;
|
|
break;
|
|
case HEADSET_REG_MIC_BIAS:
|
|
SYS_MSG("Register MIC_BIAS notifier");
|
|
hs_mgr_notifier.mic_bias_enable = notifier->func;
|
|
break;
|
|
case HEADSET_REG_MIC_SELECT:
|
|
SYS_MSG("Register MIC_SELECT notifier");
|
|
hs_mgr_notifier.mic_select = notifier->func;
|
|
break;
|
|
case HEADSET_REG_KEY_INT_ENABLE:
|
|
SYS_MSG("Register KEY_INT_ENABLE notifier");
|
|
hs_mgr_notifier.key_int_enable = notifier->func;
|
|
break;
|
|
case HEADSET_REG_KEY_ENABLE:
|
|
SYS_MSG("Register KEY_ENABLE notifier");
|
|
hs_mgr_notifier.key_enable = notifier->func;
|
|
break;
|
|
default:
|
|
SYS_MSG("Unknown register ID");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int hs_mgr_rpc_call(struct msm_rpc_server *server,
|
|
struct rpc_request_hdr *req, unsigned len)
|
|
{
|
|
struct hs_rpc_server_args_key *args_key;
|
|
|
|
DBG_MSG("");
|
|
|
|
switch (req->procedure) {
|
|
case HS_RPC_SERVER_PROC_NULL:
|
|
SYS_MSG("RPC_SERVER_NULL");
|
|
break;
|
|
case HS_RPC_SERVER_PROC_KEY:
|
|
args_key = (struct hs_rpc_server_args_key *)(req + 1);
|
|
args_key->adc = be32_to_cpu(args_key->adc);
|
|
SYS_MSG("RPC_SERVER_KEY ADC = %u (0x%X)",
|
|
args_key->adc, args_key->adc);
|
|
if (hs_mgr_notifier.rpc_key)
|
|
hs_mgr_notifier.rpc_key(args_key->adc);
|
|
else
|
|
SYS_MSG("RPC_KEY notify function doesn't exist");
|
|
break;
|
|
default:
|
|
SYS_MSG("Unknown RPC procedure");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t h2w_print_name(struct switch_dev *sdev, char *buf)
|
|
{
|
|
return sprintf(buf, "Headset\n");
|
|
}
|
|
|
|
void button_pressed(int type)
|
|
{
|
|
printk(KERN_INFO "[H2W] button_pressed %d\n", type);
|
|
atomic_set(&hi->btn_state, type);
|
|
input_report_key(hi->input, type, 1);
|
|
input_sync(hi->input);
|
|
}
|
|
|
|
void button_released(int type)
|
|
{
|
|
printk(KERN_INFO "[H2W] button_released %d\n", type);
|
|
atomic_set(&hi->btn_state, 0);
|
|
input_report_key(hi->input, type, 0);
|
|
input_sync(hi->input);
|
|
}
|
|
|
|
void headset_button_event(int is_press, int type)
|
|
{
|
|
if (!hs_hpin_stable()) {
|
|
H2WI("The HPIN is unstable, SKIP THE BUTTON EVENT.");
|
|
return;
|
|
}
|
|
|
|
if (!is_press) {
|
|
if (hi->ignore_btn)
|
|
hi->ignore_btn = 0;
|
|
else
|
|
button_released(type);
|
|
} else {
|
|
if (!hi->ignore_btn && !atomic_read(&hi->btn_state))
|
|
button_pressed(type);
|
|
}
|
|
}
|
|
|
|
static void set_35mm_hw_state(int state)
|
|
{
|
|
if (hi->mic_bias_state != state && hs_mgr_notifier.mic_bias_enable) {
|
|
hs_mgr_notifier.mic_bias_enable(state);
|
|
hi->mic_bias_state = state;
|
|
if (state) /* Wait for MIC bias stable */
|
|
msleep(HS_DELAY_MIC_BIAS);
|
|
}
|
|
|
|
if (hs_mgr_notifier.mic_select)
|
|
hs_mgr_notifier.mic_select(state);
|
|
|
|
if (hs_mgr_notifier.key_enable)
|
|
hs_mgr_notifier.key_enable(state);
|
|
|
|
if (hs_mgr_notifier.key_int_enable)
|
|
hs_mgr_notifier.key_int_enable(state);
|
|
}
|
|
|
|
static void insert_11pin_35mm(int *state)
|
|
{
|
|
int mic = HEADSET_NO_MIC;
|
|
|
|
SYS_MSG("Insert USB 3.5mm headset");
|
|
set_35mm_hw_state(1);
|
|
|
|
if (hs_mgr_notifier.mic_status)
|
|
mic = hs_mgr_notifier.mic_status();
|
|
|
|
if (mic == HEADSET_NO_MIC) {
|
|
/* without microphone */
|
|
*state |= BIT_HEADSET_NO_MIC;
|
|
hi->h2w_35mm_status = HTC_35MM_NO_MIC;
|
|
printk(KERN_INFO "11pin_3.5mm without microphone\n");
|
|
} else { /* with microphone */
|
|
*state |= BIT_HEADSET;
|
|
hi->h2w_35mm_status = HTC_35MM_MIC;
|
|
printk(KERN_INFO "11pin_3.5mm with microphone\n");
|
|
}
|
|
}
|
|
|
|
static void remove_11pin_35mm(void)
|
|
{
|
|
SYS_MSG("Remove USB 3.5mm headset");
|
|
|
|
set_35mm_hw_state(0);
|
|
|
|
if (atomic_read(&hi->btn_state))
|
|
button_released(atomic_read(&hi->btn_state));
|
|
hi->h2w_35mm_status = HTC_35MM_UNPLUG;
|
|
}
|
|
|
|
static void button_35mm_do_work(struct work_struct *w)
|
|
{
|
|
int key;
|
|
struct button_work *work;
|
|
|
|
work = container_of(w, struct button_work, key_work.work);
|
|
hi->key_level_flag = work->key_code;
|
|
|
|
if (!hi->is_ext_insert && !hi->h2w_35mm_status) {
|
|
kfree(work);
|
|
H2WI("3.5mm headset is plugged out, skip report key event");
|
|
return;
|
|
}
|
|
|
|
if (hi->key_level_flag) {
|
|
switch (hi->key_level_flag) {
|
|
case 1:
|
|
H2WI("3.5mm RC: Play Pressed");
|
|
key = HS_MGR_KEYCODE_MEDIA;
|
|
break;
|
|
case 2:
|
|
H2WI("3.5mm RC: BACKWARD Pressed");
|
|
key = HS_MGR_KEYCODE_BACKWARD;
|
|
break;
|
|
case 3:
|
|
H2WI("3.5mm RC: FORWARD Pressed");
|
|
key = HS_MGR_KEYCODE_FORWARD;
|
|
break;
|
|
default:
|
|
H2WI("3.5mm RC: WRONG Button Pressed");
|
|
return;
|
|
}
|
|
headset_button_event(1, key);
|
|
} else { /* key release */
|
|
if (atomic_read(&hi->btn_state))
|
|
headset_button_event(0, atomic_read(&hi->btn_state));
|
|
else
|
|
H2WI("3.5mm RC: WRONG Button Release");
|
|
}
|
|
|
|
wake_lock_timeout(&hi->headset_wake_lock, 1.5 * HZ);
|
|
|
|
kfree(work);
|
|
}
|
|
|
|
static void enable_metrico_headset(int enable)
|
|
{
|
|
if (enable && !hi->metrico_status) {
|
|
#if 0
|
|
enable_mos_test(1);
|
|
#endif
|
|
hi->metrico_status = 1;
|
|
printk(KERN_INFO "Enable metrico headset\n");
|
|
}
|
|
|
|
if (!enable && hi->metrico_status) {
|
|
#if 0
|
|
enable_mos_test(0);
|
|
#endif
|
|
hi->metrico_status = 0;
|
|
printk(KERN_INFO "Disable metrico headset\n");
|
|
}
|
|
}
|
|
|
|
static void remove_35mm_do_work(struct work_struct *work)
|
|
{
|
|
int state;
|
|
|
|
wake_lock_timeout(&hi->headset_wake_lock, 2.5 * HZ);
|
|
|
|
H2W_DBG("");
|
|
/*To solve the insert, remove, insert headset problem*/
|
|
if (time_before_eq(jiffies, hi->insert_jiffies))
|
|
msleep(800);
|
|
if (hi->is_ext_insert) {
|
|
H2WI("Skip 3.5mm headset plug out!!!");
|
|
return;
|
|
}
|
|
|
|
SYS_MSG("Remove 3.5mm headset");
|
|
set_35mm_hw_state(0);
|
|
|
|
/* For HW Metrico lab test */
|
|
if (hi->metrico_status)
|
|
enable_metrico_headset(0);
|
|
|
|
if (atomic_read(&hi->btn_state))
|
|
button_released(atomic_read(&hi->btn_state));
|
|
hi->ext_35mm_status = HTC_35MM_UNPLUG;
|
|
|
|
mutex_lock(&hi->mutex_lock);
|
|
state = switch_get_state(&hi->sdev);
|
|
|
|
if (hi->usb_dev_type == USB_HEADSET) {
|
|
hi->usb_dev_status = STATUS_CONNECTED_ENABLED;
|
|
state &= ~(BIT_35MM_HEADSET | BIT_HEADSET);
|
|
state |= BIT_HEADSET_NO_MIC;
|
|
switch_set_state(&hi->sdev, state);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
} else if (hi->usb_dev_type == H2W_TVOUT) {
|
|
state &= ~(BIT_HEADSET | BIT_35MM_HEADSET);
|
|
state |= BIT_HEADSET_NO_MIC;
|
|
switch_set_state(&hi->sdev, state);
|
|
#if 0
|
|
} else if (hi->cable_in1 && !gpio_get_value(hi->cable_in1)) {
|
|
state &= ~BIT_35MM_HEADSET;
|
|
switch_set_state(&hi->sdev, state);
|
|
queue_delayed_work(detect_wq, &detect_h2w_work,
|
|
HS_DELAY_ZERO_JIFFIES);
|
|
#endif
|
|
} else {
|
|
state &= ~(BIT_HEADSET | BIT_HEADSET_NO_MIC |
|
|
BIT_35MM_HEADSET);
|
|
switch_set_state(&hi->sdev, state);
|
|
}
|
|
|
|
mutex_unlock(&hi->mutex_lock);
|
|
}
|
|
|
|
static void insert_35mm_do_work(struct work_struct *work)
|
|
{
|
|
int state;
|
|
int i, mic1, mic2;
|
|
|
|
H2W_DBG("");
|
|
hi->insert_jiffies = jiffies + HZ;
|
|
|
|
wake_lock_timeout(&hi->headset_wake_lock, 1.5 * HZ);
|
|
|
|
#if 0
|
|
if (hi->usb_dev_type && hi->is_ext_insert &&
|
|
hi->usb_dev_type != H2W_TVOUT && hi->usb_dev_type != USB_HEADSET)
|
|
remove_headset();
|
|
else if (hi->usb_dev_type == USB_HEADSET)
|
|
hi->usb_dev_status = STATUS_CONNECTED_DISABLED;
|
|
#endif
|
|
|
|
if (hi->usb_dev_type == USB_HEADSET)
|
|
hi->usb_dev_status = STATUS_CONNECTED_DISABLED;
|
|
|
|
if (hi->is_ext_insert) {
|
|
SYS_MSG("Insert 3.5mm headset");
|
|
set_35mm_hw_state(1);
|
|
hi->ignore_btn = 0;
|
|
|
|
mic1 = mic2 = HEADSET_NO_MIC;
|
|
if (hs_mgr_notifier.mic_status) {
|
|
if (hi->ext_35mm_status == HTC_35MM_NO_MIC ||
|
|
hi->h2w_35mm_status == HTC_35MM_NO_MIC)
|
|
for (i = 0; i < 10; i++) {
|
|
mic1 = hs_mgr_notifier.mic_status();
|
|
msleep(HS_DELAY_MIC_DETECT);
|
|
mic2 = hs_mgr_notifier.mic_status();
|
|
if (mic1 == mic2)
|
|
break;
|
|
}
|
|
else
|
|
mic1 = mic2 = hs_mgr_notifier.mic_status();
|
|
}
|
|
|
|
/* For HW Metrico lab test */
|
|
if (mic2 == HEADSET_METRICO && !hi->metrico_status)
|
|
enable_metrico_headset(1);
|
|
|
|
mutex_lock(&hi->mutex_lock);
|
|
state = switch_get_state(&hi->sdev);
|
|
state &= ~(BIT_HEADSET | BIT_HEADSET_NO_MIC);
|
|
if (mic2 == HEADSET_NO_MIC || mic1 != mic2) {
|
|
state |= BIT_HEADSET_NO_MIC;
|
|
printk(KERN_INFO "3.5mm_headset without microphone\n");
|
|
} else {
|
|
state |= BIT_HEADSET;
|
|
printk(KERN_INFO "3.5mm_headset with microphone\n");
|
|
}
|
|
|
|
state |= BIT_35MM_HEADSET;
|
|
switch_set_state(&hi->sdev, state);
|
|
if (state & BIT_HEADSET_NO_MIC)
|
|
hi->ext_35mm_status = HTC_35MM_NO_MIC;
|
|
else
|
|
hi->ext_35mm_status = HTC_35MM_MIC;
|
|
mutex_unlock(&hi->mutex_lock);
|
|
}
|
|
}
|
|
|
|
int htc_35mm_remote_notify_insert_ext_headset(int insert)
|
|
{
|
|
if (hi) {
|
|
mutex_lock(&hi->mutex_lock);
|
|
hi->is_ext_insert = insert;
|
|
mutex_unlock(&hi->mutex_lock);
|
|
|
|
H2WI(" %d", hi->is_ext_insert);
|
|
if (!hi->is_ext_insert)
|
|
queue_work(detect_wq, &remove_35mm_work);
|
|
else
|
|
queue_work(detect_wq, &insert_35mm_work);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int htc_35mm_remote_notify_microp_ready(void)
|
|
{
|
|
if (hi) {
|
|
if (hi->is_ext_insert)
|
|
queue_work(detect_wq, &insert_35mm_work);
|
|
#if 0
|
|
if (hi->h2w_35mm_status)
|
|
insert_headset(NORMAL_HEARPHONE);
|
|
#endif
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int htc_35mm_remote_notify_button_status(int key_level)
|
|
{
|
|
struct button_work *work;
|
|
|
|
if (hi->ext_35mm_status == HTC_35MM_NO_MIC ||
|
|
hi->h2w_35mm_status == HTC_35MM_NO_MIC) {
|
|
SYS_MSG("MIC re-detection");
|
|
msleep(HS_DELAY_MIC_DETECT);
|
|
queue_work(detect_wq, &insert_35mm_work);
|
|
} else if (!hs_hpin_stable()) {
|
|
H2WI("The HPIN is unstable, SKIP THE BUTTON EVENT.");
|
|
return 1;
|
|
} else {
|
|
work = kzalloc(sizeof(struct button_work), GFP_KERNEL);
|
|
if (!work) {
|
|
printk(KERN_INFO "Failed to allocate button memory\n");
|
|
return 1;
|
|
}
|
|
work->key_code = key_level;
|
|
INIT_DELAYED_WORK(&work->key_work, button_35mm_do_work);
|
|
queue_delayed_work(button_wq, &work->key_work,
|
|
HS_JIFFIES_BUTTON);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void usb_headset_detect(int type)
|
|
{
|
|
int state;
|
|
|
|
mutex_lock(&hi->mutex_lock);
|
|
state = switch_get_state(&hi->sdev);
|
|
|
|
switch (type) {
|
|
case NO_DEVICE:
|
|
if (hi->usb_dev_type == USB_HEADSET) {
|
|
printk(KERN_INFO "Remove USB headset\n");
|
|
hi->usb_dev_type = NO_DEVICE;
|
|
hi->usb_dev_status = STATUS_DISCONNECTED;
|
|
state &= ~BIT_USB_HEADSET;
|
|
if (!hi->is_ext_insert)
|
|
state &= ~BIT_HEADSET_NO_MIC;
|
|
}
|
|
break;
|
|
case USB_HEADSET:
|
|
printk(KERN_INFO "Insert USB headset\n");
|
|
hi->usb_dev_type = USB_HEADSET;
|
|
if (hi->is_ext_insert) {
|
|
printk(KERN_INFO "Disable USB headset\n");
|
|
hi->usb_dev_status = STATUS_CONNECTED_DISABLED;
|
|
state |= BIT_USB_HEADSET;
|
|
} else {
|
|
printk(KERN_INFO "Enable USB headset\n");
|
|
hi->usb_dev_status = STATUS_CONNECTED_ENABLED;
|
|
state |= (BIT_USB_HEADSET | BIT_HEADSET_NO_MIC);
|
|
}
|
|
break;
|
|
default:
|
|
printk(KERN_INFO "Unknown headset type\n");
|
|
}
|
|
|
|
switch_set_state(&hi->sdev, state);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
}
|
|
|
|
void headset_ext_detect(int type)
|
|
{
|
|
switch (type) {
|
|
case NO_DEVICE:
|
|
if (hi->usb_dev_type == USB_HEADSET)
|
|
usb_headset_detect(type);
|
|
break;
|
|
case USB_HEADSET:
|
|
usb_headset_detect(type);
|
|
break;
|
|
default:
|
|
printk(KERN_INFO "Unknown headset type\n");
|
|
}
|
|
}
|
|
|
|
int switch_send_event(unsigned int bit, int on)
|
|
{
|
|
unsigned long state;
|
|
|
|
mutex_lock(&hi->mutex_lock);
|
|
state = switch_get_state(&hi->sdev);
|
|
state &= ~(bit);
|
|
|
|
if (on)
|
|
state |= bit;
|
|
|
|
switch_set_state(&hi->sdev, state);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t tty_flag_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
char *s = buf;
|
|
mutex_lock(&hi->mutex_lock);
|
|
s += sprintf(s, "%d\n", hi->tty_enable_flag);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
return (s - buf);
|
|
}
|
|
|
|
static ssize_t tty_flag_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int state;
|
|
|
|
mutex_lock(&hi->mutex_lock);
|
|
state = switch_get_state(&hi->sdev);
|
|
state &= ~(BIT_TTY_FULL | BIT_TTY_VCO | BIT_TTY_HCO);
|
|
|
|
if (count == (strlen("enable") + 1) &&
|
|
strncmp(buf, "enable", strlen("enable")) == 0) {
|
|
hi->tty_enable_flag = 1;
|
|
switch_set_state(&hi->sdev, state | BIT_TTY_FULL);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
printk(KERN_INFO "Enable TTY FULL\n");
|
|
return count;
|
|
}
|
|
if (count == (strlen("vco_enable") + 1) &&
|
|
strncmp(buf, "vco_enable", strlen("vco_enable")) == 0) {
|
|
hi->tty_enable_flag = 2;
|
|
switch_set_state(&hi->sdev, state | BIT_TTY_VCO);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
printk(KERN_INFO "Enable TTY VCO\n");
|
|
return count;
|
|
}
|
|
if (count == (strlen("hco_enable") + 1) &&
|
|
strncmp(buf, "hco_enable", strlen("hco_enable")) == 0) {
|
|
hi->tty_enable_flag = 3;
|
|
switch_set_state(&hi->sdev, state | BIT_TTY_HCO);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
printk(KERN_INFO "Enable TTY HCO\n");
|
|
return count;
|
|
}
|
|
if (count == (strlen("disable") + 1) &&
|
|
strncmp(buf, "disable", strlen("disable")) == 0) {
|
|
hi->tty_enable_flag = 0;
|
|
switch_set_state(&hi->sdev, state);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
printk(KERN_INFO "Disable TTY\n");
|
|
return count;
|
|
}
|
|
printk(KERN_ERR "tty_enable_flag_store: invalid argument\n");
|
|
return -EINVAL;
|
|
}
|
|
static DEVICE_ACCESSORY_ATTR(tty, 0666, tty_flag_show, tty_flag_store);
|
|
|
|
static ssize_t fm_flag_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
int state;
|
|
|
|
mutex_lock(&hi->mutex_lock);
|
|
state = switch_get_state(&hi->sdev);
|
|
state &= ~(BIT_FM_HEADSET | BIT_FM_SPEAKER);
|
|
|
|
if (count == (strlen("fm_headset") + 1) &&
|
|
strncmp(buf, "fm_headset", strlen("fm_headset")) == 0) {
|
|
hi->fm_flag = 1;
|
|
state |= BIT_FM_HEADSET;
|
|
printk(KERN_INFO "Enable FM HEADSET\n");
|
|
} else if (count == (strlen("fm_speaker") + 1) &&
|
|
strncmp(buf, "fm_speaker", strlen("fm_speaker")) == 0) {
|
|
hi->fm_flag = 2;
|
|
state |= BIT_FM_SPEAKER;
|
|
printk(KERN_INFO "Enable FM SPEAKER\n");
|
|
} else if (count == (strlen("disable") + 1) &&
|
|
strncmp(buf, "disable", strlen("disable")) == 0) {
|
|
hi->fm_flag = 0 ;
|
|
printk(KERN_INFO "Disable FM\n");
|
|
} else {
|
|
mutex_unlock(&hi->mutex_lock);
|
|
printk(KERN_ERR "fm_enable_flag_store: invalid argument\n");
|
|
return -EINVAL;
|
|
}
|
|
switch_set_state(&hi->sdev, state);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t fm_flag_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
char *s = buf;
|
|
char *show_str;
|
|
mutex_lock(&hi->mutex_lock);
|
|
if (hi->fm_flag == 0)
|
|
show_str = "disable";
|
|
if (hi->fm_flag == 1)
|
|
show_str = "fm_headset";
|
|
if (hi->fm_flag == 2)
|
|
show_str = "fm_speaker";
|
|
|
|
s += sprintf(s, "%s\n", show_str);
|
|
mutex_unlock(&hi->mutex_lock);
|
|
return (s - buf);
|
|
}
|
|
static DEVICE_ACCESSORY_ATTR(fm, 0666, fm_flag_show, fm_flag_store);
|
|
|
|
static int register_common_headset(struct h2w_info *h2w, int create_attr)
|
|
{
|
|
int ret = 0;
|
|
hi = h2w;
|
|
|
|
hi->htc_accessory_class = class_create(THIS_MODULE, "htc_accessory");
|
|
if (IS_ERR(hi->htc_accessory_class)) {
|
|
ret = PTR_ERR(hi->htc_accessory_class);
|
|
hi->htc_accessory_class = NULL;
|
|
goto err_create_class;
|
|
}
|
|
|
|
hi->tty_dev = device_create(hi->htc_accessory_class,
|
|
NULL, 0, "%s", "tty");
|
|
if (unlikely(IS_ERR(hi->tty_dev))) {
|
|
ret = PTR_ERR(hi->tty_dev);
|
|
hi->tty_dev = NULL;
|
|
goto err_create_tty_device;
|
|
}
|
|
|
|
/* register the attributes */
|
|
ret = device_create_file(hi->tty_dev, &dev_attr_tty);
|
|
if (ret)
|
|
goto err_create_tty_device_file;
|
|
|
|
hi->fm_dev = device_create(hi->htc_accessory_class,
|
|
NULL, 0, "%s", "fm");
|
|
if (unlikely(IS_ERR(hi->fm_dev))) {
|
|
ret = PTR_ERR(hi->fm_dev);
|
|
hi->fm_dev = NULL;
|
|
goto err_create_fm_device;
|
|
}
|
|
|
|
/* register the attributes */
|
|
ret = device_create_file(hi->fm_dev, &dev_attr_fm);
|
|
if (ret)
|
|
goto err_create_fm_device_file;
|
|
|
|
return 0;
|
|
|
|
err_create_fm_device_file:
|
|
device_unregister(hi->fm_dev);
|
|
err_create_fm_device:
|
|
device_remove_file(hi->tty_dev, &dev_attr_tty);
|
|
err_create_tty_device_file:
|
|
device_unregister(hi->tty_dev);
|
|
err_create_tty_device:
|
|
class_destroy(hi->htc_accessory_class);
|
|
err_create_class:
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void unregister_common_headset(struct h2w_info *h2w)
|
|
{
|
|
hi = h2w;
|
|
device_remove_file(hi->tty_dev, &dev_attr_tty);
|
|
device_unregister(hi->tty_dev);
|
|
device_remove_file(hi->fm_dev, &dev_attr_fm);
|
|
device_unregister(hi->fm_dev);
|
|
class_destroy(hi->htc_accessory_class);
|
|
}
|
|
|
|
static int htc_35mm_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
|
|
struct htc_headset_mgr_platform_data *pdata = pdev->dev.platform_data;
|
|
|
|
SYS_MSG("++++++++++++++++++++");
|
|
|
|
hi = kzalloc(sizeof(struct h2w_info), GFP_KERNEL);
|
|
if (!hi)
|
|
return -ENOMEM;
|
|
|
|
hi->driver_flag = pdata->driver_flag;
|
|
hi->hpin_jiffies = jiffies;
|
|
|
|
hi->ext_35mm_status = 0;
|
|
hi->h2w_35mm_status = 0;
|
|
hi->is_ext_insert = 0;
|
|
hi->mic_bias_state = 0;
|
|
hi->key_level_flag = -1;
|
|
|
|
atomic_set(&hi->btn_state, 0);
|
|
hi->ignore_btn = 0;
|
|
hi->usb_dev_type = NO_DEVICE;
|
|
hi->tty_enable_flag = 0;
|
|
hi->fm_flag = 0;
|
|
hi->mic_switch_flag = 1;
|
|
hi->rc_flag = 0;
|
|
|
|
hi->insert_11pin_35mm = insert_11pin_35mm;
|
|
hi->remove_11pin_35mm = remove_11pin_35mm;
|
|
|
|
mutex_init(&hi->mutex_lock);
|
|
mutex_init(&hi->mutex_rc_lock);
|
|
|
|
wake_lock_init(&hi->headset_wake_lock, WAKE_LOCK_SUSPEND, "headset");
|
|
|
|
hi->sdev.name = "h2w";
|
|
hi->sdev.print_name = h2w_print_name;
|
|
|
|
ret = switch_dev_register(&hi->sdev);
|
|
if (ret < 0)
|
|
goto err_switch_dev_register;
|
|
|
|
detect_wq = create_workqueue("detection");
|
|
if (detect_wq == NULL) {
|
|
ret = -ENOMEM;
|
|
goto err_create_detect_work_queue;
|
|
}
|
|
button_wq = create_workqueue("button");
|
|
if (button_wq == NULL) {
|
|
ret = -ENOMEM;
|
|
goto err_create_button_work_queue;
|
|
}
|
|
|
|
hi->input = input_allocate_device();
|
|
if (!hi->input) {
|
|
ret = -ENOMEM;
|
|
goto err_request_input_dev;
|
|
}
|
|
|
|
hi->input->name = "h2w headset";
|
|
set_bit(EV_SYN, hi->input->evbit);
|
|
set_bit(EV_KEY, hi->input->evbit);
|
|
set_bit(KEY_END, hi->input->keybit);
|
|
set_bit(KEY_MUTE, hi->input->keybit);
|
|
set_bit(KEY_VOLUMEDOWN, hi->input->keybit);
|
|
set_bit(KEY_VOLUMEUP, hi->input->keybit);
|
|
set_bit(KEY_NEXTSONG, hi->input->keybit);
|
|
set_bit(KEY_PLAYPAUSE, hi->input->keybit);
|
|
set_bit(KEY_PREVIOUSSONG, hi->input->keybit);
|
|
set_bit(KEY_MEDIA, hi->input->keybit);
|
|
set_bit(KEY_SEND, hi->input->keybit);
|
|
|
|
ret = input_register_device(hi->input);
|
|
if (ret < 0)
|
|
goto err_register_input_dev;
|
|
|
|
ret = register_common_headset(hi, 0);
|
|
if (ret)
|
|
goto err_register_common_headset;
|
|
|
|
if (hi->driver_flag & DRIVER_HS_MGR_RPC_SERVER) {
|
|
/* Create RPC server */
|
|
ret = msm_rpc_create_server(&hs_rpc_server);
|
|
if (ret < 0) {
|
|
SYS_MSG("Failed to create RPC server");
|
|
goto err_create_rpc_server;
|
|
}
|
|
SYS_MSG("Create RPC server successfully");
|
|
}
|
|
|
|
SYS_MSG("--------------------");
|
|
|
|
return 0;
|
|
|
|
err_create_rpc_server:
|
|
|
|
err_register_common_headset:
|
|
input_unregister_device(hi->input);
|
|
|
|
err_register_input_dev:
|
|
input_free_device(hi->input);
|
|
|
|
err_request_input_dev:
|
|
destroy_workqueue(button_wq);
|
|
|
|
err_create_button_work_queue:
|
|
destroy_workqueue(detect_wq);
|
|
|
|
err_create_detect_work_queue:
|
|
switch_dev_unregister(&hi->sdev);
|
|
|
|
err_switch_dev_register:
|
|
|
|
printk(KERN_ERR "H2W: Failed to register driver\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int htc_35mm_remove(struct platform_device *pdev)
|
|
{
|
|
H2W_DBG("");
|
|
|
|
#if 0
|
|
if ((switch_get_state(&hi->sdev) &
|
|
(BIT_HEADSET | BIT_HEADSET_NO_MIC)) != 0)
|
|
remove_headset();
|
|
#endif
|
|
|
|
unregister_common_headset(hi);
|
|
input_unregister_device(hi->input);
|
|
destroy_workqueue(detect_wq);
|
|
destroy_workqueue(button_wq);
|
|
switch_dev_unregister(&hi->sdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver htc_35mm_driver = {
|
|
.probe = htc_35mm_probe,
|
|
.remove = htc_35mm_remove,
|
|
.driver = {
|
|
.name = "HTC_HEADSET_MGR",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
|
|
static int __init htc_35mm_init(void)
|
|
{
|
|
H2W_DBG("");
|
|
return platform_driver_register(&htc_35mm_driver);
|
|
}
|
|
|
|
static void __exit htc_35mm_exit(void)
|
|
{
|
|
platform_driver_unregister(&htc_35mm_driver);
|
|
}
|
|
|
|
module_init(htc_35mm_init);
|
|
module_exit(htc_35mm_exit);
|
|
|
|
MODULE_DESCRIPTION("HTC headset manager driver");
|
|
MODULE_LICENSE("GPL");
|