9.RK平台GPIO的复用设置以及驱动方法

GPIO是写单片机出身的人的“白月光”

GPIO,全称 General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。对于GPIO的开发包括,通过用户空间设置其输入抑或输出,设置或获取其高低电平状态;在内核里设置引脚输入输出,设置中断;以及完成复用等。

一、Userspace使用GPIO

1.硬件以及规格书

以firefly ROC-RK3588S-PC 开发板为例,

在这里插入图片描述

2.GPIO的计算

ROC-RK3588S-PC 有 5 组 GPIO bank:GPIO0~GPIO4,每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分,常用以下公式计算引脚:

GPIO pin脚计算公式:pin = bank * 32 + number

GPIO 小组编号计算公式:number = group * 8 + X

下面演示GPIO3_C6 pin脚计算方法:

bank = 3; //GPIO3_C6 => 3, bank ∈ [0,4]

group = 2; //GPIO3_C6 => 2, group ∈ {(A=0), (B=1), (C=2), (D=3)}

X = 6; //GPIO3_C6 => 0, X ∈ [0,7]

number = group * 8 + X = 2 * 8 + 6 = 22

pin = bank32 + number= 3 * 32 + 22 = 118;

3.设备节点控制gpio

当GPIO3_C6 脚没有被其它外设复用时, 我们可以通过export导出该引脚去使用

# cd /sys/class/gpio/
# ls
export     gpiochip128  gpiochip509  gpiochip96
gpiochip0  gpiochip32   gpiochip64   unexport

# echo 118 > export

# ls /sys/class/gpio/gpio118
active_low  device  direction  edge  power  subsystem  uevent  value

#设置输出
# echo out > gpio118/direction

#输出低电平,可以测得该引脚电压为0V
# echo 0 >  gpio118/value

#输出高电平,可以测得该引脚电压为1.8V
# echo 1 >  gpio118/value
#设置输入
# echo in > gpio118/direction

#获取该引脚的电平,可以测得为1.8V
# cat gpio118/value
1

二、内核控制GPIO

以Firefly-RK3399为例,增加一个gpio输出控制等的驱动

1.硬件与原理图

在这里插入图片描述

如图绿灯在DIY_LED是高电平时点亮,其中WORK_LED是gpio0_B5

2.设备树描述硬件设备以及复用信息

由于已经有驱动控制该引脚,需要注释掉之前存在的

leds {
		compatible = "gpio-leds";

		work {
			label = "firefly:blue:power";
			linux,default-trigger = "ir-power-click";
			gpios = <&gpio2 27 GPIO_ACTIVE_HIGH>;
			pinctrl-names = "default";
			pinctrl-0 = <&led_power>;
			default-state = "on";
		};

		/*
		user {
			label = "firefly:yellow:user";
			linux,default-trigger = "ir-user-click";
			gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
			pinctrl-names = "default";
			pinctrl-0 = <&led_user>;
			default-state = "off";
		};*/
	};

	usergpio {
		compatible = "my_user_gpio";	
		gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
		pinctrl-names = "default";
		pinctrl-0 = <&led_green>;
	};

作为一个描述gpio设备的设备节点,包含有该设备的compatible描述符,有能被驱动计算出gpio索引号的语句,有配置引脚复用的语句

&pinctrl {
	...
    leds {
            led_power: led-power {
                rockchip,pins = <2 27 RK_FUNC_GPIO &pcfg_pull_none>;
            };

            /*led_user: led-user {
                rockchip,pins = <0 13 RK_FUNC_GPIO &pcfg_pull_none>;
            };*/
        };
        usergpio {
            led_green: led-green {
                rockchip,pins = <0 13 RK_FUNC_GPIO &pcfg_pull_none>;
            };
        };
     ...
 }

在设备树中配置 GPIO,需要配置引脚的功能复用与电气属性

对于 rockchip 引脚,配置如下:

rockchip,pins = <PIN_BANK PIN_BANK_IDX MUX &phandle>

其中:

  • PIN_BANK:引脚所在的 bank
  • PIN_BANK_IDX:引脚所在 bank 的引脚号
  • MUX:功能复用配置,0 表示普通 GPIO,1-N 表示特殊的功能复用
  • phandle:引脚一般配置,例如内部上拉、电流强度等,在Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt文件中描述

配置 GPIO0_B5 引脚:

rockchip,pins = <0 13 RK_FUNC_GPIO &pcfg_pull_none>;

此处的含义:

  1. PIN_BANK等于0
  2. PIN_BANK_IDX等于13
  3. RK_FUNC_GPIO代表使用普通 GPIO 功能
  4. pcfg_pull_none代表普通配置
