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);