例程代碼路徑:ELF 1開發(fā)板資料包3-例程源碼3-2 驅(qū)動例程源碼4_Pinctrl和GPIO子系統(tǒng)myled
前面章節(jié)的驅(qū)動,沒有對實際的硬件進行操作,主要通過一些打印信了解了驅(qū)動的加載流程和調(diào)用流程。本章節(jié)開始,以實際操作硬件為例進行講解,操作之前需要先解壓一份從NXP官網(wǎng)下載的源碼,避免加載驅(qū)動時獲取不到硬件資源。如果有使用git進行管理源碼,只需要新建一個分支,然后切換到第一次提交的commit號即可。
首先使用GPIO子系統(tǒng)API函數(shù)編寫一個用戶層能夠控制LED_Y亮滅的驅(qū)動。
復(fù)用GPIO
(一)查看原理圖和引腳復(fù)用表格,可以得到LED_Y由GPIO1_18控制,所以我們需要配置GPIO1_18引腳為輸出,而且能夠在用戶空間控制它輸出高電平還是低電平。
(二)在設(shè)備樹中的IOMUX中添加引腳的復(fù)用,設(shè)備樹路徑:linux-imx-imx_4.1.15_2.0.0_ga/arch/arm/boot/dts/imx6ull-elf1-emmc.dts,將其復(fù)用成GPIO功能,并檢查設(shè)備樹中是否有其它的地方也用到了此引腳,如果用到了就需要將其屏蔽掉,避免復(fù)用沖突。
pinctrl_hog_1: hoggrp-1 { ????????????????????????fsl,pins = < ????????????????????????????????MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 ??????0x17059 /* SD1 CD */ ????????????????????????????????MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT ???0x17059 /* SD1 VSELECT */ ????????????????????????????????MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 ???????0x17059 /* SD1 RESET */ ????????????????????????????????MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 ??????0x17059 /*LED_Y*/ ????????????????????????>; ????????????????}; |
添加后效果如下:
(三)編譯設(shè)備樹
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi elf@ubuntu:~/work/linux-imx-imx_4.1.15_2.0.0_ga$ make dtbs |
編譯生成的設(shè)備樹文件為imx6ull-elf1-emmc.dtb,參考《01-0 ELF1、ELF1S開發(fā)板_快速啟動手冊_V1》4.4節(jié)單獨更新設(shè)備樹。
驅(qū)動源碼myled.c編寫
(一)頭文件引用
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件 #include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/uaccess.h> ?????// 包含用戶空間數(shù)據(jù)訪問函數(shù)的頭文件 #include <linux/cdev.h> ????????//包含字符設(shè)備頭文件 #include <linux/device.h> ??????//包含設(shè)備節(jié)點相關(guān)的頭文件 #include <linux/gpio.h> ????????//包含gpio操作函數(shù)的相關(guān)頭文件 |
(二)創(chuàng)建相關(guān)宏定義
#define DEVICE_NAME "mydevice" ?// 設(shè)備名稱 #define LED_IOC_MAGIC 'k' ???????//定義ioctl幻數(shù) #define SET_LED_ON _IO(LED_IOC_MAGIC, 0) ?//定義LED開命令 #define SET_LED_OFF _IO(LED_IOC_MAGIC, 1) ?//定義LED關(guān)命令 #define GPIO_LED_PIN_NUM 18 ???//定義操作的GPIO編號 |
(1)ioctl幻數(shù)
ioctl幻數(shù)是一個用于區(qū)分ioctl命令的標識符。它是一個唯一的整數(shù)值,并且通常使用ASCII字符來表示?;脭?shù)的目的是確保ioctl命令的唯一性,以免不同設(shè)備的命令發(fā)生沖突。
(2)_IO宏定義
_IO宏定義用于創(chuàng)建無參數(shù)的ioctl命令代碼。
_IO(LED_IOC_MAGIC, 0),LED_IOC_MAGIC表示定義的幻數(shù),0或者1表示具體的命令代碼。
(3)GPIO編號
在imx6ull上確定GPIO編號的公式為:GPIOn_IOx=(n-1)*32+x。
因為選擇的引腳為GPIO1_IO18,所以通過(n-1)*32+x計算得到的編號為18。
(三)相關(guān)變量定義
static dev_t dev_num; ??//分配的設(shè)備號 struct ?cdev my_cdev; ?????????//字符設(shè)備指針 int major; ?//主設(shè)備號 int minor; ?//次設(shè)備號 static struct class *my_led; static struct device *my_device; |
(四)mydevice_init(void)函數(shù)的實現(xiàn)
static int __init mydevice_init(void) { ????int ret; ?????//釋放之前申請的GPIO,避免申請失敗 ?????gpio_free(GPIO_LED_PIN_NUM); ????// 申請GPIO,并判斷是否申請成功 ????????if (gpio_request(GPIO_LED_PIN_NUM, "led_run")) { ????????????????printk("request %s gpio faile n", "led_run"); ?????????????????return -1; ?????????} ????// 注冊字符設(shè)備驅(qū)動程序 ????ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME); ????if (ret < 0) { ????????printk(KERN_ALERT "Failed to allocate device number: %dn", ret); ????????return ret; } ???major=MAJOR(dev_num); ???minor=MINOR(dev_num); printk(KERN_INFO "major number: %dn",major); printk(KERN_INFO "minor number: %dn",minor); ? my_cdev.owner = THIS_MODULE; ????????cdev_init(&my_cdev,&fops); ????????cdev_add(&my_cdev,dev_num,1); ? ???????// 創(chuàng)建設(shè)備類 ????my_led = class_create(THIS_MODULE, "my_led"); ????if (IS_ERR(my_led)) { ????????pr_err("Failed to create classn"); ????????return PTR_ERR(my_led); } ??// 創(chuàng)建設(shè)備節(jié)點并關(guān)聯(lián)到設(shè)備類 ????my_device = device_create(my_led, NULL, MKDEV(major, minor), NULL, "my_device"); ????if (IS_ERR(my_device)) { ????????pr_err("Failed to create devicen"); ????????class_destroy(my_led); ????????return PTR_ERR(my_device); ????} ???????????printk(KERN_INFO "Device registered successfully.n"); ????return 0; } |
主要添加了gpio_free()和gpio_request()函數(shù)的調(diào)用,gpio_free()函數(shù)原型如下:
void gpio_free(unsigned int gpio); |
(1)參數(shù)說明
gpio:要釋放的GPIO引腳的編號。
(2)返回值
無返回值。
(3)函數(shù)功能
gpio_free()函數(shù)釋放先前請求的GPIO引腳,并將其解除分配。釋放后的GPIO引腳可以被其他驅(qū)動程序或設(shè)備重新請求使用。
gpio_request()函數(shù)原型如下:
int gpio_request(unsigned int gpio, const char *label); |
(1)參數(shù)說明
gpio:要請求的GPIO引腳的編號。
label:用于標識該GPIO引腳的描述字符串。
(2)返回值
成功時,返回0表示請求成功。
失敗時,返回負數(shù)錯誤代碼,表示請求失敗。
(3)函數(shù)功能
gpio_request()函數(shù)請求一個GPIO引腳并進行相關(guān)的配置,以便將其用于后續(xù)的操作。
它會檢查GPIO引腳是否已經(jīng)被其他驅(qū)動程序或內(nèi)核使用,如果被占用則請求失敗。
請求成功后,該GPIO引腳將被鎖定,使其他驅(qū)動程序無法再次請求該引腳。
(五)mydevice_exit(void)函數(shù)的實現(xiàn)
static void __exit mydevice_exit(void) { // 在這里執(zhí)行驅(qū)動程序的清理操作 //釋放申請的GPIO資源 gpio_free(GPIO_LED_PIN_NUM); // 銷毀設(shè)備節(jié)點 ?device_destroy(my_led, MKDEV(major, minor)); // 銷毀設(shè)備類 ?class_destroy(my_led); // 刪除字符設(shè)備 ?cdev_del(&my_cdev); // 注銷字符設(shè)備驅(qū)動程序 ?unregister_chrdev(0, DEVICE_NAME); ? ????printk(KERN_INFO "Device unregistered.n"); } |
(六)定義fops結(jié)構(gòu)體
static struct file_operations fops = { ????.owner = THIS_MODULE, ????.open = device_open, ????.release = device_release, ????.unlocked_ioctl = myled_ioctl, }; |
此處用到了unlocked_ioctl函數(shù),unlocked_ioctl函數(shù)是Linux內(nèi)核中用于設(shè)備控制和通信的重要函數(shù)之一。它允許用戶空間程序與設(shè)備驅(qū)動程序進行交互,發(fā)送特定的命令和參數(shù)來執(zhí)行設(shè)備相關(guān)的操作。
unlocked_ioctl()函數(shù)原型如下:
long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); |
(1)參數(shù)說明
filp:指向struct file結(jié)構(gòu)的指針,表示要執(zhí)行ioctl操作的文件。
cmd:請求代碼,用于指定要執(zhí)行的具體操作。請求代碼通常是一個整數(shù),可以是預(yù)定義的常量或自定義的命令。
arg:參數(shù),根據(jù)具體的請求代碼而定。
(2)返回值
unlocked_ioctl函數(shù)返回一個long類型的值,通常用于表示操作的成功與否,返回值為負數(shù)表示出現(xiàn)錯誤。
(七)device_open()函數(shù)的實現(xiàn):
static int device_open(struct inode *inode, struct file *file) { // 在這里處理設(shè)備打開的操作 //設(shè)置GPIO引腳為輸出模式,并將其初始化為高電平 gpio_direction_output(GPIO_LED_PIN_NUM, 1); printk(KERN_INFO "This is device_open.n"); ????return 0; } |
gpio_direction_output()函數(shù)原型如下:
int gpio_direction_output(unsigned int gpio, int value); |
(1)參數(shù)說明
gpio:要配置的GPIO引腳的編號。
value:設(shè)置GPIO引腳的初始輸出值,0表示低電平,非零表示高電平。
(2)返回值
成功時,返回0表示設(shè)置成功。
失敗時,返回負數(shù)錯誤代碼,表示設(shè)置失敗。
(3)函數(shù)功能
gpio_direction_output()函數(shù)將指定的GPIO引腳配置為輸出模式,并設(shè)置其初始輸出值。
在配置為輸出模式后,可以使用其他函數(shù)(如gpio_set_value())來更改GPIO引腳的輸出值。
(八)myled_ioctl()函數(shù)的實現(xiàn)
static long myled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { ? ????switch (cmd) { ????????????case SET_LED_ON: ????????????// 設(shè)置GPIO引腳為低電平 ????????????gpio_set_value(GPIO_LED_PIN_NUM, 0); ????????????break; ? ????????case SET_LED_OFF: ??????????//設(shè)置GPIO引腳為高電平 ??????????gpio_set_value(GPIO_LED_PIN_NUM, 1); ????????????break; ? ????????default: ????????????return -ENOTTY; ????} ? ????return 0; } |
gpio_set_value()函數(shù)原型如下:
void gpio_set_value(unsigned int gpio, int value); |
(1)參數(shù)說明
gpio:要設(shè)置的GPIO引腳的編號。
value:要設(shè)置的輸出值,0表示低電平,非零表示高電平。
(2)返回值
無返回值。
(3)函數(shù)功能
gpio_set_value()函數(shù)用于設(shè)置指定GPIO引腳的輸出值。該函數(shù)會將GPIO引腳配置為輸出模式(如果尚未配置),并設(shè)置其輸出值為指定的值。
(九)device_release()函數(shù)的實現(xiàn):
static int device_release(struct inode *inode, struct file *file) { // 在這里處理設(shè)備關(guān)閉的操作 //設(shè)置GPIO引腳為輸出模式,并將置為高電平 gpio_direction_output(GPIO_LED_PIN_NUM, 1); printk(KERN_INFO "This is device_release.n"); ? ????return 0; } |
完整的驅(qū)動myled.c示例源碼
#include <linux/module.h> ??????// 包含模塊相關(guān)函數(shù)的頭文件 #include <linux/fs.h> ??????????// 包含文件系統(tǒng)相關(guān)函數(shù)的頭文件 #include <linux/uaccess.h> ?????// 包含用戶空間數(shù)據(jù)訪問函數(shù)的頭文件 #include <linux/cdev.h> ????????//包含字符設(shè)備頭文件 #include <linux/device.h> #include <linux/gpio.h> ? #define DEVICE_NAME "mydevice" ?// 設(shè)備名稱 #define LED_IOC_MAGIC 'k' #define SET_LED_ON _IO(LED_IOC_MAGIC, 0) #define SET_LED_OFF _IO(LED_IOC_MAGIC, 1) #define GPIO_LED_PIN_NUM 18 static dev_t dev_num; ??//分配的設(shè)備號 struct ?cdev my_cdev; ?????????//字符設(shè)備指針 int major; ?//主設(shè)備號 int minor; ?//次設(shè)備號 static struct class *my_led; static struct device *my_device; ? ? ? ? ? static int device_open(struct inode *inode, struct file *file) { // 在這里處理設(shè)備打開的操作 //設(shè)置GPIO引腳為輸出模式,并將其初始化為高電平 gpio_direction_output(GPIO_LED_PIN_NUM, 1); printk(KERN_INFO "This is device_open.n"); ????return 0; } ? static int device_release(struct inode *inode, struct file *file) { // 在這里處理設(shè)備關(guān)閉的操作 gpio_direction_output(GPIO_LED_PIN_NUM, 1); printk(KERN_INFO "This is device_release.n"); ? ????return 0; } ? static long myled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { ? ????switch (cmd) { ????????????case SET_LED_ON: ????????????// 設(shè)置GPIO引腳為低電平 ????????????gpio_set_value(GPIO_LED_PIN_NUM, 0); ????????????break; ? ????????case SET_LED_OFF: ??????????//設(shè)置GPIO引腳為高電平 ??????????gpio_set_value(GPIO_LED_PIN_NUM, 1); ????????????break; ? ????????default: ????????????return -ENOTTY; ????} ? ????return 0; } ? static struct file_operations fops = { ????.owner = THIS_MODULE, ????.open = device_open, ????.release = device_release, ????.unlocked_ioctl = myled_ioctl, }; static int __init mydevice_init(void) { ????int ret; ? // 在這里執(zhí)行驅(qū)動程序的初始化操作 //釋放之前申請的GPIO,避免申請失敗 ????gpio_free(GPIO_LED_PIN_NUM); ????????if (gpio_request(GPIO_LED_PIN_NUM, "led_run")) { ????????????????printk("request %s gpio faile n", "led_run"); ?????????????????return -1; ?????????} ????// 注冊字符設(shè)備驅(qū)動程序 ????ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME); ????if (ret < 0) { ????????printk(KERN_ALERT "Failed to allocate device number: %dn", ret); ????????return ret; } ???major=MAJOR(dev_num); ???minor=MINOR(dev_num); printk(KERN_INFO "major number: %dn",major); printk(KERN_INFO "minor number: %dn",minor); ? my_cdev.owner = THIS_MODULE; ????????cdev_init(&my_cdev,&fops); ????????cdev_add(&my_cdev,dev_num,1); ? ???????// 創(chuàng)建設(shè)備類 ????my_led = class_create(THIS_MODULE, "my_led"); ????if (IS_ERR(my_led)) { ????????pr_err("Failed to create classn"); ????????return PTR_ERR(my_led); } ??// 創(chuàng)建設(shè)備節(jié)點并關(guān)聯(lián)到設(shè)備類 ????my_device = device_create(my_led, NULL, MKDEV(major, minor), NULL, "my_device"); ????if (IS_ERR(my_device)) { ????????pr_err("Failed to create devicen"); ????????class_destroy(my_led); ????????return PTR_ERR(my_device); ????} ???????????printk(KERN_INFO "Device registered successfully.n"); ????return 0; } static void __exit mydevice_exit(void) { // 在這里執(zhí)行驅(qū)動程序的清理操作 //釋放申請的GPIO資源 gpio_free(GPIO_LED_PIN_NUM); // 銷毀設(shè)備節(jié)點 ?device_destroy(my_led, MKDEV(major, minor)); // 銷毀設(shè)備類 ?class_destroy(my_led); // 刪除字符設(shè)備 ????cdev_del(&my_cdev); ????// 注銷字符設(shè)備驅(qū)動程序 ????unregister_chrdev(0, DEVICE_NAME); ? ????printk(KERN_INFO "Device unregistered.n"); } ? module_init(mydevice_init); module_exit(mydevice_exit); ? MODULE_LICENSE("GPL"); ?????// 指定模塊的許可證信息 MODULE_AUTHOR("Your Name"); // 指定模塊的作者信息 MODULE_DESCRIPTION("A simple character device driver"); // 指定模塊的描述信息 |
編譯
復(fù)制7.4.3驅(qū)動中的Makefile文件,將其中的copy_form_user.o修改為myled.o,效果如下:
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi elf@ubuntu:~/work/test/04_Pinctrl和GPIO子系統(tǒng)/myled$ make |
編譯成ko模塊并拷貝到開發(fā)板中。
編寫測試應(yīng)用源碼led_app.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <errno.h> #include <fcntl.h> ? #define DEV_NAME "/dev/my_device" #define LED_IOC_MAGIC 'k' #define SET_LED_ON _IO(LED_IOC_MAGIC, 0) #define SET_LED_OFF _IO(LED_IOC_MAGIC, 1) ? int main(int argc, char *argv[]) { ????????int i; ????????int fd = 0; ????????fd = open (DEV_NAME, O_RDONLY); ????????if (fd < 0) { ????????????????perror("Open "DEV_NAME" Failed!n"); ?????????exit(1); } ? ????????for (i=0; i<5; i++) ????????{ ????????????????ioctl(fd, SET_LED_ON); ????????????????sleep(1); ????????????????ioctl(fd, SET_LED_OFF); ????????????????sleep(1); ????????} ? ????????close(fd); ????????return 0; } |
編譯應(yīng)用
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi elf@ubuntu:~/work/test/04_Pinctrl和GPIO子系統(tǒng)/led_app$ $CC led_app.c -o led_app |
將編譯生成的應(yīng)用拷貝到開發(fā)板中。
測試
root@ELF1:~# insmod myled.ko major number: 245 minor number: 0 Device registered successfully. root@ELF1:~#?./led_app This is device_open. This is device_release. root@ELF1:~# rmmod myled.ko |
執(zhí)行l(wèi)ed_app后,黃色LED燈循環(huán)閃爍5次。