Quantcast
Channel: Sam的技术Blog
Viewing all articles
Browse latest Browse all 158

Linux系统IIC驱动程序  基础知识

$
0
0

作者:Sam (甄峰)  sam_code@hotmail.com

 

0. Linux I2C驱动体系概述:

Linux I2C驱动体系结构由以下三部分组成:

A:  I2C Core。  B:I2C总线驱动。  C:I2C设备驱动。



I2C核心是I2C总线驱动和I2C设备驱动的中间枢纽,它以通用的、与平台无关的接口实现了I2C中设备与适配器的沟通

I2C总线驱动填充i2c_adapteri2c_algorithm结构体。

I2C设备驱动填充i2c_driveri2c_client结构体。

 

很显然,I2C Core是Linux Kernel提供好的用来支持的模块。而根据I2C总线和I2C设备的区别,I2C Driver也分两个层次。

 

I2C 总线层驱动: 根据核心板的芯片手册,编写总线层驱动。例如 使用Nvidia K1开发板手册中的I2C总线文档,实现总线层驱动。总线层主要向内核注册一个Adapter,并填充Adapter支持的类型和方法。

I2C 设备层驱动:  I2C Driver设备层,主要针对不同的I2C硬件设备编写驱动,并为用户提供接口。

I2C子系统下设备驱动有两种模式:

A: 用户模式驱动,依赖于I2C子系统中的i2c-dev.

B: 普通Device Driver。

 

 

1. Linux I2C驱动程序目录结构

1.1:I2C Core:

kernel/drivers/i2c

i2c-boardinfo.c  i2c-core.c  i2c-dev.c  i2c-mux.c  i2c-slave.c  i2c-smbus.c  i2c-stub.c

 

i2c-core.c

可以看到,模块自我介绍为:I2C-Bus main module

提供(EXPORT_SYMBOL)了很多I2C核心功能。

如:

i2c_new_device() ,i2c_unregister_device(): 增加,删除i2c device---struct i2c_client  

i2c_add_adapter(). i2c_del_adapter(): 增加,删除 i2c adapter--struct i2c_adapter

i2c_register_driver(), i2c_del_driver(): 增加,删除i2c driver.

i2c_master_send()

同时,它注册了一个 bus_register(&i2c_bus_type);

这里有有一个函数:i2c_device_match(). 它来判断哪个设备驱动和I2C设备是匹配的。

它调用: of_driver_match_device(),和i2c_match_id().

 

 

i2c-dev.c:

I2C /dev entries driver

实现了 I2C适配器设备文件功能。

/dev/i2c-0,  /dev/i2c-1, /dev/i2c-n

创建了字符设备驱动--register_chrdev()

创建了/dev/i2c-n  node,device_create()

提供了read, write...等功能。

这样,用户就可以在应用程序层通过读写/dev/i2c-n 来操作I2C设备。

按照Sam当前理解:每个node(/dev/i2c-0..... /dev/i2c-n)对应一个I2C总线。每个总线可以挂载多个I2C设备。那么打开node,就应该可以和挂载在总线上的设备通讯了。

 

 

 1.2:I2C总线驱动:

I2C总线驱动和当前目标开发板有关,例如:Sam当前拿的是Nvidia K1开发板。则开发I2C总线驱动,需要详细读取K1 IIC部分文档。 幸好这部分在出厂前会由厂商搞定,(BSP部分)。我们就姑且看之吧。

总线驱动位于kernel/drivers/i2c/busses/目录。这里包含很多常用的I2C控制器驱动

 

kernel/drivers/i2c/busses/i2c-tegra.c -----nVidia Tegra2 I2C Bus Controller driver

它首先注册了一个platform drivert : platform_driver_register()

 

当系统发现匹配的I2C设备时,会调用:tegra_i2c_probe(), 这里初始化Tegra I2C总线,设置了clk的频率, 配置了中断处理程序,并调用i2c_add_numbered_adapter() 增加一个adpter。

 

Adapter是I2C总线驱动的关键,那Adapter到底是什么语义的?

Sam当前理解是:Adapter对应物理上存在的一个I2C总线。一个总线上能够挂载多个I2C设备。总线控制器控制数据传输。

 

struct i2c_adapter {
 struct module *owner;
 unsigned int class;   
 const struct i2c_algorithm *algo;
 void *algo_data;

 
 struct rt_mutex bus_lock;

 int timeout;   
 int retries;
 struct device dev;  

 bool cancel_xfer_on_shutdown;
 bool atomic_xfer_only;

 int nr;
 char name[48];
 struct completion dev_released;

 struct mutex userspace_clients_lock;
 struct list_head userspace_clients;

