I2C驱动程序框架
I2C的协议流程在I2C协议原理简述一文中写过,这里就不再讲解。为了更好的书写I2C客户驱动程序,我们先理一理I2C驱动程序的框架,才能更好的写出属于我们自己的驱动程序。
上图中的I2C总线驱动程序(也就是I2C适配器驱动程序)是芯片商为我们实现的,知道如何收发数据;而需要我们自己实现的I2C客户驱动程序,则知道数据的具体含义。
那么我们书写的客户驱动程序什么时候会被调用呢?这个问题要先从分析I2C适配器驱动程序开始讲起。下面给出一个分析流程:
结合linux-2.6.22.6\drivers\i2c\busses\i2c-s3c2410.c,分析一下由芯片商实现的adapter的驱动程序
i2c_adap_s3c_init
ret = platform_driver_register(&s3c2440_i2c_driver);
s3c24xx_i2c_probe
clk_enable(i2c->clk); //使能I2C
ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED, pdev->name, i2c); //注册中断
ret = i2c_add_adapter(&i2c->adap); //添加adapter结构体
i2c_register_adapter(adapter);
/* let legacy drivers scan this bus for matching devices */
list_for_each(item,&drivers) {
driver = list_entry(item, struct i2c_driver, list);
if (driver->attach_adapter)
/* We ignore the return code; if it fails, too bad */
driver->attach_adapter(adap); //这个driver就是我们I2C客户驱动程序注册时,挂载在drivers链表上的
}
我们可以看到,最后会调用I2C客户驱动程序的driver->attach_adapter函数,而这个driver就是在初始化是就注册进系统的。这样我们就知道了什么时候会调用我们书写的I2C驱动程序了,那下面讲讲怎么写我们自己的I2C客户驱动程序。
I2C客户驱动程序示例
先给出一个已经写好的I2C驱动程序:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
static unsigned short ignore[] = { I2C_CLIENT_END }; /*顾名思义就是忽略的意思*/
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /*地址值为7位:我们的I2C设备地址为0x50*/
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short *forces[] = {force_addr, NULL};
static struct i2c_client_address_data addr_data = {
.normal_i2c = ignore, /*要发出start信号和设备地址,并且收到ACK信号后,才能确定存在这个设备*/
.probe = ignore,
.ignore = ignore,
.forces = forces, /*强制认为存在设备,不用通过发送start信号和设备地址来确认*/
};
static struct i2c_driver at24cxx_driver;
struct i2c_client *at24cxx_client;
static struct class *at24cxx_class;
static struct class_device *at24cxx_class_dev;
static int major = 0;
static ssize_t at24cxx_read (struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
int ret = 0;
unsigned char address;
unsigned char data;
struct i2c_msg msg[2];
if (size != 1)
return -EINVAL;
copy_from_user(&address, buf, 1);
/*数据传输3要素:源,目的,长度*/
/*在读取AT24CXX的数据前,需要将存储数据的空间的地址告诉它*/
msg[0].addr = at24cxx_client->addr; /*目的*/
msg[0].buf = &address; /*源*/
msg[0].len = 1; /*地址 = 1bytes*/
msg[0].flags = 0; /*写*/
/*然后启动读操作*/
msg[1].addr = at24cxx_client->addr; /*源*/
msg[1].buf = &data; /*目的*/
msg[1].len = 1; /*地址 = 1bytes*/
msg[1].flags = I2C_M_RD; /*读*/
ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if (2 == ret) {
copy_to_user(buf, &data, 1);
return 1;
}
else
return -EIO;
}
static ssize_t at24cxx_write (struct file *file, const char __user *buf, size_t size, loff_t *ppos)
{
int ret = 0;
unsigned char val[2];
struct i2c_msg msg[1];
/* address = buf[0]
* data = buf[1]
*/
copy_from_user(val, buf, 2);
/*数据传输3要素:源,目的,长度*/
msg[0].addr = at24cxx_client->addr; /*目的*/
msg[0].buf = val; /*源*/
msg[0].len = 2; /*地址+数据 = 2bytes*/
msg[0].flags = 0; /*写*/
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if (1 == ret)
return 2;
else
return -EIO;
}
static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
};
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
int err = 0;
if (!(at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL))) {
err = -ENOMEM;
goto exit;
}
at24cxx_client->addr = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver = &at24cxx_driver;
major = register_chrdev(0, "at24cxx_drv", &at24cxx_fops);
at24cxx_class = class_create(THIS_MODULE, "at24cxx_drv");
at24cxx_class_dev = class_device_create(at24cxx_class, NULL, MKDEV(major, 0), NULL, "at24cxx_dev");
/* Fill in the remaining client fields */
strlcpy(at24cxx_client->name, "at24cxx", I2C_NAME_SIZE);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(at24cxx_client)))
goto exit_kfree;
printk("at24cxx_detect\n");
return 0;
exit_kfree:
kfree(at24cxx_client);
exit:
return err;
}
static int at24cxx_attach_adapter(struct i2c_adapter *adapter)
{
printk("in %s\n", __FUNCTION__);
return i2c_probe(adapter, &addr_data, at24cxx_detect);
}
static int at24cxx_detach_client(struct i2c_client *client)
{
int err;
printk("in %s\n", __FUNCTION__);
err = i2c_detach_client(client);
if (err)
return err;
kfree(i2c_get_clientdata(client));
class_device_unregister(at24cxx_class_dev);
class_destroy(at24cxx_class);
unregister_chrdev(major, "at24cxx_drv");
return 0;
}
/*1. 分配一个i2c_driver结构体*/
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",
},
.attach_adapter = at24cxx_attach_adapter,
.detach_client = at24cxx_detach_client,
};
static int at24cxx_init(void)
{
return i2c_add_driver(&at24cxx_driver);
}
static void at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
由上面的代码,可以得出一个编写I2C驱动程序的步骤:
(1)分配一个i2c_driver结构体
/*1. 分配一个i2c_driver结构体*/
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",
},
.attach_adapter = at24cxx_attach_adapter,
.detach_client = at24cxx_detach_client,
};
(2)有了i2c_driver结构体后,在入口函数将它注册进系统
static int at24cxx_init(void)
{
return i2c_add_driver(&at24cxx_driver);
}
(3)在分析I2C驱动程序的框架时,我们知道适配器驱动程序会调用我们写的attach_adapter,也就是at24cxx_attach_adapter函数。这个函数里面接着调用了i2c_probe函数。
(4)我们可以看到i2c_probe的第二个参数addr_data结构体其实就是从设备的地址,一般normal_i2c这一项是填入从设备真正的地址,也就是.normal_i2c = normal_addr。但是我们这里强制设置.forces = forces,也就是不管设备是否存在,都认为其存在了。
static unsigned short ignore[] = { I2C_CLIENT_END }; /*顾名思义就是忽略的意思*/
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /*地址值为7位:我们的I2C设备地址为0x50*/
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short *forces[] = {force_addr, NULL};
static struct i2c_client_address_data addr_data = {
.normal_i2c = ignore, /*要发出start信号和设备地址,并且收到ACK信号后,才能确定存在这个设备*/
.probe = ignore,
.ignore = ignore,
.forces = forces, /*强制认为存在设备,不用通过发送start信号和设备地址来确认*/
};
如果设置了真正的从设备地址后,i2c_probe调用的过程,也是向从设备发起一个探测命令的过程,如果从设备回应了一个ACK,那就判断从设备存在。接着就调用i2c_probe的第三个参数指向的at24cxx_detect函数。
(5)
我们可以看到在at24cxx_detect函数里面,设置了一下at24cxx_client这个结构体,包括一些从设备的地址,后面会用到
at24cxx_client->addr = address;
以及创建一个字符设备节点,方便后面和用户进程交互
major = register_chrdev(0, "at24cxx_drv", &at24cxx_fops);
at24cxx_class = class_create(THIS_MODULE, "at24cxx_drv");
at24cxx_class_dev = class_device_create(at24cxx_class, NULL, MKDEV(major, 0), NULL, "at24cxx_dev");
(6)
后面就是字符设备常见的操作了,用户进程调用驱动程序的write和read函数来和I2C设备进行通信。不过这里要说明一下驱动程序是怎么发送数据的,以及数据的结构是怎么样的。
比如write函数里面就分配了一个i2c_msg结构体,然后填充里面的内容,再利用i2c_transfer函数进行发送。
struct i2c_msg msg[1];
/* address = buf[0]
* data = buf[1]
*/
copy_from_user(val, buf, 2);
/*数据传输3要素:源,目的,长度*/
msg[0].addr = at24cxx_client->addr; /*目的*/
msg[0].buf = val; /*源*/
msg[0].len = 2; /*地址+数据 = 2bytes*/
msg[0].flags = 0; /*写*/
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
这里说明下,为什么传输的内容是:地址+数据,这个是at24cxx这个从设备规定的: