程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

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函数的介绍。

二、AT24C08驱动测试应用程序

在20.at24c08_dev下创建test文件夹,保存测试应用程序。

2.1 main

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/types.h>

#define i2c_dev "/dev/at24c08"

int main(void)
{
        int fd, i = 0, count = 0;
        fd = open(i2c_dev, O_RDWR);
         /* 读取的数据:片内地址+1个数据 */
        unsigned char rbuffer;
        unsigned char address[5];
        address[0] = 0x05;
        address[1] = 0x06;
        address[2] = 0x07;
        address[3] = 0x08;
        address[4] = 0x09;
        unsigned char data[5] = {'h', 'e', 'l', 'l', 'o'};
        unsigned char wbuffer[2]; /* 片内地址+h */
        if(fd < 0){
                printf("open %s failed!\n", i2c_dev);
                exit(1);
        }else {
                printf("open %s succeed!\n", i2c_dev);
                printf("\n#####################################\n");
                for(i = 0; i < 5; i++)
                {
                        rbuffer = address[i];
                        read(fd, &rbuffer, 1);
                        printf("-0x[%x]-", rbuffer);
                }
                printf("\n#####################################\n");
                for(i = 0; i < 5; i++)
                {
                        wbuffer[0] = address[i];
                        wbuffer[1] = data[i];
                        write(fd, wbuffer, 2);
                }
                printf("\n#####################################\n");
                for(i = 0; i < 5; i++)
                {
                        rbuffer = address[i];
                        read(fd, &rbuffer, 1);
                        printf("-0x[%x]-", rbuffer);
                }
                printf("\n");
        }
}

2.2 Makefile

all:
    arm-linux-gcc -march=armv4t -o main main.c
clean:
    rm -rf *.o main

三、烧录开发板测试

3.1 编译内核

编译内核:

make s3c2440_defconfig
make V=1 uImage

将uImage复制到tftp服务器路径下:

root@zhengyang:/work/sambashare/linux-5.2.8# cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

3.2 烧录内核

开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。

设置开发板ip地址,从而可以使用网络服务:

SMDK2440 # set ipaddr 192.168.0.105
SMDK2440 # save
Saving Environment to NAND...
Erasing NAND...

Erasing at 0x40000 -- 100% complete.
Writing to NAND... OK
SMDK2440 # ping 192.168.0.200
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 08:00:3e:26:0a:5b
operating at unknown: 0 mode
Using dm9000 device
host 192.168.0.200 is alive

设置tftp服务器地址,也就是我们ubuntu服务器地址:

set serverip 192.168.0.200
save

下载内核到内存,并写NAND FLASH:

tftp 30000000 uImage
nand erase.part kernel
nand write 30000000 kernel
bootm

3.3 编译驱动

执行make命令编译驱动,并将驱动程序拷贝到nfs文件系统:

root@zhengyang:/# cd /work/sambashare/drivers/20.at24c08_dev/
root@zhengyang:/work/sambashare/drivers/20.at24c08_dev# make
root@zhengyang:/work/sambashare/drivers/20.at24c08_dev# cp at24c08_drv.ko /work/nfs_root/rootfs/

安装驱动:

[root@zy:/]# insmod at24c08_drv.ko
at24c08_drv: loading out-of-tree module taints kernel.
alloc device number : major:[248], minor:[0] succeed!
create device file 'at24c08' succeed!
get i2c_client, client name = at24c08, addr = 0x50
get i2c_adapter, adapter name = s3c2410-i2c
at24c08 probe()
at24c08 i2c_driver was added into the system. 

查看设备节点文件:

