X-006-UBOOT-pinctrl driver移植(Bubblegum-96平台)

作者:wowo 发布于:2016-7-9 21:47 分类:X Project

1. 前言

wowo觉得,在linux kernel新引入的众多子系统中,pinctrl subsystem是一个特别晦涩难懂的子系统,它所解决的问题,和它所引入的困扰,不相上下。在平时工作的过程中,年轻工程师问的最多的,就是在驱动中要怎么使用pinctrl?这样配置pinctrl到底是什么意思?等等。

对一个子系统来说,如果不能让它的使用者(consumer)很容易的理解和掌握,就宣告了它的失败。更不用说让它的提供者(provider)简单、快速地编写驱动程序了。

为什么会这样呢?通俗一点讲,就是“大炮打蚊子”。从技术的角度看,pinctrl是一个非常优秀的子系统,有着复杂而巧妙的封装和抽象,但它所要解决的问题,实际上非常简单、直白。这就造成了一个落差,从而带来了各种理解上的困惑。

正因为此,u-boot在照搬linux kernel pinctrl的同时,额外提供了一种简洁的方法。本文将借助“X Project” u-boot中pin control driver的移植过程,介绍、分析这种方法,并以此理解pinctrl的本质。

与此同时,我们会基于“X-005-UBOOT-device tree移植(Bubblegum-96平台)”,扩展对device tree的使用,以加深对device tree的理解和掌握。

2. pinctrl要解决的问题

有关pinctrl的基本概念,可参考本站的三篇文章(本文不再赘述了):

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

linux内核中的GPIO系统之(2):pin control subsystem

Linux内核中的GPIO系统之(3):pin controller driver代码分析

基于上面三篇文章,我们可以总结出如下要点:

1)CPU中有些管脚的功能和参数,是可配置的。例如D5和A8两个管脚(名称是随便写的),即可以配置成具有上拉功能的I2C2的SCL/SDA,也可以配置为UART5的RX/TX,又可以配置为两个正常的GPIO,等等。

2)这些可配置参数的组合,受限于具体CPU的功能,个数是有限的。这一点很重要。

3)某个产品的硬件设计确定下来之后,这些可配置的功能和参数,基本上都确定了下来了,很少会在运行时修改。例如,连接3G modem的UART2,由于物理连接的限制,它所在对应的管脚,以及这些管脚的参数,都已经固定了。这一点同样很重要。

因此,pinctrl的本质功能,可总结为:

产品(或平台)的硬件设计确定后,在某些运行状态下,某些管脚的功能和参数也已经确定。软件需要根据这些确定的信息,在需要的时候(通常是对应的driver初始化的时候),将这些管脚配置成所需的功能和参数(通常是通过寄存器配置)。

而本着抽象和封装的原则,我们不希望每个驱动工程师,在为各自负责的硬件模块编写驱动的时候,都要研究一下本平台的硬件设计,确定和该硬件模块有关的管脚分配情况(这在复杂的SOC系统中,是一项非常繁琐的事情)。因此希望pinctrl driver,能否将这些繁琐的事情,封装起来,以比较友好的方式,向各个设备驱动提供服务。

听着有点拗口(但我相信大家都理解),下面我以Bubblegum-96平台调试用的UART5(可参考“X-004-UBOOT-串口驱动移植(Bubblegum-96平台)”)为例,做进一步的说明:

由“X-004-UBOOT-串口驱动移植(Bubblegum-96平台)”的描述可知,Bubblegum-96平台中,UART5是一个很明确的功能,它的RX/TX两个信号,分别连接到S900的A7和D8两个管脚上。这两个管脚和GPIOA25、GPIOA27、SENS0_VSYNC、SENS0_HSYNC、PWM2等功能复用。

想要UART5正常工作,就需要把A7和D8两个管脚配置为UART_RX和UART_TX功能(通过MFP_CTL1寄存器的bit28:23)。如果没有pinctrl driver,这个配置的工作,就需要serial driver负责(就像“X-004-UBOOT-串口驱动移植(Bubblegum-96平台)”中所描述的那样)。但这显然不符合模块化的设计理念,因此需要pinctrl driver代劳。

pinctrl driver要做什么事情呢?

它要抽象出来一个UART5功能(function,例如“pinctrl_serial5”),并允许serial driver使用这个function。具体的配置工作,由pinctrl driver负责,serial driver只需要“拿来主义”即可。具体可参考后面章节的介绍。

3. 设计思路

基于上面的描述,我们将设计一个最简的pinctrl driver,思路如下:

1)基于具体的版型(这里是Bubblegum-96),以该版型所对应的硬件功能为单位,将管脚配置抽象出来。借助device tree描述,有如下的架构:

/* arch/arm/dts/s900-bubblegum.dts */

#include "s900-bubblegum-pinctrl.dtsi"

“s900-bubblegum.dts”是Bubblegum-96的硬件描述,包含了描述该版型下所有的管脚配置的“s900-bubblegum-pinctrl.dtsi”文件:

/* arch/arm/dts/s900-bubblegum-pinctrl.dtsi */

#include "s900.dtsi"

/ {
        pinctrl@e01b0000 {
                /* actions,pins = , ...*/
               pinctrl_serial5: serial5 {
                        actions,pins = <0x0044 23="" 0x3f="" 0xc="">;
                };
                …
        };
};

其中黄色背景所描述的dts节点,就是对应的硬件功能,本例为用于控制台的UART5。

2)每一个硬件功能,所对应的管脚配置,都由一系列的寄存器配置值表示,这些值最终会写入到pinctrl的寄存器中,使对应的硬件功能生效,如上面例子中红色背景的部分,格式说明如下。

actions,pins = , ...

reg,所要操作的寄存器值,是相对pinctrl寄存器(e01b0000)的一个偏移偏移;

offset,所要配置的value在寄存器中的偏移量(位置);

mask,所需要配置的value的mask。

value,所要配置的value。

例如,需要使能S900 SOC A7和D8管脚的UART5功能,需要将0xe01b0044(MFP_CTL1)的bit28:23配置为0xc,只需如下的配置即可:actions,pins = <0x0044 23="" 0x3f="" 0xc="">;

当然,如果需要多组寄存器配置,也是okay的,只要:actions,pins = <0x0044 23="" 0x3f="" 0xc="">, <0x00xx xx="" 0xxx="">, …;即可。

3)对应的硬件功能的driver,需要在自己的dts节点中,引用“管脚配置”(pinctrl_serial5),这样就可以在u-boot启动的时候,由设备模型调用pinctrl的接口,自动将寄存器配置生效。或者,在需要的时候,手动调用pinctrl的接口,将某个pinctrl配置项生效,如下:

/* arch/arm/dts/s900-bubblegum.dts */

/ {

        …

        serial5: serial@e012a000 {
                u-boot,dm-pre-reloc;
                pinctrl-names = "default";
                pinctrl-0 = <&pinctrl_serial5>;
        };
};

u-boot的设备模型,配合pinctrl core,会在设备枚举的时候,将设备中pinctrl-name为“default”或者pinctrl句柄为“pinctrl-0”的pinctrl配置项生效。

另外,我们可以指定多个pinctrl功能,并在driver中随意的使能/禁止相应的功能,这里不再详细介绍,需要使用的同学可自行揣摩。

4)基于该设计思路,pinctrl有关的开发任务可总结为:

a)硬件设计确定后,负责pinctrl driver的工程师,需要将各个硬件功能以及对应的寄存器配置,抽象出来,保存在“xxx-pinctrl.dtsi ”中。

b)其它设备驱动,在需要的时候,引用并使用“xxx-pinctrl.dtsi ”所抽象的功能。

4. 移植过程及说明

4.1 u-boot pinctrl子系统介绍

u-boot的pinctrl子系统和linux kernel的非常类似,因此也比较复杂,这里只列出一些要点(具体细节不再详细介绍):

1)基于PINCTRL uclass。

2)根据复杂度、代码量、标准fdt的支持等不同,提供全功能(CONFIG_PINCTRL_FULL)、通用功能(CONFIG_PINCTRL_GENERIC)等不同类型的功能。我们只使用全功能(CONFIG_PINCTRL_FULL),并且只使用其中的简单接口(set_state_simple)。

4.2 新建一个pinctrl驱动,并添加到u-boot的编译框架中

1)新建s900 soc的pinctrl驱动----pinctrl-owl.c(OWL可能是S900 soc所在的系列的代码,copy自Actions代码,大家不用太纠结)。

touch drivers/pinctrl/pinctrl-owl.c

2)修改u-boot pinctrl driver的Kconfig和Makefile文件,将该驱动添加进去

/* drivers/pinctrl/Kconfig */

@@ -141,6 +141,11 @@ config PIC32_PINCTRL
by a device tree node which contains both GPIO defintion and pin control
functions.

+config OWL_PINCTRL
+         bool "Actions pin control driver for OWL soc"
+         depends on DM
+         help
+                Support pin multiplexing control on Actions OWL SoCs.


endif

source "drivers/pinctrl/nxp/Kconfig"

 

/* drivers/pinctrl/Makefile */

@@ -11,3 +11,5 @@ obj-$(CONFIG_PINCTRL_SANDBOX) += pinctrl-sandbox.o
obj-$(CONFIG_PINCTRL_UNIPHIER) += uniphier/
obj-$(CONFIG_PIC32_PINCTRL) += pinctrl_pic32.o
+
+obj-$(CONFIG_OWL_PINCTRL) += pinctrl-owl.o

4.3 配置u-boot,使能pinctrl有关的配置项

/* configs/bubblegum_defconfig */

#
# Pin controllers
#
-# CONFIG_PINCTRL is not set
+CONFIG_PINCTRL=y
+CONFIG_PINCTRL_FULL=y
+# CONFIG_PINCTRL_GENERIC is not set
+# CONFIG_ROCKCHIP_PINCTRL is not set
+# CONFIG_ROCKCHIP_3036_PINCTRL is not set
+CONFIG_OWL_PINCTRL=y

#
# Power

其中CONFIG_PINCTRL为pinctrl总开关。CONFIG_PINCTRL_FULL可以提供比较全面的pinctrl功能,暂时不用深究。CONFIG_OWL_PINCTRL使我们新加的pinctrl driver。

4.4 添加pinctrl device有关的device tree描述

在这里,我们将DTS文件拆分为三个:

s900.dtsi,SOC级别的文件,存放该SOC共同的内容,如serial controller、pinctrl controller等通用的配置。

s900-bubblegum-pinctrl.dtsi,Bubblegum-96平台所对应的pinctrl配置,具体可参考第3章的介绍。

s900-bubblegum.dts,Bubblegum-96平台的dts文件,存放该平台特有的内容。

对于pinctrl device来说,需要在s900.dtsi中定义基本的controller信息,如下:

+ pinctrl@e01b0000 {
+         compatible = "actions,s900-pinctrl";
+         reg = <0 0="" 0xe01b0000="" 0x1000="">;
+ };

然后在s900-bubblegum-pinctrl.dtsi中定义所有的pinctrl功能,以及相应的寄存器配置信息,如下:

+ pinctrl@e01b0000 {
+        /* actions,pins = , ...*/
+        pinctrl_serial5: serial5 {
+               actions,pins = <0x0044 23="" 0x3f="" 0xc="">;
+        };

+ };

最后,在中定义版型特有的配置:

+ pinctrl@e01b0000 {
+        u-boot,dm-pre-reloc;
+ };

4.5 编写pinctrl driver

要点包括(具体可参考“https://github.com/wowotechX/u-boot/blob/7340ec6450f5cfa8076dac59811ef2e459dc92b4/drivers/pinctrl/pinctrl-owl.c”):

1)基于pinctrl uclass,注册owl_pinctrl driver,并结合dts文件,添加对应的of_match table

static const struct udevice_id owl_pinctrl_match[] = {
    { .compatible = "actions,s900-pinctrl" },
    { /* sentinel */ }
};

U_BOOT_DRIVER(owl_pinctrl) = {
    .name        = "owl_pinctrl",
    .id        = UCLASS_PINCTRL,
    .of_match    = owl_pinctrl_match,
    .probe        = owl_pinctrl_probe,
    .ops        = &owl_pinctrl_ops,
    .flags        = DM_FLAG_PRE_RELOC,
    .priv_auto_alloc_size = sizeof(struct owl_pinctrl_priv),
};

2)提供probe(owl_pinctrl_probe)接口,以便获取pinctrl device的寄存器基地址,并保存在priv_auto_alloc_size 中(struct owl_pinctrl_priv)。

3)pinctrl uclass要求实现对应的struct pinctrl_ops结构,我们只实现其中最简单的一个----set_state_simple:

static const struct pinctrl_ops owl_pinctrl_ops = {
    .set_state_simple = owl_pinctrl_set_state_simple,
};

4)set_state_simple的原型如下:

static int owl_pinctrl_set_state_simple(struct udevice *dev,
                    struct udevice *periph)

其中dev代表pinctrl device,periph代表引用了相应pinctrl功能的device(如本文中的serial device)。我们需要

在这个接口中,通过“pinctrl-0”关键字,找到该设备所引用的所有pinctrl节点的句柄,并以此获得对应的寄存器配置信息(通过"actions,pins"关键字),然后将配置信息写入到具体的寄存器即可,如下:

{
    int entry, count, i;
    uint32_t cell[20];
    struct owl_pinctrl_priv *priv = dev_get_priv(dev);

    entry = fdtdec_lookup_phandle(gd->fdt_blob, periph->of_offset,
                      "pinctrl-0");
    if (entry < 0)
        return -1;

    count = fdtdec_get_int_array_count(gd->fdt_blob, entry, "actions,pins",
                       cell, ARRAY_SIZE(cell));
    if (count < 4)
        return -1;

    for (i = 0; i < count / 4; i += 4)
        clrsetbits_le32(priv->base + cell[i],
                cell[i + 2] << cell[i + 1],
                cell[i + 3] << cell[i + 1]);

    return 0;
}

注1:当前的实现,只处理了pinctrl-0中第一个字段,后续可以增加多个字段的处理。

5. pinctrl的生效过程

正常情况下(如第4章中serial driver的例子),设备驱动在dts文件中引用对应的pinctrl配置后,设备模型会在设备枚举的时候,自动调用pinctrl提供的接口,将配置生效,具体过程可参考如下的流程(比较简单,因此不再详细分析,感兴趣的同学可以自己阅读源代码):

serial_init—>serial_find_console_or_panic(drivers/serial/serial-uclass.c)
        device_probe(drivers/core/device.c)
                pinctrl_select_state(dev, "default")
                        pinctrl_select_state_full—>pinctrl_select_state_simple(drivers/pinctrl/pinctrl-uclass.c)
                                ops->set_state_simple(pctldev, dev);(drivers/pinctrl/pinctrl-owl.c)

6. 总结

本文通过一个简单的实例,介绍了在u-boot中实现一个简单的pinctrl driver的步骤,以加深对pinctrl subsystem理解。与此同时,通过引入更多的device tree操作(例如分离出不同的DTS文件、通过DTS解析pinctrl配置信息等),也进一步加深了我们对device tree的理解。

最后,本文涉及到的示例代码,可以从如下链接获取:

https://github.com/wowotechX/u-boot/commit/7340ec6450f5cfa8076dac59811ef2e459dc92b4

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

标签: subsystem u-boot porting pinctrl

评论:

wyn93
2017-07-20 16:11
最近在做pinctrl这相关的内容,对于xxx-pinctrl.dtsi这个文件的编写中有几个问题,希望可以解惑。
1、在看这个文件的过程中发现它其实就是在配置管脚(复用寄存器)的复用功能!
2、这个文件中需要列出那些设备有什么决定?
3、您说pin有几种功能是没有关系的,但是我们在区分功能的时候是什么依据?
   比如:
         pmx0: pinconf@f8a21000 {
            pinctrl-names = "default";
            pinctrl-0 = <
                &emmc_p1_pmx_cfg
                &emmc_p2_pmx_cfg
                &emmc_p3_pmx_cfg
                                ****
                        >;
   其中的属性值,在driver中会读取其中的内容吧!谢谢!
wowo
2017-07-21 16:39
@wyn93:1、在看这个文件的过程中发现它其实就是在配置管脚(复用寄存器)的复用功能!
   是的。
2、这个文件中需要列出那些设备有什么决定?
   由硬件决定。
3、您说pin有几种功能是没有关系的,但是我们在区分功能的时候是什么依据?
   你是指状态吗?“default”之类的?状态是根据工作状态,例如正常工作和suspend状态,有些设备可能需要不同的管脚配置。
wyn93
2017-07-25 08:20
@wowo:谢谢你的回复。
1、{  2、这个文件中需要列出那些设备有什么决定?
   由硬件决定。
}
例如:dts中一个设备对应了下面的这几个文件:
s3c2416.dtsi  s3c2416-smdk2416.dts  s3c2416-pinctrl.dtsi  s3c24xx.dtsi
你说的硬件是不是就是s3c2416-smdk2416.dts这个文件中所列出的。

2、{您说pin有几种功能是没有关系的,但是我们在区分功能的时候是什么依据?
   你是指状态吗?“default”之类的?状态是根据工作状态,例如正常工作和     suspend状态,有些设备可能需要不同的管脚配置。
}
我说的并不是状态,我们这个里面没有其他的什么状态,只有一个default状态。
我的意思是对于一个emmc这个设备为什么要分成这三个节点:
      &emmc_p1_pmx_cfg
      &emmc_p2_pmx_cfg
      &emmc_p3_pmx_cfg
去描述,不能一个,两个..。这其中有什么依据。
wowo
2017-07-25 08:52
@wyn93:1. 之所以有这么多文件,和某一个芯片厂商的芯片体系结构有关,例如s3c2416属于s3c24xx系列,s3c2416-smdk2416是基于s3c2416的一个板子。dts的层次结构,用于抽象共性。

2. 要分成几个节点,完全是软件设计的时候根据实际情况弄出来的,至于原因,有时候很好理解,有时候却比较难(要看设计者当时吃的什么饭、想的什么事了,呵呵)。
至于你这个例子,我看不到code,不好下结论,你可以对着硬件想一下。
漫道
2016-07-20 16:23
据说好像一年多没来博主这里了
wowo
2016-07-10 16:30
几种不同的功能,也没有关系的,就在pinctrl driver中将他们都抽象出来,例如:
pinctrl@e01b0000 {
       pinctrl_funcA: funcA {
              ...
       };
       pinctrl_funcB: funcB {
              ...
       };
       pinctrl_funcC: funcC {
              ...
       };
       ...
};

然后在需要使用的DTS中,将它们都引用过来,如:
                pinctrl-names = "funcA", "funcCB", "funcC";
                pinctrl-0 = <&pinctrl_funcA>. <&pinctrl_funcB>. <&pinctrl_funcC>;

在driver中,就可以调用标准接口,切换这三个状态了。

pinctrl的设计思路,大体如此。
qdzhaozx
2016-07-11 09:39
@wowo:嗯 可以,3Q
qdzhaozx
2016-07-10 14:26
我这边做过的产品,有些pin需要几种不同的功能,并且运行的过程要动态改变pin的外设功能属性。所以当时接触pinctrl的时候,完全和之前2.6的代码不一样了。2.6的代码对应的mach下有特定的类似 set_peripheral_A,set_peripheral_B.
无奈在新的代码里将pinctrl driver里export一个接口 设置 A B C GPIO的一个接口

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog