本文以 Linux-2.6.22.6 内核树下的 drivers/i2c/chips/ds1337.c 为例, 讨论Linux系统下的I2C设备的驱动代码的主要构成。
ds1337是一款 i2c 接口的串行实时时钟芯片。
i2c_driver 和 i2c_client 是I2C设备驱动的核心数据结构。I2C 设备驱动要使用 i2c_driver 和 i2c_client 数据结构并填充其中的成员函数。
另外值得一提的是,i2c_adapter和i2c_algorithm这两者则为I2C总线驱动的核心数据结构。以上这四个数据结构都在include/linux/i2c.h中被定义。i2c_client是硬件上的i2c芯片/设备(比如:本文的ds1337芯片)的抽象,而i2c_driver是i2c_client的一系列操作方法的一个封装;类似的,i2c_adapter是硬件上的i2c_适配器(比如:s3c2440的I2C控制器)的抽象,而i2c_algorithm则是对i2c_adapter的一系列操作方法的一个封装。
i2c_client 被包含在设备的私有信息结构体 ds1337_data 中,而 i2c_driver 则被定义为全局变量并初始化。如下:
struct ds1337_data {
struct i2c_client client;
struct list_head list;
};
static struct i2c_driver ds1337_driver = {
.driver = {
.name = "ds1337",
},
.attach_adapter = ds1337_attach_adapter,
.detach_client = ds1337_detach_client,
.command = ds1337_command,
};
另外,内核定义了i2c_msg 结构体来用于I2C 适配器和 I2C 设备之间的数据传输。
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x10 /* we have a ten bit chip address */
#define I2C_M_RD 0x01
#define I2C_M_NOSTART 0x4000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_NO_RD_ACK 0x0800
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
可见,它其实就是I2C通信协议的数据格式在内核中的抽象。其成员:addr对应于从设备的地址;flags对应于读写控制位;len为消息长度;buf指针则指向主机中的缓存。
一、 I2C设备驱动模块加载与卸载
static int __init ds1337_init(void)
{
return i2c_add_driver(&ds1337_driver);
}
static void __exit ds1337_exit(void)
{
i2c_del_driver(&ds1337_driver);
}
加载
通过 I2C 核心的 i2c_add_driver()函数添加 i2c_driver。
卸载
通过 I2C 核心的 i2c_del_driver()函数删除 i2c_driver。
i2c_add_driver()函数和i2c_del_driver()函数最终都通过i2c_driver中的 probe, remove, attach_adapter, detach_adapter, detach_client 这些成员去实现真正的操作。
模块加载过程和卸载过程引发的一系列反应如下图所示,在下文会讨论具体的过程。
二、 i2c_driver的成员函数
static struct i2c_driver ds1337_driver = {
.driver = {
.name = "ds1337",
},
.attach_adapter = ds1337_attach_adapter,
.detach_client = ds1337_detach_client,
.command = ds1337_command,
};
1. ds1337_attach_adapter
I2C 设备驱动的加载函数进行i2c_add_driver(&ds1337_driver)的调用,会引发i2c_driver 结构体中ds1337_attach_adapter()函数的执行,它简单地调用 I2C 核心的i2c_probe()函数。ds1337_detect()函数的指针作为参数传入i2c_probe()中;i2c_probe()函数调用i2c_probe_address(),从而引发ds1337_detect()函数的调用。
static int ds1337_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, ds1337_detect);
}
在 ds1337_detect()函数中初始化i2c_client结构体的成员;调用i2c_attach_client()函数来建立i2c_client和i2c_adapter 的依附关系(把它加入队列的尾部:list_add_tail(&client->list, &adapter->clients));调用ds1337_init_client()函数来实现具体硬件相关的初始化;最后,把client加入本地链表(list_add(&data->list, &ds1337_clients))。
static int ds1337_detect(struct i2c_adapter *adapter, int address, int kind)
{
struct i2c_client *new_client;
struct ds1337_data *data;
int err = 0;
const char *name = "";
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_I2C))
goto exit;
if (!(data = kzalloc(sizeof(struct ds1337_data), GFP_KERNEL))) {
err = -ENOMEM;
goto exit;
}
INIT_LIST_HEAD(&data->list);
/* The common I2C client data is placed right before the
* DS1337-specific data.
*/
new_client = &data->client;
i2c_set_clientdata(new_client, data);
new_client->addr = address;
new_client->adapter = adapter;
new_client->driver = &ds1337_driver;
new_client->flags = 0;
/* Default to an DS1337 if forced */
if (kind == 0)
kind = ds1337;
if (kind < 0) { /* detection and identification */
u8 data;
/* Check that status register bits 6-2 are zero */
if ((ds1337_read(new_client, DS1337_REG_STATUS, &data) < 0) ||
(data & 0x7c))
goto exit_free;
/* Check for a valid day register value */
if ((ds1337_read(new_client, DS1337_REG_DAY, &data) < 0) ||
(data == 0) || (data & 0xf8))
goto exit_free;
/* Check for a valid date register value */
if ((ds1337_read(new_client, DS1337_REG_DATE, &data) < 0) ||
(data == 0) || (data & 0xc0) || ((data & 0x0f) > 9) ||
(data >= 0x32))
goto exit_free;
/* Check for a valid month register value */
if ((ds1337_read(new_client, DS1337_REG_MONTH, &data) < 0) ||
(data == 0) || (data & 0x60) || ((data & 0x0f) > 9) ||
((data >= 0x13) && (data <= 0x19)))
goto exit_free;
/* Check that control register bits 6-5 are zero */
if ((ds1337_read(new_client, DS1337_REG_CONTROL, &data) < 0) ||
(data & 0x60))
goto exit_free;
kind = ds1337;
}
if (kind == ds1337)
name = "ds1337";
/* We can fill in the remaining client fields */
strlcpy(new_client->name, name, I2C_NAME_SIZE);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto exit_free;
/* Initialize the DS1337 chip */
ds1337_init_client(new_client);
/* Add client to local list */
list_add(&data->list, &ds1337_clients);
return 0;
exit_free:
kfree(data);
exit:
return err;
}
2. ds1337_detach_client
I2C 设备驱动的卸载函数进行 i2c_del_driver(&ds1337_driver)调用后,会引发与ds1337_driver 关联的每个 i2c_client 与之解除关联,即 ds1337_detach_client()函数将因此而被调用。
static int ds1337_detach_client(struct i2c_client *client)
{
int err;
struct ds1337_data *data = i2c_get_clientdata(client);
if ((err = i2c_detach_client(client)))
return err;
list_del(&data->list);
kfree(data);
return 0;
}
3. ds1337_command
i2c_driver的command成员函数实现了针对设备的控制命令。具体的控制命令是设备相关的,如对于本文的ds1337实时钟而言,命令将是设置时间和获取时间,而对于视频 AD 设备而言,命令会是设置采样方式、选择通道等。
ds1337 设备接受 DS1337_GET_DATE 和DS1337_GET_DATE 这两种命令,而处理这两个命令的函数分别为ds1337_get_datetime和ds1337_set_datetime,如下:
static int ds1337_command(struct i2c_client *client, unsigned int cmd, void *arg)
{
dev_dbg(&client->dev, "%s: cmd=%d\n", __FUNCTION__, cmd);
switch (cmd) {
case DS1337_GET_DATE:
return ds1337_get_datetime(client, arg);
case DS1337_SET_DATE:
return ds1337_set_datetime(client, arg);
default:
return -EINVAL;
}
}
具体命令的实现是通过组件 i2c_msg 消息数组,并调用 I2C 核心的i2c_transfer()。i2c_transfer()寻找到i2c_adapter 对应的 i2c_algorithm,并使用 i2c_algorithm 的master_xfer()函数真正驱动硬件流程,来实现 I2C 适配器和 I2C 设备之间的一组消息交互。
static int ds1337_get_datetime(struct i2c_client *client, struct rtc_time *dt)
{
int result;
u8 buf[7];
u8 val;
struct i2c_msg msg[2];
u8 offs = 0;
if (!dt) {
dev_dbg(&client->dev, "%s: EINVAL: dt=NULL\n", __FUNCTION__);
return -EINVAL;
}
/*构造一条“写消息”*/
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = &offs;
/*构造一条“读消息”*/
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = sizeof(buf);
msg[1].buf = &buf[0];
/*完成数据的传输*/
result = i2c_transfer(client->adapter, msg, 2);
/*具体的硬件操作:读取缓存中接收到的BCD码,转换成二进制码*/
dev_dbg(&client->dev, "%s: [%d] %02x %02x %02x %02x %02x %02x %02x\n",
__FUNCTION__, result, buf[0], buf[1], buf[2], buf[3],
buf[4], buf[5], buf[6]);
if (result == 2) {
dt->tm_sec = BCD2BIN(buf[0]);
dt->tm_min = BCD2BIN(buf[1]);
val = buf[2] & 0x3f;
dt->tm_hour = BCD2BIN(val);
dt->tm_wday = BCD2BIN(buf[3]) - 1;
dt->tm_mday = BCD2BIN(buf[4]);
val = buf[5] & 0x7f;
dt->tm_mon = BCD2BIN(val) - 1;
dt->tm_year = BCD2BIN(buf[6]);
if (buf[5] & 0x80)
dt->tm_year += 100;
dev_dbg(&client->dev, "%s: secs=%d, mins=%d, "
"hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
__FUNCTION__, dt->tm_sec, dt->tm_min,
dt->tm_hour, dt->tm_mday,
dt->tm_mon, dt->tm_year, dt->tm_wday);
return 0;
}
dev_err(&client->dev, "error reading data! %d\n", result);
return -EIO;
}
以上讨论的,即是Linux系统中I2C设备驱动的基本构成,至于其中跟底层的和具体硬件相关的代码,可以参阅Linux-2.6.22.6内核树下的
drivers/i2c/chips/ds1337.c 文件及其它相关源码。