pinctrl与gpio子系统下的字符设备驱动框架

描述

pinctrl与gpio子系统下的字符设备驱动框架

点亮Linux驱动开发路上的第一个灯一文中将与外设有关的寄存器信息,定义到驱动代码中,直接操作寄存器来控制外设。缺点是当芯片的寄存器发了变动,就要对底层的驱动进行重写。

设备树下的字符设备驱动框架一文中将与外设有关的寄存器信息,写到了设备树文件中,通过设备树API函数获取外设信息。当外设的信息有变化时,只需要修改设备树文件即可,无需修改底层驱动,提高了驱动代码的复用能力,但仍需要直接操作寄存器来控制外设。

本文介绍的pinctrl和gpio子系统实现了对寄存器的操作,我们只需要使用子系统提供的API函数即可,而无需再直接操作寄存器了。

1. pinctrl与gpio子系统介绍

1.1 pinctrl子系统

pinctrl子系统就是管理PIN引脚的一个系统,在设备树里设置PIN的配置信息,pinctrl会根据提供的信息来配置PIN功能。其主要工作内容如下:

  • 获取设备树中的PIN信息
  • 设置获取到的PIN的复用功能
  • 设置获取到的PIN的电气特性

打开设备树文件imx6ull-andyxi-emmc.dts,其中iomuxc节点就是I.MX6ULL的外设节点。代码中的pinctrl_hog_1子节点就是和热插拔有关的PIN集合

&iomuxc {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hog_1>;
    imx6ul-evk {
        pinctrl_hog_1: hoggrp-1 {
            fsl,pins = <
                MX6UL_PAD_UART1_RTS_B__GPIO1_IO19        0x17059 
                MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT     0x17059 
                MX6UL_PAD_GPIO1_IO09__GPIO1_IO09         0x17059 
                MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID     0x13058
           >;
        };
        ......
    };
};

下面以UART1_RTS_B这个PIN为例,介绍如何添加PIN的配置信息:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19     0x17059

前半部分:设置UART1_RTS_B引脚的复用功能

//MX6UL_PAD_UART1_RTS_B__GPIO1_IO19宏定义在文件arch/arm/boot/dts/imx6ul-pinfunc.h中
#define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS          0x0090 0x031C 0x0620 0x0 0x3
#define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS          0x0090 0x031C 0x0000 0x0 0x0
#define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER            0x0090 0x031C 0x0000 0x1 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B            0x0090 0x031C 0x0668 0x2 0x1
#define MX6UL_PAD_UART1_RTS_B__CSI_DATA05             0x0090 0x031C 0x04CC 0x3 0x1
#define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT  0x0090 0x031C 0x0000 0x4 0x0
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19             0x0090 0x031C 0x0000 0x5 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B            0x0090 0x031C 0x0674 0x8 0x2

上面代码中共有8个UART1_RTS_B引脚的宏定义,分别对应该引脚的8个复用IO。宏定义的值,被分为了5段,每段的值都有具体的含义:

寄存器

  • mux_reg:mux_reg寄存器偏移地址 (见下图1)
  • conf_reg:conf_reg寄存器偏移地址 (见下图2)
  • input_reg:input_reg寄存器偏移地址 (此处无效)
  • mux_mode:mux_reg寄存器的值,用于设置复用功能 (见下图3)
  • input_val:input_reg寄存器值 (此处无效)

寄存器

寄存器

寄存器

后半部分:此值就是conf_reg寄存器的值,用于设置UART1_RTS_B引脚的电气特性

1.2 gpio子系统

gpio子系统就是管理gpio功能的一个系统,其作用是初始化gpio,并提供对外的API接口。使用gpio子系统后,就无需自己操作寄存器,通过调用相关API函数即可实现对gpio的控制。

仍以热插拔节点为例,pinctrl子系统已经将UART1_RTS_B复用为GPIO1_IO19,并设置好了电气属性。驱动程序通过读取其高低电平来判断SD卡有没有插入。

那驱动程序怎么知道CD引脚连接的是GPIO1_IO19呢 ?这就需要设备树来告诉驱动了,在设备树的SD卡节点下添加一个描述CD引脚的属性:

&usdhc1 {
    pinctrl-names = "default", "state_100mhz", "state_200mhz";
    pinctrl-0 = <&pinctrl_usdhc1>;
    pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
    pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
    pinctrl-3 = <&pinctrl_hog_1>;
    cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
    keep-power-in-suspend;
    enable-sdio-wakeup;
    vmmc-supply = <®_sd1_vmmc>;
    status = "okay";
};

上面代码中pinctrl-3指定了CD引脚的pinctrl信息,cd-gpios属性描述了CD引脚使用哪个IO,属性值共有三个,具体含义如下:

  • &gpio1 表示CD引脚所使用的IO属于GPIO1组
  • 19 表示GPIO1组的第19号IO
  • GPIO_ACTIVE_LOW 表示低电平有效

