蜗窝科技

X-025-KERNEL-Linux gpio driver的移植之基本功能

wowo · · X Project · 阅读 25018 · 2017年9月存档

1. 前言

本文将基于本站GPIO subsystem[1]相关的文章,结合”X Project”的开发过程,实现一个简单的gpio driver,并利用gpiolib提供的sysfs api进行简单的测试,进而加深对gpio相关概念的理解。

注1:本文后续的描述,kernel基于本站“X Project”所使用的kernel版本,硬件基于 ”X Project”所使用的“Bubbugum-96”平台[2]。

2. 添加gpio driver的驱动框架

kernel中所有驱动的开发都是一个套路,包括如下几个步骤:

a)新建driver文件(例如drivers/gpio/gpio-owl.c)并从其它地方copy一个架子(例如从drivers/pinctrl/pinctrl-owl.c中,并将pinctrl替换为gpio)。

b)修改drivers/gpio/Kconfig和drivers/gpio/Makefile,将这个新的driver添加到编译体系中:

$ git diff drivers/gpio/
…
— a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -342,6 +342,12 @@ config GPIO_OMAP
help
Say yes here to enable GPIO support for TI OMAP SoCs.
+config GPIO_OWL

  • bool “GPIO driver for Actions OWL soc”
  • default y if ARCH_OWL
  • help
  • This selects the gpio driver for Actions OWL soc.

…

— a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
…
+obj-$(CONFIG_GPIO_OWL) += gpio-owl.o
obj-$(CONFIG_GPIO_PCA953X) += gpio-pca953x.o
…

注2:参考drivers/gpio/Kconfig中有关的注释,为了使我们的driver看着更专业、更规范,我们需要以字母为顺序把我们新的driver放到合适的位置:

config GPIO_GENERIC
tristate

put drivers in the right section, in alphabetical order

This symbol is selected by both I2C and SPI expanders

config GPIO_MAX730X
tristate

c)修改arch/arm64/Kconfig.platforms,选中ARCH_REQUIRE_GPIOLIB配置项,这样就会默认编译gpio有关的代码。

vim@ubuntu:~/work/xprj/linux$ git diff arch/arm64/Kconfig.platforms
diff –git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index b03d2538..48c4ae2 100644
— a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -178,6 +178,7 @@ config ARCH_OWL
bool “Actions OWL 64-bit SoC Family”
select PINCTRL
select PINMUX

  • select ARCH_REQUIRE_GPIOLIB
    help
    This enables support for Actions OWL based SoCs like the S900.

d)编译kernel,发现新的driver已经编译进来了

vim@ubuntu:~/work/xprj/build$ make kernel
CHK include/generated/timeconst.h
CHK include/generated/asm-offsets.h
CALL /home/vim/work/xprj/linux/scripts/checksyscalls.sh
CHK include/generated/compile.h
CC drivers/gpio/gpio-owl.o
LD drivers/gpio/built-in.o
CC drivers/irqchip/irqchip.o
CC drivers/irqchip/irq-gic.o

至此GPIO driver的驱动框架已经成型(具体的patch如下),后续根据实际的硬件,往里面填充东西使其慢慢完善即可。

https://github.com/wowotechX/linux/commit/2e8b86d7f2e4e892d4a9e1d82cbaa6d3051cfa56

3. 添加gpiochip

由[3]中的介绍可知,编写gpio driver驱动的主要工作就是使用gpiochip抽象gpio控制器,一般情况下,有两种抽象策略:

a)使用一个gpiochip抽象并管理系统中所有的gpio。

b)每个bank(有关bank的概念,熟悉gpio的同学应该都了解,这里就不解释了)使用一个gpiochip抽象(一个实现,多个实例)。

至于使用哪种策略,主要是由代码编写的方便与否决定的,可以自行发挥,这里以第二种为例。

gpiochip的实现比较简单,主要包括(可参考后面的patch):

a)实现request和free接口,可交给pinctrl[4](可以先什么都不做)。

b)实现direction_input、direction_output、get 、set等接口。

c)根据实际情况,填充其它字段。

patch:https://github.com/wowotechX/linux/commit/d05448f039f961f09226f8ef30cba6b706aa8c15

4. 结合gpiolib提供的sysfs api调试

实现基本的功能之后,我们可以借助gpiolib提供的sysfs api进行简单的验证和测试,步骤如下:

4.1 打开SYSFS配置项(CONFIG_SYSFS)和GPIO_SYSFS配置项(CONFIG_GPIO_SYSFS)

make kernel-config

File systems —>
Pseudo filesystems —>
d[*] sysfs file system support

