一、一个最基本的LED字符设备驱动

GPIO是最简单的硬件操作,GPIO输出又更易于GPIO输入,所以驱动的学习也应始于GPIO输出操作。废话不多说了,现在就来写一个简单的LED字符设备驱动,用来点亮和熄灭板子上的LED灯。

我用的板子是正点原子的ALPHA,使用的芯片是IMX6ULL,板子上有一颗LED灯,使用的IO口是GPIO1_IO03,我们通过字符设备控制这个IO口输出高电平和低电平,从而达到控制LED灯的亮灭。

我写的也不是什么教程,我也是跟着别人的教程在学习,所以就不罗嗦什么原理之类的了,就直接贴代码了。不过要说明的是,代码是我自己优化过的,因为经过了自己的思考,所以绝对会比教程里面的代码更合理,也更贴近实际工程中使用,大家通过与教程代码对比也可以看得出来。

驱动源码工程下载链接:一个最基本的LED字符设备驱动

leddrv.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/ide.h>

/*这个宏用于获取寄存器位掩码中有效位的偏移位置
val: 寄存器的位掩码值,1表示有效位(连续的),0表示无效位
pos: 获取到的偏移位置,为右侧0(无效位)的个数
*/
#define GET_MASK_OFFSET(val, pos) \
	for(pos = 0; pos < 32; pos++) \
		if(val & (0x1 << pos)) break; \

#define LED_ON 				(0)

/*配置一个GPIO引脚所需要使用的的寄存器集合,该GPIO引脚在寄存器中的对应配置位以及配置值
[0]: 寄存器地址
[1]: 有效配置位
[2]: 配置值
*/
struct gpio_regs
{
	unsigned int ccm_reg[3];
	unsigned int mux_reg[3];
	unsigned int pad_reg[3];
	unsigned int gdir_reg[3];
	unsigned int dr_reg[3];
};

/*寄存器的内存映射地址集合,打开一个LED时可作为该LED的私有数据
*/
struct led_resource
{
	void __iomem *ccm_map;
	void __iomem *mux_map;
	void __iomem *pad_map;
	void __iomem *gdir_map;
	void __iomem *dr_map;
	
	unsigned int bitmask;	//记录DR寄存器的控制位,用于后续的电平输出
};

/*该数组中gpio_regs结构体的个数决定了LED灯的数量,一个gpio_regs结构体代表一个LED灯
*/
static const struct gpio_regs led_regs[] = {
	{ 	
		{ 0x020C406C, 0x0C000000, 0x3 		},	//GPIO时钟模块配置
		{ 0x020E0068, 0x0000000F, 0x5 		},	//GPIO引脚复用配置
		{ 0x020E02F4, 0x0001FFFF, 0x10B0 	},	//GPIO引脚电气属性配置
		{ 0x0209C004, 0x00000008, 0x1		},	//GPIO引脚配置为输出
		{ 0x0209C000, 0x00000008, 0x1 		},	//GPIO默认输出高电平
	},
	//可在此添加gpio_regs结构体,增加LED灯
};

static int led_count;
static int led_major;
static struct class *leddrv_class;
static struct led_resource *led_res;

static int leddrv_open(struct inode *inode, struct file *filp)
{
	unsigned int value, offset;
	int minor = iminor(inode);

	//设备号超过LED数量
	if(minor >= led_count)
		return -1;

	/*在OPEN里面初始化具体的硬件,因为打开一个设备才表示真正要用到这个设备*/

	//开启GPIO模块时钟
	GET_MASK_OFFSET(led_regs[minor].ccm_reg[1], offset);
	value = readl(led_res[minor].ccm_map);
	value &= (~led_regs[minor].ccm_reg[1]);
	value |= (led_regs[minor].ccm_reg[2] << offset);
	writel(value, led_res[minor].ccm_map);

	//复用引脚为GPIO
	GET_MASK_OFFSET(led_regs[minor].mux_reg[1], offset);
	value = readl(led_res[minor].mux_map);
	value &= (~led_regs[minor].mux_reg[1]);
	value |= (led_regs[minor].mux_reg[2] << offset);
	writel(value, led_res[minor].mux_map);

	//设置引脚电气属性
	GET_MASK_OFFSET(led_regs[minor].pad_reg[1], offset);
	value = readl(led_res[minor].pad_map);
	value &= (~led_regs[minor].pad_reg[1]);
	value |= (led_regs[minor].pad_reg[2] << offset);
	writel(value, led_res[minor].pad_map);

	//配置GPIO引脚为输出
	GET_MASK_OFFSET(led_regs[minor].gdir_reg[1], offset);
	value = readl(led_res[minor].gdir_map);
	value &= (~led_regs[minor].gdir_reg[1]);
	value |= (led_regs[minor].gdir_reg[2] << offset);
	writel(value, led_res[minor].gdir_map);

	//默认输出高电平
	GET_MASK_OFFSET(led_regs[minor].dr_reg[1], offset);
	value = readl(led_res[minor].dr_map);
	value &= (~led_regs[minor].dr_reg[1]);
	value |= (led_regs[minor].dr_reg[2] << offset);
	writel(value, led_res[minor].dr_map);

	filp->private_data = (void*)&led_res[minor];

 	return 0;
}

