瑞芯微Rockchip开发者社区
直播中

刘杰

7年用户 1310经验值
私信 关注
[经验]

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节点用于读写。

主要代码

主要参考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,
};

在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;
}

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()

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

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" };

gpiod_direction_output_raw() 设置gpio输出,输出电平为实际物理电平, 不考虑ACTIVE_LOW 的状态。

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。

原作者:Terry.W

更多回帖

发帖
×
20
完善资料,
赚取积分