 struct i2c_bus_recovery_info *bus_recovery_info;
};

 

这里给I2C总线起了名字:

strlcpy(i2c_dev->adapter.name, "Tegra I2C adapter",sizeof(i2c_dev->adapter.name));

叫:Tegra I2C adapter

cat /sys/class/i2c-dev/i2c-0/name

Tegra I2C adapter

 

 

这里还有一个概念需要注意:

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 *);
};

master_xfer:I2C总线的通讯协议函数。

smbus_xfer: SMBus总线的通讯协议函数。 

 

i2c_algorithm:作用就是这个I2C Bus的数据传输。如果没有它,这个I2C Bus不能传送数据。

数据格式为: struct i2c_msg。

这部分实现和I2C总线控制器设计有关,它按照芯片手册中的说明,操作寄存器控制数据发送和接收。方便I2C设备驱动使用。

 

这里还有个知识点:

Linux I2C GPIO Driver. 当没有I2C总线控制器时,可以使用GPIO来模拟I2C总线,使用2个GPIO分别模拟SDA, SCL, 并使用驱动,让GPIO模拟I2C总线时序,完成Linux 与I2C设备的通讯过程。

与有I2C控制芯片的驱动不同的是传输算法的实现,GPIO模拟I2C驱动,不再是设置寄存器控制数据发送和接收。而是用程序来控制。代码在drivers/i2c/busses/i2c-gpio.c,时序控制:drivers/i2c/algos/i2c-algo-bit.c

理论上,如果没有I2C控制器,只需要修改总线驱动中master_xfer为GPIO模式即可。

至于一些具体设置,如哪个GPIO对应SDA, 哪个GPIO对应SCL等信息,则在板级代码中写入:

static struct i2c_gpio_platform_data pdata = {
 .sda_pin  = AT91_PIN_PA25,
 .sda_is_open_drain = 1,
 .scl_pin  = AT91_PIN_PA26,
 .scl_is_open_drain = 1,
 .udelay   = 2,  
};

(请注意:Linux Device Tree使用后,这些板级代码可能被合并到DTS文件内了)

 

 

 

如需要使用Linux I2C GPIO Driver, 则需要make menuconfig时加入此modules.

 

 

 

 

 

 

1.3:I2C设备层驱动:

1.3.1: I2C接口硬件设备驱动程序不同的实现方式:

从广义上说,I2C设备层驱动是指为各种I2C接口的硬件提供驱动。在不同纬度上,有不同的实现方式:

A: 用户模式驱动程序。

B: 普通驱动程序。

 

1.3.2:用户模式驱动程序:

依赖于i2c子系统中的/dev/i2c-0,  /dev/i2c-1, /dev/i2c-n

打开/dev/i2c-n,这个node, 相当于打开了一个物理存在的I2C适配器。按Sam理解,也就是得到了一个I2C Bus。 后面向不同I2C数据写入读出数据,就是向某个挂载在这个I2C总线上的设备写入和读出。

很难说这就是Driver。但却是实现了向I2C设备发送和接收数据。  

 

以后再填充内容。

 

1.3.3:普通驱动程序:

不利用i2c-dev.c, 直接写I2C设备驱动

 

在比较老的Linux Kernel中,I2C设备Driver也可以使用两种方式。Adapter方式和Probe方式。Kernel推荐使用Probe方式。且在某个Kernel版本后,Adapter方式不再支持了(存疑)

 

1.3.3.1: Adapter方式:

这个方式的思路是,创建一个i2c_drveir结构体并注册给kernel:

A: I2C Device Driver开发者填充i2c_driver结构体。

最重要的两个成员是函数指针:attach_adapter()--调用时机较复杂,后面再说, detach_client()--脱离适配器时被调用。

B: 注册这个 i2c_driver:

i2c_add_driver(&inv_mod_driver);

如此,整个驱动就完整了。但如何运作的呢?

前面谈到过,I2C 总线驱动会创建几个I2C总线Adapter. 每个Adapter代表一个实际存在的I2C Bus。

当I2C 设备驱动使用i2c_add_drvier()注册了一个设备驱动时,则会依次使用每个Adapter来尝试连接我们的I2C Device。 这个过程,就是调用attach_adapter(struct i2c_adapter* adapter).

 

attach_adapter中调用:

i2c_probe(adapter, &addr_data, xxxx_detect);
               

 其中:addr_data很关键:

static unsigned short normal_i2c[] = { xx,xx,xx,xx,xx,xx,xx,xx

I2C_CLIENT_END };

地址必须是I2C芯片的地址。否则无法探测到。

 

