【驱动】I2C驱动分析(六)-I2C驱动模板
前言
Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片、音视频采集芯片、音视频输出芯片、EEROM芯片、AD/DA转换芯片等等。下面我们看下如何写一个基本的I2C驱动。
内核I2C驱动框架
I2C driver端
I2C驱动初始化及模块声明
驱动初始化主要是调用内核的i2c_add_driver
向内核注册hello_drv
结构体。hello_drv
结构体中描述了驱动的名称。exit函数会调用i2c_del_driver
删除注册的hello_drv
。
/* 驱动入口函数 */
static int hello_i2c_drv_init(void)
{
int ret = 0;
printk(KERN_ALERT "hello i2c driver init!!!\n");
ret = i2c_add_driver(&hello_i2c_driver);
if(ret){
printk(KERN_ALERT "add driver failed!!!\n");
return -ENODEV;
}
return 0;
}
/* 驱动出口函数 */
static void hello_i2c_drv_exit(void)
{
i2c_del_driver(&hello_i2c_driver);
return ;
}
module_init(hello_i2c_drv_init);
module_exit(hello_i2c_drv_exit);
MODULE_DESCRIPTION("hello i2c driver");
MODULE_AUTHOR("ZhongYi");
MODULE_LICENSE("GPL");
i2c_driver结构体
每一个I2C设备驱动,必须首先创造一个i2c_driver
结构体对象,该结构体包含了I2C设备探测和注销的一些基本方法和信息。
static struct i2c_driver hello_i2c_driver =
{
.probe = hello_probe,
.remove = hello_remove,
.driver =
{
.owner = THIS_MODULE,
.name = "hello_i2c",
.of_match_table = of_match_ptr(hello_i2c_of_match),
},
.id_table = hello_drv_id_table,
};
name
字段标识本驱动的名称,hello_probe
和hello_remove
字段为函数指针,这两个函数在I2C设备注册和注销的时候会自动调用,需要自己实现这两个函数。接下来的任务就是填充i2c_driver 结构体。
hello_drv_id_table
hello_drv_id_table
表示以传统方式匹配设备和驱动程序。hello_i2c_of_match
表示以设备树方式匹配。现在大多数都是以设备树方式匹配。
/*传统方式匹配*/
static const struct i2c_device_id hello_drv_id_table[] = {
{"hello_i2c",0},
{},
};
/* 设备树匹配表 */
static const struct of_device_id hello_i2c_of_match[] = {
{
.compatible = "hello_i2c_driver",
},
{}
};
I2C驱动探测函数
probe
函数会以CLASS_NAME
创建一个class结构,这个动作将会在/sys/class
目录创建一个名为CLASS_NAME
的目录,接着以DEVICE_NAME
为名,参考/sys/class/CLASS_NAME
在/dev目录下创建一个设备:/dev/DEVICE_NAME*/
static int hello_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
printk(KERN_ALERT "addr = %x\n",client->addr);
hello_client = client;
major = register_chrdev(0,DEVICE_NAME,&file_oprts);
if(major < 0 ){
printk(KERN_ALERT "Register failed!!\r\n");
return major;
}
printk(KERN_ALERT "Registe success,major number is %d\r\n",major);
i2c_hello_cls = class_create(THIS_MODULE,CLASS_NAME);
if(IS_ERR(i2c_hello_cls))
{
unregister_chrdev(major,DEVICE_NAME);
return PTR_ERR(i2c_hello_cls);
}
i2c_hello_dev = device_create(i2c_hello_cls,NULL,MKDEV(major,0),NULL,DEVICE_NAME);
if(IS_ERR(i2c_hello_dev))
{
class_destroy(i2c_hello_cls);
unregister_chrdev(major,DEVICE_NAME);
return PTR_ERR(i2c_hello_dev);
}
printk(KERN_ALERT "i2c_hello device init success!!\r\n");
}
hello_remove
当匹配关系不存在时(device或是driver被卸载),调用remove
函数,remove
函数是probe
函数的反操作,将probe
函数中申请的资源全部释放。
static int hello_remove(struct i2c_client *client)
{
printk(KERN_ALERT "hello i2c remove!!!\n");
device_destroy(i2c_hello_cls,MKDEV(major,0));
class_unregister(i2c_hello_cls);
class_destroy(i2c_hello_cls);
unregister_chrdev(major,DEVICE_NAME);
return 0;
}
I2C file_operations 结构体
file_operations
结构体描述的read,write等函数,对应于用户空间的readwrite。在用户态可以直接操作/dev/DEVICE_NAME*/
节点来使用I2C驱动。
static struct file_operations file_oprts =
{
.open = i2c_hello_open,
.read = i2c_hello_read,
.write = i2c_hello_write,
.release = i2c_hello_release,
};
I2C 设备数据收发处理流程
通过 i2c_smbus_read_byte_data
函数对I2C 设备寄存器进行读写操作。使用i2c_smbus_read_byte_data
函数发送数据之前要先构建好 i2c_msg
。
static ssize_t i2c_hello_read(struct file *file,char *buf, size_t len,loff_t *offset)
{
int cnt = 0;
uint8_t reg = 0;
uint8_t val = 0;
copy_from_user(®,buf,1);
val = i2c_smbus_read_byte_data(hello_client,reg);
cnt = copy_to_user(&buf[1],&val,1);
return 1;
}
static ssize_t i2c_hello_write(struct file *file,const char *buf,size_t len,loff_t *offset)
{
uint8_t recv_msg[255] = {0};
uint8_t reg = 0;
int cnt = 0;
cnt = copy_from_user(recv_msg,buf,len);
reg = recv_msg[0];
if(i2c_smbus_write_byte_data(hello_client,reg,recv_msg[1]) < 0){
printk(KERN_ALERT " write failed!!!\n");
return -EIO;
}
return len;
}
完整代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
static int major;
static struct class *i2c_hello_cls;
static struct device *i2c_hello_dev;
static const char* CLASS_NAME = "I2C_HELLO_CLASS";
static const char* DEVICE_NAME = "I2C_HELLO_DEVICE";
static struct i2c_client *hello_client;
static int i2c_hello_open(struct inode *node, struct file *file)
{
printk(KERN_ALERT "hello i2c open!\n");
return 0;
}
static int i2c_hello_release(struct inode *node,struct file *file)
{
printk(KERN_INFO "hello i2c release!!\n");
return 0;
}
static ssize_t i2c_hello_read(struct file *file,char *buf, size_t len,loff_t *offset)
{
int cnt = 0;
uint8_t reg = 0;
uint8_t val = 0;
copy_from_user(®,buf,1);
val = i2c_smbus_read_byte_data(hello_client,reg);
cnt = copy_to_user(&buf[1],&val,1);
return 1;
}
static ssize_t i2c_hello_write(struct file *file,const char *buf,size_t len,loff_t *offset)
{
uint8_t recv_msg[255] = {0};
uint8_t reg = 0;
int cnt = 0;
cnt = copy_from_user(recv_msg,buf,len);
reg = recv_msg[0];
if(i2c_smbus_write_byte_data(hello_client,reg,recv_msg[1]) < 0){
printk(KERN_ALERT " write failed!!!\n");
return -EIO;
}
return len;
}
static struct file_operations file_oprts =
{
.open = i2c_hello_open,
.read = i2c_hello_read,
.write = i2c_hello_write,
.release = i2c_hello_release,
};
static int hello_remove(struct i2c_client *client)
{
printk(KERN_ALERT "hello i2c remove!!!\n");
device_destroy(i2c_hello_cls,MKDEV(major,0));
class_unregister(i2c_hello_cls);
class_destroy(i2c_hello_cls);
unregister_chrdev(major,DEVICE_NAME);
return 0;
}
static int hello_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
printk(KERN_ALERT "addr = %x\n",client->addr);
hello_client = client;
major = register_chrdev(0,DEVICE_NAME,&file_oprts);
if(major < 0 ){
printk(KERN_ALERT "Register failed!!\r\n");
return major;
}
printk(KERN_ALERT "Registe success,major number is %d\r\n",major);
i2c_hello_cls = class_create(THIS_MODULE,CLASS_NAME);
if(IS_ERR(i2c_hello_cls))
{
unregister_chrdev(major,DEVICE_NAME);
return PTR_ERR(i2c_hello_cls);
}
i2c_hello_dev = device_create(i2c_hello_cls,NULL,MKDEV(major,0),NULL,DEVICE_NAME);
if(IS_ERR(i2c_hello_dev))
{
class_destroy(i2c_hello_cls);
unregister_chrdev(major,DEVICE_NAME);
return PTR_ERR(i2c_hello_dev);
}
printk(KERN_ALERT "i2c_hello device init success!!\r\n");
}
/*传统方式匹配*/
static const struct i2c_device_id hello_drv_id_table[] = {
{"hello_i2c",0},
{},
};
/* 设备树匹配表 */
static const struct of_device_id hello_i2c_of_match[] = {
{
.compatible = "hello_i2c_driver",
},
{}
};
static struct i2c_driver hello_i2c_driver =
{
.probe = hello_probe,
.remove = hello_remove,
.driver =
{
.owner = THIS_MODULE,
.name = "hello_i2c",
.of_match_table = of_match_ptr(hello_i2c_of_match),
},
.id_table = hello_drv_id_table,
};
/* 驱动入口函数 */
static int hello_i2c_drv_init(void)
{
int ret = 0;
printk(KERN_ALERT "hello i2c driver init!!!\n");
ret = i2c_add_driver(&hello_i2c_driver);
if(ret){
printk(KERN_ALERT "add driver failed!!!\n");
return -ENODEV;
}
return 0;
}
/* 驱动出口函数 */
static void hello_i2c_drv_exit(void)
{
i2c_del_driver(&hello_i2c_driver);
return ;
}
module_init(hello_i2c_drv_init);
module_exit(hello_i2c_drv_exit);
MODULE_DESCRIPTION("hello i2c driver");
MODULE_AUTHOR("ZhongYi");
MODULE_LICENSE("GPL");
device端
dev_init
dev_init
调用i2c_get_adapter
函数,用来获取指定适配器的函数,适配器一般指板上I2C控制器,实现i2c底层协议的字节收发,特殊情况下,用普通gpio模拟I2C也可作为适配器。i2c_new_device
函数,用于创建一个新的I2C设备。函数传入参数adap
表示要使用的适配器,&hello_board
是一个指向设备描述结构体的指针,描述了要创建的设备的属性。
int dev_init(void)
{
adap = i2c_get_adapter(2);
if(IS_ERR(adap)){
printk(KERN_ALERT "I2c_get_adapter failed!!!\n");
return -ENODEV;
}
client = i2c_new_device(adap,&hello_board);
i2c_put_adapter(adap);
if(!client){
printk(KERN_ALERT "Get new device failed!!!\n");
return -ENODEV;
}
return 0;
}
I2C 设备信息描述
未使用设备树的时候,使用 i2c_board_info
结构体来描述一个具体的 I2C 设备。type
和 addr
这两个成员变量是必须要设置的,一个是 I2C 设备的名字,一个是 I2C 设备的器件地址。当这个模块被加载时,i2c总线将使用hello_i2c
名称匹配相应的drv。
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
static struct i2c_board_info hello_board = {
I2C_BOARD_INFO("hello_i2c",I2C_DEVICE_ADDR),
};
使用设备树的时候,通过在设备树中创建相应的节点就行了。
&i2c1 {
clock - frequency = <100000>;
pinctrl - names = "default";
pinctrl - 0 = <&pinctrl_i2c1>;
status = "okay";
mag3110 @0e {
compatible = "hello_i2c_driver";
reg = <0x0e>;
position = <2>;
};
};
现在基本上都使用设备树
完整代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/regmap.h>
static struct i2c_adapter *adap;
static struct i2c_client *client;
#define I2C_DEVICE_ADDR 0x68
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
static struct i2c_board_info hello_board = {
I2C_BOARD_INFO("hello_i2c",I2C_DEVICE_ADDR),
};
int dev_init(void)
{
adap = i2c_get_adapter(2);
if(IS_ERR(adap)){
printk(KERN_ALERT "I2c_get_adapter failed!!!\n");
return -ENODEV;
}
client = i2c_new_device(adap,&hello_board);
i2c_put_adapter(adap);
if(!client){
printk(KERN_ALERT "Get new device failed!!!\n");
return -ENODEV;
}
return 0;
}
void dev_exit(void)
{
i2c_unregister_device(client);
return ;
}
用户态代码
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
static char buf[256] = {1};
int main(int argc,char *argv[])
{
int ret = 0;
uint8_t buf[2] = {0};
char cmd[6] = {0};
int reg_addr = 0;
int value = 0;
int fd = open("/dev/I2C_HELLO_DEVICE",O_RDWR);
if(fd < 0)
{
perror("Open file failed!!!\r\n");
}
while(1){
printf("Enter your cmd:<read/write> <reg_addr> <val> : \n");
scanf("%s",cmd);
scanf("%x",®_addr);
scanf("%x",&value);
printf("%s :%x :%x\n",cmd,reg_addr,value);
if(0 == memcmp(cmd,"write",5)){
buf[0] = reg_addr;
buf[1] = value;
int ret = write(fd,buf,2);
if(ret < 0){
perror("Failed to write!!\n");
}else{
printf("Write value %x to reg addr %x success\n",value,reg_addr);
}
}
else if(0 == memcmp(cmd,"read",4)){
buf[0] = reg_addr;
ret = read(fd,buf,1);
if(ret < 0){
perror("Read failed!!\n");
}else{
printf("Read %x from addr %x\n",buf[1],reg_addr);
}
}
else{
printf("Unsupport cmd\n");
}
memset(cmd,0,sizeof(cmd));
}
close(fd);
return 0;
}
模拟I2C驱动
模拟I2C驱动主要是配置dts,没什么可讲的。大家可以参考这篇文章:https://blog.csdn.net/landishu/article/details/118441943
本文参考
https://blog.csdn.net/ZHONGCAI0901/article/details/131167968