九鼎创展论坛中文版English
登录 | 立即注册 设为首页收藏本站 切换到宽版
查看: 7038|回复: 0
打印 上一主题 下一主题

x4412&ibox项目实战54-Linux触摸屏驱动之I2C驱动实验

[复制链接]
跳转到指定楼层
楼主
发表于 2014-10-24 15:01:52 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
前面我们分析了linux触摸屏驱动的input子系统机制,本章节分析linux触摸屏驱动的i2c机制。
驱动源码路径:
  1. kernel/drivers/input/touchscreen/ft5x06_touch.c
  2. kernel/drivers/input/touchscreen/ft5x06_firmware_1024_600.h
  3. kernel/drivers/input/touchscreen/ft5x06_firmware_800_480.h
复制代码
在探测函数ft5x06_ts_probe中,定义了两个结构体指针adapter和client,分别指向i2c的主设备结构体i2c_adapter和i2c的从设备结构体i2c_client。另外定义了一个记录i2c从设备信息的结构体info,用于记录从设备的名称和设备地址。
首先通过i2c_get_adapter函数获得一个i2c_adapter指针:
  1. adapter = i2c_get_adapter(FT5X06_I2C_BUS);
复制代码
函数的传入参数为I2C的通道,这里为通道1,因为x4412开发板上对应的触摸屏芯片接在I2C的1通道。
再将从设备的名称和地址填充到结构体info:
  1. memset(&info, 0, sizeof(struct i2c_board_info));
  2. info.addr = FT5X06_I2C_ADDRESS;//填充i2c_board_info结构体中I2C的地址
  3. strlcpy(info.type, "ft5x06-iic", I2C_NAME_SIZE);
复制代码
注意,这里的设备名称为ft5x06-iic,后面在i2c_driver结构体中一定要有和它匹配的名称,否则i2c驱动的probe函数将无法执行。
接着使用i2c_new_device函数将i2c的主设备和从设备关联起来,组成一个客户端,并返回一个指向i2c_client的结构体client。这个结构体非常重要,它将通过i2c_add_driver函数传给i2c对应的probe函数ft5x06_iic_probe,后面整个i2c操作都离不开它。
前面通过i2c_get_adapter函数获得了adapter结构体后,一旦通过adapter获得了从设备的结构体client,它的任务即已经完成,需要使用i2c_put_adapter函数释放该指针:
  1. i2c_put_adapter(adapter);
复制代码
最后通过i2c_add_driver函数注册一个i2c驱动:
  1. ret = i2c_add_driver(&ft5x06_iic_driver);
  2. ft5x06_iic_driver对应的结构体为:
  3. static struct i2c_driver ft5x06_iic_driver = {
  4.          .driver                  = {
  5.                    .name         = "ft5x06-iic",//结构体中已经存在id_table,故匹配名称时以id_table为准
  6.          },

  7.          .probe                  = ft5x06_iic_probe,
  8.          .remove               = ft5x06_iic_remove,
  9.          .suspend     = ft5x06_iic_suspend,
  10.          .resume                = ft5x06_iic_resume,
  11.          .id_table     = ft5x06_iic_id,
  12. };
复制代码
注意,该结构体中已经存在id_talbe,platform匹配时,将不再认成员driver中的name,而是会在id_table中查找是否有和前面我们定义的i2c名称“ft5x06_iic_probe”相同。
id_table对应内容如下:
  1. static const struct i2c_device_id ft5x06_iic_id[] = {
  2.          { "ft5x06-iic", 0},//该名称与i2c_board_info结构体中的驱动名称匹配,则调用probe函数
  3.          { }
  4. };
复制代码
可见,内核将会成功匹配,i2c的探测函数ft5x06_iic_probe得以运行。在该探测函数中,首先通过i2c_check_functionality函数检查i2c主设备的驱动能力:
  1. if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))//查看I2C适配器的能力
  2.                    return -ENODEV;
复制代码
再将函数中定义的指向ft5x06_ts结构体的指针ts的数据存放在client->dev->p->driver_data,以备调用:
  1. ts->client = client;//将从设备i2c_client数据赋值给ts
  2. i2c_set_clientdata(client, ts);
复制代码
在配置完触摸屏的input子系统以及触摸屏中断后,就开始通过i2c读取和写入数据了。
函数ft5x06_read_fw_ver用于读取FT5x06芯片的firmware版本:
  1. uc_reg_value = ft5x06_read_fw_ver(client);
复制代码
这里传入了前面申请的结构体client,其函数原型如下:
  1. static uint8_t ft5x06_read_fw_ver(struct i2c_client * client)
  2. {
  3.          uint8_t ver;

  4.          ft5x06_read_reg(client, FT5X0X_REG_FIRMID, &ver);
  5.          return (ver);
  6. }
  7. static int ft5x06_read_reg(struct i2c_client * client, uint8_t addr, uint8_t * data)
  8. {
  9.     uint8_t buf[2];
  10.     struct i2c_msg msgs[2];
  11.     int ret;

  12.     buf[0] = addr;

  13.     msgs[0].addr = client->addr;//i2c芯片地址
  14.     msgs[0].flags = 0; //0表示写
  15.     msgs[0].len = 1;   //要写的字节数为1
  16.     msgs[0].buf = buf; //需要读的寄存器地址

  17.     msgs[1].addr = client->addr;//i2c芯片地址
  18.     msgs[1].flags = I2C_M_RD;//1表示读,I2C_M_RD=1
  19.     msgs[1].len = 1;   //要读的字节数为1
  20.     msgs[1].buf = buf;         //读取的数据保存到buf

  21.     ret = i2c_transfer(client->adapter, msgs, 2);//传输2个msg
  22.     if(ret < 0)
  23.         printk("msg i2c read error\n");

  24.     *data = buf[0];
  25.     return ret;
  26. }
复制代码
真正干活的是函数ft5x06_read_reg,它通过i2c_transfer函数读取寄存器值。传入参数addr对应需要读取的寄存器的地址,*data返回从寄存器中读取的寄存器值。ft5x06_read_reg函数是一个典型的利用i2c_transfer读取寄存器值的模板,它定义了两个i2c_msg结构体msgs,msgs[0]用于写寄存器的地址,msgs[1]用于读该寄存器的值。I2C每次读寄存器的值,都需要先对该寄存器的地址发写命令,再发读命令返回写入寄存器的地址对应的寄存器值。
i2c_msg结构体内容如下:
  1. struct i2c_msg {
  2.          __u16 addr;         /* slave address                      */
  3.          __u16 flags;
  4. #define I2C_M_TEN             0x0010       /* this is a ten bit chip address */
  5. #define I2C_M_RD               0x0001       /* read data, from slave to master */
  6. #define I2C_M_NOSTART            0x4000       /* if I2C_FUNC_PROTOCOL_MANGLING */
  7. #define I2C_M_REV_DIR_ADDR        0x2000       /* if I2C_FUNC_PROTOCOL_MANGLING */
  8. #define I2C_M_IGNORE_NAK    0x1000       /* if I2C_FUNC_PROTOCOL_MANGLING */
  9. #define I2C_M_NO_RD_ACK               0x0800       /* if I2C_FUNC_PROTOCOL_MANGLING */
  10. #define I2C_M_RECV_LEN                   0x0400       /* length will be first received byte */
  11.          __u16 len;           /* msg length                                   */
  12.          __u8 *buf;           /* pointer to msg data                      */
  13. };
