自学内容网 自学内容网

Linux驱动开发——pinctrl 和 和 gpio 子系统实验


系列文章:
Linux驱动开发——字符设备驱动开发
Linux驱动开发——LED驱动开发
Linux驱动开发——新字符设备驱动开发
Linux驱动开发——设备树
Linux驱动开发——基于设备树的LED驱动开发

1 概述

前面的项目中编写了基于设备数的LED驱动,这和裸机开发差不多,都是配置GPIO寄存器的相关参数。但是GPIO这种都是通用的接口,Linux内核中提供了统一的配置方式,可以通过pinctrl和gpio子系统去开发GPIO驱动。

2 pinctrl子系统

2.1 pinctrl子系统简介

Linux驱动讲究驱动分离与分层,pinctrl子系统和gpio子系统就是分层思想的产物。
在之前基于设备树或者裸机开发中,我们首先需要配置引脚的复用功能,然后再配置GPIO的相关参数。
这里就可以将其分为两部分,一部分是引脚的配置,这个就是交给pinctrl子系统来处理的。另一部分就是GPIO相关参数的设置,这个是交给gpio子系统来处理的。
所以pinctrl子系统的主要功能就是配置引脚相关的属性。

2.2 rk3568的pinctrl子系统驱动

2.2.1 PIN配置信息详解

pinctrl: pinctrl {
compatible = "rockchip,rk3568-pinctrl";
rockchip,grf = <&grf>;
rockchip,pmu = <&pmugrf>;
#address-cells = <2>; //起始地址长度为2字节
#size-cells = <2>; //地址长度为2字节
ranges;

gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;

gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};

gpio1: gpio@fe740000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe740000 0x0 0x100>;
interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO1>, <&cru DBCLK_GPIO1>;

gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 32 32>;
interrupt-controller;
#interrupt-cells = <2>;
};

gpio2: gpio@fe750000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe750000 0x0 0x100>;
interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO2>, <&cru DBCLK_GPIO2>;

gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 64 32>;
interrupt-controller;
#interrupt-cells = <2>;
};

gpio3: gpio@fe760000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe760000 0x0 0x100>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO3>, <&cru DBCLK_GPIO3>;

gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 96 32>;
interrupt-controller;
#interrupt-cells = <2>;
};

gpio4: gpio@fe770000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfe770000 0x0 0x100>;
interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_GPIO4>, <&cru DBCLK_GPIO4>;

gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 128 32>;
interrupt-controller;
#interrupt-cells = <2>;
};
};

以上是rk3568.dtsi文件中的pinctrl配置,主要配置了4组gpio端口。但是没有pin引脚的具体配置信息,具体配置信息在rk3568-pinctrl.dtsi文件中:

&pinctrl {
...
can0 {
/omit-if-no-ref/
can0m0_pins: can0m0-pins {
rockchip,pins =
/* can0_rxm0 */
<0 RK_PB4 2 &pcfg_pull_none>,
/* can0_txm0 */
<0 RK_PB3 2 &pcfg_pull_none>;
};

/omit-if-no-ref/
can0m1_pins: can0m1-pins {
rockchip,pins =
/* can0_rxm1 */
<2 RK_PA2 4 &pcfg_pull_none>,
/* can0_txm1 */
<2 RK_PA1 4 &pcfg_pull_none>;
};
};

can1 {
/omit-if-no-ref/
can1m0_pins: can1m0-pins {
rockchip,pins =
/* can1_rxm0 */
<1 RK_PA0 3 &pcfg_pull_none>,
/* can1_txm0 */
<1 RK_PA1 3 &pcfg_pull_none>;
};

/omit-if-no-ref/
can1m1_pins: can1m1-pins {
rockchip,pins =
/* can1_rxm1 */
<4 RK_PC2 3 &pcfg_pull_none>,
/* can1_txm1 */
<4 RK_PC3 3 &pcfg_pull_none>;
};
};

can2 {
/omit-if-no-ref/
can2m0_pins: can2m0-pins {
rockchip,pins =
/* can2_rxm0 */
<4 RK_PB4 3 &pcfg_pull_none>,
/* can2_txm0 */
<4 RK_PB5 3 &pcfg_pull_none>;
};

/omit-if-no-ref/
can2m1_pins: can2m1-pins {
rockchip,pins =
/* can2_rxm1 */
<2 RK_PB1 4 &pcfg_pull_none>,
/* can2_txm1 */
<2 RK_PB2 4 &pcfg_pull_none>;
};
};
...
}

以上配置了三个can接口的信息,每个pinctrl节点必须至少包含一个子节点来存放pinctrl相关信息,也就是pinctrl集,这个集合里面存放当前外设用到哪些引脚、复用配置、上下位、驱动能力等。一般存放这个pinctrl集的子节点名字是“rockchip,pins”
根据rockchip,pinctrl.txt文档里面的介绍,引脚复用设置的格式如下:

rockchip,pins = <PIN_BANK PIN_BANK_IDX MUX &phandle>

一共分为四部分:

  1. PIN_BANK
    PIN_BANK就是PIN所属的组,RK3568一共有5组PIN:GPIO0~GPIO4,分别对应0-4。
  2. PIN_BANK_IDX
    PIN_BANK_IDX是组内编号,以GPIO0为例,一共有A0-A7、B0-B7、C0-C7、D0-D7,这32个PIN,瑞芯微已经给这些PIN编了号,在include/dt-bindings/pinctrl/rockchip.h中:
#define RK_GPIO00
#define RK_GPIO11
#define RK_GPIO22
#define RK_GPIO33
#define RK_GPIO44
#define RK_GPIO66

#define RK_PA00
#define RK_PA11
#define RK_PA22
#define RK_PA33
#define RK_PA44
#define RK_PA55
#define RK_PA66
#define RK_PA77
#define RK_PB08
#define RK_PB19
#define RK_PB210
#define RK_PB311
#define RK_PB412
#define RK_PB513
#define RK_PB614
#define RK_PB715
#define RK_PC016
#define RK_PC117
#define RK_PC218
#define RK_PC319
#define RK_PC420
#define RK_PC521
#define RK_PC622
#define RK_PC723
#define RK_PD024
#define RK_PD125
#define RK_PD226
#define RK_PD327
#define RK_PD428
#define RK_PD529
#define RK_PD630
#define RK_PD731

如果要设置GPIO0_C0,那么PIN_BANK_IDX需要设置为RK_PC0
3. MUX
MUX就是设置PIN的复用功能,一个PIN最多有16种复用功能,在include/dt-bindings/pinctrl/rockchip.h文件中有描述

#define RK_FUNC_GPIO0
#define RK_FUNC_00
#define RK_FUNC_11
#define RK_FUNC_22
#define RK_FUNC_33
#define RK_FUNC_44
#define RK_FUNC_55
#define RK_FUNC_66
#define RK_FUNC_77
#define RK_FUNC_88
#define RK_FUNC_99
#define RK_FUNC_1010
#define RK_FUNC_1111
#define RK_FUNC_1212
#define RK_FUNC_1313
#define RK_FUNC_1414
#define RK_FUNC_1515
  1. phandle
    phandle用来描述一些引脚的通用配置信息
&pinctrl {

/omit-if-no-ref/
pcfg_pull_up: pcfg-pull-up {
bias-pull-up;
};

/omit-if-no-ref/
pcfg_pull_down: pcfg-pull-down {
bias-pull-down;
};

/omit-if-no-ref/
pcfg_pull_none: pcfg-pull-none {
bias-disable;
};

/omit-if-no-ref/
pcfg_pull_none_drv_level_0: pcfg-pull-none-drv-level-0 {
bias-disable;
drive-strength = <0>;
};
...
}

上面就是上下拉的配置
最终,如果要将GPIO0_C0设置为GPIO功能,那么配置为:

&pinctrl {

/omit-if-no-ref/
pcfg_pull_up: pcfg-pull-up {
bias-pull-up;
};

/omit-if-no-ref/
pcfg_pull_down: pcfg-pull-down {
bias-pull-down;
};

/omit-if-no-ref/
pcfg_pull_none: pcfg-pull-none {
bias-disable;
};

/omit-if-no-ref/
pcfg_pull_none_drv_level_0: pcfg-pull-none-drv-level-0 {
bias-disable;
drive-strength = <0>;
};
...
}

上面就是设置上下拉
最终,如果要将GPIO0_C0设置为GPIO,那么配置为:

rockchip,pins =<0 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>;

2.2.2 设备树中添加pinctrl节点模板

&pinctrl {
uart2 {
/omit-if-no-ref/
uart2m0_xfer: uart2m0-xfer {
rockchip,pins =
/* uart2_tx_m1 */
<0 RK_PD1 1 &pcfg_pull_up>;
};
};
};

在pinctrl节点下添加了一个uart2节点,这里只添加了UART2的TX引脚。
按道理来讲,当我们将一个 PIN 用作 GPIO 功能的时候也需要创建对应的 pinctrl 节点,并且将所用的 PIN 复用为 GPIO 功能,但是!对于 RK3568 而言,如果一个 PIN 用作 GPIO 功能的时候不需要创建对应的 pinctrl 节点!

3 gpio子系统

上一节是pinctrl子系统,用于配置pin引脚,pin引脚配置好之后,如果引脚复用为GPIO,则需要使用gpio子系统来初始化GPIO的一些常用配置。驱动开发者在设备树中添加gpio相关信息,然后可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,极大的方便了驱动开发者使用GPIO。

3.1 rk3568的gpio子系统驱动

gpio0: gpio@fdd60000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xfdd60000 0x0 0x100>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;

gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupt-controller;
#interrupt-cells = <2>;
};

以上是GPIO0引脚的配置信息,属于pinctrl子节点,绑定文档中的Documentation/devicetree/bindings/gpio/gpio.txt 详细描述了gpio控制器节点各个属性信息。compatible配置为rockchip,gpio-bank,可以通过这个找到GPIO驱动源文件,哪个驱动使用了该设备树配置。
reg属性配置了GPIO0控制器的寄存器基地址。
interrupts描述了GPIO0控制器的中断信息。
clocks属性指定GPIO0控制器的时钟。
gpio-controller表示gpio0节点是个GPIO控制器,每个GPIO控制器节点必须包含该属性。
#gpio-cells属性表示有两个cell,第一个为GPIO编号,第二个表示GPIO极性,如果为0表示高电平有效,为1表示低电平有效。
以上是GPIO0控制器节点,具体的某个引脚作为GPIO使用的时候还需要进一步配置。
以CSI1摄像头为例,该摄像头的I2C连接在I2C4上,其使用GPIO3_B6作为RESET引脚

imx415: imx415@1a {
status = "okay";
compatible = "sony,imx415";
reg = <0x1a>;
clocks = <&cru CLK_CIF_OUT>;
clock-names = "xvclk";
power-domains = <&power RK3568_PD_VI>;
pinctrl-names = "rockchip,camera_default";
pinctrl-0 = <&cif_clk>;
reset-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_LOW>;
power-gpios = <&gpio4 RK_PB4 GPIO_ACTIVE_HIGH>;
rockchip,camera-module-index = <0>;
rockchip,camera-module-facing = "back";
rockchip,camera-module-name = "CMK-OT1522-FG3";
rockchip,camera-module-lens-name = "CS-P1150-IRC-8M-FAU";
port {
imx415_out: endpoint {
remote-endpoint = <&mipi_in_ucam1>;
data-lanes = <1 2 3 4>;
};
};
};

在i2c4节点下面有以上子节点,reset-gpios描述使用GPIO3_B6引脚作为reset引脚,低电平有效。GPIO_ACTIVE_LOW这种有效位定义在include/linux/gpio/machine.h中:

enum gpio_lookup_flags {
GPIO_ACTIVE_HIGH = (0 << 0),
GPIO_ACTIVE_LOW = (1 << 0),
GPIO_OPEN_DRAIN = (1 << 1),
GPIO_OPEN_SOURCE = (1 << 2),
GPIO_PERSISTENT = (0 << 3),
GPIO_TRANSITORY = (1 << 3),
};

3.2 gpio子系统API函数

  1. gpio_request函数
int gpio_request(unsigned gpio, const char *label)

用于申请一个GPIO管脚,在使用GPIO之前都需要先申请一个GPIO管脚。gpio表示gpio标号,使用of_get_named_gpio从设备树获取指定GPIO属性信息,会返回该标号。label是给gpio设置一个名字。
返回值:0,申请成功;1,申请失败
2. gpio_free函数

void gpio_free(unsigned gpio)

如果某个申请的gpio不再使用了,使用该函数进行释放
3. gpio_direction_input函数

int gpio_direction_input(unsigned gpio)

该函数用于将某个gpio设置为输入
返回值:0,成功;负数,设置失败
4. gpio_direction_output 函数

int gpio_direction_output(unsigned gpio)

该函数用于将某个gpio设置为输出
返回值:0,成功;负数,设置失败
5. gpio_get_value 函数

int gpio_get_value(unsigned int gpio)

用于获取某个GPIO的值
返回值:非负值,GPIO当前值;负数,获取失败
6. gpio_set_value函数

void gpio_set_value(unsigned int gpio, int value)

用于设置某个GPIO的值

3.3 设备树中添加gpio节点模板

gpioled {
compatible = "alientek,led";
led-gpio = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
status = "okay";
};

在根目录下添加led设备子节点,led-gpio配置了led使用的是哪一个gpio。

3.4 与gpio相关的OF函数

  1. of_gpio_named_count函数
int of_gpio_named_count(struct device_node *np, const char *propname)

该函数用于获取设备树某个属性定义了几个GPIO信息,如果定义是空的GPIO信息也会被统计。np表示设备节点,propname表示要统计的GPIO属性。
返回值:正值,统计到的GPIO数量;负值,失败。

gpios = <0 
&gpio1 1 2
0
&gpio2 3 4>;

以上GPIO,其中有两个是空的,但是该函数还是会返回4。
2. of_gpio_count 函数

int of_gpio_count(struct device_node *np)

此函数统计的是“gpios”属性的GPIO数量,而of_gpio_named_count可以统计任意属性的GPIO信息。
3. of_get_named_gpio 函数

int of_get_named_gpio(struct device_node *np,
const char *propname,
int index)

用于获取GPIO编号,GPIO操作的函数需要使用GPIO编号,此函数会将设备树中类似<&gpio4 RK_PA1 GPIO_ACTIVE_LOW>的属性信息转换成对应的GPIO编号,此函数使用较为频繁。
propname表示要获取GPIO信息的属性名。index表示GPIO索引,一个属性中可能包含多个GPIO,此参数指定获取哪个GPIO编号,如果只有一个GPIO信息的话,此参数可以为0。

4 实验程序编写

4.1 修改设备树

gpioled {
compatible = "alientek,led";
led-gpio = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
status = "okay";
};

添加设备模块

4.2 驱动程序编写

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
#define LEDON 1
#define LEDOFF 0

static void __iomem *PMU_GRF_GPIO0C_IOMUX_L_PI;
static void __iomem *PMU_GRF_GPIO0C_DS_0_PI;
static void __iomem *GPIO0_SWPORT_DR_H_PI;
static void __iomem *GPIO0_SWPORT_DDR_H_PI;

struct gpioled_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int led_gpio;
};

struct gpioled_dev gpioled;

void led_switch(u8 sta) {
    u32 val = 0;
    if (sta == LEDON) {
        val = readl(GPIO0_SWPORT_DR_H_PI);
        val &= ~(0x1 << 0);
        val |= ((0x1 << 16) | (0x1 << 0));
        writel(val, GPIO0_SWPORT_DR_H_PI);
    } else if (sta == LEDOFF) {
        val = readl(GPIO0_SWPORT_DR_H_PI);
        val &= ~(0x1 << 0);
        val |= ((0x1 << 16) | (0x0 << 0));
        writel(val, GPIO0_SWPORT_DR_H_PI);
    }
}

