20.2 设备树中的 platform 驱动编写

一、设备树下的 platform 驱动

  platform 驱动框架分为总线、设备和驱动,总线不需要我们去管理,这个是 Linux 内核提供。在有了设备树的前提下,我们只需要实现 platform_driver 即可。

 

1.  修改 pinctrl-stm32.c 文件 

  先复习一下 pinctrl 子系统和 gpio子系统,pinctrl 子系统是在设备树中去配置 pin 的信息和电气属性(复用、上/下拉,速度等),gpio 子系统是初始化 gpio,比如设置 gpio 的输入输出,读取 gpio 的值等。

  这ST 针对 STM32MP1 提供的 Linux 系统中,其 pinctrl 的前提是在 platform 平台下引用,一切以使用的芯片为准。在使用 pinctrl 的时候需要先修改 pinctrl-stm32.c 文件,否则当某个引脚作为 GPIO 的时候是无法申请到的。

  首先进入 /linux/atk-mpl/linux/my_linux/linux-5.4.31/drivers/pinctrl/stm32 文件夹下,打开 pinctrl-stm32.c,修改文件:

/* 865行开始 */
static const struct pinmux_ops stm32_pmx_ops = {
	.get_functions_count	= stm32_pmx_get_funcs_cnt,
	.get_function_name	= stm32_pmx_get_func_name,
	.get_function_groups	= stm32_pmx_get_func_groups,
	.set_mux		= stm32_pmx_set_mux,
	.gpio_set_direction	= stm32_pmx_gpio_set_direction,
	.strict			= false,    // 这里原本是 true
                                // 这里设置false就是不采用严格模式
};

  修改完成重新编译 Linux 内核:

make uImage LOADADDR=0XC2000040 -j16
/* 
 uImage代表了要编译出来的内核镜像的格式
 LOADADDR=0XC2000040是指定内核加载到的起始地址
 */ 

  把 uImage 复制:

cd arch/arm/boot/
sudo cp uImage /home/alientek/linux/tftpboot/ -f

  测试一下看是否成功打开 uImage:

 

2.  创建设备的 pinctrl 节点

  进入 /linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts 文件夹下,打开 stm32mp15-pinctrl.dtsi 文件,之后 STM32MP1 的所有引脚都是在 pinctrl 文件里配置完成。pinctrl 节点加入以下内容:

 

3.  在设备树中创建设备节点

  这里面重点是配置 compatible 属性值,因为 platform 总线需要通过设备节点中的 compatible 属性值来匹配驱动。打开 /linux/atk-mpl/linux/my_linux/linux-5.4.31/arch/arm/boot/dts 文件夹下的 stm32mp157d-atk.dts:

 

4.  编写 platform 驱动的时候注意兼容属性

  使用设备树的时候,platform 驱动会通过 of_match_table 来保存兼容性值,表明此驱动兼容哪些设备。下面是 platform_driver 例程:

static const struct of_device_id led_of_match[] = {
    { .compatible = "alientek,led" }, /* 兼容属性 */    // 这里面的每个元素都是兼容属性,表示兼容的设备
    { /* Sentinel */ }    // 注意最后一个元素为空!!!
};

MODULE_DEVICE_TABLE(of, led_of_match);    // 声明led_of_match设备匹配表    
                                          // 内核模块可以将 led_of_match 数组注册到内核中

static struct platform_driver led_platform_driver = {
    .driver = {
        .name = "stm32mp1-led",
        .of_match_table = led_of_match,
    },
    .probe = led_probe,
    .remove = led_remove,
};

 

二、检查引脚复用配置 

1. 检查引脚 pinctrl 配置 

   在嵌入式 Linux 下,严格按照一个引脚对应一个功能设计硬件。比如 PIO 现在用作 GPIO 来驱动 LED,那么就不能把 PI0 作为其他的功能。开发版上是将 PI0 连接到了 LED0,但是 ST 官方是这样:

  这里 ST 官方是把 PI0 设置为 LCD_G5,模式为端口复用,现在只能一个 IO 复用为一个功能,所以这里需要屏蔽掉。

  继续往下看:

  这里 ST 官方是把 PI0 设置为 LCD_G5,模式为模拟输入模式,所以也要屏蔽掉。

 

2. 检查 gpio 占用

  上一节检查 PI0 引脚有没有被复用多个设备。当我们将一个引脚作为 GPIO 的时候,一定要检查当前设备树里面是否有其他设备也用到了这个 GPIO,保证设备树中只有一个设备树在使用这个 GPIO。

 

三、实验程序编写

  首先就要修改设备树文件,设备树里面加上我们需要的设备信息。需要创建 LED0 引脚的 pinctrl 节点,另外需要创建的 LED0 的设备节点。虽然之前完成了,但是没有编译:

cd linux/atk-mpl/linux/my_linux/linux-5.4.31
make dtbs -j32

# 将编译好的stm32mp157d-atk.dtb文件复制到
sudo cp stm32mp157d-atk.dtb /home/alientek/linux/tftpboot/ -f

 

1. platform 驱动程序编写

  在 cd linux/atk-mpl/Drivers/ 文件夹下,新建 18_dtsplatform,在里面新建 Vscode 工作区,并新建 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;		/* 设备		*/	
	struct device_node *node;	/* LED设备节点 */
	int gpio_led;				/* LED灯GPIO标号 */
};

struct leddev_dev leddev; 		/* led设备 */

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	if (sta == LEDON)
		gpio_set_value(leddev.gpio_led, 0);
	else if (sta == LEDOFF)
		gpio_set_value(leddev.gpio_led, 1);
}

