/*所需的头文件定义*/
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/ioctl.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <asm/atomic.h>
#include <asm/irq.h>
#include <asm/unistd.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
/*定义LED驱动的设备名"*/
#define DEVICE_NAME "leds"

/*定义LED对应的GPIO端口列表,对应定义设备号0~3*/
static unsigned long leds_table[] = {
	S3C2410_GPB(5),
	S3C2410_GPB(6),
	S3C2410_GPB(7),
	S3C2410_GPB(8),
};

/*定义LED对应端口将要输出的状态列表*/
static unsigned int leds_cfg_table[]=
{
	S3C2410_GPIO_OUTPUT,
	S3C2410_GPIO_OUTPUT,
	S3C2410_GPIO_OUTPUT,
	S3C2410_GPIO_OUTPUT,
};

/*ioctl 函数的实现
* 在应用/用户层将通过ioctl 函数向内核传递参数,以控制LED的输出状态
*ioctl(fd, on, led_number);
*inode和file指针是对应应用程序传递的文件描述符fd的值,
*和传递给 open 方法的相同参数
*/
static int leds_ioctl(
	struct inode *inode,
	struct file  *file,
	/*cmd为0接受为0则灯灭,因为下面取值为!cmd,s3c2440定义GPIO输出为低电平时有效*/
	unsigned int cmd,
	/*arg为输入的自定义设备号,设备号为0~3*/
	unsigned long arg)
{
	switch(cmd){
		case 0:
		case 1:
			if (arg > 4){
				return -EINVAL;
			}
			/*s3c2440定义GPIO输出为低电平时有效,这里cmd取反值
			*通过s3c2410_gpio_setpin()来做,此函数为驱动函数的核心操作*/
			s3c2410_gpio_setpin(leds_table[arg],!cmd);
			return 0;
		default:
			return -EINVAL;
	}
}

/*
* 设备函数操作集,在此只有ioctl 函数,通常还有read,write,open,close等,因为本LED驱动在下面已经
* 注册为misc设备,因此也可以不用open/close
*/
static struct file_operations leds_fops=
{
	/*.owner只有在编译为模块的时候才有实际用处.*/
	.owner = THIS_MODULE,
	.ioctl = leds_ioctl,
};

/*
*把LED 驱动注册为MISC设备
*Minor为指定次设备号,等于MISC_DYNAMIC_MINOR表示为动态获取次设备号.
*Name为设备名.
*Fops为file_operations结构,设备的操作函数指针集合.
*/
static struct miscdevice leds=
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &leds_fops,
};

/*
*模块初始化
*GPIO口的设置:
*S3C2440设置GPIO口主要是设置3个寄存器.分别为GPxCON,GPxDAT,GPxUP(x=A….J),
*GPxCON用于设置IO口的功能,每两位对应一个IO口,输入=00,输出=01;特殊功能=10;
*GPxDAT为IO口的数据寄存器,每一位对应一个IO口.GPxUP为是否使用上拉电阻,0为使用,
*注意的是并不是每组IO口都有内部集成上拉电阻,如果没有集成,则没该寄存器.
*设置这三个寄存器相对应的函数为:s3c2410_gpio_cfgpin,s3c2410_gpio_setpin.
*/
static int __init leds_init(void)
{
	int ret;
	int i;
	/*下面为设置LED初始化后4个LED全处于发光状态*/
	for (i=1; i<4; i++)
	{
		/*设置4个LED对应的端口寄存器为输出(OUTPUT)*/
		s3c2410_gpio_cfgpin(leds_table[i],leds_cfg_table[i]);
		/*设置LED 对应的端口寄存器为低电平输出,
		在模块加载结束后,四个LED 应该是全部都是发光状态*/
		s3c2410_gpio_setpin(leds_table[i],0);
	};
	/*
	*misc设备注册
	*非标准设备使用misc_register,
	*即一些字符设备不符合预先确定的字符设备范畴,
	*这些设备就用主编号10一起归于"其他类型",
	*misc_register()用主编号10调用register_chrdev(),
	*设备名称和函数表指针通过miscdevice数据结构获得.
	*同样,miscdevice数据结构还保存设备驱动程序所使用的次要号码.
	*/    
	ret = misc_register(&leds);
	printk (DEVICE_NAME"\tis initialized\n");
	return ret;
}

/*设备卸载*/
static void __exit leds_exit(void)
{
	/*
	*misc(混合,其他类型,不能严格划分的设备类型)
	*类设备的注销函数,成功返回为0,错误返回一个错误代码
	*/
	misc_deregister(&leds);
}

/*模块初始化,仅当使用insmod/podprobe命令加载时有用,
如果设备不是通过模块方式加载,此处将不会被调用*/
module_init(leds_init);
/*卸载模块,当该设备通过模块方式加载后,
可以通过rmmod 命令卸载,将调用此函数*/
module_exit(leds_exit);