一、概述
I2C工作原理:I2C总线标准的两根传输线,SDA是数据线,Scl是时钟线,当SCL为高,SDA由高到低时,发送启动信息,发送9个脉冲,1-7是地址,8是读写控制位,9是ACK应答位,所以挂在I2C上的被控设备都接受所发送的信息,并把接收到的7位地址与自己的地址进行比较,如果相同ACK就会反馈应答。当SCL为低,SDA由低到高,则发送停止信号。
二、I2C驱动架构
Linux的I2C构架分为三个部分:
1)I2C core框架
提供了核心数据结构的定义和相关接口函数,用来实现I2C适配器
驱动和设备驱动的注册、注销管理,以及I2C通信方法上层的、与具体适配器无关的代码,为系统中每个I2C总线增加相应的读写方法。
2) I2C总线驱动
定义描述具体I2C总线适配器的i2c_adapter数据结构、实现在具体I2C适配器上的I2C总线通信方法,并由i2c_algorithm数据结构进行描述。 经过I2C总线驱动的的代码,可以为我们控制I2C产生开始位、停止位、读写周期以及从设备的读写、产生ACK等。
3) I2C 设备驱动
是对具体I2C硬件驱动的实现。I2C 设备驱动通过I2C适配器与CPU通信。其中主要包含i2c_driver和i2c_client数据结构,i2c_driver结构对应一套具体的驱动方法,例如:probe、remove、suspend等,需要自己申明。i2c_client数据结构由内核根据具体的设备注册信息自动生成,设备驱动根据硬件具体情况填充。
编写具体的I2C驱动时,工程师需要处理的主要工作如下:
1).提供I2C适配器的硬件驱动,探测,初始化I2C适配器(如申请I2C的I/O地址和中断号),驱动CPU控制的I2C适配器从硬件上产生。
2).提供I2C控制的algorithm, 用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋给i2c_adapter的algo指针。
3).实现I2C设备驱动中的i2c_driver接口,用具体yyy的yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和i2c_device_id设备ID表赋给i2c_driver的probe,remove,suspend,resume和id_table指针。
4).实现I2C设备所对应类型的具体 ,i2c_driver只是实现设备与总线的挂接。
下面详细描述I2C设备驱动如何挂载到总线。
三、示例
下面以GPIO模拟i2c总线的驱动为例,来介绍实现I2C设备驱动如何挂载到总线上
1) i2c-gpio总线和i2c设备驱动设备树
首先要添加 i2c-gpio总线和i2c设备驱动设备树节点,总线设备树节点无需添加,只需在添加设备驱动时,将对应的的I2C总线打开,添加节点内容在不同平台所相对应的设备树dts文件里,做法如下:
&i2c4 {
status = "okay";
<name>[@<unit_address>]@{ //其中name就是设备名,最长可以是31个字符长度。unit_address一般是设备地址,用来唯一标识一个节点
compatible = "name2"; //需要与name_ids的 .compatible一致,不然注册不成功
reg = <unit_address>;
irq_gpio = <&gpio7 GPIO_B2 IRQ_TYPE_LEVEL_LOW>;//中断脚GPIO
reset_gpio = <&gpio7 GPIO_B1 GPIO_ACTIVE_HIGH>; //复位脚GPIO
};
}
2) i2c-gpio总线和i2c设备驱动注册
i2c总线注册无需自己注册,把i2c设备驱动注册到i2c-gpio总线。做法如下,
I2C设备树:
做法如下。首先定义设备ID:
static const struct i2c_device_id name_id[] = {
{ "name1", 0 },//设备名和设备是有数据长度
{ }
};
设备树名称:
static struct of_device_id name_ids[] = {
{ .compatible = "name2" },//需要与设备树compatible一致,不然注册不成功
{ }
};
然后声明i2c_driver结构:
static struct i2c_driver name_driver = {
.probe = name_probe,
.remove = name_remove,
.suspend = name_suspend,
.resume = name_resume,//上面4个函数根据具体情况取舍
.id_table = name_id,
.driver = {
.name = "name1", //需要与name_id里的设备名一致
.of_match_table = of_match_ptr(name_ids),
},
};
最后调用static inline int i2c_add_driver(struct i2c_driver *driver)注册name驱动到I2C总线,如下:
static int __init name_init(void)
{
return i2c_add_driver(&name_driver);//注册name_driver
};
module_init(name_init);
3) 实现设备与总线的挂接
i2c_driver实现设备与总线的挂接。用具体yyy的yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和i2c_device_id设备ID表赋给i2c_driver的probe,remove,suspend,resume和id_table指针。其中.probe指针所对应的yyy_probe()函数指针则判断上电时序、I2C是否读取正确,返回值正确才能够实现I2C驱动设备的使用。
yyy_probe()函数指针判断大致流程如下,以上面缩写的设备树为例:
第一步;从设备数获取指定GPIO属性信息
irq_gpio = of_get_named_gpio(np, "irq_gpio",0);
reset_gpio = of_get_named_gpio(np, "irq_gpio", 0);
第二步:申请GPIO管脚
ret = gpio_request(reset_gpio, "touch_gpio_reset");
ret += gpio_request(irq_gpio, "touch_gpio_irq");
第三步:判断上电时序
不同设备有不同的上电时序,根绝供应商所提供的资料编写
第四步:读取I2C
s32 name_i2c_read(struct i2c_client *client, u8 *buf, s32 len)
{
struct i2c_msg msgs[2];
s32 ret=-1;
s32 retries = 0;
GTP_DEBUG_FUNC();
msgs[0].flags = !I2C_M_RD;
msgs[0].addr = client->addr;
msgs[0].len = GTP_ADDR_LENGTH;
msgs[0].buf = &buf[0];
#ifdef CONFIG_I2C_ROCKCHIP_COMPAT
msgs[0].scl_rate=200 * 1000;
//msgs[0].scl_rate = 300 * 1000; // for Rockchip, etc.
#endif
msgs[1].flags = I2C_M_RD;
msgs[1].addr = client->addr;
msgs[1].len = len - GTP_ADDR_LENGTH;
msgs[1].buf = &buf[GTP_ADDR_LENGTH];
#ifdef CONFIG_I2C_ROCKCHIP_COMPAT
msgs[1].scl_rate=200 * 1000;
//msgs[1].scl_rate = 300 * 1000; // for Rockchip, etc.
#endif
while(retries < 5)
{
ret = i2c_transfer(client->adapter, msgs, 2);
if(ret == 2)break;
retries++;
}
if((retries >= 5))
{
printk("I2C Read: 0x%04X, %d bytes failed, errcode: %d! Process reset.", (((u16)(buf[0] << 8)) | buf[1]), len-2, ret);
}
return ret;
}
第五步:读取到正确的I2C,将GPIO管脚释放
gpio_free(reset_gpio);
gpio_free(irq_gpio);
4) 实现I2C设备所对应类型的具体
实现 I2C 设备驱动的文件操作接口,即实现具体设备 yyy 的 yyy_read() 、 yyy_write() 和 yyy_ioctl() 函数等。