使用 i2c_driver 结构体来表示一个I2C设备驱动。
使用 i2c_device_id 结构体来表示I2C驱动所支持的设备的列表,由 i2c_driver 的 id_table 成员指向这个结构体的一个实例化的数组。
static const struct i2c_device_id xxx_table[] = {
{ "xxx" },
{ },
};
MODULE_DEVICE_TABLE(i2c, xxx_table);
使用 i2c_client 结构体来表示一个I2C总线上匹配到的从设备,通常它被包含在设备的私有数据结构体中;在设备驱动中操作I2C设备时,需要先获得此结构体的实例。
使用 i2c_msg 结构体来表示一次I2C传输,它指定了一次传输中包括的消息数目(通常读为2,写为1),从设备地址,消息的标志(如,读或写等),传输的数据的缓冲区的地址指针,以及数据的字节数。
注意,如果是使用 SMBus接口进行传输,是不需要使用 i2c_msg 结构体的。
在设备的 init 函数中注册 i2c 设备驱动;在设备的 exit 函数中注销 i2c 设备驱动。
在驱动注册之前 i2c_driver 结构体需要被正确地初始化,有4个成员要求必须被初始化:
static struct i2c_driver xxx_driver = {
.driver = {
.name = "xxx",
},
.probe = xxx_probe,
.remove = xxx_remove,
.id_table = xxx_table,
};
init 和 exit 函数:
static int __init xxx_init(void)
{
return i2c_add_driver(&xxx_driver);
}
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
也可以直接使用 module_i2c_driver 宏来注册驱动
module_i2c_driver(xxx_driver);
一个I2C设备在设备树中的 reg 属性提供其从设备地址,如:
&i2c1 {
status = "okay";
max17040:max17040@36{
compatible = "max17040";
reg = <0x36>;
status = "okay";
};
};
设备与驱动匹配之后,i2c_client 的 adapter 成员指向对应的主机控制器,设备名、从设备地址被填入 i2c_client 的 name 和 addr 成员。
然后,已被初始化的 i2c_client 被传入其匹配的驱动的 probe 函数。
I2C 设备的 probe 和 remove 函数的原型如下:
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id);
static int __devexit xxx_remove(struct i2c_client *client);
(1) 为设备私有数据的结构体分配内存,设置其中的client成员为传入的client
(2) 若有必要,检查I2C适配器支持的传输方法,以决定之后的I2C通信使用哪一种传输方法。目前有两种传输方法:smbus_xfer,master_xfer。
(3) 设置和初始化结构体的其它成员
(4) 申请硬件资源(中断、GPIO等),与硬件相关的初始化操作
(1) 与硬件相关的退出操作,释放硬件资源
(2) 注销私有数据结构体中的成员,释放驱动申请的内存等
(3) 释放私有数据结构体
/* linux/i2c.h */
static inline void i2c_set_clientdata(struct i2c_client *dev, void *data);
static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func);
/* drivers/i2c/i2c-core.c */
void i2c_unregister_device(struct i2c_client *client)
设备驱动可以在文件系统中创建设备节点,而使得用户空间的程序可以通过 ioctl, read, write 等函数访问设备节点来完成的I2C设备的数据传输的控制;drivers/i2c/i2c-dev.c 就是这样的一个例子,它向用户空间提供了一个通用的接口来访问 i2c驱动。
用户空间的 ioctl, read, write 等函数,对应驱动中 file_operation 的相应函数;file_operation 中的相应函数会去调用更底层的 i2c 数据传输函数。通常在设备驱动中构造这样的函数,以适应不同 i2c 设备本身不同的通信要求,比如设备的寄存器地址长度、数据的长度、调用 i2c_transfer() 或者 smbus API 等。
目前适配器主要支持两种传输方法:master_xfer和 smbus_xfer。一般来说,如果设配器支持了 master_xfer 那么它也可以模拟支持 smbus 的传输。但如果只实现smbus_xfer,则不支持一些i2c的传输。
设备驱动通过 i2c_trasfer() 来调用适配器的 master_xfer方法,它需要先初始化好 i2c_msg;设备驱动通过 i2c_smbus_xxx 函数组来调用适配器的 smbus_xfer 方法。
/* drivers/i2c/i2c-core.c */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
在使用 i2c_transfer() 之前,需要先构造好要传输的消息,i2c_msg 有4个成员:
成员 | 含义 |
---|---|
__u16 addr | 目标设备的从设备地址 |
__u16 flags | 消息标志,常用的有,0表示写,I2C_M_RD表示读 |
__u16 len | 消息的长度,以字节为单位 |
__u8 *buf | 消息内容的缓冲区地址 |
以下是之前调试 tc358775 这个芯片(16位寄存器地址,32位数据)写的数据传输函数:
/**
* tc358775_i2c_read: 从指定的16位寄存器中读出32位数据
* @data: 设备私有数据的结构体
* @reg: 16位寄存器地址
* @value: 读取出来的32位数据的缓冲区指针
* return: 成功则返回发送的消息数,失败则返回-1
*/
static int tc358775_i2c_read(struct tc358775_data *data, u16 reg, u32 *value)
{
struct i2c_msg msg[2];
u8 msgbuf[2];
u8 buf[4];
int status;
struct i2c_client *client = data->client;
memset(msg, 0, sizeof(msg));
/* 组装寄存器地址 */
msgbuf[0] = reg >> 8;
msgbuf[1] = reg;
//msg0: 传输要读取的寄存器地址
msg[0].addr = client->addr;
msg[0].buf = msgbuf;
msg[0].len = 2;
//msg1: 从设备读出数据,存放在buf
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = buf;
msg[1].len = 4;
//transfer
status = i2c_transfer(client->adapter, msg, 2);
if(status != 2) {
printk("%s: i2c_transfer reg=%d, error(%d)\n", __func__, reg, status);
return -1;
}
memcpy(value, buf, 4);
return status;
}
/**
* tc358775_i2c_write: 向指定的16位寄存器中写入32位数据
* @data: 设备私有数据的结构体
* @reg: 16位寄存器地址
* @value: 要写入的32位数据
* return: 成功则返回发送的消息数,失败则返回-1
*/
static int tc358775_i2c_write(struct tc358775_data *data, u16 reg, u32 value)
{
struct i2c_msg msg;
int status;
struct i2c_client *client = data->client;
msg.addr = client->addr;
msg.flags = 0;
msg.buf = data->writebuf;
/* 组装寄存器地址 */
msg.buf[0] = reg >> 8;
msg.buf[1] = reg;
/* 组装消息内容 */
memcpy(&msg.buf[2], &value, 4);
msg.len = 6;
status = i2c_transfer(client->adapter, &msg, 1);
if(status != 1)
printk("%s: i2c_transfer reg=%d, error(%d)\n", __func__, reg, status);
return status;
}
另外,可参考 ili210x 触摸屏芯片的驱动:
/* drivers/input/touchscreen/ili210x.c */
static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf,
size_t len)
{
struct i2c_msg msg[2] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = ®,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = len,
.buf = buf,
}
};
if (i2c_transfer(client->adapter, msg, 2) != 2) {
dev_err(&client->dev, "i2c transfer failed\n");
return -EIO;
}
return 0;
}
SMBus 是在 I2C 总线的基础上实现的,SMBus 具有超时功能,工作频率在 10kHz~100kHz
/* drivers/i2c/i2c-core.c */
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client, u8 command,
u8 length, u8 *values);
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client, u8 command,
u8 length, const u8 *values);
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command,
u8 value);
at24.c 的例子:
/* drivers/misc/eeprom/at24.c */
static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
unsigned offset, size_t count)
{
switch (at24->use_smbus) {
case I2C_SMBUS_I2C_BLOCK_DATA:
/* Smaller eeproms can work given some SMBus extension calls */
if (count > I2C_SMBUS_BLOCK_MAX)
count = I2C_SMBUS_BLOCK_MAX;
break;
case I2C_SMBUS_WORD_DATA:
count = 2;
break;
case I2C_SMBUS_BYTE_DATA:
count = 1;
break;
default:
...
}
timeout = jiffies + msecs_to_jiffies(write_timeout);
do {
read_time = jiffies;
switch (at24->use_smbus) {
/* 读取超出 2 Bytes 的数据 */
case I2C_SMBUS_I2C_BLOCK_DATA:
status = i2c_smbus_read_i2c_block_data(client, offset,
count, buf);
break;
/* 读取 2 Bytes 的数据 */
case I2C_SMBUS_WORD_DATA:
status = i2c_smbus_read_word_data(client, offset);
if (status >= 0) {
buf[0] = status & 0xff;
buf[1] = status >> 8;
status = count;
}
break;
/* 读取 1 Byte 的数据 */
case I2C_SMBUS_BYTE_DATA:
status = i2c_smbus_read_byte_data(client, offset);
if (status >= 0) {
buf[0] = status;
status = count;
}
break;
default:
status = i2c_transfer(client->adapter, msg, 2);
if (status == 2)
status = count;
}
dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
count, offset, status, jiffies);
if (status == count)
return count;
/* REVISIT: at HZ=100, this is sloooow */
msleep(1);
} while (time_before(read_time, timeout));
}
static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,
unsigned offset, size_t count)
{
...
timeout = jiffies + msecs_to_jiffies(write_timeout);
do {
write_time = jiffies;
if (at24->use_smbus) {
/* 调用smbus接口,不需要使用 */
/* offset:偏移地址; count:数据长度; buf: 数据缓冲区指针 */
status = i2c_smbus_write_i2c_block_data(client,
offset, count, buf);
if (status == 0)
status = count;
} else {
status = i2c_transfer(client->adapter, &msg, 1);
if (status == 1)
status = count;
}
dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",
count, offset, status, jiffies);
if (status == count)
return count;
/* REVISIT: at HZ=100, this is sloooow */
msleep(1);
} while (time_before(write_time, timeout));
...
}
max17040_battery.c 的例子:
/* drivers/power/max17040_battery.c */
static int max17040_write_reg(struct i2c_client *client, int reg, u8 value)
{
int ret;
ret = i2c_smbus_write_byte_data(client, reg, value);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
static int max17040_read_reg(struct i2c_client *client, int reg)
{
int ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
dev_err(&client->dev, "%s: err %d\n", __func__, ret);
return ret;
}
linux/i2c.h
drivers/i2c/i2c-core.c
drivers/i2c/i2c-dev.c
drivers/misc/eeprom/at24.c
drivers/power/max17040_battery.c
drivers/input/touchscreen/ili210x.c