[root@zy:/]# ls /dev/at24c08 -l
crw-rw----    1 0        0         155,   0 Jan  1 00:00 /dev/at24c08
[root@zy:/]# ls /sys/devices/platform/s3c2440-i2c.0/i2c-0/ -l
total 0
drwxr-xr-x    3 0        0                0 Jan  1 00:00 0-0050
--w-------    1 0        0             4096 Jan  1 00:00 delete_device
lrwxrwxrwx    1 0        0                0 Jan  1 00:00 device -> ../../s3c2440-i2c.0
-r--r--r--    1 0        0             4096 Jan  1 00:00 name
--w-------    1 0        0             4096 Jan  1 00:00 new_device
drwxr-xr-x    2 0        0                0 Jan  1 00:00 power
lrwxrwxrwx    1 0        0                0 Jan  1 00:00 subsystem -> ../../../../bus/i2c
-rw-r--r--    1 0        0             4096 Jan  1 00:00 uevent
[root@zy:/]# ls /sys/devices/platform/s3c2440-i2c.0/i2c-0/0-0050/ -l
total 0
lrwxrwxrwx    1 0        0                0 Jan  1 00:00 driver -> ../../../../../bus/i2c/drivers/at24c08
-r--r--r--    1 0        0             4096 Jan  1 00:00 modalias
-r--r--r--    1 0        0             4096 Jan  1 00:00 name
drwxr-xr-x    2 0        0                0 Jan  1 00:00 power
lrwxrwxrwx    1 0        0                0 Jan  1 00:00 subsystem -> ../../../../../bus/i2c
-rw-r--r--    1 0        0             4096 Jan  1 00:00 uevent
[root@zy:/]# ls /sys/bus/i2c/devices -l
total 0
lrwxrwxrwx    1 0        0                0 Jan  1 00:01 0-0050 -> ../../../devices/platform/s3c2440-i2c.0/i2c-0/0-0050
lrwxrwxrwx    1 0        0                0 Jan  1 00:01 i2c-0 -> ../../../devices/platform/s3c2440-i2c.0/i2c-0

3.4 编译测试应用程序

执行make命令编译测试应用程序,并将测试应用程序拷贝到nfs文件系统:

root@zhengyang:/work/sambashare/drivers/20.at24c08_dev# cd test
root@zhengyang:/work/sambashare/drivers/20.at24c08_dev/test# make
arm-linux-gcc -march=armv4t -o main main.c
root@zhengyang:/work/sambashare/drivers/20.at24c08_dev/test# cp ./main /work/nfs_root/rootfs

在开发板终端运行应用程序:

./main 

终端输出信息如下:

[root@zy:/]# ./main
at24c08 open
open /dev/at24c08 succeed!

#####################################
at24c08_read succeed           #I2C读完成
at24c08_read succeed
at24c08_read succeed
at24c08_read succeed
at24c08_read succeed
at24c08_write......             # 第一次写
write parameters : args[0] = 0x5, args[1] = 0x68
at24c08_write succeed
at24c08_write......            # 第二次写 
write parameters : args[0] = 0x6, args[1] = 0x65
at24c08_write succeed
at24c08_write......             # 第三次写 
write parameters : args[0] = 0x7, args[1] = 0x6c
at24c08_write succeed
at24c08_write......             # 第四次写 
write parameters : args[0] = 0x8, args[1] = 0x6c
at24c08_write succeed
at24c08_write......             # 第五次写
write parameters : args[0] = 0x9, args[1] = 0x6f
at24c08_write succeed           #I2C写完成
at24c08_read succeed            #I2C写完成 
at24c08_read succeed           
at24c08_read succeed
at24c08_read succeed
at24c08_read succeed
0x[62]--0x[61]--0x[79]--0x[21]--0x[21]-   #写之前读取的
#####################################

#####################################
-0x[68]--0x[65]--0x[6c]--0x[6c]--0x[6f]-    # 写之后读取的 hello

3.5 卸载at24c08驱动

通过用lsmod可以查看当前安装了哪些驱动:

[root@zy:/]# lsmod
at24c08_dev 3463 0 - Live 0xbf000000 (O)

卸载时直接运行:

[root@zy:/]# rmmod at24c08_drv
at24c08 remove()
at24c08 i2c_driver was deleted from the system.

四、代码下载

Young / s3c2440_project[drivers]

参考文章:

[1]I2C之AT24C08驱动及应用程序

[2]Linux·i2c驱动示例

[3]Linux I2C 驱动 24C256 E2PROM

[4]Linux3.4.2之IIC驱动

posted @ 2023-02-20 23:55  大奥特曼打小怪兽  阅读(528)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步