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

万航渡路

9年用户 1409经验值
擅长:电源/新能源
私信 关注
[问答]

怎样去使用linux下的pintcrl和gpio子系统呢

pinctrl和gpio内部的原理是如何实现的?
怎样去使用linux下的pintcrl和gpio子系统呢?



回帖(2)

李悠冉

2022-3-7 15:11:40
       linux下的pintcrl和gpio子系统就类似于ST的“BSP库”,但是linux的pinctrl和gpio系统实现的功能和过程要远远比STM32的“BSP库”复杂。linux下引入pincrtl和gpio子系统,大大释放了驱动工程师的工作量,特别是引入“设备树”之后,使用一个外设时,对于pin引脚的初始化和管理,只需通过设备树描述即可,然后由pin子系统管理;对于gpio则由gpio子系统管理。

  因此,与CPU引脚“关联”的设备驱动,最终都会调用pincrtl和gpio子系统。二者是设备驱动的基础,这二者也是一个设备驱动。

2. pinctrl子系统

2.1 pinctrl子系统功能

  CPU的gpio引脚除了的方向、速度、上下拉、驱动能力等基本的电气特性外,一般会包括复用功能,即该引脚既可以作为普通gpio,还可能复位为i2c引脚、uart引脚等。如果采用直接配置寄存器的方式进行驱动开发,会非常繁琐,每更改一个功能就得重新翻阅手册配一遍寄存器,另一方面还可能存在“冲突”问题,比如该引脚已被复用为i2c在使用,但被驱动工程师忽略了,再去使用该gpio时会导致未知预期的问题。引入pintctrl子系统就可以解决诸如此类问题,结合设备树的使用,只需把pin信息在设备树描述清楚,即由pinctrl子系统介入管理。


pinctrl对于pin管理功能:


  • .关联设备树,根据设备树pin信息在内核起来后进行配置pin引脚
  • pin复用功能管理
  • pin电气特性设置


2.2 pin设备树描述


