Zynqlinux的I2C驱动学习笔记(一)-创新互联
最近在用米尔的Z-TURN BOARD单板做小项目。顺便也加强学习I2C驱动,记一篇做记录。
I2C总线知识非常简单,SDA,SCL,他们的时序规则是:I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,在信息的传输过程中,I2C总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接收器),这取决于它所要完成的功能。CPU发出的控制信号分为地址码和控制量两部分,地址码用来选址,即接通需要控制的电路,确定控制的种类;控制量决定该调整的类别(如对比度、亮度等)及需要调整的量。这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。
I2C总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
在LINUX系统初始化的过程中,通过 i2c_register_board_info,将所需要的I2C从设备加入一个名为_i2c_board_list双向循环链表,系统在成功加载I2C主设备adapt后,就会对这张链表里所有I2C从设备逐一地完成 i2c_client的注册。
也就是说,i2c_client和i2c_adapter都是由i2c_core来维护的。
在xilinx-linux中,i2c从设备是通过dts文件传递给内核的,内核通过zynq_init_machine函数注册所有的i2c从设备,i2c_client.
I2C的linux必须知道4个结构体:i2c_adapter,i2c_algorithm,i2c_client,i2c_driver
struct i2c_adapter {
struct module owner;
unsigned int class; / classes to allow probing for /
const struct i2c_algorithm algo; / the algorithm to access the bus/
void *algo_data;
/ data fields that are valid for all devices/
struct rt_mutex bus_lock;
int timeout; / in jIFfies /
int retries;
struct device dev; / the adapter device/
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总线控制器数据依附于algo_data,比如xi2cps,s3c24xx_i2c。
struct device dev;成员表明i2c_adapter是一个硬件,对应SoC上的I2C控制器。而i2c_algorithm则是这个I2C控制器的底层驱动程序。
同理:
struct i2c_client {
unsigned short flags; / div., see below /
unsigned short addr; / chip address - NOTE: 7bit /
/ addresses are stored in the /
/ LOWER 7 bits/
char name[I2C_NAME_SIZE];
struct i2c_adapter adapter; / the adapter we sit on /
struct i2c_driver driver; / and our access routines /
struct device dev; / the device structure /
int irq; / irq issued by device/
struct list_head detected;
};
struct i2c_client代表一个挂载到i2c总线上的i2c从设备,该设备所需要的数据结构,其中包括
该i2c从设备所依附的i2c主设备 struct i2c_adapter adapter
该i2c从设备的驱动程序struct i2c_driverdriver
作为i2c从设备所通用的成员变量,比如addr, name等
该i2c从设备驱动所特有的数据,依附于dev->driver_data下,在i2c_driver中的probe函数中设置这个结构体成员。比如eeprom的eeprom_data。
所有i2c从设备组成的双向链表:detected
struct device dev表明struct i2c_client代表的是一个硬件,比如eeprom芯片,或则rtc芯片,通过i2c总线连接到i2c_adapter硬件上。
而i2c_driver则是这个i2c_client芯片硬件的驱动程序。
我们一般会对每个I2C字符设备定义一个私有信息结构体,而i2c_client一般被包含在这个私有信息结构体中。看过LDR3源代码的hacker应该比较清楚。
i2c_client依附于i2c_adapter,也就是I2C设备和I2C总线控制器的对应关系,一个i2c_adapter可以挂接多个i2c_client,i2c_adapter的struct list_head userspace_clients;结构成员就是所有client的链表。
linux的最新版本基本上支持目前所有的I2C适配器硬件和I2C从设备,但是对于工程师来说,可能要面临各种情况:为i2c_adapter和i2c_client编写驱动程序。
二、I2C核心
I2C核心是源码位于drivers/i2c/i2c-core.c,它并不依赖于硬件平台的接口函数,是I2C总线驱动和设备驱动的纽带。
增加/删除i2c_adapter
int i2c_add_adapter(struct i2c_adapter adapter) //调用i2c_register_adapter()
int i2c_del_adapter(struct i2c_adapteradapter)
增加/删除i2c_driver
int i2c_register_driver(struct module owner, struct i2c_driverdriver)
int i2c_add_driver(struct i2c_driver driver) //调用i2c_register_driver
void i2c_del_driver(struct i2c_driverdriver)
增加/删除i2c_client
struct i2c_client i2c_new_device(struct i2c_adapteradap, struct i2c_board_info const info)
void i2c_unregister_device(struct i2c_clientclient)
注:在2.6.30版本之前使用的是i2c_attach_client()和i2c_detach_client()函数。之后attach被merge到了i2c_new_device中,而detach直接被unregister取代。实际上这两个函数内部都是调用了device_register()和device_unregister()
I2C传输、发送接收
int i2c_transfer(struct i2c_adapter adap, struct i2c_msgmsgs, int num)
int i2c_master_send(struct i2c_client client,const charbuf ,int count)
int i2c_master_recv(struct i2c_client client, charbuf ,int count)
i2c_transfer()函数用于进行I2C 适配器和I2C 设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。
i2c_transfer()本身不能和硬件完成消息交互,它寻找i2c_adapter对应的i2c_algorithm,要实现数据传送就要实现i2c_algorithm的master_xfer(),这个函数与具体的硬件有关,大部分时间由厂商完成。
i2c_transfer()通过调用__i2c_transfer()完成I2C通讯:
int __i2c_transfer(struct i2c_adapter adap, struct i2c_msgmsgs, int num)
{
unsigned long orig_jiffies;
int ret, try;
/ Retry automatically on arbitration loss/
orig_jiffies = jiffies;
for (ret = 0, try = 0; try <= adap->retries; try++) {
ret = adap->algo->master_xfer(adap, msgs, num);
if (ret != -EAGAIN)
break;
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
}
return ret;
}
可见retries为重传尝试次数,timeout为超时时间。
三、Linux I2C总线驱动
1、I2C适配器的加载和卸除
加载:申请硬件资源,比如IO地址,中断号,调用i2c_add_adapter加载适配器
i2c_add_adapter中会调用i2c_register_adapter函数
static int i2c_register_adapter(struct i2c_adapter *adap)
{
device_register(&adap->dev); //完成I2C主设备adapter的注册,即注册object和发送uevent等
i2c_scan_static_board_info(adap); //注册i2c_clienlt
}
static void i2c_scan_static_board_info(struct i2c_adapter adapter)
{
struct i2c_devinfodevinfo;
down_read(&__i2c_board_lock);
list_for_each_entry(devinfo, &i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
“Can’t create device at 0x%02x\n”,
devinfo->board_info.addr);
}
up_read(&i2c_board_lock);
}
i2c_new_device调用device_register注册i2c从设备。
那么,这个I2C从设备组成的双向循环链表,是什么时候通过什么方式建立起来的呢?
以 /arch/ARM/mach-pxa/saar.c 为例
static void __init saar_init(void)
{
saar_init_i2c();
}
static void __init saar_init_i2c(void)
{
pxa_set_i2c_info(NULL);
i2c_register_board_info(0, ARRAY_AND_SIZE(saar_i2c_info));
}
static struct i2c_board_info saar_i2c_info[] = {
[0] = {
.type = “da9034”,
.addr = 0x34,
.platform_data = &saar_da9034_info,
.irq = PXA_GPIO_TO_IRQ(mfp_to_gpio(MFP_PIN_GPIO83)),
},
};
/ drivers/i2c/i2c-boardinfo.c/
int __init i2c_register_board_info(int busnum, structi2c_board_info const info, unsigned len)
{
struct i2c_devinfodevinfo;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list); //将I2C从设备加入该链表中
}
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。
文章标题:Zynqlinux的I2C驱动学习笔记(一)-创新互联
本文网址:http://hbruida.cn/article/dhgeoh.html