字符设备驱动用的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; }
将应用程序编译运行后,验证确实可以操作蜂鸣器。