基于设备树的platform驱动之LED(平台设备驱动)

这篇具有很好参考价值的文章主要介绍了基于设备树的platform驱动之LED(平台设备驱动)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一个小点

  • platform 设备驱动是在驱动的分离与分层这样的软件思路下诞生的。

简介

  platform 驱动框架分为总线、设备和驱动。
总线:是 Linux 内核提供的,不需要我们这些驱动程序员去管理。我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。
设备、驱动:在没有设备树的 Linux 内核下,我们需要分别编写并注册 platform_device 和platform_driver,分别代表设备和驱动。在使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver 即可。当内核在解析设备树的时候会自动帮我们创建一个 platform_device 对象。

  在编写基于设备树的 platform 驱动的时候我们需要注意一下几点:

1. 在设备树中创建设备节点
  作用:描述设备信息。重点是要设置好compatible 属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动。
  以LED灯为例:

 led {
	compatible = "alientek,led";
	status = "okay";
	default-state = "on";
	led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
};

  第 2行的 compatible 属性值为“ alientek,led”,因此一会在编写 platform 驱动的时候 of_match_table 属性表中要有“ alientek,led”。
2. 编写 platform 驱动的时候要注意兼容属性
   在使用设备树的时候 platform 驱动会通过 of_match_table来保存兼容性值,也就是表明此驱动兼容哪些设备。所以 of_match_table 将会尤为重要。
例如:

static const struct of_device_id leds_of_match[] = {
	{ .compatible = "alientek,led" }, /* 兼容属性 */
	{ /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, leds_of_match);

static struct platform_driver leds_platform_driver = {
	.driver = {
		.name = "zynq-led",
		.of_match_table = leds_of_match,
	},
	.probe = leds_probe,
	.remove = leds_remove,
};

第 1~4 行, of_device_id 表,也就是驱动的兼容表,是一个数组,每个数组元素为of_device_id 类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们仅仅匹配了一个设备。
第 2 行的 compatible 值为“ alientek,led”,驱动中的 compatible 属性和设备中的compatible 属性相匹配,因此驱动中对应的 probe 函数就会执行。
注意第 3 行是一个空元素,在编写 of_device_id 的时候最后一个元素一定要为空!
第 6 行,通过 MODULE_DEVICE_TABLE 声明一下 leds_of_match 这个设备匹配表。 注意这个宏一般用于热拔插设备动态地进行驱动的加载与卸载,例如 USB 类设备。
第 11 行, 将 leds_of_match 匹配表绑定到 platform 驱动结构体 leds_platform_driver中,至此我们就设置好了 platform 驱动的匹配表了。
3. 编写 platform 驱动
  **当驱动和设备匹配成功以后就会执行 probe 函数。**我们需要在 probe 函数里面执行字符设备驱动那一套,当注销驱动模块的时候 remove 函数就会执行。

实验程序编写

1. 修改设备树文件

例如led设备树:

#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW 1

/dts-v1/;
#include "zynq-7000.dtsi"
#include "pl.dtsi"
#include "pcw.dtsi"
/ {
	model = "Alientek ZYNQ Development Board";
	
	chosen {
		bootargs = "console=ttyPS0,115200 earlyprintk root=/dev/mmcblk0p2 rw rootwait";
		stdout-path = "serial0:115200n8";
	};
	aliases {
		ethernet0 = &gem0;
		i2c0 = &i2c_2;
		i2c1 = &i2c0;
		i2c2 = &i2c1;
		serial0 = &uart0;
		serial1 = &uart1;
		spi0 = &qspi;
	};
	memory {
		device_type = "memory";
		reg = <0x0 0x20000000>;
	};

	led {
		compatible = "alientek,led";
		status = "okay";
		default-state = "on";
		led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
	};
};

  第 1和第 2行,在设备树文件中定义了两个宏,这两个宏用来表示 GPIO 是高电平有效还是低电平有效, GPIO_ACTIVE_HIGH 表示高电平有效。
  第 29~34行,加上了一个“ led-gpio”属性,它的值等于“ <&gpio0 7 GPIO_ACTIVE_HIGH>”, led-gpio 属性指定了 LED 灯所使用的 GPIO,在这里就是 GPIO0 的 IO07,低电平有效, 稍后编写驱动程序的时候会获取 led-gpio 属性的内容来得到 GPIO 编号,因为 gpio 子系统的 API 操作函数需要 GPIO 编号。

2. platform 驱动程序编写
  新建名为leddriver.c 的驱动文件,在 leddriver.c 中输入如下所示内容:

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 文件名    : leddriver.c
 作者      : 
 版本      : 
 描述      : platform总线编程示例之platform驱动模块
 其他      : 无
 论坛      : 
 日志      : 
 ***************************************************************/

#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>

#define MYLED_CNT		1			/* 设备号个数 */
#define MYLED_NAME		"myled"		/* 名字 */

/* LED设备结构体 */
struct myled_dev {
	dev_t devid;			/* 设备号 */
	struct cdev cdev;		/* cdev结构体 */
	struct class *class;	/* 类 */
	struct device *device;	/* 设备 */
	int led_gpio;			/* GPIO号 */
};

static struct myled_dev myled;		/* led设备 */

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

/*
 * @description		: 向设备写数据 
 * @param – filp	: 设备文件,表示打开的文件描述符
 * @param - buf		: 要写给设备写入的数据
 * @param - cnt		: 要写入的数据长度
 * @param - offt	: 相对于文件首地址的偏移
 * @return			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t myled_write(struct file *filp, const char __user *buf, 
			size_t cnt, loff_t *offt)
{
	int ret;
	char kern_buf[1];

	ret = copy_from_user(kern_buf, buf, cnt);	// 得到应用层传递过来的数据
	if(0 > ret) {
		printk(KERN_ERR "myled: Failed to copy data from user buffer\r\n");
		return -EFAULT;
	}

	if (0 == kern_buf[0])
		gpio_set_value(myled.led_gpio, 0);		// 如果传递过来的数据是0则关闭led
	else if (1 == kern_buf[0])
		gpio_set_value(myled.led_gpio, 1);		// 如果传递过来的数据是1则点亮led

	return 0;
}

static int myled_init(struct device_node *nd)
{
	const char *str;
	int val;
	int ret;

	/* 从设备树中获取GPIO */
	myled.led_gpio = of_get_named_gpio(nd, "led-gpio", 0);
	if(!gpio_is_valid(myled.led_gpio)) {
		printk(KERN_ERR "myled: Failed to get led-gpio\n");
		return -EINVAL;
	}

	/* 申请使用GPIO */
	ret = gpio_request(myled.led_gpio, "PS_LED0 Gpio");
	if (ret) {
		printk(KERN_ERR "myled: Failed to request led-gpio\n");
		return ret;
	}

	/* 确定LED初始状态 */
	ret = of_property_read_string(nd, "default-state", &str);
	if(!ret) {
		if (!strcmp(str, "on"))
			val = 1;
		else
			val = 0;
	} else
		val = 0;

	/* 将GPIO设置为输出模式并设置GPIO初始电平状态 */
	gpio_direction_output(myled.led_gpio, val);

	return 0;
}

/* LED设备操作函数 */
static struct file_operations myled_fops = {
	.owner = THIS_MODULE,
	.open = myled_open,
	.write = myled_write,
};

/*
 * @description		: platform驱动的probe函数,当驱动与设备
 * 					  匹配成功以后此函数就会执行
 * @param - pdev	: platform设备指针
 * @return			: 0,成功;其他负值,失败
 */
static int myled_probe(struct platform_device *pdev)
{
	int ret;

	printk(KERN_INFO "myled: led driver and device has matched!\r\n");

	/* led初始化 */
	ret = myled_init(pdev->dev.of_node);
	if (ret)
		return ret;

	/* 初始化cdev */
	ret = alloc_chrdev_region(&myled.devid, 0, MYLED_CNT, MYLED_NAME);
	if (ret)
		goto out1;

	myled.cdev.owner = THIS_MODULE;
	cdev_init(&myled.cdev, &myled_fops);

	/* 添加cdev */
	ret = cdev_add(&myled.cdev, myled.devid, MYLED_CNT);
	if (ret)
		goto out2;

	/* 创建类class */
	myled.class = class_create(THIS_MODULE, MYLED_NAME);
	if (IS_ERR(myled.class)) {
		ret = PTR_ERR(myled.class);
		goto out3;
	}

	/* 创建设备 */
	myled.device = device_create(myled.class, &pdev->dev,
				myled.devid, NULL, MYLED_NAME);
	if (IS_ERR(myled.device)) {
		ret = PTR_ERR(myled.device);
		goto out4;
	}

	return 0;

out4:
	class_destroy(myled.class);

out3:
	cdev_del(&myled.cdev);

out2:
	unregister_chrdev_region(myled.devid, MYLED_CNT);

out1:
	gpio_free(myled.led_gpio);

	return ret;
}

/*
 * @description		: platform驱动模块卸载时此函数会执行
 * @param - dev		: platform设备指针
 * @return			: 0,成功;其他负值,失败
 */
static int myled_remove(struct platform_device *dev)
{
	printk(KERN_INFO "myled: led platform driver remove!\r\n");

	/* 注销设备 */
	device_destroy(myled.class, myled.devid);

	/* 注销类 */
	class_destroy(myled.class);

	/* 删除cdev */
	cdev_del(&myled.cdev);

	/* 注销设备号 */
	unregister_chrdev_region(myled.devid, MYLED_CNT);

	/* 删除地址映射 */
	gpio_free(myled.led_gpio);

	return 0;
}

/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
	{ .compatible = "alientek,led" },
	{ /* Sentinel */ }
};

/* platform驱动结构体 */
static struct platform_driver myled_driver = {
	.driver = {
		.name			= "zynq-led",		// 驱动名字,用于和设备匹配
		.of_match_table	= led_of_match,		// 设备树匹配表,用于和设备树中定义的设备匹配
	},
	.probe		= myled_probe,	// probe函数
	.remove		= myled_remove,	// remove函数
};

/*
 * @description		: 模块入口函数
 * @param			: 无
 * @return			: 无
 */
static int __init myled_driver_init(void)
{
	return platform_driver_register(&myled_driver);
}

/*
 * @description		: 模块出口函数
 * @param			: 无
 * @return			: 无
 */
static void __exit myled_driver_exit(void)
{
	platform_driver_unregister(&myled_driver);
}

module_init(myled_driver_init);
module_exit(myled_driver_exit);

MODULE_AUTHOR("");
MODULE_DESCRIPTION("Led Platform Driver");
MODULE_LICENSE("GPL");

  第 72~106 行,自定义函数 myled_init, 该函数的参数是 struct device_node 类型的指针,也就是 led 对应的设备节点,当调用函数的时候传递进来。
  第 121~175 行, platform 驱动的 probe 函数 myled_probe,当设备树中的设备节点与驱动之间匹配成功以后此函数就会执行, 第 128 行调用 myled_init 函数时,将pdev->dev.of_node 作为参数传递到函数中, platform_device 结构体中内置了一个 structdevice 类型的变量 dev,在 struct device 结构体中定义了一个 struct device_node 类型的指针变量 of_node,使用设备树方式进行匹配的情况,当匹配成功之后, of_node 会指向设备树中定义的节点,所以在这里我们不需要通过调用of_find_node_by_path(“/led”)函数得到 led 的节点。我们原来在驱动加载函数里面做的工作现在全部放到 probe 函数里面完成。
  第 182~202 行, platform 驱动的 remobe 函数 myled_remove,当 platform 驱动模块被卸载时此函数就会执行。在此函数里面释放内存、注销字符设备等,也就是将原来驱动卸载函数里面的工作全部都放到 remove 函数中完成。
  第 205~208 行,匹配表,描述了此驱动都和什么样的设备匹配,第 206 行添加了一条值为"alientek,led"的 compatible 属性值,当设备树中某个设备节点的 compatible 属性值也为“ alientek,led”的时候就会与此驱动匹配。
  第 211~218 行, platform_driver 驱动结构体变量 myled_driver, 213 行设置这个platform 驱动的名字为“ zynq-led”,因此,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“ zynq-led”的文件。第 214 行绑定platform 驱动的 of_match_table 表。
  第 225~228 行, platform 驱动模块入口函数,在此函数里面通过platform_driver_register 向 Linux 内核注册一个 platform 驱动 led_driver。
  第 235~238 行, platform 驱动驱动模块出口函数,在此函数里面通过platform_driver_unregister 从 Linux 内核卸载一个 platform 驱动 led_driver。

编写测试 APP

文件名ledApp.c


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

/*
 * @description		: main主程序
 * @param - argc	: argv数组元素个数
 * @param - argv	: 具体参数
 * @return			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, ret;
	unsigned char buf[1];

	if(3 != argc) {
		printf("Usage:\n"
		"\t./ledApp /dev/myled 1		@ close LED\n"
		"\t./ledApp /dev/myled 0		@ open LED\n"
		);
		return -1;
	}

	/* 打开设备 */
	fd = open(argv[1], O_RDWR);
	if(0 > fd) {
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	/* 将字符串转换为int型数据 */
	buf[0] = atoi(argv[2]);

	/* 向驱动写入数据 */
	ret = write(fd, buf, sizeof(buf));
	if(0 > ret){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	/* 关闭设备 */
	close(fd);
	return 0;
}

编译测试文件:

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

MAKEFILE文件

KERN_DIR := /home/zynq/linux/kernel/linux-xlnx-xilinx-v2018.3

obj-m := leddriver.o

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules

clean:
	make -C $(KERN_DIR) M=`pwd` clean

make编译生成一个名为“ leddriver.o”的驱动模块文件。

测试

使用如下指令加载模块

depmod //第一次加载驱动的时候需要运行此命令
modprobe leddriver.ko //加载驱动模块

输入如下命令打开 LED 灯:

./ledApp /dev/myled 1 //打开 LED 灯

在输入如下命令关闭 LED 灯:文章来源地址https://www.toymoban.com/news/detail-426138.html

./ledApp /dev/myled 0 //关闭 LED 灯

到了这里,关于基于设备树的platform驱动之LED(平台设备驱动)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 不写一行代码(一):实现安卓基于GPIO的LED设备驱动

    第1篇 :不写一行代码(一):实现安卓基于GPIO的LED设备驱动 第2篇 :不写一行代码(二):实现安卓基于PWM的LED设备驱动 第3篇:不写一行代码(三):实现安卓基于i2c bus的Slaver设备驱动 安卓设备驱动,本质上依旧还是Linux架构的驱动程序,基于Linux Kernel。在做安卓ROM开发的过程中

    2024年02月05日
    浏览(43)
  • 14_Linux设备树下的platform驱动编写

    目录 设备树下的platform驱动简介 运行测试 platform驱动框架分为总线、设备和驱动 , 其中总线不需要我们这些驱动程序员去管理,这个是Linux内核提供的 , 我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。在没有设备树的Linux内核下 , 我们需要分别编写并注册plat

    2024年02月16日
    浏览(42)
  • I.MX6ULL_Linux_驱动篇(42)设备树与platform设备驱动

    上一章我们详细的讲解了 Linux 下的驱动分离与分层,以及总线、设备和驱动这样的驱动框架。基于总线、设备和驱动这样的驱动框架, Linux 内核提出来 platform 这个虚拟总线,相应的也有 platform 设备和 platform 驱动。上一章我们讲解了传统的、未采用设备树的 platform 设备和驱

    2024年02月14日
    浏览(43)
  • 【嵌入式Linux学习笔记】platform设备驱动和input子系统

    对于Linux这种庞大的操作系统,代码重用性非常重要,所以需要有相关的机制来提升效率,去除重复无意义的代码,尤其是对于驱动程序,所以就有了platform和INPUT子系统这两种工作机制。 学习视频地址:【正点原子】STM32MP157开发板 platform 驱动框架分为总线、设备和驱动。总

    2024年02月07日
    浏览(60)
  • 驱动开发 字符设备驱动分部注册实现LED灯

    head.h 驱动文件 应用文件 现象实现

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

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

    2024年04月28日
    浏览(37)
  • Linux下LED设备驱动开发(LED灯实现闪烁)

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

    2024年02月17日
    浏览(43)
  • 驱动开发 day8 (设备树驱动,按键中断实现led亮灭)

    //编译驱动  (注意Makefile的编译到移植到开发板的内核)         make arch=arm //清除编译生成文件         make clean ****************************************** //安装驱动         insmod mycdev.ko //卸载驱动         rmmod mycdev   需要在内核路径/arch/arm/boot/dts/  修改 stm32mp157a-fsmp1a-dts 文件 *

    2024年02月14日
    浏览(42)
  • QEMU学习(二):LED设备仿真及驱动开发

    在仿真led之前,先来了解一下QEMU源码结构及GPIO仿真原理。 QEMU源码目录 我们只罗列出涉及的少许文件,由此可以看出,我们要仿真的设备文件都放在hw目录下,一般来说一个.c 文件会有一个.h 文件,它们的目录类似。 比如 hw/gpio/imx_gpio.c 对应的头文件为 include/hw/gpio/imx_gpio.

    2024年02月09日
    浏览(55)
  • 【编写LED驱动,创建三个设备文件,每一个设备文件和一个LED灯绑定,当操作这个设备文件时只能控制对应的这盏灯】

    编写LED驱动,创建三个设备文件,每一个设备文件和一个LED灯绑定,当操作这个设备文件时只能控制对应的这盏灯。 1.将GPIO的相关寄存器封装成结构体 -------- head.h 2.LED相关驱动文件 -------- led.c led0 ------ LED1 led1 ------ LED2 led2 ------ LED3 3.应用层测试文件 -------- test.c 实验结果

    2024年02月12日
    浏览(47)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包