Linux I2C framework(2)_I2C provider

作者:wowo 发布于:2016-2-26 22:49 分类:通信类协议

1. 前言

本文从Provider的角度,介绍怎么借助I2C framework管理I2C相关的SOC资源。换句话说,就是怎么编写I2C controller驱动。

2. 关键数据结构和API介绍

2.1 I2C adapter

由“Linux I2C framework(1)_概述”可知,I2C framework使用struct i2c_adapter抽象I2C控制器,具体如下:

  1: /* include/linux/i2c.h */
  2: 
  3: /*
  4:  * i2c_adapter is the structure used to identify a physical i2c bus along
  5:  * with the access algorithms necessary to access it.
  6:  */
  7: struct i2c_adapter {
  8:         struct module *owner;
  9:         unsigned int class;               /* classes to allow probing for */
 10:         const struct i2c_algorithm *algo; /* the algorithm to access the bus */
 11:         void *algo_data;
 12: 
 13:         /* data fields that are valid for all devices   */
 14:         struct rt_mutex bus_lock;
 15: 
 16:         int timeout;                    /* in jiffies */
 17:         int retries;
 18:         struct device dev;              /* the adapter device */
 19: 
 20:         int nr;
 21:         char name[48];
 22:         struct completion dev_released;
 23: 
 24:         struct mutex userspace_clients_lock;
 25:         struct list_head userspace_clients;
 26: 
 27:         struct i2c_bus_recovery_info *bus_recovery_info;
 28: };

该数据结构比较简单,只需要着重关注如下事项:

1)由它的注释可知,struct i2c_adapter是用于标识物理的I2C总线(physical i2c bus),且该总线需要有一套用于访问slave设备的算法(access algorithm)。

2)所谓的access algorithm,就是通过I2C总线发送和接收数据的方法,它保存在algo指针(struct i2c_algorithm,具体可参考后续2.2小节的描述)中。

3)基于I2C传输的特性,不一定每一次总线访问(发送或者接收数据)都会成功,在传输失败的时候,可以选择重试。重试的逻辑由I2C core自行完成,但I2C controller driver需要设定重试的次数,这就是retries字段的意义。另外,有些consumer对结果的返回是有时间要求的,因此不能无节制的重试,timeout字段(单位为jiffies)在retries基础上,增加了时间限制,超过这个时间,就不能重试了。

4)nr,该I2C bus的ID,会体现在sysfs中(/sys/bus/i2c/devices/i2c-n中的‘n’),可由I2C controller driver在注册adapter时指定,或者通过DTS解析(后面会介绍),或者自动分配。

5)class,该I2C bus支持哪些类型的slave device,只有匹配的slave device才能和bus绑定。具体的类型包括(可参考include/linux/i2c.h中的定义和注释):
    I2C_CLASS_HWMON,硬件监控类,如lm_sensors等;
    I2C_CLASS_DDC,DDC是数字显示通道(Digital Display Channel)的意思, 通常用于显示设备信息的获取;
    I2C_CLASS_SPD,存储类的模组;
    I2C_CLASS_DEPRECATED,不再使用的class。

6)dev,我们在“Linux I2C framework(1)_概述”的第2章提到过,I2C framework将I2C adapter当做了I2C bus中的一类特殊的设备,因此dev变量是它在设备模型中的体现。

2.2 i2c algorithm

