问答
直播中

中科院

10年用户 208经验值
擅长:可编程逻辑 电源/新能源 MEMS/传感技术 嵌入式技术 连接器 光电显示 存储技术 接口/总线/驱动 控制/MCU RF/无线
私信 关注

【Z-turn Board试用体验】+ Zynq linux的I2C驱动学习笔记(一)

本帖最后由 中科院 于 2015-6-23 18:51 编辑

      去年做树莓派的驱动1602时没有使用wiringPi的库,而是使用了有I2C的芯片PCF,当时就想要学I2C驱动的一些细节,但是苦于自己水平差一些,就一直没有去看,现在学习了一段时间,再一看,感觉就比较轻松了。于是移植到Z-TURN BOARD上面,就记一篇做记录。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%02xn",
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从设备加入该链表中
}




回帖(1)

1652981125.048900

2015-12-22 11:23:29
入门好难呀!!!!!
举报

更多回帖

发帖
×
20
完善资料,
赚取积分