嵌入式Linux(7): 字符设备LED驱动

Linux 下 LED 灯驱动原理

I.MX6U-ALPHA 开发板上的 LED 连接到 I.MX6ULL 的 GPIO1_IO03 这个引脚上,虚拟地址(VA,Virtual Address)、物理地址(PA, Physcical Address)。对于 32 位的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 512MB 的 DDR3,这 512MB 的内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间。

Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟 地 址 。 比 如 I.MX6ULL 的 GPIO1_IO03 引 脚 的 复 用寄 存 器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为0X020E0068。如果没有开启 MMU 的话直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启了 MMU,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数: ioremap 和 iounmap。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

I/O 内存访问函数

在这里插入图片描述

实验(手动创建设备号、设备节点)

1、建立工程

在共享文件夹新建名为“2_led”文件夹,然后在 2_led 文件夹里面创建 VSCode 工程,工作区命名为“led”。
在这里插入图片描述
在 VSCode 上点击文件->打开文件夹…,选刚刚创建的“2_led”文件夹,打开可以看出此时的文件夹“2_led”是空的,点击文件->将工作区另存为…,打开工作区命名对话框,输入要保存的工作区路径和工作区名字
在这里插入图片描述
工作区保存成功以后,点击“新建文件”按钮创建led.c和ledApp.c,按下“Ctrl+Shift+P”打开搜索框,然后输入“Edit configurations”,选择“C/C++:Edit configurations…”,打开 C/C++编辑配置文件,会自动在.vscode 目录下生成一个名为 c_cpp_properties.json 的文件,修改成

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/bruce/share/linux/IMX6ULL/linux_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include",
                "/home/bruce/share/linux/IMX6ULL/linux_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include",
                "/home/bruce/share/linux/IMX6ULL/linux_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

2、编写驱动程序led.c

  1. 注册模块加载和卸载函数以及添加 LICENSE 和作者信息
  2. 驱动入口、出口函数
  3. 在驱动入口、出口函数中分别加入字符设备注册与注销
  4. 实现设备的具体操作函数open、read、write、release函数
  5. 在入口函数补充初始化LED的程序:寄存器地址映射、使能GPIO1时钟、设置 GPIO1_IO03 的复用功能GPIO1_IO03,设置 IO 属性、设置 GPIO1_IO03 为输出功能、默认关闭 LED、驱动设备程序
  6. 补充驱动涉及到的头文件、宏定义、映射后的寄存器虚拟地址、 设备操作函数
  7. 根据设备操作函数结构体完善具体操作函数open、read、write、release
  8. 根据向设备写数据write函数补充LED 打开/关闭函数
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.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 201 /*主设备号*/
#define LED_NAME "led" /*设备名字*/

#define LEDOFF 0 /*关灯*/
#define LEDON 1 /*开灯*/

/*寄存器物理地址*/
#define CCM_CCGR1_BASE         (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE          (0X0209C000) 
#define GPIO1_GDIR_BASE        (0X0209C004)

/*映射后的寄存器虚拟地址指针*/
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;

/*LED打开/关闭
LEDON(1) 打开 LED, LEDOFF(0) 关闭 LED*/
void led_switch(u8 sta)
{
    u32 val =0;
    if(sta == LEDON){
        val=readl(GPIO1_DR);
        val &= ~(1<<3);
        writel(val,GPIO1_DR);
    }else if (sta == LEDOFF)
    {
        val=readl(GPIO1_DR);
        val |=(1<<3);
        writel(val,GPIO1_DR);
    }
}

/*打开设备*/
static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

/*从设备读取
 filp : 要打开的设备文件(文件描述符)
 buf : 返回给用户空间的数据缓冲区
 cnt : 要读取的数据长度
 offt : 相对于文件首地址的偏移
*/
static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
    return 0;
}

