概述
- I2C驱动分为两个部分:主机驱动(SOC的I2C控制器驱动),设备驱动(针对具体设备编写的驱动),和platform驱动相似。
- 一般i2c控制器驱动SOC厂商已经写好了,我们只需要编写设备驱动。
- 本实验基于IIC2端口读取温度芯片TMP1075(设备地址0x48,温度寄存器地址0x00,2字节)。
基础知识
(一)IIC读时序
IIC读时序分为4大步,第一步时发送设备地址,第二步时发送要读取的寄存器地址,第三步重新发送设备地址,最后一步是IIC器件输出要读取的寄存器值。时序如下图:
(二)结构体i2c_client和i2c_drvier
I2C设备驱动重点关注两个数据结构体:i2c_client和i2c_drvier。
i2c_client:描述了设备信息,在include/linux/i2c.h中。
i2c_drvier:描述了驱动信息,类似于platform_driver。
(三)I2C_driver注册驱动示例代码
除此之外,参考linux自带的IIC设备驱动,可以学习相关IIC接口函数的使用。
extern int i2c_add_adapter(struct i2c_adapter *);
extern void i2c_del_adapter(struct i2c_adapter *);
extern int i2c_add_numbered_adapter(struct i2c_adapter *);
extern int i2c_register_driver(struct module *, struct i2c_driver *);
extern void i2c_del_driver(struct i2c_driver *);
IIC驱动框架 示例代码如下:
/*i2c驱动的probe函数*/
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
return 0;
}
/*i2c驱动的remove函数*/
static int xxx_remove(struct i2c_client *client)
{
return 0;
}
/* 传统匹配方式ID列表 (无设备树时的匹配ID表)*/
static const struct i2c_device_id xxx_id[] = {
{"xxx", 0},
{}
};
/* 设备树匹配列表 (设备树所使用的匹配表)*/
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx" },
{ /* Sentinel */ }
};
/*i2c驱动结构体 */
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};
/*驱动入口函数*/
static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_driver);
return ret;
}
/*驱动出口函数*/
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
编写设备树
在.dts文件中创建相应的设备节点
i2c2: i2c@f801c000 {// f0018000地址可以在datasheet中的Memory Mapping(内存映射)中找到。
status = "okay";
tmp1075@48 {//48为I2C设备地址,查找芯片手册可以查到。
compatible = "tmp1075";
reg = <0x48>;
};
};
编译设备树“make dtbs”下载过后,可以在i2c总线上看到新增的设备。
同时,系统的设备树也多了这项
说明:IIC2具体使用的引脚,在SOC的设备数据已经定义好了,如下图:
代码实现
直接上代码,详细解释看备注。
#include <linux/fs.h> /*包含file_operation结构体*/
#include <linux/init.h> /* 包含module_init module_exit */
#include <linux/module.h> /* 包含LICENSE的宏 */
#include <linux/miscdevice.h>/*包含miscdevice结构体*/
#include <linux/kernel.h> /*包含printk等操作函数*/
#include <asm/uaccess.h> /*包含copy_to_user操作函数*/
#include <linux/interrupt.h> /*包含request_irq操作函数*/
#include <linux/of.h> /*设备树操作相关的函数*/
#include <linux/of_gpio.h> /*of_get_named_gpio等函数*/
#include <linux/i2c.h> /*I2C驱动相关函数*/
void *private_data; /* 私有数据 */
static int tmp1075_read_regs(struct i2c_client *client, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* ap3216c地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* ap3216c地址 */
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=0x%x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
static int tmp1075_open(struct inode *inode, struct file *file)
{
file->private_data = private_data;
return 0;
}
/* 定义一个打开设备的,read函数 */
//读IIC某个寄存器步骤:写入寄存器(传输地址的时候标记为写),读值(传输地址的时候标记为写)。
ssize_t tmp1075_read(struct file *file, char __user *array, size_t size, loff_t *ppos)
{
long res;
unsigned char data[2];
struct i2c_client *dev = (struct i2c_client *)file->private_data;
tmp1075_read_regs(dev,0x00,data,2);
res = copy_to_user(array, data, sizeof(data));
return 0;
}
/*字符设备驱动程序就是为具体硬件的file_operations结构编写各个函数*/
static const struct file_operations tmp1075_ctl={
.owner = THIS_MODULE,
.open = tmp1075_open,
.read = tmp1075_read,
};
/*杂项设备,主设备号为10的字符设备,相对普通字符设备,使用更简单*/
static struct miscdevice tmp1075_miscdev = {
.minor = 255,
.name = "tmp1075",//设备节点名字
.fops = &tmp1075_ctl,
};
/*i2c驱动的probe函数,当驱动与设备匹配以后此函数就会执行*/
static int tmp1075_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
char res;
/*注册杂项设备驱动*/
printk("ap3216c_probe\r\n");
printk("ap3216c_probe addr111 =%x\r\n",client->addr);
res = misc_register(&tmp1075_miscdev);
printk(KERN_ALERT"tmp1075_probe = %d\n",res);
private_data = client;
return 0;
}
/*i2c驱动的remove函数,移除i2c驱动的时候此函数会执行*/
static int tmp1075_remove(struct i2c_client *client)
{
/*释放杂项设备*/
misc_deregister(&tmp1075_miscdev);
printk("tmp1075_remove\r\n");
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id tmp1075_id[] = {
{"tmp1075_dev", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id tmp1075_of_match[] = {
{ .compatible = "tmp1075" },
{ /* Sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver tmp1075_driver = {
.probe = tmp1075_probe,
.remove = tmp1075_remove,
.driver = {
.owner = THIS_MODULE,
.name = "tmp1075_dev",//i2c驱动名字和i2c设备名字匹配一致,才能进入probe函数。与platform一致
.of_match_table = tmp1075_of_match, //compatible用于匹配设备树
},
.id_table = tmp1075_id,
};
static int __init tmp1075_init(void)
{
int res;
res = i2c_add_driver(&tmp1075_driver);
printk("tmp1075_init = %d \r\n",res);
return 0;
}
static void __exit tmp1075_exit(void)
{
i2c_del_driver(&tmp1075_driver);
printk(KERN_ALERT"tmp1075_exit\r\n");
}
/*驱动模块的加载和卸载入口*/
module_init(tmp1075_init);
module_exit(tmp1075_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("boyee");
MODULE_DESCRIPTION("tmp1075 temperature read");
测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#define TMP1075_DEV "/dev/tmp1075"
int main(int argc, char * const argv[])
{
int fd = 0;
unsigned char data[3];;
int ret = 0;
fd = open(TMP1075_DEV, O_RDONLY);
if(fd <= 0)
{
perror("open err\r\n");
exit(1);
}
while(1){
ret = read(fd, data, 2);
if(ret == -1) {
perror("Failed to read.\n");
exit(1);
}
printf("temp register data = 0x%02x%02x\r\n",data[0],data[1]);
}
return 0;
}
运行测试App