分析ov5640.c驱动
// 定义ov5640设备的i2c设备id列表 static const struct i2c_device_id ov5640_id[] = { {"ov5640", 0}, // 设备名称为"ov5640",标识为0 {}, // 空的设备id作为结尾 }; // 定义ov5640设备的设备树id列表 static const struct of_device_id ov5640_dt_ids[] = { { .compatible = "ovti,ov5640" }, // 设备兼容性字符串为"ovti,ov5640" { /* sentinel */ } // 结尾标志 }; // 定义ov5640设备的i2c驱动结构体 static struct i2c_driver ov5640_i2c_driver = { .driver = { .name = "ov5640", // 驱动名称为"ov5640" .of_match_table = ov5640_dt_ids, // 设备树id列表 }, .id_table = ov5640_id, // 设备id列表 .probe_new = ov5640_probe, // 指定驱动的探测函数 .remove = ov5640_remove, // 指定驱动的移除函数 }; // 注册ov5640设备的i2c驱动 module_i2c_driver(ov5640_i2c_driver);
之后我们进入ov5640_probe查看
static int ov5640_probe(struct i2c_client *client) { struct device *dev = &client->dev; // 获取i2c_client对应的设备结构体指针 struct fwnode_handle *endpoint; // 用于处理设备树节点的句柄 struct ov5640_dev *sensor; // 自定义的ov5640设备结构体指针 struct v4l2_mbus_framefmt *fmt; // V4L2媒体总线格式结构体指针,用于描述图像帧格式 u32 rotation; // 用于存储传感器的旋转角度 int ret; // 用于保存函数返回值 sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); // 为ov5640_dev结构体分配内存空间 if (!sensor) return -ENOMEM; sensor->i2c_client = client; // 将i2c_client指针保存到ov5640_dev结构体中 /* * 默认初始化序列将传感器初始化为 * YUV422 UYVY VGA@30fps */ fmt = &sensor->fmt; // 获取ov5640_dev结构体中的v4l2_mbus_framefmt结构体指针 fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; // 设置图像帧的格式为UYVY8_2X8 fmt->colorspace = V4L2_COLORSPACE_SRGB; // 设置图像帧的色彩空间为SRGB fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); // 根据色彩空间设置亮度和色度编码方式 fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; // 设置图像帧的量化范围为全范围 fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); // 根据色彩空间设置传输函数 fmt->width = 640; // 设置图像帧的宽度为640像素 fmt->height = 480; // 设置图像帧的高度为480像素 fmt->field = V4L2_FIELD_NONE; // 设置图像帧的场类型为无场 sensor->frame_interval.numerator = 1; // 设置帧间隔的分子为1 sensor->frame_interval.denominator = ov5640_framerates[OV5640_30_FPS]; // 设置帧间隔的分母为30fps对应的值 sensor->current_fr = OV5640_30_FPS; // 设置当前帧率为30fps sensor->current_mode = &ov5640_mode_data[OV5640_MODE_VGA_640_480]; // 设置当前模式为VGA 640x480 sensor->last_mode = sensor->current_mode; // 将当前模式作为上一个模式保存 sensor->ae_target = 52; // 设置自动曝光的目标值为52 /* 可选的传感器物理旋转指示 */ /* 即为我们在v4l2架构章节添加设备树时在media/i2c/ov5640.txt文件下看见的rotation = <180>;值*/ ret = fwnode_property_read_u32(dev_fwnode(&client->dev), "rotation", &rotation); // 从设备树中读取传感器的旋转角度 if (!ret) { switch (rotation) { // 根据旋转角度进行处理 case 180: sensor->upside_down = true; // 如果旋转角度为180度,则设置倒置标志位为true /* fall through */ case 0: break; default: dev_warn(dev, "%u degrees rotation is not supported, ignoring...\n", rotation); // 如果旋转角度不为0或180,则打印警告信息 } } endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL); // 获取传感器在设备树中的端点节点 if (!endpoint) { dev_err(dev, "endpoint node not found\n"); // 如果节点不存在,则打印错误信息 return -EINVAL; // 返回无效参数错误码 } ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep); // 解析端点节点,将结果保存到ov5640_dev结构体中 fwnode_handle_put(endpoint); // 释放端点节点的句柄 if (ret) { dev_err(dev, "Could not parse endpoint\n"); // 如果解析失败,则打印错误信息 return ret; // 返回错误码 } /* 获取系统时钟(xclk)*/ /* 以下节点都在v4l2设备树章节分析过 */ sensor->xclk = devm_clk_get(dev, "xclk"); // 获取xclk时钟 if (IS_ERR(sensor->xclk)) { dev_err(dev, "failed to get xclk\n"); // 如果获取失败,则打印错误信息 return PTR_ERR(sensor->xclk); // 返回错误码 } sensor->xclk_freq = clk_get_rate(sensor->xclk); // 获取xclk时钟的频率 if (sensor->xclk_freq < OV5640_XCLK_MIN || sensor->xclk_freq > OV5640_XCLK_MAX) { dev_err(dev, "xclk frequency out of range: %d Hz\n", sensor->xclk_freq); // 如果xclk频率超出范围,则打印错误信息 return -EINVAL; // 返回无效参数错误码 } /* 请求可选的电源关闭引脚 */ sensor->pwdn_gpio = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); // 获取电源关闭引脚 if (IS_ERR(sensor->pwdn_gpio)) return PTR_ERR(sensor->pwdn_gpio); // 如果获取失败,则返回错误码 /* 请求可选的复位引脚 */ sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); // 获取复位引脚 if (IS_ERR(sensor->reset_gpio)) return PTR_ERR(sensor->reset_gpio); // 如果获取失败,则返回错误码 /*================================ 以上操作可以统一称为硬件初始化 ===========================================*/ /* *static const struct v4l2_subdev_ops ov5640_subdev_ops = { * .core = &ov5640_core_ops, //用于处理与v4l2子设备核心功能相关的操作 * .video = &ov5640_video_ops, //用于处理与v4l2子设备视频相关的操作。 * .pad = &ov5640_pad_ops, //用于处理与v4l2子设备pad相关的操作 * 在v4l2子设备中,pad是指输入和输出端口。每个子设备可能有多个输入和输出端口(pad),并且每个端口都可以连接到不同的设备或者 其他子设备。pad操作主要涉及对输入和输出端口的配置、查询和控制。 *}; */ v4l2_i2c_subdev_init(&sensor->sd, client, &ov5640_subdev_ops); // 初始化v4l2子设备结构体 sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; // 设置子设备标志位 sensor->pad.flags = MEDIA_PAD_FL_SOURCE; // 设置传感器子设备的pad标志位 sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; // 设置子设备的功能为相机传感器 ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); // 初始化子设备的实体和pad if (ret) return ret; // 如果初始化失败,则返回错误码 ret = ov5640_get_regulators(sensor); // 获取传感器的电源 if (ret) return ret; // 如果获取失败,则返回错误码 mutex_init(&sensor->lock); // 初始化互斥锁 ret = ov5640_check_chip_id(sensor); // 检查传感器的芯片ID if (ret) goto entity_cleanup; // 如果检查失败,则跳转到清理实体的代码 ret = ov5640_init_controls(sensor); // 初始化传感器的控制器 if (ret) goto entity_cleanup; // 如果初始化失败,则跳转到清理实体的代码 ret = v4l2_async_register_subdev_sensor_common(&sensor->sd); // 注册v4l2异步子设备 if (ret) goto free_ctrls; // 如果注册失败,则跳转到释放控制器的代码 return 0; // 返回成功 free_ctrls: v4l2_ctrl_handler_free(&sensor->ctrls.handler); // 释放控制器 entity_cleanup: mutex_destroy(&sensor->lock); // 销毁互斥锁 media_entity_cleanup(&sensor->sd.entity); // 清理子设备的实体 return ret; // 返回错误码 }
通过probe函数我们可以看到几个结构体
struct ov5640_dev { struct i2c_client *i2c_client; // OV5640所连接的I2C客户端设备 struct v4l2_subdev sd; // V4L2子设备结构体 /*v4l2架构中分析过不加以赘述*/ struct media_pad pad; // 媒体子设备的媒体pad /* struct media_pad { 媒体子设备的端口结构体 struct media_gobj graph_obj; 媒体图形对象,必须作为结构体的第一个字段 struct media_entity *entity; 指向该端口所属的媒体实体的指针 u16 index; 端口的索引值 enum media_pad_signal_type sig_type; 端口的信号类型 unsigned long flags; 端口的标志位 }; * 该结构体用于表示媒体子设备的端口信息。每个媒体子设备可以有一个或多个端口,每个端口都由一个media_pad结构体表示。 * 结构体中的字段含义如下: * - graph_obj: 媒体图形对象,必须作为结构体的第一个字段。用于将该端口绑定到媒体图形对象上。 * - entity: 指向该端口所属的媒体实体的指针。 * - index: 端口的索引值,用于唯一标识该端口。 * - sig_type: 端口的信号类型,标识端口所传输的信号类型。 * - flags: 端口的标志位,用于描述端口的一些特性或状态。 */ struct v4l2_fwnode_endpoint ep; // 解析DT端点信息的v4l2_fwnode_endpoint结构体 /* * struct v4l2_fwnode_endpoint { * struct fwnode_endpoint base; // 基于fwnode_endpoint结构体的成员,表示设备节点的端点信息 * enum v4l2_mbus_type bus_type; // 总线类型枚举成员,表示总线类型 * union { * struct v4l2_fwnode_bus_parallel parallel; // 如果总线类型为平行总线,则使用parallel字段 * struct v4l2_fwnode_bus_mipi_csi1 mipi_csi1; // 如果总线类型为MIPI CSI-1,则使用mipi_csi1字段 * struct v4l2_fwnode_bus_mipi_csi2 mipi_csi2; // 如果总线类型为MIPI CSI-2,则使用mipi_csi2字段 * } bus; // 总线类型具体信息的联合体成员 * u64 *link_frequencies; // 指向链接频率数组的指针,链接频率是数据传输的速率 * unsigned int nr_of_link_frequencies; // 链接频率数组中的元素数量 * }; */ struct clk *xclk; // OV5640的系统时钟 u32 xclk_freq; // 系统时钟频率 struct regulator_bulk_data supplies[OV5640_NUM_SUPPLIES]; // OV5640的供电电压 struct gpio_desc *reset_gpio; // 复位引脚 struct gpio_desc *pwdn_gpio; // 电源关闭引脚 bool upside_down; // 是否上下翻转图像 /* 用于保护以下所有成员的互斥锁 */ struct mutex lock; int power_count; // 电源引用计数 struct v4l2_mbus_framefmt fmt; // 当前的视频格式 bool pending_fmt_change; // 是否有待更改的视频格式 const struct ov5640_mode_info *current_mode; // 当前模式 const struct ov5640_mode_info *last_mode; // 上一个模式 enum ov5640_frame_rate current_fr; // 当前帧率 struct v4l2_fract frame_interval; // 帧间隔 struct ov5640_ctrls ctrls; // OV5640的控制项 u32 prev_sysclk, prev_hts; // 上一个系统时钟和水平总线同步时间 u32 ae_low, ae_high, ae_target; // 自动曝光的低、高、目标值 bool pending_mode_change; // 是否有待更改的模式 bool streaming; // 是否正在流式传输 }
struct v4l2_mbus_framefmt { __u32 width; // 帧的宽度 __u32 height; // 帧的高度 __u32 code; // 帧的编码格式 __u32 field; // 帧的扫描方式 __u32 colorspace; // 帧的颜色空间 __u16 ycbcr_enc; // YCbCr编码方式 __u16 quantization; // 量化方式 __u16 xfer_func; // 传输函数 __u16 reserved[11]; // 保留字段 };
我们看到了一个非常重要的函数v4l2_i2c_subdev_init
void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client, const struct v4l2_subdev_ops *ops) { v4l2_subdev_init(sd, ops); // 初始化v4l2_subdev结构体 sd->flags |= V4L2_SUBDEV_FL_IS_I2C; // 设置标志位,表示这是一个I2C子设备 sd->owner = client->dev.driver->owner; // 设置owner为i2c_client的驱动程序owner sd->dev = &client->dev; // 设置dev指针指向i2c_client的dev v4l2_set_subdevdata(sd, client); // 设置v4l2_subdev的subdev_data为i2c_client i2c_set_clientdata(client, sd); // 设置i2c_client的client_data为v4l2_subdev v4l2_i2c_subdev_set_name(sd, client, NULL, NULL); // 设置v4l2_subdev的名称 }
在上文中我们看到了这个结构体现在拿出来分析
static const struct v4l2_subdev_ops ov5640_subdev_ops = { .core = &ov5640_core_ops, //用于处理与v4l2子设备核心功能相关的操作 .video = &ov5640_video_ops, //用于处理与v4l2子设备视频相关的操作。 .pad = &ov5640_pad_ops, //用于处理与v4l2子设备pad相关的操作 };
我们把每个相关操作拿出来分析
static const struct v4l2_subdev_pad_ops ov5640_pad_ops = { .enum_mbus_code = ov5640_enum_mbus_code, //这是一个函数指针,指向ov5640_enum_mbus_code函数,用于枚举支持的视频总线编码格式。 .get_fmt = ov5640_get_fmt, //这是一个函数指针,指向ov5640_get_fmt函数,用于获取当前视频格式设置。 .set_fmt = ov5640_set_fmt, //这是一个函数指针,指向ov5640_set_fmt函数,用于设置视频格式。 .enum_frame_size = ov5640_enum_frame_size, //这是一个函数指针,指向ov5640_enum_frame_size函数,用于枚举支持的帧大小。 .enum_frame_interval = ov5640_enum_frame_interval, //这是一个函数指针,指向ov5640_enum_frame_interval函数,用于枚举支持的帧间隔。 };
static const struct v4l2_subdev_video_ops ov5640_video_ops = { .g_frame_interval = ov5640_g_frame_interval, //该函数用于获取帧间隔信息 .s_frame_interval = ov5640_s_frame_interval, //该函数用于设置帧间隔信息。 .s_stream = ov5640_s_stream, //该函数用于开启和关闭视频流。 };
static const struct v4l2_subdev_core_ops ov5640_core_ops = { .s_power = ov5640_s_power, //用于控制子设备的电源 .log_status = v4l2_ctrl_subdev_log_status, //用于记录子设备的状态信息 .subscribe_event = v4l2_ctrl_subdev_subscribe_event, //用于订阅子设备的事件 .unsubscribe_event = v4l2_event_subdev_unsubscribe, //取消订阅子设备的事件 };
v4l2_async_register_subdev_sensor_common(&sensor->sd); // 注册v4l2异步子设备
int v4l2_async_register_subdev_sensor_common(struct v4l2_subdev *sd) { struct v4l2_async_notifier *notifier; int ret; // 检查子设备是否有有效的设备指针 if (WARN_ON(!sd->dev)) return -ENODEV; // 分配并初始化一个v4l2_async_notifier结构体 notifier = kzalloc(sizeof(*notifier), GFP_KERNEL); if (!notifier) return -ENOMEM; v4l2_async_notifier_init(notifier); // 解析设备节点的传感器相关信息,填充notifier ret = v4l2_async_notifier_parse_fwnode_sensor_common(sd->dev, notifier); if (ret < 0) goto out_cleanup; // 注册子设备的通知器,将其与notifier关联 ret = v4l2_async_subdev_notifier_register(sd, notifier); if (ret < 0) goto out_cleanup; // 注册子设备到异步通知框架中 ret = v4l2_async_register_subdev(sd); if (ret < 0) goto out_unregister; // 将notifier设置为子设备的通知器 sd->subdev_notifier = notifier; return 0; out_unregister: // 注销通知器 v4l2_async_notifier_unregister(notifier); out_cleanup: // 清理通知器资源 v4l2_async_notifier_cleanup(notifier); kfree(notifier); return ret; }