測試平台是訊為的itop-4412開發板
驅動led步驟
步驟:
- 修改設備樹,添加led相關的節點,編譯后燒錄進板卡
- 編寫driver驅動代碼,初始化platform_driver結構體,使of_match_table屬性的compatible與設備樹中的一致
- 在驅動入口函數中,向平台注冊driver
- 匹配成功
- 在probe函數里獲取gpio編號(從設備樹獲取)
- 向內核申請gpio
- 設置gpio方向
- 注冊雜項設備
- 編寫文件操作集里的ioctl函數,控制gpio
- 編寫應用程序,打開對應雜項設備,使用ioctl控制led燈
常用的gpio函數
獲取gpio編號
<linux/of_gpio.h>
int of_get_named_gpio(struct device_node *np,
const char *propname, int index);
參數:節點,屬性名,標號
返回:得到GPIO編號;負值表示失敗
向內核申請GPIO
申請gpio,申請后其他進程就不能再申請這個gpio,相當於被占用
int gpio_request(unsigned gpio, const char *label);
參數:gpio編號,給這個gpio起的名字
返回值:0成功,其他值失敗
操作GPIO
設置gpio方向
int gpio_direction_input(unsigned gpio); //設置輸入,參數是gpio編號,返回0成功
int gpio_direction_output(unsigned gpio, int value);
讀寫gpio的值
int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);
將gpio導出到用戶空間
int gpio_export(unsigned gpio, bool direction_may_change);
//gpio編號,用戶是否可以改變gpio的方向
導出后用戶層/sys/class/gpio/目錄下會有gpio+編號的目錄,和在用戶層export申請gpio效果類似
對應的取消導出為gpio_unexport函數
釋放gpio
gpio_free(unsigned gpio);
1. 修改設備樹
在設備樹中添加:
taxue_leds:taxue_leds {
compatible = "taxue_leds";
gpios1 = <&gpl2 0 GPIO_ACTIVE_HIGH>;
gpios2 = <&gpk1 1 GPIO_ACTIVE_HIGH>;
status = "disabled";
};
&taxue_leds {
status = "okay";
};
2. 賦值of_match_table
定義並初始化platform_driver結構體
#define DEVICE_NAME "taxue_leds" //和設備樹節點中的compatible一致
//平台device和設備樹device可以通過match_table匹配
static const struct of_device_id of_leds_match[] = {
{.compatible = DEVICE_NAME},
{},
};
static struct platform_driver pdrv = {
.probe = drProbe,
.remove = drRemove,
.driver = {
.name = "DEVICE_NAME", //如果使用設備樹中的設備,則不根據name匹配
.owner = THIS_MODULE,
.of_match_table = of_leds_match, //使用match_table進行匹配
},
};
3. 注冊平台driver
//這段寫在驅動入口處
int ret=0;
ret = platform_driver_register(&pdrv);
if(ret < 0){
printk("platform driver regist failed\n");
return -1;
}
4. 匹配
由平台總線匹配
5. 在probe函數里獲取gpio編號
int ledgpios[2];
{
led_node = of_find_node_by_path("/taxue_leds"); //從設備樹路徑獲取節點
if(led_node == NULL){
printk("find node failed\n");
return -1;
}
ledgpios[0] = of_get_named_gpio( led_node, "gpios1", 0); //獲取gpio編號,存在ledgpios[0]里
}
6. 向內核申請gpio
在probe函數里
ret = gpio_request(ledgpios[0], "led0"); //向內核申請gpio
if(ret != 0){
printk("request led gpio 0 failed\n");
return -1;
}
7. 設置gpio方向
在probe函數里
gpio_direction_output(ledgpios[0],0); //設置為輸出,默認低電平
8. 注冊雜項設備
在probe里注冊
- 初始化file_operations文件操作集
- 初始化miscdevice(主要包含:設備節點名,文件操作集)
- 調用misc_register函數注冊雜項設備
9. 編寫文件操作集里的ioctl函數
#define CMD_TEST_2 _IOW('A', 2, int)
//參數:句柄、接口命令、傳遞的數據
//返回值:
long misc_ioctl(struct file *fd, unsigned int cmd, unsigned long b){
switch(cmd){
case CMD_TEST_2:
printk("CMD_TEST_2 date=%ld\n",b);
if(b >0){
gpio_set_value(ledgpios[0], 1); //如果傳入的值大於0就將gpio置高,led亮
}else{
gpio_set_value(ledgpios[0], 0); //如果傳入的值等於0就將gpio置低,led滅
}
break;
}
return 0;
}
10. 編寫應用程序
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include <unistd.h>
#include<sys/ioctl.h>
#define CMD_TEST_2 _IOW('A', 2, int)
int main(int argc, char *argv[]){
int fd=0;
int i=0;
fd = open("/dev/hello_led", O_RDWR); //設備名具體需要根據驅動注冊設備節點時的名字
if(fd < 0){
printf("open failed\n");
exit(1);
}
printf("open success\n");
//讓led間隔一秒閃爍,循環10次
for(i=0;i<10;i++){
ioctl( fd, CMD_TEST_2, 0);
sleep(1);
ioctl( fd, CMD_TEST_2, 1);
sleep(1);
}
close(fd);
return 0;
}
驅動完整代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#define CMD_TEST_0 _IO('A', 0)
#define CMD_TEST_2 _IOW('A', 2, int)
#define DEVICE_NAME "taxue_leds"
struct device_node *led_node=NULL;
int ledgpios[2];
int misc_open(struct inode *a,struct file *b){
printk("misc open \n");
return 0;
}
int misc_release (struct inode * a, struct file * b){
printk("misc file release\n");
return 0;
}
//參數:句柄、接口命令、傳遞的數據
//返回值:
long misc_ioctl(struct file *fd, unsigned int cmd, unsigned long b){
printk("cmd type=%c\t nr=%d\t dir=%d\t size=%d\n", _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_DIR(cmd), _IOC_SIZE(cmd));
switch(cmd){
case CMD_TEST_0:
printk("CMD_TEST_0\n");
gpio_set_value(ledgpios[0], 0);
break;
case CMD_TEST_2:
printk("CMD_TEST_2 date=%ld\n",b);
if(b >0){
gpio_set_value(ledgpios[0], 1);
}else{
gpio_set_value(ledgpios[0], 0);
}
break;
}
return 0;
}
//文件操作集
static struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.unlocked_ioctl = misc_ioctl
};
static struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR, //自動分配從設備號
.name = "hello_led", //設備節點名
.fops = &misc_fops //文件操作集
};
int drProbe(struct platform_device *dev){
int ret;
printk("probe here\n");
led_node = of_find_node_by_path("/taxue_leds"); //從設備樹路徑獲取節點
if(led_node == NULL){
printk("find node failed\n");
return -1;
}
ledgpios[0] = of_get_named_gpio( led_node, "gpios1", 0); //獲取gpio編號
ret = gpio_request(ledgpios[0], "led0"); //向內核申請gpio
if(ret != 0){
printk("request led gpio 0 failed\n");
return -1;
}
gpio_direction_output(ledgpios[0],0); //設置為輸出,默認低電平
ret = misc_register(&misc_dev); //注冊雜項設備
if(ret < 0){
printk("misc regist failed\n");
return -1;
}
printk("misc regist succeed\n");
return 0;
}
int drRemove(struct platform_device *dev){
gpio_free(ledgpios[0]);
misc_deregister(&misc_dev);
printk("driver remove\n");
return 0;
}
//平台device和設備樹device可以通過match_table匹配
static const struct of_device_id of_leds_match[] = {
{.compatible = DEVICE_NAME},
{},
};
static struct platform_driver pdrv = {
.probe = drProbe,
.remove = drRemove,
.driver = {
.name = "DEVICE_NAME", //如果使用設備樹中的設備,則不根據name匹配
.owner = THIS_MODULE,
.of_match_table = of_leds_match, //使用match_table進行匹配
},
};
static int driver_init_led(void){
int ret=0;
ret = platform_driver_register(&pdrv); //向平台注冊driver
if(ret < 0){
printk("platform driver regist failed\n");
return -1;
}
return 0;
}
static void driver_exit_led(void){
platform_driver_unregister(&pdrv);
printk("platform driver exit!\n");
}
module_init(driver_init_led); //模塊入口,加載驅動時執行參數內的函數
module_exit(driver_exit_led); //模塊出口,卸載模塊時執行參數內的函數
MODULE_LICENSE("Dual BSD/GPL"); //遵循BSD和GPL開源許可
MODULE_AUTHOR("TAXUE"); //模塊作者