代码编织梦想


0.前言

  之前使用了三种方式编写并挂载了LED设备驱动:旧版字符设备驱动、新版字符设备驱动(改进设备号的申请方式)、设备树下的字符设备驱动(从设备树中获取设备属性)。本质上是对GPIO口的高低电平进行操作,但与裸机下的驱动开发没有太大区别,都是对板子的寄存器进行操作。在Linux系统中,对这种简单的GPIO驱动提供了pinctrl和gpio子系统,可以更加简便的进行开发。

一、pinctrl子系统

  Linux讲究驱动的分离与分层,也就是要按照面向对象编程的设计思想来设计设备驱动框架。在之前的驱动编写中,需要先获取到GPIO的寄存器地址(包括直接定义和从reg属性中获取),然后操作这些寄存器来设置GPIO的功能、上下拉、速度等。这种配置方式繁琐,且容易产生冲突,因为有一些GPIO可以复用成其他功能,如SPI引脚、定时器引脚等。
  pinctrl子系统就是为了简化属性配置、减少功能冲突而设计的,开发人员只需要在设备树文件中设置好某个pin的相关属性即可,初始化工作交给pinctrl功能来完成。pinctrl子系统的源码在linux kernel源码目录的driver/pinctrl中,其主要工作内容包括三个方面:
①获取设备树中的pin信息
②根据获取到的pin信息设置pin的复用功能
③根据获取到的pin信息设置pin的电气属性,如上下拉、速度、驱动能力等

1.pinctrl节点相关内容

stm32mp151.dtsi文件中pinctrl节点内容:

pinctrl: pin-controller@50002000 {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "st,stm32mp157-pinctrl";
	ranges = <0 0x50002000 0xa400>;
	interrupt-parent = <&exti>;
	st,syscfg = <&exti 0x60 0xff>;
	hwlocks = <&hsem 0 1>;
	pins-are-numbered;
	......
};

#address-cells 属性值为 1 和#size-cells 属性值为 1,表示 pinctrl 下的所有子节点的 reg 第一位是起始地址,第二位为长度。
ranges 属性表示 STM32MP1 的 GPIO 相关寄存器地址, STM32MP1 系列芯片最多拥有 176 个通用 GPIO,分为 12 组,分别为:标号为ABCDEFGHIJ、KZ,前者每组有0-15一共16个引脚,KZ两组每组0-7一共8个引脚。寄存器地址PA~PK的在一起,起始为0x50002000,结束地址0x5000C3FF,由pinctrl节点描述。PZ的起始地址0x54004000,终止为0x540043FF,由pinctrl_z节点单独描述。所以ranges属性中,0x50002000表示起始地址,0xa400表示寄存器地址范围。
interrupt-parent 属性值为“&exti”,表示父中断为 exti。

stm32mp15-pinctrl.dtsi文件中的pinctrl节点内容:

&pinctrl {
......
	m_can1_pins_a: m-can1-0 {
		pins1 {
			pinmux = <STM32_PINMUX('H', 13, AF9)>; /* CAN1_TX */
			slew-rate = <1>;
			drive-push-pull;
			bias-disable;
		};
		pins2 {
			pinmux = <STM32_PINMUX('I', 9, AF9)>; /* CAN1_RX */
			bias-disable;
		};
	};
......
	pwm1_pins_a: pwm1-0 {
		pins {
			pinmux = <STM32_PINMUX('E', 9, AF1)>, /* TIM1_CH1 */
			<STM32_PINMUX('E', 11, AF1)>, /* TIM1_CH2 */
			<STM32_PINMUX('E', 14, AF1)>; /* TIM1_CH4 */
			bias-pull-down;
			drive-push-pull;
			slew-rate = <0>;
		};
	};
......
};

表示向pinctrl节点中追加内容。每个 pincrtl 节点必须至少包含一个子节点来存放 pincrtl 相关信息,也就是 pinctrl 集,集合里面存放当前外设所用到引脚的相关属性。例如m_can1_pins_a节点分别用pins1和pins2描述了 PH13 和 PI9 两个IO口的相关配置,因为这两个IO的功能和属性不同。而pwm1_pins_a只用了一个pins节点描述了三个属性相同的IO: PE9、PE11、PE14,这三个IO同属于PWM1。关于STM32的PIN信息存放在源码目录下的文档Documentation/devicetree/bindings/pinctrl/st,stm32-pinctrl.yaml 中。

2.pins子节点的相关属性

在 pins 子节点里面存放了外设的引脚描述信息,包括:

①pinmux属性:用来存放外设所要使用的所有 IO

如上述代码中的 pinmux = <STM32_PINMUX('H', 13, AF9)>;STM32_PINMUX 宏来配置引脚和引脚的复用功能,这个宏定义在include/dt-bindings/pinctrl/stm32-pinfunc.h中:

#define PIN_NO(port, line) (((port) - 'A') * 0x10 + (line))
#define STM32_PINMUX(port, line, mode) (((PIN_NO(port, line)) << 8) | (mode))

port:表示用哪一组 GPIO(例:H 表示为 GPIO 第 H 组,也就是 GPIOH)。
line:表示这组 GPIO 的第几个引脚(例:13 表示为 GPIOH_13,也就是 PH13)。
mode:表示当前引脚要做哪种复用功能(例:AF9 表示为用第 9 个复用功能)
关于可用的复用功能可以查阅《STM32MP157A&D 数据手册》,一个 IO 最大有 16种复用方法:AF0~AF15。
例如:
在这里插入图片描述
表格中为PH13的引脚复用功能,如果要将PH13设置为FDCAN1的TX引脚,需要复用成AF9。如果想要复用成UART_4的功能,就使用AF8。
使用AF8时就按照如下配置:

&pinctrl {
......
	myuart4_pins_a: myuart4-0 {
		pins{
			pinmux = <STM32_PINMUX('H', 13, AF8)>;
		};
	};
};

建议一个 PIN 只作为一个外设使用,否则在驱动编写时要对解析出来的属性值做出筛选后调用。不能将一个引脚同时复用成两个功能。

stm32-pinfunc.h:

/* define PIN modes */
#define GPIO 0x0
#define AF0 0x1
#define AF1 0x2
#define AF2 0x3
#define AF3 0x4
#define AF4 0x5
#define AF5 0x6
#define AF6 0x7
#define AF7 0x8
#define AF8 0x9
#define AF9 0xa
#define AF10 0xb
#define AF11 0xc
#define AF12 0xd
#define AF13 0xe
#define AF14 0xf
#define AF15 0x10
#define ANALOG 0x11
#define RSVD 0x12

此文件中定义了一个 PIN 的所有配置项 AF0-AF15,除了 A0~AF15,还有 GPIO、 ANALOG 这两个,如果一个 PIN 只是作为最基本的 GPIO 功能,那么就用“GPIO”;如果要用作模拟功能,如 ADC 采集引脚,那么就设置为“ANALOG”。

②电气属性配置

电气属性在pinctrl子系统中不是必须的,可以不用配置,但是前面的pinmux属性必须设置。stm32-pinctrl.yaml文件中描述了如何配置引脚的电气属性。
在这里插入图片描述
bootlean 类型表示在 pinctrl 子系统只要定义这个电气属性即可,例如:要禁用内部电压,只要在 PIN 的配置集里添加“bias-disable”,这个时候 bias-pull-down和 bias-pull-up 都不能使用,因为已经禁用了内部电压,所以不能配置上下拉。 enum 类型使用方法与 C 语言的一样,比如:要设置 PIN 速度为最低就可以使用“slew-rate=<0>”。
以上述的PH13作为串口TX为例:

&pinctrl {
......
	myuart4_pins_a: myuart4-0 {
		pins1{
			pinmux = <STM32_PINMUX('H', 13, AF8)>;
			bias-pull-up;
			drive-push-pull;
		};
	};
}

添加bias-pull-up; drive-push-pull;表示给 myuart4-0 添加了两个电气属性,分别为内部上拉和推挽输出。

3.在设备树中添加pinctrl节点

步骤:
1.创建对应的节点
2.添加pins属性值
3.在pins节点中添加PIN配置信息
添加完后的节点模板如下:

&pinctrl {
	uart4_pins: uart4-0 {
		pins1{
			pinmux = <STM32_PINMUX('G', 11, AF6)>; /* UART4_TX */
			bias-disable;
			drive-push-pull;
		};
		pins2{
		//RX配置,此处省略
		}
	};
};