3.probe对设备树的解析
int gpio_num = 0;
int usergpio_probe(struct platform_device *pdev)
{
	enum of_gpio_flags flag;      
    struct device_node *usergpio_device_node = pdev->dev.of_node;  
    
    //gpio_num = of_get_named_gpio(usergpio_device_node, "gpios", 0);
    gpio_num = of_get_named_gpio_flags(usergpio_device_node, "gpios", 0, &flag);
    //printk ("gpio_num is %d \n ",gpio_num);
    //printk ("flag is %d \n ",flag);

of_get_named_gpioof_get_named_gpio_flags都可以从设备树中读取 "gpios"的 GPIO 配置编号,后者还能可以从传入的flag指针获取设备树里的gpio配置

4.gpio的控制
 	   if(!gpio_is_valid(gpio_num))
       {
               printk("of_get_named_gpio_flags is error \n");  
               return -1;
       }
       
       
       if(gpio_request(gpio_num,"usergpio_gpios"))
       {
               printk("gpio_request is error \n");
               gpio_free(gpio_num);
               return -1;      
       }
       gpio_direction_output(gpio_num,flag);
       msleep(500);
       gpio_direction_output(gpio_num,~flag);
       msleep(500);
       gpio_direction_output(gpio_num,flag);
       msleep(500);
       gpio_direction_output(gpio_num,~flag);

gpio_is_valid 判断该 GPIO 编号是否有效,gpio_request 则申请占用该 GPIO。如果初始化过程出错,需要调用 gpio_free 来释放之前申请过且成功的 GPIO 。在驱动中调用 gpio_direction_output 就可以设置输出高还是低电平,这里默认输出从 DTS 获取得到的有效电平 GPIO_ACTIVE_HIGH,即为高电平,后面对flag取反,使灯闪烁

实际中如果要读出 GPIO,需要先设置成输入模式,然后再读取值:

int val;
gpio_direction_input(your_gpio);
val = gpio_get_value(your_gpio);

下面是常用的 GPIO API 定义:

#include <linux/gpio.h>
#include <linux/of_gpio.h>

enum of_gpio_flags {
     OF_GPIO_ACTIVE_LOW = 0x1,
};
int of_get_named_gpio_flags(struct device_node *np, const char *propname,
int index, enum of_gpio_flags *flags);
int gpio_is_valid(int gpio);
int gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);
int gpio_direction_input(int gpio);
int gpio_direction_output(int gpio, int v);

三、设备节点控制gpio

上面让gpio输出高低电平加延时的做法显然不符合Linux驱动的思想

理想状态应该为,用户空间有设备节点能写入或读取灯的状态,驱动能根据用户空间传递来的控制命令的字符串,控制灯GPIO。

下面为优化驱动的结果

#echo on灯会亮,echo off灯灭,cat user_led_status能看出灯的亮灭
root@firefly:/sys/devices/virtual/user_led_class/user_led# echo on > user_led_status
root@firefly:/sys/devices/virtual/user_led_class/user_led# echo off > user_led_status
root@firefly:/sys/devices/virtual/user_led_class/user_led# cat user_led_status
off

其优化的思路为:

​ 由原来的platform_driver的probe里获取设备树里的gpio信息,并控制

​ 改为platform_driver的probe里获取设备树里的gpio信息,创建一个字符设备,在给字符设备创建一个文件,在文件的attr(属性)下的操作符里控制gpio

int usergpio_probe(struct platform_device *pdev)
{
	...
	major_num = register_chrdev(0,DEVICE_NAME,&user_led_fops);

	user_led_class = class_create(THIS_MODULE, "user_led_class");
	user_led_device = device_create(user_led_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME);

	device_create_file(user_led_device, &dev_attr_user_led_status);
        

	gpio_num = of_get_named_gpio_flags(usergpio_device_node, "gpios", 0, &flag);
	...
}

//cat命令时,将会调用该函数
static ssize_t usr_led_status_show(struct device *dev,
                              struct device_attribute *attr, char *buf)        
{
    return sprintf(buf, "%s", kernel_buffer);
}
 
//echo命令时,将会调用该函数
static ssize_t usr_led_status_set(struct device *dev,
                             struct device_attribute *attr,
                             const char *buf, size_t len)        
{
	sprintf(kernel_buffer, "%s", buf);
	if(!strcmp(kernel_buffer, "on\n"))
	{
		gpio_direction_output(gpio_num,~flag);		
	}
	if(!strcmp(kernel_buffer, "off\n"))
	{
		gpio_direction_output(gpio_num,flag);		
	}
	return len;
}
//定义一个名字为my_device_test的设备属性文件
static DEVICE_ATTR(user_led_status, S_IWUSR|S_IRUSR, usr_led_status_show, usr_led_status_set);

**PS:**字符设备、class、DEVICE_ATTR是什么与本文标题关系不大,故略过。应用程序中当然也可以使用c程序,直接操作字符设备fd,我觉得效果不如echo、cat直观,故略过。本文更适合那种喜欢看现象再编程的小白。

