跳转至

使用示例

5 使用示例

5.1 使用 pin 的驱动 dts 配置示例

对于使用 pin 的驱动来说,驱动主要设置 pin 的常用的几种功能,列举如下:

驱动使用者只配置通用 GPIO, 即用来做输入、输出和中断的

驱动使用者设置 pin 的 pin mux,如 uart 设备的 pin,lcd 设备的 pin 等,用于特殊功能

驱动使用者既要配置 pin 的通用功能,也要配置 pin 的特性

下面对常见使用场景进行分别介绍。

5.1.1 配置通用 GPIO 功能/中断功能

用法一:配置 GPIO,中断,device tree 配置 demo 如下所示:

soc{
    ...
    gpiokey {
        device_type = "gpiokey"; 
        compatible = "gpio-keys";

        ok_key {
            device_type = "ok_key";
            label = "ok_key";
            gpios = <&r_pio PL 0x4 0x0 0x1 0x0 0x1>; //如果是linux-5.4,则应该为gpios = <&r_pio 0 4 GPIO_ACTIVE_HIGH>;
            linux,input-type = "1>";
            linux,code = <0x1c>;
            wakeup-source = <0x1>;
            };
        };
    ...
};

说明

说明:gpio in/gpio out/ interrupt采用dts的配置方法,配置参数解释如下:
对于linux-4.9:
gpios = <&r_pio PL 0x4 0x0 0x1 0x0 0x1>;
            |    |  |   |   |   |   `---输出电平,只有output才有效
            |    |  |   |   |   `-------驱动能力,值为0x0时采用默认值
            |    |  |   |   `-----------上下拉,值为0x1时采用默认值
            |    |  |   `---------------复用类型
            |    |  `-------------------当前bank中哪个引脚
            |    `-----------------------哪个bank
            `---------------------------指向哪个pio,属于cpus要用&r_pio
使用上述方式配置gpio时,需要驱动调用以下接口解析dts的配置参数:
int of_get_named_gpio_flags(struct device_node *np, const char *list_name, int index,
enum of_gpio_flags *flags)
拿到gpio的配置信息后(保存在flags参数中,见4.2.8.小节),在根据需要调用相应的标准接口实现自己的功能
对于linux-5.4:
gpios = <&r_pio 0 4 GPIO_ACTIVE_HIGH>;
            |   |      |
            |   |      `-------------------gpio active时状态,如果需要上下拉,还可以或上
            GPIO_PULL_UP、GPIO_PULL_DOWN标志
            |   `-----------------------哪个bank
            `---------------------------指向哪个pio,属于cpus要用&r_pio

5.1.2 用法二

用法二:配置设备引脚,device tree 配置 demo 如下所示:

device tree对应配置
soc{
    pio: pinctrl@0300b000 {
        ...
        uart0_ph_pins_a: uart0-ph-pins-a {
            allwinner,pins = "PH7", "PH8"; 
            allwinner,function = "uart0"; 
            allwinner,muxsel = <3>;
            allwinner,drive = <0x1>;
            allwinner,pull = <0x1>;
        };
        /* 对于linux-5.4 请使用下面这种方式配置 */
        mmc2_ds_pin: mmc2-ds-pin {
            pins = "PC1";
            function = "mmc2";
            drive-strength = <30>;
            bias-pull-up;
        };
        ...
    };
    ...
    uart0: uart@05000000 {
        compatible = "allwinner,sun8i-uart";
        device_type = "uart0";
        reg = <0x0 0x05000000 0x0 0x400>;
        interrupts = <GIC_SPI 49 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&clk_uart0>;
        pinctrl-names = "default", "sleep";
        pinctrl-0 = <&uart0_pins_a>;
        pinctrl-1 = <&uart0_pins_b>;
        uart0_regulator = "vcc-io";
        uart0_port = <0>;
        uart0_type = <2>;
    };
    ...
};

其中:

pinctrl-0 对应 pinctrl-names 中的 default,即模块正常工作模式下对应的 pin 配置

pinctrl-1 对应 pinctrl-names 中的 sleep,即模块休眠模式下对应的 pin 配置

5.2 接口使用示例

5.2.1 配置设备引脚

一般设备驱动只需要使用一个接口 devm_pinctrl_get_select_default 就可以申请到设备所有pin 资源。

static int sunxi_pin_req_demo(struct platform_device *pdev)
{ 
    struct pinctrl *pinctrl;
    /* request device pinctrl, set as default state */
    pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
    if (IS_ERR_OR_NULL(pinctrl))
        return -EINVAL;

    return 0;
}

5.2.2 获取 GPIO 号

static int sunxi_pin_req_demo(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    unsigned int gpio;

    #get gpio config in device node.
    gpio = of_get_named_gpio(np, "vdevice_3", 0);
    if (!gpio_is_valid(gpio)) {
        if (gpio != -EPROBE_DEFER)
            dev_err(dev, "Error getting vdevice_3\n");
        return gpio;
    }
}

5.2.3 GPIO 属性配置

通过 pin_config_set/pin_config_get/pin_config_group_set/pin_config_group_get 接口单独控制指定 pin 或 group 的相关属性。

static int pctrltest_request_all_resource(void)
{
    struct device *dev;
    struct device_node *node;
    struct pinctrl *pinctrl;
    struct sunxi_gpio_config *gpio_list = NULL;
    struct sunxi_gpio_config *gpio_cfg;
    unsigned gpio_count = 0;
    unsigned gpio_index;
    unsigned long config;
    int ret;

    dev = bus_find_device_by_name(&platform_bus_type, NULL, sunxi_ptest_data->dev_name);
    if (!dev) {
        pr_warn("find device [%s] failed...\n", sunxi_ptest_data->dev_name);
        return -EINVAL;
    }

    node = of_find_node_by_type(NULL, dev_name(dev));
    if (!node) {
        pr_warn("find node for device [%s] failed...\n", dev_name(dev));
        return -EINVAL;
    }
    dev->of_node = node;

    pr_warn("++++++++++++++++++++++++++++%s++++++++++++++++++++++++++++\n", __func__);
    pr_warn("device[%s] all pin resource we want to request\n", dev_name(dev));
    pr_warn("-----------------------------------------------\n");

    pr_warn("step1: request pin all resource.\n");
    pinctrl = devm_pinctrl_get_select_default(dev);
    if (IS_ERR_OR_NULL(pinctrl)) {
        pr_warn("request pinctrl handle for device [%s] failed...\n", dev_name(dev));
        return -EINVAL;
    }

    pr_warn("step2: get device[%s] pin count.\n", dev_name(dev));
    ret = dt_get_gpio_list(node, &gpio_list, &gpio_count);
    if (ret < 0 || gpio_count == 0) {
        pr_warn(" devices own 0 pin resource or look for main key failed!\n");
        return -EINVAL;
    }

    pr_warn("step3: get device[%s] pin configure and check.\n", dev_name(dev));
    for (gpio_index = 0; gpio_index < gpio_count; gpio_index++) {
        gpio_cfg = &gpio_list[gpio_index];

        /*check function config */
        config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_FUNC, 0xFFFF);
        pin_config_get(SUNXI_PINCTRL, gpio_cfg->name, &config);
        if (gpio_cfg->mulsel != SUNXI_PINCFG_UNPACK_VALUE(config)) {
            pr_warn("failed! mul value isn't equal as dt.\n");
            return -EINVAL;
        }

        /*check pull config */
        if (gpio_cfg->pull != GPIO_PULL_DEFAULT) {
            config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_PUD, 0xFFFF);
            pin_config_get(SUNXI_PINCTRL, gpio_cfg->name, &config);
            if (gpio_cfg->pull != SUNXI_PINCFG_UNPACK_VALUE(config)) {
                pr_warn("failed! pull value isn't equal as dt.\n");
                return -EINVAL;
            }
        }

        /*check dlevel config */
        if (gpio_cfg->drive != GPIO_DRVLVL_DEFAULT) {
            config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DRV, 0XFFFF);
            pin_config_get(SUNXI_PINCTRL, gpio_cfg->name, &config);
            if (gpio_cfg->drive != SUNXI_PINCFG_UNPACK_VALUE(config)) {
                pr_warn("failed! dlevel value isn't equal as dt.\n");
                return -EINVAL;
            }
        }

        /*check data config */
        if (gpio_cfg->data != GPIO_DATA_DEFAULT) {
            config = SUNXI_PINCFG_PACK(SUNXI_PINCFG_TYPE_DAT, 0XFFFF);
            pin_config_get(SUNXI_PINCTRL, gpio_cfg->name, &config);
            if (gpio_cfg->data != SUNXI_PINCFG_UNPACK_VALUE(config)) {
                pr_warn("failed! pin data value isn't equal as dt.\n");
                return -EINVAL;
            }
        }
    }

    pr_warn("-----------------------------------------------\n");
    pr_warn("test pinctrl request all resource success!\n");
    pr_warn("++++++++++++++++++++++++++++end++++++++++++++++++++++++++++\n\n");
    return 0;
}
注:需要注意,存在SUNXI_PINCTRL和SUNXI_R_PINCTRL两个pinctrl设备,cpus域的pin需要使用
SUNXI_R_PINCTRL

! 警告

linux5.4 中 使 用 pinctrl_gpio_set_config 配 置 gpio 属 性, 对 应 使 用**pinconf_to_config_pack** 生成 config 参数:

SUNXI_PINCFG_TYPE_FUNC 已不再生效,暂未支持 FUNC 配置(建议使用 **pinctrl_select_state**接口代替)

SUNXI_PINCFG_TYPE_PUD 更新为内核标准定义(PIN_CONFIG_BIAS_PULL_UP/PIN_CONFIG_BIAS_PULL_DOWN

SUNXI_PINCFG_TYPE_DRV 更新为内核标准定义(PIN_CONFIG_DRIVE_STRENGTH),相应的 val 对应关系为(4.9->5.4: 0->10, 1->20…

SUNXI_PINCFG_TYPE_DAT 已不再生效,暂未支持 DAT 配置(建议使用 gpio_direction_output**或者 **__gpio_set_value 设置电平值)

5.3 设备驱动使用 GPIO 中断功能

方式一:通过 gpio_to_irq 获取虚拟中断号,然后调用申请中断函数即可目前 sunxi-pinctrl 使用 irq-domain 为 gpio 中断实现虚拟 irq 的功能,使用 gpio 中断功能时,设备驱动只需要通过 gpio_to_irq 获取虚拟中断号后,其他均可以按标准 irq 接口操作。

static int sunxi_gpio_eint_demo(struct platform_device *pdev)
{ 
    struct device *dev = &pdev->dev;
    int virq;
    int ret;
    /* map the virq of gpio */
    virq = gpio_to_irq(GPIOA(0));
    if (IS_ERR_VALUE(virq)) {
        pr_warn("map gpio [%d] to virq failed, errno = %d\n",
                                                GPIOA(0), virq);
        return -EINVAL;
    }
    pr_debug("gpio [%d] map to virq [%d] ok\n", GPIOA(0), virq);
    /* request virq, set virq type to high level trigger */
    ret = devm_request_irq(dev, virq, sunxi_gpio_irq_test_handler,
                                IRQF_TRIGGER_HIGH, "PA0_EINT", NULL);
    if (IS_ERR_VALUE(ret)) {
        pr_warn("request virq %d failed, errno = %d\n", virq, ret);
        return -EINVAL;
    }

    return 0;
}

方式二:通过 dts 配置 gpio 中断,通过 dts 解析函数获取虚拟中断号,最后调用申请中断函数即可,demo 如下所示:

dts配置如下:
soc{
    ...
    Vdevice: vdevice@0 {
        compatible = "allwinner,sun8i-vdevice";
        device_type = "Vdevice";
        interrupt-parent = <&pio>; /*依赖的中断控制器(带interrupt-controller属性的结 点)*/
        interrupts = < PD 3 IRQ_TYPE_LEVEL_HIGH>;
                        | |   `------------------中断触发条件、类型
                        | `-------------------------pin bank内偏移
                        `---------------------------哪个bank
        pinctrl-names = "default";
        pinctrl-0 = <&vdevice_pins_a>;
        test-gpios = <&pio PC 3 1 2 2 1>;
        status = "okay";
    };
    ...
};

在驱动中,通过 platform_get_irq() 标准接口获取虚拟中断号,如下所示:

static int sunxi_pctrltest_probe(struct platform_device *pdev)
{ 
    struct device_node *np = pdev->dev.of_node;
    struct gpio_config config;
    int gpio, irq;
    int ret;

    if (np == NULL) {
        pr_err("Vdevice failed to get of_node\n");
        return -ENODEV;
    }
    ....
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        printk("Get irq error!\n");
        return -EBUSY;
    }
    .....
    sunxi_ptest_data->irq = irq;
    ......
    return ret;
}

//申请中断:
static int pctrltest_request_irq(void)
{
    int ret;
    int virq = sunxi_ptest_data->irq;
    int trigger = IRQF_TRIGGER_HIGH;

    reinit_completion(&sunxi_ptest_data->done);

    pr_warn("step1: request irq(%s level) for irq:%d.\n",
        trigger == IRQF_TRIGGER_HIGH ? "high" : "low", virq);
    ret = request_irq(virq, sunxi_pinctrl_irq_handler_demo1,
            trigger, "PIN_EINT", NULL);
    if (IS_ERR_VALUE(ret)) {
        pr_warn("request irq failed !\n");
        return -EINVAL;
    }

    pr_warn("step2: wait for irq.\n");
    ret = wait_for_completion_timeout(&sunxi_ptest_data->done, HZ);

    if (ret == 0) {
        pr_warn("wait for irq timeout!\n");
        free_irq(virq, NULL);
        return -EINVAL;
    }

    free_irq(virq, NULL);

    pr_warn("-----------------------------------------------\n");
    pr_warn("test pin eint success !\n");
    pr_warn("+++++++++++++++++++++++++++end++++++++++++++++++++++++++++\n\n\n");

    return 0;
}

5.4 设备驱动设置中断 debounce 功能

方式一:通过 dts 配置每个中断 bank 的 debounce,以 pio 设备为例,如下所示:

&pio {
    /* takes the debounce time in usec as argument */
    input-debounce = <0 0 0 0 0 0 0>;
                      | | | | | | `----------PA bank
                      | | | | | `------------PC bank
                      | | | | `--------------PD bank
                      | | | `----------------PF bank
                      | | `------------------PG bank
                      | `--------------------PH bank
                      `----------------------PI bank
};

注意:input-debounce 的属性值中需把 pio 设备支持中断的 bank 都配上,如果缺少,会以bank 的顺序设置相应的属性值到 debounce 寄存器,缺少的 bank 对应的 debounce 应该是默认值(启动时没修改的情况)。sunxi linux-4.9 平台,中断采样频率最大是 24M, 最小 32k,debounce 的属性值只能为 0 或 1。对于 linux-5.4,debounce 取值范围是 0~1000000(单位 usec)。

方式二:驱动模块调用 gpio 相关接口设置中断 debounce

static inline int gpio_set_debounce(unsigned gpio, unsigned debounce);
int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);

在驱动中,调用上面两个接口即可设置 gpio 对应的中断 debounce 寄存器,注意,debounce 是以 ms 为单位的 (linux-5.4 已经移除这个接口)。