[分享] 《原子Linux驱动开发》基础阅读2:Linux LED驱动开发

秦天qintian0303   2024-3-9 21:22 楼主

        了解完字符设备驱动开发的一般流程与基本介绍,下面我们进行具体的一个字符设备驱动开发。

        和咱们进行裸机开发,或者是基于arm内核的stm32开发一样,咱们在进行开发都是先易后难,例如咱们进行LED的操作,在stm32中实际上就是对寄存器的初始化后进行具体的寄存器的操作,来实现LED的亮与灭,基于linux的LED驱动编写要符合linux的驱动框架。

ATK-CLMP135B中对应的LED控制口为PI3:

image-20240309211615-1.png  

        咱们在进行裸机开发时或者stm32的控制时,一般不会太重点注意到物理空间的位置,而这个时候我们在进行linux开发时就要注意一个问题了。地址的映射,我们要首先了解一下内存管理单元mmu完成的主要功能,一个就是虚拟空间和物理空间的映射那也就是驱动完成的具体功能,用户层不需要考虑设备的具体物理地址,但是设备的具体地址确是对外设的直接操作的,实现的用户层与物理层的映射,第二个功能就是内存保护,设置存储器的访问权限、设置虚拟存储空间的缓冲特性。

        地址映射,由于其范围的局限性,一般会存在虚拟地址多对一物理地址的情况,虚拟地址比物理地址范围大的问题由处理器自行处理,内核启动的时候会初始化内存管理单元,设置好内存映射,以后的CPU访问的都是虚拟地址。这个时候我们可以通过ioremap函数获取指定物理地址空间对应的虚拟地址,应用层实际操作的也是通过对虚拟地址空间的操作,我们在卸载驱动的时候也需要使用iounmap函数释放掉前期所做的映射。

        LED的驱动实际上就是IO口的高低电平的控制,在这里IO的输入输出并不是我们学习单片机时候讲的gpio引脚。这里涉及两个概念,IO端口和IO内存。外部寄存器或内存映射到IO空间时称为端口,寄存器或内存映射到内存空间时称之为内存。通过ioremap函数将寄存器的物理地址映射到虚拟地址以后,就可以直接通过指针访问这些地址,Linux内核建议使用操作函数对映射后的内存进行读写操作。

        接下来进行LED程序的编写:

头文件:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

宏定义

#define LED_MAJOR		200		/* 主设备号 */
#define LED_NAME		"led" 	/* 设备名字 */

#define LEDOFF 	0				/* 关灯 */
#define LEDON 	1				/* 开灯 */
 
/* 寄存器物理地址 */
#define PERIPH_BASE     		     	(0x40000000)
#define MPU_AHB4_PERIPH_BASE			(PERIPH_BASE + 0x10000000)
#define RCC_BASE        		    	(MPU_AHB4_PERIPH_BASE + 0x0000)	
#define RCC_MP_AHB4ENSETR				(RCC_BASE + 0XA28)
#define GPIOI_BASE						(MPU_AHB4_PERIPH_BASE + 0xA000)	
#define GPIOI_MODER      			    (GPIOI_BASE + 0x0000)	
#define GPIOI_OTYPER      			    (GPIOI_BASE + 0x0004)	
#define GPIOI_OSPEEDR      			    (GPIOI_BASE + 0x0008)	
#define GPIOI_PUPDR      			    (GPIOI_BASE + 0x000C)	
#define GPIOI_BSRR      			    (GPIOI_BASE + 0x0018)


/* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;

函数

void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIOI_BSRR_PI);
		val |= (1 << 19);	
		writel(val, GPIOI_BSRR_PI);
	}else if(sta == LEDOFF) {
		val = readl(GPIOI_BSRR_PI);
		val|= (1 << 3);	
		writel(val, GPIOI_BSRR_PI);
	}	
}

void led_unmap(void)
{
	/* 取消映射 */
	iounmap(MPU_AHB4_PERIPH_RCC_PI);
	iounmap(GPIOI_MODER_PI);
	iounmap(GPIOI_OTYPER_PI);
	iounmap(GPIOI_OSPEEDR_PI);
	iounmap(GPIOI_PUPDR_PI);
	iounmap(GPIOI_BSRR_PI);
}

static int led_open(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);		/* 关闭LED灯 */
	}
	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

static int __init led_init(void)
{
	int retvalue = 0;
	u32 val = 0;

	/* 初始化LED */
	/* 1、寄存器地址映射 */
  	MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);
	GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);
  	GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);
	GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);
	GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);
	GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);

	/* 2、使能PI时钟 */
	val = readl(MPU_AHB4_PERIPH_RCC_PI);
	val &= ~(0X1 << 8);	/* 清除以前的设置 */
	val |= (0X1 << 8);	/* 设置新值 */
	writel(val, MPU_AHB4_PERIPH_RCC_PI);

	/* 3、设置PI3通用的输出模式。*/
	val = readl(GPIOI_MODER_PI);
	val &= ~(0X3 << 3);	/* bit0:1清零 */
	val |= (0X1 << 3);	/* bit0:1设置01 */
	writel(val, GPIOI_MODER_PI);

	/* 3、设置PI3为推挽模式。*/
	val = readl(GPIOI_OTYPER_PI);
	val &= ~(0X1 << 3);	/* bit0清零,设置为上拉*/
	writel(val, GPIOI_OTYPER_PI);

	/* 4、设置PI3为高速。*/
	val = readl(GPIOI_OSPEEDR_PI);
	val &= ~(0X3 << 3); /* bit0:1 清零 */
	val |= (0x2 << 3); /* bit0:1 设置为10*/
	writel(val, GPIOI_OSPEEDR_PI);

	/* 5、设置PI3为上拉。*/
	val = readl(GPIOI_PUPDR_PI);
	val &= ~(0X3 << 3); /* bit0:1 清零*/
	val |= (0x1 << 3); /*bit0:1 设置为01*/
	writel(val,GPIOI_PUPDR_PI);

	/* 6、默认关闭LED */
	val = readl(GPIOI_BSRR_PI);
	val |= (0x1 << 3);	
	writel(val, GPIOI_BSRR_PI);

	/* 6、注册字符设备驱动 */
	retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
	if(retvalue < 0) {
		printk("register chrdev failed!\r\n");
		goto fail_map;
	}
	return 0;
	
fail_map:
	led_unmap();
	return -EIO;
}

static void __exit led_exit(void)
{
	/* 取消映射 */
	led_unmap();

	/* 注销字符设备驱动 */
	unregister_chrdev(LED_MAJOR, LED_NAME);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

        是不是和裸机开发驱动很相似,主要的功能就是LED的初始化,LED的操作,设备的注册与注销,设备的打开和释放等等,主要的区别就是linux驱动开发要在其开发框架下进行。

在爱好的道路上不断前进,在生活的迷雾中播撒光引

回复评论 (7)

感谢分享,谢谢!

点赞  2024-3-9 21:42

楼主这书感觉怎么样?详细介绍一下吧。

比如针对哪些板子,linux版本号,例子有哪些。

 

点赞  2024-3-10 10:36
引用: damiaa 发表于 2024-3-10 10:36 楼主这书感觉怎么样?详细介绍一下吧。 比如针对哪些板子,linux版本号,例子有哪些。  

正点原子的例程资料还是相当全的,这个书里面用的I.MX6U的板子,不过我这边只有STM32MP135的

在爱好的道路上不断前进,在生活的迷雾中播撒光引
点赞  2024-3-10 10:42
引用: damiaa 发表于 2024-3-10 10:36 楼主这书感觉怎么样?详细介绍一下吧。 比如针对哪些板子,linux版本号,例子有哪些。  

书里面的例子非常多,书也是相当的厚,估计规定时间内也就对基础知识了解一遍就不错了,后续实际应用的时候再一个一个试验

在爱好的道路上不断前进,在生活的迷雾中播撒光引
点赞  2024-3-10 10:43
引用: 秦天qintian0303 发表于 2024-3-10 10:43 书里面的例子非常多,书也是相当的厚,估计规定时间内也就对基础知识了解一遍就不错了,后续实际应用的时 ...

要是st家就好点。这些每家的还是有一些不一样。

点赞  2024-3-10 10:46
引用: damiaa 发表于 2024-3-10 10:46 要是st家就好点。这些每家的还是有一些不一样。

确实每家都不同,我本来计划一边实践一边学的,结构发现太难了,想要两个月彻底掌握也没可能,所以还是先了解基础,慢慢推进了

在爱好的道路上不断前进,在生活的迷雾中播撒光引
点赞  2024-3-10 11:00

感谢楼主推荐的这本书籍信息,希望楼主能提供一下这本书的全面简介,

点赞  2024-3-18 08:55
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复