linux驱动之i2c框架

一、前言

I2C总线 是一种常用的总线协议,在设备中经常看到,比如 sensor陀螺仪等都是使用 I2C总线。而 Linux内核 也提供成熟的 I2C框架,工程师可以根据硬件特性直接使用该框架编写驱动程序。本文将着重阐述 Linux内核 关于 I2C总线 的一些概念和实现

PS:本文默认读者已经熟悉I2C协议,请不了解I2C协议的读者自行查阅其他资料了解

二、I2C总线

2.1 重要概念

在了解 Linux内核I2C框架 之前,需要对框架的一些重要概念进行认识,这样有助于理解框架的设计及思路,加深印象。

  • i2c_bus_typei2c_bus_typeLinux内核设备框架 中的 总线。该 变量 是一个全局变量,用于 匹配和删除I2C设备和I2C驱动 ,并负责提供 匹配规则i2c_bus_typei2c_device_match 会查看 驱动设备 是否 匹配,如果匹配则通过 i2c_device_probe 调用 驱动的probe函数
    其代码如下:
struct bus_type i2c_bus_type = {
    .name       = "i2c",
    .match      = i2c_device_match,
    .probe      = i2c_device_probe,
    .remove     = i2c_device_remove,
    .shutdown   = i2c_device_shutdown,
};
  • i2c_adapteri2c_adapter 称为 I2C适配器。所谓 I2C适配器 就是 Soc 上的 I2C控制器,而 i2c_adapter 就是 硬件I2C控制器驱动实现,即实现了 CPU通过I2C控制器与外界进行数据交换
struct i2c_adapter {
    /* I2C适配器的通信方法 */
    const struct i2c_algorithm *algo;
    /* I2C适配器的device结构体,表明其也是一个设备 */
    struct device dev;
};
  • i2c_algorithm i2c_algorithmI2C适配器通信方法 实现,一般通过该结构体实现 数据的发送和接受
struct i2c_algorithm {
    /* 主机发送函数 */
    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);
    /* 返回通信方法适用的适配器特性 */
    u32 (*functionality) (struct i2c_adapter *);
};
  • i2c_clienti2c_client 就是 I2C设备,该结构体描述了 I2C设备硬件信息
struct i2c_client {
    /* 设备I2C地址 */
    unsigned short addr;        /* chip address - NOTE: 7bit    */
    /* 设备名称 */
    char name[I2C_NAME_SIZE];
    /* I2C适配器 */
    struct i2c_adapter *adapter;    /* the adapter we sit on    */
    /* device结构体,表明其为一个设备 */
    struct device dev;      /* the device structure     */
};
  • i2c_driveri2c_driver 就是 I2C驱动程序,就是 I2C硬件设备端的实现。一般挂接在 I2C适配器 上,并通过 I2C适配器CPU 交换数据。
struct i2c_driver {
    /* 驱动的probe函数 */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    /* 驱动的remove函数 */
    int (*remove)(struct i2c_client *);
    /* I2C驱动的device_driver结构体,表明其也是一个设备驱动 */
    struct device_driver driver;
    /* 驱动ID table,用于匹配 */
    const struct i2c_device_id *id_table;
    /* 该驱动拥有的I2C地址 */
    const unsigned short *address_list;
    /* 该驱动拥有的I2C设备链表 */
    struct list_head clients;
};

笔者粗略的画了下他们之间的关系图,如下所示:

关系图

虚线表示对应关系,实现表示数据

  1. 硬件信息 通过 设备树 解析到了 i2c_client
  2. i2c_driver匹配信息驱动程序 中填充。
  3. i2c_clienti2c_driver 通过 i2c_bus_type 进行匹配,如果成功 i2c_bus_type 调用 i2c_driverprobe函数
  4. i2c_driver 通过 i2c_adapter 向外发送数据

2.2 代码分层

了解完主要的数据结构,下面看看 I2C框架 的主体代码组成,其源码目录在 /drivers/i2c。下分多个文件和文件夹,包括 I2C框架代码不同体系结构的I2C驱动代码 等。
I2C框架的主体代码和目录 分别如下:

  • i2c-core.cI2C框架核心功能源码,该文件使得 I2C框架 实现了 硬件软件 的分层。
  • i2c-dev.c:该源码实现了 I2C适配器设备文件的功能,给 I2C适配器 分配一个 设备号 并将其实现为 /dev目录 下的 设备。用户可以通过该 设备文件 直接在 应用层 操作 Soc的I2C控制器
  • busses:不同 Soc的I2C控制器 的驱动代码。

2.3 代码讲解

2.3.1 I2C驱动注册

编写 I2C设备驱动 时一般按照下面几个步骤进行:

  1. 使用 i2c_add_driver 添加驱动
  2. 实现 I2C设备驱动操作集,比如 proberemove
  3. 实现 I2C设备文件操作集,比如 readwrite

实现设备调用 i2c_add_driver函数 有多种方式,如下:

  • 使用 宏module_i2c_driver 直接添加驱动,该宏会在 驱动装载 时自动调用 i2c_add_driver函数,其本质是注册 驱动初始函数驱动去初始化函数
  • 与一般设备一样,通过设备树或多种方法 手动调用i2c_add_driver

i2c_add_driver 调用图谱如下:

i2c_add_driver(宏)
  ->i2c_register_driver(函数)
    ->driver_register(想内核注册驱动)
      ->bus_add_driver(在i2c_bus_type上注册驱动)
    ->i2c_for_each_dev(driver, __process_new_driver);(为每个驱动调用__process_new_driver函数)
      ->__process_new_driver
        ->i2c_do_add_adapter(加驱动添加到对应的I2C适配器)
          ->i2c_detect(I2C适配器探测是否存在驱动所对应的I2C设备)
            ->i2c_detect_address(I2C适配器使用地址对I2C设备进行探测)
              ->i2c_new_device(生成新的i2c_client(用于描述I2C设备硬件信息),并挂接在I2C驱动上)

代码如下:

#define module_i2c_driver(__i2c_driver) \
    module_driver(__i2c_driver, i2c_add_driver, \
            i2c_del_driver)
#define i2c_add_driver(driver) \
    i2c_register_driver(THIS_MODULE, driver)

struct bus_type i2c_bus_type = {
    .name       = "i2c",
    .match      = i2c_device_match,
    .probe      = i2c_device_probe,
    .remove     = i2c_device_remove,
    .shutdown   = i2c_device_shutdown,
};

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    ....
    /* 注册i2c_driver的总线为i2c_bus_type  */
    driver->driver.bus = &i2c_bus_type;
    /* 初始化i2c_driver的i2c_client链表,一个driver可以有多个client,挂接在该链表上 */
    INIT_LIST_HEAD(&driver->clients);
    ....
    /* 注册驱动 */
    res = driver_register(&driver->driver);
    ....
    /* 对i2c_driver上的每一个i2c_client都调用__process_new_driver */
    i2c_for_each_dev(driver, __process_new_driver);

    return 0;
}

下面分为 2个 阶段来阐述注册过程:

  • driver_register:注册驱动
  • i2c_for_each_dev:遍历设备并调用 __process_new_driver 创建 i2c_client

下面先看看 driver_register 的代码流程:

int driver_register(struct device_driver *drv)
{
    ......
    /* 将驱动添加到总线上 */
    ret = bus_add_driver(drv);
    ......
    return ret;
}

int bus_add_driver(struct device_driver *drv)
{
     /* 将驱动添加到总线的驱动链表(bus->p->klist_drivers) */
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
    ......
    if (drv->bus->p->drivers_autoprobe) {
        if (driver_allows_async_probing(drv)) {
            ......
        } else {
            error = driver_attach(drv);
            ......
        }
    }
    ......
    return 0;

}

int driver_attach(struct device_driver *drv)
{
    /* 遍历总线上的所有驱动并调用__driver_attach */
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, fn)
{
    /* 
      遍历i2c_bus_type总线的所有设备链表(bus->p->klist_devices)的所有设备,执行fn函数 
      这里的设备就是i2c_client中的struct device成员
      在设备树解析时会根据i2c节点创建i2c_client,并将其struct device成员链入i2c_bus_type的bus->p->klist_devices链表
    */
    while ((dev = next_device(&i)) && !error)
        error = fn(dev, data);
}

static int __driver_attach(struct device *dev, void *data)
{
    struct device_driver *drv = data;
    int ret;
    /* 匹配设备和驱动,如果不成功则返回 */
    ret = driver_match_device(drv, dev);
        if (ret == 0) {
        /* no match */
        return 0;
    }
    ......
    /* 匹配设备和驱动成功,调用probe函数 */
    if (!dev->driver)
        driver_probe_device(drv, dev);
    ......
    return 0;
}
static inline int driver_match_device(struct device_driver *drv,
                      struct device *dev)
{
    /* 调用i2c_bus_type总线的macth函数,即i2c_device_match函数 */
    return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{

    ......
    driver = to_i2c_driver(drv);

    /* 调用i2c_match_id进行配 */
    if (i2c_match_id(driver->id_table, client))
        return 1;

    return 0;
}

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++;
    }
}

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    ......
    /* 如果匹配到设备则调用probe函数 */
    ret = really_probe(dev, drv);
    ......
}

static int really_probe(struct device *dev, struct device_driver *drv)
{
    /* 调用i2c_bus_type的probe函数,即i2c_device_probe函数 */
    if (dev->bus->probe) {
        ret = dev->bus->probe(dev);
        if (ret)
            goto probe_failed;
    } 
}
static int i2c_device_probe(struct device *dev)
{
    /* 
      调用i2c驱动的probe函数 
    */
    driver->probe(client, i2c_match_id(driver->id_table, client));
}

到了这里,driver_register 的流程基本结束,下面主要执行 i2c_for_each_dev,代码流程如下:

int i2c_for_each_dev(void *data, fn)
{
    ......
    /* 对i2c_bus_type总线上的设备进行遍历 */
    res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
    ......
}
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int fn)
{
    ......
    /* 
      找出挂在bus->p->klist_devices链表上的首个设备(struct device)的迭代器。
      这里可能有读者有疑问,这些struct device什么时链入bus->p->klist_devices的呢
      在设备树解析期间,如果设备树的i2c节点有设备节点,则会调用i2c_new_device。
      该函数会创建一个i2c_client,并将该i2c_client的struct device成员链入bus->p->klist_devices链表
      其中的bus就是i2c_bus_type
    */
    klist_iter_init_node(&bus->p->klist_devices, &i,
                 (start ? &start->p->knode_bus : NULL));
    /* 遍历总线上的设备链表(bus->p->klist_devices)的所有设备,调用fn函数 */
    while ((dev = next_device(&i)) && !error)
        error = fn(dev, data);  
    ......
}
static int __process_new_driver(struct device *dev, void *data)
{
    if (dev->type != &i2c_adapter_type)
        return 0;
    /* 
      根据函数传递进来的参数可知,data参数就是i2c_driver结构体 
      而to_i2c_adapter(dev)用于找出该设备对应的i2c_adapter。
      这里也是同理,在设备树解析时,会根据设备所在的i2c节点(设备树的i2c节点对应一个i2c_控制器)
      将设备链入对应的i2c_adapter结构体
    */
    return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
static int i2c_do_add_adapter(struct i2c_driver *driver,
                  struct i2c_adapter *adap)
{
    /* 使用对应的i2c_adapter探测驱动所对应的设备是否存在 */
    i2c_detect(adap, driver);
    ......
    return 0;
}
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
    ......
    /* 获取驱动的I2C地址链表 */
    address_list = driver->address_list;
    /* 
      如果i2c_driver没实现detect函数,那么将会在这里返回 
      有些i2c驱动没有实现detect函数,所以注册流程到这里就结束了
    */
    if (!driver->detect || !address_list)
        return 0;

    for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
        ......
        /* i2c_detect_address会根据地址链表去探测是否存在设备 */
        temp_client->addr = address_list[i];
        err = i2c_detect_address(temp_client, driver);
        ......
    }
}
static int i2c_detect_address(struct i2c_client *temp_client,
                  struct i2c_driver *driver)
{
    struct i2c_board_info info;
    struct i2c_adapter *adapter = temp_client->adapter;
    int addr = temp_client->addr;
    int err;

    ......
    memset(&info, 0, sizeof(struct i2c_board_info));
    info.addr = addr;
    /* 调用驱动的detecr函数 */
    err = driver->detect(temp_client, &info);
    if (err) {
        /* 失败就返回-ENODEV */
        return err == -ENODEV ? 0 : err;
    }

    /* Consistency check */
    if (info.type[0] == '\0') {
        ......
    } else {
        /* 如果探测成功则使用i2c_new_device创建client并添加到driver的clients链表 */
        client = i2c_new_device(adapter, &info);
        if (client)
            list_add_tail(&client->detected, &driver->clients);
        else
            dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
                info.type, info.addr);
    }
    return 0;
}

有些 I2C驱动 并不会实现 detect函数,所以在知道到 driver_register 时已经完成大部分工作,在 执行 i2c_for_each_dev 时就已经早早退出了。

前面的代码多次提到过 i2c_client结构体struct device结构,从前面的代码讲解中我们已经知道 设备树解析 时会根据 I2C节点 的信息创建对应的 i2c_clientstruct device,那么这里也提一下如何在 设备树 中添加 I2C设备节点信息

Documentation/devicetree/bindings/i2c 目录下的 i2c.txt文档 中可以查看 i2c_adapter 的设备节点信息,而在同一目录下存在 多个Soc平台i2c适配器说明

I2C设备 一般是作为 slaver(从机) 存在,而一般的 I2C从机节点信息 有以下属性:

  • compatible:用于 驱动匹配
  • regI2C设备 的总线地址

下面为 设备树 代码例子:

i2c0@0xXXXXXXXX {
        /* i2c控制器的硬件信息 */
        ......
        /* i2c设备节点信息 */
        i2c_test: i2c_test@34{
                compatible = "xxxx";
                /* reg表明该I2C设备的地址为0x34 */
                reg = <0x34>;
        };
};

2.3.2 I2C设备注册

I2C驱动注册 中使用的是默认的 自动注册设备,也就是通过 设备树,由操作系统帮我们完成 设备的生成和注册

在解析 I2C设备节点 时,一般调用的是:

  • of_i2c_register_device
  • of_i2c_register_devices

它们最终都会调用到 i2c_new_device 来创建 I2C设备

如果在某些场景,比如 设备树 没有 I2C设备节点, 想 手动注册I2C设备 ,那么就可以通过手动调用 i2c_new_device 来创建和注册 I2C设备

其函数调用图谱如下:

->i2c_new_device
    ->device_register
        ->device_add
            ->bus_add_device
            ->bus_probe_device
                ->__device_attach
                    ->driver_match_device
                    ->driver_probe_device
struct i2c_client* i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    ......
    /* 创建完i2c_client后对其进行一列的初始化 */
    client->adapter = adap;
    client->dev.platform_data = info->platform_data;
    client->flags = info->flags;
    client->addr = info->addr;
    client->irq = info->irq;
    strlcpy(client->name, info->type, sizeof(client->name));
    ......
    /* 设备i2c_client的struct device成员 */
    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;
    client->dev.type = &i2c_client_type;
    client->dev.of_node = info->of_node;
    client->dev.fwnode = info->fwnode;
    ......
    i2c_dev_set_name(adap, client);
    ......
    /* 注册shebei */
    status = device_register(&client->dev);
    ......
    return client;

}
int device_register(struct device *dev)
{
    device_initialize(dev);
    /* 添加设备 */
    return device_add(dev);
}
int device_add(struct device *dev)
{
    ......
    /* 将设备添加是bus上 */
    error = bus_add_device(dev);

    ......
  /* 探测bus上是否有与设备匹配的驱动 */
    bus_probe_device(dev);
    ......
}
int bus_add_device(struct device *dev)
{
    struct bus_type *bus = bus_get(dev->bus);

    ......

    if (bus) {
      /* 将设备添加到bus的klist_devices链表欧尚,该成员在注册驱动时会使用到。用于匹配 */
        klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
    }
    return 0;

}
void bus_probe_device(struct device *dev)
{
    struct bus_type *bus = dev->bus;
    struct subsys_interface *sif;
    ......
    if (bus->p->drivers_autoprobe)
    /* device_initial_probe直接调用__device_attach */
        device_initial_probe(dev);
    ......
}
static int __device_attach(struct device *dev, bool allow_async)
{
    ......
    /* 对bus上的每个设备都调用__device_attach_driver */
    ret = bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
    ......
}
static int __device_attach_driver(struct device_driver *drv, void *_data)
{

    ......
    /* 匹配设备与驱动 */
    ret = driver_match_device(drv, dev);
    ......
    /* 匹配成功则调用probe */
    return driver_probe_device(drv, dev);
}

2.4 驱动接口及步骤

2.4.1 I2C框架接口

  1. 增加/删除I2C适配器
  • i2c_add_adapter
  • i2c_del_adapter
  1. 增加/删除I2C驱动
  • i2c_add_driver
  • i2c_del_driver
  1. I2C传输、发送和接收
  • i2c_transfer(adap, msgs, num):用于 I2C适配器设备 之间的 一组 消息交互。注意其传入的实例为 I2C适配器
  • i2c_master_recv:用于 主机接收,内部调用 i2c_transfer。注意其传入的实例为 I2C设备(i2c_client)
  • i2c_master_send(client, buf, count):用于 主机发送,内部调用 i2c_transfer。注意其传入的实例为 I2C设备(i2c_client)

2.4.2 I2C步骤

I2C驱动 的移植和编写主要有 2部分

  • I2C适配器:完成 I2C适配器 的硬件驱动

    1. 探测及初始化 I2C适配器,如 申请寄存器地址申请中断
    2. 驱动 I2C适配器 产生信号及 中断处理
    3. 提供 I2C适配器algorithm(即通信方法)
    4. 使用 i2c_add_adapter 添加 I2C适配器
  • I2C驱动:完成 I2C设备外设 的硬件驱动

    1. 实现 i2c_driver 的接口,如 probe、remove、suspend、resume
    2. 填充 i2c_device_id表 并赋值给 i2c_driver
    3. 实现 I2C设备 的具体方法,比如 read、write、ioctl

三、参考链接

Linux I2C驱动框架
Linux i2c 设备驱动程序框架详解
I2C中的重复起始条件到底是什么意思

你可能感兴趣的:(linux驱动之i2c框架)