先看代码吧
#include <linux/module.h> // 固定格式 #include <linux/init.h> //固定格式 #include <linux/kernel.h> // printk() #include <linux/fs.h> //struct fops #include <linux/errno.h> // error codes #include <linux/cdev.h> // cdev_alloc() #include <asm/io.h> // ioremap() #include <linux/ioport.h> // request_mem_region() #include <asm/ioctl.h> // Linux kernel space head file for macro _IO() to generate ioctl command #ifndef __KERNEL__ #include <sys/ioctl.h> // User space head file for macro _IO() to generate ioctl command #endif #define DRV_AUTHOR "Guo Wenxue <guowenxue@gmail.com>" //作者信息 #define DRV_DESC "S3C24XX LED driver" //驱动信息 #define DEV_NAME "led" //驱动名 #define LED_NUM 4 //驱动数 #ifndef LED_MAJOR #define LED_MAJOR 0 //如果没定义主设备号。。设租设备号为0。。主设备号不可能为0。。这里是实现一个逻 辑。。下面详细说明
#endif
#define DRV_MAJOR_VER 1 //三个版本号
#define DRV_MINOR_VER 0
#define DRV_REVER_VER 0
#define DISABLE 0 //使能宏
#define ENABLE 1
#define GPIO_INPUT 0x00 //输入输出位宏。。下面说明
#define GPIO_OUTPUT 0x01
#define PLATDRV_MAGIC 0x60 //魔术字。。配合下面的定义使用。。详见ioctl解释
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
#define S3C_GPB_BASE 0x56000010 //gpb基地址及偏移地址
#define GPBCON_OFFSET 0
#define GPBDAT_OFFSET 4
#define GPBUP_OFFSET 8
#define S3C_GPB_LEN 0x10 /* 0x56000010~0x56000020 */
int led[LED_NUM] = {5,6,8,10}; /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */ //led虚拟设备号对应的物力位
static void __iomem *s3c_gpb_membase; //定义了一个__iomem *类型的变量
#define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase) //实现了虚拟地址到物理地址的读写
#define s3c_gpio_read(reg) __raw_readl((reg)+s3c_gpb_membase)
int dev_count = ARRAY_SIZE(led); //设备总数
int dev_major = LED_MAJOR; //主设备号
int dev_minor = 0; //次设备号
int debug = DISABLE;
static struct cdev *led_cdev; //定义结构体指针led_cdev
static int s3c_hw_init(void) //初始化函数
{
int i;
volatile unsigned long gpb_con, gpb_dat, gpb_up;
if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led")) //判断IO内存是否申请成功
{
return -EBUSY;
}
if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) ) //判断虚拟地址到物理地址的映射是否成功
{
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);
return -ENOMEM;
}
for(i=0; i<dev_count; i++)
{
gpb_con = s3c_gpio_read(GPBCON_OFFSET); //读要操作位出来
gpb_con &= ~(0x3<<(2*led[i])); //操作位清零
gpb_con |= GPIO_OUTPUT<<(2*led[i]); //改成output模式
s3c_gpio_write(gpb_con, GPBCON_OFFSET); //把操作好的位信息写回去
gpb_up = s3c_gpio_read(GPBUP_OFFSET);
gpb_up |= (0x1<<led[i]); //使能led对应位
s3c_gpio_write(gpb_up, GPBUP_OFFSET);
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
gpb_dat |= (0x1<<led[i]); //初始使所有led位off状态
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
return 0;
}
static void turn_led(int which, unsigned int cmd) //操作led函数
{
volatile unsigned long gpb_dat;
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET); //读led操作位出来
if(LED_ON == cmd) //如果传参是led_on。。即设置为该为为ON
{
gpb_dat &= ~(0x1<<led[which]); /* Turn LED On */
}
else if(LED_OFF == cmd) //如果传参是led_off。。即设置为该为为OFF
{
gpb_dat |= (0x1<<led[which]); /* Turn LED off */
}
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET); //把操作完毕的位传回去
}
static void s3c_hw_term(void) //清除led函数
{
int i;
volatile unsigned long gpb_dat;
for(i=0; i<dev_count; i++)
{
gpb_dat = s3c_gpio_read(GPBDAT_OFFSET); //设置每个led位off
gpb_dat |= (0x1<<led[i]);
s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
}
release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); //删除IO内存
iounmap(s3c_gpb_membase); //删除映射表
}
static int led_open(struct inode *inode, struct file *file) //led打开函数
{
int minor = iminor(inode); //从inode中独处次设备号
file->private_data = (void *)minor; //保存到一个全局变量中。。方便在其与函数中调用
printk(KERN_DEBUG "/dev/led%d opened.\n", minor);
return 0;
}
static int led_release(struct inode *inode, struct file *file) //led关闭函数
{
printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));
return 0;
}
static void print_help(void) //帮助信息
{
printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME);
printk("Turn LED on command : %u\n", LED_ON);
printk("Turn LED off command : %u\n", LED_OFF);
return;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) //ioctl函数
{
int which = (int)file->private_data; //接受次设备号
switch (cmd) //判断参数要实现的功能
{
case LED_ON: //如果是led_on。。调用turn_led函数。。并传参led_on给turn函数
turn_led(which, LED_ON); break; case LED_OFF: //同上 turn_led(which, LED_OFF); break; default: //如果传进来的参数既不是led_on也不是led_off。。则打印出错并打印help
结构体 printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd); print_help(); break; } return 0; } static struct file_operations led_fops = //fops函数 { .owner = THIS_MODULE, .open = led_open, .release = led_release, .unlocked_ioctl = led_ioctl, }; static int __init s3c_led_init(void) //init函数 { int result; dev_t devno; if( 0 != s3c_hw_init() ) //判断初始化是否成功 { printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n"); return -ENODEV; } if (0 != dev_major) //实现了一个逻辑。。如过参数中定义了主设备号。。则此时主设备号变量会
被改变。。则一定不为0.。即实现下面的静态分配设备号。。如果没给主设备
号。。则为定义的0。。函数判断为零即得知木有分配主设备号。。就会执行
自动动态分配主设备号的函数。。 { devno = MKDEV(dev_major, 0); //静态分配 result = register_chrdev_region (devno, dev_count, DEV_NAME); } else { result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME); //动态分配函数 dev_major = MAJOR(devno); } if (result < 0) //若分配失败。。打印信息 { printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major); return -ENODEV; } printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major); if(NULL == (led_cdev=cdev_alloc()) ) //若led_cdev分配不成功。。打印信息 { printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME); unregister_chrdev_region(devno, dev_count); return -ENOMEM; } led_cdev->owner = THIS_MODULE; //若分配成功。。定义参数 cdev_init(led_cdev, &led_fops); //链接led_cdev与fops result = cdev_add(led_cdev, devno, dev_count); //结果加入内存 if (0 != result) //出错打印信息 { printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result); goto ERROR; } printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n", //打印成功信息+版本号 DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER); return 0; ERROR: printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME); //加入内存出错执行程序。。按照分配的顺序相反的顺序进行释放 cdev_del(led_cdev); unregister_chrdev_region(devno, dev_count); return result; } static void __exit s3c_led_exit(void) //led退出函数 { dev_t devno = MKDEV(dev_major, dev_minor); //获得参数 s3c_hw_term(); //执行off函数 cdev_del(led_cdev); //执行撤销结构体函数 unregister_chrdev_region(devno, dev_count); //撤销 printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n", DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER); return ; } module_init(s3c_led_init); module_exit(s3c_led_exit); module_param(debug, int, S_IRUGO); module_param(dev_major, int, S_IRUGO); MODULE_AUTHOR(DRV_AUTHOR); MODULE_DESCRIPTION(DRV_DESC); MODULE_LICENSE("GPL");以上就是函数全体。。
解释一下led驱动的流程。。
首先装入过程调用module__init。。进而调用s3c_led_init函数。。这个函数会先调用s3c_hw_init初始化。。s3c_hw_init函数会先分配内存。。再映射再讲要操作的管脚设置为可操作状态并且使能他们并且将他们设置为初始状态off。。随后s3c_led_init会根据是否传入参数来判断是采用动态分配主次设备号还是静态分配主次设备号。。接下来分配结构体。。如果成功链接owner。。再将其与fops链接。。这时装入函数就完成了。。
接下来说可以实现的功能。。这里功能比较单一。。就是on和off。。这些函数作为功能函数存在于ioctl。。ioctl与装入卸载次设备号驱动以及模块信息一起装在fops中与主次设备号结构体链接在一起。。ioctl通过传参来实现不同功能
最后是撤销函数 。。
值得强调的一点是。。static void __iomem *s3c_gpb_membase;语句应该在read和write函数前面。。以及fops实现的函数应该在fops结构体前面。。。所有的函数都应该在init与exit函数前面。。防止函数不识别还要声明函数
最后。。Makefile函数应该注明开发板内核位置以及交叉编译器位置。。毕竟是要从windows上编译到开发板。。。所以要指定交叉编译器位置以及之前制作的内核位置。。