版权声明:本文为博主原创文章,未经博主允许不得转载。
在Linux驱动中I2C系统中主要包含以下几个成员:
I2C adapter 即I2C适配器
I2C driver 某个I2C设备的设备驱动,可以以driver理解。
I2C client 某个I2C设备的设备声明,可以以device理解。
I2C adapter
是CPU集成或外接的I2C适配器,用来控制各种I2C从设备,其驱动需要完成对适配器的完整描述,最主要的工作是需要完成i2c_algorithm结构体。这个结构体包含了此I2C控制器的数据传输具体实现,以及对外上报此设备所支持的功能类型。i2c_algorithm结构体如下:
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality) (struct i2c_adapter *);
};
如果一个I2C适配器不支持I2C通道,那么就将master_xfer成员设为NULL。如果适配器支持SMBUS协议,那么需要去实现smbus_xfer,如果smbus_xfer指针被设为NULL,那么当使用SMBUS协议的时候将会通过I2C通道进行仿真。master_xfer指向的函数的返回值应该是已经成功处理的消息数,或者返回负数表示出错了。functionality指针很简单,告诉询问着这个I2C主控器都支持什么功能。
在内核的drivers/i2c/i2c-stub.c中实现了一个i2c adapter的例子,其中实现的是更为复杂的SMBUS。
SMBus 与 I2C的区别
通常情况下,I2C和SMBus是兼容的,但是还是有些微妙的区别的。
时钟速度对比:
I2C | SMBus | |
---|---|---|
最小 | 无 | 10kHz |
最大 | 100kHZ(标准)400kHz(快速模式)2MHz(高速模式) | 100kHz |
超时 | 无 | 35ms |
在电气特性上他们也有所不同,SMBus要求的电压范围更低。
I2C driver
具体的I2C设备驱动,如相机、传感器、触摸屏、背光控制器常见硬件设备大多都有或都是通过I2C协议与主机进行数据传输、控制。结构体如下:
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; //旧的与设备进行解绑的接口函数
/* Standard driver model interfaces */
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; //I2C设备的驱动模型
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;
};
#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver) //一般编写驱动过程中对象常是driver类型,可以通过to_i2c_driver找到其父类型i2c_driver
如同普通设备的驱动能够驱动多个设备一样,一个I2C driver也可以对应多个I2C client。
以重力传感器AXLL34X为例,其实现的I2C驱动为:
static const struct i2c_device_id adxl34x_id[] = {
{ "adxl34x", 0 }, //匹配i2c client名为adxl34x的设备
{ }
};
MODULE_DEVICE_TABLE(i2c, adxl34x_id);
static struct i2c_driver adxl34x_driver = {
.driver = {
.name = "adxl34x",
.owner = THIS_MODULE,
.pm = &adxl34x_i2c_pm, //指定设备驱动的电源管理接口,包含suspend、resume
},
.probe = adxl34x_i2c_probe, //组装设备匹配时候的匹配动作
.remove = adxl34x_i2c_remove, //组装设备移除接口
.id_table = adxl34x_id, //制定匹配设备列表
};
module_i2c_driver(adxl34x_driver);
这里要说明一下module_i2c_driver宏定义(i2c.h):
#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, \
i2c_del_driver)
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
module_driver():
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
理解上述宏定义后,将module_i2c_driver(adxl34x_driver)展开就可以得到:
static int __int adxl34x_driver_init(void)
{
return i2c_register_driver(&adxl34x_driver);
}
module_init(adxl34x_driver_init);
static void __exit adxl34x_driver_exit(void)
{
return i2c_del_driver(&adxl34x_driver);
}
module_exit(adxl34x_driver_exit);
这一句宏就解决了模块module安装卸载的复杂代码。这样驱动开发者在实现I2C驱动时只要将i2c_driver结构体填充进来就可以了,无需关心设备的注册与反注册过程。
I2C client
即I2C设备。I2C设备的注册一般在板级代码中,在解析实例前还是先熟悉几个定义:
struct i2c_client {
unsigned short flags; //I2C_CLIENT_TEN表示设备使用10bit从地址,I2C_CLIENT_PEC表示设备使用SMBus检错
unsigned short addr; //设备从地址,7bit。这里说一下为什么是7位,因为最后以为0表示写,1表示读,通过对这个7bit地址移位处理即可。addr<<1 & 0x0即写,addr<<1 | 0x01即读。
char name[I2C_NAME_SIZE]; //从设备名称
struct i2c_adapter *adapter; //此从设备依附于哪个adapter上
struct i2c_driver *driver; // 此设备对应的I2C驱动指针
struct device dev; // 设备模型
int irq; // 设备使用的中断号
struct list_head detected; //用于链表操作
};
#define to_i2c_client(d) container_of(d, struct i2c_client, dev) //通常使用device设备模型进行操作,可以通过to_i2c_client找到对应client指针
struct i2c_board_info {
char type[I2C_NAME_SIZE]; //设备名,最长20个字符,最终安装到client的name上
unsigned short flags; //最终安装到client.flags
unsigned short addr; //设备从地址slave address,最终安装到client.addr上
void *platform_data; //设备数据,最终存储到i2c_client.dev.platform_data上
struct dev_archdata *archdata;
struct device_node *of_node; //OpenFirmware设备节点指针
struct acpi_dev_node acpi_node;
int irq; //设备采用的中断号,最终存储到i2c_client.irq上
};
//可以看到,i2c_board_info基本是与i2c_client对应的。
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
//通过这个宏定义可以方便的定义I2C设备的名称和从地址(别忘了是7bit的)
下面还是以adxl34x为例:
static struct i2c_board_info i2c0_devices[] = {
{
I2C_BOARD_INFO("ak4648", 0x12),
},
{
I2C_BOARD_INFO("r2025sd", 0x32),
},
{
I2C_BOARD_INFO("ak8975", 0x0c),
.irq = intcs_evt2irq(0x3380), /* IRQ28 */
},
{
I2C_BOARD_INFO("adxl34x", 0x1d),
.irq = intcs_evt2irq(0x3340), /* IRQ26 */
},
};
...
i2c_register_board_info(0, i2c0_devices, ARRAY_SIZE(i2c0_devices));
这样ADXL34X的i2c设备就被注册到了系统中,当名字与i2c_driver中的id_table中的成员匹配时就能够出发probe匹配函数了。
在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter、i2c_driver、i2c_client。三者的关系也在上一节进行了描述。应该已经算是对Linux I2C子系统有了初步的了解。下面再对他们之间的关系进行代码层的深入分析,我认为对他们的关系了解的越好,越有助于I2C设备的驱动开发及调试。
带着问题去分析可能会更有帮助吧,通过对(一)的了解后,可能会产生以下的几点疑问:
- i2c_adapter驱动如何添加?
- i2c_client与i2c_board_info究竟是什么关系?
I2C对外API
在解答问题前,不妨先缕顺一下Linux内核的I2C子系统对驱动模块的API有哪些。(来自https://www.kernel.org/doc/htmldocs/device-drivers/i2c.html)
// 对外数据结构
struct i2c_driver — 代表一个I2C设备驱动
struct i2c_client — 代表一个I2C从设备
struct i2c_board_info — 从设备创建的模版
I2C_BOARD_INFO — 创建I2C设备的宏,包含名字和地址
struct i2c_algorithm — 代表I2C传输方法
struct i2c_bus_recovery_info — I2C总线恢复信息?内核新加入的结构,不是很清楚。
//对外函数操作
module_i2c_driver — 注册I2C设备驱动的宏定义
i2c_register_board_info — 静态声明(注册)I2C设备,可多个
i2c_verify_client — 如果设备是i2c_client的dev成员则返回其父指针,否则返回NULL。用来校验设备是否为I2C设备
i2c_lock_adapter — I2C总线持锁操作,会找到最根源的那个i2c_adapter。说明你的模块必须符合GPL协议才可以使用这个接口。后边以GPL代表。
i2c_unlock_adapter — 上一个的反操作,GPL
i2c_new_device — 由i2c_board_info信息声明一个i2c设备(client),GPL
i2c_unregister_device — 上一个的反操作,GPL。
i2c_new_dummy — 声明一个名为dummy(指定地址)的I2C设备,GPL
i2c_verify_adapter — 验证是否是i2c_adapter
i2c_add_adapter — 声明I2C适配器,系统动态分配总线号。
i2c_add_numbered_adapter — 同样是声明I2C适配器,但是指定了总线号,GPL
i2c_del_adapter — 卸载I2C适配器
i2c_del_driver — 卸载I2C设备驱动
i2c_use_client — i2c_client引用数+1
i2c_release_client — i2c_client引用数-1
__i2c_transfer — 没有自动持锁(adapter lock)的I2C传输接口
i2c_transfer — 自动持锁的I2C传输接口
i2c_master_send — 单条消息发送
i2c_master_recv — 单条消息接收
i2c_smbus_read_byte — SMBus “receive byte” protocol
i2c_smbus_write_byte — SMBus “send byte” protocol
i2c_smbus_read_byte_data — SMBus “read byte” protocol
i2c_smbus_write_byte_data — SMBus “write byte” protocol
i2c_smbus_read_word_data — SMBus “read word” protocol
i2c_smbus_write_word_data — SMBus “write word” protocol
i2c_smbus_read_block_data — SMBus “block read” protocol
i2c_smbus_write_block_data — SMBus “block write” protocol
i2c_smbus_xfer — execute SMBus protocol operations
(一)中对几个基本的结构体和宏定义也有了大概的解释,相信结合I2C的理论基础不难理解。对以上一些I2C的API进行分类:
No. | Adapter | Driver | Device(client) | Transfer |
---|---|---|---|---|
1 | i2c_add_adapter | module_i2c_driver | i2c_register_board_info | __i2c_transfer |
2 | i2c_add_numbered_adapter | i2c_del_driver | i2c_new_device | i2c_transfer |
3 | i2c_del_adapter | i2c_new_dummy | i2c_master_send | |
4 | i2c_lock_adapter | i2c_verify_client | i2c_master_recv | |
5 | i2c_unlock_adapter | i2c_unregister_device | i2c_smbus_read_byte | |
6 | i2c_verify_adapter | i2c_use_client | i2c_smbus_write_byte | |
7 | i2c_release_client | i2c_smbus_read_byte_data | ||
8 | i2c_smbus_write_byte_data | |||
9 | i2c_smbus_read_word_data | |||
10 | i2c_smbus_write_word_data | |||
11 | i2c_smbus_read_block_data | |||
12 | i2c_smbus_write_block_data | |||
13 | i2c_smbus_xfer |
经过一个表格的整理,不难发现在Linux I2C子系统中,最重要的要数i2c_client,而最多样化的就是数据的传输。
为了更好的理解和衔接,我想也许倒着分析会更有帮助,而这里先暂且不讨论I2C传输过程中的细节。下边的顺序是由client到driver,再到adapter。
I2C client的注册
i2c_client即I2C设备的注册接口有三个:
i2c_register_board_info
i2c_new_device
i2c_new_dummy
而i2c_new_dummy在内部其实也就是将client的name指定为dummy后依旧执行的是i2c_new_device,所以就只分析前两个就可以了。首先看这两个函数的原型:
i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
busnum 通过总线号指定这个(些)设备属于哪个总线
info i2c设备的数组集合 i2c_board_info格式
len 数组个数ARRAY_SIZE(info)
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
adap 此设备所依附的I2C适配器指针
info 此设备描述,i2c_board_info格式,bus_num成员是被忽略的
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;
}
看完后相信都会产生个疑问?怎么将相关信息放到链表中就算完事了吗?不着急,来看下内核中已经给出的解释:
* Systems using the Linux I2C driver stack can declare tables of board info
* while they initialize. This should be done in board-specific init code
* near arch_initcall() time, or equivalent, before any I2C adapter driver is
* registered. For example, mainboard init code could define several devices,
* as could the init code for each daughtercard in a board stack.
*
* The I2C devices will be created later, after the adapter for the relevant
* bus has been registered. After that moment, standard driver model tools
* are used to bind "new style" I2C drivers to the devices. The bus number
* for any device declared using this routine is not available for dynamic
* allocation.
核心内容就是说关于集成的I2C设备注册过程应该在板级代码初始化期间,也就是arch_initcall前后的时间,或者就在这个时候(board-xxx-yyy.c中),切记切记!!!一定要在I2C适配器驱动注册前完成!!!为什么说是静态注册,是因为真实的I2C设备是在适配器成功注册后才被生成的。如果在I2C适配器注册完后还想要添加I2C设备的话,就要通过新方式!(即i2c_new_device)
小弟永远要挡在老大前边嘛!老大还没出来前,小弟们赶紧前排列阵好,老大到了可不等你,你列阵也没用了。而对于迟到的小弟,自己想办法追上去吧,在原地自己列阵是白费工夫了。
对于__i2c_board_list链表中的信息是如何变成实际的i2c设备信息的过程放在之后adapter注册过程的分析中。记得,重点是i2c_register_board_info方式一定要赶在I2C适配器的注册前,这样就没有问题。
i2c_new_device
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
client = kzalloc(sizeof *client, GFP_KERNEL); //为即将注册的client申请内存
if (!client)
return NULL;
client->adapter = adap; //绑定指定的adapter适配器
client->dev.platform_data = info->platform_data; //保存设备数据
if (info->archdata) //代码上看是DMA相关操作数据
client->dev.archdata = *info->archdata;
client->flags = info->flags; //类型,(一)中说过,或是10位地址,或是使用SMBus检错
client->addr = info->addr; //设备从地址
client->irq = info->irq; //设备终端
strlcpy(client->name, info->type, sizeof(client->name)); //从设备名
//瞧!(一)中说过i2c_board_info中的信息是与i2c_client有对应关系的,灵验了吧!
/* Check for address validity */
status = i2c_check_client_addr_validity(client); //检测地址是否有效,10位地址是否大于0x3ff,7位地址是否大于0x7f或为0
if (status) { //非零(实际上为-22,无效参数Invalid argument
dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
goto out_err_silent;
}
/* Check for address business */
status = i2c_check_addr_busy(adap, client->addr); //检测指定适配器上该地址状态
if (status)
goto out_err;
client->dev.parent = &client->adapter->dev; //建立从设备与适配器的父子关系
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
ACPI_HANDLE_SET(&client->dev, info->acpi_node.handle);
/* For 10-bit clients, add an arbitrary offset to avoid collisions */
dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
client->addr | ((client->flags & I2C_CLIENT_TEN)
? 0xa000 : 0)); //如果是10位地址设备,那名字格式与7bit的会有不同
status = device_register(&client->dev); //注册了!注册了!!!
if (status)
goto out_err;
dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));
return client;
out_err:
dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
"(%d)\n", client->name, client->addr, status);
out_err_silent:
kfree(client);
return NULL;
}
i2d_new_device没什么好多说的,由于有i2c_register_board_info的铺垫,相信也很好理解了。而i2c_new_device不但印证了i2c_client与i2c_board_info的对应关系,还顺便体现了10bit地址设备与7bit地址设备的略微不同。通过两者的对比,可以再总结出几点区别,从而更好的理解I2C设备的注册方法:
- i2c_register_board_info的形参需要的是总线号
- i2c_new_device的形参需要的直接是适配器的指针
我想这也正好能完美的说明两者的根本区别,对于板级设备更在乎适配器的总线号,因为这是固定的,没有异议的。而对于可插拔设备,因为其适配器可能非板级集成的,所以不能在乎其总线号,反而只要寻求其适配器指针进行绑定即可。后边adapter注册的分析能更好的证明这一点。
- i2c_register_board_info可以同时注册多个I2C设备
- i2c_new_device只能一次注册一个I2C设备
这也是其根本区别决定的,板级代码中常包含有许多I2C设备,所以i2c_register_board_info需要有同时注册多个I2C设备的能力也可以说是刚需。而i2c_new_device既然是用来给可插拔设备用的,想必设备数量并不多,而常可能只是一个两个而已,所以一次一个就够了,需求量并不大。
I2C driver
I2C设备驱动。Linux内核给出的接口只有两个,一个是注册,另一个就是卸载。在(一)也分析过module_i2c_driver这个宏定义,因为有它的存在,I2C设备驱动的开发可以不用在意你的I2C驱动需要如何注册以及如何卸载的,全部的精力都放在i2c_driver的完善上就可以了。
通过最开始的表单能明显察觉到,I2C子系统中I2C driver的开放接口最少,说白了就是需要驱动编写者完成完了i2c_driver放入module_i2c_driver宏中即可,而正因为如此,也恰恰说明,i2c_driver的灵活性是最高的。通常驱动会首先在意在用户空间的打开、关闭、读写等接口,但是对于i2c_driver来说,这些工作是I2C子系统已经做好的,关于常用的读写最终也是通过adapter实现的i2c_algorithm达到目的。好吧,再次说明了I2C子系统的完善程度,对于I2C设备及驱动开发来说是极其方便的。那么I2C驱动要实现什么呢?
再次回顾一下i2c_driver结构体,不过现在要剔除一些不常用的成员:
struct i2c_driver {
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 *); //从挂起状态恢复
struct device_driver driver; //I2C设备的驱动模型
const struct i2c_device_id *id_table; //匹配设备列表
...
};
如果有可能的话,我还想再精简一下:
struct i2c_driver {
int (*probe)(struct i2c_client *, const struct i2c_device_id *); //现行通用的与对应设备进行绑定的接口函数
int (*remove)(struct i2c_client *); //现行通用与对应设备进行解绑的接口函数
struct device_driver driver; //I2C设备的驱动模型
const struct i2c_device_id *id_table; //匹配设备列表
...
};
好了,精简到这种程度,为什么把电源管理相关也干掉了呢?实际上没有,通常实际的I2C驱动喜欢在drivers中完成这个动作(以mpu3050为例):
static UNIVERSAL_DEV_PM_OPS(mpu3050_pm, mpu3050_suspend, mpu3050_resume, NULL);
static const struct i2c_device_id mpu3050_ids[] = {
{ "mpu3050", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, mpu3050_ids);
static const struct of_device_id mpu3050_of_match[] = {
{ .compatible = "invn,mpu3050", },
{ },
};
MODULE_DEVICE_TABLE(of, mpu3050_of_match);
static struct i2c_driver mpu3050_i2c_driver = {
.driver = {
.name = "mpu3050",
.owner = THIS_MODULE,
.pm = &mpu3050_pm,
.of_match_table = mpu3050_of_match,
},
.probe = mpu3050_probe,
.remove = mpu3050_remove,
.id_table = mpu3050_ids,
};
module_i2c_driver(mpu3050_i2c_driver);
可以看到,实际驱动中喜欢将电源管理集成在i2c_driver的driver成员中。
UNIVERSAL_DEV_PM_OPS这个名字很犀利,貌似是“宇宙终极驱动电源管理大法”的样子:
#define UNIVERSAL_DEV_PM_OPS(name, suspend_fn, resume_fn, idle_fn) \
const struct dev_pm_ops name = { \
SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn) \
SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
}
#define SET_SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn) \
.suspend = suspend_fn, \
.resume = resume_fn, \
.freeze = suspend_fn, \
.thaw = resume_fn, \
.poweroff = suspend_fn, \
.restore = resume_fn,
#define SET_RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
.runtime_suspend = suspend_fn, \
.runtime_resume = resume_fn, \
.runtime_idle = idle_fn,
结合MPU3050的驱动将其完整展开可以得到:
static const struct dev_pm_ops mpu3050_pm = {
.suspend = mpu3050_suspend,
.resume = mpu3050_resume,
.freeze = mpu3050_suspend,
.thaw = mpu3050_resume,
.poweroff = mpu3050_suspend,
.restore = mpu3050_resume,
.runtime_suspend = mpu3050_suspend,
.runtime_resume = mpu3050_resume,
.runtime_idle = NULL,
}
对电源管理有兴趣的可以去查阅pm.h,其中对电源管理有详尽的说明了,这里不做分析。可以看到,在电源管理中,有很多成员实际上是一样的,在现实驱动中这样的情况也经常出现,所以会有“终极电源管理大法”宏的出现了。
of_match_table是OpenFirmware相关,在3.0(具体版本本人不清楚)kernel后对arm平台引入了Device Tree,可通过dts配置文件代替大量板级代码,有兴趣可自行查阅。
上边说过,i2c_driver的多样化最多,从mpu3050的驱动注册中也可以发现,其注重实现的为probe与电源管理,其中probe最为重要(好像是废话,哪个驱动这个都是最重要的-。-)。因为主要是从驱动的角度看待I2C子系统,所以这里不详尽分析mpu3050的代码,只以其为例说明I2C驱动大体框架。在mpu3050的probe主要对此传感器进行上电、工作模式初始化、注册INPUT子系统接口、关联中断处理程序(在中断处理线程中上报三轴参数)等工作。
关于I2C设备驱动的小总结
I2C设备驱动通常只是需要挂载在I2C总线(即依附于I2C子系统),I2C子系统对于设备驱动来说只是一个载体、基石。许多设备的主要核心是建立在其他子系统上,如重力传感器、三轴传感器、触摸屏等通常主要工作集中在INPUT子系统中,而相机模块、FM模块、GPS模块大多主要依附于V4L2子系统。这也能通过I2C设计理念证明,I2C的产生正是为了节省外围电路复杂度,让CPU使用有限的IO口挂载更多的外部模块。假设CPU的扩展IO口足够多,我想I2C也没什么必要存在了,毕竟直接操作IO口驱动设备比I2C来的更简单。
I2C adapter的注册
如上表所示,对于I2C adapter的注册有两种途径:i2c_add_adapter 或i2c_add_numbered_adapter,两者的区别是后者在注册时已经指定了此I2C适配器的总线号,而前者的总线号将由系统自动分配。
其各自的声明格式为:
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
在i2c_add_numberd_adapter使用前必须制定adap->nr,如果给-1,说明还是叫系统去自动生成总线号的。
使用场景
之所以区分开两种I2C adapter的注册方式,是因为他们的使用场景有所不同。
-
i2c_add_adapter的使用经常是用来注册那些可插拔设备,如USB PCI设备等。主板上的其他模块与其没有直接联系,说白了就是现有模块不在乎新加入的I2C适配器的总线号是多少,因为他们不需要。反而这个可插拔设备上的一些模块会需要其注册成功的适配器指针。回看一开始就分析的i2c_client,会发现不同场景的设备与其匹配的适配器有着这样的对应关系:
1. i2c_register_board_info需要指定已有的busnum,而i2c_add_numbered_adapter注册前已经指定总线号; 2. i2c_new_device需要指定adapter指针,而i2c_add_adapter注册成功后恰好这个指针就有了。
想象这样一个场景:新设备插入后,对应的驱动程序通过i2c_add_adapter注册自己的I2C适配器,然后根据与小弟们的协定将其是适配器指针存放在某处,相当于对小弟们(依附在其上的I2C设备)说:“看见没?你们注册你们自己的设备的时候就通过这个就能找到我,就能跟我混了!”然后驱动程序继续,当执行到对自己的I2C设备注册时候,小弟们去约定地点找老大留下的记号,发现有效信息后,一拥而上:“看!老大在那!!!”
- i2c_add_numbered_adapter用来注册CPU自带的I2C适配器,或是集成在主板上的I2C适配器。主板上的其他I2C从设备(client)在注册时候需要这个总线号。
通过简短的代码分析看一看他们的区别究竟如何,以及为什么静态注册的i2c_client必须要在adapter注册前(此处会精简部分代码,只留重要部分):
int i2c_add_adapter(struct i2c_adapter *adapter)
{
int id, res = 0;
res = idr_get_new_above(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, &id); //动态获取总线号
adapter->nr = id;
return i2c_register_adapter(adapter); //注册adapter
}
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
int id;
int status;
if (adap->nr == -1) /* -1 means dynamically assign bus id */
return i2c_add_adapter(adap);
status = i2c_register_adapter(adap);
return status;
}
可见,最终他们都是通过i2c_register_adapter注册适配器:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = 0;
/* Can't register until after driver model init */ //时序检查
if (unlikely(WARN_ON(!i2c_bus_type.p))) {
res = -EAGAIN;
goto out_list;
}
/* Sanity checks */
if (unlikely(adap->name[0] == '\0')) { //防御型代码,检查适配器名称
pr_err("i2c-core: Attempt to register an adapter with "
"no name!\n");
return -EINVAL;
}
if (unlikely(!adap->algo)) { //适配器是否已经完成了通信方法的实现
pr_err("i2c-core: Attempt to register adapter '%s' with "
"no algo!\n", adap->name);
return -EINVAL;
}
rt_mutex_init(&adap->bus_lock);
mutex_init(&adap->userspace_clients_lock);
INIT_LIST_HEAD(&adap->userspace_clients);
/* Set default timeout to 1 second if not already set */
if (adap->timeout == 0)
adap->timeout = HZ;
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev); //注册设备节点
if (res)
goto out_list;
/* create pre-declared device nodes */ //创建预-声明的I2C设备节点
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
//如果adapter的总线号小于动态分配的总线号的最小那个,说明是板级adapter。
//因为通过i2c_add_adapter加入的适配器所分配的总线号一定是比__i2c_first_dynamic_bus_num大的。
...
}
对于i2c_add_numbered_adapter来说会触发i2c_scan_static_board_info:
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) { //又见__i2c_board_list,这不是通过i2c_register_board_info组建起来的那个链表吗!
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info)) //找到总线号与刚注册的这个adapter相同的并通过i2c_new_device进行注册
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock); //释放读写锁
}
而i2c_board_info成员与i2c_client的对应动作也是在i2c_new_device中进行的,这一点在上边已经分析过了。看到这里,对adapter与client的微妙关系应该了解程度就比较深了,为什么说i2c_register_board_info与i2c_add_numbered_adapter对应而不是i2c_add_adapter也可以说得通。
那么,最终回答开篇提出的那两个问题:
- i2c_adapter驱动如何添加?
板级适配器(CPU自带、主板集成)要通过i2c_add_numbered_adapter注册,注册前要指定总线号,从0开始。假如板级I2C适配器注册了3个,那么第一个动态总线号一定是3,也就是说可插拔设备所带有的I2C适配器需要通过i2c_add_adapter进行注册,其总线号由系统指定。
- i2c_client与i2c_board_info究竟是什么关系?
i2c_client与i2c_board_info的对应关系在i2c_new_device中有完整体现。
i2c_client->dev.platform_data = i2c_board_info->platform_data;
i2c_client->dev.archdata = i2c_board_info->archdata;
i2c_client->flags = i2c_board_info->flags;
i2c_client->addr = i2c_board_info->addr;
i2c_client->irq = i2c_board_info->irq;
Linux I2C设备驱动编写(三)-实例分析AM3359
版权声明:本文为博主原创文章,未经博主允许不得转载。
TI-AM3359 I2C适配器实例分析
I2C Spec简述
特性:
- 兼容飞利浦I2C 2.1版本规格
- 支持标准模式(100K bits/s)和快速模式(400K bits/s)
- 多路接收、发送模式
- 支持7bit、10bit设备地址模式
- 32字节FIFO缓冲区
- 可编程时钟发生器
- 双DMA通道,一条中断线
- 三个I2C模块实例I2C0\I2C1\I2C2
- 时钟信号能够达到最高48MHz,来自PRCM
不支持
- SCCB协议
- 高速模式(3.4MBPS)
管脚
管脚 类型 描述 I2Cx_SCL I/OD I2C 串行时钟 I2Cx_SDA I/OD I2C 串行数据
I2C重置
- 通过系统重置PIRSTNA=0,所有寄存器都会被重置到上电状态
- 软重置,置位I2C_SYSC寄存器的SRST位。
- I2C_CON寄存器的I2C_EN位可以让I2C模块重置。当PIRSTNA=1,I2C_EN=0会让I2C模块功能部分重置,所有寄存器数据会被暂存(不会恢复上电状态)
数据有效性
- SDA在SCL高电平期间必须保持稳定,而只有在SCL低电平期间数据线(SDA)才可以进行高低电平切换
开始位&停止位
当I2C模块被设置为主控制时会产生START和STOP:
- START开始位是SCL高电平期间SDA HIGH->LOW
SCL _____ _______
\____/
SDA __
\____________
- STOP停止位是SCL高电平期间SDA LOW->HIGH
SCL _____ _______
\____/
SDA
___________
__/
- 在START信号后总线就会被认为是busy忙状态,而在STOP后其会被视为空闲状态
串行数据格式
8位数据格式,每个放在SDA线上的都是1个字节即8位长,总共有多少个字节要发送/接收是需要写在DCOUNT寄存器中的。数据是高位先传输,如果I2C模块处于接收模式中,那么一个应答位后跟着一个字节的数据。I2C模块支持两种数据格式:
- 7bit/10bit地址格式
- 带有多个开始位的7bit/10bit地址格式
![](https://img-blog.csdn.net/20140318150916218?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlyazAwMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
FIFO控制
I2C模块有两个内部的32字节FIFO,FIFO的深度可以通过控制I2C_IRQSTATUS_RAW.FIFODEPTH寄存器修改。
如何编程I2C
1. 使能模块前先设置
- 使分频器产生约12MHz的I2C模块时钟(设置I2C_PSC=x,x的值需要根据系统时钟频率进行计算)
- 使I2C时钟产生100Kpbs(Standard Mode)或400Kbps(Fast Mode)(SCLL = x 及 SCLH = x,这些值也是需要根据系统时钟频率进行计算)
- 如果是FS模式,则配置自己的地址(I2C_OA = x)
- 重置I2C模块(I2C_CON:I2C_EN=1)
2. 初始化程序
- 设置I2C工作模式寄存器(I2C_CON)
- 若想用传输数据中断则使能中断掩码(I2C_IRQENABLE_SET)
- 如果在FS模式中,使用DMA传输数据的话,使能DMA(I2C_BUF及I2C_DMA/RX/TX/ENABLE_SET)且配置DMA控制器
3. 设置从地址和数据计数器
在主动模式中,设置从地址(I2C_SA = x),设置传输需要的字节数(I2C_CNT = x)
4. 初始化一次传输
在FS模式中。查询一下I2C状态寄存器(I2C_IRQSTATUS_RAW)中总线状态(BB),如果是0则说明总线不是忙状态,设置START/STOP(I2C_CON:STT/STP)初始化一次传输。
5. 接收数据
检查I2C状态寄存器(I2C_IRQSTATUS_RAW)中代表接收数据是否准备好的中断位(RRDY),用这个RRDY中断(I2C_IRQENABLE_SET.RRDY_IE置位)或使用DMA_RX(I2C_BUF.RDMA_EN置位且I2C_DMARXENABLE_SET置位)去数据接收寄存器(I2C_DATA)中去读接收到的数据。
6. 发送数据
查询代表传输数据是否准备好的中断位(XRDY)(还是在状态寄存器I2C_IRQSTATUS_RAW中),用XRDY中断(I2C_IRQENABLE_SET.XRDY_IE置位)或DMA_TX(I2C_BUF.XDMA_EN与I2C_DMATXENABLE_SET置位)去将数据写入到I2C_DATA寄存器中。
I2C寄存器
由于寄存器众多,这里只将上述提到过的几个拿出来(不包含DMA相关)。
偏移量 寄存器名 概述 00h I2C_REVNB_LO 只读,存储着硬烧写的此模块的版本号 04h I2C_REVNB_HI 只读,存储功能和SCHEME信息 24h I2C_IRQSTATUS_RAW 读写,提供相关中断信息,是否使能等 2Ch I2C_IRQENABLE_SET 读写,使能中断 98h I2C_CNT 读写,设置I2C数据承载量(多少字节),在STT设1和接到ARDY间不能改动此寄存器 9Ch I2C_DATA 读写,8位,本地数据读写到FIFO寄存器 A4h I2C_CON 读写,在传输期间不要修改(STT为1到接收到ARDY间),I2C控制设置 A8h I2C_OA 读写,8位,传输期间不能修改。设置自身I2C地址7bit/10bit ACh I2C_SA 读写,10位,设置从地址7bit/10bit B0h I2C_PSC 读写,8位,分频器设置,使能I2C前可修改 B4h I2C_SCLL 读写,8位,使能I2C前可修改,占空比低电平时间 B8h I2C_SCLH 读写,8位,使能I2C前可修改,占空比高电平时间
适配器代码解读
在Linux内核驱动中,此适配器驱动存在于drivers/i2c/busses/i2c-omap.c。根据前几节对适配器i2c_adapter的理解,在写I2C适配器驱动时,主要集中在对传输、设备初始化、电源管理这几点。
平台设备注册
static struct platform_driver omap_i2c_driver = {
.probe = omap_i2c_probe,
.remove = omap_i2c_remove,
.driver = {
.name = "omap_i2c",
.owner = THIS_MODULE,
.pm = OMAP_I2C_PM_OPS,
.of_match_table = of_match_ptr(omap_i2c_of_match),
},
};
可以看到,此适配器的匹配是通过dts(Device Tree)进行匹配的,omap_i2c_of_match为:
static const struct of_device_id omap_i2c_of_match[] = {
{
.compatible = "ti,omap4-i2c",
.data = &omap4_pdata,
},
{
.compatible = "ti,omap3-i2c",
.data = &omap3_pdata,
},
{ },
};
通过在查阅相关dts,不难发现有这样的设备节点存在:
i2c0: i2c@44e0b000 {
compatible = "ti,omap4-i2c";
#address-cells = <1>;
#size-cells = <0>;
ti,hwmods = "i2c1"; /* TODO: Fix hwmod */
reg = <0x44e0b000 0x1000>;
interrupts = <70>;
status = "disabled";
};
i2c1: i2c@4802a000 {
compatible = "ti,omap4-i2c";
#address-cells = <1>;
#size-cells = <0>;
ti,hwmods = "i2c2"; /* TODO: Fix hwmod */
reg = <0x4802a000 0x1000>;
interrupts = <71>;
status = "disabled";
};
i2c2: i2c@4819c000 {
compatible = "ti,omap4-i2c";
#address-cells = <1>;
#size-cells = <0>;
ti,hwmods = "i2c3"; /* TODO: Fix hwmod */
reg = <0x4819c000 0x1000>;
interrupts = <30>;
status = "disabled";
};
通过查阅AM3359手册168页的内存映射表可以发现,这个dts所描述的3个I2C总线节点是与AM3359完全对应的,而名称(即compatible)也与驱动中所指定的列表项能够匹配。至于中断号的确定可通过手册的212页TABLE 6-1. ARM Cortex-A8 Interrupts得到,这里不再贴图,关于DTS的相关知识也非本问涉及,不做介绍。
![](https://img-blog.csdn.net/20140318151022656?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlyazAwMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20140318151035656?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlyazAwMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20140318151045390?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWlyazAwMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
下面重点分析此驱动的probe及电源管理。
匹配动作probe
由于DTS的存在,一旦内核检测到匹配的Device Tree节点就会触发probe匹配动作(因为DTS节省了对原本platform_device在板级代码中的存在)。由于probe函数内容较多,此处部分节选:
static int
omap_i2c_probe(struct platform_device *pdev)
{
struct omap_i2c_dev *dev;
struct i2c_adapter *adap;
struct resource *mem;
const struct omap_i2c_bus_platform_data *pdata =
pdev->dev.platform_data;
struct device_node *node = pdev->dev.of_node;
const struct of_device_id *match;
int irq;
int r;
u32 rev;
u16 minor, major, scheme;
struct pinctrl *pinctrl;
/* NOTE: driver uses the static register mapping */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); //对应DTS中reg
if (!mem) {
dev_err(&pdev->dev, "no mem resource?\n");
return -ENODEV;
}
irq = platform_get_irq(pdev, 0); //对应DTS中interrupts
if (irq < 0) {
dev_err(&pdev->dev, "no irq resource?\n");
return irq;
}
dev = devm_kzalloc(&pdev->dev, sizeof(struct omap_i2c_dev), GFP_KERNEL);
if (!dev) {
dev_err(&pdev->dev, "Menory allocation failed\n");
return -ENOMEM;
}
dev->base = devm_request_and_ioremap(&pdev->dev, mem); //做内存和IO映射
if (!dev->base) {
dev_err(&pdev->dev, "I2C region already claimed\n");
return -ENOMEM;
}
match = of_match_device(of_match_ptr(omap_i2c_of_match), &pdev->dev); //通过DTS进行匹配
if (match) {
u32 freq = 100000; /* default to 100000 Hz */
pdata = match->data;
dev->flags = pdata->flags;
of_property_read_u32(node, "clock-frequency", &freq);
/* convert DT freq value in Hz into kHz for speed */
dev->speed = freq / 1000; //若成功匹配则设置I2C总线适配器速度为clock-frequency的数值
} else if (pdata != NULL) {
dev->speed = pdata->clkrate; //若没匹配成功,而又有pdata(即通过传统方式注册platform_device)
dev->flags = pdata->flags;
dev->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat;
}
rev = __raw_readw(dev->base + 0x04); //读取I2C_REVNB_HI寄存器
/*
* #define OMAP_I2C_SCHEME(rev) ((rev & 0xc000) >> 14)
* 对应spec中描述:4244页,15-14位SCHEME,只读。
*/
scheme = OMAP_I2C_SCHEME(rev);
switch (scheme) {
case OMAP_I2C_SCHEME_0:
dev->regs = (u8 *)reg_map_ip_v1;
dev->rev = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG);
minor = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
major = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
break;
case OMAP_I2C_SCHEME_1:
/* FALLTHROUGH */
default:
dev->regs = (u8 *)reg_map_ip_v2;
rev = (rev << 16) |
omap_i2c_read_reg(dev, OMAP_I2C_IP_V2_REVNB_LO);
minor = OMAP_I2C_REV_SCHEME_1_MINOR(rev);
major = OMAP_I2C_REV_SCHEME_1_MAJOR(rev);
dev->rev = rev;
}
上述代码为版本判断,根据不同版本确定不同的寄存器地图。根据spec能够确定,实际AM3359的I2C总线适配器应该是OMAP_I2C_SCHEME_1类型,其寄存器地图为reg_map_ip_v2:
static const u8 reg_map_ip_v2[] = {
[OMAP_I2C_REV_REG] = 0x04,
[OMAP_I2C_IE_REG] = 0x2c,
[OMAP_I2C_STAT_REG] = 0x28,
[OMAP_I2C_IV_REG] = 0x34,
[OMAP_I2C_WE_REG] = 0x34,
[OMAP_I2C_SYSS_REG] = 0x90,
[OMAP_I2C_BUF_REG] = 0x94,
[OMAP_I2C_CNT_REG] = 0x98,
[OMAP_I2C_DATA_REG] = 0x9c,
[OMAP_I2C_SYSC_REG] = 0x10,
[OMAP_I2C_CON_REG] = 0xa4,
[OMAP_I2C_OA_REG] = 0xa8,
[OMAP_I2C_SA_REG] = 0xac,
[OMAP_I2C_PSC_REG] = 0xb0,
[OMAP_I2C_SCLL_REG] = 0xb4,
[OMAP_I2C_SCLH_REG] = 0xb8,
[OMAP_I2C_SYSTEST_REG] = 0xbC,
[OMAP_I2C_BUFSTAT_REG] = 0xc0,
[OMAP_I2C_IP_V2_REVNB_LO] = 0x00,
[OMAP_I2C_IP_V2_REVNB_HI] = 0x04,
[OMAP_I2C_IP_V2_IRQSTATUS_RAW] = 0x24,
[OMAP_I2C_IP_V2_IRQENABLE_SET] = 0x2c,
[OMAP_I2C_IP_V2_IRQENABLE_CLR] = 0x30,
};
与spec能够对应上。不过这个列表不是根据寄存器地址排序的,是根据:
enum {
OMAP_I2C_REV_REG = 0,
OMAP_I2C_IE_REG,
OMAP_I2C_STAT_REG,
OMAP_I2C_IV_REG,
OMAP_I2C_WE_REG,
OMAP_I2C_SYSS_REG,
OMAP_I2C_BUF_REG,
OMAP_I2C_CNT_REG,
OMAP_I2C_DATA_REG,
OMAP_I2C_SYSC_REG,
OMAP_I2C_CON_REG,
OMAP_I2C_OA_REG,
OMAP_I2C_SA_REG,
OMAP_I2C_PSC_REG,
OMAP_I2C_SCLL_REG,
OMAP_I2C_SCLH_REG,
OMAP_I2C_SYSTEST_REG,
OMAP_I2C_BUFSTAT_REG,
/* only on OMAP4430 */
OMAP_I2C_IP_V2_REVNB_LO,
OMAP_I2C_IP_V2_REVNB_HI,
OMAP_I2C_IP_V2_IRQSTATUS_RAW,
OMAP_I2C_IP_V2_IRQENABLE_SET,
OMAP_I2C_IP_V2_IRQENABLE_CLR,
};
共计23个寄存器。接下来是获取FIFO信息:
if (!(dev->flags & OMAP_I2C_FLAG_NO_FIFO)) {
u16 s;
/*
* OMAP_I2C_BUFSTAT_REG对应寄存器地图中的寄存器0xc0,即I2C_BUFSTAT寄存器。
* 其第14~15位代表FIFO大小:0x0-8字节,0x1-16字节,0x2-32字节,0x3-64字节,只读寄存器。
* 改变RX/TX FIFO可通过改写I2C_BUF 0x94寄存器
*/
s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3;
dev->fifo_size = 0x8 << s;
dev->fifo_size = (dev->fifo_size / 2); //折半是为了处理潜在事件
}
接下来是对I2C适配器的初始化:
/* reset ASAP, clearing any IRQs */ //尽快重置,清除所有中断位
omap_i2c_init(dev);
进入此函数后在对具体硬件操作前还进行了时钟的相关计算,由于代码比较冗长,这里直接根据实际情况提炼出部分代码进行分析:
static int omap_i2c_init(struct omap_i2c_dev *dev)
{
u16 psc = 0, scll = 0, sclh = 0;
u16 fsscll = 0, fssclh = 0, hsscll = 0, hssclh = 0;
unsigned long fclk_rate = 12000000; //12MHz
unsigned long internal_clk = 0;
struct clk *fclk;
if (!(dev->flags & OMAP_I2C_FLAG_SIMPLE_CLOCK)) {
//上边的代码中表示过,默认为100KHz。即标准模式,而此I2C适配器只能支持标准和快速,对于高速模式并不支持
internal_clk = 4000;
fclk = clk_get(dev->dev, "fck");
fclk_rate = clk_get_rate(fclk) / 1000;
clk_put(fclk);
/* Compute prescaler divisor */
psc = fclk_rate / internal_clk; //计算分频器系数,0~0xff表示1倍到256倍
psc = psc - 1;
/*
* SCLL为SCL低电平设置,持续时间tROW = (SCLL + 7) * ICLK,即SCLL = tROW / ICLK - 7
* SCLH为SCL高电平设置,持续时间tHIGH= (SCLH + 5) * ICLK,即SCLH = tHIGH/ ICLK - 5
*/
/* Standard mode */
fsscll = internal_clk / (dev->speed * 2) - 7;
fssclh = internal_clk / (dev->speed * 2) - 5;
scll = (hsscll << OMAP_I2C_SCLL_HSSCLL) | fsscll;
sclh = (hssclh << OMAP_I2C_SCLH_HSSCLH) | fssclh;
}
dev->iestate = (OMAP_I2C_IE_XRDY | OMAP_I2C_IE_RRDY |
OMAP_I2C_IE_ARDY | OMAP_I2C_IE_NACK |
OMAP_I2C_IE_AL) | ((dev->fifo_size) ?
(OMAP_I2C_IE_RDR | OMAP_I2C_IE_XDR) : 0); //设置传输数据相关中断位
dev->pscstate = psc;
dev->scllstate = scll;
dev->sclhstate = sclh;
__omap_i2c_init(dev);
return 0;
}
对一些最后的必要参数计算或匹配完后,通过最终的__omap_i2c_init(dev)进行最后的写入:
static void __omap_i2c_init(struct omap_i2c_dev *dev)
{
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0); //重置控制器
/* Setup clock prescaler to obtain approx 12MHz I2C module clock: */
omap_i2c_write_reg(dev, OMAP_I2C_PSC_REG, dev->pscstate); //设置分频器参数
/* SCL low and high time values */
omap_i2c_write_reg(dev, OMAP_I2C_SCLL_REG, dev->scllstate); //设置SCL高低电平参数
omap_i2c_write_reg(dev, OMAP_I2C_SCLH_REG, dev->sclhstate);
if (dev->rev >= OMAP_I2C_REV_ON_3430_3530)
omap_i2c_write_reg(dev, OMAP_I2C_WE_REG, dev->westate);
/* Take the I2C module out of reset: */
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, OMAP_I2C_CON_EN); //使能I2C适配器
/*
* Don't write to this register if the IE state is 0 as it can
* cause deadlock.
*/
if (dev->iestate)
omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate); //设置中断使能位
}
到这里硬件模块的初始化工作就全部完成了。接下来继续,包含了中断处理程序注册、适配器注册等。
r = devm_request_threaded_irq(&pdev->dev, dev->irq,
omap_i2c_isr, omap_i2c_isr_thread,
IRQF_NO_SUSPEND | IRQF_ONESHOT,
pdev->name, dev);
//申请中断,并安装相应的handle及中断工作线程(主要包含传输工作)
if (r) {
dev_err(dev->dev, "failure requesting irq %i\n", dev->irq);
goto err_unuse_clocks;
}
adap = &dev->adapter; //开始准备适配器的注册工作
i2c_set_adapdata(adap, dev); //之前设置、计算的那些参数不能丢掉,要保存在adapter的dev->p->driver_data中。
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON;
strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name));
adap->algo = &omap_i2c_algo; //此适配器的通讯算法
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
/* i2c device drivers may be active on return from add_adapter() */
adap->nr = pdev->id; //指定总线号
r = i2c_add_numbered_adapter(adap); //注册适配器
of_i2c_register_devices(adap); //注册在DTS中声明的I2C设备
至此此I2C适配器成功注册,属于他的I2C设备也即将通过注册。稍做休息,然后分析最最重要的adapter->algo成员。
static const struct i2c_algorithm omap_i2c_algo = {
.master_xfer = omap_i2c_xfer,
.functionality = omap_i2c_func,
};
先看简单的功能查询接口函数:
static u32
omap_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
I2C_FUNC_PROTOCOL_MANGLING;
}
支持I2C、支持仿真SMBUS但不支持快速协议、支持协议编码(自定义协议)。在分析master_xfer成员前先熟悉一下i2c_msg的数据结构:
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ //10bit从地址
#define I2C_M_RD 0x0001 /* read data, from slave to master */ //读数据
/*
* 相关资料 https://www.kernel.org/doc/Documentation/i2c/i2c-protocol
*/
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ //每个消息后都会带有一个STOP位
#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 */ //no ACK位会被视为ACK
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ //读消息时候,主设备的ACK/no ACK位会被忽略
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
- addr即从设备地址
- flags可以控制数据、协议格式等
- len代表消息产股的
- buf是指向所传输数据的指针
下面介绍AM3359 I2C适配器的传输机制:
static int
omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
int i;
int r;
r = pm_runtime_get_sync(dev->dev);
if (IS_ERR_VALUE(r))
goto out;
r = omap_i2c_wait_for_bb(dev); //通过读取寄存器I2C_IRQSTATUS的12位BB查询总线状态,等待总线空闲
if (r < 0)
goto out;
if (dev->set_mpu_wkup_lat != NULL)
dev->set_mpu_wkup_lat(dev->dev, dev->latency);
for (i = 0; i < num; i++) {
r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); //传输消息,最后一条消息接STOP位
if (r != 0)
break;
}
if (r == 0)
r = num;
omap_i2c_wait_for_bb(dev);
out:
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
return r;
}
omap_i2c_xfer_msg比较长,让我们慢慢分析:
static int omap_i2c_xfer_msg(struct i2c_adapter *adap,
struct i2c_msg *msg, int stop)
{
struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
unsigned long timeout;
u16 w;
dev_dbg(dev->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n",
msg->addr, msg->len, msg->flags, stop);
if (msg->len == 0) //无效长度检测
return -EINVAL;
dev->receiver = !!(msg->flags & I2C_M_RD); //判断是否为读取数据,若是则为receiver模式
omap_i2c_resize_fifo(dev, msg->len, dev->receiver); //根据所需发送/接收数据调整并清空对应FIFO,操作I2C_BUF寄存器0x94
//14位,清除接收FIFO,13~8位设置接收FIFO大小,最大64字节
//6位,清除发送FIFO,0~5位设置发送FIFO大小,最大64字节
omap_i2c_write_reg(dev, OMAP_I2C_SA_REG, msg->addr); //写入从地址
/* REVISIT: Could the STB bit of I2C_CON be used with probing? */
dev->buf = msg->buf; //组装消息
dev->buf_len = msg->len;
/* make sure writes to dev->buf_len are ordered */
barrier();
omap_i2c_write_reg(dev, OMAP_I2C_CNT_REG, dev->buf_len); //写入消息数量
/* Clear the FIFO Buffers */
w = omap_i2c_read_reg(dev, OMAP_I2C_BUF_REG);
w |= OMAP_I2C_BUF_RXFIF_CLR | OMAP_I2C_BUF_TXFIF_CLR;
omap_i2c_write_reg(dev, OMAP_I2C_BUF_REG, w); //依然是清除FIFO,在omap_i2c_resize_fifo中只清除了RX/TX之一,由dev->receiver决定
INIT_COMPLETION(dev->cmd_complete); //初始化等待量,是为中断处理线程准备的
dev->cmd_err = 0; //清空错误码
w = OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | OMAP_I2C_CON_STT; //使能I2C适配器,并设置master模式,产生开始位。即S-A-D
/* S开始位,A从地址,D数据,P停止位。在I2C适配器发送数据时的序列为:
* S-A-D-(n)-P
* 而即便是I2C适配器从从设备中读取数据,其协议头也是一样的,之后后续发生改变:
* S-A-D-S-A-D-P 关于读写方向,一包含在A中。所以无论是读还是写,第一个S-A-D都会有的。
*/
/* High speed configuration */
if (dev->speed > 400)
w |= OMAP_I2C_CON_OPMODE_HS;
if (msg->flags & I2C_M_STOP)
stop = 1;
if (msg->flags & I2C_M_TEN) //10bit从地址扩展
w |= OMAP_I2C_CON_XA;
if (!(msg->flags & I2C_M_RD))
w |= OMAP_I2C_CON_TRX; //设置是发送、接收模式
if (!dev->b_hw && stop) //在传输最后生成一个STOP位,若flags设置了I2C_M_STOP则每一个消息后都要跟一个STOP位(真的有这样的从设备需求)
w |= OMAP_I2C_CON_STP;
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w); //通过设置I2C_CON寄存器初始化一次传输,此处后进入中断程序
/*
* Don't write stt and stp together on some hardware.
*/
if (dev->b_hw && stop) {
unsigned long delay = jiffies + OMAP_I2C_TIMEOUT;
u16 con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
while (con & OMAP_I2C_CON_STT) {
con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
/* Let the user know if i2c is in a bad state */
if (time_after(jiffies, delay)) {
dev_err(dev->dev, "controller timed out "
"waiting for start condition to finish\n");
return -ETIMEDOUT;
}
cpu_relax();
}
w |= OMAP_I2C_CON_STP;
w &= ~OMAP_I2C_CON_STT;
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w); //写停止位
}
/*
* REVISIT: We should abort the transfer on signals, but the bus goes
* into arbitration and we're currently unable to recover from it.
*/
timeout = wait_for_completion_timeout(&dev->cmd_complete,
OMAP_I2C_TIMEOUT); //等待中断处理完成
if (timeout == 0) {
dev_err(dev->dev, "controller timed out\n");
omap_i2c_reset(dev);
__omap_i2c_init(dev);
return -ETIMEDOUT;
}
if (likely(!dev->cmd_err)) //下边是一些错误处理,错误码会在中断处理中出错的时候配置上
return 0;
/* We have an error */
if (dev->cmd_err & (OMAP_I2C_STAT_AL | OMAP_I2C_STAT_ROVR |
OMAP_I2C_STAT_XUDF)) {
omap_i2c_reset(dev);
__omap_i2c_init(dev);
return -EIO;
}
if (dev->cmd_err & OMAP_I2C_STAT_NACK) {
if (msg->flags & I2C_M_IGNORE_NAK)
return 0;
if (stop) {
w = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
w |= OMAP_I2C_CON_STP;
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);
}
return -EREMOTEIO;
}
return -EIO;
}
可见,这里只是对消息的发送、接收做了前期的初始化以及扫尾工作,关键在于中断如何处理:
static irqreturn_t
omap_i2c_isr(int irq, void *dev_id)
{
struct omap_i2c_dev *dev = dev_id;
irqreturn_t ret = IRQ_HANDLED;
u16 mask;
u16 stat;
spin_lock(&dev->lock);
mask = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
stat = omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG);
if (stat & mask) //检验中断是否有效,若有效则开启中断线程
ret = IRQ_WAKE_THREAD;
spin_unlock(&dev->lock);
return ret;
}
接下来进入I2C适配器的中断处理线程:
static irqreturn_t
omap_i2c_isr_thread(int this_irq, void *dev_id)
{
struct omap_i2c_dev *dev = dev_id;
unsigned long flags;
u16 bits;
u16 stat;
int err = 0, count = 0;
spin_lock_irqsave(&dev->lock, flags);
do {
bits = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
stat = omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG);
stat &= bits; //IRQ status和使能寄存器基本是一一对应的(除部分保留位)
/* If we're in receiver mode, ignore XDR/XRDY */ //根据不同模式自动忽略对应寄存器
if (dev->receiver)
stat &= ~(OMAP_I2C_STAT_XDR | OMAP_I2C_STAT_XRDY);
else
stat &= ~(OMAP_I2C_STAT_RDR | OMAP_I2C_STAT_RRDY);
if (!stat) {
/* my work here is done */
goto out;
} //过滤一圈下来发现白扯了~Orz
dev_dbg(dev->dev, "IRQ (ISR = 0x%04x)\n", stat);
if (count++ == 100) { //一次中断可能带有多个事件,如事件过多(100个)直接放弃……
dev_warn(dev->dev, "Too much work in one IRQ\n");
break;
}
if (stat & OMAP_I2C_STAT_NACK) { //收到NO ACK位
err |= OMAP_I2C_STAT_NACK;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_NACK); //记录错误码,清空此位
break;
}
if (stat & OMAP_I2C_STAT_AL) { //在发送模式中,丢失Arbitration后自动置位
dev_err(dev->dev, "Arbitration lost\n");
err |= OMAP_I2C_STAT_AL;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_AL);
break;
}
/*
* ProDB0017052: Clear ARDY bit twice
*/
if (stat & (OMAP_I2C_STAT_ARDY | OMAP_I2C_STAT_NACK |
OMAP_I2C_STAT_AL)) {
omap_i2c_ack_stat(dev, (OMAP_I2C_STAT_RRDY |
OMAP_I2C_STAT_RDR |
OMAP_I2C_STAT_XRDY |
OMAP_I2C_STAT_XDR |
OMAP_I2C_STAT_ARDY));
break;
}
//接收数据,不过我没太弄懂RDR和RRDY的关系,应该是一个是FIFO中的数据,一个不是。有高手请帮解读下,不胜感激。
if (stat & OMAP_I2C_STAT_RDR) { //RDR有效
u8 num_bytes = 1;
if (dev->fifo_size)
num_bytes = dev->buf_len;
omap_i2c_receive_data(dev, num_bytes, true); //从I2C_DATA寄存器中读取接收到的数据
if (dev->errata & I2C_OMAP_ERRATA_I207)
i2c_omap_errata_i207(dev, stat);
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_RDR);
continue;
}
if (stat & OMAP_I2C_STAT_RRDY) { //有新消息待读
u8 num_bytes = 1;
if (dev->threshold)
num_bytes = dev->threshold;
omap_i2c_receive_data(dev, num_bytes, false); //接收数据
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_RRDY);
continue;
}
//发送数据相关
if (stat & OMAP_I2C_STAT_XDR) {
u8 num_bytes = 1;
int ret;
if (dev->fifo_size)
num_bytes = dev->buf_len;
ret = omap_i2c_transmit_data(dev, num_bytes, true); //将数据写入I2C_DATA寄存器
if (ret < 0)
break;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XDR);
continue;
}
if (stat & OMAP_I2C_STAT_XRDY) {
u8 num_bytes = 1;
int ret;
if (dev->threshold)
num_bytes = dev->threshold;
ret = omap_i2c_transmit_data(dev, num_bytes, false);
if (ret < 0)
break;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XRDY);
continue;
}
if (stat & OMAP_I2C_STAT_ROVR) { //接收溢出
dev_err(dev->dev, "Receive overrun\n");
err |= OMAP_I2C_STAT_ROVR;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_ROVR);
break;
}
if (stat & OMAP_I2C_STAT_XUDF) { //发送溢出
dev_err(dev->dev, "Transmit underflow\n");
err |= OMAP_I2C_STAT_XUDF;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XUDF);
break;
}
} while (stat);
omap_i2c_complete_cmd(dev, err); //通知传输函数完成(可以写STOP位了),并带回错误码
out:
spin_unlock_irqrestore(&dev->lock, flags);
return IRQ_HANDLED;
}
到这里就分析完AM3359的I2C总线适配器的消息传输算法了。关于RDR/RRDY和XDR/XRDY的困惑之后我会去自己分辨,如果有了新的理解会及时更新。若有大牛路过,也希望对此给予指点一二。
总结:
通过对AM3359集成的I2C总线适配器的驱动分析,可以看到对于适配器驱动来说,需要包含一下几点:
- 电源管理
- 初始化(时钟、中断等参数设置)
- 消息传输算法实现
其中最复杂,也最重要的模块就是传输算法的实现,虽然模式中主要就是两种(master/slave),但是对中断状态的检测尤为重要,而且其中还要有必要的判错防御代码来保证在出现异常的情况下I2C适配器能够自矫正进而继续正常工作。
版权声明:本文为博主原创文章,未经博主允许不得转载。
TI-AM3359 I2C适配器实例分析
I2C Spec简述
特性:
- 兼容飞利浦I2C 2.1版本规格
- 支持标准模式(100K bits/s)和快速模式(400K bits/s)
- 多路接收、发送模式
- 支持7bit、10bit设备地址模式
- 32字节FIFO缓冲区
- 可编程时钟发生器
- 双DMA通道,一条中断线
- 三个I2C模块实例I2C0\I2C1\I2C2
- 时钟信号能够达到最高48MHz,来自PRCM
不支持
- SCCB协议
- 高速模式(3.4MBPS)
管脚
管脚 | 类型 | 描述 |
---|---|---|
I2Cx_SCL | I/OD | I2C 串行时钟 |
I2Cx_SDA | I/OD | I2C 串行数据 |
I2C重置
- 通过系统重置PIRSTNA=0,所有寄存器都会被重置到上电状态
- 软重置,置位I2C_SYSC寄存器的SRST位。
- I2C_CON寄存器的I2C_EN位可以让I2C模块重置。当PIRSTNA=1,I2C_EN=0会让I2C模块功能部分重置,所有寄存器数据会被暂存(不会恢复上电状态)
数据有效性
- SDA在SCL高电平期间必须保持稳定,而只有在SCL低电平期间数据线(SDA)才可以进行高低电平切换
开始位&停止位
当I2C模块被设置为主控制时会产生START和STOP:
- START开始位是SCL高电平期间SDA HIGH->LOW
- STOP停止位是SCL高电平期间SDA LOW->HIGH
- 在START信号后总线就会被认为是busy忙状态,而在STOP后其会被视为空闲状态
串行数据格式
8位数据格式,每个放在SDA线上的都是1个字节即8位长,总共有多少个字节要发送/接收是需要写在DCOUNT寄存器中的。数据是高位先传输,如果I2C模块处于接收模式中,那么一个应答位后跟着一个字节的数据。I2C模块支持两种数据格式:
- 7bit/10bit地址格式
- 带有多个开始位的7bit/10bit地址格式
FIFO控制
I2C模块有两个内部的32字节FIFO,FIFO的深度可以通过控制I2C_IRQSTATUS_RAW.FIFODEPTH寄存器修改。
如何编程I2C
1. 使能模块前先设置
- 使分频器产生约12MHz的I2C模块时钟(设置I2C_PSC=x,x的值需要根据系统时钟频率进行计算)
- 使I2C时钟产生100Kpbs(Standard Mode)或400Kbps(Fast Mode)(SCLL = x 及 SCLH = x,这些值也是需要根据系统时钟频率进行计算)
- 如果是FS模式,则配置自己的地址(I2C_OA = x)
- 重置I2C模块(I2C_CON:I2C_EN=1)
2. 初始化程序
- 设置I2C工作模式寄存器(I2C_CON)
- 若想用传输数据中断则使能中断掩码(I2C_IRQENABLE_SET)
- 如果在FS模式中,使用DMA传输数据的话,使能DMA(I2C_BUF及I2C_DMA/RX/TX/ENABLE_SET)且配置DMA控制器
3. 设置从地址和数据计数器
在主动模式中,设置从地址(I2C_SA = x),设置传输需要的字节数(I2C_CNT = x)
4. 初始化一次传输
在FS模式中。查询一下I2C状态寄存器(I2C_IRQSTATUS_RAW)中总线状态(BB),如果是0则说明总线不是忙状态,设置START/STOP(I2C_CON:STT/STP)初始化一次传输。
5. 接收数据
检查I2C状态寄存器(I2C_IRQSTATUS_RAW)中代表接收数据是否准备好的中断位(RRDY),用这个RRDY中断(I2C_IRQENABLE_SET.RRDY_IE置位)或使用DMA_RX(I2C_BUF.RDMA_EN置位且I2C_DMARXENABLE_SET置位)去数据接收寄存器(I2C_DATA)中去读接收到的数据。
6. 发送数据
查询代表传输数据是否准备好的中断位(XRDY)(还是在状态寄存器I2C_IRQSTATUS_RAW中),用XRDY中断(I2C_IRQENABLE_SET.XRDY_IE置位)或DMA_TX(I2C_BUF.XDMA_EN与I2C_DMATXENABLE_SET置位)去将数据写入到I2C_DATA寄存器中。
I2C寄存器
由于寄存器众多,这里只将上述提到过的几个拿出来(不包含DMA相关)。
偏移量 | 寄存器名 | 概述 |
---|---|---|
00h | I2C_REVNB_LO | 只读,存储着硬烧写的此模块的版本号 |
04h | I2C_REVNB_HI | 只读,存储功能和SCHEME信息 |
24h | I2C_IRQSTATUS_RAW | 读写,提供相关中断信息,是否使能等 |
2Ch | I2C_IRQENABLE_SET | 读写,使能中断 |
98h | I2C_CNT | 读写,设置I2C数据承载量(多少字节),在STT设1和接到ARDY间不能改动此寄存器 |
9Ch | I2C_DATA | 读写,8位,本地数据读写到FIFO寄存器 |
A4h | I2C_CON | 读写,在传输期间不要修改(STT为1到接收到ARDY间),I2C控制设置 |
A8h | I2C_OA | 读写,8位,传输期间不能修改。设置自身I2C地址7bit/10bit |
ACh | I2C_SA | 读写,10位,设置从地址7bit/10bit |
B0h | I2C_PSC | 读写,8位,分频器设置,使能I2C前可修改 |
B4h | I2C_SCLL | 读写,8位,使能I2C前可修改,占空比低电平时间 |
B8h | I2C_SCLH | 读写,8位,使能I2C前可修改,占空比高电平时间 |
适配器代码解读
在Linux内核驱动中,此适配器驱动存在于drivers/i2c/busses/i2c-omap.c。根据前几节对适配器i2c_adapter的理解,在写I2C适配器驱动时,主要集中在对传输、设备初始化、电源管理这几点。
平台设备注册
static struct platform_driver omap_i2c_driver = {
.probe = omap_i2c_probe,
.remove = omap_i2c_remove,
.driver = {
.name = "omap_i2c",
.owner = THIS_MODULE,
.pm = OMAP_I2C_PM_OPS,
.of_match_table = of_match_ptr(omap_i2c_of_match),
},
};
可以看到,此适配器的匹配是通过dts(Device Tree)进行匹配的,omap_i2c_of_match为:
static const struct of_device_id omap_i2c_of_match[] = {
{
.compatible = "ti,omap4-i2c",
.data = &omap4_pdata,
},
{
.compatible = "ti,omap3-i2c",
.data = &omap3_pdata,
},
{ },
};
通过在查阅相关dts,不难发现有这样的设备节点存在:
i2c0: i2c@44e0b000 {
compatible = "ti,omap4-i2c";
#address-cells = <1>;
#size-cells = <0>;
ti,hwmods = "i2c1"; /* TODO: Fix hwmod */
reg = <0x44e0b000 0x1000>;
interrupts = <70>;
status = "disabled";
};
i2c1: i2c@4802a000 {
compatible = "ti,omap4-i2c";
#address-cells = <1>;
#size-cells = <0>;
ti,hwmods = "i2c2"; /* TODO: Fix hwmod */
reg = <0x4802a000 0x1000>;
interrupts = <71>;
status = "disabled";
};
i2c2: i2c@4819c000 {
compatible = "ti,omap4-i2c";
#address-cells = <1>;
#size-cells = <0>;
ti,hwmods = "i2c3"; /* TODO: Fix hwmod */
reg = <0x4819c000 0x1000>;
interrupts = <30>;
status = "disabled";
};
通过查阅AM3359手册168页的内存映射表可以发现,这个dts所描述的3个I2C总线节点是与AM3359完全对应的,而名称(即compatible)也与驱动中所指定的列表项能够匹配。至于中断号的确定可通过手册的212页TABLE 6-1. ARM Cortex-A8 Interrupts得到,这里不再贴图,关于DTS的相关知识也非本问涉及,不做介绍。
下面重点分析此驱动的probe及电源管理。
匹配动作probe
由于DTS的存在,一旦内核检测到匹配的Device Tree节点就会触发probe匹配动作(因为DTS节省了对原本platform_device在板级代码中的存在)。由于probe函数内容较多,此处部分节选:
static int
omap_i2c_probe(struct platform_device *pdev)
{
struct omap_i2c_dev *dev;
struct i2c_adapter *adap;
struct resource *mem;
const struct omap_i2c_bus_platform_data *pdata =
pdev->dev.platform_data;
struct device_node *node = pdev->dev.of_node;
const struct of_device_id *match;
int irq;
int r;
u32 rev;
u16 minor, major, scheme;
struct pinctrl *pinctrl;
/* NOTE: driver uses the static register mapping */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); //对应DTS中reg
if (!mem) {
dev_err(&pdev->dev, "no mem resource?\n");
return -ENODEV;
}
irq = platform_get_irq(pdev, 0); //对应DTS中interrupts
if (irq < 0) {
dev_err(&pdev->dev, "no irq resource?\n");
return irq;
}
dev = devm_kzalloc(&pdev->dev, sizeof(struct omap_i2c_dev), GFP_KERNEL);
if (!dev) {
dev_err(&pdev->dev, "Menory allocation failed\n");
return -ENOMEM;
}
dev->base = devm_request_and_ioremap(&pdev->dev, mem); //做内存和IO映射
if (!dev->base) {
dev_err(&pdev->dev, "I2C region already claimed\n");
return -ENOMEM;
}
match = of_match_device(of_match_ptr(omap_i2c_of_match), &pdev->dev); //通过DTS进行匹配
if (match) {
u32 freq = 100000; /* default to 100000 Hz */
pdata = match->data;
dev->flags = pdata->flags;
of_property_read_u32(node, "clock-frequency", &freq);
/* convert DT freq value in Hz into kHz for speed */
dev->speed = freq / 1000; //若成功匹配则设置I2C总线适配器速度为clock-frequency的数值
} else if (pdata != NULL) {
dev->speed = pdata->clkrate; //若没匹配成功,而又有pdata(即通过传统方式注册platform_device)
dev->flags = pdata->flags;
dev->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat;
}
rev = __raw_readw(dev->base + 0x04); //读取I2C_REVNB_HI寄存器
/*
* #define OMAP_I2C_SCHEME(rev) ((rev & 0xc000) >> 14)
* 对应spec中描述:4244页,15-14位SCHEME,只读。
*/
scheme = OMAP_I2C_SCHEME(rev);
switch (scheme) {
case OMAP_I2C_SCHEME_0:
dev->regs = (u8 *)reg_map_ip_v1;
dev->rev = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG);
minor = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
major = OMAP_I2C_REV_SCHEME_0_MAJOR(dev->rev);
break;
case OMAP_I2C_SCHEME_1:
/* FALLTHROUGH */
default:
dev->regs = (u8 *)reg_map_ip_v2;
rev = (rev << 16) |
omap_i2c_read_reg(dev, OMAP_I2C_IP_V2_REVNB_LO);
minor = OMAP_I2C_REV_SCHEME_1_MINOR(rev);
major = OMAP_I2C_REV_SCHEME_1_MAJOR(rev);
dev->rev = rev;
}
上述代码为版本判断,根据不同版本确定不同的寄存器地图。根据spec能够确定,实际AM3359的I2C总线适配器应该是OMAP_I2C_SCHEME_1类型,其寄存器地图为reg_map_ip_v2:
static const u8 reg_map_ip_v2[] = {
[OMAP_I2C_REV_REG] = 0x04,
[OMAP_I2C_IE_REG] = 0x2c,
[OMAP_I2C_STAT_REG] = 0x28,
[OMAP_I2C_IV_REG] = 0x34,
[OMAP_I2C_WE_REG] = 0x34,
[OMAP_I2C_SYSS_REG] = 0x90,
[OMAP_I2C_BUF_REG] = 0x94,
[OMAP_I2C_CNT_REG] = 0x98,
[OMAP_I2C_DATA_REG] = 0x9c,
[OMAP_I2C_SYSC_REG] = 0x10,
[OMAP_I2C_CON_REG] = 0xa4,
[OMAP_I2C_OA_REG] = 0xa8,
[OMAP_I2C_SA_REG] = 0xac,
[OMAP_I2C_PSC_REG] = 0xb0,
[OMAP_I2C_SCLL_REG] = 0xb4,
[OMAP_I2C_SCLH_REG] = 0xb8,
[OMAP_I2C_SYSTEST_REG] = 0xbC,
[OMAP_I2C_BUFSTAT_REG] = 0xc0,
[OMAP_I2C_IP_V2_REVNB_LO] = 0x00,
[OMAP_I2C_IP_V2_REVNB_HI] = 0x04,
[OMAP_I2C_IP_V2_IRQSTATUS_RAW] = 0x24,
[OMAP_I2C_IP_V2_IRQENABLE_SET] = 0x2c,
[OMAP_I2C_IP_V2_IRQENABLE_CLR] = 0x30,
};
与spec能够对应上。不过这个列表不是根据寄存器地址排序的,是根据:
enum {
OMAP_I2C_REV_REG = 0,
OMAP_I2C_IE_REG,
OMAP_I2C_STAT_REG,
OMAP_I2C_IV_REG,
OMAP_I2C_WE_REG,
OMAP_I2C_SYSS_REG,
OMAP_I2C_BUF_REG,
OMAP_I2C_CNT_REG,
OMAP_I2C_DATA_REG,
OMAP_I2C_SYSC_REG,
OMAP_I2C_CON_REG,
OMAP_I2C_OA_REG,
OMAP_I2C_SA_REG,
OMAP_I2C_PSC_REG,
OMAP_I2C_SCLL_REG,
OMAP_I2C_SCLH_REG,
OMAP_I2C_SYSTEST_REG,
OMAP_I2C_BUFSTAT_REG,
/* only on OMAP4430 */
OMAP_I2C_IP_V2_REVNB_LO,
OMAP_I2C_IP_V2_REVNB_HI,
OMAP_I2C_IP_V2_IRQSTATUS_RAW,
OMAP_I2C_IP_V2_IRQENABLE_SET,
OMAP_I2C_IP_V2_IRQENABLE_CLR,
};
共计23个寄存器。接下来是获取FIFO信息:
if (!(dev->flags & OMAP_I2C_FLAG_NO_FIFO)) {
u16 s;
/*
* OMAP_I2C_BUFSTAT_REG对应寄存器地图中的寄存器0xc0,即I2C_BUFSTAT寄存器。
* 其第14~15位代表FIFO大小:0x0-8字节,0x1-16字节,0x2-32字节,0x3-64字节,只读寄存器。
* 改变RX/TX FIFO可通过改写I2C_BUF 0x94寄存器
*/
s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3;
dev->fifo_size = 0x8 << s;
dev->fifo_size = (dev->fifo_size / 2); //折半是为了处理潜在事件
}
接下来是对I2C适配器的初始化:
/* reset ASAP, clearing any IRQs */ //尽快重置,清除所有中断位
omap_i2c_init(dev);
进入此函数后在对具体硬件操作前还进行了时钟的相关计算,由于代码比较冗长,这里直接根据实际情况提炼出部分代码进行分析:
static int omap_i2c_init(struct omap_i2c_dev *dev)
{
u16 psc = 0, scll = 0, sclh = 0;
u16 fsscll = 0, fssclh = 0, hsscll = 0, hssclh = 0;
unsigned long fclk_rate = 12000000; //12MHz
unsigned long internal_clk = 0;
struct clk *fclk;
if (!(dev->flags & OMAP_I2C_FLAG_SIMPLE_CLOCK)) {
//上边的代码中表示过,默认为100KHz。即标准模式,而此I2C适配器只能支持标准和快速,对于高速模式并不支持
internal_clk = 4000;
fclk = clk_get(dev->dev, "fck");
fclk_rate = clk_get_rate(fclk) / 1000;
clk_put(fclk);
/* Compute prescaler divisor */
psc = fclk_rate / internal_clk; //计算分频器系数,0~0xff表示1倍到256倍
psc = psc - 1;
/*
* SCLL为SCL低电平设置,持续时间tROW = (SCLL + 7) * ICLK,即SCLL = tROW / ICLK - 7
* SCLH为SCL高电平设置,持续时间tHIGH= (SCLH + 5) * ICLK,即SCLH = tHIGH/ ICLK - 5
*/
/* Standard mode */
fsscll = internal_clk / (dev->speed * 2) - 7;
fssclh = internal_clk / (dev->speed * 2) - 5;
scll = (hsscll << OMAP_I2C_SCLL_HSSCLL) | fsscll;
sclh = (hssclh << OMAP_I2C_SCLH_HSSCLH) | fssclh;
}
dev->iestate = (OMAP_I2C_IE_XRDY | OMAP_I2C_IE_RRDY |
OMAP_I2C_IE_ARDY | OMAP_I2C_IE_NACK |
OMAP_I2C_IE_AL) | ((dev->fifo_size) ?
(OMAP_I2C_IE_RDR | OMAP_I2C_IE_XDR) : 0); //设置传输数据相关中断位
dev->pscstate = psc;
dev->scllstate = scll;
dev->sclhstate = sclh;
__omap_i2c_init(dev);
return 0;
}
对一些最后的必要参数计算或匹配完后,通过最终的__omap_i2c_init(dev)进行最后的写入:
static void __omap_i2c_init(struct omap_i2c_dev *dev)
{
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0); //重置控制器
/* Setup clock prescaler to obtain approx 12MHz I2C module clock: */
omap_i2c_write_reg(dev, OMAP_I2C_PSC_REG, dev->pscstate); //设置分频器参数
/* SCL low and high time values */
omap_i2c_write_reg(dev, OMAP_I2C_SCLL_REG, dev->scllstate); //设置SCL高低电平参数
omap_i2c_write_reg(dev, OMAP_I2C_SCLH_REG, dev->sclhstate);
if (dev->rev >= OMAP_I2C_REV_ON_3430_3530)
omap_i2c_write_reg(dev, OMAP_I2C_WE_REG, dev->westate);
/* Take the I2C module out of reset: */
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, OMAP_I2C_CON_EN); //使能I2C适配器
/*
* Don't write to this register if the IE state is 0 as it can
* cause deadlock.
*/
if (dev->iestate)
omap_i2c_write_reg(dev, OMAP_I2C_IE_REG, dev->iestate); //设置中断使能位
}
到这里硬件模块的初始化工作就全部完成了。接下来继续,包含了中断处理程序注册、适配器注册等。
r = devm_request_threaded_irq(&pdev->dev, dev->irq,
omap_i2c_isr, omap_i2c_isr_thread,
IRQF_NO_SUSPEND | IRQF_ONESHOT,
pdev->name, dev);
//申请中断,并安装相应的handle及中断工作线程(主要包含传输工作)
if (r) {
dev_err(dev->dev, "failure requesting irq %i\n", dev->irq);
goto err_unuse_clocks;
}
adap = &dev->adapter; //开始准备适配器的注册工作
i2c_set_adapdata(adap, dev); //之前设置、计算的那些参数不能丢掉,要保存在adapter的dev->p->driver_data中。
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON;
strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name));
adap->algo = &omap_i2c_algo; //此适配器的通讯算法
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
/* i2c device drivers may be active on return from add_adapter() */
adap->nr = pdev->id; //指定总线号
r = i2c_add_numbered_adapter(adap); //注册适配器
of_i2c_register_devices(adap); //注册在DTS中声明的I2C设备
至此此I2C适配器成功注册,属于他的I2C设备也即将通过注册。稍做休息,然后分析最最重要的adapter->algo成员。
static const struct i2c_algorithm omap_i2c_algo = {
.master_xfer = omap_i2c_xfer,
.functionality = omap_i2c_func,
};
先看简单的功能查询接口函数:
static u32
omap_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
I2C_FUNC_PROTOCOL_MANGLING;
}
支持I2C、支持仿真SMBUS但不支持快速协议、支持协议编码(自定义协议)。在分析master_xfer成员前先熟悉一下i2c_msg的数据结构:
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ //10bit从地址
#define I2C_M_RD 0x0001 /* read data, from slave to master */ //读数据
/*
* 相关资料 https://www.kernel.org/doc/Documentation/i2c/i2c-protocol
*/
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ //每个消息后都会带有一个STOP位
#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 */ //no ACK位会被视为ACK
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ //读消息时候,主设备的ACK/no ACK位会被忽略
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
- addr即从设备地址
- flags可以控制数据、协议格式等
- len代表消息产股的
- buf是指向所传输数据的指针
下面介绍AM3359 I2C适配器的传输机制:
static int
omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
int i;
int r;
r = pm_runtime_get_sync(dev->dev);
if (IS_ERR_VALUE(r))
goto out;
r = omap_i2c_wait_for_bb(dev); //通过读取寄存器I2C_IRQSTATUS的12位BB查询总线状态,等待总线空闲
if (r < 0)
goto out;
if (dev->set_mpu_wkup_lat != NULL)
dev->set_mpu_wkup_lat(dev->dev, dev->latency);
for (i = 0; i < num; i++) {
r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); //传输消息,最后一条消息接STOP位
if (r != 0)
break;
}
if (r == 0)
r = num;
omap_i2c_wait_for_bb(dev);
out:
pm_runtime_mark_last_busy(dev->dev);
pm_runtime_put_autosuspend(dev->dev);
return r;
}
omap_i2c_xfer_msg比较长,让我们慢慢分析:
static int omap_i2c_xfer_msg(struct i2c_adapter *adap,
struct i2c_msg *msg, int stop)
{
struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
unsigned long timeout;
u16 w;
dev_dbg(dev->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n",
msg->addr, msg->len, msg->flags, stop);
if (msg->len == 0) //无效长度检测
return -EINVAL;
dev->receiver = !!(msg->flags & I2C_M_RD); //判断是否为读取数据,若是则为receiver模式
omap_i2c_resize_fifo(dev, msg->len, dev->receiver); //根据所需发送/接收数据调整并清空对应FIFO,操作I2C_BUF寄存器0x94
//14位,清除接收FIFO,13~8位设置接收FIFO大小,最大64字节
//6位,清除发送FIFO,0~5位设置发送FIFO大小,最大64字节
omap_i2c_write_reg(dev, OMAP_I2C_SA_REG, msg->addr); //写入从地址
/* REVISIT: Could the STB bit of I2C_CON be used with probing? */
dev->buf = msg->buf; //组装消息
dev->buf_len = msg->len;
/* make sure writes to dev->buf_len are ordered */
barrier();
omap_i2c_write_reg(dev, OMAP_I2C_CNT_REG, dev->buf_len); //写入消息数量
/* Clear the FIFO Buffers */
w = omap_i2c_read_reg(dev, OMAP_I2C_BUF_REG);
w |= OMAP_I2C_BUF_RXFIF_CLR | OMAP_I2C_BUF_TXFIF_CLR;
omap_i2c_write_reg(dev, OMAP_I2C_BUF_REG, w); //依然是清除FIFO,在omap_i2c_resize_fifo中只清除了RX/TX之一,由dev->receiver决定
INIT_COMPLETION(dev->cmd_complete); //初始化等待量,是为中断处理线程准备的
dev->cmd_err = 0; //清空错误码
w = OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | OMAP_I2C_CON_STT; //使能I2C适配器,并设置master模式,产生开始位。即S-A-D
/* S开始位,A从地址,D数据,P停止位。在I2C适配器发送数据时的序列为:
* S-A-D-(n)-P
* 而即便是I2C适配器从从设备中读取数据,其协议头也是一样的,之后后续发生改变:
* S-A-D-S-A-D-P 关于读写方向,一包含在A中。所以无论是读还是写,第一个S-A-D都会有的。
*/
/* High speed configuration */
if (dev->speed > 400)
w |= OMAP_I2C_CON_OPMODE_HS;
if (msg->flags & I2C_M_STOP)
stop = 1;
if (msg->flags & I2C_M_TEN) //10bit从地址扩展
w |= OMAP_I2C_CON_XA;
if (!(msg->flags & I2C_M_RD))
w |= OMAP_I2C_CON_TRX; //设置是发送、接收模式
if (!dev->b_hw && stop) //在传输最后生成一个STOP位,若flags设置了I2C_M_STOP则每一个消息后都要跟一个STOP位(真的有这样的从设备需求)
w |= OMAP_I2C_CON_STP;
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w); //通过设置I2C_CON寄存器初始化一次传输,此处后进入中断程序
/*
* Don't write stt and stp together on some hardware.
*/
if (dev->b_hw && stop) {
unsigned long delay = jiffies + OMAP_I2C_TIMEOUT;
u16 con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
while (con & OMAP_I2C_CON_STT) {
con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
/* Let the user know if i2c is in a bad state */
if (time_after(jiffies, delay)) {
dev_err(dev->dev, "controller timed out "
"waiting for start condition to finish\n");
return -ETIMEDOUT;
}
cpu_relax();
}
w |= OMAP_I2C_CON_STP;
w &= ~OMAP_I2C_CON_STT;
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w); //写停止位
}
/*
* REVISIT: We should abort the transfer on signals, but the bus goes
* into arbitration and we're currently unable to recover from it.
*/
timeout = wait_for_completion_timeout(&dev->cmd_complete,
OMAP_I2C_TIMEOUT); //等待中断处理完成
if (timeout == 0) {
dev_err(dev->dev, "controller timed out\n");
omap_i2c_reset(dev);
__omap_i2c_init(dev);
return -ETIMEDOUT;
}
if (likely(!dev->cmd_err)) //下边是一些错误处理,错误码会在中断处理中出错的时候配置上
return 0;
/* We have an error */
if (dev->cmd_err & (OMAP_I2C_STAT_AL | OMAP_I2C_STAT_ROVR |
OMAP_I2C_STAT_XUDF)) {
omap_i2c_reset(dev);
__omap_i2c_init(dev);
return -EIO;
}
if (dev->cmd_err & OMAP_I2C_STAT_NACK) {
if (msg->flags & I2C_M_IGNORE_NAK)
return 0;
if (stop) {
w = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG);
w |= OMAP_I2C_CON_STP;
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w);
}
return -EREMOTEIO;
}
return -EIO;
}
可见,这里只是对消息的发送、接收做了前期的初始化以及扫尾工作,关键在于中断如何处理:
static irqreturn_t
omap_i2c_isr(int irq, void *dev_id)
{
struct omap_i2c_dev *dev = dev_id;
irqreturn_t ret = IRQ_HANDLED;
u16 mask;
u16 stat;
spin_lock(&dev->lock);
mask = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
stat = omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG);
if (stat & mask) //检验中断是否有效,若有效则开启中断线程
ret = IRQ_WAKE_THREAD;
spin_unlock(&dev->lock);
return ret;
}
接下来进入I2C适配器的中断处理线程:
static irqreturn_t
omap_i2c_isr_thread(int this_irq, void *dev_id)
{
struct omap_i2c_dev *dev = dev_id;
unsigned long flags;
u16 bits;
u16 stat;
int err = 0, count = 0;
spin_lock_irqsave(&dev->lock, flags);
do {
bits = omap_i2c_read_reg(dev, OMAP_I2C_IE_REG);
stat = omap_i2c_read_reg(dev, OMAP_I2C_STAT_REG);
stat &= bits; //IRQ status和使能寄存器基本是一一对应的(除部分保留位)
/* If we're in receiver mode, ignore XDR/XRDY */ //根据不同模式自动忽略对应寄存器
if (dev->receiver)
stat &= ~(OMAP_I2C_STAT_XDR | OMAP_I2C_STAT_XRDY);
else
stat &= ~(OMAP_I2C_STAT_RDR | OMAP_I2C_STAT_RRDY);
if (!stat) {
/* my work here is done */
goto out;
} //过滤一圈下来发现白扯了~Orz
dev_dbg(dev->dev, "IRQ (ISR = 0x%04x)\n", stat);
if (count++ == 100) { //一次中断可能带有多个事件,如事件过多(100个)直接放弃……
dev_warn(dev->dev, "Too much work in one IRQ\n");
break;
}
if (stat & OMAP_I2C_STAT_NACK) { //收到NO ACK位
err |= OMAP_I2C_STAT_NACK;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_NACK); //记录错误码,清空此位
break;
}
if (stat & OMAP_I2C_STAT_AL) { //在发送模式中,丢失Arbitration后自动置位
dev_err(dev->dev, "Arbitration lost\n");
err |= OMAP_I2C_STAT_AL;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_AL);
break;
}
/*
* ProDB0017052: Clear ARDY bit twice
*/
if (stat & (OMAP_I2C_STAT_ARDY | OMAP_I2C_STAT_NACK |
OMAP_I2C_STAT_AL)) {
omap_i2c_ack_stat(dev, (OMAP_I2C_STAT_RRDY |
OMAP_I2C_STAT_RDR |
OMAP_I2C_STAT_XRDY |
OMAP_I2C_STAT_XDR |
OMAP_I2C_STAT_ARDY));
break;
}
//接收数据,不过我没太弄懂RDR和RRDY的关系,应该是一个是FIFO中的数据,一个不是。有高手请帮解读下,不胜感激。
if (stat & OMAP_I2C_STAT_RDR) { //RDR有效
u8 num_bytes = 1;
if (dev->fifo_size)
num_bytes = dev->buf_len;
omap_i2c_receive_data(dev, num_bytes, true); //从I2C_DATA寄存器中读取接收到的数据
if (dev->errata & I2C_OMAP_ERRATA_I207)
i2c_omap_errata_i207(dev, stat);
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_RDR);
continue;
}
if (stat & OMAP_I2C_STAT_RRDY) { //有新消息待读
u8 num_bytes = 1;
if (dev->threshold)
num_bytes = dev->threshold;
omap_i2c_receive_data(dev, num_bytes, false); //接收数据
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_RRDY);
continue;
}
//发送数据相关
if (stat & OMAP_I2C_STAT_XDR) {
u8 num_bytes = 1;
int ret;
if (dev->fifo_size)
num_bytes = dev->buf_len;
ret = omap_i2c_transmit_data(dev, num_bytes, true); //将数据写入I2C_DATA寄存器
if (ret < 0)
break;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XDR);
continue;
}
if (stat & OMAP_I2C_STAT_XRDY) {
u8 num_bytes = 1;
int ret;
if (dev->threshold)
num_bytes = dev->threshold;
ret = omap_i2c_transmit_data(dev, num_bytes, false);
if (ret < 0)
break;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XRDY);
continue;
}
if (stat & OMAP_I2C_STAT_ROVR) { //接收溢出
dev_err(dev->dev, "Receive overrun\n");
err |= OMAP_I2C_STAT_ROVR;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_ROVR);
break;
}
if (stat & OMAP_I2C_STAT_XUDF) { //发送溢出
dev_err(dev->dev, "Transmit underflow\n");
err |= OMAP_I2C_STAT_XUDF;
omap_i2c_ack_stat(dev, OMAP_I2C_STAT_XUDF);
break;
}
} while (stat);
omap_i2c_complete_cmd(dev, err); //通知传输函数完成(可以写STOP位了),并带回错误码
out:
spin_unlock_irqrestore(&dev->lock, flags);
return IRQ_HANDLED;
}
到这里就分析完AM3359的I2C总线适配器的消息传输算法了。关于RDR/RRDY和XDR/XRDY的困惑之后我会去自己分辨,如果有了新的理解会及时更新。若有大牛路过,也希望对此给予指点一二。
总结:
通过对AM3359集成的I2C总线适配器的驱动分析,可以看到对于适配器驱动来说,需要包含一下几点:
- 电源管理
- 初始化(时钟、中断等参数设置)
- 消息传输算法实现
其中最复杂,也最重要的模块就是传输算法的实现,虽然模式中主要就是两种(master/slave),但是对中断状态的检测尤为重要,而且其中还要有必要的判错防御代码来保证在出现异常的情况下I2C适配器能够自矫正进而继续正常工作。