static int led_gpio_init(struct device_node *nd)
{
	int ret;

	/* 从设备树中获取GPIO */
	leddev.gpio_led = of_get_named_gpio(nd, "led-gpio", 0);
	if(!gpio_is_valid(leddev.gpio_led)) {
        printk(KERN_ERR "leddev: Failed to get led-gpio\n");
        return -EINVAL;
    }
	
	/* 申请使用GPIO */
	ret = gpio_request(leddev.gpio_led, "LED0");
    if (ret) {
        printk(KERN_ERR "led: Failed to request led-gpio\n");
        return ret;
	}
	
	/* 将GPIO设置为输出模式并设置GPIO初始电平状态 */
    gpio_direction_output(leddev.gpio_led,1);
	
	return 0;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
	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[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 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 *pdev)	// 如果设备和驱动匹配成功,先去初始化pinctrl配置的IO,再去执行probe函数
{	
	int ret;
	
	printk("led driver and device was matched!\r\n");
	
	/* 初始化 LED */
	ret = led_gpio_init(pdev->dev.of_node);	// of_node连接着设备树的设备节点
	if(ret < 0)
		return ret;
		
	/* 1、申请设备号 */
	ret = alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
	if(ret < 0) {
		pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", LEDDEV_NAME, ret);
		goto free_gpio;
	}
	
	/* 2、初始化cdev  */
	leddev.cdev.owner = THIS_MODULE;
	cdev_init(&leddev.cdev, &led_fops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
	if(ret < 0)
		goto del_unregister;
	
	/* 4、创建类      */
	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(leddev.class)) {
		goto del_cdev;
	}

	/* 5、创建设备 */
	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
	if (IS_ERR(leddev.device)) {
		goto destroy_class;
	}
	
	return 0;
destroy_class:
	class_destroy(leddev.class);
del_cdev:
	cdev_del(&leddev.cdev);
del_unregister:
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
free_gpio:
	gpio_free(leddev.gpio_led);
	return -EIO;
}

/*
 * @description		: platform驱动的remove函数,移除platform驱动的时候此函数会执行
 * @param - dev 	: platform设备
 * @return 			: 0,成功;其他负值,失败
 */
static int led_remove(struct platform_device *dev)
{
	gpio_set_value(leddev.gpio_led, 1); 	/* 卸载驱动的时候关闭LED */
	gpio_free(leddev.gpio_led);	/* 注销GPIO */
	cdev_del(&leddev.cdev);				/*  删除cdev */
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
	device_destroy(leddev.class, leddev.devid);	/* 注销设备 */
	class_destroy(leddev.class); /* 注销类 */
	return 0;
}

/* 匹配列表(设备树才有的)*/	// 
// 这里的省略了获取compatible属性值,因为这里的匹配列表直接和设备匹配
static const struct of_device_id led_of_match[] = {
	{ .compatible = "alientek,led" },
	{ /* Sentinel */ }
};

MODULE_DEVICE_TABLE(of, led_of_match);		// 声明 led_of_match

/* platform驱动结构体 */
static struct platform_driver led_driver = {
	.driver		= {
		/* 设置这个 platform 驱动的名字为“stm32mp1-led”后,当驱动加载成功以后就会在
		/sys/bus/platform/drivers/目录下存在一个名为“stm32mp1-led”的文件 */
		.name	= "stm32mp1-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);	// 注册platform平台
}

/*
 * @description	: 驱动模块卸载函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit leddriver_exit(void)
{
	platform_driver_unregister(&led_driver);	// 卸载platform平台
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

 

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[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/alientek/linux/atk-mpl/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)

obj-m := leddriver.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

  编译 leddriver.c 和 ledApp.c 文件:

make -j32
arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp

  将编译好的 ledApp 和 leddriver.ko 文件复制:

sudo cp ledApp leddriver.ko /home/alientek/linux/nfs/rootfs/lib/modules/5.4.31/ -f

  开启开发板,输入一下命令:

cd lib/modules/5.4.31/
depmod
modprobe leddriver.ko

  之后就会看到这样的消息:

  在设备节点中有 gpioled,在驱动文件中有 stm32mp1-led 驱动文件,设备和驱动匹配成功后才会出现上图中的消息。

  测试 App:

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

/* 
 有一点忘记 dtsplatled 怎么来的,复习了一下是在最开始创建设备名字,
 在创建类,创建设备用到了,所以名字为 dtsplatled
 */

   卸载驱动:rmmod leddriver.ko

 

总结

  这一节是在设备树下使用platform,先是修改了 pinctrl-stm32.c 文件,这个是根据具体的芯片来修改这个的,这里修改了严格模式。之后创建设备的 pinctrl 节点,这里是配置io口和电气设置的。然后在设备树中创建设备节点,这里面需要添加 pinctrl-names 和 pinctrl-0。最后要检查 引脚 pinctrl 的配置和 gpio 占用情况,因为 Linux 下必须严格按照一个引脚对应一个功能设计硬件。

  程序中:

  ① 把之前的驱动入口函数(驱动模块加载)转移到了 probe 函数中,将驱动出口函数(驱动模块卸载)转移到了 remove 函数;

  ② 因为有设备树的前提下,新增匹配列表,这里面最重要的就是匹配设备的 compatible,注意最后一行需要留空,类似下图:

   ③ 上一节新增的 platform 驱动结构体,.driver里面成员变量 .name = XXX 和 .of_match_table,.name 在驱动和设备匹配成功后会在 /sys/bus/platform/drivers/目录下新建一个名为"XXX" 的文件夹,.of_match_table 是让之前读取 status 属性等获取设备树代码没有的关键。

本文作者:烟儿公主

本文链接:https://www.cnblogs.com/toutiegongzhu/p/17679911.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   烟儿公主  阅读(299)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 夏日大冒险 暴躁的兔子
夏日大冒险 - 暴躁的兔子
00:00 / 00:00
An audio error has occurred.

作词 : 暴躁的兔子

作曲 : 暴躁的兔子

编曲 : IOF

混音:Gfanfan

出品:网易飓风

夏天 不要再浪费时间

实现 你承诺过的改变

别再 找一堆借口拖延

现在就和我一起飞向海边

人生苦短 你应该学会如何作乐

低着头还怎么应对挫折

人应该为自己活着

不用去迎合

要去寻欢作乐

撮合我的浪漫和悲欢

把这荒诞人生都塞满

生活难免磕磕绊绊

对抗生活的平庸就是浪漫

学会取悦自己逆风翻盘

去反抗变态的三观

把条条框框都砸烂

建立新的规则推翻谈判

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

去冒险

我真的不care你是否会喜欢我

不跟风被定义的美 全都是灾祸

我才不讨好大多数绝不与示弱

过好你的生活

你管我应该怎么快活

没有人能有资格审判

别人的生活和牵绊

快闭上你的高谈阔论

乘风破浪吧 理想的风帆

我就是肆意张扬又如何

我就是锋芒毕露又如何

我就是离经叛道又如何

我就是要出格 你管我要如何

我就是与众不同又如何

我就是特立独行又如何

我就是不知好歹又如何

你管我怎样出格 你管我如何

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

不知进退的人呐

和我一起去海边

聊聊曾经的理想 一起想当年

那曾想改变世界的人

是否还满腔热忱

不羁的我们放肆着

反抗那命运的指针

解放灵魂

推广:网易飓风

企划:贾焱祺

监制:徐思灵

出品人:谢奇笛