1、通信接口
i2c发送或者接收一次数据都以数据包 struct i2c_msg 封装
- struct i2c_msg {
- __u16 addr; // 从机地址
- __u16 flags; // 标志
- #define I2C_M_TEN 0x0010 // 十位地址标志
- #define I2C_M_RD 0x0001 // 接收数据标志
- __u16 len; // 数据长度
- __u8 *buf; // 数据指针
- };
其中addr为从机地址;flags则是这次通信的标志,发送数据为0,接收数据则为 I2C_M_RD;len为此次通信的数据字节数;buf 为发送或接收数据的指针。在设备驱动中我们通常调用 i2c-core 定义的接口 i2c_master_send 和 i2c_master_recv 来发送或接收一次数据。
- int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
- {
- int ret;
- struct i2c_adapter *adap=client->adapter; // 获取adapter信息
- struct i2c_msg msg; // 定义一个临时的数据包
- msg.addr = client->addr; // 将从机地址写入数据包
- msg.flags = client->flags & I2C_M_TEN; // 将从机标志并入数据包
- msg.len = count; // 将此次发送的数据字节数写入数据包
- msg.buf = (char *)buf; // 将发送数据指针写入数据包
- ret = i2c_transfer(adap, &msg, 1); // 调用平台接口发送数据
- /* If everything went ok (i.e. 1 msg transmitted), return #bytes
- transmitted, else error code. */
- return (ret == 1) ? count : ret; // 如果发送成功就返回字节数
- }
- EXPORT_SYMBOL(i2c_master_send);
i2c_master_send 接口的三个参数:client 为此次与主机通信的从机,buf 为发送的数据指针,count 为发送数据的字节数。
- int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
- {
- struct i2c_adapter *adap=client->adapter; // 获取adapter信息
- struct i2c_msg msg; // 定义一个临时的数据包
- int ret;
- msg.addr = client->addr; // 将从机地址写入数据包
- msg.flags = client->flags & I2C_M_TEN; // 将从机标志并入数据包
- msg.flags |= I2C_M_RD; // 将此次通信的标志并入数据包
- msg.len = count; // 将此次接收的数据字节数写入数据包
- msg.buf = buf;
- ret = i2c_transfer(adap, &msg, 1); // 调用平台接口接收数据
- /* If everything went ok (i.e. 1 msg transmitted), return #bytes
- transmitted, else error code. */
- return (ret == 1) ? count : ret; // 如果接收成功就返回字节数
- }
- EXPORT_SYMBOL(i2c_master_recv);
i2c_master_recv 接口的三个参数:client 为此次与主机通信的从机,buf 为接收的数据指针,count 为接收数据的字节数。我们看一下 i2c_transfer 接口的参数说明:
- int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
- static int read_reg(struct i2c_client *client, unsigned char reg, unsigned char *data)
- {
- int ret;
- struct i2c_msg msgs[] = {
- {
- .addr = client->addr,
- .flags = 0,
- .len = 1,
- .buf = ®, // 寄存器地址
- },
- {
- .addr = client->addr,
- .flags = I2C_M_RD,
- .len = 1,
- .buf = data, // 寄存器的值
- },
- };
- ret = i2c_transfer(client->adapter, msgs, 2); // 这里 num = 2,通信成功 ret = 2
- if (ret < 0)
- tp_err("%s error: %d\n", __func__, ret);
- return ret;
- }
还可调用前面所述的接口封装:
- static unsigned char read_reg(struct i2c_client *client, unsigned char reg)
- {
- unsigned char buf;
- i2c_master_send(client, ®, 1); // 发送寄存器地址
- i2c_master_recv(client, &buf, 1); // 接收寄存器的值
- return buf;
- }
最近因为平台的i2c总线经常发生死锁,用逻辑分析仪检测发现通常为SDA和SCL都被拉低,于是在i2c-core中加入了reset机制,总体思路如下:
(1)在i2c.driver和i2c.adapter的结构中加入reset接口,即每一个i2c设备都可以注册reset函数,每条i2c总线都有相应的reset接口
(2)当发生死锁时,首先根据i2c-timeout的信息获取当前通信的设备地址和总线编号,然后依次执行当前总线下所有i2c设备的reset函数,再尝试发送是否成功;如果总线仍然处于死锁状态则执行i2c.adapter的reset函数;如果总线还是处于死锁状态就重启机器;总共3层reset机制
(3)i2c.driver的reset函数一般操作设备的reset pin或者电源(需要根据硬件设计进行相应操作)
(4)i2c.adapter的reset函数首选进行SCL的模拟解锁方案,然后再是操作整个总线上设备的电源(需要根据硬件设计进行相应操作)
(5)重启是最后的一层机制,此时无法恢复设备的正常使用就只能重启了
因为i2c.adapter层的需要,在i2c-core中加入了遍历当前总线所有设备并执行设备reset函数的接口i2c_reset_device:
- /**
- * i2c_reset_device - reset I2C device when bus dead
- * @adapter: the adapter being reset
- * @addr: the device address
- */
- static int __i2c_reset_device(struct device *dev, void *addrp)
- {
- struct i2c_client *client = to_i2c_client(dev);
- int addr = *(int *)addrp;
- if (client && client->driver && client->driver->reset)
- return client->driver->reset();
- return 0;
- }
- int i2c_reset_device(struct i2c_adapter *adapter, int addr)
- {
- return device_for_each_child(&adapter->dev, &addr, __i2c_reset_device);
- }
- EXPORT_SYMBOL(i2c_reset_device);
需要注意的是i2c.driver的reset函数返回值需要为0,不然device_for_each_child不会继续后面的遍历。用GPIO模拟SCL解锁的参考代码如下:
- static int i2c_reset_adapter(void)
- {
- int counter = 0;
- gpio_request(I2C_BUS_DATA, "gpioxx");
- gpio_request(I2C_BUS_CLK, "gpioxx");
- /* try to recover I2C bus */
- gpio_direction_input(I2C_BUS_DATA);
- if (!__gpio_get_value(I2C_BUS_DATA)) {
- while((!__gpio_get_value(I2C_BUS_DATA)) && ++counter < 10)
- {
- udelay(5);
- gpio_direction_output(I2C_BUS_CLK, 1);
- udelay(5);
- gpio_direction_output(I2C_BUS_CLK, 0);
- }
- i2c_err("try to recover i2c bus, retry times are %d\n",counter);
- if (counter < 10) {
- udelay(5);
- gpio_direction_output(I2C_BUS_DATA, 0);
- udelay(5);
- gpio_direction_output(I2C_BUS_CLK, 1);
- udelay(5);
- gpio_direction_output(I2C_BUS_DATA, 1);
- msleep(10);
- } else {
- i2c_err("try to recover i2c bus failed!\n");
- }
- }
- gpio_free(I2C_BUS_DATA);
- gpio_free(I2C_BUS_CLK);
- return 0;
- }
3、i2c_detect 接口
最近在看一个 wifi 驱动的时候发现了另外一种注册 i2c 设备的方式,其实也是属于动态注册的一种,不过内核支持得更好,可以用内核提供的宏来实现。该方法以 struct i2c_client_address_data 结构为基础,我们先来看下这个结构的组成:
- struct i2c_client_address_data {
- const unsigned short *normal_i2c; /* 常规探测地址 */
- const unsigned short *probe; /* 常规的通道和地址 */
- const unsigned short *ignore; /* 过滤的通道和地址 */
- const unsigned short * const *forces; /* 强制的通道和地址 */
- };
- static const unsigned short normal_i2c[] = {0x2c, 0x2d, 0x2e, I2C_CLIENT_END}; /* 该数组定义了3个可能的设备地址 */
- static const struct i2c_device_id wifi_core_i2c_id[] = {{WIFI_CORE_I2C_DEVNAME, 0}, {}};
- MODULE_DEVICE_TABLE(i2c, wifi_core_i2c_id);
- static unsigned short wifi_core_force[] = {ANY_I2C_BUS, WIFI_CORE_ADDR, I2C_CLIENT_END, I2C_CLIENT_END};
- static const unsigned short * const wifi_core_forces[] = {wifi_core_force, NULL};
- static struct i2c_client_address_data wifi_core_addr_data = {
- .forces = wifi_core_forces,
- };
- static int wifi_core_detect(struct i2c_client *client, int kind, struct i2c_board_info *info)
- {
- strcpy(info->type, WIFI_CORE_I2C_DEVNAME); /* 如果探测成功则在detect接口中至少要设置设备名称 */
- return 0;
- }
- static struct i2c_driver wifi_core_driver =
- {
- .probe = wifi_core_probe,
- .remove = wifi_core_remove,
- .detect = wifi_core_detect,
- .shutdown = wifi_shutdown,
- .driver.name = WIFI_CORE_I2C_DEVNAME,
- .id_table = wifi_core_i2c_id,
- .address_data = &wifi_core_addr_data,
- };
- i2c_add_driver(&wifi_core_driver)