i2c读时序如图所示,通过时序图可以发现,i2c进行读操作可以概括为两大步,读和写。
1.先发送寄存器地址
2.读取寄存器值
i2c写时序如图所示,通过时序图可以发现,i2c进行写操作一步完成。
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 device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
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;
};
一个i2c_msg结构的变量,代表着一次单方向的传输。
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 标志位,指定进行的操作 0:write 1:read*/
#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 */
};
函数实现位置:drivers/i2c/i2c-core.c
int i2c_master_send(const struct i2c_client *client,
const char *buf,
int count);
int i2c_master_recv(const struct i2c_client *client,
char *buf,
int count);
注意count要小于64KB,因为i2c_msg的len成员变量是一个u16类型的数据
由于读操作分为写和读两个方向,因此采用两个i2c_msg结构体变量。
/*
* 入口参数@client:从机地址
* @reg :寄存器地址
* @buffer:保存读取数据
* @length:reg/buffer的长度
*/
static int i2c_read_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
{
int err = 0;
/* msg[0]是发送要读取的寄存器首地址 */
struct i2c_msg msg[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1, //表示寄存器地址字节长度,是以byte为单位
.buf = ®,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = length, //表示期望读到数据的字节长度(寄存器长度),是以byte为单位
.buf = buffer, //将读取到的数据保存在buffer中
},
};
err = i2c_transfer(client->adapter, msg,2);
if(err != 2)C
{
err = -EINVAL;
printk("read regs from ap3216c has been failed\n\r");
}
return err;
}
static int ap3216c_write_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
{
int err = 0;
u8 b[256];
struct i2c_msg msg;
b[0] = reg;
memcpy(&b[1],buffer,length);
msg.addr = client->addr,
msg.flags = 0;
msg.len = length + 1; /* +1是因为还有一个b[0]所存储的寄存器 */
msg.buf = b;
err = i2c_transfer(client->adapter, &msg,1);
if(err != 1)
{
err = -EINVAL;
printk("write data to ap3216c has been failed\n\r");
}
return err;
}
i2c设备树配置,最重要的信息是设备地址的配置。
&i2c1{
status = "okay";
rn6752: rn6752@2c {
compatible = "richnex,rn6752";//匹配名称
reg = <0x2c>;//设备地址
};
};
设备树修改完成以后使用 make dts 重新编译,然后使用新的设备树启动Linux内核。如果设备树修改正确的话,在/sys/bus/i2c/devices目录下看到一个0-002c的子目录。说明修改成功。
板级信息注册,系统会根据该信息生成对应的i2c_client结构
static struct i2c_board_info i2c_devices[] __initdata = {
{I2C_BOARD_INFO("24c02", 0x50), },
{}
};
i2c_register_board_info(0,i2c_devices,ARRAY_SIZE(i2c_devices));
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "rn6752V1_configuration.h"
#define rn6752_CNT 1
#define rn6752_NAME "rn6752"
struct rn6752_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
unsigned short ir, als, ps; /* 三个光传感器数据 */
};
static struct rn6752_dev rn6752dev;
/*
* @description : 从rn6752读取多个寄存器数据
* @param - dev: rn6752设备
* @param - reg: 要读取的寄存器首地址
* @param - val: 读取到的数据
* @param - len: 要读取的数据长度
* @return : 操作结果
*/
static int rn6752_read_regs(struct rn6752_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
printk("rn6752_read_regs add 0x%x,0x%x\n", client->addr, reg);
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* rn6752地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* rn6752地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
// printk("buf:0x%x,len:%d\n", *(msg[1].buf), msg[1].len);
// printk("0x%x\n", (*(msg[1].buf)) & 0xF1);
if (ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n", ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向rn6752多个寄存器写入数据
* @param - dev: rn6752设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 rn6752_write_regs(struct rn6752_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1], buf, len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* rn6752地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* @description : 读取rn6752指定寄存器值,读取一个寄存器
* @param - dev: rn6752设备
* @param - reg: 要读取的寄存器
* @return : 读取到的寄存器值
*/
static unsigned char rn6752_read_reg(struct rn6752_dev *dev, u8 reg)
{
u8 data = 0;
rn6752_read_regs(dev, reg, &data, 1);
return data;
#if 0
struct i2c_client *client = (struct i2c_client *)dev->private_data;
return i2c_smbus_read_byte_data(client, reg);
#endif
}
/*
* @description : 向rn6752指定寄存器写入指定的值,写一个寄存器
* @param - dev: rn6752设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void rn6752_write_reg(struct rn6752_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
rn6752_write_regs(dev, reg, &buf, 1);
}
/*
* @description : 读取rn6752的数据,读取原始数据,包括ALS,PS和IR, 注意!
* : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
* @param - ir : ir数据
* @param - ps : ps数据
* @param - ps : als数据
* @return : 无。
*/
void rn6752_readdata(struct rn6752_dev *dev)
{
unsigned char buf[6] = "";
/* 循环读取所有传感器数据 */
// for (i = 0; i < 6; i++) {
// buf[i] = rn6752_read_reg(dev, rn6752_IRDATALOW + i);
// }
if (buf[0] & 0X80) {
/* IR_OF位为1,则数据无效 */
dev->ir = 0;
} else {
/* 读取IR传感器的数据*/
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
}
/* 读取ALS传感器的数据*/
dev->als = ((unsigned short)buf[3] << 8) | buf[2];
/* IR_OF位为1,则数据无效*/
if (buf[4] & 0x40) {
dev->ps = 0;
} else {
/* 读取PS传感器的数据*/
dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) |
(buf[4] & 0X0F);
}
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int rn6752_open(struct inode *inode, struct file *filp)
{
filp->private_data = &rn6752dev;
/* 初始化rn6752 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t rn6752_read(struct file *filp, char __user *buf, size_t cnt,
loff_t *off)
{
short data[3];
long err = 0;
struct rn6752_dev *dev = (struct rn6752_dev *)filp->private_data;
rn6752_readdata(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int rn6752_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* rn6752操作函数 */
static const struct file_operations rn6752_ops = {
.owner = THIS_MODULE,
.open = rn6752_open,
.read = rn6752_read,
.release = rn6752_release,
};
/*
* @description : i2c驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c设备
* @param - id : i2c设备ID
* @return : 0,成功;其他负值,失败
*/
static int rn6752_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
unsigned char reg_addr = 0x00;
int nLoop = 0;
/* 1、构建设备号 */
if (rn6752dev.major) {
rn6752dev.devid = MKDEV(rn6752dev.major, 0);
register_chrdev_region(rn6752dev.devid, rn6752_CNT,
rn6752_NAME);
} else {
alloc_chrdev_region(&rn6752dev.devid, 0, rn6752_CNT,
rn6752_NAME);
rn6752dev.major = MAJOR(rn6752dev.devid);
}
/* 2、注册设备 */
cdev_init(&rn6752dev.cdev, &rn6752_ops);
cdev_add(&rn6752dev.cdev, rn6752dev.devid, rn6752_CNT);
/* 3、创建类 */
rn6752dev.class = class_create(THIS_MODULE, rn6752_NAME);
if (IS_ERR(rn6752dev.class)) {
return PTR_ERR(rn6752dev.class);
}
/* 4、创建设备 */
rn6752dev.device = device_create(rn6752dev.class, NULL, rn6752dev.devid,
NULL, rn6752_NAME);
if (IS_ERR(rn6752dev.device)) {
return PTR_ERR(rn6752dev.device);
}
rn6752dev.private_data = client;
/*读取寄存器状态,测试链接是否正确*/
rn6752_read_reg(&rn6752dev, reg_addr);
// unsigned char buf;
// int ret = 0;
// printk("0----------0x%x,reg:0x%x\n", client->addr, reg_addr);
// ret = i2c_master_send(client, ®_addr, 1); // 发送寄存器地址
// printk("1----------ret:%d\n", ret);
// ret = i2c_master_recv(client, &buf, 1); // 接收寄存器的值
// printk("2----------ret:%d,0x%x\n", ret,buf);
/*写寄存器,配置rn6752输出格式*/
for (nLoop = 0; nLoop < sizeof(HD_720P25_video) / sizeof(char);) {
rn6752_write_reg(&rn6752dev, HD_720P25_video[nLoop],
HD_720P25_video[nLoop + 1]);
nLoop += 2;
}
return 0;
}
/*
* @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
* @param - client : i2c设备
* @return : 0,成功;其他负值,失败
*/
static int rn6752_remove(struct i2c_client *client)
{
/* 删除设备 */
cdev_del(&rn6752dev.cdev);
unregister_chrdev_region(rn6752dev.devid, rn6752_CNT);
/* 注销掉类和设备 */
device_destroy(rn6752dev.class, rn6752dev.devid);
class_destroy(rn6752dev.class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id rn6752_id[] = { { "richnex,rn6752", 0 }, {} };
/* 设备树匹配列表 */
static const struct of_device_id rn6752_of_match[] = {
{ .compatible = "richnex,rn6752" },
{ /* Sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver rn6752_driver = {
.probe = rn6752_probe,
.remove = rn6752_remove,
.driver = {
.owner = THIS_MODULE,
.name = "rn6752",
.of_match_table = rn6752_of_match,
},
.id_table = rn6752_id,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init rn6752_init(void)
{
int ret = 0;
ret = i2c_add_driver(&rn6752_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit rn6752_exit(void)
{
i2c_del_driver(&rn6752_driver);
}
/* module_i2c_driver(rn6752_driver) */
module_init(rn6752_init);
module_exit(rn6752_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("[email protected]");
./i2cget -f -y 1 0x20 0x77 读取i2c总线1上0x20器件的0x77寄存器
./i2cset -f -y 1 0x20 0x77 0x3f 设置i2c总线1上地址为0x20器件的0x77寄存器地址为0x3f
static int i2c_read_bytes(struct i2c_client *client, u8 cmd, u8 *data, u8 data_len) {
struct i2c_msg msgs[2];
int ret;
u8 *buffer;
buffer = kzalloc(data_len, GFP_KERNEL);
if (!buffer)
return -ENOMEM;;
msgs[0].addr = client->addr;
msgs[0].flags = client->flags;
msgs[0].len = 1;
msgs[0].buf = &cmd;
ret = i2c_transfer(client->adapter, msgs, 1);
if (ret < 0) {
dev_err(&client->adapter->dev, "i2c read failed\n");
kfree(buffer);
return ret;
}
msgs[1].addr = client->addr;
msgs[1].flags = client->flags | I2C_M_RD;
msgs[1].len = data_len;
msgs[1].buf = buffer;
ret = i2c_transfer(client->adapter, &msgs[1], 1);
if (ret < 0)
dev_err(&client->adapter->dev, "i2c read failed\n");
else
memcpy(data, buffer, data_len);
kfree(buffer);
return ret;
}
把i2c设备当作一个普通的字符设备来处理,用i2c-dev.c文件提供的API,封装设备时序数据。直接操作i2c设备器驱动对应的设备文件,实现与设备通讯。
i2c-dev.c源码位置
drivers/i2c/i2c-dev.c
i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等文件操作接口,在用户空间的应用层可以借用这些接口访问挂接在适配器上的i2c设备的存储空间货寄存器。并控制i2c设备的工作方式。
i2c适配器的设备节点是/dev/i2c-x,其中x是数字,代表适配器的编号,适配器编号是动态分配的,所以想了解哪一个适配器对应什么编号,可以查看/sys/class/i2c-dev/目录下的文件内容。
[root@zlg /]# cat /sys/class/i2c-dev/i2c-0/name
PNX4008-I2C0
[root@zlg /]# cat /sys/class/i2c-dev/i2c-1/name
PNX4008-I2C1
[root@zlg /]# cat /sys/class/i2c-dev/i2c-2/name
USB-I2C
ioctl 命令
查看include/linux/i2c-dev.h文件,可以看到i2c-dev支持的IOCTL命令。如i2c-dev IOCTL命令
#define I2C_RETRIES 0x0701 /*设置收不到ACK时的重试次数*/
#define I2C_TIMEOUT 0x0702 /* 设置超时时限的jiffies */
#define I2C_SLAVE 0x0703 /*设置从机地址 */
#define I2C_SLAVE_FORCE 0x0706 /* 强制设置从机地址 */
#define I2C_TENBIT 0x0704 /*选择地址位长:=0 for 7bit , != 0 for 10 bit */
#define I2C_FUNCS 0x0705 /*获取适配器支持的功能 */
#define I2C_RDWR 0x0707 /*Combined R/W transfer (one STOP only) */
#define I2C_PEC 0x0708 /* != 0 to use PEC with SMBus */
#define I2C_SMBUS 0x0720 /*SMBus transfer */
使用例程
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
intfd, ret;
unsignedchar rdwr_addr = 0x42; /* e2prom 读写地址 */
unsignedchar device_addr = 0x50; /* e2prom 设备地址 */
unsignedchar data = 0x12; /* 向e2prom写的数据 */
structi2c_rdwr_ioctl_data e2prom_data;
fd= open("/dev/i2c/0", O_RDWR);
if(fd < 0) {
perror("openerror");
exit(1);
}
e2prom_data.msgs= (struct i2c_msg *)malloc(e2prom_data.nmsgs * \
sizeof(structi2c_msg));
if(e2prom_data.msgs == NULL) {
perror("mallocerror");
exit(1);
}
ioctl(fd,I2C_TIMEOUT, 1); /* 设置超时 */
ioctl(fd,I2C_RETRIES, 2); /* 设置重试次数 */
/*向e2prom的rdwr_addr地址写入数据data*/
e2prom_data.nmsgs= 1;
e2prom_data.msgs[0].len= 2;
e2prom_data.msgs[0].addr= device_addr;
e2prom_data.msgs[0].flags= 0; /* write */
e2prom_data.msgs[0].buf= (unsigned char *)malloc(2);
e2prom_data.msgs[0].buf[0]= rdwr_addr; /* write address */
e2prom_data.msgs[0].buf[1]= data; /* write data */
ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
if(ret < 0) {
perror("writedata error");
exit(1);
}
printf("writedata: %d to address: %#x\n", data, rdwr_addr);
data= 0; /* be zero*/
/*从e2prom的rdwr_addr地址读取数据存入buf*/
e2prom_data.nmsgs= 2;
e2prom_data.msgs[0].len= 1;
e2prom_data.msgs[0].addr= device_addr;
// e2prom_data.msgs[0].flags= 0; /* write */
e2prom_data.msgs[0].buf= &rdwr_addr;
e2prom_data.msgs[1].len= 1;
e2prom_data.msgs[1].addr= device_addr;
e2prom_data.msgs[1].flags= 1; /* read */
e2prom_data.msgs[1].buf= &data;
ret= ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
if(ret < 0) {
perror("readerror");
exit(1);
}
printf("read data: %d from address: %#x\n", data,rdwr_addr);
free(e2prom_data.msgs);
close(fd);
return0;
}