注:对于 STM32MP1 而言,如果一个 IO 用作 GPIO 功能的时候不需要创建对应的 pinctrl 节点。但对于其他一些芯片,应该规范写法,创建相应的节点,并设置pinmux = <STM32_PINMUX('I', 0, GPIO)>将其设置为GPIO属性。

二、GPIO子系统

  pinctrl 子系统主要是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO,那么就要用到 gpio 子系统了。
  gpio 子系统用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO。

1.设备树中的gpio信息

以PI0引脚所在的GPIOI为例,在设备树stm32mp151.dtsi中:

pinctrl: pin-controller@50002000 {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "st,stm32mp157-pinctrl";
	......
	gpioi: gpio@5000a000 {
		gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>;
		reg = <0x800 0x400>;
		clocks = <&rcc GPIOI>;
		st,bank-name = "GPIOI";
		status = "disabled";
	};
};

在gpioi子节点中:
gpio-controlle表示 gpioi 节点是个 GPIO 控制器,每个 GPIO 控制器节点必须包含“gpio-controller”属性;
#gpio-cells属性和“#address-cells”类似, #gpio-cells 应该为 2,表示一共有两个 cell,第一个 cell 为 GPIO 编号,比如“&gpioi 0”就表示 PI0。第二个 cell 表示 GPIO 极性,如果为0(GPIO_ACTIVE_HIGH)表示高电平有效,如果为 1(GPIO_ACTIVE_LOW)表示低电平有效;
reg 属性设置了 GPIOI 控制器的寄存器基地址偏移为 0X800,因此 GPIOI 寄存器地址为 0X50002000+0X800=0X5000A000;
clocks 属性指定这个 GPIOI 控制器的时钟;

  当某个具体的引脚作为 GPIO 使用的时候还需要进一步设置,例如:将 PG1 用作 SD 卡的检测(CD)引脚,那么在SD卡的设备节点中,需要指定该引脚设置:

&sdmmc1 {
	pinctrl-names = "default", "opendrain", "sleep";
	pinctrl-0 = <&sdmmc1_b4_pins_a &sdmmc1_dir_pins_a>;
	pinctrl-1 = <&sdmmc1_b4_od_pins_a &sdmmc1_dir_pins_a>;
	pinctrl-2 = <&sdmmc1_b4_sleep_pins_a &sdmmc1_dir_sleep_pins_a>;
	cd-gpios = <&gpiog 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;		/*指定控制引脚*/
	disable-wp;
	......
	status = "okay";
};

属性“cd-gpios”描述了 SD 卡的 CD 引脚使用的哪个 IO,属性值一共有三个,“&gpiog”表示 CD 引脚所使用的 IO 属于 GPIOG 组,“1”表示 GPIOG 组的第 1 号 IO,通过这两个值 SD 卡驱动程序就知道 CD 引脚使用了 PG1 这个 GPIO。最后一个是“GPIO_ACTIVE_LOW | GPIO_PULL_UP”,这个宏包含在include/linux/gpio/machine.h文件中定义的枚举类型gpio_lookup_flags里。“GPIO_ACTIVE_LOW”表示低电平有效,“GPIO_PULL_UP” 表示上拉,所以该语句表示,PG1引脚为低电平时,SD卡有效。
注:这里也能看出,在STM32MP1中引脚被用作GPIO时不需要添加pinctrl节点。

2.gpio子系统API函数

gpio_request函数
功能:用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request进行申请
原型

int gpio_request(unsigned gpio, const char *label)

参数
gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信
息,此函数会返回这个 GPIO 的标号
label:给 gpio 设置个名字
返回值
0:申请成功
其他值:申请失败

gpio_free函数
功能:如果不使用某个 GPIO 了,就需要调用 gpio_free 函数进行释放
原型

void gpio_free(unsigned gpio)

参数
gpio:要释放的 gpio 标号
返回值:无

gpio_direction_input函数
功能:用于设置某个 GPIO 为输入
原型

int gpio_direction_input(unsigned gpio)

参数
gpio:要设置为输入的 GPIO 标号
返回值
0:设置成功
负值:设置失败

gpio_direction_output函数
功能:用于设置某个 GPIO 为输出,并设置输出默认值
原型

int gpio_direction_output(unsigned gpio, int value)

参数
gpio:要设置为输出的 GPIO 标号
value: GPIO 默认输出值
返回值
0:设置成功
负值:设置失败

