Linux 设备树深度解析之 MediaTek SoC
第一部分:MediaTek SoC架构约定
1.1 涉及模块
SCPSYS (System Control Processor System):系统功耗管理中枢,负责电源域控制、热测量、DVFS、睡眠控制。
PMIC Wrapper:PMIC SPI通信封装器,支持加密与双IO模式。
DEVAPC (Device Access Permission Control):总线矩阵TrustZone安全违规检测。
MediaTek MDP组件:CCORR (Color Correction)、MUTEX (帧同步)、WDMA (Write DMA)。
SVS (Smart Voltage Scaling):基于芯片工艺角的自适应电压调节。
1.2 通用内核模块开发规范
平台驱动模型:所有模块均遵循
platform_driver结构,通过of_match_table与设备树兼容性字符串匹配。资源获取典范:使用
devm_*系列API实现自动资源回收,避免内存泄漏。时钟与电源域管理:严格遵循时钟框架与Generic PM Domain绑定,确保仅在时钟与电源域就绪后访问寄存器。
中断处理原则:使用线程化中断或工作队列处理耗时操作,上半部仅做状态确认与屏蔽。
调试抽象:使用
dev_dbg/dev_err结合dynamic debug,关键路径加tracepoint。
1.3 注释约定
本文所有代码示例均采用 包含@brief,@param,@return,@note等标签,示例:
/** * @brief 初始化SCPSYS电源域 * @param dev 设备指针 * @return 0成功,负值错误码 * @note 调用前需确保时钟已启用 */
第二部分:SCPSYS —— 电源域控制核心
2.1 设计精髓
SCPSYS的本质是SoC电源域的状态机。它不是简单的开关,而是一个集成热测量、电压频率调节、中断滤波的闭环控制系统。SPM(System Power Manager)子模块专门处理MTCMOS电源域控制,允许动态切换每个域的开关状态,需驱动遵循Generic PM domain bindings。
精髓分析:
多SoC兼容性:通过不同的compatible字符串与头文件定义(如
mt8173-power.h),将具体电源域ID抽象为宏,驱动只需维护一张域信息表。时钟前置依赖:硬件要求必须先使能某些时钟才能操作对应电源域(如MT8173需要
mm,mfg等)。这不仅是供电问题,更是时序保障:时钟作为内部同步信号,若未运行则寄存器访问可能锁死总线。可选电源供应器:
*-supply属性将外部regulator与电源域关联,驱动需在域上电时依次开启regulator和内部开关,掉电时反向操作。
2.2 驱动实现要点
核心数据结构:
/** * struct scpsys_domain - MTK SCPSYS电源域描述符 * @name: 域名称,用于调试 * @id: 域ID,对应dt-bindings/power头文件宏 * @ctl_offs: 域控制寄存器偏移 * @clk_names: 必须启用的时钟名称数组 * @num_clks: 时钟数量 * @supply_name: 外部regulator名称,NULL表示无 */ struct scpsys_domain { const char *name; u32 id; u32 ctl_offs; const char * const *clk_names; int num_clks; const char *supply_name; }; /** * struct scpsys - SCPSYS设备私有数据 * @dev: 设备指针 * @base: 映射后的寄存器基址 * @clk_bulk: 域操作时需要的时钟批量管理 * @domains: 域描述符数组 * @num_domains: 域数量 */ struct scpsys { struct device *dev; void __iomem *base; struct clk_bulk_data *clks; const struct scpsys_domain *domains; int num_domains; };驱动初始化流程:
/** * @brief SCPSYS驱动探测函数 * 解析设备树,映射寄存器,初始化电源域并注册到PM域框架。 * @param pdev 平台设备 * @return 0成功,见ERR_PTR */ static int scpsys_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct scpsys *scp; // ... 分配内存,映射寄存器 // 解析domain数据,根据compatible选择表 // 批量获取时钟 // 对每个域调用 pm_genpd_init 并注册 }时钟批量处理技巧:利用devm_clk_bulk_get_optional简化时钟管理,即使某些时钟不存在也不会报错(optional),满足不同SoC差异。
2.3 调试场景
电源域状态检查:读取
/sys/kernel/debug/pm_genpd/pm_genpd_summary查看域状态。时钟使能失败定位:若上电后域无法工作,在域控制函数中加入
dev_dbg打印clk_is_enabled结果,确认所有依赖时钟已运行。外部regulator问题:使用
regulator_summary查看supply是否获取成功并在域上电时激活。
第三部分:PMIC Wrapper —— SPI封装与桥接
3.1 设计精髓
PMIC Wrapper将CPU对SPI总线的直接访问隐藏在SoC内部,提供了一种间接、安全且可加速的通信路径。精髓在于:
抽象层:CPU只需读写wrapper寄存器,wrapper自动转换为SPI事务,避免了CPU处理SPI时序的复杂性。
IP Pairing(桥接):某些SoC内部外设的引脚实际在PMIC上,其信号通过SPI总线透传。这要求wrapper支持额外的桥接寄存器和复位信号。
加密与双IO模式:非标准SPI特性,驱动需根据PMIC能力协商开启。
3.2 驱动实现
关键数据结构:
/** * struct mtk_pwrap - PMIC Wrapper私有数据 * @dev: 设备指针 * @base: 主寄存器地址 * @bridge_base: 桥接寄存器地址(可选) * @rstc: 主模块复位控制 * @bridge_rstc: 桥接模块复位控制 */ struct mtk_pwrap { struct device *dev; void __iomem *base; void __iomem *bridge_base; struct reset_control *rstc; struct reset_control *bridge_rstc; };获取可选桥接资源技巧:
/** * @brief 获取并初始化IP Pairing桥接资源 * @param pwrap 设备数据 * @return 0成功,-ENODEV表示无桥接(继续正常流程) */ static int mtk_pwrap_init_bridge(struct mtk_pwrap *pwrap) { struct device *dev = pwrap->dev; /* 尝试获取第二个reg区域,不存在时返回NULL */ pwrap->bridge_base = devm_platform_ioremap_resource_byname(pdev, "pwrap-bridge"); if (IS_ERR(pwrap->bridge_base)) { if (PTR_ERR(pwrap->bridge_base) == -ENODEV) return 0; /* 桥接可选,不是错误 */ return PTR_ERR(pwrap->bridge_base); } /* 获取桥接复位控制 */ pwrap->bridge_rstc = devm_reset_control_get_optional_exclusive(dev, "pwrap-bridge"); // ... 复位去断言 return 0; }3.3 调试技巧
SPI通信异常:检查wrapper内部状态寄存器(如有),确认FIFO状态、时钟分频是否合理。
桥接问题:若外围设备(通过IP Pairing连接的)不工作,使用
reset_control_assert/deassert重新复位桥接逻辑,并确认PMIC与SoC的桥接寄存器偏移无误。加密交互:在开启加密前,务必确保PMIC支持,并通过wrapper密钥寄存器写入会话密钥。
第四部分:DEVAPC —— 硬件安全哨兵
4.1 设计精髓
DEVAPC是TrustZone体系下总线访问权限控制的违规记录器,不负责阻止访问(通常由TZPC或SIE做防火墙),而是监控并上报非法访问。精髓在于:
被动检测:硬件自动记录违规主设备ID、从设备地址等信息,向CPU发送中断。
安全审计:驱动收到中断后,需读取违规日志寄存器,解析违规细节,可能记录到内核日志或启动对策(如杀死进程、通知安全OS)。
4.2 驱动实现
中断处理框架:
/** * @brief DEVAPC中断处理下半部 * 读取违规寄存器,解析并打印违规信息,根据策略处理。 * @param dev 设备 * @param vio_addr 违规地址 * @param master_id 违规主设备ID * @param domain 安全域 */ static void devapc_report_violation(struct device *dev, u32 vio_addr, u8 master_id, u8 domain) { dev_err(dev, "Security violation: master=%u addr=0x%x domain=%u\n", master_id, vio_addr, domain); /* 可触发进一步措施,如通知TEE */ } static irqreturn_t devapc_isr(int irq, void *data) { struct platform_device *pdev = data; /* 读取状态并清除中断 */ /* 调度工作队列处理违规报告 */ return IRQ_HANDLED; }时钟与时钟命名规范:必须使用clock-names精确匹配,因为IP集成有时钟门控,若未开启则无法记录违规。
4.3 调试与模拟
触发测试违规:利用debugfs导出功能,故意发起非法访问(需root与硬件支持),观察中断是否产生及日志输出。
时钟缺失检查:若无违规日志但系统正常,可能是时钟未开导致模块未工作,通过
clk_summary确认devapc相关时钟开启。
第五部分:多媒体数据通路模块 —— CCORR, MUTEX, WDMA
5.1 共同精髓
这三个模块均属于MediaTek MDP(Media Data Path)或显示子系统,共同特点:
命令队列(GCE)驱动:它们由GCE(Global Command Engine)控制,通过
mediatek,gce-client-reg与mediatek,gce-events声明GCE可访问的寄存器区间和硬件事件信号(SOF/EOF)。这意味着主CPU不直接控制模块的帧级操作,而是配置GCE指令流,由GCE硬件自动执行,实现低延迟、零CPU干预的帧同步。电源域依赖:模块分别位于
MM、DISP等电源域,需通过power-domains属性确保上电。IOMMU集成:WDMA需要IOMMU映射,以保证DMA地址转换安全。
5.2 GCE交互设计分析
驱动需要解析mediatek,gce-client-reg(phandle + subsys id + offset + size),用于GCE寄存器访问的硬件路径配置,并非直接映射到CPU虚拟地址。典型实现:
/** * @brief 解析GCE客户端寄存器配置 * @param np 设备节点 * @param client 输出GCE客户端信息 * @return 0成功 */ static int mtk_mdp_parse_gce_client(struct device_node *np, struct gce_client *client) { struct of_phandle_args args; int ret = of_parse_phandle_with_fixed_args(np, "mediatek,gce-client-reg", 4, 0, &args); if (ret) return ret; client->gce_node = args.np; client->subsys_id = args.args[0]; client->reg_offset = args.args[1]; client->reg_size = args.args[2]; return 0; }GCE事件则用于建立硬件事件到SW回调的映射,但通常由GCE驱动内部维护,客户端驱动只需声明依赖的事件ID,配置SOF/EOF作为命令触发条件。
5.3 实战:MUTEX的SOF/EOF同步精髓
MUTEX作为帧同步触发器,通过硬件信号SOF/EOF串联显示管道各模块。精髓在于:
只用MUTEX产生SOF,后续模块(如RDMA、CCORR)配置为等待SOF才读取寄存器配置的shadow寄存器,保证一帧内配置一致性。
驱动中设置MUTEX的触发间隔(基于vblank)、使能特定SOF输出,完全通过GCE指令包实现,CPU仅需下发指令,无需实时干预。
5.4 调试要点
GCE事件未触发:检查事件ID与硬件手册是否一致;检查MUTEX时钟和电源域是否正常。
DMA页表错误:WDMA发生IOMMU fault时,使用
iommu debug追踪DMA地址映射问题。色彩校正无效果:确认CCORR矩阵配置通过GCE在帧开始前更新,而非直接CPU写入(除非绕过GCE)。
第六部分:SVS —— 自适应电压调节引擎
6.1 设计精髓
SVS的核心是以硬件换能效:根据芯片制造工艺差异、温度等因素,动态计算最优电压点,输出给DVFS驱动或PMIC。其精髓在于:
多Bank架构:每个Bank对应一个功耗域(CPU/GPU/CCI),独立控制。
Efuse校准数据:通过
nvmem-cells获取芯片特定的工艺角参数,无需人工标定,实现硬件自校准。闭环反馈:结合温度传感器(thermal efuse),调整电压以补偿温度漂移。
6.2 驱动实现
数据流设计:
/** * struct svs_bank - 单个SVS bank描述 * @type: 目标电源域类型 (CPU/GPU/CCI) * @dev: 所属SVS设备 * @cal_data: 从Efuse解析的校准值 * @temp_offset: 温度校准偏移 */ struct svs_bank { enum svs_bank_type type; struct device *dev; u32 cal_data; int temp_offset; /* ... */ }; /** * @brief 从nvmem读取校准数据并解析 * @param dev 设备 * @return 0成功 */ static int svs_read_efuse(struct device *dev) { struct nvmem_cell *cell; size_t len; u32 *buf; cell = devm_nvmem_cell_get(dev, "svs-calibration-data"); if (IS_ERR(cell)) return PTR_ERR(cell); buf = nvmem_cell_read(cell, &len); // 根据芯片型号解析buf到具体bank }复位与时钟管理:驱动通过resets和clocks确保硬件在初始化前处于已知状态,SVS主时钟通常来自系统低速时钟,需保持常开(在probe中使能并保持)。
6.3 验证场景
电压输出检查:SVS计算结果可通过debugfs输出,与PMIC实际设定电压对比。
温度影响测试:人为加热,观察SVS计算电压是否随温度补偿增加。
Efuse错误处理:若nvmem cell获取失败,驱动应降级为默认电压表,并打印警告。
第七部分:内核驱动编写规范模板
以下是一个通用的MediaTek平台驱动模板,融合了上述模块的最佳实践:
/** * @file mediatek-example.c * @brief MediaTek示例设备驱动 * * 适用模块: 通用平台驱动框架,展示设备树绑定、PM域、时钟等标准操作。 */ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/pm_domain.h> #include <linux/clk.h> #include <linux/reset.h> #include <linux/of.h> #include <linux/interrupt.h> #include <linux/io.h> /** * struct mtk_example_dev - 设备私有数据 * @dev: 内核设备指针 * @base: 寄存器基址 * @clk: 主时钟 * @domain: 电源域句柄(可选) */ struct mtk_example_dev { struct device *dev; void __iomem *base; struct clk *clk; struct generic_pm_domain *pd; }; /** * @brief 设备探测入口 * @param pdev 平台设备 * @retval 0 成功 * @retval -ENOMEM 内存不足 * @retval -EINVAL 设备树配置错误 * * 该函数完成: * - 映射寄存器 * - 获取并启用时钟 * - 申请电源域(由PM域核心自动管理) * - 注册中断 * - 导出debugfs节点 */ static int mtk_example_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtk_example_dev *ex; struct resource *res; int irq, ret; dev_info(dev, "probe start\n"); ex = devm_kzalloc(dev, sizeof(*ex), GFP_KERNEL); if (!ex) return -ENOMEM; ex->dev = dev; platform_set_drvdata(pdev, ex); /* 1. 映射寄存器 */ ex->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ex->base)) return PTR_ERR(ex->base); /* 2. 时钟获取(可选标准化) */ ex->clk = devm_clk_get_optional(dev, "main"); if (IS_ERR(ex->clk)) return dev_err_probe(dev, PTR_ERR(ex->clk), "failed to get main clock\n"); ret = clk_prepare_enable(ex->clk); if (ret) return ret; /* 3. 电源域绑定(通常由PM GenPD框架处理,此处示例手动获取) */ if (of_property_read_bool(dev->of_node, "power-domains")) { ex->pd = dev_pm_domain_attach(dev, true); if (IS_ERR(ex->pd)) { ret = PTR_ERR(ex->pd); goto err_clk; } } /* 4. 中断注册 */ irq = platform_get_irq(pdev, 0); if (irq < 0) { ret = irq; goto err_pd; } ret = devm_request_threaded_irq(dev, irq, NULL, mtk_example_irq_handler, IRQF_ONESHOT, "mtk-example", ex); if (ret) goto err_pd; return 0; err_pd: if (ex->pd) dev_pm_domain_detach(ex->pd, true); err_clk: clk_disable_unprepare(ex->clk); return ret; } /** * @brief 设备移除回调 * @param pdev 平台设备 * @return 0 */ static int mtk_example_remove(struct platform_device *pdev) { struct mtk_example_dev *ex = platform_get_drvdata(pdev); if (ex->pd) dev_pm_domain_detach(ex->pd, true); clk_disable_unprepare(ex->clk); dev_info(&pdev->dev, "removed\n"); return 0; } /** * @brief 中断处理函数 * @param irq 中断号 * @param dev_id 设备私有数据 * @return IRQ_HANDLED * * 读取并清除中断状态,记录事件。 */ static irqreturn_t mtk_example_irq_handler(int irq, void *dev_id) { struct mtk_example_dev *ex = dev_id; dev_dbg(ex->dev, "IRQ triggered\n"); return IRQ_HANDLED; } /* 设备匹配表 */ static const struct of_device_id mtk_example_of_match[] = { { .compatible = "mediatek,mt8173-example" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mtk_example_of_match); /* 平台驱动结构 */ static struct platform_driver mtk_example_drv = { .probe = mtk_example_probe, .remove = mtk_example_remove, .driver = { .name = "mtk-example", .of_match_table = mtk_example_of_match, }, }; module_platform_driver(mtk_example_drv); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MediaTek Example Driver"); MODULE_AUTHOR("Your Name <email>");规范要点:
dev_err_probe处理-EPROBE_DEFER场景,延迟打印。使用
devm_*自动释放资源,简化错误路径。电源域多数情况下通过
pm_genpd框架自动附着,此处展示手动附加方法用于调试。中断使用线程化(threaded),避免中断上下文耗时。
第八部分:场景调试方法论
8.1 驱动初始化失败(-EPROBE_DEFER 循环)
现象:驱动反复重试,无法成功探测。诊断:
# 查看驱动加载日志 cat /sys/kernel/debug/devices_deferred
若列表中出现你的设备,说明缺少资源。检查:
时钟提供者是否就绪(
clk_summary确认父时钟存在)电源域控制器是否加载(
pm_genpd_summary)复位控制器是否提供(
reset_control框架)IOMMU是否可用(
iommu_present)
8.2 悬挂或系统崩溃在寄存器访问
可能原因:时钟未使能或电源域未上电导致总线死锁。调试手段:
在
probe中加入dev_dbg打印iomap前后,确认基址有效。在关键寄存器读写前后,添加
mb()内存屏障并打印值,隔离崩溃点。使用硬件断点或JTAG捕捉异常,确认是在访问哪个模块地址时故障。
8.3 GCE命令执行异常
问题:MDP/显示模块帧触发不正常。排查:
检查
mediatek,gce-client-reg中subsys ID与GCE驱动中的映射是否匹配。通过GCE调试节点(如
mts_cmdqdebugfs)查看命令队列状态。确认SOF事件源(MUTEX)是否已正确产生事件,可临时在MUTEX驱动中加打印。
验证IOMMU映射:WDMA等模块若使用
iommus属性,查看DMA映射是否因权限问题失败。
8.4 安全违规无日志(DEVAPC)
检查点:
确认
clocks和clock-names在设备树中匹配硬件,通过clk_summary查看 clock 是否有enable_count。测试违规理论存在(如调试器强制访问保护区域),观察中断计数器:
cat /proc/interrupts | grep devapc是否增加。中断触发模式必须与硬件匹配(通常为 level-high/low),错误配置会导致中断丢失。
8.5 电压调节异常(SVS)
分析步骤:
读取Efuse内容,打印原始数据,与数据手册对照校准位定义。
若使用外部regulator,检查
svs-bank输出的电压值是否应用于 Buck(通过 I2C/SPI 命令记录)。温度传感器链:
t-calibration-data是否完整?使用独立thermal zone读取温度,对比SVS内部补偿。
