韦东山嵌入式linux系列-驱动总结

1 总结几种写驱动程序的方法

一步步由简单到框架

1.1 资源和驱动在同一个文件里

应用程序调用open等函数最简单的方法是驱动层也提供对应的drv_open,应用程序调用read,驱动层也提供对应的drv_read等等。需要写出驱动层的函数,为了便于管理,将这些函数放到file_operations结构体中,即第一步定义对应的file_operations结构体,并实现对应的open等程序(第一步);实现完成之后,将file_operations结构体通过register_chrdev注册到内核(第二步);然后通过入口函数调用注册函数(chrdev),安装驱动程序的时候,内核会调用入口函数,完成file_operations结构体的注册(第三步);有入口函数就有出口函数(第四步)。对于字符设备驱动程序而言,file_operations结构体是核心,每一个驱动程序都对应file_operations结构体,内核中有众多file_operations结构体,怎么才能找到对应的结构体呢?

应用程序要访问驱动程序,需要打开一个设备结点,里面有主设备号,根据设备结点的主设备号在内核中找到对应的file_operations结构体。注册结构体时足以提供主设备号,可以让内核分配;最后就是完善信息,创建类,创建设备。

详见:韦东山嵌入式linux系列-第一个实验-CSDN博客

hello.drv.c
/*************************************************************************
 > File Name: hello.drv.c
 > Author: Winter
 > Created Time: Sun 07 Jul 2024 12:35:19 AM EDT
 ************************************************************************/
 
#include <linux/module.h>
 
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
 
 
// 1确定主设备号,也可以让内核分配
static int major = 0;                           // 让内核分配
static char kernel_buf[1024];           // 保存应用程序的数据
static struct class *hello_class;
 
#define MIN(a, b) (a < b ? a : b)
 
// 3 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
        int err;
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        // 将kernel_buf区的数据拷贝到用户区数据buf中,即从内核kernel_buf中读数据
        err = copy_to_user(buf, kernel_buf, MIN(1024, size));
        return MIN(1024, size);
}
 
static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
        int err;
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        // 把用户区的数据buf拷贝到内核区kernel_buf,即向写到内核kernel_buf中写数据
        err = copy_from_user(kernel_buf, buf, MIN(1024, size));
        return MIN(1024, size);
}
 
static int hello_drv_open (struct inode *node, struct file *file)
{
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        return 0;
}
 
static int hello_drv_close (struct inode *node, struct file *file)
{
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        return 0;
}
 
 
// 2定义自己的 file_operations 结构体
static struct file_operations hello_drv = {
        .owner = THIS_MODULE,
        .open = hello_drv_open,
        .read = hello_drv_read,
        .write = hello_drv_write,
        .release = hello_drv_close,
};
 
 
// 4把 file_operations 结构体告诉内核: register_chrdev
// 5谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init hello_init(void)
{
        int err;
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        // 注册hello_drv,返回主设备号
        major = register_chrdev(0, "hello", &hello_drv);  /* /dev/hello */
        // 创建class
        hello_class = class_create(THIS_MODULE, "hello_class");
        err = PTR_ERR(hello_class);
        if (IS_ERR(hello_class)) {
                printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
                unregister_chrdev(major, "hello");
                return -1;
        }
        // 创建device
        device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
 
        return 0;
}
 
// 6有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit hello_exit(void)
{
        printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        device_destroy(hello_class, MKDEV(major, 0));
        class_destroy(hello_class);
        // 卸载
        unregister_chrdev(major, "hello");
}
 
 
// 7其他完善:提供设备信息,自动创建设备节点: class_create,device_create
module_init(hello_init);
module_exit(hello_exit);
 
MODULE_LICENSE("GPL");

1.2 stm32mp157LED 驱动程序框架

这里需要根据stm32mp157的开发手册和数据手册配置对应的引脚

1.设置 RCC_PLL4CR 使能 hclk4 使用的时钟。
2.设置 RCC_MP_AHB4ENSETR 使能 GPIOA 外设时钟。
3.设置 GPIOA_MODER 中某位为 1,把该引脚设置为输入功能。
4.读 GPIOG_IDR 某位的值为 1 或者 0 来判断输入是否电平

详见:韦东山嵌入式linux系列-LED驱动程序-CSDN博客

led_drv.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
 
 
// 主设备号
static int major = 0;
static struct class *led_class;
 
// 不能使用物理地址,需要映射
// 1寄存器
// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的
static volatile unsigned int* RCC_PLL4CR;
 
// 2使能GPIOA本身
// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
static volatile unsigned int* RCC_MP_AHB4ENSETR;
 