gpio_get_value函数
功能:用于获取某个 GPIO 的值(0 或 1),此函数是个宏
原型

#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)

参数
gpio:要获取的 GPIO 标号
返回值
非负值:要获取的 GPIO 标号
负值:获取失败

gpio_set_value函数
功能:用于设置某个 GPIO 的值,此函数是个宏
原型

#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)

参数
gpio:要设置的 GPIO 标号
value: 要设置的值
返回值:无

3.设备树中添加gpio节点

步骤:
1.创建led设备节点
2.添加GPIO信息
添加完后的节点模板如下:

led {
	compatible = "atk,led";
	gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;
	status = "okay";
};

三、使用pinctrl和gpio子系统驱动LED

1.与gpio相关的OF函数

of_gpio_named_count函数
功能:用于获取设备树某个属性里面定义了几个 GPIO 信息(空的GPIO信息也会统计)
原型

int of_gpio_named_count(struct device_node *np, const char *propname)

参数
np:设备节点
propname:要统计的 GPIO 属性。
返回值
正值:统计到的 GPIO 数量
负值:失败

of_gpio_count函数
功能:和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量
原型

int of_gpio_count(struct device_node *np)

参数
np:设备节点
返回值
正值:统计到的 GPIO 数量
负值:失败

of_get_named_gpio函数
功能:获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpioi 0(GPIO_ACTIVE_LOW | GPIO_PULL_UP)>的属性信息转换为对应的 GPIO 编号,此函数在驱动中使用很频繁!
原型

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

参数
np:设备节点
propname:包含要获取 GPIO 信息的属性名
index: GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值
正值:获取到的 GPIO 数量
负值:失败

2.修改设备树文件

在stm32mp157d-atk.dts文件夹的根节点/下创建LED灯节点,节点名为gpioled,内容如下:

gpioled {
	compatible = "alientek,led";
	status = "okay";
	led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;
};

由于LED引脚为普通的GPIO脚,所以不需要设置pinctrl节点。修改完后使用make dtbs命令即可编译得到设备树文件,用此文件启动Linux开发板内核。
在这里插入图片描述

3.驱动程序编写

gpioled.c:

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>


#define GPIOLED_CNT   1   /*设备号个数*/
#define GPIOLED_NAME  "gpioled" /*名字*/

#define LEDOFF  0       /*关灯*/
#define LEDON   1       /*开灯*/

/*dtsled设备结构体*/
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;              /*led所使用的GPIO编号*/
};

struct gpioled_dev gpioled;     /*LED设备*/

/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled; /* 设置私有数据 */
    return 0;
}

/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
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, 0);      /*打开LED灯*/
    } else if(ledstat == LEDOFF){
        gpio_set_value(dev->led_gpio, 1);     /*关闭LED灯*/
    }
    
    return 0;
}

