欢迎光临
我们一直在努力

i.MX6ULL 平台 TC358743 HDMI 输入驱动流程源码分析

简介

HDMI 是一种可以同时传输视频、音频和控制信息的数字接口。不同 SoC 对 HDMI 输入/输出的处理方式可能不同,有些 SoC 内部集成 HDMI RX/TX 控制器,有些则需要外接桥接芯片,例如 TC358743 将 HDMI 输入转换为 MIPI CSI-2 输出。从硬件接口上看,HDMI 主要包含以下几类信号:

1、TMDS 差分信号线

常见引脚命名如下:

HDMI_RX0_P / HDMI_RX0_N
HDMI_RX1_P / HDMI_RX1_N
HDMI_RX2_P / HDMI_RX2_N
HDMI_RXC_P / HDMI_RXC_N

TMDS 差分线是 HDMI 中真正传输高速数据的通道,主要承载视频像素数据、音频数据以及部分控制信息。也就是说,真正的 HDMI 图像数据并不是通过 I2C 或 DDC 传输的,而是通过这些 TMDS 差分线传输的。

2、DDC_SCL / DDC_SDA

DDC 是 HDMI 中的一组类似 I2C 的通信通道,主要用于 HDMI 源设备读取接收端的 EDID 信息。

例如电脑作为 HDMI 源设备,会通过 DDC_SCL / DDC_SDA 读取接收端提供的 EDID,从而知道对方支持哪些分辨率、刷新率、颜色格式和音频能力。

此外,HDCP 认证过程中的部分信息交互也会通过 DDC 完成,例如 HDMI 源设备向接收端发送 AKSV,接收端则通过 BKSV、BCAPS、BSTATUS 等 HDCP port 寄存器向源设备反馈认证相关状态。

3、HPD

HPD 全称是 Hot Plug Detect,即热插拔检测信号。它是 HDMI 接收端输出给 HDMI 源设备的信号,用来告诉源设备:“接收端已经连接并准备好了。”当 HDMI 源设备检测到 HPD 有效后,才会通过 DDC 通道读取 EDID,并根据 EDID 信息决定输出什么分辨率和视频格式。

因此,HPD 不是直接传输 EDID 的信号,而是通知源设备可以开始进行 EDID 读取和后续 HDMI 输出流程。

4、5V

HDMI 源设备会向接收端提供一个 5V 检测信号。接收端可以通过这个 5V 判断 HDMI 源设备是否已经插入、是否处于连接状态。

在 TC358743 驱动中,tx_5v_power_present(sd) 就是用来判断 HDMI 源设备的 5V 是否存在。如果检测到 5V,说明 HDMI 源设备已经连接;随后驱动会准备 EDID、配置 DDC/HPD,并打开 HDMI 相关中断,等待源设备读取 EDID 并开始输出 HDMI 信号。

补充说明:Amlogic 的一些 SoC 内部集成了 HDMI RX 模块,可以直接接收 HDMI 的 TMDS 差分信号,主要负责接收和解码 TMDS 信号,解析出 HDMI 视频数据、同步信息以及辅助信息。例如,它可以识别 HSync、VSync、DE 等同步信号,判断当前输入格式是 1080p60、720p60 还是 4K30,也可以解析颜色格式,例如 RGB、YUV444、YUV422。同时,它还会处理 HDMI 音频、InfoFrame,以及 HPD、DDC、EDID、HDCP 等 HDMI 握手和认证相关逻辑。解析完成后,视频数据会被送入 SoC 内部的 VDIN / TVIN / 显示处理链路,供后级模块显示、处理或写入内存。

而 i.MX6ULL 内部没有 HDMI RX 模块,不能直接解析 HDMI 的 TMDS 信号。因此在 i.MX6ULL 平台上,如果需要实现 HDMI 输入,通常需要外接 TC358743 这类 HDMI 转 MIPI CSI-2 桥接芯片。TC358743 负责完成 HDMI 接收、EDID/HPD/DDC/HDCP 处理以及 TMDS 信号解析,并将解析后的视频数据转换成 MIPI CSI-2 输出给 i.MX6ULL。之后 i.MX6ULL 通过 CSI/V4L2 采集链路接收视频流,并通过 DMA 写入 DDR,最终供用户空间 V4L2 应用读取。

一、HDMI 整体链路

HDMI 源设备,比如电脑/播放器
|
| HDMI TMDS 视频信号
| DDC,用于读 EDID 和 HDCP 认证
| HPD,用于热插拔通知
| HDCP,用于受保护内容认证
v
TC358743 HDMI-RX 桥接芯片
|
| HDMI RX -> 内部像素流 -> MIPI CSI-2 TX
v
SoC MIPI CSI-2 RX,前提是硬件存在
|
v
SoC CSI host / capture host
|
| DMA 写入 V4L2 buffer
v
/dev/videoX
|
v
应用程序通过 V4L2 读取帧
|
v
应用程序再送到 framebuffer / LCDIF / DRM 显示

二、硬件层

HDMI 接口上的 TMDS 差分信号线会直接连接到 TC358743 的 HDMI RX 输入脚,用于传输 HDMI 音视频数据。这些是高速物理信号线,一般不需要在设备树里像 GPIO、I2C 那样配置 pinctrl。

HDMI 接口上的 DDC_SCL / DDC_SDA 也会连接到 TC358743 的 HDMI DDC 引脚,用于 HDMI 源设备读取 EDID,以及进行 HDCP 相关访问。注意,这组 DDC 不是 SoC 控制 TC358743 的 I2C 总线。设备树中的:

tc358743@0f

表示 TC358743 挂在 SoC 的某个 I2C 控制器下面,例如 i2c1 或 i2c2,I2C 地址是 0x0f。
这条 I2C 总线才是 SoC 用来配置 TC358743 寄存器的管理通道,例如配置 EDID、HPD、HDCP、CSI-2 输出等。

如果 TC358743 挂在 i2c1 下,那么 i2c1 的 SCL / SDA 引脚需要配置好 pinctrl。TC358743 设备树里的 port / endpoint 描述的不是 HDMI 输入脚,而是 TC358743 的 MIPI CSI-2 输出端。例如:

tc358743_out: endpoint {
remote-endpoint = <&mipi_csi2_in>;
data-lanes = <1 2 3 4>;
clock-lanes = <0>;
};

这表示 TC358743 的 MIPI CSI-2 输出连接到 SoC 的 mipi_csi2_in 输入端。其中:

data-lanes = <1 2 3 4>;
clock-lanes = <0>;

表示使用 4 条 MIPI CSI-2 data lane 和 1 条 clock lane。
这里的 data-lanes 不是 HDMI TMDS lane,也不是 I2C 引脚。

SoC 侧也会有对应的 endpoint:

mipi_csi / csi
|
+– port
|
+– endpoint
|
+– remote-endpoint = <&tc358743_out>;

这样 TC358743 的输出端和 SoC 的输入端就通过 remote-endpoint 连接起来。
后续 V4L2 async / media graph 就可以根据这个关系,把 TC358743 subdev 和 SoC CSI capture host 匹配起来。

三、分配 tc358743_state

当设备树中存在 tc358743@0f,并且它挂在某个 I2C 控制器下面时,内核启动后,I2C Core 会根据设备树创建一个 struct i2c_client。这个 i2c_client 表示:

I2C 总线上地址为 0x0f 的 TC358743 芯片

随后,内核会根据:

compatible = "toshiba,tc358743";

匹配到 TC358743 驱动,并调用:

tc358743_probe(client, id);

tc358743_probe() 的主要作用是:
创建 TC358743 的驱动对象,并把它注册成一个 V4L2 subdev,等待 SoC CSI host 来绑定。


1. 分配私有结构体

state = devm_kzalloc(&client->dev, sizeof(struct tc358743_state), GFP_KERNEL);

