input子系统一 i2c设备

来源:互联网 发布:mysql 重启 编辑:程序博客网 时间:2024/06/05 06:58
一、I2C体系结构        
          Linux的I2C体系结构分为3个组成部分:I2C核心、I2C总线驱动、I2C设备驱动,如下图所示。I2C核心提供总线驱动和设备驱动的注册、注销方法、I2C通信方法(简称algorithm);I2C总线驱动对硬件体系结构中适配器的实现,主要包括适配器i2c_adapter、适配器通信算法i2c_algorithm,如果CPU集成了I2C控制器并且linux内核支持这个CPU,那么总线驱动就不用管,比如S3C2440就属于这类情况,在后文中我们将分析它的总线驱动;I2C设备驱动是具体的一个设备(如AT24C02),挂接在CPU控制的I2C适配器的设备驱动,有了这部分驱动才能使用控制器操作该设备,设备驱动主要包括i2c_driver 和i2c_client数据结构。

二、具体描叙
1、i2c核心
       I2C核心是总线驱动和设备驱动的纽带,源码位于drivers/i2c/i2c-core.c,它并不依赖于硬件平台的接口函数,I2C核心中一些重要函数如下:

        增加/删除i2c_adapter

                int i2c_add_adapter(struct i2c_adapter *adapter)

                int i2c_del_adapter(struct i2c_adapter *adapter)

        增加/删除i2c_driver

                int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

                int i2c_add_driver(struct i2c_driver *driver)         //调用i2c_register_driver

                void i2c_del_driver(struct i2c_driver *driver)

        增加/删除i2c_client

                struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

                void i2c_unregister_device(struct i2c_client *client)

        I2C传输、发送接收

                int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

                int i2c_master_send(struct i2c_client *client,const char *buf ,int count)

                int i2c_master_recv(struct i2c_client *client, char *buf ,int count)

             i2c_transfer()函数用于进行I2C 适配器和I2C 设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息,i2c_transfer()本身不能和硬件完成消息交互,它寻找i2c_adapter对应的i2c_algorithm,要实现数据传送就要实现i2c_algorithm的master_xfer(),在总线驱动中就是重点。

2、i2c总线驱动
       I2C总线驱动模块的加载函数要完成两个工作:
           (1)初始化I2C适配器所使用的硬件资源,如申请I/O地址、终端号;
           (2)通过I2C核心提供的i2c_add_adapter()添加i2c_adapter的数据结构,当然这个i2c_adaper数据结构的成员已经被适配器的相应函数指针所初始化;
       I2C总线驱动模块的卸载函数要完n成的工作与加载函数刚好相反;
            (1)释放i2c适配器所使用的硬件资源,如释放I/O地址、中断号等;
            (2)通过i2c_del_adapter()删除i2c_adapter删除i2c_adapter的数据结构;

3、i2c总线通信方法
        我们需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm的master_xfer()函数和functionality()函数。
        functionality函数非常简单,用于返回algorithm所支持的通信协议,如:I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_READ_BYTE等。
        master_xfer()函数在I2C适配器上完成传递给它的i2c_msg数组中的每个i2c消息。

static int i2c_adapter_xxx_xfer(structi2c_adapter *adap, struct i2c_msg *msgs, int num)  {     ......     for (i = 0; i < num; i++) {         i2c_adapter_xxx_start();         /*产生起始位*/         if (msgs[i]->flags & I2C_M_RD) {    /*读取*/             i2c_adapter_xxx_setaddr((msg->addr << 1) | 1);/*发送从设备地址*/             i2c_adapter_xxx_wait_ack();   /*获得从设备的ACK*/             i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);              /*读取len长度的数据到buf中*/         } else {             i2c_adapter_xxx_setaddr(msg->addr << 1);             i2c_adapter_xxx_wait_ack();             i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len);         }      }      i2c_adapter_xxx_stop(); /*产生停止位*/  } 

单开始信号情况:

合多开始信号的情况:
三、四个结构体描叙i2c设备

i2c结构体系主要由以下四个结构体描叙:

1、i2c_adapter适配器通俗一点说就是一种起中间连接作用的配件:

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;    /*algorithm数据 */         /* 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;    /*client链表头*/         struct i2c_bus_recovery_info *bus_recovery_info;};

2、 i2c_driver
struct i2c_driver {         unsigned int class;         /* Notifies the driver that a new bus has appeared. You should avoid          * using this, it will be removed in a near future.          */         int (*attach_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;         const struct i2c_device_id *id_table; //驱动所支持的i2c设备的ID表         /* 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;};

3、struct i2c_client
struct i2c_client {         unsigned short flags;/* div., see below*/         unsigned short addr;/* chip address - NOTE: 7bit     芯片地址 */             /* addresses are stored in the*/             /* _LOWER_ 7 bits*/         char name[I2C_NAME_SIZE];         struct i2c_adapter *adapter;/* the adapter we sit on*/         struct i2c_driver *driver;/* and our access routines*/         struct device dev;/* the device structure*/         int irq;/* irq issued by device*/         struct list_head detected;};

4、struct i2c_algorithm
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 *);};

i2c体系主要由上面四个结构体描叙,另外还有一个描叙i2c设备的的结构体:
kernel/include/linux/device.h
struct device {        struct device   * parent; //父设备,一般一个bus也对应一个设备。        struct kobject kobj;//代表自身        char bus_id[BUS_ID_SIZE];         struct bus_type * bus;   /* 所属的总线 */        struct device_driver *driver; /* 匹配的驱动*/        struct device_node *of_node;        void   *driver_data; /* data private to the driver 指向驱动 */        void   *platform_data; /* Platform specific data,由驱动定义并使用*/        ///更多字段忽略了};

5、四者之间的关系
(1)i2c_adapter与i2c_algotithm
        i2c_adapter对应于物理上的一个适配器,而i2c_algotithm对应于一套通信方法,一个i2c适配器需要i2c_algotithm中提供的通信函数来控制适配器上产生特定的访问周期,缺少i2c_algotithm的i2c_adapter什么也做不了,因此i2c_adapter结构体中需要包含其使用的i2c_algotithm。
       i2c_algotithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(即i2c消息)为单位,i2c_msg结构体也非常重要,其内容为:
struct  i2c_msg{
        _ _u16  addr;/*设备地址*/
        _ _u16  flags;/*标志  0->发送数据,I2C_M_RD->接收数据*/
        _ _u16  len;    /*消息长度*/
        _ _u8    *buf;/*消息数据*/
}   

(2)i2c_driver与i2c_client
         i2c_driver对应一套驱动方法,其只要成员函数是probe()、remove()、suspend()、resume()等,i2c_client用于描叙真实的物理设备,每一个I2C都需要一个i2c_client来描叙,i2c_driver与i2c_client的关系是一对多,一个i2c_driver上可以支持多个同等类型的i2c_client 。    

(3)i2c_adpater与i2c_client
        i2c_adapter与i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于依附于i2c_adpater。由于一个适配器可以连接多个I2C设备,所以一个i2c_adapter也可以被多个i2c_client依附,i2c_adapter中包括依附于它的i2c_client链表。

四、驱动代码分析(tp)
  输入设备(按键、键盘、触摸屏等)是典型的字符设备,其一般的工作原理都是:底层在按键、触摸等工作发送时产生一个终端(或驱动通过timer定时轮询),然后CPU通过spi、i2c或外部存储器总线读取键值、坐标等数据,放入一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值或坐标等数据。
1、驱动程序初始化和销毁模块
    module_init(ft5x0x_ts_init);
    module_exit(ft5x0x_ts_exit);

2、注册设备和注销设备
        I2C设备驱动的模块加载函数通用的方法是在I2C设备驱动的模块加载函数通过i2c核心提供的i2c_add_driver()函数添加i2c_driver的工作,而卸载函数做相反的工作,通过i2c核心提供的i2c_del_driver()函数删除i2c_driver,代码如下:
    static int __init ft5x0x_ts_init(void)    {         return i2c_add_driver(&ft5x0x_ts_driver);    }    static void __exit ft5x0x_ts_exit(void)    {         i2c_del_driver(&ft5x0x_ts_driver);    }           i2c_add_driver 调用 i2c_register_driver函数:     i2c_register_driver会调用driver_register() 来将设备驱动添加到总线的设备驱动链表中:     int i2c_register_driver(struct module *owner, struct i2c_driver *driver)    {        ........         /* add the driver to the list of i2c drivers in the driver core */             driver->driver.owner = owner;             driver->driver.bus = &i2c_bus_type;             res = driver_register(&driver->driver);             INIT_LIST_HEAD(&driver->clients); //初始化链表          /* Walk the adapters that are already present */             i2c_for_each_dev(driver, __process_new_driver);        ........    }                           int driver_register(struct device_driver *drv)      {            ...........                     other = driver_find(drv->name, drv->bus); //判断驱动是否已经注册                     ret = bus_add_driver(drv); //将设备驱动添加到bus(总线)上                    ret = driver_add_groups(drv, drv->groups);                     //如果grop不为空的话,将在驱动文件夹下创建以group名字的子文件夹,然后在子文件夹下                        添加group的属性文件,在sysfs下表现为同一个目录            ...........       }
        i2c总线注册好后将会有如下文件夹结构/sys/bus/i2c/,在/sys/bus/i2c/文件夹下会有如下文件夹uevent、devices、drivers、drivers_probe、drivers_autoprobe,当你注册驱动的时候,将会在/sys/bus/i2c/drivers/下注册一个改驱动的文件夹,比如ov7675,那么它将会注册成/sys/bus/i2c/drivers/ov7675/,其实这些文件夹都对应一个kobject,通过kset容器组成一个很清晰的层次结构。当driver中的.compatible与dts中的匹配上就开始执行probe函数。

3、I2C设备驱动要使用i2c_driver数据结构填充i2c_driver中的成员函数
    static struct i2c_driver ft5x0x_ts_driver = {
         .probe = ft5x0x_ts_probe,
         .remove = ft5x0x_ts_remove,
         .id_table = ft5x0x_ts_id, 
         .driver = {
                  .name = FOCALTECH_TS_NAME,
                  .owner = THIS_MODULE,
                  .of_match_table = focaltech_of_match,
         },
         .suspend = ft5x0x_suspend,
         .resume = ft5x0x_resume,
    };

        id_table 中设备的名字,i2c总线根据设备client名字和id_table中的名字进行匹配的。如果匹配了,则返回id值,在i2c_device_match中则返回真。也就是bus的match函数将会返回真。那将会进入driver_probe_device()。

4、实现 ft5x0x_ts_probe
   ft5x0x_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)    {            struct ft5x0x_ts_platform_data *pdata = client->dev.platform_data;//保存数据            pdata = ft5x0x_ts_parse_dt(&client->dev);//读取设备树dts中的配置信息             if (np && !pdata){                      pdata = ft5x0x_ts_parse_dt(&client->dev); //接收返回的dts配置信息                      if(pdata){ //获取成功                   client->dev.platform_data = pdata; //将获得的数据传输给client->device->dev->platform                      }                      else{ //获取失败                                   err = -ENOMEM;                                   goto exit_alloc_platform_data_failed;                           }              }                //检查适配器通信协议是否匹配I2C_FUNC_I2C              if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {                          err = -ENODEV;                          goto exit_check_functionality_failed;              }              ft5x0x_ts->platform_data = pdata;//将获取的dts资源赋给驱动程序              this_client = client;//将client结构体赋给全局变量              ft5x0x_ts_hw_init(ft5x0x_ts);//初始化引脚,开启电源等              i2c_set_clientdata(client, ft5x0x_ts);//设置设备的私有信息                //将上面ft5x0x_ts_parse_dt定义的中断引脚设置为中断模式              client->irq = gpio_to_irq(pdata->irq_gpio_number);               err = ft5x0x_read_reg(FT5X0X_REG_CIPHER, &uc_reg_value);//检查tp型号               ft5x0x_write_reg(FT5X0X_REG_PERIODACTIVE, 7);  //设置报点率               INIT_WORK(&ft5x0x_ts->resume_work, ft5x0x_ts_resume_work);//初始化工作队列:用于唤醒                                                                                                                         tp,开启中断等工作;               input_dev = input_allocate_device();  //申请一个Input_dev设备               __set_bit(ABS_MT_TOUCH_MAJOR, input_dev->absbit);//设置键值类型               __set_bit(ABS_MT_POSITION_X, input_dev->absbit);                //给设备的input_dev结构体初始化               input_set_abs_params(input_dev,ABS_MT_POSITION_X, 0, pdata->TP_MAX_X, 0, 0);               input_set_abs_params(input_dev,ABS_MT_POSITION_Y, 0, pdata->TP_MAX_Y, 0, 0);               err = input_register_device(input_dev); //注册输入Input_dev设备                // request_irq() 函数来注册中断服务函数               err = request_irq(client->irq, ft5x0x_ts_interrupt,  IRQF_TRIGGER_FALLING | IRQF_ONESHOT                            | IRQF_NO_SUSPEND, client->name, ft5x0x_ts);                thread = kthread_run(touch_event_handler, 0, "focal-wait-queue");//创建线程执行中断函数                .........    }    static inline void    i2c_set_clientdata(struct i2c_client *dev, void *data)     {    //driver_data是驱动特殊信息的私有指针,i2c_set_clientdata(client, dev)就是将自定义的设备结构dev赋给设备    //驱动client的私有指针,我猜测是用来区别其他驱动client,             dev_set_drvdata(&dev->dev, data);     }    static void  ft5x0x_ts_hw_init(struct ft5x0x_ts_data *ft5x0x_ts)     {             struct regulator *reg_vdd;             struct i2c_client *client = ft5x0x_ts->client;             struct ft5x0x_ts_platform_data *pdata = ft5x0x_ts->platform_data;             pr_info("[FST] %s [irq=%d];[rst=%d]\n",__func__,             pdata->irq_gpio_number,pdata->reset_gpio_number); //获取初始化引脚             gpio_request(pdata->irq_gpio_number, "ts_irq_pin"); //获取中断引脚             gpio_request(pdata->reset_gpio_number, "ts_rst_pin");             gpio_direction_output(pdata->reset_gpio_number, 1); //引脚输出1             gpio_direction_input(pdata->irq_gpio_number); //引脚输入             reg_vdd = regulator_get(&client->dev, pdata->vdd_name);             if (!WARN(IS_ERR(reg_vdd), "[FST] ft5x0x_ts_hw_init regulator: failed to get %s.\n", pdata->vdd_name))                         {                      regulator_set_voltage(reg_vdd, 2800000, 2800000);                      regulator_enable(reg_vdd);             }             msleep(100);             ft5x0x_ts_reset();//复位操作    }    static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func)    {             return (func & i2c_get_functionality(adap)) == func;    }    static struct ft5x0x_ts_platform_data *ft5x0x_ts_parse_dt(struct device *dev)    {             struct ft5x0x_ts_platform_data *pdata;             struct device_node *np = dev->of_node;             int ret;             pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);             if (!pdata) {                  dev_err(dev, "Could not allocate struct ft5x0x_ts_platform_data");                  return NULL;             }             pdata->reset_gpio_number = of_get_gpio(np, 0); //获得复位引脚             if(pdata->reset_gpio_number < 0){                      dev_err(dev, "fail to get reset_gpio_number\n");                      goto fail;             }             pdata->irq_gpio_number = of_get_gpio(np, 1); //获得中断引脚信息             if(pdata->reset_gpio_number < 0){                  dev_err(dev, "fail to get reset_gpio_number\n");                  goto fail;             }             ret = of_property_read_string(np, "vdd_name", &pdata->vdd_name); //读取供电电压             if(ret){                      dev_err(dev, "fail to get vdd_name\n");                      goto fail;             }             ret = of_property_read_u32_array(np, "virtualkeys", &pdata->virtualkeys,12); //读取虚拟按键信息             if(ret){                      dev_err(dev, "fail to get virtualkeys\n");                      goto fail;             }             ret = of_property_read_u32(np, "TP_MAX_X", &pdata->TP_MAX_X); //读取X的最大值             if(ret){                      dev_err(dev, "fail to get TP_MAX_X\n");                      goto fail;             }             return pdata; //返回获得的数据       fail:             kfree(pdata);             return NULL;    }    struct ft5x0x_ts_platform_data{//描述tp硬件信息的结构体             int irq_gpio_number;             int reset_gpio_number;             const char *vdd_name;             int virtualkeys[12];             int TP_MAX_X;             int TP_MAX_Y;    };

5、TP键值读取到上报的过程
(1)触摸产生中断,通过I2C读取底层数据
        在上面的驱动probe函数中通过 kthread_run(touch_event_handler, 0, "focal-wait-queue");创建了一个线程用来处理中断函数touch_event_handler
        static int touch_event_handler(void *unused)
        {
                 do {
                              mutex_lock(&g_mutex);
                              ......
                              ft5x0x_update_data(); //中断执行函数
                              mutex_unlock(&g_mutex);

                         } while (!kthread_should_stop());
        }

        static int ft5x0x_update_data(void)
        {
                .........
                   ret = ft5x0x_i2c_rxdata(buf, 31); //通过i2c读取底层数据

                   input_report_abs(ft5x0x_ts->input_dev, ABS_MT_POSITION_X, x);
                   input_report_abs(ft5x0x_ts->input_dev, ABS_MT_POSITION_Y, y);
                   input_report_key(ft5x0x_ts->input_dev, BTN_TOUCH, 1);
                .........
        }

        static int ft5x0x_i2c_rxdata(char *rxdata, int length)//I2C数据传送
        {
                 int ret = 0;
                 struct i2c_msg msgs[] = {
                  {
                           .addr = this_client->addr,
                           .flags = 0,   //发送数据
                           .len = 1,
                           .buf = rxdata,
                   },
                   {
                           .addr = this_client->addr,
                           .flags = I2C_M_RD,  //接收数据
                           .len = length, //接收数据长度
                           .buf = rxdata, //接收数据保存位置
                   },
         };

            if (i2c_transfer(this_client->adapter, msgs, 2) != 2) {
                      ret = -EIO;
                      pr_err("msg %s i2c read error: %d\n", __func__, ret);
             }
         return ret;
      }

i nt i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); 
        函数路径:kernel/driver/i2c/i2c-core.c
        最终通过调用i2c_algotithm(通信方法)中的关键函数master_xfer(),这个函数用于产生I2C访问周期需要的信号,是以i2c_mag(即I2C消息)为单位。
        其中 adap 为此次主机与从机通信的适配器;msgs 为通信的数据包,这里可以是单个或多个数据包;num 用于指定数据包的个数,如果大于1则表明将进行不止一次的通信。通信一次就需要寻址一次,如果需要多次通信就需要多次寻址,前面2个接口都是进行一次通信,所以 num 为1;有的情况下我们要读一个寄存器的值,就需要先向从机发送一个寄存器地址然后再接收数据,这样如果想自己封装一个接口就需要将 num 设置为2。接口的返回值如果失败则为负数,如果成功则返回传输的数据包个数。
下篇会分析具体数据上报过程。
到这里从i2c读取数据到中断的上报介绍过程就已经完毕了,下篇分析linux中的Input子系统架构。


作者:frank_zyp

您的支持是对博主最大的鼓励,感谢您的认真阅读。

本文无所谓版权,欢迎转载。


1 0