linux设备驱动(3)--LED字符驱动

代码学习资料来源于:

第4.1讲 Linux LED灯驱动实验(直接操作寄存器)-地址映射_哔哩哔哩_bilibili

仅用于个人学习/复习,侵联删

1、LED驱动原理

IMX6ULL系列芯片是一款基于ARM Cortex A7内核的低功耗高性能且低成本的应用处理器。

linux下任何外设驱动,最终都是要配置相应的硬件寄存器,所以本章的LED灯驱动最终也是对IMX6ULL的IO口进行配置,在linux下编写驱动要符合linux的驱动框架,IMX6U-ALPHA开发板上的LED连接到IMX6ULL的GPIO_IO3的这个引脚上。

cat /proc/devices显示所有的设备,但是操作设备的话需要控制相应的/dev目录下的设备节点

2、地址映射

linux驱动开发不能直接对寄存器地址进行读写操作,比如寄存器A物理地址为0x01010101。裸机开发可以直接对物理地址进行操作,但是linux不行,因为linux会使能MMU,MMU全称为memory manager unit,就是内存管理单元。在老版本的linux下处理器要求有MMU,但是现在linux内核已经支持无MMU的处理器了。MMU主要完成的功能如下:

1)完成虚拟空间到物理空间的映射,也称为地址映射

2)内存保护,设置存储器的保护权限

虚拟地址范围比物理地址范围大的问题不用深究。

在linux里面操作的都是虚拟地址,所以需要先得到0x01010101的虚拟地址。比如IMX6ULL的GPIO_IO03引脚的复用寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的地址为0X020E0068。如果没有开启MMU的话直接向0X020E0068这个寄存器写入数据就可以配置GPIO1_IO03的复用功能。现在开启了MMU,我们就需要先得到0X020E0068这个物理地址在linux系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要得到两个函数:ioremap和iounmap

arch/arm64/include/asm/io.h

#define ioremap(addr, size)             __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
static inline void __iomem *__ioremap(unsigned long port, unsigned long size,
                                      unsigned long flags)
{
        return ioremap(port, size);
}
参数说明:
addr:起始地址
size:要映射的内存空间大小
flags:ioremap类型,选择MT_DEVICE

返回值:__iomem类型的指针,指向映射后的虚拟地址首地址

#define iounmap                         __iounmap
extern void __iounmap(volatile void __iomem *addr);

举个这两个函数的例子:

#define SW_MUX_GPIO1_IO03_BASE 0X020E0068
static void __iomem *SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
...
iounmap(SW_MUX_GPIO1_IO03_BASE);

宏SW_MUX_GPIO1_IO03_BASE是寄存器物理地址,SW_MUX_GPIO1_IO03是映射后的虚拟地址。对于IMX6ULL的一个寄存器是4字节的,所以映射内存的长度为4,映射完成以后就可以直接对SW_MUX_GPIO1_IO03进行读写操作即可。

卸载驱动时需要iounmap释放虚拟地址映射

3、LED灯字符驱动

在STM32来对IO进行设置时,需要做以下几点:

1)使能指定GPIO的时钟

2)初始化GPIO、比如输出功能、上拉、速度等

3)STM32有的IO口可以作为其他外设的引脚,也就是IO复用,如果要将IO作为其他外设引脚使用的话就需要设置IO的复用功能。

4)最后设置GPIO输出高电平或者低电平

GPIO的各个寄存器含义和作用可参考:

IMX6ULL裸机开发之点亮LED灯_Swiler的博客-CSDN博客

GPIO框图中有8个寄存器,分别是:

Data register (GPIO_DR):数据寄存器(32位),一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应一个 GPIO。

GPIO direction register (GPIO_GDIR):方向寄存器(32位),即配置输入输出。0:输入,1:输出。

Pad sample register (GPIO_PSR):状态寄存器(32位),用于获取GPIO的高低电平值。

Interrupt control registers (GPIO_ICR1, GPIO_ICR2):中断控制寄存器,ICR1配置低16个GPIO,ICR2配置高16位个GPIO。

两个位对应四种触发状态:00:低电平触发,01:高电平触发,10:上升沿触发,11:下降沿触发。

Edge select register (GPIO_EDGE_SEL):配置边沿中断寄存器(32位):配置该寄存器会覆盖 ICR1 和 ICR2 的设置,置1则为双边沿触发。

Interrupt mask register (GPIO_IMR):中断屏蔽寄存器(32位),用于使能或禁止中断。

Interrupt status register (GPIO_ISR):中断标志位寄存器(32位),只要某个 GPIO 的中断发生,那么 ISR 中相应的位就会被置 1。通过读取这个寄存器的值可以判断中断是否产生,但是要记得在处理完中断后,要手动清除中断标志位,即向ISR中对应位写1。

点灯需要控制的寄存器如下:

#define CCM_CCGR1_BASE (0X020C406C)     // 时钟寄存器
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)     // 设置 IO 的复用功能,使其复用为 GPIO 功能
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)     // 设置 IO 的上下拉、速度等属性
#define GPIO1_DR_BASE  (0X0209C000)     // 数据寄存器(32位),一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应一个 GPIO
#define GPIO1_GDIR_BASE  (0X0209C004)   // 配置输入输出。0:输入,1:输出

4、I/O内存访问函数

使用ioremap函数将寄存器的物理地址映射到虚拟地址之后,我们就可以直接通过指针访问这些地址,但是linux不建议这么做,而是推荐使用一组操作函数来将映射后的内存进行读写操作

1)读操作函数

extern inline u8 readb(const volatile void __iomem *addr)
extern inline u16 readw(const volatile void __iomem *addr)
extern inline u32 readl(const volatile void __iomem *addr)

三个函数分别对应8bit,16bit,32bit读操作,参数addr就是要读取内存地址,返回值为读到的数据

2)写操作函数

extern inline void writeb(u8 b, volatile void __iomem *addr)
extern inline void writew(u16 b, volatile void __iomem *addr)
extern inline void writel(u32 b, volatile void __iomem *addr)

三个函数分别对应8bit,16bit,32bit写操作,参数value表示要写入的数值,addr是写入的地址

5、LED字符驱动demo

led.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched/signal.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/parport.h>
#include <linux/ctype.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/ppdev.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/compat.h>

#define LED_MAJOR 200
#define LED_NAME "led"

/* register physical address */
#define CCM_CCGR1_BASE (0X020C406C)        // 时钟寄存器
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)        // 设置 IO 的复用功能,使其复用为 GPIO 功能
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)        // 设置 IO 的上下拉、速度等属性
#define GPIO1_DR_BASE  (0X0209C000)        // 数据寄存器(32位),一个 GPIO 组最大只有 32 个 IO,因此 DR 寄存器中的每个位都对应一个 GPIO,低电平0 led亮
#define GPIO1_GDIR_BASE  (0X0209C004)        // 方向寄存器,设置为输入还输出。0:输入,1:输出

static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LEDOFF 0;        // 关闭
#define LEDON 1;        // 打开

static ssize_t led_write(struct file *file,
                                char __user *buf,
                                     size_t count, loff_t *offset)
{
        int retval;
        unsigned char databuf[1];
        retval = copy_from_user(databuf, buf, count);
        if (retval < 0) {
                printk("kernel write failed! \n");
                return -EFAULT;
        }

        if (databuf[1] == LEDOFF) {
                val = readl(GPIO1_DR);
                val |= (1 << 3);        // bit3置1,关闭led
                writel(val, GPIO1_DR);
        }

        if (databuf[1] == LEDON) {
                val = readl(GPIO1_DR);
                val &= ~(1 << 3);        // bit3置0,打开led
                writel(val, GPIO1_DR);
        }

        return 0;
}

static int led_open(struct inode *inode, struct file *file)
{
        printk("led_open success \n");
        return 0;
}

static int led_release(struct inode *inode, struct file *file)
{
        printk("led close success \n");
        return 0;
}

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

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

        /*init led, address remap*/
        IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
        SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
        SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
        GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
        GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

        val = readl(IMX6U_CCM_CCGR1);
        val &= ~(3 << 26);        // 先清除以前的配置bit26和bit27
        val |= 3 << 26;                // bit26,bit27置1
        writel(val, IMX6U_CCM_CCGR1);

        writel(0x5, SW_MUX_GPIO1_IO03);                // 设置复用
        writel(0x1080, SW_PAD_GPIO1_IO03);        // 设置电气属性

        val = readl(GPIO1_GDIR);
        val |= 1 << 3;                // bit3置1,设置为输出
        writel(val, GPIO1_GDIR);

        val = readl(GPIO1_DR);
        val &= ~(1 << 3);        // bit3清零,打开led
        writel(val, GPIO1_DR);

        ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
        if (ret < 0) {
                printk("led register failed \n");
                return -EIO;
        }

        printk("led_init \n");
        return 0;
}

static void __exit led_exit(void)
{
        int val = 0;

        iounmap(IMX6U_CCM_CCGR1);
        iounmap(SW_MUX_GPIO1_IO03);
        iounmap(SW_PAD_GPIO1_IO03);
        iounmap(GPIO1_DR);
        iounmap(GPIO1_GDIR);

        val = readl(GPIO1_DR);
        val |= (1 << 3);        // bit3置1,关闭led
        writel(val, GPIO1_DR);

        unregister_chrdev(LED_MAJOR, LED_NAME);

        printk("led_exit \n");
}

module_init(led_init);
module_exit(led_exit);

ledAPP.c

#include <sys/types.h>                                                                                                                                                                                      
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/*
 * ./ledAPP <filename> <1/2>
 * 1 读数据
 * 2 写数据
 * */ 

#define LEDOFF 0
#define LEDON 1

int main(int argc, int *argv[])
{
        char *filename;
        int fd;
        int ret;
        unsigned char databuf[1];
        
        filename = argv[1];

        if (argc < 3) {
                printf("usage error \n");
        }

        fd = open(filename, O_RDWR);
        if (fd < 0) {
                printf("open %s failed\n", filename);
                return -1;
        }

        databuf[0] = atoi(argv[2]);
        ret = write(fd, databuf, 1);            // 打开led灯
        if (ret < 0) {
                printf("LED control failed \n");
        }   

        ret = close(fd);
        if (ret < 0) {
                printf("close %s failed\n", filename);
                return -1; 
        }   

        return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_58550520/article/details/129162807