RK3568 Android12 gpio驱动实现(三)

Platform: RK3568
OS: Android 12
Kernel: v4.19.206
SDK Version:android-12.0-mid-rkr1
Module: gpio


目标

承接上文 RK3568 Android12 gpio驱动实现(二),添加gpio的direction和value节点用于读写。

主要代码

  1. 主要参考kernel源码的drivers/gpio/gpiolib-sysfs.c,实现store和show函数,创建读写节点,填充twgpio_class结构体
struct gpio_desc *tw_gpio1;

static ssize_t tw_gpio1_direction_show(struct device *dev,
                 struct device_attribute *attr, char *buf){
					 			
        ssize_t			status;		
		
        if (!tw_gpio1) {
			dev_err(dev, "tw_gpio1 failed!\n");			
			return -EFAULT;	   
		} else {
	        gpiod_get_direction(tw_gpio1);		
	        status = sprintf(buf, "%s\n",
			    test_bit(FLAG_IS_OUT, &tw_gpio1->flags)
				? "out" : "in");				  			
		}

	    return status;		
}

static ssize_t tw_gpio1_direction_store(struct device *dev,
                 struct device_attribute *attr,
                 const char *buf, size_t size){
        
        ssize_t			status;       		
	    		
	    if(sysfs_streq(buf, "out")|| sysfs_streq(buf, "low")) {       
            status = gpiod_direction_output_raw(tw_gpio1, 0);        
        } else if(sysfs_streq(buf, "high")) {        
            status = gpiod_direction_output_raw(tw_gpio1, 1); 
        } else if(sysfs_streq(buf, "in")) {        
            status = gpiod_direction_input(tw_gpio1);        
        } else {
		    pr_warn("%s: tw_gpio1_direction: Invalid argument!\n", __func__);
		    return -EINVAL;
	    }
	        
	    return status ? : size;
}
static DEVICE_ATTR_RW(tw_gpio1_direction);

static ssize_t tw_gpio1_value_show(struct device *dev,
                 struct device_attribute *attr, char *buf){
		
            ssize_t			status;
            
	        status = gpiod_get_raw_value_cansleep(tw_gpio1);
	        if (status < 0) {
				pr_warn("ERROR status = %zd n", status);
		        return -EINVAL;
            }
	            buf[0] = '0' + status;
	            buf[1] = '\n';
	            status = 2;
	            
	        return status;
		
}

static ssize_t tw_gpio1_value_store(struct device *dev,
                 struct device_attribute *attr,
                 const char *buf, size_t count){					 
	
	if(sysfs_streq(buf, "1")) {       
        gpiod_set_raw_value_cansleep(tw_gpio1, 1);        
    } else if(sysfs_streq(buf, "0")) {        
        gpiod_set_raw_value_cansleep(tw_gpio1, 0);        
    } else {
		pr_warn("tw_gpio1_value: Invalid argument!\n");
		return -EINVAL;
	}	
        return count;
}

static DEVICE_ATTR_PREALLOC(tw_gpio1_value, S_IWUSR | S_IRUGO, tw_gpio1_value_show, tw_gpio1_value_store);

static struct attribute *twgpio_class_attrs[] = {
	&dev_attr_tw_gpio1_direction.attr,
	&dev_attr_tw_gpio1_value.attr,
	NULL,
};
ATTRIBUTE_GROUPS(twgpio_class);

static struct class twgpio_class = {
	.name =		"twgpio",
	.owner =	THIS_MODULE,
	.class_groups = twgpio_class_groups,
};
  1. 在probe中获取gpio,设置默认的方向和值,注册twgpio_class
tw_gpio1 = devm_gpiod_get(dev, "tw1", GPIOD_OUT_LOW);
    if (IS_ERR(tw_gpio1)) {
        dev_warn(dev, "Failed to get tw_gpio1\n");
    }
    
gpiod_direction_output_raw(tw_gpio1,0);
pr_info("%s:terry gpio set tw-gpio1 output default low\n",__func__);
	
ret = class_register(&twgpio_class);	
	pr_info("%s: ret = %d\n", __func__, ret);
	if(ret < 0) {
		return -EINVAL; 
	}	        
  1. dts配置中的gpio属性改写为
    tw1-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_LOW>;

新接口简介

从上面代码中可以看到,与上篇文章主要用到gpio_get_value和gpio_set_value接口不同,本篇主要是用到了gpiod_ 前缀的接口来实现功能,这是linux的gpio子系统推荐使用的新接口。根据参考资料1 2,目前gpio子系统提供有2套API接口:

  • legacy API:integer-based GPIO interface,形式为 gpio_xxx(),例如 void gpio_set_value(unsigned gpio, int value),不推荐使用该 API;

  • 推荐 API: descriptor-based GPIO interface,形式为 gpiod_xxx(),例如 void gpiod_set_value(struct gpio_desc *desc, int value),新添加的驱动代码一律采用这套 API。

gpiod_xxx() API
在 gpio 子系统中,用 struct gpio_desc 来描述一个 gpio 引脚,gpiod_xxx() 都是围绕着 strcut gpio_desc 进行操作的。

完整的接口定义位于 linux/gpio/consumer.h,大约共有 70个 API。

常用 API:
获得/释放 一个或者一组 gpio:
[devm]_gpiod_get*()
[devm]_gpiod_put*()

设置/查询 输入或者输出:
gpiod_direction_input()
gpiod_direction_output()
gpiod_get_direction()

读写一个 gpio:
gpiod_get_value()
gpiod_set_value()
gpiod_get_value_cansleep()
gpiod_set_value_cansleep()