// 3设置引脚为输出模式
// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式
static volatile unsigned int* GPIOA_MODER;
 
 
// 4设置输出电平
// 方法2:直接写寄存器,一次操作即可,高效
// GPIOA_BSRR地址: 0x50002000 + 0x18
static volatile unsigned int* GPIOA_BSRR;
 
 
// write函数
static ssize_t led_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
	// 从用户拷贝数据
	char value;
	copy_from_user(&value, buf, 1);				// 将用户空间buf的数据拷贝1字节到value中
 
	// 设置GPIOA10寄存器1/0
	if (value)
	{
		// 设置led on,让引脚输出低电平
		*GPIOA_BSRR =  (1 << 26); 				// 1左移26
		
	}
	else
	{
		// 设置led off,让引脚输出高电平
		*GPIOA_BSRR =  (1 << 10); 				// 1左移10
	}
 
	return 1;
}
 
 
// open函数
static int led_open(struct inode *inode, struct file *filp)
{
	// 使能PLL4,是所有GPIO的时钟
	*RCC_PLL4CR |= (1 << 0);					// 设置bit0为1
	while ((*RCC_PLL4CR & (1 << 1)) == 0);		// 如果bit1一直为0的话,就等待
	
	// 使能GPIOA
	*RCC_MP_AHB4ENSETR |= (1 << 0);				// 1左移0位
 
	// 将GPIOA的第十个引脚配置成GPIO
	// 配置GPIO是输出模式,只有用户程序open的时候,才表示要使用这个引脚,这个时候再配置引脚	
	*GPIOA_MODER &= ~(3 << 20);					// 清零 11左移20位,取反,
	*GPIOA_MODER |= (1 << 20);					// 20位设置成1,配置成01,输出模式
	
	return 0;
}
 
 
// file_operations结构体
static struct file_operations led_fops = {
	.owner	= THIS_MODULE,
	.write	= led_write,
	.open	= led_open,
};
 
 
// 入口函数
static int __init led_drv(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);				// 打印
	// 注册file_operations结构体,返回值是主设备号
	major = register_chrdev(0, "winter_led", &led_fops);
 
	// 驱动程序访问硬件,必须先ioremap,在这里映射,映射的是一页4k的地址,参考
	// ioremap(base_phy, size);
	// 1寄存器
	// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的
	// static volatile unsigned int* RCC_PLL4CR;
	RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);
 
	// 2使能GPIOA本身
	// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
	// static volatile unsigned int* RCC_MP_AHB4ENSETR;
	RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);
 
	// 3设置引脚为输出模式
	// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式
	// static volatile unsigned int* GPIOA_MODER;
	GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);
 
 
	// 4设置输出电平
	// 方法2:直接写寄存器,一次操作即可,高效
	// GPIOA_BSRR地址: 0x50002000 + 0x18
	// static volatile unsigned int* GPIOA_BSRR;
	GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);
	
 
	// 创建class
	led_class = class_create(THIS_MODULE, "myled");
	// 创建设备
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled");		// 系统会创建名为/dev/myled的设备节点
 
 
	return 0;
}
 
// 出口函数
static void __exit led_exit(void)
{
	iounmap(RCC_PLL4CR);
	iounmap(RCC_MP_AHB4ENSETR);
	iounmap(GPIOA_MODER);
	iounmap(GPIOA_BSRR);
	
	// 卸载设备
	device_destroy(led_class, MKDEV(major, 0));
	// 销毁类
	class_destroy(led_class);
	// 卸载
	unregister_chrdev(major, "winter_led");
	
};
 
module_init(led_drv);
module_exit(led_exit);
MODULE_LICENSE("GPL");

1.3 LED 驱动能支持多个板子的基础: 分层思想

说白了以前是一个drv.c文件,在里面定义了file_operation结构体、并实现了对应的read/open等函数,再在init函数中注册file_operaions结构体。

现在是将drv.c抽象抽象成两层:

(1)底层抽象为led_operations结构体,这里面主要有init初始化属性(函数指针)和ctl控制属性(函数指针),然后针对不同的板子,去实现对应的初始化函数和控制函数;

(2)上层中依旧保留原来大的框架,在初始化函数(open函数)中利用led_operations结构体指针调用init初始化属性,在控制函数(read/write函数)中利用led_operations结构体指针调用ctl控制属性。从而实现具体板子的功能业务和框架的分离。

 详见:韦东山嵌入式linux系列-LED 驱动程序框架-CSDN博客

led.drv.c
/*************************************************************************
 > File Name: led.drv.c
 > Author: Winter
 > Created Time: Sun 07 Jul 2024 12:35:19 AM EDT
 ************************************************************************/
 
#include <linux/module.h>
 
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_operations.h"
 
#define LED_NUM 2
 
 
// 1确定主设备号,也可以让内核分配
static int major = 0;				// 让内核分配
static struct class *led_class;
struct led_operations* p_led_operations;

 
#define MIN(a, b) (a < b ? a : b)
 
// 3 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
// write(fd, &val, 1);
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode* node;
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 把用户区的数据buf拷贝到内核区status,即向写到内核status中写数据
	err = copy_from_user(&status, buf, 1);
	// 根据次设备号和status控制LED
	node = file_inode(file);
	minor = iminor(node);
	p_led_operations->ctl(minor, status);
 
	return 1;
}
 
static int led_drv_open (struct inode *node, struct file *file)
{
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 得到次设备号
	minor = iminor(node);
	
	// 根据次设备号初始化LED
	p_led_operations->init(minor);
	return 0;
}
 
static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
 
// 2定义自己的 file_operations 结构体
static struct file_operations led_drv = {
	.owner = THIS_MODULE,
	.open = led_drv_open,
	.read = led_drv_read,
	.write = led_drv_write,
	.release = led_drv_close,
};
 
 
// 4把 file_operations 结构体告诉内核: register_chrdev
// 5谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_init(void)
{
	int err, i;	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 注册led_drv,返回主设备号
	major = register_chrdev(0, "winter_led", &led_drv);  /* /dev/led */
	// 创建class
	led_class = class_create(THIS_MODULE, "led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led_class");
		return -1;
	}
	// 创建device
	// 根据次设备号访问多个LED
//	device_create(led_class, NULL, MKDEV(major, 0), NULL, "winter_led0"); /* /dev/winter_led0 */
//	device_create(led_class, NULL, MKDEV(major, 1), NULL, "winter_led1"); /* /dev/winter_led1 */
	for (i = 0; i < LED_NUM; i++)
	{
		device_create(led_class, NULL, MKDEV(major, i), NULL, "winter_led%d", i);
	}
 
	// 入口函数获得结构体指针
	p_led_operations = get_board_led_operations();
	
	return 0;
}
 

