程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

linux驱动移植-I2C设备驱动移植(AT24C08)

一、AT24C08设备驱动

在上一节我们已经编写I2C适配器驱动,已经可以控制S3C2440 I2C控制器进行数据传输了,那么接下来我们开始编写I2C设备驱动,I2C设备驱动就是让内核知道什么时候发数据和发什么数据。

一般SOC的I2C适配器驱动都是由半导体厂商编写的,设备驱动开发者只要专注于 I2C 设备驱动即可。 I2C设备驱动的框架有多种,这里我们只介绍其中的一种示例代码。

1.1 项目结构

我们在/work/sambashare/drivers路径下创建项目20.at24c08_dev。

1.2 I2C驱动编写流程

1.2.1 模块入口函数
  • 声明i2c_driver结构体变量:
    • 设置成员probe:当驱动和设备信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中;
    • 设置成员remove:设备被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中;
    • 设置成员driver:设置驱动名称、owner等;
    • 设置成员id_table:id列表,用于和I2C从设备名称进行匹配;
    • 设置成员class:一般设置为I2C_CLASS_HWMON | I2C_CLASS_SPD即可;
  • 调用i2c_add_driver注册I2C驱动;
1.2.2  模块出口函数
  • 调用i2c_del_driver卸载I2C驱动;
1.2.3 probe函数

一般就是字符设备的注册流程:

  • 动态分配字符设备编号;
  • 字符设备初始化以及注册;
  • 创建类、以及在/dev路径下生成设备节点;
  • 保存当前i2c_client;
1.2.4 remove函数

一般就是字符设备的卸载流程:

  • 注销类、以及类下设备;
  • 移除字符设备;
  • 注销设备编号;
1.2.5 字符设备读写等函数

由于我们为I2C从设备注册了字符设备驱动,这样我们通过对字符设备节点读写,就可实现对硬件的读写操作。

我们需要实现字符设备的操作集合。也就是主要就是对各种I2C从设备的的读写操作,这一部分是通过控制I2C适配器来实现的,我们按照芯片读写指令时序进行I2C传输控制即可。

1.3  I2C从设备注册流程

I2C设备的注册可以分为两种:

手动I2C从设备的注册又有多种方式,比如方式一:

  • 定义板级I2C从设备信息;
  • 在注册I2C适配器之前调用i2c_register_board_info进行I2C从设备的注册;

方式二:

  • 定义板级I2C从设备信息;
  • 通过i2c_get_adapter函数获取I2C适配器,参数是I2C适配器编号;
  • 调用i2c_new_device进行I2C从设备的注册,将I2C从设备关联到当前I2C适配器上;
  • 调用i2c_put_adapter将添加完设备的结构体放回去,

1.4 at24c08_drv.c 

创建文件at24c08_drv.c:

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>

/*
 全局静态变量:希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在
 定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用
 */
#define OK   (0)
#define ERROR  (-1)

/* I2C设备驱动 */
static struct i2c_driver at24c08_driver;

/* I2C从设备 */
static struct i2c_client *at24c08_client;

 /* i2c设备地址 */
static unsigned short addr = 0x50;

/* 设备编号 */
static dev_t devid;

/* 设备class */
static struct class *at24c08_class;

 /* at24c08字符设备哦 */
static struct cdev i2c_cdev;

/**
 *   Function    : at24c08读函数
 *   Input       : buffer 1个字节 at24c08片内地址 0~0xFF
 *                 size  读取数据的长度  1次只能读取一个字节
 */
static ssize_t at24c08_read(struct file *filp, char __user *buffer, size_t size, loff_t *off)
{
        struct i2c_msg msg[2];    /* 封装消息 */
        unsigned char args;       /* 参数和数据 */
        unsigned char data;       /* 要返回的数据 */
        if(size != 1){
                return -EINVAL;
        }

        /* 从用户空间读取一个参数赋值给args,也就是把要读取的地址传递给内核,args就是要读取的地址,由用户给出 */
        copy_from_user(&args, buffer, 1);

        /* at24c08随机读操作 先写地址,再读取数据 一共2次通讯 */
        msg[0].addr = addr;   /* 设备地址 */
        msg[0].buf = &args;   /* 要读取的地址 */
        msg[0].len = 1;       /* 消息的长度 */
        msg[0].flags = 0;     /**/

        /* 再读 */
        msg[1].addr = addr;
        msg[1].buf = &data;        /* 接收读取的数据 */
        msg[1].len = 1;            /* 要读取的数据长度 */
        msg[1].flags = I2C_M_RD;   /**/

        /* 与目标设备进行2次通讯 */
        if(i2c_transfer(at24c08_client->adapter, msg, 2) == 2) {
                /* 返回2,表示成功通讯2次 */
                copy_to_user(buffer, &data, 1);
                printk(KERN_INFO "at24c08_read succeed\n");
                return 1;
        }
        else{
                printk(KERN_INFO "at24c08_read failed\n");
                return -EIO;
        }
}

/*
 *   Function:    at24c08写函数
 *   Input       : buffer 2个字节 第一个字节at24c08片内地址0~0xFF,第二个字节为要写入的数据
 *                 size  写数据的长度  两个字节
 */
static ssize_t at24c081_write(struct file *filp, const char __user *buffer, size_t size, loff_t *off)
{
        struct i2c_msg msg[1];
        unsigned char args[2];
        if(size != 2){
                return -EINVAL;
        }

        printk(KERN_INFO "at24c08_write......\n");
        /* 成功返回0;失败返回未完成字节数 */
        copy_from_user(&args, buffer, 2);
        printk(KERN_INFO "write parameters : args[0] = 0x%x, args[1] = 0x%x\n", args[0], args[1]);

        /* args[0]:addr, args[1]:value */
        msg[0].addr = addr;    /* 设备地址 */
        msg[0].buf = args;     /* 写入的数据 */
        msg[0].len = 2;        /* 长度 */
        msg[0].flags = 0;      /* 写标志 */
        if(i2c_transfer(at24c08_client->adapter, msg, 1) == 1){
                printk(KERN_INFO "at24c08_write succeed\n");
                return 2;
        }
        else{
                printk(KERN_INFO "at24c08_write failed\n");
                return -EIO;
        }
}

