- 一、pinctrl子系统
- 二、GPIO子系统
- 三、GPIO操作步骤
- 3.1、获取GPIO描述符
- 3.2、设置方向
- 3.3、读写值
- 四、编写LED驱动
- 4.1、硬件原理图
- 4.2、修改设备树
- 4.3、编写驱动程序
- 4.4、安装驱动测试
- 4.5、应用程序测试
一、pinctrl子系统
现代 SoC(系统级芯片)的引脚功能高度灵活,单个物理引脚可能被多个外设(如 GPIO、I2C、SPI 等)复用,且需要配置电气属性(如上拉/下拉、驱动强度、电压等),画个草图方便大家理解:
Pin_A引脚可以被用于普通的 GPIO 功能,也可以被复用为 I2C 功能,具体用作哪个功能,通过芯片的IOMUXC
控制器来实现配置,配置的过程本质就是操作引脚相关的寄存器。但是一个芯片几百个引脚,都这样直接操作寄存器显然工程量很大,所以很多厂商在中间抽象了 pinctrl 子系统。pinctrl 子系统统一管理引脚复用、配置和状态切换,程序员调用pinctrl子系统提供的接口即可,无需关注底层硬件差异。至于 pinctrl 子系统如何运行、如何设置 pin 复用和电气属性,最终肯定仍要初始化寄存器,这部分已由厂商写好。
我们在上篇文章学过了,硬件信息都应该写在设备树文件中,那么 pinctrl 子系统是如何从设备树文件中获取关于 PIN 的信息呢?其实,pinctrl 子系统根据 iomuxc 节点中的fsl,pins的属性值来获取 PIN 配置信息的。
关于MX6UL_PAD_NAND_DQS__GPIO4_IO16
的定义在arch/arm/boot/dts/imx6ul-pinfunc.h
中:
#define MX6UL_PAD_NAND_DQS__RAWNAND_DQS 0x01b8 0x0444 0x0000 0 0
#define MX6UL_PAD_NAND_DQS__CSI_FIELD 0x01b8 0x0444 0x0530 1 1
#define MX6UL_PAD_NAND_DQS__QSPI_A_SS0_B 0x01b8 0x0444 0x0000 2 0
#define MX6UL_PAD_NAND_DQS__PWM5_OUT 0x01b8 0x0444 0x0000 3 0
#define MX6UL_PAD_NAND_DQS__EIM_WAIT 0x01b8 0x0444 0x0000 4 0
#define MX6UL_PAD_NAND_DQS__GPIO4_IO16 0x01b8 0x0444 0x0000 5 0
#define MX6UL_PAD_NAND_DQS__SDMA_EXT_EVENT01 0x01b8 0x0444 0x0614 6 1
#define MX6UL_PAD_NAND_DQS__SPDIF_EXT_CLK 0x01b8 0x0444 0x061c 8 1
二、GPIO子系统
如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统。gpio 子系统用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO 为输入输出,读取 GPIO 的值等。
我们需要做的工作就是在设备树根节点下添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API 函数来操作 GPIO。那我们如何在根节点下添加 gpio 相关信息呢?具体来说,我们首先要在根节点下创建设备节点,然后添加 pinctrl 信息描述 pin 复用及电气属性,最后添加 GPIO 属性信息描述使用的 gpio 引脚。
接下来,我们的驱动程序就可以通过函数对GPIO进行操作了。
三、GPIO操作步骤
3.1、获取GPIO描述符
1.struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags);
2.struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev, const char *con_id, unsigned int index, enum gpiod_flags flags);
3.struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int index, enum gpiod_flags flags);
//dev:指向设备结构体的指针,表示 GPIO 消费者设备;
//con_id:GPIO 的功能标识符,通常在设备树中定义。如果为 NULL,表示该 GPIO 没有特定的功能名称;
//index:GPIO 的索引,用于从设备中获取特定的 GPIO 引脚。例如,如果一个设备有多个 GPIO 引脚,可以通过索引区分它们
//flags:GPIO 初始化标志,用于指定 GPIO 的初始状态和方向。常见的标志包括:(GPIOD_ASIS:不初始化 GPIO,保持其当前状态;GPIOD_IN:将 GPIO 初始化为输入模式;GPIOD_OUT_LOW:将 GPIO 初始化为输出模式,并设置为低电平;GPIOD_OUT_HIGH:将 GPIO 初始化为输出模式,并设置为高电平)
//成功时返回一个有效的 GPIO 描述符(struct gpio_desc)。失败时返回一个错误码,可以通过 IS_ERR() 检查。
3.2、设置方向
1.int gpiod_direction_output(struct gpio_desc *desc, int value);
2.int gpiod_direction_input(struct gpio_desc *desc);
//desc:指向 GPIO 描述符的指针,表示要操作的 GPIO 引脚;
//value:要设置的初始输出值,可以是 0(低电平)或 1(高电平);
//成功时返回 0。失败时返回负的错误码;
3.3、读写值
1.void gpiod_set_value(struct gpio_desc *desc, int value);
2.int gpiod_get_value(struct gpio_desc *desc);
//desc:指向 GPIO 描述符的指针,表示要操作的 GPIO 引脚;
//value:要设置的电平值;
四、编写LED驱动
4.1、硬件原理图
我这里使用的是一款共阴极三色灯,原理图及引脚连接如图所示,通过原理图我们可以看出,该三色灯为高电平有效。
4.2、修改设备树
创建一个dts文件夹,并将设备树文件拷贝过来(arch/arm/boot/dts/igkboard-imx6ull.dts
),在根节点处添加:
rgbled {compatible = "rgb,leds";pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpio_rgbleds>;status = "okay";gpios = <&gpio1 23 GPIO_ACTIVE_HIGH /* 33# */&gpio5 1 GPIO_ACTIVE_HIGH /* 35# */&gpio5 8 GPIO_ACTIVE_HIGH /* 37# */>;};
在下面的iomxuc
节点中添加:
pinctrl_gpio_rgbleds: rgb-leds {fsl,pins = <MX6UL_PAD_UART2_RTS_B__GPIO1_IO23 0x17059 /* RGB Led red */MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x17059 /* RGB Led green */MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x17059 /* RGB Led blue */>;};
编写一个Makefile
文件用于编译DTS:
ARCH ?= arm
KERNAL_DIR ?= ${HOME}/igkboard-imx6ull/bsp/kernel/linux-imxCPP_CFLAGS=-Wp,-MD,.x.pre.tmp -nostdinc -undef -D__DTS__ -x assembler-with-cpp
CPP_CFLAGS+= -I ${KERNAL_DIR}/arch/${ARCH}/boot/dts -I ${KERNAL_DIR}/include/DTC=${KERNAL_DIR}/scripts/dtc/dtc
DTC_FLAGS=-q -@ -I dts -O dtbDTS_NAME=igkboard-imx6ullall:@cpp ${CPP_CFLAGS} ${DTS_NAME}.dts -o .${DTS_NAME}.dts.tmp${DTC} ${DTC_FLAGS} .${DTS_NAME}.dts.tmp -o ${DTS_NAME}.dtb@rm -f .*.tmpdecompile:${DTC} -q -I dtb -O dts ${DTS_NAME}.dtb -o decompile.dtsclean:rm -f *.dtb decompile.dts
接下来我们可以使用 make 命令编译 igkboard-imx6ull.dts
生成 igkboard-imx6ull.dtb
文件
make
4.3、编写驱动程序
根据上面所学,进行编写程序,代码如下:
点击查看代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/ioctl.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>#define LED_IOC_MAGIC 'l'
#define LED_IOC_SET _IOW(LED_IOC_MAGIC, 1, int)struct led_desc
{struct cdev cdev;struct device *dev;struct gpio_desc *gpio;
};struct led_priv
{struct class *class;dev_t devt;int nleds;struct led_desc *leds;
};struct led_priv *priv;static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{int led_id;int brightness;led_id = iminor(file->f_inode);switch(cmd){case LED_IOC_SET:brightness = (int)arg;gpiod_set_value_cansleep(priv->leds[led_id].gpio, brightness?1:0);break;default:return -ENOTTY;}return 0;
}static const struct file_operations led_fops = {.owner = THIS_MODULE,.unlocked_ioctl = led_ioctl,
};static int led_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;int ret, i;priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);if( !priv ){return -ENOMEM;}priv->nleds = gpiod_count(dev, NULL);if(priv->nleds < 1){dev_err(dev, "Failed to read leds gpio from device tree\n");return -EINVAL;}dev_info(dev, "led driver probe for %d leds from device tree\n", priv->nleds);priv->leds = devm_kzalloc(dev,priv->nleds*sizeof(*priv->leds), GFP_KERNEL);if( !priv->leds ){return -ENOMEM;}for(i=0; i<priv->nleds; i++){priv->leds[i].gpio = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS);if(IS_ERR(priv->leds[i].gpio))return PTR_ERR(priv->leds[i].gpio);gpiod_direction_output(priv->leds[i].gpio, 0);}ret = alloc_chrdev_region(&priv->devt, 0, priv->nleds, "myled");if(ret){dev_err(dev, "Failed to allocate char dev region\n");return ret;}#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)priv->class = class_create("myled");
#elsepriv->class = class_create(THIS_MODULE, "myled");
#endifif(IS_ERR(priv->class)){ret = PTR_ERR(priv->class);goto failed_unregister_chrdev;}for(i=0; i<priv->nleds; i++){cdev_init(&priv->leds[i].cdev, &led_fops);priv->leds[i].cdev.owner = THIS_MODULE;ret = cdev_add(&priv->leds[i].cdev, MKDEV(MAJOR(priv->devt), i), 1);if(ret){dev_err(&pdev->dev, "Failed to add cdev for led%d\n", i);while(--i >= 0){cdev_del(&priv->leds[i].cdev);}goto failed_destroy_class;}priv->leds[i].dev = device_create(priv->class, &pdev->dev, MKDEV(MAJOR(priv->devt), i), NULL, "led%d", i);if (IS_ERR(priv->leds[i].dev)) {ret = PTR_ERR(priv->leds[i].dev);dev_err(&pdev->dev, "Failed to create device node for led%d\n", i);while (--i >= 0) {device_destroy(priv->class, MKDEV(MAJOR(priv->devt), i));cdev_del(&priv->leds[i].cdev);}goto failed_destroy_class;}}platform_set_drvdata(pdev, priv);return 0;failed_destroy_class:class_destroy(priv->class);failed_unregister_chrdev:unregister_chrdev_region(priv->devt, priv->nleds);return ret;
}static int led_remove(struct platform_device *pdev)
{struct led_priv *priv = platform_get_drvdata(pdev);int i;for(i=0; i<priv->nleds; i++){device_destroy(priv->class, MKDEV(MAJOR(priv->devt), i));cdev_del(&priv->leds[i].cdev);}class_destroy(priv->class);unregister_chrdev_region(priv->devt, priv->nleds);dev_info(&pdev->dev, "led driver removed.\n");return 0;
}static const struct of_device_id led_of_match[] = {{.compatible = "rgb,leds",},{},
};MODULE_DEVICE_TABLE(of, led_of_match);static struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver = {.name = "leds",.of_match_table = led_of_match,},
};module_platform_driver(led_driver);MODULE_LICENSE("GPL");
对应的Makefile
文件代码如下:
点击查看代码
ARCH ?= arm
CROSS_COMPILE ?= /opt/gcc-aarch32-10.3-2021.07/bin/arm-none-linux-gnueabihf-
KERNAL_DIR ?= ~/igkboard-imx6ull/bsp/kernel/linux-imx/PWD :=$(shell pwd)obj-m += ledv1.omodules:$(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KERNAL_DIR) M=$(PWD) modules@make clearclear:@rm -f *.o *.cmd *.mod *.mod.c@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f@rm -f .*ko.cmd .*.o.cmd .*.o.d@rm -f *.unsignedclean:@rm -f *.ko
4.4、安装驱动测试
接下来我们可以将新的设备树文件下载到开发板上,并重启生效。
mount /dev/mmcblk1p1 /media/
ls /media/config.txt igkboard-imx6ull.dtb overlays zImage
//我这里通过rz、sz命令将新的设备树文件下载到开发板上替换掉原文件,使用scp命令也可以
sync && reboot
使用insmod
命令安装驱动模块文件,如果安装失败,可以通过dmesg
查看驱动加载的信息,如果报错是XXX already requested by XXX
,那就是RGB的引脚已经被复用其他功能了,关闭该功能就不会再报错了。
insmod ledv1.ko
成功安装后,我们可以在/dev
路径下看到三个设备文件了。
ls -l /dev/led*
crw------- 1 root root 243, 0 Jan 2 03:02 /dev/led0
crw------- 1 root root 243, 1 Jan 2 03:02 /dev/led1
crw------- 1 root root 243, 2 Jan 2 03:02 /dev/led2
4.5、应用程序测试
为了测试我们的驱动程序,编写一个应用程序,代码如下:
点击查看代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <signal.h>#define DEVNAME "/dev/led"
#define MAX_LED 10#define RGBLED_IOC_MAGIC 'l'
#define RGBLED_IOC_SET _IOW(RGBLED_IOC_MAGIC, 1, int)int g_stop = 0;void sig_handler(int signum)
{switch(signum){case SIGINT:case SIGTERM:g_stop = 1;break;default:break;}return;
}int main (int argc, char **argv)
{char devname[32];int fd[MAX_LED];int led_num = 0;int i;/* open all the led device */for(i=0; i<MAX_LED; i++){snprintf(devname, sizeof(devname), "%s%d", DEVNAME, i);/* check device node exist or not */if( access(devname, F_OK)<0 )break; /* can not found any more */fd[i] = open(devname, O_RDWR);if( fd < 0 ){printf("Open device %s failed: %s\n", devname, strerror(errno));return 1;}led_num ++;printf("open %s -> fd[%d]\n", devname, fd[i]);}/* install signal to turn led off when program exit */signal(SIGINT, sig_handler);signal(SIGTERM, sig_handler);/* blink the RGB leds */i = 0;while( !g_stop ){ioctl(fd[i], RGBLED_IOC_SET, 1);usleep(300000);ioctl(fd[i], RGBLED_IOC_SET, 0);usleep(300000);i = (i+1)%led_num;}cleanup:/* turn all the leds off and close the fd */for(i=0; i<led_num; i++){printf("close %s -> fd[%d]\n", devname, fd[i]);ioctl(fd[i], RGBLED_IOC_SET, 0);close(fd[i]);}return 0;
}
在编写一个对应的Makefile
文件,代码如下:
点击查看代码
BUILD_ARCH=$(shell uname -m)
ifeq ($(findstring "x86_64" "i386", $(BUILD_ARCH)),)CROSS_COMPILE?=/opt/gcc-aarch32-10.3-2021.07/bin/arm-none-linux-gnueabihf-
endifCC=${CROSS_COMPILE}gccSRCFILES = $(wildcard *.c)
BINARIES=$(SRCFILES:%.c=%)all: ${BINARIES}%: %.c$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)clean:rm -f ${BINARIES}
编译完成后,运行./app_led
,我们便可以观察到三色灯的变化
open /dev/led0 -> fd[3]
open /dev/led1 -> fd[4]
open /dev/led2 -> fd[5]
^Cclose /dev/led3 -> fd[3]
close /dev/led3 -> fd[4]
close /dev/led3 -> fd[5]
卸载驱动后,设备文件也会随之消失。