/*向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
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、寄存器地址映射*/
    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);

    /* 2、使能GPIO1时钟*/
    val =readl(IMX6U_CCM_CCGR1);
    val &= ~(3<<26);   /*将11左移26位,即26 27位置1,再取反,再与val与运算(同1为1)*/
    val |= (3<<26);    /*或运算,有1为1,设置新值*/
    writel(val,IMX6U_CCM_CCGR1);

    /* 3、设置GPIO_IO03的复用功能,将其复用为GPIO_IO03,最后设置IO属性*/
    writel(5,SW_MUX_GPIO1_IO03);

    /*寄存器SW_PAD_GPIO1_IO03设置IO属性*/
    writel(0x10B0,SW_PAD_GPIO1_IO03);

    /* 4、设置GPIO1_IO03为输出功能*/
    val=readl(GPIO1_GDIR);
    val &= ~(1<<3);
    val |= (1<<3);
    writel(val,GPIO1_GDIR);

    /*5、默认关闭*/
    val =readl(GPIO1_DR);
    val |=(1<<3);
    writel(val,GPIO1_DR);

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

/*驱动出口函数*/
static void __exit led_exit(void)
{
    /*取消映射*/
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);
    
    /*注销字符设备驱动*/
    unregister_chrdev(LED_MAJOR,LED_NAME);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("bruce");

3、测试应用程序ledApp.c

led 驱动加载成功以后手动创建/dev/led 节点,应用 APP 通过操作/dev/led文件来完成对 LED 设备的控制。向/dev/led 文件写 0 表示关闭 LED 灯,写 1 表示打开 LED 灯。

  1. 主程序,初始化用到的变量
  2. 打开led驱动
  3. 执行打开或关闭操作
  4. 向/dev/led 文件写入数据
  5. 关闭文件
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/*----------------------------------------------------------------*/
/* 使用方法 : ./ledtest /dev/led 0 关闭 LED
              ./ledtest /dev/led 1 打开 LED*/

#define LEDOFF 0
#define LEDON 1

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

    if(argc!=3){
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /*打开led驱动*/
    fd=open(filename,O_RDWR);
    if(fd<0){
        printf("file %s open failed!\r\n",argv[1]);
        return -1;
    }

    databuf[0]=atoi(argv[2]);/*要执行的操作,打开或关闭*/

    /*向/dev/led文件写入数据*/
    retvalue=write(fd,databuf,sizeof(databuf));
    if(retvalue<0){
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }

    retvalue=close(fd);/*关闭文件*/
    if(retvalue<0){
        printf("file %s close failed!\r\n",argv[1]);
        return -1;
    }
    return 0;
}

4、编译驱动程序和编译测试应用程序

  • 编写Makefile文件
KERNELDIR:=/home/bruce/share/linux/IMX6ULL/linux_kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH:=$(shell pwd)
obj-m:=led.o

build:kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

输入命令make -j32编译出驱动模块文件:led.ko

  • 编译ledApp.c
arm-linux-gnueabihf-gcc ledApp.c -o ledApp

编译成功后会生成 ledApp 这个应用程序

  • 将上一小节编译出来的 led.ko和 ledApp这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中,重启开发板,在软件终端进入到目录 lib/modules/4.1.15 中
    • 加载led.ko驱动模块
    • 创建/dev/led设备节点
    • 测试红色 LED 灯是否点亮
    • 测试红色 LED 灯是否熄灭
      在这里插入图片描述

实验(自动申请设备号、自动创建设备节点)

1、分配和释放设备号

  • 使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会带来两个问题:
    ①、需要我们事先确定好哪些主设备号没有使用。
    ②、会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被 LED 一个设备分走了。

解决这两个问题最好的方法就是要使用设备号的时候向 Linux 内核申请,需要几个就申请几个,由 Linux 内核分配设备可以使用的设备号。

申请设备号

1 int major; /* 主设备号 */
2 int minor; /* 次设备号 */
3 dev_t devid; /* 设备号 */
4 
5 if (major) { /* 定义了主设备号 */
6 	devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择 0 */
7	 register_chrdev_region(devid, 1, "test");
8 } else { /* 没有定义设备号 */
9 	alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */
10 	major = MAJOR(devid); /* 获取分配号的主设备号 */
11 	minor = MINOR(devid); /* 获取分配号的次设备号 */
12 }

第 1~3 行,定义了主/次设备号变量 major 和 minor,以及设备号变量 devid。
第 5 行,判断主设备号 major是否有效,在 Linux 驱动中一般给出主设备号的话就表示这个设备的设备号已经确定了,因为次设备号基本上都选择 0,这算个 Linux驱动开发中约定俗 成的一种规定了。
第 6 行,如果 major 有效的话就使用 MKDEV 来构建设备号,次设备号选择 0。
第 7 行,使用 register_chrdev_region 函数来注册设备号。
第 9~11 行,如果 major无效,那就表示没有给定设备号。此时就要使用 alloc_chrdev_region 函数来申请设备号。设备号申请成功以后使用 MAJOR 和MINOR 来提取出主设备号和次设备号。

注销设备号

unregister_chrdev_region(devid, 1); /* 注销设备号 */

2、字符设备注册和删除

  • 字符设备结构
    在 Linux 中使用 cdev 结构体表示一个字符设备,cdev 结构体在 include/linux/cdev.h 文件中的定义
1 struct cdev {
2      struct kobject kobj;
3      struct module *owner;
4      const struct file_operations *ops;
5      struct list_head list;
6      dev_t dev;
7      unsigned int count;
8 };

编写字符设备驱动之前需要定义一个 cdev 结构体变量:struct cdev test_cdev;

  • cdev_init 函数
    定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化void cdev_init(struct cdev *cdev, const struct file_operations *fops)参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。

  • cdev_add 函数
    cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量):int cdev_add(struct cdev *p, dev_t dev, unsigned count)参数 p 指向要添加的字符设备(cdev 结构体变量),参数 dev 就是设备所使用的设备号,参数 count 是要添加的设备数量

注册字符设备

1 struct cdev testcdev;
2 
3 /* 设备操作函数 */
4 static struct file_operations test_fops = {
5       .owner = THIS_MODULE,
6       /* 其他具体的初始项 */
7 };
8 
9 testcdev.owner = THIS_MODULE;
10 cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */
11 cdev_add(&testcdev, devid, 1); /* 添加字符设备 */

申请设备号程序加上注册字符设备代码一起实现的就是函数 register_chrdev 的功能。

  • cdev_del 函数
    卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备,void cdev_del(struct cdev *p)参数 p 就是要删除的字符设备。

删除字符设备

cdev_del(&testcdev); /* 删除 cdev */

注销设备号代码unregister_chrdev_region和删除字符设备代码cdev_del 相当于 unregister_chrdev 函数。

3、自动创建设备节点

  • 创建和删除类
    自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。
    class_create 是类创建函数, class_create 是个宏定义
struct class *class_create (struct module *owner, const char *name)

class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。返回值是个指向结构体 class 的指针,也就是创建的类。

卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy

void class_destroy(struct class *cls);
参数 cls 就是要删除的类
  • 创建和卸载设备
    在这个类下创建一个设备。使用 device_create 函数在类下面创建设备
struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)

device_create 是个可变参数函数,参数 class 就是设备要创建哪个类下面;参数 parent 是父设备,一般为 NULL,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用的一些数据,一般为 NULL;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。

卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy

void device_destroy(struct class *class, dev_t devt)

参数 classs 是要删除的设备所处的类,参数 devt 是要删除的设备号。

在驱动入口函数里面创建类和设备,在驱动出口函数里面删除类和设备

1 struct class *class; /* 类 */
2 struct device *device; /* 设备 */
3 dev_t devid; /* 设备号 */
4 
5 /* 驱动入口函数 */
6 static int __init xxx_init(void)
7 {
8 /* 创建类 */
9 class = class_create(THIS_MODULE, "xxx");
10 /* 创建设备 */
11 device = device_create(class, NULL, devid, NULL, "xxx");
12 return 0;
13 }
14
15 /* 驱动出口函数 */
16 static void __exit led_exit(void)
17 {
18 /* 删除设备 */
19 device_destroy(newchrled.class, newchrled.devid);
20 /* 删除类 */
21 class_destroy(newchrled.class);
22 }
23
24 module_init(led_init);
25 module_exit(led_exit);

4、设置文件私有数据

每个硬件设备都有一些属性,比如主设备号(dev_t),类(class)、设备(device)、开关状态(state)等等,在编写驱动的时候你可以将这些属性全部写成变量的形式。

编写驱动 open 函数的时候将设备结构体作为私有数据添加到设备文件中:

/* 设备结构体 */
1 struct test_dev{
2 		dev_t devid; /* 设备号 */
3		struct cdev cdev; /* cdev */
4 		struct class *class; /* 类 */
5 		struct device *device; /* 设备 */
6 		int major; /* 主设备号 */
7 		int minor; /* 次设备号 */
8 };
9
10 struct test_dev testdev;
11
12 /* open 函数 */
13 static int test_open(struct inode *inode, struct file *filp)
14 {
15 		filp->private_data = &testdev; /* 设置私有数据 */
16 		return 0;
17 }

在 open 函数里面设置好私有数据以后,在 write、 read、 close 等函数中直接读取 private_data即可得到设备结构体。

5、程序编写

在2_led基础上进行修改,复制2_led文件夹改名3_newchrled,
在这里插入图片描述
将工程中的文件名修改,并赋予权限sudo chmod 777 xxx
在这里插入图片描述

驱动程序编写

将led.c中的注册和注销字符设备驱动的代码修改

驱动入口函数

  • 创建设备号
  • 初始化cdev字符设备
  • 添加一个字符设备cdev
  • 创建类
  • 创建设备

驱动出口函数

  • 删除字符设备和设备号
  • 删除设备和类

创建设备结构并定义

struct newchrled_dev{
    dev_t devid;  /* 设备号 */
    struct cdev cdev;
    struct class *class;/* 类 */
    struct device *device;/*设备*/
    int major;
    int minor;
};

struct newchrled_dev newchrled;

在 open 的时候将 private_data 指向设备结构体

static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &newchrled; /* 设置私有数据 */
    return 0;
}

newchrled.c代码段

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

#define NEWCHRLED_CNT 1  /*设备号个数*/
#define NEWCHRLED_NAME "newchrled" /*设备名字*/
#define LEDOFF 0 /*关灯*/
#define LEDON 1 /*开灯*/

/*寄存器物理地址*/
#define CCM_CCGR1_BASE         (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE          (0X0209C000) 
#define GPIO1_GDIR_BASE        (0X0209C004)

/*映射后的寄存器虚拟地址指针*/
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;

/*设备结构体*/
struct newchrled_dev{
    dev_t devid;  /* 设备号 */
    struct cdev cdev;
    struct class *class;/* 类 */
    struct device *device;/*设备*/
    int major;
    int minor;
};

/*led设备*/
struct newchrled_dev newchrled;

/*LED打开/关闭
LEDON(1) 打开 LED, LEDOFF(0) 关闭 LED*/
void led_switch(u8 sta)
{
    u32 val =0;
    if(sta == LEDON){
        val=readl(GPIO1_DR);
        val &= ~(1<<3);
        writel(val,GPIO1_DR);
    }else if (sta == LEDOFF)
    {
        val=readl(GPIO1_DR);
        val |=(1<<3);
        writel(val,GPIO1_DR);
    }
}

/*打开设备*/
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &newchrled; /* 设置私有数据 */
    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 newchrled_fops={
    .owner	= THIS_MODULE,
    .open	= led_open,
    .read   = led_read,
    .write  = led_write,
    .release = led_release,
};

/*驱动入口函数*/
static int __init led_init(void)
{
    u32 val = 0;

    /*初始化LED*/
    /* 1、寄存器地址映射*/
    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);

    /* 2、使能GPIO1时钟*/
    val =readl(IMX6U_CCM_CCGR1);
    val &= ~(3<<26);   /*将11左移26位,即26 27位置1,再取反,再与val与运算(同1为1)*/
    val |= (3<<26);    /*或运算,有1为1,设置新值*/
    writel(val,IMX6U_CCM_CCGR1);

    /* 3、设置GPIO_IO03的复用功能,将其复用为GPIO_IO03,最后设置IO属性*/
    writel(5,SW_MUX_GPIO1_IO03);

    /*寄存器SW_PAD_GPIO1_IO03设置IO属性*/
    writel(0x10B0,SW_PAD_GPIO1_IO03);

    /* 4、设置GPIO1_IO03为输出功能*/
    val=readl(GPIO1_GDIR);
    val &= ~(1<<3);
    val |= (1<<3);
    writel(val,GPIO1_GDIR);

    /*5、默认关闭*/
    val =readl(GPIO1_DR);
    val |=(1<<3);
    writel(val,GPIO1_DR);

    /*注册字符设备驱动*/
    /*01\创建设备号*/
    
/*    retvalue =register_chrdev(LED_MAJOR,LED_NAME,&led_fops);
    if(retvalue < 0){
        printk("register chrdev failed!\r\n");
        return -EIO;
    }*/

    if(newchrled.major){          /* 定义了设备号 */
       newchrled.devid = MKDEV(newchrled.major, 0); 
       register_chrdev_region(newchrled.devid,NEWCHRLED_CNT,NEWCHRLED_NAME);
    }else{                       /*没有定义设备号 */
       alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_CNT,NEWCHRLED_NAME);
       newchrled.major=MAJOR(newchrled.devid);
       newchrled.minor=MINOR(newchrled.devid);
    }
    printk("newchrled major=%d,minor=%d\r\n",newchrled.major,newchrled.minor);

    /*02\初始化cdev*/
    newchrled.cdev.owner=THIS_MODULE;
    cdev_init(&newchrled.cdev,&newchrled_fops);

    /*03\添加一个cdev*/
    cdev_add(&newchrled.cdev,newchrled.devid,NEWCHRLED_CNT);
    
    /*04\创建类*/
    newchrled.class=class_create(THIS_MODULE,NEWCHRLED_NAME);
    if(IS_ERR(newchrled.class)){
        return PTR_ERR(newchrled.class);
    }

    /*05\创建设备*/
    newchrled.device=device_create(newchrled.class,NULL,newchrled.devid,NULL,NEWCHRLED_NAME);
    if(IS_ERR(newchrled.device)){
        return PTR_ERR(newchrled.device);
    }

    return 0;
}

/*驱动出口函数*/
static void __exit led_exit(void)
{
    /*取消映射*/
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);
    
    /*注销字符设备驱动*/
    //unregister_chrdev(LED_MAJOR,LED_NAME);

    /*删除字符设备和设备号*/
    cdev_del(&newchrled.cdev);
    unregister_chrdev_region(newchrled.devid,NEWCHRLED_CNT);

    /*删除设备和类*/
    device_destroy(newchrled.class,newchrled.devid);
    class_destroy(newchrled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bruce");

newchrledApp.c与ledApp.c代码一样,Makefile中设置 obj-m 变量的值为 newchrled.o

运行测试

  • 在linux终端输入命令make -j32编译出驱动模块文件:newchrled.ko

  • 编译lednewchrledApp.c

arm-linux-gnueabihf-gcc lednewchrledApp.c -o lednewchrledApp

编译成功后会生成newchrledApp 这个应用程序

  • 将上一小节编译出来的 newchrled.ko和 newchrledApp这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中,重启开发板,在软件终端进入到目录 lib/modules/4.1.15 中
    • 加载led.ko驱动模块insmod newchrled.ko
      驱动加载成功以后会输出申请到的主设备号和次设备号(自动申请,不需要手动)
    • 驱动加载成功以后会自动在/dev 目录下创建设备节点文件/dev/newchrled,输入如下命令查看/dev/newchrled 这个设备节点文件是否存在:ls /dev/newchrled -l
    • 测试红色 LED 灯是否点亮./newchrledApp /dev/newchrled 1
    • 测试红色 LED 灯是否熄灭./newchrledApp /dev/newchrled 0

在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值