Zynq linux的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_driver driver 
作为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_adapter adapter) 
增加/删除i2c_driver 
int i2c_register_driver(struct module owner, struct i2c_driver driver) 
int i2c_add_driver(struct i2c_driver driver) //调用i2c_register_driver 
void i2c_del_driver(struct i2c_driver driver) 
增加/删除i2c_client 
struct i2c_client i2c_new_device(struct i2c_adapter adap, struct i2c_board_info const info) 
void i2c_unregister_device(struct i2c_client client) 
注:在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_msg msgs, int num) 
int i2c_master_send(struct i2c_client client,const char buf ,int count) 
int i2c_master_recv(struct i2c_client client, char buf ,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_msg msgs, 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_devinfo devinfo; 
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_devinfo devinfo; 
devinfo->board_info = *info; 
list_add_tail(&devinfo->list, &__i2c_board_list); //将I2C从设备加入该链表中

为满足不同企业的网络连接需求,海外专线服务提供多种带宽速率选项,通过灵活、便捷的接入方式,直连运营商骨干互联网,再透传访问全球互联网内容,助力高质量、高安全的实现全球互联互通。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值