字符设备驱动用的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;
}
将应用程序编译运行后,验证确实可以操作蜂鸣器。
转载于:https://blog.51cto.com/whylinux/1932491