15.2linux设备树下的platform驱动编写(程序)_csdn
我尽量讲的更详细,为了关注我的粉丝!!!
修改设备树文件:
这个我们在上一章已经写过了,但是还是带着大家来重写一遍!
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 1
int 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