linux v4l2-i2c 框架学习

一般摄像头传感器都会带有i2c接口等类似的总线接口来进行配置他,所以学习V4l2 的也不免会引入其他驱动框架的学习。

在学习V4L2 之前,先看一下I2C 驱动框架的实现。

从网上找了一张I2C的linux 驱动接口图:

网上很多资料将I2C驱动归结为3层。分为I2c 设备驱动层,I2c 核心层 以及I2c 总线驱动层。下面说一下我对这些层的理解。

1)I2c驱动层:分为i2c_client和i2c_driver,这个层是用来描述i2c总线上面挂载的某个具体设备的,比如说mcu的i2c总线上挂载了一个i2c 控制的camera,则需要实现i2c_client和i2c_driver。i2c_client 用来描述这个设备的具体硬件特性,而i2c_driver 则用来提供该设备的具体操作方法。

2 )I2c 核心层:该层基本不需要改动,主要是提供了一些注册,以及驱动层和总线层交互的一些函数,linux 把一些通用的东西抽象出来,放在核心层,便于驱动人员进行二次开发。

3 )I2c 总线驱动层,这边主要是实现I2c_adapter的一个结构,该结构要来描述mcu上面具体某个i2c 控制器,该层一般由soc厂家来做,对于某个soc,一般需要提供具体的I2c 控制器驱动。最终在i2c 总线上的数据收发,都会通过 i2c_adapter 这个结构提供的方法来实现。

 

然后再介绍一下v4l2的框架,下面是从网上找到的一张不错的结构图:

v4l2 有几个比较重要的结构:  vidoe_device,v4l2_dev,v4l2_subdev

其中 vidoe_device 这个结构,用来向上层应用软件提供字符设备接口的,该设备名称通常是 /dev/videox ,通常利用video_register_device 注册一个字符设备接口,并提供file_operation 操作函数。vidoe_device 一般指代camera 控制器,当然vidoe_device通常被嵌入更大的结构体里面,来描述更复杂的camera 控制器。而v4l2_dev则用来管理v4l2_subdev,一个v4l2_dev可以对应多个v4l2_subdev,v4l2_subdev通常是用来描述某个子设备的,比如说mcu外接的某个camera,则该camera可描述为v4l2_subdev。一般会把vidoe_device和v4l2_dev关联起来,这样通过camera 控制器就能找到v4l2_dev,进而再找到v4l2_subdev

上面的图里面具体也可以分为几层:

V4L2核心驱动: 核心层基本都不需要改动,提供通用的一些操作函数。

平台V4L2 驱动: 该层一般来说主要由soc厂家来实现,对于soc上面的具体的camera控制器,适配相应的驱动

具体的sensor驱动:当我们要使用不同的外接camera时,需要实现这一层。

所以,对于某个带i2c接口的camera来说,其在linux下v4l2和i2c的调用关系就变成如下图:

应用层软件,可以通过video接口,调用video的一些通用函数,来设备camera 控制器以及i2c camera sensor,当然也可以直接使用i2c的字符设备接口,来设置i2c总线上面挂载的设备。

有了上面的具体概念,下面就从代码层面来分析i2c和v4l2的调用流程,具体驱动代码基于nxp的IMX6ULRM,linux 内核版本为4.1.15 .

首先看一下I2c总线驱动(i2c控制器)的具体注册过程,具体代码在drivers/i2c/busses/i2c-imx.c中。

static const struct of_device_id i2c_imx_dt_ids[] = {
	{ .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
	{ .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
	{ .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
	{ /* sentinel */ }
};
static struct platform_driver i2c_imx_driver = {
	.probe = i2c_imx_probe,
	.remove = i2c_imx_remove,
	.driver	= {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = i2c_imx_dt_ids,
		.pm = IMX_I2C_PM,
	},
	.id_table	= imx_i2c_devtype,
};

static int __init i2c_adap_imx_init(void)
{
	return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);

可以看到利用了平台总线的方式,注册了i2c_imx_driver,这边有platform_driver,那么就需要platform_device作匹配,在dts中找到如下信息:

i2c1: i2c@43f80000 {
				#address-cells = <1>;
				#size-cells = <0>;
				compatible = "fsl,imx35-i2c", "fsl,imx1-i2c";
				reg = <0x43f80000 0x4000>;
				clocks = <&clks 51>;
				clock-names = "ipg_per";
				interrupts = <10>;
				status = "disabled";
			};

可以看到 comptiable 里面fsl,imx1-i2c是能匹配成功的,相应的设备信息也有了,所以这边会调用platform_driver的i2c_imx_probe。

static int i2c_imx_probe(struct platform_device *pdev)
{
	const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,
							   &pdev->dev);
	struct imx_i2c_struct *i2c_imx;
	struct resource *res;
	struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
	void __iomem *base;
	int irq, ret;
	dma_addr_t phy_addr;

	dev_dbg(&pdev->dev, "<%s>\n", __func__);

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "can't get irq number\n");
		return irq;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(base))
		return PTR_ERR(base);

	phy_addr = (dma_addr_t)res->start;
	i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);
	if (!i2c_imx)
		return -ENOMEM;

	if (of_id)
		i2c_imx->hwdata = of_id->data;
	else
		i2c_imx->hwdata = (struct imx_i2c_hwdata *)
				platform_get_device_id(pdev)->driver_data;

	/* Setup i2c_imx driver structure */
	strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));
	i2c_imx->adapter.owner		= THIS_MODULE;
	i2c_imx->adapter.algo		= &i2c_imx_algo;
	i2c_imx->adapter.dev.parent	= &pdev->dev;
	i2c_imx->adapter.nr		= pdev->id;
	i2c_imx->adapter.dev.of_node	= pdev->dev.of_node;
	i2c_imx->base			= base;

	/* Get I2C clock */
	i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(i2c_imx->clk)) {
		dev_err(&pdev->dev, "can't get I2C clock\n");
		return PTR_ERR(i2c_imx->clk);
	}

	ret = clk_prepare_enable(i2c_imx->clk);
	if (ret) {
		dev_err(&pdev->dev, "can't enable I2C clock\n");
		return ret;
	}
	/* Request IRQ */
	ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
			       IRQF_NO_SUSPEND, pdev->name, i2c_imx);
	if (ret) {
		dev_err(&pdev->dev, "can't claim irq %d\n", irq);
		goto clk_disable;
	}

	/* Init queue */
	init_waitqueue_head(&i2c_imx->queue);

	/* Set up adapter data */
	i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);

	/* Set up clock divider */
	i2c_imx->bitrate = IMX_I2C_BIT_RATE;
	ret = of_property_read_u32(pdev->dev.of_node,
				   "clock-frequency", &i2c_imx->bitrate);
	if (ret < 0 && pdata && pdata->bitrate)
		i2c_imx->bitrate = pdata->bitrate;

	/* Set up chip registers to defaults */
	imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
			i2c_imx, IMX_I2C_I2CR);
	imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);

	/* Add I2C adapter */
	ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
	if (ret < 0) {
		dev_err(&pdev->dev, "registration failed\n");
		goto clk_disable;
	}

	/* Set up platform driver data */
	platform_set_drvdata(pdev, i2c_imx);
	clk_disable_unprepare(i2c_imx->clk);

	dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq);
	dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res);
	dev_dbg(&i2c_imx->adapter.dev, "adapter name: \"%s\"\n",
		i2c_imx->adapter.name);
	dev_info(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n");

	/* Init DMA config if supported */
	i2c_imx_dma_request(i2c_imx, phy_addr);

	return 0;   /* Return OK */

clk_disable:
	clk_disable_unprepare(i2c_imx->clk);
	return ret;
}

