我尽量讲的更详细,为了关注我的粉丝!!!
修改设备树文件:
这个我们在上一章已经写过了,但是还是带着大家来重写一遍!
1.打开pinctrl-stm32.c 这个文件:
strict 成员变量默认为 true,我们需要将其改为 false。
2.在linux源代码中,打开 stm32mp15-pinctrl.dtsi 文件并进行修改:
同时屏蔽PIO这个端口的其他复用功能。
找到<STM32_PINMUX('I', 0, ANALOG)>, /* LCD_G5 */
和<STM32_PINMUX('I', 0, AF14)>, /* LCD_G5 */
,进行屏蔽。
3.在设备树中创建设备节点,打开stm32mp157d-atk.dts 文件,修改gpioled根节点:
之前的博客也是跟大家按照肌肉记忆来编写程序!一步一步按照思路来编写!
总代码会放在最后。
为了让大家更能明白,可以先对着总代码,进行对我的写代码流程更加详细得当!
放心,我也是一步一步打的代码,不是复制粘贴!!!
我们发现驱动文件不用再写地址映射了,是因为都在设备节点和pinctrl准备好了!跟之前一样申请IO即可,这里和以前的区别就是驱动的分离和分层,可以匹配多个设备!通过platform总线来匹配而已!
1、头文件
#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>
2、驱动注册和注销
之前讲过就不用过多赘述了!
有了platform注册驱动后,就要编写platform_driver驱动结构体:跟之前的字符驱动注册差不多!
3、编写platform_driver驱动结构体
这里有为重要的就是设备树匹配表。
3.1、编写设备树匹配表
这个之前就讲过:
platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。
通过 MODULE_DEVICE_TABLE 声明一下 led_of_match 这个设备匹配表。
在编写 of_device_id 的时候最后一个元素一定要为空!也就是 { /* Sentinel */ }
。
compatible 值为“alientek,led”,在设备树中也有这个,会互相匹配:
因此驱动中对应的 probe 函数就会执行!
最后我们也可以在内核驱动文件中./drivers中发现stm32mp1-led这个,在./devices中发现gpioled这个,就说明相互匹配了!
3.2、编写probe和remove函数
相互匹配成功后,就会自动执行这个函数,所以我们在这里放注册字符设备驱动以及GPIO相关设置。
匹配成功后:显示这个
放在probe函数内:
3.2.1、配置led字符设备结构体
3.2.2、注册字符设备驱动
同样编写宏定义:
这个名字在以后的/dev目录下构建。
同理编写注销字符设备驱动:
3.2.3、初始化cdev以及添加cdev
配置cdev设备结构体:
作用:可以执行到/dev:
同样编写字符驱动操作集:
static int led_open(struct inode *inode, struct file *filp){return 0;}static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt){return 0;}static int led_release(struct inode *inode, struct file *filp){return 0;}/*设备操作函数*/static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.write = led_write,.release = led_release,};
同理需要删除cdev:
3.2.4、配置设备类和设备节点
配置相关设备结构体:
配置设备类:
同理删掉设备类:
配置设备节点:
同理删掉设备节点:
接下来就是跟以前一样的操作,目前注册好了设备节点,就要获取设备树的信息。
3.2.5、获取设备树的信息模块集成
接下来集成模块,进行获取设备树上的信息,并申请IO。
已经注册好了设备节点,目前要配置设备节点的相关信息。
配置设备节点相关信息:
配置设备结构体:
集成模块化:
放在probe函数内:
pdev->dev.of_node
能够指向设备节点(struct device_node
)。
在 Linux 内核里,设备树描述硬件信息,内核解析后会生成设备节点(struct device_node
)。struct platform_device
是表示平台设备的结构体,它包含 struct device
类型的 dev
成员。而 struct device
有 struct device_node *of_node
成员,内核创建设备时会把对应设备树节点的指针赋给 of_node
。所以 pdev->dev.of_node
能指向设备节点。
因为这里没有用到自己定义的LED设备节点:为了更直观看到:
一个意思led.node和pdev->dev.of_node。
配置led-gpio结构体:
配置补充集成模块函数:
同样释放GPIO:
从这里可以看出和以前代码不一样,并没有读取compatible和status属性值。这里可以不用写,简化代码。
为何代码可不读取 compatible
和 status
属性
在 Linux 内核里,当使用平台驱动(platform_driver
)时,内核会自动依据驱动的 of_match_table
来匹配设备树节点的 compatible
属性。
驱动需要支持多种不同的设备,且这些设备的 compatible
属性值不同,就可能需要在 probe
函数里手动读取 compatible
属性,进而依据不同的 compatible
值执行不同的初始化操作。
3.2.6、配置错误信息
同时修改集成模块:
这里发生错误了:
在 C 语言里,goto
语句只能跳转到同一个函数内部定义的标号处。
所以fail_findnode和fail_setoutput还是得跳转定义到led_gpio_init函数中:
4、配置操作集
这个都是以前讲过很多遍了,就不多介绍了,直接贴代码!
5、测试效果
开灯或关灯:
6、总代码
dtsleddriver.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 LED_CNT 1 /* 设备号长度 */
#define LED_NAME "dtsplatled" /* 设备名字 */
#define LEDOFF 0
#define LEDON 1/*led设备结构体*/
struct led_dev{dev_t devid;//设备号int major;//主设备号int minor;//次设备号struct cdev cdev; /*cdev*/struct class *class; /*设备类*/struct device *device; /*设备节点*/struct device_node *node; /*LED设备节点*/int gpio_led; /*LED灯GPIO标号*/
};
struct led_dev led;//led设备void led_switch(u8 sta)
{if (sta == LEDON )gpio_set_value(led.gpio_led, 0);else if (sta == LEDOFF)gpio_set_value(led.gpio_led, 1);
}static int led_gpio_init(struct device_node *nd)
{int ret;/*1.从设备树中获取GPIO*/led.gpio_led=of_get_named_gpio(nd,"led-gpio",0);if(!gpio_is_valid(led.gpio_led)) {printk(KERN_ERR "leddev: Failed to get led-gpio\n");goto fail_findnode;}/*2.申请使用GPIO*/ret=gpio_request(led.gpio_led,"LED0");if (ret) {printk(KERN_ERR "led: Failed to request led-gpio\n");goto fail_findnode;}/*3.将GPIO设置为输出模式并设置GPIO初始电平状态*/ret=gpio_direction_output(led.gpio_led,1);if (ret < 0) {ret = -EINVAL;goto fail_setoutput;}return 0;
fail_setoutput:gpio_free(led.gpio_led);
fail_findnode:device_destroy(led.class,led.devid);return ret;
}static int led_open(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];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) {led_switch(LEDON);} else if (ledstat == LEDOFF) {led_switch(LEDOFF);}return 0;
}static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/*设备操作函数*/
static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.write = led_write,.release = led_release,
};/*platform 驱动的 probe 函数,当驱动与设备匹配以后此函数
就会执行*/
static int led_probe(struct platform_device *pdev)
{int ret=0;printk("led driver and device was matched!\r\n");/*6.初始化LED*/led.node=pdev->dev.of_node;ret=led_gpio_init(pdev->dev.of_node);if(ret < 0){return ret;}/*1.注册字符设备驱动*/led.major=0;if(led.major){//若给定主设备号led.devid=MKDEV(led.major,0);ret=register_chrdev_region(led.devid,LED_CNT,LED_NAME);}else{//若未给定主设备号ret=alloc_chrdev_region(&led.devid,0,LED_CNT,LED_NAME);led.major=MAJOR(led.devid);led.minor=MINOR(led.devid);}if(ret<0){goto fail_devid;}printk("major=%d,minor=%d,NUm=%d,NAME=%s\r\n",led.major,led.minor,LED_CNT,LED_NAME);/*2.初始化cdev*/led.cdev.owner=THIS_MODULE;cdev_init(&led.cdev,&led_fops);/*3.添加cdev*/ret=cdev_add(&led.cdev,led.devid,LED_CNT);if(ret<0){goto fail_cdev;}/*4.创建设备类*/led.class=class_create(THIS_MODULE,LED_NAME);if(IS_ERR(led.class)){ret = PTR_ERR(led.class);goto fail_class;}/*5.创建设备节点*/led.device=device_create(led.class,NULL,led.devid,NULL,LED_NAME);if(IS_ERR(led.device)){ret = PTR_ERR(led.device);goto fail_device;}return 0;
fail_device:class_destroy(led.class);
fail_class:cdev_del(&led.cdev);
fail_cdev:unregister_chrdev_region(led.devid,LED_CNT);
fail_devid:return ret;
}
/*platform 驱动的 remove 函数*/
static int led_remove(struct platform_device *dev)
{/*卸载驱动的时候关闭LED*/gpio_set_value(led.gpio_led,1);/*注销GPIO*/gpio_free(led.gpio_led); /*注销设备节点*/device_destroy(led.class,led.devid);/*注销设备类*/class_destroy(led.class);/*注销字符设备对象*/cdev_del(&led.cdev);/*注销字符设备驱动*/unregister_chrdev_region(led.devid,LED_CNT); return 0;
}/*匹配列表*/
static const struct of_device_id led_of_match[] = {{ .compatible = "alientek,led" },{ /* Sentinel */ }
};MODULE_DEVICE_TABLE(of, led_of_match);/*platform驱动结构体*/
static struct platform_driver led_driver = {.driver = {.name = "stm32mp1-led", /*驱动名字,用于和设备匹配*/.of_match_table = led_of_match, /*设备树匹配表*/},.probe = led_probe,.remove = led_remove,
};/*驱动模块注册*/
static int __init leddriver_init(void)
{return platform_driver_register(&led_driver);
}
/*驱动模块注销*/
static void __exit leddriver_exit(void)
{platform_driver_unregister(&led_driver);
}module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree,"Y");
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"#define LEDOFF 0
#define LEDON 1int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char databuf[1];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
KERNELDIR := /home/chensir/linux/atk-mp1/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)
obj-m := dtsleddriver.o
build: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean