在 MTK 平台上做 sensor 适配光距感ALS/PS这套框架是我每次拿到新项目最先要摸清楚的东西。原因也很简单一颗芯片同时承担环境光检测和接近检测屏幕亮度自动调节、通话熄屏亮屏、口袋防误触全都挂在它身上。本文按照 alsps 驱动从设备树匹配到数据上报的完整流程走一遍适合刚接触 MTK 平台驱动开发或者把上层功能调通了却不知道底层到底是怎么运转的读者。1. 先搞清楚 alsps 在整条链路中到底是什么1.1 光距感为什么总被做成一颗芯片在很多方案里环境光传感器ALS和距离传感器PS是封装在同一颗芯片里的。硬件上它们需要的光学窗、PCB 布局基本一致软件上又都依赖 I2C 总线读写寄存器、靠一个 GPIO 中断通知主控状态变化。这些共性决定了把它们合成一颗芯片、共用一套驱动框架是最经济的选择。在 MTK 平台上这套框架通常就叫 alsps驱动里同时管理两个逻辑上的传感器一个上报光线强度单位 lux一个上报距离状态一般用 0 表示遮挡、非 0 表示远离不同项目可能相反。这里要理解的关键点在于虽然物理上是一颗芯片但在内核里它们往往注册成了两种类型的输入事件方便上层分别消费。1.2 数据从传感器到 App 一共经过哪些层完整的链路可以简化成这个方向传感器芯片 - I2C 控制器 - 内核 alsps 驱动 - input 子系统 - HAL 层 - Sensor 服务 - 系统上层应用每一层的职责完全不同。内核驱动负责初始化芯片、设置中断、读寄存器、计算照度和距离并把结果通过 input 事件上报HAL 层MTK 平台一般有一个 sensor HAL 进程把内核的 input 事件转换成标准传感器数据再交给系统服务Sensor 服务屏蔽硬件差异给 App 提供统一的传感器接口上层应用像亮度自动调节这类系统应用不需要关心硬件细节只需要调用标准接口拿 lux 值。经常有人把光距不稳定归结到应用层实际上问题往往出在内核侧的上报逻辑或者 HAL 的事件解析上。所以做驱动的人先把这条链路每一层分别能验证什么搞清楚后面排查会省很多时间。1.3 先把粗链路跑通再谈优化我的习惯是拿到一个新平台第一步不是去看寄存器的每一个位而是先确认驱动能否 probe 成功、能否读到芯片 ID、中断能不能触发、HAL 能不能拿到事件。只有这条粗链路通了才有资格去优化什么阈值、回差、功耗。2. 驱动入口设备树匹配与 probe 初始化流程2.1 设备树节点怎么和驱动对上MTK 很多板级配置都写在 DTS 里。一个光距感设备通常挂在一个 I2C 总线上设备树里会有一个节点包含 compatible、regI2C 地址、中断引脚、供电等信息。内核通过 compatible 字符串匹配到具体的 I2C 驱动。下面是一个常见的结构片段我把关键字段注释了一下i2c2 { alsps39 { /* i2c 地址 0x39 */ compatible mediatek,alsps_xxx; /* 与驱动匹配 */ reg 0x39; interrupt-parent pio; interrupts 10 IRQ_TYPE_LEVEL_LOW; /* 中断引脚配置 */ ldo_default 1; /* 供电开关 */ }; };这里容易踩的第一个坑是 I2C 地址。不同光距感芯片的默认地址不一样同一个芯片也可能因为引脚电平不同有两个地址。设备树里写的地址必须和芯片实际应答的地址一致。排查时最简单的方法是看驱动里的 I2C 读写返回值或者用逻辑分析仪抓包看是否有 ACK 应答。2.2 probe 阶段到底要做什么probe 被调用时驱动要做的事情有固定的顺序。我一般按照下面的步骤推进用设备树解析接口拿到中断号、GPIO 编号、供电信息把 I2C 传输方式初始化好确认芯片所在总线访问正常读芯片 ID 寄存器确认通讯对象真的是预期的光距感芯片申请 GPIO、配置中断触发方式初始化输入设备注册对应的事件轴写入芯片使能配置、设置初始阈值创建内核工作队列准备处理中断里的数据读取。这些步骤的顺序不是随便排的。比如读芯片 ID 必须放在申请中断之前假设总线接错了中断可能永远触发不了直接卡死后续流程而 input 设备注册要尽早因为后面一旦有数据上报input 子系统必须已经就绪。2.3 为什么初始化阶段就要申请中断光距感属于事件型传感器PS 尤其需要实时响应因为通话时屏幕熄灭的延迟稍高一点用户就会有明显感知。大多数 alsps 芯片都支持把近/远状态变化映射到中断引脚主控在中断处理里再去读取最新的距离结果。这样既能保证低延迟又不需要一直占用 I2C 总线。在 probe 阶段申请中断的意义在于驱动可能打开光距感功能之后就立刻有遮挡事件不等整个初始化流程结束中断服务函数也应该能被触发到。很多工程问题表现为“能 probe 成功但没有数据”排查到最后发现是中断配置成了边沿触发而芯片输出的是电平信号导致中断只触发了一次。2.4 一个典型 probe 日志怎么读正常 probe 时内核日志会包括总线探测、芯片 ID 读取成功、中断注册完成、input 设备初始化完成几个节点。如果某个节点缺失就顺着缺失点往前找。我在实际项目中比较常用的是在 dmesg 里搜索驱动名关键字dmesg | grep -i alsps能看到类似“chip id 0xaa, version 0x01”的打印就说明芯片通信已经通了。后面再看到“input: als_ps as /devices/... ”就能确认 input 设备注册成功。这个验证方式比直接从上层日志入手可靠得多。3. 数据链路ADC 采集到 input 上报的完整流程3.1 光感ALS侧是怎么读数据并换算 lux 的ALS 芯片内部有光电二极管光照射在上面会产生对应的电流芯片通过 ADC 把这个电流转换成寄存器里的一串原始值。驱动要做的是在事件触发或定时采样的时机读出原始值再根据芯片手册的公式换算成标准 lux。换算公式一般长这样lux 原始 ADC 值 × 系数 / 曝光时间有时候还会配合查表和分段线性映射。MTK 平台的 alsps 驱动里通常会有校准接口可以写入默认系数。虽然每个项目的屏幕模组不同但这个值一般可以直接用。因为 ALS 对环境变化的响应天然带有滞后DTS 或驱动里常常会配置一个轮询周期。比如每 100ms 读一次 ADC如果数值变化超过一定幅度就更新上报否则沿用上一次值。这也是为什么你会发现屏幕亮度不会瞬间跳变而是平滑过渡。3.2 距离感PS侧的中断式上报逻辑PS 芯片会向外发射红外光然后接收被遮挡物反射回来的红外信号反射强度转换成 ADC 原始值。物体越近反射越强寄存器里的值越大物体远离寄存器值回落。驱动在中断触发时先读状态寄存器确认事件来源再读 PS 原始值和预先设置的高阈值、低阈值做比较。超过高阈值判定为遮挡低于低阈值判定为远离。在这两个判断之间往往会加一段去抖逻辑——连续几次采样都满足条件才会真正上报给 input 子系统。不然人的手指在传感器上方稍微抖一下就会导致通话界面忽亮忽灭。3.3 input 事件是如何发出去的内核侧上报事件非常简单。假设驱动注册了一个 input 设备轴是 ABS_DISTANCE那么上报“遮挡”就像这样input_report_abs(ps_input_dev, ABS_DISTANCE, 0); input_sync(ps_input_dev);上报“远离”则把第二个参数改成 1 或某个最大距离值。而 ALS 的照度一般会用另一个 input 设备或同一设备上的其他轴来上报HAL 层再区分事件类型。要特别注意 input 设备初始化时给各个轴设置的 min、max 范围。很多调试问题出在轴范围没有配置正确驱动上报了一个超出范围的数值input 子系统会直接丢弃或夹取导致 HAL 收到的数据始终不对。这是一个非常隐蔽的坑。3.4 轮询与中断模式的选择逻辑光距感的运行模式大体分两种中断模式和轮询模式。PS 对实时性要求高通常用中断模式ALS 对实时性要求低可以轮询。也有不少方案把 ALS 也做成中断模式——芯片支持在亮度变化超过阈值时主动上报实际上是在寄存器里配置一个迟滞区间光照度跳出这个区间就触发中断。选择逻辑看需求通话熄屏这类功能必须用中断亮度自动调节只需要秒级的响应轮询即可。MTK 平台的 alsps 驱动往往两种模式都支持由节点或配置来切换。我建议在没有功耗压力时统一用中断模式省去一堆轮询调优的麻烦如果有严格的待机功耗指标再把 ALS 切回轮询并适当降低采样频率。4. 距离检测稳定性的核心阈值、回差和去抖4.1 近/远判定为什么必须有两个阈值如果把 PS 检测做成“ADC 值超过 X 判定为遮挡低于 X 判定为远离”实际使用时会发现边界处输出疯狂抖动。原因在于反射强度在临界点附近必然有噪声波动信号稍强一点判远稍弱一点判近用户手机听筒附近就会忽亮忽灭。于是驱动里都会采用迟滞比较遮挡判定阈值高阈值和远离判定阈值低阈值分开设置。比如判定遮挡要求 ADC 值大于 800一旦进入遮挡状态只有当 ADC 值跌到 600 以下才切回远离。中间的 600 到 800 区间是滞回区在这段时间内保持上一次的判定不变。这个“回差”给了信号噪声足够的容忍度是距离检测稳定性的第一道防线。4.2 迟滞区间怎么确定迟滞区间不能拍脑袋定。太窄去不了噪声太宽手离得已经很远了屏幕还不亮体验很差。常见做法是把高阈值定在目标距离对应 ADC 值以上低阈值定在目标距离的约 70% 到 80% 处。举个例子如果想让遮挡判定点在 3cm 处先测得 3cm 时的 ADC 平均值大约 1000那么高阈值可以设 1100低阈值设在 700 左右。调参手段一般是临时打开驱动的调试节点把 ADC 原始值实时打出来然后用真实的遮挡物比如手指或一个接近肤色的小块在不同距离上观察数值分布。理论计算只能给起点最后还是要落在实测上。4.3 软件去抖是在什么层面做的阈值有了回差之后还是可能出现单次误触发。比如通话时拿着东西慢慢接近屏幕传感器偶发一个超过高阈值的尖峰。软件去抖就是要求连续若干次采样都满足同一判定条件才认为状态真的变化了。去抖计数器放在内核驱动的中断处理或轮询函数里。每次读到满足近的条件就把计数加一不满足就清零计数达到 N 次比如 3 次才真正上报 input 事件。这个 N 不能太大否则遮挡后屏幕熄灭会有明显延迟也不能太小否则起不到过滤作用。一般 PS 的去抖次数在 2 到 4 次之间具体结合中断触发频率来算延迟时间。4.4 ALS 的亮度跳变为什么也需要迟滞屏幕亮度自动调节最怕的是亮度值在某个临界点来回跳用户会看到屏幕亮度闪烁。ALS 侧的迟滞逻辑一般有两层硬件层的芯片会把当前照度与配置的迟滞阈值比较只有跳出区间才触发中断软件层驱动里也会做一个缓存值新算出的 lux 和缓存值差得不够大就不上报。这样做的原因是让画面亮度变化曲线平滑。虽然上层可以做时间滤波但底层如果频繁报数上层滤波算法反而会很难处理。所以我始终建议驱动层先做好输出稳定性把干净的数据交上去而不是自己以为“上层会处理”。5. 调试手段与常见问题排查5.1 快速验证光距感功能的几个命令接手 MTK 平台项目时我通常不看复杂日志工具先用最简单的命令快速确认链路是通的。adb shell dmesg | grep -i alsps # 看驱动 probe 和调试打印 adb shell cat /sys/bus/i2c/devices/2-0039/reg_dump # 看原始寄存器如果驱动有这个节点 adb shell getevent -c 10 # 抓 input 事件先用 dmesg 确认驱动加载再用 getevent 手动遮挡、移开传感器观察有没有对应事件。如果 getevent 有事件而 HAL 层没有数据问题就在 HAL 解析上如果 getevent 没有事件问题就在驱动或硬件上。这样一次就能把问题范围砍掉一半。5.2 常见问题一probe 失败怎么排查光距感不工作的第一大类原因是 probe 阶段就挂了。常见表现是 dmesg 里完全没有本驱动的打印或者打印停在“chip id read failed”。这个时候从三个方向查I2C 地址芯片的实际应答地址和设备树上的 reg 是否一致供电时序VDD 和 GPIO 供电的使能动作是否在读取芯片 ID 之前完成总线频率I2C 设置过快导致芯片无响应把时钟往下降试试。我在某个项目里就遇到过设备树地址看着正确但芯片因为外部上拉电阻配置不同实际工作在另一个地址上直接读取会有应答但 ID 不对。最后是读了一遍地址扫描列表才发现真正地址偏移了一位。5.3 常见问题二有中断但不上报事件这里说的“不上报”是指中断处理函数确实执行了寄存器值也确实在读但 getevent 里始终看不到事件。最容易忽略的原因是 input 设备轴的范围没配置对。比如 ABS_DISTANCE 的 min 和 max 都设置成 0或者没有定义这个轴上报时内核直接把事件过滤掉了。解决办法是在驱动 probe 时打印 input 设备支持的轴范围并在上报前打印实际上报值逐一核对。另一个相关问题是 HAL 层在解析 input 设备时用固定的事件类型去匹配如果驱动的上报类型和 HAL 期望不一致即使内核有事件上层也接不到。5.4 常见问题三距离状态乱跳、误触发如果在通话中屏幕忽亮忽灭大部分原因都在阈值和去抖参数。先从驱动节点读实时 ADC 值确认远近两种状态在正常使用下的数值分布再看高低阈值与去抖次数是否留了足够余量。另外还要考虑外部红外干扰源比如在阳光下传感器可能被自然光干扰PS 寄存器读数会偏高这种情况除了加滤波算法还要检查驱动里的工作配置是否符合芯片建议。还有一个容易忽略的点PCB 上如果有其他红外发射设备非常靠近光距感也可能导致 ADC 值持续偏高。这种问题调参数解决不了要回到结构上处理。6. 一些长期在用的实战习惯6.1 给驱动加一个强上报调试接口我习惯在开发早期给驱动加一个强报告调试接口也就是写入一个节点时驱动直接模拟一次遮挡/远离的 input 上报。上层联调不依赖真实遮挡动作可以在硬件还没完全装进结构件的时候就先把 HAL、上层逻辑跑通。这个方法帮我省过不少沟通时间。比如在 sysfs 里加一个属性写 1 模拟遮挡、写 0 模拟远离HAL 开发同事就可以提前验证自己的解析逻辑不必等结构件到位。等到实物整机组起来再拿真实数据去替换模拟值。6.2 定位问题时的顺序惯性任何时候不要只看着代码就断定问题出在哪一层。先按“kernel 事件 - HAL 数据 - 上层效果”的顺序逐层定位每一层验证完再往下走。大多数光距感问题其实不是芯片坏了而是一两个阈值写偏或者中断配置错了方向。把这条链路熟悉到算条件反射的程度再复杂的 sensor 问题也都只是体力活。