Linux IIC 驱动分析

一、整体架构图

每一个厂家出来soc上基本都会有一个或者多个I2C控制器,每一个I2C控制器下面接的就是SCL 和 SDA 两条线。I2C设备设备都是挂在这两条线下面的,这就是就硬件层的基本结构。

硬件层的上面就是Linux内核中的适配器驱动层,每一个soc厂家都会在Linux框架上实现自家IIC控制的的驱动程序,这部分是不需要设备驱动来实现,是由soc厂家的bsp工程师提供的。其实就是根据I2C控制器的寄存器操作实现了如何将数据送到I2C总线下的设备中去,以及怎么读取数据。厂家将这一层封装好,驱动工程师写驱动的时候就只需要根据具体的I2C设备来控制设备逻辑数据,不需要自己来实现I2C时序。简单点就说设备驱动只负责准备要发的数据,告诉适配器就可以了。至于怎么发就是厂家自己去实现了,毕竟只有他们才最熟悉自家的芯片

再往上就是内核里里面的I2C设备驱动层了,这一层就是由我们自己来实现的驱动,根据不同的硬件设备来准备不同的数据,实现正确的操作设备。在设备驱动层和适配器驱动层中间有一层I2C-CORE。这是Linux内核实现的提供给设备驱动层和和适配器层调用的公共函数,也是通过这一层将设备和适配器进行一个匹配,这一层也不需要我们自己实现,是由Linux内核抽象出去的。

最上面的就是APP层了,设备驱动安装好会生成一个设备文件,应用程序可以根据这个设备文件来对这个设备进行open,read,write,ioctl,  lseek,close等操作。

接下来主要是对内核层的设备驱动和适配器驱动做一个分析。

二、适配器(adapter)驱动分析

适配器驱动是每一个厂家自己实现的,在drivers\i2c\busses 目录下会有各个厂家是实现的源码,由三星的,atmel的,新塘的等等。这里分析的是Freescale 厂家实现的 i2c-imx.c。分析驱动都是先从入口函数开始:

static struct platform_driver i2c_imx_driver = {
	.probe = i2c_imx_probe,   //核心函数
	.remove = i2c_imx_remove,
	.driver = {
		.name = DRIVER_NAME,
		.pm = I2C_IMX_PM_OPS,
		.of_match_table = i2c_imx_dt_ids,
	},
	.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);

static void __exit i2c_adap_imx_exit(void)
{
	platform_driver_unregister(&i2c_imx_driver);
}

从i2c_adap_imx_init函数可以看出来这是一个平台总线驱动方式,所以我们可以直接分析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, ret=%d\n", ret);
		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 platform driver data */
	platform_set_drvdata(pdev, i2c_imx);

	pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT);
	pm_runtime_use_autosuspend(&pdev->dev);
	pm_runtime_set_active(&pdev->dev);
	pm_runtime_enable(&pdev->dev);

	ret = pm_runtime_get_sync(&pdev->dev);
	if (ret < 0)
		goto rpm_disable;

	/* 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);

	/* Init optional bus recovery function */
	ret = i2c_imx_init_recovery_info(i2c_imx, pdev);
	/* Give it another chance if pinctrl used is not ready yet */
	if (ret == -EPROBE_DEFER)
		goto rpm_disable;

	/* Add I2C adapter */
	ret = i2c_add_numbered_adapter(&i2c_imx->adapter); //注册adapter
	if (ret < 0)
		goto rpm_disable;

	pm_runtime_mark_last_busy(&pdev->dev);
	pm_runtime_put_autosuspend(&pdev->dev);

	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 */

rpm_disable:
	pm_runtime_put_noidle(&pdev->dev);
	pm_runtime_disable(&pdev->dev);
	pm_runtime_set_suspended(&pdev->dev);
	pm_runtime_dont_use_autosuspend(&pdev->dev);

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

