当前位置: 首页 > news >正文

imx6ull-驱动开发篇35——设备树下的 platform 驱动实验

目录

设备树下的 platform 驱动

在设备树中创建设备节点

platform 驱动兼容属性

编写 platform 驱动

实验程序编写

修改设备树文件

添加 pinctrl 节点

添加 LED 设备节点

检查 PIN 是否被其他外设使用

驱动程序

leddriver.c

ledApp.c

Makefile 文件

运行测试


本讲内容,我们来学习一下如何在设备树下编写 platform 驱动。

设备树下的 platform 驱动

platform 驱动框架分为总线、设备和驱动,在没有设备树的 Linux 内核下,我们需要分别编写并注册 platform_deviceplatform_driver,分别代表设备和驱动。

在使用设备树的时候,设备的描述被放到了设备树中,因此我们只需要实现 platform_driver

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

在设备树中创建设备节点

platform 总线需要通过设备节点的 compatible 属性值来匹配驱动,所以在设备树中创建设备节点来描述设备信息,需要设置好 compatible属性的值。

示例代码如下:

gpioled {#address-cells = <1>;#size-cells = <1>;compatible = "atkalpha-gpioled";pinctrl-names = "default";pinctrl-0 = <&pinctrl_led>;led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;status = "okay";
};

其中,compatible 属性值为“atkalpha-gpioled”,要和 platform驱动的 of_match_table 属性表匹配。

platform 驱动兼容属性

在使用设备树的时候 ,platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。

示例代码如下:

static const struct of_device_id leds_of_match[] = {{ .compatible = "atkalpha-gpioled" }, /* 兼容属性 */{ /* Sentinel */ }
};MODULE_DEVICE_TABLE(of, leds_of_match);static struct platform_driver leds_platform_driver = {.driver = {.name = "imx6ul-led",.of_match_table = leds_of_match,},.probe = leds_probe,.remove = leds_remove,
};

of_device_id 表,驱动的兼容表,是一个数组,每个数组元素为 of_device_id类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。

compatible 值为“atkalpha-gpioled”,驱动中的 compatible 属性和设备中的 compatible 属性相匹配,因此驱动中对应的 probe 函数就会执行。

.compatible = "atkalpha-gpioled"

通过 MODULE_DEVICE_TABLE ,声明一下 leds_of_match 这个设备匹配表。

MODULE_DEVICE_TABLE(of, leds_of_match);

设置 platform_driver 中的 of_match_table 匹配表,为上面创建的 leds_of_match

    .driver = {.name = "imx6ul-led",.of_match_table = leds_of_match,},

编写 platform 驱动

基于设备树的 platform 驱动,和无设备树的 platform 驱动基本一样,都是当驱动和设备匹配成功以后就会执行 probe 函数。

实验程序编写

修改设备树文件

正点原子I.MX6U-ALPHA 开发板上的 LED 灯使用了 GPIO1_IO03 这个 PIN。

添加 pinctrl 节点

打开 imx6ull-alientek-emmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_led”的子节点。

pinctrl_led”节点内容如下所示:

pinctrl_led: ledgrp {fsl,pins = <MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */>;
};

添加 LED 设备节点

在根节点“/”下创建 LED 灯节点,节点名为“gpioled”,

gpioled”节点内容如下:

gpioled {#address-cells = <1>;                // 子节点地址用1个u32表示#size-cells = <1>;                   // 子节点大小用1个u32表示compatible = "atkalpha-gpioled";     // 驱动匹配名称pinctrl-names = "default";           // 引脚控制状态名称pinctrl-0 = <&pinctrl_led>;          // 默认状态使用的引脚配置组led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; // GPIO控制引脚定义status = "okay";                     // 设备状态(启用)
};

检查 PIN 是否被其他外设使用

检查 PIN 有没有被其他外设使用包括两个方面:

  • 检查 pinctrl 设置。
  • 如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 是否被其它外设使用。

驱动程序

leddriver.c

