soc camera 子系统之soc camera host 与soc camera device 注册

本文详细解析了SOC Camera子系统中设备注册的过程,包括如何将Camera设备与Host进行关联,以及Camera设备如何通过V4L2系统进行注册。重点介绍了关键函数soc_camera_host_register和soc_camera_probe的工作原理。

      上一节中,我们已经知道,某个soc camera device 已经被添加到device链表中,那么,什么情况下,它会被注册呢。下面我们就结合camera host 进一步分析。

      我们来看看在soc_camera.c中有一个函数int soc_camera_host_register(struct soc_camera_host *ici),该函数有一个参数struct soc_camera_host的指针,那么这个函数是由谁来调用的呢,这就要牵涉到soc camera host驱动的编写了,暂时不表,在这里也不须太过关心。我们首先来贴出这个函数来分析一下

int soc_camera_host_register(struct soc_camera_host *ici)
{
	struct soc_camera_host *ix;
	int ret;
......

⑴
mutex_lock(&list_lock);
	list_for_each_entry(ix, &hosts, list) {
		if (ix->nr == ici->nr) {
			ret = -EBUSY;
			goto edevreg;
		}
	}
⑵
	ret = v4l2_device_register(ici->v4l2_dev.dev, &ici->v4l2_dev);
	if (ret < 0)
		goto edevreg;
⑶
	list_add_tail(&ici->list, &hosts);
	mutex_unlock(&list_lock);
⑷
	scan_add_host(ici);

	return 0;

edevreg:
	mutex_unlock(&list_lock);
	return ret;
}

      (1)处的代码,用于判断,当前host链表中是否已经存在index 为ici->nr的soc camera host,如果存在,则说明该index已经被占用,返回出错码-EBUSY。

      (2)处的代码调用了函数v4l2_device_register,那么该函数到底做了什么呢?有兴趣的同学可以到v4l2-device.c中查看源码,也可以简单的理解为向内核注册了soc camera host设备。当然,v4l2_device_register也会做一些自旋锁,互斥锁的初始化,以及引用计数和链表subdevs的初始化,其中特别注意,这个链表subdevs,这个会涉及到image capture设备,其实每个image capture设备也会对应一个subdev。

      (3)处的代码,就很简单了,将ici添加到本地链表hosts中。

      (4)处的代码调用了函数scan_add_host,那么这个函数又做了什么呢?好了,高潮来了,我们展开这个函数,来分析一下。

/* So far this function cannot fail */
static void scan_add_host(struct soc_camera_host *ici)
{
	struct soc_camera_device *icd;

	mutex_lock(&list_lock);

	list_for_each_entry(icd, &devices, list) {
		if (icd->iface == ici->nr) {
			int ret;
			icd->dev.parent = ici->v4l2_dev.dev;
			dev_set_name(&icd->dev, "%u-%u", icd->iface,
				     icd->devnum);
			ret = device_register(&icd->dev);
			if (ret < 0) {
				icd->dev.parent = NULL;
				dev_err(&icd->dev,
					"Cannot register device: %d\n", ret);
			}
		}
	}

	mutex_unlock(&list_lock);
}

      看到了吗,这个函数的核心还是一个for循环,遍历链表devices,确定devices链表上是否有camera device需要连接到host上,如果有,则将icd->dev.parent赋值为ici->v4l2_dev.dev,其中核心工作是dev_register(&icd->dev),如果对Linux内核中的“总线,设备,驱动”模型熟悉的话,相信就会明白接下来会发生什么事情。大家还记得吗,icd->dev所挂载的总线是本地的总线soc_camera_bus_type,其中soc_camera_type代码如下:

struct bus_type soc_camera_bus_type = {
	.name		= "soc-camera",
	.probe		= soc_camera_probe,
	.remove		= soc_camera_remove,
	.suspend	= soc_camera_suspend,
	.resume		= soc_camera_resume,
};

      大家很容易就能发现,该bus_type并没有实现match函数,而实现了probe函数,那么这么做的目的是什么呢?我们来先展开device_register函数的调用流程


      看了流程图之后,大家是不是明白了,接下来会调用soc_camera_bus_type的成员函数probe了,好了,我们来着重分析soc_camera_probe函数,首先将代码展开如下:

/* Called during host-driver probe */
static int soc_camera_probe(struct device *dev)
{
	⑴
	struct soc_camera_device *icd = to_soc_camera_dev(dev);
	struct soc_camera_host *ici = to_soc_camera_host(dev->parent);
	struct soc_camera_link *icl = to_soc_camera_link(icd);
	struct device *control = NULL;
	int ret;
   ⑵
	ret = ici->ops->add(icd);
	if (ret < 0)
		goto eadd;


	/* The camera could have been already on, try to reset */
	if (icl->reset)
		icl->reset(icd->pdev);

	/* Must have icd->vdev before registering the device */
⑶
	ret = video_dev_create(icd);
	if (ret < 0)
		goto evdc;
⑷
	/* Non-i2c cameras, e.g., soc_camera_platform, have no board_info */
	if (icl->board_info) {
		ret = soc_camera_init_i2c(icd, icl);
		if (ret < 0)
			goto eadddev;
	} else if (!icl->add_device || !icl->del_device) {
		ret = -EINVAL;
		goto eadddev;
	} else {
⑸
		if (icl->module_name)
			ret = request_module(icl->module_name);

		ret = icl->add_device(icl, &icd->dev);
		if (ret < 0)
			goto eadddev;

		/*
		 * FIXME: this is racy, have to use driver-binding notification,
		 * when it is available
		 */
		control = to_soc_camera_control(icd);
		if (!control || !control->driver || !dev_get_drvdata(control) ||
		    !try_module_get(control->driver->owner)) {
			icl->del_device(icl);
			goto enodrv;
		}
	}
⑹
	/* At this point client .probe() should have run already */
	ret = soc_camera_init_user_formats(icd);
	if (ret < 0)
		goto eiufmt;

	icd->field = V4L2_FIELD_ANY;

	icd->vdev->lock = &icd->video_lock;

	/*
	 * ..._video_start() will create a device node, video_register_device()
	 * itself is protected against concurrent open() calls, but we also have
	 * to protect our data.
	 */
	mutex_lock(&icd->video_lock);
⑺
	ret = soc_camera_video_start(icd);
	if (ret < 0)
		goto evidstart;

	......
}

      首先来看(1)处的几个重要的内联函数,展开代码。

static inline struct soc_camera_device *to_soc_camera_dev(
	const struct device *dev)
{
	return container_of(dev, struct soc_camera_device, dev);
}

      相信,对于内核链表相当熟悉的人,那么对container_of这个宏也应该很清楚了,这是通过指向结构体内部成员变量的指针,来获取该结构体的地址。

      接着看下一个内联函数,展开代码如下:

static inline struct soc_camera_host *to_soc_camera_host(
	const struct device *dev)
{
	struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);

	return container_of(v4l2_dev, struct soc_camera_host, v4l2_dev);
}

      这个内联函数也很好理解,首先,通过icd->dev.parent获取v4l2_dev,可能有人会问,既然get了,哪里set的呢,首先,大家要搞清楚,icd->dev.parent= ici->v4l2_dev.dev(这个赋值是在scan_add_host里面做的),然后,我们要知道,哪里set了呢,前面分析的函数soc_camera_host_register还记得吧,就是在该函数体内通过调用函数v4l2_device_register函数来设置的。继续分析,获取了v4l2_dev之后,就简单了,还是通过container_of来获取外部结构体soc_camera_host。

      接着展开最后一个内联函数.

static inline struct soc_camera_link *to_soc_camera_link(
	const struct soc_camera_device *icd)
{
	return icd->dev.platform_data;
}

      这个函数也是相当的简单,直接从icd->dev.platform_data中获取soc_camera_link,大家可能会问,这个哪里赋值的呢?我们在上一篇博客分析soc_camera_devic 初始化的时候,分析了platform driver probe函数,该函数体内调用了soc_camera_device_init函数,就是在这个函数体内,将soccamera link赋值给了icd->dev.platform_data。

      好了,这几个内联函数解析完了,不论是在soc_camera.c中或者自己写soc camera host驱动的时候,就会经常用到这几个宏。接下来继续分析soc_camera_probe函数,再来看(2)处的代码,该代码会调用soc camera host 驱动实现的一个add函数,该add函数主要是做一些硬件方面的初始化,比如时钟的使能。

      继续分析(3)处的代码,闲话少说,直接展开video_dev_create的代码。

static int video_dev_create(struct soc_camera_device *icd)
{
	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
	struct video_device *vdev = video_device_alloc();

	if (!vdev)
		return -ENOMEM;

	strlcpy(vdev->name, ici->drv_name, sizeof(vdev->name));

	vdev->parent		= &icd->dev;
	vdev->current_norm	= V4L2_STD_UNKNOWN;
	vdev->fops		= &soc_camera_fops;
	vdev->ioctl_ops		= &soc_camera_ioctl_ops;
	vdev->release		= video_device_release;
	vdev->tvnorms		= V4L2_STD_UNKNOWN;

	icd->vdev = vdev;

	return 0;
}

在分析video_dev_create这个函数之前,我们要明白V4L2 系统的体现结构,soccamera 子系统是基于V4L2 系统的,所以来说,不论是soc camera host还是soc camera device都是存在于soc camera 子系统中的,而对于v4l2 系统来说,它不管你将host或者image capture抽象成什么结构体,最终,你要注册给V4L2一个抽象出来的实体,那就是Video_device,V4L2系统就是通过这个实体来起到承上启下的作用的。所以在video_dev_create函数体内,要申请一段内存,并且初始化video_device,其中重要的三个初始化如下:

vdev->parent = &icd->dev;
vdev->fops = &soc_camera_fops;
	vdev->ioctl_ops  = &soc_camera_iotcl_ops;

      至于这三个成员的初始化为什么重要,先不表,大家先留个印象,待用时我们在着重分析。

      接着分析(4)处的代码,先看if体内,对I2C熟悉的同学,应该明白,看到board info,大家应该就明白了,这是要初始化i2c啊。在前面的博客soc camera 子系统简介中,我曾经提到过,soc camera 子系统主要是针对当前的移动平台,而多数移动平台的摄像头都是固定在移动设备上的,一般是通过i2c总线集成到soc上面的,因此这里需要初始化i2c,先展开代码。

static int soc_camera_init_i2c(struct soc_camera_device *icd,
			       struct soc_camera_link *icl)
{
	struct i2c_client *client;
	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
	struct i2c_adapter *adap = i2c_get_adapter(icl->i2c_adapter_id);
	struct v4l2_subdev *subdev;

	if (!adap) {
		dev_err(&icd->dev, "Cannot get I2C adapter #%d. No driver?\n",
			icl->i2c_adapter_id);
		goto ei2cga;
	}

	icl->board_info->platform_data = icd;

	subdev = v4l2_i2c_new_subdev_board(&ici->v4l2_dev, adap,
				icl->board_info, NULL);
	if (!subdev)
		goto ei2cnd;

	client = v4l2_get_subdevdata(subdev);

	/* Use to_i2c_client(dev) to recover the i2c client */
	dev_set_drvdata(&icd->dev, &client->dev);

	return 0;
ei2cnd:
	i2c_put_adapter(adap);
ei2cga:
	return -ENODEV;
}

      这个函数分析起来稍显繁琐,因为要牵涉到image capture 驱动(主要是i2c driver的probe函数),我只讲一下大致做了哪些事情。

      首先,大家要注意这个函数内部出现了一个新的结构体,v4l2_subdev,这里是针对image capture的,即每个image capture都会对应一个v4l2_subdev,这个结构体内有一个重要的成员变量v4l2_subdev_ops,是有image capture驱动来实现的,而v4l2_subdev就是soc_camera.c以及host驱动来调用imagecapture相关实现的中介。我们来画一个subdev和soc camera device得关系图,方便大家理解。


       通过这个关系图,很好理解下面的内联函数。


static inline struct device *to_soc_camera_control(
	const struct soc_camera_device *icd)
{
	return dev_get_drvdata(&icd->dev);
}

static inline struct v4l2_subdev *soc_camera_to_subdev(
	const struct soc_camera_device *icd)
{
	struct device *control = to_soc_camera_control(icd);
	return dev_get_drvdata(control);
}

      大家可以根据上面的关系图,来理解内联函数soc_camera_to_subdev,这个内联函数就是通过soccamera device来获取subdev的。其实subdev和soc camera device都是对应一个image capture,只是所起的作用不一样罢了。

      (6)处的代码用于获取当前摄像头所支持的所有格式,并且存储在icd->user_formats数组中,是通过subdev来调用image capture 驱动实现的回调函数来实现的,

      (7)处的代码先展开如下:

/*
 * Called from soc_camera_probe() above (with .video_lock held???)
 */
static int soc_camera_video_start(struct soc_camera_device *icd)
{
	const struct device_type *type = icd->vdev->dev.type;
	int ret;

	if (!icd->dev.parent)
		return -ENODEV;

	if (!icd->ops ||
	    !icd->ops->query_bus_param ||
	    !icd->ops->set_bus_param)
		return -EINVAL;

	ret = video_register_device(icd->vdev, VFL_TYPE_GRABBER, -1);
	if (ret < 0) {
		dev_err(&icd->dev, "video_register_device failed: %d\n", ret);
		return ret;
	}

	/* Restore device type, possibly set by the subdevice driver */
	icd->vdev->dev.type = type;

	return 0;
}

      主要做的工作就是向V4L2系统注册设备video device。下一篇会继续分析!

 











评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值