用上一节代码中用到的几个接口进行简要说明:

  1. devm_gpiod_get()来获取gpio,根据参考资料34和内核源码,该接口是有资源管理功能的,可以自动释放资源。跟一下调用关系:
    devm_gpiod_get ->
    devm_gpiod_get_index-> index=0
    gpiod_get_index

可以看出该接口实际是对gpiod_get_index 的封装,其中index为0。
gpiod_get_index接口定义:

struct gpio_desc *__must_check gpiod_get_index(struct device *dev,
                                               const char *con_id,
                                               unsigned int idx, 
                                               enum gpiod_flags flags)
{
        struct gpio_desc *desc = NULL;
        int status;
        enum gpio_lookup_flags lookupflags = 0; 
        /* Maybe we have a device name, maybe not */
        const char *devname = dev ? dev_name(dev) : "?"; 

        dev_dbg(dev, "GPIO lookup for consumer %s\n", con_id);

        if (dev) {
                /* Using device tree? */
                if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
                        dev_dbg(dev, "using device tree for GPIO lookup\n");
                        desc = of_find_gpio(dev, con_id, idx, &lookupflags);
                } else if (ACPI_COMPANION(dev)) {
                        dev_dbg(dev, "using ACPI for GPIO lookup\n");
                        desc = acpi_find_gpio(dev, con_id, idx, &flags, &lookupflags);
                }    
        }
……
        status = gpiod_request(desc, con_id ? con_id : devname);
        if (status < 0)
                return ERR_PTR(status);

        status = gpiod_configure_flags(desc, con_id, lookupflags, flags);
        if (status < 0) {
                dev_dbg(dev, "setup of GPIO %s failed\n", con_id);
                gpiod_put(desc);
                return ERR_PTR(status);
        }

该接口已经做了3个动作,分别是“of_find_gpio”,“gpiod_request”,“gpiod_configure_flags”;
这3个函数,整合了从devicetree获取gpio,请求gpio和初始化gpio这3步,比较方便。
但是要注意该接口对dts中gpio属性的命名有要求,推荐要写为xxx-gpios的形式,然后con_id中直接填前缀xxx就可以识别到了。具体可以看of_find_gpio()的接口定义:

struct gpio_desc *of_find_gpio(struct device *dev, const char *con_id,
                               unsigned int idx,
                               enum gpio_lookup_flags *flags)
{
        char prop_name[32]; /* 32 is max size of property name */
        enum of_gpio_flags of_flags;
        struct gpio_desc *desc;
        unsigned int i;

        /* Try GPIO property "foo-gpios" and "foo-gpio" */
        for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
                if (con_id)
                        snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id,
                                 gpio_suffixes[i]);
                else
                        snprintf(prop_name, sizeof(prop_name), "%s",
                                 gpio_suffixes[i]);

                desc = of_get_named_gpiod_flags(dev->of_node, prop_name, idx,
                                                &of_flags);
……                                                

可以看到gpio的属性名prop_name 是由con_id(如果有的话) 和gpio_suffixes[i] 拼接而成,然后是调用of_get_named_gpiod_flags()来获取gpio。gpio_suffixes[]的定义如下,后缀用gpios或者gpio都可以。

/* gpio suffixes used for ACPI and device tree lookup */
static __maybe_unused const char * const gpio_suffixes[] = { "gpios", "gpio" };
  1. gpiod_direction_output_raw() 设置gpio输出,输出电平为实际物理电平, 不考虑ACTIVE_LOW 的状态。

  2. gpiod_get_raw_value_cansleep() 和gpiod_set_raw_value_cansleep()
    同样也是不考虑ACTIVE_LOW 的状态,读写gpio的实际物理电平。以 _cansleep 为后缀的函数是可能会睡眠的 API,不可以在 hard (non-threaded) IRQ handlers 中使用。

功能测试

可以看到对value和direction节点进行cat/echo 操作都是基本OK的。

console:/ # cat /d/gpio |grep tw
 gpio-19  (                    |tw1                 ) out lo    

console:/sys/class/twgpio # ls
tw_gpio1_direction  tw_gpio1_value
console:/sys/class/twgpio # cat tw
tw_gpio1_direction  tw_gpio1_value
console:/sys/class/twgpio # cat tw_gpio1_direction                             
out
console:/sys/class/twgpio # cat t
tw_gpio1_direction  tw_gpio1_value
console:/sys/class/twgpio # cat tw_gpio1_value                                 
0
console:/sys/class/twgpio # echo high > tw_gpio1_direction
console:/sys/class/twgpio # cat tw_gpio1_direction
out
console:/sys/class/twgpio # cat tw_gpio1_value                                 
1
console:/sys/class/twgpio # echo in >tw_gpio1_direction
console:/sys/class/twgpio # cat tw_gpio1_direction                             
in
console:/sys/class/twgpio # cat tw_gpio1_value                                 
0

小结与展望

已实现用新接口在/sys/class/twgpio/目录下生成value和direction 节点用于读写gpio的方向和值。下一步想要在dts中配置子节点,并在class目录下生成对应的sysfs子目录,每个目录均可读写gpio的value和direction。


如有谬误欢迎指正,感谢阅读~

参考资料


  1. Linux 驱动开发 / gpio子系统 / 快速入门 ↩︎

  2. Documentation/driver-api/gpio/ ↩︎

  3. GPIO系列(2)——Linux的GPIO控制“gpiod_”和“gpio_”浅析 ↩︎

  4. [RK3399][Android7.1] 获取gpio函数devm_gpiod_get_optional() ↩︎

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值