Device Drivers —>
-*- GPIO Support —>
[*] /sys/class/gpio/… (sysfs interface)

结果如下:

— a/arch/arm64/configs/xprj_defconfig
+++ b/arch/arm64/configs/xprj_defconfig
…
+CONFIG_GPIO_SYSFS=y

CONFIG_HWMON is not set

…
-# CONFIG_SYSFS is not set

CONFIG_MISC_FILESYSTEMS is not set

4.2 修改dts,增加gpio的node

注2:“Bubbugum-96”平台[2]平台中,pinctrl和gpio是同一个模块,控制寄存器都在一起,这里偷懒,不想区分哪个是pinctrl的、哪个是gpio的了,就使用相同的寄存器范围就行了。不过呢,这一来我们在代码中就不能使用devm_ioremap_resource进行寄存器映射(devm_xxx类的接口具有排他性),要换成ioremap,具体请参考下面的改动。

  • gpioa: gpio@0xe01b0000 {
  • compatible = “actions,s900-gpio”;
  • reg = <0 0="" 12="" 0xe01b0000="">;
  • };
  • gpiob: gpio@0xe01b000c {
  • compatible = “actions,s900-gpio”;
  • reg = <0 0="" 12="" 0xe01b000c="">;
  • };
    …

— a/drivers/pinctrl/pinctrl-owl.c
+++ b/drivers/pinctrl/pinctrl-owl.c
…

  • priv->membase = devm_ioremap_resource(dev, resource);
  • priv->membase = ioremap(resource->start, resource_size(resource));

以上4.1和4.2的修改可参考下面的patch:https://github.com/wowotechX/linux/commit/3c8fa7f1076cc6786166b7c3c742ce19ec640c53

4.3 编译并在板子上运行

跑起来后发现有如下错误:

[ 0.218562] gpio gpiochip1: GPIO integer space overlap, cannot add chip
[ 0.225124] gpiochip_add_data: GPIOs 0..31 (owl-gpio) failed to register
[ 0.231781] owl_gpio e01b000c.gpio: Failed to add gpiochip
[ 0.237249] owl_gpio: probe of e01b000c.gpio failed with error -16
[ 0.243437] owl_gpio e01b0018.gpio: owl_gpio_probe
[ 0.248187] gpio gpiochip1: GPIO integer space overlap, cannot add chip
[ 0.254749] gpiochip_add_data: GPIOs 0..31 (owl-gpio) failed to register
[ 0.261437] owl_gpio e01b0018.gpio: Failed to add gpiochip
[ 0.266906] owl_gpio: probe of e01b0018.gpio failed with error -16

总结来说,原因有二:

1) 每一个gpio bank(4.2中的dts node)要对应一个gpiochip结构,我们上面的移植忽略了这个事情。

2)每一个gpiochip的base要小心维护,要在整个gpio subsystem中唯一,因为sysfs api以base命名相应的gpiochip(可参考后面的介绍),并以base和nrgpio计算逻辑上的gpio number,我们可以简单的将base设置为0、32、64、等等。

修改的方法如下:

diff –git a/arch/arm64/boot/dts/actions/s900-bubblegum.dts b/arch/arm64/boot/dts/actions/s900-bubblegum.dts
index 2c959d2..6119dbc 100644
— a/arch/arm64/boot/dts/actions/s900-bubblegum.dts
+++ b/arch/arm64/boot/dts/actions/s900-bubblegum.dts
@@ -55,26 +55,32 @@
gpioa: gpio@0xe01b0000 {
compatible = “actions,s900-gpio”;
reg = <0 0="" 12="" 0xe01b0000="">;

  • base = <0>;
    };
    gpiob: gpio@0xe01b000c {
    compatible = “actions,s900-gpio”;
    reg = <0 0="" 12="" 0xe01b000c="">;
  • base = <32>;
    };
    …

diff –git a/drivers/gpio/gpio-owl.c b/drivers/gpio/gpio-owl.c
index f71bc7a..e32e292 100644
— a/drivers/gpio/gpio-owl.c
+++ b/drivers/gpio/gpio-owl.c
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include
#include

@@ -34,6 +35,8 @@

struct owl_gpio_priv {
void __iomem *membase;
+

  • struct gpio_chip gpiochip;
    };

…

