结合之前对Linux内核的misc类设备驱动框架的分析 ,本文将编写基于misc类设备驱动框架的Buzzer设备的实例代码并对其进行分析。
misc类设备驱动框架的分析,详见Linux字符设备驱动框架(三):Linux内核的misc类设备驱动框架。
硬件接口:
CPU:s5pv210;
Buzzer的GPIO:GPIO_D0_2 ;
LED的工作方式:低电平停,高电平响。
P.S.:无源蜂鸣器声音频率可控,一般采用PWM控制;有源蜂鸣器用普通IO控制方式即可。
1. 驱动程序分析
本代码取自,九鼎科技x210开发板的BSP。
#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> #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; //设置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: "); if (arg == 0) return -EINVAL; PWM_Set_Freq(arg); break; case PWM_IOCTL_STOP: default: printk("PWM_IOCTL_STOP: "); PWM_Stop(); break; } return 0; } //buzzer设备操作集 static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = x210_pwm_open, .release = x210_pwm_close, .ioctl = x210_pwm_ioctl, }; //misc类设备信息初始化 static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; //buzzer设备注册 static int __init dev_init(void) { int ret; init_MUTEX(&lock); //上锁 ret = misc_register(&misc); //将buzzer注册到misc类下 /* GPD0_2 (PWMTOUT2) */ ret =
gpio_request(S5PV210_GPD0(2), "GPD0"); //申请GPIO if(ret) printk("buzzer-x210: request gpio GPD0(2) fail"); s3c_gpio_setpull(S5PV210_GPD0(2), S3C_GPIO_PULL_UP);//将GPIO_D0_2设置为上拉 s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(1)); //将GPIO_D0_2设置为输出模式 gpio_set_value(S5PV210_GPD0(2), 0); //将GPIO_D0_2设置为低电平 printk ("x210 "DEVICE_NAME" initialized "); return ret; } //buzzer设备注销 static void __exit dev_exit(void) { init_MUTEX_LOCKED(&lock); //解锁 gpio_free(S5PV210_GPD0(2));//释放GPIO misc_deregister(&misc); //注销buzzer设备 } module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("www.9tripod.com"); MODULE_DESCRIPTION("x210 PWM Driver");
2. 测试
Buzzer驱动模块被装载进内核之后,可运行如下应用程序测试该驱动是否工作正常。若运行应用程序之后,蜂鸣器连响3声,则说明驱动工作正常。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <sys/ioctl.h> #define FILE_BUZZER "/dev/buzzer" #define PWM_IOCTL_SET_FREQ 1 #define PWM_IOCTL_STOP 0 int main(void) { int fd = -1; int i = 0; fd = open(FILE_BUZZER, O_RDWR); if (fd < 0) { printf("open buzzer file error. "); return -1; } for (i=0; i<3; i++) { ioctl(fd, PWM_IOCTL_SET_FREQ, 10000); sleep(3); ioctl(fd, PWM_IOCTL_STOP); sleep(3); } return 0; }