Linux I2C体系

I2C总线仅仅使用 SCL 、 SDA 两根信号线就实现了设备之间的数据交互。

由于各种SOC都有自己的I2C总线,为了上层能统一接口,采用这种三层I2C架构.

I2C总线驱动主要实现了适用于特定I2C控制器的总线读写方法,并注册到Linux内核的I2C架构,I2C外设就可以通过I2C架构完成设备和总线的适配。但是总线驱动本身并不会进行任何的通讯,它只是提供通讯的实现,等待设备驱动来调用其函数。
I2C Core的管理正好屏蔽了I2C总线驱动的差异,使得I2C设备驱动可以忽略各种总线控制器的不同,不用考虑其如何与硬件设备通讯的细节

Linux的I2C构架分为三个部分:

1)I2C core框架

提供了核心数据结构的定义和相关接口函数,用来实现I2C适配器
驱动和设备驱动的注册、注销管理,以及I2C通信方法上层的、与具体适配器无关的代码,为系统中每个I2C总线增加相应的读写方法。

kernel抽象出I2C bus(/sys/bus/i2c),用于挂载和I2C adapter通过I2C总线连接的各个I2C slave device。
I2C Bus 并不是通讯上的总线,而是linux系统为了管理设备和驱动而虚拟出来的,在I2C Bus用来挂载后面将会使用到的I2C 适配器(adapter)和I2C设备(client)

2) I2C总线驱动 (i2c_adapter)

定义描述具体I2C总线适配器的i2c_adapter数据结构、实现在具体I2C适配器上的I2C总线通信方法,并由i2c_algorithm数据结 构进行描述。

封装了 struct device ,因此它是作为一个设备注册到内核中去的(是注册到i2c_bus_type里),此外非常重要的一个成员struct i2c_algorithm *algo ,这就是我们上边提到的 i2c 控制器收发数据的方法。

经过I2C总线驱动的的代码,可以为我们控制I2C产生开始位、停止位、读写周期以及从设备的读写、产生ACK等。

I2C总线驱动具体实现在/drivers/i2c目录下busses文件夹。

例如:
Linux I2C GPIO总线驱动为i2c_gpio.c.

全志 drivers/i2c/busses/i2c-sunxi.c

I2C总线算法在/drivers/i2c目录下algos文件夹。

例如:Linux I2C GPIO总线驱动算法实现在i2c_algo_bit.c.

针对不同类型的I2C控制器,实现对I2C总线访问的具体方法.(各种SOC不一样)

3) I2C 设备驱动(I2C client driver)

是对具体I2C硬件驱动的实现。I2C 设备驱动通过I2C适配器与CPU通信。

其中主要包含i2c_driver和i2c_client数据结构。
i2c_driver结构对应一套具体的驱动 方法,例如:probe、remove、suspend等,需要自己申明。

i2c_client数据结构由内核根据具体的设备注册信息自动生成,设备驱动 根据硬件具体情况填充。

I2C 设备驱动具体实现放在在/drivers/i2c目录下chips文件夹。

clipboard.png

重要的结构体

i2c_driver
 struct i2c_driver {
 unsigned int class;
 int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针
 int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针
 int (*probe)(struct i2c_client *, const struct i2c_device_id *);
 int (*remove)(struct i2c_client *);
 void (*shutdown)(struct i2c_client *);
 int (*suspend)(struct i2c_client *, pm_message_t mesg);
 int (*resume)(struct i2c_client *);
 void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void*arg);//命令列表
 struct device_driver driver;
13 const struct i2c_device_id *id_table;//该驱动所支持的设备ID表
 int (*detect)(struct i2c_client *, struct i2c_board_info *);
 const unsigned short *address_list;
 struct list_head clients;
 };
