1、优缺点以及驱动的框架
上一个介绍的是总线驱动框架,是一种编程方式,将操作、资源、标准驱动分开,并没有使用到设备树,但是代码的框架和思路,与设备树基础下的驱动的框架和思路是一样的。
优点:各个功能进行划分开,方便对引脚进行修改,修改的时候,只需要对应修改设备树和驱动中对硬件的操作就可以了。
缺点:代码是比较冗余的。
与设备树相关的语法:设备树创建的基本语法,设备树基本性质的获得函数(大部分函数以“of_”开头,以及函数的头文件)。设备树的修改,设备树的编译,板子上设备树的修改。
设备树的修改和编译,以及板子上设备树的替代可以看自己的写的word笔记。
下面是设备树驱动代码的框架:
![](https://img-blog.csdnimg.cn/img_convert/1292ae66976f7cefa926d5055286f06a.png)
2、设备树结点添加代码
下面的宏要定义到设备树文件中。
#define GROUP_PIN(g,p) ((g<<16) | (p))
下面根节点里面的设备树节点的创建,也要添加到设备树里面。
设备树添加的内容与硬件是没有关系的,硬件的操作还要放在init(),ctl()函数中。
/ {
100ask_led@0 {
compatible = "100as,leddrv";
pin = <GROUP_PIN(3, 1)>;
};
100ask_led@1 {
compatible = "100as,leddrv";
pin = <GROUP_PIN(5, 8)>;
};
};
3、chip_led.h代码
主要就是声明自己创建的chip_operation控制函数结构体。
#ifndef __chip_led_h__
#define __chip_led_h__
#include "board_led.h"
//创建的设备类,用于init和ctl函数
struct chip_operation
{
int (*init) (int devnumber);
int (*ctl) (int devnumber, char state);
};
#endif
4、chip_led.c代码
主要实现了硬件操作函数、根据产生的设备树结点创建文件设备结点。
#include "chip_led.h"
#include "led_dev.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
#include <asm/io.h> //ioremap()函数的头文件
#include <linux/of.h>
static int chip_board_resource[100];
static int chip_board_num=0;
//设备的基础物理地址
/* GPIO1_3 GPIO5_3 */
/* registers */
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0 地址:0x02290000 + 0x08
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0;
//GPIO5_GDIR 地址:0x020AC004
static volatile unsigned int *GPIO5_GDIR;
//GPIO1_GDIR 地址:0x0209C004
static volatile unsigned int *GPIO1_GDIR;
//GPIO5_DR 地址:0x020AC000
static volatile unsigned int *GPIO5_DR;
//GPIO1_DR 地址:0x0209C000
static volatile unsigned int *GPIO1_DR;
//devnumber这个参数对应的是board_led.c文件里面的结构体的下标
int chip_dev_init (int devnumber)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
switch(devnumber)
{
case 0:
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14,4);
GPIO5_GDIR = ioremap(0x020AC004,4);
GPIO5_DR = ioremap(0x020AC000,4);
//将引脚设置为gpio模式
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;
//将引脚设置为输出模式
*GPIO5_GDIR |= (1<<PIN(chip_board_resource[devnumber]));
break;
case 1:
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0 = ioremap(0x02290000 + 0x08,4);
GPIO1_GDIR = ioremap(0x0209C004,4);
GPIO1_DR = ioremap(0x0209C000,4);
//将引脚设置为gpio模式
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0 &= ~0xf;
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER0 |= 0x5;
//将引脚设置为输出模式
*GPIO1_GDIR |= (1<<PIN(chip_board_resource[devnumber]));
break;
case 2:
break;
case 3:
break;
default:
break;
}
return 0;
}
int chip_dev_ctl (int devnumber, char state)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
switch(devnumber)
{
case 0:
if(!state)
//对数据寄存器的操作。
*GPIO5_DR&=~(0x00000001<<PIN(chip_board_resource[devnumber]));
else
*GPIO5_DR|=(0x00000001<<PIN(chip_board_resource[devnumber]));
printk("group:%d pin:%d state:%d\n",GROUP(chip_board_resource[devnumber]),PIN(chip_board_resource[devnumber]),state);
break;
case 1:
if(!state)
*GPIO1_DR&=~(0x00000001<<PIN(chip_board_resource[devnumber]));
else
*GPIO1_DR|=(0x00000001<<PIN(chip_board_resource[devnumber]));
printk("group:%d pin:%d state:%d\n",GROUP(chip_board_resource[devnumber]),PIN(chip_board_resource[devnumber]),state);
break;
case 2:
break;
case 3:
break;
default:
break;
}
return 0;
}
static struct chip_operation chip_dev_operation=
{
.init = chip_dev_init,
.ctl = chip_dev_ctl,
};
//这个函数没有用到。
struct chip_operation* chip_get_operation(void)
{
return &chip_dev_operation;
}
//使用设备树进行创建设备的时候,对比上次,只需要对下面的两个函数进行修改就可以了。
//并且不需要在注册platform_device设备结构体了,系统会根据设备树进行注册设备结构体。
int chip_dev_create_device(struct platform_device *pdev)
{
struct device_node *np;
int res;
int led_pin;
//通过函数的入口参数pdev获得of_node,
//platform_device.dev.of_node 指向 device_node, 可以通过它获得其他属性
np = pdev->dev.of_node;
if (!np)
{
return -1;
}
res = of_property_read_u32(np, "pin", &led_pin);
chip_board_resource[chip_board_num] = led_pin;
led_dev_create_device(chip_board_num);
chip_board_num++;
return 0;
}
int chip_dev_destory_device(struct platform_device *pdev)
{
struct device_node *np;
int res,i;
int led_pin;
np = pdev->dev.of_node;
if (!np)
{
return -1;
}
res = of_property_read_u32(np, "pin", &led_pin);
for(i=0 ; i<chip_board_num ; i++)
{
if(led_pin == chip_board_resource[i])
{
led_dev_destroy_device(i);
chip_board_resource[i]=-1;
break;
}
}
for(i=0 ; i<chip_board_num ; i++)
{
if(chip_board_resource[i] != -1)
{
break;
}
}
if(i==chip_board_num)
chip_board_num=0;
return 0;
}
static const struct of_device_id ask100_leds[] = {
{ .compatible = "100as,leddrv" },
{ },
};
static struct platform_driver chip_platform_driver =
{
.probe = chip_dev_create_device,
.remove = chip_dev_destory_device,
//这里的name要和board中的结构体中的name相同才能进行匹配
.driver = {
.name = "100ask_led",
.of_match_table = ask100_leds, //表示支持设备树
},
};
static int __init chip_gpio_drv_init(void)
{
int err;
err = platform_driver_register(&chip_platform_driver);
//这里要调用驱动代码里面的上面chip_dev_operation结构体的获得函数。
led_dev_chip_operation(&chip_dev_operation);
return 0;
}
static void __exit lchip_gpio_drv_exit(void)
{
platform_driver_unregister(&chip_platform_driver);
}
module_init(chip_gpio_drv_init);
module_exit(lchip_gpio_drv_exit);
MODULE_LICENSE("GPL");
5、led_dev.h代码
struct chip_operation结构体函数的获得函数,文件设备结点的创建、删除函数。
#ifndef __led_dev_h__
#define __led_dev_h__
#include "chip_led.h"
void led_dev_create_device(int which);//创建次设备节点
void led_dev_destroy_device(int which);//删除次设备节点
void led_dev_chip_operation(struct chip_operation* oper);//让led_dev.c文件获得operation结构体
//上面的三个函数都是chip_led.c文件里面进行调用的
#endif
6、led_dev.c代码
主要实现主设备号的创建,设备类的创建,文件设备结点的创建、删除函数,struct chip_operation结构体获得函数的实现,文件的打开、关闭、读、写代码的实现。
#include "led_dev.h"
#include "chip_led.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
static int major = 0;//主设备号
static struct class *led_class;//设备类
static struct chip_operation* led_chip_operation;//init和ctl的控制函数的结构体
void led_dev_create_device(int which)
{
device_create(led_class, NULL, MKDEV(major, which) , 0, "mydevled%d", which);
}
void led_dev_destroy_device(int which)
{
device_destroy(led_class, MKDEV(major, which));
}
void led_dev_chip_operation(struct chip_operation* oper)
{
led_chip_operation = oper;
}
//EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,
//不用修改内核代码就可以在您的内核模块中直接调用,
//即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。
EXPORT_SYMBOL(led_dev_create_device);
EXPORT_SYMBOL(led_dev_destroy_device);
EXPORT_SYMBOL(led_dev_chip_operation);
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{ //变量的定义最好在函数的最前面,不然会产生不符合C90的语法错误
int err;
char mystate;
struct inode *inode;
int minor;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//根据应用层传来的fd获得次设备的名字
inode = file_inode(file);
//根据次设备的名字获得次设备号
minor = iminor(inode);
err = copy_from_user(&mystate, buf, 1);
//printk("minor:%d state:%d\n",minor,mystate);
//调用chip_led.c文件中的结构体内函数,进行控制
//得到的minor是board_led.c文件中的结构体的下标。
led_chip_operation->ctl(minor,mystate);
return 0;
}
static int led_drv_open (struct inode *node, struct file *file)
{
int minor;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//根据传入的设备驱动的名称获得次设备号
minor = iminor(node);
//调用chip_led.c文件中的结构体内函数,进行初始化
//得到的minor是board_led.c文件中的结构体的下标。
led_chip_operation->init(minor);
return 0;
}
static int led_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static struct file_operations led_file_operation=
{
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
static int __init led_init(void)
{ //注册函数里面只注册了主设备号、创建了设备类,并没有创建次设备节点和设备号
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "100ask_led_dev", &led_file_operation);
led_class = class_create(THIS_MODULE, "100ask_led_dev");
err = PTR_ERR(led_class);
if (IS_ERR(led_class))
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "100ask_led_dev");
return -1;
}
return 0;
}
static void __exit led_exit(void)
{ //只注销了设备类和主设备节点。
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
class_destroy(led_class);
unregister_chrdev(major, "100ask_led_dev");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
7、led_test.c代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/*
./les_test /dev/mydevled0 on
./les_test /dev/mydevled0 off
./les_test /dev/mydevled1 on
./les_test /dev/mydevled1 off
*/
int main(int argc, char** argv)
{
int fd;
unsigned char data;
if(argc != 3)
{
printf("Usage:%s is err\n",argv[0]);
printf(" ./led_test /dev/mydevled0 on\n");
printf(" ./led_test /dev/mydevled0 off\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if(fd == -1)
{
printf("%s can not open\n",argv[1]);
return -1;
}
if(strcmp(argv[2],"on") == 0)
{
data=0;
write(fd, &data, 1);
}else if(strcmp(argv[2],"off") == 0) //这里最好判断一下,不然识别不了off
{
data=1;
write(fd, &data, 1); //写入数据的格式和位数要注意,后面copy_from_user()要对应获得数据
}
return 0;
}
8、Makefile代码
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o led_test led_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f led_test
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
obj-m += chip_led.o led_dev.o