分析ov5640.c驱动

分析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;
}
​
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值