转载请注明出处:http://blog.csdn.net/ruoyunliufeng/article/details/38515205
之前一直在考虑该不该写这篇,由于我之前在博客里有写过LED的驱动,可是没有具体的解说。后来本着叫大家都能看懂驱动的想法,我还是决定要写一下。我想通过LED的驱动,让不了解驱动的小伙伴。可以有一个感性的认识。
一.代码
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <linux/device.h> #include <mach/gpio.h> static struct class *wvm_led_class; static int major; volatile unsigned long *gpbcon = NULL; volatile unsigned long *gpbdat = NULL; static int wvm_led_drv_open(struct inode *inode, struct file *file) { /* * LED1,LED2,LED4相应GPB5、GPB6、GPB7、GPB8 */ /* 配置GPB5,6,7,8为输出 */ *gpbcon &= ~((0x3<<(5*2)) | (0x3<<(6*2)) | (0x3<<(7*2)) | (0x3<<(8*2))); *gpbcon |= ((0x1<<(5*2)) | (0x1<<(6*2)) | (0x1<<(7*2)) | (0x1<<(8*2))); return 0; } static int wvm_led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val,ret; ret=copy_from_user(&val, buf, count); // copy_to_user(); if (ret) return -EAGAIN; if (val == 1) { // 点灯 *gpbdat &= ~((1<<5) | (1<<6) | (1<<7) | (1<<8)); } else { // 灭灯 *gpbdat |= (1<<5) | (1<<6) | (1<<7) | (1<<8); } return 0; } static struct file_operations wvm_led_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自己主动创建的__this_module变量 */ .open = wvm_led_drv_open, .write = wvm_led_drv_write, }; static int wvm_led_drv_init(void) //入口函数(做初始化,创建设备等工作) { major = register_chrdev(0, "wvm_led_drv", &wvm_led_drv_fops); // 注冊, 告诉内核 if(major < 0) { printk( " wvm_led_drv register falid!/n"); return major; } wvm_led_class = class_create(THIS_MODULE, "wvm_led"); if(IS_ERR(wvm_led_class)) { printk( " wvm_led_drv register class falid!/n"); return -1; } device_create(wvm_led_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */ gpbcon = (volatile unsigned long *)ioremap(0x56000010, 16); gpbdat = gpbcon + 1; return 0; } static void wvm_led_drv_exit(void) //出口函数(做卸载和销毁工作) { unregister_chrdev(major, "wvm_led_drv"); // 卸载 device_destroy(wvm_led_class, MKDEV(major, 0)); class_destroy(wvm_led_class); iounmap(gpbcon); } module_init(wvm_led_drv_init); //定义入口函数 module_exit(wvm_led_drv_exit); //定义出口函数 MODULE_LICENSE("GPL");
二.驱动结构
正所谓麻雀虽小五脏俱全,它包含了一个驱动的基本功能。以下我写一个类似于模板的东西给大家。
//头文件
...
//定义一些变量和结构体等
...
//各种操作函数
xx_open()
{
.....
}
xx_close()
{
.....
xx_ioctl()
{
.....
...
//file_operations结构体
static struct file_operations XXX_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏。推向编译模块时自己主动创建的__this_module变量 */
.open = XXX_open, //后面的名字要与操作函数一致
.close = XXX_close,
.ioctl
= XXX_ioctl,
};
//入口函数
static int XXX_init(void)
{
主要做创建设备等初始化工作。參照前面驱动(要推断返回值)。
}
//出口函数
static voidXXX_exit(void)
{
主要卸载创建的设备。做一些清理工作。參考前面的驱动去写
}
//入口、出口、证书的声明
module_init(XXX_init);
module_exit(XXX_exit);
MODULE_LICENSE("GPL");
三.应用測试
应用就是简单的測试一下开灯和关灯
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> /* led_test on 开灯 * led_test off 关灯 */ int main(int argc, char **argv) { int fd; int val = 1; fd = open("/dev/led", O_RDWR); if (fd < 0) { printf("can't open! "); } if (argc != 2) { printf("Usage : "); printf("%s <on|off> ", argv[0]); return 0; } if (strcmp(argv[1], "on") == 0) { val = 1; } else { val = 0; } write(fd, &val, 4); return 0; }
看到这也许有的小伙伴们已经买白了驱动怎么写,可是灯怎么亮的呢?
应用程序:write()----->驱动程序:write()
细心的小伙伴已经注意到驱动中的write()中有ret=copy_from_user(&val, buf, count);
然后就能够開始运行亮灯和灭灯了
两步搞定亮灭灯:
因为一个引脚可能同意有不同的功能,所以引脚的寄存器分为两类。一类为控制寄存器。一类数据寄存器
要操作某个引脚先设置控制寄存器(配置为某种功能)。然后设置数据寄存器(实现功能)
*gpbcon &= ~((0x3<<(5*2)) | (0x3<<(6*2)) | (0x3<<(7*2)) | (0x3<<(8*2))); //清零
*gpbcon |= ((0x1<<(5*2)) | (0x1<<(6*2)) | (0x1<<(7*2)) | (0x1<<(8*2))); //配置为输出引脚
// 点灯
*gpbdat &= ~((1<<5) | (1<<6) | (1<<7) | (1<<8));
// 灭灯
*gpbdat |= (1<<5) | (1<<6) | (1<<7) | (1<<8);
这里面要注意一点,在曾经单片机敲代码的的时候我们能够直接操作物理地址,可是如今驱动要操作虚拟地址。
所以我们要做一个映射
gpbcon = (volatile unsigned long *)ioremap(0x56000010, 16); //物理地址0x56000010 ,映射长度16字节
gpbdat = gpbcon + 1;