7.IMX6ULL LINUX驱动之Pinctl子系统

379 阅读4分钟

一、Pinctl子系统

借助pinctl设置引脚的复用与电气属性的设置。我们之前的实验是直接操作相应的寄存器,但是这种配置比较繁琐,且容易出现问题。为了解决,引入pinctl子系统:获取设备树的引脚信息,通过获取的引脚信息来设置复用及电气特性。

在参数手册中,IOMUXC部分中有如下三种控制器的寄存器。在设备树当中分别存储了控制器的首地址。

iomuxc: iomuxc@020e0000 {
    compatible = "fsl,imx6ul-iomuxc";
    reg = <0x020e0000 0x4000>;
};
​
gpr: iomuxc-gpr@020e4000 {
    compatible = "fsl,imx6ul-iomuxc-gpr",
    "fsl,imx6q-iomuxc-gpr", "syscon";
    reg = <0x020e4000 0x4000>;
};
iomuxc_snvs: iomuxc-snvs@02290000 {
    compatible = "fsl,imx6ull-iomuxc-snvs";
    reg = <0x02290000 0x10000>;
};

我举一个使用GPIO01_04的LED接口的例子。在imx6ull-14x14-ddr3-arm2.dts文件中有一个结点定义到了。那么问题来了,它是什么呢,其实在该文件的头文件中跟踪,可以跟踪到imx6ul-pinfunc.c中有定义。

它代表五个值,分别:

寄存器的根节点的偏移地址,

电气设置的偏移地址,

输入寄存器的寄存器,

复用模式,

输入寄存器中填写的值。

        pinctrl_tsc: tscgrp {
            fsl,pins = <
                MX6UL_PAD_GPIO1_IO01__GPIO1_IO01    0xb0
                MX6UL_PAD_GPIO1_IO02__GPIO1_IO02    0xb0
                MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0xb0
                MX6UL_PAD_GPIO1_IO04__GPIO1_IO04    0xb0
            >;
        };
/*<mux_reg conf_reg input_reg mux_mode input_val>*/
#define MX6UL_PAD_GPIO1_IO04__GPIO1_IO04                          0x006C 0x02F8 0x0000 0x5 0x0

1.pincrtl驱动

找到pincrtl子系统的驱动程序,它是厂商做的部分。设备树如何找到驱动呢?通过compatible属性将设备树与.c驱动文件进行匹配。因此我们可以进行全局搜索字符串的方法来找到pinctl驱动程序。

匹配以后,执行probe函数。probe执行与之匹配设备的函数。

3_probe.png

二、GPIO子系统

使用GPIO的框架。作为GPIO功能就要使用GPIO的子系统。我们以前的程序使用了大量手动映射寄存器,很是麻烦,使用gpio框架的子系统后,提供了很多操作gpio的函数,对于开发方便了很多。

1、设置GPIO

就拿SD卡的结点做例子。

其中**cd-gpios = <&gpio1 1 GPIO_ACTIVE_LOW>; **这一行很重要。描述SD卡的CD引脚的gpio1_1的IO口,GPIO_ACTIVE_LOW = 1<<0代表低电平有效。

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

2、如何使用cd-gpios信息

使用gpio相关的of函数。

1.获取设备结点

2.获取gpio编号, of_get_named_gpio函数。

3.请求编号的gpio gpio_request

4.设置gpio输入输出, gpio_dierction_input/gpio_dierction_output

5.如果是输入,gpio,通过gpio_get_value读取GPIO ;如果是输出,通过gpio_set_value设置GPIO

6.最后释放 gpio_free

3、GPIO驱动

在drivers/gpio-xxx.c文件下对应的最底层的部分。

gpiolib-xxx.c文件是中间文件用于底层与应用层文件进行交互。

现在暂且不去分析了。

三、写驱动

1.设备树修改

写入pinctl的信息以及gpio子系统的信息。

其中重要的是pinctrl-0信息,led-gpios的信息。其实分别就是pinctrl子系统与gpio子系统了。

gpioled{
        compatible = "yifan,gpioled";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_gpioled>;
        led-gpios = <&gpio1 4 GPIO_ACTIVE_LOW>, /* red */
                    <&gpio4 20 GPIO_ACTIVE_LOW>, /* green */
                    <&gpio4 19 GPIO_ACTIVE_LOW>; /* blue */
        status = "okay";
    };
};
pinctrl_gpioled:ledgrp{
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO04__GPIO1_IO04    0x10b0
        MX6UL_PAD_CSI_HSYNC__GPIO4_IO20     0x10b0
        MX6UL_PAD_CSI_VSYNC__GPIO4_IO19     0x10b0
    >;
};

2.搭建驱动框架

不详细介绍了,总之就是注册注销模块。构建fops结构体,填充指针函数。

3.使用GPIO的of函数

1.首先根据路径获取设备结点,我觉得这样方便一些。

2.根据设备树获取led对应的编号。

3.根据led编号申请IO。

4.设置IO为输出,并且赋初值。

/*设备结构体*/
struct gpio_led{
    dev_t   devid;        /*设备号*/
    int     major;        /*主设备号*/
    int     minor;        /*次设备号*/
    struct  cdev cdev;    /*字符设备*/
    struct class *class;  /*设备的类*/
    struct device *device;/*设备*/
    struct device_node *nd;/*设备结点*/
    
    int    led_gpio[3];      /*IO的编号*/
    char   *name[10];         /*IO申请名称*/ 
};
struct gpio_led led;
    /*  1.获取设备结点    */
    led.nd = of_find_node_by_path("/gpioled");
    if(led.nd == NULL){
        ret = -EINVAL;
        goto failed_findnd;
    }
    
    /*  2.获取Led对应的GPIO   */
    for(i = 0 ; i < 3 ; i++ ){
        led.led_gpio[i] = of_get_named_gpio(led.nd , "led-gpios" , i);
        if(led.led_gpio[i] < 0){
            printk("led.led_gpio%d < 0\r\n" ,i );
            ret = -EINVAL;
            goto failed_findnd;
        }
        printk("led.led_gpio[%d]  = %d\r\n" , i ,led.led_gpio[i] );
​
        /*  3.申请IO    */
        led.name[i]="---------";
        sprintf(led.name[i], "led-gpios");
        printk("%s\r\n" , led.name[i]);
        ret = gpio_request(led.led_gpio[i], led.name[i]);
        if (ret) {
            printk("failed to request GPIO for LED\n");
            ret = -EINVAL;
            goto failed_findnd;
        }
        
        /*  4.使用IO,设置为输出  */
        ret = gpio_direction_output(led.led_gpio[i] ,1 );
        if(ret < 0){
            goto failed_setio;
        }
        //printk("led.led_gpio[%d] is close\r\n" , i);
​
        /*  5.输出电平 ,点亮LED灯 */
        gpio_set_value(led.led_gpio[i] , 0);
    }
​
    return 0;
failed_setio:
    for(i = 0; i< 3 ; i++)
        gpio_free(led.led_gpio[i]);
    printk("failed_request\r\n");
failed_findnd:
    device_destroy(led.class , led.devid);
    printk("failed_findnd\r\n");
failed_device:
    class_destroy(led.class);
    printk("failed_device\r\n");
failed_class:
    cdev_del(&led.cdev);
    printk("failed_class\r\n");
failed_cdev:
    unregister_chrdev_region(led.devid , LED_CNT);
    printk("failed_cdev\r\n");
failed_devid:
    return ret;

4.应用接口函数

与以往的程序相差无几。只需要使用gpio_set_value,原型如下。

static inline void gpio_set_value(unsigned gpio, int value);

\