复制代码
这里addr表示从设备的地址,flags表示I2C需要执行的动作,为0表示写,为1表示读。len表示写入或读出的寄存器的字节数。当为写时,buf表示需要写入的寄存器地址,当为读时,buf返回从寄存器读出的值。i2c_transfer函数的第三个参数表示传入的msg的个数,通常读一个寄存器时,该值为2,写一个寄存器时,该值为1。
ft5x06_write_reg函数用于写寄存器,其源码如下:
  1. static int ft5x06_write_reg(struct i2c_client * client, uint8_t addr, uint8_t data)
  2. {
  3.          uint8_t buf[3];
  4.     int ret = -1;

  5.     buf[0] = addr;
  6.     buf[1] = data;

  7.     ret = ft5x06_i2c_txdata(client, buf, 2);
  8.     if (ret < 0)
  9.     {
  10.         printk("write reg failed! %#x ret: %d", buf[0], ret);
  11.         return -1;
  12.     }

  13.     return 0;
  14. }
  15. static int ft5x06_i2c_txdata(struct i2c_client * client, char * txdata, int length)
  16. {
  17.     int ret;

  18.     struct i2c_msg msg[] = {
  19.         {
  20.             .addr     = client->addr,     //i2c芯片地址
  21.             .flags     = 0,            //0表示写寄存器
  22.             .len        = length,     //要写入的数据的长度为2
  23.             .buf       = txdata,     //寄存器地址和该地址要写入的数据
  24.         },
  25.     };

  26.     ret = i2c_transfer(client->adapter, msg, 1);//传输1个msg
  27.     if (ret < 0)
  28.         printk("%s i2c write error: %d\n", __func__, ret);

  29.     return ret;
  30. }
复制代码
       ft5x06_write_reg函数中,通过形参将要写入的寄存器的地址和数据保存到buf数组,在ft5x06_i2c_txdata函数中定义了一个i2c_msg结构体msg,它的flags标志为0,表示写寄存器,buf[0]buf[1]分别为要写入的寄存器的地址和数据,写入的长度len2。最后通过i2c_transfer函数写入寄存器。
       在触摸屏芯片第一次执行时,需要通过I2C给芯片里面的固件升级,这时需要成批的操作寄存器。
       FTS_BOOLbyte_write函数用于批量写寄存器,其原型如下:
  1. static FTS_BOOL byte_write(struct i2c_client * client, FTS_BYTE* pbt_buf, FTS_DWRD dw_len)
  2. {
  3.     return i2c_write_interface(client, FT5X06_I2C_ADDRESS, pbt_buf, dw_len);
  4. }
  5. static FTS_BOOL i2c_write_interface(struct i2c_client * client, FTS_BYTE bt_ctpm_addr, FTS_BYTE* pbt_buf, FTS_DWRD dw_lenth)
  6. {
  7.     int ret;

  8.     ret = i2c_master_send(client, pbt_buf, dw_lenth);//一次性写入多个寄存器
  9.     if(ret<=0)
  10.     {
  11.         printk("[FTS]i2c_write_interface error line = %d, ret = %d\n", __LINE__, ret);
  12.         return FTS_FALSE;
  13.     }

  14.     return FTS_TRUE;
  15. }
复制代码
       FTS_BOOLbyte_write函数的传入参数中,pbt_buf指向要写入的寄存器值表,通常对应一个头文件,如ft5x06_firmware_1024_600.hdw_len表示一次性要写入的寄存器的数量。
       byte_read函数用于批量读寄存器,其原型如下:
  1. static FTS_BOOL byte_read(struct i2c_client * client, FTS_BYTE* pbt_buf, FTS_BYTE bt_len)
  2. {
  3.     return i2c_read_interface(client, FT5X06_I2C_ADDRESS, pbt_buf, bt_len);
  4. }
  5. static FTS_BOOL i2c_read_interface(struct i2c_client * client, FTS_BYTE bt_ctpm_addr, FTS_BYTE* pbt_buf, FTS_DWRD dw_lenth)
  6. {
  7.     int ret;

  8.     ret = i2c_master_recv(client, pbt_buf, dw_lenth);//一次性读取多个寄存器

  9.     if(ret<=0)
  10.     {
  11.         printk("[FTS]i2c_read_interface error\n");
  12.         return FTS_FALSE;
  13.     }

  14.     return FTS_TRUE;
  15. }
复制代码
       这里pbt_buf用于存储批量读取的寄存器值,bt_len表示一次性读取的寄存器数。
       通过本实例可以一句话总结出使用linux内核的I2C机制编写I2C设备驱动的方法,首先需要获取一个i2c_client结构体,再通过i2c_transfer函数读写寄存器,或通过i2c_master_sendi2c_master_recv函数批量读写寄存器。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳市九鼎创展科技官方论坛 ( 粤ICP备11028681号-2  

GMT+8, 2024-6-15 13:53 , Processed in 0.019057 second(s), 19 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表