Linux I2C相对来说稍微复杂了些,当时听课时,理解了大半,后来因为没有及时复习,导致渐渐遗忘。
现在重新拾起,写一份详细的笔记,以备后期复习,也供大家学习之用。
I2C我就不解释了。有兴趣看笔记的自然也知道什么是I2C。
先来个实用的,说说一般我们是怎么利用I2C框架操作I2C驱动设备的。
如果我们需要在产品中增加一个保存一些少量数据的功能,那么可以在硬件上增加一个E2prom(at24c08)芯片,
该芯片是用I2C协议通信的,我们就以这个例子来说说。
设备、资源准备(完成I2C硬件设备的添加):
1、首先将24c08焊接到S5PV210的I2C0控制器引脚上,调整其A0\A1\A2引脚的高低电平 来设置其器件地址(0xA0)。
2、将24c08的器件地址以及设备名加入到 __i2c_board_list 链表
1、在Mach-smdkv210.c文件中的 i2c_board_info smdkv210_i2c_devs0 增加:
static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
...
{ I2C_BOARD_INFO("24c08", 0x50), }, /* Samsung S524AD0XD1 */
...
...
};
(注意,这里的24c08的真实器件地址是0xA0,因为还有个读写位,所以需要将0xA0右移动一位 0xA0>>1 = 0x50 )
在使用I2C子系统的时候,需要将 i2c-dev.c i2c-core.c i2c-s3c2410.c 编译进内核( make zImage/uImage)
1、首先看源码所在目录下的Makefile ~/linux_source/linux-3.0.8/drivers/i2c/Makefile
1、i2c-dev.c 驱动层 i2c-core.c 核心层
2、i2c-s3c2410.c 总线 ~/linux_source/linux-3.0.8/drivers/i2c/busses/Makefile
2、针对Makefile中的 CONFIG_I2C->I2C、CONFIG_I2C_SMBUS 在Kconfig中寻找
3、在Kconfig中找到 I2C_support、I2C_device interface,然后配置内核 make menuconfig
主界面 ---> Device Drivers ---> I2C_support ---> I2C_device_interface
应用层利用Linux I2C框架来读写I2C设备有两种方式(大概流程-后面会有详细实现代码):
1、利用I2C适配器设备文件的方式(需要指定I2C设备的器件地址)
直接可以编写应用层代码即可。
步骤:(以E2prom为例子,它挂在第0个I2C控制器)
1、打开I2C设备I2C控制器0
fd = open("/dev/i2c-0", O_RDWR);
2、发送E2prom的I2C器件地址 addr = 0x50
ioctl(fd, I2C_SLAVE, addr)
3、读数据 需要先写入要读取的起始地址(E2prom里的地址)
write(fd, &chip_addr, 1);
read(fd, rbuf, 8);
4、写数据 需要先写入要写入的起始地址(E2prom里的地址)
wbuf[0] = 0x0; // 指定要从 0 地址开始写
strncpy(tmp+1, data, strlen(data));
write(fd, wbuf, 9);
5、关闭
close(fd);
2、自己写驱动,为指定I2C设备创建一个设备文件供应用层直接打开操作(只需要提供设备名即可)
需要自己写一个ko驱动,然后应用层打开由这个ko文件创建的设备文件,进行操作
步骤:(以E2prom为例子,它的设备名为 24c08)
1、编写ko驱动流程
1、构建struct i2c_driver,实现 .probe .remove 成员
2、实现.probe()、.remove() 函数
1、获取Client
2、申请设备号、关联操作方法(即 fops)
3、创建设备类、设备节点文件(如 e2prom) 供上层操作
4、实现fops各函数,如read、write 等。。。
3、i2c_add_driver(&e2prom_drv);
当注册I2C驱动后,i2c核心层会将此驱动的id_table与设备链表中的I2C_Client的
名字进行匹配,匹配成功后会执行.probe()函数
2、编写上层应用
fd =open("/dev/e2prom",O_RDWR);
读写方式与上面一样,只是不需要再利用ioctl发送器件地址。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
好了,用是知道怎么用,那么实现原理是什么呢。这里涉及到平台驱动的知识,因为I2C需要通过平台驱动来初始化I2C控制器,
不懂的童鞋请先搞懂平台驱动。
首先要了解I2C驱动的工作的大致流程,抓住这个大致流程后,再具体分析代码时就会轻松很多:
第一步,就是我们准备的资源了:
1、所有I2C设备的板级信息,如E2prom的设备名和器件地址
~/linux_source/linux-3.0.8/arch/arm/mach-s5pv210/mach-smdkv210.c
static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
{ I2C_BOARD_INFO("24c08", 0x50), }, /* Samsung S524AD0XD1 */
{ I2C_BOARD_INFO("wm8580", 0x1b), },
};
static struct i2c_board_info smdkv210_i2c_devs1[] __initdata = {};
static struct i2c_board_info smdkv210_i2c_devs2[] __initdata = {};
(这里的I2C设备资源会在mach-smdkv210.c 里面被加入了 __i2c_board_list 链表)
2、每个I2C控制器的平台设备资源,主要是控制器的硬件资源(如相关寄存器、中断)(S5PV210 平台有三个I2C控制器)
~/linux_source/linux-3.0.8/arch/arm/plat-samsung/dev-i2c0.c -> struct platform_device s3c_device_i2c0 = {}
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C_PA_IIC,
.end = S3C_PA_IIC + SZ_4K - 1,
.flags = IORESOURCE_MEM, // 内存资源 即寄存器地址
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ, // 中断资源
},