由2.1的描述可知,struct i2c_algorithm抽象了通过I2C总线发送和接收数据的方法,其定义如下:

  1: /* include/linux/i2c.h */
  2: 
  3: /**
  4:  * struct i2c_algorithm - represent I2C transfer method
  5:  * @master_xfer: Issue a set of i2c transactions to the given I2C adapter
  6:  *   defined by the msgs array, with num messages available to transfer via
  7:  *   the adapter specified by adap.
  8:  * @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this
  9:  *   is not present, then the bus layer will try and convert the SMBus calls
 10:  *   into I2C transfers instead.
 11:  * @functionality: Return the flags that this algorithm/adapter pair supports
 12:  *   from the I2C_FUNC_* flags.
 13:  *
 14:  * The following structs are for those who like to implement new bus drivers:
 15:  * i2c_algorithm is the interface to a class of hardware solutions which can
 16:  * be addressed using the same bus algorithms - i.e. bit-banging or the PCF85
 17:  * to name two of the most common.
 18:  *
 19:  * The return codes from the @master_xfer field should indicate the type of
 20:  * error code that occured during the transfer, as documented in the kernel
 21:  * Documentation file Documentation/i2c/fault-codes.
 22:  */
 23: struct i2c_algorithm {
 24:         /* If an adapter algorithm can't do I2C-level access, set master_xfer
 25:            to NULL. If an adapter algorithm can do SMBus access, set
 26:            smbus_xfer. If set to NULL, the SMBus protocol is simulated
 27:            using common I2C messages */
 28:         /* master_xfer should return the number of messages successfully
 29:            processed, or a negative value on error */
 30:         int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
 31:                            int num);
 32:         int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
 33:                            unsigned short flags, char read_write,
 34:                            u8 command, int size, union i2c_smbus_data *data);
 35: 
 36:         /* To determine what the adapter supports */
 37:         u32 (*functionality) (struct i2c_adapter *);
 38: };

1)functionality,通过一个bitmap,告诉调用者该I2C adapter支持的功能,包括(具体可参考include/uapi/linux/i2c.h中的定义和注释):

I2C_FUNC_I2C,支持传统的I2C功能;
I2C_FUNC_10BIT_ADDR,支持10bit地址;
I2C_FUNC_PROTOCOL_MANGLING,支持非标准的协议行为(具体请参考2.3小节的介绍);
I2C_FUNC_NOSTART,支持不需要发送START信号的I2C传输(具体请参考2.3小节的介绍);
I2C_FUNC_SMBUS_xxx,SMBUS相关的功能,不再详细介绍。

2)master_xfer,I2C协议有关的数据传输接口,输入参数是struct i2c_msg类型(可参考2.3小节的介绍)的数组(大小由num指定)。返回值是成功传输的msg的个数,如有错误返回负值。

3)smbus_xfer,SMBUS有关的数据传输接口,如果为NULL,I2C core会尝试使用master_xfer模拟。

2.3 i2c msg

由2.2的介绍可知,I2C传输(读或者写)以i2c msg为单位,该数据结构的定义如下:

  1: /* include/uapi/linux/i2c.h */
  2: 
  3: struct i2c_msg {
  4:         __u16 addr;     /* slave address                        */
  5:         __u16 flags;
  6: #define I2C_M_TEN               0x0010  /* this is a ten bit chip address */
  7: #define I2C_M_RD                0x0001  /* read data, from slave to master */
  8: #define I2C_M_STOP              0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */
  9: #define I2C_M_NOSTART           0x4000  /* if I2C_FUNC_NOSTART */
 10: #define I2C_M_REV_DIR_ADDR      0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
 11: #define I2C_M_IGNORE_NAK        0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
 12: #define I2C_M_NO_RD_ACK         0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
 13: #define I2C_M_RECV_LEN          0x0400  /* length will be first received byte */
 14:         __u16 len;              /* msg length                           */
 15:         __u8 *buf;              /* pointer to msg data                  */
 16: };

1)addr,I2C slave device的地址。

2)flags,数据传输可携带的flag,包括(具体可参考include/uapi/linux/i2c.h中的定义和注释):
     I2C_M_TEN,支持10-bit的slave地址;
     I2C_M_RD,此次传输是读操作;
     其它flag,下面再详细描述。

3)len,数据传输的长度,单位为byte。

4)buf,数据buf。

2.4 I2C传输有关的flags

正常情况下,如果I2C msg中的flags为0,adapter将按照标准I2C协议操作总线,进行传输操作,即(具体可参考Documentation/i2c/i2c-protocol):

写操作:

S Addr Wr [A] Data [A] Data [A] ... [A] Data [A] P

读操作:

S Addr Rd [A] [Data] A [Data] A ... A [Data] NA P

注1:对读操作来说,当master收到slave发送的NA(NACK)消息时,表明已经没有数据可读,应当停止读取(当然,有例外,下面会介绍)。

读写混合操作:

S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P

否则,可按照flags中指定的行为,进行非标的I2C传输(当然,adapter需要支持,具体可参考2.2小节中有关functionality的介绍),这些flag包括:

1)I2C_M_IGNORE_NAK

