I2C总线仅仅使用 SCL 、 SDA 两根信号线就实现了设备之间的数据交互。
由于各种SOC都有自己的I2C总线,为了上层能统一接口,采用这种三层I2C架构.
I2C总线驱动主要实现了适用于特定I2C控制器的总线读写方法,并注册到Linux内核的I2C架构,I2C外设就可以通过I2C架构完成设备和总线的适配。但是总线驱动本身并不会进行任何的通讯,它只是提供通讯的实现,等待设备驱动来调用其函数。
I2C Core的管理正好屏蔽了I2C总线驱动的差异,使得I2C设备驱动可以忽略各种总线控制器的不同,不用考虑其如何与硬件设备通讯的细节。
Linux的I2C构架分为三个部分:
1)I2C core框架
提供了核心数据结构的定义和相关接口函数,用来实现I2C适配器
驱动和设备驱动的注册、注销管理,以及I2C通信方法上层的、与具体适配器无关的代码,为系统中每个I2C总线增加相应的读写方法。
kernel抽象出I2C bus(/sys/bus/i2c),用于挂载和I2C adapter通过I2C总线连接的各个I2C slave device。
I2C Bus 并不是通讯上的总线,而是linux系统为了管理设备和驱动而虚拟出来的,在I2C Bus用来挂载后面将会使用到的I2C 适配器(adapter)和I2C设备(client)
2) I2C总线驱动 (i2c_adapter)
定义描述具体I2C总线适配器的i2c_adapter数据结构、实现在具体I2C适配器上的I2C总线通信方法,并由i2c_algorithm数据结 构进行描述。
封装了 struct device ,因此它是作为一个设备注册到内核中去的(是注册到i2c_bus_type里),此外非常重要的一个成员struct i2c_algorithm *algo ,这就是我们上边提到的 i2c 控制器收发数据的方法。
经过I2C总线驱动的的代码,可以为我们控制I2C产生开始位、停止位、读写周期以及从设备的读写、产生ACK等。
I2C总线驱动具体实现在/drivers/i2c目录下busses文件夹。
例如:
Linux I2C GPIO总线驱动为i2c_gpio.c.
全志 drivers/i2c/busses/i2c-sunxi.c
I2C总线算法在/drivers/i2c目录下algos文件夹。
例如:Linux I2C GPIO总线驱动算法实现在i2c_algo_bit.c.
针对不同类型的I2C控制器,实现对I2C总线访问的具体方法.(各种SOC不一样)
3) I2C 设备驱动(I2C client driver)
是对具体I2C硬件驱动的实现。I2C 设备驱动通过I2C适配器与CPU通信。
其中主要包含i2c_driver和i2c_client数据结构。
i2c_driver结构对应一套具体的驱动 方法,例如:probe、remove、suspend等,需要自己申明。
i2c_client数据结构由内核根据具体的设备注册信息自动生成,设备驱动 根据硬件具体情况填充。
I2C 设备驱动具体实现放在在/drivers/i2c目录下chips文件夹。
重要的结构体
i2c_driver
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针
int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void*arg);//命令列表
struct device_driver driver;
13 const struct i2c_device_id *id_table;//该驱动所支持的设备ID表
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
i2c_client
struct i2c_client {
unsigned short flags;//标志
unsigned short addr; //低7位为芯片地址
char name[I2C_NAME_SIZE];//设备名称
struct i2c_adapter *adapter;//依附的i2c_adapter
struct i2c_driver *driver;//依附的i2c_driver
struct device dev;//设备结构体
int irq;//设备所使用的结构体
struct list_head detected;//链表头
};
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;
char name[48];//适配器名称
struct completion dev_released;//用于同步
struct list_head userspace_clients;//client链表头
15 };
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也非常关键,调用驱动中的发送接收函数需要填充该结构体
i2c_driver和i2c_client
i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client()
i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述
i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.
i2c_adapter和i2c_client
i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。
struct list_head userspace_clients;//client链表头
重要的接口函数
注册一个驱动
【函数原型】:i2c_add_driver
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
【功能描述】:注册一个I2C设备驱动。从代码可以看带i2c_add_driver()是一个宏,由函数i2c_register_driver()实现。
【参数说明】:driver,i2c_driver类型的指针,其中包含了I2C设备的名称、probe、detect等接口信息。
注册一个设备
【函数原型】:i2c_register_board_info
int i2c_register_board_info(int busnum, struct i2c_board_info const *info,
unsigned n)
【功能描述】:向某个I2C总线注册I2C设备信息,I2C子系统通过此接口保存I2C总线和I2C设备的适配关系。
busnum 通过总线号指定这个(些)设备属于哪个总线
info i2c设备的数组集合i2c_board_info格式
i2c_register_board_info具体实现: 相关信息放到链表中就算完事
int __init
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock); //i2c设备信息读写锁,锁写操作,其他只读
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num) //与动态分配的总线号相关,动态分配的总线号应该是从已经现有最大总线号基础上+1的,这样能够保证动态分配出的总线号与板级总线号不会产生冲突
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) { //处理info数组中每个成员
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum; //组装总线号
devinfo->board_info = *info; //组装设备信息
list_add_tail(&devinfo->list, &__i2c_board_list); //加入到__i2c_board_list链表中(尾部)
}
up_write(&__i2c_board_lock); //释放读锁,其他可读可写
return status;
}
调用i2c_register_board_info的I2C设备注册过程应该在板级代码初始化期间,也就是arch_initcall前后的时间,
在I2C适配器驱动注册前完成。
如果在I2C适配器注册完后还想要添加I2C设备的话,就要通过新方式!(即i2c_new_device)
【函数原型】: i2c_new_device
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
adap 此设备所依附的I2C适配器指针
info 此设备描述,i2c_board_info格式,bus_num成员是被忽略的
struct i2c_board_info info={
.type = SENSOR=NAME,
.addr = SENSOR_I2C_ADDR,
}
adapter = i2c_get_adapter(0); //参数代表i2c num
client = i2c_new_device(adapter, &info);
数据传输
I2C设备驱动使用"struct i2c_msg"向I2C总线请求读写I/O。
一个i2c_msg中包含了一个I2C操作,通过调用i2c_transfer()接口触发I2C总线的数据收发。
【函数原型】:i2c_transfer
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
【功能描述】:完成I2C总线和I2C设备之间的一定数目的I2C message交互。
【函数原型】:i2c_master_recv
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
【功能描述】:通过封装i2c_transfer()完成一次I2c接收操作。
【函数原型】:i2c_master_send
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
【功能描述】:通过封装i2c_transfer()完成一次I2c发送操作。
【函数原型】:i2c_smbus_read_byte
s32 i2c_smbus_read_byte(const struct i2c_client *client)
【功能描述】:从I2C总线读取一个字节。(内部是通过i2c_transfer()实现,以下几个接口同。)
【函数原型】:i2c_smbus_write_byte
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
【功能描述】:从I2C总线写入一个字节。
驱动代码例子
设备
1)BSP文件中静态声明一个I2C设备
static struct i2c_board_info i2c_devices[] __initdata = {
{I2C_BOARD_INFO("24c02", 0x50), },
{}
};
2)向总线注册I2C设备信息:
i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));
驱动
1)模块初始化时添加/撤销时删除i2c_driver
module_init(mma7660_init); //模块入口
module_exit(mma7660_exit); //模块出口
2)
init中和驱动框架相关的就一句话ret = i2c_add_driver(&mma7660_driver)而这一句话表示向I2C总线注册一个驱动,根据宏定义i2c_driver结构并完成其相应函数:
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "i2c_demo",
.owner = THIS_MODULE,
},
.probe = my_i2c_probe,
.remove = my_i2c_remove,
.id_table = my_ids,
};
3)使用/dev entry 访问方法
register_chrdev(I2C_MAJOR,DEVICE_NAME,&i2c_fops);
创建类class_create(THIS_MODULE, DEVICE_NAME);
在/dev下创建设备节点
device_create(my_dev_class, &client->dev,MKDEV(I2C_MAJOR, 0), NULL, DEVICE_NAME);
I2c detect的方法
必须要定义address_list
static unsigned short s_Normal_I2c[] = {0x35, I2C_CLIENT_END}; // 芯片地址
static const struct i2c_device_id i2c_detect_id[] = {
{LMX_I2C_DETECT_DEVICE_NAME, 0},
{}
};
static struct i2c_driver i2c_detect_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.owner = THIS_MODULE,
.name = LMX_I2C_DETECT_DEVICE_NAME,
},
.detect = i2c_detect_Detect, // 会往所有的 I2C 控制器上寻指定的 I2C 设备地址的 I2C 设备,若有 ACK 回应则会调用该函数
.id_table = i2c_detect_id,
.address_list = s_Normal_I2c, // 地址列表
};
i2c_core.c 里面有一个 i2c_detect函数
if(driver->detect || !address_list)
return 0;