1. 相关结构体
在i2c驱动总线模型下,存在以下几个重要的结构体:
i2c_driver: 用于表示i2c驱动,结构体成员包括 probe、remove、成员函数,id_table、of_match_table 成员数组,用于和设备i2c_client做匹配,
i2c_client:用来表示一个挂载在i2c总线下的一个设备,结构体成员包括 addr – 芯片地址、name数据存放设备名、以及 i2c_adapter,表示对应的i2c控制器
i2c_adapter :内核中描述一个i2c控制器,负责i2c设备与驱动之间的通信。主要成员 struct i2c_algorithm 结构体(master_xfer,用于传输一个或多个i2c_msg),和 nr 变量
2. i2c_driver
从入口函数说,驱动加载需要调用驱动程序中的init函数,在init函数中需要将i2c驱动注册到i2c总线上,在注册过程中,会调用 i2c_register_driver函数,在该函数里面实现驱动类型的赋值,之后调用 driver_register,在该函数里面查找该驱动(driver_find)检测该驱动是否已经注册,调用 bus_add_driver函数,去实现将驱动加载到总线上,在该函数中会依次获得总线类型、初始化链表、将驱动插入链表,并调用 driver_attach 去在设备链表中与驱动程序匹配的设备,如果匹配成功,则会调用driver_probe_device(drv,dev)函数做了一堆检查,最终调用 really_probe(dev, drv) 函数,内核会优先调用总线的probe函数,如果他不存在,则会调用对应驱动的probe函数,并在之前还会在sysfs下建立好对应的文件,如果失败就取消注册。至此,为整个驱动注册到总线的过程。
在驱动程序注册完成之后,会调用到驱动程序的probe函数。在函数内部,调用 register_chrdev 实现字符驱动的注册,将驱动程序的file_operations结构体注册到内核,(参数:主设备、驱动名、file_opertions结构体),并调用 class_create 和 device_create 函数,在dev目录下创建出设备节点。
3. i2c_client
表示的是一个挂载在i2c总线下设备,在驱动程序中,需要生成i2c_client 设备,
驱动程序通过以下方法去实现
struct i2c_adapter *i2c_adap = i2c_get_adapter(0); // 0 号i2c总线
- i2c_new_device (adapter, &xxx_info)
static struct i2c_board_info g_board_info = { //不使用设备树描述设备信息时,用此方法。在这个结构体里面, type 和 addr 这两个成员变量是必须要设置的,用宏设置如下
I2C_BOARD_INFO(“es8388”, 0x20), // #define I2C_BOARD_INFO(dev_type, dev_addr) . type = dev_type, .addr = (dev_addr)
}; - 2c_new_probed_device(adapter, &xxx_info, addr_list, NULL)
该方法需要确定有真实的设备存在 - i2c_register_board_info
内核没有EXPORT_SYMBOL(i2c_register_board_info)
使用这个函数的驱动必须编进内核里去
最后调用
- i2c_put_adapter(adapter);
也可以在用户空间创建设备,方便调试
//创建设备
echo at24c08 0x50 > /sys/bus/i2c/devices/i2c-0/new_device
//导致i2c_new_device被调用
//删除设备
echo 0x50 > /sys/bus/i2c/devices/i2c-0/delete_device
//导致i2c_unregister_device
3. i2c_adapter
分配、配置、注册一个i2c_adapter结构体
- i2c_adapter的核心是 i2c_algorithm 结构体
- i2c_algorithm 的核心是 master_xfer 函数
所涉及的函数
- 分配
struct i2c_adapter *adap = kzalloc(sizeof(struct i2c_adapter),GFP_KERNEL)
- 设置
adap->owner = THIS_MODULE;
adap->algo = &stm32f7_i2c_algo;
- 注册 i2c_add_adapter / i2c_add_numbered_adapter
ret = i2c_add_adapter(adap); //不管 adap->nr原来是什么,都动态设置adap->nr
ret = i2c_add_numbered_adapter(adap); //如果 adap->nr ==1 则动态分配nr ;否则使用该 nr
- 反注册
i2c_del_adapter(adap);
4. i2c_msg
一个i2c_msg结构的变量,代表着一次单方向的传输
i2c设备驱动读函数编写
通过上面对于读时序的描述,将i2c的读操作分为“两步”,即写方向和读方向,因此可以有两个i2c_msg结构的变量,分别通过对结构体成员变量flag的赋值来确定传输方向,函数编写如下:
/* 从ap3216c读取多个寄存器数据
* 入口参数@client:从机地址
* @reg :寄存器地址
* @buffer:保存读取数据
* @length:reg/buffer的长度
*/
static int ap3216c_read_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
{
int err = 0;
/* msg[0]是发送要读取的寄存器首地址 */
struct i2c_msg msg[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1, //表示寄存器地址字节长度,是以byte为单位
.buf = ®,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = length, //表示期望读到数据的字节长度(寄存器长度),是以byte为单位
.buf = buffer, //将读取到的数据保存在buffer中
},
};
err = i2c_transfer(client->adapter, msg,2);
if(err != 2)C
{
err = -EINVAL;
printk("read regs from ap3216c has been failed\n\r");
}
return err;
}
i2c设备驱动写函数编写
static int ap3216c_write_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
{
int err = 0;
u8 b[256];
struct i2c_msg msg;
b[0] = reg;
memcpy(&b[1],buffer,length);
msg.addr = client->addr,
msg.flags = 0;
msg.len = length + 1; /* +1是因为还有一个b[0]所存储的寄存器 */
msg.buf = b;
err = i2c_transfer(client->adapter, &msg,1);
if(err != 1)
{
err = -EINVAL;
printk("write data to ap3216c has been failed\n\r");
}
return err;
}
在上述函数中,只使用了一个i2c_msg结构变量,因此整个函数是一次完整单向的数据传输,使用数组是为了让数据连续
分析:首先,i2c_msg是一次完整的单向数据传输,也就是每一个完整的i2c_msg传输过程中,当flag为0时,那么就意味着在进行写寄存器操作,并不是说有两个i2c_msg结构体连续且都为写的情况下,第二个i2c_msg结构体变量就是向第一个i2c_msg结构体变量中的reg进行写数据了,i2c_msg成员变量buf远远没有达到这么智能的程度,它只能知道在一个单次且完整的写过程中,第一个传递给buf的是寄存器地址。因此,尽管程序中用了结构体数组来定义了两个连续的i2c_msg结构体变量,但是它们依旧是两个单次的写数据过程,因此i2c_msg[1]还是写地址,而不是写数据,所以在指定地址读取不到0x03,而是其他值(不一定是0x0).