I2C子系统由上到下分为3层:,这一层需要客户实现。I2C设备驱动层、I2C核心层和I2C适配器驱动层。
I2C设备驱动层:真正实现具体设备的时序的代码。使用核心层提供API接口写,有特定编写框架。
I2C核心层:由内核提供,代码不用修改,提供了设备驱动层和适配器驱动层需要API接口,以及实现收发数据管理功能。起到一个连接上下两的作用。
I2C适配器驱动层:由芯片厂家提供,但需要移植。通俗说就是直接操作硬件上IIC控制的的驱动代码。真正的实现IIC数据收发。
核心层:
i2c-core.c i2c-boardinfo.c i2c-smbus.c i2c-mux.c
I2C适配器驱动层:
busses文件夹下的一个C文件对应于一个物理的 I2C适配器 驱动程序
设备驱动层:(这一层是核心,由驱动工程师实现)
1.核心结构:
I2c.h include\Linux
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared or is about to be
* removed. You should avoid using this, it will be removed in a
* near future. 老接口,可能会消失,建议不要使用.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*detach_adapter)(struct i2c_adapter *) __deprecated;
/* 新的接口,用来替代上面两个接口,必须实现,功能类型平台模型的probe,remove*/
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
内核使用 struct i2c_client 来描述一个设备信息。
struct i2c_client {
unsigned short flags; //标志,比如说设备地址10位还是7位
unsigned short addr; //低7位为芯片地址
char name[I2C_NAME_SIZE]; //设备名称,随便,但是要和驱动层id_table相同
struct i2c_adapter *adapter; //依附的 i2c_adapter,它表示一个IIC控制器
struct i2c_driver *driver; //依附的i2c_driver
struct device dev; //设备结构体
int irq; //设备所使用的中断号
struct list_head detected; //链表头
};
最小实现:flags;addr,name,adapter。
2.API函数
int i2c_add_driver(struct i2c_driver *driver)
注册I2C设备驱动,driver 是已经填充好的struct i2c_driver 结构指针
一般写在模块的初始化代码。
void i2c_del_driver(struct i2c_driver *driver)
注销I2C设备驱动,
一般写在模块的出口处。
int i2c_master_send(struct i2c_client *client,
const char *buf ,
int count) //发送函数
功能:发送数据给真正的硬件设备。
参数:
client:指针I2C设备结构的指针。
buf:发送的数据指针
count:发送的字符数量
int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
功能:从硬件中读取数据
参数:
client:指针I2C设备结构的指针。
buf:存放数据指针
count:要读的字节数量
注意:此函数只是实现标准IIC的读协议,不代表具体器件读协议。
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
功能:这个函数是I2C传输函数,收发一体的函数。
参数:
adap: 指针I2C适配器结构的指针,这个指针是使用 i2c_client 中的 adapter 指针。
msgs:存放数据指针
num: 要传输的 struct i2c_msg 数量
内核使用 struct i2c_msg 结构来描述一则消息(一则消息不是传递一个字节,可以传递多个字节,最大65536字节),包含了目标地址,操作方式(读写),数据存放位置 或 源位置。
struct i2c_msg {
__u16 addr; /* 这条消息是发送给谁 */
__u16 flags; /* 消息额外的标志,可选择的值有以下宏*/
#define I2C_M_TEN 0x0010 /* 表示目标器件地址是10位的 */
#define I2C_M_RD 0x0001 /* 在从设备中读取数据 */
/* 没有专门定义一个写的标志,默认是写,*/
//以下标志使用不到
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* 传输的数据字节数量 */
__u8 *buf; /* 接收/发送缓冲区 */
};
如:要写数据给AT24C02 ,从内部地址10开始写,写2个:
char subaddr = 10; //内部地址
char data[100]={1,2,3};//等待发送的数据
struct i2c_msg msgs[2]={
[0]={
.addr = EEPROM_DEVICE_ADDR,// EEPROM_DEVICE_ADDR器件地址
.flags= 0 ,
.len = 1,
.buf =&subaddr
},
[1]={
.addr = addr,//器件地址
.flags= 0 ,
.len = 2,
.buf = data
},
};
i2c_transfer(client->adapter,msgs,2);
以下是 smbus 总线的操作函数,smbus是系统管理总线,可以看成是IIC总线 的一个子集,它的规范大部分都是基于I2C标准。所以,大部分的IIC器件也可以使用 smbus 总线来操作。非常合适操作那些有内部地址的器件。
s32 i2c_smbus_read_byte_data(const struct i2c_client *client,u8 command):
功能:指定地址的单字节读
command:内部地址
返回值:读到的数据
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,
u8 command, u8 value)
功能:指定地址的单字节写入数据
command:内部地址
value:要写入的数据
如果器件存在页写问题,建议使用这个函数,循环操作。
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client,
u8 command,
u8 length,
u8 *values)
功能:从指定地址开始读取指定长度的数据
command:内部地址
length:要读的数据长度
value:存放数据的指针
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,
u8 command,
u8 length,
u8 *values)
功能:从指定地址开始读取指定长度的数据
command:内部地址
length:要写的数据长度
value:源数据指针
3. 驱动层代码编程模板
static const struct i2c_device_id ft5x0x_id_table[] = {
{ "ft5x0x", 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, ft5x0x_id_table);
struct i2c_client *tsclient;
/* 针对单点的 */
static int __devinit ft5x0x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
s32 val;
tsclient = client;
//给用户提供访问接口
……
return 0;
}
static int __devexit ft5x0x_remove(struct i2c_client *client)
{
return 0;
}
static struct i2c_driver ft5x0x_driver = {
.driver = {
.name = "xyz", //可以在 sys/bus/i2c/drivers查看到
.owner = THIS_MODULE,
},
.probe = ft5x0x_probe,
.remove = __devexit_p(ft5x0x_remove),
.id_table = ft5x0x_id_table,
};
static __init int ft5x0x_drv_init(void)
{
/* 注册一个IIC驱动 */
i2c_add_driver(&ft5x0x_driver);
return 0;
}
static __exit void ft5x0x_drv_exit(void)
{
i2c_del_driver(&ft5x0x_driver);
}
module_init(ft5x0x_drv_init);
module_exit(ft5x0x_drv_exit);
MODULE_LICENSE("GPL");
设备层
struct i2c_adapter *i2c_get_adapter(int nr)
作用:1)获取 i2c_adapter 结构地址
struct i2c_adapter {
struct module *owner; //所属模块
unsigned int id; //algorithm的类型,定义于i2c-id.h,
unsigned int class;
const struct i2c_algorithm *algo; //总线通信方法结构体指针
void *algo_data; //algorithm数据
struct rt_mutex bus_lock; //控制并发访问的自旋锁
int timeout; //读写超时时间
int retries; //重试次数
struct device dev; //适配器设备
int nr; // 总线编号 像i2c0,i2c1,……
char name[48]; //适配器名称
struct completion dev_released; //用于同步
struct list_head userspace_clients;//client链表头
};
这个结构是用来表示一个I2C适配器。 要写适配器驱动就是实现这个结构,然后使用核心层提供的适配器注册函数注册。一般情况是由芯片商提供,不需要程序员写,只需要移植。
2)增加 i2c_adapter 结构的引用计数,防止使用过程中被移除。
nr:就是适配器的总线编号:
i2c0, --- 就是0
i2c1, --- 就是1
返回:指针适配器结构的首地址。
i2c_put_adapter(struct i2c_adapter *adap)
减少i2c_adapter引用计数,使用 i2c_get_adapter 后都需要使用这个函数。
struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap,
struct i2c_board_info *info,
unsigned short const *addr_list,
int (*probe)(struct i2c_adapter *, unsigned short addr))
作用:注册I2C设备
参数:
adap : i2c_client 所依赖的适配器指针
adap获取:通过i2c_get_adapter 函数获取
info:i2c设备的描述信息
addr_list:i2c设备可能出现的地址表,是一个数组
内核使用 struct i2c_board_info 描述一个i2c设备基本信息,通过这些可以创建一个 i2c_client 结构。
/**
* struct i2c_board_info - template for device creation
* @type:用来初始化 i2c_client.name
* @flags: 用来初始化 i2c_client.flags
* @addr: 用来初始化 i2c_client.addr
* @platform_data: 用来初始化 i2c_client.dev.platform_data
* @archdata: copied into i2c_client.dev.archdata
* @of_node: pointer to OpenFirmware device node
* @irq: 用来初始化 i2c_client.irq
*
*/
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
int irq;
};
probe:探测到i2c设备时候会执行回调函数,一般是NULL。
I2C子系统机制和平台设备不一样,虽然是通过id_table,但是还有一个条件,就是i2c器件是真实存在的。
void i2c_unregister_device(struct i2c_client *client)
取消一个I2C设备注册
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num);//I2C传函数指针
int (*smbus_xfer) (struct i2c_adapter *adap,
u16 addr,unsigned short flags,
char ead_write,u8 command, int size, union
i2c_smbus_data *data);//smbus传输函数指针
u32 (*functionality) (struct i2c_adapter *);//返回适配器支持的功能
};
提供了怎么产生字节发送,接收时序的代码。master_xfer一定要实现。
各结构体的作用与它们之间的关系
1.i2c_adapter与i2c_algorithm
i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。
i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 这则的的特征,可取的值以下宏*/
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* 读操作,写操作没有定义宏,不设置这个宏就是写*/
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* 消息长度 */
__u8 *buf; /* 指向存放或数据源的地址,1,1,2,3,,*/
};
这个结构是代表一则消息,一则消息可以包含多个数据(读写多个数据)
2.i2c_driver和i2c_client
i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client(),这两个是旧的写法,新挑版本内核中会被删除,应该使用 probe和remove接口。
i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述。
i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.
3. i2c_adapter和i2c_client
i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。
从i2c驱动架构图中可以看出,linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器),core层不但为上面的设备驱动提供封装后的内核注册函数,而且还为下面的硬件事件提供注册接口(也就是i2c总线注册接口),可以说core层起到了承上启下的作用。
代码调用层次图
上面这些代码的展示是告诉我们:linux内核和芯片提供商为我们的的驱动程序提供了 i2c驱动的框架,以及框架底层与硬件相关的代码的实现。
剩下的就是针对挂载在i2c两线上的i2c设备了device,而编写的即具体设备驱动了,这里的设备就是硬件接口外挂载的设备,而非硬件接口本身(soc硬件接口本身的驱动可以理解为总线驱动)
通过i2c的ID表匹配。
i2c_client----i2c_tranfer----i2c core----i2c_adaptor----i2c时序
编写驱动需要完成的工作
编写具体的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总线驱动,后面两个属于I2C设备驱动。