全志T7 nvp6134驱动

文件结构

nvp6134c.c nvp6134驱动入口、与T7 sensor_helper.c结合完成v4l2驱动的注册等。
csi_dev_nvp6134.c nvp6134芯片寄存器初始化
video.c 主要是与视频参数相关的一些配置,csi_dev_nvp6134.c会调用到这个文件的函数
eq.c eq_common.c eq_recovery.c nvp6134内部Equalizer(均衡)过滤器的配置,一般不需要修改
acp.c acp_firmup.c nvp6134内部AHD解析器(Analog Clamp)的配置

NVP6134C通过EQ模式通过电缆决定衰减的图像信号电平; 通过均衡器补偿滤波器补偿衰减的图像信号。 (EQ模式)

nvp6134驱动入口

nvp6134驱动的入口文件是drivers\media\platform\sunxi-vin\modules\sensor\nvp6134c.c,入口函数是init_sensor ()–>i2c_add_driver(sensor_driver)。与DST匹配后执行sensor_probe()

static int sensor_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{

	struct sensor_info * info;
	。。。。。。分配info的内存空间
	//probe函数执行完后收回info指针,但info指向的kzalloc
	//并没有被释放,需要用kfree释放,所以gl_sd将
	//一直可以取得这个内存空间的数据
	gl_sd = &info->sd;
	cci_dev_probe_helper(gl_sd, client, &sensor_ops, &cci_drv);
	mutex_init(&info->lock);

	info->fmt = &sensor_formats[0];//sensor输出视频格式
	info->fmt_pt = &sensor_formats[0];
	info->win_pt = &sensor_win_sizes[0];//保存sensor窗口大小的配置
	info->fmt_num = N_FMTS;//默认只有一种输出格式
	info->win_size_num = N_WIN_SIZES;//默认只有一个窗口大小配置项
	/*ensor 的 field 默认设置为 V4L2_FIELD_NONE,如果某些 sensor 分奇偶
	场的,可以根据实际情况修改为 V4L2_FIELD_INTERLACED 等,普通 
	sensor 一般不需要修改。*/
	info->sensor_field = V4L2_FIELD_NONE;

	return 0;
}

cci_dev_probe_helper()是唯一的分支函数,一会详细分析。
结构体cci_driver类似于csi_dev、isp_dev的作用,内部存放一个v4l2_subdev。每个sensor驱动都有一个cci_driver和sensor_info结构体变量,cci_driver对接v4l2框架,完成对Sensor的控制,而sensor_info保存Sensor相关的属性。
T7的media架构中为每一个sensor驱动自定义一个sensor_info结构体变量:
在这里插入图片描述
sensor_info结构体变量主要存放代表这个sensor的v4l2_subdev、sensor输出的视频格式(上面代码的sensor_formats[])、sensor输出得到视频分辨率即输出窗口大小(sensor_vin_sizes[]):
在这里插入图片描述
在这里插入图片描述
从上面两个结构体可以得到,这里配置nvp6134输出分辨率为1280*720、帧率为30fps的视频输出视频信息,视频采用V4L2_MBUS_FMT_YVYU8_1X16,参考https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/subdev-formats.html?highlight=v4l2_mbus_pixelcode,即MEDIA_BUS_FMT_YVYU8_1X16,一个颜色的像素点由16bit表示,bit07表示VU,bit815表示Y,一帧视频的像素安装YVYU来存储。这里的desc写明为“BT656 4CH”,难道用BT656总线协议的?其实不然,后面有说明如何配置为BT1120输出。

接下来看一下cci_dev_probe_helper(),这个函数是在cci_helper.c,这里的cci不是硬件上的cci接口,而是为了方便软件管理sensor设备而假设的、虚拟的(其实就是一个软件封装类),cci_helper.c里面有很多函数是给sensor驱动调用的。

int cci_dev_probe_helper(struct v4l2_subdev *sd, struct i2c_client *client,
			 const struct v4l2_subdev_ops *sensor_ops,
			 struct cci_driver *cci_drv)
{
		v4l2_i2c_subdev_init(sd, client, sensor_ops);1/*change sd name to sensor driver name*/
		snprintf(sd->name, sizeof(sd->name), "%s", cci_drv->name);
		cci_drv->sd = sd;
		v4l2_set_subdev_hostdata(sd, cci_drv);2)

	sd->grp_id = VIN_GRP_ID_SENSOR;
	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
	sd->internal_ops = &sensor_internal_ops;
	cci_sys_register(cci_drv);3cci_media_entity_init_helper(sd, cci_drv);4}

1、v4l2_i2c_subdev_init

很多V4L2设备都需要与子设备进行交互,通常情况下子设备用于音视频的编解码以及混合处理,对于网络摄像机来说子设备就是 sensors 和 camera 控制器。通常情况下它们都是 I2C 设备,但也有例外。v4l2_subdev 结构体被用于子设备管理。
每一个子设备驱动都必须有一个 v4l2_subdev 结构体,这个结构体可以作为独立的简单子设备存在,也可以嵌入到更大的结构体(自定义的子设备结构体,例如sensor_info)里面。通常会有一个由内核设置的低层次结构体(i2c_client,也就是上面说的 i2c 设备),它包含了一些设备数据,要调用 v4l2_set_subdevdata来设置子设备私有数据指针指向它,这样的话就可以很方便的从 subdev 找到相关的 I2C 设备数据。另外也需要设置低级别结构的私有数据指针指向 v4l2_subdev 结构体,方便从低级别的结构体访问 v4l2_subdev 结构体,达到双向访问的目的,对于 i2c_client 来说,可以用 i2c_set_clientdata 函数(上面的v4l2_i2c_subdev_init()函数里面就有调用它)来设置,其它的需使用与之相应的函数来完成设置。
为了达到双向访问的目的,也为了让I2C 驱动里面添加 v4l2_subdev 支持,sensor驱动中调用v4l2_i2c_subdev_init()。这要求先把 v4l2_subdev 结构体嵌入到I2C 实例结构体里面,有一些比较简单的 I2C 设备不需要自定义的状态结构体,此时只需要创建一个单独的 v4l2_subdev 结构体即可,例如上面提到的再nvp6134驱动中自定义的结构体变量如下所示:

    struct sensor_info {
        struct v4l2_subdev sd;
        ...  /* additional state fields */
    };

使用V4L2-common.c中v4l2_i2c_subdev_init() 去初始化一个 I2C 子设备,该函数会填充 v4l2_subdev 的所有成员并确保 v4l2_subdev 与 i2c_client 互相指向对方。这样后续可以使用以下内联函数来从 v4l2_subdev 的指针获取到 i2c_client 结构体:

struct i2c_client *client = v4l2_get_subdevdata(sd);

也可以从i2c_client结构体指针获取到v4l2_subdev结构体:

struct v4l2_subdev *sd = i2c_get_clientdata(client);

桥驱动可以使用以下帮助函数来创建一个I2C子设备:

// 该函数会加载给定的模块(可以为空)并且调用 i2c_new_device 根据传入的参数创建子设备结构体,最后注册 v4l2_subdev。
struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter,"module_foo", "chipid", 0x36, NULL);