读操作的时候,忽略slave返回的NA,把它当做ACK信号,继续读取。还别说,那真有那比较贱的slave,比如电视(通过I2C读取EDID的时候)。

2)I2C_M_NO_RD_ACK

读操作的时候,忽略所有的NACK/ACK信号。(霸气,不过没用过!)

3)I2C_M_NOSTART

读写混合操作的情况下,假如要传输多个msg(以2个为例),如果第二个msg携带了该标志,则不再发送'S Addr Wr/Rd [A]'信号,即从

S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P

S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P

变为

S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P

S Addr Rd [A] [Data] NA  Data [A] P

奇奇怪怪的场景啊,用到的时候再仔细阅读kernel的document吧。

4)I2C_M_REV_DIR_ADDR,将读写flag翻转,即读的时候发Wr信号,写的时候发Rd信号。至于为什么这么用,只有天知道。

5)I2C_M_STOP,msg传输完成后forece STOP。不太明白现实意义,用过的同学帮忙科普一下。

6)I2C_M_RECV_LEN,SMBUS的一个flag,意义不明。

2.5 I2C adapter相关的API

I2C adapter定义好之后,要把它注册到kernel中去,相关的API如下:

  1: /* include/linux/i2c.h */
  2: 
  3: extern int i2c_add_adapter(struct i2c_adapter *);
  4: extern void i2c_del_adapter(struct i2c_adapter *);
  5: extern int i2c_add_numbered_adapter(struct i2c_adapter *);
  6: 
  7: static inline u32 i2c_get_functionality(struct i2c_adapter *adap)
  8: static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func)
  9: static inline int i2c_adapter_id(struct i2c_adapter *adap)
 10: 
 11: extern struct i2c_adapter *i2c_get_adapter(int nr);
 12: extern void i2c_put_adapter(struct i2c_adapter *adap);
 13: 
 14: /* must call put_device() when done with returned i2c_adapter device */
 15: extern struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node);

1)i2c_add_adapter和i2c_add_numbered_adapter是I2C adapter的注册接口,它们的区别是:i2c_add_adapter会自动分配adapter ID(adapter->nr,见2.1),i2c_add_numbered_adapter则可以指定ID(adapter->nr需要时有效值,否则会调用i2c_add_adapter自动分配)。

2)i2c_del_adapter将I2C adapter从内核中删除。

3)i2c_get_functionality获取指定adapter所支持的功能,i2c_check_functionality可用于检查指定adapter是否具备指定功能。

4)i2c_adapter_id可以获取指定adapter的ID。

5)i2c_get_adapter通过ID获得指定adapter的指针,由于该接口会尝试调用try_module_get增加模块的引用计数,因此使用完毕后,需要调用i2c_put_adapter将引用计数减去。

6)of_find_i2c_adapter_by_node,通过device的device_node查找相应的adapter结构,使用完后需要调用put_device将adapter->dev所在的模块引用计数减去。

3. 编写I2C controller驱动的步骤

了解了I2C adapter有关的数据结构和API之后,编写I2C控制器驱动就简单多了,主要步骤如下:

1)定义一个struct i2c_algorithm变量,并根据I2C controller的特性,实现其中的回调函数。

