如下图所示,虽然TinkerBoardR支持九路I2C,但是对于开发者而言,我们主要需要注意的是其中的7路。其中五路板子已经内置或是与硬件接口相绑定,此外还包含两路IO扩展I2C,分别对应I2C6和I2C7。
I2C总线在物理连接上由两条线组成:
这两条数据线需要接上拉电阻(如果不接上拉电阻,这两个引脚就处于悬空状态,然而悬空状态引脚的电平是无法确定的)。这样的话,总线在空闲的时候,SCL与SDA都会处于高电平状态。
I2C总线在工作的时候是按照一定协议规则来运行的,并且支持多个从机进行通信的。我们可以通过不同的设备地址来区分这些从机。总线连接的框图如图所示
Linux的I2C接口只支持作为MASTER端,I2C接口围绕两种驱动和两种设备来组织,适配器驱动(“Adapter Driver”)用来抽象硬件上的I2C控制器,它绑定到一个物理设备(可能是PCI设备或者平台设备),并为它管理的每一条I2C总线使用一个结构体"i2c_adapter"来表示。而在I2C总线上,使用结构体”i2c_adapter“来表示。而在i2c总线上,使用结构体"i2c_client"来代表挂接在上面的i2c设备。这些I2C设备都应该绑定到一个结构体"i2c_driver"上以符合Linux的驱动模型。有一系列的函数用来进行I2C协议的操作,到目前为止,只能从任务上下文使用它们。
Linux中I2C框架中,分为总线(BUS)驱动和设备(DEVICE)驱动,总线驱动的职责是为系统中每一个I2C总线实现相应的读写方法,提供给设备驱动来使用,但是总线驱动并不进行任何的通信。设备驱动则是与挂接在I2C总线上的设备进行通讯的驱动,通过I2C总线驱动提供的方法,设备驱动可以忽略不同的I2C总线控制器的差异。
在系统开机时,首先装载总线驱动,一个驱动用于支持一条特定的I2C总线的读写,一个总线驱动通常使用两种数据结构来描述:
i2c_adapter用于描述一个特定的i2c总线控制器
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* 单位为 jiffies */
int retries;
struct device dev;
int nr; /*i2c bus 编号, 若置为-1, 则代表动态分配*/
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
};
向系统中注册i2c_adapter可以使用如下API
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
int i2c_add_adapter(struct i2c_adapter *adapter)
这两个api使用任意一个即可,它们都会自动检查是否需要动态分配总线号还是使用指定的总线号
注销i2c_adapter可以使用
void i2c_del_adapter(struct i2c_adapter *adap)
i2c_algorithm用于描述i2c总线的传输方法和实现
struct i2c_algorithm {
/* 如果adapter不能支持i2c访问, 则置 master_xfer 为NULL */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/* 如果adapter支持SMBus访问, 则设置smbus_xfer, 若 smbus_xfer 为NULL, 则使用I2C访问来模拟SMBus访问 */
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality) (struct i2c_adapter *); /* 用于查询i2c adapter支持那些function */
};
i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。一个i2c适配器需要i2c_algorithm提供的通信函数来控制适配器,使它产生特定的访问信号。因此在i2c_adapter中包含其使用的i2c_algorithm的指针。
不同i2c总线的控制器,都有各自的i2c_adapter,但是若他们的操作方式相同,则可以共享同一i2c_algorithm,例如,移动设备的SOC上通常集成有多条i2c总线,但是他们的操作方式是相同的,因此可以共享同一i2c_algorithm
i2c_algorithm中的通信函数以i2c_msg为通信的基本单位:
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 */
#define I2C_M_NEED_DELAY 0x0020 // add by kfx
#define I2C_M_REG8_DIRECT 0x0040 // add by kfx
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
总线驱动只是实现了对一条总线的读写,与具体的设备进行通信是由i2c设备驱动来进行的,一个i2c的设备驱动由两个模块(i2c_driver和i2c_client)来描述:
struct i2c_client {
unsigned short flags;
unsigned short addr; //设备地址
char name[I2C_NAME_SIZE]; //设备名称
struct i2c_adapter * adapter; //设备所在的i2c总线的adapter
struct i2c_driver * driver; //绑定的driver
struct device dev;
int irq; //设备的irq号
struct list_head detected; //用于将同一个i2c_driver所驱动的i2c_client形成链表
};
i2c_client对应于真实的物理设备,每一个i2c设备都需要一个i2c_client来表示
struct i2c_driver {
unsigned int class;
int (* attach_adapter) (struct i2c_adapter *); /* 旧式i2c driver的方法, 不要再使用 */
int (* probe) (struct i2c_client *, const struct i2c_device_id *);
int (* remove) (struct i2c_client *);
void (* shutdown) (struct i2c_client *);
int (* suspend) (struct i2c_client *, pm_message_t mesg);
int (* resume) (struct i2c_client *);
void (* alert) (struct i2c_client *, unsigned int data);
int (* command) (struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id * id_table;
int (* detect) (struct i2c_client *, struct i2c_board_info *);
const unsigned short * address_list;
struct list_head clients;
};
i2c_driver对应一个驱动的方法,不对应任何的物理实体
struct i2c_device_id 用于描述i2c_driver和i2c_client匹配的条件
struct i2c_device_id {
char name[I2C_NAME_SIZE]; //该name和i2c_client.name相同,则i2c_client和i2c_driver匹配成功, 进行后续的probe过程
kernel_ulong_t driver_data; //传递给driver的私有数据, 不使用则置0
};
例如
struct i2c_device_id kxtj2_id[] = {
{ "kxtj2", 0, }
{ "kxtj9", 0, }
{} /*空成员, 用于标识结尾*/
};
struct i2c_driver kxtj2_driver = {
.driver = {
.name = "gsensor-kxtj2",
.owner = THIS_MODULE,
},
.id_table = kxtj2_id,
......
}
i2c_driver.id_table中可以保存多个struct i2c_device_id,在匹配时,依次比较其中的每一个struct i2c_device_id,直到结束或者匹配成功
一个i2c_client只能绑定到一个i2c_driver,一个i2c_driver可以绑定到多个i2c_client,当注册一个i2c_client或者i2c_driver时,Linux中的i2c_bus_core会遍历已经注册的i2c_driver或者已注册但是未绑定driver的i2c_client,当i2c_client和i2c_driver匹配成功后,进行linux driver model的probe过程
利用i2c_driver.id_table进行匹配是linux i2c core的标准做法,为了支持ACPI和Open Firmware,还扩展了i2c_driver.acpi_match_table和i2c_driver.of_match_table
下面根据官方提供的mpu6500示例来讲解I2C配置流程
在注册 I2C 设备时,需要结构体 i2c_client 来描述 I2C 设备。然而在标准 Linux 中,用户只需要提供相应的 I2C 设备信息,Linux 就会根据所提供的信息构造 i2c_client 结构体。
用户所提供的 I2C 设备信息以节点的形式写到 DTS 文件中,如下所示:
//kernel/arch/arm64/boot/dts/rockchip/rk3399pro-tinker_edge_r.dtsi
&i2c1 {
status = "okay";
i2c-scl-rising-time-ns = <140>;
i2c-scl-falling-time-ns = <30>;
mpu6500@68 {
status = "okay";
compatible = "invensense,mpu6500";
reg = <0x68>;
irq-gpio = <&gpio3 RK_PD2 IRQ_TYPE_EDGE_RISING>;
mpu-int_config = <0x10>;
mpu-level_shifter = <0>;
mpu-orientation = <0 1 0 1 0 0 0 0 1>;
orientation-x= <0>;
orientation-y= <0>;
orientation-z= <1>;
mpu-debug = <1>;
};
...
};
配置完成后在加载驱动时,of_device_id会调用DTS中文件定义的设备信息,如下所示
//kernel/drivers/staging/iio/imu/inv_mpu/inv_mpu_i2c.c
static const struct of_device_id inv_mpu_of_match[] = {
.............
{ .compatible = "invensense,mpu6500", },
.............
{}
};
定义变量i2c_device_id:
//kernel/drivers/staging/iio/imu/inv_mpu/inv_mpu_i2c.c
static const struct i2c_device_id inv_mpu_id[] = {
.............
{"mpu6500", INV_MPU6500},
.............
{}
};
MODULE_DEVICE_TABLE(of, inv_mpu_of_match);
i2c_driver如下所示:
//kernel/drivers/staging/iio/imu/inv_mpu/inv_mpu_i2c.c
static struct i2c_driver inv_mpu_driver = {
.class = I2C_CLASS_HWMON,
.probe = inv_mpu_probe,
.remove = inv_mpu_remove,
.shutdown = inv_mpu_shutdown,
.id_table = inv_mpu_id,
.driver = {
.owner = THIS_MODULE,
.name = "inv-mpu-iio",
.pm = INV_MPU_PMOPS,
.of_match_table = of_match_ptr(inv_mpu_of_match),
},
.address_list = normal_i2c,
};
注:变量 id_table 指示该驱动所支持的设备。
最后使用i2c_add_driver函数注册i2c驱动
//kernel/drivers/staging/iio/imu/inv_mpu/inv_mpu_i2c.c
static int __init inv_mpu_init(void)
{
int result = i2c_add_driver(&inv_mpu_driver);
pr_info("%s:%d\n", __func__, __LINE__);
if (result) {
pr_err("failed\n");
return result;
}
return 0;
}
.................
module_init(inv_mpu_init);
在调用 i2c_add_driver 注册 I2C 驱动时,会遍历 I2C 设备,如果该驱动支持所遍历到的设备,则会调用该驱动的 probe 函数
//kernel/drivers/staging/iio/imu/inv_mpu/inv_mpu_i2c.c
static int inv_mpu_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct inv_mpu_iio_s *st;
struct iio_dev *indio_dev;
int result;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
result = -ENOSYS;
pr_err("I2c function error\n");
goto out_no_free;
}
#ifdef INV_KERNEL_3_10
indio_dev = iio_device_alloc
..............
在注册完成设备后,便可进行i2c通信。
//kernel/drivers/staging/iio/imu/inv_mpu/inv_mpu_i2c.c
static int inv_i2c_single_write(struct inv_mpu_iio_s *st, u8 reg, u8 data)
{
return inv_i2c_single_write_base(st, st->i2c_addr, reg, data);
}
static int inv_i2c_read(struct inv_mpu_iio_s *st, u8 reg, int len, u8 *data)
{
return inv_i2c_read_base(st, st->i2c_addr, reg, len, data);
}
static int inv_i2c_secondary_read(struct inv_mpu_iio_s *st, u8 reg, int len, u8 *data)
{
return inv_i2c_read_base(st, st->plat_data.secondary_i2c_addr, reg, len, data);
}
static int inv_i2c_secondary_write(struct inv_mpu_iio_s *st, u8 reg, u8 data)
{
return inv_i2c_single_write_base(st, st->plat_data.secondary_i2c_addr, reg, data);
}
.....