今天我为大家讲解下OK6410关于PWM蜂鸣器驱动的软件和硬件方面的一些知识点。首先我们为了写pwm的驱动程序我们先来了解下它的硬件电路和关于PWM的一些知识点。 在飞凌嵌入式OK6410开发板中,蜂鸣器的IO口为GPF15 关于GPF15的GPIO详细参考S3C6410的PDF手册 GPF14 [29:28] 00 = Input 01 = Output 10 = PWM TOUT[0] 11 = CLKOUT[0]
GPF15 [31:30] 00 = Input 01 = Output 10 = PWM TOUT[1] 11 = Reserved 下面我们重点介绍PWM定时器工作: S3C6410X中有5个定时器,这些定时器产生内部中断。其中,Timer0和Timer1具有PWM功能,而Timer2,3,4没有此功能。 PWM具有两种操作模式:自动装载模式,一次触发模式。为实现PWM功能,芯片提供了16个功能寄存器。这些功能寄存器都连接APB总线。 定时器具有双缓冲特性,这样就能在不停止当前定时器操作的情况下,为下次定时器运行装入新的数值。尽管为定时器设置了新数值,但当前的定时操作能够成功完成。定时器从TCNTBn读取的值是为下次延时定时用的,并不影响当前定时器的运行。当TCNTn减小到0的时候,TCNTBn的值会自动复制到TCNTn中,这就是说的自动装载操作。定时器的当前计数值可以从定时计数观察寄存器中TCNTOn读取。如果TCNTn为0且从装载也为0的话则TCNTn不在进行下次操作。 在S3C6410的PDF手册中介绍PWM的工作流程: The Pulse Width Modulation function (PWM) uses the value of the TCMPBn register. The timer control logic
changes the output level when the down-counter value matches the value of the compare register in the timer
control logic. Therefore, the compare register determines the turn-on time (or turn-off time) of a PWM output.
The TCNTBn and TCMPBn registers are double buffered to allow the timer parameters to be updated in the
middle of a cycle. The new values will not take effect until the current timer cycle completes.
A simple example of a PWM cycle is shown in the figure below. file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps_clip_image-23666.png
1. Initialize the TCNTBn with 159(50+109) and the TCMPBn with 109.
2. Start Timer by setting the start bit and manual update bit off.
The TCNTBn value of 159 is loaded into the down-counter, the output is driven low.
3. When down-counter counts down to the value in the TCMPBn register 109,
the output is changed from low to high
4. When the down-counter reaches 0, the interrupt request is generated.
5. The down-counter is automatically reloaded with TCNTBn, which restarts the cycle. 对于PWM 功能,要用到寄存器TCMPBn,当递减计数器down-counter的值和比较寄存器TCMPBn的值相同时,定时控制逻辑模块就会改变输出电平。因此比较寄存器TCMPBn决定了PWM的输出。 定时器0工作寄存器: file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps_clip_image-2011.png file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps_clip_image-17848.png 在TCFG1 (Timer Configuration Register) 中 file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps_clip_image-7879.png 在 TCON (Timer Control Register)中 file:///C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps_clip_image-8913.png 通过对上述内容的了解现在我们来写下PWM的驱动程序 1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <linux/poll.h> 7 #include <asm/irq.h> 8 #include <asm/io.h> 9 #include <linux/interrupt.h> 10 #include <asm/uaccess.h> 11 #include <mach/hardware.h> 12 #include <plat/regs-timer.h> 13 #include <mach/regs-irq.h> 14 #include <asm/mach/time.h> 15 #include <linux/clk.h> 16 #include <linux/cdev.h> 17 #include <linux/device.h> 18 #include <linux/miscdevice.h> 19 20 #include <mach/map.h> 21 #include <mach/regs-clock.h> 22 #include <mach/regs-gpio.h> 23 24 #include <plat/gpio-cfg.h> 25 #include <mach/gpio-bank-e.h> 26 #include <mach/gpio-bank-f.h> 27 #include <mach/gpio-bank-k.h> 28 29 #define DEVICE_NAME "pwm" 30 31 #define PWM_IOCTL_SET_FREQ 1 32 #define PWM_IOCTL_STOP 0 33 34 static struct semaphore lock; //信号量 35 36 /* freq: pclk/50/16/65536 ~ pclk/50/16 37 * if pclk = 50MHz, freq is 1Hz to 62500Hz 38 * human ear : 20Hz~ 20000Hz 39 */ 40 static void PWM_Set_Freq( unsigned long freq ) 41 { 42 unsigned long tcon; 43 unsigned long tcnt; 44 unsigned long tcfg1; 45 unsigned long tcfg0; 46 47 struct clk *clk_p; 48 unsigned long pclk; 49 50 unsigned tmp; 51 52 tmp = readl(S3C64XX_GPFCON); 53 //tmp &= ~(0x3U << 28); 54 //tmp |= (0x2U << 28); 55 56 tmp &=~(0x3U << 30); //GPF15 [31:30] 10 = PWM TOUT[1] 57 tmp |= (0x2U << 30); 58 59 writel(tmp, S3C64XX_GPFCON); 60 61 tcon = __raw_readl(S3C_TCON); //定时器控制寄存器 62 tcfg1 = __raw_readl(S3C_TCFG1); //时钟多路复用器和 DMA模式的选择 63 tcfg0 = __raw_readl(S3C_TCFG0); //时钟预定标器和死区结构 64 65 //prescaler = 50 Prescaler 0 [7:0] R/W Prescaler 0 value for timer 0 & 1 66 tcfg0 &= ~S3C_TCFG_PRESCALER0_MASK; //{prescaler value} = 1~255 67 tcfg0 |= (50 - 1); //prescaler value=50 68 69 //mux = 1/16 TCFG1 Divider MUX0 [3:0] R/W Select Mux input for PWM Timer 0 70 tcfg1 &= ~S3C_TCFG1_MUX0_MASK; 71 tcfg1 |= S3C_TCFG1_MUX0_DIV16; // 0100: 1/16 72 73 __raw_writel(tcfg1, S3C_TCFG1); 74 __raw_writel(tcfg0, S3C_TCFG0); 75 76 77 /* clk_get获取一个名为id的时针 78 * 输入参数dev: 可以为NULL 79 * 输入参数id: 时针名称,如fclk、hclk、pclk等 80 * 返回值: 返回该时钟的clk结构体 81 * 82 *再将clk_get返回的clk结构体传递给clk_get_rate,获取该时钟的频率 83 */ 84 /*PCLK is used for APB bus, which is used by the peripherals 85 *such as WDT, IIS, I2C, PWM timer, MMC */ 86 87 clk_p = clk_get(NULL, "pclk"); //获取一个名为id的时针 88 pclk = clk_get_rate(clk_p); //获取该时钟的频率 89 //定时器输入时钟频率 90 //Timer input clock Frequency = PCLK / ( {prescaler value + 1} ) / {divider value} 91 tcnt = (pclk/50/16)/freq; 92 93 __raw_writel(tcnt, S3C_TCNTB(0)); //TCNTB0:定时器0计数缓冲器。 94 __raw_writel(tcnt/2, S3C_TCMPB(0));//TCMPB0:定时器0比较缓冲寄存器。 95 96 tcon &= ~0x1f; 97 tcon |= 0xb; //disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0 98 __raw_writel(tcon, S3C_TCON); 99 100 /*Note: Manual update bit must be 1’b0 before Start/Stop bit is 1’b1. 101 If Manual update bit is 1’b1 and Start/Stop bit is 1’b1, 102 timer counter is not update by new value. 103 Timer counter value is last value. */ 104 105 tcon &= ~2; //clear manual update bit 106 __raw_writel(tcon, S3C_TCON); 107 } 108 109 void PWM_Stop( void ) 110 { 111 unsigned tmp; 112 tmp = readl(S3C64XX_GPFCON); 113 //tmp &= ~(0x3U << 28); 114 tmp &= ~(0x3U << 30); 115 116 writel(tmp, S3C64XX_GPFCON); 117 } 118 119 /* 该函数尝试获得信号量sem,如果能够立刻获得, 120 它就获得该信号量并返回0,否则,返回非0值。 121 它不会导致调用者睡眠,可以在中断上下文使用。 122 123 */ 124 static int s3c64xx_pwm_open(struct inode *inode, struct file *file) 125 { 126 if (!down_trylock(&lock)) 127 return 0; 128 else 129 return -EBUSY; 130 } 131 132 133 static int s3c64xx_pwm_close(struct inode *inode, struct file *file) 134 { 135 up(&lock); //该函数释放信号量sem,唤醒等待者 136 return 0; 137 } 138 139 140 static long s3c64xx_pwm_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) 141 { 142 switch (cmd) { 143 case PWM_IOCTL_SET_FREQ: 144 if (arg == 0) 145 return -EINVAL; 146 PWM_Set_Freq(arg); 147 break; 148 149 case PWM_IOCTL_STOP: 150 default: 151 PWM_Stop(); 152 break; 153 } 154 155 return 0; 156 } 157 158 159 static struct file_operations dev_fops = { 160 .owner = THIS_MODULE, 161 .open = s3c64xx_pwm_open, 162 .release = s3c64xx_pwm_close, 163 .unlocked_ioctl = s3c64xx_pwm_ioctl, 164 }; 165 166 static struct miscdevice misc = { 167 .minor = MISC_DYNAMIC_MINOR, 168 .name = DEVICE_NAME, 169 .fops = &dev_fops, 170 }; 171 172 static int __init dev_init(void) 173 { 174 int ret; 175 176 /* 该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1, 177 等同于sema_init (struct semaphore *sem, 1)*/ 178 init_MUTEX(&lock); 179 ret = misc_register(&misc); 180 181 printk (DEVICE_NAME"\tinitialized\n"); 182 return ret; 183 } 184 185 static void __exit dev_exit(void) 186 { 187 misc_deregister(&misc); 188 } 189 190 module_init(dev_init); 191 module_exit(dev_exit); 192 MODULE_LICENSE("GPL"); 193 MODULE_AUTHOR("FORLINX Inc."); 194 MODULE_DESCRIPTION("S3C6410 PWM Driver");
|