要点:
1.本驱动基于信号量的并发控制。相关的API:
static struct semaphore lock;
void init_MUTEX(&lock); //初始化信号量
int down_trylock(&lock);尝试获取信号量,如果有效则立即获得,同时返回0,否则返回非0,此函数不会导致休眠,可以用在中断上下
void up(&lock) //释放信号量
2.蜂蜜器采用的是GPB0口,在设置频率的时候注意要将该口设置成TOUT0模式,
s3c2410_gpio_cfgpin(S3C2410_GPB0,S3C2410_GPB0_TOUT0);
在static void pwm_stop()中只需要将GPB0设置成低电平,所以要配置成输出
s3c2410_gpio_cfgpin(S3C2410_GPB0,S3C2410_GPB0_OUTP);
3.在linux的驱动中不要直接去操作寄存器地址,系统提供了API:
__raw_readl(寄存器)//
tcon = __raw_readl(S3C2410_TCON);
__raw_writel (value,寄存器) //写value 到寄存器中
__raw_writel(tcon, S3C2410_TCON);
下面是驱动程序:
#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/regs-gpio.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> #define DEVICE_NAME "pwm" #define SET_FREQ 1 //设置频率 #define STOP 2 //停止 #define START 3 //开始 static struct semaphore lock; //定义一个信号量 static void pwm_stop() { s3c2410_gpio_cfgpin(S3C2410_GPB0,S3C2410_GPB0_OUTP); s3c2410_gpio_setpin(S3C2410_GPB0,0); //低电平,停止 } static void pwm_set_freq(unsigned long freq) { unsigned long tcon; unsigned long tcfg0; unsigned long tcnt; unsigned long tcfg1; struct clk *clk_p; unsigned long pclk; s3c2410_gpio_cfgpin(S3C2410_GPB0,S3C2410_GPB0_TOUT0); tcon = __raw_readl(S3C2410_TCON); tcfg0 = __raw_readl(S3C2410_TCFG0); tcfg1 = __raw_readl(S3C2410_TCFG1); tcfg0 &=~S3C2410_TCFG_PRESCALER0_MASK; tcfg0 |=49; //mux = 1/16 tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK; tcfg1 |= S3C2410_TCFG1_MUX0_DIV16; __raw_writel(tcfg1, S3C2410_TCFG1); //重新写回寄存器 __raw_writel(tcfg0, S3C2410_TCFG0); clk_p = clk_get(NULL, "pclk"); pclk = clk_get_rate(clk_p); tcnt = (pclk/50/16)/freq; __raw_writel(tcnt, S3C2410_TCNTB(0)); __raw_writel(tcnt/2, S3C2410_TCMPB(0)); tcon &=~0x1f; tcon |= (1<<3) |(1<<1) |(1<<0) ; //printk("tcon =%x\n",tcon); //tcon |= 0xb; __raw_writel(tcon, S3C2410_TCON); tcon &= ~2; //清除手动更新,注意这个时必须的见NOTE __raw_writel(tcon, S3C2410_TCON); } static int s3c24xx_pwm_open(struct inode *inode, struct file *file) { if(!down_trylock(&lock)) //获取信号量成功则返回0 return 0; else return -EBUSY; } static void s3c24xx_pwm_close(struct inode *inode, struct file *file) { pwm_stop(); up(&lock); } static int s3c24xx_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { switch(cmd) { case SET_FREQ: if(arg==0) //如果参数为0,表示传递的参数不正确 return -EINVAL; pwm_set_freq(arg); //设置频率 break; case STOP: pwm_stop(); break; } return 0; } static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = s3c24xx_pwm_open, .release = s3c24xx_pwm_close, .ioctl = s3c24xx_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); printk(DEVICE_NAME "\tinitialized\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("Huang renjun"
测试程序:
include <stdio.h> #include <termios.h> #include <unistd.h> #include <stdlib.h> #define PWM_IOCTL_SET_FREQ 1 #define PWM_IOCTL_STOP 2 #define ESC_KEY 0x1b static int getch(void) { struct termios oldt,newt; int ch; if (!isatty(STDIN_FILENO)) { fprintf(stderr, "this problem should be run at a terminal\n"); exit(1); } // save terminal setting if(tcgetattr(STDIN_FILENO, &oldt) < 0) { perror("save the terminal setting"); exit(1); } // set terminal as need newt = oldt; newt.c_lflag &= ~( ICANON | ECHO ); if(tcsetattr(STDIN_FILENO,TCSANOW, &newt) < 0) { perror("set terminal"); exit(1); } ch = getchar(); // restore termial setting if(tcsetattr(STDIN_FILENO,TCSANOW,&oldt) < 0) { perror("restore the termial setting"); exit(1); } return ch; } static int fd = -1; static void close_buzzer(void); static void open_buzzer(void) { fd = open("/dev/pwm", 0); if (fd < 0) { perror("open pwm_buzzer device"); exit(1); } // any function exit call will stop the buzzer atexit(close_buzzer); } static void close_buzzer(void) { if (fd >= 0) { ioctl(fd, PWM_IOCTL_STOP); close(fd); fd = -1; } } static void set_buzzer_freq(int freq) { // this IOCTL command is the key to set frequency int ret = ioctl(fd, PWM_IOCTL_SET_FREQ, freq); if(ret < 0) { perror("set the frequency of the buzzer"); exit(1); } } static void stop_buzzer(void) { int ret = ioctl(fd, PWM_IOCTL_STOP); if(ret < 0) { perror("stop the buzzer"); exit(1); } } int main(int argc, char **argv) { int freq = 1000 ; open_buzzer(); printf( "\nBUZZER TEST ( PWM Control )\n" ); printf( "Press +/- to increase/reduce the frequency of the BUZZER\n" ) ; printf( "Press 'ESC' key to Exit this program\n\n" ); while( 1 ) { int key; set_buzzer_freq(freq); printf( "\tFreq = %d\n", freq ); key = getch(); printf( "\tkey = %c\n", key ); switch(key) { case '=': if( freq < 20000 ) freq += 10; break; case '-': if( freq > 11 ) freq -= 10 ; break; case ESC_KEY: case EOF: stop_buzzer(); exit(0); default: break; } } }