v4l2_subdev(子设备)驱动的初始化使用 v4l2_subdev_init 函数来完成(上面的v4l2_i2c_subdev_init()函数里面就有调用它;v4l2_subdev_init 函数只是初始化一些 v4l2_subdev 的成员变量,内容比较简单),在初始化之后需要设置子设备结构体的 name 和 owner 成员(如果是 i2c 设备的话,这个在 i2c helper 函数里面就会被设置)。该部分 ioctl 可以直接通过用户空间的 ioctl 命令访问到。内核里面可以使用 v4l2_subdev_call 函数来对这些回调函数进行调用,这个在 pipeline 管理的时候十分受用。
v4l2_i2c_subdev_init()初始化了v4l2_subdev,我们为该函数传递了一个v4l2_subdev_ops的结构体指针用于初始化v4l2_subdev(例如nvp6134驱动中传递了sensor_ops),下面说明v4l2_subdev_ops的作用。
每个 v4l2_subdev 结构体都包含有一些函数指针,指向驱动实现的回调函数,内核对这些回调函数**进行了分类以避免出现定义了一个巨大的回调函数集、但里面只有那么几个用得上的尴尬情况。**最顶层的操作函数结构体内部包含指向各个不同类别操作函数结构体的指针成员,如下所示:

struct v4l2_subdev_core_ops {
        int (*log_status)(struct v4l2_subdev *sd);
        int (*init)(struct v4l2_subdev *sd, u32 val);
        ...
    };

    struct v4l2_subdev_tuner_ops {
        ...
    };

    struct v4l2_subdev_audio_ops {
        ...
    };

    struct v4l2_subdev_video_ops {
        ...
    };

    struct v4l2_subdev_pad_ops {
        ...
    };

    struct v4l2_subdev_ops {
        const struct v4l2_subdev_core_ops   *core;
        const struct v4l2_subdev_tuner_ops  *tuner;
        const struct v4l2_subdev_audio_ops  *audio;
        const struct v4l2_subdev_video_ops  *video;
        const struct v4l2_subdev_vbi_ops    *vbi;
        const struct v4l2_subdev_ir_ops     *ir;
        const struct v4l2_subdev_sensor_ops *sensor;
        const struct v4l2_subdev_pad_ops    *pad;
    };

这部分的设计我个人觉得是非常实用的,linux 要想支持大量的设备的同时又要保持代码的精简就必须得这样去实现。core ops成员对于所有的子设备来说都是通用的,其余的成员不同的驱动会有选择的去使用,例如:video 设备就不需要支持 audio 这个 ops 成员。如果子设备需要处理 video 数据,就需要实现 v4l2_subdev_video_ops 成员,如果要集成到 media_framework 里面,就必须要实现 v4l2_subdev_pad_ops 成员,此时使用 pad_ops 中与 format 有关的成员代替 v4l2_subdev_video_ops 中的相关成员。nvp6134用到的v4l2_subdev_ops结构体如下:
在这里插入图片描述
关于这些ops里面的函数啥时候被调用,在《从应用程序角度分析驱动的调用过程.docx》有说明。nvp6134.c文件里面大部分的代码都是为了实现这个ops里面的函数,部分Sensor共用的函数则再sensor_helper.c实现。相对原来t7的代码,我的建议是修改函数的命名,把特定sensor文件实现的函数与sensor_helper.c实现的函数区分出来,例如我们添加“nvp6134_”前缀(这些函数是我们移植过程中实现的):
在这里插入图片描述

3、v4l2_set_subdev_hostdata

回到cci_dev_probe_helper(),接下来它调用v4l2_set_subdev_hostdata()。
桥驱动器需要存储每一个子设备的私有数据,v4l2_subdev 结构体提供了主机私有数据指针成员来实现此目的,使用以下函数可以对主机私有数据进行访问控制:

    v4l2_get_subdev_hostdata();
    v4l2_set_subdev_hostdata();

