accdet相關驅動程序:
accdet_drv.c accdet_drv.h
accdet.c accdet.h
accdet_custom.c accdet_custom.h
accdet_custom_def.c accdet_custom_def.h
驅動分析
- accdet_drv.c:繪製驅動大體框圖,描述框架結構
module_init(accdet_mod_init);
static int accdet_mod_init(void)
{
platform_device_register(&accdet_device);
platform_driver_register(&accdet_driver);
return 0;
}
struct platform_device accdet_device = {
.name ="Accdet_Driver",
.id = -1,
};
static struct platform_driver accdet_driver = {
.probe = accdet_probe,
.remove = accdet_remove,
.driver = {
.name = "Accdet_Driver",
#ifdef CONFIG_PM
.pm = &accdet_pm_ops,
#endif
},
};
static int accdet_probe(struct platform_device *dev)
{
mt_accdet_probe();
return 0;
}
- accdet.c:填充具體實現細節
mt_accdet_probe
int mt_accdet_probe(void)
{
/*獲取按鍵長按時間*/
struct headset_key_custom* press_key_time = get_headset_key_custom_setting();
/*------------------------------------
struct headset_key_custom{
int headset_long_press_time;
};
struct headset_key_custom headset_key_custom_setting = {
2000
};
struct headset_key_custom* get_headset_key_custom_setting(void)
{
return &headset_key_custom_setting;
}
-------------------------------------*/
/*註冊開關類設備*/
accdet_data.name = "h2w";
accdet_data.index = 0;
accdet_data.state = NO_DEVICE;
switch_dev_register(&accdet_data);
/*------------------------------------
accdet_data:全局變量 static struct switch_dev accdet_data;
accdet_data.state的狀態有:
enum accdet_report_state
{
NO_DEVICE =0,
HEADSET_MIC = 1,
HEADSET_NO_MIC = 2,
//HEADSET_ILEGAL = 3,
//DOUBLE_CHECK_TV = 4
};
作用:爲上層提供操作接口,註冊成功後會在sys/class/switch/目錄下生成名爲h2w目錄,該目錄下會有name,state等屬性文件,當硬件中斷髮生,驅動會改變這些屬性的值,如state,上層通過讀取這些屬性文件來獲取耳機的狀態等信息。
-------------------------------------*/
/*獲取耳機的配置信息*/
cust_headset_settings = get_cust_headset_settings();
/*------------------------------------
struct headset_mode_settings{
int pwm_width; //pwm frequence
int pwm_thresh; //pwm duty 增加該值可提高耳機PWM的佔空比,延長headset芯片內部比較器的work時
間,使雙擊識別率提高,建議值 0x600 或 0x400
int fall_delay; //falling stable time
int rise_delay; //rising stable time
int debounce0; //hook switch or double check debounce 減小該值也可以提高雙擊識別率 建議值 600
int debounce1; //mic bias debounce
int debounce3; //plug out debounce
};
static struct headset_mode_settings cust_headset_settings = {
0x500, 0x200, 1, 0x1f0, 0x800, 0x8000, 0x0
};
struct headset_mode_settings* get_cust_headset_settings(void)
{
return &cust_headset_settings;
}
-------------------------------------*/
/*註冊字符設備對象並自動創建設備節點inode*/
alloc_chrdev_region(&accdet_devno, 0, 1, ACCDET_DEVNAME);//
accdet_cdev = cdev_alloc();//static struct cdev *accdet_cdev
accdet_cdev->owner = THIS_MODULE;
accdet_cdev->ops = accdet_get_fops();
cdev_add(accdet_cdev, accdet_devno, 1);
accdet_class = class_create(THIS_MODULE, ACCDET_DEVNAME);
accdet_nor_device = device_create(accdet_class, NULL, accdet_devno, NULL, ACCDET_DEVNAME);
/*創建並註冊輸入設備*/
kpd_accdet_dev = input_allocate_device();//static struct input_dev *kpd_accdet_dev;
__set_bit(EV_KEY, kpd_accdet_dev->evbit);//按鍵類事件
__set_bit(KEY_CALL, kpd_accdet_dev->keybit);//call功能按鍵
__set_bit(KEY_ENDCALL, kpd_accdet_dev->keybit);//結束call功能鍵
__set_bit(KEY_NEXTSONG, kpd_accdet_dev->keybit);//下一首功能鍵
__set_bit(KEY_PREVIOUSSONG, kpd_accdet_dev->keybit);//上一首功能鍵
__set_bit(KEY_PLAYPAUSE, kpd_accdet_dev->keybit);//播放暫停功能鍵
__set_bit(KEY_STOPCD, kpd_accdet_dev->keybit);//終止播放功能鍵
__set_bit(KEY_VOLUMEDOWN, kpd_accdet_dev->keybit);//音量-功能鍵
__set_bit(KEY_VOLUMEUP, kpd_accdet_dev->keybit);//音量+功能鍵
kpd_accdet_dev->id.bustype = BUS_HOST;
kpd_accdet_dev->name = "ACCDET";
if(input_register_device(kpd_accdet_dev))
{
ACCDET_DEBUG("[Accdet]kpd_accdet_dev register : fail!\n");
}else
{
ACCDET_DEBUG("[Accdet]kpd_accdet_dev register : success!!\n");
}
/*INIT the timer to disable micbias. */
init_timer(&micbias_timer);
micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER;
micbias_timer.function = &disable_micbias;
micbias_timer.data = ((unsigned long) 0 );
/*創建工作隊列*/
accdet_workqueue = create_singlethread_workqueue("accdet");
INIT_WORK(&accdet_work, accdet_work_callback);
/*初始化喚醒鎖,阻止進入深度睡眠*/
wake_lock_init(&accdet_suspend_lock, WAKE_LOCK_SUSPEND, "accdet wakelock");
wake_lock_init(&accdet_irq_lock, WAKE_LOCK_SUSPEND, "accdet irq wakelock");
wake_lock_init(&accdet_key_lock, WAKE_LOCK_SUSPEND, "accdet key wakelock");
wake_lock_init(&accdet_timer_lock, WAKE_LOCK_SUSPEND, "accdet timer wakelock");
/*創建sys節點*/
#if DEBUG_THREAD
if((ret = accdet_create_attr(&accdet_driver_hal.driver))!=0)
{
ACCDET_DEBUG("create attribute err = %d\n", ret);
}
#endif
/*註冊pmic中斷*/ //有可能會在pmic.c中的PMIC_EINT_SETTING函數處註冊
pmic_register_interrupt_callback(12,accdet_int_handler);
pmic_register_interrupt_callback(13,accdet_eint_int_handler);
/*長按時間*/
long_press_time = press_key_time->headset_long_press_time;
ACCDET_DEBUG("[Accdet]accdet_probe : ACCDET_INIT\n");
/*static int g_accdet_first = 1;*/
if (g_accdet_first == 1)
{
long_press_time_ns = (s64)long_press_time * NSEC_PER_MSEC;
#ifdef ACCDET_EINT_IRQ
accdet_eint_workqueue = create_singlethread_workqueue("accdet_eint");
INIT_WORK(&accdet_eint_work, accdet_eint_work_callback);
accdet_disable_workqueue = create_singlethread_workqueue("accdet_disable");
INIT_WORK(&accdet_disable_work, disable_micbias_callback);
#endif
//Accdet Hardware Init
accdet_init();
accdet_pmic_Read_Efuse_HPOffset();
queue_work(accdet_workqueue, &accdet_work); //schedule a work for the first detection
#ifdef ACCDET_EINT
accdet_disable_workqueue = create_singlethread_workqueue("accdet_disable");
INIT_WORK(&accdet_disable_work, disable_micbias_callback);
accdet_eint_workqueue = create_singlethread_workqueue("accdet_eint");
INIT_WORK(&accdet_eint_work, accdet_eint_work_callback);
accdet_setup_eint();//初始化中斷
#endif
g_accdet_first = 0;
}
probe爲耳機的插入做好了充分的準備,下面分解兩種耳機的插入與拔出:
三段式耳機插入與拔出:觸發普通中斷
插入->accdet中斷產生->中斷處理函數前半部accdet_eint_func->後半部accdet_eint_work_callback
/*
上半部主要內容:
1.改變觸發方式
2.設置延時
3.改變耳機插入狀態標識cur_eint_state
4.關中斷,調度下半部
*/
static irqreturn_t accdet_eint_func(int irq,void *data)
{
int ret=0;
/*拔出走這裏*/
if(cur_eint_state == EINT_PIN_PLUG_IN )
{
/*重置觸發方式,爲下次插入做準備*/
#ifndef ACCDET_EINT_IRQ
if (CUST_EINT_ACCDET_TYPE == CUST_EINTF_TRIGGER_HIGH){
irq_set_irq_type(accdet_irq,IRQ_TYPE_LEVEL_HIGH);
}else{
irq_set_irq_type(accdet_irq,IRQ_TYPE_LEVEL_LOW);
}
#endif
/*設置延時*/
mt_gpio_set_debounce(gpiopin,headsetdebounce);
/* update the eint status */
cur_eint_state = EINT_PIN_PLUG_OUT;
}
/*插入走這裏*/
else
{
/*重置觸發方式,爲下次拔出做準備*/
#ifndef ACCDET_EINT_IRQ
if (CUST_EINT_ACCDET_TYPE == CUST_EINTF_TRIGGER_HIGH){
irq_set_irq_type(accdet_irq,IRQ_TYPE_LEVEL_LOW);
}else{
irq_set_irq_type(accdet_irq,IRQ_TYPE_LEVEL_HIGH);
}
#endif
mt_gpio_set_debounce(gpiopin,ACCDET_SHORT_PLUGOUT_DEBOUNCE_CN*1000);
/* update the eint status */
cur_eint_state = EINT_PIN_PLUG_IN;
/*開定時器,6秒*/
mod_timer(&micbias_timer, jiffies + MICBIAS_DISABLE_TIMER);
}
#ifndef ACCDET_EINT_IRQ
disable_irq_nosync(accdet_irq);
#endif
ACCDET_DEBUG("[Accdet]accdet_eint_func after cur_eint_state=%d\n", cur_eint_state);
ret = queue_work(accdet_eint_workqueue, &accdet_eint_work); //調度後半部accdet_eint_work_callback
return IRQ_HANDLED;
}
/*
後半部主要內容:
1.改變插入標識eint_accdet_sync_flag
2.pmic初始化
3.使能或禁止accdet,使檢測四段耳機插入
4.重開中斷
*/
static void accdet_eint_work_callback(struct work_struct *work)
{
ACCDET_DEBUG("[Accdet]accdet_eint_work_callback cur_eint_state=%d\n", cur_eint_state);
if (cur_eint_state == EINT_PIN_PLUG_IN) {
ACCDET_DEBUG("[Accdet]EINT func :plug-in\n");
mutex_lock(&accdet_eint_irq_sync_mutex);
eint_accdet_sync_flag = 1;//插入標識
mutex_unlock(&accdet_eint_irq_sync_mutex);
wake_lock_timeout(&accdet_timer_lock, 7*HZ);//七秒後鎖生效,阻止進入深度睡眠
accdet_init();// do set pwm_idle on in accdet_init
//set PWM IDLE on
pmic_pwrap_write(ACCDET_STATE_SWCTRL, (pmic_pwrap_read(ACCDET_STATE_SWCTRL)|ACCDET_SWCTRL_IDLE_EN));
//enable ACCDET unit
enable_accdet(ACCDET_SWCTRL_EN); //使能accdet檢測,以便開始檢測是否爲四段耳機
}
else {//EINT_PIN_PLUG_OUT
ACCDET_DEBUG("[Accdet]EINT func :plug-out\n");
mutex_lock(&accdet_eint_irq_sync_mutex);
eint_accdet_sync_flag = 0;
mutex_unlock(&accdet_eint_irq_sync_mutex);
del_timer_sync(&micbias_timer);
disable_accdet();//關閉accdet檢測
headset_plug_out();
}
#ifdef CONFIG_OF
enable_irq(accdet_irq);//前半部關中斷,後半部處理完後要重開中斷
#else
mt_eint_unmask(CUST_EINT_ACCDET_NUM);
#endif
ACCDET_DEBUG("[Accdet]enable_irq !!!!!!\n");
#endif
}
四段式耳機插入與拔出:觸發普通中斷 觸發pmic中斷
插入->accdet中斷產生->中斷處理函數前半部accdet_eint_func->後半部accdet_eint_work_callback->檢測到四段觸發pmic中斷->accdet_irq_handler()
int accdet_irq_handler(void)
{
U64 cur_time = 0;
cur_time = accdet_get_current_time();//獲取當前時間
if((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT)) {
clear_accdet_interrupt();
}//清中斷標識
if (accdet_status == MIC_BIAS){
pmic_pwrap_write(ACCDET_PWM_WIDTH, REGISTER_VALUE(cust_headset_settings->pwm_width));
pmic_pwrap_write(ACCDET_PWM_THRESH, REGISTER_VALUE(cust_headset_settings->pwm_width));
}//設置脈衝寬度
accdet_workqueue_func();//調度工作accdet_work_callback
while(((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT) &&
(accdet_timeout_ns(cur_time, ACCDET_TIME_OUT)))) {
}//等待超時,硬件穩定相關的。
return 1;
}
accdet_workqueue_func();//調度工作accdet_work_callback
static void accdet_work_callback(struct work_struct *work)
{
wake_lock(&accdet_irq_lock);
check_cable_type();// 狀態機處理
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
switch_set_state((struct switch_dev *)&accdet_data, cable_type);// 上報SWITCH
}else {
ACCDET_DEBUG("[Accdet] Headset has plugged out don't set accdet state\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
ACCDET_DEBUG( " [accdet] set state in cable_type status\n");
wake_unlock(&accdet_irq_lock);
}
check_cable_type(); // 狀態機處理
static inline void check_cable_type(void)
{
int current_status = 0;
int irq_temp = 0; //for clear IRQ_bit
int wait_clear_irq_times = 0;
current_status = ((pmic_pwrap_read(ACCDET_STATE_RG) & 0xc0)>>6); //A=bit1; B=bit0
ACCDET_DEBUG("[Accdet]accdet interrupt happen:[%s]current AB = %d\n",
accdet_status_string[accdet_status], current_status);
button_status = 0;
pre_status = accdet_status;
ACCDET_DEBUG("[Accdet]check_cable_type: ACCDET_IRQ_STS = 0x%x\n", pmic_pwrap_read(ACCDET_IRQ_STS));
IRQ_CLR_FLAG = FALSE;
switch(accdet_status)
{
case PLUG_OUT://剛插入時走這裏,作用:標記耳機類型是三段還是四段
if(current_status == 0)//三段耳機插入
{
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
cable_type = HEADSET_NO_MIC;
accdet_status = HOOK_SWITCH;
}else {
ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
#endif
}
else if(current_status == 1)//四段耳機插入
{
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
accdet_status = MIC_BIAS;
cable_type = HEADSET_MIC;
pmic_pwrap_write(ACCDET_DEBOUNCE3, cust_headset_settings->debounce3*30);//AB=11 debounce=30ms
}else {
ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
pmic_pwrap_write(ACCDET_DEBOUNCE0, button_press_debounce);
}
else if(current_status == 3)
{
ACCDET_DEBUG("[Accdet]PLUG_OUT state not change!\n");
#ifdef ACCDET_EINT
ACCDET_DEBUG("[Accdet] do not send plug out event in plug out\n");
}
else
{
ACCDET_DEBUG("[Accdet]PLUG_OUT can't change to this state!\n");
}
break;
case MIC_BIAS://一般是按鍵觸發進來的,所以這裏主要處理按鍵按下的情況
pmic_pwrap_write(ACCDET_DEBOUNCE0, cust_headset_settings->debounce0);
if(current_status == 0)//有按鍵按下
{
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
while((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT) && (wait_clear_irq_times<3))
{
ACCDET_DEBUG("[Accdet]check_cable_type: MIC BIAS clear IRQ on-going1....\n");
wait_clear_irq_times++;
msleep(5);
}
irq_temp = pmic_pwrap_read(ACCDET_IRQ_STS);
irq_temp = irq_temp & (~IRQ_CLR_BIT);
pmic_pwrap_write(ACCDET_IRQ_STS, irq_temp);
IRQ_CLR_FLAG = TRUE;
accdet_status = HOOK_SWITCH;//改變狀態
}else {
ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
button_status = 1;
if(button_status)
{
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
multi_key_detection(current_status);//按鍵檢測
}else {
ACCDET_DEBUG("[Accdet] multi_key_detection: Headset has plugged out\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
//accdet_auxadc_switch(0);
//recover pwm frequency and duty
pmic_pwrap_write(ACCDET_PWM_WIDTH, REGISTER_VALUE(cust_headset_settings->pwm_width));
pmic_pwrap_write(ACCDET_PWM_THRESH, REGISTER_VALUE(cust_headset_settings->pwm_thresh));
}
}
else if(current_status == 1)//一般不會進這個裏面
{
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
accdet_status = MIC_BIAS;
cable_type = HEADSET_MIC;
ACCDET_DEBUG("[Accdet]MIC_BIAS state not change!\n");
}else {
ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
}
else if(current_status == 3)//拔出耳機
{
ACCDET_DEBUG("[Accdet]do not send plug ou in micbiast\n");
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
accdet_status = PLUG_OUT;
}else {
ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
}
else
{
ACCDET_DEBUG("[Accdet]MIC_BIAS can't change to this state!\n");
}
break;
case HOOK_SWITCH://三段耳機或按鍵鬆開的情況
if(current_status == 0)//按鍵仍然按下
{
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
//for avoid 01->00 framework of Headset will report press key info for Audio
//cable_type = HEADSET_NO_MIC;
//accdet_status = HOOK_SWITCH;
ACCDET_DEBUG("[Accdet]HOOK_SWITCH state not change!\n");
}else {
ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
}
else if(current_status == 1)//按鍵鬆開彈起
{
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
multi_key_detection(current_status);//current_status 爲1, 上報按鍵彈起。
accdet_status = MIC_BIAS;
cable_type = HEADSET_MIC;
}else {
ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
pmic_pwrap_write(ACCDET_DEBOUNCE0, button_press_debounce);
}
else if(current_status == 3)
{
ACCDET_DEBUG("[Accdet] do not send plug out event in hook switch\n");
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
accdet_status = PLUG_OUT;
}else {
ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
}
mutex_unlock(&accdet_eint_irq_sync_mutex);
}
else
{
ACCDET_DEBUG("[Accdet]HOOK_SWITCH can't change to this state!\n");
}
break;
if(!IRQ_CLR_FLAG)
{
mutex_lock(&accdet_eint_irq_sync_mutex);
if(1 == eint_accdet_sync_flag) {
while((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT) && (wait_clear_irq_times<3))
{
ACCDET_DEBUG("[Accdet]check_cable_type: Clear interrupt on-going2....\n");
wait_clear_irq_times++;
msleep(5);
}
}
irq_temp = pmic_pwrap_read(ACCDET_IRQ_STS);
irq_temp = irq_temp & (~IRQ_CLR_BIT);
pmic_pwrap_write(ACCDET_IRQ_STS, irq_temp);
mutex_unlock(&accdet_eint_irq_sync_mutex);
IRQ_CLR_FLAG = TRUE;
}
else
{
IRQ_CLR_FLAG = FALSE;
}
}