【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

这篇具有很好参考价值的文章主要介绍了【Linux】遇事不决,可先点灯,LED驱动的进化之路---2。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

前言:

一、Pinctrl子系统重要概念

1.1 重要概念

1.1.1 pin controller

1.1.2 client device

1.1.3 补充概念

二、GPIO子系统重要概念

2.1 在设备树指定GPIO引脚

2.2 在驱动代码中调用GPIO子系统

三、基于GPIO子系统的LED驱动程序

3.1 修改设备树文件

3.1.1 添加Pinctrl信息

 3.1.2 设备节点信息(放在根节点下)

3.1.3 设置交叉编译工具链并编译dtbs文件

3.2 驱动程序

3.2.1 驱动代码(leddrv.c)

3.2.2 Makefile代码

 3.2.3 测试程序(ledtest.c)

3.3 上机测试


前言:

本文展示LED驱动进化升级化蝶的过程II,基于GPIO/Pinctrl子系统来实现LED驱动,解放硬件上的繁杂操作。遇到搞不明白的,就不妨先点个灯吧。

参考:韦老师课程

https://www.bilibili.com/video/BV14f4y1Q7ti

过一遍驱动框架,有大体认知后还需要进一步的实践感受。

https://blog.csdn.net/weixin_42373086/article/details/130521999

一、Pinctrl子系统重要概念

硬件上的操作方面,现在的芯片动辄有几百个引脚,一个引脚一个引脚去找对应的寄存器,是比较麻烦的,如何解决?

这里用Pinctrl子系统管理,Pinctrl子系统起到的作用主要为引脚复用和引脚配置,Pinctrl子系统的设计方面是由BSP驱动工程师实现。

一方面需要深刻理解Pinctrl子系统机制,另一方面功能实现上调用系统中的函数即可。这里使用Pinctrl子系统的方式---设备树。

1.1 重要概念

这里会涉及到两个对象,分别为pin controller和client device。

  • 前者提供服务,可以用它来复用引脚、配置引脚
  • 后者使用服务,声明自己使用哪些引脚的哪些功能,怎么配置它们。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

1.1.1 pin controller

这里可以认为它对应IOMUX---用来复用引脚,还可以配置引脚(例如上下拉电阻等)。

这里注意pin controller与GPIO controller之间的区别,GPIO controller只是具有把引脚配置为输入、输出等简单功能。即先用pin controller把引脚配置为GPIO,再用GPIO Controller把引脚配置为输入或输出。

1.1.2 client device

简单来讲,就是使用Pinctrl系统的设备。这里会在设备树里定义为一个节点,在节点里声明要用哪些引脚。

1.1.3 补充概念

①pin state:

举个例子,对于UART设备来讲,它会有多个状态,如default和sleep。

  • 上图内容里的pinctrl-0,对应的配置是在pin controller里定义,状态为default
  • 上图内容里的pinctrl-1,对应的配置是在pin controller里定义,状态为sleep

当设备处于default状态时,pinctrl子系统会自动根据上述信息把所有引脚复用为uart0功能。

当设备处于sleep状态时,pinctrl子系统会根据上述信息把引脚配置为高电平。

②groups和function:

一个设备会用到一个或多个引脚,这些引脚可以归为一组(group);这些引脚可以复用为某个功能(function)。

二、GPIO子系统重要概念

以往我们通过寄存器来操作GPIO引脚,现如今可以使用BSP工程师实现的GPIO子系统来设置。

 主要有的操作:

  • 在设备树指定GPIO引脚
  • 使用GPIO子系统里的标准函数获得GPIO、设置GPIO方向、读取/设置GPIO值。

2.1 在设备树指定GPIO引脚

在使用GPIO子系统之前,就要先确定:它是哪组的?组里的哪一个?

在设备树中,“GPIO组”就是一个GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它的名字。

gpio-controller;
#gpio-cells = <2>;

gpio-controller:表示这个节点是一个GPIO Controller,它下面有很多引脚。

#gpio-cells = <2>:表示这个控制器下要用2个32位数来描述。

用第一个cell表示是哪一个引脚,第二个cell来表示有效电平。

注:定义GPIO Controller是芯片厂家的事务,我们在自己的设备节点中使用属性“[<name>-]”gpios,来指定GPIO引脚,示例如下:

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

2.2 在驱动代码中调用GPIO子系统

在设备树中指定了GPIO引脚后,在驱动代码中如何使用?

应用GPIO子系统的函数接口,这里有两套,基于描述符的(descriptor-based)、老的(legacy),常用的函数如下: 

//需要包含的头文件
#include <linux/gpio/consumer.h> // descriptor-based
#include <linux/gpio.h> // legacy

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

注:这些函数会在驱动代码中调用,来实现获取GPIO、设置方向以及释放等操作。

三、基于GPIO子系统的LED驱动程序

这里相较于LED驱动进化之路1的内容,主要是修改设备树,编译设备树后,相应的设备树节点会被内核转换为platform_device。

实现点灯的思路步骤:

  1. 在设备树中添加Pinctrl信息、GPIO信息
  2. 驱动程序的编写,这里主要注册和实现platform_driver(probe函数-file_operations)。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

3.1 修改设备树文件

3.1.1 添加Pinctrl信息

要使用某个引脚,需要使用Pinctrl子系统把引脚配置成GPIO。

对于imx6ull芯片,NXP公司有设备树生成工具,“Pins_Tool_i.MX_Processors_v6_x64.exe”,打开相应的配置文件“MCIMX6Y2xxx08.mex”,可以在GUI界面中选择引脚,配置它的功能,就可以自动生成Pinctrl的子节点信息。(这里LED对应的引脚为GPIO5_3)

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

 完成上述图里的过程,就可以轻松修改内核源码目录中arch/arm/boot/dts/100ask_imx6ull-14x14.dts。

引用上述生成的代码,复制到设备树文件中&iomuxc_snvs部分即可。

        myled_for_gpio: myled_for_gpio {        /*!< Function assigned for the core: Cortex-A7[ca7] */
            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
            >;
        };

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

 3.1.2 设备节点信息(放在根节点下)

 //compatible要跟驱动代码对应上
 myled {
 compatible = "100ask,leddrv";
 pinctrl-names = "default";
 pinctrl-0 = <&myled_for_gpio>;
 led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
 };

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2 这里GPIO5_3是也有被用于系统指示灯的,所以需要对其功能进行禁止。

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

3.1.3 设置交叉编译工具链并编译dtbs文件

这里编译后获得我们想要的dtb文件,并复制到nfs挂载文件夹里。

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
make dtbs
cp  arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

3.2 驱动程序

实际主体步骤如下:

  • 第一步 定义、注册一个platform_driver
  • 第二步 在probe函数里
    • 根据platform_device的设备树信息确定GPIO:gpio_get
    • 定义、注册一个file_operations结构体
    • 在file_operations中使用GPIO子系统的函数操作(gpiod_direction_outputgpiod_set_value

3.2.1 驱动代码(leddrv.c)

相较于LED驱动的进化之路---1中简单框架下的驱动代码,主要是进行probe函数以及相应file_operations函数的修改。

#include <linux/module.h>
#include <linux/platform_device.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>


/* 1. 确定主设备号                                                                 */
static int major = 0;
static struct class *led_class;
static struct gpio_desc *led_gpio;


/* 3. 实现对应的open/read/write等函数,填入file_operations结构�?                  */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	//struct inode *inode = file_inode(file);
	//int minor = iminor(inode);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status(逻辑值)控制LED 高电平有效*/
	gpiod_set_value(led_gpio, status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	//int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	gpiod_direction_output(led_gpio, 0);
	
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 定义自己的file_operations结构�?                                             */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 从platform_device获得GPIO
 *    把file_operations结构体告诉内核:注册驱动程序
 */
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
	//int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 4.1 设备树中定义�? led-gpios=<...>;	*/
    led_gpio = gpiod_get(&pdev->dev, "led", 0);				/*获得引脚,这里不设置引脚的方向*/
	if (IS_ERR(led_gpio)) {
		dev_err(&pdev->dev, "Failed to get GPIO for led\n");
		return PTR_ERR(led_gpio);
	}
    
	/* 4.2 注册file_operations 	*/
	major = register_chrdev(0, "100ask_led", &led_drv);  /* /devices/100ask_led(关注一下文件在哪里) */

	/*生成设备节点 class device create*/
	led_class = class_create(THIS_MODULE, "100ask_led_class");
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		gpiod_put(led_gpio);
		return PTR_ERR(led_class);
	}

	device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */
        
    return 0;
    
}

static int chip_demo_gpio_remove(struct platform_device *pdev)
{
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");
	gpiod_put(led_gpio);                    
    
    return 0;
}


static const struct of_device_id ask100_leds[] = {
    { .compatible = "100ask,leddrv" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "100ask_led",
        .of_match_table = ask100_leds,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init led_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&chip_demo_gpio_driver); 
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函�? *     卸载platform_driver
 */
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&chip_demo_gpio_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

3.2.2 Makefile代码

老生常谈,需要注意KERN_DIR要对应上自己内核的路径。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o ledtest ledtest.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f ledtest

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o



obj-m += leddrv.o

 3.2.3 测试程序(ledtest.c)

用于点灯测试实验。


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./ledtest /dev/100ask_led0 on
 * ./ledtest /dev/100ask_led0 off
 */
int main(int argc, char **argv)
{
	int fd;
	char status;
	
	/* 1. 判断参数 */
	if (argc != 3) 
	{
		printf("Usage: %s <dev> <on | off>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	/* 3. 写文件 */
	if (0 == strcmp(argv[2], "on"))
	{
		status = 1;
		write(fd, &status, 1);
	}
	else
	{
		status = 0;
		write(fd, &status, 1);
	}
	
	close(fd);
	
	return 0;
}

3.3 上机测试

重启加载设备树文件

cp /mnt/100ask_imx6ull-14x14.dtb /boot/
reboot

加载模块,并点灯测试 

insmod leddrv.ko
./ledtest /dev/100ask_led0 on
./ledtest /dev/100ask_led0 off

测试结果:

【Linux】遇事不决,可先点灯,LED驱动的进化之路---2

总结:对于BSP工程师和驱动工程师之间工作的细分区别,会有进一步的理解。在有了GPIO子系统和Pinctrl子系统之后,我们对于硬件上的操作控制的确方便了非常多。文章来源地址https://www.toymoban.com/news/detail-500740.html

到了这里,关于【Linux】遇事不决,可先点灯,LED驱动的进化之路---2的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ARMday04(开发版简介、LED点灯)

    开发板为stm32MP157AAA,附加一个拓展版 PCB PCB( Printed Circuit Board),中文名称为印制电路板,又称印刷线路板,是重要的电子部件,是电子元器件的支撑体,是电子元器件电气相互连接的载体。由于它是采用电子印刷术制作的,故被称为“印刷”电路板。 电路板丝印  可以通过

    2024年02月02日
    浏览(39)
  • DAY3,ARM(LED点灯实验)

    结果:(我的板LD1坏了,所以不亮) 

    2024年02月12日
    浏览(40)
  • Linux下LED设备驱动开发(LED灯实现闪烁)

    前面我们介绍了Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统等,大家看这篇文章之前需要提前知道的基础都在这篇文章中: Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍 有部分函数没有涉及到的最后会讲解

    2024年02月17日
    浏览(43)
  • Linux -- 字符设备驱动--LED的驱动开发(初级框架)

    看原理图确定引脚,确定引脚输出什么电平才能点亮 / 熄灭 LED 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是? 编写驱动:先写框架,再写硬件操作的代码 注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。 需要使

    2024年04月28日
    浏览(37)
  • [Linux_IMX6ULL驱动开发]-LED驱动

    其实在本人的理解看来,在驱动上面操控LED,和使用STM32在操控LED是大同小异的,因为本质都是控制引脚的输出电平,来达到点亮或者熄灭LED的作用,在这里,我们想要操控LED,我们首先要先清除它的原理图是什么样的。 如上图所示可知,想要点亮LED,那么我们需要控制引脚

    2024年04月14日
    浏览(36)
  • Linux 驱动开发基础知识——认识LED驱动程序 (二)

     个人名片: 🦁作者简介:一名喜欢分享和记录学习的在校大学生 🐯个人主页:妄北y 🐧个人QQ:2061314755 🐻个人邮箱:2061314755@qq.com 🦉个人WeChat:Vir2021GKBS 🐼 本文由妄北y原创,首发CSDN 🎊🎊🎊 🐨座右铭:大多数人想要改造这个世界,但却罕有人想改造自己。 专栏导

    2024年01月21日
    浏览(40)
  • FPGA实战-----点灯大师(1)led灯闪烁流水跑马+按键

    FPGA实战 用verliog语言点亮FPGA开发板上的led灯是最最最最最基础的操作。 这里用的EP4CE6F17C8开发板,上边一共有四个led灯珠,可以实现简单的例如4个全亮,流水灯,跑马灯以及相比之下难了一点的呼吸灯等等等等效果。 本文最终目的就是实现 用按键切换led灯的运动模式 。做

    2024年02月04日
    浏览(49)
  • Linux驱动-基于QT控制LED灯

    平台 韦东山100ask imax6ull pro 大象嵌入式开发板 Build Root 使用Build root编译image,具体配置可参考《嵌入式Linux应用开发完全手册-IMX6ULL开发板(从零移植篇-预览版)-V0.1.pdf》,使用buildroot后仅需要配置menuconfig即可自动编译出完整的镜像,而且各种安装包也可以在 output/build 目录下拿

    2024年02月13日
    浏览(97)
  • Zynq(2):MIO,EMIO点灯之路

    由于个人原因,最近一直在对基础知识的复习,所以ZYNQ的后续学习记录,一直没有更新。 FLAG:新年新气象,争取2022年春节之前将所有关于ZYNQ中ARM裸机部分内容更新完毕,主要是ARM外设。 ZYNQ 分为 PS 和 PL 两部分,那么器件的引脚(Pin)资源同样也分成了两部分。ZYNQ PS 中的

    2024年02月13日
    浏览(41)
  • STM32——LED内容补充(寄存器点灯及反转的原理)

    本篇文章使用的是STM32F103xC系列的芯片,四个led灯在PE2,PE3,PE4,PE5上连接 1.开时钟 2.配置IO口 (1)清零指定寄存器位 (2)设置模式为推挽输出模式(led灯低电平有效) 3.设置开关灯 4.宏定义灯的反转 开时钟 1.先看时钟树,找到PE总线在APB2时钟 2.我们先来打开对应芯片的参考手

    2024年02月14日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包