#源自rk3399-firefly-port.dtsi
&pinctrl {
        buttons {
                pwrbtn: pwrbtn {
                        rockchip,pins = <0 5 RK_FUNC_GPIO &pcfg_pull_up>;
                };
        };

        leds {
                led_power: led-power {
                        rockchip,pins = <2 27 RK_FUNC_GPIO &pcfg_pull_none>;
                };

                led_user: led-user {
                        rockchip,pins = <0 13 RK_FUNC_GPIO &pcfg_pull_none>;
                };
        };
..........
#源自rk3399.dtsi
i2c3 {
                        i2c3_xfer: i2c3-xfer {
                                rockchip,pins =
                                        <4 17 RK_FUNC_1 &pcfg_pull_none>,        #复用为i2c属性
                                        <4 16 RK_FUNC_1 &pcfg_pull_none>;
                        };

                        i2c3_gpio: i2c3_gpio {
                                rockchip,pins =
                                        <4 17 RK_FUNC_GPIO &pcfg_pull_none>,#复用为普通gpio属性
                                        <4 16 RK_FUNC_GPIO &pcfg_pull_none>;
                        };

                };

以上设备树摘自部分rk3399的设备树文件:



  • 按键、LED等pin设置为普通GPIO功能
  • 对于i2c3的pin,存在复用功能,在对应的外设节点的设备树下指定使用哪个pin属性,但不能同时用,同时使用pin子系统会返回错误信息


#源自rk3399.dtsi
i2c3: i2c@ff130000 {
           compatible = "rockchip,rk3399-i2c";
           reg = <0x0 0xff130000 0x0 0x1000>;
           clocks = <&cru SCLK_I2C3>, <&cru PCLK_I2C3>;
           clock-names = "i2c", "pclk";
           interrupts = ;
           pinctrl-names = "default";        #默认功能是i2c
           pinctrl-0 = <&i2c3_xfer>;        #使用i2c复用功能的pin属性
           #address-cells = <1>;
           #size-cells = <0>;
           status = "disabled";
   };



  • 关于pin的宏(RK_FUNC_1、RK_FUNC_GPIO等) 位于 “kernel/include/dt-bindings/pinctrl/rockchip.h” 中声明。

2.3 pin驱动

  pin驱动一般芯片原厂已经提供,rk3399 pin驱动位于源码位于“kernel/drivers/pinctrl/pinctrl-rockship.c”中。“pinctrl-rockchip.c”支持了瑞芯微常用的CPU,如rk3288、rk3399、px30等,使用哪一款CPU,与linux其他驱动一样,可以通过pinctrl节点的设备树进行自动选择匹配。

rockchip pinctrl驱动匹配表

static const struct of_device_id rockchip_pinctrl_dt_match[] = {
        ........
        { .compatible = "rockchip,rk3328-pinctrl",
                .data = &rk3328_pin_ctrl },
        { .compatible = "rockchip,rk3366-pinctrl",
                .data = &rk3366_pin_ctrl },
        { .compatible = "rockchip,rk3368-pinctrl",
                .data = &rk3368_pin_ctrl },
        { .compatible = "rockchip,rk3399-pinctrl",
                .data = &rk3399_pin_ctrl },
        {},
};
static struct platform_driver rockchip_pinctrl_driver = {
        .probe                = rockchip_pinctrl_probe,
        .driver = {
                .name        = "rockchip-pinctrl",
                .pm = &rockchip_pinctrl_dev_pm_ops,
                .of_match_table = rockchip_pinctrl_dt_match,
        },
};

rk3399 pinctrl 节点设备树描述

pinctrl: pinctrl {
                compatible = "rockchip,rk3399-pinctrl";
                rockchip,grf = <&grf>;
                rockchip,pmu = <&pmugrf>;
                #address-cells = <2>;
                #size-cells = <2>;
                ranges;
                .........


3. gpio子系统

3.1 gpio子系统功能

  pinctrl子系统主要是管理pin的电气属性和复用功能,而gpio子系统则是管理gpio的申请释放、控制输入输出、io中断等功能。gpio子系统屏蔽了gpio相关寄存器的配置过程,换而提供了常用的接口函数给驱动工程师使用,方便gpio相关的驱动开发。

常见与gpio相关的驱动:



  • 普通字符驱动,LED、Button
  • 通过gpio触发中断的驱动,touch、mp6050

gpio子系统功能


  • 对于驱动层,屏蔽gpio寄存器配置细节,提供统一gpio操作接口
  • 对于BSP层,统一框架,方便不同CPU接入,只需更换pinctrl子系统的驱动

3.2 gpio子系统常用函数


  gpio子系统对于驱动层的API位于“/kernel/include/linux/gpio.h”中。

【1】检查gpio是否可用

int gpio_is_valid(int number);

[tr]参数/含义/含义[/tr]
numbergpio序号
返回可用返回true,不可用返回false


【2】申请使用一个gpio

  使用一个gpio前,必须向内核申请该gpio。

int gpio_request(unsigned gpio, const char *label)

[tr]参数/含义[/tr]
gpio待申请gpio序号
labelgpio命名
返回成功返回0,失败返回负数

【3】释放已申请gpio

  如果不使用该gpio,则需要释放,否则其他模块申请不到该gpio序号。

int gpio_free(unsigned gpio)

[tr]参数/含义[/tr]
gpio待释放gpio序号
labelgpio命名


【4】设置gpio输入模式

int gpio_direction_input(unsigned gpio)

[tr]参数含义[/tr]
gpio待设置gpio序号
返回成功返回0,失败返回负数


【5】设置gpio输出模式

void gpio_set_value(unsigned gpio, int value)

[tr]参数含义[/tr]
gpio待设置gpio序号
value默认输出状态
返回成功返回0,失败返回负数



【6】读取 gpio状态

int gpio_get_value(unsigned int gpio)

[tr]参数含义[/tr]
gpio待读取gpio序号
返回成功返回gpio状态(1/0),失败返回负数


【7】设置 gpio状态

void gpio_set_value(unsigned int gpio, int value)

[tr]参数含义[/tr]
gpio待设置gpio序号
value待设置值

【8】中断号映射

int gpio_to_irq(unsigned gpio)

[tr]参数含义[/tr]
gpio待设置gpio序号
返回成功返回中断号,失败返回负数

4. 应用

  利用pinctrl和gpio子系统实现一个io翻转控制LED。firefly-rk3399板子上有两个LED,分别是power led和user led。linux自带的“gpio-leds”驱动是在linux led框架的基础上实现的,我们使用user led的gpio口,不使用led框架,直接使用gpio子系统实现io输出状态控制和读取。

4.1 修改设备树


【1】首先屏蔽rk3399原有的“led user”设备树,否则驱动会冲突。

    leds {
                compatible = "gpio-leds";
                ......
                /*user {
                 *      label = "firefly:yellow:user";
                 *      linux,default-trigger = "ir-user-click";
                 *      gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
                 *      pinctrl-names = "default";
                 *      pinctrl-0 = <&led_user>;
                 *      default-state = "off";
                 *};
                 */
        };

【2】添加本次实现的驱动设备树。

  在“kernel/arch/arm64/boot/dts/rockchip/rk3399-firefly.dtsi”

      gpiopin{
                compatible = "gpiopin";        /*驱动兼容属性*/
                gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;/* gpio描述,提供给pinctrl子系统使用*/
                pinctrl-names = "default";        /*pin脚默认状态*/
                pinctrl-0 = <&led_user>;        /*直接使用rk原命名的pin脚设备树,也可以单独重新命名*/
                default-state = "off";                /*默认gpio输出0*/
        };
#源自rk3399-firefly-port.dtsi
&pinctrl {
        leds {
                led_power: led-power {
                        rockchip,pins = <2 27 RK_FUNC_GPIO &pcfg_pull_none>;
                };

                led_user: led-user {
                        rockchip,pins = <0 13 RK_FUNC_GPIO &pcfg_pull_none>;/*pin脚为普通gpio模式*/
                };
        };

  修改完设备树,可以编译内核,更新板子boot区域(内核和设备树文件)。

4.2 驱动源码

  设备驱动则是套用linux的platform驱动框架了,platform驱动框架结合设备树一起使用,通过设备节点和驱动匹配,然后注册驱动。另外,通过gpio子系统,控制一个gpio是非常简易的事情了,就类似使用STM32的标准库控制gpio。

4.2.1 platform驱动匹配和注册


static struct of_device_id of_gp0_ids[] = {
   {.compatible = "gpiopin"},        /* 与节点设备树“compatible ”属性一致 */
   { }   
};

static struct platform_driver gp0_driver = {
        .driver   = {
        .owner    = THIS_MODULE,
        .name     = DEV_NAME,
        .of_match_table = of_match_ptr(of_gp0_ids),
        },
        .probe           = gp0_probe,
        .remove   = gp0_remove,
};

module_platform_driver(gp0_driver);                /* platform 驱动注册和注销 */
举报

李杨

2022-3-7 15:11:43
4.2.2 probe

  probe探测函数,驱动和设备树匹配时,会调用该函数,该函数完成gpio配置及字符驱动的创建过程。



  • 调用gpio子系统函数接口和获取设备树节点属性进行配置gpio
  • 创建字符驱动


static int gp0_probe(struct platform_device *pdev)  
{     
    struct device *dev;
        int ret = -1;
        dev_t        id = 0;

        //gp0.nd = of_find_node_by_path("/gpiopin");
        gp0.nd =  pdev->dev.of_node;
        if(gp0.nd == NULL)
        {
                printk("get node faileedn");
                return -1;
        }
        gp0.gpio = of_get_named_gpio(gp0.nd, "gpios", 0);/* 获取gpio序号,"gpios"为设备树的描述信息 */
        if(gp0.gpio < 0)
        {
                printk("get gpio failedn");
                return -1;
        }
        ret = gpio_request(gp0.gpio, "gp0");                /* 申请GPIO */
        if(ret < 0)
        {
                printk("gpio request failedn");
                return ret;
        }
      
        ret = gpio_direction_output(gp0.gpio, 0);        /* output,low default */
        if(ret<0)
        {
                printk("gpio set failedn");
                gpio_free(gp0.gpio);
                return ret;
        }
      
        /*创建字符驱动过程*/
        ......
}


4.2.3 read / write

  read/write函数主要调用gpio子系统函数读取或者设置gpio状态。

static ssize_t gp0_read(struct file *pfile, char __user *buf, size_t size, loff_t *offset)
{
        int ret = 0;
        struct gpiopin_dev *p;
        char level = 0;
      
        p = pfile->private_data;

        level = gpio_get_value(p->gpio);      
        ret = copy_to_user(buf, &level, sizeof(level));
      
    return ret;
}

static ssize_t gp0_write(struct file *pfile, const char __user *buf, size_t size, loff_t *offset)
{
        int ret = 0;
        struct gpiopin_dev *p;
        char level = 0;
      
        p = pfile->private_data;

        ret = copy_from_user(&level, buf, size);
        gpio_set_value(p->gpio, level);
      
    return ret;
}

4.3 Makefile

ifeq ($(KERNELRELEASE),)

KERNELDIR = /opt/rk3399/linux-sdk/linux-sdk/kernel
PWD := $(shell pwd)

modules:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
        rm -rf *.o *.ko .mod.o *.mod.c *.symvers

else
        obj-m := gpiopin.o
endif

4.4 运行

4.4.1 加载驱动

  执行Makefile把驱动程编译为模块文件,生成gpiopin.ko文件,把文件通过NFS、U盘等传输至板子,系统起来后手动insmod到内核。驱动加载成功,会在“/dev”目录上生成设备文件。如图,“gp0”就是生成的驱动文件。


4.4.2

  测试应用程序,执行“aarch64-linux-gnu-gcc gpiopin_app.c -o gpiopin_app”,生成可以执行文件“gpiopin_app”,把文件通过NFS、U盘等传输至板子“/home”目录,然后需更改文件属性,增加可执行属性。

int main(int argc, int **argv)
{
        int fd;
        uint8_t w_level = 1, r_level = 0;
      
        fd = open("/dev/gp0", O_RDWR);
        if(-1 == fd)
        {
                perror("open gpiopin failedn");
                return -1;
        }
        printf("open gpiopin driver succn");
      
        for(;;)
        {
                write(fd, &w_level, 1);
                read(fd, &r_level, 1);
                printf("pin = %dn", r_level);
                w_level = !w_level;
                sleep(1);
        }
        close(fd);
      
        return 0;
}



5.源码

6. 参考

  本文主要目的是理清pinctrl子系统和gpio子系统的使用,关于pinctrl和gpio内部的原理实现可以参考以下分析文章。pinctrl子系统和gpio子系统的驱动属于原厂“BSP”范畴,芯片原厂已经实现,如果是驱动工程师只需熟悉其接口(当然能够深入理解其实现原理和框架是最好的)即可进行驱动开发工作。
举报

更多回帖

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