从源码可以看出来首先就是获取一些硬件资源来设置 imx_i2c_struct 这个结构体的一些成员变量。其中需要重点分析的是会执行 i2c_imx->adapter.algo = i2c_imx_algo。 i2c_imx_algo这是一个i2c_algorithm结构变量,这个结构成员就是用来真正传输的函数.master_xfer=i2c_imx_xfer 和 .functionality=i2c_imx_func。 在这里先预先设置好,以便于后面的调用来传输数据。等到设备驱动要传送数据的时候实际上调用的就是.master_xfer这个成员回调函数,也就是它指向的i2c_imx_xfer,有兴趣的可以去分析下这个函数的实现,真正的IIC控制器(adapter)的驱动就是在这里实现的

将一些初始化工作做完之后就会调用 i2c_add_numbered_adapter 来添加一个适配器到内核。下面就对这个函数的流程做一个简要的分析。这个函数的入参是一个之前初始化完成的 struct i2c_adapter指针, 一路跟踪最终调用的就是  i2c_register_adapter(struct i2c_adapter *adap)。部分源码如下:

static int i2c_register_adapter(struct i2c_adapter *adap)
{
	int res = -EINVAL;

	dev_set_name(&adap->dev, "i2c-%d", adap->nr);
	adap->dev.bus = &i2c_bus_type;  //设备要注册在 i2c_bus_type 这条总线上
	adap->dev.type = &i2c_adapter_type;  //标明这是一个adapter类型,i2c_driver匹配i2c_client的时候会跳过这个类型。
	res = device_register(&adap->dev);
	if (res) {
		pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
		goto out_list;
	}

	//扫描设备树信息,将i2c设备节点都注册成i2c device,注意,注册的type不是i2c_adapter_type, 而是i2c_client_type, i2c_driver注册的时候就会匹配这个类型的i2c_client.
	of_i2c_register_devices(adap); 
	i2c_acpi_register_devices(adap);
	i2c_acpi_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;

}

这个函数其实就是三部分主要功能:第一,注册一个i2c_adapter_type类型的adapter到i2c_bus_type类型的总线上去。第二,就是注册一个i2c_client_type类型的client 到i2c_bus_type类型的总线上去。第三,调用bus_for_each_drv去实际探测i2c总线上的设备是否真实存在,如果存在就注册一个client到总线上。注意第三步是要求i2c_driver 的 detect ,address_list 都有设置才会执行,是为了更进一步确定探测到的设备是这个驱动真实想匹配的设备。如果一个设备驱动认为没必要执行这一步,detect成员变量就不会被设置,那就会直接根据设备树或者board_info,去注册一个i2c_client。第二步和第三步一般不会同时存在。具体的client注册流程可以分析of_i2c_register_devices(扫描设备树) 或者 i2c_scan_static_board_info (扫描板级信息)函数。无论哪一种方式注册的类型都是i2c_client_type。整个的注册调用流程如下图所示:

至此,soc上的adapter就和client进行了一个一一对应的绑定,而且都注册进内核总线上了。有多少个硬件adapter就会在总线上注册多少个client

三、设备驱动(i2c_driver)分析。

在设备驱动的init函数里面直接调用i2c_add_driver 添加一个驱动,会调用i2c_register_driver来注册一个驱动,源码如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;

	/* 如果注册过了就不能再次注册,直接返回错误码 */
	if (WARN_ON(!is_registered))
		return -EAGAIN;

	/* add the driver to the list of i2c drivers in the driver core */
	driver->driver.owner = owner;
    //在i2c_bus_type类型的总线下注册,和client注册的总线是同一个。
	driver->driver.bus = &i2c_bus_type;  
	INIT_LIST_HEAD(&driver->clients);

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
    //注册一个device_dirver, 会去i2c_bus_type总线上找到与之匹配device, 并获取到对应的i2c_driver和i2c_client, 并调用i2c_driver的prob函数
	res = driver_register(&driver->driver); 
	if (res)
		return res;

	pr_debug("driver [%s] registered\n", driver->driver.name);

	/* Walk the adapters that are already present */
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
}

在注册一个driver的时候会调用总线上的prob函数去匹配device, i2c_bus_type 类型的定义如下struct bus_type i2c_bus_type = {
    .name        = "i2c",
    .match        = i2c_device_match,
    .probe        = i2c_device_probe,
    .remove        = i2c_device_remove,
    .shutdown    = i2c_device_shutdown,
};

也就是调用i2c_device_match来匹配device, 如果匹配成功就会调用i2c_device_probe,这个函数会通过dev获取出一个包含其的i2c_client和一个i2c_driver,  最终调用i2c_driver->prob,并将对应的i2c——client作为形参传进去,这样i2c_driver就和i2c_cleint绑定起来了,当设备驱动想要和i2c设备通信的时候可以通过client->adapter->algo 这个结构里面的回调函数来实现。就是这个i2c的driver, client和adapter就一一绑定起来,所以设备驱动就只需要根据具体的设备需求准备好要交互的数据逻辑,数据的发送完全可以交给厂家的adapter驱动来实现。进到i2c_driver的prob函数后,就是设备驱动的实现过程了。以at24c02为你,在prob函数里面就是注册一个字符设备,提供给应用程序调用。read和write函数都可以调用i2c_transfer来完成,这个函数最终会将数据送到client->adapter->algo里面的传输函数中发送给I2C硬件设备。

注: 可以参考内核文档Documentation\i2c 下的instantiating-devices和writing-clients。以及源码 drivers/hwmon/lm90.c 这个驱动,自行分析。lm90.c这个驱动是有设置detect成员变量的。

四、实例分享

最后分享一个at24c02的驱动实例,这个例子在imx6ull 开发板,Linux4.9.88内核上实现,有需要的可以根据自己的实际需求参考。主要就是和设备树的匹配方面必须根据自己的实际板子来进行一个匹配。源码如下,仅供参考:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <asm/ioctl.h>
#include <asm/uaccess.h>

#define MY_DRV_NAME     "at24cxx"
#define MY_CLASS        "at24cxxClass"


static int AT24CXX_SIZE = 256;


struct my_at24_dev {
	struct i2c_client *	m_client; 
	struct class * 		m_eeprom_class;
	int 				m_major;
	struct mutex  		m_mutex;
};
static struct my_at24_dev * my_at24_devp;

unsigned int i2c_RxAddr;



static const struct i2c_device_id my_at24_ids[] = {
	{"my_24cxx", 0},  //和设备树节点 .compatible 属性值匹配
	{ },

};

static ssize_t my_at24c_read(struct file * flip, char __user * buff, size_t count, loff_t * offset)
{
	ssize_t retval = 0;
	unsigned char addr = i2c_RxAddr&0xff;

	unsigned char recv_buff[512];
	struct i2c_msg msgs[2];
	struct my_at24_dev * at24_dev = (struct my_at24_dev *)flip->private_data;
	//printk(KERN_INFO "%s enter,count=%d,i2c_RxAddr[0]=%d,i2c_RxAddr[1]=%d\r\n", __FUNCTION__,count,i2c_RxAddr[0],i2c_RxAddr[1]);
	mutex_lock(&at24_dev->m_mutex);
    /*first write the register offset address */
	msgs[0].addr = at24_dev->m_client->addr;    // address of slave device
	msgs[0].flags = 0;    // 0:write, 1:read
	msgs[0].len = 1;
	msgs[0].buf = &addr;

	msgs[1].addr = at24_dev->m_client->addr;    
	msgs[1].flags = 1;    
	msgs[1].len = sizeof(recv_buff);
	msgs[1].buf = recv_buff;

	retval = i2c_transfer(at24_dev->m_client->adapter, msgs, ARRAY_SIZE(msgs));
	//printk(KERN_INFO "%s: i2c_transfer return %d\r\n", __FUNCTION__,retval);
	if(retval != 2){
		printk(KERN_INFO "my_at24c_read failed");
		goto err;
	}
	
	retval = copy_to_user(buff, recv_buff, count);
	retval = count;
	
err:	
	mutex_unlock(&at24_dev->m_mutex);
	return retval;
}

static ssize_t my_at24c_write(struct file *flip, const char __user *buff, size_t count, loff_t *offset)
{
	ssize_t retval = 0;
	unsigned char send_buff[512];
	struct i2c_msg msgs[1];
	unsigned int len = 0;
	struct my_at24_dev * at24_dev = (struct my_at24_dev *)flip->private_data;
	mutex_lock(&at24_dev->m_mutex);

	if(count > AT24CXX_SIZE){
		retval = -1;
		goto err;
	}
    //offset address
	send_buff[len++] = i2c_RxAddr&0xff;

	retval = copy_from_user(&send_buff[len], buff, count);
	msgs[0].addr = at24_dev->m_client->addr;  // address of slave device
	msgs[0].flags = 0;    					// 0:write, 1:read
	msgs[0].len = len+count;
	msgs[0].buf = send_buff;
	retval = i2c_transfer(at24_dev->m_client->adapter, msgs, ARRAY_SIZE(msgs));
	if(retval > 0){
		retval = 0;
		//printk(KERN_INFO "my_at24c_write successful\r\n");
	}
	
	
err:	
	mutex_unlock(&at24_dev->m_mutex);
	return retval;
}
static loff_t  my_at24c_llseek(struct file * flip, loff_t offset, int flag)
{
	loff_t retval = 0;
	struct my_at24_dev * at24_dev = (struct my_at24_dev *)flip->private_data;
	//printk(KERN_INFO "%s enter,offset=%d, flag=%d\r\n", __FUNCTION__, (int)offset, flag);

	mutex_lock(&at24_dev->m_mutex);
	if(flag == SEEK_SET){
		if(offset >= AT24CXX_SIZE){
			i2c_RxAddr = 0;
		}else{
			i2c_RxAddr = offset;
		}
		
	}
	mutex_unlock(&at24_dev->m_mutex);
	return retval;
}
static int my_at24c_open(struct inode * node, struct file * flip)
{
	mutex_init(&my_at24_devp->m_mutex);
	i2c_RxAddr = 0;
	flip->private_data = my_at24_devp;  			//将at24结构存到flip中,便于后续读写使用
	return 0;
}

static long my_at24c_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
	return 0;
}

static int my_at24c_release (struct inode *inodep, struct file *flip) 
{ 
   flip->private_data = NULL; 
   i2c_RxAddr = 0;
   return 0; 
}

struct file_operations my_at24_fops = {
	.owner = THIS_MODULE,
	.llseek = my_at24c_llseek,
	.read =   my_at24c_read,
	.write=   my_at24c_write,
	.open =   my_at24c_open,
	.compat_ioctl = my_at24c_ioctl,
	.release =      my_at24c_release, 
};

/*will be called when the device and driver matched success*/
static int my_at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int retval = 0;
	
	my_at24_devp = kmalloc(sizeof(struct my_at24_dev), GFP_KERNEL);
	if(!my_at24_devp){
		retval = -ENOMEM;
		goto err;
	}
	memset (my_at24_devp, 0, sizeof (struct my_at24_dev));
	my_at24_devp->m_client = client;

	//register char device
	my_at24_devp->m_major = register_chrdev(0, MY_DRV_NAME, &my_at24_fops);   
	if(my_at24_devp->m_major < 0){
		printk (KERN_NOTICE "register_chrdev failed return %d",my_at24_devp->m_major); 
		goto err;
	}

	//create class
	my_at24_devp->m_eeprom_class = class_create(THIS_MODULE, MY_CLASS);
	if (IS_ERR(my_at24_devp->m_eeprom_class)) {
		printk(KERN_ERR "%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(my_at24_devp->m_major, MY_DRV_NAME);
		retval = -1;
		goto err;
 	}
	
	device_create(my_at24_devp->m_eeprom_class, NULL, MKDEV(my_at24_devp->m_major,0), NULL, "%s%d",MY_DRV_NAME, 0);  // 在 /dev下创建 MY_DRV_NAME 设备文件(/dev/Myled0, 1, ...)
	
err:
	return retval;
}


static int my_at24_remove(struct i2c_client *client)
{
	int retval = 0;
	device_destroy(my_at24_devp->m_eeprom_class, MKDEV(my_at24_devp->m_major, 0));
	class_destroy(my_at24_devp->m_eeprom_class);
    unregister_chrdev(my_at24_devp->m_major, MY_DRV_NAME);
	kfree (my_at24_devp); 
	return retval;
}



static struct i2c_driver my_at24_driver = {
	.driver = {
		.name = "at24cxx",
		.owner = THIS_MODULE,
	},
	.probe = my_at24_probe,
	.remove = my_at24_remove,
	.id_table = my_at24_ids,
};


static int __init my_at24_init(void)
{
	return i2c_add_driver(&my_at24_driver);
}
module_init(my_at24_init);

static void __exit my_at24_exit(void)
{
	i2c_del_driver(&my_at24_driver);
}
module_exit(my_at24_exit);

MODULE_DESCRIPTION("Driver for I2C EEPROM");
MODULE_AUTHOR("Daniel.Dai");
MODULE_LICENSE("GPL");




  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
CMOS_CHECK_POINTER(pstAeSnsDft); IMX335_SENSOR_GET_CTX(ViPipe, pstSnsState); CMOS_CHECK_POINTER(pstSnsState); if (IMX335_5M_30FPS_12BIT_LINEAR_MODE == pstSnsState->u8ImgMode) { u32Fll = IMX335_VMAX_5M_30FPS_12BIT_LINEAR; U32MaxFps = 30; pstSnsState->u32FLStd = u32Fll*U32MaxFps/DIV_0_TO_1_FLOAT(gu32STimeFps); } else if (IMX335_5M_30FPS_10BIT_WDR_MODE == pstSnsState->u8ImgMode) { u32Fll = IMX335_VMAX_5M_30FPS_10BIT_WDR; U32MaxFps = 30; pstSnsState->u32FLStd = u32Fll*U32MaxFps/DIV_0_TO_1_FLOAT(gu32STimeFps); if (0 != (pstSnsState->u32FLStd % 4)) { pstSnsState->u32FLStd = pstSnsState->u32FLStd - (pstSnsState->u32FLStd % 4) + 4; //Because FSC value an integer multiple of 8 } pstSnsState->u32FLStd = pstSnsState->u32FLStd*2; } else if (IMX335_4M_30FPS_10BIT_WDR_MODE == pstSnsState->u8ImgMode) { u32Fll = IMX335_VMAX_4M_30FPS_10BIT_WDR; U32MaxFps = 30; pstSnsState->u32FLStd = u32Fll*U32MaxFps/DIV_0_TO_1_FLOAT(gu32STimeFps); if (0 != (pstSnsState->u32FLStd % 4)) { pstSnsState->u32FLStd = pstSnsState->u32FLStd - (pstSnsState->u32FLStd % 4) + 4; //Because FSC value an integer multiple of 8 } pstSnsState->u32FLStd = pstSnsState->u32FLStd*2; } else if (IMX335_4M_25FPS_10BIT_WDR_MODE == pstSnsState->u8ImgMode) { u32Fll = IMX335_VMAX_4M_25FPS_10BIT_WDR; U32MaxFps = 25; pstSnsState->u32FLStd = u32Fll*U32MaxFps/DIV_0_TO_1_FLOAT(gu32STimeFps); if (0 != (pstSnsState->u32FLStd % 4)) { pstSnsState->u32FLStd = pstSnsState->u32FLStd - (pstSnsState->u32FLStd % 4) + 4; //Because FSC value an integer multiple of 8 } pstSnsState->u32FLStd = pstSnsState->u32FLStd*2; } else { u32Fll = IMX335_VMAX_5M_30FPS_12BIT_LINEAR; U32MaxFps = 30; pstSnsState->u32FLStd = u32Fll*U32MaxFps/DIV_0_TO_1_FLOAT(gu32STimeFps); } //pstSnsState->u32FLStd = u32Fll;

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟~阿斌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值