嵌入式Linux中的LED驱动控制(基于misc)
在开发驱动程序的时候,有时希望快速地实现其业务功能,以把精力放在文件接口功能的测试上来。这样,对于常见的一些繁琐而又不能省略的步骤(如设备号的申请、字符设备的注册、设备节点文件的创建等),就希望能尽量地简化。这时,就可以借用Linux内核提供的misc(杂项)来实现。
这里仍然使用“嵌入式Linux中的LED驱动控制”一文中的例子,实现三个LED的驱动,但改成使用misc来实现,其驱动程序代码如下,文件名为led.c。
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/miscdevice.h> //以下定义总线及寄存器的物理地址 #define CCM_BASE_ADDR (0x20C4000) #define CCM_CCGR1_ADDR (CCM_BASE_ADDR + 0x6C) #define CCM_CCGR2_ADDR (CCM_BASE_ADDR + 0x70) #define CCM_CCGR3_ADDR (CCM_BASE_ADDR + 0x74) #define GPIO1_BASE_ADDR (0x209C000) #define GPIO1_DR_ADDR (GPIO1_BASE_ADDR + 0x0000) #define GPIO1_GDIR_ADDR (GPIO1_BASE_ADDR + 0x0004) #define GPIO1_PSR_ADDR (GPIOB_BASE_ADDR + 0x0008) #define GPIO4_BASE_ADDR (0x20A8000) #define GPIO4_DR_ADDR (GPIO4_BASE_ADDR + 0x0000) #define GPIO4_GDIR_ADDR (GPIO4_BASE_ADDR + 0x0004) #define GPIO4_PSR_ADDR (GPIOC_BASE_ADDR + 0x0008) #define SW_MUX_CTL_ADDR (0x20E0000) #define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04_ADDR (SW_MUX_CTL_ADDR + 0x6C) #define IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC_ADDR (SW_MUX_CTL_ADDR + 0x1E0) #define IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC_ADDR (SW_MUX_CTL_ADDR + 0x1DC) #define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04_ADDR (SW_MUX_CTL_ADDR + 0x2F8) #define IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC_ADDR (SW_MUX_CTL_ADDR + 0x46C) #define IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC_ADDR (SW_MUX_CTL_ADDR + 0x468) //以下定义时钟控制寄存器名称 static void __iomem *CCM_CCGR1; static void __iomem *CCM_CCGR3; static void __iomem *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04; static void __iomem *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04; static void __iomem *IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC; static void __iomem *IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC; static void __iomem *IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC; static void __iomem *IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC; static void __iomem *GPIO1_GDIR; static void __iomem *GPIO1_DR; static void __iomem *GPIO4_GDIR; static void __iomem *GPIO4_DR; //实现open函数,为file_oprations结构体成员函数 static int led_open(struct inode *inode, struct file *filp) { unsigned int tmp; //以下使能GPIO1、GPIO4的端口时钟 tmp = ioread32(CCM_CCGR1); tmp |= 0xC000000; iowrite32(tmp, CCM_CCGR1); tmp = ioread32(CCM_CCGR3); tmp |= 0x3000; iowrite32(tmp, CCM_CCGR3); return 0; } //实现write函数,为file_oprations结构体成员函数 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { unsigned long ret; unsigned char value = 7; unsigned int tmp; ret = copy_from_user(&value, buf, cnt); //从应用空间获取值 if(ret < 0) { printk("kernel write failed!\r\n"); return -EFAULT; } switch(value) //根据应用空间的值判断具体操作 { case 0: //全部点亮三个LED tmp = ioread32(GPIO1_DR); tmp &= ~(1<<4); iowrite32(tmp, GPIO1_DR); tmp = ioread32(GPIO4_DR); tmp &= ~(1<<20 | 1<<19); iowrite32(tmp, GPIO4_DR); break; case 1: //点亮红色LED tmp = ioread32(GPIO1_DR); tmp &= ~(1<<4); iowrite32(tmp, GPIO1_DR); break; case 2: //点亮绿色LED tmp = ioread32(GPIO4_DR); tmp &= ~(1<<20); iowrite32(tmp, GPIO4_DR); break; case 3: //点亮蓝色LED tmp = ioread32(GPIO4_DR); tmp &= ~(1<<19); iowrite32(tmp, GPIO4_DR); break; case 4: //熄灭红色LED tmp = ioread32(GPIO1_DR); tmp |= (1<<4); iowrite32(tmp, GPIO1_DR); break; case 5: //熄灭绿色LED tmp = ioread32(GPIO4_DR); tmp |= (1<<20); iowrite32(tmp, GPIO4_DR); break; case 6: //熄灭蓝色LED tmp = ioread32(GPIO4_DR); tmp |= (1<<19); iowrite32(tmp, GPIO4_DR); break; case 7: //全部熄灭三个LED tmp = ioread32(GPIO1_DR); tmp |= (1<<4); iowrite32(tmp, GPIO1_DR); tmp = ioread32(GPIO4_DR); tmp |= (1<<20 | 1<<19); iowrite32(tmp, GPIO4_DR); break; default: break; } return 0; } //实现release函数,为file_oprations结构体函数 static int led_release(struct inode *inode, struct file *filp) { unsigned int tmp; //以下禁止GPIO1、GPIO4的端口时钟 tmp = ioread32(CCM_CCGR1); tmp &= ~0xC000000; iowrite32(tmp, CCM_CCGR1); tmp = ioread32(CCM_CCGR3); tmp &= ~0x3000; iowrite32(tmp, CCM_CCGR3); return 0; } //填充一个file_oprations类型的结构体,名为led_dev_fops,包含上述声明的成员函数 static struct file_operations led_dev_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, .release = led_release, }; //填充一个miscdevice类型的结构体,名为led static struct miscdevice led = { .minor = 0, //定义次设备号 .name = "led", //定义设备节点名称 .fops = &led_dev_fops, //指定file_oprations接口 }; //初始化函数,此处为驱动模块的入口函数 static int __init led_init(void) { unsigned int tmp; //以下实现各个寄存器的地址映射 CCM_CCGR1 = ioremap(CCM_CCGR1_ADDR, 4); CCM_CCGR3 = ioremap(CCM_CCGR3_ADDR, 4); IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 = ioremap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04_ADDR, 4); IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC = ioremap(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC_ADDR, 4); IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC = ioremap(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC_ADDR, 4); IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 = ioremap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04_ADDR, 4); IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC = ioremap(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC_ADDR, 4); IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC = ioremap(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC_ADDR, 4); GPIO1_GDIR = ioremap(GPIO1_GDIR_ADDR, 4); GPIO4_GDIR = ioremap(GPIO4_GDIR_ADDR, 4); GPIO1_DR = ioremap(GPIO1_DR_ADDR, 4); GPIO4_DR = ioremap(GPIO4_DR_ADDR, 4); //以下使能GPIO1、GPIO4的端口时钟 tmp = ioread32(CCM_CCGR1); tmp |= 0xC000000; iowrite32(tmp, CCM_CCGR1); tmp = ioread32(CCM_CCGR3); tmp |= 0x3000; iowrite32(tmp, CCM_CCGR3); //以下把GPIO1_4、GPIO4_19、GPIO4_20端口配置为GPIO模式 tmp = ioread32(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04); tmp = 0x05; iowrite32(tmp, IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04); tmp = ioread32(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC); tmp = 0x05; iowrite32(tmp, IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC); tmp = ioread32(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC); tmp = 0x05; iowrite32(tmp, IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC); //以下把GPIO1_4、GPIO4_19、GPIO4_20端口配置为上拉、低速、无迟滞等模式 tmp = ioread32(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04); tmp = 0xF838; iowrite32(tmp, IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04); tmp = ioread32(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC); tmp = 0xF838; iowrite32(tmp, IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC); tmp = ioread32(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC); tmp = 0xF838; iowrite32(tmp, IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC); //以下把GPIO1_4、GPIO4_19、GPIO4_20端口配置为输出 tmp = ioread32(GPIO1_GDIR); tmp |= 0x10; iowrite32(tmp, GPIO1_GDIR); tmp = ioread32(GPIO4_GDIR); tmp |= 0x180000; iowrite32(tmp, GPIO4_GDIR); //以下设定GPIO1_4、GPIO4_19、GPIO4_20端口的初始值 tmp = ioread32(GPIO1_DR); tmp |= (1<<4); iowrite32(tmp, GPIO1_DR); tmp = ioread32(GPIO4_DR); tmp |= (1<<20 | 1<<19); iowrite32(tmp, GPIO4_DR); //以下禁止GPIO1、GPIO4的端口时钟 tmp = ioread32(CCM_CCGR1); tmp &= ~0xC000000; iowrite32(tmp, CCM_CCGR1); tmp = ioread32(CCM_CCGR3); tmp &= ~0x3000; iowrite32(tmp, CCM_CCGR3); //注册misc设备 tmp = misc_register(&led); return tmp; } //退出函数,此处为驱动模块的出口函数 static void __exit led_exit(void) { //以下实现各个寄存器的解除映射 iounmap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04); iounmap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04); iounmap(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC); iounmap(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC); iounmap(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC); iounmap(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC); iounmap(GPIO1_GDIR); iounmap(GPIO1_DR); iounmap(GPIO4_GDIR); iounmap(GPIO4_DR); iounmap(CCM_CCGR1); iounmap(CCM_CCGR3); //销毁misc设备 misc_deregister(&led); } module_init(led_init); //模块入口声明 module_exit(led_exit); //模块出口声明 MODULE_LICENSE("GPL"); //GPL协议声明 MODULE_INFO(intree,"Y");
配套的Makefile文件内容如下。
KERNEL_DIR=/opt/ebf_linux_kernel/build_image/build ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- export ARCH CROSS_COMPILE obj-m := led.o all: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules clean: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
Makefile文件中的第一行指定了开发板的内核源代码位置,若放到了其他路径可自行修改。另外,该内容在复制粘贴后,需要修改命令两行(倒数第1、3行)前面的空余部分,修改为Tab键开头。
以下是测试用的应用程序代码,文件名为app.c。
#include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { int fd; unsigned char val = 0; fd = open("/dev/led", O_RDWR); //打开设备节点 if( fd < 0 ) printf("can`t open\n"); if( argc != 3 ) //命令参数不对时提示 { printf("Usage :\n"); printf("%s <all|red|green|blue> <on|off>\n", argv[0]); return 0; } if(strcmp(argv[1], "all") == 0) { if(strcmp(argv[2], "on") == 0) val = 0; //值为0时全部点亮 else val = 7; //值为7时全部熄灭 } else if(strcmp(argv[1], "red") == 0) { if(strcmp(argv[2], "on") == 0) val = 1; //值为1时红色点亮 else val = 4; //值为4时红色熄灭 } else if(strcmp(argv[1], "green") == 0) { if(strcmp(argv[2], "on") == 0) val = 2; //值为2时绿色点亮 else val = 5; //值为5时绿色熄灭 } else if(strcmp(argv[1], "blue") == 0) { if(strcmp(argv[2], "on") == 0) val = 3; //值为3时蓝色点亮 else val = 6; //值为6时蓝色熄灭 } write(fd, &val, 1); //把值写入设备节点 close(fd); //关闭设备节点 return 0; }
执行make命令编译驱动程序,成功后会生成名为led.ko的驱动模块文件。再对应用程序进行交叉编译,执行“arm-linux-gnueabihf-gcc app.c -o app”即可。实验结果与“嵌入式Linux中的LED驱动控制”一文中的完全一样,这里就不给出了。
把上面的驱动代码和“嵌入式Linux中的LED驱动控制”一文中的代码进行比较可以看出,本例的代码省略了动态申请设备号、定义字符结构体cdev、建立类、创建设备节点文件等部分,只关注了具体的控制过程。为何这些内容可以被精简?其时并没有减,而是在Linux内核中,本来就有一个名为misc的设备(杂项设备),其主设备号固定为10(可通过cat /proc/devices查看)。本例代码其实就是“借用”了这个设备名和主设备号,所以就不用再申请和注册了。misc设备有一个自己的结构体名为struct miscdevice,在它内部可通过minor成员指定次设备号(主设备号已固定为10),通过name成员指定设备节点文件的名称(设备名称已固定为misc),通过fops成员指定file_oprations结构体。一般只需要指定这三个成员变量就可以了,文件操作的接口仍然通过file_oprations结构体来指定。在驱动模块的入口函数中,在进行寄存器映射和配置之后,调用了misc_register函数,对前面定义的miscdevice结构体(misc设备)进行注册。在出口函数中,在解除寄存器映射之后,调用了misc_deregister函数,对注册的设备进行销毁。
本例代码中使用的应用程序和“嵌入式Linux中的LED驱动控制”一文中的完全一样,因为驱动的设备节点文件没变,对于驱动操作的接口(file_oprations)也没变,所以可以延用该应用程序。
可见,通过misc杂项设备的应用,可让驱动开发者专注于具体功能的实现,而忽略一些形式化的东西。当然,在开发测试完成后,最好还是回到通用的方法上来,这样比较规范和保险。

浙公网安备 33010602011771号