利用设备树和平台总线驱动led(GPIO)
测试平台是讯为的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"); //模块作者