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;
}
}