void led_unmap(void) {
    iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI);
    iounmap(PMU_GRF_GPIO0C_DS_0_PI);
    iounmap(GPIO0_SWPORT_DR_H_PI);
    iounmap(GPIO0_SWPORT_DDR_H_PI);
}

static int led_open(struct inode *inode, struct file *filp) {
    filp->private_data = &gpioled;
    return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
    return 0;
}

static ssize_t led_write(struct file *filp, const 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, 1);
    } else if (ledStat == LEDOFF) {
        gpio_set_value(dev->led_gpio, 0);
    }
    return 0;
}

static int led_release(struct inode *inode, struct file *filp) {
    return 0;
}

static struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

static int __init led_init(void)
{
    int ret = 0;
    const char *str;

    /* 设置LED所使用的GPIO */
    /* 1、获取设备节点:gpioled */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL) {
        printk("gpioled node not find!\r\n");
        return -EINVAL;
    }

    /* 2.读取status属性 */
    ret = of_property_read_string(gpioled.nd, "status", &str);
    if(ret < 0) 
        return -EINVAL;

    if (strcmp(str, "okay"))
        return -EINVAL;
    
    /* 3、获取compatible属性值并进行匹配 */
    ret = of_property_read_string(gpioled.nd, "compatible", &str);
    if(ret < 0) {
        printk("gpioled: Failed to get compatible property\n");
        return -EINVAL;
    }

    if (strcmp(str, "alientek,led")) {
        printk("gpioled: Compatible match failed\n");
        return -EINVAL;
    }

    /* 4、 获取设备树中的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);

    /* 5.向gpio子系统申请使用GPIO */
    ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
    if (ret) {
        printk(KERN_ERR "gpioled: Failed to request led-gpio\n");
        return ret;
    }

    /* 6、设置GPIO为输出,并且输出低电平,默认关闭LED灯 */
    ret = gpio_direction_output(gpioled.led_gpio, 0);
    if(ret < 0) {
        printk("can't set gpio!\r\n");
    }

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (gpioled.major) {        /*  定义了设备号 */
        gpioled.devid = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
        if(ret < 0) {
            pr_err("cannot register %s char driver [ret=%d]\n", GPIOLED_NAME, GPIOLED_CNT);
            goto free_gpio;
        }
    } else {                        /* 没有定义设备号 */
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);    /* 申请设备号 */
        if(ret < 0) {
            pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", GPIOLED_NAME, ret);
            goto free_gpio;
        }
        gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);   
    
    /* 2、初始化cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    
    /* 3、添加一个cdev */
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    if(ret < 0)
        goto del_unregister;
        
    /* 4、创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class)) {
        goto del_cdev;
    }

    /* 5、创建设备 */
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device)) {
        goto destroy_class;
    }
    return 0;
    
destroy_class:
    class_destroy(gpioled.class);
del_cdev:
    cdev_del(&gpioled.cdev);
del_unregister:
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
free_gpio:
    gpio_free(gpioled.led_gpio);
    return -EIO;
}

static void __exit led_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&gpioled.cdev);/*  删除cdev */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
    device_destroy(gpioled.class, gpioled.devid);/* 注销设备 */
    class_destroy(gpioled.class);/* 注销类 */
    gpio_free(gpioled.led_gpio); /* 释放GPIO */
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

4.3 测试驱动程序

使用Linux驱动开发——新字符设备驱动开发中的测试程序进行测试
首先insmod ko文件,会发现报错如下:
在这里插入图片描述
是因为GPIO已经被分配给系统原生的心跳灯驱动。
这里需要禁用
在这里插入图片描述
禁用掉这个设备节点。整编kernel和刷机包,刷机完成之后,可以看到心跳灯已经被禁用了。
然后测试

./ledApp /dev/gpioled 1
./ledApp /dev/gpioled 0

能够看到led亮灭


原文地址:https://blog.csdn.net/weixin_49274713/article/details/143068173

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!