第一次玩驱动,就来分析老师给的源码
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "led.h"
/* led对应的GPIOM虚拟地址*/
unsigned long GPIOM_VA_BASE;
/*led对应GPIOM各寄存器虚拟地址*/
#define GPIOM_CON_VA GPIOM_VA_BASE
#define GPIOM_DAT_VA GPIOM_VA_BASE+0x4
#define GPIOM_PUD_VA GPIOM_VA_BASE+0x8
/*led对应GPIOM物理地址*/
#define GPIOM_PA_BASE 0x7f008820
struct resource ok6410_led_resource = {
.name = "led io-mem",
.start = GPIOM_PA_BASE,
.end = GPIOM_PA_BASE + 0xc,
.flags = IORESOURCE_MEM,
};
/* 完成申请、映射,映射获得的虚拟地址由全局变量GPIOM_VA_BASE获得。
同时,完成GPIOM的配置寄存器进行初始化,设置led对应端口为输出 */
static void ok6410_led_pin_setup(void)
{
//这里获得内存的起始地址与大小
unsigned long start = ok6410_led_resource.start;
unsigned long size = ok6410_led_resource.end - start;
unsigned long tmp;
/* 申请IO内存*/
request_mem_region(start, size, ok6410_led_resource.name);
/* 映射io内存*/
GPIOM_VA_BASE = (unsigned long)ioremap(start, size);
printk ("<1> [GPIOM_VA_BASE = 0x%lx]\n", GPIOM_VA_BASE);
/*对应管脚设置为输出*/
tmp = readl(GPIOM_CON_VA);
tmp = (tmp & ~(0xffffU))|(0x1111U);
writel(tmp, GPIOM_CON_VA);
/*对应管脚置高,使led全灭*/
tmp = readl(GPIOM_DAT_VA);
tmp |= 0xF;
writel(tmp, GPIOM_DAT_VA);
}
/* led端口释放函数,进行申请和映射的逆过程*/
static void ok6410_led_pin_release(void)
{
iounmap((void*)GPIOM_VA_BASE);
release_mem_region(ok6410_led_resource.start,
ok6410_led_resource.end - ok6410_led_resource.start);
}
/* 读写led对应的GPIO数据寄存器值,writel(),readl()函数分别
读写对应寄存器,也就是在访问映射过来的IO内存 */
static unsigned long ok6410_led_getdat(void)
{
return (readl(GPIOM_DAT_VA)& 0xF);
}
static void ok6410_led_setdat(int dat)
{
unsigned long tmp;
tmp = readl(GPIOM_DAT_VA);
tmp = (tmp & ~0xF) | (dat&0xF);
writel(tmp, GPIOM_DAT_VA);
}
/* 实现led设备的ioctl操作,对于命令LED_IOCGETDAT,
调用函数ok6410_led_getdat()
获得led对应端口寄存器的值,并通过put_user函数将它传回用户空间。
对于命令LED_IOCSETDAT,则将用户空间传过来的值,
通过函数ok6410_led_setdat()设置对应的寄存器。*/
static long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ioarg, ret;
/* 检测命令的有效性*/
if (_IOC_TYPE(cmd) != LED_IOC_MAGIC)
return -EINVAL;
if (_IOC_NR(cmd) > LED_IOC_MAXNR)
return -EINVAL;
/* 根据命令,执行相应的操作*/
switch(cmd) {
case LED_IOCGETDAT:
ioarg = ok6410_led_getdat();
ret = put_user(ioarg, (int *)arg);
break;
case LED_IOCSETDAT:
ret = get_user(ioarg, (int *)arg);
ok6410_led_setdat(ioarg);
break;
default:
return -EINVAL;
}
return ret;
}
/* 将该设备作为混杂设备方式注册,因此主次设备号都不需要考虑,
而只需要实现miscdevice结构,
以及在设备初始化函数dev_init()中调用misc_register()函数注册即可 */
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl= led_ioctl,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
/* 将MISC_DYNAMIC_MINOR赋值给miscdevice结构的minor成员,
表示自动分配次设备号,
在使用misc_register()注册混杂设备后,
还会在/dev目录下自动创建设备节点,节点名称由
设备名称DEVICE_NAME指定。在模块初始化函数中,
我们还需要对设备进行必要的初始化。
这里设计一个用于初始化设备的函数接口ok6410_led_pin_setup(),
后面再来实现它。
模块卸载函数完成与初始化函数相反的工作。*/
static int __init dev_init(void)
{
int ret;
ok6410_led_pin_setup();
ret = misc_register(&misc);
printk (DEVICE_NAME" initialized minor=%d\n", misc.minor);
return ret;
}
static void __exit dev_exit(void)
{
ok6410_led_pin_release();
misc_deregister(&misc);
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.enjoylinux.cn");