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函数的介绍。
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
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,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于决定:把自己家的能源管理系统开源了!
· C#实现 Winform 程序在系统托盘显示图标 & 开机自启动
· 了解 ASP.NET Core 中的中间件
· 实现windows下简单的自动化窗口管理
· 【C语言学习】——命令行编译运行 C 语言程序的完整流程
2022-02-20 linux同步机制-进程同步和调度