leddriver.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 <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define LEDDEV_CNT		1				/* 设备号长度 	*/
#define LEDDEV_NAME		"dtsplatled"	/* 设备名字 	*/
#define LEDOFF 			0
#define LEDON 			1/* leddev设备结构体 */
struct leddev_dev{dev_t devid;				/* 设备号	*/struct cdev cdev;			/* cdev		*/struct class *class;		/* 类 		*/struct device *device;		/* 设备		*/int major;					/* 主设备号	*/	struct device_node *node;	/* LED设备节点 */int led0;					/* LED灯GPIO标号 */
};struct leddev_dev leddev; 		/* led设备 *//** @description		: LED打开/关闭* @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return 			: 无*/
void led0_switch(u8 sta)
{if (sta == LEDON )gpio_set_value(leddev.led0, 0);else if (sta == LEDOFF)gpio_set_value(leddev.led0, 1);	
}/** @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 = &leddev; /* 设置私有数据  */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[2];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];if (ledstat == LEDON) {led0_switch(LEDON);} else if (ledstat == LEDOFF) {led0_switch(LEDOFF);}return 0;
}/* 设备操作函数 */
static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.write = led_write,
};/** @description		: flatform驱动的probe函数,当驱动与* 					  设备匹配以后此函数就会执行* @param - dev 	: platform设备* @return 			: 0,成功;其他负值,失败*/
static int led_probe(struct platform_device *dev)
{	printk("led driver and device was matched!\r\n");/* 1、设置设备号 */if (leddev.major) {leddev.devid = MKDEV(leddev.major, 0);register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);} else {alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);leddev.major = MAJOR(leddev.devid);}/* 2、注册设备      */cdev_init(&leddev.cdev, &led_fops);cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);/* 3、创建类      */leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);if (IS_ERR(leddev.class)) {return PTR_ERR(leddev.class);}/* 4、创建设备 */leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);if (IS_ERR(leddev.device)) {return PTR_ERR(leddev.device);}/* 5、初始化IO */	leddev.node = of_find_node_by_path("/gpioled");if (leddev.node == NULL){printk("gpioled node nost find!\r\n");return -EINVAL;} leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0);if (leddev.led0 < 0) {printk("can't get led-gpio\r\n");return -EINVAL;}gpio_request(leddev.led0, "led0");gpio_direction_output(leddev.led0, 1); /* led0 IO设置为输出,默认高电平	*/return 0;
}/** @description		: platform驱动的remove函数,移除platform驱动的时候此函数会执行* @param - dev 	: platform设备* @return 			: 0,成功;其他负值,失败*/
static int led_remove(struct platform_device *dev)
{gpio_set_value(leddev.led0, 1); 	/* 卸载驱动的时候关闭LED */gpio_free(leddev.led0);				/* 释放IO 			*/cdev_del(&leddev.cdev);				/*  删除cdev */unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */device_destroy(leddev.class, leddev.devid);class_destroy(leddev.class);return 0;
}/* 匹配列表 */
static const struct of_device_id led_of_match[] = {{ .compatible = "atkalpha-gpioled" },{ /* Sentinel */ }
};/* platform驱动结构体 */
static struct platform_driver led_driver = {.driver		= {.name	= "imx6ul-led",			/* 驱动名字,用于和设备匹配 */.of_match_table	= led_of_match, /* 设备树匹配表 		 */},.probe		= led_probe,.remove		= led_remove,
};/** @description	: 驱动模块加载函数* @param 		: 无* @return 		: 无*/
static int __init leddriver_init(void)
{return platform_driver_register(&led_driver);
}/** @description	: 驱动模块卸载函数* @param 		: 无* @return 		: 无*/
static void __exit leddriver_exit(void)
{platform_driver_unregister(&led_driver);
}module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");

关键代码分析如下:

platform 驱动的 probe 函数,当设备树中的设备节点与驱动之间匹配成功以后此函数就会执行。

static int led_probe(struct platform_device *dev)

led_remove 函数,当卸载 platform 驱动的时候此函数就会执行。在此函数里面释放内存、注销字符设备等.

static int led_remove(struct platform_device *dev)
{gpio_set_value(leddev.led0, 1); 	/* 卸载驱动的时候关闭LED */gpio_free(leddev.led0);				/* 释放IO 			*/cdev_del(&leddev.cdev);				/*  删除cdev */unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */device_destroy(leddev.class, leddev.devid);class_destroy(leddev.class);return 0;
}

led_of_match[] 匹配表,描述了此驱动都和什么样的设备匹配,添加了一条值为"atkalpha-gpioled"的 compatible 属性值,当设备树中某个设备节点的 compatible 属性值也为“atkalpha-gpioled”的时候,就会与此驱动匹配。

static const struct of_device_id led_of_match[] = {{ .compatible = "atkalpha-gpioled" },{ /* Sentinel */ }
};

platform_driver 驱动结构体, 设置这个 platform 驱动的名字为“imx6ulled”,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“imx6uled”的文件。 

