人生十有八九不如意,在最黑暗的日子里,依然寄希望于奋笔疾书减轻心中的苦闷。缘起缘灭,上天注定,只需要静静的看着天意的安排就好了。看到一部微电影,故事情节挺老套的,但是结尾却给我留下了深刻印象。故事的开始,女主角过马路,差点被一辆车撞倒,男二号从车上下来,询问女主角是否受伤,还递给她一张名片,然后开车离开了。这时候,女主角手腕上的感应朱砂亮了,她坚信男二号就是她苦苦追寻的前世恋人,因为她没有忘记前世的记忆。女主角疯狂的追求男二号,男二号对她并没有好感。这时候男主角出现,他对女主角一见钟情,但是女主角从来没有把他放在心上。故事就这样在三角恋里纠缠,在危机关头女主角替男主角当了一刀,男主角抱着她的时候,她手腕上的感应朱砂再次亮了,原来她一直追寻的前世恋人是男主角。谜底揭晓了,原来第一次朱砂亮起的时候,男主角也在现场,他藏在男二号的车尾后备箱里,准备杀死男二号,因为男二号是男主角的杀父仇人的儿子。那么问题来了,现实中会不会出现像剧中的女主角那样,错把过往记忆里的人当作前世注定的情缘不肯放手,错过真正的前世情缘呢?不扯闲话了,开始进入正题。
Linux系统中i2c驱动分成三个部分:i2c-core,i2c-bus和i2c-dev。i2c核心是i2c总线驱动和i2c设备驱动的中间枢纽,它以通用的、与平台无关的接口实现了i2c中设备与适配器的沟通。i2c总线驱动填充i2c_adapter和i2c_algorithm结构体。i2c设备驱动填充i2c_driver和i2c_client结构体。i2c核心有操作系统实现,i2c设备驱动在以前的文档中已经详细介绍,本文档重点关注i2c总线驱动的实现。
Linux2.6版本以后,系统使用platform总线模式实现外设驱动,i2c总线驱动也遵循这种设计模式。Platform总线模式的优点:设备驱动中引入platform 概念,隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体匹配信息,而在驱动中,只需要通过API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
一、 platform总线架构
Linux实现了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动称为 platform_driver。Platform总线的初始化有内核实现,platform设备和platform驱动需要自己注册到platform总线上。针对i2c总线驱动,我们把i2c总线驱动作为一个设备,然后遵循platform设计模式注册到platform总线上。基于Platform总线的驱动开发流程如下:(1)定义platform device;(2)注册platform device;(3)定义platform driver;(4)注册platform driver。
1、 定义platform device
Linux内核在\include\linux\platform_device.h文件中定义了
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
2、注册platform device
Imx6在\arch\arm\mach-mx6\board-mx6q_sabresd.c文件中调用imx6q_add_imx_i2c(2, &mx6q_sabresd_i2c_data);函数实现平台设备注册,该函数中调用
Imx6在\arch\arm\plat-mxc\devices\platform-imx-i2c.c文件中的如下函数实现真正的注册功能。
struct platform_device *__init imx_add_imx_i2c(const struct imx_imx_i2c_data *data,
const struct imxi2c_platform_data *pdata)
{
struct resource res[] = {
{
.start = data->iobase,
.end = data->iobase + data->iosize - 1,
.flags = IORESOURCE_MEM,
},
{
.start = data->irq,
.end = data->irq,
.flags = IORESOURCE_IRQ,
},
};
return imx_add_platform_device("imx-i2c", data->id,res, ARRAY_SIZE(res),pdata, sizeof(*pdata));
}
imx_add_platform_device()函数最终调用platform_device_add()函数实现平台设备注册,i2c总线设备名字为”imx-i2c”。
3、定义platform driver
Linux内核在\include\linux\platform_device.h文件中定义了
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
Imx6在\drivers\i2c\busses\i2c-imx.c文件中对该结构体的部分成员进行了初始化
static struct platform_driver i2c_imx_driver = {
.remove = __exit_p(i2c_imx_remove),
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
}
};
I2c总线驱动的名字为”imx-i2c”。
4、注册platform driver
Imx6在\drivers\i2c\busses\i2c-imx.c文件中调用platform_driver_probe(&i2c_imx_driver, i2c_imx_probe);函数实现平台驱动的注册。当struct platform_device结构体中的name和struct platform_driver结构体中的struct device_driver结构体里的name名称一致时,注册的i2c_imx_probe探测函数开始运行,从此处开始进入i2c总线的初始化和配置。
二、 I2c总线驱动
1、i2c总线结构体
I2c总线驱动主要填充i2c_adapter和i2c_algorithm结构体,在\include\linux\i2c.h文件中定义了这两个结构体:
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_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
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);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
Imx6在\drivers\i2c\busses\i2c-imx.c文件中实现了i2c总线驱动,不过对这两个结构体的实现进行了进一步的封装,变成了填充如下的结构体:
struct imx_i2c_struct {
struct i2c_adapter adapter;
struct resource *res;
struct clk *clk;
void __iomem *base;
int irq;
wait_queue_head_t queue;
unsigned long i2csr;
unsigned int disable_delay;
int stopped;
unsigned int ifdr; /* IMX_I2C_IFDR */
unsigned int cur_clk;
};
2、 i2c总线结构体实现
(1) 注册i2c平台驱动
在注册平台驱动部分提到,当平台设备中的名称和平台驱动中的名称一致时,函数platform_driver_probe(&i2c_imx_driver, i2c_imx_probe);中的i2c_imx_probe()函数将会被调用,从此开始i2c总线的配置。
(2) I2c总线的配置
I2c总线的配置操作在i2c_imx_probe()函数中实现,由于函数体的内容太多,这里只概括大致的功能:
① 把i2c寄存器所在的物理地址映射到虚拟地址,便与在系统中操作i2c寄存器;
② 填充struct imx_i2c_struct结构体,重点关注其中的struct i2c_algorithm结构体的具体实现,因为它实现了i2c总线读写操作的时序,后面在详细说明该过程;
③ 注册i2c中断事件,调用i2c_imx_isr()函数完成中断处理,后面详细说明该过程;
④ 设置i2c的总线速率;
⑤ I2c总线适配器注册到内核中,调用i2c_add_numbered_adapter(&i2c_imx->adapter);
(3) I2c总线操作
I2c总线操作在struct i2c_algorithm结构体中实现,imx6中通过注册
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
};
I2c总线的读写操作通过i2c_imx_xfer()函数实现,该函数实现了主机收发的操作,对于从机接收操作在i2c中断函数中处理。I2c总线使用中断方式收发数据,该函数中使用了内核等待队列机制实现i2c状态位的等待wait_event_timeout(i2c_imx->queue, i2c_imx->i2csr & I2SR_IIF, HZ / 10);。
(4) I2c中断
I2c中断函数读取中断标志位,然后清除中断,通过wake_up(&i2c_imx->queue);函数唤醒i2c总线操作时调用的等待队列。
三、 i2c总线从机操作
上述主要描述了i2c做主机时的操作流程,这节描述i2c做从机接收的操作流程。I2c总线空闲时默认配置成从机模式,当i2c做主机时才配置成主模式。Imx6中i2c3实现了从机接收的功能。
(1) Imx6在\drivers\i2c\busses\i2c-imx.c文件i2c_imx_probe()函数中把i2c3默认配置成从机模式,在i2c_imx_xfer()函数中当需要主发送时把i2c3配成主模式,发送完成后设置成从机模式;使用init_waitqueue_head()初始化内核等待队列,与poll、select结合使用,上报从机接收到完整数据事件给应用层;使用kfifo_alloc创建环形队列,用于存储i2c3从机接收到的数据;使用hrtimer_init()创建10us定时器,检测i2c3从机是否接收完一帧数据,如果接收完一帧数据就使用kfifo_in()把接收到的数据拷贝到环形队列中,并使用wake_up_interruptible()唤醒刚才的等待队列,供应用层读取数据;
(2) 在中断函数i2c_imx_isr()里对中断事件类型进行判断,如果i2c3是从机接收数据事件,把接收到的数据放在接收缓存区中;
四、 i2c设备驱动
imx6的i2c3设备驱动在\drivers\ght\i2c_slave.c文件中,下面介绍设备注册步骤:
(1) i2c3设备板信息注册
Imx6在\arch\arm\mach-mx6\board-mx6q_sabresd.c文件中调用i2c_register_board_info(2,mxc_i2c2_board_info,ARRAY_SIZE(mxc_i2c2_board_info));函数注册设备板信息,板信息结构体定义如下:
static struct i2c_board_info mxc_i2c2_board_info[] __initdata = {
{
I2C_BOARD_INFO("i2c_slave", 0x10),
},
};
(2) i2c3设备驱动信息注册
在\drivers\ght\i2c_slave.c文件模块初始化函数i2c_slave_init()中调用i2c_add_driver(&i2c_slave_i2c_driver);函数实现i2c3设备驱动信息注册,驱动信息结构体定义如下:
static struct i2c_driver i2c_slave_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "i2c_slave",
},
.probe = i2c_slave_probe,
.remove = i2c_slave_remove,
.id_table = i2c_slave_id,
};
当板信息和设备驱动名称匹配时,即两个名称都是”i2c_slave”时,i2c_slave_probe()函数被调用,在该函数中只是获取了i2c3设备的设备指针。
(3) i2c3设备操作接口注册
在\drivers\ght\i2c_slave.c文件模块初始化函数i2c_slave_init()中调用misc_register(&i2c_slave_miscdev);函数实现i2c3操作接口注册,该函数注册了一个杂项设备,其中定义了杂项设备的具体实现如下:
static struct miscdevice i2c_slave_miscdev = {
.minor = I2C_SLAVE_MINOR,
.name = "i2c_slave",
.fops = &i2c_slave_fops,
};
具体的操作接口有i2c_slave_fops结构体实现,它的实现如下:
static const struct file_operations i2c_slave_fops = {
.owner = THIS_MODULE,
.llseek = NULL,//no_llseek,
.write = i2c_slave_write,
.read = i2c_slave_read,
.poll = i2c_slave_poll,
.unlocked_ioctl = NULL,
};
(4) i2c3设备操作接口解释
i2c_slave_write()函数实现i2c3设备主写功能,i2c_slave_read()函数实现i2c3设备从机模式读数据功能,数据是从kfifo环形队列中读出的,i2c_slave_poll函数实现阻塞模式读数据功能。
当应用层调用select()函数查看i2c3设备句柄是否有数据需要读时,会调用到驱动的i2c_slave_poll()函数,在该函数中我们注册了内核等待队列并处于睡眠状态,这样select()函数调用后会阻塞。当定时器检测到i2c3从机接收到完整的一帧数据时,会唤醒等待队列,这样select()会退出阻塞状态,然后调用read()函数读出数据。