如果应聘Linux驱动的工作岗位,面试官一般就会问你i2c驱动程序框架,下面就以我个人的理解来谈谈i2c驱动程序框架。
Linux驱动中的 i2c驱动主要有 四个结构体,要弄清Linux下i2c驱动,必须理解一下四个结构体的关系,
struct i2c_adapter {
struct module *owner; //所属模块
unsigned int id; //algorithm 类型,定义在i2c-id.h中,以 I2C_ALGO_开始
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; //主要是i2c通信函数
void *algo_data; //私有数据,自己定义的数据一般指向这里
/* data fields that are valid for all devices */
u8 level; /* nesting level for lockdep */
struct mutex bus_lock;
int timeout; /* in jiffies */
int retries; //重复传输的次数,一般是1,
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
};
</pre><pre class="cpp" name="code">struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
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);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit 低7位*/
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared or is about to be
* removed. You should avoid using this if you can, it will probably
* be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *);
int (*detach_adapter)(struct i2c_adapter *);
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);
const struct i2c_client_address_data *address_data;
struct list_head clients;
};
理解以上四个结构体非常重要,i2c_adapter是适配器,即cpu里的i2c控制器,i2c_algorithm 是传输算法,即CPU中的I2C控制器怎样传输,i2c_client对应的是外设,比如e2prom,i2c_driver对应的则是外设驱动,就驱动client的方法,
这四个结构体里各指针指向关系如下图,
如下是AT24CXX的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>
#include <asm/uaccess.h>
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
/* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */
static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};
/*如果将.normal_i2c注释掉,将.force打开,则强制认为这个设备存在*/ /
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 强制认为存在这个设备 */
};
static struct i2c_driver at24cxx_driver;
static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char address;
unsigned char data;
struct i2c_msg msg[2];
int ret;
/* address = buf[0]
* data = buf[1]
*/
if (size != 1)
return -EINVAL;
copy_from_user(&address, buf, 1);
/* 数据传输三要素: 源,目的,长度 */
/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = &address; /* 源 */
msg[0].len = 1; /* 地址=1 byte */
msg[0].flags = 0; /* 表示写 */
/* 然后启动读操作 */
msg[1].addr = at24cxx_client->addr; /* 源 */
msg[1].buf = &data; /* 目的 */
msg[1].len = 1; /* 数据=1 byte */
msg[1].flags = I2C_M_RD; /* 表示读 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if (ret == 2)
{
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 *offset)
{
unsigned char val[2];
struct i2c_msg msg[1];
int ret;
/* address = buf[0]
* data = buf[1]
*/
if (size != 2)
return -EINVAL;
copy_from_user(val, buf, 2);
/* 数据传输三要素: 源,目的,长度 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = val; /* 源 */
msg[0].len = 2; /* 地址+数据=2 byte */
msg[0].flags = 0; /* 表示写 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if (ret == 1)
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)
{
printk("at24cxx_detect\n");
/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
at24cxx_client->addr = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver = &at24cxx_driver;
strcpy(at24cxx_client->name, "at24cxx");
i2c_attach_client(at24cxx_client);
major = register_chrdev(0, "at24cxx", &at24cxx_fops);
cls = class_create(THIS_MODULE, "at24cxx");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
return 0;
}
static int at24cxx_attach(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, at24cxx_detect);
}
static int at24cxx_detach(struct i2c_client *client)
{
printk("at24cxx_detach\n");
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "at24cxx");
i2c_detach_client(client);
kfree(i2c_get_clientdata(client));
return 0;
}
/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",
},
.attach_adapter = at24cxx_attach,
.detach_client = at24cxx_detach,
};
static int at24cxx_init(void)
{
i2c_add_driver(&at24cxx_driver);
return 0;
}
static void at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
i2c在Linux中是总线的形势来管理adapter和client的,如下
当调用i2c_add_adapter的时候,就会在i2c总线上挂载一个i2c_adapter,当注册一个client的时候就会在总线链表上挂一个i2c_client
每次注册一 i2c_driverr的时候就会遍历总线上的i2c_adapter链表,取每个adapter,用adapter中的传输函数来探测外设,如果有应答,则将该client中的adapter指针指向这个i2c_adapter结构体,
i2c_add_driver
i2c_register_drvier
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
bus_for_each_dev(&i2c_bus_type, NULL, driver, __attach_adapter); //遍历i2c上的adapter,取每个adapter,然后用该adapter中的传输函数来探测该adapter是否匹配该client
__attach_adapter
adapter = to_i2c_adapter(dev);
i2c_detect(adapter, driver);
temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); //构造 i2c_client
i2c_detect_address(temp_client, -1, driver);
i2c_smbus_xfer
adapter->algo->smbus_xfer //该传输函数对应i2c-s3c2410.c中的s3c24xx_i2c_xfer
我们编写i2c的时候,只需要定义i2c_driver就可以,当然前提是对应的处理器是已经定义了i2c_adapter并且已经注册了,比如s3c24xx处理器,在Linux里就已经有对应的adapter,所以我们只需要编写client端就可以,
注册i2c_adapter的时候也一样,会遍历i2c总线上的client,取每个client,然后探测该client是否能够利用该adapter中的传输函数来探测,
at24cxx的测试函数如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* i2c_test r addr
* i2c_test w addr val
*/
void print_usage(char *file)
{
printf("%s r addr\n", file);
printf("%s w addr val\n", file);
}
int main(int argc, char **argv)
{
int fd;
unsigned char buf[2];
if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/at24cxx", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/at24cxx\n");
return -1;
}
if (strcmp(argv[1], "r") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
read(fd, buf, 1);
printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
}
else if (strcmp(argv[1], "w") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
buf[1] = strtoul(argv[3], NULL, 0);
write(fd, buf, 2);
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}