/*
 * 打开设备
 */
int at24c08_open(struct inode *inode, struct file *filp)
{
        printk(KERN_INFO "at24c08 open\n");
        return 0;
}

/*
 * at24c08操作集
 */
static struct file_operations at24c08_fops = {
        .owner = THIS_MODULE,
        .read = at24c08_read,
        .write = at24c081_write,
        .open = at24c08_open,
};

/*
* 当驱动和设备信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中;
*/
static int at24c08_probe(struct i2c_client *client, const struct i2c_device_id *i2c_device)
{
        int result;
         /* 动态分配字符设备编号: (major,0) */
        if(alloc_chrdev_region(&devid, 0, 1, "at24c08") == OK){
             printk(KERN_INFO "alloc device number : major:[%d], minor:[%d] succeed!\n", MAJOR(devid), MINOR(devid));
        }else{
              printk(KERN_INFO "register device number error!\n");
              return ERROR;
         }

        /* 字符设备初始化以及注册 */
        cdev_init(&i2c_cdev, &at24c08_fops);
        cdev_add(&i2c_cdev, devid, 1);


        /* 创建类,它会在sys目录下创建/sys/class/at24c08_class这个类  */
        at24c08_class = class_create(THIS_MODULE, "at24c08_class");
        if(IS_ERR(at24c08_class)){
            printk("can't create class\n");
            return ERROR;
       }

        /*  在/sys/class/at24c08_class下创建at24c08设备,然后mdev通过这个自动创建/dev/at24c08这个设备节点 */
        device_create(at24c08_class, NULL, devid, NULL, "at24c08");
        printk(KERN_INFO "create device file 'at24c08' succeed!\n");

        /* 保存当前i2c_client */
        at24c08_client = client;
        printk(KERN_INFO "get i2c_client, client name = %s, addr = 0x%x\n", at24c08_client->name, at24c08_client->addr);
        printk(KERN_INFO "get i2c_adapter, adapter name = %s\n", at24c08_client->adapter->name);

        printk(KERN_INFO "at24c08 probe()\n");
        return 0;
}

/*
 * 设备被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中
 */
static int at24c08_remove(struct i2c_client *client)
{
        /* 注销类、以及类设备 /sys/class/at24c08_class会被移除*/
        device_destroy(at24c08_class, devid);
        class_destroy(at24c08_class);

        /* 移除字符设备 */
        cdev_del(&i2c_cdev);

        /* 注销设备编号 */
        unregister_chrdev_region(devid, 1);
        printk(KERN_INFO "at24c08 remove()\n");
        return 0;
}


/* i2c设备id列表 */
static const struct i2c_device_id at24c08_id[] = {
        { "at24c08", 0 },
        { } /* 最后一个必须为空,表示结束 */
};


/*
 * i2c驱动入口函数
*/
static struct i2c_driver at24c08_driver = {
        .probe = at24c08_probe,
        .remove = at24c08_remove,
        .driver = {
                .name = "at24c08",      /* 驱动名称 */
                .owner = THIS_MODULE,
        },
        .id_table = at24c08_id, /* id列表 */
        .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,       /* 内存设备 */
};


/*
 *init入口函数
*/
static int __init at24c08_init(void)
{
        i2c_add_driver(&at24c08_driver); /* 将i2c_driver注册到系统中去 */
        printk(KERN_INFO "at24c08 i2c_driver was added into the system.\n");
        return 0;
}

/*
 * exit出口函数
 */
static void __exit at24c08_exit(void)
{
        i2c_del_driver(&at24c08_driver);
        printk(KERN_INFO "at24c08 i2c_driver was deleted from the system.\n");
        return ;
}


module_init(at24c08_init);
module_exit(at24c08_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("at24c08 device driver, 2023-02-19");
复制代码

1.5 Makefile

KERN_DIR :=/work/sambashare/linux-5.2.8
all:
    make -C $(KERN_DIR) M=`pwd` modules
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order

obj-m += at24c08_drv.o

1.6 注册I2C板级设备

修改版级arch/arm/mach-s3c24xx/mach-smdk2440.c。

(1) 添加引用 #include <linux/i2c.h>;

(2) 添加I2C从设备信息

static struct i2c_board_info i2c_devices[] __initdata = {
    {I2C_BOARD_INFO("at24c08", 0x50),},    /* 设备名称,设备地址(7位) */
};

(3) 在smdk2440_machine_init添加函数 

i2c_register_board_info(0, i2c_devices, ARRAY_SIZE(i2c_devices));    /* 注册板级设备: I2C从设备所属I2C控制器编号,设备信息,设备数量 */

需要注意的是这个函数要在注册I2C适配器之前执行,也就是这行代码要放在platform_add_devices之前。

具体原因这里不再阐述,可以参考上一节关于i2c_register_board_info函数的介绍。

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(596)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于决定:把自己家的能源管理系统开源了!
· C#实现 Winform 程序在系统托盘显示图标 & 开机自启动
· 了解 ASP.NET Core 中的中间件
· 实现windows下简单的自动化窗口管理
· 【C语言学习】——命令行编译运行 C 语言程序的完整流程
历史上的今天:
2022-02-20 linux同步机制-进程同步和调度
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示