通用i2c设备驱动分析

Linux i2c驱动框架分析 (一)
Linux i2c驱动框架分析 (二)
Linux i2c驱动框架分析 (三)
通用i2c设备驱动分析

内核提供了一个通用的i2c驱动,这个驱动程序为每个适配器提供了设备文件的功能,下面就简要分析一下这个驱动程序,对应的驱动文件为drivers\i2c\i2c-dev.c。

入口函数如下:

static int __init i2c_dev_init(void)
{
	int res;

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

	//申请字符设备设备号,主设备号89
	res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
	if (res)
		goto out;

	//创建class,用于后面创建设备文件
	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;

	/* 注册一个通知链,在注册/卸载适配器时会调用到
   * i2cdev_notifier.notifier_call函数,即i2cdev_notifier_call,而这个
   * 函数根据不同的事件(注册/卸载),进一步调用不同的处理函数,对于增添事件
   * 调用i2cdev_attach_adapter函数,该函数也是下面要分析的
   */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	//遍历i2c bus上的适配器,调用i2cdev_attach_adapter函数
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

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


static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
			 void *data)
{
	struct device *dev = data;

	switch (action) {
	case BUS_NOTIFY_ADD_DEVICE:
		return i2cdev_attach_adapter(dev, NULL);
	case BUS_NOTIFY_DEL_DEVICE:
		return i2cdev_detach_adapter(dev, NULL);
	}

	return 0;
}


static struct notifier_block i2cdev_notifier = {
	.notifier_call = i2cdev_notifier_call,
};

i2cdev_attach_adapter定义如下:

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

	//字符设备相关设置,file_operations设置为i2cdev_fops
	cdev_init(&i2c_dev->cdev, &i2cdev_fops);
	i2c_dev->cdev.owner = THIS_MODULE;

	//注册字符设备,bus num作为次设备号
	res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);
	if (res)
		goto error_cdev;

	/* 创建设备文件 */
	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:
	cdev_del(&i2c_dev->cdev);
error_cdev:
	put_i2c_dev(i2c_dev);
	return res;
}

i2c-dev.c实现了i2c适配器设备文件的功能,为应用层接提供了统一的编程接口。当应用层open、read等操作时,会调用到驱动层对应操作函数。


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,
};

如,当应用层“open /dev/i2c-0”时,会调用到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;

	//通过次设备号获得对应的i2c_adapter 
	adap = i2c_get_adapter(minor);
	if (!adap)
		return -ENODEV;

	//创建一个临时的i2c_client
	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;
}

close时,会释放临时的i2c_client:

static int i2cdev_release(struct inode *inode, struct file *file)
{
	struct i2c_client *client = file->private_data;

	i2c_put_adapter(client->adapter);
	kfree(client);
	file->private_data = NULL;

	return 0;
}

这个临时i2c_client的地址是通过ioctl设置的,如“ioctl(fd, I2C_SLAVE, 0x50)”,将设备地址设置为0x50,ioctl操作最终会调用到i2cdev_ioctl函数:

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct i2c_client *client = file->private_data;
	unsigned long funcs;

	dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
		cmd, arg);

	switch (cmd) {
	//设置地址
	case I2C_SLAVE:
	case I2C_SLAVE_FORCE:
		if ((arg > 0x3ff) ||
		    (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
			return -EINVAL;

		/*如果这个地址对应的i2c设备已有驱动与之匹配,并且不是强制设置(I2C_SLAVE_FORCE)
     *   返回-EBUSY
     */
		if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
			return -EBUSY;
		/* REVISIT: address could become busy later */
		client->addr = arg;
		return 0;
	......
	case I2C_RDWR:
		return i2cdev_ioctl_rdwr(client, arg);

	case I2C_SMBUS:
		return i2cdev_ioctl_smbus(client, arg);
	......
	}
	return 0;
}

下面分析最主要的读写命令I2C_RDWR:

static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
		unsigned long arg)
{
	struct i2c_rdwr_ioctl_data rdwr_arg;
	struct i2c_msg *rdwr_pa;
	u8 __user **data_ptrs;
	int i, res;

	......

	/* 调用i2c_transfer函数发送数据,该函数最终会调用底层的adap->algo->master_xfer
	 * 函数进行发送数据
   */ 
	res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);
	......
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值