@@ -166,7 +169,10 @@ static int owl_gpio_probe(struct platform_device *pdev)
return PTR_ERR(priv->membase);
}

  • ret = devm_gpiochip_add_data(dev, &owl_gpio_chip, priv);
  • memcpy(&priv->gpiochip, &owl_gpio_chip, sizeof(struct gpio_chip));
  • of_property_read_u32(dev->of_node, “base”, &priv->gpiochip.base);
  • ret = devm_gpiochip_add_data(dev, &priv->gpiochip, priv);
    if (ret < 0) {
    dev_err(dev, “Failed to add gpiochip\n”);
    return ret;

修改后再次编译运行,已经可以正确probe了:

[ 0.209124] owl_gpio e01b0000.gpio: owl_gpio_probe
[ 0.213968] owl_gpio e01b000c.gpio: owl_gpio_probe
[ 0.218718] owl_gpio e01b0018.gpio: owl_gpio_probe
[ 0.223468] owl_gpio e01b0024.gpio: owl_gpio_probe
[ 0.228249] owl_gpio e01b0030.gpio: owl_gpio_probe
[ 0.232999] owl_gpio e01b00f0.gpio: owl_gpio_probe

4.4 通过sysfs api控制gpio点亮、熄灭LED灯

由于当前测试环境(bubblegum96)的rootfs的是在ramdisk中,因此在系统启动后,我们需要手动创建/sys目录并挂载sysfs,如下:

mkdir /sys
mount -t sysfs /sys /sys

sysfs挂在后可以在gpio class下看到gpiolib提供的sysfs api,如下:

ls /sys/class/gpio
export gpiochip128 gpiochip32 gpiochip96
gpiochip0 gpiochip160 gpiochip64 unexport

参考[5]中的介绍,我们可以将需要使用的gpio number(逻辑number)写入到export文件,这样就可以将该gpio申请出来,并体现在/sys/class/gpio/目录中。以GPIOA19(LED0)为例(gpio的base是0,因此逻辑number也是19):

/ # echo 19 > /sys/class/gpio/export
sh: write error: No such device

又出现错误,经过排查,原来我们的request接口还没有正确实现,没关系,先返回OK(0):

static int owl_gpio_request(struct gpio_chip *chip, unsigned offset)
{

  • return pinctrl_request_gpio(offset);
  • //return pinctrl_request_gpio(offset);
  • return 0;
    }
    …

再次测试,OK:

echo 19 > /sys/class/gpio/export
/ # ls /sys/class/gpio
lass/gpio
export gpiochip0 gpiochip160 gpiochip64 unexport
gpio19 gpiochip128 gpiochip32 gpiochip96

然后我们可以向“/sys/class/gpio/gpio19/direction ”中写入“out”字符串,将gpio的方向设置为输出,然后向/sys/class/gpio/gpio19/value中写入“1”或者“0”控制gpio的输出电平,如下:

/ # echo out > /sys/class/gpio/gpio19/direction
echo out > /sys/class/gpio/gpio19/di[ 36.780156] owl_gpio e01b0000.gpio: offset 19, value 0
rection

/ # echo 1 > /sys/class/gpio/gpio19/value
echo 1 > /sys/class/gpio/gpio19/val[ 54.310562] owl_gpio e01b0000.gpio: offset 19, value 1
ue
/ # echo 0 > /sys/class/gpio/gpio19/value

/sys/class/gpio/gpio19/valu[ 60.714062] owl_gpio e01b0000.gpio: offset 19, value 0
e

然后观察LED灯,检查是否可以正确控制,如果可以则测试成功。

以上patch可参考:https://github.com/wowotechX/linux/commit/30fcb89aabc7eef0043034262f76d3ebe4a9c789

5. 参考文档

[1] /sort/gpio_subsystem

[2] https://github.com/96boards/documentation/blob/master/ConsumerEdition/Bubblegum-96/HardwareDocs/SoC_bubblegum96.pdf

[3] linux内核中的GPIO系统之(1):软件框架

[4] linux内核中的GPIO系统之(5):gpio subsysem和pinctrl subsystem之间的耦合

[5] Documentation/gpio/sysfs.txt

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net。

标签:sysfsdriverGPIOportinggpiolib

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(2 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • 小白

    我看了代码,devm_ioremap_resource具有排他性,这个devm_ioremap好像是没有排他性的!
    • wowo

      @小白:多谢提醒,好像是这个样子的,devm_ioremap_resource中会调用__request_region申请一个“busy resource region”,所谓的busy,就是这段memory区域只能自己使用。用devm_ioremap/ioremap取代就没有这个限制了。 /** * __request_region - create a new busy resource region * @parent: parent resource descriptor * @start: resource start address * @n: resource region size * @name: reserving caller's ID string * @flags: IO resource flags */ struct resource * __request_region(struct resource *parent, resource_size_t start, resource_size_t n, const char *name, int flags)