// 6有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_exit(void)
{
	int i;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	for (i = 0; i < LED_NUM; i++)
	{
		device_destroy(led_class, MKDEV(major, i));
	}
	class_destroy(led_class);
	// 卸载
	unregister_chrdev(major, "winter_led");
}
 
 
// 7其他完善:提供设备信息,自动创建设备节点: class_create,device_create
module_init(led_init);
module_exit(led_exit);
 
MODULE_LICENSE("GPL");
board_demo.c
#include <linux/gfp.h>
#include "led_operations.h"
 
// init函数
static int board_demo_led_init(int which)
{
	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
	return 0;
}
 
// ctl函数
static int board_demo_led_ctl(int which, char status)
{
	printk("%s %s line %d, led %d, %s\n", 
		__FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
	return 0;
}
 
static struct led_operations board_demo_led_operations = {
	.init = board_demo_led_init,
	.ctl = board_demo_led_ctl,
};
 
// 返回结构体
struct led_operations* get_board_led_operations(void)
{
	return &board_demo_led_operations;
}
led_operations.h
#ifndef LED_OPERATIONS_H
#define LED_OPERATIONS_H
 
struct led_operations {
    int (*init) (int which);                // 初始化LED,which是哪一个LED
    int (*ctl) (int which, char status);     // 控制LED,which-哪一个LED,status-1亮,0灭
};
 
// 返回结构体指针
struct led_operations* get_board_led_operations(void);
 
 
#endif

1.4 LED 驱动能支持多个板子的基础: 分层思想-stm32mp157

将1.2和1.3结合起来

详见:韦东山嵌入式linux系列-具体单板的 LED 驱动程序-CSDN博客

led_drv.c
/*************************************************************************
 > File Name: led.drv.c
 > Author: Winter
 > Created Time: Sun 07 Jul 2024 12:35:19 AM EDT
 ************************************************************************/
 
#include <linux/module.h>
 
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_operations.h"
 
// #define LED_NUM 2
 
// 1确定主设备号,也可以让内核分配
static int major = 0;				// 让内核分配
static struct class *led_class;
struct led_operations* p_led_operations;
 
#define MIN(a, b) (a < b ? a : b)
 
// 3 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
// write(fd, &val, 1);
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode* node;
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 把用户区的数据buf拷贝到内核区status,即向写到内核status中写数据
	err = copy_from_user(&status, buf, 1);
	// 根据次设备号和status控制LED
	node = file_inode(file);
	minor = iminor(node);
	p_led_operations->ctl(minor, status);
 
	return 1;
}
 
static int led_drv_open (struct inode *node, struct file *file)
{
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 得到次设备号
	minor = iminor(node);
	
	// 根据次设备号初始化LED
	p_led_operations->init(minor);
	return 0;
}
 
static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
 
// 2定义自己的 file_operations 结构体
static struct file_operations led_drv = {
	.owner = THIS_MODULE,
	.open = led_drv_open,
	.read = led_drv_read,
	.write = led_drv_write,
	.release = led_drv_close,
};
 
 
// 4把 file_operations 结构体告诉内核: register_chrdev
// 5谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_init(void)
{
	int err, i;	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 注册led_drv,返回主设备号
	major = register_chrdev(0, "winter_led", &led_drv);  /* /dev/led */
	// 创建class
	led_class = class_create(THIS_MODULE, "led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led_class");
		return -1;
	}
	// 入口函数获得结构体指针
	p_led_operations = get_board_led_operations();
	
	// 创建device
	// 根据次设备号访问多个LED
//	device_create(led_class, NULL, MKDEV(major, 0), NULL, "winter_led0"); /* /dev/winter_led0 */
//	device_create(led_class, NULL, MKDEV(major, 1), NULL, "winter_led1"); /* /dev/winter_led1 */
	for (i = 0; i < p_led_operations->num; i++)
	{
		device_create(led_class, NULL, MKDEV(major, i), NULL, "winter_led%d", i);
	}
	
	return 0;
}
 
 
 
// 6有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_exit(void)
{
	int i;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	for (i = 0; i < p_led_operations->num; i++)
	{
		device_destroy(led_class, MKDEV(major, i));
	}
	class_destroy(led_class);
	// 卸载
	unregister_chrdev(major, "winter_led");
}
 
 
// 7其他完善:提供设备信息,自动创建设备节点: class_create,device_create
module_init(led_init);
module_exit(led_exit);
 
MODULE_LICENSE("GPL");
stmp32mp157.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
 
#include "led_operations.h"
 
// 不能使用物理地址,需要映射
// 1寄存器
// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的
static volatile unsigned int* RCC_PLL4CR;
 
// 2使能GPIOA本身
// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
static volatile unsigned int* RCC_MP_AHB4ENSETR;
 
// 3设置引脚为输出模式
// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式
static volatile unsigned int* GPIOA_MODER;
 