xxxxx_detect(). 它是在探测到设备后,会被调用的函数。

这个过程很关键,填充i2c_client,创建node均在此处实现。

xxxxx_detect(struct i2c_adapter *adapter, int address, int kind)

可以

A:创建i2c_client, 填充其内容。利用i2c_attach_client()注册i2c_client。

i2c_client--代表的就是一个I2C Device。

B:创建字符设备node.

C:  实现字符设备的read,write,ioctl等。

 

其它部分如detach()等,就是反向操作了。

 

 

1.3.3.2:Probe方式I2C Device Driver

这个方式也正是被推荐的方式。思路如下:

A:I2C Device Driver开发者填充i2c_driver结构体。与Adapter方式不同的主要是i2c_driver中需要被填充的内容。

比较重要的是probe(), remove(), id_table.

 

 

 static struct i2c_driver inv_mod_driver = {
 .class = I2C_CLASS_HWMON,
 .probe  = nvi_probe,
 .remove  = nvi_remove,
 .id_table = nvi_mpu_id,

 .driver = {
  .owner = THIS_MODULE,
  .name = "inv_dev",
  .of_match_table = of_match_ptr(nvi_mpu_of_match),
#ifdef CONFIG_PM
  .pm = &nvi_pm_ops,
#endif
 },
 .address_list = normal_i2c,
 .shutdown = nvi_shutdown,
};

 

 B:注册Driver:

i2c_add_driver(&inv_mod_driver);

具体创建字符设备Device Node等工作。都在probe中做了。

 

现在具体分析:

我们知道,i2c_driver对应的是一个i2C driver. 它的probe()函数,就是当有符合条件的I2C Device出现在I2C Bus上时被Kernel调用的。但Kernel如何知道哪个I2C Device复合Driver条件呢?和USB设备一样,使用id_table.

 

static struct i2c_device_id nvi_mpu_id[] = {
 {"itg3500", INV_ITG3500},
 {"mpu3050", INV_MPU3050},
 {"mpu6050", INV_MPU6050},
 {"mpu9150", INV_MPU9150},
 {"mpu6500", INV_MPU6500},
 {"mpu9250", INV_MPU9250},
 {"mpu6xxx", INV_MPU6XXX},
 {"mpu9350", INV_MPU9350},
 {"mpu6515", INV_MPU6515},
 {}
};

系统会调用:i2c_device_match()----i2c_match_id()来判断咱们注册的I2C Driver和Bus 上链接的Device是否匹配。如果匹配,则调用probe.

 

但Sam在想,USB 设备是因为其可以存储一小块数据,这块数据和 .id_table的内容比对,看是否匹配。但I2C设备理应没有这块区域啊。那i2c_driver中存储的.id_table的数据,和谁比对呢?

Sam认为, kernel的平台代码(arch/arm/mach-xxxx.   在NV-Tegra平台是:arch/arm/mach-tegra/board-ardbeg-sensors.c)中,会针对将要支持的Device,构建各自的i2c_board_info。

 

类似:

static struct i2c_board_info __initdata inv_mpu9250_i2c0_board_info[] = {
 {
  I2C_BOARD_INFO(MPU_GYRO_NAME, MPU_GYRO_ADDR),
  .platform_data = &mpu9250_gyro_data,
 },


 {
  I2C_BOARD_INFO(MPU_BMP_NAME, MPU_BMP_ADDR),
  .platform_data = &mpu_bmp_pdata,
 },


 {
  I2C_BOARD_INFO(MPU_COMPASS_NAME, MPU_COMPASS_ADDR),
  .platform_data = &mpu_compass_data,
 },
};

 

然后调用 i2c_register_board_info()。

 

Probe分析:

维护字符设备驱动程序。使用户层程序可以访问Device。

 

 

 

 附录1:



这个图标上,可以很清晰的看到,I2C总线驱动部分,I2C设备驱动部分,各自有两个概念:

i2c_adapter, i2c_algorithm. i2c_client, i2c_driver. 咱们总结一下他们各自的用途和语义。

 

Sam认为;

i2c_adapter: 对应的就是一个I2C总线,物理上存在的I2C Bus。每个I2C总线上,可以挂载多个I2C设备。

i2c_algorithm:每个I2C总线,都要包含一个i2c_algorithm,它相当于传输协议。没有它,I2C总线无法传输数据。

i2c_driver: 对应一个I2C驱动。驱动程序可以与I2C设备匹配,帮助用户访问I2C设备。

i2c_client: 对应一个物理存在的I2C 设备。

 

 

 

 

 

 


 

Viewing all articles
Browse latest Browse all 158

Trending Articles