赞
踩
应用层操作 I2C 是以数据包进行交流的,所有在应用层就要进行封包的操作。 数据包对应的结构体是 i2c_rdwr_ioctl_data,这个结构体定义在 include\uapi\linux\i2c-dev.h 下面:定义如下:
/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
}
第一个结构体成员是要发送的数据包的指针,第二个结构体成员发送数据包的个数。i2c_msg 结构体的定义,这个结构体是定义在 include\uapi\linux\i2c.h 下 面,定义如下:
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_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#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 为读写标志位,如果flags为1,则为读,反之为0,则为写。len为buf的大小,单位是字节。
当flags为1是,buf就是我们要接收的数据,当flags为0时,就是要发送的数据。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <linux/i2c.h> #include <linux/i2c-dev.h> #include <sys/ioctl.h> int fd; //I2C 读函数 int i2c_read_date(unsigned int slave_addr,unsigned char reg_addr) { unsigned char data; struct i2c_rdwr_ioctl_data i2c_read_lcd; int ret; struct i2c_msg msg[2] = { [0] = { .addr = slave_addr, .flags = 0, .buf = ®_addr, .len = sizeof(reg_addr) }, [1] = { .addr = slave_addr, .flags = 1, .buf = &data, .len = sizeof(data) }, }; i2c_read_lcd.msgs = msg; i2c_read_lcd.nmsgs = 2; ret = ioctl(fd, I2C_RDWR,&i2c_read_lcd); if(ret < 0){ perror("i2c ioctl error:"); return ret; } return data; } int main(int argc,char *argv[]) { int GEST_ID; fd = open("/dev/i2c-1", O_RDWR); if(fd < 0){ perror("open i2c error:"); return fd; } while(1) { GEST_ID = i2c_read_date(0x38,0x01); printf("GEST_ID value is %#x\n",GEST_ID); sleep(1); } close(fd); return 0; }
Linux 中的 I2C 也是按照平台总线模型设计的,既然也是按照平台总线模型设计分为一个 device 和一个 driver,但是I2C这里的 device 不叫 device,而是叫 client。在讲platform的时候就说过,platform是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于I2C而言,不需要虚拟出一条总线,直接使用 I2C 总线即可。同样,也是先从非设备树开始,先来看一下,在没有设备树之前是怎么实现的 I2C 的 device 部分,也就是 client 部分。然后在学习有了设备树之后,再看client 是怎么编写的。按照 Linux 的发展路径来学习。 在没有使用设备树之前,使用的是i2c_board_info这个结构体来描述一个I2C设备 的,i2c_board_info 这个结构体如下:
struct i2c_board_info {
char type[I2C_NAME_SIZE]; /* I2C 设备名字 */
unsigned short flags; /* 标志 */
unsigned short addr; /* I2C 器件地址 */
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct fwnode_handle *fwnode;
int irq;
};
在这个结构体里面, type 和 addr 这两个成员变量是必须要设置的,一个是 I2C 设备的名字,这个名字就是用来进行匹配用的,一个是I2C设备的器件地址。也可以使用宏:
#define I2C_BOARD_INFO(dev_type, dev_addr) \
. type = dev_type, .addr = (dev_addr)
可以看出,I2C_BOARD_INFO宏其实就是设置 i2c_board_info 的 type 和 addr 这 两个成员变量。
I2C 设备和驱动的匹配过程是由 I2C 核心来完成的,在 Linux 源码的drivers/i2c/i2c core.c 就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的 API 函数,如下:
函数作用:获得一个I2C适配器原型:
struct i2c_adapter *i2c_get_adapter(int nr);
参数:要获得的哪个 I2C 适配器的编号
返回值:失败返回 NULL
作用:释放 I2C 适配器
原型:
void i2c_put_adapter(struct i2c_adapter *adap);
参数:要释放的 I2C 适配器
返回值:失败返回 NULL
作用:把 I2C 适配器和 I2C 器件关联起来。
原型:
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
参数:
struct i2c_adapter *adap : I2C 适配器
struct i2c_board_info const *info : i2c_board_info 的指针
返回值:失败返回 NULL;
作用:注销一个 client 。
原型:
void i2c_unregister_device(struct i2c_client *client)
参数: i2c client 的指针。
返回值:失败返回 NULL
在使用了设备树以后,就不用这么复杂了,使用设备树的时候只要在对应的 I2C 节点下创 建相应设备的节点即可,比如想添加一个触摸芯片 FT5X06 的设备,就可以在对应的 I2C 的节点下这样写,如下图所示:
&i2c2 { clock_frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c2>; status = "okay"; edt-ft5x06@38 { compatible = "edt,edt-ft5306", "edt,edt-ft5x06", "edt,edt-ft5406"; pinctrl-names = "default"; pinctrl-0 = <&ts_int_pin &ts_reset_pin>; reg = <0x38>; interrupt-parent = <&gpio1>; interrupts = <9 0>; reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; irq-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; status = "okay"; }; }
然后再来看 driver 部分。 不管是使用设备树还是非设备树, driver 部分就比较复杂了。 和注册一个杂项设备或者是字符设备的套路一样,也是要先定一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 *); /* 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; /* 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;
};
在驱动注册之前 i 2c_driver 结构体需要被正确地初始化,有 4 个成员要求必须被初始化,其中id_table不管用不用设备树都要被初始化,否则不能匹配成功:
static struct i2c_driver xxx_driver =
{
.driver = { . name = "xxx", },
.probe = xxx_probe,
.remove = xxx_remove,
.id_table = xxx_table,
};
初始化完成以后就是把 i2c_driver 注册进内核,注册进内核使用是i2c_add_driver 。来看一下这个函数:
注册一个 i2c 驱动 函数原型:
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
参数: struct i2c_driver 的指针。
返回值:失败返回负值
函数作用:
删除一个 i2c 驱动 函数原型:
extern void i2c_del_driver(struct i2c_driver *);
参数: struct i2c_driver 的指针
返回值:失败返回负值
#include<linux/init.h> #include<linux/module.h> #include <linux/i2c.h> //分配一个I2C适配器指针 struct i2c_adapter *i2c_ada; //分配一个I2C_client指针 struct i2c_client *i2c_client; //支持的IIC设备列表 struct i2c_board_info ft5x06_info[] = { //每一项代表一个IIC设备,这个设备的名字是ft5x06,器件地址是0x38 {I2C_BOARD_INFO("ft5x06_test",0x38)}, {} }; static int ft5x06_client_init(void) { //调用i2c_get_adapter,获得到iic总线,因为ft5x06挂在到iic 2总线,参数就是1。 //把这个触摸芯片挂在到i2c上。 i2c_ada = i2c_get_adapter(1); //把iic client 和iic器件关联起来 i2c_client = i2c_new_device(i2c_ada,ft5x06_info); //释放iic控制器 i2c_put_adapter(i2c_ada); printk("This is ft5x06_client\n"); return 0; } static void ft5x06_client_exit(void) { i2c_unregister_device(i2c_client); printk("bye bye\n"); } module_init(ft5x06_client_init); module_exit(ft5x06_client_exit); MODULE_LICENSE("GPL");
#include<linux/init.h> #include<linux/module.h> #include <linux/i2c.h> static const struct i2c_device_id ft5x06_id_ts[] = { {"xxx",0}, {} }; static const struct of_device_id ft5x06_id[] = { {.compatible = "edt,edt-ft5306",0,}, {.compatible = "edt,edt-ft5x06",0,}, {.compatible = "edt,edt-ft5406",0,}, {} }; int ft5x06_probe(struct i2c_client *i2c_client,const struct i2c_device_id *id) { printk("ft5x06_probe\n"); //比如可以注册一个杂项设备。 return 0; } int ft5x06_remove(struct i2c_client *i2c_client) { return 0; } static struct i2c_driver ft5x06_driver = { //必须实现如下四个功能 //int (*probe)(struct i2c_client *, const struct i2c_device_id *); //int (*remove)(struct i2c_client *); //struct device_driver driver; //const struct i2c_device_id *id_table; .driver = { .owner = THIS_MODULE, .name = "ft5x06_test", .of_match_table = ft5x06_id, }, .probe = ft5x06_probe, .remove = ft5x06_remove, .id_table = ft5x06_id_ts }; static int ft5x06_driver_init(void) { int ret; ret = i2c_add_driver(&ft5x06_driver); if (ret < 0) { printk("i2c_add_driver is error\n"); return ret; } printk("i2c_add_driver init\n"); return 0; } static void ft5x06_driver_exit(void) { i2c_del_driver(&ft5x06_driver); printk("i2c_del_driver exit\n"); } module_init(ft5x06_driver_init); module_exit(ft5x06_driver_exit); MODULE_LICENSE("GPL");
驱动程序实现IIC通信:
#include<linux/init.h> #include<linux/module.h> #include <linux/i2c.h> static struct i2c_client *ft5x06_client; static void ft5x06_write_reg(u8 reg_addr,u8 data,u8 len); static void ft5x06_write_reg(u8 reg_addr,u8 data,u8 len) { u8 buff[256]; struct i2c_msg msgs[] = { [0] = { .addr = ft5x06_client->addr, .flags = 0, .len = len+1, .buf = buff, } }; buff[0] = reg_addr;//存放寄存器的地址 memcpy(&buff[1],&data,len); i2c_transfer(ft5x06_client->adapter,msgs,1); } static int ft5x06_read_reg(u8 reg_addr) { u8 data; struct i2c_msg msgs[] = { [0] = { .addr = ft5x06_client->addr, .flags = 0, .len = sizeof(reg_addr), .buf = ®_addr, }, [1] = { .addr = ft5x06_client->addr, .flags = 1, .len = sizeof(data), .buf = &data, } }; i2c_transfer(ft5x06_client->adapter,msgs,2); return data; } static const struct i2c_device_id ft5x06_id_ts[] = { {"xxx",0}, {} }; static const struct of_device_id ft5x06_id[] = { {.compatible = "edt,edt-ft5306",0,}, {.compatible = "edt,edt-ft5x06",0,}, {.compatible = "edt,edt-ft5406",0,}, {} }; int ft5x06_probe(struct i2c_client *client,const struct i2c_device_id *id) { int ret; printk("ft5x06_probe\n"); ft5x06_client = client;//因为我们要在别的地方用到client,所以我们要把他复制出来 //注册一个杂项设备 //往地址为0x80的寄存器写入数据0x4b ft5x06_write_reg(0x80,0x4b,1); //读取寄存器地址为0x80的数据。 ret = ft5x06_read_reg(0x80); printk("ret is %#x",ret); return 0; } int ft5x06_remove(struct i2c_client *i2c_client) { return 0; } static struct i2c_driver ft5x06_driver = { //unsigned int class; //int (*probe)(struct i2c_client *, const struct i2c_device_id *); //int (*remove)(struct i2c_client *); //struct device_driver driver; //const struct i2c_device_id *id_table; .driver = { .owner = THIS_MODULE, .name = "ft5x06_test", .of_match_table = ft5x06_id, }, .probe = ft5x06_probe, .remove = ft5x06_remove, .id_table = ft5x06_id_ts }; static int ft5x06_driver_init(void) { int ret; ret = i2c_add_driver(&ft5x06_driver); if (ret < 0) { printk("i2c_add_driver is error\n"); return ret; } printk("i2c_add_driver init\n"); return 0; } static void ft5x06_driver_exit(void) { i2c_del_driver(&ft5x06_driver); printk("i2c_del_driver exit\n"); } module_init(ft5x06_driver_init); module_exit(ft5x06_driver_exit); MODULE_LICENSE("GPL");
至此,IIC驱动实验已经完成。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。