失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > I.MX6ULL ARM驱动开发---设备树下的platfrom设备驱动

I.MX6ULL ARM驱动开发---设备树下的platfrom设备驱动

时间:2024-06-13 10:08:53

相关推荐

I.MX6ULL ARM驱动开发---设备树下的platfrom设备驱动

引言

最新的 Linux 内核已经支持了设备树,因此在设备树下如何编写 platform 驱动就显得尤为重要,本章我们就来学习一下如何在设备树下编写 platform 驱动。

一、设备树下的 platform 驱动

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

在使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver 即可。在编写基于设备树的 platform 驱动的时候我们需要注意一下几点:

1、在设备树中创建设备节点

毫无疑问,肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动!这点要切记。比如,我们可以编写如下所示的设备节点来描述我们本章实验要用到的 LED 这个设备:

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 属性表中要有“atkalpha-gpioled”。

2、编写 platform 驱动的时候要注意兼容属性

在使用设备树的时候 platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。所以,of_match_table 将会尤为重要,比如本例程的 platform 驱动中 platform_driver 就可以按照如下所示设置:

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,};

3、编写 platform 驱动

基于设备树的 platform 驱动和上一章无设备树的 platform 驱动基本一样,都是当驱动和设备匹配成功以后就会执行 probe 函数。我们需要在 probe 函数里面执行字符设备驱动那一套,当注销驱动模块的时候 remove 函数就会执行,都是大同小异的。

二、程序编写

1、platform 驱动程序编写

编写 leddriver.c 这个 platform 驱动文件,在 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_CNT1/* 设备号长度 */#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");

2、测试 APP 编写

#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;}

3、Makefile

KERNELDIR := /home/sh/Desktop/MUL/zimageCURRENT_PATH := $(shell pwd)obj-m := leddriver.oARCH=armCROSS_COMPILE=arm-linux-gnueabihf-build: kernel_moduleskernel_modules:$(MAKE) -j32 -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(CURRENT_PATH) modulesclean:$(MAKE) -j32 -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(CURRENT_PATH) clean

三、运行测试

输入如下命令加载 leddriver.ko 驱动模块:

insmod leddriver.ko //加载驱动

驱动模块加载成功以后 platform 总线就会进行匹配,当驱动和设备匹配成功后,就会输出如下图所示一行语句:

在/sys/bus/platform/drivers/目录下,存在一个名字“imx6ul-led”的文件。

在/sys/bus/platform/devices/目录下,存在一个名字“gpioled”的文件,也就是设备树中 gpioled 这个节点。

驱动和设备匹配成功以后就可以测试 LED 灯驱动了,输入如下命令打开 LED 灯:

/ledApp /dev/dtsplatled 1 //打开 LED 灯

在输入如下命令关闭 LED 灯:

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

观察一下 LED 灯能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如下命令即可:

rmmod leddriver.ko

如果觉得《I.MX6ULL ARM驱动开发---设备树下的platfrom设备驱动》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。