/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @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,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init led_init(void)
{
    int retvalue = 0;
    const char *str;

    /*获取设备树中的属性数据*/
    /*1、获取设备节点:gpioled*/
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL){
        printk("gpioled node not find!\r\n");
        return -EINVAL;
    } else {
        printk("gpioled node find!\r\n");
    }

    /*2、获取status属性内容*/
    retvalue = of_property_read_string(gpioled.nd, "status", &str);
    if(retvalue < 0){
        printk("status read failed!\n\r");
        return -EINVAL;
    } 
    if(strcmp(str, "okay")){
        printk("gpioled not okay!\r\n");
        return -EINVAL;
    }

    /*3、获取compatible属性内容并匹配*/
    retvalue = of_property_read_string(gpioled.nd, "compatible", &str);
    if(retvalue < 0){
        printk("gpioled: Failed to get compatible property!\n\r");
        return -EINVAL;
    } 
    if(strcmp(str, "alientek,led")){
        printk("gpioled: Compatible match failed!\r\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!\r\n");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);

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

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

    /*注册字符设备驱动*/
    /*创建设备号*/
    if(gpioled.major){        /*已经定义过设备号*/
        gpioled.devid = MKDEV(gpioled.major, 0);
        retvalue = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);

        if(retvalue < 0){
            pr_err("cannot register %s char driver, [retvalue = %d]\r\n", GPIOLED_NAME, GPIOLED_CNT);
            goto free_gpio;
        }
    } else {        /*没有定义设备号*/
        retvalue = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /*申请设备号*/
        
        if(retvalue < 0){
            pr_err("%s couldn't alloc_chedev_region, ret=%d\r\n", GPIOLED_NAME, retvalue);
            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);

    /*初始化cdev*/
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

    /*添加一个cdev*/
    retvalue = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    if(retvalue < 0){
        goto del_unregister;
    }

    /*创建类*/
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if(IS_ERR(gpioled.class)){
        goto del_cdev;
    }

    /*创建设备*/
    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;
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
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("Amonter");
MODULE_INFO(intree, "Y");

主要修改:
1.添加GPIO相关的头文件<linux/gpio.h>
2.删除了地址映射的相关宏定义、函数声明及实现,包括io_map和unmap的相关设备操作,改用gpio的相关API,如gpio_request和gpio_free
3.在设备结构体gpio_dev中添加int类型的变量led_gpio,用于保存从设备树中读取到的引脚标号
4.整体的驱动只保留了open、read、write、release操作,精简了驱动的内容。另外在init初始化函数中,取消了通过寄存器操作来初始化的步骤,改用OF函数中与GPIO相关的API来读取GPIO引脚标号、申请使用GPIO、设置GPIO电气属性等操作。在write中,也是通过GPIO相关API来设置引脚电平。在exit释放操作中,使用gpio_free函数来进行最后的GPIO释放。

注:在init函数中的 /*3、获取compatible属性内容并匹配*/部分,用于匹配的字符串需要与设备树中定义的属性值相同,包括空格和缩进等字符,否则会匹配失败。

4.编译及运行

驱动编写完成后即可进行编译及运行测试,同前几节。之前编写的测试程序依然可用。
运行结果:
在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_45682654/article/details/128015243

Linux驱动分析——pinctrl子系统-爱代码爱编程

stm32mp157  盘古开发板  Linux内核版本4.19 1.Linux Pinctrl子系统简介 在许多soc内部都包含有pin控制器,通过pin控制器的寄存器,我们可以配置一个或者一组引脚的功能和特性。在软件方面,Linux内核提供了pinctrl子系统,目的是为了统一各soc厂商的pin脚管理。 2.Linux Pinctrl子系统

stm32cubeide外部中断_stm32mp157:GPIO外部中断详解-爱代码爱编程

原标题:stm32mp157:GPIO外部中断详解 写在前面: 本文章为《STM32MP1系列教程之Cortex-M4开发篇》系列中的一篇,全系列总计11篇。笔者使用的开发平台为华清远见FS-MP1A开发板(STM32MP157开发板)。针对该开发平台,后续会陆续发布更多系列教程,包括Cortex-A7开发篇、Cortex-M4开发篇、Linux应

stm32cubeide外部中断_stm32mp157 Cortex M4开发篇:GPIO外部中断详解-爱代码爱编程

写在前面: 本文章为《STM32MP1系列教程之Cortex-M4开发篇》系列中的一篇,全系列总计11篇。笔者使用的开发平台为华清远见FS-MP1A开发板(STM32MP157开发板)。针对该开发平台,后续会陆续更多更多系列教程,包括Cortex-A7开发篇、Cortex-M4开发篇、Linux应用开发篇、Linux系统移植篇、Linux驱动开发篇、

STM32MP157 LCD的 linux驱动程序--基于之Framebuffer框架-爱代码爱编程

STM32MP157 LCD的linux驱动程序–基于之Framebuffer框架 1. LCD硬件分析 LCD直观的描述 LCD就是多个像素点而组成的,如下图所示: 上图中的yres值就是y轴方向有多少个像素点,xres的值就是X轴的像素点的个数,平时所说的分辨率如1920X1080,就代表X轴有1920个像素点,Y轴有1080个像素点,那这个显

Stm32mp157A-DK1评估Linux LED驱动-4 gpio子系统-爱代码爱编程

我们之前的程序都是对需要的gpio寄存器地址进行映射,从而实现对gpio口的配置,在Linux内核中可以调用某些API接口实现对gpio口的设置,这就是gpio子系统。 可以将设备树下的led节点改为: stm32mp1_led {               compatible= "stm32mp1-led";               status

STM32MP157 Pinctl子系统-爱代码爱编程

Pinctrl子系统 1.什么是Pinctrl子系统 Pinctrl: Pin Controller,引脚控制器,用来控制引脚的多路复用和配置参数的硬件模块,对于复用来说,一个引脚可能有多个功能,如GPIO,I2C,SPI等等,我们要想使用哪个功能就得去配置,对于配置参数如上拉,下拉,开漏,推挽等等,这里说是一个硬件模块,但是大多数SOC并没有这个模块

STM32MP157 GPIO子系统-爱代码爱编程

GPIO子系统 1.GPIO子系统作用 GPIO说明 芯片有很多引脚,每个引脚的功能很多,可用作GPIO、I2C、UART、SPI等功能,在Pincrt子系统篇,讲解了Pinctrl子系统,我们可以通过Pinctrl子系统对选择引脚的复用功能(如复用为GPIO或I2C等功能)以及配置引脚(上拉、下拉、驱动能力等)。 当一个引脚被复用为GPIO功能时,

STM32MP157 Linux系统移植开发篇7:Linux内核目录结构详解-爱代码爱编程

本文章为《STM32MP157 Linux系统移植开发篇》系列中的一篇,笔者使用的开发平台为华清远见FS-MP1A开发板(STM32MP157开发板)。stm32mp157是ARM双核,2个A7核,1个M4核,A7核上可以跑Linux操作系统,M4核上可以跑FreeRTOS、RT-Thread等实时操作系统,STM32MP157开发板所以既可以学嵌入式

【正点原子MP157连载】第二十三章 Linux设备树-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7-爱代码爱编程

1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html 4)正点原子官方B站:https://space.bil

【正点原子MP157连载】第二十五章 pinctrl和gpio子系统实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7-爱代码爱编程

1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html 4)正点原子官方B站:https://space.bil

