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结构体。注册结构体时足以提供主设备号,可以让内核分配;最后就是完善信息,创建类,创建设备。
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 来判断输入是否电平
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控制属性。从而实现具体板子的功能业务和框架的分离。
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结合起来
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。
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中)
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博客
针对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");
文章评论