linux下I2C总线驱动架构分析

        人生十有八九不如意,在最黑暗的日子里,依然寄希望于奋笔疾书减轻心中的苦闷。缘起缘灭,上天注定,只需要静静的看着天意的安排就好了。看到一部微电影,故事情节挺老套的,但是结尾却给我留下了深刻印象。故事的开始,女主角过马路,差点被一辆车撞倒,男二号从车上下来,询问女主角是否受伤,还递给她一张名片,然后开车离开了。这时候,女主角手腕上的感应朱砂亮了,她坚信男二号就是她苦苦追寻的前世恋人,因为她没有忘记前世的记忆。女主角疯狂的追求男二号,男二号对她并没有好感。这时候男主角出现,他对女主角一见钟情,但是女主角从来没有把他放在心上。故事就这样在三角恋里纠缠,在危机关头女主角替男主角当了一刀,男主角抱着她的时候,她手腕上的感应朱砂再次亮了,原来她一直追寻的前世恋人是男主角。谜底揭晓了,原来第一次朱砂亮起的时候,男主角也在现场,他藏在男二号的车尾后备箱里,准备杀死男二号,因为男二号是男主角的杀父仇人的儿子。那么问题来了,现实中会不会出现像剧中的女主角那样,错把过往记忆里的人当作前世注定的情缘不肯放手,错过真正的前世情缘呢?不扯闲话了,开始进入正题。    

     Linux系统中i2c驱动分成三个部分:i2c-corei2c-busi2c-devi2c核心是i2c总线驱动和i2c设备驱动的中间枢纽,它以通用的、与平台无关的接口实现了i2c中设备与适配器的沟通。i2c总线驱动填充i2c_adapteri2c_algorithm结构体。i2c设备驱动填充i2c_driveri2c_client结构体。i2c核心有操作系统实现,i2c设备驱动在以前的文档中已经详细介绍,本文档重点关注i2c总线驱动的实现。

   Linux2.6版本以后,系统使用platform总线模式实现外设驱动,i2c总线驱动也遵循这种设计模式。Platform总线模式的优点:设备驱动中引入platform 概念,隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体匹配信息,而在驱动中,只需要通过API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

一、 platform总线架构

      Linux实现了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动称为 platform_driverPlatform总线的初始化有内核实现,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结构体中的namestruct platform_driver结构体中的struct device_driver结构体里的name名称一致时,注册的i2c_imx_probe探测函数开始运行,从此处开始进入i2c总线的初始化和配置。

二、 I2c总线驱动

1i2c总线结构体

I2c总线驱动主要填充i2c_adapteri2c_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做主机时才配置成主模式。Imx6i2c3实现了从机接收的功能。

(1) Imx6\drivers\i2c\busses\i2c-imx.c文件i2c_imx_probe()函数中把i2c3默认配置成从机模式,在i2c_imx_xfer()函数中当需要主发送时把i2c3配成主模式,发送完成后设置成从机模式;使用init_waitqueue_head()初始化内核等待队列,pollselect结合使用,上报从机接收到完整数据事件给应用层;使用kfifo_alloc创建环形队列,用于存储i2c3从机接收到的数据;使用hrtimer_init()创建10us定时器,检测i2c3从机是否接收完一帧数据,如果接收完一帧数据就使用kfifo_in()把接收到的数据拷贝到环形队列中,并使用wake_up_interruptible()唤醒刚才的等待队列,供应用层读取数据;

(2) 在中断函数i2c_imx_isr()里对中断事件类型进行判断,如果i2c3是从机接收数据事件,把接收到的数据放在接收缓存区中;

四、 i2c设备驱动

imx6i2c3设备驱动在\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()函数读出数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值