static ssize_t leddrv_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
	int ret;
	char val;
	unsigned value, offset;
	struct led_resource *res;

	int minor = iminor(file_inode(filp));

	//设备号超过LED数量
	if(minor >= led_count)
		return -1;

	//获取LED设备对应的资源结构体
	res = (struct led_resource *)(filp->private_data);
	if(!res)
		return -1;

	//获取用户参数
	ret = copy_from_user(&val, buf, 1);
	if(ret < 0)
		return -EFAULT;

	//设置GPIO引脚电平
	GET_MASK_OFFSET(res->bitmask, offset);
	value = readl(res->dr_map);
	value &= (~res->bitmask);

	if(val == LED_ON)
		value &= (0x0 << offset);
	else
		value |= (0x1 << offset);
		
	writel(value, res->dr_map);

	return 1;
}

static int leddrv_close(struct inode *inode, struct file *filp)
{
	struct led_resource *res;
	unsigned value, offset;
	int minor = iminor(inode);

	//设备号超过LED数量
	if(minor >= led_count)
		return -1;

	//获取LED设备对应的资源结构体
	res = (struct led_resource *)(filp->private_data);
	if(!res)
		return -1;

	//关闭LED灯
	GET_MASK_OFFSET(res->bitmask, offset);
	value = readl(res->dr_map);
	value &= (~res->bitmask);
	value |= (0x1 << offset);
	writel(value, res->dr_map);

	filp->private_data = NULL;

	return 0;
}

static const struct file_operations drv_fops = {
	.owner		= THIS_MODULE,
	.open		= leddrv_open,
	.write		= leddrv_write,
	.release 	= leddrv_close,
};

static int __init leddrv_init(void)
{
	dev_t dev;
	int i, ret;

	//获取LED数量
	led_count = sizeof(led_regs) / sizeof(struct gpio_regs);
	if(led_count < 1) return 0;

	//分配LED内存映射结构体
	led_res = (struct led_resource *)kzalloc(sizeof(struct led_resource) * led_count, GFP_KERNEL);
	if(!led_res) {
		ret = -ENOMEM;
		goto err_maps_memory;
	}

	//将LED用到的寄存器进行内存映射
	for(i = 0; i < led_count; i++) {
		led_res[i].ccm_map 	= ioremap(led_regs[i].ccm_reg[0], 4);
		led_res[i].mux_map 	= ioremap(led_regs[i].mux_reg[0], 4);
		led_res[i].pad_map 	= ioremap(led_regs[i].pad_reg[0], 4);
		led_res[i].gdir_map = ioremap(led_regs[i].gdir_reg[0], 4);
		led_res[i].dr_map 	= ioremap(led_regs[i].dr_reg[0], 4);

		led_res[i].bitmask = led_regs[i].dr_reg[1];
	}

	//注冊字符设备驱动
	led_major = register_chrdev(0, "led driver", &drv_fops);
	if(led_major < 0) {
		printk(KERN_ERR "Led driver - cannot register device\n");
		ret = led_major;
		goto err_register_dev;
	}

	//为LED设备创建设备节点
	leddrv_class = class_create(THIS_MODULE, "led class");
	if(IS_ERR(leddrv_class)) {
		printk(KERN_ERR "Led driver - cannot create class\n");
		ret = PTR_ERR(leddrv_class);
		goto err_create_class;
	}
	for(i = 0; i < led_count; i++) {
		dev = MKDEV(led_major, i);
		device_create(leddrv_class, NULL, dev, NULL, "alpha-led-%d", i);
	}

	return 0;

err_create_class:
	unregister_chrdev(led_major, "led driver");

err_register_dev:
	for(i = 0; i < led_count; i++) {
		iounmap(led_res[i].ccm_map);
		iounmap(led_res[i].mux_map);
		iounmap(led_res[i].pad_map);
		iounmap(led_res[i].gdir_map);
		iounmap(led_res[i].dr_map);
	}
	kfree(led_res);

err_maps_memory:
	return ret;
}

static void __exit leddrv_exit(void)
{
	int i;
	dev_t dev;

	for(i = 0; i < led_count; i++) {
		dev = MKDEV(led_major, i);
		device_destroy(leddrv_class, dev);
	}
	class_destroy(leddrv_class);
	unregister_chrdev(led_major, "led driver");

	for(i = 0; i < led_count; i++) {
		iounmap(led_res[i].ccm_map);
		iounmap(led_res[i].mux_map);
		iounmap(led_res[i].pad_map);
		iounmap(led_res[i].gdir_map);
		iounmap(led_res[i].dr_map);
	}
	kfree(led_res);
}

module_init(leddrv_init);
module_exit(leddrv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David.Tang");

这个驱动程序只有一个leddrv.c文件,仅仅使用了几个Linux内核提供的字符设备驱动接口函数,没有使用任何框架,从字符设备的注册到操作具体的GPIO,全都在一个leddrv.c文件里面,简单直观。

我们能找到的大多数教程都是将LED的个数以及要使用的寄存器地址用宏定义的方式写死。在实际使用中,这非常不便于扩展,而且让人感觉很奇怪,因为“写死”在编程中就是不灵活不可扩展的代称。我这里进行了改善,定义一个struct gpio_regs结构体来表示LED灯要用到的寄存器以及配置值。这样一个struct gpio_regs结构体就代表了一个LED灯,有多少个LED灯就定义多少个结构体就行了,这就是面向对象的思维,非常重要。

这是一个简单粗暴的LED字符设备驱动,简单直观,但是还存在问题,我们下一篇文章接着进行优化。

学习心得:
1)Linux驱动 = 驱动框架 + 单片机(硬件操作),我们要花更多的精力在学习Linux驱动框架上,理解和复用已有的框架,可以加快开发的速度和兼容别人的应用。
2)在驱动的open函数里面才配置具体的硬件,而不是在驱动的入口函数里面。因为硬件往往可以复用为多个功能,加载一个驱动也并不代表要使用一个硬件设备,只有open一个设备的时候才表示要使用那个硬件。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值