本文转载自:http://whylinux.blog.51cto.com/10900429/1932491
字符设备驱动用的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文件中
1
|
obj-$(CONFIG_BUZZER_DRIVER) += x210-buzzer.o
|
CONFIG_BUZZER_DRIVER宏是否定义了,这个宏是否定义就要取决于这个目录下的kconfig文件中
1
2
3
4
5
|
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这个设备驱动文件节点。此时就可以正常使用这个设备文件来操作蜂鸣器驱动了。这个时候我们就可以写应用程序控制蜂鸣器,来验证蜂鸣器驱动是否好用了。要想写应用程序,就要知道驱动是怎么实现的,应用程序和驱动是配套的,驱动中提供了哪些应用程序来操作蜂鸣器的接口程序,所以此时我们要看下蜂鸣器驱动代码是怎么实现的,从而知道驱动提供了哪些接口函数给应用层。蜂鸣器的驱动代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
#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接口来操作蜂鸣器设备了。
应用程序利用九鼎的蜂鸣器驱动操作蜂鸣器设备的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#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;
}
|
将应用程序编译运行后,验证确实可以操作蜂鸣器。