前面章节写的都是不涉及具体硬件的驱动程序,本章节基于STM32MP157硬件,编写一个通过寄存器控制LED灯的驱动程序
如下是LED灯的原理图,它的正极通过电阻连接到3.3v,负极通过LED0网络标号连接到核心板的A98引脚,在核心板中又将A98引脚连接到STM32MP157的GPIO I0(LED正极通过电阻接3.3v,负极连接到GPIO I0),此时可以通过控制GPIO I0的电平就可控制LED的亮灭,输出高电平(即为1)熄灭LED,输出低电平(即0)点亮LED。
GPIO的操作主要涉及到两种类型的寄存器,分别是时钟管理模块RCC的寄存器和GPIO外设模块的寄存器
这里只需要关注一个打开/关闭GPIO I外设模块时钟的寄存器
寄存器bit0~bit10分别控制GPIO A到GPIO K的外设模块时钟,为1时使能相应的时钟
Linux中使用虚拟地址访问内存(包括外设寄存器),因此无法直接通过芯片手册给的物理地址分为对应的外设寄存器,必需要先用系统提供的接口对内存(外设寄存器)地址进行映射,将物理地址转换为虚拟地址,然后在通过虚拟地址去读写内存(外设寄存器),下面是一些常用的外设寄存器映射和读写的接口:
//申请内存资源,在系统中内存资源不可以被重复申请,如果此段内存在别的地方被申请过了此操作会执行失败
#define request_region(start,n,name)
//释放内存资源
#define release_mem_region(start,n)
//将内存映射到虚拟地址
void __iomem *ioremap(unsigned long port, unsigned long size)
//取消ioremap的映射
void iounmap(volatile void __iomem *virtual)
//读取8为
u8 readb(const volatile void __iomem *addr)
int ioread8(void __iomem *addr)
//读16位
u16 readw(const volatile void __iomem *addr)
unsigned int ioread16(void __iomem *addr)
//读32位
u32 readl(const volatile void __iomem *addr)
unsigned int ioread32(void __iomem *addr)
//写8位
void writeb(u8 b, volatile void __iomem *addr)
void iowrite8(u8 b, void __iomem *addr)
//写16位
void writew(u16 b, volatile void __iomem *addr)
void iowrite16(u16 b, void __iomem *addr)
//写32位
void writel(u32 b, volatile void __iomem *addr)
void iowrite32(u32 b, void __iomem *addr)
//按8位为单位连续读
void ioread8_rep(void __iomem *addr, void *dst, unsigned long count)
//按16位为单位连续读
void ioread16_rep(void __iomem *addr, void *dst, unsigned long count)
//按32位为单位连续读
void ioread32_rep(void __iomem *addr, void *dst, unsigned long count)
//按8位为单位连续写
void iowrite8_rep(void __iomem *addr, const void *src, unsigned long count)
//按16位为单位连续写
void iowrite16_rep(void __iomem *addr, const void *src, unsigned long count)
//按32位为单位连续写
void iowrite32_rep(void __iomem *addr, const void *src, unsigned long count)
在Linux内核中一般按如下步骤操作外设寄存器
这里顺便提一下Linux内核中一些常用的内存操作函数
//申请指定大小的内存,内存在物理地址上连续
void *kmalloc(size_t size, gfp_t gfp_mask)
//释放内存,与kmalloc配合使用
void kfree(const void *block)
//申请指定大小的内存,内存在物理地址上不连续,但此函数相对于上面3个函数可以申请更大的内存空间
void *vmalloc(unsigned long size)
//释放内存,与vmalloc配合使用
void vfree(const void *addr)
//申请2的n次方页内存,内存在物理地址上连续
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
//释放内存,与__get_free_pages配合使用
void free_pages(unsigned long addr, unsigned int order)
//申请一页内存,内存在物理地址上连续
#define __get_free_page(gfp_mask)
//释放内存,与__get_free_page配合使用
#define free_page(addr)
/* 内存分配掩码gfp_mask
* GFP_ATOMIC 以原子方式分配内存,内存分配过程不会引起进程切换
* GFP_KERNEL 常规方式内存
* GFP_USER 为用户空间分配,也需要内核空间能访问,常被映射到用户空间
* GFP_DMA 在ZONE_DMA区域分配内存,要求分配的内存能进行DMA映射
*/
//根据结构体成员变量的地址计算出结构体变量的地址
#define container_of(ptr, type, member)
//将应用层的数据拷贝到内核层,应用层与内核层之间不能通过赋值运算符拷贝,不能通过memcpy拷贝
unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
//将内核层的数据拷贝到应用层
unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
下面是一个控制通过GPIO控制LED状态的代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
//使用自动分配设备号
#define USING_ALLOC_DEV_ID
//主设备号
#define LED_MAJOR 500
//次设备号
#define LED_MINOR 0
//设备名称
#define LED_NAME "test_led"
//总线基地址定义
#define PERIPH_BASE 0x40000000
#define MPU_AHB4_PERIPH_BASE (PERIPH_BASE + 0x10000000)
#define RCC_BASE (MPU_AHB4_PERIPH_BASE + 0x0000)
#define GPIOI_BASE (MPU_AHB4_PERIPH_BASE + 0xA000)
//RCC寄存器地址,用于使能GPIOI的时钟(RCC_BASE + 0XA28)
//RCC时钟寄存器,控制GPIOI的时钟
#define RCC_MP_AHB4ENSETR_OFFSET 0XA28
//GPIO寄存器地址,用于控制GPIO
//端口模式寄存器,配置输入、输出、复用、模拟
#define GPIOI_MODER_OFFSET 0x0000
//端口输出类型寄存器
#define GPIOI_OTYPER_OFFSET 0x0004
//端口输出速度寄存器
#define GPIOI_OSPEEDR_OFFSET 0x0008
//端口上拉/下拉寄存器
#define GPIOI_PUPDR_OFFSET 0x000C
//端口置位/复位寄存器,其中高16位用于控制置位,低16位用于控制复位
#define GPIOI_BSRR_OFFSET 0x0018
struct led_device{
//IO内存虚拟地址
void __iomem *RCC_ADDR;
void __iomem *GPIOI_ADDR;
//寄存器虚拟地址
void __iomem *RCC_MP_AHB4ENSETR;
void __iomem *GPIOI_MODER;
void __iomem *GPIOI_OTYPER;
void __iomem *GPIOI_OSPEEDR;
void __iomem *GPIOI_PUPDR;
void __iomem *GPIOI_BSRR;
//I/O内存资源,同一段IO内存只能申请一次,但是可以映射多次
struct resource *RCC_resource;
struct resource *GPIOI_resource;
//设备号
dev_t dev;
//设备对象
struct cdev cdev;
//class对象
struct class *cls;
//led状态,0灭,1亮
int led_state;
};
//led句柄
static struct led_device led;
void led_switch(struct led_device *led, uint8_t state)
{
uint32_t val = 0;
if(state)
{
//设置GPIO0为低电平,点亮led
val = readl(led->GPIOI_BSRR);
val |= (1 << 16);
writel(val, led->GPIOI_BSRR);
}
else
{
//设置GPIOI0为高电平,熄灭led
val = readl(led->GPIOI_BSRR);
val |= (1 << 0);
writel(val, led->GPIOI_BSRR);
}
}
static int led_open(struct inode *inode, struct file *file)
{
struct led_device *led;
//根据结构体成员变量的地址找到结构体变量的地址
led = container_of(inode->i_cdev, struct led_device, cdev);
file->private_data = led;
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t led_read(struct file *file, char __user *buf, size_t len, loff_t *pos)
{
char kernal_data;
struct led_device *led = file->private_data;
if(led->led_state == 0)
kernal_data = '0';
else if(led->led_state == 1)
kernal_data = '1';
else
return -EINVAL;
//将数据从内核空间拷贝到用户空间
if(copy_to_user(buf, &kernal_data, 1))
return -EFAULT;
return 1;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t len, loff_t *pos)
{
char user_data;
struct led_device *led = file->private_data;
//将数据从用户框架拷贝到内核框架
if(copy_from_user(&user_data, buf, 1))
return -EFAULT;
if(user_data == '0')
{
//熄灭led
led->led_state = 0;
led_switch(led, 0);
}
else if(user_data == '1')
{
//点亮led
led->led_state = 1;
led_switch(led, 1);
}
else
return -EINVAL;
return len;
}
//取消I/O内存映射
static void io_unmap(struct led_device *led)
{
if(led->RCC_ADDR)
{
/* 取消IO内存映射
* addr 映射后的虚拟地址
*/
iounmap(led->RCC_ADDR);
led->RCC_ADDR = NULL;
led->RCC_MP_AHB4ENSETR = NULL;
}
if(led->GPIOI_ADDR)
{
iounmap(led->GPIOI_ADDR);
led->GPIOI_ADDR = NULL;
led->GPIOI_MODER = NULL;
led->GPIOI_OTYPER = NULL;
led->GPIOI_OSPEEDR = NULL;
led->GPIOI_PUPDR = NULL;
led->GPIOI_BSRR = NULL;
}
// if(led->RCC_resource)
// {
// /* 释放I/O内存资源
// * start 物理地址起始
// * n 大小
// */
// release_mem_region(RCC_BASE, 4096);
// led->RCC_resource = NULL;
// }
// if(led->GPIOI_resource)
// {
// release_mem_region(GPIOI_BASE, 128);
// led->GPIOI_resource = NULL;
// }
}
//进行I/O内存映射
static int io_map(struct led_device *led)
{
// //在系统中I/O内存资源不可以被重复申请,因为内核中的官方驱动模块依据申请了此段IO内存资源,所以这里无法再次申请
// /* 申请IO内存资源
// * start 物理地址起始
// * n 大小
// * name 内存资源名称
// * 成功返回内存资源句柄
// */
// led->RCC_resource = request_mem_region(RCC_BASE, 4096, "RCC_BASE");
// if(!led->RCC_resource)
// {
// io_unmap(led);
// return -EIO;
// }
// led->GPIOI_resource = request_mem_region(GPIOI_BASE, 128, "GPIOI_BASE");
// if(!led->GPIOI_resource)
// {
// io_unmap(led);
// return -EIO;
// }
//在系统中I/O内存可以被重复映射
/* 映射IO寄存器
* port 寄存器物理地址
* size 映射大小
* 成功返回映射后的虚拟地址
**/
led->RCC_ADDR = ioremap(RCC_BASE, 4096);
if(!led->RCC_ADDR)
{
io_unmap(led);
return -EIO;
}
led->RCC_MP_AHB4ENSETR = led->RCC_ADDR + RCC_MP_AHB4ENSETR_OFFSET;
led->GPIOI_ADDR = ioremap(GPIOI_BASE, 128);
if(!led->GPIOI_ADDR)
{
io_unmap(led);
return -EIO;
}
led->GPIOI_MODER = led->GPIOI_ADDR + GPIOI_MODER_OFFSET;
led->GPIOI_OTYPER = led->GPIOI_ADDR + GPIOI_OTYPER_OFFSET;
led->GPIOI_OSPEEDR = led->GPIOI_ADDR + GPIOI_OSPEEDR_OFFSET;
led->GPIOI_PUPDR = led->GPIOI_ADDR + GPIOI_PUPDR_OFFSET;
led->GPIOI_BSRR = led->GPIOI_ADDR + GPIOI_BSRR_OFFSET;
return 0;
}
static void gpio_init(struct led_device *led)
{
uint32_t val;
/* 使能PI时钟 */
val = readl(led->RCC_MP_AHB4ENSETR);
val |= (0X1 << 8);
writel(val, led->RCC_MP_AHB4ENSETR);
/* 设置PI0通用的输出模式。*/
val = readl(led->GPIOI_MODER);
val &= ~(0X3 << 0);
val |= (0X1 << 0);
writel(val, led->GPIOI_MODER);
/* 设置PI0为推挽模式。*/
val = readl(led->GPIOI_OTYPER);
val &= ~(0X1 << 0);
writel(val, led->GPIOI_OTYPER);
/* 设置PI0为高速。*/
val = readl(led->GPIOI_OSPEEDR);
val &= ~(0X3 << 0);
val |= (0x2 << 0);
writel(val, led->GPIOI_OSPEEDR);
/* 设置PI0为上拉。*/
val = readl(led->GPIOI_PUPDR);
val &= ~(0X3 << 0);
val |= (0x1 << 0);
writel(val, led->GPIOI_PUPDR);
/* 默认开启LED */
led_switch(led, 1);
led->led_state = 1;
}
static void gpio_deinit(struct led_device *led)
{
uint32_t val;
/* 关闭LED */
led_switch(led, 0);
led->led_state = 0;
/* 设置PI0为悬空。*/
val = readl(led->GPIOI_PUPDR);
val &= ~(0X3 << 0);
writel(val, led->GPIOI_PUPDR);
/* 设置PI0通用的输入模式。*/
val = readl(led->GPIOI_MODER);
val &= ~(0X3 << 0);
writel(val, led->GPIOI_MODER);
}
static struct file_operations led_ops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
static int register_led(struct led_device *led)
{
int result;
struct device *device;
#ifndef USING_ALLOC_DEV_ID
//注册设备ID
led->dev = MKDEV(LED_MAJOR, LED_MINOR);
result = register_chrdev_region(led->dev, 1, LED_NAME);
if(result != 0)
return result;
#else
//动态分配设备ID
result = alloc_chrdev_region(&led->dev, LED_MINOR, 1, LED_NAME);
if(result != 0)
return result;
#endif
//初始化CDEV对象
cdev_init(&led->cdev, &led_ops);
led->cdev.owner = THIS_MODULE;
//添加cdev对象到系统
result = cdev_add(&led->cdev, led->dev, 1);
if(result != 0)
{
unregister_chrdev_region(led->dev, 1);
return result;
}
//自动创建设备文件
led->cls = class_create(THIS_MODULE, LED_NAME);
if(IS_ERR(led->cls))
{
cdev_del(&led->cdev);
unregister_chrdev_region(led->dev, 1);
return PTR_ERR(led->cls);
}
device = device_create(led->cls, NULL, led->dev, NULL, "led_test");
if(IS_ERR(device))
{
class_destroy(led->cls);
cdev_del(&led->cdev);
unregister_chrdev_region(led->dev, 1);
return PTR_ERR(device);
}
printk("MAJOR %d,MINOR %d\r\n", MAJOR(led->dev), MINOR(led->dev));
return 0;
}
static void unregister_led(struct led_device *led)
{
//删除设备文件
device_destroy(led->cls, led->dev);
class_destroy(led->cls);
//从内核删除cdev对象
cdev_del(&led->cdev);
//注销设备ID
unregister_chrdev_region(led->dev, 1);
}
static int __init led_init(void)
{
int result;
//IO内存映射
result = io_map(&led);
if(result != 0)
{
printk("map io mem failed\r\n");
return result;
}
//初始化LED设备
gpio_init(&led);
//注册led字符设备
result = register_led(&led);
if(result != 0)
{
gpio_deinit(&led);
io_unmap(&led);
printk("register led failed\r\n");
return result;
}
return 0;
}
static void __exit led_exit(void)
{
//注销led设备
unregister_led(&led);
//反初始化GPIO
gpio_deinit(&led);
//取消IO内存映射
io_unmap(&led);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("led test");