LED驱动程序框架,兼容多个单板

一、整体框架

在这里插入图片描述

以面向对象的方式,把驱动拆分为通用的框架(leddrv.c)、具体的硬件操作(board_X.c):
抽象出一个结构体

struct led_operations{
	int num;						/*灯的数量*/
	int (*init) (void); 			/*初始化LED,which-哪一个LED*/
	int (*open) (int which);
	int (*ctl) (int which, char status);/*控制对应的LED的亮灭*/
	void (*exit) (void);	/*卸载驱动时调用,用于iounmap*/
};

每个单板相关的文件实现自己的led_operations结构体,利用函数指针实现,供上层单元(led_drv.c)使用。从而针对不同的单板,led_drv.c文件是不用修改的,只需要修改与硬件相关的board_xxx.c文件。

二、驱动程序

2.1 led_drv.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 "led_operations.h"

//#define LED_NUM 2
/*1、确定主设备号*/
static int major = 0;
//static char kernel_buf[1024];
static struct class *led_class;
struct led_operations* p_led_opr;

#define MIN(a,b) (a < b ? a : b)


static ssize_t led_drv_read (struct file *, char __user *, size_t, loff_t *);
static ssize_t led_drv_write (struct file *, const char __user *, size_t, loff_t *);
static int led_drv_open (struct inode *, struct file *);
static int led_drv_close (struct inode *, struct file *);


/*2、定义自己的file_operations结构体*/
static const struct file_operations led_drv_fops = {
	.owner		= THIS_MODULE,
	.open		= led_drv_open,
	.read		= led_drv_read,
	.write		= led_drv_write,
	.release	= led_drv_close,
};
	

/*3、实现对应的open、read、write等函数,填入file_operations结构体内*/

static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
	//int err;
	printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
	//err = copy_to_user(buf, kernel_buf, MIN(1024, size));
	return 0;
}

