【驱动】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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架