在上一篇I2C协议的简单介绍后,我们马上结合Linux源码来了解下Linux中的I2C框架是如何的。
1. 基本框架了解
如上图显示的,要讨论的东西包括driver、client、i2c-dev、i2c-core、Algorithm和adapter。在上层的位置中,Client可以简单的理解为具体的物理设备,而driver就是驱动物理设备的驱动程序,i2c-dev则是为不同的client设备提供统一访问接口的作用。i2c-core在中间的位置,起到承上启下的作用,为上层驱动提供client与driver匹配接口以及上层driver和下层的adapter匹配接口,对下层起到将adapter添加到内核中以及adapter和Algorithm进行匹配的作用。下层中的adapter可以理解为芯片内的I2C驱动器,正是因为不同的芯片拥有不同的架构,而且其数据传输适中要求和芯片引脚不同,所以需要搭配不同的Algorithm来使用。
2. 几个重要的结构体
2.1 i2c_driver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
struct i2c_driver { unsigned int class; // 指向依附i2c_adapter的函数指针 int (*attach_adapter)(struct i2c_adapter *) __deprecated; // 指向脱离i2c_adapter的函数指针 int (*detach_adapter)(struct i2c_adapter *) __deprecated; // 标准驱动接口,probe用于初始化,remove用于注销 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); // 类似于ioctl的函数 int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); // 设备驱动结构体 struct device_driver driver; // 适配该驱动的设备TABLE const struct i2c_device_id *id_table; // 检测i2c_client设备 int (*detect)(struct i2c_client *, struct i2c_board_info *); const unsigned short *address_list; // client设备链表头 struct list_head clients; }; |
2.2 i2c_client
1 2 3 4 5 6 7 8 9 10 11 12 |
struct i2c_client { unsigned short flags; /* div., see below */ unsigned short addr; /* chip address - NOTE: 7bit */ /* 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; }; |
如上面的英文注解,addr的底七位记录client的地址,adapter记录client依附的adapter,而driver记录client使用的设备驱动i2c_driver。
2.3 i2c_algorithm
1 2 3 4 5 6 7 8 9 10 11 12 |
struct i2c_algorithm { // i2c传输函数指针 int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); // 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); // 返回i2c_adapter支持的功能 u32 (*functionality) (struct i2c_adapter *); }; |
2.4 i2c_adapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
struct i2c_adapter { struct module *owner; unsigned int class; /* classes to allow probing for */ // 适配的Algorithm const struct i2c_algorithm *algo; /* the algorithm to access the bus */ // Algorithm数据 void *algo_data; /* data fields that are valid for all devices */ // 总线控制互斥锁 struct rt_mutex bus_lock; // 超时时间 int timeout; /* in jiffies */ // 重试次数 int retries; struct device dev; /* the adapter device */ int nr; // adapter的名称 char name[48]; struct completion dev_released; struct mutex userspace_clients_lock; // client链表头 struct list_head userspace_clients; }; |
2.5 i2c_msg
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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_NOSTART */ #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 */ __u16 len; /* msg length */ __u8 *buf; /* pointer to msg data */ }; |
2.6 总结
上面给出了5个常用到的结构体,i2c_msg用于I2C通信的消息传递,这里主要总结下i2c_client、i2c_driver、i2c_adapter和i2c_algorithm之间的关系。
i2c_driver和i2c_client的关系
正如之前所说的,i2c_client代表的是设备上具体的物理设备,例如EEPROM。那么在i2c_client的结构体内存在这i2c_adapter,主要是用于记录client所依附的adapter,当对设备读写时也就能找到对应的adapter以及读写设备的Algorithm了,其次还有一个i2c_driver,主要用于记录该client所使用的i2c_driver。i2c_driver是一个抽象的驱动,不对应任何的物理实体内容。博主目前认为如此设计,主要可能是因为driver和client是一对多的关系,即i2c_client对应的物理设备所使用驱动方法可能是相同的,那么抽象出来就能够服用了。i2c_driver内有一个client链表,就是用来记录以来该抽象i2c_driver的client的。同时i2c_driver提供了client用于注册到和注销相应adapter的方法。
按照这里的说法,在添加新的设备时,肯定是需要填写对应的i2c_client结构体的,至于i2c_driver结构体,如果没有适合的i2c_driver可以复用的话,那么也是需要工程师进行添加的。
i2c_adapter和i2c_algorithm的关系
i2c_adapter对应的是物理上的适配器,一般这个适配器会集成在芯片内,而i2c_algorithm对应的是一套通信的方法。i2c_algorithm就是为特定的i2c_adapter提供通信方法的,如果没有为i2c_adapter提供对应的i2c_algorithm的话,那么这个i2c_adapter就什么都做不了。所以我们会发现在i2c_adapter结构体内会存在一个记录i2c_algorithm的指针。而i2c_algorithm中的master_xfer和smbus_xfer指针就是记录对应的通信方法的。在执行I2C通信的时候会调用到。
那么对于工程师来说,如果如果没有提供对应的适配器方法就需要自己填写i2c_adapter结构体,既然适配器的内容是自行添加的,那么对应的i2c通信方法也需要根据具体的时序和芯片手册标注的寄存器进行编程。这一部分也是需要工程师干预的。
i2c_adapter和i2c_client的关系
结合上面介绍的内容,很容易就能理解i2c_adapter和i2c_client之间的关系了,无非就是物理适配器和物理设备之间的对应关系。i2c_client要依附于i2c_adapter。由于一个适配器上可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client的链表。
所以,可能需要工程师干预的部分如下:
- 根据I2C适配器的特性,提供I2C适配器的硬件驱动、探测和初始化适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适配器从硬件上产生各种信息以及处理I2C中断
- 提供与I2C适配器协同运作的Algorithm,用具体适配器的xxx_xfer()方法填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的alog指针
- 实现I2C设备的i2c_driver接口,使用具体xxx_attach_adapter()函数指针、xxx_detach_client()函数指针和xxx_command()函数指针赋给i2c_driver的对应指针域
- 实现具体I2C设备的文件操作接口,如open、write、read接口。
上面说到的前两点和后两点是可以分开进行的。毕竟可以将i2c_adapter可看做是一个驱动来向内核注册。
3. i2c-core简单分析
i2c-core中存在着许多有用的函数,所以这里只列出部分函数的代码,有兴趣的可以自行查看i2c-core.c文件进行查看。
3.1 i2c_device_match
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, const struct i2c_client *client) { while (id->name[0]) { if (strcmp(client->name, id->name) == 0) return id; id++; } return NULL; } static int i2c_device_match(struct device *dev, struct device_driver *drv) { struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver; if (!client) return 0; /* Attempt an OF style match */ if (of_driver_match_device(dev, drv)) return 1; driver = to_i2c_driver(drv); /* match on an id table if there is one */ if (driver->id_table) return i2c_match_id(driver->id_table, client) != NULL; return 0; } |
i2c_device_match就是用来匹配设备device和设备driver的函数,看上面的代码可以知道,匹配过程首先尝试通过设备树的方法,如果设备树的方式匹配失败了在调用i2c_match_id函数进行匹配,i2c_match_id方法是通过名字进行匹配的。所以,我们编写的i2c_client和i2c_driver结构体的时候,其名字字段必须是相同的。
3.2 i2c_add_adapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
static int i2c_register_adapter(struct i2c_adapter *adap) { int res = 0; /* Can't register until after driver model init */ if (unlikely(WARN_ON(!i2c_bus_type.p))) { res = -EAGAIN; goto out_list; } // adapter的名字不能为空 if (unlikely(adap->name[0] == '\0')) { pr_err("i2c-core: Attempt to register an adapter with " "no name!\n"); return -EINVAL; } // adapter的algorithm不能为空 if (unlikely(!adap->algo)) { pr_err("i2c-core: Attempt to register adapter '%s' with " "no algo!\n", adap->name); return -EINVAL; } // 初始化互斥锁和client链表 rt_mutex_init(&adap->bus_lock); mutex_init(&adap->userspace_clients_lock); INIT_LIST_HEAD(&adap->userspace_clients); /* Set default timeout to 1 second if not already set */ if (adap->timeout == 0) adap->timeout = HZ; dev_set_name(&adap->dev, "i2c-%d", adap->nr); adap->dev.bus = &i2c_bus_type; adap->dev.type = &i2c_adapter_type; // 调用device_register注册adapter设备 res = device_register(&adap->dev); if (res) goto out_list; dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); #ifdef CONFIG_I2C_COMPAT res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev, adap->dev.parent); if (res) dev_warn(&adap->dev, "Failed to create compatibility class link\n"); #endif /* create pre-declared device nodes */ if (adap->nr < __i2c_first_dynamic_bus_num) i2c_scan_static_board_info(adap); // 扫描设备,触发匹配过程 /* Notify drivers */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock); return 0; out_list: mutex_lock(&core_lock); idr_remove(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); return res; } int i2c_add_adapter(struct i2c_adapter *adapter) { int id, res = 0; retry: if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) return -ENOMEM; mutex_lock(&core_lock); /* "above" here means "above or equal to", sigh */ res = idr_get_new_above(&i2c_adapter_idr, adapter, __i2c_first_dynamic_bus_num, &id); mutex_unlock(&core_lock); if (res < 0) { if (res == -EAGAIN) goto retry; return res; } adapter->nr = id; return i2c_register_adapter(adapter); } EXPORT_SYMBOL(i2c_add_adapter); |
i2c_add_adapter会调用到i2c_register_adapter进行adapter添加。那么i2c_del_adapter是完成的工作理应和上面的相反的,这里就不列出i2c_del_adapter的代码了。
3.3 i2c_register_driver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
int i2c_register_driver(struct module *owner, struct i2c_driver *driver) { int res; /* Can't register until after driver model init */ if (unlikely(WARN_ON(!i2c_bus_type.p))) return -EAGAIN; // 设置driver的内容 driver->driver.owner = owner; driver->driver.bus = &i2c_bus_type; /* When registration returns, the driver core * will have called probe() for all matching-but-unbound devices. */ // 如注释说,会调用driver core的probe()方法尽心设备匹配 res = driver_register(&driver->driver); if (res) return res; /* Drivers should switch to dev_pm_ops instead. */ if (driver->suspend) pr_warn("i2c-core: driver [%s] using legacy suspend method\n", driver->driver.name); if (driver->resume) pr_warn("i2c-core: driver [%s] using legacy resume method\n", driver->driver.name); pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name); INIT_LIST_HEAD(&driver->clients); // 扫描设备,触发匹配过程 i2c_for_each_dev(driver, __process_new_driver); return 0; } EXPORT_SYMBOL(i2c_register_driver); |
3.4 i2c_master_send && i2c_master_recv
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
int i2c_master_send(const struct i2c_client *client, const char *buf, int count) { int ret; struct i2c_adapter *adap = client->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); int i2c_master_recv(const struct i2c_client *client, char *buf, int count) { struct i2c_adapter *adap = client->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_xxxx操作都是先填充对应的i2c_msg结构体的内容,然后调用i2c_transfer进行实际的通信操作。同时应该注意到,i2c_master_send和i2c_master_recv方法只能够发送或接收一条i2c_msg信息,如果要一次发送或读取多条i2c_msg信息的话,就需要另寻方法了。
3.5 i2c_transfer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { unsigned long orig_jiffies; int ret, try; if (adap->algo->master_xfer) { #ifdef DEBUG for (ret = 0; ret < num; ret++) { dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, " "len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W', msgs[ret].addr, msgs[ret].len, (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : ""); } #endif if (in_atomic() || irqs_disabled()) { ret = i2c_trylock_adapter(adap); if (!ret) /* I2C activity is ongoing. */ return -EAGAIN; } else { i2c_lock_adapter(adap); } /* Retry automatically on arbitration loss */ orig_jiffies = jiffies; // 正如前面所说,这里会调用到adapter的algorithm的master_xfer方法 for (ret = 0, try = 0; try <= adap->retries; try++) { ret = adap->algo->master_xfer(adap, msgs, num); if (ret != -EAGAIN) break; if (time_after(jiffies, orig_jiffies + adap->timeout)) break; } i2c_unlock_adapter(adap); return ret; } else { dev_dbg(&adap->dev, "I2C level transfers not supported\n"); return -EOPNOTSUPP; } } EXPORT_SYMBOL(i2c_transfer); |
i2c-core.c文件中还有很多函数方法,这里就不一一列出了,毕竟暂时能力有限,理解不对反而祸害了读者,哈哈!
4. i2c-dev.c简单分析
i2c-dev.c可以看做一个I2C设备驱动。在我看来,i2c-dev是为软件访问I2C设备提供统一的接口。这样,我们的i2c_driver就不用编写过多的read、write方法了。可以通过i2c-dev内的方法,加上i2c_adapter中的algorithm,就能完成I2C的传输。
下面稍微看几个i2c-dev中的函数
4.1 i2cdev_read
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offset) { char *tmp; int ret; // 获取client的私有数据 struct i2c_client *client = file->private_data; if (count > 8192) count = 8192; tmp = kmalloc(count, GFP_KERNEL); if (tmp == NULL) return -ENOMEM; pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n", iminor(file->f_path.dentry->d_inode), count); // 调用i2c-core中的i2c_master_recv方法 ret = i2c_master_recv(client, tmp, count); if (ret >= 0) ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret; kfree(tmp); return ret; } int i2c_master_recv(const struct i2c_client *client, char *buf, int count) { // 获取client中对应的adapter struct i2c_adapter *adap = client->adapter; struct i2c_msg msg; int ret; // 构造i2c_msg结构体 msg.addr = client->addr; msg.flags = client->flags & I2C_M_TEN; msg.flags |= I2C_M_RD; msg.len = count; msg.buf = buf; // 调用i2c_transfer方法,该方法中最后会调用algorithm中的实际传输方法 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; } |
上面可以看出来,i2cdev_read也是会调用到i2c-core中的方法。而且看i2cdev_read中的代码就能感觉到该方法是通用的。
4.2 i2cdev_write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
static ssize_t i2cdev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) { int ret; char *tmp; struct i2c_client *client = file->private_data; if (count > 8192) count = 8192; tmp = memdup_user(buf, count); if (IS_ERR(tmp)) return PTR_ERR(tmp); pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n", iminor(file->f_path.dentry->d_inode), count); ret = i2c_master_send(client, tmp, count); kfree(tmp); return ret; } |
i2cdev_write方法和read方法基本类似。
当然还有IOCTL方法了,这里就不贴代码了,大家想了解的还是看看源码比较好。之后找个机会和时间实际使用i2c-dev中的方法来编写一个应用来访问I2C好了,那样应该能够更好的理解和使用i2c-dev的作用。