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