Linux RN6752 驱动编写

一、概述

关于 RN6752V1 这个芯片这里就不做介绍了,看到这篇笔记的小伙伴应该都明白,虽然说 RN6752V1 芯片是 AHD 信号的解码芯片,但是也可以把芯片当做是一个 YUV 信号的 MIPI 摄像头,所以驱动的编写和 MIPI 摄像头无太大的区别。这里主要是介绍具体的函数,关于 MIPI 驱动的框架程序看我之前的笔记:Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)

二、RN6752 帧格式

RN6752 支持 DVP 和 MIPI 信号,这里我主要是对 MIPI 信号的使用,当然 DVP 通信的操作也可以做参考。

  1. 寄存地配置
    通过代理商提供的头文件中可以获取到相关寄存器的配置,如下所示:

    static const struct sensor_register rn6752_fhd_1080P25_video[] = { 	{ 0x19, 0x0A }, // 视频格式检测滞后控制 	{ 0x81, 0x01 }, // 打开视频解码器 	{ 0xDF, 0xFE }, // 启用HD格式 	{ 0xF0, 0xC0 },	// 使能 FIFO 和 144 MHz 解码器输出 	{ 0xA3, 0x04 }, // 启用 HD 输出 	{ 0x88, 0x40 }, // 禁用 SCLK1 输出 	{ 0xF6, 0x40 }, // 禁用 SCLK3A 输出  	/* 切换到ch0(默认;可选) */ 	{ 0xFF, 0x00 },	// 寄存器集选择 	{ 0x33, 0x10 }, // 检测中的视频 	{ 0x4A, 0xA8 }, // 检测中的视频 	{ 0x00, 0x20 }, // internal use* 	{ 0x06, 0x08 }, // internal use* 	{ 0x07, 0x63 }, // 高清格式 	{ 0x2A, 0x01 }, // 滤波器控制 	{ 0x3A, 0x24 }, // 在SAV/EAV代码中插入通道ID 	{ 0x3F, 0x10 }, // 通道ID 	{ 0x4C, 0x37 }, // 均衡器 	{ 0x4F, 0x03 }, // 同步控制 	{ 0x50, 0x03 }, // 1080p分辨率 	{ 0x56, 0x02 }, // 144M 和 BT656模式 	{ 0x5F, 0x44 }, // 消隐电平 	{ 0x63, 0xF8 }, // 滤波器控制 	{ 0x59, 0x00 }, // 扩展寄存器存取 	{ 0x5A, 0x48 }, // 扩展寄存器的数据 	{ 0x58, 0x01 }, // 启用扩展寄存器写入 	{ 0x59, 0x33 }, // 扩展寄存器存取 	{ 0x5A, 0x23 }, // 扩展寄存器的数据 	{ 0x58, 0x01 }, // 启用扩展寄存器写入 	{ 0x51, 0xF4 }, // 比例因子1 	{ 0x52, 0x29 }, // 比例因子2 	{ 0x53, 0x15 }, // 比例因子3 	{ 0x5B, 0x01 }, // H-标度控制 	{ 0x5E, 0x08 }, // 启用H缩放控制 	{ 0x6A, 0x87 }, // H-标度控制 	{ 0x28, 0x92 }, // 剪裁 	{ 0x03, 0x80 }, // 饱和 	{ 0x04, 0x80 }, // 颜色 	{ 0x05, 0x04 }, // 尖锐 	{ 0x57, 0x23 }, // 黑色/白色拉伸 	{ 0x68, 0x00 }, // coring 	{ 0x37, 0x33 }, //  	{ 0x61, 0x6C }, // #ifdef USE_BLUE_SCREEN 	{ 0x3A, 0x24 }, // AHD 断开链接时,屏幕为蓝色 #else 	{ 0x3A, 0x2C }, // AHD 断开链接时,屏幕为黑色 	{ 0x3B, 0x00 }, // 	{ 0x3C, 0x80 }, // 	{ 0x3D, 0x80 }, // #endif 	{ 0x2E, 0x30 }, // 强制不播放视频 	{ 0x2E, 0x00 }, // 回归平常  	/* mipi 连接 */ 	{ 0xFF, 0x09 }, // 切换到 mipi tx1 	{ 0x00, 0x03 }, // enable bias 	{ 0xFF, 0x08 }, // 切换到 mipi csi1 	{ 0x04, 0x03 }, // csi1 和 tx1 重置 	{ 0x6C, 0x11 }, // 禁用 ch 输出,打开 ch0 #ifdef USE_MIPI_4LANES 	{ 0x06, 0x7C }, // mipi 4 线 #else 	{ 0x06, 0x4C }, // mipi 2 线 #endif 	{ 0x21, 0x01 }, // 启用 hs 时钟 	{ 0x34, 0x06 }, // 	{ 0x35, 0x0B }, //  	{ 0x78, 0xC0 }, // ch0 的 Y/C 计数 	{ 0x79, 0x03 }, // ch0 的 Y/C 计数 	{ 0x6C, 0x01 }, // 启用 ch 输出 	{ 0x04, 0x00 }, // csi1 和 tx1 重置完成 	{ 0x20, 0xAA }, //  #ifdef USE_MIPI_NON_CONTINUOUS_CLOCK 	{ 0x07, 0x05 }, // 启用非连续时钟 #else 	{ 0x07, 0x04 }, // 启用连续时钟 #endif 	{ 0xFF, 0x0A }, // 切换到 mipi csi3 	{ 0x6C, 0x10 }, // 禁用 ch 输出;关闭 ch0~3 	{REG_NULL, 0x00}, }; 

    注意: 其他格式的寄存器我这里就不附上了,可以参考代理商提供的头文件

  2. 将配置信息存入帧列表中

    static const struct rn6752_framesize rn6752_mipi_framesizes[] = { 	{ 		.width		= 1280, 		.height		= 720, 		.max_fps = { 			.numerator = 10000, 			.denominator = 250000, 		}, 		.regs		= rn6752_fhd_720P25_video, 	},  	{ 		.width		= 1280, 		.height		= 720, 		.max_fps = { 			.numerator = 10000, 			.denominator = 300000, 		}, 		.regs		= rn6752_fhd_720P30_video, 	},  	{ 		.width		= 1920, 		.height		= 1080, 		.max_fps = { 			.numerator = 10000, 			.denominator = 250000, 		}, 		.regs		= rn6752_fhd_1080P25_video, 	},  	{ 		.width		= 1920, 		.height		= 1080, 		.max_fps = { 			.numerator = 10000, 			.denominator = 300000, 		}, 		.regs		= rn6752_fhd_1080P30_video, 	},  	{ 		.width		= 1280, 		.height		= 960, 		.max_fps = { 			.numerator = 10000, 			.denominator = 250000, 		}, 		.regs		= rn6752_fhd_960P25_video, 	},  	{ 		.width		= 1280, 		.height		= 960, 		.max_fps = { 			.numerator = 10000, 			.denominator = 300000, 		}, 		.regs		= rn6752_fhd_960P30_video, 	} }; 
  3. 配置默认帧
    在 rn6752_probe 函数中存入默认支持的帧列表,如下所示

    static void rn6752_get_default_format(struct rn6752 *rn6752, 				      struct v4l2_mbus_framefmt *format) { 	format->width = rn6752->framesize_cfg[2].width; 	/* 设置默认宽度 */ 	format->height = rn6752->framesize_cfg[2].height; 	/* 设置默认高度 */ 	format->colorspace = V4L2_COLORSPACE_SRGB; 			/* 设置默认色彩空间为标准的 sRGB 色彩空间 */ 	format->code = rn6752_formats[0].code; 				/* 设置默认编码格式 */ 	format->field = V4L2_FIELD_NONE; 					/* 设置默认场模式 */ }  /* 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[2]; 			/* 设置帧大小 */ rn6752->format.width = rn6752->framesize_cfg[2].width; 		/* 设置宽度 */ rn6752->format.height = rn6752->framesize_cfg[2].height; 	/* 设置高度 */ rn6752->fps = DIV_ROUND_CLOSEST( 	rn6752->framesize_cfg[2].max_fps.denominator, 	rn6752->framesize_cfg[2].max_fps.numerator); 			/* 设置最大帧速率 */ 

    注意:

    • 首先将所有支持的帧列表存入了 rn6752->framesize_cfg 中
    • 将支持的列表数量存入 rn6752->cfg_num 中
    • 将默认支持的帧格式和大小存入 rn6752->format 中,这个在用户空间可以查看
    • 将默认支持的帧大小存入 rn6752->frame_size 中
    • 将默认支持的帧率存入 rn6752->fps 中
    • 以上这些默认变量将在后面的函数中经常用到,所以需要特别注意一下,不然很难理解数据从哪里来的

三、Media 设备节点

之前在 Media 子系统中提到过模块之间的关系查看命令media-ctl -p -d /dev/mediaX ,通过命令可以得到驱动中的一些信息,如下图所示
Linux RN6752 驱动编写

  1. Media 帧大小
    Media 帧大小是在驱动初始化时,通过 rn6752_get_fmt 函数获取的,程序如下

    static int rn6752_get_fmt(struct v4l2_subdev *sd, 			  struct v4l2_subdev_pad_config *cfg, 			  struct v4l2_subdev_format *fmt) { 	struct i2c_client *client = v4l2_get_subdevdata(sd); /* 获取i2c_client指针 */ 	struct rn6752 *rn6752 = to_rn6752(sd);  	/* 使用dev_dbg打印日志,显示当前函数进入 */ 	// dev_info(&client->dev, "%s entern", __func__);  	/* 条件成立时,表示要获取正在尝试的格式 */ 	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 		struct v4l2_mbus_framefmt *mf;  		/* 获取正在尝试的格式 */ 		mf = v4l2_subdev_get_try_format(sd, cfg, 0); 		mutex_lock(&rn6752->lock); 		fmt->format = *mf; 		mutex_unlock(&rn6752->lock); 		return 0; #else 		return -ENOTTY; #endif 	}  	/* 条件不成立时,表示要获取当前的格式 */ 	mutex_lock(&rn6752->lock); 	fmt->format = rn6752->format; 	mutex_unlock(&rn6752->lock);  	/* 使用dev_dbg打印日志,显示当前格式的代码值、宽度和高度 */ 	dev_dbg(&client->dev, "%s: %x %dx%dn", __func__, rn6752->format.code, 		rn6752->format.width, rn6752->format.height);  	return 0; } 
  2. 帧格式判断
    Media 设备是通过 rn6752_enum_frame_sizes 和 rn6752_enum_frame_interval 函数枚举了帧大小和帧率,这两个函数主要起到判断的作用,确实当前帧率是否是驱动支持的,程序如下

    static int rn6752_enum_frame_sizes(struct v4l2_subdev *sd, 				   struct v4l2_subdev_pad_config *cfg, 				   struct v4l2_subdev_frame_size_enum *fse) { 	struct rn6752 *rn6752 = to_rn6752(sd); 	struct i2c_client *client = v4l2_get_subdevdata(sd); 	int i = ARRAY_SIZE(rn6752_formats); 	printk(KERN_INFO 	       "rn6752_enum_frame_sizes................................................n");  	dev_dbg(&client->dev, "%s:n", __func__);  	if (fse->index >= rn6752->cfg_num) 		return -EINVAL;  	while (--i) 		if (fse->code == rn6752_formats[i].code) 			break;  	fse->code = rn6752_formats[i].code;  	fse->min_width  = rn6752->framesize_cfg[fse->index].width; 	fse->max_width  = fse->min_width; 	fse->max_height = rn6752->framesize_cfg[fse->index].height; 	fse->min_height = fse->max_height; 	return 0; }  static int rn6752_enum_frame_interval(struct v4l2_subdev *sd, 			   struct v4l2_subdev_pad_config *cfg, 			   struct v4l2_subdev_frame_interval_enum *fie) { 	struct rn6752 *rn6752 = to_rn6752(sd); 	printk(KERN_INFO 	       "rn6752_enum_frame_interval index: %d....................n", fie->index );  	/* 检查传入的 fie 结构体中的 index 字段是否超出了 rn6752 所支持的帧间隔配置数量(cfg_num) */ 	if (fie->index >= rn6752->cfg_num) 		return -EINVAL;  	/* 检查传入的 fie 结构体中的 code 字段是否与期望的媒体总线格式(MEDIA_BUS_FMT_UYVY8_2X8)匹配 */ 	if (fie->code != MEDIA_BUS_FMT_UYVY8_2X8) 		return -EINVAL;  	fie->width = rn6752->framesize_cfg[fie->index].width;           /* 宽 */ 	fie->height = rn6752->framesize_cfg[fie->index].height;         /* 高 */ 	fie->interval = rn6752->framesize_cfg[fie->index].max_fps;      /* 最大帧率 */  	return 0; } 
  3. 帧大小设置
    可以通过 rn6752_set_fmt 函数设置帧的大小,程序如下

    tatic int rn6752_set_fmt(struct v4l2_subdev *sd, 			  struct v4l2_subdev_pad_config *cfg, 			  struct v4l2_subdev_format *fmt) { 	struct i2c_client *client = v4l2_get_subdevdata(sd); 	int index = ARRAY_SIZE(rn6752_formats); 	struct v4l2_mbus_framefmt *mf = &fmt->format; 	const struct rn6752_framesize *size = NULL; 	struct rn6752 *rn6752 = to_rn6752(sd); 	printk(KERN_INFO 	       "rn6752_set_fmt................................................n");  	dev_info(&client->dev, "%s entern", __func__);  	/* 根据传入的参数调整帧大小和帧速率,并返回适合的帧大小和帧速率 */ 	__rn6752_try_frame_size_fps(rn6752, mf, &size, rn6752->fps);  	/* 遍历rn6752_formats数组 */ 	while (--index >= 0) 		if (rn6752_formats[index].code == mf->code) 			break;  	if (index < 0) 		return -EINVAL;  	/* 色彩空间为sRGB,场为无 */ 	mf->colorspace = V4L2_COLORSPACE_SRGB; 	mf->code = rn6752_formats[index].code; 	mf->field = V4L2_FIELD_NONE;  	mutex_lock(&rn6752->lock);  	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 		mf = v4l2_subdev_get_try_format( 			sd, cfg, 			fmt->pad); /* 使用v4l2_subdev_get_try_format函数获取正在尝试的格式 */ 		*mf = fmt->format; #else 		return -ENOTTY; #endif 	} else { 		if (rn6752->streaming) { 			mutex_unlock(&rn6752->lock); 			return -EBUSY; 		}  		/* 分别设置为获取到的帧大小和传入的格式 */ 		rn6752->frame_size = size; 		rn6752->format = fmt->format; 	}  	mutex_unlock(&rn6752->lock);  	return 0; } 
  4. 帧间隔获取

    static int rn6752_g_frame_interval(struct v4l2_subdev *sd, 				   struct v4l2_subdev_frame_interval *fi) { 	struct rn6752 *rn6752 = to_rn6752(sd); 	printk(KERN_INFO 	       "rn6752_g_frame_interval................................................n");  	mutex_lock(&rn6752->lock); 	fi->interval = rn6752->frame_size->max_fps; 	mutex_unlock(&rn6752->lock); 	return 0; } 

四、总线编码格式

之前有提到过,RN6752 支持 DVP 和 MIPI 总线格式,所以可以在一个驱动中实现两个功能,这里我就是写了 MIPI 的通信方式,我目前对 DVP 也不了解,以后在补上。

刚好驱动中提供了两个函数可以获取驱动总线的格式,如下所示

  1. 获取当前媒体总线配置的函数

    static int rn6752_g_mbus_config(struct v4l2_subdev *sd, 				struct v4l2_mbus_config *config) { 	printk(KERN_INFO 	       "rn6752_g_mbus_config................................................n");  	/* 总线类型是CSI-2 */ 	config->type = V4L2_MBUS_CSI2; 	config->flags = V4L2_MBUS_CSI2_4_LANE | V4L2_MBUS_CSI2_CHANNEL_0 | 			V4L2_MBUS_CSI2_CHANNEL_1 | 			V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;  	return 0; }  
  2. 枚举所有支持的媒体总线编码和格式

    static int rn6752_enum_mbus_code(struct v4l2_subdev *sd, 				 struct v4l2_subdev_pad_config *cfg, 				 struct v4l2_subdev_mbus_code_enum *code) { 	struct i2c_client *client = v4l2_get_subdevdata(sd); 	printk(KERN_INFO 	       "rn6752_enum_mbus_code................................................n");  	dev_dbg(&client->dev, "%s:n", __func__);  	if (code->index >= ARRAY_SIZE(rn6752_formats)) 		return -EINVAL;  	code->code = rn6752_formats[code->index].code; 	return 0; } 

五、电源管理

摄像头每次开启和关闭时,都需要通过电源管理函数配置摄像头电源

static int rn6752_power(struct v4l2_subdev *sd, int on) { 	struct rn6752 *rn6752 = to_rn6752(sd); 	struct i2c_client *client = rn6752->client; 	int ret = 0;  	/* 使用dev_info打印日志,显示当前函数和行号,并打印on参数的值 */ 	dev_dbg(&client->dev, "%s(%d) on(%d)n", __func__, __LINE__, on);  	mutex_lock(&rn6752->lock);  	if (rn6752->power_on == !! on) 		goto unlock_and_return; 	 	if (on) { 		ret = pm_runtime_get_sync(&client->dev); 		if (ret < 0) { 			pm_runtime_put_noidle(&client->dev); 			goto unlock_and_return; 		} 		rn6752->power_on = true; 	} else { 		pm_runtime_put(&client->dev); 		rn6752->power_on = false; 	}  unlock_and_return: 	mutex_unlock(&rn6752->lock);  	return ret; } 

六、摄像头控制

由于这里我没有实现太多的控制功能,所以只实现了必要的两个控制,最主要的是复位时执行的 RKMODULE_SET_QUICK_STREAM 功能

static long rn6752_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) { 	struct rn6752 *rn6752 = to_rn6752(sd); 	// struct rkmodule_hdr_cfg *hdr; 	long ret = 0; 	u32 stream = 0;  	// dev_dbg(KERN_INFO "rn6752_ioctl  0x%x..........n", cmd);  	switch (cmd) { 	case RKMODULE_GET_MODULE_INFO: 		rn6752_get_module_info(rn6752, (struct rkmodule_inf *)arg); 		break; 	case RKMODULE_SET_QUICK_STREAM: 		stream = *((u32 *)arg);  		rn6752_set_streaming(rn6752, !!stream); 		break;  	default: 		ret = -ENOIOCTLCMD; 		break; 	} 	return ret; }  #ifdef CONFIG_COMPAT static long rn6752_compat_ioctl32(struct v4l2_subdev *sd, unsigned int cmd, 				  unsigned long arg) { 	void __user *up = compat_ptr(arg); 	struct rkmodule_inf *inf; 	struct rkmodule_awb_cfg *cfg; 	long ret; 	u32 stream = 0;  	// dev_dbg(KERN_INFO "rn6752_compat_ioctl32..........n");  	switch (cmd) { 	case RKMODULE_GET_MODULE_INFO: 		inf = kzalloc(sizeof(*inf), GFP_KERNEL); 		if (!inf) { 			ret = -ENOMEM; 			return ret; 		}  		ret = rn6752_ioctl(sd, cmd, inf); 		if (!ret) 			ret = copy_to_user(up, inf, sizeof(*inf)); 		kfree(inf); 		break; 	case RKMODULE_AWB_CFG: 		cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); 		if (!cfg) { 			ret = -ENOMEM; 			return ret; 		}  		ret = copy_from_user(cfg, up, sizeof(*cfg)); 		if (!ret) 			ret = rn6752_ioctl(sd, cmd, cfg); 		kfree(cfg); 		break; 	case RKMODULE_SET_QUICK_STREAM: 		ret = copy_from_user(&stream, up, sizeof(u32)); 		if (!ret) 			ret = rn6752_ioctl(sd, cmd, &stream); 		break; 	default: 		ret = -ENOIOCTLCMD; 		break; 	} 	return 0; } #endif 

七、数据流控制

整个驱动最重要的便是流控制函数,通过此函数完成了摄像头的启动和停止

static int rn6752_set_streaming(struct rn6752 *rn6752, int on) { 	struct i2c_client *client = rn6752->client; 	int ret = 0;  	dev_info(&client->dev, "%s: on: %dn", __func__, on);  	if (on) 	{ 		ret = rn6752_write(client, 0x80, 0x31); 		usleep_range(200, 500); 		ret |= rn6752_write(client, 0x80, 0x30); 		if (ret) 		{ 			dev_err(&client->dev, "rn6752 soft reset failedn"); 			return ret; 		}  		ret = rn6752_write_array(client, rn6752->frame_size->regs); 		if (ret) 			dev_err(&client->dev, "rn6752 start initialization failedn"); 	} 	else 	{ 		ret = rn6752_write(client, 0x80, 0x00); 		if (ret) 			dev_err(&client->dev, "rn6752 soft standby failedn"); 			 	} 	return ret; }  static int rn6752_s_stream(struct v4l2_subdev *sd, int on) { 	struct i2c_client *client = v4l2_get_subdevdata(sd); 	struct rn6752 *rn6752 = to_rn6752(sd); 	int ret = 0; 	unsigned int fps;  	/* 计算帧率和延迟时间 */ 	fps = DIV_ROUND_CLOSEST(rn6752->frame_size->max_fps.denominator, 					rn6752->frame_size->max_fps.numerator);  	dev_info(&client->dev, "%s: on: %d, %dx%d@%dn", __func__, on, 		 rn6752->frame_size->width, rn6752->frame_size->height, 		 DIV_ROUND_CLOSEST(rn6752->frame_size->max_fps.denominator, 				   rn6752->frame_size->max_fps.numerator));  	mutex_lock(&rn6752->lock);  	on = !!on;  	if (rn6752->streaming == on) 		goto unlock;  	rn6752->streaming = on;  	if (on) { 		ret = pm_runtime_get_sync(&client->dev); 		if (ret < 0) 		{ 			pm_runtime_put_noidle(&client->dev); 			goto unlock; 		}  		ret = rn6752_set_streaming(rn6752, on); 		if (ret) 		{ 			rn6752->streaming = !on; 			pm_runtime_put(&client->dev); 		} 			 	} 	else 	{ 		ret = rn6752_set_streaming(rn6752, on); 		pm_runtime_put(&client->dev); 	}  unlock: 	mutex_unlock(&rn6752->lock); 	return ret; } 

注意: 摄像头驱动中并没有图像接收之类的关系,而数据流操作函数主要的作用是对芯片进行初始化,使摄像头进入工作模式。从上面的驱动程序可以看出,整个驱动并没有其他特别的功能,就是一个 I2C 控制功能,所以摄像头的驱动其实就是一个 I2C 驱动程序。

由于笔记内容有点多,这里我就不附上完成的驱动程序了,其次是驱动程序也比较简单,看完的小伙伴应该都能明白。主要的难度都在调试摄像头驱动上面,我也折腾了很久,有需要的小伙变可以看我后面的笔记

参考资料

  1. gc2145.c 和 imx335.c 驱动程序
  2. MIPI、CSI基础:http://681314.com/A/3SUXTEc3LN
  3. linux V4L2子系统——v4l2架构(1):https://blog.csdn.net/u013836909/article/details/125359789
  4. Camera | 2.MIPI、CSI基础 (681314.com):http://681314.com/A/3SUXTEc3LN
  5. linux v4l2-i2c 框架学习:https://blog.csdn.net/oqqYuJi12345678/article/details/93755475
发表评论

评论已关闭。

相关文章