上一篇文章已经讲解过摄像头数据流动环节的第一部分------sensor驱动,接下来进行剩余部分的讲解,本篇文章进行DPHY驱动和CSI驱动加载过程的解析:
Sensor (OV13855) ➔ DPHY (物理层) ➔ CSI Host (控制器) ➔ VICAP(CIF) ➔ ISP
驱动源码解析:DPHY(物理层)驱动
一、设备树节点:
arch/arm64/boot/dts/rockchip/rk3588s.dtsi
arch/arm64/boot/dts/rockchip/rk3588s-orangepi-5-camera1.dtsi
这个 csi2_dphy0 节点属于普通节点(非I2C、SPI节点等),因此内核在启动时,会根据其生成一个platform_device结构体。
cpp
&csi2_dphy0 {
status = "disabled";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
mipi_in_ucam0: endpoint@0 {
reg = <0>;
remote-endpoint = <&ov13850_out2>;
data-lanes = <1 2>;
};
mipi_in_ucam1: endpoint@1 {
reg = <1>;
remote-endpoint = <&ov13855_out2>;
data-lanes = <1 2>;
};
};
port@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
csidphy0_out: endpoint@0 {
reg = <0>;
remote-endpoint = <&mipi2_csi2_input>;
};
};
};
};
/* dphy0 full mode */
csi2_dphy0: csi2-dphy0 {
compatible = "rockchip,rk3568-csi2-dphy";
rockchip,hw = <&csi2_dphy0_hw>;
status = "disabled";
};
二、驱动代码详细解析
drivers/phy/rockchip/phy-rockchip-csi2-dphy.c
2.1 驱动注册、匹配流程
cpp
static const struct of_device_id rockchip_csi2_dphy_match_id[] = {
{
.compatible = "rockchip,rk3568-csi2-dphy", // 设备树与此项匹配
.data = &rk3568_dphy_drv_data,
},
{
.compatible = "rockchip,rk3588-csi2-dcphy",
.data = &rk3588_dcphy_drv_data,
},
{
.compatible = "rockchip,rv1106-csi2-dphy",
.data = &rv1106_dphy_drv_data,
},
{}
};
struct platform_driver rockchip_csi2_dphy_driver = {
.probe = rockchip_csi2_dphy_probe, // driver 和 driver 匹配时调用
.remove = rockchip_csi2_dphy_remove,
.driver = {
.name = "rockchip-csi2-dphy",
.pm = &rockchip_csi2_dphy_pm_ops,
.of_match_table = rockchip_csi2_dphy_match_id, // 匹配表
},
};
static int __init rockchip_csi2_dphy_init(void)
{
return platform_driver_register(&rockchip_csi2_dphy_driver); // 注册 platform_driver
}
程序在入口函数中注册了一个platform_driver结构体 ,当platform_driver结构体的 ".of_match_table" 与设备树节点的 "compatible" 匹配时,probe函数会被调用。
2.2 probe函数详解
详细的注释已经添加在函数中了:
cpp
/**
* rockchip_csi2_dphy_probe - Rockchip CSI2 DPHY 驱动初始化函数
* @pdev: 平台设备指针
*
* 功能:初始化 MIPI CSI-2 物理层(DPHY)接口,用于连接摄像头传感器
* 返回值:0表示成功,负数表示错误码
*/
static int rockchip_csi2_dphy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev; // 获取设备指针
const struct of_device_id *of_id; // 设备树匹配ID
struct csi2_dphy *csi2dphy; // DPHY设备结构体
struct v4l2_subdev *sd; // V4L2子设备
const struct dphy_drv_data *drv_data; // 驱动私有数据(不同芯片的配置)
int ret; // 返回值
/* ========== 1. 分配内存并保存设备指针 ========== */
// 为 DPHY 设备结构体分配内存(驱动卸载时会自动释放)
csi2dphy = devm_kzalloc(dev, sizeof(*csi2dphy), GFP_KERNEL);
if (!csi2dphy)
return -ENOMEM; // 内存不足错误
csi2dphy->dev = dev; // 保存设备指针,方便后续使用
/* ========== 2. 匹配设备树并获取驱动配置数据 ========== */
// 从设备树中查找匹配的设备(根据 compatible 属性)
of_id = of_match_device(rockchip_csi2_dphy_match_id, dev);
if (!of_id)
return -EINVAL; // 设备树中没有匹配的节点
// 获取该设备的私有配置数据(如寄存器地址、时钟名称等)
drv_data = of_id->data;
csi2dphy->drv_data = drv_data;
/* ========== 3. 获取 PHY 索引编号 ========== */
// 从设备树别名中获取 PHY 编号(本节点为 csi2-dphy0,因此取到0)
csi2dphy->phy_index = of_alias_get_id(dev->of_node, drv_data->dev_name);
// 如果索引无效(小于0或超出最大值),默认设为0
if (csi2dphy->phy_index < 0 || csi2dphy->phy_index >= PHY_MAX)
csi2dphy->phy_index = 0;
/* ========== 4. 根据厂商类型连接硬件资源 ========== */
// 判断 DPHY 的厂商类型,采用不同的初始化方法,获取硬件资源(本节点是非Samsung的dphy,因此走else分支)
if (csi2dphy->drv_data->vendor == PHY_VENDOR_SAMSUNG) {
/* Samsung DCPHY(双模PHY,支持CSI和DSI) */
ret = rockchip_csi2_dphy_attach_samsung_phy(csi2dphy);
// 设置 Samsung 特定的 DPHY 参数
csi2dphy->dphy_param = rk3588_dcphy_param;
} else {
/* Rockchip 标准 MIPI DPHY */
ret = rockchip_csi2_dphy_attach_hw(csi2dphy);
}
// 如果硬件资源获取失败(如寄存器映射、时钟获取失败)
if (ret) {
dev_err(dev,
"csi2 dphy hw can't be attached, register dphy%d failed!\n",
csi2dphy->phy_index);
return -ENODEV; // 设备不存在错误
}
/* ========== 5. 初始化 V4L2 子设备 ========== */
sd = &csi2dphy->sd; // 获取子设备指针
// 初始化互斥锁,防止多线程并发访问冲突
mutex_init(&csi2dphy->mutex);
// 初始化 V4L2 子设备,注册操作函数集
v4l2_subdev_init(sd, &csi2_dphy_subdev_ops);
// 标记该设备有独立的设备节点(可在 /dev/ 下访问)
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
// 生成设备名称,如 "rockchip-csi2-dphy0"
snprintf(sd->name, sizeof(sd->name),
"rockchip-csi2-dphy%d", csi2dphy->phy_index);
sd->dev = dev; // 关联设备指针
/* ========== 6. 保存平台设备私有数据 ========== */
// 将 V4L2 实体指针存到平台设备的私有数据中
// 后续可通过 platform_get_drvdata() 获取
platform_set_drvdata(pdev, &sd->entity);
/* ========== 7. 初始化 Media Controller ========== */
// Media Controller 用于管理视频数据流(摄像头→PHY→ISP→内存)
ret = rockchip_csi2dphy_media_init(csi2dphy);
if (ret < 0)
goto detach_hw; // 失败则跳转到资源清理
/* ========== 8. 启用运行时电源管理 ========== */
// 允许设备在不使用时自动进入省电模式
pm_runtime_enable(&pdev->dev);
/* ========== 9. 打印成功信息 ========== */
dev_info(dev, "csi2 dphy%d probe successfully!\n", csi2dphy->phy_index);
return 0; // 初始化成功
/* ========== 错误处理:清理已分配的资源 ========== */
detach_hw:
// 销毁互斥锁
mutex_destroy(&csi2dphy->mutex);
// 根据厂商类型调用对应的硬件资源释放函数
if (csi2dphy->drv_data->vendor == PHY_VENDOR_SAMSUNG)
rockchip_csi2_dphy_detach_samsung_phy(csi2dphy); // 释放Samsung PHY资源
else
rockchip_csi2_dphy_detach_hw(csi2dphy); // 释放标准DPHY资源
return 0; // 注意:这里应该返回ret(错误码),但代码写的是0(可能是bug)
}
2.3 重点解析
如果看过前面的sensor驱动程序解析,就会很熟悉这里的三大步了,即:分配、设置、注册。这里就不详细解释了,快速过一下。
1. 分配subdev
cpp
csi2dphy = devm_kzalloc(dev, sizeof(*csi2dphy), GFP_KERNEL);
分配csi2dphy结构体,顺带分配sundev结构体。
2. 设置subdev
cpp
sd = &csi2dphy->sd;
mutex_init(&csi2dphy->mutex);
v4l2_subdev_init(sd, &csi2_dphy_subdev_ops);
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
snprintf(sd->name, sizeof(sd->name),
"rockchip-csi2-dphy%d", csi2dphy->phy_index);
sd->dev = dev;
这里重点设置了subdev的操作函数集 csi2_dphy_subdev_ops。
3. 注册subdev
cpp
ret = rockchip_csi2dphy_media_init(csi2dphy);
以下是函数原型:
cpp
/**
* rockchip_csi2dphy_media_init - 初始化 DPHY 的 Media Controller 接口
* @dphy: DPHY 设备结构体指针
*
* 功能:
* 1. 配置 media pads(数据端口)
* 2. 注册异步通知器,等待摄像头传感器连接
* 3. 注册为 V4L2 子设备
*
* 返回值:0表示成功,负数表示错误码
*/
static int rockchip_csi2dphy_media_init(struct csi2_dphy *dphy)
{
int ret;
/* ========== 1. 配置 Media Pads(数据端口) ========== */
/* 配置 SOURCE pad(数据输出端)*/
dphy->pads[CSI2_DPHY_RX_PAD_SOURCE].flags =
MEDIA_PAD_FL_SOURCE | // 标记为数据源(输出数据)
MEDIA_PAD_FL_MUST_CONNECT; // 必须连接到下一级设备(如ISP)
/* 配置 SINK pad(数据接收端)*/
dphy->pads[CSI2_DPHY_RX_PAD_SINK].flags =
MEDIA_PAD_FL_SINK | // 标记为数据接收端(接收摄像头数据)
MEDIA_PAD_FL_MUST_CONNECT; // 必须连接到上一级设备(摄像头传感器)
/* 设置设备功能类型:视频接口桥接器(连接摄像头和ISP) */
dphy->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
/* 初始化 media entity 的 pads(注册2个端口:SINK和SOURCE) */
ret = media_entity_pads_init(&dphy->sd.entity,
CSI2_DPHY_RX_PADS_NUM, // pads数量(通常是2)
dphy->pads); // pads数组
if (ret < 0)
return ret; // 初始化失败
/* ========== 2. 初始化异步通知器 ========== */
/* 初始化异步通知器(用于等待摄像头传感器设备就绪) */
v4l2_async_notifier_init(&dphy->notifier);
/*
* 解析设备树中的 endpoint,查找连接的摄像头传感器
* - 从设备树的 port 0 中解析 endpoint
* - 为每个 endpoint 分配一个 sensor_async_subdev 结构体
* - 调用 rockchip_csi2_dphy_fwnode_parse 解析具体配置
*/
ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(
dphy->dev, // 设备指针
&dphy->notifier, // 通知器
sizeof(struct sensor_async_subdev), // 每个子设备的结构体大小
0, // 解析 port 0(摄像头输入端口)
rockchip_csi2_dphy_fwnode_parse); // 解析回调函数
if (ret < 0)
return ret; // 解析失败(可能设备树没配置摄像头)
/* ========== 3. 注册异步通知器 ========== */
/* 将通知器关联到子设备 */
dphy->sd.subdev_notifier = &dphy->notifier;
/* 设置通知器的操作函数(当摄像头设备就绪时会调用这些函数) */
dphy->notifier.ops = &rockchip_csi2_dphy_async_ops;
/* 注册异步子设备通知器 */
ret = v4l2_async_subdev_notifier_register(&dphy->sd, &dphy->notifier);
if (ret) {
dev_err(dphy->dev,
"failed to register async notifier : %d\n", ret);
/* 清理通知器资源 */
v4l2_async_notifier_cleanup(&dphy->notifier);
return ret;
}
/* ========== 4. 注册为 V4L2 异步子设备 ========== */
/*
* 将 DPHY 注册为 V4L2 子设备
* - 等待上级设备(如 ISP)连接
* - 等待下级设备(摄像头传感器)连接
* - 所有设备连接后,整个视频管道才能工作
*/
return v4l2_async_register_subdev(&dphy->sd);
}
这个函数的流程基本与上一篇文章的sensor异步注册subdev过程一致,因此不做过多讲解,简单看下注释即可。
三、细节小结
看到这里,有人可能会发现之前的sensor注册过程和dphy注册过程有一点差别,具体如下:
3.1 sensor:
cpp
v4l2_async_notifier_init(notifier);
ret = v4l2_async_notifier_parse_fwnode_sensor_common(sd->dev, notifier);
ret = v4l2_async_subdev_notifier_register(sd, notifier);
ret = v4l2_async_register_subdev(sd);
3.2 dphy:
cpp
v4l2_async_notifier_init(&dphy->notifier);
ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(dphy->dev, &dphy->notifier,
sizeof(struct sensor_async_subdev), 0,
rockchip_csi2_dphy_fwnode_parse);
dphy->notifier.ops = &rockchip_csi2_dphy_async_ops; // 主要区别!!!
ret = v4l2_async_subdev_notifier_register(&dphy->sd, &dphy->notifier);
v4l2_async_register_subdev(&dphy->sd);
这两个驱动虽然都使用异步通知器,但用途完全不同 ,所以是否需要设置 ops 也不同。
3.3 关键区别:谁在等待谁?
1. rockchip_csi2dphy_media_init(DPHY驱动)
cpp
dphy->notifier.ops = &rockchip_csi2_dphy_async_ops; // 设置了 ops
作用:DPHY 等待摄像头传感器连接
css
数据流向:摄像头 → DPHY → ISP
DPHY 的角色:
- 我是"中间设备",需要等待上游的摄像头
- 摄像头连接成功时,我需要执行一些操作(通过 ops 回调)
ops 的具体作用:
cpp
static const struct v4l2_async_notifier_operations rockchip_csi2_dphy_async_ops = {
/* 当摄像头设备绑定成功时调用 */
.bound = rockchip_csi2_dphy_notifier_bound,
/* 当所有等待的设备都就绪时调用 */
.complete = rockchip_csi2_dphy_notifier_complete,
};
为什么需要 ops?
-
摄像头连接后,DPHY 需要建立数据链路(media link)
-
需要配置数据通道数(data-lanes)
-
需要通知下游设备(ISP)准备接收数据
2. v4l2_async_register_subdev_sensor_common(sensor驱动)
cpp
// 注意:这里没有设置 notifier->ops!
v4l2_async_notifier_init(notifier);
作用:摄像头传感器等待镜头/闪光灯等外围设备
css
数据流向:镜头 → 传感器 → DPHY
传感器的角色:
- 我是"最上游设备",只需要等待镜头、闪光灯等配件
- 这些配件连接与否不影响数据流,只是功能增强
- 不需要复杂的回调处理
为什么不需要 ops?
-
镜头(lens)、闪光灯(flash)只是可选配件
-
它们连接后,传感器只需要记录引用,不需要额外操作
-
V4L2 框架会自动处理基本的绑定逻辑
3.4 设备树对应关系
1. DPHY 的设备树(有 ops)
cpp
csi2_dphy0 {
ports {
port@0 { // ← 这个 port 会被 parse_fwnode_endpoints_by_port 解析
mipi_in_ucam1: endpoint@1 {
reg = <1>;
remote-endpoint = <&ov13855_out2>;
data-lanes = <1 2>;
};
};
};
};
解析后的行为:
-
v4l2_async_notifier_parse_fwnode_endpoints_by_port 发现 &ov13855_output
-
添加到等待列表
-
当 OV13855 驱动加载后,触发 rockchip_csi2_dphy_async_ops.bound
-
在 bound 回调中建立 media link
2. sensor的设备树(无 ops)
cpp
ov13855: ov13855@36 {
compatible = "ovti,ov13855";
/* 可选的镜头配件 */
lens-focus = <&dw9714_p1>; // ← parse_fwnode_sensor_common 解析这个
port {
ov13855_out2: endpoint {
remote-endpoint = <&mipi_in_ucam1>;
data-lanes = <1 2>;
};
};
};
解析后的行为:
-
v4l2_async_notifier_parse_fwnode_sensor_common 查找 lens-focus
-
添加到等待列表
-
镜头/闪光灯加载后,自动绑定,不需要额外回调
-
传感器只需要通过 sd->lens 访问镜头即可
3.5 代码执行流程对比
场景1:DPHY 等待摄像头(需要 ops)
css
1. DPHY 驱动加载
↓
2. rockchip_csi2dphy_media_init()
↓
3. 解析设备树,发现需要等待 ov13855
↓
4. 注册通知器,设置 ops
↓
5. OV13855 驱动加载
↓
6. 触发 DPHY 的 ops.bound 回调
↓
【在回调中执行】:
- 获取摄像头的数据通道配置
- 创建 media link: ov13855:0 → dphy:0
- 配置 DPHY 的接收参数
↓
7. 所有设备就绪,触发 ops.complete
↓
【在回调中执行】:
- 注册整个 media controller 图
- 通知用户空间可以使用摄像头
ops.bound 实际代码示例:
cpp
static int rockchip_csi2_dphy_notifier_bound(
struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_subdev *asd)
{
struct csi2_dphy *dphy = container_of(notifier, struct csi2_dphy, notifier);
struct sensor_async_subdev *s_asd = container_of(asd, ...);
/* 保存传感器引用 */
dphy->sensor_sd = sd;
/* 获取数据通道配置 */
dphy->lane_num = s_asd->lanes;
/* 创建 media link */
return media_create_pad_link(&sd->entity, 0,
&dphy->sd.entity, CSI2_DPHY_RX_PAD_SINK,
MEDIA_LNK_FL_ENABLED);
}
场景2:传感器等待镜头(不需要 ops)
css
1. OV13855 驱动加载
↓
2. v4l2_async_register_subdev_sensor_common()
↓
3. 解析设备树,发现 lens-focus = <&dw9714_p1>
↓
4. 注册通知器,不设置 ops(使用默认行为)
↓
5. DW9714 镜头驱动加载
↓
6. V4L2 框架自动绑定(默认行为)
↓
【框架自动执行】:
- 设置 sd->lens = dw9714_subdev
↓
7. 传感器可以直接使用 sd->lens 控制镜头对焦
为什么不需要 ops? 因为 V4L2 框架已经提供了默认的绑定逻辑:
cpp
// V4L2 框架内部的默认绑定代码(简化版)
static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_subdev *asd)
{
// 如果是镜头设备
if (asd->match.fwnode.fwnode == lens_fwnode) {
notifier->sd->lens = sd; // 自动保存镜头引用
}
// 如果是闪光灯设备
if (asd->match.fwnode.fwnode == flash_fwnode) {
notifier->sd->flash = sd; // 自动保存闪光灯引用
}
// 不需要用户提供额外的 ops
return 0;
}
3.6 什么时候需要设置 ops?
需要设置 ops 的情况:
-
需要建立 media link(如 DPHY ↔ ISP)
-
需要配置硬件参数(如 DPHY 的数据通道数)
-
需要通知下游设备(如告诉 ISP 准备接收数据)
-
需要复杂的初始化流程(如 ISP 的多级管道配置)
不需要设置 ops 的情况:
-
只是简单的设备引用(如传感器 ↔ 镜头)
-
框架已提供默认行为(如 lens、flash 的自动绑定)
-
不涉及数据流配置(镜头不参与数据传输)
驱动源码解析:CSI(控制器)驱动
一、设备树节点
同理,内核在启动时,会根据其生成一个platform_device结构体。
cpp
&mipi2_csi2 {
status = "disabled";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
mipi2_csi2_input: endpoint@1 {
reg = <1>;
remote-endpoint = <&csidphy0_out>;
};
};
port@1 {
reg = <1>;
#address-cells = <1>;
#size-cells = <0>;
mipi2_csi2_output: endpoint@0 {
reg = <0>;
remote-endpoint = <&cif_mipi_in2>;
};
};
};
};
mipi2_csi2: mipi2-csi2@fdd30000 {
compatible = "rockchip,rk3588-mipi-csi2";
reg = <0x0 0xfdd30000 0x0 0x10000>;
reg-names = "csihost_regs";
interrupts = <GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "csi-intr1", "csi-intr2";
clocks = <&cru PCLK_CSI_HOST_2>;
clock-names = "pclk_csi2host";
resets = <&cru SRST_P_CSI_HOST_2>, <&cru SRST_CSIHOST2_VICAP>;
reset-names = "srst_csihost_p", "srst_csihost_vicap";
status = "disabled";
};
二、驱动代码解析
drivers/media/platform/rockchip/cif/mipi-csi2.c
CSI2的驱动注册、匹配流程和之前的dphy驱动注册、匹配流程完全一致,这里不过多赘述。下面主要看prob函数中是否还是那三大步(分配、设置、注册),验证我们所了解的"套路"。
2.1 分配subdev
cpp
csi2 = devm_kzalloc(&pdev->dev, sizeof(*csi2), GFP_KERNEL);
分配csi2结构体时,顺带分配subdev
2.2 设置subdev
cpp
v4l2_subdev_init(&csi2->sd, &csi2_subdev_ops);
v4l2_set_subdevdata(&csi2->sd, &pdev->dev);
csi2->sd.entity.ops = &csi2_entity_ops;
csi2->sd.dev = &pdev->dev;
csi2->sd.owner = THIS_MODULE;
csi2->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
ret = strscpy(csi2->sd.name, DEVICE_NAME, sizeof(csi2->sd.name));
ret = csi2_media_init(&csi2->sd);
设置subdev中的一系列成员。
cpp
static int csi2_media_init(struct v4l2_subdev *sd)
{
struct csi2_dev *csi2 = sd_to_dev(sd);
int i = 0, num_pads = 0;
num_pads = csi2->match_data->num_pads;
for (i = 0; i < num_pads; i++) {
csi2->pad[i].flags = (i == CSI2_SINK_PAD) ?
MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
}
csi2->pad[RK_CSI2X_PAD_SOURCE0].flags =
MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT;
csi2->pad[RK_CSI2_PAD_SINK].flags =
MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
// .......
return media_entity_pads_init(&sd->entity, num_pads, csi2->pad);
}
初始化entity的pad端口(entity与subdev是一一对应的)。
2.3 注册subdev
cpp
ret = csi2_notifier(csi2);
static int csi2_notifier(struct csi2_dev *csi2)
{
struct v4l2_async_notifier *ntf = &csi2->notifier;
int ret;
v4l2_async_notifier_init(ntf);
ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(csi2->dev,
&csi2->notifier,
sizeof(struct v4l2_async_subdev), 0,
csi2_parse_endpoint);
if (ret < 0)
return ret;
csi2->sd.subdev_notifier = &csi2->notifier;
csi2->notifier.ops = &csi2_async_ops;
ret = v4l2_async_subdev_notifier_register(&csi2->sd, &csi2->notifier);
if (ret) {
v4l2_err(&csi2->sd,
"failed to register async notifier : %d\n",
ret);
v4l2_async_notifier_cleanup(&csi2->notifier);
return ret;
}
ret = v4l2_async_register_subdev(&csi2->sd);
return ret;
}
和之前的套路一样:初始化异步通知器、解析设备树port、注册异步通知器、异步注册subdev。