这里分配 struct tc358743_state,它是 TC358743 驱动的核心对象,里面保存 I2C 设备、V4L2 subdev、media pad、HDMI 时序、EDID、HPD 等状态。可以理解为:

tc358743_state
|
+– i2c_client
+– sd
+– pad
+– hdl
+– timings
+– delayed_work_enable_hotplug


2. 保存 I2C 设备对象

state->i2c_client = client;

这里的 client 就是设备树里的 tc358743@0f。它里面包含:

client
|
+– addr // I2C 地址,例如 0x0f
+– adapter // 挂在哪个 I2C 控制器下面
+– dev // Linux device 对象

后面驱动读写 TC358743 寄存器时,就是通过 state->i2c_client 找到 I2C 控制器和芯片地址。


3. 初始化成 V4L2 subdev

sd = &state->sd;
v4l2_i2c_subdev_init(sd, client, &tc358743_ops);

这一步会把 TC358743 初始化成一个 I2C 类型的 V4L2 subdev。执行后大致关系是:

state->sd
|
+– ops = &tc358743_ops
| // s_stream、set_edid、get_edid、query_dv_timings 等操作
|
+– dev = &client->dev
|
+– name = "tc358743 …"

以后可以通过 sd 找回整个 tc358743_state:

container_of(sd, struct tc358743_state, sd);


4. 创建 media entity 和 source pad

TC358743 是 HDMI RX 转 MIPI CSI-2 TX 芯片。所以对 SoC 来说,TC358743 有一个视频输出端口。

先设置:

state->pad.flags = MEDIA_PAD_FL_SOURCE;

表示这个 pad 是输出端口。然后调用:

media_entity_pads_init(&sd->entity, 1, &state->pad);

执行后关系如下:

TC358743 v4l2_subdev
|
+– sd->entity
|
+– num_pads = 1
|
+– pads = &state->pad
|
+– entity = &sd->entity
+– index = 0
+– flags = MEDIA_PAD_FL_SOURCE

可以理解为:

TC358743 entity
|
+– source pad
|
+– TC358743 的 MIPI CSI-2 输出端

后面这个 source pad 会通过设备树中的 remote-endpoint,连接到 SoC CSI host 的 sink pad:

TC358743 source pad
|
v
SoC MIPI CSI-2 RX / CSI host sink pad

5. 注册到 V4L2 async 框架

v4l2_async_register_subdev(sd);

这一步会把 TC358743 的 sd 注册到 V4L2 async 框架中。

需要注意:
这一步不会创建 /dev/videoX。
TC358743 只是一个前端 subdev,真正创建 /dev/videoX 的通常是 SoC 侧的 CSI host / capture host 驱动。注册后有两种情况:

情况 1:SoC CSI host 还没 probe
|
+– TC358743 subdev 先进入等待列表
+– 等 CSI host 后面注册 notifier 后再匹配

情况 2:SoC CSI host 已经 probe
|
+– V4L2 async 立即尝试匹配
|
+– 匹配成功后调用 bound 回调
|
+– CSI host 保存 TC358743 的 subdev 指针

例如匹配成功后,CSI host 可能会保存:

csi_dev->sd = subdev;

这样 SoC CSI host 就知道自己的上游设备是 TC358743。


6、结构体

tc358743_probe() 执行完成后,大致得到:

tc358743_state
|
+– i2c_client
| // I2C 地址 0x0f,对应 TC358743 芯片
|
+– sd
| |
| +– ops = &tc358743_ops
| | // TC358743 的 V4L2 操作函数
| |
| +– entity
| |
| +– source pad
| // TC358743 的 MIPI CSI-2 输出端
|
+– timings
| // HDMI 输入时序
|
+– delayed_work_enable_hotplug

 tc358743_probe() 就是把设备树中的 TC358743 芯片,初始化成内核里的 tc358743_state +v4l2_subdev + source pad,并注册到 V4L2 async 框架,等待 SoC CSI host 绑定。

四、采集数据

当 V4L2 async 匹配成功后,CSI host 会保存 TC358743 的 v4l2_subdev 指针:

CSI host
|
+– csi_dev->sd = TC358743 的 v4l2_subdev

这样后面 CSI host 就可以通过 csi_dev->sd 调用 TC358743 的操作函数。

后续流程可以分成两条线理解:


1. HDMI 源设备读取 EDID / HDCP 交互

这条线主要发生在 HDMI 源设备和 TC358743 之间,和 csi_dev->sd 不是同一个动作。

首先,驱动会准备 EDID:

tc358743_s_edid()
|
+– i2c_wr(sd, EDID_RAM + i, …)
|
+– 通过 SoC I2C 控制总线
把 EDID 写入 TC358743 内部 EDID RAM

然后使能 EDID,并准备拉高 HPD:

tc358743_enable_edid()
|
+– schedule_delayed_work(…)
|
+– tc358743_delayed_work_enable_hotplug()
|
+– i2c_wr8_and_or(sd, HPD_CTL, …, MASK_HPD_OUT0)
|
+– 拉高 HPD

当 HDMI 源设备检测到 HPD 高电平后,就会通过 HDMI 接口上的 DDC 通道读取 EDID:

HDMI 源设备
|
+– 通过 HDMI DDC_SCL / DDC_SDA 读取 EDID
|
+– 读取的是 TC358743 内部 EDID RAM
|
+– 根据 EDID 选择分辨率、刷新率、颜色格式等参数

如果输入内容需要 HDCP 保护,HDMI 源设备还会通过 DDC 访问 TC358743 的 HDCP 相关端口:

HDMI 源设备
|
+– 通过 DDC 访问 TC358743 HDCP port
|
+– 读取 BKSV / BCAPS / BSTATUS
|
+– 执行 HDCP authentication

这部分主要由 HDMI 源设备和 TC358743 硬件完成。

不过需要注意:如果驱动中默认配置为:

state->pdata.enable_hdcp = false;

那么 HDCP 功能没有开启,受 HDCP 保护的内容可能会认证失败,表现为黑屏或不输出受保护视频。


2. 应用程序开始采集数据

这条线才会用到:

csi_dev->sd = subdev;

因为 CSI host 在启动视频流时,需要通过这个指针调用 TC358743 的 s_stream() 操作。

应用程序打开视频设备:

应用程序
|
+– open("/dev/videoX")
|
+– 进入 CSI host 的 file_operations

然后申请和准备 V4L2 buffer:

VIDIOC_REQBUFS
|
+– CSI host 分配 / 准备 V4L2 buffer

VIDIOC_QBUF
|
+– 应用程序把空 buffer 放入 CSI host 队列

当应用调用 VIDIOC_STREAMON 后,视频流真正启动:

VIDIOC_STREAMON
|
+– mx6s_vidioc_streamon()
|
+– vb2_streamon()
| |
| +– 启动 CSI host 的 buffer / DMA 队列
|
+– v4l2_subdev_call(csi_dev->sd, video, s_stream, 1)
|
+– 通过 csi_dev->sd 找到 TC358743 subdev
|
+– tc358743_s_stream(sd, 1)
|
+– enable_stream(sd, true)
|
+– i2c_wr32(sd, TXOPTIONCNTRL, 0)
|
+– i2c_wr32(sd, TXOPTIONCNTRL, MASK_CONTCLKMODE)
|
+– i2c_wr8(sd, VI_MUTE, MASK_AUTO_MUTE)

这一步的作用是通过 I2C 配置 TC358743,解除视频 mute,并启动 TC358743 的 MIPI CSI-2 输出。

此时数据流可以理解为:

HDMI 源设备
|
| HDMI TMDS 数据
v
TC358743 HDMI RX
|
| 解码 HDMI 视频
| 输出 MIPI CSI-2
v
SoC MIPI CSI-2 RX / CSI host
|
| DMA 写入 V4L2 buffer
v
/dev/videoX
|
v
应用程序读取视频帧

赞(0)
未经允许不得转载:171主机测评 » i.MX6ULL 平台 TC358743 HDMI 输入驱动流程源码分析
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址