一、概述

在编写 MIPI 摄像头驱动之前,需要先了解 Media 子系统的链路关系,这里我就不介绍了,需要的看我之前的笔记:Linux Media 子系统链路分析

理解 Media 子系统链路关系后,会发现 ISP 不论是在摄像头端,还是集成在 SOC 中,驱动程序都是差不多的。多观察一下开发板中的其他案例,便会明白 MIPI 摄像头驱动部分的程序就是一个 I2C 驱动程序,而 D-PHY 部分的驱动相关厂商已经编写好了,我们只需要通过 I2C 通道配置好摄像头相关的寄存器即可。

在 linux 中,摄像头驱动是基于 V4L2 框架进行实现的,所以在编写驱动之前,还需明白 V4L2 的框架是怎么回事,需要了解的可以看其他大佬的博客,这里我就不深入介绍了,主要内容还是程序的编写。V4L2 的框架如下图所示:

二、测试环境

  1. 开发板:RV1126
  2. ARM Linux 版本:4.19.111
  3. 驱动芯片:RN6752V1
  4. MIPI 通道的数据格式:YUV

三、添加驱动文件

  1. 创建驱动文件

    在 SDK 的 sdk/kernel/drivers/media/i2c/ 目录下添加 rn6752.c 文件,内容如下

    /* 驱动名称 */
    #define DRIVER_NAME "rn6752" /* 驱动版本信息 */
    #define DRIVER_VERSION KERNEL_VERSION(0, 0x00, 0x01) /**
    * @brief 系统检测到与该驱动程序匹配的 I2C 设备时
    * @param client 指向 I2C 客户端结构体的指针。该结构体包含了有关 I2C 设备的信息,如设备地址、总线信息等
    * @param id 指向 I2C 设备 ID 的指针。这个参数用于在多个相同类型的设备中进行区分和匹配
    * @return 返回初始化结构
    */
    static int rn6752_probe(struct i2c_client *client, const struct i2c_device_id *id)
    {
    struct device *dev = &client->dev; /* 打印驱动的版本信息的函数,其作用相当于 printk() 函数 */
    dev_info(dev, "driver version: %02x.%02x.%02x", DRIVER_VERSION >> 16,
    (DRIVER_VERSION & 0xff00) >> 8, DRIVER_VERSION & 0x00ff); return 0;
    } /**
    * @brief 当设备驱动被删除释放时,执行此函数
    * @param client 指向 I2C 客户端结构体的指针。该结构体包含了有关 I2C 设备的信息,如设备地址、总线信息等
    * @return 返回操作结果
    */
    static int rn6752_remove(struct i2c_client *client)
    { return 0;
    } /* 设备树节点匹配表格,与设备树中的节点描述信息一样时,匹配成功 */
    #if IS_ENABLED(CONFIG_OF)
    static const struct of_device_id rn6752_of_match[] = {
    { .compatible = "richnex,rn6752v1" },
    { /* sentinel */ },
    };
    MODULE_DEVICE_TABLE(of, rn6752_of_match);
    #endif /* 设备 ID 表格,与设备名称一样时匹配成功,主要用于低版本linux内核的匹配方式 */
    static const struct i2c_device_id rn6752_match_id[] = {
    { "richnex,rn6752v1", 0 },
    { /* sentinel */ },
    }; /* 描述和注册一个 I2C 设备驱动程序 */
    static struct i2c_driver rn6752_i2c_driver = {
    .driver = { /* 驱动程序信息的子结构体 */
    .name = DRIVER_NAME, /* 设置驱动程序的名称 */
    .of_match_table = of_match_ptr(rn6752_of_match), /* 用于匹配设备树节点的表格 */
    },
    .probe = rn6752_probe, /* I2C 设备被检测到时进行设备初始化和处理 */
    .remove = rn6752_remove, /* I2C 设备从系统中移除时进行清理和资源释放 */
    .id_table = rn6752_match_id, /* I2C 设备 ID 表格,平台设备匹配方式之一,用设备和驱动的名称进行匹配 */
    }; static int __init sensor_mod_init(void)
    {
    /* 注册 I2C 驱动程序 */
    return i2c_add_driver(&rn6752_i2c_driver);
    } static void __exit sensor_mod_exit(void)
    {
    /* 注销 I2C 驱动程序 */
    i2c_del_driver(&rn6752_i2c_driver);
    } device_initcall_sync(sensor_mod_init); /* 注册一个设备初始化函数 */
    module_exit(sensor_mod_exit); /* 注销一个设备初始化函数 */ MODULE_AUTHOR("jiaozhu <cn_jiaozhu@qq.com>");
    MODULE_DESCRIPTION("RN6752 CMOS Image Sensor driver");

    注意: 这里的代码是 I2C 驱动的基础,有不明白的小伙伴可以参看相关资料,也可以看我之前写的一些驱动笔记

  2. 添加编译选项

    在 SDK 的 sdk/kernel/drivers/media/i2c/Makefile 文件中添加 obj-$(CONFIG_VIDEO_RN6752V1) += rn6752.o,如下图所示

  3. 添加配置信息

    在 SDK 的 sdk/kernel/drivers/media/i2c/Kconfig 文件中添加配置信息,内容如下

    config VIDEO_RN6752V1
    tristate "Richnex RN6752V1 sensor support"
    depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
    depends on MEDIA_CAMERA_SUPPORT
    help
    This is a Video4Linux2 sensor driver for the Sony
    RN6752V1 camera. To compile this driver as a module, choose M here: the
    module will be called rn6752v1.
  4. 打开 RN6752 驱动的编译选项

    在对应的.config 文件或 make menuconfig 图形配置界面中打开 RN6752 的驱动程序,完后会得到 CONFIG_VIDEO_RN6752V1 = y 的信息

  5. 添设备树信息

    在 SDK 的 sdk/kernel/arch/arm/boot/dts/rv1126-alientek.dts 文件的 I2C 节点中添加 RN6752 解码芯片的设备信息,内容如下

    rn6752: rn6752@2c {
    compatible = "richnex,rn6752v1";
    reg = <0x2c>;
    clocks = <&cru CLK_MIPICSI_OUT>;
    clock-names = "xvclk";
    power-domains = <&power RV1126_PD_VI>;
    pinctrl-names = "rockchip,camera_default";
    pinctrl-0 = <&mipicsi_clk0>;
    avdd-supply = <&vcc_avdd>;
    dovdd-supply = <&vcc_dovdd>;
    dvdd-supply = <&vcc_dvdd>;
    pwdn-gpios = <&gpio1 RK_PD4 GPIO_ACTIVE_HIGH>;
    reset-gpios = <&gpio4 RK_PA0 GPIO_ACTIVE_LOW>;
    rockchip,camera-hdr-mode = <0>;
    rockchip,camera-module-index = <0>;
    rockchip,camera-module-facing = "front";
    rockchip,camera-module-name = "abcd";
    rockchip,camera-module-lens-name = "a-bc-d";
    port {
    ucam_out0: endpoint {
    remote-endpoint = <&mipi_in_ucam0>;
    data-lanes = <1 2 3 4>;
    };
    };
    };
  6. 完成以上内容后,准备工作基本完成了,编译并重写烧写内核程序后,会在启动日志中打印版本信息,如下图所示