static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *off)
{
	char status;
	struct inode *inode = file_inode(file);
	int dev = iminor(inode) & 0x0f;
	
	printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
	copy_from_user(&status, buf, 1);
	/*根据此设备号和status控制LED*/
	p_led_opr->ctl(dev,status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	int minor = iminor(node) & 0x0f;
	printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
	/*根据此设备号初始化结构体*/
	//p_led_opr->init(minor);
	p_led_opr->open(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;
}



/*4、吧file_operations结构体告诉内核:注册驱动程序*/

/*5、谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数*/
static int __init led_init(void)
{
	int err;
	int i;
	printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "tanzhenwen_led", &led_drv_fops);

	//自动创建设备节点
	led_class = class_create(THIS_MODULE, "tanzhenwen_led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class))
	{
		unregister_chrdev(major,"tanzhenwen_led");
		return -1;
	}
	p_led_opr = get_board_led_opr();
	p_led_opr->init();
	
	for(i = 0; i < p_led_opr->num; i++)
	{
		device_create(led_class, NULL, MKDEV(major, i), NULL, "tanzhenwen_led%d",i);// /dev/tanzhenwen_led0,设备节点
	}

	
	return 0;
}


/*6、有入口函数就有出口函数:卸载驱动时就会去调用这个出口函数*/
static void __exit led_exit(void)
{
	int i;
	printk("%s %s line %d\n",__FILE__, __FUNCTION__, __LINE__);
	for(i = 0; i < p_led_opr->num; i++)
	{
		device_destroy(led_class, MKDEV(major, i));
	}
	class_destroy(led_class);
	unregister_chrdev( major, "tanzhenwen_led");
}



/*7、其它完善:提供设备信息,自动创建设备节点*/

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tanzhenwen");


2.2 单板相关文件

board_stm32mp157.c文件

#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 <asm/io.h>

#include "led_operations.h"


/* 寄存器 */
//RCC PLL4 Control Register (RCC_PLL4CR) 0x50000000+0x894	//使能PLL4时钟
static volatile unsigned int *RCC_PLL4CR;	

//RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28					//使能GPIO
static volatile unsigned int *RCC_MP_AHB4ENSETR;

//GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,		//PA10输出
static volatile unsigned int *GPIOA_MODER;

//GPIOA_ODR地址: 0x50002000 + 0x14							//读寄存、修改值、写回去(低效)
static volatile unsigned int *GPIOA_ODR;

//GPIOA_BSRR地址: 0x50002000 + 0x18							// 直接写寄存器,一次操作即可,高效
static volatile unsigned int *GPIOA_BSRR;

//GPIOG_MODER地址:0x50008000 + 0x00,							//PG8输出
static volatile unsigned int *GPIOG_MODER;
//GPIOG_ODR地址: 0x50008000 + 0x14							//读寄存、修改值、写回去(低效)
static volatile unsigned int *GPIOG_ODR;


//GPIOG_BSRR地址: 0x50008000 + 0x18							// 直接写寄存器,一次操作即可,高效
static volatile unsigned int *GPIOG_BSRR;



static int board_stm32mp157_led_init(void)
{
	
	/* ioremap :使用虚拟地址,后期可以用虚拟地址来访问寄存器 */
	//ioremap(mem_addr, mem_len);
	//RCC PLL4 Control Register (RCC_PLL4CR) 0x50000000+0x894	//使能PLL4时钟
	if(!RCC_PLL4CR)
	{
		RCC_PLL4CR = ioremap(0x50000000+0x894, 4);	
	
		//RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28					//使能GPIO
		RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28, 4);
		
		//GPIOA_MODER地址:0x50002000 + 0x00,设置bit[21:20]=0b01,		//PA10输出
		GPIOA_MODER = ioremap(0x50002000 + 0x00, 4);
		
		//GPIOA_ODR地址: 0x50002000 + 0x14							//读寄存、修改值、写回去(低效)
		GPIOA_ODR = ioremap(0x50002000 + 0x14, 4);
		
		//GPIOA_BSRR地址: 0x50002000 + 0x18 						// 直接写寄存器,一次操作即可,高效
		GPIOA_BSRR = ioremap(0x50002000 + 0x18, 4);


		//PG8输出
		//GPIOG_MODER地址:0x50008000 + 0x00,							
		GPIOG_MODER = ioremap(0x50008000 + 0x00, 4);
		//GPIOG_ODR地址: 0x50008000 + 0x14							//读寄存、修改值、写回去(低效)
		GPIOG_ODR = ioremap(0x50008000 + 0x14, 4);
		//GPIOG_BSRR地址: 0x50008000 + 0x18							// 直接写寄存器,一次操作即可,高效
		GPIOG_BSRR = ioremap(0x50008000 + 0x18, 4);
	}

	printk("%s %s %d, led\n", __FILE__, __FUNCTION__, __LINE__);
	
	return 0;
}

static void board_stm32mp157_led_exit(void)
{
	iounmap(RCC_PLL4CR);
	iounmap(RCC_MP_AHB4ENSETR);
	
	iounmap(GPIOA_MODER);
	iounmap(GPIOA_ODR);
	iounmap(GPIOA_BSRR);

	iounmap(GPIOG_MODER);
	iounmap(GPIOG_ODR);
	iounmap(GPIOG_BSRR);
}

static int board_stm32mp157_led_open(int which)
{
	if(which == 0)
	{
		/* 配置gpio,使能、配置为gpio,配置输入输出 */
		//配置时钟
		*RCC_PLL4CR |= (1<<0);
		while((*RCC_PLL4CR & (1<<1)) == 0);

		//使能GPIOA
		*RCC_MP_AHB4ENSETR |= (1<<0);

		//设置PA10用作输出,先清零,在设置为输出
		*GPIOA_MODER &= ~(3<<20);
		*GPIOA_MODER |= (0x01<<20);
	}
	else if(which == 1)
	{
		/* 配置gpio,使能、配置为gpio,配置输入输出 */
		//PG8
		//配置时钟
		*RCC_PLL4CR |= (1<<0);
		while((*RCC_PLL4CR & (1<<1)) == 0);

		
		//使能GPIOG
		*RCC_MP_AHB4ENSETR |= (1<<6);

		//设置PG8用作输出,先清零,在设置为输出
		*GPIOG_MODER &= ~(3<<16);
		*GPIOG_MODER |= (0x01<<16);
	}
	return 0;
}

static int board_stm32mp157_led_ctl(int which, char status)
{
	printk("%s %s %d, led %d,%s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");

	if(which == 0)
	{
		if(status)
		{	
			//灯亮
			*GPIOA_BSRR = (1<<26);	//输出低电平 GPIOA10
		}
		else
		{
			//灯灭
			*GPIOA_BSRR = (1<<10); //输出高电平
		}
	}
	else if(which == 1)
	{
		if(status)
		{	
			//灯亮
			*GPIOG_BSRR = (1<<24);	//输出低电平 GPIOA10
		}
		else
		{
			//灯灭
			*GPIOG_BSRR = (1<<8); //输出高电平
		}
	}
	return 0;
}

static struct led_operations board_stm32mp157_led_opr = {
	.num 	= 2,
	.init 	= board_stm32mp157_led_init,
	.open   = board_stm32mp157_led_open,
	.exit	= board_stm32mp157_led_exit,
	.ctl 	= board_stm32mp157_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
	return &board_stm32mp157_led_opr;
}


2.3 头文件

led_operations.h文件

#ifndef _LED_OPERATIONS_H_
#define _LED_OPERATIONS_H_

struct led_operations{
	int num;						/*灯的数量*/
	int (*init) (void); 			/*初始化LED,which-哪一个LED*/
	int (*open) (int which);
	int (*ctl) (int which, char status);/*控制对应的LED的亮灭*/
	void (*exit) (void);	/*卸载驱动时调用,用于iounmap*/
};

struct led_operations *get_board_led_opr(void);
#endif

2.4 Makefile

KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_test led_test.c
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order  
	rm -f hello_drv_test

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

# led_drv.c board_demo.c 编译成 100ask.ko
tanzhenwen_led-y := led_drv.o board_stm32mp157.o 
obj-m        += tanzhenwen_led.o

三、测试

可观测到对应的现象。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值