通過之前的學習,瞭解到linux驅動編寫的流程是:先通過註冊函式註冊我們編寫的入口函式,然後在入口函式中獲取裝置號->註冊字元裝置->自動建立裝置節點->獲取裝置樹資訊,最後通過銷燬函式將出口函式中需要釋放的資源進行釋放,想知道具實現的小夥伴可以檢視我之前的文章。完成之前的學習,這篇文章所涉及的知識就比較簡單了,現在我們開始led驅動的學習。
一、準備材料
開發環境:VMware
作業系統:ubuntu
開發版:湃兔i2S-6UB
庫檔案:linux開發板或ubuntu的核心原始碼
二、GPIO原理圖
我用的是i2C-6ULX-B開發版,想要了解更多開發版的資訊可以檢視i2C-6ULX-B開發套件,外觀如下圖所示:
通過湃兔官方提供的原理圖,可以知道開發板上的兩個LED,有一個是電源指示燈通電就亮,所以我們能使用的只有一個,具體如下圖所示:
從原理圖中可知led是低電平亮,高電平熄,然後接著檢視湃兔核心板的引腳圖,如下圖所示:
最後在檢視湃兔官方提供的引腳定義,具體如下圖所以:
現在不用我多說小夥伴們都知道i2C-6ULX-B開發版上的led燈接的是晶片的gpio5.IO[5],在Linux中的GPIO計算方法是,GPIO_num = (<imx6ul_gpio_port> - 1) * 32 + <imx6ul_gpio_pin>,所以湃兔i2C-6ULX-B開發板的led接的是133引腳。
三、GPIO配置
瞭解led的硬體原理後,需要在裝置樹中進行配置,有需要的小夥伴可以瞭解湃兔官方的GPIO配置教程,好吧說得比較簡單,沒有學習過裝置樹的小夥伴可能看不懂,需要的可以百度搜尋下相關教程。在配置之前我們還需要了解一下什麼是GPIO子系統和pinctrl子系統,需要的朋友可以瞭解一下gpio子系統和pinctrl子系統(上)。
好吧,趕緊回來扯遠了,看不明白不要緊,我們主要是先實踐再理論,只學理論知識可能很讓人絕望啊,等用多了,再回頭學習自然就明白了。現在我們開始在裝置樹中配置gpio,開啟'arch/arm/boot/dts'目錄下的'i2c6ulxb-i2s6ull-emmc.dtsi'檔案,在跟節點中新增如下資訊
dtsled{
compatible = "i2som,gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_dtsled>;
led-gpios = <&gpio5 5 GPIO_ACTIVE_LOW>;
status = "okay";
};
如下圖所示:
細心的小夥伴可以已經看出來了,去註釋了一行資訊,因為湃兔的這個開發板只有一個led燈,然後被系統用於心跳燈使用,為了更好的驗證,所以我們把系統使用的心跳燈給註釋了,如下圖所示:
最後在'iomuxc_snvs'這個節點中新增如下資訊:
pinctrl_dtsled: dtsled {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER5__GPIO5_IO05 0x1b0b0
>;
};
如下圖所示:
到此我們的裝置樹已經更改完成了,接下來編寫驅動程式。
四、led驅動程式
其他的函式我就不過多介紹了,有需要的小夥伴可以檢視我直接的文章,我使用led的驅動函式是如下所示
/* 獲取GPIO */
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
/* 檢查gpio number是否合法 */
int gpio_to_irq(unsigned gpio)
/* 申請IO */
int gpio_request(unsigned gpio, const char *label)
/* 釋放IO */
void gpio_free(unsigned gpio)
/* 設定gpio 為輸入*/
int gpio_direction_input(unsigned gpio)
/* 設定IO為輸出模式 */
int gpio_direction_output(unsigned gpio, int value)
/* 設定IO輸出電平 */
gpio_set_value(unsigned gpio, int value)
/* 設定gpio的消抖時間 */
int gpio_set_debounce(unsigned gpio, unsigned debounce)
/* 獲取gpio對應的中斷線路 */
int gpio_to_irq(unsigned gpio)
/* gpio中斷 */
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
相信這些函式都不用過多的介紹,小夥伴們應該都知道怎麼使用了,如想了解具體的引數含義可以檢視Linux 驅動學習筆記 - gpio 子系統 (八)這篇文章,接下來開始編寫原始碼。
五、程式原始碼
驅動dtsled.c檔案
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#define DTSLED_NAME "dtsled"
#define DTSLED_COUNT 1
#define LEDOFF 0
#define LEDON 1
/*裝置結構體*/
struct dtsled_dev{
dev_t devid; /* 裝置號 */
int major; /* 主裝置號 */
int minor; /* 次裝置號 */
struct cdev cdev; /* 字元裝置 */
struct class *class; /* 類結構體 */
struct device *device; /* 裝置 */
struct device_node *nd; /* 裝置節點 */
int gpio_number; /* gpio的編號 */
};
struct dtsled_dev dtsled;
static int dtsled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &dtsled;
return 0;
}
static int dtsled_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t dtsled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
unsigned char databuf[1];
struct dtsled_dev *dev = filp->private_data;
ret = copy_from_user(databuf, buf, count);
if (ret < 0) {
return -EINVAL;
}
if (databuf[0] == LEDON) {
gpio_set_value(dev->gpio_number, 0);
} else if (databuf[0] == LEDOFF) {
gpio_set_value(dev->gpio_number, 1);
}
return 0;
}
/*
* 字元裝置操作集合
*/
static const struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.open = dtsled_open,
.release = dtsled_release,
.write = dtsled_write,
};
/*
* 模組入口
*/
static int __init dtsled_init(void)
{
int ret = 0;
printk("dtsled_init\r\n");
/* 申請裝置號 */
dtsled.major = 0; /* 設定裝置號由記憶體分配 */
if (dtsled.major){
dtsled.devid = MKDEV(dtsled.major, 0);
ret = register_chrdev_region(dtsled.devid, DTSLED_COUNT, DTSLED_NAME);
} else {
ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_COUNT, DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.devid);
}
if (ret < 0) {
printk("dtsled chrdev_region err!\r\n");
goto fail_devid;
}
/* 註冊字元裝置 */
dtsled.cdev.owner = dtsled_fops.owner;
cdev_init(&dtsled.cdev, &dtsled_fops);
ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_COUNT);
if (ret < 0) {
goto fail_cdev;
}
/* 自動建立裝置節點 */
dtsled.class = class_create(dtsled_fops.owner, DTSLED_NAME);
if (IS_ERR(dtsled.class)) {
ret = PTR_ERR(dtsled.class);
goto fail_class;
}
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if (IS_ERR(dtsled.device)) {
ret = PTR_ERR(dtsled.device);
goto fail_device;
}
/* 獲取裝置樹的屬性內容 */
dtsled.nd = of_find_node_by_path("/dtsled");
if (dtsled.nd == NULL) {
ret = -EINVAL;
goto fail_findnd;
}
/* 獲取GPIO */
dtsled.gpio_number = of_get_named_gpio(dtsled.nd, "led-gpios", 0);
if (dtsled.gpio_number < 0) {
printk("can't find led gpio");
ret = -EINVAL;
goto fail_rs;
} else {
printk("led gpio num = %d\r\n", dtsled.gpio_number);
}
/* 申請IO */
ret = gpio_request(dtsled.gpio_number, "led-gpio");
if (ret < 0) {
printk("failde to request the led gpio\r\n");
ret = -EINVAL;
goto fail_rs;
}
/* 設定IO為輸出模式 */
ret = gpio_direction_output(dtsled.gpio_number, 1);
if (ret < 0) {
ret = -EINVAL;
goto fail_setoutput;
}
/* 設定IO預設輸出低電平 */
gpio_set_value(dtsled.gpio_number, 0);
return 0;
fail_setoutput:
gpio_free(dtsled.gpio_number);
fail_rs:
fail_findnd:
device_destroy(dtsled.class, dtsled.devid);
fail_device:
class_destroy(dtsled.class);
fail_class:
cdev_del(&dtsled.cdev);
fail_cdev:
unregister_chrdev_region(dtsled.devid, DTSLED_COUNT);
fail_devid:
return ret;
}
/*
* 模組出口
*/
static void __exit dtsled_exit(void)
{
printk("dtsled_exit\r\n");
/* 關閉led */
gpio_set_value(dtsled.gpio_number, 1);
/* 刪除字元裝置 */
cdev_del(&dtsled.cdev);
/* 釋放字元設號 */
unregister_chrdev_region(dtsled.devid, DTSLED_COUNT);
/* 摧毀裝置 */
device_destroy(dtsled.class, dtsled.devid);
/* 摧毀類 */
class_destroy(dtsled.class);
/* 釋放IO */
gpio_free(dtsled.gpio_number);
}
/*
* 模組註冊入口
*/
module_init(dtsled_init);
/*
* 模組註冊出口
*/
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiaozhu");
Makefile檔案
KERNELDIR := /home/xfg/linux/imx_7ull/i2x_6ub/system_file/i2SOM-iMX-Linux
CURRENT_PATH := $(shell pwd)
obj-m := dtsled.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
ledApp.c應用測試檔案
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/*
*argc:應用程式引數個數
*argv[]:具體的引數內容,字串形式
*./ledApp <filename> <0:1> 0表示關,1表示開
*./ledApp /dev/dtsled 0 關閉led燈
*./ledApp /dev/dtsled 1 開啟led燈
* */
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int ret = 0;
int fd = 0;
char *filename;
unsigned char databuf[1];
if(argc !=3) {
printf("Instruction usage error!!!\r\n");
printf("./ledApp <filename> <0:1> 0表示關,1表示開\r\n");
printf("./ledApp ./dev/dtsled 1 \r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("open file %s failed\r\n", filename);
}
databuf[0] = atoi(argv[2]);
ret = write(fd, databuf, sizeof(databuf));
if(ret < 0) {
printf("write file %s failed\r\n", filename);
}
ret =close(fd);
if(ret <0) {
printf("close file %s falied!\r\n", filename);
}
return 0;
}
六、測試
1.裝置樹測試
回到核心原始碼的根目錄下重新編譯裝置樹檔案,編譯命令
make dtbs
編譯完成後將/arch/arm/boot/dts目錄下的i2c6ulxb-i2s6ull-emmc.dtb拷貝至tftp伺服器下,然後重啟開發版即可,我使用的是nfs掛載根檔案系統的方式進行測試的,當然也可以直接更新開發板中的裝置樹,只是這樣比較麻煩,開發中不建議這麼操作。
重新啟動後,進入/proc/device-tree/目錄下檢視我們更改的節點資訊是否存在,如下圖所示:
可知我們新增的dtsled節點資訊是確定的,接下來測試驅動。
1.驅動測試
將應用檔案和驅動檔案編譯後拷貝到開發版的/lib/modules/4.1.43/目錄下,掛載led驅動,如下圖所示:
掛載成功後可以看到我們申請的gpio引腳編號是133,最後通過應用測試led是否能開啟和關閉,如下圖所示:
如果能正常開啟和關閉led燈,說明我們的驅動和應用程式沒有問題,若有寫得不好的地方,望各位大佬指出。
參考文獻
gpio子系統和pinctrl子系統(上):https://www.cnblogs.com/rongpmcu/p/7662751.html
Linux 驅動學習筆記 - gpio 子系統 (八):https://blog.csdn.net/tyustli/article/details/105484666