四、probe 函数实现

  1. 内存申请

    首先需要申请一块内存,用于存放 RN6752 的结构体

    /* 为设备分配内存,并将内存与设备进行关联。在驱动程序退出时,内存会自动被释放。被称为“设备内存管理” */
    rn6752 = devm_kzalloc(dev, sizeof(*rn6752), GFP_KERNEL);
    if (!rn6752)
    {
    dev_err(dev, "Memory control request failed\n");
    return -ENOMEM;
    }
  2. 获取设备树配置的信息

    /* 获取设备树信息 */
    ret = rn6752_device_tree_info(rn6752);
    if (ret != 0)
    return -EINVAL;
  3. 保留驱动的所有帧格式,方便在其他函数中使用

    /* rn6752_mipi_framesizes 是 rn6752 mipi 通信支持的所有帧格式 */
    rn6752->framesize_cfg = rn6752_mipi_framesizes;
    rn6752->cfg_num = ARRAY_SIZE(rn6752_mipi_framesizes);
    /* 获取摄像头传感器支持的图像帧格式 */
    rn6752_get_default_format(rn6752, &rn6752->format);
    rn6752->frame_size = &rn6752->framesize_cfg[0]; /* 设置帧大小 */
    rn6752->format.width = rn6752->framesize_cfg[0].width; /* 设置宽度 */
    rn6752->format.height = rn6752->framesize_cfg[0].height; /* 设置高度 */
    rn6752->fps = DIV_ROUND_CLOSEST(rn6752->framesize_cfg[0].max_fps.denominator,
    rn6752->framesize_cfg[0].max_fps.numerator); /* 设置最大帧速率 */
  4. 初始化互斥锁

    mutex_init(&rn6752->lock);
  5. 注册一个V4L2子设备

    v4l2_i2c_subdev_init(sd, client, &rn6752_subdev_ops);
  6. 绑定硬件操作函数

    ret = rn6752_initialize_controls(rn6752);
    if (ret)
    {
    dev_info(dev, "V4l2 control menu initialization failed");
    goto err_destroy_mutex;
    }

    注意: 此函数的作用是绑定硬件部分的控制功能,也就是或可以通过相应的设备节点更改设备的引荐参数,比如亮度、对比度、饱和度、色调等。

    可以通过命令 v4l2-ctl -d /dev/v4l-subdevX --list-ctrls 查看,如下图所示:

  7. 关联 V4l2 子设备内部操作函数

    /* 关联子设备的内部操作函数,用于打开摄像头操作 */
    sd->internal_ops = &rn6752_subdev_internal_ops;
    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | /* 设备有对应的设备节点文件,可以通过该文件进行访问 */
    V4L2_SUBDEV_FL_HAS_EVENTS; /* 设备具有事件机制,可以作为事件源供其他驱动程序使用 */
  8. 打开电源

    mipi 设备一般都有复位引脚和休眠引脚,工作是需要对相应的引脚进行操作

    /* 打开设备的电源 */
    ret = __rn6752_power_on(rn6752);
    if (ret)
    goto err_free_handler;
  9. 读取设备产品号

    通过读取 mipi 设备的产品 id 进行对比,判断初始化时,设备是否连接,最终确定时候正常加载驱动

    /* 通过读取 RN6752 的产品 ID 判断设备是否存在 */
    ret = rn6752_check_sersor_id(rn6752);
    if (ret)
    goto err_power_off;
  10. 创建 media 设备

    rn6752->pad.flags = MEDIA_PAD_FL_SOURCE;            /* 表明该子设备是媒体管道中的源设备,即数据的起始点 */
    sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; /* 表示该实体是一个相机传感器 */
    ret = media_entity_pads_init(&sd->entity, 1, &rn6752->pad); /* 初始化 sd->entity 媒体实体的 pads(端口)属性 */
    if (ret < 0) {
    dev_err(dev, "Media pad port initialization failed\n");
    goto err_power_off;
    }
  11. 将传感器子设备添加到V4L2异步子系统中

    这里需要注意一下,在执行此函数之前,在系统中是不会生成 media 设备节点的

    /* 根据设备树提供的信息,判断摄像头的方向 */
    memset(facing, 0, sizeof(facing));
    if (strcmp(rn6752->module_facing, "back") == 0)
    facing[0] = 'b';
    else
    facing[0] = 'f'; /* 名称格式如 m00_f_rn6752 1-002c:bus */
    snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
    rn6752->module_index, facing, DRIVER_NAME, dev_name(sd->dev));
    /* 将传感器子设备添加到V4L2异步子系统中,以便能够与其他V4L2组件进行交互。 */
    /* 它会自动设置子设备的相关回调函数,并与异步框架进行适当的关联,以管理传感器的采集和控制操作 */
    ret = v4l2_async_register_subdev_sensor_common(sd);
    if (ret)
    {
    dev_err(dev, "Failed to add sensor to V4L2 asynchronous subsystem\n");
    goto err_clean_entity;
    }

    注意: 在 RV1126 开发板中,创建 media 设备时,需要注意 mipi 连接的通道,连接 mipi

    csi0 时,需要获取设备的数据格式,否则会出现内存地址错误。解决此错误的方法是在 rn6752_get_fmt 函数中默认一个类型值即可,如下图所示:

  12. 进入休眠模式

    完成初始化后,使传感器进入休眠模式

     /* 完成初始化后,使 rn6752 进入睡眠模式 */
    rn6752->power_on = false;
    if (!IS_ERR(rn6752->pwdn_gpio))
    gpiod_set_value_cansleep(rn6752->pwdn_gpio, 1);

到此整个驱动程序就算完成了,由于内容较多,所以分成了两篇笔记,相关的控制函数的实现请看下一篇笔记,下面是 MIPI 驱动框架的程序

五、程序源码

rn6752.c

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/media.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/videodev2.h>
#include <linux/version.h>
#include <linux/rk-camera-module.h>
#include <media/media-entity.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-image-sizes.h>
#include <media/v4l2-mediabus.h>
#include <media/v4l2-subdev.h> /* 驱动名称 */
#define DRIVER_NAME "rn6752" /* 驱动版本信息 */
#define DRIVER_VERSION KERNEL_VERSION(0, 0x00, 0x01) #define RN6752_XVCLK_CLOCK 37125000 #define RN6752_PIXEL_RATE (120 * 1000 * 1000) /* RN6752 产品 ID 的寄存器地址及默认值,用于判断设备是否存在 */
#define REG_SC_CHIP_ID_H 0xFE
#define REG_SC_CHIP_ID_L 0xFD
#define SENSOR_ID(_msb, _lsb) ((_msb) << 8 | (_lsb))
#define RN6752_ID 0x2601
#define REG_NULL 0xFFFF /* Array end token */ struct rn6752_pixfmt {
u32 code;
/* Output format Register Value (REG_FORMAT_CTRL00) */
struct sensor_register *format_ctrl_regs;
}; struct sensor_register {
u16 addr;
u8 value;
}; struct rn6752_framesize {
u16 width;
u16 height;
struct v4l2_fract max_fps;
u16 max_exp_lines;
const struct sensor_register *regs;
}; static const char * const rn6752_supply_names[] = {
"dovdd", /* Digital I/O power */
"avdd", /* Analog power */
"dvdd", /* Digital core power */
}; #define RN6752_NUM_SUPPLIES ARRAY_SIZE(rn6752_supply_names) struct rn6752 {
struct i2c_client *client; /* I2C 客户端结构体的指针,表示 RN6752 摄像头所连接的 I2C 设备 */ struct v4l2_subdev subdev; /* V4L2 子设备结构体,表示 RN6752 摄像头的 V4L2 子设备 */
struct v4l2_ctrl_handler ctrls; /* V4L2 控制器句柄,用于管理 RN6752 摄像头的各种控制器 */
struct v4l2_ctrl *link_frequency; /* V4L2 链路频率控制器,用于设置和获取 RN6752 摄像头的链路频率 */
struct v4l2_fwnode_endpoint bus_cfg; /* RN6752 摄像头的总线配置,包括数据线数量、数据线极性等信息 */
struct v4l2_mbus_framefmt format; /* V4L2 子设备帧格式结构体,表示 RN6752 摄像头支持的图像帧格式 */ struct media_pad pad; /* 媒体子系统中的媒体端口结构体,表示与 RN6752 摄像头相关联的媒体端口 */ struct gpio_desc *pwdn_gpio; /* RN6752 摄像头的 pwdn GPIO 引脚 */
struct gpio_desc *reset_gpio; /* RN6752 摄像头的复位 GPIO 引脚 */
struct regulator_bulk_data supplies[RN6752_NUM_SUPPLIES]; /* RN6752 摄像头使用的电源资源 */
struct mutex lock; /* 互斥锁,用于保护并发访问摄像头设备 */ unsigned int fps; /* RN6752 摄像头的帧率 */
const struct rn6752_framesize *frame_size; /* RN6752 摄像头支持的帧大小列表的指针 */
const struct rn6752_framesize *framesize_cfg; /* 当前 RN6752 摄像头所选择的帧大小的指针 */
unsigned int cfg_num; /* RN6752 摄像头支持的帧大小的数量 */
int streaming; /* RN6752 摄像头是否正在流式传输 */
bool power_on; /* RN6752 摄像头的电源状态 */ u32 module_index; /* RN6752 摄像头的模块索引 */
const char *module_facing; /* RN6752 摄像头面向的方向 */
const char *module_name; /* RN6752 摄像头的名称 */
const char *len_name; /* RN6752 摄像头的镜头名称 */ struct clk *xvclk; /* RN6752 摄像头使用的时钟资源 */
unsigned int xvclk_frequency; /* RN6752 摄像头的 xvclk 频率 */
}; static const struct rn6752_framesize rn6752_mipi_framesizes[] = { }; static const s64 link_freq_menu_items[] = {
594000000,
}; static const struct rn6752_pixfmt rn6752_formats[] = {
{
.code = MEDIA_BUS_FMT_UYVY8_2X8,
}
}; static const char * const rn6752_test_pattern_menu[] = {
"Disabled",
"Vertical Color Bars",
}; /**
* @brief 从结构体中的成员指针获取包含该成员的结构体指针
*/
static inline struct rn6752 *to_rn6752(struct v4l2_subdev *sd)
{
return container_of(sd, struct rn6752, subdev);
} /**
* @brief 打开摄像头电源管理
* @param rn6752 摄像头结构体
* @return 返回摄像头电源设置结果
*/
static int __rn6752_power_on(struct rn6752 *rn6752)
{
int ret;
struct device *dev = &rn6752->client->dev; /* 打印一条调试信息 */
dev_dbg(dev, "%s(%d)\n", __func__, __LINE__); /* 启用所有的供电器 */
if (!IS_ERR(rn6752->supplies)) {
ret = regulator_bulk_enable(RN6752_NUM_SUPPLIES, rn6752->supplies);
if (ret < 0)
dev_info(dev, "Failed to enable regulators\n"); usleep_range(20000, 50000);
} /* 首先将其引脚置为低电平(0),延迟一段时间,然后再将其引脚置为高电平(1),再次延迟一段时间 */
if (!IS_ERR(rn6752->reset_gpio)) {
gpiod_set_value_cansleep(rn6752->reset_gpio, 0);
usleep_range(2000, 5000);
gpiod_set_value_cansleep(rn6752->reset_gpio, 1);
usleep_range(2000, 5000);
} /* 使 RN6752 退出睡眠模式 */
if (!IS_ERR(rn6752->pwdn_gpio)) {
gpiod_set_value_cansleep(rn6752->pwdn_gpio, 0);
usleep_range(2000, 5000);
} /* 将 xvclk 的频率设置为 RN6752_XVCLK_CLOCK */
if (!IS_ERR(rn6752->xvclk)) {
ret = clk_set_rate(rn6752->xvclk, RN6752_XVCLK_CLOCK);
if (ret < 0)
dev_warn(dev, "Failed to set xvclk rate %d MHz\n", RN6752_XVCLK_CLOCK); ret = clk_get_rate(rn6752->xvclk);
if (ret != RN6752_XVCLK_CLOCK)
dev_warn(dev, "xvclk mismatched\n"); /* 准备并启用 xvclk */
ret = clk_prepare_enable(rn6752->xvclk);
if (ret < 0)
{
dev_err(dev, "Failed to enable xvclk\n");
return -EINVAL;
} } rn6752->power_on = true;
return 0;
} /**
* @brief 关闭摄像头电源管理
* @param rn6752 摄像头结构体
* @return 返回摄像头电源设置结果
*/
static void __rn6752_power_off(struct rn6752 *rn6752)
{
dev_info(&rn6752->client->dev, "%s(%d)\n", __func__, __LINE__);
if (!IS_ERR(rn6752->xvclk))
clk_disable_unprepare(rn6752->xvclk);
if (!IS_ERR(rn6752->supplies))
regulator_bulk_disable(RN6752_NUM_SUPPLIES, rn6752->supplies);
if (!IS_ERR(rn6752->pwdn_gpio))
gpiod_set_value_cansleep(rn6752->pwdn_gpio, 1);
if (!IS_ERR(rn6752->reset_gpio))
gpiod_set_value_cansleep(rn6752->reset_gpio, 0); rn6752->power_on = false;
} /**
* @brief 从 I2C 通道中读取一个字节的数据
* @param client I2C 结构体指针
* @param reg 寄存器地址
* @param val 读取的数据
*/
static int rn6752_read(struct i2c_client *client, u8 reg, u8 *val)
{
int ret = 0;
struct i2c_msg msg[2]; msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = &reg;
msg[0].len = 1; msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = val;
msg[1].len = 1; ret = i2c_transfer(client->adapter, msg, 2);
if (ret != 2) {
dev_err(&client->dev, "rn6752 read reg:0x%x failed !\n", reg);
return -1;
} return 0;
} /**
* @brief 获取摄像头传感器的默认格式
* @param rn6752 摄像头传感器设备
* @param format 视频帧格式
*/
static void rn6752_get_default_format(struct rn6752 *rn6752,
struct v4l2_mbus_framefmt *format)
{
format->width = rn6752->framesize_cfg[0].width; /* 设置默认宽度 */
format->height = rn6752->framesize_cfg[0].height; /* 设置默认高度 */
format->colorspace = V4L2_COLORSPACE_SRGB; /* 设置默认色彩空间为标准的 sRGB 色彩空间 */
format->code = rn6752_formats[0].code; /* 设置默认编码格式 */
format->field = V4L2_FIELD_NONE; /* 设置默认场模式 */
} /**
* @brief 获取摄像头的图像格式
* @param sd v4l2_subdev 结构体指针
* @param cfg v4l2_subdev_pad_config 结构体指针
* @param fmt v4l2_subdev_format 结构体指针
*/
static int rn6752_get_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *fmt)
{
printk(KERN_INFO "rn6752_get_fmt................................................\n");
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
} else {
fmt->format.width = 1280;
fmt->format.height = 720;
fmt->format.code = MEDIA_BUS_FMT_SRGGB10_1X10;
fmt->format.field = V4L2_FIELD_NONE;
fmt->reserved[0] = 10;
}
return 0;
} /* 定义V4L2子设备的内部操作函数 */
static const struct v4l2_subdev_internal_ops rn6752_subdev_internal_ops = {
// .open = rn6752_open,
}; /* 结构体定义了控制器操作函数的回调函数 */
static const struct v4l2_ctrl_ops rn6752_ctrl_ops = {
// .s_ctrl = rn6752_s_ctrl,
}; static const struct v4l2_subdev_core_ops rn6752_subdev_core_ops = {
// .ioctl = rn6752_ioctl, /* 用于处理V4L2控制命令 */
// #ifdef CONFIG_COMPAT
// .compat_ioctl32 = rn6752_compat_ioctl32, /* 在启用32位兼容模式时,用于处理32位兼容的V4L2控制命令 */
// #endif
// .s_power = rn6752_power, /* 用于控制子设备的电源状态 */
}; static const struct v4l2_subdev_video_ops rn6752_subdev_video_ops = {
// .s_stream = rn6752_s_stream, /* 用于启动或停止视频流的函数 */
// .g_mbus_config = rn6752_g_mbus_config, /* 用于获取当前媒体总线配置的函数 */
// .g_frame_interval = rn6752_g_frame_interval, /* 用于获取当前帧间隔的函数 */
// .s_frame_interval = rn6752_s_frame_interval, /* 用于设置帧间隔的函数 */
}; static const struct v4l2_subdev_pad_ops rn6752_subdev_pad_ops = {
// .enum_mbus_code = rn6752_enum_mbus_code, /* 用于枚举所有支持的媒体总线编码和格式 */
// .enum_frame_size = rn6752_enum_frame_sizes, /* 用于枚举所有支持的图像尺寸 */
// .enum_frame_interval = rn6752_enum_frame_interval, /* 用于枚举所有支持的帧率和帧间隔 */
.get_fmt = rn6752_get_fmt, /* 用于获取当前端口的图像格式 */
// .set_fmt = rn6752_set_fmt, /* 用于设置当前端口的图像格式 */
}; static const struct v4l2_subdev_ops rn6752_subdev_ops = {
.core = &rn6752_subdev_core_ops, /* 定义了V4L2子设备核心操作的函数指针。包括日志记录、事件订阅和取消订阅、控制命令等 */
.video = &rn6752_subdev_video_ops, /* 定义了V4L2子设备视频操作的函数指针。包括流开关、格式和帧间隔等参数的获取和设置 */
.pad = &rn6752_subdev_pad_ops, /* 定义了V4L2子设备端口操作的函数指针。包括数据编解码格式、帧尺寸和帧间隔等相关参数的获取和设置 */
}; /**
* @brief 用于解析设备树配置的信息和引脚等
* @param rn6752 摄像头结构体
* @return 返回执行结果
*/
static int rn6752_device_tree_info(struct rn6752 *rn6752)
{
struct device *dev = &rn6752->client->dev;
struct device_node *node = dev->of_node; /* 设备树节点 */
int ret;
unsigned int i; /* 获取设备树中 “rockchip,camera-module-index” 节点的信息,用于摄像头模块索引 */
ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
&rn6752->module_index);
/* 获取设备树中 “rockchip,camera-module-facing” 节点的信息,用于摄像头面向的方向 */
ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,
&rn6752->module_facing);
/* 获取设备树中 “rockchip,camera-module-name” 节点的信息,用于摄像头的名称 */
ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,
&rn6752->module_name);
/* 获取设备树中 “rockchip,camera-module-lens-name” 节点的信息,用于摄像头的镜头名称 */
ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,
&rn6752->len_name);
if (ret) {
dev_err(dev, "could not get module information!\n");
return -EINVAL;
} /* 获取设备 PWDN 和复位的引脚句柄 */
rn6752->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW);
if (IS_ERR(rn6752->pwdn_gpio))
dev_warn(dev, "Failed to get pwdn-gpios, maybe no use\n");
rn6752->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(rn6752->reset_gpio))
dev_warn(dev, "Failed to get reset-gpios, maybe no use\n"); /* 获取摄像头的时钟资源 */
rn6752->xvclk = devm_clk_get(dev, "xvclk");
if (IS_ERR(rn6752->xvclk)) {
dev_err(dev, "Failed to get xvclk\n");
return -EINVAL;
} for (i = 0; i < RN6752_NUM_SUPPLIES; i++)
rn6752->supplies[i].supply = rn6752_supply_names[i]; /* 获取摄像头设备的电源管理器句柄 */
ret = devm_regulator_bulk_get(dev, RN6752_NUM_SUPPLIES, rn6752->supplies);
if (ret)
{
dev_err(dev, "Failed to get power regulators\n");
return -EINVAL;
} return 0;
} /**
* @brief 初始化 rn6752 摄像头的硬件控制器函数,通过命令
* “v4l2-ctl -d /dev/v4l-subdev5 --list-ctrls” 可以查看
* @param rn6752 摄像头结构体指针
*/
static int rn6752_initialize_controls(struct rn6752 *rn6752)
{
struct v4l2_ctrl_handler *handler;
int ret; handler = &rn6752->ctrls; /* 初始化 rn6752->ctrls,该函数需要两个参数:控制处理器对象和控制器的数量 */
ret = v4l2_ctrl_handler_init(handler, 3);
if (ret)
return ret; handler->lock = &rn6752->lock; /* 创建一个 V4L2 控制器对象,并将其与 rn6752->ctrls 关联起来 */
rn6752->link_frequency = v4l2_ctrl_new_std(handler, &rn6752_ctrl_ops,
V4L2_CID_PIXEL_RATE, 0, RN6752_PIXEL_RATE, 1, RN6752_PIXEL_RATE); /* 创建一个带有选项菜单的 V4L2 控制器对象 */
v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
ARRAY_SIZE(link_freq_menu_items) - 1, 0, link_freq_menu_items); /* 创建另一个带有选项菜单的 V4L2 控制器对象 */
v4l2_ctrl_new_std_menu_items(handler, &rn6752_ctrl_ops, V4L2_CID_TEST_PATTERN,
ARRAY_SIZE(rn6752_test_pattern_menu) - 1, 0, 0, rn6752_test_pattern_menu);
rn6752->subdev.ctrl_handler = &rn6752->ctrls; if (handler->error) {
ret = handler->error;
dev_err(&rn6752->client->dev, "Failed to init controls(%d)\n", ret);
goto err_free_handler;
} return 0; err_free_handler:
v4l2_ctrl_handler_free(handler); return ret;
} /**
* @brief 根据读取到的芯片 ID 和版本号计算出一个 id 值,并与预定义的 RN6752_ID 进行比较
* @param rn6752 摄像头设备
*/
static int rn6752_check_sersor_id(struct rn6752 *rn6752)
{
struct i2c_client *client = rn6752->client;
u8 pid, ver;
int ret; dev_dbg(&client->dev, "%s:\n", __func__); /* Check sensor revision */
ret = rn6752_read(client, REG_SC_CHIP_ID_H, &pid); /* 读取芯片 ID 的高字节 */
if (!ret)
ret = rn6752_read(client, REG_SC_CHIP_ID_L, &ver); /* 读取芯片 ID 的低字节 */ if (!ret) {
unsigned short id; id = SENSOR_ID(pid, ver);
if (id != RN6752_ID) {
ret = -1;
dev_err(&client->dev, "Sensor detection failed (%04X, %d)\n", id, ret);
} else {
dev_info(&client->dev, "Found %04X sensor\n", id);
}
} return ret;
} /**
* @brief 系统检测到与该驱动程序匹配的 I2C 设备时
* @param client 指向 I2C 客户端结构体的指针。该结构体包含了有关 I2C 设备的信息,如设备地址、总线信息等
* @param id 指向 I2C 设备 ID 的指针。这个参数用于在多个相同类型的设备中进行区分和匹配
* @return 返回初始化结构
*/
static int rn6752_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct v4l2_subdev *sd;
struct rn6752 *rn6752;
char facing[2];
int ret; /* 打印驱动的版本信息的函数,其作用相当于 printk() 函数 */
dev_info(dev, "driver version: %02x.%02x.%02x", DRIVER_VERSION >> 16,
(DRIVER_VERSION & 0xff00) >> 8, DRIVER_VERSION & 0x00ff); /* 为设备分配内存,并将内存与设备进行关联。在驱动程序退出时,内存会自动被释放。被称为“设备内存管理” */
rn6752 = devm_kzalloc(dev, sizeof(*rn6752), GFP_KERNEL);
if (!rn6752)
{
dev_err(dev, "Memory control request failed\n");
return -ENOMEM;
} rn6752->client = client;
sd = &rn6752->subdev; /* 获取设备树信息 */
ret = rn6752_device_tree_info(rn6752);
if (ret != 0)
return -EINVAL; /* rn6752_mipi_framesizes 是 rn6752 mipi 通信支持的所有帧格式 */
rn6752->framesize_cfg = rn6752_mipi_framesizes;
rn6752->cfg_num = ARRAY_SIZE(rn6752_mipi_framesizes);
/* 获取摄像头传感器支持的图像帧格式 */
rn6752_get_default_format(rn6752, &rn6752->format);
rn6752->frame_size = &rn6752->framesize_cfg[0]; /* 设置帧大小 */
rn6752->format.width = rn6752->framesize_cfg[0].width; /* 设置宽度 */
rn6752->format.height = rn6752->framesize_cfg[0].height; /* 设置高度 */
rn6752->fps = DIV_ROUND_CLOSEST(rn6752->framesize_cfg[0].max_fps.denominator,
rn6752->framesize_cfg[0].max_fps.numerator); /* 设置最大帧速率 */ mutex_init(&rn6752->lock); #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
/* 通过v4l2框架注册一个V4L2子设备,并初始化该子设备的操作函数和内部操作函数指针 */
v4l2_i2c_subdev_init(sd, client, &rn6752_subdev_ops);
ret = rn6752_initialize_controls(rn6752);
if (ret)
{
dev_info(dev, "V4l2 control menu initialization failed");
goto err_destroy_mutex;
} /* 关联子设备的内部操作函数,用于打开摄像头操作 */
sd->internal_ops = &rn6752_subdev_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | /* 设备有对应的设备节点文件,可以通过该文件进行访问 */
V4L2_SUBDEV_FL_HAS_EVENTS; /* 设备具有事件机制,可以作为事件源供其他驱动程序使用 */
#endif /* 打开设备的电源 */
ret = __rn6752_power_on(rn6752);
if (ret)
goto err_free_handler; /* 通过读取 RN6752 的产品 ID 判断设备是否存在 */
ret = rn6752_check_sersor_id(rn6752);
if (ret)
goto err_power_off; #if defined(CONFIG_MEDIA_CONTROLLER)
rn6752->pad.flags = MEDIA_PAD_FL_SOURCE; /* 表明该子设备是媒体管道中的源设备,即数据的起始点 */
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; /* 表示该实体是一个相机传感器 */
ret = media_entity_pads_init(&sd->entity, 1, &rn6752->pad); /* 初始化 sd->entity 媒体实体的 pads(端口)属性 */
if (ret < 0) {
dev_err(dev, "Media pad port initialization failed\n");
goto err_power_off;
}
#endif /* 根据设备树提供的信息,判断摄像头的方向 */
memset(facing, 0, sizeof(facing));
if (strcmp(rn6752->module_facing, "back") == 0)
facing[0] = 'b';
else
facing[0] = 'f'; /* 名称格式如 m00_f_rn6752 1-002c:bus */
snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
rn6752->module_index, facing, DRIVER_NAME, dev_name(sd->dev));
/* 将传感器子设备添加到V4L2异步子系统中,以便能够与其他V4L2组件进行交互。 */
/* 它会自动设置子设备的相关回调函数,并与异步框架进行适当的关联,以管理传感器的采集和控制操作 */
ret = v4l2_async_register_subdev_sensor_common(sd);
if (ret)
{
dev_err(dev, "Failed to add sensor to V4L2 asynchronous subsystem\n");
goto err_clean_entity;
} /* 摄像头传感器注册成功,并打印日志信息 */
dev_info(dev, "%s sensor driver registered !!\n", sd->name); /* 完成初始化后,使 rn6752 进入睡眠模式 */
rn6752->power_on = false;
if (!IS_ERR(rn6752->pwdn_gpio))
gpiod_set_value_cansleep(rn6752->pwdn_gpio, 1); return 0; err_clean_entity:
#if defined(CONFIG_MEDIA_CONTROLLER)
media_entity_cleanup(&sd->entity);
#endif
err_power_off:
__rn6752_power_off(rn6752);
err_free_handler:
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
v4l2_ctrl_handler_free(&rn6752->ctrls);
#endif
err_destroy_mutex:
mutex_destroy(&rn6752->lock); return ret;
} /**
* @brief 当设备驱动被删除释放时,执行此函数
* @param client 指向 I2C 客户端结构体的指针。该结构体包含了有关 I2C 设备的信息,如设备地址、总线信息等
* @return 返回操作结果
*/
static int rn6752_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct rn6752 *rn6752 = to_rn6752(sd); #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
/* 释放V4L2控制器处理器所占用的资源 */
v4l2_ctrl_handler_free(&rn6752->ctrls);
#endif
/* 注销一个V4L2子设备 */
v4l2_async_unregister_subdev(sd);
#if defined(CONFIG_MEDIA_CONTROLLER)
media_entity_cleanup(&sd->entity);
#endif
mutex_destroy(&rn6752->lock); __rn6752_power_off(rn6752);
return 0;
} /* 设备树节点匹配表格,与设备树中的节点描述信息一样时,匹配成功 */
#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id rn6752_of_match[] = {
{ .compatible = "richnex,rn6752v1" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rn6752_of_match);
#endif /* 设备 ID 表格,与设备名称一样时匹配成功,主要用于低版本linux内核的匹配方式 */
static const struct i2c_device_id rn6752_match_id[] = {
{ "richnex,rn6752v1", 0 },
{ /* sentinel */ },
}; /* 描述和注册一个 I2C 设备驱动程序 */
static struct i2c_driver rn6752_i2c_driver = {
.driver = { /* 驱动程序信息的子结构体 */
.name = DRIVER_NAME, /* 设置驱动程序的名称 */
.of_match_table = of_match_ptr(rn6752_of_match), /* 用于匹配设备树节点的表格 */
},
.probe = rn6752_probe, /* I2C 设备被检测到时进行设备初始化和处理 */
.remove = rn6752_remove, /* I2C 设备从系统中移除时进行清理和资源释放 */
.id_table = rn6752_match_id, /* I2C 设备 ID 表格,平台设备匹配方式之一,用设备和驱动的名称进行匹配 */
}; static int __init sensor_mod_init(void)
{
/* 注册 I2C 驱动程序 */
return i2c_add_driver(&rn6752_i2c_driver);
} static void __exit sensor_mod_exit(void)
{
/* 注销 I2C 驱动程序 */
i2c_del_driver(&rn6752_i2c_driver);
} device_initcall_sync(sensor_mod_init); /* 注册一个设备初始化函数 */
module_exit(sensor_mod_exit); /* 注销一个设备初始化函数 */ MODULE_AUTHOR("jiaozhu <cn_jiaozhu@qq.com>");
MODULE_DESCRIPTION("RN6752 CMOS Image Sensor driver");

参考资料

  1. MIPI扫盲——D-PHY介绍:https://zhuanlan.zhihu.com/p/638769112?utm_id=0

Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)的更多相关文章

  1. USB摄像头驱动框架分析(五)

    一.USB摄像头驱动框架如下所示:1.构造一个usb_driver2.设置   probe:        2.1. 分配video_device:video_device_alloc        ...

  2. Hi3559AV100外接UVC/MJPEG相机实时采图设计(一):Linux USB摄像头驱动分析

    下面将给出Hi3559AV100外接UVC/MJPEG相机实时采图设计的整体流程,主要实现是通过V4L2接口将UVC/MJPEG相机采集的数据送入至MPP平台,经过VDEC.VPSS.VO最后通过HD ...

  3. 基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读(一)

    作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 FIMC这个名字应该是从S5PC1x0開始出现的.在s5pv210里面的定义是摄像头接口.可是它相 ...

  4. Linux USB摄像头驱动【转】

    本文转载自:http://www.itdadao.com/articles/c15a509940p0.html 在 cortex-a8 中,可接入摄像头的接口通常可以分为两种, CAMERA 接口和 ...

  5. Linux USB 摄像头驱动

    在 cortex-a8 中,可接入摄像头的接口通常可以分为两种, CAMERA 接口和 USB 接口的摄像头.这一章主要是介绍 USB 摄像头的设备驱动程序.在我们印象中,驱动程序都是一个萝卜一个坑, ...

  6. USB摄像头驱动框架分析

    usb摄像头驱动程序,里面涉及硬件的操作.比如说,想设置亮度的时候,需要把亮度的参数发给硬件.去得到真正视频数据的时候,需要访问硬件得到数据.usb摄像头驱动程序框架与虚拟摄像头驱动程序的框架是一样的 ...

  7. Linux下USB驱动框架分析【转】

    转自:http://blog.csdn.net/brucexu1978/article/details/17583407 版权声明:本文为博主原创文章,未经博主允许不得转载. http://www.c ...

  8. 一步步理解linux字符设备驱动框架(转)

    /* *本文版权归于凌阳教育.如转载请注明 *原作者和原文链接 http://blog.csdn.net/edudriver/article/details/18354313* *特此说明并保留对其追 ...

  9. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  10. 2.1 摄像头V4L2驱动框架分析

    学习目标:学习V4L2(V4L2:vidio for linux version 2)摄像头驱动框架,分析vivi.c(虚拟视频硬件相关)驱动源码程序,总结V4L2硬件相关的驱动的步骤:  一.V4L ...

随机推荐

  1. PostgreSql: 安装与链接

    环境介绍 使用宝塔面板,在阿里云中安装PostgreSql,并使用DataGrip在本地进行链接 postgresql 配置 安装postgresql 在宝塔中安装postgresql 管理器 在此处 ...

  2. Mysql高级5-SQL优化

    一.插入数据优化 1.1 批量插入 如果有多条数据需要同时插入,不要每次插入一条,然后分多次插入,因为每执行一次插入的操作,都要进行数据库的连接,多个操作就会连接多次,而一次批量操作只需要连接1次 1 ...

  3. (转)IBM Appscan9.0.3安全扫描简单安装、使用以及高危漏洞修复

    最近手上负责一个的项目要进行等保评测.请的第三方公司采用IBM Security AppScan Standard对项目进行安全测试.测试报告高危漏洞主要包含sql注入.sql盲注.跨站点脚本编制如下 ...

  4. Cesium SuperMap问题调研汇总

    https://segmentfault.com/a/1190000040577369?sort=newest

  5. 加密算法解析:MD5、DES和RAS的工作原理与特点

    一.MD5不可逆加密 1.1-理解MD5 MD5公开的算法,任何语言实现后其实都是一样的.通用的 不可逆加密:原文--加密--密文,密文无法解密出原文 1.2-MD5封装 using System.I ...

  6. 【Unity3D】激光雷达特效

    1 由深度纹理重构世界坐标 ​ 屏幕深度和法线纹理简介中对深度和法线纹理的来源.使用及推导过程进行了讲解,本文将介绍使用深度纹理重构世界坐标的方法,并使用重构后的世界坐标模拟激光雷达特效. ​ 本文完 ...

  7. 【路由器】OpenWrt 配置使用

    目录 Web 界面 汉化 root 密码 ssh 升级 LuCI 美化 锐捷认证 MentoHUST MiniEAP 防火墙 开放端口 端口转发 IPv6 USB 安装 USB 驱动 自动挂载 Ext ...

  8. .NET API 中的 FromRoute、FromQuery、FromBody 用法

    原文链接:https://www.cnblogs.com/ysmc/p/17663663.html 最近技术交流群里,还有不少小伙伴不知道 FromRoute.FromQuery.FromBody 这 ...

  9. pythonapi接口怎么对接?

    ​ Python API接口对接是使用Python语言开发应用程序时,与外部API接口进行交互的一种方式.API(应用程序接口)是一种定义了程序或系统如何与另一个程序或系统进行交互的协议.通过使用Py ...

  10. 震坤行根据ID取商品详情 API

    item_get-根据ID取商品详情 注册开通 zhenkunhang.item_get 公共参数 名称 类型 必须 描述 key String 是 调用key(必须以GET方式拼接在URL中) se ...