一、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-imx CPP_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 dtb DTS_NAME=igkboard-imx6ull all:
@cpp ${CPP_CFLAGS} ${DTS_NAME}.dts -o .${DTS_NAME}.dts.tmp
${DTC} ${DTC_FLAGS} .${DTS_NAME}.dts.tmp -o ${DTS_NAME}.dtb
@rm -f .*.tmp decompile:
${DTC} -q -I dtb -O dts ${DTS_NAME}.dtb -o decompile.dts clean:
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");
#else
priv->class = class_create(THIS_MODULE, "myled");
#endif if(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.o modules:
$(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KERNAL_DIR) M=$(PWD) modules
@make clear clear:
@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 *.unsigned clean:
@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-
endif CC=${CROSS_COMPILE}gcc SRCFILES = $(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]

卸载驱动后,设备文件也会随之消失。

Linux驱动---LED的更多相关文章

  1. Linux 驱动——Led驱动2

    led_drv.c驱动文件: #include <linux/module.h>#include <linux/kernel.h>#include <linux/init ...

  2. Linux 驱动——LED(驱动分离分层)

    led_dev.c文件: #include <linux/module.h>#include <linux/version.h> #include <linux/init ...

  3. Linux 驱动——Led驱动1

    led_drv.c驱动文件: #include <linux/module.h>#include <linux/kernel.h>#include <linux/init ...

  4. arm Linux 驱动LED子系统 测试

    Linux内核在3.0以上引入了设备树概念(具体哪个版本不清楚)在编译内核后需要将与之对应的dtb文件也下载人板子上才能使内核与硬件关联起来. dtb文件是有dts文件编译后生成的:例如 /* * C ...

  5. 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  6. 驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  7. (笔记)linux设备驱动--LED驱动

    linux设备驱动--LED驱动 最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友 ...

  8. 超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”

    版权声明:本文为博主原创文章,未经博主同意不得转载.转载联系 QQ 30952589,加好友请注明来意. https://blog.csdn.net/sleks/article/details/251 ...

  9. 嵌入式Linux学习笔记(三) 字符型设备驱动--LED的驱动开发

    在成功构建了一个能够运行在开发板平台的系统后,下一步就要正式开始应用的开发(这里前提是有一定的C语言基础,对ARM体系的软/硬件,这部分有疑问可能要参考其它教程),根据需求仔细分解任务,可以发现包含的 ...

  10. linux驱动之LED驱动

    通过之前的学习,了解到linux驱动编写的流程是:先通过注册函数注册我们编写的入口函数,然后在入口函数中获取设备号->注册字符设备->自动创建设备节点->获取设备树信息,最后通过销毁 ...

随机推荐

  1. 使用maven 找到依赖的JAR包

    1.业务场景 有些时候,我需要知道某个jar包依赖了哪些包,这个时候可以通过maven 依赖插件将依赖的包copy出来. 2.具体做法 我们可以创建一个空的项目,增加 pom.xml 文件,增加我们需 ...

  2. Android 12 适配之 "Android:exported"

    Android 12 适配之 "Android:exported" 将 build.gradle 中的 targetSDKVersion 和 compileSdkVersion 改 ...

  3. 使用PG的部分索引

    PG 又带来一个惊喜. 现在有一张表,每天增加几十万数据,数据量迅速超过 1亿.此时 create_at 上的索引已经非常庞大,检索速度很慢. 接下来要分表分区了? NO,PG 有一个非常有意思的特性 ...

  4. 【C#】【平时作业】习题-4-流程控制

    T1 创建一个Windows应用程序,先输入年龄值,再判断是否大于18,最后显示判断结果,运行效果如图所示. 提示: 注意保持逻辑完整: 引用数据需要明确出处. [程序代码] private void ...

  5. 【前端】【H5 API】Web存储 Web Storage

    Web存储 传统的方式是使用document.cookie来进行存储,但是由于其存储空间有限(大约4KB),并且需要复杂的操作来解析,给开发者带来了诸多不便. 为此,HTML 5规范提出了网络存储的相 ...

  6. IDEA跳转到上一个下一个方法的快捷键

    假如一个方法很不规范,写了好几百行,你想去下一个方法,如果用鼠标往下滑,会挺崩溃的.或者有的时候,就是需要一个一个方法往下看,那么IDEA有没有这样方便的快捷键呢?是有的:按住Alt键,再按上/下方向 ...

  7. Qt编写视频播放器(支持pbonon/qmediaplayer/ffmpeg/vlc/mpv等多种内核)

    一.前言 花了一年多的时间,终于把这个超级播放器做成了自己想要的架构,用户的需求是一方面,自己架构方面的提升也是一方面,最主要是将界面和解码解耦了,这样才能动态的挂载不同的解码内核到不同的视频监控窗体 ...

  8. Qt开源作品11-屏幕录制控件

    一.前言 在平时的写作过程中,经常需要将一些操作动作和效果图截图成gif格式,使得涵盖的信息更全面更生动,有时候可以将整个操作过程和运行效果录制成MP4,但是文件体积比较大,而且很多网站不便于上传,基 ...

  9. JavaScript之Object.defineProperty()

    1. 对象的定义与赋值 经常使用的定义与赋值方法obj.prop =value或者obj['prop']=value let Person = {}; Person.name = "Jack ...

  10. CDS标准视图:有技术对象的维修工单 I_MAINTORDERTECHOBJCUBE

    视图名称:有技术对象的维修工单 I_MAINTORDERTECHOBJCUBE 视图类型:基础 视图代码: 点击查看代码 @EndUserText.label: 'Maintenance Order ...