一、IIC简介
二、应用层实现IIC
应用层操作 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时,就是要发送的数据。
应用层代码实现IIC
#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;
}
IIC驱动开发
一. Linux I2C 驱动框架简介
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 函数,如下:
1、 i2c_get_adapter
函数作用:获得一个I2C适配器原型:
struct i2c_adapter *i2c_get_adapter(int nr);
参数:要获得的哪个 I2C 适配器的编号
返回值:失败返回 NULL
2、i2c_put_adapter 函数
作用:释放 I2C 适配器
原型:
void i2c_put_adapter(struct i2c_adapter *adap);
参数:要释放的 I2C 适配器
返回值:失败返回 NULL
3 、 i2c_new_device 函数
作用:把 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;
4、i2c_unregister_device 函数
作用:注销一个 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 。来看一下这个函数:
1、i2c_add_driver 函数作用:
注册一个 i2c 驱动 函数原型:
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
参数: struct i2c_driver 的指针。
返回值:失败返回负值
2、i2c_del_driver
函数作用:
删除一个 i2c 驱动 函数原型:
extern void i2c_del_driver(struct i2c_driver *);
参数: struct i2c_driver 的指针
返回值:失败返回负值
实验课
IIC_client代码部分编写:
#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");
IIC总线实现driver驱动:
#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驱动实验已经完成。