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