Linux驱动之IIC实验(基于设备树编程)

概述

  • 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 = &reg;					/* 读取的首地址 */
	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

发布了49 篇原创文章 · 获赞 76 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/fengweibo112/article/details/103149840