设置好设备树后就可使用gpio子系统提供的API函数来操作指定的GPIO,gpio子系统向开发人员屏蔽了具体的读写寄存器过程。下图中有常用的API函数介,此外还有pinctrl和gpio子系统的使用模板:

寄存器

**2. **pinctrl与gpio子系统字符设备驱动框架

下图为pinctrl与gpio子系统下的字符设备驱动框架:

寄存器

接下来根据上面的框架图,以驱动LED (GPIO1_IO03) 为例,分步介绍具体的代码编写流程

2.1 修改设备树文件

添加pinctrl节点:在iomuxc节点的imx6ul-evk子节点下创建pinctrl_led节点,复用GPIO1_IO03

pinctrl_led: ledgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0x10B0
    >;
};
//MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 复用GPIO1_IO03
//0x10B0 设置pin的电气特性

添加LED设备节点:在根节点下创建LED设备节点,指定对应的pinctrl节点,指定所使用的GPIO

gpioled {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "alpha-gpioled";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_led>;
    led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
    status = "okay";
};

检查PIN是否冲突:检查pinctrl和设备节点中指定的引脚有没有被别的外设占用

//检查GPIO_IO03这个PIN有没有被其他的pinctrl节点使用
pinctrl_tsc: tscgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
        MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
        //GPIO_IO03被pinctrl_tsc节点占用,因此需要屏蔽掉
        /* MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0 */ 
        MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
    >;
};
//检查"gpio1 3"有没有被其他设备节点占用
&tsc {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_tsc>;
    //"gpio1 3"被tsc设备节点占用,因此需要屏蔽掉
    /* xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; */
    measure-delay-time = <0xffff>;
    pre-charge-time = <0xfff>;
    status = "okay";
};

编译设备树:使用make dtbs命令编译设备树,并使用该设备树启动Linux系统

#在内核根目录下
make dtbs                #编译设备树
#启动Linux系统后
cd /proc/device-tree     #查看"gpioled"节点是否存在

2.2 编写驱动程序

创建驱动程序文件gpioled.c,添加如下代码

宏定义及设备结构体定义

struct gpioled_dev{
    dev_t devid;                //设备号
    struct cdev cdev;           //cdev字符设备
    struct class *class;        //类
    struct device *device;      //设备
    int major;                  //主设备号
    int minor;                  //次设备号
    struct device_node *nd;     //设备节点
    int led_gpio;               //所使用的gpio编号
};

struct gpioled_dev gpioled;     //定义led设备

编写设备操作函数:write函数中,直接使用gpio子系统提供的API函数操作GPIO

static ssize_t led_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;
    retvalue = copy_from_user(databuf, buf, cnt); 
    if(retvalue < 0){
        printk("kernel write failed!\\r\\n");
        return -EFAULT;
    }

    ledstat = databuf[0];
    if(ledstat == LEDON){
        gpio_set_value(dev->led_gpio, 0);
    }else if(ledstat == LEDOFF){
        gpio_set_value(dev->led_gpio, 1);
    }
    return 0;
}

驱动入口函数中:获取GPIO编号后初始化

/* 设置 LED 所使用的 GPIO */
/* 1、获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
    printk("gpioled node cant not found!\\r\\n");
    return -EINVAL;
} else {
    printk("gpioled node has been found!\\r\\n");
}
/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0) {
    printk("can't get led-gpio");
    return -EINVAL;
}
printk("led-gpio num = %d\\r\\n", gpioled.led_gpio);
/* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
    printk("can't set gpio!\\r\\n");
}

驱动入口函数中:注册字符设备驱动,代码与Linux点灯中一样

驱动出口函数中:注销设备驱动,删除类和设备,代码可参考Linux点灯一文

2.3 编写测试程序

实现操作驱动文件对外设进行控制的功能。创建测试程序文件gpioledApp.c,代码内容与Linux点灯一文中的测试程序代码一致,此处不再赘述

2.4 编译测试

编译驱动程序:当前目录下创建Makefile文件,并make编译

KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := gpioled.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译测试程序:无需内核参与,直接编译即可

arm-linux-gnueabihf-gcc gpioledApp.c -o gpioledApp

运行测试:启动开发板后,加载驱动模块,之后使用应用程序测试驱动是否正常工作

depmod                         #第一次加载驱动的时候需要运行此命令
modprobe gpioled.ko            #加载驱动
./gpioledApp /dev/gpioled 1    #打开LED灯
./gpioledApp /dev/gpioled 0    #关闭LED灯
rmmod gpioled.ko               #卸载驱动模块
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分