字符设备驱动用的fileopretion结构体。



1、板载蜂鸣器的驱动测试


    我手里有一个BSP,九鼎的Bsp,里面有蜂鸣器的驱动,我们先测试一下好不好用。我们拿到一个BSP时,如果要做或移植蜂鸣器的驱动,首先要确定下这个内核

中究竟有没有蜂鸣器的驱动,我们可以用sourceInsight将内核放进去,搜索buzzer这个文件,看有没有,如果不行,也可以在内核中输入make menuconfig,利用这个配置界面来搜索buzzer英文,看不能找到相应的信息,从而也会知道这个设备在哪个路径下,通过对九鼎的内核进行make menuconfig后,搜索buzzer后,知道buzzer的驱动在/driver/char/buzzer/目录下,去这个目录中看,发现了x210-buzzer.c这么一个文件还有makefile和kconfig,说明这个文件就是驱动文件,这样我们就找到驱动代码,同时也说明在这个内核中是有蜂鸣器的驱动代码的。你还可以通过在内核源码目录下,通过输入grep "buzzer" * -nR的方式进行搜索含有buzzer字样的位置,从而确定你的板子的蜂鸣器驱动是哪个,或者有没有。


    我们九鼎内核中的蜂鸣器的驱动源代码在/driver/char/buzzer/x210-buzzer.c中。这个驱动有没有工作,或者被编译到内核中,那就要取决于这个目录下的makefile文件中

obj-$(CONFIG_BUZZER_DRIVER) += x210-buzzer.o

CONFIG_BUZZER_DRIVER宏是否定义了,这个宏是否定义就要取决于这个目录下的kconfig文件中

config X210_BUZZER_DRIVER
	bool "x210 buzzer driver"
	default y
	help
	compile for buzzer driver,y for kernel,m for module.

给的值到底是y还是n了。这个kconfig的给的这个宏的值是y还是n,取决于make menuconfig中,你是否选择了这个蜂鸣器驱动。你也可以在内核源码目录下的.config文件中看这个CONFIG_BUZZER_DRIVER宏的值是否为y来确定是非让其编译到内核中。

蜂鸣器这个设备应该是属于misc设备的,所以按道理来说make menuconfig时应该是在misc设备中去找的,但是因为九鼎移植的时候很乱,并没有将蜂鸣器的驱动放在misc设备目录中,而是放在了char目录下,所以make menuconfig时我们要在char类型的设备下找这个蜂鸣器设备的驱动,看是否被使能了,如果使能了说明那个CONFIG_BUZZER_DRIVER宏就是被使能的了,我们也可以在源码目录下的.config文件中观察确认。

    因为九鼎内核中已经提供了蜂鸣器的源码驱动,我们在make menuconfig之后在char类型的设备驱动中找到了x210 buzzer drvier选项,这个蜂鸣器的驱动,使能后重新编译内核,此时内核中就会有蜂鸣器的驱动了。

    misc设备杂散类设备,驱动加载成功后,会在系统/dev目录下创建出一个设备节点文件出来,从而进行操作,但是我们系统启动后,在/dev目录下并没有看到buzzer这个设备节点文件,这是因为九鼎提供的蜂鸣器驱动有一个bug,这个bug就是蜂鸣器驱动源代码所在的目录,也就是/driver/char/buzzer/目录,这个目录里面的makefile的obj后面的宏是CONFIG_BUZZER_DRIVER,但是kconfig文件中的config名字叫做X210_BUZZER_DRIVER,全名为CONFIG_X210_BUZZER_DRVIER,在我们make menuconfig的时候已经将蜂鸣器驱动选上了,在.config文件中确实能够看到这个宏已经为y了,宏的名字叫做CONFIG_X210_BUZZER_DRIVER,但是makefile中obj后面的名字叫做CONFIG_BUZZER_DRVIER,这是不对的,makefile中的宏名也应该叫做CONFIG_X210_BUZZER_DRIVER,这样才能真正的编译进行去。修改的方法很简单,就是进去到源码的/drvier/char/buzzer目录下,将makefile中obj后面的宏名改为CONFIG_X210_BUZZER_DRIVER,这样就和.config文件中的蜂鸣器驱动使能的宏名一样了,此时makefiel才会将蜂鸣器驱动真正的编译到内核中。

    此时系统在启动后,就会在/dev目录下看到buzzer这个设备驱动文件节点。此时就可以正常使用这个设备文件来操作蜂鸣器驱动了。这个时候我们就可以写应用程序控制蜂鸣器,来验证蜂鸣器驱动是否好用了。要想写应用程序,就要知道驱动是怎么实现的,应用程序和驱动是配套的,驱动中提供了哪些应用程序来操作蜂鸣器的接口程序,所以此时我们要看下蜂鸣器驱动代码是怎么实现的,从而知道驱动提供了哪些接口函数给应用层。蜂鸣器的驱动代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <plat/regs-timer.h>
#include <mach/regs-irq.h>
#include <asm/mach/time.h>
#include <linux/clk.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/miscdevice.h>

#include <linux/gpio.h>

#include <plat/gpio-cfg.h>
//#include <plat/regs-clock.h>
//#include <plat/regs-gpio.h>

//#include <plat/gpio-bank-e.h>
//#include <plat/gpio-bank-f.h>
//#include <plat/gpio-bank-k.h>

#define DEVICE_NAME     "buzzer"

#define PWM_IOCTL_SET_FREQ		1
#define PWM_IOCTL_STOP			0

static struct semaphore lock;

// TCFG0在Uboot中设置,这里不再重复设置
// Timer0输入频率Finput=pclk/(prescaler1+1)/MUX1
//                     =66M/16/16
// TCFG0 = tcnt = (pclk/16/16)/freq;
// PWM0输出频率Foutput =Finput/TCFG0= freq
static void PWM_Set_Freq( unsigned long freq )
{
	unsigned long tcon;
	unsigned long tcnt;
	unsigned long tcfg1;

	struct clk *clk_p;
	unsigned long pclk;

	//unsigned tmp;
	
	//设置GPD0_2为PWM输出
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(2));

	tcon = __raw_readl(S3C2410_TCON);
	tcfg1 = __raw_readl(S3C2410_TCFG1);

	//mux = 1/16
	tcfg1 &= ~(0xf<<8);
	tcfg1 |= (0x4<<8);
	__raw_writel(tcfg1, S3C2410_TCFG1);
	
	clk_p = clk_get(NULL, "pclk");
	pclk  = clk_get_rate(clk_p);

	tcnt  = (pclk/16/16)/freq;
	
	__raw_writel(tcnt, S3C2410_TCNTB(2));
	__raw_writel(tcnt/2, S3C2410_TCMPB(2));//占空比为50%

	tcon &= ~(0xf<<12);
	tcon |= (0xb<<12);		//disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0
	__raw_writel(tcon, S3C2410_TCON);
	
	tcon &= ~(2<<12);			//clear manual update bit
	__raw_writel(tcon, S3C2410_TCON);
}

void PWM_Stop( void )
{
	//将GPD0_2设置为input
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(0));	
}

static int x210_pwm_open(struct inode *inode, struct file *file)
{
	if (!down_trylock(&lock))
		return 0;
	else
		return -EBUSY;
	
}


static int x210_pwm_close(struct inode *inode, struct file *file)
{
	up(&lock);
	return 0;
}

// PWM:GPF14->PWM0
static int x210_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
	switch (cmd) 
	{
		case PWM_IOCTL_SET_FREQ:
			printk("PWM_IOCTL_SET_FREQ:\r\n");
			if (arg == 0)
				return -EINVAL;
			PWM_Set_Freq(arg);
			break;

		case PWM_IOCTL_STOP:
		default:
			printk("PWM_IOCTL_STOP:\r\n");
			PWM_Stop();
			break;
	}

	return 0;
}