在i2c_imx_probe中,主要关注i2c_add_numbered_adapter 函数,该函数注册了一个i2c_adapter 设备。

i2c_add_numbered_adapter
    ------->i2c_add_adapter
        ------->i2c_register_adapter

其中在i2c_add_adapter中:

int i2c_add_adapter(struct i2c_adapter *adapter)
{
	struct device *dev = &adapter->dev;
	int id;

	if (dev->of_node) {
		id = of_alias_get_id(dev->of_node, "i2c");
		if (id >= 0) {
			adapter->nr = id;
			return __i2c_add_numbered_adapter(adapter);
		}
	}

	mutex_lock(&core_lock);
	id = idr_alloc(&i2c_adapter_idr, adapter,
		       __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
	mutex_unlock(&core_lock);
	if (id < 0)
		return id;

	adapter->nr = id;

	return i2c_register_adapter(adapter);
}

可以看到设置了adapter->nr ,这个nr比较重要,后面在字符设备操作的时候,会利用这个总线number nr来得到某个i2c_adapter.

static struct i2c_algorithm i2c_imx_algo = {
	.master_xfer	= i2c_imx_xfer,
	.functionality	= i2c_imx_func,
};
static int i2c_register_adapter(struct i2c_adapter *adap)
{
    int res = 0;

    /* Can't register until after driver model init */
    if (unlikely(WARN_ON(!i2c_bus_type.p))) {
        res = -EAGAIN;
        goto out_list;
    }

    /* Sanity checks */
    if (unlikely(adap->name[0] == '\0')) {
        pr_err("i2c-core: Attempt to register an adapter with "
               "no name!\n");
        return -EINVAL;
    }
    if (unlikely(!adap->algo)) {
        pr_err("i2c-core: Attempt to register adapter '%s' with "
               "no algo!\n", adap->name);
        return -EINVAL;
    }

    rt_mutex_init(&adap->bus_lock);
    mutex_init(&adap->userspace_clients_lock);
    INIT_LIST_HEAD(&adap->userspace_clients);

    /* Set default timeout to 1 second if not already set */
    if (adap->timeout == 0)
        adap->timeout = HZ;

    dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    adap->dev.bus = &i2c_bus_type;
    adap->dev.type = &i2c_adapter_type;
    res = device_register(&adap->dev);
  ......................................................................................................................................................................................................................................................................................................................................................................................................................
exit_recovery:
    /* create pre-declared device nodes */
    of_i2c_register_devices(adap);
    acpi_i2c_register_devices(adap);
    acpi_i2c_install_space_handler(adap);

    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);

    /* Notify drivers */
    mutex_lock(&core_lock);
    bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
    mutex_unlock(&core_lock);

    return 0;

out_list:
    mutex_lock(&core_lock);
    idr_remove(&i2c_adapter_idr, adap->nr);
    mutex_unlock(&core_lock);
    return res;
}

可以看到把adapter总线设置为i2c_bus_type,并注册该adapter设备,而且可以看到adapter的i2c_imx_algo结构,该结构是adapter的核心结构,使用i2c控制器收发收据都由该结构中的函数来完成。

同时下面有两个比较重要的函数of_i2c_register_devices和i2c_scan_static_board_info。其中of_i2c_register_devices 用于在dts中描述了i2c子设备,来建立一个i2c_client,和后面注册i2c_drvier的时候进行匹配。而i2c_scan_static_board_info 则是当不用dts,利用了board_info 这个结构来描述了i2c子设备时,构建相应的i2c_client。我们这边使用的dts,所以进去看一下of_i2c_register_devices函数:

static void of_i2c_register_devices(struct i2c_adapter *adap)
{
	struct device_node *node;

	/* Only register child devices if the adapter has a node pointer set */
	if (!adap->dev.of_node)
		return;

	dev_dbg(&adap->dev, "of_i2c: walking child nodes\n");

	for_each_available_child_of_node(adap->dev.of_node, node)
		of_i2c_register_device(adap, node);
}

for_each_available_child_of_node为寻找adapter的子节,在dts中可以看到i2c的子节点:

&i2c2 {
	ar0134: ar0134@18 {
		compatible = "hon,sensors";
		reg = <0x18>;
		clocks = <&clks IMX6UL_CLK_CSI>;
		clock-names = "csi_mclk";
		csi_id = <0>;
		mclk = <24000000>;
		status = "okay";
		gpio_reset = <&gpio3 14 GPIO_ACTIVE_LOW>;
		gpio_power = <&gpio3 28 GPIO_ACTIVE_LOW>;
		reset_base = <&gpio3>;
		power_base = <&gpio3>;

		port {
			ar0134_ep: endpoint {
				remote-endpoint = <&csi1_ep>;
			};
		};
	};
};

其中hon,sensors即为camera sensor,作为i2c的一个子节点存在,所以就调用of_i2c_register_device

static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,
						 struct device_node *node)
{
	struct i2c_client *result;
	struct i2c_board_info info = {};
	struct dev_archdata dev_ad = {};
	const __be32 *addr;
	int len;

	dev_dbg(&adap->dev, "of_i2c: register %s\n", node->full_name);

	if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) {
		dev_err(&adap->dev, "of_i2c: modalias failure on %s\n",
			node->full_name);
		return ERR_PTR(-EINVAL);
	}

	addr = of_get_property(node, "reg", &len);
	if (!addr || (len < sizeof(int))) {
		dev_err(&adap->dev, "of_i2c: invalid reg on %s\n",
			node->full_name);
		return ERR_PTR(-EINVAL);
	}

	info.addr = be32_to_cpup(addr);
	if (info.addr > (1 << 10) - 1) {
		dev_err(&adap->dev, "of_i2c: invalid addr=%x on %s\n",
			info.addr, node->full_name);
		return ERR_PTR(-EINVAL);
	}

	info.of_node = of_node_get(node);
	info.archdata = &dev_ad;

	if (of_get_property(node, "wakeup-source", NULL))
		info.flags |= I2C_CLIENT_WAKE;

	result = i2c_new_device(adap, &info);
	if (result == NULL) {
		dev_err(&adap->dev, "of_i2c: Failure registering %s\n",
			node->full_name);
		of_node_put(node);
		return ERR_PTR(-EINVAL);
	}
	return result;
}

然后调用i2c_new_device:

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
	struct i2c_client	*client;
	int			status;

	client = kzalloc(sizeof *client, GFP_KERNEL);
	if (!client)
		return NULL;

	client->adapter = adap;

	client->dev.platform_data = info->platform_data;

	if (info->archdata)
		client->dev.archdata = *info->archdata;

	client->flags = info->flags;
	client->addr = info->addr;
	client->irq = info->irq;

	strlcpy(client->name, info->type, sizeof(client->name));

	/* Check for address validity */
	status = i2c_check_client_addr_validity(client);
	if (status) {
		dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
			client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
		goto out_err_silent;
	}

	/* Check for address business */
	status = i2c_check_addr_busy(adap, client->addr);
	if (status)
		goto out_err;

	client->dev.parent = &client->adapter->dev;
	client->dev.bus = &i2c_bus_type;
	client->dev.type = &i2c_client_type;
	client->dev.of_node = info->of_node;
	client->dev.fwnode = info->fwnode;

	i2c_dev_set_name(adap, client);
	status = device_register(&client->dev);
	if (status)
		goto out_err;

	dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
		client->name, dev_name(&client->dev));

	return client;

out_err:
	dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
		"(%d)\n", client->name, client->addr, status);
out_err_silent:
	kfree(client);
	return NULL;
}

可以看到注册了一个i2c_client device ,并且把i2c_client 和adapter 关联起来。

前面已经实现了adapter 和i2c_client,那么字符设备操作的接口和i2c_driver也是需要的。先来看如何注册i2c_adapter的字符设备操作接口。查看drivers/i2c/i2c-dev.c 文件

static const struct file_operations i2cdev_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= i2cdev_read,
	.write		= i2cdev_write,
	.unlocked_ioctl	= i2cdev_ioctl,
	.open		= i2cdev_open,
	.release	= i2cdev_release,
};
static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");

	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;

	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
	i2c_dev_class->dev_groups = i2c_groups;

	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	/* Bind to already existing adapters right away */
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev(I2C_MAJOR, "i2c");
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}



module_init(i2c_dev_init);

可以看到注册了一个字符设备,其中i2cdev_fops操作函数是所有i2c 字符设备的操作入口函数。

static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");

	res = register_chrdev(I2C_MAJOR, "karry-i2c", &i2cdev_fops);
	if (res)
		goto out;

	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
	i2c_dev_class->dev_groups = i2c_groups;

	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	/* Bind to already existing adapters right away */
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev(I2C_MAJOR, "i2c");
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}

其中最后的i2c_for_each_dev(NULL, i2cdev_attach_adapter); 遍历device,找到每个adapter,找到以后,调用i2cdev_attach_adapter,然后为其建立在/sys/下创建设备节点,最终用户层会由udev 会在/dev目录下生成i2c-x设备节点。

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;
	int res;

	if (dev->type != &i2c_adapter_type)
		return 0;
	adap = to_i2c_adapter(dev);

	i2c_dev = get_free_i2c_dev(adap);
	if (IS_ERR(i2c_dev))
		return PTR_ERR(i2c_dev);

	/* register this i2c device with the driver core */
	i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
				     MKDEV(I2C_MAJOR, adap->nr), NULL,
				     "i2c-%d", adap->nr);
	if (IS_ERR(i2c_dev->dev)) {
		res = PTR_ERR(i2c_dev->dev);
		goto error;
	}

	pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
		 adap->name, adap->nr);
	return 0;
error:
	return_i2c_dev(i2c_dev);
	return res;
}

i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
                     MKDEV(I2C_MAJOR, adap->nr), NULL,
                     "i2c-%d", adap->nr);用于生成设备节点,可以看到是以adap->nr为次设备号生成节点,可以再看一下字符设备i2cdev_open函数:

static int i2cdev_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode);
	struct i2c_client *client;
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;

	i2c_dev = i2c_dev_get_by_minor(minor);
	if (!i2c_dev)
		return -ENODEV;

	adap = i2c_get_adapter(i2c_dev->adap->nr);
	if (!adap)
		return -ENODEV;

	/* This creates an anonymous i2c_client, which may later be
	 * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
	 *
	 * This client is ** NEVER REGISTERED ** with the driver model
	 * or I2C core code!!  It just holds private copies of addressing
	 * information and maybe a PEC flag.
	 */
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client) {
		i2c_put_adapter(adap);
		return -ENOMEM;
	}
	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

	client->adapter = adap;
	file->private_data = client;

	return 0;
}

可以看到 minor = iminor(inode);以次设备号为索引,找到对应的adapter,所以对于同一套file_operation,可以打开不同的i2c_adapter设备。这个时候,已经可以直接利用/dev/i2c-x字符设备接口,来操作i2c总线上的i2c设备了。但是对于camera设备,当然需要video相关的操作才能继续进行。这边把v4l2相关的整合放到i2c_driver里面实现,在介绍i2c_driver之前,先介绍一下v4l2 video的初始化过程。先分析/drivers/media/mx6ul_capture.c 文件。

static const struct of_device_id mx6s_csi_dt_ids[] = {
	{ .compatible = "fsl,imx6ul-csi", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mx6s_csi_dt_ids);

static struct platform_driver mx6s_csi_driver = {
	.driver		= {
		.name	= MX6UL_CAM_DRV_NAME,
		.of_match_table = of_match_ptr(mx6s_csi_dt_ids),
		.pm = &mx6s_csi_pm_ops,
	},
	.probe	= mx6s_csi_probe,
	.remove	= mx6s_csi_remove,
};

module_platform_driver(mx6s_csi_driver);

系统初始化的时候,注册mx6s_csi_driver,也是利用pltform_bus的方式进行初始化的,mx6s_csi_driver指的就是camera 控制器的驱动,查看一下dts,可以发现:

csi: csi@021c4000 {
				compatible = "fsl,imx6ul-csi", "fsl,imx6s-csi";
				reg = <0x021c4000 0x4000>;
				interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_DUMMY>,
					<&clks IMX6UL_CLK_CSI>,
					<&clks IMX6UL_CLK_DUMMY>;
				clock-names = "disp-axi", "csi_mclk", "disp_dcic";
				status = "disabled";
			};

匹配imx6ul-csi,说明 platform_device 会被系统初始化好,match以后,会调用mx6s_csi_probe:

static int mx6s_csi_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct mx6s_csi_dev *csi_dev;
	struct video_device *vdev;
	struct resource *res;
	struct device_node *np = pdev->dev.of_node;
	int ret = 0;
	printk(KERN_INFO "mx6s_csi_probe\n");
	dev_dbg(dev, "initialising\n");

	/* Prepare our private structure */
	csi_dev = devm_kzalloc(dev, sizeof(struct mx6s_csi_dev), GFP_ATOMIC);
	if (!csi_dev) {
		dev_err(dev, "Can't allocate private structure\n");
		return -ENODEV;
	}

	。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

	ret = v4l2_device_register(dev, &csi_dev->v4l2_dev);
	if (ret < 0) {
		dev_err(dev, "v4l2_device_register() failed: %d\n", ret);
		return -ENODEV;
	}

	/* initialize locks */
	mutex_init(&csi_dev->lock);
	spin_lock_init(&csi_dev->slock);

	/* Allocate memory for video device */
	vdev = video_device_alloc();
	if (vdev == NULL) {
		ret = -ENOMEM;
		goto err_vdev;
	}

	snprintf(vdev->name, sizeof(vdev->name), "mx6ul-csi");

	vdev->v4l2_dev		= &csi_dev->v4l2_dev;
	vdev->fops			= &mx6s_csi_fops;
	vdev->ioctl_ops		= &mx6s_csi_ioctl_ops;
	vdev->release		= video_device_release;
	vdev->lock			= &csi_dev->lock;

	vdev->queue = &csi_dev->vb2_vidq;

	csi_dev->vdev = vdev;

	video_set_drvdata(csi_dev->vdev, csi_dev);
	mutex_lock(&csi_dev->lock);

	ret = video_register_device(csi_dev->vdev, VFL_TYPE_GRABBER, -1);
	if (ret < 0) {
		video_device_release(csi_dev->vdev);
		mutex_unlock(&csi_dev->lock);
		goto err_vdev;
	}

	/* install interrupt handler */
	if (devm_request_irq(dev, csi_dev->irq, mx6s_csi_irq_handler,
				0, "csi", (void *)csi_dev)) {
		mutex_unlock(&csi_dev->lock);
		dev_err(dev, "Request CSI IRQ failed.\n");
		ret = -ENODEV;
		goto err_irq;
	}

	mutex_unlock(&csi_dev->lock);

	ret = mx6sx_register_subdevs(csi_dev);
	if (ret < 0)
		goto err_irq;

	pm_runtime_enable(csi_dev->dev);
	return 0;

err_irq:
	video_unregister_device(csi_dev->vdev);
err_vdev:
	v4l2_device_unregister(&csi_dev->v4l2_dev);
	return ret;
}

v4l2_device_register中好像没做什么太重要的事情,把device和v4l2_dev 关联起来。

vdev = video_device_alloc();这个函数比较重要,分配了一个video_device,并且

    vdev->fops            = &mx6s_csi_fops;
    vdev->ioctl_ops        = &mx6s_csi_ioctl_ops;

这两个参数很重要,mx6s_csi_fops; 是个v4l2_file_operations,v4l2的相关操作都是调到这里来,mx6s_csi_ioctl_ops则是v4l2 ioctl 的具体实现都在这个函数里。vdev->queue = &csi_dev->vb2_vidq;这个queue则用来管理图像采集的buffer。

在video_register_device 函数中,还注册了一个字符设备:

static const struct file_operations v4l2_fops = {
	.owner = THIS_MODULE,
	.read = v4l2_read,
	.write = v4l2_write,
	.open = v4l2_open,
	.get_unmapped_area = v4l2_get_unmapped_area,
	.mmap = v4l2_mmap,
	.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = v4l2_compat_ioctl32,
#endif
	.release = v4l2_release,
	.poll = v4l2_poll,
	.llseek = no_llseek,
};