i2c_client
 struct i2c_client {
  unsigned short flags;//标志  
  unsigned short addr; //低7位为芯片地址  
 char name[I2C_NAME_SIZE];//设备名称
  struct i2c_adapter *adapter;//依附的i2c_adapter
  struct i2c_driver *driver;//依附的i2c_driver 
  struct device dev;//设备结构体  
  int irq;//设备所使用的结构体  
  struct list_head detected;//链表头
 };
 struct i2c_adapter {
  struct module *owner;//所属模块
  unsigned int id;//algorithm的类型,定义于i2c-id.h,
  unsigned int class;    
  const struct i2c_algorithm *algo; //总线通信方法结构体指针
  void *algo_data;//algorithm数据
  struct rt_mutex bus_lock;//控制并发访问的自旋锁
  int timeout;   
  int retries;//重试次数
  struct device dev; //适配器设备 
  int nr;
  char name[48];//适配器名称
  struct completion dev_released;//用于同步
  struct list_head userspace_clients;//client链表头
15 };
i2c_adapter与i2c_algorithm

i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
  i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。
  i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体

i2c_driver和i2c_client

i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client()
  i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述
  i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.

i2c_adapter和i2c_client

i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。

    struct list_head userspace_clients;//client链表头

重要的接口函数

注册一个驱动

【函数原型】:i2c_add_driver

#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

【功能描述】:注册一个I2C设备驱动。从代码可以看带i2c_add_driver()是一个宏,由函数i2c_register_driver()实现。
【参数说明】:driver,i2c_driver类型的指针,其中包含了I2C设备的名称、probe、detect等接口信息。

注册一个设备

【函数原型】:i2c_register_board_info

int i2c_register_board_info(int busnum, struct i2c_board_info const *info,
unsigned n)

【功能描述】:向某个I2C总线注册I2C设备信息,I2C子系统通过此接口保存I2C总线和I2C设备的适配关系。
busnum 通过总线号指定这个(些)设备属于哪个总线
info i2c设备的数组集合i2c_board_info格式

i2c_register_board_info具体实现: 相关信息放到链表中就算完事

int __init
i2c_register_board_info(int busnum,
    struct i2c_board_info const *info, unsigned len)
{
    int status;
 
    down_write(&__i2c_board_lock);  //i2c设备信息读写锁,锁写操作,其他只读
 
    /* dynamic bus numbers will be assigned after the last static one */
    if (busnum >= __i2c_first_dynamic_bus_num)  //与动态分配的总线号相关,动态分配的总线号应该是从已经现有最大总线号基础上+1的,这样能够保证动态分配出的总线号与板级总线号不会产生冲突
        __i2c_first_dynamic_bus_num = busnum + 1;
 
    for (status = 0; len; len--, info++) {  //处理info数组中每个成员
        struct i2c_devinfo    *devinfo;
 
        devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
        if (!devinfo) {
            pr_debug("i2c-core: can't register boardinfo!\n");
            status = -ENOMEM;
            break;
        }
 
        devinfo->busnum = busnum;  //组装总线号
        devinfo->board_info = *info;  //组装设备信息
        list_add_tail(&devinfo->list, &__i2c_board_list);  //加入到__i2c_board_list链表中(尾部)
    }
 
    up_write(&__i2c_board_lock);  //释放读锁,其他可读可写
 
    return status;
}

调用i2c_register_board_info的I2C设备注册过程应该在板级代码初始化期间,也就是arch_initcall前后的时间,
在I2C适配器驱动注册前完成。

如果在I2C适配器注册完后还想要添加I2C设备的话,就要通过新方式!(即i2c_new_device)

【函数原型】: i2c_new_device

i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

adap 此设备所依附的I2C适配器指针
info 此设备描述,i2c_board_info格式,bus_num成员是被忽略的

struct i2c_board_info info={
    .type = SENSOR=NAME,
    .addr = SENSOR_I2C_ADDR,
}
adapter = i2c_get_adapter(0);    //参数代表i2c num
client = i2c_new_device(adapter, &info);
数据传输

I2C设备驱动使用"struct i2c_msg"向I2C总线请求读写I/O。
一个i2c_msg中包含了一个I2C操作,通过调用i2c_transfer()接口触发I2C总线的数据收发。

【函数原型】:i2c_transfer

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

【功能描述】:完成I2C总线和I2C设备之间的一定数目的I2C message交互。

【函数原型】:i2c_master_recv

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

【功能描述】:通过封装i2c_transfer()完成一次I2c接收操作。

【函数原型】:i2c_master_send

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)

【功能描述】:通过封装i2c_transfer()完成一次I2c发送操作。

【函数原型】:i2c_smbus_read_byte

s32 i2c_smbus_read_byte(const struct i2c_client *client)

【功能描述】:从I2C总线读取一个字节。(内部是通过i2c_transfer()实现,以下几个接口同。)

【函数原型】:i2c_smbus_write_byte

s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)

【功能描述】:从I2C总线写入一个字节。

驱动代码例子

设备

1)BSP文件中静态声明一个I2C设备

static struct i2c_board_info i2c_devices[] __initdata = {  
  
    {I2C_BOARD_INFO("24c02", 0x50), },  
  
     {}  
  
}; 

2)向总线注册I2C设备信息:

i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));  
驱动

1)模块初始化时添加/撤销时删除i2c_driver
module_init(mma7660_init); //模块入口
module_exit(mma7660_exit); //模块出口

2)
init中和驱动框架相关的就一句话ret = i2c_add_driver(&mma7660_driver)而这一句话表示向I2C总线注册一个驱动,根据宏定义i2c_driver结构并完成其相应函数:

static struct i2c_driver my_i2c_driver = {  
  .driver = {  
      .name = "i2c_demo",  
      .owner = THIS_MODULE,  
    },  
    .probe = my_i2c_probe,  
    .remove = my_i2c_remove,  
    .id_table = my_ids,  
 };  

3)使用/dev entry 访问方法

register_chrdev(I2C_MAJOR,DEVICE_NAME,&i2c_fops);  
  
创建类class_create(THIS_MODULE, DEVICE_NAME);  
  
在/dev下创建设备节点  
  
device_create(my_dev_class, &client->dev,MKDEV(I2C_MAJOR, 0), NULL, DEVICE_NAME);  
I2c detect的方法

必须要定义address_list

static unsigned short s_Normal_I2c[] = {0x35, I2C_CLIENT_END};    // 芯片地址

static const struct i2c_device_id i2c_detect_id[] = {
    {LMX_I2C_DETECT_DEVICE_NAME, 0},
    {}
};
static struct i2c_driver i2c_detect_driver = {
    .class = I2C_CLASS_HWMON,
    .driver = {
            .owner = THIS_MODULE,
            .name = LMX_I2C_DETECT_DEVICE_NAME,
            },
    .detect = i2c_detect_Detect,   // 会往所有的 I2C 控制器上寻指定的 I2C 设备地址的 I2C 设备,若有 ACK 回应则会调用该函数
    .id_table = i2c_detect_id,
    .address_list = s_Normal_I2c,  // 地址列表
};

i2c_core.c 里面有一个 i2c_detect函数

if(driver->detect || !address_list)
return 0;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,322评论 5 465
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,288评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,227评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,015评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,936评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,534评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,995评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,616评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,907评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,923评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,741评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,525评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,016评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,141评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,453评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,054评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,249评论 2 339

推荐阅读更多精彩内容

  • 简介 I2C驱动由I2C核心,I2C总线驱动和I2C设备驱动组成.I2C核心是I2C总线驱动和I2C设备驱动的中间...
    傀儡世界阅读 1,082评论 0 1
  • Linux i2c system I2C总线是由PHILIPS公司开发的两线式串行总线,每个连接到总线的器件都可以...
    Creator_Ly阅读 1,800评论 0 8
  • 本文开启 linux 内核 V4L2 框架部分的学习之旅,本文仅先对 V4L2 的框架做一个综述性的概括介绍,然后...
    yellowmax阅读 7,545评论 0 13
  • 宋宝华 Barry Song 21cnbao@gmail.comhttp://blog.csdn.net/21cn...
    JosephDHF阅读 1,292评论 0 1
  • 1-50的平方3条地铁线路1副扑克牌顺序phpstorm快捷键vim快捷键thinkphp5文档 2017-08-...
    chaosii阅读 453评论 0 0