参考:Linux I2C驱动框架(超详细)_JT同学的博客-CSDN博客_i2c驱动
一、几个重要的对象 1、I2C总线struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
I2C总线对应着/bus下的一条总线,这个i2c总线结构体管理着i2c设备与I2C驱动的匹配,删除等操作,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,如果匹配就调用i2c_device_probe函数,进而调用I2C驱动的probe函数。
2、I2C驱动struct i2c_driver {
int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函数
struct device_driver driver; //表明这是一个驱动
const struct i2c_device_id *id_table; //要匹配的从设备信息(名称)
int (*detect)(struct i2c_client *, struct i2c_board_info *); //设备探测函数
const unsigned short *address_list; //设备地址
struct list_head clients; //设备链表
};
对应的是I2C驱动程序
3、I2C设备struct i2c_client {
unsigned short addr; //设备地址
char name[I2C_NAME_SIZE]; //设备名称
struct i2c_adapter *adapter; //设配器,值I2C控制器
struct i2c_driver *driver; //设备对应的驱动
struct device dev; //表明这是一个设备
int irq; //中断号
struct list_head detected; //节点
};
对应的是I2C设备
4、I2C设配器I2C适配器是什么?
经过上面的介绍,知道有I2C驱动和I2C设备,我们需要通过I2C驱动去和I2C设备通讯,这其中就需要一个I2C设配器。
struct i2c_adapter {
unsigned int id; //设备器的编号
const struct i2c_algorithm *algo; //算法,发送时序
struct device dev; //表明这是一个设备
};
其中的i2c_algorithm是算法的意思,对应的就是如何发送I2C时序
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
};
小结:
I2C驱动有4个重要的东西,I2C总线、I2C驱动、I2C设备、I2C设备器
I2C总线:维护着两个链表(I2C驱动、I2C设备),管理I2C设备和I2C驱动的匹配和删除等
I2C驱动:对应的就是I2C设备的驱动程序
I2C设备:是具体硬件设备的一个抽象
I2C设配器:用于I2C驱动和I2C设备间的通用,是SOC上I2C控制器的一个抽象
以注册I2C驱动为例,简单讲解I2C总线的运行机制(I2C设备道理相同)
1、注册I2C驱动
2、将I2C驱动添加到I2C总线的驱动链表中
3、遍历I2C总线上的设备链表,根据i2c_device_match函数进行匹配,如果匹配成功系统则会分配一个i2c_client结构体,并当做参数传递给i2c_device_probe函数。
4、i2c_device_probe函数会调用I2C驱动的probe函数
对于i2c总线属于内核的内容,留着以后学~,i2c adapter已经由芯片厂商编写,因此我们只需学习设备驱动编写。
1、修改设备树从原理图中可以看出,UART4_RXD作为I2C1_SDA,UART4_TXD作为I2C1_SCL。
因此需要修改对应的pinctrl节点信息,使管脚复用为I2C1功能 。修改后结果如下:
随后在设备树i2c1接口上添加AP3216C设备节点信息,添加结束后如下:
其中@后面的“1e”是 ap3216c 的器件地址,compatible属性用于驱动和设备的匹配,reg属性也用来保存ap3216c的器件地址。
修改完设备树后,重启开发板可以发现i2c总线上已经可以看到ap3216c这个设备了。
2、 搭建i2c驱动框架#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ap3216creg.h" static struct i2c_device_id ap3216c_id[] = { {"ap3216c,idtable",0}, {}, }; static struct of_device_id ap3216c_of_match[] = { {.compatible = "ap3216c,tree"}, {}, }; static int ap3216c_probe(struct i2c_client *client,const struct i2c_device_id *id){ printk("ap3216c initrn"); return 0; } static int ap3216c_remove(struct i2c_client *client){ printk("ap3216c exitrn"); return 0; } static struct i2c_driver ap3216c_driver = { .probe = ap3216c_probe, .remove = ap3216c_remove, .driver = { .name = "ap3216c", .owner = THIS_MODULE, .of_match_table = of_match_ptr(ap3216c_of_match), }, .id_table = ap3216c_id, }; static int __init ap3216c_init(void){ int ret = 0; ret = i2c_add_driver(&ap3216c_driver); return ret; } static void __exit ap3216c_exit(void){ i2c_del_driver(&ap3216c_driver); } module_init(ap3216c_init); module_exit(ap3216c_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZYC");
i2c驱动框架采用,设备树匹配方式进行。当ap3216c_of_match[]中的compatible属性与设备树节点compatible属性匹配后就会执行.probe函数,卸载驱动时会执行.remove函数。
验证结果如下:
注意:我遇到了一个问题,当我把传统id匹配方式注释掉,只保留设备树匹配方式以后发现,.probe函数无法执行????我还无法解决,因此暂时跳过!!!完整驱动框架如下:
#include3、 AP3216C读写函数编写#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ap3216creg.h" #define AP3216C_NAME "AP3216C" #define AP3216C_COUNT 1 struct ap3216c_dev{ struct cdev cdev; dev_t devid; struct class *class; struct device *device; int major; int minor; }; struct ap3216c_dev ap3216c; static int ap3216c_open(struct inode *inode, struct file *filp){ filp->private_data = &ap3216c; printk("ap3216c openrn"); return 0; } static ssize_t ap3216c_read(struct file *file, char __user *buf, size_t count, loff_t *ppos){ printk("ap3216c readrn"); return 0; } static int ap3216c_release(struct inode *inode, struct file *filp){ printk("ap3216c releasern"); return 0; } static const struct file_operations ap3216c_fops = { .owner = THIS_MODULE, .open = ap3216c_open, .read = ap3216c_read, .release = ap3216c_release, }; static struct i2c_device_id ap3216c_id[] = { {"ap3216c,idtable",0}, {}, }; static struct of_device_id ap3216c_of_match[] = { {.compatible = "ap3216c,tree"}, {}, }; static int ap3216c_probe(struct i2c_client *client,const struct i2c_device_id *id){ int ret = 0; printk("ap3216c initrn"); ap3216c.major = 0; if(ap3216c.major){ ap3216c.devid = MKDEV(ap3216c.major, 0); ret = register_chrdev_region(ap3216c.devid, AP3216C_COUNT, AP3216C_NAME); } else{ ret = alloc_chrdev_region(&ap3216c.devid, 0, AP3216C_COUNT, AP3216C_NAME); ap3216c.major = MAJOR(ap3216c.devid); ap3216c.minor = MINOR(ap3216c.devid); } if(ret < 0){ printk("ap3216c register error!rn"); goto fail_devid; } printk("ap3216c major = %d, minor = %d rn", ap3216c.major, ap3216c.minor);//打印主次设备号 ap3216c.cdev.owner = THIS_MODULE; cdev_init(&ap3216c.cdev, &ap3216c_fops); ret = cdev_add(&ap3216c.cdev, ap3216c.devid, AP3216C_COUNT); if(ret < 0){ goto fail_cdev; } ap3216c.class = class_create(THIS_MODULE, AP3216C_NAME); if(IS_ERR(ap3216c.class)){ ret = PTR_ERR(ap3216c.class); goto fail_class; } ap3216c.device = device_create(ap3216c.class, NULL, ap3216c.devid, NULL, AP3216C_NAME); if(IS_ERR(ap3216c.device)){ ret = PTR_ERR(ap3216c.device); goto fail_device; } return 0; fail_device: class_destroy(ap3216c.class); fail_class: cdev_del(&ap3216c.cdev); fail_cdev: unregister_chrdev_region(ap3216c.devid, AP3216C_COUNT); fail_devid: return ret; } static int ap3216c_remove(struct i2c_client *client){ printk("ap3216c exitrn"); cdev_del(&ap3216c.cdev); unregister_chrdev_region(ap3216c.devid, AP3216C_COUNT); device_destroy(ap3216c.class, ap3216c.devid); class_destroy(ap3216c.class); return 0; } static struct i2c_driver ap3216c_driver = { .probe = ap3216c_probe, .remove = ap3216c_remove, .driver = { .name = "ap3216c", .owner = THIS_MODULE, .of_match_table = of_match_ptr(ap3216c_of_match), }, .id_table = ap3216c_id, }; static int __init ap3216c_init(void){ int ret = 0; ret = i2c_add_driver(&ap3216c_driver); return ret; } static void __exit ap3216c_exit(void){ i2c_del_driver(&ap3216c_driver); } module_init(ap3216c_init); module_exit(ap3216c_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZYC");
通过IIC适配器,来向AP3216C发送或读取数据
①向AP3216C寄存器写数据 如图是一个i2c的写数据时序图,从图中可以看出具体步骤:##器件地址为7位,末位0是写
开始信号-->发送i2c器件地址-->从机发送应答-->发送要写入寄存器地址-->从机发送应答-->发送要写入的数据-->从机发送应答-->停止信号。
具体函数为:
static s32 ap3216c_write_regs(struct ap3216c_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);
msg.addr = client->addr;
msg.flags = 0;
msg.buf = b;
msg.len = len + 1;
return i2c_transfer(client->adapter, &msg, 1);
}
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap3216c_write_regs(dev, reg, &buf, 1);
}
Linux内核采用msg这个参数描述一个消息。其中msg.addr保存i2c设备的器件地址。msg.flags是一个标志位,为0表示写数据。msg.buf保存要写数据的首地址。msg.len表示要写数据的长度,单位为字节。
注意:为什么写数据会有两个函数?因为ap3216c的寄存器长度是8位,有的i2c寄存器长度可能不止8位,因此s32 ap3216c_write_regs其实是一个通用写多个寄存器的函数,ap3216c_write_reg才是针对ap3216c的寄存器写数据的函数。
②从AP3216C寄存器读数据
如图是一个i2c的读数据时序图,从图中可以看出具体步骤:##器件地址为7位,末位1是读
开始信号-->发送i2c器件地址-->从机发送应答-->重新发送起始信号-->发送要读的寄存器地址-->从机发送应答-->重新发送起始信号-->重新发送i2c器件地址-->从机发送应答信号-->主机读取从机发送数据-->主机发送NO ACK表示读取完成-->主机发送STOP信号结束通讯
具体函数为:
static int ap3216c_read_regs(struct ap3216c_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;
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = ®
msg[0].len = 1;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = val;
msg[1].len = len;
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%dn",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
u8 data = 0;
ap3216c_read_regs(dev, reg, &data, 1);
return data;
}
i2c读数据的步骤比写数据麻烦一些。因为既包含写寄存器又包含读寄存器。因此需要建立一个msg[]数组保存两种信息。
4、完整驱动#include5、应用#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ap3216creg.h" #define AP3216C_CNT 1 #define AP3216C_NAME "ap3216c" struct ap3216c_dev { dev_t devid; struct cdev cdev; struct class *class; struct device *device; struct device_node *nd; int major; void *private_data; unsigned short ir, als, ps; }; static struct ap3216c_dev ap3216cdev; static int ap3216c_read_regs(struct ap3216c_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; msg[0].addr = client->addr; msg[0].flags = 0; msg[0].buf = ® msg[0].len = 1; msg[1].addr = client->addr; msg[1].flags = I2C_M_RD; msg[1].buf = val; msg[1].len = len; ret = i2c_transfer(client->adapter, msg, 2); if(ret == 2) { ret = 0; } else { printk("i2c rd failed=%d reg=%06x len=%dn",ret, reg, len); ret = -EREMOTEIO; } return ret; } static s32 ap3216c_write_regs(struct ap3216c_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); msg.addr = client->addr; msg.flags = 0; msg.buf = b; msg.len = len + 1; return i2c_transfer(client->adapter, &msg, 1); } static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg) { u8 data = 0; ap3216c_read_regs(dev, reg, &data, 1); return data; } static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data) { u8 buf = 0; buf = data; ap3216c_write_regs(dev, reg, &buf, 1); } void ap3216c_readdata(struct ap3216c_dev *dev) { unsigned char i =0; unsigned char buf[6]; for(i = 0; i < 6; i++) { buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i); } if(buf[0] & 0x80) dev->ir = 0; else dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0x03); dev->als = ((unsigned short)buf[3] << 8) | buf[2]; if(buf[4] & 0x40) dev->ps = 0; else dev->ps = ((unsigned short)(buf[5] & 0x3F) << 4) | (buf[4] & 0x0F); } static int ap3216c_open(struct inode *inode, struct file *filp) { filp->private_data = &ap3216cdev; ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04); mdelay(50); ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x03); return 0; } static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off) { short data[3]; long err = 0; struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data; ap3216c_readdata(dev); data[0] = dev->ir; data[1] = dev->als; data[2] = dev->ps; err = copy_to_user(buf, data, sizeof(data)); return 0; } static int ap3216c_release(struct inode *inode, struct file *filp) { return 0; } static const struct file_operations ap3216c_ops = { .owner = THIS_MODULE, .open = ap3216c_open, .read = ap3216c_read, .release = ap3216c_release, }; static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id) { if (ap3216cdev.major) { ap3216cdev.devid = MKDEV(ap3216cdev.major, 0); register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME); } else { alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME); ap3216cdev.major = MAJOR(ap3216cdev.devid); } cdev_init(&ap3216cdev.cdev, &ap3216c_ops); cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT); ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME); if (IS_ERR(ap3216cdev.class)) { return PTR_ERR(ap3216cdev.class); } ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME); if (IS_ERR(ap3216cdev.device)) { return PTR_ERR(ap3216cdev.device); } ap3216cdev.private_data = client; return 0; } static int ap3216c_remove(struct i2c_client *client) { cdev_del(&ap3216cdev.cdev); unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT); device_destroy(ap3216cdev.class, ap3216cdev.devid); class_destroy(ap3216cdev.class); return 0; } static const struct i2c_device_id ap3216c_id[] = { {"ap3216c,idtable",0}, {} }; static const struct of_device_id ap3216c_of_match[] = { { .compatible = "ap3216c,tree" }, { } }; static struct i2c_driver ap3216c_driver = { .probe = ap3216c_probe, .remove = ap3216c_remove, .driver = { .owner = THIS_MODULE, .name = "ap3216c", .of_match_table = ap3216c_of_match, }, .id_table = ap3216c_id, }; static int __init ap3216c_init(void) { int ret = 0; ret = i2c_add_driver(&ap3216c_driver); return ret; } static void __exit ap3216c_exit(void) { i2c_del_driver(&ap3216c_driver); } module_init(ap3216c_init); module_exit(ap3216c_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "sys/ioctl.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" #include结果测试:#include #include #include #include int main(int argc, char *argv[]) { int fd; char *filename; unsigned short databuf[3]; unsigned short ir, als, ps; int ret = 0; if (argc != 2) { printf("Error Usage!rn"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0) { printf("can't open file %srn", filename); return -1; } while (1) { ret = read(fd, databuf, sizeof(databuf)); if(ret == 0) { ir = databuf[0]; als = databuf[1]; ps = databuf[2]; printf("ir = %d, als = %d, ps = %drn", ir, als, ps); } usleep(200000); } close(fd); return 0; }



