1、基本知识
1.1、耳机插口种类
耳机插口种类大概分为三段式和四段式。
三段式从左到右分为左声道、右声道、地。四段式从左到右分为左声道、右声道、地、麦克风。
首先是美标的。从最前头开始数第1、2、3、4节,分别是左声道/右声道/地线/MIC
然后是国标的。从最前头开始数第1、2、3、4节,分别是左声道/右声道/MIC/地线
可以通过用万用表测阻抗来区分国标和美标耳机,美标耳机阻抗为32欧,测第二段和最后一段。
带三个按键和MIC的标准耳机电路:
1.2、MTK平台耳机检测种类
1.2.1、EINT+AuxADC
【检测原理】
(1) 耳机检测方式为EINT+AuxADC。
(2) EINT HISR为AUX_EINT_HISR,在aux_task_main中注册中断。插入耳机,若是高电平触发中断,需要保证AUX_EINT_STATE为1,否则为0。
(3) hook key的检测采用ADC。当耳机plug in,trigger AUX_EINT_HISR,此时turn on micbias, double check plug in state后,再发送msg AUX_ID_EARPHONE给UEM。
然后turn off micbias。当aux收到来电消息,也会turn on micbias,检测SENDKEY ADC,直到挂断电话,才turn off micbias。
【软件配置】ACCDET_SUPPORT=FALSE
【典型平台】MT6236,MT6252
1.2.2、Accdet only
【检测原理】
(1) 耳机检测方式为ACCDET only。
(2) HISR为ACCDET_HISR,在ACCDET_INIT注册。
(3) hook key的检测采用ACCDET。
(4) 耳机状态与accdet处的电压对应关系如下表:
| Earphone state | Accdet voltage | Comparator AB state |
| ---------- |:-------------|: --------|
| Plug out | 1.9V | A=1,B=1 |
| Plug in | 0.4V~1.7V | A=0,B=1 |
| Hook key pressed | 0V | A=0,B=0 |
此方式是让耳机micbias 常开下,依靠耳机内部中断来检测耳机处于的状态的。但此方式会带来耳机插入瞬间有pop杂音的出现。
【软件配置】ACCDET_SUPPORT=TRUE
【典型平台】MT6276,MT6250D/MT6250
1.2.3、EINT_ACCDET
【检测原理】
(1) 耳机检测方式为EINT+ACCDET。
(2) EINT HISR为AUX_EINT2_HISR,在ACCDET_INIT注册。插入耳机,若是高电平触发中断,需要将AUX_EINT_STATE置为1,否则置为0。
(3) hook key的检测采用accdet。
(4) 耳机状态与accdet处的电压对应关系同上表。
(5) 耳机插入,触发eint之后,enable ACCDET,耳机拔出之后会disable ACCDET。
此方式在耳机为插入时,micbias是被disable的。利用中断EINT来打开micbias`,从而达到省功耗和减小杂音的效果的。待插入后,耳机检测走的路线还是accdet内部中断。
【软件配置】ACCDET_SUPPORT=EINT_ACCDET
【典型平台】MT6255,MT6250/MT6250D
1.3、MIC种类
MIC根据功放电路可以分为AC couple和DC couple。
ACC模式:这时在L/R引脚和MT6328电路间有两个隔直电容。
DCC模式:L/R引脚和MT6328电路间没有隔直电容。
main mic和sec mic用micbias0,headset mic用micbias1
MICBIAS(偏置电压):
偏置:在电路某点给一个参考分量,使电路能适应工作需要。
偏置可以是DC偏置,也可以是AC偏置。也可分为电流偏置和电压偏置。常见的是DC偏置。即电路某点经过一个起偏置作用的元件接到某个DC电源上。例如单级三极管发射极放大电路,至少需要一个基极偏置电阻。由于三极管放大电路经常用电流放大系数来计算放大效果。因此偏置电阻定义为电流偏置电阻,以便于计算和分析。 CMOS门电路输入端,接的上拉电阻或下拉电阻,一般可认为是电压偏置电阻。因为通过这个电阻的电流很少,电阻基本上是给门输入端一个静态参考电压。 交流偏置的一个典型应用例子:录音机的交流偏磁。
AC/DC couple(交流、直流耦合):
直流耦合
直流耦合(DC Coupling)就是直通,交流直流一起过,并不去掉了交流分量。
比如在3V的直流电平上叠加一个1Vpp的正弦波,如果用直流耦合,看到的是以3V为基准,+/-0.5V的正弦波。在功率放大中有重要应用。
交流耦合
交流耦合(AC Coupling)就是通过隔直电容耦合,去掉了直流分量。
交流耦合和直流耦合在匹配电路上的区别
直流耦合就是直接的导线连接,包括通过像电阻之类的线性元件的连接。它适用于对包括直流分量的信号的放大电路中。在直流耦合电路中,各级电路的静态工作点是互相影响的。一级的工作点改变了相邻的二级也会受到影响。因此不能单独地调整工作点电流和电压。而在交流耦合直流不耦合的电路中各级电路是用电容或者是电感隔离开的。因此静态工作点是独立的,调整静态工作点比较容易。直流耦合中因为各级的输入和输出阻抗是一定的,不好作阻抗变换,直接耦合时高效率匹配就很难做到。而在交流耦合电路中用线间变压器就很好地进行阻抗变换实现高效率的匹配。特别是选频放大电路中普遍采用的LC谐振电路更是极大地提高了电路的效率。
2、分析accdet driver
kernel-3.18/drivers/misc/mediatek/accdet/accdet_drv.c
kernel-3.18/drivers/misc/mediatek/accdet/mt6735/accdet.c
分析之前,先介绍下MT6737宏配置。
2.1、MT6737 ACCDET检测功能宏配置
平台支持的ACCDET检测功能如下,没有CONFIG开头的为其他旧平台的宏配置。
CONFIG_ACCDET_EINT(ACCDET+EINT、ACCDET+EINT multi-key)
CONFIG_ACCDET_PIN_RECOGNIZATION(ACCDET+EINT multi-key+Pin recognition)
CONFIG_ACCDET_EINT_IRQ(ACCDET only)
CONFIG_ACCDET_PIN_SWAP
1) ACCDET_EINT
是否启用了外部中断来侦测是否有耳机插入和拔出
2) ACCDET_MULTI_KEY_FEATURE
是否支持使用A/D来侦测key,这里要注意的是即使耳机只有一个key若是在89的平台也需打开此宏开关
3) ACCDET_LOW_POWER
当插入三段耳机6s后自动关闭micbias,达到省电的目的(MT6737平台改成5s并默认打开此功能)
以上三个宏在MT6737平台可以看成一个宏CONFIG_ACCDET_EINT,为ACCDET+EINT检测电路的宏配置
4) ACCDET_28v_MODE
在我们内部有一个switch是针对外部耳机是用2.8还是1.9V的切换开关,美标的是2.8V, 国标的是1.9V
5) ACCDET_PIN_RECOGNIZATION
美标的插孔识别国标的耳机,国标的耳机识别美标的插孔,MT6737平台为CONFIG_ACCDET_PIN_RECOGNIZATION,兼容识别OMTP和CTIA耳机的功能
6)ACCDET_SHORT_PLUGOUT_DEBOUNCE
ACCDET_SHORT_PLUGOUT_DEBOUNCE_CN 25
拔出耳机后有时候图标会再弹出后在消失, 主要解决类似bug,MT6737平台改到DTS里边配置,后面会提及
7)ACCDET_PIN_SWAP
美标的插孔识别国标的耳机,这个时候需要借助accdet的一个上拉电阻,当有这种情形的时候AB一直为0,达到检测到的目的,当然也有误判的时候, 4段耳机按住按键插入后会有误判,MT6737平台为CONFIG_ACCDET_PIN_SWAP
8)CONFIG_ACCDET_EINT_IRQ
MT6737平台ACCDET only检测电路的宏配置
2.2、MT6737 MIC宏配置
ACCDET_MIC_MODE
1)L/R和PMIC间有隔直电容,配置成1,为ACC模式
2)L/R和PMIC见没有隔直电容,配置成2或者6,为DCC模式
DCC模式详细分为两种:low cost with internal bias和low cost without internal bias
如果MIC引脚和PMIC的MICBIAS脚之间有连接电阻,起偏置上拉作用,则为前者,配置成2,如果没有连接MICBIAS引脚,则为后者,配置成6。
2.3、MT6737平台ACCDET驱动DTS配置
accdet: accdet@ {
compatible = "mediatek,mt6735-accdet",
"mediatek,mt6735m-accdet";
};
ACCDET@6{
compatible = "mediatek, ACCDET-eint";
interrupt-parent = <&eintc>;
interrupts = <6 8>;
debounce = <6 256000>;
}
&accdet {
/*
000:1.7v 001:1.8v 010:1.9v 011:2.0v 100:2.1v
101:2.3v 110:2.4v 111:2.5v
*/
accdet-mic-vol = <7>;//mic电压2.5v
headset-mode-setting = <0x500 0x200 1 0x1F0 0x800 0x800 0x20>;
accdet-plugout-debounce = <20>;//拔出耳机去抖时间间隔
/*1:ACC mode, 2:low cost without in bias, 6:low cost with in bias*/
accdet-mic-mode = <1>;
/*0--MD_MAX--UP_MAX--DW_MAX*/
/*
三段式耳机(MID/UP/DOWN PMIC内部通过AUXADC采样)检测电压范围(mV)
0<= Vol < 80: MID
80<= Vol < 228: UP
228<= Vol < 500: DOWN
*/
headset-three-key-threshold = <0 80 220 500>;
/*0--MD_MAX--VOICE_MAX--UP_MAX--DW_MAX*/
/*
四段式耳机(MID/VOICE/UP/DOWN PMIC内部通过AUXADC采样)检测电压范围(mV)
0<= Vol < 58: MID
58<= Vol < 121: VOICE
121<= Vol < 192: UP
192<= Vol < 450: DOWN
*/
headset-four-key-threshold = <0 58 121 192 450>;
/*add by zhaofei for headset eint*/
pinctrl-names = "default", "state_eint_as_int";
pinctrl-0 = <&accdet_pins_default>;
pinctrl-1 = <&accdet_pins_eint_as_int>;
status = "okay";
};
&pio{
accdet_pins_default: accdetdefault{
};
accdet_pins_eint_as_int: accdeteint@0 {
pins_cmd_dat {
pins = <PINMUX_GPIO6__FUNC_GPIO6>;
slew-rate = <0>;
bias-disable;
};
};
};
2.4、ACCDET驱动流程概述(此处采用ACCDET+EINT模式来讲述)
对于Headset装置的插入检测,一般通过Jack即耳机插座来完成,大致的原理是使用带检测机械结构的耳机插座,将检测脚连到可GPIO中断上,当耳机插入时,耳机插头的金属会碰到检测脚,使得检测脚的电平产生变化,从而引起中断。这样就可以在中断处理函数中读取GPIO的的值,进一步判断出耳机是插入还是拔出。
而对于Headset是否带mic的检测,需要通过codec附加的micbias电流的功能
绿色部分:用户操作和耳机状态
1)用户插入耳机,由AP EINT检测中断
2)AP EINT中断下半部初始化PMIC ACCDET设置,打开ACCDET中断
3)继续插入耳机,PMIC ACCDET中断,内部比较器比较电压,判断插入、拔出和耳机类型
4)驱动上报耳机类型
ACCDET检测状态机:
ACCDET内部有两个比较器,会根据传入的电压判断并产生中断。ACCDET的输入电压即耳机 MIC PIN 的电压,内部比较器的输出分别对应A/B两个寄存器。
ACCDET内部两个比较器的Vref分别是1.77V和0.4V(硬件决定的,不能修改),所以,对应的电压有3个范围:
1.77V-1.9V: 未插入耳机的状态(AB=B11)
0.4V-1.77V: 插入4段式(有Mic)耳机时的状态(AB = B01)
0-0.4V : 插入3段式耳机时的状态,或者4段式按键按下时的状态(AB = B00)
耳机的状态会保存到ACCDET的寄存器中,当电压在任意2个范围间切换时,状态发生变化,ACCDET产生中断,中断处理中读取状态寄存器的值,并根据状态的变化做相应的处理.
accdet_drv.c:
module_init(accdet_mod_init)->platform_driver_register(&accdet_driver)
static struct platform_driver accdet_driver = {
.probe = accdet_probe,
/* .suspend = accdet_suspend, */
/* .resume = accdet_resume, */
.remove = accdet_remove,
.driver = {
.name = "Accdet_Driver",
#ifdef CONFIG_PM
.pm = &accdet_pm_ops,
#endif
.of_match_table = accdet_of_match,//.compatible = "mediatek,mt6735-accdet"
},
};
2.4.1、accdet_probe
static int accdet_probe(struct platform_device *dev)
{
return mt_accdet_probe(dev);
}
int mt_accdet_probe(struct platform_device *dev)
{
int ret = 0;
#if DEBUG_THREAD
struct platform_driver accdet_driver_hal = accdet_driver_func();
#endif
#if defined(CONFIG_TS3A225E_ACCDET)//TS3A225E是一款音频头戴耳机开关设备,由 I2C 或者外部触发器引脚触发的检测
if (ts3a225e_i2c_client == NULL) {
ACCDET_DEBUG("[Accdet]ts3a225e_i2c_client is NULL!\n");
return -EPROBE_DEFER;
}
#endif
ACCDET_INFO("[Accdet]accdet_probe begin!\n");
/*--------------------------------------------------------------------
// below register accdet as switch class
//------------------------------------------------------------------*/
/* switch是Android引进的一个新驱动,用于检测一些开关量。比如检测耳机插入和USB设备插入等。
struct switch_dev {
const char *name;
struct device *dev;
int index;
int state;
ssize_t (*print_name)(structswitch_dev *sdev, char *buf);
ssize_t (*print_state)(structswitch_dev *sdev, char *buf);
};
其中name表示设备的名称;dev表示具体的设备对象;由于系统中可能存在多个switch设备,index则表示该设备是index个被注册的switch设备;state表示当前设备的状态;另外的两个函数指针都是用于操作sysfs文件系统的,其中print_name函数用于在sysfs中显示设备名称不,而print_state函数则用于显示设备的状态。
*/
accdet_data.name = "h2w";
accdet_data.index = 0;
accdet_data.state = NO_DEVICE;
/*switch_dev_register(),首先,判断是否已经创建switch_class,如果没有,则创建switch_class;其次,取得要创建的设备的索引,然后通过device_create创建设备;最后,通过device_create_file函数在sysfs中分别创建两个entry,如果创建失败,则分别删除已经创建的文件或者switch_class,一个用于输出设备状态state;另一个用于输出设备名称name。*/
ret = switch_dev_register(&accdet_data);//它会建立一个用于耳机插拔检测的目录/sys/class/switch/h2w,在此目录下有个设备结点名为state,switch driver通过更新state的值,从而通知Android上层耳机状态的改变。上图是通过adb shell打印的耳机插入和拔出state值。
if (ret) {
ACCDET_ERROR("[Accdet]switch_dev_register returned:%d!\n", ret);
return 1;
}
/*----------------------------------------------------------------------
// Create normal device for auido use
//--------------------------------------------------------------------*/
ret = alloc_chrdev_region(&accdet_devno, 0, 1, ACCDET_DEVNAME);//动态分配设备号
if (ret)
ACCDET_ERROR("[Accdet]alloc_chrdev_region: Get Major number error!\n");
accdet_cdev = cdev_alloc();//空间的申请
accdet_cdev->owner = THIS_MODULE;
accdet_cdev->ops = accdet_get_fops();//上层会通过这传命令
/*
accdet_get_fops->accdet_fops
static const struct file_operations accdet_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = accdet_unlocked_ioctl,
.open = accdet_open,
.release = accdet_release,
};
accdet_unlocked_ioctl->mt_accdet_unlocked_ioctl
long mt_accdet_unlocked_ioctl(unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case ACCDET_INIT:
break;
case SET_CALL_STATE://当状态state改变,通知headset驱动
call_status = (int)arg;
ACCDET_DEBUG("[Accdet]accdet_ioctl : CALL_STATE=%d\n", call_status);
break;
case GET_BUTTON_STATUS://获取耳机按键状态
ACCDET_DEBUG("[Accdet]accdet_ioctl : Button_Status=%d (state:%d)\n", button_status, accdet_data.state);
return button_status;
default:
ACCDET_DEBUG("[Accdet]accdet_ioctl : default\n");
break;
}
return 0;
}
*/
ret = cdev_add(accdet_cdev, accdet_devno, 1);//注册字符设备
if (ret)
ACCDET_ERROR("[Accdet]accdet error: cdev_add\n");
accdet_class = class_create(THIS_MODULE, ACCDET_DEVNAME);//为accdet创建一个class
/* if we want auto creat device node, we must call this*/
//#define ACCDET_DEVNAME "accdet"
accdet_nor_device = device_create(accdet_class, NULL, accdet_devno, NULL, ACCDET_DEVNAME); //创建一个设备节点,在sys/class下可以找到accdet设备节点
/*--------------------------------------------------------------------
// Create input device
//创建并注册输入设备
//------------------------------------------------------------------*/
kpd_accdet_dev = input_allocate_device();//为kpd_accdet_dev分配一个输入设备结构体空间
if (!kpd_accdet_dev) {
ACCDET_ERROR("[Accdet]kpd_accdet_dev : fail!\n");
return -ENOMEM;
}
/*INIT the timer to disable micbias.*/
init_timer(&micbias_timer);//初始化一个定时器
micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER;
micbias_timer.function = &disable_micbias;//关mic偏置电压
micbias_timer.data = ((unsigned long)0);
//注册input设备,定义按键
/*define multi-key keycode*/
//evbit用于记录支持的事件类型的位图
//keybit记录支持的按键值的位图
__set_bit(EV_KEY, kpd_accdet_dev->evbit);//按键类事件
__set_bit(KEY_PLAYPAUSE, kpd_accdet_dev->keybit);//播放暂停功能键
__set_bit(KEY_VOLUMEDOWN, kpd_accdet_dev->keybit);//音量减
__set_bit(KEY_VOLUMEUP, kpd_accdet_dev->keybit);//音量加
__set_bit(KEY_VOICECOMMAND, kpd_accdet_dev->keybit);//接电话
/*
//id用于匹配事件处理层handler
struct input_id {
__u16 bustype;//总线类型
__u16 vendor;//生产商编号
__u16 product;//产品编号
__u16 version;//版本号
};*/
kpd_accdet_dev->id.bustype = BUS_HOST;//主机总线
kpd_accdet_dev->name = "ACCDET";
if (input_register_device(kpd_accdet_dev))//注册输入设备结构体kpd_accdet_dev
ACCDET_ERROR("[Accdet]kpd_accdet_dev register : fail!\n");
/*------------------------------------------------------------------
// Create workqueue创建工作队列
//------------------------------------------------------------------ */
accdet_workqueue = create_singlethread_workqueue("accdet");//创建工作队列
INIT_WORK(&accdet_work, accdet_work_callback);//初始化工作队列accdet_work_callback
/*------------------------------------------------------------------
// wake lock
//------------------------------------------------------------------*/
//初始化唤醒锁,阻止进入深度睡眠
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");
#if DEBUG_THREAD
ret = accdet_create_attr(&accdet_driver_hal.driver);//创建sys节点,在sys/bus/platform/drivers/Accdet_Driver可以看到节点,会创建dump_register、set_headset_mode、start_debug、set_register节点。下图为通过adb看到的创建的节点。为了调试使用。
if (ret != 0)
ACCDET_ERROR("create attribute err = %d\n", ret);
#endif
//注册pmic中断
pmic_register_interrupt_callback(12, accdet_int_handler);
pmic_register_interrupt_callback(13, accdet_eint_int_handler);
ACCDET_INFO("[Accdet]accdet_probe : ACCDET_INIT\n");
//Accdet Hardware Init
if (g_accdet_first == 1) {
eint_accdet_sync_flag = 1;
#ifdef CONFIG_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_get_dts_data();//获取dts数据
accdet_init();//硬件初始化
accdet_pmic_Read_Efuse_HPOffset();
/*schedule a work for the first detection*/
queue_work(accdet_workqueue, &accdet_work);
#ifdef CONFIG_ACCDET_EINT
accdet_disable_workqueue = create_singlethread_workqueue("accdet_disable");//schedule a work for the first detection
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(dev);//初始化中断
#endif
g_accdet_first = 0;
}
ACCDET_INFO("[Accdet]accdet_probe done!\n");
return 0;
}
2.5、耳机的插入
下面从插入耳机进行分析整个驱动流程。
当插入耳机,会触发中断。
static irqreturn_t accdet_eint_func(int irq, void *data)
{
int ret = 0;
ACCDET_DEBUG("[Accdet]Enter accdet_eint_func !!!!!!\n");
if (cur_eint_state == EINT_PIN_PLUG_IN) {/*拔出耳机*/
/*
To trigger EINT when the headset was plugged in
We set the polarity back as we initialed.
*/
#ifndef CONFIG_ACCDET_EINT_IRQ
/*重置触发方式,为下次插入做准备*/
if (accdet_eint_type == IRQ_TYPE_LEVEL_HIGH)
irq_set_irq_type(accdet_irq, IRQ_TYPE_LEVEL_HIGH);
else
irq_set_irq_type(accdet_irq, IRQ_TYPE_LEVEL_LOW);
#endif
#ifdef CONFIG_ACCDET_EINT_IRQ
pmic_pwrap_write(ACCDET_EINT_CTL, pmic_pwrap_read(ACCDET_EINT_CTL) & (~(7 << 4)));
/*debounce=256ms*/
pmic_pwrap_write(ACCDET_EINT_CTL, pmic_pwrap_read(ACCDET_EINT_CTL) | EINT_IRQ_DE_IN);
pmic_pwrap_write(ACCDET_DEBOUNCE3, cust_headset_settings->debounce3);
#else
gpio_set_debounce(gpiopin, headsetdebounce);//设置按键的去抖时间间隔,20000us
#endif
/* update the eint status */
cur_eint_state = EINT_PIN_PLUG_OUT;//更新中断状态
} else {
/*
To trigger EINT when the headset was plugged out
We set the opposite polarity to what we initialed.
*/
/*重置触发方式,为下次拔出做准备*/
#ifndef CONFIG_ACCDET_EINT_IRQ
if (accdet_eint_type == IRQ_TYPE_LEVEL_HIGH)
irq_set_irq_type(accdet_irq, IRQ_TYPE_LEVEL_LOW);
else
irq_set_irq_type(accdet_irq, IRQ_TYPE_LEVEL_HIGH);
#endif
#ifdef CONFIG_ACCDET_EINT_IRQ
pmic_pwrap_write(ACCDET_EINT_CTL, pmic_pwrap_read(ACCDET_EINT_CTL) & (~(7 << 4)));
/*debounce=16ms*/
pmic_pwrap_write(ACCDET_EINT_CTL, pmic_pwrap_read(ACCDET_EINT_CTL) | EINT_IRQ_DE_OUT);
#else
gpio_set_debounce(gpiopin, accdet_dts_data.accdet_plugout_debounce * 1000);//设置按键的去抖时间间隔
#endif
/* update the eint status */
cur_eint_state = EINT_PIN_PLUG_IN;
//mod_timer函数也可以操作那些已经初始化,但还没有被激活的定时器,如果定时器没有激活,mod_timer会激活它。
mod_timer(&micbias_timer, jiffies + MICBIAS_DISABLE_TIMER);//激活定时器,6s
}
/*
disable_irq:在非中断处理函数中使用,会阻塞;
disable_irq_nosync:在中断处理函数中使用,不会阻塞;用于屏蔽相应中断;
为什么要屏蔽中断?使能中断后,一旦触发中断,系统会进入中断处理函数;如果下一个中断触发的时候,前一个中断处理函数已经完成,这是理想状态,不会发生异常;如果前一个中断处理函数还未完成,那么就会导致中断嵌套。为了不出现中断嵌套,必须在中断处理函数中屏蔽中断,待中断处理完成后,再主动使能中断。
*/
#ifndef CONFIG_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;
}
accdet_eint_func函数为中断上半部,主要做了以下内容。
1)重置触发方式 2)设置去抖延时 3)更新耳机中断状态 4)在中断处理函数中屏蔽中断,调用中断下半部
accdet_eint_workqueue = create_singlethread_workqueue("accdet_eint");
INIT_WORK(&accdet_eint_work, accdet_eint_work_callback);
从这我们可知,中断上半部调用的中断下半部处理函数为accdet_eint_work_callback。
static void accdet_eint_work_callback(struct work_struct *work)
{
#ifdef CONFIG_ACCDET_EINT_IRQ
......
//这里与不是我们讨论的中断模式,所以这一段不走
#else
/*KE under fastly plug in and plug out*/
if (cur_eint_state == EINT_PIN_PLUG_IN) {//插入耳机状态
ACCDET_DEBUG("[Accdet]ACC EINT func :plug-in, cur_eint_state = %d\n", cur_eint_state);
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);//七秒后锁生效,阻止进入深度睡眠
#ifdef CONFIG_ACCDET_PIN_SWAP
......
#endif
#if defined(CONFIG_TS3A225E_ACCDET)
......
#endif
//硬件寄存器初始化
accdet_init(); /* do set pwm_idle on in accdet_init*/
#ifdef CONFIG_ACCDET_PIN_RECOGNIZATION
......
#endif
/*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检测,开始检测pmic
} else {//拔出耳机
/*EINT_PIN_PLUG_OUT*/
/*Disable ACCDET*/
ACCDET_DEBUG("[Accdet]ACC EINT func :plug-out, cur_eint_state = %d\n", cur_eint_state);
mutex_lock(&accdet_eint_irq_sync_mutex);
eint_accdet_sync_flag = 0;//中断标识置为拔出
mutex_unlock(&accdet_eint_irq_sync_mutex);
del_timer_sync(&micbias_timer);//删除定时器
#ifdef CONFIG_ACCDET_PIN_RECOGNIZATION
.....
#endif
#ifdef CONFIG_ACCDET_PIN_SWAP
......
#endif
#if defined(CONFIG_TS3A225E_ACCDET)
.......
#endif
/*accdet_auxadc_switch(0);*/
disable_accdet();//关闭accdet检测
headset_plug_out();
}
enable_irq(accdet_irq);//前半部关中断,后半部处理完后要重开中断
ACCDET_DEBUG("[Accdet]enable_irq !!!!!!\n");
#endif
}
accdet_eint_work_callback函数为中断下半部,主要做了下面一些工作。
1)改变中断插入标识eint_accdet_sync_flag
2)pmic初始化
3)使能或禁止accdet,使检测四段式耳机插入
4)将前半部关闭的中断,在后半部重新打开
如果为四段式耳机的插入,在此会触发pmic中断。
static inline void accdet_init(void)
{
ACCDET_DEBUG("[Accdet]accdet hardware init\n");
/*clock*/
pmic_pwrap_write(TOP_CKPDN_CLR, RG_ACCDET_CLK_CLR);
/*ACCDET_DEBUG("[Accdet]accdet TOP_CKPDN=0x%x!\n", pmic_pwrap_read(TOP_CKPDN)); */
/*reset the accdet unit*/
/*ACCDET_DEBUG("ACCDET reset : reset start!\n\r");*/
pmic_pwrap_write(TOP_RST_ACCDET_SET, ACCDET_RESET_SET);
/*ACCDET_DEBUG("ACCDET reset function test: reset finished!!\n\r");*/
pmic_pwrap_write(TOP_RST_ACCDET_CLR, ACCDET_RESET_CLR);
/*init 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));
pmic_pwrap_write(ACCDET_STATE_SWCTRL, 0x07);
/*rise and fall delay of PWM*/
pmic_pwrap_write(ACCDET_EN_DELAY_NUM,
(cust_headset_settings->fall_delay << 15 | cust_headset_settings->rise_delay));
/* init the debounce time*/
#ifdef CONFIG_ACCDET_PIN_RECOGNIZATION
pmic_pwrap_write(ACCDET_DEBOUNCE0, cust_headset_settings->debounce0);
pmic_pwrap_write(ACCDET_DEBOUNCE1, 0xFFFF); /*2.0s*/
pmic_pwrap_write(ACCDET_DEBOUNCE3, cust_headset_settings->debounce3);
pmic_pwrap_write(ACCDET_DEBOUNCE4, ACCDET_DE4);
#else
pmic_pwrap_write(ACCDET_DEBOUNCE0, cust_headset_settings->debounce0);
pmic_pwrap_write(ACCDET_DEBOUNCE1, cust_headset_settings->debounce1);
pmic_pwrap_write(ACCDET_DEBOUNCE3, cust_headset_settings->debounce3);
pmic_pwrap_write(ACCDET_DEBOUNCE4, ACCDET_DE4);
#endif
/*enable INT *///使能中断
#ifdef CONFIG_ACCDET_EINT
pmic_pwrap_write(ACCDET_IRQ_STS, pmic_pwrap_read(ACCDET_IRQ_STS) & (~IRQ_CLR_BIT));
#endif
pmic_pwrap_write(INT_CON_ACCDET_SET, RG_ACCDET_IRQ_SET);
/*********************ACCDET Analog Setting***********************************************************/
pmic_set_register_value(PMIC_RG_AUDMICBIASVREF, accdet_dts_data.mic_mode_vol);
pmic_pwrap_write(ACCDET_RSV, 0x1290); /*TODO: need confirm pull low,6328 bit[12]=1*/
//mic模式,根据dts定义选择
if (accdet_dts_data.accdet_mic_mode == 1) /* ACC mode*/
pmic_set_register_value(PMIC_RG_AUDMICBIAS1DCSWPEN, 0);
else if (accdet_dts_data.accdet_mic_mode == 2)/* Low cost mode without internal bias*/
pmic_pwrap_write(ACCDET_RSV, pmic_pwrap_read(ACCDET_RSV) | ACCDET_INPUT_MICP);
else if (accdet_dts_data.accdet_mic_mode == 6) {/* Low cost mode with internal bias*/
pmic_pwrap_write(ACCDET_RSV, pmic_pwrap_read(ACCDET_RSV) | ACCDET_INPUT_MICP);
pmic_set_register_value(PMIC_RG_AUDMICBIAS1DCSWPEN, 1); /*switch P internal*/
}
/**************************************************************************************************/
#if defined CONFIG_ACCDET_EINT
/* disable ACCDET unit*/
pre_state_swctrl = pmic_pwrap_read(ACCDET_STATE_SWCTRL);
pmic_pwrap_write(ACCDET_CTRL, ACCDET_DISABLE);
pmic_pwrap_write(ACCDET_STATE_SWCTRL, 0x0);
pmic_pwrap_write(TOP_CKPDN_SET, RG_ACCDET_CLK_SET);
#elif defined CONFIG_ACCDET_EINT_IRQ
...
#else
...
#endif
/**************************************AUXADC enable auto sample****************************************/
pmic_pwrap_write(ACCDET_AUXADC_AUTO_SPL, (pmic_pwrap_read(ACCDET_AUXADC_AUTO_SPL) | ACCDET_AUXADC_AUTO_SET));
}
accdet_init函数完成pmic寄存器操作,并触发了pmic中断。
pmic_register_interrupt_callback(12, accdet_int_handler)
void accdet_int_handler(void)
{
int ret = 0;
ACCDET_DEBUG("[accdet_int_handler]....\n");
ret = accdet_irq_handler();//调用accdet_irq_handler函数
if (0 == ret)
ACCDET_DEBUG("[accdet_int_handler] don't finished\n");
}
int accdet_irq_handler(void)
{
u64 cur_time = 0;
cur_time = accdet_get_current_time();//获取当前时间
#ifdef CONFIG_ACCDET_EINT_IRQ
.....
#else
if ((pmic_pwrap_read(ACCDET_IRQ_STS) & IRQ_STATUS_BIT))
clear_accdet_interrupt();//清中断标识
if (accdet_status == MIC_BIAS) {//检测到mic偏置电压
/*accdet_auxadc_switch(1);*/
//设置脉冲宽度
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))))//等待超时,硬件稳定相关的
;
#endif
#ifdef ACCDET_NEGV_IRQ
....
#endif
return 1;
}
accdet_workqueue_func();
static void accdet_workqueue_func(void)
{
int ret;
ret = queue_work(accdet_workqueue, &accdet_work);
if (!ret)
ACCDET_DEBUG("[Accdet]accdet_work return:%d!\n", ret);
}
accdet_workqueue = create_singlethread_workqueue("accdet");
INIT_WORK(&accdet_work, accdet_work_callback);
通过上面可知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_set_state来设置不同的状态值。
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);
}
accdet_work_callback函数主要做了状态机处理和设置耳机状态值state。下面进入状态机函数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) {//三段式耳机
#ifdef CONFIG_ACCDET_PIN_RECOGNIZATION
........
#else
mutex_lock(&accdet_eint_irq_sync_mutex);
if (1 == eint_accdet_sync_flag) {//耳机插入
cable_type = HEADSET_NO_MIC;//不带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;//给accdet_status标记为mic偏置电压
cable_type = HEADSET_MIC;//带mic
/*AB=11 debounce=30ms*/
pmic_pwrap_write(ACCDET_DEBOUNCE3, cust_headset_settings->debounce3 * 30);
} 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 CONFIG_ACCDET_EINT
ACCDET_DEBUG("[Accdet] do not send plug out event in plug out\n");
#else
......
#endif
} else {
ACCDET_DEBUG("[Accdet]PLUG_OUT can't change to this state!\n");
}
break;
case MIC_BIAS://一般是耳机按键触发进来的,所以这里主要处理按键按下的情况
/*solution: resume hook switch debounce time*/
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(20);
}
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);//AD 采样并上报按键键值。 注意current_status 为0, 上报的是按键按下。
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) {//拔出耳机
#if defined CONFIG_ACCDET_EINT || defined CONFIG_ACCDET_EINT_IRQ
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;//将accdet_status标识为拔出耳机
else
ACCDET_DEBUG("[Accdet] Headset has plugged out\n");
mutex_unlock(&accdet_eint_irq_sync_mutex);
#else
.....
#endif
} 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);
/*solution: reduce hook switch debounce time to 0x400*/
pmic_pwrap_write(ACCDET_DEBOUNCE0, button_press_debounce);
} else if (current_status == 3) {
#if defined CONFIG_ACCDET_EINT || defined CONFIG_ACCDET_EINT_IRQ
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
......
#endif
} else {
ACCDET_DEBUG("[Accdet]HOOK_SWITCH can't change to this state!\n");
}
break;
case STAND_BY:
if (current_status == 3) {
#if defined CONFIG_ACCDET_EINT || defined CONFIG_ACCDET_EINT_IRQ
ACCDET_DEBUG("[Accdet]accdet do not send plug out event in stand by!\n");
#else
.....
#endif
} else {
ACCDET_DEBUG("[Accdet]STAND_BY can't change to this state!\n");
}
break;
default:
ACCDET_DEBUG("[Accdet]check_cable_type: accdet current status error!\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(20);
}
}
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;
ACCDET_DEBUG("[Accdet]check_cable_type:Clear interrupt:Done[0x%x]!\n", pmic_pwrap_read(ACCDET_IRQ_STS));
} else {
IRQ_CLR_FLAG = false;
}
ACCDET_DEBUG("[Accdet]cable type:[%s], status switch:[%s]->[%s]\n",
accdet_report_string[cable_type], accdet_status_string[pre_status],
accdet_status_string[accdet_status]);
}
mtk log:
[Headset_mic], status switch:[Plug_out]->[Headset_plug_in] 刚插入耳机状态
[Headset_mic], status switch:[Hook_switch]->[Headset_plug_in] 耳机按键弹起状态
[Headset_mic], status switch:[Headset_plug_in]->[Hook_switch]耳机按键被按下状态
[Headset_plug_in]current AB = 0 插入3段式耳机时的状态,或者4段式按键按下时的状态
[Hook_switch]current AB = 1 插入4段式(有Mic)耳机时的状态
[Headset_plug_in]current AB = 3 未插入耳机的状态