static struct platform_driver led_driver = {.driver		= {.name	= "imx6ul-led",			/* 驱动名字,用于和设备匹配 */.of_match_table	= led_of_match, /* 设备树匹配表 		 */},.probe		= led_probe,.remove		= led_remove,
};

leddriver_init函数,驱动模块加载函数,通过 platform_driver_register 向 Linux 内核注册 led_driver 驱动。

leddriver_exit函数,驱动模块卸载函数,通过 platform_driver_unregister 从 Linux内核卸载 led_driver 驱动。

static int __init leddriver_init(void)
{return platform_driver_register(&led_driver);
}static void __exit leddriver_exit(void)
{platform_driver_unregister(&led_driver);
}

ledApp.c

测试文件ledApp.c,打开和关闭 LED 灯。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#define LEDOFF 	0
#define LEDON 	1/** @description		: main主程序* @param - argc 	: argv数组元素个数* @param - argv 	: 具体参数* @return 			: 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char databuf[2];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开led驱动 */fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 */retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}retvalue = close(fd); /* 关闭文件 */if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}

Makefile 文件

makefile文件只需要修改 obj-m 变量的值,改为leddriver.o。

内容如下:

KERNELDIR := /home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m :=  leddriver.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

运行测试

编译代码:

make -j32  //编译出驱动模块文件
arm-linux-gnueabihf-gcc ledApp.c -o ledApp  //编译测试 ledApp.c 这个测试程序

编译成功以后,就会生成一个名为“leddriver.o”的驱动模块文件,和ledApp 这个应用程序。

将编译出来 leddriver.ko和 ledApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15目录中,重启开发板。

进入到目录 lib/modules/4.1.15 中,输入如下命令加载 leddriver.ko 这个驱动模块:

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

查看/sys/bus/platform/devices/目录,看看我们的设备是否存在:

/sys/bus/platform/devices/目录,查看是否存在 led 的设备文件

驱动和模块都存在,当驱动和设备匹配成功以后,就会如图:

测试 LED 灯驱动:

./ledApp /dev/dtsplatled 1 //打开 LED 灯
./ledApp /dev/dtsplatled 0 //关闭 LED 灯

观察一下 LED 灯能否打开和关闭,如果可以的话就说明驱动工作正常。

卸载驱动,输入如下命令:

rmmod leddriver.ko
http://www.dtcms.com/a/344243.html

相关文章:

  • 【渗透测试】SQLmap实战:一键获取MySQL数据库权限
  • 如何在 Axios 中处理多个 baseURL 而不造成混乱
  • 用过redis哪些数据类型?Redis String 类型的底层实现是什么?
  • 【Java后端】 Spring Boot 集成 Redis 全攻略
  • java视频播放网站
  • 正点原子【第四期】Linux之驱动开发学习笔记-2.1LED灯驱动实验(直接操作寄存器)
  • 分布式与微服务
  • 20250822在Ubuntu24.04.2下指定以太网卡的IP地址
  • 深度学习入门详解:从神经网络到实践应用
  • 【English】复合句中的先行词在从句中是否充当成分
  • 吉利汽车与芯鼎微成立联合创新实验室共谱车规级LCoS显示新篇章
  • 面向RF设计人员的微带贴片天线计算器
  • Gamma校正硬件设计实现
  • Elasticsearch搜索原理
  • 加密狗如何抵御各类破解与攻击?深度解析加密狗多层保护机制
  • 关于数据产业规模测算的认识与思考
  • Paddle3D-PETRv1 精度测试与推理实践指南
  • JavaSSM框架从入门到精通!第三天(MyBatis(二))!
  • C++ OpenGL中几个常见库及其区别
  • 轮廓检测技术不仅能精确计算图像中的轮廓数量,还能完整记录每个轮廓包含的所有像素点坐标
  • Linux服务测试
  • Jenkins用户授权管理 企业级jenkins授权策略 jenkins用户权限分配
  • Flutter InheritedWidget 详解
  • 学习嵌入式的第二十四天——数据结构——队列和树
  • Flutter 从入门到精通 - 完整课程总结
  • 打印机怎么连接电脑?打印机驱动?【图文详解】USB连接打印机?wifi连接打印机?
  • ZKmall模块商城的跨境电商支付安全方案:加密与权限的双重防护
  • STL关联式容器解析:map与set详解
  • 电脑芯片大的32位与64位指的是什么
  • 94. 城市间货物运输 I, Bellman_ford 算法