linux I2C 总线驱动总结

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_createdevice_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 函数
所涉及的函数
  1. 分配
struct i2c_adapter *adap = kzalloc(sizeofstruct i2c_adapter),GFP_KERNEL)
  1. 设置
adap->owner = THIS_MODULE;
adap->algo = &stm32f7_i2c_algo;
  1. 注册 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
  1. 反注册
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   = &reg,
        },
        {
            .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).

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值