static struct file_operations dev_fops = {
    .owner   =   THIS_MODULE,
    .open    =   x210_pwm_open,
    .release =   x210_pwm_close, 
    .ioctl   =   x210_pwm_ioctl,
};

static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &dev_fops,
};

static int __init dev_init(void)
{
	int ret;

	init_MUTEX(&lock);
	ret = misc_register(&misc);
	
	/* GPD0_2 (PWMTOUT2) */
	ret = gpio_request(S5PV210_GPD0(2), "GPD0");
	if(ret)
		printk("buzzer-x210: request gpio GPD0(2) fail");
		
	s3c_gpio_setpull(S5PV210_GPD0(2), S3C_GPIO_PULL_UP);
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(1));
	gpio_set_value(S5PV210_GPD0(2), 0);

	printk ("x210 "DEVICE_NAME" initialized\n");
    	return ret;
}

static void __exit dev_exit(void)
{
	misc_deregister(&misc);
}

module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.9tripod.com");
MODULE_DESCRIPTION("x210 PWM Driver");

我们观察到这个misc设备蜂鸣器设备的驱动用的是file_opreations结构体,这个结构体里面绑定了几个成员函数,就是对应的操作蜂鸣器驱动的方法,其中open和close函数没有什么内容,就是上锁和解锁操作。在file_opreation结构体中成员中,还有一个成员ioctl,绑定的是x210_pwm_ioctl函数,观察代码可以知道,这个蜂鸣器驱动是使用x210_pwm_ioctl函数来操作蜂鸣器设备的,既然这个函数被绑定到了ioctl中,那么应用程序就是使用ioctl这个函数来进行操作蜂鸣器的。

    我们可以在linux系统中用man 3 ioctl来知道ioctl函数有几个参数,第一个参数是文件描述符,第二个参数是命令码,之后就是arg。

    在驱动中也可以看出和ioctl绑定的x210_pwm_ioctl函数的参数第一个是文件节点,就是文件描述符。在x210_pwm_iotcl函数的代码,可以看出来cmd参数就是命令码,可以看出PWM_IOCTL_SET_FREQ宏和PWM_IOCTL_STOP宏就是操作蜂鸣器设备文件的命令码,看代码知道第一个命令码宏是打开蜂鸣器并且设置其频率,第二个命令码宏是关闭蜂鸣器,第一个命令码宏需要再带参数arg,带的参数rag就是频率,第二个命令码宏不需要再带参数arg。

    既然已经知道了应用程序操作蜂鸣器设备的方法是使用ioctl这个函数,第一个参数是设备文件的描述符,第二个参数是命令码,第三个参数是arg参数,在命令码为PWM_IOCTL_SET_FREQ宏时,代表打开蜂鸣器并设备蜂鸣器的频率,需要第三个参数arg来表示频率是多少,在命令码为PWM_IOCTL_STOP时,表示关闭蜂鸣器设备,不需要arg参数,那么我们就可以写应用程序来使用驱动提供的api接口来操作蜂鸣器设备了。

应用程序利用九鼎的蜂鸣器驱动操作蜂鸣器设备的代码如下:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>


#define DEVNAME		"/dev/buzzer"

#define PWM_IOCTL_SET_FREQ		1
#define PWM_IOCTL_STOP			0


int main(void)
{
	int fd = -1;
	
	fd = open(DEVNAME, O_RDWR);    //打开蜂鸣器设备驱动文件
	if ( fd < 0 )
	{
		perror("open");
		return -1;
	}
	
	ioctl(fd, PWM_IOCTL_SET_FREQ, 10000);    //打开蜂鸣器。参数:文件描述符,对应驱动中给的命令码,蜂鸣器频率值
	sleep(3);
	ioctl(fd, PWM_IOCTL_STOP);                //关闭蜂鸣器。
	sleep(3);
	ioctl(fd, PWM_IOCTL_SET_FREQ, 3000);
	sleep(3);
	ioctl(fd, PWM_IOCTL_STOP);
	sleep(3);
	
	
	close(fd);
	
	return 0;
}

将应用程序编译运行后,验证确实可以操作蜂鸣器。