2、MTK平台驱动架构:从HAL到内核的完整链路
好,咱们今天聊聊MTK平台的驱动架构。说实话,我刚接触MTK平台时,也被它那套分层体系搞得有点晕。但摸清楚之后你会发现,这套设计其实挺讲究的——它把Android的HAL层、Linux内核驱动、还有硬件总线协议串成了一条完整的链路。
我个人习惯把MTK的驱动架构想象成「三层楼」:
- 顶楼:HAL层,负责跟Android框架打交道
- 中间层:内核驱动框架,负责具体的硬件操作
- 底楼:I2C/SPI总线,负责物理通信
而设备树(DTS)就是连接这三层楼的「施工图纸」。下面咱们一层层拆开看。
2.1 MTK HAL层架构
MTK的HAL层,说白了就是Android HAL规范在MTK芯片上的具体实现。它位于Android Framework和内核驱动之间,起到「翻译官」的作用。
核心结构:
hardware/libhardware/include/hardware/sensors.h:定义传感器HAL的标准接口vendor/mediatek/proprietary/hardware/sensors/:MTK私有的传感器HAL实现- 每个传感器类型对应一个HAL模块(如加速度计、陀螺仪、磁力计)
我记得第一次调试MTK的传感器HAL时,踩过一个坑:MTK的HAL层默认会做一次「数据校准」,但有些第三方传感器芯片自己也有校准逻辑。两者叠加,数据反而乱了。后来我直接在HAL层把MTK的校准关掉,只用芯片自带的。
关键点:MTK HAL层通过 hw_get_module() 加载传感器模块,然后调用 sensors_poll_device_t 结构体中的函数指针来操作硬件。
HAL层的典型调用流程是这样的:
- Framework调用
sensors_poll_device_t->activate()使能传感器 - HAL层通过
ioctl()或read()与内核驱动通信 - 数据从内核驱动上报到HAL层,再通过
sensors_event_t结构体返回给Framework
个人经验:调试HAL层时,建议在 sensors_poll_device_t->poll() 函数里加打印,看数据是否真的从内核上来了。我曾经花了两天查一个「传感器没数据」的问题,最后发现是HAL层的线程优先级太低,被系统调度卡住了。
2.2 Linux内核驱动框架
内核驱动这块,MTK基本遵循标准的Linux驱动模型,但加了一些自家的「调味料」。
标准框架:
- 字符设备驱动:通过
file_operations结构体实现open/read/write/ioctl - 输入子系统:传感器通常注册为
input_dev,通过input_event上报数据 - IIO子系统:MTK部分传感器也支持Industrial I/O框架
你想想看,为什么MTK要用输入子系统?因为Android的EventHub直接监听 /dev/input 节点,用输入子系统可以省去一层适配。嗯,这里要注意:MTK的传感器驱动通常注册为 input_polled_dev,通过定时器轮询读取数据。
我曾经在项目中遇到一个诡异问题:传感器数据偶尔会「跳变」。查了两天,发现是内核驱动的轮询线程和中断处理函数发生了竞态。解决办法很简单——加一把自旋锁。
避坑指南:MTK平台的内核驱动中,ioctl 命令号定义在 mtk_sensor_ioctl.h 里。不同版本的MTK平台,命令号可能不一样。我曾经因为用了旧平台的命令号,导致新平台上的传感器完全无法控制。
2.3 I2C/SPI总线在MTK上的应用
传感器和主控之间的通信,99%的情况靠I2C或SPI。MTK平台对这两种总线的支持很成熟,但有些细节你得注意。
I2C总线:
- MTK的I2C控制器驱动在
drivers/i2c/busses/i2c-mt65xx.c - 支持标准模式(100kHz)和快速模式(400kHz)
- 传感器通常使用7位地址,地址在DTS中配置
SPI总线:
- MTK的SPI控制器驱动在
drivers/spi/spi-mt65xx.c - 支持4种模式(CPOL/CPHA组合)
- 传感器通常使用模式0或模式3
我个人习惯:能用I2C就别用SPI。为什么?I2C的接线少(只需要SDA和SCL),而且MTK的I2C驱动稳定性更好。SPI虽然速度快,但多一根CS线,布局布线时容易出问题。
看一个典型的I2C传感器驱动初始化代码:
static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct sensor_data *data;
int ret;
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
i2c_set_clientdata(client, data);
data->client = client;
/* 检查芯片ID */
ret = i2c_smbus_read_byte_data(client, REG_CHIP_ID);
if (ret < 0) {
dev_err(&client->dev, "failed to read chip id\n");
return ret;
}
/* 注册输入设备 */
data->input_dev = devm_input_allocate_device(&client->dev);
// ... 后续初始化
}
经验之谈:I2C通信失败时,别急着怀疑驱动代码。先用示波器抓一下SDA和SCL的波形。我遇到过好几次,问题出在I2C上拉电阻没焊,或者阻值不对。
2.4 设备树(DTS)配置基础
设备树,就是告诉内核「我的板子上接了哪些外设,它们怎么连的」。MTK平台对DTS的依赖非常重,几乎所有硬件配置都在DTS里完成。
DTS文件结构:
arch/arm64/boot/dts/mediatek/mtxxxx.dtsi:芯片级定义(SoC内部外设)arch/arm64/boot/dts/mediatek/mtxxxx-evb.dts:板级定义(具体开发板)arch/arm64/boot/dts/mediatek/mtxxxx-project.dts:项目级定义(你的产品)
一个典型的I2C传感器DTS节点长这样:
&i2c0 {
status = "okay";
clock-frequency = <400000>; /* 400kHz快速模式 */
sensor@68 {
compatible = "vendor,sensor-name";
reg = <0x68>; /* I2C 7位地址 */
interrupt-parent = <&pio>;
interrupts = <10 IRQ_TYPE_EDGE_FALLING>;
pinctrl-names = "default", "sleep";
pinctrl-0 = <&sensor_pins_default>;
pinctrl-1 = <&sensor_pins_sleep>;
};
};
这里有几个关键点:
compatible:驱动匹配的关键字,必须和驱动中的of_match_table一致reg:I2C设备地址,注意是7位地址左移1位后的值interrupts:中断配置,MTK的GPIO中断号从0开始pinctrl:引脚复用配置,MTK平台必须配,否则I2C不通
曾经踩过的坑:我在一个项目里把I2C地址写成了0xD0(8位地址格式),但内核期望的是7位地址0x68。结果驱动一直probe失败,查了两天才发现是地址格式问题。记住:DTS里I2C地址用7位格式!
另外,MTK平台还有一个特殊的 mediatek,pull-up 属性,用来配置I2C引脚内部上拉。如果你的板子上I2C上拉电阻没焊,可以试试这个属性——但我不建议依赖它,毕竟内部上拉的驱动能力有限。
小结
嗯,这一章的内容就这些。总结一下:
- HAL层是Android和内核的桥梁,MTK有自己的实现套路
- 内核驱动用输入子系统或IIO框架,注意竞态问题
- I2C/SPI是传感器通信的主力,调试时多用示波器
- DTS配置要仔细,地址、中断、pinctrl一个都不能少
下一章咱们会深入讲传感器数据流的具体实现,包括中断上报和轮询策略的选择。到时候我会分享一个实际项目中的性能调优案例,挺有意思的。