【驱动】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_probehello_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(&reg,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(&reg,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 设备。typeaddr 这两个成员变量是必须要设置的,一个是 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",&reg_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

https://blog.csdn.net/chenyefei/article/details/51823851

.https://blog.csdn.net/ch122633/article/details/120144785

posted @ 2024-01-18 22:58  学习,积累,成长  阅读(578)  评论(0编辑  收藏  举报