转眼间,已到了mini2440厂家友善所提供驱动的最后一课——PWM。本驱动用定时器0产生PWM脉冲信号,GPB0管脚输出接蜂鸣器。贴出代码如下:
#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 <linux/interrupt.h>
#include <linux/gpio.h>
#include <asm/irq.h>
#include <asm/io.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 PWM_IOCTL_SET_FREQ 1 //宏变量,用于ioctl的cmd变量
#define PWM_IOCTL_STOP 0
static struct semaphore lock; //定义信号量 lock
/* freq: pclk/50/16/65536 ~ pclk/50/16
* if pclk = 50MHz, freq is 1Hz to 62500Hz
* human ear : 20Hz~ 20000Hz
*/
static void PWM_Set_Freq( unsigned long freq )
{
unsigned long tcon;
unsigned long tcnt;
unsigned long tcfg1;
unsigned long tcfg0;
struct clk *clk_p;
unsigned long pclk;
s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPB0_TOUT0);
//设置GPB0为PWM输出模式
tcon = __raw_readl(S3C2410_TCON); //读取寄存器TCON
tcfg1 = __raw_readl(S3C2410_TCFG1); //读取寄存取TCFG1
tcfg0 = __raw_readl(S3C2410_TCFG0); //读取寄存器TCFG0
tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; //预分频掩码:TCFG0[0:7]
tcfg0 |= (50 - 1); //设置50分频
tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK; //分割值掩码:TCFG1[0:3]
tcfg1 |= S3C2410_TCFG1_MUX0_DIV16; //定时器0进行16分割
__raw_writel(tcfg1, S3C2410_TCFG1); //把设置后数据存入TCFG1、TCFG0
__raw_writel(tcfg0, S3C2410_TCFG0);
clk_p = clk_get(NULL, "pclk");
pclk = clk_get_rate(clk_p); //得到pclk时钟
tcnt = (pclk/50/16)/freq; //得到定时器初始化数值大小
__raw_writel(tcnt, S3C2410_TCNTB(0)); //tcnt写入到定时器计数寄存器
__raw_writel(tcnt/2, S3C2410_TCMPB(0)); //占空比50%
tcon &= ~0x1f; //disable deadzone, auto-reload, inv-off,
tcon |= 0xb; //update TCNTB0&TCMPB0, start timer 0
__raw_writel(tcon, S3C2410_TCON);
tcon &= ~2; //clear manual update bit
__raw_writel(tcon, S3C2410_TCON);
}
static void PWM_Stop(void)
{ //设置GPB0的为输出模式,并输出低电平,停止PWM工作
s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);
s3c2410_gpio_setpin(S3C2410_GPB(0), 0);
}
static int s3c24xx_pwm_open(struct inode *inode, struct file *file)
{
if (!down_trylock(&lock)) //信号量-1,若成功,down_trylock(&lock)返回0
return 0;
else
return -EBUSY;
}
static int s3c24xx_pwm_close(struct inode *inode, struct file *file)
{
PWM_Stop(); //调用Stop函数,停用PWM
up(&lock); //释放信号量
return 0;
}
static int s3c24xx_pwm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{ //根据不同的cmd,选择相应的操作
switch (cmd)
{
case PWM_IOCTL_SET_FREQ: if (arg == 0)
return -EINVAL;
PWM_Set_Freq(arg);
break;
case PWM_IOCTL_STOP: PWM_Stop();
break;
}
return 0;
}
static struct file_operations dev_fops =
{ //本驱动所提供给上层的funtion
.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); //注册mise设备
printk (DEVICE_NAME"/tinitialized/n");
return ret;
}
static void __exit dev_exit(void)
{
misc_deregister(&misc); //注销mise设备
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FriendlyARM Inc.");
MODULE_DESCRIPTION("S3C2410/S3C2440 PWM Driver");
结语:
通过这一系列驱动的研读和测试,我对Linxu驱动这一块有了更深刻的认识,或许例子比较简单、肤浅,但想必这样更符合初学者的水平,为我们广大的初学者提供了一个很好的切入点。本系列文章不足的地方有很多,个人感觉最不好的就是没有贴出上层应用程序出来与大家分享,看一下上层的应用程序究竟是怎么调用底层驱动所提供的功能。(还是因为自己有点懒~~) 若有需要查看应用程序代码的,随时与鄙人联系,乐意共同研究。
由于本系列的驱动学习是一课紧接一课的,所以在程序的注释上,后面的课程就没有重复前面已经讲解过的,如果有朋友看到不明白的地方,可以翻翻鄙人前面同一个专题的博文,若还没找到满意的答案,欢迎与鄙人取得联系,共同学习进步!