从桥驱动器的角度来看,我们加载子设备模块之后可以用某种方式获取子设备指针。对于 i2c 设备来说,调用 i2c_get_clientdata 函数即可完成,其它类型的设备也有与之相似的操作,在内核里面提供了不少的帮助函数来协助完成这部分工作,编程时可以多多使用。
这里调用v4l2_set_subdev_hostdata()时传递的参数是v4l2_subdev和cci_driver,调用完成后完成下图红色的“连接”:
在这里插入图片描述
在Cci_helper.c的cci_write()和cci_read()中,我们调用v4l2_get_subdev_hostdata()从sensor_info. v4l2_subdev中取得cci_driver,然后通过cci驱动的读写接口(最终是读写I2C)完成对sensor的读写控制操作。
在实际应用中,不建议应用程序能直接操作sensor,应用工程师而已不希望了解到芯片内部实现过程,因此在本项目cci_write()和cci_read()并不推荐使用。设置sensor的功能需要被封装为ioctl或通过ctrl_handler机制,然后提供一个简单调用给应用程序。驱动则通过gpio_i2c_write()–>cci_write_a8_d8()–>i2c_transfer()来操作I2C,完成读写寄存器,cci_write_a8_d8()内部会调用v4l2_get_subdevdata(),相对的v4l2_set_subdevdata()是在csi_probe()–>__csi_init_subdev()–>v4l2_set_subdevdata()调用的。

4、cci_sys_register

cci_sys_register()函数先系统注册了一个名为“nvp6134”的cci设备。其中“nvp6134”是Kobj目录,其下有多个读写节点,其中"cci_client"节点将调用上面提到的cci_write()函数实现对sensor的写操作。
除了调试时可以操作这个“nvp6134”目录里面的节点,尽量不要让应用程序有权限可以操作它。

5、cci_media_entity_init_helper

再次截取cci_dev_probe_helper()函数中的代码:
在这里插入图片描述
在这里插入图片描述
如果需要与 media framework 进行集成,必须调用media_entity_init初始化 media_entity 结构体并将其嵌入到 v4l2_subdev 结构体里面。调用media_entity_init的第二个参数SENSOR_PAD_NUM=1,表示只有一个pad;第三个参数 media_pad结构体变量必须提前初始化,这里的media_pad结构体变量中flags初始化为MEDIA_PAD_FL_SOURCE,表示sensor是一个源pad,即提供数据给下一个pad的。entity 的引用计数在子设备节点被打开/关闭的时候会自动地增减。在销毁子设备的时候需使用 media_entity_cleanup 函数对 entity 进行清理。
另外,上面代码还设置了sd->internal_ops为sensor_internal_ops结构体。v4l2_subdev提供了一组内部操作函数,该内部函数的调用时机在下面有描述,原型如下所示:

struct v4l2_subdev_internal_ops {
    int (*registered)(struct v4l2_subdev *sd);
    void (*unregistered)(struct v4l2_subdev *sd);
    int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
    int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
};

这些函数仅供 v4l2 framework 使用,驱动程序不应该显式的去调用这些回调

  • registered/unregister:在子设备被注册(v4l2_device_register_subdev)/反注册的时候被调用
  • open/close:如果子设备在用户空间创建了设备节点,那么这两个函数就会在用户空间的设备节点被打开/关闭的时候调用到,主要是用来创建/关闭v4l2_fh以供v4l2_ctrl_handler等的使用。
    在这里插入图片描述
    如上,sensor_internal_ops结构体变量只显示了registered所指函数,即nvp6134子设备被注册时该函数被调用。该函数参考本文下一节的分析。

分析完sensor_probe()的cci_dev_probe_helper()调用,nvp6134驱动已经启动完成,此时等待的是APP程序通过v4l2节点或v4l2_subdev节点来访问驱动程序,类似的open/close/ioctl等文件调用方法最终将调用到nvp6134驱动中v4l2_subdev_ops结构体里面的函数。这部分等后面再分析。

nvp6134电源控制

vin.c vin_probe ()–>vin_md_register_entities ()–>__vin_register_module()–>cci_helper.c sensor_registered()
在这里插入图片描述
这里将以此调用nvp6134_sensor_power(PWR_ON)、nvp6134_sensor_init(0)和nvp6134_sensor_power(PWR_OFF)
打印信息如下:

[    4.813109] [VIN_LOG_CONFIG]nvp6134 is not in the sensor_list_t!
[    4.824349] sensor_power +
[    4.827361] [nvp6134]CSI_SUBDEV_PWR_ON!
[    4.831621] [VIN_LOG_POWER]mclk2 set rate 24000000, get rate 24000000
[    4.838780] [VIN_LOG_POWER]sensor mclk on, use_count 1!
[    4.846633] scsi 0:0:0:0: Direct-Access     Generic  STORAGE DEVICE   0250 PQ: 0 ANSI: 0
[    4.859380] sd 0:0:0:0: [sda] Attached SCSI removable disk
[    4.871029] [VIN_LOG_POWER]set regulator iovdd-csi = 2800000,return 0
[    4.891321] sensor_power -
[    4.894336] sensor_detect +
[    4.898077] [nvp6134]sensor id = 0x91
[    4.902134] sensor_detect -
[    4.905262] sensor_power +
[    4.908252] [nvp6134]CSI_SUBDEV_PWR_OFF!
[    4.912594] [VIN_LOG_POWER]sensor mclk off, use_count 0!
[    4.929222] [VIN_LOG_POWER]regulator_is already disabled
[    4.935204] sensor_power -
[    4.938203] [VIN_LOG_MD]nvp6134 register OK!

sensor_power()主要是控制时钟和GPIO,给sensor上电或掉电。而sensor_init()对sensor_info结构体做了一些初始化(注意这里并没有对实际的寄存器进行初始化):
在这里插入图片描述

nvp6134初始化

应用程序调用VIDIOC_STREAMON Ioctl时,会调用nvp6134驱动的nvp6134_sensor_reg_init(),调用关系如下:
vidioc_streamon
–> __vin_s_stream_handle()–>vin_pipe_ops. set_stream=__vin_pipeline_s_stream()–> __vin_subdev_set_stream()–>sensor_s_stream()–>nvp6134_sensor_reg_init()
nvp6134_sensor_reg_init()函数用于初始nvp6134寄存器。根据《SUNXI-VIN模块使用文档.pdf》的说明:
在旧的平台的寄存器初始化分为两个阶段,

  • 第一阶段是 sensor_init 时,该接口会在 video open 时回调,此时初始化的寄存器一般放在default_reg数组中,是一组公共寄存器。
  • 第二阶段是 set_fmt 时,此时主要设置与格式、分辨率以及帧率等相关的结存器组。
    这一种设计的初衷是在切换分辨率时只需重写较少的寄存器即可完成分辨率的切换,但是在实际调试过程中往往会出现一些很难解释的 bug。而且以上两个阶段进行 sensor 寄存器的设置的时机本身不太合理,因而在 vin 驱动中将采用 s_stream 接口,在 stream on 时统一对 sensor 的寄存器进行初始化。基于这个原因分析, s_stream 只需要将之前 sensor_init 中和 set_fmt 中寄存器初始化相关的代码抠出来即可。
    在这里插入图片描述
    首先关注sensor_init_hardware()(csi_dev_nvp6134.c的代码几乎都是这个函数的),提取一些主要的信息:
    在这里插入图片描述
    上面代码表明:本项目只有1个nvp6134芯片,芯片ID号为0x91,I2C地址为0x62。这里我们使用的芯片是128pin的nvp6134,因没有这个规格书,所以我们看看nvp6134c的规格书,它的ID号是0x90:
    在这里插入图片描述
    I2C地址配置如下图,需要参考硬件的SA1和SA0引脚电平,本项目的SA1=0,SA0=1,所以I2C地址是0x62(这里是使用7位地址,不需要右移一位,和触摸屏的不一样)
    在这里插入图片描述
    在这里插入图片描述
    上面代码表明,本项目的nvp6134默认输入是1280x720@25p(30)。
    在这里插入图片描述
    上面代码表明,本项目的nvp6134输出使用BT1120总线格式,总线数据包含了4个通道的数据:
    在这里插入图片描述
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值