四、附录源码

改进后的源码

#include <linux/init.h>
 #include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/uaccess.h> 
#include <linux/device.h>
#include "usergpio.h"

#define DEVICE_NUMBER 1
#define DEVICE_NAME   "user_led"
#define DEVICE_MINOR_NUMBER  0

int gpio_num = 0;
enum of_gpio_flags flag;
static struct class *user_led_class;
struct device *user_led_device;
int ioarg = 500;

static int major_num = 0;

//module_param(major_num, int, S_IRUSR);
//module_param(minor_num, int, S_IRUSR);

static char kernel_buffer[] = "!!!";

//cat命令时,将会调用该函数
static ssize_t usr_led_status_show(struct device *dev,
                              struct device_attribute *attr, char *buf)        
{
    return sprintf(buf, "%s", kernel_buffer);
}
 
//echo命令时,将会调用该函数
static ssize_t usr_led_status_set(struct device *dev,
                             struct device_attribute *attr,
                             const char *buf, size_t len)        
{
	sprintf(kernel_buffer, "%s", buf);
	if(!strcmp(kernel_buffer, "on\n"))
	{
		gpio_direction_output(gpio_num,~flag);		
	}
	if(!strcmp(kernel_buffer, "off\n"))
	{
		gpio_direction_output(gpio_num,flag);		
	}
	return len;
}
//定义一个名字为my_device_test的设备属性文件
static DEVICE_ATTR(user_led_status, S_IWUSR|S_IRUSR, usr_led_status_show, usr_led_status_set);

 
static int user_led_open(struct inode *inode, struct file *file) {
	printk("user_led_open\n");
	return 0;
}
 
static int user_led_release(struct inode *inode, struct file *file) {
	printk("user_led_release\n");
	return 0;
}
 

 


static const struct file_operations user_led_fops = {
    .owner = THIS_MODULE,
    .open = user_led_open,
    .release = user_led_release,


};

int usergpio_probe(struct platform_device *pdev)
{
	
	struct device_node *usergpio_device_node = pdev->dev.of_node;
	printk( "\033[0;32m         ***func is %s , line is %d  ***          \033[0m",__func__,__LINE__);
	
	//usergpio_device_node = of_find_node_by_path("/usergpio");
	//gpio_num = of_get_named_gpio(usergpio_device_node, "gpios", 0);

	if(usergpio_device_node == NULL) 
	{
		printk("of_find_node_by_pdev is error \n");
		return -1;
	}

	major_num = register_chrdev(0,DEVICE_NAME,&user_led_fops);

	user_led_class = class_create(THIS_MODULE, "user_led_class");
	user_led_device = device_create(user_led_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME);

	device_create_file(user_led_device, &dev_attr_user_led_status);
        

	gpio_num = of_get_named_gpio_flags(usergpio_device_node, "gpios", 0, &flag);
	if(!gpio_is_valid(gpio_num))
	{
		printk("of_get_named_gpio_flags is error \n");	
		return -1;
	}
	if(gpio_request(gpio_num,"usergpio_gpios"))
	{
		printk("gpio_request is error \n");
		gpio_free(gpio_num);
		return -1;	
	}
	gpio_direction_output(gpio_num,flag);

	
	return 0;
}

int usergpio_remove(struct platform_device *pdev)
{
	printk("usergpio_remove, Bye Bye!\n");
	device_remove_file(user_led_device,&dev_attr_user_led_status);
	device_destroy(user_led_class,MKDEV(major_num,0));
        class_unregister(user_led_class);
        class_destroy(user_led_class);
        unregister_chrdev(major_num,DEVICE_NAME);
	gpio_free(gpio_num);
	return 0;
}





const struct of_device_id of_match_table_wxdtest[] = {
	{.compatible = "my_user_gpio"},
	{}
};

struct platform_driver usergpio_driver = {
	.probe = usergpio_probe,
	.remove = usergpio_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "my_user_gpio",
		.of_match_table = of_match_table_wxdtest
	},
	
};

static int usergpio_init(void)
{

	int ret = 0;
	printk(KERN_ALERT "wxd kernel driver enter\n");
	ret = platform_driver_register(&usergpio_driver);

	if(ret < 0)
	{
		printk("platform_driver_register error\n");
		return ret;
	}

	printk("platform_driver_register ok \n");
	return 0;
}

static void usergpio_exit(void)
{
	printk(KERN_ALERT "wxd kernel driver exit\n");
	platform_driver_unregister(&usergpio_driver);
}


 module_init(usergpio_init);
 module_exit(usergpio_exit);
 MODULE_LICENSE("Dual BSD/GPL");
 MODULE_DESCRIPTION("A Sample user_led  Module");
 MODULE_ALIAS("A Sample module");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值