了解完字符设备驱动开发的一般流程与基本介绍,下面我们进行具体的一个字符设备驱动开发。
和咱们进行裸机开发,或者是基于arm内核的stm32开发一样,咱们在进行开发都是先易后难,例如咱们进行LED的操作,在stm32中实际上就是对寄存器的初始化后进行具体的寄存器的操作,来实现LED的亮与灭,基于linux的LED驱动编写要符合linux的驱动框架。
ATK-CLMP135B中对应的LED控制口为PI3:
咱们在进行裸机开发时或者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驱动开发要在其开发框架下进行。