// 4设置输出电平
// 方法2:直接写寄存器,一次操作即可,高效
// GPIOA_BSRR地址: 0x50002000 + 0x18
static volatile unsigned int* GPIOA_BSRR;
 
 
// init函数-配置引脚,把引脚配置成GPIO输出功能
static int board_demo_led_init(int which)
{
	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
	// 之前没有映射,就映射
	if (!RCC_PLL4CR)
	{
		// 驱动程序访问硬件,必须先ioremap,在这里映射,映射的是一页4k的地址,参考
		// ioremap(base_phy, size);
		// 1寄存器
		// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的
		// static volatile unsigned int* RCC_PLL4CR;
		RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);
 
		// 2使能GPIOA本身
		// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
		// static volatile unsigned int* RCC_MP_AHB4ENSETR;
		RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);
 
		// 3设置引脚为输出模式
		// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式
		// static volatile unsigned int* GPIOA_MODER;
		GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);
 
 
		// 4设置输出电平
		// 方法2:直接写寄存器,一次操作即可,高效
		// GPIOA_BSRR地址: 0x50002000 + 0x18
		// static volatile unsigned int* GPIOA_BSRR;
		GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);
	}
 
	// 初始化引脚
	if (which == 0)
	{
		// 使能PLL4,是所有GPIO的时钟
		*RCC_PLL4CR |= (1 << 0);					// 设置bit0为1
		while ((*RCC_PLL4CR & (1 << 1)) == 0);		// 如果bit1一直为0的话,就等待
		
		// 使能GPIOA
		*RCC_MP_AHB4ENSETR |= (1 << 0); 			// 1左移0位
		
		// 将GPIOA的第十个引脚配置成GPIO
		// 配置GPIO是输出模式,只有用户程序open的时候,才表示要使用这个引脚,这个时候再配置引脚	
		*GPIOA_MODER &= ~(3 << 20); 				// 清零 11左移20位,取反,
		*GPIOA_MODER |= (1 << 20);					// 20位设置成1,配置成01,输出模式
	}
	
	return 0;
}
 
// ctl函数-通过参数把引脚设置成高/低电平
static int board_demo_led_ctl(int which, char status)
{
	printk("%s %s line %d, led %d, %s\n", 
		__FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
	// 设置高/低电平
	if (which == 0)
	{
		// 设置GPIOA10寄存器1/0
		if (status)
		{
			// 设置led on,让引脚输出低电平
			*GPIOA_BSRR =  (1 << 26);				// 1左移26
			
		}
		else
		{
			// 设置led off,让引脚输出高电平
			*GPIOA_BSRR =  (1 << 10);				// 1左移10
		}
 
	}
		
	return 0;
}
 
// 加一个num属性
static struct led_operations board_demo_led_operations = {
	.num = 1,
	.init = board_demo_led_init,
	.ctl = board_demo_led_ctl,
};
 
// 返回结构体
struct led_operations* get_board_led_operations(void)
{
	return &board_demo_led_operations;
}
led_operations.h
#ifndef LED_OPERATIONS_H
#define LED_OPERATIONS_H
 
struct led_operations {
	int num;								// 灯的数量
    int (*init) (int which);                // 初始化LED,which是哪一个LED
    int (*ctl) (int which, char status);     // 控制LED,which-哪一个LED,status-1亮,0灭
};
 
// 返回结构体指针
struct led_operations* get_board_led_operations(void);
 
#endif

1.5 LED驱动设计的思想(面向对象/分层/分离)

在1.3分离的基础上继续改进,加入分离。

以面向对象的思想,在 board_A_led.c 中实现 led_resouce 结构体,它定义“资源”──要用哪一个引脚。
在 chipY_gpio.c 中仍是实现 led_operations 结构体,它要写得更完善,支持所有 GPIO。

详见:韦东山嵌入式linux系列-驱动设计的思想(面向对象/分层/分离)-CSDN博客

led_drv.c
/*************************************************************************
 > File Name: led.drv.c
 > Author: Winter
 > Created Time: Sun 07 Jul 2024 12:35:19 AM EDT
 ************************************************************************/
 
#include <linux/module.h>
 
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_operations.h"
 
#define LED_NUM 2
 
 
// 1确定主设备号,也可以让内核分配
static int major = 0;				// 让内核分配
static struct class *led_class;
struct led_operations* p_led_operations;

 
#define MIN(a, b) (a < b ? a : b)
 
// 3 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
// write(fd, &val, 1);
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode* node;
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 把用户区的数据buf拷贝到内核区status,即向写到内核status中写数据
	err = copy_from_user(&status, buf, 1);
	// 根据次设备号和status控制LED
	node = file_inode(file);
	minor = iminor(node);
	p_led_operations->ctl(minor, status);
 
	return 1;
}
 
static int led_drv_open (struct inode *node, struct file *file)
{
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 得到次设备号
	minor = iminor(node);
	
	// 根据次设备号初始化LED
	p_led_operations->init(minor);
	return 0;
}
 
static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
 
// 2定义自己的 file_operations 结构体
static struct file_operations led_drv = {
	.owner = THIS_MODULE,
	.open = led_drv_open,
	.read = led_drv_read,
	.write = led_drv_write,
	.release = led_drv_close,
};
 
 
// 4把 file_operations 结构体告诉内核: register_chrdev
// 5谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_init(void)
{
	int err, i;	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 注册led_drv,返回主设备号
	major = register_chrdev(0, "winter_led", &led_drv);  /* /dev/led */
	// 创建class
	led_class = class_create(THIS_MODULE, "led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led_class");
		return -1;
	}
	// 创建device
	// 根据次设备号访问多个LED
//	device_create(led_class, NULL, MKDEV(major, 0), NULL, "winter_led0"); /* /dev/winter_led0 */
//	device_create(led_class, NULL, MKDEV(major, 1), NULL, "winter_led1"); /* /dev/winter_led1 */
	for (i = 0; i < LED_NUM; i++)
	{
		device_create(led_class, NULL, MKDEV(major, i), NULL, "winter_led%d", i);
	}
 
	// 入口函数获得结构体指针
	p_led_operations = get_board_led_operations();
	
	return 0;
}

 
// 6有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_exit(void)
{
	int i;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	for (i = 0; i < LED_NUM; i++)
	{
		device_destroy(led_class, MKDEV(major, i));
	}
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	// 卸载
	unregister_chrdev(major, "winter_led");
}
 
 
// 7其他完善:提供设备信息,自动创建设备节点: class_create,device_create
module_init(led_init);
module_exit(led_exit);
 
MODULE_LICENSE("GPL");
chip_demo_gpio.c
#include <linux/gfp.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include "led_resource.h"
#include "led_operations.h"
 
static struct led_resource* p_led_reource;
 
 
// init函数
static int board_demo_led_init(int which)
{
	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
	if (!p_led_reource)
	{
		p_led_reource = get_led_resource();
	}
	printk("init gpio: group: %d, pin: %d\n", GROUP(p_led_reource->pin), PIN(p_led_reource->pin));
	switch(GROUP(p_led_reource->pin))
	{
		case 0:
		{
			printk("init pin of group 0...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3...\n");
			break;
		}
	}
	return 0;
}
 
// ctl函数
static int board_demo_led_ctl(int which, char status)
{
	printk("set led: %s: group: %d, pin: %d\n", status ? "on" : "off",
		GROUP(p_led_reource->pin), PIN(p_led_reource->pin));
	switch(GROUP(p_led_reource->pin))
	{
		case 0:
		{
			printk("init pin of group 0...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3...\n");
			break;
		}
	}
	return 0;
}
 
static struct led_operations board_demo_led_operations = {
	.init = board_demo_led_init,
	.ctl = board_demo_led_ctl,
};
 
// 返回结构体
struct led_operations* get_board_led_operations(void)
{
	return &board_demo_led_operations;
}
board_A_led.c
#include "led_resource.h"
 
// gpio3_1
// 3(11)左移16位 -- 11 0000 0000 0000 0000
static struct led_resource board_A_led = {
	.pin = GROUP_PIN(3, 1),
};
 
// 返回指针
struct led_resource* get_led_resource(void)
{
	return &board_A_led;
}
led_operations.h
#ifndef LED_OPERATIONS_H
#define LED_OPERATIONS_H
 
struct led_operations {
    int (*init) (int which);                // 初始化LED,which是哪一个LED
    int (*ctl) (int which, char status);     // 控制LED,which-哪一个LED,status-1亮,0灭
};
 
// 返回结构体指针
struct led_operations* get_board_led_operations(void);
 
 
#endif
led_resource.h
#ifndef LED_RESOURCE_H
#define LED_RESOURCE_H
 
// gpio3_0
// bit[31:16] = group
// bit[15:0] = which pin
#define GROUP(x) ((x) >> 16)
#define PIN(x) ((x) & 0xFFFF)
#define GROUP_PIN(g, p) (((g) << 16) | (p))
 
struct led_resource {
	int pin;
};
 
// 声明函数
struct led_resource* get_led_resource(void);
 
#endif

1.6 模板驱动程序的改造:总线设备驱动模型

首先用通用的platform_device结构体定义资源(引脚)代替之前的board_A_led.c;

再用platform_driver(里面有probe函数)结构体代替之前的chip_demo_gpio.c,当左右两边匹配的时候,probe函数就会被调用。

匹配之后具体做什么呢?

(1)记录资源;(2)对于每一个引脚要调用device_create(辅助作用)

这三个文件会被编译成三个ko文件。

入口函数注册platform_driver(在leddrv.c中)

详见:韦东山嵌入式linux系列-LED 模板驱动程序的改造:总线设备驱动模型-CSDN博客

 led_drv.c
/*************************************************************************
 > File Name: led.drv.c
 > Author: Winter
 > Created Time: Sun 07 Jul 2024 12:35:19 AM EDT
 ************************************************************************/
 
#include <linux/module.h>
 
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "led_operations.h"
 
#define LED_NUM 2
 
 
// 1确定主设备号,也可以让内核分配
static int major = 0;				// 让内核分配
static struct class *led_class;
struct led_operations* p_led_operations;
 
#define MIN(a, b) (a < b ? a : b)
 
// 封装device_create函数,创建第minor个
// 供其他文件调用
void led_device_create(int minor)
{
	device_create(led_class, NULL, MKDEV(major, minor), NULL, "winter_led%d", minor);
}
 
void led_device_destroy(int minor)
{
	device_destroy(led_class, MKDEV(major, minor));
}
 
// 底层向上册注册函数,底层在入口函数调用
void register_led_operations(struct led_operations* operations)
{
	// 由底层提供
	p_led_operations = operations;
}
 
 
// 在驱动程序中,这个文件作为ko加载,别的驱动文件想要引用这个函数,必须把它导出来
// 就得先加载这个ko文件,再加载引用它的ko文件
EXPORT_SYMBOL(led_device_create);
EXPORT_SYMBOL(led_device_destroy);
EXPORT_SYMBOL(register_led_operations);
 
 
// 3 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
// write(fd, &val, 1);
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode* node;
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 把用户区的数据buf拷贝到内核区status,即向写到内核status中写数据
	err = copy_from_user(&status, buf, 1);
	// 根据次设备号和status控制LED
	node = file_inode(file);
	minor = iminor(node);
	p_led_operations->ctl(minor, status);
 
	return 1;
}
 
static int led_drv_open (struct inode *node, struct file *file)
{
	int minor;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 得到次设备号
	minor = iminor(node);
	
	// 根据次设备号初始化LED
	p_led_operations->init(minor);
	return 0;
}
 
static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
 
 
// 2定义自己的 file_operations 结构体
static struct file_operations led_drv = {
	.owner = THIS_MODULE,
	.open = led_drv_open,
	.read = led_drv_read,
	.write = led_drv_write,
	.release = led_drv_close,
};
 
 
// 4把 file_operations 结构体告诉内核: register_chrdev
// 5谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
static int __init led_init(void)
{
	int err;	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 注册led_drv,返回主设备号
	major = register_chrdev(0, "winter_led", &led_drv);  /* /dev/led */
	// 创建class
	led_class = class_create(THIS_MODULE, "led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led_class");
		return -1;
	}
	// 创建device
	// 根据次设备号访问多个LED
//	device_create(led_class, NULL, MKDEV(major, 0), NULL, "winter_led0"); /* /dev/winter_led0 */
//	device_create(led_class, NULL, MKDEV(major, 1), NULL, "winter_led1"); /* /dev/winter_led1 */
	// 没有像之前那样创建device,空架子
 
	// 入口函数获得结构体指针,存在交叉依赖
	// p_led_operations = get_board_led_operations();
	return 0;
}
 
 
// 6有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	class_destroy(led_class);
	// 卸载
	unregister_chrdev(major, "winter_led");
}
 
 
// 7其他完善:提供设备信息,自动创建设备节点: class_create,device_create
module_init(led_init);
module_exit(led_exit);
 
MODULE_LICENSE("GPL");
chip_demo_gpio.c

在另外一边需要提供匹配的platform_driver。

分配、设置、注册某一个platform_driver结构体

platform_driver有name,和platform_device匹配;也有probe函数、remove函数

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
 
#include "led_resource.h"
#include "led_operations.h"
#include "led_drv.h"
 
 
static int g_ledpins[100];					// 记录引脚
static int g_ledcount = 0;					// 计数器
 
 
// init函数
static int board_demo_led_init(int which)
{
	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
	printk("init gpio: group: %d, pin: %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3...\n");
			break;
		}
	}
	return 0;
}
 
// ctl函数
static int board_demo_led_ctl(int which, char status)
{
	printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
	printk("set led: %s: group: %d, pin: %d\n", status ? "on" : "off",
		GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3...\n");
			break;
		}
	}
	return 0;
}
 
static struct led_operations board_demo_led_operations = {
	.init = board_demo_led_init,
	.ctl = board_demo_led_ctl,
};
 
// get函数
struct led_operations *get_board_led_operations(void)
{
    return &board_demo_led_operations;
}
 
 
// probe函数
// (1)记录引脚;(2)对于每一个引脚要调用device_create(辅助作用)
static int chip_demo_gpio_probe(struct platform_device *device)
{
	int i = 0;
	struct resource* res;
	while (1)
	{
		// 获取资源,IORESOURCE_IRQ类型的资源,第i个资源
		res = platform_get_resource(device, IORESOURCE_IRQ, i++);
		if (!res)
		{
			break;
		}
		// 记录引脚
		g_ledpins[g_ledcount] = res->start;
		// 同时创建device
		led_device_create(g_ledcount);
		g_ledcount++;
	}
 
	return 0;
}
 
 
// remove函数
static int chip_demo_gpio_remove(struct platform_device *device)
{
	struct resource *res;
	int i = 0;
	
	while (1)
	{
		res = platform_get_resource(device, IORESOURCE_IRQ, i);
		if (!res)
		{
			break;
		}
		led_device_destroy(i);
		i++;
		g_ledcount--;
	}
	return 0;
}
 
 
// platform_driver结构体
// name一致
static struct platform_driver chip_demo_gpio_drv = {
	.driver = {
		.name  = "winter_led",
	},
	.probe = chip_demo_gpio_probe,
	.remove = chip_demo_gpio_remove,
};
 
 
// 注册这个结构体,入口出口
static int chip_demo_gpio_drv_init(void)
{
	int err;
	err = platform_driver_register(&chip_demo_gpio_drv);
	// 向上层提供了led的操作函数
	register_led_operations(&board_demo_led_operations);
	return 0;
}
 
// 出口函数,注销用
static void chip_demo_gpio_drv_exit(void)
{
	platform_driver_unregister(&chip_demo_gpio_drv);
}
 
// 修饰为入口函数和出口函数
module_init(chip_demo_gpio_drv_init);
module_exit(chip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");
board_A_led.c
#include <linux/module.h>
 
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
 
 
#include "led_resource.h"
 
 
// 资源数组
// flag表示是哪一类资源,暂时认定IORESOURCE_IRQ表示引脚(IO不就是引脚吗)
// end先不管
static struct resource resource[] = {
	{
		.name = "winter_led_pin",
		.start = GROUP_PIN(3, 1),
		.flags = IORESOURCE_IRQ
	},
	{
		.name = "winter_led_pin",
		.start = GROUP_PIN(5, 8),
		.flags = IORESOURCE_IRQ
	},
};
 
// platform_device结构体
static struct platform_device board_A_led_drv = {
	.name = "winter_led",
	.num_resources = ARRAY_SIZE(resource),
	.resource = resource,
};
 
// 入口函数,注册用
static int led_dev_init(void)
{
	int err;
	err = platform_device_register(&board_A_led_drv);
	return 0;
}
 
// 出口函数,注销用
static void led_dev_exit(void)
{
	platform_device_unregister(&board_A_led_drv);
}
 
// 修饰为入口函数和出口函数
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

1.7 LED 模板驱动程序的改造:设备树

针对总线设备驱动模型

左边是和具体硬件打交道,右边对应是抽象。通过总线bus管理,两两匹配,右边的代码基本不用修改,左边的代码针对不同的硬件提供不同的device。会有大量的,导致内核庞大臃肿。

引入设备树

左边是和具体硬件打交道,右边对应是抽象。通过总线bus管理,两两匹配,右边的代码基本不用修改,左边的代码针对不同的硬件提供不同的device。会有大量的,导致内核庞大臃肿。

首先用通用的platform_device结构体定义资源(引脚)代替之前的board_A_led.c;

再用platform_driver(里面有probe函数)结构体代替之前的chip_demo_gpio.c,当左右两边匹配的时候,probe函数就会被调用。

匹配之后具体做什么呢?

(1)记录资源;(2)对于每一个引脚要调用device_create(辅助作用)

这三个文件会被编译成三个ko文件。

入口函数注册platform_driver(在leddrv.c中)

详见:

韦东山嵌入式linux系列-驱动进化之路:总线设备驱动模型-CSDN博客

韦东山嵌入式linux系列-LED 模板驱动程序的改造:总线设备驱动模型-CSDN博客

韦东山嵌入式linux系列-LED 模板驱动程序的改造:设备树-CSDN博客

针对stm32mp157的板子先修改arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts

现在不需要②了,上一次文件leddrv.c,里面注册file_operations结构体,结构体中有open/write等函数,open函数会去调用底层chip_demo_gpio.c中提供的int函数来初始化引脚;write函数会调用底层chip_demo_gpio.c中提供的ctl函数来控制引脚,上下分层。上面的leddrv和硬件关系不大,下面的chip_demo_gpio.c用来操作具体的硬件。

chip_demo_gpio.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/of.h>
#include <linux/platform_device.h>

#include "led_resource.h"
#include "led_operations.h"
#include "led_drv.h"


static int g_ledpins[100];					// 记录引脚
static int g_ledcount = 0;					// 计数器


// init函数
static int board_demo_led_init(int which)
{
	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
	printk("init gpio: group: %d, pin: %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3...\n");
			break;
		}
	}
	return 0;
}