2)在DTS文件(一般都放到DTSI)中,定义I2C controller相关的DTS node,例如:

  1: /* arch/arm/boot/dts/am33xx.dtsi
  2: 
  3: i2c0: i2c@44e0b000 {
  4:         compatible = "ti,omap4-i2c";
  5:         #address-cells = <1>;
  6:         #size-cells = <0>;
  7:         ti,hwmods = "i2c1";
  8:         reg = <0x44e0b000 0x1000>;
  9:         interrupts = <70>;
 10:         status = "disabled";
 11: };
 12: 
 13: i2c1: i2c@4802a000 {
 14:         compatible = "ti,omap4-i2c";
 15:         #address-cells = <1>;
 16:         #size-cells = <0>;
 17:         ti,hwmods = "i2c2";
 18:         reg = <0x4802a000 0x1000>;
 19:         interrupts = <71>;
 20:         status = "disabled";
 21: };
 22: 
 23: ...

3)在drives/i2c/busses目录下,以i2c-xxx.c的命名方式,编写I2C controller的platform driver,并提供match id、probe、remove等接口。

4)在platform driver的probe接口中,分配一个adapter结构,并进行必要的初始化操作后,调用i2c_add_adapter或者i2c_add_numbered_adapter接口,将其注册到kernel中即可。

4. i2c_add_adapter流程分析

最后,我们简单的看一下i2c adapter的add流程,主要关注三点:adapter ID的分配;设备模型有关的内容;I2C slave device的创建和注册。

4.1 adapter ID的分配

由第2章的描述可知,I2C adapter ID(adapter->nr)可通过两种方法分配,一种是driver直接赋值,并经过下面的函数调用注册adapter(具体可参考drivers/i2c/i2c-core.c):

i2c_add_numbered_adapter--->__i2c_add_numbered_adapter--->i2c_register_adapter

另一种方法,是调用i2c_add_adapter动态分配,如下:

  1: /* drivers/i2c/i2c-core.c */
  2: 
  3: int i2c_add_adapter(struct i2c_adapter *adapter)
  4: {
  5:         struct device *dev = &adapter->dev;
  6:         int id;
  7: 
  8:         if (dev->of_node) {
  9:                 id = of_alias_get_id(dev->of_node, "i2c");
 10:                 if (id >= 0) {
 11:                         adapter->nr = id;
 12:                         return __i2c_add_numbered_adapter(adapter);
 13:                 }
 14:         }
 15: 
 16:         mutex_lock(&core_lock);
 17:         id = idr_alloc(&i2c_adapter_idr, adapter,
 18:                        __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
 19:         mutex_unlock(&core_lock);
 20:         if (id < 0)
 21:                 return id;
 22: 
 23:         adapter->nr = id;
 24: 
 25:         return i2c_register_adapter(adapter);
 26: }

动态分配也有两种手段:

8~13行,通过of_alias_get_id获取。该方法会通过DTS中的alias解析指定I2C adapter的ID,例如:

aliases {
        i2c0 = &i2c0;
};

i2c0: i2c@44e0b000 {
        compatible = "ti,omap4-i2c";
        …
};

/* arch/arm/boot/dts/am33xx.dtsi */

相关讨论可参考“http://www.wowotech.net/linux_kenrel/dt_basic_concept.html”留言中的讨论。

16~19,通过idr_alloc分配(idr也是比较好玩的一个功能,有空分析一下,这里不再细述了)。

最后,在较新版本的kernel中,adapter ID的作用仅仅体现在sysfs中,即:

/sys/bus/i2c/devices/i2c-n/
---->/sys/devices/xxxxxxxx.i2c/i2c-n

中的‘n’。

4.2 设备模型有关的流程

adapter ID分配完后,或执行i2c_register_adapter,该接口会在“/sys/devices/xxxxxxxx.i2c/”中创建该adapter的目录(/sys/devices/xxxxxxxx.i2c/i2c-n),如下:

dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);

由此可知,I2C adapter被挂到i2c总线(i2c_bus_type)上了,同时,通过“device_register--->device_add--->bus_add_device--->sysfs_create_link”的调用,在/sys/bus/i2c/devices/中创建对应的符号链接(/sys/bus/i2c/devices/i2c-n/)。支持,I2C adapter有关的设备模型结构,在sysfs中创建完毕。

注2:adapter的目录创建的位置,取决于adapter->dev的parent的设备,通常是I2C控制器所对应的platform设备,如:

static int
omap_i2c_probe(struct platform_device *pdev)
{
    …
    adap->dev.parent = &pdev->dev;
    …
}

4.3 I2C slave device的创建和注册

在DTS的支持下,I2C adapter注册的时候,会为它下面所有的slave device创建struct i2c_client结构,并注册到I2C bus上,调用流程是:

i2c_register_adapter--->of_i2c_register_devices--->i2c_new_device

由于牵涉到I2C consumer的DTS结构,该部分内容下一篇文章再介绍。

 

原创文章,转发请注明出处。蜗窝科技www.wowotech.net

标签: Linux driver I2C

评论:

小亮
2017-09-26 17:00
struct i2c_bus_recovery_info,新加入的这种机制的目的是什么呢?
wowo
2017-09-27 10:17
@小亮:这是为了实现“可以将i2c bus从异常状态恢复的机制”,具体可参考i2c spec“bus clear”有关的章节(这里有一个nxp的版本:https://www.nxp.com/docs/en/user-guide/UM10204.pdf)

3.1.16 Bus clear
In the unlikely event where the clock (SCL) is stuck LOW, the preferential procedure is to
reset the bus using the HW reset signal if your I2C devices have HW reset inputs. If the
I
2C devices do not have HW reset inputs, cycle power to the devices to activate the
mandatory internal Power-On Reset (POR) circuit.
If the data line (SDA) is stuck LOW, the master should send nine clock pulses. The device
that held the bus LOW should release it sometime within those nine clocks. If not, then
use the HW reset or cycle power to clear the bus
小亮
2017-09-27 11:28
@wowo:@wowo 明白了,谢谢大神
hangkeke052
2016-10-19 17:08
Hi wowo大神:
  我现在使用了一款NXP的uart转i2c controller 的芯片,我现在大概有一个思路,然后想请你参考一下
1)在device tree 中我应该把这个设备描述成为一个i2c controller
2)nxp_i2c_probe  adapter_hw_init 中首先将这个设备识别成为dev下的普通串口设备,并且设置比特率等串口设备信息,还有芯片的 power reset动作
3)通过串口设备的读和写 实现 i2c_algorithm 操作
4)i2c_add_adapter 将i2c adapter 注册到platform bus 上 。
wowo
2016-10-19 21:45
@hangkeke052:我觉得的你这个场景很有意思,要怎么做,得先问一个问题:你打算把这个串口实现为一个标准的TTY设备并通过用户空间访问它吗?
如果不是,就简单了,i2c driver该怎么做就怎么做,区别就是,平常的i2c driver通过读写i2c controller的寄存器,实现i2c的收发,而此处的i2c driver,则是通过读写UART controller的寄存器,实现i2c的收发。你在这个driver中,将一切差异屏蔽掉就okay了。
如果是,就有点麻烦,你要先实现标准的uart driver,然后想办法利用uart的API实现i2c driver。此时的dts拓扑结构应该是UART driver---->I2C driver,为从属关系。
hangkeke052
2016-10-21 15:09
@wowo:"平常的i2c driver通过读写i2c controller的寄存器,实现i2c的收发,而此处的i2c driver,则是通过读写UART controller的寄存器,实现i2c的收发"
这一句不是很理解,其中的i2c driver 指的是device 的i2c_driver 这个结构体还是指的是platform driver ? https://community.nxp.com/servlet/JiveServlet/showImage/2-842429-166109/pastedImage_1.png 这个是我画的一个草图,对i2c子设备的读写操做只能通过uart向SC18IM700发送特定format 的消息。而发送消息则是open操作 /dev/tty*,那应该是第二种方式?
hangkeke052
2016-10-21 15:11
@wowo:"平常的i2c driver通过读写i2c controller的寄存器,实现i2c的收发,而此处的i2c driver,则是通过读写UART controller的寄存器,实现i2c的收发"
这一句不是很理解,其中的i2c driver 指的是device 的i2c_driver 这个结构体还是指的是platform driver ? https://community.nxp.com/servlet/JiveServlet/showImage/2-842429-166109/pastedImage_1.png 这个是我画的一个草图,对i2c子设备的读写操做只能通过uart向SC18IM700发送特定format 的消息。而发送消息则是open操作 /dev/tty*,那应该是第二种方式?
wowo
2016-10-21 15:59
@hangkeke052:我只是觉得,这个UART只作为i2c转换用的话,没必要绕道到用户空间。
其实kernel有一个子系统,是支持这种需求的:serio,类似serial子系统(可以理解为kernel态的串口驱动吧)。
并且kernel中也有类似的例子:
drivers/i2c/busses/i2c-taos-evm.c
如果真的要标准,而且不怕麻烦的话,就照着这个做就行了。
dy1224
2016-04-15 14:17
希望博主可以发布一下I2C出错之后的信息解析
wowo
2016-04-15 22:43
@dy1224:多谢建议,后面会考虑添加。
buhui
2016-03-04 16:22
忽然发现蜗握科技的文章最能提高我的技术水平,相反,呆在某些科技型公司,并不能提高技术,提高的都是为了老板资本家赚钱的能力。
郭健
2016-03-04 17:36
@buhui:这是蜗窝科技的荣幸
众宏健身器材
2016-02-27 09:46
博主的内容更新很快哈

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog