Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)

一、概述

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

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

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

Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)

二、测试环境

  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,如下图所示
    Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)

  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. 完成以上内容后,准备工作基本完成了,编译并重写烧写内核程序后,会在启动日志中打印版本信息,如下图所示
    Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)

四、probe 函数实现

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

    /* 为设备分配内存,并将内存与设备进行关联。在驱动程序退出时,内存会自动被释放。被称为“设备内存管理” */ 	rn6752 = devm_kzalloc(dev, sizeof(*rn6752), GFP_KERNEL); 	if (!rn6752) 	{ 		dev_err(dev, "Memory control request failedn"); 		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 查看,如下图所示:
    Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)

  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 failedn"); 		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 subsystemn"); 		goto err_clean_entity; 	} 

    注意: 在 RV1126 开发板中,创建 media 设备时,需要注意 mipi 连接的通道,连接 mipi
    csi0 时,需要获取设备的数据格式,否则会出现内存地址错误。解决此错误的方法是在 rn6752_get_fmt 函数中默认一个类型值即可,如下图所示:
    Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)

  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 regulatorsn");  		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 MHzn", RN6752_XVCLK_CLOCK);  		ret = clk_get_rate(rn6752->xvclk); 		if (ret != RN6752_XVCLK_CLOCK) 			dev_warn(dev, "xvclk mismatchedn"); 		 		/* 准备并启用 xvclk */ 		ret = clk_prepare_enable(rn6752->xvclk); 		if (ret < 0) 		{ 			dev_err(dev, "Failed to enable xvclkn"); 			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 usen"); 	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 usen"); 	 	/* 获取摄像头的时钟资源 */ 	rn6752->xvclk = devm_clk_get(dev, "xvclk"); 	if (IS_ERR(rn6752->xvclk)) { 		dev_err(dev, "Failed to get xvclkn"); 		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 regulatorsn"); 		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 sensorn", 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 failedn"); 		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 failedn"); 		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 subsystemn"); 		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
发表评论

评论已关闭。

相关文章