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驱动的自动探测功能,linux驱动移植-I2C驱动移植(OLED SSD1306)中案例采用这种方式;
- 手动进行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]
参考文章: