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
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
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-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
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-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2022-02-20 linux同步机制-进程同步和调度