int __video_register_device(struct video_device *vdev, int type, int nr,
		int warn_if_nr_in_use, struct module *owner)
{
	int i = 0;
	int ret;
	int minor_offset = 0;
	int minor_cnt = VIDEO_NUM_DEVICES;
	const char *name_base;

	/* A minor value of -1 marks this video device as never
	   having been registered */
	vdev->minor = -1;

。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
	/* Part 3: Initialize the character device */
	vdev->cdev = cdev_alloc();
	if (vdev->cdev == NULL) {
		ret = -ENOMEM;
		goto cleanup;
	}
	vdev->cdev->ops = &v4l2_fops;
	vdev->cdev->owner = owner;
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	if (ret < 0) {
		printk(KERN_ERR "%s: cdev_add failed\n", __func__);
		kfree(vdev->cdev);
		vdev->cdev = NULL;
		goto cleanup;
	}

	/* Part 4: register the device with sysfs */
	vdev->dev.class = &video_class;
	vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
	vdev->dev.parent = vdev->dev_parent;
	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
	ret = device_register(&vdev->dev);
	if (ret < 0) {
		printk(KERN_ERR "%s: device_register failed\n", __func__);
		goto cleanup;
	}
	/* Register the release callback that will be called when the last
	   reference to the device goes away. */
	vdev->dev.release = v4l2_device_release;
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
	/* Part 6: Activate this minor. The char device can now be used. */
	set_bit(V4L2_FL_REGISTERED, &vdev->flags);

	return 0;

cleanup:
	mutex_lock(&videodev_lock);
	if (vdev->cdev)
		cdev_del(vdev->cdev);
	video_device[vdev->minor] = NULL;
	devnode_clear(vdev);
	mutex_unlock(&videodev_lock);
	/* Mark this video device as never having been registered. */
	vdev->minor = -1;
	return ret;
}

注册字符设备以后,最终生成/dev/videox节点,video_device[vdev->minor] = vdev; 操作根据次设备号在video_device中记录该video结构。对video_device节点的操作,直接调用到v4l2_fops,可以看一下v4l2_open:

static int v4l2_open(struct inode *inode, struct file *filp)
{
	struct video_device *vdev;
	int ret = 0;

	/* Check if the video device is available */
	mutex_lock(&videodev_lock);
	vdev = video_devdata(filp);
	/* return ENODEV if the video device has already been removed. */
	if (vdev == NULL || !video_is_registered(vdev)) {
		mutex_unlock(&videodev_lock);
		return -ENODEV;
	}
	/* and increase the device refcount */
	video_get(vdev);
	mutex_unlock(&videodev_lock);
	if (vdev->fops->open) {
		if (video_is_registered(vdev))
			ret = vdev->fops->open(filp);
		else
			ret = -ENODEV;
	}

	if (vdev->dev_debug & V4L2_DEV_DEBUG_FOP)
		printk(KERN_DEBUG "%s: open (%d)\n",
			video_device_node_name(vdev), ret);
	/* decrease the refcount in case of an error */
	if (ret)
		video_put(vdev);
	return ret;
}

vdev = video_devdata(filp); 根据次设备号从video_device[]数组中取出注册的video_device,然后调用vdev->fops->open函数,即前面的v4l2_file_operations操作集mx6s_csi_fops。贴一下这边实现的v4l2_file_operations 和v4l2_ioctl_ops 操作集函数:

static const struct v4l2_ioctl_ops mx6s_csi_ioctl_ops = {
	.vidioc_querycap          = mx6s_vidioc_querycap,
	.vidioc_enum_fmt_vid_cap  = mx6s_vidioc_enum_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap   = mx6s_vidioc_try_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap     = mx6s_vidioc_g_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap     = mx6s_vidioc_s_fmt_vid_cap,
	.vidioc_cropcap       = mx6s_vidioc_cropcap,
	.vidioc_s_crop        = mx6s_vidioc_s_crop,
	.vidioc_g_crop        = mx6s_vidioc_g_crop,
	.vidioc_reqbufs       = mx6s_vidioc_reqbufs,
	.vidioc_querybuf      = mx6s_vidioc_querybuf,
	.vidioc_qbuf          = mx6s_vidioc_qbuf,
	.vidioc_dqbuf         = mx6s_vidioc_dqbuf,
	.vidioc_g_std         = mx6s_vidioc_g_std,
	.vidioc_s_std         = mx6s_vidioc_s_std,
	.vidioc_querystd      = mx6s_vidioc_querystd,
	.vidioc_enum_input    = mx6s_vidioc_enum_input,
	.vidioc_g_input       = mx6s_vidioc_g_input,
	.vidioc_s_input       = mx6s_vidioc_s_input,
	.vidioc_streamon      = mx6s_vidioc_streamon,
	.vidioc_streamoff     = mx6s_vidioc_streamoff,
	.vidioc_g_parm        = mx6s_vidioc_g_parm,
	.vidioc_s_parm        = mx6s_vidioc_s_parm,
	.vidioc_enum_framesizes = mx6s_vidioc_enum_framesizes,
	.vidioc_enum_frameintervals = mx6s_vidioc_enum_frameintervals,
	.vidioc_s_ctrl	      = mx6s_vidioc_s_ctrl,
	.vidioc_g_ctrl	      = mx6s_vidioc_g_ctrl,
	.vidioc_default       = mx6s_vidioc_default,
};
static struct v4l2_file_operations mx6s_csi_fops = {
	.owner		= THIS_MODULE,
	.open		= mx6s_csi_open,
	.release	= mx6s_csi_close,
	.read		= mx6s_csi_read,
	.poll		= mx6s_csi_poll,
	.unlocked_ioctl	= mx6s_csi_ioctl, /* V4L2 ioctl handler */
	.mmap		= mx6s_csi_mmap,
};

这边有些函数是一定要实现的,有些是不一定要实现的。

在mx6s_csi_probe函数最后,还调用了mx6sx_register_subdevs函数,这个函数也比较重要,会在后面的i2c_driver 里面匹配v4l2_sudev使用:

static int mx6sx_register_subdevs(struct mx6s_csi_dev *csi_dev)
{
	struct device_node *parent = csi_dev->dev->of_node;
	struct device_node *node, *port, *rem;
	int ret;

	/* Attach sensors linked to csi receivers */
	for_each_available_child_of_node(parent, node) {
		if (of_node_cmp(node->name, "port"))
			continue;

		/* The csi node can have only port subnode. */
		port = of_get_next_child(node, NULL);
		if (!port)
			continue;
		rem = of_graph_get_remote_port_parent(port);
		of_node_put(port);
		if (rem == NULL) {
			v4l2_info(&csi_dev->v4l2_dev,
						"Remote device at %s not found\n",
						port->full_name);
			return -1;
		}

		csi_dev->asd.match_type = V4L2_ASYNC_MATCH_OF;
		csi_dev->asd.match.of.node = rem;
		csi_dev->async_subdevs[0] = &csi_dev->asd;

		of_node_put(rem);
		break;
	}

	csi_dev->subdev_notifier.subdevs = csi_dev->async_subdevs;
	csi_dev->subdev_notifier.num_subdevs = 1;
	csi_dev->subdev_notifier.bound = subdev_notifier_bound;

	ret = v4l2_async_notifier_register(&csi_dev->v4l2_dev,
					&csi_dev->subdev_notifier);
	if (ret)
		dev_err(csi_dev->dev,
					"Error register async notifier regoster\n");

	return ret;
}

这边会csi_dev->dev->of_node;根据camera控制器的节点,找到相应的port,然后进一步提取子节点,可以看到如下dts:

&csi {
	port {
		csi1_ep: endpoint {
			remote-endpoint = <&ar0134_ep>;
		};
	};
};
&i2c2 {
	ar0134: ar0134@18 {
		compatible = "hon,sensors";
		reg = <0x18>;
		clocks = <&clks IMX6UL_CLK_CSI>;
		clock-names = "csi_mclk";
		csi_id = <0>;
		mclk = <24000000>;
		status = "okay";
		gpio_reset = <&gpio3 14 GPIO_ACTIVE_LOW>;
		gpio_power = <&gpio3 28 GPIO_ACTIVE_LOW>;
		reset_base = <&gpio3>;
		power_base = <&gpio3>;

		port {
			ar0134_ep: endpoint {
				remote-endpoint = <&csi1_ep>;
			};
		};
	};
};

可以看到最终会找到ar0134_ep,而ar0134_ep 就是i2c 设备的节点。所以这边先建立一个异步的async_subdevs,放到通知链里面,等会后再进行subdevs的初始化。

csi_dev->subdev_notifier.bound = subdev_notifier_bound;设置绑定函数,等match了再调用。调用v4l2_async_notifier_register,把v4l2_dev 注册到subdev_notifier上面。

这边实现了camera驱动器的相关操作,现在通过/dev/videox 节点可以使用控制器了,但是camera 子设备还没看到呢,接下来分析一下i2c_driver的实现,即如何挂载v4l2_subdev.平常所说给一个产品编写一个camera驱动也大多指的是这边驱动代码的实现。

#ifdef CONFIG_USE_OF
static const struct of_device_id sensor_dt_match[] = {
	{.compatible = "hon,sensors",  .data = 0},
	{}
};
MODULE_DEVICE_TABLE(of, sensor_dt_match);
#endif

static struct i2c_driver sensor_i2c_driver = {
	.driver = {
		.name	= MODULE_NAME,
#ifdef CONFIG_USE_OF
		.of_match_table = sensor_dt_match,
#endif
		.pm = &i2c_sensor_pm_ops,
	},
	.probe		= sensor_probe,
	.remove		= sensor_remove,
	.id_table	= sensor_id,
};


module_i2c_driver(sensor_i2c_driver);

可以看到初始化的时候调用module_i2c_driver这个宏注册了i2c_driver,在注册的时候,会在device上面匹配i2c_client,因为前面注册完i2c_adapter 会根据dts注册i2c_client,所以这边会match,然后调用sensor_probe函数。

static int sensor_probe(struct i2c_client *client,
		     const struct i2c_device_id *devid)
{
	int ret;
	struct device *dev = &client->dev;
	struct sensor_dev *sensor;
	struct i2c_adapter * adapter = client->adapter; //to_i2c_adapter(client->dev.parent);
	const __be32 *parp;
	struct device_node *np = NULL;
	
	dev_info(&client->dev, "devid=%s, name=%s-%x\n", 
		devid->name, client->name, client->addr);

	sensor = kzalloc(sizeof(struct sensor_dev), GFP_KERNEL);
	if (!sensor) {
		dev_err(&client->dev, "Failed to allocate memory for private data.\n");
		return -ENOMEM;
	}
   
	if (strcmp("jade", dev->of_node->name) == 0) {
		sensor->type = SENSOR_JADE;
	} else if (strcmp("ar0134", dev->of_node->name) == 0) {
		sensor->type = SENSOR_AR0134;
	} else {
		sensor->type = SENSOR_GENERIC;
	}
	sensor->sensor_clk = devm_clk_get(dev, "csi_mclk");
	if (IS_ERR(sensor->sensor_clk)) {
		dev_err(dev, "get mclk failed\n");
		ret = PTR_ERR(sensor->sensor_clk);
		goto err_data;
	}

	ret = of_property_read_u32(dev->of_node, "mclk",
					&sensor->mclk);
	if (ret) {
		dev_err(dev, "mclk frequency is invalid\n");
		goto err_data;
	}

	sensor->i2c_client = client;

	sensor_set_clk_rate(sensor);

	v4l2_i2c_subdev_init(&sensor->subdev, client, &sensor_subdev_ops);

	ret = v4l2_async_register_subdev(&sensor->subdev);
	if (ret < 0) {
		dev_err(dev,
			"%s--Async register failed, ret=%d\n", __func__, ret);
		goto err_data;
	}


	if (sensor->type == SENSOR_AR0134 || sensor->type == SENSOR_JADE)
	{
		sensor->power_gpio = of_get_named_gpio(dev->of_node, "gpio_power", 0);
		if (!gpio_is_valid(sensor->power_gpio))
			dev_err(&client->dev, "can't find power gpio.\n");

		parp = of_get_property(dev->of_node, "reset_base", NULL);
		np = of_find_node_by_phandle(be32_to_cpup(parp));
		sensor->reset_base = of_iomap(np, 0);

		parp = of_get_property(dev->of_node, "power_base", NULL);
		np = of_find_node_by_phandle(be32_to_cpup(parp));
		sensor->power_base = of_iomap(np, 0);
	}

	if (sensor->type == SENSOR_JADE)
	{
		sensor->standby_gpio = of_get_named_gpio(dev->of_node, "gpio_standby", 0);
		if (!gpio_is_valid(sensor->power_gpio))
			dev_err(&client->dev, "can't find standby gpio.\n");

		parp = of_get_property(dev->of_node, "standby_base", NULL);
		np = of_find_node_by_phandle(be32_to_cpup(parp));
		sensor->standby_base = of_iomap(np, 0);

	}

	sensor->keepPowerOn = 0;
	dev_info(&client->dev, "device successfully probed.\n");
	device_init_wakeup(dev, true);
	pm_set_wakeup_min_time_ms(dev,1000);

	return 0;

err_data:
	kfree(dev);
	return ret;
}

可以看到调用v4l2_i2c_subdev_init:

void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,
		const struct v4l2_subdev_ops *ops)
{
	v4l2_subdev_init(sd, ops);
	sd->flags |= V4L2_SUBDEV_FL_IS_I2C;
	/* the owner is the same as the i2c_client's driver owner */
	sd->owner = client->dev.driver->owner;
	sd->dev = &client->dev;
	/* i2c_client and v4l2_subdev point to one another */
	v4l2_set_subdevdata(sd, client);
	i2c_set_clientdata(client, sd);
	/* initialize name */
	snprintf(sd->name, sizeof(sd->name), "%s %d-%04x",
		client->dev.driver->name, i2c_adapter_id(client->adapter),
		client->addr);
}

把 i2c_client 和 v4l2_subdev关联起来,并且设置了v4l2_subdev的操作函数sensor_subdev_ops,当vidoe的相关操作需要camera sensor支持时,则会进一步调用到sensor_subdev_ops中实现的函数,可以看一下里面的具体内容:

static struct v4l2_subdev_core_ops sensor_subdev_core_ops = {
	.s_power = sensor_s_power,
	.ioctl = sensor_ioctl,
	.init = sensor_init,
	.g_ctrl = sensor_subdev_g_ctrl,
	.s_ctrl = sensor_subdev_s_ctrl,
};

static struct v4l2_subdev_video_ops sensor_subdev_video_ops = {
	.enum_mbus_fmt = sensor_enum_fmt,  /* enum_mbus_fmt: enumerate pixel formats, provided by a video data source */
	.try_mbus_fmt = sensor_try_fmt,    /* try_mbus_fmt: try to set a pixel format on a video data source */
	.g_mbus_fmt = sensor_g_fmt,        /* g_mbus_fmt: get the current pixel format, provided by a video data source */
	.s_mbus_fmt = sensor_s_fmt,        /* s_mbus_fmt: set a pixel format on a video data source */
	.cropcap = sensor_cropcap,
	.g_crop = sensor_g_crop,
	.s_stream = sensor_s_stream,
};

static struct v4l2_subdev_ops sensor_subdev_ops = {
	.core = &sensor_subdev_core_ops,
	.video = &sensor_subdev_video_ops,
};

通常会以这样的v4l2_subdev_call 形式来调用里面的操作函数。

下面再看一下probe中的v4l2_async_register_subdev函数:

int v4l2_async_register_subdev(struct v4l2_subdev *sd)
{
	struct v4l2_async_notifier *notifier;

	mutex_lock(&list_lock);

	INIT_LIST_HEAD(&sd->async_list);

	list_for_each_entry(notifier, &notifier_list, list) {
		struct v4l2_async_subdev *asd = v4l2_async_belongs(notifier, sd);
		if (asd) {
			int ret = v4l2_async_test_notify(notifier, sd, asd);
			mutex_unlock(&list_lock);
			return ret;
		}
	}

	/* None matched, wait for hot-plugging */
	list_add(&sd->async_list, &subdev_list);

	mutex_unlock(&list_lock);

	return 0;
}

从notifier_list链中取出挂载的v4l2_async_notifier,前面在video 控制器初始化的时候我们已经挂载了一个结构在里面,所以这边能找到v4l2_async_subdev asd,调用v4l2_async_test_notify:

static int v4l2_async_test_notify(struct v4l2_async_notifier *notifier,
				  struct v4l2_subdev *sd,
				  struct v4l2_async_subdev *asd)
{
	int ret;

	/* Remove from the waiting list */
	list_del(&asd->list);
	sd->asd = asd;
	sd->notifier = notifier;

	if (notifier->bound) {
		ret = notifier->bound(notifier, sd, asd);
		if (ret < 0)
			return ret;
	}
	/* Move from the global subdevice list to notifier's done */
	list_move(&sd->async_list, &notifier->done);

	ret = v4l2_device_register_subdev(notifier->v4l2_dev, sd);
	if (ret < 0) {
		if (notifier->unbind)
			notifier->unbind(notifier, sd, asd);
		return ret;
	}

	if (list_empty(&notifier->waiting) && notifier->complete)
		return notifier->complete(notifier);

	return 0;
}

调用notifier->bound,该函数就是前面设置的subdev_notifier_bound:

static int subdev_notifier_bound(struct v4l2_async_notifier *notifier,
			    struct v4l2_subdev *subdev,
			    struct v4l2_async_subdev *asd)
{
	struct mx6s_csi_dev *csi_dev = notifier_to_mx6s_dev(notifier);

	/* Find platform data for this sensor subdev */
	if (csi_dev->asd.match.of.node == subdev->dev->of_node)
		csi_dev->sd = subdev;

	if (subdev == NULL)
		return -EINVAL;

	v4l2_info(&csi_dev->v4l2_dev, "Registered sensor subdevice: %s\n",
		  subdev->name);

	return 0;
}

可以看到设置了csi_dev->sd = subdev;这下可以直接从控制器 device直接寻址到subdev,video的操作从上到下基本打通了。

然后调用v4l2_device_register_subdev,把subdev赋值给v4l2_subdev 链表。

随便看一个subdev提供的操作函数:

int sensor_subdev_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *control)
{
	struct i2c_client *client = v4l2_get_subdevdata(sd);
	struct sensor_dev *sensor = to_sensor(client);

	if (sensor->type == SENSOR_AR0134) {
		u16 val;
		switch (control->id) {
		case V4L2_CID_GAIN:
			sensor_read(client, 0x30B0, &val);
			val &= 0xffcf;
			val |= ((control->value << 4) & 0x0030);
			return sensor_write(client, 0x30B0, val & 0xFFFF);
		case V4L2_CID_HFLIP:
			return -EINVAL;
		case V4L2_CID_EXPOSURE:
			/* 0x3012 - coarse
			 *  0x3014 - fine
			 */
			return sensor_write(client, 0x3012, control->value & 0xFFFF);
		}
	} else {
		dev_err(&client->dev, "error: unsupported control 0x%08X\n", control->id);
	}

	return 0;
}

可以看到获取了i2c_client,然后调用sensor_write:

static int sensor_write(struct i2c_client *client, u16 reg, u16 val)
{
	struct i2c_msg msg;
	u8 buf[4];
	int ret;

	buf[0] = reg >> 8;
	buf[1] = reg & 0xFF;
	buf[2] = val >> 8;
	buf[3] = val & 0xFF;

	msg.addr = client->addr;
	msg.flags = client->flags;
	msg.buf = buf;
	msg.len = sizeof(buf);

	ret = i2c_transfer(client->adapter, &msg, 1);
	if (ret >= 0)
		return 0;

	dev_err(&client->dev, "sensor_write reg(0x%x val:0x%x) failed !\n", reg, val);
	return ret;
}
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	int ret;

	/* REVISIT the fault reporting model here is weak:
	 *
	 *  - When we get an error after receiving N bytes from a slave,
	 *    there is no way to report "N".
	 *
	 *  - When we get a NAK after transmitting N bytes to a slave,
	 *    there is no way to report "N" ... or to let the master
	 *    continue executing the rest of this combined message, if
	 *    that's the appropriate response.
	 *
	 *  - When for example "num" is two and we successfully complete
	 *    the first message but get an error part way through the
	 *    second, it's unclear whether that should be reported as
	 *    one (discarding status on the second message) or errno
	 *    (discarding status on the first one).
	 */

	if (adap->algo->master_xfer) {
#ifdef DEBUG
		for (ret = 0; ret < num; ret++) {
			dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
				"len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
				? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
				(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
		}
#endif

		if (in_atomic() || irqs_disabled()) {
			ret = i2c_trylock_adapter(adap);
			if (!ret)
				/* I2C activity is ongoing. */
				return -EAGAIN;
		} else {
			i2c_lock_adapter(adap);
		}

		ret = __i2c_transfer(adap, msgs, num);
		i2c_unlock_adapter(adap);

		return ret;
	} else {
		dev_dbg(&adap->dev, "I2C level transfers not supported\n");
		return -EOPNOTSUPP;
	}
}

最终调用adapter 提供的方法来读写i2c设备。可以把上面讲的调用流程用下面的一张图来概括:

下面总结一下v4l2 里面重要操作集函数的调用流程。

从网上找了一张数据采集的流程图,稍微加工了下:

  • 12
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Linux中,使用V4L2来进行视频采集和处理。V4L2是Video4Linux2的简称,是一个内核框架,它提供了摄像头驱动程序的内核API,允许用户空间应用程序访问这些设备并进行配置。这里我们来介绍一种使用Linux V4L2-ctrl抓取单张图片的方法。 在这种情况下,用户可以使用V4L2控件库,通过V4L2控件库来控制摄像头设备驱动程序,来实现视频采集和图像截取的操作。 1. 安装V4L2-ctrl库 在Ubuntu中,可以使用以下命令安装V4L2-ctrl控件库: ``` sudo apt-get install libv4l-dev ``` 在其他Linux发行版中,也可以使用类似的命令进行安装。 2. 打开摄像头设备 使用以下命令打开摄像头设备: ``` $ v4l2-ctl --device=/dev/video0 --stream-mmap --stream-count=1 --stream-to=snapshot.raw ``` 以上命令将打开/dev/video0设备,并设置为内存映射流模式,采集一帧图像并将其保存到snapshot.raw文件中。 3. 转换图像 将采集到的原始图像转换为JPEG格式: ``` $ raw2jpeg snapshot.raw snapshot.jpeg ``` 这里使用了一个名为raw2jpeg的工具,它可以将原始图像数据转换为JPEG格式。 4. 查看抓取的图片 最后,可以使用图像查看器来查看抓取的JPEG图片。在Ubuntu中,可以使用以下命令来安装图像查看器: ``` sudo apt-get install eog ``` 完成安装后,可以使用eog来打开JPEG图像: ``` $ eog snapshot.jpeg ``` 这样,就可以使用Linux V4L2-ctrl抓取单张图片了。 总结:通过V4L2控件库,我们可以控制摄像头设备驱动程序进行视频采集和图像截取的操作。实现这个功能需要安装V4L2-ctrl库,打开摄像头设备,转换采集到的原始图像数据,并查看抓取的JPEG图片。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值