0. gpio子系統
gpio子系統是linux內核當中用于管理GPIO資源的一套系統,它提供了很多GPIO相關的API接口,驅動程序中使用GPIO之前需要向gpio子系統申請。
gpio子系統的主要目的就是方便驅動開發者使用gpio,驅動開發者在設備樹中添加gpio相關信息,然后就可以在驅動程序中使用gpio子系統提供的API函數來操作GPIO。
Linux內核向驅動開發者屏蔽掉了GPIO的設置過程,極大的方便了驅動開發者使用GPIO。
- 設備樹
使用gpio子系統時,需要更改設備樹。
led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
表示led引腳使用的IO屬于gpio0,是gpio0的7號引腳,高電平有效。
led {compatible = "alientek,led";status = "okay";default-state = "on";led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
}
- GPIO驅動程序
在使用gpio子系統之前,需要向內核gpio子系統注冊這一套操作硬件寄存器的“方法”,也就是GPIO驅動。但對于驅動開發,設置好設備樹以后就可以使用gpio子系統提供的API函數來操作指定的GPIO,gpio子系統向驅動開發人員屏蔽了具體的讀寫寄存器過程。
gpio_request()
gpio_free()
gpio_direction_input()
gpio_direction_output()
gpio_get_value()
gpio_set_value()
1. gpio子系統下的LED驅動
- 修改設備樹
led {compatible = "alientek,led";status = "okay";default-state = "on";led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
}
- 編寫gpio子系統下的LED驅動
- 定義設備結構體
設備結構體包含cdev、類、設備、設備號等。- 實現開關讀寫4個系統調用函數
在文件打開函數中使用filp->private_data設置私有數據;使用copy_from_user()實現用戶空間和內存空間數據交互,并使用gpio_set_value()設置IO值(LED的亮滅)。- 初始化設備操作函數結構體
- 實現設備注冊和注銷函數
使用of_find_node_by_path(“/led”)函數led設備節點,使用of_property_read_string()函數讀取status屬性,使用of_property_read_string()函數獲取compatible屬性值并進行匹配,使用of_get_named_gpio()獲取設備樹中的led-gpio屬性,得到LED所使用的GPIO編號,然后使用gpio_request()向gpio子系統申請使用GPIO,使用gpio_direction_output()將led gpio管腳設置為輸出模式,使用register_chrdev_region()函數、alloc_chrdev_region()函數創建設備號,并使用module_init()函數指定設別注冊函數實現insmod,使用cdev_init()初始化cdev,使用cdev_add()添加cdev,使用class_create()創建類,使用device_create()創建設備;使用device_destroy()注銷設備,使用class_destroy()注銷類,使用cdev_del()刪除cdev,使用unregister_chrdev_region()注銷設備號,使用led_iounmap()取消地址映射,并使用module_exit()函數指定設備注銷函數實現rmmod。- 添加LICENSE和作者
使用MODULE_LICENSE()函數和MODULE_AUTHOR()函數添加LICENSE和作者。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>#define GPIOLED_CNT 1 /* 設備號個數 */
#define GPIOLED_NAME "gpioled" /* 名字 */// 0. **定義設備結構體**
/* dtsled設備結構體 */
struct gpioled_dev {dev_t devid; /* 設備號 */struct cdev cdev; /* cdev */struct class *class; /* 類 */struct device *device; /* 設備 */int major; /* 主設備號 */int minor; /* 次設備號 */struct device_node *nd; /* 設備節點 */int led_gpio; /* LED所使用的GPIO編號 */
};static struct gpioled_dev gpioled; /* led設備 */// 1. **實現開關讀寫4個系統調用函數**
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &gpioled; /* 設置私有數據 */return 0;
}static ssize_t led_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{return 0;
}static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{int ret;char kern_buf[1];ret = copy_from_user(kern_buf, buf, cnt); // 得到應用層傳遞過來的數據if(0 > ret) {printk(KERN_ERR "kernel write failed!\r\n");return -EFAULT;}if (0 == kern_buf[0])gpio_set_value(gpioled.led_gpio, 0); // 如果傳遞過來的數據是0則關閉ledelse if (1 == kern_buf[0])gpio_set_value(gpioled.led_gpio, 1); // 如果傳遞過來的數據是1則點亮ledreturn 0;
}static int led_release(struct inode *inode, struct file *filp)
{return 0;
}// 2. **初始化設備操作函數結構體**
static struct file_operations gpioled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release,
};// 3. **實現設備注冊和注銷函數**
static int __init led_init(void)
{const char *str;int ret;/* 1.獲取led設備節點 */gpioled.nd = of_find_node_by_path("/led");if(NULL == gpioled.nd) {printk(KERN_ERR "gpioled: Failed to get /led node\n");return -EINVAL;}/* 2.讀取status屬性 */ret = of_property_read_string(gpioled.nd, "status", &str);if(!ret) {if (strcmp(str, "okay"))return -EINVAL;}/* 2、獲取compatible屬性值并進行匹配 */ret = of_property_read_string(gpioled.nd, "compatible", &str);if(0 > ret) {printk(KERN_ERR "gpioled: Failed to get compatible property\n");return ret;}if (strcmp(str, "alientek,led")) {printk(KERN_ERR "gpioled: Compatible match failed\n");return -EINVAL;}printk(KERN_INFO "gpioled: device matching successful!\r\n");/* 4.獲取設備樹中的led-gpio屬性,得到LED所使用的GPIO編號 */gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);if(!gpio_is_valid(gpioled.led_gpio)) {printk(KERN_ERR "gpioled: Failed to get led-gpio\n");return -EINVAL;}printk(KERN_INFO "gpioled: led-gpio num = %d\r\n", gpioled.led_gpio);/* 5.向gpio子系統申請使用GPIO */ret = gpio_request(gpioled.led_gpio, "LED-GPIO");if (ret) {printk(KERN_ERR "gpioled: Failed to request led-gpio\n");return ret;}/* 6.將led gpio管腳設置為輸出模式 */gpio_direction_output(gpioled.led_gpio, 0);/* 7.初始化LED的默認狀態 */ret = of_property_read_string(gpioled.nd, "default-state", &str);if(!ret) {if (!strcmp(str, "on"))gpio_set_value(gpioled.led_gpio, 1);elsegpio_set_value(gpioled.led_gpio, 0);} elsegpio_set_value(gpioled.led_gpio, 0);/* 8.注冊字符設備驅動 *//* 創建設備號 */if (gpioled.major) {gpioled.devid = MKDEV(gpioled.major, 0);ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);if (ret)goto out1;} else {ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);if (ret)goto out1;gpioled.major = MAJOR(gpioled.devid);gpioled.minor = MINOR(gpioled.devid);}printk("gpioled: major=%d,minor=%d\r\n",gpioled.major, gpioled.minor); /* 初始化cdev */gpioled.cdev.owner = THIS_MODULE;cdev_init(&gpioled.cdev, &gpioled_fops);/* 添加一個cdev */ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);if (ret)goto out2;/* 創建類 */gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);if (IS_ERR(gpioled.class)) {ret = PTR_ERR(gpioled.class);goto out3;}/* 創建設備 */gpioled.device = device_create(gpioled.class, NULL,gpioled.devid, NULL, GPIOLED_NAME);if (IS_ERR(gpioled.device)) {ret = PTR_ERR(gpioled.device);goto out4;}return 0;out4:class_destroy(gpioled.class);out3:cdev_del(&gpioled.cdev);out2:unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);out1:gpio_free(gpioled.led_gpio);return ret;
}static void __exit led_exit(void)
{/* 注銷設備 */device_destroy(gpioled.class, gpioled.devid);/* 注銷類 */class_destroy(gpioled.class);/* 刪除cdev */cdev_del(&gpioled.cdev);/* 注銷設備號 */unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);/* 釋放GPIO */gpio_free(gpioled.led_gpio);
}/* 驅動模塊入口和出口函數注冊 */
module_init(led_init);
module_exit(led_exit);// 4. **添加LICENSE和作者**
MODULE_AUTHOR("DengTao <773904075@qq.com>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");
應用程序:
- 打開文件
- 從文件讀取數據
- 將數據寫入文件
- 關閉文件
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>/** @description : main主程序* @param - argc : argv數組元素個數* @param - argv : 具體參數* @return : 0 成功;其他 失敗*/
int main(int argc, char *argv[])
{int fd, ret;unsigned char buf[1];if(3 != argc) {printf("Usage:\n""\t./ledApp /dev/led 1 @ close LED\n""\t./ledApp /dev/led 0 @ open LED\n");return -1;}/* 打開設備 */fd = open(argv[1], O_RDWR);if(0 > fd) {printf("file %s open failed!\r\n", argv[1]);return -1;}/* 將字符串轉換為int型數據 */buf[0] = atoi(argv[2]);/* 向驅動寫入數據 */ret = write(fd, buf, sizeof(buf));if(0 > ret){printf("LED Control Failed!\r\n");close(fd);return -1;}/* 關閉設備 */close(fd);return 0;
}