一. 简介
前一篇文章编写了SPI设备驱动框架代码,文章如下:
Linux下SPI设备驱动实验:SPI设备驱动框架编写-CSDN博客
本文继续SPI驱动代码的编写。向SPI驱动框架中加入字符设备驱动框架代码。
二. 向SPI驱动框架中加入字符设备驱动框架代码
1. 添加字符设备驱动框架的代码
打开 ubuntu系统,通过 vscode 打开18_spi 工程。向SPI设备驱动框架中添加字符设备驱动框架的代码。添加如下:
(1) 字符设备注册的一套流程,放在 spi_driver的 probe函数中。
(2) 字符设备注销的一套流程,放在 spi_driver的 remove函数中。
添加字符设备驱动框架的代码后,spi_icm20608.c文件中代码如下:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/of.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#define ICM20608_NAME "icm20608"
#define ICM20608_CNT 1
//设备结构体
struct icm20608_Dev{
dev_t devid; //设备号
int major; //主设备号
int minor; //次设备号
struct cdev led_cdev;
struct class * class; //类(自动创建设备节点用)
struct device * device; //设备(自动创建设备节点用)
};
struct icm20608_Dev icm20608_dev;
/*打开设字符备函数 */
static int icm20608_open(struct inode *inode, struct file *filp)
{
printk("icm20608_open\n");
return 0;
}
/*读字符设备中数据的函数*/
ssize_t icm20608_read(struct file * filp, char __user * buf, size_t count, loff_t * ppos)
{
int ret = 0;
printk("icm20608_read!\n");
return ret;
}
/*关闭设备函数*/
int icm20608_release(struct inode * inode, struct file * filp)
{
printk("led_release\n");
return 0;
}
//字符设备的函数集
const struct file_operations fops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.release = icm20608_release,
};
static int icm20608_probe(struct spi_device* spi_dev)
{
int ret = 0;
printk("icm20608_probe!\n");
//设备号的分配
icm20608_dev.major = 0;
if(icm20608_dev.major) //给定主设备号
{
icm20608_dev.devid = MKDEV(icm20608_dev.major, 0);
ret = register_chrdev_region(icm20608_dev.devid, ICM20608_CNT, ICM20608_NAME);
}
else{ //没有给定主设备号
ret = alloc_chrdev_region(&(icm20608_dev.devid), 0, ICM20608_CNT, ICM20608_NAME);
icm20608_dev.major = MAJOR(icm20608_dev.devid);
icm20608_dev.minor = MINOR(icm20608_dev.devid);
}
printk("dev.major: %d\n", icm20608_dev.major);
printk("dev.minor: %d\n", icm20608_dev.minor);
if (ret < 0) {
printk("register-chrdev failed!\n");
goto fail_devid;
}
//初始化设备
icm20608_dev.led_cdev.owner = THIS_MODULE;
cdev_init(&icm20608_dev.led_cdev, &fops);
//注册设备
ret = cdev_add(&icm20608_dev.led_cdev, icm20608_dev.devid, ICM20608_CNT);
if(ret < 0)
{
printk("cdev_add failed!\r\n");
goto fail_adddev;
}
/* 3.自动创建设备节点 */
//创建类
icm20608_dev.class = class_create(THIS_MODULE, ICM20608_NAME);
if (IS_ERR(icm20608_dev.class)) {
ret = PTR_ERR(icm20608_dev.class);
goto fail_class;
}
//创建设备
icm20608_dev.device = device_create(icm20608_dev.class, NULL, icm20608_dev.devid, NULL, ICM20608_NAME);
if (IS_ERR(icm20608_dev.device)) {
ret = PTR_ERR(icm20608_dev.device);
goto fail_dev_create;
}
return 0;
fail_dev_create:
class_destroy(icm20608_dev.class);
fail_class:
cdev_del(&icm20608_dev.led_cdev);
fail_adddev:
unregister_chrdev_region(icm20608_dev.devid, ICM20608_CNT);
fail_devid:
return ret;
}
static int icm20608_remove(struct spi_device* spi_dev)
{
printk("icm20608_remove!\n");
/*注销字符设备*/
//1. 删除设备
cdev_del(&icm20608_dev.led_cdev);
//2. 注销设备号
unregister_chrdev_region(icm20608_dev.devid, ICM20608_CNT);
/*摧毁类与设备(自动创建设备节点时用) */
//3. 摧毁设备
device_destroy(icm20608_dev.class, icm20608_dev.devid);
//4. 摧毁类
class_destroy(icm20608_dev.class);
return 0;
}
//传统驱动与设备匹配方法
static struct spi_device_id spi_device_id_table[] = {
{ "icm20608", 0},
{ }
};
//设备树匹配方法
static struct of_device_id of_device_table[] = {
{ .compatible = "alientek,icm20608" }, //必须与设备树中设备节点compatible值一致
{ }
};
/*SPI驱动结构体*/
struct spi_driver icm20608_driver = {
.driver = {
.name = "icm20608",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_device_table),
},
.id_table = spi_device_id_table,
.probe = icm20608_probe,
.remove = icm20608_remove,
};
/*模块加载 */
static int __init icm20608_init(void)
{
return spi_register_driver(&icm20608_driver);
}
/*模块卸载 */
static void __exit icm20608_exit(void)
{
spi_unregister_driver(&icm20608_driver);
}
/*驱动加载与卸载 */
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL"); //模块 Licence
MODULE_AUTHOR("WeiWuXian"); //作者
2. 编译驱动模块
对以上的驱动代码进行模块化编译:
wangtian@wangtian-virtual-machine:~/zhengdian_Linux/Linux_Drivers/18_spi$ make
make -C /home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/wangtian/zhengdian_Linux/Linux_Drivers/18_spi modules
make[1]: 进入目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
CC [M] /home/wangtian/zhengdian_Linux/Linux_Drivers/18_spi/spi_icm20608.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/wangtian/zhengdian_Linux/Linux_Drivers/18_spi/spi_icm20608.mod.o
LD [M] /home/wangtian/zhengdian_Linux/Linux_Drivers/18_spi/spi_icm20608.ko
make[1]: 离开目录“/home/wangtian/zhengdian_Linux/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga”
可以看出,驱动模块正常编译通过。
三. 编写应用程序(测试程序)
在 18_spi工程目录下,创建应用程序 icm20608_app.c文件。icm20608_app.c文件代码实现如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
/*
* 打开/关闭 Led灯
* 参数:
* ./icm20608_app /dev/icm20608
*/
int main(int argc, char* argv[])
{
int fd = 0,ret = 0;
char * device_name = NULL;
unsigned short data[3] = {0};
if(argc != 2)
{
printf("main's param number error!\n");
return -1;
}
device_name = argv[1];
fd = open(device_name, O_RDWR);
if(fd < 0)
{
printf("open led device failed!\n");
return -1;
}
ret = read(fd, data, sizeof(data));
close(fd);
return 0;
}
终端进入 18_spi工程的根目录下,输入如下命令编译应用程序:
arm-linux-gnueabihf-gcc icm20608_app.c -o icm20608_app
这里可以编译通过。生成 icm20608_app。
接下来通过运行应用程序,对驱动模块进行测试,确定是否会正常运行到 open函数,read函数,release函数。
测试方法以及确认SPI驱动是否运行正常,可以参考如下文章:
I2C驱动实验:测试I2C驱动框架代码-CSDN博客