前面我们分析了linux触摸屏驱动的input子系统机制,本章节分析linux触摸屏驱动的i2c机制。 驱动源码路径: - kernel/drivers/input/touchscreen/ft5x06_touch.c
- kernel/drivers/input/touchscreen/ft5x06_firmware_1024_600.h
- 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指针: - adapter = i2c_get_adapter(FT5X06_I2C_BUS);
复制代码函数的传入参数为I2C的通道,这里为通道1,因为x4412开发板上对应的触摸屏芯片接在I2C的1通道。 再将从设备的名称和地址填充到结构体info: - memset(&info, 0, sizeof(struct i2c_board_info));
- info.addr = FT5X06_I2C_ADDRESS;//填充i2c_board_info结构体中I2C的地址
- 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函数释放该指针: - i2c_put_adapter(adapter);
复制代码最后通过i2c_add_driver函数注册一个i2c驱动: - ret = i2c_add_driver(&ft5x06_iic_driver);
- ft5x06_iic_driver对应的结构体为:
- static struct i2c_driver ft5x06_iic_driver = {
- .driver = {
- .name = "ft5x06-iic",//结构体中已经存在id_table,故匹配名称时以id_table为准
- },
-
- .probe = ft5x06_iic_probe,
- .remove = ft5x06_iic_remove,
- .suspend = ft5x06_iic_suspend,
- .resume = ft5x06_iic_resume,
- .id_table = ft5x06_iic_id,
- };
复制代码注意,该结构体中已经存在id_talbe,platform匹配时,将不再认成员driver中的name,而是会在id_table中查找是否有和前面我们定义的i2c名称“ft5x06_iic_probe”相同。 id_table对应内容如下: - static const struct i2c_device_id ft5x06_iic_id[] = {
- { "ft5x06-iic", 0},//该名称与i2c_board_info结构体中的驱动名称匹配,则调用probe函数
- { }
- };
复制代码可见,内核将会成功匹配,i2c的探测函数ft5x06_iic_probe得以运行。在该探测函数中,首先通过i2c_check_functionality函数检查i2c主设备的驱动能力: - if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))//查看I2C适配器的能力
- return -ENODEV;
复制代码再将函数中定义的指向ft5x06_ts结构体的指针ts的数据存放在client->dev->p->driver_data,以备调用: - ts->client = client;//将从设备i2c_client数据赋值给ts
- i2c_set_clientdata(client, ts);
复制代码在配置完触摸屏的input子系统以及触摸屏中断后,就开始通过i2c读取和写入数据了。 函数ft5x06_read_fw_ver用于读取FT5x06芯片的firmware版本: - uc_reg_value = ft5x06_read_fw_ver(client);
复制代码这里传入了前面申请的结构体client,其函数原型如下: - static uint8_t ft5x06_read_fw_ver(struct i2c_client * client)
- {
- uint8_t ver;
-
- ft5x06_read_reg(client, FT5X0X_REG_FIRMID, &ver);
- return (ver);
- }
- static int ft5x06_read_reg(struct i2c_client * client, uint8_t addr, uint8_t * data)
- {
- uint8_t buf[2];
- struct i2c_msg msgs[2];
- int ret;
-
- buf[0] = addr;
-
- msgs[0].addr = client->addr;//i2c芯片地址
- msgs[0].flags = 0; //0表示写
- msgs[0].len = 1; //要写的字节数为1
- msgs[0].buf = buf; //需要读的寄存器地址
-
- msgs[1].addr = client->addr;//i2c芯片地址
- msgs[1].flags = I2C_M_RD;//1表示读,I2C_M_RD=1
- msgs[1].len = 1; //要读的字节数为1
- msgs[1].buf = buf; //读取的数据保存到buf
-
- ret = i2c_transfer(client->adapter, msgs, 2);//传输2个msg
- if(ret < 0)
- printk("msg i2c read error\n");
-
- *data = buf[0];
- return ret;
- }
复制代码真正干活的是函数ft5x06_read_reg,它通过i2c_transfer函数读取寄存器值。传入参数addr对应需要读取的寄存器的地址,*data返回从寄存器中读取的寄存器值。ft5x06_read_reg函数是一个典型的利用i2c_transfer读取寄存器值的模板,它定义了两个i2c_msg结构体msgs,msgs[0]用于写寄存器的地址,msgs[1]用于读该寄存器的值。I2C每次读寄存器的值,都需要先对该寄存器的地址发写命令,再发读命令返回写入寄存器的地址对应的寄存器值。 i2c_msg结构体内容如下: - struct i2c_msg {
- __u16 addr; /* slave address */
- __u16 flags;
- #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
- #define I2C_M_RD 0x0001 /* read data, from slave to master */
- #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
- __u16 len; /* msg length */
- __u8 *buf; /* pointer to msg data */
- };
复制代码这里addr表示从设备的地址,flags表示I2C需要执行的动作,为0表示写,为1表示读。len表示写入或读出的寄存器的字节数。当为写时,buf表示需要写入的寄存器地址,当为读时,buf返回从寄存器读出的值。i2c_transfer函数的第三个参数表示传入的msg的个数,通常读一个寄存器时,该值为2,写一个寄存器时,该值为1。 ft5x06_write_reg函数用于写寄存器,其源码如下: - static int ft5x06_write_reg(struct i2c_client * client, uint8_t addr, uint8_t data)
- {
- uint8_t buf[3];
- int ret = -1;
-
- buf[0] = addr;
- buf[1] = data;
-
- ret = ft5x06_i2c_txdata(client, buf, 2);
- if (ret < 0)
- {
- printk("write reg failed! %#x ret: %d", buf[0], ret);
- return -1;
- }
-
- return 0;
- }
- static int ft5x06_i2c_txdata(struct i2c_client * client, char * txdata, int length)
- {
- int ret;
-
- struct i2c_msg msg[] = {
- {
- .addr = client->addr, //i2c芯片地址
- .flags = 0, //0表示写寄存器
- .len = length, //要写入的数据的长度为2
- .buf = txdata, //寄存器地址和该地址要写入的数据
- },
- };
-
- ret = i2c_transfer(client->adapter, msg, 1);//传输1个msg
- if (ret < 0)
- printk("%s i2c write error: %d\n", __func__, ret);
-
- return ret;
- }
复制代码 在ft5x06_write_reg函数中,通过形参将要写入的寄存器的地址和数据保存到buf数组,在ft5x06_i2c_txdata函数中定义了一个i2c_msg结构体msg,它的flags标志为0,表示写寄存器,buf[0]和buf[1]分别为要写入的寄存器的地址和数据,写入的长度len为2。最后通过i2c_transfer函数写入寄存器。 在触摸屏芯片第一次执行时,需要通过I2C给芯片里面的固件升级,这时需要成批的操作寄存器。 FTS_BOOLbyte_write函数用于批量写寄存器,其原型如下: - static FTS_BOOL byte_write(struct i2c_client * client, FTS_BYTE* pbt_buf, FTS_DWRD dw_len)
- {
- return i2c_write_interface(client, FT5X06_I2C_ADDRESS, pbt_buf, dw_len);
- }
- static FTS_BOOL i2c_write_interface(struct i2c_client * client, FTS_BYTE bt_ctpm_addr, FTS_BYTE* pbt_buf, FTS_DWRD dw_lenth)
- {
- int ret;
-
- ret = i2c_master_send(client, pbt_buf, dw_lenth);//一次性写入多个寄存器
- if(ret<=0)
- {
- printk("[FTS]i2c_write_interface error line = %d, ret = %d\n", __LINE__, ret);
- return FTS_FALSE;
- }
-
- return FTS_TRUE;
- }
复制代码 FTS_BOOLbyte_write函数的传入参数中,pbt_buf指向要写入的寄存器值表,通常对应一个头文件,如ft5x06_firmware_1024_600.h。dw_len表示一次性要写入的寄存器的数量。 byte_read函数用于批量读寄存器,其原型如下: - static FTS_BOOL byte_read(struct i2c_client * client, FTS_BYTE* pbt_buf, FTS_BYTE bt_len)
- {
- return i2c_read_interface(client, FT5X06_I2C_ADDRESS, pbt_buf, bt_len);
- }
- static FTS_BOOL i2c_read_interface(struct i2c_client * client, FTS_BYTE bt_ctpm_addr, FTS_BYTE* pbt_buf, FTS_DWRD dw_lenth)
- {
- int ret;
-
- ret = i2c_master_recv(client, pbt_buf, dw_lenth);//一次性读取多个寄存器
-
- if(ret<=0)
- {
- printk("[FTS]i2c_read_interface error\n");
- return FTS_FALSE;
- }
-
- return FTS_TRUE;
- }
复制代码 这里pbt_buf用于存储批量读取的寄存器值,bt_len表示一次性读取的寄存器数。 通过本实例可以一句话总结出使用linux内核的I2C机制编写I2C设备驱动的方法,首先需要获取一个i2c_client结构体,再通过i2c_transfer函数读写寄存器,或通过i2c_master_send和i2c_master_recv函数批量读写寄存器。
|