前文介绍了利用/dev/i2c-0在应用层完成对i2c设备的操作,但很多时候我们还是习惯为i2c设备在内核层编写驱动程序。目前内核支持两种编写i2c驱动程序的方式。这里分别称这两种方式为“Adapter方式(LEGACY)”和“Probe方式(new style)”。
在介绍i2c设备驱动前首先认识下两个重要的结构体:
struct i2c_driver { int (*attach_adapter)(struct i2c_adapter *); /*依附i2c_adapter的函数指针*/ int (*detach_client)(struct i2c_client *); /*脱离i2c_client函数指针*/ int (*probe)(struct i2c_client *); /* driver model interfaces that don't relate to enumeration */ int (*command)(struct i2c_client *client,unsigned int cmd, void *arg); struct device_driver driver; |
struct i2c_client { unsigned short flags; //设备的标志,如唤醒标志等等 /* chip address - NOTE: 7bit */ /* addresses are stored in the */ /* _LOWER_ 7 bits */ unsigned short addr; //设备的地址 char name[I2C_NAME_SIZE]; //设备的名字 struct i2c_adapter *adapter; //设备所属的适配器 struct i2c_driver *driver; //设备的driver struct device dev; /* the device structure */ int irq; //设备所使用的中断号 char driver_name[KOBJ_NAME_LEN]; struct list_head list; //链表头 struct completion released; //用于同步 }; |
下面利用一个实例介绍“Adapter方式“的实现。
(1)/* 构造i2c_driver */
static struct i2c_driver at24c02_driver = {
.driver = {
.name = "at24c02",
},
.attach_adapter = at24c02_attach_adapter,
.detach_client = at24c02_detach_client,
};
(2)/*注册i2c_driver结构*/
static int __init at24c02_init(void)
{
i2c_add_driver(&at24c02_driver);
return 0;
}
执行i2c_add_driver(&at24c02_driver)后会,如果内核中已经注册了i2c适配器,则顺序调用这些适配器来连接我们的i2c设备。此过程是通过调用i2c_driver中的attach_adapter方法完成的。具体实现形式如下:
(3)/* 识别、探测 I2C设备 */
static int at24c02_attach_adapter(struct i2c_adapter *adapter)
{
/* i2c_probe首先会确定是否存在地址为0x50的设备
* 如果存在, 则使用at24c02_detect进一步处理
*/
return i2c_probe(adapter, &at24c02_addr_data, at24c02_detect);
/*
adapter:适配器
at24c02_addr_data:地址信息at24c02_addr_data.normal_i2c[]
at24c02_detect:探测到设备后调用的函数
*/
}
地址信息at24c02_addr_data是由下面代码指定的
static struct i2c_client_address_data at24c02_addr_data = {
.normal_i2c = at24c02_addr,
.probe = ignore,
.ignore = ignore,
};
static unsigned short at24c02_addr[] = { 0x50, I2C_CLIENT_END };
注意:normal_i2c里的地址必须是你i2c芯片的地址。否则将无法正确探测到设备。
(4)构建i2c_client,并注册字符设备驱动
i2c_probe在探测到目标设备后,后调用at24c02_detect,并把当时的探测地址addr作为参数传入。
/*实现检测函数*/
static int at24c02_detect(struct i2c_adapter *adap, int addr, int kind)
{
int rc;
u8 value;
printk("at24c02_detect\n");
/* 分配一个i2c_client结构体 */
at24c02_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (!at24c02_client)
return -ENOMEM;
strncpy(at24c02_client->name, name, strlen(name)+1);/*static char name[] = "at24c02";*/
at24c02_client->addr = addr; /* 0x50 */
at24c02_client->adapter = adap;
at24c02_client->driver = &at24c02_driver;
/* 注册i2c_client */
if ((rc = i2c_attach_client(at24c02_client)) != 0) {
kfree(at24c02_client);
return rc;
}
/* 注册字符设备。那么自然要先实现字符设备file_operations结构体*/
major = register_chrdev(0, "at24c02", &at24c02_fops);
// 构造供udev/mdev创建设备节点用的信息
at24c02_class = class_create(THIS_MODULE, "at24c02");
if (IS_ERR(at24c02_class))
{
unregister_chrdev(major, "buttons");
return -1;
}
class_device_create(at24c02_class, NULL, MKDEV(major, 0), NULL, "at24c02");
return 0;
}
(5)/*字符设备操作函数的实现,应该在at24c02_detect函数前实现,要么得先声明*/
static int at24c02_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
int ret;
struct i2c_msg msg[2];
char addr = 0;
if (count > 1024)
{
return -EINVAL;
}
/* 先写地址 */
msg[0].addr = at24c02_client->addr;
msg[0].flags = 0; /* 0 表示写 */
msg[0].len = 1; /* 1个地址 */
msg[0].buf = &addr;
/* 再读数据 */
msg[1].addr = at24c02_client->addr;
msg[1].flags = 1; /* 1: 表示读 */
msg[1].len = count; /* 要读的数据个数 */
msg[1].buf = at24c02_buf;
ret = i2c_transfer(at24c02_client->adapter, msg, 2);
if (ret == 2)
{
copy_to_user(buff, at24c02_buf, count);
return count;
}
else
return -EIO;
}
static ssize_t at24c02_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp)
{
int ret;
struct i2c_msg msg[1];
if (count > 1024)
{
return -EINVAL;
}
copy_from_user(&at24c02_buf[1], buff, count);
at24c02_buf[0] = 0; /* 表示写到哪里 */
msg[0].addr = at24c02_client->addr;
msg[0].flags = 0; /* 0 表示写 */
msg[0].len = count + 1; /* 1个地址+count个数据 */
msg[0].buf = at24c02_buf;
ret = i2c_transfer(at24c02_client->adapter, msg, 1);//最后会调用i2c_algorithm中的master_xfer
if (ret == 1)
return count;
else
return -EIO;
}
/*5,字符驱动的file_operations结构体,并在at24c02_detect函数前实现*/
static struct file_operations at24c02_fops = {
.owner = THIS_MODULE, /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
.read = at24c02_read,
.write = at24c02_write,
};
字符设备驱动本身没有什么好说的,这里主要想说一下,如何在驱动中调用i2c设配器帮我们完成数据传输。
目前设配器主要支持两种传输方法:smbus_xfer和master_xfer。一般来说,如果设配器支持了master_xfer那么它也可以模拟支持smbus的传输。但如果只实现smbus_xfer,则不支持一些i2c的传输。
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data * data);
master_xfer中的参数设置,和前面的用户空间编程一致。现在只是要在驱动中构建相关的参数然后调用i2c_transfer来完成传输既可。
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
ret = adap->algo->master_xfer(adap,msgs,num);
将调用i2c_algorithm中的master_xfer函数,在这里调用i2c-s3c2410.c中的
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)函数。
里面调用ret = s3c24xx_i2c_doxfer(i2c, msgs, num);函数传送i2c消息,在s3c24xx_i2c_doxfer里面首先ret =s3c24xx_i2c_set_master(i2c);将i2c适配器设置为i2c主设备,其后初始化 s3c2410_i2c结构体,使能i2c中断,并调用s3c24xx_i2c_message_start(i2c, msgs);函数启动i2c消息的传输。
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer, /*真正的实现发送的函数,最底层的了*/
.functionality = s3c24xx_i2c_func,
};
(6)/注销i2c_driver*/
static void __exit at24c02_exit(void)
{
i2c_del_driver(&at24c02_driver);
}
detach_client动作
顺序调用内核中注册的适配器来断开我们注册过的i2c设备。此过程通过调用i2c_driver中的detach_client 方法完成的。具体实现形式如下:
static int at24c02_detach_client(struct i2c_client *client)
{
int err;
if ((err = i2c_detach_client(at24c02_client)))
return err;
kfree(at24c02_client);
unregister_chrdev(major, "at24c02");
class_device_destroy(at24c02_class, MKDEV(major, 0));
class_destroy(at24c02_class);
return 0;
}