i2c驱动在Linux通过一个周的学习后发现i2c总线的驱动框架还是和Linux整体的驱动框架是相同的,思想并不特殊比较复杂的内容如i2c核心的内容都是内核驱动框架实现完成的,今天我们暂时只分析驱动开发需要完成的部分。iic驱动驱动开发主要是完成四个部分的内容,struct i2c_adapter(适配器),struct i2c_algorithm (通讯接口),i2c_driver(设备驱动),struct i2c_client(iic设备),其中适配器和通讯接口的实现一般SOC厂商都会针对自家的i2c控制器IP实现在Linux 内核驱动中,也就是可以参考。即使没有驱动开发人员也可以自己实现主要就是按照框架需求实现主要的接口函数就可以了。所以这里需要先明确一点的就是I2C驱动是分为总线驱动和设备驱动的,设备在I2C架构中由i2c_client描述,用户空间也可以直接使用总线驱动完成和总线上的设备的数据交换只是有些设备的i2c操作时序很复杂,如果在用户空间来操作i2c设备效率和软件分层上也是不符合常用习惯的。所以I2C的拓扑图大就是总线驱动,设备驱动和设备,设备和设备驱动都是挂接在具体的总线上的这是由硬件决定的,有了设备驱动对i2c设备的操作就编程读写普通文件一样容易从而降低了用户控件的程序开发的复杂程度并提高了执行效率。
总线驱动
i2c_adapter 主要是用来抽像的描述i2c控制器的,结合其中algo通讯接口部分一起组成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; /* in jiffies */ int retries; struct device dev; /* the adapter device */ int nr; char name[48]; struct completion dev_released; struct mutex userspace_clients_lock; struct list_head userspace_clients; struct i2c_bus_recovery_info *bus_recovery_info; };
其中的algo 指向适配对应到的具体的硬件的操作接口封装。除此之外其中还有一些属性如retries重试次数,class 保存i2c的一些特有的属性标志。总线驱动的适配器的部分内容比较简单基本就是一些软件部分内容的实现设置;要完成i2c总线驱动部分最主要的还是硬件通讯接口的实现,这一部分是和具体的硬件相关的所以也是驱动开发的重点。i2c总线驱动将硬件操作接口抽象封装如下:
struct i2c_algorithm { /* If an adapter algorithm can't do I2C-level access, set master_xfer to NULL. If an adapter algorithm can do SMBus access, set smbus_xfer. If set to NULL, the SMBus protocol is simulated using common I2C messages */ /* master_xfer should return the number of messages successfully processed, or a negative value on error */ int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); /* To determine what the adapter supports */ u32 (*functionality) (struct i2c_adapter *); };
其中master_xfer接口就是i2c主机驱动传输数据的接口,包括收和发。其中smbus_xfer 用于smbus时序发送数据。因为i2c接口和smbus是相同的有些SOC的i2c外设控制器同时支持这两种时序发送数据。最后就是functionality 接口他会返回当前总线支持的通讯协议和特性。三星的实现如下,其中具体的标志的含义可以参考具体说明文档。I2C_FUNC_I2C 指示支持I2C时序,I2C_FUNC_SMBUS_EMUL 应该是支持smbus时序。
/* declare our i2c functionality */ static u32 s3c24xx_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART | I2C_FUNC_PROTOCOL_MANGLING; }
i2c总线驱动的的定义一步骤如下(前提是已经实现了接口封装中的各个通讯函数)
- 分配内存。
- 然后开始配置具体的成员变量。
- 在绑定具体的接口函数即algo。
最后就是总线驱动的注册接口了由i2c内核实现,完成向内核注册增加一个I2C总线适配器。
增加适配器
int i2c_add_adapter(struct i2c_adapter * adap);
int i2c_add_adapter(struct i2c_adapter *adapter) { struct device *dev = &adapter->dev; int id; //设备树的方式添加设备 if (dev->of_node) { id = of_alias_get_id(dev->of_node, "i2c"); if (id >= 0) { adapter->nr = id; return __i2c_add_numbered_adapter(adapter); } } //普通将设备写在板级文件中的方式 mutex_lock(&core_lock); //分配一个总线ID 这个ID有总线驱动子系统维护 id = idr_alloc(&i2c_adapter_idr, adapter, __i2c_first_dynamic_bus_num, 0, GFP_KERNEL); mutex_unlock(&core_lock); if (id < 0) return id; adapter->nr = id; //适配器的注册,前提适配器有id return i2c_register_adapter(adapter); }
整个执行流程基本分为如下的过程,如果未指定适配器id则先申请ID然后就是调用i2c_register_adapter
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; } /* Sanity checks */ if (unlikely(adap->name[0] == '