<think>我们正在分析 `mt_accdet_probe` 函数,这是耳机检测(Accdet)驱动的平台设备探测函数。该函数负责初始化驱动,包括注册设备、创建输入设备、工作队列、唤醒锁以及设置中断处理等。
### 函数流程解析
#### 1. **条件编译与初始化检查**
```c
#if defined(CONFIG_TS3A225E_ACCDET)
if (ts3a225e_i2c_client == NULL) {
ACCDET_DEBUG("[Accdet]ts3a225e_i2c_client is NULL!\n");
return -EPROBE_DEFER;
}
#endif
```
- 如果配置了 `CONFIG_TS3A225E_ACCDET`(使用TI的耳机检测芯片),则检查I2C客户端是否初始化。若为空,返回 `-EPROBE_DEFER` 表示稍后重试。
#### 2. **注册Switch设备(条件编译)**
```c
#if JUST_INPUT_NO_SWITCH
accdet_data.name = "h2w";
accdet_data.index = 0;
accdet_data.state = NO_DEVICE;
ret = switch_dev_register(&accdet_data);
if (ret) {
ACCDET_ERROR("[Accdet]switch_dev_register returned:%d!\n", ret);
return 1;
}
#endif
```
- 当启用 `JUST_INPUT_NO_SWITCH` 时,注册一个Switch设备(名为"h2w"),用于报告耳机插拔状态。
#### 3. **注册字符设备**
```c
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();
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_nor_device = device_create(accdet_class, NULL, accdet_devno, NULL, ACCDET_DEVNAME);
```
- 为音频相关操作注册字符设备(如 `ioctl` 控制)。步骤包括:
1. 分配设备号。
2. 分配并初始化 `cdev` 结构,设置文件操作函数。
3. 添加字符设备到内核。
4. 创建设备类和设备节点。
#### 4. **创建输入设备**
```c
kpd_accdet_dev = input_allocate_device();
if (!kpd_accdet_dev) {
ACCDET_ERROR("[Accdet]kpd_accdet_dev : fail!\n");
return -ENOMEM;
}
// 初始化定时器(用于关闭麦克风偏置电压)
timer_setup(&micbias_timer, disable_micbias, 0);
micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER;
// 设置按键事件
__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);
// 设置开关事件(耳机/麦克风插入)
__set_bit(EV_SW, kpd_accdet_dev->evbit);
__set_bit(SW_HEADPHONE_INSERT, kpd_accdet_dev->swbit);
__set_bit(SW_MICROPHONE_INSERT, kpd_accdet_dev->swbit);
__set_bit(SW_JACK_PHYSICAL_INSERT, kpd_accdet_dev->swbit);
__set_bit(SW_LINEOUT_INSERT, kpd_accdet_dev->swbit);
kpd_accdet_dev->id.bustype = BUS_HOST;
kpd_accdet_dev->name = "ACCDET";
if (input_register_device(kpd_accdet_dev))
ACCDET_ERROR("[Accdet]kpd_accdet_dev register : fail!\n");
```
- 分配并初始化输入设备:
- 设置按键事件:播放/暂停、音量加减、语音命令。
- 设置开关事件:耳机插入、麦克风插入、物理插孔插入、线输出插入。
- 注册输入设备。
#### 5. **创建工作队列**
```c
accdet_workqueue = create_singlethread_workqueue("accdet");
INIT_WORK(&accdet_work, accdet_work_callback);
```
- 创建单线程工作队列 `accdet`,并初始化工作项 `accdet_work`,其回调函数为 `accdet_work_callback`(用于处理耳机检测事件)。
#### 6. **注册唤醒锁**
```c
accdet_suspend_lock = wakeup_source_register(NULL, "accdet_suspend_lock");
accdet_irq_lock = wakeup_source_register(NULL, "accdet_irq_lock");
accdet_key_lock = wakeup_source_register(NULL, "accdet_key_lock");
accdet_timer_lock = wakeup_source_register(NULL, "accdet_timer_lock");
```
- 注册多个唤醒锁,用于在关键操作期间阻止系统休眠。
#### 7. **注册中断处理函数**
```c
pmic_register_interrupt_callback(12, accdet_int_handler);
pmic_register_interrupt_callback(13, accdet_eint_int_handler);
```
- 注册PMIC中断回调:
- 中断12:用于耳机插入检测。
- 中断13:用于外部中断(EINT)检测。
#### 8. **首次初始化(`g_accdet_first` 控制)**
```c
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_get_dts_data();
// 初始化硬件
accdet_init();
// 读取EFUSE中的耳机偏移值
accdet_pmic_Read_Efuse_HPOffset();
// 调度工作队列进行首次检测
queue_work(accdet_workqueue, &accdet_work);
#ifdef CONFIG_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);
// 设置EINT
accdet_setup_eint(dev);
#endif
g_accdet_first = 0;
}
```
- 只在首次探测时执行:
1. 设置同步标志 `eint_accdet_sync_flag=1`(允许事件上报)。
2. 若配置了外部中断(`CONFIG_ACCDET_EINT_IRQ` 或 `CONFIG_ACCDET_EINT`),创建工作队列并初始化工作项。
3. 调用 `accdet_get_dts_data` 从设备树读取参数。
4. 硬件初始化 `accdet_init()`。
5. 读取EFUSE中的耳机偏移值(用于校准)。
6. 调度工作队列进行首次耳机检测(模拟插入事件)。
7. 设置外部中断(`accdet_setup_eint`)。
8. 标记初始化完成(`g_accdet_first=0`)。
### 关键点总结
- **多设备支持**:通过字符设备、输入设备、Switch设备提供多种接口。
- **工作队列**:处理耗时操作(如硬件检测),避免在中断上下文中执行。
- **唤醒锁**:确保关键操作期间系统不会休眠。
- **首次检测**:在驱动加载时主动检测当前耳机状态。
- **条件编译**:根据内核配置支持不同硬件(如TI芯片、外部中断等)。
§§相关问题§§
1. 为什么在注册PMIC中断(12和13)时不检查返回值?如果注册失败会导致什么问题?
2. 当 `g_accdet_first` 为0时(非首次初始化),哪些关键初始化步骤会被跳过?这是否会导致功能异常?
3. 在输入设备中同时设置 `EV_KEY` 和 `EV_SW` 事件的作用是什么?用户空间如何区分这些事件?
4. 函数中两次创建同名工作队列(如 `accdet_disable_workqueue`)的条件编译块(`CONFIG_ACCDET_EINT_IRQ` 和 `CONFIG_ACCDET_EINT`)是否会导致资源重复分配?
5. 定时器 `micbias_timer` 的作用是延迟关闭麦克风偏置电压,其超时时间 `MICBIAS_DISABLE_TIMER` 的单位是什么?如何计算实际延迟时间?</think>### `mt_accdet_probe` 函数深度解析
此函数是耳机检测(Accdet)驱动的**核心初始化函数**,负责设备注册、资源分配和硬件初始化。以下是关键流程的详细分析:
#### 1. **外部芯片检测(条件编译)**
```c
#if defined(CONFIG_TS3A225E_ACCDET)
if (ts3a225e_i2c_client == NULL) {
return -EPROBE_DEFER; // 延迟探测
}
#endif
```
- **作用**:检测外部耳机芯片(如TI的TS3A225E)是否就绪,未就绪时返回 `-EPROBE_DEFER` 让内核稍后重试。
---
#### 2. **设备注册三重机制**
##### (1) Switch设备(用户空间状态通知)
```c
#if JUST_INPUT_NO_SWITCH
accdet_data.name = "h2w";
switch_dev_register(&accdet_data); // 注册到/sys/class/switch/h2w
#endif
```
- 提供标准接口供Android框架监听耳机状态变化。
##### (2) 字符设备(控制接口)
```c
alloc_chrdev_region(&accdet_devno, 0, 1, ACCDET_DEVNAME); // 分配设备号
cdev_add(accdet_cdev, accdet_devno, 1); // 注册字符设备
device_create(accdet_class, NULL, accdet_devno, NULL, ACCDET_DEVNAME); // 创建设备节点
```
- 创建 `/dev/accdet` 节点,支持 `ioctl` 等控制操作。
##### (3) 输入设备(事件上报)
```c
kpd_accdet_dev = input_allocate_device();
// 设置按键事件
__set_bit(KEY_PLAYPAUSE, kpd_accdet_dev->keybit);
__set_bit(KEY_VOLUMEDOWN, kpd_accdet_dev->keybit);
// 设置开关事件
__set_bit(SW_HEADPHONE_INSERT, kpd_accdet_dev->swbit);
__set_bit(SW_MICROPHONE_INSERT, kpd_accdet_dev->swbit);
input_register_device(kpd_accdet_dev); // 注册输入设备
```
- **事件能力**:
- `EV_KEY`:媒体按键(播放/暂停、音量控制)
- `EV_SW`:插入状态(耳机/麦克风/线缆)
- 设备名称为 `"ACCDET"`,通过 `/dev/input/eventX` 上报事件。
---
#### 3. **核心资源初始化**
##### (1) 工作队列
```c
accdet_workqueue = create_singlethread_workqueue("accdet"); // 单线程工作队列
INIT_WORK(&accdet_work, accdet_work_callback); // 绑定工作回调
```
- 创建专用工作队列处理耳机检测任务。
##### (2) 唤醒锁(Power Management)
```c
accdet_suspend_lock = wakeup_source_register(NULL, "accdet_suspend_lock");
accdet_irq_lock = wakeup_source_register(NULL, "accdet_irq_lock");
accdet_key_lock = wakeup_source_register(NULL, "accdet_key_lock");
```
- 四种唤醒锁:
- `suspend_lock`:阻止系统休眠
- `irq_lock`:中断处理期间保持唤醒
- `key_lock`:按键事件处理期间保持唤醒
- `timer_lock`:定时器运行时保持唤醒
##### (3) 定时器(麦克风偏置控制)
```c
timer_setup(&micbias_timer, disable_micbias, 0); // 初始化定时器
micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER; // 设置超时时间
```
- 用于延迟关闭麦克风偏置电压,节省功耗。
---
#### 4. **中断与首次初始化**
##### (1) 中断注册
```c
pmic_register_interrupt_callback(12, accdet_int_handler); // 中断12:耳机检测
pmic_register_interrupt_callback(13, accdet_eint_int_handler); // 中断13:外部中断
```
- 绑定硬件中断到处理函数。
##### (2) 首次初始化流程
```c
if (g_accdet_first == 1) { // 仅首次执行
eint_accdet_sync_flag = 1; // 允许事件上报
accdet_get_dts_data(); // 从设备树加载参数
accdet_init(); // 硬件初始化
accdet_pmic_Read_Efuse_HPOffset(); // 读取校准值
queue_work(accdet_workqueue, &accdet_work); // 触发首次检测
#ifdef CONFIG_ACCDET_EINT
accdet_setup_eint(dev); // 配置外部中断
#endif
g_accdet_first = 0; // 标记初始化完成
}
```
- **关键操作**:
1. 读取设备树参数(防抖时间、按键阈值等)
2. 初始化硬件寄存器
3. 读取EFUSE中的耳机偏移校准值
4. 主动触发首次耳机检测(模拟插入事件)
5. 配置外部中断引脚(如耳机插孔检测)
---
### 函数执行流程总结
```mermaid
graph TD
A[开始探测] --> B{外部芯片就绪?}
B -- 是 --> C[注册Switch设备]
C --> D[注册字符设备]
D --> E[注册输入设备]
E --> F[创建工作队列]
F --> G[注册唤醒锁]
G --> H[设置麦克风定时器]
H --> I[注册PMIC中断]
I --> J{首次初始化?}
J -- 是 --> K[加载设备树参数]
K --> L[初始化硬件]
L --> M[读取校准值]
M --> N[触发首次检测]
N --> O[配置外部中断]
O --> P[标记初始化完成]
```
### 设计要点
1. **状态隔离**:通过 `g_accdet_first` 确保硬件初始化仅执行一次
2. **事件分层**:
- 输入设备 → 原始事件上报
- Switch设备 → 高层状态抽象
- 字符设备 → 控制接口
3. **功耗优化**:
- 唤醒锁按场景细分(4种类型)
- 麦克风偏置电压延迟关闭
4. **健壮性**:
- 外部芯片未就绪时延迟探测(`-EPROBE_DEFER`)
- 首次检测主动同步硬件状态