1、获取内核
要编译驱动文件,我们必需要有对应内核的原码,因此,我们需要在ubuntu上获取内核。在ubuntu16.04中输入下面的指令:
git clone https://github.com/xillybus/xillinux-kernel.git
等大约两个小时,就可以下载解压完成了。
给一下编译内核的代码,虽然这里用不到:
export CROSS_COMPILE=/path/to/crosscompiler/arm-xilinx-linux-gnueabi-
make ARCH=armxillinux_defconfig
make ARCH=arm -j 8uImage modules LOADADDR=0x8000
内核的编译用到的是arm-xilinx-linux-gnueabi-这一个编译器。
2、编写驱动程序
xillinux上的驱动开发与一般的arm差别不大,理论这里不讲,直接给出板上的BTN8和BTN9两个按键的读取驱动。
首先要看原理图,我们找到BTN8和BTN9两个按键对应于MIO50和MIO51。
下面就来说说写驱动的过程。
加载驱动的时候,首先要进入驱动的入口函数:
module_platform_driver(gpio_miobtn_driver);
通过这个函数,会读取gpio_miobtn_driver这个结构体。
其中,name要与设备树中写的一致,owner就是THIS_MODULE,of_match_table会匹配一个结构体gpio_btn_of_match:
这个结构体也是要与设备树匹配的。
当所有的参数都匹配后,就会进入probe函数
这个函数就相当于一个初始化函数了,用来初始化一些参数的,这里我用来初始化GPIO。
那么,下在就是GPIO的初始化了。
一些初始化定义的参数:
首先要定义一下device_node*np这个结构体,这是用来获取GPIO用的。
我把它定义成了全局变量,方便调用。
初始化时,就先初始化一下这个结构体:
初始化完后就获取一下GPIO,btn8和btn9与设备树中写的一致。
因为这里要调试,所以打印一下信息:
装载驱动的时候,会发现这两个引脚号分别是32和33。
获取到GPIO号后,就要进行GPIO初始化了。
来看下GPIO初始化函数。
首先定义一下GPIO的参数,用到gpio这个结构体:
依照地定义如下:
这里只有两个按键。
先检测一下GPIO是否可用:
不可用返回0,可用返回非0数吧。
检测到可用后,就申请一下GPIO:
好了,申请成功会返回0,否则返回非0。
GPIO的申请可以一组申请或者一个一个地申请。
可写一个申请出错的返回:
至此,GPIO的申请函数就写完了,回到probe函数。
申请到GPIO后,先读取一下它的值看看,这样就可以在装载驱动的时候读取一下它的值,看看效果。
初始化到这时就基本完成了,下面就注册一下杂项设备。
注册杂项设备:
注册杂项设备也是需要有一个结构体的,杂项设备可以在/dev/目录下查找到设备节点。
name不需要与设备树对应,可以自己定义,最后会显示在/dev/目录下的。
同样,在杂项设备下要有一个file_operations的结构体,这是在打开设备的操作设备时用到一个结构体,很重要。
这里,我只定义了它的打开,控制和关闭的三个函数,更多功能不在这里列出来了。
open函数没有什么可以说的。
关闭的函数也是。
这里关键是ioctl函数。
主要是通过CMD来控制一下读取哪个键值而已,其实也没有什么特别。
好了,初始化和操作部分就基本写完了。
卸载驱动的时候也是要做一些动做的,比如释放GPIO和注销杂项设备,这个就在platform_driver结构体的remove函数中。
注释写得很清楚了,这时不多说。
3、编写Makefile文件
新建一个名为Makefile的文件,在里面加入下图中的代码。
其中KDIR需要对应到你的内核代码目录。
4、编译驱动
把上面写好的驱动程序和Makefile文件得制到ubuntu中。
指定编译平台:exportARCH=arm
指定交叉编译器:exportCROSS_COMPILE= arm-xilinx-linux-gnueabi-
输入“make”进行编译。
最后把“.ko”文件复制到开发板上。
装载驱动:insmodfilename.ko
卸载驱动:rmmodfilename
5、编译设备树
为了让上面编译写的驱动程序能够顺利地注册,我们需要修改设备树文件。
在内核根目录下打开设备树文件:vimarch/arm/boot/dts/xillinux-zedboard.dts
按照下面的说明添加代码。
首先要指定匹配名称:compatible= "gpio-btns"。这个与gpio_miobtn_driver这个结构体中的DRIVER_NAME要致。
指定两个GPIO。
50和51分别对应到原理图上的MIO50和MIO51。
完整的代码如下:
在内核的根目录下输入指令:makeARCH=arm dtbs编译设备树。
最后在设备树文件夹中找到xillinux-zedboard.dtb文件,改名为devicetree.dtb,复制到SD卡上覆盖原来的文件。
重新启动开发板就可以顺利装载驱动了。
当初,为了写这个驱动,我也搞了很久,书上、网上都没找到参考的。所以,为了方便大家,我还是把完整的驱动代码贴上吧,如果要写其它驱动,也可以以这个为模板来写。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
//驱动和设备的名称
#define DRIVER_NAME "gpio-btns"
#define DEVICE_NAME "gpio-btns-ctl"
MODULE_LICENSE("GPL");
//PS按键设备结构体
struct gpio_btn_device {
const char *name;
//Pin Assignment
unsigned long BTN8;
unsigned long BTN9;
//platform device structures
struct platform_device *pdev;
};
//申请静态变量,方便调用
static struct gpio_btn_device *gpio_btn_dev;
//打开驱动的调用的函数
static int miobtn_open(struct inode *inode, struct file *file)
{
printk(KERN_EMERG "\tmiobtn open\n");
return 0;
}
//ioctl函数,主要用来返回读取到的按键值
static long miobtn_ioctl(struct file *filp, unsigned int CMD, unsigned long arg)
{
int btn;
printk(KERN_EMERG "\tmiobtn ioctl, CMD = %d, and arg is %ld but unuse!\n", CMD, arg);
switch(CMD)
{
case 8:
btn = gpio_get_value(gpio_btn_dev->BTN8);
break;
case 9:
btn = gpio_get_value(gpio_btn_dev->BTN9);
break;
default:
btn = -1;
break;
}
return btn;
}
static int miobtn_release(struct inode *inode, struct file *file)
{
printk(KERN_EMERG "\tmiobtn release\n");
return 0;
}
//file_operations结构体
static struct file_operations miobtn_ops =
{
.owner = THIS_MODULE,
.open = miobtn_open,
.unlocked_ioctl = miobtn_ioctl,
.release = miobtn_release,
};
//杂项设备的结构体
static struct miscdevice miobtn_dev =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &miobtn_ops,
};
static int gpio_btn_init_gpio(void)
{
struct gpio gpio_btn_ctrl[] = {
{ gpio_btn_dev->BTN8, GPIOF_IN, "BTN8" },
{ gpio_btn_dev->BTN9, GPIOF_IN, "BTN9" },
};
int status;
int i;
printk(KERN_EMERG "\tgpio init!\n");
//测试GPIO是否可用
for (i = 0; i < ARRAY_SIZE(gpio_btn_ctrl); i++) {
status = gpio_is_valid(gpio_btn_ctrl[i].gpio);
if (!status) {
printk(KERN_EMERG "\tBTN%d is invalid\n", i+8);
goto gpio_invalid;
}
}
printk(KERN_EMERG "\tBTN is valid\n");
//申请一组GPIO,当然也可以用gpio_request_one对一个GPIO单独申请
status = gpio_request_array(gpio_btn_ctrl, ARRAY_SIZE(gpio_btn_ctrl));
if (status) {
printk(KERN_EMERG "\tBTN request failed, status is %d\n", status);
//若申请不成功,则释放GPIO
gpio_free_array(gpio_btn_ctrl, 2);
goto gpio_invalid;
}
printk(KERN_EMERG "\tgpio request successfully!\n");
return 0;
gpio_invalid:
return status;
}
static const struct of_device_id gpio_btn_of_match[] = {
{ .compatible = "gpio-btns", },
{},
};
MODULE_DEVICE_TABLE(of, gpio_btn_of_match);
static int gpio_btn_of_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int status = 0;
int ret; //gpio request return value
int v_btn8, v_btn9; //初始化GPIO是获取一下键值的返回值变量
printk(KERN_EMERG "\tenter probe\n");
//Alloc Space for platform device structure
gpio_btn_dev = kzalloc(sizeof(*gpio_btn_dev), GFP_KERNEL);
if (!gpio_btn_dev) {
status = -ENOMEM;
goto dev_alloc_err;
}
//Get the GPIO Pins
gpio_btn_dev->BTN8 = of_get_named_gpio(np, "btn8", 0);
gpio_btn_dev->BTN9 = of_get_named_gpio(np, "btn9", 0);
//打印相关信息
pr_info(DRIVER_NAME " %s: BTN8: 0x%lx\n", np->name, gpio_btn_dev->BTN8);
pr_info(DRIVER_NAME " %s: BTN9 : 0x%lx\n", np->name, gpio_btn_dev->BTN9);
printk(KERN_EMERG "BTN8 = %lx!\n",gpio_btn_dev->BTN8);
printk(KERN_EMERG "BTN9 = %lx!\n",gpio_btn_dev->BTN9);
printk(KERN_EMERG "get the gpio!\n");
//申请GPIO
ret = gpio_btn_init_gpio();
if (ret) {
printk(KERN_EMERG "\tgpio request failed!\n");
goto gpio_init_error;
}
printk(KERN_EMERG "\tgpio request successed!\n");
//获取GPIO的初始值
v_btn8 = gpio_get_value(gpio_btn_dev->BTN8);
v_btn9 = gpio_get_value(gpio_btn_dev->BTN9);
printk(KERN_EMERG "\tget BTN value successed!\nBTN8 = %d, BTN9 = %d\n", v_btn8, v_btn9);
//杂项设备注册
misc_register(&miobtn_dev);
return 0;
dev_alloc_err:
return status;
gpio_init_error:
return ret;
}
static int gpio_btn_of_remove(struct platform_device *pdev)
{
//button GPIO
struct gpio gpio_btn_ctrl[] = {
{ gpio_btn_dev->BTN8, GPIOF_IN, "BTN8" },
{ gpio_btn_dev->BTN9, GPIOF_IN, "BTN9" },
};
printk(KERN_EMERG "\tenter remove\n");
//释放GPIO
//写一个释放GPIO的函数,因为如果卸载了驱动,没有释放GPIO
//那么第二次安装驱动的时候就会申请失败,当然你也可以重启一下系统,这太麻烦了。
gpio_free_array(gpio_btn_ctrl, 2);
//注销杂项设备
misc_deregister(&miobtn_dev);
return 0;
}
//平台文件结构体
static struct platform_driver gpio_miobtn_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = gpio_btn_of_match,
},
.probe = gpio_btn_of_probe,
.remove = gpio_btn_of_remove,
};
module_platform_driver(gpio_miobtn_driver);