【正点原子MP157连载】第三十六章 Linux自带的LED灯驱动实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7-爱代码爱编程

1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html 4)正点原子官方B站:https://space.bil

STM32MP157开发板嵌入式Linux指南资料-爱代码爱编程

iTOP-STM32MP157开发板资料全面升级了!现手册资料,由两部分组成: 开发板使用手册+开发指南手册 目前两份手册资料编写有1700页左右,后续资料会不断更新,不断完善,帮助用户快速入门,大大提升研发速度。 iTOP-STM32MP157开发板使用手册最新版本为1.2版本,嵌入式Linux开发指南(STM32MP157)最新为1.0版本。 教

stm32mp157 | 使用输入(input)子系统上报按键事件_dkdn的博客-爱代码爱编程

一、概念 输入子系统用于实现Linux输入设备驱动的一种框架。Linux内核将其中固定的部分放入内核中,驱动开发只需要实现其中不固定的部分。 输入子系统对应的设备文件是固定名称/devlinputlevent0…1…2.

stm32mp157驱动开发——设备树下的led驱动_amonter的博客-爱代码爱编程

STM32MP157驱动开发——设备树下的LED驱动 主要内容:将之前章节中使用新设备设备驱动编写的LED驱动改成设备树形式 文章目录 STM32MP157驱动开发——设备树下的LED驱动一、主要步骤二、设备树修

stm32mp157驱动开发——蜂鸣器设备驱动_amonter的博客-爱代码爱编程

STM32MP157驱动开发——蜂鸣器设备驱动 0.相关知识一、驱动程序开发1.设备树修改2.启动程序编写3.测试程序编写 二、编译及运行测试 0.相关知识   蜂鸣器常用于计算机、打印机、报警器、电

【嵌入式linux】嵌入式linux驱动开发基础知识之pinctrl子系统和gpio子系统的使用_driver里的pinctrrl和gpio-爱代码爱编程

文章目录 前言1、Pinctrl子系统1.1、为什么有Pinctrl子系统1.2、重要的概念1.3、代码中怎么引用pinctrl 2、GPIO子系统2.1、为什么有GPIO子系统2.2、在设备树中指定GP

stm32mp157驱动开发——linux设备树_stm32mp157的设备树文件是哪个-爱代码爱编程

文章目录 一、设备树相关知识1.设备树是什么2.设备树的由来3.DTS相关语法4.标准属性 二、创建自定义设备树1.创建小型模板设备树2.设备树在系统中的体现3.特殊节点4.绑定信息文档5.设备树常用OF