之前已经说过,有2种i2c驱动程序的设计,比如说针对EEPROM的驱动程序。我们可以专门编写一个针对EEPROM的驱动程序。另一种方式就是通过i2c-dev,即通过i2c通用通用驱动,来编写一个应用程序,来完成对设备的控制。
我们现在就来实现i2c用户态驱动程序的设计。
通用设备驱动分析
首先需要分析i2c-dev,先打开i2c-dev.c这个文件,找到i2c_dev_init函数
/* ------------------------------------------------------------------------- */
/*
* module load/unload record keeping
*/
static int __init i2c_dev_init(void)
{
int res;
printk(KERN_INFO "i2c /dev entries driver\n");
res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
if (res)
goto out;
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;
}
res = i2c_add_driver(&i2cdev_driver);
if (res)
goto out_unreg_class;
return 0;
out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev(I2C_MAJOR, "i2c");
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}
register_chrdev用于创建注册一个字符设备,class_create用于生产一个字符类的设备文件,i2c_add_driver这是用来向Linux系统注册一个i2c设备驱动。
接下来分析操作函数
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,
};
这里包含很多操作,我们重点分析i2cdev_ioctl,因为在用户态中,主要通过这个函数来实现对设备的操作。
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct i2c_client *client = (struct i2c_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:
/* NOTE: devices set up to work with "new style" drivers
* can't use I2C_SLAVE, even when the device node is not
* bound to a driver. Only I2C_SLAVE_FORCE will work.
*
* Setting the PEC flag here won't affect kernel drivers,
* which will be using the i2c_client node registered with
* the driver model core. Likewise, when that client has
* the PEC flag already set, the i2c-dev driver won't see
* (or use) this setting.
*/
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
return -EINVAL;
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_TENBIT:
if (arg)
client->flags |= I2C_M_TEN;
else
client->flags &= ~I2C_M_TEN;
return 0;
case I2C_PEC:
if (arg)
client->flags |= I2C_CLIENT_PEC;
else
client->flags &= ~I2C_CLIENT_PEC;
return 0;
case I2C_FUNCS:
funcs = i2c_get_functionality(client->adapter);
return put_user(funcs, (unsigned long __user *)arg);
case I2C_RDWR:
return i2cdev_ioctl_rdrw(client, arg);
case I2C_SMBUS:
return i2cdev_ioctl_smbus(client, arg);
case I2C_RETRIES:
client->adapter->retries = arg;
break;
case I2C_TIMEOUT:
/* For historical reasons, user-space sets the timeout
* value in units of 10 ms.
*/
client->adapter->timeout = msecs_to_jiffies(arg * 10);
break;
default:
/* NOTE: returning a fault code here could cause trouble
* in buggy userspace code. Some old kernel bugs returned
* zero in this case, and userspace code might accidentally
* have depended on that bug.
*/
return -ENOTTY;
}
return 0;
}
里面实现了很多操作,我们主要关心的又是I2C_RDWR这个操作,即读和写。我们看看这个函数i2cdev_ioctl_rdrw
static noinline int i2cdev_ioctl_rdrw(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;
if (copy_from_user(&rdwr_arg,
(struct i2c_rdwr_ioctl_data __user *)arg,
sizeof(rdwr_arg)))
return -EFAULT;
/* Put an arbitrary limit on the number of messages that can
* be sent at once */
if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
return -EINVAL;
rdwr_pa = (struct i2c_msg *)
kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg),
GFP_KERNEL);
if (!rdwr_pa)
return -ENOMEM;
if (copy_from_user(rdwr_pa, rdwr_arg.msgs,
rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
kfree(rdwr_pa);
return -EFAULT;
}
data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);
if (data_ptrs == NULL) {
kfree(rdwr_pa);
return -ENOMEM;
}
res = 0;
for (i = 0; i < rdwr_arg.nmsgs; i++) {
/* Limit the size of the message to a sane amount;
* and don't let length change either. */
if ((rdwr_pa[i].len > 8192) ||
(rdwr_pa[i].flags & I2C_M_RECV_LEN)) {
res = -EINVAL;
break;
}
data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;
rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);
if (rdwr_pa[i].buf == NULL) {
res = -ENOMEM;
break;
}
if (copy_from_user(rdwr_pa[i].buf, data_ptrs[i],
rdwr_pa[i].len)) {
++i; /* Needs to be kfreed too */
res = -EFAULT;
break;
}
}
if (res < 0) {
int j;
for (j = 0; j < i; ++j)
kfree(rdwr_pa[j].buf);
kfree(data_ptrs);
kfree(rdwr_pa);
return res;
}
res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);
while (i-- > 0) {
if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD)) {
if (copy_to_user(data_ptrs[i], rdwr_pa[i].buf,
rdwr_pa[i].len))
res = -EFAULT;
}
kfree(rdwr_pa[i].buf);
}
kfree(data_ptrs);
kfree(rdwr_pa);
return res;
}
先来分析这个函数的参数,参数有2个client和arg,client应该是需要操作的设备,arg则是需要读写的参数,这个参数首先被赋值给这个结构i2c_rdwr_ioctl_data
/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
};
这里有2个成员,一个是消息指针,另一个是消息的数量。消息数量很好理解,我们看看消息指针的类型:
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
里面包含了设备的地址addr,flags(0为写,1为读)读写标志,消息的字节数,消息的数据指针。
接着来分析这个函数,做一些判断之后,接下来肯定就是读取消息数据了,它通过一个大循环for (i = 0; i < rdwr_arg.nmsgs; i++) 来读取参数里面的数据,然后使用i2c_transfer来传输这些数据。这个函数是属于i2c-croe里面的一个函数,但是这个函数并不会直接读写,而是找到挂载i2c总线上的适配器,通过设备器上面的算法来真正实现数据的传输。这个数据传输的线路和上面一节的数据流程图一摸一样。
因此对于用户态的i2c设备驱动编写就很明了了,首先需要构造一条i2c消息i2c_rdwr_ioctl_data,然后通过i2cdev_ioctl_rdrw函数把这些数据读写到设备中去。我们接下来就编写用户态下面i2c驱动程序的编写。
用户态驱动设计
我们先分析一下程序大概的流程:
1、打开通用的字符设备文件
依然是使用open打开设备文件,在开发板的/dev/下面我们可以找到一个叫做i2c-0的设备文件,我们以读写的方式打开这个设备文件
2、构造需要写入到EEPROM中的消息
我们首先需要赋值消息的定义到我们的程序中。即i2c_msg和i2c_rdwr_ioctl_data。可以把一些不需要的数据删掉。
然后定义一个消息结构,i2c_rdwr_ioctl_data eeprom_data,然后初始化这个结构(别忘了给指针分配空间)。特别要注意的是对应消息的数量读和写肯定是不一样的,因为对于写只需要一个消息,而对于读只需要2个消息,因为先做了一次写,然后在做了一次读。因此我们按最大的长度2,来给i2c_msg 分配空间。
接下来可以初始化写的消息,写的信息有2个字节,所以len=2,第一个是偏移地址,第二个是需要写入的数据。初始化后如下:
eeprom_data.nmsgs = 1; //写只有一条消息
(eeprom_data.msgs) = (struct i2c_msg *)malloc(2 * sizeof(struct i2c_msg));
(eeprom_data.msgs[0]).addr = 0x50;
(eeprom_data.msgs[0]).flags = 0;
(eeprom_data.msgs[0]).len = 2;
(eeprom_data.msgs[0]).buf = (unsigned char *)malloc(2);
(eeprom_data.msgs[0]).buf[0] = 0x10;//写入到EEPROM的偏移地址
(eeprom_data.msgs[0]).buf[1] = 0x60;//写入到偏移地址的数据
3、使用ioctl写入数据
ioctl的第一个参数是fd,第二个参数是操作类型,这里是I2C_RDWR,我们需要拷贝I2C_RDWR到自己的程序中,第三个是参数就是eeprom_data了,我们在取地址之后需要进行类型转换,因为i2cdev_ioctl_rdrw的参数是unsigned long
4、构造从EEPROM读数据的消息
读消息的构造也类似,不过这里需要2个消息,第一个实现写,第二个实现读:
//构造从EEPROM读数据的消息
eeprom_data.nmsgs = 2; //读有二条消息
(eeprom_data.msgs[0]).addr = 0x50;//先写入需要开始读取的偏移地址,然后开始读
(eeprom_data.msgs[0]).flags = 0;
(eeprom_data.msgs[0]).len = 1;
(eeprom_data.msgs[0]).buf[0] = 0x10;
(eeprom_data.msgs[1]).addr = 0x50;
(eeprom_data.msgs[1]).flags = 1;
(eeprom_data.msgs[1]).len = 1;
(eeprom_data.msgs[1]).buf = (unsigned char *)malloc(2);
(eeprom_data.msgs[1]).buf[0] = 0;//先把读取缓冲清0
5、使用ioctl读出消息
ioctl(fd, I2C_RDWR, (unsigned long)&eeprom_data);
读取到的消息会保存在以buf[0]为起始地址的存储空间中。
6、关闭字符设备
很简单 close(fd)
最后,整体代码如下:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define I2C_RDWR 0x0707 /* Combined R/W transfer (one STOP only) */
struct i2c_msg {
unsigned short addr; /* slave address */
unsigned short flags;
unsigned short len; /* msg length */
unsigned char *buf; /* pointer to msg data */
};
/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
struct i2c_msg *msgs; /* pointers to i2c_msgs */
unsigned long nmsgs; /* number of i2c_msgs */
};
int main()
{
int fd=0;
struct i2c_rdwr_ioctl_data eeprom_data;
//打开字符设备文件
fd = open("/dev/i2c-0", O_RDWR);
//构造需要写入到EEPROM的消息
eeprom_data.nmsgs = 1; //写只有一条消息
(eeprom_data.msgs) = (struct i2c_msg *)malloc(2 * sizeof(struct i2c_msg));
(eeprom_data.msgs[0]).addr = 0x50;//I2C设备地址
(eeprom_data.msgs[0]).flags = 0;//0为写,1为读
(eeprom_data.msgs[0]).len = 2;//写入数据长度
(eeprom_data.msgs[0]).buf = (unsigned char *)malloc(2);//申请2个字节
(eeprom_data.msgs[0]).buf[0] = 0x10;//写入到EEPROM的偏移地址
(eeprom_data.msgs[0]).buf[1] = 0x60;//写入到偏移地址的数据
//使用ioctl把数据写入到EEPROM中
ioctl(fd, I2C_RDWR, (unsigned long)&eeprom_data);//需要做类型转换,因为i2cdev_ioctl_rdrw的参数是unsigned long
//构造从EEPROM读数据的消息
eeprom_data.nmsgs = 2; //读有二条消息
(eeprom_data.msgs[0]).addr = 0x50;//先写入需要开始读取的偏移地址,然后开始读
(eeprom_data.msgs[0]).flags = 0;
(eeprom_data.msgs[0]).len = 1;
(eeprom_data.msgs[0]).buf[0] = 0x10;
(eeprom_data.msgs[1]).addr = 0x50;//然后开始读取数据,len的长度为1,表示读取数据的长度
(eeprom_data.msgs[1]).flags = 1;
(eeprom_data.msgs[1]).len = 1;
(eeprom_data.msgs[1]).buf = (unsigned char *)malloc(2);
(eeprom_data.msgs[1]).buf[0] = 0;//先把读取缓冲清0
//使用ioctl读出消息
ioctl(fd, I2C_RDWR, (unsigned long)&eeprom_data);
printf("buf[0]:%x\n", (eeprom_data.msgs[1]).buf[0]);
//关闭字符设备
close(fd);
return 0;
}
更多Linux资料及视频教程点击这里