// ctl函数
static int board_demo_led_ctl(int which, char status)
{
	printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
	printk("set led: %s: group: %d, pin: %d\n", status ? "on" : "off",
		GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	switch(GROUP(g_ledpins[which]))
	{
		case 0:
		{
			printk("init pin of group 0...\n");
			break;
		}
		case 1:
		{
			printk("init pin of group 1...\n");
			break;
		}
		case 2:
		{
			printk("init pin of group 2...\n");
			break;
		}
		case 3:
		{
			printk("init pin of group 3...\n");
			break;
		}
	}
	return 0;
}

static struct led_operations board_demo_led_operations = {
	.init = board_demo_led_init,
	.ctl = board_demo_led_ctl,
};

// get函数
struct led_operations *get_board_led_operations(void)
{
    return &board_demo_led_operations;
}


// probe函数
// (1)记录引脚;(2)对于每一个引脚要调用device_create(辅助作用)
static int chip_demo_gpio_probe(struct platform_device *device)
{
	// 需要从platform_device中找到对应的设备结点,取出里面的pin属性
	struct device_node* np;
	int led_pin, err;
	np = device->dev.of_node;
	if (!np)
	{
		return -1;
	}
	// 从np结点中读出pin属性,存到led_pin中
	err = of_property_read_u32(np, "pin", &led_pin);
	// 记录引脚
	g_ledpins[g_ledcount] = led_pin;
	// 同时创建device
	led_device_create(g_ledcount);
	g_ledcount++;


	return 0;
}


// remove函数
static int chip_demo_gpio_remove(struct platform_device *device)
{
	struct device_node* np;
	int i = 0;
	int led_pin;
	int err;

	// 获取node结点
	np = device->dev.of_node;
	if (!np)
	{
		return -1;
	}
	// 取出pin属性
	err = of_property_read_u32(np, "pin", &led_pin);

	for (i = 0; i < g_ledcount; i++)
	{
		if (g_ledpins[i] == led_pin)
		{
			// 注销
			led_device_destroy(i);
			g_ledpins[i] = -1;
			break;
		}
	}

	for (i = 0; i < g_ledcount; i++)
	{
		if (g_ledpins[i] != -1)
		{
			break;
		}
	}
	if (i == g_ledcount)
	{
		g_ledcount = 0;
	}
	
	
	return 0;
}

static const struct of_device_id winter_leds[] = {
	{ .compatible = "winter_leddrv" },
};



// platform_driver结构体
// name一致
static struct platform_driver chip_demo_gpio_drv = {
	.driver = {
		.name  = "winter_led",
		.of_match_table = winter_leds,
	},
	.probe = chip_demo_gpio_probe,
	.remove = chip_demo_gpio_remove,
};


// 注册这个结构体,入口出口
static int chip_demo_gpio_drv_init(void)
{
	int err;
	err = platform_driver_register(&chip_demo_gpio_drv);
	// 向上层提供了led的操作函数
	register_led_operations(&board_demo_led_operations);
	return 0;
}

// 出口函数,注销用
static void chip_demo_gpio_drv_exit(void)
{
	platform_driver_unregister(&chip_demo_gpio_drv);
}

// 修饰为入口函数和出口函数
module_init(chip_demo_gpio_drv_init);
module_exit(chip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");

其他的代码和之前一样

1.8 模板驱动程序的改造:设备树-具体单板stm32mp157的驱动程序

代码部分和 韦东山嵌入式linux系列-LED 模板驱动程序的改造:设备树-CSDN博客 相比只改变了chip_demo_gpio.c中board_demo_led_init函数和board_demo_led_ctl函数,其他的没有变化。

详见:韦东山嵌入式linux系列-LED 模板驱动程序的改造:设备树-具体单板stm32mp157的驱动程序-CSDN博客

chip_demo_gpio.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/of.h>
#include <asm/io.h>
#include <linux/platform_device.h>

#include "led_resource.h"
#include "led_operations.h"
#include "led_drv.h"


static int g_ledpins[100];					// 记录引脚
static int g_ledcount = 0;					// 计数器

// 不能使用物理地址,需要映射
// 1寄存器
// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的
static volatile unsigned int* RCC_PLL4CR;

// 2使能GPIOA本身
// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
static volatile unsigned int* RCC_MP_AHB4ENSETR;

// 3设置引脚为输出模式
// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式
static volatile unsigned int* GPIOA_MODER;

// 4设置输出电平
// 方法2:直接写寄存器,一次操作即可,高效
// GPIOA_BSRR地址: 0x50002000 + 0x18
static volatile unsigned int* GPIOA_BSRR;



// init函数
static int board_demo_led_init(int which)
{
	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
	printk("init gpio: group: %d, pin: %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	// 之前没有映射,就映射
	if (!RCC_PLL4CR)
	{
		// 驱动程序访问硬件,必须先ioremap,在这里映射,映射的是一页4k的地址,参考
		// ioremap(base_phy, size);
		// 1寄存器
		// RCC_PLL4CR地址:0x50000000 + 0x894,提供时钟的
		// static volatile unsigned int* RCC_PLL4CR;
		RCC_PLL4CR = ioremap(0x50000000 + 0x894, 4);

		// 2使能GPIOA本身
		// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
		// static volatile unsigned int* RCC_MP_AHB4ENSETR;
		RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);

		// 3设置引脚为输出模式
		// GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,用于输出模式
		// static volatile unsigned int* GPIOA_MODER;
		GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);


		// 4设置输出电平
		// 方法2:直接写寄存器,一次操作即可,高效
		// GPIOA_BSRR地址: 0x50002000 + 0x18
		// static volatile unsigned int* GPIOA_BSRR;
		GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);
	}

	// 初始化引脚
	if (which == 0)
	{
		// 使能PLL4,是所有GPIO的时钟
		*RCC_PLL4CR |= (1 << 0);					// 设置bit0为1
		while ((*RCC_PLL4CR & (1 << 1)) == 0);		// 如果bit1一直为0的话,就等待
		
		// 使能GPIOA
		*RCC_MP_AHB4ENSETR |= (1 << 0); 			// 1左移0位
		
		// 将GPIOA的第十个引脚配置成GPIO
		// 配置GPIO是输出模式,只有用户程序open的时候,才表示要使用这个引脚,这个时候再配置引脚	
		*GPIOA_MODER &= ~(3 << 20); 				// 清零 11左移20位,取反,
		*GPIOA_MODER |= (1 << 20);					// 20位设置成1,配置成01,输出模式
	}

	return 0;
}

// ctl函数
static int board_demo_led_ctl(int which, char status)
{
	printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
	printk("set led: %s: group: %d, pin: %d\n", status ? "on" : "off",
		GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
	// 设置高/低电平
	if (which == 0)
	{
		// 设置GPIOA10寄存器1/0
		if (status)
		{
			// 设置led on,让引脚输出低电平
			*GPIOA_BSRR =  (1 << 26);				// 1左移26
			
		}
		else
		{
			// 设置led off,让引脚输出高电平
			*GPIOA_BSRR =  (1 << 10);				// 1左移10
		}

	}

	return 0;
}

static struct led_operations board_demo_led_operations = {
	.init = board_demo_led_init,
	.ctl = board_demo_led_ctl,
};

// get函数
struct led_operations *get_board_led_operations(void)
{
    return &board_demo_led_operations;
}


// probe函数
// (1)记录引脚;(2)对于每一个引脚要调用device_create(辅助作用)
static int chip_demo_gpio_probe(struct platform_device *device)
{
	// 需要从platform_device中找到对应的设备结点,取出里面的pin属性
	struct device_node* np;
	int led_pin, err;
	np = device->dev.of_node;
	if (!np)
	{
		return -1;
	}
	// 从np结点中读出pin属性,存到led_pin中
	err = of_property_read_u32(np, "pin", &led_pin);
	// 记录引脚
	g_ledpins[g_ledcount] = led_pin;
	// 同时创建device
	led_device_create(g_ledcount);
	g_ledcount++;


	return 0;
}


// remove函数
static int chip_demo_gpio_remove(struct platform_device *device)
{
	struct device_node* np;
	int i = 0;
	int led_pin;
	int err;

	// 获取node结点
	np = device->dev.of_node;
	if (!np)
	{
		return -1;
	}
	// 取出pin属性
	err = of_property_read_u32(np, "pin", &led_pin);

	for (i = 0; i < g_ledcount; i++)
	{
		if (g_ledpins[i] == led_pin)
		{
			// 注销
			led_device_destroy(i);
			g_ledpins[i] = -1;
			break;
		}
	}

	for (i = 0; i < g_ledcount; i++)
	{
		if (g_ledpins[i] != -1)
		{
			break;
		}
	}
	if (i == g_ledcount)
	{
		g_ledcount = 0;
	}
	
	
	return 0;
}

static const struct of_device_id winter_leds[] = {
	{ .compatible = "winter_leddrv" },
};



// platform_driver结构体
// name一致
static struct platform_driver chip_demo_gpio_drv = {
	.driver = {
		.name  = "winter_led",
		.of_match_table = winter_leds,
	},
	.probe = chip_demo_gpio_probe,
	.remove = chip_demo_gpio_remove,
};


// 注册这个结构体,入口出口
static int chip_demo_gpio_drv_init(void)
{
	int err;
	err = platform_driver_register(&chip_demo_gpio_drv);
	// 向上层提供了led的操作函数
	register_led_operations(&board_demo_led_operations);
	return 0;
}

// 出口函数,注销用
static void chip_demo_gpio_drv_exit(void)
{
	platform_driver_unregister(&chip_demo_gpio_drv);
}

// 修饰为入口函数和出口函数
module_init(chip_demo_gpio_drv_init);
module_exit(chip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值